Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191#include "wifi.h"#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <signal.h>#include <sys/wait.h>#include <sys/stat.h>#include <fcntl.h>#include <errno.h>#include <dirent.h>#include <time.h>#include <stdarg.h>
// Defined in ac-native.cextern void ac_log(const char *fmt, ...);
// Write to both ac_log and the wifi ring buffer (readable from JS)static void wifi_log(ACWifi *wifi, const char *fmt, ...) __attribute__((format(printf, 2, 3)));static void wifi_log(ACWifi *wifi, const char *fmt, ...) { char buf[128]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); ac_log("[wifi] %s", buf); if (wifi) { int idx = wifi->log_count % 32; strncpy(wifi->log[idx], buf, 127); wifi->log[idx][127] = 0; wifi->log_count++; }}
// ============================================================// Helpers (called from wifi thread only — blocking is fine)// ============================================================
static int run_cmd(const char *cmd) { ac_log("[wifi] exec: %s", cmd); int r = system(cmd); if (r != 0) ac_log("[wifi] cmd failed (%d): %s", r, cmd); return r;}
static int file_exists(const char *path) { struct stat st; return stat(path, &st) == 0;}
static int detect_iface(char *out, int out_len) { // Chromebooks (esp. HP 14 G7 / Jasper Lake w/ Intel AX201) often boot // with WiFi rfkill-blocked at the ACPI / coreboot level — the iwlwifi // driver loads but mac80211 refuses to register an interface, so // `iw dev` returns nothing. Unblock unconditionally before we look // for an interface; it's a no-op on hardware that wasn't blocked. if (system("rfkill unblock all >/dev/null 2>&1") != 0) { // rfkill binary not available — fall back to writing the soft // flag on every rfkill node we can find. DIR *rd = opendir("/sys/class/rfkill"); if (rd) { struct dirent *ent; while ((ent = readdir(rd)) != NULL) { if (ent->d_name[0] == '.') continue; char path[256]; snprintf(path, sizeof(path), "/sys/class/rfkill/%s/soft", ent->d_name); FILE *f = fopen(path, "w"); if (f) { fputs("0\n", f); fclose(f); } } closedir(rd); } }
FILE *fp = popen("iw dev 2>/dev/null | grep Interface | head -1 | awk '{print $2}'", "r"); if (fp) { char buf[32] = ""; if (fgets(buf, sizeof(buf), fp)) { buf[strcspn(buf, "\n")] = 0; if (buf[0]) { snprintf(out, out_len, "%s", buf); pclose(fp); return 1; } } pclose(fp); } fp = popen("ls -d /sys/class/net/*/wireless 2>/dev/null | head -1", "r"); if (fp) { char buf[128] = ""; if (fgets(buf, sizeof(buf), fp)) { buf[strcspn(buf, "\n")] = 0; char *start = strstr(buf, "/net/"); if (start) { start += 5; char *end = strchr(start, '/'); if (end) { *end = 0; snprintf(out, out_len, "%s", start); pclose(fp); return 1; } } } pclose(fp); } return 0;}
// Thread-safe state update helpersstatic void wifi_set_state(ACWifi *wifi, WiFiState st) { pthread_mutex_lock(&wifi->lock); wifi->state = st; pthread_mutex_unlock(&wifi->lock);}
static void wifi_set_status(ACWifi *wifi, const char *msg) { pthread_mutex_lock(&wifi->lock); snprintf(wifi->status_msg, sizeof(wifi->status_msg), "%s", msg); pthread_mutex_unlock(&wifi->lock);}
static void wifi_set_state_and_status(ACWifi *wifi, WiFiState st, const char *msg) { pthread_mutex_lock(&wifi->lock); wifi->state = st; snprintf(wifi->status_msg, sizeof(wifi->status_msg), "%s", msg); pthread_mutex_unlock(&wifi->lock);}
// ============================================================// Scan (runs on wifi thread)// ============================================================
static void wifi_do_scan(ACWifi *wifi) { if (!wifi->iface[0]) return;
wifi_set_state_and_status(wifi, WIFI_STATE_SCANNING, "scanning...");
// Bring interface up + set regulatory domain (blocking — that's fine here) char cmd[256]; snprintf(cmd, sizeof(cmd), "ip link set %s up 2>/dev/null", wifi->iface); run_cmd(cmd); run_cmd("iw reg set US 2>/dev/null");
// Remove stale scan files unlink("/tmp/wifi_scan.txt"); unlink("/tmp/wifi_scan_err.txt");
// Run scan synchronously on this thread (no need for background shell) snprintf(cmd, sizeof(cmd), "iw dev %s scan > /tmp/wifi_scan.txt 2>/tmp/wifi_scan_err.txt", wifi->iface); int rc = run_cmd(cmd); if (rc != 0) { ac_log("[wifi] scan returned code %d", rc); FILE *err = fopen("/tmp/wifi_scan_err.txt", "r"); if (err) { char errbuf[256] = ""; while (fgets(errbuf, sizeof(errbuf), err)) { errbuf[strcspn(errbuf, "\n")] = 0; if (errbuf[0]) ac_log("[wifi] scan stderr: %s", errbuf); } fclose(err); } }
// Parse scan results FILE *fp = fopen("/tmp/wifi_scan.txt", "r"); if (!fp) { wifi_set_state_and_status(wifi, WIFI_STATE_SCAN_DONE, "scan failed"); return; }
WiFiNetwork nets[WIFI_MAX_NETWORKS]; int count = 0; char line[512]; int cur = -1;
while (fgets(line, sizeof(line), fp) && count < WIFI_MAX_NETWORKS) { char bssid[18]; if (sscanf(line, "BSS %17s", bssid) == 1) { cur = count++; memset(&nets[cur], 0, sizeof(WiFiNetwork)); strncpy(nets[cur].bssid, bssid, 17); nets[cur].signal = -100; } if (cur < 0) continue;
int sig; if (sscanf(line, "\tsignal: %d", &sig) == 1) nets[cur].signal = sig;
char ssid[64]; if (sscanf(line, "\tSSID: %63[^\n]", ssid) == 1) strncpy(nets[cur].ssid, ssid, WIFI_SSID_MAX - 1);
if (strstr(line, "WPA") || strstr(line, "RSN")) nets[cur].encrypted = 1; } fclose(fp); unlink("/tmp/wifi_scan.txt");
// Sort by signal (strongest first) for (int i = 0; i < count - 1; i++) for (int j = i + 1; j < count; j++) if (nets[j].signal > nets[i].signal) { WiFiNetwork tmp = nets[i]; nets[i] = nets[j]; nets[j] = tmp; }
// Remove empty SSIDs int w = 0; for (int r = 0; r < count; r++) if (nets[r].ssid[0]) { if (w != r) nets[w] = nets[r]; w++; }
// Commit results under lock pthread_mutex_lock(&wifi->lock); memcpy(wifi->networks, nets, sizeof(WiFiNetwork) * w); wifi->network_count = w; wifi->state = WIFI_STATE_SCAN_DONE; snprintf(wifi->status_msg, sizeof(wifi->status_msg), "%d networks", w); pthread_mutex_unlock(&wifi->lock);
wifi_log(wifi, "Scan complete: %d networks", w);}
// ============================================================// Connect (runs on wifi thread)// ============================================================
static void wifi_do_connect(ACWifi *wifi, const char *ssid, const char *password) { if (!ssid || !wifi->iface[0]) return;
wifi_set_state_and_status(wifi, WIFI_STATE_CONNECTING, "connecting..."); wifi_log(wifi, "Connecting to '%s'", ssid);
// Kill any existing wpa_supplicant / dhclient if (wifi->wpa_pid > 0) { kill(wifi->wpa_pid, SIGTERM); waitpid(wifi->wpa_pid, NULL, 0); wifi->wpa_pid = 0; } if (wifi->dhcp_pid > 0) { kill(wifi->dhcp_pid, SIGTERM); waitpid(wifi->dhcp_pid, NULL, 0); wifi->dhcp_pid = 0; } run_cmd("killall wpa_supplicant 2>/dev/null; killall dhclient 2>/dev/null"); usleep(300000); // let a just-killed wpa_supplicant release wlan0 + its ctrl socket
// Write wpa_supplicant config FILE *fp = fopen("/tmp/wpa.conf", "w"); if (!fp) { wifi_set_state_and_status(wifi, WIFI_STATE_FAILED, "config error"); return; } fprintf(fp, "ctrl_interface=/var/run/wpa_supplicant\n"); fprintf(fp, "update_config=1\n\n"); fprintf(fp, "network={\n"); fprintf(fp, " ssid=\"%s\"\n", ssid); if (password && password[0]) fprintf(fp, " psk=\"%s\"\n", password); else fprintf(fp, " key_mgmt=NONE\n"); fprintf(fp, "}\n"); fclose(fp);
// Create required directories mkdir("/var", 0755); mkdir("/var/run", 0755); mkdir("/var/run/wpa_supplicant", 0755);
// Remove stale ctrl_interface socket — wpa_supplicant refuses to start // if /var/run/wpa_supplicant/<iface> already exists from a prior run { char sock_path[128]; snprintf(sock_path, sizeof(sock_path), "/var/run/wpa_supplicant/%s", wifi->iface); if (unlink(sock_path) == 0) wifi_log(wifi, "Removed stale socket: %s", sock_path); }
// Start wpa_supplicant pid_t pid = fork(); if (pid == 0) { const char *wpa_paths[] = { "/bin/wpa_supplicant", "/usr/bin/wpa_supplicant", "/usr/sbin/wpa_supplicant", "/sbin/wpa_supplicant", NULL }; for (int i = 0; wpa_paths[i]; i++) { if (file_exists(wpa_paths[i])) { execl(wpa_paths[i], "wpa_supplicant", "-i", wifi->iface, "-c", "/tmp/wpa.conf", "-B", "-P", "/tmp/wpa.pid", NULL); } } _exit(1); } wifi->wpa_pid = pid; waitpid(pid, NULL, 0); // Wait for wpa_supplicant to daemonize (-B)
// Confirm the daemon actually came up: -B forks and the parent exits, so a // failed start (interface still busy from the previous connect, exec not // found) leaves nothing listening and wpa_cli reports an empty state for // the whole 60s poll. Wait briefly for the ctrl socket, retrying the start // once, before committing to the poll loop. Bug seen on CULTUREHUB LA // reconnects right after another network came up. { char sock_path[128]; snprintf(sock_path, sizeof(sock_path), "/var/run/wpa_supplicant/%s", wifi->iface); int up = 0; for (int attempt = 0; attempt < 2 && !up; attempt++) { for (int w = 0; w < 20; w++) { // up to ~1s if (access(sock_path, F_OK) == 0) { up = 1; break; } usleep(50000); } if (up) break; wifi_log(wifi, "wpa_supplicant didn't come up, restarting (attempt %d)", attempt + 1); run_cmd("killall wpa_supplicant 2>/dev/null"); usleep(300000); unlink(sock_path); pid_t rpid = fork(); if (rpid == 0) { const char *wpa_paths[] = { "/bin/wpa_supplicant", "/usr/bin/wpa_supplicant", "/usr/sbin/wpa_supplicant", "/sbin/wpa_supplicant", NULL }; for (int i = 0; wpa_paths[i]; i++) if (file_exists(wpa_paths[i])) execl(wpa_paths[i], "wpa_supplicant", "-i", wifi->iface, "-c", "/tmp/wpa.conf", "-B", "-P", "/tmp/wpa.pid", NULL); _exit(1); } wifi->wpa_pid = rpid; waitpid(rpid, NULL, 0); } if (!up) wifi_log(wifi, "wpa_supplicant not responding for '%s' — will poll anyway", ssid); }
pthread_mutex_lock(&wifi->lock); strncpy(wifi->connected_ssid, ssid, WIFI_SSID_MAX - 1); pthread_mutex_unlock(&wifi->lock);
// Poll for WPA completion + DHCP (blocking loop on this thread) int connect_ticks = 0; int dhcp_started = 0; char last_wpa_state[64] = "";
while (connect_ticks < 1200 && wifi->thread_running) { // ~60 seconds max (50ms polls) // Check if a new command interrupted us if (wifi->pending_cmd != WIFI_CMD_NONE) { wifi_log(wifi, "Connect interrupted by new command"); return; }
usleep(50000); // 50ms between polls connect_ticks++;
// Check wpa_supplicant status char cmd[256]; snprintf(cmd, sizeof(cmd), "wpa_cli -i %s status 2>/dev/null | grep wpa_state", wifi->iface); FILE *wfp = popen(cmd, "r"); if (!wfp) continue;
char line[128] = ""; fgets(line, sizeof(line), wfp); pclose(wfp);
// Extract WPA state for logging/status { char *eq = strchr(line, '='); const char *st = eq ? eq + 1 : line; char state_str[64] = ""; strncpy(state_str, st, sizeof(state_str) - 1); state_str[strcspn(state_str, "\n\r")] = 0; if (state_str[0] && strcmp(state_str, last_wpa_state) != 0) { wifi_log(wifi, "WPA: %s (tick %d)", state_str, connect_ticks); strncpy(last_wpa_state, state_str, sizeof(last_wpa_state) - 1); // Update user-visible status with WPA state detail if (strstr(state_str, "SCANNING")) wifi_set_status(wifi, "scanning..."); else if (strstr(state_str, "ASSOCIATING")) wifi_set_status(wifi, "associating..."); else if (strstr(state_str, "4WAY_HANDSHAKE")) wifi_set_status(wifi, "authenticating..."); else if (strstr(state_str, "GROUP_HANDSHAKE")) wifi_set_status(wifi, "group handshake..."); } }
if (strstr(line, "COMPLETED")) { // WPA connected — start DHCP if not already running if (!dhcp_started) { wifi_log(wifi, "WPA connected, starting DHCP"); wifi_set_status(wifi, "getting IP...");
pid_t dpid = fork(); if (dpid == 0) { int fd = open("/tmp/dhcp.log", O_WRONLY|O_CREAT|O_TRUNC, 0644); if (fd >= 0) { dup2(fd, 2); dup2(fd, 1); close(fd); } // Prefer udhcpc (busybox) — fast, no script needed const char *udhcpc = NULL; if (file_exists("/sbin/udhcpc")) udhcpc = "/sbin/udhcpc"; else if (file_exists("/bin/udhcpc")) udhcpc = "/bin/udhcpc"; else if (file_exists("/usr/bin/udhcpc")) udhcpc = "/usr/bin/udhcpc"; if (udhcpc) { fprintf(stderr, "[wifi] using udhcpc: %s\n", udhcpc); execl(udhcpc, "udhcpc", "-i", wifi->iface, "-n", // exit if no lease (don't background) "-q", // quit after obtaining lease "-s", "/usr/share/udhcpc/default.script", "-t", "5", // 5 retries "-T", "3", // 3 second timeout NULL); // execl failed fprintf(stderr, "[wifi] udhcpc execl failed: %s\n", strerror(errno)); } // Fallback to dhclient const char *dhc_paths[] = { "/sbin/dhclient", "/usr/sbin/dhclient", NULL }; const char *script = "/sbin/dhclient-script"; if (file_exists("/bin/dhclient-script")) script = "/bin/dhclient-script"; for (int i = 0; dhc_paths[i]; i++) { if (file_exists(dhc_paths[i])) { execl(dhc_paths[i], "dhclient", "-v", "-1", "-sf", script, "-pf", "/tmp/dhclient.pid", "-lf", "/tmp/dhclient.leases", wifi->iface, NULL); } } _exit(1); } wifi->dhcp_pid = dpid; dhcp_started = 1; }
// Check for IP address snprintf(cmd, sizeof(cmd), "ip addr show %s 2>/dev/null | grep 'inet ' | awk '{print $2}' | cut -d/ -f1", wifi->iface); FILE *ifp = popen(cmd, "r"); if (ifp) { char ip[32] = ""; if (fgets(ip, sizeof(ip), ifp)) { ip[strcspn(ip, "\n")] = 0; if (ip[0] && strcmp(ip, "0.0.0.0") != 0) { pthread_mutex_lock(&wifi->lock); strncpy(wifi->ip_address, ip, sizeof(wifi->ip_address) - 1); wifi->state = WIFI_STATE_CONNECTED; snprintf(wifi->status_msg, sizeof(wifi->status_msg), "%s", ip); pthread_mutex_unlock(&wifi->lock); wifi_log(wifi, "Connected! IP: %s", ip);
// Captive portal detection + auto-accept { char portal_cmd[512]; // Step 1: Check connectivity (expect 204 = no portal) snprintf(portal_cmd, sizeof(portal_cmd), "curl -sL -o /dev/null -w '%%{http_code}' " "--max-time 5 --connect-timeout 3 " "http://connectivitycheck.gstatic.com/generate_204 " "2>/dev/null"); FILE *pf = popen(portal_cmd, "r"); if (pf) { char code[8] = ""; if (fgets(code, sizeof(code), pf)) code[strcspn(code, "\n")] = 0; pclose(pf); if (strcmp(code, "204") == 0) { wifi_log(wifi, "Internet: OK (no captive portal)"); } else if (code[0]) { wifi_log(wifi, "Captive portal detected (HTTP %s), trying auto-accept...", code);
// Step 2: Get the portal redirect URL + save HTML for debugging snprintf(portal_cmd, sizeof(portal_cmd), "curl -sk --max-time 8 --connect-timeout 5 " "-o /tmp/portal_page.html -w '%%{url_effective}' " "-L http://connectivitycheck.gstatic.com/generate_204 " "2>/dev/null"); FILE *uf = popen(portal_cmd, "r"); char portal_url[256] = ""; if (uf) { if (fgets(portal_url, sizeof(portal_url), uf)) portal_url[strcspn(portal_url, "\n")] = 0; pclose(uf); } wifi_log(wifi, "Portal URL: %s", portal_url); // Save portal HTML to USB for inspection run_cmd("cp /tmp/portal_page.html /mnt/portal_page.html 2>/dev/null || true");
// Step 3: Try multiple accept strategies // Strategy A: ClearPass/Aruba — change cmd=login to cmd=authenticate { char auth_url[512] = ""; const char *cmd_pos = strstr(portal_url, "cmd=login"); if (cmd_pos) { int prefix_len = (int)(cmd_pos - portal_url); snprintf(auth_url, sizeof(auth_url), "%.*scmd=authenticate%s", prefix_len, portal_url, cmd_pos + 9); wifi_log(wifi, "ClearPass: trying %s", auth_url); snprintf(portal_cmd, sizeof(portal_cmd), "curl -skL -o /dev/null -w '%%{http_code}' " "--max-time 10 '%s' 2>/dev/null", auth_url); FILE *cf2 = popen(portal_cmd, "r"); if (cf2) { char cc[8] = ""; if (fgets(cc, sizeof(cc), cf2)) cc[strcspn(cc, "\n")] = 0; pclose(cf2); wifi_log(wifi, "ClearPass auth response: HTTP %s", cc); } } } // Strategy B: Extract HTML form action and POST snprintf(portal_cmd, sizeof(portal_cmd), "sh -c '" "ACTION=$(grep -oi \"action=\\\"[^\\\"]*\\\"\" /tmp/portal_page.html 2>/dev/null " "| head -1 | sed \"s/action=\\\"//;s/\\\"//\"); " "if [ -n \"$ACTION\" ]; then " " case \"$ACTION\" in http*) URL=\"$ACTION\" ;; /*) " " HOST=$(echo \"%s\" | sed \"s|^\\(https\\?://[^/]*\\).*|\\1|\"); " " URL=\"${HOST}${ACTION}\" ;; " " *) URL=\"%s\" ;; esac; " " curl -skL -o /dev/null -w \"%%{http_code}\" " " --max-time 10 -X POST \"$URL\" 2>/dev/null; " "else " " curl -skL -o /dev/null -w \"%%{http_code}\" " " --max-time 10 \"%s\" 2>/dev/null; " "fi'", portal_url, portal_url, portal_url); FILE *af = popen(portal_cmd, "r"); if (af) { char acode[8] = ""; if (fgets(acode, sizeof(acode), af)) acode[strcspn(acode, "\n")] = 0; pclose(af); wifi_log(wifi, "Portal form submit: HTTP %s", acode); } // Strategy C: POST to portal URL with common accept params snprintf(portal_cmd, sizeof(portal_cmd), "curl -skL -o /dev/null -w '%%{http_code}' " "--max-time 10 -X POST " "-d 'accept=true&cmd=authenticate&Login=Login' " "'%s' 2>/dev/null", portal_url); FILE *pf2 = popen(portal_cmd, "r"); if (pf2) { char pc[8] = ""; if (fgets(pc, sizeof(pc), pf2)) pc[strcspn(pc, "\n")] = 0; pclose(pf2); wifi_log(wifi, "Portal POST accept: HTTP %s", pc); }
// Step 4: Re-check connectivity usleep(1000000); // 1s for portal to propagate snprintf(portal_cmd, sizeof(portal_cmd), "curl -sL -o /dev/null -w '%%{http_code}' " "--max-time 5 --connect-timeout 3 " "http://connectivitycheck.gstatic.com/generate_204 " "2>/dev/null"); FILE *cf = popen(portal_cmd, "r"); if (cf) { char ccode[8] = ""; if (fgets(ccode, sizeof(ccode), cf)) ccode[strcspn(ccode, "\n")] = 0; pclose(cf); if (strcmp(ccode, "204") == 0) wifi_log(wifi, "Captive portal cleared!"); else wifi_log(wifi, "Portal still active (HTTP %s) — may need manual login", ccode); } } else { wifi_log(wifi, "Connectivity check failed (no response)"); } } }
// Save credentials for auto-reconnect strncpy(wifi->last_ssid, ssid, WIFI_SSID_MAX - 1); strncpy(wifi->last_pass, password ? password : "", WIFI_PASS_MAX - 1); wifi->reconnect_failures = 0;
// Ensure resolv.conf has DNS fallbacks mkdir("/etc", 0755); FILE *rf = fopen("/etc/resolv.conf", "a"); if (rf) { fseek(rf, 0, SEEK_END); if (ftell(rf) == 0) fprintf(rf, "nameserver 8.8.8.8\nnameserver 1.1.1.1\n"); fclose(rf); } pclose(ifp); return; // Success! } } pclose(ifp); }
// Check if DHCP client died if (wifi->dhcp_pid > 0) { int status; pid_t r = waitpid(wifi->dhcp_pid, &status, WNOHANG); if (r > 0) { if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { wifi_log(wifi, "DHCP exit %d", WEXITSTATUS(status)); FILE *dlog = fopen("/tmp/dhcp.log", "r"); if (dlog) { char dline[256]; while (fgets(dline, sizeof(dline), dlog)) wifi_log(wifi, "dhclient: %s", dline); fclose(dlog); } // Retry DHCP on next iteration wifi->dhcp_pid = 0; dhcp_started = 0; } if (WIFSIGNALED(status)) { wifi_log(wifi, "dhclient killed by signal %d", WTERMSIG(status)); wifi->dhcp_pid = 0; dhcp_started = 0; } } } } else if (strstr(line, "DISCONNECTED") || strstr(line, "INTERFACE_DISABLED")) { // Still waiting for WPA auth if (connect_ticks > 200) { // ~10 seconds (50ms polls) wifi_set_state_and_status(wifi, WIFI_STATE_FAILED, "auth failed"); wifi_log(wifi, "Auth failed after %d ticks", connect_ticks); return; } } }
// Timed out if (wifi->state == WIFI_STATE_CONNECTING) { wifi_set_state_and_status(wifi, WIFI_STATE_FAILED, "timeout"); wifi_log(wifi, "Connect timed out after 60s (last WPA: %s)", last_wpa_state); }}
// ============================================================// Disconnect (runs on wifi thread)// ============================================================
static void wifi_do_disconnect(ACWifi *wifi) { run_cmd("killall wpa_supplicant 2>/dev/null"); run_cmd("killall dhclient 2>/dev/null");
char cmd[128]; snprintf(cmd, sizeof(cmd), "ip addr flush dev %s 2>/dev/null", wifi->iface); run_cmd(cmd);
if (wifi->wpa_pid > 0) { kill(wifi->wpa_pid, SIGTERM); waitpid(wifi->wpa_pid, NULL, 0); } if (wifi->dhcp_pid > 0) { kill(wifi->dhcp_pid, SIGTERM); waitpid(wifi->dhcp_pid, NULL, 0); }
pthread_mutex_lock(&wifi->lock); wifi->wpa_pid = 0; wifi->dhcp_pid = 0; wifi->state = WIFI_STATE_OFF; wifi->connected_ssid[0] = 0; wifi->ip_address[0] = 0; snprintf(wifi->status_msg, sizeof(wifi->status_msg), "disconnected"); pthread_mutex_unlock(&wifi->lock);
wifi_log(wifi, "Disconnected");}
// ============================================================// Auto-connect (runs on wifi thread)// ============================================================
// Hardcoded fallback SSID/pass (matches wifi.mjs AC_SSID/AC_PASS)#define AC_SSID "aesthetic.computer"#define AC_PASS "aesthetic.computer"
static void wifi_do_autoconnect(ACWifi *wifi) { wifi_log(wifi, "Auto-connect: scanning...");
// Step 1: Scan wifi_do_scan(wifi); if (wifi->network_count == 0) { wifi_log(wifi, "Auto-connect: no networks found"); wifi_set_state_and_status(wifi, WIFI_STATE_SCAN_DONE, "no networks"); return; }
// Step 2: Read saved credentials from /mnt/wifi_creds.json // Format: [{"ssid":"MyNet","pass":"secret"}, ...] typedef struct { char ssid[WIFI_SSID_MAX]; char pass[WIFI_PASS_MAX]; } SavedCred; SavedCred creds[16]; int cred_count = 0;
// Always include preset networks strncpy(creds[0].ssid, AC_SSID, WIFI_SSID_MAX - 1); strncpy(creds[0].pass, AC_PASS, WIFI_PASS_MAX - 1); cred_count = 1; // ATT home network strncpy(creds[cred_count].ssid, "ATT2AWTpcr", WIFI_SSID_MAX - 1); strncpy(creds[cred_count].pass, "t84q%7%g2h8u", WIFI_PASS_MAX - 1); cred_count++; // ATT secondary strncpy(creds[cred_count].ssid, "ATTcifXGXi", WIFI_SSID_MAX - 1); strncpy(creds[cred_count].pass, "dvt%mnk8h6z", WIFI_PASS_MAX - 1); cred_count++; // GettyLink (open network, no password) strncpy(creds[cred_count].ssid, "GettyLink", WIFI_SSID_MAX - 1); creds[cred_count].pass[0] = '\0'; cred_count++; // Tondo_Guest strncpy(creds[cred_count].ssid, "Tondo_Guest", WIFI_SSID_MAX - 1); strncpy(creds[cred_count].pass, "California", WIFI_PASS_MAX - 1); cred_count++; // Eightfold Coffee strncpy(creds[cred_count].ssid, "Eightfold Coffee", WIFI_SSID_MAX - 1); strncpy(creds[cred_count].pass, "wecloseat430", WIFI_PASS_MAX - 1); cred_count++; // Plot strncpy(creds[cred_count].ssid, "Plot", WIFI_SSID_MAX - 1); strncpy(creds[cred_count].pass, "blanketfort", WIFI_PASS_MAX - 1); cred_count++; // CultureHub LA staff network strncpy(creds[cred_count].ssid, "CULTUREHUB LA", WIFI_SSID_MAX - 1); strncpy(creds[cred_count].pass, "LaMama47!", WIFI_PASS_MAX - 1); cred_count++;
FILE *fp = fopen("/mnt/wifi_creds.json", "r"); if (fp) { char buf[2048] = ""; size_t n = fread(buf, 1, sizeof(buf) - 1, fp); buf[n] = 0; fclose(fp);
// Minimal JSON array parse: find each {"ssid":"...","pass":"..."} char *p = buf; while ((p = strstr(p, "\"ssid\"")) && cred_count < 16) { char *sq = strchr(p + 6, '"'); // opening quote of ssid value if (!sq) break; sq++; char *eq = strchr(sq, '"'); // closing quote if (!eq) break;
int len = (int)(eq - sq); if (len > 0 && len < WIFI_SSID_MAX) { strncpy(creds[cred_count].ssid, sq, len); creds[cred_count].ssid[len] = 0;
// Find corresponding "pass" value creds[cred_count].pass[0] = 0; char *pp = strstr(eq, "\"pass\""); if (pp) { char *pq = strchr(pp + 6, '"'); if (pq) { pq++; char *pe = strchr(pq, '"'); if (pe) { int plen = (int)(pe - pq); if (plen >= 0 && plen < WIFI_PASS_MAX) { strncpy(creds[cred_count].pass, pq, plen); creds[cred_count].pass[plen] = 0; } } } }
// Skip duplicates of AC preset if (strcmp(creds[cred_count].ssid, AC_SSID) != 0) cred_count++; } p = eq + 1; } wifi_log(wifi, "Auto-connect: %d saved creds", cred_count); } else { wifi_log(wifi, "Auto-connect: no saved creds, preset only"); }
// Step 2b: Preferred networks win over signal order. When one of these // is in range and we hold its credentials, try it first; only if it // fails do we fall through to strongest-known-first below. static const char *PREFERRED_SSIDS[] = { "CULTUREHUB LA" }; char tried_preferred[WIFI_SSID_MAX] = ""; for (size_t pi = 0; pi < sizeof(PREFERRED_SSIDS) / sizeof(PREFERRED_SSIDS[0]); pi++) { const char *want = PREFERRED_SSIDS[pi]; int cj = -1; for (int j = 0; j < cred_count; j++) if (strcmp(creds[j].ssid, want) == 0) { cj = j; break; } if (cj < 0) continue; int seen = 0, sig = 0; pthread_mutex_lock(&wifi->lock); for (int i = 0; i < wifi->network_count; i++) if (strcmp(wifi->networks[i].ssid, want) == 0) { seen = 1; sig = wifi->networks[i].signal; break; } pthread_mutex_unlock(&wifi->lock); if (!seen) continue; wifi_log(wifi, "Trying preferred '%s' (%d dBm)", want, sig); wifi_do_connect(wifi, creds[cj].ssid, creds[cj].pass); if (wifi->state == WIFI_STATE_CONNECTED) { wifi_log(wifi, "Auto-connect: success (preferred)!"); return; } wifi_log(wifi, "preferred '%s' failed, falling back to signal order", want); strncpy(tried_preferred, want, WIFI_SSID_MAX - 1); }
// Step 3: Match scanned networks against saved creds (by signal strength) // Networks are already sorted by signal (strongest first) from wifi_do_scan pthread_mutex_lock(&wifi->lock); for (int i = 0; i < wifi->network_count; i++) { if (tried_preferred[0] && strcmp(wifi->networks[i].ssid, tried_preferred) == 0) continue; for (int j = 0; j < cred_count; j++) { if (strcmp(wifi->networks[i].ssid, creds[j].ssid) == 0) { char ssid[WIFI_SSID_MAX], pass[WIFI_PASS_MAX]; strncpy(ssid, creds[j].ssid, WIFI_SSID_MAX - 1); ssid[WIFI_SSID_MAX - 1] = 0; strncpy(pass, creds[j].pass, WIFI_PASS_MAX - 1); pass[WIFI_PASS_MAX - 1] = 0; pthread_mutex_unlock(&wifi->lock);
wifi_log(wifi, "Trying '%s' (%d dBm)", ssid, wifi->networks[i].signal); wifi_do_connect(wifi, ssid, pass);
if (wifi->state == WIFI_STATE_CONNECTED) { wifi_log(wifi, "Auto-connect: success!"); return; } wifi_log(wifi, "'%s' failed, trying next...", ssid); pthread_mutex_lock(&wifi->lock); break; // Move to next scanned network } } } pthread_mutex_unlock(&wifi->lock);
wifi_log(wifi, "Auto-connect: no match"); wifi_set_state_and_status(wifi, WIFI_STATE_SCAN_DONE, "no saved network");}
// ============================================================// Worker thread// ============================================================
// Check if interface still has an IP (connectivity watchdog)// Also updates signal_strength while connected.static int wifi_check_link(ACWifi *wifi) { if (!wifi->iface[0]) return 0; char cmd[256]; snprintf(cmd, sizeof(cmd), "ip -4 addr show %s 2>/dev/null | grep -q 'inet '", wifi->iface); int has_ip = system(cmd) == 0;
// Update signal strength if (has_ip) { snprintf(cmd, sizeof(cmd), "iw dev %s link 2>/dev/null | grep signal | awk '{print $2}'", wifi->iface); FILE *fp = popen(cmd, "r"); if (fp) { char buf[32] = ""; if (fgets(buf, sizeof(buf), fp)) { int sig = atoi(buf); if (sig < 0) { pthread_mutex_lock(&wifi->lock); wifi->signal_strength = sig; pthread_mutex_unlock(&wifi->lock); } } pclose(fp); } }
return has_ip;}
static void *wifi_thread_fn(void *arg) { ACWifi *wifi = (ACWifi *)arg; int watchdog_counter = 0;
while (wifi->thread_running) { // Wait for a command (with 2s timeout for watchdog checks) pthread_mutex_lock(&wifi->lock); if (wifi->pending_cmd == WIFI_CMD_NONE) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 2; // 2-second watchdog interval pthread_cond_timedwait(&wifi->cond, &wifi->lock, &ts); }
WiFiCommand cmd = wifi->pending_cmd; wifi->pending_cmd = WIFI_CMD_NONE;
// Copy command args before releasing lock char ssid[WIFI_SSID_MAX] = ""; char pass[WIFI_PASS_MAX] = ""; if (cmd == WIFI_CMD_CONNECT) { strncpy(ssid, wifi->cmd_ssid, WIFI_SSID_MAX - 1); strncpy(pass, wifi->cmd_pass, WIFI_PASS_MAX - 1); } WiFiState cur_state = wifi->state; pthread_mutex_unlock(&wifi->lock);
// Execute command (blocking calls are fine — we're on the wifi thread) switch (cmd) { case WIFI_CMD_SCAN: wifi_do_scan(wifi); break; case WIFI_CMD_CONNECT: wifi_do_connect(wifi, ssid, pass); break; case WIFI_CMD_DISCONNECT: wifi_do_disconnect(wifi); break; case WIFI_CMD_AUTOCONNECT: wifi_do_autoconnect(wifi); break; default: break; }
// Watchdog: check connectivity every ~10s (5 iterations × 2s) if (cmd == WIFI_CMD_NONE && cur_state == WIFI_STATE_CONNECTED) { watchdog_counter++; if (watchdog_counter >= 5) { watchdog_counter = 0; if (!wifi_check_link(wifi)) { wifi_log(wifi, "Connection lost — reconnecting '%s'", wifi->last_ssid); wifi_set_state_and_status(wifi, WIFI_STATE_CONNECTING, "reconnecting..."); wifi_do_connect(wifi, wifi->last_ssid, wifi->last_pass); if (wifi->state != WIFI_STATE_CONNECTED) { wifi->reconnect_failures++; wifi_log(wifi, "Reconnect failed (%d)", wifi->reconnect_failures); // Back off: wait longer between retries if (wifi->reconnect_failures > 3) sleep(wifi->reconnect_failures * 2); } } } } else { watchdog_counter = 0; } }
return NULL;}
// ============================================================// Public API (called from main thread — all non-blocking)// ============================================================
ACWifi *wifi_init(void) { ACWifi *wifi = calloc(1, sizeof(ACWifi)); if (!wifi) return NULL;
wifi->state = WIFI_STATE_OFF; snprintf(wifi->status_msg, sizeof(wifi->status_msg), "initializing..."); wifi->iface[0] = 0; pthread_mutex_init(&wifi->lock, NULL); pthread_cond_init(&wifi->cond, NULL);
// Check if iw exists int has_iw = file_exists("/usr/sbin/iw") || file_exists("/sbin/iw") || file_exists("/usr/bin/iw") || file_exists("/bin/iw"); if (!has_iw) has_iw = (system("which iw >/dev/null 2>&1") == 0); if (!has_iw) { snprintf(wifi->status_msg, sizeof(wifi->status_msg), "iw not found"); ac_log("[wifi] iw binary not found"); return wifi; } ac_log("[wifi] iw binary found");
// Log diagnostic info { FILE *dbg; dbg = popen("ls /sys/class/net/ 2>/dev/null", "r"); if (dbg) { char buf[256] = ""; if (fgets(buf, sizeof(buf), dbg)) { buf[strcspn(buf, "\n")] = 0; ac_log("[wifi] net interfaces at start: %s", buf); } pclose(dbg); } dbg = popen("ls /lib/firmware/iwlwifi-*.ucode 2>/dev/null | head -3", "r"); if (dbg) { char buf[256] = ""; while (fgets(buf, sizeof(buf), dbg)) { buf[strcspn(buf, "\n")] = 0; ac_log("[wifi] firmware: %s", buf); } pclose(dbg); } // Read kernel log for wireless-related messages int kmsg_fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK); if (kmsg_fd >= 0) { lseek(kmsg_fd, 0, SEEK_SET); char kbuf[512]; int kmsg_count = 0, matched = 0; ssize_t rr; while ((rr = read(kmsg_fd, kbuf, sizeof(kbuf) - 1)) > 0 && kmsg_count < 2000) { kbuf[rr] = 0; if (strcasestr(kbuf, "iwl") || strcasestr(kbuf, "wifi") || strcasestr(kbuf, "wlan") || strcasestr(kbuf, "80211") || strcasestr(kbuf, "firmware") || (strcasestr(kbuf, "pci") && kmsg_count < 100)) { char *msg = strchr(kbuf, ';'); if (msg) msg = strchr(msg + 1, ';'); if (msg) msg = strchr(msg + 1, ';'); if (msg) msg++; else msg = kbuf; msg[strcspn(msg, "\n")] = 0; ac_log("[wifi] kmsg: %s", msg); matched++; } kmsg_count++; } ac_log("[wifi] kmsg: %d total messages, %d matched", kmsg_count, matched); close(kmsg_fd); } // PCI device enumeration dbg = popen("for d in /sys/bus/pci/devices/*; do echo \"$(basename $d) $(cat $d/vendor 2>/dev/null):$(cat $d/device 2>/dev/null)\"; done 2>/dev/null", "r"); if (dbg) { char buf[256] = ""; while (fgets(buf, sizeof(buf), dbg)) { buf[strcspn(buf, "\n")] = 0; ac_log("[wifi] PCI id: %s", buf); } pclose(dbg); } }
// Wait for wireless interface (up to 3 seconds) int found = 0; for (int attempt = 0; attempt < 30; attempt++) { if (detect_iface(wifi->iface, sizeof(wifi->iface))) { found = 1; break; } ac_log("[wifi] Waiting for wireless interface (attempt %d/30)...", attempt + 1); usleep(100000); }
if (!found) { ac_log("[wifi] No wireless interface detected"); FILE *fp = popen("ls /sys/class/net/ 2>/dev/null", "r"); if (fp) { char buf[256] = ""; if (fgets(buf, sizeof(buf), fp)) { buf[strcspn(buf, "\n")] = 0; ac_log("[wifi] interfaces: %s", buf); } pclose(fp); } snprintf(wifi->status_msg, sizeof(wifi->status_msg), "no wifi hw"); return wifi; } ac_log("[wifi] Detected interface: %s", wifi->iface);
// Unblock rfkill if needed { char cmd[256]; snprintf(cmd, sizeof(cmd), "cat /sys/class/net/%s/phy80211/rfkill*/soft 2>/dev/null | head -1", wifi->iface); FILE *fp = popen(cmd, "r"); if (fp) { char buf[8] = ""; if (fgets(buf, sizeof(buf), fp) && buf[0] == '1') { ac_log("[wifi] Soft-blocked, unblocking..."); run_cmd("rfkill unblock wifi 2>/dev/null"); usleep(200000); } pclose(fp); } }
// Bring up the interface { char cmd[128]; snprintf(cmd, sizeof(cmd), "ip link set %s up 2>/dev/null", wifi->iface); run_cmd(cmd);
snprintf(cmd, sizeof(cmd), "ip link show %s 2>/dev/null", wifi->iface); FILE *fp = popen(cmd, "r"); if (fp) { char buf[256] = ""; if (fgets(buf, sizeof(buf), fp)) { buf[strcspn(buf, "\n")] = 0; ac_log("[wifi] link status: %s", buf); } pclose(fp); } }
snprintf(wifi->status_msg, sizeof(wifi->status_msg), "ready"); ac_log("[wifi] Interface %s is up", wifi->iface);
// Start worker thread wifi->thread_running = 1; if (pthread_create(&wifi->thread, NULL, wifi_thread_fn, wifi) != 0) { ac_log("[wifi] Failed to create wifi thread"); wifi->thread_running = 0; } else { ac_log("[wifi] Worker thread started"); }
return wifi;}
void wifi_scan(ACWifi *wifi) { if (!wifi || !wifi->iface[0] || !wifi->thread_running) return;
pthread_mutex_lock(&wifi->lock); // Don't interrupt scanning, connecting, or DHCP in progress if (wifi->state == WIFI_STATE_SCANNING || wifi->state == WIFI_STATE_CONNECTING || wifi->state == WIFI_STATE_CONNECTED) { pthread_mutex_unlock(&wifi->lock); return; } wifi->pending_cmd = WIFI_CMD_SCAN; pthread_cond_signal(&wifi->cond); pthread_mutex_unlock(&wifi->lock);}
void wifi_connect(ACWifi *wifi, const char *ssid, const char *password) { if (!wifi || !ssid || !wifi->iface[0] || !wifi->thread_running) return;
pthread_mutex_lock(&wifi->lock); strncpy(wifi->cmd_ssid, ssid, WIFI_SSID_MAX - 1); wifi->cmd_ssid[WIFI_SSID_MAX - 1] = 0; if (password) { strncpy(wifi->cmd_pass, password, WIFI_PASS_MAX - 1); wifi->cmd_pass[WIFI_PASS_MAX - 1] = 0; } else { wifi->cmd_pass[0] = 0; } wifi->pending_cmd = WIFI_CMD_CONNECT; pthread_cond_signal(&wifi->cond); pthread_mutex_unlock(&wifi->lock);}
void wifi_disconnect(ACWifi *wifi) { if (!wifi || !wifi->thread_running) return;
pthread_mutex_lock(&wifi->lock); wifi->pending_cmd = WIFI_CMD_DISCONNECT; pthread_cond_signal(&wifi->cond); pthread_mutex_unlock(&wifi->lock);}
// No-ops — polling now happens inside the wifi thread.// Main thread just reads wifi->state / wifi->networks directly.int wifi_scan_poll(ACWifi *wifi) { (void)wifi; return 0;}
int wifi_connect_poll(ACWifi *wifi) { (void)wifi; return 0;}
void wifi_autoconnect(ACWifi *wifi) { if (!wifi || !wifi->iface[0] || !wifi->thread_running) return;
pthread_mutex_lock(&wifi->lock); wifi->pending_cmd = WIFI_CMD_AUTOCONNECT; pthread_cond_signal(&wifi->cond); pthread_mutex_unlock(&wifi->lock);}
void wifi_destroy(ACWifi *wifi) { if (!wifi) return;
// Signal thread to stop wifi->thread_running = 0; pthread_mutex_lock(&wifi->lock); pthread_cond_signal(&wifi->cond); pthread_mutex_unlock(&wifi->lock);
// Wait for thread to finish if (wifi->thread) pthread_join(wifi->thread, NULL);
wifi_do_disconnect(wifi);
pthread_mutex_destroy(&wifi->lock); pthread_cond_destroy(&wifi->cond); free(wifi);}