MEDIUM: http: add a new "http-response" ruleset

Some actions were clearly missing to process response headers. This
patch adds a new "http-response" ruleset which provides the following
actions :
  - allow : stop evaluating http-response rules
  - deny : stop and reject the response with a 502
  - add-header : add a header in log-format mode
  - set-header : set a header in log-format mode
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 1f3bbb9..a0f8827 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1138,6 +1138,7 @@
 http-check expect                         -          -         X         X
 http-check send-state                     X          -         X         X
 http-request                              -          X         X         X
+http-response                             -          X         X         X
 id                                        -          X         X         X
 ignore-persist                            -          X         X         X
 log                                  (*)  X          X         X         X
@@ -2760,6 +2761,52 @@
   See also : "stats http-request", section 3.4 about userlists and section 7
              about ACL usage.
 
+http-response { allow | deny | add-header <name> <fmt> |
+                set-header <name> <fmt> } [ { if | unless } <condition> ]
+  Access control for Layer 7 responses
+
+  May be used in sections:   defaults | frontend | listen | backend
+                                no    |    yes   |   yes  |   yes
+
+  The http-response statement defines a set of rules which apply to layer 7
+  processing. The rules are evaluated in their declaration order when they are
+  met in a frontend, listen or backend section. Any rule may optionally be
+  followed by an ACL-based condition, in which case it will only be evaluated
+  if the condition is true. Since these rules apply on responses, the backend
+  rules are applied first, followed by the frontend's rules.
+
+  The first keyword is the rule's action. Currently supported actions include :
+    - "allow" : this stops the evaluation of the rules and lets the response
+      pass the check. No further "http-response" rules are evaluated for the
+      current section.
+
+    - "deny" : this stops the evaluation of the rules and immediately rejects
+      the response and emits an HTTP 502 error. No further "http-response"
+      rules are evaluated.
+
+    - "add-header" appends an HTTP header field whose name is specified in
+      <name> and whose value is defined by <fmt> which follows the log-format
+      rules (see Custom Log Format in section 8.2.4). This may be used to send
+      a cookie to a client for example, or to pass some internal information.
+      This rule is not final, so it is possible to add other similar rules.
+      Note that header addition is performed immediately, so one rule might
+      reuse the resulting header from a previous rule.
+
+    - "set-header" does the same as "add-header" except that the header name
+      is first removed if it existed. This is useful when passing security
+      information to the server, where the header must not be manipulated by
+      external users.
+
+  There is no limit to the number of http-response statements per instance.
+
+  It is important to know that http-reqsponse rules are processed very early in
+  the HTTP processing, before "reqdel" or "reqrep" rules. That way, headers
+  added by "add-header"/"set-header" are visible by almost all further ACL
+  rules.
+
+  See also : "http-request", section 3.4 about userlists and section 7 about
+             ACL usage.
+
 http-send-name-header [<header>]
   Add the server name to a request. Use the header string given by <header>
 
diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h
index e789fc1..24e3581 100644
--- a/include/proto/proto_http.h
+++ b/include/proto/proto_http.h
@@ -111,6 +111,7 @@
 void http_reset_txn(struct session *s);
 
 struct http_req_rule *parse_http_req_cond(const char **args, const char *file, int linenum, struct proxy *proxy);
+struct http_res_rule *parse_http_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy);
 void free_http_req_rules(struct list *r);
 struct chunk *http_error_message(struct session *s, int msgnum);
 struct redirect_rule *http_parse_redirect_rule(const char *file, int line, struct proxy *curproxy,
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index 12e446f..6190e6c 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -236,6 +236,7 @@
 	HTTP_AUTH_DIGEST,
 };
 
+/* actions for "http-request" */
 enum {
 	HTTP_REQ_ACT_UNKNOWN = 0,
 	HTTP_REQ_ACT_ALLOW,
@@ -248,6 +249,16 @@
 	HTTP_REQ_ACT_MAX /* must always be last */
 };
 
+/* actions for "http-response" */
+enum {
+	HTTP_RES_ACT_UNKNOWN = 0,
+	HTTP_RES_ACT_ALLOW,
+	HTTP_RES_ACT_DENY,
+	HTTP_RES_ACT_ADD_HDR,
+	HTTP_RES_ACT_SET_HDR,
+	HTTP_RES_ACT_MAX /* must always be last */
+};
+
 /*
  * All implemented return codes
  */
@@ -360,6 +371,19 @@
 	} arg;                                 /* arguments used by some actions */
 };
 
+struct http_res_rule {
+	struct list list;
+	struct acl_cond *cond;                 /* acl condition to meet */
+	unsigned int action;                   /* HTTP_RES_* */
+	union {
+		struct {
+			char *name;            /* header name */
+			int name_len;          /* header name's length */
+			struct list fmt;       /* log-format compatible expression */
+		} hdr_add;                     /* args used by "add-header" and "set-header" */
+	} arg;                                 /* arguments used by some actions */
+};
+
 /* This is an HTTP transaction. It contains both a request message and a
  * response message (which can be empty).
  */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 273fb8b..e6bc755 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -211,7 +211,8 @@
 		char *name;			/* default backend name during config parse */
 	} defbe;
 	struct list acl;                        /* ACL declared on this proxy */
