code wiki / _hdl_build / nx_mesh_gateway.nx

nx_mesh_gateway.nx source

↩ module page · 336 lines · 24911 B

1// nx_gallery_gateway.nx -- OPAQUE-gated reverse proxy for the NSFW gallery, mounted under /mesh/*. 2// Auth = the gallery's OWN OPAQUE realm (own keys+store via argv -> isolated NSFW realm) + the 3// Service-Worker header-injection model (the SW adds X-Nishi-Session to every /mesh/* request incl. 4// media -> NO cookie, cardinal C1 preserved). The gateway validates X-Nishi-Session per request via 5// olg_whoami, then reverse-proxies the request (prefix-stripped) to the gallery backend. No valid 6// session -> 401 (never a public byte). Bootstrap: GET /mesh/login (page) registers the SW. 7// argv: [1]=listen_port [2]=keys_path [3]=store_path [4]=budget [5]=backend_port 8// [6]=allow_register(0|1; PROD=0) [7]=m_cost(opt 65536) [8]=t(opt 3) [9]=p(opt 4) 9// R1 = loopback proof (no TLS yet; TLS termination + path-route into nishifamily.com = R3). 10import "nx_opaque_login.nx" // olg_ctx_setup / olg_register / olg_login / olg_whoami + NxAuthContext + NX_MAUTH_* 11import "nx_http_form.nx" // nx_http_form_get_field 12import "nx_connect.nx" // bounded connect 13const GGW_MAGIC_262144: i64 = 262144 14const GGW_MAGIC_6291456: i64 = 6291456 15const GGW_MAGIC_131072: i64 = 131072 16const GGW_MAGIC_131071: i64 = 131071 17const GGW_MAGIC_8192: i64 = 8192 18 19const GGW_PROD_M: i64 = 65536 20// Session lifetime for this LOW-RISK single-operator NSFW media realm. 86400 = 24h = the auth lib's HARD CAP 21// (NX_MAUTH_HARD_MAX_TTL_S; nx_auth_context_init REJECTS anything larger with BAD_INPUT -> a bigger value crashes 22// the gateway at CTX-INIT-FAIL, which it did at 2592000). 24h still kills the 15-min re-login (96x longer); 23// "days" = sliding refresh (nx_modern_auth_refresh_session re-issues on activity), a later rung. argv[10] overrides. 24const GGW_SESSION_TTL: i64 = 86400 25 26func gw_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 27func gw_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 } 28func gw_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 } 29func gw_find(buf: *u8, n: i64, needle: *u8, nl: i64) -> i64 { 30 if nl==0 { return 0 } 31 var i: i64=0 32 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 } 33 return 0-1 34} 35func gw_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 } 36func gw_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 } 37func gw_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 } 38 39// send a full HTTP/1.1 response (single write). status e.g. "200 OK", ctype e.g. "text/html". 40func gw_send(cfd: i64, status: *u8, ctype: *u8, body: *u8, blen: i64) -> i64 { 41 let buf: *u8 = sys_mmap(GGW_MAGIC_262144); var o: i64 = 0 42 o = gw_cat(buf, o, "HTTP/1.1 " as *u8); o = gw_cat(buf, o, status) 43 o = gw_cat(buf, o, "\r\nContent-Type: " as *u8); o = gw_cat(buf, o, ctype) 44 o = gw_cat(buf, o, "\r\nContent-Length: " as *u8); o = gw_itoa(buf, o, blen) 45 o = gw_cat(buf, o, "\r\nConnection: close\r\nCache-Control: no-store\r\n\r\n" as *u8) 46 o = gw_catb(buf, o, body, blen) 47 sys_write(cfd, buf, o); return 0 48} 49func gw_401(cfd: i64) -> i64 { 50 let b: *u8 = "{\"error\":\"login required\"}" as *u8 51 gw_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, b, gw_slen(b)); return 0 52} 53// 1 iff this looks like a top-level page navigation (so an expired session should bounce to the login page, 54// not flash a raw 401/blank grid). Sec-Fetch-Mode: navigate is the browser-set, spoof-irrelevant signal. 55func gw_is_nav(req: *u8, n: i64) -> i64 { if gw_find(req, n, "Sec-Fetch-Mode: navigate" as *u8, 24) >= 0 { return 1 } return 0 } 56// 302 to the login page (graceful re-login). Body-less; no-store so the redirect itself is never cached. 57func gw_302_login(cfd: i64) -> i64 { 58 let b: *u8 = "HTTP/1.1 302 Found\r\nLocation: /mesh/login\r\nContent-Length: 0\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n" as *u8 59 sys_write(cfd, b, gw_slen(b)); return 0 60} 61// 200 response that ALSO sets the session as an HttpOnly cookie -> the browser sends it on every /mesh/* 62// request (page navigation, <img>, <video>) so the page load itself authenticates, no Service-Worker timing 63// dependency. HttpOnly = JS cannot read it; Secure = HTTPS only; SameSite=Strict = no cross-site send. 64func gw_send_ck(cfd: i64, ctype: *u8, body: *u8, blen: i64, ckval: *u8, ckvallen: i64, ttl: i64) -> i64 { 65 let buf: *u8 = sys_mmap(GGW_MAGIC_262144); var o: i64 = 0 66 o = gw_cat(buf, o, "HTTP/1.1 200 OK\r\nContent-Type: " as *u8); o = gw_cat(buf, o, ctype) 67 o = gw_cat(buf, o, "\r\nSet-Cookie: ngs=" as *u8); o = gw_catb(buf, o, ckval, ckvallen) 68 o = gw_cat(buf, o, "; HttpOnly; Secure; SameSite=Strict; Path=/mesh; Max-Age=" as *u8); o = gw_itoa(buf, o, ttl) 69 o = gw_cat(buf, o, "\r\nContent-Length: " as *u8); o = gw_itoa(buf, o, blen) 70 o = gw_cat(buf, o, "\r\nConnection: close\r\nCache-Control: no-store\r\n\r\n" as *u8) 71 o = gw_catb(buf, o, body, blen) 72 sys_write(cfd, buf, o); return 0 73} 74// pull the session token from the `ngs=` cookie in req[0..hend] -> len into out (NUL-term). 75func gw_cookie_val(req: *u8, hend: i64, out: *u8, cap: i64) -> i64 { 76 let p: i64 = gw_find(req, hend, "ngs=" as *u8, 4) 77 if p < 0 { out[0]=0 as u8; return 0 } 78 var i: i64 = p + 4; var o: i64 = 0 79 while i < hend { let c: u8 = req[i]; if c==(59 as u8){i=hend} else { if c==(13 as u8){i=hend} else { if c==(10 as u8){i=hend} else { if c==(32 as u8){i=hend} else { if o<cap-1 {out[o]=c; o=o+1} i=i+1 } } } } } 80 out[o]=0 as u8; return o 81} 82// request header value for `name` (incl trailing ':') over req[0..hend] -> len into out (NUL-term) 83func gw_hdr_val(req: *u8, hend: i64, name: *u8, nl: i64, out: *u8, cap: i64) -> i64 { 84 let p: i64 = gw_find(req, hend, name, nl) 85 if p < 0 { out[0]=0 as u8; return 0 } 86 var i: i64 = p + nl 87 if i < hend { if req[i]==(32 as u8) { i=i+1 } } 88 var o: i64 = 0 89 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 } } } 90 out[o]=0 as u8; return o 91} 92// request-target path (between first space and next space) -> len into out (NUL-term) 93func gw_reqpath(req: *u8, rn: i64, out: *u8, cap: i64) -> i64 { 94 var s1: i64 = 0-1; var i: i64 = 0 95 while i < rn { if req[i]==(32 as u8) { s1=i; i=rn } else { i=i+1 } } 96 if s1 < 0 { out[0]=0 as u8; return 0 } 97 var p: i64 = s1+1; var o: i64 = 0 98 while p < rn { let c: u8 = req[p]; if c==(32 as u8) { p=rn } else { if o<cap-1 { out[o]=c; o=o+1 } p=p+1 } } 99 out[o]=0 as u8; return o 100} 101// reverse-proxy: connect 127.0.0.1:bport, forward method + backend_path (+ body), relay response to cfd. 102// buffered relay (R1: API/images); streaming/range hardening = R4. Returns bytes relayed (or negative). 103// emit "Range: <rng>\r\n" verbatim (pass-through for suffix/multi-range/unparseable forms). 104func gw_emit_range_raw(rq: *u8, o0: i64, rng: *u8, rngn: i64) -> i64 { 105 var o: i64 = gw_cat(rq, o0, "Range: " as *u8); o = gw_catb(rq, o, rng, rngn); o = gw_cat(rq, o, "\r\n" as *u8); return o 106} 107// Cap an OPEN-ENDED or oversized byte-range to a CHUNK window so the whole 206 fits the buffered front proxy 108// (sites_v2 reads the backend response fully into an 8MB buffer before sending; a 206 claiming the full file 109// would be truncated -> the browser rejects the malformed partial). "bytes=START-" / span>CHUNK becomes 110// "bytes=START-(START+CHUNK-1)"; the <video> element fetches the next window as it plays/seeks. Small specific 111// ranges, suffix ranges (bytes=-N), and multi-ranges pass through unchanged. 112func gw_cap_range(rq: *u8, o0: i64, rng: *u8, rngn: i64) -> i64 { 113 let CHUNK: i64 = GGW_MAGIC_6291456 114 var eq: i64 = 0 - 1; var comma: i64 = 0; var k: i64 = 0 115 while k < rngn { if rng[k]==(61 as u8) { if eq<0 { eq=k } } if rng[k]==(44 as u8) { comma=1 } k=k+1 } 116 if eq < 0 { return gw_emit_range_raw(rq, o0, rng, rngn) } 117 if comma == 1 { return gw_emit_range_raw(rq, o0, rng, rngn) } 118 var p: i64 = eq + 1; var start: i64 = 0; var sany: i64 = 0 119 while p < rngn { let c: i64 = rng[p] as i64; if c>=48 { if c<=57 { start=start*10+(c-48); sany=1; p=p+1 } else { p=rngn } } else { p=rngn } } 120 if sany == 0 { return gw_emit_range_raw(rq, o0, rng, rngn) } 121 var dash: i64 = 0 - 1; var d: i64 = eq+1 122 while d < rngn { if rng[d]==(45 as u8) { dash=d; d=rngn } else { d=d+1 } } 123 var end: i64 = 0; var eany: i64 = 0 124 if dash >= 0 { var t: i64 = dash+1; while t < rngn { let c2: i64 = rng[t] as i64; if c2>=48 { if c2<=57 { end=end*10+(c2-48); eany=1; t=t+1 } else { t=rngn } } else { t=rngn } } } 125 if eany == 0 { end = start + CHUNK - 1 } else { if (end - start + 1) > CHUNK { end = start + CHUNK - 1 } } 126 var o: i64 = gw_cat(rq, o0, "Range: bytes=" as *u8) 127 o = gw_itoa(rq, o, start); rq[o]=45 as u8; o=o+1; o = gw_itoa(rq, o, end) 128 o = gw_cat(rq, o, "\r\n" as *u8) 129 return o 130} 131 132func gw_proxy(cfd: i64, bport: i64, method: *u8, mlen: i64, bpath: *u8, bplen: i64, body: *u8, blen: i64, oreq: *u8, ohe: i64) -> i64 { 133 let fd: i64 = sys_socket(2, 1, 0); if fd < 0 { return 0-1 } 134 sys_set_socket_timeout(fd, 20) 135 let a: *u8 = sys_mmap(16) 136 a[0]=2 as u8; a[1]=0 as u8; a[2]=((bport>>8)&0xff) as u8; a[3]=(bport&0xff) as u8 137 a[4]=127 as u8; a[5]=0 as u8; a[6]=0 as u8; a[7]=1 as u8 138 var zi: i64=8; while zi<16 { a[zi]=0 as u8; zi=zi+1 } 139 if nx_connect_bounded(fd, a, 16, NX_CONN_DEFAULT_MS) != 0 { sys_close(fd); return 0-2 } 140 let rq: *u8 = sys_mmap(GGW_MAGIC_131072); var o: i64 = 0 141 o = gw_catb(rq, o, method, mlen); rq[o]=32 as u8; o=o+1 142 o = gw_catb(rq, o, bpath, bplen) 143 o = gw_cat(rq, o, " HTTP/1.1\r\nHost: 127.0.0.1\r\nAccept: */*\r\nConnection: close\r\n" as *u8) 144 // Forward the client's Range header (anchored on a header-line start so "If-Range:" can't false-match) 145 // so the backend answers 206 Partial Content + Content-Range + Accept-Ranges -> native <video> seeks and 146 // plays (esp. iOS/Safari, which refuse a 200 full-file response). No Range present -> byte-identical req. 147 let rng: *u8 = sys_mmap(256) 148 let rngn: i64 = gw_hdr_val(oreq, ohe, "\r\nRange:" as *u8, 8, rng, 256) 149 if rngn > 0 { o = gw_cap_range(rq, o, rng, rngn) } 150 if blen > 0 { 151 o = gw_cat(rq, o, "Content-Type: application/x-www-form-urlencoded\r\nContent-Length: " as *u8) 152 o = gw_itoa(rq, o, blen); o = gw_cat(rq, o, "\r\n\r\n" as *u8) 153 o = gw_catb(rq, o, body, blen) 154 } else { 155 o = gw_cat(rq, o, "\r\n" as *u8) 156 } 157 sys_write(fd, rq, o) 158 let buf: *u8 = sys_mmap(GGW_MAGIC_262144) 159 var total: i64 = 0; var go: i64 = 1 160 while go==1 { let r: i64 = sys_read(fd, buf, GGW_MAGIC_262144); if r<=0 {go=0} else { sys_write(cfd, buf, r); total=total+r } } 161 sys_close(fd) 162 return total 163} 164 165// The Service Worker (served at /mesh/sw.js, scope /mesh/): injects X-Nishi-Session (from IndexedDB) 166// into every /mesh/* request EXCEPT the auth + sw.js + login bootstrap. No cookie. (Browser-tested in R4.) 167const GGW_SW_JS: *u8 = "var DBN='nishi_gallery',ST='auth';function tok(){return new Promise(function(res){try{var r=indexedDB.open(DBN,1);r.onupgradeneeded=function(e){e.target.result.createObjectStore(ST)};r.onsuccess=function(e){var db=e.target.result;try{var g=db.transaction(ST,'readonly').objectStore(ST).get('nsess');g.onsuccess=function(){res(g.result||'')};g.onerror=function(){res('')}}catch(x){res('')}};r.onerror=function(){res('')}}catch(x){res('')}})}self.addEventListener('install',function(e){self.skipWaiting()});self.addEventListener('activate',function(e){e.waitUntil(self.clients.claim())});self.addEventListener('fetch',function(e){var u;try{u=new URL(e.request.url)}catch(x){return}if(u.origin!==self.location.origin){return}var pn=u.pathname;if(pn.indexOf('/mesh/')!==0){return}if(pn.indexOf('/mesh/auth/')===0||pn==='/mesh/sw.js'||pn==='/mesh/login'){return}e.respondWith(tok().then(function(t){var h=new Headers(e.request.headers);if(t){h.set('X-Nishi-Session',t)}if(e.request.method==='GET'){return fetch(new Request(u.href,{headers:h}))}var rq;try{rq=new Request(e.request,{headers:h})}catch(x){rq=e.request}return fetch(rq)}).catch(function(){return fetch(e.request)}))});" as *u8 168 169// The login + SW-bootstrap page (served at /mesh/login). OPAQUE login -> store token in IndexedDB -> 170// register the SW (scope /mesh/) -> go to /mesh/. No passphrase ever leaves as anything but OPAQUE. 171const GGW_LOGIN_HTML: *u8 = "<!doctype html><html><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi Mesh</title><style>body{font-family:system-ui,sans-serif;max-width:420px;margin:8vh auto;padding:0 18px;color:#cdd7e6;background:#0b1019}h1{font-size:1.2rem;color:#e8eef7}p{color:#7c8aa5;font-size:.86rem}input{width:100%;padding:9px;margin:5px 0;box-sizing:border-box;border:1px solid #2a3550;border-radius:5px;background:#121a28;color:#e8eef7}button{padding:9px 16px;margin:6px 6px 0 0;background:#2d6cdf;color:#fff;border:0;border-radius:5px;cursor:pointer}#m{margin:14px 0;padding:12px;background:#121a28;border-left:3px solid #2d6cdf;color:#cdd7e6;word-break:break-all;min-height:1.2em}</style></head><body><h1>Nishi Mesh &mdash; private console</h1><p>Full OPAQUE aPAKE (RFC 9807). The passphrase never leaves your browser as anything crackable; the session rides a Service Worker, not a cookie.</p><div id=m>Log in to reach the sovereign mesh console.</div><input id=h placeholder=handle autocomplete=username><input id=p type=password placeholder=passphrase autocomplete=current-password><button onclick=login()>Login</button> <button onclick=reg()>Register</button><script>function $(i){return document.getElementById(i)} function M(t){$('m').textContent=t} function setTok(t){return new Promise(function(res){var r=indexedDB.open('nishi_gallery',1);r.onupgradeneeded=function(e){e.target.result.createObjectStore('auth')};r.onsuccess=function(e){var db=e.target.result;var tx=db.transaction('auth','readwrite').objectStore('auth').put(t,'nsess');tx.onsuccess=function(){res()};tx.onerror=function(){res()}};r.onerror=function(){res()}})} async function reg(){M('Registering (memory-hard, a moment)...');try{var r=await fetch('/mesh/auth/register',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'handle='+encodeURIComponent($('h').value)+'&pw='+encodeURIComponent($('p').value)});var j=await r.json();M(r.ok?('Registered. SAVE THIS RECOVERY MNEMONIC: '+j.mnemonic):('Register failed: '+(j.error||r.status)))}catch(e){M('error: '+e)}} async function login(){M('Logging in...');try{var r=await fetch('/mesh/auth/login',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'handle='+encodeURIComponent($('h').value)+'&pw='+encodeURIComponent($('p').value)});var j=await r.json();if(!r.ok){M('Login failed: '+(j.error||r.status));return}await setTok(j.token);if('serviceWorker' in navigator){try{await navigator.serviceWorker.register('/mesh/sw.js',{scope:'/mesh/'});await navigator.serviceWorker.ready}catch(e){}}M('Logged in. Opening mesh console...');location.href='/mesh/'}catch(e){M('error: '+e)}}</script></body></html>" as *u8 172 173// Read the FULL request: loop until end-of-headers, then until Content-Length bytes of body are present. 174// A single sys_read can return only the headers (or a partial body) when the client splits the POST across 175// TCP segments -> the login body (handle/pw) arrives truncated -> olg_login fails -> intermittent 401. 176// This is the root cause of the flaky gallery login; GET requests (no body) were unaffected by it. 177func gw_read_full(cfd: i64, req: *u8, cap: i64) -> i64 { 178 var total: i64 = 0 179 var he: i64 = 0 - 1 180 while he < 0 { 181 if total >= cap { return total } 182 let r: i64 = sys_read(cfd, ((req as i64) + total) as *u8, cap - total) 183 if r <= 0 { return total } 184 total = total + r 185 he = gw_find(req, total, "\r\n\r\n" as *u8, 4) 186 } 187 let clbuf: *u8 = sys_mmap(32) 188 let cln: i64 = gw_hdr_val(req, he, "\r\nContent-Length:" as *u8, 17, clbuf, 32) 189 var need: i64 = he + 4 190 if cln > 0 { need = he + 4 + gw_atoi(clbuf) } 191 while total < need { 192 if total >= cap { return total } 193 let r2: i64 = sys_read(cfd, ((req as i64) + total) as *u8, cap - total) 194 if r2 <= 0 { return total } 195 total = total + r2 196 } 197 return total 198} 199 200func main(argc: i64, argv: *i64) -> i64 { 201 if argc < 6 { 202 sys_write(1, "usage: nx_gallery_gateway <port> <keys> <store> <budget> <backend_port> [allow_register] [m] [t] [p]\n" as *u8, 100) 203 sys_exit(2); return 2 204 } 205 let port: i64 = gw_atoi(argv[1] as *u8) 206 let keys_path: *u8 = argv[2] as *u8 207 let store_path: *u8 = argv[3] as *u8 208 let budget: i64 = gw_atoi(argv[4] as *u8) 209 let bport: i64 = gw_atoi(argv[5] as *u8) 210 var allow_reg: i64 = 0 211 if argc > 6 { allow_reg = gw_atoi(argv[6] as *u8) } 212 var m_cost: i64 = GGW_PROD_M 213 if argc > 7 { m_cost = gw_atoi(argv[7] as *u8) } 214 var t_cost: i64 = 3 215 if argc > 8 { t_cost = gw_atoi(argv[8] as *u8) } 216 var p_cost: i64 = 4 217 if argc > 9 { p_cost = gw_atoi(argv[9] as *u8) } 218 var session_ttl: i64 = GGW_SESSION_TTL 219 if argc > 10 { session_ttl = gw_atoi(argv[10] as *u8) } 220 221 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext 222 if olg_ctx_setup_ttl(ctx, keys_path, store_path, "nishi_gallery" as *u8, 13, "Nishi Gallery" as *u8, 13, session_ttl, m_cost, t_cost, p_cost) != 0 { 223 sys_write(1, "CTX-INIT-FAIL\n" as *u8, 14); sys_exit(1); return 1 224 } 225 226 let addr: *u8 = sys_mmap(16) 227 addr[0]=2 as u8; addr[1]=0 as u8 228 addr[2]=((port>>8)&0xff) as u8; addr[3]=(port&0xff) as u8 229 addr[4]=0 as u8; addr[5]=0 as u8; addr[6]=0 as u8; addr[7]=0 as u8 230 var zi: i64=8; while zi<16 { addr[zi]=0 as u8; zi=zi+1 } 231 let lfd: i64 = sys_socket(2, 1, 0) 232 if lfd < 0 { sys_write(1, "SOCKET-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 233 let optv: *u8 = sys_mmap(4); optv[0]=1 as u8 234 sys_setsockopt(lfd, 1, 2, optv, 4) 235 if sys_bind(lfd, addr, 16) < 0 { sys_write(1, "BIND-FAIL\n" as *u8, 10); sys_exit(1); return 1 } 236 if sys_listen(lfd, 16) < 0 { sys_write(1, "LISTEN-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 237 sys_write(1, "MESH-GATEWAY-UP\n" as *u8, 16) 238 239 let st: *i64 = sys_mmap(16) as *i64 240 var served: i64 = 0 241 while served < budget { 242 let cfd: i64 = sys_accept(lfd) 243 if cfd >= 0 { 244 let pid: i64 = sys_fork() 245 if pid == 0 { 246 sys_close(lfd) 247 sys_set_socket_timeout(cfd, 20) 248 let req: *u8 = sys_mmap(GGW_MAGIC_131072) 249 let rn: i64 = gw_read_full(cfd, req, GGW_MAGIC_131071) 250 if rn > 0 { 251 let he: i64 = gw_find(req, rn, "\r\n\r\n" as *u8, 4) 252 var body: *u8 = req; var bn: i64 = 0 253 if he >= 0 { body = ((req as i64) + he + 4) as *u8; bn = rn - he - 4 } 254 let now: i64 = sys_now_realtime_sec() 255 let path: *u8 = sys_mmap(GGW_MAGIC_8192) 256 let plen: i64 = gw_reqpath(req, rn, path, GGW_MAGIC_8192) 257 let resp: *u8 = sys_mmap(GGW_MAGIC_8192) 258 259 if gw_starts(req, rn, "POST /mesh/auth/register" as *u8) == 1 { 260 if allow_reg == 1 { 261 let hbuf: *u8 = sys_mmap(128); let hl: *i64 = sys_mmap(16) as *i64 262 let pbuf: *u8 = sys_mmap(320); let pl: *i64 = sys_mmap(16) as *i64 263 nx_http_form_get_field(body, bn, "handle" as *u8, 6, hbuf, 127, hl) 264 nx_http_form_get_field(body, bn, "pw" as *u8, 2, pbuf, 319, pl) 265 let mn: *u8 = sys_mmap(512); let mnn: *i64 = sys_mmap(16) as *i64 266 if olg_register(ctx, hbuf, hl[0], pbuf, pl[0], mn, 512, mnn) == NX_MAUTH_OK { 267 var o: i64 = gw_cat(resp, 0, "{\"mnemonic\":\"" as *u8); o = gw_catb(resp, o, mn, mnn[0]); o = gw_cat(resp, o, "\"}" as *u8) 268 gw_send(cfd, "200 OK" as *u8, "application/json" as *u8, resp, o) 269 } else { 270 let o: i64 = gw_cat(resp, 0, "{\"error\":\"register failed\"}" as *u8) 271 gw_send(cfd, "400 Bad Request" as *u8, "application/json" as *u8, resp, o) 272 } 273 } else { 274 let o: i64 = gw_cat(resp, 0, "{\"error\":\"registration closed\"}" as *u8) 275 gw_send(cfd, "403 Forbidden" as *u8, "application/json" as *u8, resp, o) 276 } 277 } else { if gw_starts(req, rn, "POST /mesh/auth/login" as *u8) == 1 { 278 let hbuf: *u8 = sys_mmap(128); let hl: *i64 = sys_mmap(16) as *i64 279 let pbuf: *u8 = sys_mmap(320); let pl: *i64 = sys_mmap(16) as *i64 280 nx_http_form_get_field(body, bn, "handle" as *u8, 6, hbuf, 127, hl) 281 nx_http_form_get_field(body, bn, "pw" as *u8, 2, pbuf, 319, pl) 282 let b64: *u8 = sys_mmap(512); let b64n: *i64 = sys_mmap(16) as *i64 283 if olg_login(ctx, hbuf, hl[0], pbuf, pl[0], b64, 512, b64n) == NX_MAUTH_OK { 284 var o: i64 = gw_cat(resp, 0, "{\"token\":\"" as *u8); o = gw_catb(resp, o, b64, b64n[0]); o = gw_cat(resp, o, "\"}" as *u8) 285 gw_send_ck(cfd, "application/json" as *u8, resp, o, b64, b64n[0], session_ttl) 286 } else { 287 let o: i64 = gw_cat(resp, 0, "{\"error\":\"invalid credentials\"}" as *u8) 288 gw_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, resp, o) 289 } 290 } else { if gw_starts(req, rn, "GET /mesh/auth/whoami" as *u8) == 1 { 291 let tb: *u8 = sys_mmap(512) 292 var tl: i64 = gw_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, 512) 293 if tl == 0 { tl = gw_cookie_val(req, he, tb, 512) } 294 let uh: *u8 = sys_mmap(64); let uhn: *i64 = sys_mmap(16) as *i64 295 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK { 296 let o: i64 = gw_cat(resp, 0, "{\"ok\":1}" as *u8) 297 gw_send(cfd, "200 OK" as *u8, "application/json" as *u8, resp, o) 298 } else { 299 gw_401(cfd) 300 } 301 } else { if gw_starts(req, rn, "GET /mesh/sw.js" as *u8) == 1 { 302 gw_send(cfd, "200 OK" as *u8, "application/javascript" as *u8, GGW_SW_JS, gw_slen(GGW_SW_JS)) 303 } else { if gw_starts(req, rn, "GET /mesh/login" as *u8) == 1 { 304 gw_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, GGW_LOGIN_HTML, gw_slen(GGW_LOGIN_HTML)) 305 } else { 306 if gw_starts(path, plen, "/mesh" as *u8) == 1 { 307 let tb: *u8 = sys_mmap(512) 308 var tl: i64 = gw_hdr_val(req, he, "X-Nishi-Session:" as *u8, 16, tb, 512) 309 if tl == 0 { tl = gw_cookie_val(req, he, tb, 512) } 310 let uh: *u8 = sys_mmap(64); let uhn: *i64 = sys_mmap(16) as *i64 311 if olg_whoami(ctx, tb, tl, now, uh, 64, uhn) == NX_MAUTH_OK { 312 var bp: *u8 = ((path as i64) + 5) as *u8 313 var bpl: i64 = plen - 5 314 if bpl <= 0 { bp = "/" as *u8; bpl = 1 } 315 let msp: i64 = gw_find(req, rn, " " as *u8, 1) 316 gw_proxy(cfd, bport, req, msp, bp, bpl, body, bn, req, he) 317 } else { 318 if gw_is_nav(req, rn) == 1 { gw_302_login(cfd) } else { gw_401(cfd) } 319 } 320 } else { 321 let o: i64 = gw_cat(resp, 0, "not found" as *u8) 322 gw_send(cfd, "404 Not Found" as *u8, "text/plain" as *u8, resp, o) 323 } 324 } } } } } 325 } 326 sys_close(cfd) 327 sys_exit(0) 328 } 329 sys_close(cfd) 330 var reaped: i64 = 1 331 while reaped > 0 { reaped = sys_wait4(0 - 1, st, 1) } 332 } 333 served = served + 1 334 } 335 sys_close(lfd); sys_exit(0); return 0 336}