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