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
newmode(char * ms,mode_t new_mode,mode_t umsk,char * file,char * path)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
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)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
abs(mode_t mode,o_mode_t * group_clear_bits,o_mode_t * group_set_bits)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
who(void)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
what(void)540 what(void)
541 {
542 switch (*msp) {
543 case '+':
544 case '-':
545 case '=':
546 return (*msp++);
547 }
548 return (0);
549 }
550