negotiate explicitly registered IRC capabilities

This commit is contained in:
2026-10-02 14:15:22 -06:00
parent 9126ce9202
commit c630730a1d
6 changed files with 218 additions and 4 deletions
+74 -4
View File
@@ -41,6 +41,8 @@
#include "network.h"
#include "notify.h"
#include "outbound.h"
#include <gmodule.h>
#include "plugin.h"
#include "inbound.h"
#include "server.h"
#include "servlist.h"
@@ -1962,10 +1964,58 @@ inbound_cap_ack (server *serv, char *nick, char *extensions,
inbound_toggle_caps (serv, extensions, TRUE);
}
/* Split requests only at token boundaries, including plugin-added names. */
static void
inbound_cap_request_append (server *serv, char *buffer, gsize size,
const char *name, time_t timestamp)
{
if (strlen (buffer) + strlen (name) + 1 >= size)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPREQ, serv->server_session,
buffer + 9, NULL, NULL, NULL, 0, timestamp);
tcp_sendf (serv, "%s\r\n", g_strchomp (buffer));
g_strlcpy (buffer, "CAP REQ :", size);
}
g_strlcat (buffer, name, size);
g_strlcat (buffer, " ", size);
}
static void
inbound_plugin_cap_new (server *serv, const char *extensions, time_t timestamp)
{
char buffer[500] = "CAP REQ :";
char **tokens = g_strsplit (extensions, " ", 0);
GHashTable *seen = g_hash_table_new (g_str_hash, g_str_equal);
int i;
gboolean requested = FALSE;
for (i = 0; tokens[i]; i++)
{
char *value = strchr (tokens[i], '=');
if (value)
*value = '\0';
if (plugin_requests_capability (serv, tokens[i]) &&
!g_hash_table_contains (seen, tokens[i]))
{
g_hash_table_add (seen, tokens[i]);
inbound_cap_request_append (serv, buffer, sizeof (buffer), tokens[i], timestamp);
requested = TRUE;
}
}
if (requested)
{
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPREQ, serv->server_session,
buffer + 9, NULL, NULL, NULL, 0, timestamp);
tcp_sendf (serv, "%s\r\n", g_strchomp (buffer));
}
g_hash_table_destroy (seen);
g_strfreev (tokens);
}
void
inbound_cap_new (server *serv, char *nick, char *extensions,
const message_tags_data *tags_data)
{
gboolean sts_upgrade_triggered = FALSE;
if (extensions)
{
char **tokens = g_strsplit (extensions, " ", 0);
@@ -1977,7 +2027,7 @@ inbound_cap_new (server *serv, char *nick, char *extensions,
if (!g_strcmp0 (parts[0], "sts") && parts[1] && parts[1][0])
{
sts_handle_capability (serv, parts[1]);
sts_upgrade_triggered |= sts_handle_capability (serv, parts[1]);
}
g_strfreev (parts);
@@ -1986,6 +2036,9 @@ inbound_cap_new (server *serv, char *nick, char *extensions,
g_strfreev (tokens);
}
if (extensions && !sts_upgrade_triggered)
inbound_plugin_cap_new (serv, extensions, tags_data->timestamp);
EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPACK, serv->server_session, nick, extensions,
NULL, NULL, 0, tags_data->timestamp);
@@ -2116,6 +2169,7 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
gboolean want_cap = FALSE; /* format the CAP REQ string based on previous capabilities being requested or not */
gboolean sts_upgrade_triggered = FALSE;
char **extensions;
GString *requests = g_string_new ("");
int i;
if (g_str_has_prefix (extensions_str, "* "))
@@ -2181,7 +2235,7 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
}
want_cap = TRUE;
serv->waiting_on_sasl = TRUE;
g_strlcat (buffer, "sasl ", sizeof(buffer));
g_string_append (requests, "sasl ");
continue;
}
@@ -2189,28 +2243,44 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
{
if (!g_strcmp0 (extension, supported_caps[x]))
{
g_strlcat (buffer, extension, sizeof(buffer));
g_strlcat (buffer, " ", sizeof(buffer));
g_string_append (requests, extension);
g_string_append_c (requests, ' ');
want_cap = TRUE;
break;
}
}
if (x == G_N_ELEMENTS (supported_caps) && plugin_requests_capability (serv, extension))
{
g_string_append (requests, extension);
g_string_append_c (requests, ' ');
want_cap = TRUE;
}
}
g_strfreev (extensions);
if (sts_upgrade_triggered)
{
g_string_free (requests, TRUE);
return;
}
if (want_cap)
{
char **tokens = g_strsplit (requests->str, " ", 0);
/* Defer every batch until STS has decided whether to reconnect. */
for (i = 0; tokens[i]; i++)
if (tokens[i][0])
inbound_cap_request_append (serv, buffer, sizeof (buffer), tokens[i], tags_data->timestamp);
g_strfreev (tokens);
/* 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));
}
g_string_free (requests, TRUE);
if (!serv->waiting_on_sasl && !serv->waiting_on_cap)
{
/* if we use SASL, CAP END is dealt via raw numerics */
+39
View File
@@ -99,6 +99,7 @@ struct _zoitechat_hook
int tag; /* for timers & FDs only */
int type; /* HOOK_* */
int pri; /* fd */ /* priority / fd for HOOK_FD only */
int cap_connection_id;
int connection_filter;
char *channel_filter;
gboolean context_filtered;
@@ -147,6 +148,7 @@ enum
HOOK_TIMER = 1 << 5, /* timeouts */
HOOK_FD = 1 << 6, /* sockets & fds */
HOOK_PRINT_AFTER = 1 << 8, /* opt-in post-display observers */
HOOK_CAPABILITY = 1 << 9, /* script-owned CAP requests */
HOOK_DELETED = 1 << 7 /* marked for deletion */
};
@@ -355,6 +357,7 @@ plugin_add (session *sess, char *filename, void *handle, void *init_func,
pl->zoitechat_emit_print_attrs = zoitechat_emit_print_attrs;
pl->zoitechat_event_attrs_create = zoitechat_event_attrs_create;
pl->zoitechat_event_attrs_free = zoitechat_event_attrs_free;
pl->zoitechat_register_capability = zoitechat_register_capability;
pl->zoitechat_hook_server_filtered = zoitechat_hook_server_filtered;
pl->zoitechat_hook_print_filtered = zoitechat_hook_print_filtered;
pl->zoitechat_hook_print_after = zoitechat_hook_print_after;
@@ -1114,6 +1117,39 @@ zoitechat_hook_print_filtered (zoitechat_plugin *ph, const char *name, int pri,
HOOK_PRINT_ATTRS, callback, userdata);
}
zoitechat_hook *
zoitechat_register_capability (zoitechat_plugin *ph, const char *name, int flags,
int connection_id, void *userdata)
{
const unsigned char *p;
zoitechat_hook *hook;
if (flags != 0 || connection_id < -1 || !name || !name[0] || strlen (name) > 200 ||
strcmp (name, "sasl") == 0 || strcmp (name, "sts") == 0)
return NULL; /* authentication and transport policy belong to core */
for (p = (const unsigned char *)name; *p; p++)
if (!g_ascii_islower (*p) && !g_ascii_isdigit (*p) &&
*p != '-' && *p != '.' && *p != '/' && *p != '_')
return NULL;
hook = plugin_add_hook (ph, HOOK_CAPABILITY, 0, name, NULL, NULL, 0, userdata);
hook->cap_connection_id = connection_id;
return hook;
}
gboolean
plugin_requests_capability (server *serv, const char *name)
{
GSList *entry;
for (entry = hook_list; entry; entry = entry->next)
{
zoitechat_hook *hook = entry->data;
if (hook && hook->type == HOOK_CAPABILITY &&
(hook->cap_connection_id == -1 || hook->cap_connection_id == serv->id) &&
strcmp (hook->name, name) == 0)
return TRUE;
}
return FALSE;
}
zoitechat_hook *
zoitechat_hook_command (zoitechat_plugin *ph, const char *name, int pri,
zoitechat_cmd_cb *callb, const char *help_text, void *userdata)
@@ -1289,6 +1325,9 @@ zoitechat_get_info (zoitechat_plugin *ph, const char *id)
if (strcmp (id, "api_context_filters") == 0)
return "1";
if (strcmp (id, "api_plugin_caps") == 0)
return "1";
hash = str_hash (id);
/* do the session independant ones first */
switch (hash)
+1
View File
@@ -183,6 +183,7 @@ int plugin_emit_command (session *sess, char *name, char *word[], char *word_eol
int plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[],
time_t server_time);
int plugin_emit_print (session *sess, char *word[], time_t server_time);
gboolean plugin_requests_capability (server *serv, const char *name);
void plugin_emit_print_after (session *sess, char *word[], time_t server_time);
int plugin_emit_dummy_print (session *sess, char *name);
int plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, gunichar key);
+5
View File
@@ -222,6 +222,10 @@ zoitechat_hook *zoitechat_hook_print_filtered (zoitechat_plugin *ph,
int (*callback) (char *word[], zoitechat_event_attrs *attrs, void *userdata),
void *userdata);
/* api_plugin_caps=1: register before CAP LS; flags=0, connection_id=-1 for any. */
zoitechat_hook *zoitechat_register_capability (zoitechat_plugin *ph,
const char *name, int flags, int connection_id, void *userdata);
zoitechat_hook *
zoitechat_hook_command (zoitechat_plugin *ph,
const char *name,
@@ -441,6 +445,7 @@ zoitechat_pluginpref_list (zoitechat_plugin *ph,
#define zoitechat_hook_server ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server)
#define zoitechat_hook_server_attrs ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server_attrs)
#define zoitechat_hook_print ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print)
#define zoitechat_register_capability ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_register_capability)
#define zoitechat_hook_server_filtered ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_server_filtered)
#define zoitechat_hook_print_filtered ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_filtered)
#define zoitechat_hook_print_after ((ZOITECHAT_PLUGIN_HANDLE)->zoitechat_hook_print_after)