1 /* $NetBSD: setmode.c,v 1.31 2005/10/01 20:08:01 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1989, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Dave Borman at Cray Research, Inc. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #include <sys/cdefs.h> 36 #if defined(LIBC_SCCS) && !defined(lint) 37 #if 0 38 static char sccsid[] = "@(#)setmode.c 8.2 (Berkeley) 3/25/94"; 39 #else 40 __RCSID("$NetBSD: setmode.c,v 1.31 2005/10/01 20:08:01 christos Exp $"); 41 #endif 42 #endif /* LIBC_SCCS and not lint */ 43 44 #include "namespace.h" 45 #include <sys/types.h> 46 #include <sys/stat.h> 47 48 #include <assert.h> 49 #include <ctype.h> 50 #include <errno.h> 51 #include <signal.h> 52 #include <stdlib.h> 53 #include <limits.h> 54 #include <unistd.h> 55 56 #ifdef SETMODE_DEBUG 57 #include <stdio.h> 58 #endif 59 60 #ifdef __weak_alias 61 __weak_alias(getmode,_getmode) 62 __weak_alias(setmode,_setmode) 63 #endif 64 65 #define SET_LEN 6 /* initial # of bitcmd struct to malloc */ 66 #define SET_LEN_INCR 4 /* # of bitcmd structs to add as needed */ 67 68 typedef struct bitcmd { 69 char cmd; 70 char cmd2; 71 mode_t bits; 72 } BITCMD; 73 74 #define CMD2_CLR 0x01 75 #define CMD2_SET 0x02 76 #define CMD2_GBITS 0x04 77 #define CMD2_OBITS 0x08 78 #define CMD2_UBITS 0x10 79 80 static BITCMD *addcmd __P((BITCMD *, mode_t, mode_t, mode_t, mode_t)); 81 static void compress_mode __P((BITCMD *)); 82 #ifdef SETMODE_DEBUG 83 static void dumpmode __P((BITCMD *)); 84 #endif 85 86 /* 87 * Given the old mode and an array of bitcmd structures, apply the operations 88 * described in the bitcmd structures to the old mode, and return the new mode. 89 * Note that there is no '=' command; a strict assignment is just a '-' (clear 90 * bits) followed by a '+' (set bits). 91 */ 92 mode_t 93 getmode(bbox, omode) 94 const void *bbox; 95 mode_t omode; 96 { 97 const BITCMD *set; 98 mode_t clrval, newmode, value; 99 100 _DIAGASSERT(bbox != NULL); 101 102 set = (const BITCMD *)bbox; 103 newmode = omode; 104 for (value = 0;; set++) 105 switch(set->cmd) { 106 /* 107 * When copying the user, group or other bits around, we "know" 108 * where the bits are in the mode so that we can do shifts to 109 * copy them around. If we don't use shifts, it gets real 110 * grundgy with lots of single bit checks and bit sets. 111 */ 112 case 'u': 113 value = (newmode & S_IRWXU) >> 6; 114 goto common; 115 116 case 'g': 117 value = (newmode & S_IRWXG) >> 3; 118 goto common; 119 120 case 'o': 121 value = newmode & S_IRWXO; 122 common: if (set->cmd2 & CMD2_CLR) { 123 clrval = 124 (set->cmd2 & CMD2_SET) ? S_IRWXO : value; 125 if (set->cmd2 & CMD2_UBITS) 126 newmode &= ~((clrval<<6) & set->bits); 127 if (set->cmd2 & CMD2_GBITS) 128 newmode &= ~((clrval<<3) & set->bits); 129 if (set->cmd2 & CMD2_OBITS) 130 newmode &= ~(clrval & set->bits); 131 } 132 if (set->cmd2 & CMD2_SET) { 133 if (set->cmd2 & CMD2_UBITS) 134 newmode |= (value<<6) & set->bits; 135 if (set->cmd2 & CMD2_GBITS) 136 newmode |= (value<<3) & set->bits; 137 if (set->cmd2 & CMD2_OBITS) 138 newmode |= value & set->bits; 139 } 140 break; 141 142 case '+': 143 newmode |= set->bits; 144 break; 145 146 case '-': 147 newmode &= ~set->bits; 148 break; 149 150 case 'X': 151 if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH)) 152 newmode |= set->bits; 153 break; 154 155 case '\0': 156 default: 157 #ifdef SETMODE_DEBUG 158 (void)printf("getmode:%04o -> %04o\n", omode, newmode); 159 #endif 160 return (newmode); 161 } 162 } 163 164 #define ADDCMD(a, b, c, d) do { \ 165 if (set >= endset) { \ 166 BITCMD *newset; \ 167 setlen += SET_LEN_INCR; \ 168 newset = realloc(saveset, sizeof(BITCMD) * setlen); \ 169 if (newset == NULL) \ 170 goto out; \ 171 set = newset + (set - saveset); \ 172 saveset = newset; \ 173 endset = newset + (setlen - 2); \ 174 } \ 175 set = addcmd(set, (mode_t)(a), (mode_t)(b), (mode_t)(c), (d)); \ 176 } while (/*CONSTCOND*/0) 177 178 #define STANDARD_BITS (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO) 179 180 void * 181 setmode(p) 182 const char *p; 183 { 184 int serrno; 185 char op, *ep; 186 BITCMD *set, *saveset, *endset; 187 sigset_t signset, sigoset; 188 mode_t mask, perm, permXbits, who; 189 long lval; 190 int equalopdone = 0; /* pacify gcc */ 191 int setlen; 192 193 if (!*p) { 194 errno = EINVAL; 195 return NULL; 196 } 197 198 /* 199 * Get a copy of the mask for the permissions that are mask relative. 200 * Flip the bits, we want what's not set. Since it's possible that 201 * the caller is opening files inside a signal handler, protect them 202 * as best we can. 203 */ 204 sigfillset(&signset); 205 (void)sigprocmask(SIG_BLOCK, &signset, &sigoset); 206 (void)umask(mask = umask(0)); 207 mask = ~mask; 208 (void)sigprocmask(SIG_SETMASK, &sigoset, NULL); 209 210 setlen = SET_LEN + 2; 211 212 if ((set = malloc((u_int)(sizeof(BITCMD) * setlen))) == NULL) 213 return (NULL); 214 saveset = set; 215 endset = set + (setlen - 2); 216 217 /* 218 * If an absolute number, get it and return; disallow non-octal digits 219 * or illegal bits. 220 */ 221 if (isdigit((unsigned char)*p)) { 222 errno = 0; 223 lval = strtol(p, &ep, 8); 224 if (*ep) { 225 errno = EINVAL; 226 goto out; 227 } 228 if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) 229 goto out; 230 if (lval & ~(STANDARD_BITS|S_ISTXT)) { 231 errno = EINVAL; 232 goto out; 233 } 234 perm = (mode_t)lval; 235 ADDCMD('=', (STANDARD_BITS|S_ISTXT), perm, mask); 236 set->cmd = 0; 237 return (saveset); 238 } 239 240 /* 241 * Build list of structures to set/clear/copy bits as described by 242 * each clause of the symbolic mode. 243 */ 244 for (;;) { 245 /* First, find out which bits might be modified. */ 246 for (who = 0;; ++p) { 247 switch (*p) { 248 case 'a': 249 who |= STANDARD_BITS; 250 break; 251 case 'u': 252 who |= S_ISUID|S_IRWXU; 253 break; 254 case 'g': 255 who |= S_ISGID|S_IRWXG; 256 break; 257 case 'o': 258 who |= S_IRWXO; 259 break; 260 default: 261 goto getop; 262 } 263 } 264 265 getop: if ((op = *p++) != '+' && op != '-' && op != '=') { 266 errno = EINVAL; 267 goto out; 268 } 269 if (op == '=') 270 equalopdone = 0; 271 272 who &= ~S_ISTXT; 273 for (perm = 0, permXbits = 0;; ++p) { 274 switch (*p) { 275 case 'r': 276 perm |= S_IRUSR|S_IRGRP|S_IROTH; 277 break; 278 case 's': 279 /* 280 * If specific bits where requested and 281 * only "other" bits ignore set-id. 282 */ 283 if (who == 0 || (who & ~S_IRWXO)) 284 perm |= S_ISUID|S_ISGID; 285 break; 286 case 't': 287 /* 288 * If specific bits where requested and 289 * only "other" bits ignore set-id. 290 */ 291 if (who == 0 || (who & ~S_IRWXO)) { 292 who |= S_ISTXT; 293 perm |= S_ISTXT; 294 } 295 break; 296 case 'w': 297 perm |= S_IWUSR|S_IWGRP|S_IWOTH; 298 break; 299 case 'X': 300 permXbits = S_IXUSR|S_IXGRP|S_IXOTH; 301 break; 302 case 'x': 303 perm |= S_IXUSR|S_IXGRP|S_IXOTH; 304 break; 305 case 'u': 306 case 'g': 307 case 'o': 308 /* 309 * When ever we hit 'u', 'g', or 'o', we have 310 * to flush out any partial mode that we have, 311 * and then do the copying of the mode bits. 312 */ 313 if (perm) { 314 ADDCMD(op, who, perm, mask); 315 perm = 0; 316 } 317 if (op == '=') 318 equalopdone = 1; 319 if (op == '+' && permXbits) { 320 ADDCMD('X', who, permXbits, mask); 321 permXbits = 0; 322 } 323 ADDCMD(*p, who, op, mask); 324 break; 325 326 default: 327 /* 328 * Add any permissions that we haven't already 329 * done. 330 */ 331 if (perm || (op == '=' && !equalopdone)) { 332 if (op == '=') 333 equalopdone = 1; 334 ADDCMD(op, who, perm, mask); 335 perm = 0; 336 } 337 if (permXbits) { 338 ADDCMD('X', who, permXbits, mask); 339 permXbits = 0; 340 } 341 goto apply; 342 } 343 } 344 345 apply: if (!*p) 346 break; 347 if (*p != ',') 348 goto getop; 349 ++p; 350 } 351 set->cmd = 0; 352 #ifdef SETMODE_DEBUG 353 (void)printf("Before compress_mode()\n"); 354 dumpmode(saveset); 355 #endif 356 compress_mode(saveset); 357 #ifdef SETMODE_DEBUG 358 (void)printf("After compress_mode()\n"); 359 dumpmode(saveset); 360 #endif 361 return (saveset); 362 out: 363 serrno = errno; 364 free(saveset); 365 errno = serrno; 366 return NULL; 367 } 368 369 static BITCMD * 370 addcmd(set, op, who, oparg, mask) 371 BITCMD *set; 372 mode_t oparg, who, op, mask; 373 { 374 375 _DIAGASSERT(set != NULL); 376 377 switch (op) { 378 case '=': 379 set->cmd = '-'; 380 set->bits = who ? who : STANDARD_BITS; 381 set++; 382 383 op = '+'; 384 /* FALLTHROUGH */ 385 case '+': 386 case '-': 387 case 'X': 388 set->cmd = op; 389 set->bits = (who ? who : mask) & oparg; 390 break; 391 392 case 'u': 393 case 'g': 394 case 'o': 395 set->cmd = op; 396 if (who) { 397 set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) | 398 ((who & S_IRGRP) ? CMD2_GBITS : 0) | 399 ((who & S_IROTH) ? CMD2_OBITS : 0); 400 set->bits = (mode_t)~0; 401 } else { 402 set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS; 403 set->bits = mask; 404 } 405 406 if (oparg == '+') 407 set->cmd2 |= CMD2_SET; 408 else if (oparg == '-') 409 set->cmd2 |= CMD2_CLR; 410 else if (oparg == '=') 411 set->cmd2 |= CMD2_SET|CMD2_CLR; 412 break; 413 } 414 return (set + 1); 415 } 416 417 #ifdef SETMODE_DEBUG 418 static void 419 dumpmode(set) 420 BITCMD *set; 421 { 422 423 _DIAGASSERT(set != NULL); 424 425 for (; set->cmd; ++set) 426 (void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n", 427 set->cmd, set->bits, set->cmd2 ? " cmd2:" : "", 428 set->cmd2 & CMD2_CLR ? " CLR" : "", 429 set->cmd2 & CMD2_SET ? " SET" : "", 430 set->cmd2 & CMD2_UBITS ? " UBITS" : "", 431 set->cmd2 & CMD2_GBITS ? " GBITS" : "", 432 set->cmd2 & CMD2_OBITS ? " OBITS" : ""); 433 } 434 #endif 435 436 /* 437 * Given an array of bitcmd structures, compress by compacting consecutive 438 * '+', '-' and 'X' commands into at most 3 commands, one of each. The 'u', 439 * 'g' and 'o' commands continue to be separate. They could probably be 440 * compacted, but it's not worth the effort. 441 */ 442 static void 443 compress_mode(set) 444 BITCMD *set; 445 { 446 BITCMD *nset; 447 int setbits, clrbits, Xbits, op; 448 449 _DIAGASSERT(set != NULL); 450 451 for (nset = set;;) { 452 /* Copy over any 'u', 'g' and 'o' commands. */ 453 while ((op = nset->cmd) != '+' && op != '-' && op != 'X') { 454 *set++ = *nset++; 455 if (!op) 456 return; 457 } 458 459 for (setbits = clrbits = Xbits = 0;; nset++) { 460 if ((op = nset->cmd) == '-') { 461 clrbits |= nset->bits; 462 setbits &= ~nset->bits; 463 Xbits &= ~nset->bits; 464 } else if (op == '+') { 465 setbits |= nset->bits; 466 clrbits &= ~nset->bits; 467 Xbits &= ~nset->bits; 468 } else if (op == 'X') 469 Xbits |= nset->bits & ~setbits; 470 else 471 break; 472 } 473 if (clrbits) { 474 set->cmd = '-'; 475 set->cmd2 = 0; 476 set->bits = clrbits; 477 set++; 478 } 479 if (setbits) { 480 set->cmd = '+'; 481 set->cmd2 = 0; 482 set->bits = setbits; 483 set++; 484 } 485 if (Xbits) { 486 set->cmd = 'X'; 487 set->cmd2 = 0; 488 set->bits = Xbits; 489 set++; 490 } 491 } 492 } 493