version 0.2.1
[fms.git] / libs / shttpd / shttpd.c
index 36372d2..a0f1bcc 100644 (file)
@@ -10,7 +10,7 @@
 
 /*
  * Small and portable HTTP server, http://shttpd.sourceforge.net
- * $Id: shttpd.c,v 1.10 2007/06/01 17:59:32 drozd Exp $
+ * $Id: shttpd.c,v 1.28 2008/02/17 21:45:09 drozd Exp $
  */
 
 #include "defs.h"
@@ -18,8 +18,6 @@
 time_t         current_time;   /* Current UTC time             */
 int            tz_offset;      /* Time zone offset from UTC    */
 
-static LL_HEAD(listeners);     /* List of listening sockets    */
-
 const struct vec known_http_methods[] = {
        {"GET",         3},
        {"POST",        4},
@@ -58,6 +56,7 @@ static const struct http_header http_headers[] = {
 };
 
 struct shttpd_ctx *init_ctx(const char *config_file, int argc, char *argv[]);
+static void process_connection(struct conn *, int, int);
 
 int
 url_decode(const char *src, int src_len, char *dst, int dst_len)
@@ -78,9 +77,6 @@ url_decode(const char *src, int src_len, char *dst, int dst_len)
                                dst[j] = '%';
                        }
                        break;
-               case '+':
-                       dst[j] = ' ';
-                       break;
                default:
                        dst[j] = src[i];
                        break;
@@ -91,37 +87,21 @@ url_decode(const char *src, int src_len, char *dst, int dst_len)
        return (j);
 }
 
-void
-shttpd_add_mime_type(struct shttpd_ctx *ctx, const char *ext, const char *mime)
-{
-       struct mime_type_link   *e;
-       const char              *error_msg = "shttpd_add_mime_type: no memory";
-
-       if ((e = malloc(sizeof(*e))) == NULL) {
-               elog(E_FATAL, 0, error_msg);
-       } else if ((e->ext= my_strdup(ext)) == NULL) {
-               elog(E_FATAL, 0, error_msg);
-       } else if ((e->mime = my_strdup(mime)) == NULL) {
-               elog(E_FATAL, 0, error_msg);
-       } else {
-               e->ext_len = strlen(ext);
-               LL_TAIL(&ctx->mime_types, &e->link);
-       }
-}
-
-
 static const char *
 is_alias(struct shttpd_ctx *ctx, const char *uri,
                struct vec *a_uri, struct vec *a_path)
 {
-       const char      *p, *s = ctx->aliases;
+       const char      *p, *s = ctx->options[OPT_ALIASES];
        size_t          len;
 
        DBG(("is_alias: aliases [%s]", s == NULL ? "" : s));
 
        FOR_EACH_WORD_IN_LIST(s, len) {
-               if ((p = memchr(s, '=', len)) != NULL &&
-                   memcmp(uri, s, p - s) == 0) {
+
+               if ((p = memchr(s, '=', len)) == NULL || p >= s + len || p == s)
+                       continue;
+
+               if (memcmp(uri, s, p - s) == 0) {
                        a_uri->ptr = s;
                        a_uri->len = p - s;
                        a_path->ptr = ++p;
@@ -202,7 +182,8 @@ get_headers_len(const char *buf, size_t buflen)
 
        for (s = buf, e = s + buflen - 1; len == 0 && s < e; s++)
                /* Control characters are not allowed but >=128 is. */
-               if (!isprint(*(unsigned char *)s) && *s != '\r' && *s != '\n' && *(unsigned char *)s < 128)
+               if (!isprint(* (unsigned char *) s) && *s != '\r' &&
+                   *s != '\n' && * (unsigned char *) s < 128)
                        len = -1;
                else if (s[0] == '\n' && s[1] == '\n')
                        len = s - buf + 2;
@@ -219,7 +200,6 @@ get_headers_len(const char *buf, size_t buflen)
 void
 send_server_error(struct conn *c, int status, const char *reason)
 {
-#ifdef EMBEDDED
        struct llhead           *lp;
        struct error_handler    *e;
 
@@ -231,16 +211,20 @@ send_server_error(struct conn *c, int status, const char *reason)
                            c->loc.io_class->close != NULL)
                                c->loc.io_class->close(&c->loc);
                        io_clear(&c->loc.io);
-                       setup_embedded_stream(c, e->callback, NULL);
+                       setup_embedded_stream(c, e->callback, e->callback_data);
                        return;
                }
        }
-#endif /* EMBEDDED */
 
        io_clear(&c->loc.io);
-       c->loc.headers_len = c->loc.io.head = my_snprintf(c->loc.io.buf,
-           c->loc.io.size, "HTTP/1.1 %d %s\r\n\r\n%d %s",
-           status, reason, status, reason);
+       c->loc.io.head = my_snprintf(c->loc.io.buf, c->loc.io.size,
+           "HTTP/1.1 %d %s\r\n"
+           "Content-Type: text/plain\r\n"
+           "Content-Length: 12\r\n"
+           "\r\n"
+           "Error: %03d\r\n",
+           status, reason, status);
+       c->loc.content_len = 10;
        c->status = status;
        stop_stream(&c->loc);
 }
@@ -313,8 +297,8 @@ remove_double_dots(char *s)
 
        while (*s != '\0') {
                *p++ = *s++;
-               if (s[-1] == '/')
-                       while (*s == '.' || *s == '/')
+               if (s[-1] == '/' || s[-1] == '\\')
+                       while (*s == '.' || *s == '/' || *s == '\\')
                                s++;
        }
        *p = '\0';
@@ -367,6 +351,85 @@ parse_headers(const char *s, int len, struct headers *parsed)
        }
 }
 
