MEDIUM: http: implement http-response track-sc* directive

This enables tracking of sticky counters from current response. The only
difference from "http-request track-sc" is the <key> sample expression
can only make use of samples in response (eg. res.*, status etc.) and
samples below Layer 6.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 1a96edb..2a1782a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -3980,6 +3980,7 @@
                 del-map(<file name>) <key fmt> |
                 set-map(<file name>) <key fmt> <value fmt> |
                 set-var(<var-name>) <expr> |
+                { track-sc0 | track-sc1 | track-sc2 } <key> [table <table>] |
                 sc-inc-gpc0(<sc-id>) |
                 sc-set-gpt0(<sc-id>) <int> |
                 silent-drop |
@@ -4187,6 +4188,14 @@
 
          http-response set-var(sess.last_redir) res.hdr(location)
 
+    - { track-sc0 | track-sc1 | track-sc2 } <key> [table <table>] :
+      enables tracking of sticky counters from current response. Please refer to
+      "http-request track-sc" for a complete description. The only difference
+      from "http-request track-sc" is the <key> sample expression can only make
+      use of samples in response (eg. res.*, status etc.) and samples below
+      Layer 6 (eg. ssl related samples, see section 7.3.4). If the sample is
+      not supported, haproxy will fail and warn while parsing the config.
+
     - sc-set-gpt0(<sc-id>) <int> :
       This action sets the GPT0 tag according to the sticky counter designated
       by <sc-id> and the value of <int>. The expected result is a boolean. If
diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h
index 0aa6643..9cdd589 100644
--- a/include/proto/proto_http.h
+++ b/include/proto/proto_http.h
@@ -242,10 +242,10 @@
 	return len;
 }
 
-/* for an http-request action ACT_HTTP_REQ_TRK_*, return a tracking index
+/* for an http-request/response action ACT_ACTION_TRK_SC*, return a tracking index
  * starting at zero for SC0. Unknown actions also return zero.
  */
-static inline int http_req_trk_idx(int trk_action)
+static inline int http_trk_idx(int trk_action)
 {
 	return trk_action - ACT_ACTION_TRK_SC0;
 }
