Home | History | Annotate | Download | only in chmod
      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