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