[MEDIUM] enable/disable servers from the stats web interface

Based on a patch provided by Judd Montgomery, it is now possible to
enable/disable servers from the stats web interface. This allows to select
several servers in a backend and apply the action to them at the same time.

Currently, there are 2 known limitations :
- The POST data are limited to one packet
  (don't alter too many servers at a time).
- Expect: 100-continue is not supported.
(cherry picked from commit 7693948766cb5647ac03b48e782cfee2b1f14491)
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index 56d8abb..7038f46 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -53,6 +53,12 @@
 #define STAT_CLI_O_ERR  7   /* dump errors */
 #define STAT_CLI_O_TAB  8   /* dump tables */
 
+/* status codes (strictly 4 chars) used in the URL to display a message */
+#define STAT_STATUS_UNKN "UNKN"	/* an unknown error occured, shouldn't happen */
+#define STAT_STATUS_DONE "DONE"	/* the action is successful */
+#define STAT_STATUS_NONE "NONE"	/* nothing happened (no action chosen or servers state didn't change) */
+#define STAT_STATUS_EXCD "EXCD"	/* an error occured becayse the buffer couldn't store all data */
+
 
 int stats_accept(struct session *s);
 int stats_sock_parse_request(struct stream_interface *si, char *line);
diff --git a/include/types/session.h b/include/types/session.h
index d1e54ac..0bbb9bf 100644
--- a/include/types/session.h
+++ b/include/types/session.h
@@ -217,6 +217,7 @@
 			short px_st, sv_st;	/* DATA_ST_INIT or DATA_ST_DATA */
 			unsigned int flags;	/* STAT_* */
 			int iid, type, sid;	/* proxy id, type and service id if bounding of stats is enabled */
+			const char *st_code;	/* pointer to the status code returned by an action */
 		} stats;
 		struct {
 			struct bref bref;	/* back-reference from the session being dumped */
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 59607f9..5195d8c 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -1135,6 +1135,44 @@
 }
 
 
