Request 'sasl' even if it's the only recognized cap
[rofl0r-ixchat.git] / src / common / proto-irc.c
blobd644c68f4253f91d2346371ad5cf475d88b7c0c2
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"
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 */
48 if (serv->password[0])
49 tcp_sendf (serv, "PASS %s\r\n", serv->password);
51 tcp_sendf (serv,
52 "NICK %s\r\n"
53 "USER %s %s %s :%s\r\n",
54 serv->nick, user, user, serv->servername, realname);
57 static void
58 irc_nickserv (server *serv, char *cmd, char *arg1, char *arg2, char *arg3)
60 /* are all ircd authors idiots? */
61 switch (serv->nickservtype)
63 case 0:
64 tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
65 break;
66 case 1:
67 tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
68 break;
69 case 2:
70 tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
71 break;
72 case 3:
73 tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
74 break;
75 case 4:
76 /* why couldn't QuakeNet implement one of the existing ones? */
77 tcp_sendf (serv, "AUTH %s%s%s\r\n", cmd, arg1, arg2, arg3);
81 static void
82 irc_ns_identify (server *serv, char *pass)
84 irc_nickserv (serv, "IDENTIFY", pass, "", "");
87 static void
88 irc_ns_ghost (server *serv, char *usname, char *pass)
90 if (serv->nickservtype != 4)
91 irc_nickserv (serv, "GHOST", usname, " ", pass);
94 static void
95 irc_join (server *serv, char *channel, char *key)
97 if (key[0])
98 tcp_sendf (serv, "JOIN %s %s\r\n", channel, key);
99 else
100 tcp_sendf (serv, "JOIN %s\r\n", channel);
103 static void
104 irc_join_list_flush (server *serv, GString *c, GString *k)
106 char *chanstr, *keystr;
108 chanstr = g_string_free (c, FALSE);
109 keystr = g_string_free (k, FALSE);
110 if (chanstr[0])
112 if (keystr[0])
113 tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr);
114 else
115 tcp_sendf (serv, "JOIN %s\r\n", chanstr);
117 g_free (chanstr);
118 g_free (keystr);
121 /* join a whole list of channels & keys, split to multiple lines
122 * to get around 512 limit */
124 static void
125 irc_join_list (server *serv, GSList *channels, GSList *keys)
127 GSList *clist;
128 GSList *klist;
129 GString *c = g_string_new (NULL);
130 GString *k = g_string_new (NULL);
131 int len;
132 int add;
133 int i, j;
135 i = j = 0;
136 len = 9; /* "JOIN<space><space>\r\n" */
137 clist = channels;
138 klist = keys;
140 while (clist)
142 /* measure how many bytes this channel would add... */
143 if (1)
145 add = strlen (clist->data);
146 if (i != 0)
147 add++; /* comma */
150 if (klist->data)
152 add += strlen (klist->data);
154 else
156 add++; /* 'x' filler */
159 if (j != 0)
160 add++; /* comma */
162 /* too big? dump buffer and start a fresh one */
163 if (len + add > 512)
165 irc_join_list_flush (serv, c, k);
167 c = g_string_new (NULL);
168 k = g_string_new (NULL);
169 i = j = 0;
170 len = 9;
173 /* now actually add it to our GStrings */
174 if (1)
176 add = strlen (clist->data);
177 if (i != 0)
179 add++;
180 g_string_append_c (c, ',');
182 g_string_append (c, clist->data);
183 i++;
186 if (klist->data)
188 add += strlen (klist->data);
189 if (j != 0)
191 add++;
192 g_string_append_c (k, ',');
194 g_string_append (k, klist->data);
195 j++;
197 else
199 add++;
200 if (j != 0)
202 add++;
203 g_string_append_c (k, ',');
205 g_string_append_c (k, 'x');
206 j++;
209 len += add;
211 klist = klist->next;
212 clist = clist->next;
215 irc_join_list_flush (serv, c, k);
218 static void
219 irc_part (server *serv, char *channel, char *reason)
221 if (reason[0])
222 tcp_sendf (serv, "PART %s :%s\r\n", channel, reason);
223 else
224 tcp_sendf (serv, "PART %s\r\n", channel);
227 static void
228 irc_quit (server *serv, char *reason)
230 if (reason[0])
231 tcp_sendf (serv, "QUIT :%s\r\n", reason);
232 else
233 tcp_send_len (serv, "QUIT\r\n", 6);
236 static void
237 irc_set_back (server *serv)
239 tcp_send_len (serv, "AWAY\r\n", 6);
242 static void
243 irc_set_away (server *serv, char *reason)
245 if (reason)
247 if (!reason[0])
248 reason = " ";
250 else
252 reason = " ";
255 tcp_sendf (serv, "AWAY :%s\r\n", reason);
258 static void
259 irc_ctcp (server *serv, char *to, char *msg)
261 tcp_sendf (serv, "PRIVMSG %s :\001%s\001\r\n", to, msg);
264 static void
265 irc_nctcp (server *serv, char *to, char *msg)
267 tcp_sendf (serv, "NOTICE %s :\001%s\001\r\n", to, msg);
270 static void
271 irc_cycle (server *serv, char *channel, char *key)
273 tcp_sendf (serv, "PART %s\r\nJOIN %s %s\r\n", channel, channel, key);
276 static void
277 irc_kick (server *serv, char *channel, char *nick, char *reason)
279 if (reason[0])
280 tcp_sendf (serv, "KICK %s %s :%s\r\n", channel, nick, reason);
281 else
282 tcp_sendf (serv, "KICK %s %s\r\n", channel, nick);
285 static void
286 irc_invite (server *serv, char *channel, char *nick)
288 tcp_sendf (serv, "INVITE %s %s\r\n", nick, channel);
291 static void
292 irc_mode (server *serv, char *target, char *mode)
294 tcp_sendf (serv, "MODE %s %s\r\n", target, mode);
297 /* find channel info when joined */
299 static void
300 irc_join_info (server *serv, char *channel)
302 tcp_sendf (serv, "MODE %s\r\n", channel);
305 /* initiate userlist retreival */
307 static void
308 irc_user_list (server *serv, char *channel)
310 if (serv->have_whox)
311 tcp_sendf (serv, "WHO %s %%chtsunfra,152\r\n", channel);
312 else
313 tcp_sendf (serv, "WHO %s\r\n", channel);
316 /* userhost */
318 static void
319 irc_userhost (server *serv, char *nick)
321 tcp_sendf (serv, "USERHOST %s\r\n", nick);
324 static void
325 irc_away_status (server *serv, char *channel)
327 if (serv->have_whox)
328 tcp_sendf (serv, "WHO %s %%chtsunfra,152\r\n", channel);
329 else
330 tcp_sendf (serv, "WHO %s\r\n", channel);
333 /*static void
334 irc_get_ip (server *serv, char *nick)
336 tcp_sendf (serv, "WHO %s\r\n", nick);
341 * Command: WHOIS
342 * Parameters: [<server>] <nickmask>[,<nickmask>[,...]]
344 static void
345 irc_user_whois (server *serv, char *nicks)
347 tcp_sendf (serv, "WHOIS %s\r\n", nicks);
350 static void
351 irc_message (server *serv, char *channel, char *text)
353 tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text);
356 static void
357 irc_action (server *serv, char *channel, char *act)
359 tcp_sendf (serv, "PRIVMSG %s :\001ACTION %s\001\r\n", channel, act);
362 static void
363 irc_notice (server *serv, char *channel, char *text)
365 tcp_sendf (serv, "NOTICE %s :%s\r\n", channel, text);
368 static void
369 irc_topic (server *serv, char *channel, char *topic)
371 if (!topic)
372 tcp_sendf (serv, "TOPIC %s :\r\n", channel);
373 else if (topic[0])
374 tcp_sendf (serv, "TOPIC %s :%s\r\n", channel, topic);
375 else
376 tcp_sendf (serv, "TOPIC %s\r\n", channel);
379 static void
380 irc_list_channels (server *serv, char *arg, int min_users)
382 if (arg[0])
384 tcp_sendf (serv, "LIST %s\r\n", arg);
385 return;
388 if (serv->use_listargs)
389 tcp_sendf (serv, "LIST >%d,<10000\r\n", min_users - 1);
390 else
391 tcp_send_len (serv, "LIST\r\n", 6);
394 static void
395 irc_names (server *serv, char *channel)
397 tcp_sendf (serv, "NAMES %s\r\n", channel);
400 static void
401 irc_change_nick (server *serv, char *new_nick)
403 tcp_sendf (serv, "NICK %s\r\n", new_nick);
406 static void
407 irc_ping (server *serv, char *to, char *timestring)
409 if (*to)
410 tcp_sendf (serv, "PRIVMSG %s :\001PING %s\001\r\n", to, timestring);
411 else
412 tcp_sendf (serv, "PING %s\r\n", timestring);
415 static int
416 irc_raw (server *serv, char *raw)
418 int len;
419 char tbuf[4096];
420 if (*raw)
422 len = strlen (raw);
423 if (len < sizeof (tbuf) - 3)
425 len = snprintf (tbuf, sizeof (tbuf), "%s\r\n", raw);
426 tcp_send_len (serv, tbuf, len);
427 } else
429 tcp_send_len (serv, raw, len);
430 tcp_send_len (serv, "\r\n", 2);
432 return TRUE;
434 return FALSE;
437 /* ============================================================== */
438 /* ======================= IRC INPUT ============================ */
439 /* ============================================================== */
442 static void
443 channel_date (session *sess, char *chan, char *timestr)
445 time_t timestamp = (time_t) atol (timestr);
446 char *tim = ctime (&timestamp);
447 tim[24] = 0; /* get rid of the \n */
448 EMIT_SIGNAL (XP_TE_CHANDATE, sess, chan, tim, NULL, NULL, 0);
451 static void
452 process_numeric (session * sess, int n,
453 char *word[], char *word_eol[], char *text)
455 server *serv = sess->server;
456 /* show whois is the server tab */
457 session *whois_sess = serv->server_session;
459 /* unless this setting is on */
460 if (prefs.irc_whois_front)
461 whois_sess = serv->front_session;
463 char *ex;
465 switch (n)
467 case 1:
468 inbound_login_start (sess, word[3], word[1]);
469 /* if network is PTnet then you must get your IP address
470 from "001" server message */
471 if ((strncmp(word[7], "PTnet", 5) == 0) &&
472 (strncmp(word[8], "IRC", 3) == 0) &&
473 (strncmp(word[9], "Network", 7) == 0) &&
474 (strrchr(word[10], '@') != NULL))
476 serv->use_who = FALSE;
477 if (prefs.ip_from_server)
478 inbound_foundip (sess, strrchr(word[10], '@')+1);
481 /* use /NICKSERV */
482 if (strcasecmp (word[7], "DALnet") == 0 ||
483 strcasecmp (word[7], "BRASnet") == 0)
484 serv->nickservtype = 1;
486 /* use /NS */
487 else if (strcasecmp (word[7], "FreeNode") == 0)
488 serv->nickservtype = 2;
490 goto def;
492 case 4: /* check the ircd type */
493 serv->use_listargs = FALSE;
494 serv->modes_per_line = 3; /* default to IRC RFC */
495 if (strncmp (word[5], "bahamut", 7) == 0) /* DALNet */
497 serv->use_listargs = TRUE; /* use the /list args */
498 } else if (strncmp (word[5], "u2.10.", 6) == 0) /* Undernet */
500 serv->use_listargs = TRUE; /* use the /list args */
501 serv->modes_per_line = 6; /* allow 6 modes per line */
502 } else if (strncmp (word[5], "glx2", 4) == 0)
504 serv->use_listargs = TRUE; /* use the /list args */
506 goto def;
508 case 5:
509 inbound_005 (serv, word);
510 goto def;
512 case 263: /*Server load is temporarily too heavy */
513 if (fe_is_chanwindow (sess->server))
515 fe_chan_list_end (sess->server);
516 fe_message (word_eol[5] + 1, FE_MSG_ERROR);
518 goto def;
520 case 301:
521 inbound_away (serv, word[4],
522 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
523 break;
525 case 302:
526 if (serv->skip_next_userhost)
528 char *eq = strchr (word[4], '=');
529 if (eq)
531 *eq = 0;
532 if (!serv->p_cmp (word[4] + 1, serv->nick))
534 char *at = strrchr (eq + 1, '@');
535 if (at)
536 inbound_foundip (sess, at + 1);
540 serv->skip_next_userhost = FALSE;
541 break;
543 else goto def;
545 case 303:
546 word[4]++;
547 notify_markonline (serv, word);
548 break;
550 case 305:
551 inbound_uback (serv);
552 goto def;
554 case 306:
555 inbound_uaway (serv);
556 goto def;
558 case 312:
559 if (!serv->skip_next_whois)
560 EMIT_SIGNAL (XP_TE_WHOIS3, whois_sess, word[4], word_eol[5], NULL, NULL, 0);
561 else
562 inbound_user_info (sess, NULL, NULL, NULL, word[5], word[4], NULL, NULL, 0xff);
563 break;
565 case 311: /* WHOIS 1st line */
566 serv->inside_whois = 1;
567 inbound_user_info_start (sess, word[4]);
568 if (!serv->skip_next_whois)
569 EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
570 word[6], word_eol[8] + 1, 0);
571 else
572 inbound_user_info (sess, NULL, word[5], word[6], NULL, word[4],
573 word_eol[8][0] == ':' ? word_eol[8] + 1 : word_eol[8], NULL, 0xff);
574 break;
576 case 314: /* WHOWAS */
577 inbound_user_info_start (sess, word[4]);
578 EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
579 word[6], word_eol[8] + 1, 0);
580 break;
582 case 317:
583 if (!serv->skip_next_whois)
585 time_t timestamp = (time_t) atol (word[6]);
586 long idle = atol (word[5]);
587 char *tim;
588 char outbuf[64];
590 snprintf (outbuf, sizeof (outbuf),
591 "%02ld:%02ld:%02ld", idle / 3600, (idle / 60) % 60,
592 idle % 60);
593 if (timestamp == 0)
594 EMIT_SIGNAL (XP_TE_WHOIS4, whois_sess, word[4],
595 outbuf, NULL, NULL, 0);
596 else
598 tim = ctime (&timestamp);
599 tim[19] = 0; /* get rid of the \n */
600 EMIT_SIGNAL (XP_TE_WHOIS4T, whois_sess, word[4],
601 outbuf, tim, NULL, 0);
604 break;
606 case 318: /* END OF WHOIS */
607 if (!serv->skip_next_whois)
608 EMIT_SIGNAL (XP_TE_WHOIS6, whois_sess, word[4], NULL,
609 NULL, NULL, 0);
610 serv->skip_next_whois = 0;
611 serv->inside_whois = 0;
612 break;
614 case 313:
615 case 319:
616 if (!serv->skip_next_whois)
617 EMIT_SIGNAL (XP_TE_WHOIS2, whois_sess, word[4],
618 word_eol[5] + 1, NULL, NULL, 0);
619 break;
621 case 307: /* dalnet version */
622 case 320: /* :is an identified user */
623 if (!serv->skip_next_whois)
624 EMIT_SIGNAL (XP_TE_WHOIS_ID, whois_sess, word[4],
625 word_eol[5] + 1, NULL, NULL, 0);
626 break;
628 case 321:
629 if (!fe_is_chanwindow (sess->server))
630 EMIT_SIGNAL (XP_TE_CHANLISTHEAD, serv->server_session, NULL, NULL, NULL, NULL, 0);
631 break;
633 case 322:
634 if (fe_is_chanwindow (sess->server))
636 fe_add_chan_list (sess->server, word[4], word[5], word_eol[6] + 1);
637 } else
639 PrintTextf (serv->server_session, "%-16s %-7d %s\017\n",
640 word[4], atoi (word[5]), word_eol[6] + 1);
642 break;
644 case 323:
645 if (!fe_is_chanwindow (sess->server))
646 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], word[2], NULL, 0);
647 else
648 fe_chan_list_end (sess->server);
649 break;
651 case 324:
652 sess = find_channel (serv, word[4]);
653 if (!sess)
654 sess = serv->server_session;
655 if (sess->ignore_mode)
656 sess->ignore_mode = FALSE;
657 else
658 EMIT_SIGNAL (XP_TE_CHANMODES, sess, word[4], word_eol[5],
659 NULL, NULL, 0);
660 fe_update_mode_buttons (sess, 't', '-');
661 fe_update_mode_buttons (sess, 'n', '-');
662 fe_update_mode_buttons (sess, 's', '-');
663 fe_update_mode_buttons (sess, 'i', '-');
664 fe_update_mode_buttons (sess, 'p', '-');
665 fe_update_mode_buttons (sess, 'm', '-');
666 fe_update_mode_buttons (sess, 'l', '-');
667 fe_update_mode_buttons (sess, 'k', '-');
668 handle_mode (serv, word, word_eol, "", TRUE);
669 break;
671 case 329:
672 sess = find_channel (serv, word[4]);
673 if (sess)
675 if (sess->ignore_date)
676 sess->ignore_date = FALSE;
677 else
678 channel_date (sess, word[4], word[5]);
680 break;
682 case 330:
683 if (!serv->skip_next_whois)
684 EMIT_SIGNAL (XP_TE_WHOIS_AUTH, whois_sess, word[4],
685 word_eol[6] + 1, word[5], NULL, 0);
686 inbound_user_info (sess, NULL, NULL, NULL, NULL, word[4], NULL, word[5], 0xff);
687 break;
689 case 332:
690 inbound_topic (serv, word[4],
691 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
692 break;
694 case 333:
695 inbound_topictime (serv, word[4], word[5], atol (word[6]));
696 break;
698 #if 0
699 case 338: /* Undernet Real user@host, Real IP */
700 EMIT_SIGNAL (XP_TE_WHOIS_REALHOST, sess, word[4], word[5], word[6],
701 (word_eol[7][0]==':') ? word_eol[7]+1 : word_eol[7], 0);
702 break;
703 #endif
705 case 341: /* INVITE ACK */
706 EMIT_SIGNAL (XP_TE_UINVITE, sess, word[4], word[5], serv->servername,
707 NULL, 0);
708 break;
710 case 352: /* WHO */
712 unsigned int away = 0;
713 session *who_sess = find_channel (serv, word[4]);
715 if (*word[9] == 'G')
716 away = 1;
718 inbound_user_info (sess, word[4], word[5], word[6], word[7],
719 word[8], word_eol[11], NULL, away);
721 /* try to show only user initiated whos */
722 if (!who_sess || !who_sess->doing_who)
723 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
724 word[2], NULL, 0);
726 break;
728 case 354: /* undernet WHOX: used as a reply for irc_away_status */
730 unsigned int away = 0;
731 session *who_sess;
733 /* irc_away_status and irc_user_list sends out a "152" */
734 if (!strcmp (word[4], "152"))
736 who_sess = find_channel (serv, word[5]);
738 if (*word[10] == 'G')
739 away = 1;
741 /* :server 354 yournick 152 #channel ~ident host servname nick H account :realname */
742 inbound_user_info (sess, word[5], word[6], word[7], word[8],
743 word[9], word_eol[12]+1, word[11], away);
745 /* try to show only user initiated whos */
746 if (!who_sess || !who_sess->doing_who)
747 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
748 word[1], word[2], NULL, 0);
749 } else
750 goto def;
752 break;
754 case 315: /* END OF WHO */
756 session *who_sess;
757 who_sess = find_channel (serv, word[4]);
758 if (who_sess)
760 if (!who_sess->doing_who)
761 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
762 word[1], word[2], NULL, 0);
763 who_sess->doing_who = FALSE;
764 } else
766 if (!serv->doing_dns)
767 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
768 word[1], word[2], NULL, 0);
769 serv->doing_dns = FALSE;
772 break;
774 case 348: /* +e-list entry */
775 if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], TRUE))
776 goto def;
777 break;
779 case 349: /* end of exemption list */
780 sess = find_channel (serv, word[4]);
781 if (!sess)
783 sess = serv->front_session;
784 goto def;
786 if (!fe_is_banwindow (sess))
787 goto def;
788 fe_ban_list_end (sess, TRUE);
789 break;
791 case 353: /* NAMES */
792 inbound_nameslist (serv, word[5],
793 (word_eol[6][0] == ':') ? word_eol[6] + 1 : word_eol[6]);
794 break;
796 case 366:
797 if (!inbound_nameslist_end (serv, word[4]))
798 goto def;
799 break;
801 case 367: /* banlist entry */
802 inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], FALSE);
803 break;
805 case 368:
806 sess = find_channel (serv, word[4]);
807 if (!sess)
809 sess = serv->front_session;
810 goto def;
812 if (!fe_is_banwindow (sess))
813 goto def;
814 fe_ban_list_end (sess, FALSE);
815 break;
817 case 369: /* WHOWAS end */
818 case 406: /* WHOWAS error */
819 EMIT_SIGNAL (XP_TE_SERVTEXT, whois_sess, text, word[1], word[2], NULL, 0);
820 serv->inside_whois = 0;
821 break;
823 case 372: /* motd text */
824 case 375: /* motd start */
825 if (!prefs.skipmotd || serv->motd_skipped)
826 EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, NULL,
827 NULL, 0);
828 break;
830 case 376: /* end of motd */
831 case 422: /* motd file is missing */
832 inbound_login_end (sess, text);
833 break;
835 case 433: /* nickname in use */
836 case 432: /* erroneous nickname */
837 if (serv->end_of_motd)
838 goto def;
839 inbound_next_nick (sess, word[4]);
840 break;
842 case 437:
843 if (serv->end_of_motd || is_channel (serv, word[4]))
844 goto def;
845 inbound_next_nick (sess, word[4]);
846 break;
848 case 471:
849 EMIT_SIGNAL (XP_TE_USERLIMIT, sess, word[4], NULL, NULL, NULL, 0);
850 break;
852 case 473:
853 EMIT_SIGNAL (XP_TE_INVITE, sess, word[4], NULL, NULL, NULL, 0);
854 break;
856 case 474:
857 EMIT_SIGNAL (XP_TE_BANNED, sess, word[4], NULL, NULL, NULL, 0);
858 break;
860 case 475:
861 EMIT_SIGNAL (XP_TE_KEYWORD, sess, word[4], NULL, NULL, NULL, 0);
862 break;
864 case 601:
865 notify_set_offline (serv, word[4], FALSE);
866 break;
868 case 605:
869 notify_set_offline (serv, word[4], TRUE);
870 break;
872 case 600:
873 case 604:
874 notify_set_online (serv, word[4]);
875 break;
877 case 730: /* RPL_MONONLINE */
878 ex = strchr (word[4], '!'); /* only send the nick */
879 if (ex)
880 ex[0] = 0;
881 notify_set_online (serv, word[4] + 1);
882 break;
884 case 731: /* RPL_MONOFFLINE */
885 ex = strchr (word[4], '!'); /* only send the nick */
886 if (ex)
887 ex[0] = 0;
888 notify_set_offline (serv, word[4] + 1, FALSE);
889 break;
891 case 900: /* successful SASL 'logged in as ' */
892 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, word_eol[6]+1, word[1], 900, NULL, 0);
893 break;
894 case 903: /* successful SASL auth */
895 case 904: /* aborted SASL auth */
896 case 905: /* failed SASL auth */
897 case 906: /* registration completes before SASL auth */
898 case 907: /* attempting to re-auth after a successful auth */
899 EMIT_SIGNAL (XP_TE_SASLRESPONSE, serv->server_session, word[1], word[2], word[3], ++word_eol[4], 0);
900 tcp_send_len (serv, "CAP END\r\n", 9);
901 break;
903 default:
905 if (serv->inside_whois && word[4][0])
907 /* some unknown WHOIS reply, ircd coders make them up weekly */
908 if (!serv->skip_next_whois)
909 EMIT_SIGNAL (XP_TE_WHOIS_SPECIAL, whois_sess, word[4],
910 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5],
911 word[2], NULL, 0);
912 return;
915 def:
916 if (is_channel (serv, word[4]))
918 session *realsess = find_channel (serv, word[4]);
919 if (!realsess)
920 realsess = serv->server_session;
921 EMIT_SIGNAL (XP_TE_SERVTEXT, realsess, text, word[1], word[2], NULL, 0);
922 } else
924 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
925 word[2], NULL, 0);
930 /* handle named messages that starts with a ':' */
932 static void
933 process_named_msg (session *sess, char *type, char *word[], char *word_eol[])
935 server *serv = sess->server;
936 char ip[128], nick[NICKLEN];
937 char *text, *ex;
938 int len = strlen (type);
940 /* fill in the "ip" and "nick" buffers */
941 ex = strchr (word[1], '!');
942 if (!ex) /* no '!', must be a server message */
944 safe_strcpy (ip, word[1], sizeof (ip));
945 safe_strcpy (nick, word[1], sizeof (nick));
946 } else
948 safe_strcpy (ip, ex + 1, sizeof (ip));
949 ex[0] = 0;
950 safe_strcpy (nick, word[1], sizeof (nick));
951 ex[0] = '!';
954 if (len == 4)
956 guint32 t;
958 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
959 /* this should compile to a bunch of: CMP.L, JE ... nice & fast */
960 switch (t)
963 case WORDL('A','C','C','O'):
964 inbound_account (serv, nick, word[3]);
965 return;
967 case WORDL('J','O','I','N'):
969 char *chan = word[3];
970 char *account = word[4];
971 char *realname = word_eol[5] + 1;
973 if (*chan == ':')
974 chan++;
975 if (!serv->p_cmp (nick, serv->nick))
976 inbound_ujoin (serv, chan, nick, ip);
977 else
978 inbound_join (serv, chan, nick, ip, account, realname);
980 return;
982 case WORDL('K','I','C','K'):
984 char *kicked = word[4];
985 char *reason = word_eol[5];
986 if (*kicked)
988 if (*reason == ':')
989 reason++;
990 if (!strcmp (kicked, serv->nick))
991 inbound_ukick (serv, word[3], nick, reason);
992 else
993 inbound_kick (serv, word[3], kicked, nick, reason);
996 return;
998 case WORDL('K','I','L','L'):
999 EMIT_SIGNAL (XP_TE_KILL, sess, nick, word_eol[5], NULL, NULL, 0);
1000 return;
1002 case WORDL('M','O','D','E'):
1003 handle_mode (serv, word, word_eol, nick, FALSE); /* modes.c */
1004 return;
1006 case WORDL('N','I','C','K'):
1007 inbound_newnick (serv, nick, (word_eol[3][0] == ':')
1008 ? word_eol[3] + 1 : word_eol[3], FALSE);
1009 return;
1011 case WORDL('P','A','R','T'):
1013 char *chan = word[3];
1014 char *reason = word_eol[4];
1016 if (*chan == ':')
1017 chan++;
1018 if (*reason == ':')
1019 reason++;
1020 if (!strcmp (nick, serv->nick))
1021 inbound_upart (serv, chan, ip, reason);
1022 else
1023 inbound_part (serv, chan, nick, ip, reason);
1025 return;
1027 case WORDL('P','O','N','G'):
1028 inbound_ping_reply (serv->server_session,
1029 (word[4][0] == ':') ? word[4] + 1 : word[4], word[3]);
1030 return;
1032 case WORDL('Q','U','I','T'):
1033 inbound_quit (serv, nick, ip,
1034 (word_eol[3][0] == ':') ? word_eol[3] + 1 : word_eol[3]);
1035 return;
1037 case WORDL('A','W','A','Y'):
1038 inbound_away_notify (serv, nick,
1039 (word_eol[3][0] == ':') ? word_eol[3] + 1 : NULL);
1040 return;
1043 goto garbage;
1046 else if(len == 3) {
1047 guint32 t;
1048 guint32 want_cap; /* format the CAP REQ string based on previous capabilities being requested or not */
1049 guint32 want_sasl; /* CAP END shouldn't be sent when SASL is requested, it needs further responses */
1050 char *pass; /* buffer for SASL password */
1051 char buffer[256]; /* buffer for requesting capabilities and emitting the signal */
1053 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
1054 switch (t)
1056 case WORDL('C','A','P','\0'):
1057 if (strncasecmp (word[4], "ACK", 3) == 0)
1059 EMIT_SIGNAL (XP_TE_CAPACK, sess->server->server_session, word[1], word[5][0]==':' ? ++word_eol[5] : word_eol[5], NULL, NULL, 0);
1061 if (strstr (word_eol[5], "identify-msg") != 0)
1063 serv->have_idmsg = TRUE;
1066 if (strstr (word_eol[5], "multi-prefix") != 0)
1068 serv->have_namesx = TRUE;
1071 if (strstr (word_eol[5], "away-notify") != 0)
1073 serv->have_awaynotify = TRUE;
1076 if (strstr (word_eol[5], "account-notify") != 0)
1078 serv->have_accnotify = TRUE;
1081 if (strstr (word_eol[5], "extended-join") != 0)
1083 serv->have_extjoin = TRUE;
1086 if (strstr (word_eol[5], "sasl") != 0)
1088 serv->have_sasl = TRUE;
1089 EMIT_SIGNAL (XP_TE_SASLAUTH, serv->server_session, sess->server->sasluser, NULL, NULL, NULL, 0);
1090 tcp_send_len (serv, "AUTHENTICATE PLAIN\r\n", 20);
1092 pass = encode_sasl_pass (sess->server->sasluser, sess->server->saslpassword);
1093 tcp_sendf (sess->server, "AUTHENTICATE %s\r\n", pass);
1094 free (pass);
1097 else if (strncasecmp (word[4], "LS", 2) == 0)
1099 EMIT_SIGNAL (XP_TE_CAPLIST, serv->server_session, word[1], word[5][0]==':' ? ++word_eol[5] : word_eol[5], NULL, NULL, 0);
1100 want_cap = 0;
1101 want_sasl = 0;
1103 if (strstr (word_eol[5], "identify-msg") != 0)
1105 strcpy (buffer, "CAP REQ :identify-msg");
1106 want_cap = 1;
1108 if (strstr (word_eol[5], "multi-prefix") != 0)
1110 want_cap ? strcat (buffer, " multi-prefix") : strcpy (buffer, "CAP REQ :multi-prefix");
1111 want_cap = 1;
1113 if (strstr (word_eol[5], "away-notify") != 0)
1115 want_cap ? strcat (buffer, " away-notify") : strcpy (buffer, "CAP REQ :away-notify");
1116 want_cap = 1;
1118 if (strstr (word_eol[5], "account-notify") != 0)
1120 want_cap ? strcat (buffer, " account-notify") : strcpy (buffer, "CAP REQ :account-notify");
1121 want_cap = 1;
1124 if (strstr (word_eol[5], "extended-join") != 0)
1126 want_cap ? strcat (buffer, " extended-join") : strcpy (buffer, "CAP REQ :extended-join");
1127 want_cap = 1;
1129 /* if the SASL password is set, request SASL auth */
1130 if (strstr (word_eol[5], "sasl") != 0 && strlen (sess->server->saslpassword) != 0)
1132 want_cap ? strcat (buffer, " sasl") : strcpy (buffer, "CAP REQ :sasl");
1133 want_cap = 1;
1134 want_sasl = 1;
1137 if (want_cap)
1139 /* buffer + 9 = emit buffer without "CAP REQ :" */
1140 EMIT_SIGNAL (XP_TE_CAPREQ, sess->server->server_session, buffer + 9, NULL, NULL, NULL, 0);
1141 tcp_sendf (serv, "%s\r\n", buffer);
1143 if (!want_sasl)
1145 /* if we use SASL, CAP END is dealt via raw numerics */
1146 tcp_send_len (serv, "CAP END\r\n", 9);
1149 else if (strncasecmp (word[4], "NAK", 3) == 0)
1151 tcp_send_len (serv, "CAP END\r\n", 9);
1154 return;
1159 else if (len >= 5)
1161 guint32 t;
1163 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
1164 /* this should compile to a bunch of: CMP.L, JE ... nice & fast */
1165 switch (t)
1167 case WORDL('I','N','V','I'):
1168 if (ignore_check (word[1], IG_INVI))
1169 return;
1171 if (word[4][0] == ':')
1172 EMIT_SIGNAL (XP_TE_INVITED, sess, word[4] + 1, nick,
1173 serv->servername, NULL, 0);
1174 else
1175 EMIT_SIGNAL (XP_TE_INVITED, sess, word[4], nick,
1176 serv->servername, NULL, 0);
1178 return;
1180 case WORDL('N','O','T','I'):
1182 int id = FALSE; /* identified */
1184 text = word_eol[4];
1185 if (*text == ':')
1186 text++;
1188 if (serv->have_idmsg)
1190 if (*text == '+')
1192 id = TRUE;
1193 text++;
1194 } else if (*text == '-')
1195 text++;
1198 if (!ignore_check (word[1], IG_NOTI))
1199 inbound_notice (serv, word[3], nick, text, ip, id);
1201 return;
1203 case WORDL('P','R','I','V'):
1205 char *to = word[3];
1206 int len;
1207 int id = FALSE; /* identified */
1208 if (*to)
1210 text = word_eol[4];
1211 if (*text == ':')
1212 text++;
1213 if (serv->have_idmsg)
1215 if (*text == '+')
1217 id = TRUE;
1218 text++;
1219 } else if (*text == '-')
1220 text++;
1222 len = strlen (text);
1223 if (text[0] == 1 && text[len - 1] == 1) /* ctcp */
1225 text[len - 1] = 0;
1226 text++;
1227 if (strncasecmp (text, "ACTION", 6) != 0)
1228 flood_check (nick, ip, serv, sess, 0);
1229 if (strncasecmp (text, "DCC ", 4) == 0)
1230 /* redo this with handle_quotes TRUE */
1231 process_data_init (word[1], word_eol[1], word, word_eol, TRUE, FALSE);
1232 ctcp_handle (sess, to, nick, ip, text, word, word_eol, id);
1233 } else
1235 if (is_channel (serv, to))
1237 if (ignore_check (word[1], IG_CHAN))
1238 return;
1239 inbound_chanmsg (serv, NULL, to, nick, text, FALSE, id);
1240 } else
1242 if (ignore_check (word[1], IG_PRIV))
1243 return;
1244 inbound_privmsg (serv, nick, ip, text, id);
1249 return;
1251 case WORDL('T','O','P','I'):
1252 inbound_topicnew (serv, nick, word[3],
1253 (word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4]);
1254 return;
1256 case WORDL('W','A','L','L'):
1257 text = word_eol[3];
1258 if (*text == ':')
1259 text++;
1260 EMIT_SIGNAL (XP_TE_WALLOPS, sess, nick, text, NULL, NULL, 0);
1261 return;
1265 garbage:
1266 /* unknown message */
1267 PrintTextf (sess, "GARBAGE: %s\n", word_eol[1]);
1270 /* handle named messages that DON'T start with a ':' */
1272 static void
1273 process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol[])
1275 sess = sess->server->server_session;
1277 if (!strncmp (buf, "PING ", 5))
1279 tcp_sendf (sess->server, "PONG %s\r\n", buf + 5);
1280 return;
1282 if (!strncmp (buf, "ERROR", 5))
1284 EMIT_SIGNAL (XP_TE_SERVERERROR, sess, buf + 7, NULL, NULL, NULL, 0);
1285 return;
1287 if (!strncmp (buf, "NOTICE ", 7))
1289 buf = word_eol[3];
1290 if (*buf == ':')
1291 buf++;
1292 EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, buf, sess->server->servername, NULL, NULL, 0);
1293 return;
1295 if (!strncmp (buf, "AUTHENTICATE +", 14)) /* omit SASL "empty" responses */
1297 return;
1300 EMIT_SIGNAL (XP_TE_SERVTEXT, sess, buf, sess->server->servername, rawname, NULL, 0);
1303 /* irc_inline() - 1 single line received from serv */
1305 static void
1306 irc_inline (server *serv, char *buf, int len)
1308 session *sess, *tmp;
1309 char *type, *text;
1310 char *word[PDIWORDS+1];
1311 char *word_eol[PDIWORDS+1];
1312 char pdibuf_static[522]; /* 1 line can potentially be 512*6 in utf8 */
1313 char *pdibuf = pdibuf_static;
1315 url_check_line (buf, len);
1317 /* need more than 522? fall back to malloc */
1318 if (len >= sizeof (pdibuf_static))
1319 pdibuf = malloc (len + 1);
1321 sess = serv->front_session;
1323 /* Python relies on this */
1324 word[PDIWORDS] = NULL;
1325 word_eol[PDIWORDS] = NULL;
1327 if (buf[0] == ':')
1329 /* split line into words and words_to_end_of_line */
1330 process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
1332 /* find a context for this message */
1333 if (is_channel (serv, word[3]))
1335 tmp = find_channel (serv, word[3]);
1336 if (tmp)
1337 sess = tmp;
1340 /* for server messages, the 2nd word is the "message type" */
1341 type = word[2];
1343 word[0] = type;
1344 word_eol[1] = buf; /* keep the ":" for plugins */
1345 if (plugin_emit_server (sess, type, word, word_eol))
1346 goto xit;
1347 word[1]++;
1348 word_eol[1] = buf + 1; /* but not for xchat internally */
1350 } else
1352 process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
1353 word[0] = type = word[1];
1354 if (plugin_emit_server (sess, type, word, word_eol))
1355 goto xit;
1358 if (buf[0] != ':')
1360 process_named_servermsg (sess, buf, word[0], word_eol);
1361 goto xit;
1364 /* see if the second word is a numeric */
1365 if (isdigit ((unsigned char) word[2][0]))
1367 text = word_eol[4];
1368 if (*text == ':')
1369 text++;
1371 process_numeric (sess, atoi (word[2]), word, word_eol, text);
1372 } else
1374 process_named_msg (sess, type, word, word_eol);
1377 xit:
1378 if (pdibuf != pdibuf_static)
1379 free (pdibuf);
1382 void
1383 proto_fill_her_up (server *serv)
1385 serv->p_inline = irc_inline;
1386 serv->p_invite = irc_invite;
1387 serv->p_cycle = irc_cycle;
1388 serv->p_ctcp = irc_ctcp;
1389 serv->p_nctcp = irc_nctcp;
1390 serv->p_quit = irc_quit;
1391 serv->p_kick = irc_kick;
1392 serv->p_part = irc_part;
1393 serv->p_ns_identify = irc_ns_identify;
1394 serv->p_ns_ghost = irc_ns_ghost;
1395 serv->p_join = irc_join;
1396 serv->p_join_list = irc_join_list;
1397 serv->p_login = irc_login;
1398 serv->p_join_info = irc_join_info;
1399 serv->p_mode = irc_mode;
1400 serv->p_user_list = irc_user_list;
1401 serv->p_away_status = irc_away_status;
1402 /*serv->p_get_ip = irc_get_ip;*/
1403 serv->p_whois = irc_user_whois;
1404 serv->p_get_ip = irc_user_list;
1405 serv->p_get_ip_uh = irc_userhost;
1406 serv->p_set_back = irc_set_back;
1407 serv->p_set_away = irc_set_away;
1408 serv->p_message = irc_message;
1409 serv->p_action = irc_action;
1410 serv->p_notice = irc_notice;
1411 serv->p_topic = irc_topic;
1412 serv->p_list_channels = irc_list_channels;
1413 serv->p_change_nick = irc_change_nick;
1414 serv->p_names = irc_names;
1415 serv->p_ping = irc_ping;
1416 serv->p_raw = irc_raw;
1417 serv->p_cmp = rfc_casecmp; /* can be changed by 005 in modes.c */