/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "dsp/scale/cdecimate2xuint8x8.h"
#include "libc/bits/atomic.h"
#include "libc/bits/bits.h"
#include "libc/bits/likely.h"
#include "libc/bits/popcnt.h"
#include "libc/bits/safemacros.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/ioctl.h"
#include "libc/calls/math.h"
#include "libc/calls/sigbits.h"
#include "libc/calls/strace.internal.h"
#include "libc/calls/struct/dirent.h"
#include "libc/calls/struct/filter.h"
#include "libc/calls/struct/flock.h"
#include "libc/calls/struct/rusage.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/termios.h"
#include "libc/calls/ttydefaults.h"
#include "libc/dce.h"
#include "libc/dns/dns.h"
#include "libc/dns/hoststxt.h"
#include "libc/dos.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/fmt/fmt.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/intrin/spinlock.h"
#include "libc/log/backtrace.internal.h"
#include "libc/log/check.h"
#include "libc/log/log.h"
#include "libc/macros.internal.h"
#include "libc/math.h"
#include "libc/mem/alloca.h"
#include "libc/mem/fmt.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/bsf.h"
#include "libc/nexgen32e/bsr.h"
#include "libc/nexgen32e/crc32.h"
#include "libc/nexgen32e/nt2sysv.h"
#include "libc/nexgen32e/rdtsc.h"
#include "libc/nexgen32e/rdtscp.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/nt/runtime.h"
#include "libc/nt/thread.h"
#include "libc/nt/version.h"
#include "libc/rand/rand.h"
#include "libc/runtime/clktck.h"
#include "libc/runtime/directmap.internal.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/gc.internal.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/memtrack.internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/runtime/symbols.internal.h"
#include "libc/sock/goodsocket.internal.h"
#include "libc/sock/sock.h"
#include "libc/stdio/append.internal.h"
#include "libc/stdio/hex.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/str/slice.h"
#include "libc/str/str.h"
#include "libc/str/undeflate.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/audit.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/clone.h"
#include "libc/sysv/consts/dt.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/grnd.h"
#include "libc/sysv/consts/inaddr.h"
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/lock.h"
#include "libc/sysv/consts/madv.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/msync.h"
#include "libc/sysv/consts/nr.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/pr.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/consts/rusage.h"
#include "libc/sysv/consts/s.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/shut.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/so.h"
#include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/sol.h"
#include "libc/sysv/consts/tcp.h"
#include "libc/sysv/consts/termios.h"
#include "libc/sysv/consts/w.h"
#include "libc/sysv/errfuns.h"
#include "libc/testlib/testlib.h"
#include "libc/time/time.h"
#include "libc/x/x.h"
#include "libc/zip.h"
#include "net/http/escape.h"
#include "net/http/http.h"
#include "net/http/ip.h"
#include "net/http/url.h"
#include "net/https/https.h"
#include "third_party/getopt/getopt.h"
#include "third_party/linenoise/linenoise.h"
#include "third_party/lua/cosmo.h"
#include "third_party/lua/lauxlib.h"
#include "third_party/lua/lrepl.h"
#include "third_party/lua/ltests.h"
#include "third_party/lua/lua.h"
#include "third_party/lua/luaconf.h"
#include "third_party/lua/lualib.h"
#include "third_party/mbedtls/asn1.h"
#include "third_party/mbedtls/asn1write.h"
#include "third_party/mbedtls/cipher.h"
#include "third_party/mbedtls/config.h"
#include "third_party/mbedtls/ctr_drbg.h"
#include "third_party/mbedtls/debug.h"
#include "third_party/mbedtls/ecp.h"
#include "third_party/mbedtls/entropy.h"
#include "third_party/mbedtls/entropy_poll.h"
#include "third_party/mbedtls/error.h"
#include "third_party/mbedtls/iana.h"
#include "third_party/mbedtls/md.h"
#include "third_party/mbedtls/md5.h"
#include "third_party/mbedtls/oid.h"
#include "third_party/mbedtls/pk.h"
#include "third_party/mbedtls/rsa.h"
#include "third_party/mbedtls/san.h"
#include "third_party/mbedtls/sha1.h"
#include "third_party/mbedtls/ssl.h"
#include "third_party/mbedtls/ssl_ticket.h"
#include "third_party/mbedtls/x509.h"
#include "third_party/mbedtls/x509_crt.h"
#include "third_party/regex/regex.h"
#include "third_party/zlib/zlib.h"
#include "tool/args/args.h"
#include "tool/build/lib/case.h"
#include "tool/build/lib/psk.h"
#include "tool/net/lfuncs.h"
#include "tool/net/luacheck.h"
#include "tool/net/sandbox.h"
STATIC_STACK_SIZE(0x40000);
STATIC_YOINK("zip_uri_support");
/**
* @fileoverview redbean - single-file distributable web server
*
* redbean makes it possible to share web applications that run offline
* as a single-file αcτµαlly pδrταblε εxεcµταblε zip archive which
* contains your assets. All you need to do is download the redbean.com
* program below, change the filename to .zip, add your content in a zip
* editing tool, and then change the extension back to .com.
*
* redbean can serve 1 million+ gzip encoded responses per second on a
* cheap personal computer. That performance is thanks to zip and gzip
* using the same compression format, which enables kernelspace copies.
* Another reason redbean goes fast is that it's a tiny static binary,
* which makes fork memory paging nearly free.
*
* redbean is also easy to modify to suit your own needs. The program
* itself is written as a single .c file. It embeds the Lua programming
* language and SQLite which let you write dynamic pages.
*/
#ifndef REDBEAN
#define REDBEAN "redbean"
#endif
#define VERSION 0x020000
#define HEARTBEAT 5000 /*ms*/
#define HASH_LOAD_FACTOR /* 1. / */ 4
#define MONITOR_MICROS 150000
#define READ(F, P, N) readv(F, &(struct iovec){P, N}, 1)
#define WRITE(F, P, N) writev(F, &(struct iovec){P, N}, 1)
#define LockInc(P) asm volatile("lock incq\t%0" : "=m"(*(P)))
#define LockDec(P) asm volatile("lock decq\t%0" : "=m"(*(P)))
#define AppendCrlf(P) mempcpy(P, "\r\n", 2)
#define HasHeader(H) (!!msg.headers[H].a)
#define HeaderData(H) (inbuf.p + msg.headers[H].a)
#define HeaderLength(H) (msg.headers[H].b - msg.headers[H].a)
#define HeaderEqualCase(H, S) \
SlicesEqualCase(S, strlen(S), HeaderData(H), HeaderLength(H))
#define TRACE_BEGIN \
do { \
if (!IsTiny()) { \
if (funtrace) ++__ftrace; \
if (systrace) ++__strace; \
} \
} while (0)
#define TRACE_END \
do { \
if (!IsTiny()) { \
if (funtrace) --__ftrace; \
if (systrace) --__strace; \
} \
} while (0)
// letters not used: EIJNOQYnoqwxy
// digits not used: 0123456789
// puncts not used: !"#$%&'()*+,-./;<=>@[\]^_`{|}~
#define GETOPTS "BSVXZabdfghijkmsuvzA:C:D:F:G:H:K:L:M:P:R:T:U:W:c:e:l:p:r:t:"
extern unsigned long long __kbirth;
static const uint8_t kGzipHeader[] = {
0x1F, // MAGNUM
0x8B, // MAGNUM
0x08, // CM: DEFLATE
0x00, // FLG: NONE
0x00, // MTIME: NONE
0x00, //
0x00, //
0x00, //
0x00, // XFL
kZipOsUnix, // OS
};
static const char *const kIndexPaths[] = {
#ifndef STATIC
"index.lua",
#endif
"index.html",
};
static const char *const kAlpn[] = {
"http/1.1",
NULL,
};
struct Buffer {
size_t n, c;
char *p;
};
struct TlsBio {
int fd, c;
unsigned a, b;
unsigned char t[4000];
unsigned char u[1430];
};
struct Strings {
size_t n, c;
struct String {
size_t n;
const char *s;
} * p;
};
struct DeflateGenerator {
int t;
void *b;
size_t i;
uint32_t c;
uint32_t z;
z_stream s;
struct Asset *a;
};
static struct Ips {
size_t n;
uint32_t *p;
} ips;
static struct Ports {
size_t n;
uint16_t *p;
} ports;
static struct Servers {
size_t n;
struct Server {
int fd;
struct sockaddr_in addr;
} * p;
} servers;
static struct Freelist {
size_t n, c;
void **p;
} freelist;
static struct Unmaplist {
size_t n, c;
struct Unmap {
int f;
void *p;
size_t n;
} * p;
} unmaplist;
static struct Psks {
size_t n;
struct Psk {
char *key;
size_t key_len;
char *identity;
size_t identity_len;
char *s;
} * p;
} psks;
static struct Suites {
size_t n;
uint16_t *p;
} suites;
static struct Certs {
size_t n;
struct Cert *p;
} certs;
static struct Redirects {
size_t n;
struct Redirect {
int code;
struct String path;
struct String location;
} * p;
} redirects;
static struct Assets {
uint32_t n;
struct Asset {
bool istext;
uint32_t hash;
uint64_t cf;
uint64_t lf;
int64_t lastmodified;
char *lastmodifiedstr;
struct File {
struct String path;
struct stat st;
} * file;
} * p;
} assets;
static struct Shared {
int workers;
long double nowish;
long double lastreindex;
long double lastmeltdown;
char currentdate[32];
struct rusage server;
struct rusage children;
struct Counters {
#define C(x) long x;
#include "tool/net/counters.inc"
#undef C
} c;
_Alignas(64) char montermlock;
} * shared;
static const char kCounterNames[] =
#define C(x) #x "\0"
#include "tool/net/counters.inc"
#undef C
;
typedef ssize_t (*reader_f)(int, void *, size_t);
typedef ssize_t (*writer_f)(int, struct iovec *, int);
static bool usessl;
static bool suiteb;
static bool killed;
static bool istext;
static bool zombied;
static bool gzipped;
static bool branded;
static bool funtrace;
static bool systrace;
static bool meltdown;
static bool unsecure;
static bool printport;
static bool daemonize;
static bool logrusage;
static bool logbodies;
static bool isterminal;
static bool sslcliused;
static bool loglatency;
static bool terminated;
static bool uniprocess;
static bool memmonalive;
static bool invalidated;
static bool logmessages;
static bool isinitialized;
static bool checkedmethod;
static bool sslinitialized;
static bool sslfetchverify;
static bool hascontenttype;
static bool interpretermode;
static bool sslclientverify;
static bool connectionclose;
static bool hasonworkerstop;
static bool isexitingworker;
static bool hasonworkerstart;
static bool leakcrashreports;
static bool hasonhttprequest;
static bool ishandlingrequest;
static bool listeningonport443;
static bool hasonprocesscreate;
static bool hasonprocessdestroy;
static bool loggednetworkorigin;
static bool ishandlingconnection;
static bool hasonclientconnection;
static bool evadedragnetsurveillance;
static _Atomic(bool) terminatemonitor;
static int zfd;
static int frags;
static int gmtoff;
static int client;
static int mainpid;
static int sandboxed;
static int changeuid;
static int changegid;
static int isyielding;
static int statuscode;
static int shutdownsig;
static int sslpskindex;
static int oldloglevel;
static int maxpayloadsize;
static int messageshandled;
static int sslticketlifetime;
static uint32_t clientaddrsize;
static size_t zsize;
static char *outbuf;
static lua_State *GL;
static lua_State *YL;
static char *content;
static uint8_t *zmap;
static uint8_t *zbase;
static uint8_t *zcdir;
static size_t hdrsize;
static size_t msgsize;
static size_t amtread;
static reader_f reader;
static writer_f writer;
static char *extrahdrs;
static char *luaheaderp;
static const char *zpath;
static const char *brand;
static char gzip_footer[8];
static const char *pidpath;
static const char *logpath;
static const char *histpath;
static struct pollfd *polls;
static struct Strings loops;
static size_t payloadlength;
static size_t contentlength;
static int64_t cacheseconds;
static const char *monitortty;
static const char *serverheader;
static struct Strings stagedirs;
static struct Strings hidepaths;
static const char *launchbrowser;
static const char *referrerpolicy;
static ssize_t (*generator)(struct iovec[3]);
static struct Buffer inbuf_actual;
static struct Buffer inbuf;
static struct Buffer oldin;
static struct Buffer hdrbuf;
static struct timeval timeout;
static struct Buffer effectivepath;
static struct Url url;
static struct HttpMessage msg;
static struct stat zst;
static long double startread;
static long double lastrefresh;
static long double startserver;
static long double startrequest;
static long double lastheartbeat;
static long double startconnection;
static struct sockaddr_in clientaddr;
static struct sockaddr_in *serveraddr;
static mbedtls_ssl_config conf;
static mbedtls_ssl_context ssl;
static mbedtls_ctr_drbg_context rng;
static mbedtls_ssl_ticket_context ssltick;
static mbedtls_ssl_config confcli;
static mbedtls_ssl_context sslcli;
static mbedtls_ctr_drbg_context rngcli;
static struct TlsBio g_bio;
static char slashpath[PATH_MAX];
static struct DeflateGenerator dg;
static char *Route(const char *, size_t, const char *, size_t);
static char *RouteHost(const char *, size_t, const char *, size_t);
static char *RoutePath(const char *, size_t);
static char *HandleAsset(struct Asset *, const char *, size_t);
static char *ServeAsset(struct Asset *, const char *, size_t);
static char *SetStatus(unsigned, const char *);
static void TlsInit(void);
static void OnChld(void) {
zombied = true;
}
static void OnUsr1(void) {
invalidated = true;
}
static void OnUsr2(void) {
meltdown = true;
}
static void OnTerm(int sig) {
if (!terminated) {
shutdownsig = sig;
terminated = true;
} else {
killed = true;
}
}
static void OnInt(int sig) {
OnTerm(sig);
}
static void OnHup(int sig) {
if (daemonize) {
OnUsr1();
} else {
OnTerm(sig);
}
}
static void Free(void *p) {
free(*(void **)p);
*(void **)p = 0;
}
static long ParseInt(const char *s) {
return strtol(s, 0, 0);
}
static void *FreeLater(void *p) {
if (p) {
if (++freelist.n > freelist.c) {
freelist.c = freelist.n + (freelist.n >> 1);
freelist.p = xrealloc(freelist.p, freelist.c * sizeof(*freelist.p));
}
freelist.p[freelist.n - 1] = p;
}
return p;
}
static void UnmapLater(int f, void *p, size_t n) {
if (++unmaplist.n > unmaplist.c) {
unmaplist.c = unmaplist.n + (unmaplist.n >> 1);
unmaplist.p = xrealloc(unmaplist.p, unmaplist.c * sizeof(*unmaplist.p));
}
unmaplist.p[unmaplist.n - 1].f = f;
unmaplist.p[unmaplist.n - 1].p = p;
unmaplist.p[unmaplist.n - 1].n = n;
}
static void CollectGarbage(void) {
__log_level = oldloglevel;
DestroyHttpMessage(&msg);
while (freelist.n) {
free(freelist.p[--freelist.n]);
}
while (unmaplist.n) {
--unmaplist.n;
LOGIFNEG1(munmap(unmaplist.p[unmaplist.n].p, unmaplist.p[unmaplist.n].n));
LOGIFNEG1(close(unmaplist.p[unmaplist.n].f));
}
}
static void UseOutput(void) {
content = FreeLater(outbuf);
contentlength = appendz(outbuf).i;
outbuf = 0;
}
static void DropOutput(void) {
FreeLater(outbuf);
outbuf = 0;
}
static char *MergePaths(const char *p, size_t n, const char *q, size_t m,
size_t *z) {
char *r;
if (n && p[n - 1] == '/') --n;
if (m && q[0] == '/') ++q, --m;
r = xmalloc(n + 1 + m + 1);
mempcpy(mempcpy(mempcpy(mempcpy(r, p, n), "/", 1), q, m), "", 1);
if (z) *z = n + 1 + m;
return r;
}
static long FindRedirect(const char *s, size_t n) {
int c, m, l, r, z;
l = 0;
r = redirects.n - 1;
while (l <= r) {
m = (l + r) >> 1;
c = CompareSlices(redirects.p[m].path.s, redirects.p[m].path.n, s, n);
if (c < 0) {
l = m + 1;
} else if (c > 0) {
r = m - 1;
} else {
return m;
}
}
return -1;
}
static mbedtls_x509_crt *GetTrustedCertificate(mbedtls_x509_name *name) {
size_t i;
for (i = 0; i < certs.n; ++i) {
if (certs.p[i].cert &&
!mbedtls_x509_name_cmp(name, &certs.p[i].cert->subject)) {
return certs.p[i].cert;
}
}
return 0;
}
static void UseCertificate(mbedtls_ssl_config *c, struct Cert *kp,
const char *role) {
VERBOSEF("(ssl) using %s certificate %`'s for HTTPS %s",
mbedtls_pk_get_name(&kp->cert->pk),
gc(FormatX509Name(&kp->cert->subject)), role);
CHECK_EQ(0, mbedtls_ssl_conf_own_cert(c, kp->cert, kp->key));
}
static void AppendCert(mbedtls_x509_crt *cert, mbedtls_pk_context *key) {
certs.p = realloc(certs.p, ++certs.n * sizeof(*certs.p));
certs.p[certs.n - 1].cert = cert;
certs.p[certs.n - 1].key = key;
}
static void InternCertificate(mbedtls_x509_crt *cert, mbedtls_x509_crt *prev) {
int r;
size_t i;
if (cert->next) InternCertificate(cert->next, cert);
if (prev) {
if (mbedtls_x509_crt_check_parent(prev, cert, 1)) {
DEBUGF("(ssl) unbundling %`'s from %`'s",
gc(FormatX509Name(&prev->subject)),
gc(FormatX509Name(&cert->subject)));
prev->next = 0;
} else if ((r = mbedtls_x509_crt_check_signature(prev, cert, 0))) {
WARNF("(ssl) invalid signature for %`'s -> %`'s (-0x%04x)",
gc(FormatX509Name(&prev->subject)),
gc(FormatX509Name(&cert->subject)), -r);
}
}
if (mbedtls_x509_time_is_past(&cert->valid_to)) {
WARNF("(ssl) certificate %`'s is expired",
gc(FormatX509Name(&cert->subject)));
} else if (mbedtls_x509_time_is_future(&cert->valid_from)) {
WARNF("(ssl) certificate %`'s is from the future",
gc(FormatX509Name(&cert->subject)));
}
for (i = 0; i < certs.n; ++i) {
if (!certs.p[i].cert) continue;
if (mbedtls_pk_get_type(&cert->pk) ==
mbedtls_pk_get_type(&certs.p[i].cert->pk) &&
!mbedtls_x509_name_cmp(&cert->subject, &certs.p[i].cert->subject)) {
VERBOSEF("(ssl) %s %`'s is already loaded",
mbedtls_pk_get_name(&cert->pk),
gc(FormatX509Name(&cert->subject)));
return;
}
}
for (i = 0; i < certs.n; ++i) {
if (!certs.p[i].cert && certs.p[i].key &&
!mbedtls_pk_check_pair(&cert->pk, certs.p[i].key)) {
certs.p[i].cert = cert;
return;
}
}
LogCertificate("loaded certificate", cert);
if (!cert->next && !IsSelfSigned(cert) && cert->max_pathlen) {
for (i = 0; i < certs.n; ++i) {
if (!certs.p[i].cert) continue;
if (mbedtls_pk_can_do(&cert->pk, certs.p[i].cert->sig_pk) &&
!mbedtls_x509_crt_check_parent(cert, certs.p[i].cert, 1) &&
!IsSelfSigned(certs.p[i].cert)) {
if (ChainCertificate(cert, certs.p[i].cert)) break;
}
}
}
if (!IsSelfSigned(cert)) {
for (i = 0; i < certs.n; ++i) {
if (!certs.p[i].cert) continue;
if (certs.p[i].cert->next) continue;
if (certs.p[i].cert->max_pathlen &&
mbedtls_pk_can_do(&certs.p[i].cert->pk, cert->sig_pk) &&
!mbedtls_x509_crt_check_parent(certs.p[i].cert, cert, 1)) {
ChainCertificate(certs.p[i].cert, cert);
}
}
}
AppendCert(cert, 0);
}
static void ProgramCertificate(const char *p, size_t n) {
int rc;
unsigned char *waqapi;
mbedtls_x509_crt *cert;
waqapi = malloc(n + 1);
memcpy(waqapi, p, n);
waqapi[n] = 0;
cert = calloc(1, sizeof(mbedtls_x509_crt));
rc = mbedtls_x509_crt_parse(cert, waqapi, n + 1);
mbedtls_platform_zeroize(waqapi, n);
free(waqapi);
if (rc < 0) {
WARNF("(ssl) failed to load certificate (grep -0x%04x)", rc);
return;
} else if (rc > 0) {
VERBOSEF("(ssl) certificate bundle partially loaded");
}
InternCertificate(cert, 0);
}
static void ProgramPrivateKey(const char *p, size_t n) {
int rc;
size_t i;
unsigned char *waqapi;
mbedtls_pk_context *key;
waqapi = malloc(n + 1);
memcpy(waqapi, p, n);
waqapi[n] = 0;
key = calloc(1, sizeof(mbedtls_pk_context));
rc = mbedtls_pk_parse_key(key, waqapi, n + 1, 0, 0);
mbedtls_platform_zeroize(waqapi, n);
free(waqapi);
if (rc != 0) DIEF("(ssl) error: load key (grep -0x%04x)", -rc);
for (i = 0; i < certs.n; ++i) {
if (certs.p[i].cert && !certs.p[i].key &&
!mbedtls_pk_check_pair(&certs.p[i].cert->pk, key)) {
certs.p[i].key = key;
return;
}
}
VERBOSEF("(ssl) loaded private key");
AppendCert(0, key);
}
static void ProgramFile(const char *path, void program(const char *, size_t)) {
char *p;
size_t n;
DEBUGF("(srvr) ProgramFile(%`'s)", path);
if ((p = xslurp(path, &n))) {
program(p, n);
mbedtls_platform_zeroize(p, n);
free(p);
} else {
DIEF("(srvr) error: failed to read file %`'s", path);
}
}
static void ProgramPort(long port) {
if (!(0 <= port && port <= 65535)) {
DIEF("(cfg) error: bad port: %d", port);
}
if (port == 443) listeningonport443 = true;
ports.p = realloc(ports.p, ++ports.n * sizeof(*ports.p));
ports.p[ports.n - 1] = port;
}
static void ProgramMaxPayloadSize(long x) {
maxpayloadsize = MAX(1450, x);
}
static void ProgramSslTicketLifetime(long x) {
sslticketlifetime = x;
}
static uint32_t ResolveIp(const char *addr) {
ssize_t rc;
uint32_t ip;
struct addrinfo *ai = NULL;
struct addrinfo hint = {AI_NUMERICSERV, AF_INET, SOCK_STREAM, IPPROTO_TCP};
if ((rc = getaddrinfo(addr, "0", &hint, &ai)) != EAI_SUCCESS) {
DIEF("(cfg) error: bad addr: %s (EAI_%s)", addr, gai_strerror(rc));
}
ip = ntohl(ai->ai_addr4->sin_addr.s_addr);
freeaddrinfo(ai);
return ip;
}
static void ProgramAddr(const char *addr) {
uint32_t ip;
if (IsTiny()) {
ip = ParseIp(addr, -1);
} else {
ip = ResolveIp(addr);
}
ips.p = realloc(ips.p, ++ips.n * sizeof(*ips.p));
ips.p[ips.n - 1] = ip;
}
static void ProgramRedirect(int code, const char *sp, size_t sn, const char *dp,
size_t dn) {
long i, j;
struct Redirect r;
if (code && code != 301 && code != 302 && code != 307 && code != 308) {
DIEF("(cfg) error: unsupported redirect code %d", code);
}
if (!(FreeLater(EncodeHttpHeaderValue(dp, dn, 0)))) {
DIEF("(cfg) error: invalid location %s", dp);
}
r.code = code;
r.path.s = sp;
r.path.n = sn;
r.location.s = dp;
r.location.n = dn;
if ((i = FindRedirect(r.path.s, r.path.n)) != -1) {
redirects.p[i] = r;
} else {
i = redirects.n;
redirects.p = xrealloc(redirects.p, (i + 1) * sizeof(*redirects.p));
for (j = i; j; --j) {
if (CompareSlices(r.path.s, r.path.n, redirects.p[j - 1].path.s,
redirects.p[j - 1].path.n) < 0) {
redirects.p[j] = redirects.p[j - 1];
} else {
break;
}
}
redirects.p[j] = r;
++redirects.n;
}
}
static void ProgramRedirectArg(int code, const char *s) {
size_t n;
const char *p;
n = strlen(s);
if (!(p = memchr(s, '=', n))) {
DIEF("(cfg) error: redirect arg missing '='");
}
ProgramRedirect(code, s, p - s, p + 1, n - (p - s + 1));
}
static void DescribeAddress(char buf[40], uint32_t addr, uint16_t port) {
char *p;
const char *s;
p = buf;
p = FormatUint64(p, (addr & 0xFF000000) >> 030), *p++ = '.';
p = FormatUint64(p, (addr & 0x00FF0000) >> 020), *p++ = '.';
p = FormatUint64(p, (addr & 0x0000FF00) >> 010), *p++ = '.';
p = FormatUint64(p, (addr & 0x000000FF) >> 000), *p++ = ':';
p = FormatUint64(p, port);
*p = '\0';
assert(p - buf < 40);
}
static inline void GetServerAddr(uint32_t *ip, uint16_t *port) {
*ip = ntohl(serveraddr->sin_addr.s_addr);
if (port) *port = ntohs(serveraddr->sin_port);
}
static inline void GetClientAddr(uint32_t *ip, uint16_t *port) {
*ip = ntohl(clientaddr.sin_addr.s_addr);
if (port) *port = ntohs(clientaddr.sin_port);
}
static inline void GetRemoteAddr(uint32_t *ip, uint16_t *port) {
GetClientAddr(ip, port);
if (HasHeader(kHttpXForwardedFor) &&
(IsPrivateIp(*ip) || IsLoopbackIp(*ip))) {
if (ParseForwarded(HeaderData(kHttpXForwardedFor),
HeaderLength(kHttpXForwardedFor), ip, port) == -1)
WARNF("invalid X-Forwarded-For value: %`'.*s",
HeaderLength(kHttpXForwardedFor), HeaderData(kHttpXForwardedFor));
}
}
static char *DescribeClient(void) {
uint32_t ip;
uint16_t port;
static char clientaddrstr[40];
GetRemoteAddr(&ip, &port);
DescribeAddress(clientaddrstr, ip, port);
return clientaddrstr;
}
static char *DescribeServer(void) {
uint32_t ip;
uint16_t port;
static char serveraddrstr[40];
GetServerAddr(&ip, &port);
DescribeAddress(serveraddrstr, ip, port);
return serveraddrstr;
}
static void ProgramBrand(const char *s) {
char *p;
free(brand);
free(serverheader);
if (!(p = EncodeHttpHeaderValue(s, -1, 0))) {
DIEF("(cfg) error: brand isn't latin1 encodable: %`'s", s);
}
brand = strdup(s);
serverheader = xasprintf("Server: %s\r\n", p);
free(p);
}
static void ProgramUid(long x) {
changeuid = x;
}
static void ProgramGid(long x) {
changegid = x;
}
#define MINTIMEOUT 10
static void ProgramTimeout(long ms) {
ldiv_t d;
if (ms < 0) {
timeout.tv_sec = ms; /* -(keepalive seconds) */
timeout.tv_usec = 0;
} else {
if (ms < MINTIMEOUT) {
DIEF("(cfg) error: timeout needs to be %dms or greater", MINTIMEOUT);
}
d = ldiv(ms, 1000);
timeout.tv_sec = d.quot;
timeout.tv_usec = d.rem * 1000;
}
}
static void ProgramCache(long x) {
cacheseconds = x;
}
static void SetDefaults(void) {
ProgramBrand(
gc(xasprintf("%s/%hhd.%hhd", REDBEAN, VERSION >> 020, VERSION >> 010)));
__log_level = kLogInfo;
maxpayloadsize = 64 * 1024;
ProgramCache(-1);
ProgramTimeout(60 * 1000);
ProgramSslTicketLifetime(24 * 60 * 60);
sslfetchverify = true;
}
static void AddString(struct Strings *l, const char *s, size_t n) {
if (++l->n > l->c) {
l->c = l->n + (l->n >> 1);
l->p = realloc(l->p, l->c * sizeof(*l->p));
}
l->p[l->n - 1].s = s;
l->p[l->n - 1].n = n;
}
static bool HasString(struct Strings *l, const char *s, size_t n) {
size_t i;
for (i = 0; i < l->n; ++i) {
if (SlicesEqual(l->p[i].s, l->p[i].n, s, n)) {
return true;
}
}
return false;
}
static void ProgramDirectory(const char *path) {
char *s;
size_t n;
struct stat st;
if (stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
DIEF("(cfg) error: not a directory: %`'s", path);
}
s = strdup(path);
n = strlen(s);
INFOF("(cfg) program directory: %s", s);
AddString(&stagedirs, s, n);
}
static void ProgramHeader(const char *s) {
char *p, *v, *h;
if ((p = strchr(s, ':')) && IsValidHttpToken(s, p - s) &&
(v = EncodeLatin1(p + 1, -1, 0, kControlC0 | kControlC1 | kControlWs))) {
switch (GetHttpHeader(s, p - s)) {
case kHttpDate:
case kHttpConnection:
case kHttpContentLength:
case kHttpContentEncoding:
case kHttpContentRange:
case kHttpLocation:
DIEF("(cfg) error: can't program header: %`'s", s);
case kHttpServer:
ProgramBrand(p + 1);
break;
default:
p = xasprintf("%s%.*s:%s\r\n", extrahdrs ? extrahdrs : "", p - s, s, v);
free(extrahdrs);
extrahdrs = p;
break;
}
free(v);
} else {
DIEF("(cfg) error: illegal header: %`'s", s);
}
}
static void ProgramLogPath(const char *s) {
logpath = strdup(s);
}
static void ProgramPidPath(const char *s) {
pidpath = strdup(s);
}
static bool IsServerFd(int fd) {
size_t i;
for (i = 0; i < servers.n; ++i) {
if (servers.p[i].fd == fd) {
return true;
}
}
return false;
}
static void ChangeUser(void) {
if (changegid) {
if (setgid(changegid)) {
FATALF("setgid() failed: %m");
}
}
// order matters
if (changeuid) {
if (setuid(changeuid)) {
FATALF("setuid() failed: %m");
}
}
}
static void Daemonize(void) {
char ibuf[21];
int i, fd, pid;
for (i = 0; i < 256; ++i) {
if (!IsServerFd(i)) {
close(i);
}
}
if ((pid = fork()) > 0) exit(0);
setsid();
if ((pid = fork()) > 0) _exit(0);
umask(0);
if (pidpath) {
fd = open(pidpath, O_CREAT | O_WRONLY, 0644);
WRITE(fd, ibuf, FormatInt32(ibuf, getpid()) - ibuf);
close(fd);
}
if (!logpath) ProgramLogPath("/dev/null");
open("/dev/null", O_RDONLY);
open(logpath, O_APPEND | O_WRONLY | O_CREAT, 0640);
dup2(1, 2);
ChangeUser();
}
static void LogLuaError(char *hook, char *err) {
ERRORF("(lua) failed to run %s: %s", hook, err);
}
static bool LuaEvalCode(const char *code) {
lua_State *L = GL;
int status = luaL_loadstring(L, code);
if (status != LUA_OK || LuaCallWithTrace(L, 0, 0, NULL) != LUA_OK) {
LogLuaError("lua code", lua_tostring(L, -1));
lua_pop(L, 1); // pop error
return false;
}
AssertLuaStackIsAt(L, 0);
return true;
}
static bool LuaEvalFile(const char *path) {
return LuaEvalCode(gc(xslurp(path, 0)));
}
static bool LuaOnClientConnection(void) {
#ifndef STATIC
bool dropit;
uint32_t ip, serverip;
uint16_t port, serverport;
lua_State *L = GL;
lua_getglobal(L, "OnClientConnection");
GetClientAddr(&ip, &port);
GetServerAddr(&serverip, &serverport);
lua_pushinteger(L, ip);
lua_pushinteger(L, port);
lua_pushinteger(L, serverip);
lua_pushinteger(L, serverport);
if (LuaCallWithTrace(L, 4, 1, NULL) == LUA_OK) {
dropit = lua_toboolean(L, -1);
} else {
LogLuaError("OnClientConnection", lua_tostring(L, -1));
dropit = false;
}
lua_pop(L, 1); // pop result or error
AssertLuaStackIsAt(L, 0);
return dropit;
#else
return false;
#endif
}
static void LuaOnProcessCreate(int pid) {
#ifndef STATIC
uint32_t ip, serverip;
uint16_t port, serverport;
lua_State *L = GL;
lua_getglobal(L, "OnProcessCreate");
GetClientAddr(&ip, &port);
GetServerAddr(&serverip, &serverport);
lua_pushinteger(L, pid);
lua_pushinteger(L, ip);
lua_pushinteger(L, port);
lua_pushinteger(L, serverip);
lua_pushinteger(L, serverport);
if (LuaCallWithTrace(L, 5, 0, NULL) != LUA_OK) {
LogLuaError("OnProcessCreate", lua_tostring(L, -1));
lua_pop(L, 1); // pop error
}
AssertLuaStackIsAt(L, 0);
#endif
}
static void LuaOnProcessDestroy(int pid) {
#ifndef STATIC
lua_State *L = GL;
lua_getglobal(L, "OnProcessDestroy");
lua_pushinteger(L, pid);
if (LuaCallWithTrace(L, 1, 0, NULL) != LUA_OK) {
LogLuaError("OnProcessDestroy", lua_tostring(L, -1));
lua_pop(L, 1); // pop error
}
AssertLuaStackIsAt(L, 0);
#endif
}
static inline bool IsHookDefined(const char *s) {
#ifndef STATIC
lua_State *L = GL;
bool res = !!lua_getglobal(L, s);
lua_pop(L, 1);
return res;
#else
return false;
#endif
}
static void CallSimpleHook(const char *s) {
#ifndef STATIC
lua_State *L = GL;
int n = lua_gettop(L);
lua_getglobal(L, s);
if (LuaCallWithTrace(L, 0, 0, NULL) != LUA_OK) {
LogLuaError(s, lua_tostring(L, -1));
lua_pop(L, 1); // pop error
}
AssertLuaStackIsAt(L, n);
#endif
}
static void CallSimpleHookIfDefined(const char *s) {
if (IsHookDefined(s)) {
CallSimpleHook(s);
}
}
static void ReportWorkerExit(int pid, int ws) {
int workers;
workers = atomic_fetch_sub(&shared->workers, 1) - 1;
if (WIFEXITED(ws)) {
if (WEXITSTATUS(ws)) {
LockInc(&shared->c.failedchildren);
WARNF("(stat) %d exited with %d (%,d workers remain)", pid,
WEXITSTATUS(ws), workers);
} else {
DEBUGF("(stat) %d exited (%,d workers remain)", pid, workers);
}
} else {
LockInc(&shared->c.terminatedchildren);
WARNF("(stat) %d terminated with %s (%,d workers remain)", pid,
strsignal(WTERMSIG(ws)), workers);
}
}
static void ReportWorkerResources(int pid, struct rusage *ru) {
char *s, *b = 0;
if (logrusage || LOGGABLE(kLogDebug)) {
AppendResourceReport(&b, ru, "\n");
if (b) {
if ((s = IndentLines(b, appendz(b).i - 1, 0, 1))) {
LOGF(kLogDebug, "(stat) resource report for pid %d\n%s", pid, s);
free(s);
}
free(b);
}
}
}
static void HandleWorkerExit(int pid, int ws, struct rusage *ru) {
LockInc(&shared->c.connectionshandled);
AddRusage(&shared->children, ru);
ReportWorkerExit(pid, ws);
ReportWorkerResources(pid, ru);
if (hasonprocessdestroy) {
LuaOnProcessDestroy(pid);
}
}
static void KillGroupImpl(int sig) {
LOGIFNEG1(kill(0, sig));
}
static void KillGroup(void) {
KillGroupImpl(SIGTERM);
}
static void WaitAll(void) {
int ws, pid;
struct rusage ru;
for (;;) {
if ((pid = wait4(-1, &ws, 0, &ru)) != -1) {
HandleWorkerExit(pid, ws, &ru);
} else {
if (errno == ECHILD) {
errno = 0;
break;
}
if (errno == EINTR) {
if (killed) {
killed = false;
terminated = false;
WARNF("(srvr) redbean shall terminate harder");
KillGroup();
}
errno = 0;
continue;
}
DIEF("(srvr) wait error: %m");
}
}
}
static void ReapZombies(void) {
int ws, pid;
struct rusage ru;
do {
zombied = false;
if ((pid = wait4(-1, &ws, WNOHANG, &ru)) != -1) {
if (pid) {
HandleWorkerExit(pid, ws, &ru);
} else {
break;
}
} else {
if (errno == ECHILD) {
errno = 0;
break;
}
if (errno == EINTR) {
errno = 0;
continue;
}
DIEF("(srvr) wait error: %m");
}
} while (!terminated);
}
static ssize_t ReadAll(int fd, char *p, size_t n) {
ssize_t rc;
size_t i, got;
for (i = 0; i < n;) {
rc = READ(fd, p + i, n - i);
if (rc != -1) {
got = rc;
i += got;
} else if (errno != EINTR) {
WARNF("(file) read error: %m");
return -1;
}
}
return i;
}
static ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) {
int i;
ssize_t rc;
size_t wrote, total;
i = 0;
total = 0;
do {
if (i) {
while (i < iovlen && !iov[i].iov_len) ++i;
if (i == iovlen) break;
}
if ((rc = writev(fd, iov + i, iovlen - i)) != -1) {
wrote = rc;
total += wrote;
do {
if (wrote >= iov[i].iov_len) {
wrote -= iov[i++].iov_len;
} else {
iov[i].iov_base = (char *)iov[i].iov_base + wrote;
iov[i].iov_len -= wrote;
wrote = 0;
}
} while (wrote);
} else if (errno == EINTR) {
errno = 0;
LockInc(&shared->c.writeinterruputs);
if (killed || (meltdown && nowl() - startread > 2)) {
return total ? total : -1;
}
} else {
return total ? total : -1;
}
} while (i < iovlen);
return total;
}
static int TlsFlush(struct TlsBio *bio, const unsigned char *buf, size_t len) {
struct iovec v[2];
if (len || bio->c > 0) {
v[0].iov_base = bio->u;
v[0].iov_len = MAX(0, bio->c);
v[1].iov_base = buf;
v[1].iov_len = len;
if (WritevAll(bio->fd, v, 2) != -1) {
if (bio->c > 0) bio->c = 0;
} else if (errno == EINTR) {
errno = 0;
return MBEDTLS_ERR_NET_CONN_RESET;
} else if (errno == EAGAIN) {
errno = 0;
return MBEDTLS_ERR_SSL_TIMEOUT;
} else if (errno == EPIPE || errno == ECONNRESET || errno == ENETRESET) {
return MBEDTLS_ERR_NET_CONN_RESET;
} else {
WARNF("(ssl) TlsSend error: %m");
return MBEDTLS_ERR_NET_SEND_FAILED;
}
}
return 0;
}
static int TlsSend(void *ctx, const unsigned char *buf, size_t len) {
int rc;
struct iovec v[2];
struct TlsBio *bio = ctx;
if (bio->c >= 0 && bio->c + len <= sizeof(bio->u)) {
memcpy(bio->u + bio->c, buf, len);
bio->c += len;
return len;
}
if ((rc = TlsFlush(bio, buf, len)) < 0) return rc;
return len;
}
static int TlsRecvImpl(void *ctx, unsigned char *p, size_t n, uint32_t o) {
int r;
ssize_t s;
struct iovec v[2];
struct TlsBio *bio = ctx;
if ((r = TlsFlush(bio, 0, 0)) < 0) return r;
if (bio->a < bio->b) {
r = MIN(n, bio->b - bio->a);
memcpy(p, bio->t + bio->a, r);
if ((bio->a += r) == bio->b) bio->a = bio->b = 0;
return r;
}
v[0].iov_base = p;
v[0].iov_len = n;
v[1].iov_base = bio->t;
v[1].iov_len = sizeof(bio->t);
while ((r = readv(bio->fd, v, 2)) == -1) {
if (errno == EINTR) {
errno = 0;
return MBEDTLS_ERR_SSL_WANT_READ;
} else if (errno == EAGAIN) {
errno = 0;
return MBEDTLS_ERR_SSL_TIMEOUT;
} else if (errno == EPIPE || errno == ECONNRESET || errno == ENETRESET) {
return MBEDTLS_ERR_NET_CONN_RESET;
} else {
WARNF("(ssl) tls read() error: %m");
return MBEDTLS_ERR_NET_RECV_FAILED;
}
}
if (r > n) bio->b = r - n;
return MIN(n, r);
}
static int TlsRecv(void *ctx, unsigned char *buf, size_t len, uint32_t tmo) {
int rc;
struct TlsBio *bio = ctx;
if (oldin.n) {
rc = MIN(oldin.n, len);
memcpy(buf, oldin.p, rc);
oldin.p += rc;
oldin.n -= rc;
return rc;
}
return TlsRecvImpl(ctx, buf, len, tmo);
}
static ssize_t SslRead(int fd, void *buf, size_t size) {
int rc;
rc = mbedtls_ssl_read(&ssl, buf, size);
if (!rc) {
errno = ECONNRESET;
rc = -1;
} else if (rc < 0) {
if (rc == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
rc = 0;
} else if (rc == MBEDTLS_ERR_NET_CONN_RESET ||
rc == MBEDTLS_ERR_SSL_TIMEOUT) {
errno = ECONNRESET;
rc = -1;
} else if (rc == MBEDTLS_ERR_SSL_WANT_READ) {
errno = EINTR;
rc = -1;
errno = 0;
} else if (rc == MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE) {
WARNF("(ssl) %s SslRead error -0x%04x (%s)", DescribeClient(), -rc,
"fatal alert message");
errno = EIO;
rc = -1;
} else if (rc == MBEDTLS_ERR_SSL_INVALID_RECORD) {
WARNF("(ssl) %s SslRead error -0x%04x (%s)", DescribeClient(), -rc,
"invalid record");
errno = EIO;
rc = -1;
} else if (rc == MBEDTLS_ERR_SSL_INVALID_MAC) {
WARNF("(ssl) %s SslRead error -0x%04x (%s)", DescribeClient(), -rc,
"hmac verification failed");
errno = EIO;
rc = -1;
} else {
WARNF("(ssl) %s SslRead error -0x%04x", DescribeClient(), -rc);
errno = EIO;
rc = -1;
}
}
return rc;
}
static ssize_t SslWrite(int fd, struct iovec *iov, int iovlen) {
int i;
size_t n;
ssize_t rc;
const unsigned char *p;
for (i = 0; i < iovlen; ++i) {
p = iov[i].iov_base;
n = iov[i].iov_len;
while (n) {
if ((rc = mbedtls_ssl_write(&ssl, p, n)) > 0) {
p += rc;
n -= rc;
} else if (rc == MBEDTLS_ERR_NET_CONN_RESET) {
errno = ECONNRESET;
return -1;
} else if (rc == MBEDTLS_ERR_SSL_TIMEOUT) {
errno = ETIMEDOUT;
return -1;
} else {
WARNF("(ssl) %s SslWrite error -0x%04x", DescribeClient(), -rc);
errno = EIO;
return -1;
}
}
}
return 0;
}
static void NotifyClose(void) {
#ifndef UNSECURE
if (usessl) {
DEBUGF("(ssl) SSL notifying close");
mbedtls_ssl_close_notify(&ssl);
}
#endif
}
static void WipeSigningKeys(void) {
size_t i;
if (uniprocess) return;
for (i = 0; i < certs.n; ++i) {
if (!certs.p[i].key) continue;
if (!certs.p[i].cert) continue;
if (!certs.p[i].cert->ca_istrue) continue;
mbedtls_pk_free(certs.p[i].key);
Free(&certs.p[i].key);
}
}
static void PsksDestroy(void) {
size_t i;
for (i = 0; i < psks.n; ++i) {
mbedtls_platform_zeroize(psks.p[i].key, psks.p[i].key_len);
free(psks.p[i].key);
free(psks.p[i].identity);
}
Free(&psks.p);
psks.n = 0;
}
static void CertsDestroy(void) {
size_t i;
// break up certificate chains to prevent double free
for (i = 0; i < certs.n; ++i) {
if (certs.p[i].cert) {
certs.p[i].cert->next = 0;
}
}
for (i = 0; i < certs.n; ++i) {
mbedtls_x509_crt_free(certs.p[i].cert);
free(certs.p[i].cert);
mbedtls_pk_free(certs.p[i].key);
free(certs.p[i].key);
}
Free(&certs.p);
certs.n = 0;
}
static void WipeServingKeys(void) {
size_t i;
long double t = nowl();
if (uniprocess) return;
mbedtls_ssl_ticket_free(&ssltick);
mbedtls_ssl_key_cert_free(conf.key_cert), conf.key_cert = 0;
CertsDestroy();
PsksDestroy();
}
static bool CertHasCommonName(const mbedtls_x509_crt *cert, const void *s,
size_t n) {
const mbedtls_x509_name *name;
for (name = &cert->subject; name; name = name->next) {
if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid)) {
if (SlicesEqualCase(s, n, name->val.p, name->val.len)) {
return true;
}
break;
}
}
return false;
}
static bool TlsRouteFind(mbedtls_pk_type_t type, mbedtls_ssl_context *ssl,
const unsigned char *host, size_t size, int64_t ip) {
int i;
for (i = 0; i < certs.n; ++i) {
if (IsServerCert(certs.p + i, type) &&
(((certs.p[i].cert->ext_types & MBEDTLS_X509_EXT_SUBJECT_ALT_NAME) &&
(ip == -1 ? CertHasHost(certs.p[i].cert, host, size)
: CertHasIp(certs.p[i].cert, ip))) ||
CertHasCommonName(certs.p[i].cert, host, size))) {
CHECK_EQ(
0, mbedtls_ssl_set_hs_own_cert(ssl, certs.p[i].cert, certs.p[i].key));
DEBUGF("(ssl) TlsRoute(%s, %`'.*s) %s %`'s", mbedtls_pk_type_name(type),
size, host, mbedtls_pk_get_name(&certs.p[i].cert->pk),
gc(FormatX509Name(&certs.p[i].cert->subject)));
return true;
}
}
return false;
}
static bool TlsRouteFirst(mbedtls_pk_type_t type, mbedtls_ssl_context *ssl) {
int i;
for (i = 0; i < certs.n; ++i) {
if (IsServerCert(certs.p + i, type)) {
CHECK_EQ(
0, mbedtls_ssl_set_hs_own_cert(ssl, certs.p[i].cert, certs.p[i].key));
DEBUGF("(ssl) TlsRoute(%s) %s %`'s", mbedtls_pk_type_name(type),
mbedtls_pk_get_name(&certs.p[i].cert->pk),
gc(FormatX509Name(&certs.p[i].cert->subject)));
return true;
}
}
return false;
}
static int TlsRoute(void *ctx, mbedtls_ssl_context *ssl,
const unsigned char *host, size_t size) {
int64_t ip;
bool ok1, ok2;
ip = ParseIp((const char *)host, size);
ok1 = TlsRouteFind(MBEDTLS_PK_ECKEY, ssl, host, size, ip);
ok2 = TlsRouteFind(MBEDTLS_PK_RSA, ssl, host, size, ip);
if (!ok1 && !ok2) {
WARNF("(ssl) TlsRoute(%`'.*s) not found", size, host);
ok1 = TlsRouteFirst(MBEDTLS_PK_ECKEY, ssl);
ok2 = TlsRouteFirst(MBEDTLS_PK_RSA, ssl);
}
return ok1 || ok2 ? 0 : -1;
}
static int TlsRoutePsk(void *ctx, mbedtls_ssl_context *ssl,
const unsigned char *identity, size_t identity_len) {
size_t i;
for (i = 0; i < psks.n; ++i) {
if (SlicesEqual((void *)identity, identity_len, psks.p[i].identity,
psks.p[i].identity_len)) {
DEBUGF("(ssl) TlsRoutePsk(%`'.*s)", identity_len, identity);
mbedtls_ssl_set_hs_psk(ssl, psks.p[i].key, psks.p[i].key_len);
// keep track of selected psk to report its identity
sslpskindex = i + 1; // use index+1 to check against 0 (when not set)
return 0;
}
}
WARNF("(ssl) TlsRoutePsk(%`'.*s) not found", identity_len, identity);
return -1;
}
static bool TlsSetup(void) {
int r;
oldin.p = inbuf.p;
oldin.n = amtread;
inbuf.p += amtread;
inbuf.n -= amtread;
inbuf.c = amtread;
amtread = 0;
g_bio.fd = client;
g_bio.a = 0;
g_bio.b = 0;
g_bio.c = 0;
sslpskindex = 0;
for (;;) {
if (!(r = mbedtls_ssl_handshake(&ssl)) && TlsFlush(&g_bio, 0, 0) != -1) {
LockInc(&shared->c.sslhandshakes);
g_bio.c = -1;
usessl = true;
reader = SslRead;
writer = SslWrite;
WipeServingKeys();
VERBOSEF("(ssl) shaken %s %s %s%s %s", DescribeClient(),
mbedtls_ssl_get_ciphersuite(&ssl), mbedtls_ssl_get_version(&ssl),
ssl.session->compression ? " COMPRESSED" : "",
ssl.curve ? ssl.curve->name : "uncurved");
DEBUGF("(ssl) client ciphersuite preference was %s",
gc(FormatSslClientCiphers(&ssl)));
return true;
} else if (r == MBEDTLS_ERR_SSL_WANT_READ) {
LockInc(&shared->c.handshakeinterrupts);
if (terminated || killed || (meltdown && nowl() - startread > 2)) {
return false;
}
} else {
LockInc(&shared->c.sslhandshakefails);
mbedtls_ssl_session_reset(&ssl);
switch (r) {
case MBEDTLS_ERR_SSL_CONN_EOF:
DEBUGF("(ssl) %s SSL handshake EOF", DescribeClient());
return false;
case MBEDTLS_ERR_NET_CONN_RESET:
DEBUGF("(ssl) %s SSL handshake reset", DescribeClient());
return false;
case MBEDTLS_ERR_SSL_TIMEOUT:
LockInc(&shared->c.ssltimeouts);
DEBUGF("(ssl) %s %s", DescribeClient(), "ssltimeouts");
return false;
case MBEDTLS_ERR_SSL_NO_CIPHER_CHOSEN:
LockInc(&shared->c.sslnociphers);
WARNF("(ssl) %s %s %s", DescribeClient(), "sslnociphers",
gc(FormatSslClientCiphers(&ssl)));
return false;
case MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE:
LockInc(&shared->c.sslcantciphers);
WARNF("(ssl) %s %s %s", DescribeClient(), "sslcantciphers",
gc(FormatSslClientCiphers(&ssl)));
return false;
case MBEDTLS_ERR_SSL_BAD_HS_PROTOCOL_VERSION:
LockInc(&shared->c.sslnoversion);
WARNF("(ssl) %s %s %s", DescribeClient(), "sslnoversion",
mbedtls_ssl_get_version(&ssl));
return false;
case MBEDTLS_ERR_SSL_INVALID_MAC:
LockInc(&shared->c.sslshakemacs);
WARNF("(ssl) %s %s", DescribeClient(), "sslshakemacs");
return false;
case MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE:
LockInc(&shared->c.sslnoclientcert);
WARNF("(ssl) %s %s", DescribeClient(), "sslnoclientcert");
NotifyClose();
return false;
case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED:
LockInc(&shared->c.sslverifyfailed);
WARNF("(ssl) %s SSL %s", DescribeClient(),
gc(DescribeSslVerifyFailure(
ssl.session_negotiate->verify_result)));
return false;
case MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE:
switch (ssl.fatal_alert) {
case MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN:
LockInc(&shared->c.sslunknowncert);
DEBUGF("(ssl) %s %s", DescribeClient(), "sslunknowncert");
return false;
case MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA:
LockInc(&shared->c.sslunknownca);
DEBUGF("(ssl) %s %s", DescribeClient(), "sslunknownca");
return false;
default:
WARNF("(ssl) %s SSL shakealert %s", DescribeClient(),
GetAlertDescription(ssl.fatal_alert));
return false;
}
default:
WARNF("(ssl) %s SSL handshake failed -0x%04x", DescribeClient(), -r);
return false;
}
}
}
}
static void ConfigureCertificate(mbedtls_x509write_cert *cw, struct Cert *ca,
int usage, int type) {
int r;
const char *s;
bool isduplicate;
size_t i, j, k, nsan;
struct HostsTxt *htxt;
struct mbedtls_san *san;
const mbedtls_x509_name *xname;
char *name, *subject, *issuer, notbefore[16], notafter[16], hbuf[256];
san = 0;
nsan = 0;
name = 0;
htxt = GetHostsTxt();
strcpy(hbuf, "localhost");
gethostname(hbuf, sizeof(hbuf));
for (i = 0; i < htxt->entries.i; ++i) {
for (j = 0; j < ips.n; ++j) {
if (ips.p[j] == READ32BE(htxt->entries.p[i].ip)) {
isduplicate = false;
s = htxt->strings.p + htxt->entries.p[i].name;
if (!name) name = s;
for (k = 0; k < nsan; ++k) {
if (san[k].tag == MBEDTLS_X509_SAN_DNS_NAME &&
!strcasecmp(s, san[k].val)) {
isduplicate = true;
break;
}
}
if (!isduplicate) {
san = realloc(san, ++nsan * sizeof(*san));
san[nsan - 1].tag = MBEDTLS_X509_SAN_DNS_NAME;
san[nsan - 1].val = s;
}
}
}
}
for (i = 0; i < ips.n; ++i) {
san = realloc(san, ++nsan * sizeof(*san));
san[nsan - 1].tag = MBEDTLS_X509_SAN_IP_ADDRESS;
san[nsan - 1].ip4 = ips.p[i];
}
ChooseCertificateLifetime(notbefore, notafter);
subject = xasprintf("CN=%s", name ? name : hbuf);
if (ca) {
issuer = calloc(1, 1000);
CHECK_GT(mbedtls_x509_dn_gets(issuer, 1000, &ca->cert->subject), 0);
} else {
issuer = strdup(subject);
}
if ((r = mbedtls_x509write_crt_set_subject_alternative_name(cw, san, nsan)) ||
(r = mbedtls_x509write_crt_set_validity(cw, notbefore, notafter)) ||
(r = mbedtls_x509write_crt_set_basic_constraints(cw, false, -1)) ||
#if defined(MBEDTLS_SHA1_C)
(r = mbedtls_x509write_crt_set_subject_key_identifier(cw)) ||
(r = mbedtls_x509write_crt_set_authority_key_identifier(cw)) ||
#endif
(r = mbedtls_x509write_crt_set_key_usage(cw, usage)) ||
(r = mbedtls_x509write_crt_set_ext_key_usage(cw, type)) ||
(r = mbedtls_x509write_crt_set_subject_name(cw, subject)) ||
(r = mbedtls_x509write_crt_set_issuer_name(cw, issuer))) {
DIEF("(ssl) configure certificate (grep -0x%04x)", -r);
}
free(subject);
free(issuer);
free(san);
}
static struct Cert GetKeySigningKey(void) {
size_t i;
for (i = 0; i < certs.n; ++i) {
if (!certs.p[i].key) continue;
if (!certs.p[i].cert) continue;
if (!certs.p[i].cert->ca_istrue) continue;
if (mbedtls_x509_crt_check_key_usage(certs.p[i].cert,
MBEDTLS_X509_KU_KEY_CERT_SIGN)) {
continue;
}
return certs.p[i];
}
return (struct Cert){0};
}
static struct Cert GenerateEcpCertificate(struct Cert *ca) {
mbedtls_pk_context *key;
mbedtls_md_type_t md_alg;
mbedtls_x509write_cert wcert;
md_alg = suiteb ? MBEDTLS_MD_SHA384 : MBEDTLS_MD_SHA256;
key = InitializeKey(ca, &wcert, md_alg, MBEDTLS_PK_ECKEY);
CHECK_EQ(0, mbedtls_ecp_gen_key(
suiteb ? MBEDTLS_ECP_DP_SECP384R1 : MBEDTLS_ECP_DP_SECP256R1,
mbedtls_pk_ec(*key), GenerateHardRandom, 0));
GenerateCertificateSerial(&wcert);
ConfigureCertificate(&wcert, ca, MBEDTLS_X509_KU_DIGITAL_SIGNATURE,
MBEDTLS_X509_NS_CERT_TYPE_SSL_SERVER |
MBEDTLS_X509_NS_CERT_TYPE_SSL_CLIENT);
return FinishCertificate(ca, &wcert, key);
}
static struct Cert GenerateRsaCertificate(struct Cert *ca) {
mbedtls_pk_context *key;
mbedtls_md_type_t md_alg;
mbedtls_x509write_cert wcert;
md_alg = suiteb ? MBEDTLS_MD_SHA384 : MBEDTLS_MD_SHA256;
key = InitializeKey(ca, &wcert, md_alg, MBEDTLS_PK_RSA);
CHECK_EQ(0, mbedtls_rsa_gen_key(mbedtls_pk_rsa(*key), GenerateHardRandom, 0,
suiteb ? 4096 : 2048, 65537));
GenerateCertificateSerial(&wcert);
ConfigureCertificate(
&wcert, ca,
MBEDTLS_X509_KU_DIGITAL_SIGNATURE | MBEDTLS_X509_KU_KEY_ENCIPHERMENT,
MBEDTLS_X509_NS_CERT_TYPE_SSL_SERVER |
MBEDTLS_X509_NS_CERT_TYPE_SSL_CLIENT);
return FinishCertificate(ca, &wcert, key);
}
static void LoadCertificates(void) {
size_t i;
struct Cert ksk, ecp, rsa;
bool havecert, haveclientcert;
havecert = false;
haveclientcert = false;
for (i = 0; i < certs.n; ++i) {
if (certs.p[i].key && certs.p[i].cert && !certs.p[i].cert->ca_istrue &&
!mbedtls_x509_crt_check_key_usage(certs.p[i].cert,
MBEDTLS_X509_KU_DIGITAL_SIGNATURE)) {
if (!mbedtls_x509_crt_check_extended_key_usage(
certs.p[i].cert, MBEDTLS_OID_SERVER_AUTH,
MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH))) {
LogCertificate("using server certificate", certs.p[i].cert);
UseCertificate(&conf, certs.p + i, "server");
havecert = true;
}
if (!mbedtls_x509_crt_check_extended_key_usage(
certs.p[i].cert, MBEDTLS_OID_CLIENT_AUTH,
MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH))) {
LogCertificate("using client certificate", certs.p[i].cert);
UseCertificate(&confcli, certs.p + i, "client");
haveclientcert = true;
}
}
}
if (!havecert && (!psks.n || ksk.key)) {
if ((ksk = GetKeySigningKey()).key) {
DEBUGF("(ssl) generating ssl certificates using %`'s",
gc(FormatX509Name(&ksk.cert->subject)));
} else {
VERBOSEF("(ssl) could not find non-CA SSL certificate key pair with"
" -addext keyUsage=digitalSignature"
" -addext extendedKeyUsage=serverAuth");
VERBOSEF("(ssl) could not find CA key signing key pair with"
" -addext keyUsage=keyCertSign");
VERBOSEF("(ssl) generating self-signed ssl certificates");
}
#ifdef MBEDTLS_ECP_C
ecp = GenerateEcpCertificate(ksk.key ? &ksk : 0);
if (!havecert) UseCertificate(&conf, &ecp, "server");
if (!haveclientcert && ksk.key) UseCertificate(&confcli, &ecp, "client");
AppendCert(ecp.cert, ecp.key);
#endif
#ifdef MBEDTLS_RSA_C
rsa = GenerateRsaCertificate(ksk.key ? &ksk : 0);
if (!havecert) UseCertificate(&conf, &rsa, "server");
if (!haveclientcert && ksk.key) UseCertificate(&confcli, &rsa, "client");
AppendCert(rsa.cert, rsa.key);
#endif
}
WipeSigningKeys();
}
static bool ClientAcceptsGzip(void) {
return msg.version >= 10 && /* RFC1945 § 3.5 */
HeaderHas(&msg, inbuf.p, kHttpAcceptEncoding, "gzip", 4);
}
char *FormatUnixHttpDateTime(char *s, int64_t t) {
struct tm tm;
gmtime_r(&t, &tm);
FormatHttpDateTime(s, &tm);
return s;
}
static void UpdateCurrentDate(long double now) {
int64_t t;
struct tm tm;
t = now;
shared->nowish = now;
gmtime_r(&t, &tm);
FormatHttpDateTime(shared->currentdate, &tm);
}
static int64_t GetGmtOffset(int64_t t) {
struct tm tm;
localtime_r(&t, &tm);
return tm.tm_gmtoff;
}
forceinline bool IsCompressed(struct Asset *a) {
return !a->file &&
ZIP_LFILE_COMPRESSIONMETHOD(zbase + a->lf) == kZipCompressionDeflate;
}
forceinline int GetMode(struct Asset *a) {
return a->file ? a->file->st.st_mode : GetZipCfileMode(zbase + a->cf);
}
forceinline bool IsCompressionMethodSupported(int method) {
return method == kZipCompressionNone || method == kZipCompressionDeflate;
}
static inline unsigned Hash(const void *p, unsigned long n) {
unsigned h, i;
for (h = i = 0; i < n; i++) {
h += ((unsigned char *)p)[i];
h *= 0x9e3779b1;
}
return MAX(1, h);
}
static void FreeAssets(void) {
size_t i;
for (i = 0; i < assets.n; ++i) {
Free(&assets.p[i].lastmodifiedstr);
}
Free(&assets.p);
assets.n = 0;
}
static void FreeStrings(struct Strings *l) {
size_t i;
for (i = 0; i < l->n; ++i) {
Free(&l->p[i].s);
}
Free(&l->p);
l->n = 0;
}
static void IndexAssets(void) {
uint64_t cf;
struct Asset *p;
struct timespec lm;
uint32_t i, n, m, step, hash;
DEBUGF("(zip) indexing assets (inode %#lx)", zst.st_ino);
FreeAssets();
CHECK_GE(HASH_LOAD_FACTOR, 2);
CHECK(READ32LE(zcdir) == kZipCdir64HdrMagic ||
READ32LE(zcdir) == kZipCdirHdrMagic);
n = GetZipCdirRecords(zcdir);
m = roundup2pow(MAX(1, n) * HASH_LOAD_FACTOR);
p = xcalloc(m, sizeof(struct Asset));
for (cf = GetZipCdirOffset(zcdir); n--; cf += ZIP_CFILE_HDRSIZE(zbase + cf)) {
CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zbase + cf));
if (!IsCompressionMethodSupported(
ZIP_CFILE_COMPRESSIONMETHOD(zbase + cf))) {
WARNF("(zip) don't understand zip compression method %d used by %`'.*s",
ZIP_CFILE_COMPRESSIONMETHOD(zbase + cf),
ZIP_CFILE_NAMESIZE(zbase + cf), ZIP_CFILE_NAME(zbase + cf));
continue;
}
hash = Hash(ZIP_CFILE_NAME(zbase + cf), ZIP_CFILE_NAMESIZE(zbase + cf));
step = 0;
do {
i = (hash + (step * (step + 1)) >> 1) & (m - 1);
++step;
} while (p[i].hash);
GetZipCfileTimestamps(zbase + cf, &lm, 0, 0, gmtoff);
p[i].hash = hash;
p[i].cf = cf;
p[i].lf = GetZipCfileOffset(zbase + cf);
p[i].istext = !!(ZIP_CFILE_INTERNALATTRIBUTES(zbase + cf) & kZipIattrText);
p[i].lastmodified = lm.tv_sec;
p[i].lastmodifiedstr = FormatUnixHttpDateTime(xmalloc(30), lm.tv_sec);
}
assets.p = p;
assets.n = m;
}
static bool OpenZip(bool force) {
int fd;
size_t n;
struct stat st;
uint8_t *m, *b, *d, *p;
if (stat(zpath, &st) != -1) {
if (force || st.st_ino != zst.st_ino || st.st_size > zst.st_size) {
if (st.st_ino == zst.st_ino) {
fd = zfd;
} else if ((fd = open(zpath, O_RDWR)) == -1) {
WARNF("(zip) open() error: %m");
return false;
}
if ((m = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) !=
MAP_FAILED) {
n = st.st_size;
if ((p = FindEmbeddedApe(m, n))) {
b = p;
n -= p - m;
} else {
b = m;
}
if ((d = GetZipCdir(b, n))) {
if (zmap) {
LOGIFNEG1(munmap(zmap, zbase + zsize - zmap));
}
zmap = m;
zbase = b;
zsize = n;
zcdir = d;
DCHECK(IsZipCdir32(zbase, zsize, zcdir - zbase) ||
IsZipCdir64(zbase, zsize, zcdir - zbase));
memcpy(&zst, &st, sizeof(st));
IndexAssets();
return true;
} else {
WARNF("(zip) couldn't locate central directory");
}
} else {
WARNF("(zip) mmap() error: %m");
}
}
} else {
WARNF("(zip) stat() error: %m");
}
return false;
}
static struct Asset *GetAssetZip(const char *path, size_t pathlen) {
uint32_t i, step, hash;
if (pathlen > 1 && path[0] == '/') ++path, --pathlen;
hash = Hash(path, pathlen);
for (step = 0;; ++step) {
i = (hash + (step * (step + 1)) >> 1) & (assets.n - 1);
if (!assets.p[i].hash) return NULL;
if (hash == assets.p[i].hash &&
pathlen == ZIP_CFILE_NAMESIZE(zbase + assets.p[i].cf) &&
memcmp(path, ZIP_CFILE_NAME(zbase + assets.p[i].cf), pathlen) == 0) {
return &assets.p[i];
}
}
}
static struct Asset *GetAssetFile(const char *path, size_t pathlen) {
size_t i;
struct Asset *a;
if (stagedirs.n) {
a = FreeLater(xcalloc(1, sizeof(struct Asset)));
a->file = FreeLater(xmalloc(sizeof(struct File)));
for (i = 0; i < stagedirs.n; ++i) {
LockInc(&shared->c.stats);
a->file->path.s = FreeLater(MergePaths(stagedirs.p[i].s, stagedirs.p[i].n,
path, pathlen, &a->file->path.n));
if (stat(a->file->path.s, &a->file->st) != -1) {
a->lastmodifiedstr = FormatUnixHttpDateTime(
FreeLater(xmalloc(30)),
(a->lastmodified = a->file->st.st_mtim.tv_sec));
return a;
} else {
LockInc(&shared->c.statfails);
}
}
}
return NULL;
}
static struct Asset *GetAsset(const char *path, size_t pathlen) {
char *path2;
struct Asset *a;
if (!(a = GetAssetFile(path, pathlen))) {
if (!(a = GetAssetZip(path, pathlen))) {
if (pathlen > 1 && path[pathlen - 1] != '/' &&
pathlen + 1 <= sizeof(slashpath)) {
memcpy(mempcpy(slashpath, path, pathlen), "/", 1);
a = GetAssetZip(slashpath, pathlen + 1);
}
}
}
return a;
}
static char *AppendHeader(char *p, const char *k, const char *v) {
if (!v) return p;
return AppendCrlf(stpcpy(stpcpy(stpcpy(p, k), ": "), v));
}
static char *AppendContentType(char *p, const char *ct) {
p = stpcpy(p, "Content-Type: ");
p = stpcpy(p, ct);
if ((istext = startswith(ct, "text/"))) {
if (!strchr(ct + 5, ';')) {
p = stpcpy(p, "; charset=utf-8");
}
if (!referrerpolicy && startswith(ct + 5, "html")) {
referrerpolicy = "no-referrer-when-downgrade";
}
}
hascontenttype = true;
return AppendCrlf(p);
}
static char *AppendExpires(char *p, int64_t t) {
struct tm tm;
gmtime_r(&t, &tm);
p = stpcpy(p, "Expires: ");
p = FormatHttpDateTime(p, &tm);
return AppendCrlf(p);
}
static char *AppendCache(char *p, int64_t seconds) {
if (seconds < 0) return p;
p = stpcpy(p, "Cache-Control: max-age=");
p = FormatUint64(p, seconds);
if (seconds) {
p = stpcpy(p, ", public");
} else {
p = stpcpy(p, ", no-store");
}
p = AppendCrlf(p);
return AppendExpires(p, (int64_t)shared->nowish + seconds);
}
static inline char *AppendContentLength(char *p, size_t n) {
p = stpcpy(p, "Content-Length: ");
p = FormatUint64(p, n);
return AppendCrlf(p);
}
static char *AppendContentRange(char *p, long a, long b, long c) {
p = stpcpy(p, "Content-Range: bytes ");
if (a >= 0 && b > 0) {
p = FormatUint64(p, a);
*p++ = '-';
p = FormatUint64(p, a + b - 1);
} else {
*p++ = '*';
}
*p++ = '/';
p = FormatUint64(p, c);
return AppendCrlf(p);
}
static bool Inflate(void *dp, size_t dn, const void *sp, size_t sn) {
int rc;
z_stream zs;
struct DeflateState ds;
LockInc(&shared->c.inflates);
if (IsTiny()) {
return undeflate(dp, dn, sp, sn, &ds) != -1;
} else {
zs.zfree = 0;
zs.zalloc = 0;
zs.next_in = sp;
zs.avail_in = sn;
zs.total_in = sn;
zs.next_out = dp;
zs.avail_out = dn;
zs.total_out = dn;
CHECK_EQ(Z_OK, inflateInit2(&zs, -MAX_WBITS));
switch ((rc = inflate(&zs, Z_NO_FLUSH))) {
case Z_STREAM_END:
CHECK_EQ(Z_OK, inflateEnd(&zs));
return true;
case Z_DATA_ERROR:
inflateEnd(&zs);
WARNF("(zip) Z_DATA_ERROR: %s", zs.msg);
return false;
case Z_NEED_DICT:
inflateEnd(&zs);
WARNF("(zip) Z_NEED_DICT");
return false;
case Z_MEM_ERROR:
DIEF("(zip) Z_MEM_ERROR");
default:
DIEF("(zip) inflate()→%d dn=%ld sn=%ld "
"next_in=%ld avail_in=%ld next_out=%ld avail_out=%ld",
rc, dn, sn, (char *)zs.next_in - (char *)sp, zs.avail_in,
(char *)zs.next_out - (char *)dp, zs.avail_out);
}
}
}
static bool Verify(void *data, size_t size, uint32_t crc) {
uint32_t got;
LockInc(&shared->c.verifies);
if (crc == (got = crc32_z(0, data, size))) {
return true;
} else {
LockInc(&shared->c.thiscorruption);
WARNF("(zip) corrupt zip file at %`'.*s had crc 0x%08x but expected 0x%08x",
msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a, got, crc);
return false;
}
}
static void *Deflate(const void *data, size_t size, size_t *out_size) {
void *res;
z_stream zs;
LockInc(&shared->c.deflates);
zs.zfree = 0;
zs.zalloc = 0;
CHECK_EQ(Z_OK, deflateInit2(&zs, 4, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL,
Z_DEFAULT_STRATEGY));
zs.next_in = data;
zs.avail_in = size;
zs.avail_out = compressBound(size);
zs.next_out = res = xmalloc(zs.avail_out);
CHECK_EQ(Z_STREAM_END, deflate(&zs, Z_FINISH));
CHECK_EQ(Z_OK, deflateEnd(&zs));
*out_size = zs.total_out;
return xrealloc(res, zs.total_out);
}
static void *LoadAsset(struct Asset *a, size_t *out_size) {
int mode;
size_t size;
uint8_t *data;
if (S_ISDIR(GetMode(a))) {
WARNF("(srvr) can't load directory");
return NULL;
}
if (!a->file) {
size = GetZipLfileUncompressedSize(zbase + a->lf);
if (size == SIZE_MAX || !(data = malloc(size + 1))) return NULL;
if (IsCompressed(a)) {
if (!Inflate(data, size, ZIP_LFILE_CONTENT(zbase + a->lf),
GetZipCfileCompressedSize(zbase + a->cf))) {
free(data);
return NULL;
}
} else {
memcpy(data, ZIP_LFILE_CONTENT(zbase + a->lf), size);
}
if (!Verify(data, size, ZIP_LFILE_CRC32(zbase + a->lf))) {
free(data);
return NULL;
}
data[size] = '\0';
if (out_size) *out_size = size;
return data;
} else {
LockInc(&shared->c.slurps);
return xslurp(a->file->path.s, out_size);
}
}
static wontreturn void PrintUsage(int fd, int rc) {
size_t n;
const char *p;
struct Asset *a;
if (!(a = GetAssetZip("/help.txt", 9)) || !(p = LoadAsset(a, &n))) {
fprintf(stderr, "error: /help.txt is not a zip asset\n");
exit(1);
}
__paginate(fd, p);
exit(rc);
}
static void AppendLogo(void) {
size_t n;
char *p, *q;
struct Asset *a;
if ((a = GetAsset("/redbean.png", 12)) && (p = LoadAsset(a, &n))) {
if ((q = EncodeBase64(p, n, &n))) {
appends(&outbuf, "\r\n");
free(q);
}
free(p);
}
}
static ssize_t Send(struct iovec *iov, int iovlen) {
ssize_t rc;
if ((rc = writer(client, iov, iovlen)) == -1) {
if (errno == ECONNRESET) {
LockInc(&shared->c.writeresets);
DEBUGF("(rsp) %s write reset", DescribeClient());
} else if (errno == EAGAIN) {
LockInc(&shared->c.writetimeouts);
WARNF("(rsp) %s write timeout", DescribeClient());
errno = 0;
} else {
LockInc(&shared->c.writeerrors);
WARNF("(rsp) %s write error: %m", DescribeClient());
}
connectionclose = true;
}
return rc;
}
static bool IsSslCompressed(void) {
return usessl && ssl.session->compression;
}
static char *CommitOutput(char *p) {
uint32_t crc;
size_t outbuflen;
if (!contentlength) {
outbuflen = appendz(outbuf).i;
if (istext && outbuflen >= 100) {
if (!IsTiny() && !IsSslCompressed()) {
p = stpcpy(p, "Vary: Accept-Encoding\r\n");
}
if (!IsTiny() && !IsSslCompressed() && ClientAcceptsGzip()) {
gzipped = true;
crc = crc32_z(0, outbuf, outbuflen);
WRITE32LE(gzip_footer + 0, crc);
WRITE32LE(gzip_footer + 4, outbuflen);
content = FreeLater(Deflate(outbuf, outbuflen, &contentlength));
DropOutput();
} else {
UseOutput();
}
} else {
UseOutput();
}
} else {
DropOutput();
}
return p;
}
static char *ServeDefaultErrorPage(char *p, unsigned code, const char *reason,
const char *details) {
p = AppendContentType(p, "text/html; charset=ISO-8859-1");
reason = FreeLater(EscapeHtml(reason, -1, 0));
appends(&outbuf, "\
\r\n\
%s\r\n", FreeLater(EscapeHtml(details, -1, 0))); } UseOutput(); return p; } static char *ServeErrorImpl(unsigned code, const char *reason, const char *details) { size_t n; char *p, *s; struct Asset *a; LockInc(&shared->c.errors); DropOutput(); p = SetStatus(code, reason); s = xasprintf("/%d.html", code); a = GetAsset(s, strlen(s)); free(s); if (!a) { return ServeDefaultErrorPage(p, code, reason, details); } else if (a->file) { LockInc(&shared->c.slurps); content = FreeLater(xslurp(a->file->path.s, &contentlength)); return AppendContentType(p, "text/html; charset=utf-8"); } else { content = (char *)ZIP_LFILE_CONTENT(zbase + a->lf); contentlength = GetZipCfileCompressedSize(zbase + a->cf); if (IsCompressed(a)) { n = GetZipLfileUncompressedSize(zbase + a->lf); if ((s = FreeLater(malloc(n))) && Inflate(s, n, content, contentlength)) { content = s; contentlength = n; } else { return ServeDefaultErrorPage(p, code, reason, details); } } if (Verify(content, contentlength, ZIP_LFILE_CRC32(zbase + a->lf))) { return AppendContentType(p, "text/html; charset=utf-8"); } else { return ServeDefaultErrorPage(p, code, reason, details); } } } static char *ServeErrorWithDetail(unsigned code, const char *reason, const char *details) { ERRORF("(srvr) server error: %d %s", code, reason); return ServeErrorImpl(code, reason, details); } static char *ServeError(unsigned code, const char *reason) { return ServeErrorWithDetail(code, reason, NULL); } static char *ServeFailure(unsigned code, const char *reason) { ERRORF("(srvr) failure: %d %s %s HTTP%02d %.*s %`'.*s %`'.*s %`'.*s %`'.*s", code, reason, DescribeClient(), msg.version, msg.xmethod.b - msg.xmethod.a, inbuf.p + msg.xmethod.a, HeaderLength(kHttpHost), HeaderData(kHttpHost), msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a, HeaderLength(kHttpReferer), HeaderData(kHttpReferer), HeaderLength(kHttpUserAgent), HeaderData(kHttpUserAgent)); return ServeErrorImpl(code, reason, NULL); } static ssize_t YieldGenerator(struct iovec v[3]) { int nresults, status; if (isyielding > 1) { do { if (!YL || lua_status(YL) != LUA_YIELD) return 0; // done yielding contentlength = 0; status = lua_resume(YL, NULL, 0, &nresults); if (status != LUA_OK && status != LUA_YIELD) { LogLuaError("resume", lua_tostring(YL, -1)); lua_pop(YL, 1); return -1; } lua_pop(YL, nresults); if (!contentlength) UseOutput(); // continue yielding if nothing to return to keep generator running } while (!contentlength); } DEBUGF("(lua) yielded with %ld bytes generated", contentlength); isyielding++; v[0].iov_base = content; v[0].iov_len = contentlength; return contentlength; } static void OnLuaServerPageCtrlc(int i) { lua_sigint(GL, i); } static int LuaCallWithYield(lua_State *L) { int status; // since yield may happen in OnHttpRequest and in ServeLua, // need to fully restart the yield generator; // the second set of headers is not going to be sent struct sigaction sa, saold; lua_State *co = lua_newthread(L); if (__replmode) { sa.sa_flags = SA_RESETHAND; sa.sa_handler = OnLuaServerPageCtrlc; sigemptyset(&sa.sa_mask); sigaction(SIGINT, &sa, &saold); } status = LuaCallWithTrace(L, 0, 0, co); if (__replmode) { sigaction(SIGINT, &saold, 0); } if (status == LUA_YIELD) { CHECK_GT(lua_gettop(L), 0); // make sure that coroutine is anchored YL = co; generator = YieldGenerator; if (!isyielding) isyielding = 1; istext = false; // reset istext flag to avoid zipping yielded chunk status = LUA_OK; } return status; } static ssize_t DeflateGenerator(struct iovec v[3]) { int i, rc; size_t no; void *res; int level; i = 0; if (!dg.t) { v[0].iov_base = kGzipHeader; v[0].iov_len = sizeof(kGzipHeader); ++dg.t; ++i; } else if (dg.t == 3) { return 0; } if (dg.t != 2) { CHECK_EQ(0, dg.s.avail_in); dg.s.next_in = (void *)(content + dg.i); dg.s.avail_in = MIN(dg.z, contentlength - dg.i); dg.c = crc32_z(dg.c, dg.s.next_in, dg.s.avail_in); dg.i += dg.s.avail_in; } dg.s.next_out = dg.b; dg.s.avail_out = dg.z; no = dg.s.avail_in; rc = deflate(&dg.s, dg.i < contentlength ? Z_SYNC_FLUSH : Z_FINISH); if (rc != Z_OK && rc != Z_STREAM_END) { DIEF("(zip) deflate()→%d oldin:%,zu/%,zu in:%,zu/%,zu out:%,zu/%,zu", rc, no, dg.z, dg.s.avail_in, dg.z, dg.s.avail_out, dg.z); } else { NOISEF("(zip) deflate()→%d oldin:%,zu/%,zu in:%,zu/%,zu out:%,zu/%,zu", rc, no, dg.z, dg.s.avail_in, dg.z, dg.s.avail_out, dg.z); } no = dg.z - dg.s.avail_out; if (no) { v[i].iov_base = dg.b; v[i].iov_len = no; ++i; } if (rc == Z_OK) { CHECK_GT(no, 0); if (dg.s.avail_out) { dg.t = 1; } else { dg.t = 2; } } else if (rc == Z_STREAM_END) { CHECK_EQ(contentlength, dg.i); CHECK_EQ(Z_OK, deflateEnd(&dg.s)); WRITE32LE(gzip_footer + 0, dg.c); WRITE32LE(gzip_footer + 4, contentlength); v[i].iov_base = gzip_footer; v[i].iov_len = sizeof(gzip_footer); dg.t = 3; } return v[0].iov_len + v[1].iov_len + v[2].iov_len; } static char *ServeAssetCompressed(struct Asset *a) { char *p; uint32_t crc; LockInc(&shared->c.deflates); LockInc(&shared->c.compressedresponses); DEBUGF("(srvr) ServeAssetCompressed()"); dg.t = 0; dg.i = 0; dg.c = 0; if (usessl) { dg.z = 512 + (rand64() & 1023); } else { dg.z = 65536; } gzipped = true; generator = DeflateGenerator; bzero(&dg.s, sizeof(dg.s)); CHECK_EQ(Z_OK, deflateInit2(&dg.s, 4, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY)); dg.b = FreeLater(malloc(dg.z)); p = SetStatus(200, "OK"); p = stpcpy(p, "Content-Encoding: gzip\r\n"); return p; } static ssize_t InflateGenerator(struct iovec v[3]) { int i, rc; size_t no; void *res; i = 0; if (!dg.t) { ++dg.t; } else if (dg.t == 3) { return 0; } if (dg.t != 2) { CHECK_EQ(0, dg.s.avail_in); dg.s.next_in = (void *)(content + dg.i); dg.s.avail_in = MIN(dg.z, contentlength - dg.i); dg.i += dg.s.avail_in; } dg.s.next_out = dg.b; dg.s.avail_out = dg.z; rc = inflate(&dg.s, Z_NO_FLUSH); if (rc != Z_OK && rc != Z_STREAM_END) DIEF("(zip) inflate()→%d", rc); no = dg.z - dg.s.avail_out; if (no) { v[i].iov_base = dg.b; v[i].iov_len = no; dg.c = crc32_z(dg.c, dg.b, no); ++i; } if (rc == Z_OK) { CHECK_GT(no, 0); dg.t = dg.s.avail_out ? 1 : 2; } else if (rc == Z_STREAM_END) { CHECK_EQ(Z_OK, inflateEnd(&dg.s)); CHECK_EQ(ZIP_CFILE_CRC32(zbase + dg.a->cf), dg.c); dg.t = 3; } return v[0].iov_len + v[1].iov_len + v[2].iov_len; } static char *ServeAssetDecompressed(struct Asset *a) { char *p; size_t size; uint32_t crc; LockInc(&shared->c.inflates); LockInc(&shared->c.decompressedresponses); size = GetZipCfileUncompressedSize(zbase + a->cf); DEBUGF("(srvr) ServeAssetDecompressed(%ld)→%ld", contentlength, size); if (msg.method == kHttpHead) { content = 0; contentlength = size; return SetStatus(200, "OK"); } else if (!IsTiny()) { dg.t = 0; dg.i = 0; dg.c = 0; dg.a = a; dg.z = 65536; CHECK_EQ(Z_OK, inflateInit2(&dg.s, -MAX_WBITS)); generator = InflateGenerator; dg.b = FreeLater(malloc(dg.z)); return SetStatus(200, "OK"); } else if ((p = FreeLater(malloc(size))) && Inflate(p, size, content, contentlength) && Verify(p, size, ZIP_CFILE_CRC32(zbase + a->cf))) { content = p; contentlength = size; return SetStatus(200, "OK"); } else { return ServeError(500, "Internal Server Error"); } } static inline char *ServeAssetIdentity(struct Asset *a, const char *ct) { LockInc(&shared->c.identityresponses); DEBUGF("(srvr) ServeAssetIdentity(%`'s)", ct); return SetStatus(200, "OK"); } static inline char *ServeAssetPrecompressed(struct Asset *a) { size_t size; uint32_t crc; DEBUGF("(srvr) ServeAssetPrecompressed()"); LockInc(&shared->c.precompressedresponses); gzipped = true; crc = ZIP_CFILE_CRC32(zbase + a->cf); size = GetZipCfileUncompressedSize(zbase + a->cf); WRITE32LE(gzip_footer + 0, crc); WRITE32LE(gzip_footer + 4, size); return SetStatus(200, "OK"); } static char *ServeAssetRange(struct Asset *a) { char *p; long rangestart, rangelength; DEBUGF("(srvr) ServeAssetRange()"); if (ParseHttpRange(HeaderData(kHttpRange), HeaderLength(kHttpRange), contentlength, &rangestart, &rangelength) && rangestart >= 0 && rangelength >= 0 && rangestart < contentlength && rangestart + rangelength <= contentlength) { LockInc(&shared->c.partialresponses); p = SetStatus(206, "Partial Content"); p = AppendContentRange(p, rangestart, rangelength, contentlength); content += rangestart; contentlength = rangelength; return p; } else { LockInc(&shared->c.badranges); WARNF("(client) bad range %`'.*s", HeaderLength(kHttpRange), HeaderData(kHttpRange)); p = SetStatus(416, "Range Not Satisfiable"); p = AppendContentRange(p, -1, -1, contentlength); content = ""; contentlength = 0; return p; } } static char *GetAssetPath(uint8_t *zcf, size_t *out_size) { char *p1, *p2; size_t n1, n2; p1 = ZIP_CFILE_NAME(zcf); n1 = ZIP_CFILE_NAMESIZE(zcf); p2 = xmalloc(1 + n1 + 1); n2 = 1 + n1 + 1; p2[0] = '/'; memcpy(p2 + 1, p1, n1); p2[1 + n1] = '\0'; if (out_size) *out_size = 1 + n1; return p2; } static bool IsHiddenPath(const char *s, size_t n) { size_t i; for (i = 0; i < hidepaths.n; ++i) { if (n >= hidepaths.p[i].n && !memcmp(s, hidepaths.p[i].s, hidepaths.p[i].n)) { return true; } } return false; } static char *GetBasicAuthorization(size_t *z) { size_t n; const char *p, *q; struct HttpSlice *g; g = msg.headers + (HasHeader(kHttpProxyAuthorization) ? kHttpProxyAuthorization : kHttpAuthorization); p = inbuf.p + g->a; n = g->b - g->a; if ((q = memchr(p, ' ', n)) && SlicesEqualCase(p, q - p, "Basic", 5)) { return DecodeBase64(q + 1, n - (q + 1 - p), z); } else { return NULL; } } static const char *GetSystemUrlLauncherCommand(void) { if (IsWindows()) { return "explorer"; } else if (IsXnu()) { return "open"; } else { return "xdg-open"; } } static void LaunchBrowser(const char *path) { int pid, ws; struct in_addr addr; const char *u, *prog; sigset_t chldmask, savemask; struct sigaction ignore, saveint, savequit; uint16_t port = 80; path = firstnonnull(path, "/"); // use the first server address if there is at least one server if (servers.n) { addr = servers.p[0].addr.sin_addr; port = ntohs(servers.p[0].addr.sin_port); } // assign a loopback address if no server or unknown server address if (!servers.n || !addr.s_addr) addr.s_addr = htonl(INADDR_LOOPBACK); if (*path != '/') path = gc(xasprintf("/%s", path)); if ((prog = commandv(GetSystemUrlLauncherCommand(), gc(malloc(PATH_MAX)), PATH_MAX))) { u = gc(xasprintf("http://%s:%d%s", inet_ntoa(addr), port, gc(EscapePath(path, -1, 0)))); DEBUGF("(srvr) opening browser with command %`'s %s", prog, u); ignore.sa_flags = 0; ignore.sa_handler = SIG_IGN; sigemptyset(&ignore.sa_mask); sigaction(SIGINT, &ignore, &saveint); sigaction(SIGQUIT, &ignore, &savequit); sigemptyset(&chldmask); sigaddset(&chldmask, SIGCHLD); sigprocmask(SIG_BLOCK, &chldmask, &savemask); CHECK_NE(-1, (pid = fork())); if (!pid) { setpgrp(); // ctrl-c'ing redbean shouldn't kill browser sigaction(SIGINT, &saveint, 0); sigaction(SIGQUIT, &savequit, 0); sigprocmask(SIG_SETMASK, &savemask, 0); execv(prog, (char *const[]){prog, u, 0}); _Exit(127); } while (wait4(pid, &ws, 0, 0) == -1) { CHECK_EQ(EINTR, errno); errno = 0; } sigaction(SIGINT, &saveint, 0); sigaction(SIGQUIT, &savequit, 0); sigprocmask(SIG_SETMASK, &savemask, 0); if (!(WIFEXITED(ws) && WEXITSTATUS(ws) == 0)) { WARNF("(srvr) command %`'s exited with %d", GetSystemUrlLauncherCommand(), WIFEXITED(ws) ? WEXITSTATUS(ws) : 128 + WEXITSTATUS(ws)); } } else { WARNF("(srvr) can't launch browser because %`'s isn't installed", GetSystemUrlLauncherCommand()); } } static char *BadMethod(void) { LockInc(&shared->c.badmethods); return stpcpy(ServeError(405, "Method Not Allowed"), "Allow: GET, HEAD\r\n"); } static int GetDecimalWidth(long x) { return LengthInt64Thousands(x); } static int GetOctalWidth(int x) { return !x ? 1 : x < 8 ? 2 : 1 + bsr(x) / 3; } static const char *DescribeCompressionRatio(char rb[8], uint8_t *zcf) { long percent; if (ZIP_CFILE_COMPRESSIONMETHOD(zcf) == kZipCompressionNone) { return "n/a"; } else { percent = lround(100 - (double)GetZipCfileCompressedSize(zcf) / GetZipCfileUncompressedSize(zcf) * 100); sprintf(rb, "%ld%%", MIN(999, MAX(-999, percent))); return rb; } } static char *ServeListing(void) { long x; ldiv_t y; int w[3]; uint8_t *zcf; struct tm tm; const char *and; struct rusage ru; char *p, *q, *path; struct timespec lastmod; char rb[8], tb[20], *rp[6]; size_t i, n, pathlen, rn[6]; LockInc(&shared->c.listingrequests); if (msg.method != kHttpGet && msg.method != kHttpHead) return BadMethod(); appends(&outbuf, "\ \r\n\ \r\n\
\r\n", strnlen(GetZipCdirComment(zcdir), GetZipCdirCommentSize(zcdir)), GetZipCdirComment(zcdir)); bzero(w, sizeof(w)); n = GetZipCdirRecords(zcdir); for (zcf = zbase + GetZipCdirOffset(zcdir); n--; zcf += ZIP_CFILE_HDRSIZE(zcf)) { CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zcf)); path = GetAssetPath(zcf, &pathlen); if (!IsHiddenPath(path, pathlen)) { w[0] = min(80, max(w[0], strwidth(path, 0) + 2)); w[1] = max(w[1], GetOctalWidth(GetZipCfileMode(zcf))); w[2] = max(w[2], GetDecimalWidth(GetZipCfileUncompressedSize(zcf))); } free(path); } n = GetZipCdirRecords(zcdir); for (zcf = zbase + GetZipCdirOffset(zcdir); n--; zcf += ZIP_CFILE_HDRSIZE(zcf)) { CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zcf)); path = GetAssetPath(zcf, &pathlen); if (!IsHiddenPath(path, pathlen)) { rp[0] = VisualizeControlCodes(path, pathlen, &rn[0]); rp[1] = EscapePath(path, pathlen, &rn[1]); rp[2] = EscapeHtml(rp[1], rn[1], &rn[2]); rp[3] = VisualizeControlCodes( ZIP_CFILE_COMMENT(zcf), strnlen(ZIP_CFILE_COMMENT(zcf), ZIP_CFILE_COMMENTSIZE(zcf)), &rn[3]); rp[4] = EscapeHtml(rp[0], rn[0], &rn[4]); GetZipCfileTimestamps(zcf, &lastmod, 0, 0, gmtoff); localtime_r(&lastmod.tv_sec, &tm); iso8601(tb, &tm); if (IsCompressionMethodSupported(ZIP_CFILE_COMPRESSIONMETHOD(zcf)) && IsAcceptablePath(path, pathlen)) { appendf(&outbuf, "%-*.*s %s %0*o %4s %,*ld %'s\r\n", rn[2], rp[2], w[0], rn[4], rp[4], tb, w[1], GetZipCfileMode(zcf), DescribeCompressionRatio(rb, zcf), w[2], GetZipCfileUncompressedSize(zcf), rp[3]); } else { appendf(&outbuf, "%-*.*s %s %0*o %4s %,*ld %'s\r\n", w[0], rn[4], rp[4], tb, w[1], GetZipCfileMode(zcf), DescribeCompressionRatio(rb, zcf), w[2], GetZipCfileUncompressedSize(zcf), rp[3]); } free(rp[4]); free(rp[3]); free(rp[2]); free(rp[1]); free(rp[0]); } free(path); } appends(&outbuf, "\\r\n"); p = SetStatus(200, "OK"); p = AppendContentType(p, "text/html"); p = AppendCache(p, 0); return CommitOutput(p); } static const char *MergeNames(const char *a, const char *b) { return FreeLater(xasprintf("%s.%s", a, b)); } static void AppendLong1(const char *a, long x) { if (x) appendf(&outbuf, "%s: %ld\r\n", a, x); } static void AppendLong2(const char *a, const char *b, long x) { if (x) appendf(&outbuf, "%s.%s: %ld\r\n", a, b, x); } static void AppendTimeval(const char *a, struct timeval *tv) { AppendLong2(a, "tv_sec", tv->tv_sec); AppendLong2(a, "tv_usec", tv->tv_usec); } static void AppendRusage(const char *a, struct rusage *ru) { AppendTimeval(MergeNames(a, "ru_utime"), &ru->ru_utime); AppendTimeval(MergeNames(a, "ru_stime"), &ru->ru_stime); AppendLong2(a, "ru_maxrss", ru->ru_maxrss); AppendLong2(a, "ru_ixrss", ru->ru_ixrss); AppendLong2(a, "ru_idrss", ru->ru_idrss); AppendLong2(a, "ru_isrss", ru->ru_isrss); AppendLong2(a, "ru_minflt", ru->ru_minflt); AppendLong2(a, "ru_majflt", ru->ru_majflt); AppendLong2(a, "ru_nswap", ru->ru_nswap); AppendLong2(a, "ru_inblock", ru->ru_inblock); AppendLong2(a, "ru_oublock", ru->ru_oublock); AppendLong2(a, "ru_msgsnd", ru->ru_msgsnd); AppendLong2(a, "ru_msgrcv", ru->ru_msgrcv); AppendLong2(a, "ru_nsignals", ru->ru_nsignals); AppendLong2(a, "ru_nvcsw", ru->ru_nvcsw); AppendLong2(a, "ru_nivcsw", ru->ru_nivcsw); } static void ServeCounters(void) { const long *c; const char *s; for (c = (const long *)&shared->c, s = kCounterNames; *s; ++c, s += strlen(s) + 1) { AppendLong1(s, *c); } } static char *ServeStatusz(void) { char *p; LockInc(&shared->c.statuszrequests); if (msg.method != kHttpGet && msg.method != kHttpHead) return BadMethod(); AppendLong1("pid", getpid()); AppendLong1("ppid", getppid()); AppendLong1("now", nowl()); AppendLong1("nowish", shared->nowish); AppendLong1("gmtoff", gmtoff); AppendLong1("CLK_TCK", CLK_TCK); AppendLong1("startserver", startserver); AppendLong1("lastmeltdown", shared->lastmeltdown); AppendLong1("workers", shared->workers); AppendLong1("assets.n", assets.n); #ifndef STATIC lua_State *L = GL; AppendLong1("lua.memory", lua_gc(L, LUA_GCCOUNT) * 1024 + lua_gc(L, LUA_GCCOUNTB)); #endif ServeCounters(); AppendRusage("server", &shared->server); AppendRusage("children", &shared->children); p = SetStatus(200, "OK"); p = AppendContentType(p, "text/plain"); p = AppendCache(p, 0); return CommitOutput(p); } static char *RedirectSlash(void) { size_t n; char *p, *e; LockInc(&shared->c.redirects); p = SetStatus(307, "Temporary Redirect"); p = stpcpy(p, "Location: "); e = EscapePath(url.path.p, url.path.n, &n); p = mempcpy(p, e, n); p = stpcpy(p, "/\r\n"); free(e); return p; } static char *ServeIndex(const char *path, size_t pathlen) { size_t i, n; char *p, *q; for (p = 0, i = 0; !p && i < ARRAYLEN(kIndexPaths); ++i) { q = MergePaths(path, pathlen, kIndexPaths[i], strlen(kIndexPaths[i]), &n); p = RoutePath(q, n); free(q); } return p; } static char *GetLuaResponse(void) { return luaheaderp ? luaheaderp : SetStatus(200, "OK"); } static bool ShouldServeCrashReportDetails(void) { uint32_t ip; uint16_t port; if (leakcrashreports) { return true; } else { GetRemoteAddr(&ip, &port); return IsLoopbackIp(ip) || IsPrivateIp(ip); } } static char *LuaOnHttpRequest(void) { char *error; lua_State *L = GL; effectivepath.p = url.path.p; effectivepath.n = url.path.n; lua_settop(L, 0); // clear Lua stack, as it needs to start fresh lua_getglobal(L, "OnHttpRequest"); if (LuaCallWithYield(L) == LUA_OK) { return CommitOutput(GetLuaResponse()); } else { LogLuaError("OnHttpRequest", lua_tostring(L, -1)); error = ServeErrorWithDetail( 500, "Internal Server Error", ShouldServeCrashReportDetails() ? lua_tostring(L, -1) : NULL); lua_pop(L, 1); // pop error return error; } } static char *ServeLua(struct Asset *a, const char *s, size_t n) { char *code; size_t codelen; lua_State *L = GL; LockInc(&shared->c.dynamicrequests); effectivepath.p = s; effectivepath.n = n; if ((code = FreeLater(LoadAsset(a, &codelen)))) { int status = luaL_loadbuffer(L, code, codelen, FreeLater(xasprintf("@%s", FreeLater(strndup(s, n))))); if (status == LUA_OK && LuaCallWithYield(L) == LUA_OK) { return CommitOutput(GetLuaResponse()); } else { char *error; LogLuaError("lua code", lua_tostring(L, -1)); error = ServeErrorWithDetail( 500, "Internal Server Error", ShouldServeCrashReportDetails() ? lua_tostring(L, -1) : NULL); lua_pop(L, 1); // pop error return error; } } return ServeError(500, "Internal Server Error"); } static char *HandleRedirect(struct Redirect *r) { int code; struct Asset *a; if (!r->code && (a = GetAsset(r->location.s, r->location.n))) { LockInc(&shared->c.rewrites); DEBUGF("(rsp) internal redirect to %`'s", r->location.s); if (!HasString(&loops, r->location.s, r->location.n)) { AddString(&loops, r->location.s, r->location.n); return RoutePath(r->location.s, r->location.n); } else { LockInc(&shared->c.loops); return SetStatus(508, "Loop Detected"); } } else if (msg.version < 10) { return ServeError(505, "HTTP Version Not Supported"); } else { LockInc(&shared->c.redirects); code = r->code; if (!code) code = 307; DEBUGF("(rsp) %d redirect to %`'s", code, r->location.s); return AppendHeader( SetStatus(code, GetHttpReason(code)), "Location", FreeLater(EncodeHttpHeaderValue(r->location.s, r->location.n, 0))); } } static char *HandleFolder(const char *path, size_t pathlen) { char *p; if (url.path.n && url.path.p[url.path.n - 1] != '/' && SlicesEqual(path, pathlen, url.path.p, url.path.n)) { return RedirectSlash(); } if ((p = ServeIndex(path, pathlen))) { return p; } else { LockInc(&shared->c.forbiddens); WARNF("(srvr) directory %`'.*s lacks index page", pathlen, path); return ServeError(403, "Forbidden"); } } static bool Reindex(void) { if (OpenZip(false)) { LockInc(&shared->c.reindexes); return true; } else { return false; } } static const char *LuaCheckPath(lua_State *L, int idx, size_t *pathlen) { const char *path; if (lua_isnoneornil(L, idx)) { path = url.path.p; *pathlen = url.path.n; } else { path = luaL_checklstring(L, idx, pathlen); if (!IsReasonablePath(path, *pathlen)) { WARNF("(srvr) bad path %`'.*s", *pathlen, path); luaL_argerror(L, idx, "bad path"); unreachable; } } return path; } static const char *LuaCheckHost(lua_State *L, int idx, size_t *hostlen) { const char *host; if (lua_isnoneornil(L, idx)) { host = url.host.p; *hostlen = url.host.n; } else { host = luaL_checklstring(L, idx, hostlen); if (!IsAcceptableHost(host, *hostlen)) { WARNF("(srvr) bad host %`'.*s", *hostlen, host); luaL_argerror(L, idx, "bad host"); unreachable; } } return host; } static void OnlyCallFromInitLua(lua_State *L, const char *api) { if (isinitialized) { luaL_error(L, "%s() should be called %s", api, "from the global scope of .init.lua"); unreachable; } } static void OnlyCallFromMainProcess(lua_State *L, const char *api) { if (__isworker) { luaL_error(L, "%s() should be called %s", api, "from .init.lua or the repl"); unreachable; } } static void OnlyCallDuringConnection(lua_State *L, const char *api) { if (!ishandlingconnection) { luaL_error(L, "%s() can only be called ", api, "while handling a connection"); unreachable; } } static void OnlyCallDuringRequest(lua_State *L, const char *api) { if (!ishandlingrequest) { luaL_error(L, "%s() can only be called %s", api, "while handling a request"); unreachable; } } static int LuaServe(lua_State *L, const char *api, char *impl(void)) { OnlyCallDuringRequest(L, api); luaheaderp = impl(); return 0; } static int LuaServeListing(lua_State *L) { return LuaServe(L, "ServeListing", ServeListing); } static int LuaServeStatusz(lua_State *L) { return LuaServe(L, "ServeStatusz", ServeStatusz); } static int LuaServeAsset(lua_State *L) { size_t pathlen; struct Asset *a; const char *path; OnlyCallDuringRequest(L, "ServeAsset"); path = LuaCheckPath(L, 1, &pathlen); if ((a = GetAsset(path, pathlen)) && !S_ISDIR(GetMode(a))) { luaheaderp = ServeAsset(a, path, pathlen); lua_pushboolean(L, true); } else { lua_pushboolean(L, false); } return 1; } static int LuaServeIndex(lua_State *L) { size_t pathlen; const char *path; OnlyCallDuringRequest(L, "ServeIndex"); path = LuaCheckPath(L, 1, &pathlen); lua_pushboolean(L, !!(luaheaderp = ServeIndex(path, pathlen))); return 1; } static int LuaServeRedirect(lua_State *L) { size_t loclen; const char *location, *eval; int code; OnlyCallDuringRequest(L, "ServeRedirect"); code = luaL_checkinteger(L, 1); if (!(300 <= code && code <= 399)) { luaL_argerror(L, 1, "bad status code"); unreachable; } location = luaL_checklstring(L, 2, &loclen); if (msg.version < 10) { (void)ServeError(505, "HTTP Version Not Supported"); lua_pushboolean(L, false); } else { if (!(eval = EncodeHttpHeaderValue(location, loclen, 0))) { luaL_argerror(L, 2, "invalid location"); unreachable; } VERBOSEF("(rsp) %d redirect to %`'s", code, location); luaheaderp = AppendHeader(SetStatus(code, GetHttpReason(code)), "Location", eval); free(eval); lua_pushboolean(L, true); } return 1; } static int LuaRoutePath(lua_State *L) { size_t pathlen; const char *path; OnlyCallDuringRequest(L, "RoutePath"); path = LuaCheckPath(L, 1, &pathlen); lua_pushboolean(L, !!(luaheaderp = RoutePath(path, pathlen))); return 1; } static int LuaRouteHost(lua_State *L) { size_t hostlen, pathlen; const char *host, *path; OnlyCallDuringRequest(L, "RouteHost"); host = LuaCheckHost(L, 1, &hostlen); path = LuaCheckPath(L, 2, &pathlen); lua_pushboolean(L, !!(luaheaderp = RouteHost(host, hostlen, path, pathlen))); return 1; } static int LuaRoute(lua_State *L) { size_t hostlen, pathlen; const char *host, *path; OnlyCallDuringRequest(L, "Route"); host = LuaCheckHost(L, 1, &hostlen); path = LuaCheckPath(L, 2, &pathlen); lua_pushboolean(L, !!(luaheaderp = Route(host, hostlen, path, pathlen))); return 1; } static int LuaRespond(lua_State *L, char *R(unsigned, const char *)) { char *p; int code; size_t reasonlen; const char *reason; OnlyCallDuringRequest(L, "Respond"); code = luaL_checkinteger(L, 1); if (!(100 <= code && code <= 999)) { luaL_argerror(L, 1, "bad status code"); unreachable; } if (lua_isnoneornil(L, 2)) { luaheaderp = R(code, GetHttpReason(code)); } else { reason = lua_tolstring(L, 2, &reasonlen); if ((p = EncodeHttpHeaderValue(reason, MIN(reasonlen, 128), 0))) { luaheaderp = R(code, p); free(p); } else { luaL_argerror(L, 2, "invalid"); unreachable; } } return 0; } static int LuaSetStatus(lua_State *L) { return LuaRespond(L, SetStatus); } static int LuaGetStatus(lua_State *L) { OnlyCallDuringRequest(L, "GetStatus"); if (!luaheaderp) { lua_pushnil(L); } else { lua_pushinteger(L, statuscode); } return 1; } static int LuaGetSslIdentity(lua_State *L) { const mbedtls_x509_crt *cert; OnlyCallDuringRequest(L, "GetSslIdentity"); if (!usessl) { lua_pushnil(L); } else { if (sslpskindex) { CHECK((sslpskindex - 1) >= 0 && (sslpskindex - 1) < psks.n); lua_pushlstring(L, psks.p[sslpskindex - 1].identity, psks.p[sslpskindex - 1].identity_len); } else { cert = mbedtls_ssl_get_peer_cert(&ssl); lua_pushstring(L, cert ? gc(FormatX509Name(&cert->subject)) : ""); } } return 1; } static int LuaServeError(lua_State *L) { return LuaRespond(L, ServeError); } static int LuaLoadAsset(lua_State *L) { void *data; struct Asset *a; const char *path; size_t size, pathlen; path = LuaCheckPath(L, 1, &pathlen); if ((a = GetAsset(path, pathlen))) { if (!a->file && !IsCompressed(a)) { /* fast path: this avoids extra copy */ data = ZIP_LFILE_CONTENT(zbase + a->lf); size = GetZipLfileUncompressedSize(zbase + a->lf); if (Verify(data, size, ZIP_LFILE_CRC32(zbase + a->lf))) { lua_pushlstring(L, data, size); return 1; } // any error from Verify has already been reported } else if ((data = LoadAsset(a, &size))) { lua_pushlstring(L, data, size); free(data); return 1; } else { WARNF("(srvr) could not load asset: %`'.*s", pathlen, path); } } else { WARNF("(srvr) could not find asset: %`'.*s", pathlen, path); } return 0; } static void GetDosLocalTime(int64_t utcunixts, uint16_t *out_time, uint16_t *out_date) { struct tm tm; CHECK_NOTNULL(localtime_r(&utcunixts, &tm)); *out_time = DOS_TIME(tm.tm_hour, tm.tm_min, tm.tm_sec); *out_date = DOS_DATE(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday + 1); } static void StoreAsset(char *path, size_t pathlen, char *data, size_t datalen, int mode) { int64_t ft; int i; uint32_t crc; char *comp, *p; long double now; struct Asset *a; struct iovec v[13]; uint8_t *cdir, era; const char *use; uint16_t gflags, iattrs, mtime, mdate, dosmode, method, disk; size_t oldcdirsize, oldcdiroffset, records, cdiroffset, cdirsize, complen, uselen; if (IsOpenbsd() || IsNetbsd() || IsWindows()) { DIEF("(cfg) StoreAsset() not available on Windows/NetBSD/OpenBSD yet"); } INFOF("Storing asset %`'s", path); disk = gflags = iattrs = 0; if (_isutf8(path, pathlen)) gflags |= kZipGflagUtf8; if (_istext(data, datalen)) iattrs |= kZipIattrText; crc = crc32_z(0, data, datalen); if (datalen < 100) { method = kZipCompressionNone; comp = 0; use = data; uselen = datalen; era = kZipEra1989; } else { comp = Deflate(data, datalen, &complen); if (complen < datalen) { method = kZipCompressionDeflate; use = comp; uselen = complen; era = kZipEra1993; } else { method = kZipCompressionNone; use = data; uselen = datalen; era = kZipEra1989; } } ////////////////////////////////////////////////////////////////////////////// CHECK_NE(-1, fcntl(zfd, F_SETLKW, &(struct flock){F_WRLCK})); OpenZip(false); now = nowl(); a = GetAssetZip(path, pathlen); if (!mode) mode = a ? GetMode(a) : 0644; if (!(mode & S_IFMT)) mode |= S_IFREG; if (pathlen > 1 && path[0] == '/') ++path, --pathlen; dosmode = !(mode & 0200) ? kNtFileAttributeReadonly : 0; ft = (now + MODERNITYSECONDS) * HECTONANOSECONDS; GetDosLocalTime(now, &mtime, &mdate); // local file header if (uselen >= 0xffffffff || datalen >= 0xffffffff) { era = kZipEra2001; v[2].iov_base = p = alloca((v[2].iov_len = 2 + 2 + 8 + 8)); p = WRITE16LE(p, kZipExtraZip64); p = WRITE16LE(p, 8 + 8); p = WRITE64LE(p, uselen); p = WRITE64LE(p, datalen); } else { v[2].iov_len = 0; v[2].iov_base = 0; } v[0].iov_base = p = alloca((v[0].iov_len = kZipLfileHdrMinSize)); p = WRITE32LE(p, kZipLfileHdrMagic); *p++ = era; *p++ = kZipOsDos; p = WRITE16LE(p, gflags); p = WRITE16LE(p, method); p = WRITE16LE(p, mtime); p = WRITE16LE(p, mdate); p = WRITE32LE(p, crc); p = WRITE32LE(p, MIN(uselen, 0xffffffff)); p = WRITE32LE(p, MIN(datalen, 0xffffffff)); p = WRITE16LE(p, pathlen); p = WRITE16LE(p, v[2].iov_len); v[1].iov_len = pathlen; v[1].iov_base = path; // file data v[3].iov_len = uselen; v[3].iov_base = use; // old central directory entries oldcdirsize = GetZipCdirSize(zcdir); oldcdiroffset = GetZipCdirOffset(zcdir); if (a) { // to remove an existing asset, // first copy the central directory part before its record v[4].iov_base = zbase + oldcdiroffset; v[4].iov_len = a->cf - oldcdiroffset; // and then the rest of the central directory v[5].iov_base = zbase + oldcdiroffset + (v[4].iov_len + ZIP_CFILE_HDRSIZE(zbase + a->cf)); v[5].iov_len = oldcdirsize - (v[4].iov_len + ZIP_CFILE_HDRSIZE(zbase + a->cf)); } else { v[4].iov_base = zbase + oldcdiroffset; v[4].iov_len = oldcdirsize; v[5].iov_base = 0; v[5].iov_len = 0; } // new central directory entry if (uselen >= 0xffffffff || datalen >= 0xffffffff || zsize >= 0xffffffff) { v[8].iov_base = p = alloca((v[8].iov_len = 2 + 2 + 8 + 8 + 8)); p = WRITE16LE(p, kZipExtraZip64); p = WRITE16LE(p, 8 + 8 + 8); p = WRITE64LE(p, uselen); p = WRITE64LE(p, datalen); p = WRITE64LE(p, zsize); } else { v[8].iov_len = 0; v[8].iov_base = 0; } v[9].iov_base = p = alloca((v[9].iov_len = 2 + 2 + 4 + 2 + 2 + 8 + 8 + 8)); p = WRITE16LE(p, kZipExtraNtfs); p = WRITE16LE(p, 4 + 2 + 2 + 8 + 8 + 8); p = WRITE32LE(p, 0); p = WRITE16LE(p, 1); p = WRITE16LE(p, 8 + 8 + 8); p = WRITE64LE(p, ft); p = WRITE64LE(p, ft); p = WRITE64LE(p, ft); v[6].iov_base = p = alloca((v[6].iov_len = kZipCfileHdrMinSize)); p = WRITE32LE(p, kZipCfileHdrMagic); *p++ = kZipCosmopolitanVersion; *p++ = kZipOsUnix; *p++ = era; *p++ = kZipOsDos; p = WRITE16LE(p, gflags); p = WRITE16LE(p, method); p = WRITE16LE(p, mtime); p = WRITE16LE(p, mdate); p = WRITE32LE(p, crc); p = WRITE32LE(p, MIN(uselen, 0xffffffff)); p = WRITE32LE(p, MIN(datalen, 0xffffffff)); p = WRITE16LE(p, pathlen); p = WRITE16LE(p, v[8].iov_len + v[9].iov_len); p = WRITE16LE(p, 0); p = WRITE16LE(p, disk); p = WRITE16LE(p, iattrs); p = WRITE16LE(p, dosmode); p = WRITE16LE(p, mode); p = WRITE32LE(p, MIN(zsize, 0xffffffff)); v[7].iov_len = pathlen; v[7].iov_base = path; // zip64 end of central directory cdiroffset = zsize + v[0].iov_len + v[1].iov_len + v[2].iov_len + v[3].iov_len; cdirsize = v[4].iov_len + v[5].iov_len + v[6].iov_len + v[7].iov_len + v[8].iov_len + v[9].iov_len; records = GetZipCdirRecords(zcdir) + !a; if (records >= 0xffff || cdiroffset >= 0xffffffff || cdirsize >= 0xffffffff) { v[10].iov_base = p = alloca((v[10].iov_len = kZipCdir64HdrMinSize + kZipCdir64LocatorSize)); p = WRITE32LE(p, kZipCdir64HdrMagic); p = WRITE64LE(p, 2 + 2 + 4 + 4 + 8 + 8 + 8 + 8); p = WRITE16LE(p, kZipCosmopolitanVersion); p = WRITE16LE(p, kZipEra2001); p = WRITE32LE(p, disk); p = WRITE32LE(p, disk); p = WRITE64LE(p, records); p = WRITE64LE(p, records); p = WRITE64LE(p, cdirsize); p = WRITE64LE(p, cdiroffset); p = WRITE32LE(p, kZipCdir64LocatorMagic); p = WRITE32LE(p, disk); p = WRITE64LE(p, cdiroffset + cdirsize); p = WRITE32LE(p, disk); } else { v[10].iov_len = 0; v[10].iov_base = 0; } // end of central directory v[12].iov_base = GetZipCdirComment(zcdir); v[12].iov_len = GetZipCdirCommentSize(zcdir); v[11].iov_base = p = alloca((v[11].iov_len = kZipCdirHdrMinSize)); p = WRITE32LE(p, kZipCdirHdrMagic); p = WRITE16LE(p, disk); p = WRITE16LE(p, disk); p = WRITE16LE(p, MIN(records, 0xffff)); p = WRITE16LE(p, MIN(records, 0xffff)); p = WRITE32LE(p, MIN(cdirsize, 0xffffffff)); p = WRITE32LE(p, MIN(cdiroffset, 0xffffffff)); p = WRITE16LE(p, v[12].iov_len); CHECK_NE(-1, lseek(zfd, zbase + zsize - zmap, SEEK_SET)); CHECK_NE(-1, WritevAll(zfd, v, 13)); CHECK_NE(-1, fcntl(zfd, F_SETLK, &(struct flock){F_UNLCK})); ////////////////////////////////////////////////////////////////////////////// OpenZip(false); free(comp); } static void StoreFile(char *path) { char *p; size_t plen, tlen; struct stat st; char *target = path; if (startswith(target, "./")) target += 2; tlen = strlen(target); if (!IsReasonablePath(target, tlen)) DIEF("(cfg) error: can't store %`'s: contains '.' or '..' segments", target); if (lstat(path, &st) == -1) DIEF("(cfg) error: can't stat %`'s: %m", path); if (!(p = xslurp(path, &plen))) DIEF("(cfg) error: can't read %`'s: %m", path); StoreAsset(target, tlen, p, plen, st.st_mode & 0777); free(p); } static void StorePath(const char *dirpath) { DIR *d; char *path; struct dirent *e; if (!isdirectory(dirpath) && !endswith(dirpath, "/")) return StoreFile(dirpath); if (!(d = opendir(dirpath))) DIEF("(cfg) error: can't open %`'s", dirpath); while ((e = readdir(d))) { if (strcmp(e->d_name, ".") == 0) continue; if (strcmp(e->d_name, "..") == 0) continue; path = gc(xjoinpaths(dirpath, e->d_name)); if (e->d_type == DT_DIR) { StorePath(path); } else { StoreFile(path); } } closedir(d); } static int LuaStoreAsset(lua_State *L) { const char *path, *data; size_t pathlen, datalen; int mode; path = LuaCheckPath(L, 1, &pathlen); if (pathlen > 0xffff) { return luaL_argerror(L, 1, "path too long"); } data = luaL_checklstring(L, 2, &datalen); mode = luaL_optinteger(L, 3, 0); StoreAsset(path, pathlen, data, datalen, mode); return 0; } static void ReseedRng(mbedtls_ctr_drbg_context *r, const char *s) { #ifndef UNSECURE if (unsecure) return; CHECK_EQ(0, mbedtls_ctr_drbg_reseed(r, (void *)s, strlen(s))); #endif } static wontreturn void LuaThrowTlsError(lua_State *L, const char *s, int r) { const char *code; code = gc(xasprintf("-0x%04x", -r)); if (!IsTiny()) { luaL_error(L, "tls %s failed (%s %s)", s, code, GetTlsError(r)); } else { luaL_error(L, "tls %s failed (grep %s)", s, code); } unreachable; } static void LogMessage(const char *d, const char *s, size_t n) { size_t n2, n3; char *s2, *s3; if (!LOGGABLE(kLogInfo)) return; while (n && (s[n - 1] == '\r' || s[n - 1] == '\n')) --n; if ((s2 = DecodeLatin1(s, n, &n2))) { if ((s3 = IndentLines(s2, n2, &n3, 1))) { INFOF("(stat) %s %,ld byte message\r\n%.*s", d, n, n3, s3); free(s3); } free(s2); } } static void LogBody(const char *d, const char *s, size_t n) { char *s2, *s3; size_t n2, n3; if (!n) return; if (!LOGGABLE(kLogInfo)) return; while (n && (s[n - 1] == '\r' || s[n - 1] == '\n')) --n; if ((s2 = VisualizeControlCodes(s, n, &n2))) { if ((s3 = IndentLines(s2, n2, &n3, 1))) { INFOF("(stat) %s %,ld byte payload\r\n%.*s", d, n, n3, s3); free(s3); } free(s2); } } static int LuaFetch(lua_State *L) { #define ssl nope // TODO(jart): make this file less huge char *p; ssize_t rc; bool usessl; uint32_t ip; struct Url url; int t, ret, sock, methodidx, hdridx; char *host, *port; struct TlsBio *bio; struct addrinfo *addr; struct Buffer inbuf; // shadowing intentional struct HttpMessage msg; // shadowing intentional struct HttpUnchunker u; const char *urlarg, *request, *body, *method; char *conlenhdr = ""; char *headers = 0; char *hosthdr = 0; char *agenthdr = brand; char *key, *val, *hdr; size_t keylen, vallen; size_t urlarglen, requestlen, paylen, bodylen; size_t g, i, n, hdrsize; int numredirects = 0, maxredirects = 5; bool followredirect = true; struct addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP, .ai_flags = AI_NUMERICSERV}; /* * Get args: url [, body | {method = "PUT", body = "..."}] */ urlarg = luaL_checklstring(L, 1, &urlarglen); if (lua_istable(L, 2)) { lua_settop(L, 2); // discard any extra arguments lua_getfield(L, 2, "body"); body = luaL_optlstring(L, -1, "", &bodylen); lua_getfield(L, 2, "method"); // use GET by default if no method is provided method = strtoupper(luaL_optstring(L, -1, kHttpMethod[kHttpGet])); lua_getfield(L, 2, "followredirect"); if (lua_isboolean(L, -1)) followredirect = lua_toboolean(L, -1); lua_getfield(L, 2, "maxredirects"); maxredirects = luaL_optinteger(L, -1, maxredirects); lua_getfield(L, 2, "numredirects"); numredirects = luaL_optinteger(L, -1, numredirects); lua_getfield(L, 2, "headers"); if (!lua_isnil(L, -1)) { if (!lua_istable(L, -1)) return luaL_argerror(L, 2, "invalid headers value; table expected"); lua_pushnil(L); while (lua_next(L, -2)) { if (lua_type(L, -2) == LUA_TSTRING) { // skip any non-string keys key = lua_tolstring(L, -2, &keylen); if (!IsValidHttpToken(key, keylen)) return luaL_argerror(L, 2, "invalid header name"); val = lua_tolstring(L, -1, &vallen); if (!(hdr = gc(EncodeHttpHeaderValue(val, vallen, 0)))) return luaL_argerror(L, 2, "invalid header value encoding"); // Content-Length and Connection will be overwritten; // skip them to avoid duplicates; // also allow unknown headers if ((hdridx = GetHttpHeader(key, keylen)) == -1 || hdridx != kHttpContentLength && hdridx != kHttpConnection) { if (hdridx == kHttpUserAgent) { agenthdr = hdr; } else if (hdridx == kHttpHost) { hosthdr = hdr; } else { appendd(&headers, key, keylen); appendw(&headers, READ16LE(": ")); appends(&headers, hdr); appendw(&headers, READ16LE("\r\n")); } } } lua_pop(L, 1); // pop the value, keep the key for the next iteration } } lua_settop(L, 2); // drop all added elements to keep the stack balanced } else if (lua_isnoneornil(L, 2)) { body = ""; bodylen = 0; method = kHttpMethod[kHttpGet]; } else { body = luaL_checklstring(L, 2, &bodylen); method = kHttpMethod[kHttpPost]; } // provide Content-Length header unless it's zero and not expected methodidx = GetHttpMethod(method, -1); if (bodylen > 0 || !(methodidx == kHttpGet || methodidx == kHttpHead || methodidx == kHttpTrace || methodidx == kHttpDelete || methodidx == kHttpConnect)) { conlenhdr = gc(xasprintf("Content-Length: %zu\r\n", bodylen)); } /* * Parse URL. */ gc(ParseUrl(urlarg, urlarglen, &url)); gc(url.params.p); usessl = false; if (url.scheme.n) { #ifndef UNSECURE if (!unsecure && url.scheme.n == 5 && !memcasecmp(url.scheme.p, "https", 5)) { usessl = true; } else #endif if (!(url.scheme.n == 4 && !memcasecmp(url.scheme.p, "http", 4))) { luaL_argerror(L, 1, "bad scheme"); unreachable; } } #ifndef UNSECURE if (usessl && !sslinitialized) TlsInit(); #endif if (url.host.n) { host = gc(strndup(url.host.p, url.host.n)); if (url.port.n) { port = gc(strndup(url.port.p, url.port.n)); #ifndef UNSECURE } else if (usessl) { port = "443"; #endif } else { port = "80"; } } else { ip = servers.n ? ntohl(servers.p[0].addr.sin_addr.s_addr) : INADDR_LOOPBACK; host = gc(xasprintf("%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16, ip >> 8, ip)); port = gc(xasprintf("%d", servers.n ? ntohs(servers.p[0].addr.sin_port) : 80)); } if (!IsAcceptableHost(host, -1)) { luaL_argerror(L, 1, "invalid host"); unreachable; } if (!hosthdr) hosthdr = gc(xasprintf("%s:%s", host, port)); url.fragment.p = 0, url.fragment.n = 0; url.scheme.p = 0, url.scheme.n = 0; url.user.p = 0, url.user.n = 0; url.pass.p = 0, url.pass.n = 0; url.host.p = 0, url.host.n = 0; url.port.p = 0, url.port.n = 0; if (!url.path.n || url.path.p[0] != '/') { p = gc(xmalloc(1 + url.path.n)); mempcpy(mempcpy(p, "/", 1), url.path.p, url.path.n); url.path.p = p; ++url.path.n; } /* * Create HTTP message. */ request = gc(xasprintf("%s %s HTTP/1.1\r\n" "Host: %s\r\n" "Connection: close\r\n" "User-Agent: %s\r\n" "%s%s" "\r\n%s", method, gc(EncodeUrl(&url, 0)), hosthdr, agenthdr, conlenhdr, headers ? headers : "", body)); requestlen = strlen(request); /* * Perform DNS lookup. */ DEBUGF("(ftch) client resolving %s", host); if ((rc = getaddrinfo(host, port, &hints, &addr)) != EAI_SUCCESS) { luaL_error(L, "getaddrinfo(%s:%s) error: EAI_%s %s", host, port, gai_strerror(rc), strerror(errno)); unreachable; } /* * Connect to server. */ ip = ntohl(((struct sockaddr_in *)addr->ai_addr)->sin_addr.s_addr); DEBUGF("(ftch) client connecting %hhu.%hhu.%hhu.%hhu:%d", ip >> 24, ip >> 16, ip >> 8, ip, ntohs(((struct sockaddr_in *)addr->ai_addr)->sin_port)); CHECK_NE(-1, (sock = GoodSocket(addr->ai_family, addr->ai_socktype, addr->ai_protocol, false, &timeout))); rc = connect(sock, addr->ai_addr, addr->ai_addrlen); freeaddrinfo(addr), addr = 0; if (rc == -1) { close(sock); luaL_error(L, "connect(%s:%s) error: %s", host, port, strerror(errno)); unreachable; } #ifndef UNSECURE if (usessl) { if (sslcliused) { mbedtls_ssl_session_reset(&sslcli); } else { ReseedRng(&rngcli, "child"); } sslcliused = true; DEBUGF("(ftch) client handshaking %`'s", host); if (!evadedragnetsurveillance) { mbedtls_ssl_set_hostname(&sslcli, host); } bio = gc(malloc(sizeof(struct TlsBio))); bio->fd = sock; bio->a = 0; bio->b = 0; bio->c = -1; mbedtls_ssl_set_bio(&sslcli, bio, TlsSend, 0, TlsRecvImpl); while ((ret = mbedtls_ssl_handshake(&sslcli))) { switch (ret) { case MBEDTLS_ERR_SSL_WANT_READ: break; case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: goto VerifyFailed; default: close(sock); LuaThrowTlsError(L, "handshake", ret); unreachable; } } LockInc(&shared->c.sslhandshakes); VERBOSEF("(ftch) shaken %s:%s %s %s", host, port, mbedtls_ssl_get_ciphersuite(&sslcli), mbedtls_ssl_get_version(&sslcli)); } #endif /* UNSECURE */ /* * Send HTTP Message. */ DEBUGF("(ftch) client sending %s request", method); #ifndef UNSECURE if (usessl) { ret = mbedtls_ssl_write(&sslcli, request, requestlen); if (ret != requestlen) { if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) goto VerifyFailed; close(sock); LuaThrowTlsError(L, "write", ret); unreachable; } } else #endif if (WRITE(sock, request, requestlen) != requestlen) { close(sock); luaL_error(L, "write error: %s", strerror(errno)); unreachable; } if (logmessages) { LogMessage("sent", request, requestlen); } /* * Handle response. */ bzero(&inbuf, sizeof(inbuf)); InitHttpMessage(&msg, kHttpResponse); for (hdrsize = paylen = t = 0;;) { if (inbuf.n == inbuf.c) { inbuf.c += 1000; inbuf.c += inbuf.c >> 1; inbuf.p = realloc(inbuf.p, inbuf.c); } NOISEF("(ftch) client reading"); #ifndef UNSECURE if (usessl) { if ((rc = mbedtls_ssl_read(&sslcli, inbuf.p + inbuf.n, inbuf.c - inbuf.n)) < 0) { if (rc == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { rc = 0; } else { close(sock); free(inbuf.p); DestroyHttpMessage(&msg); LuaThrowTlsError(L, "read", rc); unreachable; } } } else #endif if ((rc = READ(sock, inbuf.p + inbuf.n, inbuf.c - inbuf.n)) == -1) { close(sock); free(inbuf.p); DestroyHttpMessage(&msg); luaL_error(L, "read error: %s", strerror(errno)); unreachable; } g = rc; inbuf.n += g; switch (t) { case kHttpClientStateHeaders: if (!g) { WARNF("(ftch) HTTP client %s error", "EOF headers"); goto TransportError; } rc = ParseHttpMessage(&msg, inbuf.p, inbuf.n); if (rc == -1) { WARNF("(ftch) HTTP client %s error", "ParseHttpMessage"); goto TransportError; } if (rc) { hdrsize = rc; if (logmessages) { LogMessage("received", inbuf.p, hdrsize); } if (100 <= msg.status && msg.status <= 199) { if ((HasHeader(kHttpContentLength) && !HeaderEqualCase(kHttpContentLength, "0")) || (HasHeader(kHttpTransferEncoding) && !HeaderEqualCase(kHttpTransferEncoding, "identity"))) { WARNF("(ftch) HTTP client %s error", "Content-Length #1"); goto TransportError; } DestroyHttpMessage(&msg); InitHttpMessage(&msg, kHttpResponse); memmove(inbuf.p, inbuf.p + hdrsize, inbuf.n - hdrsize); inbuf.n -= hdrsize; break; } if (msg.status == 204 || msg.status == 304) { goto Finished; } if (HasHeader(kHttpTransferEncoding) && !HeaderEqualCase(kHttpTransferEncoding, "identity")) { if (HeaderEqualCase(kHttpTransferEncoding, "chunked")) { t = kHttpClientStateBodyChunked; bzero(&u, sizeof(u)); goto Chunked; } else { WARNF("(ftch) HTTP client %s error", "Transfer-Encoding"); goto TransportError; } } else if (HasHeader(kHttpContentLength)) { rc = ParseContentLength(HeaderData(kHttpContentLength), HeaderLength(kHttpContentLength)); if (rc == -1) { WARNF("(ftch) HTTP client %s error", "Content-Length #2"); goto TransportError; } if ((paylen = rc) <= inbuf.n - hdrsize) { goto Finished; } else { t = kHttpClientStateBodyLengthed; } } else { t = kHttpClientStateBody; } } break; case kHttpClientStateBody: if (!g) { paylen = inbuf.n; goto Finished; } break; case kHttpClientStateBodyLengthed: if (!g) { WARNF("(ftch) HTTP client %s error", "EOF body"); goto TransportError; } if (inbuf.n - hdrsize >= paylen) { goto Finished; } break; case kHttpClientStateBodyChunked: Chunked: rc = Unchunk(&u, inbuf.p + hdrsize, inbuf.n - hdrsize, &paylen); if (rc == -1) { WARNF("(ftch) HTTP client %s error", "Unchunk"); goto TransportError; } if (rc) goto Finished; break; default: unreachable; } } Finished: if (paylen && logbodies) LogBody("received", inbuf.p + hdrsize, paylen); VERBOSEF("(ftch) completed %s HTTP%02d %d %s %`'.*s", method, msg.version, msg.status, urlarg, HeaderLength(kHttpServer), HeaderData(kHttpServer)); if (followredirect && HasHeader(kHttpLocation) && (msg.status == 301 || msg.status == 308 || // permanent redirects msg.status == 302 || msg.status == 307 || // temporary redirects msg.status == 303 /* see other; non-GET changes to GET, body lost */) && numredirects < maxredirects) { // if 303, then remove body and set method to GET if (msg.status == 303) { body = ""; bodylen = 0; method = kHttpMethod[kHttpGet]; } // create table if needed if (!lua_istable(L, 2)) { lua_settop(L, 1); // pop body if present lua_createtable(L, 0, 3); // body, method, numredirects } lua_pushlstring(L, body, bodylen); lua_setfield(L, -2, "body"); lua_pushstring(L, method); lua_setfield(L, -2, "method"); lua_pushinteger(L, numredirects + 1); lua_setfield(L, -2, "numredirects"); // replace URL with Location header lua_pushlstring(L, HeaderData(kHttpLocation), HeaderLength(kHttpLocation)); lua_replace(L, -3); DestroyHttpMessage(&msg); free(inbuf.p); close(sock); return LuaFetch(L); } else { lua_pushinteger(L, msg.status); LuaPushHeaders(L, &msg, inbuf.p); lua_pushlstring(L, inbuf.p + hdrsize, paylen); DestroyHttpMessage(&msg); free(inbuf.p); close(sock); return 3; } TransportError: DestroyHttpMessage(&msg); free(inbuf.p); close(sock); luaL_error(L, "transport error"); unreachable; #ifndef UNSECURE VerifyFailed: LockInc(&shared->c.sslverifyfailed); close(sock); LuaThrowTlsError( L, gc(DescribeSslVerifyFailure(sslcli.session_negotiate->verify_result)), ret); unreachable; #endif #undef ssl } static int LuaGetDate(lua_State *L) { lua_pushinteger(L, shared->nowish); return 1; } static int LuaGetHttpVersion(lua_State *L) { OnlyCallDuringRequest(L, "GetHttpVersion"); lua_pushinteger(L, msg.version); return 1; } static int LuaGetRedbeanVersion(lua_State *L) { lua_pushinteger(L, VERSION); return 1; } static int LuaGetMethod(lua_State *L) { OnlyCallDuringRequest(L, "GetMethod"); if (msg.method) { lua_pushstring(L, kHttpMethod[msg.method]); } else { lua_pushlstring(L, inbuf.p + msg.xmethod.a, msg.xmethod.b - msg.xmethod.a); } return 1; } static int LuaGetAddr(lua_State *L, void GetAddr(uint32_t *, uint16_t *)) { uint32_t ip; uint16_t port; GetAddr(&ip, &port); lua_pushinteger(L, ip); lua_pushinteger(L, port); return 2; } static int LuaGetServerAddr(lua_State *L) { OnlyCallDuringConnection(L, "GetServerAddr"); return LuaGetAddr(L, GetServerAddr); } static int LuaGetClientAddr(lua_State *L) { OnlyCallDuringConnection(L, "GetClientAddr"); return LuaGetAddr(L, GetClientAddr); } static int LuaGetRemoteAddr(lua_State *L) { OnlyCallDuringRequest(L, "GetRemoteAddr"); return LuaGetAddr(L, GetRemoteAddr); } static int LuaLog(lua_State *L) { int level, line; lua_Debug ar; const char *msg, *module; level = luaL_checkinteger(L, 1); if (LOGGABLE(level)) { msg = luaL_checkstring(L, 2); if (lua_getstack(L, 1, &ar) && lua_getinfo(L, "Sl", &ar)) { module = ar.short_src; line = ar.currentline; } else { module = gc(strndup(effectivepath.p, effectivepath.n)); line = -1; } flogf(level, module, line, NULL, "%s", msg); } return 0; } static int LuaEncodeSmth(lua_State *L, int Encoder(lua_State *, char **, char *, int)) { int useoutput = false; int maxdepth = 64; char *numformat = "%.14g"; char *p = 0; if (lua_istable(L, 2)) { lua_settop(L, 2); // discard any extra arguments lua_getfield(L, 2, "useoutput"); // ignore useoutput outside of request handling if (ishandlingrequest && lua_isboolean(L, -1)) { useoutput = lua_toboolean(L, -1); } lua_getfield(L, 2, "numformat"); numformat = luaL_optstring(L, -1, numformat); } lua_settop(L, 1); // keep the passed argument on top Encoder(L, useoutput ? &outbuf : &p, numformat, -1); if (useoutput) { lua_pushnil(L); } else { lua_pushstring(L, p); free(p); } return 1; } static int LuaEncodeJson(lua_State *L) { return LuaEncodeSmth(L, LuaEncodeJsonData); } static int LuaEncodeLua(lua_State *L) { return LuaEncodeSmth(L, LuaEncodeLuaData); } static int LuaGetUrl(lua_State *L) { char *p; size_t n; OnlyCallDuringRequest(L, "GetUrl"); p = EncodeUrl(&url, &n); lua_pushlstring(L, p, n); free(p); return 1; } static int LuaGetScheme(lua_State *L) { OnlyCallDuringRequest(L, "GetScheme"); LuaPushUrlView(L, &url.scheme); return 1; } static int LuaGetPath(lua_State *L) { OnlyCallDuringRequest(L, "GetPath"); LuaPushUrlView(L, &url.path); return 1; } static int LuaGetEffectivePath(lua_State *L) { OnlyCallDuringRequest(L, "GetEffectivePath"); lua_pushlstring(L, effectivepath.p, effectivepath.n); return 1; } static int LuaGetFragment(lua_State *L) { OnlyCallDuringRequest(L, "GetFragment"); LuaPushUrlView(L, &url.fragment); return 1; } static int LuaGetUser(lua_State *L) { size_t n; const char *p, *q; OnlyCallDuringRequest(L, "GetUser"); if (url.user.p) { LuaPushUrlView(L, &url.user); } else if ((p = GetBasicAuthorization(&n))) { if (!(q = memchr(p, ':', n))) q = p + n; lua_pushlstring(L, p, q - p); free(p); } else { lua_pushnil(L); } return 1; } static int LuaGetPass(lua_State *L) { size_t n; const char *p, *q; OnlyCallDuringRequest(L, "GetPass"); if (url.user.p) { LuaPushUrlView(L, &url.pass); } else if ((p = GetBasicAuthorization(&n))) { if ((q = memchr(p, ':', n))) { lua_pushlstring(L, q + 1, p + n - (q + 1)); } else { lua_pushnil(L); } free(p); } else { lua_pushnil(L); } return 1; } static int LuaGetHost(lua_State *L) { char b[16]; OnlyCallDuringRequest(L, "GetHost"); if (url.host.n) { lua_pushlstring(L, url.host.p, url.host.n); } else { inet_ntop(AF_INET, &serveraddr->sin_addr.s_addr, b, sizeof(b)); lua_pushstring(L, b); } return 1; } static int LuaGetPort(lua_State *L) { int i, x = 0; OnlyCallDuringRequest(L, "GetPort"); for (i = 0; i < url.port.n; ++i) x = url.port.p[i] - '0' + x * 10; if (!x) x = ntohs(serveraddr->sin_port); lua_pushinteger(L, x); return 1; } static int LuaGetBody(lua_State *L) { OnlyCallDuringRequest(L, "GetBody"); lua_pushlstring(L, inbuf.p + hdrsize, payloadlength); return 1; } static int LuaGetHeader(lua_State *L) { int h; const char *key; size_t i, keylen; OnlyCallDuringRequest(L, "GetHeader"); key = luaL_checklstring(L, 1, &keylen); if ((h = GetHttpHeader(key, keylen)) != -1) { if (msg.headers[h].a) { return LuaPushHeader(L, &msg, inbuf.p, h); } } else { for (i = 0; i < msg.xheaders.n; ++i) { if (SlicesEqualCase(key, keylen, inbuf.p + msg.xheaders.p[i].k.a, msg.xheaders.p[i].k.b - msg.xheaders.p[i].k.a)) { LuaPushLatin1(L, inbuf.p + msg.xheaders.p[i].v.a, msg.xheaders.p[i].v.b - msg.xheaders.p[i].v.a); return 1; } } } lua_pushnil(L); return 1; } static int LuaGetHeaders(lua_State *L) { OnlyCallDuringRequest(L, "GetHeaders"); return LuaPushHeaders(L, &msg, inbuf.p); } static int LuaSetHeader(lua_State *L) { int h; ssize_t rc; char *p, *q; const char *key, *val, *eval; size_t i, keylen, vallen, evallen; OnlyCallDuringRequest(L, "SetHeader"); key = luaL_checklstring(L, 1, &keylen); val = luaL_checklstring(L, 2, &vallen); if ((h = GetHttpHeader(key, keylen)) == -1) { if (!IsValidHttpToken(key, keylen)) { luaL_argerror(L, 1, "invalid"); unreachable; } } if (!(eval = EncodeHttpHeaderValue(val, vallen, &evallen))) { luaL_argerror(L, 2, "invalid"); unreachable; } p = GetLuaResponse(); while (p - hdrbuf.p + keylen + 2 + evallen + 2 + 512 > hdrbuf.n) { hdrbuf.n += hdrbuf.n >> 1; q = xrealloc(hdrbuf.p, hdrbuf.n); luaheaderp = p = q + (p - hdrbuf.p); hdrbuf.p = q; } switch (h) { case kHttpConnection: if (!SlicesEqualCase(eval, evallen, "close", 5)) { luaL_argerror(L, 2, "unsupported"); unreachable; } connectionclose = true; break; case kHttpContentType: p = AppendContentType(p, eval); break; case kHttpServer: branded = true; p = AppendHeader(p, "Server", eval); break; case kHttpReferrerPolicy: referrerpolicy = FreeLater(strdup(eval)); break; default: p = AppendHeader(p, key, eval); break; } luaheaderp = p; free(eval); return 0; } static int LuaGetCookie(lua_State *L) { char *cookie = 0, *cookietmpl, *cookieval; OnlyCallDuringRequest(L, "GetCookie"); cookietmpl = gc(xasprintf(" %s=", luaL_checkstring(L, 1))); if (HasHeader(kHttpCookie)) { appends(&cookie, " "); // prepend space to simplify cookie search appendd(&cookie, HeaderData(kHttpCookie), HeaderLength(kHttpCookie)); } if (cookie && (cookieval = strstr(cookie, cookietmpl))) { cookieval += strlen(cookietmpl); lua_pushlstring(L, cookieval, strchrnul(cookieval, ';') - cookieval); } else { lua_pushnil(L); } if (cookie) free(cookie); return 1; } static int LuaSetCookie(lua_State *L) { const char *key, *val; size_t keylen, vallen; char *expires, *samesite = ""; char *buf = 0; bool ishostpref, issecurepref; const char *hostpref = "__Host-"; const char *securepref = "__Secure-"; OnlyCallDuringRequest(L, "SetCookie"); key = luaL_checklstring(L, 1, &keylen); val = luaL_checklstring(L, 2, &vallen); if (!IsValidHttpToken(key, keylen)) { luaL_argerror(L, 1, "invalid"); unreachable; } if (!IsValidCookieValue(val, vallen)) { luaL_argerror(L, 2, "invalid"); unreachable; } ishostpref = keylen > strlen(hostpref) && SlicesEqual(key, strlen(hostpref), hostpref, strlen(hostpref)); issecurepref = keylen > strlen(securepref) && SlicesEqual(key, strlen(securepref), securepref, strlen(securepref)); if ((ishostpref || issecurepref) && !usessl) { luaL_argerror( L, 1, gc(xasprintf("%s and %s prefixes require SSL", hostpref, securepref))); unreachable; } appends(&buf, key); appends(&buf, "="); appends(&buf, val); if (lua_istable(L, 3)) { if (lua_getfield(L, 3, "expires") != LUA_TNIL || lua_getfield(L, 3, "Expires") != LUA_TNIL) { if (lua_isnumber(L, -1)) { expires = FormatUnixHttpDateTime(FreeLater(xmalloc(30)), lua_tonumber(L, -1)); } else { expires = lua_tostring(L, -1); if (!ParseHttpDateTime(expires, -1)) { luaL_argerror(L, 3, "invalid data format in Expires"); unreachable; } } appends(&buf, "; Expires="); appends(&buf, expires); } if ((lua_getfield(L, 3, "maxage") == LUA_TNUMBER || lua_getfield(L, 3, "MaxAge") == LUA_TNUMBER) && lua_isinteger(L, -1)) { appends(&buf, "; Max-Age="); appends(&buf, lua_tostring(L, -1)); } if (lua_getfield(L, 3, "samesite") == LUA_TSTRING || lua_getfield(L, 3, "SameSite") == LUA_TSTRING) { samesite = lua_tostring(L, -1); // also used in the Secure check appends(&buf, "; SameSite="); appends(&buf, samesite); } // Secure attribute is required for __Host and __Secure prefixes // as well as for the SameSite=None if (ishostpref || issecurepref || !strcmp(samesite, "None") || ((lua_getfield(L, 3, "secure") == LUA_TBOOLEAN || lua_getfield(L, 3, "Secure") == LUA_TBOOLEAN) && lua_toboolean(L, -1))) { appends(&buf, "; Secure"); } if (!ishostpref && (lua_getfield(L, 3, "domain") == LUA_TSTRING || lua_getfield(L, 3, "Domain") == LUA_TSTRING)) { appends(&buf, "; Domain="); appends(&buf, lua_tostring(L, -1)); } if (ishostpref || lua_getfield(L, 3, "path") == LUA_TSTRING || lua_getfield(L, 3, "Path") == LUA_TSTRING) { appends(&buf, "; Path="); appends(&buf, ishostpref ? "/" : lua_tostring(L, -1)); } if ((lua_getfield(L, 3, "httponly") == LUA_TBOOLEAN || lua_getfield(L, 3, "HttpOnly") == LUA_TBOOLEAN) && lua_toboolean(L, -1)) { appends(&buf, "; HttpOnly"); } } DEBUGF("(srvr) Set-Cookie: %s", buf); // empty the stack and push header name/value lua_settop(L, 0); lua_pushliteral(L, "Set-Cookie"); lua_pushstring(L, buf); free(buf); return LuaSetHeader(L); } static int LuaHasParam(lua_State *L) { size_t i, n; const char *s; OnlyCallDuringRequest(L, "HasParam"); s = luaL_checklstring(L, 1, &n); for (i = 0; i < url.params.n; ++i) { if (SlicesEqual(s, n, url.params.p[i].key.p, url.params.p[i].key.n)) { lua_pushboolean(L, true); return 1; } } lua_pushboolean(L, false); return 1; } static int LuaGetParam(lua_State *L) { size_t i, n; const char *s; OnlyCallDuringRequest(L, "GetParam"); s = luaL_checklstring(L, 1, &n); for (i = 0; i < url.params.n; ++i) { if (SlicesEqual(s, n, url.params.p[i].key.p, url.params.p[i].key.n)) { if (url.params.p[i].val.p) { lua_pushlstring(L, url.params.p[i].val.p, url.params.p[i].val.n); return 1; } else { break; } } } lua_pushnil(L); return 1; } static int LuaGetParams(lua_State *L) { OnlyCallDuringRequest(L, "GetParams"); LuaPushUrlParams(L, &url.params); return 1; } static int LuaWrite(lua_State *L) { size_t size; const char *data; OnlyCallDuringRequest(L, "Write"); if (!lua_isnil(L, 1)) { data = luaL_checklstring(L, 1, &size); appendd(&outbuf, data, size); } return 0; } static dontinline int LuaProgramInt(lua_State *L, void P(long)) { P(luaL_checkinteger(L, 1)); return 0; } static int LuaProgramPort(lua_State *L) { OnlyCallFromInitLua(L, "ProgramPort"); return LuaProgramInt(L, ProgramPort); } static int LuaProgramCache(lua_State *L) { OnlyCallFromMainProcess(L, "ProgramCache"); return LuaProgramInt(L, ProgramCache); } static int LuaProgramTimeout(lua_State *L) { OnlyCallFromInitLua(L, "ProgramTimeout"); return LuaProgramInt(L, ProgramTimeout); } static int LuaProgramUid(lua_State *L) { OnlyCallFromInitLua(L, "ProgramUid"); return LuaProgramInt(L, ProgramUid); } static int LuaProgramGid(lua_State *L) { OnlyCallFromInitLua(L, "ProgramGid"); return LuaProgramInt(L, ProgramGid); } static int LuaGetClientFd(lua_State *L) { OnlyCallDuringConnection(L, "GetClientFd"); lua_pushinteger(L, client); return 1; } static int LuaIsClientUsingSsl(lua_State *L) { OnlyCallDuringConnection(L, "IsClientUsingSsl"); lua_pushboolean(L, usessl); return 1; } static int LuaProgramSslTicketLifetime(lua_State *L) { OnlyCallFromInitLua(L, "ProgramSslTicketLifetime"); return LuaProgramInt(L, ProgramSslTicketLifetime); } static int LuaProgramUniprocess(lua_State *L) { OnlyCallFromInitLua(L, "ProgramUniprocess"); if (!lua_isboolean(L, 1) && !lua_isnoneornil(L, 1)) { return luaL_argerror(L, 1, "invalid uniprocess mode; boolean expected"); } lua_pushboolean(L, uniprocess); if (lua_isboolean(L, 1)) uniprocess = lua_toboolean(L, 1); return 1; } static dontinline int LuaProgramString(lua_State *L, void P(const char *)) { P(luaL_checkstring(L, 1)); return 0; } static int LuaProgramAddr(lua_State *L) { OnlyCallFromInitLua(L, "ProgramAddr"); return LuaProgramString(L, ProgramAddr); } static int LuaProgramBrand(lua_State *L) { OnlyCallFromMainProcess(L, "ProgramBrand"); return LuaProgramString(L, ProgramBrand); } static int LuaProgramDirectory(lua_State *L) { return LuaProgramString(L, ProgramDirectory); } static int LuaProgramLogPath(lua_State *L) { OnlyCallFromInitLua(L, "ProgramLogPath"); return LuaProgramString(L, ProgramLogPath); } static int LuaProgramPidPath(lua_State *L) { OnlyCallFromInitLua(L, "ProgramPidPath"); return LuaProgramString(L, ProgramPidPath); } static int LuaProgramSslPresharedKey(lua_State *L) { struct Psk psk; size_t n1, n2, i; const char *p1, *p2; OnlyCallFromMainProcess(L, "ProgramSslPresharedKey"); p1 = luaL_checklstring(L, 1, &n1); p2 = luaL_checklstring(L, 2, &n2); if (!n1 || n1 > MBEDTLS_PSK_MAX_LEN || !n2) { luaL_argerror(L, 1, "bad preshared key length"); unreachable; } psk.key = memcpy(malloc(n1), p1, n1); psk.key_len = n1; psk.identity = memcpy(malloc(n2), p2, n2); psk.identity_len = n2; for (i = 0; i < psks.n; ++i) { if (SlicesEqual(psk.identity, psk.identity_len, psks.p[i].identity, psks.p[i].identity_len)) { mbedtls_platform_zeroize(psks.p[i].key, psks.p[i].key_len); free(psks.p[i].key); free(psks.p[i].identity); psks.p[i] = psk; return 0; } } psks.p = realloc(psks.p, ++psks.n * sizeof(*psks.p)); psks.p[psks.n - 1] = psk; return 0; } static int LuaProgramSslCiphersuite(lua_State *L) { mbedtls_ssl_ciphersuite_t *suite; OnlyCallFromInitLua(L, "ProgramSslCiphersuite"); if (!(suite = GetCipherSuite(luaL_checkstring(L, 1)))) { luaL_argerror(L, 1, "unsupported or unknown ciphersuite"); unreachable; } suites.p = realloc(suites.p, (++suites.n + 1) * sizeof(*suites.p)); suites.p[suites.n - 1] = suite->id; suites.p[suites.n - 0] = 0; return 0; } static int LuaProgramPrivateKey(lua_State *L) { size_t n; const char *p; OnlyCallFromInitLua(L, "ProgramPrivateKey"); p = luaL_checklstring(L, 1, &n); ProgramPrivateKey(p, n); return 0; } static int LuaProgramCertificate(lua_State *L) { size_t n; const char *p; OnlyCallFromInitLua(L, "ProgramCertificate"); p = luaL_checklstring(L, 1, &n); ProgramCertificate(p, n); return 0; } static int LuaProgramHeader(lua_State *L) { ProgramHeader( gc(xasprintf("%s: %s", luaL_checkstring(L, 1), luaL_checkstring(L, 2)))); return 0; } static int LuaProgramRedirect(lua_State *L) { int code; const char *from, *to; code = luaL_checkinteger(L, 1); from = luaL_checkstring(L, 2); to = luaL_checkstring(L, 3); ProgramRedirect(code, strdup(from), strlen(from), strdup(to), strlen(to)); return 0; } static dontinline int LuaProgramBool(lua_State *L, bool *b) { *b = lua_toboolean(L, 1); return 0; } static int LuaProgramSslClientVerify(lua_State *L) { OnlyCallFromInitLua(L, "ProgramSslClientVerify"); return LuaProgramBool(L, &sslclientverify); } static int LuaProgramSslFetchVerify(lua_State *L) { OnlyCallFromMainProcess(L, "ProgramSslFetchVerify"); return LuaProgramBool(L, &sslfetchverify); } static int LuaProgramSslInit(lua_State *L) { OnlyCallFromInitLua(L, "SslInit"); TlsInit(); return 0; } static int LuaProgramLogMessages(lua_State *L) { return LuaProgramBool(L, &logmessages); } static int LuaProgramLogBodies(lua_State *L) { return LuaProgramBool(L, &logbodies); } static int LuaEvadeDragnetSurveillance(lua_State *L) { return LuaProgramBool(L, &evadedragnetsurveillance); } static int LuaProgramSslCompression(lua_State *L) { #ifndef UNSECURE if (!unsecure) { OnlyCallFromInitLua(L, "ProgramSslCompression"); conf.disable_compression = confcli.disable_compression = !lua_toboolean(L, 1); } #endif return 0; } static int LuaHidePath(lua_State *L) { size_t pathlen; const char *path; path = luaL_checklstring(L, 1, &pathlen); AddString(&hidepaths, memcpy(malloc(pathlen), path, pathlen), pathlen); return 0; } static int LuaIsHiddenPath(lua_State *L) { size_t n; const char *s; s = luaL_checklstring(L, 1, &n); lua_pushboolean(L, IsHiddenPath(s, n)); return 1; } static int LuaGetZipPaths(lua_State *L) { char *path; uint8_t *zcf; size_t i, n, pathlen, prefixlen; char *prefix = luaL_optlstring(L, 1, "", &prefixlen); lua_newtable(L); i = 0; n = GetZipCdirRecords(zcdir); for (zcf = zbase + GetZipCdirOffset(zcdir); n--; zcf += ZIP_CFILE_HDRSIZE(zcf)) { CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zcf)); path = GetAssetPath(zcf, &pathlen); if (prefixlen == 0 || startswith(path, prefix)) { lua_pushlstring(L, path, pathlen); lua_seti(L, -2, ++i); } free(path); } return 1; } static int LuaGetAssetMode(lua_State *L) { size_t pathlen; struct Asset *a; const char *path; path = LuaCheckPath(L, 1, &pathlen); if ((a = GetAsset(path, pathlen))) { lua_pushinteger(L, GetMode(a)); } else { lua_pushnil(L); } return 1; } static int LuaGetLastModifiedTime(lua_State *L) { size_t pathlen; struct Asset *a; const char *path; struct timespec lm; int64_t zuluseconds; path = LuaCheckPath(L, 1, &pathlen); if ((a = GetAsset(path, pathlen))) { if (a->file) { zuluseconds = a->file->st.st_mtim.tv_sec; } else { GetZipCfileTimestamps(zbase + a->cf, &lm, 0, 0, gmtoff); zuluseconds = lm.tv_sec; } lua_pushinteger(L, zuluseconds); } else { lua_pushnil(L); } return 1; } static int LuaGetAssetSize(lua_State *L) { size_t pathlen; struct Asset *a; const char *path; path = LuaCheckPath(L, 1, &pathlen); if ((a = GetAsset(path, pathlen))) { if (a->file) { lua_pushinteger(L, a->file->st.st_size); } else { lua_pushinteger(L, GetZipLfileUncompressedSize(zbase + a->lf)); } } else { lua_pushnil(L); } return 1; } static int LuaIsCompressed(lua_State *L) { size_t pathlen; struct Asset *a; const char *path; path = LuaCheckPath(L, 1, &pathlen); if ((a = GetAsset(path, pathlen))) { lua_pushboolean(L, IsCompressed(a)); } else { lua_pushnil(L); } return 1; } static int LuaIsDaemon(lua_State *L) { lua_pushboolean(L, daemonize); return 1; } static int LuaGetAssetComment(lua_State *L) { struct Asset *a; const char *path; size_t pathlen, m; path = LuaCheckPath(L, 1, &pathlen); if ((a = GetAssetZip(path, pathlen)) && (m = strnlen(ZIP_CFILE_COMMENT(zbase + a->cf), ZIP_CFILE_COMMENTSIZE(zbase + a->cf)))) { lua_pushlstring(L, ZIP_CFILE_COMMENT(zbase + a->cf), m); } else { lua_pushnil(L); } return 1; } static int LuaLaunchBrowser(lua_State *L) { OnlyCallFromInitLua(L, "LaunchBrowser"); launchbrowser = strdup(luaL_optstring(L, 1, "/")); return 0; } static bool LuaRunAsset(const char *path, bool mandatory) { int status; struct Asset *a; const char *code; size_t pathlen, codelen; pathlen = strlen(path); if ((a = GetAsset(path, pathlen))) { if ((code = FreeLater(LoadAsset(a, &codelen)))) { lua_State *L = GL; effectivepath.p = path; effectivepath.n = pathlen; DEBUGF("(lua) LuaRunAsset(%`'s)", path); status = luaL_loadbuffer(L, code, codelen, FreeLater(xasprintf("@%s", path))); if (status != LUA_OK || LuaCallWithTrace(L, 0, 0, NULL) != LUA_OK) { LogLuaError("lua code", lua_tostring(L, -1)); lua_pop(L, 1); // pop error if (mandatory) exit(1); } } } return !!a; } //