You know the game, fix URL, EOL.
[vomak.git] / src / irc.c
blob40608388fd72541de72d7b6cc0c3ff007a62227d
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 #include <time.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"
48 config_t *config;
51 static void irc_kick(irc_conn_t *irc_conn, const gchar *nickname);
52 static gboolean irc_is_user_op(irc_conn_t *irc_conn, const gchar *nickname);
54 enum
56 GOODIES_COFFEE,
57 GOODIES_COKE,
58 GOODIES_BEER,
59 GOODIES_TEA,
60 GOODIES_PIZZA,
62 typedef struct
64 const gchar *command;
65 const gchar *message;
66 } goodies_t;
68 const goodies_t goodies[] = {
69 { "!coffee", "A nice sexy waitress brings %s a big cup of coffee!" },
70 { "!coke", "A nice sexy waitress brings %s a cool bottle of coke!" },
71 { "!beer", "A nice sexy waitress brings %s a nice bottle of beer!" },
72 { "!tea", "A nice sexy waitress brings %s a cup of hot tea!" },
73 { "!pizza", "Someone calls Mario, and he brings %s a tasty hawaiian pizza!" },
74 { NULL, NULL }
77 gboolean irc_query_names(gpointer data)
79 irc_conn_t *irc = data;
80 static gchar msg[1024];
81 guint msg_len;
83 TRACE
85 msg_len = g_snprintf(msg, sizeof msg, "NAMES #%s\r\n", config->channel);
86 // send the message directly to avoid logging (prevent log file spamming)
87 send(irc->socket, msg, msg_len, MSG_NOSIGNAL);
89 return TRUE;
93 /* 'line' should be terminated by "\r\n" (CRLF) */
94 static void irc_log(irc_conn_t *irc_conn, const gchar *line, gboolean send)
96 time_t t;
97 struct tm *tp;
98 static gchar str[256];
99 GString *log_line;
101 if (! irc_conn->log_fd)
102 return;
104 t = time(NULL);
105 tp = localtime(&t);
106 strftime(str, sizeof str, "%F %T %z ", tp);
108 log_line = g_string_new(str);
109 if (send) // if we are sending a message, add our ident string
111 g_string_append_printf(log_line, ":%s!n=%s@%s ",
112 config->nickname, config->username, config->servername);
114 g_string_append(log_line, line);
115 /* add \r\n if it is missing */
116 if (strncmp(log_line->str + log_line->len - 2, "\r\n", 2) != 0)
118 g_string_append(log_line, "\r\n");
120 fwrite(log_line->str, log_line->len, 1, irc_conn->log_fd);
121 fflush(irc_conn->log_fd);
122 g_string_free(log_line, TRUE);
126 static void irc_send_private_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
128 static gchar tmp_msg[1024];
129 static gchar msg[1024];
130 guint msg_len;
131 va_list ap;
133 if (target == NULL)
134 return;
136 va_start(ap, format);
137 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
138 va_end(ap);
140 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG %s :%s, %s\r\n", target, target, tmp_msg);
141 irc_send(irc_conn, msg, msg_len);
145 static gchar *get_nickname(const gchar *line, guint len)
147 static gchar result[20];
148 guint i, j = 0;
150 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df
151 for (i = 0; i < len; i++)
153 if (line[i] == '!' || line[i] == '=' || j >= 19)
154 break;
156 if (line[i] == ':')
157 continue;
159 result[j++] = line[i];
161 result[j] = '\0';
163 return result;
167 static gchar *get_command_sender(const gchar *line, guint len, const gchar *command)
169 guint i;
170 gsize cmd_len = strlen(command);
172 // :eht16!n=enrico@uvena.de PRIVMSG GeanyTestBot :hi
173 for (i = 0; i < len; i++)
175 if (line[i] != ' ')
176 continue;
178 if (strncmp(command, line + i + 1, cmd_len) == 0)
180 static gchar name[20];
181 g_snprintf(name, sizeof(name), "%s :", config->nickname);
182 // if the receiver of the message is me, then it's a private message and
183 // we return the sender's nickname, otherwise NULL
184 if (strncmp(name, line + i + cmd_len + 2, strlen(config->nickname) + 2) == 0 ||
185 strncmp("KICK", command, 4) == 0)
187 return get_nickname(line, len);
189 else
190 return NULL;
194 return NULL;
198 static gint get_response(const gchar *line, guint len)
200 static gchar result[4];
201 guint i, j = 0;
202 gboolean in_response = FALSE;
204 // :kornbluth.freenode.net 353 GeanyTestBot @ #eht16 :GeanyTestBot eht16
205 // :kornbluth.freenode.net 366 GeanyTestBot #eht16 :End of /NAMES list.
206 for (i = 0; i < len; i++)
208 // before a response code
209 if (line[i] != ' ' && ! in_response)
210 continue;
212 // after a response code
213 if (line[i] == ' ' && in_response)
215 in_response = FALSE;
216 break;
219 if (line[i] == ' ' )
220 i++; // skip the space
222 result[j++] = line[i];
223 in_response = TRUE;
225 result[j] = '\0';
227 return atoi(result);
231 const gchar *irc_get_message(const gchar *line, guint len)
233 static gchar result[1024] = { 0 };
234 guint i, j = 0;
235 gint state = 0; // represents the current part of the whole line, separated by spaces
237 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :df foo: var
238 // -> df foo: var
239 for (i = 0; i < len; i++)
241 if (state < 3)
243 if (line[i] == ' ')
245 state++;
246 i++; // skip the ':'
247 continue;
250 else if (line[i] != '\r' && line[i] != '\n')
252 result[j++] = line[i];
255 result[j] = '\0';
257 return result;
261 static const gchar *irc_get_message_with_name(const gchar *line, guint len, const gchar *name)
263 const gchar *tmp;
264 gsize name_len;
266 tmp = irc_get_message(line, len);
267 name_len = strlen(name);
269 if (strncmp(tmp, name, name_len) == 0)
270 tmp += name_len;
272 return tmp;
276 // returns a nickname argument given to cmd, it may also be "cmd for nickname", then the "for" is
277 // skipped
278 static const gchar *get_argument_target(const gchar *line, guint len, const gchar *cmd)
280 static gchar result[20];
281 const gchar *tmp;
282 gchar **parts;
284 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!beer for me
285 tmp = irc_get_message(line, len);
287 // -> !beer for me
288 parts = g_strsplit(tmp, " ", -1);
289 if (parts == NULL || parts[0] == NULL)
291 g_strfreev(parts);
292 return NULL;
295 // if cmd doesn't match, skip it
296 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
298 g_strfreev(parts);
299 return NULL;
302 if (parts[1] == NULL)
304 g_strfreev(parts);
305 return NULL;
308 if (strcmp("for", parts[1]) == 0)
310 if (parts[2] != NULL)
312 if (strcmp(parts[2], "me") == 0)
314 g_strfreev(parts);
315 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
317 else
318 g_strlcpy(result, parts[2], sizeof(result));
320 else
321 g_strlcpy(result, parts[1], sizeof(result));
323 else if (strcmp(parts[1], "me") == 0)
325 g_strfreev(parts);
326 return NULL; // if we return NULL, the nickname of the caller is used, aka "me"
328 else
330 g_strlcpy(result, parts[1], sizeof(result));
333 g_strfreev(parts);
334 return result;
338 // Parses the line and puts the first argument in arg1, all further arguments are concatenated
339 // in arg2. arg1 and arg2 should be freed when no longer needed.
340 // If arg1 and arg2 were set successfully, TRUE is returned, if any error occurs, FALSE is returned
341 // and arg1 and arg2 are set to NULL.
342 static gboolean get_argument_two(const gchar *line, guint len, const gchar *cmd,
343 gchar **arg1, gchar **arg2)
345 const gchar *tmp;
346 gchar **parts;
348 *arg1 = NULL;
349 *arg2 = NULL;
351 // :eht16!n=enrico@uvena.de PRIVMSG #eht16 :!learn keyword text to be added
352 tmp = irc_get_message(line, len);
354 // -> !learn keyword text to be added
355 parts = g_strsplit(tmp, " ", 3);
356 if (parts == NULL || parts[0] == NULL)
358 g_strfreev(parts);
359 return FALSE;
362 // if cmd doesn't match, skip it
363 if (parts[0] != NULL && strcmp(parts[0], cmd) != 0)
365 g_strfreev(parts);
366 return FALSE;
369 if (parts[1] == NULL || parts[2] == NULL)
371 g_strfreev(parts);
372 return FALSE;
375 *arg1 = g_strdup(parts[1]);
376 *arg2 = g_strdup(parts[2]);
378 g_strfreev(parts);
380 return TRUE;
384 static void command_fortune(irc_conn_t *irc_conn)
386 GError *error = NULL;
387 gchar *out = NULL;
388 gchar *err = NULL;
390 if (g_spawn_command_line_sync(config->fortune_cmd, &out, &err, NULL, &error))
392 if (NZV(out))
394 gchar **lines = g_strsplit(out, "\n", -1);
395 gsize i, len;
397 len = g_strv_length(lines);
398 for (i = 0; i < len; i++)
400 if (strlen(g_strchomp(lines[i])) > 0)
401 irc_send_message(irc_conn, NULL, lines[i]);
403 g_strfreev(lines);
405 else
407 vomak_warning("Executing fortune failed (%s)", err);
409 g_free(out);
410 g_free(err);
412 else
414 vomak_warning("Executing fortune failed (%s)", error->message);
415 g_error_free(error);
420 static void command_moo(irc_conn_t *irc_conn)
422 gint32 rand = g_random_int();
424 if (rand % 2 == 0)
426 gsize i;
427 const gchar *moo_str[] = {
428 " ^__^\r\n",
429 " (oo)\r\n",
430 " /-----(__)\r\n",
431 " / | ||\r\n",
432 " * /\\---/\\\r\n",
433 " ~~ ~~\n\r\n",
434 "..\"Have you mooed today?\"..\r\n",
435 NULL
438 for (i = 0; moo_str[i] != NULL; i++)
440 irc_send_message(irc_conn, NULL, moo_str[i]);
443 else
445 irc_send_message(irc_conn, NULL, "I have Super Cow Powers. Have you mooed today?");
450 static void command_learn(irc_conn_t *irc_conn, const gchar *line, guint len)
452 gchar *arg1, *arg2;
454 if (get_argument_two(line, len, "!learn", &arg1, &arg2))
456 gint result = help_system_learn(arg1, arg2);
457 gchar *text;
459 switch (result)
461 case 0:
463 text = g_strdup_printf("new keyword \"%s\" was added.", arg1);
464 break;
466 case 1:
468 text = g_strdup_printf("existing keyword \"%s\" was updated.", arg1);
469 break;
471 default:
473 text = g_strdup("an error occurred. Database not updated.");
474 break;
477 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
479 g_free(text);
480 g_free(arg1);
481 g_free(arg2);
483 else
484 irc_send_message(irc_conn, get_nickname(line, len),
485 "wrong usage of !learn. Use \"?? learn\" for usage information.");
489 static void command_alias(irc_conn_t *irc_conn, const gchar *line, guint len)
491 gchar *arg1, *arg2;
493 if (get_argument_two(line, len, "!alias", &arg1, &arg2))
495 // detect if arg2 has more than one word by scanning for spaces in
496 // the string
497 if (strchr(arg2, ' '))
499 irc_send_message(irc_conn, get_nickname(line, len),
500 "You gave me more than two arguments for !alias. I can not handle this.");
502 // check if the target actually exist and refuse if it doesn't exist
503 else if (g_hash_table_lookup(config->data, arg2) == NULL)
505 irc_send_message(irc_conn, get_nickname(line, len),
506 "The given target for the alias does not exist. I will refuse your request.");
508 else
510 gint result;
511 gchar *text;
512 gchar *alias = g_strconcat("@", arg2, NULL);
514 result = help_system_learn(arg1, alias);
516 switch (result)
518 case 0:
520 text = g_strdup_printf("new alias \"%s\" was added.", arg1);
521 break;
523 case 1:
525 text = g_strdup_printf("existing alias \"%s\" was updated.", arg1);
526 break;
528 default:
530 text = g_strdup("An error occurred. Database not updated.");
531 break;
535 irc_send_message(irc_conn, get_nickname(line, len), "%s", text);
537 g_free(alias);
538 g_free(text);
539 g_free(arg1);
540 g_free(arg2);
543 else
544 irc_send_message(irc_conn, get_nickname(line, len),
545 "wrong usage of !alias. Use \"?? alias\" for usage information.");
549 static void command_goodies(irc_conn_t *irc_conn, const gchar *line, guint len, gint goodie)
551 const goodies_t *g = &goodies[goodie];
552 const gchar *arg = get_argument_target(line, len, g->command);
554 if (arg == NULL)
555 arg = get_nickname(line, len);
557 irc_send_message(irc_conn, NULL,
558 g->message, arg);
562 static void process_command(irc_conn_t *irc_conn, const gchar *line, guint len, const gchar *content)
564 // !test
565 if (strncmp(content, "!test", 5) == 0)
567 irc_send_message(irc_conn, get_nickname(line, len), "I don't like tests!");
569 // !moo
570 else if (strncmp(content, "!moo", 4) == 0)
572 command_moo(irc_conn);
574 // !fortune
575 else if (config->fortune_cmd != NULL && strncmp(content, "!fortune", 8) == 0)
577 command_fortune(irc_conn);
579 // !coffee
580 else if (strncmp(content, "!coffee", 7) == 0)
582 command_goodies(irc_conn, line, len, GOODIES_COFFEE);
584 // !coke
585 else if (strncmp(content, "!coke", 5) == 0)
587 command_goodies(irc_conn, line, len, GOODIES_COKE);
589 // !beer
590 else if (strncmp(content, "!beer", 5) == 0)
592 command_goodies(irc_conn, line, len, GOODIES_BEER);
594 // !pizza
595 else if (strncmp(content, "!pizza", 5) == 0)
597 command_goodies(irc_conn, line, len, GOODIES_PIZZA);
599 // !tea
600 else if (strncmp(content, "!tea", 5) == 0)
602 command_goodies(irc_conn, line, len, GOODIES_TEA);
604 // !help
605 else if (strncmp(content, "!help", 5) == 0)
607 help_system_query("?? help");
610 * Fun with !roulette
611 * You have to register your bot with nickserv and add it to the access-list
612 * of your channel to make the !roulette-command work.
613 * This is just tested on FreeNode. Please feel free to write patches, that
614 * will make this work on other Networks.
616 else if (strncmp(content, "!roulette", 9) == 0)
618 static gint32 rand = -1;
619 static gint bullets_fired = 0;
621 if (rand == -1)
622 rand = g_random_int() % 6;
623 if (rand == bullets_fired)
625 irc_send_message(irc_conn, NULL, "*bang*");
626 irc_kick(irc_conn, get_nickname(line, len));
627 rand = g_random_int() % 6;
628 bullets_fired = 0;
631 else
633 irc_send_message(irc_conn, NULL, "*click*");
634 bullets_fired++;
637 // !learn
638 /// TODO require op privileges for !learn
639 else if (strncmp(content, "!learn", 6) == 0)
641 command_learn(irc_conn, line, len);
643 // !alias
644 else if (strncmp(content, "!alias", 6) == 0)
646 command_alias(irc_conn, line, len);
651 static gboolean delayed_quit(gpointer data)
653 main_quit();
654 return FALSE;
658 static gboolean process_line(irc_conn_t *irc_conn, const gchar *line, guint len)
660 static gchar msg[1024];
661 guint msg_len;
662 gint response = get_response(line, len);
663 static gchar tmp_userlist[1024];
664 gchar *priv_sender;
665 const gchar *content;
666 static gboolean connected = FALSE;
668 // don't log the NAMES command's output (prevent log file spam)
669 if (response != 353 && response != 366)
670 irc_log(irc_conn, line, FALSE);
672 // An error occurred, try to quit cleanly and print the error
673 if ((response > 400 && response < 503))
675 // ignore Freenode's info messages sent with error code 477
676 // (see http://freenode.net/faq.shtml#freenode-info)
677 if (response != 477 || strstr(line, "[freenode-info]") == NULL)
679 #ifndef DEBUG
680 syslog(LOG_WARNING, "received error: %d (%s)", response, g_strstrip((gchar*) line));
681 #else
682 g_print("Error: %s", line);
683 #endif
684 g_free(irc_conn->quit_msg);
685 irc_conn->quit_msg = g_strdup("I got an error and better leave in advance. Bye.");
686 irc_goodbye(irc_conn);
687 g_timeout_add_seconds(1, delayed_quit, NULL);
688 return FALSE;
692 if (! connected)
694 if (response == 376)
695 connected = TRUE;
696 else
697 return TRUE;
700 content = irc_get_message(line, len);
702 // retrieve user name list
703 if (response == 353)
705 if (tmp_userlist[0] == '\0')
706 g_strlcpy(tmp_userlist, strchr(content, ':') + 1, sizeof(tmp_userlist));
707 else
708 g_strlcat(tmp_userlist, strchr(content, ':') + 1, sizeof(tmp_userlist));
710 // retrieve end of user name list
711 else if (response == 366)
713 if (tmp_userlist[0] != '\0')
715 set_user_list(tmp_userlist);
716 tmp_userlist[0] = '\0';
719 else if (! connected)
721 // don't do anything else until we got finished connecting (to skip MOTD messages)
723 // PING-PONG
724 else if (strncmp("PING :", line, 6) == 0)
726 msg_len = g_snprintf(msg, sizeof msg, "PONG %s\r\n", line + 6); // 7 = "PING :"
727 debug("PONG: -%s-\n", msg);
728 irc_send(irc_conn, msg, msg_len);
730 // handle private message
731 else if ((priv_sender = get_command_sender(line, len, "PRIVMSG")) != NULL)
733 // to be able to send private messages to users, you need to register your bot's
734 // nickname with Nickserv (at least on Freenode)
735 irc_send_private_message(irc_conn, priv_sender, "I don't like private messages!");
737 // handle kicks (we were kicked, bastards)
738 else if (get_command_sender(line, len, "KICK") != NULL)
740 if (config->auto_rejoin)
742 g_usleep(5000000); // just wait a little bit
743 msg_len = g_snprintf(msg, sizeof msg, "JOIN #%s\r\n", config->channel);
744 irc_send(irc_conn, msg, msg_len);
746 else
748 main_quit();
751 // Hi /me, acts on "hello $nickname" and "hi $nickname", hi and hello are case-insensitive
752 // Thanks /me
753 else if (strstr(content, config->nickname) != NULL)
755 const gchar *tmp_msg = irc_get_message_with_name(line, len, config->nickname);
757 if (strncasecmp("hi", content, 2) == 0 || strncasecmp("hello", content, 5) == 0 || strncasecmp("hey", content, 3) == 0 ||
758 strcasecmp(", hi", tmp_msg) == 0 || strcasecmp(", hello", tmp_msg) == 0 || strcasecmp(", hey", tmp_msg) == 0 ||
759 strcasecmp(": hi", tmp_msg) == 0 || strcasecmp(": hello", tmp_msg) == 0 || strcasecmp(": hey", tmp_msg) == 0)
761 irc_send_message(irc_conn, NULL,
762 "Hi %s. My name is %s and I'm here to offer additional services to you! "
763 "Try \"?? help\" for general information and \"?? vomak\" for information about me.",
764 get_nickname(line, len), config->nickname);
766 else if (strncasecmp("thanks", content, 6) == 0 || strncasecmp("thx", content, 3) == 0 ||
767 strcasecmp(", thanks", tmp_msg) == 0 || strcasecmp(", thx", tmp_msg) == 0 ||
768 strcasecmp(": thanks", tmp_msg) == 0 || strcasecmp(": thx", tmp_msg) == 0)
770 irc_send_message(irc_conn, get_nickname(line, len),
771 "no problem. It was a pleasure to serve you.");
774 // ?? ...
775 else if (strncmp(content, "?? ", 3) == 0)
777 help_system_query(content);
779 // pass to process_command() to process other commands (!beer, !test, !learn, ...)
780 else if (*content == '!')
782 process_command(irc_conn, line, len, content);
785 return TRUE;
790 * Please note that this will not work on Networks without ChanServ, e.g. on
791 * Quakenet or IRCnet. Your Bot has to be registered with NickServ and to be
792 * added to the channel access list for this to work.
794 static gboolean irc_toggle_op(irc_conn_t *irc_conn, gboolean request_op)
796 const gchar *cmd;
797 static gchar msg[1024];
798 guint msg_len;
800 if (irc_is_user_op(irc_conn, "ChanServ"))
802 cmd = (request_op) ? "op" : "deop";
803 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG ChanServ :%s #%s\r\n", cmd, config->channel);
804 irc_send(irc_conn, msg, msg_len);
806 return TRUE;
808 return FALSE; /* it seems we don't have a ChanServ bot */
812 static gboolean irc_is_user_op(irc_conn_t *irc_conn, const gchar *nickname)
814 const gchar *pos;
815 const gchar *userlist;
817 if (nickname == NULL)
818 return FALSE;
820 userlist = get_user_list();
822 if ( (pos = strstr(userlist, nickname)) )
824 if ( (pos - 1) >= userlist && (*(pos - 1) == '@') )
826 return TRUE;
829 else
831 #ifdef DEBUG
832 irc_send_message(irc_conn, NULL,
833 "Hey. There are crazy things going on here. (O.o)");
834 #else
835 syslog(LOG_WARNING, "user %s not found in names list of #%s", nickname, config->channel);
836 #endif
839 return FALSE;
843 static void irc_kick(irc_conn_t *irc_conn, const gchar *nickname)
845 static gchar msg[1024];
846 gboolean need_deop = FALSE;
847 guint msg_len;
849 TRACE
851 if (! irc_is_user_op(irc_conn, config->nickname))
853 // if irc_toggle_op fails, most probably we don't have a ChanServ and at this point we
854 // know we are not an op, so fail silently and don't try to kick
855 if (! irc_toggle_op(irc_conn, TRUE)) /// TODO: prüfen, ob das auch erfolreich war
856 return;
857 need_deop = TRUE;
860 // give the server a chance to set the op status for us before we make us of it,
861 // and let the victim read his *bang* message
862 g_usleep(2000000);
864 msg_len = g_snprintf(msg, sizeof msg, "KICK #%s %s\r\n", config->channel, nickname);
865 irc_send(irc_conn, msg, msg_len);
866 if (need_deop)
867 irc_toggle_op(irc_conn, FALSE);
871 static gboolean input_cb(GIOChannel *source, GIOCondition cond, gpointer data)
873 #if 1
874 gchar buf[1024];
875 guint buf_len;
876 irc_conn_t *irc = data;
877 gboolean ret = TRUE;
879 if (cond & (G_IO_ERR | G_IO_HUP))
880 return FALSE;
882 if ((buf_len = socket_fd_gets(irc->socket, buf, sizeof(buf))) != -1)
884 ret = process_line(irc, buf, buf_len);
886 #else
887 gsize buf_len;
888 irc_conn_t *irc = data;
890 if (cond & (G_IO_IN | G_IO_PRI))
892 gchar *buf = NULL;
893 GIOStatus rv;
894 GError *err = NULL;
898 rv = g_io_channel_read_line(source, &buf, &buf_len, NULL, &err);
899 if (buf != NULL)
901 buf_len -= 2;
902 buf[buf_len] = '\0'; // skip trailing \r\n
904 process_line(irc, buf, buf_len);
905 g_free(buf);
907 if (err != NULL)
909 debug("%s: error: %s", __func__, err->message);
910 g_error_free(err);
911 err = NULL;
914 while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
915 debug("%s: status %d\n", __func__, rv);
917 #endif
918 return ret;
922 void irc_send_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
924 static gchar tmp_msg[1024];
925 static gchar msg[1024];
926 guint msg_len;
927 va_list ap;
929 va_start(ap, format);
930 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
931 va_end(ap);
933 if (target)
934 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s, %s\r\n", config->channel, target, tmp_msg);
935 else
936 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s\r\n", config->channel, tmp_msg);
938 irc_send(irc_conn, msg, msg_len);
943 * target should not be NULL!
945 void irc_send_notice(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
947 static gchar tmp_msg[1024];
948 static gchar msg[1024];
949 guint msg_len;
950 va_list ap;
952 va_start(ap, format);
953 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
954 va_end(ap);
956 msg_len = g_snprintf(msg, sizeof msg, "NOTICE #%s :%s, %s\r\n", config->channel, target, tmp_msg);
958 irc_send(irc_conn, msg, msg_len);
962 // simple wrapper for send() to enable logging for sent commands
963 gint irc_send(irc_conn_t *irc_conn, const gchar *msg, guint msg_len)
965 irc_log(irc_conn, msg, TRUE);
966 return send(irc_conn->socket, msg, msg_len, MSG_NOSIGNAL);
970 void irc_goodbye(irc_conn_t *irc)
972 guint len;
973 gchar msg[256];
975 if (NZV(irc->quit_msg))
976 len = g_snprintf(msg, sizeof msg, "QUIT :%s\r\n", irc->quit_msg);
977 else
978 len = g_strlcpy(msg, "QUIT :Good bye. It was a pleasure to serve you\r\n", sizeof msg);
979 irc_send(irc, msg, len);
983 void irc_logfile_reopen(irc_conn_t *irc_conn)
985 TRACE
987 if (irc_conn->log_fd != NULL)
988 fclose(irc_conn->log_fd);
990 if (NZV(config->logfile))
992 irc_conn->log_fd = g_fopen(config->logfile, "a");
993 if (! irc_conn->log_fd)
994 vomak_warning("Logfile could not be opened.");
999 gint irc_finalize(irc_conn_t *irc_conn)
1001 if (irc_conn->socket < 0)
1002 return -1;
1004 if (irc_conn->lock_tag > 0)
1005 g_source_remove(irc_conn->lock_tag);
1007 if (irc_conn->log_fd != NULL)
1009 irc_log(irc_conn, "Stop logging\r\n", FALSE);
1010 fclose(irc_conn->log_fd);
1013 if (irc_conn->read_ioc)
1015 g_io_channel_shutdown(irc_conn->read_ioc, TRUE, NULL);
1016 g_io_channel_unref(irc_conn->read_ioc);
1017 irc_conn->read_ioc = NULL;
1019 socket_fd_close(irc_conn->socket);
1020 irc_conn->socket = -1;
1022 g_free(irc_conn->quit_msg);
1024 return 0;
1028 void irc_connect(irc_conn_t *irc_conn)
1030 struct hostent *he;
1031 struct sockaddr_in their_addr;
1032 gchar msg[256];
1033 guint msg_len;
1035 TRACE
1037 // Connect the socket to the server
1038 if ((he = gethostbyname(config->server)) == NULL)
1040 perror("gethostbyname");
1041 exit(1);
1044 if ((irc_conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
1046 perror("socket");
1047 exit(1);
1050 their_addr.sin_family = PF_INET;
1051 their_addr.sin_port = htons(6667);
1052 their_addr.sin_addr = *((struct in_addr *)he->h_addr_list[0]);
1053 memset(&(their_addr.sin_zero), '\0', 8);
1055 if (connect(irc_conn->socket, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
1057 perror("connect");
1058 exit(1);
1060 // Logging
1061 if (NZV(config->logfile))
1063 irc_conn->log_fd = g_fopen(config->logfile, "a");
1064 if (irc_conn->log_fd)
1065 irc_log(irc_conn, "Start logging\r\n", FALSE);
1066 else
1067 vomak_warning("Logfile could not be opened.");
1069 // say who we are
1070 msg_len = g_snprintf(msg, sizeof(msg), "USER %s %s %s :%s\r\n",
1071 config->username, config->servername, config->servername, config->realname);
1072 if (irc_send(irc_conn, msg, msg_len) == -1)
1074 perror("send USER");
1076 // and how we are called
1077 msg_len = g_snprintf(msg, sizeof(msg), "NICK %s\r\n", config->nickname);
1078 if (irc_send(irc_conn, msg, msg_len) == -1)
1080 perror("send NICK");
1082 // identify our nick
1083 if (NZV(config->nickserv_password))
1085 msg_len = g_snprintf(msg, sizeof(msg), "PRIVMSG nickserv :identify %s\r\n", config->nickserv_password);
1086 // don't use irc_send() here, no need to log our password
1087 if (send(irc_conn->socket, msg, msg_len, MSG_NOSIGNAL) == -1)
1089 perror("send NICKSERV");
1092 // join the channel
1093 msg_len = g_snprintf(msg, sizeof msg, "JOIN #%s\r\n", config->channel);
1094 if (irc_send(irc_conn, msg, msg_len) == -1)
1096 perror("send");
1099 // input callback, attached to the main loop
1100 irc_conn->read_ioc = g_io_channel_unix_new(irc_conn->socket);
1101 //~ g_io_channel_set_flags(irc_conn.read_ioc, G_IO_FLAG_NONBLOCK, NULL);
1102 g_io_channel_set_encoding(irc_conn->read_ioc, NULL, NULL);
1103 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);