Another copy of my dotfiles. Because I don't completely trust GitHub.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

314 lines
8.0 KiB

/* 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;
}