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
get_tz_continents(struct tz_continent ** cont)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
get_tz_countries(struct tz_country ** country,struct tz_continent * cont)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
get_timezones_by_country(struct tz_timezone ** tmzone,struct tz_country * country)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
free_tz_continents(struct tz_continent * cont)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
free_tz_countries(struct tz_country * country)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
free_timezones(struct tz_timezone * timezone)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 *
conv_gmt(int seconds,int flag)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 *
_conv_gmt_posix(int hour)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 *
_conv_gmt_zoneinfo(int hour)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
isvalid_tz(char * timezone,char * root,int flag)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
_tz_match(const char * expr,const char * string)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 *
get_system_tz(char * root)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
set_system_tz(char * tz,char * root)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
trav_link(char ** path)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
remove_component(char * path)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
get_coord(struct tz_timezone * tp,char * p_coord,size_t len_coord)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 *
skipwhite(char * cp)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
skipline(char * line)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
strip_quotes(char * from,char * to)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
compar(struct tz_country * p1,struct tz_country * p2)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 }
1388