-	struct list http_req_rules;		/* HTTP request rules: allow/deny/http-auth */
+	struct list http_req_rules;		/* HTTP request rules: allow/deny/... */
+	struct list http_res_rules;		/* HTTP response rules: allow/deny/... */
 	struct list block_cond;                 /* early blocking conditions (chained) */
 	struct list redirect_rules;             /* content redirecting rules (chained) */
 	struct list switching_rules;            /* content switching rules (chained) */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 276766b..d74dfe0 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2635,6 +2635,7 @@
 		    !LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->cond &&
 		    (LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->action == HTTP_REQ_ACT_ALLOW ||
 		     LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->action == HTTP_REQ_ACT_DENY ||
+		     LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->action == HTTP_REQ_ACT_REDIR ||
 		     LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->action == HTTP_REQ_ACT_AUTH)) {
 			Warning("parsing [%s:%d]: previous '%s' action is final and has no condition attached, further entries are NOOP.\n",
 			        file, linenum, args[0]);
@@ -2654,6 +2655,37 @@
 
 		LIST_ADDQ(&curproxy->http_req_rules, &rule->list);
 	}
+	else if (!strcmp(args[0], "http-response")) {	/* response access control */
+		struct http_res_rule *rule;
+
+		if (curproxy == &defproxy) {
+			Alert("parsing [%s:%d]: '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		if (!LIST_ISEMPTY(&curproxy->http_res_rules) &&
+		    !LIST_PREV(&curproxy->http_res_rules, struct http_res_rule *, list)->cond &&
+		    (LIST_PREV(&curproxy->http_res_rules, struct http_res_rule *, list)->action == HTTP_RES_ACT_ALLOW ||
+		     LIST_PREV(&curproxy->http_res_rules, struct http_res_rule *, list)->action == HTTP_RES_ACT_DENY)) {
+			Warning("parsing [%s:%d]: previous '%s' action is final and has no condition attached, further entries are NOOP.\n",
+			        file, linenum, args[0]);
+			err_code |= ERR_WARN;
+		}
+
+		rule = parse_http_res_cond((const char **)args + 1, file, linenum, curproxy);
+
+		if (!rule) {
+			err_code |= ERR_ALERT | ERR_ABORT;
+			goto out;
+		}
+
+		err_code |= warnif_cond_conflicts(rule->cond,
+	                                          (curproxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR,
+	                                          file, linenum);
+
+		LIST_ADDQ(&curproxy->http_res_rules, &rule->list);
+	}
 	else if (!strcmp(args[0], "http-send-name-header")) { /* send server name in request header */
 		/* set the header name and length into the proxy structure */
 		if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
diff --git a/src/proto_http.c b/src/proto_http.c
index da9b97c..47f413e 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3232,6 +3232,71 @@
 }
 
 
+/* Executes the http-response rules <rules> for session <s>, proxy <px> and
+ * transaction <txn>. Returns the first rule that prevents further processing
+ * of the response (deny, ...) or NULL if it executed all rules or stopped
+ * on an allow. It may set the TX_SVDENY on txn->flags if it encounters a deny
+ * rule.
+ */
+static struct http_res_rule *
+http_res_get_intercept_rule(struct proxy *px, struct list *rules, struct session *s, struct http_txn *txn)
+{
+	struct http_res_rule *rule;
+	struct hdr_ctx ctx;
+
+	list_for_each_entry(rule, rules, list) {
+		if (rule->action >= HTTP_RES_ACT_MAX)
+			continue;
+
+		/* check optional condition */
+		if (rule->cond) {
+			int ret;
+
+			ret = acl_exec_cond(rule->cond, px, s, txn, SMP_OPT_DIR_RES|SMP_OPT_FINAL);
+			ret = acl_pass(ret);
+
+			if (rule->cond->pol == ACL_COND_UNLESS)
+				ret = !ret;
+
+			if (!ret) /* condition not matched */
+				continue;
+		}
+
+
+		switch (rule->action) {
+		case HTTP_RES_ACT_ALLOW:
+			return NULL; /* "allow" rules are OK */
+
+		case HTTP_RES_ACT_DENY:
+			txn->flags |= TX_SVDENY;
+			return rule;
+
+		case HTTP_RES_ACT_SET_HDR:
+			ctx.idx = 0;
+			/* remove all occurrences of the header */
+			while (http_find_header2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len,
+						 txn->rsp.chn->buf->p, &txn->hdr_idx, &ctx)) {
+				http_remove_header2(&txn->rsp, &txn->hdr_idx, &ctx);
+			}
+			/* now fall through to header addition */
+
+		case HTTP_RES_ACT_ADD_HDR:
+			chunk_printf(&trash, "%s: ", rule->arg.hdr_add.name);
+			memcpy(trash.str, rule->arg.hdr_add.name, rule->arg.hdr_add.name_len);
+			trash.len = rule->arg.hdr_add.name_len;
+			trash.str[trash.len++] = ':';
+			trash.str[trash.len++] = ' ';
+			trash.len += build_logline(s, trash.str + trash.len, trash.size - trash.len, &rule->arg.hdr_add.fmt);
+			http_header_add_tail2(&txn->rsp, &txn->hdr_idx, trash.str, trash.len);
+			break;
+		}
+	}
+
+	/* we reached the end of the rules, nothing to report */
+	return NULL;
+}
+
+
 /* Perform an HTTP redirect based on the information in <rule>. The function
  * returns non-zero on success, or zero in case of a, irrecoverable error such
  * as too large a request to build a valid response.
@@ -5494,6 +5559,7 @@
 	struct http_msg *msg = &txn->rsp;
 	struct proxy *cur_proxy;
 	struct cond_wordlist *wl;
+	struct http_res_rule *http_res_last_rule = NULL;
 
 	DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%d analysers=%02x\n",
 		now_ms, __FUNCTION__,
@@ -5591,6 +5657,10 @@
 		while (1) {
 			struct proxy *rule_set = cur_proxy;
 
+			/* evaluate http-response rules */
+			if (!http_res_last_rule)
+				http_res_last_rule = http_res_get_intercept_rule(cur_proxy, &cur_proxy->http_res_rules, t, txn);
+
 			/* try headers filters */
 			if (rule_set->rsp_exp != NULL) {
 				if (apply_filters_to_response(t, rep, rule_set) < 0) {
@@ -8283,6 +8353,7 @@
 	}
 }
 
+/* parse an "http-request" rule */
 struct http_req_rule *parse_http_req_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
 {
 	struct http_req_rule *rule;
@@ -8384,6 +8455,74 @@
 	return NULL;
 }
 
+/* parse an "http-respose" rule */
+struct http_res_rule *parse_http_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
+{
+	struct http_res_rule *rule;
+	int cur_arg;
+
+	rule = calloc(1, sizeof(*rule));
+	if (!rule) {
+		Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
+		goto out_err;
+	}
+
+	if (!strcmp(args[0], "allow")) {
+		rule->action = HTTP_RES_ACT_ALLOW;
+		cur_arg = 1;
+	} else if (!strcmp(args[0], "deny")) {
+		rule->action = HTTP_RES_ACT_DENY;
+		cur_arg = 1;
+	} else if (strcmp(args[0], "add-header") == 0 || strcmp(args[0], "set-header") == 0) {
+		rule->action = *args[0] == 'a' ? HTTP_RES_ACT_ADD_HDR : HTTP_RES_ACT_SET_HDR;
+		cur_arg = 1;
+
+		if (!*args[cur_arg] || !*args[cur_arg+1] ||
+		    (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
+			Alert("parsing [%s:%d]: 'http-response %s' expects exactly 2 arguments.\n",
+			      file, linenum, args[0]);
+			goto out_err;
+		}
+
+		rule->arg.hdr_add.name = strdup(args[cur_arg]);
+		rule->arg.hdr_add.name_len = strlen(rule->arg.hdr_add.name);
+		LIST_INIT(&rule->arg.hdr_add.fmt);
+
+		proxy->conf.args.ctx = ARGC_HDR;
+		parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.hdr_add.fmt, 0,
+				       (proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR);
+		cur_arg += 2;
+	} else {
+		Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', 'add-header', 'set-header', but got '%s'%s.\n",
+		      file, linenum, args[0], *args[0] ? "" : " (missing argument)");
+		goto out_err;
+	}
+
+	if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
+		struct acl_cond *cond;
+		char *errmsg = NULL;
+
+		if ((cond = build_acl_cond(file, linenum, proxy, args+cur_arg, &errmsg)) == NULL) {
+			Alert("parsing [%s:%d] : error detected while parsing an 'http-response %s' condition : %s.\n",
+			      file, linenum, args[0], errmsg);
+			free(errmsg);
+			goto out_err;
+		}
+		rule->cond = cond;
+	}
+	else if (*args[cur_arg]) {
+		Alert("parsing [%s:%d]: 'http-response %s' expects"
+		      " either 'if' or 'unless' followed by a condition but found '%s'.\n",
+		      file, linenum, args[0], args[cur_arg]);
+		goto out_err;
+	}
+
+	return rule;
+ out_err:
+	free(rule);
+	return NULL;
+}
+
 /* Parses a redirect rule. Returns the redirect rule on success or NULL on error,
  * with <err> filled with the error message.
  */
diff --git a/src/proxy.c b/src/proxy.c
index 9317774..e6720c9 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -433,6 +433,7 @@
 	LIST_INIT(&p->pendconns);
 	LIST_INIT(&p->acl);
 	LIST_INIT(&p->http_req_rules);
+	LIST_INIT(&p->http_res_rules);
 	LIST_INIT(&p->block_cond);
 	LIST_INIT(&p->redirect_rules);
 	LIST_INIT(&p->mon_fail_cond);