code wiki / _hdl_build / nx_cms_tls_front.nx

nx_cms_tls_front.nx source

↩ module page · 177 lines · 8203 B

1// nx_cms_tls_front.nx -- CMS C8: TLS-terminating FRONT for the CMS admin daemon (the sites-daemon 2// integration shape, scoped to one backend). Speaks the SAME stack as nx_sites_daemon_v2: TLS 1.3 3// server session (Ed25519 CV -- the andelinwest/wiki daemon path), record-framed receive, chunked 4// app_send (any response size). Each accepted connection: handshake -> read ONE request's plaintext 5// -> forward over loopback TCP to the CMS admin -> relay the full response back inside TLS -> close. 6// The admin stays loopback-only plain HTTP (single responsibility); THIS process owns the TLS 7// boundary, exactly how the NAS sites daemon would adopt a CMS backend (route a vhost to a backend 8// instead of static files). 9// HONEST CONTRACT: request plaintext is taken up to CRLFCRLF reassembly (+ same-record body bytes); 10// a body split into records AFTER the header record is not reassembled -- form posts from real 11// browsers and our gates fit one record (<16KB). Named follow-on if uploads (C11) need more. 12// argv: [1]=listen port [2]=backend port [3]=cert.der path [4]=ed25519 key path (32B) [5]=budget 13// license_tier: ORIGINAL 14import "nx_syscalls.nx" 15import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 16import "nx_csprng.nx" 17import "nx_tls13_server_session.nx" 18import "nx_tls13_server_session_run.nx" 19import "nx_tls13_server_session_app_data.nx" 20import "nx_tls13_read_record_from_fd.nx" 21import "_hdl_build/nx_tls13_app_send_fd.nx" 22 23const TF_RECCAP: i64 = 20480 24const TF_PLAINCAP: i64 = 65536 25const TF_OUTREC: i64 = 17408 26const TF_RESPCAP: i64 = 524288 27 28func tf_atoi(s: *u8) -> i64 { 29 var v: i64 = 0 30 var i: i64 = 0 31 while s[i] != (0 as u8) { 32 let c: i64 = s[i] as i64 33 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } 34 i = i + 1 35 } 36 return v 37} 38// A BARE "BIND-FAIL" IS NOT A DIAGNOSIS -- it cannot separate EADDRINUSE from EACCES, whose 39// remedies are opposite. Printing the errno is what makes the next reader's first move correct. 40func tf_ws(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 41func tf_wnum(v: i64) -> i64 { let b: *u8 = sys_mmap(32); var x: i64 = v; var nd: i64 = 1; var t: i64 = v; while t >= 10 { nd = nd + 1; t = t / 10 } var i: i64 = nd; while i > 0 { i = i - 1; b[i] = ((x % 10) + 48) as u8; x = x / 10 } sys_write(1, b, nd); return 0 } 42func tf_contains(hay: *u8, hlen: i64, needle: *u8, nlen: i64) -> i64 { 43 var i: i64 = 0 44 while i + nlen <= hlen { 45 var q: i64 = 0 46 var ok: i64 = 1 47 while q < nlen { if (hay[i+q] as i64) != (needle[q] as i64) { ok = 0; q = nlen } q = q + 1 } 48 if ok == 1 { return 1 } 49 i = i + 1 50 } 51 return 0 52} 53 54// record-framed request receive (the sites-daemon-v2 pattern) 55func tf_recv_request(s: *Tls13ServerSession, cfd: i64, rec_buf: *u8, plain: *u8, plain_cap: i64) -> i64 { 56 var total: i64 = 0 57 var recs: i64 = 0 58 while recs < 8 { 59 let rn: i64 = nx_tls13_read_record_from_fd(cfd, rec_buf, TF_RECCAP) 60 if rn <= 0 { if total > 0 { return total } return 0 - 1 } 61 let pn: i64 = nx_tls13_server_session_app_recv(s, rec_buf, rn, (plain as i64 + total) as *u8, plain_cap - total) 62 if pn <= 0 { if total > 0 { return total } return 0 - 1 } 63 total = total + pn 64 if tf_contains(plain, total, "\r\n\r\n" as *u8, 4) == 1 { return total } 65 recs = recs + 1 66 } 67 return total 68} 69 70// forward the plaintext request to the loopback backend; read its response to EOF. -1 on fail. 71func tf_backend(port: i64, req: *u8, rn: i64, resp: *u8, cap: i64) -> i64 { 72 let fd: i64 = sys_socket(2, 1, 0) 73 if fd < 0 { return 0 - 1 } 74 let addr: *u8 = sys_mmap(16) 75 addr[0] = 2 as u8; addr[1] = 0 as u8 76 addr[2] = ((port >> 8) & 0xff) as u8 77 addr[3] = (port & 0xff) as u8 78 addr[4] = 127 as u8; addr[5] = 0 as u8; addr[6] = 0 as u8; addr[7] = 1 as u8 79 var zi: i64 = 8 80 while zi < 16 { addr[zi] = 0 as u8; zi = zi + 1 } 81 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(fd); return 0 - 1 } 82 var off: i64 = 0 83 var go: i64 = 1 84 while go == 1 { 85 go = 0 86 if off < rn { 87 let w: i64 = sys_write(fd, (req + off) as *u8, rn - off) 88 if w > 0 { off = off + w; go = 1 } 89 } 90 } 91 sys_set_socket_timeout(fd, 10) 92 var total: i64 = 0 93 go = 1 94 while go == 1 { 95 go = 0 96 if total < cap { 97 let r: i64 = sys_read(fd, (resp + total) as *u8, cap - total) 98 if r > 0 { total = total + r; go = 1 } 99 } 100 } 101 sys_close(fd) 102 return total 103} 104 105func main(argc: i64, argv: *i64) -> i64 { 106 if argc < 6 { 107 sys_write(1, "usage: nx_cms_tls_front <port> <backend-port> <cert.der> <ed25519.key> <budget>\n" as *u8, 81) 108 sys_exit(2) 109 return 2 110 } 111 let port: i64 = tf_atoi(argv[1] as *u8) 112 let bport: i64 = tf_atoi(argv[2] as *u8) 113 let budget: i64 = tf_atoi(argv[5] as *u8) 114 115 // fail-fast startup (rule 20): cert + key must load 116 let clb: *i64 = (sys_mmap(8)) as *i64 117 clb[0] = 0 118 let cert_der: *u8 = sys_read_file(argv[3] as *u8, clb) 119 if (cert_der as i64) == 0 { sys_write(1, "NO-CERT\n" as *u8, 8); sys_exit(1); return 1 } 120 let cert_der_len: i64 = clb[0] 121 if cert_der_len < 100 { sys_write(1, "BAD-CERT\n" as *u8, 9); sys_exit(1); return 1 } 122 let klb: *i64 = (sys_mmap(8)) as *i64 123 klb[0] = 0 124 let priv: *u8 = sys_read_file(argv[4] as *u8, klb) 125 if (priv as i64) == 0 { sys_write(1, "NO-KEY\n" as *u8, 7); sys_exit(1); return 1 } 126 if klb[0] != 32 { sys_write(1, "BAD-KEY\n" as *u8, 8); sys_exit(1); return 1 } 127 128 // listen on loopback (gate instrument; the NAS deploy binds public like sites-daemon-v2) 129 let addr: *u8 = sys_mmap(16) 130 addr[0] = 2 as u8; addr[1] = 0 as u8 131 addr[2] = ((port >> 8) & 0xff) as u8 132 addr[3] = (port & 0xff) as u8 133 addr[4] = 127 as u8; addr[5] = 0 as u8; addr[6] = 0 as u8; addr[7] = 1 as u8 134 var zi: i64 = 8 135 while zi < 16 { addr[zi] = 0 as u8; zi = zi + 1 } 136 let lfd: i64 = sys_socket(2, 1, 0) 137 if lfd < 0 { sys_write(1, "SOCKET-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 138 let optval: *u8 = sys_mmap(4) 139 optval[0] = 1 as u8; optval[1] = 0 as u8; optval[2] = 0 as u8; optval[3] = 0 as u8 140 sys_setsockopt(lfd, 1, 2, optval, 4) 141 let brc: i64 = sys_bind(lfd, addr, 16); if brc < 0 { tf_ws("BIND-FAIL errno=" as *u8); tf_wnum(0 - brc); tf_ws(" port=" as *u8); tf_wnum(port); tf_ws(" -- 98=EADDRINUSE (a live process holds the port: nx_orphan_reap), 13=EACCES (privilege), 99=EADDRNOTAVAIL (wrong address). These have OPPOSITE remedies, which is exactly why a bare BIND-FAIL cost a full diagnostic pass.\n" as *u8); sys_exit(1); return 1 } 142 if sys_listen(lfd, 16) < 0 { sys_write(1, "LISTEN-FAIL\n" as *u8, 12); sys_exit(1); return 1 } 143 sys_write(1, "CMS-TLS-FRONT-UP\n" as *u8, 17) 144 145 let server_random: *u8 = sys_mmap(32) 146 let server_x25519_priv: *u8 = sys_mmap(32) 147 let rec_buf: *u8 = sys_mmap(TF_RECCAP) 148 let plain: *u8 = sys_mmap(TF_PLAINCAP) 149 let out_rec: *u8 = sys_mmap(TF_OUTREC) 150 let resp: *u8 = sys_mmap(TF_RESPCAP) 151 152 var served: i64 = 0 153 while served < budget { 154 let cfd: i64 = sys_accept(lfd) 155 if cfd >= 0 { 156 sys_set_socket_timeout(cfd, 10) 157 nx_csprng_fill(server_random, 32) 158 nx_csprng_fill(server_x25519_priv, 32) 159 let hs_rc: i64 = nx_tls13_server_session_run_ed25519( 160 cfd, server_random, server_x25519_priv, 161 cert_der, cert_der_len, priv) 162 if hs_rc > 0 { 163 let s: *Tls13ServerSession = hs_rc as *Tls13ServerSession 164 let plain_n: i64 = tf_recv_request(s, cfd, rec_buf, plain, TF_PLAINCAP) 165 if plain_n > 0 { 166 let rn: i64 = tf_backend(bport, plain, plain_n, resp, TF_RESPCAP) 167 if rn > 0 { nx_tls13_app_send_fd(s, resp, rn, cfd, out_rec, TF_OUTREC) } 168 } 169 } 170 sys_close(cfd) 171 served = served + 1 172 } 173 } 174 sys_write(1, "CMS-TLS-FRONT-BUDGET-DONE\n" as *u8, 26) 175 sys_exit(0) 176 return 0 177}