From 835b9212f698338d1556cb2d2557664ae16b66b8 Mon Sep 17 00:00:00 2001 From: Dragan Dosen Date: Fri, 12 Feb 2016 13:23:03 +0100 Subject: [PATCH] MEDIUM: log: add a new log format flag "E" The +E mode escapes characters '"', '\' and ']' with '\' as prefix. It mostly makes sense to use it in the RFC5424 structured-data log formats. Example: log-format-sd %{+Q,+E}o\ [exampleSDID@1234\ header=%[capture.req.hdr(0)]] --- doc/configuration.txt | 11 ++- include/proto/log.h | 8 ++ include/types/log.h | 1 + src/haproxy.c | 1 + src/log.c | 185 +++++++++++++++++++++++++++++++++++++----- 5 files changed, 184 insertions(+), 22 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 25b94afaa..a62ca6132 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -14500,7 +14500,7 @@ prefixing them with a '+' or '-' sign. Special variable "%o" may be used to propagate its flags to all other variables on the same format string. This is particularly handy with quoted -string formats ("Q"). +("Q") and escaped ("E") string formats. If a variable is named between square brackets ('[' .. ']') then it is used as a sample expression rule (see section 7.3). This it useful to add some @@ -14511,15 +14511,24 @@ Note: spaces must be escaped. A space character is considered as a separator. In order to emit a verbatim '%', it must be preceded by another '%' resulting in '%%'. HAProxy will automatically merge consecutive separators. +Note: when using the RFC5424 syslog message format, the characters '"', +'\' and ']' inside PARAM-VALUE should be escaped with '\' as prefix (see +https://tools.ietf.org/html/rfc5424#section-6.3.3 for more details). In +such cases, the use of the flag "E" should be considered. + Flags are : * Q: quote a string * X: hexadecimal representation (IPs, Ports, %Ts, %rt, %pid) + * E: escape characters '"', '\' and ']' in a string with '\' as prefix + (intended purpose is for the RFC5424 structured-data log formats) Example: log-format %T\ %t\ Some\ Text log-format %{+Q}o\ %t\ %s\ %{-Q}r + log-format-sd %{+Q,+E}o\ [exampleSDID@1234\ header=%[capture.req.hdr(0)]] + At the moment, the default HTTP format is defined this way : log-format %ci:%cp\ [%t]\ %ft\ %b/%s\ %Tq/%Tw/%Tc/%Tr/%Tt\ %ST\ %B\ %CC\ \ diff --git a/include/proto/log.h b/include/proto/log.h index 02e01f7e5..e606a3c76 100644 --- a/include/proto/log.h +++ b/include/proto/log.h @@ -48,6 +48,14 @@ extern char *logline; extern char *logline_rfc5424; +/* + * Initializes some log data. + */ +void init_log(); + +/* + * Builds a log line. + */ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list_format); /* diff --git a/include/types/log.h b/include/types/log.h index 35c1228aa..e9740869a 100644 --- a/include/types/log.h +++ b/include/types/log.h @@ -141,6 +141,7 @@ struct logformat_node { #define LOG_OPT_REQ_CAP 0x00000008 #define LOG_OPT_RES_CAP 0x00000010 #define LOG_OPT_HTTP 0x00000020 +#define LOG_OPT_ESC 0x00000040 /* Fields that need to be extracted from the incoming connection or request for diff --git a/src/haproxy.c b/src/haproxy.c index 8ceabde9a..4d38d2713 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -597,6 +597,7 @@ void init(int argc, char **argv) get_localtime(start_date.tv_sec, &curtime); strftime(localtimezone, 6, "%z", &curtime); + init_log(); signal_init(); if (init_acl() != 0) exit(1); diff --git a/src/log.c b/src/log.c index 3855ae00f..3e25bc79d 100644 --- a/src/log.c +++ b/src/log.c @@ -66,6 +66,21 @@ static const struct log_fmt log_formats[LOG_FORMATS] = { } }; +#define FD_SETS_ARE_BITFIELDS +#ifdef FD_SETS_ARE_BITFIELDS +/* + * This map is used with all the FD_* macros to check whether a particular bit + * is set or not. Each bit represents an ACSII code. FD_SET() sets those bytes + * which should be escaped. When FD_ISSET() returns non-zero, it means that the + * byte should be escaped. Be careful to always pass bytes from 0 to 255 + * exclusively to the macros. + */ +fd_set rfc5424_escape_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))]; + +#else +#error "Check if your OS uses bitfields for fd_sets" +#endif + const char *log_facilities[NB_LOG_FACILITIES] = { "kern", "user", "mail", "daemon", "auth", "syslog", "lpr", "news", @@ -211,6 +226,7 @@ struct logformat_var_args var_args_list[] = { { "M", LOG_OPT_MANDATORY }, { "Q", LOG_OPT_QUOTE }, { "X", LOG_OPT_HEXA }, + { "E", LOG_OPT_ESC }, { 0, 0 } }; @@ -701,9 +717,104 @@ int get_log_facility(const char *fac) return facility; } +/* + * Encode the string. + * + * When using the +E log format option, it will try to escape '"\]' + * characters with '\' as prefix. The same prefix should not be used as + * . + */ +static char *lf_encode_string(char *start, char *stop, + const char escape, const fd_set *map, + const char *string, + struct logformat_node *node) +{ + if (node->options & LOG_OPT_ESC) { + if (start < stop) { + stop--; /* reserve one byte for the final '\0' */ + while (start < stop && *string != '\0') { + if (!FD_ISSET((unsigned char)(*string), map)) { + if (!FD_ISSET((unsigned char)(*string), rfc5424_escape_map)) + *start++ = *string; + else { + if (start + 2 >= stop) + break; + *start++ = '\\'; + *start++ = *string; + } + } + else { + if (start + 3 >= stop) + break; + *start++ = escape; + *start++ = hextab[(*string >> 4) & 15]; + *start++ = hextab[*string & 15]; + } + string++; + } + *start = '\0'; + } + } + else { + return encode_string(start, stop, escape, map, string); + } + + return start; +} + +/* + * Encode the chunk. + * + * When using the +E log format option, it will try to escape '"\]' + * characters with '\' as prefix. The same prefix should not be used as + * . + */ +static char *lf_encode_chunk(char *start, char *stop, + const char escape, const fd_set *map, + const struct chunk *chunk, + struct logformat_node *node) +{ + char *str, *end; + + if (node->options & LOG_OPT_ESC) { + if (start < stop) { + str = chunk->str; + end = chunk->str + chunk->len; + + stop--; /* reserve one byte for the final '\0' */ + while (start < stop && str < end) { + if (!FD_ISSET((unsigned char)(*str), map)) { + if (!FD_ISSET((unsigned char)(*str), rfc5424_escape_map)) + *start++ = *str; + else { + if (start + 2 >= stop) + break; + *start++ = '\\'; + *start++ = *str; + } + } + else { + if (start + 3 >= stop) + break; + *start++ = escape; + *start++ = hextab[(*str >> 4) & 15]; + *start++ = hextab[*str & 15]; + } + str++; + } + *start = '\0'; + } + } + else { + return encode_chunk(start, stop, escape, map, chunk); + } + + return start; +} + /* * Write a string in the log string - * Take cares of quote options + * Take cares of quote and escape options * * Return the adress of the \0 character, or NULL on error */ @@ -718,9 +829,21 @@ char *lf_text_len(char *dst, const char *src, size_t len, size_t size, struct lo } if (src && len) { - if (++len > size) - len = size; - len = strlcpy2(dst, src, len); + if (node->options & LOG_OPT_ESC) { + struct chunk chunk; + char *ret; + + chunk_initlen(&chunk, (char *)src, 0, len); + ret = escape_chunk(dst, dst + size, '\\', rfc5424_escape_map, &chunk); + if (ret == NULL || *ret != '\0') + return NULL; + len = ret - dst; + } + else { + if (++len > size) + len = size; + len = strlcpy2(dst, src, len); + } size -= len; dst += len; @@ -1135,6 +1258,26 @@ const char sess_set_cookie[8] = "NPDIRU67"; /* No set-cookie, Set-cookie found a } while(0) +/* Initializes some log data. + */ +void init_log() +{ + char *tmp; + + /* Initialize the escape map for the RFC5424 structured-data : '"\]' + * inside PARAM-VALUE should be escaped with '\' as prefix. + * See https://tools.ietf.org/html/rfc5424#section-6.3.3 for more + * details. + */ + memset(rfc5424_escape_map, 0, sizeof(rfc5424_escape_map)); + + tmp = "\"\\]"; + while (*tmp) { + FD_SET(*tmp, rfc5424_escape_map); + tmp++; + } +} + /* Builds a log line in based on , and stops before reaching * characters. Returns the size of the output string in characters, * not counting the trailing zero which is always added if the resulting size @@ -1203,8 +1346,8 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list if (!key && (tmp->options & LOG_OPT_RES_CAP)) key = sample_fetch_as_type(be, sess, s, SMP_OPT_DIR_RES|SMP_OPT_FINAL, tmp->expr, SMP_T_STR); if (tmp->options & LOG_OPT_HTTP) - ret = encode_chunk(tmplog, dst + maxsize, - '%', http_encode_map, key ? &key->data.u.str : &empty); + ret = lf_encode_chunk(tmplog, dst + maxsize, + '%', http_encode_map, key ? &key->data.u.str : &empty, tmp); else ret = lf_text_len(tmplog, key ? key->data.u.str.str : NULL, key ? key->data.u.str.len : 0, dst + maxsize - tmplog, tmp); if (ret == 0) @@ -1651,8 +1794,8 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list if (hdr) LOGCHAR('|'); if (s->req_cap[hdr] != NULL) { - ret = encode_string(tmplog, dst + maxsize, - '#', hdr_encode_map, s->req_cap[hdr]); + ret = lf_encode_string(tmplog, dst + maxsize, + '#', hdr_encode_map, s->req_cap[hdr], tmp); if (ret == NULL || *ret != '\0') goto out; tmplog = ret; @@ -1674,8 +1817,8 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list if (tmp->options & LOG_OPT_QUOTE) LOGCHAR('"'); if (s->req_cap[hdr] != NULL) { - ret = encode_string(tmplog, dst + maxsize, - '#', hdr_encode_map, s->req_cap[hdr]); + ret = lf_encode_string(tmplog, dst + maxsize, + '#', hdr_encode_map, s->req_cap[hdr], tmp); if (ret == NULL || *ret != '\0') goto out; tmplog = ret; @@ -1699,8 +1842,8 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list if (hdr) LOGCHAR('|'); if (s->res_cap[hdr] != NULL) { - ret = encode_string(tmplog, dst + maxsize, - '#', hdr_encode_map, s->res_cap[hdr]); + ret = lf_encode_string(tmplog, dst + maxsize, + '#', hdr_encode_map, s->res_cap[hdr], tmp); if (ret == NULL || *ret != '\0') goto out; tmplog = ret; @@ -1722,8 +1865,8 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list if (tmp->options & LOG_OPT_QUOTE) LOGCHAR('"'); if (s->res_cap[hdr] != NULL) { - ret = encode_string(tmplog, dst + maxsize, - '#', hdr_encode_map, s->res_cap[hdr]); + ret = lf_encode_string(tmplog, dst + maxsize, + '#', hdr_encode_map, s->res_cap[hdr], tmp); if (ret == NULL || *ret != '\0') goto out; tmplog = ret; @@ -1741,8 +1884,8 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list if (tmp->options & LOG_OPT_QUOTE) LOGCHAR('"'); uri = txn->uri ? txn->uri : ""; - ret = encode_string(tmplog, dst + maxsize, - '#', url_encode_map, uri); + ret = lf_encode_string(tmplog, dst + maxsize, + '#', url_encode_map, uri, tmp); if (ret == NULL || *ret != '\0') goto out; tmplog = ret; @@ -1780,7 +1923,7 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list chunk.len = spc - uri; } - ret = encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk); + ret = lf_encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk, tmp); if (ret == NULL || *ret != '\0') goto out; @@ -1814,7 +1957,7 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list chunk.len = uri - qmark; } - ret = encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk); + ret = lf_encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk, tmp); if (ret == NULL || *ret != '\0') goto out; @@ -1854,7 +1997,7 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list chunk.len = spc - uri; } - ret = encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk); + ret = lf_encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk, tmp); if (ret == NULL || *ret != '\0') goto out; @@ -1884,7 +2027,7 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list chunk.len = spc - uri; } - ret = encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk); + ret = lf_encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk, tmp); if (ret == NULL || *ret != '\0') goto out; @@ -1929,7 +2072,7 @@ int build_logline(struct stream *s, char *dst, size_t maxsize, struct list *list chunk.len = end - uri; } - ret = encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk); + ret = lf_encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk, tmp); if (ret == NULL || *ret != '\0') goto out;