Compare commits

...

22 commits

Author SHA1 Message Date
8b3ef2c346 defensive: NULL-check calloc results before dereferencing
Five sites allocated with calloc() and dereferenced the result on the
very next line. Under OOM the module would have segfaulted instead of
degrading. Each site now bails (or sends a Canceled D-Bus error, in
the agent path) when the allocation fails.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:27:44 +07:00
20945c6329 popup: resolve connect/forget targets by netpath at click time
The row click handlers held a raw Iwd_Network * captured at paint time.
If iwd vanished (name-owner change clears the network hash via
_on_name_vanished) between paint and click, the struct was freed and
the callback dereferenced it. Mirror the pattern already used for the
forget-confirmation dialog: stash a strdup'd object path on the button,
free it via EVAS_CALLBACK_DEL when the row is destroyed, and re-resolve
through the live hash at click time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:27:04 +07:00
d467231321 popup: clear hidden passphrase stash on close and on radio-off
The pre-armed passphrase entered through the hidden-network dialog is
process-global, not popup-scoped. If the user closed the popup (or
toggled Wi-Fi off) before iwd asked for it, the passphrase sat in the
heap until the 30 s timer fired. Wipe it eagerly on _destroy() and
when state transitions to IWD_STATE_OFF.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 18:26:11 +07:00
282bc830ee agent: document unavoidable passphrase residue in eldbus message
The passphrase is copied into the libdbus-owned outbound message buffer
and freed asynchronously by eldbus after the reply is sent — we cannot
wipe it ourselves. Callers already explicit_bzero their own copies; add
a comment so future readers don't mistake the missing wipe here for an
oversight.
2026-04-29 15:00:13 +07:00
9a40d38ad8 hidden: wipe SSID buffer and entry on dialog close
Mirror the passphrase handling so the heap is consistent: explicit_bzero
the strdup'd SSID before free, and clear the SSID entry widget alongside
the passphrase entry. SSIDs aren't secret per se, but leaving identifiable
network names in freed memory after a hidden-network prompt is avoidable.
2026-04-29 14:59:53 +07:00
862594256a adapter: capture manager (not Iwd_Adapter) in Set(Powered) reply
On _on_name_vanished the adapter hash is freed, so an in-flight
Set(Powered) reply that lands as a local error after disconnect would
deref a freed Iwd_Adapter. Mirror the pattern already used in
iwd_network.c / iwd_device.c: capture the manager pointer plus a
strdup'd path in a small reply context, free in the reply callback.
2026-04-29 14:59:30 +07:00
438fffbacd popup: resolve forget target by netpath at click time
The confirmation popup captured Iwd_Network * raw, so if the network
disappeared from a scan refresh or iwd restart between opening the
dialog and clicking Forget, the click would UAF. Stash the object path
instead and re-resolve through the live network hash on click.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:57:09 +07:00
1d4d125a93 device: capture manager (not Iwd_Device) in reply contexts
A device can be removed (rfkill, hot-unplug, iwd restart) while a
Scan/Disconnect/ConnectHiddenNetwork/GetOrderedNetworks call is in
flight, after which the reply would dereference a freed Iwd_Device.
The manager outlives every sub-object and exposes the network hash
needed by GetOrderedNetworks, so pass it directly instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:56:35 +07:00
11b21c8fd9 network: capture manager (not Iwd_Network) in reply context
If a Connect or Forget reply arrives after the Iwd_Network was freed
(network disappeared from a scan, iwd vanished mid-call), the callback
would dereference ctx->n->manager — use-after-free. The manager outlives
every sub-object, so capture it directly along with a strdup'd SSID;
the network back-ref isn't actually used for anything else.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:55:27 +07:00
4b3598d36e build: pin UTF-8 input/exec charset
The sources contain UTF-8 string literals (signal bars, ✕, ★, ✔, …).
Without an explicit charset, GCC honors LC_ALL/LANG at compile time,
so a build under a non-UTF-8 locale can mangle them. Probe the flags
with cc.get_supported_arguments so older/other compilers stay happy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:52:49 +07:00
e7dd97d713 popup: set access_info on network and forget buttons
The button labels embed Unicode signal bars (▂▄▆█) and ★/✔ markers,
which screen readers announce as raw codepoints. Provide a spoken
label that conveys the same info as words.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:52:25 +07:00
480476f59d ui: remove unused wifi_list/wifi_status stubs
The popup builds its network list inline in e_mod_popup.c and shows
status via labels there; these stubs were never wired up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:51:53 +07:00
c115148f4a gadget: explicitly remove mouse-down callback on shutdown
evas_object_del would clean up the callback as a side effect, but
matching every add with an explicit del avoids relying on that ordering
and keeps the lifetime obvious to readers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:50:24 +07:00
ad3d752b12 iwd: extract shared state/security label helpers
Both popup.c and gadget.c carried near-identical _state_label/_sec_label
helpers, with the gadget version using bare ints instead of the
Iwd_Security enum. Move to iwd/iwd_labels.{c,h} and use the enum
consistently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:50:01 +07:00
b03d10b164 config: use E_FREE/eina_stringshare_replace on stale config
free() on memory returned by e_config_domain_load mixes allocators on
the stringshare member. Use eina_stringshare_replace to drop the
stringshared field and E_FREE for the struct.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:48:29 +07:00
92d4fbc5ff popup: UTF-8-aware SSID truncation
%.*s cuts at byte index, splitting multi-byte sequences and producing
broken glyphs followed by the ellipsis. Walk codepoints instead and
truncate at a codepoint boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:48:03 +07:00
4df3b04690 gadget: replace _theme_path static buffer with caller-provided one
Static buffers in identity-like helpers are footguns: they're only safe
when consumed immediately and break when callers ever stash the pointer.
Take a caller-provided buffer instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:46:49 +07:00
853e6ae454 popup: confirmation dialog before Forget
Forget destroys the saved passphrase irreversibly. A stray click on
the ✕ next to a known network would wipe credentials with no recovery
and (until the previous commit) no error feedback either. Add an
elm_popup confirmation that names the SSID before invoking Forget.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:30:33 +07:00
0418e8bab9 manager: surface D-Bus call errors to the user
Connect / Forget / Set(Powered) / Scan / Disconnect / RegisterAgent /
ConnectHidden previously discarded reply errors with NULL callbacks, so
"Connecting…" could hang forever after a refused call (rfkill, busy
adapter, another agent already registered, bad credentials on a known
network). The user had no way to see the failure.

Add iwd_manager_{report,last,clear}_error and wire reply callbacks in
adapter / device / network / agent. The popup status line now appends
the latest error to the state label, and user actions (rescan, toggle,
connect, disconnect) clear it.

