/* * Copyright (c) 2004-2005 Sergey Lyubka * 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.57 2008/08/23 21:00:38 drozd Exp $ */ #include "defs.h" time_t _shttpd_current_time; /* Current UTC time */ int _shttpd_tz_offset; /* Time zone offset from UTC */ int _shttpd_exit_flag; /* Program exit flag */ const struct vec _shttpd_known_http_methods[] = { {"GET", 3}, {"POST", 4}, {"PUT", 3}, {"DELETE", 6}, {"HEAD", 4}, {NULL, 0} }; /* * 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 _shttpd_is_true(const char *str) { static const char *trues[] = {"1", "yes", "true", "jawohl", NULL}; const char **p; for (p = trues; *p != NULL; p++) if (str && !strcmp(str, *p)) return (TRUE); return (FALSE); } 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_destructor(struct llhead *lp) { struct listener *listener = LL_ENTRY(lp, struct listener, link); (void) closesocket(listener->sock); free(listener); } 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); } int _shttpd_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); } 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->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 || 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; a_path->len = (s + len) - p; return (s); } } return (NULL); } void _shttpd_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, (int) io_data_len(&stream->io))); } /* * Setup listening socket on given port, return socket */ static int shttpd_open_listening_port(int port) { int sock, on = 1; struct usa sa; #ifdef _WIN32 {WSADATA data; WSAStartup(MAKEWORD(2,2), &data);} #endif /* _WIN32 */ sa.len = sizeof(sa.u.sin); sa.u.sin.sin_family = AF_INET; sa.u.sin.sin_port = htons((uint16_t) port); sa.u.sin.sin_addr.s_addr = htonl(INADDR_ANY); if ((sock = socket(PF_INET, SOCK_STREAM, 6)) == -1) goto fail; if (_shttpd_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); _shttpd_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 _shttpd_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 _shttpd_send_server_error(struct conn *c, int status, const char *reason) { 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); _shttpd_setup_embedded_stream(c, e->callback, e->callback_data); return; } } io_clear(&c->loc.io); c->loc.io.head = _shttpd_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; _shttpd_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(&_shttpd_current_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 _shttpd_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 && !_shttpd_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 */ } } 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 _shttpd_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] == '.' && !_shttpd_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] == '.' && !_shttpd_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 */ 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->options[OPT_INDEX_FILES]; size_t len; FOR_EACH_WORD_IN_LIST(s, len) { /* path must end with '/' character */ _shttpd_snprintf(buf, sizeof(buf), "%s%.*s", path, len, s); if (_shttpd_stat(buf, stp) == 0) { _shttpd_strlcpy(path, buf, maxpath); _shttpd_get_mime_type(c->ctx, s, len, &c->mime_type); 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 (_shttpd_stat(path, stp) == 0) return (0); p = path + strlen(path); e = path + strlen(c->ctx->options[OPT_ROOT]) + 2; /* Strip directory parts of the path one by one */ for (; p > e; p--) if (*p == '/') { *p = '\0'; if (!_shttpd_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; const char *root; struct vec alias_uri, alias_path; struct stat st; int rc; struct registered_uri *ruri; DBG(("decide_what_to_do: [%s]", c->uri)); if ((c->query = strchr(c->uri, '?')) != NULL) *c->query++ = '\0'; _shttpd_url_decode(c->uri, strlen(c->uri), c->uri, strlen(c->uri) + 1); remove_double_dots(c->uri); root = c->ctx->options[OPT_ROOT]; if (strlen(c->uri) + strlen(root) >= URI_MAX) { _shttpd_send_server_error(c, 400, "URI is too long"); return; } path = malloc( URI_MAX ); (void) _shttpd_snprintf(path, URI_MAX, "%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) { (void) _shttpd_snprintf(path, URI_MAX, "%.*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) if (_shttpd_check_authorization(c, path) != 1) { _shttpd_send_authorization_request(c); } else #endif /* NO_AUTH */ if ((ruri = _shttpd_is_registered_uri(c->ctx, c->uri)) != NULL) { _shttpd_setup_embedded_stream(c, ruri->callback, ruri->callback_data); } else if (strstr(path, HTPASSWD)) { /* Do not allow to view passwords files */ _shttpd_send_server_error(c, 403, "Forbidden"); } else #if !defined(NO_AUTH) if ((c->method == METHOD_PUT || c->method == METHOD_DELETE) && (c->ctx->options[OPT_AUTH_PUT] == NULL || !_shttpd_is_authorized_for_put(c))) { _shttpd_send_authorization_request(c); } else #endif /* NO_AUTH */ if (c->method == METHOD_PUT) { c->status = _shttpd_stat(path, &st) == 0 ? 200 : 201; if (c->ch.range.v_vec.len > 0) { _shttpd_send_server_error(c, 501, "PUT Range Not Implemented"); } else if ((rc = _shttpd_put_dir(path)) == 0) { _shttpd_send_server_error(c, 200, "OK"); } else if (rc == -1) { _shttpd_send_server_error(c, 500, "PUT Directory Error"); } else if (c->rem.content_len == 0) { _shttpd_send_server_error(c, 411, "Length Required"); } else if ((c->loc.chan.fd = _shttpd_open(path, O_WRONLY | O_BINARY | O_CREAT | O_NONBLOCK | O_TRUNC, 0644)) == -1) { _shttpd_send_server_error(c, 500, "PUT Error"); } else { DBG(("PUT file [%s]", c->uri)); c->loc.io_class = &_shttpd_io_file; c->loc.flags |= FLAG_W | FLAG_ALWAYS_READY ; } } else if (c->method == METHOD_DELETE) { DBG(("DELETE [%s]", c->uri)); if (_shttpd_remove(path) == 0) _shttpd_send_server_error(c, 200, "OK"); else _shttpd_send_server_error(c, 500, "DELETE Error"); } else if (get_path_info(c, path, &st) != 0) { _shttpd_send_server_error(c, 404, "Not Found"); } else if (S_ISDIR(st.st_mode) && path[strlen(path) - 1] != '/') { char buf[1024]; (void) _shttpd_snprintf(buf, sizeof(buf), "Moved Permanently\r\nLocation: %s/", c->uri); _shttpd_send_server_error(c, 301, buf); } else if (S_ISDIR(st.st_mode) && find_index_file(c, path, URI_MAX - 1, &st) == -1 && !IS_TRUE(c->ctx, OPT_DIR_LIST)) { _shttpd_send_server_error(c, 403, "Directory Listing Denied"); } else if (S_ISDIR(st.st_mode) && IS_TRUE(c->ctx, OPT_DIR_LIST)) { if ((c->loc.chan.dir.path = _shttpd_strdup(path)) != NULL) _shttpd_get_dir(c); else _shttpd_send_server_error(c, 500, "GET Directory Error"); } else if (S_ISDIR(st.st_mode) && !IS_TRUE(c->ctx, OPT_DIR_LIST)) { _shttpd_send_server_error(c, 403, "Directory listing denied"); #if !defined(NO_CGI) } else if (_shttpd_match_extension(path, c->ctx->options[OPT_CGI_EXTENSIONS])) { if (c->method != METHOD_POST && c->method != METHOD_GET) { _shttpd_send_server_error(c, 501, "Bad method "); } else if ((_shttpd_run_cgi(c, path)) == -1) { _shttpd_send_server_error(c, 500, "Cannot exec CGI"); } else { _shttpd_do_cgi(c); } #endif /* NO_CGI */ #if !defined(NO_SSI) } else if (_shttpd_match_extension(path, c->ctx->options[OPT_SSI_EXTENSIONS])) { if ((c->loc.chan.fd = _shttpd_open(path, O_RDONLY | O_BINARY, 0644)) == -1) { _shttpd_send_server_error(c, 500, "SSI open error"); } else { _shttpd_do_ssi(c); } #endif /* NO_CGI */ } else if (c->ch.ims.v_time && st.st_mtime <= c->ch.ims.v_time) { _shttpd_send_server_error(c, 304, "Not Modified"); } else if ((c->loc.chan.fd = _shttpd_open(path, O_RDONLY | O_BINARY, 0644)) != -1) { _shttpd_get_file(c, &st); } else { _shttpd_send_server_error(c, 500, "Internal Error"); } free(path); } static int set_request_method(struct conn *c) { const struct vec *v; /* Set the request method */ for (v = _shttpd_known_http_methods; v->ptr != NULL; v++) if (!memcmp(c->rem.io.buf, v->ptr, v->len)) { c->method = v - _shttpd_known_http_methods; break; } return (v->ptr == NULL); } static void parse_http_request(struct conn *c) { char *s, *e, *p, *start; int uri_len, req_len, n; s = io_data(&c->rem.io);; req_len = c->rem.headers_len = _shttpd_get_headers_len(s, io_data_len(&c->rem.io)); if (req_len == 0 && io_space_len(&c->rem.io) == 0) { io_clear(&c->rem.io); _shttpd_send_server_error(c, 400, "Request is too big"); } if (req_len == 0) { return; } else if (req_len < 16) { /* Minimal: "GET / HTTP/1.0\n\n" */ _shttpd_send_server_error(c, 400, "Bad request"); } else if (set_request_method(c)) { _shttpd_send_server_error(c, 501, "Method Not Implemented"); } else if ((c->request = _shttpd_strndup(s, req_len)) == NULL) { _shttpd_send_server_error(c, 500, "Cannot allocate request"); } if (c->loc.flags & FLAG_CLOSED) return; io_inc_tail(&c->rem.io, req_len); 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/. */ if (sscanf(p, "HTTP/%lu.%lu%n", &c->major_version, &c->minor_version, &n) != 2 || p[n] != '\0') { _shttpd_send_server_error(c, 400, "Bad HTTP version"); } else if (c->major_version > 1 || (c->major_version == 1 && c->minor_version > 1)) { _shttpd_send_server_error(c, 505, "HTTP version not supported"); } else if (uri_len <= 0) { _shttpd_send_server_error(c, 400, "Bad URI"); } else if ((c->uri = malloc(uri_len + 1)) == NULL) { _shttpd_send_server_error(c, 500, "Cannot allocate URI"); } else { _shttpd_strlcpy(c->uri, (char *) start, uri_len + 1); _shttpd_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; decide_what_to_do(c); } } static void add_socket(struct worker *worker, int sock, int is_ssl) { struct shttpd_ctx *ctx = worker->ctx; struct conn *c; struct usa sa; int l = IS_TRUE(ctx, OPT_INETD) ? E_FATAL : E_LOG; #if !defined(NO_SSL) SSL *ssl = NULL; #else is_ssl = is_ssl; /* supress warnings */ #endif /* NO_SSL */ sa.len = sizeof(sa.u.sin); (void) _shttpd_set_non_blocking_mode(sock); if (getpeername(sock, &sa.u.sa, &sa.len)) { _shttpd_elog(l, NULL, "add_socket: %s", strerror(errno)); #if !defined(NO_SSL) } else if (is_ssl && (ssl = SSL_new(ctx->ssl_ctx)) == NULL) { _shttpd_elog(l, NULL, "add_socket: SSL_new: %s", strerror(ERRNO)); (void) closesocket(sock); } else if (is_ssl && SSL_set_fd(ssl, sock) == 0) { _shttpd_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 * IO_BUFSIZE)) == NULL) { #if !defined(NO_SSL) if (ssl) SSL_free(ssl); #endif /* NO_SSL */ (void) closesocket(sock); _shttpd_elog(l, NULL, "add_socket: calloc: %s", strerror(ERRNO)); } else { c->rem.conn = c->loc.conn = c; c->ctx = ctx; c->worker = worker; c->sa = sa; c->birth_time = _shttpd_current_time; c->expire_time = _shttpd_current_time + EXPIRE_TIME; (void) getsockname(sock, &sa.u.sa, &sa.len); c->loc_port = sa.u.sin.sin_port; _shttpd_set_close_on_exec(sock); c->loc.io_class = NULL; c->rem.io_class = &_shttpd_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 + IO_BUFSIZE; c->loc.io.size = c->rem.io.size = IO_BUFSIZE; #if !defined(NO_SSL) if (is_ssl) { c->rem.io_class = &_shttpd_io_ssl; c->rem.chan.ssl.sock = sock; c->rem.chan.ssl.ssl = ssl; _shttpd_ssl_handshake(&c->rem); } #endif /* NO_SSL */ LL_TAIL(&worker->connections, &c->link); worker->num_conns++; DBG(("%s:%hu connected (socket %d)", inet_ntoa(* (struct in_addr *) &sa.u.sin.sin_addr.s_addr), ntohs(sa.u.sin.sin_port), sock)); } } static struct worker * first_worker(struct shttpd_ctx *ctx) { return (LL_ENTRY(ctx->workers.next, struct worker, link)); } static void pass_socket(struct shttpd_ctx *ctx, int sock, int is_ssl) { struct llhead *lp; struct worker *worker, *lazy; int buf[3]; lazy = first_worker(ctx); /* Find least busy worker */ LL_FOREACH(&ctx->workers, lp) { worker = LL_ENTRY(lp, struct worker, link); if (worker->num_conns < lazy->num_conns) lazy = worker; } buf[0] = CTL_PASS_SOCKET; buf[1] = sock; buf[2] = is_ssl; (void) send(lazy->ctl[1], (void *) buf, sizeof(buf), 0); } static int set_ports(struct shttpd_ctx *ctx, const char *p) { int sock, len, is_ssl, port; struct listener *l; free_list(&ctx->listeners, &listener_destructor); FOR_EACH_WORD_IN_LIST(p, len) { is_ssl = p[len - 1] == 's' ? 1 : 0; port = atoi(p); if ((sock = shttpd_open_listening_port(port)) == -1) { _shttpd_elog(E_LOG, NULL, "cannot open port %d", port); goto fail; } else if (is_ssl && ctx->ssl_ctx == NULL) { (void) closesocket(sock); _shttpd_elog(E_LOG, NULL, "cannot add SSL socket, " "please specify certificate file"); goto fail; } else if ((l = calloc(1, sizeof(*l))) == NULL) { (void) closesocket(sock); _shttpd_elog(E_LOG, NULL, "cannot allocate listener"); goto fail; } else { l->is_ssl = is_ssl; l->sock = sock; l->ctx = ctx; LL_TAIL(&ctx->listeners, &l->link); DBG(("shttpd_listen: added socket %d", sock)); } } return (TRUE); fail: free_list(&ctx->listeners, &listener_destructor); return (FALSE); } static void read_stream(struct stream *stream) { int n, len; 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 */ assert(stream->io_class != NULL); n = stream->io_class->read(stream, io_space(&stream->io), len); if (n > 0) io_inc_head(&stream->io, n); else if (n == -1 && (ERRNO == EINTR || ERRNO == EWOULDBLOCK)) n = n; /* Ignore EINTR and EAGAIN */ else if (!(stream->flags & FLAG_DONT_CLOSE)) _shttpd_stop_stream(stream); 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)); /* * 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) _shttpd_stop_stream(stream); } stream->conn->expire_time = _shttpd_current_time + EXPIRE_TIME; } static void write_stream(struct stream *from, struct stream *to) { int n, len; 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 = _shttpd_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); else if (n == -1 && (ERRNO == EINTR || ERRNO == EWOULDBLOCK)) n = n; /* Ignore EINTR and EAGAIN */ else if (!(to->flags & FLAG_DONT_CLOSE)) _shttpd_stop_stream(to); } static void connection_desctructor(struct llhead *lp) { 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 (c->request != NULL && c->ctx->access_log != NULL) _shttpd_log_access(c->ctx->access_log, c); /* In inetd mode, exit if request is finished. */ if (IS_TRUE(c->ctx, OPT_INETD)) 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 */ do_close = (c->ch.connection.v_vec.len >= vec.len && !_shttpd_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); /* 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 = 0; c->loc.content_len = 0; c->rem.flags = FLAG_W | FLAG_R | FLAG_SSL_ACCEPTED; c->query = c->request = c->uri = c->path_info = NULL; c->mime_type.len = 0; (void) memset(&c->ch, 0, sizeof(c->ch)); io_clear(&c->loc.io); c->birth_time = _shttpd_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); LL_DEL(&c->link); c->worker->num_conns--; assert(c->worker->num_conns >= 0); free(c); } } static void worker_destructor(struct llhead *lp) { struct worker *worker = LL_ENTRY(lp, struct worker, link); free_list(&worker->connections, connection_desctructor); free(worker); } 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) { FD_SET(fd, set); if (fd > *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: %d [%.*s]", (int) io_data_len(&c->loc.io), (int) io_data_len(&c->loc.io), io_data(&c->loc.io))); DBG(("rem: %d [%.*s]", (int) io_data_len(&c->rem.io), (int) 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); /* Check whether we should close this connection */ if ((_shttpd_current_time > c->expire_time) || (c->rem.flags & FLAG_CLOSED) || ((c->loc.flags & FLAG_CLOSED) && !io_data_len(&c->loc.io))) connection_desctructor(&c->link); } static int num_workers(const struct shttpd_ctx *ctx) { char *p = ctx->options[OPT_THREADS]; return (p ? atoi(p) : 1); } static void handle_connected_socket(struct shttpd_ctx *ctx, struct usa *sap, int sock, int is_ssl) { #if !defined(_WIN32) if (sock >= (int) FD_SETSIZE) { _shttpd_elog(E_LOG, NULL, "ctx %p: discarding " "socket %d, too busy", ctx, sock); (void) closesocket(sock); } else #endif /* !_WIN32 */ if (!is_allowed(ctx, sap)) { _shttpd_elog(E_LOG, NULL, "%s is not allowed to connect", inet_ntoa(sap->u.sin.sin_addr)); (void) closesocket(sock); } else if (num_workers(ctx) > 1) { pass_socket(ctx, sock, is_ssl); } else { add_socket(first_worker(ctx), sock, is_ssl); } } static int do_select(int max_fd, fd_set *read_set, fd_set *write_set, int milliseconds) { struct timeval tv; int n; tv.tv_sec = milliseconds / 1000; tv.tv_usec = (milliseconds % 1000) * 1000; /* Check IO readiness */ if ((n = 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)); } return (n); } static int multiplex_worker_sockets(const struct worker *worker, int *max_fd, fd_set *read_set, fd_set *write_set) { struct llhead *lp; struct conn *c; int nowait = FALSE; /* Add control socket */ add_to_set(worker->ctl[0], read_set, max_fd); /* Multiplex streams */ LL_FOREACH(&worker->connections, lp) { c = LL_ENTRY(lp, struct conn, link); /* If there is a space in remote IO, check remote socket */ 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 == &_shttpd_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 == &_shttpd_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->loc.flags & FLAG_SUSPEND)) add_to_set(c->rem.chan.fd, write_set, max_fd); /* * Set select wait interval to zero if FLAG_ALWAYS_READY set */ if (io_space_len(&c->loc.io) && (c->loc.flags & FLAG_R) && (c->loc.flags & FLAG_ALWAYS_READY)) nowait = TRUE; if (io_data_len(&c->rem.io) && (c->loc.flags & FLAG_W) && (c->loc.flags & FLAG_ALWAYS_READY)) nowait = TRUE; } return (nowait); } int shttpd_join(struct shttpd_ctx *ctx, fd_set *read_set, fd_set *write_set, int *max_fd) { struct llhead *lp; struct listener *l; int nowait = FALSE; /* Add listening sockets to the read set */ LL_FOREACH(&ctx->listeners, lp) { l = LL_ENTRY(lp, struct listener, link); add_to_set(l->sock, read_set, max_fd); DBG(("FD_SET(%d) (listening)", l->sock)); } if (num_workers(ctx) == 1) nowait = multiplex_worker_sockets(first_worker(ctx), max_fd, read_set, write_set); return (nowait); } static void process_worker_sockets(struct worker *worker, fd_set *read_set) { struct llhead *lp, *tmp; int cmd, skt[2], sock = worker->ctl[0]; struct conn *c; /* Check if new socket is passed to us over the control socket */ if (FD_ISSET(worker->ctl[0], read_set)) while (recv(sock, (void *) &cmd, sizeof(cmd), 0) == sizeof(cmd)) switch (cmd) { case CTL_PASS_SOCKET: (void)recv(sock, (void *) &skt, sizeof(skt), 0); add_socket(worker, skt[0], skt[1]); break; case CTL_WAKEUP: (void)recv(sock, (void *) &c, sizeof(c), 0); c->loc.flags &= FLAG_SUSPEND; break; default: _shttpd_elog(E_FATAL, NULL, "ctx %p: ctl cmd %d", worker->ctx, cmd); break; } /* Process all connections */ LL_FOREACH_SAFE(&worker->connections, lp, tmp) { c = LL_ENTRY(lp, struct conn, link); process_connection(c, FD_ISSET(c->rem.chan.sock, read_set), c->loc.io_class != NULL && ((c->loc.flags & FLAG_ALWAYS_READY) #if !defined(NO_CGI) || (c->loc.io_class == &_shttpd_io_cgi && FD_ISSET(c->loc.chan.fd, read_set)) #endif /* NO_CGI */ )); } } /* * 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; struct listener *l; fd_set read_set, write_set; int sock, max_fd = -1; struct usa sa; _shttpd_current_time = time(0); FD_ZERO(&read_set); FD_ZERO(&write_set); if (shttpd_join(ctx, &read_set, &write_set, &max_fd)) milliseconds = 0; if (do_select(max_fd, &read_set, &write_set, milliseconds) < 0) return;; /* Check for incoming connections on listener sockets */ LL_FOREACH(&ctx->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) handle_connected_socket(ctx,&sa,sock,l->is_ssl); } while (sock != -1); } if (num_workers(ctx) == 1) process_worker_sockets(first_worker(ctx), &read_set); } /* * Deallocate shttpd object, free up the resources */ void shttpd_fini(struct shttpd_ctx *ctx) { size_t i; free_list(&ctx->workers, worker_destructor); free_list(&ctx->registered_uris, registered_uri_destructor); free_list(&ctx->acl, acl_destructor); free_list(&ctx->listeners, listener_destructor); #if !defined(NO_SSI) free_list(&ctx->ssi_funcs, _shttpd_ssi_func_destructor); #endif /* !NO_SSI */ 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); /* TODO: free SSL context */ free(ctx); } /* * UNIX socketpair() implementation. Why? Because Windows does not have it. * Return 0 on success, -1 on error. */ int shttpd_socketpair(int sp[2]) { struct sockaddr_in sa; int sock, ret = -1; socklen_t len = sizeof(sa); sp[0] = sp[1] = -1; (void) memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(0); sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if ((sock = socket(AF_INET, SOCK_STREAM, 0)) != -1 && !bind(sock, (struct sockaddr *) &sa, len) && !listen(sock, 1) && !getsockname(sock, (struct sockaddr *) &sa, &len) && (sp[0] = socket(AF_INET, SOCK_STREAM, 6)) != -1 && !connect(sp[0], (struct sockaddr *) &sa, len) && (sp[1] = accept(sock,(struct sockaddr *) &sa, &len)) != -1) { /* Success */ ret = 0; } else { /* Failure, close descriptors */ if (sp[0] != -1) (void) closesocket(sp[0]); if (sp[1] != -1) (void) closesocket(sp[1]); } (void) closesocket(sock); (void) _shttpd_set_non_blocking_mode(sp[0]); (void) _shttpd_set_non_blocking_mode(sp[1]); #ifndef _WIN32 (void) fcntl(sp[0], F_SETFD, FD_CLOEXEC); (void) fcntl(sp[1], F_SETFD, FD_CLOEXEC); #endif /* _WIN32*/ return (ret); } static int isbyte(int n) { return (n >= 0 && n <= 255); } static int set_inetd(struct shttpd_ctx *ctx, const char *flag) { ctx = NULL; /* Unused */ if (_shttpd_is_true(flag)) { shttpd_set_option(ctx, "ports", NULL); (void) freopen("/dev/null", "a", stderr); add_socket(first_worker(ctx), 0, 0); } return (TRUE); } static int set_uid(struct shttpd_ctx *ctx, const char *uid) { struct passwd *pw; ctx = NULL; /* Unused */ #if !defined(_WIN32) if ((pw = getpwnam(uid)) == NULL) _shttpd_elog(E_FATAL, 0, "%s: unknown user [%s]", __func__, uid); else if (setgid(pw->pw_gid) == -1) _shttpd_elog(E_FATAL, NULL, "%s: setgid(%s): %s", __func__, uid, strerror(errno)); else if (setuid(pw->pw_uid) == -1) _shttpd_elog(E_FATAL, NULL, "%s: setuid(%s): %s", __func__, uid, strerror(errno)); #endif /* !_WIN32 */ return (TRUE); } static int set_acl(struct shttpd_ctx *ctx, const char *s) { struct acl *acl = NULL; char flag; int len, a, b, c, d, n, mask; /* Delete the old ACLs if any */ free_list(&ctx->acl, acl_destructor); FOR_EACH_WORD_IN_LIST(s, len) { mask = 32; if (sscanf(s, "%c%d.%d.%d.%d%n",&flag,&a,&b,&c,&d,&n) != 5) { _shttpd_elog(E_FATAL, NULL, "[%s]: subnet must be " "[+|-]x.x.x.x[/x]", s); } else if (flag != '+' && flag != '-') { _shttpd_elog(E_FATAL, NULL, "flag must be + or -: [%s]", s); } else if (!isbyte(a)||!isbyte(b)||!isbyte(c)||!isbyte(d)) { _shttpd_elog(E_FATAL, NULL, "bad ip address: [%s]", s); } else if ((acl = malloc(sizeof(*acl))) == NULL) { _shttpd_elog(E_FATAL, NULL, "%s", "cannot malloc subnet"); } else if (sscanf(s + n, "/%d", &mask) == 0) { /* Do nothing, no mask specified */ } else if (mask < 0 || mask > 32) { _shttpd_elog(E_FATAL, NULL, "bad subnet mask: %d [%s]", n, s); } acl->ip = (a << 24) | (b << 16) | (c << 8) | d; acl->mask = mask ? 0xffffffffU << (32 - mask) : 0; acl->flag = flag; LL_TAIL(&ctx->acl, &acl->link); } return (TRUE); } #ifndef NO_SSL /* * Dynamically load SSL library. Set up ctx->ssl_ctx pointer. */ static int set_ssl(struct shttpd_ctx *ctx, const char *pem) { SSL_CTX *CTX; void *lib; struct ssl_func *fp; int retval = FALSE; /* Load SSL library dynamically */ if ((lib = dlopen(SSL_LIB, RTLD_LAZY)) == NULL) { _shttpd_elog(E_LOG, NULL, "set_ssl: cannot load %s", SSL_LIB); return (FALSE); } for (fp = ssl_sw; fp->name != NULL; fp++) if ((fp->ptr.v_void = dlsym(lib, fp->name)) == NULL) { _shttpd_elog(E_LOG, NULL,"set_ssl: cannot find %s", fp->name); return (FALSE); } /* Initialize SSL crap */ SSL_library_init(); if ((CTX = SSL_CTX_new(SSLv23_server_method())) == NULL) _shttpd_elog(E_LOG, NULL, "SSL_CTX_new error"); else if (SSL_CTX_use_certificate_file(CTX, pem, SSL_FILETYPE_PEM) == 0) _shttpd_elog(E_LOG, NULL, "cannot open %s", pem); else if (SSL_CTX_use_PrivateKey_file(CTX, pem, SSL_FILETYPE_PEM) == 0) _shttpd_elog(E_LOG, NULL, "cannot open %s", pem); else retval = TRUE; ctx->ssl_ctx = CTX; return (retval); } #endif /* NO_SSL */ static int open_log_file(FILE **fpp, const char *path) { int retval = TRUE; if (*fpp != NULL) (void) fclose(*fpp); if (path == NULL) { *fpp = NULL; } else if ((*fpp = fopen(path, "a")) == NULL) { _shttpd_elog(E_LOG, NULL, "cannot open log file %s: %s", path, strerror(errno)); retval = FALSE; } return (retval); } static int set_alog(struct shttpd_ctx *ctx, const char *path) { return (open_log_file(&ctx->access_log, path)); } static int set_elog(struct shttpd_ctx *ctx, const char *path) { return (open_log_file(&ctx->error_log, path)); } static void show_cfg_page(struct shttpd_arg *arg); static int set_cfg_uri(struct shttpd_ctx *ctx, const char *uri) { free_list(&ctx->registered_uris, ®istered_uri_destructor); if (uri != NULL) shttpd_register_uri(ctx, uri, &show_cfg_page, ctx); return (TRUE); } static struct worker * add_worker(struct shttpd_ctx *ctx) { struct worker *worker; if ((worker = calloc(1, sizeof(*worker))) == NULL) _shttpd_elog(E_FATAL, NULL, "Cannot allocate worker"); LL_INIT(&worker->connections); worker->ctx = ctx; (void) shttpd_socketpair(worker->ctl); LL_TAIL(&ctx->workers, &worker->link); return (worker); } #if !defined(NO_THREADS) static void poll_worker(struct worker *worker, int milliseconds) { fd_set read_set, write_set; int max_fd = -1; FD_ZERO(&read_set); FD_ZERO(&write_set); if (multiplex_worker_sockets(worker, &max_fd, &read_set, &write_set)) milliseconds = 0; if (do_select(max_fd, &read_set, &write_set, milliseconds) < 0) return;; process_worker_sockets(worker, &read_set); } static void worker_function(void *param) { struct worker *worker = param; while (worker->exit_flag == 0) poll_worker(worker, 1000 * 10); free_list(&worker->connections, connection_desctructor); free(worker); } static int set_workers(struct shttpd_ctx *ctx, const char *value) { int new_num, old_num; struct llhead *lp, *tmp; struct worker *worker; new_num = atoi(value); old_num = 0; LL_FOREACH(&ctx->workers, lp) old_num++; if (new_num == 1) { if (old_num > 1) /* Stop old threads */ LL_FOREACH_SAFE(&ctx->workers, lp, tmp) { worker = LL_ENTRY(lp, struct worker, link); LL_DEL(&worker->link); worker = LL_ENTRY(lp, struct worker, link); worker->exit_flag = 1; } (void) add_worker(ctx); } else { /* FIXME: we cannot here reduce the number of threads */ while (new_num > 1 && new_num > old_num) { worker = add_worker(ctx); _beginthread(worker_function, 0, worker); old_num++; } } return (TRUE); } #endif /* NO_THREADS */ static const struct opt { int index; /* Index in shttpd_ctx */ const char *name; /* Option name in config file */ const char *description; /* Description */ const char *default_value; /* Default option value */ int (*setter)(struct shttpd_ctx *, const char *); } known_options[] = { {OPT_ROOT, "root", "\tWeb root directory", ".", NULL}, {OPT_INDEX_FILES, "index_files", "Index files", INDEX_FILES, NULL}, #ifndef NO_SSL {OPT_SSL_CERTIFICATE, "ssl_cert", "SSL certificate file", NULL,set_ssl}, #endif /* NO_SSL */ {OPT_PORTS, "ports", "Listening ports", LISTENING_PORTS, set_ports}, {OPT_DIR_LIST, "dir_list", "Directory listing", "yes", NULL}, {OPT_CFG_URI, "cfg_uri", "Config uri", NULL, set_cfg_uri}, {OPT_PROTECT, "protect", "URI to htpasswd mapping", NULL, NULL}, #ifndef NO_CGI {OPT_CGI_EXTENSIONS, "cgi_ext", "CGI extensions", CGI_EXT, NULL}, {OPT_CGI_INTERPRETER, "cgi_interp", "CGI interpreter", NULL, NULL}, {OPT_CGI_ENVIRONMENT, "cgi_env", "Additional CGI env vars", NULL, NULL}, #endif /* NO_CGI */ {OPT_SSI_EXTENSIONS, "ssi_ext", "SSI extensions", SSI_EXT, NULL}, #ifndef NO_AUTH {OPT_AUTH_REALM, "auth_realm", "Authentication domain name",REALM,NULL}, {OPT_AUTH_GPASSWD, "auth_gpass", "Global passwords file", NULL, NULL}, {OPT_AUTH_PUT, "auth_PUT", "PUT,DELETE auth file", NULL, NULL}, #endif /* !NO_AUTH */ #ifdef _WIN32 {OPT_SERVICE, "service", "Manage WinNNT service (install" "|uninstall)", NULL, _shttpd_set_nt_service}, {OPT_HIDE, "systray", "Hide console, show icon on systray", "no", _shttpd_set_systray}, #else {OPT_INETD, "inetd", "Inetd mode", "no", set_inetd}, {OPT_UID, "uid", "\tRun as user", NULL, set_uid}, #endif /* _WIN32 */ {OPT_ACCESS_LOG, "access_log", "Access log file", NULL, set_alog}, {OPT_ERROR_LOG, "error_log", "Error log file", NULL, set_elog}, {OPT_MIME_TYPES, "mime_types", "Additional mime types list", NULL,NULL}, {OPT_ALIASES, "aliases", "Path=URI mappings", NULL, NULL}, {OPT_ACL, "acl", "\tAllow/deny IP addresses/subnets", NULL, set_acl}, #if !defined(NO_THREADS) {OPT_THREADS, "threads", "Number of worker threads", "1", set_workers}, #endif /* !NO_THREADS */ {-1, NULL, NULL, NULL, NULL} }; static const struct opt * find_opt(const char *opt_name) { int i; for (i = 0; known_options[i].name != NULL; i++) if (!strcmp(opt_name, known_options[i].name)) return (known_options + i); _shttpd_elog(E_FATAL, NULL, "no such option: [%s]", opt_name); /* UNREACHABLE */ return (NULL); } int shttpd_set_option(struct shttpd_ctx *ctx, const char *opt, const char *val) { const struct opt *o = find_opt(opt); int retval = TRUE; /* Call option setter first, so it can use both new and old values */ if (o->setter != NULL) retval = o->setter(ctx, val); /* Free old value if any */ if (ctx->options[o->index] != NULL) free(ctx->options[o->index]); /* Set new option value */ ctx->options[o->index] = val ? _shttpd_strdup(val) : NULL; return (retval); } static void show_cfg_page(struct shttpd_arg *arg) { struct shttpd_ctx *ctx = arg->user_data; char opt_name[20], value[BUFSIZ]; const struct opt *o; opt_name[0] = value[0] = '\0'; if (!strcmp(shttpd_get_env(arg, "REQUEST_METHOD"), "POST")) { if (arg->flags & SHTTPD_MORE_POST_DATA) return; (void) shttpd_get_var("o", arg->in.buf, arg->in.len, opt_name, sizeof(opt_name)); (void) shttpd_get_var("v", arg->in.buf, arg->in.len, value, sizeof(value)); shttpd_set_option(ctx, opt_name, value[0] ? value : NULL); } shttpd_printf(arg, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" "

