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