From 0fe631b4ed8e6ba9f9551eef3f2464fd0b94b4a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 15:55:03 +0000 Subject: [PATCH 1/3] WiFi helper: act on the connected adapter, not the first one alphabetically With a built-in card plus a USB adapter, status reported 'Not connected' for the idle built-in card while the USB adapter carried the connection. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_013wi3ZQveYdjfcrEsyazq32 --- .../files/usr/local/bin/kiosk-wifi-helper | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/provision/files/usr/local/bin/kiosk-wifi-helper b/provision/files/usr/local/bin/kiosk-wifi-helper index f1d7d10..09af41f 100755 --- a/provision/files/usr/local/bin/kiosk-wifi-helper +++ b/provision/files/usr/local/bin/kiosk-wifi-helper @@ -168,8 +168,39 @@ default_country() { echo "${c^^}" } +# Every wireless interface, by what it is (see detect_iface). +list_wifi_ifaces() { + local d + for d in $SYS_CLASS/net/*; do + if [[ -d "$d/wireless" || -e "$d/phy80211" ]]; then + basename "$d" + fi + done +} + +# The wireless adapter that is connected right now (associated and has an +# address), if any. With a built-in card plus a USB dongle, the connected +# one is often not the first in name order - status/scan/connect must act +# on the adapter that is actually carrying the connection. +connected_iface() { + local i + for i in $(list_wifi_ifaces); do + if [[ -n "$(current_ssid "$i")" && -n "$(iface_ip "$i")" ]]; then + echo "$i" + return 0 + fi + done + return 1 +} + +# Which adapter to use: the connected one, else the one our netplan file +# configures, else the first found. +pick_iface() { + connected_iface || prefer_configured_iface || detect_iface +} + need_iface() { - IFACE=$(prefer_configured_iface || detect_iface) || reply_error "No WiFi adapter found. If it's a USB adapter, check it's plugged in." + IFACE=$(pick_iface) || reply_error "No WiFi adapter found. If it's a USB adapter, check it's plugged in." command -v iw >/dev/null || reply_error "WiFi support (iw, wpasupplicant) isn't installed - run Core Settings -> WiFi once from the admin menu" } @@ -254,7 +285,7 @@ do_region_set() { do_status() { local iface ssid="" ip="" configured configured=$(configured_ssid) - if ! iface=$(detect_iface); then + if ! iface=$(pick_iface); then jq -cn --arg c "$configured" '{ok:true,iface:"",connected:false,ssid:"",ip:"",configured:$c}' return 0 fi From 07e7d2866f3c3a51092200e06d76c29a879e364b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 16:00:10 +0000 Subject: [PATCH 2/3] WiFi screen: reach the root helper through a socket instead of sudo; adapter dropdown The kiosk app can end up with the no-new-privileges flag, after which sudo refuses to run ("The no new privileges flag is set"). The helper is now served over a root-owned, kiosk-group-only unix socket (systemd socket activation); sudo stays as a fallback. With several WiFi adapters the screen shows a dropdown and lists all of them; status/scan/connect take an iface. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_013wi3ZQveYdjfcrEsyazq32 --- Readme.md | 4 +- kiosk-app/main.js | 72 +++++++++++++++-- kiosk-app/wifi.html | 57 +++++++++++-- lib/provision.sh | 17 ++++ menus/complete_uninstall.sh | 2 + .../systemd/system/kiosk-wifi-helper.socket | 14 ++++ .../systemd/system/kiosk-wifi-helper@.service | 10 +++ .../files/usr/local/bin/kiosk-wifi-helper | 81 +++++++++++++++++-- .../usr/local/bin/kiosk-wifi-helper-socket | 38 +++++++++ 9 files changed, 271 insertions(+), 24 deletions(-) create mode 100644 provision/files/etc/systemd/system/kiosk-wifi-helper.socket create mode 100644 provision/files/etc/systemd/system/kiosk-wifi-helper@.service create mode 100755 provision/files/usr/local/bin/kiosk-wifi-helper-socket diff --git a/Readme.md b/Readme.md index fa8839b..d3c1a1b 100644 --- a/Readme.md +++ b/Readme.md @@ -528,7 +528,9 @@ It writes the same netplan file as Core Settings → WiFi (`/etc/netplan/60-kios **About Fn:** the Fn key is handled inside most keyboards and never reaches the computer, so it isn't part of the combo - holding it too does no harm where Fn+W is still W. On compact keyboards where Fn+W is something else (often Up arrow), leave Fn out. -The root part is `/usr/local/bin/kiosk-wifi-helper` (status/scan/connect only), which the kiosk user may run via sudo through `/etc/sudoers.d/kiosk-wifi` - nothing else. Existing kiosks get it with Advanced → Upgrade. +**More than one WiFi adapter** (e.g. a built-in card and a USB dongle): a *WiFi adapter* dropdown appears at the top, and the status box lists every adapter with what it's connected to. Status, scan and connect act on the one you pick; by default it's the adapter that's connected right now. + +The root part is `/usr/local/bin/kiosk-wifi-helper` (status/scan/connect and region only). The app reaches it through a root-owned socket, `/run/kiosk-wifi-helper.sock` (`kiosk-wifi-helper.socket`, group-restricted to the kiosk user, `0660`), rather than `sudo`: the app can end up running with Linux's "no new privileges" flag, and then sudo refuses with *"The no new privileges flag is set"*. A passwordless-sudo rule (`/etc/sudoers.d/kiosk-wifi`) stays as a fallback. Existing kiosks get both with Advanced → Upgrade. --- diff --git a/kiosk-app/main.js b/kiosk-app/main.js index 14fd168..c118b3c 100644 --- a/kiosk-app/main.js +++ b/kiosk-app/main.js @@ -1,5 +1,6 @@ const {app,BrowserWindow,BrowserView,globalShortcut,ipcMain,dialog,session}=require('electron'); const {exec,spawn}=require('child_process'); +const net=require('net'); const fs=require('fs'); const path=require('path'); const os=require('os'); @@ -1215,7 +1216,56 @@ const WIFI_HELPER='/usr/local/bin/kiosk-wifi-helper'; const WIFI_IDLE_TIMEOUT=5*60000; // closes after 5 minutes untouched let wifiWindow=null,wifiUnlocked=false,wifiIdleTimer=null; -function runWifiHelper(action,input){ +const WIFI_SOCKET='/run/kiosk-wifi-helper.sock'; + +// Parse the helper's one JSON reply line (the last line of its output). +function parseWifiReply(out){ + const line=out.trim().split('\n').pop(); + return JSON.parse(line); +} + +// Preferred route: a root-owned socket (kiosk-wifi-helper.socket). Unlike +// sudo it keeps working when this app has the "no new privileges" flag set. +// Resolves null when the socket isn't usable (not installed yet, or this +// user isn't allowed), so the caller falls back to sudo. +function runWifiHelperSocket(action,input){ + return new Promise(resolve=>{ + let out='',done=false; + const finish=result=>{if(!done){done=true;clearTimeout(killer);resolve(result);}}; + const sock=net.connect({path:WIFI_SOCKET,allowHalfOpen:true}); + const killer=setTimeout(()=>{sock.destroy();finish({ok:false,error:'The WiFi helper timed out'});},120000); + sock.on('connect',()=>{ + // Request = action line, then the JSON body; half-close so the + // helper sees end of input. + sock.end(action+'\n'+(input?JSON.stringify(input):'')); + }); + sock.on('data',d=>{out+=d;}); + sock.on('error',e=>{ + if(out){ + // A reset after the reply arrived - use the reply. + try{finish(parseWifiReply(out));return;}catch(_){} + } + if(!out&&['ENOENT','ECONNREFUSED','EACCES','EPERM'].includes(e.code)){ + console.log('[WIFI] Helper socket unavailable ('+e.code+') - falling back to sudo'); + finish(null); + }else{ + finish({ok:false,error:'Could not reach the WiFi helper: '+e.message}); + } + }); + sock.on('close',()=>{ + if(done)return; + try{ + finish(parseWifiReply(out)); + }catch(e){ + console.error('[WIFI] helper socket output:',out); + finish({ok:false,error:'The WiFi helper gave no usable answer'}); + } + }); + }); +} + +// Fallback: the helper through passwordless sudo (/etc/sudoers.d/kiosk-wifi). +function runWifiHelperSudo(action,input){ return new Promise(resolve=>{ let out='',err='',done=false; const finish=result=>{if(!done){done=true;resolve(result);}}; @@ -1232,12 +1282,11 @@ function runWifiHelper(action,input){ child.on('error',e=>{clearTimeout(killer);finish({ok:false,error:'Could not run the WiFi helper: '+e.message});}); child.on('close',()=>{ clearTimeout(killer); - const line=out.trim().split('\n').pop(); try{ - finish(JSON.parse(line)); + finish(parseWifiReply(out)); }catch(e){ - // Not our JSON - typically sudo refusing ("a password is required") - // on a kiosk set up before this feature: Upgrade installs the rule. + // Not our JSON - typically sudo refusing ("a password is required", + // or the "no new privileges" flag) - Upgrade installs the socket. console.error('[WIFI] helper output:',out,err); finish({ok:false,error:(err.trim()||'The WiFi helper is not installed')+' - run Advanced -> Upgrade from the admin menu'}); } @@ -1247,6 +1296,12 @@ function runWifiHelper(action,input){ }); } +async function runWifiHelper(action,input){ + const viaSocket=await runWifiHelperSocket(action,input); + if(viaSocket!==null)return viaSocket; + return runWifiHelperSudo(action,input); +} + function resetWifiIdleTimer(){ if(wifiIdleTimer)clearTimeout(wifiIdleTimer); wifiIdleTimer=setTimeout(()=>{ @@ -1327,7 +1382,10 @@ ipcMain.handle('wifi-request',async(event,req)=>{ return result; } if(!wifiUnlocked)return{ok:false,error:'Enter the PIN first'}; - if(action==='status'||action==='scan')return runWifiHelper(action); + if(action==='ifaces')return runWifiHelper('ifaces'); + if(action==='status'||action==='scan'){ + return runWifiHelper(action,req.iface?{iface:String(req.iface)}:null); + } if(action==='region-status')return runWifiHelper('region-status'); if(action==='region-set'){ const input={}; @@ -1337,7 +1395,7 @@ ipcMain.handle('wifi-request',async(event,req)=>{ } if(action==='connect'){ console.log('[WIFI] Connecting to',JSON.stringify(String(req.ssid||''))); - const result=await runWifiHelper('connect',{ssid:String(req.ssid||''),password:String(req.password||'')}); + const result=await runWifiHelper('connect',{ssid:String(req.ssid||''),password:String(req.password||''),iface:req.iface?String(req.iface):''}); console.log('[WIFI] Connect result:',result.ok?('ok '+result.ip):result.error); return result; } diff --git a/kiosk-app/wifi.html b/kiosk-app/wifi.html index dbb6277..550bda5 100644 --- a/kiosk-app/wifi.html +++ b/kiosk-app/wifi.html @@ -95,7 +95,11 @@