code wiki / _hdl_build / nx_email_submit_gate.nx
nx_email_submit_gate.nx source
↩ module page · 148 lines · 7247 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"
19import "nx_gate_verdict.nx"
20
21const SUB_LOG: *u8 = "knowledge/status/email_submit.log"
22
23func 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 }
24func ewn(fd: i64, v: i64) -> i64 {
25 let b: *u8 = sys_mmap(28); var m: i64 = v
26 if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) }
27 let t: *u8 = sys_mmap(28); var k: i64 = 0
28 if m == 0 { t[0] = 48; k = 1 }
29 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
30 var i: i64 = 0; while i < k { b[i] = t[k - 1 - i]; i = i + 1 }
31 sys_write(fd, b, k); return 0
32}
33func 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 }
34func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
35func 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 }
36
37func main() -> i64 {
38 var ok: i64 = 1
39 let out: *u8 = sys_mmap(1024)
40 let scratch: *u8 = sys_mmap(512)
41 let dec: *u8 = sys_mmap(512)
42 let oact: *i64 = sys_mmap(8) as *i64
43
44 // ---- AUTH PLAIN round-trip (decode the produced token) ----
45 let pl: i64 = nx_submit_auth_plain(out, 1024, "user" as *u8, 4, "pass" as *u8, 4, scratch)
46 // token = out[11 .. pl-2]; decode and compare to \0user\0pass (10 bytes)
47 let tlen: i64 = pl - 2 - 11
48 let dn: i64 = b64_decode(out + 11, tlen, dec)
49 let exp: *u8 = sys_mmap(16)
50 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
51 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
52 var auth_plain: i64 = 0
53 if dn == 10 && memeq(dec, exp, 10) == 1 { auth_plain = 1 } else { ok = 0 }
54 // also the command framing
55 var auth_plain_frame: i64 = 0
56 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 }
57
58 // ---- AUTH LOGIN token decodes to username ----
59 let ll: i64 = nx_submit_auth_login_token(out, 1024, "user" as *u8, 4)
60 let dl: i64 = b64_decode(out, ll - 2, dec)
61 var auth_login: i64 = 0
62 if dl == 4 && memeq(dec, "user" as *u8, 4) == 1 { auth_login = 1 } else { ok = 0 }
63
64 // ---- STARTTLS capability detection ----
65 let ehlo_tls: *u8 = "250-mail.nishi\r\n250-STARTTLS\r\n250 AUTH PLAIN LOGIN\r\n" as *u8
66 let ehlo_plain: *u8 = "250-mail.nishi\r\n250 AUTH PLAIN LOGIN\r\n" as *u8
67 var starttls_detect: i64 = 0
68 if nx_submit_starttls_avail(ehlo_tls, slen(ehlo_tls)) == 1 { starttls_detect = 1 } else { ok = 0 }
69 var starttls_absent: i64 = 0
70 if nx_submit_starttls_avail(ehlo_plain, slen(ehlo_plain)) == 0 { starttls_absent = 1 } else { ok = 0 }
71
72 // ---- ports ----
73 var ports_ok: i64 = 0
74 if nx_submit_port(1) == 465 && nx_submit_port(0) == 587 { ports_ok = 1 } else { ok = 0 }
75
76 // ---- FSM happy path to READY ----
77 let codes_h: *i64 = sys_mmap(8 * 8) as *i64
78 codes_h[0] = 220; codes_h[1] = 250; codes_h[2] = 220; codes_h[3] = 250; codes_h[4] = 235
79 let acts: *i64 = sys_mmap(8 * 8) as *i64
80 var st: i64 = SUB_INIT
81 var na: i64 = 0
82 var ci: i64 = 0
83 while ci < 5 && st != SUB_READY && st != SUB_FAIL {
84 st = nx_submit_advance(st, codes_h[ci], 1, 1, oact)
85 acts[na] = *oact; na = na + 1
86 ci = ci + 1
87 }
88 var happy_ok: i64 = 1
89 if na != 5 || st != SUB_READY { happy_ok = 0 }
90 if na == 5 {
91 if acts[0] != SUB_A_EHLO { happy_ok = 0 }
92 if acts[1] != SUB_A_STARTTLS { happy_ok = 0 }
93 if acts[2] != SUB_A_TLS_UPGRADE { happy_ok = 0 }
94 if acts[3] != SUB_A_AUTH { happy_ok = 0 }
95 if acts[4] != SUB_A_READY { happy_ok = 0 }
96 }
97 if happy_ok != 1 { ok = 0 }
98
99 // ---- SECURITY: no STARTTLS offered -> ABORT before sending creds ----
100 var st2: i64 = SUB_INIT
101 var last2: i64 = SUB_A_NONE
102 st2 = nx_submit_advance(st2, 220, 1, 0, oact); last2 = *oact // -> EHLO
103 st2 = nx_submit_advance(st2, 250, 1, 0, oact); last2 = *oact // EHLO 250, no starttls -> ABORT
104 var cleartext_refused: i64 = 0
105 if last2 == SUB_A_ABORT && st2 == SUB_FAIL { cleartext_refused = 1 } else { ok = 0 }
106
107 // ---- AUTH failure -> ABORT/FAIL ----
108 let codes_f: *i64 = sys_mmap(8 * 8) as *i64
109 codes_f[0] = 220; codes_f[1] = 250; codes_f[2] = 220; codes_f[3] = 250; codes_f[4] = 535
110 var st3: i64 = SUB_INIT
111 var last3: i64 = SUB_A_NONE
112 var cj: i64 = 0
113 while cj < 5 && st3 != SUB_READY && st3 != SUB_FAIL {
114 st3 = nx_submit_advance(st3, codes_f[cj], 1, 1, oact); last3 = *oact
115 cj = cj + 1
116 }
117 var authfail: i64 = 0
118 if last3 == SUB_A_ABORT && st3 == SUB_FAIL { authfail = 1 } else { ok = 0 }
119
120 var fd: i64 = 1
121 while fd >= 1 {
122 ew(fd, "SUBMITGATE authored=organ rfc=6409+4616+3207 " as *u8)
123 epf(fd, "auth_plain_roundtrip=" as *u8, auth_plain)
124 epf(fd, " auth_plain_frame=" as *u8, auth_plain_frame)
125 epf(fd, " auth_login_token=" as *u8, auth_login)
126 epf(fd, " starttls_detect=" as *u8, starttls_detect)
127 epf(fd, " starttls_absent_negctrl=" as *u8, starttls_absent)
128 epf(fd, " ports_465_587=" as *u8, ports_ok)
129 epf(fd, " fsm_happy_to_READY=" as *u8, happy_ok)
130 epf(fd, " cleartext_creds_refused=" as *u8, cleartext_refused)
131 epf(fd, " authfail_abort=" as *u8, authfail)
132 if ok == 1 { ew(fd, " verdict=GREEN\n" as *u8) } else { ew(fd, " verdict=RED\n" as *u8) }
133 if fd == 1 {
134 let lf: i64 = sys_openat_append(SUB_LOG, 420)
135 if lf >= 1 { fd = lf } else { fd = 0 }
136 } else { sys_close(fd); fd = 0 }
137 }
138
139 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
140 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
141 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
142 let ctr__dry: *i64 = gv_ctr()
143 ctr__dry[0] = ok
144 ctr__dry[1] = 1
145 let rc__dry: i64 = gv_verdict("EMAIL-SUBMIT-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
146 sys_exit(rc__dry)
147 return rc__dry
148}