diff --git a/meson.build b/meson.build index 718947c..6d929ef 100644 --- a/meson.build +++ b/meson.build @@ -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') diff --git a/src/e_mod_config.c b/src/e_mod_config.c index 50bfdd6..255fc0e 100644 --- a/src/e_mod_config.c +++ b/src/e_mod_config.c @@ -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; diff --git a/src/e_mod_gadget.c b/src/e_mod_gadget.c index 45d278f..62cac2c 100644 --- a/src/e_mod_gadget.c +++ b/src/e_mod_gadget.c @@ -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 +#include /* ----- 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 = diff --git a/src/e_mod_main.c b/src/e_mod_main.c index 160316a..bcd8f60 100644 --- a/src/e_mod_main.c +++ b/src/e_mod_main.c @@ -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); diff --git a/src/e_mod_popup.c b/src/e_mod_popup.c index c987cf2..c8ef68a 100644 --- a/src/e_mod_popup.c +++ b/src/e_mod_popup.c @@ -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 @@ -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 %s?
" + "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); diff --git a/src/e_mod_popup.h b/src/e_mod_popup.h index 33717a9..a053212 100644 --- a/src/e_mod_popup.h +++ b/src/e_mod_popup.h @@ -4,8 +4,9 @@ #include 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 diff --git a/src/iwd/iwd_adapter.c b/src/iwd/iwd_adapter.c index 4747dad..8cce39d 100644 --- a/src/iwd/iwd_adapter.c +++ b/src/iwd/iwd_adapter.c @@ -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; diff --git a/src/iwd/iwd_agent.c b/src/iwd/iwd_agent.c index 6947f90..e378039 100644 --- a/src/iwd/iwd_agent.c +++ b/src/iwd/iwd_agent.c @@ -1,5 +1,7 @@ #include "iwd_agent.h" #include "iwd_dbus.h" +#include "iwd_manager.h" +#include #include #include @@ -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); diff --git a/src/iwd/iwd_agent.h b/src/iwd/iwd_agent.h index 9fcc168..49e6b65 100644 --- a/src/iwd/iwd_agent.h +++ b/src/iwd/iwd_agent.h @@ -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); diff --git a/src/iwd/iwd_device.c b/src/iwd/iwd_device.c index 1c05c52..572dd5a 100644 --- a/src/iwd/iwd_device.c +++ b/src/iwd/iwd_device.c @@ -3,7 +3,6 @@ #include "iwd_props.h" #include "iwd_manager.h" #include "iwd_network.h" -#include #include #include @@ -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); } diff --git a/src/iwd/iwd_labels.c b/src/iwd/iwd_labels.c new file mode 100644 index 0000000..c3cfcf7 --- /dev/null +++ b/src/iwd/iwd_labels.c @@ -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 ""; +} diff --git a/src/iwd/iwd_labels.h b/src/iwd/iwd_labels.h new file mode 100644 index 0000000..9a4dee4 --- /dev/null +++ b/src/iwd/iwd_labels.h @@ -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 diff --git a/src/iwd/iwd_manager.c b/src/iwd/iwd_manager.c index 5cd78a2..ec4648e 100644 --- a/src/iwd/iwd_manager.c +++ b/src/iwd/iwd_manager.c @@ -4,6 +4,8 @@ #include "iwd_device.h" #include "iwd_network.h" #include +#include +#include #include #include @@ -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); } diff --git a/src/iwd/iwd_manager.h b/src/iwd/iwd_manager.h index 900fad4..f23d3b5 100644 --- a/src/iwd/iwd_manager.h +++ b/src/iwd/iwd_manager.h @@ -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, diff --git a/src/iwd/iwd_network.c b/src/iwd/iwd_network.c index 18a84d5..de83d92 100644 --- a/src/iwd/iwd_network.c +++ b/src/iwd/iwd_network.c @@ -2,7 +2,6 @@ #include "iwd_dbus.h" #include "iwd_props.h" #include "iwd_manager.h" -#include #include #include @@ -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); diff --git a/src/meson.build b/src/meson.build index a7a4324..6f52292 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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', diff --git a/src/ui/wifi_auth.c b/src/ui/wifi_auth.c index 8fdb1e5..81bcfce 100644 --- a/src/ui/wifi_auth.c +++ b/src/ui/wifi_auth.c @@ -1,5 +1,6 @@ #include "wifi_auth.h" #include +#include 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 — diff --git a/src/ui/wifi_hidden.c b/src/ui/wifi_hidden.c index 1e936f8..2f93215 100644 --- a/src/ui/wifi_hidden.c +++ b/src/ui/wifi_hidden.c @@ -1,5 +1,6 @@ #include "wifi_hidden.h" #include +#include 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. */ diff --git a/src/ui/wifi_list.c b/src/ui/wifi_list.c deleted file mode 100644 index 2717a01..0000000 --- a/src/ui/wifi_list.c +++ /dev/null @@ -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 */ } diff --git a/src/ui/wifi_list.h b/src/ui/wifi_list.h deleted file mode 100644 index cfc0bcf..0000000 --- a/src/ui/wifi_list.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef WIFI_LIST_H -#define WIFI_LIST_H - -#include - -Evas_Object *wifi_list_add(Evas_Object *parent); -void wifi_list_refresh(Evas_Object *list); - -#endif diff --git a/src/ui/wifi_status.c b/src/ui/wifi_status.c deleted file mode 100644 index 1f61cfe..0000000 --- a/src/ui/wifi_status.c +++ /dev/null @@ -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 */ } diff --git a/src/ui/wifi_status.h b/src/ui/wifi_status.h deleted file mode 100644 index 857f386..0000000 --- a/src/ui/wifi_status.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef WIFI_STATUS_H -#define WIFI_STATUS_H - -#include - -Evas_Object *wifi_status_add(Evas_Object *parent); -void wifi_status_refresh(Evas_Object *o); - -#endif