1/*	$OpenBSD: catopen.c,v 1.21 2017/04/27 23:54:08 millert Exp $ */
2/*-
3 * Copyright (c) 1996 The NetBSD Foundation, Inc.
4 * All rights reserved.
5 *
6 * This code is derived from software contributed to The NetBSD Foundation
7 * by J.T. Conklin.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#define _NLS_PRIVATE
32
33#include <sys/types.h>
34#include <sys/stat.h>
35#include <sys/mman.h>
36#include <errno.h>
37#include <fcntl.h>
38#include <limits.h>
39#include <nl_types.h>
40#include <stdlib.h>
41#include <string.h>
42#include <unistd.h>
43
44#define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))
45
46#define NLS_DEFAULT_LANG "C"
47
48static nl_catd	load_msgcat(const char *);
49static int	verify_msgcat(nl_catd);
50
51nl_catd
52catopen(const char *name, int oflag)
53{
54	char tmppath[PATH_MAX];
55	char *nlspath;
56	char *lang;
57	char *s, *t, *sep, *dot;
58	const char *u;
59	nl_catd catd;
60
61	if (name == NULL || *name == '\0')
62		return (nl_catd) -1;
63
64	/* absolute or relative path? */
65	if (strchr(name, '/'))
66		return load_msgcat(name);
67
68	if (issetugid() != 0 || (nlspath = getenv("NLSPATH")) == NULL)
69		return (nl_catd) -1;
70
71	lang = NULL;
72	if (oflag & NL_CAT_LOCALE) {
73		lang = getenv("LC_ALL");
74		if (lang == NULL)
75			lang = getenv("LC_MESSAGES");
76	}
77	if (lang == NULL)
78		lang = getenv("LANG");
79	if (lang == NULL)
80		lang = NLS_DEFAULT_LANG;
81	if (strcmp(lang, "POSIX") == 0)
82		lang = NLS_DEFAULT_LANG;
83
84	s = nlspath;
85	t = tmppath;
86
87	/*
88	 * Locale names are of the form language[_territory][.codeset].
89	 * See POSIX-1-2008 "8.2 Internationalization Variables"
90	 */
91	sep = strchr(lang, '_');
92	dot = strrchr(lang, '.');
93	if (dot && sep && dot < sep)
94		dot = NULL; /* ignore dots preceeding _ */
95	if (dot == NULL)
96		lang = NLS_DEFAULT_LANG; /* no codeset specified */
97	do {
98		while (*s && *s != ':') {
99			if (*s == '%') {
100				switch (*(++s)) {
101				case 'L':	/* LANG or LC_MESSAGES */
102					u = lang;
103					while (*u && t < tmppath + PATH_MAX-1)
104						*t++ = *u++;
105					break;
106				case 'N':	/* value of name parameter */
107					u = name;
108					while (*u && t < tmppath + PATH_MAX-1)
109						*t++ = *u++;
110					break;
111				case 'l':	/* language part */
112					u = lang;
113					while (*u && t < tmppath + PATH_MAX-1) {
114						*t++ = *u++;
115						if (sep && u >= sep)
116							break;
117						if (dot && u >= dot)
118							break;
119					}
120					break;
121				case 't':	/* territory part */
122					if (sep == NULL)
123						break;
124					u = sep + 1;
125					while (*u && t < tmppath + PATH_MAX-1) {
126						*t++ = *u++;
127						if (dot && u >= dot)
128							break;
129					}
130					break;
131				case 'c':	/* codeset part */
132					if (dot == NULL)
133						break;
134					u = dot + 1;
135					while (*u && t < tmppath + PATH_MAX-1)
136						*t++ = *u++;
137					break;
138				default:
139					if (t < tmppath + PATH_MAX-1)
140						*t++ = *s;
141				}
142			} else {
143				if (t < tmppath + PATH_MAX-1)
144					*t++ = *s;
145			}
146			s++;
147		}
148
149		*t = '\0';
150		catd = load_msgcat(tmppath);
151		if (catd != (nl_catd) -1)
152			return catd;
153
154		if (*s)
155			s++;
156		t = tmppath;
157	} while (*s);
158
159	return (nl_catd) -1;
160}
161DEF_WEAK(catopen);
162
163static nl_catd
164load_msgcat(const char *path)
165{
166	struct stat st;
167	nl_catd catd;
168	void *data;
169	int fd;
170
171	catd = NULL;
172
173	if ((fd = open(path, O_RDONLY|O_CLOEXEC)) == -1)
174		return (nl_catd) -1;
175
176	if (fstat(fd, &st) != 0) {
177		close (fd);
178		return (nl_catd) -1;
179	}
180
181	if (st.st_size > INT_MAX || st.st_size < sizeof (struct _nls_cat_hdr)) {
182		errno = EINVAL;
183		close (fd);
184		return (nl_catd) -1;
185	}
186
187	data = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
188	close (fd);
189
190	if (data == MAP_FAILED)
191		return (nl_catd) -1;
192
193	if (ntohl(((struct _nls_cat_hdr *) data)->__magic) != _NLS_MAGIC)
194		goto invalid;
195
196	if ((catd = malloc(sizeof (*catd))) == 0)
197		goto invalid;
198
199	catd->__data = data;
200	catd->__size = st.st_size;
201
202	if (verify_msgcat(catd))
203		goto invalid;
204
205	return catd;
206
207invalid:
208	free(catd);
209	munmap(data, st.st_size);
210	errno = EINVAL;
211	return (nl_catd) -1;
212}
213
214static int
215verify_msgcat(nl_catd catd)
216{
217	struct _nls_cat_hdr *cat;
218	struct _nls_set_hdr *set;
219	struct _nls_msg_hdr *msg;
220	size_t remain;
221	int hdr_offset, i, index, j, msgs, nmsgs, nsets, off, txt_offset;
222
223	remain = catd->__size;
224	cat = (struct _nls_cat_hdr *) catd->__data;
225
226	hdr_offset = ntohl(cat->__msg_hdr_offset);
227	nsets = ntohl(cat->__nsets);
228	txt_offset = ntohl(cat->__msg_txt_offset);
229
230	/* catalog must contain at least one set and no negative offsets */
231	if (nsets < 1 || hdr_offset < 0 || txt_offset < 0)
232		return (1);
233
234	remain -= sizeof (*cat);
235
236	/* check if offsets or set size overflow */
237	if (remain <= hdr_offset || remain <= ntohl(cat->__msg_txt_offset) ||
238	    remain / sizeof (*set) < nsets)
239		return (1);
240
241	set = (struct _nls_set_hdr *) ((char *) catd->__data + sizeof (*cat));
242
243	/* make sure that msg has space for at least one index */
244	if (remain - hdr_offset < sizeof(*msg))
245		return (1);
246
247	msg = (struct _nls_msg_hdr *) ((char *) catd->__data + sizeof (*cat)
248	    + hdr_offset);
249
250	/* validate and retrieve largest string offset from sets */
251	off = 0;
252	for (i = 0; i < nsets; i++) {
253		index = ntohl(set[i].__index);
254		nmsgs = ntohl(set[i].__nmsgs);
255		/* set must contain at least one message */
256		if (index < 0 || nmsgs < 1)
257			return (1);
258
259		if (INT_MAX - nmsgs < index)
260			return (1);
261		msgs = index + nmsgs;
262
263		/* avoid msg index overflow */
264		if ((remain - hdr_offset) / sizeof(*msg) < msgs)
265			return (1);
266
267		/* retrieve largest string offset */
268		for (j = index; j < nmsgs; j++) {
269			if (ntohl(msg[j].__offset) > INT_MAX)
270				return (1);
271			off = MAXIMUM(off, ntohl(msg[j].__offset));
272		}
273	}
274
275	/* check if largest string offset is nul-terminated */
276	if (remain - txt_offset < off ||
277	    memchr((char *) catd->__data + sizeof(*cat) + txt_offset + off,
278	    '\0', remain - txt_offset - off) == NULL)
279		return (1);
280
281	return (0);
282}
283
284