MEDIUM: cli: 'show cli sockets' list the CLI sockets

'show cli sockets' from the CLI socket displays the list of CLI sockets
available, with their level and process number.
diff --git a/include/types/applet.h b/include/types/applet.h
index 29f6f5c..41dc4f2 100644
--- a/include/types/applet.h
+++ b/include/types/applet.h
@@ -162,6 +162,7 @@
 			unsigned int max_frame_size;
 			struct list  list;
 		} spoe;                         /* used by SPOE filter */
+		struct list *cli_socket;        /* pointer to the latest dumped CLI socket in the list */
 	} ctx;					/* used by stats I/O handlers to dump the stats */
 };
 
diff --git a/src/cli.c b/src/cli.c
index 3d537ba..c37feb7 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -728,6 +728,104 @@
 	return 1;
 }
 
+/*
+ * CLI IO handler for `show cli sockets`
+ */
+static int cli_io_handler_show_cli_sock(struct appctx *appctx)
+{
+	struct bind_conf *bind_conf;
+	struct stream_interface *si = appctx->owner;
+
+	chunk_reset(&trash);
+
+	switch (appctx->st2) {
+		case STAT_ST_INIT:
+			chunk_printf(&trash, "# socket lvl processes\n");
+			if (bi_putchk(si_ic(si), &trash) == -1) {
+				si_applet_cant_put(si);
+				return 0;
+			}
+			appctx->ctx.cli_socket = NULL;
+			appctx->st2 = STAT_ST_LIST;
+
+		case STAT_ST_LIST:
+			if (global.stats_fe) {
+				list_for_each_entry(bind_conf, &global.stats_fe->conf.bind, by_fe) {
+					struct listener *l;
+
+					/*
+					 * get the latest dumped node in appctx->ctx.cli_socket
+					 * if the current node is the first of the list
+					 */
+
+					if (appctx->ctx.cli_socket  &&
+					    &bind_conf->by_fe == (&global.stats_fe->conf.bind)->n
+					   ) {
+						/* change the current node to the latest dumped and continue the loop */
+						bind_conf = LIST_ELEM(appctx->ctx.cli_socket, typeof(bind_conf), by_fe);
+						continue;
+					}
+
+					list_for_each_entry(l, &bind_conf->listeners, by_bind) {
+
+						char addr[46];
+						char port[6];
+
+						if (l->addr.ss_family == AF_UNIX) {
+							const struct sockaddr_un *un;
+
+							un = (struct sockaddr_un *)&l->addr;
+							chunk_appendf(&trash, "%s ", un->sun_path);
+						} else if (l->addr.ss_family == AF_INET) {
+							addr_to_str(&l->addr, addr, sizeof(addr));
+							port_to_str(&l->addr, port, sizeof(port));
+							chunk_appendf(&trash, "%s:%s ", addr, port);
+						} else if (l->addr.ss_family == AF_INET6) {
+							addr_to_str(&l->addr, addr, sizeof(addr));
+							port_to_str(&l->addr, port, sizeof(port));
+							chunk_appendf(&trash, "[%s]:%s ", addr, port);
+						} else
+							continue;
+
+						if (bind_conf->level == ACCESS_LVL_USER)
+							chunk_appendf(&trash, "user ");
+						else if (bind_conf->level == ACCESS_LVL_OPER)
+							chunk_appendf(&trash, "operator ");
+						else if (bind_conf->level == ACCESS_LVL_ADMIN)
+							chunk_appendf(&trash, "admin ");
+						else
+							chunk_appendf(&trash, "  ");
+
+						if (bind_conf->bind_proc != 0) {
+							int pos;
+
+							for (pos = 0; pos < sizeof(bind_conf->bind_proc); pos++) {
+								if (bind_conf->bind_proc & (1 << pos)) {
+									chunk_appendf(&trash, "%d,", pos+1);
+								}
+							}
+							/* replace the latest comma by a newline */
+							trash.str[trash.len-1] = '\n';
+
+						} else {
+							chunk_appendf(&trash, "all\n");
+						}
+
+						if (bi_putchk(si_ic(si), &trash) == -1) {
+							si_applet_cant_put(si);
+							return 0;
+						}
+					}
+					appctx->ctx.cli_socket = &bind_conf->by_fe; /* store the latest list node dumped */
+				}
+			}
+		default:
+			appctx->st2 = STAT_ST_FIN;
+			return 1;
+	}
+}
+
+
 /* parse a "show env" CLI request. Returns 0 if it needs to continue, 1 if it
  * wants to stop here.
  */
@@ -826,6 +924,12 @@
 	return 1;
 }
 
+
+int cli_parse_default(char **args, 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)
 {
@@ -919,6 +1023,7 @@
 	{ { "set", "rate-limit", NULL }, "set rate-limit : change a rate limiting value", cli_parse_set_ratelimit, NULL },
 	{ { "set", "timeout",  NULL }, "set timeout    : change a timeout setting", cli_parse_set_timeout, NULL, NULL },
 	{ { "show", "env",  NULL }, "show env [var] : dump environment variables known to the process", cli_parse_show_env, cli_io_handler_show_env, NULL },
+	{ { "show", "cli", "sockets",  NULL }, "show cli sockets : dump list of cli sockets", cli_parse_default, cli_io_handler_show_cli_sock, NULL },
 	{{},}
 }};