shellinabox/libhttp/server.c
Witold Filipczyk 4d8ec30100 Unix domain sockets support.
The socket is not removed on shutdown, but the rest seems to work.
2015-07-01 13:06:04 +02:00

678 lines
24 KiB
C

// server.c -- Generic server that can deal with HTTP connections
// Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// In addition to these license terms, the author grants the following
// additional rights:
//
// If you modify this program, or any covered work, by linking or
// combining it with the OpenSSL project's OpenSSL library (or a
// modified version of that library), containing parts covered by the
// terms of the OpenSSL or SSLeay licenses, the author
// grants you additional permission to convey the resulting work.
// Corresponding Source for a non-source form of such a combination
// shall include the source code for the parts of OpenSSL used as well
// as that of the covered work.
//
// You may at your option choose to remove this additional permission from
// the work, or from any part of it.
//
// It is possible to build this program in a way that it loads OpenSSL
// libraries at run-time. If doing so, the following notices are required
// by the OpenSSL and SSLeay licenses:
//
// This product includes software developed by the OpenSSL Project
// for use in the OpenSSL Toolkit. (http://www.openssl.org/)
//
// This product includes cryptographic software written by Eric Young
// (eay@cryptsoft.com)
//
//
// The most up-to-date version of this program is always available from
// http://shellinabox.com
#include "config.h"
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "libhttp/server.h"
#include "libhttp/httpconnection.h"
#include "libhttp/ssl.h"
#include "logging/logging.h"
#ifdef HAVE_UNUSED
#defined ATTR_UNUSED __attribute__((unused))
#defined UNUSED(x) do { } while (0)
#else
#define ATTR_UNUSED
#define UNUSED(x) do { (void)(x); } while (0)
#endif
#define INITIAL_TIMEOUT (10*60)
// Maximum amount of payload (e.g. form values that have been POST'd) that we
// read into memory. If the application needs any more than this, the streaming
// API should be used, instead.
#define MAX_PAYLOAD_LENGTH (64<<10)
#if defined(__APPLE__) && defined(__MACH__)
// While MacOS X does ship with an implementation of poll(), this
// implementation is apparently known to be broken and does not comply
// with POSIX standards. Fortunately, the operating system is not entirely
// unable to check for input events. We can fall back on calling select()
// instead. This is generally not desirable, as it is less efficient and
// has a compile-time restriction on the maximum number of file
// descriptors. But on MacOS X, that's the best we can do.
int x_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
fd_set r, w, x;
FD_ZERO(&r);
FD_ZERO(&w);
FD_ZERO(&x);
int maxFd = -1;
for (int i = 0; i < nfds; ++i) {
if (fds[i].fd > maxFd) {
maxFd = fds[i].fd;
} else if (fds[i].fd < 0) {
continue;
}
if (fds[i].events & POLLIN) {
FD_SET(fds[i].fd, &r);
}
if (fds[i].events & POLLOUT) {
FD_SET(fds[i].fd, &w);
}
if (fds[i].events & POLLPRI) {
FD_SET(fds[i].fd, &x);
}
}
struct timeval tmoVal = { 0 }, *tmo;
if (timeout < 0) {
tmo = NULL;
} else {
tmoVal.tv_sec = timeout / 1000;
tmoVal.tv_usec = (timeout % 1000) * 1000;
tmo = &tmoVal;
}
int numRet = select(maxFd + 1, &r, &w, &x, tmo);
for (int i = 0, n = numRet; i < nfds && n > 0; ++i) {
if (fds[i].fd < 0) {
continue;
}
if (FD_ISSET(fds[i].fd, &x)) {
fds[i].revents = POLLPRI;
} else if (FD_ISSET(fds[i].fd, &r)) {
fds[i].revents = POLLIN;
} else {
fds[i].revents = 0;
}
if (FD_ISSET(fds[i].fd, &w)) {
fds[i].revents |= POLLOUT;
}
}
return numRet;
}
#define poll x_poll
#endif
char unixDomainSocket[UNIX_PATH_MAX];
time_t currentTime;
int unixDomainUser;
int unixDomainGroup;
int unixDomainChmod;
struct PayLoad {
int (*handler)(struct HttpConnection *, void *, const char *, int);
void *arg;
int len;
char *bytes;
};
static int serverCollectFullPayload(struct HttpConnection *http,
void *payload_, const char *buf, int len) {
int rc = HTTP_READ_MORE;
struct PayLoad *payload = (struct PayLoad *)payload_;
if (buf && len) {
if (payload->len + len > MAX_PAYLOAD_LENGTH) {
httpSendReply(http, 400, "Bad Request", NO_MSG);
return HTTP_DONE;
}
check(len > 0);
check(payload->bytes = realloc(payload->bytes, payload->len + len));
memcpy(payload->bytes + payload->len, buf, len);
payload->len += len;
}
const char *contentLength = getFromHashMap(httpGetHeaders(http),
"content-length");
if (!contentLength ||
(payload->bytes &&
((contentLength && atoi(contentLength) <= payload->len) || !buf))) {
rc = payload->handler(http, payload->arg,
payload->bytes ? payload->bytes : "", payload->len);
free(payload->bytes);
payload->bytes = NULL;
payload->len = 0;
}
if (!buf) {
if (rc == HTTP_SUSPEND || rc == HTTP_PARTIAL_REPLY) {
// Tell the other party that the connection is getting torn down, even
// though it requested it to be suspended.
payload->handler(http, payload->arg, NULL, 0);
rc = HTTP_DONE;
}
free(payload);
}
return rc;
}
static int serverCollectHandler(struct HttpConnection *http, void *handler_) {
struct HttpHandler *handler = handler_;
struct PayLoad *payload;
check(payload = malloc(sizeof(struct PayLoad)));
payload->handler = handler->streamingHandler;
payload->arg = handler->streamingArg;
payload->len = 0;
payload->bytes = malloc(0);
httpSetCallback(http, serverCollectFullPayload, payload);
return HTTP_READ_MORE;
}
static void serverDestroyHandlers(void *arg ATTR_UNUSED, char *value) {
UNUSED(arg);
free(value);
}
void serverRegisterHttpHandler(struct Server *server, const char *url,
int (*handler)(struct HttpConnection *, void *,
const char *, int), void *arg) {
if (!handler) {
addToTrie(&server->handlers, url, NULL);
} else {
struct HttpHandler *h;
check(h = malloc(sizeof(struct HttpHandler)));
h->handler = serverCollectHandler;
h->arg = h;
h->streamingHandler = handler;
h->websocketHandler = NULL;
h->streamingArg = arg;
addToTrie(&server->handlers, url, (char *)h);
}
}
void serverRegisterStreamingHttpHandler(struct Server *server, const char *url,
int (*handler)(struct HttpConnection *, void *),
void *arg) {
if (!handler) {
addToTrie(&server->handlers, url, NULL);
} else {
struct HttpHandler *h;
check(h = malloc(sizeof(struct HttpHandler)));
h->handler = handler;
h->streamingHandler = NULL;
h->websocketHandler = NULL;
h->streamingArg = NULL;
h->arg = arg;
addToTrie(&server->handlers, url, (char *)h);
}
}
void serverRegisterWebSocketHandler(struct Server *server, const char *url,
int (*handler)(struct HttpConnection *, void *, int, const char *, int),
void *arg) {
if (!handler) {
addToTrie(&server->handlers, url, NULL);
} else {
struct HttpHandler *h;
check(h = malloc(sizeof(struct HttpHandler)));
h->handler = NULL;
h->streamingHandler = NULL;
h->websocketHandler = handler;
h->arg = arg;
addToTrie(&server->handlers, url, (char *)h);
}
}
static int serverQuitHandler(struct HttpConnection *http ATTR_UNUSED,
void *arg) {
UNUSED(arg);
httpSendReply(http, 200, "Good Bye", NO_MSG);
httpExitLoop(http, 1);
return HTTP_DONE;
}
struct Server *newCGIServer(int localhostOnly, int portMin, int portMax,
int timeout) {
struct Server *server;
check(server = malloc(sizeof(struct Server)));
initServer(server, localhostOnly, portMin, portMax, timeout);
return server;
}
struct Server *newServer(int localhostOnly, int port) {
return newCGIServer(localhostOnly, port, port, -1);
}
void initServer(struct Server *server, int localhostOnly, int portMin,
int portMax, int timeout) {
server->looping = 0;
server->exitAll = 0;
server->serverTimeout = timeout;
server->numericHosts = 0;
server->connections = NULL;
server->numConnections = 0;
int true = 1;
if (unixDomainSocket[0]) {
server->serverFd = socket(AF_UNIX, SOCK_STREAM, 0);
check(server->serverFd >= 0);
check(!setsockopt(server->serverFd, SOL_SOCKET, SO_REUSEADDR,
&true, sizeof(true)));
struct sockaddr_un serverAddr = { 0 };
unlink(unixDomainSocket);
serverAddr.sun_family = AF_UNIX;
strcpy(serverAddr.sun_path, unixDomainSocket);
int servlen = sizeof(serverAddr.sun_family) + strlen(unixDomainSocket);
if (bind(server->serverFd, (struct sockaddr *)&serverAddr, servlen)) {
fatal("Failed to bind to unix socket");
}
check(!chown(unixDomainSocket, unixDomainUser, unixDomainGroup));
check(!chmod(unixDomainSocket, unixDomainChmod));
check(!listen(server->serverFd, SOMAXCONN));
check(server->pollFds = malloc(sizeof(struct pollfd)));
server->pollFds->fd = server->serverFd;
server->pollFds->events = POLLIN;
initTrie(&server->handlers, serverDestroyHandlers, NULL);
serverRegisterStreamingHttpHandler(server, "/quit", serverQuitHandler, NULL);
initSSL(&server->ssl);
return;
}
server->serverFd = socket(PF_INET, SOCK_STREAM, 0);
check(server->serverFd >= 0);
check(!setsockopt(server->serverFd, SOL_SOCKET, SO_REUSEADDR,
&true, sizeof(true)));
struct sockaddr_in serverAddr = { 0 };
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(localhostOnly
? INADDR_LOOPBACK : INADDR_ANY);
// Linux unlike BSD does not have support for picking a local port range.
// So, we have to randomly pick a port from our allowed port range, and then
// keep iterating until we find an unused port.
if (portMin || portMax) {
struct timeval tv;
check(!gettimeofday(&tv, NULL));
srand((int)(tv.tv_usec ^ tv.tv_sec));
check(portMin > 0);
check(portMax < 65536);
check(portMax >= portMin);
int portStart = rand() % (portMax - portMin + 1) + portMin;
for (int p = 0; p <= portMax-portMin; p++) {
int port = (p+portStart)%(portMax-portMin+1)+ portMin;
serverAddr.sin_port = htons(port);
if (!bind(server->serverFd, (struct sockaddr *)&serverAddr,
sizeof(serverAddr))) {
break;
}
serverAddr.sin_port = 0;
}
if (!serverAddr.sin_port) {
fatal("Failed to find any available port");
}
}
check(!listen(server->serverFd, SOMAXCONN));
socklen_t socklen = (socklen_t)sizeof(serverAddr);
check(!getsockname(server->serverFd, (struct sockaddr *)&serverAddr,
&socklen));
check(socklen == sizeof(serverAddr));
server->port = ntohs(serverAddr.sin_port);
info("Listening on port %d", server->port);
check(server->pollFds = malloc(sizeof(struct pollfd)));
server->pollFds->fd = server->serverFd;
server->pollFds->events = POLLIN;
initTrie(&server->handlers, serverDestroyHandlers, NULL);
serverRegisterStreamingHttpHandler(server, "/quit", serverQuitHandler, NULL);
initSSL(&server->ssl);
}
void destroyServer(struct Server *server) {
if (server) {
if (server->serverFd >= 0) {
info("Shutting down server");
NOINTR(close(server->serverFd));
}
for (int i = 0; i < server->numConnections; i++) {
server->connections[i].destroyConnection(server->connections[i].arg);
}
free(server->connections);
free(server->pollFds);
destroyTrie(&server->handlers);
destroySSL(&server->ssl);
if (unixDomainSocket[0]) unlink(unixDomainSocket);
}
}
void deleteServer(struct Server *server) {
destroyServer(server);
free(server);
}
int serverGetListeningPort(struct Server *server) {
return server->port;
}
int serverGetFd(struct Server *server) {
return server->serverFd;
}
struct ServerConnection *serverAddConnection(struct Server *server, int fd,
int (*handleConnection)(struct ServerConnection *c,
void *arg, short *events,
short revents),
void (*destroyConnection)(void *arg),
void *arg) {
check(server->connections = realloc(server->connections,
++server->numConnections*
sizeof(struct ServerConnection)));
check(server->pollFds = realloc(server->pollFds,
(server->numConnections + 1) *
sizeof(struct pollfd)));
server->pollFds[server->numConnections].fd = fd;
server->pollFds[server->numConnections].events = POLLIN;
struct ServerConnection *connection =
server->connections + server->numConnections - 1;
connection->deleted = 0;
connection->timeout = 0;
connection->handleConnection = handleConnection;
connection->destroyConnection = destroyConnection;
connection->arg = arg;
return connection;
}
void serverDeleteConnection(struct Server *server, int fd) {
for (int i = 0; i < server->numConnections; i++) {
if (fd == server->pollFds[i + 1].fd && !server->connections[i].deleted) {
server->connections[i].deleted = 1;
server->connections[i].destroyConnection(server->connections[i].arg);
return;
}
}
}
void serverSetTimeout(struct ServerConnection *connection, time_t timeout) {
if (!currentTime) {
currentTime = time(NULL);
}
connection->timeout = timeout > 0 ? timeout + currentTime : 0;
}
time_t serverGetTimeout(struct ServerConnection *connection) {
if (connection->timeout) {
// Returns <0 if expired, 0 if not set, and >0 if still pending.
if (!currentTime) {
currentTime = time(NULL);
}
int remaining = connection->timeout - currentTime;
if (!remaining) {
remaining--;
}
return remaining;
} else {
return 0;
}
}
struct ServerConnection *serverGetConnection(struct Server *server,
struct ServerConnection *hint,
int fd) {
if (hint &&
server->connections <= hint &&
server->connections + server->numConnections > hint) {
// The compiler would like to optimize the expression:
// &server->connections[hint - server->connections] <=>
// server->connections + hint - server->connections <=>
// hint
// This transformation is correct as far as the language specification is
// concerned, but it is unintended as we actually want to check whether
// the alignment is correct. So, instead of comparing
// &server->connections[hint - server->connections] == hint
// we first use memcpy() to break aliasing.
uintptr_t ptr1, ptr2;
memcpy(&ptr1, &hint, sizeof(ptr1));
memcpy(&ptr2, &server->connections, sizeof(ptr2));
int idx = (ptr1 - ptr2)/sizeof(*server->connections);
if (&server->connections[idx] == hint &&
!hint->deleted &&
server->pollFds[hint - server->connections + 1].fd == fd) {
return hint;
}
}
for (int i = 0; i < server->numConnections; i++) {
if (server->pollFds[i + 1].fd == fd && !server->connections[i].deleted) {
return server->connections + i;
}
}
return NULL;
}
short serverConnectionSetEvents(struct Server *server,
struct ServerConnection *connection, int fd,
short events) {
dcheck(server);
dcheck(connection);
dcheck(connection >= server->connections);
dcheck(connection < server->connections + server->numConnections);
dcheck(connection == &server->connections[connection - server->connections]);
dcheck(!connection->deleted);
int idx = connection - server->connections;
short oldEvents = server->pollFds[idx + 1].events;
dcheck(fd == server->pollFds[idx + 1].fd);
server->pollFds[idx + 1].events = events;
return oldEvents;
}
void serverExitLoop(struct Server *server, int exitAll) {
server->looping--;
server->exitAll |= exitAll;
}
void serverLoop(struct Server *server) {
check(server->serverFd >= 0);
time_t lastTime;
currentTime = time(&lastTime);
int loopDepth = ++server->looping;
while (server->looping >= loopDepth && !server->exitAll) {
// TODO: There probably should be some limit on the maximum number
// of concurrently opened HTTP connections, as this could lead to
// memory exhaustion and a DoS attack.
time_t timeout = -1;
int numFds = server->numConnections + 1;
for (int i = 0; i < server->numConnections; i++) {
while (i < numFds - 1 && !server->pollFds[i + 1].events) {
// Sort filedescriptors that currently do not expect any events to
// the end of the list.
check(--numFds > 0);
struct pollfd tmpPollFd;
memmove(&tmpPollFd, server->pollFds + numFds, sizeof(struct pollfd));
memmove(server->pollFds + numFds, server->pollFds + i + 1,
sizeof(struct pollfd));
memmove(server->pollFds + i + 1, &tmpPollFd, sizeof(struct pollfd));
struct ServerConnection tmpConnection;
memmove(&tmpConnection, server->connections + numFds - 1,
sizeof(struct ServerConnection));
memmove(server->connections + numFds - 1, server->connections + i,
sizeof(struct ServerConnection));
memmove(server->connections + i, &tmpConnection,
sizeof(struct ServerConnection));
}
if (server->connections[i].timeout &&
(timeout < 0 || timeout > server->connections[i].timeout)) {
timeout = server->connections[i].timeout;
}
}
// serverTimeout is always a delta value, unlike connection timeouts
// which are absolute times.
if (server->serverTimeout >= 0) {
if (timeout < 0 || timeout > server->serverTimeout + currentTime) {
timeout = server->serverTimeout+currentTime;
}
}
if (timeout >= 0) {
// Wait at least one second longer than needed, so that even if
// poll() decides to return a second early (due to possible rounding
// errors), we still correctly detect a timeout condition.
if (timeout >= lastTime) {
timeout = (timeout - lastTime + 1) * 1000;
} else {
timeout = 1000;
}
}
int eventCount = NOINTR(poll(server->pollFds,
numFds,
timeout));
check(eventCount >= 0);
if (timeout >= 0) {
timeout += lastTime;
}
currentTime = time(&lastTime);
int isTimeout = timeout >= 0 &&
timeout/1000 <= lastTime;
if (eventCount > 0 && server->pollFds[0].revents) {
eventCount--;
if (server->pollFds[0].revents && POLLIN) {
struct sockaddr_in clientAddr;
socklen_t sockLen = sizeof(clientAddr);
int clientFd = accept(
server->serverFd, (struct sockaddr *)&clientAddr, &sockLen);
dcheck(clientFd >= 0);
if (clientFd >= 0) {
check(!fcntl(clientFd, F_SETFL, O_RDWR | O_NONBLOCK));
struct HttpConnection *http;
http = newHttpConnection(
server, clientFd, server->port,
server->ssl.enabled ? &server->ssl : NULL,
server->numericHosts);
serverSetTimeout(
serverAddConnection(server, clientFd, httpHandleConnection,
(void (*)(void *))deleteHttpConnection,
http),
INITIAL_TIMEOUT);
}
}
} else {
if (server->serverTimeout > 0 && !server->numConnections) {
// In CGI mode, exit the server, if we haven't had any active
// connections in a while.
break;
}
}
for (int i = 1;
(isTimeout || eventCount > 0) && i <= server->numConnections;
i++) {
struct ServerConnection *connection = server->connections + i - 1;
if (connection->deleted) {
continue;
}
if (!eventCount) {
server->pollFds[i].revents = 0;
}
if (server->pollFds[i].revents ||
(connection->timeout && lastTime >= connection->timeout)) {
if (server->pollFds[i].revents) {
eventCount--;
}
short events = server->pollFds[i].events;
short oldEvents = events;
if (!connection->handleConnection(connection, connection->arg,
&events, server->pollFds[i].revents)){
connection = server->connections + i - 1;
connection->destroyConnection(connection->arg);
connection->deleted = 1;
} else if (events != oldEvents) {
server->pollFds[i].events = events;
}
}
}
for (int i = 1; i <= server->numConnections; i++) {
if (server->connections[i-1].deleted) {
memmove(server->pollFds + i, server->pollFds + i + 1,
(server->numConnections - i) * sizeof(struct pollfd));
memmove(server->connections + i - 1, server->connections + i,
(server->numConnections - i)*sizeof(struct ServerConnection));
check(--i >= 0);
check(--server->numConnections >= 0);
}
}
}
// Even if multiple clients requested for us to exit the loop, we only
// ever exit the outer most loop.
server->looping = loopDepth - 1;
}
void serverEnableSSL(struct Server *server, int flag) {
if (flag) {
check(serverSupportsSSL());
}
sslEnable(&server->ssl, flag);
}
void serverSetCertificate(struct Server *server, const char *filename,
int autoGenerateMissing) {
sslSetCertificate(&server->ssl, filename, autoGenerateMissing);
}
void serverSetCertificateFd(struct Server *server, int fd) {
sslSetCertificateFd(&server->ssl, fd);
}
void serverSetNumericHosts(struct Server *server, int numericHosts) {
server->numericHosts = numericHosts;
}
struct Trie *serverGetHttpHandlers(struct Server *server) {
return &server->handlers;
}