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.5 (Berkeley) 02/16/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 (isdigit((int) *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 (isdigit((int) *p)) /* skip over year */ 366 p++; 367 p = nextatom(p); /* hours */ 368 tm.tm_hour = atoi(p); 369 while (isdigit((int) *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 (isdigit((int) *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 (isdigit((int) *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