MINOR: config: add a function to dump all known config keywords

All registered config keywords that are valid in the config parser are
dumped to stdout organized like the regular sections (global, listen,
etc). Some keywords that are known to only be valid in frontends or
backends will be suffixed with [FE] or [BE].

All regularly registered "bind" and "server" keywords are also dumped,
one per "bind" or "server" line. Those depending on ssl are listed after
the "ssl" keyword. Doing so required to export the listener and server
keyword lists that were static.

The function is called from dump_registered_keywords() for keyword
class "cfg".
diff --git a/include/haproxy/cfgparse.h b/include/haproxy/cfgparse.h
index 72a3720..2708f76 100644
--- a/include/haproxy/cfgparse.h
+++ b/include/haproxy/cfgparse.h
@@ -126,6 +126,7 @@
 const char *cfg_find_best_match(const char *word, const struct list *list, int section, const char **extra);
 int warnifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint);
 int failifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint);
+void cfg_dump_registered_keywords();
 
 /* simplified way to define a section parser */
 #define REGISTER_CONFIG_SECTION(name, parse, post)                            \
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 036a303..6dd8210 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -4241,6 +4241,128 @@
 	}
 }
 
+/* dumps all registered keywords by section on stdout */
+void cfg_dump_registered_keywords()
+{
+	const char* sect_names[] = { "", "global", "listen", "userlist", "peers", 0 };
+	int section;
+	int index;
+
+	for (section = 1; sect_names[section]; section++) {
+		struct cfg_kw_list *kwl;
+
+		printf("%s\n", sect_names[section]);
+		list_for_each_entry(kwl, &cfg_keywords.list, list) {
+			for (index = 0; kwl->kw[index].kw != NULL; index++)
+				if (kwl->kw[index].section == section)
+					printf("\t%s\n", kwl->kw[index].kw);
+		}
+
+		if (section == CFG_LISTEN) {
+			/* there are plenty of other keywords there */
+			extern struct list tcp_req_conn_keywords, tcp_req_sess_keywords,
+				tcp_req_cont_keywords, tcp_res_cont_keywords;
+			extern struct bind_kw_list bind_keywords;
+			extern struct ssl_bind_kw ssl_bind_kws[] __maybe_unused;
+			extern struct srv_kw_list srv_keywords;
+			struct action_kw_list *akwl;
+			struct bind_kw_list *bkwl;
+			struct srv_kw_list *skwl;
+
+			list_for_each_entry(bkwl, &bind_keywords.list, list) {
+				for (index = 0; bkwl->kw[index].kw != NULL; index++) {
+					if (!bkwl->kw[index].skip)
+						printf("\tbind <addr> %s\n", bkwl->kw[index].kw);
+					else
+						printf("\tbind <addr> %s +%d\n", bkwl->kw[index].kw, bkwl->kw[index].skip);
+				}
+			}
+
+#if defined(USE_OPENSSL)
+			for (index = 0; ssl_bind_kws[index].kw != NULL; index++) {
+				if (!ssl_bind_kws[index].skip)
+					printf("\tbind <addr> ssl %s\n", ssl_bind_kws[index].kw);
+				else
+					printf("\tbind <addr> ssl %s +%d\n", ssl_bind_kws[index].kw, ssl_bind_kws[index].skip);
+			}
+#endif
+
+			list_for_each_entry(skwl, &srv_keywords.list, list) {
+				for (index = 0; skwl->kw[index].kw != NULL; index++) {
+					if (!skwl->kw[index].skip)
+						printf("\tserver <name> <addr> %s\n", skwl->kw[index].kw);
+					else
+						printf("\tserver <name> <addr> %s +%d\n", skwl->kw[index].kw, skwl->kw[index].skip);
+				}
+			}
+
+			for (index = 0; cfg_opts[index].name; index++) {
+				printf("\toption %s [ ", cfg_opts[index].name);
+				if (cfg_opts[index].cap & PR_CAP_FE)
+					printf("FE ");
+				if (cfg_opts[index].cap & PR_CAP_BE)
+					printf("BE ");
+				if (cfg_opts[index].mode == PR_MODE_HTTP)
+					printf("HTTP ");
+				printf("]\n");
+			}
+
+			for (index = 0; cfg_opts2[index].name; index++) {
+				printf("\toption %s [ ", cfg_opts2[index].name);
+				if (cfg_opts2[index].cap & PR_CAP_FE)
+					printf("FE ");
+				if (cfg_opts2[index].cap & PR_CAP_BE)
+					printf("BE ");
+				if (cfg_opts2[index].mode == PR_MODE_HTTP)
+					printf("HTTP ");
+				printf("]\n");
+			}
+
+			list_for_each_entry(akwl, &tcp_req_conn_keywords, list) {
+				for (index = 0; akwl->kw[index].kw != NULL; index++)
+					printf("\ttcp-request connection %s%s\n", akwl->kw[index].kw,
+					       (akwl->kw[index].flags & KWF_MATCH_PREFIX) ? "*" : "");
+			}
+
+			list_for_each_entry(akwl, &tcp_req_sess_keywords, list) {
+				for (index = 0; akwl->kw[index].kw != NULL; index++)
+					printf("\ttcp-request session %s%s\n", akwl->kw[index].kw,
+					       (akwl->kw[index].flags & KWF_MATCH_PREFIX) ? "*" : "");
+			}
+
+			list_for_each_entry(akwl, &tcp_req_cont_keywords, list) {
+				for (index = 0; akwl->kw[index].kw != NULL; index++)
+					printf("\ttcp-request content %s%s\n", akwl->kw[index].kw,
+					       (akwl->kw[index].flags & KWF_MATCH_PREFIX) ? "*" : "");
+			}
+
+			list_for_each_entry(akwl, &tcp_res_cont_keywords, list) {
+				for (index = 0; akwl->kw[index].kw != NULL; index++)
+					printf("\ttcp-response content %s%s\n", akwl->kw[index].kw,
+					       (akwl->kw[index].flags & KWF_MATCH_PREFIX) ? "*" : "");
+			}
+
+			list_for_each_entry(akwl, &http_req_keywords.list, list) {
+				for (index = 0; akwl->kw[index].kw != NULL; index++)
+					printf("\thttp-request %s%s\n", akwl->kw[index].kw,
+					       (akwl->kw[index].flags & KWF_MATCH_PREFIX) ? "*" : "");
+			}
+
+			list_for_each_entry(akwl, &http_res_keywords.list, list) {
+				for (index = 0; akwl->kw[index].kw != NULL; index++)
+					printf("\thttp-response %s%s\n", akwl->kw[index].kw,
+					       (akwl->kw[index].flags & KWF_MATCH_PREFIX) ? "*" : "");
+			}
+
+			list_for_each_entry(akwl, &http_after_res_keywords.list, list) {
+				for (index = 0; akwl->kw[index].kw != NULL; index++)
+					printf("\thttp-after-response %s%s\n", akwl->kw[index].kw,
+					       (akwl->kw[index].flags & KWF_MATCH_PREFIX) ? "*" : "");
+			}
+		}
+	}
+}
+
 /* these are the config sections handled by default */
 REGISTER_CONFIG_SECTION("listen",         cfg_parse_listen,    NULL);
 REGISTER_CONFIG_SECTION("frontend",       cfg_parse_listen,    NULL);
