1
0
mirror of https://github.com/graygnuorg/pound.git synced 2026-10-28 11:35:07 +03:00
Files
pound/http.c
Sergey Poznyakoff 3f9050bef9 Minor cleanup
* configure.ac: New subst variables SSL_CPPFLAGS and SSL_LDFLAGS.
Avoid modifying CFLAGS directly, unless absolutely necessary.
* Makefile.am: Use SSL_CPPFLAGS and SSL_LDFLAGS
* config.c (get_subjectaltnames): Remove useless memory allocation.
(IMPLEMENT_LHASH_HASH_FN,IMPLEMENT_LHASH_COMP_FN): Not needed
for OpenSSL version 1.1
* http.c: Remove declarations of unused variables.
* pound.c (l_init, l_id, l_lock): Not used in OpenSSL 1.1. Protect
with #if.
Remove declarations of unused variables.
* pound.h (get_thr_qlen): Use explicit return type.
* poundctl.c: Remove declarations of unused variables.
* svc.c: Likewise.
2018-03-02 12:24:21 +00:00

1699 lines
64 KiB
C

/*
* Pound - the reverse-proxy load-balancer
* Copyright (C) 2002-2010 Apsis GmbH
*
* This file is part of Pound.
*
* Pound is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Pound is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Contact information:
* Apsis GmbH
* P.O.Box
* 8707 Uetikon am See
* Switzerland
* EMail: roseg@apsis.ch
*/
#include "pound.h"
/* HTTP error replies */
static char *h500 = "500 Internal Server Error",
*h501 = "501 Not Implemented",
*h503 = "503 Service Unavailable",
*h400 = "Bad Request";
static char *err_response = "HTTP/1.0 %s\r\nContent-Type: text/html\r\nContent-Length: %d\r\nExpires: now\r\nPragma: no-cache\r\nCache-control: no-cache,no-store\r\n\r\n%s";
/*
* Reply with an error
*/
static void
err_reply(BIO *const c, const char *head, const char *txt)
{
BIO_printf(c, err_response, head, strlen(txt), txt);
BIO_flush(c);
return;
}
/*
* Reply with a redirect
*/
static void
redirect_reply(BIO *const c, const char *url, const int code)
{
char rep[MAXBUF], cont[MAXBUF], safe_url[MAXBUF], *code_msg;
int i, j;
switch(code) {
case 301:
code_msg = "Moved Permanently";
break;
case 307:
code_msg = "Temporary Redirect";
break;
default:
code_msg = "Found";
break;
}
/*
* Make sure to return a safe version of the URL (otherwise CSRF becomes a possibility)
*/
memset(safe_url, 0, MAXBUF);
for(i = j = 0; i < MAXBUF && j < MAXBUF && url[i]; i++)
if(isalnum(url[i]) || url[i] == '_' || url[i] == '.' || url[i] == ':' || url[i] == '/'
|| url[i] == '?' || url[i] == '&' || url[i] == ';' || url[i] == '-' || url[i] == '=')
safe_url[j++] = url[i];
else {
sprintf(safe_url + j, "%%%02x", url[i]);
j += 3;
}
snprintf(cont, sizeof(cont),
"<html><head><title>Redirect</title></head><body><h1>Redirect</h1><p>You should go to <a href=\"%s\">%s</a></p></body></html>",
safe_url, safe_url);
snprintf(rep, sizeof(rep),
"HTTP/1.0 %d %s\r\nLocation: %s\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n",
code, code_msg, safe_url, (int)strlen(cont));
BIO_write(c, rep, strlen(rep));
BIO_write(c, cont, strlen(cont));
BIO_flush(c);
return;
}
/*
* Read and write some binary data
*/
static int
copy_bin(BIO *const cl, BIO *const be, LONG cont, LONG *res_bytes, const int no_write)
{
char buf[MAXBUF];
int res;
while(cont > L0) {
if((res = BIO_read(cl, buf, cont > MAXBUF? MAXBUF: cont)) < 0)
return -1;
else if(res == 0)
return -2;
if(!no_write)
if(BIO_write(be, buf, res) != res)
return -3;
cont -= res;
if(res_bytes)
*res_bytes += res;
}
if(!no_write)
if(BIO_flush(be) != 1)
return -4;
return 0;
}
/*
* Get a "line" from a BIO, strip the trailing newline, skip the input stream if buffer too small
* The result buffer is NULL terminated
* Return 0 on success
*/
static int
get_line(BIO *const in, char *const buf, const int bufsize)
{
char tmp;
int i, seen_cr;
memset(buf, 0, bufsize);
for(i = 0, seen_cr = 0; i < bufsize - 1; i++)
switch(BIO_read(in, &tmp, 1)) {
case -2:
/* BIO_gets not implemented */
return -1;
case 0:
case -1:
return 1;
default:
if(seen_cr)
if(tmp != '\n') {
/* we have CR not followed by NL */
do {
if(BIO_read(in, &tmp, 1) < 0)
return 1;
} while(tmp != '\n');
return 1;
} else {
buf[i - 1] = '\0';
return 0;
}
if(!iscntrl(tmp) || tmp == '\t') {
buf[i] = tmp;
continue;
}
if(tmp == '\r') {
seen_cr = 1;
continue;
}
if(tmp == '\n') {
/* line ends in NL only (no CR) */
buf[i] = 0;
return 0;
}
/* all other control characters cause an error */
do {
if(BIO_read(in, &tmp, 1) < 0)
return 1;
} while(tmp != '\n');
return 1;
}
/* line too long */
do {
if(BIO_read(in, &tmp, 1) < 0)
return 1;
} while(tmp != '\n');
return 1;
}
/*
* Strip trailing CRLF
*/
static int
strip_eol(char *lin)
{
while(*lin)
if(*lin == '\n' || (*lin == '\r' && *(lin + 1) == '\n')) {
*lin = '\0';
return 1;
} else
lin++;
return 0;
}
/*
* Copy chunked
*/
static int
copy_chunks(BIO *const cl, BIO *const be, LONG *res_bytes, const int no_write, const LONG max_size)
{
char buf[MAXBUF];
LONG cont, tot_size;
regmatch_t matches[2];
int res;
for(tot_size = 0L;;) {
if((res = get_line(cl, buf, MAXBUF)) < 0) {
logmsg(LOG_NOTICE, "(%lx) chunked read error: %s", pthread_self(), strerror(errno));
return -1;
} else if(res > 0)
/* EOF */
return 0;
if(!regexec(&CHUNK_HEAD, buf, 2, matches, 0))
cont = STRTOL(buf, NULL, 16);
else {
/* not chunk header */
logmsg(LOG_NOTICE, "(%lx) bad chunk header <%s>: %s", pthread_self(), buf, strerror(errno));
return -2;
}
if(!no_write)
if(BIO_printf(be, "%s\r\n", buf) <= 0) {
logmsg(LOG_NOTICE, "(%lx) error write chunked: %s", pthread_self(), strerror(errno));
return -3;
}
tot_size += cont;
if(max_size > L0 && tot_size > max_size) {
logmsg(LOG_WARNING, "(%lx) chunk content too large", pthread_self);
return -4;
}
if(cont > L0) {
if(copy_bin(cl, be, cont, res_bytes, no_write)) {
if(errno)
logmsg(LOG_NOTICE, "(%lx) error copy chunk cont: %s", pthread_self(), strerror(errno));
return -4;
}
} else
break;
/* final CRLF */
if((res = get_line(cl, buf, MAXBUF)) < 0) {
logmsg(LOG_NOTICE, "(%lx) error after chunk: %s", pthread_self(), strerror(errno));
return -5;
} else if(res > 0) {
logmsg(LOG_NOTICE, "(%lx) unexpected EOF after chunk", pthread_self());
return -5;
}
if(buf[0])
logmsg(LOG_NOTICE, "(%lx) unexpected after chunk \"%s\"", pthread_self(), buf);
if(!no_write)
if(BIO_printf(be, "%s\r\n", buf) <= 0) {
logmsg(LOG_NOTICE, "(%lx) error after chunk write: %s", pthread_self(), strerror(errno));
return -6;
}
}
/* possibly trailing headers */
for(;;) {
if((res = get_line(cl, buf, MAXBUF)) < 0) {
logmsg(LOG_NOTICE, "(%lx) error post-chunk: %s", pthread_self(), strerror(errno));
return -7;
} else if(res > 0)
break;
if(!no_write)
if(BIO_printf(be, "%s\r\n", buf) <= 0) {
logmsg(LOG_NOTICE, "(%lx) error post-chunk write: %s", pthread_self(), strerror(errno));
return -8;
}
if(!buf[0])
break;
}
if(!no_write)
if(BIO_flush(be) != 1) {
logmsg(LOG_NOTICE, "(%lx) copy_chunks flush error: %s", pthread_self(), strerror(errno));
return -4;
}
return 0;
}
static int err_to = -1;
typedef struct {
int timeout;
RENEG_STATE *reneg_state;
} BIO_ARG;
/*
* Time-out for client read/gets
* the SSL manual says not to do it, but it works well enough anyway...
*/
static long
bio_callback(BIO *const bio, const int cmd, const char *argp, int argi, long argl, long ret)
{
BIO_ARG *bio_arg;
struct pollfd p;
int to, p_res, p_err;
if(cmd != BIO_CB_READ && cmd != BIO_CB_WRITE)
return ret;
/* a time-out already occured */
if((bio_arg = (BIO_ARG*)BIO_get_callback_arg(bio))==NULL)
return ret;
if((to = bio_arg->timeout * 1000) < 0) {
errno = ETIMEDOUT;
return -1;
}
/* Renegotiations */
/* logmsg(LOG_NOTICE, "RENEG STATE %d", bio_arg->reneg_state==NULL?-1:*bio_arg->reneg_state); */
if (bio_arg->reneg_state != NULL && *bio_arg->reneg_state == RENEG_ABORT) {
logmsg(LOG_NOTICE, "REJECTING renegotiated session");
errno = ECONNABORTED;
return -1;
}
if (to == 0)
return ret;
for(;;) {
memset(&p, 0, sizeof(p));
BIO_get_fd(bio, &p.fd);
p.events = (cmd == BIO_CB_READ)? (POLLIN | POLLPRI): POLLOUT;
p_res = poll(&p, 1, to);
p_err = errno;
switch(p_res) {
case 1:
if(cmd == BIO_CB_READ) {
if((p.revents & POLLIN) || (p.revents & POLLPRI))
/* there is readable data */
return ret;
else {
#ifdef EBUG
logmsg(LOG_WARNING, "(%lx) CALLBACK read 0x%04x poll: %s",
pthread_self(), p.revents, strerror(p_err));
#endif
errno = EIO;
}
} else {
if(p.revents & POLLOUT)
/* data can be written */
return ret;
else {
#ifdef EBUG
logmsg(LOG_WARNING, "(%lx) CALLBACK write 0x%04x poll: %s",
pthread_self(), p.revents, strerror(p_err));
#endif
errno = ECONNRESET;
}
}
return -1;
case 0:
/* timeout - mark the BIO as unusable for the future */
bio_arg->timeout = err_to;
#ifdef EBUG
logmsg(LOG_WARNING, "(%lx) CALLBACK timeout poll after %d secs: %s",
pthread_self(), to / 1000, strerror(p_err));
#endif
errno = ETIMEDOUT;
return 0;
default:
/* error */
if(p_err != EINTR) {
#ifdef EBUG
logmsg(LOG_WARNING, "(%lx) CALLBACK bad %d poll: %s",
pthread_self(), p_res, strerror(p_err));
#endif
return -2;
#ifdef EBUG
} else
logmsg(LOG_WARNING, "(%lx) CALLBACK interrupted %d poll: %s",
pthread_self(), p_res, strerror(p_err));
#else
}
#endif
}
}
}
/*
* Check if the file underlying a BIO is readable
*/
static int
is_readable(BIO *const bio, const int to_wait)
{
struct pollfd p;
if(BIO_pending(bio) > 0)
return 1;
memset(&p, 0, sizeof(p));
BIO_get_fd(bio, &p.fd);
p.events = POLLIN | POLLPRI;
return (poll(&p, 1, to_wait * 1000) > 0);
}
static void
free_headers(char **headers)
{
int i;
for(i = 0; i < MAXHEADERS; i++)
if(headers[i])
free(headers[i]);
free(headers);
return;
}
static char **
get_headers(BIO *const in, BIO *const cl, const LISTENER *lstn)
{
char **headers, buf[MAXBUF];
int res, n;
/* HTTP/1.1 allows leading CRLF */
memset(buf, 0, MAXBUF);
while((res = get_line(in, buf, MAXBUF)) == 0)
if(buf[0])
break;
if(res < 0) {
/* this is expected to occur only on client reads */
/* logmsg(LOG_NOTICE, "headers: bad starting read"); */
return NULL;
}
if((headers = (char **)calloc(MAXHEADERS, sizeof(char *))) == NULL) {
logmsg(LOG_WARNING, "(%lx) e500 headers: out of memory", pthread_self());
err_reply(cl, h500, lstn->err500);
return NULL;
}
if((headers[0] = (char *)malloc(MAXBUF)) == NULL) {
free_headers(headers);
logmsg(LOG_WARNING, "(%lx) e500 header: out of memory", pthread_self());
err_reply(cl, h500, lstn->err500);
return NULL;
}
memset(headers[0], 0, MAXBUF);
strncpy(headers[0], buf, MAXBUF - 1);
for(n = 1; n < MAXHEADERS; n++) {
if(get_line(in, buf, MAXBUF)) {
free_headers(headers);
/* this is not necessarily an error, EOF/timeout are possible
logmsg(LOG_WARNING, "(%lx) e500 can't read header", pthread_self());
err_reply(cl, h500, lstn->err500);
*/
return NULL;
}
if(!buf[0])
return headers;
if((headers[n] = (char *)malloc(MAXBUF)) == NULL) {
free_headers(headers);
logmsg(LOG_WARNING, "(%lx) e500 header: out of memory", pthread_self());
err_reply(cl, h500, lstn->err500);
return NULL;
}
memset(headers[n], 0, MAXBUF);
strncpy(headers[n], buf, MAXBUF - 1);
}
free_headers(headers);
logmsg(LOG_NOTICE, "(%lx) e500 too many headers", pthread_self());
err_reply(cl, h500, lstn->err500);
return NULL;
}
#define LOG_TIME_SIZE 32
/*
* Apache log-file-style time format
*/
static void
log_time(char *res)
{
time_t now;
struct tm *t_now, t_res;
now = time(NULL);
#ifdef HAVE_LOCALTIME_R
t_now = localtime_r(&now, &t_res);
#else
t_now = localtime(&now);
#endif
strftime(res, LOG_TIME_SIZE - 1, "%d/%b/%Y:%H:%M:%S %z", t_now);
return;
}
static double
cur_time(void)
{
#ifdef HAVE_GETTIMEOFDAY
struct timeval tv;
struct timezone tz;
int sv_errno;
sv_errno = errno;
gettimeofday(&tv, &tz);
errno = sv_errno;
return tv.tv_sec * 1000000.0 + tv.tv_usec;
#else
return time(NULL) * 1000000.0;
#endif
}
#define LOG_BYTES_SIZE 32
/*
* Apache log-file-style number format
*/
static void
log_bytes(char *res, const LONG cnt)
{
if(cnt > L0)
#ifdef HAVE_LONG_LONG_INT
snprintf(res, LOG_BYTES_SIZE - 1, "%lld", cnt);
#else
snprintf(res, LOG_BYTES_SIZE - 1, "%ld", cnt);
#endif
else
strcpy(res, "-");
return;
}
/* Cleanup code. This should really be in the pthread_cleanup_push, except for bugs in some implementations */
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
# define clear_error()
#elif OPENSSL_VERSION_NUMBER >= 0x10000000L
# define clear_error() \
if(ssl != NULL) { ERR_clear_error(); ERR_remove_thread_state(NULL); }
#else
# define clear_error() \
if(ssl != NULL) { ERR_clear_error(); ERR_remove_state(0); }
#endif
#define clean_all() { \
if(ssl != NULL) { BIO_ssl_shutdown(cl); } \
if(be != NULL) { BIO_flush(be); BIO_reset(be); BIO_free_all(be); be = NULL; } \
if(cl != NULL) { BIO_flush(cl); BIO_reset(cl); BIO_free_all(cl); cl = NULL; } \
if(x509 != NULL) { X509_free(x509); x509 = NULL; } \
clear_error(); \
}
/*
* handle an HTTP request
*/
void
do_http(thr_arg *arg)
{
int cl_11, be_11, res, chunked, n, sock, no_cont, skip, conn_closed, force_10, sock_proto, is_rpc;
LISTENER *lstn;
SERVICE *svc;
BACKEND *backend, *cur_backend, *old_backend;
struct addrinfo from_host, z_addr;
struct sockaddr_storage from_host_addr;
BIO *cl, *be, *bb, *b64;
X509 *x509;
char request[MAXBUF], response[MAXBUF], buf[MAXBUF], url[MAXBUF], loc_path[MAXBUF], **headers,
headers_ok[MAXHEADERS], v_host[MAXBUF], referer[MAXBUF], u_agent[MAXBUF], u_name[MAXBUF],
caddr[MAXBUF], req_time[LOG_TIME_SIZE], s_res_bytes[LOG_BYTES_SIZE], *mh;
SSL *ssl, *be_ssl;
LONG cont, res_bytes;
regmatch_t matches[4];
struct linger l;
double start_req, end_req;
RENEG_STATE reneg_state;
BIO_ARG ba1, ba2;
reneg_state = RENEG_INIT;
ba1.reneg_state = &reneg_state;
ba2.reneg_state = &reneg_state;
ba1.timeout = 0;
ba2.timeout = 0;
from_host = ((thr_arg *)arg)->from_host;
memcpy(&from_host_addr, from_host.ai_addr, from_host.ai_addrlen);
from_host.ai_addr = (struct sockaddr *)&from_host_addr;
lstn = ((thr_arg *)arg)->lstn;
sock = ((thr_arg *)arg)->sock;
free(((thr_arg *)arg)->from_host.ai_addr);
free(arg);
if(lstn->allow_client_reneg)
reneg_state = RENEG_ALLOW;
n = 1;
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&n, sizeof(n));
l.l_onoff = 1;
l.l_linger = 10;
setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&l, sizeof(l));
#ifdef TCP_LINGER2
n = 5;
setsockopt(sock, SOL_TCP, TCP_LINGER2, (void *)&n, sizeof(n));
#endif
n = 1;
setsockopt(sock, SOL_TCP, TCP_NODELAY, (void *)&n, sizeof(n));
cl = NULL;
be = NULL;
ssl = NULL;
x509 = NULL;
if((cl = BIO_new_socket(sock, 1)) == NULL) {
logmsg(LOG_WARNING, "(%lx) BIO_new_socket failed", pthread_self());
shutdown(sock, 2);
close(sock);
return;
}
ba1.timeout = lstn->to;
BIO_set_callback_arg(cl, (char *)&ba1);
BIO_set_callback(cl, bio_callback);
if(lstn->ctx != NULL) {
if((ssl = SSL_new(lstn->ctx->ctx)) == NULL) {
logmsg(LOG_WARNING, "(%lx) SSL_new: failed", pthread_self());
BIO_reset(cl);
BIO_free_all(cl);
return;
}
SSL_set_app_data(ssl, &reneg_state);
SSL_set_bio(ssl, cl, cl);
if((bb = BIO_new(BIO_f_ssl())) == NULL) {
logmsg(LOG_WARNING, "(%lx) BIO_new(Bio_f_ssl()) failed", pthread_self());
BIO_reset(cl);
BIO_free_all(cl);
return;
}
BIO_set_ssl(bb, ssl, BIO_CLOSE);
BIO_set_ssl_mode(bb, 0);
cl = bb;
if(BIO_do_handshake(cl) <= 0) {
/* no need to log every client without a certificate...
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "BIO_do_handshake with %s failed: %s", caddr,
ERR_error_string(ERR_get_error(), NULL));
x509 = NULL;
*/
BIO_reset(cl);
BIO_free_all(cl);
return;
} else {
if((x509 = SSL_get_peer_certificate(ssl)) != NULL && lstn->clnt_check < 3
&& SSL_get_verify_result(ssl) != X509_V_OK) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "Bad certificate from %s", caddr);
X509_free(x509);
BIO_reset(cl);
BIO_free_all(cl);
return;
}
}
} else {
x509 = NULL;
}
cur_backend = NULL;
if((bb = BIO_new(BIO_f_buffer())) == NULL) {
logmsg(LOG_WARNING, "(%lx) BIO_new(buffer) failed", pthread_self());
if(x509 != NULL)
X509_free(x509);
BIO_reset(cl);
BIO_free_all(cl);
return;
}
BIO_set_close(cl, BIO_CLOSE);
BIO_set_buffer_size(cl, MAXBUF);
cl = BIO_push(bb, cl);
for(cl_11 = be_11 = 0;;) {
res_bytes = L0;
is_rpc = -1;
v_host[0] = referer[0] = u_agent[0] = u_name[0] = '\0';
conn_closed = 0;
for(n = 0; n < MAXHEADERS; n++)
headers_ok[n] = 1;
if((headers = get_headers(cl, cl, lstn)) == NULL) {
if(!cl_11) {
if(errno) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) error read from %s: %s", pthread_self(), caddr, strerror(errno));
/* err_reply(cl, h500, lstn->err500); */
}
}
clean_all();
return;
}
memset(req_time, 0, LOG_TIME_SIZE);
start_req = cur_time();
log_time(req_time);
/* check for correct request */
strncpy(request, headers[0], MAXBUF);
if(!regexec(&lstn->verb, request, 3, matches, 0)) {
no_cont = !strncasecmp(request + matches[1].rm_so, "HEAD", matches[1].rm_eo - matches[1].rm_so);
if(!strncasecmp(request + matches[1].rm_so, "RPC_IN_DATA", matches[1].rm_eo - matches[1].rm_so))
is_rpc = 1;
else if(!strncasecmp(request + matches[1].rm_so, "RPC_OUT_DATA", matches[1].rm_eo - matches[1].rm_so))
is_rpc = 0;
} else {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_WARNING, "(%lx) e501 bad request \"%s\" from %s", pthread_self(), request, caddr);
err_reply(cl, h501, lstn->err501);
free_headers(headers);
clean_all();
return;
}
cl_11 = (request[strlen(request) - 1] == '1');
n = cpURL(url, request + matches[2].rm_so, matches[2].rm_eo - matches[2].rm_so);
if(n != strlen(url)) {
/* the URL probably contained a %00 aka NULL - which we don't allow */
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e501 URL \"%s\" (contains NULL) from %s", pthread_self(), url, caddr);
err_reply(cl, h501, lstn->err501);
free_headers(headers);
clean_all();
return;
}
if(lstn->has_pat && regexec(&lstn->url_pat, url, 0, NULL, 0)) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e501 bad URL \"%s\" from %s", pthread_self(), url, caddr);
err_reply(cl, h501, lstn->err501);
free_headers(headers);
clean_all();
return;
}
/* check other headers */
for(chunked = 0, cont = L_1, n = 1; n < MAXHEADERS && headers[n]; n++) {
/* no overflow - see check_header for details */
switch(check_header(headers[n], buf)) {
case HEADER_HOST:
strcpy(v_host, buf);
break;
case HEADER_REFERER:
strcpy(referer, buf);
break;
case HEADER_USER_AGENT:
strcpy(u_agent, buf);
break;
case HEADER_CONNECTION:
if(!strcasecmp("close", buf))
conn_closed = 1;
break;
case HEADER_TRANSFER_ENCODING:
if(!strcasecmp("chunked", buf))
chunked = 1;
else {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e400 multiple Transfer-encoding \"%s\" from %s", pthread_self(), url, caddr);
err_reply(cl, h400, "Bad request: multiple Transfer-encoding values");
free_headers(headers);
clean_all();
return;
}
break;
case HEADER_CONTENT_LENGTH:
if(cont != L_1 || strchr(buf, ',')) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e400 multiple Content-length \"%s\" from %s", pthread_self(), url, caddr);
err_reply(cl, h400, "Bad request: multiple Content-length values");
free_headers(headers);
clean_all();
return;
}
for(mh = buf; *mh; mh++)
if(!isdigit(*mh)) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e400 Content-length bad value \"%s\" from %s", pthread_self(), url, caddr);
err_reply(cl, h400, "Bad request: Content-length bad value");
free_headers(headers);
clean_all();
return;
}
if((cont = ATOL(buf)) < 0L)
headers_ok[n] = 0;
if(is_rpc == 1 && (cont < 0x20000L || cont > 0x80000000L))
is_rpc = -1;
break;
case HEADER_EXPECT:
/*
* we do NOT support the "Expect: 100-continue" headers
* support may involve severe performance penalties (non-responding back-end, etc)
* as a stop-gap measure we just skip these headers
*/
if(!strcasecmp("100-continue", buf))
headers_ok[n] = 0;
break;
case HEADER_ILLEGAL:
if(lstn->log_level > 0) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) bad header from %s (%s)", pthread_self(), caddr, headers[n]);
}
headers_ok[n] = 0;
break;
}
if(headers_ok[n] && lstn->head_off) {
/* maybe header to be removed */
MATCHER *m;
for(m = lstn->head_off; m; m = m->next)
if(!(headers_ok[n] = regexec(&m->pat, headers[n], 0, NULL, 0)))
break;
}
/* get User name */
if(!regexec(&AUTHORIZATION, headers[n], 2, matches, 0)) {
int inlen;
if((bb = BIO_new(BIO_s_mem())) == NULL) {
logmsg(LOG_WARNING, "(%lx) Can't alloc BIO_s_mem", pthread_self());
continue;
}
if((b64 = BIO_new(BIO_f_base64())) == NULL) {
logmsg(LOG_WARNING, "(%lx) Can't alloc BIO_f_base64", pthread_self());
BIO_free(bb);
continue;
}
b64 = BIO_push(b64, bb);
BIO_write(bb, headers[n] + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so);
BIO_write(bb, "\n", 1);
if((inlen = BIO_read(b64, buf, MAXBUF - 1)) <= 0) {
logmsg(LOG_WARNING, "(%lx) Can't read BIO_f_base64", pthread_self());
BIO_free_all(b64);
continue;
}
BIO_free_all(b64);
if((mh = strchr(buf, ':')) == NULL) {
logmsg(LOG_WARNING, "(%lx) Unknown authentication", pthread_self());
continue;
}
*mh = '\0';
strcpy(u_name, buf);
}
}
/* check for possible request smuggling attempt */
if(chunked != 0 && cont != L_1) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e501 Transfer-encoding and Content-length \"%s\" from %s", pthread_self(), url, caddr);
err_reply(cl, h400, "Bad request: Transfer-encoding and Content-length headers present");
free_headers(headers);
clean_all();
return;
}
/* possibly limited request size */
if(lstn->max_req > L0 && cont > L0 && cont > lstn->max_req && is_rpc != 1) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e501 request too large (%ld) from %s", pthread_self(), cont, caddr);
err_reply(cl, h501, lstn->err501);
free_headers(headers);
clean_all();
return;
}
if(be != NULL) {
if(is_readable(be, 0)) {
/* The only way it's readable is if it's at EOF, so close it! */
BIO_reset(be);
BIO_free_all(be);
be = NULL;
}
}
/* check that the requested URL still fits the old back-end (if any) */
if((svc = get_service(lstn, url, &headers[1])) == NULL) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e503 no service \"%s\" from %s %s", pthread_self(), request, caddr, v_host[0]? v_host: "-");
err_reply(cl, h503, lstn->err503);
free_headers(headers);
clean_all();
return;
}
if((backend = get_backend(svc, &from_host, url, &headers[1])) == NULL) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e503 no back-end \"%s\" from %s %s", pthread_self(), request, caddr, v_host[0]? v_host: "-");
err_reply(cl, h503, lstn->err503);
free_headers(headers);
clean_all();
return;
}
if(be != NULL && backend != cur_backend) {
BIO_reset(be);
BIO_free_all(be);
be = NULL;
}
while(be == NULL && backend->be_type == 0) {
switch(backend->addr.ai_family) {
case AF_INET:
sock_proto = PF_INET;
break;
case AF_INET6:
sock_proto = PF_INET6;
break;
case AF_UNIX:
sock_proto = PF_UNIX;
break;
default:
logmsg(LOG_WARNING, "(%lx) e503 backend: unknown family %d", pthread_self(), backend->addr.ai_family);
err_reply(cl, h503, lstn->err503);
free_headers(headers);
clean_all();
return;
}
if((sock = socket(sock_proto, SOCK_STREAM, 0)) < 0) {
str_be(buf, MAXBUF - 1, backend);
logmsg(LOG_WARNING, "(%lx) e503 backend %s socket create: %s", pthread_self(), buf, strerror(errno));
err_reply(cl, h503, lstn->err503);
free_headers(headers);
clean_all();
return;
}
if(connect_nb(sock, &backend->addr, backend->conn_to) < 0) {
str_be(buf, MAXBUF - 1, backend);
logmsg(LOG_WARNING, "(%lx) backend %s connect: %s", pthread_self(), buf, strerror(errno));
shutdown(sock, 2);
close(sock);
/*
* kill the back-end only if no HAport is defined for it
* otherwise allow the HAport mechanism to do its job
*/
memset(&z_addr, 0, sizeof(z_addr));
if(memcmp(&(backend->ha_addr), &(z_addr), sizeof(z_addr)) == 0)
kill_be(svc, backend, BE_KILL);
/*
* ...but make sure we don't get into a loop with the same back-end
*/
old_backend = backend;
if((backend = get_backend(svc, &from_host, url, &headers[1])) == NULL || backend == old_backend) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e503 no back-end \"%s\" from %s", pthread_self(), request, caddr);
err_reply(cl, h503, lstn->err503);
free_headers(headers);
clean_all();
return;
}
continue;
}
if(sock_proto == PF_INET || sock_proto == PF_INET6) {
n = 1;
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&n, sizeof(n));
l.l_onoff = 1;
l.l_linger = 10;
setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&l, sizeof(l));
#ifdef TCP_LINGER2
n = 5;
setsockopt(sock, SOL_TCP, TCP_LINGER2, (void *)&n, sizeof(n));
#endif
n = 1;
setsockopt(sock, SOL_TCP, TCP_NODELAY, (void *)&n, sizeof(n));
}
if((be = BIO_new_socket(sock, 1)) == NULL) {
logmsg(LOG_WARNING, "(%lx) e503 BIO_new_socket server failed", pthread_self());
shutdown(sock, 2);
close(sock);
err_reply(cl, h503, lstn->err503);
free_headers(headers);
clean_all();
return;
}
BIO_set_close(be, BIO_CLOSE);
if(backend->to > 0) {
ba2.timeout = backend->to;
BIO_set_callback_arg(be, (char *)&ba2);
BIO_set_callback(be, bio_callback);
}
if(backend->ctx != NULL) {
if((be_ssl = SSL_new(backend->ctx)) == NULL) {
logmsg(LOG_WARNING, "(%lx) be SSL_new: failed", pthread_self());
err_reply(cl, h503, lstn->err503);
free_headers(headers);
clean_all();
return;
}
SSL_set_bio(be_ssl, be, be);
if((bb = BIO_new(BIO_f_ssl())) == NULL) {
logmsg(LOG_WARNING, "(%lx) BIO_new(Bio_f_ssl()) failed", pthread_self());
err_reply(cl, h503, lstn->err503);
free_headers(headers);
clean_all();
return;
}
BIO_set_ssl(bb, be_ssl, BIO_CLOSE);
BIO_set_ssl_mode(bb, 1);
be = bb;
if(BIO_do_handshake(be) <= 0) {
str_be(buf, MAXBUF - 1, backend);
logmsg(LOG_NOTICE, "BIO_do_handshake with %s failed: %s", buf,
ERR_error_string(ERR_get_error(), NULL));
err_reply(cl, h503, lstn->err503);
free_headers(headers);
clean_all();
return;
}
}
if((bb = BIO_new(BIO_f_buffer())) == NULL) {
logmsg(LOG_WARNING, "(%lx) e503 BIO_new(buffer) server failed", pthread_self());
err_reply(cl, h503, lstn->err503);
free_headers(headers);
clean_all();
return;
}
BIO_set_buffer_size(bb, MAXBUF);
BIO_set_close(bb, BIO_CLOSE);
be = BIO_push(bb, be);
}
cur_backend = backend;
/* if we have anything but a BACK_END we close the channel */
if(be != NULL && cur_backend->be_type) {
BIO_reset(be);
BIO_free_all(be);
be = NULL;
}
/* send the request */
if(cur_backend->be_type == 0) {
for(n = 0; n < MAXHEADERS && headers[n]; n++) {
if(!headers_ok[n])
continue;
/* this is the earliest we can check for Destination - we had no back-end before */
if(lstn->rewr_dest && check_header(headers[n], buf) == HEADER_DESTINATION) {
if(regexec(&LOCATION, buf, 4, matches, 0)) {
logmsg(LOG_NOTICE, "(%lx) Can't parse Destination %s", pthread_self(), buf);
break;
}
str_be(caddr, MAXBUF - 1, cur_backend);
strcpy(loc_path, buf + matches[3].rm_so);
snprintf(buf, MAXBUF, "Destination: http://%s%s", caddr, loc_path);
free(headers[n]);
if((headers[n] = strdup(buf)) == NULL) {
logmsg(LOG_WARNING, "(%lx) rewrite Destination - out of memory: %s",
pthread_self(), strerror(errno));
free_headers(headers);
clean_all();
return;
}
}
if(BIO_printf(be, "%s\r\n", headers[n]) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write to %s/%s: %s (%.3f sec)",
pthread_self(), buf, request, strerror(errno),
(end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
free_headers(headers);
clean_all();
return;
}
}
/* add header if required */
if(lstn->add_head != NULL)
if(BIO_printf(be, "%s\r\n", lstn->add_head) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write AddHeader to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
free_headers(headers);
clean_all();
return;
}
}
free_headers(headers);
/* if SSL put additional headers for client certificate */
if(cur_backend->be_type == 0 && ssl != NULL) {
const SSL_CIPHER *cipher;
if((cipher = SSL_get_current_cipher(ssl)) != NULL) {
SSL_CIPHER_description(cipher, buf, MAXBUF - 1);
strip_eol(buf);
if(BIO_printf(be, "X-SSL-cipher: %s/%s\r\n", SSL_get_version(ssl), buf) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write X-SSL-cipher to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
clean_all();
return;
}
}
if(lstn->clnt_check > 0 && x509 != NULL && (bb = BIO_new(BIO_s_mem())) != NULL) {
X509_NAME_print_ex(bb, X509_get_subject_name(x509), 8, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB);
get_line(bb, buf, MAXBUF);
if(BIO_printf(be, "X-SSL-Subject: %s\r\n", buf) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write X-SSL-Subject to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
BIO_free_all(bb);
clean_all();
return;
}
X509_NAME_print_ex(bb, X509_get_issuer_name(x509), 8, XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB);
get_line(bb, buf, MAXBUF);
if(BIO_printf(be, "X-SSL-Issuer: %s\r\n", buf) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write X-SSL-Issuer to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
BIO_free_all(bb);
clean_all();
return;
}
ASN1_TIME_print(bb, X509_get_notBefore(x509));
get_line(bb, buf, MAXBUF);
if(BIO_printf(be, "X-SSL-notBefore: %s\r\n", buf) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write X-SSL-notBefore to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
BIO_free_all(bb);
clean_all();
return;
}
ASN1_TIME_print(bb, X509_get_notAfter(x509));
get_line(bb, buf, MAXBUF);
if(BIO_printf(be, "X-SSL-notAfter: %s\r\n", buf) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write X-SSL-notAfter to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
BIO_free_all(bb);
clean_all();
return;
}
if(BIO_printf(be, "X-SSL-serial: %ld\r\n", ASN1_INTEGER_get(X509_get_serialNumber(x509))) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write X-SSL-serial to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
BIO_free_all(bb);
clean_all();
return;
}
#if CERT1L
PEM_write_bio_X509(bb, x509);
get_line(bb, buf, MAXBUF);
if(BIO_printf(be, "X-SSL-certificate: %s", buf) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write X-SSL-certificate to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
BIO_free_all(bb);
clean_all();
return;
}
while(get_line(bb, buf, MAXBUF) == 0) {
if(BIO_printf(be, "%s", buf) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write X-SSL-certificate to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
BIO_free_all(bb);
clean_all();
return;
}
}
if(BIO_printf(be, "%s\r\n", buf) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write X-SSL-certificate to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
BIO_free_all(bb);
clean_all();
return;
}
#else
PEM_write_bio_X509(bb, x509);
get_line(bb, buf, MAXBUF);
if(BIO_printf(be, "X-SSL-certificate: %s\r\n", buf) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write X-SSL-certificate to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
BIO_free_all(bb);
clean_all();
return;
}
while(get_line(bb, buf, MAXBUF) == 0) {
if(BIO_printf(be, "\t%s\r\n", buf) <= 0) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
logmsg(LOG_WARNING, "(%lx) e500 error write X-SSL-certificate to %s: %s (%.3f sec)",
pthread_self(), buf, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
BIO_free_all(bb);
clean_all();
return;
}
}
#endif
BIO_free_all(bb);
}
}
/* put additional client IP header */
if(cur_backend->be_type == 0) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
BIO_printf(be, "X-Forwarded-For: %s\r\n", caddr);
/* final CRLF */
BIO_puts(be, "\r\n");
}
if(cl_11 && chunked) {
/* had Transfer-encoding: chunked so read/write all the chunks (HTTP/1.1 only) */
if(copy_chunks(cl, be, NULL, cur_backend->be_type, lstn->max_req)) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e500 for %s copy_chunks to %s/%s (%.3f sec)",
pthread_self(), caddr, buf, request, (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
clean_all();
return;
}
} else if(cont > L0 && is_rpc != 1) {
/* had Content-length, so do raw reads/writes for the length */
if(copy_bin(cl, be, cont, NULL, cur_backend->be_type)) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e500 for %s error copy client cont to %s/%s: %s (%.3f sec)",
pthread_self(), caddr, buf, request, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
clean_all();
return;
}
} else if(cont > 0L && is_readable(cl, lstn->to)) {
char one;
BIO *cl_unbuf;
/*
* special mode for RPC_IN_DATA - content until EOF
* force HTTP/1.0 - client closes connection when done.
*/
cl_11 = be_11 = 0;
/*
* first read whatever is already in the input buffer
*/
while(BIO_pending(cl)) {
if(BIO_read(cl, &one, 1) != 1) {
logmsg(LOG_NOTICE, "(%lx) error read request pending: %s",
pthread_self(), strerror(errno));
clean_all();
pthread_exit(NULL);
}
if (++res_bytes > cont) {
logmsg(LOG_NOTICE, "(%lx) error read request pending: max. RPC length exceeded",
pthread_self());
clean_all();
pthread_exit(NULL);
}
if(BIO_write(be, &one, 1) != 1) {
if(errno)
logmsg(LOG_NOTICE, "(%lx) error write request pending: %s",
pthread_self(), strerror(errno));
clean_all();
pthread_exit(NULL);
}
}
BIO_flush(be);
/*
* find the socket BIO in the chain
*/
if ((cl_unbuf = BIO_find_type(cl, lstn->ctx? BIO_TYPE_SSL : BIO_TYPE_SOCKET)) == NULL) {
logmsg(LOG_WARNING, "(%lx) error get unbuffered: %s", pthread_self(), strerror(errno));
clean_all();
pthread_exit(NULL);
}
/*
* copy till EOF
*/
while((res = BIO_read(cl_unbuf, buf, MAXBUF)) > 0) {
if((res_bytes += res) > cont) {
logmsg(LOG_NOTICE, "(%lx) error copy request body: max. RPC length exceeded",
pthread_self());
clean_all();
pthread_exit(NULL);
}
if(BIO_write(be, buf, res) != res) {
if(errno)
logmsg(LOG_NOTICE, "(%lx) error copy request body: %s",
pthread_self(), strerror(errno));
clean_all();
pthread_exit(NULL);
} else {
BIO_flush(be);
}
}
}
/* flush to the back-end */
if(cur_backend->be_type == 0 && BIO_flush(be) != 1) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e500 for %s error flush to %s/%s: %s (%.3f sec)",
pthread_self(), caddr, buf, request, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
clean_all();
return;
}
/*
* check on no_https_11:
* - if 0 ignore
* - if 1 and SSL force HTTP/1.0
* - if 2 and SSL and MSIE force HTTP/1.0
*/
switch(lstn->noHTTPS11) {
case 1:
force_10 = (ssl != NULL);
break;
case 2:
force_10 = (ssl != NULL && strstr(u_agent, "MSIE") != NULL);
break;
default:
force_10 = 0;
break;
}
/* if we have a redirector */
if(cur_backend->be_type) {
memset(buf, 0, sizeof(buf));
if(!cur_backend->redir_req)
snprintf(buf, sizeof(buf) - 1, "%s%s", cur_backend->url, url);
else
strncpy(buf, cur_backend->url, sizeof(buf) - 1);
redirect_reply(cl, buf, cur_backend->be_type);
addr2str(caddr, MAXBUF - 1, &from_host, 1);
switch(lstn->log_level) {
case 0:
break;
case 1:
case 2:
logmsg(LOG_INFO, "%s %s - REDIRECT %s", caddr, request, buf);
break;
case 3:
if(v_host[0])
logmsg(LOG_INFO, "%s %s - %s [%s] \"%s\" %d 0 \"%s\" \"%s\"", v_host, caddr,
u_name[0]? u_name: "-", req_time, request, cur_backend->be_type, referer, u_agent);
else
logmsg(LOG_INFO, "%s - %s [%s] \"%s\" %d 0 \"%s\" \"%s\"", caddr,
u_name[0]? u_name: "-", req_time, request, cur_backend->be_type, referer, u_agent);
break;
case 4:
case 5:
logmsg(LOG_INFO, "%s - %s [%s] \"%s\" %d 0 \"%s\" \"%s\"", caddr,
u_name[0]? u_name: "-", req_time, request, cur_backend->be_type, referer, u_agent);
break;
}
if(!cl_11 || conn_closed || force_10)
break;
continue;
} else if(is_rpc == 1) {
/* log RPC_IN_DATA */
end_req = cur_time();
memset(s_res_bytes, 0, LOG_BYTES_SIZE);
/* actual request length */
log_bytes(s_res_bytes, res_bytes);
addr2str(caddr, MAXBUF - 1, &from_host, 1);
str_be(buf, MAXBUF - 1, cur_backend);
switch(lstn->log_level) {
case 0:
break;
case 1:
logmsg(LOG_INFO, "%s %s - -", caddr, request);
break;
case 2:
if(v_host[0])
logmsg(LOG_INFO, "%s %s - - (%s/%s -> %s) %.3f sec",
caddr, request, v_host, svc->name[0]? svc->name: "-", buf,
(end_req - start_req) / 1000000.0);
else
logmsg(LOG_INFO, "%s %s - - (%s -> %s) %.3f sec",
caddr, request, svc->name[0]? svc->name: "-", buf,
(end_req - start_req) / 1000000.0);
break;
case 3:
logmsg(LOG_INFO, "%s %s - %s [%s] \"%s\" 000 %s \"%s\" \"%s\"",
v_host[0]? v_host: "-",
caddr, u_name[0]? u_name: "-", req_time, request,
s_res_bytes, referer, u_agent);
break;
case 4:
logmsg(LOG_INFO, "%s - %s [%s] \"%s\" 000 %s \"%s\" \"%s\"",
caddr, u_name[0]? u_name: "-", req_time, request,
s_res_bytes, referer, u_agent);
break;
case 5:
logmsg(LOG_INFO, "%s %s - %s [%s] \"%s\" 000 %s \"%s\" \"%s\" (%s -> %s) %.3f sec",
v_host[0]? v_host: "-",
caddr, u_name[0]? u_name: "-", req_time, request,
s_res_bytes, referer, u_agent, svc->name[0]? svc->name: "-", buf,
(end_req - start_req) / 1000000.0);
break;
}
/* no response expected - bail out */
break;
}
/* get the response */
for(skip = 1; skip;) {
if((headers = get_headers(be, cl, lstn)) == NULL) {
str_be(buf, MAXBUF - 1, cur_backend);
end_req = cur_time();
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) e500 for %s response error read from %s/%s: %s (%.3f secs)",
pthread_self(), caddr, buf, request, strerror(errno), (end_req - start_req) / 1000000.0);
err_reply(cl, h500, lstn->err500);
clean_all();
return;
}
strncpy(response, headers[0], MAXBUF);
be_11 = (response[7] == '1');
/* responses with code 100 are never passed back to the client */
skip = !regexec(&RESP_SKIP, response, 0, NULL, 0);
/* some response codes (1xx, 204, 304) have no content */
if(!no_cont && !regexec(&RESP_IGN, response, 0, NULL, 0))
no_cont = 1;
for(chunked = 0, cont = -1L, n = 1; n < MAXHEADERS && headers[n]; n++) {
switch(check_header(headers[n], buf)) {
case HEADER_CONNECTION:
if(!strcasecmp("close", buf))
conn_closed = 1;
break;
case HEADER_TRANSFER_ENCODING:
if(!strcasecmp("chunked", buf)) {
chunked = 1;
no_cont = 0;
}
break;
case HEADER_CONTENT_LENGTH:
cont = ATOL(buf);
/* treat RPC_OUT_DATA like reply without content-length */
if(is_rpc == 0) {
if(cont >= 0x20000L && cont <= 0x80000000L)
cont = -1L;
else
is_rpc = -1;
}
break;
case HEADER_LOCATION:
if(v_host[0] && need_rewrite(lstn->rewr_loc, buf, loc_path, v_host, lstn, cur_backend)) {
snprintf(buf, MAXBUF, "Location: %s://%s/%s",
(ssl == NULL? "http": "https"), v_host, loc_path);
free(headers[n]);
if((headers[n] = strdup(buf)) == NULL) {
logmsg(LOG_WARNING, "(%lx) rewrite Location - out of memory: %s",
pthread_self(), strerror(errno));
free_headers(headers);
clean_all();
return;
}
}
break;
case HEADER_CONTLOCATION:
if(v_host[0] && need_rewrite(lstn->rewr_loc, buf, loc_path, v_host, lstn, cur_backend)) {
snprintf(buf, MAXBUF, "Content-location: %s://%s/%s",
(ssl == NULL? "http": "https"), v_host, loc_path);
free(headers[n]);
if((headers[n] = strdup(buf)) == NULL) {
logmsg(LOG_WARNING, "(%lx) rewrite Content-location - out of memory: %s",
pthread_self(), strerror(errno));
free_headers(headers);
clean_all();
return;
}
}
break;
}
}
/* possibly record session information (only for cookies/header) */
upd_session(svc, &headers[1], cur_backend);
/* send the response */
if(!skip)
for(n = 0; n < MAXHEADERS && headers[n]; n++) {
if(BIO_printf(cl, "%s\r\n", headers[n]) <= 0) {
if(errno) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) error write to %s: %s", pthread_self(), caddr, strerror(errno));
}
free_headers(headers);
clean_all();
return;
}
}
free_headers(headers);
/* final CRLF */
if(!skip)
BIO_puts(cl, "\r\n");
if(BIO_flush(cl) != 1) {
if(errno) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) error flush headers to %s: %s", pthread_self(), caddr, strerror(errno));
}
clean_all();
return;
}
if(!no_cont) {
/* ignore this if request was HEAD or similar */
if(be_11 && chunked) {
/* had Transfer-encoding: chunked so read/write all the chunks (HTTP/1.1 only) */
if(copy_chunks(be, cl, &res_bytes, skip, L0)) {
/* copy_chunks() has its own error messages */
clean_all();
return;
}
} else if(cont >= L0) {
/* may have had Content-length, so do raw reads/writes for the length */
if(copy_bin(be, cl, cont, &res_bytes, skip)) {
if(errno)
logmsg(LOG_NOTICE, "(%lx) error copy server cont: %s", pthread_self(), strerror(errno));
clean_all();
return;
}
} else if(!skip) {
if(is_readable(be, cur_backend->to)) {
char one;
BIO *be_unbuf;
/*
* old-style response - content until EOF
* also implies the client may not use HTTP/1.1
*/
cl_11 = be_11 = 0;
/*
* first read whatever is already in the input buffer
*/
while(BIO_pending(be)) {
if(BIO_read(be, &one, 1) != 1) {
logmsg(LOG_NOTICE, "(%lx) error read response pending: %s",
pthread_self(), strerror(errno));
clean_all();
return;
}
if(BIO_write(cl, &one, 1) != 1) {
if(errno)
logmsg(LOG_NOTICE, "(%lx) error write response pending: %s",
pthread_self(), strerror(errno));
clean_all();
return;
}
res_bytes++;
}
BIO_flush(cl);
/*
* find the socket BIO in the chain
*/
if((be_unbuf = BIO_find_type(be, cur_backend->ctx? BIO_TYPE_SSL : BIO_TYPE_SOCKET)) == NULL) {
logmsg(LOG_WARNING, "(%lx) error get unbuffered: %s", pthread_self(), strerror(errno));
clean_all();
return;
}
/*
* copy till EOF
*/
while((res = BIO_read(be_unbuf, buf, MAXBUF)) > 0) {
if(BIO_write(cl, buf, res) != res) {
if(errno)
logmsg(LOG_NOTICE, "(%lx) error copy response body: %s",
pthread_self(), strerror(errno));
clean_all();
return;
} else {
res_bytes += res;
BIO_flush(cl);
}
}
}
}
if(BIO_flush(cl) != 1) {
/* client closes RPC_OUT_DATA connection - no error */
if(is_rpc == 0 && res_bytes > 0L)
break;
if(errno) {
addr2str(caddr, MAXBUF - 1, &from_host, 1);
logmsg(LOG_NOTICE, "(%lx) error final flush to %s: %s", pthread_self(), caddr, strerror(errno));
}
clean_all();
return;
}
}
}
end_req = cur_time();
/* log what happened */
memset(s_res_bytes, 0, LOG_BYTES_SIZE);
log_bytes(s_res_bytes, res_bytes);
addr2str(caddr, MAXBUF - 1, &from_host, 1);
if(anonymise) {
char *last;
if((last = strrchr(caddr, '.')) != NULL || (last = strrchr(caddr, ':')) != NULL)
strcpy(++last, "0");
}
str_be(buf, MAXBUF - 1, cur_backend);
switch(lstn->log_level) {
case 0:
break;
case 1:
logmsg(LOG_INFO, "%s %s - %s", caddr, request, response);
break;
case 2:
if(v_host[0])
logmsg(LOG_INFO, "%s %s - %s (%s/%s -> %s) %.3f sec",
caddr, request, response, v_host, svc->name[0]? svc->name: "-", buf,
(end_req - start_req) / 1000000.0);
else
logmsg(LOG_INFO, "%s %s - %s (%s -> %s) %.3f sec",
caddr, request, response, svc->name[0]? svc->name: "-", buf,
(end_req - start_req) / 1000000.0);
break;
case 3:
logmsg(LOG_INFO, "%s %s - %s [%s] \"%s\" %c%c%c %s \"%s\" \"%s\"",
v_host[0]? v_host: "-",
caddr, u_name[0]? u_name: "-", req_time, request, response[9],
response[10], response[11], s_res_bytes, referer, u_agent);
break;
case 4:
logmsg(LOG_INFO, "%s - %s [%s] \"%s\" %c%c%c %s \"%s\" \"%s\"",
caddr, u_name[0]? u_name: "-", req_time, request, response[9], response[10],
response[11], s_res_bytes, referer, u_agent);
break;
case 5:
logmsg(LOG_INFO, "%s %s - %s [%s] \"%s\" %c%c%c %s \"%s\" \"%s\" (%s -> %s) %.3f sec",
v_host[0]? v_host: "-",
caddr, u_name[0]? u_name: "-", req_time, request, response[9], response[10],
response[11], s_res_bytes, referer, u_agent, svc->name[0]? svc->name: "-", buf,
(end_req - start_req) / 1000000.0);
break;
}
if(!be_11) {
BIO_reset(be);
BIO_free_all(be);
be = NULL;
}
/*
* Stop processing if:
* - client is not HTTP/1.1
* or
* - we had a "Connection: closed" header
* or
* - this is an SSL connection and we had a NoHTTPS11 directive
*/
if(!cl_11 || conn_closed || force_10)
break;
}
/*
* This may help with some versions of IE with a broken channel shutdown
*/
if(ssl != NULL)
SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
clean_all();
return;
}
void *
thr_http(void *dummy)
{
thr_arg *arg;
for(;;) {
while((arg = get_thr_arg()) == NULL)
logmsg(LOG_NOTICE, "NULL get_thr_arg");
do_http(arg);
}
}