improve dynamic handler control flow logic

Merge branch 'feature-dynamic-handler-control-flow'
personal/stbuehler/mod-csrf-old
Glenn Strauss 7 years ago
commit eb0cbb00ff

@ -2,6 +2,13 @@
#include "base.h"
#include "connections.h"
#include "joblist.h"
#include "log.h"
#ifdef USE_OPENSSL
# include <openssl/ssl.h>
# include <openssl/err.h>
#endif
const char *connection_get_state(connection_state_t state) {
switch (state) {
@ -45,3 +52,318 @@ int connection_set_state(server *srv, connection *con, connection_state_t state)
return 0;
}
#if 0
static void dump_packet(const unsigned char *data, size_t len) {
size_t i, j;
if (len == 0) return;
for (i = 0; i < len; i++) {
if (i % 16 == 0) fprintf(stderr, " ");
fprintf(stderr, "%02x ", data[i]);
if ((i + 1) % 16 == 0) {
fprintf(stderr, " ");
for (j = 0; j <= i % 16; j++) {
unsigned char c;
if (i-15+j >= len) break;
c = data[i-15+j];
fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.');
}
fprintf(stderr, "\n");
}
}
if (len % 16 != 0) {
for (j = i % 16; j < 16; j++) {
fprintf(stderr, " ");
}
fprintf(stderr, " ");
for (j = i & ~0xf; j < len; j++) {
unsigned char c;
c = data[j];
fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.');
}
fprintf(stderr, "\n");
}
}
#endif
static int connection_handle_read_ssl(server *srv, connection *con) {
#ifdef USE_OPENSSL
int r, ssl_err, len, count = 0;
char *mem = NULL;
size_t mem_len = 0;
if (!con->srv_socket->is_ssl) return -1;
ERR_clear_error();
do {
chunkqueue_get_memory(con->read_queue, &mem, &mem_len, 0, SSL_pending(con->ssl));
#if 0
/* overwrite everything with 0 */
memset(mem, 0, mem_len);
#endif
len = SSL_read(con->ssl, mem, mem_len);
chunkqueue_use_memory(con->read_queue, len > 0 ? len : 0);
if (con->renegotiations > 1 && con->conf.ssl_disable_client_renegotiation) {
log_error_write(srv, __FILE__, __LINE__, "s", "SSL: renegotiation initiated by client, killing connection");
connection_set_state(srv, con, CON_STATE_ERROR);
return -1;
}
if (len > 0) {
con->bytes_read += len;
count += len;
}
} while (len == (ssize_t) mem_len && count < MAX_READ_LIMIT);
if (len < 0) {
int oerrno = errno;
switch ((r = SSL_get_error(con->ssl, len))) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
con->is_readable = 0;
/* the manual says we have to call SSL_read with the same arguments next time.
* we ignore this restriction; no one has complained about it in 1.5 yet, so it probably works anyway.
*/
return 0;
case SSL_ERROR_SYSCALL:
/**
* man SSL_get_error()
*
* SSL_ERROR_SYSCALL
* Some I/O error occurred. The OpenSSL error queue may contain more
* information on the error. If the error queue is empty (i.e.
* ERR_get_error() returns 0), ret can be used to find out more about
* the error: If ret == 0, an EOF was observed that violates the
* protocol. If ret == -1, the underlying BIO reported an I/O error
* (for socket I/O on Unix systems, consult errno for details).
*
*/
while((ssl_err = ERR_get_error())) {
/* get all errors from the error-queue */
log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:",
r, ERR_error_string(ssl_err, NULL));
}
switch(oerrno) {
default:
log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL:",
len, r, oerrno,
strerror(oerrno));
break;
}
break;
case SSL_ERROR_ZERO_RETURN:
/* clean shutdown on the remote side */
if (r == 0) {
/* FIXME: later */
}
/* fall thourgh */
default:
while((ssl_err = ERR_get_error())) {
switch (ERR_GET_REASON(ssl_err)) {
case SSL_R_SSL_HANDSHAKE_FAILURE:
case SSL_R_TLSV1_ALERT_UNKNOWN_CA:
case SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN:
case SSL_R_SSLV3_ALERT_BAD_CERTIFICATE:
if (!con->conf.log_ssl_noise) continue;
break;
default:
break;
}
/* get all errors from the error-queue */
log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:",
r, ERR_error_string(ssl_err, NULL));
}
break;
}
connection_set_state(srv, con, CON_STATE_ERROR);
return -1;
} else if (len == 0) {
con->is_readable = 0;
/* the other end close the connection -> KEEP-ALIVE */
return -2;
} else {
joblist_append(srv, con);
}
return 0;
#else
UNUSED(srv);
UNUSED(con);
return -1;
#endif
}
/* 0: everything ok, -1: error, -2: con closed */
int connection_handle_read(server *srv, connection *con) {
int len;
char *mem = NULL;
size_t mem_len = 0;
int toread;
if (con->srv_socket->is_ssl) {
return connection_handle_read_ssl(srv, con);
}
/* default size for chunks is 4kb; only use bigger chunks if FIONREAD tells
* us more than 4kb is available
* if FIONREAD doesn't signal a big chunk we fill the previous buffer
* if it has >= 1kb free
*/
#if defined(__WIN32)
chunkqueue_get_memory(con->read_queue, &mem, &mem_len, 0, 4096);
len = recv(con->fd, mem, mem_len, 0);
#else /* __WIN32 */
if (ioctl(con->fd, FIONREAD, &toread) || toread == 0 || toread <= 4*1024) {
toread = 4096;
}
else if (toread > MAX_READ_LIMIT) {
toread = MAX_READ_LIMIT;
}
chunkqueue_get_memory(con->read_queue, &mem, &mem_len, 0, toread);
len = read(con->fd, mem, mem_len);
#endif /* __WIN32 */
chunkqueue_use_memory(con->read_queue, len > 0 ? len : 0);
if (len < 0) {
con->is_readable = 0;
#if defined(__WIN32)
{
int lastError = WSAGetLastError();
switch (lastError) {
case EAGAIN:
return 0;
case EINTR:
/* we have been interrupted before we could read */
con->is_readable = 1;
return 0;
case ECONNRESET:
/* suppress logging for this error, expected for keep-alive */
break;
default:
log_error_write(srv, __FILE__, __LINE__, "sd", "connection closed - recv failed: ", lastError);
break;
}
}
#else /* __WIN32 */
switch (errno) {
case EAGAIN:
return 0;
case EINTR:
/* we have been interrupted before we could read */
con->is_readable = 1;
return 0;
case ECONNRESET:
/* suppress logging for this error, expected for keep-alive */
break;
default:
log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno);
break;
}
#endif /* __WIN32 */
connection_set_state(srv, con, CON_STATE_ERROR);
return -1;
} else if (len == 0) {
con->is_readable = 0;
/* the other end close the connection -> KEEP-ALIVE */
/* pipelining */
return -2;
} else if (len != (ssize_t) mem_len) {
/* we got less then expected, wait for the next fd-event */
con->is_readable = 0;
}
con->bytes_read += len;
#if 0
dump_packet(b->ptr, len);
#endif
return 0;
}
handler_t connection_handle_read_post_state(server *srv, connection *con) {
chunkqueue *cq = con->read_queue;
chunkqueue *dst_cq = con->request_content_queue;
int is_closed = 0;
if (con->is_readable) {
con->read_idle_ts = srv->cur_ts;
switch(connection_handle_read(srv, con)) {
case -1:
return HANDLER_ERROR;
case -2:
is_closed = 1;
break;
default:
break;
}
}
chunkqueue_remove_finished_chunks(cq);
if (con->request.content_length <= 64*1024) {
/* don't buffer request bodies <= 64k on disk */
chunkqueue_steal(dst_cq, cq, (off_t)con->request.content_length - dst_cq->bytes_in);
}
else if (0 != chunkqueue_steal_with_tempfiles(srv, dst_cq, cq, (off_t)con->request.content_length - dst_cq->bytes_in)) {
/* writing to temp file failed */
con->http_status = 500; /* Internal Server Error */
con->keep_alive = 0;
con->mode = DIRECT;
chunkqueue_reset(con->write_queue);
return HANDLER_FINISHED;
}
chunkqueue_remove_finished_chunks(cq);
if (dst_cq->bytes_in == (off_t)con->request.content_length) {
/* Content is ready */
connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
return HANDLER_GO_ON;
} else if (is_closed) {
#if 0
con->http_status = 400; /* Bad Request */
con->keep_alive = 0;
con->mode = DIRECT;
chunkqueue_reset(con->write_queue);
return HANDLER_FINISHED;
#endif
return HANDLER_ERROR;
} else {
return HANDLER_WAIT_FOR_EVENT;
}
}

