mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-02-07 03:40:09 +00:00
491 lines
10 KiB
C
491 lines
10 KiB
C
/* httpd.c - multi-client httpd, with cgi and dirindex support, in <500 LOC.
|
|
* Run as: httpd [-p port] <root>
|
|
* u+x or g+x files are considered cgi programs.
|
|
*/
|
|
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <netinet/in.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#define LINEBUFMAX 4096
|
|
#define REQBUFMAX 4096
|
|
#define FILEBUFMAX 4096
|
|
|
|
static const char *docroot;
|
|
static int printreqs = 0;
|
|
|
|
struct reactor {
|
|
int epfd;
|
|
};
|
|
|
|
struct socket {
|
|
int fd;
|
|
struct sockaddr_in sa;
|
|
struct reactor *r;
|
|
void (*read)(struct socket *);
|
|
void (*write)(struct socket *);
|
|
void (*close)(struct socket *);
|
|
void *priv;
|
|
};
|
|
|
|
struct client {
|
|
struct socket *s;
|
|
void (*line)(struct client *, char *);
|
|
void (*writedone)(struct client *);
|
|
|
|
char *rbuf;
|
|
size_t rbufsize;
|
|
size_t rbuffill;
|
|
char *wbuf;
|
|
size_t wbufsize;
|
|
size_t wbuffill;
|
|
|
|
char *reqmethod;
|
|
char *requrl;
|
|
|
|
int fillfd;
|
|
};
|
|
|
|
static void udie(const char *prefix) {
|
|
perror(prefix);
|
|
abort();
|
|
}
|
|
|
|
static void *xmalloc(size_t sz) {
|
|
void *p = malloc(sz);
|
|
if (!p)
|
|
abort();
|
|
memset(p, 0, sz);
|
|
return p;
|
|
}
|
|
|
|
static char *xstrdup(const char *s) {
|
|
char *n = strdup(s);
|
|
if (!n)
|
|
abort();
|
|
return n;
|
|
}
|
|
|
|
static void strlcpy(char *dest, const char *src, size_t n) {
|
|
strncpy(dest, src, n - 1);
|
|
dest[n - 1] = '\0';
|
|
}
|
|
|
|
static void strlcat(char *dest, const char *src, size_t n) {
|
|
strncat(dest, src, n - 1);
|
|
dest[n - 1] = '\0';
|
|
}
|
|
|
|
static struct reactor *reactor_new(void) {
|
|
struct reactor *r = xmalloc(sizeof *r);
|
|
r->epfd = epoll_create1(0);
|
|
if (r->epfd < 0)
|
|
udie("epoll_create1()");
|
|
return r;
|
|
}
|
|
|
|
static struct socket *reactor_add(struct reactor *r, int fd) {
|
|
struct socket *s = xmalloc(sizeof *s);
|
|
struct epoll_event evt;
|
|
|
|
s->fd = fd;
|
|
s->r = r;
|
|
evt.events = 0;
|
|
evt.data.ptr = s;
|
|
if (epoll_ctl(r->epfd, EPOLL_CTL_ADD, fd, &evt) < 0)
|
|
udie("epoll_ctl()");
|
|
return s;
|
|
};
|
|
|
|
static void reactor_refresh(struct reactor *r, struct socket *s) {
|
|
struct epoll_event evt;
|
|
evt.events = 0;
|
|
evt.data.ptr = s;
|
|
if (s->read)
|
|
evt.events |= EPOLLIN;
|
|
if (s->write)
|
|
evt.events |= EPOLLOUT;
|
|
if (s->close)
|
|
evt.events |= EPOLLRDHUP;
|
|
if (epoll_ctl(r->epfd, EPOLL_CTL_MOD, s->fd, &evt) < 0)
|
|
udie("epoll_ctl()");
|
|
}
|
|
|
|
static void reactor_del(struct reactor *r, struct socket *s) {
|
|
if (epoll_ctl(r->epfd, EPOLL_CTL_DEL, s->fd, NULL) < 0)
|
|
udie("epoll_ctl()");
|
|
free(s);
|
|
}
|
|
|
|
static void reactor_run(struct reactor *r) {
|
|
struct epoll_event evts[16];
|
|
int n;
|
|
int i;
|
|
struct socket *s;
|
|
|
|
n = epoll_wait(r->epfd, evts, sizeof(evts) / sizeof(evts[0]), -1);
|
|
if (n < 0)
|
|
udie("epoll_wait()");
|
|
for (i = 0; i < n; i++) {
|
|
s = evts[i].data.ptr;
|
|
if (evts[i].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) {
|
|
if (s->close)
|
|
s->close(s);
|
|
reactor_del(r, s);
|
|
} else if ((evts[i].events & EPOLLIN) && s->read) {
|
|
s->read(s);
|
|
} else if ((evts[i].events & EPOLLOUT) && s->write) {
|
|
s->write(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void reqline(struct client *, char *);
|
|
|
|
static struct client *client_new(struct socket *s) {
|
|
struct client *c = xmalloc(sizeof *c);
|
|
c->s = s;
|
|
c->rbuf = xmalloc(REQBUFMAX);
|
|
c->rbufsize = REQBUFMAX;
|
|
c->rbuffill = 0;
|
|
c->line = reqline;
|
|
c->wbufsize = 0;
|
|
c->wbuffill = 0;
|
|
c->writedone = NULL;
|
|
return c;
|
|
}
|
|
|
|
static void client_read(struct socket *s) {
|
|
struct client *c = s->priv;
|
|
char *p;
|
|
ssize_t len;
|
|
|
|
len = read(s->fd, c->rbuf + c->rbuffill, c->rbufsize - c->rbuffill);
|
|
if (len < 0)
|
|
udie("read()");
|
|
c->rbuffill += len;
|
|
while ((p = strstr(c->rbuf, "\n"))) {
|
|
*p = '\0';
|
|
if (p > c->rbuf && p[-1] == '\r')
|
|
p[-1] = '\0';
|
|
p++;
|
|
c->line(c, c->rbuf);
|
|
memmove(c->rbuf, p, c->rbufsize - (p - c->rbuf));
|
|
c->rbuffill -= (p - c->rbuf);
|
|
memset(c->rbuf + c->rbuffill, 0, c->rbufsize - c->rbuffill);
|
|
}
|
|
}
|
|
|
|
static void client_write(struct socket *s) {
|
|
struct client *c = s->priv;
|
|
ssize_t len;
|
|
|
|
len = write(s->fd, c->wbuf, c->wbuffill);
|
|
if (len < 0)
|
|
udie("write()");
|
|
if ((size_t)len < c->wbuffill)
|
|
memmove(c->wbuf, c->wbuf + len, c->wbuffill - len);
|
|
c->wbuffill -= len;
|
|
if (c->wbuffill)
|
|
return;
|
|
free(c->wbuf);
|
|
c->wbuf = NULL;
|
|
c->wbufsize = 0;
|
|
s->write = NULL;
|
|
c->writedone(c);
|
|
}
|
|
|
|
static void client_writeb(struct client *c, const char *buf, size_t len) {
|
|
if (!c->wbufsize || c->wbufsize - c->wbuffill < len) {
|
|
size_t growby = len - (c->wbufsize - c->wbuffill);
|
|
c->wbuf = realloc(c->wbuf, c->wbufsize + growby);
|
|
c->wbufsize += growby;
|
|
}
|
|
memcpy(c->wbuf + c->wbuffill, buf, len);
|
|
c->wbuffill += len;
|
|
if (!c->s->write) {
|
|
c->s->write = client_write;
|
|
reactor_refresh(c->s->r, c->s);
|
|
}
|
|
}
|
|
|
|
static void client_writeln(struct client *c, const char *fmt, ...) {
|
|
char buf[LINEBUFMAX];
|
|
va_list ap;
|
|
char *p;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, LINEBUFMAX, fmt, ap);
|
|
va_end(ap);
|
|
|
|
p = buf + strlen(buf);
|
|
if (p > buf + LINEBUFMAX - 2)
|
|
p = buf + LINEBUFMAX - 2;
|
|
*p++ = '\r';
|
|
*p++ = '\n';
|
|
client_writeb(c, buf, p - buf);
|
|
}
|
|
|
|
static void client_writedone(struct client *c) {
|
|
close(c->s->fd);
|
|
}
|
|
|
|
static void client_refillbuf(struct client *c) {
|
|
char buf[FILEBUFMAX];
|
|
ssize_t len;
|
|
|
|
len = read(c->fillfd, buf, sizeof(buf));
|
|
if (len < 0)
|
|
udie("read()");
|
|
if (len == 0) {
|
|
c->writedone = client_writedone;
|
|
close(c->fillfd);
|
|
} else {
|
|
c->writedone = client_refillbuf;
|
|
}
|
|
client_writeb(c, buf, len);
|
|
}
|
|
|
|
static void client_close(struct socket *s) {
|
|
struct client *c = s->priv;
|
|
free(c->reqmethod);
|
|
free(c->requrl);
|
|
free(c->rbuf);
|
|
free(c->wbuf);
|
|
free(c);
|
|
/* ... */
|
|
}
|
|
|
|
static void listener_read(struct socket *s) {
|
|
struct sockaddr_in sa;
|
|
socklen_t salen = sizeof(sa);
|
|
int nfd = accept(s->fd, (struct sockaddr *)&sa, &salen);
|
|
struct socket *n;
|
|
if (nfd == -1)
|
|
udie("accept()");
|
|
if (fcntl(nfd, F_SETFD, FD_CLOEXEC) < 0)
|
|
udie("fcntl()");
|
|
n = reactor_add(s->r, nfd);
|
|
memcpy(&n->sa, &sa, sizeof(n->sa));
|
|
n->read = client_read;
|
|
n->close = client_close;
|
|
reactor_refresh(s->r, n);
|
|
n->priv = client_new(n);
|
|
}
|
|
|
|
static void error(struct client *c, int code) {
|
|
client_writeln(c, "HTTP/1.1 %u Error", code);
|
|
client_writeln(c, "");
|
|
c->writedone = client_writedone;
|
|
}
|
|
|
|
static void iptobuf(struct client *c, char *buf) {
|
|
unsigned int ip = ntohl(c->s->sa.sin_addr.s_addr);
|
|
sprintf(buf, "%u.%u.%u.%u", (ip >> 24) & 0xFF,
|
|
(ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
|
|
}
|
|
|
|
static void runcgi(struct client *c, const char *prog, const char *args) {
|
|
char buf[] = "REMOTE_ADDR=255.255.255.255";
|
|
iptobuf(c, buf + strlen("REMOTE_ADDR="));
|
|
putenv(buf);
|
|
dup2(c->s->fd, 0);
|
|
dup2(c->s->fd, 1);
|
|
dup2(c->s->fd, 2);
|
|
execl(prog, prog, args, NULL);
|
|
}
|
|
|
|
static void cgi(struct client *c, const char *prog, const char *args) {
|
|
int p;
|
|
p = fork();
|
|
if (!p)
|
|
runcgi(c, prog, args);
|
|
else if (p < 0)
|
|
error(c, 500);
|
|
else
|
|
c->writedone = client_writedone;
|
|
}
|
|
|
|
static void genindex(struct client *c, const char *url) {
|
|
DIR *d = fdopendir(c->fillfd);
|
|
struct dirent *e;
|
|
|
|
client_writeln(c, "Content-Type: text/html");
|
|
client_writeln(c, "");
|
|
|
|
client_writeln(c, "<html>");
|
|
client_writeln(c, " <head>");
|
|
client_writeln(c, " <title>Index of %s</title>", url);
|
|
client_writeln(c, " </head>");
|
|
client_writeln(c, " <body>");
|
|
client_writeln(c, " <h1>Index of %s</h1>", url);
|
|
client_writeln(c, " <ul>");
|
|
while ((e = readdir(d))) {
|
|
client_writeln(c, " <li>");
|
|
client_writeln(c, " <a href=\"%s%s%s\">%s</a>", url,
|
|
url[strlen(url) - 1] == '/' ? "" : "/", e->d_name,
|
|
e->d_name);
|
|
client_writeln(c, " </li>");
|
|
}
|
|
client_writeln(c, " </ul>");
|
|
client_writeln(c, " </body>");
|
|
client_writeln(c, "</html>");
|
|
closedir(d);
|
|
c->writedone = client_writedone;
|
|
}
|
|
|
|
static void get(struct client *c, char *url) {
|
|
char rp[PATH_MAX];
|
|
char *rpcanon;
|
|
char *rest;
|
|
struct stat st;
|
|
|
|
strlcpy(rp, docroot, sizeof(rp));
|
|
if ((rest = strchr(url, '?')))
|
|
*rest++ = '\0';
|
|
strlcat(rp, url, sizeof(rp));
|
|
rpcanon = realpath(rp, NULL);
|
|
if (!rpcanon) {
|
|
error(c, 404);
|
|
return;
|
|
}
|
|
|
|
if (strstr(rpcanon, docroot) != rpcanon) {
|
|
error(c, 403);
|
|
free(rpcanon);
|
|
return;
|
|
}
|
|
|
|
c->fillfd = open(rpcanon, O_RDONLY);
|
|
if (c->fillfd == -1) {
|
|
free(rpcanon);
|
|
error(c, 403); /* XXX: not all open() failures are 403s */
|
|
return;
|
|
}
|
|
|
|
if (fstat(c->fillfd, &st) == -1)
|
|
udie("fstat()");
|
|
|
|
client_writeln(c, "HTTP/1.1 200 OK");
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
genindex(c, url);
|
|
} else if (st.st_mode & (S_IXUSR | S_IXGRP)) {
|
|
cgi(c, rpcanon, rest);
|
|
} else {
|
|
client_writeln(c, "");
|
|
client_refillbuf(c);
|
|
}
|
|
free(rpcanon);
|
|
}
|
|
|
|
static void reqdone(struct client *c) {
|
|
if (printreqs) {
|
|
char buf[32];
|
|
iptobuf(c, buf);
|
|
printf("%s %s %s\n", buf, c->reqmethod, c->requrl);
|
|
}
|
|
if (!strcasecmp(c->reqmethod, "GET"))
|
|
get(c, c->requrl);
|
|
else
|
|
error(c, 405);
|
|
}
|
|
|
|
static void reqhdr(struct client *c, char *line) {
|
|
|
|
if (!strlen(line)) {
|
|
reqdone(c);
|
|
return;
|
|
}
|
|
|
|
/* XXX */
|
|
}
|
|
|
|
static void reqline(struct client *c, char *line) {
|
|
char *method, *url, *version;
|
|
|
|
method = strtok(line, " ");
|
|
url = strtok(NULL, " ");
|
|
version = strtok(NULL, " ");
|
|
|
|
if (!method || !url) {
|
|
error(c, 400);
|
|
return;
|
|
}
|
|
|
|
c->reqmethod = xstrdup(method);
|
|
c->requrl = xstrdup(url);
|
|
c->line = reqhdr;
|
|
}
|
|
|
|
static int serve(int port) {
|
|
int sfd = socket(AF_INET, SOCK_STREAM, 0);
|
|
struct sockaddr_in sa;
|
|
if (sfd == -1)
|
|
udie("socket()");
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
sa.sin_port = htons(port);
|
|
if (bind(sfd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
|
|
udie("bind()");
|
|
if (listen(sfd, 20) < 0)
|
|
udie("listen()");
|
|
if (fcntl(sfd, F_SETFD, FD_CLOEXEC) < 0)
|
|
udie("fcntl()");
|
|
return sfd;
|
|
}
|
|
|
|
static void usage(const char *progn) {
|
|
printf("Usage: %s [-p port] [-v] <root>\n", progn);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
struct reactor *r = reactor_new();
|
|
struct socket *listener;
|
|
int opt;
|
|
int port = 80;
|
|
|
|
while ((opt = getopt(argc, argv, "p:v")) != -1) {
|
|
switch (opt) {
|
|
case 'p':
|
|
port = atoi(optarg);
|
|
break;
|
|
case 'v':
|
|
printreqs = 1;
|
|
break;
|
|
default:
|
|
usage(argv[0]);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (optind >= argc) {
|
|
usage(argv[0]);
|
|
exit(1);
|
|
}
|
|
|
|
docroot = argv[optind];
|
|
|
|
listener = reactor_add(r, serve(port));
|
|
listener->read = listener_read;
|
|
reactor_refresh(r, listener);
|
|
|
|
signal(SIGCHLD, SIG_IGN);
|
|
|
|
while (1) {
|
|
reactor_run(r);
|
|
}
|
|
}
|