+/* We don't want to land on the posted stats page because a refresh will
+ * repost the data.  We don't want this to happen on accident so we redirect
+ * the browse to the stats page with a GET.
+ */
+int stats_http_redir(struct session *s, struct buffer *rep, struct uri_auth *uri)
+{
+	struct chunk msg;
+
+	chunk_init(&msg, trash, sizeof(trash));
+
+	switch (s->data_state) {
+	case DATA_ST_INIT:
+		chunk_printf(&msg,
+			"HTTP/1.0 303 See Other\r\n"
+			"Cache-Control: no-cache\r\n"
+			"Content-Type: text/plain\r\n"
+			"Connection: close\r\n"
+			"Location: %s;st=%s",
+			uri->uri_prefix, s->data_ctx.stats.st_code);
+		chunk_printf(&msg, "\r\n\r\n");
+
+		if (buffer_feed_chunk(rep, &msg) >= 0)
+			return 0;
+
+		s->txn.status = 303;
+
+		if (!(s->flags & SN_ERR_MASK))  // this is not really an error but it is
+			s->flags |= SN_ERR_PRXCOND; // to mark that it comes from the proxy
+		if (!(s->flags & SN_FINST_MASK))
+			s->flags |= SN_FINST_R;
+
+		s->data_state = DATA_ST_FIN;
+		return 1;
+	}
+	return 1;
+}
+
+
 /* This I/O handler runs as an applet embedded in a stream interface. It is
  * used to send HTTP stats over a TCP socket. The mechanism is very simple.
  * si->st0 becomes non-zero once the transfer is finished. The handler
@@ -1154,9 +1192,16 @@
 		si->st0 = 1;
 
 	if (!si->st0) {
-		if (stats_dump_http(s, res, s->be->uri_auth)) {
-			si->st0 = 1;
-			si->shutw(si);
+		if (s->txn.meth == HTTP_METH_POST) {
+			if (stats_http_redir(s, res, s->be->uri_auth)) {
+				si->st0 = 1;
+				si->shutw(si);
+			}
+		} else {
+			if (stats_dump_http(s, res, s->be->uri_auth)) {
+				si->st0 = 1;
+				si->shutw(si);
+			}
 		}
 	}
 
@@ -1446,6 +1491,39 @@
 			     ""
 			     );
 
+			if (s->data_ctx.stats.st_code) {
+				if (strcmp(s->data_ctx.stats.st_code, STAT_STATUS_DONE) == 0) {
+					chunk_printf(&msg,
+						     "<p><div class=active3>"
+						     "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
+						     "Action processed successfully."
+						     "</div>\n", uri->uri_prefix);
+				}
+				else if (strcmp(s->data_ctx.stats.st_code, STAT_STATUS_NONE) == 0) {
+					chunk_printf(&msg,
+						     "<p><div class=active2>"
+						     "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
+						     "Nothing has changed."
+						     "</div>\n", uri->uri_prefix);
+				}
+				else if (strcmp(s->data_ctx.stats.st_code, STAT_STATUS_EXCD) == 0) {
+					chunk_printf(&msg,
+						     "<p><div class=active0>"
+						     "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
+						     "<b>Action not processed : the buffer couldn't store all the data.<br>"
+						     "You should retry with less servers at a time.</b>"
+						     "</div>\n", uri->uri_prefix);
+				}
+				else {
+					chunk_printf(&msg,
+						     "<p><div class=active6>"
+						     "<a class=lfsb href=\"%s\" title=\"Remove this message\">[X]</a> "
+						     "Unexpected result."
+						     "</div>\n", uri->uri_prefix);
+				}
+				chunk_printf(&msg,"<p>\n");
+			}
+
 			if (buffer_feed_chunk(rep, &msg) >= 0)
 				return 0;
 		}
@@ -1546,6 +1624,13 @@
 
 	case DATA_ST_PX_TH:
 		if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
+			if (px->cap & PR_CAP_BE && px->srv) {
+				/* A form to enable/disable this proxy servers */
+				chunk_printf(&msg,
+					"<form action=\"%s\" method=\"post\">",
+					uri->uri_prefix);
+			}
+
 			/* print a new table */
 			chunk_printf(&msg,
 				     "<table class=\"tbl\" width=\"100%%\">\n"
@@ -1568,7 +1653,18 @@
 				     "</tr>\n"
 				     "</table>\n"
 				     "<table class=\"tbl\" width=\"100%%\">\n"
-				     "<tr class=\"titre\">"
+				     "<tr class=\"titre\">",
+				     (uri->flags & ST_SHLGNDS)?"<u>":"",
+				     px->id, px->id, px->id,
+				     (uri->flags & ST_SHLGNDS)?"</u>":"",
+				     px->desc ? "desc" : "empty", px->desc ? px->desc : "");
+
+			if (px->cap & PR_CAP_BE && px->srv) {
+				 /* Column heading for Enable or Disable server */
+				chunk_printf(&msg, "<th rowspan=2 width=1></th>");
+			}
+
+			chunk_printf(&msg,
 				     "<th rowspan=2></th>"
 				     "<th colspan=3>Queue</th>"
 				     "<th colspan=3>Session rate</th><th colspan=5>Sessions</th>"
@@ -1585,11 +1681,7 @@
 				     "<th>Status</th><th>LastChk</th><th>Wght</th><th>Act</th>"
 				     "<th>Bck</th><th>Chk</th><th>Dwn</th><th>Dwntme</th>"
 				     "<th>Thrtle</th>\n"
-				     "</tr>",
-				     (uri->flags & ST_SHLGNDS)?"<u>":"",
-				     px->id, px->id, px->id,
-				     (uri->flags & ST_SHLGNDS)?"</u>":"",
-				     px->desc ? "desc" : "empty", px->desc ? px->desc : "");
+				     "</tr>");
 
 			if (buffer_feed_chunk(rep, &msg) >= 0)
 				return 0;
@@ -1605,9 +1697,18 @@
 			if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
 				chunk_printf(&msg,
 				     /* name, queue */
-				     "<tr class=\"frontend\"><td class=ac>"
+				     "<tr class=\"frontend\">");
+
+				if (px->cap & PR_CAP_BE && px->srv) {
+					/* Column sub-heading for Enable or Disable server */
+					chunk_printf(&msg, "<td></td>");
+				}
+
+				chunk_printf(&msg,
+				     "<td class=ac>"
 				     "<a name=\"%s/Frontend\"></a>"
-				     "<a class=lfsb href=\"#%s/Frontend\">Frontend</a></td><td colspan=3></td>"
+				     "<a class=lfsb href=\"#%s/Frontend\">Frontend</a></td>"
+				     "<td colspan=3></td>"
 				     "",
 				     px->id, px->id);
 
@@ -1765,7 +1866,12 @@
 			}
 
 			if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
