Now inbound_cap_ls() can enable extensions when a bouncer uses a namespace for the...
[rofl0r-ixchat.git] / src / common / proto-irc.c
blob4c5b4c517621ebf5de88d87fe0271d0b95cf59de
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"
44 static void
45 irc_login (server *serv, char *user, char *realname)
47 tcp_sendf (serv, "CAP LS\r\n"); /* start with CAP LS as Charybdis sasl.txt suggests */
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 #if 0
73 case LOGIN_MSG_NS:
74 tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
75 break;
76 case LOGIN_NS:
77 tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
78 break;
79 case LOGIN_AUTH:
80 /* why couldn't QuakeNet implement one of the existing ones? */
81 tcp_sendf (serv, "AUTH %s %s\r\n", arg1, arg2);
82 break;
83 #endif
87 static void
88 irc_ns_identify (server *serv, char *pass)
90 switch (serv->loginmethod)
92 case LOGIN_CHALLENGEAUTH:
93 tcp_sendf (serv, "PRIVMSG %s :CHALLENGE\r\n", CHALLENGEAUTH_NICK); /* request a challenge from Q */
94 break;
95 #if 0
96 case LOGIN_AUTH:
97 irc_nickserv (serv, "", serv->nick, pass, "");
98 break;
99 #endif
100 default:
101 irc_nickserv (serv, "IDENTIFY", pass, "", "");
105 static void
106 irc_ns_ghost (server *serv, char *usname, char *pass)
108 if (serv->loginmethod != LOGIN_CHALLENGEAUTH)
110 irc_nickserv (serv, "GHOST", usname, " ", pass);
114 static void
115 irc_join (server *serv, char *channel, char *key)
117 if (key[0])
118 tcp_sendf (serv, "JOIN %s %s\r\n", channel, key);
119 else
120 tcp_sendf (serv, "JOIN %s\r\n", channel);
123 static void
124 irc_join_list_flush (server *serv, GString *c, GString *k)
126 char *chanstr, *keystr;
128 chanstr = g_string_free (c, FALSE);
129 keystr = g_string_free (k, FALSE);
130 if (chanstr[0])
132 if (keystr[0])
133 tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr);
134 else
135 tcp_sendf (serv, "JOIN %s\r\n", chanstr);
137 g_free (chanstr);
138 g_free (keystr);
141 /* join a whole list of channels & keys, split to multiple lines
142 * to get around 512 limit */
144 static void
145 irc_join_list (server *serv, GSList *channels, GSList *keys)
147 GSList *clist;
148 GSList *klist;
149 GString *c = g_string_new (NULL);
150 GString *k = g_string_new (NULL);
151 int len;
152 int add;
153 int i, j;
155 i = j = 0;
156 len = 9; /* "JOIN<space><space>\r\n" */
157 clist = channels;
158 klist = keys;
160 while (clist)
162 /* measure how many bytes this channel would add... */
163 if (1)
165 add = strlen (clist->data);
166 if (i != 0)
167 add++; /* comma */
170 if (klist->data)
172 add += strlen (klist->data);
174 else
176 add++; /* 'x' filler */
179 if (j != 0)
180 add++; /* comma */
182 /* too big? dump buffer and start a fresh one */
183 if (len + add > 512)
185 irc_join_list_flush (serv, c, k);
187 c = g_string_new (NULL);
188 k = g_string_new (NULL);
189 i = j = 0;
190 len = 9;
193 /* now actually add it to our GStrings */
194 if (1)
196 add = strlen (clist->data);
197 if (i != 0)
199 add++;
200 g_string_append_c (c, ',');
202 g_string_append (c, clist->data);
203 i++;
206 if (klist->data)
208 add += strlen (klist->data);
209 if (j != 0)
211 add++;
212 g_string_append_c (k, ',');
214 g_string_append (k, klist->data);
215 j++;
217 else
219 add++;
220 if (j != 0)
222 add++;
223 g_string_append_c (k, ',');
225 g_string_append_c (k, 'x');
226 j++;
229 len += add;
231 klist = klist->next;
232 clist = clist->next;
235 irc_join_list_flush (serv, c, k);
238 static void
239 irc_part (server *serv, char *channel, char *reason)
241 if (reason[0])
242 tcp_sendf (serv, "PART %s :%s\r\n", channel, reason);
243 else
244 tcp_sendf (serv, "PART %s\r\n", channel);
247 static void
248 irc_quit (server *serv, char *reason)
250 if (reason[0])
251 tcp_sendf (serv, "QUIT :%s\r\n", reason);
252 else
253 tcp_send_len (serv, "QUIT\r\n", 6);
256 static void
257 irc_set_back (server *serv)
259 tcp_send_len (serv, "AWAY\r\n", 6);
262 static void
263 irc_set_away (server *serv, char *reason)
265 if (reason)
267 if (!reason[0])
268 reason = " ";
270 else
272 reason = " ";
275 tcp_sendf (serv, "AWAY :%s\r\n", reason);
278 static void
279 irc_ctcp (server *serv, char *to, char *msg)
281 tcp_sendf (serv, "PRIVMSG %s :\001%s\001\r\n", to, msg);
284 static void
285 irc_nctcp (server *serv, char *to, char *msg)
287 tcp_sendf (serv, "NOTICE %s :\001%s\001\r\n", to, msg);
290 static void
291 irc_cycle (server *serv, char *channel, char *key)
293 tcp_sendf (serv, "PART %s\r\nJOIN %s %s\r\n", channel, channel, key);
296 static void
297 irc_kick (server *serv, char *channel, char *nick, char *reason)
299 if (reason[0])
300 tcp_sendf (serv, "KICK %s %s :%s\r\n", channel, nick, reason);
301 else
302 tcp_sendf (serv, "KICK %s %s\r\n", channel, nick);
305 static void
306 irc_invite (server *serv, char *channel, char *nick)
308 tcp_sendf (serv, "INVITE %s %s\r\n", nick, channel);
311 static void
312 irc_mode (server *serv, char *target, char *mode)
314 tcp_sendf (serv, "MODE %s %s\r\n", target, mode);
317 /* find channel info when joined */
319 static void
320 irc_join_info (server *serv, char *channel)
322 tcp_sendf (serv, "MODE %s\r\n", channel);
325 /* initiate userlist retreival */
327 static void
328 irc_user_list (server *serv, char *channel)
330 if (serv->have_whox)
331 tcp_sendf (serv, "WHO %s %%chtsunfra,152\r\n", channel);
332 else
333 tcp_sendf (serv, "WHO %s\r\n", channel);
336 /* userhost */
338 static void
339 irc_userhost (server *serv, char *nick)
341 tcp_sendf (serv, "USERHOST %s\r\n", nick);
344 static void
345 irc_away_status (server *serv, char *channel)
347 if (serv->have_whox)
348 tcp_sendf (serv, "WHO %s %%chtsunfra,152\r\n", channel);
349 else
350 tcp_sendf (serv, "WHO %s\r\n", channel);
353 /*static void
354 irc_get_ip (server *serv, char *nick)
356 tcp_sendf (serv, "WHO %s\r\n", nick);
361 * Command: WHOIS
362 * Parameters: [<server>] <nickmask>[,<nickmask>[,...]]
364 static void
365 irc_user_whois (server *serv, char *nicks)
367 tcp_sendf (serv, "WHOIS %s\r\n", nicks);
370 static void
371 irc_message (server *serv, char *channel, char *text)
373 tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text);
376 static void
377 irc_action (server *serv, char *channel, char *act)
379 tcp_sendf (serv, "PRIVMSG %s :\001ACTION %s\001\r\n", channel, act);
382 static void
383 irc_notice (server *serv, char *channel, char *text)
385 tcp_sendf (serv, "NOTICE %s :%s\r\n", channel, text);
388 static void
389 irc_topic (server *serv, char *channel, char *topic)
391 if (!topic)
392 tcp_sendf (serv, "TOPIC %s :\r\n", channel);
393 else if (topic[0])
394 tcp_sendf (serv, "TOPIC %s :%s\r\n", channel, topic);
395 else
396 tcp_sendf (serv, "TOPIC %s\r\n", channel);
399 static void
400 irc_list_channels (server *serv, char *arg, int min_users)
402 if (arg[0])
404 tcp_sendf (serv, "LIST %s\r\n", arg);
405 return;
408 if (serv->use_listargs)
409 tcp_sendf (serv, "LIST >%d,<10000\r\n", min_users - 1);
410 else
411 tcp_send_len (serv, "LIST\r\n", 6);
414 static void
415 irc_names (server *serv, char *channel)
417 tcp_sendf (serv, "NAMES %s\r\n", channel);
420 static void
421 irc_change_nick (server *serv, char *new_nick)
423 tcp_sendf (serv, "NICK %s\r\n", new_nick);
426 static void
427 irc_ping (server *serv, char *to, char *timestring)
429 if (*to)
430 tcp_sendf (serv, "PRIVMSG %s :\001PING %s\001\r\n", to, timestring);
431 else
432 tcp_sendf (serv, "PING %s\r\n", timestring);
435 static int
436 irc_raw (server *serv, char *raw)
438 int len;
439 char tbuf[4096];
440 if (*raw)
442 len = strlen (raw);
443 if (len < sizeof (tbuf) - 3)
445 len = snprintf (tbuf, sizeof (tbuf), "%s\r\n", raw);
446 tcp_send_len (serv, tbuf, len);
447 } else
449 tcp_send_len (serv, raw, len);
450 tcp_send_len (serv, "\r\n", 2);
452 return TRUE;
454 return FALSE;
457 /* ============================================================== */
458 /* ======================= IRC INPUT ============================ */
459 /* ============================================================== */
462 static void
463 channel_date (session *sess, char *chan, char *timestr)
465 time_t timestamp = (time_t) atol (timestr);
466 char *tim = ctime (&timestamp);
467 tim[24] = 0; /* get rid of the \n */
468 EMIT_SIGNAL (XP_TE_CHANDATE, sess, chan, tim, NULL, NULL, 0);
471 static void
472 process_numeric (session * sess, int n,
473 char *word[], char *word_eol[], char *text)
475 server *serv = sess->server;
476 /* show whois is the server tab */
477 session *whois_sess = serv->server_session;
479 /* unless this setting is on */
480 if (prefs.irc_whois_front)
481 whois_sess = serv->front_session;
483 char *ex;
485 switch (n)
487 case 1:
488 inbound_login_start (sess, word[3], word[1]);
489 /* if network is PTnet then you must get your IP address
490 from "001" server message */
491 if ((strncmp(word[7], "PTnet", 5) == 0) &&
492 (strncmp(word[8], "IRC", 3) == 0) &&
493 (strncmp(word[9], "Network", 7) == 0) &&
494 (strrchr(word[10], '@') != NULL))
496 serv->use_who = FALSE;
497 if (prefs.ip_from_server)
498 inbound_foundip (sess, strrchr(word[10], '@')+1);
501 goto def;
503 case 4: /* check the ircd type */
504 serv->use_listargs = FALSE;
505 serv->modes_per_line = 3; /* default to IRC RFC */
506 if (strncmp (word[5], "bahamut", 7) == 0) /* DALNet */
508 serv->use_listargs = TRUE; /* use the /list args */
509 } else if (strncmp (word[5], "u2.10.", 6) == 0) /* Undernet */
511 serv->use_listargs = TRUE; /* use the /list args */
512 serv->modes_per_line = 6; /* allow 6 modes per line */
513 } else if (strncmp (word[5], "glx2", 4) == 0)
515 serv->use_listargs = TRUE; /* use the /list args */
517 goto def;
519 case 5:
520 inbound_005 (serv, word);
521 goto def;
523 case 263: /*Server load is temporarily too heavy */
524 if (fe_is_chanwindow (sess->server))
526 fe_chan_list_end (sess->server);
527 fe_message (word_eol[5] + 1, FE_MSG_ERROR);
529 goto def;
531 case 301:
532 inbound_away (serv, word[4],
533 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
534 break;
536 case 302:
537 if (serv->skip_next_userhost)
539 char *eq = strchr (word[4], '=');
540 if (eq)
542 *eq = 0;
543 if (!serv->p_cmp (word[4] + 1, serv->nick))
545 char *at = strrchr (eq + 1, '@');
546 if (at)
547 inbound_foundip (sess, at + 1);
551 serv->skip_next_userhost = FALSE;
552 break;
554 else goto def;
556 case 303:
557 word[4]++;
558 notify_markonline (serv, word);
559 break;
561 case 305:
562 inbound_uback (serv);
563 goto def;
565 case 306:
566 inbound_uaway (serv);
567 goto def;
569 case 312:
570 if (!serv->skip_next_whois)
571 EMIT_SIGNAL (XP_TE_WHOIS3, whois_sess, word[4], word_eol[5], NULL, NULL, 0);
572 else
573 inbound_user_info (sess, NULL, NULL, NULL, word[5], word[4], NULL, NULL, 0xff);
574 break;
576 case 311: /* WHOIS 1st line */
577 serv->inside_whois = 1;
578 inbound_user_info_start (sess, word[4]);
579 if (!serv->skip_next_whois)
580 EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
581 word[6], word_eol[8] + 1, 0);
582 else
583 inbound_user_info (sess, NULL, word[5], word[6], NULL, word[4],
584 word_eol[8][0] == ':' ? word_eol[8] + 1 : word_eol[8], NULL, 0xff);
585 break;
587 case 314: /* WHOWAS */
588 inbound_user_info_start (sess, word[4]);
589 EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
590 word[6], word_eol[8] + 1, 0);
591 break;
593 case 317:
594 if (!serv->skip_next_whois)
596 time_t timestamp = (time_t) atol (word[6]);
597 long idle = atol (word[5]);
598 char *tim;
599 char outbuf[64];
601 snprintf (outbuf, sizeof (outbuf),
602 "%02ld:%02ld:%02ld", idle / 3600, (idle / 60) % 60,
603 idle % 60);
604 if (timestamp == 0)
605 EMIT_SIGNAL (XP_TE_WHOIS4, whois_sess, word[4],
606 outbuf, NULL, NULL, 0);
607 else
609 tim = ctime (&timestamp);
610 tim[19] = 0; /* get rid of the \n */
611 EMIT_SIGNAL (XP_TE_WHOIS4T, whois_sess, word[4],
612 outbuf, tim, NULL, 0);
615 break;
617 case 318: /* END OF WHOIS */
618 if (!serv->skip_next_whois)
619 EMIT_SIGNAL (XP_TE_WHOIS6, whois_sess, word[4], NULL,
620 NULL, NULL, 0);
621 serv->skip_next_whois = 0;
622 serv->inside_whois = 0;
623 break;
625 case 313:
626 case 319:
627 if (!serv->skip_next_whois)
628 EMIT_SIGNAL (XP_TE_WHOIS2, whois_sess, word[4],
629 word_eol[5] + 1, NULL, NULL, 0);
630 break;
632 case 307: /* dalnet version */
633 case 320: /* :is an identified user */
634 if (!serv->skip_next_whois)
635 EMIT_SIGNAL (XP_TE_WHOIS_ID, whois_sess, word[4],
636 word_eol[5] + 1, NULL, NULL, 0);
637 break;
639 case 321:
640 if (!fe_is_chanwindow (sess->server))
641 EMIT_SIGNAL (XP_TE_CHANLISTHEAD, serv->server_session, NULL, NULL, NULL, NULL, 0);
642 break;
644 case 322:
645 if (fe_is_chanwindow (sess->server))
647 fe_add_chan_list (sess->server, word[4], word[5], word_eol[6] + 1);
648 } else
650 PrintTextf (serv->server_session, "%-16s %-7d %s\017\n",
651 word[4], atoi (word[5]), word_eol[6] + 1);
653 break;
655 case 323:
656 if (!fe_is_chanwindow (sess->server))
657 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], word[2], NULL, 0);
658 else
659 fe_chan_list_end (sess->server);
660 break;
662 case 324:
663 sess = find_channel (serv, word[4]);
664 if (!sess)
665 sess = serv->server_session;
666 if (sess->ignore_mode)
667 sess->ignore_mode = FALSE;
668 else
669 EMIT_SIGNAL (XP_TE_CHANMODES, sess, word[4], word_eol[5],
670 NULL, NULL, 0);
671 fe_update_mode_buttons (sess, 't', '-');
672 fe_update_mode_buttons (sess, 'n', '-');
673 fe_update_mode_buttons (sess, 's', '-');
674 fe_update_mode_buttons (sess, 'i', '-');
675 fe_update_mode_buttons (sess, 'p', '-');
676 fe_update_mode_buttons (sess, 'm', '-');
677 fe_update_mode_buttons (sess, 'l', '-');
678 fe_update_mode_buttons (sess, 'k', '-');
679 handle_mode (serv, word, word_eol, "", TRUE);
680 break;
682 case 329:
683 sess = find_channel (serv, word[4]);
684 if (sess)
686 if (sess->ignore_date)
687 sess->ignore_date = FALSE;
688 else
689 channel_date (sess, word[4], word[5]);
691 break;
693 case 330:
694 if (!serv->skip_next_whois)
695 EMIT_SIGNAL (XP_TE_WHOIS_AUTH, whois_sess, word[4],
696 word_eol[6] + 1, word[5], NULL, 0);
697 inbound_user_info (sess, NULL, NULL, NULL, NULL, word[4], NULL, word[5], 0xff);
698 break;
700 case 332:
701 inbound_topic (serv, word[4],
702 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
703 break;
705 case 333:
706 inbound_topictime (serv, word[4], word[5], atol (word[6]));
707 break;
709 #if 0
710 case 338: /* Undernet Real user@host, Real IP */
711 EMIT_SIGNAL (XP_TE_WHOIS_REALHOST, sess, word[4], word[5], word[6],
712 (word_eol[7][0]==':') ? word_eol[7]+1 : word_eol[7], 0);
713 break;
714 #endif
716 case 341: /* INVITE ACK */
717 EMIT_SIGNAL (XP_TE_UINVITE, sess, word[4], word[5], serv->servername,
718 NULL, 0);
719 break;
721 case 352: /* WHO */
723 unsigned int away = 0;
724 session *who_sess = find_channel (serv, word[4]);
726 if (*word[9] == 'G')
727 away = 1;
729 inbound_user_info (sess, word[4], word[5], word[6], word[7],
730 word[8], word_eol[11], NULL, away);
732 /* try to show only user initiated whos */
733 if (!who_sess || !who_sess->doing_who)
734 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
735 word[2], NULL, 0);
737 break;
739 case 354: /* undernet WHOX: used as a reply for irc_away_status */
741 unsigned int away = 0;
742 session *who_sess;
744 /* irc_away_status and irc_user_list sends out a "152" */
745 if (!strcmp (word[4], "152"))
747 who_sess = find_channel (serv, word[5]);
749 if (*word[10] == 'G')
750 away = 1;
752 /* :server 354 yournick 152 #channel ~ident host servname nick H account :realname */
753 inbound_user_info (sess, word[5], word[6], word[7], word[8],
754 word[9], word_eol[12]+1, word[11], away);
756 /* try to show only user initiated whos */
757 if (!who_sess || !who_sess->doing_who)
758 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
759 word[1], word[2], NULL, 0);
760 } else
761 goto def;
763 break;
765 case 315: /* END OF WHO */
767 session *who_sess;
768 who_sess = find_channel (serv, word[4]);
769 if (who_sess)
771 if (!who_sess->doing_who)
772 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
773 word[1], word[2], NULL, 0);
774 who_sess->doing_who = FALSE;
775 } else
777 if (!serv->doing_dns)
778 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
779 word[1], word[2], NULL, 0);
780 serv->doing_dns = FALSE;
783 break;
785 case 348: /* +e-list entry */
786 if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], TRUE))
787 goto def;
788 break;
790 case 349: /* end of exemption list */
791 sess = find_channel (serv, word[4]);
792 if (!sess)
794 sess = serv->front_session;
795 goto def;
797 if (!fe_is_banwindow (sess))
798 goto def;
799 fe_ban_list_end (sess, TRUE);
800 break;
802 case 353: /* NAMES */
803 inbound_nameslist (serv, word[5],
804 (word_eol[6][0] == ':') ? word_eol[6] + 1 : word_eol[6]);
805 break;
807 case 366:
808 if (!inbound_nameslist_end (serv, word[4]))
809 goto def;
810 break;
812 case 367: /* banlist entry */
813 inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], FALSE);
814 break;
816 case 368:
817 sess = find_channel (serv, word[4]);
818 if (!sess)
820 sess = serv->front_session;
821 goto def;
823 if (!fe_is_banwindow (sess))
824 goto def;
825 fe_ban_list_end (sess, FALSE);
826 break;
828 case 369: /* WHOWAS end */
829 case 406: /* WHOWAS error */
830 EMIT_SIGNAL (XP_TE_SERVTEXT, whois_sess, text, word[1], word[2], NULL, 0);
831 serv->inside_whois = 0;
832 break;
834 case 372: /* motd text */
835 case 375: /* motd start */
836 if (!prefs.skipmotd || serv->motd_skipped)
837 EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, NULL,
838 NULL, 0);
839 break;
841 case 376: /* end of motd */
842 case 422: /* motd file is missing */
843 inbound_login_end (sess, text);
844 break;
846 case 433: /* nickname in use */
847 case 432: /* erroneous nickname */
848 if (serv->end_of_motd)
849 goto def;
850 inbound_next_nick (sess, word[4]);
851 break;
853 case 437:
854 if (serv->end_of_motd || is_channel (serv, word[4]))
855 goto def;
856 inbound_next_nick (sess, word[4]);
857 break;
859 case 471:
860 EMIT_SIGNAL (XP_TE_USERLIMIT, sess, word[4], NULL, NULL, NULL, 0);
861 break;
863 case 473:
864 EMIT_SIGNAL (XP_TE_INVITE, sess, word[4], NULL, NULL, NULL, 0);
865 break;
867 case 474:
868 EMIT_SIGNAL (XP_TE_BANNED, sess, word[4], NULL, NULL, NULL, 0);
869 break;
871 case 475:
872 EMIT_SIGNAL (XP_TE_KEYWORD, sess, word[4], NULL, NULL, NULL, 0);
873 break;
875 case 601:
876 notify_set_offline (serv, word[4], FALSE);
877 break;
879 case 605:
880 notify_set_offline (serv, word[4], TRUE);
881 break;
883 case 600:
884 case 604:
885 notify_set_online (serv, word[4]);
886 break;
888 case 730: /* RPL_MONONLINE */
889 ex = strchr (word[4], '!'); /* only send the nick */
890 if (ex)
891 ex[0] = 0;
892 notify_set_online (serv, word[4] + 1);
893 break;
895 case 731: /* RPL_MONOFFLINE */
896 ex = strchr (word[4], '!'); /* only send the nick */
897 if (ex)
898 ex[0] = 0;
899 notify_set_offline (serv, word[4] + 1, FALSE);
900 break;
902 case 900: /* successful SASL 'logged in as ' */
903 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, word_eol[6]+1, word[1], word[2], NULL, 0);
904 break;
905 case 903: /* successful SASL auth */
906 case 904: /* aborted SASL auth */
907 case 905: /* failed SASL auth */
908 case 906: /* registration completes before SASL auth */
909 case 907: /* attempting to re-auth after a successful auth */
910 EMIT_SIGNAL (XP_TE_SASLRESPONSE, serv->server_session, word[1], word[2], word[3], ++word_eol[4], 0);
911 tcp_send_len (serv, "CAP END\r\n", 9);
912 break;
914 default:
916 if (serv->inside_whois && word[4][0])
918 /* some unknown WHOIS reply, ircd coders make them up weekly */
919 if (!serv->skip_next_whois)
920 EMIT_SIGNAL (XP_TE_WHOIS_SPECIAL, whois_sess, word[4],
921 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5],
922 word[2], NULL, 0);
923 return;
926 def:
927 if (is_channel (serv, word[4]))
929 session *realsess = find_channel (serv, word[4]);
930 if (!realsess)
931 realsess = serv->server_session;
932 EMIT_SIGNAL (XP_TE_SERVTEXT, realsess, text, word[1], word[2], NULL, 0);
933 } else
935 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
936 word[2], NULL, 0);
941 /* handle named messages that starts with a ':' */
943 static void
944 process_named_msg (session *sess, char *type, char *word[], char *word_eol[])
946 server *serv = sess->server;
947 char ip[128], nick[NICKLEN];
948 char *text, *ex;
949 int len = strlen (type);
951 /* fill in the "ip" and "nick" buffers */
952 ex = strchr (word[1], '!');
953 if (!ex) /* no '!', must be a server message */
955 safe_strcpy (ip, word[1], sizeof (ip));
956 safe_strcpy (nick, word[1], sizeof (nick));
957 } else
959 safe_strcpy (ip, ex + 1, sizeof (ip));
960 ex[0] = 0;
961 safe_strcpy (nick, word[1], sizeof (nick));
962 ex[0] = '!';
965 if (len == 4)
967 guint32 t;
969 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
970 /* this should compile to a bunch of: CMP.L, JE ... nice & fast */
971 switch (t)
974 case WORDL('A','C','C','O'):
975 inbound_account (serv, nick, word[3]);
976 return;
978 case WORDL('J','O','I','N'):
980 char *chan = word[3];
981 char *account = word[4];
982 char *realname = word_eol[5] + 1;
984 if (*chan == ':')
985 chan++;
986 if (!serv->p_cmp (nick, serv->nick))
987 inbound_ujoin (serv, chan, nick, ip);
988 else
989 inbound_join (serv, chan, nick, ip, account, realname);
991 return;
993 case WORDL('K','I','C','K'):
995 char *kicked = word[4];
996 char *reason = word_eol[5];
997 if (*kicked)
999 if (*reason == ':')
1000 reason++;
1001 if (!strcmp (kicked, serv->nick))
1002 inbound_ukick (serv, word[3], nick, reason);
1003 else
1004 inbound_kick (serv, word[3], kicked, nick, reason);
1007 return;
1009 case WORDL('K','I','L','L'):
1010 EMIT_SIGNAL (XP_TE_KILL, sess, nick, word_eol[5], NULL, NULL, 0);
1011 return;
1013 case WORDL('M','O','D','E'):
1014 handle_mode (serv, word, word_eol, nick, FALSE); /* modes.c */
1015 return;
1017 case WORDL('N','I','C','K'):
1018 inbound_newnick (serv, nick, (word_eol[3][0] == ':')
1019 ? word_eol[3] + 1 : word_eol[3], FALSE);
1020 return;
1022 case WORDL('P','A','R','T'):
1024 char *chan = word[3];
1025 char *reason = word_eol[4];
1027 if (*chan == ':')
1028 chan++;
1029 if (*reason == ':')
1030 reason++;
1031 if (!strcmp (nick, serv->nick))
1032 inbound_upart (serv, chan, ip, reason);
1033 else
1034 inbound_part (serv, chan, nick, ip, reason);
1036 return;
1038 case WORDL('P','O','N','G'):
1039 inbound_ping_reply (serv->server_session,
1040 (word[4][0] == ':') ? word[4] + 1 : word[4], word[3]);
1041 return;
1043 case WORDL('Q','U','I','T'):
1044 inbound_quit (serv, nick, ip,
1045 (word_eol[3][0] == ':') ? word_eol[3] + 1 : word_eol[3]);
1046 return;
1048 case WORDL('A','W','A','Y'):
1049 inbound_away_notify (serv, nick,
1050 (word_eol[3][0] == ':') ? word_eol[3] + 1 : NULL);
1051 return;
1054 goto garbage;
1057 else if(len == 3) {
1058 guint32 t;
1060 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
1061 switch (t)
1063 case WORDL('C','A','P','\0'):
1064 if (strncasecmp (word[4], "ACK", 3) == 0)
1066 inbound_cap_ack (serv, word[1],
1067 word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5]);
1069 else if (strncasecmp (word[4], "LS", 2) == 0)
1071 inbound_cap_ls (serv, word[1],
1072 word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5]);
1074 else if (strncasecmp (word[4], "NAK", 3) == 0)
1076 inbound_cap_nak (serv);
1078 else if (strncasecmp (word[4], "LIST", 4) == 0)
1080 inbound_cap_list (serv, word[1],
1081 word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5]);
1084 return;
1089 else if (len >= 5)
1091 guint32 t;
1093 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
1094 /* this should compile to a bunch of: CMP.L, JE ... nice & fast */
1095 switch (t)
1097 case WORDL('I','N','V','I'):
1098 if (ignore_check (word[1], IG_INVI))
1099 return;
1101 if (word[4][0] == ':')
1102 EMIT_SIGNAL (XP_TE_INVITED, sess, word[4] + 1, nick,
1103 serv->servername, NULL, 0);
1104 else
1105 EMIT_SIGNAL (XP_TE_INVITED, sess, word[4], nick,
1106 serv->servername, NULL, 0);
1108 return;
1110 case WORDL('N','O','T','I'):
1112 int id = FALSE;
1113 char *response;
1115 text = word_eol[4];
1116 if (*text == ':')
1118 text++;
1121 if (!strncmp (text, "CHALLENGE ", 10)) /* QuakeNet CHALLENGE upon our request */
1123 response = challengeauth_response (((ircnet *)serv->network)->user ? ((ircnet *)serv->network)->user : prefs.username, serv->password, word[5]);
1125 tcp_sendf (serv, "PRIVMSG %s :CHALLENGEAUTH %s %s %s\r\n",
1126 CHALLENGEAUTH_NICK,
1127 ((ircnet *)serv->network)->user ? ((ircnet *)serv->network)->user : prefs.username,
1128 response,
1129 CHALLENGEAUTH_ALGO);
1131 g_free (response);
1132 return; /* omit the CHALLENGE <hash> ALGOS message */
1135 if (serv->have_idmsg)
1137 if (*text == '+')
1139 id = TRUE;
1140 text++;
1141 } else if (*text == '-')
1142 text++;
1145 if (!ignore_check (word[1], IG_NOTI))
1146 inbound_notice (serv, word[3], nick, text, ip, id);
1148 return;
1150 case WORDL('P','R','I','V'):
1152 char *to = word[3];
1153 int len;
1154 int id = FALSE; /* identified */
1155 if (*to)
1157 text = word_eol[4];
1158 if (*text == ':')
1159 text++;
1160 if (serv->have_idmsg)
1162 if (*text == '+')
1164 id = TRUE;
1165 text++;
1166 } else if (*text == '-')
1167 text++;
1169 len = strlen (text);
1170 if (text[0] == 1 && text[len - 1] == 1) /* ctcp */
1172 text[len - 1] = 0;
1173 text++;
1174 if (strncasecmp (text, "ACTION", 6) != 0)
1175 flood_check (nick, ip, serv, sess, 0);
1176 if (strncasecmp (text, "DCC ", 4) == 0)
1177 /* redo this with handle_quotes TRUE */
1178 process_data_init (word[1], word_eol[1], word, word_eol, TRUE, FALSE);
1179 ctcp_handle (sess, to, nick, ip, text, word, word_eol, id);
1180 } else
1182 if (is_channel (serv, to))
1184 if (ignore_check (word[1], IG_CHAN))
1185 return;
1186 inbound_chanmsg (serv, NULL, to, nick, text, FALSE, id);
1187 } else
1189 if (ignore_check (word[1], IG_PRIV))
1190 return;
1191 inbound_privmsg (serv, nick, ip, text, id);
1196 return;
1198 case WORDL('T','O','P','I'):
1199 inbound_topicnew (serv, nick, word[3],
1200 (word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4]);
1201 return;
1203 case WORDL('W','A','L','L'):
1204 text = word_eol[3];
1205 if (*text == ':')
1206 text++;
1207 EMIT_SIGNAL (XP_TE_WALLOPS, sess, nick, text, NULL, NULL, 0);
1208 return;
1212 garbage:
1213 /* unknown message */
1214 PrintTextf (sess, "GARBAGE: %s\n", word_eol[1]);
1217 /* handle named messages that DON'T start with a ':' */
1219 static void
1220 process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol[])
1222 sess = sess->server->server_session;
1224 if (!strncmp (buf, "PING ", 5))
1226 tcp_sendf (sess->server, "PONG %s\r\n", buf + 5);
1227 return;
1229 if (!strncmp (buf, "ERROR", 5))
1231 EMIT_SIGNAL (XP_TE_SERVERERROR, sess, buf + 7, NULL, NULL, NULL, 0);
1232 return;
1234 if (!strncmp (buf, "NOTICE ", 7))
1236 buf = word_eol[3];
1237 if (*buf == ':')
1238 buf++;
1239 EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, buf, sess->server->servername, NULL, NULL, 0);
1240 return;
1242 if (!strncmp (buf, "AUTHENTICATE +", 14)) /* omit SASL "empty" responses */
1244 return;
1247 EMIT_SIGNAL (XP_TE_SERVTEXT, sess, buf, sess->server->servername, rawname, NULL, 0);
1250 /* irc_inline() - 1 single line received from serv */
1252 static void
1253 irc_inline (server *serv, char *buf, int len)
1255 session *sess, *tmp;
1256 char *type, *text;
1257 char *word[PDIWORDS+1];
1258 char *word_eol[PDIWORDS+1];
1259 char pdibuf_static[522]; /* 1 line can potentially be 512*6 in utf8 */
1260 char *pdibuf = pdibuf_static;
1262 url_check_line (buf, len);
1264 /* need more than 522? fall back to malloc */
1265 if (len >= sizeof (pdibuf_static))
1266 pdibuf = malloc (len + 1);
1268 sess = serv->front_session;
1270 /* Python relies on this */
1271 word[PDIWORDS] = NULL;
1272 word_eol[PDIWORDS] = NULL;
1274 if (buf[0] == ':')
1276 /* split line into words and words_to_end_of_line */
1277 process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
1279 /* find a context for this message */
1280 if (is_channel (serv, word[3]))
1282 tmp = find_channel (serv, word[3]);
1283 if (tmp)
1284 sess = tmp;
1287 /* for server messages, the 2nd word is the "message type" */
1288 type = word[2];
1290 word[0] = type;
1291 word_eol[1] = buf; /* keep the ":" for plugins */
1292 if (plugin_emit_server (sess, type, word, word_eol))
1293 goto xit;
1294 word[1]++;
1295 word_eol[1] = buf + 1; /* but not for xchat internally */
1297 } else
1299 process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
1300 word[0] = type = word[1];
1301 if (plugin_emit_server (sess, type, word, word_eol))
1302 goto xit;
1305 if (buf[0] != ':')
1307 process_named_servermsg (sess, buf, word[0], word_eol);
1308 goto xit;
1311 /* see if the second word is a numeric */
1312 if (isdigit ((unsigned char) word[2][0]))
1314 text = word_eol[4];
1315 if (*text == ':')
1316 text++;
1318 process_numeric (sess, atoi (word[2]), word, word_eol, text);
1319 } else
1321 process_named_msg (sess, type, word, word_eol);
1324 xit:
1325 if (pdibuf != pdibuf_static)
1326 free (pdibuf);
1329 void
1330 proto_fill_her_up (server *serv)
1332 serv->p_inline = irc_inline;
1333 serv->p_invite = irc_invite;
1334 serv->p_cycle = irc_cycle;
1335 serv->p_ctcp = irc_ctcp;
1336 serv->p_nctcp = irc_nctcp;
1337 serv->p_quit = irc_quit;
1338 serv->p_kick = irc_kick;
1339 serv->p_part = irc_part;
1340 serv->p_ns_identify = irc_ns_identify;
1341 serv->p_ns_ghost = irc_ns_ghost;
1342 serv->p_join = irc_join;
1343 serv->p_join_list = irc_join_list;
1344 serv->p_login = irc_login;
1345 serv->p_join_info = irc_join_info;
1346 serv->p_mode = irc_mode;
1347 serv->p_user_list = irc_user_list;
1348 serv->p_away_status = irc_away_status;
1349 /*serv->p_get_ip = irc_get_ip;*/
1350 serv->p_whois = irc_user_whois;
1351 serv->p_get_ip = irc_user_list;
1352 serv->p_get_ip_uh = irc_userhost;
1353 serv->p_set_back = irc_set_back;
1354 serv->p_set_away = irc_set_away;
1355 serv->p_message = irc_message;
1356 serv->p_action = irc_action;
1357 serv->p_notice = irc_notice;
1358 serv->p_topic = irc_topic;
1359 serv->p_list_channels = irc_list_channels;
1360 serv->p_change_nick = irc_change_nick;
1361 serv->p_names = irc_names;
1362 serv->p_ping = irc_ping;
1363 serv->p_raw = irc_raw;
1364 serv->p_cmp = rfc_casecmp; /* can be changed by 005 in modes.c */