xref: /freebsd/lib/libugidfw/ugidfw.c (revision 8d20be1e22095c27faf8fe8b2f0d089739cc742e)
1 /*-
2  * Copyright (c) 2002-2005 Networks Associates Technology, Inc.
3  * All rights reserved.
4  *
5  * This software was developed for the FreeBSD Project by Network Associates
6  * Laboratories, the Security Research Division of Network Associates, Inc.
7  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
8  * DARPA CHATS research program.
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  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  *
31  * $FreeBSD$
32  */
33 #include <sys/param.h>
34 #include <sys/errno.h>
35 #include <sys/time.h>
36 #include <sys/sysctl.h>
37 #include <sys/ucred.h>
38 #include <sys/mount.h>
39 
40 #include <security/mac_bsdextended/mac_bsdextended.h>
41 
42 #include <grp.h>
43 #include <pwd.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 
48 #include "ugidfw.h"
49 
50 /*
51  * Text format for rules: rules contain subject and object elements, mode.
52  * The total form is "subject [s_element] object [o_element] mode [mode]".
53  * At least * one of a uid or gid entry must be present; both may also be
54  * present.
55  */
56 
57 #define	MIB	"security.mac.bsdextended"
58 
59 int
60 bsde_rule_to_string(struct mac_bsdextended_rule *rule, char *buf, size_t buflen)
61 {
62 	struct group *grp;
63 	struct passwd *pwd;
64 	struct statfs *mntbuf;
65 	char *cur, type[sizeof(rule->mbr_object.mbo_type) * CHAR_BIT + 1];
66 	size_t left, len;
67 	int anymode, unknownmode, truncated, numfs, i, notdone;
68 
69 	cur = buf;
70 	left = buflen;
71 	truncated = 0;
72 
73 	len = snprintf(cur, left, "subject ");
74 	if (len < 0 || len > left)
75 		goto truncated;
76 	left -= len;
77 	cur += len;
78 	if (rule->mbr_subject.mbs_flags) {
79 		if (rule->mbr_subject.mbs_neg == MBS_ALL_FLAGS) {
80 			len = snprintf(cur, left, "not ");
81 			if (len < 0 || len > left)
82 				goto truncated;
83 			left -= len;
84 			cur += len;
85 			notdone = 1;
86 		} else {
87 			notdone = 0;
88 		}
89 
90 		if (!notdone && (rule->mbr_subject.mbs_neg & MBO_UID_DEFINED)) {
91 			len = snprintf(cur, left, "! ");
92 			if (len < 0 || len > left)
93 				goto truncated;
94 			left -= len;
95 			cur += len;
96 		}
97 		if (rule->mbr_subject.mbs_flags & MBO_UID_DEFINED) {
98 			pwd = getpwuid(rule->mbr_subject.mbs_uid_min);
99 			if (pwd != NULL) {
100 				len = snprintf(cur, left, "uid %s",
101 				    pwd->pw_name);
102 				if (len < 0 || len > left)
103 					goto truncated;
104 				left -= len;
105 				cur += len;
106 			} else {
107 				len = snprintf(cur, left, "uid %u",
108 				    rule->mbr_subject.mbs_uid_min);
109 				if (len < 0 || len > left)
110 					goto truncated;
111 				left -= len;
112 				cur += len;
113 			}
114 			if (rule->mbr_subject.mbs_uid_min !=
115 			    rule->mbr_subject.mbs_uid_max) {
116 				pwd = getpwuid(rule->mbr_subject.mbs_uid_max);
117 				if (pwd != NULL) {
118 					len = snprintf(cur, left, ":%s ",
119 					    pwd->pw_name);
120 					if (len < 0 || len > left)
121 						goto truncated;
122 					left -= len;
123 					cur += len;
124 				} else {
125 					len = snprintf(cur, left, ":%u ",
126 					    rule->mbr_subject.mbs_uid_max);
127 					if (len < 0 || len > left)
128 						goto truncated;
129 					left -= len;
130 					cur += len;
131 				}
132 			} else {
133 				len = snprintf(cur, left, " ");
134 				if (len < 0 || len > left)
135 					goto truncated;
136 				left -= len;
137 				cur += len;
138 			}
139 		}
140 		if (!notdone && (rule->mbr_subject.mbs_neg & MBO_GID_DEFINED)) {
141 			len = snprintf(cur, left, "! ");
142 			if (len < 0 || len > left)
143 				goto truncated;
144 			left -= len;
145 			cur += len;
146 		}
147 		if (rule->mbr_subject.mbs_flags & MBO_GID_DEFINED) {
148 			grp = getgrgid(rule->mbr_subject.mbs_gid_min);
149 			if (grp != NULL) {
150 				len = snprintf(cur, left, "gid %s",
151 				    grp->gr_name);
152 				if (len < 0 || len > left)
153 					goto truncated;
154 				left -= len;
155 				cur += len;
156 			} else {
157 				len = snprintf(cur, left, "gid %u",
158 				    rule->mbr_subject.mbs_gid_min);
159 				if (len < 0 || len > left)
160 					goto truncated;
161 				left -= len;
162 				cur += len;
163 			}
164 			if (rule->mbr_subject.mbs_gid_min !=
165 			    rule->mbr_subject.mbs_gid_max) {
166 				grp = getgrgid(rule->mbr_subject.mbs_gid_max);
167 				if (grp != NULL) {
168 					len = snprintf(cur, left, ":%s ",
169 					    grp->gr_name);
170 					if (len < 0 || len > left)
171 						goto truncated;
172 					left -= len;
173 					cur += len;
174 				} else {
175 					len = snprintf(cur, left, ":%u ",
176 					    rule->mbr_subject.mbs_gid_max);
177 					if (len < 0 || len > left)
178 						goto truncated;
179 					left -= len;
180 					cur += len;
181 				}
182 			} else {
183 				len = snprintf(cur, left, " ");
184 				if (len < 0 || len > left)
185 					goto truncated;
186 				left -= len;
187 				cur += len;
188 			}
189 		}
190 		if (!notdone && (rule->mbr_subject.mbs_neg & MBS_PRISON_DEFINED)) {
191 			len = snprintf(cur, left, "! ");
192 			if (len < 0 || len > left)
193 				goto truncated;
194 			left -= len;
195 			cur += len;
196 		}
197 		if (rule->mbr_subject.mbs_flags & MBS_PRISON_DEFINED) {
198 			len = snprintf(cur, left, "jailid %d ",
199 			    rule->mbr_subject.mbs_prison);
200 			if (len < 0 || len > left)
201 				goto truncated;
202 			left -= len;
203 			cur += len;
204 		}
205 	}
206 
207 	len = snprintf(cur, left, "object ");
208 	if (len < 0 || len > left)
209 		goto truncated;
210 	left -= len;
211 	cur += len;
212 	if (rule->mbr_object.mbo_flags) {
213 		if (rule->mbr_object.mbo_neg == MBO_ALL_FLAGS) {
214 			len = snprintf(cur, left, "not ");
215 			if (len < 0 || len > left)
216 				goto truncated;
217 			left -= len;
218 			cur += len;
219 			notdone = 1;
220 		} else {
221 			notdone = 0;
222 		}
223 
224 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_UID_DEFINED)) {
225 			len = snprintf(cur, left, "! ");
226 			if (len < 0 || len > left)
227 				goto truncated;
228 			left -= len;
229 			cur += len;
230 		}
231 		if (rule->mbr_object.mbo_flags & MBO_UID_DEFINED) {
232 			pwd = getpwuid(rule->mbr_object.mbo_uid_min);
233 			if (pwd != NULL) {
234 				len = snprintf(cur, left, "uid %s",
235 				    pwd->pw_name);
236 				if (len < 0 || len > left)
237 					goto truncated;
238 				left -= len;
239 				cur += len;
240 			} else {
241 				len = snprintf(cur, left, "uid %u",
242 				    rule->mbr_object.mbo_uid_min);
243 				if (len < 0 || len > left)
244 					goto truncated;
245 				left -= len;
246 				cur += len;
247 			}
248 			if (rule->mbr_object.mbo_uid_min !=
249 			    rule->mbr_object.mbo_uid_max) {
250 				pwd = getpwuid(rule->mbr_object.mbo_uid_max);
251 				if (pwd != NULL) {
252 					len = snprintf(cur, left, ":%s ",
253 					    pwd->pw_name);
254 					if (len < 0 || len > left)
255 						goto truncated;
256 					left -= len;
257 					cur += len;
258 				} else {
259 					len = snprintf(cur, left, ":%u ",
260 					    rule->mbr_object.mbo_uid_max);
261 					if (len < 0 || len > left)
262 						goto truncated;
263 					left -= len;
264 					cur += len;
265 				}
266 			} else {
267 				len = snprintf(cur, left, " ");
268 				if (len < 0 || len > left)
269 					goto truncated;
270 				left -= len;
271 				cur += len;
272 			}
273 		}
274 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_GID_DEFINED)) {
275 			len = snprintf(cur, left, "! ");
276 			if (len < 0 || len > left)
277 				goto truncated;
278 			left -= len;
279 			cur += len;
280 		}
281 		if (rule->mbr_object.mbo_flags & MBO_GID_DEFINED) {
282 			grp = getgrgid(rule->mbr_object.mbo_gid_min);
283 			if (grp != NULL) {
284 				len = snprintf(cur, left, "gid %s",
285 				    grp->gr_name);
286 				if (len < 0 || len > left)
287 					goto truncated;
288 				left -= len;
289 				cur += len;
290 			} else {
291 				len = snprintf(cur, left, "gid %u",
292 				    rule->mbr_object.mbo_gid_min);
293 				if (len < 0 || len > left)
294 					goto truncated;
295 				left -= len;
296 				cur += len;
297 			}
298 			if (rule->mbr_object.mbo_gid_min !=
299 			    rule->mbr_object.mbo_gid_max) {
300 				grp = getgrgid(rule->mbr_object.mbo_gid_max);
301 				if (grp != NULL) {
302 					len = snprintf(cur, left, ":%s ",
303 					    grp->gr_name);
304 					if (len < 0 || len > left)
305 						goto truncated;
306 					left -= len;
307 					cur += len;
308 				} else {
309 					len = snprintf(cur, left, ":%u ",
310 					    rule->mbr_object.mbo_gid_max);
311 					if (len < 0 || len > left)
312 						goto truncated;
313 					left -= len;
314 					cur += len;
315 				}
316 			} else {
317 				len = snprintf(cur, left, " ");
318 				if (len < 0 || len > left)
319 					goto truncated;
320 				left -= len;
321 				cur += len;
322 			}
323 		}
324 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_FSID_DEFINED)) {
325 			len = snprintf(cur, left, "! ");
326 			if (len < 0 || len > left)
327 				goto truncated;
328 			left -= len;
329 			cur += len;
330 		}
331 		if (rule->mbr_object.mbo_flags & MBO_FSID_DEFINED) {
332 			numfs = getmntinfo(&mntbuf, MNT_NOWAIT);
333 			for (i = 0; i < numfs; i++)
334 				if (memcmp(&(rule->mbr_object.mbo_fsid),
335 				    &(mntbuf[i].f_fsid),
336 				    sizeof(mntbuf[i].f_fsid)) == 0)
337 					break;
338 			len = snprintf(cur, left, "filesys %s ",
339 			    i == numfs ? "???" : mntbuf[i].f_mntonname);
340 			if (len < 0 || len > left)
341 				goto truncated;
342 			left -= len;
343 			cur += len;
344 		}
345 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_SUID)) {
346 			len = snprintf(cur, left, "! ");
347 			if (len < 0 || len > left)
348 				goto truncated;
349 			left -= len;
350 			cur += len;
351 		}
352 		if (rule->mbr_object.mbo_flags & MBO_SUID) {
353 			len = snprintf(cur, left, "suid ");
354 			if (len < 0 || len > left)
355 				goto truncated;
356 			left -= len;
357 			cur += len;
358 		}
359 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_SGID)) {
360 			len = snprintf(cur, left, "! ");
361 			if (len < 0 || len > left)
362 				goto truncated;
363 			left -= len;
364 			cur += len;
365 		}
366 		if (rule->mbr_object.mbo_flags & MBO_SGID) {
367 			len = snprintf(cur, left, "sgid ");
368 			if (len < 0 || len > left)
369 				goto truncated;
370 			left -= len;
371 			cur += len;
372 		}
373 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_UID_SUBJECT)) {
374 			len = snprintf(cur, left, "! ");
375 			if (len < 0 || len > left)
376 				goto truncated;
377 			left -= len;
378 			cur += len;
379 		}
380 		if (rule->mbr_object.mbo_flags & MBO_UID_SUBJECT) {
381 			len = snprintf(cur, left, "uid_of_subject ");
382 			if (len < 0 || len > left)
383 				goto truncated;
384 			left -= len;
385 			cur += len;
386 		}
387 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_GID_SUBJECT)) {
388 			len = snprintf(cur, left, "! ");
389 			if (len < 0 || len > left)
390 				goto truncated;
391 			left -= len;
392 			cur += len;
393 		}
394 		if (rule->mbr_object.mbo_flags & MBO_GID_SUBJECT) {
395 			len = snprintf(cur, left, "gid_of_subject ");
396 			if (len < 0 || len > left)
397 				goto truncated;
398 			left -= len;
399 			cur += len;
400 		}
401 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_TYPE_DEFINED)) {
402 			len = snprintf(cur, left, "! ");
403 			if (len < 0 || len > left)
404 				goto truncated;
405 			left -= len;
406 			cur += len;
407 		}
408 		if (rule->mbr_object.mbo_flags & MBO_TYPE_DEFINED) {
409 			i = 0;
410 			if (rule->mbr_object.mbo_type & MBO_TYPE_REG)
411 				type[i++] = 'r';
412 			if (rule->mbr_object.mbo_type & MBO_TYPE_DIR)
413 				type[i++] = 'd';
414 			if (rule->mbr_object.mbo_type & MBO_TYPE_BLK)
415 				type[i++] = 'b';
416 			if (rule->mbr_object.mbo_type & MBO_TYPE_CHR)
417 				type[i++] = 'c';
418 			if (rule->mbr_object.mbo_type & MBO_TYPE_LNK)
419 				type[i++] = 'l';
420 			if (rule->mbr_object.mbo_type & MBO_TYPE_SOCK)
421 				type[i++] = 's';
422 			if (rule->mbr_object.mbo_type & MBO_TYPE_FIFO)
423 				type[i++] = 'p';
424 			if (rule->mbr_object.mbo_type == MBO_ALL_TYPE) {
425 				i = 0;
426 				type[i++] = 'a';
427 			}
428 			type[i++] = '\0';
429 			len = snprintf(cur, left, "type %s ", type);
430 			if (len < 0 || len > left)
431 				goto truncated;
432 			left -= len;
433 			cur += len;
434 		}
435 	}
436 
437 	len = snprintf(cur, left, "mode ");
438 	if (len < 0 || len > left)
439 		goto truncated;
440 	left -= len;
441 	cur += len;
442 
443 	anymode = (rule->mbr_mode & MBI_ALLPERM);
444 	unknownmode = (rule->mbr_mode & ~MBI_ALLPERM);
445 
446 	if (rule->mbr_mode & MBI_ADMIN) {
447 		len = snprintf(cur, left, "a");
448 		if (len < 0 || len > left)
449 			goto truncated;
450 
451 		left -= len;
452 		cur += len;
453 	}
454 	if (rule->mbr_mode & MBI_READ) {
455 		len = snprintf(cur, left, "r");
456 		if (len < 0 || len > left)
457 			goto truncated;
458 
459 		left -= len;
460 		cur += len;
461 	}
462 	if (rule->mbr_mode & MBI_STAT) {
463 		len = snprintf(cur, left, "s");
464 		if (len < 0 || len > left)
465 			goto truncated;
466 
467 		left -= len;
468 		cur += len;
469 	}
470 	if (rule->mbr_mode & MBI_WRITE) {
471 		len = snprintf(cur, left, "w");
472 		if (len < 0 || len > left)
473 			goto truncated;
474 
475 		left -= len;
476 		cur += len;
477 	}
478 	if (rule->mbr_mode & MBI_EXEC) {
479 		len = snprintf(cur, left, "x");
480 		if (len < 0 || len > left)
481 			goto truncated;
482 
483 		left -= len;
484 		cur += len;
485 	}
486 	if (!anymode) {
487 		len = snprintf(cur, left, "n");
488 		if (len < 0 || len > left)
489 			goto truncated;
490 
491 		left -= len;
492 		cur += len;
493 	}
494 	if (unknownmode) {
495 		len = snprintf(cur, left, "?");
496 		if (len < 0 || len > left)
497 			goto truncated;
498 
499 		left -= len;
500 		cur += len;
501 	}
502 
503 	return (0);
504 
505 truncated:
506 	return (-1);
507 }
508 
509 int
510 bsde_parse_uidrange(char *spec, uid_t *min, uid_t *max,
511     size_t buflen, char *errstr){
512 	struct passwd *pwd;
513 	uid_t uid1, uid2;
514 	char *spec1, *spec2, *endp;
515 	unsigned long value;
516 	size_t len;
517 
518 	spec2 = spec;
519 	spec1 = strsep(&spec2, ":");
520 
521 	pwd = getpwnam(spec1);
522 	if (pwd != NULL)
523 		uid1 = pwd->pw_uid;
524 	else {
525 		value = strtoul(spec1, &endp, 10);
526 		if (*endp != '\0') {
527 			len = snprintf(errstr, buflen,
528 			    "invalid uid: '%s'", spec1);
529 			return (-1);
530 		}
531 		uid1 = value;
532 	}
533 
534 	if (spec2 == NULL) {
535 		*max = *min = uid1;
536 		return (0);
537 	}
538 
539 	pwd = getpwnam(spec2);
540 	if (pwd != NULL)
541 		uid2 = pwd->pw_uid;
542 	else {
543 		value = strtoul(spec2, &endp, 10);
544 		if (*endp != '\0') {
545 			len = snprintf(errstr, buflen,
546 			    "invalid uid: '%s'", spec2);
547 			return (-1);
548 		}
549 		uid2 = value;
550 	}
551 
552 	*min = uid1;
553 	*max = uid2;
554 
555 	return (0);
556 }
557 
558 int
559 bsde_parse_gidrange(char *spec, gid_t *min, gid_t *max,
560     size_t buflen, char *errstr){
561 	struct group *grp;
562 	gid_t gid1, gid2;
563 	char *spec1, *spec2, *endp;
564 	unsigned long value;
565 	size_t len;
566 
567 	spec2 = spec;
568 	spec1 = strsep(&spec2, ":");
569 
570 	grp = getgrnam(spec1);
571 	if (grp != NULL)
572 		gid1 = grp->gr_gid;
573 	else {
574 		value = strtoul(spec1, &endp, 10);
575 		if (*endp != '\0') {
576 			len = snprintf(errstr, buflen,
577 			    "invalid gid: '%s'", spec1);
578 			return (-1);
579 		}
580 		gid1 = value;
581 	}
582 
583 	if (spec2 == NULL) {
584 		*max = *min = gid1;
585 		return (0);
586 	}
587 
588 	grp = getgrnam(spec2);
589 	if (grp != NULL)
590 		gid2 = grp->gr_gid;
591 	else {
592 		value = strtoul(spec2, &endp, 10);
593 		if (*endp != '\0') {
594 			len = snprintf(errstr, buflen,
595 			    "invalid gid: '%s'", spec2);
596 			return (-1);
597 		}
598 		gid2 = value;
599 	}
600 
601 	*min = gid1;
602 	*max = gid2;
603 
604 	return (0);
605 }
606 
607 int
608 bsde_parse_subject(int argc, char *argv[],
609     struct mac_bsdextended_subject *subject, size_t buflen, char *errstr)
610 {
611 	int not_seen, flags;
612 	int current, neg, nextnot;
613 	char *endp;
614 	uid_t uid_min, uid_max;
615 	gid_t gid_min, gid_max;
616 	int jid;
617 	size_t len;
618 	long value;
619 
620 	current = 0;
621 	flags = 0;
622 	neg = 0;
623 	nextnot = 0;
624 
625 	if (strcmp("not", argv[current]) == 0) {
626 		not_seen = 1;
627 		current++;
628 	} else
629 		not_seen = 0;
630 
631 	while (current < argc) {
632 		if (strcmp(argv[current], "uid") == 0) {
633 			if (current + 2 > argc) {
634 				len = snprintf(errstr, buflen, "uid short");
635 				return (-1);
636 			}
637 			if (flags & MBS_UID_DEFINED) {
638 				len = snprintf(errstr, buflen, "one uid only");
639 				return (-1);
640 			}
641 			if (bsde_parse_uidrange(argv[current+1],
642 			    &uid_min, &uid_max, buflen, errstr) < 0)
643 				return (-1);
644 			flags |= MBS_UID_DEFINED;
645 			if (nextnot) {
646 				neg ^= MBS_UID_DEFINED;
647 				nextnot = 0;
648 			}
649 			current += 2;
650 		} else if (strcmp(argv[current], "gid") == 0) {
651 			if (current + 2 > argc) {
652 				len = snprintf(errstr, buflen, "gid short");
653 				return (-1);
654 			}
655 			if (flags & MBS_GID_DEFINED) {
656 				len = snprintf(errstr, buflen, "one gid only");
657 				return (-1);
658 			}
659 			if (bsde_parse_gidrange(argv[current+1],
660 			    &gid_min, &gid_max, buflen, errstr) < 0)
661 				return (-1);
662 			flags |= MBS_GID_DEFINED;
663 			if (nextnot) {
664 				neg ^= MBS_GID_DEFINED;
665 				nextnot = 0;
666 			}
667 			current += 2;
668 		} else if (strcmp(argv[current], "jailid") == 0) {
669 			if (current + 2 > argc) {
670 				len = snprintf(errstr, buflen, "prison short");
671 				return (-1);
672 			}
673 			if (flags & MBS_PRISON_DEFINED) {
674 				len = snprintf(errstr, buflen, "one jail only");
675 				return (-1);
676 			}
677 			value = strtol(argv[current+1], &endp, 10);
678 			if (*endp != '\0') {
679 				len = snprintf(errstr, buflen,
680 				    "invalid jid: '%s'", argv[current+1]);
681 				return (-1);
682 			}
683 			jid = value;
684 			flags |= MBS_PRISON_DEFINED;
685 			if (nextnot) {
686 				neg ^= MBS_PRISON_DEFINED;
687 				nextnot = 0;
688 			}
689 			current += 2;
690 		} else if (strcmp(argv[current], "!") == 0) {
691 			if (nextnot) {
692 				len = snprintf(errstr, buflen,
693 				    "double negative");
694 				return (-1);
695 			}
696 			nextnot = 1;
697 			current += 1;
698 		} else {
699 			len = snprintf(errstr, buflen, "'%s' not expected",
700 			    argv[current]);
701 			return (-1);
702 		}
703 	}
704 
705 	subject->mbs_flags = flags;
706 	if (not_seen)
707 		subject->mbs_neg = MBS_ALL_FLAGS ^ neg;
708 	else
709 		subject->mbs_neg = neg;
710 	if (flags & MBS_UID_DEFINED) {
711 		subject->mbs_uid_min = uid_min;
712 		subject->mbs_uid_max = uid_max;
713 	}
714 	if (flags & MBS_GID_DEFINED) {
715 		subject->mbs_gid_min = gid_min;
716 		subject->mbs_gid_max = gid_max;
717 	}
718 	if (flags & MBS_PRISON_DEFINED)
719 		subject->mbs_prison = jid;
720 
721 	return (0);
722 }
723 
724 int
725 bsde_parse_type(char *spec, int *type, size_t buflen, char *errstr)
726 {
727 	size_t len;
728 	int i;
729 
730 	*type = 0;
731 	for (i = 0; i < strlen(spec); i++) {
732 		switch (spec[i]) {
733 		case 'r':
734 		case '-':
735 			*type |= MBO_TYPE_REG;
736 			break;
737 		case 'd':
738 			*type |= MBO_TYPE_DIR;
739 			break;
740 		case 'b':
741 			*type |= MBO_TYPE_BLK;
742 			break;
743 		case 'c':
744 			*type |= MBO_TYPE_CHR;
745 			break;
746 		case 'l':
747 			*type |= MBO_TYPE_LNK;
748 			break;
749 		case 's':
750 			*type |= MBO_TYPE_SOCK;
751 			break;
752 		case 'p':
753 			*type |= MBO_TYPE_FIFO;
754 			break;
755 		case 'a':
756 			*type |= MBO_ALL_TYPE;
757 			break;
758 		default:
759 			len = snprintf(errstr, buflen, "Unknown type code: %c",
760 			    spec[i]);
761 			return (-1);
762 		}
763 	}
764 
765 	return (0);
766 }
767 
768 int
769 bsde_parse_fsid(char *spec, struct fsid *fsid, size_t buflen, char *errstr)
770 {
771 	size_t len;
772 	struct statfs buf;
773 
774 	if (statfs(spec, &buf) < 0) {
775 		len = snprintf(errstr, buflen, "Unable to get id for %s: %s",
776 		    spec, strerror(errno));
777 		return (-1);
778 	}
779 
780 	*fsid = buf.f_fsid;
781 
782 	return (0);
783 }
784 
785 int
786 bsde_parse_object(int argc, char *argv[],
787     struct mac_bsdextended_object *object, size_t buflen, char *errstr)
788 {
789 	int not_seen, flags;
790 	int current, neg, nextnot;
791 	uid_t uid_min, uid_max;
792 	gid_t gid_min, gid_max;
793 	int type;
794 	struct fsid fsid;
795 	size_t len;
796 
797 	current = 0;
798 	flags = 0;
799 	neg = 0;
800 	nextnot = 0;
801 
802 	if (strcmp("not", argv[current]) == 0) {
803 		not_seen = 1;
804 		current++;
805 	} else
806 		not_seen = 0;
807 
808 	while (current < argc) {
809 		if (strcmp(argv[current], "uid") == 0) {
810 			if (current + 2 > argc) {
811 				len = snprintf(errstr, buflen, "uid short");
812 				return (-1);
813 			}
814 			if (flags & MBO_UID_DEFINED) {
815 				len = snprintf(errstr, buflen, "one uid only");
816 				return (-1);
817 			}
818 			if (bsde_parse_uidrange(argv[current+1],
819 			    &uid_min, &uid_max, buflen, errstr) < 0)
820 				return (-1);
821 			flags |= MBO_UID_DEFINED;
822 			if (nextnot) {
823 				neg ^= MBO_UID_DEFINED;
824 				nextnot = 0;
825 			}
826 			current += 2;
827 		} else if (strcmp(argv[current], "gid") == 0) {
828 			if (current + 2 > argc) {
829 				len = snprintf(errstr, buflen, "gid short");
830 				return (-1);
831 			}
832 			if (flags & MBO_GID_DEFINED) {
833 				len = snprintf(errstr, buflen, "one gid only");
834 				return (-1);
835 			}
836 			if (bsde_parse_gidrange(argv[current+1],
837 			    &gid_min, &gid_max, buflen, errstr) < 0)
838 				return (-1);
839 			flags |= MBO_GID_DEFINED;
840 			if (nextnot) {
841 				neg ^= MBO_GID_DEFINED;
842 				nextnot = 0;
843 			}
844 			current += 2;
845 		} else if (strcmp(argv[current], "filesys") == 0) {
846 			if (current + 2 > argc) {
847 				len = snprintf(errstr, buflen, "filesys short");
848 				return (-1);
849 			}
850 			if (flags & MBO_FSID_DEFINED) {
851 				len = snprintf(errstr, buflen, "one fsid only");
852 				return (-1);
853 			}
854 			if (bsde_parse_fsid(argv[current+1], &fsid,
855 			    buflen, errstr) < 0)
856 				return (-1);
857 			flags |= MBO_FSID_DEFINED;
858 			if (nextnot) {
859 				neg ^= MBO_FSID_DEFINED;
860 				nextnot = 0;
861 			}
862 			current += 2;
863 		} else if (strcmp(argv[current], "suid") == 0) {
864 			flags |= MBO_SUID;
865 			if (nextnot) {
866 				neg ^= MBO_SUID;
867 				nextnot = 0;
868 			}
869 			current += 1;
870 		} else if (strcmp(argv[current], "sgid") == 0) {
871 			flags |= MBO_SGID;
872 			if (nextnot) {
873 				neg ^= MBO_SGID;
874 				nextnot = 0;
875 			}
876 			current += 1;
877 		} else if (strcmp(argv[current], "uid_of_subject") == 0) {
878 			flags |= MBO_UID_SUBJECT;
879 			if (nextnot) {
880 				neg ^= MBO_UID_SUBJECT;
881 				nextnot = 0;
882 			}
883 			current += 1;
884 		} else if (strcmp(argv[current], "gid_of_subject") == 0) {
885 			flags |= MBO_GID_SUBJECT;
886 			if (nextnot) {
887 				neg ^= MBO_GID_SUBJECT;
888 				nextnot = 0;
889 			}
890 			current += 1;
891 		} else if (strcmp(argv[current], "type") == 0) {
892 			if (current + 2 > argc) {
893 				len = snprintf(errstr, buflen, "type short");
894 				return (-1);
895 			}
896 			if (flags & MBO_TYPE_DEFINED) {
897 				len = snprintf(errstr, buflen, "one type only");
898 				return (-1);
899 			}
900 			if (bsde_parse_type(argv[current+1], &type,
901 			    buflen, errstr) < 0)
902 				return (-1);
903 			flags |= MBO_TYPE_DEFINED;
904 			if (nextnot) {
905 				neg ^= MBO_TYPE_DEFINED;
906 				nextnot = 0;
907 			}
908 			current += 2;
909 		} else if (strcmp(argv[current], "!") == 0) {
910 			if (nextnot) {
911 				len = snprintf(errstr, buflen,
912 				    "double negative'");
913 				return (-1);
914 			}
915 			nextnot = 1;
916 			current += 1;
917 		} else {
918 			len = snprintf(errstr, buflen, "'%s' not expected",
919 			    argv[current]);
920 			return (-1);
921 		}
922 	}
923 
924 	object->mbo_flags = flags;
925 	if (not_seen)
926 		object->mbo_neg = MBO_ALL_FLAGS ^ neg;
927 	else
928 		object->mbo_neg = neg;
929 	if (flags & MBO_UID_DEFINED) {
930 		object->mbo_uid_min = uid_min;
931 		object->mbo_uid_max = uid_max;
932 	}
933 	if (flags & MBO_GID_DEFINED) {
934 		object->mbo_gid_min = gid_min;
935 		object->mbo_gid_max = gid_max;
936 	}
937 	if (flags & MBO_FSID_DEFINED)
938 		object->mbo_fsid = fsid;
939 	if (flags & MBO_TYPE_DEFINED)
940 		object->mbo_type = type;
941 
942 	return (0);
943 }
944 
945 int
946 bsde_parse_mode(int argc, char *argv[], mode_t *mode, size_t buflen,
947     char *errstr)
948 {
949 	size_t len;
950 	int i;
951 
952 	if (argc == 0) {
953 		len = snprintf(errstr, buflen, "mode expects mode value");
954 		return (-1);
955 	}
956 
957 	if (argc != 1) {
958 		len = snprintf(errstr, buflen, "'%s' unexpected", argv[1]);
959 		return (-1);
960 	}
961 
962 	*mode = 0;
963 	for (i = 0; i < strlen(argv[0]); i++) {
964 		switch (argv[0][i]) {
965 		case 'a':
966 			*mode |= MBI_ADMIN;
967 			break;
968 		case 'r':
969 			*mode |= MBI_READ;
970 			break;
971 		case 's':
972 			*mode |= MBI_STAT;
973 			break;
974 		case 'w':
975 			*mode |= MBI_WRITE;
976 			break;
977 		case 'x':
978 			*mode |= MBI_EXEC;
979 			break;
980 		case 'n':
981 			/* ignore */
982 			break;
983 		default:
984 			len = snprintf(errstr, buflen, "Unknown mode letter: %c",
985 			    argv[0][i]);
986 			return (-1);
987 		}
988 	}
989 
990 	return (0);
991 }
992 
993 int
994 bsde_parse_rule(int argc, char *argv[], struct mac_bsdextended_rule *rule,
995     size_t buflen, char *errstr)
996 {
997 	int subject, subject_elements, subject_elements_length;
998 	int object, object_elements, object_elements_length;
999 	int mode, mode_elements, mode_elements_length;
1000 	int error, i;
1001 	size_t len;
1002 
1003 	bzero(rule, sizeof(*rule));
1004 
1005 	if (argc < 1) {
1006 		len = snprintf(errstr, buflen, "Rule must begin with subject");
1007 		return (-1);
1008 	}
1009 
1010 	if (strcmp(argv[0], "subject") != 0) {
1011 		len = snprintf(errstr, buflen, "Rule must begin with subject");
1012 		return (-1);
1013 	}
1014 	subject = 0;
1015 	subject_elements = 1;
1016 
1017 	/* Search forward for object. */
1018 
1019 	object = -1;
1020 	for (i = 1; i < argc; i++)
1021 		if (strcmp(argv[i], "object") == 0)
1022 			object = i;
1023 
1024 	if (object == -1) {
1025 		len = snprintf(errstr, buflen, "Rule must contain an object");
1026 		return (-1);
1027 	}
1028 
1029 	/* Search forward for mode. */
1030 	mode = -1;
1031 	for (i = object; i < argc; i++)
1032 		if (strcmp(argv[i], "mode") == 0)
1033 			mode = i;
1034 
1035 	if (mode == -1) {
1036 		len = snprintf(errstr, buflen, "Rule must contain mode");
1037 		return (-1);
1038 	}
1039 
1040 	subject_elements_length = object - subject - 1;
1041 	object_elements = object + 1;
1042 	object_elements_length = mode - object_elements;
1043 	mode_elements = mode + 1;
1044 	mode_elements_length = argc - mode_elements;
1045 
1046 	error = bsde_parse_subject(subject_elements_length,
1047 	    argv + subject_elements, &rule->mbr_subject, buflen, errstr);
1048 	if (error)
1049 		return (-1);
1050 
1051 	error = bsde_parse_object(object_elements_length,
1052 	    argv + object_elements, &rule->mbr_object, buflen, errstr);
1053 	if (error)
1054 		return (-1);
1055 
1056 	error = bsde_parse_mode(mode_elements_length, argv + mode_elements,
1057 	    &rule->mbr_mode, buflen, errstr);
1058 	if (error)
1059 		return (-1);
1060 
1061 	return (0);
1062 }
1063 
1064 int
1065 bsde_parse_rule_string(const char *string, struct mac_bsdextended_rule *rule,
1066     size_t buflen, char *errstr)
1067 {
1068 	char *stringdup, *stringp, *argv[100], **ap;
1069 	int argc, error;
1070 
1071 	stringp = stringdup = strdup(string);
1072 	while (*stringp == ' ' || *stringp == '\t')
1073 		stringp++;
1074 
1075 	argc = 0;
1076 	for (ap = argv; (*ap = strsep(&stringp, " \t")) != NULL;) {
1077 		argc++;
1078 		if (**ap != '\0')
1079 			if (++ap >= &argv[100])
1080 				break;
1081 	}
1082 
1083 	error = bsde_parse_rule(argc, argv, rule, buflen, errstr);
1084 
1085 	free(stringdup);
1086 
1087 	return (error);
1088 }
1089 
1090 int
1091 bsde_get_mib(const char *string, int *name, size_t *namelen)
1092 {
1093 	size_t len;
1094 	int error;
1095 
1096 	len = *namelen;
1097 	error = sysctlnametomib(string, name, &len);
1098 	if (error)
1099 		return (error);
1100 
1101 	*namelen = len;
1102 	return (0);
1103 }
1104 
1105 int
1106 bsde_check_version(size_t buflen, char *errstr)
1107 {
1108 	size_t len;
1109 	int error;
1110 	int version;
1111 
1112 	len = sizeof(version);
1113 	error = sysctlbyname(MIB ".rule_version", &version, &len, NULL, 0);
1114 	if (error) {
1115 		len = snprintf(errstr, buflen, "version check failed: %s",
1116 		    strerror(errno));
1117 		return (-1);
1118 	}
1119 	if (version != MB_VERSION) {
1120 		len = snprintf(errstr, buflen, "module v%d != library v%d",
1121 		    version, MB_VERSION);
1122 		return (-1);
1123 	}
1124 	return (0);
1125 }
1126 
1127 int
1128 bsde_get_rule_count(size_t buflen, char *errstr)
1129 {
1130 	size_t len;
1131 	int error;
1132 	int rule_count;
1133 
1134 	len = sizeof(rule_count);
1135 	error = sysctlbyname(MIB ".rule_count", &rule_count, &len, NULL, 0);
1136 	if (error) {
1137 		len = snprintf(errstr, buflen, "%s", strerror(errno));
1138 		return (-1);
1139 	}
1140 	if (len != sizeof(rule_count)) {
1141 		len = snprintf(errstr, buflen, "Data error in %s.rule_count",
1142 		    MIB);
1143 		return (-1);
1144 	}
1145 
1146 	return (rule_count);
1147 }
1148 
1149 int
1150 bsde_get_rule_slots(size_t buflen, char *errstr)
1151 {
1152 	size_t len;
1153 	int error;
1154 	int rule_slots;
1155 
1156 	len = sizeof(rule_slots);
1157 	error = sysctlbyname(MIB ".rule_slots", &rule_slots, &len, NULL, 0);
1158 	if (error) {
1159 		len = snprintf(errstr, buflen, "%s", strerror(errno));
1160 		return (-1);
1161 	}
1162 	if (len != sizeof(rule_slots)) {
1163 		len = snprintf(errstr, buflen, "Data error in %s.rule_slots",
1164 		    MIB);
1165 		return (-1);
1166 	}
1167 
1168 	return (rule_slots);
1169 }
1170 
1171 /*
1172  * Returns 0 for success;
1173  * Returns -1 for failure;
1174  * Returns -2 for not present
1175  */
1176 int
1177 bsde_get_rule(int rulenum, struct mac_bsdextended_rule *rule, size_t errlen,
1178     char *errstr)
1179 {
1180 	int name[10];
1181 	size_t len, size;
1182 	int error;
1183 
1184 	if (bsde_check_version(errlen, errstr) != 0)
1185 		return (-1);
1186 
1187 	len = 10;
1188 	error = bsde_get_mib(MIB ".rules", name, &len);
1189 	if (error) {
1190 		len = snprintf(errstr, errlen, "%s: %s", MIB ".rules",
1191 		    strerror(errno));
1192 		return (-1);
1193 	}
1194 
1195 	size = sizeof(*rule);
1196 	name[len] = rulenum;
1197 	len++;
1198 	error = sysctl(name, len, rule, &size, NULL, 0);
1199 	if (error  == -1 && errno == ENOENT)
1200 		return (-2);
1201 	if (error) {
1202 		len = snprintf(errstr, errlen, "%s.%d: %s", MIB ".rules",
1203 		    rulenum, strerror(errno));
1204 		return (-1);
1205 	} else if (size != sizeof(*rule)) {
1206 		len = snprintf(errstr, errlen, "Data error in %s.%d: %s",
1207 		    MIB ".rules", rulenum, strerror(errno));
1208 		return (-1);
1209 	}
1210 
1211 	return (0);
1212 }
1213 
1214 int
1215 bsde_delete_rule(int rulenum, size_t buflen, char *errstr)
1216 {
1217 	struct mac_bsdextended_rule rule;
1218 	int name[10];
1219 	size_t len, size;
1220 	int error;
1221 
1222 	if (bsde_check_version(buflen, errstr) != 0)
1223 		return (-1);
1224 
1225 	len = 10;
1226 	error = bsde_get_mib(MIB ".rules", name, &len);
1227 	if (error) {
1228 		len = snprintf(errstr, buflen, "%s: %s", MIB ".rules",
1229 		    strerror(errno));
1230 		return (-1);
1231 	}
1232 
1233 	name[len] = rulenum;
1234 	len++;
1235 
1236 	size = sizeof(rule);
1237 	error = sysctl(name, len, NULL, NULL, &rule, 0);
1238 	if (error) {
1239 		len = snprintf(errstr, buflen, "%s.%d: %s", MIB ".rules",
1240 		    rulenum, strerror(errno));
1241 		return (-1);
1242 	}
1243 
1244 	return (0);
1245 }
1246 
1247 int
1248 bsde_set_rule(int rulenum, struct mac_bsdextended_rule *rule, size_t buflen,
1249     char *errstr)
1250 {
1251 	int name[10];
1252 	size_t len, size;
1253 	int error;
1254 
1255 	if (bsde_check_version(buflen, errstr) != 0)
1256 		return (-1);
1257 
1258 	len = 10;
1259 	error = bsde_get_mib(MIB ".rules", name, &len);
1260 	if (error) {
1261 		len = snprintf(errstr, buflen, "%s: %s", MIB ".rules",
1262 		    strerror(errno));
1263 		return (-1);
1264 	}
1265 
1266 	name[len] = rulenum;
1267 	len++;
1268 
1269 	size = sizeof(*rule);
1270 	error = sysctl(name, len, NULL, NULL, rule, size);
1271 	if (error) {
1272 		len = snprintf(errstr, buflen, "%s.%d: %s", MIB ".rules",
1273 		    rulenum, strerror(errno));
1274 		return (-1);
1275 	}
1276 
1277 	return (0);
1278 }
1279 
1280 int
1281 bsde_add_rule(int *rulenum, struct mac_bsdextended_rule *rule, size_t buflen,
1282     char *errstr)
1283 {
1284 	char charstr[BUFSIZ];
1285 	int name[10];
1286 	size_t len, size;
1287 	int error, rule_slots;
1288 
1289 	if (bsde_check_version(buflen, errstr) != 0)
1290 		return (-1);
1291 
1292 	len = 10;
1293 	error = bsde_get_mib(MIB ".rules", name, &len);
1294 	if (error) {
1295 		len = snprintf(errstr, buflen, "%s: %s", MIB ".rules",
1296 		    strerror(errno));
1297 		return (-1);
1298 	}
1299 
1300 	rule_slots = bsde_get_rule_slots(BUFSIZ, charstr);
1301 	if (rule_slots == -1) {
1302 		len = snprintf(errstr, buflen, "unable to get rule slots: %s",
1303 		    strerror(errno));
1304 		return (-1);
1305 	}
1306 
1307 	name[len] = rule_slots;
1308 	len++;
1309 
1310 	size = sizeof(*rule);
1311 	error = sysctl(name, len, NULL, NULL, rule, size);
1312 	if (error) {
1313 		len = snprintf(errstr, buflen, "%s.%d: %s", MIB ".rules",
1314 		    rule_slots, strerror(errno));
1315 		return (-1);
1316 	}
1317 
1318 	if (rulenum != NULL)
1319 		*rulenum = rule_slots;
1320 
1321 	return (0);
1322 }
1323