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.3 (Berkeley) 01/21/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 "UT" "GMT" "EST" "EDT" "CST" "CDT" "MST" "MDT" 219 ** "PST" "PDT" or "+"4*DIGIT or "-"4*DIGIT 220 ** (case-insensitive; military zones not useful 221 ** per RFC1123) 222 ** Additional whitespace or comments may occur. 223 */ 224 225 static char MonthDays[] = { 226 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 227 }; 228 229 char * 230 arpatounix(s, e) 231 char *s; 232 ENVELOPE *e; 233 { 234 char *p; 235 char *n; 236 int h_offset = 0; /* hours */ 237 int m_offset = 0; /* minutes */ 238 struct tm tm; 239 extern char *DowList[]; /* defined in collect.c */ 240 extern char *MonthList[]; /* defined in collect.c */ 241 242 bzero((char *) &tm, sizeof tm); 243 tm.tm_wday = -1; /* impossible value */ 244 p = nextatom (s); 245 246 /* next atom must be a day or a date */ 247 if (isalpha((int) *p)) 248 { 249 /* day */ 250 for (tm.tm_wday = 0; DowList[tm.tm_wday]; tm.tm_wday++) 251 { 252 if (strncasecmp (p, DowList[tm.tm_wday], 3)) 253 continue; 254 else 255 { 256 p += 3; 257 break; 258 } 259 } 260 p = nextatom(p); /* ',' */ 261 if (*p == ',') 262 p = nextatom(++p); 263 } 264 265 /* now must have date */ 266 tm.tm_mday = atoi(p); 267 while (isdigit((int) *p)) /* skip over date */ 268 p++; 269 p = nextatom(p); /* point to month name */ 270 for (tm.tm_mon = 0; MonthList[tm.tm_mon]; tm.tm_mon++) 271 { 272 if (strncasecmp(p, MonthList[tm.tm_mon], 3) == 0) 273 { 274 p += 3; 275 break; 276 } 277 } 278 p = nextatom(p); /* year */ 279 tm.tm_year = atoi(p); 280 281 /* if this was 4 digits, subtract 1900 */ 282 if (tm.tm_year > 999) 283 tm.tm_year -= 1900; 284 else 285 { 286 /* if 2 or 3 digits, guess which century and convert */ 287 time_t now; 288 struct tm *gmt; 289 290 (void) time(&now); 291 gmt = gmtime(&now); 292 293 /* more likely +1 day than -100(0) years */ 294 if (gmt->tm_mon == 11 && gmt->tm_mday == 31 && 295 tm.tm_mon == 0 && tm.tm_mday == 1) 296 gmt->tm_year++; 297 if (tm.tm_year > 99) 298 { 299 /* 3 digits */ 300 tm.tm_year += ((gmt->tm_year + 900 - tm.tm_year) / 1000) * 1000; 301 } 302 else 303 { 304 /* 2 digits */ 305 tm.tm_year += ((gmt->tm_year - tm.tm_year) / 100) * 100; 306 } 307 } 308 while (isdigit((int) *p)) /* skip over year */ 309 p++; 310 p = nextatom(p); /* hours */ 311 tm.tm_hour = atoi(p); 312 while (isdigit((int) *p)) /* skip over hours */ 313 p++; 314 p = nextatom(p); /* colon */ 315 if (*p == ':') 316 p = nextatom(++p); 317 p = nextatom(p); /* minutes */ 318 tm.tm_min = atoi(p); 319 while (isdigit((int) *p)) /* skip over minutes */ 320 p++; 321 p = nextatom(p); /* colon or zone */ 322 if (*p == ':') /* have seconds field */ 323 { 324 p = nextatom(++p); 325 tm.tm_sec = atoi(p); 326 while (isdigit((int) *p)) /* skip over seconds */ 327 p++; 328 } 329 p = nextatom(p); /* zone */ 330 if (!strncasecmp(p, "UT", 2) || !strncasecmp(p, "GMT", 3)) 331 ; 332 else if (!strncasecmp(p, "EDT", 3)) 333 h_offset = -4; 334 else if (!strncasecmp(p, "EST", 3)) 335 { 336 h_offset = -5; 337 tm.tm_isdst = 1; 338 } 339 else if (!strncasecmp(p, "CDT", 3)) 340 h_offset = -5; 341 else if (!strncasecmp(p, "CST", 3)) 342 { 343 h_offset = -6; 344 tm.tm_isdst = 1; 345 } 346 else if (!strncasecmp(p, "MDT", 3)) 347 h_offset = -6; 348 else if (!strncasecmp(p, "MST", 3)) 349 { 350 h_offset = -7; 351 tm.tm_isdst = 1; 352 } 353 else if (!strncasecmp(p, "PDT", 3)) 354 h_offset = -7; 355 else if (!strncasecmp(p, "PST", 3)) 356 { 357 h_offset = -8; 358 tm.tm_isdst = 1; 359 } 360 else if (*p == '+') 361 { 362 int off; 363 364 off = atoi(++p); 365 h_offset = off / 100; 366 m_offset = off % 100; 367 } 368 else if (*p == '-') 369 { 370 int off; 371 372 off = atoi(++p); 373 h_offset = off / -100; 374 m_offset = -1 * (off % 100); 375 } 376 else 377 { 378 #ifdef LOG 379 if (LogLevel > 0) 380 syslog(LOG_NOTICE, "%s: arpatounix: unparseable date: %s", 381 e->e_id, s); 382 #endif /* LOG */ 383 return(NULL); 384 } 385 386 /* is the year a leap year? */ 387 if ((tm.tm_year % 4 == 0) && 388 ((tm.tm_year % 100 != 0) || (tm.tm_year % 400 == 0))) 389 MonthDays[2] = 29; 390 else 391 MonthDays[2] = 28; 392 393 /* apply offset */ 394 if (h_offset || m_offset) 395 { 396 tm.tm_min += m_offset; 397 tm.tm_hour += h_offset; 398 399 /* normalize */ 400 if (tm.tm_min < 0) 401 { 402 tm.tm_hour--; 403 tm.tm_min += 60; 404 } 405 else if (tm.tm_min > 59) 406 { 407 tm.tm_hour++; 408 tm.tm_min -= 60; 409 } 410 if (tm.tm_hour < 0) 411 { 412 tm.tm_mday--; 413 tm.tm_wday--; 414 tm.tm_hour += 24; 415 } 416 else if (tm.tm_hour > 23) 417 { 418 tm.tm_mday++; 419 tm.tm_wday++; 420 tm.tm_hour -= 24; 421 } 422 if (tm.tm_mday < 1) 423 { 424 if (--tm.tm_mon == -1) 425 { 426 tm.tm_mon = 11; 427 tm.tm_year--; 428 429 /* is the year a leap year? */ 430 if ((tm.tm_year % 4 == 0) && 431 ((tm.tm_year % 100 != 0) || (tm.tm_year % 400 == 0))) 432 MonthDays[2] = 29; 433 else 434 MonthDays[2] = 28; 435 } 436 tm.tm_mday += MonthDays[tm.tm_mon]; 437 } 438 else if (tm.tm_mday > MonthDays[tm.tm_mon]) 439 { 440 tm.tm_mday -= MonthDays[tm.tm_mon++]; 441 if (tm.tm_mon > 11) 442 { 443 tm.tm_mon = 0; 444 tm.tm_year++; 445 446 /* 447 * Don't have to worry about leap years in 448 * January. 449 */ 450 } 451 } 452 } 453 454 /* determine day of week if not set from RFC822/1123 line */ 455 if (tm.tm_wday < 0) 456 { 457 int i; 458 459 for (i = 0; i < tm.tm_mon; i++) 460 tm.tm_yday += MonthDays[i]; 461 tm.tm_yday += tm.tm_mday; 462 463 /* I wouldn't change these constants if I were you... */ 464 tm.tm_wday = (int) (((((tm.tm_year + 699L) * 146097L) / 400L) + tm.tm_yday) % 7); 465 } 466 467 /* now get UT */ 468 if ((p = asctime(&tm)) == NULL || *p == '\0' || strlen(p) < 25) 469 { 470 #ifdef LOG 471 if (LogLevel > 0) 472 syslog(LOG_NOTICE, "%s: arpatounix: asctime failed: %s", 473 e->e_id, s); 474 #endif /* LOG */ 475 return(NULL); 476 } 477 if ((n = index(p, '\n')) != NULL) 478 *n = '\0'; 479 return(p); 480 } 481