code wiki / _hdl_build / nx_opaque_login_daemon.nx

nx_opaque_login_daemon.nx source

↩ module page · 567 lines · 47144 B

1// nx_opaque_login_daemon.nx -- R2: thin HTTP daemon wrapping the gated no-cookie OPAQUE login (nx_opaque_login). 2// Serves the wiki's auth model for any site: full OPAQUE (modauth, RFC 9807) + no-cookie X-Nishi-Session token. 3// argv: [1]=port [2]=server-keys-path [3]=account-store-path [4]=request-budget [5]=argon2id_m_cost (opt; default 4// 65536=64MiB production; smoke passes a small value). Plain HTTP (loopback/LAN); TLS termination = the R3 rung 5// (route on the wiki HTTPS daemon). Routes: GET /login (page) | POST /login | GET /whoami | POST /register. 6// No shortcuts: production KSF by default, every reject path returns a clean status, no token leak on failure. 7import "nx_opaque_login.nx" // olg_ctx_setup / olg_register / olg_login / olg_whoami + NxAuthContext + NX_MAUTH_* 8import "nx_http_form.nx" // nx_http_form_get_field 9import "nx_audio_web.nx" // nx_audio_web_page -- the PROTECTED sovereign-audio page (served only on a valid session) 10import "nx_framed_append.nx" // fa_appendz -- the atomic op-RECEIPT ledger (military acceptance/receipt for the workstream) 11import "nx_lan_signup.nx" // ls_signup_allowed / ls_ip4_from_sockaddr -- THE SIGNUP GATE (LAN AND invited) 12 13const OLGD_REALM_N: i64 = 16 // "nishi_site_admin" -- the same realm this daemon inits its auth context with 14 15// ---- PER-REQUEST BUFFER DISCIPLINE (2026-08-18, lane F box health) ------------------------------------------- 16// MEASURED on the live daemon (pid 29279): VmSize 177,867,972 kB == VmData (all ANONYMOUS), VmPTE 347,408 kB, 17// against RSS+Swap 2.4 GB -- and growing in EXACT 2 MiB steps every ~15 s (nx_leak_check series: 8,383 kB/min). 18// The step was olgd_send's per-RESPONSE sys_mmap(2 MiB) compose buffer: one fresh, never-unmapped mapping per 19// response, of which only the header+body pages were ever touched -- plus req/resp/pg pages, ~26 KB touched per 20// request; ~84,000 requests over ~15 days = the 177 GB, and 347 MB of unswappable PAGE TABLES for address space 21// nothing used. A BUFFER ALLOCATED PER RESPONSE IN A DAEMON IS A LEAK WHOSE SIZE IS THE BUFFER, NOT THE RESPONSE. 22// Every per-request buffer is now allocated ONCE, above the accept loop, and WIPED at the end of each request, so 23// passphrases (pbuf, the POST body in req), session tokens (b64/rnb/tb/tbw, response JSON) and mnemonics 24// (rmn/nm/mn) no longer persist in leaked pages -- the live daemon had 2 GB of exactly those pages SWAPPED TO 25// DISK. Wiping restores the mmap-returns-zeroed contract for the next request BY CONSTRUCTION, so no branch code 26// had to change. Response-body wipes live in olgd_send; the /wiki compose buffer is wiped to its used length. 27// sys_arena_reset (nx_syscalls) was deliberately NOT adopted here: nx_seg_store.ss_hget lazily allocates its 28// static scratch (ssh_vo/ssh_vl, 16 B = arena-class) on FIRST CALL -- i.e. inside a request -- and a reset would 29// zero-and-recycle a live static: silent aliasing inside an auth daemon. Hoisting needs no such contract. 30static olgd_sendbuf: *u8 // olgd_send compose buffer (OLGD_MAGIC_2097152, mapped once, wiped per send) 31static olgd_pgbuf: *u8 // login page compose buffer (OLGD_MAGIC_16384, mapped once; content is constant) 32static olgd_recbuf: *u8 // op-receipt line (OLGD_RECBUF, mapped once, wiped per receipt) 33static olgd_scr: *u8 // small scratch page: [0..27] olgd_itoa digits (own page, so arena-immune) 34const OLGD_SCR_BYTES: i64 = 4096 35const OLGD_RECBUF: i64 = 512 36func olgd_wipe(p: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { p[i] = 0 as u8; i = i + 1 } return 0 } 37 38// THE SIGNUP DECISION, ADOPTED (2026-07-30). This daemon used to gate POST /register on ONE global boolean, 39// which is precisely the "old single-flag world" that nx_account_admin_exceed_gate scores as the NAIVE 40// BASELINE our own ls_signup_allowed beats -- and the flag was live-MEASURED as '1', so anyone on the 41// internet could mint an account on the family domain. Now BOTH must hold: the ops kill-switch AND the 42// by-construction decision (peer IP is LAN **and** the handle is INVITED in this realm's HR roster). 43// FAIL-CLOSED ON EVERY UNKNOWN: no peer address, or no HR roster configured, means DENY -- an unknown 44// origin must never be able to register, and a daemon started without a roster must not fall open. 45func olgd_signup_ok(ip4: *u8, hrstore: *u8, handle: *u8, hn: i64) -> i64 { 46 if olg_registration_open() == 0 { return 0 } 47 if (ip4 as i64) == 0 { return 0 } 48 if (hrstore as i64) == 0 { return 0 } 49 if hrstore[0] == (0 as u8) { return 0 } 50 return ls_signup_allowed(ip4, hrstore, "nishi_site_admin" as *u8, OLGD_REALM_N, handle, hn) 51} 52 53// Emit a structured op RECEIPT (the "acceptance" teammates/workstreams read to validate orchestration). 54// Schema: ts<TAB>actor<TAB>op<TAB>target<TAB>verdict<TAB>detail. NO secrets ever. Atomic. detail carries measured ms. 55func olgd_receipt(op: *u8, target: *u8, tn: i64, ok: i64, detail: *u8, now_s: i64) -> i64 { 56 if (olgd_recbuf as i64) == 0 { olgd_recbuf = sys_mmap(OLGD_RECBUF) } 57 let rec: *u8 = olgd_recbuf; var o: i64 = 0 58 o = fa_catn(rec, o, now_s); rec[o] = 9 as u8; o = o + 1 59 o = fa_cat(rec, o, "nx_opaque_login" as *u8); rec[o] = 9 as u8; o = o + 1 60 o = fa_cat(rec, o, op); rec[o] = 9 as u8; o = o + 1 61 var i: i64 = 0; while i < tn { rec[o] = target[i]; o = o + 1; i = i + 1 } rec[o] = 9 as u8; o = o + 1 62 if ok == 1 { o = fa_cat(rec, o, "ACCEPTED" as *u8) } else { o = fa_cat(rec, o, "REJECTED" as *u8) } 63 rec[o] = 9 as u8; o = o + 1 64 o = fa_cat(rec, o, detail) 65 rec[o] = 0 as u8 66 let frc: i64 = fa_appendz("/volume1/homes/elderwesto/nishihost/op_receipts.tsv" as *u8, rec, OLGD_RECBUF) 67 olgd_wipe(rec, OLGD_RECBUF) 68 return frc 69} 70// build "ms=<n>" into buf (>=32B) -> the MEASURED-latency detail for a receipt ("don't guess, measure"). 71func olgd_ms_detail(buf: *u8, ms: i64) -> *u8 { 72 var o: i64 = fa_cat(buf, 0, "ms=" as *u8); o = fa_catn(buf, o, ms); buf[o] = 0 as u8; return buf 73} 74import "nx_hr_entitle.nx" // he_emit_super (superuser-by-construction entitlements) + hra_is_superadmin (HR SSOT) 75const OLGD_MAGIC_2097152: i64 = 2097152 76const OLGD_MAGIC_16384: i64 = 16384 77const OLGD_MAGIC_86400: i64 = 86400 78const OLGD_MAGIC_16383: i64 = 16383 79const OLGD_MAGIC_8192: i64 = 8192 80const OLGD_MAGIC_2048: i64 = 2048 81 82const OLGD_PROD_M: i64 = 65536 // production argon2id m_cost (64 MiB, OWASP 2026); argv[5] overrides for smoke 83const OLGD_WIKI_DIR: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/knowledge/wiki_pages" 84// no-cookie browse layer: appended to every gated page so link-clicks fetch the next page WITH the 85// X-Nishi-Session header (no cookie, true to the no-cookie cardinal). document.write keeps it SPA-like. 86const OLGD_SPA: *u8 = "<script>(function(){var t=sessionStorage.getItem('nsess');document.addEventListener('click',function(e){var a=e.target.closest&&e.target.closest('a');if(!a)return;var h=a.getAttribute('href');if(!h)return;if(h.slice(-5)!='.html')return;e.preventDefault();fetch('/wiki/'+h,{headers:{'X-Nishi-Session':t}}).then(function(r){return r.ok?r.text():null}).then(function(x){if(x){document.open();document.write(x);document.close()}else{location='/'}})})})();</script>" 87 88func olgd_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 89func olgd_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let c: i64=s[i] as i64; if c<48{return v} if c>57{return v} v=v*10+(c-48); i=i+1 } return v } 90func olgd_starts(buf: *u8, n: i64, pre: *u8) -> i64 { var i: i64=0; while pre[i]!=(0 as u8){ if i>=n {return 0} if buf[i]!=pre[i]{return 0} i=i+1 } return 1 } 91func olgd_find(buf: *u8, n: i64, needle: *u8, nl: i64) -> i64 { 92 if nl==0 { return 0 } 93 var i: i64=0 94 while i+nl<=n { var j: i64=0; var ok: i64=1; while j<nl { if buf[i+j]!=needle[j]{ok=0; j=nl} else {j=j+1} } if ok==1 {return i} i=i+1 } 95 return 0-1 96} 97func olgd_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var i: i64=0; while s[i]!=(0 as u8){dst[o]=s[i]; o=o+1; i=i+1} return o } 98func olgd_catb(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { var o: i64=off; var i: i64=0; while i<n {dst[o]=src[i]; o=o+1; i=i+1} return o } 99func olgd_itoa(dst: *u8, off: i64, v: i64) -> i64 { if (olgd_scr as i64) == 0 { olgd_scr = sys_mmap(OLGD_SCR_BYTES) } let t: *u8=olgd_scr; var m: i64=v; var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var o: i64=off; var q: i64=k-1; while q>=0{dst[o]=t[q];o=o+1;q=q-1} return o } 100func olgd_hex(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { let hx: *u8="0123456789abcdef" as *u8; var o: i64=off; var i: i64=0; while i<n { let c: i64=(src[i] as i64)&0xff; dst[o]=hx[(c>>4)&15]; dst[o+1]=hx[c&15]; o=o+2; i=i+1 } return o } 101 102// ---- AUTHZ: data-driven entitlements (no hardcoded policy) ---- 103// CUTOVER (no-TSV): entitlements are now a sovereign nx_seg_store (he_ent_put records key/label/url, enumerated by 104// nx_hr_entitle); OLGD_ENT_PATH is the seg_store PREFIX. The live read is he_emit_super (below). The legacy 105// olgd_emit_access / olgd_grant_append TSV funcs are DEAD (no call sites) -- superseded by he_emit_super. 106// nishi_uid_handle.tsv : lines uidhex<TAB>handle (built at login so /access resolves by handle) 107const OLGD_ENT_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/nishi_entitlements-" as *u8 108const OLGD_IDX_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/nishi_uid_handle.tsv" as *u8 109// NISHI HR SSOT: the superadmin (owner, lvl 3) lookup -- an ACTIVE owner here gets EVERY entitlement by construction. 110const OLGD_HR_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/nishi_hr-" as *u8 // CUTOVER: seg_store prefix (was .log) 111 112// uidhex -> handle from the index. writes handle to out_h, returns its length (0 if not found). flag-style 113// loops throughout (break/continue in nested-ifs miscompile on this toolchain -- same landmine as the sites daemon). 114func olgd_idx_lookup(uidhex: *u8, uxn: i64, out_h: *u8, cap: i64) -> i64 { 115 let lb: *i64 = sys_mmap(16) as *i64; lb[0] = 0 116 let data: *u8 = sys_read_file(OLGD_IDX_PATH, lb) 117 if (data as i64) == 0 { return 0 } 118 let n: i64 = lb[0] 119 var i: i64 = 0 120 var found: i64 = 0 121 var run: i64 = 1 122 while run == 1 { 123 if i >= n { run = 0 } 124 else { 125 var e: i64 = i; var g: i64 = 1 126 while g == 1 { if e >= n { g = 0 } else { if data[e] == (10 as u8) { g = 0 } else { e = e + 1 } } } 127 var m: i64 = 1 128 if i + uxn > e { m = 0 } 129 if m == 1 { var j: i64 = 0; while j < uxn { if data[i+j] != uidhex[j] { m = 0; j = uxn } else { j = j + 1 } } } 130 if m == 1 { if i + uxn < e { if data[i+uxn] == (9 as u8) { 131 var s: i64 = i + uxn + 1; var en: i64 = e 132 if en > s { if data[en-1] == (13 as u8) { en = en - 1 } } 133 var o: i64 = 0 134 while s < en { if o < cap - 1 { out_h[o] = data[s]; o = o + 1 } s = s + 1 } 135 found = o; run = 0 136 } } } 137 i = e + 1 138 } 139 } 140 sys_free_file(data, n) // one index read per /whoami and /access; was never released 141 return found 142} 143 144// append "uidhex<TAB>handle\n" to the index IF not already present (idempotent; reuses the lookup). 145func olgd_idx_record(uidhex: *u8, uxn: i64, handle: *u8, hn: i64) -> i64 { 146 let tmp: *u8 = sys_mmap(160) 147 if olgd_idx_lookup(uidhex, uxn, tmp, 160) > 0 { return 0 } 148 let fd: i64 = sys_openat_append(OLGD_IDX_PATH, 420) 149 if fd >= 0 { 150 let line: *u8 = sys_mmap(320); var o: i64 = 0 151 o = olgd_catb(line, o, uidhex, uxn); line[o] = 9 as u8; o = o + 1 152 o = olgd_catb(line, o, handle, hn); line[o] = 10 as u8; o = o + 1 153 sys_write(fd, line, o); sys_close(fd) 154 } 155 return 0 156} 157 158// emit {"handle":"<h>","links":[{"label":..,"url":..},...]} for every entitlement line whose key is "*" or `handle`. 159func olgd_emit_access(handle: *u8, hn: i64, resp: *u8, cap: i64) -> i64 { 160 var o: i64 = 0 161 o = olgd_cat(resp, o, "{\"handle\":\"" as *u8) 162 o = olgd_catb(resp, o, handle, hn) 163 o = olgd_cat(resp, o, "\",\"links\":[" as *u8) 164 let lb: *i64 = sys_mmap(16) as *i64; lb[0] = 0 165 let data: *u8 = sys_read_file(OLGD_ENT_PATH, lb) 166 var first: i64 = 1 167 if (data as i64) != 0 { 168 let n: i64 = lb[0] 169 var i: i64 = 0 170 var run: i64 = 1 171 while run == 1 { 172 if i >= n { run = 0 } 173 else { 174 var e: i64 = i; var g0: i64 = 1 175 while g0 == 1 { if e >= n { g0 = 0 } else { if data[e] == (10 as u8) { g0 = 0 } else { e = e + 1 } } } 176 var k1: i64 = i; var g1: i64 = 1 177 while g1 == 1 { if k1 >= e { g1 = 0 } else { if data[k1] == (9 as u8) { g1 = 0 } else { k1 = k1 + 1 } } } 178 var k2: i64 = k1 + 1; var g2: i64 = 1 179 while g2 == 1 { if k2 >= e { g2 = 0 } else { if data[k2] == (9 as u8) { g2 = 0 } else { k2 = k2 + 1 } } } 180 var ue: i64 = e 181 if ue > k2 { if data[ue-1] == (13 as u8) { ue = ue - 1 } } 182 let keylen: i64 = k1 - i 183 var keyhit: i64 = 0 184 if keylen == 1 { if data[i] == (42 as u8) { keyhit = 1 } } 185 if keylen == hn { var j: i64 = 0; var mm: i64 = 1; while j < hn { if data[i+j] != handle[j] { mm = 0; j = hn } else { j = j + 1 } } if mm == 1 { keyhit = 1 } } 186 if keyhit == 1 { if k1 < e { if k2 < e { 187 if first == 0 { resp[o] = 44; o = o + 1 } 188 o = olgd_cat(resp, o, "{\"label\":\"" as *u8) 189 o = olgd_catb(resp, o, ((data as i64) + k1 + 1) as *u8, k2 - k1 - 1) 190 o = olgd_cat(resp, o, "\",\"url\":\"" as *u8) 191 o = olgd_catb(resp, o, ((data as i64) + k2 + 1) as *u8, ue - k2 - 1) 192 o = olgd_cat(resp, o, "\"}" as *u8) 193 first = 0 194 } } } 195 i = e + 1 196 } 197 } 198 } 199 o = olgd_cat(resp, o, "]}" as *u8) 200 return o 201} 202 203const OLGD_ADMINS_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/nishi_admins.tsv" as *u8 204 205// 1 if `handle` is listed (one handle per line) in nishi_admins.tsv. Only admins may grant entitlements. 206func olgd_is_admin(handle: *u8, hn: i64) -> i64 { 207 if hn <= 0 { return 0 } 208 let lb: *i64 = sys_mmap(16) as *i64; lb[0] = 0 209 let data: *u8 = sys_read_file(OLGD_ADMINS_PATH, lb) 210 if (data as i64) == 0 { return 0 } 211 let n: i64 = lb[0] 212 var i: i64 = 0; var found: i64 = 0; var run: i64 = 1 213 while run == 1 { 214 if i >= n { run = 0 } 215 else { 216 var e: i64 = i; var g: i64 = 1 217 while g == 1 { if e >= n { g = 0 } else { if data[e] == (10 as u8) { g = 0 } else { e = e + 1 } } } 218 var en: i64 = e 219 if en > i { if data[en-1] == (13 as u8) { en = en - 1 } } 220 if en - i == hn { 221 var j: i64 = 0; var m: i64 = 1 222 while j < hn { if data[i+j] != handle[j] { m = 0; j = hn } else { j = j + 1 } } 223 if m == 1 { found = 1; run = 0 } 224 } 225 i = e + 1 226 } 227 } 228 return found 229} 230 231// field must be 1..maxn printable-ASCII bytes -- rejects TAB/newline/control (TSV-injection) + length abuse. 232func olgd_field_ok(s: *u8, n: i64, maxn: i64) -> i64 { 233 if n <= 0 { return 0 } 234 if n > maxn { return 0 } 235 var i: i64 = 0; var ok: i64 = 1 236 while i < n { let c: i64 = s[i] as i64; if c < 32 { ok = 0; i = n } else { if c > 126 { ok = 0; i = n } else { i = i + 1 } } } 237 return ok 238} 239 240// append "who<TAB>label<TAB>url\n" to the entitlements TSV if that exact triple is not already present. 241func olgd_grant_append(who: *u8, wn: i64, label: *u8, ln: i64, url: *u8, un: i64) -> i64 { 242 let line: *u8 = sys_mmap(512); var o: i64 = 0 243 o = olgd_catb(line, o, who, wn); line[o] = 9 as u8; o = o + 1 244 o = olgd_catb(line, o, label, ln); line[o] = 9 as u8; o = o + 1 245 o = olgd_catb(line, o, url, un) 246 let lb: *i64 = sys_mmap(16) as *i64; lb[0] = 0 247 let data: *u8 = sys_read_file(OLGD_ENT_PATH, lb) 248 var present: i64 = 0 249 if (data as i64) != 0 { if olgd_find(data, lb[0], line, o) >= 0 { present = 1 } } 250 if present == 0 { 251 let fd: i64 = sys_openat_append(OLGD_ENT_PATH, 420) 252 if fd >= 0 { line[o] = 10 as u8; sys_write(fd, line, o + 1); sys_close(fd) } 253 } 254 return 0 255} 256 257// send a full HTTP/1.1 response with Content-Length (single write). status e.g. "200 OK", ctype e.g. "application/json". 258func olgd_send(cfd: i64, status: *u8, ctype: *u8, body: *u8, blen: i64) -> i64 { 259 // THE 2 MiB STEP (see the discipline note at the top): this buffer was a fresh mapping per response. 260 if (olgd_sendbuf as i64) == 0 { olgd_sendbuf = sys_mmap(OLGD_MAGIC_2097152) } 261 let buf: *u8 = olgd_sendbuf; var o: i64 = 0 262 o = olgd_cat(buf, o, "HTTP/1.1 " as *u8); o = olgd_cat(buf, o, status) 263 o = olgd_cat(buf, o, "\r\nContent-Type: " as *u8); o = olgd_cat(buf, o, ctype) 264 o = olgd_cat(buf, o, "\r\nContent-Length: " as *u8); o = olgd_itoa(buf, o, blen) 265 o = olgd_cat(buf, o, "\r\nConnection: close\r\nCache-Control: no-store\r\n\r\n" as *u8) 266 if o + blen <= OLGD_MAGIC_2097152 { 267 o = olgd_catb(buf, o, body, blen) 268 sys_write(cfd, buf, o) 269 } else { 270 // A body larger than the compose buffer goes out as header + body in two writes: still ONE 271 // Content-Length-framed response. The old code copied it in unbounded and would have written past the mapping. 272 sys_write(cfd, buf, o); sys_write(cfd, body, blen) 273 } 274 olgd_wipe(buf, o) // tokens and mnemonics ride in response bodies: never leave them in a long-lived buffer 275 return 0 276} 277// extract a request header value (req[0..hend]) for `name` (incl trailing ':'), trimming a leading space. -> len 278func olgd_hdr_val(req: *u8, hend: i64, name: *u8, nl: i64, out: *u8, cap: i64) -> i64 { 279 let p: i64 = olgd_find(req, hend, name, nl) 280 if p < 0 { return 0 } 281 var i: i64 = p + nl 282 if i < hend { if req[i]==(32 as u8) { i=i+1 } } // skip one space after ':' 283 var o: i64 = 0 284 while i < hend { let c: u8 = req[i]; if c==(13 as u8){i=hend} else { if c==(10 as u8){i=hend} else { if o<cap-1 {out[o]=c; o=o+1} i=i+1 } } } 285 out[o]=0 as u8; return o 286} 287 288const OLGD_HTML: *u8 = "<!doctype html><html><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi OPAQUE login</title><style>body{font-family:system-ui,sans-serif;max-width:480px;margin:5vh auto;padding:0 18px;color:#16202e}h1{font-size:1.2rem}h3{margin:18px 0 4px}input{width:100%;padding:9px;margin:5px 0;box-sizing:border-box;border:1px solid #b9c2d6;border-radius:5px}button{padding:9px 16px;margin:6px 6px 6px 0;background:#0b2545;color:#fff;border:0;border-radius:5px;cursor:pointer;font-size:.95rem}#msg{margin:14px 0;padding:12px;background:#f1f4fa;border-left:3px solid #0b2545;word-break:break-all;min-height:1.2em}</style></head><body><h1>Nishi &mdash; no-cookie OPAQUE login</h1><p style=\"color:#667;font-size:.9rem\">Full OPAQUE aPAKE (RFC 9807). The server never stores your passphrase; sessions are Ed25519 no-cookie tokens (sessionStorage + X-Nishi-Session header), never cookies.</p><div id=msg>Register a handle + passphrase, then log in.</div><h3>1. Register</h3><input id=rh placeholder=\"handle (e.g. elder)\"><input id=rp type=password placeholder=\"passphrase\"><button onclick=reg()>Register</button><h3>2. Login</h3><input id=lh placeholder=handle><input id=lp type=password placeholder=passphrase><button onclick=login()>Login</button> <button onclick=who()>Who am I?</button> <button onclick=board()>Open Hub</button> <button onclick=out()>Sign out</button><script>function $(i){return document.getElementById(i)}function M(t){$('msg').textContent=t}async function G(p,b){try{var o={method:b?'POST':'GET',headers:{}};if(b){o.headers['Content-Type']='application/x-www-form-urlencoded';o.body=b}else{o.headers['X-Nishi-Session']=sessionStorage.getItem('nsess')||''}var r=await fetch(p,o);var j={};try{j=await r.json()}catch(e){}return{ok:r.ok,s:r.status,j:j}}catch(e){return{ok:false,s:0,j:{error:'network: '+e}}}}async function reg(){M('Registering\\u2026 (memory-hard OPAQUE step \\u2014 a moment)');var r=await G('/register','handle='+encodeURIComponent($('rh').value)+'&pw='+encodeURIComponent($('rp').value));M(r.ok?('Registered! SAVE THIS RECOVERY MNEMONIC: '+r.j.mnemonic):('Register failed ('+r.s+'): '+(r.j.error||'unknown')))}async function login(){M('Logging in\\u2026 (a moment)');var r=await G('/login','handle='+encodeURIComponent($('lh').value)+'&pw='+encodeURIComponent($('lp').value));if(r.ok){sessionStorage.setItem('nsess',r.j.token);var ret=new URLSearchParams(location.search).get('return');if(ret&&ret.charAt(0)=='/'&&ret.charAt(1)!='/'){location=ret}else if(location.pathname!='/login'){location.reload()}else{board()}}else{M('Login failed ('+r.s+'): '+(r.j.error||'unknown'))}}async function showSpace(){M('Loading your space\\u2026');var r=await G('/whoami');if(!r.ok){M('Session error ('+r.s+')');return}var j=r.j;var h='<h1>Welcome, '+(j.handle||'member')+'</h1><p style=color:#667>Signed in with a no-cookie OPAQUE session. Your access:</p>';if(j.links&&j.links.length){for(var i=0;i<j.links.length;i++){h+='<p><a href='+JSON.stringify(j.links[i].url)+'>'+j.links[i].label+' \\u2192</a></p>'}}else{h+='<p>No resources assigned to you yet.</p>'}h+='<p style=margin-top:20px><button onclick=out()>Sign out</button></p>';document.body.innerHTML=h}async function who(){if(!sessionStorage.getItem('nsess')){M('No session \\u2014 log in first.');return}M('Validating\\u2026');var r=await G('/whoami');M(r.ok?('Valid no-cookie session. user-id hash: '+r.j.uid):('Session rejected ('+r.s+'): '+(r.j.error||'unknown')))}async function board(){if(!sessionStorage.getItem('nsess')){M('Log in first.');return}M('Opening hub...');try{var r=await fetch('/wiki/hub.html',{headers:{'X-Nishi-Session':sessionStorage.getItem('nsess')}});if(r.ok){document.open();document.write(await r.text());document.close()}else{M('Hub denied ('+r.status+')')}}catch(e){M('hub error: '+e)}}function out(){sessionStorage.removeItem('nsess');location='/login'}</script></body></html>" as *u8 289 290// The post-login landing: fetches /access with the no-cookie token and renders ONLY the links this user is 291// entitled to (dynamic per-user). No session -> bounce to /login. The links come from the server (authorized), 292// never from client-claimed identity. 293const OLGD_WELCOME: *u8 = "<!doctype html><html><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi &mdash; your space</title><style>body{font-family:system-ui,sans-serif;max-width:520px;margin:6vh auto;padding:0 18px;color:#16202e}h1{font-size:1.3rem}ul{list-style:none;padding:0}li{margin:10px 0}a.card{display:block;padding:14px 16px;background:#0b2545;color:#fff;text-decoration:none;border-radius:8px;font-size:1.05rem}a.card:hover{background:#13386b}.sub{color:#667;font-size:.9rem}button{margin-top:18px;padding:8px 14px;background:#eef;border:1px solid #b9c2d6;border-radius:6px;cursor:pointer}</style></head><body><h1 id=h>Your space</h1><p class=sub>Signed in with a no-cookie OPAQUE session. These are the resources you have access to.</p><div id=links>Loading\\u2026</div><button onclick=out()>Sign out</button><script>var t=sessionStorage.getItem('nsess');function out(){sessionStorage.removeItem('nsess');location='/login'}if(!t){location='/login'}else{fetch('/access',{headers:{'X-Nishi-Session':t}}).then(function(r){if(!r.ok){location='/login';return null}return r.json()}).then(function(j){if(!j)return;if(j.handle)document.getElementById('h').textContent='Welcome, '+j.handle;var d=document.getElementById('links');if(j.links&&j.links.length){var u='<ul>';for(var i=0;i<j.links.length;i++){u+='<li><a class=card href=\"'+j.links[i].url+'\">'+j.links[i].label+' &rarr;</a></li>'}u+='</ul>';d.innerHTML=u}else{d.textContent='No resources are assigned to you yet.'}}).catch(function(e){document.getElementById('links').textContent='error: '+e})}</script></body></html>" as *u8 294 295// Recover fragment appended to the login page (kept a SEPARATE const because nx_cc caps single string literals; 296// OLGD_HTML is already near that cap). Self-contained: own nrec() + own ids, posts to /register with a mnemonic 297// field (the daemon routes that to olg_recover). The NEW rotated mnemonic is shown ONLY in the user's browser. 298const OLGD_RECOVER: *u8 = "<div style=\"max-width:480px;margin:20px auto;padding:0 18px;font-family:system-ui,sans-serif;color:#16202e\"><h3>Recover access (forgot passphrase)</h3><input id=ch placeholder=handle style=\"width:100%;padding:9px;margin:4px 0;box-sizing:border-box\"><input id=cm placeholder=\"24-word recovery mnemonic\" style=\"width:100%;padding:9px;margin:4px 0;box-sizing:border-box\"><input id=cp type=password placeholder=\"new passphrase\" style=\"width:100%;padding:9px;margin:4px 0;box-sizing:border-box\"><button onclick=nrec() style=\"padding:9px 16px;background:#0b2545;color:#fff;border:0;border-radius:5px;cursor:pointer\">Recover</button><div id=rmsg style=\"margin:10px 0;word-break:break-all\"></div></div><script>function nrec(){var m=document.getElementById('rmsg');m.textContent='Recovering...';fetch('/register',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'handle='+encodeURIComponent(document.getElementById('ch').value)+'&mnemonic='+encodeURIComponent(document.getElementById('cm').value.trim())+'&pw='+encodeURIComponent(document.getElementById('cp').value)}).then(function(r){return r.json().then(function(j){return{ok:r.ok,j:j}})}).then(function(x){m.textContent=x.ok?('Recovered! SAVE THIS NEW MNEMONIC: '+x.j.mnemonic+' -- now log in above with your new passphrase.'):('Recovery failed: '+(x.j.error||'check handle and mnemonic'))})}</script>" as *u8 299 300// AUTO-RECOVER: the missing twin of OLGD_SPA. OLGD_SPA carries the token on link CLICKS; this carries it on the 301// INITIAL navigation. When the login page is served at a NON-/login URL (a top-level nav to a gated path with no 302// X-Nishi-Session header = the "logs in but isn't let in" bounce), it re-fetches the SAME path WITH the token from 303// sessionStorage and renders the real content. sessionStorage, NOT a cookie -- true to the no-cookie cardinal. 304const OLGD_AUTORECOVER: *u8 = "<script>(function(){var t=sessionStorage.getItem('nsess');if(t&&location.pathname!='/login'){fetch(location.pathname,{headers:{'X-Nishi-Session':t}}).then(function(r){return r.ok?r.text():null}).then(function(h){if(h){document.open();document.write(h);document.close()}})}})();</script>" 305 306// serve the login page + the recover fragment + the auto-recover (three consts concatenated -- under the cap). 307func olgd_send_loginpage(cfd: i64) -> i64 { 308 if (olgd_pgbuf as i64) == 0 { olgd_pgbuf = sys_mmap(OLGD_MAGIC_16384) } 309 let pg: *u8 = olgd_pgbuf; var po: i64 = 0 // constant content: recomposed in place, nothing to wipe 310 po = olgd_cat(pg, 0, OLGD_HTML) 311 po = olgd_cat(pg, po, OLGD_RECOVER) 312 po = olgd_cat(pg, po, OLGD_AUTORECOVER) // nav-bounce fix: a gated-path nav re-fetches itself WITH the token 313 olgd_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, pg, po) 314 return 0 315} 316 317func main(argc: i64, argv: *i64) -> i64 { 318 if argc < 5 { 319 sys_write(1, "usage: nx_opaque_login_daemon <port> <keys-path> <store-path> <budget> [argon2_m_cost]\n" as *u8, 86) 320 sys_exit(2); return 2 321 } 322 let port: i64 = olgd_atoi(argv[1] as *u8) 323 let keys_path: *u8 = argv[2] as *u8 324 let store_path: *u8 = argv[3] as *u8 325 let budget: i64 = olgd_atoi(argv[4] as *u8) 326 var m_cost: i64 = OLGD_PROD_M 327 if argc > 5 { m_cost = olgd_atoi(argv[5] as *u8) } 328 var t_cost: i64 = 3 329 if argc > 6 { t_cost = olgd_atoi(argv[6] as *u8) } 330 var p_cost: i64 = 4 331 if argc > 7 { p_cost = olgd_atoi(argv[7] as *u8) } 332 var wikidir: *u8 = OLGD_WIKI_DIR 333 if argc > 8 { wikidir = argv[8] as *u8 } // serving dir (NAS path in prod, /mnt/c local) 334 // [9] = this realm's HR ROSTER (the invite list ls_signup_allowed consults). ABSENT -> signup stays 335 // DENIED, which is exactly today's state, so adding the gate cannot silently re-open registration. 336 // It makes reopening POSSIBLE and SAFE (LAN + invited) instead of a global boolean. 337 var hrstore: *u8 = 0 as *u8 338 if argc > 9 { hrstore = argv[9] as *u8 } 339 340 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext 341 // SESSION LIFETIME = 24h (86400s, the charter hard cap), up from the 900s/15-min default that caused 342 // re-login-every-15-min. The TTL is baked into each minted token; validators read its embedded expiry. 343 // Active sessions stay logged in for a day; sliding-refresh (nx_modern_auth_refresh_session) is the follow-on. 344 if olg_ctx_setup_ttl(ctx, keys_path, store_path, "nishi_site_admin" as *u8, 16, "Nishi site admin" as *u8, 16, OLGD_MAGIC_86400, m_cost, t_cost, p_cost) != 0 { 345 sys_write(1, "CTX-INIT-FAIL\n" as *u8, 14); sys_exit(1); return 1 346 } 347 348 // bind 0.0.0.0:port (LAN + loopback reachable for smoke/test; TLS-fronted in R3) 349 let addr: *u8 = sys_mmap(16) 350 addr[0]=2 as u8; addr[1]=0 as u8 351 addr[2]=((port>>8)&0xff) as u8; addr[3]=(port&0xff) as u8 352 addr[4]=0 as u8; addr[5]=0 as u8; addr[6]=0 as u8; addr[7]=0 as u8 353 var zi: i64=8; while zi<16 { addr[zi]=0 as u8; zi=zi+1 } 354 let lfd: i64 = sys_socket(2, 1, 0) 355 if lfd < 0 { sys_write(1, "SOCKET-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 356 let optv: *u8 = sys_mmap(4); optv[0]=1 as u8 357 sys_setsockopt(lfd, 1, 2, optv, 4) 358 if sys_bind(lfd, addr, 16) < 0 { sys_write(1, "BIND-FAIL\n" as *u8, 10); sys_exit(1); return 1 } 359 if sys_listen(lfd, 16) < 0 { sys_write(1, "LISTEN-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 360 sys_write(1, "OPAQUE-LOGIN-UP\n" as *u8, 16) 361 362 var served: i64 = 0 363 // Peer-address buffers HOISTED above the accept loop deliberately: a sys_mmap per connection is the 364 // per-call-alloc-in-a-loop class that leaked 8KB/call through ss_hget across 205 call sites. 365 let sa: *u8 = sys_mmap(64) 366 let sal: *i64 = sys_mmap(16) as *i64 367 let ip4: *u8 = sys_mmap(4) 368 // PER-REQUEST BUFFERS, ALLOCATED ONCE (see the discipline note at the top). Every one of these used to be a 369 // fresh sys_mmap inside the accept loop, never unmapped -- 24 KB+ of address space per request before any 370 // route ran, and the passphrase/token/mnemonic ones outlived the request. All are wiped before sys_close(cfd). 371 let req: *u8 = sys_mmap(OLGD_MAGIC_16384) 372 let resp: *u8 = sys_mmap(OLGD_MAGIC_8192) 373 let hbuf: *u8 = sys_mmap(128); let hl: *i64 = sys_mmap(16) as *i64 374 let pbuf: *u8 = sys_mmap(320); let pl: *i64 = sys_mmap(16) as *i64 375 let b64: *u8 = sys_mmap(512); let b64n: *i64 = sys_mmap(16) as *i64 376 let ldet: *u8 = sys_mmap(32); let cdet: *u8 = sys_mmap(32); let gdet: *u8 = sys_mmap(32) 377 let ruh: *u8 = sys_mmap(64); let ruhn: *i64 = sys_mmap(16) as *i64; let rux: *u8 = sys_mmap(160) 378 let tb: *u8 = sys_mmap(512); let uh: *u8 = sys_mmap(64); let uhn: *i64 = sys_mmap(16) as *i64 379 let wux: *u8 = sys_mmap(160); let whh: *u8 = sys_mmap(128); let wbig: *u8 = sys_mmap(OLGD_MAGIC_16384) 380 let rnb: *u8 = sys_mmap(512); let rnbn: *i64 = sys_mmap(16) as *i64; let wacc: *u8 = sys_mmap(OLGD_MAGIC_16384) 381 let lbd: *i64 = sys_mmap(16) as *i64 382 let rmn: *u8 = sys_mmap(512); let rml: *i64 = sys_mmap(16) as *i64 383 let nm: *u8 = sys_mmap(512); let nmn: *i64 = sys_mmap(16) as *i64 384 let mn: *u8 = sys_mmap(512); let mnn: *i64 = sys_mmap(16) as *i64 385 let ux: *u8 = sys_mmap(160); let hh: *u8 = sys_mmap(128); let big: *u8 = sys_mmap(OLGD_MAGIC_16384) 386 let tbw: *u8 = sys_mmap(512); let uhw: *u8 = sys_mmap(64); let uhnw: *i64 = sys_mmap(16) as *i64 387 let wpage: *u8 = sys_mmap(512); let wpath: *u8 = sys_mmap(OLGD_MAGIC_2048); let wlb: *i64 = sys_mmap(16) as *i64 388 let comb: *u8 = sys_mmap(OLGD_MAGIC_2097152) // /wiki page + SPA shim compose buffer; wiped to its used length after each send 389 while served < budget { 390 sal[0] = 16 391 // zero the octets EVERY accept: a stale address from the previous connection would be a 392 // LAN verdict inherited by a WAN caller -- the gate must never read last request's peer. 393 ip4[0] = 0 as u8; ip4[1] = 0 as u8; ip4[2] = 0 as u8; ip4[3] = 0 as u8 394 let cfd: i64 = sys_accept_with_addr(lfd, sa, sal) 395 if cfd >= 0 { ls_ip4_from_sockaddr(sa, ip4) } 396 if cfd >= 0 { 397 sys_set_socket_timeout(cfd, 5) 398 let rn: i64 = sys_read(cfd, req, OLGD_MAGIC_16383) 399 if rn > 0 { 400 let he: i64 = olgd_find(req, rn, "\r\n\r\n" as *u8, 4) 401 var body: *u8 = req; var bn: i64 = 0 402 if he >= 0 { body = ((req as i64) + he + 4) as *u8; bn = rn - he - 4 } 403 let now: i64 = sys_now_realtime_sec() 404 405 if olgd_starts(req, rn, "POST /login" as *u8) == 1 { 406 nx_http_form_get_field(body, bn, "handle" as *u8, 6, hbuf, 127, hl) 407 nx_http_form_get_field(body, bn, "pw" as *u8, 2, pbuf, 319, pl) 408 let lt0: i64 = sys_now_ms() 409 var lok: i64 = 0 410 if olg_login(ctx, hbuf, hl[0], pbuf, pl[0], b64, 512, b64n) == NX_MAUTH_OK { lok = 1 } 411 olgd_receipt("login" as *u8, hbuf, hl[0], lok, olgd_ms_detail(ldet, sys_now_ms() - lt0), now) 412 if lok == 1 { 413 // record uid->handle so /access resolves this user's entitlements by handle 414 if olg_whoami(ctx, b64, b64n[0], now, ruh, 64, ruhn) == NX_MAUTH_OK { 415 let ruxn: i64 = olgd_hex(rux, 0, ruh, ruhn[0]) 416 olgd_idx_record(rux, ruxn, hbuf, hl[0]) 417 } 418 var o: i64 = olgd_cat(resp, 0, "{\"token\":\"" as *u8); o = olgd_catb(resp, o, b64, b64n[0]); o = olgd_cat(resp, o, "\"}" as *u8) 419 olgd_send(cfd, "200 OK" as *u8, "application/json" as *u8, resp, o) 420 } else { 421 let o: i64 = olgd_cat(resp, 0, "{\"error\":\"invalid credentials\"}" as *u8) 422 olgd_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, resp, o) 423 } 424 } else { if olgd_starts(req, rn, "GET /whoami" as *u8) == 1 { 425 let tl: i64 = olgd_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, 512) 426 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK { 427 // uid + handle + entitled links in ONE response so the login page can render the landing 428 // INLINE via /whoami -- which the root :8443 front already proxies (no need to update that 429 // root front for the new /welcome+/access routes). Additive fields (uid still present). 430 let wuxn: i64 = olgd_hex(wux, 0, uh, uhn[0]) 431 let whhn: i64 = olgd_idx_lookup(wux, wuxn, whh, 128) 432 var o: i64 = olgd_cat(wbig, 0, "{\"uid\":\"" as *u8); o = olgd_catb(wbig, o, wux, wuxn); o = olgd_cat(wbig, o, "\"," as *u8) 433 // SLIDING REFRESH: re-mint the session (bumped 24h expiry) + return it as "nsess"; a client 434 // heartbeat stores it so an active session never lapses. Additive field; old clients ignore it. 435 if olg_refresh(ctx, tb, tl, now, rnb, 512, rnbn) == NX_MAUTH_OK { o = olgd_cat(wbig, o, "\"nsess\":\"" as *u8); o = olgd_catb(wbig, o, rnb, rnbn[0]); o = olgd_cat(wbig, o, "\"," as *u8) } 436 let wsup: i64 = hra_is_superadmin(OLGD_HR_PATH, wux, wuxn) // owner -> auto-everything 437 let wan: i64 = he_emit_super(wsup, whh, whhn, OLGD_ENT_PATH, wacc, OLGD_MAGIC_16384) 438 o = olgd_catb(wbig, o, ((wacc as i64) + 1) as *u8, wan - 1) 439 olgd_send(cfd, "200 OK" as *u8, "application/json" as *u8, wbig, o) 440 } else { 441 let o: i64 = olgd_cat(resp, 0, "{\"error\":\"session rejected\"}" as *u8) 442 olgd_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, resp, o) 443 } 444 } else { if olgd_starts(req, rn, "GET /board" as *u8) == 1 { 445 // PROTECTED: the Work Board is served ONLY to a valid OPAQUE session (Nishi Family). 446 // No valid X-Nishi-Session token -> 401 (never a public file). 447 let tl: i64 = olgd_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, 512) 448 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK { 449 lbd[0] = 0 450 let page: *u8 = sys_read_file("/tmp/nx_work_board.html" as *u8, lbd) 451 if (page as i64) != 0 { olgd_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, page, lbd[0]); sys_free_file(page, lbd[0]) } 452 else { let o2: i64 = olgd_cat(resp, 0, "board not generated yet" as *u8); olgd_send(cfd, "503 Service Unavailable" as *u8, "text/plain" as *u8, resp, o2) } 453 } else { 454 let o: i64 = olgd_cat(resp, 0, "{\"error\":\"login required\"}" as *u8) 455 olgd_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, resp, o) 456 } 457 } else { if olgd_starts(req, rn, "POST /register" as *u8) == 1 { 458 nx_http_form_get_field(body, bn, "handle" as *u8, 6, hbuf, 127, hl) 459 nx_http_form_get_field(body, bn, "pw" as *u8, 2, pbuf, 319, pl) 460 nx_http_form_get_field(body, bn, "mnemonic" as *u8, 8, rmn, 511, rml) 461 if rml[0] > 0 { 462 // RECOVERY (ungated): the 24-word mnemonic credential uses IDENTITY-KSF (no argon), so it 463 // verifies regardless of the daemon's m_cost; on success it RESETS the passphrase under the 464 // CURRENT daemon params + ROTATES the mnemonic (the new one returns to the user's browser only). 465 var rok: i64 = 0 466 let ct0: i64 = sys_now_ms() 467 if olg_recover(ctx, hbuf, hl[0], rmn, rml[0], pbuf, pl[0], nm, 512, nmn) == NX_MAUTH_OK { rok = 1 } 468 olgd_receipt("recover" as *u8, hbuf, hl[0], rok, olgd_ms_detail(cdet, sys_now_ms() - ct0), now) 469 if rok == 1 { 470 var o: i64 = olgd_cat(resp, 0, "{\"ok\":true,\"mnemonic\":\"" as *u8); o = olgd_catb(resp, o, nm, nmn[0]); o = olgd_cat(resp, o, "\"}" as *u8) 471 olgd_send(cfd, "200 OK" as *u8, "application/json" as *u8, resp, o) 472 } else { 473 let o: i64 = olgd_cat(resp, 0, "{\"error\":\"recovery failed -- check handle + 24-word mnemonic\"}" as *u8) 474 olgd_send(cfd, "400 Bad Request" as *u8, "application/json" as *u8, resp, o) 475 } 476 } else { if olgd_signup_ok(ip4, hrstore, hbuf, hl[0]) == 0 { 477 // ONE refusal for every denial reason ON PURPOSE: telling a caller WHICH condition 478 // failed would leak whether a handle is invited (an enumeration oracle on the family roster). 479 let orc: i64 = olgd_cat(resp, 0, "{\"error\":\"registration closed -- signup requires the home network and an invite\"}" as *u8) 480 olgd_send(cfd, "403 Forbidden" as *u8, "application/json" as *u8, resp, orc) 481 } else { 482 var rgok: i64 = 0 483 let gt0: i64 = sys_now_ms() 484 if olg_register(ctx, hbuf, hl[0], pbuf, pl[0], mn, 512, mnn) == NX_MAUTH_OK { rgok = 1 } 485 olgd_receipt("register" as *u8, hbuf, hl[0], rgok, olgd_ms_detail(gdet, sys_now_ms() - gt0), now) 486 if rgok == 1 { 487 var o: i64 = olgd_cat(resp, 0, "{\"mnemonic\":\"" as *u8); o = olgd_catb(resp, o, mn, mnn[0]); o = olgd_cat(resp, o, "\"}" as *u8) 488 olgd_send(cfd, "200 OK" as *u8, "application/json" as *u8, resp, o) 489 } else { 490 let o: i64 = olgd_cat(resp, 0, "{\"error\":\"register failed\"}" as *u8) 491 olgd_send(cfd, "400 Bad Request" as *u8, "application/json" as *u8, resp, o) 492 } 493 } } 494 } else { if olgd_starts(req, rn, "GET /access" as *u8) == 1 { 495 // AUTHZ: validate session -> uid -> handle (index) -> this user's entitled links as JSON 496 let tl: i64 = olgd_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, 512) 497 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK { 498 let uxn: i64 = olgd_hex(ux, 0, uh, uhn[0]) 499 let hhn: i64 = olgd_idx_lookup(ux, uxn, hh, 128) 500 let asup: i64 = hra_is_superadmin(OLGD_HR_PATH, ux, uxn) // owner -> auto-everything 501 let bn: i64 = he_emit_super(asup, hh, hhn, OLGD_ENT_PATH, big, OLGD_MAGIC_16384) 502 olgd_send(cfd, "200 OK" as *u8, "application/json" as *u8, big, bn) 503 } else { 504 let oa: i64 = olgd_cat(resp, 0, "{\"error\":\"session rejected\"}" as *u8) 505 olgd_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, resp, oa) 506 } 507 } else { if olgd_starts(req, rn, "GET /welcome" as *u8) == 1 { 508 olgd_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, OLGD_WELCOME, olgd_slen(OLGD_WELCOME)) 509 } else { if olgd_starts(req, rn, "GET /wiki" as *u8) == 1 { 510 // PROTECTED: serve any wiki_pages/<page> ONLY to a valid OPAQUE session (path-sanitized). 511 let tlw: i64 = olgd_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tbw, 512) 512 if olg_whoami(ctx, tbw, tlw, now, uhw, 64, uhnw) == NX_MAUTH_OK { 513 var ps: i64 = 9 514 if ps < rn { if req[ps] == (47 as u8) { ps = ps + 1 } } 515 var po: i64 = 0; var rg: i64 = 1 516 while rg == 1 { if ps >= rn { rg = 0 } else { let cc: i64 = req[ps] as i64; if cc == 32 { rg = 0 } else { if cc == 63 { rg = 0 } else { if cc == 13 { rg = 0 } else { if po < 500 { wpage[po] = cc as u8; po = po + 1 } ps = ps + 1 } } } } } 517 if po == 0 { let idx: *u8 = "index.html" as *u8; var ii: i64 = 0; while idx[ii] != (0 as u8) { wpage[ii] = idx[ii]; po = po + 1; ii = ii + 1 } } 518 wpage[po] = 0 as u8 519 var safe: i64 = 1; var si: i64 = 0 520 while si < po { let cc: i64 = wpage[si] as i64; var ok: i64 = 0; if cc >= 97 { if cc <= 122 { ok = 1 } } if cc >= 65 { if cc <= 90 { ok = 1 } } if cc >= 48 { if cc <= 57 { ok = 1 } } if cc == 45 { ok = 1 } if cc == 95 { ok = 1 } if cc == 46 { ok = 1 } if ok == 0 { safe = 0; si = po } si = si + 1 } 521 var di: i64 = 0; while di + 1 < po { if wpage[di] == (46 as u8) { if wpage[di + 1] == (46 as u8) { safe = 0; di = po } } di = di + 1 } 522 if safe == 1 { 523 var wo: i64 = olgd_cat(wpath, 0, wikidir); wpath[wo] = 47 as u8; wo = wo + 1 524 var wi: i64 = 0; while wpage[wi] != (0 as u8) { wpath[wo] = wpage[wi]; wo = wo + 1; wi = wi + 1 } wpath[wo] = 0 as u8 525 wlb[0] = 0 526 let wdata: *u8 = sys_read_file(wpath, wlb) 527 if (wdata as i64) != 0 { 528 // BOUNDED (was an unchecked copy into a 2 MiB buffer): a page that cannot fit with the SPA shim is 529 // refused BY NAME, never truncated or overrun. The file buffer is released either way. 530 if wlb[0] + olgd_slen(OLGD_SPA) < OLGD_MAGIC_2097152 { 531 var co: i64 = 0; while co < wlb[0] { comb[co] = wdata[co]; co = co + 1 } co = olgd_cat(comb, co, OLGD_SPA) 532 olgd_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, comb, co) 533 olgd_wipe(comb, co) 534 } else { let o6: i64 = olgd_cat(resp, 0, "page exceeds the 2 MiB serve cap" as *u8); olgd_send(cfd, "500 Internal Server Error" as *u8, "text/plain" as *u8, resp, o6) } 535 sys_free_file(wdata, wlb[0]) 536 } 537 else { let o4: i64 = olgd_cat(resp, 0, "page not found" as *u8); olgd_send(cfd, "404 Not Found" as *u8, "text/plain" as *u8, resp, o4) } 538 } else { let o5: i64 = olgd_cat(resp, 0, "bad page name" as *u8); olgd_send(cfd, "400 Bad Request" as *u8, "text/plain" as *u8, resp, o5) } 539 } else { olgd_send_loginpage(cfd) } 540 } else { 541 // GET / or GET /login -> the login page + recover fragment 542 olgd_send_loginpage(cfd) 543 } } } } } } } 544 } 545 // WIPE EVERY PER-REQUEST BUFFER before the socket closes: the request body (passphrase), every token and 546 // mnemonic buffer, every response scratch. This is what makes the hoisted buffers equivalent to fresh 547 // zeroed mappings for the next request -- and what keeps secrets out of a long-lived, swappable process. 548 if rn > 0 { olgd_wipe(req, rn) } 549 olgd_wipe(resp, OLGD_MAGIC_8192) 550 olgd_wipe(hbuf, 128); olgd_wipe(hl as *u8, 16); olgd_wipe(pbuf, 320); olgd_wipe(pl as *u8, 16) 551 olgd_wipe(b64, 512); olgd_wipe(b64n as *u8, 16); olgd_wipe(ldet, 32); olgd_wipe(cdet, 32); olgd_wipe(gdet, 32) 552 olgd_wipe(ruh, 64); olgd_wipe(ruhn as *u8, 16); olgd_wipe(rux, 160) 553 olgd_wipe(tb, 512); olgd_wipe(uh, 64); olgd_wipe(uhn as *u8, 16) 554 olgd_wipe(wux, 160); olgd_wipe(whh, 128); olgd_wipe(wbig, OLGD_MAGIC_16384) 555 olgd_wipe(rnb, 512); olgd_wipe(rnbn as *u8, 16); olgd_wipe(wacc, OLGD_MAGIC_16384) 556 olgd_wipe(lbd as *u8, 16) 557 olgd_wipe(rmn, 512); olgd_wipe(rml as *u8, 16); olgd_wipe(nm, 512); olgd_wipe(nmn as *u8, 16) 558 olgd_wipe(mn, 512); olgd_wipe(mnn as *u8, 16) 559 olgd_wipe(ux, 160); olgd_wipe(hh, 128); olgd_wipe(big, OLGD_MAGIC_16384) 560 olgd_wipe(tbw, 512); olgd_wipe(uhw, 64); olgd_wipe(uhnw as *u8, 16) 561 olgd_wipe(wpage, 512); olgd_wipe(wpath, OLGD_MAGIC_2048); olgd_wipe(wlb as *u8, 16) 562 sys_close(cfd) 563 } 564 served = served + 1 565 } 566 sys_close(lfd); sys_exit(0); return 0 567}