Fix crash when !alias was called with more than two arguments.
[vomak.git] / irc.c
blobaa3e2c9ac245ad44969e2a0bc02c06ea3c27959d
1 /*
2 * irc.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.
21 #define _GNU_SOURCE
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <netdb.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <netinet/in.h>
30 #include <sys/socket.h>
31 #include <unistd.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <signal.h>
35 #ifndef DEBUG
36 # include <syslog.h>
37 #endif
39 #include <glib.h>
41 #include "vomak.h"
42 #include "socket.h"
43 #include "irc.h"
46 config_t *config;
50 gboolean irc_query_names(gpointer data)
52 irc_conn_t *irc = data;
53 static gchar msg[1024];
54 guint msg_len;
56 TRACE
58 msg_len = g_snprintf(msg, sizeof msg, "NAMES #%s\r\n", config->channel);
59 send(irc->socket, msg, msg_len, 0);
61 return TRUE;
65 static void irc_send_private_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
67 static gchar tmp_msg[1024];
68 static gchar msg[1024];
69 guint msg_len;
70 va_list ap;
72 if (target == NULL)
73 return;
75 va_start(ap, format);
76 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
77 va_end(ap);
79 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG %s :%s, %s\r\n", target, target, tmp_msg);
80 send(irc_conn->socket, msg, msg_len, 0);
84 static gchar *get_nickname(const gchar *line, guint len)
86 static gchar result[20];
87 guint i, j = 0;
89 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df
90 for (i = 0; i < len; i++)
92 if (line[i] == '!' || line[i] == '=' || j >= 19)
93 break;
95 if (line[i] == ':')
96 continue;
98 result[j++] = line[i];
100 result[j] = '\0';
102 return result;
106 static gchar *get_private_message_sender(const gchar *line, guint len)
108 guint i;
110 // :eht16!n=enrico@uvena.de PRIVMSG GeanyTestBot :hi
111 for (i = 0; i < len; i++)
113 if (line[i] != ' ')
114 continue;
116 if (strncmp("PRIVMSG", line + i + 1, 7) == 0)
118 static gchar name[20];
119 g_snprintf(name, sizeof(name), "%s :", config->nickname);
120 // if the receiver of the message is me, then it's a private message and we return the
121 // sender's nickname, otherwise NULL
122 if (strncmp(name, line + i + 9, strlen(config->nickname) + 2) == 0)
123 return get_nickname(line, len);
124 else
125 return NULL;
129 return NULL;
133 static gint get_response(const gchar *line, guint len)
135 static gchar result[4];
136 guint i, j = 0;
137 gboolean in_response = FALSE;
139 // :kornbluth.freenode.net 353 GeanyTestBot @ #eht16 :GeanyTestBot eht16
140 // :kornbluth.freenode.net 366 GeanyTestBot #eht16 :End of /NAMES list.
141 for (i = 0; i < len; i++)
143 // before a response code
144 if (line[i] != ' ' && ! in_response)
145 continue;
147 // after a response code
148 if (line[i] == ' ' && in_response)
150 in_response = FALSE;
151 break;
154 if (line[i] == ' ' )
155 i++; // skip the space
157 result[j++] = line[i];
158 in_response = TRUE;
160 result[j] = '\0';
162 return atoi(result);
166 const gchar *irc_get_message(const gchar *line, guint len)
168 static gchar result[1024] = { 0 };
169 guint i, j = 0;
170 gint state = 0; // represents the current part of the whole line, separated by spaces
172 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df foo: var
173 // -> df foo: var
174 for (i = 0; i < len; i++)
176 if (state < 3)
178 if (line[i] == ' ')
180 state++;
181 i++; // skip the ':'
182 continue;
185 else if (line[i] != '\r' && line[i] != '\n')
187 result[j++] = line[i];
190 result[j] = '\0';
192 return result;
196 static const gchar *irc_get_message_with_name(const gchar *line, guint len, const gchar *name)
198 const gchar *tmp;
199 gsize name_len;
201 tmp = irc_get_message(line, len);
202 name_len = strlen(name);
204 if (strncmp(tmp, name, name_len) == 0)
205 tmp += name_len;
207 return tmp;
211 // returns a nickname argument given to cmd, it may also be "cmd for nickname", then the "for" is
212 // skipped
213 static const gchar *get_argument_target(const gchar *line, guint len, const gchar *cmd)
215 static gchar result[20];
216 const gchar *tmp;
217 gchar **parts;
219 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!beer for me
220 tmp = irc_get_message(line, len);
222 // -> !beer for me
223 parts = g_strsplit(tmp, " ", -1);
224 if (parts == NULL || parts[0] == NULL)
226 g_strfreev(parts);
227 return NULL;
230 // if cmd doesn't match, skip it
231 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
233 g_strfreev(parts);
234 return NULL;
237 if (parts[1] == NULL)
239 g_strfreev(parts);
240 return NULL;
243 if (strcmp("for", parts[1]) == 0)
245 if (parts[2] != NULL)
247 if (strcmp(parts[2], "me") == 0)
249 g_strfreev(parts);
250 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
252 else
253 g_strlcpy(result, parts[2], sizeof(result));
255 else
256 g_strlcpy(result, parts[1], sizeof(result));
258 else if (strcmp(parts[1], "me") == 0)
260 g_strfreev(parts);
261 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
263 else
265 g_strlcpy(result, parts[1], sizeof(result));
268 g_strfreev(parts);
269 return result;
273 // Parses the line and puts the first argument in arg1, all further arguments are concatenated
274 // in arg2. arg1 and arg2 should be freed when no longer needed.
275 // If arg1 and arg2 were set successfully, TRUE is returned, if any error occurs, FALSE is returned
276 // and arg1 and arg2 are set to NULL.
277 static gboolean get_argument_two(const gchar *line, guint len, const gchar *cmd,
278 gchar **arg1, gchar **arg2)
280 const gchar *tmp;
281 gchar **parts;
283 *arg1 = NULL;
284 *arg2 = NULL;
286 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!learn keyword text to be added
287 tmp = irc_get_message(line, len);
289 // -> !learn keyword text to be added
290 parts = g_strsplit(tmp, " ", 3);
291 if (parts == NULL || parts[0] == NULL)
293 g_strfreev(parts);
294 return FALSE;
297 // if cmd doesn't match, skip it
298 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
300 g_strfreev(parts);
301 return FALSE;
304 if (parts[1] == NULL || parts[2] == NULL)
306 g_strfreev(parts);
307 return FALSE;
310 *arg1 = g_strdup(parts[1]);
311 *arg2 = g_strdup(parts[2]);
313 g_strfreev(parts);
315 return TRUE;
319 static gboolean process_line(irc_conn_t *irc_conn, const gchar *line, guint len)
321 static gchar msg[1024];
322 guint msg_len;
323 gint response = get_response(line, len);
324 static gchar tmp_userlist[1024];
325 gchar *priv_sender;
326 const gchar *content;
328 content = irc_get_message(line, len);
330 // An error occurred, try to quit cleanly and print the error
331 if (response > 400 && response < 503)
333 // ignore Freenode's info messages sent with error code 477
334 // (see http://freenode.net/faq.shtml#freenode-info)
335 if (response != 477 || strstr(line, "[freenode-info]") == NULL)
337 g_print("Error: %s", line);
338 #ifndef DEBUG
339 syslog(LOG_WARNING, "received error: %d (%s)", response, g_strstrip((gchar*) line));
340 #endif
341 main_quit();
342 return FALSE;
345 // retrieve user name list
346 else if (response == 353)
348 if (tmp_userlist[0] == '\0')
349 g_strlcpy(tmp_userlist, strchr(content, ':') + 1, sizeof(tmp_userlist));
350 else
351 g_strlcat(tmp_userlist, strchr(content, ':') + 1, sizeof(tmp_userlist));
353 // retrieve end of user name list
354 else if (response == 366)
356 if (tmp_userlist[0] != '\0')
358 set_user_list(tmp_userlist);
359 tmp_userlist[0] = '\0';
362 else if (! irc_conn->connected)
364 // don't do anything else until we got finished connecting (to skip MOTD messages)
366 // PING-PONG
367 else if (strncmp("PING :", line, 6) == 0)
369 msg_len = g_snprintf(msg, sizeof msg, "PONG %s\r\n", line + 6); // 7 = "PING :"
370 debug("PONG: -%s-\n", msg);
371 send(irc_conn->socket, msg, msg_len, 0);
373 // handle private message
374 else if ((priv_sender = get_private_message_sender(line, len)) != NULL)
376 // to be able to send private messages to users, you need to register your bot's
377 // nickname with Nickserv (at least on Freenode)
378 irc_send_private_message(irc_conn, priv_sender, "I don't like private messages!");
380 // !test
381 else if (strncmp(content, "!test", 5) == 0)
383 irc_send_message(irc_conn, get_nickname(line, len), "I don't like tests!");
387 * Fun with !roulette
388 * You have to register your bot with nickserv and add it to the access-list
389 * of your channel to make "!roulette kick" work.
390 * This is just tested on FreeNode. Please feel free to write patches, that
391 * will make this work on other Networks.
393 else if (strncmp(content, "!roulette", 9) == 0)
395 gint32 rand = g_random_int();
397 gchar *arg1, *arg2;
399 if (rand % 6 == 0)
401 if (get_argument_two(line, len, "!roulette", &arg1, &arg2))
404 * er geht in diesen Abschnitt der Verzweigung nicht rein,
405 * wenn Kommando "!roulette kick" aufgerufen wurde.
408 * TODO: this needs a better argument-parser
410 g_free(arg2);
412 if ( strstr(arg1, "kick") )
414 irc_kick(irc_conn, get_nickname(line, len));
415 g_free(arg1);
419 else
421 irc_send_message(irc_conn, NULL, "*bang*");
422 irc_send_message(irc_conn, get_nickname(line, len), "You are dead.");
426 else
428 irc_send_message(irc_conn, NULL, "*click*");
431 // !coffee
432 else if (strncmp(content, "!coffee", 7) == 0)
434 const gchar *arg = get_argument_target(line, len, "!coffee");
436 if (arg == NULL)
437 arg = get_nickname(line, len);
439 irc_send_message(irc_conn, NULL,
440 "A nice sexy waitress brings %s a big cup of coffee!", arg);
442 // !coke
443 else if (strncmp(content, "!coke", 5) == 0)
445 const gchar *arg = get_argument_target(line, len, "!coke");
447 if (arg == NULL)
448 arg = get_nickname(line, len);
450 irc_send_message(irc_conn, NULL,
451 "A nice sexy waitress brings %s a cool bottle of coke!", arg);
453 // !beer
454 else if (strncmp(content, "!beer", 5) == 0)
456 const gchar *arg = get_argument_target(line, len, "!beer");
458 if (arg == NULL)
459 arg = get_nickname(line, len);
461 irc_send_message(irc_conn, NULL,
462 "A nice sexy waitress brings %s a nice bottle of beer!", arg);
464 // !help
465 else if (strncmp(content, "!help", 5) == 0)
467 irc_send_message(irc_conn, get_nickname(line, len), "please use ?? help");
469 // !learn
470 /// TODO require op privileges for !learn
471 else if (strncmp(content, "!learn", 6) == 0)
473 gchar *arg1, *arg2;
475 if (get_argument_two(line, len, "!learn", &arg1, &arg2))
477 gint result = help_system_learn(arg1, arg2);
478 gchar *text;
480 switch (result)
482 case 0:
484 text = g_strdup_printf("new keyword \"%s\" was added.", arg1);
485 break;
487 case 1:
489 text = g_strdup_printf("existing keyword \"%s\" was updated.", arg1);
490 break;
492 default:
494 text = g_strdup("an error occurred. Database not updated.");
495 break;
498 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
500 g_free(text);
501 g_free(arg1);
502 g_free(arg2);
504 else
505 irc_send_message(irc_conn, get_nickname(line, len),
506 "wrong usage of !learn. Use \"?? learn\" for usage information.");
509 // !alias-command
510 else if (strncmp(content, "!alias", 6) == 0)
512 gchar *arg1, *arg2;
514 if (get_argument_two(line, len, "!alias", &arg1, &arg2))
516 // detect if arg2 has more than one word by scanning for spaces in
517 // the string
518 if (strchr(arg2, ' '))
520 irc_send_message(irc_conn, get_nickname(line, len),
521 "You gave me more than two arguments for !alias. I can not handle this.");
523 // check if the target actually exist and refuse if it doesn't exist
524 else if (g_hash_table_lookup(config->data, arg2) == NULL)
526 irc_send_message(irc_conn, get_nickname(line, len),
527 "The given target for the alias does not exist. I will refuse your request.");
529 else
531 gint result;
532 gchar *text;
533 gchar *alias = g_strconcat("@", arg2, NULL);
535 result = help_system_learn(arg1, alias);
537 switch (result)
539 case 0:
541 text = g_strdup_printf("new alias \"%s\" was added.", arg1);
542 break;
544 case 1:
546 text = g_strdup_printf("existing alias \"%s\" was updated.", arg1);
547 break;
549 default:
551 text = g_strdup("An error occurred. Database not updated.");
552 break;
556 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
558 g_free(alias);
559 g_free(text);
560 g_free(arg1);
561 g_free(arg2);
564 else
565 irc_send_message(irc_conn, get_nickname(line, len),
566 "wrong usage of !alias. Use \"?? alias\" for usage information.");
569 // ?? ...
570 else if (strncmp(content, "?? ", 3) == 0)
572 help_system_query(content);
574 // Hi /me, acts on "hello $nickname" and "hi $nickname", hi and hello are case-insensitive
575 // Thanks /me
576 else if (strstr(content, config->nickname) != NULL)
578 const gchar *tmp_msg = irc_get_message_with_name(line, len, config->nickname);
580 if (strncasecmp("hi", content, 2) == 0 || strncasecmp("hello", content, 5) == 0 ||
581 strcasecmp(", hi", tmp_msg) == 0 || strcasecmp(", hello", tmp_msg) == 0 ||
582 strcasecmp(": hi", tmp_msg) == 0 || strcasecmp(": hello", tmp_msg) == 0)
584 irc_send_message(irc_conn, NULL,
585 "Hi %s. My name is %s and I'm here to offer additional services to you! "
586 "Try \"?? help\" for general information and \"?? vomak\" for information about me.",
587 get_nickname(line, len), config->nickname);
589 else if (strncasecmp("thanks", content, 6) == 0 || strncasecmp("thx", content, 3) == 0 ||
590 strcasecmp(", thanks", tmp_msg) == 0 || strcasecmp(", thx", tmp_msg) == 0 ||
591 strcasecmp(": thanks", tmp_msg) == 0 || strcasecmp(": thx", tmp_msg) == 0)
593 irc_send_message(irc_conn, get_nickname(line, len),
594 "no problem. It was a pleasure to serve you.");
598 return TRUE;
602 void irc_kick(irc_conn_t *irc_conn, gchar *nickname)
604 static gchar msg[1024];
605 guint msg_len;
607 irc_send_private_message(irc_conn, "ChanServ", g_strconcat("op #", config->channel, NULL));
608 msg_len = g_snprintf(msg, sizeof msg, "KICK #%s %s\r\n", config->channel, nickname);
609 send(irc_conn->socket, msg, msg_len, 0);
610 irc_send_private_message(irc_conn, "ChanServ", g_strconcat("deop #", config->channel, NULL));
614 static gboolean input_cb(GIOChannel *source, GIOCondition cond, gpointer data)
616 #if 1
617 gchar buf[1024];
618 guint buf_len;
619 irc_conn_t *irc = data;
620 gboolean ret = TRUE;
622 if ((buf_len = socket_fd_gets(irc->socket, buf, sizeof(buf))) != -1)
624 ret = process_line(irc, buf, buf_len);
626 #else
627 gsize buf_len;
628 irc_conn_t *irc = data;
630 if (cond & (G_IO_IN | G_IO_PRI))
632 gchar *buf = NULL;
633 GIOStatus rv;
634 GError *err = NULL;
638 rv = g_io_channel_read_line(source, &buf, &buf_len, NULL, &err);
639 if (buf != NULL)
641 buf_len -= 2;
642 buf[buf_len] = '\0'; // skip trailing \r\n
644 process_line(irc, buf, buf_len);
645 g_free(buf);
647 if (err != NULL)
649 debug("%s: error: %s", __func__, err->message);
650 g_error_free(err);
651 err = NULL;
654 while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
655 debug("%s: status %d\n", __func__, rv);
657 #endif
658 return ret;
662 void irc_send_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
664 static gchar tmp_msg[1024];
665 static gchar msg[1024];
666 guint msg_len;
667 va_list ap;
669 va_start(ap, format);
670 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
671 va_end(ap);
673 if (target)
674 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s, %s\r\n", config->channel, target, tmp_msg);
675 else
676 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s\r\n", config->channel, tmp_msg);
678 send(irc_conn->socket, msg, msg_len, 0);
682 void irc_goodbye(irc_conn_t *irc)
684 guint len;
685 gchar msg[256];
687 len = g_strlcpy(msg, "QUIT :Good bye. It was a pleasure to serve you\r\n", sizeof msg);
688 send(irc->socket, msg, len, 0);
692 gint irc_finalize(irc_conn_t *irc_conn)
694 if (irc_conn->socket < 0)
695 return -1;
697 if (irc_conn->lock_tag > 0)
698 g_source_remove(irc_conn->lock_tag);
700 if (irc_conn->read_ioc)
702 g_io_channel_shutdown(irc_conn->read_ioc, TRUE, NULL);
703 g_io_channel_unref(irc_conn->read_ioc);
704 irc_conn->read_ioc = NULL;
706 socket_fd_close(irc_conn->socket);
707 irc_conn->socket = -1;
709 return 0;
713 void irc_connect(irc_conn_t *irc_conn)
715 struct hostent *he;
716 struct sockaddr_in their_addr;
717 gchar msg[256];
718 guint msg_len;
720 TRACE
722 // Connect the socket to the server
723 if ((he = gethostbyname(config->server)) == NULL)
725 perror("gethostbyname");
726 exit(1);
729 if ((irc_conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
731 perror("socket");
732 exit(1);
735 their_addr.sin_family = PF_INET;
736 their_addr.sin_port = htons(6667);
737 their_addr.sin_addr = *((struct in_addr *)he->h_addr_list[0]);
738 memset(&(their_addr.sin_zero), '\0', 8);
740 if (connect(irc_conn->socket, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
742 perror("connect");
743 exit(1);
745 // say who we are
746 msg_len = g_snprintf(msg, sizeof(msg), "USER %s %s %s :%s\r\nNICK %s\r\n",
747 config->username, config->servername, config->servername, config->realname, config->nickname);
748 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
750 perror("send USER");
752 // identify our nick
753 if (NZV(config->nickserv_password))
755 msg_len = g_snprintf(msg, sizeof(msg), "PRIVMSG nickserv :identify %s\r\n", config->nickserv_password);
756 if (send(irc_conn->socket, msg, msg_len, 0) == -1)
758 perror("send NICKSERV");
761 // join the channel
762 g_snprintf(msg, sizeof msg, "JOIN #%s\r\n ", config->channel);
763 if (send(irc_conn->socket, msg, strlen(msg), 0) == -1)
765 perror("send");
768 // input callback, attached to the main loop
769 irc_conn->read_ioc = g_io_channel_unix_new(irc_conn->socket);
770 //~ g_io_channel_set_flags(irc_conn.read_ioc, G_IO_FLAG_NONBLOCK, NULL);
771 g_io_channel_set_encoding(irc_conn->read_ioc, NULL, NULL);
772 irc_conn->lock_tag = g_io_add_watch(irc_conn->read_ioc, G_IO_IN|G_IO_PRI|G_IO_ERR, input_cb, irc_conn);