code wiki / (root) / nx_email_mta.nx

nx_email_mta.nx source

↩ module page · 134 lines · 5556 B

1// nx_email_mta.nx -- EMAIL RUNG R7: receiving MTA (SMTP server logic). 2// 3// module: nishi-core.email.mta 4// depends: (none -- pure RFC 5321 server-side command parse + policy + FSM) 5// capability: CORE_EMAIL 6// 7// The server end of SMTP: classify inbound commands, enforce the RCPT 8// relay policy (accept mail for OUR domain, refuse to relay for others -- 9// the open-relay defense), and drive the receive state machine emitting 10// the correct reply codes. Pure logic; the integration capstone (accept 11// loop over nx_socket, dot-unstuff the DATA via R4's nx_pop3_dedot, and 12// deliver into R3's nx_email_mailbox) is exercised end-to-end in the gate. 13// 14// license_tier: INDEPENDENT_REDERIVE 15// genealogy_id: international-research-sources/ietf/rfc_5321 16// lineage_id: nishi_email_mta_r7 17// 18// nx_safety_envelope: 19// intended_use: "Inbound SMTP command parse + RCPT relay policy + 20// server reply-code FSM. Receiving MTA substrate." 21// sil_target: SIL2 (open relay / misdelivery) 22// evidence: [cmd_classify, addr_extract, relay_refused, 23// out_of_sequence_503, reply_code_fsm] 24// hazard_register: [bug-tape-open-relay, bug-tape-smtp-state-confusion] 25// residual_risk: "Greylisting/rate-limit/SPF-at-RCPT (compose R5) 26// + live accept loop are upstream/_io concerns." 27// verdict: NOT_YET_EVALUATED 28 29// ---- inbound command enum ---- 30const MTA_C_UNKNOWN: i64 = 0 31const MTA_C_EHLO: i64 = 1 32const MTA_C_HELO: i64 = 2 33const MTA_C_MAIL: i64 = 3 34const MTA_C_RCPT: i64 = 4 35const MTA_C_DATA: i64 = 5 36const MTA_C_QUIT: i64 = 6 37const MTA_C_RSET: i64 = 7 38const MTA_C_NOOP: i64 = 8 39 40// ---- server states ---- 41const MTA_S_GREET: i64 = 0 // 220 sent, await EHLO 42const MTA_S_READY: i64 = 1 // after EHLO, await MAIL 43const MTA_S_MAIL: i64 = 2 // after MAIL, await RCPT 44const MTA_S_RCPT: i64 = 3 // >=1 RCPT accepted, await RCPT/DATA 45const MTA_S_DATA: i64 = 4 // in DATA collection 46const MTA_S_QUIT: i64 = 5 47 48func mta_upper(c: i64) -> i64 { if c >= 97 && c <= 122 { return c - 32 } return c } 49 50func mta_verb_eq(line: *u8, n: i64, v: *u8) -> i64 { 51 if n < 4 { return 0 } 52 var i: i64 = 0 53 while i < 4 { if mta_upper(line[i] & 0xff) != (v[i] & 0xff) { return 0 } i = i + 1 } 54 return 1 55} 56 57// Classify an inbound command line. 58func nx_mta_parse_cmd(line: *u8, n: i64) -> i64 { 59 if mta_verb_eq(line, n, "EHLO" as *u8) == 1 { return MTA_C_EHLO } 60 if mta_verb_eq(line, n, "HELO" as *u8) == 1 { return MTA_C_HELO } 61 if mta_verb_eq(line, n, "MAIL" as *u8) == 1 { return MTA_C_MAIL } 62 if mta_verb_eq(line, n, "RCPT" as *u8) == 1 { return MTA_C_RCPT } 63 if mta_verb_eq(line, n, "DATA" as *u8) == 1 { return MTA_C_DATA } 64 if mta_verb_eq(line, n, "QUIT" as *u8) == 1 { return MTA_C_QUIT } 65 if mta_verb_eq(line, n, "RSET" as *u8) == 1 { return MTA_C_RSET } 66 if mta_verb_eq(line, n, "NOOP" as *u8) == 1 { return MTA_C_NOOP } 67 return MTA_C_UNKNOWN 68} 69 70// Extract the address between '<' and '>' into out (null-terminated). 71// Returns address length or -1 if absent/malformed. 72func nx_mta_extract_addr(line: *u8, n: i64, out: *u8, cap: i64) -> i64 { 73 var lt: i64 = 0 - 1 74 var i: i64 = 0 75 while i < n { if (line[i] & 0xff) == 60 { lt = i; i = n } else { i = i + 1 } } 76 if lt < 0 { return 0 - 1 } 77 var oi: i64 = 0 78 var j: i64 = lt + 1 79 while j < n && (line[j] & 0xff) != 62 && oi < cap - 1 { out[oi] = line[j]; oi = oi + 1; j = j + 1 } 80 if j >= n { return 0 - 1 } 81 if (line[j] & 0xff) != 62 { return 0 - 1 } 82 out[oi] = 0 as u8 83 return oi 84} 85 86// RCPT relay policy: accept iff the recipient's domain (after the last 87// '@') matches OUR domain (case-insensitive). 1 accept / 0 refuse-relay. 88func nx_mta_rcpt_accept(addr: *u8, alen: i64, domain: *u8, dlen: i64) -> i64 { 89 var at: i64 = 0 - 1 90 var i: i64 = 0 91 while i < alen { if (addr[i] & 0xff) == 64 { at = i } i = i + 1 } 92 if at < 0 { return 0 } 93 let ds: i64 = at + 1 94 let dl: i64 = alen - ds 95 if dl != dlen { return 0 } 96 var k: i64 = 0 97 while k < dl { 98 if mta_upper(addr[ds + k] & 0xff) != mta_upper(domain[k] & 0xff) { return 0 } 99 k = k + 1 100 } 101 return 1 102} 103 104// Server FSM: given current state + parsed command (+ whether the RCPT 105// passed policy), set *out_next and return the SMTP reply code. 106func nx_mta_reply_for(state: i64, cmd: i64, rcpt_ok: i64, out_next: *i64) -> i64 { 107 if cmd == MTA_C_QUIT { *out_next = MTA_S_QUIT; return 221 } 108 if cmd == MTA_C_RSET { *out_next = MTA_S_READY; return 250 } 109 if cmd == MTA_C_NOOP { *out_next = state; return 250 } 110 if cmd == MTA_C_EHLO || cmd == MTA_C_HELO { *out_next = MTA_S_READY; return 250 } 111 if cmd == MTA_C_MAIL { 112 if state == MTA_S_READY { *out_next = MTA_S_MAIL; return 250 } 113 *out_next = state; return 503 114 } 115 if cmd == MTA_C_RCPT { 116 if state == MTA_S_MAIL || state == MTA_S_RCPT { 117 if rcpt_ok == 1 { *out_next = MTA_S_RCPT; return 250 } 118 *out_next = state; return 550 // relay denied 119 } 120 *out_next = state; return 503 121 } 122 if cmd == MTA_C_DATA { 123 if state == MTA_S_RCPT { *out_next = MTA_S_DATA; return 354 } 124 *out_next = state; return 503 125 } 126 *out_next = state; return 500 127} 128 129// Reply code after a DATA body has been received + delivered. 130func nx_mta_data_delivered(stored_ok: i64, out_next: *i64) -> i64 { 131 *out_next = MTA_S_READY 132 if stored_ok == 1 { return 250 } 133 return 451 134}