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