Hello Willy,

On Thu, Apr 19, 2018 at 8:51 AM, Willy Tarreau <w...@1wt.eu> wrote:

> Hoping this helps,

Yes, I hope it does too.

I guess I was sent off-track by the will to make something very flexible
(at least in my mind) despite your multiple good examples, sorry about
that.

Anyway, here are three patches that, I hope, will do what is needed:
    - add a way to specify an optional payload by using << at the end of
      the first line of a command
    - add payload support to "add map"
    - add payload support to "set ssl ocsp-response"

More commands could benefit from using a payload but for a start I only
did these two.

Thanks for all your time, I really appreciated it.

-- 
Aurélien Nephtali
>From f7b1a17afff1d054d02300535f03c7921e8ef7df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien=20Nephtali?= <aurelien.nepht...@corp.ovh.com>
Date: Wed, 18 Apr 2018 13:26:46 +0200
Subject: [PATCH 1/3] MEDIUM: cli: Add payload support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

In order to use arbitrary data in the CLI (multiple lines or group of words
that must be considered as a whole, for example), it is now possible to add a
payload to the commands. To do so, the first line needs to end with a special
pattern: <<\n. Everything that follows will be left untouched by the CLI parser
and will be passed to the commands parsers.

Per-command support will need to be added to take advantage of this
feature.

Signed-off-by: Aurélien Nephtali <aurelien.nepht...@corp.ovh.com>
---
 doc/management.txt     |  17 ++++
 include/proto/applet.h |   4 +-
 include/proto/cli.h    |   1 -
 include/types/applet.h |   6 +-
 include/types/cli.h    |   2 +-
 src/cache.c            |   2 +-
 src/cli.c              | 228 ++++++++++++++++++++++++++++++-------------------
 src/dns.c              |   2 +-
 src/hlua.c             |   2 +-
 src/map.c              |  12 +--
 src/proto_http.c       |   2 +-
 src/proxy.c            |  16 ++--
 src/server.c           |  20 ++---
 src/ssl_sock.c         |   6 +-
 src/stats.c            |   6 +-
 src/stick_table.c      |   2 +-
 src/stream.c           |   6 +-
 17 files changed, 205 insertions(+), 129 deletions(-)

diff --git a/doc/management.txt b/doc/management.txt
index 4b6901851..5fe0d2e8e 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -1298,6 +1298,23 @@ delimiter to mark an end of output for each command, and takes care of ensuring
 that no command can emit an empty line on output. A script can thus easily
 parse the output even when multiple commands were pipelined on a single line.
 
+Some commands may take an optional payload. To add one to a command, the first
+line needs to end with the "<<\n" pattern. The next lines will be treated as
+the payload and can contain as many lines as needed. To validate a command with
+a payload, it needs to end with an empty line.
+
+Limitations do exist: the length of the whole buffer passed to the CLI must
+not be greater than tune.bfsize and the pattern "<<" must not be glued to the
+last word of the line.
+
+When entering a paylod while in interactive mode, the prompt will change from
+"> " to "+ ".
+
+Example:
+
+    # echo -e "set ssl ocsp-response <<\n$(base64 ocsp.der)\n" | \
+	socat /tmp/sock1 -
+
 It is important to understand that when multiple haproxy processes are started
 on the same sockets, any process may pick up the request and will output its
 own stats.
