[MEDIUM] errorfile: use a local file to feed error messages

It is now possible to read error messages from local files,
using the 'errorfile' keyword. Those files are read during
parsing, so there's no I/O involved. They make it possible
to return custom error messages with custom status and headers.
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 69f11e0..77bd827 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -18,6 +18,10 @@
 #include <pwd.h>
 #include <grp.h>
 #include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
 
 #include <common/cfgparse.h>
 #include <common/config.h>
@@ -2093,11 +2097,6 @@
 		int errnum, errlen;
 		char *err;
 
-		// if (curproxy == &defproxy) {
-		//     Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
-		//     return -1;
-		// }
-
 		if (warnifnotcap(curproxy, PR_CAP_FE | PR_CAP_BE, file, linenum, args[0], NULL))
 			return 0;
 
@@ -2131,6 +2130,64 @@
 			free(err);
 		}
 	}
+	else if (!strcmp(args[0], "errorfile")) { /* error message from a file */
+		int errnum, errlen, fd;
+		char *err;
+		struct stat stat;
+
+		if (warnifnotcap(curproxy, PR_CAP_FE | PR_CAP_BE, file, linenum, args[0], NULL))
+			return 0;
+
+		if (*(args[2]) == 0) {
+			Alert("parsing [%s:%d] : <%s> expects <status_code> and <file> as arguments.\n", file, linenum);
+			return -1;
+		}
+
+		fd = open(args[2], O_RDONLY);
+		if ((fd < 0) || (fstat(fd, &stat) < 0)) {
+			Alert("parsing [%s:%d] : error opening file <%s> for custom error message <%s>.\n",
+			      file, linenum, args[2], args[1]);
+			if (fd >= 0)
+				close(fd);
+			return -1;
+		}
+
+		if (stat.st_size <= BUFSIZE) {
+			errlen = stat.st_size;
+		} else {
+			Warning("parsing [%s:%d] : custom error message file <%s> larger than %d bytes. Truncating.\n",
+				file, linenum, args[2], BUFSIZE);
+			errlen = BUFSIZE;
+		}
+
+		err = malloc(errlen); /* malloc() must succeed during parsing */
+		errnum = read(fd, err, errlen);
+		if (errnum != errlen) {
+			Alert("parsing [%s:%d] : error reading file <%s> for custom error message <%s>.\n",
+			      file, linenum, args[2], args[1]);
+			close(fd);
+			free(err);
+			return -1;
+		}
+		close(fd);
+
+		errnum = atol(args[1]);
+		for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
+			if (http_err_codes[rc] == errnum) {
+				if (curproxy->errmsg[rc].str)
+					free(curproxy->errmsg[rc].str);
+				curproxy->errmsg[rc].str = err;
+				curproxy->errmsg[rc].len = errlen;
+				break;
+			}
+		}
+
+		if (rc >= HTTP_ERR_SIZE) {
+			Warning("parsing [%s:%d] : status code %d not handled, error customization will be ignored.\n",
+				file, linenum, errnum);
+			free(err);
+		}
+	}
 	else {
 		Alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "listen");
 		return -1;