diff --git a/src/haproxy.c b/src/haproxy.c
index a037611..b5f368d 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -1825,11 +1825,17 @@
 		if (strcmp(kwd_dump, "help") == 0) {
 			printf("# List of supported keyword classes:\n");
 			printf("all: list all keywords\n");
+			printf("cfg: configuration keywords\n");
 			continue;
 		}
 		else if (strcmp(kwd_dump, "all") == 0) {
 			all = 1;
 		}
+
+		if (all || strcmp(kwd_dump, "cfg") == 0) {
+			printf("# List of registered configuration keywords:\n");
+			cfg_dump_registered_keywords();
+		}
 	}
 }
 
diff --git a/src/listener.c b/src/listener.c
index 82463b8..1e1a832 100644
--- a/src/listener.c
+++ b/src/listener.c
@@ -40,7 +40,7 @@
 
 
 /* List head of all known bind keywords */
-static struct bind_kw_list bind_keywords = {
+struct bind_kw_list bind_keywords = {
 	.list = LIST_HEAD_INIT(bind_keywords.list)
 };
 
diff --git a/src/server.c b/src/server.c
index d3f86b0..a87f864 100644
--- a/src/server.c
+++ b/src/server.c
@@ -62,7 +62,7 @@
 };
 
 /* List head of all known server keywords */
-static struct srv_kw_list srv_keywords = {
+struct srv_kw_list srv_keywords = {
 	.list = LIST_HEAD_INIT(srv_keywords.list)
 };