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