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