#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
+#include <arpa/inet.h>
#include <errno.h>
+#include <time.h>
#ifdef HAVE_CONFIG_H
#include <config.h>
#include <mt.h>
#include <log.h>
#include <req.h>
+#include <proc.h>
#define EV_READ 1
#define EV_WRITE 2
struct blocker *n, *p;
int fd;
int ev;
+ time_t to;
struct muth *th;
};
static struct blocker *blockers;
+int plex;
-static int block(int fd, int ev)
+static int block(int fd, int ev, time_t to)
{
struct blocker *bl;
int rv;
omalloc(bl);
bl->fd = fd;
bl->ev = ev;
+ if(to > 0)
+ bl->to = time(NULL) + to;
bl->th = current;
bl->n = blockers;
if(blockers)
ret = recv(fd, buf->b + buf->d, buf->s - buf->d, MSG_DONTWAIT);
if(ret <= 0) {
if((ret < 0) && (errno == EAGAIN)) {
- block(fd, EV_READ);
+ if(block(fd, EV_READ, 60) <= 0)
+ return(-1);
continue;
}
return(-1);
__buf__ = 1; \
} \
__buf__;})
-static struct htreq *parseraw(char *buf)
+static struct hthead *parserawreq(char *buf)
{
char *p, *p2, *nl;
char *method, *url, *ver;
- struct htreq *req;
+ struct hthead *req;
if((nl = strchr(buf, '\n')) == NULL)
return(NULL);
for(nl = p2; (*nl != '\r') && (*nl != '\n'); nl++);
if(!SKIPNL(nl))
goto fail;
- reqappheader(req, p, p2);
+ if(strncasecmp(p, "x-ash-", 6))
+ headappheader(req, p, p2);
p = nl;
}
return(req);
fail:
- freereq(req);
+ freehthead(req);
return(NULL);
}
+static struct hthead *parserawresp(char *buf)
+{
+ char *p, *p2, *nl;
+ char *msg, *ver;
+ int code;
+ struct hthead *resp;
+
+ if((nl = strchr(buf, '\n')) == NULL)
+ return(NULL);
+ p = strchr(buf, '\r');
+ if((p != NULL) && (p < nl))
+ nl = p;
+ if(strncmp(buf, "HTTP/", 5))
+ return(NULL);
+ ver = p = buf + 5;
+ for(; ((*p >= '0') && (*p <= '9')) || (*p == '.'); p++);
+ if(*p != ' ')
+ return(NULL);
+ *(p++) = 0;
+ if(((p2 = strchr(p, ' ')) == NULL) || (p2 > nl))
+ return(NULL);
+ *(p2++) = 0;
+ code = atoi(p);
+ if((code < 100) || (code >= 600))
+ return(NULL);
+ if(p2 >= nl)
+ return(NULL);
+ msg = p2;
+ p = nl;
+ if(!SKIPNL(p))
+ return(NULL);
+
+ resp = mkresp(code, msg, ver);
+ while(1) {
+ if(SKIPNL(p)) {
+ if(*p)
+ goto fail;
+ break;
+ }
+ if((nl = strchr(p, '\n')) == NULL)
+ goto fail;
+ if(((p2 = strchr(p, ':')) == NULL) || (p2 > nl))
+ goto fail;
+ *(p2++) = 0;
+ for(; (*p2 == ' ') || (*p2 == '\t'); p2++);
+ for(nl = p2; (*nl != '\r') && (*nl != '\n'); nl++);
+ if(!SKIPNL(nl))
+ goto fail;
+ headappheader(resp, p, p2);
+ p = nl;
+ }
+ return(resp);
+
+fail:
+ freehthead(resp);
+ return(NULL);
+}
+
+static off_t passdata(int src, int dst, struct charbuf *buf, off_t max)
+{
+ size_t dataoff, smax;
+ off_t sent;
+ int eof, ret;
+
+ sent = 0;
+ eof = 0;
+ while(!eof || (buf->d > 0)) {
+ if(!eof && (buf->d < buf->s) && ((max < 0) || (sent + buf->d < max))) {
+ while(1) {
+ ret = recv(src, buf->b + buf->d, buf->s - buf->d, MSG_DONTWAIT);
+ if((ret < 0) && (errno == EAGAIN)) {
+ } else if(ret < 0) {
+ return(-1);
+ } else if(ret == 0) {
+ eof = 1;
+ break;
+ } else {
+ buf->d += ret;
+ break;
+ }
+ if(buf->d > 0)
+ break;
+ if(block(src, EV_READ, 0) <= 0)
+ return(-1);
+ }
+ }
+ for(dataoff = 0; (dataoff < buf->d) && ((max < 0) || (sent < max));) {
+ if(block(dst, EV_WRITE, 120) <= 0)
+ return(-1);
+ smax = buf->d - dataoff;
+ if(sent + smax > max)
+ smax = max - sent;
+ ret = send(dst, buf->b + dataoff, smax, MSG_NOSIGNAL | MSG_DONTWAIT);
+ if(ret < 0)
+ return(-1);
+ dataoff += ret;
+ sent += ret;
+ }
+ bufeat(*buf, dataoff);
+ }
+ return(sent);
+}
+
static void serve(struct muth *muth, va_list args)
{
vavar(int, fd);
- struct charbuf buf;
- struct htreq *req;
+ vavar(struct sockaddr_storage, name);
+ int cfd;
+ char old;
+ char *hd;
+ struct charbuf inbuf, outbuf;
+ struct hthead *req, *resp;
+ off_t sent;
size_t headoff;
+ char nmbuf[256];
- bufinit(buf);
+ bufinit(inbuf);
+ bufinit(outbuf);
+ cfd = -1;
+ req = NULL;
while(1) {
- buf.d = 0;
- if((headoff = readhead(fd, &buf)) < 0)
+ /*
+ * First, find and decode the header:
+ */
+ if((headoff = readhead(fd, &inbuf)) < 0)
+ goto out;
+ if(headoff > 65536) {
+ /* We cannot handle arbitrarily large headers, as they
+ * need to fit within a single Unix datagram. This is
+ * probably a safe limit, and larger packets than this are
+ * most likely erroneous (or malicious) anyway. */
+ goto out;
+ }
+ old = inbuf.b[headoff];
+ inbuf.b[headoff] = 0;
+ if((req = parserawreq(inbuf.b)) == NULL)
goto out;
- if((req = parseraw(buf.b)) == NULL)
+ inbuf.b[headoff] = old;
+ bufeat(inbuf, headoff);
+
+ /*
+ * Add metainformation and then send the request to the root
+ * multiplexer:
+ */
+ if(name.ss_family == AF_INET) {
+ headappheader(req, "X-Ash-Address", inet_ntop(AF_INET, &((struct sockaddr_in *)&name)->sin_addr, nmbuf, sizeof(nmbuf)));
+ headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in *)&name)->sin_port)));
+ } else if(name.ss_family == AF_INET6) {
+ headappheader(req, "X-Ash-Address", inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&name)->sin6_addr, nmbuf, sizeof(nmbuf)));
+ headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in6 *)&name)->sin6_port)));
+ }
+ cfd = sendreq(plex, req);
+
+ /*
+ * Find and decode the response header:
+ */
+ outbuf.d = 0;
+ headoff = readhead(cfd, &outbuf);
+ hd = memcpy(smalloc(headoff + 1), outbuf.b, headoff);
+ hd[headoff] = 0;
+ if((resp = parserawresp(hd)) == NULL)
goto out;
- printf("\"%s\", \"%s\", \"%s\", \"%s\"\n", req->method, req->url, req->ver, getheader(req, "host"));
- freereq(req);
+
+ /*
+ * Pass the actual output:
+ */
+ sizebuf(outbuf, 65536);
+ sent = passdata(cfd, fd, &outbuf, -1);
+ sent -= headoff;
+
+ /*
+ * Check for connection expiry
+ */
+ if(strcasecmp(req->method, "head")) {
+ if((hd = getheader(resp, "content-length")) != NULL) {
+ if(sent != atoo(hd)) {
+ /* Exit because of error */
+ goto out;
+ }
+ } else {
+ if(((hd = getheader(resp, "transfer-encoding")) == NULL) || !strcasecmp(hd, "identity"))
+ break;
+ }
+ if(((hd = getheader(req, "connection")) != NULL) && !strcasecmp(hd, "close"))
+ break;
+ if(((hd = getheader(resp, "connection")) != NULL) && !strcasecmp(hd, "close"))
+ break;
+ }
+
+ close(cfd);
+ cfd = -1;
+ freehthead(req);
+ req = NULL;
+ freehthead(resp);
+ resp = NULL;
}
out:
- buffree(buf);
+ if(cfd >= 0)
+ close(cfd);
+ if(req != NULL)
+ freehthead(req);
+ if(resp != NULL)
+ freehthead(resp);
+ buffree(inbuf);
+ buffree(outbuf);
close(fd);
}
while(1) {
namelen = sizeof(name);
- block(ss, EV_READ);
+ block(ss, EV_READ, 0);
ns = accept(ss, (struct sockaddr *)&name, &namelen);
if(ns < 0) {
flog(LOG_ERR, "accept: %s", strerror(errno));
goto out;
}
- mustart(serve, ns);
+ mustart(serve, ns, name);
}
out:
int ret;
fd_set rfds, wfds, efds;
struct blocker *bl, *nbl;
+ struct timeval toval;
+ time_t now, timeout;
int maxfd;
int ev;
FD_ZERO(&wfds);
FD_ZERO(&efds);
maxfd = 0;
+ now = time(NULL);
+ timeout = 0;
for(bl = blockers; bl; bl = bl->n) {
if(bl->ev & EV_READ)
FD_SET(bl->fd, &rfds);
FD_SET(bl->fd, &efds);
if(bl->fd > maxfd)
maxfd = bl->fd;
+ if((bl->to != 0) && ((timeout == 0) || (timeout > bl->to)))
+ timeout = bl->to;
}
- ret = select(maxfd + 1, &rfds, &wfds, &efds, NULL);
+ toval.tv_sec = timeout - now;
+ toval.tv_usec = 0;
+ ret = select(maxfd + 1, &rfds, &wfds, &efds, timeout?(&toval):NULL);
if(ret < 0) {
if(errno != EINTR) {
flog(LOG_CRIT, "ioloop: select errored out: %s", strerror(errno));
sleep(1);
}
}
+ now = time(NULL);
for(bl = blockers; bl; bl = nbl) {
nbl = bl->n;
ev = 0;
ev = -1;
if(ev != 0)
resume(bl->th, ev);
+ else if((bl->to != 0) && (bl->to <= now))
+ resume(bl->th, 0);
}
}
}
{
int fd;
+ if(argc < 2) {
+ fprintf(stderr, "usage: htparser ROOT [ARGS...]\n");
+ exit(1);
+ }
+ if((plex = stdmkchild(argv + 1)) < 0) {
+ flog(LOG_ERR, "could not spawn root multiplexer: %s", strerror(errno));
+ return(1);
+ }
if((fd = listensock6(8080)) < 0) {
flog(LOG_ERR, "could not listen on IPv6: %s", strerror(errno));
return(1);