X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=libs%2Fshttpd%2Fshttpd.c;fp=libs%2Fshttpd%2Fshttpd.c;h=a0f1bccf537126020db885cc5a84ee4f745f442e;hb=1dee4e3cd008a27789bbce05b0eb47b0eb5d121a;hp=36372d22da59190b4d25b501edacf81f988de718;hpb=3dc3ac3cfe10b7196a7977e9c041c29fa141c35e;p=fms.git diff --git a/libs/shttpd/shttpd.c b/libs/shttpd/shttpd.c index 36372d2..a0f1bcc 100644 --- a/libs/shttpd/shttpd.c +++ b/libs/shttpd/shttpd.c @@ -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/. */ - 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)); - } -}