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