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}