code wiki / _hdl_build / nx_smtp_server_gate.nx
nx_smtp_server_gate.nx source
↩ module page · 204 lines · 9871 B
1// nx_smtp_server_gate.nx -- GATE for EMAIL R2 (SMTP server, nx_smtp_server).
2//
3// Drives the REAL nx_smtp_server parser + reply builders + un-dot-stuff +
4// sequence machine over scripted CLIENT transcripts (no network, fully
5// deterministic) and asserts:
6//
7// COMPLETENESS : EHLO/HELO/MAIL/RCPT/DATA/QUIT/RSET/NOOP classify with the
8// correct verb; the EHLO domain and the MAIL/RCPT <addr> spans
9// extract exactly; reply builders emit exact CRLF-framed bytes
10// for 250/354/503/220; un-dot-stuff reverses the client's
11// leading-dot stuffing AND flags the lone "." terminator
12// (complete=1); the sequence machine walks the full happy path
13// INIT->GREETED->MAIL->RCPT->RCPT->DATA->(body)->GREETED->QUIT
14// with codes 250/250/250/250/354/250/221.
15// NEG-CONTROL : MAIL before EHLO -> 503 (state unchanged); RCPT before MAIL
16// -> 503; DATA before RCPT -> 503 -- no envelope is ever
17// accepted out of sequence.
18// TAMPER : "MAILBOX ..." does NOT classify as MAIL (verb-delimiter
19// guard); "ZZZZ ..." -> UNKNOWN and advances to 500; a stuffed
20// "..world" line un-stuffs to ".world" and is NOT mistaken for
21// the end-of-DATA terminator (complete stays 0) -- the inverse
22// of nx_smtp_dot_stuff, receive-side truncation/injection
23// defense (RFC 5321 4.5.2).
24//
25// Evidence -> knowledge/status/smtp_server.log
26// (SMTPSRVGATE authored=organ ... verdict=GREEN)
27// license_tier: ORIGINAL
28import "nx_smtp_server.nx"
29import "nx_syscalls.nx"
30import "nx_gate_verdict.nx"
31
32const SMTPS_LOG: *u8 = "knowledge/status/smtp_server.log"
33
34func ew(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
35func ewn(fd: i64, v: i64) -> i64 {
36 let bb: *u8 = sys_mmap(28); var m: i64 = v
37 if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) }
38 let t: *u8 = sys_mmap(28); var k: i64 = 0
39 if m == 0 { t[0] = 48; k = 1 }
40 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
41 var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
42 sys_write(fd, bb, k); return 0
43}
44func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
45func memeq(a: *u8, b: *u8, len: i64) -> i64 { var i: i64 = 0; while i < len { if a[i] != b[i] { return 0 } i = i + 1 } return 1 }
46
47func main() -> i64 {
48 var ok: i64 = 1
49 let aoff: *i64 = sys_mmap(8) as *i64
50 let alen: *i64 = sys_mmap(8) as *i64
51 let code: *i64 = sys_mmap(8) as *i64
52
53 // ---- PARSER: verbs + spans ----
54 var parse_ok: i64 = 1
55 let l_ehlo: *u8 = "EHLO relay.nishi\r\n" as *u8
56 let v_ehlo: i64 = nx_smtp_srv_parse(l_ehlo, slen(l_ehlo), aoff, alen)
57 if v_ehlo != SV_EHLO { parse_ok = 0 }
58 let dptr: *u8 = (((l_ehlo as i64) + *aoff) as *u8)
59 if memeq(dptr, "relay.nishi" as *u8, *alen) != 1 { parse_ok = 0 }
60 if *alen != slen("relay.nishi" as *u8) { parse_ok = 0 }
61
62 let l_mail: *u8 = "MAIL FROM:<a@nishi.test>\r\n" as *u8
63 let v_mail: i64 = nx_smtp_srv_parse(l_mail, slen(l_mail), aoff, alen)
64 if v_mail != SV_MAIL { parse_ok = 0 }
65 let mptr: *u8 = (((l_mail as i64) + *aoff) as *u8)
66 if memeq(mptr, "a@nishi.test" as *u8, *alen) != 1 { parse_ok = 0 }
67 if *alen != slen("a@nishi.test" as *u8) { parse_ok = 0 }
68
69 let l_rcpt: *u8 = "RCPT TO:<b@nishi.test>\r\n" as *u8
70 let v_rcpt: i64 = nx_smtp_srv_parse(l_rcpt, slen(l_rcpt), aoff, alen)
71 if v_rcpt != SV_RCPT { parse_ok = 0 }
72 let rptr: *u8 = (((l_rcpt as i64) + *aoff) as *u8)
73 if memeq(rptr, "b@nishi.test" as *u8, *alen) != 1 { parse_ok = 0 }
74
75 let l_data: *u8 = "DATA\r\n" as *u8
76 if nx_smtp_srv_parse(l_data, slen(l_data), aoff, alen) != SV_DATA { parse_ok = 0 }
77 let l_quit: *u8 = "QUIT\r\n" as *u8
78 if nx_smtp_srv_parse(l_quit, slen(l_quit), aoff, alen) != SV_QUIT { parse_ok = 0 }
79 let l_rset: *u8 = "RSET\r\n" as *u8
80 if nx_smtp_srv_parse(l_rset, slen(l_rset), aoff, alen) != SV_RSET { parse_ok = 0 }
81 let l_noop: *u8 = "NOOP\r\n" as *u8
82 if nx_smtp_srv_parse(l_noop, slen(l_noop), aoff, alen) != SV_NOOP { parse_ok = 0 }
83 if parse_ok != 1 { ok = 0 }
84
85 // ---- TAMPER: verb-delimiter guard + unknown ----
86 var tamper_ok: i64 = 1
87 let l_mbx: *u8 = "MAILBOX foo\r\n" as *u8
88 if nx_smtp_srv_parse(l_mbx, slen(l_mbx), aoff, alen) != SV_UNKNOWN { tamper_ok = 0 }
89 let l_zz: *u8 = "ZZZZ junk\r\n" as *u8
90 if nx_smtp_srv_parse(l_zz, slen(l_zz), aoff, alen) != SV_UNKNOWN { tamper_ok = 0 }
91 if tamper_ok != 1 { ok = 0 }
92
93 // ---- REPLY BUILDERS (exact bytes) ----
94 var reply_ok: i64 = 1
95 let rb: *u8 = sys_mmap(256)
96 let rl1: i64 = nx_smtp_srv_reply(rb, 256, 250, "OK" as *u8)
97 if rl1 != slen("250 OK\r\n" as *u8) { reply_ok = 0 }
98 if memeq(rb, "250 OK\r\n" as *u8, rl1) != 1 { reply_ok = 0 }
99 let rl2: i64 = nx_smtp_srv_reply(rb, 256, 354, "Start mail input; end with <CRLF>.<CRLF>" as *u8)
100 if memeq(rb, "354 Start mail input; end with <CRLF>.<CRLF>\r\n" as *u8, rl2) != 1 { reply_ok = 0 }
101 let rl3: i64 = nx_smtp_srv_reply(rb, 256, 503, "Bad sequence of commands" as *u8)
102 if memeq(rb, "503 Bad sequence of commands\r\n" as *u8, rl3) != 1 { reply_ok = 0 }
103 let rl4: i64 = nx_smtp_srv_reply(rb, 256, 220, "mail.nishi ESMTP ready" as *u8)
104 if memeq(rb, "220 mail.nishi ESMTP ready\r\n" as *u8, rl4) != 1 { reply_ok = 0 }
105 if reply_ok != 1 { ok = 0 }
106
107 // ---- UN-DOT-STUFF: round-trip + terminator ----
108 var undot_ok: i64 = 1
109 let recv: *u8 = "A\r\n..\r\n..B\r\n.\r\n" as *u8
110 let cmpl: *i64 = sys_mmap(8) as *i64
111 let dout: *u8 = sys_mmap(256)
112 let dn: i64 = nx_smtp_srv_undot(recv, slen(recv), dout, 256, cmpl)
113 let dexp: *u8 = "A\r\n.\r\n.B\r\n" as *u8
114 if dn != slen(dexp) { undot_ok = 0 }
115 if memeq(dout, dexp, dn) != 1 { undot_ok = 0 }
116 if *cmpl != 1 { undot_ok = 0 }
117 if undot_ok != 1 { ok = 0 }
118
119 // ---- TAMPER (injection): a stuffed ".." line is NOT the terminator ----
120 var inject_ok: i64 = 1
121 let recv2: *u8 = "hello\r\n..world\r\n" as *u8
122 let cmpl2: *i64 = sys_mmap(8) as *i64
123 let dout2: *u8 = sys_mmap(256)
124 let dn2: i64 = nx_smtp_srv_undot(recv2, slen(recv2), dout2, 256, cmpl2)
125 let dexp2: *u8 = "hello\r\n.world\r\n" as *u8
126 if *cmpl2 != 0 { inject_ok = 0 }
127 if dn2 != slen(dexp2) { inject_ok = 0 }
128 if memeq(dout2, dexp2, dn2) != 1 { inject_ok = 0 }
129 if inject_ok != 1 { ok = 0 }
130
131 // ---- SEQUENCE MACHINE: happy path ----
132 var happy_ok: i64 = 1
133 var st: i64 = SS_INIT
134 st = nx_smtp_srv_advance(st, SV_EHLO, code)
135 if *code != 250 { happy_ok = 0 }
136 if st != SS_GREETED { happy_ok = 0 }
137 st = nx_smtp_srv_advance(st, SV_MAIL, code)
138 if *code != 250 { happy_ok = 0 }
139 if st != SS_MAIL { happy_ok = 0 }
140 st = nx_smtp_srv_advance(st, SV_RCPT, code)
141 if *code != 250 { happy_ok = 0 }
142 if st != SS_RCPT { happy_ok = 0 }
143 st = nx_smtp_srv_advance(st, SV_RCPT, code)
144 if *code != 250 { happy_ok = 0 }
145 if st != SS_RCPT { happy_ok = 0 }
146 st = nx_smtp_srv_advance(st, SV_DATA, code)
147 if *code != 354 { happy_ok = 0 }
148 if st != SS_DATA { happy_ok = 0 }
149 st = nx_smtp_srv_data_complete(code)
150 if *code != 250 { happy_ok = 0 }
151 if st != SS_GREETED { happy_ok = 0 }
152 st = nx_smtp_srv_advance(st, SV_QUIT, code)
153 if *code != 221 { happy_ok = 0 }
154 if st != SS_QUIT { happy_ok = 0 }
155 if happy_ok != 1 { ok = 0 }
156
157 // ---- NEG-CONTROL: out-of-sequence verbs -> 503, state unchanged ----
158 var neg_ok: i64 = 1
159 let ns1: i64 = nx_smtp_srv_advance(SS_INIT, SV_MAIL, code)
160 if *code != 503 { neg_ok = 0 }
161 if ns1 != SS_INIT { neg_ok = 0 }
162 let ns2: i64 = nx_smtp_srv_advance(SS_GREETED, SV_RCPT, code)
163 if *code != 503 { neg_ok = 0 }
164 if ns2 != SS_GREETED { neg_ok = 0 }
165 let ns3: i64 = nx_smtp_srv_advance(SS_MAIL, SV_DATA, code)
166 if *code != 503 { neg_ok = 0 }
167 if ns3 != SS_MAIL { neg_ok = 0 }
168 let ns4: i64 = nx_smtp_srv_advance(SS_GREETED, SV_UNKNOWN, code)
169 if *code != 500 { neg_ok = 0 }
170 if ns4 != SS_GREETED { neg_ok = 0 }
171 if neg_ok != 1 { ok = 0 }
172
173 // ---- evidence (stdout + append to log) ----
174 var fd: i64 = 1
175 while fd >= 1 {
176 ew(fd, "SMTPSRVGATE authored=organ rfc=5321 parse_ok=" as *u8); ewn(fd, parse_ok)
177 ew(fd, " tamper_verbguard=" as *u8); ewn(fd, tamper_ok)
178 ew(fd, " reply_ok=" as *u8); ewn(fd, reply_ok)
179 ew(fd, " undot_len=" as *u8); ewn(fd, dn)
180 ew(fd, " undot_complete=" as *u8); ewn(fd, *cmpl)
181 ew(fd, " undot_ok=" as *u8); ewn(fd, undot_ok)
182 ew(fd, " inject_terminator_safe=" as *u8); ewn(fd, inject_ok)
183 ew(fd, " happy_final=" as *u8); if st == SS_QUIT { ew(fd, "QUIT" as *u8) } else { ew(fd, "NOT_QUIT" as *u8) }
184 ew(fd, " happy_ok=" as *u8); ewn(fd, happy_ok)
185 ew(fd, " neg_503_ok=" as *u8); ewn(fd, neg_ok)
186 if ok == 1 { ew(fd, " verdict=GREEN\n" as *u8) } else { ew(fd, " verdict=RED\n" as *u8) }
187 if fd == 1 {
188 let lf: i64 = sys_openat_append(SMTPS_LOG, 420)
189 if lf >= 1 { fd = lf } else { fd = 0 }
190 } else {
191 sys_close(fd); fd = 0
192 }
193 }
194
195 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
196 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
197 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
198 let ctr__dry: *i64 = gv_ctr()
199 ctr__dry[0] = ok
200 ctr__dry[1] = 1
201 let rc__dry: i64 = gv_verdict("SMTP-SERVER-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
202 sys_exit(rc__dry)
203 return rc__dry
204}