SHTTPD v. %s

", shttpd_version()); shttpd_printf(arg, "%s", "" ""); if (opt_name[0] != '\0' && value[0] != '\0') shttpd_printf(arg, "

Saved: %s=%s

", opt_name, value[0] ? value : "NULL"); for (o = known_options; o->name != NULL; o++) { shttpd_printf(arg, "" "" "" "", o->name, o->description, o->name, ctx->options[o->index] ? ctx->options[o->index] : ""); } shttpd_printf(arg, "%s", "
OptionDescriptionValue
%s%s
"); arg->flags |= SHTTPD_END_OF_OUTPUT; } /* * Show usage string and exit. */ void _shttpd_usage(const char *prog) { const struct opt *o; (void) fprintf(stderr, "SHTTPD version %s (c) Sergey Lyubka\n" "usage: %s [options] [config_file]\n", SHTTPD_VERSION, prog); #if !defined(NO_AUTH) fprintf(stderr, " -A \n"); #endif /* NO_AUTH */ for (o = known_options; o->name != NULL; o++) { (void) fprintf(stderr, " -%s\t%s", o->name, o->description); if (o->default_value != NULL) fprintf(stderr, " (default: %s)", o->default_value); fputc('\n', stderr); } exit(EXIT_FAILURE); } static void set_opt(struct shttpd_ctx *ctx, const char *opt, const char *value) { const struct opt *o; o = find_opt(opt); if (ctx->options[o->index] != NULL) free(ctx->options[o->index]); ctx->options[o->index] = _shttpd_strdup(value); } static void process_command_line_arguments(struct shttpd_ctx *ctx, char *argv[]) { const char *config_file = CONFIG_FILE; char line[BUFSIZ], opt[BUFSIZ], val[BUFSIZ], path[FILENAME_MAX], *p; FILE *fp; size_t i, line_no = 0; /* First find out, which config file to open */ for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2) if (argv[i + 1] == NULL) _shttpd_usage(argv[0]); if (argv[i] != NULL && argv[i + 1] != NULL) { /* More than one non-option arguments are given w*/ _shttpd_usage(argv[0]); } else if (argv[i] != NULL) { /* Just one non-option argument is given, this is config file */ config_file = argv[i]; } else { /* No config file specified. Look for one where shttpd lives */ if ((p = strrchr(argv[0], DIRSEP)) != 0) { _shttpd_snprintf(path, sizeof(path), "%.*s%s", p - argv[0] + 1, argv[0], config_file); config_file = path; } } fp = fopen(config_file, "r"); /* If config file was set in command line and open failed, exit */ if (fp == NULL && argv[i] != NULL) _shttpd_elog(E_FATAL, NULL, "cannot open config file %s: %s", config_file, strerror(errno)); if (fp != NULL) { _shttpd_elog(E_LOG, NULL, "Loading config file %s", config_file); /* Loop over the lines in config file */ while (fgets(line, sizeof(line), fp) != NULL) { line_no++; /* Ignore empty lines and comments */ if (line[0] == '#' || line[0] == '\n') continue; if (sscanf(line, "%s %[^\n#]", opt, val) != 2) _shttpd_elog(E_FATAL, NULL, "line %d in %s is invalid", line_no, config_file); set_opt(ctx, opt, val); } (void) fclose(fp); } /* Now pass through the command line options */ for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2) set_opt(ctx, &argv[i][1], argv[i + 1]); } struct shttpd_ctx * shttpd_init(int argc, char *argv[]) { struct shttpd_ctx *ctx; struct tm *tm; const struct opt *o; if ((ctx = calloc(1, sizeof(*ctx))) == NULL) _shttpd_elog(E_FATAL, NULL, "cannot allocate shttpd context"); LL_INIT(&ctx->registered_uris); LL_INIT(&ctx->error_handlers); LL_INIT(&ctx->acl); LL_INIT(&ctx->ssi_funcs); LL_INIT(&ctx->listeners); LL_INIT(&ctx->workers); /* Initialize options. First pass: set default option values */ for (o = known_options; o->name != NULL; o++) ctx->options[o->index] = o->default_value ? _shttpd_strdup(o->default_value) : NULL; /* Second and third passes: config file and argv */ if (argc > 0 && argv != NULL) process_command_line_arguments(ctx, argv); /* Call setter functions */ for (o = known_options; o->name != NULL; o++) if (o->setter && ctx->options[o->index] != NULL) if (o->setter(ctx, ctx->options[o->index]) == FALSE) { shttpd_fini(ctx); return (NULL); } _shttpd_current_time = time(NULL); tm = localtime(&_shttpd_current_time); _shttpd_tz_offset = 0; if (num_workers(ctx) == 1) (void) add_worker(ctx); #if 0 tm->tm_gmtoff - 3600 * (tm->tm_isdst > 0 ? 1 : 0); #endif #ifdef _WIN32 {WSADATA data; WSAStartup(MAKEWORD(2,2), &data);} #endif /* _WIN32 */ return (ctx); }