Implement logging error messages from the server to syslog.
[vomak.git] / vomak.c
blob36109d5a7b629fd00eb83ab35a57721ecb40bd9a
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>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 #define _GNU_SOURCE
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <errno.h>
27 #include <netdb.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <netinet/in.h>
31 #include <sys/socket.h>
32 #include <unistd.h>
33 #include <string.h>
34 #include <signal.h>
35 #ifndef DEBUG
36 # include <syslog.h>
37 #endif
39 #include <glib.h>
40 #include <glib/gstdio.h>
42 #include "vomak.h"
43 #include "socket.h"
44 #include "irc.h"
46 #define CONFIG_SECTION_GENERAL "general"
47 #define CONFIG_SECTION_DATABASE "general"
50 static irc_conn_t irc_conn;
51 static socket_info_t socket_info;
52 static GMainLoop *main_loop = NULL;
53 static gchar *userlist = NULL;
54 static GList *words = NULL;
55 config_t *config;
59 static void load_database_real(GKeyFile *keyfile)
61 gsize j, len_keys = 0;
62 gchar **keys;
64 TRACE
66 // unload previously loaded data, aka reload
67 if (config->data != NULL)
69 g_hash_table_destroy(config->data);
72 config->data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
74 keys = g_key_file_get_keys(keyfile, CONFIG_SECTION_DATABASE, &len_keys, NULL);
75 for (j = 0; j < len_keys; j++)
77 g_hash_table_insert(config->data, g_strdup(keys[j]),
78 g_key_file_get_string(keyfile, CONFIG_SECTION_DATABASE, keys[j], NULL));
80 g_strfreev(keys);
84 static void load_database()
86 gchar *config_name = g_build_filename(getenv("HOME"), ".vomak", "database", NULL);
87 GKeyFile *keyfile = g_key_file_new();
89 TRACE
91 g_key_file_load_from_file(keyfile, config_name, G_KEY_FILE_NONE, NULL);
93 load_database_real(keyfile);
95 g_key_file_free(keyfile);
96 g_free(config_name);
100 static void signal_cb(gint sig)
102 if (sig == SIGTERM || sig == SIGINT)
104 gchar *signame;
106 switch (sig)
108 case SIGTERM: signame = "SIGTERM"; break;
109 case SIGINT: signame = "SIGINT"; break;
110 default: signame = "unknown"; break;
112 debug("Received %s, cleaning up", signame);
114 irc_goodbye(&irc_conn);
116 main_quit();
121 void main_quit()
123 #ifndef DEBUG
124 closelog();
125 #endif
126 irc_finalize(&irc_conn);
127 socket_finalize(&socket_info);
129 g_main_loop_quit(main_loop);
134 static gboolean socket_input_cb(GIOChannel *ioc, GIOCondition cond, gpointer data)
136 gint fd, sock;
137 static gchar buf[1024];
138 static gchar msg[1024];
139 struct sockaddr_in caddr;
140 guint caddr_len, len, msg_len;
142 caddr_len = sizeof(caddr);
144 fd = g_io_channel_unix_get_fd(ioc);
145 sock = accept(fd, (struct sockaddr *)&caddr, &caddr_len);
147 TRACE
148 while ((len = socket_fd_gets(sock, buf, sizeof(buf))) != -1)
150 if (strncmp("quit", buf, 4) == 0)
152 signal_cb(SIGTERM);
154 else if (strncmp("reload", buf, 6) == 0)
156 load_database();
158 else if (strncmp("userlist", buf, 8) == 0)
160 if (userlist)
162 send(sock, userlist, strlen(userlist), 0);
163 send(sock, "\n", 1, 0);
165 else
166 send(sock, "error\n", 6, 0);
168 else
170 msg_len = g_snprintf(msg, sizeof msg, "%s\r\n", buf);
171 send(irc_conn.socket, msg, msg_len, 0);
172 vdebug(msg);
175 socket_fd_close(sock);
177 return TRUE;
181 static void hash_add_to_list(gpointer key, gpointer value, gpointer user_data)
183 if (key != NULL)
185 words = g_list_insert_sorted(words, key, (GCompareFunc) strcmp);
190 static gint write_file(const gchar *filename, const gchar *text)
192 FILE *fp;
193 gint bytes_written, len;
195 if (filename == NULL)
197 return ENOENT;
200 len = strlen(text);
202 fp = g_fopen(filename, "w");
203 if (fp != NULL)
205 bytes_written = fwrite(text, sizeof (gchar), len, fp);
206 fclose(fp);
208 if (len != bytes_written)
210 debug("written only %d bytes, had to write %d bytes to %s", bytes_written, len, filename);
211 return EIO;
214 else
216 debug("could not write to file %s (%s)", filename, g_strerror(errno));
217 return errno;
219 return 0;
223 void help_system_query(const gchar *line, guint len)
225 gchar *msg = irc_get_message(line, len);
226 gchar *result;
228 if (strncmp("?? ", msg, 3) != 0)
229 return;
231 msg += 3;
233 if (strncmp("keywords", msg, 8) == 0)
235 GList *item;
236 GString *str = g_string_sized_new(256);
238 words = NULL;
240 g_hash_table_foreach(config->data, hash_add_to_list, NULL);
242 for (item = words; item != NULL; item = g_list_next(item))
244 g_string_append(str, item->data);
245 g_string_append_c(str, ' ');
248 irc_send_message(&irc_conn, NULL, "%s: %s", msg, str->str);
249 g_list_free(words);
250 g_string_free(str, TRUE);
252 else
254 result = g_hash_table_lookup(config->data, msg);
255 if (result)
256 irc_send_message(&irc_conn, NULL, "%s: %s", msg, result);
261 // return value:
262 // 0 - added
263 // 1 - updated
264 // -1 - error
265 gint help_system_learn(const gchar *keyword, const gchar *text)
267 gchar *config_name;
268 gchar *data;
269 gchar *key;
270 gint ret = 0;
271 GKeyFile *keyfile;
273 TRACE
275 if (keyword == NULL || text == NULL)
276 return -1;
278 config_name = g_build_filename(getenv("HOME"), ".vomak", "database", NULL);
279 keyfile = g_key_file_new();
281 g_key_file_load_from_file(keyfile, config_name, G_KEY_FILE_NONE, NULL);
283 key = g_hash_table_lookup(config->data, keyword);
284 if (key != NULL)
285 ret = 1; // if key is non-NULL it is already available and gets updated
287 g_key_file_set_string(keyfile, CONFIG_SECTION_DATABASE, keyword, text);
289 data = g_key_file_to_data(keyfile, NULL, NULL);
290 write_file(config_name, data);
291 g_free(data);
293 load_database_real(keyfile);
295 g_key_file_free(keyfile);
296 g_free(config_name);
298 return ret;
302 static void config_free()
304 TRACE
305 g_free(config->socketname);
306 g_free(config->socketperm);
307 g_free(config->server);
308 g_free(config->channel);
309 g_free(config->servername);
310 g_free(config->nickname);
311 g_free(config->username);
312 g_free(config->realname);
313 g_free(config->nickserv_password);
314 //~ g_hash_table_destroy(config->data);
315 g_free(config);
316 config = NULL;
320 static config_t *config_read()
322 GKeyFile *keyfile = g_key_file_new();
323 gchar *config_name = g_build_filename(getenv("HOME"), ".vomak", "config", NULL);
324 config_t *config = g_new0(config_t, 1);
326 TRACE
328 g_key_file_load_from_file(keyfile, config_name, G_KEY_FILE_NONE, NULL);
330 config->socketname = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "socketname", NULL);
331 config->socketperm = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "socketperm", NULL);
332 config->server = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "server", NULL);
333 config->port = g_key_file_get_integer(keyfile, CONFIG_SECTION_GENERAL, "port", NULL);
334 config->channel = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "channel", NULL);
335 config->servername = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "servername", NULL);
336 config->nickname = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "nickname", NULL);
337 config->username = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "username", NULL);
338 config->realname = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "realname", NULL);
339 config->nickserv_password = g_key_file_get_string(keyfile, CONFIG_SECTION_GENERAL, "nickserv_password", NULL);
341 g_key_file_free(keyfile);
342 g_free(config_name);
344 // if anything could not be read, abort. We don't use default values
345 if (config->socketname == NULL ||
346 config->socketperm == NULL ||
347 config->server == NULL ||
348 config->channel == NULL ||
349 config->servername == NULL ||
350 config->nickname == NULL ||
351 config->username == NULL ||
352 config->realname == NULL)
354 fprintf(stderr, "Config file does not exist or is not complete.\n");
355 exit(1);
358 return config;
362 static void init_socket()
364 TRACE
366 /* create unix domain socket for remote operation */
367 socket_info.lock_socket = -1;
368 socket_info.lock_socket_tag = 0;
369 socket_init(&socket_info, config->socketname);
371 if (socket_info.lock_socket >= 0)
373 mode_t mode;
374 sscanf(config->socketperm, "%o", &mode);
375 /// FIXME for some reason the sticky bit is set when socketperm is "0640"
376 chmod(config->socketname, mode);
377 socket_info.read_ioc = g_io_channel_unix_new(socket_info.lock_socket);
378 g_io_channel_set_flags(socket_info.read_ioc, G_IO_FLAG_NONBLOCK, NULL);
379 socket_info.lock_socket_tag = g_io_add_watch(socket_info.read_ioc,
380 G_IO_IN|G_IO_PRI|G_IO_ERR, socket_input_cb, NULL);
385 static gboolean mark_connect(gpointer data)
387 TRACE
389 // delay the internal connected status to skip parsing of MOTD messages
390 irc_conn.connected = TRUE;
392 return FALSE;
396 void set_user_list(const gchar *list)
398 g_free(userlist);
399 userlist = g_strdup(list);
403 gint main(gint argc, gchar **argv)
405 irc_conn.connected = FALSE;
407 // init stuff
408 main_loop = g_main_loop_new(NULL, FALSE);
409 config = config_read();
410 load_database();
411 init_socket();
412 irc_connect(&irc_conn);
413 signal(SIGTERM, signal_cb);
414 signal(SIGINT, signal_cb);
416 g_timeout_add(15000, mark_connect, NULL);
417 g_timeout_add(180000, irc_query_names, &irc_conn);
419 // if not a debug build, deattach from console and go into background
420 #ifndef DEBUG
421 signal(SIGTTOU, SIG_IGN);
422 signal(SIGTTIN, SIG_IGN);
423 signal(SIGTSTP, SIG_IGN);
424 openlog("vomak", LOG_PID, LOG_DAEMON);
426 if (daemon(1, 0) < 0)
428 g_printerr("Unable to daemonize\n");
429 return -1;
431 #endif
433 // main loop
434 g_main_loop_run(main_loop);
436 config_free();
438 return 0;