diff --git a/include/types/stick_table.h b/include/types/stick_table.h
index cab11c1..77eeccd 100644
--- a/include/types/stick_table.h
+++ b/include/types/stick_table.h
@@ -181,6 +181,16 @@
 
 /* WARNING: if new fields are added, they must be initialized in stream_accept()
  * and freed in stream_free() !
+ *
+ * What's the purpose of there two macro:
+ *   - STKCTR_TRACK_BACKEND indicates that a tracking pointer was set from the backend
+ *    and thus that when a keep-alive request goes to another backend, the track
+ *    must cease.
+ *
+ *   - STKCTR_TRACK_CONTENT indicates that the tracking pointer was set in a
+ *    content-aware rule (tcp-request content or http-request) and that the
+ *    tracking has to be performed in the stream and not in the session, and
+ *    will cease for a new keep-alive request over the same connection.
  */
 #define STKCTR_TRACK_BACKEND 1
 #define STKCTR_TRACK_CONTENT 2
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 4e4775a..a65c701 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -7880,7 +7880,7 @@
 			if (!target) {
 				Alert("Proxy '%s': unable to find table '%s' referenced by track-sc%d.\n",
 				      curproxy->id, hrqrule->arg.trk_ctr.table.n,
-				      http_req_trk_idx(hrqrule->action));
+				      http_trk_idx(hrqrule->action));
 				cfgerr++;
 			}
 			else if (target->table.size == 0) {
@@ -7891,7 +7891,46 @@
 			else if (!stktable_compatible_sample(hrqrule->arg.trk_ctr.expr,  target->table.type)) {
 				Alert("Proxy '%s': stick-table '%s' uses a type incompatible with the 'track-sc%d' rule.\n",
 				      curproxy->id, hrqrule->arg.trk_ctr.table.n ? hrqrule->arg.trk_ctr.table.n : curproxy->id,
-				      http_req_trk_idx(hrqrule->action));
+				      http_trk_idx(hrqrule->action));
+				cfgerr++;
+			}
+			else {
+				free(hrqrule->arg.trk_ctr.table.n);
+				hrqrule->arg.trk_ctr.table.t = &target->table;
+				/* Note: if we decide to enhance the track-sc syntax, we may be able
+				 * to pass a list of counters to track and allocate them right here using
+				 * stktable_alloc_data_type().
+				 */
+			}
+		}
+
+		/* find the target table for 'http-response' layer 7 rules */
+		list_for_each_entry(hrqrule, &curproxy->http_res_rules, list) {
+			struct proxy *target;
+
+			if (hrqrule->action < ACT_ACTION_TRK_SC0 || hrqrule->action > ACT_ACTION_TRK_SCMAX)
+				continue;
+
+			if (hrqrule->arg.trk_ctr.table.n)
+				target = proxy_tbl_by_name(hrqrule->arg.trk_ctr.table.n);
+			else
+				target = curproxy;
+
+			if (!target) {
+				Alert("Proxy '%s': unable to find table '%s' referenced by track-sc%d.\n",
+				      curproxy->id, hrqrule->arg.trk_ctr.table.n,
+				      http_trk_idx(hrqrule->action));
+				cfgerr++;
+			}
+			else if (target->table.size == 0) {
+				Alert("Proxy '%s': table '%s' used but not configured.\n",
+				      curproxy->id, hrqrule->arg.trk_ctr.table.n ? hrqrule->arg.trk_ctr.table.n : curproxy->id);
+				cfgerr++;
+			}
+			else if (!stktable_compatible_sample(hrqrule->arg.trk_ctr.expr,  target->table.type)) {
+				Alert("Proxy '%s': stick-table '%s' uses a type incompatible with the 'track-sc%d' rule.\n",
+				      curproxy->id, hrqrule->arg.trk_ctr.table.n ? hrqrule->arg.trk_ctr.table.n : curproxy->id,
+				      http_trk_idx(hrqrule->action));
 				cfgerr++;
 			}
 			else {
diff --git a/src/proto_http.c b/src/proto_http.c
index 7d1b678..d2090bb 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3532,7 +3532,7 @@
 			 * applies.
 			 */
 
-			if (stkctr_entry(&s->stkctr[http_req_trk_idx(rule->action)]) == NULL) {
+			if (stkctr_entry(&s->stkctr[http_trk_idx(rule->action)]) == NULL) {
 				struct stktable *t;
 				struct stksess *ts;
 				struct stktable_key *key;
@@ -3542,7 +3542,7 @@
 				key = stktable_fetch_key(t, s->be, sess, s, SMP_OPT_DIR_REQ | SMP_OPT_FINAL, rule->arg.trk_ctr.expr, NULL);
 
 				if (key && (ts = stktable_get_entry(t, key))) {
-					stream_track_stkctr(&s->stkctr[http_req_trk_idx(rule->action)], t, ts);
+					stream_track_stkctr(&s->stkctr[http_trk_idx(rule->action)], t, ts);
 
 					/* let's count a new HTTP request as it's the first time we do it */
 					ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
@@ -3554,9 +3554,9 @@
 						update_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate),
 						                       t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
 
-					stkctr_set_flags(&s->stkctr[http_req_trk_idx(rule->action)], STKCTR_TRACK_CONTENT);
+					stkctr_set_flags(&s->stkctr[http_trk_idx(rule->action)], STKCTR_TRACK_CONTENT);
 					if (sess->fe != s->be)
-						stkctr_set_flags(&s->stkctr[http_req_trk_idx(rule->action)], STKCTR_TRACK_BACKEND);
+						stkctr_set_flags(&s->stkctr[http_trk_idx(rule->action)], STKCTR_TRACK_BACKEND);
 				}
 			}
 			break;
@@ -3778,6 +3778,50 @@
 				return HTTP_RULE_RES_BADREQ;
 			return HTTP_RULE_RES_DONE;
 
+		case ACT_ACTION_TRK_SC0 ... ACT_ACTION_TRK_SCMAX:
+			/* Note: only the first valid tracking parameter of each
+			 * applies.
+			 */
+
+			if (stkctr_entry(&s->stkctr[http_trk_idx(rule->action)]) == NULL) {
+				struct stktable *t;
+				struct stksess *ts;
+				struct stktable_key *key;
+				void *ptr;
+
+				t = rule->arg.trk_ctr.table.t;
+				key = stktable_fetch_key(t, s->be, sess, s, SMP_OPT_DIR_RES | SMP_OPT_FINAL, rule->arg.trk_ctr.expr, NULL);
+
+				if (key && (ts = stktable_get_entry(t, key))) {
+					stream_track_stkctr(&s->stkctr[http_trk_idx(rule->action)], t, ts);
+
+					/* let's count a new HTTP request as it's the first time we do it */
+					ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_CNT);
+					if (ptr)
+						stktable_data_cast(ptr, http_req_cnt)++;
+
+					ptr = stktable_data_ptr(t, ts, STKTABLE_DT_HTTP_REQ_RATE);
+					if (ptr)
+						update_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate),
+											   t->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
+
+					stkctr_set_flags(&s->stkctr[http_trk_idx(rule->action)], STKCTR_TRACK_CONTENT);
+					if (sess->fe != s->be)
+						stkctr_set_flags(&s->stkctr[http_trk_idx(rule->action)], STKCTR_TRACK_BACKEND);
+
+					/* When the client triggers a 4xx from the server, it's most often due
+					 * to a missing object or permission. These events should be tracked
+					 * because if they happen often, it may indicate a brute force or a
+					 * vulnerability scan. Normally this is done when receiving the response
+					 * but here we're tracking after this ought to have been done so we have
+					 * to do it on purpose.
+					 */
+					if ((unsigned)(txn->status - 400) < 100)
+						stream_inc_http_err_ctr(s);
+				}
+			}
+			break;
+
 		case ACT_CUSTOM:
 			if ((px->options & PR_O_ABRT_CLOSE) && (s->req.flags & (CF_SHUTR|CF_READ_NULL|CF_READ_ERROR)))
 				act_flags |= ACT_FLAG_FINAL;
