MINOR: log: adds syslog udp message handler and parsing.

This patch introduce a new fd handler used to parse syslog
message on udp.

The parsing function returns level, facility and metadata that
can be immediatly reused to forward message to a log server.

This handler is enabled on udp listeners if proxy is internally set
to mode PR_MODE_SYSLOG
diff --git a/include/haproxy/log.h b/include/haproxy/log.h
index 7f78137..683c135 100644
--- a/include/haproxy/log.h
+++ b/include/haproxy/log.h
@@ -46,6 +46,8 @@
 extern THREAD_LOCAL char *logline;
 extern THREAD_LOCAL char *logline_rfc5424;
 
+/* syslog UDP message handler */
+void syslog_fd_handler(int fd);
 
 /* Initialize/Deinitialize log buffers used for syslog messages */
 int init_log_buffers();
diff --git a/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h
index c7e4999..6216524 100644
--- a/include/haproxy/proxy-t.h
+++ b/include/haproxy/proxy-t.h
@@ -56,6 +56,8 @@
 	PR_MODE_HTTP,
 	PR_MODE_HEALTH,
 	PR_MODE_CLI,
+	PR_MODE_SYSLOG,
+	PR_MODES
 } __attribute__((packed));
 
 enum PR_SRV_STATE_FILE {
diff --git a/src/cfgparse.c b/src/cfgparse.c
index e46a4f6..f408d65 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2377,6 +2377,13 @@
 		case PR_MODE_CLI:
 			cfgerr += proxy_cfg_ensure_no_http(curproxy);
 			break;
+		case PR_MODE_SYSLOG:
+		case PR_MODES:
+			/* should not happen, bug gcc warn missing switch statement */
+			ha_alert("config : %s '%s' cannot use syslog mode for this proxy.\n",
+				 proxy_type_str(curproxy), curproxy->id);
+			cfgerr++;
+			break;
 		}
 
 		if (curproxy != global.stats_fe && (curproxy->cap & PR_CAP_FE) && LIST_ISEMPTY(&curproxy->conf.listeners)) {
diff --git a/src/log.c b/src/log.c
index fec6102..b6d5c45 100644
--- a/src/log.c
+++ b/src/log.c
@@ -3166,6 +3166,382 @@
 
 	__send_log(logsrvs, tag, level, logline, data_len, default_rfc5424_sd_log_format, 2);
 }
