Fix crash when requesting an alias without target.
[vomak.git] / vomak.c
blobf408ed9096ad4c71b32ba55a37e8b5d52b7f3499
1 /*
2 * vomak.c - this file is part of vomak - a very simple IRC bot
4 * Copyright 2008 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2008 Dominic Hopf <dh(at)dmaphy(dot)de>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; version 2 of the License.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 #define _GNU_SOURCE
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <errno.h>
28 #include <netdb.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <netinet/in.h>
32 #include <sys/socket.h>
33 #include <unistd.h>
34 #include <string.h>
35 #include <signal.h>
36 #ifndef DEBUG
37 # include <syslog.h>
38 #endif
40 #include <glib.h>
41 #include <glib/gstdio.h>
43 #include "vomak.h"
44 #include "socket.h"
45 #include "irc.h"
47 #define CONFIG_SECTION_GENERAL "general"
48 #define CONFIG_SECTION_DATABASE "general"
51 static irc_conn_t irc_conn;
52 static socket_info_t socket_info;
53 static GMainLoop *main_loop = NULL;
54 static gchar *userlist = NULL;
55 static GList *words = NULL;
56 config_t *config;
58 static void load_database_real(GKeyFile *keyfile)
60 gsize j, len_keys = 0;
61 gchar **keys;
63 TRACE
65 // unload previously loaded data, aka reload
66 if (config->data != NULL)
68 g_hash_table_destroy(config->data);
71 config->data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
73 keys = g_key_file_get_keys(keyfile, CONFIG_SECTION_DATABASE, &len_keys, NULL);
74 for (j = 0; j < len_keys; j++)
76 g_hash_table_insert(config->data, g_strdup(keys[j]),
77 g_key_file_get_string(keyfile, CONFIG_SECTION_DATABASE, keys[j], NULL));
79 g_strfreev(keys);
83 static void load_database()
85 gchar *config_name = g_build_filename(getenv("HOME"), ".vomak", "database", NULL);
86 GKeyFile *keyfile = g_key_file_new();
88 TRACE
90 g_key_file_load_from_file(keyfile, config_name, G_KEY_FILE_NONE, NULL);
92 load_database_real(keyfile);
94 g_key_file_free(keyfile);
95 g_free(config_name);
99 static void signal_cb(gint sig)
101 if (sig == SIGTERM || sig == SIGINT)
103 gchar *signame;
105 switch (sig)
107 case SIGTERM: signame = "SIGTERM"; break;
108 case SIGINT: signame = "SIGINT"; break;
109 default: signame = "unknown"; break;
111 debug("Received %s, cleaning up", signame);
113 irc_goodbye(&irc_conn);
115 main_quit();
120 void main_quit()
122 #ifndef DEBUG
123 closelog();
124 #endif
125 irc_finalize(&irc_conn);
126 socket_finalize(&socket_info);
128 g_main_loop_quit(main_loop);
133 static gboolean socket_input_cb(GIOChannel *ioc, GIOCondition cond, gpointer data)
135 gint fd, sock;
136 static gchar buf[1024];
137 static gchar msg[1024];
138 struct sockaddr_in caddr;
139 guint caddr_len, len, msg_len;
141 caddr_len = sizeof(caddr);
143 fd = g_io_channel_unix_get_fd(ioc);
144 sock = accept(fd, (struct sockaddr *)&caddr, &caddr_len);
146 TRACE
147 while ((len = socket_fd_gets(sock, buf, sizeof(buf))) != -1)
149 if (strncmp("quit", buf, 4) == 0)
151 signal_cb(SIGTERM);
153 else if (strncmp("reload", buf, 6) == 0)
155 load_database();
157 else if (strncmp("userlist", buf, 8) == 0)
159 if (userlist)
161 send(sock, userlist, strlen(userlist), 0);
162 send(sock, "\n", 1, 0);
164 else
165 send(sock, "error\n", 6, 0);
167 else
169 msg_len = g_snprintf(msg, sizeof msg, "%s\r\n", buf);
170 send(irc_conn.socket, msg, msg_len, 0);
171 vdebug(msg);
174 socket_fd_close(sock);
176 return TRUE;
180 static void hash_add_to_list(gpointer key, gpointer value, gpointer user_data)
182 if (key != NULL)
184 words = g_list_insert_sorted(words, key, (GCompareFunc) strcmp);
189 static gint write_file(const gchar *filename, const gchar *text)
191 FILE *fp;
192 gint bytes_written, len;
194 if (filename == NULL)
196 return ENOENT;
199 len = strlen(text);
201 fp = g_fopen(filename, "w");
202 if (fp != NULL)
204 bytes_written = fwrite(text, sizeof (gchar), len, fp);
205 fclose(fp);
207 if (len != bytes_written)
209 debug("written only %d bytes, had to write %d bytes to %s", bytes_written, len, filename);
210 return EIO;
213 else
215 debug("could not write to file %s (%s)", filename, g_strerror(errno));
216 return errno;
218 return 0;
222 void help_system_query(const gchar *line, guint len)
224 const gchar *msg = irc_get_message(line, len);
225 gchar *result;
227 if (strncmp("?? ", msg, 3) != 0)
228 return;
230 msg += 3;
232 if (strncmp("keywords", msg, 8) == 0)
234 GList *item;
235 GString *str = g_string_sized_new(256);
237 words = NULL;
239 g_hash_table_foreach(config->data, hash_add_to_list, NULL);
241 for (item = words; item != NULL; item = g_list_next(item))
243 g_string_append(str, item->data);
244 g_string_append_c(str, ' ');
247 irc_send_message(&irc_conn, NULL, "%s: %s", msg, str->str);
248 g_list_free(words);
249 g_string_free(str, TRUE);
251 else
253 result = g_hash_table_lookup(config->data, msg);
256 * Look for the @-Char in the result string. If one is found, most
257 * likely the !alias-command was used and this has to be resolved
258 * recursively until no @ appears anymore in the string.
260 while (result && result[0] == '@')
261 result = g_hash_table_lookup(config->data, result + 1);
263 if (result)
264 irc_send_message(&irc_conn, NULL, "%s: %s", msg, result);
269 // return value:
270 // 0 - added
271 // 1 - updated
272 // -1 - error
273 gint help_system_learn(const gchar *keyword, const gchar *text)
275 gchar *config_name;
276 gchar *data;
277 gchar *key;
278 gint ret = 0;
279 GKeyFile *keyfile;
281 TRACE
283 if (keyword == NULL || text == NULL)
284 return -1;
286 config_name = g_build_filename(getenv("HOME"), ".vomak", "database", NULL);
287 keyfile = g_key_file_new();
289 g_key_file_load_from_file(keyfile, config_name, G_KEY_FILE_NONE, NULL);
291 key = g_hash_table_lookup(config->data, keyword);
292 if (key != NULL)
293 ret = 1; // if key is non-NULL it is already available and gets updated
295 g_key_file_set_string(keyfile, CONFIG_SECTION_DATABASE, keyword, text);
297 data = g_key_file_to_data(keyfile, NULL, NULL);
298 write_file(config_name, data);
299 g_free(data);
301 load_database_real(keyfile);
303 g_key_file_free(keyfile);
304 g_free(config_name);
306 return ret;
310 static void config_free()
312 TRACE
313 g_free(config->socketname);
314 g_free(config->socketperm);
315 g_free(config->server);
316 g_free(config->channel);
317 g_free(config->servername);
318 g_free(config->nickname);
319 g_free(config->username);
320 g_free(config->realname);
321 g_free(config->nickserv_password);
322 //~ g_hash_table_destroy(config->data);
323 g_free(config);
324 config = NULL;
328 static config_t *config_read()
330 GKeyFile *keyfile = g_key_file_new();
331 gchar *config_name = g_build_filename(getenv("HOME"), ".vomak", "config", NULL);
332 config_t *config = g_new0(config_t, 1);
334 TRACE
336 g_key_file_load_from_file(keyfile, config_name, G_KEY_FILE_NONE, NULL);
338 config->socketname = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "socketname", NULL);
339 config->socketperm = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "socketperm", NULL);
340 config->server = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "server", NULL);
341 config->port = g_key_file_get_integer(keyfile, CONFIG_SECTION_GENERAL, "port", NULL);
342 config->channel = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "channel", NULL);
343 config->servername = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "servername", NULL);
344 config->nickname = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "nickname", NULL);
345 config->username = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "username", NULL);
346 config->realname = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "realname", NULL);
347 config->nickserv_password = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "nickserv_password", NULL);
349 g_key_file_free(keyfile);
350 g_free(config_name);
352 // if anything could not be read, abort. We don't use default values
353 if (config->socketname == NULL ||
354 config->socketperm == NULL ||
355 config->server == NULL ||
356 config->channel == NULL ||
357 config->servername == NULL ||
358 config->nickname == NULL ||
359 config->username == NULL ||
360 config->realname == NULL)
362 fprintf(stderr, "Config file does not exist or is not complete.\n");
363 exit(1);
366 return config;
370 static void init_socket()
372 TRACE
374 /* create unix domain socket for remote operation */
375 socket_info.lock_socket = -1;
376 socket_info.lock_socket_tag = 0;
377 socket_init(&socket_info, config->socketname);
379 if (socket_info.lock_socket >= 0)
381 mode_t mode;
382 sscanf(config->socketperm, "%o", &mode);
383 /// FIXME for some reason the sticky bit is set when socketperm is "0640"
384 chmod(config->socketname, mode);
385 socket_info.read_ioc = g_io_channel_unix_new(socket_info.lock_socket);
386 g_io_channel_set_flags(socket_info.read_ioc, G_IO_FLAG_NONBLOCK, NULL);
387 socket_info.lock_socket_tag = g_io_add_watch(socket_info.read_ioc,
388 G_IO_IN|G_IO_PRI|G_IO_ERR, socket_input_cb, NULL);
393 static gboolean mark_connect(gpointer data)
395 TRACE
397 // delay the internal connected status to skip parsing of MOTD messages
398 irc_conn.connected = TRUE;
400 return FALSE;
404 void set_user_list(const gchar *list)
406 g_free(userlist);
407 userlist = g_strdup(list);
411 gint main(gint argc, gchar **argv)
413 irc_conn.connected = FALSE;
415 // init stuff
416 main_loop = g_main_loop_new(NULL, FALSE);
417 config = config_read();
418 load_database();
419 init_socket();
420 irc_connect(&irc_conn);
421 signal(SIGTERM, signal_cb);
422 signal(SIGINT, signal_cb);
424 g_timeout_add(15000, mark_connect, NULL);
425 g_timeout_add(180000, irc_query_names, &irc_conn);
427 // if not a debug build, deattach from console and go into background
428 #ifndef DEBUG
429 signal(SIGTTOU, SIG_IGN);
430 signal(SIGTTIN, SIG_IGN);
431 signal(SIGTSTP, SIG_IGN);
432 openlog("vomak", LOG_PID, LOG_DAEMON);
434 if (daemon(1, 0) < 0)
436 g_printerr("Unable to daemonize\n");
437 return -1;
439 #endif
441 // main loop
442 g_main_loop_run(main_loop);
444 config_free();
446 return 0;