@@ -9211,7 +9255,7 @@
 		action_build_list(&http_req_keywords.list, &trash);
 		Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'redirect', "
 		      "'tarpit', 'add-header', 'set-header', 'replace-header', 'replace-value', 'set-nice', "
-		      "'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map'"
+		      "'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map', 'track-sc*'"
 		      "%s%s, but got '%s'%s.\n",
 		      file, linenum, *trash.str ? ", " : "", trash.str, args[0], *args[0] ? "" : " (missing argument)");
 		goto out_err;
@@ -9556,6 +9600,53 @@
 		redir->cond = NULL;
 		cur_arg = 2;
 		return rule;
+	} else if (strncmp(args[0], "track-sc", 8) == 0 &&
+	                   args[0][9] == '\0' && args[0][8] >= '0' &&
+	                   args[0][8] < '0' + MAX_SESS_STKCTR) { /* track-sc 0..9 */
+		struct sample_expr *expr;
+		unsigned int where;
+		char *err = NULL;
+
+		cur_arg = 1;
+		proxy->conf.args.ctx = ARGC_TRK;
+
+		expr = sample_parse_expr((char **)args, &cur_arg, file, linenum, &err, &proxy->conf.args);
+		if (!expr) {
+			Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-response %s' rule : %s.\n",
+			      file, linenum, proxy_type_str(proxy), proxy->id, args[0], err);
+			free(err);
+			goto out_err;
+		}
+
+		where = 0;
+		if (proxy->cap & PR_CAP_FE)
+			where |= SMP_VAL_FE_HRS_HDR;
+		if (proxy->cap & PR_CAP_BE)
+			where |= SMP_VAL_BE_HRS_HDR;
+
+		if (!(expr->fetch->val & where)) {
+			Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-response %s' rule :"
+			      " fetch method '%s' extracts information from '%s', none of which is available here.\n",
+			      file, linenum, proxy_type_str(proxy), proxy->id, args[0],
+			      args[cur_arg-1], sample_src_names(expr->fetch->use));
+			free(expr);
+			goto out_err;
+		}
+
+		if (strcmp(args[cur_arg], "table") == 0) {
+			cur_arg++;
+			if (!args[cur_arg]) {
+				Alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-response %s' rule : missing table name.\n",
+				      file, linenum, proxy_type_str(proxy), proxy->id, args[0]);
+				free(expr);
+				goto out_err;
+			}
+			/* we copy the table name for now, it will be resolved later */
+			rule->arg.trk_ctr.table.n = strdup(args[cur_arg]);
+			cur_arg++;
+		}
+		rule->arg.trk_ctr.expr = expr;
+		rule->action = ACT_ACTION_TRK_SC0 + args[0][8] - '0';
 	} else if (((custom = action_http_res_custom(args[0])) != NULL)) {
 		char *errmsg = NULL;
 		cur_arg = 1;
@@ -9572,7 +9663,7 @@
 		action_build_list(&http_res_keywords.list, &trash);
 		Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', "
 		      "'add-header', 'del-header', 'set-header', 'replace-header', 'replace-value', 'set-nice', "
-		      "'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map'"
+		      "'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map', 'track-sc*'"
 		      "%s%s, but got '%s'%s.\n",
 		      file, linenum, *trash.str ? ", " : "", trash.str, args[0], *args[0] ? "" : " (missing argument)");
 		goto out_err;