Replace various network options with universal login method option
[rofl0r-ixchat.git] / src / common / proto-irc.c
blobee3ddb6ae59aa278cce8b74aba935406bbd57a21
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] & serv->loginmethod == 7)
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->loginmethod)
63 case 1:
64 tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
65 break;
66 case 2:
67 tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
68 break;
69 case 3:
70 tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
71 break;
72 case 4:
73 tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
74 break;
75 case 5:
76 /* why couldn't QuakeNet implement one of the existing ones? */
77 tcp_sendf (serv, "AUTH %s %s\r\n", arg1, arg2);
81 static void
82 irc_ns_identify (server *serv, char *pass)
84 if (serv->loginmethod == 5) /* QuakeNet needs to do everything in its own ways... */
86 irc_nickserv (serv, "", serv->nick, pass, "");
88 else
90 irc_nickserv (serv, "IDENTIFY", pass, "", "");
94 static void
95 irc_ns_ghost (server *serv, char *usname, char *pass)
97 if (serv->loginmethod != 5)
98 irc_nickserv (serv, "GHOST", usname, " ", pass);
101 static void
102 irc_join (server *serv, char *channel, char *key)
104 if (key[0])
105 tcp_sendf (serv, "JOIN %s %s\r\n", channel, key);
106 else
107 tcp_sendf (serv, "JOIN %s\r\n", channel);
110 static void
111 irc_join_list_flush (server *serv, GString *c, GString *k)
113 char *chanstr, *keystr;
115 chanstr = g_string_free (c, FALSE);
116 keystr = g_string_free (k, FALSE);
117 if (chanstr[0])
119 if (keystr[0])
120 tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr);
121 else
122 tcp_sendf (serv, "JOIN %s\r\n", chanstr);
124 g_free (chanstr);
125 g_free (keystr);
128 /* join a whole list of channels & keys, split to multiple lines
129 * to get around 512 limit */
131 static void
132 irc_join_list (server *serv, GSList *channels, GSList *keys)
134 GSList *clist;
135 GSList *klist;
136 GString *c = g_string_new (NULL);
137 GString *k = g_string_new (NULL);
138 int len;
139 int add;
140 int i, j;
142 i = j = 0;
143 len = 9; /* "JOIN<space><space>\r\n" */
144 clist = channels;
145 klist = keys;
147 while (clist)
149 /* measure how many bytes this channel would add... */
150 if (1)
152 add = strlen (clist->data);
153 if (i != 0)
154 add++; /* comma */
157 if (klist->data)
159 add += strlen (klist->data);
161 else
163 add++; /* 'x' filler */
166 if (j != 0)
167 add++; /* comma */
169 /* too big? dump buffer and start a fresh one */
170 if (len + add > 512)
172 irc_join_list_flush (serv, c, k);
174 c = g_string_new (NULL);
175 k = g_string_new (NULL);
176 i = j = 0;
177 len = 9;
180 /* now actually add it to our GStrings */
181 if (1)
183 add = strlen (clist->data);
184 if (i != 0)
186 add++;
187 g_string_append_c (c, ',');
189 g_string_append (c, clist->data);
190 i++;
193 if (klist->data)
195 add += strlen (klist->data);
196 if (j != 0)
198 add++;
199 g_string_append_c (k, ',');
201 g_string_append (k, klist->data);
202 j++;
204 else
206 add++;
207 if (j != 0)
209 add++;
210 g_string_append_c (k, ',');
212 g_string_append_c (k, 'x');
213 j++;
216 len += add;
218 klist = klist->next;
219 clist = clist->next;
222 irc_join_list_flush (serv, c, k);
225 static void
226 irc_part (server *serv, char *channel, char *reason)
228 if (reason[0])
229 tcp_sendf (serv, "PART %s :%s\r\n", channel, reason);
230 else
231 tcp_sendf (serv, "PART %s\r\n", channel);
234 static void
235 irc_quit (server *serv, char *reason)
237 if (reason[0])
238 tcp_sendf (serv, "QUIT :%s\r\n", reason);
239 else
240 tcp_send_len (serv, "QUIT\r\n", 6);
243 static void
244 irc_set_back (server *serv)
246 tcp_send_len (serv, "AWAY\r\n", 6);
249 static void
250 irc_set_away (server *serv, char *reason)
252 if (reason)
254 if (!reason[0])
255 reason = " ";
257 else
259 reason = " ";
262 tcp_sendf (serv, "AWAY :%s\r\n", reason);
265 static void
266 irc_ctcp (server *serv, char *to, char *msg)
268 tcp_sendf (serv, "PRIVMSG %s :\001%s\001\r\n", to, msg);
271 static void
272 irc_nctcp (server *serv, char *to, char *msg)
274 tcp_sendf (serv, "NOTICE %s :\001%s\001\r\n", to, msg);
277 static void
278 irc_cycle (server *serv, char *channel, char *key)
280 tcp_sendf (serv, "PART %s\r\nJOIN %s %s\r\n", channel, channel, key);
283 static void
284 irc_kick (server *serv, char *channel, char *nick, char *reason)
286 if (reason[0])
287 tcp_sendf (serv, "KICK %s %s :%s\r\n", channel, nick, reason);
288 else
289 tcp_sendf (serv, "KICK %s %s\r\n", channel, nick);
292 static void
293 irc_invite (server *serv, char *channel, char *nick)
295 tcp_sendf (serv, "INVITE %s %s\r\n", nick, channel);
298 static void
299 irc_mode (server *serv, char *target, char *mode)
301 tcp_sendf (serv, "MODE %s %s\r\n", target, mode);
304 /* find channel info when joined */
306 static void
307 irc_join_info (server *serv, char *channel)
309 tcp_sendf (serv, "MODE %s\r\n", channel);
312 /* initiate userlist retreival */
314 static void
315 irc_user_list (server *serv, char *channel)
317 if (serv->have_whox)
318 tcp_sendf (serv, "WHO %s %%chtsunfra,152\r\n", channel);
319 else
320 tcp_sendf (serv, "WHO %s\r\n", channel);
323 /* userhost */
325 static void
326 irc_userhost (server *serv, char *nick)
328 tcp_sendf (serv, "USERHOST %s\r\n", nick);
331 static void
332 irc_away_status (server *serv, char *channel)
334 if (serv->have_whox)
335 tcp_sendf (serv, "WHO %s %%chtsunfra,152\r\n", channel);
336 else
337 tcp_sendf (serv, "WHO %s\r\n", channel);
340 /*static void
341 irc_get_ip (server *serv, char *nick)
343 tcp_sendf (serv, "WHO %s\r\n", nick);
348 * Command: WHOIS
349 * Parameters: [<server>] <nickmask>[,<nickmask>[,...]]
351 static void
352 irc_user_whois (server *serv, char *nicks)
354 tcp_sendf (serv, "WHOIS %s\r\n", nicks);
357 static void
358 irc_message (server *serv, char *channel, char *text)
360 tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text);
363 static void
364 irc_action (server *serv, char *channel, char *act)
366 tcp_sendf (serv, "PRIVMSG %s :\001ACTION %s\001\r\n", channel, act);
369 static void
370 irc_notice (server *serv, char *channel, char *text)
372 tcp_sendf (serv, "NOTICE %s :%s\r\n", channel, text);
375 static void
376 irc_topic (server *serv, char *channel, char *topic)
378 if (!topic)
379 tcp_sendf (serv, "TOPIC %s :\r\n", channel);
380 else if (topic[0])
381 tcp_sendf (serv, "TOPIC %s :%s\r\n", channel, topic);
382 else
383 tcp_sendf (serv, "TOPIC %s\r\n", channel);
386 static void
387 irc_list_channels (server *serv, char *arg, int min_users)
389 if (arg[0])
391 tcp_sendf (serv, "LIST %s\r\n", arg);
392 return;
395 if (serv->use_listargs)
396 tcp_sendf (serv, "LIST >%d,<10000\r\n", min_users - 1);
397 else
398 tcp_send_len (serv, "LIST\r\n", 6);
401 static void
402 irc_names (server *serv, char *channel)
404 tcp_sendf (serv, "NAMES %s\r\n", channel);
407 static void
408 irc_change_nick (server *serv, char *new_nick)
410 tcp_sendf (serv, "NICK %s\r\n", new_nick);
413 static void
414 irc_ping (server *serv, char *to, char *timestring)
416 if (*to)
417 tcp_sendf (serv, "PRIVMSG %s :\001PING %s\001\r\n", to, timestring);
418 else
419 tcp_sendf (serv, "PING %s\r\n", timestring);
422 static int
423 irc_raw (server *serv, char *raw)
425 int len;
426 char tbuf[4096];
427 if (*raw)
429 len = strlen (raw);
430 if (len < sizeof (tbuf) - 3)
432 len = snprintf (tbuf, sizeof (tbuf), "%s\r\n", raw);
433 tcp_send_len (serv, tbuf, len);
434 } else
436 tcp_send_len (serv, raw, len);
437 tcp_send_len (serv, "\r\n", 2);
439 return TRUE;
441 return FALSE;
444 /* ============================================================== */
445 /* ======================= IRC INPUT ============================ */
446 /* ============================================================== */
449 static void
450 channel_date (session *sess, char *chan, char *timestr)
452 time_t timestamp = (time_t) atol (timestr);
453 char *tim = ctime (&timestamp);
454 tim[24] = 0; /* get rid of the \n */
455 EMIT_SIGNAL (XP_TE_CHANDATE, sess, chan, tim, NULL, NULL, 0);
458 static void
459 process_numeric (session * sess, int n,
460 char *word[], char *word_eol[], char *text)
462 server *serv = sess->server;
463 /* show whois is the server tab */
464 session *whois_sess = serv->server_session;
466 /* unless this setting is on */
467 if (prefs.irc_whois_front)
468 whois_sess = serv->front_session;
470 char *ex;
472 switch (n)
474 case 1:
475 inbound_login_start (sess, word[3], word[1]);
476 /* if network is PTnet then you must get your IP address
477 from "001" server message */
478 if ((strncmp(word[7], "PTnet", 5) == 0) &&
479 (strncmp(word[8], "IRC", 3) == 0) &&
480 (strncmp(word[9], "Network", 7) == 0) &&
481 (strrchr(word[10], '@') != NULL))
483 serv->use_who = FALSE;
484 if (prefs.ip_from_server)
485 inbound_foundip (sess, strrchr(word[10], '@')+1);
488 goto def;
490 case 4: /* check the ircd type */
491 serv->use_listargs = FALSE;
492 serv->modes_per_line = 3; /* default to IRC RFC */
493 if (strncmp (word[5], "bahamut", 7) == 0) /* DALNet */
495 serv->use_listargs = TRUE; /* use the /list args */
496 } else if (strncmp (word[5], "u2.10.", 6) == 0) /* Undernet */
498 serv->use_listargs = TRUE; /* use the /list args */
499 serv->modes_per_line = 6; /* allow 6 modes per line */
500 } else if (strncmp (word[5], "glx2", 4) == 0)
502 serv->use_listargs = TRUE; /* use the /list args */
504 goto def;
506 case 5:
507 inbound_005 (serv, word);
508 goto def;
510 case 263: /*Server load is temporarily too heavy */
511 if (fe_is_chanwindow (sess->server))
513 fe_chan_list_end (sess->server);
514 fe_message (word_eol[5] + 1, FE_MSG_ERROR);
516 goto def;
518 case 301:
519 inbound_away (serv, word[4],
520 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
521 break;
523 case 302:
524 if (serv->skip_next_userhost)
526 char *eq = strchr (word[4], '=');
527 if (eq)
529 *eq = 0;
530 if (!serv->p_cmp (word[4] + 1, serv->nick))
532 char *at = strrchr (eq + 1, '@');
533 if (at)
534 inbound_foundip (sess, at + 1);
538 serv->skip_next_userhost = FALSE;
539 break;
541 else goto def;
543 case 303:
544 word[4]++;
545 notify_markonline (serv, word);
546 break;
548 case 305:
549 inbound_uback (serv);
550 goto def;
552 case 306:
553 inbound_uaway (serv);
554 goto def;
556 case 312:
557 if (!serv->skip_next_whois)
558 EMIT_SIGNAL (XP_TE_WHOIS3, whois_sess, word[4], word_eol[5], NULL, NULL, 0);
559 else
560 inbound_user_info (sess, NULL, NULL, NULL, word[5], word[4], NULL, NULL, 0xff);
561 break;
563 case 311: /* WHOIS 1st line */
564 serv->inside_whois = 1;
565 inbound_user_info_start (sess, word[4]);
566 if (!serv->skip_next_whois)
567 EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
568 word[6], word_eol[8] + 1, 0);
569 else
570 inbound_user_info (sess, NULL, word[5], word[6], NULL, word[4],
571 word_eol[8][0] == ':' ? word_eol[8] + 1 : word_eol[8], NULL, 0xff);
572 break;
574 case 314: /* WHOWAS */
575 inbound_user_info_start (sess, word[4]);
576 EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5],
577 word[6], word_eol[8] + 1, 0);
578 break;
580 case 317:
581 if (!serv->skip_next_whois)
583 time_t timestamp = (time_t) atol (word[6]);
584 long idle = atol (word[5]);
585 char *tim;
586 char outbuf[64];
588 snprintf (outbuf, sizeof (outbuf),
589 "%02ld:%02ld:%02ld", idle / 3600, (idle / 60) % 60,
590 idle % 60);
591 if (timestamp == 0)
592 EMIT_SIGNAL (XP_TE_WHOIS4, whois_sess, word[4],
593 outbuf, NULL, NULL, 0);
594 else
596 tim = ctime (&timestamp);
597 tim[19] = 0; /* get rid of the \n */
598 EMIT_SIGNAL (XP_TE_WHOIS4T, whois_sess, word[4],
599 outbuf, tim, NULL, 0);
602 break;
604 case 318: /* END OF WHOIS */
605 if (!serv->skip_next_whois)
606 EMIT_SIGNAL (XP_TE_WHOIS6, whois_sess, word[4], NULL,
607 NULL, NULL, 0);
608 serv->skip_next_whois = 0;
609 serv->inside_whois = 0;
610 break;
612 case 313:
613 case 319:
614 if (!serv->skip_next_whois)
615 EMIT_SIGNAL (XP_TE_WHOIS2, whois_sess, word[4],
616 word_eol[5] + 1, NULL, NULL, 0);
617 break;
619 case 307: /* dalnet version */
620 case 320: /* :is an identified user */
621 if (!serv->skip_next_whois)
622 EMIT_SIGNAL (XP_TE_WHOIS_ID, whois_sess, word[4],
623 word_eol[5] + 1, NULL, NULL, 0);
624 break;
626 case 321:
627 if (!fe_is_chanwindow (sess->server))
628 EMIT_SIGNAL (XP_TE_CHANLISTHEAD, serv->server_session, NULL, NULL, NULL, NULL, 0);
629 break;
631 case 322:
632 if (fe_is_chanwindow (sess->server))
634 fe_add_chan_list (sess->server, word[4], word[5], word_eol[6] + 1);
635 } else
637 PrintTextf (serv->server_session, "%-16s %-7d %s\017\n",
638 word[4], atoi (word[5]), word_eol[6] + 1);
640 break;
642 case 323:
643 if (!fe_is_chanwindow (sess->server))
644 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], word[2], NULL, 0);
645 else
646 fe_chan_list_end (sess->server);
647 break;
649 case 324:
650 sess = find_channel (serv, word[4]);
651 if (!sess)
652 sess = serv->server_session;
653 if (sess->ignore_mode)
654 sess->ignore_mode = FALSE;
655 else
656 EMIT_SIGNAL (XP_TE_CHANMODES, sess, word[4], word_eol[5],
657 NULL, NULL, 0);
658 fe_update_mode_buttons (sess, 't', '-');
659 fe_update_mode_buttons (sess, 'n', '-');
660 fe_update_mode_buttons (sess, 's', '-');
661 fe_update_mode_buttons (sess, 'i', '-');
662 fe_update_mode_buttons (sess, 'p', '-');
663 fe_update_mode_buttons (sess, 'm', '-');
664 fe_update_mode_buttons (sess, 'l', '-');
665 fe_update_mode_buttons (sess, 'k', '-');
666 handle_mode (serv, word, word_eol, "", TRUE);
667 break;
669 case 329:
670 sess = find_channel (serv, word[4]);
671 if (sess)
673 if (sess->ignore_date)
674 sess->ignore_date = FALSE;
675 else
676 channel_date (sess, word[4], word[5]);
678 break;
680 case 330:
681 if (!serv->skip_next_whois)
682 EMIT_SIGNAL (XP_TE_WHOIS_AUTH, whois_sess, word[4],
683 word_eol[6] + 1, word[5], NULL, 0);
684 inbound_user_info (sess, NULL, NULL, NULL, NULL, word[4], NULL, word[5], 0xff);
685 break;
687 case 332:
688 inbound_topic (serv, word[4],
689 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]);
690 break;
692 case 333:
693 inbound_topictime (serv, word[4], word[5], atol (word[6]));
694 break;
696 #if 0
697 case 338: /* Undernet Real user@host, Real IP */
698 EMIT_SIGNAL (XP_TE_WHOIS_REALHOST, sess, word[4], word[5], word[6],
699 (word_eol[7][0]==':') ? word_eol[7]+1 : word_eol[7], 0);
700 break;
701 #endif
703 case 341: /* INVITE ACK */
704 EMIT_SIGNAL (XP_TE_UINVITE, sess, word[4], word[5], serv->servername,
705 NULL, 0);
706 break;
708 case 352: /* WHO */
710 unsigned int away = 0;
711 session *who_sess = find_channel (serv, word[4]);
713 if (*word[9] == 'G')
714 away = 1;
716 inbound_user_info (sess, word[4], word[5], word[6], word[7],
717 word[8], word_eol[11], NULL, away);
719 /* try to show only user initiated whos */
720 if (!who_sess || !who_sess->doing_who)
721 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
722 word[2], NULL, 0);
724 break;
726 case 354: /* undernet WHOX: used as a reply for irc_away_status */
728 unsigned int away = 0;
729 session *who_sess;
731 /* irc_away_status and irc_user_list sends out a "152" */
732 if (!strcmp (word[4], "152"))
734 who_sess = find_channel (serv, word[5]);
736 if (*word[10] == 'G')
737 away = 1;
739 /* :server 354 yournick 152 #channel ~ident host servname nick H account :realname */
740 inbound_user_info (sess, word[5], word[6], word[7], word[8],
741 word[9], word_eol[12]+1, word[11], away);
743 /* try to show only user initiated whos */
744 if (!who_sess || !who_sess->doing_who)
745 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
746 word[1], word[2], NULL, 0);
747 } else
748 goto def;
750 break;
752 case 315: /* END OF WHO */
754 session *who_sess;
755 who_sess = find_channel (serv, word[4]);
756 if (who_sess)
758 if (!who_sess->doing_who)
759 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
760 word[1], word[2], NULL, 0);
761 who_sess->doing_who = FALSE;
762 } else
764 if (!serv->doing_dns)
765 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text,
766 word[1], word[2], NULL, 0);
767 serv->doing_dns = FALSE;
770 break;
772 case 348: /* +e-list entry */
773 if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], TRUE))
774 goto def;
775 break;
777 case 349: /* end of exemption list */
778 sess = find_channel (serv, word[4]);
779 if (!sess)
781 sess = serv->front_session;
782 goto def;
784 if (!fe_is_banwindow (sess))
785 goto def;
786 fe_ban_list_end (sess, TRUE);
787 break;
789 case 353: /* NAMES */
790 inbound_nameslist (serv, word[5],
791 (word_eol[6][0] == ':') ? word_eol[6] + 1 : word_eol[6]);
792 break;
794 case 366:
795 if (!inbound_nameslist_end (serv, word[4]))
796 goto def;
797 break;
799 case 367: /* banlist entry */
800 inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], FALSE);
801 break;
803 case 368:
804 sess = find_channel (serv, word[4]);
805 if (!sess)
807 sess = serv->front_session;
808 goto def;
810 if (!fe_is_banwindow (sess))
811 goto def;
812 fe_ban_list_end (sess, FALSE);
813 break;
815 case 369: /* WHOWAS end */
816 case 406: /* WHOWAS error */
817 EMIT_SIGNAL (XP_TE_SERVTEXT, whois_sess, text, word[1], word[2], NULL, 0);
818 serv->inside_whois = 0;
819 break;
821 case 372: /* motd text */
822 case 375: /* motd start */
823 if (!prefs.skipmotd || serv->motd_skipped)
824 EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, NULL,
825 NULL, 0);
826 break;
828 case 376: /* end of motd */
829 case 422: /* motd file is missing */
830 inbound_login_end (sess, text);
831 break;
833 case 433: /* nickname in use */
834 case 432: /* erroneous nickname */
835 if (serv->end_of_motd)
836 goto def;
837 inbound_next_nick (sess, word[4]);
838 break;
840 case 437:
841 if (serv->end_of_motd || is_channel (serv, word[4]))
842 goto def;
843 inbound_next_nick (sess, word[4]);
844 break;
846 case 471:
847 EMIT_SIGNAL (XP_TE_USERLIMIT, sess, word[4], NULL, NULL, NULL, 0);
848 break;
850 case 473:
851 EMIT_SIGNAL (XP_TE_INVITE, sess, word[4], NULL, NULL, NULL, 0);
852 break;
854 case 474:
855 EMIT_SIGNAL (XP_TE_BANNED, sess, word[4], NULL, NULL, NULL, 0);
856 break;
858 case 475:
859 EMIT_SIGNAL (XP_TE_KEYWORD, sess, word[4], NULL, NULL, NULL, 0);
860 break;
862 case 601:
863 notify_set_offline (serv, word[4], FALSE);
864 break;
866 case 605:
867 notify_set_offline (serv, word[4], TRUE);
868 break;
870 case 600:
871 case 604:
872 notify_set_online (serv, word[4]);
873 break;
875 case 730: /* RPL_MONONLINE */
876 ex = strchr (word[4], '!'); /* only send the nick */
877 if (ex)
878 ex[0] = 0;
879 notify_set_online (serv, word[4] + 1);
880 break;
882 case 731: /* RPL_MONOFFLINE */
883 ex = strchr (word[4], '!'); /* only send the nick */
884 if (ex)
885 ex[0] = 0;
886 notify_set_offline (serv, word[4] + 1, FALSE);
887 break;
889 case 900: /* successful SASL 'logged in as ' */
890 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, word_eol[6]+1, word[1], word[2], NULL, 0);
891 break;
892 case 903: /* successful SASL auth */
893 case 904: /* aborted SASL auth */
894 case 905: /* failed SASL auth */
895 case 906: /* registration completes before SASL auth */
896 case 907: /* attempting to re-auth after a successful auth */
897 EMIT_SIGNAL (XP_TE_SASLRESPONSE, serv->server_session, word[1], word[2], word[3], ++word_eol[4], 0);
898 tcp_send_len (serv, "CAP END\r\n", 9);
899 break;
901 default:
903 if (serv->inside_whois && word[4][0])
905 /* some unknown WHOIS reply, ircd coders make them up weekly */
906 if (!serv->skip_next_whois)
907 EMIT_SIGNAL (XP_TE_WHOIS_SPECIAL, whois_sess, word[4],
908 (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5],
909 word[2], NULL, 0);
910 return;
913 def:
914 if (is_channel (serv, word[4]))
916 session *realsess = find_channel (serv, word[4]);
917 if (!realsess)
918 realsess = serv->server_session;
919 EMIT_SIGNAL (XP_TE_SERVTEXT, realsess, text, word[1], word[2], NULL, 0);
920 } else
922 EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
923 word[2], NULL, 0);
928 /* handle named messages that starts with a ':' */
930 static void
931 process_named_msg (session *sess, char *type, char *word[], char *word_eol[])
933 server *serv = sess->server;
934 char ip[128], nick[NICKLEN];
935 char *text, *ex;
936 int len = strlen (type);
938 /* fill in the "ip" and "nick" buffers */
939 ex = strchr (word[1], '!');
940 if (!ex) /* no '!', must be a server message */
942 safe_strcpy (ip, word[1], sizeof (ip));
943 safe_strcpy (nick, word[1], sizeof (nick));
944 } else
946 safe_strcpy (ip, ex + 1, sizeof (ip));
947 ex[0] = 0;
948 safe_strcpy (nick, word[1], sizeof (nick));
949 ex[0] = '!';
952 if (len == 4)
954 guint32 t;
956 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
957 /* this should compile to a bunch of: CMP.L, JE ... nice & fast */
958 switch (t)
961 case WORDL('A','C','C','O'):
962 inbound_account (serv, nick, word[3]);
963 return;
965 case WORDL('J','O','I','N'):
967 char *chan = word[3];
968 char *account = word[4];
969 char *realname = word_eol[5] + 1;
971 if (*chan == ':')
972 chan++;
973 if (!serv->p_cmp (nick, serv->nick))
974 inbound_ujoin (serv, chan, nick, ip);
975 else
976 inbound_join (serv, chan, nick, ip, account, realname);
978 return;
980 case WORDL('K','I','C','K'):
982 char *kicked = word[4];
983 char *reason = word_eol[5];
984 if (*kicked)
986 if (*reason == ':')
987 reason++;
988 if (!strcmp (kicked, serv->nick))
989 inbound_ukick (serv, word[3], nick, reason);
990 else
991 inbound_kick (serv, word[3], kicked, nick, reason);
994 return;
996 case WORDL('K','I','L','L'):
997 EMIT_SIGNAL (XP_TE_KILL, sess, nick, word_eol[5], NULL, NULL, 0);
998 return;
1000 case WORDL('M','O','D','E'):
1001 handle_mode (serv, word, word_eol, nick, FALSE); /* modes.c */
1002 return;
1004 case WORDL('N','I','C','K'):
1005 inbound_newnick (serv, nick, (word_eol[3][0] == ':')
1006 ? word_eol[3] + 1 : word_eol[3], FALSE);
1007 return;
1009 case WORDL('P','A','R','T'):
1011 char *chan = word[3];
1012 char *reason = word_eol[4];
1014 if (*chan == ':')
1015 chan++;
1016 if (*reason == ':')
1017 reason++;
1018 if (!strcmp (nick, serv->nick))
1019 inbound_upart (serv, chan, ip, reason);
1020 else
1021 inbound_part (serv, chan, nick, ip, reason);
1023 return;
1025 case WORDL('P','O','N','G'):
1026 inbound_ping_reply (serv->server_session,
1027 (word[4][0] == ':') ? word[4] + 1 : word[4], word[3]);
1028 return;
1030 case WORDL('Q','U','I','T'):
1031 inbound_quit (serv, nick, ip,
1032 (word_eol[3][0] == ':') ? word_eol[3] + 1 : word_eol[3]);
1033 return;
1035 case WORDL('A','W','A','Y'):
1036 inbound_away_notify (serv, nick,
1037 (word_eol[3][0] == ':') ? word_eol[3] + 1 : NULL);
1038 return;
1041 goto garbage;
1044 else if(len == 3) {
1045 guint32 t;
1046 guint32 want_cap; /* format the CAP REQ string based on previous capabilities being requested or not */
1047 guint32 want_sasl; /* CAP END shouldn't be sent when SASL is requested, it needs further responses */
1048 char *pass; /* buffer for SASL password */
1049 char buffer[256]; /* buffer for requesting capabilities and emitting the signal */
1051 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
1052 switch (t)
1054 case WORDL('C','A','P','\0'):
1055 if (strncasecmp (word[4], "ACK", 3) == 0)
1057 EMIT_SIGNAL (XP_TE_CAPACK, sess->server->server_session, word[1], word[5][0]==':' ? ++word_eol[5] : word_eol[5], NULL, NULL, 0);
1059 if (strstr (word_eol[5], "identify-msg") != 0)
1061 serv->have_idmsg = TRUE;
1064 if (strstr (word_eol[5], "multi-prefix") != 0)
1066 serv->have_namesx = TRUE;
1069 if (strstr (word_eol[5], "away-notify") != 0)
1071 serv->have_awaynotify = TRUE;
1074 if (strstr (word_eol[5], "account-notify") != 0)
1076 serv->have_accnotify = TRUE;
1079 if (strstr (word_eol[5], "extended-join") != 0)
1081 serv->have_extjoin = TRUE;
1084 if (strstr (word_eol[5], "sasl") != 0)
1086 serv->have_sasl = TRUE;
1087 EMIT_SIGNAL (XP_TE_SASLAUTH, serv->server_session, sess->server->sasluser, NULL, NULL, NULL, 0);
1088 tcp_send_len (serv, "AUTHENTICATE PLAIN\r\n", 20);
1090 pass = encode_sasl_pass (sess->server->sasluser, sess->server->password);
1091 tcp_sendf (sess->server, "AUTHENTICATE %s\r\n", pass);
1092 free (pass);
1095 else if (strncasecmp (word[4], "LS", 2) == 0)
1097 EMIT_SIGNAL (XP_TE_CAPLIST, serv->server_session, word[1], word[5][0]==':' ? ++word_eol[5] : word_eol[5], NULL, NULL, 0);
1098 want_cap = 0;
1099 want_sasl = 0;
1101 strcpy (buffer, "CAP REQ :");
1103 if (strstr (word_eol[5], "identify-msg") != 0)
1105 strcat (buffer, "identify-msg ");
1106 want_cap = 1;
1108 if (strstr (word_eol[5], "multi-prefix") != 0)
1110 strcat (buffer, "multi-prefix ");
1111 want_cap = 1;
1113 if (strstr (word_eol[5], "away-notify") != 0)
1115 strcat (buffer, "away-notify ");
1116 want_cap = 1;
1118 if (strstr (word_eol[5], "account-notify") != 0)
1120 strcat (buffer, "account-notify ");
1121 want_cap = 1;
1123 if (strstr (word_eol[5], "extended-join") != 0)
1125 strcat (buffer, "extended-join ");
1126 want_cap = 1;
1128 /* if the SASL password is set AND auth mode is set to SASL, request SASL auth */
1129 if (strstr (word_eol[5], "sasl") != 0 && strlen (sess->server->password) != 0 && serv->loginmethod == 6)
1131 strcat (buffer, "sasl ");
1132 want_cap = 1;
1133 want_sasl = 1;
1136 if (want_cap)
1138 /* buffer + 9 = emit buffer without "CAP REQ :" */
1139 EMIT_SIGNAL (XP_TE_CAPREQ, sess->server->server_session, buffer + 9, NULL, NULL, NULL, 0);
1140 tcp_sendf (serv, "%s\r\n", buffer);
1142 if (!want_sasl)
1144 /* if we use SASL, CAP END is dealt via raw numerics */
1145 tcp_send_len (serv, "CAP END\r\n", 9);
1148 else if (strncasecmp (word[4], "NAK", 3) == 0)
1150 tcp_send_len (serv, "CAP END\r\n", 9);
1153 return;
1158 else if (len >= 5)
1160 guint32 t;
1162 t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
1163 /* this should compile to a bunch of: CMP.L, JE ... nice & fast */
1164 switch (t)
1166 case WORDL('I','N','V','I'):
1167 if (ignore_check (word[1], IG_INVI))
1168 return;
1170 if (word[4][0] == ':')
1171 EMIT_SIGNAL (XP_TE_INVITED, sess, word[4] + 1, nick,
1172 serv->servername, NULL, 0);
1173 else
1174 EMIT_SIGNAL (XP_TE_INVITED, sess, word[4], nick,
1175 serv->servername, NULL, 0);
1177 return;
1179 case WORDL('N','O','T','I'):
1181 int id = FALSE; /* identified */
1183 text = word_eol[4];
1184 if (*text == ':')
1185 text++;
1187 if (serv->have_idmsg)
1189 if (*text == '+')
1191 id = TRUE;
1192 text++;
1193 } else if (*text == '-')
1194 text++;
1197 if (!ignore_check (word[1], IG_NOTI))
1198 inbound_notice (serv, word[3], nick, text, ip, id);
1200 return;
1202 case WORDL('P','R','I','V'):
1204 char *to = word[3];
1205 int len;
1206 int id = FALSE; /* identified */
1207 if (*to)
1209 text = word_eol[4];
1210 if (*text == ':')
1211 text++;
1212 if (serv->have_idmsg)
1214 if (*text == '+')
1216 id = TRUE;
1217 text++;
1218 } else if (*text == '-')
1219 text++;
1221 len = strlen (text);
1222 if (text[0] == 1 && text[len - 1] == 1) /* ctcp */
1224 text[len - 1] = 0;
1225 text++;
1226 if (strncasecmp (text, "ACTION", 6) != 0)
1227 flood_check (nick, ip, serv, sess, 0);
1228 if (strncasecmp (text, "DCC ", 4) == 0)
1229 /* redo this with handle_quotes TRUE */
1230 process_data_init (word[1], word_eol[1], word, word_eol, TRUE, FALSE);
1231 ctcp_handle (sess, to, nick, ip, text, word, word_eol, id);
1232 } else
1234 if (is_channel (serv, to))
1236 if (ignore_check (word[1], IG_CHAN))
1237 return;
1238 inbound_chanmsg (serv, NULL, to, nick, text, FALSE, id);
1239 } else
1241 if (ignore_check (word[1], IG_PRIV))
1242 return;
1243 inbound_privmsg (serv, nick, ip, text, id);
1248 return;
1250 case WORDL('T','O','P','I'):
1251 inbound_topicnew (serv, nick, word[3],
1252 (word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4]);
1253 return;
1255 case WORDL('W','A','L','L'):
1256 text = word_eol[3];
1257 if (*text == ':')
1258 text++;
1259 EMIT_SIGNAL (XP_TE_WALLOPS, sess, nick, text, NULL, NULL, 0);
1260 return;
1264 garbage:
1265 /* unknown message */
1266 PrintTextf (sess, "GARBAGE: %s\n", word_eol[1]);
1269 /* handle named messages that DON'T start with a ':' */
1271 static void
1272 process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol[])
1274 sess = sess->server->server_session;
1276 if (!strncmp (buf, "PING ", 5))
1278 tcp_sendf (sess->server, "PONG %s\r\n", buf + 5);
1279 return;
1281 if (!strncmp (buf, "ERROR", 5))
1283 EMIT_SIGNAL (XP_TE_SERVERERROR, sess, buf + 7, NULL, NULL, NULL, 0);
1284 return;
1286 if (!strncmp (buf, "NOTICE ", 7))
1288 buf = word_eol[3];
1289 if (*buf == ':')
1290 buf++;
1291 EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, buf, sess->server->servername, NULL, NULL, 0);
1292 return;
1294 if (!strncmp (buf, "AUTHENTICATE +", 14)) /* omit SASL "empty" responses */
1296 return;
1299 EMIT_SIGNAL (XP_TE_SERVTEXT, sess, buf, sess->server->servername, rawname, NULL, 0);
1302 /* irc_inline() - 1 single line received from serv */
1304 static void
1305 irc_inline (server *serv, char *buf, int len)
1307 session *sess, *tmp;
1308 char *type, *text;
1309 char *word[PDIWORDS+1];
1310 char *word_eol[PDIWORDS+1];
1311 char pdibuf_static[522]; /* 1 line can potentially be 512*6 in utf8 */
1312 char *pdibuf = pdibuf_static;
1314 url_check_line (buf, len);
1316 /* need more than 522? fall back to malloc */
1317 if (len >= sizeof (pdibuf_static))
1318 pdibuf = malloc (len + 1);
1320 sess = serv->front_session;
1322 /* Python relies on this */
1323 word[PDIWORDS] = NULL;
1324 word_eol[PDIWORDS] = NULL;
1326 if (buf[0] == ':')
1328 /* split line into words and words_to_end_of_line */
1329 process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
1331 /* find a context for this message */
1332 if (is_channel (serv, word[3]))
1334 tmp = find_channel (serv, word[3]);
1335 if (tmp)
1336 sess = tmp;
1339 /* for server messages, the 2nd word is the "message type" */
1340 type = word[2];
1342 word[0] = type;
1343 word_eol[1] = buf; /* keep the ":" for plugins */
1344 if (plugin_emit_server (sess, type, word, word_eol))
1345 goto xit;
1346 word[1]++;
1347 word_eol[1] = buf + 1; /* but not for xchat internally */
1349 } else
1351 process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
1352 word[0] = type = word[1];
1353 if (plugin_emit_server (sess, type, word, word_eol))
1354 goto xit;
1357 if (buf[0] != ':')
1359 process_named_servermsg (sess, buf, word[0], word_eol);
1360 goto xit;
1363 /* see if the second word is a numeric */
1364 if (isdigit ((unsigned char) word[2][0]))
1366 text = word_eol[4];
1367 if (*text == ':')
1368 text++;
1370 process_numeric (sess, atoi (word[2]), word, word_eol, text);
1371 } else
1373 process_named_msg (sess, type, word, word_eol);
1376 xit:
1377 if (pdibuf != pdibuf_static)
1378 free (pdibuf);
1381 void
1382 proto_fill_her_up (server *serv)
1384 serv->p_inline = irc_inline;
1385 serv->p_invite = irc_invite;
1386 serv->p_cycle = irc_cycle;
1387 serv->p_ctcp = irc_ctcp;
1388 serv->p_nctcp = irc_nctcp;
1389 serv->p_quit = irc_quit;
1390 serv->p_kick = irc_kick;
1391 serv->p_part = irc_part;
1392 serv->p_ns_identify = irc_ns_identify;
1393 serv->p_ns_ghost = irc_ns_ghost;
1394 serv->p_join = irc_join;
1395 serv->p_join_list = irc_join_list;
1396 serv->p_login = irc_login;
1397 serv->p_join_info = irc_join_info;
1398 serv->p_mode = irc_mode;
1399 serv->p_user_list = irc_user_list;
1400 serv->p_away_status = irc_away_status;
1401 /*serv->p_get_ip = irc_get_ip;*/
1402 serv->p_whois = irc_user_whois;
1403 serv->p_get_ip = irc_user_list;
1404 serv->p_get_ip_uh = irc_userhost;
1405 serv->p_set_back = irc_set_back;
1406 serv->p_set_away = irc_set_away;
1407 serv->p_message = irc_message;
1408 serv->p_action = irc_action;
1409 serv->p_notice = irc_notice;
1410 serv->p_topic = irc_topic;
1411 serv->p_list_channels = irc_list_channels;
1412 serv->p_change_nick = irc_change_nick;
1413 serv->p_names = irc_names;
1414 serv->p_ping = irc_ping;
1415 serv->p_raw = irc_raw;
1416 serv->p_cmp = rfc_casecmp; /* can be changed by 005 in modes.c */