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}