code wiki / (root) / nx_email_mta_io.nx

nx_email_mta_io.nx source

↩ module page · 165 lines · 7028 B

1// nx_email_mta_io.nx -- EMAIL LIVE LAYER: SMTP server over a real socket. 2// 3// module: nishi-core.email.mta_io 4// depends: nishi-core.email.mta, nishi-core.email.retrieve, nishi-core.email.mailbox 5// capability: CORE_EMAIL 6// 7// Crosses the pure R7 server LOGIC into a running service: read SMTP 8// command lines off a connected fd, drive the nx_email_mta FSM, collect 9// the DATA phase, de-dot-stuff it (R4 nx_pop3_dedot), and DELIVER each 10// accepted recipient's copy into the R3 mailbox store (nx_email_mailbox 11// over nx_seg_store). One function serves one connection so it drops 12// straight into an accept loop (the loopback gate forks a client at it; 13// a real daemon would loop accept()). 14// 15// This is the receive half of a working mailbox: mail addressed to a 16// local recipient (e.g. self@jasonewest.com) lands in the store, where 17// the web UI (next rung) reads it. 18// 19// license_tier: INDEPENDENT_REDERIVE 20// genealogy_id: international-research-sources/ietf/rfc_5321 21// lineage_id: nishi_email_mta_io 22// 23// nx_safety_envelope: 24// intended_use: "Serve one inbound SMTP connection: FSM + relay 25// policy + DATA collect + dedot + store delivery." 26// sil_target: SIL2 (open relay / mail loss) 27// evidence: [live_loopback_deliver, relay_refused_on_wire, 28// byte_exact_after_dedot] 29// hazard_register: [bug-tape-open-relay, bug-tape-unbounded-data] 30// residual_risk: "STARTTLS-on-25 + SIZE limit + concurrent accept 31// loop are follow-ons; this serves one conn." 32// verdict: NOT_YET_EVALUATED 33 34import "nx_syscalls.nx" 35import "nx_email_mta.nx" 36import "nx_email_retrieve.nx" 37import "nx_email_mailbox.nx" 38const MTAIO_MAGIC_4096: i64 = 4096 39 40const MTAIO_MAX_RCPT: i64 = 8 41const MTAIO_DATA_CAP: i64 = 262144 42 43// write a null-terminated string to fd (full write). 44func mtaio_send(fd: i64, s: *u8) -> i64 { 45 var n: i64 = 0 46 while s[n] != (0 as u8) { n = n + 1 } 47 var w: i64 = 0 48 while w < n { 49 let k: i64 = sys_write(fd, (s as i64 + w) as *u8, n - w) 50 if k <= 0 { return 0 - 1 } 51 w = w + k 52 } 53 return 0 54} 55 56// send the canonical reply line for a code. 57func mtaio_send_code(fd: i64, code: i64) -> i64 { 58 if code == 250 { return mtaio_send(fd, "250 OK\r\n" as *u8) } 59 if code == 221 { return mtaio_send(fd, "221 Bye\r\n" as *u8) } 60 if code == 354 { return mtaio_send(fd, "354 End data with <CR><LF>.<CR><LF>\r\n" as *u8) } 61 if code == 550 { return mtaio_send(fd, "550 5.7.1 Relay denied\r\n" as *u8) } 62 if code == 503 { return mtaio_send(fd, "503 5.5.1 Bad sequence of commands\r\n" as *u8) } 63 if code == 451 { return mtaio_send(fd, "451 4.3.0 Local delivery error\r\n" as *u8) } 64 return mtaio_send(fd, "500 5.5.2 Unknown command\r\n" as *u8) 65} 66 67// read one line (up to and including LF) into buf. Returns bytes read, 68// 0 on EOF/closed. 69func mtaio_recv_line(fd: i64, buf: *u8, cap: i64) -> i64 { 70 var i: i64 = 0 71 while i < cap - 1 { 72 let r: i64 = sys_read(fd, (buf as i64 + i) as *u8, 1) 73 if r <= 0 { return i } 74 let c: i64 = buf[i] & 0xff 75 i = i + 1 76 if c == 10 { return i } 77 } 78 return i 79} 80 81// Serve ONE inbound SMTP connection on fd. Delivers accepted recipients' 82// mail into the store at `prefix`. segid_base is the first segment id to 83// use (incremented per delivered copy). Returns the number of messages 84// delivered (>= 0). 85func nx_mta_serve_conn(fd: i64, prefix: *u8, domain: *u8, dlen: i64, segid_base: i64) -> i64 { 86 mtaio_send(fd, "220 nishi.mail ESMTP ready\r\n" as *u8) 87 let line: *u8 = sys_mmap(MTAIO_MAGIC_4096) 88 let addr: *u8 = sys_mmap(512) 89 let recip: *u8 = sys_mmap(MTAIO_MAGIC_4096) 90 let roff: *i64 = sys_mmap(8 * MTAIO_MAX_RCPT) as *i64 91 let raw: *u8 = sys_mmap(MTAIO_DATA_CAP) 92 let body: *u8 = sys_mmap(MTAIO_DATA_CAP) 93 let nxt: *i64 = sys_mmap(8) as *i64 94 var rcount: i64 = 0 95 var rcur: i64 = 0 96 var st: i64 = MTA_S_GREET 97 var segid: i64 = segid_base 98 var delivered: i64 = 0 99 var running: i64 = 1 100 while running == 1 { 101 let ll: i64 = mtaio_recv_line(fd, line, MTAIO_MAGIC_4096) 102 if ll <= 0 { 103 running = 0 104 } else { 105 let cmd: i64 = nx_mta_parse_cmd(line, ll) 106 if cmd == MTA_C_DATA && st == MTA_S_RCPT { 107 mtaio_send_code(fd, 354) 108 var rn: i64 = 0 109 var data_done: i64 = 0 110 while data_done == 0 { 111 let dl: i64 = mtaio_recv_line(fd, line, MTAIO_MAGIC_4096) 112 if dl <= 0 { 113 data_done = 1 114 } else { 115 var term: i64 = 0 116 if dl == 3 && (line[0] & 0xff) == 46 && (line[1] & 0xff) == 13 && (line[2] & 0xff) == 10 { term = 1 } 117 if dl == 2 && (line[0] & 0xff) == 46 && (line[1] & 0xff) == 10 { term = 1 } 118 if term == 1 { 119 data_done = 1 120 } else { 121 var k: i64 = 0 122 while k < dl && rn < MTAIO_DATA_CAP - 1 { raw[rn] = line[k]; rn = rn + 1; k = k + 1 } 123 } 124 } 125 } 126 let blen: i64 = nx_pop3_dedot(raw, rn, body, MTAIO_DATA_CAP) 127 var ri: i64 = 0 128 while ri < rcount { 129 if nx_mbox_append(prefix, (recip as i64 + roff[ri]) as *u8, body, blen, segid) >= 0 { delivered = delivered + 1 } 130 segid = segid + 1 131 ri = ri + 1 132 } 133 mtaio_send(fd, "250 2.0.0 OK message accepted for delivery\r\n" as *u8) 134 st = MTA_S_READY 135 rcount = 0 136 rcur = 0 137 } else { 138 if cmd == MTA_C_RCPT { 139 let alen: i64 = nx_mta_extract_addr(line, ll, addr, 512) 140 var okr: i64 = 0 141 if alen > 0 { okr = nx_mta_rcpt_accept(addr, alen, domain, dlen) } 142 let code: i64 = nx_mta_reply_for(st, cmd, okr, nxt) 143 st = *nxt 144 if okr == 1 && code == 250 { 145 if rcount < MTAIO_MAX_RCPT { 146 var k: i64 = 0 147 while k < alen { recip[rcur + k] = addr[k]; k = k + 1 } 148 recip[rcur + alen] = 0 as u8 149 roff[rcount] = rcur 150 rcur = rcur + alen + 1 151 rcount = rcount + 1 152 } 153 } 154 mtaio_send_code(fd, code) 155 } else { 156 let code2: i64 = nx_mta_reply_for(st, cmd, 0, nxt) 157 st = *nxt 158 mtaio_send_code(fd, code2) 159 if cmd == MTA_C_QUIT { running = 0 } 160 } 161 } 162 } 163 } 164 return delivered 165}