lcFile.c revision 3b4ba46c
1/*
2 *
3 * Copyright IBM Corporation 1993
4 *
5 * All Rights Reserved
6 *
7 * License to use, copy, modify, and distribute this software and its
8 * documentation for any purpose and without fee is hereby granted,
9 * provided that the above copyright notice appear in all copies and that
10 * both that copyright notice and this permission notice appear in
11 * supporting documentation, and that the name of IBM not be
12 * used in advertising or publicity pertaining to distribution of the
13 * software without specific, written prior permission.
14 *
15 * IBM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
16 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS, AND
17 * NONINFRINGEMENT OF THIRD PARTY RIGHTS, IN NO EVENT SHALL
18 * IBM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
19 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
20 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
21 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
22 * SOFTWARE.
23 *
24*/
25
26#ifdef HAVE_CONFIG_H
27#include <config.h>
28#endif
29#include <stdlib.h>
30#include <stdio.h>
31#include <ctype.h>
32#include "Xlibint.h"
33#include "XlcPubI.h"
34#include <X11/Xos.h>
35#include <unistd.h>
36
37/************************************************************************/
38
39#ifndef HAVE_SETEUID
40# define seteuid setuid
41#endif
42#define	iscomment(ch)	((ch) == '#' || (ch) == '\0')
43#if defined(WIN32)
44#define isreadable(f)	(_XAccessFile(f))
45#else
46#define isreadable(f)	((access((f), R_OK) != -1) ? 1 : 0)
47#endif
48
49#define LC_PATHDELIM ':'
50
51#define XLC_BUFSIZE 256
52
53#include "pathmax.h"
54
55#define NUM_LOCALEDIR	64
56
57/* Splits a NUL terminated line into constituents, at colons and newline
58   characters. Leading whitespace is removed from constituents. The
59   constituents are stored at argv[0..argsize-1]. The number of stored
60   constituents (<= argsize) is returned. The line is destructively
61   modified. */
62static int
63parse_line(
64    char *line,
65    char **argv,
66    int argsize)
67{
68    int argc = 0;
69    char *p = line;
70
71    while (argc < argsize) {
72	while (isspace(*p)) {
73	    ++p;
74	}
75	if (*p == '\0') {
76	    break;
77	}
78	argv[argc++] = p;
79	while (*p != ':' && *p != '\n' && *p != '\0') {
80	    ++p;
81	}
82	if (*p == '\0') {
83	    break;
84	}
85	*p++ = '\0';
86    }
87
88    return argc;
89}
90
91#ifdef WIN32
92/* this is parse_line but skips drive letters at the beginning of the entry */
93static int
94parse_line1(
95    char *line,
96    char **argv,
97    int argsize)
98{
99    int argc = 0;
100    char *p = line;
101
102    while (argc < argsize) {
103	while (isspace(*p)) {
104	    ++p;
105	}
106	if (*p == '\0') {
107	    break;
108	}
109	argv[argc++] = p;
110        if (isalpha(*p) && p[1] == ':') {
111            p+= 2; /* skip drive letters */
112        }
113	while (*p != ':' && *p != '\n' && *p != '\0') {
114	    ++p;
115	}
116	if (*p == '\0') {
117	    break;
118	}
119	*p++ = '\0';
120    }
121
122    return argc;
123}
124#endif   /* WIN32 */
125
126/* Splits a colon separated list of directories, and returns the constituent
127   paths (without trailing slash). At most argsize constituents are stored
128   at argv[0..argsize-1]. The number of stored constituents is returned. */
129static int
130_XlcParsePath(
131    char *path,
132    char **argv,
133    int argsize)
134{
135    char *p = path;
136    int n, i;
137
138#ifndef WIN32
139    n = parse_line(path, argv, argsize);
140#else
141    n = parse_line1(path, argv, argsize);
142#endif
143    for (i = 0; i < n; ++i) {
144	int len;
145	p = argv[i];
146	len = (int) strlen(p);
147	if (len > 0 && p[len - 1] == '/') {
148	    /* eliminate trailing slash */
149	    p[len - 1] = '\0';
150	}
151    }
152    return n;
153}
154
155#ifndef XLOCALEDIR
156#define XLOCALEDIR "/usr/lib/X11/locale"
157#endif
158
159void
160xlocaledir(
161    char *buf,
162    int buf_len)
163{
164    char *p = buf;
165    int len = 0;
166
167#ifndef NO_XLOCALEDIR
168    char *dir;
169    int priv = 1;
170
171    dir = getenv("XLOCALEDIR");
172
173    if (dir) {
174#ifndef WIN32
175	/*
176	 * Only use the user-supplied path if the process isn't privileged.
177	 */
178	if (getuid() == geteuid() && getgid() == getegid()) {
179#if defined(HASSETUGID)
180	    priv = issetugid();
181#elif defined(HASGETRESUID)
182	    {
183		uid_t ruid, euid, suid;
184		gid_t rgid, egid, sgid;
185		if ((getresuid(&ruid, &euid, &suid) == 0) &&
186		    (getresgid(&rgid, &egid, &sgid) == 0))
187		    priv = (euid != suid) || (egid != sgid);
188	    }
189#else
190	    /*
191	     * If there are saved ID's the process might still be privileged
192	     * even though the above test succeeded.  If issetugid() and
193	     * getresgid() aren't available, test this by trying to set
194	     * euid to 0.
195	     *
196	     * Note: this only protects setuid-root clients.  It doesn't
197	     * protect other setuid or any setgid clients.  If this tradeoff
198	     * isn't acceptable, run configure with --disable-xlocaledir .
199	     */
200	    unsigned int oldeuid;
201	    oldeuid = geteuid();
202	    if (seteuid(0) != 0) {
203		priv = 0;
204	    } else {
205		if (seteuid(oldeuid) == -1) {
206		    /* XXX ouch, couldn't get back to original uid
207		     what can we do ??? */
208		    _exit(127);
209		}
210		priv = 1;
211	    }
212#endif
213	}
214#else
215	priv = 0;
216#endif
217	if (!priv) {
218	    len = (int) strlen(dir);
219	    strncpy(p, dir, (size_t) buf_len);
220	    if (len < buf_len) {
221	        p[len++] = LC_PATHDELIM;
222	        p += len;
223	    }
224	}
225    }
226#endif /* NO_XLOCALEDIR */
227
228    if (len < buf_len)
229      strncpy(p, XLOCALEDIR, (size_t) (buf_len - len));
230    buf[buf_len-1] = '\0';
231}
232
233static void
234xlocalelibdir(
235    char *buf,
236    int buf_len)
237{
238    char *p = buf;
239    int len = 0;
240
241#ifndef NO_XLOCALEDIR
242    char *dir;
243    int priv = 1;
244
245    dir = getenv("XLOCALELIBDIR");
246
247    if (dir) {
248#ifndef WIN32
249	/*
250	 * Only use the user-supplied path if the process isn't privileged.
251	 */
252	if (getuid() == geteuid() && getgid() == getegid()) {
253#if defined(HASSETUGID)
254	    priv = issetugid();
255#elif defined(HASGETRESUID)
256	    {
257		uid_t ruid, euid, suid;
258		gid_t rgid, egid, sgid;
259		if ((getresuid(&ruid, &euid, &suid) == 0) &&
260		    (getresgid(&rgid, &egid, &sgid) == 0))
261		    priv = (euid != suid) || (egid != sgid);
262	    }
263#else
264	    /*
265	     * If there are saved ID's the process might still be privileged
266	     * even though the above test succeeded.  If issetugid() and
267	     * getresgid() aren't available, test this by trying to set
268	     * euid to 0.
269	     *
270	     * Note: this only protects setuid-root clients.  It doesn't
271	     * protect other setuid or any setgid clients.  If this tradeoff
272	     * isn't acceptable, run configure with --disable-xlocaledir .
273	     */
274	    unsigned int oldeuid;
275	    oldeuid = geteuid();
276	    if (seteuid(0) != 0) {
277		priv = 0;
278	    } else {
279		if (seteuid(oldeuid) == -1) {
280		    /* XXX ouch, couldn't get back to original uid
281		     what can we do ??? */
282		    _exit(127);
283		}
284		priv = 1;
285	    }
286#endif
287	}
288#else
289	priv = 0;
290#endif
291	if (!priv) {
292	    len = (int) strlen(dir);
293	    strncpy(p, dir, (size_t) buf_len);
294	    if (len < buf_len) {
295	        p[len++] = LC_PATHDELIM;
296	        p += len;
297	    }
298	}
299    }
300#endif /* NO_XLOCALEDIR */
301
302    if (len < buf_len)
303      strncpy(p, XLOCALELIBDIR, (size_t) (buf_len - len));
304    buf[buf_len-1] = '\0';
305}
306
307/* Mapping direction */
308typedef enum {
309  LtoR,		/* Map first field to second field */
310  RtoL		/* Map second field to first field */
311} MapDirection;
312
313static char *
314resolve_name(
315    const char *lc_name,
316    char *file_name,
317    MapDirection direction)
318{
319    FILE *fp;
320    char buf[XLC_BUFSIZE], *name = NULL;
321
322    fp = _XFopenFile (file_name, "r");
323    if (fp == NULL)
324	return NULL;
325
326    while (fgets(buf, XLC_BUFSIZE, fp) != NULL) {
327	char *p = buf;
328	int n;
329	char *args[2], *from, *to;
330	while (isspace(*p)) {
331	    ++p;
332	}
333	if (iscomment(*p)) {
334	    continue;
335	}
336	n = parse_line(p, args, 2);		/* get first 2 fields */
337	if (n != 2) {
338	    continue;
339	}
340	if (direction == LtoR) {
341	    from = args[0], to = args[1];	/* left to right */
342	} else {
343	    from = args[1], to = args[0];	/* right to left */
344	}
345	if (! strcmp(from, lc_name)) {
346	    name = strdup(to);
347	    break;
348	}
349    }
350    fclose(fp);
351    return name;
352}
353
354#define	c_tolower(ch)	((ch) >= 'A' && (ch) <= 'Z' ? (ch) - 'A' + 'a' : (ch))
355
356static char *
357lowercase(
358    char *dst,
359    const char *src)
360{
361    const char *s;
362    char *t;
363
364    for (s = src, t = dst; *s; ++s, ++t)
365	*t = (char) c_tolower(*s);
366    *t = '\0';
367    return dst;
368}
369
370/*
371 * normalize_lcname(): remove any '_' and '-' and convert any character
372 * to lower case after the <language>_<territory> part. If result is identical
373 * to argument, free result and
374 * return NULL.
375 */
376static char *
377normalize_lcname (const char *name)
378{
379    char *p, *ret;
380    const char *tmp = name;
381
382    p = ret = Xmalloc(strlen(name) + 1);
383    if (!p)
384	return NULL;
385
386    if (tmp) {
387	while (*tmp && *tmp != '.' && *tmp != '@')
388	    *p++ = *tmp++;
389	while (*tmp) {
390	    if (*tmp != '-')
391		*p++ = (char) c_tolower(*tmp);
392	    tmp++;
393	}
394    }
395    *p = '\0';
396
397    if (strcmp(ret, name) == 0) {
398	Xfree(ret);
399	return NULL;
400    }
401
402    return ret;
403}
404
405/************************************************************************/
406char *
407_XlcFileName(
408    XLCd lcd,
409    const char *category)
410{
411    char *siname;
412    char cat[XLC_BUFSIZE], dir[XLC_BUFSIZE];
413    int i, n;
414    char *args[NUM_LOCALEDIR];
415    char *file_name = NULL;
416
417    if (lcd == (XLCd)NULL)
418	return NULL;
419
420    siname = XLC_PUBLIC(lcd, siname);
421
422    if (category)
423	lowercase(cat, category);
424    else
425	cat[0] = '\0';
426    xlocaledir(dir,XLC_BUFSIZE);
427    n = _XlcParsePath(dir, args, NUM_LOCALEDIR);
428    for (i = 0; i < n; ++i) {
429	char buf[PATH_MAX], *name;
430
431	if (args[i] == NULL)
432	    continue;
433
434	name = NULL;
435	if (snprintf(buf, PATH_MAX, "%s/%s.dir", args[i], cat) < PATH_MAX) {
436	    name = resolve_name(siname, buf, RtoL);
437	}
438	if (name == NULL) {
439	    continue;
440	}
441	if (*name == '/') {
442	    /* supposed to be absolute path name */
443	    file_name = name;
444	} else {
445	    if (snprintf(buf, PATH_MAX, "%s/%s", args[i], name) < PATH_MAX)
446		file_name = strdup(buf);
447	    else
448		file_name = NULL;
449	    Xfree(name);
450	}
451	if (file_name && isreadable(file_name)) {
452	    break;
453	}
454	Xfree(file_name);
455	file_name = NULL;
456	/* Then, try with next dir */
457    }
458    return file_name;
459}
460
461/************************************************************************/
462#ifndef LOCALE_ALIAS
463#define LOCALE_ALIAS    "locale.alias"
464#endif
465
466int
467_XlcResolveLocaleName(
468    const char* lc_name,
469    XLCdPublicPart* pub)
470{
471    char dir[PATH_MAX], buf[PATH_MAX], *name = NULL;
472    char *dst;
473    int i, n, sinamelen;
474    char *args[NUM_LOCALEDIR];
475    static const char locale_alias[] = LOCALE_ALIAS;
476    char *tmp_siname;
477    char *nlc_name = NULL;
478
479    xlocaledir (dir, PATH_MAX);
480    n = _XlcParsePath(dir, args, NUM_LOCALEDIR);
481    for (i = 0; i < n; ++i) {
482	if (args[i] == NULL)
483	    continue;
484
485	if (snprintf (buf, PATH_MAX, "%s/%s", args[i], locale_alias)
486	    < PATH_MAX) {
487	    name = resolve_name (lc_name, buf, LtoR);
488	    if (!name) {
489		if (!nlc_name)
490		    nlc_name = normalize_lcname(lc_name);
491		if (nlc_name)
492		    name = resolve_name (nlc_name, buf, LtoR);
493	    }
494	}
495	if (name != NULL) {
496	    break;
497	}
498    }
499    Xfree(nlc_name);
500
501    if (name == NULL) {
502	/* vendor locale name == Xlocale name, no expansion of alias */
503	pub->siname = strdup (lc_name);
504    } else {
505	pub->siname = name;
506    }
507
508    sinamelen = (int) strlen (pub->siname);
509    if (sinamelen == 1 && pub->siname[0] == 'C') {
510	pub->language = pub->siname;
511	pub->territory = pub->codeset = NULL;
512	return 1;
513    }
514
515    /*
516     * pub->siname is in the format <lang>_<terr>.<codeset>, typical would
517     * be "en_US.ISO8859-1", "en_US.utf8", "ru_RU.KOI-8", or ja_JP.SJIS,
518     * although it could be ja.SJIS too.
519     */
520    tmp_siname = Xrealloc (pub->siname, 2 * (sinamelen + 1));
521    if (tmp_siname == NULL) {
522	return 0;
523    }
524    pub->siname = tmp_siname;
525
526    /* language */
527    dst = &pub->siname[sinamelen + 1];
528    strcpy (dst, pub->siname);
529    pub->language = dst;
530
531    /* territory */
532    dst = strchr (dst, '_');
533    if (dst) {
534	*dst = '\0';
535	pub->territory = ++dst;
536    } else
537	dst = &pub->siname[sinamelen + 1];
538
539    /* codeset */
540    dst = strchr (dst, '.');
541    if (dst) {
542	*dst = '\0';
543	pub->codeset = ++dst;
544    }
545
546    return (pub->siname[0] != '\0') ? 1 : 0;
547}
548
549/************************************************************************/
550int
551_XlcResolveI18NPath(char *buf, int buf_len)
552{
553    if (buf != NULL) {
554	xlocaledir(buf, buf_len);
555    }
556    return 1;
557}
558
559char *
560_XlcLocaleDirName(char *dir_name, size_t dir_len, const char *lc_name)
561{
562    char dir[PATH_MAX], buf[PATH_MAX];
563    int i, n;
564    char *args[NUM_LOCALEDIR];
565    static char locale_alias[] = LOCALE_ALIAS;
566    char *target_name = NULL;
567    char *target_dir = NULL;
568    char *nlc_name = NULL;
569    static char*  last_dir_name = 0;
570    static size_t last_dir_len = 0;
571    static char*  last_lc_name = 0;
572
573    if (last_lc_name != 0 && strcmp (last_lc_name, lc_name) == 0
574       && dir_len >= last_dir_len) {
575        strcpy (dir_name, last_dir_name);
576        return dir_name;
577    }
578
579    xlocaledir (dir, PATH_MAX);
580    n = _XlcParsePath(dir, args, NUM_LOCALEDIR);
581    for (i = 0; i < n; ++i) {
582	char *name = NULL;
583
584	if (args[i] == NULL)
585	    continue;
586
587	if (snprintf (buf, PATH_MAX, "%s/%s", args[i], locale_alias)
588	    < PATH_MAX) {
589 	    name = resolve_name(lc_name, buf, LtoR);
590	    if (!name) {
591		if (!nlc_name)
592		    nlc_name = normalize_lcname(lc_name);
593		if (nlc_name)
594		    name = resolve_name (nlc_name, buf, LtoR);
595	    }
596 	}
597
598 	/* look at locale.dir */
599
600 	target_dir = args[i];
601	if (snprintf(buf, PATH_MAX, "%s/locale.dir", target_dir) < PATH_MAX) {
602	    /* If name is not an alias, use lc_name for locale.dir search */
603	    target_name = resolve_name(name ? name : lc_name, buf, RtoL);
604 	}
605	Xfree(name);
606	name = NULL;
607 	if (target_name != NULL) {
608 	    char *p = 0;
609 	    if ((p = strstr(target_name, "/XLC_LOCALE"))) {
610 		*p = '\0';
611 		break;
612 	    }
613 	    Xfree(target_name);
614 	    target_name = NULL;
615 	}
616    }
617    Xfree(nlc_name);
618
619    if (target_name == NULL)
620 	/* vendor locale name == Xlocale name, no expansion of alias */
621	snprintf(dir_name, dir_len, "%s/%s", args[0], lc_name);
622    else
623	snprintf(dir_name, dir_len, "%s/%s", target_dir, target_name);
624
625    Xfree(target_name);
626    Xfree (last_dir_name);
627    Xfree (last_lc_name);
628
629    last_dir_name = strdup (dir_name);
630    last_dir_len = (last_dir_name != NULL) ? strlen (last_dir_name) + 1 : 0;
631
632    last_lc_name = strdup (lc_name);
633
634    return dir_name;
635}
636
637char *
638_XlcLocaleLibDirName(char *dir_name, size_t dir_len, const char *lc_name)
639{
640    char dir[PATH_MAX], buf[PATH_MAX];
641    int i, n;
642    char *args[NUM_LOCALEDIR];
643    static char locale_alias[] = LOCALE_ALIAS;
644    char *target_name = NULL;
645    char *target_dir = NULL;
646    char *nlc_name = NULL;
647    static char*  last_dir_name = 0;
648    static size_t last_dir_len = 0;
649    static char*  last_lc_name = 0;
650
651    if (last_lc_name != 0 && strcmp (last_lc_name, lc_name) == 0
652       && dir_len >= last_dir_len) {
653	strcpy (dir_name, last_dir_name);
654	return dir_name;
655    }
656
657    xlocalelibdir (dir, PATH_MAX);
658    n = _XlcParsePath(dir, args, NUM_LOCALEDIR);
659    for (i = 0; i < n; ++i) {
660	char *name = NULL;
661
662	if (args[i] == NULL)
663	    continue;
664
665	if (snprintf (buf, PATH_MAX, "%s/%s", args[i], locale_alias)
666	    < PATH_MAX) {
667 	    name = resolve_name(lc_name, buf, LtoR);
668	    if (!name) {
669		if (!nlc_name)
670		    nlc_name = normalize_lcname(lc_name);
671		if (nlc_name)
672		    name = resolve_name (nlc_name, buf, LtoR);
673	    }
674 	}
675
676 	/* look at locale.dir */
677
678 	target_dir = args[i];
679	if (snprintf(buf, PATH_MAX, "%s/locale.dir", target_dir) < PATH_MAX) {
680	    /* If name is not an alias, use lc_name for locale.dir search */
681	    target_name = resolve_name(name ? name : lc_name, buf, RtoL);
682 	}
683	Xfree(name);
684	name = NULL;
685 	if (target_name != NULL) {
686 	    char *p = 0;
687 	    if ((p = strstr(target_name, "/XLC_LOCALE"))) {
688 		*p = '\0';
689 		break;
690 	    }
691 	    Xfree(target_name);
692 	    target_name = NULL;
693 	}
694    }
695    Xfree(nlc_name);
696
697    if (target_name == NULL)
698 	/* vendor locale name == Xlocale name, no expansion of alias */
699	snprintf(dir_name, dir_len, "%s/%s", args[0], lc_name);
700    else
701	snprintf(dir_name, dir_len, "%s/%s", target_dir, target_name);
702    Xfree(target_name);
703    Xfree (last_dir_name);
704    Xfree (last_lc_name);
705
706    last_dir_name = strdup (dir_name);
707    last_dir_len = (last_dir_name != NULL) ? strlen (last_dir_name) + 1 : 0;
708
709    last_lc_name = strdup (lc_name);
710
711    return dir_name;
712}
713