code wiki / _hdl_build / nx_opaque_login_daemon.nx

nx_opaque_login_daemon.nx source

↩ module page · 505 lines · 41865 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// THE SIGNUP DECISION, ADOPTED (2026-07-30). This daemon used to gate POST /register on ONE global boolean, 16// which is precisely the "old single-flag world" that nx_account_admin_exceed_gate scores as the NAIVE 17// BASELINE our own ls_signup_allowed beats -- and the flag was live-MEASURED as '1', so anyone on the 18// internet could mint an account on the family domain. Now BOTH must hold: the ops kill-switch AND the 19// by-construction decision (peer IP is LAN **and** the handle is INVITED in this realm's HR roster). 20// FAIL-CLOSED ON EVERY UNKNOWN: no peer address, or no HR roster configured, means DENY -- an unknown 21// origin must never be able to register, and a daemon started without a roster must not fall open. 22func olgd_signup_ok(ip4: *u8, hrstore: *u8, handle: *u8, hn: i64) -> i64 { 23 if olg_registration_open() == 0 { return 0 } 24 if (ip4 as i64) == 0 { return 0 } 25 if (hrstore as i64) == 0 { return 0 } 26 if hrstore[0] == (0 as u8) { return 0 } 27 return ls_signup_allowed(ip4, hrstore, "nishi_site_admin" as *u8, OLGD_REALM_N, handle, hn) 28} 29 30// Emit a structured op RECEIPT (the "acceptance" teammates/workstreams read to validate orchestration). 31// Schema: ts<TAB>actor<TAB>op<TAB>target<TAB>verdict<TAB>detail. NO secrets ever. Atomic. detail carries measured ms. 32func olgd_receipt(op: *u8, target: *u8, tn: i64, ok: i64, detail: *u8, now_s: i64) -> i64 { 33 let rec: *u8 = sys_mmap(512); var o: i64 = 0 34 o = fa_catn(rec, o, now_s); rec[o] = 9 as u8; o = o + 1 35 o = fa_cat(rec, o, "nx_opaque_login" as *u8); rec[o] = 9 as u8; o = o + 1 36 o = fa_cat(rec, o, op); rec[o] = 9 as u8; o = o + 1 37 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 38 if ok == 1 { o = fa_cat(rec, o, "ACCEPTED" as *u8) } else { o = fa_cat(rec, o, "REJECTED" as *u8) } 39 rec[o] = 9 as u8; o = o + 1 40 o = fa_cat(rec, o, detail) 41 rec[o] = 0 as u8 42 return fa_appendz("/volume1/homes/elderwesto/nishihost/op_receipts.tsv" as *u8, rec, 512) 43} 44// build "ms=<n>" into buf (>=32B) -> the MEASURED-latency detail for a receipt ("don't guess, measure"). 45func olgd_ms_detail(buf: *u8, ms: i64) -> *u8 { 46 var o: i64 = fa_cat(buf, 0, "ms=" as *u8); o = fa_catn(buf, o, ms); buf[o] = 0 as u8; return buf 47} 48import "nx_hr_entitle.nx" // he_emit_super (superuser-by-construction entitlements) + hra_is_superadmin (HR SSOT) 49const OLGD_MAGIC_2097152: i64 = 2097152 50const OLGD_MAGIC_16384: i64 = 16384 51const OLGD_MAGIC_86400: i64 = 86400 52const OLGD_MAGIC_16383: i64 = 16383 53const OLGD_MAGIC_8192: i64 = 8192 54const OLGD_MAGIC_2048: i64 = 2048 55 56const OLGD_PROD_M: i64 = 65536 // production argon2id m_cost (64 MiB, OWASP 2026); argv[5] overrides for smoke 57const OLGD_WIKI_DIR: *u8 = "/mnt/c/Users/elder/nishi-core/nxc2/knowledge/wiki_pages" 58// no-cookie browse layer: appended to every gated page so link-clicks fetch the next page WITH the 59// X-Nishi-Session header (no cookie, true to the no-cookie cardinal). document.write keeps it SPA-like. 60const 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>" 61 62func olgd_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 63func 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 } 64func 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 } 65func olgd_find(buf: *u8, n: i64, needle: *u8, nl: i64) -> i64 { 66 if nl==0 { return 0 } 67 var i: i64=0 68 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 } 69 return 0-1 70} 71func 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 } 72func 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 } 73func olgd_itoa(dst: *u8, off: i64, v: i64) -> i64 { let t: *u8=sys_mmap(28); 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 } 74func 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 } 75 76// ---- AUTHZ: data-driven entitlements (no hardcoded policy) ---- 77// CUTOVER (no-TSV): entitlements are now a sovereign nx_seg_store (he_ent_put records key/label/url, enumerated by 78// nx_hr_entitle); OLGD_ENT_PATH is the seg_store PREFIX. The live read is he_emit_super (below). The legacy 79// olgd_emit_access / olgd_grant_append TSV funcs are DEAD (no call sites) -- superseded by he_emit_super. 80// nishi_uid_handle.tsv : lines uidhex<TAB>handle (built at login so /access resolves by handle) 81const OLGD_ENT_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/nishi_entitlements-" as *u8 82const OLGD_IDX_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/nishi_uid_handle.tsv" as *u8 83// NISHI HR SSOT: the superadmin (owner, lvl 3) lookup -- an ACTIVE owner here gets EVERY entitlement by construction. 84const OLGD_HR_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/nishi_hr-" as *u8 // CUTOVER: seg_store prefix (was .log) 85 86// uidhex -> handle from the index. writes handle to out_h, returns its length (0 if not found). flag-style 87// loops throughout (break/continue in nested-ifs miscompile on this toolchain -- same landmine as the sites daemon). 88func olgd_idx_lookup(uidhex: *u8, uxn: i64, out_h: *u8, cap: i64) -> i64 { 89 let lb: *i64 = sys_mmap(16) as *i64; lb[0] = 0 90 let data: *u8 = sys_read_file(OLGD_IDX_PATH, lb) 91 if (data as i64) == 0 { return 0 } 92 let n: i64 = lb[0] 93 var i: i64 = 0 94 var found: i64 = 0 95 var run: i64 = 1 96 while run == 1 { 97 if i >= n { run = 0 } 98 else { 99 var e: i64 = i; var g: i64 = 1 100 while g == 1 { if e >= n { g = 0 } else { if data[e] == (10 as u8) { g = 0 } else { e = e + 1 } } } 101 var m: i64 = 1 102 if i + uxn > e { m = 0 } 103 if m == 1 { var j: i64 = 0; while j < uxn { if data[i+j] != uidhex[j] { m = 0; j = uxn } else { j = j + 1 } } } 104 if m == 1 { if i + uxn < e { if data[i+uxn] == (9 as u8) { 105 var s: i64 = i + uxn + 1; var en: i64 = e 106 if en > s { if data[en-1] == (13 as u8) { en = en - 1 } } 107 var o: i64 = 0 108 while s < en { if o < cap - 1 { out_h[o] = data[s]; o = o + 1 } s = s + 1 } 109 found = o; run = 0 110 } } } 111 i = e + 1 112 } 113 } 114 return found 115} 116 117// append "uidhex<TAB>handle\n" to the index IF not already present (idempotent; reuses the lookup). 118func olgd_idx_record(uidhex: *u8, uxn: i64, handle: *u8, hn: i64) -> i64 { 119 let tmp: *u8 = sys_mmap(160) 120 if olgd_idx_lookup(uidhex, uxn, tmp, 160) > 0 { return 0 } 121 let fd: i64 = sys_openat_append(OLGD_IDX_PATH, 420) 122 if fd >= 0 { 123 let line: *u8 = sys_mmap(320); var o: i64 = 0 124 o = olgd_catb(line, o, uidhex, uxn); line[o] = 9 as u8; o = o + 1 125 o = olgd_catb(line, o, handle, hn); line[o] = 10 as u8; o = o + 1 126 sys_write(fd, line, o); sys_close(fd) 127 } 128 return 0 129} 130 131// emit {"handle":"<h>","links":[{"label":..,"url":..},...]} for every entitlement line whose key is "*" or `handle`. 132func olgd_emit_access(handle: *u8, hn: i64, resp: *u8, cap: i64) -> i64 { 133 var o: i64 = 0 134 o = olgd_cat(resp, o, "{\"handle\":\"" as *u8) 135 o = olgd_catb(resp, o, handle, hn) 136 o = olgd_cat(resp, o, "\",\"links\":[" as *u8) 137 let lb: *i64 = sys_mmap(16) as *i64; lb[0] = 0 138 let data: *u8 = sys_read_file(OLGD_ENT_PATH, lb) 139 var first: i64 = 1 140 if (data as i64) != 0 { 141 let n: i64 = lb[0] 142 var i: i64 = 0 143 var run: i64 = 1 144 while run == 1 { 145 if i >= n { run = 0 } 146 else { 147 var e: i64 = i; var g0: i64 = 1 148 while g0 == 1 { if e >= n { g0 = 0 } else { if data[e] == (10 as u8) { g0 = 0 } else { e = e + 1 } } } 149 var k1: i64 = i; var g1: i64 = 1 150 while g1 == 1 { if k1 >= e { g1 = 0 } else { if data[k1] == (9 as u8) { g1 = 0 } else { k1 = k1 + 1 } } } 151 var k2: i64 = k1 + 1; var g2: i64 = 1 152 while g2 == 1 { if k2 >= e { g2 = 0 } else { if data[k2] == (9 as u8) { g2 = 0 } else { k2 = k2 + 1 } } } 153 var ue: i64 = e 154 if ue > k2 { if data[ue-1] == (13 as u8) { ue = ue - 1 } } 155 let keylen: i64 = k1 - i 156 var keyhit: i64 = 0 157 if keylen == 1 { if data[i] == (42 as u8) { keyhit = 1 } } 158 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 } } 159 if keyhit == 1 { if k1 < e { if k2 < e { 160 if first == 0 { resp[o] = 44; o = o + 1 } 161 o = olgd_cat(resp, o, "{\"label\":\"" as *u8) 162 o = olgd_catb(resp, o, ((data as i64) + k1 + 1) as *u8, k2 - k1 - 1) 163 o = olgd_cat(resp, o, "\",\"url\":\"" as *u8) 164 o = olgd_catb(resp, o, ((data as i64) + k2 + 1) as *u8, ue - k2 - 1) 165 o = olgd_cat(resp, o, "\"}" as *u8) 166 first = 0 167 } } } 168 i = e + 1 169 } 170 } 171 } 172 o = olgd_cat(resp, o, "]}" as *u8) 173 return o 174} 175 176const OLGD_ADMINS_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/nishi_admins.tsv" as *u8 177 178// 1 if `handle` is listed (one handle per line) in nishi_admins.tsv. Only admins may grant entitlements. 179func olgd_is_admin(handle: *u8, hn: i64) -> i64 { 180 if hn <= 0 { return 0 } 181 let lb: *i64 = sys_mmap(16) as *i64; lb[0] = 0 182 let data: *u8 = sys_read_file(OLGD_ADMINS_PATH, lb) 183 if (data as i64) == 0 { return 0 } 184 let n: i64 = lb[0] 185 var i: i64 = 0; var found: i64 = 0; var run: i64 = 1 186 while run == 1 { 187 if i >= n { run = 0 } 188 else { 189 var e: i64 = i; var g: i64 = 1 190 while g == 1 { if e >= n { g = 0 } else { if data[e] == (10 as u8) { g = 0 } else { e = e + 1 } } } 191 var en: i64 = e 192 if en > i { if data[en-1] == (13 as u8) { en = en - 1 } } 193 if en - i == hn { 194 var j: i64 = 0; var m: i64 = 1 195 while j < hn { if data[i+j] != handle[j] { m = 0; j = hn } else { j = j + 1 } } 196 if m == 1 { found = 1; run = 0 } 197 } 198 i = e + 1 199 } 200 } 201 return found 202} 203 204// field must be 1..maxn printable-ASCII bytes -- rejects TAB/newline/control (TSV-injection) + length abuse. 205func olgd_field_ok(s: *u8, n: i64, maxn: i64) -> i64 { 206 if n <= 0 { return 0 } 207 if n > maxn { return 0 } 208 var i: i64 = 0; var ok: i64 = 1 209 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 } } } 210 return ok 211} 212 213// append "who<TAB>label<TAB>url\n" to the entitlements TSV if that exact triple is not already present. 214func olgd_grant_append(who: *u8, wn: i64, label: *u8, ln: i64, url: *u8, un: i64) -> i64 { 215 let line: *u8 = sys_mmap(512); var o: i64 = 0 216 o = olgd_catb(line, o, who, wn); line[o] = 9 as u8; o = o + 1 217 o = olgd_catb(line, o, label, ln); line[o] = 9 as u8; o = o + 1 218 o = olgd_catb(line, o, url, un) 219 let lb: *i64 = sys_mmap(16) as *i64; lb[0] = 0 220 let data: *u8 = sys_read_file(OLGD_ENT_PATH, lb) 221 var present: i64 = 0 222 if (data as i64) != 0 { if olgd_find(data, lb[0], line, o) >= 0 { present = 1 } } 223 if present == 0 { 224 let fd: i64 = sys_openat_append(OLGD_ENT_PATH, 420) 225 if fd >= 0 { line[o] = 10 as u8; sys_write(fd, line, o + 1); sys_close(fd) } 226 } 227 return 0 228} 229 230// send a full HTTP/1.1 response with Content-Length (single write). status e.g. "200 OK", ctype e.g. "application/json". 231func olgd_send(cfd: i64, status: *u8, ctype: *u8, body: *u8, blen: i64) -> i64 { 232 let buf: *u8 = sys_mmap(OLGD_MAGIC_2097152); var o: i64 = 0 233 o = olgd_cat(buf, o, "HTTP/1.1 " as *u8); o = olgd_cat(buf, o, status) 234 o = olgd_cat(buf, o, "\r\nContent-Type: " as *u8); o = olgd_cat(buf, o, ctype) 235 o = olgd_cat(buf, o, "\r\nContent-Length: " as *u8); o = olgd_itoa(buf, o, blen) 236 o = olgd_cat(buf, o, "\r\nConnection: close\r\nCache-Control: no-store\r\n\r\n" as *u8) 237 o = olgd_catb(buf, o, body, blen) 238 sys_write(cfd, buf, o); return 0 239} 240// extract a request header value (req[0..hend]) for `name` (incl trailing ':'), trimming a leading space. -> len 241func olgd_hdr_val(req: *u8, hend: i64, name: *u8, nl: i64, out: *u8, cap: i64) -> i64 { 242 let p: i64 = olgd_find(req, hend, name, nl) 243 if p < 0 { return 0 } 244 var i: i64 = p + nl 245 if i < hend { if req[i]==(32 as u8) { i=i+1 } } // skip one space after ':' 246 var o: i64 = 0 247 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 } } } 248 out[o]=0 as u8; return o 249} 250 251const 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 252 253// The post-login landing: fetches /access with the no-cookie token and renders ONLY the links this user is 254// entitled to (dynamic per-user). No session -> bounce to /login. The links come from the server (authorized), 255// never from client-claimed identity. 256const 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 257 258// Recover fragment appended to the login page (kept a SEPARATE const because nx_cc caps single string literals; 259// OLGD_HTML is already near that cap). Self-contained: own nrec() + own ids, posts to /register with a mnemonic 260// field (the daemon routes that to olg_recover). The NEW rotated mnemonic is shown ONLY in the user's browser. 261const 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 262 263// AUTO-RECOVER: the missing twin of OLGD_SPA. OLGD_SPA carries the token on link CLICKS; this carries it on the 264// INITIAL navigation. When the login page is served at a NON-/login URL (a top-level nav to a gated path with no 265// X-Nishi-Session header = the "logs in but isn't let in" bounce), it re-fetches the SAME path WITH the token from 266// sessionStorage and renders the real content. sessionStorage, NOT a cookie -- true to the no-cookie cardinal. 267const 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>" 268 269// serve the login page + the recover fragment + the auto-recover (three consts concatenated -- under the cap). 270func olgd_send_loginpage(cfd: i64) -> i64 { 271 let pg: *u8 = sys_mmap(OLGD_MAGIC_16384); var po: i64 = 0 272 po = olgd_cat(pg, 0, OLGD_HTML) 273 po = olgd_cat(pg, po, OLGD_RECOVER) 274 po = olgd_cat(pg, po, OLGD_AUTORECOVER) // nav-bounce fix: a gated-path nav re-fetches itself WITH the token 275 olgd_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, pg, po) 276 return 0 277} 278 279func main(argc: i64, argv: *i64) -> i64 { 280 if argc < 5 { 281 sys_write(1, "usage: nx_opaque_login_daemon <port> <keys-path> <store-path> <budget> [argon2_m_cost]\n" as *u8, 86) 282 sys_exit(2); return 2 283 } 284 let port: i64 = olgd_atoi(argv[1] as *u8) 285 let keys_path: *u8 = argv[2] as *u8 286 let store_path: *u8 = argv[3] as *u8 287 let budget: i64 = olgd_atoi(argv[4] as *u8) 288 var m_cost: i64 = OLGD_PROD_M 289 if argc > 5 { m_cost = olgd_atoi(argv[5] as *u8) } 290 var t_cost: i64 = 3 291 if argc > 6 { t_cost = olgd_atoi(argv[6] as *u8) } 292 var p_cost: i64 = 4 293 if argc > 7 { p_cost = olgd_atoi(argv[7] as *u8) } 294 var wikidir: *u8 = OLGD_WIKI_DIR 295 if argc > 8 { wikidir = argv[8] as *u8 } // serving dir (NAS path in prod, /mnt/c local) 296 // [9] = this realm's HR ROSTER (the invite list ls_signup_allowed consults). ABSENT -> signup stays 297 // DENIED, which is exactly today's state, so adding the gate cannot silently re-open registration. 298 // It makes reopening POSSIBLE and SAFE (LAN + invited) instead of a global boolean. 299 var hrstore: *u8 = 0 as *u8 300 if argc > 9 { hrstore = argv[9] as *u8 } 301 302 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext 303 // SESSION LIFETIME = 24h (86400s, the charter hard cap), up from the 900s/15-min default that caused 304 // re-login-every-15-min. The TTL is baked into each minted token; validators read its embedded expiry. 305 // Active sessions stay logged in for a day; sliding-refresh (nx_modern_auth_refresh_session) is the follow-on. 306 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 { 307 sys_write(1, "CTX-INIT-FAIL\n" as *u8, 14); sys_exit(1); return 1 308 } 309 310 // bind 0.0.0.0:port (LAN + loopback reachable for smoke/test; TLS-fronted in R3) 311 let addr: *u8 = sys_mmap(16) 312 addr[0]=2 as u8; addr[1]=0 as u8 313 addr[2]=((port>>8)&0xff) as u8; addr[3]=(port&0xff) as u8 314 addr[4]=0 as u8; addr[5]=0 as u8; addr[6]=0 as u8; addr[7]=0 as u8 315 var zi: i64=8; while zi<16 { addr[zi]=0 as u8; zi=zi+1 } 316 let lfd: i64 = sys_socket(2, 1, 0) 317 if lfd < 0 { sys_write(1, "SOCKET-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 318 let optv: *u8 = sys_mmap(4); optv[0]=1 as u8 319 sys_setsockopt(lfd, 1, 2, optv, 4) 320 if sys_bind(lfd, addr, 16) < 0 { sys_write(1, "BIND-FAIL\n" as *u8, 10); sys_exit(1); return 1 } 321 if sys_listen(lfd, 16) < 0 { sys_write(1, "LISTEN-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 322 sys_write(1, "OPAQUE-LOGIN-UP\n" as *u8, 16) 323 324 var served: i64 = 0 325 // Peer-address buffers HOISTED above the accept loop deliberately: a sys_mmap per connection is the 326 // per-call-alloc-in-a-loop class that leaked 8KB/call through ss_hget across 205 call sites. 327 let sa: *u8 = sys_mmap(64) 328 let sal: *i64 = sys_mmap(16) as *i64 329 let ip4: *u8 = sys_mmap(4) 330 while served < budget { 331 sal[0] = 16 332 // zero the octets EVERY accept: a stale address from the previous connection would be a 333 // LAN verdict inherited by a WAN caller -- the gate must never read last request's peer. 334 ip4[0] = 0 as u8; ip4[1] = 0 as u8; ip4[2] = 0 as u8; ip4[3] = 0 as u8 335 let cfd: i64 = sys_accept_with_addr(lfd, sa, sal) 336 if cfd >= 0 { ls_ip4_from_sockaddr(sa, ip4) } 337 if cfd >= 0 { 338 sys_set_socket_timeout(cfd, 5) 339 let req: *u8 = sys_mmap(OLGD_MAGIC_16384) 340 let rn: i64 = sys_read(cfd, req, OLGD_MAGIC_16383) 341 if rn > 0 { 342 let he: i64 = olgd_find(req, rn, "\r\n\r\n" as *u8, 4) 343 var body: *u8 = req; var bn: i64 = 0 344 if he >= 0 { body = ((req as i64) + he + 4) as *u8; bn = rn - he - 4 } 345 let now: i64 = sys_now_realtime_sec() 346 let resp: *u8 = sys_mmap(OLGD_MAGIC_8192) 347 348 if olgd_starts(req, rn, "POST /login" as *u8) == 1 { 349 let hbuf: *u8 = sys_mmap(128); let hl: *i64 = sys_mmap(16) as *i64 350 let pbuf: *u8 = sys_mmap(320); let pl: *i64 = sys_mmap(16) as *i64 351 nx_http_form_get_field(body, bn, "handle" as *u8, 6, hbuf, 127, hl) 352 nx_http_form_get_field(body, bn, "pw" as *u8, 2, pbuf, 319, pl) 353 let b64: *u8 = sys_mmap(512); let b64n: *i64 = sys_mmap(16) as *i64 354 let lt0: i64 = sys_now_ms() 355 var lok: i64 = 0 356 if olg_login(ctx, hbuf, hl[0], pbuf, pl[0], b64, 512, b64n) == NX_MAUTH_OK { lok = 1 } 357 let ldet: *u8 = sys_mmap(32); olgd_receipt("login" as *u8, hbuf, hl[0], lok, olgd_ms_detail(ldet, sys_now_ms() - lt0), now) 358 if lok == 1 { 359 // record uid->handle so /access resolves this user's entitlements by handle 360 let ruh: *u8 = sys_mmap(64); let ruhn: *i64 = sys_mmap(16) as *i64 361 if olg_whoami(ctx, b64, b64n[0], now, ruh, 64, ruhn) == NX_MAUTH_OK { 362 let rux: *u8 = sys_mmap(160); let ruxn: i64 = olgd_hex(rux, 0, ruh, ruhn[0]) 363 olgd_idx_record(rux, ruxn, hbuf, hl[0]) 364 } 365 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) 366 olgd_send(cfd, "200 OK" as *u8, "application/json" as *u8, resp, o) 367 } else { 368 let o: i64 = olgd_cat(resp, 0, "{\"error\":\"invalid credentials\"}" as *u8) 369 olgd_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, resp, o) 370 } 371 } else { if olgd_starts(req, rn, "GET /whoami" as *u8) == 1 { 372 let tb: *u8 = sys_mmap(512) 373 let tl: i64 = olgd_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, 512) 374 let uh: *u8 = sys_mmap(64); let uhn: *i64 = sys_mmap(16) as *i64 375 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK { 376 // uid + handle + entitled links in ONE response so the login page can render the landing 377 // INLINE via /whoami -- which the root :8443 front already proxies (no need to update that 378 // root front for the new /welcome+/access routes). Additive fields (uid still present). 379 let wux: *u8 = sys_mmap(160); let wuxn: i64 = olgd_hex(wux, 0, uh, uhn[0]) 380 let whh: *u8 = sys_mmap(128); let whhn: i64 = olgd_idx_lookup(wux, wuxn, whh, 128) 381 let wbig: *u8 = sys_mmap(OLGD_MAGIC_16384) 382 var o: i64 = olgd_cat(wbig, 0, "{\"uid\":\"" as *u8); o = olgd_catb(wbig, o, wux, wuxn); o = olgd_cat(wbig, o, "\"," as *u8) 383 // SLIDING REFRESH: re-mint the session (bumped 24h expiry) + return it as "nsess"; a client 384 // heartbeat stores it so an active session never lapses. Additive field; old clients ignore it. 385 let rnb: *u8 = sys_mmap(512); let rnbn: *i64 = sys_mmap(16) as *i64 386 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) } 387 let wacc: *u8 = sys_mmap(OLGD_MAGIC_16384) 388 let wsup: i64 = hra_is_superadmin(OLGD_HR_PATH, wux, wuxn) // owner -> auto-everything 389 let wan: i64 = he_emit_super(wsup, whh, whhn, OLGD_ENT_PATH, wacc, OLGD_MAGIC_16384) 390 o = olgd_catb(wbig, o, ((wacc as i64) + 1) as *u8, wan - 1) 391 olgd_send(cfd, "200 OK" as *u8, "application/json" as *u8, wbig, o) 392 } else { 393 let o: i64 = olgd_cat(resp, 0, "{\"error\":\"session rejected\"}" as *u8) 394 olgd_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, resp, o) 395 } 396 } else { if olgd_starts(req, rn, "GET /board" as *u8) == 1 { 397 // PROTECTED: the Work Board is served ONLY to a valid OPAQUE session (Nishi Family). 398 // No valid X-Nishi-Session token -> 401 (never a public file). 399 let tb: *u8 = sys_mmap(512) 400 let tl: i64 = olgd_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, 512) 401 let uh: *u8 = sys_mmap(64); let uhn: *i64 = sys_mmap(16) as *i64 402 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK { 403 let lbd: *i64 = sys_mmap(16) as *i64; lbd[0] = 0 404 let page: *u8 = sys_read_file("/tmp/nx_work_board.html" as *u8, lbd) 405 if (page as i64) != 0 { olgd_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, page, lbd[0]) } 406 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) } 407 } else { 408 let o: i64 = olgd_cat(resp, 0, "{\"error\":\"login required\"}" as *u8) 409 olgd_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, resp, o) 410 } 411 } else { if olgd_starts(req, rn, "POST /register" as *u8) == 1 { 412 let hbuf: *u8 = sys_mmap(128); let hl: *i64 = sys_mmap(16) as *i64 413 let pbuf: *u8 = sys_mmap(320); let pl: *i64 = sys_mmap(16) as *i64 414 let rmn: *u8 = sys_mmap(512); let rml: *i64 = sys_mmap(16) as *i64 415 nx_http_form_get_field(body, bn, "handle" as *u8, 6, hbuf, 127, hl) 416 nx_http_form_get_field(body, bn, "pw" as *u8, 2, pbuf, 319, pl) 417 nx_http_form_get_field(body, bn, "mnemonic" as *u8, 8, rmn, 511, rml) 418 if rml[0] > 0 { 419 // RECOVERY (ungated): the 24-word mnemonic credential uses IDENTITY-KSF (no argon), so it 420 // verifies regardless of the daemon's m_cost; on success it RESETS the passphrase under the 421 // CURRENT daemon params + ROTATES the mnemonic (the new one returns to the user's browser only). 422 let nm: *u8 = sys_mmap(512); let nmn: *i64 = sys_mmap(16) as *i64 423 var rok: i64 = 0 424 let ct0: i64 = sys_now_ms() 425 if olg_recover(ctx, hbuf, hl[0], rmn, rml[0], pbuf, pl[0], nm, 512, nmn) == NX_MAUTH_OK { rok = 1 } 426 let cdet: *u8 = sys_mmap(32); olgd_receipt("recover" as *u8, hbuf, hl[0], rok, olgd_ms_detail(cdet, sys_now_ms() - ct0), now) 427 if rok == 1 { 428 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) 429 olgd_send(cfd, "200 OK" as *u8, "application/json" as *u8, resp, o) 430 } else { 431 let o: i64 = olgd_cat(resp, 0, "{\"error\":\"recovery failed -- check handle + 24-word mnemonic\"}" as *u8) 432 olgd_send(cfd, "400 Bad Request" as *u8, "application/json" as *u8, resp, o) 433 } 434 } else { if olgd_signup_ok(ip4, hrstore, hbuf, hl[0]) == 0 { 435 // ONE refusal for every denial reason ON PURPOSE: telling a caller WHICH condition 436 // failed would leak whether a handle is invited (an enumeration oracle on the family roster). 437 let orc: i64 = olgd_cat(resp, 0, "{\"error\":\"registration closed -- signup requires the home network and an invite\"}" as *u8) 438 olgd_send(cfd, "403 Forbidden" as *u8, "application/json" as *u8, resp, orc) 439 } else { 440 let mn: *u8 = sys_mmap(512); let mnn: *i64 = sys_mmap(16) as *i64 441 var rgok: i64 = 0 442 let gt0: i64 = sys_now_ms() 443 if olg_register(ctx, hbuf, hl[0], pbuf, pl[0], mn, 512, mnn) == NX_MAUTH_OK { rgok = 1 } 444 let gdet: *u8 = sys_mmap(32); olgd_receipt("register" as *u8, hbuf, hl[0], rgok, olgd_ms_detail(gdet, sys_now_ms() - gt0), now) 445 if rgok == 1 { 446 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) 447 olgd_send(cfd, "200 OK" as *u8, "application/json" as *u8, resp, o) 448 } else { 449 let o: i64 = olgd_cat(resp, 0, "{\"error\":\"register failed\"}" as *u8) 450 olgd_send(cfd, "400 Bad Request" as *u8, "application/json" as *u8, resp, o) 451 } 452 } } 453 } else { if olgd_starts(req, rn, "GET /access" as *u8) == 1 { 454 // AUTHZ: validate session -> uid -> handle (index) -> this user's entitled links as JSON 455 let tb: *u8 = sys_mmap(512) 456 let tl: i64 = olgd_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, 512) 457 let uh: *u8 = sys_mmap(64); let uhn: *i64 = sys_mmap(16) as *i64 458 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK { 459 let ux: *u8 = sys_mmap(160); let uxn: i64 = olgd_hex(ux, 0, uh, uhn[0]) 460 let hh: *u8 = sys_mmap(128); let hhn: i64 = olgd_idx_lookup(ux, uxn, hh, 128) 461 let big: *u8 = sys_mmap(OLGD_MAGIC_16384) 462 let asup: i64 = hra_is_superadmin(OLGD_HR_PATH, ux, uxn) // owner -> auto-everything 463 let bn: i64 = he_emit_super(asup, hh, hhn, OLGD_ENT_PATH, big, OLGD_MAGIC_16384) 464 olgd_send(cfd, "200 OK" as *u8, "application/json" as *u8, big, bn) 465 } else { 466 let oa: i64 = olgd_cat(resp, 0, "{\"error\":\"session rejected\"}" as *u8) 467 olgd_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, resp, oa) 468 } 469 } else { if olgd_starts(req, rn, "GET /welcome" as *u8) == 1 { 470 olgd_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, OLGD_WELCOME, olgd_slen(OLGD_WELCOME)) 471 } else { if olgd_starts(req, rn, "GET /wiki" as *u8) == 1 { 472 // PROTECTED: serve any wiki_pages/<page> ONLY to a valid OPAQUE session (path-sanitized). 473 let tbw: *u8 = sys_mmap(512) 474 let tlw: i64 = olgd_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tbw, 512) 475 let uhw: *u8 = sys_mmap(64); let uhnw: *i64 = sys_mmap(16) as *i64 476 if olg_whoami(ctx, tbw, tlw, now, uhw, 64, uhnw) == NX_MAUTH_OK { 477 var ps: i64 = 9 478 if ps < rn { if req[ps] == (47 as u8) { ps = ps + 1 } } 479 let page: *u8 = sys_mmap(512); var po: i64 = 0; var rg: i64 = 1 480 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 { page[po] = cc as u8; po = po + 1 } ps = ps + 1 } } } } } 481 if po == 0 { let idx: *u8 = "index.html" as *u8; var ii: i64 = 0; while idx[ii] != (0 as u8) { page[ii] = idx[ii]; po = po + 1; ii = ii + 1 } } 482 page[po] = 0 as u8 483 var safe: i64 = 1; var si: i64 = 0 484 while si < po { let cc: i64 = page[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 } 485 var di: i64 = 0; while di + 1 < po { if page[di] == (46 as u8) { if page[di + 1] == (46 as u8) { safe = 0; di = po } } di = di + 1 } 486 if safe == 1 { 487 let wpath: *u8 = sys_mmap(OLGD_MAGIC_2048); var wo: i64 = olgd_cat(wpath, 0, wikidir); wpath[wo] = 47 as u8; wo = wo + 1 488 var wi: i64 = 0; while page[wi] != (0 as u8) { wpath[wo] = page[wi]; wo = wo + 1; wi = wi + 1 } wpath[wo] = 0 as u8 489 let wlb: *i64 = sys_mmap(16) as *i64; wlb[0] = 0 490 let wdata: *u8 = sys_read_file(wpath, wlb) 491 if (wdata as i64) != 0 { let comb: *u8 = sys_mmap(OLGD_MAGIC_2097152); var co: i64 = 0; while co < wlb[0] { comb[co] = wdata[co]; co = co + 1 } co = olgd_cat(comb, co, OLGD_SPA); olgd_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, comb, co) } 492 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) } 493 } 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) } 494 } else { olgd_send_loginpage(cfd) } 495 } else { 496 // GET / or GET /login -> the login page + recover fragment 497 olgd_send_loginpage(cfd) 498 } } } } } } } 499 } 500 sys_close(cfd) 501 } 502 served = served + 1 503 } 504 sys_close(lfd); sys_exit(0); return 0 505}