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 — 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 — 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+' →</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}