1 /*	$OpenBSD: setmode.c,v 1.17 2005/08/08 08:05:34 espie Exp $	*/
2 /*	$NetBSD: setmode.c,v 1.15 1997/02/07 22:21:06 christos Exp $	*/
3 
4 /*
5  * Copyright (c) 1989, 1993, 1994
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * Dave Borman at Cray Research, Inc.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 
39 #include <ctype.h>
40 #include <errno.h>
41 #include <signal.h>
42 #include <stdint.h>
43 #include <stdlib.h>
44 #include <unistd.h>
45 
46 #ifdef SETMODE_DEBUG
47 #include <stdio.h>
48 #endif
49 
50 __SCCSID("@(#)setmode.c	8.2 (Berkeley) 3/25/94");
51 __RCSID("$MirOS: src/lib/libc/gen/setmode.c,v 1.15 2010/10/08 17:57:00 tg Exp $");
52 
53 #define	SET_LEN		6	/* initial # of bitcmd struct to malloc */
54 #define	SET_LEN_INCR	4	/* # of bitcmd structs to add as needed */
55 
56 typedef struct bitcmd {
57 	mode_t	bits;
58 	char	cmd;
59 	char	cmd2;
60 } BITCMD;
61 
62 #define	CMD2_CLR	0x01
63 #define	CMD2_SET	0x02
64 #define	CMD2_GBITS	0x04
65 #define	CMD2_OBITS	0x08
66 #define	CMD2_UBITS	0x10
67 
68 static BITCMD	*addcmd(BITCMD *, int, int, int, u_int);
69 static void	 compress_mode(BITCMD *);
70 #ifdef SETMODE_DEBUG
71 static void	 dumpmode(BITCMD *);
72 #endif
73 
74 /*
75  * Given the old mode and an array of bitcmd structures, apply the operations
76  * described in the bitcmd structures to the old mode, and return the new mode.
77  * Note that there is no '=' command; a strict assignment is just a '-' (clear
78  * bits) followed by a '+' (set bits).
79  */
80 mode_t
getmode(const void * bbox,mode_t omode)81 getmode(const void *bbox, mode_t omode)
82 {
83 	const BITCMD *set;
84 	mode_t clrval, newmode, value;
85 
86 	set = (const BITCMD *)bbox;
87 	newmode = omode;
88 	for (value = 0;; set++)
89 		switch(set->cmd) {
90 		/*
91 		 * When copying the user, group or other bits around, we "know"
92 		 * where the bits are in the mode so that we can do shifts to
93 		 * copy them around.  If we don't use shifts, it gets real
94 		 * grundgy with lots of single bit checks and bit sets.
95 		 */
96 		case 'u':
97 			value = (newmode & S_IRWXU) >> 6;
98 			goto common;
99 
100 		case 'g':
101 			value = (newmode & S_IRWXG) >> 3;
102 			goto common;
103 
104 		case 'o':
105 			value = newmode & S_IRWXO;
106  common:
107 			if (set->cmd2 & CMD2_CLR) {
108 				clrval =
109 				    (set->cmd2 & CMD2_SET) ?  S_IRWXO : value;
110 				if (set->cmd2 & CMD2_UBITS)
111 					newmode &= ~((clrval<<6) & set->bits);
112 				if (set->cmd2 & CMD2_GBITS)
113 					newmode &= ~((clrval<<3) & set->bits);
114 				if (set->cmd2 & CMD2_OBITS)
115 					newmode &= ~(clrval & set->bits);
116 			}
117 			if (set->cmd2 & CMD2_SET) {
118 				if (set->cmd2 & CMD2_UBITS)
119 					newmode |= (value<<6) & set->bits;
120 				if (set->cmd2 & CMD2_GBITS)
121 					newmode |= (value<<3) & set->bits;
122 				if (set->cmd2 & CMD2_OBITS)
123 					newmode |= value & set->bits;
124 			}
125 			break;
126 
127 		case '+':
128 			newmode |= set->bits;
129 			break;
130 
131 		case '-':
132 			newmode &= ~set->bits;
133 			break;
134 
135 		case 'X':
136 			if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH))
137 				newmode |= set->bits;
138 			break;
139 
140 		case '\0':
141 		default:
142 #ifdef SETMODE_DEBUG
143 			(void)printf("getmode:%04o -> %04o\n", omode, newmode);
144 #endif
145 			return (newmode);
146 		}
147 }
148 
149 #define notoktomul(a, b)	((a) && (b) && (SIZE_MAX / (a) < (b)))
150 
151 #define	ADDCMD(a, b, c, d)						\
152 	if (set >= endset) {						\
153 		BITCMD *newset;						\
154 		setlen += SET_LEN_INCR;					\
155 		if (notoktomul(setlen, sizeof(BITCMD)) ||		\
156 		    (newset = realloc(saveset, setlen *			\
157 		    sizeof(BITCMD))) == NULL) {				\
158 			free(saveset);					\
159 			return (NULL);					\
160 		}							\
161 		set = newset + (set - saveset);				\
162 		saveset = newset;					\
163 		endset = newset + (setlen - 2);				\
164 	}								\
165 	set = addcmd(set, (a), (b), (c), (d))
166 
167 #define	STANDARD_BITS	(S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
168 
169 void *
setmode(const char * p)170 setmode(const char *p)
171 {
172 	int perm, who;
173 	char op, *ep;
174 	BITCMD *set, *saveset, *endset;
175 	sigset_t signset, sigoset;
176 	mode_t mask;
177 	int equalopdone = 0, permXbits, setlen;
178 	u_long perml;
179 
180 	if (!*p)
181 		return (NULL);
182 
183 	/*
184 	 * Get a copy of the mask for the permissions that are mask relative.
185 	 * Flip the bits, we want what's not set.  Since it's possible that
186 	 * the caller is opening files inside a signal handler, protect them
187 	 * as best we can.
188 	 */
189 	sigfillset(&signset);
190 	(void)sigprocmask(SIG_BLOCK, &signset, &sigoset);
191 	(void)umask(mask = umask(0));
192 	mask = ~mask;
193 	(void)sigprocmask(SIG_SETMASK, &sigoset, NULL);
194 
195 	setlen = SET_LEN + 2;
196 
197 	if (notoktomul(setlen, sizeof(BITCMD)) ||
198 	    (set = malloc(setlen * sizeof(BITCMD))) == NULL)
199 		return (NULL);
200 	saveset = set;
201 	endset = set + (setlen - 2);
202 
203 	/*
204 	 * If an absolute number, get it and return; disallow non-octal digits
205 	 * or illegal bits.
206 	 */
207 	if (isdigit((unsigned char)*p)) {
208 		perml = strtoul(p, &ep, 8);
209 		/* The test on perml will also catch overflow. */
210 		if (*ep != '\0' || (perml & ~(STANDARD_BITS|S_ISTXT))) {
211 			free(saveset);
212 			errno = ERANGE;
213 			return (NULL);
214 		}
215 		perm = (mode_t)perml;
216 		ADDCMD('=', (STANDARD_BITS|S_ISTXT), perm, mask);
217 		set->cmd = 0;
218 		return (saveset);
219 	}
220 
221 	/*
222 	 * Build list of structures to set/clear/copy bits as described by
223 	 * each clause of the symbolic mode.
224 	 */
225 	for (;;) {
226 		/* First, find out which bits might be modified. */
227 		for (who = 0;; ++p) {
228 			switch (*p) {
229 			case 'a':
230 				who |= STANDARD_BITS;
231 				break;
232 			case 'u':
233 				who |= S_ISUID|S_IRWXU;
234 				break;
235 			case 'g':
236 				who |= S_ISGID|S_IRWXG;
237 				break;
238 			case 'o':
239 				who |= S_IRWXO;
240 				break;
241 			default:
242 				goto getop;
243 			}
244 		}
245 
246  getop:
247 		if ((op = *p++) != '+' && op != '-' && op != '=') {
248 			free(saveset);
249 			return (NULL);
250 		}
251 		if (op == '=')
252 			equalopdone = 0;
253 
254 		who &= ~S_ISTXT;
255 		for (perm = 0, permXbits = 0;; ++p) {
256 			switch (*p) {
257 			case 'r':
258 				perm |= S_IRUSR|S_IRGRP|S_IROTH;
259 				break;
260 			case 's':
261 				/*
262 				 * If specific bits where requested and
263 				 * only "other" bits ignore set-id.
264 				 */
265 				if (who == 0 || (who & ~S_IRWXO))
266 					perm |= S_ISUID|S_ISGID;
267 				break;
268 			case 't':
269 				/*
270 				 * If specific bits where requested and
271 				 * only "other" bits ignore sticky.
272 				 */
273 				if (who == 0 || (who & ~S_IRWXO)) {
274 					who |= S_ISTXT;
275 					perm |= S_ISTXT;
276 				}
277 				break;
278 			case 'w':
279 				perm |= S_IWUSR|S_IWGRP|S_IWOTH;
280 				break;
281 			case 'X':
282 				permXbits = S_IXUSR|S_IXGRP|S_IXOTH;
283 				break;
284 			case 'x':
285 				perm |= S_IXUSR|S_IXGRP|S_IXOTH;
286 				break;
287 			case 'u':
288 			case 'g':
289 			case 'o':
290 				/*
291 				 * When ever we hit 'u', 'g', or 'o', we have
292 				 * to flush out any partial mode that we have,
293 				 * and then do the copying of the mode bits.
294 				 */
295 				if (perm) {
296 					ADDCMD(op, who, perm, mask);
297 					perm = 0;
298 				}
299 				if (op == '=')
300 					equalopdone = 1;
301 				if (op == '+' && permXbits) {
302 					ADDCMD('X', who, permXbits, mask);
303 					permXbits = 0;
304 				}
305 				ADDCMD(*p, who, op, mask);
306 				break;
307 
308 			default:
309 				/*
310 				 * Add any permissions that we haven't already
311 				 * done.
312 				 */
313 				if (perm || (op == '=' && !equalopdone)) {
314 					if (op == '=')
315 						equalopdone = 1;
316 					ADDCMD(op, who, perm, mask);
317 					perm = 0;
318 				}
319 				if (permXbits) {
320 					ADDCMD('X', who, permXbits, mask);
321 					permXbits = 0;
322 				}
323 				goto apply;
324 			}
325 		}
326 
327  apply:
328 		if (!*p)
329 			break;
330 		if (*p != ',')
331 			goto getop;
332 		++p;
333 	}
334 	set->cmd = 0;
335 #ifdef SETMODE_DEBUG
336 	(void)printf("Before compress_mode()\n");
337 	dumpmode(saveset);
338 #endif
339 	compress_mode(saveset);
340 #ifdef SETMODE_DEBUG
341 	(void)printf("After compress_mode()\n");
342 	dumpmode(saveset);
343 #endif
344 	return (saveset);
345 }
346 
347 static BITCMD *
addcmd(BITCMD * set,int op,int who,int oparg,u_int mask)348 addcmd(BITCMD *set, int op, int who, int oparg, u_int mask)
349 {
350 	switch (op) {
351 	case '=':
352 		set->cmd = '-';
353 		set->bits = who ? who : STANDARD_BITS;
354 		set++;
355 
356 		op = '+';
357 		/* FALLTHROUGH */
358 	case '+':
359 	case '-':
360 	case 'X':
361 		set->cmd = op;
362 		set->bits = (who ? who : (int)mask) & oparg;
363 		break;
364 
365 	case 'u':
366 	case 'g':
367 	case 'o':
368 		set->cmd = op;
369 		if (who) {
370 			set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) |
371 				    ((who & S_IRGRP) ? CMD2_GBITS : 0) |
372 				    ((who & S_IROTH) ? CMD2_OBITS : 0);
373 			set->bits = (mode_t)~0;
374 		} else {
375 			set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;
376 			set->bits = mask;
377 		}
378 
379 		if (oparg == '+')
380 			set->cmd2 |= CMD2_SET;
381 		else if (oparg == '-')
382 			set->cmd2 |= CMD2_CLR;
383 		else if (oparg == '=')
384 			set->cmd2 |= CMD2_SET|CMD2_CLR;
385 		break;
386 	}
387 	return (set + 1);
388 }
389 
390 #ifdef SETMODE_DEBUG
391 static void
dumpmode(BITCMD * set)392 dumpmode(BITCMD *set)
393 {
394 	for (; set->cmd; ++set)
395 		(void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n",
396 		    set->cmd, set->bits, set->cmd2 ? " cmd2:" : "",
397 		    set->cmd2 & CMD2_CLR ? " CLR" : "",
398 		    set->cmd2 & CMD2_SET ? " SET" : "",
399 		    set->cmd2 & CMD2_UBITS ? " UBITS" : "",
400 		    set->cmd2 & CMD2_GBITS ? " GBITS" : "",
401 		    set->cmd2 & CMD2_OBITS ? " OBITS" : "");
402 }
403 #endif
404 
405 /*
406  * Given an array of bitcmd structures, compress by compacting consecutive
407  * '+', '-' and 'X' commands into at most 3 commands, one of each.  The 'u',
408  * 'g' and 'o' commands continue to be separate.  They could probably be
409  * compacted, but it's not worth the effort.
410  */
411 static void
compress_mode(BITCMD * set)412 compress_mode(BITCMD *set)
413 {
414 	BITCMD *nset;
415 	int setbits, clrbits, Xbits, op;
416 
417 	for (nset = set;;) {
418 		/* Copy over any 'u', 'g' and 'o' commands. */
419 		while ((op = nset->cmd) != '+' && op != '-' && op != 'X') {
420 			*set++ = *nset++;
421 			if (!op)
422 				return;
423 		}
424 
425 		for (setbits = clrbits = Xbits = 0;; nset++) {
426 			if ((op = nset->cmd) == '-') {
427 				clrbits |= nset->bits;
428 				setbits &= ~nset->bits;
429 				Xbits &= ~nset->bits;
430 			} else if (op == '+') {
431 				setbits |= nset->bits;
432 				clrbits &= ~nset->bits;
433 				Xbits &= ~nset->bits;
434 			} else if (op == 'X')
435 				Xbits |= nset->bits & ~setbits;
436 			else
437 				break;
438 		}
439 		if (clrbits) {
440 			set->cmd = '-';
441 			set->cmd2 = 0;
442 			set->bits = clrbits;
443 			set++;
444 		}
445 		if (setbits) {
446 			set->cmd = '+';
447 			set->cmd2 = 0;
448 			set->bits = setbits;
449 			set++;
450 		}
451 		if (Xbits) {
452 			set->cmd = 'X';
453 			set->cmd2 = 0;
454 			set->bits = Xbits;
455 			set++;
456 		}
457 	}
458 }
459