Compare commits
18 Commits
v1.1.0
...
61c52d793c
| Author | SHA1 | Date | |
|---|---|---|---|
|
61c52d793c
|
|||
|
b4dd0052ff
|
|||
|
e3c47c33fa
|
|||
|
80c1e8f8eb
|
|||
|
c5f49ab1e6
|
|||
|
6f62b9c0c7
|
|||
|
c1d69e3630
|
|||
|
c75ef167f2
|
|||
|
ddffc71abe
|
|||
|
5a0b2d1c57
|
|||
|
bb451a5050
|
|||
|
61f15ead8a
|
|||
|
17f430043a
|
|||
|
735096d76d
|
|||
|
1ba59e6ee0
|
|||
|
f9ba682c0e
|
|||
|
8e8ffe2c73
|
|||
|
d05c85833d
|
@@ -61,7 +61,8 @@ endif ()
|
|||||||
|
|
||||||
# -lrt is only for glibc < 2.17
|
# -lrt is only for glibc < 2.17
|
||||||
# -liconv may or may not be a part of libc
|
# -liconv may or may not be a part of libc
|
||||||
foreach (extra iconv rt)
|
# -lm may or may not be a part of libc
|
||||||
|
foreach (extra iconv rt m)
|
||||||
find_library (extra_lib_${extra} ${extra})
|
find_library (extra_lib_${extra} ${extra})
|
||||||
if (extra_lib_${extra})
|
if (extra_lib_${extra})
|
||||||
list (APPEND project_libraries ${extra_lib_${extra}})
|
list (APPEND project_libraries ${extra_lib_${extra}})
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
|
Copyright (c) 2014 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted.
|
purpose with or without fee is hereby granted.
|
||||||
|
|||||||
19
NEWS
19
NEWS
@@ -1,3 +1,22 @@
|
|||||||
|
1.2.0 (202?-??-??) "There Are Other Countries As Well"
|
||||||
|
|
||||||
|
* degesch: added a /squery command for IRCnet
|
||||||
|
|
||||||
|
* degesch: added trivial support for SASL EXTERNAL, enabled by adding "sasl"
|
||||||
|
to the respective server's "capabilities" list
|
||||||
|
|
||||||
|
* degesch: now supporting IRCv3.2 capability negotiation, including CAP DEL
|
||||||
|
|
||||||
|
* degesch: added support for IRCv3 chghost
|
||||||
|
|
||||||
|
* degesch: /deop and /devoice without arguments will use the client's user
|
||||||
|
|
||||||
|
* degesch: /set +=/-= now treats its argument as a string array
|
||||||
|
|
||||||
|
* censor.lua: now stripping colours from censored messages;
|
||||||
|
their attributes are also configurable rather than always black on black
|
||||||
|
|
||||||
|
|
||||||
1.1.0 (2020-10-31) "What Do You Mean By 'This Isn't Germany'?"
|
1.1.0 (2020-10-31) "What Do You Mean By 'This Isn't Germany'?"
|
||||||
|
|
||||||
* degesch: made fancy-prompt.lua work with libedit
|
* degesch: made fancy-prompt.lua work with libedit
|
||||||
|
|||||||
@@ -125,6 +125,10 @@ as a `forking` type systemd user service.
|
|||||||
|
|
||||||
Client Certificates
|
Client Certificates
|
||||||
-------------------
|
-------------------
|
||||||
|
'degesch' will use the SASL EXTERNAL method to authenticate using the TLS
|
||||||
|
client certificate specified by the respective server's `tls_cert` option
|
||||||
|
if you add `sasl` to the `capabilities` option and the server supports this.
|
||||||
|
|
||||||
'kike' uses SHA-1 fingerprints of TLS client certificates to authenticate users.
|
'kike' uses SHA-1 fingerprints of TLS client certificates to authenticate users.
|
||||||
To get the fingerprint from a certificate file in the required form, use:
|
To get the fingerprint from a certificate file in the required form, use:
|
||||||
|
|
||||||
|
|||||||
356
degesch.c
356
degesch.c
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* degesch.c: a terminal-based IRC client
|
* 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
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted.
|
* purpose with or without fee is hereby granted.
|
||||||
@@ -51,6 +51,7 @@ enum
|
|||||||
#include "common.c"
|
#include "common.c"
|
||||||
#include "kike-replies.c"
|
#include "kike-replies.c"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
#include <langinfo.h>
|
#include <langinfo.h>
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
@@ -1718,8 +1719,10 @@ struct server
|
|||||||
char *irc_user_host; ///< Our current user@host
|
char *irc_user_host; ///< Our current user@host
|
||||||
bool autoaway_active; ///< Autoaway is currently active
|
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_echo_message; ///< Whether the server echoes messages
|
||||||
bool cap_away_notify; ///< Whether we get AWAY notifications
|
bool cap_away_notify; ///< Whether we get AWAY notifications
|
||||||
|
bool cap_sasl; ///< Whether SASL is available
|
||||||
|
|
||||||
// Server-specific information (from RPL_ISUPPORT):
|
// Server-specific information (from RPL_ISUPPORT):
|
||||||
|
|
||||||
@@ -1841,6 +1844,7 @@ server_new (struct poller *poller)
|
|||||||
|
|
||||||
self->irc_user_mode = str_make ();
|
self->irc_user_mode = str_make ();
|
||||||
|
|
||||||
|
self->cap_ls_buf = strv_make ();
|
||||||
server_init_specifics (self);
|
server_init_specifics (self);
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -1887,6 +1891,7 @@ server_destroy (struct server *self)
|
|||||||
str_free (&self->irc_user_mode);
|
str_free (&self->irc_user_mode);
|
||||||
free (self->irc_user_host);
|
free (self->irc_user_host);
|
||||||
|
|
||||||
|
strv_free (&self->cap_ls_buf);
|
||||||
server_free_specifics (self);
|
server_free_specifics (self);
|
||||||
free (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
|
// This is a pure function and we don't use threads, static storage is fine
|
||||||
static int table[6 * 6 * 6];
|
static int table[6 * 6 * 6];
|
||||||
size_t len_counter = 0;
|
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 r = x / 36;
|
||||||
int g = (x / 6) % 6;
|
int g = (x / 6) % 6;
|
||||||
int b = (x % 6);
|
int b = (x % 6);
|
||||||
|
|
||||||
// Use the luma value of colours within the cube to filter colours that
|
// The first step is 95/255, the rest are 40/255,
|
||||||
// look okay-ish on terminals with both black and white backgrounds
|
// as an approximation we can double the first step
|
||||||
double luma = 0.2126 * r / 6. + 0.7152 * g / 6. + 0.0722 * b / 6.;
|
double linear_R = pow ((r + !!r) / 6., 2.2);
|
||||||
if (luma >= .3 && luma <= .5)
|
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;
|
table[len_counter++] = 16 + x;
|
||||||
}
|
}
|
||||||
*len = len_counter;
|
*len = len_counter;
|
||||||
@@ -2337,7 +2348,7 @@ static struct config_schema g_config_server[] =
|
|||||||
.type = CONFIG_ITEM_STRING_ARRAY,
|
.type = CONFIG_ITEM_STRING_ARRAY,
|
||||||
.validate = config_validate_nonjunk_string,
|
.validate = config_validate_nonjunk_string,
|
||||||
.default_ = "\"multi-prefix,invite-notify,server-time,echo-message,"
|
.default_ = "\"multi-prefix,invite-notify,server-time,echo-message,"
|
||||||
"message-tags,away-notify\"" },
|
"message-tags,away-notify,cap-notify,chghost\"" },
|
||||||
|
|
||||||
{ .name = "tls",
|
{ .name = "tls",
|
||||||
.comment = "Whether to use 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, \
|
log_server ((s), (buffer), BUFFER_LINE_STATUS | BUFFER_LINE_UNIMPORTANT, \
|
||||||
"#n is now known as #n", (old), (new_))
|
"#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) \
|
#define log_outcoming_notice(s, buffer, who, text) \
|
||||||
log_server_status ((s), (buffer), "#s(#n): #m", "Notice", (who), (text))
|
log_server_status ((s), (buffer), "#s(#n): #m", "Notice", (who), (text))
|
||||||
#define log_outcoming_privmsg(s, buffer, prefixes, 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);
|
str_reset (&s->irc_user_mode);
|
||||||
cstr_set (&s->irc_user_host, NULL);
|
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_echo_message = false;
|
||||||
|
s->cap_sasl = false;
|
||||||
|
|
||||||
// Need to call this before server_init_specifics()
|
// Need to call this before server_init_specifics()
|
||||||
irc_set_casemapping (s, irc_tolower, irc_strxfrm);
|
irc_set_casemapping (s, irc_tolower, irc_strxfrm);
|
||||||
@@ -5021,14 +5042,17 @@ irc_initiate_disconnect (struct server *s, const char *reason)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
initiate_quit (struct app_context *ctx, const char *message)
|
request_quit (struct app_context *ctx, const char *message)
|
||||||
|
{
|
||||||
|
if (!ctx->quitting)
|
||||||
{
|
{
|
||||||
log_global_status (ctx, "Shutting down");
|
log_global_status (ctx, "Shutting down");
|
||||||
|
ctx->quitting = true;
|
||||||
|
|
||||||
// Hide the user interface
|
// Disable the user interface
|
||||||
CALL (ctx->input, hide);
|
CALL (ctx->input, hide);
|
||||||
|
}
|
||||||
|
|
||||||
// Initiate a connection close
|
|
||||||
struct str_map_iter iter = str_map_iter_make (&ctx->servers);
|
struct str_map_iter iter = str_map_iter_make (&ctx->servers);
|
||||||
struct server *s;
|
struct server *s;
|
||||||
while ((s = str_map_iter_next (&iter)))
|
while ((s = str_map_iter_next (&iter)))
|
||||||
@@ -5042,7 +5066,6 @@ initiate_quit (struct app_context *ctx, const char *message)
|
|||||||
irc_destroy_connector (s);
|
irc_destroy_connector (s);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->quitting = true;
|
|
||||||
try_finish_quit (ctx);
|
try_finish_quit (ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5689,9 +5712,9 @@ irc_register (struct server *s)
|
|||||||
const char *realname = get_config_string (s->config, "realname");
|
const char *realname = get_config_string (s->config, "realname");
|
||||||
hard_assert (username && 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
|
// 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");
|
const char *password = get_config_string (s->config, "password");
|
||||||
if (password)
|
if (password)
|
||||||
@@ -6398,11 +6421,11 @@ irc_handle_mode_user (struct server *s, char **params)
|
|||||||
static void
|
static void
|
||||||
irc_handle_sent_cap (struct server *s, const struct irc_message *msg)
|
irc_handle_sent_cap (struct server *s, const struct irc_message *msg)
|
||||||
{
|
{
|
||||||
if (msg->params.len < 2)
|
if (msg->params.len < 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const char *subcommand = msg->params.vector[1];
|
const char *subcommand = msg->params.vector[0];
|
||||||
const char *args = (msg->params.len > 2) ? msg->params.vector[2] : "";
|
const char *args = (msg->params.len > 1) ? msg->params.vector[1] : "";
|
||||||
if (!strcasecmp_ascii (subcommand, "REQ"))
|
if (!strcasecmp_ascii (subcommand, "REQ"))
|
||||||
log_server_status (s, s->buffer,
|
log_server_status (s, s->buffer,
|
||||||
"#s: #S", "Capabilities requested", args);
|
"#s: #S", "Capabilities requested", args);
|
||||||
@@ -6519,6 +6542,20 @@ irc_process_sent_message (const struct irc_message *msg, struct server *s)
|
|||||||
|
|
||||||
// --- Input handling ----------------------------------------------------------
|
// --- 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
|
static void
|
||||||
irc_handle_away (struct server *s, const struct irc_message *msg)
|
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;
|
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
|
static void
|
||||||
irc_handle_cap (struct server *s, const struct irc_message *msg)
|
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;
|
active = false;
|
||||||
cap++;
|
cap++;
|
||||||
}
|
}
|
||||||
if (!strcasecmp_ascii (cap, "echo-message"))
|
irc_toggle_cap (s, cap, active);
|
||||||
s->cap_echo_message = active;
|
|
||||||
if (!strcasecmp_ascii (cap, "away-notify"))
|
|
||||||
s->cap_away_notify = 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"))
|
else if (!strcasecmp_ascii (subcommand, "NAK"))
|
||||||
{
|
{
|
||||||
log_server_error (s, s->buffer,
|
log_server_error (s, s->buffer,
|
||||||
"#s: #S", "Capabilities not acknowledged", args);
|
"#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"))
|
else if (!strcasecmp_ascii (subcommand, "LS"))
|
||||||
{
|
{
|
||||||
log_server_status (s, s->buffer,
|
|
||||||
"#s: #S", "Capabilities supported", args);
|
|
||||||
|
|
||||||
struct strv chosen = strv_make ();
|
if (msg->params.len > 3 && !strcmp (args, "*"))
|
||||||
struct strv use = strv_make ();
|
cstr_split (msg->params.vector[3], " ", true, &s->cap_ls_buf);
|
||||||
|
else
|
||||||
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++)
|
|
||||||
{
|
{
|
||||||
const char *cap = v.vector[i];
|
strv_append_vector (&s->cap_ls_buf, v.vector);
|
||||||
for (size_t k = 0; k < use.len; k++)
|
irc_process_cap_ls (s);
|
||||||
if (!strcasecmp_ascii (use.vector[k], cap))
|
|
||||||
strv_append (&chosen, cap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
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
|
static void
|
||||||
irc_handle_error (struct server *s, const struct irc_message *msg)
|
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);
|
str_map_set (&s->irc_buffer_map, channel->name, buffer);
|
||||||
|
|
||||||
buffer_add (s->ctx, buffer);
|
buffer_add (s->ctx, buffer);
|
||||||
// XXX: this is annoying, consider only doing it a while after /join
|
|
||||||
|
char *input = CALL (s->ctx->input, get_line);
|
||||||
|
if (!*input)
|
||||||
buffer_activate (s->ctx, buffer);
|
buffer_activate (s->ctx, buffer);
|
||||||
|
else
|
||||||
|
buffer->highlighted = true;
|
||||||
|
free (input);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (irc_is_this_us (s, msg->prefix))
|
if (irc_is_this_us (s, msg->prefix))
|
||||||
@@ -7236,8 +7369,10 @@ irc_handle_topic (struct server *s, const struct irc_message *msg)
|
|||||||
static struct irc_handler g_irc_handlers[] =
|
static struct irc_handler g_irc_handlers[] =
|
||||||
{
|
{
|
||||||
// This list needs to stay sorted
|
// This list needs to stay sorted
|
||||||
|
{ "AUTHENTICATE", irc_handle_authenticate },
|
||||||
{ "AWAY", irc_handle_away },
|
{ "AWAY", irc_handle_away },
|
||||||
{ "CAP", irc_handle_cap },
|
{ "CAP", irc_handle_cap },
|
||||||
|
{ "CHGHOST", irc_handle_chghost },
|
||||||
{ "ERROR", irc_handle_error },
|
{ "ERROR", irc_handle_error },
|
||||||
{ "INVITE", irc_handle_invite },
|
{ "INVITE", irc_handle_invite },
|
||||||
{ "JOIN", irc_handle_join },
|
{ "JOIN", irc_handle_join },
|
||||||
@@ -7932,6 +8067,15 @@ irc_process_numeric (struct server *s,
|
|||||||
if (irc_handle_rpl_endofwho (s, msg)) buffer = NULL;
|
if (irc_handle_rpl_endofwho (s, msg)) buffer = NULL;
|
||||||
break;
|
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_RPL_LIST:
|
||||||
|
|
||||||
case IRC_ERR_UNKNOWNCOMMAND:
|
case IRC_ERR_UNKNOWNCOMMAND:
|
||||||
@@ -10916,45 +11060,38 @@ handle_command_buffer (struct handler_args *a)
|
|||||||
return result;
|
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
|
static bool
|
||||||
handle_command_set_add
|
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 ();
|
for (size_t i = 0; i < values->len; i++)
|
||||||
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
|
|
||||||
{
|
{
|
||||||
strv_append (&items, value);
|
const char *value = values->vector[i];
|
||||||
result = replace_string_array (item, &items, e);
|
if (strv_find (items, values->vector[i]) != -1)
|
||||||
|
return error_set (e, "already present in the array: %s", value);
|
||||||
|
strv_append (items, value);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
strv_free (&items);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
handle_command_set_remove
|
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 ();
|
struct strv items = strv_make ();
|
||||||
if (item->type != CONFIG_ITEM_NULL)
|
if (item->type != CONFIG_ITEM_NULL)
|
||||||
@@ -10962,18 +11099,23 @@ handle_command_set_remove
|
|||||||
if (items.len == 1 && !*items.vector[0])
|
if (items.len == 1 && !*items.vector[0])
|
||||||
strv_reset (&items);
|
strv_reset (&items);
|
||||||
|
|
||||||
// FIXME: handle multiple items properly
|
struct strv values = strv_make ();
|
||||||
bool result = false;
|
cstr_split (value, ",", false, &values);
|
||||||
ssize_t i = strv_find (&items, value);
|
bool result = add
|
||||||
if (i == -1)
|
? handle_command_set_add (&items, &values, e)
|
||||||
error_set (e, "not present in the array: %s", value);
|
: handle_command_set_remove (&items, &values, e);
|
||||||
else
|
|
||||||
|
if (result)
|
||||||
{
|
{
|
||||||
strv_remove (&items, i);
|
char *changed = strv_join (&items, ",");
|
||||||
result = replace_string_array (item, &items, e);
|
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 (&items);
|
||||||
|
strv_free (&values);
|
||||||
return result;
|
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);
|
config_item_set_from (item, config_item_clone (new_), &e);
|
||||||
else if (item->schema->type != CONFIG_ITEM_STRING_ARRAY)
|
else if (item->schema->type != CONFIG_ITEM_STRING_ARRAY)
|
||||||
error_set (&e, "not a string array");
|
error_set (&e, "not a string array");
|
||||||
else if (add)
|
else
|
||||||
handle_command_set_add (item, new_->value.string.str, &e);
|
handle_command_set_modify (item, new_->value.string.str, add, &e);
|
||||||
else if (remove)
|
|
||||||
handle_command_set_remove (item, new_->value.string.str, &e);
|
|
||||||
|
|
||||||
if (e)
|
if (e)
|
||||||
{
|
{
|
||||||
@@ -11241,6 +11381,20 @@ handle_command_notice (struct handler_args *a)
|
|||||||
return true;
|
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
|
static bool
|
||||||
handle_command_ctcp (struct handler_args *a)
|
handle_command_ctcp (struct handler_args *a)
|
||||||
{
|
{
|
||||||
@@ -11280,7 +11434,7 @@ handle_command_me (struct handler_args *a)
|
|||||||
static bool
|
static bool
|
||||||
handle_command_quit (struct handler_args *a)
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11769,11 +11923,17 @@ static bool
|
|||||||
handle_command_channel_mode
|
handle_command_channel_mode
|
||||||
(struct handler_args *a, bool adding, char mode_char)
|
(struct handler_args *a, bool adding, char mode_char)
|
||||||
{
|
{
|
||||||
if (!*a->arguments)
|
const char *targets = a->arguments;
|
||||||
|
if (!*targets)
|
||||||
|
{
|
||||||
|
if (adding)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
targets = a->s->irc_user->nickname;
|
||||||
|
}
|
||||||
|
|
||||||
struct strv v = strv_make ();
|
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);
|
mass_channel_mode (a->s, a->channel_name, adding, mode_char, &v);
|
||||||
strv_free (&v);
|
strv_free (&v);
|
||||||
return true;
|
return true;
|
||||||
@@ -11856,6 +12016,9 @@ g_command_handlers[] =
|
|||||||
{ "notice", "Send notice to a nick or channel",
|
{ "notice", "Send notice to a nick or channel",
|
||||||
"<target> <message>",
|
"<target> <message>",
|
||||||
handle_command_notice, HANDLER_SERVER | HANDLER_NEEDS_REG },
|
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",
|
{ "ctcp", "Send a CTCP query",
|
||||||
"<target> <tag>",
|
"<target> <tag>",
|
||||||
handle_command_ctcp, HANDLER_SERVER | HANDLER_NEEDS_REG },
|
handle_command_ctcp, HANDLER_SERVER | HANDLER_NEEDS_REG },
|
||||||
@@ -11877,13 +12040,13 @@ g_command_handlers[] =
|
|||||||
"<nick>...",
|
"<nick>...",
|
||||||
handle_command_op, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
handle_command_op, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
||||||
{ "deop", "Remove channel operator status",
|
{ "deop", "Remove channel operator status",
|
||||||
"<nick>...",
|
"[<nick>...]",
|
||||||
handle_command_deop, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
handle_command_deop, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
||||||
{ "voice", "Give voice",
|
{ "voice", "Give voice",
|
||||||
"<nick>...",
|
"<nick>...",
|
||||||
handle_command_voice, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
handle_command_voice, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
||||||
{ "devoice", "Remove voice",
|
{ "devoice", "Remove voice",
|
||||||
"<nick>...",
|
"[<nick>...]",
|
||||||
handle_command_devoice, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
handle_command_devoice, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
|
||||||
|
|
||||||
{ "mode", "Change mode",
|
{ "mode", "Change mode",
|
||||||
@@ -13722,12 +13885,15 @@ on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
|
|||||||
while (try_reap_child (ctx))
|
while (try_reap_child (ctx))
|
||||||
;
|
;
|
||||||
|
|
||||||
if (g_termination_requested && !ctx->quitting)
|
if (g_termination_requested)
|
||||||
initiate_quit (ctx, NULL);
|
{
|
||||||
|
g_termination_requested = false;
|
||||||
|
request_quit (ctx, NULL);
|
||||||
|
}
|
||||||
if (g_winch_received)
|
if (g_winch_received)
|
||||||
{
|
{
|
||||||
redraw_screen (ctx);
|
|
||||||
g_winch_received = false;
|
g_winch_received = false;
|
||||||
|
redraw_screen (ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,3 +85,9 @@
|
|||||||
482 IRC_ERR_CHANOPRIVSNEEDED "%s :You're not channel operator"
|
482 IRC_ERR_CHANOPRIVSNEEDED "%s :You're not channel operator"
|
||||||
501 IRC_ERR_UMODEUNKNOWNFLAG ":Unknown MODE flag"
|
501 IRC_ERR_UMODEUNKNOWNFLAG ":Unknown MODE flag"
|
||||||
502 IRC_ERR_USERSDONTMATCH ":Cannot change mode for other users"
|
502 IRC_ERR_USERSDONTMATCH ":Cannot change mode for other users"
|
||||||
|
902 IRC_ERR_NICKLOCKED ":You must use a nick assigned to you"
|
||||||
|
903 IRC_RPL_SASLSUCCESS ":SASL authentication successful"
|
||||||
|
904 IRC_ERR_SASLFAIL ":SASL authentication failed"
|
||||||
|
905 IRC_ERR_SASLTOOLONG ":SASL message too long"
|
||||||
|
906 IRC_ERR_SASLABORTED ":SASL authentication aborted"
|
||||||
|
907 IRC_ERR_SASLALREADY ":You have already authenticated using SASL"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
--
|
--
|
||||||
-- censor.lua: black out certain users' messages
|
-- censor.lua: black out certain users' messages
|
||||||
--
|
--
|
||||||
-- Copyright (c) 2016, Přemysl Eric Janouch <p@janouch.name>
|
-- Copyright (c) 2016 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||||
--
|
--
|
||||||
-- Permission to use, copy, modify, and/or distribute this software for any
|
-- Permission to use, copy, modify, and/or distribute this software for any
|
||||||
-- purpose with or without fee is hereby granted.
|
-- purpose with or without fee is hereby granted.
|
||||||
@@ -38,6 +38,7 @@ local read_masks = function (v)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local quote
|
||||||
degesch.setup_config {
|
degesch.setup_config {
|
||||||
masks = {
|
masks = {
|
||||||
type = "string_array",
|
type = "string_array",
|
||||||
@@ -45,13 +46,29 @@ degesch.setup_config {
|
|||||||
comment = "user masks (optionally \"/#channel\") to censor",
|
comment = "user masks (optionally \"/#channel\") to censor",
|
||||||
on_change = read_masks
|
on_change = read_masks
|
||||||
},
|
},
|
||||||
|
quote = {
|
||||||
|
type = "string",
|
||||||
|
default = "\"\\x0301,01\"",
|
||||||
|
comment = "formatting prefix for censored messages",
|
||||||
|
on_change = function (v) quote = v end
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local decolor = function (text)
|
||||||
|
local rebuilt, last = {""}, 1
|
||||||
|
for start in text:gmatch ('()\x03') do
|
||||||
|
table.insert (rebuilt, text:sub (last, start - 1))
|
||||||
|
local sub = text:sub (start + 1)
|
||||||
|
last = start + (sub:match ('^%d%d?,%d%d?()') or sub:match ('^%d?%d?()'))
|
||||||
|
end
|
||||||
|
return table.concat (rebuilt) .. text:sub (last)
|
||||||
|
end
|
||||||
|
|
||||||
local censor = function (line)
|
local censor = function (line)
|
||||||
-- Taking a shortcut to avoid lengthy message reassembly
|
-- Taking a shortcut to avoid lengthy message reassembly
|
||||||
local start, text = line:match ("^(.- PRIVMSG .- :)(.*)$")
|
local start, text = line:match ("^(.- PRIVMSG .- :)(.*)$")
|
||||||
local ctcp, rest = text:match ("^(\x01%g+ )(.*)")
|
local ctcp, rest = text:match ("^(\x01%g+ )(.*)")
|
||||||
text = ctcp and ctcp .. "\x0301,01" .. rest or "\x0301,01" .. text
|
text = ctcp and ctcp .. quote .. decolor (rest) or quote .. decolor (text)
|
||||||
return start .. text
|
return start .. text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
23
test-nick-colors
Executable file
23
test-nick-colors
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Check whether the terminal colours filtered by our algorithm are legible
|
||||||
|
export example=$(
|
||||||
|
tcc "-run -lm" - <<-EOF
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
$(perl -0777 -ne 'print $& if /^.*?\nfilter_color(?s:.*?)^}$/m' degesch.c)
|
||||||
|
|
||||||
|
void main () {
|
||||||
|
size_t len = 0;
|
||||||
|
int *table = filter_color_cube_for_acceptable_nick_colors (&len);
|
||||||
|
for (size_t i = 0; i < len; i++)
|
||||||
|
printf ("<@\\x1b[38;5;%dmIRCuser\\x1b[m> I'm typing!\n", table[i]);
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
# Both should give acceptable results,
|
||||||
|
# which results in a bad compromise that the main author himself needs
|
||||||
|
xterm -bg black -fg white -e 'echo $example; cat' &
|
||||||
|
xterm -bg white -fg black -e 'echo $example; cat' &
|
||||||
Reference in New Issue
Block a user