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