+/*
+ * This function parse a received log message <buf>, of size <buflen>
+ * it fills <level>, <facility> and <metadata> depending of the detected
+ * header format and message will point on remaining payload of <size>
+ *
+ * <metadata> must point on a preallocated array of LOG_META_FIELDS*sizeof(struct ist)
+ * struct ist len will be set to 0 if field is not found
+ * <level> and <facility> will be set to -1 if not found.
+ */
+void parse_log_message(char *buf, size_t buflen, int *level, int *facility,
+                       struct ist *metadata, char **message, size_t *size)
+{
+
+	char *p;
+	int fac_level = 0;
+
+	*level = *facility = -1;
+
+	*message = buf;
+	*size = buflen;
+
+	memset(metadata, 0, LOG_META_FIELDS*sizeof(struct ist));
+
+	p = buf;
+	if (*size < 2 || *p != '<')
+		return;
+
+	p++;
+	while (*p != '>') {
+		if (*p > '9' || *p < '0')
+			return;
+		fac_level = 10*fac_level + (*p - '0');
+		p++;
+		if ((p - buf) > buflen)
+			return;
+	}
+
+	*facility = fac_level >> 3;
+	*level = fac_level & 0x7;
+	p++;
+
+	metadata[LOG_META_PRIO] = ist2(buf, p - buf);
+
+	buflen -= p - buf;
+	buf = p;
+
+	*size = buflen;
+	*message = buf;
+
+	/* for rfc5424, prio is always followed by '1' and ' ' */
+	if ((*size > 2) && (p[0] == '1') && (p[1] == ' ')) {
+		/* format is always '1 TIMESTAMP HOSTNAME TAG PID MSGID STDATA '
+		 * followed by message.
+		 * Each header field can present NILVALUE: '-'
+		 */
+
+		p += 2;
+		/* timestamp is NILVALUE '-' */
+		if (*size > 2 && (p[0] == '-') && p[1] == ' ') {
+			metadata[LOG_META_TIME] = ist2(p, 1);
+			p++;
+		}
+		else if (*size > LOG_ISOTIME_MINLEN) {
+			metadata[LOG_META_TIME].ptr = p;
+
+			/* check if optionnal secfrac is present
+			 * in timestamp.
+			 * possible format are:
+			 * ex: '1970-01-01T00:00:00.000000Z'
+			 *     '1970-01-01T00:00:00.000000+00:00'
+			 *     '1970-01-01T00:00:00.000000-00:00'
+			 *     '1970-01-01T00:00:00Z'
+			 *     '1970-01-01T00:00:00+00:00'
+			 *     '1970-01-01T00:00:00-00:00'
+			 */
+			p += 19;
+			if (*p == '.') {
+				p++;
+				if ((p - buf) >= buflen)
+					goto bad_format;
+				while (*p != 'Z' && *p != '+' && *p != '-') {
+					if ((unsigned char)(*p - '0') > 9)
+						goto bad_format;
+
+					p++;
+					if ((p - buf) >= buflen)
+						goto bad_format;
+				}
+			}
+
+			if (*p == 'Z')
+				p++;
+			else
+				p += 6; /* case of '+00:00 or '-00:00' */
+
+			if ((p - buf) >= buflen || *p != ' ')
+				goto bad_format;
+			metadata[LOG_META_TIME].len = p - metadata[LOG_META_TIME].ptr;
+		}
+		else
+			goto bad_format;
+
+
+		p++;
+		if ((p - buf) >= buflen || *p == ' ')
+			goto bad_format;
+
+		metadata[LOG_META_HOST].ptr = p;
+		while (*p != ' ') {
+			p++;
+			if ((p - buf) >= buflen)
+				goto bad_format;
+		}
+		metadata[LOG_META_HOST].len = p - metadata[LOG_META_HOST].ptr;
+		if (metadata[LOG_META_HOST].len == 1 && metadata[LOG_META_HOST].ptr[0] == '-')
+			metadata[LOG_META_HOST].len = 0;
+
+		p++;
+		if ((p - buf) >= buflen || *p == ' ')
+			goto bad_format;
+
+		metadata[LOG_META_TAG].ptr = p;
+		while (*p != ' ') {
+			p++;
+			if ((p - buf) >= buflen)
+				goto bad_format;
+		}
+		metadata[LOG_META_TAG].len = p - metadata[LOG_META_TAG].ptr;
+		if (metadata[LOG_META_TAG].len == 1 && metadata[LOG_META_TAG].ptr[0] == '-')
+			metadata[LOG_META_TAG].len = 0;
+
+		p++;
+		if ((p - buf) >= buflen || *p == ' ')
+			goto bad_format;
+
+		metadata[LOG_META_PID].ptr = p;
+		while (*p != ' ') {
+			p++;
+			if ((p - buf) >= buflen)
+				goto bad_format;
+		}
+		metadata[LOG_META_PID].len = p - metadata[LOG_META_PID].ptr;
+		if (metadata[LOG_META_PID].len == 1 && metadata[LOG_META_PID].ptr[0] == '-')
+			metadata[LOG_META_PID].len = 0;
+
+		p++;
+		if ((p - buf) >= buflen || *p == ' ')
+			goto bad_format;
+
+		metadata[LOG_META_MSGID].ptr = p;
+		while (*p != ' ') {
+			p++;
+			if ((p - buf) >= buflen)
+				goto bad_format;
+		}
+		metadata[LOG_META_MSGID].len = p - metadata[LOG_META_MSGID].ptr;
+		if (metadata[LOG_META_MSGID].len == 1 && metadata[LOG_META_MSGID].ptr[0] == '-')
+			metadata[LOG_META_MSGID].len = 0;
+
+		p++;
+		if ((p - buf) >= buflen || *p == ' ')
+			goto bad_format;
+
+		/* structured data format is:
+		 * ex:
+		 *    '[key1=value1 key2=value2][key3=value3]'
+		 *
+		 * space is invalid outside [] because
+		 * considered as the end of structured data field
+		 */
+		metadata[LOG_META_STDATA].ptr = p;
+		if (*p == '[') {
+			int elem = 0;
+
+			while (1) {
+				if (elem) {
+					/* according to rfc this char is escaped in param values */
+					if (*p == ']' && *(p-1) != '\\')
+						elem = 0;
+				}
+				else {
+					if (*p == '[')
+						elem = 1;
+					else if (*p == ' ')
+						break;
+					else
+						goto bad_format;
+				}
+				p++;
+				if ((p - buf) >= buflen)
+					goto bad_format;
+			}
+		}
+		else if (*p == '-') {
+			/* case of NILVALUE */
+			p++;
+			if ((p - buf) >= buflen || *p != ' ')
+				goto bad_format;
+		}
+		else
+			goto bad_format;
+
+		metadata[LOG_META_STDATA].len = p - metadata[LOG_META_STDATA].ptr;
+		if (metadata[LOG_META_STDATA].len == 1 && metadata[LOG_META_STDATA].ptr[0] == '-')
+			metadata[LOG_META_STDATA].len = 0;
+
+		p++;
+
+		buflen -= p - buf;
+		buf = p;
+
+		*size = buflen;
+		*message = p;
+	}
+	else if (*size > LOG_LEGACYTIME_LEN) {
+		int m;
+
+		/* supported header format according to rfc3164.
+		 * ex:
+		 *  'Jan  1 00:00:00 HOSTNAME TAG[PID]: '
+		 *  or 'Jan  1 00:00:00 HOSTNAME TAG: '
+		 *  or 'Jan  1 00:00:00 HOSTNAME '
+		 * Note: HOSTNAME is mandatory, and day
+		 * of month uses a single space prefix if
+		 * less than 10 to ensure hour offset is
+		 * always the same.
+		 */
+
+		/* Check month to see if it correspond to a rfc3164
+		 * header ex 'Jan  1 00:00:00' */
+		for (m = 0; m < 12; m++)
+			if (!memcmp(monthname[m], p, 3))
+				break;
+		/* Month not found */
+		if (m == 12)
+			goto bad_format;
+
+		metadata[LOG_META_TIME] = ist2(p, LOG_LEGACYTIME_LEN);
+
+		p += LOG_LEGACYTIME_LEN;
+		if ((p - buf) >= buflen || *p != ' ')
+			goto bad_format;
+
+		p++;
+		if ((p - buf) >= buflen || *p == ' ')
+			goto bad_format;
+
+		metadata[LOG_META_HOST].ptr = p;
+		while (*p != ' ') {
+			p++;
+			if ((p - buf) >= buflen)
+				goto bad_format;
+		}
+		metadata[LOG_META_HOST].len = p - metadata[LOG_META_HOST].ptr;
+
+		/* TAG seems to no be mandatory */
+		p++;
+
+		buflen -= p - buf;
+		buf = p;
+
+		*size = buflen;
+		*message = buf;
+
+		if (!buflen)
+			return;
+
+		while (((p  - buf) < buflen) && *p != ' ' && *p != ':')
+			p++;
+
+		/* a tag must present a trailing ':' */
+		if (((p - buf) >= buflen) || *p != ':')
+			return;
+		p++;
+		/* followed by a space */
+		if (((p - buf) >= buflen) || *p != ' ')
+			return;
+
+		/* rewind to parse tag and pid */
+		p = buf;
+		metadata[LOG_META_TAG].ptr = p;
+		/* we have the guarantee that ':' will be reach before size limit */
+		while (*p != ':') {
+			if (*p == '[') {
+				metadata[LOG_META_TAG].len = p - metadata[LOG_META_TAG].ptr;
+				metadata[LOG_META_PID].ptr = p + 1;
+			}
+			else if (*p == ']' && metadata[LOG_META_PID].ptr) {
+				if (p[1] != ':')
+					return;
+				metadata[LOG_META_PID].len = p - metadata[LOG_META_PID].ptr;
+			}
+			p++;
+		}
+		if (!metadata[LOG_META_TAG].len)
+			metadata[LOG_META_TAG].len = p - metadata[LOG_META_TAG].ptr;
+
+		/* let pass ':' and ' ', we still have warranty size is large enought */
+		p += 2;
+
+		buflen -= p - buf;
+		buf = p;
+
+		*size = buflen;
+		*message = buf;
+	}
+
+	return;
+
+bad_format:
+	/* bad syslog format, we reset all parsed syslog fields
+	 * but priority is kept because we are able to re-build
+	 * this message using LOF_FORMAT_PRIO.
+	 */
+	metadata[LOG_META_TIME].len = 0;
+	metadata[LOG_META_HOST].len = 0;
+	metadata[LOG_META_TAG].len = 0;
+	metadata[LOG_META_PID].len = 0;
+	metadata[LOG_META_MSGID].len = 0;
+	metadata[LOG_META_STDATA].len = 0;
+
+	return;
+}
+
+/*
+ * UDP syslog fd handler
+ */
+void syslog_fd_handler(int fd)
+{
+	static THREAD_LOCAL struct ist metadata[LOG_META_FIELDS];
+	ssize_t ret = 0;
+	struct buffer *buf = get_trash_chunk();
+	size_t size;
+	char *message;
+	int level;
+	int facility;
+	struct listener *l = objt_listener(fdtab[fd].owner);
+	int max_accept;
+
+	if(!l)
+		ABORT_NOW();
+
+	if (fdtab[fd].ev & FD_POLL_IN) {
+
+		if (!fd_recv_ready(fd))
+			return;
+
+		max_accept = l->maxaccept ? l->maxaccept : 1;
+
+		do {
+			/* Source address */
+			struct sockaddr_storage saddr = {0};
+			socklen_t saddrlen;
+
+			saddrlen = sizeof(saddr);
+
+			ret = recvfrom(fd, buf->area, buf->size, 0, (struct sockaddr *)&saddr, &saddrlen);
+			if (ret < 0) {
+				if (errno == EINTR)
+					continue;
+				if (errno == EAGAIN)
+					fd_cant_recv(fd);
+				goto out;
+			}
+			buf->data = ret;
+
+			parse_log_message(buf->area, buf->data, &level, &facility, metadata, &message, &size);
+
+			process_send_log(&l->bind_conf->frontend->logsrvs, level, facility, metadata, message, size);
+
+		} while (--max_accept);
+	}
+
+out:
+	return;
+}
 
 /* parse the "show startup-logs" command, returns 1 if a message is returned, otherwise zero */
 static int cli_parse_show_startup_logs(char **args, char *payload, struct appctx *appctx, void *private)
diff --git a/src/proto_udp.c b/src/proto_udp.c
index d006c45..6defe7e 100644
--- a/src/proto_udp.c
+++ b/src/proto_udp.c
@@ -303,9 +303,14 @@
 	listener->fd = fd;
 	listener->state = LI_LISTEN;
 
-	err |= ERR_FATAL | ERR_ALERT;
-	msg = "UDP is not yet supported on this proxy mode";
-	goto udp_close_return;
+	if (listener->bind_conf->frontend->mode == PR_MODE_SYSLOG)
+		fd_insert(fd, listener, syslog_fd_handler,
+		          thread_mask(listener->bind_conf->bind_thread) & all_threads_mask);
+	else {
+		err |= ERR_FATAL | ERR_ALERT;
+		msg = "UDP is not yet supported on this proxy mode";
+		goto udp_close_return;
+	}
 
  udp_return:
 	if (msg && errlen) {