diff options
Diffstat (limited to 'src/common/proto-irc.c')
-rw-r--r-- | src/common/proto-irc.c | 1260 |
1 files changed, 1260 insertions, 0 deletions
diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c new file mode 100644 index 00000000..f1df6ccd --- /dev/null +++ b/src/common/proto-irc.c @@ -0,0 +1,1260 @@ +/* X-Chat + * Copyright (C) 2002 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* IRC RFC1459(+commonly used extensions) protocol implementation */ + +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdarg.h> + +#include "xchat.h" +#include "ctcp.h" +#include "fe.h" +#include "ignore.h" +#include "inbound.h" +#include "modes.h" +#include "notify.h" +#include "plugin.h" +#include "server.h" +#include "text.h" +#include "outbound.h" +#include "util.h" +#include "xchatc.h" + + +static void +irc_login (server *serv, char *user, char *realname) +{ + if (serv->password[0]) + tcp_sendf (serv, "PASS %s\r\n", serv->password); + + tcp_sendf (serv, + "NICK %s\r\n" + "USER %s %s %s :%s\r\n", + serv->nick, user, user, serv->servername, realname); +} + +static void +irc_nickserv (server *serv, char *cmd, char *arg1, char *arg2, char *arg3) +{ + /* are all ircd authors idiots? */ + switch (serv->nickservtype) + { + case 0: + tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 1: + tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 2: + tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 3: + tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3); + break; + case 4: + /* why couldn't QuakeNet implement one of the existing ones? */ + tcp_sendf (serv, "AUTH %s%s%s\r\n", cmd, arg1, arg2, arg3); + } +} + +static void +irc_ns_identify (server *serv, char *pass) +{ + irc_nickserv (serv, "IDENTIFY", pass, "", ""); +} + +static void +irc_ns_ghost (server *serv, char *usname, char *pass) +{ + if (serv->nickservtype != 4) + irc_nickserv (serv, "GHOST", usname, " ", pass); +} + +static void +irc_join (server *serv, char *channel, char *key) +{ + if (key[0]) + tcp_sendf (serv, "JOIN %s %s\r\n", channel, key); + else + tcp_sendf (serv, "JOIN %s\r\n", channel); +} + +static void +irc_join_list_flush (server *serv, GString *c, GString *k) +{ + char *chanstr, *keystr; + + chanstr = g_string_free (c, FALSE); + keystr = g_string_free (k, FALSE); + if (chanstr[0]) + { + if (keystr[0]) + tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr); + else + tcp_sendf (serv, "JOIN %s\r\n", chanstr); + } + g_free (chanstr); + g_free (keystr); +} + +/* join a whole list of channels & keys, split to multiple lines + * to get around 512 limit */ + +static void +irc_join_list (server *serv, GSList *channels, GSList *keys) +{ + GSList *clist; + GSList *klist; + GString *c = g_string_new (NULL); + GString *k = g_string_new (NULL); + int len; + int add; + int i, j; + + i = j = 0; + len = 9; /* "JOIN<space><space>\r\n" */ + clist = channels; + klist = keys; + + while (clist) + { + /* measure how many bytes this channel would add... */ + if (1) + { + add = strlen (clist->data); + if (i != 0) + add++; /* comma */ + } + + if (klist->data) + { + add += strlen (klist->data); + } + else + { + add++; /* 'x' filler */ + } + + if (j != 0) + add++; /* comma */ + + /* too big? dump buffer and start a fresh one */ + if (len + add > 512) + { + irc_join_list_flush (serv, c, k); + + c = g_string_new (NULL); + k = g_string_new (NULL); + i = j = 0; + len = 9; + } + + /* now actually add it to our GStrings */ + if (1) + { + add = strlen (clist->data); + if (i != 0) + { + add++; + g_string_append_c (c, ','); + } + g_string_append (c, clist->data); + i++; + } + + if (klist->data) + { + add += strlen (klist->data); + if (j != 0) + { + add++; + g_string_append_c (k, ','); + } + g_string_append (k, klist->data); + j++; + } + else + { + add++; + if (j != 0) + { + add++; + g_string_append_c (k, ','); + } + g_string_append_c (k, 'x'); + j++; + } + + len += add; + + klist = klist->next; + clist = clist->next; + } + + irc_join_list_flush (serv, c, k); +} + +static void +irc_part (server *serv, char *channel, char *reason) +{ + if (reason[0]) + tcp_sendf (serv, "PART %s :%s\r\n", channel, reason); + else + tcp_sendf (serv, "PART %s\r\n", channel); +} + +static void +irc_quit (server *serv, char *reason) +{ + if (reason[0]) + tcp_sendf (serv, "QUIT :%s\r\n", reason); + else + tcp_send_len (serv, "QUIT\r\n", 6); +} + +static void +irc_set_back (server *serv) +{ + tcp_send_len (serv, "AWAY\r\n", 6); +} + +static void +irc_set_away (server *serv, char *reason) +{ + if (reason) + { + if (!reason[0]) + reason = " "; + } + else + { + reason = " "; + } + + tcp_sendf (serv, "AWAY :%s\r\n", reason); +} + +static void +irc_ctcp (server *serv, char *to, char *msg) +{ + tcp_sendf (serv, "PRIVMSG %s :\001%s\001\r\n", to, msg); +} + +static void +irc_nctcp (server *serv, char *to, char *msg) +{ + tcp_sendf (serv, "NOTICE %s :\001%s\001\r\n", to, msg); +} + +static void +irc_cycle (server *serv, char *channel, char *key) +{ + tcp_sendf (serv, "PART %s\r\nJOIN %s %s\r\n", channel, channel, key); +} + +static void +irc_kick (server *serv, char *channel, char *nick, char *reason) +{ + if (reason[0]) + tcp_sendf (serv, "KICK %s %s :%s\r\n", channel, nick, reason); + else + tcp_sendf (serv, "KICK %s %s\r\n", channel, nick); +} + +static void +irc_invite (server *serv, char *channel, char *nick) +{ + tcp_sendf (serv, "INVITE %s %s\r\n", nick, channel); +} + +static void +irc_mode (server *serv, char *target, char *mode) +{ + tcp_sendf (serv, "MODE %s %s\r\n", target, mode); +} + +/* find channel info when joined */ + +static void +irc_join_info (server *serv, char *channel) +{ + tcp_sendf (serv, "MODE %s\r\n", channel); +} + +/* initiate userlist retreival */ + +static void +irc_user_list (server *serv, char *channel) +{ + tcp_sendf (serv, "WHO %s\r\n", channel); +} + +/* userhost */ + +static void +irc_userhost (server *serv, char *nick) +{ + tcp_sendf (serv, "USERHOST %s\r\n", nick); +} + +static void +irc_away_status (server *serv, char *channel) +{ + if (serv->have_whox) + tcp_sendf (serv, "WHO %s %%ctnf,152\r\n", channel); + else + tcp_sendf (serv, "WHO %s\r\n", channel); +} + +/*static void +irc_get_ip (server *serv, char *nick) +{ + tcp_sendf (serv, "WHO %s\r\n", nick); +}*/ + + +/* + * Command: WHOIS + * Parameters: [<server>] <nickmask>[,<nickmask>[,...]] + */ +static void +irc_user_whois (server *serv, char *nicks) +{ + tcp_sendf (serv, "WHOIS %s\r\n", nicks); +} + +static void +irc_message (server *serv, char *channel, char *text) +{ + tcp_sendf (serv, "PRIVMSG %s :%s\r\n", channel, text); +} + +static void +irc_action (server *serv, char *channel, char *act) +{ + tcp_sendf (serv, "PRIVMSG %s :\001ACTION %s\001\r\n", channel, act); +} + +static void +irc_notice (server *serv, char *channel, char *text) +{ + tcp_sendf (serv, "NOTICE %s :%s\r\n", channel, text); +} + +static void +irc_topic (server *serv, char *channel, char *topic) +{ + if (!topic) + tcp_sendf (serv, "TOPIC %s :\r\n", channel); + else if (topic[0]) + tcp_sendf (serv, "TOPIC %s :%s\r\n", channel, topic); + else + tcp_sendf (serv, "TOPIC %s\r\n", channel); +} + +static void +irc_list_channels (server *serv, char *arg, int min_users) +{ + if (arg[0]) + { + tcp_sendf (serv, "LIST %s\r\n", arg); + return; + } + + if (serv->use_listargs) + tcp_sendf (serv, "LIST >%d,<10000\r\n", min_users - 1); + else + tcp_send_len (serv, "LIST\r\n", 6); +} + +static void +irc_names (server *serv, char *channel) +{ + tcp_sendf (serv, "NAMES %s\r\n", channel); +} + +static void +irc_change_nick (server *serv, char *new_nick) +{ + tcp_sendf (serv, "NICK %s\r\n", new_nick); +} + +static void +irc_ping (server *serv, char *to, char *timestring) +{ + if (*to) + tcp_sendf (serv, "PRIVMSG %s :\001PING %s\001\r\n", to, timestring); + else + tcp_sendf (serv, "PING %s\r\n", timestring); +} + +static int +irc_raw (server *serv, char *raw) +{ + int len; + char tbuf[4096]; + if (*raw) + { + len = strlen (raw); + if (len < sizeof (tbuf) - 3) + { + len = snprintf (tbuf, sizeof (tbuf), "%s\r\n", raw); + tcp_send_len (serv, tbuf, len); + } else + { + tcp_send_len (serv, raw, len); + tcp_send_len (serv, "\r\n", 2); + } + return TRUE; + } + return FALSE; +} + +/* ============================================================== */ +/* ======================= IRC INPUT ============================ */ +/* ============================================================== */ + + +static void +channel_date (session *sess, char *chan, char *timestr) +{ + time_t timestamp = (time_t) atol (timestr); + char *tim = ctime (×tamp); + tim[24] = 0; /* get rid of the \n */ + EMIT_SIGNAL (XP_TE_CHANDATE, sess, chan, tim, NULL, NULL, 0); +} + +static void +process_numeric (session * sess, int n, + char *word[], char *word_eol[], char *text) +{ + server *serv = sess->server; + /* show whois is the server tab */ + session *whois_sess = serv->server_session; + + /* unless this setting is on */ + if (prefs.irc_whois_front) + whois_sess = serv->front_session; + + switch (n) + { + case 1: + inbound_login_start (sess, word[3], word[1]); + /* if network is PTnet then you must get your IP address + from "001" server message */ + if ((strncmp(word[7], "PTnet", 5) == 0) && + (strncmp(word[8], "IRC", 3) == 0) && + (strncmp(word[9], "Network", 7) == 0) && + (strrchr(word[10], '@') != NULL)) + { + serv->use_who = FALSE; + if (prefs.ip_from_server) + inbound_foundip (sess, strrchr(word[10], '@')+1); + } + + /* use /NICKSERV */ + if (strcasecmp (word[7], "DALnet") == 0 || + strcasecmp (word[7], "BRASnet") == 0) + serv->nickservtype = 1; + + /* use /NS */ + else if (strcasecmp (word[7], "FreeNode") == 0) + serv->nickservtype = 2; + + goto def; + + case 4: /* check the ircd type */ + serv->use_listargs = FALSE; + serv->modes_per_line = 3; /* default to IRC RFC */ + if (strncmp (word[5], "bahamut", 7) == 0) /* DALNet */ + { + serv->use_listargs = TRUE; /* use the /list args */ + } else if (strncmp (word[5], "u2.10.", 6) == 0) /* Undernet */ + { + serv->use_listargs = TRUE; /* use the /list args */ + serv->modes_per_line = 6; /* allow 6 modes per line */ + } else if (strncmp (word[5], "glx2", 4) == 0) + { + serv->use_listargs = TRUE; /* use the /list args */ + } + goto def; + + case 5: + inbound_005 (serv, word); + goto def; + + case 263: /*Server load is temporarily too heavy */ + if (fe_is_chanwindow (sess->server)) + { + fe_chan_list_end (sess->server); + fe_message (word_eol[5] + 1, FE_MSG_ERROR); + } + goto def; + + case 290: /* CAPAB reply */ + if (strstr (word_eol[1], "IDENTIFY-MSG")) + { + serv->have_idmsg = TRUE; + break; + } + goto def; + + case 301: + inbound_away (serv, word[4], + (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]); + break; + + case 302: + if (serv->skip_next_userhost) + { + char *eq = strchr (word[4], '='); + if (eq) + { + *eq = 0; + if (!serv->p_cmp (word[4] + 1, serv->nick)) + { + char *at = strrchr (eq + 1, '@'); + if (at) + inbound_foundip (sess, at + 1); + } + } + + serv->skip_next_userhost = FALSE; + break; + } + else goto def; + + case 303: + word[4]++; + notify_markonline (serv, word); + break; + + case 305: + inbound_uback (serv); + goto def; + + case 306: + inbound_uaway (serv); + goto def; + + case 312: + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS3, whois_sess, word[4], word_eol[5], NULL, NULL, 0); + else + inbound_user_info (sess, NULL, NULL, NULL, word[5], word[4], NULL, 0xff); + break; + + case 311: /* WHOIS 1st line */ + serv->inside_whois = 1; + inbound_user_info_start (sess, word[4]); + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5], + word[6], word_eol[8] + 1, 0); + else + inbound_user_info (sess, NULL, word[5], word[6], NULL, word[4], + word_eol[8][0] == ':' ? word_eol[8] + 1 : word_eol[8], 0xff); + break; + + case 314: /* WHOWAS */ + inbound_user_info_start (sess, word[4]); + EMIT_SIGNAL (XP_TE_WHOIS1, whois_sess, word[4], word[5], + word[6], word_eol[8] + 1, 0); + break; + + case 317: + if (!serv->skip_next_whois) + { + time_t timestamp = (time_t) atol (word[6]); + long idle = atol (word[5]); + char *tim; + char outbuf[64]; + + snprintf (outbuf, sizeof (outbuf), + "%02ld:%02ld:%02ld", idle / 3600, (idle / 60) % 60, + idle % 60); + if (timestamp == 0) + EMIT_SIGNAL (XP_TE_WHOIS4, whois_sess, word[4], + outbuf, NULL, NULL, 0); + else + { + tim = ctime (×tamp); + tim[19] = 0; /* get rid of the \n */ + EMIT_SIGNAL (XP_TE_WHOIS4T, whois_sess, word[4], + outbuf, tim, NULL, 0); + } + } + break; + + case 318: /* END OF WHOIS */ + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS6, whois_sess, word[4], NULL, + NULL, NULL, 0); + serv->skip_next_whois = 0; + serv->inside_whois = 0; + break; + + case 313: + case 319: + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS2, whois_sess, word[4], + word_eol[5] + 1, NULL, NULL, 0); + break; + + case 307: /* dalnet version */ + case 320: /* :is an identified user */ + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS_ID, whois_sess, word[4], + word_eol[5] + 1, NULL, NULL, 0); + break; + + case 321: + if (!fe_is_chanwindow (sess->server)) + EMIT_SIGNAL (XP_TE_CHANLISTHEAD, serv->server_session, NULL, NULL, NULL, NULL, 0); + break; + + case 322: + if (fe_is_chanwindow (sess->server)) + { + fe_add_chan_list (sess->server, word[4], word[5], word_eol[6] + 1); + } else + { + PrintTextf (serv->server_session, "%-16s %-7d %s\017\n", + word[4], atoi (word[5]), word_eol[6] + 1); + } + break; + + case 323: + if (!fe_is_chanwindow (sess->server)) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], word[2], NULL, 0); + else + fe_chan_list_end (sess->server); + break; + + case 324: + sess = find_channel (serv, word[4]); + if (!sess) + sess = serv->server_session; + if (sess->ignore_mode) + sess->ignore_mode = FALSE; + else + EMIT_SIGNAL (XP_TE_CHANMODES, sess, word[4], word_eol[5], + NULL, NULL, 0); + fe_update_mode_buttons (sess, 't', '-'); + fe_update_mode_buttons (sess, 'n', '-'); + fe_update_mode_buttons (sess, 's', '-'); + fe_update_mode_buttons (sess, 'i', '-'); + fe_update_mode_buttons (sess, 'p', '-'); + fe_update_mode_buttons (sess, 'm', '-'); + fe_update_mode_buttons (sess, 'l', '-'); + fe_update_mode_buttons (sess, 'k', '-'); + handle_mode (serv, word, word_eol, "", TRUE); + break; + + case 329: + sess = find_channel (serv, word[4]); + if (sess) + { + if (sess->ignore_date) + sess->ignore_date = FALSE; + else + channel_date (sess, word[4], word[5]); + } + break; + + case 330: + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS_AUTH, whois_sess, word[4], + word_eol[6] + 1, word[5], NULL, 0); + break; + + case 332: + inbound_topic (serv, word[4], + (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5]); + break; + + case 333: + inbound_topictime (serv, word[4], word[5], atol (word[6])); + break; + +#if 0 + case 338: /* Undernet Real user@host, Real IP */ + EMIT_SIGNAL (XP_TE_WHOIS_REALHOST, sess, word[4], word[5], word[6], + (word_eol[7][0]==':') ? word_eol[7]+1 : word_eol[7], 0); + break; +#endif + + case 341: /* INVITE ACK */ + EMIT_SIGNAL (XP_TE_UINVITE, sess, word[4], word[5], serv->servername, + NULL, 0); + break; + + case 352: /* WHO */ + { + unsigned int away = 0; + session *who_sess = find_channel (serv, word[4]); + + if (*word[9] == 'G') + away = 1; + + inbound_user_info (sess, word[4], word[5], word[6], word[7], + word[8], word_eol[11], away); + + /* try to show only user initiated whos */ + if (!who_sess || !who_sess->doing_who) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], + word[2], NULL, 0); + } + break; + + case 354: /* undernet WHOX: used as a reply for irc_away_status */ + { + unsigned int away = 0; + session *who_sess; + + /* irc_away_status sends out a "152" */ + if (!strcmp (word[4], "152")) + { + who_sess = find_channel (serv, word[5]); + + if (*word[7] == 'G') + away = 1; + + /* :SanJose.CA.us.undernet.org 354 z1 152 #zed1 z1 H@ */ + inbound_user_info (sess, word[5], 0, 0, 0, word[6], 0, away); + + /* try to show only user initiated whos */ + if (!who_sess || !who_sess->doing_who) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, + word[1], word[2], NULL, 0); + } else + goto def; + } + break; + + case 315: /* END OF WHO */ + { + session *who_sess; + who_sess = find_channel (serv, word[4]); + if (who_sess) + { + if (!who_sess->doing_who) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, + word[1], word[2], NULL, 0); + who_sess->doing_who = FALSE; + } else + { + if (!serv->doing_dns) + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, + word[1], word[2], NULL, 0); + serv->doing_dns = FALSE; + } + } + break; + + case 348: /* +e-list entry */ + if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], TRUE)) + goto def; + break; + + case 349: /* end of exemption list */ + sess = find_channel (serv, word[4]); + if (!sess) + { + sess = serv->front_session; + goto def; + } + if (!fe_is_banwindow (sess)) + goto def; + fe_ban_list_end (sess, TRUE); + break; + + case 353: /* NAMES */ + inbound_nameslist (serv, word[5], + (word_eol[6][0] == ':') ? word_eol[6] + 1 : word_eol[6]); + break; + + case 366: + if (!inbound_nameslist_end (serv, word[4])) + goto def; + break; + + case 367: /* banlist entry */ + inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], FALSE); + break; + + case 368: + sess = find_channel (serv, word[4]); + if (!sess) + { + sess = serv->front_session; + goto def; + } + if (!fe_is_banwindow (sess)) + goto def; + fe_ban_list_end (sess, FALSE); + break; + + case 369: /* WHOWAS end */ + case 406: /* WHOWAS error */ + EMIT_SIGNAL (XP_TE_SERVTEXT, whois_sess, text, word[1], word[2], NULL, 0); + serv->inside_whois = 0; + break; + + case 372: /* motd text */ + case 375: /* motd start */ + if (!prefs.skipmotd || serv->motd_skipped) + EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, NULL, + NULL, 0); + break; + + case 376: /* end of motd */ + case 422: /* motd file is missing */ + inbound_login_end (sess, text); + break; + + case 433: /* nickname in use */ + case 432: /* erroneous nickname */ + if (serv->end_of_motd) + goto def; + inbound_next_nick (sess, word[4]); + break; + + case 437: + if (serv->end_of_motd || is_channel (serv, word[4])) + goto def; + inbound_next_nick (sess, word[4]); + break; + + case 471: + EMIT_SIGNAL (XP_TE_USERLIMIT, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 473: + EMIT_SIGNAL (XP_TE_INVITE, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 474: + EMIT_SIGNAL (XP_TE_BANNED, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 475: + EMIT_SIGNAL (XP_TE_KEYWORD, sess, word[4], NULL, NULL, NULL, 0); + break; + + case 601: + notify_set_offline (serv, word[4], FALSE); + break; + + case 605: + notify_set_offline (serv, word[4], TRUE); + break; + + case 600: + case 604: + notify_set_online (serv, word[4]); + break; + + default: + + if (serv->inside_whois && word[4][0]) + { + /* some unknown WHOIS reply, ircd coders make them up weekly */ + if (!serv->skip_next_whois) + EMIT_SIGNAL (XP_TE_WHOIS_SPECIAL, whois_sess, word[4], + (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5], + word[2], NULL, 0); + return; + } + + def: + if (is_channel (serv, word[4])) + { + session *realsess = find_channel (serv, word[4]); + if (!realsess) + realsess = serv->server_session; + EMIT_SIGNAL (XP_TE_SERVTEXT, realsess, text, word[1], word[2], NULL, 0); + } else + { + EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1], + word[2], NULL, 0); + } + } +} + +/* handle named messages that starts with a ':' */ + +static void +process_named_msg (session *sess, char *type, char *word[], char *word_eol[]) +{ + server *serv = sess->server; + char ip[128], nick[NICKLEN]; + char *text, *ex; + int len = strlen (type); + + /* fill in the "ip" and "nick" buffers */ + ex = strchr (word[1], '!'); + if (!ex) /* no '!', must be a server message */ + { + safe_strcpy (ip, word[1], sizeof (ip)); + safe_strcpy (nick, word[1], sizeof (nick)); + } else + { + safe_strcpy (ip, ex + 1, sizeof (ip)); + ex[0] = 0; + safe_strcpy (nick, word[1], sizeof (nick)); + ex[0] = '!'; + } + + if (len == 4) + { + guint32 t; + + t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]); + /* this should compile to a bunch of: CMP.L, JE ... nice & fast */ + switch (t) + { + case WORDL('J','O','I','N'): + { + char *chan = word[3]; + + if (*chan == ':') + chan++; + if (!serv->p_cmp (nick, serv->nick)) + inbound_ujoin (serv, chan, nick, ip); + else + inbound_join (serv, chan, nick, ip); + } + return; + + case WORDL('K','I','C','K'): + { + char *kicked = word[4]; + char *reason = word_eol[5]; + if (*kicked) + { + if (*reason == ':') + reason++; + if (!strcmp (kicked, serv->nick)) + inbound_ukick (serv, word[3], nick, reason); + else + inbound_kick (serv, word[3], kicked, nick, reason); + } + } + return; + + case WORDL('K','I','L','L'): + EMIT_SIGNAL (XP_TE_KILL, sess, nick, word_eol[5], NULL, NULL, 0); + return; + + case WORDL('M','O','D','E'): + handle_mode (serv, word, word_eol, nick, FALSE); /* modes.c */ + return; + + case WORDL('N','I','C','K'): + inbound_newnick (serv, nick, (word_eol[3][0] == ':') + ? word_eol[3] + 1 : word_eol[3], FALSE); + return; + + case WORDL('P','A','R','T'): + { + char *chan = word[3]; + char *reason = word_eol[4]; + + if (*chan == ':') + chan++; + if (*reason == ':') + reason++; + if (!strcmp (nick, serv->nick)) + inbound_upart (serv, chan, ip, reason); + else + inbound_part (serv, chan, nick, ip, reason); + } + return; + + case WORDL('P','O','N','G'): + inbound_ping_reply (serv->server_session, + (word[4][0] == ':') ? word[4] + 1 : word[4], word[3]); + return; + + case WORDL('Q','U','I','T'): + inbound_quit (serv, nick, ip, + (word_eol[3][0] == ':') ? word_eol[3] + 1 : word_eol[3]); + return; + } + + goto garbage; + } + + else if (len >= 5) + { + guint32 t; + + t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]); + /* this should compile to a bunch of: CMP.L, JE ... nice & fast */ + switch (t) + { + case WORDL('I','N','V','I'): + if (ignore_check (word[1], IG_INVI)) + return; + + if (word[4][0] == ':') + EMIT_SIGNAL (XP_TE_INVITED, sess, word[4] + 1, nick, + serv->servername, NULL, 0); + else + EMIT_SIGNAL (XP_TE_INVITED, sess, word[4], nick, + serv->servername, NULL, 0); + + return; + + case WORDL('N','O','T','I'): + { + int id = FALSE; /* identified */ + + text = word_eol[4]; + if (*text == ':') + text++; + + if (serv->have_idmsg) + { + if (*text == '+') + { + id = TRUE; + text++; + } else if (*text == '-') + text++; + } + + if (!ignore_check (word[1], IG_NOTI)) + inbound_notice (serv, word[3], nick, text, ip, id); + } + return; + + case WORDL('P','R','I','V'): + { + char *to = word[3]; + int len; + int id = FALSE; /* identified */ + if (*to) + { + text = word_eol[4]; + if (*text == ':') + text++; + if (serv->have_idmsg) + { + if (*text == '+') + { + id = TRUE; + text++; + } else if (*text == '-') + text++; + } + len = strlen (text); + if (text[0] == 1 && text[len - 1] == 1) /* ctcp */ + { + text[len - 1] = 0; + text++; + if (strncasecmp (text, "ACTION", 6) != 0) + flood_check (nick, ip, serv, sess, 0); + if (strncasecmp (text, "DCC ", 4) == 0) + /* redo this with handle_quotes TRUE */ + process_data_init (word[1], word_eol[1], word, word_eol, TRUE, FALSE); + ctcp_handle (sess, to, nick, ip, text, word, word_eol, id); + } else + { + if (is_channel (serv, to)) + { + if (ignore_check (word[1], IG_CHAN)) + return; + inbound_chanmsg (serv, NULL, to, nick, text, FALSE, id); + } else + { + if (ignore_check (word[1], IG_PRIV)) + return; + inbound_privmsg (serv, nick, ip, text, id); + } + } + } + } + return; + + case WORDL('T','O','P','I'): + inbound_topicnew (serv, nick, word[3], + (word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4]); + return; + + case WORDL('W','A','L','L'): + text = word_eol[3]; + if (*text == ':') + text++; + EMIT_SIGNAL (XP_TE_WALLOPS, sess, nick, text, NULL, NULL, 0); + return; + } + } + +garbage: + /* unknown message */ + PrintTextf (sess, "GARBAGE: %s\n", word_eol[1]); +} + +/* handle named messages that DON'T start with a ':' */ + +static void +process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol[]) +{ + sess = sess->server->server_session; + + if (!strncmp (buf, "PING ", 5)) + { + tcp_sendf (sess->server, "PONG %s\r\n", buf + 5); + return; + } + if (!strncmp (buf, "ERROR", 5)) + { + EMIT_SIGNAL (XP_TE_SERVERERROR, sess, buf + 7, NULL, NULL, NULL, 0); + return; + } + if (!strncmp (buf, "NOTICE ", 7)) + { + buf = word_eol[3]; + if (*buf == ':') + buf++; + EMIT_SIGNAL (XP_TE_SERVNOTICE, sess, buf, sess->server->servername, NULL, NULL, 0); + return; + } + + EMIT_SIGNAL (XP_TE_SERVTEXT, sess, buf, sess->server->servername, rawname, NULL, 0); +} + +/* irc_inline() - 1 single line received from serv */ + +static void +irc_inline (server *serv, char *buf, int len) +{ + session *sess, *tmp; + char *type, *text; + char *word[PDIWORDS+1]; + char *word_eol[PDIWORDS+1]; + char pdibuf_static[522]; /* 1 line can potentially be 512*6 in utf8 */ + char *pdibuf = pdibuf_static; + + /* need more than 522? fall back to malloc */ + if (len >= sizeof (pdibuf_static)) + pdibuf = malloc (len + 1); + + sess = serv->front_session; + + /* Python relies on this */ + word[PDIWORDS] = NULL; + word_eol[PDIWORDS] = NULL; + + if (buf[0] == ':') + { + /* split line into words and words_to_end_of_line */ + process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE); + + /* find a context for this message */ + if (is_channel (serv, word[3])) + { + tmp = find_channel (serv, word[3]); + if (tmp) + sess = tmp; + } + + /* for server messages, the 2nd word is the "message type" */ + type = word[2]; + + word[0] = type; + word_eol[1] = buf; /* keep the ":" for plugins */ + if (plugin_emit_server (sess, type, word, word_eol)) + goto xit; + word[1]++; + word_eol[1] = buf + 1; /* but not for xchat internally */ + + } else + { + process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE); + word[0] = type = word[1]; + if (plugin_emit_server (sess, type, word, word_eol)) + goto xit; + } + + if (buf[0] != ':') + { + process_named_servermsg (sess, buf, word[0], word_eol); + goto xit; + } + + /* see if the second word is a numeric */ + if (isdigit ((unsigned char) word[2][0])) + { + text = word_eol[4]; + if (*text == ':') + text++; + + process_numeric (sess, atoi (word[2]), word, word_eol, text); + } else + { + process_named_msg (sess, type, word, word_eol); + } + +xit: + if (pdibuf != pdibuf_static) + free (pdibuf); +} + +void +proto_fill_her_up (server *serv) +{ + serv->p_inline = irc_inline; + serv->p_invite = irc_invite; + serv->p_cycle = irc_cycle; + serv->p_ctcp = irc_ctcp; + serv->p_nctcp = irc_nctcp; + serv->p_quit = irc_quit; + serv->p_kick = irc_kick; + serv->p_part = irc_part; + serv->p_ns_identify = irc_ns_identify; + serv->p_ns_ghost = irc_ns_ghost; + serv->p_join = irc_join; + serv->p_join_list = irc_join_list; + serv->p_login = irc_login; + serv->p_join_info = irc_join_info; + serv->p_mode = irc_mode; + serv->p_user_list = irc_user_list; + serv->p_away_status = irc_away_status; + /*serv->p_get_ip = irc_get_ip;*/ + serv->p_whois = irc_user_whois; + serv->p_get_ip = irc_user_list; + serv->p_get_ip_uh = irc_userhost; + serv->p_set_back = irc_set_back; + serv->p_set_away = irc_set_away; + serv->p_message = irc_message; + serv->p_action = irc_action; + serv->p_notice = irc_notice; + serv->p_topic = irc_topic; + serv->p_list_channels = irc_list_channels; + serv->p_change_nick = irc_change_nick; + serv->p_names = irc_names; + serv->p_ping = irc_ping; + serv->p_raw = irc_raw; + serv->p_cmp = rfc_casecmp; /* can be changed by 005 in modes.c */ +} |