quark

quark web server
git clone git://git.suckless.org/quark
Log | Files | Refs | LICENSE

commit e2463e733e4880c1d8034e3b90072825509ceb69
parent 2920ba56d92b461c39f3c7c9c5a264f38b899fd7
Author: Laslo Hunhold <dev@frign.de>
Date:   Mon, 15 Feb 2021 18:25:33 +0100

Refactor server- and connection-logic into separate components

The server-part (creating a pool, launching workers, etc.) and methods
concerning the handling of connections (accepting, serving, logging,
etc.) were all in main.c, making it unnecessarily complicated on top of
the fact that there is already a lot to do there.

Now, these parts are moved into server.h/server.c and connection.h/
connection.c respectively, while main() now just only has to call
into server_init_thread_pool(). In other words, main() now only has to
deal with the argument parsing and preparations, and the rest is
done by other program parts.

The methods are prefixed with server_* and connection_* respectively,
making it much easier to see where each method is defined in the
program.

This refactoring also separates concerns well. The server-struct is
now in server.h (and not in util.h, where it was always out of place)
and spacetok() is moved into util.c.

Signed-off-by: Laslo Hunhold <dev@frign.de>

Diffstat:
MMakefile | 14++++++++------
Aconnection.c | 314+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconnection.h | 32++++++++++++++++++++++++++++++++
Mhttp.h | 19+------------------
Mmain.c | 552++-----------------------------------------------------------------------------
Aserver.c | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aserver.h | 35+++++++++++++++++++++++++++++++++++
Mutil.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutil.h | 27+--------------------------
9 files changed, 650 insertions(+), 593 deletions(-)