-				chunk_printf(&msg, "<tr class=socket><td class=ac");
+				chunk_printf(&msg, "<tr class=socket>");
+				if (px->cap & PR_CAP_BE && px->srv) {
+					 /* Column sub-heading for Enable or Disable server */
+					chunk_printf(&msg, "<td></td>");
+				}
+				chunk_printf(&msg, "<td class=ac");
 
 					if (uri->flags&ST_SHLGNDS) {
 						char str[INET6_ADDRSTRLEN], *fmt = NULL;
@@ -1939,16 +2045,21 @@
 				if ((sv->state & SRV_MAINTAIN) || (svs->state & SRV_MAINTAIN)) {
 					chunk_printf(&msg,
 					    /* name */
-					    "<tr class=\"maintain\"><td class=ac"
+					    "<tr class=\"maintain\">"
 					);
 				}
 				else {
 					chunk_printf(&msg,
 					    /* name */
-					    "<tr class=\"%s%d\"><td class=ac",
+					    "<tr class=\"%s%d\">",
 					    (sv->state & SRV_BACKUP) ? "backup" : "active", sv_state);
 				}
 
+				chunk_printf(&msg,
+					     "<td><input type=\"checkbox\" name=\"s\" value=\"%s\"></td>"
+					     "<td class=ac",
+					     sv->id);
+
 				if (uri->flags&ST_SHLGNDS) {
 					char str[INET6_ADDRSTRLEN];
 
@@ -2279,9 +2390,12 @@
 		if ((px->cap & PR_CAP_BE) &&
 		    (!(s->data_ctx.stats.flags & STAT_BOUND) || (s->data_ctx.stats.type & (1 << STATS_TYPE_BE)))) {
 			if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
-				chunk_printf(&msg,
-				     /* name */
-				     "<tr class=\"backend\"><td class=ac");
+				chunk_printf(&msg, "<tr class=\"backend\">");
+				if (px->cap & PR_CAP_BE && px->srv) {
+					/* Column sub-heading for Enable or Disable server */
+					chunk_printf(&msg, "<td></td>");
+				}
+				chunk_printf(&msg, "<td class=ac");
 
 				if (uri->flags&ST_SHLGNDS) {
 					/* balancing */
@@ -2305,6 +2419,7 @@
 				}
 
 				chunk_printf(&msg,
+				     /* name */
 				     ">%s<a name=\"%s/Backend\"></a>"
 				     "<a class=lfsb href=\"#%s/Backend\">Backend</a>%s</td>"
 				     /* queue : current, max */
@@ -2467,7 +2582,24 @@
 
 	case DATA_ST_PX_END:
 		if (!(s->data_ctx.stats.flags & STAT_FMT_CSV)) {
-			chunk_printf(&msg, "</table><p>\n");
+			chunk_printf(&msg, "</table>");
+
+			if (px->cap & PR_CAP_BE && px->srv) {
+				/* close the form used to enable/disable this proxy servers */
+				chunk_printf(&msg,
+					"Choose the action to perform on the checked servers : "
+					"<select name=action>"
+					"<option value=\"\"></option>"
+					"<option value=\"disable\">Disable</option>"
+					"<option value=\"enable\">Enable</option>"
+					"</select>"
+					"<input type=\"hidden\" name=\"b\" value=\"%s\">"
+					"&nbsp;<input type=\"submit\" value=\"Apply\">"
+					"</form>",
+					px->id);
+			}
+
+			chunk_printf(&msg, "<p>\n");
 
 			if (buffer_feed_chunk(rep, &msg) >= 0)
 				return 0;
diff --git a/src/proto_http.c b/src/proto_http.c
index 859b7fc..3d408e1 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -2839,6 +2839,110 @@
 	return 0;
 }
 
+/* We reached the stats page through a POST request.
+ * Parse the posted data and enable/disable servers if necessary.
+ * Returns 0 if request was parsed.
+ * Returns 1 if there was a problem parsing the posted data.
+ */
+int http_process_req_stat_post(struct session *s, struct buffer *req)
+{
+	struct http_txn *txn = &s->txn;
+	struct proxy *px;
+	struct server *sv;
+
+	char *backend = NULL;
+	int action = 0;
+
+	char *first_param, *cur_param, *next_param, *end_params;
+
+	first_param = req->data + txn->req.eoh + 2;
+	end_params  = first_param + txn->req.hdr_content_len;
+
+	cur_param = next_param = end_params;
+
+	if (end_params >= req->data + req->size) {
+		/* Prevent buffer overflow */
+		s->data_ctx.stats.st_code = STAT_STATUS_EXCD;
+		return 1;
+	}
+	else if (end_params > req->data + req->l) {
+		/* This condition also rejects a request with Expect: 100-continue */
+		s->data_ctx.stats.st_code = STAT_STATUS_EXCD;
+		return 1;
+	}
+
+	*end_params = '\0';
+
+	s->data_ctx.stats.st_code = STAT_STATUS_NONE;
+
+	/*
+	 * Parse the parameters in reverse order to only store the last value.
+	 * From the html form, the backend and the action are at the end.
+	 */
+	while (cur_param > first_param) {
+		char *key, *value;
+
+		cur_param--;
+		if ((*cur_param == '&') || (cur_param == first_param)) {
+			/* Parse the key */
+			key = cur_param;
+			if (cur_param != first_param) {
+				/* delimit the string for the next loop */
+				*key++ = '\0';
+			}
+
+			/* Parse the value */
+			value = key;
+			while (*value != '\0' && *value != '=') {
+				value++;
+			}
+			if (*value == '=') {
+				/* Ok, a value is found, we can mark the end of the key */
+				*value++ = '\0';
+			}
+
+			/* Now we can check the key to see what to do */
+			if (!backend && strcmp(key, "b") == 0) {
+				backend = value;
+			}
+			else if (!action && strcmp(key, "action") == 0) {
+				if (strcmp(value, "disable") == 0) {
+					action = 1;
+				}
+				else if (strcmp(value, "enable") == 0) {
+					action = 2;
+				} else {
+					/* unknown action, no need to continue */
+					break;
+				}
+			}
+			else if (strcmp(key, "s") == 0) {
+				if (backend && action && get_backend_server(backend, value, &px, &sv)) {
+					switch (action) {
+					case 1:
+						if (! (sv->state & SRV_MAINTAIN)) {
+							/* Not already in maintenance, we can change the server state */
+							sv->state |= SRV_MAINTAIN;
+							set_server_down(sv);
+							s->data_ctx.stats.st_code = STAT_STATUS_DONE;
+						}
+						break;
+					case 2:
+						if ((sv->state & SRV_MAINTAIN)) {
+							/* Already in maintenance, we can change the server state */
+							set_server_up(sv);
+							s->data_ctx.stats.st_code = STAT_STATUS_DONE;
+						}
+						break;
+					}
+				}
+			}
+			next_param = cur_param;
+		}
+	}
+	return 0;
+}
+
 /* This stream analyser runs all HTTP request processing which is common to
  * frontends and backends, which means blocking ACLs, filters, connection-close,
  * reqadd, stats and redirects. This is performed for the designated proxy.
@@ -3053,6 +3157,11 @@
 		 * make it follow standard rules (eg: clear req->analysers).
 		 */
 
+		/* Was the status page requested with a POST ? */
+		if (txn->meth == HTTP_METH_POST) {
+			http_process_req_stat_post(s, req);
+		}
+
 		s->logs.tv_request = now;
 		s->data_source = DATA_SRC_STATS;
 		s->data_state  = DATA_ST_INIT;
@@ -6943,10 +7052,10 @@
 }
 
 /*
- * In a GET or HEAD request, check if the requested URI matches the stats uri
+ * In a GET, HEAD or POST request, check if the requested URI matches the stats uri
  * for the current backend.
  *
- * It is assumed that the request is either a HEAD or GET and that the
+ * It is assumed that the request is either a HEAD, GET, or POST and that the
  * t->be->uri_auth field is valid.
  *
  * Returns 1 if stats should be provided, otherwise 0.
@@ -6960,7 +7069,7 @@
 	if (!uri_auth)
 		return 0;
 
-	if (txn->meth != HTTP_METH_GET && txn->meth != HTTP_METH_HEAD)
+	if (txn->meth != HTTP_METH_GET && txn->meth != HTTP_METH_HEAD && txn->meth != HTTP_METH_POST)
 		return 0;
 
 	memset(&t->data_ctx.stats, 0, sizeof(t->data_ctx.stats));
@@ -7004,6 +7113,24 @@
 		h++;
 	}
 
+	h = txn->req.sol + txn->req.sl.rq.u + uri_auth->uri_len;
+	while (h <= txn->req.sol + txn->req.sl.rq.u + txn->req.sl.rq.u_l - 8) {
+		if (memcmp(h, ";st=", 4) == 0) {
+			h += 4;
+
+			if (memcmp(h, STAT_STATUS_DONE, 4) == 0)
+				t->data_ctx.stats.st_code = STAT_STATUS_DONE;
+			else if (memcmp(h, STAT_STATUS_NONE, 4) == 0)
+				t->data_ctx.stats.st_code = STAT_STATUS_NONE;
+			else if (memcmp(h, STAT_STATUS_EXCD, 4) == 0)
+				t->data_ctx.stats.st_code = STAT_STATUS_EXCD;
+			else
+				t->data_ctx.stats.st_code = STAT_STATUS_UNKN;
+			break;
+		}
+		h++;
+	}
+
 	t->data_ctx.stats.flags |= STAT_SHOW_STAT | STAT_SHOW_INFO;
 
 	return 1;