|
|
|
|
@@ -1,7 +1,7 @@
|
|
|
|
|
/*
|
|
|
|
|
* degesch.c: a terminal-based IRC client
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2015 - 2020, Přemysl Eric Janouch <p@janouch.name>
|
|
|
|
|
* Copyright (c) 2015 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
|
|
|
|
*
|
|
|
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
|
* purpose with or without fee is hereby granted.
|
|
|
|
|
@@ -51,6 +51,7 @@ enum
|
|
|
|
|
#include "common.c"
|
|
|
|
|
#include "kike-replies.c"
|
|
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
#include <langinfo.h>
|
|
|
|
|
#include <locale.h>
|
|
|
|
|
#include <pwd.h>
|
|
|
|
|
@@ -1718,8 +1719,10 @@ struct server
|
|
|
|
|
char *irc_user_host; ///< Our current user@host
|
|
|
|
|
bool autoaway_active; ///< Autoaway is currently active
|
|
|
|
|
|
|
|
|
|
struct strv cap_ls_buf; ///< Buffer for IRCv3.2 CAP LS
|
|
|
|
|
bool cap_echo_message; ///< Whether the server echoes messages
|
|
|
|
|
bool cap_away_notify; ///< Whether we get AWAY notifications
|
|
|
|
|
bool cap_sasl; ///< Whether SASL is available
|
|
|
|
|
|
|
|
|
|
// Server-specific information (from RPL_ISUPPORT):
|
|
|
|
|
|
|
|
|
|
@@ -1841,6 +1844,7 @@ server_new (struct poller *poller)
|
|
|
|
|
|
|
|
|
|
self->irc_user_mode = str_make ();
|
|
|
|
|
|
|
|
|
|
self->cap_ls_buf = strv_make ();
|
|
|
|
|
server_init_specifics (self);
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
@@ -1887,6 +1891,7 @@ server_destroy (struct server *self)
|
|
|
|
|
str_free (&self->irc_user_mode);
|
|
|
|
|
free (self->irc_user_host);
|
|
|
|
|
|
|
|
|
|
strv_free (&self->cap_ls_buf);
|
|
|
|
|
server_free_specifics (self);
|
|
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
@@ -2104,17 +2109,23 @@ filter_color_cube_for_acceptable_nick_colors (size_t *len)
|
|
|
|
|
// This is a pure function and we don't use threads, static storage is fine
|
|
|
|
|
static int table[6 * 6 * 6];
|
|
|
|
|
size_t len_counter = 0;
|
|
|
|
|
for (int x = 0; x < 6 * 6 * 6; x++)
|
|
|
|
|
for (int x = 0; x < (int) N_ELEMENTS (table); x++)
|
|
|
|
|
{
|
|
|
|
|
// FIXME this isn't exactly right, the values aren't linear
|
|
|
|
|
int r = x / 36;
|
|
|
|
|
int g = (x / 6) % 6;
|
|
|
|
|
int b = (x % 6);
|
|
|
|
|
|
|
|
|
|
// Use the luma value of colours within the cube to filter colours that
|
|
|
|
|
// look okay-ish on terminals with both black and white backgrounds
|
|
|
|
|
double luma = 0.2126 * r / 6. + 0.7152 * g / 6. + 0.0722 * b / 6.;
|
|
|
|
|
if (luma >= .3 && luma <= .5)
|
|
|
|
|
// The first step is 95/255, the rest are 40/255,
|
|
|
|
|
// as an approximation we can double the first step
|
|
|
|
|
double linear_R = pow ((r + !!r) / 6., 2.2);
|
|
|
|
|
double linear_G = pow ((g + !!g) / 6., 2.2);
|
|
|
|
|
double linear_B = pow ((b + !!b) / 6., 2.2);
|
|
|
|
|
|
|
|
|
|
// Use the relative luminance of colours within the cube to filter
|
|
|
|
|
// colours that look okay-ish on terminals with both black and white
|
|
|
|
|
// backgrounds (use the test-nick-colors script to calibrate)
|
|
|
|
|
double Y = 0.2126 * linear_R + 0.7152 * linear_G + 0.0722 * linear_B;
|
|
|
|
|
if (Y >= .25 && Y <= .4)
|
|
|
|
|
table[len_counter++] = 16 + x;
|
|
|
|
|
}
|
|
|
|
|
*len = len_counter;
|
|
|
|
|
@@ -2337,7 +2348,7 @@ static struct config_schema g_config_server[] =
|
|
|
|
|
.type = CONFIG_ITEM_STRING_ARRAY,
|
|
|
|
|
.validate = config_validate_nonjunk_string,
|
|
|
|
|
.default_ = "\"multi-prefix,invite-notify,server-time,echo-message,"
|
|
|
|
|
"message-tags,away-notify\"" },
|
|
|
|
|
"message-tags,away-notify,cap-notify,chghost\"" },
|
|
|
|
|
|
|
|
|
|
{ .name = "tls",
|
|
|
|
|
.comment = "Whether to use TLS",
|
|
|
|
|
@@ -4031,6 +4042,13 @@ log_full (struct app_context *ctx, struct server *s, struct buffer *buffer,
|
|
|
|
|
log_server ((s), (buffer), BUFFER_LINE_STATUS | BUFFER_LINE_UNIMPORTANT, \
|
|
|
|
|
"#n is now known as #n", (old), (new_))
|
|
|
|
|
|
|
|
|
|
#define log_chghost_self(s, buffer, new_) \
|
|
|
|
|
log_server ((s), (buffer), BUFFER_LINE_STATUS | BUFFER_LINE_UNIMPORTANT, \
|
|
|
|
|
"You are now #N", (new_))
|
|
|
|
|
#define log_chghost(s, buffer, old, new_) \
|
|
|
|
|
log_server ((s), (buffer), BUFFER_LINE_STATUS | BUFFER_LINE_UNIMPORTANT, \
|
|
|
|
|
"#N is now #N", (old), (new_))
|
|
|
|
|
|
|
|
|
|
#define log_outcoming_notice(s, buffer, who, text) \
|
|
|
|
|
log_server_status ((s), (buffer), "#s(#n): #m", "Notice", (who), (text))
|
|
|
|
|
#define log_outcoming_privmsg(s, buffer, prefixes, who, text) \
|
|
|
|
|
@@ -4956,7 +4974,10 @@ irc_destroy_state (struct server *s)
|
|
|
|
|
str_reset (&s->irc_user_mode);
|
|
|
|
|
cstr_set (&s->irc_user_host, NULL);
|
|
|
|
|
|
|
|
|
|
strv_reset (&s->cap_ls_buf);
|
|
|
|
|
s->cap_away_notify = false;
|
|
|
|
|
s->cap_echo_message = false;
|
|
|
|
|
s->cap_sasl = false;
|
|
|
|
|
|
|
|
|
|
// Need to call this before server_init_specifics()
|
|
|
|
|
irc_set_casemapping (s, irc_tolower, irc_strxfrm);
|
|
|
|
|
@@ -5021,14 +5042,17 @@ irc_initiate_disconnect (struct server *s, const char *reason)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
initiate_quit (struct app_context *ctx, const char *message)
|
|
|
|
|
request_quit (struct app_context *ctx, const char *message)
|
|
|
|
|
{
|
|
|
|
|
log_global_status (ctx, "Shutting down");
|
|
|
|
|
if (!ctx->quitting)
|
|
|
|
|
{
|
|
|
|
|
log_global_status (ctx, "Shutting down");
|
|
|
|
|
ctx->quitting = true;
|
|
|
|
|
|
|
|
|
|
// Hide the user interface
|
|
|
|
|
CALL (ctx->input, hide);
|
|
|
|
|
// Disable the user interface
|
|
|
|
|
CALL (ctx->input, hide);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initiate a connection close
|
|
|
|
|
struct str_map_iter iter = str_map_iter_make (&ctx->servers);
|
|
|
|
|
struct server *s;
|
|
|
|
|
while ((s = str_map_iter_next (&iter)))
|
|
|
|
|
@@ -5042,7 +5066,6 @@ initiate_quit (struct app_context *ctx, const char *message)
|
|
|
|
|
irc_destroy_connector (s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx->quitting = true;
|
|
|
|
|
try_finish_quit (ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -5689,9 +5712,9 @@ irc_register (struct server *s)
|
|
|
|
|
const char *realname = get_config_string (s->config, "realname");
|
|
|
|
|
hard_assert (username && realname);
|
|
|
|
|
|
|
|
|
|
// Start IRCv3.1 capability negotiation;
|
|
|
|
|
// Start IRCv3 capability negotiation, with up to 3.2 features;
|
|
|
|
|
// at worst the server will ignore this or send a harmless error message
|
|
|
|
|
irc_send (s, "CAP LS");
|
|
|
|
|
irc_send (s, "CAP LS 302");
|
|
|
|
|
|
|
|
|
|
const char *password = get_config_string (s->config, "password");
|
|
|
|
|
if (password)
|
|
|
|
|
@@ -6398,11 +6421,11 @@ irc_handle_mode_user (struct server *s, char **params)
|
|
|
|
|
static void
|
|
|
|
|
irc_handle_sent_cap (struct server *s, const struct irc_message *msg)
|
|
|
|
|
{
|
|
|
|
|
if (msg->params.len < 2)
|
|
|
|
|
if (msg->params.len < 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const char *subcommand = msg->params.vector[1];
|
|
|
|
|
const char *args = (msg->params.len > 2) ? msg->params.vector[2] : "";
|
|
|
|
|
const char *subcommand = msg->params.vector[0];
|
|
|
|
|
const char *args = (msg->params.len > 1) ? msg->params.vector[1] : "";
|
|
|
|
|
if (!strcasecmp_ascii (subcommand, "REQ"))
|
|
|
|
|
log_server_status (s, s->buffer,
|
|
|
|
|
"#s: #S", "Capabilities requested", args);
|
|
|
|
|
@@ -6519,6 +6542,20 @@ irc_process_sent_message (const struct irc_message *msg, struct server *s)
|
|
|
|
|
|
|
|
|
|
// --- Input handling ----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
irc_handle_authenticate (struct server *s, const struct irc_message *msg)
|
|
|
|
|
{
|
|
|
|
|
if (msg->params.len < 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Empty challenge -> empty response for e.g. SASL EXTERNAL,
|
|
|
|
|
// abort anything else as it doesn't make much sense to let the user do it
|
|
|
|
|
if (!strcmp (msg->params.vector[0], "+"))
|
|
|
|
|
irc_send (s, "AUTHENTICATE +");
|
|
|
|
|
else
|
|
|
|
|
irc_send (s, "AUTHENTICATE *");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
irc_handle_away (struct server *s, const struct irc_message *msg)
|
|
|
|
|
{
|
|
|
|
|
@@ -6534,6 +6571,58 @@ irc_handle_away (struct server *s, const struct irc_message *msg)
|
|
|
|
|
user->away = !!msg->params.len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
irc_process_cap_ls (struct server *s)
|
|
|
|
|
{
|
|
|
|
|
log_server_status (s, s->buffer,
|
|
|
|
|
"#s: #&S", "Capabilities supported", strv_join (&s->cap_ls_buf, " "));
|
|
|
|
|
|
|
|
|
|
struct strv chosen = strv_make ();
|
|
|
|
|
struct strv use = strv_make ();
|
|
|
|
|
|
|
|
|
|
cstr_split (get_config_string (s->config, "capabilities"), ",", true, &use);
|
|
|
|
|
|
|
|
|
|
// Filter server capabilities for ones we can make use of
|
|
|
|
|
for (size_t i = 0; i < s->cap_ls_buf.len; i++)
|
|
|
|
|
{
|
|
|
|
|
const char *cap = s->cap_ls_buf.vector[i];
|
|
|
|
|
size_t cap_name_len = strcspn (cap, "=");
|
|
|
|
|
for (size_t k = 0; k < use.len; k++)
|
|
|
|
|
if (!strncasecmp_ascii (use.vector[k], cap, cap_name_len))
|
|
|
|
|
strv_append_owned (&chosen, xstrndup (cap, cap_name_len));
|
|
|
|
|
}
|
|
|
|
|
strv_reset (&s->cap_ls_buf);
|
|
|
|
|
|
|
|
|
|
char *chosen_str = strv_join (&chosen, " ");
|
|
|
|
|
strv_free (&chosen);
|
|
|
|
|
strv_free (&use);
|
|
|
|
|
|
|
|
|
|
// XXX: with IRCv3.2, this may end up being too long for one message,
|
|
|
|
|
// and we need to be careful with CAP END. One probably has to count
|
|
|
|
|
// the number of sent CAP REQ vs the number of received CAP ACK/NAK.
|
|
|
|
|
if (s->state == IRC_CONNECTED)
|
|
|
|
|
irc_send (s, "CAP REQ :%s", chosen_str);
|
|
|
|
|
|
|
|
|
|
free (chosen_str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
irc_toggle_cap (struct server *s, const char *cap, bool active)
|
|
|
|
|
{
|
|
|
|
|
if (!strcasecmp_ascii (cap, "echo-message")) s->cap_echo_message = active;
|
|
|
|
|
if (!strcasecmp_ascii (cap, "away-notify")) s->cap_away_notify = active;
|
|
|
|
|
if (!strcasecmp_ascii (cap, "sasl")) s->cap_sasl = active;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
irc_try_finish_cap_negotiation (struct server *s)
|
|
|
|
|
{
|
|
|
|
|
// It does not make sense to do this post-registration, although it would
|
|
|
|
|
// not hurt either, as the server must ignore it in that case
|
|
|
|
|
if (s->state == IRC_CONNECTED)
|
|
|
|
|
irc_send (s, "CAP END");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
irc_handle_cap (struct server *s, const struct irc_message *msg)
|
|
|
|
|
{
|
|
|
|
|
@@ -6559,50 +6648,89 @@ irc_handle_cap (struct server *s, const struct irc_message *msg)
|
|
|
|
|
active = false;
|
|
|
|
|
cap++;
|
|
|
|
|
}
|
|
|
|
|
if (!strcasecmp_ascii (cap, "echo-message"))
|
|
|
|
|
s->cap_echo_message = active;
|
|
|
|
|
if (!strcasecmp_ascii (cap, "away-notify"))
|
|
|
|
|
s->cap_away_notify = active;
|
|
|
|
|
irc_toggle_cap (s, cap, active);
|
|
|
|
|
}
|
|
|
|
|
irc_send (s, "CAP END");
|
|
|
|
|
if (s->cap_sasl && s->transport == &g_transport_tls)
|
|
|
|
|
irc_send (s, "AUTHENTICATE EXTERNAL");
|
|
|
|
|
else
|
|
|
|
|
irc_try_finish_cap_negotiation (s);
|
|
|
|
|
}
|
|
|
|
|
else if (!strcasecmp_ascii (subcommand, "NAK"))
|
|
|
|
|
{
|
|
|
|
|
log_server_error (s, s->buffer,
|
|
|
|
|
"#s: #S", "Capabilities not acknowledged", args);
|
|
|
|
|
irc_send (s, "CAP END");
|
|
|
|
|
irc_try_finish_cap_negotiation (s);
|
|
|
|
|
}
|
|
|
|
|
else if (!strcasecmp_ascii (subcommand, "DEL"))
|
|
|
|
|
{
|
|
|
|
|
log_server_error (s, s->buffer,
|
|
|
|
|
"#s: #S", "Capabilities deleted", args);
|
|
|
|
|
for (size_t i = 0; i < v.len; i++)
|
|
|
|
|
irc_toggle_cap (s, v.vector[i], false);
|
|
|
|
|
}
|
|
|
|
|
else if (!strcasecmp_ascii (subcommand, "LS"))
|
|
|
|
|
{
|
|
|
|
|
log_server_status (s, s->buffer,
|
|
|
|
|
"#s: #S", "Capabilities supported", args);
|
|
|
|
|
|
|
|
|
|
struct strv chosen = strv_make ();
|
|
|
|
|
struct strv use = strv_make ();
|
|
|
|
|
|
|
|
|
|
cstr_split (get_config_string (s->config, "capabilities"),
|
|
|
|
|
",", true, &use);
|
|
|
|
|
|
|
|
|
|
// Filter server capabilities for ones we can make use of
|
|
|
|
|
for (size_t i = 0; i < v.len; i++)
|
|
|
|
|
if (msg->params.len > 3 && !strcmp (args, "*"))
|
|
|
|
|
cstr_split (msg->params.vector[3], " ", true, &s->cap_ls_buf);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
const char *cap = v.vector[i];
|
|
|
|
|
for (size_t k = 0; k < use.len; k++)
|
|
|
|
|
if (!strcasecmp_ascii (use.vector[k], cap))
|
|
|
|
|
strv_append (&chosen, cap);
|
|
|
|
|
strv_append_vector (&s->cap_ls_buf, v.vector);
|
|
|
|
|
irc_process_cap_ls (s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *chosen_str = strv_join (&chosen, " ");
|
|
|
|
|
strv_free (&chosen);
|
|
|
|
|
strv_free (&use);
|
|
|
|
|
|
|
|
|
|
irc_send (s, "CAP REQ :%s", chosen_str);
|
|
|
|
|
free (chosen_str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
strv_free (&v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
irc_handle_chghost (struct server *s, const struct irc_message *msg)
|
|
|
|
|
{
|
|
|
|
|
if (!msg->prefix || msg->params.len < 2)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
char *nickname = irc_cut_nickname (msg->prefix);
|
|
|
|
|
struct user *user = str_map_find (&s->irc_users, nickname);
|
|
|
|
|
free (nickname);
|
|
|
|
|
if (!user)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
char *new_prefix = xstrdup_printf ("%s!%s@%s", user->nickname,
|
|
|
|
|
msg->params.vector[0], msg->params.vector[1]);
|
|
|
|
|
|
|
|
|
|
if (irc_is_this_us (s, msg->prefix))
|
|
|
|
|
{
|
|
|
|
|
cstr_set (&s->irc_user_host, xstrdup_printf ("%s@%s",
|
|
|
|
|
msg->params.vector[0], msg->params.vector[1]));
|
|
|
|
|
|
|
|
|
|
log_chghost_self (s, s->buffer, new_prefix);
|
|
|
|
|
|
|
|
|
|
// Log a message in all open buffers on this server
|
|
|
|
|
struct str_map_iter iter = str_map_iter_make (&s->irc_buffer_map);
|
|
|
|
|
struct buffer *buffer;
|
|
|
|
|
while ((buffer = str_map_iter_next (&iter)))
|
|
|
|
|
log_chghost_self (s, buffer, new_prefix);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Log a message in any PM buffer
|
|
|
|
|
struct buffer *buffer =
|
|
|
|
|
str_map_find (&s->irc_buffer_map, user->nickname);
|
|
|
|
|
if (buffer)
|
|
|
|
|
log_chghost (s, buffer, msg->prefix, new_prefix);
|
|
|
|
|
|
|
|
|
|
// Log a message in all channels the user is in
|
|
|
|
|
LIST_FOR_EACH (struct user_channel, iter, user->channels)
|
|
|
|
|
{
|
|
|
|
|
buffer = str_map_find (&s->irc_buffer_map, iter->channel->name);
|
|
|
|
|
hard_assert (buffer != NULL);
|
|
|
|
|
log_chghost (s, buffer, msg->prefix, new_prefix);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free (new_prefix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
irc_handle_error (struct server *s, const struct irc_message *msg)
|
|
|
|
|
{
|
|
|
|
|
@@ -6663,8 +6791,13 @@ irc_handle_join (struct server *s, const struct irc_message *msg)
|
|
|
|
|
str_map_set (&s->irc_buffer_map, channel->name, buffer);
|
|
|
|
|
|
|
|
|
|
buffer_add (s->ctx, buffer);
|
|
|
|
|
// XXX: this is annoying, consider only doing it a while after /join
|
|
|
|
|
buffer_activate (s->ctx, buffer);
|
|
|
|
|
|
|
|
|
|
char *input = CALL (s->ctx->input, get_line);
|
|
|
|
|
if (!*input)
|
|
|
|
|
buffer_activate (s->ctx, buffer);
|
|
|
|
|
else
|
|
|
|
|
buffer->highlighted = true;
|
|
|
|
|
free (input);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (irc_is_this_us (s, msg->prefix))
|
|
|
|
|
@@ -7236,22 +7369,24 @@ irc_handle_topic (struct server *s, const struct irc_message *msg)
|
|
|
|
|
static struct irc_handler g_irc_handlers[] =
|
|
|
|
|
{
|
|
|
|
|
// This list needs to stay sorted
|
|
|
|
|
{ "AWAY", irc_handle_away },
|
|
|
|
|
{ "CAP", irc_handle_cap },
|
|
|
|
|
{ "ERROR", irc_handle_error },
|
|
|
|
|
{ "INVITE", irc_handle_invite },
|
|
|
|
|
{ "JOIN", irc_handle_join },
|
|
|
|
|
{ "KICK", irc_handle_kick },
|
|
|
|
|
{ "KILL", irc_handle_kill },
|
|
|
|
|
{ "MODE", irc_handle_mode },
|
|
|
|
|
{ "NICK", irc_handle_nick },
|
|
|
|
|
{ "NOTICE", irc_handle_notice },
|
|
|
|
|
{ "PART", irc_handle_part },
|
|
|
|
|
{ "PING", irc_handle_ping },
|
|
|
|
|
{ "PRIVMSG", irc_handle_privmsg },
|
|
|
|
|
{ "QUIT", irc_handle_quit },
|
|
|
|
|
{ "TAGMSG", irc_handle_tagmsg },
|
|
|
|
|
{ "TOPIC", irc_handle_topic },
|
|
|
|
|
{ "AUTHENTICATE", irc_handle_authenticate },
|
|
|
|
|
{ "AWAY", irc_handle_away },
|
|
|
|
|
{ "CAP", irc_handle_cap },
|
|
|
|
|
{ "CHGHOST", irc_handle_chghost },
|
|
|
|
|
{ "ERROR", irc_handle_error },
|
|
|
|
|
{ "INVITE", irc_handle_invite },
|
|
|
|
|
{ "JOIN", irc_handle_join },
|
|
|
|
|
{ "KICK", irc_handle_kick },
|
|
|
|
|
{ "KILL", irc_handle_kill },
|
|
|
|
|
{ "MODE", irc_handle_mode },
|
|
|
|
|
{ "NICK", irc_handle_nick },
|
|
|
|
|
{ "NOTICE", irc_handle_notice },
|
|
|
|
|
{ "PART", irc_handle_part },
|
|
|
|
|
{ "PING", irc_handle_ping },
|
|
|
|
|
{ "PRIVMSG", irc_handle_privmsg },
|
|
|
|
|
{ "QUIT", irc_handle_quit },
|
|
|
|
|
{ "TAGMSG", irc_handle_tagmsg },
|
|
|
|
|
{ "TOPIC", irc_handle_topic },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
@@ -7932,6 +8067,15 @@ irc_process_numeric (struct server *s,
|
|
|
|
|
if (irc_handle_rpl_endofwho (s, msg)) buffer = NULL;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case IRC_ERR_NICKLOCKED:
|
|
|
|
|
case IRC_RPL_SASLSUCCESS:
|
|
|
|
|
case IRC_ERR_SASLFAIL:
|
|
|
|
|
case IRC_ERR_SASLTOOLONG:
|
|
|
|
|
case IRC_ERR_SASLABORTED:
|
|
|
|
|
case IRC_ERR_SASLALREADY:
|
|
|
|
|
irc_try_finish_cap_negotiation (s);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case IRC_RPL_LIST:
|
|
|
|
|
|
|
|
|
|
case IRC_ERR_UNKNOWNCOMMAND:
|
|
|
|
|
@@ -10916,45 +11060,38 @@ handle_command_buffer (struct handler_args *a)
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
replace_string_array
|
|
|
|
|
(struct config_item *item, struct strv *array, struct error **e)
|
|
|
|
|
{
|
|
|
|
|
char *changed = strv_join (array, ",");
|
|
|
|
|
struct str tmp = { .str = changed, .len = strlen (changed) };
|
|
|
|
|
bool result = config_item_set_from (item,
|
|
|
|
|
config_item_string_array (&tmp), e);
|
|
|
|
|
free (changed);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
handle_command_set_add
|
|
|
|
|
(struct config_item *item, const char *value, struct error **e)
|
|
|
|
|
(struct strv *items, const struct strv *values, struct error **e)
|
|
|
|
|
{
|
|
|
|
|
struct strv items = strv_make ();
|
|
|
|
|
if (item->type != CONFIG_ITEM_NULL)
|
|
|
|
|
cstr_split (item->value.string.str, ",", false, &items);
|
|
|
|
|
if (items.len == 1 && !*items.vector[0])
|
|
|
|
|
strv_reset (&items);
|
|
|
|
|
|
|
|
|
|
// FIXME: handle multiple items properly
|
|
|
|
|
bool result = false;
|
|
|
|
|
if (strv_find (&items, value) != -1)
|
|
|
|
|
error_set (e, "already present in the array: %s", value);
|
|
|
|
|
else
|
|
|
|
|
for (size_t i = 0; i < values->len; i++)
|
|
|
|
|
{
|
|
|
|
|
strv_append (&items, value);
|
|
|
|
|
result = replace_string_array (item, &items, e);
|
|
|
|
|
const char *value = values->vector[i];
|
|
|
|
|
if (strv_find (items, values->vector[i]) != -1)
|
|
|
|
|
return error_set (e, "already present in the array: %s", value);
|
|
|
|
|
strv_append (items, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
strv_free (&items);
|
|
|
|
|
return result;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
handle_command_set_remove
|
|
|
|
|
(struct config_item *item, const char *value, struct error **e)
|
|
|
|
|
(struct strv *items, const struct strv *values, struct error **e)
|
|
|
|
|
{
|
|
|
|
|
for (size_t i = 0; i < values->len; i++)
|
|
|
|
|
{
|
|
|
|
|
const char *value = values->vector[i];
|
|
|
|
|
ssize_t i = strv_find (items, value);
|
|
|
|
|
if (i == -1)
|
|
|
|
|
return error_set (e, "not present in the array: %s", value);
|
|
|
|
|
strv_remove (items, i);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
handle_command_set_modify
|
|
|
|
|
(struct config_item *item, const char *value, bool add, struct error **e)
|
|
|
|
|
{
|
|
|
|
|
struct strv items = strv_make ();
|
|
|
|
|
if (item->type != CONFIG_ITEM_NULL)
|
|
|
|
|
@@ -10962,18 +11099,23 @@ handle_command_set_remove
|
|
|
|
|
if (items.len == 1 && !*items.vector[0])
|
|
|
|
|
strv_reset (&items);
|
|
|
|
|
|
|
|
|
|
// FIXME: handle multiple items properly
|
|
|
|
|
bool result = false;
|
|
|
|
|
ssize_t i = strv_find (&items, value);
|
|
|
|
|
if (i == -1)
|
|
|
|
|
error_set (e, "not present in the array: %s", value);
|
|
|
|
|
else
|
|
|
|
|
struct strv values = strv_make ();
|
|
|
|
|
cstr_split (value, ",", false, &values);
|
|
|
|
|
bool result = add
|
|
|
|
|
? handle_command_set_add (&items, &values, e)
|
|
|
|
|
: handle_command_set_remove (&items, &values, e);
|
|
|
|
|
|
|
|
|
|
if (result)
|
|
|
|
|
{
|
|
|
|
|
strv_remove (&items, i);
|
|
|
|
|
result = replace_string_array (item, &items, e);
|
|
|
|
|
char *changed = strv_join (&items, ",");
|
|
|
|
|
struct str tmp = { .str = changed, .len = strlen (changed) };
|
|
|
|
|
result = config_item_set_from (item,
|
|
|
|
|
config_item_string_array (&tmp), e);
|
|
|
|
|
free (changed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
strv_free (&items);
|
|
|
|
|
strv_free (&values);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -10992,10 +11134,8 @@ handle_command_set_assign_item (struct app_context *ctx,
|
|
|
|
|
config_item_set_from (item, config_item_clone (new_), &e);
|
|
|
|
|
else if (item->schema->type != CONFIG_ITEM_STRING_ARRAY)
|
|
|
|
|
error_set (&e, "not a string array");
|
|
|
|
|
else if (add)
|
|
|
|
|
handle_command_set_add (item, new_->value.string.str, &e);
|
|
|
|
|
else if (remove)
|
|
|
|
|
handle_command_set_remove (item, new_->value.string.str, &e);
|
|
|
|
|
else
|
|
|
|
|
handle_command_set_modify (item, new_->value.string.str, add, &e);
|
|
|
|
|
|
|
|
|
|
if (e)
|
|
|
|
|
{
|
|
|
|
|
@@ -11241,6 +11381,20 @@ handle_command_notice (struct handler_args *a)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
handle_command_squery (struct handler_args *a)
|
|
|
|
|
{
|
|
|
|
|
if (!*a->arguments)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
char *target = cut_word (&a->arguments);
|
|
|
|
|
if (!*a->arguments)
|
|
|
|
|
log_server_error (a->s, a->s->buffer, "No text to send");
|
|
|
|
|
else
|
|
|
|
|
irc_send (a->s, "SQUERY %s :%s", target, a->arguments);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
handle_command_ctcp (struct handler_args *a)
|
|
|
|
|
{
|
|
|
|
|
@@ -11280,7 +11434,7 @@ handle_command_me (struct handler_args *a)
|
|
|
|
|
static bool
|
|
|
|
|
handle_command_quit (struct handler_args *a)
|
|
|
|
|
{
|
|
|
|
|
initiate_quit (a->ctx, *a->arguments ? a->arguments : NULL);
|
|
|
|
|
request_quit (a->ctx, *a->arguments ? a->arguments : NULL);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -11769,11 +11923,17 @@ static bool
|
|
|
|
|
handle_command_channel_mode
|
|
|
|
|
(struct handler_args *a, bool adding, char mode_char)
|
|
|
|
|
{
|
|
|
|
|
if (!*a->arguments)
|
|
|
|
|
return false;
|
|
|
|
|
const char *targets = a->arguments;
|
|
|
|
|
if (!*targets)
|
|
|
|
|
{
|
|
|
|
|
if (adding)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
targets = a->s->irc_user->nickname;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct strv v = strv_make ();
|
|
|
|
|
cstr_split (a->arguments, " ", true, &v);
|
|
|
|
|
cstr_split (targets, " ", true, &v);
|
|
|
|
|
mass_channel_mode (a->s, a->channel_name, adding, mode_char, &v);
|
|
|
|
|
strv_free (&v);
|
|
|
|
|
return true;
|
|
|
|
|
@@ -11856,6 +12016,9 @@ g_command_handlers[] =
|
|
|
|
|
{ "notice", "Send notice to a nick or channel",
|
|
|
|
|
"<target> <message>",
|
|
|
|
|
handle_command_notice, HANDLER_SERVER | HANDLER_NEEDS_REG },
|
|
|
|
|
{ "squery", "Send a message to a service",
|
|
|
|
|
"<service> <message>",
|
|
|
|
|
handle_command_squery, HANDLER_SERVER | HANDLER_NEEDS_REG },
|
|
|
|
|
{ "ctcp", "Send a CTCP query",
|
|
|
|
|
"<target> <tag>",
|
|
|
|
|
handle_command_ctcp, HANDLER_SERVER | HANDLER_NEEDS_REG },
|
|
|
|
|
@@ -11877,13 +12040,13 @@ g_command_handlers[] =
|
|
|
|
|
"<nick>...",
|
|
|
|
|
handle_command_op, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
|
|
|
|
{ "deop", "Remove channel operator status",
|
|
|
|
|
"<nick>...",
|
|
|
|
|
"[<nick>...]",
|
|
|
|
|
handle_command_deop, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
|
|
|
|
{ "voice", "Give voice",
|
|
|
|
|
"<nick>...",
|
|
|
|
|
handle_command_voice, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
|
|
|
|
{ "devoice", "Remove voice",
|
|
|
|
|
"<nick>...",
|
|
|
|
|
"[<nick>...]",
|
|
|
|
|
handle_command_devoice, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
|
|
|
|
|
|
|
|
|
{ "mode", "Change mode",
|
|
|
|
|
@@ -13722,12 +13885,15 @@ on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
|
|
|
|
|
while (try_reap_child (ctx))
|
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
if (g_termination_requested && !ctx->quitting)
|
|
|
|
|
initiate_quit (ctx, NULL);
|
|
|
|
|
if (g_termination_requested)
|
|
|
|
|
{
|
|
|
|
|
g_termination_requested = false;
|
|
|
|
|
request_quit (ctx, NULL);
|
|
|
|
|
}
|
|
|
|
|
if (g_winch_received)
|
|
|
|
|
{
|
|
|
|
|
redraw_screen (ctx);
|
|
|
|
|
g_winch_received = false;
|
|
|
|
|
redraw_screen (ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|