Scan errors that mean "already in flight" are filtered out — they're
the normal race when two scan triggers fire close together.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:29:59 +07:00
3fc41eef8f agent: UnregisterAgent on free
Tell iwd to drop our registration during shutdown instead of relying
on NameOwnerChanged GC. Avoids spurious agent calls landing while the
service interface is being torn down. Fire-and-forget — the reply,
if any, is irrelevant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:26:25 +07:00
47d70ab78d gadget: stringshare gadcon id instead of static buffer
A static char[128] returned from _gc_id_new is overwritten on every
call, so multiple gadget instances would alias the same id once gadcon
compares or stores it. eina_stringshare_add gives each instance its
own stable id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:25:56 +07:00
0ab9561d2b security: wipe passphrases, bind hidden stash to SSID, re-register agent
Wipe passphrase memory in the auth and hidden-network dialogs (explicit_bzero
on owned copies plus overwriting the elm_entry buffer before destruction) so
secrets don't linger on the heap. Bind the hidden-network passphrase stash to
its SSID with a 30s timeout, so a typo'd or out-of-range hidden connect can't
leak its passphrase to an unrelated network whose RequestPassphrase happens
to land first. Re-RegisterAgent on iwd NameOwnerChanged so PSK connects
survive systemctl restart iwd instead of silently hanging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 11:24:45 +07:00
22 changed files with 574 additions and 198 deletions

View file

@ -13,8 +13,12 @@ module_arch = enlightenment.get_variable(pkgconfig: 'module_arch',
default_value: 'linux-gnu-@0@'.format(host_machine.cpu()))
module_dir = join_paths(get_option('libdir'), 'enlightenment', 'modules', 'iwd')
utf8_args = cc.get_supported_arguments(['-finput-charset=UTF-8',
'-fexec-charset=UTF-8'])
add_project_arguments('-DPACKAGE="e_iwd"',
'-DPACKAGE_VERSION="@0@"'.format(meson.project_version()),
utf8_args,
language : 'c')
subdir('src')

View file

@ -30,9 +30,8 @@ e_iwd_config_load(void)
/* Missing or out-of-date — start fresh with defaults. */
if (e_iwd_config)
{
if (e_iwd_config->preferred_adapter)
eina_stringshare_del(e_iwd_config->preferred_adapter);
free(e_iwd_config);
eina_stringshare_replace(&e_iwd_config->preferred_adapter, NULL);
E_FREE(e_iwd_config);
}
e_iwd_config = E_NEW(E_Iwd_Config, 1);
e_iwd_config->version = CONFIG_VERSION;

View file

