code wiki / (root) / nx_porkbun_domain.nx

nx_porkbun_domain.nx source

↩ module page · 320 lines · 16765 B

1// nx_porkbun_domain.nx -- Porkbun DOMAIN lifecycle client: pricing / availability / SSL-bundle 2// retrieve / REGISTER (purchase). Completes the existing Porkbun family (nx_acme_porkbun = DNS 3// records; nx_acme_issue = ACME cert issuance; this = the domain PURCHASE + golive side). 4// Composes nx_acme_http's nx_acme_req transport (TLS 1.3 with the proven 1.2 fallback that 5// api.porkbun.com needs) + nx_acme_porkbun's pk_* builders/checkers. Creds come from the sovereign 6// vault decrypt output (line1=apikey, line2=secretapikey; NEVER printed, NEVER hardcoded). 7// 8// SPEND SAFETY BY CONSTRUCTION (the register path): 9// R1 the endpoint must be VERIFIED against banked documentation bytes on disk 10// (knowledge/library/pb_api_docs.txt must contain the endpoint path) -- an unverified 11// spend endpoint REFUSES before any network I/O (assumption-free APIs, rule 4). 12// R2 creds file must load (both lines nonempty). 13// R3 the caller must pass the LITERAL token confirm-spend (no default-yes anywhere). 14// R4 a price quote (cents) and a spend CAP (cents) are required; quote>cap REFUSES. 15// R5 only after R1-R4 does the request go out; the raw response is printed for the operator. 16// CLI modes: 17// docs -- bank the API documentation page (GET over the 1.2-capable 18// transport) -> knowledge/library/pb_api_docs.txt 19// pricing -- keyless POST /pricing/get (public price list) 20// ping <credfile> -- authed POST /ping (read-only; prints yourIp body) 21// check <domain> <credfile> -- POST /domain/checkDomain/<domain> (availability+price) 22// ssl <domain> <credfile> <outfile> -- POST /ssl/retrieve/<domain>; bundle SAVED 0600, never printed 23// register <domain> <credfile> <price_cents> <cap_cents> confirm-spend 24// license_tier: ORIGINAL 25import "nx_acme_porkbun.nx" 26import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 27const PBD_MAGIC_4194304: i64 = 4194304 28const PBD_MAGIC_2048: i64 = 2048 29const PBD_MAGIC_1200: i64 = 1200 30const PBD_MAGIC_1048576: i64 = 1048576 31 32const PBD_RESP_CAP: i64 = 262144 33const PBD_DOCS_PATH: *u8 = "knowledge/library/pb_api_docs.txt\x00" 34const PBD_SPEC_PATH: *u8 = "knowledge/library/pb_api_spec.txt\x00" 35 36func pbd_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 37// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 38// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 39// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 40// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 41func pbd_n(v: i64) -> i64 { nxi_out(v); return 0 } 42func pbd_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 43func pbd_streq(a: *u8, b: *u8) -> i64 { 44 var i: i64=0 45 while a[i]!=(0 as u8) { if a[i]!=b[i] { return 0 } i=i+1 } 46 if b[i]!=(0 as u8) { return 0 } 47 return 1 48} 49// substring scan (bounded) 50func pbd_find(buf: *u8, n: i64, needle: *u8) -> i64 { 51 let m: i64 = pbd_slen(needle) 52 if m==0 { return 0 } 53 var i: i64=0 54 while i+m<=n { 55 var j: i64=0 56 var hit: i64=1 57 while j<m { if buf[i+j]!=needle[j] { hit=0; break } j=j+1 } 58 if hit==1 { return 1 } 59 i=i+1 60 } 61 return 0 62} 63// bounded whole-file read 64func pbd_read_file(path: *u8, buf: *u8, cap: i64) -> i64 { 65 let fd: i64 = sys_openat_rd(path) 66 if fd<0 { return 0-1 } 67 var total: i64=0 68 while total<cap { 69 let r: i64 = sys_read(fd, (buf as i64 + total) as *u8, cap-total) 70 if r<=0 { break } 71 total=total+r 72 } 73 sys_close(fd) 74 return total 75} 76// load the trust store from the nxc2-relative certdata (matches nx_porkbun_ping_probe) 77func pbd_store() -> *TrustStore { 78 let lr: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt\x00" as *u8, 300, PBD_MAGIC_4194304) 79 if lr<=0 { return 0 as *TrustStore } 80 return lr as *TrustStore 81} 82// load creds: line1=apikey line2=secretapikey; strips CR. returns 0 ok / -1 fail. NEVER prints values. 83func pbd_creds(path: *u8, ak_off: *i64, ak_len: *i64, sk_off: *i64, sk_len: *i64, raw: *u8) -> i64 { 84 let rawn: i64 = pbd_read_file(path, raw, PBD_MAGIC_2048) 85 if rawn<=0 { return 0-1 } 86 var nl: i64=0 87 while nl<rawn { if raw[nl]==0x0a { break } nl=nl+1 } 88 var akn: i64=nl 89 if akn>0 { if raw[akn-1]==0x0d { akn=akn-1 } } 90 let s2: i64=nl+1 91 var nl2: i64=s2 92 while nl2<rawn { if raw[nl2]==0x0a { break } nl2=nl2+1 } 93 var skn: i64=nl2-s2 94 if skn>0 { if raw[s2+skn-1]==0x0d { skn=skn-1 } } 95 if akn<=0 { return 0-1 } 96 if skn<=0 { return 0-1 } 97 ak_off[0]=0 98 ak_len[0]=akn 99 sk_off[0]=s2 100 sk_len[0]=skn 101 return 0 102} 103// print HTTP status + body of a parsed response (public-data modes only) 104func pbd_show(resp: *u8, total: i64, maxbody: i64) -> i64 { 105 let r: *i64 = nx_http_resp_alloc() 106 if nx_http_response_parse(resp, total, r)!=0 { 107 pbd_w("(unparseable response; total=" as *u8); pbd_n(total) 108 pbd_w(" first-bytes:)\n" as *u8) 109 var dn: i64 = total 110 if dn>160 { dn=160 } 111 if dn>0 { sys_write(1, resp, dn) } 112 pbd_w("\n" as *u8) 113 return 0-1 114 } 115 pbd_w("HTTP " as *u8); pbd_n(r[1]) 116 var bn: i64 = total - r[6] 117 pbd_w(" body " as *u8); pbd_n(bn); pbd_w(" bytes\n" as *u8) 118 if bn>maxbody { bn=maxbody } 119 sys_write(1, (resp as i64 + r[6]) as *u8, bn) 120 pbd_w("\n" as *u8) 121 return r[1] 122} 123 124// ---- endpoints (paths as data; VERIFIED against banked docs for the spend path) ---- 125// POST /pricing/get -- keyless public price list 126func pbd_pricing(store: *TrustStore, now: i64, resp: *u8, rn: *i64) -> i64 { 127 let url: *u8 = "https://api.porkbun.com/api/json/v3/pricing/get\x00" as *u8 128 // the banked spec's own curl example posts an empty ARRAY body for this keyless endpoint 129 let body: *u8 = "[]" as *u8 130 return nx_acme_req(url, 47, 1, "application/json" as *u8, 16, body, 2, store, now, resp, PBD_RESP_CAP, rn) 131} 132// POST /ping -- authed read-only echo (proves creds; response = yourIp) 133func pbd_ping(raw: *u8, ao: i64, an: i64, so: i64, sn2: i64, store: *TrustStore, now: i64, resp: *u8, rn: *i64) -> i64 { 134 let url: *u8 = "https://api.porkbun.com/api/json/v3/ping\x00" as *u8 135 let body: *u8 = sys_mmap(PBD_MAGIC_2048) 136 var bo: i64 = pk_creds(body, 0, (raw as i64 + ao) as *u8, an, (raw as i64 + so) as *u8, sn2) 137 bo = pk_put(body, bo, "}" as *u8) 138 return nx_acme_req(url, 40, 1, "application/json" as *u8, 16, body, bo, store, now, resp, PBD_RESP_CAP, rn) 139} 140// POST /domain/checkDomain/<domain> -- availability + first-year pricing 141func pbd_check(raw: *u8, ao: i64, an: i64, so: i64, sn2: i64, dom: *u8, store: *TrustStore, now: i64, resp: *u8, rn: *i64) -> i64 { 142 let url: *u8 = sys_mmap(512) 143 var uo: i64 = pk_put(url, 0, "https://api.porkbun.com/api/json/v3/domain/checkDomain/" as *u8) 144 uo = pk_put(url, uo, dom) 145 url[uo]=0 as u8 146 let body: *u8 = sys_mmap(PBD_MAGIC_2048) 147 var bo: i64 = pk_creds(body, 0, (raw as i64 + ao) as *u8, an, (raw as i64 + so) as *u8, sn2) 148 bo = pk_put(body, bo, "}" as *u8) 149 return nx_acme_req(url, uo, 1, "application/json" as *u8, 16, body, bo, store, now, resp, PBD_RESP_CAP, rn) 150} 151// POST /ssl/retrieve/<domain> -- cert bundle (PRIVATE KEY inside: saved 0600, never printed) 152func pbd_ssl(raw: *u8, ao: i64, an: i64, so: i64, sn2: i64, dom: *u8, store: *TrustStore, now: i64, resp: *u8, rn: *i64) -> i64 { 153 let url: *u8 = sys_mmap(512) 154 var uo: i64 = pk_put(url, 0, "https://api.porkbun.com/api/json/v3/ssl/retrieve/" as *u8) 155 uo = pk_put(url, uo, dom) 156 url[uo]=0 as u8 157 let body: *u8 = sys_mmap(PBD_MAGIC_2048) 158 var bo: i64 = pk_creds(body, 0, (raw as i64 + ao) as *u8, an, (raw as i64 + so) as *u8, sn2) 159 bo = pk_put(body, bo, "}" as *u8) 160 return nx_acme_req(url, uo, 1, "application/json" as *u8, 16, body, bo, store, now, resp, PBD_RESP_CAP, rn) 161} 162// ---- the SPEND WALL: every check must pass before any network I/O. codes: 163// -10 endpoint unverified in banked docs; -11 creds missing; -12 confirm token absent; 164// -13 bad quote/cap; -14 quote exceeds cap; 0 = wall passed. 165func pbd_register_wall(docs_ok: i64, creds_ok: i64, confirm_ok: i64, quote_cents: i64, cap_cents: i64) -> i64 { 166 if docs_ok!=1 { return 0-10 } 167 if creds_ok!=1 { return 0-11 } 168 if confirm_ok!=1 { return 0-12 } 169 if quote_cents<=0 { return 0-13 } 170 if cap_cents<=0 { return 0-13 } 171 if quote_cents>cap_cents { return 0-14 } 172 return 0 173} 174// docs verification: the banked MACHINE-READABLE SPEC (porkbun.com/api/json/v3/spec, fetched live 175// over the sovereign transport) must name the create endpoint. Raw spec bytes JSON-escape slashes, 176// so the needle matches the escaped form domain\/create\/{domain}. 177func pbd_docs_has_create(buf: *u8, cap: i64) -> i64 { 178 let n: i64 = pbd_read_file(PBD_SPEC_PATH, buf, cap) 179 if n<=0 { return 0 } 180 return pbd_find(buf, n, "domain\\/create\\/{domain}" as *u8) 181} 182// append integer decimal digits into a body buffer 183func pbd_putn(dst: *u8, off: i64, v: i64) -> i64 { 184 let t: *u8=sys_mmap(28) 185 var m: i64=v 186 var k: i64=0 187 if m<=0 { dst[off]=48 as u8; return off+1 } 188 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 189 var o: i64=off 190 var i: i64=0 191 while i<k { dst[o]=t[k-1-i]; o=o+1; i=i+1 } 192 return o 193} 194 195func main(argc: i64, argv: *i64) -> i64 { 196 if argc<2 { 197 pbd_w("usage: nx_porkbun_domain docs|pricing|ping <credfile>|check <domain> <credfile>|ssl <domain> <credfile> <outfile>|register <domain> <credfile> <price_cents> <cap_cents> confirm-spend\n" as *u8) 198 return 1 199 } 200 let mode: *u8 = argv[1] as *u8 201 let store: *TrustStore = pbd_store() 202 if (store as i64)==0 { pbd_w("trust store load FAIL (run from nxc2; needs data/mozilla_certdata.txt)\n" as *u8); return 1 } 203 let now: i64 = sys_now_realtime_sec() 204 let resp: *u8 = sys_mmap(PBD_RESP_CAP) 205 let rn: *i64 = sys_mmap(16) as *i64 206 207 if pbd_streq(mode, "docs" as *u8)==1 { 208 var url: *u8 = "https://porkbun.com/api/json/v3/documentation\x00" as *u8 209 var outp: *u8 = PBD_DOCS_PATH 210 if argc>=3 { url = argv[2] as *u8 } 211 if argc>=4 { outp = argv[3] as *u8 } 212 let rc: i64 = nx_acme_req(url, pbd_slen(url), 0, 0 as *u8, 0, 0 as *u8, 0, store, now, resp, PBD_RESP_CAP, rn) 213 if rc!=NX_ACME_HTTP_OK { pbd_w("docs fetch FAIL rc=" as *u8); pbd_n(rc); pbd_w("\n" as *u8); return 2 } 214 let fd: i64 = sys_openat_wr(outp, 0x1a4) 215 if fd<0 { pbd_w("docs save FAIL\n" as *u8); return 2 } 216 sys_write(fd, resp, rn[0]) 217 sys_close(fd) 218 pbd_w("docs banked: " as *u8); pbd_n(rn[0]); pbd_w(" bytes -> " as *u8); pbd_w(outp); pbd_w("\n" as *u8) 219 return 0 220 } 221 if pbd_streq(mode, "pricing" as *u8)==1 { 222 let rc: i64 = pbd_pricing(store, now, resp, rn) 223 if rc!=NX_ACME_HTTP_OK { pbd_w("pricing FAIL rc=" as *u8); pbd_n(rc); pbd_w("\n" as *u8); return 2 } 224 pbd_show(resp, rn[0], 400) 225 return 0 226 } 227 let raw: *u8 = sys_mmap(PBD_MAGIC_2048) 228 let ao: *i64 = sys_mmap(8) as *i64 229 let an: *i64 = sys_mmap(8) as *i64 230 let so: *i64 = sys_mmap(8) as *i64 231 let sn2: *i64 = sys_mmap(8) as *i64 232 if pbd_streq(mode, "ping" as *u8)==1 { 233 if argc<3 { pbd_w("ping needs <credfile>\n" as *u8); return 1 } 234 if pbd_creds(argv[2] as *u8, ao, an, so, sn2, raw)!=0 { pbd_w("creds load FAIL\n" as *u8); return 2 } 235 pbd_w("creds loaded (lens " as *u8); pbd_n(an[0]); pbd_w("/" as *u8); pbd_n(sn2[0]); pbd_w("); pinging...\n" as *u8) 236 let rc: i64 = pbd_ping(raw, ao[0], an[0], so[0], sn2[0], store, now, resp, rn) 237 if rc!=NX_ACME_HTTP_OK { pbd_w("ping FAIL rc=" as *u8); pbd_n(rc); pbd_w("\n" as *u8); return 2 } 238 pbd_show(resp, rn[0], 300) 239 return 0 240 } 241 if pbd_streq(mode, "check" as *u8)==1 { 242 if argc<4 { pbd_w("check needs <domain> <credfile>\n" as *u8); return 1 } 243 if pbd_creds(argv[3] as *u8, ao, an, so, sn2, raw)!=0 { pbd_w("creds load FAIL\n" as *u8); return 2 } 244 let rc: i64 = pbd_check(raw, ao[0], an[0], so[0], sn2[0], argv[2] as *u8, store, now, resp, rn) 245 if rc!=NX_ACME_HTTP_OK { pbd_w("check FAIL rc=" as *u8); pbd_n(rc); pbd_w("\n" as *u8); return 2 } 246 pbd_show(resp, rn[0], PBD_MAGIC_1200) 247 return 0 248 } 249 if pbd_streq(mode, "ssl" as *u8)==1 { 250 if argc<5 { pbd_w("ssl needs <domain> <credfile> <outfile>\n" as *u8); return 1 } 251 if pbd_creds(argv[3] as *u8, ao, an, so, sn2, raw)!=0 { pbd_w("creds load FAIL\n" as *u8); return 2 } 252 let rc: i64 = pbd_ssl(raw, ao[0], an[0], so[0], sn2[0], argv[2] as *u8, store, now, resp, rn) 253 if rc!=NX_ACME_HTTP_OK { pbd_w("ssl FAIL rc=" as *u8); pbd_n(rc); pbd_w("\n" as *u8); return 2 } 254 // bundle contains the PRIVATE KEY -> save 0600, print ONLY status + byte count 255 let r: *i64 = nx_http_resp_alloc() 256 if nx_http_response_parse(resp, rn[0], r)!=0 { pbd_w("ssl parse FAIL\n" as *u8); return 2 } 257 let fd: i64 = sys_openat_wr(argv[4] as *u8, 0x180) 258 if fd<0 { pbd_w("ssl save FAIL\n" as *u8); return 2 } 259 sys_write(fd, (resp as i64 + r[6]) as *u8, rn[0]-r[6]) 260 sys_close(fd) 261 pbd_w("HTTP " as *u8); pbd_n(r[1]) 262 pbd_w(" bundle saved (0600): " as *u8); pbd_n(rn[0]-r[6]); pbd_w(" bytes (private key inside; NEVER printed)\n" as *u8) 263 return 0 264 } 265 var is_reg: i64 = 0 266 var is_dry: i64 = 0 267 if pbd_streq(mode, "register" as *u8)==1 { is_reg=1 } 268 if pbd_streq(mode, "dryrun" as *u8)==1 { is_dry=1 } 269 if is_reg+is_dry==1 { 270 if argc<7 { pbd_w("register|dryrun needs <domain> <credfile> <price_cents> <cap_cents> confirm-spend\n" as *u8); return 1 } 271 // R1 endpoint verified against banked docs bytes 272 let docsbuf: *u8 = sys_mmap(PBD_MAGIC_1048576) 273 let docs_ok: i64 = pbd_docs_has_create(docsbuf, PBD_MAGIC_1048576) 274 // R2 creds 275 var creds_ok: i64 = 0 276 if pbd_creds(argv[3] as *u8, ao, an, so, sn2, raw)==0 { creds_ok=1 } 277 // R3 literal confirm token 278 var confirm_ok: i64 = 0 279 if pbd_streq(argv[6] as *u8, "confirm-spend" as *u8)==1 { confirm_ok=1 } 280 // R4 quote + cap (integer cents) 281 var quote: i64 = 0 282 var cap: i64 = 0 283 let qs: *u8 = argv[4] as *u8 284 var qi: i64=0 285 while qs[qi]!=(0 as u8) { if qs[qi]>=(48 as u8) { if qs[qi]<=(57 as u8) { quote=quote*10+((qs[qi] as i64)-48) } } qi=qi+1 } 286 let cs: *u8 = argv[5] as *u8 287 var ci: i64=0 288 while cs[ci]!=(0 as u8) { if cs[ci]>=(48 as u8) { if cs[ci]<=(57 as u8) { cap=cap*10+((cs[ci] as i64)-48) } } ci=ci+1 } 289 let wall: i64 = pbd_register_wall(docs_ok, creds_ok, confirm_ok, quote, cap) 290 if wall!=0 { 291 pbd_w("REGISTER REFUSED (wall code " as *u8); pbd_n(wall) 292 pbd_w("): -10=endpoint-unverified-in-banked-docs -11=creds -12=confirm -13=quote/cap -14=over-cap\n" as *u8) 293 return 3 294 } 295 // R5 the request, on the SPEC-VERIFIED contract: POST /domain/create/{domain} 296 // body = creds + cost(pennies, must equal the checkDomain quote) + agreeToTerms. 297 // dryrun mode adds dryRun:true = full server-side pre-flight, NO order, NO charge. 298 let url: *u8 = sys_mmap(512) 299 var uo: i64 = pk_put(url, 0, "https://api.porkbun.com/api/json/v3/domain/create/" as *u8) 300 uo = pk_put(url, uo, argv[2] as *u8) 301 url[uo]=0 as u8 302 let body: *u8 = sys_mmap(PBD_MAGIC_2048) 303 var bo: i64 = pk_creds(body, 0, (raw as i64 + ao[0]) as *u8, an[0], (raw as i64 + so[0]) as *u8, sn2[0]) 304 bo = pk_put(body, bo, ",\"cost\":" as *u8) 305 bo = pbd_putn(body, bo, quote) 306 bo = pk_put(body, bo, ",\"agreeToTerms\":\"yes\"" as *u8) 307 if is_dry==1 { bo = pk_put(body, bo, ",\"dryRun\":true" as *u8) } 308 bo = pk_put(body, bo, "}" as *u8) 309 pbd_w("SPEND WALL PASSED (quote " as *u8); pbd_n(quote) 310 pbd_w(" <= cap " as *u8); pbd_n(cap) 311 if is_dry==1 { pbd_w(" cents; DRY RUN -- server validates, no charge). Sending...\n" as *u8) } 312 else { pbd_w(" cents; endpoint spec-verified; confirm-spend given; REAL REGISTRATION). Sending...\n" as *u8) } 313 let rc: i64 = nx_acme_req(url, uo, 1, "application/json" as *u8, 16, body, bo, store, now, resp, PBD_RESP_CAP, rn) 314 if rc!=NX_ACME_HTTP_OK { pbd_w("register transport FAIL rc=" as *u8); pbd_n(rc); pbd_w("\n" as *u8); return 2 } 315 pbd_show(resp, rn[0], PBD_MAGIC_1200) 316 return 0 317 } 318 pbd_w("unknown mode\n" as *u8) 319 return 1 320}