]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MINOR: log: Don't use strftime() which can clobber timezone if chrooted
authorBenoit GARNIER <chezbunch+haproxy@gmail.com>
Sun, 27 Mar 2016 01:04:16 +0000 (03:04 +0200)
committerWilly Tarreau <w@1wt.eu>
Thu, 17 Mar 2016 04:30:03 +0000 (05:30 +0100)
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
src/log.c
src/standard.c

index 353d0b023b16cf00e767023c9d5e89c1af6361c8..cd2208ca1b0119b12b3f79a416833043b19e3e89 100644 (file)
@@ -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,
index ab383531e7a386d86d5cd175fe8c038d158b15c8..4d496cdc0dca41959cf0917ab684814d46db4331 100644 (file)
--- 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;
index e08795fd167339932be1c44d4c930199945756a6..2fe92baecf2d5862f5a3389debcd920efab04a2e 100644 (file)
@@ -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++ = '/';