code wiki / _hdl_build / nx_email_mime_gate.nx

nx_email_mime_gate.nx source

↩ module page · 191 lines · 9190 B

1// nx_email_mime_gate.nx -- GATE for EMAIL R2 (RFC 5322 + MIME, nx_email_mime). 2// 3// COMPLETENESS : header/body split; case-insensitive header find; 4// folded-header unfolding; header append; QP encode/ 5// decode round-trip over arbitrary bytes incl 0x00 and 6// 0xFF; base64 76-col wrap round-trip (composing the 7// canonical nx_base64); multipart assemble + extract. 8// NEG-CONTROL : absent header -> NOT_FOUND; malformed QP "=ZZ" -> 9// BAD_QP; invalid base64 char -> -1. 10// TAMPER : header find is PREFIX-EXACT -- "Subject" returns the 11// real "Subject:" value, never the earlier "X-Subject:" 12// trap (header-confusion / injection defense); a "=CRLF" 13// soft line break decodes away (no spurious bytes). 14// 15// Evidence -> knowledge/status/email_mime.log 16// (MIMEGATE authored=organ ... verdict=GREEN) 17// license_tier: ORIGINAL 18import "nx_email_mime.nx" 19import "nx_syscalls.nx" 20import "nx_gate_verdict.nx" 21 22const MIME_LOG: *u8 = "knowledge/status/email_mime.log" 23 24func 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 } 25func ewn(fd: i64, v: i64) -> i64 { 26 let bb: *u8 = sys_mmap(28); var m: i64 = v 27 if m < 0 { m = 0 - m; sys_write(fd, "-" as *u8, 1) } 28 let t: *u8 = sys_mmap(28); var k: i64 = 0 29 if m == 0 { t[0] = 48; k = 1 } 30 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 31 var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } 32 sys_write(fd, bb, k); return 0 33} 34func epf(fd: i64, label: *u8, pass: i64) -> i64 { 35 ew(fd, label); if pass == 1 { ew(fd, "PASS" as *u8) } else { ew(fd, "FAIL" as *u8) } return 0 36} 37func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 38func 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 } 39func max_line(buf: *u8, n: i64) -> i64 { 40 var mx: i64 = 0; var cur: i64 = 0; var i: i64 = 0 41 while i < n { 42 if (buf[i] & 0xff) == 10 { cur = 0 } else { if (buf[i] & 0xff) != 13 { cur = cur + 1; if cur > mx { mx = cur } } } 43 i = i + 1 44 } 45 return mx 46} 47func has_crlf(buf: *u8, n: i64) -> i64 { 48 var i: i64 = 0 49 while i + 1 < n { if (buf[i] & 0xff) == 13 && (buf[i + 1] & 0xff) == 10 { return 1 } i = i + 1 } 50 return 0 51} 52 53func main() -> i64 { 54 var ok: i64 = 1 55 let bo: *i64 = sys_mmap(8) as *i64 56 let val: *u8 = sys_mmap(512) 57 let out: *u8 = sys_mmap(2048) 58 let out2: *u8 = sys_mmap(2048) 59 let scratch: *u8 = sys_mmap(2048) 60 61 // ---- headers ---- 62 let msg: *u8 = "From: a@x.test\r\nSubject: Hello There\r\nTo: b@y.test\r\n\r\nThis is the body.\r\n" as *u8 63 let mlen: i64 = slen(msg) 64 let hlen: i64 = nx_mime_split_body(msg, mlen, bo) 65 var split_ok: i64 = 0 66 if hlen > 0 { 67 let bodyp: *u8 = msg + *bo 68 let blen: i64 = mlen - *bo 69 if blen == slen("This is the body.\r\n" as *u8) && memeq(bodyp, "This is the body.\r\n" as *u8, blen) == 1 { split_ok = 1 } 70 } 71 if split_ok != 1 { ok = 0 } 72 73 let vs1: i64 = nx_mime_header_find(msg, hlen, "subject" as *u8, 7, val, 512) 74 var hdr_subject: i64 = 0 75 if vs1 >= 0 && memeq(val, "Hello There" as *u8, slen("Hello There" as *u8)) == 1 && vs1 == slen("Hello There" as *u8) { hdr_subject = 1 } else { ok = 0 } 76 77 let vf1: i64 = nx_mime_header_find(msg, hlen, "From" as *u8, 4, val, 512) 78 var hdr_from: i64 = 0 79 if vf1 >= 0 && memeq(val, "a@x.test" as *u8, slen("a@x.test" as *u8)) == 1 { hdr_from = 1 } else { ok = 0 } 80 81 let vabs: i64 = nx_mime_header_find(msg, hlen, "Bcc" as *u8, 3, val, 512) 82 var hdr_absent: i64 = 0 83 if vabs == (0 - NX_MIME_NOT_FOUND) { hdr_absent = 1 } else { ok = 0 } 84 85 // folded 86 let msgf: *u8 = "Subject: part one\r\n\tpart two\r\n\r\nbody" as *u8 87 let hlenf: i64 = nx_mime_split_body(msgf, slen(msgf), bo) 88 let vff: i64 = nx_mime_header_find(msgf, hlenf, "Subject" as *u8, 7, val, 512) 89 var hdr_folded: i64 = 0 90 if vff >= 0 && memeq(val, "part one part two" as *u8, slen("part one part two" as *u8)) == 1 && vff == slen("part one part two" as *u8) { hdr_folded = 1 } else { ok = 0 } 91 92 // prefix-exact tamper 93 let msgt: *u8 = "X-Subject: TRAP\r\nSubject: REAL\r\n\r\nb" as *u8 94 let hlent: i64 = nx_mime_split_body(msgt, slen(msgt), bo) 95 let vtt: i64 = nx_mime_header_find(msgt, hlent, "Subject" as *u8, 7, val, 512) 96 var hdr_prefix: i64 = 0 97 if vtt >= 0 && memeq(val, "REAL" as *u8, 4) == 1 && vtt == 4 { hdr_prefix = 1 } else { ok = 0 } 98 99 // header append 100 let ai: i64 = nx_mime_header_append(out, 0, "X-Mailer" as *u8, "nishi" as *u8) 101 var hdr_append: i64 = 0 102 if ai == slen("X-Mailer: nishi\r\n" as *u8) && memeq(out, "X-Mailer: nishi\r\n" as *u8, ai) == 1 { hdr_append = 1 } else { ok = 0 } 103 104 // ---- quoted-printable ---- 105 let qin: *u8 = sys_mmap(16) 106 qin[0] = 72 as u8; qin[1] = 105 as u8; qin[2] = 61 as u8; qin[3] = 0 as u8 107 qin[4] = 255 as u8; qin[5] = 32 as u8; qin[6] = 9 as u8; qin[7] = 87 as u8 // H i = \0 \xFF SP TAB W 108 let qenc: i64 = nx_mime_qp_encode(qin, 8, out, 2048) 109 let qdec: i64 = nx_mime_qp_decode(out, qenc, out2, 2048) 110 var qp_rt: i64 = 0 111 if qenc > 8 && qdec == 8 && memeq(out2, qin, 8) == 1 { qp_rt = 1 } else { ok = 0 } 112 113 let qkd: i64 = nx_mime_qp_decode("Hello=20World=21" as *u8, slen("Hello=20World=21" as *u8), out, 2048) 114 var qp_known: i64 = 0 115 if qkd == 12 && memeq(out, "Hello World!" as *u8, 12) == 1 { qp_known = 1 } else { ok = 0 } 116 117 let qsb: i64 = nx_mime_qp_decode("AB=\r\nCD" as *u8, slen("AB=\r\nCD" as *u8), out, 2048) 118 var qp_soft: i64 = 0 119 if qsb == 4 && memeq(out, "ABCD" as *u8, 4) == 1 { qp_soft = 1 } else { ok = 0 } 120 121 let qbad: i64 = nx_mime_qp_decode("=ZZ" as *u8, 3, out, 2048) 122 var qp_bad: i64 = 0 123 if qbad == (0 - NX_MIME_BAD_QP) { qp_bad = 1 } else { ok = 0 } 124 125 // ---- base64 wrap round-trip ---- 126 let bin: *u8 = sys_mmap(128) 127 var z: i64 = 0 128 while z < 60 { bin[z] = ((z * 7) & 0xff) as u8; z = z + 1 } 129 let benc: i64 = nx_mime_b64_encode_wrap(bin, 60, out, 2048) 130 let bdec: i64 = nx_mime_b64_decode_ws(out, benc, scratch, out2) 131 var b64_rt: i64 = 0 132 if bdec == 60 && memeq(out2, bin, 60) == 1 { b64_rt = 1 } else { ok = 0 } 133 let b64_wrapped: i64 = has_crlf(out, benc) 134 let b64_maxline: i64 = max_line(out, benc) 135 if b64_wrapped != 1 { ok = 0 } 136 if b64_maxline > 76 { ok = 0 } 137 138 let bbad: i64 = nx_mime_b64_decode_ws("@@@@" as *u8, 4, scratch, out2) 139 var b64_bad: i64 = 0 140 if bbad < 0 { b64_bad = 1 } else { ok = 0 } 141 142 // ---- multipart assemble + extract ---- 143 let mp: i64 = nx_mime_multipart_assemble( 144 "NXBOUND" as *u8, 7, 145 "text/plain" as *u8, 10, "PART-ONE" as *u8, 8, 146 "application/octet-stream" as *u8, 24, "PART-TWO" as *u8, 8, 147 out, 2048) 148 let pl0: i64 = nx_mime_multipart_part(out, mp, "NXBOUND" as *u8, 7, 0, out2, 2048) 149 var mp_p0: i64 = 0 150 if pl0 == 8 && memeq(out2, "PART-ONE" as *u8, 8) == 1 { mp_p0 = 1 } else { ok = 0 } 151 let pl1: i64 = nx_mime_multipart_part(out, mp, "NXBOUND" as *u8, 7, 1, out2, 2048) 152 var mp_p1: i64 = 0 153 if pl1 == 8 && memeq(out2, "PART-TWO" as *u8, 8) == 1 { mp_p1 = 1 } else { ok = 0 } 154 155 var fd: i64 = 1 156 while fd >= 1 { 157 ew(fd, "MIMEGATE authored=organ rfc=5322+2045+2046 " as *u8) 158 epf(fd, "split_body=" as *u8, split_ok) 159 epf(fd, " hdr_subject=" as *u8, hdr_subject) 160 epf(fd, " hdr_from=" as *u8, hdr_from) 161 epf(fd, " hdr_folded=" as *u8, hdr_folded) 162 epf(fd, " hdr_prefix_exact=" as *u8, hdr_prefix) 163 epf(fd, " hdr_absent_negctrl=" as *u8, hdr_absent) 164 epf(fd, " hdr_append=" as *u8, hdr_append) 165 epf(fd, " qp_roundtrip=" as *u8, qp_rt) 166 epf(fd, " qp_known=" as *u8, qp_known) 167 epf(fd, " qp_softbreak=" as *u8, qp_soft) 168 epf(fd, " qp_bad_negctrl=" as *u8, qp_bad) 169 epf(fd, " b64_roundtrip=" as *u8, b64_rt) 170 ew(fd, " b64_wrapped=" as *u8); ewn(fd, b64_wrapped) 171 ew(fd, " b64_maxline=" as *u8); ewn(fd, b64_maxline) 172 epf(fd, " b64_bad_negctrl=" as *u8, b64_bad) 173 epf(fd, " multipart_p0=" as *u8, mp_p0) 174 epf(fd, " multipart_p1=" as *u8, mp_p1) 175 if ok == 1 { ew(fd, " verdict=GREEN\n" as *u8) } else { ew(fd, " verdict=RED\n" as *u8) } 176 if fd == 1 { 177 let lf: i64 = sys_openat_append(MIME_LOG, 420) 178 if lf >= 1 { fd = lf } else { fd = 0 } 179 } else { sys_close(fd); fd = 0 } 180 } 181 182 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 183 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 184 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 185 let ctr__dry: *i64 = gv_ctr() 186 ctr__dry[0] = ok 187 ctr__dry[1] = 1 188 let rc__dry: i64 = gv_verdict("EMAIL-MIME-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 189 sys_exit(rc__dry) 190 return rc__dry 191}