MINOR: cli: add a new "show env" command

Using environment variables in configuration files can make troubleshooting
complicated because there's no easy way to verify that the variables are
correct. This patch introduces a new "show env" command which displays the
whole environment on the CLI, one variable per line.

The socket must at least have level operator to display the environment.
diff --git a/doc/management.txt b/doc/management.txt
index 466f40e..1b677be 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -1472,6 +1472,16 @@
   "admin". Both the backend and the server may be specified either by their
   name or by their numeric ID, prefixed with a sharp ('#').
 
+show env [<name>]
+  Dump one or all environment variables known by the process. Without any
+  argument, all variables are dumped. With an argument, only the specified
+  variable is dumped if it exists. Otherwise "Variable not found" is emitted.
+  Variables are dumped in the same format as they are stored or returned by the
+  "env" utility, that is, "<name>=<value>". This can be handy when debugging
+  certain configuration files making heavy use of environment variables to
+  ensure that they contain the expected values. This command is restricted and
+  can only be issued on sockets configured for levels "operator" or "admin".
+
 show errors [<iid>]
   Dump last known request and response errors collected by frontends and
   backends. If <iid> is specified, the limit the dump to errors concerning
diff --git a/include/types/applet.h b/include/types/applet.h
index 2e1626a..562575f 100644
--- a/include/types/applet.h
+++ b/include/types/applet.h
@@ -133,6 +133,9 @@
 		struct {
 			struct proxy *backend;
 		} server_state;
+		struct {
+			char **var;
+		} env;
 	} ctx;					/* used by stats I/O handlers to dump the stats */
 };
 
diff --git a/src/dumpstats.c b/src/dumpstats.c
index b868ef5..8b55fe3 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -94,6 +94,7 @@
 	STAT_CLI_O_RESOLVERS,/* dump a resolver's section nameservers counters */
 	STAT_CLI_O_SERVERS_STATE, /* dump server state and changing information */
 	STAT_CLI_O_BACKEND,  /* dump backend list */
+	STAT_CLI_O_ENV,      /* dump environment */
 };
 
 /* Actions available for the stats admin forms */
@@ -130,6 +131,7 @@
 };
 
 static int stats_dump_backend_to_buffer(struct stream_interface *si);
+static int stats_dump_env_to_buffer(struct stream_interface *si);
 static int stats_dump_info_to_buffer(struct stream_interface *si);
 static int stats_dump_servers_state_to_buffer(struct stream_interface *si);
 static int stats_dump_pools_to_buffer(struct stream_interface *si);
@@ -191,6 +193,7 @@
 	"  prompt         : toggle interactive mode with prompt\n"
 	"  quit           : disconnect\n"
 	"  show backend   : list backends in the current running config\n"
+	"  show env [var] : dump environment variables known to the process\n"
 	"  show info      : report information about the running process\n"
 	"  show pools     : report information about the memory pools usage\n"
 	"  show stat      : report counters for each proxy and server\n"
@@ -1162,7 +1165,35 @@
 			appctx->st2 = STAT_ST_INIT;
 			appctx->st0 = STAT_CLI_O_BACKEND;
 		}
-		 else if (strcmp(args[1], "stat") == 0) {
+		else if (strcmp(args[1], "env") == 0) {
+			extern char **environ;
+
+			if (strm_li(s)->bind_conf->level < ACCESS_LVL_OPER) {
+				appctx->ctx.cli.msg = stats_permission_denied_msg;
+				appctx->st0 = STAT_CLI_PRINT;
+				return 1;
+			}
+			appctx->ctx.env.var = environ;
+			appctx->st2 = STAT_ST_INIT;
+			appctx->st0 = STAT_CLI_O_ENV; // stats_dump_env_to_buffer
+
+			if (*args[2]) {
+				int len = strlen(args[2]);
+
+				for (; *appctx->ctx.env.var; appctx->ctx.env.var++) {
+					if (strncmp(*appctx->ctx.env.var, args[2], len) == 0 &&
+					    (*appctx->ctx.env.var)[len] == '=')
+						break;
+				}
+				if (!*appctx->ctx.env.var) {
+					appctx->ctx.cli.msg = "Variable not found\n";
+					appctx->st0 = STAT_CLI_PRINT;
+					return 1;
+				}
+				appctx->st2 = STAT_ST_END;
+			}
+		}
+		else if (strcmp(args[1], "stat") == 0) {
 			if (strcmp(args[2], "resolvers") == 0) {
 				struct dns_resolvers *presolvers;
 
@@ -2589,6 +2620,10 @@
 					appctx->st0 = STAT_CLI_PROMPT;
 				break;
 #endif
+			case STAT_CLI_O_ENV:	/* environment dump */
+				if (stats_dump_env_to_buffer(si))
+					appctx->st0 = STAT_CLI_PROMPT;
+				break;
 			default: /* abnormal state */
 				si->flags |= SI_FL_ERR;
 				break;
@@ -6649,6 +6684,38 @@
 			appctx->ctx.errors.buf = 0;
 			appctx->ctx.errors.px = appctx->ctx.errors.px->next;
 		}
+	}
+
+	/* dump complete */
+	return 1;
+}
+
+/* This function dumps all environmnent variables to the buffer. It returns 0
+ * if the output buffer is full and it needs to be called again, otherwise
+ * non-zero. Dumps only one entry if st2 == STAT_ST_END.
+ */
+static int stats_dump_env_to_buffer(struct stream_interface *si)
+{
+	struct appctx *appctx = __objt_appctx(si->end);
+
+	if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
+		return 1;
+
+	chunk_reset(&trash);
+
+	/* we have two inner loops here, one for the proxy, the other one for
+	 * the buffer.
+	 */
+	while (*appctx->ctx.env.var) {
+		chunk_printf(&trash, "%s\n", *appctx->ctx.env.var);
+
+		if (bi_putchk(si_ic(si), &trash) == -1) {
+			si_applet_cant_put(si);
+			return 0;
+		}
+		if (appctx->st2 == STAT_ST_END)
+			break;
+		appctx->ctx.env.var++;
 	}
 
 	/* dump complete */