| /* |
| * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com> |
| * All rights reserved |
| * |
| * "THE BEER-WARE LICENSE" (Revision 42): |
| * Sergey Lyubka wrote this file. As long as you retain this notice you |
| * can do whatever you want with this stuff. If we meet some day, and you think |
| * this stuff is worth it, you can buy me a beer in return. |
| */ |
| |
| /* |
| * Small and portable HTTP server, http://shttpd.sourceforge.net |
| * $Id: shttpd.c,v 1.18 2008/01/10 11:01:21 drozd Exp $ |
| */ |
| |
| #include "shttpd_defs.h" |
| |
| /* from src/server/wsmand-daemon.h */ |
| extern int wsmand_options_get_use_ipv4(void); |
| #ifdef ENABLE_IPV6 |
| extern int wsmand_options_get_use_ipv6(void); |
| extern void wsmand_options_disable_use_ipv6(void); |
| #endif |
| |
| 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}, |
| /* {"PUT", 3}, |
| {"DELETE", 6}, |
| {"HEAD", 4}, */ |
| {NULL, 0} |
| }; |
| |
| struct listener { |
| struct llhead link; |
| struct shttpd_ctx *ctx; /* Context that socket belongs */ |
| int sock; /* Listening socket */ |
| int is_ssl; /* Should be SSL-ed */ |
| }; |
| |
| /* |
| * This structure tells how HTTP headers must be parsed. |
| * Used by parse_headers() function. |
| */ |
| #define OFFSET(x) offsetof(struct headers, x) |
| static const struct http_header http_headers[] = { |
| {16, HDR_INT, OFFSET(cl), "Content-Length: " }, |
| {14, HDR_STRING, OFFSET(ct), "Content-Type: " }, |
| {12, HDR_STRING, OFFSET(useragent), "User-Agent: " }, |
| {19, HDR_DATE, OFFSET(ims), "If-Modified-Since: " }, |
| {15, HDR_STRING, OFFSET(auth), "Authorization: " }, |
| {9, HDR_STRING, OFFSET(referer), "Referer: " }, |
| {8, HDR_STRING, OFFSET(cookie), "Cookie: " }, |
| {10, HDR_STRING, OFFSET(location), "Location: " }, |
| {8, HDR_INT, OFFSET(status), "Status: " }, |
| {7, HDR_STRING, OFFSET(range), "Range: " }, |
| {12, HDR_STRING, OFFSET(connection), "Connection: " }, |
| {19, HDR_STRING, OFFSET(transenc), "Transfer-Encoding: " }, |
| {0, HDR_INT, 0, NULL } |
| }; |
| |
| 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) |
| { |
| int i, j, a, b; |
| #define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') |
| |
| for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) |
| switch (src[i]) { |
| case '%': |
| if (isxdigit(((unsigned char *) src)[i + 1]) && |
| isxdigit(((unsigned char *) src)[i + 2])) { |
| a = tolower(((unsigned char *)src)[i + 1]); |
| b = tolower(((unsigned char *)src)[i + 2]); |
| dst[j] = (HEXTOI(a) << 4) | HEXTOI(b); |
| i += 2; |
| } else { |
| dst[j] = '%'; |
| } |
| break; |
| default: |
| dst[j] = src[i]; |
| break; |
| } |
| |
| dst[j] = '\0'; /* Null-terminate the destination */ |
| |
| 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= strdup(ext)) == NULL) { |
| elog(E_FATAL, 0, error_msg); |
| } else if ((e->mime = 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; |
| 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) { |
| a_uri->ptr = s; |
| a_uri->len = p - s; |
| a_path->ptr = ++p; |
| a_path->len = (s + len) - p; |
| return (s); |
| } |
| } |
| |
| return (NULL); |
| } |
| |
| void |
| stop_stream(struct stream *stream) |
| { |
| if (stream->io_class != NULL && stream->io_class->close != NULL) |
| stream->io_class->close(stream); |
| |
| stream->io_class= NULL; |
| stream->flags |= FLAG_CLOSED; |
| stream->flags &= ~(FLAG_R | FLAG_W | FLAG_ALWAYS_READY); |
| |
| DBG(("%d %s stopped. %lu of content data, %d now in a buffer", |
| stream->conn->rem.chan.sock, |
| stream->io_class ? stream->io_class->name : "(null)", |
| (unsigned long) stream->io.total, io_data_len(&stream->io))); |
| } |
| |
| /* |
| * Setup listening socket on given port, return socket |
| */ |
| static int |
| open_listening_port(int port) |
| { |
| int sock = -1, on = 1; |
| struct usa sa; |
| |
| #ifdef _WIN32 |
| {WSADATA data; WSAStartup(MAKEWORD(2,2), &data);} |
| #endif /* _WIN32 */ |
| |
| |
| |
| #ifdef ENABLE_IPV6 |
| sa.len = sizeof(sa.u.sin6); |
| memset(&sa.u.sin6, 0, sa.len); |
| sa.u.sin6.sin6_family = AF_INET6; |
| sa.u.sin6.sin6_addr = in6addr_any; |
| sa.u.sin6.sin6_port = htons((uint16_t) port); |
| sa.u.sin6.sin6_flowinfo = 0; |
| sa.u.sin6.sin6_scope_id = 0; |
| |
| if (!wsmand_options_get_use_ipv6() |
| || (sock = socket(AF_INET6, SOCK_STREAM, 6)) == -1) { |
| wsmand_options_disable_use_ipv6(); |
| if (wsmand_options_get_use_ipv4()) { |
| #endif |
| sa.len = sizeof(sa.u.sin); |
| memset(&sa.u.sin, 0, sa.len); |
| sa.u.sin.sin_family = AF_INET; |
| sa.u.sin.sin_addr.s_addr = htonl(INADDR_ANY); |
| sa.u.sin.sin_port = htons((uint16_t) port); |
| |
| if ((sock = socket(PF_INET, SOCK_STREAM, 6)) == -1) |
| goto fail; |
| #ifdef ENABLE_IPV6 |
| } |
| else |
| goto fail; |
| } |
| #endif |
| |
| if (set_non_blocking_mode(sock) != 0) |
| goto fail; |
| if (setsockopt(sock, SOL_SOCKET, |
| SO_REUSEADDR,(char *) &on, sizeof(on)) != 0) |
| goto fail; |
| if (bind(sock, &sa.u.sa, sa.len) < 0) |
| goto fail; |
| if (listen(sock, 128) != 0) |
| goto fail; |
| |
| #ifndef _WIN32 |
| (void) fcntl(sock, F_SETFD, FD_CLOEXEC); |
| #endif /* !_WIN32 */ |
| |
| return (sock); |
| fail: |
| if (sock != -1) |
| (void) closesocket(sock); |
| elog(E_LOG, NULL, "open_listening_port(%d): %s", port, strerror(errno)); |
| return (-1); |
| } |
| |
| /* |
| * Check whether full request is buffered Return headers length, or 0 |
| */ |
| int |
| get_headers_len(const char *buf, size_t buflen) |
| { |
| const char *s, *e; |
| int len = 0; |
| |
| 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) |
| len = -1; |
| else if (s[0] == '\n' && s[1] == '\n') |
| len = s - buf + 2; |
| else if (s[0] == '\n' && &s[1] < e && |
| s[1] == '\r' && s[2] == '\n') |
| len = s - buf + 3; |
| |
| return (len); |
| } |
| |
| /* |
| * Send error message back to a client. |
| */ |
| void |
| send_server_error(struct conn *c, int status, const char *reason) |
| { |
| #ifdef EMBEDDED |
| struct llhead *lp; |
| struct error_handler *e; |
| |
| LL_FOREACH(&c->ctx->error_handlers, lp) { |
| e = LL_ENTRY(lp, struct error_handler, link); |
| |
| if (e->code == status) { |
| if (c->loc.io_class != NULL && |
| c->loc.io_class->close != NULL) |
| c->loc.io_class->close(&c->loc); |
| io_clear(&c->loc.io); |
| 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 = snprintf(c->loc.io.buf, |
| c->loc.io.size, "HTTP/1.1 %d %s\r\nConnection: Close\r\n\r\n%d %s", |
| status, reason, status, reason); |
| c->status = status; |
| stop_stream(&c->loc); |
| } |
| |
| /* |
| * Convert month to the month number. Return -1 on error, or month number |
| */ |
| static int |
| montoi(const char *s) |
| { |
| static const char *ar[] = { |
| "Jan", "Feb", "Mar", "Apr", "May", "Jun", |
| "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" |
| }; |
| size_t i; |
| |
| for (i = 0; i < sizeof(ar) / sizeof(ar[0]); i++) |
| if (!strcmp(s, ar[i])) |
| return (i); |
| |
| return (-1); |
| } |
| |
| /* |
| * Parse date-time string, and return the corresponding time_t value |
| */ |
| static time_t |
| date_to_epoch(const char *s) |
| { |
| struct tm tm, *tmp; |
| char mon[32]; |
| int sec, min, hour, mday, month, year; |
| |
| (void) memset(&tm, 0, sizeof(tm)); |
| sec = min = hour = mday = month = year = 0; |
| |
| if (((sscanf(s, "%d/%3s/%d %d:%d:%d", |
| &mday, mon, &year, &hour, &min, &sec) == 6) || |
| (sscanf(s, "%d %3s %d %d:%d:%d", |
| &mday, mon, &year, &hour, &min, &sec) == 6) || |
| (sscanf(s, "%*3s, %d %3s %d %d:%d:%d", |
| &mday, mon, &year, &hour, &min, &sec) == 6) || |
| (sscanf(s, "%d-%3s-%d %d:%d:%d", |
| &mday, mon, &year, &hour, &min, &sec) == 6)) && |
| (month = montoi(mon)) != -1) { |
| tm.tm_mday = mday; |
| tm.tm_mon = month; |
| tm.tm_year = year; |
| tm.tm_hour = hour; |
| tm.tm_min = min; |
| tm.tm_sec = sec; |
| } |
| |
| if (tm.tm_year > 1900) |
| tm.tm_year -= 1900; |
| else if (tm.tm_year < 70) |
| tm.tm_year += 100; |
| |
| /* Set Daylight Saving Time field */ |
| tmp = localtime(¤t_time); |
| tm.tm_isdst = tmp->tm_isdst; |
| |
| return (mktime(&tm)); |
| } |
| |
| static void |
| remove_double_dots(char *s) |
| { |
| char *p = s; |
| |
| while (*s != '\0') { |
| *p++ = *s++; |
| if (s[-1] == '/' || s[-1] == '\\') |
| while (*s == '.' || *s == '/' || *s == '\\') |
| s++; |
| } |
| *p = '\0'; |
| } |
| |
| void |
| parse_headers(const char *s, int len, struct headers *parsed) |
| { |
| const struct http_header *h; |
| union variant *v; |
| const char *p, *e = s + len; |
| |
| DBG(("parsing headers (len %d): [%.*s]", len, len, s)); |
| |
| /* Loop through all headers in the request */ |
| while (s < e) { |
| |
| /* Find where this header ends */ |
| for (p = s; p < e && *p != '\n'; ) p++; |
| |
| /* Is this header known to us ? */ |
| for (h = http_headers; h->len != 0; h++) |
| if (e - s > h->len && |
| !strncasecmp(s, h->name, h->len)) |
| break; |
| |
| /* If the header is known to us, store its value */ |
| if (h->len != 0) { |
| |
| /* Shift to where value starts */ |
| s += h->len; |
| |
| /* Find place to store the value */ |
| v = (union variant *) ((char *) parsed + h->offset); |
| |
| /* Fetch header value into the connection structure */ |
| if (h->type == HDR_STRING) { |
| v->v_vec.ptr = s; |
| v->v_vec.len = p - s; |
| if (p[-1] == '\r' && v->v_vec.len > 0) |
| v->v_vec.len--; |
| } else if (h->type == HDR_INT) { |
| v->v_big_int = strtoul(s, NULL, 10); |
| } else if (h->type == HDR_DATE) { |
| v->v_time = date_to_epoch(s); |
| } |
| } |
| |
| s = p + 1; /* Shift to the next header */ |
| } |
| } |
| |
| /* |
| * For given directory path, substitute it to valid index file. |
| * Return 0 if index file has been found, -1 if not found |
| */ |
| 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; |
| int len; |
| |
| FOR_EACH_WORD_IN_LIST(s, len) { |
| 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); |
| return (0); |
| } |
| } |
| |
| return (-1); |
| } |
| |
| /* |
| * Try to open requested file, return 0 if OK, -1 if error. |
| * If the file is given arguments using PATH_INFO mechanism, |
| * initialize pathinfo pointer. |
| */ |
| static int |
| get_path_info(struct conn *c, char *path, struct stat *stp) |
| { |
| char *p, *e; |
| |
| if (my_stat(path, stp) == 0) |
| return (0); |
| |
| p = path + strlen(path); |
| e = path + strlen(c->ctx->document_root) + 2; |
| |
| /* Strip directory parts of the path one by one */ |
| for (; p > e; p--) |
| if (*p == '/') { |
| *p = '\0'; |
| if (!my_stat(path, stp) && !S_ISDIR(stp->st_mode)) { |
| c->path_info = p + 1; |
| return (0); |
| } else { |
| *p = '/'; |
| } |
| } |
| |
| return (-1); |
| } |
| |
| |
| static void |
| decide_what_to_do(struct conn *c) |
| { |
| char path[URI_MAX], buf[1024]; |
| 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)); |
| |
| if ((c->query = strchr(c->uri, '?')) != NULL) |
| *c->query++ = '\0'; |
| |
| 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)) { |
| send_server_error(c, 400, "URI is too long"); |
| return; |
| } |
| |
| (void) snprintf(path, sizeof(path), "%s%s", |
| c->ctx->document_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) { |
| (void) snprintf(path, sizeof(path), "%.*s%s", |
| alias_path.len, alias_path.ptr, c->uri + alias_uri.len); |
| DBG(("using alias %.*s -> %.*s", alias_uri.len, alias_uri.ptr, |
| alias_path.len, alias_path.ptr)); |
| } |
| |
| #if !defined(NO_AUTH) |
| rc = check_authorization(c, path); |
| if(rc != 1) |
| { |
| if(rc != 2) /* 2 = multipass auth (GSS)*/ |
| 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))) { |
| send_authorization_request(c); |
| } else |
| #endif /* NO_AUTH */ |
| if (c->method == METHOD_PUT) { |
| c->status = my_stat(path, &st) == 0 ? 200 : 201; |
| |
| if (c->ch.range.v_vec.len > 0) { |
| send_server_error(c, 501, "PUT Range Not Implemented"); |
| } else if ((rc = put_dir(path)) == 0) { |
| send_server_error(c, 200, "OK"); |
| } else if (rc == -1) { |
| send_server_error(c, 500, "PUT Directory Error"); |
| } else if (c->rem.content_len == 0) { |
| send_server_error(c, 411, "Length Required"); |
| } else if ((c->loc.chan.fd = my_open(path, O_WRONLY | O_BINARY | |
| O_CREAT | O_NONBLOCK | O_TRUNC, 0644)) == -1) { |
| send_server_error(c, 500, "PUT Error"); |
| } else { |
| DBG(("PUT file [%s]", c->uri)); |
| c->loc.io_class = &io_file; |
| c->loc.flags |= FLAG_W | FLAG_ALWAYS_READY ; |
| } |
| } else if (c->method == METHOD_DELETE) { |
| DBG(("DELETE [%s]", c->uri)); |
| if (my_remove(path) == 0) |
| send_server_error(c, 200, "OK"); |
| else |
| send_server_error(c, 500, "DELETE Error"); |
| } else if (get_path_info(c, path, &st) != 0) { |
| send_server_error(c, 404, "Not Found"); |
| } else if (S_ISDIR(st.st_mode) && path[strlen(path) - 1] != '/') { |
| (void) snprintf(buf, sizeof(buf), |
| "Moved Permanently\r\nLocation: %s/", c->uri); |
| 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) { |
| send_server_error(c, 403, "Directory Listing Denied"); |
| } else if (S_ISDIR(st.st_mode) && c->ctx->dirlist) { |
| if ((c->loc.chan.dir.path = 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) { |
| send_server_error(c, 403, "Directory listing denied"); |
| #if !defined(NO_CGI) |
| } else if (match_extension(path, c->ctx->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) { |
| send_server_error(c, 500, "Cannot exec CGI"); |
| } else { |
| do_cgi(c); |
| } |
| #endif /* NO_CGI */ |
| #if !defined(NO_SSI) |
| } else if (match_extension(path, c->ctx->ssi_extensions)) { |
| if ((c->loc.chan.fd = my_open(path, |
| O_RDONLY | O_BINARY, 0644)) == -1) { |
| send_server_error(c, 500, "SSI open error"); |
| } else { |
| do_ssi(c); |
| } |
| #endif /* NO_CGI */ |
| } else if (c->ch.ims.v_time && st.st_mtime <= c->ch.ims.v_time) { |
| send_server_error(c, 304, "Not Modified"); |
| } else if ((c->loc.chan.fd = my_open(path, |
| O_RDONLY | O_BINARY, 0644)) != -1) { |
| get_file(c, &st); |
| } else { |
| send_server_error(c, 500, "Internal Error"); |
| } |
| } |
| |
| static int |
| 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)) { |
| c->method = v - known_http_methods; |
| break; |
| } |
| |
| return (v->ptr == NULL); |
| } |
| |
| static void |
| parse_http_request(struct conn *c) |
| { |
| char *s, *e, *p, *start; |
| char *end_number; |
| int uri_len, req_len; |
| |
| 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) |
| send_server_error(c, 400, "Request is too big"); |
| |
| if (req_len == 0) |
| return; |
| else if (req_len < MIN_REQ_LEN) |
| send_server_error(c, 400, "Bad request"); |
| else if (set_request_method(c)) |
| send_server_error(c, 501, "Method Not Implemented"); |
| else if ((c->request = u_strndup(s, req_len)) == NULL) |
| send_server_error(c, 500, "Cannot allocate request"); |
| |
| if (c->loc.flags & FLAG_CLOSED) |
| return; |
| |
| DBG(("Conn %d: parsing request: [%.*s]", c->rem.chan.sock, req_len, s)); |
| c->rem.flags |= FLAG_HEADERS_PARSED; |
| |
| /* Set headers pointer. Headers follow the request line */ |
| c->headers = memchr(c->request, '\n', req_len); |
| assert(c->headers != NULL); |
| assert(c->headers < c->request + req_len); |
| if (c->headers > c->request && c->headers[-1] == '\r') |
| c->headers[-1] = '\0'; |
| *c->headers++ = '\0'; |
| |
| /* |
| * Now make a copy of the URI, because it will be URL-decoded, |
| * and we need a copy of unmodified URI for the access log. |
| * 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++; |
| |
| /* 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++; |
| |
| /* Now comes the HTTP-Version in the form HTTP/<major>.<minor> */ |
| if (strncmp(p, "HTTP/", 5) != 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 || |
| (c->major_version == 1 && c->minor_version > 1)) { |
| send_server_error(c, 505, "HTTP version not supported"); |
| return; |
| } |
| |
| 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"); |
| } else { |
| my_strlcpy(c->uri, (char *) start, uri_len + 1); |
| parse_headers(c->headers, |
| (c->request + req_len) - c->headers, &c->ch); |
| |
| /* 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, int is_ssl) |
| { |
| struct conn *c; |
| struct usa sa; |
| int l = ctx->inetd_mode ? E_FATAL : E_LOG; |
| #if !defined(NO_SSL) |
| SSL *ssl = NULL; |
| #endif /* NO_SSL */ |
| |
| sa.len = sizeof(sa.u.sin); |
| (void) set_non_blocking_mode(sock); |
| |
| if (getpeername(sock, &sa.u.sa, &sa.len)) { |
| elog(l, NULL, "add_socket: %s", strerror(errno)); |
| #if !defined(NO_SSL) |
| } 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 (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) { |
| #if !defined(NO_SSL) |
| if (ssl) |
| SSL_free(ssl); |
| #endif /* NO_SSL */ |
| (void) closesocket(sock); |
| elog(l, NULL, "add_socket: calloc: %s", strerror(ERRNO)); |
| } else { |
| ctx->nrequests++; |
| c->rem.conn = c->loc.conn = c; |
| c->ctx = ctx; |
| c->sa = sa; |
| c->birth_time = current_time; |
| c->expire_time = current_time + EXPIRE_TIME; |
| |
| (void) getsockname(sock, &sa.u.sa, &sa.len); |
| #ifdef ENABLE_IPV6 |
| if (wsmand_options_get_use_ipv6()) { |
| c->loc_port = sa.u.sin6.sin6_port; |
| } |
| else { |
| #endif |
| c->loc_port = sa.u.sin.sin_port; |
| #ifdef ENABLE_IPV6 |
| } |
| #endif |
| |
| set_close_on_exec(sock); |
| |
| c->loc.io_class = NULL; |
| |
| c->rem.io_class = &io_socket; |
| c->rem.chan.sock = 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; |
| #ifdef SHTTPD_GSS |
| c->gss_ctx = GSS_C_NO_CONTEXT; |
| #endif |
| #if !defined(NO_SSL) |
| if (is_ssl) { |
| c->rem.io_class = &io_ssl; |
| c->rem.chan.ssl.sock = sock; |
| c->rem.chan.ssl.ssl = ssl; |
| ssl_handshake(&c->rem); |
| } |
| #endif /* NO_SSL */ |
| |
| EnterCriticalSection(&ctx->mutex); |
| LL_TAIL(&ctx->connections, &c->link); |
| ctx->nactive++; |
| LeaveCriticalSection(&ctx->mutex); |
| #ifdef ENABLE_IPV6 |
| if (wsmand_options_get_use_ipv6()) { |
| char str[INET6_ADDRSTRLEN]; |
| inet_ntop( AF_INET6,&sa.u.sin6.sin6_addr, str, sizeof(str)); |
| DBG(("%s:%hu connected IPv6 (socket %d)", str , ntohs(sa.u.sin6.sin6_port), sock)); |
| } |
| else { |
| #endif |
| DBG(("%s:%hu connected IPv4 (socket %d)", |
| inet_ntoa(* (struct in_addr *) &sa.u.sin.sin_addr.s_addr), |
| ntohs(sa.u.sin.sin_port), sock)); |
| #ifdef ENABLE_IPV6 |
| } |
| #endif |
| } |
| } |
| |
| int |
| shttpd_active(struct shttpd_ctx *ctx) |
| { |
| return (ctx->nactive); |
| } |
| |
| /* |
| * Setup a listening socket on given port. Return opened socket or -1 |
| */ |
| int |
| shttpd_listen(struct shttpd_ctx *ctx, int port, int is_ssl) |
| { |
| struct listener *l; |
| int sock; |
| |
| if ((sock = open_listening_port(port)) == -1) { |
| elog(E_FATAL, NULL, "cannot open port %d", port); |
| } else if ((l = calloc(1, sizeof(*l))) == NULL) { |
| (void) closesocket(sock); |
| elog(E_FATAL, NULL, "cannot allocate listener"); |
| } else if (is_ssl && ctx->ssl_ctx == NULL) { |
| (void) closesocket(sock); |
| elog(E_FATAL, NULL, "cannot add SSL socket, " |
| "please specify certificate file"); |
| } else { |
| l->is_ssl = is_ssl; |
| l->sock = sock; |
| l->ctx = ctx; |
| LL_TAIL(&listeners, &l->link); |
| DBG(("shttpd_listen: added socket %d", sock)); |
| } |
| |
| return (sock); |
| } |
| |
| int |
| shttpd_accept(int lsn_sock, int milliseconds) |
| { |
| struct timeval tv; |
| struct usa sa; |
| fd_set read_set; |
| int sock = -1; |
| |
| tv.tv_sec = milliseconds / 1000; |
| tv.tv_usec = milliseconds % 1000; |
| sa.len = sizeof(sa.u.sin); |
| FD_ZERO(&read_set); |
| FD_SET(lsn_sock, &read_set); |
| |
| if (select(lsn_sock + 1, &read_set, NULL, NULL, &tv) == 1) |
| sock = accept(lsn_sock, &sa.u.sa, &sa.len); |
| |
| return (sock); |
| } |
| |
| static void |
| read_stream(struct stream *stream) |
| { |
| int n, len; |
| int sslerr = 0; |
| len = io_space_len(&stream->io); |
| assert(len > 0); |
| /* Do not read more that needed */ |
| if (stream->content_len > 0 && |
| stream->io.total + len > stream->content_len) |
| len = stream->content_len - stream->io.total; |
| |
| /* Read from underlying channel */ |
| n = stream->nread_last = stream->io_class->read(stream, |
| io_space(&stream->io), len); |
| if (n > 0) { |
| io_inc_head(&stream->io, n); |
| stream->flags &= ~FLAG_SSL_SHOULD_SELECT_ON_WRITE; |
| } |
| else if (n == -1) { |
| if(stream->chan.ssl.ssl) { |
| sslerr = SSL_get_error(stream->chan.ssl.ssl, n); |
| } |
| /* Ignore SSL_ERROR_WANT_READ and SSL_ERROR_WANT_WRITE*/ |
| if((stream->chan.ssl.ssl && sslerr == SSL_ERROR_SYSCALL && |
| (ERRNO == EINTR || ERRNO == EWOULDBLOCK)) || |
| (ERRNO == EINTR || ERRNO == EWOULDBLOCK)) { |
| n = n; /* Ignore EINTR and EAGAIN */ |
| } |
| else if(sslerr == SSL_ERROR_WANT_READ) { |
| n = n; |
| } |
| else if(sslerr == SSL_ERROR_WANT_WRITE) { |
| stream->flags |= FLAG_SSL_SHOULD_SELECT_ON_WRITE; |
| } |
| else if (!(stream->flags & FLAG_DONT_CLOSE)) |
| stop_stream(stream); |
| |
| } |
| else if (!(stream->flags & FLAG_DONT_CLOSE)) |
| stop_stream(stream); |
| #if 0 |
| DBG(("read_stream (%d %s): read %d/%d/%lu bytes (errno %d)", |
| stream->conn->rem.chan.sock, |
| stream->io_class ? stream->io_class->name : "(null)", |
| n, len, (unsigned long) stream->io.total, ERRNO)); |
| #endif |
| |
| /* |
| * Close the local stream if everything was read |
| * XXX We do not close the remote stream though! It may be |
| * a POST data completed transfer, we do not want the socket |
| * to be closed. |
| */ |
| if (stream->content_len > 0 && stream == &stream->conn->loc) { |
| assert(stream->io.total <= stream->content_len); |
| if (stream->io.total == stream->content_len) |
| stop_stream(stream); |
| } |
| |
| |
| stream->conn->expire_time = current_time + EXPIRE_TIME; |
| } |
| |
| static void |
| write_stream(struct stream *from, struct stream *to) |
| { |
| int n, len; |
| int sslerr = 0; |
| len = io_data_len(&from->io); |
| assert(len > 0); |
| |
| /* TODO: should be assert on CAN_WRITE flag */ |
| n = to->io_class->write(to, io_data(&from->io), len); |
| to->conn->expire_time = current_time + EXPIRE_TIME; |
| /* |
| DBG(("write_stream (%d %s): written %d/%d bytes (errno %d)", |
| to->conn->rem.chan.sock, |
| to->io_class ? to->io_class->name : "(null)", n, len, ERRNO)); |
| */ |
| |
| if (n > 0) { |
| io_inc_tail(&from->io, n); |
| to->flags &= ~FLAG_SSL_SHOULD_SELECT_ON_READ; |
| } |
| else if (n == -1) { |
| if(to->chan.ssl.ssl) { |
| sslerr = SSL_get_error(to->chan.ssl.ssl, n); |
| } |
| /* Ignore SSL_ERROR_WANT_READ and SSL_ERROR_WANT_WRITE*/ |
| if((to->chan.ssl.ssl && sslerr == SSL_ERROR_SYSCALL && |
| (ERRNO == EINTR || ERRNO == EWOULDBLOCK)) || |
| (ERRNO == EINTR || ERRNO == EWOULDBLOCK)) { |
| n = n; /* Ignore EINTR and EAGAIN */ |
| } |
| else if(sslerr == SSL_ERROR_WANT_WRITE) { |
| n = n; |
| } |
| else if(sslerr == SSL_ERROR_WANT_READ) { |
| to->flags |= FLAG_SSL_SHOULD_SELECT_ON_READ; |
| } |
| |
| else if (!(to->flags & FLAG_DONT_CLOSE)) |
| stop_stream(to); |
| } |
| else if (!(to->flags & FLAG_DONT_CLOSE)) |
| stop_stream(to); |
| } |
| |
| |
| |
| static void |
| disconnect(struct llhead *lp) |
| { |
| struct conn *c = LL_ENTRY(lp, struct conn, link); |
| static const struct vec ka = {"keep-alive", 10}; |
| int dont_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); |
| |
| /* In inetd mode, exit if request is finished. */ |
| if (c->ctx->inetd_mode) |
| exit(0); |
| |
| if (c->loc.io_class != NULL && c->loc.io_class->close != NULL) |
| c->loc.io_class->close(&c->loc); |
| |
| /* |
| * 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 && |
| !strncasecmp(ka.ptr, c->ch.connection.v_vec.ptr, ka.len); |
| dont_close = 0; |
| if (c->request) |
| free(c->request); |
| if (c->uri) |
| free(c->uri); |
| |
| /* Handle Keep-Alive */ |
| if (dont_close) { |
| c->loc.io_class = NULL; |
| c->loc.flags = 0; |
| c->rem.flags = FLAG_W | FLAG_R; |
| c->query = c->request = c->uri = c->path_info = NULL; |
| c->mime_type = NULL; |
| (void) memset(&c->ch, 0, sizeof(c->ch)); |
| io_clear(&c->loc.io); |
| 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); |
| |
| EnterCriticalSection(&c->ctx->mutex); |
| LL_DEL(&c->link); |
| c->ctx->nactive--; |
| assert(c->ctx->nactive >= 0); |
| LeaveCriticalSection(&c->ctx->mutex); |
| #ifdef SHTTPD_GSS |
| { |
| OM_uint32 majStat, minStat; |
| majStat = gss_delete_sec_context(&minStat, c->gss_ctx, 0); |
| } |
| #endif |
| |
| free(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); |
| #ifdef ENABLE_IPV6 |
| if (wsmand_options_get_use_ipv6()) |
| return allowed; |
| #endif |
| (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) |
| { |
| FD_SET(fd, set); |
| if (fd > *max_fd) |
| *max_fd = fd; |
| } |
| |
| static void |
| process_connection(struct conn *c, int remote_ready, int local_ready) |
| { |
| #if 0 |
| 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))); |
| DBG(("locf=%x,remf=%x", c->loc.flags,c->rem.flags)); |
| #endif |
| |
| /* Read from remote end if it is ready */ |
| if(c->loc.flags & FLAG_RESPONSE_COMPLETE) |
| c->rem.flags &= ~ FLAG_HEADERS_PARSED; |
| 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); |
| /* 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. |
| */ |
| void |
| shttpd_poll(struct shttpd_ctx *ctx, int milliseconds) |
| { |
| struct llhead *lp, *tmp; |
| struct listener *l; |
| struct conn *c = NULL; |
| struct timeval tv; /* Timeout for select() */ |
| fd_set read_set, write_set; |
| int sock, max_fd = -1, msec = milliseconds; |
| struct usa sa; |
| |
| current_time = time(0); |
| FD_ZERO(&read_set); |
| FD_ZERO(&write_set); |
| |
| /* Add listening sockets to the read set */ |
| LL_FOREACH(&listeners, lp) { |
| l = LL_ENTRY(lp, struct listener, link); |
| FD_SET(l->sock, &read_set); |
| if (l->sock > max_fd) |
| max_fd = l->sock; |
| //DBG(("FD_SET(%d) (listening)", l->sock)); |
| } |
| |
| /* Multiplex streams */ |
| LL_FOREACH(&ctx->connections, lp) { |
| c = LL_ENTRY(lp, struct conn, link); |
| |
| /* If there is a space in remote IO, check remote socket */ |
| if (c->rem.flags & FLAG_SSL_SHOULD_SELECT_ON_READ) |
| add_to_set(c->rem.chan.fd, &read_set, &max_fd); |
| else if (io_space_len(&c->rem.io) && c->rem.flags & |
| FLAG_SSL_SHOULD_SELECT_ON_WRITE) |
| add_to_set(c->rem.chan.fd, &write_set, &max_fd); |
| else if (io_space_len(&c->rem.io)) |
| add_to_set(c->rem.chan.fd, &read_set, &max_fd); |
| |
| #if !defined(NO_CGI) |
| /* |
| * If there is a space in local IO, and local endpoint is |
| * CGI, check local socket for read availability |
| */ |
| if (io_space_len(&c->loc.io) && (c->loc.flags & FLAG_R) && |
| c->loc.io_class == &io_cgi) |
| add_to_set(c->loc.chan.fd, &read_set, &max_fd); |
| |
| /* |
| * If there is some data read from remote socket, and |
| * local endpoint is CGI, check local for write availability |
| */ |
| if (io_data_len(&c->rem.io) && (c->loc.flags & FLAG_W) && |
| c->loc.io_class == &io_cgi) |
| add_to_set(c->loc.chan.fd, &write_set, &max_fd); |
| #endif /* NO_CGI */ |
| |
| /* |
| * If there is some data read from local endpoint, check the |
| * remote socket for write availability |
| */ |
| if (io_data_len(&c->loc.io) && (c->rem.flags & |
| FLAG_SSL_SHOULD_SELECT_ON_READ)) |
| add_to_set(c->rem.chan.fd, &read_set, &max_fd); |
| else if (io_data_len(&c->loc.io)) |
| add_to_set(c->rem.chan.fd, &write_set, &max_fd); |
| |
| if (io_space_len(&c->loc.io) && (c->loc.flags & FLAG_R) && |
| (c->loc.flags & FLAG_ALWAYS_READY)) |
| msec = 0; |
| |
| if (io_data_len(&c->rem.io) && (c->loc.flags & FLAG_W) && |
| (c->loc.flags & FLAG_ALWAYS_READY)) |
| msec = 0; |
| } |
| |
| tv.tv_sec = msec / 1000; |
| tv.tv_usec = msec % 1000; |
| |
| /* Check IO readiness */ |
| if (select(max_fd + 1, &read_set, &write_set, NULL, &tv) < 0) { |
| #ifdef _WIN32 |
| /* |
| * On windows, if read_set and write_set are empty, |
| * select() returns "Invalid parameter" error |
| * (at least on my Windows XP Pro). So in this case, |
| * we sleep here. |
| */ |
| Sleep(milliseconds); |
| #endif /* _WIN32 */ |
| DBG(("select: %d", ERRNO)); |
| if(c->rem.chan.ssl.ssl == NULL) |
| return; |
| else if(!SSL_pending(c->rem.chan.ssl.ssl)) |
| return; |
| } |
| |
| /* Check for incoming connections on listener sockets */ |
| LL_FOREACH(&listeners, lp) { |
| l = LL_ENTRY(lp, struct listener, link); |
| if (!FD_ISSET(l->sock, &read_set)) |
| continue; |
| do { |
| 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, 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 */ |
| } |
| } while (sock != -1); |
| } |
| |
| /* Process all connections */ |
| LL_FOREACH_SAFE(&ctx->connections, lp, tmp) { |
| c = LL_ENTRY(lp, struct conn, link); |
| #ifndef NO_SSL |
| process_connection(c, ((FD_ISSET(c->rem.chan.fd, &read_set) && |
| !(c->rem.flags & FLAG_SSL_SHOULD_SELECT_ON_READ)) || |
| (c->rem.chan.ssl.ssl && SSL_pending(c->rem.chan.ssl.ssl)) || |
| (FD_ISSET(c->rem.chan.fd, &write_set) && |
| (c->rem.flags & FLAG_SSL_SHOULD_SELECT_ON_WRITE))), |
| ((c->loc.flags & FLAG_ALWAYS_READY) |
| |
| #else |
| |
| process_connection(c, FD_ISSET(c->rem.chan.fd, &read_set), |
| ((c->loc.flags & FLAG_ALWAYS_READY) |
| #endif |
| #if !defined(NO_CGI) |
| || (c->loc.io_class == &io_cgi && |
| FD_ISSET(c->loc.chan.fd, &read_set)) |
| #endif /* NO_CGI */ |
| )); |
| } |
| } |
| |
| static void |
| free_list(struct llhead *head, void (*dtor)(struct llhead *)) |
| { |
| struct llhead *lp, *tmp; |
| |
| LL_FOREACH_SAFE(head, lp, tmp) { |
| LL_DEL(lp); |
| dtor(lp); |
| } |
| } |
| |
| static void |
| listener_desctructor(struct llhead *lp) |
| { |
| struct listener *listener = LL_ENTRY(lp, struct listener, link); |
| |
| (void) closesocket(listener->sock); |
| free(listener); |
| } |
| |
| static void |
| mime_type_destructor(struct llhead *lp) |
| { |
| struct mime_type_link *mtl = LL_ENTRY(lp, struct mime_type_link, link); |
| |
| free(mtl->mime); |
| free(mtl->ext); |
| free(mtl); |
| } |
| |
| static void |
| registered_uri_destructor(struct llhead *lp) |
| { |
| struct registered_uri *ruri = LL_ENTRY(lp, struct registered_uri, link); |
| |
| free((void *) ruri->uri); |
| free(ruri); |
| } |
| |
| static void |
| acl_destructor(struct llhead *lp) |
| { |
| struct acl *acl = LL_ENTRY(lp, struct acl, link); |
| free(acl); |
| } |
| |
| static void |
| protected_uri_destructor(struct llhead *lp) |
| { |
| struct uri_auth *auth = LL_ENTRY(lp, struct uri_auth, link); |
| |
| free((void *) auth->file_name); |
| free((void *) auth->uri); |
| free(auth); |
| } |
| |
| /* |
| * Deallocate shttpd object, free up the resources |
| */ |
| void |
| shttpd_fini(struct shttpd_ctx *ctx) |
| { |
| free_list(&ctx->mime_types, mime_type_destructor); |
| free_list(&ctx->connections, disconnect); |
| free_list(&ctx->registered_uris, registered_uri_destructor); |
| free_list(&ctx->uri_auths, protected_uri_destructor); |
| free_list(&ctx->acl, acl_destructor); |
| free_list(&listeners, listener_desctructor); |
| |
| #if !defined(NO_SSI) |
| if (ctx->ssi_extensions) free(ctx->ssi_extensions); |
| free_list(&ctx->ssi_funcs, ssi_func_destructor); |
| #endif /* NO_SSI */ |
| |
| 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); |
| if (ctx->mime_file) free(ctx->mime_file); |
| if (ctx->ports) free(ctx->ports); |
| |
| /* TODO: free SSL context */ |
| if(ctx->ssl_ctx) |
| SSL_CTX_free(ctx->ssl_ctx); |
| 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)); |
| } |
| } |