code wiki / _hdl_build / nx_email_mta_gate.nx
nx_email_mta_gate.nx source
↩ module page · 156 lines · 7512 B
1// nx_email_mta_gate.nx -- GATE for EMAIL R7 (receiving MTA, nx_email_mta).
2//
3// The INTEGRATION CAPSTONE: a full inbound SMTP session composed across
4// rungs -- R7 command parse + policy + FSM, R4 (nx_pop3_dedot) to unstuff
5// the DATA stream, R3 (nx_email_mailbox over nx_seg_store) to DELIVER,
6// then fetch the stored message back BYTE-IDENTICAL = receive->store
7// proven end to end.
8//
9// COMPLETENESS : EHLO/MAIL/RCPT/DATA/QUIT classified + correct reply
10// codes (250/250/250/354/221); DATA dot-unstuffed;
11// delivered + fetched byte-exact.
12// POLICY/SEC : RCPT for our domain accepted; RCPT to a foreign domain
13// REFUSED with 550 (open-relay defense).
14// NEG-CONTROL : a command out of sequence (RCPT before MAIL) -> 503.
15//
16// Evidence -> knowledge/status/email_mta.log (MTAGATE ... verdict=GREEN)
17// license_tier: ORIGINAL
18import "nx_email_mta.nx"
19import "nx_email_retrieve.nx"
20import "nx_email_mailbox.nx"
21import "nx_syscalls.nx"
22import "nx_gate_verdict.nx"
23
24const MTA_LOG: *u8 = "knowledge/status/email_mta.log"
25const MTA_PREFIX: *u8 = "knowledge/status/mta_test/"
26const OUR_DOMAIN: *u8 = "nishi.test"
27
28func 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 }
29func ewn(fd: i64, v: i64) -> i64 {
30 let b: *u8 = sys_mmap(28); var m: i64 = v
31 if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) }
32 let t: *u8 = sys_mmap(28); var k: i64 = 0
33 if m == 0 { t[0] = 48; k = 1 }
34 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
35 var i: i64 = 0; while i < k { b[i] = t[k - 1 - i]; i = i + 1 }
36 sys_write(fd, b, k); return 0
37}
38func epf(fd: i64, label: *u8, pass: i64) -> i64 { ew(fd, label); if pass == 1 { ew(fd, "PASS" as *u8) } else { ew(fd, "FAIL" as *u8) } return 0 }
39func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
40func 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 }
41
42func main() -> i64 {
43 var ok: i64 = 1
44 // reset store dir
45 sys_mkdir("knowledge/status/mta_test" as *u8, 511)
46 let rfd: i64 = sys_openat_wr("knowledge/status/mta_test/manifest.txt" as *u8, 420)
47 if rfd >= 0 { sys_close(rfd) }
48
49 let nxt: *i64 = sys_mmap(8) as *i64
50 let addr: *u8 = sys_mmap(256)
51 let dedot: *u8 = sys_mmap(4096)
52 let pp: *i64 = sys_mmap(8) as *i64
53 let ll: *i64 = sys_mmap(8) as *i64
54 var st: i64 = MTA_S_GREET
55
56 // ---- command classification ----
57 var cmd_parse: i64 = 1
58 if nx_mta_parse_cmd("EHLO relay.remote\r\n" as *u8, slen("EHLO relay.remote\r\n" as *u8)) != MTA_C_EHLO { cmd_parse = 0 }
59 if nx_mta_parse_cmd("mail from:<a@b>\r\n" as *u8, slen("mail from:<a@b>\r\n" as *u8)) != MTA_C_MAIL { cmd_parse = 0 }
60 if nx_mta_parse_cmd("RCPT TO:<x@y>\r\n" as *u8, slen("RCPT TO:<x@y>\r\n" as *u8)) != MTA_C_RCPT { cmd_parse = 0 }
61 if nx_mta_parse_cmd("DATA\r\n" as *u8, slen("DATA\r\n" as *u8)) != MTA_C_DATA { cmd_parse = 0 }
62 if nx_mta_parse_cmd("QUIT\r\n" as *u8, slen("QUIT\r\n" as *u8)) != MTA_C_QUIT { cmd_parse = 0 }
63 if cmd_parse != 1 { ok = 0 }
64
65 // ---- EHLO ----
66 let r_ehlo: i64 = nx_mta_reply_for(st, MTA_C_EHLO, 0, nxt); st = *nxt
67 // ---- MAIL FROM ----
68 let r_mail: i64 = nx_mta_reply_for(st, MTA_C_MAIL, 0, nxt); st = *nxt
69
70 // ---- RCPT (our domain, accept) ----
71 let rcpt1: *u8 = "RCPT TO:<bob@nishi.test>\r\n" as *u8
72 nx_mta_extract_addr(rcpt1, slen(rcpt1), addr, 256)
73 let acc1: i64 = nx_mta_rcpt_accept(addr, slen(addr), OUR_DOMAIN, slen(OUR_DOMAIN))
74 let r_rcpt: i64 = nx_mta_reply_for(st, MTA_C_RCPT, acc1, nxt); st = *nxt
75
76 // ---- RCPT (foreign domain, refuse relay) ----
77 let rcpt2: *u8 = "RCPT TO:<eve@evil.test>\r\n" as *u8
78 nx_mta_extract_addr(rcpt2, slen(rcpt2), addr, 256)
79 let acc2: i64 = nx_mta_rcpt_accept(addr, slen(addr), OUR_DOMAIN, slen(OUR_DOMAIN))
80 let r_relay: i64 = nx_mta_reply_for(st, MTA_C_RCPT, acc2, nxt); st = *nxt
81
82 // ---- DATA ----
83 let r_data: i64 = nx_mta_reply_for(st, MTA_C_DATA, 0, nxt); st = *nxt
84
85 // ---- DATA body: dot-unstuff (R4) then deliver (R3) ----
86 let raw: *u8 = "Subject: Hi Bob\r\nFrom: alice@remote.test\r\n\r\nHello Bob, this is a test.\r\n.\r\n" as *u8
87 let exp: *u8 = "Subject: Hi Bob\r\nFrom: alice@remote.test\r\n\r\nHello Bob, this is a test.\r\n" as *u8
88 let dlen: i64 = nx_pop3_dedot(raw, slen(raw), dedot, 4096)
89 var data_dedot: i64 = 0
90 if dlen == slen(exp) && memeq(dedot, exp, dlen) == 1 { data_dedot = 1 } else { ok = 0 }
91 let stored: i64 = nx_mbox_deliver(MTA_PREFIX, "bob@nishi.test" as *u8, dedot, dlen, 1)
92 var stored_ok: i64 = 0
93 if stored == 0 { stored_ok = 1 }
94 let r_delivered: i64 = nx_mta_data_delivered(stored_ok, nxt); st = *nxt
95
96 // ---- QUIT ----
97 let r_quit: i64 = nx_mta_reply_for(st, MTA_C_QUIT, 0, nxt); st = *nxt
98
99 // ---- end-to-end: fetch the stored message back byte-exact ----
100 var e2e: i64 = 0
101 if nx_mbox_fetch(MTA_PREFIX, "bob@nishi.test" as *u8, pp, ll) == 1 {
102 if ll[0] == dlen && memeq(pp[0] as *u8, exp, dlen) == 1 { e2e = 1 }
103 }
104 if e2e != 1 { ok = 0 }
105
106 // ---- NEG-CONTROL: out-of-sequence RCPT before MAIL -> 503 ----
107 let bs: i64 = nx_mta_reply_for(MTA_S_READY, MTA_C_RCPT, 1, nxt)
108 var badseq: i64 = 0
109 if bs == 503 { badseq = 1 } else { ok = 0 }
110
111 // ---- reply-code assertions ----
112 if r_ehlo != 250 { ok = 0 }
113 if r_mail != 250 { ok = 0 }
114 if r_rcpt != 250 { ok = 0 }
115 if r_relay != 550 { ok = 0 }
116 if r_data != 354 { ok = 0 }
117 if r_delivered != 250 { ok = 0 }
118 if r_quit != 221 { ok = 0 }
119 var rcpt_accept_own: i64 = 0
120 if acc1 == 1 { rcpt_accept_own = 1 }
121 var rcpt_reject_relay: i64 = 0
122 if acc2 == 0 { rcpt_reject_relay = 1 }
123
124 var fd: i64 = 1
125 while fd >= 1 {
126 ew(fd, "MTAGATE authored=organ rfc=5321-server compose=R3+R4 " as *u8)
127 epf(fd, "cmd_parse=" as *u8, cmd_parse)
128 epf(fd, " rcpt_accept_own=" as *u8, rcpt_accept_own)
129 epf(fd, " rcpt_reject_relay=" as *u8, rcpt_reject_relay)
130 ew(fd, " reply_ehlo=" as *u8); ewn(fd, r_ehlo)
131 ew(fd, " reply_mail=" as *u8); ewn(fd, r_mail)
132 ew(fd, " reply_rcpt=" as *u8); ewn(fd, r_rcpt)
133 ew(fd, " reply_relay=" as *u8); ewn(fd, r_relay)
134 ew(fd, " reply_data=" as *u8); ewn(fd, r_data)
135 ew(fd, " reply_delivered=" as *u8); ewn(fd, r_delivered)
136 ew(fd, " reply_quit=" as *u8); ewn(fd, r_quit)
137 epf(fd, " badseq_503=" as *u8, badseq)
138 epf(fd, " data_dedot=" as *u8, data_dedot)
139 epf(fd, " e2e_fetch_byteexact=" as *u8, e2e)
140 if ok == 1 { ew(fd, " verdict=GREEN\n" as *u8) } else { ew(fd, " verdict=RED\n" as *u8) }
141 if fd == 1 {
142 let lf: i64 = sys_openat_append(MTA_LOG, 420)
143 if lf >= 1 { fd = lf } else { fd = 0 }
144 } else { sys_close(fd); fd = 0 }
145 }
146
147 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
148 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
149 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
150 let ctr__dry: *i64 = gv_ctr()
151 ctr__dry[0] = ok
152 ctr__dry[1] = 1
153 let rc__dry: i64 = gv_verdict("EMAIL-MTA-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
154 sys_exit(rc__dry)
155 return rc__dry
156}