* released 1.2.1 (1.1.28)
* added the '-V' command line option to verbosely report errors even though
the -q or 'quiet' options are specified. This is useful with '-c'.
* added a Red Hat init script and a .spec from Simon Matter <simon.matter@invoca.ch>
* added 'rspdeny' and 'rspideny' to block certain responses to avoid sensible
information leak from servers.
* more examples added into the configuration
diff --git a/haproxy.c b/haproxy.c
index 63e0b79..c96aae4 100644
--- a/haproxy.c
+++ b/haproxy.c
@@ -8,7 +8,10 @@
* 2 of the License, or (at your option) any later version.
*
* Please refer to RFC2068 or RFC2616 for informations about HTTP protocol, and
- * RFC2965 for informations about cookies usage.
+ * RFC2965 for informations about cookies usage. More generally, the IETF HTTP
+ * Working Group's web site should be consulted for protocol related changes :
+ *
+ * http://ftp.ics.uci.edu/pub/ietf/http/
*
* Pending bugs (may be not fixed because never reproduced) :
* - solaris only : sometimes, an HTTP proxy with only a dispatch address causes
@@ -54,7 +57,7 @@
#endif
#define HAPROXY_VERSION "1.2.1"
-#define HAPROXY_DATE "2004/06/05"
+#define HAPROXY_DATE "2004/06/06"
/* this is for libc5 for example */
#ifndef TCP_NODELAY
@@ -300,6 +303,7 @@
#define MODE_DAEMON 8
#define MODE_QUIET 16
#define MODE_CHECK 32
+#define MODE_VERBOSE 64
/* server flags */
#define SRV_RUNNING 1 /* the server is UP */
@@ -684,13 +688,14 @@
void usage(char *name) {
display_version();
fprintf(stderr,
- "Usage : %s -f <cfgfile> [ -vd"
+ "Usage : %s -f <cfgfile> [ -vdV"
#if STATTIME > 0
"sl"
#endif
"D ] [ -n <maxconn> ] [ -N <maxpconn> ] [ -p <pidfile> ]\n"
" -v displays version\n"
" -d enters debug mode\n"
+ " -V enters verbose mode (disables quiet mode)\n"
#if STATTIME > 0
" -s enables statistics output\n"
" -l enables long statistics format\n"
@@ -714,7 +719,7 @@
struct timeval tv;
struct tm *tm;
- if (!(global.mode & MODE_QUIET)) {
+ if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) {
va_start(argp, fmt);
gettimeofday(&tv, NULL);
@@ -736,7 +741,7 @@
struct timeval tv;
struct tm *tm;
- if (!(global.mode & MODE_QUIET)) {
+ if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) {
va_start(argp, fmt);
gettimeofday(&tv, NULL);
@@ -755,7 +760,7 @@
void qfprintf(FILE *out, char *fmt, ...) {
va_list argp;
- if (!(global.mode & MODE_QUIET)) {
+ if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) {
va_start(argp, fmt);
vfprintf(out, fmt, argp);
fflush(out);
@@ -2100,9 +2105,6 @@
s->req = s->rep = NULL; /* will be allocated later */
s->flags = 0;
- if (p->options & PR_O_CHK_CACHE)
- s->flags |= SN_CACHEABLE | SN_CACHE_COOK;
-
s->res_cr = s->res_cw = s->res_sr = s->res_sw = RES_SILENT;
s->cli_fd = cfd;
s->srv_fd = -1;
@@ -2165,7 +2167,7 @@
}
}
- if ((global.mode & MODE_DEBUG) && !(global.mode & MODE_QUIET)) {
+ if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
struct sockaddr_in sockname;
int namelen;
int len;
@@ -2525,8 +2527,10 @@
if (t->proxy->options & PR_O_HTTP_CLOSE)
buffer_replace2(req, req->h, req->h, "Connection: close\r\n", 19);
- if (!memcmp(req->data, "POST ", 5))
- t->flags |= SN_POST; /* this is a POST request */
+ if (!memcmp(req->data, "POST ", 5)) {
+ /* this is a POST request, which is not cacheable by default */
+ t->flags |= SN_POST;
+ }
t->cli_state = CL_STDATA;
req->rlim = req->data + BUFSIZE; /* no more rewrite needed */
@@ -2608,7 +2612,7 @@
delete_header = 0;
- if ((global.mode & MODE_DEBUG) && !(global.mode & MODE_QUIET)) {
+ if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
int len, max;
len = sprintf(trash, "%08x:%s.clihdr[%04x:%04x]: ", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd);
max = ptr - req->h;
@@ -3085,7 +3089,7 @@
return 0;
}
else { /* CL_STCLOSE: nothing to do */
- if ((global.mode & MODE_DEBUG) && !(global.mode & MODE_QUIET)) {
+ if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
int len;
len = sprintf(trash, "%08x:%s.clicls[%04x:%04x]\n", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd);
write(1, trash, len);
@@ -3288,6 +3292,21 @@
}
}
+ /* next, we'll block if an 'rspideny' or 'rspdeny' filter matched */
+ if (t->flags & SN_SVDENY) {
+ tv_eternity(&t->srexpire);
+ tv_eternity(&t->swexpire);
+ fd_delete(t->srv_fd);
+ t->srv_state = SV_STCLOSE;
+ t->logs.status = 502;
+ client_return(t, t->proxy->errmsg.len502, t->proxy->errmsg.msg502);
+ if (!(t->flags & SN_ERR_MASK))
+ t->flags |= SN_ERR_PRXCOND;
+ if (!(t->flags & SN_FINST_MASK))
+ t->flags |= SN_FINST_H;
+ return 1;
+ }
+
/* we'll have something else to do here : add new headers ... */
if ((t->srv) && !(t->flags & SN_DIRECT) && (t->proxy->options & PR_O_COOK_INS) &&
@@ -3366,14 +3385,38 @@
*/
- if (t->logs.logwait & LW_RESP) {
+ if (t->logs.status == -1) {
t->logs.logwait &= ~LW_RESP;
t->logs.status = atoi(rep->h + 9);
+ switch (t->logs.status) {
+ case 200:
+ case 203:
+ case 206:
+ case 300:
+ case 301:
+ case 410:
+ /* RFC2616 @13.4:
+ * "A response received with a status code of
+ * 200, 203, 206, 300, 301 or 410 MAY be stored
+ * by a cache (...) unless a cache-control
+ * directive prohibits caching."
+ *
+ * RFC2616 @9.5: POST method :
+ * "Responses to this method are not cacheable,
+ * unless the response includes appropriate
+ * Cache-Control or Expires header fields."
+ */
+ if ((!t->flags & SN_POST) && (t->proxy->options & PR_O_CHK_CACHE))
+ t->flags |= SN_CACHEABLE | SN_CACHE_COOK;
+ break;
+ default:
+ break;
+ }
}
delete_header = 0;
- if ((global.mode & MODE_DEBUG) && !(global.mode & MODE_QUIET)) {
+ if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
int len, max;
len = sprintf(trash, "%08x:%s.srvhdr[%04x:%04x]: ", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd);
max = ptr - rep->h;
@@ -3434,21 +3477,27 @@
t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK;
else if (strncasecmp(rep->h, "Cache-control: ", 15) == 0) {
if (strncasecmp(rep->h + 15, "no-cache", 8) == 0) {
- if (rep->h + 23 == ptr || rep->h[23] == ';')
+ if (rep->h + 23 == ptr || rep->h[23] == ',')
t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK;
else {
if (strncasecmp(rep->h + 23, "=\"set-cookie", 12) == 0
- && (rep->h[35] == '"' || rep->h[35] == ';'))
+ && (rep->h[35] == '"' || rep->h[35] == ','))
t->flags &= ~SN_CACHE_COOK;
}
} else if ((strncasecmp(rep->h + 15, "private", 7) == 0 &&
- (rep->h + 22 == ptr || rep->h[22] == ';'))
+ (rep->h + 22 == ptr || rep->h[22] == ','))
|| (strncasecmp(rep->h + 15, "no-store", 8) == 0 &&
- (rep->h + 23 == ptr || rep->h[23] == ';'))) {
+ (rep->h + 23 == ptr || rep->h[23] == ','))) {
t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK;
} else if (strncasecmp(rep->h + 15, "max-age=0", 9) == 0 &&
- (rep->h + 24 == ptr || rep->h[24] == ';')) {
+ (rep->h + 24 == ptr || rep->h[24] == ',')) {
t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK;
+ } else if (strncasecmp(rep->h + 15, "s-maxage=0", 10) == 0 &&
+ (rep->h + 25 == ptr || rep->h[25] == ',')) {
+ t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK;
+ } else if (strncasecmp(rep->h + 15, "public", 6) == 0 &&
+ (rep->h + 21 == ptr || rep->h[21] == ',')) {
+ t->flags |= SN_CACHEABLE | SN_CACHE_COOK;
}
}
}
@@ -3869,7 +3918,7 @@
return 0;
}
else { /* SV_STCLOSE : nothing to do */
- if ((global.mode & MODE_DEBUG) && !(global.mode & MODE_QUIET)) {
+ if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
int len;
len = sprintf(trash, "%08x:%s.srvcls[%04x:%04x]\n", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd);
write(1, trash, len);
@@ -3916,7 +3965,7 @@
s->proxy->nbconn--;
actconn--;
- if ((global.mode & MODE_DEBUG) && !(global.mode & MODE_QUIET)) {
+ if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) {
int len;
len = sprintf(trash, "%08x:%s.closed[%04x:%04x]\n", s->uniq_id, s->proxy->id, (unsigned short)s->cli_fd, (unsigned short)s->srv_fd);
write(1, trash, len);
@@ -5436,6 +5485,26 @@
chain_regex(&curproxy->rsp_exp, preg, ACT_REMOVE, strdup(args[2]));
}
+ else if (!strcmp(args[0], "rspdeny")) { /* block response header from a regex */
+ regex_t *preg;
+ if (curproxy == &defproxy) {
+ Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
+ return -1;
+ }
+
+ if (*(args[1]) == 0) {
+ Alert("parsing [%s:%d] : '%s' expects <search> as an argument.\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_DENY, strdup(args[2]));
+ }
else if (!strcmp(args[0], "rspirep")) { /* replace response header from a regex ignoring case */
regex_t *preg;
if (curproxy == &defproxy) {
@@ -5477,6 +5546,26 @@
chain_regex(&curproxy->rsp_exp, preg, ACT_REMOVE, strdup(args[2]));
}
+ else if (!strcmp(args[0], "rspideny")) { /* block response header from a regex ignoring case */
+ regex_t *preg;
+ if (curproxy == &defproxy) {
+ Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
+ return -1;
+ }
+
+ if (*(args[1]) == 0) {
+ Alert("parsing [%s:%d] : '%s' expects <search> as an argument.\n", file, linenum, args[0]);
+ return -1;
+ }
+
+ preg = calloc(1, sizeof(regex_t));
+ if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) {
+ Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]);
+ return -1;
+ }
+
+ chain_regex(&curproxy->rsp_exp, preg, ACT_DENY, strdup(args[2]));
+ }
else if (!strcmp(args[0], "rspadd")) { /* add response header */
if (curproxy == &defproxy) {
Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
@@ -5830,7 +5919,7 @@
if (1<<INTBITS != sizeof(int)*8) {
fprintf(stderr,
"Error: wrong architecture. Recompile so that sizeof(int)=%d\n",
- sizeof(int)*8);
+ (int)(sizeof(int)*8));
exit(1);
}
@@ -5851,6 +5940,8 @@
display_version();
exit(0);
}
+ else if (*flag == 'V')
+ arg_mode |= MODE_VERBOSE;
else if (*flag == 'd')
arg_mode |= MODE_DEBUG;
else if (*flag == 'c')
@@ -5884,7 +5975,7 @@
argv++; argc--;
}
- global.mode = (arg_mode & (MODE_DAEMON | MODE_QUIET | MODE_DEBUG));
+ global.mode = (arg_mode & (MODE_DAEMON | MODE_VERBOSE | MODE_QUIET | MODE_CHECK | MODE_DEBUG));
if (!cfg_cfgfile)
usage(old_argv);
@@ -5896,7 +5987,7 @@
exit(1);
}
- if (arg_mode & MODE_CHECK) {
+ if (global.mode & MODE_CHECK) {
qfprintf(stdout, "Configuration file is valid : %s\n", cfg_cfgfile);
exit(0);
}
@@ -5919,7 +6010,8 @@
/* command line debug mode inhibits configuration mode */
global.mode &= ~(MODE_DAEMON | MODE_QUIET);
}
- global.mode |= (arg_mode & (MODE_DAEMON | MODE_QUIET | MODE_DEBUG | MODE_STATS | MODE_LOG));
+ global.mode |= (arg_mode & (MODE_DAEMON | MODE_QUIET | MODE_VERBOSE
+ | MODE_DEBUG | MODE_STATS | MODE_LOG));
if ((global.mode & MODE_DEBUG) && (global.mode & (MODE_DAEMON | MODE_QUIET))) {
Warning("<debug> mode incompatible with <quiet> and <daemon>. Keeping <debug> only.\n");