xref: /illumos-gate/usr/src/cmd/chmod/common.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved						*/
29 
30 /*
31  * Portions of this source code were derived from Berkeley 4.3 BSD
32  * under license from the Regents of the University of California.
33  */
34 
35 /*
36  * Use of this object by a utility (so far chmod, mkdir and mkfifo use
37  * it) requires that the utility implement an error-processing routine
38  * named errmsg(), with a prototype as specified below.
39  *
40  * This is necessary because the mode-parsing code here makes use of such
41  * a routine, located in chmod.c.  The error-reporting style of the
42  * utilities sharing this code differs enough that it is difficult to
43  * implement a common version of this routine to be used by all.
44  */
45 
46 /*
47  *  Note that many convolutions are necessary
48  *  due to the re-use of bits between locking
49  *  and setgid
50  */
51 
52 #include <ctype.h>
53 #include <stdio.h>
54 #include <sys/types.h>
55 #include <sys/stat.h>
56 #include <dirent.h>
57 #include <locale.h>
58 #include <string.h>	/* strerror() */
59 #include <stdarg.h>
60 
61 #define	USER	05700	/* user's bits */
62 #define	GROUP	02070	/* group's bits */
63 #define	OTHER	00007	/* other's bits */
64 #define	ALL	07777	/* all */
65 
66 #define	READ	00444	/* read permit */
67 #define	WRITE	00222	/* write permit */
68 #define	EXEC	00111	/* exec permit */
69 #define	SETID	06000	/* set[ug]id */
70 #define	LOCK	02000	/* lock permit */
71 #define	STICKY	01000	/* sticky bit */
72 
73 #define	GROUP_RWX	(GROUP & (READ | WRITE | EXEC))
74 
75 #define	WHO_EMPTY 0
76 
77 static char *msp;
78 
79 extern void
80 errmsg(int severity, int code, char *format, ...);
81 
82 static int
83 what(void);
84 
85 static mode_t
86 abs(mode_t mode, o_mode_t *group_clear_bits, o_mode_t *group_set_bits),
87 who(void);
88 
89 mode_t
90 newmode_common(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path,
91     o_mode_t *group_clear_bits, o_mode_t *group_set_bits);
92 
93 /*
94  * Wrapper for newmode_common.  This function is called by mkdir and
95  * mkfifo.
96  */
97 mode_t
98 newmode(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path)
99 {
100 	o_mode_t tmp1, tmp2;
101 
102 	return (newmode_common(ms, new_mode, umsk, file, path, &tmp1, &tmp2));
103 }
104 
105 /*
106  *  We are parsing a comma-separated list of mode expressions of the form:
107  *
108  *			 [<who>] <op> [<perms>]
109  */
110 
111 /* ARGSUSED */
112 mode_t
113 newmode_common(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path,
114     o_mode_t *group_clear_bits, o_mode_t *group_set_bits)
115 {
116 	/*
117 	 * new_mode  contains the mode value constructed by parsing the
118 	 *			 expression pointed to by ms
119 	 * old_mode  contains the mode provided by the caller
120 	 * oper		 contains +|-|= information
121 	 * perms_msk contains rwx(slt) information
122 	 * umsk		 contains the umask value to be assumed.
123 	 * who_empty is non-zero if the <who> clause did not appear.
124 	 * who_msk   contains USER|GROUP|OTHER information
125 	 */
126 
127 	int oper;	/* <op> */
128 	int lcheck;
129 	int scheck;
130 	int xcheck;
131 	int goon;
132 
133 	int operand_empty = 0;
134 	int who_empty;
135 
136 	mode_t who_msk;
137 	mode_t perms_msk;
138 	mode_t old_mode = new_mode;	/* save original mode */
139 	mode_t grp_change;
140 
141 	msp = ms;
142 
143 	*group_clear_bits = 0;
144 	*group_set_bits = 0;
145 
146 	if (isdigit(*msp))
147 		return (abs(old_mode, group_clear_bits, group_set_bits));
148 
149 	do {
150 		/*
151 		 * When <who> is empty, and <oper> == `=`, the umask is
152 		 * obeyed.  So we need to make note of it here, for use
153 		 * later.
154 		 */
155 
156 		if ((who_msk = who()) == WHO_EMPTY) {
157 			who_empty = 1;
158 			who_msk = ALL;
159 		} else {
160 			who_empty = 0;
161 		}
162 
163 		while (oper = what()) {
164 			/*
165 			 *  this section processes permissions
166 			 */
167 
168 			operand_empty++;
169 			perms_msk = 0;
170 			goon = 0;
171 			lcheck = scheck = xcheck = 0;
172 
173 			switch (*msp) {
174 			case 'u':
175 				perms_msk = (new_mode & USER) >> 6;
176 				goto dup;
177 			case 'g':
178 				perms_msk = (new_mode & GROUP) >> 3;
179 				goto dup;
180 			case 'o':
181 				perms_msk = (new_mode & OTHER);
182 			dup:
183 				perms_msk &= (READ|WRITE|EXEC);
184 				perms_msk |= (perms_msk << 3) |
185 				    (perms_msk << 6);
186 				msp++;
187 				goon = 1;
188 			}
189 
190 			while (goon == 0) {
191 				switch (*msp++) {
192 				case 'r':
193 					perms_msk |= READ;
194 					continue;
195 				case 'w':
196 					perms_msk |= WRITE;
197 					continue;
198 				case 'x':
199 					perms_msk |= EXEC;
200 					xcheck = 1;
201 					continue;
202 				case 'X':
203 					if (((old_mode & S_IFMT) == S_IFDIR) ||
204 					    (old_mode & EXEC)) {
205 						perms_msk |= EXEC;
206 						xcheck = 1;
207 					}
208 					continue;
209 				case 'l':
210 					perms_msk |= LOCK;
211 					who_msk |= LOCK;
212 					lcheck = 1;
213 					continue;
214 				case 's':
215 					perms_msk |= SETID;
216 					scheck = 1;
217 					continue;
218 				case 't':
219 					perms_msk |= STICKY;
220 					continue;
221 				default:
222 					msp--;
223 					goon = 1;
224 				}
225 			}
226 
227 			perms_msk &= who_msk;
228 
229 			switch (oper) {
230 			case '+':
231 				if (who_empty) {
232 					perms_msk &= ~umsk;
233 				}
234 
235 
236 				/* is group execution requested? */
237 				if (xcheck == 1 &&
238 				    (perms_msk & GROUP & EXEC) ==
239 				    (GROUP & EXEC)) {
240 					/* not locking, too! */
241 					if (lcheck == 1 && !S_ISDIR(new_mode)) {
242 						errmsg(1, 3,
243 						    gettext("Group execution "
244 						    "and locking not permitted "
245 						    "together\n"));
246 					}
247 
248 					/*
249 					 * not if the file is already
250 					 * lockable.
251 					 */
252 					if (((new_mode & GROUP &
253 					    (LOCK | EXEC)) == LOCK) &&
254 					    !S_ISDIR(new_mode)) {
255 						errmsg(2, 0,
256 						    gettext("%s: Group "
257 						    "execution not permitted "
258 						    "on a lockable file\n"),
259 						    path);
260 						return (old_mode);
261 					}
262 				}
263 
264 				/* is setgid on execution requested? */
265 				if (scheck == 1 && (perms_msk & GROUP & SETID)
266 				    == (GROUP & SETID)) {
267 					/* not locking, too! */
268 					if (lcheck == 1 &&
269 					    ((perms_msk & GROUP & EXEC) ==
270 					    (GROUP & EXEC)) &&
271 					    !S_ISDIR(new_mode)) {
272 						errmsg(1, 4,
273 						    gettext("Set-group-ID and "
274 						    "locking not permitted "
275 						    "together\n"));
276 					}
277 
278 					/*
279 					 * not if the file is already
280 					 * lockable
281 					 */
282 
283 					if (((new_mode & GROUP &
284 					    (LOCK | EXEC)) == LOCK) &&
285 					    !S_ISDIR(new_mode)) {
286 						errmsg(2, 0,
287 						    gettext("%s: Set-group-ID "
288 						    "not permitted on a "
289 						    "lockable file\n"), path);
290 						return (old_mode);
291 					}
292 				}
293 
294 				/* is setid on execution requested? */
295 				if ((scheck == 1) &&
296 				    ((new_mode & S_IFMT) != S_IFDIR)) {
297 					/*
298 					 * the corresponding execution must
299 					 * be requested or already set
300 					 */
301 					if (((new_mode | perms_msk) &
302 					    who_msk & EXEC & (USER | GROUP)) !=
303 					    (who_msk & EXEC & (USER | GROUP))) {
304 						errmsg(2, 0,
305 						    gettext("%s: Execute "
306 						    "permission required "
307 						    "for set-ID on "
308 						    "execution \n"),
309 						    path);
310 						return (old_mode);
311 					}
312 				}
313 
314 				/* is locking requested? */
315 				if (lcheck == 1) {
316 					/*
317 					 * not if the file has group execution
318 					 * set.
319 					 * NOTE: this also covers files with
320 					 * setgid
321 					 */
322 					if ((new_mode & GROUP & EXEC) ==
323 					    (GROUP & EXEC) &&
324 					    !S_ISDIR(new_mode)) {
325 						errmsg(2, 0,
326 						    gettext("%s: Locking not "
327 						    "permitted on "
328 						    "a group executable "
329 						    "file\n"),
330 						    path);
331 						return (old_mode);
332 					}
333 				}
334 
335 				if ((grp_change = (perms_msk & GROUP_RWX) >> 3)
336 				    != 0) {
337 					*group_clear_bits &= ~grp_change;
338 					*group_set_bits |= grp_change;
339 				}
340 
341 				/* create new mode */
342 				new_mode |= perms_msk;
343 				break;
344 
345 			case '-':
346 				if (who_empty) {
347 					perms_msk &= ~umsk;
348 				}
349 
350 				/* don't turn off locking, unless it's on */
351 				if (lcheck == 1 && scheck == 0 &&
352 				    (new_mode & GROUP & (LOCK | EXEC)) !=
353 				    LOCK) {
354 					perms_msk &= ~LOCK;
355 				}
356 
357 				/* don't turn off setgid, unless it's on */
358 				if (scheck == 1 &&
359 				    ((new_mode & S_IFMT) != S_IFDIR) &&
360 				    lcheck == 0 &&
361 				    (new_mode & GROUP & (LOCK | EXEC)) ==
362 				    LOCK) {
363 					perms_msk &= ~(GROUP & SETID);
364 				}
365 
366 				/*
367 				 * if execution is being turned off and the
368 				 * corresponding setid is not, turn setid off,
369 				 * too & warn the user
370 				 */
371 				if (xcheck == 1 && scheck == 0 &&
372 				    ((who_msk & GROUP) == GROUP ||
373 				    (who_msk & USER) == USER) &&
374 				    (new_mode & who_msk & (SETID | EXEC)) ==
375 				    (who_msk & (SETID | EXEC)) &&
376 				    !S_ISDIR(new_mode)) {
377 					errmsg(2, 0,
378 					    gettext("%s: Corresponding set-ID "
379 					    "also disabled on file since "
380 					    "set-ID requires execute "
381 					    "permission\n"),
382 					    path);
383 
384 					if ((perms_msk & USER & SETID) !=
385 					    (USER & SETID) && (new_mode &
386 					    USER & (SETID | EXEC)) ==
387 					    (who_msk & USER &
388 					    (SETID | EXEC))) {
389 						perms_msk |= USER & SETID;
390 					}
391 					if ((perms_msk & GROUP & SETID) !=
392 					    (GROUP & SETID) &&
393 					    (new_mode & GROUP &
394 					    (SETID | EXEC)) ==
395 					    (who_msk & GROUP &
396 					    (SETID | EXEC))) {
397 						perms_msk |= GROUP & SETID;
398 					}
399 				}
400 
401 				if ((grp_change = (perms_msk & GROUP_RWX) >> 3)
402 				    != 0) {
403 					*group_clear_bits |= grp_change;
404 					*group_set_bits &= ~grp_change;
405 				}
406 
407 				/* create new mode */
408 				new_mode &= ~perms_msk;
409 				break;
410 
411 			case '=':
412 				if (who_empty) {
413 					perms_msk &= ~umsk;
414 				}
415 				/* is locking requested? */
416 				if (lcheck == 1) {
417 					/* not group execution, too! */
418 					if ((perms_msk & GROUP & EXEC) ==
419 					    (GROUP & EXEC) &&
420 					    !S_ISDIR(new_mode)) {
421 						errmsg(1, 3,
422 						    gettext("Group execution "
423 						    "and locking not "
424 						    "permitted together\n"));
425 					}
426 
427 					/*
428 					 * if the file has group execution set,
429 					 * turn it off!
430 					 */
431 					if ((who_msk & GROUP) != GROUP) {
432 						new_mode &= ~(GROUP & EXEC);
433 					}
434 				}
435 
436 				/*
437 				 * is setid on execution requested? the
438 				 * corresponding execution must be requested,
439 				 * too!
440 				 */
441 				if (scheck == 1 &&
442 				    (perms_msk & EXEC & (USER | GROUP)) !=
443 				    (who_msk & EXEC & (USER | GROUP)) &&
444 					!S_ISDIR(new_mode)) {
445 					errmsg(1, 2,
446 					    gettext("Execute permission "
447 					    "required for set-ID on "
448 					    "execution\n"));
449 				}
450 
451 				/*
452 				 * The ISGID bit on directories will not be
453 				 * changed when the mode argument is a string
454 				 * with "=".
455 				 */
456 				if ((old_mode & S_IFMT) == S_IFDIR)
457 					perms_msk = (perms_msk &
458 					    ~S_ISGID) | (old_mode & S_ISGID);
459 
460 				/*
461 				 * create new mode:
462 				 *   clear the who_msk bits
463 				 *   set the perms_mks bits (which have
464 				 *   been trimmed to fit the who_msk.
465 				 */
466 
467 				if ((grp_change = (perms_msk & GROUP_RWX) >> 3)
468 				    != 0) {
469 					*group_clear_bits = GROUP_RWX >> 3;
470 					*group_set_bits = grp_change;
471 				}
472 
473 				new_mode &= ~who_msk;
474 				new_mode |= perms_msk;
475 				break;
476 			}
477 		}
478 	} while (*msp++ == ',');
479 
480 	if (*--msp || operand_empty == 0) {
481 		errmsg(1, 5, gettext("invalid mode\n"));
482 	}
483 
484 	return (new_mode);
485 }
486 
487 mode_t
488 abs(mode_t mode, o_mode_t *group_clear_bits, o_mode_t *group_set_bits)
489 {
490 	int c;
491 	mode_t i;
492 
493 	for (i = 0; (c = *msp) >= '0' && c <= '7'; msp++)
494 		i = (mode_t)((i << 3) + (c - '0'));
495 	if (*msp)
496 		errmsg(1, 6, gettext("invalid mode\n"));
497 
498 /*
499  * The ISGID bit on directories will not be changed when the mode argument is
500  * octal numeric. Only "g+s" and "g-s" arguments can change ISGID bit when
501  * applied to directories.
502  */
503 	*group_clear_bits = GROUP_RWX >> 3;
504 	*group_set_bits = (i & GROUP_RWX) >> 3;
505 	if ((mode & S_IFMT) == S_IFDIR)
506 		return ((i & ~S_ISGID) | (mode & S_ISGID));
507 	return (i);
508 }
509 
510 static mode_t
511 who(void)
512 {
513 	mode_t m;
514 
515 	m = WHO_EMPTY;
516 
517 	for (; ; msp++) {
518 		switch (*msp) {
519 		case 'u':
520 			m |= USER;
521 			continue;
522 		case 'g':
523 			m |= GROUP;
524 			continue;
525 		case 'o':
526 			m |= OTHER;
527 			continue;
528 		case 'a':
529 			m |= ALL;
530 			continue;
531 		default:
532 			return (m);
533 		}
534 	}
535 }
536 
537 static int
538 what(void)
539 {
540 	switch (*msp) {
541 	case '+':
542 	case '-':
543 	case '=':
544 		return (*msp++);
545 	}
546 	return (0);
547 }
548