1 /*
2  * Copyright (c) 1983 Eric P. Allman
3  * Copyright (c) 1988 Regents of the University of California.
4  * All rights reserved.
5  *
6  * %sccs.include.redist.c%
7  */
8 
9 #ifndef lint
10 static char sccsid[] = "@(#)arpadate.c	6.6 (Berkeley) 02/18/93";
11 #endif /* not lint */
12 
13 # include "sendmail.h"
14 # include <sys/types.h>
15 
16 /*
17 **  ARPADATE -- Create date in ARPANET format
18 **
19 **	Parameters:
20 **		ud -- unix style date string.  if NULL, one is created.
21 **
22 **	Returns:
23 **		pointer to an ARPANET date field
24 **
25 **	Side Effects:
26 **		none
27 **
28 **	WARNING:
29 **		date is stored in a local buffer -- subsequent
30 **		calls will overwrite.
31 **
32 **	Bugs:
33 **		Timezone is computed from local time, rather than
34 **		from whereever (and whenever) the message was sent.
35 **		To do better is very hard.
36 **
37 **		Some sites are now inserting the timezone into the
38 **		local date.  This routine should figure out what
39 **		the format is and work appropriately.
40 */
41 
42 char *
43 arpadate(ud)
44 	register char *ud;
45 {
46 	register char *p;
47 	register char *q;
48 	register int off;
49 	register int i;
50 	register struct tm *lt;
51 	time_t t;
52 	struct tm gmt;
53 	static char b[40];
54 
55 	/*
56 	**  Get current time.
57 	**	This will be used if a null argument is passed and
58 	**	to resolve the timezone.
59 	*/
60 
61 	(void) time(&t);
62 	if (ud == NULL)
63 		ud = ctime(&t);
64 
65 	/*
66 	**  Crack the UNIX date line in a singularly unoriginal way.
67 	*/
68 
69 	q = b;
70 
71 	p = &ud[0];		/* Mon */
72 	*q++ = *p++;
73 	*q++ = *p++;
74 	*q++ = *p++;
75 	*q++ = ',';
76 	*q++ = ' ';
77 
78 	p = &ud[8];		/* 16 */
79 	if (*p == ' ')
80 		p++;
81 	else
82 		*q++ = *p++;
83 	*q++ = *p++;
84 	*q++ = ' ';
85 
86 	p = &ud[4];		/* Sep */
87 	*q++ = *p++;
88 	*q++ = *p++;
89 	*q++ = *p++;
90 	*q++ = ' ';
91 
92 	p = &ud[20];		/* 1979 */
93 	*q++ = *p++;
94 	*q++ = *p++;
95 	*q++ = *p++;
96 	*q++ = *p++;
97 	*q++ = ' ';
98 
99 	p = &ud[11];		/* 01:03:52 */
100 	for (i = 8; i > 0; i--)
101 		*q++ = *p++;
102 
103 	/*
104 	 * should really get the timezone from the time in "ud" (which
105 	 * is only different if a non-null arg was passed which is different
106 	 * from the current time), but for all practical purposes, returning
107 	 * the current local zone will do (its all that is ever needed).
108 	 */
109 	gmt = *gmtime(&t);
110 	lt = localtime(&t);
111 
112 	off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
113 
114 	/* assume that offset isn't more than a day ... */
115 	if (lt->tm_year < gmt.tm_year)
116 		off -= 24 * 60;
117 	else if (lt->tm_year > gmt.tm_year)
118 		off += 24 * 60;
119 	else if (lt->tm_yday < gmt.tm_yday)
120 		off -= 24 * 60;
121 	else if (lt->tm_yday > gmt.tm_yday)
122 		off += 24 * 60;
123 
124 	*q++ = ' ';
125 	if (off == 0) {
126 		*q++ = 'G';
127 		*q++ = 'M';
128 		*q++ = 'T';
129 	} else {
130 		if (off < 0) {
131 			off = -off;
132 			*q++ = '-';
133 		} else
134 			*q++ = '+';
135 
136 		if (off >= 24*60)		/* should be impossible */
137 			off = 23*60+59;		/* if not, insert silly value */
138 
139 		*q++ = (off / 600) + '0';
140 		*q++ = (off / 60) % 10 + '0';
141 		off %= 60;
142 		*q++ = (off / 10) + '0';
143 		*q++ = (off % 10) + '0';
144 	}
145 	*q = '\0';
146 
147 	return (b);
148 }
149 
150 /*
151 **  NEXTATOM -- Return pointer to next atom in header
152 **		(skip whitespace and comments)
153 **
154 **	Parameters:
155 **		s -- pointer to header string
156 **
157 **	Returns:
158 **		pointer advanced to next non-comment header atom
159 **
160 **	Side Effects:
161 **		none
162 */
163 
164 static char *
165 nextatom(s)
166 	char *s;
167 {
168 	char *p;
169 
170 	for (p = s;
171 	     *p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '(');
172 	     p++)
173 	{
174 		if (*p == '(')
175 		{
176 			int nested = 0;
177 
178 			/* ignore comments */
179 			p++;
180 			for (; *p; p++)
181 			{
182 				if (*p == '(')
183 					nested++;
184 				else if (*p == ')')
185 					if (!nested)
186 						break;
187 					else
188 						nested--;
189 			}
190 		}
191 	}
192 	return (p);
193 }
194 
195 /*
196 **  ARPATOUNIX -- Convert RFC-822/1123 date-time specification to ctime format.
197 **
198 **	Parameters:
199 **		s -- pointer to date string
200 **
201 **	Returns:
202 **		pointer to a string in ctime format
203 **
204 **	Side Effects:
205 **		Calls asctime() which modifies its static area.
206 **
207 **	Syntax:
208 **		date-time field specification from RFC822
209 **		as amended by RFC 1123:
210 **
211 **			[ day "," ] 1*2DIGIT month 2*4DIGIT \
212 **			2DIGIT ":" 2DIGIT [ ":" 2DIGIT  ] zone
213 **
214 **		Day can be "Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun"
215 **			(case-insensitive)
216 **		Month can be "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul"
217 **			"Aug" "Sep" "Oct" "Nov" "Dec" (also case-insensitive)
218 **		Zone can be as in the list below.
219 **		Additional whitespace or comments may occur.
220 **
221 **	Notes:
222 **		It's not clear this routine is valuable, since about the
223 **		only place UNIX dates are used is in the UNIX mailbox
224 **		header, which should probably be delivery time, not
225 **		message creation time.  Oh well, I'll leave this for
226 **		the time being.
227 */
228 
229 static char MonthDays[] = {
230 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
231 };
232 
233 struct tzone
234 {
235 	char	*tzname;	/* string name */
236 	int	tzoff;		/* offset in hours from GMT */
237 	bool	tzdst;		/* daylight savings */
238 };
239 
240 static struct tzone	Zones[] =
241 {
242 	"UT",		0,	FALSE,
243 	"GMT",		0,	FALSE,
244 	"AST",		-4,	FALSE,		/* Atlantic */
245 	"ADT",		-3,	TRUE,
246 	"EST",		-5,	FALSE,		/* Eastern */
247 	"EDT",		-4,	TRUE,
248 	"CST",		-6,	FALSE,		/* Central */
249 	"CDT",		-5,	TRUE,
250 	"MST",		-7,	FALSE,		/* Mountain */
251 	"MDT",		-6,	TRUE,
252 	"PST",		-8,	FALSE,		/* Pacific */
253 	"PDT",		-7,	TRUE,
254 	"YST",		-9,	FALSE,		/* Yukon */
255 	"YDT",		-8,	TRUE,
256 	"JST",		9,	FALSE,		/* Japan */
257 	"AHST",		-10,	FALSE,		/* Aleutian */
258 	"AHDT",		-9,	TRUE,
259 	"HAST",		-10,	FALSE,		/* Aleutian */
260 	"HADT",		-9,	TRUE,
261 	"NST",		-11,	FALSE,		/* Nome */
262 	"BST",		-11,	FALSE,		/* Bering */
263 	"SST",		-11,	FALSE,		/* Samoa */
264 	"HST",		-10,	FALSE,		/* Hawaii */
265 	"BST",		1,	TRUE,
266 	"WET",		0,	FALSE,		/* Western European */
267 	"WET DST",	1,	TRUE,
268 	"MET",		1,	FALSE,		/* Middle European */
269 	"MET DST",	2,	TRUE,
270 	"CET",		1,	FALSE,		/* Central European */
271 	"CET DST",	2,	TRUE,
272 	"EET",		2,	FALSE,		/* Eastern European */
273 	"MET DST",	3,	TRUE,
274 	"HKT",		8,	FALSE,		/* Hong Kong */
275 	"IST",		2,	FALSE,		/* Israel */
276 	"IDT",		3,	TRUE,
277 	"KST",		9,	FALSE,		/* Korea */
278 	"KDT",		10,	TRUE,
279 	"SST",		8,	FALSE,		/* Singapore */
280 	"NZST",		12,	FALSE,		/* New Zealand */
281 	"NZDT",		13,	TRUE,
282 	NULL,
283 };
284 
285 char *
286 arpatounix(s, e)
287 	char	*s;
288 	ENVELOPE *e;
289 {
290 	char *p;
291 	char *n;
292 	int h_offset = 0;		/* hours */
293 	int m_offset = 0;		/* minutes */
294 	struct tm tm;
295 	register struct tzone *z;
296 	extern char *DowList[];		/* defined in collect.c */
297 	extern char *MonthList[];	/* defined in collect.c */
298 
299 	bzero((char *) &tm, sizeof tm);
300 	tm.tm_wday = -1;	/* impossible value */
301 	p = nextatom (s);
302 
303 	/* next atom must be a day or a date */
304 	if (isalpha((int) *p))
305 	{
306 		/* day */
307 		for (tm.tm_wday = 0; DowList[tm.tm_wday]; tm.tm_wday++)
308 		{
309 			if (strncasecmp (p, DowList[tm.tm_wday], 3))
310 				continue;
311 			else
312 			{
313 				p += 3;
314 				break;
315 			}
316 		}
317 		p = nextatom(p);		/* ',' */
318 		if (*p == ',')
319 			p = nextatom(++p);
320 	}
321 
322 	/* now must have date */
323 	tm.tm_mday = atoi(p);
324 	while (isascii(*p) && isdigit(*p))	/* skip over date */
325 		p++;
326 	p = nextatom(p);			/* point to month name */
327 	for (tm.tm_mon = 0; MonthList[tm.tm_mon]; tm.tm_mon++)
328 	{
329 		if (strncasecmp(p, MonthList[tm.tm_mon], 3) == 0)
330 		{
331 			p += 3;
332 			break;
333 		}
334 	}
335 	p = nextatom(p);			/* year */
336 	tm.tm_year = atoi(p);
337 
338 	/* if this was 4 digits, subtract 1900 */
339 	if (tm.tm_year > 999)
340 		tm.tm_year -= 1900;
341 	else
342 	{
343 		/* if 2 or 3 digits, guess which century and convert */
344 		time_t now;
345 		struct tm *gmt;
346 
347 		(void) time(&now);
348 		gmt = gmtime(&now);
349 
350 		/* more likely +1 day than -100(0) years */
351 		if (gmt->tm_mon == 11 && gmt->tm_mday == 31 &&
352 		    tm.tm_mon == 0 && tm.tm_mday == 1)
353 			gmt->tm_year++;
354 		if (tm.tm_year > 99)
355 		{
356 			/* 3 digits */
357 			tm.tm_year += ((gmt->tm_year + 900 - tm.tm_year) / 1000) * 1000;
358 		}
359 		else
360 		{
361 			/* 2 digits */
362 			tm.tm_year += ((gmt->tm_year - tm.tm_year) / 100) * 100;
363 		}
364 	}
365 	while (isascii(*p) && isdigit(*p))	/* skip over year */
366 		p++;
367 	p = nextatom(p);			/* hours */
368 	tm.tm_hour = atoi(p);
369 	while (isascii(*p) && isdigit(*p))	/* skip over hours */
370 		p++;
371 	p = nextatom(p);			/* colon */
372 	if (*p == ':')
373 		p = nextatom(++p);
374 	p = nextatom(p);			/* minutes */
375 	tm.tm_min = atoi(p);
376 	while (isascii(*p) && isdigit(*p))	/* skip over minutes */
377 		p++;
378 	p = nextatom(p);			/* colon or zone */
379 	if (*p == ':')				/* have seconds field */
380 	{
381 		p = nextatom(++p);
382 		tm.tm_sec = atoi(p);
383 		while (isascii(*p) && isdigit(*p))	/* skip over seconds */
384 			p++;
385 	}
386 	p = nextatom(p);			/* zone */
387 
388 	if (*p == '+')
389 	{
390 		int off;
391 
392 		off = atoi(++p);
393 		h_offset = off / 100;
394 		m_offset = off % 100;
395 	}
396 	else if (*p == '-')
397 	{
398 		int off;
399 
400 		off = atoi(++p);
401 		h_offset = off / -100;
402 		m_offset = -1 * (off % 100);
403 	}
404 	else
405 	{
406 		for (z = Zones; z->tzname != NULL; z++)
407 		{
408 			if (strncasecmp(p, z->tzname, strlen(z->tzname)) == 0)
409 				break;
410 		}
411 
412 		if (z->tzname != NULL)
413 		{
414 			h_offset = z->tzoff;
415 			tm.tm_isdst = z->tzdst;
416 		}
417 		else
418 		{
419 #ifdef LOG
420 			if (LogLevel > 3)
421 				syslog(LOG_NOTICE, "%s: arpatounix: unparseable date: %s",
422 					e->e_id, s);
423 #endif /* LOG */
424 			return (NULL);
425 		}
426 	}
427 
428 	/* is the year a leap year? */
429 	if ((tm.tm_year % 4 == 0) &&
430 	    ((tm.tm_year % 100 != 0) || (tm.tm_year % 400 == 0)))
431 		MonthDays[2] = 29;
432 	else
433 		MonthDays[2] = 28;
434 
435 	/* apply offset -- this leaves the date in GMT */
436 	if (h_offset != 0 || m_offset != 0)
437 	{
438 		tm.tm_min -= m_offset;
439 		tm.tm_hour -= h_offset;
440 
441 		/* normalize */
442 		if (tm.tm_min < 0)
443 		{
444 			tm.tm_hour--;
445 			tm.tm_min += 60;
446 		}
447 		else if (tm.tm_min > 59)
448 		{
449 			tm.tm_hour++;
450 			tm.tm_min -= 60;
451 		}
452 		if (tm.tm_hour < 0)
453 		{
454 			tm.tm_mday--;
455 			tm.tm_wday--;
456 			tm.tm_hour += 24;
457 		}
458 		else if (tm.tm_hour > 23)
459 		{
460 			tm.tm_mday++;
461 			tm.tm_wday++;
462 			tm.tm_hour -= 24;
463 		}
464 		if (tm.tm_mday < 1)
465 		{
466 			if (--tm.tm_mon == -1)
467 			{
468 				tm.tm_mon = 11;
469 				tm.tm_year--;
470 
471 				/* is the year a leap year? */
472 				if ((tm.tm_year % 4 == 0) &&
473 				    ((tm.tm_year % 100 != 0) || (tm.tm_year % 400 == 0)))
474 					MonthDays[2] = 29;
475 				else
476 					MonthDays[2] = 28;
477 			}
478 			tm.tm_mday += MonthDays[tm.tm_mon];
479 		}
480 		else if (tm.tm_mday > MonthDays[tm.tm_mon])
481 		{
482 			tm.tm_mday -= MonthDays[tm.tm_mon++];
483 			if (tm.tm_mon > 11)
484 			{
485 				tm.tm_mon = 0;
486 				tm.tm_year++;
487 
488 				/*
489 				* Don't have to worry about leap years in
490 				* January.
491 				*/
492 			}
493 		}
494 	}
495 
496 	/* determine day of week if not set from RFC822/1123 line */
497 	if (tm.tm_wday < 0)
498 	{
499 		int i;
500 
501 		for (i = 0; i < tm.tm_mon; i++)
502 			tm.tm_yday += MonthDays[i];
503 		tm.tm_yday += tm.tm_mday;
504 
505 		/* I wouldn't change these constants if I were you... */
506 		tm.tm_wday = (int) (((((tm.tm_year + 699L) * 146097L) / 400L) + tm.tm_yday) % 7);
507 	}
508 
509 	/* now get UT */
510 	if ((p = asctime(&tm)) == NULL || *p == '\0' || strlen(p) < 25)
511 	{
512 #ifdef LOG
513 		if (LogLevel > 1)
514 			syslog(LOG_NOTICE, "%s: arpatounix: asctime failed: %s",
515 				e->e_id, s);
516 #endif /* LOG */
517 		return(NULL);
518 	}
519 	if ((n = strchr(p, '\n')) != NULL)
520 		*n = '\0';
521 	return(p);
522 }
523