Hi all,

Le lundi 26 juillet 2010 11:40:51, Timh Bergström a écrit :
> 2010/7/25 Cyril Bonté <cyril.bo...@free.fr>:
> > For security reasons, maybe the code shouldn't be committed until the
> > authentication is done.
> > So, as I don't want to spam everybody with an unfinished patch, I'll send
> > it privately to you, Judd and Willy.
> 
> Could you send me the patch as well? Which version is it written
> against (so I can get the right source)? I would happily test and help
> implement this feature (if I can).

Willy reviewed the code wich let me fix some bugs I introduced. Now it's quite 
stable enough to be shared and tested ;-)

You'll find the patch (based on haproxy 1.4.8) in attachment.

Keep in mind that this code is for *testing only* unless you know what you do 
(security, stability, ...) but the final patch that will be proposed for the 
merge should be nearly the same.

OK, now let's go for the second part of the patch to add security with an 
admin level.

--
Cyril Bonté

diff -Naur haproxy-1.4.8/include/proto/dumpstats.h haproxy-1.4.8-stats-admin/include/proto/dumpstats.h
--- haproxy-1.4.8/include/proto/dumpstats.h	2010-06-16 22:57:48.000000000 +0200
+++ haproxy-1.4.8-stats-admin/include/proto/dumpstats.h	2010-07-26 20:23:52.000000000 +0200
@@ -52,6 +52,12 @@
 #define STAT_CLI_O_SESS 6   /* dump sessions */
 #define STAT_CLI_O_ERR  7   /* dump errors */
 
+/* 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_sock_parse_request(struct stream_interface *si, char *line);
 void stats_io_handler(struct stream_interface *si);
diff -Naur haproxy-1.4.8/include/types/session.h haproxy-1.4.8-stats-admin/include/types/session.h
--- haproxy-1.4.8/include/types/session.h	2010-06-16 22:57:48.000000000 +0200
+++ haproxy-1.4.8-stats-admin/include/types/session.h	2010-07-26 20:04:39.000000000 +0200
@@ -213,6 +213,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 -Naur haproxy-1.4.8/src/dumpstats.c haproxy-1.4.8-stats-admin/src/dumpstats.c
--- haproxy-1.4.8/src/dumpstats.c	2010-06-16 22:57:48.000000000 +0200
+++ haproxy-1.4.8-stats-admin/src/dumpstats.c	2010-07-26 23:21:28.000000000 +0200
@@ -954,6 +954,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
@@ -973,9 +1011,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);
+			}
 		}
 	}
 
@@ -1265,6 +1310,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;
 		}
@@ -1365,6 +1443,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"
@@ -1387,7 +1472,16 @@
 				     "</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>"
@@ -1404,11 +1498,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;
@@ -1424,9 +1514,16 @@
 			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);
 
@@ -1584,7 +1681,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;
@@ -1758,16 +1860,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];
 
@@ -2098,9 +2205,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->cap & PR_CAP_BE) {
+					/* 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 */
@@ -2124,6 +2234,7 @@
 				}
 
 				chunk_printf(&msg,
+				     /* name */
 				     ">%s<a name=\"%s/Backend\"></a>"
 				     "<a class=lfsb href=\"#%s/Backend\">Backend</a>%s</td>"
 				     /* queue : current, max */
@@ -2286,7 +2397,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 -Naur haproxy-1.4.8/src/proto_http.c haproxy-1.4.8-stats-admin/src/proto_http.c
--- haproxy-1.4.8/src/proto_http.c	2010-06-16 22:57:48.000000000 +0200
+++ haproxy-1.4.8-stats-admin/src/proto_http.c	2010-07-26 23:22:21.000000000 +0200
@@ -2819,6 +2819,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.
@@ -3023,6 +3127,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;
@@ -6479,10 +6588,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.
@@ -6496,7 +6605,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));
@@ -6538,6 +6647,25 @@
 			break;
 		}
 		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) {
+			char st[4];
+			memcpy(st, h + 4, 4);
+
+			if (memcmp(st, STAT_STATUS_DONE, 4) == 0)
+				t->data_ctx.stats.st_code = STAT_STATUS_DONE;
+			else if (memcmp(st, STAT_STATUS_NONE, 4) == 0)
+				t->data_ctx.stats.st_code = STAT_STATUS_NONE;
+			else if (memcmp(st, 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;

Reply via email to