From 65198ff81545bd146511511eda534c699cb100b7 Mon Sep 17 00:00:00 2001
From: Benoit GARNIER <chezbunch+hapr...@gmail.com>
Date: Sun, 27 Mar 2016 03:04:16 +0200
Subject: [PATCH] BUG/MINOR: log: Don't use strftime() which can clobber
 timezone if chrooted

The strftime() function can call tzset() internally on some platforms.
When haproxy is chrooted, the /etc/localtime file is not found, and some
implementations will clobber the content of the current timezone.

The GMT offset is computed by diffing the times returned by gmtime_r() and
localtime_r(). These variants are guaranteed to not call tzset() and were
already used in haproxy while chrooted, so they should be safe.

This patch must be backported to 1.6 and 1.5.
---
 include/common/standard.h |  6 ++--
 src/log.c                 |  4 +--
 src/standard.c            | 76 ++++++++++++++++++++++++++++++++++-------------
 3 files changed, 62 insertions(+), 24 deletions(-)

diff --git a/include/common/standard.h b/include/common/standard.h
index 353d0b0..cd2208c 100644
--- a/include/common/standard.h
+++ b/include/common/standard.h
@@ -871,10 +871,11 @@ extern const char *monthname[];
 char *date2str_log(char *dest, struct tm *tm, struct timeval *date, size_t 
size);
 
 /* Return the GMT offset for a specific local time.
+ * Both t and tm must represent the same time.
  * The string returned has the same format as returned by strftime(... "%z", 
tm).
  * Offsets are kept in an internal cache for better performances.
  */
-const char *get_gmt_offset(struct tm *tm);
+const char *get_gmt_offset(time_t t, struct tm *tm);
 
 /* gmt2str_log: write a date in the format :
  * "%02d/%s/%04d:%02d:%02d:%02d +0000" without using snprintf
@@ -885,10 +886,11 @@ char *gmt2str_log(char *dst, struct tm *tm, size_t size);
 
 /* localdate2str_log: write a date in the format :
  * "%02d/%s/%04d:%02d:%02d:%02d +0000(local timezone)" without using snprintf
+ * Both t and tm must represent the same time.
  * return a pointer to the last char written (\0) or
  * NULL if there isn't enough space.
  */
-char *localdate2str_log(char *dst, struct tm *tm, size_t size);
+char *localdate2str_log(char *dst, time_t t, struct tm *tm, size_t size);
 
 /* These 3 functions parses date string and fills the
  * corresponding broken-down time in <tm>. In succes case,
diff --git a/src/log.c b/src/log.c
index ab38353..4d496cd 100644
--- a/src/log.c
+++ b/src/log.c
@@ -979,7 +979,7 @@ static char *update_log_hdr_rfc5424(const time_t time)
 
                tvsec = time;
                get_localtime(tvsec, &tm);
-               gmt_offset = get_gmt_offset(&tm);
+               gmt_offset = get_gmt_offset(time, &tm);
 
                hdr_len = snprintf(logheader_rfc5424, global.max_syslog_len,
                                   "<<<<>1 
%4d-%02d-%02dT%02d:%02d:%02d%.3s:%.2s %s ",
@@ -1495,7 +1495,7 @@ int build_logline(struct stream *s, char *dst, size_t 
maxsize, struct list *list
 
                        case LOG_FMT_DATELOCAL: // %Tl
                                get_localtime(s->logs.accept_date.tv_sec, &tm);
-                               ret = localdate2str_log(tmplog, &tm, dst + 
maxsize - tmplog);
+                               ret = localdate2str_log(tmplog, 
s->logs.accept_date.tv_sec, &tm, dst + maxsize - tmplog);
                                if (ret == NULL)
                                        goto out;
                                tmplog = ret;
diff --git a/src/standard.c b/src/standard.c
index e08795f..2fe92ba 100644
--- a/src/standard.c
+++ b/src/standard.c
@@ -2552,31 +2552,66 @@ char *date2str_log(char *dst, struct tm *tm, struct 
timeval *date, size_t size)
        return dst;
 }
 
+/* Base year used to compute leap years */
+#define TM_YEAR_BASE 1900
+
+/* Return the difference in seconds between two times (leap seconds are 
ignored).
+ * Retrieved from glibc 2.18 source code.
+ */
+static int my_tm_diff(const struct tm *a, const struct tm *b)
+{
+       /* Compute intervening leap days correctly even if year is negative.
+        * Take care to avoid int overflow in leap day calculations,
+        * but it's OK to assume that A and B are close to each other.
+        */
+       int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
+       int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
+       int a100 = a4 / 25 - (a4 % 25 < 0);
+       int b100 = b4 / 25 - (b4 % 25 < 0);
+       int a400 = a100 >> 2;
+       int b400 = b100 >> 2;
+       int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+       int years = a->tm_year - b->tm_year;
+       int days = (365 * years + intervening_leap_days
+                + (a->tm_yday - b->tm_yday));
+       return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+              + (a->tm_min - b->tm_min))
+              + (a->tm_sec - b->tm_sec));
+}
+
 /* Return the GMT offset for a specific local time.
+ * Both t and tm must represent the same time.
  * The string returned has the same format as returned by strftime(... "%z", 
tm).
  * Offsets are kept in an internal cache for better performances.
  */
