MEDIUM: cli: handle CLI level from the master CLI

Handle the CLI level in the master CLI. In order to do this, the master
CLI stores the level in the stream. Each command are prefixed by a
"user" or "operator" command before they are forwarded to the target
CLI.

The level can be configured in the haproxy program arguments with the
level keyword: -S /tmp/sock,level,admin -S /tmp/sock2,level,user.
diff --git a/doc/management.txt b/doc/management.txt
index 24a253f..ff98b74 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -2517,6 +2517,7 @@
 
    # haproxy -W -S 127.0.0.1:1234 -f test1.cfg
    # haproxy -Ws -S /tmp/master-socket,uid,1000,gid,1000,mode,600 -f test1.cfg
+   # haproxy -W -S /tmp/master-socket,level,user -f test1.cfg
 
 The master CLI introduces a new 'show proc' command to surpervise the
 processes:
diff --git a/include/types/stream.h b/include/types/stream.h
index cdd6a51..5e854c5 100644
--- a/include/types/stream.h
+++ b/include/types/stream.h
@@ -94,6 +94,7 @@
 
 
 /* flags for the proxy of the master CLI */
+/* 0x1.. to 0x3 are reserved for ACCESS_LVL_MASK */
 
 #define PCLI_F_PROMPT          0x4
 #define PCLI_F_PAYLOAD         0x8
diff --git a/src/cli.c b/src/cli.c
index 9a8a636..2299c1c 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -391,6 +391,15 @@
 	return 1;
 }
 
+/* same as cli_has_level but for the CLI proxy and without error message */
+int pcli_has_level(struct stream *s, int level)
+{
+	if ((s->pcli_flags & ACCESS_LVL_MASK) < level) {
+		return 0;
+	}
+	return 1;
+}
+
 /* Returns severity_output for the current session if set, or default for the socket */
 static int cli_get_severity_output(struct appctx *appctx)
 {
@@ -1841,6 +1850,23 @@
 		channel_shutr_now(&s->req);
 		channel_shutw_now(&s->res);
 		return argl; /* return the number of elements in the array */
+	} else if (!strcmp(args[0], "operator")) {
+		if (!pcli_has_level(s, ACCESS_LVL_OPER)) {
+			memprintf(errmsg, "Permission denied!\n");
+			return -1;
+		}
+		s->pcli_flags &= ~ACCESS_LVL_MASK;
+		s->pcli_flags |= ACCESS_LVL_OPER;
+		return argl;
+
+	} else if (!strcmp(args[0], "user")) {
+		if (!pcli_has_level(s, ACCESS_LVL_USER)) {
+			memprintf(errmsg, "Permission denied!\n");
+			return -1;
+		}
+		s->pcli_flags &= ~ACCESS_LVL_MASK;
+		s->pcli_flags |= ACCESS_LVL_USER;
+		return argl;
 	}
 
 	return 0;
@@ -1866,6 +1892,7 @@
 	char *payload = NULL;
 	int wtrim = 0; /* number of words to trim */
 	int reql = 0;
+	int ret;
 	int i = 0;
 
 	p = str;
@@ -1974,15 +2001,29 @@
 
 		b_del(&req->buf, trim - str);
 
-		return end - trim;
+		ret = end - trim;
 	} else if (wtrim < 0) {
 		/* parsing error */
 		return -1;
+	} else {
+		/* the whole string */
+		ret = end - str;
 	}
 
-	/* foward the whole comand */
-	return end - str;
+	if (ret > 1) {
+		if (pcli_has_level(s, ACCESS_LVL_ADMIN)) {
+			goto end;
+		} else if (pcli_has_level(s, ACCESS_LVL_OPER)) {
+			ci_insert_line2(req, 0, "operator", strlen("operator"));
+			ret += strlen("operator") + 2;
+		} else if (pcli_has_level(s, ACCESS_LVL_USER)) {
+			ci_insert_line2(req, 0, "user", strlen("user"));
+			ret += strlen("user") + 2;
+		}
+	}
+end:
 
+	return ret;
 }
 
 int pcli_wait_for_request(struct stream *s, struct channel *req, int an_bit)
@@ -1991,6 +2032,9 @@
 	int to_forward;
 	char *errmsg = NULL;
 
+	if ((s->pcli_flags & ACCESS_LVL_MASK) == ACCESS_LVL_NONE)
+		s->pcli_flags |= strm_li(s)->bind_conf->level & ACCESS_LVL_MASK;
+
 read_again:
 	/* if the channel is closed for read, we won't receive any more data
 	   from the client, but we don't want to forward this close to the