* httpd.c: implementation of httpd.h.
*/
-#define _GNU_SOURCE
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <assert.h>
-#include <unistd.h>
-#include <pwd.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <fcntl.h>
-#include <sys/socket.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
-#include <syslog.h>
-
-#include "malloc.h"
+#include "agedu.h"
+#include "alloc.h"
#include "html.h"
+#include "httpd.h"
/* --- Logic driving what the web server's responses are. --- */
+enum { /* connctx states */
+ READING_REQ_LINE,
+ READING_HEADERS,
+ DONE
+};
+
struct connctx {
const void *t;
char *data;
int datalen, datasize;
+ char *method, *url, *headers, *auth;
+ int state;
};
/*
cctx->t = t;
cctx->data = NULL;
cctx->datalen = cctx->datasize = 0;
+ cctx->state = READING_REQ_LINE;
+ cctx->method = cctx->url = cctx->headers = cctx->auth = NULL;
return cctx;
}
sfree(cctx);
}
-static char *http_error(char *code, char *errmsg, char *errtext, ...)
+static char *http_error(char *code, char *errmsg, char *extraheader,
+ char *errtext, ...)
{
return dupfmt("HTTP/1.1 %s %s\r\n"
"Date: %D\r\n"
- "Server: agedu\r\n"
+ "Server: " PNAME "\r\n"
"Connection: close\r\n"
+ "%s"
"Content-Type: text/html; charset=US-ASCII\r\n"
"\r\n"
"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
"<H1>%s %s</H1>\r\n"
"<P>%s</P>\r\n"
"</BODY></HTML>\r\n", code, errmsg,
+ extraheader ? extraheader : "",
code, errmsg, code, errmsg, errtext);
}
return dupfmt("HTTP/1.1 200 OK\r\n"
"Date: %D\r\n"
"Expires: %D\r\n"
- "Server: agedu\r\n"
+ "Server: " PNAME "\r\n"
"Connection: close\r\n"
"Content-Type: %s\r\n"
"\r\n"
* allocated data which the platform code will then write to the
* socket before closing it.
*/
-char *got_data(struct connctx *ctx, char *data, int length, int magic_access)
+char *got_data(struct connctx *ctx, char *data, int length,
+ int magic_access, const char *auth_string,
+ const struct html_config *cfg)
{
- char *line, *p, *q, *z1, *z2, c1, c2;
+ char *line, *p, *q, *r, *z1, *z2, c1, c2;
+ int auth_correct = 0;
unsigned long index;
char *document, *ret;
+ /*
+ * Add the data we've just received to our buffer.
+ */
if (ctx->datasize < ctx->datalen + length) {
ctx->datasize = (ctx->datalen + length) * 3 / 2 + 4096;
ctx->data = sresize(ctx->data, ctx->datasize, char);
ctx->datalen += length;
/*
- * See if we have enough of an HTTP request to work out our
- * response.
- */
- line = ctx->data;
- /*
- * RFC 2616 section 4.1: `In the interest of robustness, [...]
- * if the server is reading the protocol stream at the
- * beginning of a message and receives a CRLF first, it should
- * ignore the CRLF.'
+ * Gradually process the HTTP request as we receive it.
*/
- while (line - ctx->data < ctx->datalen &&
- (*line == '\r' || *line == '\n'))
- line++;
+ if (ctx->state == READING_REQ_LINE) {
+ /*
+ * We're waiting for the first line of the input, which
+ * contains the main HTTP request. See if we've got it
+ * yet.
+ */
- q = line;
- while (q - ctx->data < ctx->datalen && *q != '\r' && *q != '\n')
- q++;
- if (q - ctx->data >= ctx->datalen)
- return NULL; /* not got request line yet */
+ line = ctx->data;
+ /*
+ * RFC 2616 section 4.1: `In the interest of robustness,
+ * [...] if the server is reading the protocol stream at
+ * the beginning of a message and receives a CRLF first,
+ * it should ignore the CRLF.'
+ */
+ while (line - ctx->data < ctx->datalen &&
+ (*line == '\r' || *line == '\n'))
+ line++;
+ q = line;
+ while (q - ctx->data < ctx->datalen && *q != '\n')
+ q++;
+ if (q - ctx->data >= ctx->datalen)
+ return NULL; /* not got request line yet */
- /*
- * We've got the first line of the request, which is enough
- * for us to work out what to say in response and start
- * sending it. The platform side will keep reading data, but
- * it'll ignore it.
- *
- * So, zero-terminate our line and parse it.
- */
- *q = '\0';
- z1 = z2 = q;
- c1 = c2 = *q;
- p = line;
- while (*p && !isspace((unsigned char)*p)) p++;
- if (*p) {
- z1 = p++;
- c1 = *z1;
- *z1 = '\0';
+ /*
+ * We've got the first line of the request. Zero-terminate
+ * and parse it into method, URL and optional HTTP
+ * version.
+ */
+ *q = '\0';
+ ctx->headers = q+1;
+ if (q > line && q[-1] == '\r')
+ *--q = '\0';
+ z1 = z2 = q;
+ c1 = c2 = *q;
+ p = line;
+ while (*p && !isspace((unsigned char)*p)) p++;
+ if (*p) {
+ z1 = p++;
+ c1 = *z1;
+ *z1 = '\0';
+ }
+ while (*p && isspace((unsigned char)*p)) p++;
+ q = p;
+ while (*q && !isspace((unsigned char)*q)) q++;
+ z2 = q++;
+ c2 = *z2;
+ *z2 = '\0';
+ while (*q && isspace((unsigned char)*q)) q++;
+
+ /*
+ * Now `line' points at the method name; p points at the
+ * URL, if any; q points at the HTTP version, if any.
+ */
+
+ /*
+ * There should _be_ a URL, on any request type at all.
+ */
+ if (!*p) {
+ char *ret, *text;
+ /* Restore the request to the way we received it. */
+ *z2 = c2;
+ *z1 = c1;
+ text = dupfmt("<code>" PNAME "</code> received the HTTP request"
+ " \"<code>%h</code>\", which contains no URL.",
+ line);
+ ret = http_error("400", "Bad request", NULL, text);
+ sfree(text);
+ return ret;
+ }
+
+ ctx->method = line;
+ ctx->url = p;
+
+ /*
+ * If there was an HTTP version, we might need to see
+ * headers. Otherwise, the request is done.
+ */
+ if (*q) {
+ ctx->state = READING_HEADERS;
+ } else {
+ ctx->state = DONE;
+ }
}
- while (*p && isspace((unsigned char)*p)) p++;
- q = p;
- while (*q && !isspace((unsigned char)*q)) q++;
- z2 = q;
- c2 = *z2;
- *z2 = '\0';
- /*
- * Now `line' points at the method name; p points at the URL,
- * if any.
- */
-
- /*
- * There should _be_ a URL, on any request type at all.
- */
- if (!*p) {
- char *ret, *text;
- *z2 = c2;
- *z1 = c1;
- text = dupfmt("<code>agedu</code> received the HTTP request"
- " \"<code>%s</code>\", which contains no URL.",
- line);
- ret = http_error("400", "Bad request", text);
- sfree(text);
- return ret;
+ if (ctx->state == READING_HEADERS) {
+ /*
+ * While we're receiving the HTTP request headers, all we
+ * do is to keep scanning to see if we find two newlines
+ * next to each other.
+ */
+ q = ctx->data + ctx->datalen;
+ for (p = ctx->headers; p < q; p++) {
+ if (*p == '\n' &&
+ ((p+1 < q && p[1] == '\n') ||
+ (p+2 < q && p[1] == '\r' && p[2] == '\n'))) {
+ p[1] = '\0';
+ ctx->state = DONE;
+ break;
+ }
+ }
}
- if (!magic_access) {
- ret = http_error("403", "Forbidden",
- "This is a restricted-access set of pages.");
- } else {
- p += strspn(p, "/?");
- index = strtoul(p, NULL, 10);
- document = html_query(ctx->t, index, "%lu");
- if (document) {
- ret = http_success("text/html", 1, document);
- sfree(document);
+ if (ctx->state == DONE) {
+ /*
+ * Now we have the entire HTTP request. Decide what to do
+ * with it.
+ */
+ if (auth_string) {
+ /*
+ * Search the request headers for Authorization.
+ */
+ q = ctx->data + ctx->datalen;
+ for (p = ctx->headers; p < q; p++) {
+ const char *hdr = "Authorization:";
+ int i;
+ for (i = 0; hdr[i]; i++) {
+ if (p >= q || tolower((unsigned char)*p) !=
+ tolower((unsigned char)hdr[i]))
+ break;
+ p++;
+ }
+ if (!hdr[i])
+ break; /* found our header */
+ p = memchr(p, '\n', q - p);
+ if (!p)
+ p = q;
+ }
+ if (p < q) {
+ while (p < q && isspace((unsigned char)*p))
+ p++;
+ r = p;
+ while (p < q && !isspace((unsigned char)*p))
+ p++;
+ if (p < q) {
+ *p++ = '\0';
+ if (!strcasecmp(r, "Basic")) {
+ while (p < q && isspace((unsigned char)*p))
+ p++;
+ r = p;
+ while (p < q && !isspace((unsigned char)*p))
+ p++;
+ if (p < q) {
+ *p++ = '\0';
+ if (!strcmp(r, auth_string))
+ auth_correct = 1;
+ }
+ }
+ }
+ }
+ }
+
+ if (!magic_access && !auth_correct) {
+ if (auth_string) {
+ ret = http_error("401", "Unauthorized",
+ "WWW-Authenticate: Basic realm=\""PNAME"\"\r\n",
+ "\nYou must authenticate to view these pages.");
+ } else {
+ ret = http_error("403", "Forbidden", NULL,
+ "This is a restricted-access set of pages.");
+ }
} else {
- ret = http_error("404", "Not Found",
- "Pathname index out of range.");
+ p = ctx->url;
+ if (!html_parse_path(ctx->t, p, cfg, &index)) {
+ ret = http_error("404", "Not Found", NULL,
+ "This is not a valid pathname.");
+ } else {
+ char *canonpath = html_format_path(ctx->t, cfg, index);
+ if (!strcmp(canonpath, p)) {
+ /*
+ * This is a canonical path. Return the document.
+ */
+ document = html_query(ctx->t, index, cfg, 1);
+ if (document) {
+ ret = http_success("text/html", 1, document);
+ sfree(document);
+ } else {
+ ret = http_error("404", "Not Found", NULL,
+ "This is not a valid pathname.");
+ }
+ } else {
+ /*
+ * This is a non-canonical path. Return a redirect
+ * to the right one.
+ *
+ * To do this, we must search the request headers
+ * for Host:, to see what the client thought it
+ * was calling our server.
+ */
+
+ char *host = NULL;
+ q = ctx->data + ctx->datalen;
+ for (p = ctx->headers; p < q; p++) {
+ const char *hdr = "Host:";
+ int i;
+ for (i = 0; hdr[i]; i++) {
+ if (p >= q || tolower((unsigned char)*p) !=
+ tolower((unsigned char)hdr[i]))
+ break;
+ p++;
+ }
+ if (!hdr[i])
+ break; /* found our header */
+ p = memchr(p, '\n', q - p);
+ if (!p)
+ p = q;
+ }
+ if (p < q) {
+ while (p < q && isspace((unsigned char)*p))
+ p++;
+ r = p;
+ while (p < q) {
+ if (*p == '\r' && (p+1 >= q || p[1] == '\n'))
+ break;
+ p++;
+ }
+ host = snewn(p-r+1, char);
+ memcpy(host, r, p-r);
+ host[p-r] = '\0';
+ }
+ if (host) {
+ char *header = dupfmt("Location: http://%s%s\r\n",
+ host, canonpath);
+ ret = http_error("301", "Moved", header,
+ "This is not the canonical form of"
+ " this pathname.");
+ sfree(header);
+ } else {
+ ret = http_error("400", "Bad Request", NULL,
+ "Needed a Host: header to return"
+ " the intended redirection.");
+ }
+ }
+ sfree(canonpath);
+ }
}
- }
- return ret;
+ return ret;
+ } else
+ return NULL;
}
/* --- Platform support for running a web server. --- */
return ret;
}
-void check_magic_access(struct fd *fd)
+int check_owning_uid(int fd, int flip)
{
- struct sockaddr_in sock, peer;
+ struct sockaddr_storage sock, peer;
+ int connected;
socklen_t addrlen;
- char linebuf[4096], matchbuf[80];
+ char linebuf[4096], matchbuf[128];
+ char *filename;
+ int matchcol, uidcol;
FILE *fp;
addrlen = sizeof(sock);
- if (getsockname(fd->fd, (struct sockaddr *)&sock, &addrlen)) {
+ if (getsockname(fd, (struct sockaddr *)&sock, &addrlen)) {
fprintf(stderr, "getsockname: %s\n", strerror(errno));
exit(1);
}
addrlen = sizeof(peer);
- if (getpeername(fd->fd, (struct sockaddr *)&peer, &addrlen)) {
- fprintf(stderr, "getpeername: %s\n", strerror(errno));
- exit(1);
+ connected = 1;
+ if (getpeername(fd, (struct sockaddr *)&peer, &addrlen)) {
+ if (errno == ENOTCONN) {
+ connected = 0;
+ memset(&peer, 0, sizeof(peer));
+ peer.ss_family = sock.ss_family;
+ } else {
+ fprintf(stderr, "getpeername: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+
+ if (flip) {
+ struct sockaddr_storage tmp = sock;
+ sock = peer;
+ peer = tmp;
+ }
+
+#ifndef NO_IPV4
+ if (peer.ss_family == AF_INET) {
+ struct sockaddr_in *sock4 = (struct sockaddr_in *)&sock;
+ struct sockaddr_in *peer4 = (struct sockaddr_in *)&peer;
+
+ assert(peer4->sin_family == AF_INET);
+
+ sprintf(matchbuf, "%08X:%04X %08X:%04X",
+ peer4->sin_addr.s_addr, ntohs(peer4->sin_port),
+ sock4->sin_addr.s_addr, ntohs(sock4->sin_port));
+ filename = "/proc/net/tcp";
+ matchcol = 6;
+ uidcol = 75;
+ } else
+#endif
+#ifndef NO_IPV6
+ if (peer.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sock6 = (struct sockaddr_in6 *)&sock;
+ struct sockaddr_in6 *peer6 = (struct sockaddr_in6 *)&peer;
+ char *p;
+
+ assert(peer6->sin6_family == AF_INET6);
+
+ p = matchbuf;
+ for (int i = 0; i < 4; i++)
+ p += sprintf(p, "%08X",
+ ((uint32_t *)peer6->sin6_addr.s6_addr)[i]);
+ p += sprintf(p, ":%04X ", ntohs(peer6->sin6_port));
+ for (int i = 0; i < 4; i++)
+ p += sprintf(p, "%08X",
+ ((uint32_t *)sock6->sin6_addr.s6_addr)[i]);
+ p += sprintf(p, ":%04X", ntohs(sock6->sin6_port));
+
+ filename = "/proc/net/tcp6";
+ matchcol = 6;
+ uidcol = 123;
+ } else
+#endif
+ {
+ return -1; /* unidentified family */
}
- sprintf(matchbuf, "%08X:%04X %08X:%04X",
- peer.sin_addr.s_addr, ntohs(peer.sin_port),
- sock.sin_addr.s_addr, ntohs(sock.sin_port));
- fp = fopen("/proc/net/tcp", "r");
+ fp = fopen(filename, "r");
if (fp) {
while (fgets(linebuf, sizeof(linebuf), fp)) {
- if (strlen(linebuf) >= 75 &&
- !strncmp(linebuf+6, matchbuf, strlen(matchbuf))) {
- int uid = atoi(linebuf + 75);
- if (uid == getuid())
- fd->magic_access = 1;
+ if (strlen(linebuf) >= uidcol &&
+ !strncmp(linebuf+matchcol, matchbuf, strlen(matchbuf))) {
+ fclose(fp);
+ return atoi(linebuf + uidcol);
}
}
+ fclose(fp);
}
+
+ return -1;
}
-void run_httpd(const void *t)
+void check_magic_access(struct fd *fd)
{
- int fd;
- unsigned long ipaddr;
- struct fd *f;
- struct sockaddr_in addr;
+ if (check_owning_uid(fd->fd, 0) == getuid())
+ fd->magic_access = 1;
+}
+
+static void base64_encode_atom(unsigned char *data, int n, char *out)
+{
+ static const char base64_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ unsigned word;
+
+ word = data[0] << 16;
+ if (n > 1)
+ word |= data[1] << 8;
+ if (n > 2)
+ word |= data[2];
+ out[0] = base64_chars[(word >> 18) & 0x3F];
+ out[1] = base64_chars[(word >> 12) & 0x3F];
+ if (n > 1)
+ out[2] = base64_chars[(word >> 6) & 0x3F];
+ else
+ out[2] = '=';
+ if (n > 2)
+ out[3] = base64_chars[word & 0x3F];
+ else
+ out[3] = '=';
+}
+
+struct listenfds {
+ int v4, v6;
+};
+
+static int make_listening_sockets(struct listenfds *fds, const char *address,
+ const char *portstr, char **outhostname)
+{
+ /*
+ * Establish up to 2 listening sockets, for IPv4 and IPv6, on the
+ * same arbitrarily selected port. Return them in fds.v4 and
+ * fds.v6, with each entry being -1 if that socket was not
+ * established at all. Main return value is the port chosen, or <0
+ * if the whole process failed.
+ */
+ struct sockaddr_in6 addr6;
+ struct sockaddr_in addr4;
+ int got_v6, got_v4;
socklen_t addrlen;
+ int ret, port = 0;
/*
- * Establish the listening socket and retrieve its port
- * number.
+ * Special case of the address parameter: if it's "0.0.0.0", treat
+ * it like NULL, because that was how you specified listen-on-any-
+ * address in versions before the IPv6 revamp.
*/
- fd = socket(PF_INET, SOCK_STREAM, 0);
- if (fd < 0) {
- fprintf(stderr, "socket(PF_INET): %s\n", strerror(errno));
- exit(1);
+ {
+ int u,v,w,x;
+ if (address &&
+ 4 == sscanf(address, "%d.%d.%d.%d", &u, &v, &w, &x) &&
+ u==0 && v==0 && w==0 && x==0)
+ address = NULL;
}
- addr.sin_family = AF_INET;
- srand(0L);
- ipaddr = 0x7f000000;
- ipaddr += (1 + rand() % 255) << 16;
- ipaddr += (1 + rand() % 255) << 8;
- ipaddr += (1 + rand() % 255);
- addr.sin_addr.s_addr = htonl(ipaddr);
- addr.sin_port = htons(0);
- addrlen = sizeof(addr);
- if (bind(fd, (struct sockaddr *)&addr, addrlen) < 0) {
- fprintf(stderr, "bind: %s\n", strerror(errno));
- exit(1);
+
+ if (portstr && !*portstr)
+ portstr = NULL; /* normalise NULL and empty string */
+
+ if (!address) {
+ char hostname[HOST_NAME_MAX];
+ if (gethostname(hostname, sizeof(hostname)) < 0) {
+ perror("hostname");
+ return -1;
+ }
+ *outhostname = dupstr(hostname);
+ } else {
+ *outhostname = dupstr(address);
}
- if (listen(fd, 5) < 0) {
- fprintf(stderr, "listen: %s\n", strerror(errno));
- exit(1);
+
+ fds->v6 = fds->v4 = -1;
+ got_v6 = got_v4 = 0;
+
+#if defined HAVE_GETADDRINFO
+
+ /*
+ * Resolve the given address using getaddrinfo, yielding an IPv6
+ * address or an IPv4 one or both.
+ */
+
+ struct addrinfo hints;
+ struct addrinfo *addrs, *ai;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ hints.ai_flags = AI_PASSIVE;
+ ret = getaddrinfo(address, portstr, &hints, &addrs);
+ if (ret) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
+ return -1;
}
- addrlen = sizeof(addr);
- if (getsockname(fd, (struct sockaddr *)&addr, &addrlen)) {
- fprintf(stderr, "getsockname: %s\n", strerror(errno));
- exit(1);
+ for (ai = addrs; ai; ai = ai->ai_next) {
+#ifndef NO_IPV6
+ if (!got_v6 && ai->ai_family == AF_INET6) {
+ memcpy(&addr6, ai->ai_addr, ai->ai_addrlen);
+ if (portstr && !port)
+ port = ntohs(addr6.sin6_port);
+ got_v6 = 1;
+ }
+#endif
+#ifndef NO_IPV4
+ if (!got_v4 && ai->ai_family == AF_INET) {
+ memcpy(&addr4, ai->ai_addr, ai->ai_addrlen);
+ if (portstr && !port)
+ port = ntohs(addr4.sin_port);
+ got_v4 = 1;
+ }
+#endif
}
- printf("Server is at http://%s:%d/\n",
- inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+
+#elif defined HAVE_GETHOSTBYNAME
/*
- * Now construct an fd structure to hold it.
+ * IPv4-only setup using inet_addr and gethostbyname.
*/
- f = new_fdstruct(fd, FD_LISTENER);
+ struct hostent *h;
+
+ memset(&addr4, 0, sizeof(addr4));
+ addr4.sin_family = AF_INET;
+
+ if (!address) {
+ addr4.sin_addr.s_addr = htons(INADDR_ANY);
+ got_v4 = 1;
+ } else if (inet_aton(address, &addr4.sin_addr)) {
+ got_v4 = 1; /* numeric address */
+ } else if ((h = gethostbyname(address)) != NULL) {
+ memcpy(&addr4.sin_addr, h->h_addr, sizeof(addr4.sin_addr));
+ got_v4 = 1;
+ } else {
+ fprintf(stderr, "gethostbyname: %s\n", hstrerror(h_errno));
+ return -1;
+ }
+
+ if (portstr) {
+ struct servent *s;
+ if (!portstr[strspn(portstr, "0123456789")]) {
+ port = atoi(portstr);
+ } else if ((s = getservbyname(portstr, NULL)) != NULL) {
+ port = ntohs(s->s_port);
+ } else {
+ fprintf(stderr, "getservbyname: port '%s' not understood\n",
+ portstr);
+ return -1;
+ }
+ }
+
+#endif
+
+#ifndef NO_IPV6
+#ifndef NO_IPV4
+ retry:
+#endif
+ if (got_v6) {
+ fds->v6 = socket(PF_INET6, SOCK_STREAM, 0);
+ if (fds->v6 < 0) {
+ fprintf(stderr, "socket(PF_INET6): %s\n", strerror(errno));
+ goto done_v6;
+ }
+#ifdef IPV6_V6ONLY
+ {
+ int i = 1;
+ if (setsockopt(fds->v6, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&i, sizeof(i)) < 0) {
+ fprintf(stderr, "setsockopt(IPV6_V6ONLY): %s\n",
+ strerror(errno));
+ close(fds->v6);
+ fds->v6 = -1;
+ goto done_v6;
+ }
+ }
+#endif /* IPV6_V6ONLY */
+ addr6.sin6_port = htons(port);
+ addrlen = sizeof(addr6);
+ if (bind(fds->v6, (const struct sockaddr *)&addr6, addrlen) < 0) {
+ fprintf(stderr, "bind: %s\n", strerror(errno));
+ close(fds->v6);
+ fds->v6 = -1;
+ goto done_v6;
+ }
+ if (listen(fds->v6, 5) < 0) {
+ fprintf(stderr, "listen: %s\n", strerror(errno));
+ close(fds->v6);
+ fds->v6 = -1;
+ goto done_v6;
+ }
+ if (port == 0) {
+ addrlen = sizeof(addr6);
+ if (getsockname(fds->v6, (struct sockaddr *)&addr6,
+ &addrlen) < 0) {
+ fprintf(stderr, "getsockname: %s\n", strerror(errno));
+ close(fds->v6);
+ fds->v6 = -1;
+ goto done_v6;
+ }
+ port = ntohs(addr6.sin6_port);
+ }
+ }
+ done_v6:
+#endif
+
+#ifndef NO_IPV4
+ if (got_v4) {
+ fds->v4 = socket(PF_INET, SOCK_STREAM, 0);
+ if (fds->v4 < 0) {
+ fprintf(stderr, "socket(PF_INET): %s\n", strerror(errno));
+ goto done_v4;
+ }
+ addr4.sin_port = htons(port);
+ addrlen = sizeof(addr4);
+ if (bind(fds->v4, (const struct sockaddr *)&addr4, addrlen) < 0) {
+#ifndef NO_IPV6
+ if (fds->v6 >= 0) {
+ /*
+ * If we support both v6 and v4, it's a failure
+ * condition if we didn't manage to bind to both. If
+ * the port number was arbitrary, we go round and try
+ * again. Otherwise, give up.
+ */
+ close(fds->v6);
+ close(fds->v4);
+ fds->v6 = fds->v4 = -1;
+ port = 0;
+ if (!portstr)
+ goto retry;
+ }
+#endif
+ fprintf(stderr, "bind: %s\n", strerror(errno));
+ close(fds->v4);
+ fds->v4 = -1;
+ goto done_v4;
+ }
+ if (listen(fds->v4, 5) < 0) {
+ fprintf(stderr, "listen: %s\n", strerror(errno));
+ close(fds->v4);
+ fds->v4 = -1;
+ goto done_v4;
+ }
+ if (port == 0) {
+ addrlen = sizeof(addr4);
+ if (getsockname(fds->v4, (struct sockaddr *)&addr4,
+ &addrlen) < 0) {
+ fprintf(stderr, "getsockname: %s\n", strerror(errno));
+ close(fds->v4);
+ fds->v4 = -1;
+ goto done_v4;
+ }
+ port = ntohs(addr4.sin_port);
+ }
+ }
+ done_v4:
+#endif
+
+ if (fds->v6 >= 0 || fds->v4 >= 0)
+ return port;
+ else
+ return -1;
+}
+
+void run_httpd(const void *t, int authmask, const struct httpd_config *dcfg,
+ const struct html_config *incfg)
+{
+ struct listenfds lfds;
+ int ret, port;
+ int authtype;
+ char *authstring = NULL;
+ char *hostname;
+ struct sockaddr_in addr;
+ socklen_t addrlen;
+ struct html_config cfg = *incfg;
/*
- * Read from standard input, and treat EOF as a notification
- * to exit.
+ * Establish the listening socket(s) and retrieve its port
+ * number.
*/
- new_fdstruct(0, FD_CLIENT);
+ port = make_listening_sockets(&lfds, dcfg->address, dcfg->port, &hostname);
+ if (port < 0)
+ exit(1); /* already reported an error */
+
+ if ((authmask & HTTPD_AUTH_MAGIC) &&
+ (lfds.v4 < 0 || check_owning_uid(lfds.v4, 1) == getuid()) &&
+ (lfds.v6 < 0 || check_owning_uid(lfds.v6, 1) == getuid())) {
+ authtype = HTTPD_AUTH_MAGIC;
+ if (authmask != HTTPD_AUTH_MAGIC)
+ printf("Using Linux /proc/net magic authentication\n");
+ } else if ((authmask & HTTPD_AUTH_BASIC)) {
+ char username[128], password[128], userpassbuf[259];
+ const char *userpass;
+ const char *rname;
+ unsigned char passbuf[10];
+ int i, j, k, fd;
+
+ authtype = HTTPD_AUTH_BASIC;
+
+ if (authmask != HTTPD_AUTH_BASIC)
+ printf("Using HTTP Basic authentication\n");
+
+ if (dcfg->basicauthdata) {
+ userpass = dcfg->basicauthdata;
+ } else {
+ strcpy(username, PNAME);
+ rname = "/dev/urandom";
+ fd = open(rname, O_RDONLY);
+ if (fd < 0) {
+ int err = errno;
+ rname = "/dev/random";
+ fd = open(rname, O_RDONLY);
+ if (fd < 0) {
+ int err2 = errno;
+ fprintf(stderr, "/dev/urandom: open: %s\n", strerror(err));
+ fprintf(stderr, "/dev/random: open: %s\n", strerror(err2));
+ exit(1);
+ }
+ }
+ for (i = 0; i < 10 ;) {
+ j = read(fd, passbuf + i, 10 - i);
+ if (j <= 0) {
+ fprintf(stderr, "%s: read: %s\n", rname,
+ j < 0 ? strerror(errno) : "unexpected EOF");
+ exit(1);
+ }
+ i += j;
+ }
+ close(fd);
+ for (i = 0; i < 16; i++) {
+ /*
+ * 32 characters out of the 36 alphanumerics gives
+ * me the latitude to discard i,l,o for being too
+ * numeric-looking, and w because it has two too
+ * many syllables and one too many presidential
+ * associations.
+ */
+ static const char chars[32] =
+ "0123456789abcdefghjkmnpqrstuvxyz";
+ int v = 0;
+
+ k = i / 8 * 5;
+ for (j = 0; j < 5; j++)
+ v |= ((passbuf[k+j] >> (i%8)) & 1) << j;
+
+ password[i] = chars[v];
+ }
+ password[i] = '\0';
+
+ sprintf(userpassbuf, "%s:%s", username, password);
+ userpass = userpassbuf;
+
+ printf("Username: %s\nPassword: %s\n", username, password);
+ }
+
+ k = strlen(userpass);
+ authstring = snewn(k * 4 / 3 + 16, char);
+ for (i = j = 0; i < k ;) {
+ int s = k-i < 3 ? k-i : 3;
+ base64_encode_atom((unsigned char *)(userpass+i), s, authstring+j);
+ i += s;
+ j += 4;
+ }
+ authstring[j] = '\0';
+ } else if ((authmask & HTTPD_AUTH_NONE)) {
+ authtype = HTTPD_AUTH_NONE;
+ if (authmask != HTTPD_AUTH_NONE)
+ printf("Web server is unauthenticated\n");
+ } else {
+ fprintf(stderr, PNAME ": authentication method not supported\n");
+ exit(1);
+ }
+ if (port == 80) {
+ printf("URL: http://%s/\n", hostname);
+ } else {
+ printf("URL: http://%s:%d/\n", hostname, port);
+ }
+ fflush(stdout);
+
+ /*
+ * Now construct fd structure(s) to hold the listening sockets.
+ */
+ if (lfds.v4 >= 0)
+ new_fdstruct(lfds.v4, FD_LISTENER);
+ if (lfds.v6 >= 0)
+ new_fdstruct(lfds.v6, FD_LISTENER);
+
+ if (dcfg->closeoneof) {
+ /*
+ * Read from standard input, and treat EOF as a notification
+ * to exit.
+ */
+ new_fdstruct(0, FD_CLIENT);
+ }
/*
* Now we're ready to run our main loop. Keep looping round on
*/
while (1) {
fd_set rfds, wfds;
- int i, j, maxfd, ret;
+ int i, j;
+ SELECT_TYPE_ARG1 maxfd;
+ int ret;
#define FD_SET_MAX(fd, set, max) \
do { FD_SET((fd),(set)); (max) = ((max)<=(fd)?(fd)+1:(max)); } while(0)
switch (fds[i].type) {
case FD_CLIENT:
+ FD_SET_MAX(fds[i].fd, &rfds, maxfd);
+ break;
case FD_LISTENER:
FD_SET_MAX(fds[i].fd, &rfds, maxfd);
break;
}
nfds = i;
- ret = select(maxfd, &rfds, &wfds, NULL, NULL);
+ ret = select(maxfd, SELECT_TYPE_ARG234 &rfds,
+ SELECT_TYPE_ARG234 &wfds, SELECT_TYPE_ARG234 NULL,
+ SELECT_TYPE_ARG5 NULL);
if (ret <= 0) {
if (ret < 0 && (errno != EINTR)) {
fprintf(stderr, "select: %s", strerror(errno));
f = new_fdstruct(newfd, FD_CONNECTION);
f->cctx = new_connection(t);
- check_magic_access(f);
+ if (authtype == HTTPD_AUTH_MAGIC)
+ check_magic_access(f);
}
break;
case FD_CONNECTION:
* yet, keep processing data in the
* hope of acquiring one.
*/
- fds[i].wdata = got_data(fds[i].cctx, readbuf,
- ret, fds[i].magic_access);
+ fds[i].wdata = got_data
+ (fds[i].cctx, readbuf, ret,
+ (authtype == HTTPD_AUTH_NONE ||
+ fds[i].magic_access), authstring, &cfg);
if (fds[i].wdata) {
fds[i].wdatalen = strlen(fds[i].wdata);
fds[i].wdatapos = 0;