[MEDIUM] add support for logging via a UNIX socket

The code in haproxy-1.3.13.1 only supports syslogging to an internet
address. The attached patch:

 - Adds support for syslogging to a UNIX domain socket (e.g., /dev/log).
   If the address field begins with '/' (absolute file path), then
   AF_UNIX is used to construct the socket. Otherwise, AF_INET is used.

 - Achieves clean single-source build on both Mac OS X and Linux
   (sockaddr_in.sin_len and sockaddr_un.sun_len field aren't always present).

For handling sendto() failures in send_log(), it appears that the existing
code is fine (no need to close/recreate socket) for both UDP and UNIX-domain
syslog server. So I left things alone (did not close/recreate socket).
Closing/recreating socket after each failure would also work, but would lead
to increased amount of unnecessary socket creation/destruction if syslog is
temporarily unavailable for some reason (especially for verbose loggers).

Please consider this patch for inclusion into the upstream haproxy codebase.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index c9ba70d..e26fcb6 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -84,10 +84,20 @@
 log <address> <facility> [max level]
   Adds a global syslog server. Up to two global servers can be defined. They
   will receive logs for startups and exits, as well as all logs from proxies
-  configured with "log global". <address> is an IPv4 address optionally
-  followed by a colon and an UDP port. If no port is specified, 514 is used
-  by default (the standard syslog port). <facility> must be one of the 24
-  standard syslog facilities :
+  configured with "log global".
+
+  <address> can be one of:
+
+        - An IPv4 address optionally followed by a colon and an UDP port. If
+          no port is specified, 514 is used by default (the standard syslog
+          port).
+
+        - A filesystem path to a UNIX domain socket, keeping in mind
+          considerations for chroot (be sure the path is accessible inside
+          the chroot) and uid/gid (be sure the path is appropriately
+          writeable).
+
+  <facility> must be one of the 24 standard syslog facilities :
 
           kern   user   mail   daemon auth   syslog lpr    news
           uucp   cron   auth2  ftp    ntp    audit  alert  cron2
diff --git a/include/common/standard.h b/include/common/standard.h
index 3f7bed9..248bbe9 100644
--- a/include/common/standard.h
+++ b/include/common/standard.h
@@ -125,6 +125,12 @@
 extern const char *invalid_char(const char *name);
 
 /*
+ * converts <str> to a struct sockaddr_un* which is locally allocated.
+ * The format is "/path", where "/path" is a path to a UNIX domain socket.
+ */
+struct sockaddr_un *str2sun(char *str);
+
+/*
  * converts <str> to a struct sockaddr_in* which is locally allocated.
  * The format is "addr:port", where "addr" can be a dotted IPv4 address,
  * a host name, or empty or "*" to indicate INADDR_ANY.
diff --git a/include/types/global.h b/include/types/global.h
index 56c94c4..18a94b2 100644
--- a/include/types/global.h
+++ b/include/types/global.h
@@ -25,6 +25,7 @@
 #include <netinet/in.h>
 
 #include <common/config.h>
+#include <types/log.h>
 #include <types/protocols.h>
 #include <types/task.h>
 
@@ -59,7 +60,7 @@
 	char *pidfile;
 	int logfac1, logfac2;
 	int loglev1, loglev2;
-	struct sockaddr_in logsrv1, logsrv2;
+	struct logsrv logsrv1, logsrv2;
 	struct {
 		int maxpollevents; /* max number of poll events at once */
 	} tune;
diff --git a/include/types/log.h b/include/types/log.h
index 12f8e27..d15b1ab 100644
--- a/include/types/log.h
+++ b/include/types/log.h
@@ -22,6 +22,8 @@
 #ifndef _TYPES_LOG_H
 #define _TYPES_LOG_H
 
+#include <sys/un.h>
+#include <netinet/in.h>
 #include <common/config.h>
 
 #define MAX_SYSLOG_LEN          1024
@@ -44,6 +46,15 @@
 #define LW_REQHDR	1024	/* request header(s) */
 #define LW_RSPHDR	2048	/* response header(s) */
 
+struct logsrv {
+	union {
+		struct sockaddr addr;
+		struct sockaddr_un un;	/* AF_UNIX */
+		struct sockaddr_in in;	/* AF_INET */
+	} u;
+};
+
+int logsrv_addrlen(const struct logsrv *logsrv);
 
 #endif /* _TYPES_LOG_H */
 
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 240b99b..ef74893 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -38,6 +38,7 @@
 #include <types/acl.h>
 #include <types/buffers.h>
 #include <types/httperr.h>
