Cleanly quit if we got an error
[vomak.git] / src / irc.c
bloba1d907aba53196cfcc12c6c6dda586b485d90abb
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 process_line(irc_conn_t *irc_conn, const gchar *line, guint len)
653 static gchar msg[1024];
654 guint msg_len;
655 gint response = get_response(line, len);
656 static gchar tmp_userlist[1024];
657 gchar *priv_sender;
658 const gchar *content;
659 static gboolean connected = FALSE;
661 // don't log the NAMES command's output (prevent log file spam)
662 if (response != 353 && response != 366)
663 irc_log(irc_conn, line, FALSE);
665 // An error occurred, try to quit cleanly and print the error
666 if ((response > 400 && response < 503))
668 // ignore Freenode's info messages sent with error code 477
669 // (see http://freenode.net/faq.shtml#freenode-info)
670 if (response != 477 || strstr(line, "[freenode-info]") == NULL)
672 #ifndef DEBUG
673 syslog(LOG_WARNING, "received error: %d (%s)", response, g_strstrip((gchar*) line));
674 #else
675 g_print("Error: %s", line);
676 #endif
677 g_free(irc_conn->quit_msg);
678 irc_conn->quit_msg = g_strdup("I got an error and better leave in advance. Bye.");
679 irc_goodbye(irc_conn);
680 main_quit();
681 return FALSE;
685 if (! connected)
687 if (response == 376)
688 connected = TRUE;
689 else
690 return TRUE;
693 content = irc_get_message(line, len);
695 // retrieve user name list
696 if (response == 353)
698 if (tmp_userlist[0] == '\0')
699 g_strlcpy(tmp_userlist, strchr(content, ':') + 1, sizeof(tmp_userlist));
700 else
701 g_strlcat(tmp_userlist, strchr(content, ':') + 1, sizeof(tmp_userlist));
703 // retrieve end of user name list
704 else if (response == 366)
706 if (tmp_userlist[0] != '\0')
708 set_user_list(tmp_userlist);
709 tmp_userlist[0] = '\0';
712 else if (! connected)
714 // don't do anything else until we got finished connecting (to skip MOTD messages)
716 // PING-PONG
717 else if (strncmp("PING :", line, 6) == 0)
719 msg_len = g_snprintf(msg, sizeof msg, "PONG %s\r\n", line + 6); // 7 = "PING :"
720 debug("PONG: -%s-\n", msg);
721 irc_send(irc_conn, msg, msg_len);
723 // handle private message
724 else if ((priv_sender = get_command_sender(line, len, "PRIVMSG")) != NULL)
726 // to be able to send private messages to users, you need to register your bot's
727 // nickname with Nickserv (at least on Freenode)
728 irc_send_private_message(irc_conn, priv_sender, "I don't like private messages!");
730 // handle kicks (we were kicked, bastards)
731 else if (get_command_sender(line, len, "KICK") != NULL)
733 if (config->auto_rejoin)
735 g_usleep(5000000); // just wait a little bit
736 msg_len = g_snprintf(msg, sizeof msg, "JOIN #%s\r\n", config->channel);
737 irc_send(irc_conn, msg, msg_len);
739 else
741 main_quit();
744 // Hi /me, acts on "hello $nickname" and "hi $nickname", hi and hello are case-insensitive
745 // Thanks /me
746 else if (strstr(content, config->nickname) != NULL)
748 const gchar *tmp_msg = irc_get_message_with_name(line, len, config->nickname);
750 if (strncasecmp("hi", content, 2) == 0 || strncasecmp("hello", content, 5) == 0 || strncasecmp("hey", content, 3) == 0 ||
751 strcasecmp(", hi", tmp_msg) == 0 || strcasecmp(", hello", tmp_msg) == 0 || strcasecmp(", hey", tmp_msg) == 0 ||
752 strcasecmp(": hi", tmp_msg) == 0 || strcasecmp(": hello", tmp_msg) == 0 || strcasecmp(": hey", tmp_msg) == 0)
754 irc_send_message(irc_conn, NULL,
755 "Hi %s. My name is %s and I'm here to offer additional services to you! "
756 "Try \"?? help\" for general information and \"?? vomak\" for information about me.",
757 get_nickname(line, len), config->nickname);
759 else if (strncasecmp("thanks", content, 6) == 0 || strncasecmp("thx", content, 3) == 0 ||
760 strcasecmp(", thanks", tmp_msg) == 0 || strcasecmp(", thx", tmp_msg) == 0 ||
761 strcasecmp(": thanks", tmp_msg) == 0 || strcasecmp(": thx", tmp_msg) == 0)
763 irc_send_message(irc_conn, get_nickname(line, len),
764 "no problem. It was a pleasure to serve you.");
767 // ?? ...
768 else if (strncmp(content, "?? ", 3) == 0)
770 help_system_query(content);
772 // pass to process_command() to process other commands (!beer, !test, !learn, ...)
773 else if (*content == '!')
775 process_command(irc_conn, line, len, content);
778 return TRUE;
783 * Please note that this will not work on Networks without ChanServ, e.g. on
784 * Quakenet or IRCnet. Your Bot has to be registered with NickServ and to be
785 * added to the channel access list for this to work.
787 static gboolean irc_toggle_op(irc_conn_t *irc_conn, gboolean request_op)
789 const gchar *cmd;
790 static gchar msg[1024];
791 guint msg_len;
793 if (irc_is_user_op(irc_conn, "ChanServ"))
795 cmd = (request_op) ? "op" : "deop";
796 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG ChanServ :%s #%s\r\n", cmd, config->channel);
797 irc_send(irc_conn, msg, msg_len);
799 return TRUE;
801 return FALSE; /* it seems we don't have a ChanServ bot */
805 static gboolean irc_is_user_op(irc_conn_t *irc_conn, const gchar *nickname)
807 const gchar *pos;
808 const gchar *userlist;
810 if (nickname == NULL)
811 return FALSE;
813 userlist = get_user_list();
815 if ( (pos = strstr(userlist, nickname)) )
817 if ( (pos - 1) >= userlist && (*(pos - 1) == '@') )
819 return TRUE;
822 else
824 #ifdef DEBUG
825 irc_send_message(irc_conn, NULL,
826 "Hey. There are crazy things going on here. (O.o)");
827 #else
828 syslog(LOG_WARNING, "user %s not found in names list of #%s", nickname, config->channel);
829 #endif
832 return FALSE;
836 static void irc_kick(irc_conn_t *irc_conn, const gchar *nickname)
838 static gchar msg[1024];
839 gboolean need_deop = FALSE;
840 guint msg_len;
842 TRACE
844 if (! irc_is_user_op(irc_conn, config->nickname))
846 // if irc_toggle_op fails, most probably we don't have a ChanServ and at this point we
847 // know we are not an op, so fail silently and don't try to kick
848 if (! irc_toggle_op(irc_conn, TRUE)) /// TODO: prĂ¼fen, ob das auch erfolreich war
849 return;
850 need_deop = TRUE;
853 // give the server a chance to set the op status for us before we make us of it,
854 // and let the victim read his *bang* message
855 g_usleep(2000000);
857 msg_len = g_snprintf(msg, sizeof msg, "KICK #%s %s\r\n", config->channel, nickname);
858 irc_send(irc_conn, msg, msg_len);
859 if (need_deop)
860 irc_toggle_op(irc_conn, FALSE);
864 static gboolean input_cb(GIOChannel *source, GIOCondition cond, gpointer data)
866 #if 1
867 gchar buf[1024];
868 guint buf_len;
869 irc_conn_t *irc = data;
870 gboolean ret = TRUE;
872 if (cond & (G_IO_ERR | G_IO_HUP))
873 return FALSE;
875 if ((buf_len = socket_fd_gets(irc->socket, buf, sizeof(buf))) != -1)
877 ret = process_line(irc, buf, buf_len);
879 #else
880 gsize buf_len;
881 irc_conn_t *irc = data;
883 if (cond & (G_IO_IN | G_IO_PRI))
885 gchar *buf = NULL;
886 GIOStatus rv;
887 GError *err = NULL;
891 rv = g_io_channel_read_line(source, &buf, &buf_len, NULL, &err);
892 if (buf != NULL)
894 buf_len -= 2;
895 buf[buf_len] = '\0'; // skip trailing \r\n
897 process_line(irc, buf, buf_len);
898 g_free(buf);
900 if (err != NULL)
902 debug("%s: error: %s", __func__, err->message);
903 g_error_free(err);
904 err = NULL;
907 while (rv == G_IO_STATUS_NORMAL || rv == G_IO_STATUS_AGAIN);
908 debug("%s: status %d\n", __func__, rv);
910 #endif
911 return ret;
915 void irc_send_message(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
917 static gchar tmp_msg[1024];
918 static gchar msg[1024];
919 guint msg_len;
920 va_list ap;
922 va_start(ap, format);
923 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
924 va_end(ap);
926 if (target)
927 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s, %s\r\n", config->channel, target, tmp_msg);
928 else
929 msg_len = g_snprintf(msg, sizeof msg, "PRIVMSG #%s :%s\r\n", config->channel, tmp_msg);
931 irc_send(irc_conn, msg, msg_len);
936 * target should not be NULL!
938 void irc_send_notice(irc_conn_t *irc_conn, const gchar *target, const gchar *format, ...)
940 static gchar tmp_msg[1024];
941 static gchar msg[1024];
942 guint msg_len;
943 va_list ap;
945 va_start(ap, format);
946 g_vsnprintf(tmp_msg, sizeof tmp_msg, format, ap);
947 va_end(ap);
949 msg_len = g_snprintf(msg, sizeof msg, "NOTICE #%s :%s, %s\r\n", config->channel, target, tmp_msg);
951 irc_send(irc_conn, msg, msg_len);
955 // simple wrapper for send() to enable logging for sent commands
956 gint irc_send(irc_conn_t *irc_conn, const gchar *msg, guint msg_len)
958 irc_log(irc_conn, msg, TRUE);
959 return send(irc_conn->socket, msg, msg_len, MSG_NOSIGNAL);
963 void irc_goodbye(irc_conn_t *irc)
965 guint len;
966 gchar msg[256];
968 if (NZV(irc->quit_msg))
969 len = g_snprintf(msg, sizeof msg, "QUIT :%s\r\n", irc->quit_msg);
970 else
971 len = g_strlcpy(msg, "QUIT :Good bye. It was a pleasure to serve you\r\n", sizeof msg);
972 irc_send(irc, msg, len);
976 void irc_logfile_reopen(irc_conn_t *irc_conn)
978 TRACE
980 if (irc_conn->log_fd != NULL)
981 fclose(irc_conn->log_fd);
983 if (NZV(config->logfile))
985 irc_conn->log_fd = g_fopen(config->logfile, "a");
986 if (! irc_conn->log_fd)
987 vomak_warning("Logfile could not be opened.");
992 gint irc_finalize(irc_conn_t *irc_conn)
994 if (irc_conn->socket < 0)
995 return -1;
997 if (irc_conn->lock_tag > 0)
998 g_source_remove(irc_conn->lock_tag);
1000 if (irc_conn->log_fd != NULL)
1002 irc_log(irc_conn, "Stop logging\r\n", FALSE);
1003 fclose(irc_conn->log_fd);
1006 if (irc_conn->read_ioc)
1008 g_io_channel_shutdown(irc_conn->read_ioc, TRUE, NULL);
1009 g_io_channel_unref(irc_conn->read_ioc);
1010 irc_conn->read_ioc = NULL;
1012 socket_fd_close(irc_conn->socket);
1013 irc_conn->socket = -1;
1015 g_free(irc_conn->quit_msg);
1017 return 0;
1021 void irc_connect(irc_conn_t *irc_conn)
1023 struct hostent *he;
1024 struct sockaddr_in their_addr;
1025 gchar msg[256];
1026 guint msg_len;
1028 TRACE
1030 // Connect the socket to the server
1031 if ((he = gethostbyname(config->server)) == NULL)
1033 perror("gethostbyname");
1034 exit(1);
1037 if ((irc_conn->socket = socket(PF_INET, SOCK_STREAM, 0)) == -1)
1039 perror("socket");
1040 exit(1);
1043 their_addr.sin_family = PF_INET;
1044 their_addr.sin_port = htons(6667);
1045 their_addr.sin_addr = *((struct in_addr *)he->h_addr_list[0]);
1046 memset(&(their_addr.sin_zero), '\0', 8);
1048 if (connect(irc_conn->socket, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
1050 perror("connect");
1051 exit(1);
1053 // Logging
1054 if (NZV(config->logfile))
1056 irc_conn->log_fd = g_fopen(config->logfile, "a");
1057 if (irc_conn->log_fd)
1058 irc_log(irc_conn, "Start logging\r\n", FALSE);
1059 else
1060 vomak_warning("Logfile could not be opened.");
1062 // say who we are
1063 msg_len = g_snprintf(msg, sizeof(msg), "USER %s %s %s :%s\r\n",
1064 config->username, config->servername, config->servername, config->realname);
1065 if (irc_send(irc_conn, msg, msg_len) == -1)
1067 perror("send USER");
1069 // and how we are called
1070 msg_len = g_snprintf(msg, sizeof(msg), "NICK %s\r\n", config->nickname);
1071 if (irc_send(irc_conn, msg, msg_len) == -1)
1073 perror("send NICK");
1075 // identify our nick
1076 if (NZV(config->nickserv_password))
1078 msg_len = g_snprintf(msg, sizeof(msg), "PRIVMSG nickserv :identify %s\r\n", config->nickserv_password);
1079 // don't use irc_send() here, no need to log our password
1080 if (send(irc_conn->socket, msg, msg_len, MSG_NOSIGNAL) == -1)
1082 perror("send NICKSERV");
1085 // join the channel
1086 msg_len = g_snprintf(msg, sizeof msg, "JOIN #%s\r\n", config->channel);
1087 if (irc_send(irc_conn, msg, msg_len) == -1)
1089 perror("send");
1092 // input callback, attached to the main loop
1093 irc_conn->read_ioc = g_io_channel_unix_new(irc_conn->socket);
1094 //~ g_io_channel_set_flags(irc_conn.read_ioc, G_IO_FLAG_NONBLOCK, NULL);
1095 g_io_channel_set_encoding(irc_conn->read_ioc, NULL, NULL);
1096 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);