code wiki / _hdl_build / nx_smtp_server_gate.nx
nx_smtp_server_gate.nx source
↩ module page · 196 lines · 9334 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"
30
31const SMTPS_LOG: *u8 = "knowledge/status/smtp_server.log"
32
33func 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 }
34func ewn(fd: i64, v: i64) -> i64 {
35 let bb: *u8 = sys_mmap(28); var m: i64 = v
36 if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) }
37 let t: *u8 = sys_mmap(28); var k: i64 = 0
38 if m == 0 { t[0] = 48; k = 1 }
39 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
40 var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
41 sys_write(fd, bb, k); return 0
42}
43func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
44func 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 }
45
46func main() -> i64 {
47 var ok: i64 = 1
48 let aoff: *i64 = sys_mmap(8) as *i64
49 let alen: *i64 = sys_mmap(8) as *i64
50 let code: *i64 = sys_mmap(8) as *i64
51
52 // ---- PARSER: verbs + spans ----
53 var parse_ok: i64 = 1
54 let l_ehlo: *u8 = "EHLO relay.nishi\r\n" as *u8
55 let v_ehlo: i64 = nx_smtp_srv_parse(l_ehlo, slen(l_ehlo), aoff, alen)
56 if v_ehlo != SV_EHLO { parse_ok = 0 }
57 let dptr: *u8 = (((l_ehlo as i64) + *aoff) as *u8)
58 if memeq(dptr, "relay.nishi" as *u8, *alen) != 1 { parse_ok = 0 }
59 if *alen != slen("relay.nishi" as *u8) { parse_ok = 0 }
60
61 let l_mail: *u8 = "MAIL FROM:<a@nishi.test>\r\n" as *u8
62 let v_mail: i64 = nx_smtp_srv_parse(l_mail, slen(l_mail), aoff, alen)
63 if v_mail != SV_MAIL { parse_ok = 0 }
64 let mptr: *u8 = (((l_mail as i64) + *aoff) as *u8)
65 if memeq(mptr, "a@nishi.test" as *u8, *alen) != 1 { parse_ok = 0 }
66 if *alen != slen("a@nishi.test" as *u8) { parse_ok = 0 }
67
68 let l_rcpt: *u8 = "RCPT TO:<b@nishi.test>\r\n" as *u8
69 let v_rcpt: i64 = nx_smtp_srv_parse(l_rcpt, slen(l_rcpt), aoff, alen)
70 if v_rcpt != SV_RCPT { parse_ok = 0 }
71 let rptr: *u8 = (((l_rcpt as i64) + *aoff) as *u8)
72 if memeq(rptr, "b@nishi.test" as *u8, *alen) != 1 { parse_ok = 0 }
73
74 let l_data: *u8 = "DATA\r\n" as *u8
75 if nx_smtp_srv_parse(l_data, slen(l_data), aoff, alen) != SV_DATA { parse_ok = 0 }
76 let l_quit: *u8 = "QUIT\r\n" as *u8
77 if nx_smtp_srv_parse(l_quit, slen(l_quit), aoff, alen) != SV_QUIT { parse_ok = 0 }
78 let l_rset: *u8 = "RSET\r\n" as *u8
79 if nx_smtp_srv_parse(l_rset, slen(l_rset), aoff, alen) != SV_RSET { parse_ok = 0 }
80 let l_noop: *u8 = "NOOP\r\n" as *u8
81 if nx_smtp_srv_parse(l_noop, slen(l_noop), aoff, alen) != SV_NOOP { parse_ok = 0 }
82 if parse_ok != 1 { ok = 0 }
83
84 // ---- TAMPER: verb-delimiter guard + unknown ----
85 var tamper_ok: i64 = 1
86 let l_mbx: *u8 = "MAILBOX foo\r\n" as *u8
87 if nx_smtp_srv_parse(l_mbx, slen(l_mbx), aoff, alen) != SV_UNKNOWN { tamper_ok = 0 }
88 let l_zz: *u8 = "ZZZZ junk\r\n" as *u8
89 if nx_smtp_srv_parse(l_zz, slen(l_zz), aoff, alen) != SV_UNKNOWN { tamper_ok = 0 }
90 if tamper_ok != 1 { ok = 0 }
91
92 // ---- REPLY BUILDERS (exact bytes) ----
93 var reply_ok: i64 = 1
94 let rb: *u8 = sys_mmap(256)
95 let rl1: i64 = nx_smtp_srv_reply(rb, 256, 250, "OK" as *u8)
96 if rl1 != slen("250 OK\r\n" as *u8) { reply_ok = 0 }
97 if memeq(rb, "250 OK\r\n" as *u8, rl1) != 1 { reply_ok = 0 }
98 let rl2: i64 = nx_smtp_srv_reply(rb, 256, 354, "Start mail input; end with <CRLF>.<CRLF>" as *u8)
99 if memeq(rb, "354 Start mail input; end with <CRLF>.<CRLF>\r\n" as *u8, rl2) != 1 { reply_ok = 0 }
100 let rl3: i64 = nx_smtp_srv_reply(rb, 256, 503, "Bad sequence of commands" as *u8)
101 if memeq(rb, "503 Bad sequence of commands\r\n" as *u8, rl3) != 1 { reply_ok = 0 }
102 let rl4: i64 = nx_smtp_srv_reply(rb, 256, 220, "mail.nishi ESMTP ready" as *u8)
103 if memeq(rb, "220 mail.nishi ESMTP ready\r\n" as *u8, rl4) != 1 { reply_ok = 0 }
104 if reply_ok != 1 { ok = 0 }
105
106 // ---- UN-DOT-STUFF: round-trip + terminator ----
107 var undot_ok: i64 = 1
108 let recv: *u8 = "A\r\n..\r\n..B\r\n.\r\n" as *u8
109 let cmpl: *i64 = sys_mmap(8) as *i64
110 let dout: *u8 = sys_mmap(256)
111 let dn: i64 = nx_smtp_srv_undot(recv, slen(recv), dout, 256, cmpl)
112 let dexp: *u8 = "A\r\n.\r\n.B\r\n" as *u8
113 if dn != slen(dexp) { undot_ok = 0 }
114 if memeq(dout, dexp, dn) != 1 { undot_ok = 0 }
115 if *cmpl != 1 { undot_ok = 0 }
116 if undot_ok != 1 { ok = 0 }
117
118 // ---- TAMPER (injection): a stuffed ".." line is NOT the terminator ----
119 var inject_ok: i64 = 1
120 let recv2: *u8 = "hello\r\n..world\r\n" as *u8
121 let cmpl2: *i64 = sys_mmap(8) as *i64
122 let dout2: *u8 = sys_mmap(256)
123 let dn2: i64 = nx_smtp_srv_undot(recv2, slen(recv2), dout2, 256, cmpl2)
124 let dexp2: *u8 = "hello\r\n.world\r\n" as *u8
125 if *cmpl2 != 0 { inject_ok = 0 }
126 if dn2 != slen(dexp2) { inject_ok = 0 }
127 if memeq(dout2, dexp2, dn2) != 1 { inject_ok = 0 }
128 if inject_ok != 1 { ok = 0 }
129
130 // ---- SEQUENCE MACHINE: happy path ----
131 var happy_ok: i64 = 1
132 var st: i64 = SS_INIT
133 st = nx_smtp_srv_advance(st, SV_EHLO, code)
134 if *code != 250 { happy_ok = 0 }
135 if st != SS_GREETED { happy_ok = 0 }
136 st = nx_smtp_srv_advance(st, SV_MAIL, code)
137 if *code != 250 { happy_ok = 0 }
138 if st != SS_MAIL { happy_ok = 0 }
139 st = nx_smtp_srv_advance(st, SV_RCPT, code)
140 if *code != 250 { happy_ok = 0 }
141 if st != SS_RCPT { happy_ok = 0 }
142 st = nx_smtp_srv_advance(st, SV_RCPT, code)
143 if *code != 250 { happy_ok = 0 }
144 if st != SS_RCPT { happy_ok = 0 }
145 st = nx_smtp_srv_advance(st, SV_DATA, code)
146 if *code != 354 { happy_ok = 0 }
147 if st != SS_DATA { happy_ok = 0 }
148 st = nx_smtp_srv_data_complete(code)
149 if *code != 250 { happy_ok = 0 }
150 if st != SS_GREETED { happy_ok = 0 }
151 st = nx_smtp_srv_advance(st, SV_QUIT, code)
152 if *code != 221 { happy_ok = 0 }
153 if st != SS_QUIT { happy_ok = 0 }
154 if happy_ok != 1 { ok = 0 }
155
156 // ---- NEG-CONTROL: out-of-sequence verbs -> 503, state unchanged ----
157 var neg_ok: i64 = 1
158 let ns1: i64 = nx_smtp_srv_advance(SS_INIT, SV_MAIL, code)
159 if *code != 503 { neg_ok = 0 }
160 if ns1 != SS_INIT { neg_ok = 0 }
161 let ns2: i64 = nx_smtp_srv_advance(SS_GREETED, SV_RCPT, code)
162 if *code != 503 { neg_ok = 0 }
163 if ns2 != SS_GREETED { neg_ok = 0 }
164 let ns3: i64 = nx_smtp_srv_advance(SS_MAIL, SV_DATA, code)
165 if *code != 503 { neg_ok = 0 }
166 if ns3 != SS_MAIL { neg_ok = 0 }
167 let ns4: i64 = nx_smtp_srv_advance(SS_GREETED, SV_UNKNOWN, code)
168 if *code != 500 { neg_ok = 0 }
169 if ns4 != SS_GREETED { neg_ok = 0 }
170 if neg_ok != 1 { ok = 0 }
171
172 // ---- evidence (stdout + append to log) ----
173 var fd: i64 = 1
174 while fd >= 1 {
175 ew(fd, "SMTPSRVGATE authored=organ rfc=5321 parse_ok=" as *u8); ewn(fd, parse_ok)
176 ew(fd, " tamper_verbguard=" as *u8); ewn(fd, tamper_ok)
177 ew(fd, " reply_ok=" as *u8); ewn(fd, reply_ok)
178 ew(fd, " undot_len=" as *u8); ewn(fd, dn)
179 ew(fd, " undot_complete=" as *u8); ewn(fd, *cmpl)
180 ew(fd, " undot_ok=" as *u8); ewn(fd, undot_ok)
181 ew(fd, " inject_terminator_safe=" as *u8); ewn(fd, inject_ok)
182 ew(fd, " happy_final=" as *u8); if st == SS_QUIT { ew(fd, "QUIT" as *u8) } else { ew(fd, "NOT_QUIT" as *u8) }
183 ew(fd, " happy_ok=" as *u8); ewn(fd, happy_ok)
184 ew(fd, " neg_503_ok=" as *u8); ewn(fd, neg_ok)
185 if ok == 1 { ew(fd, " verdict=GREEN\n" as *u8) } else { ew(fd, " verdict=RED\n" as *u8) }
186 if fd == 1 {
187 let lf: i64 = sys_openat_append(SMTPS_LOG, 420)
188 if lf >= 1 { fd = lf } else { fd = 0 }
189 } else {
190 sys_close(fd); fd = 0
191 }
192 }
193
194 if ok == 1 { return 0 }
195 return 1
196}