diff --git a/include/proto/applet.h b/include/proto/applet.h
index cdd4d90f0..7cc2c0ad1 100644
--- a/include/proto/applet.h
+++ b/include/proto/applet.h
@@ -43,11 +43,13 @@ static int inline appctx_res_wakeup(struct appctx *appctx);
 
 /* Initializes all required fields for a new appctx. Note that it does the
  * minimum acceptable initialization for an appctx. This means only the
- * 3 integer states st0, st1, st2 are zeroed.
+ * 3 integer states st0, st1, st2 and the chunk used to gather unfinished
+ * commands are zeroed
  */
 static inline void appctx_init(struct appctx *appctx, unsigned long thread_mask)
 {
 	appctx->st0 = appctx->st1 = appctx->st2 = 0;
+	appctx->chunk = NULL;
 	appctx->io_release = NULL;
 	appctx->thread_mask = thread_mask;
 	appctx->state = APPLET_SLEEPING;
diff --git a/include/proto/cli.h b/include/proto/cli.h
index d5feb862a..da80af7d3 100644
--- a/include/proto/cli.h
+++ b/include/proto/cli.h
@@ -24,7 +24,6 @@
 #define _PROTO_CLI_H
 
 
-struct cli_kw* cli_find_kw(char **args);
 void cli_register_kw(struct cli_kw_list *kw_list);
 
 int cli_has_level(struct appctx *appctx, int level);
diff --git a/include/types/applet.h b/include/types/applet.h
index 89c318c1d..b0715866e 100644
--- a/include/types/applet.h
+++ b/include/types/applet.h
@@ -50,6 +50,9 @@ struct applet {
 #define APPLET_WOKEN_UP     0x02  /* applet was running and requested to woken up again */
 #define APPLET_WANT_DIE     0x04  /* applet was running and requested to die */
 
+#define APPCTX_CLI_ST1_PROMPT  (1 << 0)
+#define APPCTX_CLI_ST1_PAYLOAD (1 << 1)
+
 /* Context of a running applet. */
 struct appctx {
 	struct list runq;          /* chaining in the applet run queue */
@@ -57,7 +60,8 @@ struct appctx {
 	/* 3 unused bytes here */
 	unsigned short state;      /* Internal appctx state */
 	unsigned int st0;          /* CLI state for stats, session state for peers */
-	unsigned int st1;          /* prompt for stats, session error for peers */
+	unsigned int st1;          /* prompt/payload (bitwise OR of APPCTX_CLI_ST1_*) for stats, session error for peers */
+	struct chunk *chunk;       /* used to store unfinished commands */
 	unsigned int st2;          /* output state for stats, unused by peers  */
 	struct applet *applet;     /* applet this context refers to */
 	void *owner;               /* pointer to upper layer's entity (eg: stream interface) */
diff --git a/include/types/cli.h b/include/types/cli.h
index 63e0e9d05..4e7e6b124 100644
--- a/include/types/cli.h
+++ b/include/types/cli.h
@@ -27,7 +27,7 @@ struct cli_kw {
 	const char *str_kw[5];   /* keywords ended by NULL, limited to 5
 				 separated keywords combination */
 	const char *usage;   /* usage message */
-	int (*parse)(char **args, struct appctx *appctx, void *private);
+	int (*parse)(char **args, char *payload, struct appctx *appctx, void *private);
 	int (*io_handler)(struct appctx *appctx);
 	void (*io_release)(struct appctx *appctx);
 	void *private;
diff --git a/src/cache.c b/src/cache.c
index 39e0bad44..c72d9dee9 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -952,7 +952,7 @@ struct flt_ops cache_ops = {
 
 };
 
-static int cli_parse_show_cache(char **args, struct appctx *appctx, void *private)
+static int cli_parse_show_cache(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
 		return 1;
diff --git a/src/cli.c b/src/cli.c
index 965709ec8..7a61601f0 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -67,6 +67,8 @@
 #include <proto/task.h>
 #include <proto/proto_udp.h>
 
+#define PAYLOAD_PATTERN "<<"
+
 static struct applet cli_applet;
 
 static const char stats_sock_usage_msg[] =
@@ -90,7 +92,7 @@ static struct cli_kw_list cli_keywords = {
 
 extern const char *stat_status_codes[];
 
-char *cli_gen_usage_msg()
+static char *cli_gen_usage_msg(struct appctx *appctx)
 {
 	struct cli_kw_list *kw_list;
 	struct cli_kw *kw;
@@ -101,7 +103,7 @@ char *cli_gen_usage_msg()
 	dynamic_usage_msg = NULL;
 
 	if (LIST_ISEMPTY(&cli_keywords.list))
-		return NULL;
+		goto end;
 
 	chunk_reset(tmp);
 	chunk_strcat(tmp, stats_sock_usage_msg);
@@ -115,6 +117,18 @@ char *cli_gen_usage_msg()
 	chunk_init(&out, NULL, 0);
 	chunk_dup(&out, tmp);
 	dynamic_usage_msg = out.str;
+
+end:
+	if (dynamic_usage_msg) {
+		appctx->ctx.cli.severity = LOG_INFO;
+		appctx->ctx.cli.msg = dynamic_usage_msg;
+	}
+	else {
+		appctx->ctx.cli.severity = LOG_INFO;
+		appctx->ctx.cli.msg = stats_sock_usage_msg;
+	}
+	appctx->st0 = CLI_ST_PRINT;
+
 	return dynamic_usage_msg;
 }
 
@@ -379,61 +393,63 @@ static int cli_get_severity_output(struct appctx *appctx)
  * If a keyword parser is NULL and an I/O handler is declared, the I/O handler
  * will automatically be used.
  */
-static int cli_parse_request(struct appctx *appctx, char *line)
+static int cli_parse_request(struct appctx *appctx)
 {
-	char *args[MAX_STATS_ARGS + 1];
+	char *args[MAX_STATS_ARGS + 1], *p, *end, *payload = NULL;
+	int i = 0;
 	struct cli_kw *kw;
-	int arg;
-	int i, j;
-
-	while (isspace((unsigned char)*line))
-		line++;
 
-	arg = 0;
-	args[arg] = line;
+	appctx->st2 = 0;
+	memset(&appctx->ctx.cli, 0, sizeof(appctx->ctx.cli));
 
-	while (*line && arg < MAX_STATS_ARGS) {
-		if (*line == '\\') {
-			line++;
-			if (*line == '\0')
-				break;
-		}
-		else if (isspace((unsigned char)*line)) {
-			*line++ = '\0';
+	p = appctx->chunk->str;
+	end = p + appctx->chunk->len;
+
+	/* look for a payload */
+	if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD) {
+		payload = strstr(p, PAYLOAD_PATTERN);
+		end = payload;
+		/* skip the pattern */
+		payload += strlen(PAYLOAD_PATTERN);
+		/* skip whitespaces */
+		payload += strspn(payload, " \t");
+	}
 
-			while (isspace((unsigned char)*line))
-				line++;
+	/*
+	 * Get pointers on words.
+	 * One extra slot is reserved to store a pointer on a null byte.
+	 */
+	while (i < MAX_STATS_ARGS && p < end) {
+		int j, k;
 
-			args[++arg] = line;
-			continue;
-		}
+		/* skip leading spaces/tabs */
+		p += strspn(p, " \t");
+		if (!*p)
+			break;
 
-		line++;
-	}
+		args[i] = p;
+		p += strcspn(p, " \t");
+		*p++ = 0;
 
-	while (++arg <= MAX_STATS_ARGS)
-		args[arg] = line;
-
-	/* unescape '\' */
-	arg = 0;
-	while (arg <= MAX_STATS_ARGS && *args[arg] != '\0') {
-		j = 0;
-		for (i=0; args[arg][i] != '\0'; i++) {
-			if (args[arg][i] == '\\') {
-				if (args[arg][i+1] == '\\')
-					i++;
+		/* unescape backslashes (\) */
+		for (j = 0, k = 0; args[i][k]; k++) {
+			if (args[i][k] == '\\') {
+				if (args[i][k + 1] == '\\')
+					k++;
 				else
 					continue;
 			}
-			args[arg][j] = args[arg][i];
+			args[i][j] = args[i][k];
 			j++;
 		}
-		args[arg][j] = '\0';
-		arg++;
-	}
+		args[i][j] = 0;
 
-	appctx->st2 = 0;
-	memset(&appctx->ctx.cli, 0, sizeof(appctx->ctx.cli));
+		i++;
+	}
+	/* fill unused slots */
+	p = appctx->chunk->str + appctx->chunk->len;
+	for (; i < MAX_STATS_ARGS + 1; i++)
+		args[i] = p;
 
 	kw = cli_find_kw(args);
 	if (!kw)
@@ -442,7 +458,7 @@ static int cli_parse_request(struct appctx *appctx, char *line)
 	appctx->io_handler = kw->io_handler;
 	appctx->io_release = kw->io_release;
 	/* kw->parse could set its own io_handler or ip_release handler */
-	if ((!kw->parse || kw->parse(args, appctx, kw->private) == 0) && appctx->io_handler) {
+	if ((!kw->parse || kw->parse(args, payload, appctx, kw->private) == 0) && appctx->io_handler) {
 		appctx->st0 = CLI_ST_CALLBACK;
 	}
 	return 1;
@@ -519,6 +535,7 @@ static void cli_io_handler(struct appctx *appctx)
 			 * side, the conditions below will complete if needed.
 			 */
 			si_shutw(si);
+			free_trash_chunk(appctx->chunk);
 			break;
 		}
 		else if (appctx->st0 == CLI_ST_GETREQ) {
@@ -568,46 +585,49 @@ static void cli_io_handler(struct appctx *appctx)
 
 			trash.str[len] = '\0';
 
-			appctx->st0 = CLI_ST_PROMPT;
-			if (len) {
-				if (strcmp(trash.str, "quit") == 0) {
+			/* use a trash chunk to store received data */
+			if (!appctx->chunk) {
+				appctx->chunk = alloc_trash_chunk();
+				if (!appctx->chunk) {
 					appctx->st0 = CLI_ST_END;
 					continue;
 				}
-				else if (strcmp(trash.str, "prompt") == 0)
-					appctx->st1 = !appctx->st1;
-				else if (strcmp(trash.str, "help") == 0 ||
-					 !cli_parse_request(appctx, trash.str)) {
-					cli_gen_usage_msg();
-					if (dynamic_usage_msg) {
-						appctx->ctx.cli.severity = LOG_INFO;
-						appctx->ctx.cli.msg = dynamic_usage_msg;
-					}
-					else {
-						appctx->ctx.cli.severity = LOG_INFO;
-						appctx->ctx.cli.msg = stats_sock_usage_msg;
-					}
-					appctx->st0 = CLI_ST_PRINT;
-				}
-				/* NB: stats_sock_parse_request() may have put
-				 * another CLI_ST_O_* into appctx->st0.
-				 */
 			}
-			else if (!appctx->st1) {
-				/* if prompt is disabled, print help on empty lines,
-				 * so that the user at least knows how to enable
-				 * prompt and find help.
-				 */
-				cli_gen_usage_msg();
-				if (dynamic_usage_msg) {
-					appctx->ctx.cli.severity = LOG_INFO;
-					appctx->ctx.cli.msg = dynamic_usage_msg;
+			chunk_appendf(appctx->chunk, "%s", trash.str);
+			if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)
+				chunk_appendf(appctx->chunk, "\n");
+
+			appctx->st0 = CLI_ST_PROMPT;
+
+			if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD) {
+				/* empty line */
+				if (!len) {
+					/* remove the last two \n */
+					appctx->chunk->len -= 2;
+					appctx->chunk->str[appctx->chunk->len] = 0;
+
+					if (!cli_parse_request(appctx))
+						cli_gen_usage_msg(appctx);
+
+					chunk_reset(appctx->chunk);
+					/* NB: cli_sock_parse_request() may have put
+					 * another CLI_ST_O_* into appctx->st0.
+					 */
+
+					appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD;
 				}
+			}
+			else {
+				/* look for the "payload start" pattern at the end of a line */
+				if (!strcmp(appctx->chunk->str + appctx->chunk->len - strlen(PAYLOAD_PATTERN), PAYLOAD_PATTERN))
+					appctx->st1 |= APPCTX_CLI_ST1_PAYLOAD;
 				else {
-					appctx->ctx.cli.severity = LOG_INFO;
-					appctx->ctx.cli.msg = stats_sock_usage_msg;
+					/* no payload, the command is complete: parse the request */
+					if (!cli_parse_request(appctx))
+						cli_gen_usage_msg(appctx);
+
+					chunk_reset(appctx->chunk);
 				}
-				appctx->st0 = CLI_ST_PRINT;
 			}
 
 			/* re-adjust req buffer */
@@ -656,7 +676,24 @@ static void cli_io_handler(struct appctx *appctx)
 
 			/* The post-command prompt is either LF alone or LF + '> ' in interactive mode */
 			if (appctx->st0 == CLI_ST_PROMPT) {
-				if (ci_putstr(si_ic(si), appctx->st1 ? "\n> " : "\n") != -1)
+				const char *prompt = "";
+
+				if (appctx->st1 & APPCTX_CLI_ST1_PROMPT) {
+					/*
+					 * when entering a payload with interactive mode, change the prompt
+					 * to emphasize that more data can still be sent
+					 */
+					if (appctx->chunk->len && appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)
+						prompt = "+ ";
+					else
+						prompt = "\n> ";
+				}
+				else {
+					if (!(appctx->st1 & APPCTX_CLI_ST1_PAYLOAD))
+						prompt = "\n";
+				}
+
+				if (ci_putstr(si_ic(si), prompt) != -1)
 					appctx->st0 = CLI_ST_GETREQ;
 				else
 					si_applet_cant_put(si);
@@ -671,7 +708,7 @@ static void cli_io_handler(struct appctx *appctx)
 			 * buffer is empty. This still allows pipelined requests
 			 * to be sent in non-interactive mode.
 			 */
-			if ((res->flags & (CF_SHUTW|CF_SHUTW_NOW)) || (!appctx->st1 && !req->buf->o)) {
+			if ((res->flags & (CF_SHUTW|CF_SHUTW_NOW)) || (!(appctx->st1 & APPCTX_CLI_ST1_PROMPT) && !req->buf->o)) {
 				appctx->st0 = CLI_ST_END;
 				continue;
 			}
@@ -1020,7 +1057,7 @@ static int cli_io_handler_show_cli_sock(struct appctx *appctx)
  * wants to stop here. It puts the variable to be dumped into cli.p0 if a single
  * variable is requested otherwise puts environ there.
  */
-static int cli_parse_show_env(char **args, struct appctx *appctx, void *private)
+static int cli_parse_show_env(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	extern char **environ;
 	char **var;
@@ -1054,7 +1091,7 @@ static int cli_parse_show_env(char **args, struct appctx *appctx, void *private)
  * wants to stop here. It puts the FD number into cli.i0 if a specific FD is
  * requested and sets st2 to STAT_ST_END, otherwise leaves 0 in i0.
  */
-static int cli_parse_show_fd(char **args, struct appctx *appctx, void *private)
+static int cli_parse_show_fd(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (!cli_has_level(appctx, ACCESS_LVL_OPER))
 		return 1;
@@ -1069,7 +1106,7 @@ static int cli_parse_show_fd(char **args, struct appctx *appctx, void *private)
 }
 
 /* parse a "set timeout" CLI request. It always returns 1. */
-static int cli_parse_set_timeout(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_timeout(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct stream_interface *si = appctx->owner;
 	struct stream *s = si_strm(si);
@@ -1106,7 +1143,7 @@ static int cli_parse_set_timeout(char **args, struct appctx *appctx, void *priva
 }
 
 /* parse a "set maxconn global" command. It always returns 1. */
-static int cli_parse_set_maxconn_global(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_maxconn_global(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	int v;
 
@@ -1159,7 +1196,7 @@ static int set_severity_output(int *target, char *argument)
 }
 
 /* parse a "set severity-output" command. */
-static int cli_parse_set_severity_output(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_severity_output(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (*args[2] && set_severity_output(&appctx->cli_severity_output, args[2]))
 		return 0;
@@ -1170,13 +1207,13 @@ static int cli_parse_set_severity_output(char **args, struct appctx *appctx, voi
 	return 1;
 }
 
-int cli_parse_default(char **args, struct appctx *appctx, void *private)
+int cli_parse_default(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	return 0;
 }
 
 /* parse a "set rate-limit" command. It always returns 1. */
-static int cli_parse_set_ratelimit(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_ratelimit(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	int v;
 	int *res;
@@ -1296,7 +1333,7 @@ static int bind_parse_severity_output(char **args, int cur_arg, struct proxy *px
 }
 
 /* Send all the bound sockets, always returns 1 */
-static int _getsocks(char **args, struct appctx *appctx, void *private)
+static int _getsocks(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	char *cmsgbuf = NULL;
 	unsigned char *tmpbuf = NULL;
@@ -1474,6 +1511,20 @@ out:
 	return 1;
 }
 
+static int cli_parse_simple(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	if (*args[0] == 'h')
+		/* help */
+		cli_gen_usage_msg(appctx);
+	else if (*args[0] == 'p')
+		/* prompt */
+		appctx->st1 ^= APPCTX_CLI_ST1_PROMPT;
+	else if (*args[0] == 'q')
+		/* quit */
+		appctx->st0 = CLI_ST_END;
+
+	return 1;
+}
 
 
 static struct applet cli_applet = {
@@ -1485,6 +1536,9 @@ static struct applet cli_applet = {
 
 /* register cli keywords */
 static struct cli_kw_list cli_kws = {{ },{
+	{ { "help", NULL }, NULL, cli_parse_simple, NULL },
+	{ { "prompt", NULL }, NULL, cli_parse_simple, NULL },
+	{ { "quit", NULL }, NULL, cli_parse_simple, NULL },
 	{ { "set", "maxconn", "global",  NULL }, "set maxconn global : change the per-process maxconn setting", cli_parse_set_maxconn_global, NULL },
 	{ { "set", "rate-limit", NULL }, "set rate-limit : change a rate limiting value", cli_parse_set_ratelimit, NULL },
 	{ { "set", "severity-output",  NULL }, "set severity-output [none|number|string] : set presence of severity level in feedback information", cli_parse_set_severity_output, NULL, NULL },
diff --git a/src/dns.c b/src/dns.c
index 5fe44d433..385ecbb6d 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -1948,7 +1948,7 @@ static int dns_finalize_config(void)
 }
 
 /* if an arg is found, it sets the resolvers section pointer into cli.p0 */
-static int cli_parse_stat_resolvers(char **args, struct appctx *appctx, void *private)
+static int cli_parse_stat_resolvers(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct dns_resolvers *presolvers;
 
diff --git a/src/hlua.c b/src/hlua.c
index 60cf8f948..d8d8d7def 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -6925,7 +6925,7 @@ __LJMP static int hlua_register_service(lua_State *L)
 /* This function initialises Lua cli handler. It copies the
  * arguments in the Lua stack and create channel IO objects.
  */
-static int hlua_cli_parse_fct(char **args, struct appctx *appctx, void *private)
+static int hlua_cli_parse_fct(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct hlua *hlua;
 	struct hlua_function *fcn;
diff --git a/src/map.c b/src/map.c
index 7953c2a0b..d02a0255c 100644
--- a/src/map.c
+++ b/src/map.c
@@ -565,7 +565,7 @@ static void cli_release_mlook(struct appctx *appctx)
 }
 
 
-static int cli_parse_get_map(char **args, struct appctx *appctx, void *private)
+static int cli_parse_get_map(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (strcmp(args[1], "map") == 0 || strcmp(args[1], "acl") == 0) {
 		/* Set flags. */
@@ -632,7 +632,7 @@ static void cli_release_show_map(struct appctx *appctx)
 	}
 }
 
-static int cli_parse_show_map(char **args, struct appctx *appctx, void *private)
+static int cli_parse_show_map(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (strcmp(args[1], "map") == 0 ||
 	    strcmp(args[1], "acl") == 0) {
@@ -672,7 +672,7 @@ static int cli_parse_show_map(char **args, struct appctx *appctx, void *private)
 	return 0;
 }
 
-static int cli_parse_set_map(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_map(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (strcmp(args[1], "map") == 0) {
 		char *err;
@@ -772,7 +772,7 @@ static int cli_parse_set_map(char **args, struct appctx *appctx, void *private)
 	return 1;
 }
 
-static int cli_parse_add_map(char **args, struct appctx *appctx, void *private)
+static int cli_parse_add_map(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (strcmp(args[1], "map") == 0 ||
 	    strcmp(args[1], "acl") == 0) {
@@ -862,7 +862,7 @@ static int cli_parse_add_map(char **args, struct appctx *appctx, void *private)
 	return 0;
 }
 
-static int cli_parse_del_map(char **args, struct appctx *appctx, void *private)
+static int cli_parse_del_map(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (args[1][0] == 'm')
 		appctx->ctx.map.display_flags = PAT_REF_MAP;
@@ -958,7 +958,7 @@ static int cli_parse_del_map(char **args, struct appctx *appctx, void *private)
 }
 
 
-static int cli_parse_clear_map(char **args, struct appctx *appctx, void *private)
+static int cli_parse_clear_map(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (strcmp(args[1], "map") == 0 || strcmp(args[1], "acl") == 0) {
 		/* Set ACL or MAP flags. */
diff --git a/src/proto_http.c b/src/proto_http.c
index 8370889b4..540574b86 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -12491,7 +12491,7 @@ struct action_kw *action_http_res_custom(const char *kw)
 /* "show errors" handler for the CLI. Returns 0 if wants to continue, 1 to stop
  * now.
  */
-static int cli_parse_show_errors(char **args, struct appctx *appctx, void *private)
+static int cli_parse_show_errors(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (!cli_has_level(appctx, ACCESS_LVL_OPER))
 		return 1;
diff --git a/src/proxy.c b/src/proxy.c
index 89f679ff5..31253f14d 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -1393,7 +1393,7 @@ struct proxy *cli_find_backend(struct appctx *appctx, const char *arg)
  * 1 if it stops immediately. If an argument is specified, it will set the proxy
  * pointer into cli.p0 and its ID into cli.i0.
  */
-static int cli_parse_show_servers(char **args, struct appctx *appctx, void *private)
+static int cli_parse_show_servers(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct proxy *px;
 
@@ -1558,7 +1558,7 @@ static int cli_io_handler_show_backend(struct appctx *appctx)
 }
 
 /* Parses the "enable dynamic-cookies backend" directive, it always returns 1 */
-static int cli_parse_enable_dyncookie_backend(char **args, struct appctx *appctx, void *private)
+static int cli_parse_enable_dyncookie_backend(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct proxy *px;
 	struct server *s;
@@ -1579,7 +1579,7 @@ static int cli_parse_enable_dyncookie_backend(char **args, struct appctx *appctx
 }
 
 /* Parses the "disable dynamic-cookies backend" directive, it always returns 1 */
-static int cli_parse_disable_dyncookie_backend(char **args, struct appctx *appctx, void *private)
+static int cli_parse_disable_dyncookie_backend(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct proxy *px;
 	struct server *s;
@@ -1604,7 +1604,7 @@ static int cli_parse_disable_dyncookie_backend(char **args, struct appctx *appct
 }
 
 /* Parses the "set dynamic-cookie-key backend" directive, it always returns 1 */
-static int cli_parse_set_dyncookie_key_backend(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_dyncookie_key_backend(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct proxy *px;
 	struct server *s;
@@ -1641,7 +1641,7 @@ static int cli_parse_set_dyncookie_key_backend(char **args, struct appctx *appct
 }
 
 /* Parses the "set maxconn frontend" directive, it always returns 1 */
-static int cli_parse_set_maxconn_frontend(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_maxconn_frontend(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct proxy *px;
 	struct listener *l;
@@ -1686,7 +1686,7 @@ static int cli_parse_set_maxconn_frontend(char **args, struct appctx *appctx, vo
 }
 
 /* Parses the "shutdown frontend" directive, it always returns 1 */
-static int cli_parse_shutdown_frontend(char **args, struct appctx *appctx, void *private)
+static int cli_parse_shutdown_frontend(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct proxy *px;
 
@@ -1713,7 +1713,7 @@ static int cli_parse_shutdown_frontend(char **args, struct appctx *appctx, void
 }
 
 /* Parses the "disable frontend" directive, it always returns 1 */
-static int cli_parse_disable_frontend(char **args, struct appctx *appctx, void *private)
+static int cli_parse_disable_frontend(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct proxy *px;
 
@@ -1748,7 +1748,7 @@ static int cli_parse_disable_frontend(char **args, struct appctx *appctx, void *
 }
 
 /* Parses the "enable frontend" directive, it always returns 1 */
-static int cli_parse_enable_frontend(char **args, struct appctx *appctx, void *private)
+static int cli_parse_enable_frontend(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct proxy *px;
 
diff --git a/src/server.c b/src/server.c
index 28cc7418c..ebac357fb 100644
--- a/src/server.c
+++ b/src/server.c
@@ -4088,7 +4088,7 @@ struct server *cli_find_server(struct appctx *appctx, char *arg)
 }
 
 
-static int cli_parse_set_server(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_server(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct server *sv;
 	const char *warning;
@@ -4271,7 +4271,7 @@ static int cli_parse_set_server(char **args, struct appctx *appctx, void *privat
 	return 1;
 }
 
-static int cli_parse_get_weight(char **args, struct appctx *appctx, void *private)
+static int cli_parse_get_weight(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct stream_interface *si = appctx->owner;
 	struct proxy *px;
@@ -4309,7 +4309,7 @@ static int cli_parse_get_weight(char **args, struct appctx *appctx, void *privat
 	return 1;
 }
 
-static int cli_parse_set_weight(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_weight(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct server *sv;
 	const char *warning;
@@ -4331,7 +4331,7 @@ static int cli_parse_set_weight(char **args, struct appctx *appctx, void *privat
 }
 
 /* parse a "set maxconn server" command. It always returns 1. */
-static int cli_parse_set_maxconn_server(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_maxconn_server(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct server *sv;
 	const char *warning;
@@ -4353,7 +4353,7 @@ static int cli_parse_set_maxconn_server(char **args, struct appctx *appctx, void
 }
 
 /* parse a "disable agent" command. It always returns 1. */
-static int cli_parse_disable_agent(char **args, struct appctx *appctx, void *private)
+static int cli_parse_disable_agent(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct server *sv;
 
@@ -4369,7 +4369,7 @@ static int cli_parse_disable_agent(char **args, struct appctx *appctx, void *pri
 }
 
 /* parse a "disable health" command. It always returns 1. */
-static int cli_parse_disable_health(char **args, struct appctx *appctx, void *private)
+static int cli_parse_disable_health(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct server *sv;
 
@@ -4385,7 +4385,7 @@ static int cli_parse_disable_health(char **args, struct appctx *appctx, void *pr
 }
 
 /* parse a "disable server" command. It always returns 1. */
-static int cli_parse_disable_server(char **args, struct appctx *appctx, void *private)
+static int cli_parse_disable_server(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct server *sv;
 
@@ -4401,7 +4401,7 @@ static int cli_parse_disable_server(char **args, struct appctx *appctx, void *pr
 }
 
 /* parse a "enable agent" command. It always returns 1. */
-static int cli_parse_enable_agent(char **args, struct appctx *appctx, void *private)
+static int cli_parse_enable_agent(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct server *sv;
 
@@ -4424,7 +4424,7 @@ static int cli_parse_enable_agent(char **args, struct appctx *appctx, void *priv
 }
 
 /* parse a "enable health" command. It always returns 1. */
-static int cli_parse_enable_health(char **args, struct appctx *appctx, void *private)
+static int cli_parse_enable_health(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct server *sv;
 
@@ -4440,7 +4440,7 @@ static int cli_parse_enable_health(char **args, struct appctx *appctx, void *pri
 }
 
 /* parse a "enable server" command. It always returns 1. */
-static int cli_parse_enable_server(char **args, struct appctx *appctx, void *private)
+static int cli_parse_enable_server(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct server *sv;
 
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 23ad35b18..70bf66024 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -8500,7 +8500,7 @@ static int cli_io_handler_tlskeys_files(struct appctx *appctx) {
 }
 
 /* sets cli.i0 to non-zero if only file lists should be dumped */
-static int cli_parse_show_tlskeys(char **args, struct appctx *appctx, void *private)
+static int cli_parse_show_tlskeys(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	/* no parameter, shows only file list */
 	if (!*args[2]) {
@@ -8525,7 +8525,7 @@ static int cli_parse_show_tlskeys(char **args, struct appctx *appctx, void *priv
 	return 0;
 }
 
-static int cli_parse_set_tlskeys(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_tlskeys(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct tls_keys_ref *ref;
 
@@ -8561,7 +8561,7 @@ static int cli_parse_set_tlskeys(char **args, struct appctx *appctx, void *priva
 }
 #endif
 
-static int cli_parse_set_ocspresponse(char **args, struct appctx *appctx, void *private)
+static int cli_parse_set_ocspresponse(char **args, char *payload, struct appctx *appctx, void *private)
 {
 #if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP)
 	char *err = NULL;
diff --git a/src/stats.c b/src/stats.c
index b592ced57..7ad30a178 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -3524,7 +3524,7 @@ static int stats_dump_json_schema_to_buffer(struct stream_interface *si)
 	return 1;
 }
 
-static int cli_parse_clear_counters(char **args, struct appctx *appctx, void *private)
+static int cli_parse_clear_counters(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct proxy *px;
 	struct server *sv;
@@ -3586,7 +3586,7 @@ static int cli_parse_clear_counters(char **args, struct appctx *appctx, void *pr
 }
 
 
-static int cli_parse_show_info(char **args, struct appctx *appctx, void *private)
+static int cli_parse_show_info(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	appctx->ctx.stats.scope_str = 0;
 	appctx->ctx.stats.scope_len = 0;
@@ -3600,7 +3600,7 @@ static int cli_parse_show_info(char **args, struct appctx *appctx, void *private
 }
 
 
-static int cli_parse_show_stat(char **args, struct appctx *appctx, void *private)
+static int cli_parse_show_stat(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	appctx->ctx.stats.scope_str = 0;
 	appctx->ctx.stats.scope_len = 0;
diff --git a/src/stick_table.c b/src/stick_table.c
index 73c70d3e2..3e44747c1 100644
--- a/src/stick_table.c
+++ b/src/stick_table.c
@@ -3375,7 +3375,7 @@ static int table_prepare_data_request(struct appctx *appctx, char **args)
 }
 
 /* returns 0 if wants to be called, 1 if has ended processing */
-static int cli_parse_table_req(char **args, struct appctx *appctx, void *private)
+static int cli_parse_table_req(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	appctx->ctx.table.data_type = -1;
 	appctx->ctx.table.target = NULL;
diff --git a/src/stream.c b/src/stream.c
index 2d8f27834..1d0b22ca3 100644
--- a/src/stream.c
+++ b/src/stream.c
@@ -3025,7 +3025,7 @@ static int stats_dump_full_strm_to_buffer(struct stream_interface *si, struct st
 }
 
 
-static int cli_parse_show_sess(char **args, struct appctx *appctx, void *private)
+static int cli_parse_show_sess(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (!cli_has_level(appctx, ACCESS_LVL_OPER))
 		return 1;
@@ -3280,7 +3280,7 @@ static void cli_release_show_sess(struct appctx *appctx)
 }
 
 /* Parses the "shutdown session" directive, it always returns 1 */
-static int cli_parse_shutdown_session(char **args, struct appctx *appctx, void *private)
+static int cli_parse_shutdown_session(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct stream *strm, *ptr;
 
@@ -3315,7 +3315,7 @@ static int cli_parse_shutdown_session(char **args, struct appctx *appctx, void *
 }
 
 /* Parses the "shutdown session server" directive, it always returns 1 */
-static int cli_parse_shutdown_sessions_server(char **args, struct appctx *appctx, void *private)
+static int cli_parse_shutdown_sessions_server(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct server *sv;
 	struct stream *strm, *strm_bck;
-- 
2.11.0

>From 63439f2b089613973f72a37dd5dda17f45f26545 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien=20Nephtali?= <aurelien.nepht...@corp.ovh.com>
Date: Wed, 18 Apr 2018 14:04:47 +0200
Subject: [PATCH 2/3] MINOR: map: Add payload support to "add map"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

It is now possible to use a payload with the "add map" command.
These syntaxes will work the same way:

 # echo "add map #-1 key value" | socat /tmp/sock1 -

 # echo -e "add map #-1 <<\n$(cat data)\n" | socat /tmp/sock1 -

with

 # cat data
 key1 value1 with spaces
 key2 value2
 key3 value3 also with spaces

Signed-off-by: Aurélien Nephtali <aurelien.nepht...@corp.ovh.com>
---
 src/map.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 79 insertions(+), 21 deletions(-)

diff --git a/src/map.c b/src/map.c
index d02a0255c..64222dc2e 100644
--- a/src/map.c
+++ b/src/map.c
@@ -772,6 +772,20 @@ static int cli_parse_set_map(char **args, char *payload, struct appctx *appctx,
 	return 1;
 }
 
+static int map_add_key_value(struct appctx *appctx, const char *key, const char *value, char **err)
+{
+	int ret;
+
+	HA_SPIN_LOCK(PATREF_LOCK, &appctx->ctx.map.ref->lock);
+	if (appctx->ctx.map.display_flags == PAT_REF_MAP)
+		ret = pat_ref_add(appctx->ctx.map.ref, key, value, err);
+	else
+		ret = pat_ref_add(appctx->ctx.map.ref, key, NULL, err);
+	HA_SPIN_UNLOCK(PATREF_LOCK, &appctx->ctx.map.ref->lock);
+
+	return ret;
+}
+
 static int cli_parse_add_map(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	if (strcmp(args[1], "map") == 0 ||
@@ -785,13 +799,16 @@ static int cli_parse_add_map(char **args, char *payload, struct appctx *appctx,
 		else
 			appctx->ctx.map.display_flags = PAT_REF_ACL;
 
-		/* If the keywork is "map", we expect three parameters, if it
-		 * is "acl", we expect only two parameters
+		/* If the keyword is "map", we expect:
+		 *   - three parameters if there is no payload
+		 *   - one parameter if there is a payload
+		 * If it is "acl", we expect only two parameters
 		 */
 		if (appctx->ctx.map.display_flags == PAT_REF_MAP) {
-			if (!*args[2] || !*args[3] || !*args[4]) {
+			if ((!payload && (!*args[2] || !*args[3] || !*args[4])) ||
+			    (payload && !*args[2])) {
 				appctx->ctx.cli.severity = LOG_ERR;
-				appctx->ctx.cli.msg = "'add map' expects three parameters: map identifier, key and value.\n";
+				appctx->ctx.cli.msg = "'add map' expects three parameters (map identifier, key and value) or one parameter (map identifier) and a payload\n";
 				appctx->st0 = CLI_ST_PRINT;
 				return 1;
 			}
@@ -832,26 +849,67 @@ static int cli_parse_add_map(char **args, char *payload, struct appctx *appctx,
 			return 1;
 		}
 
-		/* Add value. */
+		/* Add value(s). */
 		err = NULL;
-		HA_SPIN_LOCK(PATREF_LOCK, &appctx->ctx.map.ref->lock);
-		if (appctx->ctx.map.display_flags == PAT_REF_MAP)
-			ret = pat_ref_add(appctx->ctx.map.ref, args[3], args[4], &err);
-		else
-			ret = pat_ref_add(appctx->ctx.map.ref, args[3], NULL, &err);
-		HA_SPIN_UNLOCK(PATREF_LOCK, &appctx->ctx.map.ref->lock);
-		if (!ret) {
-			if (err) {
-				memprintf(&err, "%s.\n", err);
-				appctx->ctx.cli.err = err;
-				appctx->st0 = CLI_ST_PRINT_FREE;
+		if (!payload) {
+			ret = map_add_key_value(appctx, args[3], args[4], &err);
+			if (!ret) {
+				if (err) {
+					memprintf(&err, "%s.\n", err);
+					appctx->ctx.cli.err = err;
+					appctx->st0 = CLI_ST_PRINT_FREE;
+				}
+				else {
+					appctx->ctx.cli.severity = LOG_ERR;
+					appctx->ctx.cli.msg = "Failed to add an entry.\n";
+					appctx->st0 = CLI_ST_PRINT;
+				}
+				return 1;
 			}
-			else {
-				appctx->ctx.cli.severity = LOG_ERR;
-				appctx->ctx.cli.msg = "Failed to add an entry.\n";
-				appctx->st0 = CLI_ST_PRINT;
+		}
+		else {
+			const char *end = payload + strlen(payload);
+
+			while (payload < end) {
+				char *key, *value;
+				size_t l;
+
+				/* key */
+				key = payload;
+				l = strcspn(key, " \t");
+				payload += l;
+
+				if (!*payload && appctx->ctx.map.display_flags == PAT_REF_MAP) {
+					memprintf(&err, "Missing value for key '%s'.\n", key);
+					appctx->ctx.cli.err = err;
+					appctx->st0 = CLI_ST_PRINT_FREE;
+					return 1;
+				}
+				key[l] = 0;
+				payload++;
+
+				/* value */
+				payload += strspn(payload, " \t");
+				value = payload;
+				l = strcspn(value, "\n");
+				value[l] = 0;
+				payload += l + 1;
+
+				ret = map_add_key_value(appctx, key, value, &err);
+				if (!ret) {
+					if (err) {
+						memprintf(&err, "%s.\n", err);
+						appctx->ctx.cli.err = err;
+						appctx->st0 = CLI_ST_PRINT_FREE;
+					}
+					else {
+						appctx->ctx.cli.severity = LOG_ERR;
+						appctx->ctx.cli.msg = "Failed to add a key.\n";
+						appctx->st0 = CLI_ST_PRINT;
+					}
+					return 1;
+				}
 			}
-			return 1;
 		}
 
 		/* The add is done, send message. */
-- 
2.11.0

>From 1b7f6c40b010521a8b55103c984d3c8f305e818c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien=20Nephtali?= <aurelien.nepht...@corp.ovh.com>
Date: Wed, 18 Apr 2018 14:04:58 +0200
Subject: [PATCH 3/3] MINOR: ssl: Add payload support to "set ssl
 ocsp-response"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

It is now possible to use a payload with the "set ssl ocsp-response"
command.  These syntaxes will work the same way:

 # echo "set ssl ocsp-response $(base64 -w 10000 ocsp.der)" | \
     socat /tmp/sock1 -

 # echo -e "set ssl ocsp-response <<\n$(base64 ocsp.der)\n" | \
     socat /tmp/sock1 -

Signed-off-by: Aurélien Nephtali <aurelien.nepht...@corp.ovh.com>
---
 src/ssl_sock.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 70bf66024..ddcc95cb0 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -8565,16 +8565,27 @@ static int cli_parse_set_ocspresponse(char **args, char *payload, struct appctx
 {
 #if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP)
 	char *err = NULL;
+	int i, j;
+
+	if (!payload)
+		payload = args[3];
 
 	/* Expect one parameter: the new response in base64 encoding */
-	if (!*args[3]) {
+	if (!*payload) {
 		appctx->ctx.cli.severity = LOG_ERR;
 		appctx->ctx.cli.msg = "'set ssl ocsp-response' expects response in base64 encoding.\n";
 		appctx->st0 = CLI_ST_PRINT;
 		return 1;
 	}
 
-	trash.len = base64dec(args[3], strlen(args[3]), trash.str, trash.size);
+	/* remove \r and \n from the payload */
+	for (i = 0, j = 0; payload[i]; i++) {
+		if (payload[i] == '\r' || payload[i] == '\n')
+			continue;
+		payload[j++] = payload[i];
+	}
+
+	trash.len = base64dec(payload, strlen(payload), trash.str, trash.size);
 	if (trash.len < 0) {
 		appctx->ctx.cli.severity = LOG_ERR;
 		appctx->ctx.cli.msg = "'set ssl ocsp-response' received invalid base64 encoded response.\n";
-- 
2.11.0

Reply via email to