Compare commits

..

1 Commits

Author SHA1 Message Date
61a5de6ab9 fix(omemo): bootstrap device list on prosody-class servers
All checks were successful
CI Code / Check coding style (pull_request) Successful in 29s
CI Code / Linux (debian) (pull_request) Successful in 8m38s
CI Code / Check spelling (pull_request) Successful in 14s
CI Code / Linux (ubuntu) (pull_request) Successful in 4m40s
CI Code / Linux (arch) (pull_request) Successful in 12m35s
CI Code / Code Coverage (pull_request) Successful in 10m2s
Two gaps kept /omemo gen from ever publishing on a fresh account:

- _omemo_receive_devicelist() only recognised the legacy code='404'
  error attribute as "node does not exist". Prosody sends the RFC 6120
  <item-not-found/> condition instead, so the empty-list bootstrap never
  ran. Accept the defined condition too, and log the expected bootstrap
  case at debug instead of error.

- connection_request_features() disco'd only the server domain, but
  XEP-0163 PEP services (including pubsub#publish-options) announce
  their features on the account's bare JID. Query the bare JID as well,
  registering it in the feature table, so connection_supports() sees
  PEP features.

Fixes #169
2026-07-21 09:14:59 +03:00
7 changed files with 17 additions and 67 deletions

View File

@@ -25,8 +25,6 @@
#include "ui/ui.h" #include "ui/ui.h"
#include "xmpp/xmpp.h" #include "xmpp/xmpp.h"
extern char** environ;
typedef struct EditorContext typedef struct EditorContext
{ {
gchar* filename; gchar* filename;
@@ -142,22 +140,6 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
GSource* sigchld_warmup = g_child_watch_source_new(getpid()); GSource* sigchld_warmup = g_child_watch_source_new(getpid());
g_source_unref(sigchld_warmup); g_source_unref(sigchld_warmup);
// Build the editor's env without LINES/COLUMNS pre-fork, so the child only
// reassigns environ instead of calling unsetenv() between fork and exec.
// The editor's (n)curses then reads the live window via ioctl(TIOCGWINSZ).
gsize env_len = 0;
while (environ[env_len]) {
env_len++;
}
gchar** editor_env = g_new0(gchar*, env_len + 1);
gsize env_kept = 0;
for (gsize i = 0; i < env_len; i++) {
if (g_str_has_prefix(environ[i], "LINES=") || g_str_has_prefix(environ[i], "COLUMNS=")) {
continue;
}
editor_env[env_kept++] = environ[i];
}
pid_t pid = fork(); pid_t pid = fork();
if (pid == -1) { if (pid == -1) {
log_error("[Editor] Failed to fork: %s", strerror(errno)); log_error("[Editor] Failed to fork: %s", strerror(errno));
@@ -166,14 +148,12 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
ui_resize(); ui_resize();
cons_show_error("Failed to start editor: %s", strerror(errno)); cons_show_error("Failed to start editor: %s", strerror(errno));
g_strfreev(editor_argv); g_strfreev(editor_argv);
g_free(editor_env); g_free(ctx->filename);
g_free(ctx); g_free(ctx);
return TRUE; return TRUE;
} else if (pid == 0) { } else if (pid == 0) {
// Child process: Inherits TTY from parent // Child process: Inherits TTY from parent
environ = editor_env; // live TIOCGWINSZ size, not the inherited LINES/COLUMNS
// SIGTSTP=SIG_DFL lets vim's :stop / Ctrl-Z work; profanity catches // SIGTSTP=SIG_DFL lets vim's :stop / Ctrl-Z work; profanity catches
// the STOPPED state via editor_check_stopped() and drops to the shell. // the STOPPED state via editor_check_stopped() and drops to the shell.
signal(SIGINT, SIG_DFL); signal(SIGINT, SIG_DFL);
@@ -190,7 +170,6 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
editor_pid = pid; editor_pid = pid;
g_child_watch_add((GPid)pid, _editor_exit_cb, ctx); g_child_watch_add((GPid)pid, _editor_exit_cb, ctx);
g_strfreev(editor_argv); g_strfreev(editor_argv);
g_free(editor_env); // array only; strings are borrowed from environ
return FALSE; return FALSE;
} }

View File

@@ -658,6 +658,15 @@ connection_request_features(void)
/* We don't record it as a requested feature to avoid triggering th /* We don't record it as a requested feature to avoid triggering th
* sv_ev_connection_features_received too soon */ * sv_ev_connection_features_received too soon */
iq_disco_info_request_onconnect(conn.domain); iq_disco_info_request_onconnect(conn.domain);
const char* barejid = connection_get_barejid();
if (barejid && g_strcmp0(barejid, conn.domain) != 0) {
if (!g_hash_table_contains(conn.features_by_jid, barejid)) {
g_hash_table_insert(conn.features_by_jid, strdup(barejid),
g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL));
}
iq_disco_info_request_onconnect(barejid); // XEP-0163: PEP services announce features on the account's bare JID
}
} }
void void
@@ -753,11 +762,7 @@ void
connection_features_received(const char* const jid) connection_features_received(const char* const jid)
{ {
log_info("[CONNECTION] connection_features_received %s", jid); log_info("[CONNECTION] connection_features_received %s", jid);
const char* key = jid ? jid : conn.domain; // g_str_hash crashes on NULL; NULL 'from' means the server (RFC 6120 §8.1.2.1) if (g_hash_table_remove(conn.requested_features, jid) && g_hash_table_size(conn.requested_features) == 0) {
if (!key) {
return;
}
if (g_hash_table_remove(conn.requested_features, key) && g_hash_table_size(conn.requested_features) == 0) {
sv_ev_connection_features_received(); sv_ev_connection_features_received();
} }
} }
@@ -765,11 +770,7 @@ connection_features_received(const char* const jid)
GHashTable* GHashTable*
connection_get_features(const char* const jid) connection_get_features(const char* const jid)
{ {
const char* key = jid ? jid : conn.domain; return g_hash_table_lookup(conn.features_by_jid, jid);
if (!key || !conn.features_by_jid) {
return NULL;
}
return g_hash_table_lookup(conn.features_by_jid, key);
} }
GList* GList*

