code wiki / _hdl_build / nx_torrent_gateway.nx

nx_torrent_gateway.nx source

↩ module page · 206 lines · 13995 B

1// nx_torrent_gateway.nx -- R3c: the FRONT GATEWAY that puts /torrent on the hub, GATED. Mirrors the proven 2// nx_gallery_gateway: OPAQUE login (full RFC-9807 aPAKE, no plaintext at rest) + cookie/SW token transport, 3// then per request it resolves the viewer's LEVEL via nx_torrent_authn (tau_level) and reverse-proxies to the 4// lean torrent daemon (127.0.0.1:bport) -- INJECTING the level as the trusted internal header X-Nishi-Level. 5// SECURITY KEYSTONE: the backend request is built FRESH (method + path + OUR X-Nishi-Level + Range + body), 6// so a client-supplied X-Nishi-Level is DROPPED -- the level is authoritative from the validated session, never 7// from client input. No valid session on a gated path -> 401 (no content). The daemon binds localhost behind 8// this gateway (Cardinal 12). The realm keys/store + roles registry are the HUB realm (operator-provisioned). 9// license_tier: ORIGINAL 10import "nx_torrent_authn.nx" // tau_level / tau_token / tau_find + olg_* + NxAuthContext + NX_MAUTH_OK (transitively) 11import "nx_opaque_login.nx" 12import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 13import "nx_syscalls.nx" 14const TGW_MAGIC_1024: i64 = 1024 15const TGW_MAGIC_262144: i64 = 262144 16const TGW_MAGIC_65536: i64 = 65536 17const TGW_MAGIC_131072: i64 = 131072 18const TGW_MAGIC_2048: i64 = 2048 19const TGW_MAGIC_1280: i64 = 1280 20 21func tg_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 {i=i+1} else { if c>57 {i=i+1} else { v=v*10+(c-48); i=i+1 } } } return v } 22func tg_cat(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ dst[o+i]=s[i]; i=i+1 } return o+i } 23func tg_catb(dst: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64=0; while i<n { dst[o+i]=s[i]; i=i+1 } return o+i } 24func tg_itoa(dst: *u8, o: i64, v: i64) -> i64 { 25 if v==0 { dst[o]=48 as u8; return o+1 } 26 var m: i64=v; if m<0{m=0-m} 27 let t: *u8=sys_mmap(32); var k: i64=0 28 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 29 var j: i64=0; while j<k { dst[o+j]=t[k-1-j]; j=j+1 } 30 return o+k 31} 32// request-target path (between the first two spaces) -> out (NUL-term). returns len. 33func tg_reqpath(req: *u8, n: i64, out: *u8, cap: i64) -> i64 { 34 var s1: i64 = 0-1; var i: i64 = 0 35 while i < n { if req[i]==(32 as u8) { s1=i; i=n } else { i=i+1 } } 36 if s1 < 0 { out[0]=0 as u8; return 0 } 37 var p: i64 = s1+1; var o: i64 = 0 38 while p < n { let c: u8 = req[p]; if c==(32 as u8){p=n} else { if o<cap-1 { out[o]=c; o=o+1 } p=p+1 } } 39 out[o]=0 as u8; return o 40} 41// the HTTP method (up to the first space) -> out. returns len. 42func tg_method(req: *u8, n: i64, out: *u8, cap: i64) -> i64 { 43 var o: i64 = 0 44 while o < n { let c: u8 = req[o]; if c==(32 as u8){ out[o]=0 as u8; return o } if o<cap-1 { out[o]=c } o=o+1 } 45 out[0]=0 as u8; return 0 46} 47 48// THE SECURITY KEYSTONE: build the backend request FRESH. method + path + the AUTHORITATIVE X-Nishi-Level + 49// (forwarded) Range + body. Because it's built from scratch, any client-supplied X-Nishi-Level is DROPPED. 50func tg_backend_req(method: *u8, mlen: i64, path: *u8, plen: i64, level: i64, rng: *u8, rngn: i64, body: *u8, blen: i64, out: *u8) -> i64 { 51 var o: i64 = 0 52 o = tg_catb(out, o, method, mlen); out[o]=32 as u8; o=o+1 53 o = tg_catb(out, o, path, plen) 54 o = tg_cat(out, o, " HTTP/1.1\r\nHost: 127.0.0.1\r\nAccept: */*\r\nConnection: close\r\n" as *u8) 55 o = tg_cat(out, o, "X-Nishi-Level: " as *u8); o = tg_itoa(out, o, level); o = tg_cat(out, o, "\r\n" as *u8) 56 if rngn > 0 { o = tg_cat(out, o, "Range: " as *u8); o = tg_catb(out, o, rng, rngn); o = tg_cat(out, o, "\r\n" as *u8) } 57 if blen > 0 { 58 o = tg_cat(out, o, "Content-Type: application/x-www-form-urlencoded\r\nContent-Length: " as *u8) 59 o = tg_itoa(out, o, blen); o = tg_cat(out, o, "\r\n\r\n" as *u8) 60 o = tg_catb(out, o, body, blen) 61 } else { 62 o = tg_cat(out, o, "\r\n" as *u8) 63 } 64 return o 65} 66 67// gate decision for a path at a level. A gated /torrent/* path needs level>=1 (FAMILY); level 0 -> DENY (401). 68// The auth/login/sw bootstrap paths are PUBLIC (you must reach them to log in). returns 1 ALLOW, 0 DENY. 69func tg_path_public(path: *u8, plen: i64) -> i64 { 70 if tau_find(path, plen, "/torrent/auth/" as *u8, 14) == 0 { return 1 } 71 if tau_find(path, plen, "/torrent/login" as *u8, 14) == 0 { return 1 } 72 if tau_find(path, plen, "/torrent/sw.js" as *u8, 14) == 0 { return 1 } 73 return 0 74} 75func tg_gate_decision(path: *u8, plen: i64, level: i64) -> i64 { 76 if tg_path_public(path, plen) == 1 { return 1 } // login bootstrap is reachable 77 if level >= 1 { return 1 } // any logged-in member+ may reach the gated app (area enforced downstream) 78 return 0 // no session -> DENY (401) 79} 80 81// send a small response (status, ctype, body) + close. 82func tg_send(cfd: i64, status: *u8, ctype: *u8, body: *u8, blen: i64) -> i64 { 83 let h: *u8 = sys_mmap(TGW_MAGIC_1024); var o: i64 = 0 84 o = tg_cat(h, o, "HTTP/1.1 " as *u8); o = tg_cat(h, o, status) 85 o = tg_cat(h, o, "\r\nContent-Type: " as *u8); o = tg_cat(h, o, ctype) 86 o = tg_cat(h, o, "\r\nContent-Length: " as *u8); o = tg_itoa(h, o, blen) 87 o = tg_cat(h, o, "\r\nConnection: close\r\n\r\n" as *u8) 88 sys_write(cfd, h, o) 89 if blen > 0 { sys_write(cfd, body, blen) } 90 sys_close(cfd) 91 return 0 92} 93func tg_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 94 95// reverse-proxy: connect 127.0.0.1:bport, write the fresh backend request, relay the response to cfd. 96func tg_proxy(cfd: i64, bport: i64, breq: *u8, breqn: i64) -> i64 { 97 let fd: i64 = sys_socket(2, 1, 0); if fd < 0 { return 0-1 } 98 sys_set_socket_timeout(fd, 20) 99 let a: *u8 = sys_mmap(16) 100 a[0]=2 as u8; a[1]=0 as u8; a[2]=((bport>>8)&0xff) as u8; a[3]=(bport&0xff) as u8 101 a[4]=127 as u8; a[5]=0 as u8; a[6]=0 as u8; a[7]=1 as u8 102 var zi: i64=8; while zi<16 { a[zi]=0 as u8; zi=zi+1 } 103 if nx_connect_bounded(fd, a, 16, NX_CONN_DEFAULT_MS) != 0 { sys_close(fd); return 0-2 } 104 sys_write(fd, breq, breqn) 105 let buf: *u8 = sys_mmap(TGW_MAGIC_262144); var total: i64 = 0; var go: i64 = 1 106 while go==1 { let r: i64 = sys_read(fd, buf, TGW_MAGIC_262144); if r<=0 {go=0} else { sys_write(cfd, buf, r); total=total+r } } 107 sys_close(fd) 108 return total 109} 110 111const TGW_LOGIN_HTML: *u8 = "<!doctype html><html><head><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi Torrent</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}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;word-break:break-all;min-height:1.2em}</style></head><body><h1>Nishi Torrent &mdash; sign in</h1><div id=m>Log in to manage downloads.</div><input id=h placeholder=handle autocomplete=username><input id=p type=password placeholder=passphrase autocomplete=current-password><button onclick=login()>Login</button><script>function $(i){return document.getElementById(i)}function M(t){$('m').textContent=t}async function login(){M('Logging in...');try{var r=await fetch('/torrent/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}M('Logged in. Opening...');location.href='/torrent/'}catch(e){M('error: '+e)}}</script></body></html>" as *u8 112 113func main(argc: i64, argv: *i64) -> i64 { 114 if argc < 8 { 115 sys_write(1, "usage: nx_torrent_gateway <port> <keys> <store> <roles> <budget> <backend_port> <realm> [allow_register] [m] [t] [p]\n" as *u8, 113) 116 sys_exit(2); return 2 117 } 118 let port: i64 = tg_atoi(argv[1] as *u8) 119 let keys_path: *u8 = argv[2] as *u8 120 let store_path: *u8 = argv[3] as *u8 121 let roles_path: *u8 = argv[4] as *u8 122 let budget: i64 = tg_atoi(argv[5] as *u8) 123 let bport: i64 = tg_atoi(argv[6] as *u8) 124 let realm: *u8 = argv[7] as *u8; let realm_n: i64 = tg_slen(realm) 125 var allow_reg: i64 = 0 126 if argc > 8 { allow_reg = tg_atoi(argv[8] as *u8) } 127 var m_cost: i64 = TGW_MAGIC_65536 128 if argc > 9 { m_cost = tg_atoi(argv[9] as *u8) } 129 var t_cost: i64 = 3 130 if argc > 10 { t_cost = tg_atoi(argv[10] as *u8) } 131 var p_cost: i64 = 4 132 if argc > 11 { p_cost = tg_atoi(argv[11] as *u8) } 133 134 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext 135 if olg_ctx_setup(ctx, keys_path, store_path, realm, realm_n, realm, realm_n, m_cost, t_cost, p_cost) != 0 { 136 sys_write(1, "CTX-INIT-FAIL\n" as *u8, 14); sys_exit(1); return 1 137 } 138 let addr: *u8 = sys_mmap(16) 139 addr[0]=2 as u8; addr[1]=0 as u8; addr[2]=((port>>8)&0xff) as u8; addr[3]=(port&0xff) as u8 140 var zi: i64=4; while zi<16 { addr[zi]=0 as u8; zi=zi+1 } 141 let lfd: i64 = sys_socket(2, 1, 0) 142 if lfd < 0 { sys_write(1, "SOCKET-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 143 let optv: *u8 = sys_mmap(4); optv[0]=1 as u8; sys_setsockopt(lfd, 1, 2, optv, 4) 144 if sys_bind(lfd, addr, 16) < 0 { sys_write(1, "BIND-FAIL\n" as *u8, 10); sys_exit(1); return 1 } 145 if sys_listen(lfd, 16) < 0 { sys_write(1, "LISTEN-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 146 sys_write(1, "TORRENT-GATEWAY-UP\n" as *u8, 19) 147 148 let st: *i64 = sys_mmap(16) as *i64 149 var served: i64 = 0 150 while served < budget { 151 let cfd: i64 = sys_accept(lfd) 152 if cfd >= 0 { 153 let pid: i64 = sys_fork() 154 if pid == 0 { 155 sys_close(lfd) 156 sys_set_socket_timeout(cfd, 15) 157 let req: *u8 = sys_mmap(TGW_MAGIC_131072) 158 var n: i64 = 0; var he: i64 = 0-1 159 while he < 0 { if n >= TGW_MAGIC_131072 { he = n } else { let r: i64 = sys_read(cfd, ((req as i64)+n) as *u8, TGW_MAGIC_131072-n); if r<=0 { he = n } else { n = n + r; he = tau_find(req, n, "\r\n\r\n" as *u8, 4) } } } 160 let path: *u8 = sys_mmap(TGW_MAGIC_2048); let plen: i64 = tg_reqpath(req, n, path, TGW_MAGIC_2048) 161 let meth: *u8 = sys_mmap(16); let mlen: i64 = tg_method(req, n, meth, 16) 162 // login: OPAQUE -> token + Set-Cookie ngs. 163 if tau_find(path, plen, "/torrent/auth/login" as *u8, 19) == 0 { 164 let hb: *u8 = sys_mmap(128); let pb: *u8 = sys_mmap(256) 165 var hN: i64 = tau_hdr_val(req, n, "handle=" as *u8, 7, hb, 128) // urlencoded body: handle=..&pw=.. 166 var pN: i64 = tau_hdr_val(req, n, "pw=" as *u8, 3, pb, 256) 167 var hh: i64 = 0; while hh < hN { if hb[hh]==(38 as u8) { hN = hh } else { hh = hh + 1 } } // strip "&pw=.." from handle 168 var pp: i64 = 0; while pp < pN { if pb[pp]==(38 as u8) { pN = pp } else { pp = pp + 1 } } // strip a trailing field from pw 169 let b64: *u8 = sys_mmap(512); let b64n: *i64 = sys_mmap(16) as *i64; b64n[0]=0 170 if olg_login(ctx, hb, hN, pb, pN, b64, 512, b64n) == NX_MAUTH_OK { 171 b64[b64n[0]] = 0 as u8 172 let rb: *u8 = sys_mmap(TGW_MAGIC_1024); var ro: i64 = tg_cat(rb, 0, "{\"token\":\"" as *u8); ro = tg_catb(rb, ro, b64, b64n[0]); ro = tg_cat(rb, ro, "\"}" as *u8) 173 let hh2: *u8 = sys_mmap(TGW_MAGIC_1280); var ho: i64 = 0 174 ho = tg_cat(hh2, ho, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nSet-Cookie: ngs=" as *u8); ho = tg_catb(hh2, ho, b64, b64n[0]) 175 ho = tg_cat(hh2, ho, "; HttpOnly; Secure; SameSite=Strict; Path=/torrent; Max-Age=900\r\nContent-Length: " as *u8); ho = tg_itoa(hh2, ho, ro) 176 ho = tg_cat(hh2, ho, "\r\nConnection: close\r\n\r\n" as *u8); ho = tg_catb(hh2, ho, rb, ro) 177 sys_write(cfd, hh2, ho); sys_close(cfd) 178 } else { 179 tg_send(cfd, "401 Unauthorized" as *u8, "application/json" as *u8, "{\"error\":\"login failed\"}" as *u8, 24) 180 } 181 sys_exit(0) 182 } 183 if tau_find(path, plen, "/torrent/login" as *u8, 14) == 0 { 184 tg_send(cfd, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, TGW_LOGIN_HTML, tg_slen(TGW_LOGIN_HTML)); sys_exit(0) 185 } 186 // everything else under /torrent: resolve level, gate, then proxy with X-Nishi-Level injected. 187 let lv: i64 = tau_level_now(req, n, keys_path, store_path, roles_path, realm, realm_n) 188 if tg_gate_decision(path, plen, lv) == 0 { 189 tg_send(cfd, "401 Unauthorized" as *u8, "text/plain" as *u8, "login required\n" as *u8, 15); sys_exit(0) 190 } 191 let rng: *u8 = sys_mmap(256); let rngn: i64 = tau_hdr_val(req, n, "\r\nRange:" as *u8, 8, rng, 256) 192 var bN: i64 = 0; let bo2: i64 = tau_find(req, n, "\r\n\r\n" as *u8, 4); var bodyp: *u8 = req 193 if bo2 >= 0 { bodyp = ((req as i64) + bo2 + 4) as *u8; bN = n - (bo2 + 4) } 194 let breq: *u8 = sys_mmap(TGW_MAGIC_131072); let breqn: i64 = tg_backend_req(meth, mlen, path, plen, lv, rng, rngn, bodyp, bN, breq) 195 tg_proxy(cfd, bport, breq, breqn) 196 sys_close(cfd) 197 sys_exit(0) 198 } 199 sys_close(cfd) 200 var reap: i64 = 1 201 while reap == 1 { let w: i64 = sys_wait4(0-1, st, WNOHANG); if w <= 0 { reap = 0 } } 202 } 203 served = served + 1 204 } 205 return 0 206}