+#include <types/log.h>
 #include <types/protocols.h>
 #include <types/session.h>
 #include <types/server.h>
@@ -206,7 +207,7 @@
 	struct sockaddr_in tproxy_addr;		/* non-local address we want to bind to for connect() */
 #endif
 	struct proxy *next;
-	struct sockaddr_in logsrv1, logsrv2;	/* 2 syslog servers */
+	struct logsrv logsrv1, logsrv2;		/* 2 syslog servers */
 	signed char logfac1, logfac2;		/* log facility for both servers. -1 = disabled */
 	int loglev1, loglev2;			/* log level for each server, 7 by default */
 	int to_log;				/* things to be logged (LW_*) */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 9473b75..9610cf4 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -425,7 +425,7 @@
 		global.pidfile = strdup(args[1]);
 	}
 	else if (!strcmp(args[0], "log")) {  /* syslog server address */
-		struct sockaddr_in *sa;
+		struct logsrv logsrv;
 		int facility, level;
 	
 		if (*(args[1]) == 0 || *(args[2]) == 0) {
@@ -448,17 +448,23 @@
 			}
 		}
 
-		sa = str2sa(args[1]);
-		if (!sa->sin_port)
-			sa->sin_port = htons(SYSLOG_PORT);
+		if (args[1][0] == '/') {
+			logsrv.u.addr.sa_family = AF_UNIX;
+			logsrv.u.un = *str2sun(args[1]);
+		} else {
+			logsrv.u.addr.sa_family = AF_INET;
+			logsrv.u.in = *str2sa(args[1]);
+			if (!logsrv.u.in.sin_port)
+				logsrv.u.in.sin_port = htons(SYSLOG_PORT);
+		}
 
 		if (global.logfac1 == -1) {
-			global.logsrv1 = *sa;
+			global.logsrv1 = logsrv;
 			global.logfac1 = facility;
 			global.loglev1 = level;
 		}
 		else if (global.logfac2 == -1) {
-			global.logsrv2 = *sa;
+			global.logsrv2 = logsrv;
 			global.logfac2 = facility;
 			global.loglev2 = level;
 		}
@@ -1639,7 +1645,7 @@
 		newsrv->prev_state = newsrv->state;
 	}
 	else if (!strcmp(args[0], "log")) {  /* syslog server address */
-		struct sockaddr_in *sa;
+		struct logsrv logsrv;
 		int facility;
 	
 		if (*(args[1]) && *(args[2]) == 0 && !strcmp(args[1], "global")) {
@@ -1668,17 +1674,25 @@
 				}
 			}
 
-			sa = str2sa(args[1]);
-			if (!sa->sin_port)
-				sa->sin_port = htons(SYSLOG_PORT);
+			if (args[1][0] == '/') {
+				logsrv.u.addr.sa_family = AF_UNIX;
+				logsrv.u.un = *str2sun(args[1]);
+			} else {
+				logsrv.u.addr.sa_family = AF_INET;
+				logsrv.u.in = *str2sa(args[1]);
+				if (!logsrv.u.in.sin_port) {
+					logsrv.u.in.sin_port =
+						htons(SYSLOG_PORT);
+				}
+			}
 	    
 			if (curproxy->logfac1 == -1) {
-				curproxy->logsrv1 = *sa;
+				curproxy->logsrv1 = logsrv;
 				curproxy->logfac1 = facility;
 				curproxy->loglev1 = level;
 			}
 			else if (curproxy->logfac2 == -1) {
-				curproxy->logsrv2 = *sa;
+				curproxy->logsrv2 = logsrv;
 				curproxy->logfac2 = facility;
 				curproxy->loglev2 = level;
 			}
diff --git a/src/log.c b/src/log.c
index 20f9bb4..e4ecc3c 100644
--- a/src/log.c
+++ b/src/log.c
@@ -18,6 +18,7 @@
 #include <syslog.h>
 #include <time.h>
 #include <unistd.h>
+#include <errno.h>
 
 #include <sys/time.h>
 
@@ -30,6 +31,9 @@
 #include <types/log.h>
 #include <types/session.h>
 
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL	(0)
+#endif /* !MSG_NOSIGNAL */
 
 const char *log_facilities[NB_LOG_FACILITIES] = {
 	"kern", "user", "mail", "daemon",
@@ -140,6 +144,32 @@
 	return facility;
 }
 