diff --git a/Makefile b/Makefile @@ -4,15 +4,17 @@ include config.mk -COMPONENTS = data http queue sock util +COMPONENTS = connection data http queue server sock util all: quark -data.o: data.c data.h http.h util.h config.mk -http.o: http.c config.h http.h util.h config.mk -main.o: main.c arg.h data.h http.h queue.h sock.h util.h config.mk -sock.o: sock.c sock.h util.h config.mk -util.o: util.c util.h config.mk +connection.o: connection.c config.h connection.h data.h http.h server.h sock.h util.h config.mk +data.o: data.c config.h data.h http.h server.h util.h config.mk +http.o: http.c config.h http.h server.h util.h config.mk +main.o: main.c arg.h config.h server.h sock.h util.h config.mk +server.o: server.c config.h connection.h http.h queue.h server.h util.h config.mk +sock.o: sock.c config.h sock.h util.h config.mk +util.o: util.c config.h util.h config.mk quark: config.h $(COMPONENTS:=.o) $(COMPONENTS:=.h) main.o config.mk $(CC) -o $@ $(CPPFLAGS) $(CFLAGS) $(COMPONENTS:=.o) main.o $(LDFLAGS) diff --git a/connection.c b/connection.c @@ -0,0 +1,314 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <netinet/in.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include "connection.h" +#include "data.h" +#include "http.h" +#include "server.h" +#include "sock.h" +#include "util.h" + +struct worker_data { + int insock; + size_t nslots; + const struct server *srv; +}; + +void +connection_log(const struct connection *c) +{ + char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */]; + char tstmp[21]; + + /* create timestamp */ + if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ", + gmtime(&(time_t){time(NULL)}))) { + warn("strftime: Exceeded buffer capacity"); + /* continue anyway (we accept the truncation) */ + } + + /* generate address-string */ + if (sock_get_inaddr_str(&c->ia, inaddr_str, LEN(inaddr_str))) { + warn("sock_get_inaddr_str: Couldn't generate adress-string"); + inaddr_str[0] = '\0'; + } + + printf("%s\t%s\t%s%.*d\t%s\t%s%s%s%s%s\n", + tstmp, + inaddr_str, + (c->res.status == 0) ? "dropped" : "", + (c->res.status == 0) ? 0 : 3, + c->res.status, + c->req.field[REQ_HOST][0] ? c->req.field[REQ_HOST] : "-", + c->req.path[0] ? c->req.path : "-", + c->req.query[0] ? "?" : "", + c->req.query, + c->req.fragment[0] ? "#" : "", + c->req.fragment); +} + +void +connection_reset(struct connection *c) +{ + if (c != NULL) { + shutdown(c->fd, SHUT_RDWR); + close(c->fd); + memset(c, 0, sizeof(*c)); + } +} + +void +connection_serve(struct connection *c, const struct server *srv) +{ + enum status s; + int done; + + switch (c->state) { + case C_VACANT: + /* + * we were passed a "fresh" connection which should now + * try to receive the header, reset buf beforehand + */ + memset(&c->buf, 0, sizeof(c->buf)); + + c->state = C_RECV_HEADER; + /* fallthrough */ + case C_RECV_HEADER: + /* receive header */ + done = 0; + if ((s = http_recv_header(c->fd, &c->buf, &done))) { + http_prepare_error_response(&c->req, &c->res, s); + goto response; + } + if (!done) { + /* not done yet */ + return; + } + + /* parse header */ + if ((s = http_parse_header(c->buf.data, &c->req))) { + http_prepare_error_response(&c->req, &c->res, s); + goto response; + } + + /* prepare response struct */ + http_prepare_response(&c->req, &c->res, srv); +response: + /* generate response header */ + if ((s = http_prepare_header_buf(&c->res, &c->buf))) { + http_prepare_error_response(&c->req, &c->res, s); + if ((s = http_prepare_header_buf(&c->res, &c->buf))) { + /* couldn't generate the header, we failed for good */ + c->res.status = s; + goto err; + } + } + + c->state = C_SEND_HEADER; + /* fallthrough */ + case C_SEND_HEADER: + if ((s = http_send_buf(c->fd, &c->buf))) { + c->res.status = s; + goto err; + } + if (c->buf.len > 0) { + /* not done yet */ + return; + } + + c->state = C_SEND_BODY; + /* fallthrough */ + case C_SEND_BODY: + if (c->req.method == M_GET) { + if (c->buf.len == 0) { + /* fill buffer with body data */ + if ((s = data_fct[c->res.type](&c->res, &c->buf, + &c->progress))) { + /* too late to do any real error handling */ + c->res.status = s; + goto err; + } + + /* if the buffer remains empty, we are done */ + if (c->buf.len == 0) { + break; + } + } else { + /* send buffer */ + if ((s = http_send_buf(c->fd, &c->buf))) { + /* too late to do any real error handling */ + c->res.status = s; + goto err; + } + } + return; + } + break; + default: + warn("serve: invalid connection state"); + return; + } +err: + connection_log(c); + connection_reset(c); +} + +static struct connection * +connection_get_drop_candidate(struct connection *connection, size_t nslots) +{ + struct connection *c, *minc; + size_t i, j, maxcnt, cnt; + + /* + * determine the most-unimportant connection 'minc' of the in-address + * with most connections; this algorithm has a complexity of O(n²) + * in time but is O(1) in space; there are algorithms with O(n) in + * time and space, but this would require memory allocation, + * which we avoid. Given the simplicity of the inner loop and + * relatively small number of slots per thread, this is fine. + */ + for (i = 0, minc = NULL, maxcnt = 0; i < nslots; i++) { + /* + * we determine how many connections have the same + * in-address as connection[i], but also minimize over + * that set with other criteria, yielding a general + * minimizer c. We first set it to connection[i] and + * update it, if a better candidate shows up, in the inner + * loop + */ + c = &connection[i]; + + for (j = 0, cnt = 0; j < nslots; j++) { + if (!sock_same_addr(&connection[i].ia, + &connection[j].ia)) { + continue; + } + cnt++; + + /* minimize over state */ + if (connection[j].state < c->state) { + c = &connection[j]; + } else if (connection[j].state == c->state) { + /* minimize over progress */ + if (c->state == C_SEND_BODY && + connection[i].res.type != c->res.type) { + /* + * mixed response types; progress + * is not comparable + * + * the res-type-enum is ordered as + * DIRLISTING, ERROR, FILE, i.e. + * in rising priority, because a + * file transfer is most important, + * followed by error-messages. + * Dirlistings as an "interactive" + * feature (that take up lots of + * resources) have the lowest + * priority + */ + if (connection[i].res.type < + c->res.type) { + c = &connection[j]; + } + } else if (connection[j].progress < + c->progress) { + /* + * for C_SEND_BODY with same response + * type, C_RECV_HEADER and C_SEND_BODY + * it is sufficient to compare the + * raw progress + */ + c = &connection[j]; + } + } + } + + if (cnt > maxcnt) { + /* this run yielded an even greedier in-address */ + minc = c; + maxcnt = cnt; + } + } + + return minc; +} + +struct connection * +connection_accept(int insock, struct connection *connection, size_t nslots) +{ + struct connection *c = NULL; + size_t i; + + /* find vacant connection (i.e. one with no fd assigned to it) */ + for (i = 0; i < nslots; i++) { + if (connection[i].fd == 0) { + c = &connection[i]; + break; + } + } + if (i == nslots) { + /* + * all our connection-slots are occupied and the only + * way out is to drop another connection, because not + * accepting this connection just kicks this can further + * down the road (to the next queue_wait()) without + * solving anything. + * + * This may sound bad, but this case can only be hit + * either when there's a (D)DoS-attack or a massive + * influx of requests. The latter is impossible to solve + * at this moment without expanding resources, but the + * former has certain characteristics allowing us to + * handle this gracefully. + * + * During an attack (e.g. Slowloris, R-U-Dead-Yet, Slow + * Read or just plain flooding) we can not see who is + * waiting to be accept()ed. + * However, an attacker usually already has many + * connections open (while well-behaved clients could + * do everything with just one connection using + * keep-alive). Inferring a likely attacker-connection + * is an educated guess based on which in-address is + * occupying the most connection slots. Among those, + * connections in early stages (receiving or sending + * headers) are preferred over connections in late + * stages (sending body). + * + * This quantitative approach effectively drops malicious + * connections while preserving even long-running + * benevolent connections like downloads. + */ + c = connection_get_drop_candidate(connection, nslots); + c->res.status = 0; + connection_log(c); + connection_reset(c); + } + + /* accept connection */ + if ((c->fd = accept(insock, (struct sockaddr *)&c->ia, + &(socklen_t){sizeof(c->ia)})) < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + /* + * this should not happen, as we received the + * event that there are pending connections here + */ + warn("accept:"); + } + return NULL; + } + + /* set socket to non-blocking mode */ + if (sock_set_nonblocking(c->fd)) { + /* we can't allow blocking sockets */ + return NULL; + } + + return c; +} diff --git a/connection.h b/connection.h @@ -0,0 +1,32 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef CONNECTION_H +#define CONNECTION_H + +#include "http.h" +#include "server.h" +#include "util.h" + +enum connection_state { + C_VACANT, + C_RECV_HEADER, + C_SEND_HEADER, + C_SEND_BODY, + NUM_CONN_STATES, +}; + +struct connection { + enum connection_state state; + int fd; + struct sockaddr_storage ia; + struct request req; + struct response res; + struct buffer buf; + size_t progress; +}; + +struct connection *connection_accept(int, struct connection *, size_t); +void connection_log(const struct connection *); +void connection_reset(struct connection *); +void connection_serve(struct connection *, const struct server *); + +#endif /* CONNECTION_H */ diff --git a/http.h b/http.h @@ -6,6 +6,7 @@ #include <sys/socket.h> #include "config.h" +#include "server.h" #include "util.h" enum req_field { @@ -84,24 +85,6 @@ struct response { } file; }; -enum conn_state { - C_VACANT, - C_RECV_HEADER, - C_SEND_HEADER, - C_SEND_BODY, - NUM_CONN_STATES, -}; - -struct connection { - enum conn_state state; - int fd; - struct sockaddr_storage ia; - struct request req; - struct response res; - struct buffer buf; - size_t progress; -}; - enum status http_prepare_header_buf(const struct response *, struct buffer *); enum status http_send_buf(int, struct buffer *); enum status http_recv_header(int, struct buffer *, int *); diff --git a/main.c b/main.c @@ -2,494 +2,31 @@ #include <errno.h> #include <grp.h> #include <limits.h> -#include <netinet/in.h> -#include <pthread.h> #include <pwd.h> #include <regex.h> #include <signal.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> #include <sys/resource.h> -#include <sys/socket.h> +#include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> #include <unistd.h> #include "arg.h" -#include "data.h" -#include "http.h" -#include "queue.h" +#include "server.h" #include "sock.h" #include "util.h" static char *udsname; static void -logmsg(const struct connection *c) -{ - char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */]; - char tstmp[21]; - - /* create timestamp */ - if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ", - gmtime(&(time_t){time(NULL)}))) { - warn("strftime: Exceeded buffer capacity"); - /* continue anyway (we accept the truncation) */ - } - - /* generate address-string */ - if (sock_get_inaddr_str(&c->ia, inaddr_str, LEN(inaddr_str))) { - warn("sock_get_inaddr_str: Couldn't generate adress-string"); - inaddr_str[0] = '\0'; - } - - printf("%s\t%s\t%s%.*d\t%s\t%s%s%s%s%s\n", - tstmp, - inaddr_str, - (c->res.status == 0) ? "dropped" : "", - (c->res.status == 0) ? 0 : 3, - c->res.status, - c->req.field[REQ_HOST][0] ? c->req.field[REQ_HOST] : "-", - c->req.path[0] ? c->req.path : "-", - c->req.query[0] ? "?" : "", - c->req.query, - c->req.fragment[0] ? "#" : "", - c->req.fragment); -} - -static void -reset_connection(struct connection *c) -{ - if (c != NULL) { - shutdown(c->fd, SHUT_RDWR); - close(c->fd); - memset(c, 0, sizeof(*c)); - } -} - -static void -serve_connection(struct connection *c, const struct server *srv) -{ - enum status s; - int done; - - switch (c->state) { - case C_VACANT: - /* - * we were passed a "fresh" connection which should now - * try to receive the header, reset buf beforehand - */ - memset(&c->buf, 0, sizeof(c->buf)); - - c->state = C_RECV_HEADER; - /* fallthrough */ - case C_RECV_HEADER: - /* receive header */ - done = 0; - if ((s = http_recv_header(c->fd, &c->buf, &done))) { - http_prepare_error_response(&c->req, &c->res, s); - goto response; - } - if (!done) { - /* not done yet */ - return; - } - - /* parse header */ - if ((s = http_parse_header(c->buf.data, &c->req))) { - http_prepare_error_response(&c->req, &c->res, s); - goto response; - } - - /* prepare response struct */ - http_prepare_response(&c->req, &c->res, srv); -response: - /* generate response header */ - if ((s = http_prepare_header_buf(&c->res, &c->buf))) { - http_prepare_error_response(&c->req, &c->res, s); - if ((s = http_prepare_header_buf(&c->res, &c->buf))) { - /* couldn't generate the header, we failed for good */ - c->res.status = s; - goto err; - } - } - - c->state = C_SEND_HEADER; - /* fallthrough */ - case C_SEND_HEADER: - if ((s = http_send_buf(c->fd, &c->buf))) { - c->res.status = s; - goto err; - } - if (c->buf.len > 0) { - /* not done yet */ - return; - } - - c->state = C_SEND_BODY; - /* fallthrough */ - case C_SEND_BODY: - if (c->req.method == M_GET) { - if (c->buf.len == 0) { - /* fill buffer with body data */ - if ((s = data_fct[c->res.type](&c->res, &c->buf, - &c->progress))) { - /* too late to do any real error handling */ - c->res.status = s; - goto err; - } - - /* if the buffer remains empty, we are done */ - if (c->buf.len == 0) { - break; - } - } else { - /* send buffer */ - if ((s = http_send_buf(c->fd, &c->buf))) { - /* too late to do any real error handling */ - c->res.status = s; - goto err; - } - } - return; - } - break; - default: - warn("serve: invalid connection state"); - return; - } -err: - logmsg(c); - reset_connection(c); -} - -static struct connection * -get_connection_drop_candidate(struct connection *connection, size_t nslots) -{ - struct connection *c, *minc; - size_t i, j, maxcnt, cnt; - - /* - * determine the most-unimportant connection 'minc' of the in-address - * with most connections; this algorithm has a complexity of O(n²) - * in time but is O(1) in space; there are algorithms with O(n) in - * time and space, but this would require memory allocation, - * which we avoid. Given the simplicity of the inner loop and - * relatively small number of slots per thread, this is fine. - */ - for (i = 0, minc = NULL, maxcnt = 0; i < nslots; i++) { - /* - * we determine how many connections have the same - * in-address as connection[i], but also minimize over - * that set with other criteria, yielding a general - * minimizer c. We first set it to connection[i] and - * update it, if a better candidate shows up, in the inner - * loop - */ - c = &connection[i]; - - for (j = 0, cnt = 0; j < nslots; j++) { - if (!sock_same_addr(&connection[i].ia, - &connection[j].ia)) { - continue; - } - cnt++; - - /* minimize over state */ - if (connection[j].state < c->state) { - c = &connection[j]; - } else if (connection[j].state == c->state) { - /* minimize over progress */ - if (c->state == C_SEND_BODY && - connection[i].res.type != c->res.type) { - /* - * mixed response types; progress - * is not comparable - * - * the res-type-enum is ordered as - * DIRLISTING, ERROR, FILE, i.e. - * in rising priority, because a - * file transfer is most important, - * followed by error-messages. - * Dirlistings as an "interactive" - * feature (that take up lots of - * resources) have the lowest - * priority - */ - if (connection[i].res.type < - c->res.type) { - c = &connection[j]; - } - } else if (connection[j].progress < - c->progress) { - /* - * for C_SEND_BODY with same response - * type, C_RECV_HEADER and C_SEND_BODY - * it is sufficient to compare the - * raw progress - */ - c = &connection[j]; - } - } - } - - if (cnt > maxcnt) { - /* this run yielded an even greedier in-address */ - minc = c; - maxcnt = cnt; - } - } - - return minc; -} - -struct connection * -accept_connection(int insock, struct connection *connection, - size_t nslots) -{ - struct connection *c = NULL; - size_t i; - - /* find vacant connection (i.e. one with no fd assigned to it) */ - for (i = 0; i < nslots; i++) { - if (connection[i].fd == 0) { - c = &connection[i]; - break; - } - } - if (i == nslots) { - /* - * all our connection-slots are occupied and the only - * way out is to drop another connection, because not - * accepting this connection just kicks this can further - * down the road (to the next queue_wait()) without - * solving anything. - * - * This may sound bad, but this case can only be hit - * either when there's a (D)DoS-attack or a massive - * influx of requests. The latter is impossible to solve - * at this moment without expanding resources, but the - * former has certain characteristics allowing us to - * handle this gracefully. - * - * During an attack (e.g. Slowloris, R-U-Dead-Yet, Slow - * Read or just plain flooding) we can not see who is - * waiting to be accept()ed. - * However, an attacker usually already has many - * connections open (while well-behaved clients could - * do everything with just one connection using - * keep-alive). Inferring a likely attacker-connection - * is an educated guess based on which in-address is - * occupying the most connection slots. Among those, - * connections in early stages (receiving or sending - * headers) are preferred over connections in late - * stages (sending body). - * - * This quantitative approach effectively drops malicious - * connections while preserving even long-running - * benevolent connections like downloads. - */ - c = get_connection_drop_candidate(connection, nslots); - c->res.status = 0; - logmsg(c); - reset_connection(c); - } - - /* accept connection */ - if ((c->fd = accept(insock, (struct sockaddr *)&c->ia, - &(socklen_t){sizeof(c->ia)})) < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - /* - * this should not happen, as we received the - * event that there are pending connections here - */ - warn("accept:"); - } - return NULL; - } - - /* set socket to non-blocking mode */ - if (sock_set_nonblocking(c->fd)) { - /* we can't allow blocking sockets */ - return NULL; - } - - return c; -} - -struct worker_data { - int insock; - size_t nslots; - const struct server *srv; -}; - -static void * -thread_method(void *data) -{ - queue_event *event = NULL; - struct connection *connection, *c, *newc; - struct worker_data *d = (struct worker_data *)data; - int qfd; - ssize_t nready; - size_t i; - - /* allocate connections */ - if (!(connection = calloc(d->nslots, sizeof(*connection)))) { - die("calloc:"); - } - - /* create event queue */ - if ((qfd = queue_create()) < 0) { - exit(1); - } - - /* add insock to the interest list (with data=NULL) */ - if (queue_add_fd(qfd, d->insock, QUEUE_EVENT_IN, 1, NULL) < 0) { - exit(1); - } - - /* allocate event array */ - if (!(event = reallocarray(event, d->nslots, sizeof(*event)))) { - die("reallocarray:"); - } - - for (;;) { - /* wait for new activity */ - if ((nready = queue_wait(qfd, event, d->nslots)) < 0) { - exit(1); - } - - /* handle events */ - for (i = 0; i < (size_t)nready; i++) { - c = queue_event_get_data(&event[i]); - - if (queue_event_is_error(&event[i])) { - if (c != NULL) { - queue_rem_fd(qfd, c->fd); - c->res.status = 0; - logmsg(c); - reset_connection(c); - } - - continue; - } - - if (c == NULL) { - /* add new connection to the interest list */ - if (!(newc = accept_connection(d->insock, - connection, - d->nslots))) { - /* - * the socket is either blocking - * or something failed. - * In both cases, we just carry on - */ - continue; - } - - /* - * add event to the interest list - * (we want IN, because we start - * with receiving the header) - */ - if (queue_add_fd(qfd, newc->fd, - QUEUE_EVENT_IN, - 0, newc) < 0) { - /* not much we can do here */ - continue; - } - } else { - /* serve existing connection */ - serve_connection(c, d->srv); - - if (c->fd == 0) { - /* we are done */ - memset(c, 0, sizeof(struct connection)); - continue; - } - - /* - * rearm the event based on the state - * we are "stuck" at - */ - switch(c->state) { - case C_RECV_HEADER: - if (queue_mod_fd(qfd, c->fd, - QUEUE_EVENT_IN, - c) < 0) { - reset_connection(c); - break; - } - break; - case C_SEND_HEADER: - case C_SEND_BODY: - if (queue_mod_fd(qfd, c->fd, - QUEUE_EVENT_OUT, - c) < 0) { - reset_connection(c); - break; - } - break; - default: - break; - } - } - } - } - - return NULL; -} - -static void -handle_connections(int *insock, size_t nthreads, size_t nslots, - const struct server *srv) -{ - pthread_t *thread = NULL; - struct worker_data *d = NULL; - size_t i; - - /* allocate worker_data structs */ - if (!(d = reallocarray(d, nthreads, sizeof(*d)))) { - die("reallocarray:"); - } - for (i = 0; i < nthreads; i++) { - d[i].insock = insock[i]; - d[i].nslots = nslots; - d[i].srv = srv; - } - - /* allocate and initialize thread pool */ - if (!(thread = reallocarray(thread, nthreads, sizeof(*thread)))) { - die("reallocarray:"); - } - for (i = 0; i < nthreads; i++) { - if (pthread_create(&thread[i], NULL, thread_method, &d[i]) != 0) { - if (errno == EAGAIN) { - die("You need to run as root or have " - "CAP_SYS_RESOURCE set, or are trying " - "to create more threads than the " - "system can offer"); - } else { - die("pthread_create:"); - } - } - } - - /* wait for threads */ - for (i = 0; i < nthreads; i++) { - if ((errno = pthread_join(thread[i], NULL))) { - warn("pthread_join:"); - } - } -} - -static void cleanup(void) { - if (udsname) - sock_rem_uds(udsname); + if (udsname) { + sock_rem_uds(udsname); + } } static void @@ -514,77 +51,6 @@ handlesignals(void(*hdl)(int)) sigaction(SIGQUIT, &sa, NULL); } -static int -spacetok(const char *s, char **t, size_t tlen) -{ - const char *tok; - size_t i, j, toki, spaces; - - /* fill token-array with NULL-pointers */ - for (i = 0; i < tlen; i++) { - t[i] = NULL; - } - toki = 0; - - /* don't allow NULL string or leading spaces */ - if (!s || *s == ' ') { - return 1; - } -start: - /* skip spaces */ - for (; *s == ' '; s++) - ; - - /* don't allow trailing spaces */ - if (*s == '\0') { - goto err; - } - - /* consume token */ - for (tok = s, spaces = 0; ; s++) { - if (*s == '\\' && *(s + 1) == ' ') { - spaces++; - s++; - continue; - } else if (*s == ' ') { - /* end of token */ - goto token; - } else if (*s == '\0') { - /* end of string */ - goto token; - } - } -token: - if (toki >= tlen) { - goto err; - } - if (!(t[toki] = malloc(s - tok - spaces + 1))) { - die("malloc:"); - } - for (i = 0, j = 0; j < s - tok - spaces + 1; i++, j++) { - if (tok[i] == '\\' && tok[i + 1] == ' ') { - i++; - } - t[toki][j] = tok[i]; - } - t[toki][s - tok - spaces] = '\0'; - toki++; - - if (*s == ' ') { - s++; - goto start; - } - - return 0; -err: - for (i = 0; i < tlen; i++) { - free(t[i]); - t[i] = NULL; - } - - return 1; -} - static void usage(void) { @@ -864,7 +330,7 @@ main(int argc, char *argv[]) } /* accept incoming connections */ - handle_connections(insock, nthreads, nslots, &srv); + server_init_thread_pool(insock, nthreads, nslots, &srv); exit(0); default: diff --git a/server.c b/server.c @@ -0,0 +1,177 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <pthread.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include "connection.h" +#include "queue.h" +#include "server.h" +#include "util.h" + +struct worker_data { + int insock; + size_t nslots; + const struct server *srv; +}; + +static void * +server_worker(void *data) +{ + queue_event *event = NULL; + struct connection *connection, *c, *newc; + struct worker_data *d = (struct worker_data *)data; + int qfd; + ssize_t nready; + size_t i; + + /* allocate connections */ + if (!(connection = calloc(d->nslots, sizeof(*connection)))) { + die("calloc:"); + } + + /* create event queue */ + if ((qfd = queue_create()) < 0) { + exit(1); + } + + /* add insock to the interest list (with data=NULL) */ + if (queue_add_fd(qfd, d->insock, QUEUE_EVENT_IN, 1, NULL) < 0) { + exit(1); + } + + /* allocate event array */ + if (!(event = reallocarray(event, d->nslots, sizeof(*event)))) { + die("reallocarray:"); + } + + for (;;) { + /* wait for new activity */ + if ((nready = queue_wait(qfd, event, d->nslots)) < 0) { + exit(1); + } + + /* handle events */ + for (i = 0; i < (size_t)nready; i++) { + c = queue_event_get_data(&event[i]); + + if (queue_event_is_error(&event[i])) { + if (c != NULL) { + queue_rem_fd(qfd, c->fd); + c->res.status = 0; + connection_log(c); + connection_reset(c); + } + + continue; + } + + if (c == NULL) { + /* add new connection to the interest list */ + if (!(newc = connection_accept(d->insock, + connection, + d->nslots))) { + /* + * the socket is either blocking + * or something failed. + * In both cases, we just carry on + */ + continue; + } + + /* + * add event to the interest list + * (we want IN, because we start + * with receiving the header) + */ + if (queue_add_fd(qfd, newc->fd, + QUEUE_EVENT_IN, + 0, newc) < 0) { + /* not much we can do here */ + continue; + } + } else { + /* serve existing connection */ + connection_serve(c, d->srv); + + if (c->fd == 0) { + /* we are done */ + memset(c, 0, sizeof(struct connection)); + continue; + } + + /* + * rearm the event based on the state + * we are "stuck" at + */ + switch(c->state) { + case C_RECV_HEADER: + if (queue_mod_fd(qfd, c->fd, + QUEUE_EVENT_IN, + c) < 0) { + connection_reset(c); + break; + } + break; + case C_SEND_HEADER: + case C_SEND_BODY: + if (queue_mod_fd(qfd, c->fd, + QUEUE_EVENT_OUT, + c) < 0) { + connection_reset(c); + break; + } + break; + default: + break; + } + } + } + } + + return NULL; +} + +void +server_init_thread_pool(int *insock, size_t nthreads, size_t nslots, + const struct server *srv) +{ + pthread_t *thread = NULL; + struct worker_data *d = NULL; + size_t i; + + /* allocate worker_data structs */ + if (!(d = reallocarray(d, nthreads, sizeof(*d)))) { + die("reallocarray:"); + } + for (i = 0; i < nthreads; i++) { + d[i].insock = insock[i]; + d[i].nslots = nslots; + d[i].srv = srv; + } + + /* allocate and initialize thread pool */ + if (!(thread = reallocarray(thread, nthreads, sizeof(*thread)))) { + die("reallocarray:"); + } + for (i = 0; i < nthreads; i++) { + if (pthread_create(&thread[i], NULL, server_worker, &d[i]) != 0) { + if (errno == EAGAIN) { + die("You need to run as root or have " + "CAP_SYS_RESOURCE set, or are trying " + "to create more threads than the " + "system can offer"); + } else { + die("pthread_create:"); + } + } + } + + /* wait for threads */ + for (i = 0; i < nthreads; i++) { + if ((errno = pthread_join(thread[i], NULL))) { + warn("pthread_join:"); + } + } +} diff --git a/server.h b/server.h @@ -0,0 +1,35 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef SERVER_H +#define SERVER_H + +#include <regex.h> +#include <stddef.h> + +struct vhost { + char *chost; + char *regex; + char *dir; + char *prefix; + regex_t re; +}; + +struct map { + char *chost; + char *from; + char *to; +}; + +struct server { + char *host; + char *port; + char *docindex; + int listdirs; + struct vhost *vhost; + size_t vhost_len; + struct map *map; + size_t map_len; +}; + +void server_init_thread_pool(int *, size_t, size_t, const struct server *); + +#endif /* SERVER_H */ diff --git a/util.c b/util.c @@ -123,6 +123,79 @@ prepend(char *str, size_t size, const char *prefix) return 0; } +int +spacetok(const char *s, char **t, size_t tlen) +{ + const char *tok; + size_t i, j, toki, spaces; + + /* fill token-array with NULL-pointers */ + for (i = 0; i < tlen; i++) { + t[i] = NULL; + } + toki = 0; + + /* don't allow NULL string or leading spaces */ + if (!s || *s == ' ') { + return 1; + } +start: + /* skip spaces */ + for (; *s == ' '; s++) + ; + + /* don't allow trailing spaces */ + if (*s == '\0') { + goto err; + } + + /* consume token */ + for (tok = s, spaces = 0; ; s++) { + if (*s == '\\' && *(s + 1) == ' ') { + spaces++; + s++; + continue; + } else if (*s == ' ') { + /* end of token */ + goto token; + } else if (*s == '\0') { + /* end of string */ + goto token; + } + } +token: + if (toki >= tlen) { + goto err; + } + if (!(t[toki] = malloc(s - tok - spaces + 1))) { + die("malloc:"); + } + for (i = 0, j = 0; j < s - tok - spaces + 1; i++, j++) { + if (tok[i] == '\\' && tok[i + 1] == ' ') { + i++; + } + t[toki][j] = tok[i]; + } + t[toki][s - tok - spaces] = '\0'; + toki++; + + if (*s == ' ') { + s++; + goto start; + } + + return 0; +err: + for (i = 0; i < tlen; i++) { + free(t[i]); + t[i] = NULL; + } + + return 1; +} + + + #define INVALID 1 #define TOOSMALL 2 #define TOOLARGE 3 diff --git a/util.h b/util.h @@ -8,32 +8,6 @@ #include "config.h" -/* main server struct */ -struct vhost { - char *chost; - char *regex; - char *dir; - char *prefix; - regex_t re; -}; - -struct map { - char *chost; - char *from; - char *to; -}; - -struct server { - char *host; - char *port; - char *docindex; - int listdirs; - struct vhost *vhost; - size_t vhost_len; - struct map *map; - size_t map_len; -}; - /* general purpose buffer */ struct buffer { char data[BUFFER_SIZE]; @@ -58,6 +32,7 @@ void eunveil(const char *, const char *); int timestamp(char *, size_t, time_t); int esnprintf(char *, size_t, const char *, ...); int prepend(char *, size_t, const char *); +int spacetok(const char *, char **, size_t); void *reallocarray(void *, size_t, size_t); long long strtonum(const char *, long long, long long, const char **);