1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * Copyright (c) 2018, Joyent, Inc. 29 */ 30 31 #include <stdlib.h> 32 #include <stdio.h> 33 #include <string.h> 34 #include <unistd.h> 35 #include <sys/param.h> 36 #include <sys/types.h> 37 #include <sys/stat.h> 38 #include <tzfile.h> 39 #include <fcntl.h> 40 #include <regex.h> 41 #include <errno.h> 42 #include <libintl.h> 43 #include <libzoneinfo.h> 44 45 #define DEFINIT "/etc/default/init" 46 #define ZONEINFOTABDIR "/usr/share/lib/zoneinfo/tab/" 47 #define CONTINENT_TAB ZONEINFOTABDIR "continent.tab" 48 #define COUNTRY_TAB ZONEINFOTABDIR "country.tab" 49 #define ZONE_SUN_TAB ZONEINFOTABDIR "zone_sun.tab" 50 51 #define NEWLINE "\n" 52 #define SLASH "/" 53 #define WHITESPACE "\t " 54 #define WHITESPACE_NL "\t \n" 55 #define DIGITS "0123456789" 56 #define BUFFLEN 1024 57 58 #define CCLEN 2 /* country code length */ 59 60 #define GMT_MAX (12*60*60) /* The maximum GMT offset */ 61 #define GMT_MIN (-13*60*60) /* The minimum GMT offset */ 62 #define GMT_FMT_Q "<GMT%c%d>%c%d" 63 #define GMT_FMT_Q_LEN (11) /* "<GMT+dd>+dd" - maximum 11 chars */ 64 #define GMT0_FMT "GMT0" /* backwards compatibility name */ 65 #define GMT_FMT_ZONE ":Etc/GMT%c%d" /* ":Etc/GMT+dd" */ 66 #define GMT_FMT_ZONE_LEN (11) /* ":Etc/GMT+dd" - maximum 11 chars */ 67 68 #define TZ_FMT "TZ=%s\n" /* format TZ entry init file */ 69 #define TZ_FMT_Q "TZ=\"%s\"\n" /* format quoted TZ entry init file */ 70 71 #define COORD_FMTLEN1 (sizeof ("+DDMM+DDDMM") - 1) 72 #define COORD_FMTLEN2 (sizeof ("+DDMMSS+DDDMMSS") - 1) 73 #define COORD_FMT1 (1) /* flag for format 1 */ 74 #define COORD_FMT2 (2) /* flag for format 2 */ 75 #define COORD_DLEN_LAT (2) /* length of DD for latitude */ 76 #define COORD_DLEN_LONG (3) /* length of DDD for longtitude */ 77 #define COORD_MLEN (2) /* length of MM */ 78 #define COORD_SLEN (2) /* length of SS */ 79 80 #define TRAILER "/XXXXXX" 81 #define TR_LEN (sizeof (TRAILER) -1) 82 83 /* Internal Declarations */ 84 static char *skipwhite(char *); 85 static int skipline(char *); 86 static int trav_link(char **); 87 static void remove_component(char *); 88 static void strip_quotes(char *, char *); 89 static int compar(struct tz_country *, struct tz_country *); 90 static int get_coord(struct tz_timezone *, char *, size_t); 91 static int _tz_match(const char *, const char *); 92 static char *_conv_gmt_zoneinfo(int); 93 static char *_conv_gmt_posix(int); 94 95 /* 96 * get_tz_continents() reads the continent.tab file, and 97 * returns a list of continents. 98 */ 99 int 100 get_tz_continents(struct tz_continent **cont) 101 { 102 FILE *fp; 103 char buff[BUFFLEN]; 104 char *lp; /* line pointer */ 105 char *lptr, *ptr; /* temp pointer */ 106 struct tz_continent *head = NULL, *lcp, *prev = NULL; 107 int sav_errno = 0, ncount, status; 108 size_t len; 109 110 /* open continents file */ 111 if ((fp = fopen(CONTINENT_TAB, "r")) == NULL) { 112 /* fopen() sets errno */ 113 return (-1); 114 } 115 /* read and count continents */ 116 ncount = 0; 117 /*CONSTANTCONDITION*/ 118 while (1) { 119 if (fgets(buff, sizeof (buff), fp) == NULL) { 120 if (feof(fp) == 0) { 121 /* fgets() sets errno */ 122 sav_errno = errno; 123 ncount = -1; 124 } 125 break; 126 } 127 /* Skip comments or blank/whitespace lines */ 128 if ((status = skipline(buff)) != 0) { 129 if (status == 1) 130 continue; 131 else { 132 sav_errno = EINVAL; 133 ncount = -1; 134 break; 135 } 136 } 137 /* Get continent name */ 138 lp = skipwhite(&buff[0]); 139 if ((len = strcspn(lp, WHITESPACE)) > _TZBUFLEN -1) { 140 sav_errno = ENAMETOOLONG; 141 ncount = -1; 142 break; 143 } 144 /* create continent struct */ 145 if ((lcp = (struct tz_continent *) 146 calloc(1, sizeof (struct tz_continent))) == NULL) { 147 sav_errno = ENOMEM; 148 ncount = -1; 149 break; 150 } 151 (void) strncpy(lcp->ctnt_name, lp, len); 152 lcp->ctnt_name[len] = '\0'; 153 154 /* Get continent description */ 155 lp = skipwhite(lp + len); 156 len = strcspn(lp, NEWLINE); 157 if ((ptr = malloc(len + 1)) == NULL) { 158 (void) free_tz_continents(lcp); 159 sav_errno = ENOMEM; 160 ncount = -1; 161 break; 162 } 163 (void) strncpy(ptr, lp, len); 164 *(ptr + len) = '\0'; 165 lcp->ctnt_id_desc = ptr; 166 167 /* Get localized continent description */ 168 lptr = dgettext(TEXT_DOMAIN, lcp->ctnt_id_desc); 169 if ((ptr = strdup(lptr)) == NULL) { 170 (void) free_tz_continents(lcp); 171 sav_errno = ENOMEM; 172 ncount = -1; 173 break; 174 } 175 lcp->ctnt_display_desc = ptr; 176 177 if (head == NULL) { 178 head = lcp; 179 } else { 180 prev->ctnt_next = lcp; 181 } 182 prev = lcp; 183 ncount++; 184 } 185 (void) fclose(fp); 186 if (ncount == -1) { 187 if (head != NULL) { 188 (void) free_tz_continents(head); 189 } 190 if (sav_errno) 191 errno = sav_errno; 192 } else { 193 *cont = head; 194 } 195 return (ncount); 196 } 197 198 /* 199 * get_tz_countries() finds the list of countries from the zone_sun.tab 200 * file, for the input continent, and retrieves the country 201 * names from the country.tab file. It also retrieves the localized 202 * country names. The returned list of countries is sorted by the 203 * countries' localized name fields. 204 */ 205 int 206 get_tz_countries(struct tz_country **country, struct tz_continent *cont) 207 { 208 FILE *fp_zone, *fp_cc; 209 char buff[BUFFLEN], ccbuf[_CCBUFLEN], *ptr; 210 char *lp, *lptr, *lp_coord, *lp_cc, *lp_tz; /* line pointer */ 211 struct tz_country *head = NULL, *prev = NULL, *next, *cp, *cp2; 212 int sav_errno = 0, ncount, i; 213 int cmp, status; 214 size_t len, len_coord, len_ctnt; 215 216 len_ctnt = strlen(cont->ctnt_name); 217 ccbuf[0] = '\0'; 218 219 /* open zone_sun.tab and country.tab files */ 220 if ((fp_zone = fopen(ZONE_SUN_TAB, "r")) == NULL) { 221 /* fopen() sets errno */ 222 return (-1); 223 } 224 if ((fp_cc = fopen(COUNTRY_TAB, "r")) == NULL) { 225 /* fopen() sets errno */ 226 (void) fclose(fp_zone); 227 return (-1); 228 } 229 230 /* read timezones to match continents, and get countries */ 231 ncount = 0; 232 /*CONSTANTCONDITION*/ 233 while (1) { 234 if (fgets(buff, sizeof (buff), fp_zone) == NULL) { 235 if (feof(fp_zone) == 0) { 236 /* fgets() error - errno set */ 237 sav_errno = errno; 238 ncount = -1; 239 } 240 break; 241 } 242 /* Skip comments or blank/whitespace lines */ 243 if ((status = skipline(buff)) != 0) { 244 if (status == 1) 245 continue; 246 else { 247 sav_errno = EINVAL; 248 ncount = -1; 249 break; 250 } 251 } 252 /* 253 * If country matches previously *matched* country, skip 254 * entry, since zone.tab is alphabetized by country code 255 * (It should be a *matched* country, because the same country 256 * can be in different continents.) 257 */ 258 /* Get country code */ 259 lp_cc = skipwhite(&buff[0]); 260 if (strcspn(lp_cc, WHITESPACE) != CCLEN) { 261 ncount = -1; 262 sav_errno = EINVAL; 263 break; 264 } 265 /* Check country code cache; skip if already found */ 266 if (strncmp(ccbuf, lp_cc, CCLEN) == 0) { 267 continue; 268 } 269 /* Get coordinates */ 270 lp_coord = skipwhite(lp_cc + CCLEN); 271 if (((len_coord = strcspn(lp_coord, WHITESPACE)) != 272 COORD_FMTLEN1) && 273 (len_coord != COORD_FMTLEN2)) { 274 ncount = -1; 275 sav_errno = EINVAL; 276 break; 277 } 278 279 /* Get timezone name (Skip timezone description) */ 280 lp_tz = skipwhite(lp_coord + len_coord); 281 if ((len = strcspn(lp_tz, SLASH)) == 0) { 282 ncount = -1; 283 sav_errno = EINVAL; 284 break; 285 } 286 /* If continents match, allocate a country struct */ 287 if ((len == len_ctnt) && 288 (strncmp(cont->ctnt_name, lp_tz, len) == 0)) { 289 if ((cp = (struct tz_country *) 290 calloc(1, sizeof (struct tz_country))) == NULL) { 291 sav_errno = ENOMEM; 292 ncount = -1; 293 break; 294 } 295 /* Copy and save country code (len already checked) */ 296 (void) strncpy(cp->ctry_code, lp_cc, CCLEN); 297 cp->ctry_code[CCLEN] = '\0'; 298 (void) strncpy(ccbuf, lp_cc, CCLEN); 299 ccbuf[CCLEN] = '\0'; 300 301 /* Create linked list */ 302 if (head == NULL) { 303 head = cp; 304 } else { 305 prev->ctry_next = cp; 306 }; 307 prev = cp; 308 ncount++; 309 } 310 } /* while */ 311 312 if (ncount == -1) 313 goto error; 314 315 /* Get country name from country.tab; get localized country name */ 316 /* Read country list, match country codes to process entry */ 317 cp = head; 318 /*CONSTANTCONDITION*/ 319 while (1) { 320 if (fgets(buff, sizeof (buff), fp_cc) == NULL) { 321 if (feof(fp_cc) == 0) { 322 /* fgets() sets errno */ 323 ncount = -1; 324 sav_errno = errno; 325 } 326 break; 327 } 328 /* Skip comments or blank/whitespace lines */ 329 if ((status = skipline(buff)) != 0) { 330 if (status == 1) 331 continue; 332 else { 333 sav_errno = EINVAL; 334 ncount = -1; 335 break; 336 } 337 } 338 /* Match country codes */ 339 if ((len = strcspn(buff, WHITESPACE)) != CCLEN) { 340 sav_errno = EINVAL; 341 ncount = -1; 342 break; 343 } 344 if ((cmp = strncmp(cp->ctry_code, buff, CCLEN)) == 0) { 345 /* Get country description, and localized desc. */ 346 /* Skip to country description */ 347 lp = &buff[CCLEN]; 348 if ((len = strspn(lp, WHITESPACE)) == 0) { 349 sav_errno = EINVAL; 350 ncount = -1; 351 break; 352 } 353 lp += len; /* lp points to country desc. */ 354 len = strcspn(lp, NEWLINE); 355 if ((ptr = calloc(len + 1, 1)) == NULL) { 356 ncount = -1; 357 errno = ENOMEM; 358 break; 359 } 360 (void) strncpy(ptr, lp, len); 361 *(ptr + len) = '\0'; 362 cp->ctry_id_desc = ptr; 363 364 /* Get localized country description */ 365 lptr = dgettext(TEXT_DOMAIN, ptr); 366 if ((ptr = strdup(lptr)) == NULL) { 367 ncount = -1; 368 errno = ENOMEM; 369 break; 370 } 371 cp->ctry_display_desc = ptr; 372 } else if (cmp > 0) { 373 /* Keep searching country.tab */ 374 continue; 375 } else { 376 /* Not found - should not happen */ 377 ncount = -1; 378 errno = EILSEQ; 379 break; 380 } 381 if (cp->ctry_next == NULL) { 382 /* done with countries list */ 383 break; 384 } else { 385 cp = cp->ctry_next; 386 } 387 } /* while */ 388 389 /* Now sort the list by ctry_display_desc field */ 390 if ((ncount != -1) && 391 ((cp2 = calloc(ncount, sizeof (struct tz_country))) != NULL)) { 392 /* 393 * First copy list to a static array for qsort() to use. 394 * Use the cnt_next field to point back to original structure. 395 */ 396 cp = head; 397 for (i = 0; i < ncount; i++) { 398 next = cp->ctry_next; 399 cp->ctry_next = cp; 400 (void) memcpy(&cp2[i], cp, sizeof (struct tz_country)); 401 cp = next; 402 } 403 404 /* Next, call qsort() using strcoll() to order */ 405 qsort(cp2, ncount, sizeof (struct tz_country), 406 (int (*)(const void *, const void *))compar); 407 408 /* Rearrange the country list according to qsort order */ 409 head = cp2->ctry_next; /* ctry_next is pointer to orig struct */ 410 cp = head; 411 for (i = 0; i < ncount; i++) { 412 prev = cp; 413 cp = cp2[i].ctry_next; 414 prev->ctry_next = cp; 415 } 416 cp->ctry_next = NULL; 417 418 /* Last, free the static buffer */ 419 free(cp2); 420 421 } else { 422 if (ncount != -1) 423 ncount = -1; 424 } 425 426 error: 427 (void) fclose(fp_zone); 428 (void) fclose(fp_cc); 429 if (ncount == -1) { 430 /* free the linked list */ 431 if (head != NULL) 432 (void) free_tz_countries(head); 433 if (sav_errno) 434 errno = sav_errno; 435 } else { 436 *country = head; 437 } 438 return (ncount); 439 } 440 441 /* 442 * get_timezones_by_country() finds the list of timezones from the 443 * zone_sun.tab file, for the input country. 444 */ 445 int 446 get_timezones_by_country(struct tz_timezone **tmzone, 447 struct tz_country *country) 448 { 449 FILE *fp_zone; /* zone.tab */ 450 int match = 0, ncount = 0, sav_errno = 0, status; 451 char buff[1024]; 452 char *lp_cc, *lp_tz, *lp_otz, *lp_coord, *lp_tzdesc, *ptr, *lptr; 453 size_t len_tz, len_otz, len_coord, len_tzdesc; 454 struct tz_timezone *head = NULL, *prev = NULL, *tp; 455 456 /* open zone.tab file */ 457 if ((fp_zone = fopen(ZONE_SUN_TAB, "r")) == NULL) 458 return (-1); 459 460 /* Read through zone.tab until countries match */ 461 /*CONSTANTCONDITION*/ 462 while (1) { 463 if (fgets(buff, sizeof (buff), fp_zone) == NULL) { 464 if (feof(fp_zone)) { 465 break; 466 } else { 467 /* fgets() sets errno */ 468 ncount = -1; 469 sav_errno = errno; 470 break; 471 } 472 } 473 /* Skip comments or blank/whitespace lines */ 474 if ((status = skipline(buff)) != 0) { 475 if (status == 1) 476 continue; 477 else { 478 sav_errno = EINVAL; 479 ncount = -1; 480 break; 481 } 482 } 483 /* 484 * Find country entries, or detect if no country matches. 485 */ 486 lp_cc = skipwhite(&buff[0]); 487 if (strcspn(lp_cc, WHITESPACE) != CCLEN) { 488 sav_errno = EINVAL; 489 ncount = -1; 490 break; 491 } 492 if (strncmp(country->ctry_code, lp_cc, CCLEN) == 0) { 493 match = 1; 494 495 /* Get coordinates */ 496 lp_coord = skipwhite(lp_cc + CCLEN); 497 if (((len_coord = strcspn(lp_coord, WHITESPACE)) != 498 COORD_FMTLEN1) && 499 (len_coord != COORD_FMTLEN2)) { 500 ncount = -1; 501 sav_errno = EINVAL; 502 break; 503 } 504 /* Get Olson timezone name */ 505 lp_otz = skipwhite(lp_coord + len_coord); 506 len_otz = strcspn(lp_otz, WHITESPACE); 507 508 /* Get Solaris compatible timezone name */ 509 lp_tz = skipwhite(lp_otz + len_otz); 510 len_tz = strcspn(lp_tz, WHITESPACE_NL); 511 if (*(lp_tz + len_tz - 1) == '\n') { 512 /* No timezone description */ 513 len_tz--; 514 lp_tzdesc = NULL; 515 len_tzdesc = 0; 516 } else { 517 /* Get timezone description */ 518 lp_tzdesc = skipwhite(lp_tz + 519 len_tz); 520 len_tzdesc = strcspn(lp_tzdesc, 521 NEWLINE); 522 } 523 /* 524 * Check tz name lengths. This check assumes the 525 * tz_oname and tz_name fields are the same size. 526 * (since tz_name may be written with lp_otz, if 527 * lp_tz is "-".) 528 */ 529 if ((len_otz > _TZBUFLEN - 1) || 530 (len_tz > _TZBUFLEN - 1)) { 531 sav_errno = ENAMETOOLONG; 532 ncount = -1; 533 break; 534 } 535 /* Create timezone struct */ 536 if ((tp = (struct tz_timezone *) 537 calloc(1, sizeof (struct tz_timezone))) == 538 NULL) { 539 sav_errno = ENOMEM; 540 ncount = -1; 541 break; 542 } 543 /* 544 * Copy the timezone names - use the Solaris 545 * compatible timezone name if one exists, 546 * otherwise use the current Olson timezone 547 * name. 548 */ 549 (void) strncpy(tp->tz_oname, lp_otz, len_otz); 550 tp->tz_oname[len_otz] = '\0'; 551 if (strncmp("-", lp_tz, len_tz) == 0) { 552 lp_tz = lp_otz; 553 len_tz = len_otz; 554 } 555 /* If name has numeric digits, prefix ':' */ 556 if (strcspn(lp_tz, DIGITS) < len_tz) { 557 if (len_tz > _TZBUFLEN - 2) { 558 free(tp); 559 sav_errno = ENAMETOOLONG; 560 ncount = -1; 561 break; 562 } 563 tp->tz_name[0] = ':'; 564 (void) strncpy(tp->tz_name + 1, lp_tz, len_tz); 565 tp->tz_name[len_tz + 1] = '\0'; 566 } else { 567 (void) strncpy(tp->tz_name, lp_tz, len_tz); 568 tp->tz_name[len_tz] = '\0'; 569 } 570 /* Process timezone description, if one exists */ 571 if ((lp_tzdesc != NULL) && (*lp_tzdesc != '\n')) { 572 if ((ptr = calloc(1, len_tzdesc + 1)) 573 == NULL) { 574 sav_errno = ENOMEM; 575 ncount = -1; 576 (void) free_timezones(tp); 577 break; 578 } 579 (void) strncpy(ptr, lp_tzdesc, len_tzdesc); 580 *(ptr + len_tzdesc) = '\0'; 581 tp->tz_id_desc = ptr; 582 583 /* Get localized country description */ 584 lptr = dgettext(TEXT_DOMAIN, ptr); 585 if ((ptr = strdup(lptr)) == NULL) { 586 sav_errno = ENOMEM; 587 ncount = -1; 588 (void) free_timezones(tp); 589 break; 590 } 591 tp->tz_display_desc = ptr; 592 593 } else { 594 tp->tz_id_desc = NULL; 595 tp->tz_display_desc = NULL; 596 } 597 /* Get coordinate information */ 598 if (get_coord(tp, lp_coord, len_coord) == -1) { 599 sav_errno = EILSEQ; 600 ncount = -1; 601 (void) free_timezones(tp); 602 break; 603 } 604 /* Store timezone struct in a linked list */ 605 if (head == NULL) { 606 head = tp; 607 } else { 608 prev->tz_next = tp; 609 } 610 prev = tp; 611 ncount++; 612 } else { 613 if (match == 1) { 614 /* 615 * At this point, since zone_sun.tab is ordered, 616 * if we've already found timezone entries for 617 * the input country, then we've found all of 618 * the desired timezone entries (since we will 619 * be past that country's section in 620 * zone_sun.tab), and we are done. 621 */ 622 break; 623 } 624 } 625 } 626 627 /* Finish up */ 628 (void) fclose(fp_zone); 629 if (ncount == -1) { 630 if (head != NULL) 631 (void) free_timezones(head); 632 if (sav_errno) 633 errno = sav_errno; 634 } else { 635 *tmzone = head; 636 } 637 return (ncount); 638 } 639 640 int 641 free_tz_continents(struct tz_continent *cont) 642 { 643 struct tz_continent *cptr, *cprev; 644 645 cptr = cont; 646 while (cptr != NULL) { 647 if (cptr->ctnt_id_desc != NULL) 648 free(cptr->ctnt_id_desc); 649 if (cptr->ctnt_display_desc != NULL) 650 free(cptr->ctnt_display_desc); 651 cprev = cptr; 652 cptr = cptr->ctnt_next; 653 free(cprev); 654 } 655 return (0); 656 } 657 658 int 659 free_tz_countries(struct tz_country *country) 660 { 661 struct tz_country *cptr, *cprev; 662 663 cptr = country; 664 while (cptr != NULL) { 665 if (cptr->ctry_id_desc != NULL) 666 free(cptr->ctry_id_desc); 667 if (cptr->ctry_display_desc != NULL) 668 free(cptr->ctry_display_desc); 669 cprev = cptr; 670 cptr = cptr->ctry_next; 671 free(cprev); 672 } 673 return (0); 674 } 675 676 int 677 free_timezones(struct tz_timezone *timezone) 678 { 679 struct tz_timezone *tzptr, *tzprev; 680 681 tzptr = timezone; 682 while (tzptr != NULL) { 683 if (tzptr->tz_id_desc != NULL) 684 free(tzptr->tz_id_desc); 685 if (tzptr->tz_display_desc != NULL) 686 free(tzptr->tz_display_desc); 687 tzprev = tzptr; 688 tzptr = tzptr->tz_next; 689 free(tzprev); 690 } 691 return (0); 692 } 693 694 /* 695 * conv_gmt() returns a GMT-offset style timezone 696 * If flag = 0, return Quoted POSIX timezone like: <GMT+8>+8 697 * If flag = 1, return zoneinfo timezone like: :Etc/GMT+8 698 */ 699 char * 700 conv_gmt(int seconds, int flag) 701 { 702 int hour; 703 char *cp; 704 705 if ((seconds < _GMT_MIN) || (seconds > _GMT_MAX)) { 706 errno = EINVAL; 707 return (NULL); 708 } 709 hour = (seconds / 60) / 60; 710 711 if (flag == 0) { 712 cp = _conv_gmt_posix(hour); 713 } else if (flag == 1) { 714 cp = _conv_gmt_zoneinfo(hour); 715 } else { 716 errno = EINVAL; 717 return (NULL); 718 } 719 return (cp); 720 } 721 722 static char * 723 _conv_gmt_posix(int hour) 724 { 725 char *cp; 726 char xsign; 727 728 if (hour == 0) { 729 if ((cp = strdup(GMT0_FMT)) == NULL) { 730 errno = ENOMEM; 731 return (NULL); 732 } 733 } else { 734 if (hour < 0) { 735 xsign = '-'; 736 /* make hour positive for snprintf() */ 737 hour = -hour; 738 } else { 739 xsign = '+'; 740 } 741 if ((cp = malloc(GMT_FMT_Q_LEN + 1)) == NULL) { 742 errno = ENOMEM; 743 return (NULL); 744 } 745 (void) snprintf(cp, GMT_FMT_Q_LEN + 1, GMT_FMT_Q, 746 xsign, hour, xsign, hour); 747 } 748 return (cp); 749 } 750 751 static char * 752 _conv_gmt_zoneinfo(int hour) 753 { 754 char *cp; 755 char xsign; 756 757 if (hour < 0) { 758 xsign = '-'; 759 /* make hour positive for snprintf() */ 760 hour = -hour; 761 } else { 762 xsign = '+'; 763 } 764 if ((cp = malloc(GMT_FMT_ZONE_LEN + 1)) == NULL) { 765 errno = ENOMEM; 766 return (NULL); 767 } 768 (void) snprintf(cp, GMT_FMT_ZONE_LEN + 1, GMT_FMT_ZONE, 769 xsign, hour); 770 return (cp); 771 } 772 773 /* Regular expression for POSIX GMT-offset timezone */ 774 #define _GMT_EXPR "(" _GMT_EXPR_U "|" _GMT_EXPR_Q ")" 775 #define _GMT_EXPR_U "^[gG][mM][tT][-+]?[0-2]?[0-9]$" 776 #define _GMT_EXPR_Q "^<[gG][mM][tT][-+]?[0-2]?[0-9]>[-+]?[0-2]?[0-9]$" 777 778 /* 779 * Regular expression for quoted POSIX timezone. 780 */ 781 /* Avoid alphabetic ranges (eg, a-z) due to effect of LC_COLLATE */ 782 #define _ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 783 #define _NUM "0123456789" /* for safe */ 784 #define _STD_Q_ELM "[-+" _ALPHA _NUM "]" 785 #define _STD_Q "<" _STD_Q_ELM _STD_Q_ELM _STD_Q_ELM "+>" 786 787 /* Regular expression for unquoted POSIX timezone */ 788 #define _STD_U_ELM_1 "[^-+,<" _NUM "]" 789 #define _STD_U_ELM "[^-+," _NUM "]" 790 #define _STD_U _STD_U_ELM_1 _STD_U_ELM _STD_U_ELM "+" 791 792 /* Regular expression for POSIX timezone */ 793 #define _STD "(" _STD_U "|" _STD_Q ")" 794 #define _DST _STD 795 #define _OFFSET "[-+]?" _TIME 796 #define _START "(" _DATEJ "|" _DATEn "|" _DATEM ")" 797 #define _DATEJ "J(([0-2]?[0-9]?[0-9])|3[0-5][0-9]|36[0-5])" 798 #define _DATEn "(([0-2]?[0-9]?[0-9])|3[0-5][0-9]|36[0-5])" 799 #define _DATEM "M([0-9]|10|11|12)\\.[1-5]\\.[0-6]" 800 #define _END _START 801 #define _TIME _HH "(:" _MM "(:" _SS ")?" ")?" 802 #define _HH "(([0-1]?[0-9])|20|21|22|23|24)" 803 #define _MM "[0-5]?[0-9]" 804 #define _SS _MM 805 #define _POSIX_EXPR "^" _STD _OFFSET "(" _DST "(" _OFFSET ")?" \ 806 "(," _START "(/" _TIME ")?" \ 807 "," _END "(/" _TIME ")?" ")?" ")?" "$" 808 809 #define LEN_TZDIR (sizeof (TZDIR) - 1) 810 811 /* 812 * isvalid_tz() checks if timezone is a valid POSIX or zoneinfo 813 * timezone, depending on the value of flag. For flag = _VTZ_INSTALL, 814 * isvalid_tz() behaves according to the behavior of Solaris Install 815 * in Solaris 9 and earlier, where timezones under /usr/share/lib/zoneinfo 816 * were validated. isvalid_tz() has a special check for GMT+-* timezones 817 * because Solaris Install validated /usr/share/lib/zoneinfo/GMT+-*. 818 * However, when /usr/share/lib/zoneinfo/GMT+-* are EOF'd, that check 819 * no longer works. 820 * 821 * isvalid_tz() returns 1 if a valid timezone is detected. 822 */ 823 int 824 isvalid_tz(char *timezone, char *root, int flag) 825 { 826 char path[MAXPATHLEN]; 827 char buf[sizeof (struct tzhead)]; 828 int fid, ret; 829 830 if ((timezone == NULL) || (*timezone == '\0')) { 831 return (0); 832 } 833 834 /* First check if timezone is a valid POSIX timezone */ 835 switch (flag) { 836 case _VTZ_INSTALL: 837 /* 838 * Special check for POSIX GMT timezone. 839 * If no match, check for zoneinfo timezone below 840 */ 841 if (_tz_match(_GMT_EXPR, timezone) == 0) { 842 /* Valid GMT timezone */ 843 return (1); 844 } 845 break; 846 case _VTZ_POSIX: 847 /* Check for generic POSIX timezone */ 848 if (_tz_match(_POSIX_EXPR, timezone) == 0) { 849 /* Valid POSIX timezone */ 850 return (1); 851 } 852 /* Invalid POSIX timezone */ 853 return (0); 854 case _VTZ_ALL: 855 /* Check for generic POSIX timezone */ 856 if (_tz_match(_POSIX_EXPR, timezone) == 0) { 857 /* Valid POSIX timezone */ 858 return (1); 859 } 860 break; 861 case _VTZ_ZONEINFO: 862 break; 863 default: 864 return (0); 865 } 866 867 /* 868 * Check for valid zoneinfo timezone - 869 * open zoneinfo file and check for magic number 870 */ 871 872 /* skip prepended ':' if one exists */ 873 if (*timezone == ':') { 874 timezone++; 875 } 876 /* Construct full zoneinfo pathname */ 877 if ((root != NULL) && (*root != '\0')) { 878 ret = snprintf(path, sizeof (path), 879 "%s%s/%s", root, TZDIR, timezone); 880 if (ret >= sizeof (path)) { 881 /* too long */ 882 return (0); 883 } 884 } else { 885 ret = snprintf(path, sizeof (path), 886 "%s/%s", TZDIR, timezone); 887 if (ret >= sizeof (path)) { 888 /* too long */ 889 return (0); 890 } 891 } 892 if ((fid = open(path, O_RDONLY)) == -1) { 893 return (0); 894 } 895 if (read(fid, buf, sizeof (struct tzhead)) != 896 sizeof (struct tzhead)) { 897 (void) close(fid); 898 return (0); 899 } 900 if (strncmp(buf, TZ_MAGIC, sizeof (TZ_MAGIC) - 1) != 0) { 901 (void) close(fid); 902 return (0); 903 } 904 if (close(fid) == -1) { 905 return (0); 906 } 907 /* Valid zoneinfo timezone */ 908 return (1); 909 } 910 911 #define N_MATCH 1 912 913 int 914 _tz_match(const char *expr, const char *string) 915 { 916 regex_t reg; 917 regmatch_t pmatch[N_MATCH]; 918 int ret; 919 920 ret = regcomp(®, expr, REG_EXTENDED); 921 if (ret != 0) { 922 return (-1); 923 } 924 925 ret = regexec((const regex_t *)®, string, N_MATCH, pmatch, 0); 926 if (ret == 0) { 927 #ifdef DEBUG 928 printf("OK matched - %s\n", string); 929 #endif 930 regfree(®); 931 return (0); 932 } 933 #ifdef DEBUG 934 printf("NOT matched - %s\n", string); 935 #endif 936 regfree(®); 937 return (-1); 938 } 939 940 char * 941 get_system_tz(char *root) 942 { 943 FILE *ifp; 944 char buff[512]; 945 int serrno, ret; 946 char *sp, *ptr, *p; 947 char fname[MAXPATHLEN]; 948 949 if ((ret = snprintf(fname, sizeof (fname), "%s/%s", root, DEFINIT)) >= 950 sizeof (fname)) { 951 errno = ENAMETOOLONG; 952 return (NULL); 953 } else if (ret < 0) { 954 return (NULL); 955 } 956 if ((ifp = fopen(fname, "r")) == NULL) 957 return (NULL); 958 while (fgets(buff, sizeof (buff), ifp) != NULL) { 959 if (strncmp(buff, "TZ=", 3) == 0) { 960 (void) fclose(ifp); 961 p = &buff[3]; 962 if ((sp = strchr(p, ';')) != NULL) { 963 *sp = '\0'; 964 } else if ((sp = strchr(p, '\n')) != NULL) { 965 *sp = '\0'; 966 } 967 if (strpbrk(p, "\"'") != NULL) { 968 strip_quotes(p, p); 969 } 970 ptr = strdup(p); 971 if (ptr == NULL) { 972 errno = ENOMEM; 973 return (NULL); 974 } 975 return (ptr); 976 } 977 } 978 979 /* Either reached EOF with no TZ= entry, or got fgets() error */ 980 serrno = errno; 981 if (feof(ifp) != 0) { 982 /* No "TZ=" entry found */ 983 serrno = EINVAL; 984 } 985 (void) fclose(ifp); 986 errno = serrno; 987 return (NULL); 988 } 989 990 int 991 set_system_tz(char *tz, char *root) 992 { 993 FILE *ifp, *ofp; /* Input & output files */ 994 char *tmpdir, *tmp; /* Temp file name and location */ 995 char buff[1024]; 996 int replaced = 0, ret, serrno; 997 char *tdb; 998 struct stat sb; 999 char fname[MAXPATHLEN]; 1000 const char *tzfmt; 1001 int len, fd; 1002 1003 if (tz == NULL || root == NULL) 1004 return (-1); 1005 1006 if (strchr(tz, '<')) { 1007 tzfmt = TZ_FMT_Q; 1008 } else { 1009 tzfmt = TZ_FMT; 1010 } 1011 1012 if ((ret = snprintf(fname, sizeof (fname), "%s/%s", root, DEFINIT)) >= 1013 sizeof (fname)) { 1014 errno = ENAMETOOLONG; 1015 return (-1); 1016 } else if (ret < 0) { 1017 return (-1); 1018 } 1019 1020 /* 1021 * Generate temporary file name to use. We make sure it's in the same 1022 * directory as the db we're processing so that we can use rename to 1023 * do the replace later. Otherwise we run the risk of being on the 1024 * wrong filesystem and having rename() fail for that reason. 1025 */ 1026 tdb = fname; 1027 if (trav_link(&tdb) == -1) 1028 return (-1); 1029 if ((tmpdir = strdup(tdb)) == NULL) { 1030 errno = ENOMEM; 1031 return (-1); 1032 } 1033 remove_component(tmpdir); 1034 if ((len = strlen(tmpdir)) == 0) { 1035 (void) strcpy(tmpdir, "."); 1036 len = 1; 1037 } 1038 1039 if ((tmp = malloc(len + TR_LEN + 1)) == NULL) { 1040 free(tmpdir); 1041 errno = ENOMEM; 1042 return (-1); 1043 } 1044 (void) strcpy(tmp, tmpdir); 1045 (void) strcpy(tmp + len, TRAILER); 1046 free(tmpdir); 1047 if ((fd = mkstemp(tmp)) == -1) { 1048 free(tmp); 1049 return (-1); 1050 } 1051 if ((ofp = fdopen(fd, "w")) == NULL) { 1052 serrno = errno; 1053 (void) close(fd); 1054 free(tmp); 1055 errno = serrno; 1056 return (-1); 1057 } 1058 1059 /* Preserve permissions of current file if it exists */ 1060 if (stat(tdb, &sb) == 0) { 1061 if (fchmod(fileno(ofp), sb.st_mode) == -1) { 1062 serrno = errno; 1063 (void) fclose(ofp); 1064 (void) unlink(tmp); 1065 free(tmp); 1066 errno = serrno; 1067 return (-1); 1068 } 1069 if (fchown(fileno(ofp), sb.st_uid, sb.st_gid) == -1) { 1070 serrno = errno; 1071 (void) fclose(ofp); 1072 (void) unlink(tmp); 1073 free(tmp); 1074 errno = serrno; 1075 return (-1); 1076 } 1077 } else if (errno != ENOENT) { 1078 serrno = errno; 1079 (void) fclose(ofp); 1080 (void) unlink(tmp); 1081 free(tmp); 1082 errno = serrno; 1083 return (-1); 1084 } 1085 1086 if ((ifp = fopen(fname, "r+")) != NULL) { 1087 while (fgets(buff, sizeof (buff), ifp) != NULL) { 1088 if (!replaced && (strncmp(buff, "TZ=", 3) == 0)) { 1089 ret = snprintf(buff, sizeof (buff), tzfmt, 1090 tz); 1091 if ((ret >= sizeof (buff)) || (ret < 0)) { 1092 if (ret >= sizeof (buff)) 1093 serrno = EINVAL; 1094 (void) fclose(ofp); 1095 (void) fclose(ifp); 1096 (void) unlink(tmp); 1097 free(tmp); 1098 errno = serrno; 1099 return (-1); 1100 } 1101 replaced = 1; 1102 } 1103 if (fputs(buff, ofp) == EOF) { 1104 serrno = errno; 1105 (void) fclose(ofp); 1106 (void) fclose(ifp); 1107 (void) unlink(tmp); 1108 free(tmp); 1109 errno = serrno; 1110 return (-1); 1111 } 1112 } 1113 (void) fclose(ifp); 1114 1115 } else if (errno != ENOENT) { 1116 serrno = errno; 1117 (void) fclose(ofp); 1118 (void) unlink(tmp); 1119 free(tmp); 1120 errno = serrno; 1121 return (-1); 1122 } 1123 1124 /* 1125 * no $(ROOT)/etc/default/init found, or 1126 * no "TZ=" entry found in the init file. 1127 */ 1128 if (!replaced && 1129 (fprintf(ofp, tzfmt, tz) == EOF)) { 1130 serrno = errno; 1131 (void) fclose(ofp); 1132 (void) unlink(tmp); 1133 free(tmp); 1134 errno = serrno; 1135 return (-1); 1136 } 1137 1138 if (fsync(fileno(ofp))) { 1139 serrno = errno; 1140 (void) unlink(tmp); 1141 free(tmp); 1142 errno = serrno; 1143 return (-1); 1144 } 1145 1146 (void) fclose(ofp); 1147 if (rename(tmp, tdb) != 0) { 1148 serrno = errno; 1149 (void) unlink(tmp); 1150 free(tmp); 1151 errno = serrno; 1152 return (-1); 1153 } else { 1154 free(tmp); 1155 return (0); 1156 } 1157 } 1158 1159 /* 1160 * Function to traverse a symlink path to find the real file at the end of 1161 * the rainbow. 1162 */ 1163 int 1164 trav_link(char **path) 1165 { 1166 static char newpath[MAXPATHLEN]; 1167 char lastpath[MAXPATHLEN]; 1168 int len, ret; 1169 char *tp; 1170 1171 (void) strcpy(lastpath, *path); 1172 while ((len = readlink(*path, newpath, sizeof (newpath))) != -1) { 1173 newpath[len] = '\0'; 1174 if (newpath[0] != '/') { 1175 if ((tp = strdup(newpath)) == NULL) { 1176 errno = ENOMEM; 1177 return (-1); 1178 } 1179 remove_component(lastpath); 1180 ret = snprintf(newpath, sizeof (newpath), 1181 "%s/%s", lastpath, tp); 1182 free(tp); 1183 if ((ret >= sizeof (newpath)) || (ret < 0)) 1184 return (-1); 1185 } 1186 (void) strcpy(lastpath, newpath); 1187 *path = newpath; 1188 } 1189 1190 /* 1191 * ENOENT or EINVAL is the normal exit case of the above loop. 1192 */ 1193 if ((errno == ENOENT) || (errno == EINVAL)) 1194 return (0); 1195 else 1196 return (-1); 1197 } 1198 1199 void 1200 remove_component(char *path) 1201 { 1202 char *p; 1203 1204 p = strrchr(path, '/'); /* find last '/' */ 1205 if (p == NULL) { 1206 *path = '\0'; /* set path to null str */ 1207 } else { 1208 *p = '\0'; /* zap it */ 1209 } 1210 } 1211 1212 /* 1213 * get_coord() fills in the tz_coord structure of the tz_timezone 1214 * struct. It returns 0 on success, or -1 on error. 1215 * The format of p_coord is: 1216 * 1217 * Latitude and longitude of the zone's principal location 1218 * in ISO 6709 sign-degrees-minutes-seconds format, 1219 * either +-DDMM+-DDDMM or +-DDMMSS+-DDDMMSS, 1220 * first latitude (+ is north), then longitude (+ is east). 1221 */ 1222 static int 1223 get_coord(struct tz_timezone *tp, char *p_coord, size_t len_coord) 1224 { 1225 int i, fmt_flag, nchar; 1226 int *signp, *degp, *minp, *secp; 1227 struct tz_coord *tcp; 1228 char buff[512], *endp; 1229 1230 tcp = &(tp->tz_coord); 1231 1232 /* Figure out which format to use */ 1233 if (len_coord == COORD_FMTLEN1) { 1234 /* "+-DDMM+-DDDMM" */ 1235 fmt_flag = COORD_FMT1; 1236 } else if (len_coord == COORD_FMTLEN2) { 1237 /* "+-DDMMSS+-DDDMMSS" */ 1238 fmt_flag = COORD_FMT2; 1239 } else { 1240 /* error */ 1241 return (-1); 1242 } 1243 /* 1244 * First time through, get values for latitude; 1245 * second time through, get values for longitude. 1246 */ 1247 for (i = 0; i < 2; i++) { 1248 /* Set up pointers */ 1249 if (i == 0) { 1250 /* Do latitude */ 1251 nchar = COORD_DLEN_LAT; 1252 signp = (int *)&(tcp->lat_sign); 1253 degp = (int *)&(tcp->lat_degree); 1254 minp = (int *)&(tcp->lat_minute); 1255 secp = (int *)&(tcp->lat_second); 1256 } else { 1257 /* Do longitude */ 1258 nchar = COORD_DLEN_LONG; 1259 signp = (int *)&(tcp->long_sign); 1260 degp = (int *)&tcp->long_degree; 1261 minp = (int *)&tcp->long_minute; 1262 secp = (int *)&tcp->long_second; 1263 } 1264 /* Get latitude/logitude sign */ 1265 if (*p_coord == '+') { 1266 *signp = 1; 1267 } else if (*p_coord == '-') { 1268 *signp = -1; 1269 } else { 1270 return (-1); 1271 } 1272 p_coord++; 1273 1274 /* Get DD latitude, or DDD longitude */ 1275 (void) strncpy(buff, p_coord, nchar); 1276 buff[nchar] = '\0'; 1277 errno = 0; 1278 *degp = (int)strtol(buff, &endp, 10); 1279 if ((endp != &buff[nchar]) || ((*degp == 0) && (errno != 0))) 1280 return (-1); 1281 p_coord += nchar; 1282 1283 /* Get MM latitude/longitude */ 1284 (void) strncpy(buff, p_coord, COORD_MLEN); 1285 buff[COORD_MLEN] = '\0'; 1286 errno = 0; 1287 *minp = (int)strtol(buff, &endp, 10); 1288 if ((endp != &buff[COORD_MLEN]) || 1289 ((*degp == 0) && (errno != 0))) 1290 return (-1); 1291 p_coord += COORD_MLEN; 1292 1293 /* If FMT2, then get SS latitude/longitude */ 1294 if (fmt_flag == COORD_FMT2) { 1295 (void) strncpy(buff, p_coord, COORD_SLEN); 1296 buff[COORD_SLEN] = '\0'; 1297 errno = 0; 1298 *secp = (int)strtol(buff, &endp, 10); 1299 if ((endp != &buff[COORD_SLEN]) || 1300 ((*degp == 0) && (errno != 0))) 1301 return (-1); 1302 p_coord += COORD_SLEN; 1303 } else { 1304 *secp = 0; 1305 } 1306 } 1307 return (0); 1308 } 1309 1310 static char * 1311 skipwhite(char *cp) 1312 { 1313 while (*cp && ((*cp == ' ') || (*cp == '\t'))) { 1314 cp++; 1315 } 1316 1317 return (cp); 1318 } 1319 1320 /* 1321 * skipline() checks if the line begins with a comment 1322 * comment character anywhere in the line, or if the 1323 * line is only whitespace. 1324 * skipline() also checks if the line read is too long to 1325 * fit in the buffer. 1326 * skipline() returns 1 if the line can be skipped, -1 if 1327 * the line read is too long, and 0 if the line should not be skipped. 1328 */ 1329 static int 1330 skipline(char *line) 1331 { 1332 size_t len; 1333 1334 len = strlen(line); 1335 if (line[len - 1] != '\n') 1336 return (-1); 1337 if (line[0] == '#' || line[0] == '\0' || 1338 (len = strspn(line, " \t\n")) == strlen(line) || 1339 strchr(line, '#') == line + len) 1340 1341 return (1); 1342 else 1343 return (0); 1344 } 1345 1346 /* 1347 * strip_quotes -- strip double (") or single (') quotes 1348 */ 1349 static void 1350 strip_quotes(char *from, char *to) 1351 { 1352 char *strip_ptr = NULL; 1353 1354 while (*from != '\0') { 1355 if ((*from == '"') || (*from == '\'')) { 1356 if (strip_ptr == NULL) 1357 strip_ptr = to; 1358 } else { 1359 if (strip_ptr != NULL) { 1360 *strip_ptr = *from; 1361 strip_ptr++; 1362 } else { 1363 *to = *from; 1364 to++; 1365 } 1366 } 1367 from++; 1368 } 1369 if (strip_ptr != NULL) { 1370 *strip_ptr = '\0'; 1371 } else { 1372 *to = '\0'; 1373 } 1374 } 1375 1376 /* 1377 * Compare function used by get_tz_countries() - uses strcoll() 1378 * for locale-sensitive comparison for the localized country names. 1379 */ 1380 static int 1381 compar(struct tz_country *p1, struct tz_country *p2) 1382 { 1383 int ret; 1384 1385 ret = strcoll(p1->ctry_display_desc, p2->ctry_display_desc); 1386 return (ret); 1387 }