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