code wiki / _hdl_build / nx_smtp_probe.nx

nx_smtp_probe.nx source

↩ module page · 134 lines · 6984 B

1// nx_smtp_probe.nx -- SOVEREIGN outbound-SMTP reachability probe (zero shell, zero curl). 2// THE make-or-break fact for full self-hosted MX (EMAIL-D3 first act): can this network open 3// TCP to a real Gmail MX on port 25 and receive the "220" SMTP banner? Many ISPs silently 4// block outbound 25 while 443/587 work -- in that case no amount of MTA code will ever 5// deliver to Gmail and the operator must get the port opened (or move the mail host). 6// Battery (default): gmail-smtp-in.l.google.com:25 + aspmx.l.google.com:25 (two independent 7// Google MXes = the real test) vs smtp.gmail.com:587 + :465 (submission CONTROLS -- normally 8// never ISP-blocked; they discriminate "port-25 block" from "no outbound at all"). 9// Composes the proven substrate: nx_dns_resolve_default (sovereign DNS) + BSD sockets with 10// sys_set_socket_timeout(10s) so a blackholed port FAILS GRACEFULLY, never hangs. 11// usage: nx_smtp_probe [host port] (no args = the default battery) 12// verdict: exit 0 iff at least one :25 target returned an SMTP 220 banner (OPEN); 13// exit 1 otherwise (BLOCKED-OR-UNREACHABLE). Appends knowledge/status/smtp_probe.log. 14// license_tier: ORIGINAL 15import "nx_syscalls.nx" 16import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 17import "nx_dns_resolve_a_record.nx" 18 19func sp_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 20func sp_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 21func sp_w(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 } 22func sp_putn(v: i64) -> i64 { 23 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 24 var m: i64 = v 25 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 26 let t: *u8 = sys_mmap(24); var k: i64 = 0 27 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 28 let o: *u8 = sys_mmap(24); var w: i64 = 0; var q: i64 = k - 1 29 while q >= 0 { o[w] = t[q]; w = w + 1; q = q - 1 } 30 sys_write(1, o, w); return 0 31} 32func sp_ip(packed: i64) -> i64 { 33 sp_putn((packed >> 24) & 0xff); sp_puts("." as *u8) 34 sp_putn((packed >> 16) & 0xff); sp_puts("." as *u8) 35 sp_putn((packed >> 8) & 0xff); sp_puts("." as *u8) 36 sp_putn(packed & 0xff) 37 return 0 38} 39func sp_atoi(s: *u8) -> i64 { 40 var v: i64 = 0; var i: i64 = 0 41 while s[i] != (0 as u8) { 42 let c: i64 = s[i] as i64 43 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } 44 i = i + 1 45 } 46 return v 47} 48// IPv4 sockaddr_in (16B): family=2 LE, port BE, addr BE, zero pad (mirrors nx_https_build_sockaddr). 49func sp_sockaddr(out: *u8, packed: i64, port: i64) -> i64 { 50 out[0] = 2 as u8; out[1] = 0 as u8 51 out[2] = ((port >> 8) & 0xff) as u8 52 out[3] = (port & 0xff) as u8 53 out[4] = ((packed >> 24) & 0xff) as u8 54 out[5] = ((packed >> 16) & 0xff) as u8 55 out[6] = ((packed >> 8) & 0xff) as u8 56 out[7] = (packed & 0xff) as u8 57 var i: i64 = 8 58 while i < 16 { out[i] = 0 as u8; i = i + 1 } 59 return 16 60} 61 62// probe one host:port. prints one line. returns: 2 = SMTP 220 banner; 1 = connected, no/odd 63// banner; 0 = connect failed; -1 = DNS failed. 64func sp_probe(host: *u8, port: i64) -> i64 { 65 sp_puts(" " as *u8); sp_puts(host); sp_puts(":" as *u8); sp_putn(port); sp_puts(" -> " as *u8) 66 let now: i64 = sys_now_realtime_sec() 67 let r: *DnsResolveResult = nx_dns_resolve_default(host, sp_slen(host), now) 68 if r.verdict != NX_DNS_R_OK { sp_puts("DNS-FAIL\n" as *u8); return 0 - 1 } 69 if r.ipv4_packed == 0 { sp_puts("NO-A-RECORD\n" as *u8); return 0 - 1 } 70 sp_ip(r.ipv4_packed); sp_puts(" " as *u8) 71 let fd: i64 = sys_socket(2, 1, 0) 72 if fd < 0 { sp_puts("SOCKET-FAIL\n" as *u8); return 0 } 73 sys_set_socket_timeout(fd, 10) 74 let sa: *u8 = sys_mmap(16) 75 sp_sockaddr(sa, r.ipv4_packed, port) 76 let cr: i64 = nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) 77 if cr < 0 { sys_close(fd); sp_puts("CONNECT-FAIL(" as *u8); sp_putn(cr); sp_puts(") [blocked, filtered, or unreachable]\n" as *u8); return 0 } 78 let buf: *u8 = sys_mmap(256) 79 let rn: i64 = sys_read(fd, buf, 255) 80 var got220: i64 = 0 81 if rn >= 3 { 82 if (buf[0] as i64) == 50 { if (buf[1] as i64) == 50 { if (buf[2] as i64) == 48 { got220 = 1 } } } 83 } 84 if got220 == 1 { 85 // polite QUIT, best-effort 86 sys_write(fd, "QUIT\r\n" as *u8, 6) 87 sys_close(fd) 88 sp_puts("CONNECTED banner=" as *u8) 89 var i: i64 = 0 90 while i < rn { if (buf[i] as i64) == 13 { i = rn } else { if (buf[i] as i64) == 10 { i = rn } else { sys_write(1, ((buf as i64) + i) as *u8, 1); i = i + 1 } } } 91 sp_puts(" [SMTP 220 = PORT OPEN]\n" as *u8) 92 return 2 93 } 94 sys_close(fd) 95 if rn <= 0 { sp_puts("CONNECTED but NO BANNER (read timeout/reset -- likely a transparent proxy or filter)\n" as *u8); return 1 } 96 sp_puts("CONNECTED, non-220 first bytes\n" as *u8) 97 return 1 98} 99 100func main(argc: i64, argv: *i64) -> i64 { 101 sp_puts("=== nx_smtp_probe: outbound SMTP reachability (sovereign; 10s timeouts; vantage = THIS box's uplink) ===\n" as *u8) 102 if argc >= 3 { 103 let v: i64 = sp_probe(argv[1] as *u8, sp_atoi(argv[2] as *u8)) 104 if v == 2 { sys_exit(0); return 0 } 105 sys_exit(1); return 1 106 } 107 sp_puts("real test (Gmail MX, port 25):\n" as *u8) 108 let a: i64 = sp_probe("gmail-smtp-in.l.google.com" as *u8, 25) 109 let b: i64 = sp_probe("aspmx.l.google.com" as *u8, 25) 110 sp_puts("controls (submission ports -- normally never ISP-blocked):\n" as *u8) 111 let c: i64 = sp_probe("smtp.gmail.com" as *u8, 587) 112 let d: i64 = sp_probe("smtp.gmail.com" as *u8, 465) 113 var open25: i64 = 0 114 if a == 2 { open25 = 1 } 115 if b == 2 { open25 = 1 } 116 var ctl: i64 = 0 117 if c >= 1 { ctl = 1 } 118 if d >= 1 { ctl = 1 } 119 sp_puts("----\nSMTP-PROBE port25_open=" as *u8); sp_putn(open25) 120 sp_puts(" controls_reachable=" as *u8); sp_putn(ctl) 121 let lg: i64 = sys_openat_append("knowledge/status/smtp_probe.log" as *u8, 0x1a4) 122 if lg >= 0 { 123 sp_w(lg, "SMTPPROBE port25_open=" as *u8) 124 if open25 == 1 { sp_w(lg, "1" as *u8) } else { sp_w(lg, "0" as *u8) } 125 sp_w(lg, " controls_reachable=" as *u8) 126 if ctl == 1 { sp_w(lg, "1" as *u8) } else { sp_w(lg, "0" as *u8) } 127 if open25 == 1 { sp_w(lg, " verdict=OPEN\n" as *u8) } else { sp_w(lg, " verdict=BLOCKED-OR-UNREACHABLE\n" as *u8) } 128 sys_close(lg) 129 } 130 if open25 == 1 { sp_puts(" verdict=OPEN -- full self-hosted MX outbound is POSSIBLE from this uplink\n" as *u8); sys_exit(0); return 0 } 131 if ctl == 1 { sp_puts(" verdict=BLOCKED -- outbound 25 filtered while 587/465 work: ISP port-25 block; ask provider or move the MTA\n" as *u8); sys_exit(1); return 1 } 132 sp_puts(" verdict=NO-OUTBOUND -- even the controls failed: check network/DNS before concluding anything about port 25\n" as *u8) 133 sys_exit(1); return 1 134}