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}