MEDIUM: listener: add a minimal framework to register "bind" keyword options

With the arrival of SSL, the "bind" keyword has received even more options,
all of which are processed in cfgparse in a cumbersome way. So it's time to
let modules register their own bind options. This is done very similarly to
the ACLs with a small difference in that we make the difference between an
unknown option and a known, unimplemented option.
diff --git a/include/proto/listener.h b/include/proto/listener.h
index 4019ff1..e570a9b 100644
--- a/include/proto/listener.h
+++ b/include/proto/listener.h
@@ -105,6 +105,15 @@
  */
 int listener_accept(int fd);
 
+/*
+ * Registers the bind keyword list <kwl> as a list of valid keywords for next
+ * parsing sessions.
+ */
+void bind_register_keywords(struct bind_kw_list *kwl);
+
+/* Return a pointer to the bind keyword <kw>, or NULL if not found. */
+struct bind_kw *bind_find_kw(const char *kw);
+
 /* allocate an bind_conf struct for a bind line, and chain it to list head <lh>.
  * If <arg> is not NULL, it is duplicated into ->arg to store useful config
  * information for error reporting.
diff --git a/include/types/listener.h b/include/types/listener.h
index 2406148..f21a28d 100644
--- a/include/types/listener.h
+++ b/include/types/listener.h
@@ -156,6 +156,31 @@
 	} conf;				/* config information */
 };
 
+/* Descriptor for a "bind" keyword. The ->parse() function returns 0 in case of
+ * success, or a combination of ERR_* flags if an error is encountered. The
+ * function pointer can be NULL if not implemented. The function also has an
+ * access to the current "bind" conf, which is the conf of the last listener,
+ * reachable via px->listen->bind_conf. The ->skip value tells the parser how
+ * many words have to be skipped after the keyword.
+ */
+struct bind_kw {
+	const char *kw;
+	int (*parse)(char **args, int cur_arg, struct proxy *px, struct listener *last, char **err);
+	int skip; /* nb of args to skip */
+};
+
+/*
+ * A keyword list. It is a NULL-terminated array of keywords. It embeds a
+ * struct list in order to be linked to other lists, allowing it to easily
+ * be declared where it is needed, and linked without duplicating data nor
+ * allocating memory.
+ */
+struct bind_kw_list {
+	struct list list;
+	struct bind_kw kw[VAR_ARRAY];
+};
+
+
 #endif /* _TYPES_LISTENER_H */
 
 /*
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 07b7347..c0b29c6 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1711,6 +1711,8 @@
 		}
 		cur_arg = 2;
 		while (*(args[cur_arg])) {
+			struct bind_kw *kw;
+
 			if (!strcmp(args[cur_arg], "interface")) { /* specifically bind to this interface */
 #ifdef SO_BINDTODEVICE
 				struct listener *l;
@@ -2147,6 +2149,41 @@
 				continue;
                         }
 
+			kw = bind_find_kw(args[cur_arg]);
+			if (kw) {
+				char *err = NULL;
+				int code;
+
+				if (!kw->parse) {
+					Alert("parsing [%s:%d] : '%s' : '%s' option is not implemented in this version (check build options).\n",
+					      file, linenum, args[0], args[cur_arg]);
+					cur_arg += 1 + kw->skip ;
+					err_code |= ERR_ALERT | ERR_FATAL;
+					goto out;
+				}
+
+				code = kw->parse(args, cur_arg, curproxy, last_listen, &err);
+				err_code |= code;
+
+				if (code) {
+					if (err && *err) {
+						indent_msg(&err, 2);
+						Alert("parsing [%s:%d] : '%s' : %s\n", file, linenum, args[0], err);
+					}
+					else
+						Alert("parsing [%s:%d] : '%s' : error encountered while processing '%s'.\n",
+						      file, linenum, args[0], args[cur_arg]);
+					if (code & ERR_FATAL) {
+						free(err);
+						cur_arg += 1 + kw->skip;
+						goto out;
+					}
+				}
+				free(err);
+				cur_arg += 1 + kw->skip;
+				continue;
+			}
+
 			Alert("parsing [%s:%d] : '%s' only supports the 'transparent', 'accept-proxy', 'defer-accept', 'name', 'id', 'mss', 'mode', 'uid', 'gid', 'user', 'group' and 'interface' options.\n",
 			      file, linenum, args[0]);
 			err_code |= ERR_ALERT | ERR_FATAL;
diff --git a/src/listener.c b/src/listener.c
index 0e864b2..13eb272 100644
--- a/src/listener.c
+++ b/src/listener.c
@@ -29,6 +29,11 @@
 #include <proto/log.h>
 #include <proto/task.h>
 
+/* List head of all known bind keywords */
+static struct bind_kw_list bind_keywords = {
+	.list = LIST_HEAD_INIT(bind_keywords.list)
+};
+
 /* This function adds the specified listener's file descriptor to the polling
  * lists if it is in the LI_LISTEN state. The listener enters LI_READY or
  * LI_FULL state depending on its number of connections.
@@ -409,6 +414,47 @@
 	return;
 }
 
+/*
+ * Registers the bind keyword list <kwl> as a list of valid keywords for next
+ * parsing sessions.
+ */
+void bind_register_keywords(struct bind_kw_list *kwl)
+{
+	LIST_ADDQ(&bind_keywords.list, &kwl->list);
+}
+
+/* Return a pointer to the bind keyword <kw>, or NULL if not found. If the
+ * keyword is found with a NULL ->parse() function, then an attempt is made to
+ * find one with a valid ->parse() function. This way it is possible to declare
+ * platform-dependant, known keywords as NULL, then only declare them as valid
+ * if some options are met. Note that if the requested keyword contains an
+ * opening parenthesis, everything from this point is ignored.
+ */
+struct bind_kw *bind_find_kw(const char *kw)
+{
+	int index;
+	const char *kwend;
+	struct bind_kw_list *kwl;
+	struct bind_kw *ret = NULL;
+
+	kwend = strchr(kw, '(');
+	if (!kwend)
+		kwend = kw + strlen(kw);
+
+	list_for_each_entry(kwl, &bind_keywords.list, list) {
+		for (index = 0; kwl->kw[index].kw != NULL; index++) {
+			if ((strncmp(kwl->kw[index].kw, kw, kwend - kw) == 0) &&
+			    kwl->kw[index].kw[kwend-kw] == 0) {
+				if (kwl->kw[index].parse)
+					return &kwl->kw[index]; /* found it !*/
+				else
+					ret = &kwl->kw[index];  /* may be OK */
+			}
+		}
+	}
+	return ret;
+}
+
 /************************************************************************/
 /*           All supported ACL keywords must be declared here.          */
 /************************************************************************/