diff --git a/Makefile b/Makefile index 90fbcebf..882a1c16 100644 --- a/Makefile +++ b/Makefile @@ -105,6 +105,7 @@ SRCS = \ sfp.c \ dns.c \ dns_cli.c \ + sessions.c \ ntp.c \ ntp_cli.c \ syslog.c \ diff --git a/httpd/httpd.c b/httpd/httpd.c index 93828d34..b383d8de 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -6,12 +6,12 @@ #include "cmd_parser.h" #include "rtl837x_flash.h" #include "uip.h" +#include "sessions.h" #include "html_data.h" // #define DEBUG #include "debug.h" -#define SESSION_ID_LENGTH 12 #define SESSION_TIMEOUT_DEFAULT 200 /* Seconds of inactivity after which a session cookie stops validating. @@ -75,13 +75,7 @@ __xdata uint8_t postbody_endpoint; __xdata uint16_t postbody_start; __xdata char passwd[21]; -/* Up to SESSIONS logins at once, each with its own cookie and its own - * expiry. With a single one, logging in from a second browser (or a phone) - * ended the first browser's session at its next request. */ -#define SESSIONS 4 -__xdata char session_id[SESSIONS][SESSION_ID_LENGTH + 1]; -__xdata uint32_t last_session_use[SESSIONS]; -__xdata uint8_t session_slot; +// Up to four logins at once, see sessions.c __xdata uint8_t authenticated; __xdata uint32_t now; @@ -114,33 +108,7 @@ void httpd_init(void) __banked // handling one, and nothing has set it yet at init time. for (uint8_t i = 0; i < UIP_CONNS; i++) uip_conns[i].appstate.tstate = TSTATE_CLOSED; - for (session_slot = 0; session_slot < SESSIONS; session_slot++) - session_id[session_slot][0] = NUL; -} - -/* Leaf functions, so the compiler keeps their 32-bit scratch in the overlay - * rather than in internal RAM. */ -static uint8_t session_stale(void) -{ - if (now - last_session_use[session_slot] > session_timeout) - return 1; - return 0; -} - -/* For a new login: a free or expired slot, else the least recently used. */ -static void session_pick(void) -{ - __xdata uint8_t i; - __xdata uint8_t oldest = 0; - - for (i = 0; i < SESSIONS; i++) { - session_slot = i; - if (!session_id[i][0] || session_stale()) - return; - if (last_session_use[i] < last_session_use[oldest]) - oldest = i; - } - session_slot = oldest; + session_init(); } @@ -375,40 +343,11 @@ __xdata uint8_t *scan_header(__xdata uint8_t * __xdata p) read_reg_timer(&now); - if (session) { - for (session_slot = 0; session_slot < SESSIONS; session_slot++) { - if (!session_id[session_slot][0]) - continue; - if (session_stale()) { - dbg_string("Session expired\n"); - session_id[session_slot][0] = NUL; - } else if (is_word_x(session, session_id[session_slot])) { - authenticated = 1; - last_session_use[session_slot] = now; - break; - } - } - } + if (session) + session_check(); return p; } -/* - * Generate random HEX-chars at the buffer location. - */ -void gen_random_hex_chars(__xdata uint8_t * b, __xdata uint8_t bytes) -{ - uint8_t i = 0; - while (bytes) { - if (!i) - get_random_32(); - b[--bytes] = itohex(sfr_data[i]); - if (!bytes) { break; } - b[--bytes] = itohex(sfr_data[i] >> 4 | sfr_data[i] << 4); - i = (i + 1) & 0x3; - } -} - - /* 0: body incomplete, 1: configuration stored, 2: malformed */ static uint8_t config_take(void) { @@ -710,14 +649,9 @@ static void run_login_body(__xdata uint8_t *body) if (strstart(body, "pwd=") && is_url_word_x(body + 4, passwd)) { dbg_string("Password accepted!\n"); read_reg_timer(&now); - session_pick(); - last_session_use[session_slot] = now; - gen_random_hex_chars(session_id[session_slot], SESSION_ID_LENGTH); - session_id[session_slot][SESSION_ID_LENGTH] = NUL; slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: index.html\r\n" \ "Set-Cookie: session="); - for (uint8_t i = 0; i < SESSION_ID_LENGTH; i++) - outbuf[slen++] = session_id[session_slot][i]; + session_new(); slen += strtox(outbuf + slen, "; SameSite=Strict\r\n\r\n"); } else { dbg_string("Password invalid!\n"); diff --git a/sessions.c b/sessions.c new file mode 100644 index 00000000..b8954cc8 --- /dev/null +++ b/sessions.c @@ -0,0 +1,109 @@ +/* + * Login sessions of the web interface. + * + * Up to SESSIONS logins at once, each with its own cookie and its own expiry. + * With a single one, logging in from a second browser (or a phone) ended the + * first browser's session at its next request. + * + * This sits in bank 3: the web server's own bank is full, so httpd.c only + * calls in here. It shares three things with httpd.c: the cookie value it + * parsed (session), the time read for the request (now) and the result + * (authenticated); the reply is written to outbuf at slen. + */ + +#pragma codeseg BANK3 +#pragma constseg BANK3 + +#include +#include "rtl837x_common.h" +#include "sessions.h" + +extern volatile __xdata uint8_t sfr_data[4]; +extern __xdata uint16_t session_timeout; +extern __xdata uint32_t now; +extern __xdata uint8_t authenticated; +extern __xdata uint8_t * __xdata session; +extern __xdata uint8_t outbuf[]; +extern __xdata uint16_t slen; + +__xdata char session_id[SESSIONS][SESSION_ID_LENGTH + 1]; +__xdata uint32_t last_session_use[SESSIONS]; + +static __xdata uint8_t ss_slot, ss_i, ss_oldest, ss_c; + +static __code const char ss_hex[] = "0123456789abcdef"; +/* What may follow a cookie value: ';' separates cookies in a Cookie header */ +static __code const char ss_end[] = " \t:?=\n\r;"; + +/* Leaf function, so the compiler keeps its 32-bit scratch in the overlay + * rather than in internal RAM. */ +static uint8_t ss_stale(void) +{ + if (now - last_session_use[ss_slot] > session_timeout) + return 1; + return 0; +} + +/* Whether the cookie value is the id of slot ss_slot */ +static uint8_t ss_match(void) +{ + for (ss_i = 0; ss_i < SESSION_ID_LENGTH; ss_i++) { + if (session[ss_i] != session_id[ss_slot][ss_i]) + return 0; + } + ss_c = session[SESSION_ID_LENGTH]; + if (!ss_c) + return 1; + for (ss_i = 0; ss_end[ss_i]; ss_i++) { + if (ss_c == ss_end[ss_i]) + return 1; + } + return 0; +} + +void session_init(void) __banked +{ + for (ss_slot = 0; ss_slot < SESSIONS; ss_slot++) + session_id[ss_slot][0] = NUL; +} + +void session_check(void) __banked +{ + for (ss_slot = 0; ss_slot < SESSIONS; ss_slot++) { + if (!session_id[ss_slot][0]) + continue; + if (ss_stale()) { + session_id[ss_slot][0] = NUL; + } else if (ss_match()) { + authenticated = 1; + last_session_use[ss_slot] = now; + return; + } + } +} + +void session_new(void) __banked +{ + /* A free or expired slot, else the least recently used */ + ss_oldest = 0; + for (ss_slot = 0; ss_slot < SESSIONS; ss_slot++) { + if (!session_id[ss_slot][0] || ss_stale()) + break; + if (last_session_use[ss_slot] < last_session_use[ss_oldest]) + ss_oldest = ss_slot; + } + if (ss_slot == SESSIONS) + ss_slot = ss_oldest; + + last_session_use[ss_slot] = now; + for (ss_i = 0; ss_i < SESSION_ID_LENGTH; ss_i++) { + if (!(ss_i & 7)) + get_random_32(); + ss_c = sfr_data[(ss_i >> 1) & 3]; + if (ss_i & 1) + ss_c >>= 4; + session_id[ss_slot][ss_i] = ss_hex[ss_c & 0xf]; + outbuf[slen++] = ss_hex[ss_c & 0xf]; + } + session_id[ss_slot][SESSION_ID_LENGTH] = NUL; +} diff --git a/sessions.h b/sessions.h new file mode 100644 index 00000000..66577a4c --- /dev/null +++ b/sessions.h @@ -0,0 +1,19 @@ +#ifndef _SESSIONS_H_ +#define _SESSIONS_H_ + +#include + +#define SESSIONS 4 +#define SESSION_ID_LENGTH 12 + +extern __xdata char session_id[SESSIONS][SESSION_ID_LENGTH + 1]; +extern __xdata uint32_t last_session_use[SESSIONS]; + +/* No session at boot */ +void session_init(void) __banked; +/* Sets authenticated if the cookie in session belongs to a live session */ +void session_check(void) __banked; +/* Starts a session at now and writes its id to outbuf at slen */ +void session_new(void) __banked; + +#endif diff --git a/test/Makefile b/test/Makefile index 6b145fb1..388d06bd 100644 --- a/test/Makefile +++ b/test/Makefile @@ -72,6 +72,8 @@ $(BUILDDIR)/test_lacp: test_lacp.c $(SRCROOT)/rtl837x_lacp.c | $(BUILDDIR) # this test carries the environment below them itself. $(BUILDDIR)/test_httpd_tx: test_httpd_tx.c $(SRCROOT)/httpd/httpd.c $(SRCROOT)/uip/uip.c | $(BUILDDIR) $(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS) +# the login sessions httpd.c calls into +$(BUILDDIR)/test_httpd_tx: $(SRCROOT)/sessions.c # CPU VLAN initialization with full and reduced user-port ranges. $(BUILDDIR)/test_cpu_vlan_restricted: CFLAGS += -DMACHINE_MAX_PORT=7