Files
zoitechat/src/common/inbound.c
deepend a0f0c48bc5 Added STS profile persistence and policy parsing/enforcement (including load/save, upgrades, and expiry rescheduling) to the STS module.
Integrated STS capability handling and connection lifecycle hooks (ignore CAP DEL, trigger upgrades, reschedule on disconnect, new server fields).
Initialized and cleaned up STS state during startup/shutdown to persist policies across sessions.
2026-01-25 17:20:53 -07:00

2120 lines
48 KiB
C

/* X-Chat
* Copyright (C) 1998 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
*/
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <time.h>
#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
#define WANTARPA
#define WANTDNS
#include "inet.h"
#include "zoitechat.h"
#include "util.h"
#include "ignore.h"
#include "fe.h"
#include "modes.h"
#include "notify.h"
#include "outbound.h"
#include "inbound.h"
#include "server.h"
#include "servlist.h"
#include "sts.h"
#include "text.h"
#include "ctcp.h"
#include "zoitechatc.h"
#include "chanopt.h"
void
clear_channel (session *sess)
{
if (sess->channel[0])
strcpy (sess->waitchannel, sess->channel);
sess->channel[0] = 0;
sess->doing_who = FALSE;
sess->done_away_check = FALSE;
log_close (sess);
if (sess->current_modes)
{
g_free (sess->current_modes);
sess->current_modes = NULL;
}
if (sess->mode_timeout_tag)
{
fe_timeout_remove (sess->mode_timeout_tag);
sess->mode_timeout_tag = 0;
}
fe_clear_channel (sess);
userlist_clear (sess);
fe_set_nonchannel (sess, FALSE);
fe_set_title (sess);
}
void
set_topic (session *sess, char *topic, char *stripped_topic)
{
/* The topic of dialogs are the users hostname which is logged is new */
if (sess->type == SESS_DIALOG && (!sess->topic || strcmp(sess->topic, stripped_topic))
&& sess->logfd != -1)
{
char tbuf[1024];
g_snprintf (tbuf, sizeof (tbuf), "[%s has address %s]\n", sess->channel, stripped_topic);
write (sess->logfd, tbuf, strlen (tbuf));
}
g_free (sess->topic);
sess->topic = g_strdup (stripped_topic);
fe_set_topic (sess, topic, stripped_topic);
}
static session *
find_session_from_nick (char *nick, server *serv)
{
session *sess;
GSList *list = sess_list;
sess = find_dialog (serv, nick);
if (sess)
return sess;
if (serv->front_session)
{
// If we are here for ChanServ, then it is usually a reply for the user
if (!g_ascii_strcasecmp(nick, "ChanServ") || userlist_find (serv->front_session, nick))
return serv->front_session;
}
if (current_sess && current_sess->server == serv)
{
if (userlist_find (current_sess, nick))
return current_sess;
}
while (list)
{
sess = list->data;
if (sess->server == serv)
{
if (userlist_find (sess, nick))
return sess;
}
list = list->next;
}
return NULL;
}
static session *
inbound_open_dialog (server *serv, char *from,
const message_tags_data *tags_data)
{
session *sess;
sess = new_ircwindow (serv, from, SESS_DIALOG, 0);
/* for playing sounds */
EMIT_SIGNAL_TIMESTAMP (XP_TE_OPENDIALOG, sess, NULL, NULL, NULL, NULL, 0,
tags_data->timestamp);
return sess;
}
static void
inbound_make_idtext (server *serv, char *idtext, int max, int id)
{
idtext[0] = 0;
if (serv->have_idmsg || serv->have_accnotify)
{
if (id)
{
safe_strcpy (idtext, prefs.hex_irc_id_ytext, max);
} else
{
safe_strcpy (idtext, prefs.hex_irc_id_ntext, max);
}
/* convert codes like %C,%U to the proper ones */
check_special_chars (idtext, TRUE);
}
}
void
inbound_privmsg (server *serv, char *from, char *ip, char *text, int id,
const message_tags_data *tags_data)
{
session *sess;
struct User *user;
char idtext[64];
gboolean nodiag = FALSE;
sess = find_dialog (serv, from);
if (sess || prefs.hex_gui_autoopen_dialog)
{
/*0=ctcp 1=priv will set hex_gui_autoopen_dialog=0 here is flud detected */
if (!sess)
{
if (flood_check (from, ip, serv, current_sess, 1))
/* Create a dialog session */
sess = inbound_open_dialog (serv, from, tags_data);
else
sess = serv->server_session;
if (!sess)
return; /* ?? */
}
if (ip && ip[0])
set_topic (sess, ip, ip);
inbound_chanmsg (serv, NULL, NULL, from, text, FALSE, tags_data->identified, tags_data);
return;
}
sess = find_session_from_nick (from, serv);
if (!sess)
{
sess = serv->front_session;
nodiag = TRUE; /* We don't want it to look like a normal message in front sess */
}
user = userlist_find (sess, from);
if (user)
{
user->lasttalk = time (0);
if (user->account)
id = TRUE;
}
inbound_make_idtext (serv, idtext, sizeof (idtext), id);
if (sess->type == SESS_DIALOG && !nodiag)
EMIT_SIGNAL_TIMESTAMP (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0,
tags_data->timestamp);
else
EMIT_SIGNAL_TIMESTAMP (XP_TE_PRIVMSG, sess, from, text, idtext, NULL, 0,
tags_data->timestamp);
}
/* used for Alerts section. Masks can be separated by commas and spaces. */
gboolean
alert_match_word (char *word, char *masks)
{
char *p = masks;
char endchar;
int res;
if (masks[0] == 0)
return FALSE;
while (1)
{
/* if it's a 0, space or comma, the word has ended. */
if (*p == 0 || *p == ' ' || *p == ',')
{
endchar = *p;
*p = 0;
res = match (g_strchug (masks), word);
*p = endchar;
if (res)
return TRUE; /* yes, matched! */
masks = p + 1;
if (*p == 0)
return FALSE;
}
p++;
}
}
gboolean
alert_match_text (char *text, char *masks)
{
unsigned char *p = text;
unsigned char endchar;
int res;
if (masks[0] == 0)
return FALSE;
while (1)
{
if (*p >= '0' && *p <= '9')
{
p++;
continue;
}
/* if it's RFC1459 <special>, it can be inside a word */
switch (*p)
{
case '-': case '[': case ']': case '\\':
case '`': case '^': case '{': case '}':
case '_': case '|':
p++;
continue;
}
/* if it's a 0, space or comma, the word has ended. */
if (*p == 0 || *p == ' ' || *p == ',' ||
/* if it's anything BUT a letter, the word has ended. */
(!g_unichar_isalpha (g_utf8_get_char (p))))
{
endchar = *p;
*p = 0;
res = alert_match_word (text, masks);
*p = endchar;
if (res)
return TRUE; /* yes, matched! */
text = p + g_utf8_skip [p[0]];
if (*p == 0)
return FALSE;
}
p += g_utf8_skip [p[0]];
}
}
static int
is_hilight (char *from, char *text, session *sess, server *serv)
{
if (alert_match_word (from, prefs.hex_irc_no_hilight))
return 0;
text = strip_color (text, -1, STRIP_ALL);
if (alert_match_text (text, serv->nick) ||
alert_match_text (text, prefs.hex_irc_extra_hilight) ||
alert_match_word (from, prefs.hex_irc_nick_hilight))
{
g_free (text);
if (sess != current_tab)
{
sess->tab_state |= TAB_STATE_NEW_HILIGHT;
lastact_update (sess);
}
return 1;
}
g_free (text);
return 0;
}
void
inbound_action (session *sess, char *chan, char *from, char *ip, char *text,
int fromme, int id, const message_tags_data *tags_data)
{
session *def = sess;
server *serv = sess->server;
struct User *user;
char nickchar[2] = "\000";
char idtext[64];
int privaction = FALSE;
if (!fromme)
{
if (is_channel (serv, chan))
{
sess = find_channel (serv, chan);
} else
{
/* it's a private action! */
privaction = TRUE;
/* find a dialog tab for it */
sess = find_dialog (serv, from);
/* if non found, open a new one */
if (!sess && prefs.hex_gui_autoopen_dialog)
{
/* but only if it wouldn't flood */
if (flood_check (from, ip, serv, current_sess, 1))
sess = inbound_open_dialog (serv, from, tags_data);
else
sess = serv->server_session;
}
if (!sess)
{
sess = find_session_from_nick (from, serv);
/* still not good? */
if (!sess)
sess = serv->front_session;
}
}
}
if (!sess)
sess = def;
if (sess != current_tab)
{
if (fromme)
sess->tab_state |= TAB_STATE_NEW_DATA;
else
sess->tab_state |= TAB_STATE_NEW_MSG;
lastact_update (sess);
}
user = userlist_find (sess, from);
if (user)
{
nickchar[0] = user->prefix[0];
user->lasttalk = time (0);
if (user->account)
id = TRUE;
if (user->me)
fromme = TRUE;
}
inbound_make_idtext (serv, idtext, sizeof (idtext), id);
if (!fromme && !privaction)
{
if (is_hilight (from, text, sess, serv))
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_HCHANACTION, sess, from, text, nickchar,
idtext, 0, tags_data->timestamp);
return;
}
}
if (fromme)
EMIT_SIGNAL_TIMESTAMP (XP_TE_UACTION, sess, from, text, nickchar, idtext,
0, tags_data->timestamp);
else if (!privaction)
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANACTION, sess, from, text, nickchar,
idtext, 0, tags_data->timestamp);
else if (sess->type == SESS_DIALOG)
EMIT_SIGNAL_TIMESTAMP (XP_TE_DPRIVACTION, sess, from, text, idtext, NULL,
0, tags_data->timestamp);
else
EMIT_SIGNAL_TIMESTAMP (XP_TE_PRIVACTION, sess, from, text, idtext, NULL, 0,
tags_data->timestamp);
}
void
inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
char *text, char fromme, int id,
const message_tags_data *tags_data)
{
struct User *user;
int hilight = FALSE;
char nickchar[2] = "\000";
char idtext[64];
if (!sess)
{
if (chan)
{
sess = find_channel (serv, chan);
if (!sess && !is_channel (serv, chan))
sess = find_dialog (serv, chan);
} else
{
sess = find_dialog (serv, from);
}
if (!sess)
return;
}
if (sess != current_tab)
{
sess->tab_state |= TAB_STATE_NEW_MSG;
lastact_update (sess);
}
user = userlist_find (sess, from);
if (user)
{
if (user->account)
id = TRUE;
nickchar[0] = user->prefix[0];
user->lasttalk = time (0);
if (user->me)
fromme = TRUE;
}
if (fromme)
{
if (prefs.hex_away_auto_unmark && serv->is_away && !tags_data->timestamp)
sess->server->p_set_back (sess->server);
EMIT_SIGNAL_TIMESTAMP (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL,
0, tags_data->timestamp);
return;
}
inbound_make_idtext (serv, idtext, sizeof (idtext), id);
if (is_hilight (from, text, sess, serv))
hilight = TRUE;
if (sess->type == SESS_DIALOG)
EMIT_SIGNAL_TIMESTAMP (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0,
tags_data->timestamp);
else if (hilight)
EMIT_SIGNAL_TIMESTAMP (XP_TE_HCHANMSG, sess, from, text, nickchar, idtext,
0, tags_data->timestamp);
else
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANMSG, sess, from, text, nickchar, idtext,
0, tags_data->timestamp);
}
void
inbound_newnick (server *serv, char *nick, char *newnick, int quiet,
const message_tags_data *tags_data)
{
int me = FALSE;
session *sess;
GSList *list = sess_list;
if (!serv->p_cmp (nick, serv->nick))
{
me = TRUE;
safe_strcpy (serv->nick, newnick, NICKLEN);
}
while (list)
{
sess = list->data;
if (sess->server == serv)
{
if (userlist_change (sess, nick, newnick) || (me && sess->type == SESS_SERVER))
{
if (!quiet)
{
if (me)
EMIT_SIGNAL_TIMESTAMP (XP_TE_UCHANGENICK, sess, nick,
newnick, NULL, NULL, 0,
tags_data->timestamp);
else
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANGENICK, sess, nick,
newnick, NULL, NULL, 0, tags_data->timestamp);
}
}
if (sess->type == SESS_DIALOG && !serv->p_cmp (sess->channel, nick))
{
safe_strcpy (sess->channel, newnick, CHANLEN);
fe_set_channel (sess);
}
fe_set_title (sess);
}
list = list->next;
}
dcc_change_nick (serv, nick, newnick);
if (me)
fe_set_nick (serv, newnick);
}
/* find a "<none>" tab */
static session *
find_unused_session (server *serv)
{
session *sess;
GSList *list = sess_list;
while (list)
{
sess = (session *) list->data;
if (sess->type == SESS_CHANNEL && sess->channel[0] == 0 &&
sess->server == serv)
{
if (sess->waitchannel[0] == 0)
return sess;
}
list = list->next;
}
return NULL;
}
static session *
find_session_from_waitchannel (char *chan, struct server *serv)
{
session *sess;
GSList *list = sess_list;
while (list)
{
sess = (session *) list->data;
if (sess->server == serv && sess->channel[0] == 0 && sess->type == SESS_CHANNEL)
{
if (!serv->p_cmp (chan, sess->waitchannel))
return sess;
}
list = list->next;
}
return NULL;
}
void
inbound_ujoin (server *serv, char *chan, char *nick, char *ip,
const message_tags_data *tags_data)
{
session *sess;
int found_unused = FALSE;
/* already joined? probably a bnc */
sess = find_channel (serv, chan);
if (!sess)
{
/* see if a window is waiting to join this channel */
sess = find_session_from_waitchannel (chan, serv);
if (!sess)
{
/* find a "<none>" tab and use that */
sess = find_unused_session (serv);
found_unused = sess != NULL;
if (!sess)
/* last resort, open a new tab/window */
sess = new_ircwindow (serv, chan, SESS_CHANNEL, 1);
}
}
safe_strcpy (sess->channel, chan, CHANLEN);
if (found_unused)
{
chanopt_load (sess);
scrollback_load (sess);
if (sess->scrollwritten && sess->scrollback_replay_marklast)
sess->scrollback_replay_marklast (sess);
}
fe_set_channel (sess);
fe_set_title (sess);
fe_set_nonchannel (sess, TRUE);
userlist_clear (sess);
log_open_or_close (sess);
sess->waitchannel[0] = 0;
sess->ignore_date = TRUE;
sess->ignore_mode = TRUE;
sess->ignore_names = TRUE;
sess->end_of_names = FALSE;
/* sends a MODE */
serv->p_join_info (sess->server, chan);
EMIT_SIGNAL_TIMESTAMP (XP_TE_UJOIN, sess, nick, chan, ip, NULL, 0,
tags_data->timestamp);
if (prefs.hex_irc_who_join)
{
/* sends WHO #channel */
serv->p_user_list (sess->server, chan);
sess->doing_who = TRUE;
}
}
void
inbound_ukick (server *serv, char *chan, char *kicker, char *reason,
const message_tags_data *tags_data)
{
session *sess = find_channel (serv, chan);
if (sess)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_UKICK, sess, serv->nick, chan, kicker,
reason, 0, tags_data->timestamp);
clear_channel (sess);
if (prefs.hex_irc_auto_rejoin)
{
serv->p_join (serv, chan, sess->channelkey);
safe_strcpy (sess->waitchannel, chan, CHANLEN);
}
}
}
void
inbound_upart (server *serv, char *chan, char *ip, char *reason,
const message_tags_data *tags_data)
{
session *sess = find_channel (serv, chan);
if (sess)
{
if (*reason)
EMIT_SIGNAL_TIMESTAMP (XP_TE_UPARTREASON, sess, serv->nick, ip, chan,
reason, 0, tags_data->timestamp);
else
EMIT_SIGNAL_TIMESTAMP (XP_TE_UPART, sess, serv->nick, ip, chan, NULL,
0, tags_data->timestamp);
clear_channel (sess);
}
}
void
inbound_nameslist (server *serv, char *chan, char *names,
const message_tags_data *tags_data)
{
session *sess;
char **name_list;
char *host, *nopre_name;
char name[NICKLEN];
int i;
size_t offset;
sess = find_channel (serv, chan);
if (!sess)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_USERSONCHAN, serv->server_session, chan,
names, NULL, NULL, 0, tags_data->timestamp);
return;
}
if (!sess->ignore_names)
EMIT_SIGNAL_TIMESTAMP (XP_TE_USERSONCHAN, sess, chan, names, NULL, NULL,
0, tags_data->timestamp);
if (sess->end_of_names)
{
sess->end_of_names = FALSE;
userlist_clear (sess);
}
name_list = g_strsplit (names, " ", -1);
for (i = 0; name_list[i]; i++)
{
host = NULL;
offset = sizeof(name);
if (name_list[i][0] == 0)
continue;
if (serv->have_uhnames)
{
offset = 0;
nopre_name = name_list[i];
/* Ignore prefixes so '!' won't cause issues */
while (strchr (serv->nick_prefixes, *nopre_name) != NULL)
{
nopre_name++;
offset++;
}
offset += strcspn (nopre_name, "!");
if (offset++ < strlen (name_list[i]))
host = name_list[i] + offset;
}
g_strlcpy (name, name_list[i], MIN(offset, sizeof(name)));
userlist_add (sess, name, host, NULL, NULL, tags_data);
}
g_strfreev (name_list);
}
void
inbound_topic (server *serv, char *chan, char *topic_text,
const message_tags_data *tags_data)
{
session *sess = find_channel (serv, chan);
char *stripped_topic;
if (sess)
{
stripped_topic = strip_color (topic_text, -1, STRIP_ALL);
set_topic (sess, topic_text, stripped_topic);
g_free (stripped_topic);
} else
sess = serv->server_session;
EMIT_SIGNAL_TIMESTAMP (XP_TE_TOPIC, sess, chan, topic_text, NULL, NULL, 0,
tags_data->timestamp);
}
void
inbound_topicnew (server *serv, char *nick, char *chan, char *topic,
const message_tags_data *tags_data)
{
session *sess;
char *stripped_topic;
sess = find_channel (serv, chan);
if (sess)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_NEWTOPIC, sess, nick, topic, chan, NULL, 0,
tags_data->timestamp);
stripped_topic = strip_color (topic, -1, STRIP_ALL);
set_topic (sess, topic, stripped_topic);
g_free (stripped_topic);
}
}
void
inbound_join (server *serv, char *chan, char *user, char *ip, char *account,
char *realname, const message_tags_data *tags_data)
{
session *sess = find_channel (serv, chan);
if (sess)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_JOIN, sess, user, chan, ip, account, 0,
tags_data->timestamp);
userlist_add (sess, user, ip, account, realname, tags_data);
}
}
void
inbound_kick (server *serv, char *chan, char *user, char *kicker, char *reason,
const message_tags_data *tags_data)
{
session *sess = find_channel (serv, chan);
if (sess)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_KICK, sess, kicker, user, chan, reason, 0,
tags_data->timestamp);
userlist_remove (sess, user);
}
}
void
inbound_part (server *serv, char *chan, char *user, char *ip, char *reason,
const message_tags_data *tags_data)
{
session *sess = find_channel (serv, chan);
if (sess)
{
if (*reason)
EMIT_SIGNAL_TIMESTAMP (XP_TE_PARTREASON, sess, user, ip, chan, reason,
0, tags_data->timestamp);
else
EMIT_SIGNAL_TIMESTAMP (XP_TE_PART, sess, user, ip, chan, NULL, 0,
tags_data->timestamp);
userlist_remove (sess, user);
}
}
void
inbound_topictime (server *serv, char *chan, char *nick, time_t stamp,
const message_tags_data *tags_data)
{
char *tim = ctime (&stamp);
session *sess = find_channel (serv, chan);
if (!sess)
sess = serv->server_session;
if (tim != NULL)
tim[24] = 0; /* get rid of the \n */
EMIT_SIGNAL_TIMESTAMP (XP_TE_TOPICDATE, sess, chan, nick, tim, NULL, 0,
tags_data->timestamp);
}
void
inbound_quit (server *serv, char *nick, char *ip, char *reason,
const message_tags_data *tags_data)
{
GSList *list = sess_list;
session *sess;
struct User *user;
int was_on_front_session = FALSE;
while (list)
{
sess = (session *) list->data;
if (sess->server == serv)
{
if (sess == current_sess)
was_on_front_session = TRUE;
if ((user = userlist_find (sess, nick)))
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_QUIT, sess, nick, reason, ip, NULL, 0,
tags_data->timestamp);
userlist_remove_user (sess, user);
} else if (sess->type == SESS_DIALOG && !serv->p_cmp (sess->channel, nick))
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_QUIT, sess, nick, reason, ip, NULL, 0,
tags_data->timestamp);
}
}
list = list->next;
}
notify_set_offline (serv, nick, was_on_front_session, tags_data);
}
void
inbound_account (server *serv, char *nick, char *account,
const message_tags_data *tags_data)
{
session *sess = NULL;
GSList *list;
list = sess_list;
while (list)
{
sess = list->data;
if (sess->server == serv)
userlist_set_account (sess, nick, account);
list = list->next;
}
}
void
inbound_ping_reply (session *sess, char *timestring, char *from,
const message_tags_data *tags_data)
{
unsigned long tim, nowtim, dif;
int lag = 0;
char outbuf[64];
if (strncmp (timestring, "LAG", 3) == 0)
{
timestring += 3;
lag = 1;
}
tim = strtoul (timestring, NULL, 10);
nowtim = make_ping_time ();
dif = nowtim - tim;
sess->server->ping_recv = time (0);
if (lag)
{
sess->server->lag_sent = 0;
sess->server->lag = dif;
fe_set_lag (sess->server, dif);
return;
}
if (atol (timestring) == 0)
{
if (sess->server->lag_sent)
sess->server->lag_sent = 0;
else
EMIT_SIGNAL_TIMESTAMP (XP_TE_PINGREP, sess, from, "?", NULL, NULL, 0,
tags_data->timestamp);
} else
{
g_snprintf (outbuf, sizeof (outbuf), "%ld.%03ld", dif / 1000, dif % 1000);
EMIT_SIGNAL_TIMESTAMP (XP_TE_PINGREP, sess, from, outbuf, NULL, NULL, 0,
tags_data->timestamp);
}
}
static session *
find_session_from_type (int type, server *serv)
{
session *sess;
GSList *list = sess_list;
while (list)
{
sess = list->data;
if (sess->type == type && serv == sess->server)
return sess;
list = list->next;
}
return NULL;
}
void
inbound_notice (server *serv, char *to, char *nick, char *msg, char *ip, int id,
const message_tags_data *tags_data)
{
char *ptr = to;
session *sess = 0;
int server_notice = FALSE;
if (is_channel (serv, ptr))
sess = find_channel (serv, ptr);
/* /notice [mode-prefix]#channel should end up in that channel */
if (!sess && ptr[0] && strchr(serv->nick_prefixes, ptr[0]) != NULL)
{
ptr++;
sess = find_channel (serv, ptr);
}
if (strcmp (nick, ip) == 0)
server_notice = TRUE;
if (!sess)
{
ptr = 0;
if (prefs.hex_irc_notice_pos == 0)
{
/* paranoia check */
if (msg[0] == '[' && (!serv->have_idmsg || id))
{
/* guess where chanserv meant to post this -sigh- */
if (!g_ascii_strcasecmp (nick, "ChanServ") && !find_dialog (serv, nick))
{
char *dest = g_strdup (msg + 1);
char *end = strchr (dest, ']');
if (end)
{
*end = 0;
sess = find_channel (serv, dest);
}
g_free (dest);
}
}
if (!sess)
sess = find_session_from_nick (nick, serv);
} else if (prefs.hex_irc_notice_pos == 1)
{
int stype = server_notice ? SESS_SNOTICES : SESS_NOTICES;
sess = find_session_from_type (stype, serv);
if (!sess)
{
if (stype == SESS_NOTICES)
sess = new_ircwindow (serv, "(notices)", SESS_NOTICES, 0);
else
sess = new_ircwindow (serv, "(snotices)", SESS_SNOTICES, 0);
fe_set_channel (sess);
fe_set_title (sess);
fe_set_nonchannel (sess, FALSE);
userlist_clear (sess);
log_open_or_close (sess);
}
/* Avoid redundancy with some Undernet notices */
if (!strncmp (msg, "*** Notice -- ", 14))
msg += 14;
} else
{
sess = serv->front_session;
}
if (!sess)
{
if (server_notice)
sess = serv->server_session;
else
sess = serv->front_session;
}
}
if (msg[0] == '\001')
{
size_t len;
msg++;
if (!strncmp (msg, "PING", 4))
{
inbound_ping_reply (sess, msg + 5, nick, tags_data);
return;
}
len = strlen(msg);
if (msg[len - 1] == '\001')
msg[len - 1] = '\000';
}
if (server_notice)
EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVNOTICE, sess, msg, nick, NULL, NULL, 0,
tags_data->timestamp);
else if (ptr)
EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANNOTICE, sess, nick, to, msg, NULL, 0,
tags_data->timestamp);
else
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTICE, sess, nick, msg, NULL, NULL, 0,
tags_data->timestamp);
}
void
inbound_away (server *serv, char *nick, char *msg,
const message_tags_data *tags_data)
{
struct away_msg *away = server_away_find_message (serv, nick);
session *sess = NULL;
GSList *list;
if (away && !strcmp (msg, away->message)) /* Seen the msg before? */
{
if (prefs.hex_away_show_once && !serv->inside_whois)
return;
} else
{
server_away_save_message (serv, nick, msg);
}
if (prefs.hex_irc_whois_front)
sess = serv->front_session;
else
{
if (!serv->inside_whois)
sess = find_session_from_nick (nick, serv);
if (!sess)
sess = serv->server_session;
}
/* possibly hide the output */
if (!serv->inside_whois || !serv->skip_next_whois)
EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS5, sess, nick, msg, NULL, NULL, 0,
tags_data->timestamp);
list = sess_list;
while (list)
{
sess = list->data;
if (sess->server == serv)
userlist_set_away (sess, nick, TRUE);
list = list->next;
}
}
void
inbound_away_notify (server *serv, char *nick, char *reason,
const message_tags_data *tags_data)
{
session *sess = NULL;
GSList *list;
list = sess_list;
while (list)
{
sess = list->data;
if (sess->server == serv)
{
userlist_set_away (sess, nick, reason ? TRUE : FALSE);
if (sess == serv->front_session && notify_is_in_list (serv, nick))
{
if (reason)
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYAWAY, sess, nick, reason, NULL,
NULL, 0, tags_data->timestamp);
else
EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTIFYBACK, sess, nick, NULL, NULL,
NULL, 0, tags_data->timestamp);
}
}
list = list->next;
}
}
int
inbound_nameslist_end (server *serv, char *chan,
const message_tags_data *tags_data)
{
session *sess;
GSList *list;
if (!strcmp (chan, "*"))
{
list = sess_list;
while (list)
{
sess = list->data;
if (sess->server == serv)
{
sess->end_of_names = TRUE;
sess->ignore_names = FALSE;
fe_userlist_numbers (sess);
}
list = list->next;
}
return TRUE;
}
sess = find_channel (serv, chan);
if (sess)
{
sess->end_of_names = TRUE;
sess->ignore_names = FALSE;
fe_userlist_numbers (sess);
return TRUE;
}
return FALSE;
}
static gboolean
check_autojoin_channels (server *serv)
{
int i = 0;
session *sess;
GSList *list = sess_list;
GSList *sess_channels = NULL; /* joined channels that are not in the favorites list */
favchannel *fav;
/* shouldn't really happen, the io tag is destroyed in server.c */
if (!is_server (serv))
{
return FALSE;
}
/* If there's a session (i.e. this is a reconnect), autojoin to everything that was open previously. */
while (list)
{
sess = list->data;
if (sess->server == serv)
{
if (sess->willjoinchannel[0] != 0)
{
strcpy (sess->waitchannel, sess->willjoinchannel);
sess->willjoinchannel[0] = 0;
fav = servlist_favchan_find (serv->network, sess->waitchannel, NULL); /* Is this channel in our favorites? */
/* session->channelkey is initially unset for channels joined from the favorites. You have to fill them up manually from favorites settings. */
if (fav)
{
/* session->channelkey is set if there was a key change during the session. In that case, use the session key, not the one from favorites. */
if (fav->key && !strlen (sess->channelkey))
{
safe_strcpy (sess->channelkey, fav->key, sizeof (sess->channelkey));
}
}
/* for easier checks, ensure that favchannel->key is just NULL when session->channelkey is empty i.e. '' */
if (strlen (sess->channelkey))
{
sess_channels = servlist_favchan_listadd (sess_channels, sess->waitchannel, sess->channelkey);
}
else
{
sess_channels = servlist_favchan_listadd (sess_channels, sess->waitchannel, NULL);
}
i++;
}
}
list = list->next;
}
if (sess_channels)
{
serv->p_join_list (serv, sess_channels);
g_slist_free_full (sess_channels, (GDestroyNotify) servlist_favchan_free);
}
else
{
/* If there's no session, just autojoin to favorites. */
if (serv->favlist)
{
serv->p_join_list (serv, serv->favlist);
i++;
/* FIXME this is not going to work and is not needed either. server_free() does the job already. */
/* g_slist_free_full (serv->favlist, (GDestroyNotify) servlist_favchan_free); */
}
}
serv->joindelay_tag = 0;
fe_server_event (serv, FE_SE_LOGGEDIN, i);
return FALSE;
}
void
inbound_next_nick (session *sess, char *nick, int error,
const message_tags_data *tags_data)
{
char *newnick;
server *serv = sess->server;
ircnet *net;
serv->nickcount++;
switch (serv->nickcount)
{
case 2:
newnick = prefs.hex_irc_nick2;
net = serv->network;
/* use network specific "Second choice"? */
if (net && !(net->flags & FLAG_USE_GLOBAL) && net->nick2)
{
newnick = net->nick2;
}
serv->p_change_nick (serv, newnick);
if (error)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_NICKERROR, sess, nick, newnick, NULL, NULL,
0, tags_data->timestamp);
}
else
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_NICKCLASH, sess, nick, newnick, NULL, NULL,
0, tags_data->timestamp);
}
break;
case 3:
serv->p_change_nick (serv, prefs.hex_irc_nick3);
if (error)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_NICKERROR, sess, nick, prefs.hex_irc_nick3,
NULL, NULL, 0, tags_data->timestamp);
}
else
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_NICKCLASH, sess, nick, prefs.hex_irc_nick3,
NULL, NULL, 0, tags_data->timestamp);
}
break;
default:
EMIT_SIGNAL_TIMESTAMP (XP_TE_NICKFAIL, sess, NULL, NULL, NULL, NULL, 0,
tags_data->timestamp);
}
}
static void
dns_addr_callback (GObject *obj, GAsyncResult *result, gpointer user_data)
{
GResolver *resolver = G_RESOLVER(obj);
session *sess = (session*)user_data;
gchar *addr;
g_return_if_fail (is_session(sess));
addr = g_resolver_lookup_by_address_finish (resolver, result, NULL);
if (addr)
PrintTextf (sess, _("Resolved to %s"), addr);
else
PrintText (sess, _("Not found"));
}
static void
dns_name_callback (GObject *obj, GAsyncResult *result, gpointer user_data)
{
GResolver *resolver = G_RESOLVER(obj);
session *sess = (session*)user_data;
GList* addrs;
gchar* addr;
GList* list;
g_return_if_fail (is_session (sess));
addrs = g_resolver_lookup_by_name_finish (resolver, result, NULL);
if (addrs)
{
PrintText (sess, _("Resolved to:"));
for (list = g_list_first (addrs); list; list = g_list_next (list))
{
addr = g_inet_address_to_string (list->data);
PrintTextf (sess, " %s", addr);
}
g_resolver_free_addresses (addrs);
}
else
PrintText (sess, _("Not found"));
}
void
do_dns (session *sess, char *nick, char *host,
const message_tags_data *tags_data)
{
GResolver *res = g_resolver_get_default ();
GInetAddress *addr;
char *po;
po = strrchr (host, '@');
if (po)
host = po + 1;
if (nick)
EMIT_SIGNAL_TIMESTAMP (XP_TE_RESOLVINGUSER, sess, nick, host, NULL, NULL, 0,
tags_data->timestamp);
PrintTextf (sess, _("Looking up %s..."), host);
addr = g_inet_address_new_from_string (host);
if (addr)
g_resolver_lookup_by_address_async (res, addr, NULL, dns_addr_callback, sess);
else
g_resolver_lookup_by_name_async (res, host, NULL, dns_name_callback, sess);
}
static void
set_default_modes (server *serv)
{
char modes[8];
modes[0] = '+';
modes[1] = '\0';
if (prefs.hex_irc_wallops)
strcat (modes, "w");
if (prefs.hex_irc_servernotice)
strcat (modes, "s");
if (prefs.hex_irc_invisible)
strcat (modes, "i");
if (prefs.hex_irc_hidehost)
strcat (modes, "x");
if (modes[1] != '\0')
{
serv->p_mode (serv, serv->nick, modes);
}
}
void
inbound_login_start (session *sess, char *nick, char *servname,
const message_tags_data *tags_data)
{
inbound_newnick (sess->server, sess->server->nick, nick, TRUE, tags_data);
server_set_name (sess->server, servname);
if (sess->type == SESS_SERVER)
log_open_or_close (sess);
/* reset our away status */
if (sess->server->reconnect_away)
{
handle_command (sess->server->server_session, "away", FALSE);
sess->server->reconnect_away = FALSE;
}
}
static void
inbound_set_all_away_status (server *serv, char *nick, unsigned int status)
{
GSList *list;
session *sess;
list = sess_list;
while (list)
{
sess = list->data;
if (sess->server == serv)
userlist_set_away (sess, nick, status);
list = list->next;
}
}
void
inbound_uaway (server *serv, const message_tags_data *tags_data)
{
serv->is_away = TRUE;
serv->away_time = time (NULL);
fe_set_away (serv);
inbound_set_all_away_status (serv, serv->nick, 1);
}
void
inbound_uback (server *serv, const message_tags_data *tags_data)
{
serv->is_away = FALSE;
serv->reconnect_away = FALSE;
fe_set_away (serv);
inbound_set_all_away_status (serv, serv->nick, 0);
}
void
inbound_foundip (session *sess, char *ip, const message_tags_data *tags_data)
{
struct hostent *HostAddr;
HostAddr = gethostbyname (ip);
if (HostAddr)
{
sess->server->dcc_ip = ((struct in_addr *) HostAddr->h_addr)->s_addr;
EMIT_SIGNAL_TIMESTAMP (XP_TE_FOUNDIP, sess->server->server_session,
inet_ntoa (*((struct in_addr *) HostAddr->h_addr)),
NULL, NULL, NULL, 0, tags_data->timestamp);
}
}
void
inbound_user_info_start (session *sess, char *nick,
const message_tags_data *tags_data)
{
/* set away to FALSE now, 301 may turn it back on */
inbound_set_all_away_status (sess->server, nick, 0);
}
/* reporting new information found about this user. chan may be NULL.
* away may be 0xff to indicate UNKNOWN. */
void
inbound_user_info (session *sess, char *chan, char *user, char *host,
char *servname, char *nick, char *realname,
char *account, unsigned int away,
const message_tags_data *tags_data)
{
server *serv = sess->server;
session *who_sess;
GSList *list;
char *uhost = NULL;
if (user && host)
{
uhost = g_strdup_printf ("%s@%s", user, host);
}
if (chan)
{
who_sess = find_channel (serv, chan);
if (who_sess)
userlist_add_hostname (who_sess, nick, uhost, realname, servname, account, away);
else
{
if (serv->doing_dns && nick && host)
do_dns (sess, nick, host, tags_data);
}
}
else
{
/* came from WHOIS, not channel specific */
for (list = sess_list; list; list = list->next)
{
sess = list->data;
if (sess->server != serv)
continue;
if (sess->type == SESS_CHANNEL)
{
userlist_add_hostname (sess, nick, uhost, realname, servname, account, away);
}
else if (sess->type == SESS_DIALOG && uhost && !serv->p_cmp (sess->channel, nick))
{
set_topic (sess, uhost, uhost);
}
}
}
g_free (uhost);
}
int
inbound_banlist (session *sess, time_t stamp, char *chan, char *mask,
char *banner, int rplcode, const message_tags_data *tags_data)
{
char *time_str = ctime (&stamp);
server *serv = sess->server;
char *nl;
if (stamp <= 0 || time_str == NULL)
{
time_str = "";
}
else
{
if ((nl = strchr (time_str, '\n')))
*nl = 0;
}
sess = find_channel (serv, chan);
if (!sess)
{
sess = serv->front_session;
goto nowindow;
}
if (!fe_add_ban_list (sess, mask, banner, time_str, rplcode))
{
nowindow:
EMIT_SIGNAL_TIMESTAMP (XP_TE_BANLIST, sess, chan, mask, banner, time_str,
0, tags_data->timestamp);
return TRUE;
}
return TRUE;
}
/* execute 1 end-of-motd command */
static int
inbound_exec_eom_cmd (char *str, void *sess)
{
char *cmd;
cmd = command_insert_vars ((session*)sess, (str[0] == '/') ? str + 1 : str);
handle_command ((session*)sess, cmd, TRUE);
g_free (cmd);
return 1;
}
static int
inbound_nickserv_login (server *serv)
{
/* this could grow ugly, but let's hope there won't be new NickServ types */
switch (serv->loginmethod)
{
case LOGIN_MSG_NICKSERV:
case LOGIN_NICKSERV:
case LOGIN_CHALLENGEAUTH:
#if 0
case LOGIN_NS:
case LOGIN_MSG_NS:
case LOGIN_AUTH:
#endif
return 1;
default:
return 0;
}
}
void
inbound_login_end (session *sess, char *text, const message_tags_data *tags_data)
{
GSList *cmdlist;
commandentry *cmd;
server *serv = sess->server;
ircnet *net = serv->network;
if (!serv->end_of_motd)
{
if (prefs.hex_dcc_ip_from_server && serv->use_who)
{
serv->skip_next_userhost = TRUE;
serv->p_get_ip_uh (serv, serv->nick); /* sends USERHOST mynick */
}
set_default_modes (serv);
if (net)
{
/* there may be more than 1, separated by \n */
cmdlist = net->commandlist;
while (cmdlist)
{
cmd = cmdlist->data;
inbound_exec_eom_cmd (cmd->command, sess);
cmdlist = cmdlist->next;
}
}
/* The previously ran commands can alter the state of the server */
if (serv->network != net)
return;
/* send nickserv password */
if (net && net->pass && inbound_nickserv_login (serv))
{
serv->p_ns_identify (serv, net->pass);
}
/* wait for join if command or nickserv set */
if (net && prefs.hex_irc_join_delay
&& ((net->pass && inbound_nickserv_login (serv))
|| net->commandlist))
{
serv->joindelay_tag = fe_timeout_add_seconds (prefs.hex_irc_join_delay, check_autojoin_channels, serv);
}
else
{
check_autojoin_channels (serv);
}
if (serv->supports_watch || serv->supports_monitor)
{
notify_send_watches (serv);
}
serv->end_of_motd = TRUE;
}
if (prefs.hex_irc_skip_motd && !serv->motd_skipped)
{
serv->motd_skipped = TRUE;
EMIT_SIGNAL_TIMESTAMP (XP_TE_MOTDSKIP, serv->server_session, NULL, NULL,
NULL, NULL, 0, tags_data->timestamp);
return;
}
EMIT_SIGNAL_TIMESTAMP (XP_TE_MOTD, serv->server_session, text, NULL, NULL,
NULL, 0, tags_data->timestamp);
}
void
inbound_identified (server *serv) /* 'MODE +e MYSELF' on freenode */
{
if (serv->joindelay_tag)
{
/* stop waiting, just auto JOIN now */
fe_timeout_remove (serv->joindelay_tag);
serv->joindelay_tag = 0;
check_autojoin_channels (serv);
}
}
static const char *sasl_mechanisms[] =
{
"PLAIN",
"EXTERNAL",
"SCRAM-SHA-1",
"SCRAM-SHA-256",
"SCRAM-SHA-512"
};
static void
inbound_toggle_caps (server *serv, const char *extensions_str, gboolean enable)
{
char **extensions;
gsize i;
extensions = g_strsplit (extensions_str, " ", 0);
for (i = 0; extensions[i]; i++)
{
const char *extension = extensions[i];
if (!strcmp (extension, "solanum.chat/identify-msg"))
serv->have_idmsg = enable;
else if (!strcmp (extension, "multi-prefix"))
serv->have_namesx = enable;
else if (!strcmp (extension, "account-notify"))
serv->have_accnotify = enable;
else if (!strcmp (extension, "extended-join"))
serv->have_extjoin = enable;
else if (!strcmp (extension, "userhost-in-names"))
serv->have_uhnames = enable;
else if (!strcmp (extension, "server-time")
|| !strcmp (extension, "znc.in/server-time")
|| !strcmp (extension, "znc.in/server-time-iso"))
serv->have_server_time = enable;
else if (!strcmp (extension, "away-notify"))
serv->have_awaynotify = enable;
else if (!strcmp (extension, "account-tag"))
serv->have_account_tag = enable;
else if (!strcmp (extension, "sasl"))
{
serv->have_sasl = enable;
if (enable)
{
#ifdef USE_OPENSSL
if (serv->loginmethod == LOGIN_SASLEXTERNAL)
serv->sasl_mech = MECH_EXTERNAL;
else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_1)
serv->sasl_mech = MECH_SCRAM_SHA_1;
else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_256)
serv->sasl_mech = MECH_SCRAM_SHA_256;
else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_512)
serv->sasl_mech = MECH_SCRAM_SHA_512;
#endif
/* Mechanism either defaulted to PLAIN or server gave us list */
tcp_sendf (serv, "AUTHENTICATE %s\r\n", sasl_mechanisms[serv->sasl_mech]);
}
}
}
g_strfreev (extensions);
}
void
inbound_cap_ack (server *serv, char *nick, char *extensions,
const message_tags_data *tags_data)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPACK, serv->server_session, nick, extensions,
NULL, NULL, 0, tags_data->timestamp);
inbound_toggle_caps (serv, extensions, TRUE);
}
void
inbound_cap_del (server *serv, char *nick, char *extensions,
const message_tags_data *tags_data)
{
if (extensions)
{
char **tokens = g_strsplit (extensions, " ", 0);
int i;
for (i = 0; tokens[i]; i++)
{
if (!g_strcmp0 (tokens[i], "sts") ||
g_str_has_prefix (tokens[i], "sts="))
{
/* STS cannot be disabled via CAP DEL. */
g_strfreev (tokens);
return;
}
}
g_strfreev (tokens);
}
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPDEL, serv->server_session, nick, extensions,
NULL, NULL, 0, tags_data->timestamp);
inbound_toggle_caps (serv, extensions, FALSE);
}
static const char * const supported_caps[] = {
/* IRCv3.1 */
"multi-prefix",
"away-notify",
"account-notify",
"extended-join",
/* "sasl", Handled manually */
/* IRCv3.2 */
"server-time",
"userhost-in-names",
"cap-notify",
"chghost",
"setname",
"invite-notify",
"account-tag",
"extended-monitor",
"standard-replies",
/* ZNC */
"znc.in/server-time-iso",
"znc.in/server-time",
/* Twitch */
"twitch.tv/membership",
/* Solanum */
"solanum.chat/identify-msg",
};
static int
get_supported_mech (server *serv, const char *list)
{
char **mechs = g_strsplit (list, ",", 0);
gsize i;
int ret = -1;
for (i = 0; mechs[i]; ++i)
{
#ifdef USE_OPENSSL
if (serv->loginmethod == LOGIN_SASLEXTERNAL)
{
if (!strcmp (mechs[i], "EXTERNAL"))
{
ret = MECH_EXTERNAL;
break;
}
}
else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_1)
{
if (!strcmp(mechs[i], "SCRAM-SHA-1"))
{
ret = MECH_SCRAM_SHA_1;
break;
}
}
else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_256)
{
if (!strcmp(mechs[i], "SCRAM-SHA-256"))
{
ret = MECH_SCRAM_SHA_256;
break;
}
}
else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_512)
{
if (!strcmp(mechs[i], "SCRAM-SHA-512"))
{
ret = MECH_SCRAM_SHA_512;
break;
}
}
else
#endif
if (!strcmp (mechs[i], "PLAIN"))
{
ret = MECH_PLAIN;
break;
}
}
g_strfreev (mechs);
return ret;
}
void
inbound_cap_ls (server *serv, char *nick, char *extensions_str,
const message_tags_data *tags_data)
{
char buffer[500]; /* buffer for requesting capabilities and emitting the signal */
gboolean want_cap = FALSE; /* format the CAP REQ string based on previous capabilities being requested or not */
gboolean sts_upgrade_triggered = FALSE;
char **extensions;
int i;
if (g_str_has_prefix (extensions_str, "* "))
{
serv->waiting_on_cap = TRUE;
extensions_str += 2;
extensions_str += extensions_str[0] == ':' ? 1 : 0;
}
else
{
serv->waiting_on_cap = FALSE;
}
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPLIST, serv->server_session, nick,
extensions_str, NULL, NULL, 0, tags_data->timestamp);
extensions = g_strsplit (extensions_str, " ", 0);
strcpy (buffer, "CAP REQ :");
for (i=0; extensions[i]; i++)
{
char *extension = extensions[i];
char *value;
gsize x;
/* CAP 3.2 can provide values */
if ((value = strchr (extension, '=')))
{
*value = '\0';
value++;
}
if (!g_strcmp0 (extension, "sts"))
{
if (value)
{
sts_upgrade_triggered |= sts_handle_capability (serv, value);
}
continue;
}
/* if the SASL password is set AND auth mode is set to SASL, request SASL auth */
if (!g_strcmp0 (extension, "sasl") &&
(((serv->loginmethod == LOGIN_SASL
|| serv->loginmethod == LOGIN_SASL_SCRAM_SHA_1
|| serv->loginmethod == LOGIN_SASL_SCRAM_SHA_256
|| serv->loginmethod == LOGIN_SASL_SCRAM_SHA_512)
&& strlen (serv->password) != 0)
|| serv->loginmethod == LOGIN_SASLEXTERNAL))
{
if (value)
{
int sasl_mech = get_supported_mech (serv, value);
if (sasl_mech == -1) /* No supported mech */
continue;
serv->sasl_mech = sasl_mech;
}
want_cap = TRUE;
serv->waiting_on_sasl = TRUE;
g_strlcat (buffer, "sasl ", sizeof(buffer));
continue;
}
for (x = 0; x < G_N_ELEMENTS(supported_caps); ++x)
{
if (!g_strcmp0 (extension, supported_caps[x]))
{
g_strlcat (buffer, extension, sizeof(buffer));
g_strlcat (buffer, " ", sizeof(buffer));
want_cap = TRUE;
}
}
}
g_strfreev (extensions);
if (sts_upgrade_triggered)
{
return;
}
if (want_cap)
{
/* buffer + 9 = emit buffer without "CAP REQ :" */
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPREQ, serv->server_session,
buffer + 9, NULL, NULL, NULL, 0,
tags_data->timestamp);
tcp_sendf (serv, "%s\r\n", g_strchomp (buffer));
}
if (!serv->waiting_on_sasl && !serv->waiting_on_cap)
{
/* if we use SASL, CAP END is dealt via raw numerics */
serv->sent_capend = TRUE;
tcp_send_len (serv, "CAP END\r\n", 9);
}
}
void
inbound_cap_nak (server *serv, char *extensions_str, const message_tags_data *tags_data)
{
char **extensions;
int i;
extensions = g_strsplit (extensions_str, " ", 0);
for (i=0; extensions[i]; i++)
{
if (!g_strcmp0 (extensions[i], "sasl"))
serv->waiting_on_sasl = FALSE;
}
if (!serv->waiting_on_cap && !serv->waiting_on_sasl && !serv->sent_capend)
{
serv->sent_capend = TRUE;
tcp_send_len (serv, "CAP END\r\n", 9);
}
g_strfreev (extensions);
}
void
inbound_cap_list (server *serv, char *nick, char *extensions,
const message_tags_data *tags_data)
{
if (g_str_has_prefix (extensions, "* "))
{
extensions += 2;
extensions += extensions[0] == ':' ? 1 : 0;
}
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPACK, serv->server_session, nick, extensions,
NULL, NULL, 0, tags_data->timestamp);
}
static void
plain_authenticate (server *serv, char *user, char *password)
{
char *pass = encode_sasl_pass_plain (user, password);
if (pass == NULL)
{
/* something went wrong abort */
tcp_sendf (serv, "AUTHENTICATE *\r\n");
return;
}
/* long SASL passwords must be split into 400-byte chunks
https://ircv3.net/specs/extensions/sasl-3.1#the-authenticate-command */
size_t pass_len = strlen (pass);
if (pass_len <= 400)
tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass);
else
{
size_t sent = 0;
while (sent < pass_len)
{
char *pass_chunk = g_strndup (pass + sent, 400);
tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass_chunk);
sent += 400;
g_free (pass_chunk);
}
}
if (pass_len % 400 == 0)
tcp_sendf (serv, "AUTHENTICATE +\r\n");
}
#ifdef USE_OPENSSL
/*
* Sends AUTHENTICATE messages to log in via SCRAM.
*/
static void
scram_authenticate (server *serv, const char *data, const char *digest,
const char *user, const char *password)
{
char *encoded, *decoded, *output;
scram_status status;
size_t output_len;
gsize decoded_len;
if (serv->scram_session == NULL)
{
serv->scram_session = scram_session_create (digest, user, password);
if (serv->scram_session == NULL)
{
PrintTextf (serv->server_session, _("Could not create SCRAM session with digest %s"), digest);
g_warning ("Could not create SCRAM session with digest %s", digest);
tcp_sendf (serv, "AUTHENTICATE *\r\n");
return;
}
}
decoded = g_base64_decode (data, &decoded_len);
status = scram_process (serv->scram_session, decoded, &output, &output_len);
g_free (decoded);
if (status == SCRAM_IN_PROGRESS)
{
// Authentication is still in progress
encoded = g_base64_encode ((guchar *) output, output_len);
tcp_sendf (serv, "AUTHENTICATE %s\r\n", encoded);
g_free (encoded);
g_free (output);
}
else if (status == SCRAM_SUCCESS)
{
// Authentication succeeded
tcp_sendf (serv, "AUTHENTICATE +\r\n");
g_clear_pointer (&serv->scram_session, scram_session_free);
}
else if (status == SCRAM_ERROR)
{
// Authentication failed
tcp_sendf (serv, "AUTHENTICATE *\r\n");
if (serv->scram_session->error != NULL)
{
PrintTextf (serv->server_session, _("SASL SCRAM authentication failed: %s"), serv->scram_session->error);
g_info ("SASL SCRAM authentication failed: %s", serv->scram_session->error);
}
g_clear_pointer (&serv->scram_session, scram_session_free);
}
}
#endif
void
inbound_sasl_authenticate (server *serv, char *data)
{
ircnet *net = (ircnet*)serv->network;
char *user;
const char *mech = sasl_mechanisms[serv->sasl_mech];
/* Got a list of supported mechanisms from outdated inspircd
* just ignore it as it goes against spec */
if (strchr (data, ',') != NULL)
return;
if (net->user && !(net->flags & FLAG_USE_GLOBAL))
user = net->user;
else
user = prefs.hex_irc_user_name;
switch (serv->sasl_mech)
{
case MECH_PLAIN:
plain_authenticate(serv, user, serv->password);
break;
#ifdef USE_OPENSSL
case MECH_EXTERNAL:
tcp_sendf (serv, "AUTHENTICATE +\r\n");
break;
case MECH_SCRAM_SHA_1:
scram_authenticate(serv, data, "SHA1", user, serv->password);
return;
case MECH_SCRAM_SHA_256:
scram_authenticate(serv, data, "SHA256", user, serv->password);
return;
case MECH_SCRAM_SHA_512:
scram_authenticate(serv, data, "SHA512", user, serv->password);
return;
#endif
}
EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLAUTH, serv->server_session, user, (char*)mech,
NULL, NULL, 0, 0);
}
void
inbound_sasl_error (server *serv)
{
#ifdef USE_OPENSSL
g_clear_pointer (&serv->scram_session, scram_session_free);
#endif
/* Just abort, not much we can do */
tcp_sendf (serv, "AUTHENTICATE *\r\n");
}