fix(wifi): Zugriffsschutz und AP-Daten beim Verlassen des Standalone-Modus
C1 (Merge-Blocker): Schaltet jemand den Standalone-Modus ab, waehrend keine WLAN-Daten gespeichert sind, geht die Zustandsmaschine in den Onboarding-AP - nl_wifi_sm_open_access() liefert dann true, obwohl der AP noch mit Name und Passwort des Nutzers steht. Wer das AP-Passwort kennt, koennte system/settings und system/command ohne Token benutzen: Token setzen, OTA anstossen, Werksreset. Der Netz-Task reisst den AP in diesem Fall jetzt ausdruecklich ab und baut ihn mit den Vorgabedaten neu auf - vor execute(ev) und nicht ueber die Aktionsstruktur, weil execute() start_ap vor stop_ap ausfuehrt. Damit stimmt auch die README-Zusage wieder, der AP heisse danach "ninlight-<mac4>". I1: Der zweite Rueckfall nach drei abgelehnten tls_wifi_softap_create() setzte ueber reset_ap_defaults() auch das Passwort auf das einkompilierte, das in der README oeffentlich dokumentiert ist - eine Standalone-Lampe stuende dann mit einem allgemein bekannten Passwort im Funkfeld, und sACN ist unauthentifiziert. reset_ap_defaults() ist dafuer in reset_ap_identity() (Name und Kanal) und die vollstaendige Rueckstellung aufgeteilt; der Rueckfall nimmt nur noch die Identitaet. Kanal und SSID sind ohnehin die plausiblen Gruende, die Passwortlaenge prueft die API selbst. I2: destroy_ap() liess s_ap_live_ssid/-pass/-channel stehen. Nach dem Verlassen des Standalone-Modus trugen diese Puffer weiter die Daten des Nutzers, und der erste Rueckfallzweig holte sie Minuten spaeter zurueck - die Lampe pendelte dann im 15-s-Takt zwischen Nutzerdaten und Vorgaben. Neu: forget_ap_live() in beiden Abschaltwegen (clear_standalone und enabled:false), nicht in destroy_ap() selbst, weil der Rueckfall auf zuletzt tragende Daten sonst auch im Neuaufbau-Fall verlorenginge, fuer den er gedacht ist. I3: s_ap_create_fails wurde nur bei Erfolg genullt und summierte damit ueber verschiedene Datensaetze - nach einer Korrektur von Kanal 13 auf 6 loeste schon ein einziger Fehlschlag den Rueckfall aus. Jetzt auch in destroy_ap() und bei jedem neuen Wunsch aus MSG_STANDALONE. Kleinigkeiten: nl_app_clear_standalone() wird auf seinen Rueckgabewert geprueft und ein Schreibfehler protokolliert (sonst steht die Lampe im WLAN und nach dem Neustart wieder im eigenen AP, ohne jede Spur). enabled:false auf einer Lampe, die gar nicht standalone war, erzwingt keine Neuverbindung mehr, sondern tut nichts und antwortet weiter mit 202. Abnahmeliste: der Gerätebeleg für C1 als offener Punkt.
This commit is contained in:
+78
-16
@@ -115,16 +115,35 @@ void nl_wifi_disable_sdk_auto_connect(void)
|
||||
tls_wifi_disconnect();
|
||||
}
|
||||
|
||||
/* Setzt Name, Passwort und Kanal des naechsten AP auf die Vorgaben zurueck: "ninlight-<mac4>"
|
||||
* mit dem einkompilierten Passwort auf Kanal 6. Beruehrt einen LAUFENDEN AP nicht - der
|
||||
* behaelt seine Daten, bis er abgebaut und neu aufgebaut wird. */
|
||||
static void reset_ap_defaults(void)
|
||||
/* Setzt NUR Name und Kanal des naechsten AP auf die Vorgaben zurueck: "ninlight-<mac4>" auf
|
||||
* Kanal 6. Das Passwort bleibt stehen - I1: ein Rueckfall darf das Passwort des Nutzers nicht
|
||||
* durch das einkompilierte ersetzen, das in der README oeffentlich dokumentiert ist. Beruehrt
|
||||
* einen LAUFENDEN AP nicht - der behaelt seine Daten, bis er abgebaut und neu aufgebaut wird. */
|
||||
static void reset_ap_identity(void)
|
||||
{
|
||||
s_ap_ssid[0] = '\0';
|
||||
copy(s_ap_pass, NINLIGHT_AP_PASSWORD, sizeof s_ap_pass);
|
||||
s_ap_channel = 6;
|
||||
}
|
||||
|
||||
/* I2: Vergisst, mit welchen Daten der zuletzt stehende AP aufgebaut wurde. Verlaesst die Lampe
|
||||
* den Standalone-Modus, duerfen Name und Passwort des Nutzers nicht als "hat schon einmal
|
||||
* getragen" liegenbleiben: der erste Rueckfallzweig in start_ap() holte sie sonst Minuten
|
||||
* spaeter zurueck (etwa am Rueckfall-AP nach fuenf Minuten ohne Router), und die Lampe boete
|
||||
* periodisch einen AP an, den der Nutzer laengst abgeschaltet hat. */
|
||||
static void forget_ap_live(void)
|
||||
{
|
||||
s_ap_live_ssid[0] = s_ap_live_pass[0] = '\0';
|
||||
s_ap_live_channel = 0;
|
||||
}
|
||||
|
||||
/* Vollstaendige Rueckstellung auf die Vorgaben, einschliesslich des einkompilierten Passworts.
|
||||
* Nur fuer den Weg AUS dem Standalone-Modus heraus, wo der Wunsch des Nutzers endet. */
|
||||
static void reset_ap_defaults(void)
|
||||
{
|
||||
reset_ap_identity();
|
||||
copy(s_ap_pass, NINLIGHT_AP_PASSWORD, sizeof s_ap_pass);
|
||||
}
|
||||
|
||||
/* Reisst den AP ab und raeumt dabei auch die Buchfuehrung auf. Laeuft nur im Netz-Task
|
||||
* (oder vor dessen Start) und nie unter s_lock - clear_softap_param() fasst SDK-Zustand an. */
|
||||
static void destroy_ap(void)
|
||||
@@ -133,6 +152,10 @@ static void destroy_ap(void)
|
||||
s_ap_create_pending = false;
|
||||
s_ap_live = false;
|
||||
s_ap_event_seen = s_ap_mdns_done = false;
|
||||
/* I3: Der Zaehler gehoert zu EINEM Datensatz. Ohne diese Nullung addieren sich die
|
||||
* Fehlschlaege mit den alten Daten zu denen mit den neuen, und nach einer Korrektur
|
||||
* (Kanal 13 -> Kanal 6) loeste schon ein einziger Fehlschlag den Rueckfall aus. */
|
||||
s_ap_create_fails = 0;
|
||||
clear_softap_param(); /* C2: every softap teardown clears the persisted SOFTAP bit */
|
||||
}
|
||||
|
||||
@@ -200,9 +223,14 @@ static void start_ap(void)
|
||||
copy(s_ap_ssid, s_ap_live_ssid, sizeof s_ap_ssid);
|
||||
copy(s_ap_pass, s_ap_live_pass, sizeof s_ap_pass);
|
||||
s_ap_channel = s_ap_live_channel;
|
||||
} else if (s_ap_ssid[0] || s_ap_channel != 6 || strcmp(s_ap_pass, NINLIGHT_AP_PASSWORD) != 0) {
|
||||
} else if (s_ap_ssid[0] || s_ap_channel != 6) {
|
||||
/* I1: nur Name und Kanal. Das Passwort ist der unwahrscheinlichste Grund fuer ein
|
||||
* abgelehntes tls_wifi_softap_create() - die Laenge prueft die API selbst, Kanal und
|
||||
* SSID sind die plausiblen Kandidaten. Ein Rueckfall auf NINLIGHT_AP_PASSWORD stellte
|
||||
* eine Standalone-Lampe mit einem oeffentlich dokumentierten Passwort ins Funkfeld,
|
||||
* und sACN hoert dort unauthentifiziert mit. */
|
||||
printf("wifi: AP create keeps failing, falling back to %s on ch 6\r\n", s_hostname);
|
||||
reset_ap_defaults();
|
||||
reset_ap_identity();
|
||||
}
|
||||
/* Sonst scheitern schon die Vorgaben - dann bleibt nur, es weiter zu versuchen. */
|
||||
}
|
||||
@@ -248,11 +276,17 @@ static void execute(nl_wifi_actions_t a)
|
||||
printf("wifi: standalone mode cleared\r\n");
|
||||
/* Dieselbe Rueckstellung wie beim Zurueckschalten ueber die API: sonst kaeme ein
|
||||
* spaeterer Rueckfall-AP (5 min ohne Station) mit Namen und Passwort des Nutzers
|
||||
* hoch, obwohl standalone in den Einstellungen schon 0 ist. Gefahrlos, weil dieses
|
||||
* Aktionsbuendel kein start_ap enthaelt - der noch laufende Standalone-AP bleibt
|
||||
* unberuehrt, bis die Zustandsmaschine ihn abbaut. */
|
||||
* hoch, obwohl standalone in den Einstellungen schon 0 ist. forget_ap_live() raeumt
|
||||
* dazu die Buchfuehrung ueber den zuletzt tragenden AP weg (I2) - sonst holte ihn
|
||||
* der erste Rueckfallzweig in start_ap() nach drei abgelehnten Aufbauten zurueck.
|
||||
* Der noch laufende Standalone-AP bleibt stehen, bis die Zustandsmaschine ihn
|
||||
* abbaut: dieses Aktionsbuendel enthaelt kein start_ap. */
|
||||
reset_ap_defaults();
|
||||
nl_app_clear_standalone();
|
||||
forget_ap_live();
|
||||
/* Schlaegt das Speichern fehl, steht die Lampe jetzt im WLAN, nach dem naechsten
|
||||
* Neustart aber wieder in ihrem eigenen AP - ohne jede Spur. Wenigstens melden. */
|
||||
if (nl_app_clear_standalone() != 0)
|
||||
printf("wifi: could not persist the cleared standalone flag\r\n");
|
||||
}
|
||||
if (a.pulse_on)
|
||||
pulse(true);
|
||||
@@ -289,8 +323,8 @@ static void net_task(void *arg)
|
||||
nl_wifi_actions_t ev, tick;
|
||||
uint32_t m = 0, now;
|
||||
bool got_ip_event = false, connected_now, ap_wanted, unexpected_softap,
|
||||
standalone_now;
|
||||
bool recheck_ap = false;
|
||||
standalone_now, open_now;
|
||||
bool recheck_ap = false, rebuild_ap = false, ignored_off = false;
|
||||
|
||||
memset(&ev, 0, sizeof ev);
|
||||
if (tls_os_queue_receive(s_queue, &msg, 0, HZ / 10) == TLS_OS_SUCCESS)
|
||||
@@ -319,15 +353,25 @@ static void net_task(void *arg)
|
||||
/* Steht der AP schon, liefert die Zustandsmaschine kein start_ap. Ob die
|
||||
* neuen Daten einen Neuaufbau verlangen, entscheidet start_ap() selbst. */
|
||||
recheck_ap = true;
|
||||
} else if (!s_sm.standalone) {
|
||||
/* War die Lampe gar nicht standalone, gibt es nichts abzuschalten. Ohne diese
|
||||
* Vorpruefung riefe leave_standalone() auf einer gewoehnlichen Stationslampe
|
||||
* begin_connecting() und erzwaenge eine Neuverbindung ohne Anlass. Die Antwort
|
||||
* bleibt 202: der gewuenschte Zustand gilt ja bereits. (standalone bleibt auch
|
||||
* waehrend eines Onboarding-Tests gesetzt, der Test faellt also nicht hierher.) */
|
||||
ignored_off = true;
|
||||
} else {
|
||||
/* Zurueck auf die Vorgaben: ein spaeterer Rueckfall- oder Onboarding-AP ist
|
||||
* wieder der gewohnte "ninlight-<mac4>" mit dem einkompilierten Passwort,
|
||||
* genau wie bei einer Lampe, die nie standalone war. Das greift erst beim
|
||||
* NAECHSTEN Aufbau - der noch laufende AP behaelt Name und Passwort, bis ihn
|
||||
* die Zustandsmaschine abbaut. */
|
||||
* genau wie bei einer Lampe, die nie standalone war. */
|
||||
reset_ap_defaults();
|
||||
forget_ap_live();
|
||||
ev = nl_wifi_sm_leave_standalone(&s_sm, s_ssid[0] != '\0', now);
|
||||
rebuild_ap = true;
|
||||
}
|
||||
/* I3: Ein neuer Wunsch ist ein neuer Datensatz - die Fehlschlaege des alten
|
||||
* duerfen nicht mitzaehlen. */
|
||||
s_ap_create_fails = 0;
|
||||
s_req_pending = false;
|
||||
}
|
||||
tick = nl_wifi_sm_tick(&s_sm, now);
|
||||
@@ -342,6 +386,7 @@ static void net_task(void *arg)
|
||||
unexpected_softap = m == (MSG_NETIF | NETIF_WIFI_SOFTAP_SUCCESS) && !ap_wanted;
|
||||
/* Nur die Lage merken; der Aufruf nach aussen folgt hinter der Entsperrung. */
|
||||
standalone_now = s_sm.state == NL_WIFI_ST_STANDALONE;
|
||||
open_now = nl_wifi_sm_open_access(&s_sm);
|
||||
unlock();
|
||||
|
||||
if (unexpected_softap) {
|
||||
@@ -351,6 +396,23 @@ static void net_task(void *arg)
|
||||
|
||||
if (m & MSG_NETIF)
|
||||
printf("wifi: netif event %u\r\n", (unsigned)(m & 0xFFu));
|
||||
if (ignored_off)
|
||||
printf("wifi: standalone was already off, nothing to do\r\n");
|
||||
/* C1: Beim Verlassen des Standalone-Modus OHNE gespeicherte WLAN-Daten geht die
|
||||
* Zustandsmaschine in den Onboarding-AP - dort liefert open_access() true, waehrend
|
||||
* der AP noch unveraendert mit Name und Passwort des Nutzers steht. Wer das
|
||||
* AP-Passwort kennt, koennte dann ohne Token system/settings und system/command
|
||||
* benutzen. Also hier hart abreissen und mit den Vorgabedaten neu aufbauen; damit
|
||||
* stimmt auch die README-Zusage wieder, der AP heisse danach "ninlight-<mac4>".
|
||||
* Nicht ueber die Aktionsstruktur: execute() fuehrt start_ap VOR stop_ap aus, ein
|
||||
* Buendel mit beiden Flags baute erst auf und risse dann ab. Und VOR execute(ev),
|
||||
* damit dessen start_ap in die Gleichheitspruefung von start_ap() faellt, statt den
|
||||
* eben gebauten AP ein zweites Mal abzureissen. */
|
||||
if (rebuild_ap && ap_wanted && open_now) {
|
||||
printf("wifi: left standalone without credentials, rebuilding the AP with the defaults\r\n");
|
||||
destroy_ap();
|
||||
start_ap();
|
||||
}
|
||||
execute(ev);
|
||||
/* Nach einem Wechsel in den Standalone-Modus: start_ap() baut den AP neu auf, wenn
|
||||
* Name, Passwort oder Kanal sich geaendert haben, und tut sonst nichts. */
|
||||
|
||||
Reference in New Issue
Block a user