Implement BLOWFISh, AES, and EXTERNAL SASL mechanisms
[rofl0r-ixchat.git] / src / common / proto-irc.c
blob20d8835c319dae4ba59bc8dac32701a701da489e
1 /* X-Chat
2 * Copyright (C) 2002 Peter Zelezny.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 /* IRC RFC1459(+commonly used extensions) protocol implementation */
21 #include <unistd.h>
22 #include <string.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <ctype.h>
26 #include <stdarg.h>
28 #include "xchat.h"
29 #include "ctcp.h"
30 #include "fe.h"
31 #include "ignore.h"
32 #include "inbound.h"
33 #include "modes.h"
34 #include "notify.h"
35 #include "plugin.h"
36 #include "server.h"
37 #include "text.h"
38 #include "outbound.h"
39 #include "util.h"
40 #include "xchatc.h"
41 #include "servlist.h"
43 static void
44 irc_login (server *serv, char *user, char *realname)
46 tcp_sendf (serv, "CAP LS\r\n"); /* start with CAP LS as Charybdis sasl.txt suggests */
47 serv->sent_capend = FALSE; /* track if we have finished */
49 if (serv->password[0] && serv->loginmethod == LOGIN_PASS)
51 tcp_sendf (serv, "PASS %s\r\n", serv->password);
54 tcp_sendf (serv,
55 "NICK %s\r\n"
56 "USER %s %s %s :%s\r\n",
57 serv->nick, user, user, serv->servername, realname);
60 static void
61 irc_nickserv (server *serv, char *cmd, char *arg1, char *arg2, char *arg3)
63 /* are all ircd authors idiots? */
64 switch (serv->loginmethod)
66 case LOGIN_MSG_NICKSERV:
67 tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
68 break;
69 case LOGIN_NICKSERV:
70 tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
71 break;
72 default: /* This may not work but at least it tries something when using /id or /ghost cmd */
73 tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
74 break;
75 #if 0
76 case LOGIN_MSG_NS:
77 tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
78 break;
79 case LOGIN_NS:
80 tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
81 break;
82 case LOGIN_AUTH:
83 /* why couldn't QuakeNet implement one of the existing ones? */
84 tcp_sendf (serv, "AUTH %s %s\r\n", arg1, arg2);
85 break;
86 #endif
90 static void
91 irc_ns_identify (server *serv, char *pass)
93 switch (serv->loginmethod)
95 case LOGIN_CHALLENGEAUTH:
96 tcp_sendf (serv, "PRIVMSG %s :CHALLENGE\r\n", CHALLENGEAUTH_NICK); /* request a challenge from Q */
97 break;
98 #if 0
99 case LOGIN_AUTH:
100 irc_nickserv (serv, "", serv->nick, pass, "");
101 break;
102 #endif
103 default:
104 irc_nickserv (serv, "IDENTIFY", pass, "", "");
108 static void
109 irc_ns_ghost (server *serv, char *usname, char *pass)
111 if (serv->loginmethod != LOGIN_CHALLENGEAUTH)
113 irc_nickserv (serv, "GHOST", usname, " ", pass);
117 static void
118 irc_join (server *serv, char *channel, char *key)
120 if (key[0])
121 tcp_sendf (serv, "JOIN %s %s\r\n", channel, key);
122 else
123 tcp_sendf (serv, "JOIN %s\r\n", channel);
126 static void
127 irc_join_list_flush (server *serv, GString *c, GString *k)
129 char *chanstr, *keystr;
131 chanstr = g_string_free (c, FALSE);
132 keystr = g_string_free (k, FALSE);
133 if (chanstr[0])
135 if (keystr[0])
136 tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr);
137 else
138 tcp_sendf (serv, "JOIN %s\r\n", chanstr);
140 g_free (chanstr);
141 g_free (keystr);
144 /* join a whole list of channels & keys, split to multiple lines
145 * to get around 512 limit */
147 static void
148 irc_join_list (server *serv, GSList *channels, GSList *keys)
150 GSList *clist;
151 GSList *klist;
152 GString *c = g_string_new (NULL);
153 GString *k = g_string_new (NULL);
154 int len;
155 int add;
156 int i, j;
158 i = j = 0;
159 len = 9; /* "JOIN<space><space>\r\n" */
160 clist = channels;
161 klist = keys;
163 while (clist)
165 /* measure how many bytes this channel would add... */
166 if (1)
168 add = strlen (clist->data);
169 if (i != 0)
170 add++; /* comma */
173 if (klist->data)
175 add += strlen (klist->data);
177 else
179 add++; /* 'x' filler */
182 if (j != 0)
183 add++; /* comma */
185 /* too big? dump buffer and start a fresh one */
186 if (len + add > 512)
188 irc_join_list_flush (serv, c, k);
190 c = g_string_new (NULL);
191 k = g_string_new (NULL);
192 i = j = 0;
193 len = 9;
196 /* now actually add it to our GStrings */
197 if (1)
199 add = strlen (clist->data);
200 if (i != 0)
202 add++;
203 g_string_append_c (c, ',');
205 g_string_append (c, clist->data);
206 i++;
209 if (klist->data)
211 add += strlen (klist->data);
212 if (j != 0)
214 add++;
215 g_string_append_c (k, ',');
217 g_string_append (k, klist->data);
218 j++;
220 else
222 add++;
223 if (j != 0)
225 add++;
226 g_string_append_c (k, ',');
228 g_string_append_c (k, 'x');
229 j++;
232 len += add;
234 klist = klist->next;
235 clist = clist->next;
238 irc_join_list_flush (serv, c, k);
241 static void
242 irc_part (server *serv, char *channel, char *reason)
244 if (reason[0])
245 tcp_sendf (serv, "PART %s :%s\r\n", channel, reason);
246 else
247 tcp_sendf (serv, "PART %s\r\n", channel);
250 static void
251 irc_quit (server *serv, char *reason)
253 if (reason[0])
254 tcp_sendf (serv, "QUIT :%s\r\n", reason);
255 else
256 tcp_send_len (serv, "QUIT\r\n", 6);
259 static void
260 irc_set_back (server *serv)
262 tcp_send_len (serv, "AWAY\r\n", 6);
265 static void
266 irc_set_away (server *serv, char *reason)
268 if (reason)
270 if (!reason[0])
271 reason = " ";
273 else
275 reason = " ";
278 tcp_sendf (serv, "AWAY :%s\r\n", reason);
281 static void
282 irc_ctcp (server *serv, char *to, char *msg)
284 tcp_sendf (serv, "PRIVMSG %s :\001%s\001\r\n", to, msg);
287 static void
288 irc_nctcp (server *serv, char *to, char *msg)
290 tcp_sendf (serv, "NOTICE %s :\001%s\001\r\n", to, msg);
293 static void
294 irc_cycle (server *serv, char *channel, char *key)
296 tcp_sendf (serv, "PART %s\r\nJOIN %s %s\r\n", channel, channel, key);
299 static void
300 irc_kick (server *serv, char *channel, char *nick, char *reason)
302 if (reason[0])
303 tcp_sendf (serv, "KICK %s %s :%s\r\n", channel, nick, reason);
304 else
305 tcp_sendf (serv, "KICK %s %s\r\n", channel, nick);
308 static void
309 irc_invite (server *serv, char *channel, char *nick)
311 tcp_sendf (serv, "INVITE %s %s\r\n", nick, channel);
314 static void
315 irc_mode (server *serv, char *target, char *mode)
317 tcp_sendf (serv, "MODE %s %s\r\n", target, mode);
320 /* find channel info when joined */
322 static void
323 irc_join_info (server *serv, char *channel)
325 tcp_sendf (serv, "MODE %s\r\n", channel);
328 /* initiate userlist retreival */
330 static void
331 irc_user_list (server *serv, char *channel)
333 if (serv->have_whox)
334 tcp_sendf (serv, "WHO %s %%chtsunfra,152\r\n", channel);
335 else
336 tcp_sendf (serv, "WHO %s\r\n", channel);
339 /* userhost */
341 static void
342 irc_userhost (server *serv, char *nick)
344 tcp_sendf (serv, "USERHOST %s\r\n", nick);
347 static void
348 irc_away_status (server *serv, char *channel)
350 if (serv->have_whox)
351 tcp_sendf (serv, "WHO %s %%chtsunfra,152\r\n", channel);
352 else
353 tcp_sendf (serv, "WHO %s\r\n", channel);
356 /*static void
357 irc_get_ip (server *serv, char *nick)
359 tcp_sendf (serv, "WHO %s\r\n", nick);
364 * Command: WHOIS
365 * Parameters: [<server>] <nickmask>[,<nickmask>[,...]]
367 static void
368 irc_user_whois (server *serv, char *nicks)
370 tcp_sendf (serv, "WHOIS %s\r\n", nicks);
373 static void
374 irc_message (server *serv, char *channel, char *text)
376 tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text);
379 static void
380 irc_action (server *serv, char *channel, char *act)
382 tcp_sendf (serv, "PRIVMSG %s :\001ACTION %s\001\r\n", channel, act);
385 static void
386 irc_notice (server *serv, char *channel, char *text)
388 tcp_sendf (serv, "NOTICE %s :%s\r\n", channel, text);
391 static void
392 irc_topic (server *serv, char *channel, char *topic)
394 if (!topic)
395 tcp_sendf (serv, "TOPIC %s :\r\n", channel);
396 else if (topic[0])
397 tcp_sendf (serv, "TOPIC %s :%s\r\n", channel, topic);
398 else
399 tcp_sendf (serv, "TOPIC %s\r\n", channel);
402 static void
403 irc_list_channels (server *serv, char *arg, int min_users)
405 if (arg[0])
407 tcp_sendf (serv, "LIST %s\r\n", arg);
408 return;
411 if (serv->use_listargs)
412 tcp_sendf (serv, "LIST >%d,<10000\r\n", min_users - 1);
413 else
414 tcp_send_len (serv, "LIST\r\n", 6);
417 static void
418 irc_names (server *serv, char *channel)
420 tcp_sendf (serv, "NAMES %s\r\n", channel);
423 static void
424 irc_change_nick (server *serv, char *new_nick)
426 tcp_sendf (serv, "NICK %s\r\n", new_nick);
429 static void
430 irc_ping (server *serv, char *to, char *timestring)
432 if (*to)
433 tcp_sendf (serv, "PRIVMSG %s :\001PING %s\001\r\n", to, timestring);
434 else
435 tcp_sendf (serv, "PING %s\r\n", timestring);
438 static int
439 irc_raw (server *serv, char *raw)
441 int len;
442 char tbuf[4096];
443 if (*raw)
445 len = strlen (raw);
446 if (len < sizeof (tbuf) - 3)
448 len = snprintf (tbuf, sizeof (tbuf), "%s\r\n", raw);
449 tcp_send_len (serv, tbuf, len);
450 } else
452 tcp_send_len (serv, raw, len);
453 tcp_send_len (serv, "\r\n", 2);
455 return TRUE;
457 return FALSE;
460 /* ============================================================== */
461 /* ======================= IRC INPUT ============================ */
462 /* ============================================================== */
465 static void
466 channel_date (session *sess, char *chan, char *timestr)
468 time_t timestamp = (time_t) atol (timestr);
469 char *tim = ctime (&timestamp);
470 tim[24] = 0; /* get rid of the \n */
471 EMIT_SIGNAL (XP_TE_CHANDATE, sess, chan, tim, NULL, NULL, 0);
474 static void
475 process_numeric (session * sess, int n,
476 char *word[], char *word_eol[], char *text)
478 server *serv = sess->server;
479 /* show whois is the server tab */
480 session *whois_sess = serv->server_session;
482 /* unless this setting is on */
483 if (prefs.irc_whois_front)
484 whois_sess = serv->front_session;
486 char *ex;
488 switch (n)
490 case 1:
491 inbound_login_start (sess, word[3], word[1]);
492 /* if network is PTnet then you must get your IP address
493 from "001" server message */
494 if ((strncmp(word[7], "PTnet", 5) == 0) &&
495 (strncmp(word[8], "IRC", 3) == 0) &&
496 (strncmp(word[9], "Network", 7) == 0) &&
497 (strrchr(word[10], '@') != NULL))
499 serv->use_who = FALSE;
500 if (prefs.ip_from_server)
501 inbound_foundip (sess, strrchr(word[10], '@')+1);
504 goto def;
506 case 4: /* check the ircd type */
507 serv->use_listargs = FALSE;
508 serv->modes_per_line = 3; /* default to IRC RFC */
509 if (strncmp (word[5], "bahamut", 7) == 0) /* DALNet */
511 serv->use_listargs = TRUE; /* use the /list args */
512 } else if (strncmp (word[5], "u2.10.", 6) == 0) /* Undernet */
514 serv->use_listargs = TRUE; /* use the /list args */
515 serv->modes_per_line = 6; /* allow 6 modes per line */
516 } else if (strncmp (word[5], "glx2", 4) == 0)
518 serv->use_listargs = TRUE; /* use the /list args */
520 goto def;
522 case 5:
523 inbound_005 (serv, word);
524 goto def;
526 case 263: /*Server load is temporarily too heavy */
527 if (fe_is_chanwindow (sess->server))
529 fe_chan_list_end (sess->server);
530 fe_message (word_eol[5] + 1, FE_MSG_ERROR);
532 goto def;
534 case 301:
535 inbound_away (serv, word[4],
536 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
537 break;
539 case 302:
540 if (serv->skip_next_userhost)
542 char *eq = strchr (word[4], '=');
543 if (eq)
545 *eq = 0;
546 if (!serv->p_cmp (word[4] + 1, serv->nick))
548 char *at = strrchr (eq + 1, '@');
549 if (at)
550 inbound_foundip (sess, at + 1);
554 serv->skip_next_userhost = FALSE;
555 break;
557 else goto def;
559 case 303:
560 word[4]++;
561 notify_markonline (serv, word);
562 break;
564 case 305:
565 inbound_uback (serv);
566 goto def;
568 case 306:
569 inbound_uaway (serv);
570 goto def;
572 case 312:
573 if (!serv->skip_next_whois)
574 EMIT_SIGNAL (XP_TE_WHOIS3, whois_sess, word[4], word_eol[5], NULL, NULL, 0);
575 else
576 inbound_user_info (sess, NULL, NULL, NULL, word[5], word[4], NULL, NULL, 0xff);
577 break;
579 case 311: /* WHOIS 1st line */
580 serv->inside_whois = 1;
581 inbound_user_info_start (sess, word[4]);
582 if (!serv->skip_next_whois)
583 EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
584 word[6], word_eol[8] + 1, 0);
585 else
586 inbound_user_info (sess, NULL, word[5], word[6], NULL, word[4],
587 word_eol[8][0] == ':' ? word_eol[8] + 1 : word_eol[8], NULL, 0xff);
588 break;
590 case 314: /* WHOWAS */
591 inbound_user_info_start (sess, word[4]);
592 EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
593 word[6], word_eol[8] + 1, 0);
594 break;
596 case 317:
597 if (!serv->skip_next_whois)
599 time_t timestamp = (time_t) atol (word[6]);
600 long idle = atol (word[5]);
601 char *tim;
602 char outbuf[64];
604 snprintf (outbuf, sizeof (outbuf),
605 "%02ld:%02ld:%02ld", idle / 3600, (idle / 60) % 60,
606 idle % 60);
607 if (timestamp == 0)
608 EMIT_SIGNAL (XP_TE_WHOIS4, whois_sess, word[4],
609 outbuf, NULL, NULL, 0);
610 else
612 tim = ctime (&timestamp);
613 tim[19] = 0; /* get rid of the \n */
614 EMIT_SIGNAL (XP_TE_WHOIS4T, whois_sess, word[4],
615 outbuf, tim, NULL, 0);
618 break;
620 case 318: /* END OF WHOIS */
621 if (!serv->skip_next_whois)
622 EMIT_SIGNAL (XP_TE_WHOIS6, whois_sess, word[4], NULL,
623 NULL, NULL, 0);
624 serv->skip_next_whois = 0;
625 serv->inside_whois = 0;
626 break;
628 case 313:
629 case 319:
630 if (!serv->skip_next_whois)
631 EMIT_SIGNAL (XP_TE_WHOIS2, whois_sess, word[4],
632 word_eol[5] + 1, NULL, NULL, 0);
633 break;
635 case 307: /* dalnet version */
636 case 320: /* :is an identified user */
637 if (!serv->skip_next_whois)
638 EMIT_SIGNAL (XP_TE_WHOIS_ID, whois_sess, word[4],
639 word_eol[5] + 1, NULL, NULL, 0);
640 break;
642 case 321:
643 if (!fe_is_chanwindow (sess->server))
644 EMIT_SIGNAL (XP_TE_CHANLISTHEAD, serv->server_session, NULL, NULL, NULL, NULL, 0);
645 break;
647 case 322:
648 if (fe_is_chanwindow (sess->server))
650 fe_add_chan_list (sess->server, word[4], word[5], word_eol[6] + 1);
651 } else
653 PrintTextf (serv->server_session, "%-16s %-7d %s\017\n",
654 word[4], atoi (word[5]), word_eol[6] + 1);
656 break;
658 case 323:
659 if (!fe_is_chanwindow (sess->server))
660 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], word[2], NULL, 0);
661 else
662 fe_chan_list_end (sess->server);
663 break;
665 case 324:
666 sess = find_channel (serv, word[4]);
667 if (!sess)
668 sess = serv->server_session;
669 if (sess->ignore_mode)
670 sess->ignore_mode = FALSE;
671 else
672 EMIT_SIGNAL (XP_TE_CHANMODES, sess, word[4], word_eol[5],
673 NULL, NULL, 0);
674 fe_update_mode_buttons (sess, 't', '-');
675 fe_update_mode_buttons (sess, 'n', '-');
676 fe_update_mode_buttons (sess, 's', '-');
677 fe_update_mode_buttons (sess, 'i', '-');
678 fe_update_mode_buttons (sess, 'p', '-');
679 fe_update_mode_buttons (sess, 'm', '-');
680 fe_update_mode_buttons (sess, 'l', '-');
681 fe_update_mode_buttons (sess, 'k', '-');
682 handle_mode (serv, word, word_eol, "", TRUE);
683 break;
685 case 329:
686 sess = find_channel (serv, word[4]);
687 if (sess)
689 if (sess->ignore_date)
690 sess->ignore_date = FALSE;
691 else
692 channel_date (sess, word[4], word[5]);
694 break;
696 case 330:
697 if (!serv->skip_next_whois)
698 EMIT_SIGNAL (XP_TE_WHOIS_AUTH, whois_sess, word[4],
699 word_eol[6] + 1, word[5], NULL, 0);
700 inbound_user_info (sess, NULL, NULL, NULL, NULL, word[4], NULL, word[5], 0xff);
701 break;
703 case 332:
704 inbound_topic (serv, word[4],
705 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
706 break;
708 case 333:
709 inbound_topictime (serv, word[4], word[5], atol (word[6]));
710 break;
712 #if 0
713 case 338: /* Undernet Real user@host, Real IP */
714 EMIT_SIGNAL (XP_TE_WHOIS_REALHOST, sess, word[4], word[5], word[6],
715 (word_eol[7][0]==':') ? word_eol[7]+1 : word_eol[7], 0);
716 break;
717 #endif
719 case 341: /* INVITE ACK */
720 EMIT_SIGNAL (XP_TE_UINVITE, sess, word[4], word[5], serv->servername,
721 NULL, 0);
722 break;
724 case 352: /* WHO */
726 unsigned int away = 0;
727 session *who_sess = find_channel (serv, word[4]);
729 if (*word[9] == 'G')
730 away = 1;
732 inbound_user_info (sess, word[4], word[5], word[6], word[7],
733 word[8], word_eol[11], NULL, away);
735 /* try to show only user initiated whos */
736 if (!who_sess || !who_sess->doing_who)
737 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
738 word[2], NULL, 0);
740 break;
742 case 354: /* undernet WHOX: used as a reply for irc_away_status */
744 unsigned int away = 0;
745 session *who_sess;
747 /* irc_away_status and irc_user_list sends out a "152" */
748 if (!strcmp (word[4], "152"))
750 who_sess = find_channel (serv, word[5]);
752 if (*word[10] == 'G')
753 away = 1;
755 /* :server 354 yournick 152 #channel ~ident host servname nick H account :realname */
756 inbound_user_info (sess, word[5], word[6], word[7], word[8],
757 word[9], word_eol[12]+1, word[11], away);
759 /* try to show only user initiated whos */
760 if (!who_sess || !who_sess->doing_who)
761 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
762 word[1], word[2], NULL, 0);
763 } else
764 goto def;
766 break;
768 case 315: /* END OF WHO */
770 session *who_sess;
771 who_sess = find_channel (serv, word[4]);
772 if (who_sess)
774 if (!who_sess->doing_who)
775 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
776 word[1], word[2], NULL, 0);
777 who_sess->doing_who = FALSE;
778 } else
780 if (!serv->doing_dns)
781 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
782 word[1], word[2], NULL, 0);
783 serv->doing_dns = FALSE;
786 break;
788 case 348: /* +e-list entry */
789 if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], TRUE))
790 goto def;
791 break;
793 case 349: /* end of exemption list */
794 sess = find_channel (serv, word[4]);
795 if (!sess)
797 sess = serv->front_session;
798 goto def;
800 if (!fe_is_banwindow (sess))
801 goto def;
802 fe_ban_list_end (sess, TRUE);
803 break;
805 case 353: /* NAMES */
806 inbound_nameslist (serv, word[5],
807 (word_eol[6][0] == ':') ? word_eol[6] + 1 : word_eol[6]);
808 break;
810 case 366:
811 if (!inbound_nameslist_end (serv, word[4]))
812 goto def;
813 break;
815 case 367: /* banlist entry */
816 inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], FALSE);
817 break;
819 case 368:
820 sess = find_channel (serv, word[4]);
821 if (!sess)
823 sess = serv->front_session;
824 goto def;
826 if (!fe_is_banwindow (sess))
827 goto def;
828 fe_ban_list_end (sess, FALSE);
829 break;
831 case 369: /* WHOWAS end */
832 case 406: /* WHOWAS error */
833 EMIT_SIGNAL (XP_TE_SERVTEXT, whois_sess, text, word[1], word[2], NULL, 0);
834 serv->inside_whois = 0;
835 break;
837 case 372: /* motd text */
838 case 375: /* motd start */
839 if (!prefs.skipmotd || serv->motd_skipped)
840 EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, NULL,
841 NULL, 0);
842 break;
844 case 376: /* end of motd */
845 case 422: /* motd file is missing */
846 inbound_login_end (sess, text);
847 break;
849 case 433: /* nickname in use */
850 case 432: /* erroneous nickname */
851 if (serv->end_of_motd)
852 goto def;
853 inbound_next_nick (sess, word[4]);
854 break;
856 case 437:
857 if (serv->end_of_motd || is_channel (serv, word[4]))
858 goto def;
859 inbound_next_nick (sess, word[4]);
860 break;
862 case 471:
863 EMIT_SIGNAL (XP_TE_USERLIMIT, sess, word[4], NULL, NULL, NULL, 0);
864 break;
866 case 473:
867 EMIT_SIGNAL (XP_TE_INVITE, sess, word[4], NULL, NULL, NULL, 0);
868 break;
870 case 474:
871 EMIT_SIGNAL (XP_TE_BANNED, sess, word[4], NULL, NULL, NULL, 0);
872 break;
874 case 475:
875 EMIT_SIGNAL (XP_TE_KEYWORD, sess, word[4], NULL, NULL, NULL, 0);
876 break;
878 case 601:
879 notify_set_offline (serv, word[4], FALSE);
880 break;
882 case 605:
883 notify_set_offline (serv, word[4], TRUE);
884 break;
886 case 600:
887 case 604:
888 notify_set_online (serv, word[4]);
889 break;
891 case 730: /* RPL_MONONLINE */
892 ex = strchr (word[4], '!'); /* only send the nick */
893 if (ex)
894 ex[0] = 0;
895 notify_set_online (serv, word[4] + 1);
896 break;
898 case 731: /* RPL_MONOFFLINE */
899 ex = strchr (word[4], '!'); /* only send the nick */
900 if (ex)
901 ex[0] = 0;
902 notify_set_offline (serv, word[4] + 1, FALSE);
903 break;
905 case 900: /* successful SASL 'logged in as ' */
906 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, word_eol[6]+1, word[1], word[2], NULL, 0);
907 break;
908 case 903: /* successful SASL auth */
909 case 904: /* failed SASL auth */
910 if (inbound_sasl_error (serv))
911 break; /* might retry */
912 case 905: /* failed SASL auth */
913 case 906: /* aborted */
914 case 907: /* attempting to re-auth after a successful auth */
915 EMIT_SIGNAL (XP_TE_SASLRESPONSE, serv->server_session, word[1], word[2], word[3], ++word_eol[4], 0);
916 if (!serv->sent_capend)
918 serv->sent_capend = TRUE;
919 tcp_send_len (serv, "CAP END\r\n", 9);
921 break;
923 default:
925 if (serv->inside_whois && word[4][0])
927 /* some unknown WHOIS reply, ircd coders make them up weekly */
928 if (!serv->skip_next_whois)
929 EMIT_SIGNAL (XP_TE_WHOIS_SPECIAL, whois_sess, word[4],
930 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5],
931 word[2], NULL, 0);
932 return;
935 def:
936 if (is_channel (serv, word[4]))
938 session *realsess = find_channel (serv, word[4]);
939 if (!realsess)
940 realsess = serv->server_session;
941 EMIT_SIGNAL (XP_TE_SERVTEXT, realsess, text, word[1], word[2], NULL, 0);
942 } else
944 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
945 word[2], NULL, 0);
950 /* handle named messages that starts with a ':' */
952 static void
953 process_named_msg (session *sess, char *type, char *word[], char *word_eol[])
955 server *serv = sess->server;
956 char ip[128], nick[NICKLEN];
957 char *text, *ex;
958 int len = strlen (type);
960 /* fill in the "ip" and "nick" buffers */
961 ex = strchr (word[1], '!');
962 if (!ex) /* no '!', must be a server message */
964 safe_strcpy (ip, word[1], sizeof (ip));
965 safe_strcpy (nick, word[1], sizeof (nick));
966 } else
968 safe_strcpy (ip, ex + 1, sizeof (ip));
969 ex[0] = 0;
970 safe_strcpy (nick, word[1], sizeof (nick));
971 ex[0] = '!';
974 if (len == 4)
976 guint32 t;
978 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
979 /* this should compile to a bunch of: CMP.L, JE ... nice & fast */
980 switch (t)
983 case WORDL('A','C','C','O'):
984 inbound_account (serv, nick, word[3]);
985 return;
987 case WORDL('J','O','I','N'):
989 char *chan = word[3];
990 char *account = word[4];
991 char *realname = word_eol[5] + 1;
993 if (*chan == ':')
994 chan++;
995 if (!serv->p_cmp (nick, serv->nick))
996 inbound_ujoin (serv, chan, nick, ip);
997 else
998 inbound_join (serv, chan, nick, ip, account, realname);
1000 return;
1002 case WORDL('K','I','C','K'):
1004 char *kicked = word[4];
1005 char *reason = word_eol[5];
1006 if (*kicked)
1008 if (*reason == ':')
1009 reason++;
1010 if (!strcmp (kicked, serv->nick))
1011 inbound_ukick (serv, word[3], nick, reason);
1012 else
1013 inbound_kick (serv, word[3], kicked, nick, reason);
1016 return;
1018 case WORDL('K','I','L','L'):
1019 EMIT_SIGNAL (XP_TE_KILL, sess, nick, word_eol[5], NULL, NULL, 0);
1020 return;
1022 case WORDL('M','O','D','E'):
1023 handle_mode (serv, word, word_eol, nick, FALSE); /* modes.c */
1024 return;
1026 case WORDL('N','I','C','K'):
1027 inbound_newnick (serv, nick, (word_eol[3][0] == ':')
1028 ? word_eol[3] + 1 : word_eol[3], FALSE);
1029 return;
1031 case WORDL('P','A','R','T'):
1033 char *chan = word[3];
1034 char *reason = word_eol[4];
1036 if (*chan == ':')
1037 chan++;
1038 if (*reason == ':')
1039 reason++;
1040 if (!strcmp (nick, serv->nick))
1041 inbound_upart (serv, chan, ip, reason);
1042 else
1043 inbound_part (serv, chan, nick, ip, reason);
1045 return;
1047 case WORDL('P','O','N','G'):
1048 inbound_ping_reply (serv->server_session,
1049 (word[4][0] == ':') ? word[4] + 1 : word[4], word[3]);
1050 return;
1052 case WORDL('Q','U','I','T'):
1053 inbound_quit (serv, nick, ip,
1054 (word_eol[3][0] == ':') ? word_eol[3] + 1 : word_eol[3]);
1055 return;
1057 case WORDL('A','W','A','Y'):
1058 inbound_away_notify (serv, nick,
1059 (word_eol[3][0] == ':') ? word_eol[3] + 1 : NULL);
1060 return;
1063 goto garbage;
1066 else if(len == 3) {
1067 guint32 t;
1069 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
1070 switch (t)
1072 case WORDL('C','A','P','\0'):
1073 if (strncasecmp (word[4], "ACK", 3) == 0)
1075 inbound_cap_ack (serv, word[1],
1076 word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5]);
1078 else if (strncasecmp (word[4], "LS", 2) == 0)
1080 inbound_cap_ls (serv, word[1],
1081 word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5]);
1083 else if (strncasecmp (word[4], "NAK", 3) == 0)
1085 inbound_cap_nak (serv);
1087 else if (strncasecmp (word[4], "LIST", 4) == 0)
1089 inbound_cap_list (serv, word[1],
1090 word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5]);
1093 return;
1098 else if (len >= 5)
1100 guint32 t;
1102 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
1103 /* this should compile to a bunch of: CMP.L, JE ... nice & fast */
1104 switch (t)
1106 case WORDL('I','N','V','I'):
1107 if (ignore_check (word[1], IG_INVI))
1108 return;
1110 if (word[4][0] == ':')
1111 EMIT_SIGNAL (XP_TE_INVITED, sess, word[4] + 1, nick,
1112 serv->servername, NULL, 0);
1113 else
1114 EMIT_SIGNAL (XP_TE_INVITED, sess, word[4], nick,
1115 serv->servername, NULL, 0);
1117 return;
1119 case WORDL('N','O','T','I'):
1121 int id = FALSE;
1122 char *response;
1124 text = word_eol[4];
1125 if (*text == ':')
1127 text++;
1130 if (!strncmp (text, "CHALLENGE ", 10)) /* QuakeNet CHALLENGE upon our request */
1132 response = challengeauth_response (((ircnet *)serv->network)->user ? ((ircnet *)serv->network)->user : prefs.username, serv->password, word[5]);
1134 tcp_sendf (serv, "PRIVMSG %s :CHALLENGEAUTH %s %s %s\r\n",
1135 CHALLENGEAUTH_NICK,
1136 ((ircnet *)serv->network)->user ? ((ircnet *)serv->network)->user : prefs.username,
1137 response,
1138 CHALLENGEAUTH_ALGO);
1140 g_free (response);
1141 return; /* omit the CHALLENGE <hash> ALGOS message */
1144 if (serv->have_idmsg)
1146 if (*text == '+')
1148 id = TRUE;
1149 text++;
1150 } else if (*text == '-')
1151 text++;
1154 if (!ignore_check (word[1], IG_NOTI))
1155 inbound_notice (serv, word[3], nick, text, ip, id);
1157 return;
1159 case WORDL('P','R','I','V'):
1161 char *to = word[3];
1162 int len;
1163 int id = FALSE; /* identified */
1164 if (*to)
1166 text = word_eol[4];
1167 if (*text == ':')
1168 text++;
1169 if (serv->have_idmsg)
1171 if (*text == '+')
1173 id = TRUE;
1174 text++;
1175 } else if (*text == '-')
1176 text++;
1178 len = strlen (text);
1179 if (text[0] == 1 && text[len - 1] == 1) /* ctcp */
1181 text[len - 1] = 0;
1182 text++;
1183 if (strncasecmp (text, "ACTION", 6) != 0)
1184 flood_check (nick, ip, serv, sess, 0);
1185 if (strncasecmp (text, "DCC ", 4) == 0)
1186 /* redo this with handle_quotes TRUE */
1187 process_data_init (word[1], word_eol[1], word, word_eol, TRUE, FALSE);
1188 ctcp_handle (sess, to, nick, ip, text, word, word_eol, id);
1189 } else
1191 if (is_channel (serv, to))
1193 if (ignore_check (word[1], IG_CHAN))
1194 return;
1195 inbound_chanmsg (serv, NULL, to, nick, text, FALSE, id);
1196 } else
1198 if (ignore_check (word[1], IG_PRIV))
1199 return;
1200 inbound_privmsg (serv, nick, ip, text, id);
1205 return;
1207 case WORDL('T','O','P','I'):
1208 inbound_topicnew (serv, nick, word[3],
1209 (word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4]);
1210 return;
1212 case WORDL('W','A','L','L'):
1213 text = word_eol[3];
1214 if (*text == ':')
1215 text++;
1216 EMIT_SIGNAL (XP_TE_WALLOPS, sess, nick, text, NULL, NULL, 0);
1217 return;
1221 garbage:
1222 /* unknown message */
1223 PrintTextf (sess, "GARBAGE: %s\n", word_eol[1]);
1226 /* handle named messages that DON'T start with a ':' */
1228 static void
1229 process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol[])
1231 sess = sess->server->server_session;
1233 if (!strncmp (buf, "PING ", 5))
1235 tcp_sendf (sess->server, "PONG %s\r\n", buf + 5);
1236 return;
1238 if (!strncmp (buf, "ERROR", 5))
1240 EMIT_SIGNAL (XP_TE_SERVERERROR, sess, buf + 7, NULL, NULL, NULL, 0);
1241 return;
1243 if (!strncmp (buf, "NOTICE ", 7))
1245 buf = word_eol[3];
1246 if (*buf == ':')
1247 buf++;
1248 EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, buf, sess->server->servername, NULL, NULL, 0);
1249 return;
1251 if (!strncmp (buf, "AUTHENTICATE", 12))
1253 inbound_sasl_authenticate (sess->server, word_eol[2]);
1254 return;
1257 EMIT_SIGNAL (XP_TE_SERVTEXT, sess, buf, sess->server->servername, rawname, NULL, 0);
1260 /* irc_inline() - 1 single line received from serv */
1262 static void
1263 irc_inline (server *serv, char *buf, int len)
1265 session *sess, *tmp;
1266 char *type, *text;
1267 char *word[PDIWORDS+1];
1268 char *word_eol[PDIWORDS+1];
1269 char pdibuf_static[522]; /* 1 line can potentially be 512*6 in utf8 */
1270 char *pdibuf = pdibuf_static;
1272 url_check_line (buf, len);
1274 /* need more than 522? fall back to malloc */
1275 if (len >= sizeof (pdibuf_static))
1276 pdibuf = malloc (len + 1);
1278 sess = serv->front_session;
1280 /* Python relies on this */
1281 word[PDIWORDS] = NULL;
1282 word_eol[PDIWORDS] = NULL;
1284 if (buf[0] == ':')
1286 /* split line into words and words_to_end_of_line */
1287 process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
1289 /* find a context for this message */
1290 if (is_channel (serv, word[3]))
1292 tmp = find_channel (serv, word[3]);
1293 if (tmp)
1294 sess = tmp;
1297 /* for server messages, the 2nd word is the "message type" */
1298 type = word[2];
1300 word[0] = type;
1301 word_eol[1] = buf; /* keep the ":" for plugins */
1302 if (plugin_emit_server (sess, type, word, word_eol))
1303 goto xit;
1304 word[1]++;
1305 word_eol[1] = buf + 1; /* but not for xchat internally */
1307 } else
1309 process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
1310 word[0] = type = word[1];
1311 if (plugin_emit_server (sess, type, word, word_eol))
1312 goto xit;
1315 if (buf[0] != ':')
1317 process_named_servermsg (sess, buf, word[0], word_eol);
1318 goto xit;
1321 /* see if the second word is a numeric */
1322 if (isdigit ((unsigned char) word[2][0]))
1324 text = word_eol[4];
1325 if (*text == ':')
1326 text++;
1328 process_numeric (sess, atoi (word[2]), word, word_eol, text);
1329 } else
1331 process_named_msg (sess, type, word, word_eol);
1334 xit:
1335 if (pdibuf != pdibuf_static)
1336 free (pdibuf);
1339 void
1340 proto_fill_her_up (server *serv)
1342 serv->p_inline = irc_inline;
1343 serv->p_invite = irc_invite;
1344 serv->p_cycle = irc_cycle;
1345 serv->p_ctcp = irc_ctcp;
1346 serv->p_nctcp = irc_nctcp;
1347 serv->p_quit = irc_quit;
1348 serv->p_kick = irc_kick;
1349 serv->p_part = irc_part;
1350 serv->p_ns_identify = irc_ns_identify;
1351 serv->p_ns_ghost = irc_ns_ghost;
1352 serv->p_join = irc_join;
1353 serv->p_join_list = irc_join_list;
1354 serv->p_login = irc_login;
1355 serv->p_join_info = irc_join_info;
1356 serv->p_mode = irc_mode;
1357 serv->p_user_list = irc_user_list;
1358 serv->p_away_status = irc_away_status;
1359 /*serv->p_get_ip = irc_get_ip;*/
1360 serv->p_whois = irc_user_whois;
1361 serv->p_get_ip = irc_user_list;
1362 serv->p_get_ip_uh = irc_userhost;
1363 serv->p_set_back = irc_set_back;
1364 serv->p_set_away = irc_set_away;
1365 serv->p_message = irc_message;
1366 serv->p_action = irc_action;
1367 serv->p_notice = irc_notice;
1368 serv->p_topic = irc_topic;
1369 serv->p_list_channels = irc_list_channels;
1370 serv->p_change_nick = irc_change_nick;
1371 serv->p_names = irc_names;
1372 serv->p_ping = irc_ping;
1373 serv->p_raw = irc_raw;
1374 serv->p_cmp = rfc_casecmp; /* can be changed by 005 in modes.c */