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.2 (Berkeley) 01/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 	extern struct tm *localtime(), *gmtime();
55 	extern char *ctime();
56 	extern time_t time();
57 
58 	/*
59 	**  Get current time.
60 	**	This will be used if a null argument is passed and
61 	**	to resolve the timezone.
62 	*/
63 
64 	(void) time(&t);
65 	if (ud == NULL)
66 		ud = ctime(&t);
67 
68 	/*
69 	**  Crack the UNIX date line in a singularly unoriginal way.
70 	*/
71 
72 	q = b;
73 
74 	p = &ud[0];		/* Mon */
75 	*q++ = *p++;
76 	*q++ = *p++;
77 	*q++ = *p++;
78 	*q++ = ',';
79 	*q++ = ' ';
80 
81 	p = &ud[8];		/* 16 */
82 	if (*p == ' ')
83 		p++;
84 	else
85 		*q++ = *p++;
86 	*q++ = *p++;
87 	*q++ = ' ';
88 
89 	p = &ud[4];		/* Sep */
90 	*q++ = *p++;
91 	*q++ = *p++;
92 	*q++ = *p++;
93 	*q++ = ' ';
94 
95 	p = &ud[20];		/* 1979 */
96 	*q++ = *p++;
97 	*q++ = *p++;
98 	*q++ = *p++;
99 	*q++ = *p++;
100 	*q++ = ' ';
101 
102 	p = &ud[11];		/* 01:03:52 */
103 	for (i = 8; i > 0; i--)
104 		*q++ = *p++;
105 
106 	/*
107 	 * should really get the timezone from the time in "ud" (which
108 	 * is only different if a non-null arg was passed which is different
109 	 * from the current time), but for all practical purposes, returning
110 	 * the current local zone will do (its all that is ever needed).
111 	 */
112 	gmt = *gmtime(&t);
113 	lt = localtime(&t);
114 
115 	off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
116 
117 	/* assume that offset isn't more than a day ... */
118 	if (lt->tm_year < gmt.tm_year)
119 		off -= 24 * 60;
120 	else if (lt->tm_year > gmt.tm_year)
121 		off += 24 * 60;
122 	else if (lt->tm_yday < gmt.tm_yday)
123 		off -= 24 * 60;
124 	else if (lt->tm_yday > gmt.tm_yday)
125 		off += 24 * 60;
126 
127 	*q++ = ' ';
128 	if (off == 0) {
129 		*q++ = 'G';
130 		*q++ = 'M';
131 		*q++ = 'T';
132 	} else {
133 		if (off < 0) {
134 			off = -off;
135 			*q++ = '-';
136 		} else
137 			*q++ = '+';
138 
139 		if (off >= 24*60)		/* should be impossible */
140 			off = 23*60+59;		/* if not, insert silly value */
141 
142 		*q++ = (off / 600) + '0';
143 		*q++ = (off / 60) % 10 + '0';
144 		off %= 60;
145 		*q++ = (off / 10) + '0';
146 		*q++ = (off % 10) + '0';
147 	}
148 	*q = '\0';
149 
150 	return (b);
151 }
152 
153 /*
154 **  NEXTATOM -- Return pointer to next atom in header
155 **		(skip whitespace and comments)
156 **
157 **	Parameters:
158 **		s -- pointer to header string
159 **
160 **	Returns:
161 **		pointer advanced to next non-comment header atom
162 **
163 **	Side Effects:
164 **		none
165 */
166 
167 static char *
168 nextatom(s)
169 	char *s;
170 {
171 	char *p;
172 
173 	for (p = s;
174 	     *p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '(');
175 	     p++)
176 	{
177 		if (*p == '(')
178 		{
179 			int nested = 0;
180 
181 			/* ignore comments */
182 			p++;
183 			for (; *p; p++)
184 			{
185 				if (*p == '(')
186 					nested++;
187 				else if (*p == ')')
188 					if (!nested)
189 						break;
190 					else
191 						nested--;
192 			}
193 		}
194 	}
195 	return (p);
196 }
197 
198 /*
199 **  ARPATOUNIX -- Convert RFC-822/1123 date-time specification to ctime format.
200 **
201 **	Parameters:
202 **		s -- pointer to date string
203 **
204 **	Returns:
205 **		pointer to a string in ctime format
206 **
207 **	Side Effects:
208 **		Calls asctime() which modifies its static area.
209 **
210 **	Syntax:
211 **		date-time field specification from RFC822
212 **		as amended by RFC 1123:
213 **
214 **			[ day "," ] 1*2DIGIT month 2*4DIGIT \
215 **			2DIGIT ":" 2DIGIT [ ":" 2DIGIT  ] zone
216 **
217 **		Day can be "Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun"
218 **			(case-insensitive)
219 **		Month can be "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul"
220 **			"Aug" "Sep" "Oct" "Nov" "Dec" (also case-insensitive)
221 **		Zone can be "UT" "GMT" "EST" "EDT" "CST" "CDT" "MST" "MDT"
222 **			"PST" "PDT" or "+"4*DIGIT or "-"4*DIGIT
223 **			(case-insensitive; military zones not useful
224 **			per RFC1123)
225 **		Additional whitespace or comments may occur.
226 */
227 
228 static char MonthDays[] = {
229 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
230 };
231 
232 char *
233 arpatounix(s, e)
234 	char	*s;
235 	ENVELOPE *e;
236 {
237 	char *p;
238 	char *n;
239 	int h_offset = 0;		/* hours */
240 	int m_offset = 0;		/* minutes */
241 	struct tm tm;
242 	extern char *DowList[];		/* defined in collect.c */
243 	extern char *MonthList[];	/* defined in collect.c */
244 
245 	bzero((char *) &tm, sizeof tm);
246 	tm.tm_wday = -1;	/* impossible value */
247 	p = nextatom (s);
248 
249 	/* next atom must be a day or a date */
250 	if (isalpha((int) *p))
251 	{
252 		/* day */
253 		for (tm.tm_wday = 0; DowList[tm.tm_wday]; tm.tm_wday++)
254 		{
255 			if (strncasecmp (p, DowList[tm.tm_wday], 3))
256 				continue;
257 			else
258 			{
259 				p += 3;
260 				break;
261 			}
262 		}
263 		p = nextatom(p);		/* ',' */
264 		if (*p == ',')
265 			p = nextatom(++p);
266 	}
267 
268 	/* now must have date */
269 	tm.tm_mday = atoi(p);
270 	while (isdigit((int) *p))		/* skip over date */
271 		p++;
272 	p = nextatom(p);			/* point to month name */
273 	for (tm.tm_mon = 0; MonthList[tm.tm_mon]; tm.tm_mon++)
274 	{
275 		if (strncasecmp(p, MonthList[tm.tm_mon], 3) == 0)
276 		{
277 			p += 3;
278 			break;
279 		}
280 	}
281 	p = nextatom(p);			/* year */
282 	tm.tm_year = atoi(p);
283 
284 	/* if this was 4 digits, subtract 1900 */
285 	if (tm.tm_year > 999)
286 		tm.tm_year -= 1900;
287 	else
288 	{
289 		/* if 2 or 3 digits, guess which century and convert */
290 		time_t now;
291 		struct tm *gmt;
292 
293 		(void) time(&now);
294 		gmt = gmtime(&now);
295 
296 		/* more likely +1 day than -100(0) years */
297 		if (gmt->tm_mon == 11 && gmt->tm_mday == 31 &&
298 		    tm.tm_mon == 0 && tm.tm_mday == 1)
299 			gmt->tm_year++;
300 		if (tm.tm_year > 99)
301 		{
302 			/* 3 digits */
303 			tm.tm_year += ((gmt->tm_year + 900 - tm.tm_year) / 1000) * 1000;
304 		}
305 		else
306 		{
307 			/* 2 digits */
308 			tm.tm_year += ((gmt->tm_year - tm.tm_year) / 100) * 100;
309 		}
310 	}
311 	while (isdigit((int) *p))	/* skip over year */
312 		p++;
313 	p = nextatom(p);		/* hours */
314 	tm.tm_hour = atoi(p);
315 	while (isdigit((int) *p))	/* skip over hours */
316 		p++;
317 	p = nextatom(p);		/* colon */
318 	if (*p == ':')
319 		p = nextatom(++p);
320 	p = nextatom(p);		/* minutes */
321 	tm.tm_min = atoi(p);
322 	while (isdigit((int) *p))	/* skip over minutes */
323 		p++;
324 	p = nextatom(p);		/* colon or zone */
325 	if (*p == ':')			/* have seconds field */
326 	{
327 		p = nextatom(++p);
328 		tm.tm_sec = atoi(p);
329 		while (isdigit((int) *p))	/* skip over seconds */
330 			p++;
331 	}
332 	p = nextatom(p);		/* zone */
333 	if (!strncasecmp(p, "UT", 2) || !strncasecmp(p, "GMT", 3))
334 		;
335 	else if (!strncasecmp(p, "EDT", 3))
336 		h_offset = -4;
337 	else if (!strncasecmp(p, "EST", 3))
338 	{
339 		h_offset = -5;
340 		tm.tm_isdst = 1;
341 	}
342 	else if (!strncasecmp(p, "CDT", 3))
343 		h_offset = -5;
344 	else if (!strncasecmp(p, "CST", 3))
345 	{
346 		h_offset = -6;
347 		tm.tm_isdst = 1;
348 	}
349 	else if (!strncasecmp(p, "MDT", 3))
350 		h_offset = -6;
351 	else if (!strncasecmp(p, "MST", 3))
352 	{
353 		h_offset = -7;
354 		tm.tm_isdst = 1;
355 	}
356 	else if (!strncasecmp(p, "PDT", 3))
357 		h_offset = -7;
358 	else if (!strncasecmp(p, "PST", 3))
359 	{
360 		h_offset = -8;
361 		tm.tm_isdst = 1;
362 	}
363 	else if (*p == '+')
364 	{
365 		int off;
366 
367 		off = atoi(++p);
368 		h_offset = off / 100;
369 		m_offset = off % 100;
370 	}
371 	else if (*p == '-')
372 	{
373 		int off;
374 
375 		off = atoi(++p);
376 		h_offset = off / -100;
377 		m_offset = -1 * (off % 100);
378 	}
379 	else
380 	{
381 #ifdef LOG
382 		if (LogLevel > 0)
383 			syslog(LOG_NOTICE, "%s: arpatounix: unparseable date: %s",
384 				e->e_id, s);
385 #endif /* LOG */
386 		return(NULL);
387 	}
388 
389 	/* is the year a leap year? */
390 	if ((tm.tm_year % 4 == 0) &&
391 	    ((tm.tm_year % 100 != 0) || (tm.tm_year % 400 == 0)))
392 		MonthDays[2] = 29;
393 	else
394 		MonthDays[2] = 28;
395 
396 	/* apply offset */
397 	if (h_offset || m_offset)
398 	{
399 		tm.tm_min += m_offset;
400 		tm.tm_hour += h_offset;
401 
402 		/* normalize */
403 		if (tm.tm_min < 0)
404 		{
405 			tm.tm_hour--;
406 			tm.tm_min += 60;
407 		}
408 		else if (tm.tm_min > 59)
409 		{
410 			tm.tm_hour++;
411 			tm.tm_min -= 60;
412 		}
413 		if (tm.tm_hour < 0)
414 		{
415 			tm.tm_mday--;
416 			tm.tm_wday--;
417 			tm.tm_hour += 24;
418 		}
419 		else if (tm.tm_hour > 23)
420 		{
421 			tm.tm_mday++;
422 			tm.tm_wday++;
423 			tm.tm_hour -= 24;
424 		}
425 		if (tm.tm_mday < 1)
426 		{
427 			if (--tm.tm_mon == -1)
428 			{
429 				tm.tm_mon = 11;
430 				tm.tm_year--;
431 
432 				/* is the year a leap year? */
433 				if ((tm.tm_year % 4 == 0) &&
434 				    ((tm.tm_year % 100 != 0) || (tm.tm_year % 400 == 0)))
435 					MonthDays[2] = 29;
436 				else
437 					MonthDays[2] = 28;
438 			}
439 			tm.tm_mday += MonthDays[tm.tm_mon];
440 		}
441 		else if (tm.tm_mday > MonthDays[tm.tm_mon])
442 		{
443 			tm.tm_mday -= MonthDays[tm.tm_mon++];
444 			if (tm.tm_mon > 11)
445 			{
446 				tm.tm_mon = 0;
447 				tm.tm_year++;
448 
449 				/*
450 				* Don't have to worry about leap years in
451 				* January.
452 				*/
453 			}
454 		}
455 	}
456 
457 	/* determine day of week if not set from RFC822/1123 line */
458 	if (tm.tm_wday < 0)
459 	{
460 		int i;
461 
462 		for (i = 0; i < tm.tm_mon; i++)
463 			tm.tm_yday += MonthDays[i];
464 		tm.tm_yday += tm.tm_mday;
465 
466 		/* I wouldn't change these constants if I were you... */
467 		tm.tm_wday = (int) (((((tm.tm_year + 699L) * 146097L) / 400L) + tm.tm_yday) % 7);
468 	}
469 
470 	/* now get UT */
471 	if ((p = asctime(&tm)) == NULL || *p == '\0' || strlen(p) < 25)
472 	{
473 #ifdef LOG
474 		if (LogLevel > 0)
475 			syslog(LOG_NOTICE, "%s: arpatounix: asctime failed: %s",
476 				e->e_id, s);
477 #endif /* LOG */
478 		return(NULL);
479 	}
480 	if ((n = index(p, '\n')) != NULL)
481 		*n = '\0';
482 	return(p);
483 }
484