code wiki / _hdl_build / nx_email_submit_gate.nx
nx_email_submit_gate.nx source
↩ module page · 140 lines · 6709 B
1// nx_email_submit_gate.nx -- GATE for EMAIL R6 (submission, nx_email_submit).
2//
3// COMPLETENESS : AUTH PLAIN encodes "\0user\0pass" (verified by base64-
4// decoding the produced token); AUTH LOGIN token decodes
5// to the username; STARTTLS capability detected; the
6// submission FSM walks 220->EHLO->STARTTLS->TLS_UPGRADE->
7// EHLO2->AUTH->READY; ports 465/587 selected.
8// NEG-CONTROL : an EHLO reply without STARTTLS -> not advertised (0).
9// TAMPER/SEC : if STARTTLS is NOT offered the FSM ABORTS at EHLO --
10// credentials are never sent in cleartext (downgrade
11// defense); a 535 at AUTH -> ABORT/FAIL.
12//
13// Evidence -> knowledge/status/email_submit.log
14// (SUBMITGATE authored=organ ... verdict=GREEN)
15// license_tier: ORIGINAL
16import "nx_email_submit.nx"
17import "nx_base64.nx"
18import "nx_syscalls.nx"
19
20const SUB_LOG: *u8 = "knowledge/status/email_submit.log"
21
22func 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 }
23func ewn(fd: i64, v: i64) -> i64 {
24 let b: *u8 = sys_mmap(28); var m: i64 = v
25 if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) }
26 let t: *u8 = sys_mmap(28); var k: i64 = 0
27 if m == 0 { t[0] = 48; k = 1 }
28 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
29 var i: i64 = 0; while i < k { b[i] = t[k - 1 - i]; i = i + 1 }
30 sys_write(fd, b, k); return 0
31}
32func 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 }
33func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
34func 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 }
35
36func main() -> i64 {
37 var ok: i64 = 1
38 let out: *u8 = sys_mmap(1024)
39 let scratch: *u8 = sys_mmap(512)
40 let dec: *u8 = sys_mmap(512)
41 let oact: *i64 = sys_mmap(8) as *i64
42
43 // ---- AUTH PLAIN round-trip (decode the produced token) ----
44 let pl: i64 = nx_submit_auth_plain(out, 1024, "user" as *u8, 4, "pass" as *u8, 4, scratch)
45 // token = out[11 .. pl-2]; decode and compare to \0user\0pass (10 bytes)
46 let tlen: i64 = pl - 2 - 11
47 let dn: i64 = b64_decode(out + 11, tlen, dec)
48 let exp: *u8 = sys_mmap(16)
49 exp[0] = 0 as u8; exp[1] = 117 as u8; exp[2] = 115 as u8; exp[3] = 101 as u8; exp[4] = 114 as u8 // \0 u s e r
50 exp[5] = 0 as u8; exp[6] = 112 as u8; exp[7] = 97 as u8; exp[8] = 115 as u8; exp[9] = 115 as u8 // \0 p a s s
51 var auth_plain: i64 = 0
52 if dn == 10 && memeq(dec, exp, 10) == 1 { auth_plain = 1 } else { ok = 0 }
53 // also the command framing
54 var auth_plain_frame: i64 = 0
55 if memeq(out, "AUTH PLAIN " as *u8, 11) == 1 && (out[pl - 2] & 0xff) == 13 && (out[pl - 1] & 0xff) == 10 { auth_plain_frame = 1 } else { ok = 0 }
56
57 // ---- AUTH LOGIN token decodes to username ----
58 let ll: i64 = nx_submit_auth_login_token(out, 1024, "user" as *u8, 4)
59 let dl: i64 = b64_decode(out, ll - 2, dec)
60 var auth_login: i64 = 0
61 if dl == 4 && memeq(dec, "user" as *u8, 4) == 1 { auth_login = 1 } else { ok = 0 }
62
63 // ---- STARTTLS capability detection ----
64 let ehlo_tls: *u8 = "250-mail.nishi\r\n250-STARTTLS\r\n250 AUTH PLAIN LOGIN\r\n" as *u8
65 let ehlo_plain: *u8 = "250-mail.nishi\r\n250 AUTH PLAIN LOGIN\r\n" as *u8
66 var starttls_detect: i64 = 0
67 if nx_submit_starttls_avail(ehlo_tls, slen(ehlo_tls)) == 1 { starttls_detect = 1 } else { ok = 0 }
68 var starttls_absent: i64 = 0
69 if nx_submit_starttls_avail(ehlo_plain, slen(ehlo_plain)) == 0 { starttls_absent = 1 } else { ok = 0 }
70
71 // ---- ports ----
72 var ports_ok: i64 = 0
73 if nx_submit_port(1) == 465 && nx_submit_port(0) == 587 { ports_ok = 1 } else { ok = 0 }
74
75 // ---- FSM happy path to READY ----
76 let codes_h: *i64 = sys_mmap(8 * 8) as *i64
77 codes_h[0] = 220; codes_h[1] = 250; codes_h[2] = 220; codes_h[3] = 250; codes_h[4] = 235
78 let acts: *i64 = sys_mmap(8 * 8) as *i64
79 var st: i64 = SUB_INIT
80 var na: i64 = 0
81 var ci: i64 = 0
82 while ci < 5 && st != SUB_READY && st != SUB_FAIL {
83 st = nx_submit_advance(st, codes_h[ci], 1, 1, oact)
84 acts[na] = *oact; na = na + 1
85 ci = ci + 1
86 }
87 var happy_ok: i64 = 1
88 if na != 5 || st != SUB_READY { happy_ok = 0 }
89 if na == 5 {
90 if acts[0] != SUB_A_EHLO { happy_ok = 0 }
91 if acts[1] != SUB_A_STARTTLS { happy_ok = 0 }
92 if acts[2] != SUB_A_TLS_UPGRADE { happy_ok = 0 }
93 if acts[3] != SUB_A_AUTH { happy_ok = 0 }
94 if acts[4] != SUB_A_READY { happy_ok = 0 }
95 }
96 if happy_ok != 1 { ok = 0 }
97
98 // ---- SECURITY: no STARTTLS offered -> ABORT before sending creds ----
99 var st2: i64 = SUB_INIT
100 var last2: i64 = SUB_A_NONE
101 st2 = nx_submit_advance(st2, 220, 1, 0, oact); last2 = *oact // -> EHLO
102 st2 = nx_submit_advance(st2, 250, 1, 0, oact); last2 = *oact // EHLO 250, no starttls -> ABORT
103 var cleartext_refused: i64 = 0
104 if last2 == SUB_A_ABORT && st2 == SUB_FAIL { cleartext_refused = 1 } else { ok = 0 }
105
106 // ---- AUTH failure -> ABORT/FAIL ----
107 let codes_f: *i64 = sys_mmap(8 * 8) as *i64
108 codes_f[0] = 220; codes_f[1] = 250; codes_f[2] = 220; codes_f[3] = 250; codes_f[4] = 535
109 var st3: i64 = SUB_INIT
110 var last3: i64 = SUB_A_NONE
111 var cj: i64 = 0
112 while cj < 5 && st3 != SUB_READY && st3 != SUB_FAIL {
113 st3 = nx_submit_advance(st3, codes_f[cj], 1, 1, oact); last3 = *oact
114 cj = cj + 1
115 }
116 var authfail: i64 = 0
117 if last3 == SUB_A_ABORT && st3 == SUB_FAIL { authfail = 1 } else { ok = 0 }
118
119 var fd: i64 = 1
120 while fd >= 1 {
121 ew(fd, "SUBMITGATE authored=organ rfc=6409+4616+3207 " as *u8)
122 epf(fd, "auth_plain_roundtrip=" as *u8, auth_plain)
123 epf(fd, " auth_plain_frame=" as *u8, auth_plain_frame)
124 epf(fd, " auth_login_token=" as *u8, auth_login)
125 epf(fd, " starttls_detect=" as *u8, starttls_detect)
126 epf(fd, " starttls_absent_negctrl=" as *u8, starttls_absent)
127 epf(fd, " ports_465_587=" as *u8, ports_ok)
128 epf(fd, " fsm_happy_to_READY=" as *u8, happy_ok)
129 epf(fd, " cleartext_creds_refused=" as *u8, cleartext_refused)
130 epf(fd, " authfail_abort=" as *u8, authfail)
131 if ok == 1 { ew(fd, " verdict=GREEN\n" as *u8) } else { ew(fd, " verdict=RED\n" as *u8) }
132 if fd == 1 {
133 let lf: i64 = sys_openat_append(SUB_LOG, 420)
134 if lf >= 1 { fd = lf } else { fd = 0 }
135 } else { sys_close(fd); fd = 0 }
136 }
137
138 if ok == 1 { return 0 }
139 return 1
140}