fix: more issue #112 follow-ups (stanza-id disco gate, account sanitizer)
All checks were successful
CI Code / Check spelling (pull_request) Successful in 19s
CI Code / Check coding style (pull_request) Successful in 32s
CI Code / Linux (debian) (pull_request) Successful in 4m50s
CI Code / Linux (ubuntu) (pull_request) Successful in 5m7s
CI Code / Code Coverage (pull_request) Successful in 7m14s
CI Code / Linux (arch) (pull_request) Successful in 10m31s

- xmpp: gate XEP-0359 stanza-id dedup on disco urn:xmpp:sid:0. Before
  honoring <stanza-id by='…'/> for archive_id dedup, check that the entity
  in `by` (or its domain — that's what profanity disco's on connect)
  announces the namespace; fall back to no-dedup when caps are unknown,
  matching the XEP §6 disco-gate (#1).
- accounts: narrow group-name sanitizer to only the characters GKeyFile
  actually forbids in group headers (`[`, `]`, `\n`, `\r`); `=` and `#` are
  valid inside a header and stay (#2).
- functional tests cover both branches of the disco gate — replayed
  stanza-id is flagged as duplicate when sid:0 is announced, and not
  flagged when it isn't.

Refs #112
This commit is contained in:
2026-06-05 14:49:09 +03:00
parent 77cc33eedd
commit 148b022b35
5 changed files with 97 additions and 4 deletions

View File

@@ -43,8 +43,8 @@ _sanitize_account_name(const char* const name)
gchar* sanitized = g_strdup(name);
gchar* p = sanitized;
while (*p) {
// GKeyFile special characters: [ ] = # \n \r
if (*p == '[' || *p == ']' || *p == '=' || *p == '#' || *p == '\n' || *p == '\r') {
// GKeyFile group header forbids these characters.
if (*p == '[' || *p == ']' || *p == '\n' || *p == '\r') {
*p = '_';
}
p++;

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

@@ -253,6 +253,10 @@ main(int argc, char* argv[])
PROF_FUNC_TEST(message_receive_console),
PROF_FUNC_TEST(message_receive_chatwin),
/* XEP-0359 disco gate for stanza-id trust */
PROF_FUNC_TEST(stanza_id_dedup_fires_when_server_announces_sid0),
PROF_FUNC_TEST(stanza_id_not_trusted_when_server_does_not_announce_sid0),
#ifdef HAVE_SQLITE
/* MUC (groupchat) database — export/import of type="muc" messages */
PROF_FUNC_TEST(muc_export_sqlite_to_flatfile),

View File

@@ -56,3 +56,70 @@ message_receive_chatwin(void **state)
assert_true(prof_output_regex("someuser@chatserv.org/laptop: .+How are you?"));
}
// XEP-0359 disco gate: server announces urn:xmpp:sid:0 -> stanza-id trusted -> replay flagged as duplicate.
void
stanza_id_dedup_fires_when_server_announces_sid0(void **state)
{
stbbr_for_query("http://jabber.org/protocol/disco#info",
"<iq type='result' to='stabber@localhost/profanity' from='localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='server' type='im' name='TestServer'/>"
"<feature var='urn:xmpp:sid:0'/>"
"</query>"
"</iq>"
);
prof_connect();
stbbr_send(
"<message id='m-trust-1' to='stabber@localhost' from='someuser@chatserv.org/laptop' type='chat'>"
"<body>first</body>"
"<stanza-id xmlns='urn:xmpp:sid:0' by='stabber@localhost' id='archive-id-42'/>"
"</message>"
);
assert_true(prof_output_exact("<< chat message: someuser@chatserv.org/laptop (win 2)"));
stbbr_send(
"<message id='m-trust-2' to='stabber@localhost' from='someuser@chatserv.org/laptop' type='chat'>"
"<body>replay</body>"
"<stanza-id xmlns='urn:xmpp:sid:0' by='stabber@localhost' id='archive-id-42'/>"
"</message>"
);
assert_true(prof_output_exact("Got a message with duplicate (server-generated) stanza-id from someuser@chatserv.org/laptop."));
}
// XEP-0359 disco gate: server does NOT announce urn:xmpp:sid:0 -> stanza-id untrusted -> no replay error.
void
stanza_id_not_trusted_when_server_does_not_announce_sid0(void **state)
{
stbbr_for_query("http://jabber.org/protocol/disco#info",
"<iq type='result' to='stabber@localhost/profanity' from='localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='server' type='im' name='TestServer'/>"
"<feature var='urn:xmpp:ping'/>"
"</query>"
"</iq>"
);
prof_connect();
stbbr_send(
"<message id='m-untrust-1' to='stabber@localhost' from='someuser@chatserv.org/laptop' type='chat'>"
"<body>first</body>"
"<stanza-id xmlns='urn:xmpp:sid:0' by='stabber@localhost' id='archive-id-77'/>"
"</message>"
);
assert_true(prof_output_exact("<< chat message: someuser@chatserv.org/laptop (win 2)"));
stbbr_send(
"<message id='m-untrust-2' to='stabber@localhost' from='someuser@chatserv.org/laptop' type='chat'>"
"<body>replay</body>"
"<stanza-id xmlns='urn:xmpp:sid:0' by='stabber@localhost' id='archive-id-77'/>"
"</message>"
);
prof_timeout(2);
assert_false(prof_output_exact("Got a message with duplicate (server-generated) stanza-id"));
prof_timeout_reset();
}

View File

@@ -1,3 +1,5 @@
void message_send(void **state);
void message_receive_console(void **state);
void message_receive_chatwin(void **state);
void stanza_id_dedup_fires_when_server_announces_sid0(void **state);
void stanza_id_not_trusted_when_server_does_not_announce_sid0(void **state);