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}