/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * Use of this object by a utility (so far chmod, mkdir and mkfifo use * it) requires that the utility implement an error-processing routine * named errmsg(), with a prototype as specified below. * * This is necessary because the mode-parsing code here makes use of such * a routine, located in chmod.c. The error-reporting style of the * utilities sharing this code differs enough that it is difficult to * implement a common version of this routine to be used by all. */ /* * Note that many convolutions are necessary * due to the re-use of bits between locking * and setgid */ #include #include #include #include #include #include #include /* strerror() */ #include #define USER 05700 /* user's bits */ #define GROUP 02070 /* group's bits */ #define OTHER 00007 /* other's bits */ #define ALL 07777 /* all */ #define READ 00444 /* read permit */ #define WRITE 00222 /* write permit */ #define EXEC 00111 /* exec permit */ #define SETID 06000 /* set[ug]id */ #define LOCK 02000 /* lock permit */ #define STICKY 01000 /* sticky bit */ #define GROUP_RWX (GROUP & (READ | WRITE | EXEC)) #define WHO_EMPTY 0 static char *msp; extern void errmsg(int severity, int code, char *format, ...); static int what(void); static mode_t abs(mode_t mode, o_mode_t *group_clear_bits, o_mode_t *group_set_bits), who(void); mode_t newmode_common(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path, o_mode_t *group_clear_bits, o_mode_t *group_set_bits); /* * Wrapper for newmode_common. This function is called by mkdir and * mkfifo. */ mode_t newmode(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path) { o_mode_t tmp1, tmp2; return (newmode_common(ms, new_mode, umsk, file, path, &tmp1, &tmp2)); } /* * We are parsing a comma-separated list of mode expressions of the form: * * [] [] */ /* ARGSUSED */ mode_t newmode_common(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path, o_mode_t *group_clear_bits, o_mode_t *group_set_bits) { /* * new_mode contains the mode value constructed by parsing the * expression pointed to by ms * old_mode contains the mode provided by the caller * oper contains +|-|= information * perms_msk contains rwx(slt) information * umsk contains the umask value to be assumed. * who_empty is non-zero if the clause did not appear. * who_msk contains USER|GROUP|OTHER information */ int oper; /* */ int lcheck; int scheck; int xcheck; int goon; int operand_empty = 0; int who_empty; mode_t who_msk; mode_t perms_msk; mode_t old_mode = new_mode; /* save original mode */ mode_t grp_change; msp = ms; *group_clear_bits = 0; *group_set_bits = 0; if (isdigit(*msp)) return (abs(old_mode, group_clear_bits, group_set_bits)); do { /* * When is empty, and == `=`, the umask is * obeyed. So we need to make note of it here, for use * later. */ if ((who_msk = who()) == WHO_EMPTY) { who_empty = 1; who_msk = ALL; } else { who_empty = 0; } while (oper = what()) { /* * this section processes permissions */ operand_empty++; perms_msk = 0; goon = 0; lcheck = scheck = xcheck = 0; switch (*msp) { case 'u': perms_msk = (new_mode & USER) >> 6; goto dup; case 'g': perms_msk = (new_mode & GROUP) >> 3; goto dup; case 'o': perms_msk = (new_mode & OTHER); dup: perms_msk &= (READ|WRITE|EXEC); perms_msk |= (perms_msk << 3) | (perms_msk << 6); msp++; goon = 1; } while (goon == 0) { switch (*msp++) { case 'r': perms_msk |= READ; continue; case 'w': perms_msk |= WRITE; continue; case 'x': perms_msk |= EXEC; xcheck = 1; continue; case 'X': if (((old_mode & S_IFMT) == S_IFDIR) || (old_mode & EXEC)) { perms_msk |= EXEC; xcheck = 1; } continue; case 'l': perms_msk |= LOCK; who_msk |= LOCK; lcheck = 1; continue; case 's': perms_msk |= SETID; scheck = 1; continue; case 't': perms_msk |= STICKY; continue; default: msp--; goon = 1; } } perms_msk &= who_msk; switch (oper) { case '+': if (who_empty) { perms_msk &= ~umsk; } /* is group execution requested? */ if (xcheck == 1 && (perms_msk & GROUP & EXEC) == (GROUP & EXEC)) { /* not locking, too! */ if (lcheck == 1 && !S_ISDIR(new_mode)) { errmsg(1, 3, gettext("Group execution " "and locking not permitted " "together\n")); } /* * not if the file is already * lockable. */ if (((new_mode & GROUP & (LOCK | EXEC)) == LOCK) && !S_ISDIR(new_mode)) { errmsg(2, 0, gettext("%s: Group " "execution not permitted " "on a lockable file\n"), path); return (old_mode); } } /* is setgid on execution requested? */ if (scheck == 1 && (perms_msk & GROUP & SETID) == (GROUP & SETID)) { /* not locking, too! */ if (lcheck == 1 && ((perms_msk & GROUP & EXEC) == (GROUP & EXEC)) && !S_ISDIR(new_mode)) { errmsg(1, 4, gettext("Set-group-ID and " "locking not permitted " "together\n")); } /* * not if the file is already * lockable */ if (((new_mode & GROUP & (LOCK | EXEC)) == LOCK) && !S_ISDIR(new_mode)) { errmsg(2, 0, gettext("%s: Set-group-ID " "not permitted on a " "lockable file\n"), path); return (old_mode); } } /* is setid on execution requested? */ if ((scheck == 1) && ((new_mode & S_IFMT) != S_IFDIR)) { /* * the corresponding execution must * be requested or already set */ if (((new_mode | perms_msk) & who_msk & EXEC & (USER | GROUP)) != (who_msk & EXEC & (USER | GROUP))) { errmsg(2, 0, gettext("%s: Execute " "permission required " "for set-ID on " "execution \n"), path); return (old_mode); } } /* is locking requested? */ if (lcheck == 1) { /* * not if the file has group execution * set. * NOTE: this also covers files with * setgid */ if ((new_mode & GROUP & EXEC) == (GROUP & EXEC) && !S_ISDIR(new_mode)) { errmsg(2, 0, gettext("%s: Locking not " "permitted on " "a group executable " "file\n"), path); return (old_mode); } } if ((grp_change = (perms_msk & GROUP_RWX) >> 3) != 0) { *group_clear_bits &= ~grp_change; *group_set_bits |= grp_change; } /* create new mode */ new_mode |= perms_msk; break; case '-': if (who_empty) { perms_msk &= ~umsk; } /* don't turn off locking, unless it's on */ if (lcheck == 1 && scheck == 0 && (new_mode & GROUP & (LOCK | EXEC)) != LOCK) { perms_msk &= ~LOCK; } /* don't turn off setgid, unless it's on */ if (scheck == 1 && ((new_mode & S_IFMT) != S_IFDIR) && lcheck == 0 && (new_mode & GROUP & (LOCK | EXEC)) == LOCK) { perms_msk &= ~(GROUP & SETID); } /* * if execution is being turned off and the * corresponding setid is not, turn setid off, * too & warn the user */ if (xcheck == 1 && scheck == 0 && ((who_msk & GROUP) == GROUP || (who_msk & USER) == USER) && (new_mode & who_msk & (SETID | EXEC)) == (who_msk & (SETID | EXEC)) && !S_ISDIR(new_mode)) { errmsg(2, 0, gettext("%s: Corresponding set-ID " "also disabled on file since " "set-ID requires execute " "permission\n"), path); if ((perms_msk & USER & SETID) != (USER & SETID) && (new_mode & USER & (SETID | EXEC)) == (who_msk & USER & (SETID | EXEC))) { perms_msk |= USER & SETID; } if ((perms_msk & GROUP & SETID) != (GROUP & SETID) && (new_mode & GROUP & (SETID | EXEC)) == (who_msk & GROUP & (SETID | EXEC))) { perms_msk |= GROUP & SETID; } } if ((grp_change = (perms_msk & GROUP_RWX) >> 3) != 0) { *group_clear_bits |= grp_change; *group_set_bits &= ~grp_change; } /* create new mode */ new_mode &= ~perms_msk; break; case '=': if (who_empty) { perms_msk &= ~umsk; } /* is locking requested? */ if (lcheck == 1) { /* not group execution, too! */ if ((perms_msk & GROUP & EXEC) == (GROUP & EXEC) && !S_ISDIR(new_mode)) { errmsg(1, 3, gettext("Group execution " "and locking not " "permitted together\n")); } /* * if the file has group execution set, * turn it off! */ if ((who_msk & GROUP) != GROUP) { new_mode &= ~(GROUP & EXEC); } } /* * is setid on execution requested? the * corresponding execution must be requested, * too! */ if (scheck == 1 && (perms_msk & EXEC & (USER | GROUP)) != (who_msk & EXEC & (USER | GROUP)) && !S_ISDIR(new_mode)) { errmsg(1, 2, gettext("Execute permission " "required for set-ID on " "execution\n")); } /* * The ISGID bit on directories will not be * changed when the mode argument is a string * with "=". */ if ((old_mode & S_IFMT) == S_IFDIR) perms_msk = (perms_msk & ~S_ISGID) | (old_mode & S_ISGID); /* * create new mode: * clear the who_msk bits * set the perms_mks bits (which have * been trimmed to fit the who_msk. */ if ((grp_change = (perms_msk & GROUP_RWX) >> 3) != 0) { *group_clear_bits = GROUP_RWX >> 3; *group_set_bits = grp_change; } new_mode &= ~who_msk; new_mode |= perms_msk; break; } } } while (*msp++ == ','); if (*--msp || operand_empty == 0) { errmsg(1, 5, gettext("invalid mode\n")); } return (new_mode); } mode_t abs(mode_t mode, o_mode_t *group_clear_bits, o_mode_t *group_set_bits) { int c; mode_t i; for (i = 0; (c = *msp) >= '0' && c <= '7'; msp++) i = (mode_t)((i << 3) + (c - '0')); if (*msp) errmsg(1, 6, gettext("invalid mode\n")); /* * The ISGID bit on directories will not be changed when the mode argument is * octal numeric. Only "g+s" and "g-s" arguments can change ISGID bit when * applied to directories. */ *group_clear_bits = GROUP_RWX >> 3; *group_set_bits = (i & GROUP_RWX) >> 3; if ((mode & S_IFMT) == S_IFDIR) return ((i & ~S_ISGID) | (mode & S_ISGID)); return (i); } static mode_t who(void) { mode_t m; m = WHO_EMPTY; for (; ; msp++) { switch (*msp) { case 'u': m |= USER; continue; case 'g': m |= GROUP; continue; case 'o': m |= OTHER; continue; case 'a': m |= ALL; continue; default: return (m); } } } static int what(void) { switch (*msp) { case '+': case '-': case '=': return (*msp++); } return (0); }