* released 1.2.2 (1.1.29)
* fixed a bug where a TCP connection would be logged twice if the 'logasap'
option was enabled without the 'tcplog' option.
* encode_string() would use hdr_encode_map instead of the map argument.
* the logged request is now encoded with '#XX' for unprintable characters
* new keywords 'capture request header' and 'capture response header' enable
logging of arbitrary HTTP headers in requests and responses
* removed "-DSOLARIS" after replacing the last inet_aton() with inet_pton()
diff --git a/haproxy.c b/haproxy.c
index c96aae4..426786e 100644
--- a/haproxy.c
+++ b/haproxy.c
@@ -29,6 +29,7 @@
* - fix client/server state transition when server is in connect or headers state
* and client suddenly disconnects. The server *should* switch to SHUT_WR, but
* still handle HTTP headers.
+ * - remove MAX_NEWHDR
*
*/
@@ -56,8 +57,8 @@
#include <linux/netfilter_ipv4.h>
#endif
-#define HAPROXY_VERSION "1.2.1"
-#define HAPROXY_DATE "2004/06/06"
+#define HAPROXY_VERSION "1.2.2"
+#define HAPROXY_DATE "2004/10/18"
/* this is for libc5 for example */
#ifndef TCP_NODELAY
@@ -145,6 +146,35 @@
return dst - orig;
}
+/*
+ * Returns a pointer to an area of <__len> bytes taken from the pool <pool> or
+ * dynamically allocated. In the first case, <__pool> is updated to point to
+ * the next element in the list.
+ */
+#define pool_alloc_from(__pool, __len) ({ \
+ void *__p; \
+ if ((__p = (__pool)) == NULL) \
+ __p = malloc(((__len) >= sizeof (void *)) ? (__len) : sizeof(void *)); \
+ else { \
+ __pool = *(void **)(__pool); \
+ } \
+ __p; \
+})
+
+/*
+ * Puts a memory area back to the corresponding pool.
+ * Items are chained directly through a pointer that
+ * is written in the beginning of the memory area, so
+ * there's no need for any carrier cell. This implies
+ * that each memory area is at least as big as one
+ * pointer.
+ */
+#define pool_free_to(__pool, __ptr) ({ \
+ *(void **)(__ptr) = (void *)(__pool); \
+ __pool = (void *)(__ptr); \
+})
+
+
#define MEM_OPTIM
#ifdef MEM_OPTIM
/*
@@ -154,13 +184,13 @@
* next element in the list.
*/
#define pool_alloc(type) ({ \
- void *p; \
- if ((p = pool_##type) == NULL) \
- p = malloc(sizeof_##type); \
+ void *__p; \
+ if ((__p = pool_##type) == NULL) \
+ __p = malloc(sizeof_##type); \
else { \
pool_##type = *(void **)pool_##type; \
} \
- p; \
+ __p; \
})
/*
@@ -332,6 +362,9 @@
#define LW_PXIP 64 /* proxy IP */
#define LW_PXID 128 /* proxy ID */
#define LW_BYTES 256 /* bytes read from server */
+#define LW_COOKIE 512 /* captured cookie */
+#define LW_REQHDR 1024 /* request header(s) */
+#define LW_RSPHDR 2048 /* response header(s) */
/*********************************************************************/
@@ -339,6 +372,15 @@
/*********************************************************************/
+struct cap_hdr {
+ struct cap_hdr *next;
+ char *name; /* header name, case insensitive */
+ int namelen; /* length of the header name, to speed-up lookups */
+ int len; /* capture length, not including terminal zero */
+ int index; /* index in the output array */
+ void *pool; /* pool of pre-allocated memory area of (len+1) bytes */
+};
+
struct hdr_exp {
struct hdr_exp *next;
regex_t *preg; /* expression to look for */
@@ -403,6 +445,8 @@
struct sockaddr_storage cli_addr; /* the client address */
struct sockaddr_in srv_addr; /* the address to connect to */
struct server *srv; /* the server being used */
+ char **req_cap; /* array of captured request headers (may be NULL) */
+ char **rsp_cap; /* array of captured response headers (may be NULL) */
struct {
int logwait; /* log fields waiting to be collected : LW_* */
struct timeval tv_accept; /* date of the accept() (beginning of the session) */
@@ -456,6 +500,10 @@
int nb_reqadd, nb_rspadd;
struct hdr_exp *req_exp; /* regular expressions for request headers */
struct hdr_exp *rsp_exp; /* regular expressions for response headers */
+ int nb_req_cap, nb_rsp_cap; /* # of headers to be captured */
+ struct cap_hdr *req_cap; /* chained list of request headers to be captured */
+ struct cap_hdr *rsp_cap; /* chained list of response headers to be captured */
+ void *req_cap_pool, *rsp_cap_pool; /* pools of pre-allocated char ** used to build the sessions */
char *req_add[MAX_NEWHDR], *rsp_add[MAX_NEWHDR]; /* headers to be added */
int grace; /* grace time after stop request */
char *check_req; /* HTTP request to use if PR_O_HTTP_CHK is set, else NULL */
@@ -825,6 +873,7 @@
int port, end;
next = dupstr = strdup(str);
+
while (next && *next) {
struct sockaddr_storage ss;
@@ -895,6 +944,56 @@
return tail;
}
+
+#define FD_SETS_ARE_BITFIELDS
+#ifdef FD_SETS_ARE_BITFIELDS
+/*
+ * This map is used with all the FD_* macros to check whether a particular bit
+ * is set or not. Each bit represents an ACSII code. FD_SET() sets those bytes
+ * which should be encoded. When FD_ISSET() returns non-zero, it means that the
+ * byte should be encoded. Be careful to always pass bytes from 0 to 255
+ * exclusively to the macros.
+ */
+fd_set hdr_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))];
+fd_set url_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))];
+
+#else
+#error "Check if your OS uses bitfields for fd_sets"
+#endif
+
+/* will try to encode the string <string> replacing all characters tagged in
+ * <map> with the hexadecimal representation of their ASCII-code (2 digits)
+ * prefixed by <escape>, and will store the result between <start> (included
+ *) and <stop> (excluded), and will always terminate the string with a '\0'
+ * before <stop>. The position of the '\0' is returned if the conversion
+ * completes. If bytes are missing between <start> and <stop>, then the
+ * conversion will be incomplete and truncated. If <stop> <= <start>, the '\0'
+ * cannot even be stored so we return <start> without writing the 0.
+ * The input string must also be zero-terminated.
+ */
+char hextab[16] = "0123456789ABCDEF";
+char *encode_string(char *start, char *stop,
+ const char escape, const fd_set *map,
+ const char *string)
+{
+ if (start < stop) {
+ stop--; /* reserve one byte for the final '\0' */
+ while (start < stop && *string != 0) {
+ if (!FD_ISSET((unsigned char)(*string), map))
+ *start++ = *string;
+ else {
+ if (start + 3 >= stop)
+ break;
+ *start++ = escape;
+ *start++ = hextab[(*string >> 4) & 15];
+ *start++ = hextab[*string & 15];
+ }
+ string++;
+ }
+ *start = '\0';
+ }
+ return start;
+}
/*
* This function sends a syslog message to both log servers of a proxy,
@@ -1422,6 +1521,24 @@
pool_free(buffer, s->req);
if (s->rep)
pool_free(buffer, s->rep);
+
+ if (s->rsp_cap != NULL) {
+ struct cap_hdr *h;
+ for (h = s->proxy->rsp_cap; h; h = h->next) {
+ if (s->rsp_cap[h->index] != NULL)
+ pool_free_to(h->pool, s->rsp_cap[h->index]);
+ }
+ pool_free_to(s->proxy->rsp_cap_pool, s->rsp_cap);
+ }
+ if (s->req_cap != NULL) {
+ struct cap_hdr *h;
+ for (h = s->proxy->req_cap; h; h = h->next) {
+ if (s->req_cap[h->index] != NULL)
+ pool_free_to(h->pool, s->req_cap[h->index]);
+ }
+ pool_free_to(s->proxy->req_cap_pool, s->req_cap);
+ }
+
if (s->logs.uri)
pool_free(requri, s->logs.uri);
if (s->logs.cli_cookie)
@@ -1997,7 +2114,43 @@
tm = localtime(&s->logs.tv_accept.tv_sec);
if (p->to_log & LW_REQ) {
- send_log(p, LOG_INFO, "%s:%d [%02d/%s/%04d:%02d:%02d:%02d] %s %s %d/%d/%d/%s%d %d %s%lld %s %s %c%c%c%c \"%s\"\n",
+ char tmpline[MAX_SYSLOG_LEN], *h;
+ int hdr;
+
+ h = tmpline;
+ if (p->to_log & LW_REQHDR && (h < tmpline + sizeof(tmpline) - 10)) {
+ *(h++) = ' ';
+ *(h++) = '{';
+ for (hdr = 0; hdr < p->nb_req_cap; hdr++) {
+ if (hdr)
+ *(h++) = '|';
+ if (s->req_cap[hdr] != NULL)
+ h = encode_string(h, tmpline + sizeof(tmpline) - 7, '#', hdr_encode_map, s->req_cap[hdr]);
+ }
+ *(h++) = '}';
+ }
+
+ if (p->to_log & LW_RSPHDR && (h < tmpline + sizeof(tmpline) - 7)) {
+ *(h++) = ' ';
+ *(h++) = '{';
+ for (hdr = 0; hdr < p->nb_rsp_cap; hdr++) {
+ if (hdr)
+ *(h++) = '|';
+ if (s->rsp_cap[hdr] != NULL)
+ h = encode_string(h, tmpline + sizeof(tmpline) - 4, '#', hdr_encode_map, s->rsp_cap[hdr]);
+ }
+ *(h++) = '}';
+ }
+
+ if (h < tmpline + sizeof(tmpline) - 4) {
+ *(h++) = ' ';
+ *(h++) = '"';
+ h = encode_string(h, tmpline + sizeof(tmpline) - 1, '#', url_encode_map, uri);
+ *(h++) = '"';
+ }
+ *h = '\0';
+
+ send_log(p, LOG_INFO, "%s:%d [%02d/%s/%04d:%02d:%02d:%02d] %s %s %d/%d/%d/%s%d %d %s%lld %s %s %c%c%c%c%s\n",
pn,
(s->cli_addr.ss_family == AF_INET) ?
ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port) :
@@ -2017,7 +2170,7 @@
sess_fin_state[(s->flags & SN_FINST_MASK) >> SN_FINST_SHIFT],
(p->options & PR_O_COOK_ANY) ? sess_cookie[(s->flags & SN_CK_MASK) >> SN_CK_SHIFT] : '-',
(p->options & PR_O_COOK_ANY) ? sess_set_cookie[(s->flags & SN_SCK_MASK) >> SN_SCK_SHIFT] : '-',
- uri);
+ tmpline);
}
else {
send_log(p, LOG_INFO, "%s:%d [%02d/%s/%04d:%02d:%02d:%02d] %s %s %d/%s%d %s%lld %c%c\n",
@@ -2125,6 +2278,36 @@
s->uniq_id = totalconn;
+ if (p->nb_req_cap > 0) {
+ if ((s->req_cap =
+ pool_alloc_from(p->req_cap_pool, p->nb_req_cap*sizeof(char *)))
+ == NULL) { /* no memory */
+ close(cfd); /* nothing can be done for this fd without memory */
+ pool_free(task, t);
+ pool_free(session, s);
+ return 0;
+ }
+ memset(s->req_cap, 0, p->nb_req_cap*sizeof(char *));
+ }
+ else
+ s->req_cap = NULL;
+
+ if (p->nb_rsp_cap > 0) {
+ if ((s->rsp_cap =
+ pool_alloc_from(p->rsp_cap_pool, p->nb_rsp_cap*sizeof(char *)))
+ == NULL) { /* no memory */
+ if (s->req_cap != NULL)
+ pool_free_to(p->req_cap_pool, s->req_cap);
+ close(cfd); /* nothing can be done for this fd without memory */
+ pool_free(task, t);
+ pool_free(session, s);
+ return 0;
+ }
+ memset(s->rsp_cap, 0, p->nb_rsp_cap*sizeof(char *));
+ }
+ else
+ s->rsp_cap = NULL;
+
if ((p->mode == PR_MODE_TCP || p->mode == PR_MODE_HTTP)
&& (p->logfac1 >= 0 || p->logfac2 >= 0)) {
struct sockaddr_storage sockname;
@@ -2201,11 +2384,16 @@
}
if ((s->req = pool_alloc(buffer)) == NULL) { /* no memory */
+ if (s->rsp_cap != NULL)
+ pool_free_to(p->rsp_cap_pool, s->rsp_cap);
+ if (s->req_cap != NULL)
+ pool_free_to(p->req_cap_pool, s->req_cap);
close(cfd); /* nothing can be done for this fd without memory */
pool_free(task, t);
pool_free(session, s);
return 0;
}
+
s->req->l = 0;
s->req->total = 0;
s->req->h = s->req->r = s->req->lr = s->req->w = s->req->data; /* r and w will be reset further */
@@ -2215,6 +2403,10 @@
if ((s->rep = pool_alloc(buffer)) == NULL) { /* no memory */
pool_free(buffer, s->req);
+ if (s->rsp_cap != NULL)
+ pool_free_to(p->rsp_cap_pool, s->rsp_cap);
+ if (s->req_cap != NULL)
+ pool_free_to(p->req_cap_pool, s->req_cap);
close(cfd); /* nothing can be done for this fd without memory */
pool_free(task, t);
pool_free(session, s);
@@ -2609,6 +2801,27 @@
if (!(t->logs.logwait &= ~LW_REQ))
sess_log(t);
}
+ else if (t->logs.logwait & LW_REQHDR) {
+ struct cap_hdr *h;
+ int len;
+ for (h = t->proxy->req_cap; h; h = h->next) {
+ if ((h->namelen + 2 <= ptr - req->h) &&
+ (req->h[h->namelen] == ':') &&
+ (strncasecmp(req->h, h->name, h->namelen) == 0)) {
+
+ if (t->req_cap[h->index] == NULL)
+ t->req_cap[h->index] = pool_alloc_from(h->pool, h->len + 1);
+
+ len = ptr - (req->h + h->namelen + 2);
+ if (len > h->len)
+ len = h->len;
+
+ memcpy(t->req_cap[h->index], req->h + h->namelen + 2, len);
+ t->req_cap[h->index][len]=0;
+ }
+ }
+
+ }
delete_header = 0;
@@ -3234,7 +3447,7 @@
/* if the user wants to log as soon as possible, without counting
bytes from the server, then this is the right moment. */
- if (!(t->logs.logwait & LW_BYTES)) {
+ if (t->proxy->to_log && !(t->logs.logwait & LW_BYTES)) {
t->logs.t_close = t->logs.t_connect; /* to get a valid end date */
sess_log(t);
}
@@ -3349,7 +3562,7 @@
/* if the user wants to log as soon as possible, without counting
bytes from the server, then this is the right moment. */
- if (!(t->logs.logwait & LW_BYTES)) {
+ if (t->proxy->to_log && !(t->logs.logwait & LW_BYTES)) {
t->logs.t_close = t->logs.t_data; /* to get a valid end date */
t->logs.bytes = rep->h - rep->data;
sess_log(t);
@@ -3412,6 +3625,27 @@
default:
break;
}
+ }
+ else if (t->logs.logwait & LW_RSPHDR) {
+ struct cap_hdr *h;
+ int len;
+ for (h = t->proxy->rsp_cap; h; h = h->next) {
+ if ((h->namelen + 2 <= ptr - rep->h) &&
+ (rep->h[h->namelen] == ':') &&
+ (strncasecmp(rep->h, h->name, h->namelen) == 0)) {
+
+ if (t->rsp_cap[h->index] == NULL)
+ t->rsp_cap[h->index] = pool_alloc_from(h->pool, h->len + 1);
+
+ len = ptr - (rep->h + h->namelen + 2);
+ if (len > h->len)
+ len = h->len;
+
+ memcpy(t->rsp_cap[h->index], rep->h + h->namelen + 2, len);
+ t->rsp_cap[h->index][len]=0;
+ }
+ }
+
}
delete_header = 0;
@@ -4730,7 +4964,7 @@
curproxy->logfac2 = defproxy.logfac2;
curproxy->logsrv2 = defproxy.logsrv2;
curproxy->loglev2 = defproxy.loglev2;
- curproxy->to_log = defproxy.to_log;
+ curproxy->to_log = defproxy.to_log & ~LW_COOKIE & ~LW_REQHDR & ~ LW_RSPHDR;
curproxy->grace = defproxy.grace;
curproxy->source_addr = defproxy.source_addr;
return 0;
@@ -4838,32 +5072,85 @@
return -1;
}
}
- else if (!strcmp(args[0], "capture")) { /* name of a cookie to capture */
-// if (curproxy == &defproxy) {
-// Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
-// return -1;
-// }
+ else if (!strcmp(args[0], "capture")) {
+ if (!strcmp(args[1], "cookie")) { /* name of a cookie to capture */
+ // if (curproxy == &defproxy) {
+ // Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
+ // return -1;
+ // }
- if (curproxy->capture_name != NULL) {
-// Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n",
-// file, linenum, args[0]);
-// return 0;
- free(curproxy->capture_name);
- }
+ if (curproxy->capture_name != NULL) {
+ // Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n",
+ // file, linenum, args[0]);
+ // return 0;
+ free(curproxy->capture_name);
+ }
- if (*(args[4]) == 0) {
- Alert("parsing [%s:%d] : '%s' expects 'cookie' <cookie_name> 'len' <len>.\n",
+ if (*(args[4]) == 0) {
+ Alert("parsing [%s:%d] : '%s' expects 'cookie' <cookie_name> 'len' <len>.\n",
+ file, linenum, args[0]);
+ return -1;
+ }
+ curproxy->capture_name = strdup(args[2]);
+ curproxy->capture_namelen = strlen(curproxy->capture_name);
+ curproxy->capture_len = atol(args[4]);
+ if (curproxy->capture_len >= CAPTURE_LEN) {
+ Warning("parsing [%s:%d] : truncating capture length to %d bytes.\n",
+ file, linenum, CAPTURE_LEN - 1);
+ curproxy->capture_len = CAPTURE_LEN - 1;
+ }
+ curproxy->to_log |= LW_COOKIE;
+ }
+ else if (!strcmp(args[1], "request") && !strcmp(args[2], "header")) {
+ struct cap_hdr *hdr;
+
+ if (curproxy == &defproxy) {
+ Alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]);
+ return -1;
+ }
+
+ if (*(args[3]) == 0 || strcmp(args[4], "len") != 0 || *(args[5]) == 0) {
+ Alert("parsing [%s:%d] : '%s %s' expects 'header' <header_name> 'len' <len>.\n",
+ file, linenum, args[0], args[1]);
+ return -1;
+ }
+
+ hdr = calloc(sizeof(struct cap_hdr), 1);
+ hdr->next = curproxy->req_cap;
+ hdr->name = strdup(args[3]);
+ hdr->namelen = strlen(args[3]);
+ hdr->len = atol(args[5]);
+ hdr->index = curproxy->nb_req_cap++;
+ curproxy->req_cap = hdr;
+ curproxy->to_log |= LW_REQHDR;
+ }
+ else if (!strcmp(args[1], "response") && !strcmp(args[2], "header")) {
+ struct cap_hdr *hdr;
+
+ if (curproxy == &defproxy) {
+ Alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]);
+ return -1;
+ }
+
+ if (*(args[3]) == 0 || strcmp(args[4], "len") != 0 || *(args[5]) == 0) {
+ Alert("parsing [%s:%d] : '%s %s' expects 'header' <header_name> 'len' <len>.\n",
+ file, linenum, args[0], args[1]);
+ return -1;
+ }
+ hdr = calloc(sizeof(struct cap_hdr), 1);
+ hdr->next = curproxy->rsp_cap;
+ hdr->name = strdup(args[3]);
+ hdr->namelen = strlen(args[3]);
+ hdr->len = atol(args[5]);
+ hdr->index = curproxy->nb_rsp_cap++;
+ curproxy->rsp_cap = hdr;
+ curproxy->to_log |= LW_RSPHDR;
+ }
+ else {
+ Alert("parsing [%s:%d] : '%s' expects 'cookie' or 'request header' or 'response header'.\n",
file, linenum, args[0]);
return -1;
}
- curproxy->capture_name = strdup(args[2]);
- curproxy->capture_namelen = strlen(curproxy->capture_name);
- curproxy->capture_len = atol(args[4]);
- if (curproxy->capture_len >= CAPTURE_LEN) {
- Warning("parsing [%s:%d] : truncating capture length to %d bytes.\n",
- file, linenum, CAPTURE_LEN - 1);
- curproxy->capture_len = CAPTURE_LEN - 1;
- }
}
else if (!strcmp(args[0], "contimeout")) { /* connect timeout */
if (curproxy->contimeout != defproxy.contimeout) {
@@ -5446,25 +5733,25 @@
return -1;
}
+ curproxy->req_add[curproxy->nb_reqadd++] = strdup(args[1]);
+ }
+ else if (!strcmp(args[0], "srvexp") || !strcmp(args[0], "rsprep")) { /* replace response header from a regex */
+ regex_t *preg;
+
- curproxy->req_add[curproxy->nb_reqadd++] = strdup(args[1]);
+ if (*(args[1]) == 0 || *(args[2]) == 0) {
+ Alert("parsing [%s:%d] : '%s' expects <search> and <replace> as arguments.\n",
+ file, linenum, args[0]);
+ return -1;
}
- else if (!strcmp(args[0], "srvexp") || !strcmp(args[0], "rsprep")) { /* replace response header from a regex */
- regex_t *preg;
-
- if (*(args[1]) == 0 || *(args[2]) == 0) {
- Alert("parsing [%s:%d] : '%s' expects <search> and <replace> as arguments.\n",
- file, linenum, args[0]);
- return -1;
- }
-
- preg = calloc(1, sizeof(regex_t));
- if (regcomp(preg, args[1], REG_EXTENDED) != 0) {
- Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]);
- return -1;
- }
-
- chain_regex(&curproxy->rsp_exp, preg, ACT_REPLACE, strdup(args[2]));
+
+ preg = calloc(1, sizeof(regex_t));
+ if (regcomp(preg, args[1], REG_EXTENDED) != 0) {
+ Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]);
+ return -1;
}
+
+ chain_regex(&curproxy->rsp_exp, preg, ACT_REPLACE, strdup(args[2]));
+ }
else if (!strcmp(args[0], "rspdel")) { /* delete response header from a regex */
regex_t *preg;
if (curproxy == &defproxy) {
@@ -5923,6 +6210,34 @@
exit(1);
}
+ /* initialize the log header encoding map : '{|}"#' should be encoded with
+ * '#' as prefix, as well as non-printable characters ( <32 or >= 127 ).
+ * URL encoding only requires '"', '#' to be encoded as well as non-
+ * printable characters above.
+ */
+ memset(hdr_encode_map, 0, sizeof(hdr_encode_map));
+ memset(url_encode_map, 0, sizeof(url_encode_map));
+ for (i = 0; i < 32; i++) {
+ FD_SET(i, hdr_encode_map);
+ FD_SET(i, url_encode_map);
+ }
+ for (i = 127; i < 256; i++) {
+ FD_SET(i, hdr_encode_map);
+ FD_SET(i, url_encode_map);
+ }
+
+ tmp = "\"#{|}";
+ while (*tmp) {
+ FD_SET(*tmp, hdr_encode_map);
+ tmp++;
+ }
+
+ tmp = "\"#";
+ while (*tmp) {
+ FD_SET(*tmp, url_encode_map);
+ tmp++;
+ }
+
pid = getpid();
progname = *argv;
while ((tmp = strchr(progname, '/')) != NULL)