1*7c478bd9Sstevel@tonic-gate /*
2*7c478bd9Sstevel@tonic-gate  * CDDL HEADER START
3*7c478bd9Sstevel@tonic-gate  *
4*7c478bd9Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*7c478bd9Sstevel@tonic-gate  * Common Development and Distribution License, Version 1.0 only
6*7c478bd9Sstevel@tonic-gate  * (the "License").  You may not use this file except in compliance
7*7c478bd9Sstevel@tonic-gate  * with the License.
8*7c478bd9Sstevel@tonic-gate  *
9*7c478bd9Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10*7c478bd9Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
11*7c478bd9Sstevel@tonic-gate  * See the License for the specific language governing permissions
12*7c478bd9Sstevel@tonic-gate  * and limitations under the License.
13*7c478bd9Sstevel@tonic-gate  *
14*7c478bd9Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
15*7c478bd9Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16*7c478bd9Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
17*7c478bd9Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
18*7c478bd9Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
19*7c478bd9Sstevel@tonic-gate  *
20*7c478bd9Sstevel@tonic-gate  * CDDL HEADER END
21*7c478bd9Sstevel@tonic-gate  */
22*7c478bd9Sstevel@tonic-gate /*
23*7c478bd9Sstevel@tonic-gate  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24*7c478bd9Sstevel@tonic-gate  * Use is subject to license terms.
25*7c478bd9Sstevel@tonic-gate  */
26*7c478bd9Sstevel@tonic-gate 
27*7c478bd9Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
28*7c478bd9Sstevel@tonic-gate 
29*7c478bd9Sstevel@tonic-gate #include "synonyms.h"
30*7c478bd9Sstevel@tonic-gate #include "mtlib.h"
31*7c478bd9Sstevel@tonic-gate #include <string.h>
32*7c478bd9Sstevel@tonic-gate #include <syslog.h>
33*7c478bd9Sstevel@tonic-gate #include <sys/stat.h>
34*7c478bd9Sstevel@tonic-gate #include <fcntl.h>
35*7c478bd9Sstevel@tonic-gate #include <limits.h>
36*7c478bd9Sstevel@tonic-gate #include <unistd.h>
37*7c478bd9Sstevel@tonic-gate #include <stdlib.h>
38*7c478bd9Sstevel@tonic-gate #include <thread.h>
39*7c478bd9Sstevel@tonic-gate #include <synch.h>
40*7c478bd9Sstevel@tonic-gate #include <ctype.h>
41*7c478bd9Sstevel@tonic-gate #include <errno.h>
42*7c478bd9Sstevel@tonic-gate #include "libc.h"
43*7c478bd9Sstevel@tonic-gate #include "nlspath_checks.h"
44*7c478bd9Sstevel@tonic-gate 
45*7c478bd9Sstevel@tonic-gate extern const char **environ;
46*7c478bd9Sstevel@tonic-gate 
47*7c478bd9Sstevel@tonic-gate /*
48*7c478bd9Sstevel@tonic-gate  * We want to prevent the use of NLSPATH by setugid applications but
49*7c478bd9Sstevel@tonic-gate  * not completely.  CDE depends on this very much.
50*7c478bd9Sstevel@tonic-gate  * Yes, this is ugly.
51*7c478bd9Sstevel@tonic-gate  */
52*7c478bd9Sstevel@tonic-gate 
53*7c478bd9Sstevel@tonic-gate struct trusted_systemdirs {
54*7c478bd9Sstevel@tonic-gate 	const char	*dir;
55*7c478bd9Sstevel@tonic-gate 	size_t	dirlen;
56*7c478bd9Sstevel@tonic-gate };
57*7c478bd9Sstevel@tonic-gate 
58*7c478bd9Sstevel@tonic-gate #define	_USRLIB	"/usr/lib/"
59*7c478bd9Sstevel@tonic-gate #define	_USRDT	"/usr/dt/"
60*7c478bd9Sstevel@tonic-gate #define	_USROW	"/usr/openwin/"
61*7c478bd9Sstevel@tonic-gate 
62*7c478bd9Sstevel@tonic-gate static const struct trusted_systemdirs	prefix[] = {
63*7c478bd9Sstevel@tonic-gate 	{ _USRLIB,	sizeof (_USRLIB) - 1 },
64*7c478bd9Sstevel@tonic-gate 	{ _USRDT,	sizeof (_USRDT) - 1 },
65*7c478bd9Sstevel@tonic-gate 	{ _USROW,	sizeof (_USROW) - 1 },
66*7c478bd9Sstevel@tonic-gate 	{ NULL,		0 }
67*7c478bd9Sstevel@tonic-gate };
68*7c478bd9Sstevel@tonic-gate 
69*7c478bd9Sstevel@tonic-gate static int8_t nlspath_safe;
70*7c478bd9Sstevel@tonic-gate 
71*7c478bd9Sstevel@tonic-gate /*
72*7c478bd9Sstevel@tonic-gate  * Routine to check the safety of a messages file.
73*7c478bd9Sstevel@tonic-gate  * When the program specifies a pathname and doesn't
74*7c478bd9Sstevel@tonic-gate  * use NLSPATH, it should specify the "safe" flag as 1.
75*7c478bd9Sstevel@tonic-gate  * Most checks will be disabled then.
76*7c478bd9Sstevel@tonic-gate  * fstat64 is done here and the stat structure is returned
77*7c478bd9Sstevel@tonic-gate  * to prevent duplication of system calls.
78*7c478bd9Sstevel@tonic-gate  *
79*7c478bd9Sstevel@tonic-gate  * The trust return value contains an indication of
80*7c478bd9Sstevel@tonic-gate  * trustworthiness (i.e., does check_format need to be called or
81*7c478bd9Sstevel@tonic-gate  * not)
82*7c478bd9Sstevel@tonic-gate  */
83*7c478bd9Sstevel@tonic-gate 
84*7c478bd9Sstevel@tonic-gate int
85*7c478bd9Sstevel@tonic-gate nls_safe_open(const char *path, struct stat64 *statbuf, int *trust, int safe)
86*7c478bd9Sstevel@tonic-gate {
87*7c478bd9Sstevel@tonic-gate 	int	fd;
88*7c478bd9Sstevel@tonic-gate 	int	trust_path;
89*7c478bd9Sstevel@tonic-gate 	int	systemdir = 0;
90*7c478bd9Sstevel@tonic-gate 	int	abs_path = 0;
91*7c478bd9Sstevel@tonic-gate 	int	trust_owner = 0;
92*7c478bd9Sstevel@tonic-gate 	int	trust_group = 0;
93*7c478bd9Sstevel@tonic-gate 	const struct trusted_systemdirs	*p;
94*7c478bd9Sstevel@tonic-gate 
95*7c478bd9Sstevel@tonic-gate 	/*
96*7c478bd9Sstevel@tonic-gate 	 * If SAFE_F has been specified or NLSPATH is safe (or not set),
97*7c478bd9Sstevel@tonic-gate 	 * set trust_path and trust the file as an initial value.
98*7c478bd9Sstevel@tonic-gate 	 */
99*7c478bd9Sstevel@tonic-gate 	trust_path = *trust = safe || nlspath_safe;
100*7c478bd9Sstevel@tonic-gate 
101*7c478bd9Sstevel@tonic-gate 	fd = open(path, O_RDONLY);
102*7c478bd9Sstevel@tonic-gate 
103*7c478bd9Sstevel@tonic-gate 	if (fd < 0)
104*7c478bd9Sstevel@tonic-gate 		return (-1);
105*7c478bd9Sstevel@tonic-gate 
106*7c478bd9Sstevel@tonic-gate 	if (fstat64(fd, statbuf) == -1) {
107*7c478bd9Sstevel@tonic-gate 		(void) close(fd);
108*7c478bd9Sstevel@tonic-gate 		return (-1);
109*7c478bd9Sstevel@tonic-gate 	}
110*7c478bd9Sstevel@tonic-gate 
111*7c478bd9Sstevel@tonic-gate 	/*
112*7c478bd9Sstevel@tonic-gate 	 * Trust only files owned by root or bin (uid 2), except
113*7c478bd9Sstevel@tonic-gate 	 * when specified as full path or when NLSPATH is known to
114*7c478bd9Sstevel@tonic-gate 	 * be safe.
115*7c478bd9Sstevel@tonic-gate 	 * Don't trust files writable by other or writable
116*7c478bd9Sstevel@tonic-gate 	 * by non-bin, non-root system group.
117*7c478bd9Sstevel@tonic-gate 	 * Don't trust these files even if the path is correct.
118*7c478bd9Sstevel@tonic-gate 	 * Since we don't support changing uids/gids on our files,
119*7c478bd9Sstevel@tonic-gate 	 * we hardcode them here for now.
120*7c478bd9Sstevel@tonic-gate 	 */
121*7c478bd9Sstevel@tonic-gate 
122*7c478bd9Sstevel@tonic-gate 	/*
123*7c478bd9Sstevel@tonic-gate 	 * if the path is absolute and does not contain "/../",
124*7c478bd9Sstevel@tonic-gate 	 * set abs_path.
125*7c478bd9Sstevel@tonic-gate 	 */
126*7c478bd9Sstevel@tonic-gate 	if (*path == '/' && strstr(path, "/../") == NULL) {
127*7c478bd9Sstevel@tonic-gate 		abs_path = 1;
128*7c478bd9Sstevel@tonic-gate 		/*
129*7c478bd9Sstevel@tonic-gate 		 * if the path belongs to the trusted system directory,
130*7c478bd9Sstevel@tonic-gate 		 * set systemdir.
131*7c478bd9Sstevel@tonic-gate 		 */
132*7c478bd9Sstevel@tonic-gate 		for (p = prefix; p->dir; p++) {
133*7c478bd9Sstevel@tonic-gate 			if (strncmp(p->dir, path, p->dirlen) == 0) {
134*7c478bd9Sstevel@tonic-gate 				systemdir = 1;
135*7c478bd9Sstevel@tonic-gate 				break;
136*7c478bd9Sstevel@tonic-gate 			}
137*7c478bd9Sstevel@tonic-gate 		}
138*7c478bd9Sstevel@tonic-gate 	}
139*7c478bd9Sstevel@tonic-gate 
140*7c478bd9Sstevel@tonic-gate 	/*
141*7c478bd9Sstevel@tonic-gate 	 * If the owner is root or bin, set trust_owner.
142*7c478bd9Sstevel@tonic-gate 	 */
143*7c478bd9Sstevel@tonic-gate 	if (statbuf->st_uid == 0 || statbuf->st_uid == 2) {
144*7c478bd9Sstevel@tonic-gate 		trust_owner = 1;
145*7c478bd9Sstevel@tonic-gate 	}
146*7c478bd9Sstevel@tonic-gate 	/*
147*7c478bd9Sstevel@tonic-gate 	 * If the file is neither other-writable nor group-writable by
148*7c478bd9Sstevel@tonic-gate 	 * non-bin and non-root system group, set trust_group.
149*7c478bd9Sstevel@tonic-gate 	 */
150*7c478bd9Sstevel@tonic-gate 	if ((statbuf->st_mode & (S_IWOTH)) == 0 &&
151*7c478bd9Sstevel@tonic-gate 	    ((statbuf->st_mode & (S_IWGRP)) == 0 ||
152*7c478bd9Sstevel@tonic-gate 		(statbuf->st_gid < 4 && statbuf->st_gid != 1))) {
153*7c478bd9Sstevel@tonic-gate 		trust_group = 1;
154*7c478bd9Sstevel@tonic-gate 	}
155*7c478bd9Sstevel@tonic-gate 
156*7c478bd9Sstevel@tonic-gate 	/*
157*7c478bd9Sstevel@tonic-gate 	 * Even if UNSAFE_F has been specified and unsafe-NLSPATH
158*7c478bd9Sstevel@tonic-gate 	 * has been set, trust the file as long as it belongs to
159*7c478bd9Sstevel@tonic-gate 	 * the trusted system directory.
160*7c478bd9Sstevel@tonic-gate 	 */
161*7c478bd9Sstevel@tonic-gate 	if (!*trust && systemdir) {
162*7c478bd9Sstevel@tonic-gate 		*trust = 1;
163*7c478bd9Sstevel@tonic-gate 	}
164*7c478bd9Sstevel@tonic-gate 
165*7c478bd9Sstevel@tonic-gate 	/*
166*7c478bd9Sstevel@tonic-gate 	 * If:
167*7c478bd9Sstevel@tonic-gate 	 *	file is not a full pathname,
168*7c478bd9Sstevel@tonic-gate 	 * or
169*7c478bd9Sstevel@tonic-gate 	 *	neither trust_owner nor trust_path is set,
170*7c478bd9Sstevel@tonic-gate 	 * or
171*7c478bd9Sstevel@tonic-gate 	 *	trust_group is not set,
172*7c478bd9Sstevel@tonic-gate 	 * untrust it.
173*7c478bd9Sstevel@tonic-gate 	 */
174*7c478bd9Sstevel@tonic-gate 	if (*trust &&
175*7c478bd9Sstevel@tonic-gate 	    (!abs_path || (!trust_owner && !trust_path) || !trust_group)) {
176*7c478bd9Sstevel@tonic-gate 		*trust = 0;
177*7c478bd9Sstevel@tonic-gate 	}
178*7c478bd9Sstevel@tonic-gate 
179*7c478bd9Sstevel@tonic-gate 	/*
180*7c478bd9Sstevel@tonic-gate 	 * If set[ug]id process, open for the untrusted file should fail.
181*7c478bd9Sstevel@tonic-gate 	 * Otherwise, the message extracted from the untrusted file
182*7c478bd9Sstevel@tonic-gate 	 * will have to be checked by check_format().
183*7c478bd9Sstevel@tonic-gate 	 */
184*7c478bd9Sstevel@tonic-gate 	if (issetugid()) {
185*7c478bd9Sstevel@tonic-gate 		if (!*trust) {
186*7c478bd9Sstevel@tonic-gate 			/*
187*7c478bd9Sstevel@tonic-gate 			 * Open should fail
188*7c478bd9Sstevel@tonic-gate 			 */
189*7c478bd9Sstevel@tonic-gate 			(void) close(fd);
190*7c478bd9Sstevel@tonic-gate 			return (-1);
191*7c478bd9Sstevel@tonic-gate 		}
192*7c478bd9Sstevel@tonic-gate 
193*7c478bd9Sstevel@tonic-gate 		/*
194*7c478bd9Sstevel@tonic-gate 		 * if the path does not belong to the trusted system directory
195*7c478bd9Sstevel@tonic-gate 		 * or if the owner is neither root nor bin, untrust it.
196*7c478bd9Sstevel@tonic-gate 		 */
197*7c478bd9Sstevel@tonic-gate 		if (!systemdir || !trust_owner) {
198*7c478bd9Sstevel@tonic-gate 			*trust = 0;
199*7c478bd9Sstevel@tonic-gate 		}
200*7c478bd9Sstevel@tonic-gate 	}
201*7c478bd9Sstevel@tonic-gate 
202*7c478bd9Sstevel@tonic-gate 	return (fd);
203*7c478bd9Sstevel@tonic-gate }
204*7c478bd9Sstevel@tonic-gate 
205*7c478bd9Sstevel@tonic-gate /*
206*7c478bd9Sstevel@tonic-gate  * Extract a format into a normalized format string.
207*7c478bd9Sstevel@tonic-gate  * Returns the number of arguments converted, -1 on error.
208*7c478bd9Sstevel@tonic-gate  * The string norm should contain 2N bytes; an upperbound is the
209*7c478bd9Sstevel@tonic-gate  * length of the format string.
210*7c478bd9Sstevel@tonic-gate  * The canonical format consists of two chars: one is the conversion
211*7c478bd9Sstevel@tonic-gate  * character (s, c, d, x, etc), the second one is the option flag.
212*7c478bd9Sstevel@tonic-gate  * L, ll, l, w as defined below.
213*7c478bd9Sstevel@tonic-gate  * A special conversion character, '*', indicates that the argument
214*7c478bd9Sstevel@tonic-gate  * is used as a precision specifier.
215*7c478bd9Sstevel@tonic-gate  */
216*7c478bd9Sstevel@tonic-gate 
217*7c478bd9Sstevel@tonic-gate #define	OPT_L		0x01
218*7c478bd9Sstevel@tonic-gate #define	OPT_l		0x02
219*7c478bd9Sstevel@tonic-gate #define	OPT_ll		0x04
220*7c478bd9Sstevel@tonic-gate #define	OPT_w		0x08
221*7c478bd9Sstevel@tonic-gate #define	OPT_h		0x10
222*7c478bd9Sstevel@tonic-gate #define	OPT_hh		0x20
223*7c478bd9Sstevel@tonic-gate #define	OPT_j		0x40
224*7c478bd9Sstevel@tonic-gate 
225*7c478bd9Sstevel@tonic-gate /* Number of bytes per canonical format entry */
226*7c478bd9Sstevel@tonic-gate #define	FORMAT_SIZE	2
227*7c478bd9Sstevel@tonic-gate 
228*7c478bd9Sstevel@tonic-gate /*
229*7c478bd9Sstevel@tonic-gate  * Check and store the argument; allow each argument to be used only as
230*7c478bd9Sstevel@tonic-gate  * one type even though printf allows multiple uses.  The specification only
231*7c478bd9Sstevel@tonic-gate  * allows one use, but we don't want to break existing functional code,
232*7c478bd9Sstevel@tonic-gate  * even if it's buggy.
233*7c478bd9Sstevel@tonic-gate  */
234*7c478bd9Sstevel@tonic-gate #define	STORE(buf, size, arg, val) 	if (arg * FORMAT_SIZE + 1 >= size ||\
235*7c478bd9Sstevel@tonic-gate 					    (strict ? \
236*7c478bd9Sstevel@tonic-gate 					    (buf[arg*FORMAT_SIZE] != '\0' && \
237*7c478bd9Sstevel@tonic-gate 					    buf[arg*FORMAT_SIZE] != val) \
238*7c478bd9Sstevel@tonic-gate 						: \
239*7c478bd9Sstevel@tonic-gate 					    (buf[arg*FORMAT_SIZE] == 'n'))) \
240*7c478bd9Sstevel@tonic-gate 						return (-1); \
241*7c478bd9Sstevel@tonic-gate 					else {\
242*7c478bd9Sstevel@tonic-gate 						if (arg >= maxarg) \
243*7c478bd9Sstevel@tonic-gate 							maxarg = arg + 1; \
244*7c478bd9Sstevel@tonic-gate 						narg++; \
245*7c478bd9Sstevel@tonic-gate 						buf[arg*FORMAT_SIZE] = val; \
246*7c478bd9Sstevel@tonic-gate 					}
247*7c478bd9Sstevel@tonic-gate 
248*7c478bd9Sstevel@tonic-gate /*
249*7c478bd9Sstevel@tonic-gate  * This function extracts sprintf format into a canonical
250*7c478bd9Sstevel@tonic-gate  * sprintf form.  It's not as easy as just removing everything
251*7c478bd9Sstevel@tonic-gate  * that isn't a format specifier, because of "%n$" specifiers.
252*7c478bd9Sstevel@tonic-gate  * Ideally, this should be compatible with printf and not
253*7c478bd9Sstevel@tonic-gate  * fail on bad formats.
254*7c478bd9Sstevel@tonic-gate  * However, that makes writing a proper check_format that
255*7c478bd9Sstevel@tonic-gate  * doesn't cause crashes a lot harder.
256*7c478bd9Sstevel@tonic-gate  */
257*7c478bd9Sstevel@tonic-gate 
258*7c478bd9Sstevel@tonic-gate static int
259*7c478bd9Sstevel@tonic-gate extract_format(const char *fmt, char *norm, size_t sz, int strict)
260*7c478bd9Sstevel@tonic-gate {
261*7c478bd9Sstevel@tonic-gate 	int narg = 0;
262*7c478bd9Sstevel@tonic-gate 	int t, arg, argp;
263*7c478bd9Sstevel@tonic-gate 	int dotseen;
264*7c478bd9Sstevel@tonic-gate 	char flag;
265*7c478bd9Sstevel@tonic-gate 	char conv;
266*7c478bd9Sstevel@tonic-gate 	int lastarg = -1;
267*7c478bd9Sstevel@tonic-gate 	int prevarg;
268*7c478bd9Sstevel@tonic-gate 	int maxarg = 0;		/* Highest index seen + 1 */
269*7c478bd9Sstevel@tonic-gate 	int lflag;
270*7c478bd9Sstevel@tonic-gate 
271*7c478bd9Sstevel@tonic-gate 	(void) memset(norm, '\0', sz);
272*7c478bd9Sstevel@tonic-gate 
273*7c478bd9Sstevel@tonic-gate #ifdef DEBUG
274*7c478bd9Sstevel@tonic-gate 	printf("Format \"%s\" canonical form: ", fmt);
275*7c478bd9Sstevel@tonic-gate #endif
276*7c478bd9Sstevel@tonic-gate 
277*7c478bd9Sstevel@tonic-gate 	for (; *fmt; fmt++) {
278*7c478bd9Sstevel@tonic-gate 		if (*fmt == '%') {
279*7c478bd9Sstevel@tonic-gate 			if (*++fmt == '%')
280*7c478bd9Sstevel@tonic-gate 				continue;
281*7c478bd9Sstevel@tonic-gate 
282*7c478bd9Sstevel@tonic-gate 			if (*fmt == '\0')
283*7c478bd9Sstevel@tonic-gate 				break;
284*7c478bd9Sstevel@tonic-gate 
285*7c478bd9Sstevel@tonic-gate 			prevarg = lastarg;
286*7c478bd9Sstevel@tonic-gate 			arg = ++lastarg;
287*7c478bd9Sstevel@tonic-gate 
288*7c478bd9Sstevel@tonic-gate 			t = 0;
289*7c478bd9Sstevel@tonic-gate 			while (*fmt && isdigit(*fmt))
290*7c478bd9Sstevel@tonic-gate 				t = t * 10 + *fmt++ - '0';
291*7c478bd9Sstevel@tonic-gate 
292*7c478bd9Sstevel@tonic-gate 			if (*fmt == '$') {
293*7c478bd9Sstevel@tonic-gate 				lastarg = arg = t - 1;
294*7c478bd9Sstevel@tonic-gate 				fmt++;
295*7c478bd9Sstevel@tonic-gate 			}
296*7c478bd9Sstevel@tonic-gate 
297*7c478bd9Sstevel@tonic-gate 			if (*fmt == '\0')
298*7c478bd9Sstevel@tonic-gate 				goto end;
299*7c478bd9Sstevel@tonic-gate 
300*7c478bd9Sstevel@tonic-gate 			dotseen = 0;
301*7c478bd9Sstevel@tonic-gate 			flag = 0;
302*7c478bd9Sstevel@tonic-gate 			lflag = 0;
303*7c478bd9Sstevel@tonic-gate again:
304*7c478bd9Sstevel@tonic-gate 			/* Skip flags */
305*7c478bd9Sstevel@tonic-gate 			while (*fmt) {
306*7c478bd9Sstevel@tonic-gate 				switch (*fmt) {
307*7c478bd9Sstevel@tonic-gate 				case '\'':
308*7c478bd9Sstevel@tonic-gate 				case '+':
309*7c478bd9Sstevel@tonic-gate 				case '-':
310*7c478bd9Sstevel@tonic-gate 				case ' ':
311*7c478bd9Sstevel@tonic-gate 				case '#':
312*7c478bd9Sstevel@tonic-gate 				case '0':
313*7c478bd9Sstevel@tonic-gate 					fmt++;
314*7c478bd9Sstevel@tonic-gate 					continue;
315*7c478bd9Sstevel@tonic-gate 				}
316*7c478bd9Sstevel@tonic-gate 				break;
317*7c478bd9Sstevel@tonic-gate 			}
318*7c478bd9Sstevel@tonic-gate 
319*7c478bd9Sstevel@tonic-gate 			while (*fmt && isdigit(*fmt))
320*7c478bd9Sstevel@tonic-gate 				fmt++;
321*7c478bd9Sstevel@tonic-gate 
322*7c478bd9Sstevel@tonic-gate 			if (*fmt == '*') {
323*7c478bd9Sstevel@tonic-gate 				if (isdigit(fmt[1])) {
324*7c478bd9Sstevel@tonic-gate 					fmt++;
325*7c478bd9Sstevel@tonic-gate 					t = 0;
326*7c478bd9Sstevel@tonic-gate 					while (*fmt && isdigit(*fmt))
327*7c478bd9Sstevel@tonic-gate 						t = t * 10 + *fmt++ - '0';
328*7c478bd9Sstevel@tonic-gate 
329*7c478bd9Sstevel@tonic-gate 					if (*fmt == '$') {
330*7c478bd9Sstevel@tonic-gate 						argp = t - 1;
331*7c478bd9Sstevel@tonic-gate 						STORE(norm, sz, argp, '*');
332*7c478bd9Sstevel@tonic-gate 					}
333*7c478bd9Sstevel@tonic-gate 					/*
334*7c478bd9Sstevel@tonic-gate 					 * If digits follow a '*', it is
335*7c478bd9Sstevel@tonic-gate 					 * not loaded as an argument, the
336*7c478bd9Sstevel@tonic-gate 					 * digits are used instead.
337*7c478bd9Sstevel@tonic-gate 					 */
338*7c478bd9Sstevel@tonic-gate 				} else {
339*7c478bd9Sstevel@tonic-gate 					/*
340*7c478bd9Sstevel@tonic-gate 					 * Weird as it may seem, if we
341*7c478bd9Sstevel@tonic-gate 					 * use an numbered argument, we
342*7c478bd9Sstevel@tonic-gate 					 * get the next one if we have
343*7c478bd9Sstevel@tonic-gate 					 * an unnumbered '*'
344*7c478bd9Sstevel@tonic-gate 					 */
345*7c478bd9Sstevel@tonic-gate 					if (fmt[1] == '$')
346*7c478bd9Sstevel@tonic-gate 						fmt++;
347*7c478bd9Sstevel@tonic-gate 					else {
348*7c478bd9Sstevel@tonic-gate 						argp = arg;
349*7c478bd9Sstevel@tonic-gate 						prevarg = arg;
350*7c478bd9Sstevel@tonic-gate 						lastarg = ++arg;
351*7c478bd9Sstevel@tonic-gate 						STORE(norm, sz, argp, '*');
352*7c478bd9Sstevel@tonic-gate 					}
353*7c478bd9Sstevel@tonic-gate 				}
354*7c478bd9Sstevel@tonic-gate 				fmt++;
355*7c478bd9Sstevel@tonic-gate 			}
356*7c478bd9Sstevel@tonic-gate 
357*7c478bd9Sstevel@tonic-gate 			/* Fail on two or more dots if we do strict checking */
358*7c478bd9Sstevel@tonic-gate 			if (*fmt == '.' || *fmt == '*') {
359*7c478bd9Sstevel@tonic-gate 				if (dotseen && strict)
360*7c478bd9Sstevel@tonic-gate 					return (-1);
361*7c478bd9Sstevel@tonic-gate 				dotseen = 1;
362*7c478bd9Sstevel@tonic-gate 				fmt++;
363*7c478bd9Sstevel@tonic-gate 				goto again;
364*7c478bd9Sstevel@tonic-gate 			}
365*7c478bd9Sstevel@tonic-gate 
366*7c478bd9Sstevel@tonic-gate 			if (*fmt == '\0')
367*7c478bd9Sstevel@tonic-gate 				goto end;
368*7c478bd9Sstevel@tonic-gate 
369*7c478bd9Sstevel@tonic-gate 			while (*fmt) {
370*7c478bd9Sstevel@tonic-gate 				switch (*fmt) {
371*7c478bd9Sstevel@tonic-gate 				case 'l':
372*7c478bd9Sstevel@tonic-gate 					if (!(flag & OPT_ll)) {
373*7c478bd9Sstevel@tonic-gate 						if (lflag) {
374*7c478bd9Sstevel@tonic-gate 							flag &= ~OPT_l;
375*7c478bd9Sstevel@tonic-gate 							flag |= OPT_ll;
376*7c478bd9Sstevel@tonic-gate 						} else {
377*7c478bd9Sstevel@tonic-gate 							flag |= OPT_l;
378*7c478bd9Sstevel@tonic-gate 						}
379*7c478bd9Sstevel@tonic-gate 					}
380*7c478bd9Sstevel@tonic-gate 					lflag++;
381*7c478bd9Sstevel@tonic-gate 					break;
382*7c478bd9Sstevel@tonic-gate 				case 'L':
383*7c478bd9Sstevel@tonic-gate 					flag |= OPT_L;
384*7c478bd9Sstevel@tonic-gate 					break;
385*7c478bd9Sstevel@tonic-gate 				case 'w':
386*7c478bd9Sstevel@tonic-gate 					flag |= OPT_w;
387*7c478bd9Sstevel@tonic-gate 					break;
388*7c478bd9Sstevel@tonic-gate 				case 'h':
389*7c478bd9Sstevel@tonic-gate 					if (flag & (OPT_h|OPT_hh))
390*7c478bd9Sstevel@tonic-gate 						flag |= OPT_hh;
391*7c478bd9Sstevel@tonic-gate 					else
392*7c478bd9Sstevel@tonic-gate 						flag |= OPT_h;
393*7c478bd9Sstevel@tonic-gate 					break;
394*7c478bd9Sstevel@tonic-gate 				case 'j':
395*7c478bd9Sstevel@tonic-gate 					flag |= OPT_j;
396*7c478bd9Sstevel@tonic-gate 					break;
397*7c478bd9Sstevel@tonic-gate 				case 'z':
398*7c478bd9Sstevel@tonic-gate 				case 't':
399*7c478bd9Sstevel@tonic-gate 					if (!(flag & OPT_ll)) {
400*7c478bd9Sstevel@tonic-gate 						flag |= OPT_l;
401*7c478bd9Sstevel@tonic-gate 					}
402*7c478bd9Sstevel@tonic-gate 					break;
403*7c478bd9Sstevel@tonic-gate 				case '\'':
404*7c478bd9Sstevel@tonic-gate 				case '+':
405*7c478bd9Sstevel@tonic-gate 				case '-':
406*7c478bd9Sstevel@tonic-gate 				case ' ':
407*7c478bd9Sstevel@tonic-gate 				case '#':
408*7c478bd9Sstevel@tonic-gate 				case '.':
409*7c478bd9Sstevel@tonic-gate 				case '*':
410*7c478bd9Sstevel@tonic-gate 					goto again;
411*7c478bd9Sstevel@tonic-gate 				default:
412*7c478bd9Sstevel@tonic-gate 					if (isdigit(*fmt))
413*7c478bd9Sstevel@tonic-gate 						goto again;
414*7c478bd9Sstevel@tonic-gate 					else
415*7c478bd9Sstevel@tonic-gate 						goto done;
416*7c478bd9Sstevel@tonic-gate 				}
417*7c478bd9Sstevel@tonic-gate 				fmt++;
418*7c478bd9Sstevel@tonic-gate 			}
419*7c478bd9Sstevel@tonic-gate done:
420*7c478bd9Sstevel@tonic-gate 			if (*fmt == '\0')
421*7c478bd9Sstevel@tonic-gate 				goto end;
422*7c478bd9Sstevel@tonic-gate 
423*7c478bd9Sstevel@tonic-gate 			switch (*fmt) {
424*7c478bd9Sstevel@tonic-gate 			case 'C':
425*7c478bd9Sstevel@tonic-gate 				flag |= OPT_l;
426*7c478bd9Sstevel@tonic-gate 				/* FALLTHROUGH */
427*7c478bd9Sstevel@tonic-gate 			case 'd':
428*7c478bd9Sstevel@tonic-gate 			case 'i':
429*7c478bd9Sstevel@tonic-gate 			case 'o':
430*7c478bd9Sstevel@tonic-gate 			case 'u':
431*7c478bd9Sstevel@tonic-gate 			case 'c':
432*7c478bd9Sstevel@tonic-gate 			case 'x':
433*7c478bd9Sstevel@tonic-gate 			case 'X':
434*7c478bd9Sstevel@tonic-gate 				conv = 'I';
435*7c478bd9Sstevel@tonic-gate 				break;
436*7c478bd9Sstevel@tonic-gate 			case 'e':
437*7c478bd9Sstevel@tonic-gate 			case 'E':
438*7c478bd9Sstevel@tonic-gate 			case 'f':
439*7c478bd9Sstevel@tonic-gate 			case 'F':
440*7c478bd9Sstevel@tonic-gate 			case 'a':
441*7c478bd9Sstevel@tonic-gate 			case 'A':
442*7c478bd9Sstevel@tonic-gate 			case 'g':
443*7c478bd9Sstevel@tonic-gate 			case 'G':
444*7c478bd9Sstevel@tonic-gate 				conv = 'D';
445*7c478bd9Sstevel@tonic-gate 				break;
446*7c478bd9Sstevel@tonic-gate 			case 'S':
447*7c478bd9Sstevel@tonic-gate 				flag |= OPT_l;
448*7c478bd9Sstevel@tonic-gate 				/* FALLTHROUGH */
449*7c478bd9Sstevel@tonic-gate 			case 's':
450*7c478bd9Sstevel@tonic-gate 				conv = 's';
451*7c478bd9Sstevel@tonic-gate 				break;
452*7c478bd9Sstevel@tonic-gate 			case 'p':
453*7c478bd9Sstevel@tonic-gate 			case 'n':
454*7c478bd9Sstevel@tonic-gate 				conv = *fmt;
455*7c478bd9Sstevel@tonic-gate 				break;
456*7c478bd9Sstevel@tonic-gate 			default:
457*7c478bd9Sstevel@tonic-gate 				lastarg = prevarg;
458*7c478bd9Sstevel@tonic-gate 				continue;
459*7c478bd9Sstevel@tonic-gate 			}
460*7c478bd9Sstevel@tonic-gate 
461*7c478bd9Sstevel@tonic-gate 			STORE(norm, sz, arg, conv);
462*7c478bd9Sstevel@tonic-gate 			norm[arg*FORMAT_SIZE + 1] = flag;
463*7c478bd9Sstevel@tonic-gate 		}
464*7c478bd9Sstevel@tonic-gate 	}
465*7c478bd9Sstevel@tonic-gate #ifdef DEBUG
466*7c478bd9Sstevel@tonic-gate 	for (t = 0; t < maxarg * FORMAT_SIZE; t += FORMAT_SIZE) {
467*7c478bd9Sstevel@tonic-gate 	    printf("%c(%d)", norm[t], norm[t+1]);
468*7c478bd9Sstevel@tonic-gate 	}
469*7c478bd9Sstevel@tonic-gate 	putchar('\n');
470*7c478bd9Sstevel@tonic-gate #endif
471*7c478bd9Sstevel@tonic-gate end:
472*7c478bd9Sstevel@tonic-gate 	if (strict)
473*7c478bd9Sstevel@tonic-gate 		for (arg = 0; arg < maxarg; arg++)
474*7c478bd9Sstevel@tonic-gate 			if (norm[arg*FORMAT_SIZE] == '\0')
475*7c478bd9Sstevel@tonic-gate 				return (-1);
476*7c478bd9Sstevel@tonic-gate 
477*7c478bd9Sstevel@tonic-gate 	return (maxarg);
478*7c478bd9Sstevel@tonic-gate }
479*7c478bd9Sstevel@tonic-gate 
480*7c478bd9Sstevel@tonic-gate char *
481*7c478bd9Sstevel@tonic-gate check_format(const char *org, const char *new, int strict)
482*7c478bd9Sstevel@tonic-gate {
483*7c478bd9Sstevel@tonic-gate 	char *ofmt, *nfmt, *torg;
484*7c478bd9Sstevel@tonic-gate 	size_t osz, nsz;
485*7c478bd9Sstevel@tonic-gate 	int olen, nlen;
486*7c478bd9Sstevel@tonic-gate 
487*7c478bd9Sstevel@tonic-gate 	if (!org) {
488*7c478bd9Sstevel@tonic-gate 		/*
489*7c478bd9Sstevel@tonic-gate 		 * Default message is NULL.
490*7c478bd9Sstevel@tonic-gate 		 * dtmail uses NULL for default message.
491*7c478bd9Sstevel@tonic-gate 		 */
492*7c478bd9Sstevel@tonic-gate 		torg = "(NULL)";
493*7c478bd9Sstevel@tonic-gate 	} else {
494*7c478bd9Sstevel@tonic-gate 		torg = (char *)org;
495*7c478bd9Sstevel@tonic-gate 	}
496*7c478bd9Sstevel@tonic-gate 
497*7c478bd9Sstevel@tonic-gate 	/* Short cut */
498*7c478bd9Sstevel@tonic-gate 	if (org == new || strcmp(torg, new) == 0 ||
499*7c478bd9Sstevel@tonic-gate 	    strchr(new, '%') == NULL)
500*7c478bd9Sstevel@tonic-gate 		return ((char *)new);
501*7c478bd9Sstevel@tonic-gate 
502*7c478bd9Sstevel@tonic-gate 	osz = strlen(torg) * FORMAT_SIZE;
503*7c478bd9Sstevel@tonic-gate 	ofmt = malloc(osz);
504*7c478bd9Sstevel@tonic-gate 	if (ofmt == NULL)
505*7c478bd9Sstevel@tonic-gate 		return ((char *)org);
506*7c478bd9Sstevel@tonic-gate 
507*7c478bd9Sstevel@tonic-gate 	olen = extract_format(torg, ofmt, osz, 0);
508*7c478bd9Sstevel@tonic-gate 
509*7c478bd9Sstevel@tonic-gate 	if (olen == -1)
510*7c478bd9Sstevel@tonic-gate 		syslog(LOG_AUTH|LOG_INFO,
511*7c478bd9Sstevel@tonic-gate 		    "invalid format in gettext argument: \"%s\"", torg);
512*7c478bd9Sstevel@tonic-gate 
513*7c478bd9Sstevel@tonic-gate 	nsz = strlen(new) * FORMAT_SIZE;
514*7c478bd9Sstevel@tonic-gate 	nfmt = malloc(nsz);
515*7c478bd9Sstevel@tonic-gate 	if (nfmt == NULL) {
516*7c478bd9Sstevel@tonic-gate 		free(ofmt);
517*7c478bd9Sstevel@tonic-gate 		return ((char *)org);
518*7c478bd9Sstevel@tonic-gate 	}
519*7c478bd9Sstevel@tonic-gate 
520*7c478bd9Sstevel@tonic-gate 	nlen = extract_format(new, nfmt, nsz, strict);
521*7c478bd9Sstevel@tonic-gate 
522*7c478bd9Sstevel@tonic-gate 	if (nlen == -1) {
523*7c478bd9Sstevel@tonic-gate 		free(ofmt);
524*7c478bd9Sstevel@tonic-gate 		free(nfmt);
525*7c478bd9Sstevel@tonic-gate 		syslog(LOG_AUTH|LOG_NOTICE,
526*7c478bd9Sstevel@tonic-gate 		    "invalid format in message file \"%.100s\" -> \"%s\"",
527*7c478bd9Sstevel@tonic-gate 		    torg, new);
528*7c478bd9Sstevel@tonic-gate 		errno = EBADMSG;
529*7c478bd9Sstevel@tonic-gate 		return ((char *)org);
530*7c478bd9Sstevel@tonic-gate 	}
531*7c478bd9Sstevel@tonic-gate 
532*7c478bd9Sstevel@tonic-gate 	if (strict && (olen != nlen || olen == -1)) {
533*7c478bd9Sstevel@tonic-gate 		free(ofmt);
534*7c478bd9Sstevel@tonic-gate 		free(nfmt);
535*7c478bd9Sstevel@tonic-gate 		syslog(LOG_AUTH|LOG_NOTICE,
536*7c478bd9Sstevel@tonic-gate 		    "incompatible format in message file: \"%.100s\" != \"%s\"",
537*7c478bd9Sstevel@tonic-gate 		    torg, new);
538*7c478bd9Sstevel@tonic-gate 		errno = EBADMSG;
539*7c478bd9Sstevel@tonic-gate 		return ((char *)org);
540*7c478bd9Sstevel@tonic-gate 	}
541*7c478bd9Sstevel@tonic-gate 
542*7c478bd9Sstevel@tonic-gate 	if (strict && memcmp(ofmt, nfmt, nlen * FORMAT_SIZE) == 0) {
543*7c478bd9Sstevel@tonic-gate 		free(ofmt);
544*7c478bd9Sstevel@tonic-gate 		free(nfmt);
545*7c478bd9Sstevel@tonic-gate 		return ((char *)new);
546*7c478bd9Sstevel@tonic-gate 	} else {
547*7c478bd9Sstevel@tonic-gate 		if (!strict) {
548*7c478bd9Sstevel@tonic-gate 			char *n;
549*7c478bd9Sstevel@tonic-gate 
550*7c478bd9Sstevel@tonic-gate 			nlen *= FORMAT_SIZE;
551*7c478bd9Sstevel@tonic-gate 
552*7c478bd9Sstevel@tonic-gate 			for (n = nfmt; n = memchr(n, 'n', nfmt + nlen - n);
553*7c478bd9Sstevel@tonic-gate 			    n++) {
554*7c478bd9Sstevel@tonic-gate 				int off = (n - nfmt);
555*7c478bd9Sstevel@tonic-gate 
556*7c478bd9Sstevel@tonic-gate 				if (off >= olen * FORMAT_SIZE ||
557*7c478bd9Sstevel@tonic-gate 				    ofmt[off] != 'n' ||
558*7c478bd9Sstevel@tonic-gate 				    ofmt[off+1] != nfmt[off+1]) {
559*7c478bd9Sstevel@tonic-gate 					free(ofmt);
560*7c478bd9Sstevel@tonic-gate 					free(nfmt);
561*7c478bd9Sstevel@tonic-gate 					syslog(LOG_AUTH|LOG_NOTICE,
562*7c478bd9Sstevel@tonic-gate 					    "dangerous format in message file: "
563*7c478bd9Sstevel@tonic-gate 					    "\"%.100s\" -> \"%s\"", torg, new);
564*7c478bd9Sstevel@tonic-gate 					errno = EBADMSG;
565*7c478bd9Sstevel@tonic-gate 					return ((char *)org);
566*7c478bd9Sstevel@tonic-gate 				}
567*7c478bd9Sstevel@tonic-gate 			}
568*7c478bd9Sstevel@tonic-gate 			free(ofmt);
569*7c478bd9Sstevel@tonic-gate 			free(nfmt);
570*7c478bd9Sstevel@tonic-gate 			return ((char *)new);
571*7c478bd9Sstevel@tonic-gate 		}
572*7c478bd9Sstevel@tonic-gate 		free(ofmt);
573*7c478bd9Sstevel@tonic-gate 		free(nfmt);
574*7c478bd9Sstevel@tonic-gate 		syslog(LOG_AUTH|LOG_NOTICE,
575*7c478bd9Sstevel@tonic-gate 		    "incompatible format in message file \"%.100s\" != \"%s\"",
576*7c478bd9Sstevel@tonic-gate 		    torg, new);
577*7c478bd9Sstevel@tonic-gate 		errno = EBADMSG;
578*7c478bd9Sstevel@tonic-gate 		return ((char *)org);
579*7c478bd9Sstevel@tonic-gate 	}
580*7c478bd9Sstevel@tonic-gate }
581*7c478bd9Sstevel@tonic-gate 
582*7c478bd9Sstevel@tonic-gate /*
583*7c478bd9Sstevel@tonic-gate  * s1 is either name, or name=value
584*7c478bd9Sstevel@tonic-gate  * s2 is name=value
585*7c478bd9Sstevel@tonic-gate  * if names match, return value of s2, else NULL
586*7c478bd9Sstevel@tonic-gate  * used for environment searching: see getenv
587*7c478bd9Sstevel@tonic-gate  */
588*7c478bd9Sstevel@tonic-gate const char *
589*7c478bd9Sstevel@tonic-gate nvmatch(const char *s1, const char *s2)
590*7c478bd9Sstevel@tonic-gate {
591*7c478bd9Sstevel@tonic-gate 	while (*s1 == *s2++)
592*7c478bd9Sstevel@tonic-gate 		if (*s1++ == '=')
593*7c478bd9Sstevel@tonic-gate 			return (s2);
594*7c478bd9Sstevel@tonic-gate 	if (*s1 == '\0' && *(s2-1) == '=')
595*7c478bd9Sstevel@tonic-gate 		return (s2);
596*7c478bd9Sstevel@tonic-gate 	return (NULL);
597*7c478bd9Sstevel@tonic-gate }
598*7c478bd9Sstevel@tonic-gate 
599*7c478bd9Sstevel@tonic-gate /*
600*7c478bd9Sstevel@tonic-gate  * Handle NLSPATH environment variables in the environment.
601*7c478bd9Sstevel@tonic-gate  * This routine is hooked into getenv/putenv at first call.
602*7c478bd9Sstevel@tonic-gate  *
603*7c478bd9Sstevel@tonic-gate  * The intention is to ignore NLSPATH in set-uid applications,
604*7c478bd9Sstevel@tonic-gate  * and determine whether the NLSPATH in an application was set
605*7c478bd9Sstevel@tonic-gate  * by the applications or derived from the user's environment.
606*7c478bd9Sstevel@tonic-gate  */
607*7c478bd9Sstevel@tonic-gate 
608*7c478bd9Sstevel@tonic-gate void
609*7c478bd9Sstevel@tonic-gate clean_env(void)
610*7c478bd9Sstevel@tonic-gate {
611*7c478bd9Sstevel@tonic-gate 	const char **p;
612*7c478bd9Sstevel@tonic-gate 
613*7c478bd9Sstevel@tonic-gate 	/* Find the first NLSPATH occurrence */
614*7c478bd9Sstevel@tonic-gate 	for (p = environ; *p; p++)
615*7c478bd9Sstevel@tonic-gate 		if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL)
616*7c478bd9Sstevel@tonic-gate 			break;
617*7c478bd9Sstevel@tonic-gate 
618*7c478bd9Sstevel@tonic-gate 	if (!*p)				/* None found, we're safe */
619*7c478bd9Sstevel@tonic-gate 		nlspath_safe = 1;
620*7c478bd9Sstevel@tonic-gate 	else if (issetugid()) {			/* Found and set-uid, clean */
621*7c478bd9Sstevel@tonic-gate 		int off = 1;
622*7c478bd9Sstevel@tonic-gate 
623*7c478bd9Sstevel@tonic-gate 		for (p++; (p[-off] = p[0]) != '\0'; p++)
624*7c478bd9Sstevel@tonic-gate 			if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL)
625*7c478bd9Sstevel@tonic-gate 				off++;
626*7c478bd9Sstevel@tonic-gate 
627*7c478bd9Sstevel@tonic-gate 		nlspath_safe = 1;
628*7c478bd9Sstevel@tonic-gate 	}
629*7c478bd9Sstevel@tonic-gate }
630