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.

Reply via email to