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