+static const struct {
+       const char      *extension;
+       int             ext_len;
+       const char      *mime_type;
+} builtin_mime_types[] = {
+       {"html",        4,      "text/html"                     },
+       {"htm",         3,      "text/html"                     },
+       {"txt",         3,      "text/plain"                    },
+       {"css",         3,      "text/css"                      },
+       {"ico",         3,      "image/x-icon"                  },
+       {"gif",         3,      "image/gif"                     },
+       {"jpg",         3,      "image/jpeg"                    },
+       {"jpeg",        4,      "image/jpeg"                    },
+       {"png",         3,      "image/png"                     },
+       {"svg",         3,      "image/svg+xml"                 },
+       {"torrent",     7,      "application/x-bittorrent"      },
+       {"wav",         3,      "audio/x-wav"                   },
+       {"mp3",         3,      "audio/x-mp3"                   },
+       {"mid",         3,      "audio/mid"                     },
+       {"m3u",         3,      "audio/x-mpegurl"               },
+       {"ram",         3,      "audio/x-pn-realaudio"          },
+       {"ra",          2,      "audio/x-pn-realaudio"          },
+       {"doc",         3,      "application/msword",           },
+       {"exe",         3,      "application/octet-stream"      },
+       {"zip",         3,      "application/x-zip-compressed"  },
+       {"xls",         3,      "application/excel"             },
+       {"tgz",         3,      "application/x-tar-gz"          },
+       {"tar.gz",      6,      "application/x-tar-gz"          },
+       {"tar",         3,      "application/x-tar"             },
+       {"gz",          2,      "application/x-gunzip"          },
+       {"arj",         3,      "application/x-arj-compressed"  },
+       {"rar",         3,      "application/x-arj-compressed"  },
+       {"rtf",         3,      "application/rtf"               },
+       {"pdf",         3,      "application/pdf"               },
+       {"swf",         3,      "application/x-shockwave-flash" },
+       {"mpg",         3,      "video/mpeg"                    },
+       {"mpeg",        4,      "video/mpeg"                    },
+       {"asf",         3,      "video/x-ms-asf"                },
+       {"avi",         3,      "video/x-msvideo"               },
+       {"bmp",         3,      "image/bmp"                     },
+       {NULL,          0,      NULL                            }
+};
+
+void
+get_mime_type(struct shttpd_ctx *ctx, const char *uri, int len, struct vec *vec)
+{
+       const char      *eq, *p = ctx->options[OPT_MIME_TYPES];
+       int             i, n, ext_len;
+
+       /* Firt, loop through the custom mime types if any */
+       FOR_EACH_WORD_IN_LIST(p, n) {
+               if ((eq = memchr(p, '=', n)) == NULL || eq >= p + n || eq == p)
+                       continue;
+               ext_len = eq - p;
+               if (len > ext_len && uri[len - ext_len - 1] == '.' &&
+                   !my_strncasecmp(p, &uri[len - ext_len], ext_len)) {
+                       vec->ptr = eq + 1;
+                       vec->len = p + n - vec->ptr;
+                       return;
+               }
+       }
+
+       /* If no luck, try built-in mime types */
+       for (i = 0; builtin_mime_types[i].extension != NULL; i++) {
+               ext_len = builtin_mime_types[i].ext_len;
+               if (len > ext_len && uri[len - ext_len - 1] == '.' &&
+                   !my_strncasecmp(builtin_mime_types[i].extension,
+                           &uri[len - ext_len], ext_len)) {
+                       vec->ptr = builtin_mime_types[i].mime_type;
+                       vec->len = strlen(vec->ptr);
+                       return;
+               }
+       }
+
+       /* Oops. This extension is unknown to us. Fallback to text/plain */
+       vec->ptr = "text/plain";
+       vec->len = strlen(vec->ptr);
+}
+
 /*
  * For given directory path, substitute it to valid index file.
  * Return 0 if index file has been found, -1 if not found
@@ -375,14 +438,14 @@ static int
 find_index_file(struct conn *c, char *path, size_t maxpath, struct stat *stp)
 {
        char            buf[FILENAME_MAX];
-       const char      *s = c->ctx->index_files;
+       const char      *s = c->ctx->options[OPT_INDEX_FILES];
        size_t          len;
 
        FOR_EACH_WORD_IN_LIST(s, len) {
                my_snprintf(buf, sizeof(buf), "%s%c%.*s",path, DIRSEP, len, s);
                if (my_stat(buf, stp) == 0) {
                        my_strlcpy(path, buf, maxpath);
-                       c->mime_type = get_mime_type(c->ctx, s, len);
+                       get_mime_type(c->ctx, s, len, &c->mime_type);
                        return (0);
                }
        }
@@ -404,7 +467,7 @@ get_path_info(struct conn *c, char *path, struct stat *stp)
                return (0);
 
        p = path + strlen(path);
-       e = path + strlen(c->ctx->document_root) + 2;
+       e = path + strlen(c->ctx->options[OPT_ROOT]) + 2;
        
        /* Strip directory parts of the path one by one */
        for (; p > e; p--)
