code wiki / bin / nx_http_redirect_daemon.nx

nx_http_redirect_daemon.nx source

↩ module page · 148 lines · 5888 B

1// nx_http_redirect_daemon.nx -- sovereign plain-HTTP listener that 2// 301-redirects everything to HTTPS. Pure Nishi, no TLS, no libc. 3// 4// Operator 2026-05-28: "redirect the http to it" -- port 80 must send 5// browsers to https:// instead of landing on the Synology DSM (:5000). 6// 7// Listens on 0.0.0.0:8080 (GL forwards public :80 here). For each 8// request it reads the start line + Host header and replies: 9// HTTP/1.0 301 Moved Permanently 10// Location: https://<host><path> 11// preserving the requested path so deep links survive the upgrade. 12// 13// Public-exposure hardened (SO_RCVTIMEO/SNDTIMEO) + constant memory 14// (all buffers hoisted out of the accept loop) -- same discipline as 15// nx_sites_daemon (PREVENT: no sys_mmap in the loop). 16 17import "nx_syscalls.nx" 18import "nx_http_server.nx" 19 20const NX_RD_PORT: i64 = 8080 21const NX_RD_BUDGET: i64 = 100000000 22 23func rd_find(hay: *u8, hlen: i64, needle: *u8, nlen: i64) -> i64 { 24 if nlen <= 0 { return 0 - 1 } 25 if hlen < nlen { return 0 - 1 } 26 var i: i64 = 0 27 let last: i64 = hlen - nlen 28 while i <= last { 29 var j: i64 = 0 30 var ok: i64 = 1 31 while j < nlen { 32 if hay[i + j] != needle[j] { ok = 0; j = nlen } else { j = j + 1 } 33 } 34 if ok == 1 { return i } 35 i = i + 1 36 } 37 return 0 - 1 38} 39 40func main() -> i64 { 41 let addr_buf: *u8 = sys_mmap(16) 42 nx_http_server_addr_any(addr_buf, NX_RD_PORT) 43 let lv: *i64 = (sys_mmap(8)) as *i64 44 let lfd: i64 = nx_http_server_listen(addr_buf, 16, lv) 45 if lfd < 0 { return 4 } 46 sys_write(1, "nishi http-redirect daemon: 0.0.0.0:8080 -> 301 https\n" as *u8, 54) 47 48 // Constant memory: hoist all per-connection buffers. 49 let av: *i64 = (sys_mmap(8)) as *i64 50 let req: *u8 = sys_mmap(8192) 51 let resp: *u8 = sys_mmap(8192) 52 let host: *u8 = sys_mmap(256) 53 let ka: *u8 = sys_mmap(512) // ACME HTTP-01 key authorization buffer 54 55 var served: i64 = 0 56 while served < NX_RD_BUDGET { 57 let cfd: i64 = nx_http_server_accept_one(lfd, av) 58 if cfd < 0 { served = served + 1; continue } 59 sys_set_socket_timeout(cfd, 10) 60 61 let n: i64 = sys_read(cfd, req, 8191) 62 if n > 0 { 63 req[n] = 0 64 // --- extract path from the request line: "GET <path> HTTP/.." --- 65 var ps: i64 = 0 66 while ps < n { 67 if req[ps] == 0x20 { ps = ps + 1; break } // first space after method 68 ps = ps + 1 69 } 70 var pe: i64 = ps 71 while pe < n { 72 if req[pe] == 0x20 { break } // space before HTTP/x 73 pe = pe + 1 74 } 75 // --- ACME HTTP-01: serve keyAuth at /.well-known/acme-challenge/* --- 76 // (RFC 8555 ยง8.3) instead of redirecting, so Let's Encrypt can 77 // validate domain control over plain HTTP on port 80. 78 var is_acme: i64 = 0 79 let wk: *u8 = "/.well-known/acme-challenge/" as *u8 80 if (pe - ps) >= 27 { 81 var m: i64 = 1 82 var z: i64 = 0 83 while z < 27 { if req[ps + z] != wk[z] { m = 0; z = 27 } else { z = z + 1 } } 84 is_acme = m 85 } 86 if is_acme == 1 { 87 let kfd: i64 = sys_openat_rd("/volume1/homes/elderwesto/nishihost/acme-challenge/keyauth\x00" as *u8) 88 if kfd > 0 { 89 let kn: i64 = sys_read(kfd, ka, 511) 90 sys_close(kfd) 91 if kn > 0 { 92 var rw: i64 = 0 93 let hh: *u8 = "HTTP/1.0 200 OK\r\nContent-Type: application/octet-stream\r\nConnection: close\r\n\r\n" as *u8 94 var ha: i64 = 0 95 while hh[ha] != 0 { resp[rw] = hh[ha]; rw = rw + 1; ha = ha + 1 } 96 var kk: i64 = 0 97 while kk < kn { resp[rw] = ka[kk]; rw = rw + 1; kk = kk + 1 } 98 sys_write(cfd, resp, rw) 99 } 100 } 101 sys_close(cfd) 102 served = served + 1 103 continue 104 } 105 // --- extract Host header value --- 106 var host_n: i64 = 0 107 let hpos: i64 = rd_find(req, n, "Host: " as *u8, 6) 108 if hpos >= 0 { 109 var k: i64 = hpos + 6 110 while k < n { 111 let c: i64 = req[k] as i64 112 if c == 0x0D { break } // CR 113 if c == 0x0A { break } // LF 114 if host_n < 255 { host[host_n] = req[k] as u8; host_n = host_n + 1 } 115 k = k + 1 116 } 117 } 118 if host_n == 0 { 119 let dh: *u8 = "andelinwest.com" as *u8 120 var d: i64 = 0 121 while d < 15 { host[d] = dh[d]; d = d + 1 } 122 host_n = 15 123 } 124 // --- build the 301 --- 125 var w: i64 = 0 126 let h1: *u8 = "HTTP/1.0 301 Moved Permanently\r\nLocation: https://" as *u8 127 var a: i64 = 0 128 while h1[a] != 0 { resp[w] = h1[a]; w = w + 1; a = a + 1 } 129 var hi: i64 = 0 130 while hi < host_n { resp[w] = host[hi]; w = w + 1; hi = hi + 1 } 131 // path (from request line); fall back to "/" 132 if pe > ps { 133 var pp: i64 = ps 134 while pp < pe { resp[w] = req[pp]; w = w + 1; pp = pp + 1 } 135 } else { 136 resp[w] = 0x2F as u8; w = w + 1 137 } 138 let h2: *u8 = "\r\nConnection: close\r\nContent-Length: 0\r\n\r\n" as *u8 139 var b: i64 = 0 140 while h2[b] != 0 { resp[w] = h2[b]; w = w + 1; b = b + 1 } 141 sys_write(cfd, resp, w) 142 } 143 sys_close(cfd) 144 served = served + 1 145 } 146 sys_close(lfd) 147 return 0 148}