fix: OTR/presence/OMEMO correctness, stanza-id disco gate, build hardening

- OTR: strip the whitespace tag by shifting the full message tail incl. the NUL, not tag_length bytes, so the body is no longer duplicated for messages longer than the tag.
- presence: snapshot the resource fields before connection_add_available_resource() takes ownership, removing a use-after-free in the own-presence path.
- OMEMO: propagate _omemo_finalize_identity_load() failure on connect (log + cons_show_error + stop) instead of leaving OMEMO silently unavailable.
- OMEMO: guard NULL fingerprint decode in _omemo_fingerprint_decode / omemo_is_trusted_identity / omemo_trust and log every decode failure instead of failing silently.
- stanza-id: gate XEP-0359 dedup on disco urn:xmpp:sid:0 (the `by` JID or its domain), falling back to no-dedup when caps are unknown; add functional tests for trusted vs untrusted server.
- accounts: narrow the group-name sanitizer to the characters GKeyFile forbids in headers ([ ] \n \r), keeping `=` and `#`, so read/write stays symmetric.
- build: re-introduce compiler/sanitizer flags in a Pikaur-safe form (opt-in sanitizers, -Wsign-compare) and fix the resulting -Wsign-compare warnings (incl. proftest _mkdir_recursive).fix: OTR/presence/OMEMO correctness, stanza-id disco gate, build hardening

Author: jabber.developer2 <jabber.developer2@jabber.space>
This commit is contained in:
2026-06-20 10:30:23 +00:00
parent 2d3d1ced71
commit 830479cf20
19 changed files with 225 additions and 50 deletions

View File

