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}