xref: /freebsd/lib/libugidfw/ugidfw.c (revision 0957b409)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 (memcmp(&(rule->mbr_object.mbo_fsid),
338 				    &(mntbuf[i].f_fsid),
339 				    sizeof(mntbuf[i].f_fsid)) == 0)
340 					break;
341 			len = snprintf(cur, left, "filesys %s ",
342 			    i == numfs ? "???" : mntbuf[i].f_mntonname);
343 			if (len < 0 || len > left)
344 				goto truncated;
345 			left -= len;
346 			cur += len;
347 		}
348 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_SUID)) {
349 			len = snprintf(cur, left, "! ");
350 			if (len < 0 || len > left)
351 				goto truncated;
352 			left -= len;
353 			cur += len;
354 		}
355 		if (rule->mbr_object.mbo_flags & MBO_SUID) {
356 			len = snprintf(cur, left, "suid ");
357 			if (len < 0 || len > left)
358 				goto truncated;
359 			left -= len;
360 			cur += len;
361 		}
362 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_SGID)) {
363 			len = snprintf(cur, left, "! ");
364 			if (len < 0 || len > left)
365 				goto truncated;
366 			left -= len;
367 			cur += len;
368 		}
369 		if (rule->mbr_object.mbo_flags & MBO_SGID) {
370 			len = snprintf(cur, left, "sgid ");
371 			if (len < 0 || len > left)
372 				goto truncated;
373 			left -= len;
374 			cur += len;
375 		}
376 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_UID_SUBJECT)) {
377 			len = snprintf(cur, left, "! ");
378 			if (len < 0 || len > left)
379 				goto truncated;
380 			left -= len;
381 			cur += len;
382 		}
383 		if (rule->mbr_object.mbo_flags & MBO_UID_SUBJECT) {
384 			len = snprintf(cur, left, "uid_of_subject ");
385 			if (len < 0 || len > left)
386 				goto truncated;
387 			left -= len;
388 			cur += len;
389 		}
390 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_GID_SUBJECT)) {
391 			len = snprintf(cur, left, "! ");
392 			if (len < 0 || len > left)
393 				goto truncated;
394 			left -= len;
395 			cur += len;
396 		}
397 		if (rule->mbr_object.mbo_flags & MBO_GID_SUBJECT) {
398 			len = snprintf(cur, left, "gid_of_subject ");
399 			if (len < 0 || len > left)
400 				goto truncated;
401 			left -= len;
402 			cur += len;
403 		}
404 		if (!notdone && (rule->mbr_object.mbo_neg & MBO_TYPE_DEFINED)) {
405 			len = snprintf(cur, left, "! ");
406 			if (len < 0 || len > left)
407 				goto truncated;
408 			left -= len;
409 			cur += len;
410 		}
411 		if (rule->mbr_object.mbo_flags & MBO_TYPE_DEFINED) {
412 			i = 0;
413 			if (rule->mbr_object.mbo_type & MBO_TYPE_REG)
414 				type[i++] = 'r';
415 			if (rule->mbr_object.mbo_type & MBO_TYPE_DIR)
416 				type[i++] = 'd';
417 			if (rule->mbr_object.mbo_type & MBO_TYPE_BLK)
418 				type[i++] = 'b';
419 			if (rule->mbr_object.mbo_type & MBO_TYPE_CHR)
420 				type[i++] = 'c';
421 			if (rule->mbr_object.mbo_type & MBO_TYPE_LNK)
422 				type[i++] = 'l';
423 			if (rule->mbr_object.mbo_type & MBO_TYPE_SOCK)
424 				type[i++] = 's';
425 			if (rule->mbr_object.mbo_type & MBO_TYPE_FIFO)
426 				type[i++] = 'p';
427 			if (rule->mbr_object.mbo_type == MBO_ALL_TYPE) {
428 				i = 0;
429 				type[i++] = 'a';
430 			}
431 			type[i++] = '\0';
432 			len = snprintf(cur, left, "type %s ", type);
433 			if (len < 0 || len > left)
434 				goto truncated;
435 			left -= len;
436 			cur += len;
437 		}
438 	}
439 
440 	len = snprintf(cur, left, "mode ");
441 	if (len < 0 || len > left)
442 		goto truncated;
443 	left -= len;
444 	cur += len;
445 
446 	anymode = (rule->mbr_mode & MBI_ALLPERM);
447 	unknownmode = (rule->mbr_mode & ~MBI_ALLPERM);
448 
449 	if (rule->mbr_mode & MBI_ADMIN) {
450 		len = snprintf(cur, left, "a");
451 		if (len < 0 || len > left)
452 			goto truncated;
453 
454 		left -= len;
455 		cur += len;
456 	}
457 	if (rule->mbr_mode & MBI_READ) {
458 		len = snprintf(cur, left, "r");
459 		if (len < 0 || len > left)
460 			goto truncated;
461 
462 		left -= len;
463 		cur += len;
464 	}
465 	if (rule->mbr_mode & MBI_STAT) {
466 		len = snprintf(cur, left, "s");
467 		if (len < 0 || len > left)
468 			goto truncated;
469 
470 		left -= len;
471 		cur += len;
472 	}
473 	if (rule->mbr_mode & MBI_WRITE) {
474 		len = snprintf(cur, left, "w");
475 		if (len < 0 || len > left)
476 			goto truncated;
477 
478 		left -= len;
479 		cur += len;
480 	}
481 	if (rule->mbr_mode & MBI_EXEC) {
482 		len = snprintf(cur, left, "x");
483 		if (len < 0 || len > left)
484 			goto truncated;
485 
486 		left -= len;
487 		cur += len;
488 	}
489 	if (!anymode) {
490 		len = snprintf(cur, left, "n");
491 		if (len < 0 || len > left)
492 			goto truncated;
493 
494 		left -= len;
495 		cur += len;
496 	}
497 	if (unknownmode) {
498 		len = snprintf(cur, left, "?");
499 		if (len < 0 || len > left)
500 			goto truncated;
501 
502 		left -= len;
503 		cur += len;
504 	}
505 
506 	return (0);
507 
508 truncated:
509 	return (-1);
510 }
511 
512 static int
513 bsde_parse_uidrange(char *spec, uid_t *min, uid_t *max,
514     size_t buflen, char *errstr){
515 	struct passwd *pwd;
516 	uid_t uid1, uid2;
517 	char *spec1, *spec2, *endp;
518 	unsigned long value;
519 
520 	spec2 = spec;
521 	spec1 = strsep(&spec2, ":");
522 
523 	pwd = getpwnam(spec1);
524 	if (pwd != NULL)
525 		uid1 = pwd->pw_uid;
526 	else {
527 		value = strtoul(spec1, &endp, 10);
528 		if (*endp != '\0') {
529 			snprintf(errstr, buflen, "invalid uid: '%s'", spec1);
530 			return (-1);
531 		}
532 		uid1 = value;
533 	}
534 
535 	if (spec2 == NULL) {
536 		*max = *min = uid1;
537 		return (0);
538 	}
539 
540 	pwd = getpwnam(spec2);
541 	if (pwd != NULL)
542 		uid2 = pwd->pw_uid;
543 	else {
544 		value = strtoul(spec2, &endp, 10);
545 		if (*endp != '\0') {
546 			snprintf(errstr, buflen, "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 static 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 
566 	spec2 = spec;
567 	spec1 = strsep(&spec2, ":");
568 
569 	grp = getgrnam(spec1);
570 	if (grp != NULL)
571 		gid1 = grp->gr_gid;
572 	else {
573 		value = strtoul(spec1, &endp, 10);
574 		if (*endp != '\0') {
575 			snprintf(errstr, buflen, "invalid gid: '%s'", spec1);
576 			return (-1);
577 		}
578 		gid1 = value;
579 	}
580 
581 	if (spec2 == NULL) {
582 		*max = *min = gid1;
583 		return (0);
584 	}
585 
586 	grp = getgrnam(spec2);
587 	if (grp != NULL)
588 		gid2 = grp->gr_gid;
589 	else {
590 		value = strtoul(spec2, &endp, 10);
591 		if (*endp != '\0') {
592 			snprintf(errstr, buflen, "invalid gid: '%s'", spec2);
593 			return (-1);
594 		}
595 		gid2 = value;
596 	}
597 
598 	*min = gid1;
599 	*max = gid2;
600 
601 	return (0);
602 }
603 
604 static int
605 bsde_get_jailid(const char *name, size_t buflen, char *errstr)
606 {
607 	char *ep;
608 	int jid;
609 	struct iovec jiov[4];
610 
611 	/* Copy jail_getid(3) instead of messing with library dependancies */
612 	jid = strtoul(name, &ep, 10);
613 	if (*name && !*ep)
614 		return jid;
615 	jiov[0].iov_base = __DECONST(char *, "name");
616 	jiov[0].iov_len = sizeof("name");
617 	jiov[1].iov_len = strlen(name) + 1;
618 	jiov[1].iov_base = alloca(jiov[1].iov_len);
619 	strcpy(jiov[1].iov_base, name);
620 	if (errstr && buflen) {
621 		jiov[2].iov_base = __DECONST(char *, "errmsg");
622 		jiov[2].iov_len = sizeof("errmsg");
623 		jiov[3].iov_base = errstr;
624 		jiov[3].iov_len = buflen;
625 		errstr[0] = 0;
626 		jid = jail_get(jiov, 4, 0);
627 		if (jid < 0 && !errstr[0])
628 			snprintf(errstr, buflen, "jail_get: %s",
629 			    strerror(errno));
630 	} else
631 		jid = jail_get(jiov, 2, 0);
632 	return jid;
633 }
634 
635 static int
636 bsde_parse_subject(int argc, char *argv[],
637     struct mac_bsdextended_subject *subject, size_t buflen, char *errstr)
638 {
639 	int not_seen, flags;
640 	int current, neg, nextnot;
641 	uid_t uid_min, uid_max;
642 	gid_t gid_min, gid_max;
643 	int jid = 0;
644 
645 	current = 0;
646 	flags = 0;
647 	neg = 0;
648 	nextnot = 0;
649 
650 	if (strcmp("not", argv[current]) == 0) {
651 		not_seen = 1;
652 		current++;
653 	} else
654 		not_seen = 0;
655 
656 	while (current < argc) {
657 		if (strcmp(argv[current], "uid") == 0) {
658 			if (current + 2 > argc) {
659 				snprintf(errstr, buflen, "uid short");
660 				return (-1);
661 			}
662 			if (flags & MBS_UID_DEFINED) {
663 				snprintf(errstr, buflen, "one uid only");
664 				return (-1);
665 			}
666 			if (bsde_parse_uidrange(argv[current+1],
667 			    &uid_min, &uid_max, buflen, errstr) < 0)
668 				return (-1);
669 			flags |= MBS_UID_DEFINED;
670 			if (nextnot) {
671 				neg ^= MBS_UID_DEFINED;
672 				nextnot = 0;
673 			}
674 			current += 2;
675 		} else if (strcmp(argv[current], "gid") == 0) {
676 			if (current + 2 > argc) {
677 				snprintf(errstr, buflen, "gid short");
678 				return (-1);
679 			}
680 			if (flags & MBS_GID_DEFINED) {
681 				snprintf(errstr, buflen, "one gid only");
682 				return (-1);
683 			}
684 			if (bsde_parse_gidrange(argv[current+1],
685 			    &gid_min, &gid_max, buflen, errstr) < 0)
686 				return (-1);
687 			flags |= MBS_GID_DEFINED;
688 			if (nextnot) {
689 				neg ^= MBS_GID_DEFINED;
690 				nextnot = 0;
691 			}
692 			current += 2;
693 		} else if (strcmp(argv[current], "jailid") == 0) {
694 			if (current + 2 > argc) {
695 				snprintf(errstr, buflen, "prison short");
696 				return (-1);
697 			}
698 			if (flags & MBS_PRISON_DEFINED) {
699 				snprintf(errstr, buflen, "one jail only");
700 				return (-1);
701 			}
702 			jid = bsde_get_jailid(argv[current+1], buflen, errstr);
703 			if (jid < 0)
704 				return (-1);
705 			flags |= MBS_PRISON_DEFINED;
706 			if (nextnot) {
707 				neg ^= MBS_PRISON_DEFINED;
708 				nextnot = 0;
709 			}
710 			current += 2;
711 		} else if (strcmp(argv[current], "!") == 0) {
712 			if (nextnot) {
713 				snprintf(errstr, buflen, "double negative");
714 				return (-1);
715 			}
716 			nextnot = 1;
717 			current += 1;
718 		} else {
719 			snprintf(errstr, buflen, "'%s' not expected",
720 			    argv[current]);
721 			return (-1);
722 		}
723 	}
724 
725 	subject->mbs_flags = flags;
726 	if (not_seen)
727 		subject->mbs_neg = MBS_ALL_FLAGS ^ neg;
728 	else
729 		subject->mbs_neg = neg;
730 	if (flags & MBS_UID_DEFINED) {
731 		subject->mbs_uid_min = uid_min;
732 		subject->mbs_uid_max = uid_max;
733 	}
734 	if (flags & MBS_GID_DEFINED) {
735 		subject->mbs_gid_min = gid_min;
736 		subject->mbs_gid_max = gid_max;
737 	}
738 	if (flags & MBS_PRISON_DEFINED)
739 		subject->mbs_prison = jid;
740 
741 	return (0);
742 }
743 
744 static int
745 bsde_parse_type(char *spec, int *type, size_t buflen, char *errstr)
746 {
747 	int i;
748 
749 	*type = 0;
750 	for (i = 0; i < strlen(spec); i++) {
751 		switch (spec[i]) {
752 		case 'r':
753 		case '-':
754 			*type |= MBO_TYPE_REG;
755 			break;
756 		case 'd':
757 			*type |= MBO_TYPE_DIR;
758 			break;
759 		case 'b':
760 			*type |= MBO_TYPE_BLK;
761 			break;
762 		case 'c':
763 			*type |= MBO_TYPE_CHR;
764 			break;
765 		case 'l':
766 			*type |= MBO_TYPE_LNK;
767 			break;
768 		case 's':
769 			*type |= MBO_TYPE_SOCK;
770 			break;
771 		case 'p':
772 			*type |= MBO_TYPE_FIFO;
773 			break;
774 		case 'a':
775 			*type |= MBO_ALL_TYPE;
776 			break;
777 		default:
778 			snprintf(errstr, buflen, "Unknown type code: %c",
779 			    spec[i]);
780 			return (-1);
781 		}
782 	}
783 
784 	return (0);
785 }
786 
787 static int
788 bsde_parse_fsid(char *spec, struct fsid *fsid, size_t buflen, char *errstr)
789 {
790 	struct statfs buf;
791 
792 	if (statfs(spec, &buf) < 0) {
793 		snprintf(errstr, buflen, "Unable to get id for %s: %s",
794 		    spec, strerror(errno));
795 		return (-1);
796 	}
797 
798 	*fsid = buf.f_fsid;
799 
800 	return (0);
801 }
802 
803 static int
804 bsde_parse_object(int argc, char *argv[],
805     struct mac_bsdextended_object *object, size_t buflen, char *errstr)
806 {
807 	int not_seen, flags;
808 	int current, neg, nextnot;
809 	int type;
810 	uid_t uid_min, uid_max;
811 	gid_t gid_min, gid_max;
812 	struct fsid fsid;
813 
814 	current = 0;
815 	flags = 0;
816 	neg = 0;
817 	nextnot = 0;
818 	type = 0;
819 
820 	if (strcmp("not", argv[current]) == 0) {
821 		not_seen = 1;
822 		current++;
823 	} else
824 		not_seen = 0;
825 
826 	while (current < argc) {
827 		if (strcmp(argv[current], "uid") == 0) {
828 			if (current + 2 > argc) {
829 				snprintf(errstr, buflen, "uid short");
830 				return (-1);
831 			}
832 			if (flags & MBO_UID_DEFINED) {
833 				snprintf(errstr, buflen, "one uid only");
834 				return (-1);
835 			}
836 			if (bsde_parse_uidrange(argv[current+1],
837 			    &uid_min, &uid_max, buflen, errstr) < 0)
838 				return (-1);
839 			flags |= MBO_UID_DEFINED;
840 			if (nextnot) {
841 				neg ^= MBO_UID_DEFINED;
842 				nextnot = 0;
843 			}
844 			current += 2;
845 		} else if (strcmp(argv[current], "gid") == 0) {
846 			if (current + 2 > argc) {
847 				snprintf(errstr, buflen, "gid short");
848 				return (-1);
849 			}
850 			if (flags & MBO_GID_DEFINED) {
851 				snprintf(errstr, buflen, "one gid only");
852 				return (-1);
853 			}
854 			if (bsde_parse_gidrange(argv[current+1],
855 			    &gid_min, &gid_max, buflen, errstr) < 0)
856 				return (-1);
857 			flags |= MBO_GID_DEFINED;
858 			if (nextnot) {
859 				neg ^= MBO_GID_DEFINED;
860 				nextnot = 0;
861 			}
862 			current += 2;
863 		} else if (strcmp(argv[current], "filesys") == 0) {
864 			if (current + 2 > argc) {
865 				snprintf(errstr, buflen, "filesys short");
866 				return (-1);
867 			}
868 			if (flags & MBO_FSID_DEFINED) {
869 				snprintf(errstr, buflen, "one fsid only");
870 				return (-1);
871 			}
872 			if (bsde_parse_fsid(argv[current+1], &fsid,
873 			    buflen, errstr) < 0)
874 				return (-1);
875 			flags |= MBO_FSID_DEFINED;
876 			if (nextnot) {
877 				neg ^= MBO_FSID_DEFINED;
878 				nextnot = 0;
879 			}
880 			current += 2;
881 		} else if (strcmp(argv[current], "suid") == 0) {
882 			flags |= MBO_SUID;
883 			if (nextnot) {
884 				neg ^= MBO_SUID;
885 				nextnot = 0;
886 			}
887 			current += 1;
888 		} else if (strcmp(argv[current], "sgid") == 0) {
889 			flags |= MBO_SGID;
890 			if (nextnot) {
891 				neg ^= MBO_SGID;
892 				nextnot = 0;
893 			}
894 			current += 1;
895 		} else if (strcmp(argv[current], "uid_of_subject") == 0) {
896 			flags |= MBO_UID_SUBJECT;
897 			if (nextnot) {
898 				neg ^= MBO_UID_SUBJECT;
899 				nextnot = 0;
900 			}
901 			current += 1;
902 		} else if (strcmp(argv[current], "gid_of_subject") == 0) {
903 			flags |= MBO_GID_SUBJECT;
904 			if (nextnot) {
905 				neg ^= MBO_GID_SUBJECT;
906 				nextnot = 0;
907 			}
908 			current += 1;
909 		} else if (strcmp(argv[current], "type") == 0) {
910 			if (current + 2 > argc) {
911 				snprintf(errstr, buflen, "type short");
912 				return (-1);
913 			}
914 			if (flags & MBO_TYPE_DEFINED) {
915 				snprintf(errstr, buflen, "one type only");
916 				return (-1);
917 			}
918 			if (bsde_parse_type(argv[current+1], &type,
919 			    buflen, errstr) < 0)
920 				return (-1);
921 			flags |= MBO_TYPE_DEFINED;
922 			if (nextnot) {
923 				neg ^= MBO_TYPE_DEFINED;
924 				nextnot = 0;
925 			}
926 			current += 2;
927 		} else if (strcmp(argv[current], "!") == 0) {
928 			if (nextnot) {
929 				snprintf(errstr, buflen,
930 				    "double negative'");
931 				return (-1);
932 			}
933 			nextnot = 1;
934 			current += 1;
935 		} else {
936 			snprintf(errstr, buflen, "'%s' not expected",
937 			    argv[current]);
938 			return (-1);
939 		}
940 	}
941 
942 	object->mbo_flags = flags;
943 	if (not_seen)
944 		object->mbo_neg = MBO_ALL_FLAGS ^ neg;
945 	else
946 		object->mbo_neg = neg;
947 	if (flags & MBO_UID_DEFINED) {
948 		object->mbo_uid_min = uid_min;
949 		object->mbo_uid_max = uid_max;
950 	}
951 	if (flags & MBO_GID_DEFINED) {
952 		object->mbo_gid_min = gid_min;
953 		object->mbo_gid_max = gid_max;
954 	}
955 	if (flags & MBO_FSID_DEFINED)
956 		object->mbo_fsid = fsid;
957 	if (flags & MBO_TYPE_DEFINED)
958 		object->mbo_type = type;
959 
960 	return (0);
961 }
962 
963 int
964 bsde_parse_mode(int argc, char *argv[], mode_t *mode, size_t buflen,
965     char *errstr)
966 {
967 	int i;
968 
969 	if (argc == 0) {
970 		snprintf(errstr, buflen, "mode expects mode value");
971 		return (-1);
972 	}
973 
974 	if (argc != 1) {
975 		snprintf(errstr, buflen, "'%s' unexpected", argv[1]);
976 		return (-1);
977 	}
978 
979 	*mode = 0;
980 	for (i = 0; i < strlen(argv[0]); i++) {
981 		switch (argv[0][i]) {
982 		case 'a':
983 			*mode |= MBI_ADMIN;
984 			break;
985 		case 'r':
986 			*mode |= MBI_READ;
987 			break;
988 		case 's':
989 			*mode |= MBI_STAT;
990 			break;
991 		case 'w':
992 			*mode |= MBI_WRITE;
993 			break;
994 		case 'x':
995 			*mode |= MBI_EXEC;
996 			break;
997 		case 'n':
998 			/* ignore */
999 			break;
1000 		default:
1001 			snprintf(errstr, buflen, "Unknown mode letter: %c",
1002 			    argv[0][i]);
1003 			return (-1);
1004 		}
1005 	}
1006 
1007 	return (0);
1008 }
1009 
1010 int
1011 bsde_parse_rule(int argc, char *argv[], struct mac_bsdextended_rule *rule,
1012     size_t buflen, char *errstr)
1013 {
1014 	int subject, subject_elements, subject_elements_length;
1015 	int object, object_elements, object_elements_length;
1016 	int mode, mode_elements, mode_elements_length;
1017 	int error, i;
1018 
1019 	bzero(rule, sizeof(*rule));
1020 
1021 	if (argc < 1) {
1022 		snprintf(errstr, buflen, "Rule must begin with subject");
1023 		return (-1);
1024 	}
1025 
1026 	if (strcmp(argv[0], "subject") != 0) {
1027 		snprintf(errstr, buflen, "Rule must begin with subject");
1028 		return (-1);
1029 	}
1030 	subject = 0;
1031 	subject_elements = 1;
1032 
1033 	/* Search forward for object. */
1034 
1035 	object = -1;
1036 	for (i = 1; i < argc; i++)
1037 		if (strcmp(argv[i], "object") == 0)
1038 			object = i;
1039 
1040 	if (object == -1) {
1041 		snprintf(errstr, buflen, "Rule must contain an object");
1042 		return (-1);
1043 	}
1044 
1045 	/* Search forward for mode. */
1046 	mode = -1;
1047 	for (i = object; i < argc; i++)
1048 		if (strcmp(argv[i], "mode") == 0)
1049 			mode = i;
1050 
1051 	if (mode == -1) {
1052 		snprintf(errstr, buflen, "Rule must contain mode");
1053 		return (-1);
1054 	}
1055 
1056 	subject_elements_length = object - subject - 1;
1057 	object_elements = object + 1;
1058 	object_elements_length = mode - object_elements;
1059 	mode_elements = mode + 1;
1060 	mode_elements_length = argc - mode_elements;
1061 
1062 	error = bsde_parse_subject(subject_elements_length,
1063 	    argv + subject_elements, &rule->mbr_subject, buflen, errstr);
1064 	if (error)
1065 		return (-1);
1066 
1067 	error = bsde_parse_object(object_elements_length,
1068 	    argv + object_elements, &rule->mbr_object, buflen, errstr);
1069 	if (error)
1070 		return (-1);
1071 
1072 	error = bsde_parse_mode(mode_elements_length, argv + mode_elements,
1073 	    &rule->mbr_mode, buflen, errstr);
1074 	if (error)
1075 		return (-1);
1076 
1077 	return (0);
1078 }
1079 
1080 int
1081 bsde_parse_rule_string(const char *string, struct mac_bsdextended_rule *rule,
1082     size_t buflen, char *errstr)
1083 {
1084 	char *stringdup, *stringp, *argv[100], **ap;
1085 	int argc, error;
1086 
1087 	stringp = stringdup = strdup(string);
1088 	while (*stringp == ' ' || *stringp == '\t')
1089 		stringp++;
1090 
1091 	argc = 0;
1092 	for (ap = argv; (*ap = strsep(&stringp, " \t")) != NULL;) {
1093 		argc++;
1094 		if (**ap != '\0')
1095 			if (++ap >= &argv[100])
1096 				break;
1097 	}
1098 
1099 	error = bsde_parse_rule(argc, argv, rule, buflen, errstr);
1100 
1101 	free(stringdup);
1102 
1103 	return (error);
1104 }
1105 
1106 int
1107 bsde_get_mib(const char *string, int *name, size_t *namelen)
1108 {
1109 	size_t len;
1110 	int error;
1111 
1112 	len = *namelen;
1113 	error = sysctlnametomib(string, name, &len);
1114 	if (error)
1115 		return (error);
1116 
1117 	*namelen = len;
1118 	return (0);
1119 }
1120 
1121 static int
1122 bsde_check_version(size_t buflen, char *errstr)
1123 {
1124 	size_t len;
1125 	int error;
1126 	int version;
1127 
1128 	len = sizeof(version);
1129 	error = sysctlbyname(MIB ".rule_version", &version, &len, NULL, 0);
1130 	if (error) {
1131 		snprintf(errstr, buflen, "version check failed: %s",
1132 		    strerror(errno));
1133 		return (-1);
1134 	}
1135 	if (version != MB_VERSION) {
1136 		snprintf(errstr, buflen, "module v%d != library v%d",
1137 		    version, MB_VERSION);
1138 		return (-1);
1139 	}
1140 	return (0);
1141 }
1142 
1143 int
1144 bsde_get_rule_count(size_t buflen, char *errstr)
1145 {
1146 	size_t len;
1147 	int error;
1148 	int rule_count;
1149 
1150 	len = sizeof(rule_count);
1151 	error = sysctlbyname(MIB ".rule_count", &rule_count, &len, NULL, 0);
1152 	if (error) {
1153 		snprintf(errstr, buflen, "%s", strerror(errno));
1154 		return (-1);
1155 	}
1156 	if (len != sizeof(rule_count)) {
1157 		snprintf(errstr, buflen, "Data error in %s.rule_count",
1158 		    MIB);
1159 		return (-1);
1160 	}
1161 
1162 	return (rule_count);
1163 }
1164 
1165 int
1166 bsde_get_rule_slots(size_t buflen, char *errstr)
1167 {
1168 	size_t len;
1169 	int error;
1170 	int rule_slots;
1171 
1172 	len = sizeof(rule_slots);
1173 	error = sysctlbyname(MIB ".rule_slots", &rule_slots, &len, NULL, 0);
1174 	if (error) {
1175 		snprintf(errstr, buflen, "%s", strerror(errno));
1176 		return (-1);
1177 	}
1178 	if (len != sizeof(rule_slots)) {
1179 		snprintf(errstr, buflen, "Data error in %s.rule_slots", MIB);
1180 		return (-1);
1181 	}
1182 
1183 	return (rule_slots);
1184 }
1185 
1186 /*
1187  * Returns 0 for success;
1188  * Returns -1 for failure;
1189  * Returns -2 for not present
1190  */
1191 int
1192 bsde_get_rule(int rulenum, struct mac_bsdextended_rule *rule, size_t errlen,
1193     char *errstr)
1194 {
1195 	int name[10];
1196 	size_t len, size;
1197 	int error;
1198 
1199 	if (bsde_check_version(errlen, errstr) != 0)
1200 		return (-1);
1201 
1202 	len = 10;
1203 	error = bsde_get_mib(MIB ".rules", name, &len);
1204 	if (error) {
1205 		snprintf(errstr, errlen, "%s: %s", MIB ".rules",
1206 		    strerror(errno));
1207 		return (-1);
1208 	}
1209 
1210 	size = sizeof(*rule);
1211 	name[len] = rulenum;
1212 	len++;
1213 	error = sysctl(name, len, rule, &size, NULL, 0);
1214 	if (error  == -1 && errno == ENOENT)
1215 		return (-2);
1216 	if (error) {
1217 		snprintf(errstr, errlen, "%s.%d: %s", MIB ".rules",
1218 		    rulenum, strerror(errno));
1219 		return (-1);
1220 	} else if (size != sizeof(*rule)) {
1221 		snprintf(errstr, errlen, "Data error in %s.%d: %s",
1222 		    MIB ".rules", rulenum, strerror(errno));
1223 		return (-1);
1224 	}
1225 
1226 	return (0);
1227 }
1228 
1229 int
1230 bsde_delete_rule(int rulenum, size_t buflen, char *errstr)
1231 {
1232 	struct mac_bsdextended_rule rule;
1233 	int name[10];
1234 	size_t len;
1235 	int error;
1236 
1237 	if (bsde_check_version(buflen, errstr) != 0)
1238 		return (-1);
1239 
1240 	len = 10;
1241 	error = bsde_get_mib(MIB ".rules", name, &len);
1242 	if (error) {
1243 		snprintf(errstr, buflen, "%s: %s", MIB ".rules",
1244 		    strerror(errno));
1245 		return (-1);
1246 	}
1247 
1248 	name[len] = rulenum;
1249 	len++;
1250 
1251 	error = sysctl(name, len, NULL, NULL, &rule, 0);
1252 	if (error) {
1253 		snprintf(errstr, buflen, "%s.%d: %s", MIB ".rules",
1254 		    rulenum, strerror(errno));
1255 		return (-1);
1256 	}
1257 
1258 	return (0);
1259 }
1260 
1261 int
1262 bsde_set_rule(int rulenum, struct mac_bsdextended_rule *rule, size_t buflen,
1263     char *errstr)
1264 {
1265 	int name[10];
1266 	size_t len;
1267 	int error;
1268 
1269 	if (bsde_check_version(buflen, errstr) != 0)
1270 		return (-1);
1271 
1272 	len = 10;
1273 	error = bsde_get_mib(MIB ".rules", name, &len);
1274 	if (error) {
1275 		snprintf(errstr, buflen, "%s: %s", MIB ".rules",
1276 		    strerror(errno));
1277 		return (-1);
1278 	}
1279 
1280 	name[len] = rulenum;
1281 	len++;
1282 
1283 	error = sysctl(name, len, NULL, NULL, rule, sizeof(*rule));
1284 	if (error) {
1285 		snprintf(errstr, buflen, "%s.%d: %s", MIB ".rules",
1286 		    rulenum, strerror(errno));
1287 		return (-1);
1288 	}
1289 
1290 	return (0);
1291 }
1292 
1293 int
1294 bsde_add_rule(int *rulenum, struct mac_bsdextended_rule *rule, size_t buflen,
1295     char *errstr)
1296 {
1297 	char charstr[BUFSIZ];
1298 	int name[10];
1299 	size_t len;
1300 	int error, rule_slots;
1301 
1302 	if (bsde_check_version(buflen, errstr) != 0)
1303 		return (-1);
1304 
1305 	len = 10;
1306 	error = bsde_get_mib(MIB ".rules", name, &len);
1307 	if (error) {
1308 		snprintf(errstr, buflen, "%s: %s", MIB ".rules",
1309 		    strerror(errno));
1310 		return (-1);
1311 	}
1312 
1313 	rule_slots = bsde_get_rule_slots(BUFSIZ, charstr);
1314 	if (rule_slots == -1) {
1315 		snprintf(errstr, buflen, "unable to get rule slots: %s",
1316 		    strerror(errno));
1317 		return (-1);
1318 	}
1319 
1320 	name[len] = rule_slots;
1321 	len++;
1322 
1323 	error = sysctl(name, len, NULL, NULL, rule, sizeof(*rule));
1324 	if (error) {
1325 		snprintf(errstr, buflen, "%s.%d: %s", MIB ".rules",
1326 		    rule_slots, strerror(errno));
1327 		return (-1);
1328 	}
1329 
1330 	if (rulenum != NULL)
1331 		*rulenum = rule_slots;
1332 
1333 	return (0);
1334 }
1335