@@ -117,7 +117,7 @@ jid_is_valid(const gchar* const str)
// Localpart validation
if (at) {
size_t local_len = at - str;
size_t local_len = g_diff_to_gsize(at, str);
if (local_len == 0 || local_len > JID_MAX_PART_LEN) {
return FALSE;
}
@@ -135,7 +135,7 @@ jid_is_valid(const gchar* const str)
// Resourcepart validation if present
if (slash) {
domain_len = slash - domain_start;
domain_len = g_diff_to_gsize(slash, domain_start);
size_t resource_len = strlen(slash + 1);
if (resource_len > JID_MAX_PART_LEN) {
return FALSE;

View File

@@ -65,6 +65,7 @@ static void _handle_pubsub(xmpp_stanza_t* const stanza, xmpp_stanza_t* const eve
static gboolean _handle_form(xmpp_stanza_t* const stanza);
static gboolean _handle_jingle_message(xmpp_stanza_t* const stanza);
static gboolean _should_ignore_based_on_silence(xmpp_stanza_t* const stanza);
static gboolean _stanza_id_by_trusted(const char* by);
#ifdef HAVE_OMEMO
static void _receive_omemo(xmpp_stanza_t* const stanza, ProfMessage* message);
#endif
@@ -1088,7 +1089,7 @@ _handle_groupchat(xmpp_stanza_t* const stanza)
xmpp_stanza_t* stanzaidst = xmpp_stanza_get_child_by_name_and_ns(stanza, STANZA_NAME_STANZA_ID, STANZA_NS_STABLE_ID);
if (stanzaidst) {
const char* by = xmpp_stanza_get_attribute(stanzaidst, "by");
if (by && (g_strcmp0(by, from_jid->barejid) == 0)) {
if (by && g_strcmp0(by, from_jid->barejid) == 0 && _stanza_id_by_trusted(by)) {
stanzaid = (char*)xmpp_stanza_get_attribute(stanzaidst, STANZA_ATTR_ID);
if (stanzaid) {
message->stanzaid = strdup(stanzaid);
@@ -1397,7 +1398,7 @@ _handle_chat(xmpp_stanza_t* const stanza, gboolean is_mam, gboolean is_carbon, c
xmpp_stanza_t* stanzaidst = xmpp_stanza_get_child_by_name_and_ns(stanza, STANZA_NAME_STANZA_ID, STANZA_NS_STABLE_ID);
if (stanzaidst) {
const char* by = xmpp_stanza_get_attribute(stanzaidst, "by");
if (by && equals_our_barejid(by)) {
if (by && equals_our_barejid(by) && _stanza_id_by_trusted(by)) {
stanzaid = (char*)xmpp_stanza_get_attribute(stanzaidst, STANZA_ATTR_ID);
if (stanzaid) {
message->stanzaid = strdup(stanzaid);
@@ -1735,3 +1736,22 @@ _should_ignore_based_on_silence(xmpp_stanza_t* const stanza)
}
return FALSE;
}
// XEP-0359 §6 disco-gate; falls back to domain since that's what disco is run against.
static gboolean
_stanza_id_by_trusted(const char* by)
{
if (!by) {
return FALSE;
}
GHashTable* features = connection_get_features(by);
if (features && g_hash_table_contains(features, STANZA_NS_STABLE_ID)) {
return TRUE;
}
const char* at = strchr(by, '@');
if (!at) {
return FALSE;
}
features = connection_get_features(at + 1);
return features && g_hash_table_contains(features, STANZA_NS_STABLE_ID);
}

View File

@@ -707,7 +707,7 @@ muc_autocomplete(ProfWin* window, const char* const input, gboolean previous)
} else {
search_str = last_space + 1;
if (!chat_room->autocomplete_prefix) {
chat_room->autocomplete_prefix = g_strndup(input, search_str - input);
chat_room->autocomplete_prefix = g_strndup(input, g_diff_to_gsize(search_str, input));
}
}

View File

@@ -621,15 +621,20 @@ _available_handler(xmpp_stanza_t* const stanza)
}
if (g_strcmp0(xmpp_presence->jid->barejid, my_jid->barejid) == 0) {
connection_add_available_resource(resource);
// Copy what we read before connection_add_available_resource() takes ownership.
Resource* resource_for_roster = resource_copy(resource);
auto_gchar gchar* resource_name = g_strdup(resource->name);
auto_gchar gchar* resource_status = resource->status ? g_strdup(resource->status) : NULL;
int resource_priority = resource->priority;
resource_presence_t resource_presence = resource->presence;
connection_add_available_resource(resource);
sv_ev_contact_online(xmpp_presence->jid->barejid, resource_for_roster, xmpp_presence->last_activity, pgpsig);
const char* account_name = session_get_account_name();
int max_sessions = accounts_get_max_sessions(account_name);
if (max_sessions > 0) {
auto_gchar gchar* cur_resource = accounts_get_resource(account_name);
int res_count = connection_count_available_resources();
if (res_count > max_sessions && g_strcmp0(cur_resource, resource->name)) {
if (res_count > max_sessions && g_strcmp0(cur_resource, resource_name)) {
ProfWin* console = wins_get_console();
ProfWin* current_window = wins_get_current();
auto_gchar gchar* message = g_strdup_printf("Max sessions alarm! (%d/%d devices in use)", res_count, max_sessions);
@@ -639,14 +644,14 @@ _available_handler(xmpp_stanza_t* const stanza)
}
notify(message, 10000, "Security alert");
const char* resource_presence = string_from_resource_presence(resource->presence);
win_print(console, THEME_DEFAULT, "|", "New device info: \n %s (%d), %s", resource->name, resource->priority, resource_presence);
const char* resource_presence_str = string_from_resource_presence(resource_presence);
win_print(console, THEME_DEFAULT, "|", "New device info: \n %s (%d), %s", resource_name, resource_priority, resource_presence_str);
if (resource->status) {
win_append(console, THEME_DEFAULT, ", \"%s\"", resource->status);
if (resource_status) {
win_append(console, THEME_DEFAULT, ", \"%s\"", resource_status);
}
win_appendln(console, THEME_DEFAULT, "");
auto_jid Jid* jidp = jid_create_from_bare_and_resource(my_jid->barejid, resource->name);
auto_jid Jid* jidp = jid_create_from_bare_and_resource(my_jid->barejid, resource_name);
EntityCapabilities* caps = caps_lookup(jidp->fulljid);
if (caps) {