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 — 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}