From 4d6c77704cd5095da2285c5e533a5380102a23df Mon Sep 17 00:00:00 2001 From: deepend Date: Sun, 25 Jan 2026 16:51:43 -0700 Subject: [PATCH 1/3] Added STS profile data structures plus serialize/deserialize helpers for storing profile state in a compact string form. Registered the new STS source file in the Meson and Visual Studio build definitions. --- src/common/common.vcxproj | 1 + src/common/common.vcxproj.filters | 3 + src/common/meson.build | 1 + src/common/sts.c | 152 ++++++++++++++++++++++++++++++ src/common/sts.h | 43 +++++++++ 5 files changed, 200 insertions(+) create mode 100644 src/common/sts.c create mode 100644 src/common/sts.h diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index ab83e2e5..4f4c6407 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -71,6 +71,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index d3d87016..f3b3b822 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -172,6 +172,9 @@ Source Files + + Source Files + Source Files diff --git a/src/common/meson.build b/src/common/meson.build index 76a3c5ca..f5556fe4 100644 --- a/src/common/meson.build +++ b/src/common/meson.build @@ -18,6 +18,7 @@ common_sources = [ 'scram.c', 'server.c', 'servlist.c', + 'sts.c', 'text.c', 'tree.c', 'url.c', diff --git a/src/common/sts.c b/src/common/sts.c new file mode 100644 index 00000000..d529945d --- /dev/null +++ b/src/common/sts.c @@ -0,0 +1,152 @@ +/* ZoiteChat + * Copyright (C) 2024 + * + * 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 + +#include "sts.h" + +static gboolean +sts_parse_bool (const char *value) +{ + if (!value || !*value) + { + return FALSE; + } + + return g_ascii_strcasecmp (value, "1") == 0 || + g_ascii_strcasecmp (value, "true") == 0 || + g_ascii_strcasecmp (value, "yes") == 0; +} + +sts_profile * +sts_profile_new (const char *host, guint16 port, time_t expires_at, gboolean preload) +{ + sts_profile *profile = g_new0 (sts_profile, 1); + + profile->host = g_strdup (host); + profile->port = port; + profile->expires_at = expires_at; + profile->preload = preload; + + return profile; +} + +void +sts_profile_free (sts_profile *profile) +{ + if (!profile) + { + return; + } + + g_free (profile->host); + g_free (profile); +} + +char * +sts_profile_serialize (const sts_profile *profile) +{ + GString *serialized; + char *escaped_host; + char *result; + + if (!profile || !profile->host || !*profile->host) + { + return NULL; + } + + escaped_host = g_strdup (profile->host); + serialized = g_string_new (escaped_host); + g_free (escaped_host); + + g_string_append_printf (serialized, " %u %" G_GINT64_FORMAT, + profile->port, (gint64) profile->expires_at); + + if (profile->preload) + { + g_string_append (serialized, " 1"); + } + + result = g_string_free (serialized, FALSE); + return result; +} + +sts_profile * +sts_profile_deserialize (const char *serialized) +{ + char *host = NULL; + guint16 port = 0; + gint64 expires_at = -1; + gboolean preload = FALSE; + gchar **pairs = NULL; + int i = 0; + + if (!serialized || !*serialized) + { + return NULL; + } + + pairs = g_strsplit_set (serialized, " \t", -1); + { + const char *fields[4] = {0}; + int field_count = 0; + + for (i = 0; pairs[i]; i++) + { + if (!pairs[i][0]) + { + continue; + } + + if (field_count < 4) + { + fields[field_count++] = pairs[i]; + } + } + + if (field_count >= 3) + { + host = g_strdup (fields[0]); + + gint64 port_value = g_ascii_strtoll (fields[1], NULL, 10); + if (port_value > 0 && port_value <= G_MAXUINT16) + { + port = (guint16) port_value; + } + + expires_at = g_ascii_strtoll (fields[2], NULL, 10); + + if (field_count >= 4) + { + preload = sts_parse_bool (fields[3]); + } + } + } + + if (!host || !*host || expires_at < 0) + { + g_free (host); + g_strfreev (pairs); + return NULL; + } + + sts_profile *profile = sts_profile_new (host, port, (time_t) expires_at, preload); + g_free (host); + g_strfreev (pairs); + return profile; +} diff --git a/src/common/sts.h b/src/common/sts.h new file mode 100644 index 00000000..2e60bae3 --- /dev/null +++ b/src/common/sts.h @@ -0,0 +1,43 @@ +/* ZoiteChat + * Copyright (C) 2024 + * + * 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 + */ + +#ifndef HEXCHAT_STS_H +#define HEXCHAT_STS_H + +#include +#include + +G_BEGIN_DECLS + +typedef struct sts_profile +{ + char *host; + guint16 port; + time_t expires_at; + gboolean preload; +} sts_profile; + +sts_profile *sts_profile_new (const char *host, guint16 port, time_t expires_at, gboolean preload); +void sts_profile_free (sts_profile *profile); + +char *sts_profile_serialize (const sts_profile *profile); +sts_profile *sts_profile_deserialize (const char *serialized); + +G_END_DECLS + +#endif From a0f0c48bc5253c732884367bc8fcb477433ac0c2 Mon Sep 17 00:00:00 2001 From: deepend Date: Sun, 25 Jan 2026 17:20:53 -0700 Subject: [PATCH 2/3] 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. --- src/common/inbound.c | 35 +++ src/common/server.c | 17 +- src/common/sts.c | 491 ++++++++++++++++++++++++++++++++++++++++- src/common/sts.h | 12 +- src/common/zoitechat.c | 3 + src/common/zoitechat.h | 3 + 6 files changed, 552 insertions(+), 9 deletions(-) diff --git a/src/common/inbound.c b/src/common/inbound.c index 3abb9d45..fc1fa3cc 100644 --- a/src/common/inbound.c +++ b/src/common/inbound.c @@ -43,6 +43,7 @@ #include "inbound.h" #include "server.h" #include "servlist.h" +#include "sts.h" #include "text.h" #include "ctcp.h" #include "zoitechatc.h" @@ -1722,6 +1723,25 @@ 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); @@ -1819,6 +1839,7 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str, { 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; @@ -1853,6 +1874,15 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str, 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 @@ -1888,6 +1918,11 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str, g_strfreev (extensions); + if (sts_upgrade_triggered) + { + return; + } + if (want_cap) { /* buffer + 9 = emit buffer without "CAP REQ :" */ diff --git a/src/common/server.c b/src/common/server.c index f916771b..aa5b8ff4 100644 --- a/src/common/server.c +++ b/src/common/server.c @@ -54,6 +54,7 @@ #include "proto-irc.h" #include "servlist.h" #include "server.h" +#include "sts.h" #ifdef USE_OPENSSL #include /* SSL_() */ @@ -1034,6 +1035,8 @@ server_disconnect (session * sess, int sendquit, int err) server_sendquit (sess); } + sts_reschedule_on_disconnect (serv); + fe_server_event (serv, FE_SE_DISCONNECT, 0); /* close all sockets & io tags */ @@ -1588,6 +1591,15 @@ server_connect (server *serv, char *hostname, int port, int no_login) int pid, read_des[2]; session *sess = serv->server_session; + if (!hostname[0]) + return; + + safe_strcpy (serv->sts_host, hostname, sizeof (serv->sts_host)); + if (!sts_apply_policy_for_connection (serv, hostname, &port)) + { + return; + } + #ifdef USE_OPENSSL if (!serv->ctx && serv->use_ssl) { @@ -1599,9 +1611,6 @@ server_connect (server *serv, char *hostname, int port, int no_login) } #endif - if (!hostname[0]) - return; - if (port < 1 || port > 65535) { /* use default port for this server type */ @@ -1842,6 +1851,8 @@ server_set_defaults (server *serv) serv->have_sasl = FALSE; serv->have_except = FALSE; serv->have_invite = FALSE; + serv->sts_duration_seen = FALSE; + serv->sts_upgrade_in_progress = FALSE; } char * diff --git a/src/common/sts.c b/src/common/sts.c index d529945d..9d57a905 100644 --- a/src/common/sts.c +++ b/src/common/sts.c @@ -17,9 +17,24 @@ */ #include +#include +#include +#ifdef WIN32 +#include +#else +#include +#endif + +#include "zoitechat.h" +#include "cfgfiles.h" +#include "util.h" +#include "text.h" #include "sts.h" +static GHashTable *sts_profiles = NULL; +static gboolean sts_loaded = FALSE; + static gboolean sts_parse_bool (const char *value) { @@ -34,13 +49,14 @@ sts_parse_bool (const char *value) } sts_profile * -sts_profile_new (const char *host, guint16 port, time_t expires_at, gboolean preload) +sts_profile_new (const char *host, guint16 port, time_t expires_at, guint64 duration, gboolean preload) { sts_profile *profile = g_new0 (sts_profile, 1); profile->host = g_strdup (host); profile->port = port; profile->expires_at = expires_at; + profile->duration = duration; profile->preload = preload; return profile; @@ -77,6 +93,11 @@ sts_profile_serialize (const sts_profile *profile) g_string_append_printf (serialized, " %u %" G_GINT64_FORMAT, profile->port, (gint64) profile->expires_at); + if (profile->duration > 0) + { + g_string_append_printf (serialized, " %" G_GUINT64_FORMAT, profile->duration); + } + if (profile->preload) { g_string_append (serialized, " 1"); @@ -92,7 +113,9 @@ sts_profile_deserialize (const char *serialized) char *host = NULL; guint16 port = 0; gint64 expires_at = -1; + guint64 duration = 0; gboolean preload = FALSE; + gboolean duration_seen = FALSE; gchar **pairs = NULL; int i = 0; @@ -103,7 +126,7 @@ sts_profile_deserialize (const char *serialized) pairs = g_strsplit_set (serialized, " \t", -1); { - const char *fields[4] = {0}; + const char *fields[5] = {0}; int field_count = 0; for (i = 0; pairs[i]; i++) @@ -113,7 +136,7 @@ sts_profile_deserialize (const char *serialized) continue; } - if (field_count < 4) + if (field_count < 5) { fields[field_count++] = pairs[i]; } @@ -133,7 +156,21 @@ sts_profile_deserialize (const char *serialized) if (field_count >= 4) { - preload = sts_parse_bool (fields[3]); + if (field_count == 4 && sts_parse_bool (fields[3])) + { + preload = TRUE; + } + else + { + duration = g_ascii_strtoull (fields[3], NULL, 10); + duration_seen = TRUE; + } + } + + if (field_count >= 5) + { + duration_seen = TRUE; + preload = sts_parse_bool (fields[4]); } } } @@ -145,8 +182,452 @@ sts_profile_deserialize (const char *serialized) return NULL; } - sts_profile *profile = sts_profile_new (host, port, (time_t) expires_at, preload); + if (!duration_seen && duration == 0 && expires_at > 0) + { + time_t now = time (NULL); + if (expires_at > now) + { + duration = (guint64) (expires_at - now); + } + } + + sts_profile *profile = sts_profile_new (host, port, (time_t) expires_at, duration, preload); g_free (host); g_strfreev (pairs); return profile; } + +static char * +sts_normalize_host (const char *host) +{ + char *normalized; + gsize len; + + if (!host || !*host) + { + return NULL; + } + + normalized = g_ascii_strdown (host, -1); + g_strstrip (normalized); + len = strlen (normalized); + + if (len > 2 && normalized[0] == '[' && normalized[len - 1] == ']') + { + char *trimmed = g_strndup (normalized + 1, len - 2); + g_free (normalized); + normalized = trimmed; + } + + return normalized; +} + +static void +sts_profiles_ensure (void) +{ + if (!sts_profiles) + { + sts_profiles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) sts_profile_free); + } +} + +static void +sts_profile_store (sts_profile *profile) +{ + char *normalized; + + if (!profile || !profile->host) + { + sts_profile_free (profile); + return; + } + + sts_profiles_ensure (); + normalized = sts_normalize_host (profile->host); + if (!normalized) + { + sts_profile_free (profile); + return; + } + + g_hash_table_replace (sts_profiles, normalized, profile); +} + +static void +sts_profile_remove (const char *host) +{ + char *normalized; + + if (!host) + { + return; + } + + sts_profiles_ensure (); + normalized = sts_normalize_host (host); + if (!normalized) + { + return; + } + + g_hash_table_remove (sts_profiles, normalized); + g_free (normalized); +} + +static sts_profile * +sts_profile_lookup (const char *host, time_t now) +{ + char *normalized; + sts_profile *profile = NULL; + + sts_profiles_ensure (); + normalized = sts_normalize_host (host); + if (!normalized) + { + return NULL; + } + + profile = g_hash_table_lookup (sts_profiles, normalized); + if (profile && profile->expires_at > 0 && profile->expires_at <= now) + { + g_hash_table_remove (sts_profiles, normalized); + profile = NULL; + } + + g_free (normalized); + return profile; +} + +static gboolean +sts_parse_value (const char *value, guint16 *port, guint64 *duration, gboolean *preload, + gboolean *has_port, gboolean *has_duration, gboolean *has_preload) +{ + char **tokens; + gsize i; + + if (!value || !*value) + { + return FALSE; + } + + *has_port = FALSE; + *has_duration = FALSE; + *has_preload = FALSE; + + tokens = g_strsplit (value, ",", -1); + for (i = 0; tokens[i]; i++) + { + char *token = g_strstrip (tokens[i]); + char *equals = strchr (token, '='); + char *key = token; + char *val = NULL; + + if (!*token) + { + continue; + } + + if (equals) + { + *equals = '\0'; + val = equals + 1; + } + + if (!g_ascii_strcasecmp (key, "port")) + { + gint64 port_value; + + if (*has_port || !val) + { + continue; + } + + port_value = g_ascii_strtoll (val, NULL, 10); + if (port_value > 0 && port_value <= G_MAXUINT16) + { + *port = (guint16) port_value; + *has_port = TRUE; + } + } + else if (!g_ascii_strcasecmp (key, "duration")) + { + guint64 duration_value; + + if (*has_duration || !val) + { + continue; + } + + duration_value = g_ascii_strtoull (val, NULL, 10); + *duration = duration_value; + *has_duration = TRUE; + } + else if (!g_ascii_strcasecmp (key, "preload")) + { + if (*has_preload) + { + continue; + } + *preload = TRUE; + *has_preload = TRUE; + } + } + + g_strfreev (tokens); + return TRUE; +} + +void +sts_init (void) +{ + sts_profiles_ensure (); + if (sts_loaded) + { + return; + } + + sts_loaded = TRUE; + { + int fh; + char buf[512]; + + fh = zoitechat_open_file ("sts.conf", O_RDONLY, 0, 0); + if (fh != -1) + { + while (waitline (fh, buf, sizeof buf, FALSE) != -1) + { + if (buf[0] == '#' || buf[0] == '\0') + { + continue; + } + + sts_profile *profile = sts_profile_deserialize (buf); + if (!profile) + { + continue; + } + + if (profile->expires_at <= time (NULL)) + { + sts_profile_free (profile); + continue; + } + + if (profile->duration == 0) + { + sts_profile_free (profile); + continue; + } + + sts_profile_store (profile); + } + close (fh); + } + } +} + +void +sts_save (void) +{ + GHashTableIter iter; + gpointer key; + gpointer value; + int fh; + + sts_profiles_ensure (); + fh = zoitechat_open_file ("sts.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh == -1) + { + return; + } + + g_hash_table_iter_init (&iter, sts_profiles); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + sts_profile *profile = value; + char *serialized; + + if (!profile || profile->expires_at <= time (NULL) || profile->duration == 0) + { + continue; + } + + serialized = sts_profile_serialize (profile); + if (serialized) + { + write (fh, serialized, strlen (serialized)); + write (fh, "\n", 1); + g_free (serialized); + } + } + + close (fh); +} + +void +sts_cleanup (void) +{ + if (!sts_profiles) + { + return; + } + + sts_save (); + g_hash_table_destroy (sts_profiles); + sts_profiles = NULL; + sts_loaded = FALSE; +} + +gboolean +sts_apply_policy_for_connection (struct server *serv, const char *hostname, int *port) +{ + sts_profile *profile; + time_t now; + + if (!hostname || !*hostname || !port) + { + return TRUE; + } + + sts_init (); + sts_profiles_ensure (); + now = time (NULL); + profile = sts_profile_lookup (hostname, now); + if (!profile) + { + return TRUE; + } + + if (profile->port == 0) + { + sts_profile_remove (hostname); + return TRUE; + } + +#ifdef USE_OPENSSL + serv->use_ssl = TRUE; + if (profile->port > 0) + { + *port = profile->port; + } + return TRUE; +#else + PrintTextf (serv->server_session, + _("STS policy requires TLS for %s, but TLS is not available.\n"), + hostname); + return FALSE; +#endif +} + +gboolean +sts_handle_capability (struct server *serv, const char *value) +{ + guint16 port = 0; + guint64 duration = 0; + gboolean preload = FALSE; + gboolean has_port = FALSE; + gboolean has_duration = FALSE; + gboolean has_preload = FALSE; + const char *hostname; + + if (!serv || !value) + { + return FALSE; + } + + sts_init (); + if (!sts_parse_value (value, &port, &duration, &preload, + &has_port, &has_duration, &has_preload)) + { + return FALSE; + } + + hostname = serv->sts_host[0] ? serv->sts_host : serv->servername; + if (!hostname || !*hostname) + { + return FALSE; + } + + if (!serv->use_ssl) + { + if (!has_port) + { + return FALSE; + } +#ifdef USE_OPENSSL + if (serv->sts_upgrade_in_progress) + { + return TRUE; + } + + serv->sts_upgrade_in_progress = TRUE; + serv->use_ssl = TRUE; + { + char host_copy[128]; + + safe_strcpy (host_copy, hostname, sizeof (host_copy)); + serv->disconnect (serv->server_session, FALSE, -1); + serv->connect (serv, host_copy, (int) port, serv->no_login); + } +#else + PrintTextf (serv->server_session, + _("STS upgrade requested for %s, but TLS is not available.\n"), + hostname); +#endif + return TRUE; + } + + if (!has_duration) + { + return FALSE; + } + + if (duration == 0) + { + sts_profile_remove (hostname); + serv->sts_duration_seen = FALSE; + return FALSE; + } + + { + time_t now = time (NULL); + time_t expires_at = now + (time_t) duration; + guint16 effective_port = serv->port > 0 ? (guint16) serv->port : port; + sts_profile *profile; + + if (effective_port == 0) + { + return FALSE; + } + + profile = sts_profile_new (hostname, effective_port, expires_at, duration, + has_preload ? preload : FALSE); + sts_profile_store (profile); + serv->sts_duration_seen = TRUE; + } + + return FALSE; +} + +void +sts_reschedule_on_disconnect (struct server *serv) +{ + sts_profile *profile; + time_t now; + + if (!serv || !serv->sts_duration_seen) + { + return; + } + + sts_init (); + now = time (NULL); + profile = sts_profile_lookup (serv->sts_host[0] ? serv->sts_host : serv->servername, now); + if (!profile || profile->duration == 0) + { + return; + } + + profile->expires_at = now + (time_t) profile->duration; +} diff --git a/src/common/sts.h b/src/common/sts.h index 2e60bae3..50c00e65 100644 --- a/src/common/sts.h +++ b/src/common/sts.h @@ -24,20 +24,30 @@ G_BEGIN_DECLS +struct server; + typedef struct sts_profile { char *host; guint16 port; time_t expires_at; + guint64 duration; gboolean preload; } sts_profile; -sts_profile *sts_profile_new (const char *host, guint16 port, time_t expires_at, gboolean preload); +sts_profile *sts_profile_new (const char *host, guint16 port, time_t expires_at, guint64 duration, gboolean preload); void sts_profile_free (sts_profile *profile); char *sts_profile_serialize (const sts_profile *profile); sts_profile *sts_profile_deserialize (const char *serialized); +void sts_init (void); +void sts_save (void); +void sts_cleanup (void); +gboolean sts_apply_policy_for_connection (struct server *serv, const char *hostname, int *port); +gboolean sts_handle_capability (struct server *serv, const char *value); +void sts_reschedule_on_disconnect (struct server *serv); + G_END_DECLS #endif diff --git a/src/common/zoitechat.c b/src/common/zoitechat.c index 255d1a23..d1b962b8 100644 --- a/src/common/zoitechat.c +++ b/src/common/zoitechat.c @@ -49,6 +49,7 @@ #include "notify.h" #include "server.h" #include "servlist.h" +#include "sts.h" #include "outbound.h" #include "text.h" #include "url.h" @@ -1200,6 +1201,7 @@ xchat_init (void) sound_load (); notify_load (); ignore_load (); + sts_init (); g_snprintf (buf, sizeof (buf), "NAME %s~%s~\n" "CMD query %%s\n\n"\ @@ -1352,6 +1354,7 @@ zoitechat_exit (void) sound_save (); notify_save (); ignore_save (); + sts_cleanup (); free_sessions (); chanopt_save_all (TRUE); servlist_cleanup (); diff --git a/src/common/zoitechat.h b/src/common/zoitechat.h index 30727521..810394d6 100644 --- a/src/common/zoitechat.h +++ b/src/common/zoitechat.h @@ -514,6 +514,7 @@ typedef struct server int joindelay_tag; /* waiting before we send JOIN */ char hostname[128]; /* real ip number */ char servername[128]; /* what the server says is its name */ + char sts_host[128]; char password[1024]; char nick[NICKLEN]; char linebuf[8704]; /* RFC says 512 chars including \r\n, IRCv3 message tags add 8191, plus the NUL byte */ @@ -588,6 +589,8 @@ typedef struct server unsigned int have_sasl:1; /* SASL capability */ unsigned int have_except:1; /* ban exemptions +e */ unsigned int have_invite:1; /* invite exemptions +I */ + unsigned int sts_duration_seen:1; + unsigned int sts_upgrade_in_progress:1; unsigned int have_cert:1; /* have loaded a cert */ unsigned int use_who:1; /* whether to use WHO command to get dcc_ip */ unsigned int sasl_mech; /* mechanism for sasl auth */ From bc1d2e5f7a587f5c957aaaa522699e352aa5542d Mon Sep 17 00:00:00 2001 From: deepend Date: Sun, 25 Jan 2026 18:26:20 -0700 Subject: [PATCH 3/3] Updated STS upgrade handling to fall back to the current connection port when servers omit a port in the STS capability, enabling TLS upgrades for non-TLS connections in that case. --- src/common/sts.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/common/sts.c b/src/common/sts.c index 9d57a905..d5dae4bf 100644 --- a/src/common/sts.c +++ b/src/common/sts.c @@ -553,7 +553,15 @@ sts_handle_capability (struct server *serv, const char *value) { if (!has_port) { - return FALSE; + if (serv->port > 0) + { + port = (guint16) serv->port; + has_port = TRUE; + } + else + { + return FALSE; + } } #ifdef USE_OPENSSL if (serv->sts_upgrade_in_progress)