@ -5,7 +5,9 @@
#include "iwd/iwd_manager.h"
#include "iwd/iwd_device.h"
#include "iwd/iwd_network.h"
#include "iwd/iwd_labels.h"
#include <e_gadcon.h>
#include <limits.h>
/* ----- per-instance gadget data --------------------------------------- */
@ -74,30 +76,6 @@ _icon_name_for_state(Iwd_State s)
return "network-wireless";
}
static const char *
_state_label(Iwd_State s)
{
switch (s)
{
case IWD_STATE_OFF: return "Wi-Fi disabled";
case IWD_STATE_IDLE: return "Disconnected";
case IWD_STATE_SCANNING: return "Scanning";
case IWD_STATE_CONNECTING: return "Connecting";
case IWD_STATE_CONNECTED: return "Connected";
case IWD_STATE_ERROR: return "Error";
}
return "";
}
static const char *
_sec_label(int s)
{
/* Iwd_Security values, kept in sync with iwd_network.h. */
switch (s) { case 0: return "open"; case 1: return "WPA";
case 2: return "802.1X"; case 3: return "WEP"; }
return "?";
}
static void
_build_tooltip(Instance *inst, Iwd_State s)
{
@ -108,13 +86,13 @@ _build_tooltip(Instance *inst, Iwd_State s)
if (n)
snprintf(buf, sizeof(buf), "Wi-Fi: %s — %s — signal %d/4",
n->ssid ? n->ssid : "?",
_sec_label(n->security),
iwd_security_label(n->security),
iwd_network_signal_tier(n));
else
snprintf(buf, sizeof(buf), "Wi-Fi: connected");
}
else
snprintf(buf, sizeof(buf), "Wi-Fi: %s", _state_label(s));
snprintf(buf, sizeof(buf), "Wi-Fi: %s", iwd_state_label(s));
elm_object_tooltip_text_set(inst->o_base, buf);
}
@ -173,14 +151,12 @@ _on_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, vo
/* ----- helpers --------------------------------------------------------- */
static char *
_theme_path(void)
static Eina_Bool
_theme_path(char *buf, size_t len)
{
static char buf[4096];
if (!e_iwd || !e_iwd->module) return NULL;
snprintf(buf, sizeof(buf), "%s/e-module-iwd.edj",
e_module_dir_get(e_iwd->module));
return buf;
if (!e_iwd || !e_iwd->module) return EINA_FALSE;
snprintf(buf, len, "%s/e-module-iwd.edj", e_module_dir_get(e_iwd->module));
return EINA_TRUE;
}
/* ----- gadcon class ---------------------------------------------------- */
@ -189,7 +165,8 @@ static E_Gadcon_Client *
_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style)
{
Instance *inst = E_NEW(Instance, 1);
const char *path = _theme_path();
char path[PATH_MAX];
if (!_theme_path(path, sizeof(path))) path[0] = '\0';
/* themed edje is the gadcon o_base — its intrinsic min comes from the
* theme group, just like the backlight module. */
@ -224,6 +201,9 @@ _gc_shutdown(E_Gadcon_Client *gcc)
Instance *inst = gcc->data;
if (!inst) return;
_instances = eina_list_remove(_instances, inst);
if (inst->o_base)
evas_object_event_callback_del_full(inst->o_base, EVAS_CALLBACK_MOUSE_DOWN,
_on_mouse_down, inst);
if (inst->o_icon) evas_object_del(inst->o_icon);
if (inst->o_base) evas_object_del(inst->o_base);
E_FREE(inst);
@ -250,19 +230,20 @@ _gc_label(const E_Gadcon_Client_Class *cc EINA_UNUSED) { return "iwd"; }
static Evas_Object *
_gc_icon(const E_Gadcon_Client_Class *cc EINA_UNUSED, Evas *evas)
{
const char *path = _theme_path();
char path[PATH_MAX];
Evas_Object *o = edje_object_add(evas);
if (path) edje_object_file_set(o, path, "icon");
if (_theme_path(path, sizeof(path)))
edje_object_file_set(o, path, "icon");
return o;
}
static const char *
_gc_id_new(const E_Gadcon_Client_Class *cc)
{
static char buf[128];
char buf[128];
snprintf(buf, sizeof(buf), "%s.%d", cc->name,
eina_list_count(_instances) + 1);
return buf;
return eina_stringshare_add(buf);
}
static const E_Gadcon_Client_Class _gadcon_class =

View file

@ -42,6 +42,7 @@ e_modapi_shutdown(E_Module *m EINA_UNUSED)
if (!e_iwd) return 1;
e_iwd_gadget_shutdown();
e_iwd_popup_shutdown();
if (e_iwd->manager) iwd_manager_free(e_iwd->manager);
e_iwd_config_save();
if (e_iwd->conn) eldbus_connection_unref(e_iwd->conn);

View file

@ -4,6 +4,7 @@
#include "iwd/iwd_device.h"
#include "iwd/iwd_network.h"
#include "iwd/iwd_agent.h"
#include "iwd/iwd_labels.h"
#include "ui/wifi_auth.h"
#include "ui/wifi_hidden.h"
#include <e_gadcon_popup.h>
@ -30,38 +31,42 @@ static Popup *_popup = NULL;
static Iwd_Agent_Request *_pending_req = NULL;
/* Tracked so iwd's Cancel(reason) can tear down the dialog. */
static Evas_Object *_pending_dialog = NULL;
/* One-shot passphrase pre-armed by the hidden-network dialog. */
static char *_hidden_pending_pass = NULL;
/* One-shot passphrase pre-armed by the hidden-network dialog. Bound to the
* SSID it was entered for, with a timeout that wipes it if iwd never comes
* asking so a stashed passphrase can never leak to an unrelated network. */
static char *_hidden_pending_pass = NULL;
static char *_hidden_pending_ssid = NULL;
static Ecore_Timer *_hidden_pending_timer = NULL;
#define HIDDEN_PASS_TIMEOUT 30.0 /* seconds */
static void
_hidden_pending_clear(void)
{
if (_hidden_pending_pass)
{
explicit_bzero(_hidden_pending_pass, strlen(_hidden_pending_pass));
free(_hidden_pending_pass);
_hidden_pending_pass = NULL;
}
free(_hidden_pending_ssid);
_hidden_pending_ssid = NULL;
if (_hidden_pending_timer)
{
ecore_timer_del(_hidden_pending_timer);
_hidden_pending_timer = NULL;
}
}
static Eina_Bool
_hidden_pending_timeout(void *data EINA_UNUSED)
{
_hidden_pending_timer = NULL;
_hidden_pending_clear();
return ECORE_CALLBACK_CANCEL;
}
/* ----- helpers --------------------------------------------------------- */
static const char *
_state_label(Iwd_State s)
{
switch (s) {
case IWD_STATE_OFF: return "Wi-Fi disabled";
case IWD_STATE_IDLE: return "Disconnected";
case IWD_STATE_SCANNING: return "Scanning…";
case IWD_STATE_CONNECTING: return "Connecting…";
case IWD_STATE_CONNECTED: return "Connected";
case IWD_STATE_ERROR: return "Error";
}
return "";
}
static const char *
_sec_label(Iwd_Security s)
{
switch (s) {
case IWD_SEC_OPEN: return "open";
case IWD_SEC_PSK: return "WPA";
case IWD_SEC_8021X: return "802.1X";
case IWD_SEC_WEP: return "WEP";
case IWD_SEC_UNKNOWN: return "?";
}
return "";
}
static int
_net_cmp(const void *a, const void *b)
{
@ -112,20 +117,95 @@ _active_device(void)
/* ----- list rendering -------------------------------------------------- */
/* Click data is a strdup'd object path, freed via the row's EVAS_CALLBACK_DEL.
* Holding the Iwd_Network * directly would UAF if iwd vanished (its hash is
* cleared in _on_name_vanished) between row paint and click. */
static void
_row_path_free(void *data, Evas *e EINA_UNUSED,
Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
{ free(data); }
static void
_on_net_clicked(void *data, Evas_Object *obj EINA_UNUSED, void *ev EINA_UNUSED)
{
Iwd_Network *n = data;
const char *netpath = data;
if (!netpath || !e_iwd || !e_iwd->manager) return;
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
Iwd_Network *n = h ? eina_hash_find(h, netpath) : NULL;
if (!n) return;
iwd_manager_clear_error(e_iwd->manager);
iwd_network_connect(n);
}
/* The Iwd_Network captured when the confirmation popup was opened may have
* disappeared (scan refresh, iwd restart) by the time the user clicks. We
* stash its object path on the popup and re-resolve through the live hash
* at click time so a stale pointer is never dereferenced. */
static void _forget_confirm_free(void *data, Evas *e EINA_UNUSED,
Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
{ free(data); }
static void _forget_confirm_yes(void *data, Evas_Object *obj, void *ev EINA_UNUSED)
{
const char *netpath = data;
Evas_Object *pp = evas_object_data_get(obj, "_eiwd_confirm_popup");
if (netpath && e_iwd && e_iwd->manager)
{
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
Iwd_Network *n = h ? eina_hash_find(h, netpath) : NULL;
if (n) iwd_network_forget(n);
}
if (pp) evas_object_del(pp);
}
static void _forget_confirm_no(void *data EINA_UNUSED, Evas_Object *obj, void *ev EINA_UNUSED)
{
Evas_Object *pp = evas_object_data_get(obj, "_eiwd_confirm_popup");
if (pp) evas_object_del(pp);
}
static void
_on_net_forget(void *data, Evas_Object *obj EINA_UNUSED, void *ev EINA_UNUSED)
{
Iwd_Network *n = data;
const char *netpath = data;
if (!netpath || !e_iwd || !e_iwd->manager) return;
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
Iwd_Network *n = h ? eina_hash_find(h, netpath) : NULL;
if (!n) return;
iwd_network_forget(n);
/* Forget destroys the saved passphrase irreversibly — confirm first.
* A stray click on the next to a known network would otherwise wipe
* credentials with no recovery. */
Evas_Object *parent = _popup ? _popup->box : e_comp->elm;
Evas_Object *pp = elm_popup_add(parent);
char msg[256];
snprintf(msg, sizeof(msg),
"Forget saved network <b>%s</b>?<br/>"
"The passphrase will be permanently deleted.",
n->ssid ? n->ssid : "(hidden)");
elm_object_part_text_set(pp, "title,text", "Forget network");
elm_object_text_set(pp, msg);
/* Weak ref by netpath — looked up at click time. Freed when the popup
* is destroyed (either button or the user closing the window). */
char *netpath_ref = n->path ? strdup(n->path) : NULL;
if (netpath_ref)
evas_object_event_callback_add(pp, EVAS_CALLBACK_DEL,
_forget_confirm_free, netpath_ref);
Evas_Object *yes = elm_button_add(pp);
elm_object_text_set(yes, "Forget");
evas_object_data_set(yes, "_eiwd_confirm_popup", pp);
evas_object_smart_callback_add(yes, "clicked", _forget_confirm_yes, netpath_ref);
elm_object_part_content_set(pp, "button1", yes);
Evas_Object *no = elm_button_add(pp);
elm_object_text_set(no, "Cancel");
evas_object_data_set(no, "_eiwd_confirm_popup", pp);
evas_object_smart_callback_add(no, "clicked", _forget_confirm_no, NULL);
elm_object_part_content_set(pp, "button2", no);
evas_object_show(pp);
}
static void
@ -158,13 +238,23 @@ _rebuild_list(Popup *p)
evas_object_size_hint_align_set(row, EVAS_HINT_FILL, 0);
Evas_Object *btn = elm_button_add(row);
/* Truncate long SSIDs so the row never forces horizontal scrolling. */
/* Truncate long SSIDs so the row never forces horizontal scrolling.
* Count codepoints, not bytes SSIDs may contain UTF-8 and naive
* byte truncation would split a multi-byte sequence. */
const char *raw_ssid = n->ssid ? n->ssid : "(hidden)";
char ssid_buf[32];
const int max_ssid = 22;
if ((int)strlen(raw_ssid) > max_ssid)
char ssid_buf[64];
const int max_chars = 21;
int iindex = 0, prev = 0, chars = 0;
while (chars < max_chars
&& eina_unicode_utf8_next_get(raw_ssid, &iindex))
{ prev = iindex; chars++; }
if (eina_unicode_utf8_next_get(raw_ssid, &iindex))
{
snprintf(ssid_buf, sizeof(ssid_buf), "%.*s…", max_ssid - 1, raw_ssid);
/* more remains — truncate at `prev` and append U+2026 */
int copy = prev < (int)sizeof(ssid_buf) - 4
? prev : (int)sizeof(ssid_buf) - 4;
memcpy(ssid_buf, raw_ssid, copy);
memcpy(ssid_buf + copy, "\xe2\x80\xa6", 4); /* "…" + NUL */
raw_ssid = ssid_buf;
}
char label[256];
@ -172,12 +262,32 @@ _rebuild_list(Popup *p)
_signal_bars(iwd_network_signal_tier(n)),
n->known_path ? "" : " ",
raw_ssid,
_sec_label(n->security),
iwd_security_label(n->security),
n->connected ? "" : "");
elm_object_text_set(btn, label);
/* Spoken label: avoids the ▂▄▆█ glyphs and ★/✔ markers which
* screen readers announce as raw Unicode. */
char access[256];
snprintf(access, sizeof(access),
"%s, signal %d of 4, %s%s%s",
raw_ssid,
iwd_network_signal_tier(n),
iwd_security_label(n->security),
n->known_path ? ", saved" : "",
n->connected ? ", connected" : "");
elm_object_access_info_set(btn, access);
evas_object_size_hint_weight_set(btn, EVAS_HINT_EXPAND, 0);
evas_object_size_hint_align_set(btn, EVAS_HINT_FILL, 0);
evas_object_smart_callback_add(btn, "clicked", _on_net_clicked, n);
/* Pass a copy of the object path, not the Iwd_Network *: the network
* may disappear (iwd_dbus name-vanished, scan refresh) before the
* user clicks, freeing the struct. The path is re-resolved through
* the live hash at click time. The buffer is freed via the button's
* EVAS_CALLBACK_DEL when the row is rebuilt or popup torn down. */
char *click_path = n->path ? strdup(n->path) : NULL;
if (click_path)
evas_object_event_callback_add(btn, EVAS_CALLBACK_DEL,
_row_path_free, click_path);
evas_object_smart_callback_add(btn, "clicked", _on_net_clicked, click_path);
elm_box_pack_end(row, btn);
evas_object_show(btn);
@ -186,7 +296,14 @@ _rebuild_list(Popup *p)
Evas_Object *fb = elm_button_add(row);
elm_object_text_set(fb, "");
elm_object_tooltip_text_set(fb, "Forget network");
evas_object_smart_callback_add(fb, "clicked", _on_net_forget, n);
char facc[128];
snprintf(facc, sizeof(facc), "Forget %s", raw_ssid);
elm_object_access_info_set(fb, facc);
char *forget_path = n->path ? strdup(n->path) : NULL;
if (forget_path)
evas_object_event_callback_add(fb, EVAS_CALLBACK_DEL,
_row_path_free, forget_path);
evas_object_smart_callback_add(fb, "clicked", _on_net_forget, forget_path);
elm_box_pack_end(row, fb);
evas_object_show(fb);
}
@ -202,8 +319,21 @@ _refresh(Popup *p)
{
if (!p || !e_iwd || !e_iwd->manager) return;
Iwd_State s = iwd_manager_state(e_iwd->manager);
/* Radio went off: the stash can no longer be useful (iwd won't ask)
* and we'd rather not keep a passphrase resident across a toggle. */
if (s == IWD_STATE_OFF) _hidden_pending_clear();
if (p->status_lbl)
elm_object_text_set(p->status_lbl, _state_label(s));
{
const char *err = iwd_manager_last_error(e_iwd->manager);
if (err)
{
char buf[320];
snprintf(buf, sizeof(buf), "%s — %s", iwd_state_label(s), err);
elm_object_text_set(p->status_lbl, buf);
}
else
elm_object_text_set(p->status_lbl, iwd_state_label(s));
}
if (p->btn_toggle)
elm_object_text_set(p->btn_toggle, s == IWD_STATE_OFF ? "Enable" : "Disable");
if (p->btn_scan)
@ -229,18 +359,23 @@ _on_manager_change(void *data, Iwd_Manager *m EINA_UNUSED)
static void _on_rescan (void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED)
{
if (e_iwd && e_iwd->manager) iwd_manager_scan_request(e_iwd->manager);
if (!e_iwd || !e_iwd->manager) return;
iwd_manager_clear_error(e_iwd->manager);
iwd_manager_scan_request(e_iwd->manager);
}
static void _on_toggle(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED)
{
if (!e_iwd || !e_iwd->manager) return;
iwd_manager_clear_error(e_iwd->manager);
Eina_Bool off = (iwd_manager_state(e_iwd->manager) == IWD_STATE_OFF);
iwd_manager_set_powered(e_iwd->manager, off);
}
static void _on_disconnect(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED)
{
Iwd_Device *dev = _active_device();
if (dev) iwd_device_disconnect(dev);
if (!dev) return;
if (e_iwd && e_iwd->manager) iwd_manager_clear_error(e_iwd->manager);
iwd_device_disconnect(dev);
}
static void
@ -249,10 +384,19 @@ _on_hidden_done(void *data EINA_UNUSED, const char *ssid, const char *pass, Eina
if (!ok || !ssid || !*ssid) return;
Iwd_Device *dev = _active_device();
if (!dev) return;
/* Pre-arm the agent reply so the next RequestPassphrase from iwd is
* answered automatically. If the network turns out to be open, the
* stashed passphrase is simply never consumed. */
if (pass && *pass) _hidden_pending_pass = strdup(pass);
/* Pre-arm the agent reply so the next RequestPassphrase from iwd for
* *this SSID* is answered automatically. The stash is bound to the SSID
* and self-clears after HIDDEN_PASS_TIMEOUT seconds so a typo'd or
* out-of-range SSID cannot cause the passphrase to leak to a later,
* unrelated network whose RequestPassphrase happens to land first. */
_hidden_pending_clear();
if (pass && *pass)
{
_hidden_pending_pass = strdup(pass);
_hidden_pending_ssid = strdup(ssid);
_hidden_pending_timer = ecore_timer_add(HIDDEN_PASS_TIMEOUT,
_hidden_pending_timeout, NULL);
}
iwd_device_connect_hidden(dev, ssid);
}
@ -267,7 +411,12 @@ static void
_on_auth_done(void *data EINA_UNUSED, const char *pass, Eina_Bool ok)
{
_pending_dialog = NULL;
if (!_pending_req) return;
if (!_pending_req)
{
/* Request was already canceled by iwd; nothing to do. The caller
* (wifi_auth) wipes its own copy of `pass` after we return. */
return;
}
if (ok) iwd_agent_reply (_pending_req, pass ? pass : "");
else iwd_agent_cancel(_pending_req);
_pending_req = NULL;
@ -303,15 +452,27 @@ e_iwd_popup_install_passphrase_handler(void)
static void
_on_passphrase_request(void *data EINA_UNUSED, Iwd_Agent_Request *req, const char *netpath)
{
/* If the user just kicked off a hidden-network connect with a passphrase,
* answer this request automatically without prompting. */
if (_hidden_pending_pass)
/* Resolve netpath -> network so we can both match the hidden stash
* against the requested SSID *and* show a friendly label in the dialog. */
Iwd_Network *n = NULL;
if (e_iwd && e_iwd->manager)
{
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
if (h) n = eina_hash_find(h, netpath);
}
const char *req_ssid = (n && n->ssid) ? n->ssid : NULL;
/* Use the hidden-network stash *only* if iwd is asking for the same
* SSID we entered it for. Anything else: drop the stash on the floor
* and prompt normally. */
if (_hidden_pending_pass && _hidden_pending_ssid &&
req_ssid && !strcmp(req_ssid, _hidden_pending_ssid))
{
iwd_agent_reply(req, _hidden_pending_pass);
free(_hidden_pending_pass);
_hidden_pending_pass = NULL;
_hidden_pending_clear();
return;
}
if (_pending_req)
{
iwd_agent_cancel(req);
@ -319,27 +480,8 @@ _on_passphrase_request(void *data EINA_UNUSED, Iwd_Agent_Request *req, const cha
}
_pending_req = req;
/* Look up the network for a friendly SSID, if we have it. */
const char *ssid = "network";
if (e_iwd && e_iwd->manager)
{
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
if (h)
{
Iwd_Network *n = eina_hash_find(h, netpath);
if (n && n->ssid) ssid = n->ssid;
}
}
const char *sec = NULL;
if (e_iwd && e_iwd->manager)
{
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
if (h)
{
Iwd_Network *n = eina_hash_find(h, netpath);
if (n) sec = _sec_label(n->security);
}
}
const char *ssid = req_ssid ? req_ssid : "network";
const char *sec = n ? iwd_security_label(n->security) : NULL;
_pending_dialog = wifi_auth_prompt(_popup ? _popup->box : e_comp->elm,
ssid, sec, _on_auth_done, NULL);
}
@ -355,6 +497,17 @@ _destroy(void)
if (_popup->gp) e_object_del(E_OBJECT(_popup->gp));
free(_popup);
_popup = NULL;
/* Drop any pre-armed hidden-network passphrase: if the user closes the
* popup before iwd asks, the stash would otherwise sit in the heap until
* the 30 s timer fires. The stash is process-global, not popup-scoped. */
_hidden_pending_clear();
}
void
e_iwd_popup_shutdown(void)
{
_destroy();
_hidden_pending_clear();
}
void
@ -370,6 +523,7 @@ e_iwd_popup_toggle(E_Gadcon_Client *gcc)
if (!gcc || !e_iwd) return;
Popup *p = calloc(1, sizeof(*p));
if (!p) return;
_popup = p;
p->gp = e_gadcon_popup_new(gcc, EINA_FALSE);

View file

@ -4,8 +4,9 @@
#include <e_gadcon.h>
void e_iwd_popup_install_passphrase_handler(void);
void e_iwd_popup_toggle (E_Gadcon_Client *gcc);
void e_iwd_popup_close (void);
void e_iwd_popup_refresh(void);
void e_iwd_popup_toggle (E_Gadcon_Client *gcc);
void e_iwd_popup_close (void);
void e_iwd_popup_refresh (void);
void e_iwd_popup_shutdown(void);
#endif

View file

@ -61,6 +61,28 @@ iwd_adapter_free(Iwd_Adapter *a)
free(a);
}
/* Reply context captures the *manager* (which outlives all sub-objects) and
* a strdup'd adapter path, never the Iwd_Adapter on iwd disconnect the
* adapter hash is freed, and a raw back-ref would UAF when the local-error
* reply lands. Mirrors the pattern in iwd_network.c / iwd_device.c. */
typedef struct
{
Iwd_Manager *m;
char *path;
} _Adapter_Reply_Ctx;
static void
_on_set_powered_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
{
_Adapter_Reply_Ctx *ctx = data;
const char *en, *em;
if (eldbus_message_error_get(msg, &en, &em) && ctx->m)
iwd_manager_report_error(ctx->m,
"Set Adapter.Powered failed: %s", em ? em : en);
free(ctx->path);
free(ctx);
}
void
iwd_adapter_set_powered(Iwd_Adapter *a, Eina_Bool on)
{
@ -83,7 +105,13 @@ iwd_adapter_set_powered(Iwd_Adapter *a, Eina_Bool on)
eldbus_message_iter_basic_append(variant, 'b', v);
eldbus_message_iter_container_close(iter, variant);
eldbus_proxy_send(props, msg, NULL, NULL, -1);
_Adapter_Reply_Ctx *ctx = calloc(1, sizeof(*ctx));
if (ctx)
{
ctx->m = a->manager;
ctx->path = a->path ? strdup(a->path) : NULL;
}
eldbus_proxy_send(props, msg, _on_set_powered_reply, ctx, -1);
/* Keep the props proxy alive on the adapter so the call isn't canceled. */
if (a->_props_proxy_keepalive) eldbus_proxy_unref(a->_props_proxy_keepalive);
a->_props_proxy_keepalive = props;

View file

@ -1,5 +1,7 @@
#include "iwd_agent.h"
#include "iwd_dbus.h"
#include "iwd_manager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -71,6 +73,9 @@ _request_passphrase_cb(const Eldbus_Service_Interface *iface EINA_UNUSED,
"No UI handler");
Iwd_Agent_Request *req = calloc(1, sizeof(*req));
if (!req)
return eldbus_message_error_new(msg, "net.connman.iwd.Agent.Error.Canceled",
"Out of memory");
req->agent = _self;
req->msg = eldbus_message_ref((Eldbus_Message *)msg);
_self->cb(_self->data, req, path);
@ -113,6 +118,12 @@ void
iwd_agent_reply(Iwd_Agent_Request *req, const char *passphrase)
{
if (!req) return;
/* The passphrase is copied into the eldbus/libdbus marshalled message
* buffer here. We can't wipe that buffer ourselves eldbus owns it and
* frees it asynchronously after the call is sent. Callers are expected
* to explicit_bzero their own copy of `passphrase` after this returns;
* the residue inside the outbound D-Bus message is unavoidable at this
* boundary. */
Eldbus_Message *r = eldbus_message_method_return_new(req->msg);
eldbus_message_arguments_append(r, "s", passphrase ? passphrase : "");
eldbus_connection_send(req->agent->conn, r, NULL, NULL, -1);
@ -135,14 +146,29 @@ iwd_agent_cancel(Iwd_Agent_Request *req)
/* ----- Registration with iwd ------------------------------------------ */
static void
_on_register(void *data EINA_UNUSED, const Eldbus_Message *msg,
_on_register(void *data, const Eldbus_Message *msg,
Eldbus_Pending *p EINA_UNUSED)
{
/* `data` is the manager — same pointer the trampoline carries. */
Iwd_Manager *m = data;
const char *en, *em;
if (eldbus_message_error_get(msg, &en, &em))
if (!eldbus_message_error_get(msg, &en, &em)) return;
if (m)
iwd_manager_report_error(m,
"Wi-Fi agent registration refused (another agent running?): %s",
em ? em : en);
else
fprintf(stderr, "e_iwd: agent register failed: %s: %s\n", en, em);
}
void
iwd_agent_register(Iwd_Agent *a)
{
if (!a || !a->am_proxy) return;
eldbus_proxy_call(a->am_proxy, "RegisterAgent", _on_register, a->data, -1,
"o", IWD_AGENT_PATH);
}
Iwd_Agent *
iwd_agent_new(Eldbus_Connection *conn, Iwd_Agent_Passphrase_Cb cb, void *data)
{
@ -158,12 +184,8 @@ iwd_agent_new(Eldbus_Connection *conn, Iwd_Agent_Passphrase_Cb cb, void *data)
a->am_obj = eldbus_object_get(conn, IWD_BUS_NAME, "/net/connman/iwd");
if (a->am_obj)
{
a->am_proxy = eldbus_proxy_get(a->am_obj, IWD_IFACE_AGENT_MANAGER);
if (a->am_proxy)
eldbus_proxy_call(a->am_proxy, "RegisterAgent", _on_register, NULL, -1,
"o", IWD_AGENT_PATH);
}
a->am_proxy = eldbus_proxy_get(a->am_obj, IWD_IFACE_AGENT_MANAGER);
iwd_agent_register(a);
return a;
}
@ -179,6 +201,13 @@ void
iwd_agent_free(Iwd_Agent *a)
{
if (!a) return;
/* Politely deregister so iwd doesn't keep dispatching to a dead service
* during shutdown. Fire-and-forget: the connection may already be torn
* down by the time the call would land, and there's nothing to do with
* the reply anyway. */
if (a->am_proxy)
eldbus_proxy_call(a->am_proxy, "UnregisterAgent", NULL, NULL, -1,
"o", IWD_AGENT_PATH);
if (a->svc) eldbus_service_interface_unregister(a->svc);
if (a->am_proxy) eldbus_proxy_unref(a->am_proxy);
if (a->am_obj) eldbus_object_unref(a->am_obj);

View file

@ -21,6 +21,10 @@ Iwd_Agent *iwd_agent_new (Eldbus_Connection *conn,
Iwd_Agent_Passphrase_Cb cb, void *data);
void iwd_agent_set_cancel_cb(Iwd_Agent *a, Iwd_Agent_Cancel_Cb cb, void *data);
/* Re-issue RegisterAgent. Call after iwd reappears on the bus
* (NameOwnerChanged) without this, every PSK connect silently hangs
* because no agent is registered against the new iwd instance. */
void iwd_agent_register(Iwd_Agent *a);
void iwd_agent_free(Iwd_Agent *a);
void iwd_agent_reply (Iwd_Agent_Request *req, const char *passphrase);

View file

@ -3,7 +3,6 @@
#include "iwd_props.h"
#include "iwd_manager.h"
#include "iwd_network.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -149,10 +148,15 @@ iwd_device_free(Iwd_Device *d)
/* Reply to Station.GetOrderedNetworks: a(on) — list of (object_path, RSSI).
* RSSI is a 16-bit signed value in 100*dBm units. */
/* Reply callbacks must not hold a raw Iwd_Device back-ref: a device can be
* removed (rfkill, hot-unplug, iwd restart) while a call is in flight, and
* the reply would then UAF. The manager outlives every sub-object, so we
* pass it directly. Network lookups go through the live hash, which is
* safe even after the originating device is gone. */
static void
_on_ordered_networks(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
{
Iwd_Device *d = data;
Iwd_Manager *m = data;
const char *en, *em;
if (eldbus_message_error_get(msg, &en, &em)) return;
@ -160,7 +164,7 @@ _on_ordered_networks(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EI
if (!eldbus_message_arguments_get(msg, "a(on)", &array) || !array)
return;
const Eina_Hash *nets = d->manager ? iwd_manager_networks(d->manager) : NULL;
const Eina_Hash *nets = m ? iwd_manager_networks(m) : NULL;
Eldbus_Message_Iter *entry;
Eina_Bool any = EINA_FALSE;
while (eldbus_message_iter_get_and_next(array, 'r', &entry))
@ -175,7 +179,7 @@ _on_ordered_networks(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EI
n->have_signal = EINA_TRUE;
any = EINA_TRUE;
}
if (any && d->manager) iwd_manager_notify(d->manager);
if (any && m) iwd_manager_notify(m);
}
static void
@ -183,38 +187,74 @@ _refresh_signals(Iwd_Device *d)
{
if (!d || !d->station_proxy) return;
eldbus_proxy_call(d->station_proxy, "GetOrderedNetworks",
_on_ordered_networks, d, -1, "");
_on_ordered_networks, d->manager, -1, "");
}
static void
_on_scan_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
{
Iwd_Manager *m = data;
const char *en, *em;
if (!eldbus_message_error_get(msg, &en, &em)) return;
/* "AlreadyExists" / "InProgress" is the normal race when two scan
* triggers fire close together don't spam the user with that. */
if (en && (strstr(en, "InProgress") || strstr(en, "Busy") ||
strstr(en, "AlreadyExists")))
return;
if (m) iwd_manager_report_error(m, "Scan failed: %s", em ? em : en);
}
void
iwd_device_scan(Iwd_Device *d)
{
if (!d || !d->station_proxy) return;
eldbus_proxy_call(d->station_proxy, "Scan", NULL, NULL, -1, "");
eldbus_proxy_call(d->station_proxy, "Scan", _on_scan_reply, d->manager, -1, "");
}
static void
_on_disconnect_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
{
Iwd_Manager *m = data;
const char *en, *em;
if (eldbus_message_error_get(msg, &en, &em) && m)
iwd_manager_report_error(m, "Disconnect failed: %s", em ? em : en);
}
void
iwd_device_disconnect(Iwd_Device *d)
{
if (!d || !d->station_proxy) return;
eldbus_proxy_call(d->station_proxy, "Disconnect", NULL, NULL, -1, "");
eldbus_proxy_call(d->station_proxy, "Disconnect",
_on_disconnect_reply, d->manager, -1, "");
}
typedef struct
{
Iwd_Manager *m;
char *ssid;
} _Connect_Hidden_Ctx;
static void
_on_connect_hidden_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
{
_Connect_Hidden_Ctx *ctx = data;
const char *en, *em;
char *ssid = data;
if (eldbus_message_error_get(msg, &en, &em))
fprintf(stderr, "e_iwd: ConnectHiddenNetwork('%s') failed: %s: %s\n",
ssid ? ssid : "?", en, em);
free(ssid);
if (eldbus_message_error_get(msg, &en, &em) && ctx->m)
iwd_manager_report_error(ctx->m,
"Connect to hidden '%s' failed: %s",
ctx->ssid ? ctx->ssid : "?", em ? em : en);
free(ctx->ssid);
free(ctx);
}
void
iwd_device_connect_hidden(Iwd_Device *d, const char *ssid)
{
if (!d || !d->station_proxy || !ssid || !*ssid) return;
_Connect_Hidden_Ctx *ctx = calloc(1, sizeof(*ctx));
if (!ctx) return;
ctx->m = d->manager;
ctx->ssid = strdup(ssid);
eldbus_proxy_call(d->station_proxy, "ConnectHiddenNetwork",
_on_connect_hidden_reply, strdup(ssid), -1, "s", ssid);
_on_connect_hidden_reply, ctx, -1, "s", ssid);
}

28
src/iwd/iwd_labels.c Normal file
View file

@ -0,0 +1,28 @@
#include "iwd_labels.h"
const char *
iwd_state_label(Iwd_State s)
{
switch (s) {
case IWD_STATE_OFF: return "Wi-Fi disabled";
case IWD_STATE_IDLE: return "Disconnected";
case IWD_STATE_SCANNING: return "Scanning…";
case IWD_STATE_CONNECTING: return "Connecting…";
case IWD_STATE_CONNECTED: return "Connected";
case IWD_STATE_ERROR: return "Error";
}
return "";
}
const char *
iwd_security_label(Iwd_Security s)
{
switch (s) {
case IWD_SEC_OPEN: return "open";
case IWD_SEC_PSK: return "WPA";
case IWD_SEC_8021X: return "802.1X";
case IWD_SEC_WEP: return "WEP";
case IWD_SEC_UNKNOWN: return "?";
}
return "";
}

12
src/iwd/iwd_labels.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef IWD_LABELS_H
#define IWD_LABELS_H
#include "iwd_manager.h"
#include "iwd_network.h"
/* Short, user-facing labels for state and security enums. The pointers
* returned are static literals do not free. */
const char *iwd_state_label (Iwd_State s);
const char *iwd_security_label(Iwd_Security s);
#endif

View file

@ -4,6 +4,8 @@
#include "iwd_device.h"
#include "iwd_network.h"
#include <Ecore.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -23,6 +25,7 @@ struct _Iwd_Manager
Eina_List *listeners; /* Listener * */
Iwd_State state;
Ecore_Job *notify_job;
char *last_error;
Iwd_Agent_Passphrase_Cb pass_cb;
void *pass_data;
@ -60,6 +63,7 @@ iwd_manager_listener_add(Iwd_Manager *m, Iwd_Manager_Cb cb, void *data)
{
if (!m || !cb) return;
Listener *l = calloc(1, sizeof(*l));
if (!l) return;
l->cb = cb; l->data = data;
m->listeners = eina_list_append(m->listeners, l);
}
@ -102,6 +106,33 @@ iwd_manager_notify(Iwd_Manager *m)
m->notify_job = ecore_job_add(_notify_job_cb, m);
}
const char *
iwd_manager_last_error(const Iwd_Manager *m) { return m ? m->last_error : NULL; }
void
iwd_manager_clear_error(Iwd_Manager *m)
{
if (!m || !m->last_error) return;
free(m->last_error);
m->last_error = NULL;
}
void
iwd_manager_report_error(Iwd_Manager *m, const char *fmt, ...)
{
if (!m || !fmt) return;
char buf[256];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
/* stderr keeps the dev-visible trail; the stashed copy drives the UI. */
fprintf(stderr, "e_iwd: %s\n", buf);
free(m->last_error);
m->last_error = strdup(buf);
iwd_manager_notify(m);
}
/* ----- state aggregation ---------------------------------------------- */
static void
@ -222,7 +253,14 @@ _on_iface_removed(void *data, const char *path, const char *iface)
}
static void
_on_name_appeared(void *data EINA_UNUSED) { /* GetManagedObjects will populate */ }
_on_name_appeared(void *data)
{
/* GetManagedObjects will repopulate adapters/devices/networks; we just
* need to re-register our agent against the new iwd instance. Without
* this, PSK connects silently hang after `systemctl restart iwd`. */
Iwd_Manager *m = data;
if (m && m->agent) iwd_agent_register(m->agent);
}
static void
_on_name_vanished(void *data)
@ -274,6 +312,7 @@ iwd_manager_free(Iwd_Manager *m)
eina_hash_free(m->networks);
Listener *li;
EINA_LIST_FREE(m->listeners, li) free(li);
free(m->last_error);
free(m);
}

View file

@ -36,6 +36,15 @@ void iwd_manager_listener_del (Iwd_Manager *m, Iwd_Manager_Cb cb, void *data);
/* Internal: invoked by sub-objects when their state changes. */
void iwd_manager_notify (Iwd_Manager *m);
/* Latest user-facing error string, or NULL. Owned by the manager.
* Cleared on next successful state change or by iwd_manager_clear_error. */
const char *iwd_manager_last_error (const Iwd_Manager *m);
void iwd_manager_clear_error(Iwd_Manager *m);
/* Stash a one-shot error message and notify listeners. Used by D-Bus reply
* callbacks when iwd refuses a Connect/Forget/Set(Powered)/etc. */
void iwd_manager_report_error(Iwd_Manager *m, const char *fmt, ...);
/* The UI installs its passphrase prompt here. The handler must
* eventually call iwd_agent_reply()/iwd_agent_cancel() with the request. */
void iwd_manager_set_passphrase_handler(Iwd_Manager *m,

View file

@ -2,7 +2,6 @@
#include "iwd_dbus.h"
#include "iwd_props.h"
#include "iwd_manager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -80,15 +79,39 @@ iwd_network_free(Iwd_Network *n)
free(n);
}
/* Reply context captures the *manager* (which outlives all sub-objects) and
* a strdup'd SSID, never the Iwd_Network the network may disappear from
* the next scan before iwd's reply lands, and a raw back-ref would UAF. */
typedef struct
{
Iwd_Manager *m;
char *ssid;
} _Net_Reply_Ctx;
static void
_on_connect_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
{
_Net_Reply_Ctx *ctx = data;
const char *en, *em;
const char *ssid = data;
if (eldbus_message_error_get(msg, &en, &em))
fprintf(stderr, "e_iwd: connect to '%s' failed: %s: %s\n",
ssid ? ssid : "?", en, em);
free(data);
if (eldbus_message_error_get(msg, &en, &em) && ctx->m)
iwd_manager_report_error(ctx->m,
"Connect to '%s' failed: %s",
ctx->ssid ? ctx->ssid : "?", em ? em : en);
free(ctx->ssid);
free(ctx);
}
static void
_on_forget_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED)
{
_Net_Reply_Ctx *ctx = data;
const char *en, *em;
if (eldbus_message_error_get(msg, &en, &em) && ctx->m)
iwd_manager_report_error(ctx->m,
"Forget '%s' failed: %s",
ctx->ssid ? ctx->ssid : "?", em ? em : en);
free(ctx->ssid);
free(ctx);
}
int
@ -104,6 +127,16 @@ iwd_network_signal_tier(const Iwd_Network *n)
return 1;
}
static _Net_Reply_Ctx *
_reply_ctx_new(Iwd_Network *n)
{
_Net_Reply_Ctx *ctx = calloc(1, sizeof(*ctx));
if (!ctx) return NULL;
ctx->m = n->manager;
ctx->ssid = n->ssid ? strdup(n->ssid) : NULL;
return ctx;
}
void
iwd_network_connect(Iwd_Network *n)
{
@ -111,7 +144,7 @@ iwd_network_connect(Iwd_Network *n)
/* Network.Connect() takes no args; iwd will dial the registered Agent
* for a passphrase if needed. */
eldbus_proxy_call(n->proxy, "Connect", _on_connect_reply,
n->ssid ? strdup(n->ssid) : NULL, -1, "");
_reply_ctx_new(n), -1, "");
}
void
@ -124,7 +157,7 @@ iwd_network_forget(Iwd_Network *n)
Eldbus_Proxy *kp = eldbus_proxy_get(kobj, IWD_IFACE_KNOWN_NETWORK);
if (kp)
{
eldbus_proxy_call(kp, "Forget", NULL, NULL, -1, "");
eldbus_proxy_call(kp, "Forget", _on_forget_reply, _reply_ctx_new(n), -1, "");
eldbus_proxy_unref(kp);
}
eldbus_object_unref(kobj);

