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.
29 #include <sys/types.h>
31 #include <netinet/in.h>
32 #include <sys/socket.h>
42 #include <glib/gstdio.h>
48 #define CONFIG_SECTION_GENERAL "general"
49 #define CONFIG_SECTION_IRC "irc"
50 #define CONFIG_SECTION_EXTERNALS "externals"
51 #define CONFIG_SECTION_DATABASE "general"
54 static irc_conn_t irc_conn
;
55 static socket_info_t socket_info
;
56 static GMainLoop
*main_loop
= NULL
;
57 static gchar
*userlist
= NULL
;
58 static GList
*words
= NULL
;
61 static void load_database_real(GKeyFile
*keyfile
)
63 gsize j
, len_keys
= 0;
68 // unload previously loaded data, aka reload
69 if (config
->data
!= NULL
)
71 g_hash_table_destroy(config
->data
);
74 config
->data
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
76 keys
= g_key_file_get_keys(keyfile
, CONFIG_SECTION_DATABASE
, &len_keys
, NULL
);
77 for (j
= 0; j
< len_keys
; j
++)
79 g_hash_table_insert(config
->data
, g_utf8_strdown(keys
[j
], -1),
80 g_key_file_get_string(keyfile
, CONFIG_SECTION_DATABASE
, keys
[j
], NULL
));
86 static void load_database()
88 gchar
*config_name
= g_build_filename(getenv("HOME"), ".vomak", "database", NULL
);
89 GKeyFile
*keyfile
= g_key_file_new();
93 g_key_file_load_from_file(keyfile
, config_name
, G_KEY_FILE_NONE
, NULL
);
95 load_database_real(keyfile
);
97 g_key_file_free(keyfile
);
102 static void signal_cb(gint sig
)
104 if (sig
== SIGTERM
|| sig
== SIGINT
)
110 case SIGTERM
: signame
= "SIGTERM"; break;
111 case SIGINT
: signame
= "SIGINT"; break;
112 case SIGPIPE
: signame
= "SIGPIPE"; break;
113 default: signame
= "unknown"; break;
115 debug("Received %s, cleaning up", signame
);
117 irc_goodbye(&irc_conn
);
122 irc_logfile_reopen(&irc_conn
);
131 irc_finalize(&irc_conn
);
132 socket_finalize(&socket_info
);
134 g_main_loop_quit(main_loop
);
139 static gboolean
socket_input_cb(GIOChannel
*ioc
, GIOCondition cond
, gpointer data
)
142 static gchar buf
[1024];
143 static gchar msg
[1024];
144 struct sockaddr_in caddr
;
145 guint caddr_len
, len
, msg_len
;
147 caddr_len
= sizeof(caddr
);
149 fd
= g_io_channel_unix_get_fd(ioc
);
150 sock
= accept(fd
, (struct sockaddr
*)&caddr
, &caddr_len
);
153 while ((len
= socket_fd_gets(sock
, buf
, sizeof(buf
))) != -1)
155 if (strncmp("quit", buf
, 4) == 0)
157 g_free(irc_conn
.quit_msg
);
158 irc_conn
.quit_msg
= g_strdup(g_strchomp(buf
+ 5));
161 else if (strncmp("reload", buf
, 6) == 0)
165 else if (strncmp("openlog", buf
, 7) == 0)
167 irc_logfile_reopen(&irc_conn
);
169 else if (strncmp("say", buf
, 3) == 0)
171 irc_send_message(&irc_conn
, NULL
, g_strchomp(buf
+ 4));
173 else if (strncmp("userlist", buf
, 8) == 0)
177 send(sock
, userlist
, strlen(userlist
), MSG_NOSIGNAL
);
178 send(sock
, "\n", 1, MSG_NOSIGNAL
);
181 send(sock
, "error\n", 6, MSG_NOSIGNAL
);
185 msg_len
= g_snprintf(msg
, sizeof msg
, "%s\r\n", buf
);
186 irc_send(&irc_conn
, msg
, msg_len
);
190 socket_fd_close(sock
);
196 static void hash_add_to_list(gpointer key
, gpointer value
, gpointer user_data
)
200 words
= g_list_insert_sorted(words
, key
, (GCompareFunc
) strcmp
);
205 static gint
write_file(const gchar
*filename
, const gchar
*text
)
208 gint bytes_written
, len
;
210 if (filename
== NULL
)
217 fp
= g_fopen(filename
, "w");
220 bytes_written
= fwrite(text
, sizeof (gchar
), len
, fp
);
223 if (len
!= bytes_written
)
225 debug("written only %d bytes, had to write %d bytes to %s", bytes_written
, len
, filename
);
231 debug("could not write to file %s (%s)", filename
, g_strerror(errno
));
238 void help_system_query(const gchar
*msg
)
241 gchar
*lowered_keyword
;
243 if (strncmp("?? ", msg
, 3) != 0)
248 lowered_keyword
= g_utf8_strdown(msg
, -1);
250 if (strncmp("keywords", lowered_keyword
, 8) == 0)
253 GString
*str
= g_string_sized_new(256);
257 g_hash_table_foreach(config
->data
, hash_add_to_list
, NULL
);
259 for (item
= words
; item
!= NULL
; item
= g_list_next(item
))
261 g_string_append(str
, item
->data
);
262 g_string_append_c(str
, ' ');
265 irc_send_message(&irc_conn
, NULL
, "%s: %s", msg
, str
->str
);
267 g_string_free(str
, TRUE
);
271 result
= g_hash_table_lookup(config
->data
, lowered_keyword
);
274 * Look for the @-Char in the result string. If one is found, most
275 * likely the !alias-command was used and this has to be resolved
276 * recursively until no @ appears anymore in the string.
278 while (result
&& result
[0] == '@')
279 result
= g_hash_table_lookup(config
->data
, result
+ 1);
282 irc_send_message(&irc_conn
, NULL
, "%s: %s", msg
, result
);
284 g_free(lowered_keyword
);
292 gint
help_system_learn(const gchar
*keyword
, const gchar
*text
)
302 if (keyword
== NULL
|| text
== NULL
)
305 config_name
= g_build_filename(getenv("HOME"), ".vomak", "database", NULL
);
306 keyfile
= g_key_file_new();
308 g_key_file_load_from_file(keyfile
, config_name
, G_KEY_FILE_NONE
, NULL
);
310 key
= g_hash_table_lookup(config
->data
, keyword
);
312 ret
= 1; // if key is non-NULL it is already available and gets updated
314 g_key_file_set_string(keyfile
, CONFIG_SECTION_DATABASE
, keyword
, text
);
316 data
= g_key_file_to_data(keyfile
, NULL
, NULL
);
317 write_file(config_name
, data
);
319 load_database_real(keyfile
);
321 g_key_file_free(keyfile
);
329 static void config_free()
332 g_free(config
->socketname
);
333 g_free(config
->socketperm
);
334 g_free(config
->server
);
335 g_free(config
->channel
);
336 g_free(config
->servername
);
337 g_free(config
->nickname
);
338 g_free(config
->username
);
339 g_free(config
->realname
);
340 g_free(config
->nickserv_password
);
341 g_free(config
->logfile
);
342 g_free(config
->fortune_cmd
);
343 if (config
->data
!= NULL
)
344 g_hash_table_destroy(config
->data
);
350 static config_t
*config_read()
352 GKeyFile
*keyfile
= g_key_file_new();
353 gchar
*config_name
= g_build_filename(getenv("HOME"), ".vomak", "config", NULL
);
354 config_t
*config
= g_new0(config_t
, 1);
358 g_key_file_load_from_file(keyfile
, config_name
, G_KEY_FILE_NONE
, NULL
);
360 config
->socketname
= g_key_file_get_string(keyfile
, CONFIG_SECTION_GENERAL
, "socketname", NULL
);
361 config
->socketperm
= g_key_file_get_string(keyfile
, CONFIG_SECTION_GENERAL
, "socketperm", NULL
);
362 config
->logfile
= g_key_file_get_string(keyfile
, CONFIG_SECTION_GENERAL
, "logfile", NULL
);
363 config
->query_names_interval
= g_key_file_get_integer(keyfile
, CONFIG_SECTION_GENERAL
, "query_names_interval", NULL
);
365 config
->server
= g_key_file_get_string(keyfile
, CONFIG_SECTION_IRC
, "server", NULL
);
366 config
->port
= g_key_file_get_integer(keyfile
, CONFIG_SECTION_IRC
, "port", NULL
);
367 config
->channel
= g_key_file_get_string(keyfile
, CONFIG_SECTION_IRC
, "channel", NULL
);
368 config
->servername
= g_key_file_get_string(keyfile
, CONFIG_SECTION_IRC
, "servername", NULL
);
369 config
->nickname
= g_key_file_get_string(keyfile
, CONFIG_SECTION_IRC
, "nickname", NULL
);
370 config
->username
= g_key_file_get_string(keyfile
, CONFIG_SECTION_IRC
, "username", NULL
);
371 config
->realname
= g_key_file_get_string(keyfile
, CONFIG_SECTION_IRC
, "realname", NULL
);
372 config
->nickserv_password
= g_key_file_get_string(keyfile
, CONFIG_SECTION_IRC
, "nickserv_password", NULL
);
373 config
->auto_rejoin
= g_key_file_get_boolean(keyfile
, CONFIG_SECTION_IRC
, "auto_rejoin", NULL
);
375 config
->fortune_cmd
= g_key_file_get_string(keyfile
, CONFIG_SECTION_EXTERNALS
, "fortune_cmd", NULL
);
377 g_key_file_free(keyfile
);
380 // if anything could not be read, abort. We don't use default values
381 if (config
->socketname
== NULL
||
382 config
->socketperm
== NULL
||
383 config
->server
== NULL
||
384 config
->channel
== NULL
||
385 config
->servername
== NULL
||
386 config
->nickname
== NULL
||
387 config
->username
== NULL
||
388 config
->realname
== NULL
)
390 fprintf(stderr
, "Config file does not exist or is not complete.\n");
398 static void init_socket()
402 /* create unix domain socket for remote operation */
403 socket_info
.lock_socket
= -1;
404 socket_info
.lock_socket_tag
= 0;
405 socket_init(&socket_info
, config
->socketname
);
407 if (socket_info
.lock_socket
>= 0)
410 sscanf(config
->socketperm
, "%o", &mode
);
411 /// FIXME for some reason the sticky bit is set when socketperm is "0640"
412 chmod(config
->socketname
, mode
);
413 socket_info
.read_ioc
= g_io_channel_unix_new(socket_info
.lock_socket
);
414 g_io_channel_set_flags(socket_info
.read_ioc
, G_IO_FLAG_NONBLOCK
, NULL
);
415 socket_info
.lock_socket_tag
= g_io_add_watch(socket_info
.read_ioc
,
416 G_IO_IN
|G_IO_PRI
|G_IO_ERR
, socket_input_cb
, NULL
);
421 void set_user_list(const gchar
*list
)
424 userlist
= g_strdup(list
);
428 const gchar
*get_user_list()
434 gint
main(gint argc
, gchar
**argv
)
436 irc_conn
.quit_msg
= NULL
;
439 main_loop
= g_main_loop_new(NULL
, FALSE
);
440 config
= config_read();
443 irc_connect(&irc_conn
);
444 signal(SIGTERM
, signal_cb
);
445 signal(SIGINT
, signal_cb
);
447 if (config
->query_names_interval
)
448 g_timeout_add(config
->query_names_interval
* 60000, irc_query_names
, &irc_conn
);
450 // if not a debug build, deattach from console and go into background
452 signal(SIGTTOU
, SIG_IGN
);
453 signal(SIGTTIN
, SIG_IGN
);
454 signal(SIGTSTP
, SIG_IGN
);
455 openlog("vomak", LOG_PID
, LOG_DAEMON
);
457 if (daemon(1, 0) < 0)
459 g_printerr("Unable to daemonize\n");
465 g_main_loop_run(main_loop
);
473 /* if debug mode is enabled, print the warning to stderr, else log it with syslog */
474 void vomak_warning(gchar
const *format
, ...)
477 va_start(args
, format
);
480 g_logv(G_LOG_DOMAIN
, G_LOG_LEVEL_WARNING
, format
, args
);
482 vsyslog(LOG_WARNING
, format
, args
);