mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-09-04 05:53:06 +00:00
When you send a private message, ZoiteChat sometimes gets the same message echoed back by the server so it can show it in the right window. Whether it recognizes that echo depended on the server first confirming a feature called "echo-message" during connection setup. WeeChat's relay doesn't seem to confirm that feature the way other servers (or ZNC) do, so ZoiteChat didn't recognize its own echoed messages and treated them as brand new incoming messages from yourself, opening a second, separate conversation window instead of using the one you were already typing in. This makes ZoiteChat recognize its own message by checking who sent it, instead of only trusting that feature confirmation. That way, sent and received messages always end up in the same conversation window, regardless of how well the server/relay announces its capabilities.
1892 lines
46 KiB
C
1892 lines
46 KiB
C
/* 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 <string.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
|
|
#ifndef WIN32
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "zoitechat.h"
|
|
#include "proto-irc.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 "zoitechatc.h"
|
|
#include "url.h"
|
|
#include "servlist.h"
|
|
|
|
static void
|
|
irc_login (server *serv, char *user, char *realname)
|
|
{
|
|
tcp_sendf (serv, "CAP LS 302\r\n"); /* start with CAP LS as Charybdis sasl.txt suggests */
|
|
serv->sent_capend = FALSE; /* track if we have finished */
|
|
|
|
if (serv->password[0] && serv->loginmethod == LOGIN_PASS)
|
|
{
|
|
tcp_sendf (serv, "PASS %s%s\r\n",
|
|
(serv->password[0] == ':' || strchr (serv->password, ' ')) ? ":" : "",
|
|
serv->password);
|
|
}
|
|
|
|
tcp_sendf (serv,
|
|
"NICK %s\r\n"
|
|
"USER %s 0 * :%s\r\n",
|
|
serv->nick, user, realname);
|
|
}
|
|
|
|
static void
|
|
irc_nickserv (server *serv, char *cmd, char *arg1, char *arg2, char *arg3)
|
|
{
|
|
/* are all ircd authors idiots? */
|
|
switch (serv->loginmethod)
|
|
{
|
|
case LOGIN_MSG_NICKSERV:
|
|
tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
|
|
break;
|
|
case LOGIN_NICKSERV:
|
|
tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
|
|
break;
|
|
default: /* This may not work but at least it tries something when using /id or /ghost cmd */
|
|
tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
|
|
break;
|
|
#if 0
|
|
case LOGIN_MSG_NS:
|
|
tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
|
|
break;
|
|
case LOGIN_NS:
|
|
tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
|
|
break;
|
|
case LOGIN_AUTH:
|
|
/* why couldn't QuakeNet implement one of the existing ones? */
|
|
tcp_sendf (serv, "AUTH %s %s\r\n", arg1, arg2);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void
|
|
irc_ns_identify (server *serv, char *pass)
|
|
{
|
|
switch (serv->loginmethod)
|
|
{
|
|
case LOGIN_CHALLENGEAUTH:
|
|
tcp_sendf (serv, "PRIVMSG %s :CHALLENGE\r\n", CHALLENGEAUTH_NICK); /* request a challenge from Q */
|
|
break;
|
|
#if 0
|
|
case LOGIN_AUTH:
|
|
irc_nickserv (serv, "", serv->nick, pass, "");
|
|
break;
|
|
#endif
|
|
default:
|
|
irc_nickserv (serv, "IDENTIFY", pass, "", "");
|
|
}
|
|
}
|
|
|
|
static void
|
|
irc_ns_ghost (server *serv, char *usname, char *pass)
|
|
{
|
|
if (serv->loginmethod != LOGIN_CHALLENGEAUTH)
|
|
{
|
|
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 *channels, GString *keys, int send_keys)
|
|
{
|
|
char *chanstr;
|
|
char *keystr;
|
|
|
|
chanstr = g_string_free (channels, FALSE); /* convert our strings to char arrays */
|
|
keystr = g_string_free (keys, FALSE);
|
|
|
|
if (send_keys)
|
|
{
|
|
tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr); /* send the actual command */
|
|
}
|
|
else
|
|
{
|
|
tcp_sendf (serv, "JOIN %s\r\n", chanstr); /* send the actual command */
|
|
}
|
|
|
|
g_free (chanstr);
|
|
g_free (keystr);
|
|
}
|
|
|
|
/* Join a whole list of channels & keys, split to multiple lines
|
|
* to get around the 512 limit.
|
|
*/
|
|
|
|
static void
|
|
irc_join_list (server *serv, GSList *favorites)
|
|
{
|
|
int first_item = 1; /* determine whether we add commas or not */
|
|
int send_keys = 0; /* if none of our channels have keys, we can omit the 'x' fillers altogether */
|
|
int len = 9; /* JOIN<space>channels<space>keys\r\n\0 */
|
|
favchannel *fav;
|
|
GString *chanlist = g_string_new (NULL);
|
|
GString *keylist = g_string_new (NULL);
|
|
GSList *favlist;
|
|
|
|
favlist = favorites;
|
|
|
|
while (favlist)
|
|
{
|
|
fav = favlist->data;
|
|
|
|
len += strlen (fav->name);
|
|
if (fav->key)
|
|
{
|
|
len += strlen (fav->key);
|
|
}
|
|
|
|
if (len >= 512) /* command length exceeds the IRC hard limit, flush it and start from scratch */
|
|
{
|
|
irc_join_list_flush (serv, chanlist, keylist, send_keys);
|
|
|
|
chanlist = g_string_new (NULL);
|
|
keylist = g_string_new (NULL);
|
|
|
|
len = 9;
|
|
first_item = 1; /* list dumped, omit commas once again */
|
|
send_keys = 0; /* also omit keys until we actually find one */
|
|
}
|
|
|
|
if (!first_item)
|
|
{
|
|
/* This should be done before the length check, but channel names
|
|
* are already at least 2 characters long so it would trigger the
|
|
* flush anyway.
|
|
*/
|
|
len += 2;
|
|
|
|
/* add separators but only if it's not the 1st element */
|
|
g_string_append_c (chanlist, ',');
|
|
g_string_append_c (keylist, ',');
|
|
}
|
|
|
|
g_string_append (chanlist, fav->name);
|
|
|
|
if (fav->key)
|
|
{
|
|
g_string_append (keylist, fav->key);
|
|
send_keys = 1;
|
|
}
|
|
else
|
|
{
|
|
g_string_append_c (keylist, 'x'); /* 'x' filler for keyless channels so that our JOIN command is always well-formatted */
|
|
}
|
|
|
|
first_item = 0;
|
|
favlist = favlist->next;
|
|
}
|
|
|
|
irc_join_list_flush (serv, chanlist, keylist, send_keys);
|
|
g_slist_free (favlist);
|
|
}
|
|
|
|
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: [<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_message_tagged (server *serv, char *tags, char *channel, char *text)
|
|
{
|
|
tcp_sendf (serv, "@%s PRIVMSG %s :%s\r\n", tags, channel, text);
|
|
}
|
|
|
|
static void
|
|
irc_tagmsg (server *serv, char *tags, char *target)
|
|
{
|
|
tcp_sendf (serv, "@%s TAGMSG %s\r\n", tags, target);
|
|
}
|
|
|
|
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[8704];
|
|
if (*raw)
|
|
{
|
|
len = strlen (raw);
|
|
if (len < sizeof (tbuf) - 3)
|
|
{
|
|
len = g_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,
|
|
const message_tags_data *tags_data)
|
|
{
|
|
time_t timestamp = (time_t) atol (timestr);
|
|
char *tim = ctime (×tamp);
|
|
if (tim != NULL)
|
|
tim[24] = 0; /* get rid of the \n */
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANDATE, sess, chan, tim, NULL, NULL, 0,
|
|
tags_data->timestamp);
|
|
}
|
|
|
|
static int
|
|
trailing_index(char *word_eol[])
|
|
{
|
|
int param_index;
|
|
for (param_index = 3; param_index < PDIWORDS; ++param_index)
|
|
{
|
|
if (word_eol[param_index][0] == ':')
|
|
break;
|
|
}
|
|
return param_index;
|
|
}
|
|
|
|
static void
|
|
process_numeric (session * sess, int n,
|
|
char *word[], char *word_eol[], char *text,
|
|
const message_tags_data *tags_data)
|
|
{
|
|
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], tags_data);
|
|
/* 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, tags_data);
|
|
}
|
|
|
|
goto def;
|
|
|
|
case 5:
|
|
inbound_005 (serv, word, tags_data);
|
|
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], FE_MSG_ERROR);
|
|
}
|
|
goto def;
|
|
|
|
case 301:
|
|
inbound_away (serv, word[4],
|
|
(word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5],
|
|
tags_data);
|
|
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, tags_data);
|
|
}
|
|
}
|
|
|
|
serv->skip_next_userhost = FALSE;
|
|
break;
|
|
}
|
|
else goto def;
|
|
|
|
case 303:
|
|
word[4]++;
|
|
notify_markonline (serv, word, tags_data);
|
|
break;
|
|
|
|
case 305:
|
|
inbound_uback (serv, tags_data);
|
|
goto def;
|
|
|
|
case 306:
|
|
inbound_uaway (serv, tags_data);
|
|
goto def;
|
|
|
|
case 312:
|
|
if (!serv->skip_next_whois)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS3, whois_sess, word[4], word_eol[5],
|
|
NULL, NULL, 0, tags_data->timestamp);
|
|
else
|
|
inbound_user_info (sess, NULL, NULL, NULL, word[5], word[4], NULL, NULL,
|
|
0xff, tags_data);
|
|
break;
|
|
|
|
case 311: /* WHOIS 1st line */
|
|
serv->inside_whois = 1;
|
|
inbound_user_info_start (sess, word[4], tags_data);
|
|
if (!serv->skip_next_whois)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS1, whois_sess, word[4], word[5],
|
|
word[6], (word_eol[8][0] == ':') ? word_eol[8] + 1 : word_eol[8],
|
|
0, tags_data->timestamp);
|
|
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, tags_data);
|
|
break;
|
|
|
|
case 314: /* WHOWAS */
|
|
inbound_user_info_start (sess, word[4], tags_data);
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS1, whois_sess, word[4], word[5],
|
|
word[6], word_eol[8] + 1, 0, tags_data->timestamp);
|
|
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];
|
|
|
|
g_snprintf (outbuf, sizeof (outbuf),
|
|
"%02ld:%02ld:%02ld", idle / 3600, (idle / 60) % 60,
|
|
idle % 60);
|
|
if (timestamp == 0)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS4, whois_sess, word[4],
|
|
outbuf, NULL, NULL, 0, tags_data->timestamp);
|
|
else
|
|
{
|
|
tim = ctime (×tamp);
|
|
if (tim != NULL)
|
|
tim[19] = 0; /* get rid of the \n */
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS4T, whois_sess, word[4],
|
|
outbuf, tim, NULL, 0, tags_data->timestamp);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 318: /* END OF WHOIS */
|
|
if (!serv->skip_next_whois)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS6, whois_sess, word[4], NULL,
|
|
NULL, NULL, 0, tags_data->timestamp);
|
|
serv->skip_next_whois = 0;
|
|
serv->inside_whois = 0;
|
|
break;
|
|
|
|
case 313:
|
|
case 319:
|
|
if (!serv->skip_next_whois)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS2, whois_sess, word[4],
|
|
word_eol[5][0] == ':' ? word_eol[5] + 1 : word_eol[5], NULL, NULL, 0,
|
|
tags_data->timestamp);
|
|
break;
|
|
|
|
case 307: /* dalnet version */
|
|
case 320: /* :is an identified user */
|
|
if (!serv->skip_next_whois)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS_ID, whois_sess, word[4],
|
|
word_eol[5][0] == ':' ? word_eol[5] + 1 : word_eol[5], NULL, NULL, 0,
|
|
tags_data->timestamp);
|
|
break;
|
|
|
|
case 321:
|
|
if (!fe_is_chanwindow (sess->server))
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANLISTHEAD, serv->server_session, NULL,
|
|
NULL, NULL, NULL, 0, tags_data->timestamp);
|
|
break;
|
|
|
|
case 322:
|
|
if (fe_is_chanwindow (sess->server))
|
|
{
|
|
fe_add_chan_list (sess->server, word[4], word[5], word_eol[6] + 1);
|
|
} else
|
|
{
|
|
PrintTextTimeStampf (serv->server_session, tags_data->timestamp,
|
|
"%-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_TIMESTAMP (XP_TE_SERVTEXT, serv->server_session, text,
|
|
word[1], word[2], NULL, 0, tags_data->timestamp);
|
|
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_TIMESTAMP (XP_TE_CHANMODES, sess, word[4], (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5],
|
|
NULL, NULL, 0, tags_data->timestamp);
|
|
fe_update_mode_buttons (sess, 'c', '-');
|
|
fe_update_mode_buttons (sess, 't', '-');
|
|
fe_update_mode_buttons (sess, 'n', '-');
|
|
fe_update_mode_buttons (sess, 'i', '-');
|
|
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, tags_data);
|
|
break;
|
|
|
|
case 328: /* channel url */
|
|
sess = find_channel (serv, word[4]);
|
|
if (sess)
|
|
{
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANURL, sess, word[4], (word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5],
|
|
NULL, NULL, 0, tags_data->timestamp);
|
|
}
|
|
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][0] == ':') ? word[5] + 1 : word[5], tags_data);
|
|
}
|
|
break;
|
|
|
|
case 330:
|
|
if (!serv->skip_next_whois)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS_AUTH, whois_sess, word[4],
|
|
word_eol[6] + 1, word[5], NULL, 0,
|
|
tags_data->timestamp);
|
|
inbound_user_info (sess, NULL, NULL, NULL, NULL, word[4], NULL, word[5],
|
|
0xff, tags_data);
|
|
break;
|
|
|
|
case 332:
|
|
inbound_topic (serv, word[4],
|
|
(word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5],
|
|
tags_data);
|
|
break;
|
|
|
|
case 333:
|
|
inbound_topictime (serv, word[4], word[5], atol (STRIP_COLON(word, word_eol, 6)), tags_data);
|
|
break;
|
|
|
|
#if 0
|
|
case 338: /* Undernet Real user@host, Real IP */
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS_REALHOST, sess, word[4], word[5], word[6],
|
|
(word_eol[7][0]==':') ? word_eol[7]+1 : word_eol[7],
|
|
0, tags_data->timestamp);
|
|
break;
|
|
#endif
|
|
|
|
case 341: /* INVITE ACK */
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_UINVITE, sess, word[4], STRIP_COLON(word, word_eol, 5),
|
|
serv->servername, NULL, 0, tags_data->timestamp);
|
|
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], NULL, away,
|
|
tags_data);
|
|
|
|
/* try to show only user initiated whos */
|
|
if (!who_sess || !who_sess->doing_who)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, serv->server_session, text, word[1],
|
|
word[2], NULL, 0, tags_data->timestamp);
|
|
}
|
|
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,
|
|
tags_data);
|
|
|
|
/* try to show only user initiated whos */
|
|
if (!who_sess || !who_sess->doing_who)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, serv->server_session, text,
|
|
word[1], word[2], NULL, 0,
|
|
tags_data->timestamp);
|
|
} 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_TIMESTAMP (XP_TE_SERVTEXT, serv->server_session, text,
|
|
word[1], word[2], NULL, 0,
|
|
tags_data->timestamp);
|
|
who_sess->doing_who = FALSE;
|
|
} else
|
|
{
|
|
if (!serv->doing_dns)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, serv->server_session, text,
|
|
word[1], word[2], NULL, 0, tags_data->timestamp);
|
|
serv->doing_dns = FALSE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 346: /* +I-list entry */
|
|
if (!inbound_banlist (sess, atol (STRIP_COLON (word, word_eol, 7)), word[4], word[5], word[6], 346,
|
|
tags_data))
|
|
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 (STRIP_COLON (word, word_eol, 7)), word[4], word[5], word[6], 348,
|
|
tags_data))
|
|
goto def;
|
|
break;
|
|
|
|
case 349: /* end of exemption list */
|
|
sess = find_channel (serv, word[4]);
|
|
if (!sess)
|
|
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],
|
|
tags_data);
|
|
break;
|
|
|
|
case 366:
|
|
if (!inbound_nameslist_end (serv, word[4], tags_data))
|
|
goto def;
|
|
break;
|
|
|
|
case 367: /* banlist entry */
|
|
if (!inbound_banlist (sess, atol (STRIP_COLON (word, word_eol, 7)), word[4], word[5], word[6], 367,
|
|
tags_data))
|
|
goto def;
|
|
break;
|
|
|
|
case 368:
|
|
sess = find_channel (serv, word[4]);
|
|
if (!sess)
|
|
goto def;
|
|
if (!fe_ban_list_end (sess, 368))
|
|
goto def;
|
|
break;
|
|
|
|
case 369: /* WHOWAS end */
|
|
case 406: /* WHOWAS error */
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, whois_sess, text, word[1], word[2],
|
|
NULL, 0, tags_data->timestamp);
|
|
serv->inside_whois = 0;
|
|
break;
|
|
|
|
case 372: /* motd text */
|
|
case 375: /* motd start */
|
|
if (!prefs.hex_irc_skip_motd || serv->motd_skipped)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_MOTD, serv->server_session, text, NULL,
|
|
NULL, NULL, 0, tags_data->timestamp);
|
|
break;
|
|
|
|
case 376: /* end of motd */
|
|
case 422: /* motd file is missing */
|
|
inbound_login_end (sess, text, tags_data);
|
|
break;
|
|
|
|
case 432: /* erroneous nickname */
|
|
if (serv->end_of_motd)
|
|
{
|
|
goto def;
|
|
}
|
|
inbound_next_nick (sess, word[4], 1, tags_data);
|
|
break;
|
|
|
|
case 433: /* nickname in use */
|
|
if (serv->end_of_motd)
|
|
{
|
|
goto def;
|
|
}
|
|
inbound_next_nick (sess, word[4], 0, tags_data);
|
|
break;
|
|
|
|
case 437:
|
|
if (serv->end_of_motd || is_channel (serv, word[4]))
|
|
goto def;
|
|
inbound_next_nick (sess, word[4], 0, tags_data);
|
|
break;
|
|
|
|
case 471:
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_USERLIMIT, sess, word[4], NULL, NULL, NULL, 0,
|
|
tags_data->timestamp);
|
|
break;
|
|
|
|
case 473:
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_INVITE, sess, word[4], NULL, NULL, NULL, 0,
|
|
tags_data->timestamp);
|
|
break;
|
|
|
|
case 474:
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_BANNED, sess, word[4], NULL, NULL, NULL, 0,
|
|
tags_data->timestamp);
|
|
break;
|
|
|
|
case 475:
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_KEYWORD, sess, word[4], NULL, NULL, NULL, 0,
|
|
tags_data->timestamp);
|
|
break;
|
|
|
|
case 601:
|
|
notify_set_offline (serv, word[4], FALSE, tags_data);
|
|
break;
|
|
|
|
case 605:
|
|
notify_set_offline (serv, word[4], TRUE, tags_data);
|
|
break;
|
|
|
|
case 600:
|
|
case 604:
|
|
notify_set_online (serv, word[4], tags_data);
|
|
break;
|
|
|
|
case 524: // ERR_HELPNOTFOUND
|
|
case 704: // RPL_HELPSTART
|
|
case 705: // RPL_HELPTXT
|
|
case 706: // RPL_ENDOFHELP
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, sess, STRIP_COLON(word, word_eol, 5), NULL, NULL, NULL,
|
|
0, tags_data->timestamp);
|
|
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,
|
|
tags_data))
|
|
goto def;
|
|
break;
|
|
|
|
case 729: /* end of quiet list */
|
|
if (!fe_ban_list_end (sess, 729))
|
|
goto def;
|
|
break;
|
|
|
|
case 730: /* RPL_MONONLINE */
|
|
notify_set_online_list (serv, word[4] + 1, tags_data);
|
|
break;
|
|
|
|
case 731: /* RPL_MONOFFLINE */
|
|
notify_set_offline_list (serv, word[4] + 1, FALSE, tags_data);
|
|
break;
|
|
|
|
case 900: /* successful SASL 'logged in as ' */
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, serv->server_session,
|
|
word_eol[6]+1, word[1], word[2], NULL, 0,
|
|
tags_data->timestamp);
|
|
break;
|
|
case 904: /* failed SASL auth */
|
|
inbound_sasl_error (serv);
|
|
case 903: /* successful SASL auth */
|
|
case 905: /* failed SASL auth */
|
|
case 906: /* aborted */
|
|
case 907: /* attempting to re-auth after a successful auth */
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLRESPONSE, serv->server_session, word[1],
|
|
word[2], word[3], ++word_eol[4], 0,
|
|
tags_data->timestamp);
|
|
serv->waiting_on_sasl = FALSE;
|
|
if (!serv->sent_capend)
|
|
{
|
|
serv->sent_capend = TRUE;
|
|
tcp_send_len (serv, "CAP END\r\n", 9);
|
|
}
|
|
break;
|
|
case 908: /* Supported SASL Mechs */
|
|
/* ignored for now, SASL 3.2 is a better solution and we only do PLAIN atm */
|
|
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_TIMESTAMP (XP_TE_WHOIS_SPECIAL, whois_sess, word[4],
|
|
(word_eol[5][0] == ':') ? word_eol[5] + 1 : word_eol[5],
|
|
word[2], NULL, 0, tags_data->timestamp);
|
|
return;
|
|
}
|
|
|
|
def:
|
|
{
|
|
session *sess;
|
|
|
|
if (is_channel (serv, word[4]))
|
|
{
|
|
sess = find_channel (serv, word[4]);
|
|
if (!sess)
|
|
sess = serv->server_session;
|
|
}
|
|
else if ((sess=find_dialog (serv,word[4]))) /* user with an open dialog */
|
|
;
|
|
else
|
|
sess=serv->server_session;
|
|
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, sess, text, word[1], word[2],
|
|
NULL, 0, tags_data->timestamp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* handle named messages that starts with a ':' */
|
|
|
|
|
|
static void
|
|
emit_standard_reply (session *sess, const char *type, char *command, char *code, char *text, const message_tags_data *tags_data)
|
|
{
|
|
int is_global = g_strcmp0 (command, "*") == 0;
|
|
|
|
if (!text)
|
|
text = "";
|
|
if (*text == ':')
|
|
text++;
|
|
|
|
if (!strcmp (type, "FAIL"))
|
|
{
|
|
if (is_global)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_FAIL, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
|
else
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_FAILCMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
|
}
|
|
else if (!strcmp (type, "WARN"))
|
|
{
|
|
if (is_global)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WARN, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
|
else
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WARNCMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
|
}
|
|
else if (!strcmp (type, "NOTE"))
|
|
{
|
|
if (is_global)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTE, sess, code, text, NULL, NULL, NULL, tags_data->timestamp);
|
|
else
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTECMD, sess, command, code, text, NULL, NULL, tags_data->timestamp);
|
|
}
|
|
}
|
|
|
|
static void
|
|
process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
|
|
const message_tags_data *tags_data)
|
|
{
|
|
server *serv = sess->server;
|
|
char *account;
|
|
char ip[128], nick[NICKLEN];
|
|
char *text, *ex;
|
|
size_t 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] = '!';
|
|
}
|
|
|
|
|
|
/** Update the account for this message's source. */
|
|
if (serv->have_account_tag)
|
|
{
|
|
account = tags_data->account && *tags_data->account ? tags_data->account : "*";
|
|
inbound_account (serv, nick, account, tags_data);
|
|
}
|
|
|
|
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];
|
|
|
|
if (account && strcmp (account, "*") == 0)
|
|
account = NULL;
|
|
if (realname && *realname == ':')
|
|
realname++;
|
|
if (*chan == ':')
|
|
chan++;
|
|
if (!serv->p_cmp (nick, serv->nick))
|
|
inbound_ujoin (serv, chan, nick, ip, tags_data);
|
|
else
|
|
inbound_join (serv, chan, nick, ip, account, realname,
|
|
tags_data);
|
|
}
|
|
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, tags_data);
|
|
else
|
|
inbound_kick (serv, word[3], kicked, nick, reason, tags_data);
|
|
}
|
|
}
|
|
return;
|
|
|
|
case WORDL('K','I','L','L'):
|
|
{
|
|
char *reason = word_eol[4];
|
|
if (*reason == ':')
|
|
reason++;
|
|
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_KILL, sess, nick, reason, NULL, NULL,
|
|
0, tags_data->timestamp);
|
|
}
|
|
return;
|
|
|
|
case WORDL('M','O','D','E'):
|
|
handle_mode (serv, word, word_eol, nick, FALSE, tags_data); /* 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, tags_data);
|
|
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, tags_data);
|
|
else
|
|
inbound_part (serv, chan, nick, ip, reason, tags_data);
|
|
}
|
|
return;
|
|
|
|
case WORDL('P', 'I', 'N', 'G'):
|
|
tcp_sendf (sess->server, "PONG %s\r\n", word_eol[3]);
|
|
return;
|
|
|
|
case WORDL('P','O','N','G'):
|
|
inbound_ping_reply (serv->server_session,
|
|
(word[4][0] == ':') ? word[4] + 1 : word[4],
|
|
word[3], tags_data);
|
|
return;
|
|
|
|
case WORDL('Q','U','I','T'):
|
|
inbound_quit (serv, nick, ip,
|
|
(word_eol[3][0] == ':') ? word_eol[3] + 1 : word_eol[3],
|
|
tags_data);
|
|
return;
|
|
|
|
case WORDL('A','W','A','Y'):
|
|
inbound_away_notify (serv, nick,
|
|
(word_eol[3][0] == ':') ? word_eol[3] + 1 : NULL,
|
|
tags_data);
|
|
return;
|
|
|
|
case WORDL('F','A','I','L'):
|
|
text = STRIP_COLON(word, word_eol, trailing_index(word_eol));
|
|
if (g_strcmp0(word[3], "*") == 0)
|
|
{
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_FAIL, sess, word[4], text, NULL, NULL, NULL, tags_data->timestamp);
|
|
} else
|
|
{
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_FAILCMD, sess, word[3], word[4], text, NULL, NULL, tags_data->timestamp);
|
|
}
|
|
return;
|
|
|
|
case WORDL('W','A','R','N'):
|
|
text = STRIP_COLON(word, word_eol, trailing_index(word_eol));
|
|
if (g_strcmp0(word[3], "*") == 0)
|
|
{
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WARN, sess, word[4], text, NULL, NULL, NULL, tags_data->timestamp);
|
|
} else
|
|
{
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WARNCMD, sess, word[3], word[4], text, NULL, NULL, tags_data->timestamp);
|
|
}
|
|
return;
|
|
|
|
case WORDL('N','O','T','E'):
|
|
text = STRIP_COLON(word, word_eol, trailing_index(word_eol));
|
|
if (g_strcmp0(word[3], "*") == 0)
|
|
{
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTE, sess, word[4], text, NULL, NULL, NULL, tags_data->timestamp);
|
|
} else
|
|
{
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTECMD, sess, word[3], word[4], text, NULL, NULL, tags_data->timestamp);
|
|
}
|
|
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, STRIP_COLON(word, word_eol, 3), tags_data);
|
|
return;
|
|
|
|
case WORDL('A', 'U', 'T', 'H'):
|
|
inbound_sasl_authenticate (sess->server, word_eol[3]);
|
|
return;
|
|
|
|
case WORDL('C', 'H', 'G', 'H'):
|
|
inbound_user_info (sess, NULL, word[3], STRIP_COLON(word, word_eol, 4), NULL, nick, NULL,
|
|
NULL, 0xff, tags_data);
|
|
return;
|
|
|
|
case WORDL('S', 'E', 'T', 'N'):
|
|
inbound_user_info (sess, NULL, NULL, NULL, NULL, nick, STRIP_COLON(word, word_eol, 3),
|
|
NULL, 0xff, tags_data);
|
|
return;
|
|
|
|
case WORDL('I','N','V','I'):
|
|
if (ignore_check (word[1], IG_INVI))
|
|
return;
|
|
|
|
text = STRIP_COLON(word, word_eol, 4);
|
|
if (serv->p_cmp (word[3], serv->nick))
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_INVITEDOTHER, sess, text, nick,
|
|
word[3], serv->servername, 0,
|
|
tags_data->timestamp);
|
|
else
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_INVITED, sess, text, nick,
|
|
serv->servername, NULL, 0,
|
|
tags_data->timestamp);
|
|
|
|
return;
|
|
|
|
case WORDL('N','O','T','I'):
|
|
{
|
|
text = word_eol[4];
|
|
if (*text == ':')
|
|
{
|
|
text++;
|
|
}
|
|
|
|
#ifdef USE_OPENSSL
|
|
/* QuakeNet CHALLENGE upon our request */
|
|
if (serv->loginmethod == LOGIN_CHALLENGEAUTH && !serv->p_cmp (word[1], CHALLENGEAUTH_FULLHOST)
|
|
&& !strncmp (text, "CHALLENGE ", 10) && *serv->password)
|
|
{
|
|
char *response;
|
|
ircnet *net = serv->network;
|
|
char *user = net && net->user ? net->user : prefs.hex_irc_user_name;
|
|
|
|
response = challengeauth_response (user, serv->password, word[5]);
|
|
|
|
tcp_sendf (serv, "PRIVMSG %s :CHALLENGEAUTH %s %s %s\r\n",
|
|
CHALLENGEAUTH_NICK,
|
|
user,
|
|
response,
|
|
CHALLENGEAUTH_ALGO);
|
|
|
|
g_free (response);
|
|
return; /* omit the CHALLENGE <hash> ALGOS message */
|
|
}
|
|
#endif
|
|
|
|
if (!ignore_check (word[1], IG_NOTI))
|
|
inbound_notice (serv, word[3], nick, text, ip, tags_data->identified, tags_data);
|
|
}
|
|
return;
|
|
|
|
case WORDL('P','R','I','V'):
|
|
{
|
|
char *to = word[3];
|
|
int len;
|
|
if (*to)
|
|
{
|
|
/* Handle limited channel messages, for now no special event */
|
|
if (strchr (serv->chantypes, to[0]) == NULL
|
|
&& strchr (serv->nick_prefixes, to[0]) != NULL)
|
|
to++;
|
|
|
|
text = word_eol[4];
|
|
if (*text == ':')
|
|
text++;
|
|
|
|
len = strlen (text);
|
|
if (text[0] == 1) /* ctcp */
|
|
{
|
|
char *new_pdibuf = NULL;
|
|
if (text[len - 1] == 1)
|
|
{
|
|
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)
|
|
{
|
|
int i;
|
|
char *new_word[PDIWORDS+1] = { NULL };
|
|
char *new_word_eol[PDIWORDS+1] = { NULL };
|
|
|
|
new_pdibuf = g_malloc (strlen (word_eol[6]) + 1);
|
|
|
|
/* This is a bit ugly but we handle the contents of the DCC message containing
|
|
* "quoted paths for files" here which means reparsing the message with handle_quotes.
|
|
* We avoid reparsing the entire message to avoid corrupting the non DCC parts.
|
|
* Greater than PDIWORD length DCC messages will be truncated. */
|
|
process_data_init (new_pdibuf, word_eol[6], new_word, new_word_eol, TRUE, FALSE);
|
|
for (i = 6; i < PDIWORDS; ++i)
|
|
{
|
|
word[i] = new_word[i - 5];
|
|
word_eol[i] = new_word_eol[i - 5];
|
|
}
|
|
}
|
|
|
|
ctcp_handle (sess, to, nick, ip, text, word, word_eol, tags_data->identified,
|
|
tags_data);
|
|
|
|
/* Note word will be invalid beyond this scope */
|
|
g_free (new_pdibuf);
|
|
} else
|
|
{
|
|
if (is_channel (serv, to))
|
|
{
|
|
if (ignore_check (word[1], IG_CHAN))
|
|
return;
|
|
inbound_chanmsg (serv, NULL, to, nick, text, FALSE, tags_data->identified,
|
|
tags_data);
|
|
} else
|
|
{
|
|
if (ignore_check (word[1], IG_PRIV))
|
|
return;
|
|
if (!serv->p_cmp (nick, serv->nick))
|
|
{
|
|
session *target_sess = find_dialog (serv, to);
|
|
|
|
if (!target_sess)
|
|
target_sess = find_channel (serv, to);
|
|
if (target_sess)
|
|
inbound_chanmsg (serv, target_sess, target_sess->channel, nick, text, TRUE, tags_data->identified, tags_data);
|
|
else if (serv->front_session)
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_MSGSEND, serv->front_session, to, text, NULL, NULL, 0, tags_data->timestamp);
|
|
} else
|
|
{
|
|
inbound_privmsg (serv, nick, ip, text, tags_data->identified, tags_data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
|
|
case WORDL('T','O','P','I'):
|
|
inbound_topicnew (serv, nick, word[3],
|
|
(word_eol[4][0] == ':') ? word_eol[4] + 1 : word_eol[4],
|
|
tags_data);
|
|
return;
|
|
|
|
case WORDL('T','A','G','M'):
|
|
if (tags_data->typing)
|
|
{
|
|
session *typing_sess = NULL;
|
|
char *to = word[3];
|
|
|
|
if (is_channel (serv, to))
|
|
typing_sess = find_channel (serv, to);
|
|
else if (!serv->p_cmp (to, serv->nick))
|
|
typing_sess = find_dialog (serv, nick);
|
|
if (!typing_sess)
|
|
typing_sess = sess;
|
|
if (!serv->p_cmp (nick, serv->nick))
|
|
return;
|
|
if (!strcmp (tags_data->typing, "done"))
|
|
fe_set_typing (typing_sess, nick, "done");
|
|
else if (!strcmp (tags_data->typing, "paused"))
|
|
fe_set_typing (typing_sess, nick, "paused");
|
|
else if (!strcmp (tags_data->typing, "active"))
|
|
fe_set_typing (typing_sess, nick, "active");
|
|
}
|
|
return;
|
|
|
|
case WORDL('W','A','L','L'):
|
|
text = word_eol[3];
|
|
if (*text == ':')
|
|
text++;
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_WALLOPS, sess, nick, text, NULL, NULL, 0,
|
|
tags_data->timestamp);
|
|
return;
|
|
}
|
|
}
|
|
|
|
else if (len == 3)
|
|
{
|
|
guint32 t;
|
|
|
|
t = WORDL((guint8)type[0], (guint8)type[1], (guint8)type[2], (guint8)type[3]);
|
|
switch (t)
|
|
{
|
|
case WORDL('C','A','P','\0'):
|
|
if (g_ascii_strncasecmp(word[4], "ACK", 3) == 0)
|
|
{
|
|
inbound_cap_ack (serv, word[1],
|
|
word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5],
|
|
tags_data);
|
|
}
|
|
else if (g_ascii_strncasecmp(word[4], "LS", 2) == 0)
|
|
{
|
|
inbound_cap_ls (serv, word[1],
|
|
word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5],
|
|
tags_data);
|
|
}
|
|
else if (g_ascii_strncasecmp(word[4], "NEW", 3) == 0)
|
|
{
|
|
inbound_cap_new (serv, word[1],
|
|
word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5],
|
|
tags_data);
|
|
}
|
|
else if (g_ascii_strncasecmp(word[4], "NAK", 3) == 0)
|
|
{
|
|
inbound_cap_nak (serv, word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5], tags_data);
|
|
}
|
|
else if (g_ascii_strncasecmp(word[4], "LIST", 4) == 0)
|
|
{
|
|
inbound_cap_list (serv, word[1],
|
|
word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5],
|
|
tags_data);
|
|
}
|
|
else if (g_ascii_strncasecmp(word[4], "DEL", 3) == 0)
|
|
{
|
|
inbound_cap_del (serv, word[1],
|
|
word[5][0] == ':' ? word_eol[5] + 1 : word_eol[5],
|
|
tags_data);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
garbage:
|
|
/* unknown message */
|
|
PrintTextTimeStampf (sess, tags_data->timestamp, "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[],
|
|
const message_tags_data *tags_data)
|
|
{
|
|
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_TIMESTAMP (XP_TE_SERVERERROR, sess, buf + 7, NULL, NULL, NULL,
|
|
0, tags_data->timestamp);
|
|
return;
|
|
}
|
|
if (!strncmp (buf, "NOTICE ", 7))
|
|
{
|
|
buf = word_eol[3];
|
|
if (*buf == ':')
|
|
buf++;
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVNOTICE, sess, buf,
|
|
sess->server->servername, NULL, NULL, 0,
|
|
tags_data->timestamp);
|
|
return;
|
|
}
|
|
if (!strncmp (buf, "AUTHENTICATE", 12))
|
|
{
|
|
inbound_sasl_authenticate (sess->server, word_eol[2]);
|
|
return;
|
|
}
|
|
if (!strncmp (buf, "FAIL ", 5) || !strncmp (buf, "WARN ", 5) || !strncmp (buf, "NOTE ", 5))
|
|
{
|
|
emit_standard_reply (sess, word_eol[1], word_eol[2], word_eol[3], word_eol[4], tags_data);
|
|
return;
|
|
}
|
|
|
|
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, sess, buf, sess->server->servername,
|
|
rawname, NULL, 0, tags_data->timestamp);
|
|
}
|
|
|
|
/* Returns the timezone offset. This should be the same as the variable
|
|
* "timezone" in time.h, but *BSD doesn't have it.
|
|
*/
|
|
static time_t
|
|
get_timezone (void)
|
|
{
|
|
struct tm tm_utc, tm_local;
|
|
time_t t, time_utc, time_local;
|
|
|
|
time (&t);
|
|
|
|
/* gmtime() and localtime() are thread-safe on windows.
|
|
* on other systems we should use {gmtime,localtime}_r().
|
|
*/
|
|
#if WIN32
|
|
tm_utc = *gmtime (&t);
|
|
tm_local = *localtime (&t);
|
|
#else
|
|
gmtime_r (&t, &tm_utc);
|
|
localtime_r (&t, &tm_local);
|
|
#endif
|
|
|
|
time_utc = mktime (&tm_utc);
|
|
time_local = mktime (&tm_local);
|
|
|
|
return time_utc - time_local;
|
|
}
|
|
|
|
/* Handle time-server tags.
|
|
*
|
|
* Sets tags_data->timestamp to the correct time (in unix time).
|
|
* This received time is always in UTC.
|
|
*
|
|
* See http://ircv3.atheme.org/extensions/server-time-3.2
|
|
*/
|
|
static void
|
|
handle_message_tag_time (const char *time, message_tags_data *tags_data)
|
|
{
|
|
/* The time format defined in the ircv3.2 specification is
|
|
* YYYY-MM-DDThh:mm:ss.sssZ
|
|
* but znc simply sends a unix time (with 3 decimal places for miliseconds)
|
|
* so we might as well support both.
|
|
*/
|
|
if (!*time)
|
|
return;
|
|
|
|
if (time[strlen (time) - 1] == 'Z')
|
|
{
|
|
/* as defined in the specification */
|
|
struct tm t;
|
|
int z;
|
|
|
|
/* we ignore the milisecond part */
|
|
z = sscanf (time, "%d-%d-%dT%d:%d:%d", &t.tm_year, &t.tm_mon, &t.tm_mday,
|
|
&t.tm_hour, &t.tm_min, &t.tm_sec);
|
|
|
|
if (z != 6)
|
|
return;
|
|
|
|
t.tm_year -= 1900;
|
|
t.tm_mon -= 1;
|
|
t.tm_isdst = 0; /* day light saving time */
|
|
|
|
tags_data->timestamp = mktime (&t);
|
|
|
|
if (tags_data->timestamp < 0)
|
|
{
|
|
tags_data->timestamp = 0;
|
|
return;
|
|
}
|
|
|
|
/* get rid of the local time (mktime() receives a local calendar time) */
|
|
tags_data->timestamp -= get_timezone();
|
|
}
|
|
else
|
|
{
|
|
/* znc */
|
|
long long int t;
|
|
|
|
/* we ignore the milisecond part */
|
|
if (
|
|
#if defined(__MINGW64__) || defined(__MINGW32__)
|
|
__mingw_sscanf
|
|
#else
|
|
sscanf
|
|
#endif
|
|
(time, "%lld", &t) != 1)
|
|
return;
|
|
|
|
tags_data->timestamp = (time_t) t;
|
|
}
|
|
}
|
|
|
|
/* Handle message tags.
|
|
*
|
|
* See http://ircv3.atheme.org/specification/message-tags-3.2
|
|
*/
|
|
static char *
|
|
message_tag_unescape (const char *value)
|
|
{
|
|
GString *out;
|
|
const char *p;
|
|
|
|
if (!*value)
|
|
return NULL;
|
|
|
|
out = g_string_sized_new (strlen (value));
|
|
|
|
for (p = value; *p; p++)
|
|
{
|
|
if (*p != '\\')
|
|
{
|
|
g_string_append_c (out, *p);
|
|
continue;
|
|
}
|
|
|
|
p++;
|
|
if (!*p)
|
|
break;
|
|
|
|
switch (*p)
|
|
{
|
|
case ':':
|
|
g_string_append_c (out, ';');
|
|
break;
|
|
case 's':
|
|
g_string_append_c (out, ' ');
|
|
break;
|
|
case '\\':
|
|
g_string_append_c (out, '\\');
|
|
break;
|
|
case 'r':
|
|
g_string_append_c (out, '\r');
|
|
break;
|
|
case 'n':
|
|
g_string_append_c (out, '\n');
|
|
break;
|
|
default:
|
|
g_string_append_c (out, *p);
|
|
break;
|
|
}
|
|
}
|
|
|
|
value = out->str;
|
|
if (!g_utf8_validate (value, -1, NULL))
|
|
{
|
|
g_string_free (out, TRUE);
|
|
return NULL;
|
|
}
|
|
|
|
return g_string_free (out, FALSE);
|
|
}
|
|
|
|
static void
|
|
handle_message_tags (server *serv, const char *tags_str,
|
|
message_tags_data *tags_data)
|
|
{
|
|
char **tags;
|
|
char *time = NULL;
|
|
int i;
|
|
|
|
tags = g_strsplit (tags_str, ";", 0);
|
|
|
|
for (i = 0; tags[i]; i++)
|
|
{
|
|
char *key = tags[i];
|
|
char *raw_value = strchr (tags[i], '=');
|
|
char *value = NULL;
|
|
|
|
if (!*key)
|
|
continue;
|
|
|
|
if (raw_value)
|
|
{
|
|
*raw_value = '\0';
|
|
raw_value++;
|
|
value = message_tag_unescape (raw_value);
|
|
}
|
|
|
|
if (serv->have_account_tag && !strcmp (key, "account"))
|
|
{
|
|
g_free (tags_data->account);
|
|
tags_data->account = value;
|
|
value = NULL;
|
|
}
|
|
else if (serv->have_idmsg && !strcmp (key, "solanum.chat/identified"))
|
|
{
|
|
tags_data->identified = TRUE;
|
|
}
|
|
else if (serv->have_server_time && !strcmp (key, "time"))
|
|
{
|
|
g_free (time);
|
|
time = value;
|
|
value = NULL;
|
|
}
|
|
else if (!strcmp (key, "msgid"))
|
|
{
|
|
g_free (tags_data->msgid);
|
|
tags_data->msgid = value;
|
|
value = NULL;
|
|
}
|
|
else if (!strcmp (key, "+reply"))
|
|
{
|
|
g_free (tags_data->reply);
|
|
tags_data->reply = value;
|
|
value = NULL;
|
|
}
|
|
else if (!strcmp (key, "+typing"))
|
|
{
|
|
g_free (tags_data->typing);
|
|
tags_data->typing = value;
|
|
value = NULL;
|
|
}
|
|
|
|
g_free (value);
|
|
}
|
|
|
|
if (time)
|
|
handle_message_tag_time (time, tags_data);
|
|
|
|
g_free (time);
|
|
g_strfreev (tags);
|
|
}
|
|
|
|
/* 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;
|
|
message_tags_data tags_data = MESSAGE_TAGS_DATA_INIT;
|
|
|
|
pdibuf = g_malloc (len + 1);
|
|
|
|
sess = serv->front_session;
|
|
|
|
/* Python relies on this */
|
|
word[PDIWORDS] = NULL;
|
|
word_eol[PDIWORDS] = NULL;
|
|
|
|
if (*buf == '@')
|
|
{
|
|
char *tags = buf + 1; /* skip the '@' */
|
|
char *sep = strchr (buf, ' ');
|
|
|
|
if (!sep)
|
|
goto xit;
|
|
|
|
*sep = '\0';
|
|
buf = sep + 1;
|
|
|
|
handle_message_tags(serv, tags, &tags_data);
|
|
}
|
|
|
|
url_check_line (buf);
|
|
|
|
/* split line into words and words_to_end_of_line */
|
|
process_data_init (pdibuf, buf, word, word_eol, FALSE, FALSE);
|
|
|
|
if (buf[0] == ':')
|
|
{
|
|
/* 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,
|
|
tags_data.timestamp))
|
|
goto xit;
|
|
|
|
word[1]++;
|
|
word_eol[1] = buf + 1; /* but not for ZoiteChat internally */
|
|
|
|
} else
|
|
{
|
|
word[0] = type = word[1];
|
|
|
|
if (plugin_emit_server (sess, type, word, word_eol,
|
|
tags_data.timestamp))
|
|
goto xit;
|
|
}
|
|
|
|
if (buf[0] != ':')
|
|
{
|
|
process_named_servermsg (sess, buf, word[0], word_eol, &tags_data);
|
|
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, &tags_data);
|
|
} else
|
|
{
|
|
process_named_msg (sess, type, word, word_eol, &tags_data);
|
|
}
|
|
|
|
xit:
|
|
message_tags_data_free (&tags_data);
|
|
g_free (pdibuf);
|
|
}
|
|
|
|
void
|
|
message_tags_data_free (message_tags_data *tags_data)
|
|
{
|
|
g_clear_pointer (&tags_data->account, g_free);
|
|
g_clear_pointer (&tags_data->msgid, g_free);
|
|
g_clear_pointer (&tags_data->reply, g_free);
|
|
g_clear_pointer (&tags_data->typing, g_free);
|
|
}
|
|
|
|
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_message_tagged = irc_message_tagged;
|
|
serv->p_tagmsg = irc_tagmsg;
|
|
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 */
|
|
}
|