-const char *get_gmt_offset(struct tm *tm)
+const char *get_gmt_offset(time_t t, struct tm *tm)
 {
        /* Cache offsets from GMT (depending on whether DST is active or not) */
        static char gmt_offsets[2][5+1] = { "", "" };
 
-    int old_isdst = tm->tm_isdst;
        char *gmt_offset;
-
-    /* Pretend DST not active if its status is unknown, or strftime() will 
return an empty string for "%z" */
-    if (tm->tm_isdst < 0) {
-        tm->tm_isdst = 0;
-    }
-
-    /* Fetch the offset and initialize it if needed */
-    gmt_offset = gmt_offsets[tm->tm_isdst & 0x01];
-    if (unlikely(!*gmt_offset)) {
-        strftime(gmt_offset, 5+1, "%z", tm);
-    }
-
-    /* Restore previous DST flag */
-    tm->tm_isdst = old_isdst;
+       struct tm tm_gmt;
+       int diff;
+       int isdst = tm->tm_isdst;
+
+       /* Pretend DST not active if its status is unknown */
+       if (isdst < 0)
+               isdst = 0;
+
+       /* Fetch the offset and initialize it if needed */
+       gmt_offset = gmt_offsets[isdst & 0x01];
+       if (unlikely(!*gmt_offset)) {
+               get_gmtime(t, &tm_gmt);
+               diff = my_tm_diff(tm, &tm_gmt);
+               if (diff < 0) {
+                       diff = -diff;
+                       *gmt_offset = '-';
+               } else {
+                       *gmt_offset = '+';
+               }
+               diff /= 60; /* Convert to minutes */
+               snprintf(gmt_offset+1, 4+1, "%02d%02d", diff/60, diff%60);
+       }
 
     return gmt_offset;
 }
@@ -2616,16 +2651,17 @@ char *gmt2str_log(char *dst, struct tm *tm, size_t size)
 
 /* localdate2str_log: write a date in the format :
  * "%02d/%s/%04d:%02d:%02d:%02d +0000(local timezone)" without using snprintf
- * * return a pointer to the last char written (\0) or
- * * NULL if there isn't enough space.
+ * Both t and tm must represent the same time.
+ * return a pointer to the last char written (\0) or
+ * NULL if there isn't enough space.
  */
-char *localdate2str_log(char *dst, struct tm *tm, size_t size)
+char *localdate2str_log(char *dst, time_t t, struct tm *tm, size_t size)
 {
        const char *gmt_offset;
        if (size < 27) /* the size is fixed: 26 chars + \0 */
                return NULL;
 
-       gmt_offset = get_gmt_offset(tm);
+       gmt_offset = get_gmt_offset(t, tm);
 
        dst = utoa_pad((unsigned int)tm->tm_mday, dst, 3); // day
        *dst++ = '/';
-- 
2.5.0

Reply via email to