[6482] in Kerberos
More gmt_mktime trouble: breaks on systems with leap seconds
daemon@ATHENA.MIT.EDU (Jonathan Kamens)
Tue Jan 16 14:52:35 1996
To: kerberos@MIT.EDU
Date: 16 Jan 1996 19:34:35 GMT
From: jik@annex-1-slip-jik.cam.ov.com (Jonathan Kamens)
Unfortunately, the fixes in the MIT Kerberos V5 beta 5 release to the various
bugs in gmt_mktime that were present in earlier MIT releases (that is to say,
the fixes that make the MIT release work properly during leap years) do not
address another shortcoming in the routine -- it doesn't know about leap
seconds. As a result, times will be incorrectly decoded on systems where the
gmtime() function *does* know about leap seconds and the vendor or system
administrator has programmed the leap seconds into the system.
For example.... I don't know all of the leap seconds that have occurred over
the years, but I know about the one that occurred at the end of 1995, so I
added it to my Linux system by creating a "leapseconds" file and installing it
with "zic -L leapseconds <time zone definition files>". After doing so,
here's what happens when a time value is encoded with gmtime() and then
decoded with gmt_mktime() (this is output from a debugging main() that I added
to gmt_mktime.c, as shown in the patch at the end of this message):
Enter time value (or 0 for current time): 0
in_time = 821820442
out_time = 821820441
I didn't notice this problem only theoretically.... After I added the leap
second to my system, mutual authentication between my machine and others
stopped working, because the times in the KRB_AP_REP responses I was getting
back were decoding incorrectly on my end.
The patch below fixes this problem. Here's a couple sample output cases after
the patch is applied:
Enter time value (or 0 for current time): 821820442
in_time = 821820442
out_time = 821820442
Enter time value (or 0 for current time): 0
in_time = 821820481
out_time = 821820481
The approach of the patch is simple -- after gmt_mktime finishes its
conversion, it passes the computed time_t into gmtime() and compares the
result with the original struct tm. If it differs, the computed time_t is
adjusted and gmtime() is tried again. Etc. until the original struct tm and
the new struct tm match.
No, this isn't the most efficient way to solve the problem. But just how many
leap seconds are there going to be between now and when this code starts
breaking because of time_t size limitations? :-)
Here's the patch (which is against MIT's beta 4 release, with a few beta 5
fixes merged into it, so it may or may not apply cleanly to the beta 5 version
of gmt_mktime.c):
--- gmt_mktime.c 1996/01/16 17:27:08 1.3
+++ gmt_mktime.c 1996/01/16 19:33:44
@@ -31,12 +31,35 @@
334 /* dec 31 */
};
+#ifndef PUNT_LEAPSECONDS
+static int tmcmp(t1, t2)
+struct tm *t1, *t2;
+{
+#define TMCMP(field) if (t2->field - t1->field) return(t2->field - t1->field)
+
+ TMCMP(tm_year);
+ TMCMP(tm_mon);
+ TMCMP(tm_mday);
+ TMCMP(tm_hour);
+ TMCMP(tm_min);
+ TMCMP(tm_sec);
+
+#undef TMCMP
+
+ return(0);
+}
+#endif /* PUNT_LEAPSECONDS */
+
#define hasleapday(year) (year%400?(year%100?(year%4?0:1):0):1)
time_t gmt_mktime(t)
struct tm* t;
{
time_t accum;
+#ifndef PUNT_LEAPSECONDS
+ struct tm *check_time;
+ int cmp_ret;
+#endif /* PUNT_LEAPSECONDS */
#define assert_time(cnd) if(!(cnd)) return -1
@@ -74,5 +97,54 @@
accum *= 60; /* 60 seconds/minute */
accum += t->tm_sec;
+#ifndef PUNT_LEAPSECONDS
+ /*
+ Unfortunately, while this algorithm is very clever, it doesn't
+ know anything about leap seconds, and the OS we're running under
+ may actually understand leap seconds, so we have to check our
+ result. If it's off, we have to adjust our result.
+ */
+ do {
+ check_time = gmtime(&accum);
+ cmp_ret = tmcmp(check_time, t);
+ accum += cmp_ret;
+ } while (cmp_ret);
+#endif /* PUNT_LEAPSECONDS */
+
+
return accum;
}
+
+#ifdef DEBUG_MAIN
+#include <string.h>
+
+main()
+{
+ char time_buf[20];
+ char *nlptr;
+ time_t in_time, out_time;
+ struct tm the_tm;
+
+ while (1) {
+ fprintf(stderr, "Enter time value (or 0 for current time): ");
+ if (! fgets(time_buf, sizeof(time_buf), stdin))
+ exit(0);
+
+ if (nlptr = strchr(time_buf, '\n'))
+ *nlptr = '\0';
+
+ if (! *time_buf)
+ exit(0);
+
+ in_time = atol(time_buf);
+ if (! in_time)
+ in_time = time(0);
+
+ the_tm = *gmtime(&in_time);
+
+ out_time = gmt_mktime(&the_tm);
+
+ printf("in_time = %d\nout_time = %d\n", in_time, out_time);
+ }
+}
+#endif /* DEBUG_MAIN */
--
Jonathan Kamens | OpenVision Technologies, Inc. | jik@cam.ov.com