#include <log.h>
#include <req.h>
#include <proc.h>
+#include <bufio.h>
#include "htparser.h"
static int plex;
static char *pidfile = NULL;
static int daemonize, usesyslog;
+struct mtbuf listeners;
static void trimx(struct hthead *req)
{
}
}
-static struct hthead *parsereq(FILE *in)
+static struct hthead *parsereq(struct bufio *in)
{
struct hthead *req;
struct charbuf method, url, ver;
bufinit(url);
bufinit(ver);
while(1) {
- c = getc(in);
+ c = biogetc(in);
if(c == ' ') {
break;
} else if((c == EOF) || (c < 32) || (c >= 128)) {
}
}
while(1) {
- c = getc(in);
+ c = biogetc(in);
if(c == ' ') {
break;
} else if((c == EOF) || (c < 32)) {
}
}
while(1) {
- c = getc(in);
+ c = biogetc(in);
if(c == 10) {
break;
} else if(c == 13) {
bufadd(url, 0);
bufadd(ver, 0);
req = mkreq(method.b, url.b, ver.b);
- if(parseheaders(req, in))
+ if(parseheadersb(req, in))
goto fail;
trimx(req);
goto out;
return(req);
}
-static struct hthead *parseresp(FILE *in)
+static off_t passdata(struct bufio *in, struct bufio *out, off_t max)
{
- struct hthead *req;
- int code;
- struct charbuf ver, msg;
- int c;
+ ssize_t read;
+ off_t total;
- req = NULL;
- bufinit(ver);
- bufinit(msg);
- code = 0;
- while(1) {
- c = getc(in);
- if(c == ' ') {
- break;
- } else if((c == EOF) || (c < 32) || (c >= 128)) {
- goto fail;
- } else {
- bufadd(ver, c);
- }
- }
- while(1) {
- c = getc(in);
- if(c == ' ') {
- break;
- } else if((c == EOF) || (c < '0') || (c > '9')) {
- goto fail;
- } else {
- code = (code * 10) + (c - '0');
- }
- }
- while(1) {
- c = getc(in);
- if(c == 10) {
- break;
- } else if(c == 13) {
- } else if((c == EOF) || (c < 32)) {
- goto fail;
- } else {
- bufadd(msg, c);
+ total = 0;
+ while(!bioeof(in) && ((max < 0) || (total < max))) {
+ if((read = biordata(in)) > 0) {
+ if(max >= 0)
+ read = min(max - total, read);
+ if((read = biowritesome(out, in->rbuf.b + in->rh, read)) < 0)
+ return(-1);
+ in->rh += read;
+ total += read;
}
+ if(biorspace(in) && ((max < 0) || (biordata(in) < max - total)) && (biofillsome(in) < 0))
+ return(-1);
}
- bufadd(msg, 0);
- bufadd(ver, 0);
- req = mkresp(code, msg.b, ver.b);
- if(parseheaders(req, in))
- goto fail;
- goto out;
-
-fail:
- if(req != NULL) {
- freehthead(req);
- req = NULL;
- }
-out:
- buffree(msg);
- buffree(ver);
- return(req);
+ return(total);
}
-static off_t passdata(FILE *in, FILE *out, off_t max)
+static int recvchunks(struct bufio *in, struct bufio *out)
{
- size_t read;
- off_t total;
- char buf[8192];
+ ssize_t read, chlen;
+ int c, r;
- total = 0;
- while(!feof(in) && ((max < 0) || (total < max))) {
- read = sizeof(buf);
- if(max >= 0)
- read = min(max - total, read);
- read = fread(buf, 1, read, in);
- if(ferror(in))
- return(-1);
- if(fwrite(buf, 1, read, out) != read)
+ while(1) {
+ chlen = 0;
+ r = 0;
+ while(1) {
+ c = biogetc(in);
+ if(c == 10) {
+ if(!r)
+ return(-1);
+ break;
+ } else if(c == 13) {
+ } else if((c >= '0') && (c <= '9')) {
+ chlen = (chlen << 4) + (c - '0');
+ r = 1;
+ } else if((c >= 'A') && (c <= 'F')) {
+ chlen = (chlen << 4) + (c + 10 - 'A');
+ r = 1;
+ } else if((c >= 'a') && (c <= 'f')) {
+ chlen = (chlen << 4) + (c + 10 - 'a');
+ r = 1;
+ } else {
+ /* XXX: Technically, there may be chunk extensions to
+ * be read, but since that will likely never actually
+ * happen in practice, I can just as well add support
+ * for that if it actually does become relevant. */
+ return(-1);
+ }
+ }
+ if(chlen == 0)
+ break;
+ while(chlen > 0) {
+ if((read = biordata(in)) > 0) {
+ if((read = biowritesome(out, in->rbuf.b + in->rh, min(read, chlen))) < 0)
+ return(-1);
+ in->rh += read;
+ chlen -= read;
+ }
+ if(biorspace(in) && (biordata(in) < chlen) && (biofillsome(in) <= 0))
+ return(-1);
+ }
+ if((biogetc(in) != 13) || (biogetc(in) != 10))
return(-1);
- total += read;
}
- return(total);
+ /* XXX: Technically, there may be trailers to be read, but that's
+ * just about as likely as chunk extensions. */
+ if((biogetc(in) != 13) || (biogetc(in) != 10))
+ return(-1);
+ return(0);
}
-static int passchunks(FILE *in, FILE *out)
+static int passchunks(struct bufio *in, struct bufio *out)
{
- char buf[8192];
size_t read;
- do {
- read = fread(buf, 1, sizeof(buf), in);
- if(ferror(in))
- return(-1);
- fprintf(out, "%zx\r\n", read);
- if(fwrite(buf, 1, read, out) != read)
+ while(!bioeof(in)) {
+ if((read = biordata(in)) > 0) {
+ bioprintf(out, "%zx\r\n", read);
+ if(biowrite(out, in->rbuf.b + in->rh, read) != read)
+ return(-1);
+ in->rh += read;
+ bioprintf(out, "\r\n");
+ if(bioflush(out) < 0)
+ return(-1);
+ }
+ if(biorspace(in) && (biofillsome(in) < 0))
return(-1);
- fprintf(out, "\r\n");
- } while(read > 0);
+ }
+ bioprintf(out, "0\r\n\r\n");
return(0);
}
return(!strcasecmp(hd, val));
}
-void serve(FILE *in, struct conn *conn)
+static int canonreq(struct hthead *req)
+{
+ char *p, *p2, *r;
+ int n;
+
+ if(req->url[0] == '/') {
+ replrest(req, req->url + 1);
+ if((p = strchr(req->rest, '?')) != NULL)
+ *p = 0;
+ return(1);
+ }
+ if((p = strstr(req->url, "://")) != NULL) {
+ n = p - req->url;
+ if(((n == 4) && !strncasecmp(req->url, "http", 4)) ||
+ ((n == 5) && !strncasecmp(req->url, "https", 5))) {
+ if(getheader(req, "host"))
+ return(0);
+ p += 3;
+ if((p2 = strchr(p, '/')) == NULL) {
+ headappheader(req, "Host", p);
+ free(req->url);
+ req->url = sstrdup("/");
+ } else {
+ r = sstrdup(p2);
+ *(p2++) = 0;
+ headappheader(req, "Host", p);
+ free(req->url);
+ req->url = r;
+ }
+ replrest(req, req->url + 1);
+ if((p = strchr(req->rest, '?')) != NULL)
+ *p = 0;
+ return(1);
+ }
+ }
+ return(0);
+}
+
+static int http10keep(struct hthead *req, struct hthead *resp)
+{
+ int fc;
+
+ fc = hasheader(resp, "connection", "close");
+ headrmheader(resp, "connection");
+ if(!fc && hasheader(req, "connection", "keep-alive")) {
+ headappheader(resp, "Connection", "Keep-Alive");
+ return(1);
+ } else {
+ return(0);
+ }
+}
+
+static char *connid(void)
+{
+ static struct charbuf cur;
+ int i;
+ char *ret;
+
+ for(i = 0; i < cur.d; i++) {
+ if((++cur.b[i]) > 'Z')
+ cur.b[i] = 'A';
+ else
+ goto done;
+ }
+ bufadd(cur, 'A');
+done:
+ ret = memcpy(smalloc(cur.d + 1), cur.b, cur.d);
+ ret[cur.d] = 0;
+ return(ret);
+}
+
+static void passduplex(struct bufio *a, int afd, struct bufio *b, int bfd)
+{
+ struct selected pfd[4], sel;
+ struct bufio *sio;
+ int n, ev;
+
+ while(!bioeof(a) && !bioeof(b)) {
+ biocopybuf(b, a);
+ biocopybuf(a, b);
+ n = 0;
+ if(!a->eof) {
+ ev = 0;
+ if(biorspace(a))
+ ev |= EV_READ;
+ if(biowdata(a))
+ ev |= EV_WRITE;
+ if(ev)
+ pfd[n++] = (struct selected){.fd = afd, .ev = ev};
+ }
+ if(!b->eof) {
+ ev = 0;
+ if(!b->eof && biorspace(b))
+ ev |= EV_READ;
+ if(biowdata(b))
+ ev |= EV_WRITE;
+ if(ev)
+ pfd[n++] = (struct selected){.fd = bfd, .ev = ev};
+ }
+ sel = mblock(600, n, pfd);
+ if(sel.fd == afd)
+ sio = a;
+ else if(sel.fd == bfd)
+ sio = b;
+ else
+ break;
+ if((sel.ev & EV_READ) && (biofillsome(sio) < 0))
+ break;
+ if((sel.ev & EV_WRITE) && (bioflushsome(sio) < 0))
+ break;
+ }
+}
+
+void serve(struct bufio *in, int infd, struct conn *conn)
{
int pfds[2];
- FILE *out;
+ struct bufio *out, *dout;
+ struct stdiofd *outi;
struct hthead *req, *resp;
- char *hd, *p;
+ char *hd, *id;
off_t dlen;
+ int keep, duplex;
+ id = connid();
out = NULL;
req = resp = NULL;
- while(1) {
+ while(plex >= 0) {
+ bioflush(in);
if((req = parsereq(in)) == NULL)
break;
- replrest(req, req->url);
- if(req->rest[0] == '/')
- replrest(req, req->rest + 1);
- if((p = strchr(req->rest, '?')) != NULL)
- *p = 0;
+ if(!canonreq(req))
+ break;
+ headappheader(req, "X-Ash-Connection-ID", id);
if((conn->initreq != NULL) && conn->initreq(conn, req))
break;
- if(block(plex, EV_WRITE, 60) <= 0)
+ if((plex < 0) || block(plex, EV_WRITE, 60) <= 0)
break;
if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfds))
break;
if(sendreq(plex, req, pfds[0]))
break;
close(pfds[0]);
- out = mtstdopen(pfds[1], 1, 600, "r+");
+ out = mtbioopen(pfds[1], 1, 600, "r+", &outi);
- if((hd = getheader(req, "content-length")) != NULL) {
- dlen = atoo(hd);
- if(dlen > 0) {
- if(passdata(in, out, dlen) != dlen)
+ if(getheader(req, "content-type") != NULL) {
+ if((hd = getheader(req, "content-length")) != NULL) {
+ dlen = atoo(hd);
+ if(dlen > 0) {
+ if(passdata(in, out, dlen) != dlen)
+ break;
+ }
+ } else if(((hd = getheader(req, "transfer-encoding")) != NULL) && !strcasecmp(hd, "chunked")) {
+ if(recvchunks(in, out))
break;
+ } else {
+ /* Ignore rather than abort, to be kinder to broken clients. */
+ headrmheader(req, "content-type");
}
}
- if(fflush(out))
+ if(bioflush(out))
break;
/* Make sure to send EOF */
shutdown(pfds[1], SHUT_WR);
- if((resp = parseresp(out)) == NULL)
+ if((resp = parseresponseb(out)) == NULL)
break;
replstr(&resp->ver, req->ver);
if(!getheader(resp, "server"))
headappheader(resp, "Server", sprintf3("ashd/%s", VERSION));
+ duplex = hasheader(resp, "x-ash-switch", "duplex");
+ trimx(resp);
- if(!strcmp(req->ver, "HTTP/1.0")) {
- writeresp(in, resp);
- fprintf(in, "\r\n");
+ if(duplex) {
+ if(outi->rights < 0)
+ break;
+ writerespb(in, resp);
+ bioprintf(in, "\r\n");
+ dout = mtbioopen(outi->rights, 1, 600, "r+", NULL);
+ passduplex(in, infd, dout, outi->rights);
+ outi->rights = -1;
+ bioclose(dout);
+ break;
+ } else if(!strcasecmp(req->ver, "HTTP/1.0")) {
if(!strcasecmp(req->method, "head")) {
- if(!hasheader(req, "connection", "keep-alive"))
- break;
+ keep = http10keep(req, resp);
+ writerespb(in, resp);
+ bioprintf(in, "\r\n");
} else if((hd = getheader(resp, "content-length")) != NULL) {
+ keep = http10keep(req, resp);
dlen = atoo(hd);
+ writerespb(in, resp);
+ bioprintf(in, "\r\n");
if(passdata(out, in, dlen) != dlen)
break;
- if(!hasheader(req, "connection", "keep-alive"))
- break;
} else {
+ headrmheader(resp, "connection");
+ writerespb(in, resp);
+ bioprintf(in, "\r\n");
passdata(out, in, -1);
break;
}
- if(hasheader(req, "connection", "close") || hasheader(resp, "connection", "close"))
+ if(!keep)
break;
- } else if(!strcmp(req->ver, "HTTP/1.1")) {
+ } else if(!strcasecmp(req->ver, "HTTP/1.1")) {
if(!strcasecmp(req->method, "head")) {
- writeresp(in, resp);
- fprintf(in, "\r\n");
+ writerespb(in, resp);
+ bioprintf(in, "\r\n");
} else if((hd = getheader(resp, "content-length")) != NULL) {
- writeresp(in, resp);
- fprintf(in, "\r\n");
+ writerespb(in, resp);
+ bioprintf(in, "\r\n");
dlen = atoo(hd);
if(passdata(out, in, dlen) != dlen)
break;
} else if(!getheader(resp, "transfer-encoding")) {
headappheader(resp, "Transfer-Encoding", "chunked");
- writeresp(in, resp);
- fprintf(in, "\r\n");
+ writerespb(in, resp);
+ bioprintf(in, "\r\n");
if(passchunks(out, in))
break;
} else {
- writeresp(in, resp);
- fprintf(in, "\r\n");
+ writerespb(in, resp);
+ bioprintf(in, "\r\n");
passdata(out, in, -1);
break;
}
break;
}
- fclose(out);
+ bioclose(out);
out = NULL;
freehthead(req);
freehthead(resp);
}
if(out != NULL)
- fclose(out);
+ bioclose(out);
if(req != NULL)
freehthead(req);
if(resp != NULL)
freehthead(resp);
- fclose(in);
+ bioclose(in);
+ free(id);
}
static void plexwatch(struct muth *muth, va_list args)
{
vavar(int, fd);
char *buf;
- int ret;
+ int i, s, ret;
+ s = 0;
while(1) {
- block(fd, EV_READ, 0);
+ if(block(fd, EV_READ, 0) == 0)
+ break;
buf = smalloc(65536);
ret = recv(fd, buf, 65536, 0);
if(ret < 0) {
flog(LOG_WARNING, "received error on rootplex read channel: %s", strerror(errno));
exit(1);
} else if(ret == 0) {
- exit(0);
+ s = 1;
+ free(buf);
+ break;
}
/* Maybe I'd like to implement some protocol in this direction
* some day... */
free(buf);
}
+ shutdown(plex, SHUT_RDWR);
+ for(i = 0; i < listeners.d; i++) {
+ if(listeners.b[i] == muth)
+ bufdel(listeners, i);
+ }
+ if(s) {
+ flog(LOG_INFO, "root handler exited, so shutting down listening...");
+ while(listeners.d > 0)
+ resume(listeners.b[0], 0);
+ }
}
static void initroot(void *uu)
{
int fd;
+ setsid();
if(daemonize) {
- setsid();
chdir("/");
if((fd = open("/dev/null", O_RDWR)) >= 0) {
dup2(fd, 0);
buffree(vals);
}
+static void sighandler(int sig)
+{
+ exitioloop(1);
+}
+
int main(int argc, char **argv)
{
- int c;
+ int c, d;
int i, s1;
char *root;
FILE *pidout;
flog(LOG_ERR, "could not spawn root multiplexer: %s", strerror(errno));
return(1);
}
- mustart(plexwatch, plex);
+ bufadd(listeners, mustart(plexwatch, plex));
pidout = NULL;
if(pidfile != NULL) {
if((pidout = fopen(pidfile, "w")) == NULL) {
}
}
signal(SIGPIPE, SIG_IGN);
+ signal(SIGCHLD, SIG_IGN);
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
if(daemonize) {
daemon(0, 0);
}
fprintf(pidout, "%i\n", getpid());
fclose(pidout);
}
- ioloop();
+ d = 0;
+ while(!d) {
+ switch(ioloop()) {
+ case 0:
+ d = 1;
+ break;
+ case 1:
+ if(listeners.d > 0) {
+ while(listeners.d > 0)
+ resume(listeners.b[0], 0);
+ flog(LOG_INFO, "no longer listening");
+ } else {
+ d = 1;
+ }
+ break;
+ }
+ }
return(0);
}