Revert one change from r8928 and instead apply the right answer:
[sgt/agedu] / httpd.c
diff --git a/httpd.c b/httpd.c
index 486ca21..6c90e66 100644 (file)
--- a/httpd.c
+++ b/httpd.c
@@ -2,25 +2,8 @@
  * 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"
 
@@ -66,7 +49,7 @@ static char *http_error(char *code, char *errmsg, char *extraheader,
 {
     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"
@@ -87,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"
@@ -103,10 +86,11 @@ static char *http_success(char *mimetype, int stuff_cr, char *document)
  * socket before closing it.
  */
 char *got_data(struct connctx *ctx, char *data, int length,
-              int magic_access, const char *auth_string)
+              int magic_access, const char *auth_string,
+              const struct html_config *cfg)
 {
     char *line, *p, *q, *r, *z1, *z2, c1, c2;
-    int auth_provided = 0, auth_correct = 0;
+    int auth_correct = 0;
     unsigned long index;
     char *document, *ret;
 
@@ -185,7 +169,7 @@ char *got_data(struct connctx *ctx, char *data, int length,
            /* Restore the request to the way we received it. */
            *z2 = c2;
            *z1 = c1;
-           text = dupfmt("<code>agedu</code> received the HTTP request"
+           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);
@@ -251,7 +235,6 @@ char *got_data(struct connctx *ctx, char *data, int length,
                    p = q;
            }
            if (p < q) {
-               auth_provided = 1;
                while (p < q && isspace((unsigned char)*p))
                    p++;
                r = p;
@@ -276,25 +259,87 @@ char *got_data(struct connctx *ctx, char *data, int length,
        }
 
        if (!magic_access && !auth_correct) {
-           if (auth_string && !auth_provided) {
+           if (auth_string) {
                ret = http_error("401", "Unauthorized",
-                                "WWW-Authenticate: Basic realm=\"agedu\"\r\n",
-                                "Please authenticate to view these pages.");
+                                "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 {
            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 {
+           if (!html_parse_path(ctx->t, p, cfg, &index)) {
                ret = http_error("404", "Not Found", NULL,
-                                "Pathname index out of range.");
+                                "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;
@@ -378,9 +423,11 @@ int check_owning_uid(int fd, int flip)
        while (fgets(linebuf, sizeof(linebuf), fp)) {
            if (strlen(linebuf) >= 75 &&
                !strncmp(linebuf+6, matchbuf, strlen(matchbuf))) {
+               fclose(fp);
                return atoi(linebuf + 75);
            }
        }
+       fclose(fp);
     }
 
     return -1;
@@ -416,15 +463,15 @@ static void base64_encode_atom(unsigned char *data, int n, char *out)
        out[3] = '=';
 }
 
-void run_httpd(const void *t, int authmask)
+void run_httpd(const void *t, int authmask, const struct httpd_config *dcfg,
+              const struct html_config *incfg)
 {
-    int fd;
+    int fd, ret;
     int authtype;
-    char *authstring = NULL, authbuf[512];
-    unsigned long ipaddr;
-    struct fd *f;
+    char *authstring = NULL;
     struct sockaddr_in addr;
     socklen_t addrlen;
+    struct html_config cfg = *incfg;
 
     /*
      * Establish the listening socket and retrieve its port
@@ -435,16 +482,40 @@ void run_httpd(const void *t, int authmask)
        fprintf(stderr, "socket(PF_INET): %s\n", strerror(errno));
        exit(1);
     }
+    memset(&addr, 0, sizeof(addr));
     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);
     }
@@ -460,84 +531,112 @@ void run_httpd(const void *t, int authmask)
     if ((authmask & HTTPD_AUTH_MAGIC) &&
        (check_owning_uid(fd, 1) == getuid())) {
        authtype = HTTPD_AUTH_MAGIC;
-       printf("Using Linux /proc/net magic authentication\n");
+       if (authmask != HTTPD_AUTH_MAGIC)
+           printf("Using Linux /proc/net magic authentication\n");
     } else if ((authmask & HTTPD_AUTH_BASIC)) {
-       char username[128], password[128], userpass[259];
+       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;
 
-       sprintf(username, "agedu");
-       rname = "/dev/urandom";
-       fd = open(rname, O_RDONLY);
-       if (fd < 0) {
-           int err = errno;
-           rname = "/dev/random";
+       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 err2 = errno;
-               fprintf(stderr, "/dev/urandom: open: %s\n", strerror(err));
-               fprintf(stderr, "/dev/random: open: %s\n", strerror(err2));
-               exit(1);
+               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);
+           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;
            }
-           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';
+           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;
 
-       printf("Using HTTP Basic authentication\nUsername: %s\nPassword: %s\n",
-              username, password);
+               password[i] = chars[v];
+           }
+           password[i] = '\0';
 
-       k = sprintf(userpass, "%s:%s", username, password);
+           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, authbuf+j);
+           base64_encode_atom((unsigned char *)(userpass+i), s, authstring+j);
            i += s;
            j += 4;
        }
-       authbuf[j] = '\0';
-       authstring = authbuf;
-    } else {
+       authstring[j] = '\0';
+    } else if ((authmask & HTTPD_AUTH_NONE)) {
        authtype = HTTPD_AUTH_NONE;
-       printf("Web server is unauthenticated\n");
+       if (authmask != HTTPD_AUTH_NONE)
+           printf("Web server is unauthenticated\n");
+    } else {
+       fprintf(stderr, PNAME ": authentication method not supported\n");
+       exit(1);
     }
-    printf("URL: http://%s:%d/\n",
-          inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+    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));
+    }
+    fflush(stdout);
 
     /*
      * Now construct an fd structure to hold it.
      */
-    f = new_fdstruct(fd, FD_LISTENER);
-
-    /*
-     * Read from standard input, and treat EOF as a notification
-     * to exit.
-     */
-    new_fdstruct(0, FD_CLIENT);
+    new_fdstruct(fd, 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
@@ -545,7 +644,9 @@ void run_httpd(const void *t, int authmask)
      */
     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)
@@ -569,6 +670,8 @@ void run_httpd(const void *t, int authmask)
 
            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;
@@ -594,7 +697,9 @@ void run_httpd(const void *t, int authmask)
        }
        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));
@@ -666,7 +771,7 @@ void run_httpd(const void *t, int authmask)
                            fds[i].wdata = got_data
                                (fds[i].cctx, readbuf, ret,
                                 (authtype == HTTPD_AUTH_NONE ||
-                                 fds[i].magic_access), authstring);
+                                 fds[i].magic_access), authstring, &cfg);
                            if (fds[i].wdata) {
                                fds[i].wdatalen = strlen(fds[i].wdata);
                                fds[i].wdatapos = 0;