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}