mirror of
https://github.com/NishiOwO/ircservices5.git
synced 2025-04-21 16:54:38 +00:00
953 lines
30 KiB
C
953 lines
30 KiB
C
/* Main HTTP server module.
|
|
*
|
|
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
|
* E-mail: <achurch@achurch.org>
|
|
* Parts written by Andrew Kempe and others.
|
|
* This program is free but copyrighted software; see the file GPL.txt for
|
|
* details.
|
|
*/
|
|
|
|
#include "services.h"
|
|
#include "modules.h"
|
|
#include "conffile.h"
|
|
#include "timeout.h"
|
|
#include "http.h"
|
|
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
/*************************************************************************/
|
|
|
|
static int cb_auth = -1;
|
|
static int cb_request = -1;
|
|
|
|
|
|
static int32 ListenBacklog;
|
|
static int32 RequestBufferSize;
|
|
static int32 MaxConnections;
|
|
static int32 MaxRequests;
|
|
static time_t IdleTimeout;
|
|
static int LogConnections;
|
|
|
|
/* List of ports to listen to (set by ListenTo configuration directive) */
|
|
static struct listento_ {
|
|
char ip[16]; /* aaa.bbb.ccc.ddd\0 */
|
|
uint16 port;
|
|
} *ListenTo;
|
|
static int ListenTo_count;
|
|
#define MAX_LISTENTO 32767
|
|
|
|
|
|
/* Array of listen sockets (corresponding to ListenTo[] entries) */
|
|
static Socket **listen_sockets;
|
|
|
|
/* Array of clients */
|
|
static Client *clients;
|
|
int clients_count;
|
|
|
|
/*************************************************************************/
|
|
|
|
static void do_accept(Socket *listener, void *param);
|
|
static void do_disconnect(Socket *socket, void *param_unused);
|
|
static void do_readline(Socket *s, void *param_unused);
|
|
static void do_readdata(Socket *s, void *param);
|
|
static void do_timeout(Timeout *t);
|
|
|
|
static Client *find_client(Socket *s);
|
|
static void set_timeout(Client *c);
|
|
static void clear_timeout(Client *c);
|
|
static void parse_header(Client *c, char *linestart);
|
|
static void handle_request(Client *c);
|
|
|
|
/*************************************************************************/
|
|
/*************************** HTTP server core ****************************/
|
|
/*************************************************************************/
|
|
|
|
/* Accept a connection and perform IP address checks on it; if it doesn't
|
|
* pass, disconnect it.
|
|
*/
|
|
|
|
static void do_accept(Socket *listener, void *param)
|
|
{
|
|
Socket *new = param;
|
|
struct sockaddr_in sin;
|
|
int sin_len = sizeof(sin);
|
|
|
|
if (sock_remote(new, (struct sockaddr *)&sin, &sin_len) < 0) {
|
|
module_log_perror("sock_remote() failed");
|
|
} else if (sin_len > sizeof(sin)) {
|
|
module_log("sock_remote() returned oversize address (%d)", sin_len);
|
|
} else if (sin.sin_family != AF_INET) {
|
|
module_log("sock_remote() returned bad address family (%d)",
|
|
sin.sin_family);
|
|
} else {
|
|
int i = clients_count;
|
|
ARRAY_EXTEND(clients);
|
|
snprintf(clients[i].address, sizeof(clients[i].address), "%s:%u",
|
|
unpack_ip((uint8 *)&sin.sin_addr), ntohs(sin.sin_port));
|
|
clients[i].socket = new;
|
|
clients[i].ip = sin.sin_addr.s_addr;
|
|
clients[i].port = sin.sin_port;
|
|
clients[i].timeout = NULL;
|
|
clients[i].request_count = 0;
|
|
clients[i].in_request = 0;
|
|
clients[i].request_buf = smalloc(RequestBufferSize);
|
|
clients[i].request_len = 0;
|
|
clients[i].version_major = 0;
|
|
clients[i].version_minor = 0;
|
|
clients[i].method = -1;
|
|
clients[i].url = NULL;
|
|
clients[i].data = NULL;
|
|
clients[i].data_len = 0;
|
|
clients[i].headers = NULL;
|
|
clients[i].headers_count = 0;
|
|
clients[i].variables = NULL;
|
|
clients[i].variables_count = 0;
|
|
if (clients_count >= MaxConnections) {
|
|
module_log("Dropping connection (exceeded MaxConnections: %d)"
|
|
" from %s", MaxConnections, clients[i].address);
|
|
http_error(&clients[i], HTTP_F_SERVICE_UNAVAILABLE, NULL);
|
|
/* http_error() closes socket for us, so don't try to
|
|
* disconnect it below */
|
|
} else {
|
|
set_timeout(&clients[i]);
|
|
sock_setcb(new, SCB_READLINE, do_readline);
|
|
sock_setcb(new, SCB_DISCONNECT, do_disconnect);
|
|
sock_set_blocking(new, 1);
|
|
if (LogConnections)
|
|
module_log("Accepted connection from %s",
|
|
clients[i].address);
|
|
}
|
|
return;
|
|
}
|
|
disconn(new);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static void do_disconnect(Socket *socket, void *param_unused)
|
|
{
|
|
Client *c = find_client(socket);
|
|
int index = c - clients;
|
|
|
|
if (!c) {
|
|
module_log("BUG: unexpected disconnect callback for socket %p",socket);
|
|
return;
|
|
}
|
|
clear_timeout(c);
|
|
free(c->headers);
|
|
free(c->variables);
|
|
free(c->request_buf);
|
|
ARRAY_REMOVE(clients, index);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Read a line from a client socket. If the end of the request data is
|
|
* reached, the request is passed to handle_request(); if the request data
|
|
* length exceeds the buffer size, an error is sent to the client and the
|
|
* connection is closed.
|
|
*/
|
|
|
|
static void do_readline(Socket *socket, void *param_unused)
|
|
{
|
|
Client *c = find_client(socket);
|
|
char line[HTTP_LINEMAX], *linestart, *s;
|
|
int32 i;
|
|
|
|
if (!c) {
|
|
module_log("BUG: unexpected readline callback for socket %p", socket);
|
|
disconn(socket);
|
|
return;
|
|
}
|
|
|
|
if (!sgets(line, sizeof(line), socket) || *line == 0) {
|
|
module_log("BUG: sgets() failed in readline callback for socket %p",
|
|
socket);
|
|
return;
|
|
}
|
|
i = strlen(line);
|
|
if (line[i-1] != '\n') {
|
|
module_log("%s: Request/header line too long, closing connection",
|
|
c->address);
|
|
http_error(c, HTTP_E_REQUEST_ENTITY_TOO_LARGE, NULL);
|
|
return;
|
|
}
|
|
line[--i] = 0;
|
|
if (i > 0 && line[i-1] == '\r')
|
|
line[--i] = 0;
|
|
i++; /* include trailing \0 */
|
|
if (c->request_len + i > RequestBufferSize) {
|
|
module_log("%s: Request too large, closing connection", c->address);
|
|
http_error(c, HTTP_E_REQUEST_ENTITY_TOO_LARGE, NULL);
|
|
return;
|
|
}
|
|
linestart = c->request_buf + c->request_len;
|
|
memcpy(linestart, line, i);
|
|
c->request_len += i;
|
|
|
|
if (!c->url) {
|
|
char *method, *url, *version;
|
|
if (!*linestart) {
|
|
/* RFC2616 4.2: servers SHOULD ignore initial empty lines (OK) */
|
|
c->request_len = 0;
|
|
set_timeout(c);
|
|
return;
|
|
}
|
|
method = strtok(linestart, " ");
|
|
url = strtok(NULL, " ");
|
|
version = strtok(NULL, " ");
|
|
if (!method || !url || !version) {
|
|
/* Note that we don't support REALLY old clients (HTTP 0.9)
|
|
* which don't send version strings */
|
|
/* RFC2616 10.4: SHOULD ensure client has received error before
|
|
* closing connection (NG) -- in this case the client is so
|
|
* broken that it doesn't deserve to be worried about */
|
|
module_log("%s: Invalid HTTP request", c->address);
|
|
http_error(c, HTTP_E_BAD_REQUEST, NULL);
|
|
return;
|
|
}
|
|
if (strcmp(method, "GET") == 0) {
|
|
c->method = METHOD_GET;
|
|
} else if (strcmp(method, "HEAD") == 0) {
|
|
c->method = METHOD_HEAD;
|
|
} else if (strcmp(method, "POST") == 0) {
|
|
c->method = METHOD_POST;
|
|
} else {
|
|
module_log("%s: Unimplemented/unsupported method `%s' requested",
|
|
c->address, method);
|
|
http_error(c, HTTP_F_NOT_IMPLEMENTED, NULL);
|
|
return;
|
|
}
|
|
if (strncmp(version, "HTTP/", 5) != 0 || !(s = strchr(version+5,'.'))){
|
|
module_log("%s: Bad HTTP version string: %s", c->address, version);
|
|
http_error(c, HTTP_E_BAD_REQUEST, NULL);
|
|
return;
|
|
}
|
|
*s++ = 0;
|
|
c->version_major = (int)atolsafe(version+5, 0, INT_MAX);
|
|
c->version_minor = (int)atolsafe(s, 0, INT_MAX);
|
|
if (c->version_major < 0 || c->version_minor < 0) {
|
|
module_log("%s: Bad HTTP version string: %s.%s",
|
|
c->address, version, s);
|
|
http_error(c, HTTP_E_BAD_REQUEST, NULL);
|
|
return;
|
|
}
|
|
if (c->version_major != 1) {
|
|
module_log("%s: Unsupported HTTP version: %d.%d",
|
|
c->address, c->version_major, c->version_minor);
|
|
http_error(c, HTTP_F_HTTP_VER_NOT_SUPPORTED, NULL);
|
|
return;
|
|
}
|
|
if (strnicmp(url, "http://", 7) == 0) {
|
|
/* RFC2616 5.1.2: MUST accept absolute URIs (OK, but we ignore
|
|
* the hostname and just pretend it's us) */
|
|
s = strchr(url+7, '/');
|
|
if (s) {
|
|
strmove(url, s);
|
|
} else {
|
|
url[0] = '/';
|
|
url[1] = 0;
|
|
}
|
|
}
|
|
c->url = url;
|
|
set_timeout(c);
|
|
return;
|
|
} /* if (!url) */
|
|
|
|
/* We already have the URL, so this must be a header or blank line. */
|
|
if (*linestart) {
|
|
/* Header line: process it */
|
|
parse_header(c, linestart);
|
|
set_timeout(c);
|
|
return;
|
|
}
|
|
|
|
/* End of headers. For GET/HEAD, handle any query string present in
|
|
* the URL and process the request immediately. For POST, handle
|
|
* Expect: 100-continue, then deal with the body. */
|
|
|
|
if (c->method == METHOD_GET || c->method == METHOD_HEAD) {
|
|
char *s = strchr(c->url, '?');
|
|
if (s) {
|
|
*s++ = 0;
|
|
c->data = s;
|
|
c->data_len = strlen(s);
|
|
}
|
|
handle_request(c);
|
|
} else if (c->method == METHOD_POST) {
|
|
long length;
|
|
s = http_get_header(c, "Content-Length");
|
|
if (!s) {
|
|
module_log("%s: Missing Content-Length header for POST",
|
|
c->address);
|
|
http_error(c, HTTP_E_LENGTH_REQUIRED, NULL);
|
|
return;
|
|
}
|
|
errno = 0;
|
|
length = atolsafe(s, 0, LONG_MAX);
|
|
if (length < 0) {
|
|
module_log("%s: Invalid Content-Length header: %s", c->address, s);
|
|
http_error(c, HTTP_E_BAD_REQUEST, NULL);
|
|
return;
|
|
} else if (c->request_len+length>RequestBufferSize) {
|
|
module_log("%s: Request too large, closing connection",c->address);
|
|
http_error(c, HTTP_E_REQUEST_ENTITY_TOO_LARGE, NULL);
|
|
return;
|
|
}
|
|
c->data = c->request_buf + c->request_len;
|
|
c->data_len = (int32)length;
|
|
if (length > 0) {
|
|
s = http_get_header(c, "Expect");
|
|
for (s = strtok(s, ", \t"); s; s = strtok(NULL, ", \t")) {
|
|
if (strcmp(s, "100-continue") == 0) {
|
|
sockprintf(socket, "HTTP/1.1 100 Continue\r\n\r\n");
|
|
break;
|
|
}
|
|
}
|
|
/* Set up to read POST data */
|
|
sock_setcb(socket, SCB_READ, do_readdata);
|
|
sock_setcb(socket, SCB_READLINE, NULL);
|
|
set_timeout(c);
|
|
} else {
|
|
/* length == 0: do it just like GET */
|
|
handle_request(c);
|
|
}
|
|
} else {
|
|
module_log("BUG: do_readline(): unsupported method %d", c->method);
|
|
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL);
|
|
}
|
|
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Read data from a client socket. When the end of the data is reached,
|
|
* reached, the request is passed to handle_request(). The request is
|
|
* assumed to have already been checked for exceeding the buffer size.
|
|
*/
|
|
|
|
static void do_readdata(Socket *socket, void *param)
|
|
{
|
|
Client *c = find_client(socket);
|
|
int32 available = (int32)(long)param, needed, nread;
|
|
|
|
if (!c) {
|
|
module_log("BUG: unexpected readdata callback for socket %p", socket);
|
|
disconn(socket);
|
|
return;
|
|
}
|
|
|
|
needed = c->data_len - (c->request_len - (c->data - c->request_buf));
|
|
if (available > needed)
|
|
available = needed;
|
|
if (c->request_len + available > RequestBufferSize) {
|
|
module_log("BUG: do_readdata(%s[%s]): data size exceeded buffer limit",
|
|
c->address, c->url);
|
|
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL);
|
|
return;
|
|
}
|
|
nread = sread(socket, c->request_buf + c->request_len, available);
|
|
if (nread != available) {
|
|
module_log("BUG: do_readdata(%s[%s]): nread (%d) != available (%d)",
|
|
c->address, c->url, nread, available);
|
|
}
|
|
c->request_len += nread;
|
|
needed -= nread;
|
|
if (needed <= 0) {
|
|
/* Prepare for next request, if any */
|
|
sock_setcb(socket, SCB_READ, NULL);
|
|
sock_setcb(socket, SCB_READLINE, do_readline);
|
|
/* Process this request */
|
|
handle_request(c);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Handle an idle timeout on a client. */
|
|
|
|
static void do_timeout(Timeout *t)
|
|
{
|
|
Client *c = find_client(t->data);
|
|
if (!c) {
|
|
module_log("BUG: do_timeout(): client not found for timeout %p!", t);
|
|
return;
|
|
}
|
|
c->timeout = NULL;
|
|
disconn(c->socket);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
/* Return the client structure corresponding to the given socket, or NULL
|
|
* if not found.
|
|
*/
|
|
|
|
static Client *find_client(Socket *s)
|
|
{
|
|
int i;
|
|
|
|
ARRAY_FOREACH (i, clients) {
|
|
if (clients[i].socket == s)
|
|
return &clients[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Start an idle timer running on the given client. If a timer was already
|
|
* running, clear it and start a new one.
|
|
*/
|
|
|
|
static void set_timeout(Client *c)
|
|
{
|
|
if (!c->socket) {
|
|
module_log("BUG: attempt to set timeout for client %d with no"
|
|
" socket!", (int)(c-clients));
|
|
return;
|
|
}
|
|
if (IdleTimeout) {
|
|
clear_timeout(c);
|
|
c->timeout = add_timeout(IdleTimeout, do_timeout, 0);
|
|
c->timeout->data = c->socket;
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Cancel the idle timer for the given client. */
|
|
|
|
static void clear_timeout(Client *c)
|
|
{
|
|
if (c->timeout) {
|
|
del_timeout(c->timeout);
|
|
c->timeout = NULL;
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Do appropriate things with the given header line. */
|
|
|
|
static void parse_header(Client *c, char *linestart)
|
|
{
|
|
char *s;
|
|
|
|
/* Check for whitespace-started line and no headers yet */
|
|
if ((*linestart == ' ' || *linestart == '\t') && !c->headers_count) {
|
|
http_error(c, HTTP_E_BAD_REQUEST, NULL);
|
|
return;
|
|
}
|
|
|
|
/* Remove all trailing whitespace */
|
|
s = linestart + strlen(linestart) - 1;
|
|
while (s > linestart && (*s == ' ' || *s == '\t')) {
|
|
*s-- = 0;
|
|
c->request_len--;
|
|
}
|
|
|
|
if (*linestart == ' ' || *linestart == '\t') {
|
|
/* If it starts with whitespace, just tack it onto the end of the
|
|
* previous line (convert all leading whitespace to a single space) */
|
|
linestart[-1] = ' ';
|
|
s = linestart;
|
|
while (*s == ' ' || *s == '\t')
|
|
s++;
|
|
strmove(linestart, s);
|
|
c->request_len -= s-linestart;
|
|
} else {
|
|
/* New header: split into name and value, and create a new
|
|
* headers[] entry */
|
|
int i = c->headers_count;
|
|
ARRAY_EXTEND(c->headers);
|
|
c->headers[i] = linestart;
|
|
s = strchr(linestart, ':');
|
|
if (!s) {
|
|
http_error(c, HTTP_E_BAD_REQUEST, NULL);
|
|
return;
|
|
}
|
|
*s++ = 0;
|
|
linestart = s;
|
|
while (*s == ' ' || *s == '\t')
|
|
s++;
|
|
strmove(linestart, s);
|
|
c->request_len -= s - linestart;
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Parse the data given by `buf' (null-terminated) into variables and
|
|
* create an array of them in c->variables. Assume standard
|
|
* x-www-form-urlencoding encoding (for multipart data, use
|
|
* parse_data_multipart() below).
|
|
*/
|
|
|
|
static void parse_data(Client *c, char *buf)
|
|
{
|
|
char *start;
|
|
int found_equals = 0;
|
|
char hexbuf[3];
|
|
|
|
hexbuf[2] = 0;
|
|
free(c->variables);
|
|
c->variables = NULL;
|
|
c->variables_count = 0;
|
|
start = buf;
|
|
|
|
ARRAY_EXTEND(c->variables);
|
|
c->variables[0] = start;
|
|
while (*buf) {
|
|
switch (*buf) {
|
|
case '=':
|
|
if (!found_equals) {
|
|
*buf = 0;
|
|
http_unquote_url(start);
|
|
found_equals = 1;
|
|
start = buf+1;
|
|
}
|
|
break;
|
|
case '&':
|
|
*buf = 0;
|
|
http_unquote_url(start);
|
|
found_equals = 0;
|
|
start = buf+1;
|
|
ARRAY_EXTEND(c->variables);
|
|
c->variables[c->variables_count-1] = start;
|
|
break;
|
|
}
|
|
buf++;
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Parse the data given by `buf' (null-terminated) into variables and
|
|
* create an array of them in c->variables, using the boundary string
|
|
* given by `boundary'.
|
|
*/
|
|
|
|
static void parse_data_multipart(Client *c, char *buf, const char *boundary)
|
|
{
|
|
char *dest = buf;
|
|
int boundarylen = strlen(boundary);
|
|
char *varname = NULL;
|
|
|
|
free(c->variables);
|
|
c->variables = NULL;
|
|
c->variables_count = 0;
|
|
|
|
buf = strstr(buf, boundary);
|
|
if (!buf)
|
|
return; /* boundary string not found */
|
|
|
|
while (*buf && (buf[boundarylen+2] != '-' || buf[boundarylen+3] != '-')) {
|
|
char *s;
|
|
|
|
/* Read in header for this part */
|
|
s = buf + strcspn(buf, "\r\n");
|
|
if (!*s)
|
|
return;
|
|
buf = s + strspn(s, "\r") + 1;
|
|
while (*buf != '\r' && *buf != '\n') {
|
|
s = buf + strcspn(buf, "\r\n");
|
|
if (!*s)
|
|
return;
|
|
if (*s == '\r')
|
|
*s++ = 0;
|
|
*s++ = 0;
|
|
if (strnicmp(buf,"Content-Disposition:",20) == 0) {
|
|
buf += 20;
|
|
while (*buf && isspace(*buf))
|
|
buf++;
|
|
if (*buf && strnicmp(buf,"form-data;",10) == 0) {
|
|
buf += 10;
|
|
while (*buf && isspace(*buf))
|
|
buf++;
|
|
if (*buf && strnicmp(buf,"name=",5) == 0) {
|
|
buf += 5;
|
|
if (*buf == '"') {
|
|
char *t = strchr(++buf, '"');
|
|
if (t)
|
|
*t = 0;
|
|
} else {
|
|
char *t = strchr(buf, ';');
|
|
if (t)
|
|
*t = 0;
|
|
}
|
|
varname = dest;
|
|
strmove(dest, buf);
|
|
dest += strlen(buf)+1;
|
|
}
|
|
}
|
|
}
|
|
buf = s;
|
|
} /* while (*buf != '\r' && *buf != '\n') */
|
|
if (*buf == '\r')
|
|
buf++;
|
|
buf++;
|
|
|
|
/* Read in data (variable contents) */
|
|
if (varname) {
|
|
ARRAY_EXTEND(c->variables);
|
|
c->variables[c->variables_count-1] = varname;
|
|
varname = NULL;
|
|
}
|
|
s = buf + strcspn(buf, "\r\n");
|
|
if (s > buf) {
|
|
memmove(dest, buf, s-buf);
|
|
dest += s-buf;
|
|
}
|
|
if (*s == '\r')
|
|
s++;
|
|
buf = s;
|
|
/* *buf is always pointing to a \n or \0 at the top of this loop */
|
|
while (*buf && (buf[1] != '-' || buf[2] != '-'
|
|
|| strncmp(buf+3, boundary, boundarylen) != 0)) {
|
|
s = buf+1 + strcspn(buf+1, "\r\n");
|
|
if (!s)
|
|
s = buf + strlen(buf);
|
|
memmove(dest, buf, s-buf);
|
|
dest += s-buf;
|
|
if (*s == '\r')
|
|
s++;
|
|
buf = s;
|
|
}
|
|
/* Null-terminate variable contents */
|
|
*dest++ = 0;
|
|
/* Skip over newline */
|
|
if (*buf)
|
|
buf++;
|
|
} /* while not final boundary line */
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static void handle_request(Client *c)
|
|
{
|
|
int res;
|
|
int close = 0;
|
|
|
|
/* Parse GET query string or POST data into variables */
|
|
if (c->data && c->data_len) {
|
|
char *s;
|
|
if (c->method == METHOD_POST) {
|
|
/* There were at least two newlines before the beginning of the
|
|
* data, so it's safe to move it back a byte (to add a trailing
|
|
* null) */
|
|
memmove(c->data-1, c->data, c->data_len);
|
|
c->data--;
|
|
c->data[c->data_len] = 0;
|
|
}
|
|
/* Check the content type, and extract the boundary string if it's
|
|
* multipart data */
|
|
s = http_get_header(c, "Content-Type");
|
|
if (s && strnicmp(s, "multipart/form-data;", 20) == 0) {
|
|
s += 20;
|
|
while (isspace(*s))
|
|
s++;
|
|
if (strnicmp(s, "boundary=", 9) == 0) {
|
|
s += 9;
|
|
if (*s == '"') {
|
|
char *t = strchr(++s, '"');
|
|
if (t)
|
|
*t = 0;
|
|
}
|
|
} else {
|
|
s = NULL;
|
|
}
|
|
} else {
|
|
s = NULL;
|
|
}
|
|
/* Parse data into variables */
|
|
if (s)
|
|
parse_data_multipart(c, c->data, s);
|
|
else
|
|
parse_data(c, c->data);
|
|
}
|
|
|
|
c->request_count++;
|
|
c->in_request = 1;
|
|
|
|
if (c->version_major == 1 && c->version_minor == 0) {
|
|
close = 1;
|
|
} else {
|
|
const char *s = http_get_header(c, "Connection");
|
|
if (s && strstr(s, "close")) {
|
|
/* This might accidentally trigger on something like "abcloseyz",
|
|
* but that's okay; all it means is the client has to reconnect */
|
|
close = 1;
|
|
}
|
|
}
|
|
|
|
res = call_callback_2(cb_auth, c, &close);
|
|
if (res < 0) {
|
|
module_log("handle_request(): call_callback(cb_request) failed");
|
|
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL);
|
|
close = 1;
|
|
} else if (res != HTTP_AUTH_DENY) {
|
|
res = call_callback_2(cb_request, c, &close);
|
|
if (res < 0) {
|
|
module_log("handle_request(): call_callback(cb_request) failed");
|
|
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL);
|
|
close = 1;
|
|
} else if (res == 0) {
|
|
http_error(c, HTTP_E_NOT_FOUND, NULL);
|
|
}
|
|
}
|
|
|
|
if (close || (MaxRequests && c->request_count >= MaxRequests)
|
|
|| c->in_request < 0 /* flag from http_error */
|
|
) {
|
|
disconn(c->socket);
|
|
} else {
|
|
free(c->headers);
|
|
free(c->variables);
|
|
c->in_request = 0;
|
|
c->request_len = 0;
|
|
c->version_major = 0;
|
|
c->version_minor = 0;
|
|
c->method = -1;
|
|
c->url = NULL;
|
|
c->data = NULL;
|
|
c->data_len = 0;
|
|
c->headers = NULL;
|
|
c->headers_count = 0;
|
|
c->variables = NULL;
|
|
c->variables_count = 0;
|
|
set_timeout(c);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/***************************** Module stuff ******************************/
|
|
/*************************************************************************/
|
|
|
|
static int do_ListenTo(const char *filename, int linenum, char *param);
|
|
|
|
ConfigDirective module_config[] = {
|
|
{ "IdleTimeout", { { CD_TIME, 0, &IdleTimeout } } },
|
|
{ "ListenBacklog", { { CD_POSINT, CF_DIRREQ, &ListenBacklog } } },
|
|
{ "ListenTo", { { CD_FUNC, CF_DIRREQ, do_ListenTo } } },
|
|
{ "LogConnections", { { CD_SET, 0, &LogConnections } } },
|
|
{ "MaxConnections", { { CD_POSINT, 0, &MaxConnections } } },
|
|
{ "MaxRequests", { { CD_POSINT, 0, &MaxRequests } } },
|
|
{ "RequestBufferSize",{ { CD_POSINT, 0, &RequestBufferSize } } },
|
|
{ NULL }
|
|
};
|
|
|
|
/*************************************************************************/
|
|
|
|
static int do_ListenTo(const char *filename, int linenum, char *param)
|
|
{
|
|
char *s;
|
|
int port;
|
|
uint8 *ip;
|
|
char *ipstr;
|
|
char ipbuf[15+1]; /* aaa.bbb.ccc.ddd\0 */
|
|
int recursing = 0, i;
|
|
static struct listento_ *new_ListenTo;
|
|
static int new_ListenTo_count;
|
|
|
|
if (!filename) {
|
|
/* filename == NULL, perform special operations */
|
|
switch (linenum) {
|
|
case CDFUNC_INIT: /* prepare to read new data */
|
|
free(new_ListenTo);
|
|
new_ListenTo = NULL;
|
|
new_ListenTo_count = 0;
|
|
break;
|
|
case CDFUNC_SET: /* store new data in config variable */
|
|
free(ListenTo);
|
|
ListenTo = new_ListenTo;
|
|
ListenTo_count = new_ListenTo_count;
|
|
new_ListenTo = NULL;
|
|
new_ListenTo_count = 0;
|
|
break;
|
|
case CDFUNC_DECONFIG: /* clear any stored data */
|
|
free(ListenTo);
|
|
ListenTo = NULL;
|
|
ListenTo_count = 0;
|
|
break;
|
|
} /* switch (linenum) */
|
|
return 1;
|
|
} /* if (!filename) */
|
|
|
|
/* filename != NULL, process directive */
|
|
|
|
if (linenum < 0) {
|
|
recursing = 1;
|
|
linenum = -linenum;
|
|
}
|
|
|
|
if (ListenTo_count >= MAX_LISTENTO) {
|
|
config_error(filename, linenum,
|
|
"Too many ListenTo addresses (maximum %d)",
|
|
MAX_LISTENTO);
|
|
return 0;
|
|
}
|
|
|
|
s = strchr(param, ':');
|
|
if (!s) {
|
|
config_error(filename, linenum,
|
|
"ListenTo address requires both address and port");
|
|
return 0;
|
|
}
|
|
|
|
*s++ = 0;
|
|
port = atolsafe(s, 1, 65535);
|
|
if (port < 1) {
|
|
config_error(filename, linenum, "Invalid port number `%s'", s);
|
|
return 0;
|
|
}
|
|
|
|
if (strcmp(param, "*") == 0) {
|
|
/* "*" -> all addresses (NULL string) */
|
|
ipstr = NULL;
|
|
} else if ((ip = pack_ip(param)) != NULL) {
|
|
/* IP address -> normalize (no leading zeros, etc.) */
|
|
snprintf(ipbuf, sizeof(ipbuf), "%u.%u.%u.%u",
|
|
ip[0], ip[1], ip[2], ip[3]);
|
|
if (strlen(ipbuf) > 15) {
|
|
config_error(filename, linenum, "BUG: strlen(ipbuf) > 15 [%s]",
|
|
ipbuf);
|
|
return 0;
|
|
}
|
|
ipstr = ipbuf;
|
|
} else {
|
|
/* hostname -> check for double recursion, then look up and
|
|
* recursively add addresses */
|
|
#ifdef HAVE_GETHOSTBYNAME
|
|
struct hostent *hp;
|
|
#endif
|
|
if (recursing) {
|
|
config_error(filename, linenum, "BUG: double recursion (param=%s)",
|
|
param);
|
|
return 0;
|
|
}
|
|
#ifdef HAVE_GETHOSTBYNAME
|
|
if ((hp = gethostbyname(param)) != NULL) {
|
|
if (hp->h_addrtype == AF_INET) {
|
|
for (i = 0; hp->h_addr_list[i]; i++) {
|
|
ip = (uint8 *)hp->h_addr_list[i];
|
|
snprintf(ipbuf, sizeof(ipbuf), "%u.%u.%u.%u",
|
|
ip[0], ip[1], ip[2], ip[3]);
|
|
if (strlen(ipbuf) > 15) {
|
|
config_error(filename, linenum,
|
|
"BUG: strlen(ipbuf) > 15 [%s]", ipbuf);
|
|
return 0;
|
|
}
|
|
if (!do_ListenTo(filename, -linenum, ipbuf))
|
|
return 0;
|
|
}
|
|
return 1; /* Success */
|
|
} else {
|
|
config_error(filename, linenum, "%s: no IPv4 addresses found",
|
|
param);
|
|
}
|
|
} else {
|
|
config_error(filename, linenum, "%s: %s", param,
|
|
hstrerror(h_errno));
|
|
}
|
|
#else
|
|
config_error(filename, linenum,
|
|
"gethostbyname() not available, hostnames may not be"
|
|
" used");
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
i = new_ListenTo_count;
|
|
ARRAY_EXTEND(new_ListenTo);
|
|
if (ipstr)
|
|
strcpy(new_ListenTo[i].ip, ipstr);/*safe: strlen(ip)<16 checked above*/
|
|
else
|
|
memset(new_ListenTo[i].ip, 0, sizeof(new_ListenTo[i].ip));
|
|
new_ListenTo[i].port = port;
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
int init_module()
|
|
{
|
|
int i, opencount;
|
|
|
|
cb_auth = register_callback("auth");
|
|
cb_request = register_callback("request");
|
|
if (cb_auth < 0 || cb_request < 0) {
|
|
module_log("Unable to register callbacks");
|
|
exit_module(0);
|
|
return 0;
|
|
}
|
|
|
|
listen_sockets = smalloc(sizeof(*listen_sockets) * ListenTo_count);
|
|
opencount = 0;
|
|
ARRAY_FOREACH (i, ListenTo) {
|
|
listen_sockets[i] = sock_new();
|
|
if (listen_sockets[i]) {
|
|
if (open_listener(listen_sockets[i],
|
|
*ListenTo[i].ip ? ListenTo[i].ip : NULL,
|
|
ListenTo[i].port, ListenBacklog) == 0) {
|
|
sock_setcb(listen_sockets[i], SCB_ACCEPT, do_accept);
|
|
module_log("Listening on %s:%u",
|
|
ListenTo[i].ip, ListenTo[i].port);
|
|
opencount++;
|
|
} else {
|
|
module_log_perror("Failed to open listen socket for %s:%u",
|
|
ListenTo[i].ip, ListenTo[i].port);
|
|
}
|
|
} else {
|
|
module_log("Failed to create listen socket for %s:%u",
|
|
*ListenTo[i].ip ? ListenTo[i].ip : "*",
|
|
ListenTo[i].port);
|
|
}
|
|
}
|
|
if (!opencount) {
|
|
module_log("No ports could be opened, aborting");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
int exit_module(int shutdown_unused)
|
|
{
|
|
int i;
|
|
|
|
ARRAY_FOREACH (i, ListenTo) {
|
|
if (listen_sockets[i]) {
|
|
close_listener(listen_sockets[i]);
|
|
sock_free(listen_sockets[i]);
|
|
}
|
|
}
|
|
free(ListenTo);
|
|
ListenTo = NULL;
|
|
ListenTo_count = 0;
|
|
free(listen_sockets);
|
|
listen_sockets = NULL;
|
|
|
|
unregister_callback(cb_request);
|
|
unregister_callback(cb_auth);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-file-style: "stroustrup"
|
|
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vim: expandtab shiftwidth=4:
|
|
*/
|