View file

@ -10,10 +10,9 @@ e_iwd_sources = [
'iwd/iwd_manager.c',
'iwd/iwd_device.c',
'iwd/iwd_network.c',
'ui/wifi_list.c',
'iwd/iwd_labels.c',
'ui/wifi_auth.c',
'ui/wifi_hidden.c',
'ui/wifi_status.c',
]
shared_module('module',

View file

@ -1,5 +1,6 @@
#include "wifi_auth.h"
#include <stdlib.h>
#include <string.h>
typedef struct _Auth_Ctx
{
@ -12,11 +13,27 @@ typedef struct _Auth_Ctx
} Auth_Ctx;
static void
_finish(Auth_Ctx *c, Eina_Bool ok, const char *pass)
_finish(Auth_Ctx *c, Eina_Bool ok)
{
if (c->fired) return;
c->fired = EINA_TRUE;
/* Copy the passphrase into a buffer we own so we can wipe it
* after the callback returns. The elm_entry's internal buffer
* is then overwritten before the window (and entry) are destroyed. */
char *pass = NULL;
if (ok && c->entry)
{
const char *raw = elm_entry_entry_get(c->entry);
if (raw) pass = strdup(raw);
}
if (c->cb) c->cb(c->data, pass, ok);
if (pass)
{
explicit_bzero(pass, strlen(pass));
free(pass);
}
if (c->entry) elm_entry_entry_set(c->entry, "");
if (c->win) evas_object_del(c->win);
free(c);
}
@ -24,21 +41,25 @@ _finish(Auth_Ctx *c, Eina_Bool ok, const char *pass)
static void
_on_ok(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
{
Auth_Ctx *c = data;
_finish(c, EINA_TRUE, elm_entry_entry_get(c->entry));
_finish(data, EINA_TRUE);
}
static void
_on_cancel(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
{
_finish(data, EINA_FALSE, NULL);
_finish(data, EINA_FALSE);
}
static void
_on_del(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
{
/* Window closed without cancel/ok — treat as cancel. */
_finish(data, EINA_FALSE, NULL);
Auth_Ctx *c = data;
/* The window (and entry) are being destroyed; null entry to skip the
* post-cb entry_set in _finish. */
c->win = NULL;
c->entry = NULL;
_finish(c, EINA_FALSE);
}
Evas_Object *
@ -47,6 +68,7 @@ wifi_auth_prompt(Evas_Object *parent EINA_UNUSED, const char *ssid,
Wifi_Auth_Cb cb, void *data)
{
Auth_Ctx *c = calloc(1, sizeof(*c));
if (!c) return NULL;
c->cb = cb; c->data = data;
/* A floating top-level window so the popup is actually visible —

View file

@ -1,5 +1,6 @@
#include "wifi_hidden.h"
#include <stdlib.h>
#include <string.h>
typedef struct _Hidden_Ctx
{
@ -17,9 +18,39 @@ _finish(Hidden_Ctx *c, Eina_Bool ok)
{
if (c->fired) return;
c->fired = EINA_TRUE;
const char *ssid = ok ? elm_entry_entry_get(c->e_ssid) : NULL;
const char *pass = ok ? elm_entry_entry_get(c->e_pass) : NULL;
/* Copy SSID + passphrase into buffers we own; wipe the passphrase
* (and overwrite the entry) before the window is destroyed. */
char *ssid = NULL, *pass = NULL;
if (ok)
{
if (c->e_ssid)
{
const char *r = elm_entry_entry_get(c->e_ssid);
if (r) ssid = strdup(r);
}
if (c->e_pass)
{
const char *r = elm_entry_entry_get(c->e_pass);
if (r) pass = strdup(r);
}
}
if (c->cb) c->cb(c->data, ssid, pass, ok);
if (pass)
{
explicit_bzero(pass, strlen(pass));
free(pass);
}
/* SSIDs aren't secret, but wiping keeps the heap consistent with the
* passphrase handling and avoids leaving identifiable network names in
* freed memory after a hidden-network prompt. */
if (ssid)
{
explicit_bzero(ssid, strlen(ssid));
free(ssid);
}
if (c->e_ssid) elm_entry_entry_set(c->e_ssid, "");
if (c->e_pass) elm_entry_entry_set(c->e_pass, "");
if (c->win) evas_object_del(c->win);
free(c);
}
@ -42,7 +73,11 @@ _on_cancel(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
static void
_on_del(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
{
_finish(data, EINA_FALSE);
Hidden_Ctx *c = data;
c->win = NULL;
c->e_ssid = NULL;
c->e_pass = NULL;
_finish(c, EINA_FALSE);
}
static Evas_Object *
@ -69,6 +104,7 @@ void
wifi_hidden_prompt(Evas_Object *parent EINA_UNUSED, Wifi_Hidden_Cb cb, void *data)
{
Hidden_Ctx *c = calloc(1, sizeof(*c));
if (!c) { if (cb) cb(data, NULL, NULL, EINA_FALSE); return; }
c->cb = cb; c->data = data;
/* Floating top-level so the popup actually shows. */

View file

@ -1,13 +0,0 @@
#include "wifi_list.h"
/* TODO: Genlist of networks, sorted (known first, then signal desc),
* with security icon, signal bars, and click connect/auth flow. */
Evas_Object *
wifi_list_add(Evas_Object *parent)
{
Evas_Object *gl = elm_genlist_add(parent);
return gl;
}
void wifi_list_refresh(Evas_Object *list EINA_UNUSED) { /* TODO */ }

View file

@ -1,9 +0,0 @@
#ifndef WIFI_LIST_H
#define WIFI_LIST_H
#include <Elementary.h>
Evas_Object *wifi_list_add(Evas_Object *parent);
void wifi_list_refresh(Evas_Object *list);
#endif

View file

@ -1,12 +0,0 @@
#include "wifi_status.h"
/* TODO: current connection summary widget (SSID, signal, IP, Disconnect). */
Evas_Object *
wifi_status_add(Evas_Object *parent)
{
Evas_Object *box = elm_box_add(parent);
return box;
}
void wifi_status_refresh(Evas_Object *o EINA_UNUSED) { /* TODO */ }

View file

@ -1,9 +0,0 @@
#ifndef WIFI_STATUS_H
#define WIFI_STATUS_H
#include <Elementary.h>
Evas_Object *wifi_status_add(Evas_Object *parent);
void wifi_status_refresh(Evas_Object *o);
#endif