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}