From 812e4bf21dbd3754acf9cec3ea8065054f684c8e Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 30 Oct 2008 19:32:25 +0000 Subject: [PATCH] Introduce something more like an actual framework for httpd authentication. We now support magic /proc/net auth, HTTP Basic auth, and no auth; a command-line switch chooses between those three or the fourth default choice of falling back from /proc/net to Basic. git-svn-id: svn://svn.tartarus.org/sgt/agedu@8228 cda61777-01e9-0310-a592-d414129be87e --- TODO | 9 +- agedu.c | 26 ++++- httpd.c | 407 ++++++++++++++++++++++++++++++++++++++++++++++++++-------------- httpd.h | 6 +- 4 files changed, 352 insertions(+), 96 deletions(-) diff --git a/TODO b/TODO index 6b3a311..f08917b 100644 --- a/TODO +++ b/TODO @@ -3,12 +3,6 @@ TODO list for agedu Before it's non-embarrassingly releasable: - - render HTTP access control more sane. - * we should have the configurable option to use HTTP Basic - authentication or Linux magic /proc/net/tcp - * a third option, and the default one, should be to _try_ to use - magic auth, and fall back to HTTP Basic if unavailable - - sort out the command line syntax * I think there should be a unified --mode / -M for every running mode, possibly without the one-letter option for the @@ -16,6 +10,9 @@ Before it's non-embarrassingly releasable: * there should be some configurable options: + range limits on the age display + server address in httpd mode + + HTTP authentication: specify username and/or password, the + latter by at least some means which doesn't involve it + showing up in "ps" - do some configurability for the disk scan * wildcard-based includes and excludes diff --git a/agedu.c b/agedu.c index 176714b..242865e 100644 --- a/agedu.c +++ b/agedu.c @@ -140,6 +140,7 @@ int main(int argc, char **argv) int doing_opts = 1; enum { QUERY, HTML, SCAN, DUMP, HTTPD } mode = QUERY; char *minage = "0d"; + int auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC; while (--argc > 0) { char *p = *++argv; @@ -172,6 +173,10 @@ int main(int argc, char **argv) !strcmp(p, "--server")) { mode = HTTPD; } else if (!strcmp(p, "--file") || + !strcmp(p, "--auth") || + !strcmp(p, "--http-auth") || + !strcmp(p, "--httpd-auth") || + !strcmp(p, "--server-auth") || !strcmp(p, "--minimum-age") || !strcmp(p, "--min-age") || !strcmp(p, "--age")) { @@ -193,6 +198,25 @@ int main(int argc, char **argv) !strcmp(p, "--min-age") || !strcmp(p, "--age")) { minage = optval; + } else if (!strcmp(p, "--auth") || + !strcmp(p, "--http-auth") || + !strcmp(p, "--httpd-auth") || + !strcmp(p, "--server-auth")) { + if (!strcmp(optval, "magic")) + auth = HTTPD_AUTH_MAGIC; + else if (!strcmp(optval, "basic")) + auth = HTTPD_AUTH_BASIC; + else if (!strcmp(optval, "none")) + auth = HTTPD_AUTH_NONE; + else if (!strcmp(optval, "default")) + auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC; + else { + fprintf(stderr, "%s: unrecognised authentication" + " type '%s'\n%*s options are 'magic'," + " 'basic', 'none', 'default'\n", + PNAME, optval, (int)strlen(PNAME), ""); + return 1; + } } } else { fprintf(stderr, "%s: unrecognised option '%s'\n", @@ -471,7 +495,7 @@ int main(int argc, char **argv) return 1; } - run_httpd(mappedfile); + run_httpd(mappedfile, auth); } return 0; diff --git a/httpd.c b/httpd.c index 83961e1..486ca21 100644 --- a/httpd.c +++ b/httpd.c @@ -22,13 +22,22 @@ #include "malloc.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 +50,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 +61,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" "Connection: close\r\n" + "%s" "Content-Type: text/html; charset=US-ASCII\r\n" "\r\n" "\r\n" @@ -65,6 +78,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); } @@ -88,12 +102,17 @@ 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) { - 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 +121,185 @@ 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. - */ - 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("agedu 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 && !auth_provided) { + ret = http_error("401", "Unauthorized", + "WWW-Authenticate: Basic realm=\"agedu\"\r\n", + "Please 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; + 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); + } 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 +341,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 +349,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,17 +378,49 @@ 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; + return atoi(linebuf + 75); } } } + + return -1; +} + +void check_magic_access(struct fd *fd) +{ + if (check_owning_uid(fd->fd, 0) == getuid()) + fd->magic_access = 1; } -void run_httpd(const void *t) +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) { int fd; + int authtype; + char *authstring = NULL, authbuf[512]; unsigned long ipaddr; struct fd *f; struct sockaddr_in addr; @@ -297,7 +457,75 @@ void run_httpd(const void *t) fprintf(stderr, "getsockname: %s\n", strerror(errno)); exit(1); } - printf("Server is at http://%s:%d/\n", + if ((authmask & HTTPD_AUTH_MAGIC) && + (check_owning_uid(fd, 1) == getuid())) { + authtype = HTTPD_AUTH_MAGIC; + printf("Using Linux /proc/net magic authentication\n"); + } else if ((authmask & HTTPD_AUTH_BASIC)) { + char username[128], password[128], userpass[259]; + const char *rname; + unsigned char passbuf[10]; + int i, j, k, fd; + + authtype = HTTPD_AUTH_BASIC; + + sprintf(username, "agedu"); + 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'; + + printf("Using HTTP Basic authentication\nUsername: %s\nPassword: %s\n", + username, password); + + k = sprintf(userpass, "%s:%s", username, password); + for (i = j = 0; i < k ;) { + int s = k-i < 3 ? k-i : 3; + base64_encode_atom((unsigned char *)(userpass+i), s, authbuf+j); + i += s; + j += 4; + } + authbuf[j] = '\0'; + authstring = authbuf; + } else { + authtype = HTTPD_AUTH_NONE; + printf("Web server is unauthenticated\n"); + } + printf("URL: http://%s:%d/\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); /* @@ -406,7 +634,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 +663,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); if (fds[i].wdata) { fds[i].wdatalen = strlen(fds[i].wdata); fds[i].wdatapos = 0; diff --git a/httpd.h b/httpd.h index fb3b1a5..c2ffda8 100644 --- a/httpd.h +++ b/httpd.h @@ -3,4 +3,8 @@ * pages generated by html.h. */ -void run_httpd(const void *t); +#define HTTPD_AUTH_MAGIC 1 +#define HTTPD_AUTH_BASIC 2 +#define HTTPD_AUTH_NONE 4 + +void run_httpd(const void *t, int authmask); -- 2.11.0