+/*
+ * Return the length of the address endpoint, suitable for use with sendto().
+ */
+int logsrv_addrlen(const struct logsrv *logsrv)
+{
+#ifdef __SOCKADDR_COMMON
+	switch (logsrv->u.addr.sa_family) {
+	case AF_UNIX:
+		return sizeof(logsrv->u.un);
+	case AF_INET:
+		return sizeof(logsrv->u.in);
+	default:
+		break;
+	}
+#else  /* !__SOCKADDR_COMMON */
+	switch (logsrv->u.addr.sa_family) {
+	case AF_UNIX:
+		return logsrv->u.un.sun_len;
+	case AF_INET:
+		return logsrv->u.in.sin_len;
+	default:
+		break;
+	}
+#endif /* !__SOCKADDR_COMMON */
+	return -1;
+}
 
 /*
  * This function sends a syslog message to both log servers of a proxy,
@@ -149,29 +179,20 @@
  */
 void send_log(struct proxy *p, int level, const char *message, ...)
 {
-	static int logfd = -1;	/* syslog UDP socket */
+	static int logfdunix = -1;	/* syslog to AF_UNIX socket */
+	static int logfdinet = -1;	/* syslog to AF_INET socket */
 	static long tvsec = -1;	/* to force the string to be initialized */
 	va_list argp;
 	static char logmsg[MAX_SYSLOG_LEN];
 	static char *dataptr = NULL;
 	int fac_level;
 	int hdr_len, data_len;
-	struct sockaddr_in *sa[2];
+	struct logsrv *logsrvs[2];
 	int facilities[2], loglevel[2];
+	int nblogger;
 	int nbloggers = 0;
 	char *log_ptr;
 
-	if (logfd < 0) {
-		if ((logfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
-			return;
-		/* we don't want to receive anything on this socket */
-		setsockopt(logfd, SOL_SOCKET, SO_RCVBUF, &zero, sizeof(zero));
-		/* need for AIX which does not know about MSG_DONTWAIT */
-		if (!MSG_DONTWAIT)
-			fcntl(logfd, F_SETFL, O_NONBLOCK);
-		shutdown(logfd, SHUT_RD); /* does nothing under Linux, maybe needed for others */
-	}
-    
 	if (level < 0 || progname == NULL || message == NULL)
 		return;
 
@@ -210,35 +231,70 @@
 
 	if (p == NULL) {
 		if (global.logfac1 >= 0) {
-			sa[nbloggers] = &global.logsrv1;
+			logsrvs[nbloggers] = &global.logsrv1;
 			facilities[nbloggers] = global.logfac1;
 			loglevel[nbloggers] = global.loglev1;
 			nbloggers++;
 		}
 		if (global.logfac2 >= 0) {
-			sa[nbloggers] = &global.logsrv2;
+			logsrvs[nbloggers] = &global.logsrv2;
 			facilities[nbloggers] = global.logfac2;
 			loglevel[nbloggers] = global.loglev2;
 			nbloggers++;
 		}
 	} else {
 		if (p->logfac1 >= 0) {
-			sa[nbloggers] = &p->logsrv1;
+			logsrvs[nbloggers] = &p->logsrv1;
 			facilities[nbloggers] = p->logfac1;
 			loglevel[nbloggers] = p->loglev1;
 			nbloggers++;
 		}
 		if (p->logfac2 >= 0) {
-			sa[nbloggers] = &p->logsrv2;
+			logsrvs[nbloggers] = &p->logsrv2;
 			facilities[nbloggers] = p->logfac2;
 			loglevel[nbloggers] = p->loglev2;
 			nbloggers++;
 		}
+	}
+
+	/* Lazily set up syslog sockets for protocol families of configured
+	 * syslog servers. */
+	for (nblogger = 0; nblogger < nbloggers; nblogger++) {
+		const struct logsrv *logsrv = logsrvs[nblogger];
+		int proto, *plogfd;
+		if (logsrv->u.addr.sa_family == AF_UNIX) {
+			proto = 0;
+			plogfd = &logfdunix;
+		} else {
+			/* sa_family == AF_INET */
+			proto = IPPROTO_UDP;
+			plogfd = &logfdinet;
+		}
+		if (*plogfd >= 0) {
+			/* socket already created. */
+			continue;
+		}
+		if ((*plogfd = socket(logsrv->u.addr.sa_family, SOCK_DGRAM,
+				proto)) < 0) {
+			Alert("socket for logger #%d failed: %s (errno=%d)\n",
+				nblogger + 1, strerror(errno), errno);
+			return;
+		}
+		/* we don't want to receive anything on this socket */
+		setsockopt(*plogfd, SOL_SOCKET, SO_RCVBUF, &zero, sizeof(zero));
+		/* does nothing under Linux, maybe needed for others */
+		shutdown(*plogfd, SHUT_RD);
 	}
 
-	while (nbloggers-- > 0) {
+	/* Send log messages to syslog server. */
+	for (nblogger = 0; nblogger < nbloggers; nblogger++) {
+		const struct logsrv *logsrv = logsrvs[nblogger];
+		int *plogfd = logsrv->u.addr.sa_family == AF_UNIX ?
+			&logfdunix : &logfdinet;
+		int sent;
+
 		/* we can filter the level of the messages that are sent to each logger */
-		if (level > loglevel[nbloggers])
+		if (level > loglevel[nblogger])
 			continue;
 	
 		/* For each target, we may have a different facility.
@@ -248,7 +304,7 @@
 		 * time, we only change the facility in the pre-computed header,
 		 * and we change the pointer to the header accordingly.
 		 */
-		fac_level = (facilities[nbloggers] << 3) + level;
+		fac_level = (facilities[nblogger] << 3) + level;
 		log_ptr = logmsg + 3; /* last digit of the log level */
 		do {
 			*log_ptr = '0' + fac_level % 10;
@@ -258,14 +314,12 @@
 		*log_ptr = '<';
 	
 		/* the total syslog message now starts at logptr, for dataptr+data_len-logptr */
-
-#ifndef MSG_NOSIGNAL
-		sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT,
-		       (struct sockaddr *)sa[nbloggers], sizeof(**sa));
-#else
-		sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT | MSG_NOSIGNAL,
-		       (struct sockaddr *)sa[nbloggers], sizeof(**sa));
-#endif
+		sent = sendto(*plogfd, log_ptr, dataptr + data_len - log_ptr,
+			MSG_DONTWAIT | MSG_NOSIGNAL, &logsrv->u.addr, logsrv_addrlen(logsrv));
+		if (sent < 0) {
+			Alert("sendto logger #%d failed: %s (errno=%d)\n",
+				nblogger, strerror(errno), errno);
+		}
 	}
 }
 
diff --git a/src/standard.c b/src/standard.c
index 40ad47e..647a6c8 100644
--- a/src/standard.c
+++ b/src/standard.c
@@ -77,6 +77,37 @@
 	return (n) ? ultoa_r(n, buffer, size) : (alt ? alt : "");
 }
 
+/*
+ * converts <str> to a struct sockaddr_un* which is locally allocated.
+ * The format is "/path", where "/path" is a path to a UNIX domain socket.
+ */
+struct sockaddr_un *str2sun(char *str)
+{
+	static struct sockaddr_un sun;
+	int strsz;	/* length included null */
+
+	memset(&sun, 0, sizeof(sun));
+	str = strdup(str);
+	if (str == NULL)
+		goto out_nofree;
+
+	strsz = strlen(str) + 1;
+	if (strsz > sizeof(sun.sun_path)) {
+		Alert("Socket path '%s' too long (max %d)\n",
+			str, sizeof(sun.sun_path) - 1);
+		goto out_nofree;
+	}
+
+#ifndef __SOCKADDR_COMMON
+	sun.sun_len = sizeof(sun);
+#endif  /* !__SOCKADDR_COMMON */
+	sun.sun_family = AF_UNIX;
+	memcpy(sun.sun_path, str, strsz);
+
+	free(str);
+ out_nofree:
+	return &sun;
+}
 
 /*
  * Returns non-zero if character <s> is a hex digit (0-9, a-f, A-F), else zero.
@@ -153,6 +184,9 @@
 		else
 			sa.sin_addr = *(struct in_addr *) *(he->h_addr_list);
 	}
+#ifndef __SOCKADDR_COMMON
+	sa.sin_len = sizeof(sa);
+#endif  /* !__SOCKADDR_COMMON */
 	sa.sin_port   = htons(port);
 	sa.sin_family = AF_INET;