X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/agedu/blobdiff_plain/70322ae3751bc07ac749dffad79a5f3420e67b55..2e6654103f93c61206cf60b65bc261ecd164e424:/httpd.c diff --git a/httpd.c b/httpd.c index 83961e1..62c1ceb 100644 --- a/httpd.c +++ b/httpd.c @@ -2,33 +2,25 @@ * httpd.c: implementation of httpd.h. */ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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; }; /* @@ -41,6 +33,8 @@ struct connctx *new_connection(const void *t) 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; } @@ -50,12 +44,14 @@ void free_connection(struct connctx *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" "\r\n" @@ -65,6 +61,7 @@ static char *http_error(char *code, char *errmsg, char *errtext, ...) "

%s %s

\r\n" "

%s

\r\n" "\r\n", code, errmsg, + extraheader ? extraheader : "", code, errmsg, code, errmsg, errtext); } @@ -73,7 +70,7 @@ static char *http_success(char *mimetype, int stuff_cr, char *document) 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" @@ -88,12 +85,18 @@ static char *http_success(char *mimetype, int stuff_cr, char *document) * 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_provided = 0, 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); @@ -102,87 +105,191 @@ char *got_data(struct connctx *ctx, char *data, int length, int magic_access) ctx->datalen += length; /* - * See if we have enough of an HTTP request to work out our - * response. + * Gradually process the HTTP request as we receive it. */ - 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++; + 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("" PNAME " received the HTTP request" + " \"%h\", 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("agedu received the HTTP request" - " \"%s\", 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) { + auth_provided = 1; + 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."); + char *q; + p = ctx->url; + p += strspn(p, "/?"); + index = strtoul(p, &q, 10); + if (*q) { + ret = http_error("404", "Not Found", NULL, + "This is not a valid pathname index."); + } else { + document = html_query(ctx->t, index, cfg); + if (document) { + ret = http_success("text/html", 1, document); + sfree(document); + } else { + ret = http_error("404", "Not Found", NULL, + "Pathname index out of range."); + } + } } - } - return ret; + return ret; + } else + return NULL; } /* --- Platform support for running a web server. --- */ @@ -224,7 +331,7 @@ struct fd *new_fdstruct(int fd, int type) return ret; } -void check_magic_access(struct fd *fd) +int check_owning_uid(int fd, int flip) { struct sockaddr_in sock, peer; socklen_t addrlen; @@ -232,14 +339,25 @@ void check_magic_access(struct fd *fd) 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); + if (getpeername(fd, (struct sockaddr *)&peer, &addrlen)) { + if (errno == ENOTCONN) { + peer.sin_addr.s_addr = htonl(0); + peer.sin_port = htons(0); + } else { + fprintf(stderr, "getpeername: %s\n", strerror(errno)); + exit(1); + } + } + + if (flip) { + struct sockaddr_in tmp = sock; + sock = peer; + peer = tmp; } sprintf(matchbuf, "%08X:%04X %08X:%04X", @@ -250,21 +368,58 @@ void check_magic_access(struct fd *fd) 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; + fclose(fp); + return atoi(linebuf + 75); } } + fclose(fp); } + + return -1; } -void run_httpd(const void *t) +void check_magic_access(struct fd *fd) { - int fd; - unsigned long ipaddr; + 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] = '='; +} + +void run_httpd(const void *t, int authmask, const struct httpd_config *dcfg, + const struct html_config *incfg) +{ + int fd, ret; + int authtype; + char *authstring = NULL; struct fd *f; struct sockaddr_in addr; socklen_t addrlen; + struct html_config cfg = *incfg; + + cfg.format = "%.0lu"; /* * Establish the listening socket and retrieve its port @@ -276,15 +431,38 @@ void run_httpd(const void *t) exit(1); } 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); + if (!dcfg->address) { +#ifdef RANDOM_LOCALHOST + unsigned long ipaddr; + 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); +#else + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); +#endif + addr.sin_port = htons(0); + } else { + addr.sin_addr.s_addr = inet_addr(dcfg->address); + addr.sin_port = dcfg->port ? htons(dcfg->port) : 0; + } addrlen = sizeof(addr); - if (bind(fd, (struct sockaddr *)&addr, addrlen) < 0) { + ret = bind(fd, (const struct sockaddr *)&addr, addrlen); +#ifdef RANDOM_LOCALHOST + if (ret < 0 && errno == EADDRNOTAVAIL && !dcfg->address) { + /* + * Some systems don't like us binding to random weird + * localhost-space addresses. Try again with the official + * INADDR_LOOPBACK. + */ + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = htons(0); + ret = bind(fd, (const struct sockaddr *)&addr, addrlen); + } +#endif + if (ret < 0) { fprintf(stderr, "bind: %s\n", strerror(errno)); exit(1); } @@ -297,8 +475,101 @@ void run_httpd(const void *t) fprintf(stderr, "getsockname: %s\n", strerror(errno)); exit(1); } - printf("Server is at http://%s:%d/\n", - inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + if ((authmask & HTTPD_AUTH_MAGIC) && + (check_owning_uid(fd, 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 (ntohs(addr.sin_addr.s_addr) == INADDR_ANY) { + printf("Server port: %d\n", ntohs(addr.sin_port)); + } else if (ntohs(addr.sin_port) == 80) { + printf("URL: http://%s/\n", inet_ntoa(addr.sin_addr)); + } else { + printf("URL: http://%s:%d/\n", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + } /* * Now construct an fd structure to hold it. @@ -317,7 +588,9 @@ void run_httpd(const void *t) */ 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) @@ -341,6 +614,8 @@ void run_httpd(const void *t) 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; @@ -366,7 +641,9 @@ void run_httpd(const void *t) } 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)); @@ -406,7 +683,8 @@ void run_httpd(const void *t) 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: @@ -434,8 +712,10 @@ void run_httpd(const void *t) * 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;