Files
zoitechat/src/common/proto-irc.c
ThePeGaSuS 0ba523d071 Fix: private messages split into two windows over WeeChat's IRC relay
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.
2026-08-28 20:48:12 +02:00

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 (&timestamp);
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 (&timestamp);
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 */
}