/* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ /* IRC RFC1459(+commonly used extensions) protocol implementation */ #include #include #include #include #include #ifndef WIN32 #include #endif #include "hexchat.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 "hexchatc.h" #include "url.h" static void irc_login (server *serv, char *user, char *realname) { tcp_sendf (serv, "CAP LS\r\n"); /* start with CAP LS as Charybdis sasl.txt suggests */ 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\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) { if (serv->have_whox) tcp_sendf (serv, "WHO %s %%chtsunfra,152\r\n", channel); else 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 %%chtsunfra,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: [] [,[,...]] */ 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.hex_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.hex_dcc_ip_from_server) inbound_foundip (sess, strrchr(word[10], '@')+1); } /* use /NICKSERV */ if (g_ascii_strcasecmp (word[7], "DALnet") == 0 || g_ascii_strcasecmp (word[7], "BRASnet") == 0) serv->nickservtype = 1; /* use /NS */ else if (g_ascii_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[4] + 1, FE_MSG_ERROR); } 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, 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], NULL, 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], word[10], 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 and irc_user_list sends out a "152" */ if (!strcmp (word[4], "152")) { who_sess = find_channel (serv, word[5]); if (*word[10] == 'G') away = 1; /* :server 354 yournick 152 #channel ~ident host servname nick H account :realname */ inbound_user_info (sess, word[5], word[6], word[7], word[8], word[9], word_eol[12]+1, word[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); } 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 346: /* +I-list entry */ if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 346)) goto def; break; case 347: /* end of invite list */ if (!fe_ban_list_end (sess, 347)) goto def; break; case 348: /* +e-list entry */ if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 348)) 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_ban_list_end (sess, 349)) goto def; 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 */ if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 367)) goto def; break; case 368: sess = find_channel (serv, word[4]); if (!sess) { sess = serv->front_session; goto def; } if (!fe_ban_list_end (sess, 368)) goto def; 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.hex_irc_skip_motd || 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; case 728: /* +q-list entry */ /* NOTE: FREENODE returns these results inconsistent with e.g. +b */ /* Who else has imlemented MODE_QUIET, I wonder? */ if (!inbound_banlist (sess, atol (word[8]), word[4], word[6], word[7], 728)) goto def; break; case 729: /* end of quiet list */ if (!fe_ban_list_end (sess, 729)) goto def; break; case 903: /* successful SASL auth */ case 904: /* aborted SASL auth */ case 905: /* failed SASL auth */ case 906: /* registration completes before SASL auth */ case 907: /* attempting to re-auth after a successful auth */ EMIT_SIGNAL (XP_TE_SASLRESPONSE, serv->server_session, word[1], word[2], word[3], ++word_eol[4], 0); tcp_send_len (serv, "CAP END\r\n", 9); 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]; char *account = word[4]; char *realname = word_eol[5] + 1; if (*chan == ':') chan++; if (!serv->p_cmp (nick, serv->nick)) inbound_ujoin (serv, chan, nick, ip); else inbound_join (serv, chan, nick, ip, account, realname); } 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; case WORDL('A','W','A','Y'): inbound_away_notify (serv, nick, (word_eol[3][0] == ':') ? word_eol[3] + 1 : NULL); 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('A','C','C','O'): inbound_account (serv, nick, word[3]); return; 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 (g_ascii_strncasecmp (text, "ACTION", 6) != 0) flood_check (nick, ip, serv, sess, 0); if (g_ascii_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; } } else if (len == 3) { guint32 t; guint32 want_cap; /* format the CAP REQ string based on previous capabilities being requested or not */ guint32 want_sasl; /* CAP END shouldn't be sent when SASL is requested, it needs further responses */ char *pass; /* buffer for SASL password */ char buffer[256]; /* buffer for requesting capabilities and emitting the signal */ t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]); switch (t) { case WORDL('C','A','P','\0'): if (strncasecmp (word[4], "ACK", 3) == 0) { EMIT_SIGNAL (XP_TE_CAPACK, sess->server->server_session, word[1], word[5][0]==':' ? ++word_eol[5] : word_eol[5], NULL, NULL, 0); if (strstr (word_eol[5], "identify-msg") != 0) { serv->have_idmsg = TRUE; } if (strstr (word_eol[5], "multi-prefix") != 0) { serv->have_namesx = TRUE; } if (strstr (word_eol[5], "away-notify") != 0) { serv->have_awaynotify = TRUE; } if (strstr (word_eol[5], "account-notify") != 0) { serv->have_accnotify = TRUE; } if (strstr (word_eol[5], "extended-join") != 0) { serv->have_extjoin = TRUE; } if (strstr (word_eol[5], "sasl") != 0) { serv->have_sasl = TRUE; EMIT_SIGNAL (XP_TE_SASLAUTH, serv->server_session, sess->server->sasluser, NULL, NULL, NULL, 0); tcp_send_len (serv, "AUTHENTICATE PLAIN\r\n", 20); pass = encode_sasl_pass (sess->server->sasluser, sess->server->saslpassword); tcp_sendf (sess->server, "AUTHENTICATE %s\r\n", pass); free (pass); } } else if (strncasecmp (word[4], "LS", 2) == 0) { EMIT_SIGNAL (XP_TE_CAPLIST, serv->server_session, word[1], word[5][0]==':' ? ++word_eol[5] : word_eol[5], NULL, NULL, 0); want_cap = 0; want_sasl = 0; if (strstr (word_eol[5], "identify-msg") != 0) { strcpy (buffer, "CAP REQ :identify-msg"); want_cap = 1; } if (strstr (word_eol[5], "multi-prefix") != 0) { want_cap ? strcat (buffer, " multi-prefix") : strcpy (buffer, "CAP REQ :multi-prefix"); want_cap = 1; } if (strstr (word_eol[5], "away-notify") != 0) { want_cap ? strcat (buffer, " away-notify") : strcpy (buffer, "CAP REQ :away-notify"); want_cap = 1; } if (strstr (word_eol[5], "account-notify") != 0) { want_cap ? strcat (buffer, " account-notify") : strcpy (buffer, "CAP REQ :account-notify"); want_cap = 1; } if (strstr (word_eol[5], "extended-join") != 0) { want_cap ? strcat (buffer, " extended-join") : strcpy (buffer, "CAP REQ :extended-join"); want_cap = 1; } /* if the SASL password is set, request SASL auth */ if (strstr (word_eol[5], "sasl") != 0 && strlen (sess->server->saslpassword) != 0) { want_cap ? strcat (buffer, " sasl") : strcpy (buffer, "CAP REQ :sasl"); want_sasl = 1; } if (want_cap) { /* buffer + 9 = emit buffer without "CAP REQ :" */ EMIT_SIGNAL (XP_TE_CAPREQ, sess->server->server_session, buffer + 9, NULL, NULL, NULL, 0); tcp_sendf (serv, "%s\r\n", buffer); } if (!want_sasl) { /* if we use SASL, CAP END is dealt via raw numerics */ tcp_send_len (serv, "CAP END\r\n", 9); } } else if (strncasecmp (word[4], "NAK", 3) == 0) { tcp_send_len (serv, "CAP END\r\n", 9); } 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; } if (!strncmp (buf, "AUTHENTICATE +", 14)) /* omit SASL "empty" responses */ { 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; url_check_line (buf, len); /* 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 HexChat 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 */ }