code wiki / _hdl_build / nx_email_provision_lib.nx

nx_email_provision_lib.nx source

↩ module page · 79 lines · 4427 B

1// nx_email_provision_lib.nx -- DATA-DRIVEN per-domain EMAIL provisioning (operator: finish andelinwest email, and 2// "not just andelinwest but all our sites"). Generalizes the jasonewest DNS-gate fixture into a reusable provisioner 3// keyed by domain: generate (or REUSE) the domain's ed25519 DKIM keypair, derive the public key, emit the publishable 4// DNS zone (MX/SPF/DKIM/DMARC via the proven nx_dns_zone), and persist artifacts under knowledge/status/<domain>_*. 5// IDEMPOTENT (Rule 10): if a private key already exists for the domain it is REUSED, so a previously published DKIM 6// pubkey stays valid across re-provisioning. The same keypair signs outbound (nx_email_send) and the same domain 7// string drives the receiving MTA's own-domain accept (nx_email_mta). Composes nx_email_dns + ed25519 + base64. 8// license_tier: ORIGINAL 9import "nx_email_dns.nx" 10import "nx_email_auth.nx" 11import "nx_csprng.nx" 12import "nx_base64.nx" 13import "nx_syscalls.nx" 14 15func ep_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 16func ep_cat(out: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { out[o] = s[i]; o = o + 1; i = i + 1 } return o } 17 18// knowledge/status/<domain>_<suffix> -> out (null-terminated) 19func ep_path(domain: *u8, suffix: *u8, out: *u8) -> i64 { 20 var o: i64 = ep_cat(out, 0, "knowledge/status/" as *u8) 21 o = ep_cat(out, o, domain); o = ep_cat(out, o, "_" as *u8); o = ep_cat(out, o, suffix) 22 out[o] = 0 as u8 23 return o 24} 25func ep_write(path: *u8, buf: *u8, n: i64, mode: i64) -> i64 { 26 let fd: i64 = sys_openat_wr(path, mode) 27 if fd < 0 { return 0 - 1 } 28 var w: i64 = 0 29 while w < n { let k: i64 = sys_write(fd, (buf as i64 + w) as *u8, n - w); if k <= 0 { w = n } else { w = w + k } } 30 sys_close(fd) 31 return 0 32} 33// load the domain's raw 32-byte DKIM private key if present + exactly 32 bytes. 1 = loaded, 0 = absent. 34func ep_load_priv(domain: *u8, priv32: *u8) -> i64 { 35 let path: *u8 = sys_mmap(512); ep_path(domain, "dkim_priv.bin" as *u8, path) 36 let szp: *i64 = sys_mmap(16) as *i64 37 let buf: *u8 = sys_read_file(path, szp) 38 if (buf as i64) == 0 { return 0 } 39 if szp[0] != 32 { return 0 } 40 var i: i64 = 0; while i < 32 { priv32[i] = buf[i]; i = i + 1 } 41 return 1 42} 43func ep_hexbyte(out: *u8, oi: i64, b: i64) -> i64 { 44 let hi: i64 = (b >> 4) & 0xf; let lo: i64 = b & 0xf 45 if hi < 10 { out[oi] = (48 + hi) as u8 } else { out[oi] = (97 + hi - 10) as u8 } 46 if lo < 10 { out[oi + 1] = (48 + lo) as u8 } else { out[oi + 1] = (97 + lo - 10) as u8 } 47 return oi + 2 48} 49 50// PROVISION a domain's email: fill priv32/pub32 (reuse-or-generate), emit the zone into zoneout, persist artifacts: 51// <domain>_dkim_priv.bin (raw 32B secret, 0600 -- the server signs with this) 52// <domain>_dkim_priv.key (hex, human-readable secret, 0600) 53// <domain>_dns_zone.txt (publishable MX/SPF/DKIM/DMARC, 0644) 54// Returns the zone text length (zoneout is null-terminated at zlen). 55func ep_provision(domain: *u8, host: *u8, ip: *u8, selector: *u8, priv32: *u8, pub32: *u8, zoneout: *u8, zonecap: i64) -> i64 { 56 if ep_load_priv(domain, priv32) == 0 { nx_csprng_fill(priv32, 32) } // reuse if present, else fresh 57 ed25519_pub_from_priv(priv32, pub32) 58 let pub_b64: *u8 = sys_mmap(128) 59 let bl: i64 = b64_encode(pub32, 32, pub_b64) 60 pub_b64[bl] = 0 as u8 61 let zlen: i64 = nx_dns_zone(domain, host, ip, selector, pub_b64, zoneout, zonecap) 62 zoneout[zlen] = 0 as u8 63 64 // persist: raw key (secret 0600) 65 let pbin: *u8 = sys_mmap(512); ep_path(domain, "dkim_priv.bin" as *u8, pbin) 66 ep_write(pbin, priv32, 32, 0x180) 67 // human-readable hex key (secret 0600) 68 let keyf: *u8 = sys_mmap(256); var ko: i64 = 0 69 ko = ep_cat(keyf, ko, "# SECRET ed25519 DKIM private key for " as *u8); ko = ep_cat(keyf, ko, selector) 70 ko = ep_cat(keyf, ko, "._domainkey." as *u8); ko = ep_cat(keyf, ko, domain); ko = ep_cat(keyf, ko, " -- keep offline.\n# hex(32):\n" as *u8) 71 var bi: i64 = 0; while bi < 32 { ko = ep_hexbyte(keyf, ko, priv32[bi] & 0xff); bi = bi + 1 } 72 keyf[ko] = 10 as u8; ko = ko + 1 73 let pkey: *u8 = sys_mmap(512); ep_path(domain, "dkim_priv.key" as *u8, pkey) 74 ep_write(pkey, keyf, ko, 0x180) 75 // publishable zone (0644) 76 let pzone: *u8 = sys_mmap(512); ep_path(domain, "dns_zone.txt" as *u8, pzone) 77 ep_write(pzone, zoneout, zlen, 0x1a4) 78 return zlen 79}