@ -118,7 +118,7 @@ static int connection_del(server *srv, connection *con) {
return 0;
}
int connection_close(server *srv, connection *con) {
static int connection_close(server *srv, connection *con) {
#ifdef USE_OPENSSL
server_socket *srv_sock = con->srv_socket;
#endif
@ -143,6 +143,7 @@ int connection_close(server *srv, connection *con) {
"(warning) close:", con->fd, strerror(errno));
}
#endif
con->fd = -1;
srv->cur_fds--;
#if 0
@ -156,265 +157,6 @@ int connection_close(server *srv, connection *con) {
return 0;
}
#if 0
static void dump_packet(const unsigned char *data, size_t len) {
size_t i, j;
if (len == 0) return;
for (i = 0; i < len; i++) {
if (i % 16 == 0) fprintf(stderr, " ");
fprintf(stderr, "%02x ", data[i]);
if ((i + 1) % 16 == 0) {
fprintf(stderr, " ");
for (j = 0; j <= i % 16; j++) {
unsigned char c;
if (i-15+j >= len) break;
c = data[i-15+j];
fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.');
}
fprintf(stderr, "\n");
}
}
if (len % 16 != 0) {
for (j = i % 16; j < 16; j++) {
fprintf(stderr, " ");
}
fprintf(stderr, " ");
for (j = i & ~0xf; j < len; j++) {
unsigned char c;
c = data[j];
fprintf(stderr, "%c", c > 32 && c < 128 ? c : '.');
}
fprintf(stderr, "\n");
}
}
#endif
static int connection_handle_read_ssl(server *srv, connection *con) {
#ifdef USE_OPENSSL
int r, ssl_err, len, count = 0;
char *mem = NULL;
size_t mem_len = 0;
if (!con->srv_socket->is_ssl) return -1;
ERR_clear_error();
do {
chunkqueue_get_memory(con->read_queue, &mem, &mem_len, 0, SSL_pending(con->ssl));
#if 0
/* overwrite everything with 0 */
memset(mem, 0, mem_len);
#endif
len = SSL_read(con->ssl, mem, mem_len);
chunkqueue_use_memory(con->read_queue, len > 0 ? len : 0);
if (con->renegotiations > 1 && con->conf.ssl_disable_client_renegotiation) {
log_error_write(srv, __FILE__, __LINE__, "s", "SSL: renegotiation initiated by client, killing connection");
connection_set_state(srv, con, CON_STATE_ERROR);
return -1;
}
if (len > 0) {
con->bytes_read += len;
count += len;
}
} while (len == (ssize_t) mem_len && count < MAX_READ_LIMIT);
if (len < 0) {
int oerrno = errno;
switch ((r = SSL_get_error(con->ssl, len))) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
con->is_readable = 0;
/* the manual says we have to call SSL_read with the same arguments next time.
* we ignore this restriction; no one has complained about it in 1.5 yet, so it probably works anyway.
*/
return 0;
case SSL_ERROR_SYSCALL:
/**
* man SSL_get_error()
*
* SSL_ERROR_SYSCALL
* Some I/O error occurred. The OpenSSL error queue may contain more
* information on the error. If the error queue is empty (i.e.
* ERR_get_error() returns 0), ret can be used to find out more about
* the error: If ret == 0, an EOF was observed that violates the
* protocol. If ret == -1, the underlying BIO reported an I/O error
* (for socket I/O on Unix systems, consult errno for details).
*
*/
while((ssl_err = ERR_get_error())) {
/* get all errors from the error-queue */
log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:",
r, ERR_error_string(ssl_err, NULL));
}
switch(oerrno) {
default:
log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL:",
len, r, oerrno,
strerror(oerrno));
break;
}
break;
case SSL_ERROR_ZERO_RETURN:
/* clean shutdown on the remote side */
if (r == 0) {
/* FIXME: later */
}
/* fall thourgh */
default:
while((ssl_err = ERR_get_error())) {
switch (ERR_GET_REASON(ssl_err)) {
case SSL_R_SSL_HANDSHAKE_FAILURE:
case SSL_R_TLSV1_ALERT_UNKNOWN_CA:
case SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN:
case SSL_R_SSLV3_ALERT_BAD_CERTIFICATE:
if (!con->conf.log_ssl_noise) continue;
break;
default:
break;
}
/* get all errors from the error-queue */
log_error_write(srv, __FILE__, __LINE__, "sds", "SSL:",
r, ERR_error_string(ssl_err, NULL));
}
break;
}
connection_set_state(srv, con, CON_STATE_ERROR);
return -1;
} else if (len == 0) {
con->is_readable = 0;
/* the other end close the connection -> KEEP-ALIVE */
return -2;
} else {
joblist_append(srv, con);
}
return 0;
#else
UNUSED(srv);
UNUSED(con);
return -1;
#endif
}
/* 0: everything ok, -1: error, -2: con closed */
static int connection_handle_read(server *srv, connection *con) {
int len;
char *mem = NULL;
size_t mem_len = 0;
int toread;
if (con->srv_socket->is_ssl) {
return connection_handle_read_ssl(srv, con);
}
/* default size for chunks is 4kb; only use bigger chunks if FIONREAD tells
* us more than 4kb is available
* if FIONREAD doesn't signal a big chunk we fill the previous buffer
* if it has >= 1kb free
*/
#if defined(__WIN32)
chunkqueue_get_memory(con->read_queue, &mem, &mem_len, 0, 4096);
len = recv(con->fd, mem, mem_len, 0);
#else /* __WIN32 */
if (ioctl(con->fd, FIONREAD, &toread) || toread == 0 || toread <= 4*1024) {
toread = 4096;
}
else if (toread > MAX_READ_LIMIT) {
toread = MAX_READ_LIMIT;
}
chunkqueue_get_memory(con->read_queue, &mem, &mem_len, 0, toread);
len = read(con->fd, mem, mem_len);
#endif /* __WIN32 */
chunkqueue_use_memory(con->read_queue, len > 0 ? len : 0);
if (len < 0) {
con->is_readable = 0;
#if defined(__WIN32)
{
int lastError = WSAGetLastError();
switch (lastError) {
case EAGAIN:
return 0;
case EINTR:
/* we have been interrupted before we could read */
con->is_readable = 1;
return 0;
case ECONNRESET:
/* suppress logging for this error, expected for keep-alive */
break;
default:
log_error_write(srv, __FILE__, __LINE__, "sd", "connection closed - recv failed: ", lastError);
break;
}
}
#else /* __WIN32 */
switch (errno) {
case EAGAIN:
return 0;
case EINTR:
/* we have been interrupted before we could read */
con->is_readable = 1;
return 0;
case ECONNRESET:
/* suppress logging for this error, expected for keep-alive */
break;
default:
log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno);
break;
}
#endif /* __WIN32 */
connection_set_state(srv, con, CON_STATE_ERROR);
return -1;
} else if (len == 0) {
con->is_readable = 0;
/* the other end close the connection -> KEEP-ALIVE */
/* pipelining */
return -2;
} else if (len != (ssize_t) mem_len) {
/* we got less then expected, wait for the next fd-event */
con->is_readable = 0;
}
con->bytes_read += len;
#if 0
dump_packet(b->ptr, len);
#endif
return 0;
}
static void connection_handle_errdoc_init(connection *con) {
/* reset caching response headers potentially added by mod_expire */
data_string *ds;
@ -609,6 +351,12 @@ static int connection_handle_write_prepare(server *srv, connection *con) {
}
}
if (con->request.content_length
&& (off_t)con->request.content_length > con->request_content_queue->bytes_in) {
/* request body is present and has not been read completely */
con->keep_alive = 0;
}
if (con->request.http_method == HTTP_METHOD_HEAD) {
/**
* a HEAD request has the same as a GET
@ -899,11 +647,9 @@ int connection_reset(server *srv, connection *con) {
* we get called by the state-engine and by the fdevent-handler
*/
static int connection_handle_read_state(server *srv, connection *con) {
connection_state_t ostate = con->state;
chunk *c, *last_chunk;
off_t last_offset;
chunkqueue *cq = con->read_queue;
chunkqueue *dst_cq = con->request_content_queue;
int is_closed = 0; /* the connection got closed, if we don't have a complete header, -> error */
/* when in CON_STATE_READ: about to receive first byte for a request: */
int is_request_start = chunkqueue_is_empty(cq);
@ -927,8 +673,6 @@ static int connection_handle_read_state(server *srv, connection *con) {
/* we might have got several packets at once
*/
switch(ostate) {
case CON_STATE_READ:
/* update request_start timestamp when first byte of
* next request is received on a keep-alive connection */
if (con->request_count > 1 && is_request_start) con->request_start = srv->cur_ts;
@ -1009,33 +753,11 @@ found_header_end:
con->http_status = 414; /* Request-URI too large */
con->keep_alive = 0;
connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
} else if (is_closed) {
/* the connection got closed and we didn't got enough data to leave CON_STATE_READ;
* the only way is to leave here */
connection_set_state(srv, con, CON_STATE_ERROR);
}
break;
case CON_STATE_READ_POST:
if (con->request.content_length <= 64*1024) {
/* don't buffer request bodies <= 64k on disk */
chunkqueue_steal(dst_cq, cq, con->request.content_length - dst_cq->bytes_in);
}
else if (0 != chunkqueue_steal_with_tempfiles(srv, dst_cq, cq, con->request.content_length - dst_cq->bytes_in )) {
con->http_status = 413; /* Request-Entity too large */
con->keep_alive = 0;
connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
}
/* Content is ready */
if (dst_cq->bytes_in == (off_t)con->request.content_length) {
connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
}
break;
default: break;
}
/* the connection got closed and we didn't got enough data to leave one of the READ states
* the only way is to leave here */
if (is_closed && ostate == con->state) {
connection_set_state(srv, con, CON_STATE_ERROR);
}
chunkqueue_remove_finished_chunks(cq);
@ -1101,8 +823,7 @@ static handler_t connection_handle_fdevent(server *srv, void *context, int reven
}
}
if (con->state == CON_STATE_READ ||
con->state == CON_STATE_READ_POST) {
if (con->state == CON_STATE_READ) {
connection_handle_read_state(srv, con);
}
@ -1285,6 +1006,7 @@ int connection_state_machine(server *srv, connection *con) {
connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
break;
case CON_STATE_READ_POST:
case CON_STATE_HANDLE_REQUEST:
/*
* the request is parsed
@ -1325,8 +1047,6 @@ int connection_state_machine(server *srv, connection *con) {
con->in_error_handler = 1;
connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
done = -1;
break;
} else if (con->in_error_handler) {
@ -1349,16 +1069,12 @@ int connection_state_machine(server *srv, connection *con) {
fdwaitqueue_append(srv, con);
connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
break;
case HANDLER_COMEBACK:
done = -1;
/* fallthrough */
case HANDLER_WAIT_FOR_EVENT:
/* come back here */
connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
break;
case HANDLER_ERROR:
/* something went wrong */
@ -1494,7 +1210,6 @@ int connection_state_machine(server *srv, connection *con) {
}
break;
case CON_STATE_READ_POST:
case CON_STATE_READ:
if (srv->srvconf.log_state_handling) {
log_error_write(srv, __FILE__, __LINE__, "sds",
@ -1663,11 +1378,11 @@ int connection_state_machine(server *srv, connection *con) {
(con->traffic_limit_reached == 0)) {
fdevent_event_set(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT);
} else {
fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
fdevent_event_set(srv->ev, &(con->fde_ndx), con->fd, 0);
}
break;
default:
fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
fdevent_event_set(srv->ev, &(con->fde_ndx), con->fd, 0);
break;
}

@ -10,11 +10,12 @@ int connection_reset(server *srv, connection *con);
void connections_free(server *srv);
connection * connection_accept(server *srv, server_socket *srv_sock);
int connection_close(server *srv, connection *con);
int connection_set_state(server *srv, connection *con, connection_state_t state);
const char * connection_get_state(connection_state_t state);
const char * connection_get_short_state(connection_state_t state);
int connection_state_machine(server *srv, connection *con);
int connection_handle_read(server *srv, connection *con);
handler_t connection_handle_read_post_state(server *srv, connection *con);
#endif

@ -150,28 +150,25 @@ int fdevent_unregister(fdevents *ev, int fd) {
return 0;
}
int fdevent_event_del(fdevents *ev, int *fde_ndx, int fd) {
int fde = fde_ndx ? *fde_ndx : -1;
void fdevent_event_del(fdevents *ev, int *fde_ndx, int fd) {
if (-1 == fd) return;
if (NULL == ev->fdarray[fd]) return;
if (NULL == ev->fdarray[fd]) return 0;
if (ev->event_del) fde = ev->event_del(ev, fde, fd);
if (ev->event_del) *fde_ndx = ev->event_del(ev, *fde_ndx, fd);
ev->fdarray[fd]->events = 0;
if (fde_ndx) *fde_ndx = fde;
return 0;
}
int fdevent_event_set(fdevents *ev, int *fde_ndx, int fd, int events) {
int fde = fde_ndx ? *fde_ndx : -1;
void fdevent_event_set(fdevents *ev, int *fde_ndx, int fd, int events) {
if (-1 == fd) return;
if (ev->event_set) fde = ev->event_set(ev, fde, fd, events);
ev->fdarray[fd]->events = events;
if (fde_ndx) *fde_ndx = fde;
/*(Note: skips registering with kernel if initial events is 0,
* so caller should pass non-zero events for initial registration.
* If never registered due to never being called with non-zero events,
* then FDEVENT_HUP or FDEVENT_ERR will never be returned.) */
if (ev->fdarray[fd]->events == events) return;/*(no change; nothing to do)*/
return 0;
if (ev->event_set) *fde_ndx = ev->event_set(ev, *fde_ndx, fd, events);
ev->fdarray[fd]->events = events;
}
int fdevent_poll(fdevents *ev, int timeout_ms) {

@ -173,8 +173,8 @@ fdevents *fdevent_init(struct server *srv, size_t maxfds, fdevent_handler_t type
int fdevent_reset(fdevents *ev); /* "init" after fork() */
void fdevent_free(fdevents *ev);
int fdevent_event_set(fdevents *ev, int *fde_ndx, int fd, int events); /* events can be FDEVENT_IN, FDEVENT_OUT or FDEVENT_IN | FDEVENT_OUT */
int fdevent_event_del(fdevents *ev, int *fde_ndx, int fd);
void fdevent_event_set(fdevents *ev, int *fde_ndx, int fd, int events); /* events can be FDEVENT_IN, FDEVENT_OUT or FDEVENT_IN | FDEVENT_OUT */
void fdevent_event_del(fdevents *ev, int *fde_ndx, int fd);
int fdevent_event_get_revent(fdevents *ev, size_t ndx);
int fdevent_event_get_fd(fdevents *ev, size_t ndx);
fdevent_handler fdevent_get_handler(fdevents *ev, int fd);

@ -35,10 +35,6 @@
#include <stdio.h>
#include <fcntl.h>
#ifdef HAVE_SYS_FILIO_H
# include <sys/filio.h>
#endif
#include "version.h"
enum {EOL_UNSET, EOL_N, EOL_RN};
@ -76,7 +72,9 @@ typedef struct {
typedef struct {
pid_t pid;
int fd;
int fdtocgi;
int fde_ndx; /* index into the fd-event buffer */
int fde_ndx_tocgi; /* index into the fd-event buffer */
connection *remote_conn; /* dumb pointer */
plugin_data *plugin_data; /* dumb pointer */
@ -92,6 +90,8 @@ static handler_ctx * cgi_handler_ctx_init(void) {
hctx->response = buffer_init();
hctx->response_header = buffer_init();
hctx->fd = -1;
hctx->fdtocgi = -1;
return hctx;
}
@ -385,7 +385,6 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
/* send final chunk */
http_chunk_close(srv, con);
joblist_append(srv, con);
return FDEVENT_HANDLED_FINISHED;
}
@ -473,7 +472,6 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
}
http_chunk_append_buffer(srv, con, hctx->response_header);
joblist_append(srv, con);
} else {
const char *bstart;
size_t blen;
@ -507,7 +505,6 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
if (blen > 0) {
http_chunk_append_mem(srv, con, bstart, blen);
joblist_append(srv, con);
}
}
@ -515,7 +512,6 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
}
} else {
http_chunk_append_buffer(srv, con, hctx->response);
joblist_append(srv, con);
}
#if 0
@ -526,7 +522,18 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) {
return FDEVENT_HANDLED_NOT_FINISHED;
}
static handler_t cgi_connection_close(server *srv, handler_ctx *hctx) {
static void cgi_connection_close_fdtocgi(server *srv, handler_ctx *hctx) {
/*(closes only hctx->fdtocgi)*/
fdevent_event_del(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi);
fdevent_unregister(srv->ev, hctx->fdtocgi);
if (close(hctx->fdtocgi)) {
log_error_write(srv, __FILE__, __LINE__, "sds", "cgi stdin close failed ", hctx->fdtocgi, strerror(errno));
}
hctx->fdtocgi = -1;
}
static void cgi_connection_close(server *srv, handler_ctx *hctx) {
int status;
pid_t pid;
plugin_data *p = hctx->plugin_data;
@ -548,16 +555,16 @@ static handler_t cgi_connection_close(server *srv, handler_ctx *hctx) {
if (close(hctx->fd)) {
log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno));
}
}
hctx->fd = -1;
hctx->fde_ndx = -1;
if (hctx->fdtocgi != -1) {
cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/
}
pid = hctx->pid;
con->plugin_ctx[p->id] = NULL;
/* is this a good idea ? */
cgi_handler_ctx_free(hctx);
/* if waitpid hasn't been called by response.c yet, do it here */
@ -612,7 +619,9 @@ static handler_t cgi_connection_close(server *srv, handler_ctx *hctx) {
}
#endif
if (con->state == CON_STATE_HANDLE_REQUEST) {
/* finish response (if not already finished) */
if (con->mode == p->id
&& (con->state == CON_STATE_HANDLE_REQUEST || con->state == CON_STATE_READ_POST)) {
/* (not CON_STATE_ERROR and not CON_STATE_RESPONSE_END,
* i.e. not called from cgi_connection_close_callback()) */
@ -625,18 +634,51 @@ static handler_t cgi_connection_close(server *srv, handler_ctx *hctx) {
con->file_finished = 1;
}
}
return HANDLER_GO_ON;
}
static handler_t cgi_connection_close_callback(server *srv, connection *con, void *p_d) {
plugin_data *p = p_d;
handler_ctx *hctx = con->plugin_ctx[p->id];
if (hctx) cgi_connection_close(srv, hctx);
if (con->mode != p->id) return HANDLER_GO_ON;
if (NULL == hctx) return HANDLER_GO_ON;
return HANDLER_GO_ON;
}
static int cgi_write_request(server *srv, handler_ctx *hctx, int fd);
static handler_t cgi_handle_fdevent_send (server *srv, void *ctx, int revents) {
handler_ctx *hctx = ctx;
connection *con = hctx->remote_conn;
/*(joblist only actually necessary here in mod_cgi fdevent send if returning HANDLER_ERROR)*/
joblist_append(srv, con);
if (revents & FDEVENT_OUT) {
if (0 != cgi_write_request(srv, hctx, hctx->fdtocgi)) {
cgi_connection_close(srv, hctx);
return HANDLER_ERROR;
}
/* more request body to be sent to CGI */
}
if (revents & FDEVENT_HUP) {
/* skip sending remaining data to CGI */
chunkqueue *cq = con->request_content_queue;
chunkqueue_mark_written(cq, chunkqueue_length(cq));
cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/
} else if (revents & FDEVENT_ERR) {
/* kill all connections to the cgi process */
#if 1
log_error_write(srv, __FILE__, __LINE__, "s", "cgi-FDEVENT_ERR");
#endif
cgi_connection_close(srv, hctx);
return HANDLER_ERROR;
}
return cgi_connection_close(srv, hctx);
return HANDLER_FINISHED;
}
@ -733,7 +775,7 @@ static int cgi_env_add(char_array *env, const char *key, size_t key_len, const c
*
* Also always use mmap; the files are "trusted", as we created them.
*/
static int cgi_write_file_chunk_mmap(server *srv, connection *con, int fd, chunkqueue *cq) {
static ssize_t cgi_write_file_chunk_mmap(server *srv, connection *con, int fd, chunkqueue *cq) {
chunk* const c = cq->first;
off_t offset, toSend, file_end;
ssize_t r;
@ -802,10 +844,93 @@ static int cgi_write_file_chunk_mmap(server *srv, connection *con, int fd, chunk
chunkqueue_mark_written(cq, r);
}
return r;
}
static int cgi_write_request(server *srv, handler_ctx *hctx, int fd) {
connection *con = hctx->remote_conn;
chunkqueue *cq = con->request_content_queue;
chunk *c;
/* old comment: windows doesn't support select() on pipes - wouldn't be easy to fix for all platforms.
* solution: if this is still a problem on windows, then substitute
* socketpair() for pipe() and closesocket() for close() on windows.
*/
for (c = cq->first; c; c = cq->first) {
ssize_t r = -1;
switch(c->type) {
case FILE_CHUNK:
r = cgi_write_file_chunk_mmap(srv, con, fd, cq);
break;
case MEM_CHUNK:
if ((r = write(fd, c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) {
switch(errno) {
case EAGAIN:
case EINTR:
/* ignore and try again */
r = 0;
break;
case EPIPE:
case ECONNRESET:
/* connection closed */
r = -2;
break;
default:
/* fatal error */
log_error_write(srv, __FILE__, __LINE__, "ss", "write failed due to: ", strerror(errno));
r = -1;
break;
}
} else if (r > 0) {
chunkqueue_mark_written(cq, r);
}
break;
}
if (0 == r) break; /*(might block)*/
switch (r) {
case -1:
/* fatal error */
return -1;
case -2:
/* connection reset */
log_error_write(srv, __FILE__, __LINE__, "s", "failed to send post data to cgi, connection closed by CGI");
/* skip all remaining data */
chunkqueue_mark_written(cq, chunkqueue_length(cq));
break;
default:
break;
}
}
if (chunkqueue_is_empty(cq)) {
/* sent all request body input */
/* close connection to the cgi-script */
if (-1 == hctx->fdtocgi) { /*(entire request body sent in initial send to pipe buffer)*/
if (close(fd)) {
log_error_write(srv, __FILE__, __LINE__, "sds", "cgi stdin close failed ", fd, strerror(errno));
}
} else {
cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/
}
} else {
/* more request body remains to be sent to CGI so register for fdevents */
if (-1 == hctx->fdtocgi) { /*(not registered yet)*/
hctx->fdtocgi = fd;
hctx->fde_ndx_tocgi = -1;
fdevent_register(srv->ev, hctx->fdtocgi, cgi_handle_fdevent_send, hctx);
fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, FDEVENT_OUT);
}
}
return 0;
}
static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *cgi_handler) {
static int cgi_create_env(server *srv, connection *con, plugin_data *p, handler_ctx *hctx, buffer *cgi_handler) {
pid_t pid;
#ifdef HAVE_IPV6
@ -1103,96 +1228,35 @@ static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *
close(to_cgi_fds[1]);
return -1;
default: {
handler_ctx *hctx;
/* parent proces */
/* parent process */
close(from_cgi_fds[1]);
close(to_cgi_fds[0]);
if (con->request.content_length) {
chunkqueue *cq = con->request_content_queue;
chunk *c;
/* register PID and wait for them asynchronously */
assert(chunkqueue_length(cq) == (off_t)con->request.content_length);
/* NOTE: yes, this is synchronous sending of CGI post data;
* if you need something asynchronous (recommended with large
* request bodies), use mod_fastcgi + fcgi-cgi.
*
* Also: windows doesn't support select() on pipes - wouldn't be
* easy to fix for all platforms.
*/
hctx->pid = pid;
hctx->fd = from_cgi_fds[0];
hctx->fde_ndx = -1;
if (0 == con->request.content_length) {
close(to_cgi_fds[1]);
} else {
/* there is content to send */
for (c = cq->first; c; c = cq->first) {
int r = -1;
switch(c->type) {
case FILE_CHUNK:
r = cgi_write_file_chunk_mmap(srv, con, to_cgi_fds[1], cq);
break;
case MEM_CHUNK:
if ((r = write(to_cgi_fds[1], c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) {
switch(errno) {
case EAGAIN:
case EINTR:
/* ignore and try again */
r = 0;
break;
case EPIPE:
case ECONNRESET:
/* connection closed */
r = -2;
break;
default:
/* fatal error */
log_error_write(srv, __FILE__, __LINE__, "ss", "write failed due to: ", strerror(errno));
r = -1;
break;
}
} else if (r > 0) {
chunkqueue_mark_written(cq, r);
}
break;
}
if (-1 == fdevent_fcntl_set(srv->ev, to_cgi_fds[1])) {
log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
close(to_cgi_fds[1]);
cgi_connection_close(srv, hctx);
return -1;
}
switch (r) {
case -1:
/* fatal error */
close(from_cgi_fds[0]);
close(to_cgi_fds[1]);
kill(pid, SIGTERM);
cgi_pid_add(srv, p, pid);
return -1;
case -2:
/* connection reset */
log_error_write(srv, __FILE__, __LINE__, "s", "failed to send post data to cgi, connection closed by CGI");
/* skip all remaining data */
chunkqueue_mark_written(cq, chunkqueue_length(cq));
break;
default:
break;
}
if (0 != cgi_write_request(srv, hctx, to_cgi_fds[1])) {
close(to_cgi_fds[1]);
cgi_connection_close(srv, hctx);
return -1;
}
}
close(to_cgi_fds[1]);
/* register PID and wait for them asyncronously */
con->mode = p->id;
buffer_reset(con->physical.path);
hctx = cgi_handler_ctx_init();
hctx->remote_conn = con;
hctx->plugin_data = p;
hctx->pid = pid;
hctx->fd = from_cgi_fds[0];
hctx->fde_ndx = -1;
con->plugin_ctx[p->id] = hctx;
fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx);
fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
@ -1212,6 +1276,23 @@ static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *
#endif
}
static buffer * cgi_get_handler(array *a, buffer *fn) {
size_t k, s_len = buffer_string_length(fn);
for (k = 0; k < a->used; ++k) {
data_string *ds = (data_string *)a->data[k];
size_t ct_len = buffer_string_length(ds->key);
if (buffer_is_empty(ds->key)) continue;
if (s_len < ct_len) continue;
if (0 == strncmp(fn->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {
return ds->value;
}
}
return NULL;
}
#define PATCH(x) \
p->conf.x = s->x;
static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p) {
@ -1246,7 +1327,6 @@ static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p
#undef PATCH
URIHANDLER_FUNC(cgi_is_handled) {
size_t k, s_len;
plugin_data *p = p_d;
buffer *fn = con->physical.path;
stat_cache_entry *sce = NULL;
@ -1261,26 +1341,12 @@ URIHANDLER_FUNC(cgi_is_handled) {
if (!S_ISREG(sce->st.st_mode)) return HANDLER_GO_ON;
if (p->conf.execute_x_only == 1 && (sce->st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) return HANDLER_GO_ON;
s_len = buffer_string_length(fn);
for (k = 0; k < p->conf.cgi->used; k++) {
data_string *ds = (data_string *)p->conf.cgi->data[k];
size_t ct_len = buffer_string_length(ds->key);
if (buffer_is_empty(ds->key)) continue;
if (s_len < ct_len) continue;
if (0 == strncmp(fn->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {
if (cgi_create_env(srv, con, p, ds->value)) {
con->mode = DIRECT;
con->http_status = 500;
buffer_reset(con->physical.path);
return HANDLER_FINISHED;
}
/* one handler is enough for the request */
break;
}
if (NULL != cgi_get_handler(p->conf.cgi, fn)) {
handler_ctx *hctx = cgi_handler_ctx_init();
hctx->remote_conn = con;
hctx->plugin_data = p;
con->plugin_ctx[p->id] = hctx;
con->mode = p->id;
}
return HANDLER_GO_ON;
@ -1351,11 +1417,26 @@ TRIGGER_FUNC(cgi_trigger) {
SUBREQUEST_FUNC(mod_cgi_handle_subrequest) {
plugin_data *p = p_d;
handler_ctx *hctx = con->plugin_ctx[p->id];
UNUSED(srv);
if (con->mode != p->id) return HANDLER_GO_ON;
if (NULL == hctx) return HANDLER_GO_ON;
if (con->state == CON_STATE_READ_POST) {
handler_t r = connection_handle_read_post_state(srv, con);
if (r != HANDLER_GO_ON) return r;
}
if (-1 == hctx->fd) {
buffer *handler = cgi_get_handler(p->conf.cgi, con->physical.path);
if (!handler) return HANDLER_GO_ON; /*(should not happen; checked in cgi_is_handled())*/
if (cgi_create_env(srv, con, p, hctx, handler)) {
con->http_status = 500;
con->mode = DIRECT;
return HANDLER_FINISHED;
}
}
#if 0
log_error_write(srv, __FILE__, __LINE__, "sdd", "subrequest, pid =", hctx, hctx->pid);
#endif

@ -39,10 +39,6 @@
#include <stdio.h>
#ifdef HAVE_SYS_FILIO_H
# include <sys/filio.h>
#endif
#include "sys-socket.h"
#ifdef HAVE_SYS_UIO_H
@ -325,7 +321,6 @@ typedef struct {
/* connection specific data */
typedef enum {
FCGI_STATE_UNSET,
FCGI_STATE_INIT,
FCGI_STATE_CONNECT_DELAYED,
FCGI_STATE_PREPARE_WRITE,
@ -1089,7 +1084,7 @@ static int fcgi_spawn_connection(server *srv,
/* log_error_write(srv, __FILE__, __LINE__, "sbs",
"execve failed for:", host->bin_path, strerror(errno)); */
exit(errno);
_exit(errno);
break;
}
@ -1517,8 +1512,6 @@ static void fcgi_connection_close(server *srv, handler_ctx *hctx) {
plugin_data *p;
connection *con;
if (NULL == hctx) return;
p = hctx->plugin_data;
con = hctx->remote_conn;
@ -1547,6 +1540,23 @@ static void fcgi_connection_close(server *srv, handler_ctx *hctx) {
handler_ctx_free(srv, hctx);
con->plugin_ctx[p->id] = NULL;
/* finish response (if not already finished) */
if (con->mode == p->id
&& (con->state == CON_STATE_HANDLE_REQUEST || con->state == CON_STATE_READ_POST)) {
/* (not CON_STATE_ERROR and not CON_STATE_RESPONSE_END,
* i.e. not called from fcgi_connection_reset()) */
/* Send an error if we haven't sent any data yet */
if (0 == con->file_started) {
con->http_status = 500;
con->mode = DIRECT;
}
else if (!con->file_finished) {
http_chunk_close(srv, con);
con->file_finished = 1;
}
}
}
static int fcgi_reconnect(server *srv, handler_ctx *hctx) {
@ -1609,8 +1619,8 @@ static int fcgi_reconnect(server *srv, handler_ctx *hctx) {
static handler_t fcgi_connection_reset(server *srv, connection *con, void *p_d) {
plugin_data *p = p_d;
fcgi_connection_close(srv, con->plugin_ctx[p->id]);
handler_ctx *hctx = con->plugin_ctx[p->id];
if (hctx) fcgi_connection_close(srv, hctx);
return HANDLER_GO_ON;
}
@ -2521,7 +2531,6 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) {
con->parsed_response &= ~HTTP_CONTENT_LENGTH;
con->response.content_length = -1;
hctx->send_content_body = 0; /* ignore the content */
joblist_append(srv, con);
} else {
log_error_write(srv, __FILE__, __LINE__, "sb",
"send-file error: couldn't get stat_cache entry for:",
@ -2542,7 +2551,6 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) {
}
http_chunk_append_buffer(srv, con, packet.b);
joblist_append(srv, con);
}