122695Sdist /*
234920Sbostic  * Copyright (c) 1983 Eric P. Allman
333728Sbostic  * Copyright (c) 1988 Regents of the University of California.
433728Sbostic  * All rights reserved.
533728Sbostic  *
642824Sbostic  * %sccs.include.redist.c%
733728Sbostic  */
822695Sdist 
922695Sdist #ifndef lint
10*57589Seric static char sccsid[] = "@(#)arpadate.c	6.2 (Berkeley) 01/18/93";
1133728Sbostic #endif /* not lint */
1222695Sdist 
13*57589Seric # include "sendmail.h"
142897Seric # include <sys/types.h>
15293Seric 
16293Seric /*
17293Seric **  ARPADATE -- Create date in ARPANET format
18293Seric **
19293Seric **	Parameters:
202897Seric **		ud -- unix style date string.  if NULL, one is created.
21293Seric **
22293Seric **	Returns:
23293Seric **		pointer to an ARPANET date field
24293Seric **
25293Seric **	Side Effects:
26293Seric **		none
27293Seric **
28293Seric **	WARNING:
29293Seric **		date is stored in a local buffer -- subsequent
30293Seric **		calls will overwrite.
313185Seric **
323185Seric **	Bugs:
333185Seric **		Timezone is computed from local time, rather than
343185Seric **		from whereever (and whenever) the message was sent.
353185Seric **		To do better is very hard.
364315Seric **
374315Seric **		Some sites are now inserting the timezone into the
384315Seric **		local date.  This routine should figure out what
394315Seric **		the format is and work appropriately.
40293Seric */
41293Seric 
421586Seric char *
432897Seric arpadate(ud)
442897Seric 	register char *ud;
45293Seric {
46293Seric 	register char *p;
472897Seric 	register char *q;
4836529Sbostic 	register int off;
4936529Sbostic 	register int i;
5036529Sbostic 	register struct tm *lt;
5136529Sbostic 	time_t t;
5236529Sbostic 	struct tm gmt;
53293Seric 	static char b[40];
5436529Sbostic 	extern struct tm *localtime(), *gmtime();
551586Seric 	extern char *ctime();
5636529Sbostic 	extern time_t time();
57293Seric 
584315Seric 	/*
594315Seric 	**  Get current time.
604315Seric 	**	This will be used if a null argument is passed and
614315Seric 	**	to resolve the timezone.
624315Seric 	*/
634315Seric 
644315Seric 	(void) time(&t);
652897Seric 	if (ud == NULL)
662897Seric 		ud = ctime(&t);
67293Seric 
684315Seric 	/*
694315Seric 	**  Crack the UNIX date line in a singularly unoriginal way.
704315Seric 	*/
714315Seric 
722897Seric 	q = b;
732897Seric 
7413932Seric 	p = &ud[0];		/* Mon */
7513932Seric 	*q++ = *p++;
7613932Seric 	*q++ = *p++;
7713932Seric 	*q++ = *p++;
7813932Seric 	*q++ = ',';
7913932Seric 	*q++ = ' ';
8013932Seric 
81293Seric 	p = &ud[8];		/* 16 */
82293Seric 	if (*p == ' ')
83293Seric 		p++;
842897Seric 	else
852897Seric 		*q++ = *p++;
862897Seric 	*q++ = *p++;
8710251Seric 	*q++ = ' ';
882897Seric 
895184Seric 	p = &ud[4];		/* Sep */
905184Seric 	*q++ = *p++;
915184Seric 	*q++ = *p++;
925184Seric 	*q++ = *p++;
9310251Seric 	*q++ = ' ';
942897Seric 
9556304Seric 	p = &ud[20];		/* 1979 */
965184Seric 	*q++ = *p++;
975184Seric 	*q++ = *p++;
9856304Seric 	*q++ = *p++;
9956304Seric 	*q++ = *p++;
1002897Seric 	*q++ = ' ';
1012897Seric 
1022897Seric 	p = &ud[11];		/* 01:03:52 */
1035184Seric 	for (i = 8; i > 0; i--)
1042897Seric 		*q++ = *p++;
1052897Seric 
10636529Sbostic 	/*
10736529Sbostic 	 * should really get the timezone from the time in "ud" (which
10836529Sbostic 	 * is only different if a non-null arg was passed which is different
10936529Sbostic 	 * from the current time), but for all practical purposes, returning
11036529Sbostic 	 * the current local zone will do (its all that is ever needed).
11136529Sbostic 	 */
11236529Sbostic 	gmt = *gmtime(&t);
11336529Sbostic 	lt = localtime(&t);
1142897Seric 
11536529Sbostic 	off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
11615109Skarels 
11736529Sbostic 	/* assume that offset isn't more than a day ... */
11836529Sbostic 	if (lt->tm_year < gmt.tm_year)
11936529Sbostic 		off -= 24 * 60;
12036529Sbostic 	else if (lt->tm_year > gmt.tm_year)
12136529Sbostic 		off += 24 * 60;
12236529Sbostic 	else if (lt->tm_yday < gmt.tm_yday)
12336529Sbostic 		off -= 24 * 60;
12436529Sbostic 	else if (lt->tm_yday > gmt.tm_yday)
12536529Sbostic 		off += 24 * 60;
12615109Skarels 
12736529Sbostic 	*q++ = ' ';
12836529Sbostic 	if (off == 0) {
12936529Sbostic 		*q++ = 'G';
13036529Sbostic 		*q++ = 'M';
13136529Sbostic 		*q++ = 'T';
13236529Sbostic 	} else {
13336529Sbostic 		if (off < 0) {
13436529Sbostic 			off = -off;
13536529Sbostic 			*q++ = '-';
13636529Sbostic 		} else
13736529Sbostic 			*q++ = '+';
13815109Skarels 
13936529Sbostic 		if (off >= 24*60)		/* should be impossible */
14036529Sbostic 			off = 23*60+59;		/* if not, insert silly value */
14115137Seric 
14236529Sbostic 		*q++ = (off / 600) + '0';
14336529Sbostic 		*q++ = (off / 60) % 10 + '0';
14436529Sbostic 		off %= 60;
14536529Sbostic 		*q++ = (off / 10) + '0';
14636529Sbostic 		*q++ = (off % 10) + '0';
14715137Seric 	}
14836529Sbostic 	*q = '\0';
14936529Sbostic 
15036529Sbostic 	return (b);
15115109Skarels }
152*57589Seric 
153*57589Seric /*
154*57589Seric **  NEXTATOM -- Return pointer to next atom in header
155*57589Seric **		(skip whitespace and comments)
156*57589Seric **
157*57589Seric **	Parameters:
158*57589Seric **		s -- pointer to header string
159*57589Seric **
160*57589Seric **	Returns:
161*57589Seric **		pointer advanced to next non-comment header atom
162*57589Seric **
163*57589Seric **	Side Effects:
164*57589Seric **		none
165*57589Seric */
166*57589Seric 
167*57589Seric static char *
168*57589Seric nextatom(s)
169*57589Seric 	char *s;
170*57589Seric {
171*57589Seric 	char *p;
172*57589Seric 
173*57589Seric 	for (p = s;
174*57589Seric 	     *p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '(');
175*57589Seric 	     p++)
176*57589Seric 	{
177*57589Seric 		if (*p == '(')
178*57589Seric 		{
179*57589Seric 			int nested = 0;
180*57589Seric 
181*57589Seric 			/* ignore comments */
182*57589Seric 			p++;
183*57589Seric 			for (; *p; p++)
184*57589Seric 			{
185*57589Seric 				if (*p == '(')
186*57589Seric 					nested++;
187*57589Seric 				else if (*p == ')')
188*57589Seric 					if (!nested)
189*57589Seric 						break;
190*57589Seric 					else
191*57589Seric 						nested--;
192*57589Seric 			}
193*57589Seric 		}
194*57589Seric 	}
195*57589Seric 	return (p);
196*57589Seric }
197*57589Seric 
198*57589Seric /*
199*57589Seric **  ARPATOUNIX -- Convert RFC-822/1123 date-time specification to ctime format.
200*57589Seric **
201*57589Seric **	Parameters:
202*57589Seric **		s -- pointer to date string
203*57589Seric **
204*57589Seric **	Returns:
205*57589Seric **		pointer to a string in ctime format
206*57589Seric **
207*57589Seric **	Side Effects:
208*57589Seric **		Calls asctime() which modifies its static area.
209*57589Seric **
210*57589Seric **	Syntax:
211*57589Seric **		date-time field specification from RFC822
212*57589Seric **		as amended by RFC 1123:
213*57589Seric **
214*57589Seric **			[ day "," ] 1*2DIGIT month 2*4DIGIT \
215*57589Seric **			2DIGIT ":" 2DIGIT [ ":" 2DIGIT  ] zone
216*57589Seric **
217*57589Seric **		Day can be "Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun"
218*57589Seric **			(case-insensitive)
219*57589Seric **		Month can be "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul"
220*57589Seric **			"Aug" "Sep" "Oct" "Nov" "Dec" (also case-insensitive)
221*57589Seric **		Zone can be "UT" "GMT" "EST" "EDT" "CST" "CDT" "MST" "MDT"
222*57589Seric **			"PST" "PDT" or "+"4*DIGIT or "-"4*DIGIT
223*57589Seric **			(case-insensitive; military zones not useful
224*57589Seric **			per RFC1123)
225*57589Seric **		Additional whitespace or comments may occur.
226*57589Seric */
227*57589Seric 
228*57589Seric static char MonthDays[] = {
229*57589Seric 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
230*57589Seric };
231*57589Seric 
232*57589Seric char *
233*57589Seric arpatounix(s, e)
234*57589Seric 	char	*s;
235*57589Seric 	ENVELOPE *e;
236*57589Seric {
237*57589Seric 	char *p;
238*57589Seric 	char *n;
239*57589Seric 	int h_offset = 0;		/* hours */
240*57589Seric 	int m_offset = 0;		/* minutes */
241*57589Seric 	struct tm tm;
242*57589Seric 	extern char *DowList[];		/* defined in collect.c */
243*57589Seric 	extern char *MonthList[];	/* defined in collect.c */
244*57589Seric 
245*57589Seric 	bzero((char *) &tm, sizeof tm);
246*57589Seric 	tm.tm_wday = -1;	/* impossible value */
247*57589Seric 	p = nextatom (s);
248*57589Seric 
249*57589Seric 	/* next atom must be a day or a date */
250*57589Seric 	if (isalpha((int) *p))
251*57589Seric 	{
252*57589Seric 		/* day */
253*57589Seric 		for (tm.tm_wday = 0; DowList[tm.tm_wday]; tm.tm_wday++)
254*57589Seric 		{
255*57589Seric 			if (strncasecmp (p, DowList[tm.tm_wday], 3))
256*57589Seric 				continue;
257*57589Seric 			else
258*57589Seric 			{
259*57589Seric 				p += 3;
260*57589Seric 				break;
261*57589Seric 			}
262*57589Seric 		}
263*57589Seric 		p = nextatom(p);		/* ',' */
264*57589Seric 		if (*p == ',')
265*57589Seric 			p = nextatom(++p);
266*57589Seric 	}
267*57589Seric 
268*57589Seric 	/* now must have date */
269*57589Seric 	tm.tm_mday = atoi(p);
270*57589Seric 	while (isdigit((int) *p))		/* skip over date */
271*57589Seric 		p++;
272*57589Seric 	p = nextatom(p);			/* point to month name */
273*57589Seric 	for (tm.tm_mon = 0; MonthList[tm.tm_mon]; tm.tm_mon++)
274*57589Seric 	{
275*57589Seric 		if (strncasecmp(p, MonthList[tm.tm_mon], 3) == 0)
276*57589Seric 		{
277*57589Seric 			p += 3;
278*57589Seric 			break;
279*57589Seric 		}
280*57589Seric 	}
281*57589Seric 	p = nextatom(p);			/* year */
282*57589Seric 	tm.tm_year = atoi(p);
283*57589Seric 
284*57589Seric 	/* if this was 4 digits, subtract 1900 */
285*57589Seric 	if (tm.tm_year > 999)
286*57589Seric 		tm.tm_year -= 1900;
287*57589Seric 	else
288*57589Seric 	{
289*57589Seric 		/* if 2 or 3 digits, guess which century and convert */
290*57589Seric 		time_t now;
291*57589Seric 		struct tm *gmt;
292*57589Seric 
293*57589Seric 		(void) time(&now);
294*57589Seric 		gmt = gmtime(&now);
295*57589Seric 
296*57589Seric 		/* more likely +1 day than -100(0) years */
297*57589Seric 		if (gmt->tm_mon == 11 && gmt->tm_mday == 31 &&
298*57589Seric 		    tm.tm_mon == 0 && tm.tm_mday == 1)
299*57589Seric 			gmt->tm_year++;
300*57589Seric 		if (tm.tm_year > 99)
301*57589Seric 		{
302*57589Seric 			/* 3 digits */
303*57589Seric 			tm.tm_year += ((gmt->tm_year + 900 - tm.tm_year) / 1000) * 1000;
304*57589Seric 		}
305*57589Seric 		else
306*57589Seric 		{
307*57589Seric 			/* 2 digits */
308*57589Seric 			tm.tm_year += ((gmt->tm_year - tm.tm_year) / 100) * 100;
309*57589Seric 		}
310*57589Seric 	}
311*57589Seric 	while (isdigit((int) *p))	/* skip over year */
312*57589Seric 		p++;
313*57589Seric 	p = nextatom(p);		/* hours */
314*57589Seric 	tm.tm_hour = atoi(p);
315*57589Seric 	while (isdigit((int) *p))	/* skip over hours */
316*57589Seric 		p++;
317*57589Seric 	p = nextatom(p);		/* colon */
318*57589Seric 	if (*p == ':')
319*57589Seric 		p = nextatom(++p);
320*57589Seric 	p = nextatom(p);		/* minutes */
321*57589Seric 	tm.tm_min = atoi(p);
322*57589Seric 	while (isdigit((int) *p))	/* skip over minutes */
323*57589Seric 		p++;
324*57589Seric 	p = nextatom(p);		/* colon or zone */
325*57589Seric 	if (*p == ':')			/* have seconds field */
326*57589Seric 	{
327*57589Seric 		p = nextatom(++p);
328*57589Seric 		tm.tm_sec = atoi(p);
329*57589Seric 		while (isdigit((int) *p))	/* skip over seconds */
330*57589Seric 			p++;
331*57589Seric 	}
332*57589Seric 	p = nextatom(p);		/* zone */
333*57589Seric 	if (!strncasecmp(p, "UT", 2) || !strncasecmp(p, "GMT", 3))
334*57589Seric 		;
335*57589Seric 	else if (!strncasecmp(p, "EDT", 3))
336*57589Seric 		h_offset = -4;
337*57589Seric 	else if (!strncasecmp(p, "EST", 3))
338*57589Seric 	{
339*57589Seric 		h_offset = -5;
340*57589Seric 		tm.tm_isdst = 1;
341*57589Seric 	}
342*57589Seric 	else if (!strncasecmp(p, "CDT", 3))
343*57589Seric 		h_offset = -5;
344*57589Seric 	else if (!strncasecmp(p, "CST", 3))
345*57589Seric 	{
346*57589Seric 		h_offset = -6;
347*57589Seric 		tm.tm_isdst = 1;
348*57589Seric 	}
349*57589Seric 	else if (!strncasecmp(p, "MDT", 3))
350*57589Seric 		h_offset = -6;
351*57589Seric 	else if (!strncasecmp(p, "MST", 3))
352*57589Seric 	{
353*57589Seric 		h_offset = -7;
354*57589Seric 		tm.tm_isdst = 1;
355*57589Seric 	}
356*57589Seric 	else if (!strncasecmp(p, "PDT", 3))
357*57589Seric 		h_offset = -7;
358*57589Seric 	else if (!strncasecmp(p, "PST", 3))
359*57589Seric 	{
360*57589Seric 		h_offset = -8;
361*57589Seric 		tm.tm_isdst = 1;
362*57589Seric 	}
363*57589Seric 	else if (*p == '+')
364*57589Seric 	{
365*57589Seric 		int off;
366*57589Seric 
367*57589Seric 		off = atoi(++p);
368*57589Seric 		h_offset = off / 100;
369*57589Seric 		m_offset = off % 100;
370*57589Seric 	}
371*57589Seric 	else if (*p == '-')
372*57589Seric 	{
373*57589Seric 		int off;
374*57589Seric 
375*57589Seric 		off = atoi(++p);
376*57589Seric 		h_offset = off / -100;
377*57589Seric 		m_offset = -1 * (off % 100);
378*57589Seric 	}
379*57589Seric 	else
380*57589Seric 	{
381*57589Seric #ifdef LOG
382*57589Seric 		if (LogLevel > 0)
383*57589Seric 			syslog(LOG_NOTICE, "%s: arpatounix: unparseable date: %s",
384*57589Seric 				e->e_id, s);
385*57589Seric #endif /* LOG */
386*57589Seric 		return(NULL);
387*57589Seric 	}
388*57589Seric 
389*57589Seric 	/* is the year a leap year? */
390*57589Seric 	if ((tm.tm_year % 4 == 0) &&
391*57589Seric 	    ((tm.tm_year % 100 != 0) || (tm.tm_year % 400 == 0)))
392*57589Seric 		MonthDays[2] = 29;
393*57589Seric 	else
394*57589Seric 		MonthDays[2] = 28;
395*57589Seric 
396*57589Seric 	/* apply offset */
397*57589Seric 	if (h_offset || m_offset)
398*57589Seric 	{
399*57589Seric 		tm.tm_min += m_offset;
400*57589Seric 		tm.tm_hour += h_offset;
401*57589Seric 
402*57589Seric 		/* normalize */
403*57589Seric 		if (tm.tm_min < 0)
404*57589Seric 		{
405*57589Seric 			tm.tm_hour--;
406*57589Seric 			tm.tm_min += 60;
407*57589Seric 		}
408*57589Seric 		else if (tm.tm_min > 59)
409*57589Seric 		{
410*57589Seric 			tm.tm_hour++;
411*57589Seric 			tm.tm_min -= 60;
412*57589Seric 		}
413*57589Seric 		if (tm.tm_hour < 0)
414*57589Seric 		{
415*57589Seric 			tm.tm_mday--;
416*57589Seric 			tm.tm_wday--;
417*57589Seric 			tm.tm_hour += 24;
418*57589Seric 		}
419*57589Seric 		else if (tm.tm_hour > 23)
420*57589Seric 		{
421*57589Seric 			tm.tm_mday++;
422*57589Seric 			tm.tm_wday++;
423*57589Seric 			tm.tm_hour -= 24;
424*57589Seric 		}
425*57589Seric 		if (tm.tm_mday < 1)
426*57589Seric 		{
427*57589Seric 			if (--tm.tm_mon == -1)
428*57589Seric 			{
429*57589Seric 				tm.tm_mon = 11;
430*57589Seric 				tm.tm_year--;
431*57589Seric 
432*57589Seric 				/* is the year a leap year? */
433*57589Seric 				if ((tm.tm_year % 4 == 0) &&
434*57589Seric 				    ((tm.tm_year % 100 != 0) || (tm.tm_year % 400 == 0)))
435*57589Seric 					MonthDays[2] = 29;
436*57589Seric 				else
437*57589Seric 					MonthDays[2] = 28;
438*57589Seric 			}
439*57589Seric 			tm.tm_mday += MonthDays[tm.tm_mon];
440*57589Seric 		}
441*57589Seric 		else if (tm.tm_mday > MonthDays[tm.tm_mon])
442*57589Seric 		{
443*57589Seric 			tm.tm_mday -= MonthDays[tm.tm_mon++];
444*57589Seric 			if (tm.tm_mon > 11)
445*57589Seric 			{
446*57589Seric 				tm.tm_mon = 0;
447*57589Seric 				tm.tm_year++;
448*57589Seric 
449*57589Seric 				/*
450*57589Seric 				* Don't have to worry about leap years in
451*57589Seric 				* January.
452*57589Seric 				*/
453*57589Seric 			}
454*57589Seric 		}
455*57589Seric 	}
456*57589Seric 
457*57589Seric 	/* determine day of week if not set from RFC822/1123 line */
458*57589Seric 	if (tm.tm_wday < 0)
459*57589Seric 	{
460*57589Seric 		int i;
461*57589Seric 
462*57589Seric 		for (i = 0; i < tm.tm_mon; i++)
463*57589Seric 			tm.tm_yday += MonthDays[i];
464*57589Seric 		tm.tm_yday += tm.tm_mday;
465*57589Seric 
466*57589Seric 		/* I wouldn't change these constants if I were you... */
467*57589Seric 		tm.tm_wday = (int) (((((tm.tm_year + 699L) * 146097L) / 400L) + tm.tm_yday) % 7);
468*57589Seric 	}
469*57589Seric 
470*57589Seric 	/* now get UT */
471*57589Seric 	if ((p = asctime(&tm)) == NULL || *p == '\0' || strlen(p) < 25)
472*57589Seric 	{
473*57589Seric #ifdef LOG
474*57589Seric 		if (LogLevel > 0)
475*57589Seric 			syslog(LOG_NOTICE, "%s: arpatounix: asctime failed: %s",
476*57589Seric 				e->e_id, s);
477*57589Seric #endif /* LOG */
478*57589Seric 		return(NULL);
479*57589Seric 	}
480*57589Seric 	if ((n = index(p, '\n')) != NULL)
481*57589Seric 		*n = '\0';
482*57589Seric 	return(p);
483*57589Seric }
484