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 */
82static char *skipwhite(char *);
83static int skipline(char *);
84static int trav_link(char **);
85static void remove_component(char *);
86static void strip_quotes(char *, char *);
87static int compar(struct tz_country *, struct tz_country *);
88static int get_coord(struct tz_timezone *, char *, size_t);
89static int _tz_match(const char *, const char *);
90static char *_conv_gmt_zoneinfo(int);
91static char *_conv_gmt_posix(int);
92
93/*
94 * get_tz_continents() reads the continent.tab file, and
95 * returns a list of continents.
96 */
97int
98get_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 */
203int
204get_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
428error:
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 */
447int
448get_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
642int
643free_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
660int
661free_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
678int
679free_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 */
701char *
702conv_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
724static char *
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
753static char *
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 */
825int
826isvalid_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
915int
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(&reg, expr, REG_EXTENDED);
923	if (ret != 0) {
924		return (-1);
925	}
926
927	ret = regexec((const regex_t *)&reg, string, N_MATCH, pmatch, 0);
928	if (ret == 0) {
929#ifdef DEBUG
930		printf("OK matched - %s\n", string);
931#endif
932		regfree(&reg);
933		return (0);
934	}
935#ifdef DEBUG
936	printf("NOT matched - %s\n", string);
937#endif
938	regfree(&reg);
939	return (-1);
940}
941
942char *
943get_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
992int
993set_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 */
1165int
1166trav_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
1201void
1202remove_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 */
1224static int
1225get_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
1312static char *
1313skipwhite(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 */
1331static int
1332skipline(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 */
1351static void
1352strip_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 */
1382static int
1383compar(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