This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch nm-vpn-username-capture
in repository enlightenment.
View the commit online.
commit 725b15c51feedb1f7b9e34b0f9ae2a5b39e8bf13
Author: [email protected] <[email protected]>
AuthorDate: Sun Jun 21 21:19:11 2026 -0600
networkmanager: persist VPN username before replying secrets
Defer the VPN secrets D-Bus reply until the async vpn.data username write
completes, so NM sees the username on the current activation instead of
only the next connect. Add e_nm_agent_request_detach so the deferred reply
can outlive a concurrent CancelGetSecrets without a use-after-free. Username
stays a property; password stays a secret.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
---
src/modules/networkmanager/agent.c | 99 ++++++++++++++++++++-------
src/modules/networkmanager/e_networkmanager.c | 10 +++
src/modules/networkmanager/e_networkmanager.h | 4 ++
3 files changed, 90 insertions(+), 23 deletions(-)
diff --git a/src/modules/networkmanager/agent.c b/src/modules/networkmanager/agent.c
index 0e1505a2b..c99da75cd 100644
--- a/src/modules/networkmanager/agent.c
+++ b/src/modules/networkmanager/agent.c
@@ -55,6 +55,34 @@ static E_NM_Agent_Dialog *_current_dialog = NULL;
/* Dialog callbacks */
/* -------------------------------------------------------------------------- */
+/* Captures the VPN secrets reply so it can be sent *after* the username
+ * property write completes, ensuring NM sees vpn.data["username"] on this
+ * activation rather than only the next one. Owns req + dup'd payload. */
+typedef struct _Vpn_Reply_Deferred
+{
+ E_NM_Agent_Request *req;
+ char **names;
+ char **values;
+ unsigned int n;
+} Vpn_Reply_Deferred;
+
+static void
+_vpn_reply_deferred_run(void *data, Eina_Bool ok EINA_UNUSED)
+{
+ Vpn_Reply_Deferred *d = data;
+
+ /* Reply regardless of whether the username write succeeded — a failed
+ * modify still leaves the user able to retry, and cancelling here would
+ * lose the password too. */
+ e_nm_agent_reply_vpn_secrets(d->req,
+ (const char *const *)d->names,
+ (const char *const *)d->values, d->n);
+ for (unsigned int i = 0; i < d->n; i++) { free(d->names[i]); free(d->values[i]); }
+ free(d->names);
+ free(d->values);
+ free(d);
+}
+
static void
_dialog_send_ok(E_NM_Agent_Dialog *ad)
{
@@ -70,39 +98,64 @@ _dialog_send_ok(E_NM_Agent_Dialog *ad)
}
else
{
- /* The username is a connection property, not a secret: persist it to
- * vpn.data instead of returning it through the secrets reply. */
- if (ad->has_username_field && ad->username_entry)
- {
- char *user = elm_entry_markup_to_utf8(
- elm_entry_entry_get(ad->username_entry));
- if (user && user[0] && ad->conn_name)
- enm_vpn_username_set(ad->conn_name, user, NULL, NULL);
- free(user);
- }
+ /* The username is a connection property, not a secret. Persist it to
+ * vpn.data FIRST, then send the secrets reply from the write's
+ * completion callback — so NM sees the username on this activation
+ * instead of only the next connect. The password is never sent as a
+ * username and the username is never sent as a secret. */
+ char *user = NULL;
+ char **names, **values;
- const char **values = calloc(ad->n_fields, sizeof(*values));
- char **utf8 = calloc(ad->n_fields, sizeof(*utf8));
- if (!values || !utf8)
+ if (ad->has_username_field && ad->username_entry)
+ user = elm_entry_markup_to_utf8(
+ elm_entry_entry_get(ad->username_entry));
+
+ names = calloc(ad->n_fields ?: 1, sizeof(*names));
+ values = calloc(ad->n_fields ?: 1, sizeof(*values));
+ if (!names || !values)
{
- free(values); free(utf8);
+ free(names); free(values);
e_nm_agent_reply_cancel(ad->req);
}
else
{
+ Vpn_Reply_Deferred *d;
+
for (unsigned int i = 0; i < ad->n_fields; i++)
{
- utf8[i] = elm_entry_markup_to_utf8(
- elm_entry_entry_get(ad->field_entries[i]));
- values[i] = utf8[i] ?: "";
+ names[i] = strdup(ad->field_names[i] ?: "");
+ values[i] = elm_entry_markup_to_utf8(
+ elm_entry_entry_get(ad->field_entries[i]));
+ if (!values[i]) values[i] = strdup("");
+ }
+
+ d = calloc(1, sizeof(*d));
+ if (!d)
+ {
+ for (unsigned int i = 0; i < ad->n_fields; i++)
+ { free(names[i]); free(values[i]); }
+ free(names); free(values);
+ e_nm_agent_reply_cancel(ad->req);
+ }
+ else
+ {
+ d->req = ad->req;
+ d->names = names;
+ d->values = values;
+ d->n = ad->n_fields;
+ if (user && user[0] && ad->conn_name)
+ {
+ /* The reply now outlives this scope; detach req so a
+ * concurrent CancelGetSecrets cannot free it first. */
+ e_nm_agent_request_detach(ad->req);
+ enm_vpn_username_set(ad->conn_name, user,
+ _vpn_reply_deferred_run, d);
+ }
+ else
+ _vpn_reply_deferred_run(d, EINA_TRUE);
}
- e_nm_agent_reply_vpn_secrets(ad->req,
- (const char *const *)ad->field_names,
- values, ad->n_fields);
- for (unsigned int i = 0; i < ad->n_fields; i++) free(utf8[i]);
- free(utf8);
- free(values);
}
+ free(user);
}
ad->req = NULL;
e_object_del(E_OBJECT(ad->dialog));
diff --git a/src/modules/networkmanager/e_networkmanager.c b/src/modules/networkmanager/e_networkmanager.c
index 1717fbff3..5b3a3d473 100644
--- a/src/modules/networkmanager/e_networkmanager.c
+++ b/src/modules/networkmanager/e_networkmanager.c
@@ -3098,6 +3098,16 @@ _agent_request_free(E_NM_Agent_Request *req)
free(req);
}
+void
+e_nm_agent_request_detach(E_NM_Agent_Request *req)
+{
+ /* Unlink from the agent's pending list so CancelGetSecrets can no longer
+ * match and free this request, while keeping req->agent valid for the
+ * eventual reply. Used when a reply is deferred behind an async write. */
+ if (req && req->agent)
+ req->agent->pending = eina_list_remove(req->agent->pending, req);
+}
+
static void
_agent_dict_append_str(Eldbus_Message_Iter *array, const char *key,
const char *val)
diff --git a/src/modules/networkmanager/e_networkmanager.h b/src/modules/networkmanager/e_networkmanager.h
index 46579890e..19d206974 100644
--- a/src/modules/networkmanager/e_networkmanager.h
+++ b/src/modules/networkmanager/e_networkmanager.h
@@ -58,6 +58,10 @@ struct _E_NM_Agent_Callbacks
void e_nm_agent_callbacks_set(const E_NM_Agent_Callbacks *cbs, void *data);
void e_nm_agent_reply_secrets(E_NM_Agent_Request *req, const char *psk);
void e_nm_agent_reply_cancel(E_NM_Agent_Request *req);
+/* Detach a request from the agent's pending list so a deferred reply can
+ * outlive a concurrent CancelGetSecrets without a use-after-free. The caller
+ * then owns the request and MUST still call one of the reply functions. */
+void e_nm_agent_request_detach(E_NM_Agent_Request *req);
void e_nm_agent_reply_vpn_secrets(E_NM_Agent_Request *req,
const char *const *fields,
const char *const *values,
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.