View File

@@ -2314,7 +2314,6 @@ _disco_info_response_id_handler(xmpp_stanza_t* const stanza, void* const userdat
log_debug("Received disco#info response from: %s", from); log_debug("Received disco#info response from: %s", from);
} else { } else {
log_debug("Received disco#info response"); log_debug("Received disco#info response");
from = connection_get_domain(); // RFC 6120 §8.1.2.1: no 'from' means the server itself
} }
// handle error responses // handle error responses
@@ -2398,7 +2397,6 @@ _disco_info_response_id_handler_onconnect(xmpp_stanza_t* const stanza, void* con
log_debug("Received disco#info response from: %s", from); log_debug("Received disco#info response from: %s", from);
} else { } else {
log_debug("Received disco#info response"); log_debug("Received disco#info response");
from = connection_get_domain(); // RFC 6120 §8.1.2.1: no 'from' means the server itself
} }
// handle error responses // handle error responses

View File

@@ -446,18 +446,21 @@ _omemo_receive_devicelist(xmpp_stanza_t* const stanza, void* const userdata)
GList* device_list = NULL; GList* device_list = NULL;
if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) { if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
log_error("[OMEMO] can't get OMEMO device list");
xmpp_stanza_t* error = xmpp_stanza_get_child_by_name(stanza, "error"); xmpp_stanza_t* error = xmpp_stanza_get_child_by_name(stanza, "error");
if (!error) { if (!error) {
log_error("[OMEMO] missing error element in device list response"); log_error("[OMEMO] missing error element in device list response");
return 1; return 1;
} }
// a missing node is signalled via legacy code='404' or the RFC 6120 <item-not-found/> condition
const char* code = xmpp_stanza_get_attribute(error, "code"); const char* code = xmpp_stanza_get_attribute(error, "code");
if (g_strcmp0(code, "404") == 0) { if (g_strcmp0(code, "404") == 0
|| xmpp_stanza_get_child_by_name_and_ns(error, STANZA_NAME_ITEM_NOT_FOUND, STANZA_NS_STANZAS)) {
log_debug("[OMEMO] no devicelist node for %s, bootstrapping an empty one", from);
omemo_set_device_list(from, NULL); omemo_set_device_list(from, NULL);
return 1; return 1;
} }
log_error("[OMEMO] can't get OMEMO device list");
} }
xmpp_stanza_t* root = NULL; xmpp_stanza_t* root = NULL;

View File

@@ -173,7 +173,6 @@ main(int argc, char* argv[])
PROF_FUNC_TEST(disco_info_without_name), PROF_FUNC_TEST(disco_info_without_name),
PROF_FUNC_TEST(disco_items_without_name), PROF_FUNC_TEST(disco_items_without_name),
PROF_FUNC_TEST(disco_info_service_unavailable), PROF_FUNC_TEST(disco_info_service_unavailable),
PROF_FUNC_TEST(disco_info_result_no_from),
/* Roster management - add/remove/rename contacts */ /* Roster management - add/remove/rename contacts */
PROF_FUNC_TEST(sends_new_item), PROF_FUNC_TEST(sends_new_item),

View File

@@ -396,35 +396,6 @@ disco_items_without_name(void **state)
prof_timeout_reset(); prof_timeout_reset();
} }
void
disco_info_result_no_from(void **state)
{
/*
* Test that a disco#info result without a 'from' attribute is treated as
* coming from the server itself (RFC 6120 §8.1.2.1). The on-connect
* disco#info handler used to crash on such responses (issue #168).
*/
stbbr_for_query("http://jabber.org/protocol/disco#info",
"<iq to='stabber@localhost/profanity' type='result'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='server' type='im' name='NoFromServer'/>"
"<feature var='urn:xmpp:ping'/>"
"</query>"
"</iq>"
);
/* the on-connect disco#info gets the same from-less response */
prof_connect();
prof_input("/disco info");
prof_timeout(10);
/* client survived and attributed the response to the server */
assert_true(prof_output_exact("Service discovery info for localhost"));
assert_true(prof_output_regex("NoFromServer.*im.*server"));
prof_timeout_reset();
}
void void
disco_info_service_unavailable(void **state) disco_info_service_unavailable(void **state)
{ {

View File

@@ -17,4 +17,3 @@ void disco_info_multiple_identities(void **state);
void disco_info_without_name(void **state); void disco_info_without_name(void **state);
void disco_items_without_name(void **state); void disco_items_without_name(void **state);
void disco_info_service_unavailable(void **state); void disco_info_service_unavailable(void **state);
void disco_info_result_no_from(void **state);