@@ -425,13 +488,11 @@ get_path_info(struct conn *c, char *path, struct stat *stp)
 static void
 decide_what_to_do(struct conn *c)
 {
-       char            path[URI_MAX], buf[1024];
+       char            path[URI_MAX], buf[1024], *root;
        struct vec      alias_uri, alias_path;
        struct stat     st;
        int             rc;
-#ifdef EMBEDDED
        struct registered_uri   *ruri;
-#endif /* EMBEDDED */
 
        DBG(("decide_what_to_do: [%s]", c->uri));
 
@@ -441,13 +502,13 @@ decide_what_to_do(struct conn *c)
        url_decode(c->uri, strlen(c->uri), c->uri, strlen(c->uri) + 1);
        remove_double_dots(c->uri);
        
-       if (strlen(c->uri) + strlen(c->ctx->document_root) >= sizeof(path)) {
+       root = c->ctx->options[OPT_ROOT];
+       if (strlen(c->uri) + strlen(root) >= sizeof(path)) {
                send_server_error(c, 400, "URI is too long");
                return;
        }
 
-       (void) my_snprintf(path, sizeof(path), "%s%s",
-           c->ctx->document_root, c->uri);
+       (void) my_snprintf(path, sizeof(path), "%s%s", root, c->uri);
 
        /* User may use the aliases - check URI for mount point */
        if (is_alias(c->ctx, c->uri, &alias_uri, &alias_path) != NULL) {
@@ -462,18 +523,17 @@ decide_what_to_do(struct conn *c)
                send_authorization_request(c);
        } else
 #endif /* NO_AUTH */
-#ifdef EMBEDDED
        if ((ruri = is_registered_uri(c->ctx, c->uri)) != NULL) {
                setup_embedded_stream(c, ruri->callback, ruri->callback_data);
        } else
-#endif /* EMBEDDED */
        if (strstr(path, HTPASSWD)) {
                /* Do not allow to view passwords files */
                send_server_error(c, 403, "Forbidden");
        } else
 #if !defined(NO_AUTH)
        if ((c->method == METHOD_PUT || c->method == METHOD_DELETE) &&
-           (c->ctx->put_auth_file == NULL || !is_authorized_for_put(c))) {
+           (c->ctx->options[OPT_AUTH_PUT] == NULL ||
+            !is_authorized_for_put(c))) {
                send_authorization_request(c);
        } else
 #endif /* NO_AUTH */
@@ -510,17 +570,17 @@ decide_what_to_do(struct conn *c)
                send_server_error(c, 301, buf);
        } else if (S_ISDIR(st.st_mode) &&
            find_index_file(c, path, sizeof(path) - 1, &st) == -1 &&
-           c->ctx->dirlist == 0) {
+           !IS_TRUE(c->ctx, OPT_DIR_LIST)) {
                send_server_error(c, 403, "Directory Listing Denied");
-       } else if (S_ISDIR(st.st_mode) && c->ctx->dirlist) {
+       } else if (S_ISDIR(st.st_mode) && IS_TRUE(c->ctx, OPT_DIR_LIST)) {
                if ((c->loc.chan.dir.path = my_strdup(path)) != NULL)
                        get_dir(c);
                else
                        send_server_error(c, 500, "GET Directory Error");
-       } else if (S_ISDIR(st.st_mode) && c->ctx->dirlist == 0) {
+       } else if (S_ISDIR(st.st_mode) && !IS_TRUE(c->ctx, OPT_DIR_LIST)) {
                send_server_error(c, 403, "Directory listing denied");
 #if !defined(NO_CGI)
-       } else if (match_extension(path, c->ctx->cgi_extensions)) {
+       } else if (match_extension(path, c->ctx->options[OPT_CGI_EXTENSIONS])) {
                if (c->method != METHOD_POST && c->method != METHOD_GET) {
                        send_server_error(c, 501, "Bad method ");
                } else if ((run_cgi(c, path)) == -1) {
@@ -530,7 +590,7 @@ decide_what_to_do(struct conn *c)
                }
 #endif /* NO_CGI */
 #if !defined(NO_SSI)
-       } else if (match_extension(path, c->ctx->ssi_extensions)) {
+       } else if (match_extension(path, c->ctx->options[OPT_SSI_EXTENSIONS])) {
                if ((c->loc.chan.fd = my_open(path,
                    O_RDONLY | O_BINARY, 0644)) == -1) {
                        send_server_error(c, 500, "SSI open error");
@@ -553,8 +613,6 @@ set_request_method(struct conn *c)
 {
        const struct vec        *v;
 
-       assert(c->rem.io.head >= MIN_REQ_LEN);
-
        /* Set the request method */
        for (v = known_http_methods; v->ptr != NULL; v++)
                if (!memcmp(c->rem.io.buf, v->ptr, v->len)) {
@@ -569,23 +627,28 @@ static void
 parse_http_request(struct conn *c)
 {
        char    *s, *e, *p, *start;
-       char    *end_number;
-       int     uri_len, req_len;
+       int     uri_len, req_len, n;
 
-       s = c->rem.io.buf;
-       req_len = c->rem.headers_len = get_headers_len(s, c->rem.io.head);
+       s = io_data(&c->rem.io);;
+       req_len = c->rem.headers_len =
+           get_headers_len(s, io_data_len(&c->rem.io));
 
-       if (req_len == 0 && io_space_len(&c->rem.io) == 0)
+       if (req_len == 0 && io_space_len(&c->rem.io) == 0) {
+               io_clear(&c->rem.io);
                send_server_error(c, 400, "Request is too big");
+       }
+
+       io_inc_tail(&c->rem.io, req_len);
 
-       if (req_len == 0)
+       if (req_len == 0) {
                return;
-       else if (req_len < MIN_REQ_LEN)
+       } else if (req_len < 16) {      /* Minimal: "GET / HTTP/1.0\n\n" */
                send_server_error(c, 400, "Bad request");
-       else if (set_request_method(c))
+       } else if (set_request_method(c)) {
                send_server_error(c, 501, "Method Not Implemented");
-       else if ((c->request = my_strndup(s, req_len)) == NULL)
+       } else if ((c->request = my_strndup(s, req_len)) == NULL) {
                send_server_error(c, 500, "Cannot allocate request");
+       }
 
        if (c->loc.flags & FLAG_CLOSED)
                return;
@@ -607,41 +670,25 @@ parse_http_request(struct conn *c)
         * First, we skip the REQUEST_METHOD and shift to the URI.
         */
        for (p = c->request, e = p + req_len; *p != ' ' && p < e; p++);
-       while (p < e && *p == ' ') p++;
+       while (p < e && *p == ' ')
+               p++;
 
        /* Now remember where URI starts, and shift to the end of URI */
        for (start = p; p < e && !isspace((unsigned char)*p); ) p++;
        uri_len = p - start;
+
        /* Skip space following the URI */
-       while (p < e && *p == ' ') p++;
+       while (p < e && *p == ' ')
+               p++;
 
        /* Now comes the HTTP-Version in the form HTTP/<major>.<minor> */
-       if (strncmp(p, "HTTP/", 5) != 0) {
+       if (sscanf(p, "HTTP/%lu.%lu%n",
+           &c->major_version, &c->minor_version, &n) != 2 || p[n] != '\0') {
                send_server_error(c, 400, "Bad HTTP version");
-               return;
-       }
-       p += 5;
-       /* Parse the HTTP major version number */
-       c->major_version = strtoul(p, &end_number, 10);
-       if (end_number == p || *end_number != '.') {
-               send_server_error(c, 400, "Bad HTTP major version");
-               return;
-       }
-       p = end_number + 1;
-       /* Parse the minor version number */
-       c->minor_version = strtoul(p, &end_number, 10);
-       if (end_number == p || *end_number != '\0') {
-               send_server_error(c, 400, "Bad HTTP minor version");
-               return;
-       }
-       /* Version must be <=1.1 */
-       if (c->major_version > 1 ||
+       } else if (c->major_version > 1 ||
            (c->major_version == 1 && c->minor_version > 1)) {
                send_server_error(c, 505, "HTTP version not supported");
-               return;
-       }
-
-       if (uri_len <= 0) {
+       } else if (uri_len <= 0) {
                send_server_error(c, 400, "Bad URI");
        } else if ((c->uri = malloc(uri_len + 1)) == NULL) {
                send_server_error(c, 500, "Cannot allocate URI");
@@ -653,20 +700,17 @@ parse_http_request(struct conn *c)
                /* Remove the length of request from total, count only data */
                assert(c->rem.io.total >= (big_int_t) req_len);
                c->rem.io.total -= req_len;
-
                c->rem.content_len = c->ch.cl.v_big_int;
-               io_inc_tail(&c->rem.io, req_len);
-
                decide_what_to_do(c);
        }
 }
 
 void
-shttpd_add_socket(struct shttpd_ctx *ctx, int sock)
+shttpd_add_socket(struct shttpd_ctx *ctx, int sock, int is_ssl)
 {
        struct conn     *c;
        struct usa      sa;
-       int             l = ctx->inetd_mode ? E_FATAL : E_LOG;
+       int             l = IS_TRUE(ctx, OPT_INETD) ? E_FATAL : E_LOG;
 #if !defined(NO_SSL)
        SSL             *ssl = NULL;
 #endif /* NO_SSL */
@@ -677,16 +721,15 @@ shttpd_add_socket(struct shttpd_ctx *ctx, int sock)
        if (getpeername(sock, &sa.u.sa, &sa.len)) {
                elog(l, NULL, "add_socket: %s", strerror(errno));
 #if !defined(NO_SSL)
-       } else if (ctx->ssl_ctx && (ssl = SSL_new(ctx->ssl_ctx)) == NULL) {
+       } else if (is_ssl && (ssl = SSL_new(ctx->ssl_ctx)) == NULL) {
                elog(l, NULL, "add_socket: SSL_new: %s", strerror(ERRNO));
                (void) closesocket(sock);
-       } else if (ctx->ssl_ctx && SSL_set_fd(ssl, sock) == 0) {
+       } else if (is_ssl && SSL_set_fd(ssl, sock) == 0) {
                elog(l, NULL, "add_socket: SSL_set_fd: %s", strerror(ERRNO));
                (void) closesocket(sock);
                SSL_free(ssl);
 #endif /* NO_SSL */
-       } else if ((c = calloc(1, sizeof(*c) + 2 * ctx->io_buf_size)) == NULL) {
-
+       } else if ((c = calloc(1, sizeof(*c) + 2 * URI_MAX)) == NULL) {
 #if !defined(NO_SSL)
                if (ssl)
                        SSL_free(ssl);
@@ -713,11 +756,11 @@ shttpd_add_socket(struct shttpd_ctx *ctx, int sock)
 
                /* Set IO buffers */
                c->loc.io.buf   = (char *) (c + 1);
-               c->rem.io.buf   = c->loc.io.buf + ctx->io_buf_size;
-               c->loc.io.size  = c->rem.io.size = ctx->io_buf_size;
+               c->rem.io.buf   = c->loc.io.buf + URI_MAX;
+               c->loc.io.size  = c->rem.io.size = URI_MAX;
 
 #if !defined(NO_SSL)
-               if (ssl) {
+               if (is_ssl) {
                        c->rem.io_class = &io_ssl;
                        c->rem.chan.ssl.sock = sock;
                        c->rem.chan.ssl.ssl = ssl;
@@ -764,7 +807,7 @@ shttpd_listen(struct shttpd_ctx *ctx, int port, int is_ssl)
                l->is_ssl = is_ssl;
                l->sock = sock;
                l->ctx  = ctx;
-               LL_TAIL(&listeners, &l->link);
+               LL_TAIL(&ctx->listeners, &l->link);
                DBG(("shttpd_listen: added socket %d", sock));
        }
 
@@ -860,21 +903,20 @@ write_stream(struct stream *from, struct stream *to)
 
 
 static void
-disconnect(struct conn *c)
+disconnect(struct llhead *lp)
 {
-       static const struct vec ka = {"keep-alive", 10};
-       int                     dont_close;
+       struct conn             *c = LL_ENTRY(lp, struct conn, link);
+       static const struct vec vec = {"close", 5};
+       int                     do_close;
 
        DBG(("Disconnecting %d (%.*s)", c->rem.chan.sock,
            c->ch.connection.v_vec.len, c->ch.connection.v_vec.ptr));
 
-#if !defined(_WIN32) || defined(NO_GUI)
-       if (c->ctx->access_log != NULL)
-#endif /* _WIN32 */
-                       log_access(c->ctx->access_log, c);
+       if (c->request != NULL && c->ctx->access_log != NULL)
+               log_access(c->ctx->access_log, c);
 
        /* In inetd mode, exit if request is finished. */
-       if (c->ctx->inetd_mode)
+       if (IS_TRUE(c->ctx, OPT_INETD))
                exit(0);
 
        if (c->loc.io_class != NULL && c->loc.io_class->close != NULL)
@@ -884,25 +926,29 @@ disconnect(struct conn *c)
         * Check the "Connection: " header before we free c->request
         * If it its 'keep-alive', then do not close the connection
         */
-       dont_close =  c->ch.connection.v_vec.len >= ka.len &&
-           !my_strncasecmp(ka.ptr, c->ch.connection.v_vec.ptr, ka.len);
+       do_close = (c->ch.connection.v_vec.len >= vec.len &&
+           !my_strncasecmp(vec.ptr, c->ch.connection.v_vec.ptr, vec.len)) ||
+           (c->major_version < 1 ||
+           (c->major_version >= 1 && c->minor_version < 1));
 
        if (c->request)
                free(c->request);
        if (c->uri)
                free(c->uri);
 
-       /* Handle Keep-Alive */
-       dont_close = 0;
-       if (dont_close) {
+       /* Keep the connection open only if we have Content-Length set */
+       if (!do_close && c->loc.content_len > 0) {
                c->loc.io_class = NULL;
-               c->loc.flags = c->rem.flags = 0;
+               c->loc.flags = 0;
+               c->loc.content_len = 0;
+               c->rem.flags = FLAG_W | FLAG_R;
                c->query = c->request = c->uri = c->path_info = NULL;
-               c->mime_type = NULL;
+               c->mime_type.len = 0;
                (void) memset(&c->ch, 0, sizeof(c->ch));
-               io_clear(&c->rem.io);
                io_clear(&c->loc.io);
-               c->rem.io.total = c->loc.io.total = 0;
+               c->birth_time = current_time;
+               if (io_data_len(&c->rem.io) > 0)
+                       process_connection(c, 0, 0);
        } else {
                if (c->rem.io_class != NULL)
                        c->rem.io_class->close(&c->rem);
@@ -917,6 +963,24 @@ disconnect(struct conn *c)
        }
 }
 
+static int
+is_allowed(const struct shttpd_ctx *ctx, const struct usa *usa)
+{
+       const struct acl        *acl;
+       const struct llhead     *lp;
+       int                     allowed = '+';
+       uint32_t                ip;
+
+       LL_FOREACH(&ctx->acl, lp) {
+               acl = LL_ENTRY(lp, struct acl, link);
+               (void) memcpy(&ip, &usa->u.sin.sin_addr, sizeof(ip));
+               if (acl->ip == (ntohl(ip) & acl->mask))
+                       allowed = acl->flag;
+       }
+
+       return (allowed == '+');
+}
+
 static void
 add_to_set(int fd, fd_set *set, int *max_fd)
 {
@@ -925,6 +989,45 @@ add_to_set(int fd, fd_set *set, int *max_fd)
                *max_fd = fd;
 }
 
+static void
+process_connection(struct conn *c, int remote_ready, int local_ready)
+{
+       /* Read from remote end if it is ready */
+       if (remote_ready && io_space_len(&c->rem.io))
+               read_stream(&c->rem);
+
+       /* If the request is not parsed yet, do so */
+       if (!(c->rem.flags & FLAG_HEADERS_PARSED))
+               parse_http_request(c);
+
+       DBG(("loc: %u [%.*s]", io_data_len(&c->loc.io),
+           io_data_len(&c->loc.io), io_data(&c->loc.io)));
+       DBG(("rem: %u [%.*s]", io_data_len(&c->rem.io),
+           io_data_len(&c->rem.io), io_data(&c->rem.io)));
+
+       /* Read from the local end if it is ready */
+       if (local_ready && io_space_len(&c->loc.io))
+               read_stream(&c->loc);
+
+       if (io_data_len(&c->rem.io) > 0 && (c->loc.flags & FLAG_W) &&
+           c->loc.io_class != NULL && c->loc.io_class->write != NULL)
+               write_stream(&c->rem, &c->loc);
+
+       if (io_data_len(&c->loc.io) > 0 && c->rem.io_class != NULL)
+               write_stream(&c->loc, &c->rem); 
+
+       if (c->rem.nread_last > 0)
+               c->ctx->in += c->rem.nread_last;
+       if (c->loc.nread_last > 0)
+               c->ctx->out += c->loc.nread_last;
+
+       /* Check whether we should close this connection */
+       if ((current_time > c->expire_time) ||
+           (c->rem.flags & FLAG_CLOSED) ||
+           ((c->loc.flags & FLAG_CLOSED) && !io_data_len(&c->loc.io)))
+               disconnect(&c->link);
+}
+
 /*
  * One iteration of server loop. This is the core of the data exchange.
  */
@@ -944,7 +1047,7 @@ shttpd_poll(struct shttpd_ctx *ctx, int milliseconds)
        FD_ZERO(&write_set);
 
        /* Add listening sockets to the read set */
-       LL_FOREACH(&listeners, lp) {
+       LL_FOREACH(&ctx->listeners, lp) {
                l = LL_ENTRY(lp, struct listener, link);
                FD_SET(l->sock, &read_set);
                if (l->sock > max_fd)
@@ -995,7 +1098,7 @@ shttpd_poll(struct shttpd_ctx *ctx, int milliseconds)
        }
 
        tv.tv_sec = msec / 1000;
-       tv.tv_usec = msec % 1000;
+       tv.tv_usec = (msec % 1000) * 1000;
 
        /* Check IO readiness */
        if (select(max_fd + 1, &read_set, &write_set, NULL, &tv) < 0) {
@@ -1013,7 +1116,7 @@ shttpd_poll(struct shttpd_ctx *ctx, int milliseconds)
        }
 
        /* Check for incoming connections on listener sockets */
-       LL_FOREACH(&listeners, lp) {
+       LL_FOREACH(&ctx->listeners, lp) {
                l = LL_ENTRY(lp, struct listener, link);
                if (!FD_ISSET(l->sock, &read_set))
                        continue;
@@ -1021,15 +1124,27 @@ shttpd_poll(struct shttpd_ctx *ctx, int milliseconds)
                        sa.len = sizeof(sa.u.sin);
                        if ((sock = accept(l->sock, &sa.u.sa, &sa.len)) != -1) {
 #if defined(_WIN32)
-                               shttpd_add_socket(ctx, sock);
-#else
-                               if (sock < (int) FD_SETSIZE) {
-                                       shttpd_add_socket(ctx, sock);
+                               if (!is_allowed(ctx, &sa)) {
+                                       elog(E_LOG, NULL, "shttpd_poll: %s "
+                                           "is not allowed to connect",
+                                          inet_ntoa(sa.u.sin.sin_addr));
+                                       (void) closesocket(sock);
                                } else {
+                                       shttpd_add_socket(ctx, sock, l->is_ssl);
+                               }
+#else
+                               if (sock >= (int) FD_SETSIZE) {
                                        elog(E_LOG, NULL,
                                           "shttpd_poll: ctx %p: disarding "
                                           "socket %d, too busy", ctx, sock);
                                        (void) closesocket(sock);
+                               } else if (!is_allowed(ctx, &sa)) {
+                                       elog(E_LOG, NULL, "shttpd_poll: %s "
+                                           "is not allowed to connect",
+                                          inet_ntoa(sa.u.sin.sin_addr));
+                                       (void) closesocket(sock);
+                               } else {
+                                       shttpd_add_socket(ctx, sock, l->is_ssl);
                                }
 #endif /* _WIN32 */
                        }
@@ -1039,127 +1154,74 @@ shttpd_poll(struct shttpd_ctx *ctx, int milliseconds)
        /* Process all connections */
        LL_FOREACH_SAFE(&ctx->connections, lp, tmp) {
                c = LL_ENTRY(lp, struct conn, link);
-
-               /* Read from remote end if it is ready */
-               if (FD_ISSET(c->rem.chan.fd, &read_set) &&
-                   io_space_len(&c->rem.io))
-                       read_stream(&c->rem);
-
-               /* If the request is not parsed yet, do so */
-               if (!(c->rem.flags & FLAG_HEADERS_PARSED))
-                       parse_http_request(c);
-
-               DBG(("loc: %u [%.*s]", io_data_len(&c->loc.io),
-                   io_data_len(&c->loc.io), io_data(&c->loc.io)));
-               DBG(("rem: %u [%.*s]", io_data_len(&c->rem.io),
-                   io_data_len(&c->rem.io), io_data(&c->rem.io)));
-
-               /* Read from the local end if it is ready */
-               if (io_space_len(&c->loc.io) &&
+               process_connection(c, FD_ISSET(c->rem.chan.fd, &read_set),
                    ((c->loc.flags & FLAG_ALWAYS_READY)
-                   
 #if !defined(NO_CGI)
-                   ||(c->loc.io_class == &io_cgi &&
+                   || (c->loc.io_class == &io_cgi &&
                     FD_ISSET(c->loc.chan.fd, &read_set))
 #endif /* NO_CGI */
-                   ))
-                       read_stream(&c->loc);
-
-               if (io_data_len(&c->rem.io) > 0 && (c->loc.flags & FLAG_W) &&
-                   c->loc.io_class != NULL && c->loc.io_class->write != NULL)
-                       write_stream(&c->rem, &c->loc);
-
-               if (io_data_len(&c->loc.io) > 0 && c->rem.io_class != NULL)
-                       write_stream(&c->loc, &c->rem); 
-
-               if (c->rem.nread_last > 0)
-                       c->ctx->in += c->rem.nread_last;
-               if (c->loc.nread_last > 0)
-                       c->ctx->out += c->loc.nread_last;
-
-               /* Check whether we should close this connection */
-               if ((current_time > c->expire_time) ||
-                   (c->rem.flags & FLAG_CLOSED) ||
-                   ((c->loc.flags & FLAG_CLOSED) && !io_data_len(&c->loc.io)))
-                       disconnect(c);
+                   ));
        }
 }
 
-/*
- * Deallocate shttpd object, free up the resources
- */
 void
-shttpd_fini(struct shttpd_ctx *ctx)
+free_list(struct llhead *head, void (*dtor)(struct llhead *))
 {
-       struct llhead           *lp, *tmp;
-       struct mime_type_link   *mtl;
-       struct conn             *c;
-       struct listener         *l;
-       struct registered_uri   *ruri;
+       struct llhead   *lp, *tmp;
 
-       /* Free configured mime types */
-       LL_FOREACH_SAFE(&ctx->mime_types, lp, tmp) {
-               mtl = LL_ENTRY(lp, struct mime_type_link, link);
-               free(mtl->mime);
-               free(mtl->ext);
-               free(mtl);
+       LL_FOREACH_SAFE(head, lp, tmp) {
+               LL_DEL(lp);
+               dtor(lp);
        }
+}
 
-       /* Free all connections */
-       LL_FOREACH_SAFE(&ctx->connections, lp, tmp) {
-               c = LL_ENTRY(lp, struct conn, link);
-               disconnect(c);
-       }
+void
+listener_destructor(struct llhead *lp)
+{
+       struct listener *listener = LL_ENTRY(lp, struct listener, link);
 
-       /* Free registered URIs (must be done after disconnect()) */
-       LL_FOREACH_SAFE(&ctx->registered_uris, lp, tmp) {
-               ruri = LL_ENTRY(lp, struct registered_uri, link);
-               free((void *)ruri->uri);
-               free(ruri);
-       }
+       (void) closesocket(listener->sock);
+       free(listener);
+}
 
-       /* Free listener sockets for this context */
-       LL_FOREACH_SAFE(&listeners, lp, tmp) {
-               l = LL_ENTRY(lp, struct listener, link);
-               (void) closesocket(l->sock);
-               LL_DEL(&l->link);
-               free(l);
-       }
+void
+registered_uri_destructor(struct llhead *lp)
+{
+       struct registered_uri *ruri = LL_ENTRY(lp, struct registered_uri, link);
 
-#if !defined(NO_SSI)
-       free_ssi_funcs(ctx);
-#endif /* NO_SSI */
+       free((void *) ruri->uri);
+       free(ruri);
+}
+
+static void
+acl_destructor(struct llhead *lp)
+{
+       struct acl      *acl = LL_ENTRY(lp, struct acl, link);
+       free(acl);
+}
+
+/*
+ * Deallocate shttpd object, free up the resources
+ */
+void
+shttpd_fini(struct shttpd_ctx *ctx)
+{
+       size_t  i;
+
+       free_list(&ctx->connections, disconnect);
+       free_list(&ctx->registered_uris, registered_uri_destructor);
+       free_list(&ctx->acl, acl_destructor);
+       free_list(&ctx->listeners, listener_destructor);
+       free_list(&ctx->ssi_funcs, ssi_func_destructor);
+
+       for (i = 0; i < NELEMS(ctx->options); i++)
+               if (ctx->options[i] != NULL)
+                       free(ctx->options[i]);
 
        if (ctx->access_log)            (void) fclose(ctx->access_log);
        if (ctx->error_log)             (void) fclose(ctx->error_log);
-       if (ctx->put_auth_file)         free(ctx->put_auth_file);
-       if (ctx->document_root)         free(ctx->document_root);
-       if (ctx->index_files)           free(ctx->index_files);
-       if (ctx->aliases)               free(ctx->aliases);
-#if !defined(NO_CGI)
-       if (ctx->cgi_vars)              free(ctx->cgi_vars);
-       if (ctx->cgi_extensions)        free(ctx->cgi_extensions);
-       if (ctx->cgi_interpreter)       free(ctx->cgi_interpreter);
-#endif /* NO_CGI */
-       if (ctx->auth_realm)            free(ctx->auth_realm);
-       if (ctx->global_passwd_file)    free(ctx->global_passwd_file);
-       if (ctx->uid)                   free(ctx->uid);
 
        /* TODO: free SSL context */
 
        free(ctx);
 }
-
-void
-open_listening_ports(struct shttpd_ctx *ctx)
-{
-       const char      *p = ctx->ports;
-       int             len, is_ssl;
-       
-       FOR_EACH_WORD_IN_LIST(p, len) {
-               is_ssl = p[len - 1] == 's' ? 1 : 0;
-               if (shttpd_listen(ctx, atoi(p), is_ssl) == -1)
-                       elog(E_FATAL, NULL,
-                           "Cannot open socket on port %d", atoi(p));
-       }
-}