blob: 2266ae24fc3a58259c08e59197e2eff64cc4e22c [file] [log] [blame]
/*
* 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(&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
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));
}
}