aboutsummaryrefslogtreecommitdiff
path: root/lib/libcasper
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libcasper')
-rw-r--r--lib/libcasper/Makefile10
-rw-r--r--lib/libcasper/Makefile.inc7
-rw-r--r--lib/libcasper/libcasper/Makefile43
-rw-r--r--lib/libcasper/libcasper/Makefile.depend16
-rw-r--r--lib/libcasper/libcasper/libcasper.3317
-rw-r--r--lib/libcasper/libcasper/libcasper.c357
-rw-r--r--lib/libcasper/libcasper/libcasper.h303
-rw-r--r--lib/libcasper/libcasper/libcasper_impl.c71
-rw-r--r--lib/libcasper/libcasper/libcasper_impl.h84
-rw-r--r--lib/libcasper/libcasper/libcasper_service.3119
-rw-r--r--lib/libcasper/libcasper/libcasper_service.c289
-rw-r--r--lib/libcasper/libcasper/libcasper_service.h65
-rw-r--r--lib/libcasper/libcasper/service.c472
-rw-r--r--lib/libcasper/libcasper/zygote.c219
-rw-r--r--lib/libcasper/libcasper/zygote.h47
-rw-r--r--lib/libcasper/services/Makefile16
-rw-r--r--lib/libcasper/services/Makefile.depend0
-rw-r--r--lib/libcasper/services/Makefile.inc1
-rw-r--r--lib/libcasper/services/cap_dns/Makefile31
-rw-r--r--lib/libcasper/services/cap_dns/Makefile.depend17
-rw-r--r--lib/libcasper/services/cap_dns/cap_dns.3241
-rw-r--r--lib/libcasper/services/cap_dns/cap_dns.c767
-rw-r--r--lib/libcasper/services/cap_dns/cap_dns.h131
-rw-r--r--lib/libcasper/services/cap_dns/tests/Makefile12
-rw-r--r--lib/libcasper/services/cap_dns/tests/Makefile.depend20
-rw-r--r--lib/libcasper/services/cap_dns/tests/dns_test.c703
-rw-r--r--lib/libcasper/services/cap_fileargs/Makefile38
-rw-r--r--lib/libcasper/services/cap_fileargs/Makefile.depend17
-rw-r--r--lib/libcasper/services/cap_fileargs/cap_fileargs.3300
-rw-r--r--lib/libcasper/services/cap_fileargs/cap_fileargs.c737
-rw-r--r--lib/libcasper/services/cap_fileargs/cap_fileargs.h153
-rw-r--r--lib/libcasper/services/cap_fileargs/tests/Makefile16
-rw-r--r--lib/libcasper/services/cap_fileargs/tests/fileargs_test.c777
-rw-r--r--lib/libcasper/services/cap_grp/Makefile44
-rw-r--r--lib/libcasper/services/cap_grp/Makefile.depend17
-rw-r--r--lib/libcasper/services/cap_grp/cap_grp.3236
-rw-r--r--lib/libcasper/services/cap_grp/cap_grp.c788
-rw-r--r--lib/libcasper/services/cap_grp/cap_grp.h93
-rw-r--r--lib/libcasper/services/cap_grp/tests/Makefile12
-rw-r--r--lib/libcasper/services/cap_grp/tests/Makefile.depend18
-rw-r--r--lib/libcasper/services/cap_grp/tests/grp_test.c1556
-rw-r--r--lib/libcasper/services/cap_net/Makefile46
-rw-r--r--lib/libcasper/services/cap_net/Makefile.depend17
-rw-r--r--lib/libcasper/services/cap_net/cap_net.3292
-rw-r--r--lib/libcasper/services/cap_net/cap_net.c1438
-rw-r--r--lib/libcasper/services/cap_net/cap_net.h159
-rw-r--r--lib/libcasper/services/cap_net/tests/Makefile14
-rw-r--r--lib/libcasper/services/cap_net/tests/net_test.c1487
-rw-r--r--lib/libcasper/services/cap_netdb/Makefile33
-rw-r--r--lib/libcasper/services/cap_netdb/Makefile.depend17
-rw-r--r--lib/libcasper/services/cap_netdb/cap_netdb.391
-rw-r--r--lib/libcasper/services/cap_netdb/cap_netdb.c153
-rw-r--r--lib/libcasper/services/cap_netdb/cap_netdb.h47
-rw-r--r--lib/libcasper/services/cap_netdb/tests/Makefile12
-rw-r--r--lib/libcasper/services/cap_netdb/tests/netdb_test.c91
-rw-r--r--lib/libcasper/services/cap_pwd/Makefile44
-rw-r--r--lib/libcasper/services/cap_pwd/Makefile.depend17
-rw-r--r--lib/libcasper/services/cap_pwd/cap_pwd.3242
-rw-r--r--lib/libcasper/services/cap_pwd/cap_pwd.c781
-rw-r--r--lib/libcasper/services/cap_pwd/cap_pwd.h159
-rw-r--r--lib/libcasper/services/cap_pwd/tests/Makefile12
-rw-r--r--lib/libcasper/services/cap_pwd/tests/Makefile.depend18
-rw-r--r--lib/libcasper/services/cap_pwd/tests/pwd_test.c1538
-rw-r--r--lib/libcasper/services/cap_sysctl/Makefile34
-rw-r--r--lib/libcasper/services/cap_sysctl/Makefile.depend17
-rw-r--r--lib/libcasper/services/cap_sysctl/cap_sysctl.3227
-rw-r--r--lib/libcasper/services/cap_sysctl/cap_sysctl.c531
-rw-r--r--lib/libcasper/services/cap_sysctl/cap_sysctl.h123
-rw-r--r--lib/libcasper/services/cap_sysctl/tests/Makefile17
-rw-r--r--lib/libcasper/services/cap_sysctl/tests/Makefile.depend19
-rw-r--r--lib/libcasper/services/cap_sysctl/tests/sysctl_test.c1689
-rw-r--r--lib/libcasper/services/cap_syslog/Makefile30
-rw-r--r--lib/libcasper/services/cap_syslog/Makefile.depend17
-rw-r--r--lib/libcasper/services/cap_syslog/cap_syslog.3115
-rw-r--r--lib/libcasper/services/cap_syslog/cap_syslog.c222
-rw-r--r--lib/libcasper/services/cap_syslog/cap_syslog.h59
-rw-r--r--lib/libcasper/services/tests/Makefile4
-rw-r--r--lib/libcasper/services/tests/Makefile.depend10
-rw-r--r--lib/libcasper/tests/Makefile4
79 files changed, 18986 insertions, 0 deletions
diff --git a/lib/libcasper/Makefile b/lib/libcasper/Makefile
new file mode 100644
index 000000000000..0a1074d4613d
--- /dev/null
+++ b/lib/libcasper/Makefile
@@ -0,0 +1,10 @@
+.include <src.opts.mk>
+
+SUBDIR= libcasper
+SUBDIR+= services
+
+SUBDIR.${MK_TESTS}+= tests
+
+SUBDIR_PARALLEL=
+
+.include <bsd.subdir.mk>
diff --git a/lib/libcasper/Makefile.inc b/lib/libcasper/Makefile.inc
new file mode 100644
index 000000000000..00bd221feb27
--- /dev/null
+++ b/lib/libcasper/Makefile.inc
@@ -0,0 +1,7 @@
+.include <src.opts.mk>
+
+.if ${MK_CASPER} != "no"
+CFLAGS+=-DWITH_CASPER
+.endif
+
+.include "../Makefile.inc"
diff --git a/lib/libcasper/libcasper/Makefile b/lib/libcasper/libcasper/Makefile
new file mode 100644
index 000000000000..4db26f665f19
--- /dev/null
+++ b/lib/libcasper/libcasper/Makefile
@@ -0,0 +1,43 @@
+PACKAGE= runtime
+
+SHLIBDIR?= /lib
+
+.include <src.opts.mk>
+
+.if ${MK_CASPER} != "no"
+SHLIB= casper
+SHLIB_MAJOR= 1
+
+SRCS= libcasper.c
+SRCS+= libcasper_impl.c
+SRCS+= libcasper_service.c
+SRCS+= service.c
+SRCS+= zygote.c
+.endif
+
+INCS= libcasper.h
+INCS+= libcasper_service.h
+
+LIBADD= nv
+
+CFLAGS+=-I${.CURDIR}
+
+MAN+= libcasper.3
+MAN+= libcasper_service.3
+
+MLINKS+=libcasper.3 cap_init.3
+MLINKS+=libcasper.3 cap_wrap.3
+MLINKS+=libcasper.3 cap_unwrap.3
+MLINKS+=libcasper.3 cap_sock.3
+MLINKS+=libcasper.3 cap_clone.3
+MLINKS+=libcasper.3 cap_close.3
+MLINKS+=libcasper.3 cap_limit_get.3
+MLINKS+=libcasper.3 cap_limit_set.3
+MLINKS+=libcasper.3 cap_send_nvlist.3
+MLINKS+=libcasper.3 cap_recv_nvlist.3
+MLINKS+=libcasper.3 cap_xfer_nvlist.3
+MLINKS+=libcasper.3 cap_service_open.3
+
+MLINKS+=libcasper_service.3 CREATE_SERVICE.3
+
+.include <bsd.lib.mk>
diff --git a/lib/libcasper/libcasper/Makefile.depend b/lib/libcasper/libcasper/Makefile.depend
new file mode 100644
index 000000000000..d223a871a521
--- /dev/null
+++ b/lib/libcasper/libcasper/Makefile.depend
@@ -0,0 +1,16 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/libcasper/libcasper.3 b/lib/libcasper/libcasper/libcasper.3
new file mode 100644
index 000000000000..15f231d7e366
--- /dev/null
+++ b/lib/libcasper/libcasper/libcasper.3
@@ -0,0 +1,317 @@
+.\" Copyright (c) 2013 The FreeBSD Foundation
+.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" This documentation was written by Pawel Jakub Dawidek under sponsorship
+.\" from the FreeBSD Foundation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd December 6, 2023
+.Dt LIBCASPER 3
+.Os
+.Sh NAME
+.Nm cap_init ,
+.Nm cap_wrap ,
+.Nm cap_unwrap ,
+.Nm cap_sock ,
+.Nm cap_clone ,
+.Nm cap_close ,
+.Nm cap_limit_get ,
+.Nm cap_limit_set ,
+.Nm cap_send_nvlist ,
+.Nm cap_recv_nvlist ,
+.Nm cap_xfer_nvlist ,
+.Nm cap_service_open
+.Nd "library for handling application capabilities"
+.Sh LIBRARY
+.Lb libcasper
+.Sh SYNOPSIS
+.Fd #define WITH_CASPER
+.In sys/nv.h
+.In libcasper.h
+.Ft "cap_channel_t *"
+.Fn cap_init "void"
+.Ft "cap_channel_t *"
+.Fn cap_wrap "int sock" "int flags"
+.Ft "int"
+.Fn cap_unwrap "cap_channel_t *chan" "int *flags"
+.Ft "int"
+.Fn cap_sock "const cap_channel_t *chan"
+.Ft "cap_channel_t *"
+.Fn cap_clone "const cap_channel_t *chan"
+.Ft "void"
+.Fn cap_close "cap_channel_t *chan"
+.Ft "int"
+.Fn cap_limit_get "const cap_channel_t *chan" "nvlist_t **limitsp"
+.Ft "int"
+.Fn cap_limit_set "const cap_channel_t *chan" "nvlist_t *limits"
+.Ft "int"
+.Fn cap_send_nvlist "const cap_channel_t *chan" "const nvlist_t *nvl"
+.Ft "nvlist_t *"
+.Fn cap_recv_nvlist "const cap_channel_t *chan"
+.Ft "nvlist_t *"
+.Fn cap_xfer_nvlist "const cap_channel_t *chan" "nvlist_t *nvl"
+.Ft "cap_channel_t *"
+.Fn cap_service_open "const cap_channel_t *chan" "const char *name"
+.Sh DESCRIPTION
+The
+.Nm libcasper
+library provides for the control of application capabilities through
+the casper process.
+.Pp
+An application capability, represented by the
+.Vt cap_channel_t
+type, is a communication channel between the caller and the casper
+daemon or an instance of one of the daemon's services.
+A capability to the casper process, obtained with the
+.Fn cap_init
+function, allows a program to create capabilities to access
+the casper daemon's services via the
+.Fn cap_service_open
+function.
+.Pp
+The
+.Fn cap_init
+function instantiates a capability to allow a program to access
+the casper daemon.
+.Pp
+The
+.Fn cap_wrap
+function creates a
+.Vt cap_channel_t
+based on the socket supplied in the call.
+The function is used when a capability is inherited through the
+.Xr execve 2
+system call,
+or sent over a
+.Xr unix 4
+domain socket as a file descriptor,
+and has to be converted into a
+.Vt cap_channel_t .
+The
+.Fa flags
+argument defines the channel behavior.
+The supported flags are:
+.Bl -ohang -offset indent
+.It CASPER_NO_UNIQ
+The communication between the process and the casper daemon uses no
+unique version of nvlist.
+.El
+.Pp
+The
+.Fn cap_unwrap
+function returns the
+.Xr unix 4
+domain socket used by the daemon service,
+and frees the
+.Vt cap_channel_t
+structure.
+.Pp
+The
+.Fn cap_clone
+function returns a clone of the capability passed as its only argument.
+.Pp
+The
+.Fn cap_close
+function closes, and frees, the given capability.
+.Pp
+The
+.Fn cap_sock
+function returns the
+.Xr unix 4
+domain socket descriptor associated with the given capability for use with
+system calls such as:
+.Xr kevent 2 ,
+.Xr poll 2 ,
+and
+.Xr select 2 .
+.Pp
+The
+.Fn cap_limit_get
+function stores the current limits of the given capability in the
+.Fa limitsp
+argument.
+If the function returns
+.Va 0
+and
+.Dv NULL
+is stored in the
+.Fa limitsp
+argument,
+there are no limits set.
+.Pp
+The
+.Fn cap_limit_set
+function sets limits for the given capability.
+The limits are provided as an
+.Xr nvlist 9 .
+The exact format of the limits depends on the service that the
+capability represents.
+.Fn cap_limit_set
+frees the limits passed to the call,
+whether or not the operation succeeds or fails.
+.Pp
+The
+.Fn cap_send_nvlist
+function sends the given
+.Xr nvlist 9
+over the given capability.
+This is a low level interface to communicate with casper services.
+It is expected that most services will provide a higher level API.
+.Pp
+The
+.Fn cap_recv_nvlist
+function receives the given
+.Xr nvlist 9
+over the given capability.
+.Pp
+The
+.Fn cap_xfer_nvlist
+function sends the given
+.Xr nvlist 9 ,
+destroys it,
+and receives a new
+.Xr nvlist 9
+in response over the given capability.
+It does not matter if the function succeeds or fails, the
+.Xr nvlist 9
+given for sending will always be destroyed before the function returns.
+.Pp
+The
+.Fn cap_service_open
+function opens the casper service named in the call using
+the casper capability obtained via the
+.Fn cap_init
+function.
+The
+.Fn cap_service_open
+function returns a capability that provides access to the opened service.
+Casper supports the following services in the base system:
+.Pp
+.Bl -tag -width "system.random" -compact -offset indent
+.It system.dns
+provides libc compatible DNS API
+.It system.fileargs
+provides an API for opening files specified on a command line
+.It system.grp
+provides a
+.Xr getgrent 3
+compatible API
+.It system.net
+provides a libc compatible network API
+.It system.netdb
+provides libc compatible network proto API
+.It system.pwd
+provides a
+.Xr getpwent 3
+compatible API
+.It system.sysctl
+provides a
+.Xr sysctlbyname 3
+compatible API
+.It system.syslog
+provides a
+.Xr syslog 3
+compatible API
+.El
+.Pp
+.Fn cap_init
+must be called from a single-threaded context.
+.Fn cap_clone ,
+.Fn cap_close ,
+.Fn cap_limit_get ,
+.Fn cap_limit_set ,
+.Fn cap_send_nvlist ,
+.Fn cap_recv_nvlist ,
+and
+.Fn cap_service_open
+are reentrant but not thread-safe.
+That is, they may be called from separate threads only with different
+.Vt cap_channel_t
+arguments or with synchronization.
+.Sh RETURN VALUES
+The
+.Fn cap_clone ,
+.Fn cap_init ,
+.Fn cap_recv_nvlist ,
+.Fn cap_service_open ,
+.Fn cap_wrap
+and
+.Fn cap_xfer_nvlist
+functions return
+.Dv NULL
+and set the
+.Va errno
+variable on failure.
+.Pp
+The
+.Fn cap_limit_get ,
+.Fn cap_limit_set
+and
+.Fn cap_send_nvlist
+functions return
+.Dv -1
+and set the
+.Va errno
+variable on failure.
+.Pp
+The
+.Fn cap_close ,
+.Fn cap_sock
+and
+.Fn cap_unwrap
+functions always succeed.
+.Sh SEE ALSO
+.Xr errno 2 ,
+.Xr execve 2 ,
+.Xr kevent 2 ,
+.Xr poll 2 ,
+.Xr select 2 ,
+.Xr cap_dns 3 ,
+.Xr cap_fileargs 3 ,
+.Xr cap_grp 3 ,
+.Xr cap_net 3 ,
+.Xr cap_netdb 3 ,
+.Xr cap_pwd 3 ,
+.Xr cap_sysctl 3 ,
+.Xr cap_syslog 3 ,
+.Xr libcasper_service 3 ,
+.Xr capsicum 4 ,
+.Xr unix 4 ,
+.Xr nv 9
+.Sh HISTORY
+The
+.Nm libcasper
+library first appeared in
+.Fx 10.3 .
+.Sh AUTHORS
+The
+.Nm libcasper
+library was implemented by
+.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net
+under sponsorship from the FreeBSD Foundation.
+The
+.Nm libcasper
+new architecture was implemented by
+.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org
+.
diff --git a/lib/libcasper/libcasper/libcasper.c b/lib/libcasper/libcasper/libcasper.c
new file mode 100644
index 000000000000..9cc008cdb7bc
--- /dev/null
+++ b/lib/libcasper/libcasper/libcasper.c
@@ -0,0 +1,357 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012-2013 The FreeBSD Foundation
+ * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/nv.h>
+#include <sys/procdesc.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "libcasper.h"
+#include "libcasper_impl.h"
+
+#define CASPER_VALID_FLAGS (CASPER_NO_UNIQ)
+
+/*
+ * Structure describing communication channel between two separated processes.
+ */
+#define CAP_CHANNEL_MAGIC 0xcac8a31
+struct cap_channel {
+ /*
+ * Magic value helps to ensure that a pointer to the right structure is
+ * passed to our functions.
+ */
+ int cch_magic;
+ /* Socket descriptor for IPC. */
+ int cch_sock;
+ /* Process descriptor for casper. */
+ int cch_pd;
+ /* Flags to communicate with casper. */
+ int cch_flags;
+};
+
+static bool
+cap_add_pd(cap_channel_t *chan, int pd)
+{
+
+ if (!fd_is_valid(pd))
+ return (false);
+ chan->cch_pd = pd;
+ return (true);
+}
+
+int
+cap_channel_flags(const cap_channel_t *chan)
+{
+
+ return (chan->cch_flags);
+}
+
+cap_channel_t *
+cap_init(void)
+{
+ pid_t pid;
+ int sock[2], serrno, pfd;
+ bool ret;
+ cap_channel_t *chan;
+
+ if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0,
+ sock) == -1) {
+ return (NULL);
+ }
+
+ pid = pdfork(&pfd, 0);
+ if (pid == 0) {
+ /* Child. */
+ close(sock[0]);
+ casper_main_loop(sock[1]);
+ /* NOTREACHED. */
+ } else if (pid > 0) {
+ /* Parent. */
+ close(sock[1]);
+ chan = cap_wrap(sock[0], 0);
+ if (chan == NULL) {
+ serrno = errno;
+ close(sock[0]);
+ close(pfd);
+ errno = serrno;
+ return (NULL);
+ }
+ ret = cap_add_pd(chan, pfd);
+ assert(ret);
+ return (chan);
+ }
+
+ /* Error. */
+ serrno = errno;
+ close(sock[0]);
+ close(sock[1]);
+ errno = serrno;
+ return (NULL);
+}
+
+cap_channel_t *
+cap_wrap(int sock, int flags)
+{
+ cap_channel_t *chan;
+
+ if (!fd_is_valid(sock))
+ return (NULL);
+
+ if ((flags & CASPER_VALID_FLAGS) != flags)
+ return (NULL);
+
+ chan = malloc(sizeof(*chan));
+ if (chan != NULL) {
+ chan->cch_sock = sock;
+ chan->cch_pd = -1;
+ chan->cch_flags = flags;
+ chan->cch_magic = CAP_CHANNEL_MAGIC;
+ }
+
+ return (chan);
+}
+
+int
+cap_unwrap(cap_channel_t *chan, int *flags)
+{
+ int sock;
+
+ assert(chan != NULL);
+ assert(chan->cch_magic == CAP_CHANNEL_MAGIC);
+
+ sock = chan->cch_sock;
+ if (chan->cch_pd != -1)
+ close(chan->cch_pd);
+ if (flags != NULL)
+ *flags = chan->cch_flags;
+ chan->cch_magic = 0;
+ free(chan);
+
+ return (sock);
+}
+
+cap_channel_t *
+cap_clone(const cap_channel_t *chan)
+{
+ cap_channel_t *newchan;
+ nvlist_t *nvl;
+ int newsock;
+
+ assert(chan != NULL);
+ assert(chan->cch_magic == CAP_CHANNEL_MAGIC);
+
+ nvl = nvlist_create(channel_nvlist_flags(chan));
+ nvlist_add_string(nvl, "cmd", "clone");
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (NULL);
+ if (nvlist_get_number(nvl, "error") != 0) {
+ errno = (int)nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ return (NULL);
+ }
+ newsock = nvlist_take_descriptor(nvl, "sock");
+ nvlist_destroy(nvl);
+ newchan = cap_wrap(newsock, chan->cch_flags);
+ if (newchan == NULL) {
+ int serrno;
+
+ serrno = errno;
+ close(newsock);
+ errno = serrno;
+ }
+
+ return (newchan);
+}
+
+void
+cap_close(cap_channel_t *chan)
+{
+
+ assert(chan != NULL);
+ assert(chan->cch_magic == CAP_CHANNEL_MAGIC);
+
+ chan->cch_magic = 0;
+ if (chan->cch_pd != -1)
+ close(chan->cch_pd);
+ close(chan->cch_sock);
+ free(chan);
+}
+
+int
+cap_sock(const cap_channel_t *chan)
+{
+
+ assert(chan != NULL);
+ assert(chan->cch_magic == CAP_CHANNEL_MAGIC);
+
+ return (chan->cch_sock);
+}
+
+int
+cap_limit_set(const cap_channel_t *chan, nvlist_t *limits)
+{
+ nvlist_t *nvlmsg;
+ int error;
+
+ nvlmsg = nvlist_create(channel_nvlist_flags(chan));
+ nvlist_add_string(nvlmsg, "cmd", "limit_set");
+ nvlist_add_nvlist(nvlmsg, "limits", limits);
+ nvlmsg = cap_xfer_nvlist(chan, nvlmsg);
+ if (nvlmsg == NULL) {
+ nvlist_destroy(limits);
+ return (-1);
+ }
+ error = (int)nvlist_get_number(nvlmsg, "error");
+ nvlist_destroy(nvlmsg);
+ nvlist_destroy(limits);
+ if (error != 0) {
+ errno = error;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+cap_limit_get(const cap_channel_t *chan, nvlist_t **limitsp)
+{
+ nvlist_t *nvlmsg;
+ int error;
+
+ nvlmsg = nvlist_create(channel_nvlist_flags(chan));
+ nvlist_add_string(nvlmsg, "cmd", "limit_get");
+ nvlmsg = cap_xfer_nvlist(chan, nvlmsg);
+ if (nvlmsg == NULL)
+ return (-1);
+ error = (int)nvlist_get_number(nvlmsg, "error");
+ if (error != 0) {
+ nvlist_destroy(nvlmsg);
+ errno = error;
+ return (-1);
+ }
+ if (nvlist_exists_null(nvlmsg, "limits"))
+ *limitsp = NULL;
+ else
+ *limitsp = nvlist_take_nvlist(nvlmsg, "limits");
+ nvlist_destroy(nvlmsg);
+ return (0);
+}
+
+int
+cap_send_nvlist(const cap_channel_t *chan, const nvlist_t *nvl)
+{
+
+ assert(chan != NULL);
+ assert(chan->cch_magic == CAP_CHANNEL_MAGIC);
+
+ return (nvlist_send(chan->cch_sock, nvl));
+}
+
+nvlist_t *
+cap_recv_nvlist(const cap_channel_t *chan)
+{
+
+ assert(chan != NULL);
+ assert(chan->cch_magic == CAP_CHANNEL_MAGIC);
+
+ return (nvlist_recv(chan->cch_sock,
+ channel_nvlist_flags(chan)));
+}
+
+nvlist_t *
+cap_xfer_nvlist(const cap_channel_t *chan, nvlist_t *nvl)
+{
+
+ assert(chan != NULL);
+ assert(chan->cch_magic == CAP_CHANNEL_MAGIC);
+
+ return (nvlist_xfer(chan->cch_sock, nvl,
+ channel_nvlist_flags(chan)));
+}
+
+cap_channel_t *
+cap_service_open(const cap_channel_t *chan, const char *name)
+{
+ cap_channel_t *newchan;
+ nvlist_t *nvl;
+ int sock, error;
+ int flags;
+
+ sock = -1;
+
+ nvl = nvlist_create(channel_nvlist_flags(chan));
+ nvlist_add_string(nvl, "cmd", "open");
+ nvlist_add_string(nvl, "service", name);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (NULL);
+ error = (int)nvlist_get_number(nvl, "error");
+ if (error != 0) {
+ nvlist_destroy(nvl);
+ errno = error;
+ return (NULL);
+ }
+ sock = nvlist_take_descriptor(nvl, "chanfd");
+ flags = nvlist_take_number(nvl, "chanflags");
+ assert(sock >= 0);
+ nvlist_destroy(nvl);
+ nvl = NULL;
+ newchan = cap_wrap(sock, flags);
+ if (newchan == NULL)
+ goto fail;
+ return (newchan);
+fail:
+ error = errno;
+ close(sock);
+ errno = error;
+ return (NULL);
+}
+
+int
+cap_service_limit(const cap_channel_t *chan, const char * const *names,
+ size_t nnames)
+{
+ nvlist_t *limits;
+ unsigned int i;
+
+ limits = nvlist_create(channel_nvlist_flags(chan));
+ for (i = 0; i < nnames; i++)
+ nvlist_add_null(limits, names[i]);
+ return (cap_limit_set(chan, limits));
+}
diff --git a/lib/libcasper/libcasper/libcasper.h b/lib/libcasper/libcasper/libcasper.h
new file mode 100644
index 000000000000..403ddb73ad73
--- /dev/null
+++ b/lib/libcasper/libcasper/libcasper.h
@@ -0,0 +1,303 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012-2013 The FreeBSD Foundation
+ * Copyright (c) 2015-2017 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _LIBCASPER_H_
+#define _LIBCASPER_H_
+
+#ifdef HAVE_CASPER
+#define WITH_CASPER
+#endif
+
+#include <sys/types.h>
+#include <sys/nv.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#define CASPER_NO_UNIQ 0x00000001
+
+#ifndef _NVLIST_T_DECLARED
+#define _NVLIST_T_DECLARED
+struct nvlist;
+
+typedef struct nvlist nvlist_t;
+#endif
+
+#ifndef _CAP_CHANNEL_T_DECLARED
+#define _CAP_CHANNEL_T_DECLARED
+#ifdef WITH_CASPER
+struct cap_channel;
+
+typedef struct cap_channel cap_channel_t;
+#define CASPER_SUPPORT (1)
+#else
+struct cap_channel {
+ int cch_fd;
+ int cch_flags;
+};
+typedef struct cap_channel cap_channel_t;
+#define CASPER_SUPPORT (0)
+#endif /* ! WITH_CASPER */
+#endif /* ! _CAP_CHANNEL_T_DECLARED */
+
+__BEGIN_DECLS
+
+#ifdef WITH_CASPER
+int cap_channel_flags(const cap_channel_t *chan);
+#else
+static inline int
+cap_channel_flags(const cap_channel_t *chan)
+{
+
+ return (chan->cch_flags);
+}
+#endif
+
+static inline int
+channel_nvlist_flags(const cap_channel_t *chan)
+{
+ int flags;
+
+ flags = 0;
+ if ((cap_channel_flags(chan) & CASPER_NO_UNIQ) != 0)
+ flags |= NV_FLAG_NO_UNIQUE;
+
+ return (flags);
+}
+
+/*
+ * The functions opens unrestricted communication channel to Casper.
+ */
+#ifdef WITH_CASPER
+cap_channel_t *cap_init(void);
+#else
+static inline cap_channel_t *
+cap_init(void)
+{
+ cap_channel_t *chan;
+
+ chan = (cap_channel_t *)malloc(sizeof(*chan));
+ if (chan != NULL) {
+ chan->cch_fd = -1;
+ }
+ return (chan);
+}
+#endif
+
+/*
+ * The functions to communicate with service.
+ */
+#ifdef WITH_CASPER
+cap_channel_t *cap_service_open(const cap_channel_t *chan, const char *name);
+int cap_service_limit(const cap_channel_t *chan,
+ const char * const *names, size_t nnames);
+#else
+static inline cap_channel_t *
+cap_service_open(const cap_channel_t *chan __unused,
+ const char *name __unused)
+{
+
+ return (cap_init());
+}
+
+static inline int
+cap_service_limit(const cap_channel_t *chan __unused,
+ const char * const *names __unused, size_t nnames __unused)
+{
+
+ return (0);
+}
+#endif
+
+/*
+ * The function creates cap_channel_t based on the given socket.
+ */
+#ifdef WITH_CASPER
+cap_channel_t *cap_wrap(int sock, int flags);
+#else
+static inline cap_channel_t *
+cap_wrap(int sock, int flags)
+{
+ cap_channel_t *chan;
+
+ chan = cap_init();
+ if (chan != NULL) {
+ chan->cch_fd = sock;
+ chan->cch_flags = flags;
+ }
+ return (chan);
+}
+#endif
+
+/*
+ * The function returns communication socket and frees cap_channel_t.
+ */
+#ifdef WITH_CASPER
+int cap_unwrap(cap_channel_t *chan, int *flags);
+#else
+static inline int
+cap_unwrap(cap_channel_t *chan)
+{
+ int fd;
+
+ fd = chan->cch_fd;
+ free(chan);
+ return (fd);
+}
+#endif
+
+/*
+ * The function clones the given capability.
+ */
+#ifdef WITH_CASPER
+cap_channel_t *cap_clone(const cap_channel_t *chan);
+#else
+static inline cap_channel_t *
+cap_clone(const cap_channel_t *chan)
+{
+ cap_channel_t *newchan;
+
+ newchan = cap_init();
+ if (newchan == NULL) {
+ return (NULL);
+ }
+
+ if (chan->cch_fd == -1) {
+ newchan->cch_fd = -1;
+ } else {
+ newchan->cch_fd = dup(chan->cch_fd);
+ if (newchan->cch_fd < 0) {
+ free(newchan);
+ newchan = NULL;
+ }
+ }
+ newchan->cch_flags = chan->cch_flags;
+
+ return (newchan);
+}
+#endif
+
+/*
+ * The function closes the given capability.
+ */
+#ifdef WITH_CASPER
+void cap_close(cap_channel_t *chan);
+#else
+static inline void
+cap_close(cap_channel_t *chan)
+{
+
+ if (chan->cch_fd >= 0) {
+ close(chan->cch_fd);
+ }
+ free(chan);
+}
+#endif
+
+/*
+ * The function returns socket descriptor associated with the given
+ * cap_channel_t for use with select(2)/kqueue(2)/etc.
+ */
+#ifdef WITH_CASPER
+int cap_sock(const cap_channel_t *chan);
+#else
+#define cap_sock(chan) (chan->cch_fd)
+#endif
+
+/*
+ * The function limits the given capability.
+ * It always destroys 'limits' on return.
+ */
+#ifdef WITH_CASPER
+int cap_limit_set(const cap_channel_t *chan, nvlist_t *limits);
+#else
+static inline int
+cap_limit_set(const cap_channel_t *chan __unused,
+ nvlist_t *limits __unused)
+{
+
+ return (0);
+}
+#endif
+
+/*
+ * The function returns current limits of the given capability.
+ */
+#ifdef WITH_CASPER
+int cap_limit_get(const cap_channel_t *chan, nvlist_t **limitsp);
+#else
+static inline int
+cap_limit_get(const cap_channel_t *chan __unused, nvlist_t **limitsp)
+{
+
+ *limitsp = nvlist_create(channel_nvlist_flags(chan));
+ return (0);
+}
+#endif
+
+/*
+ * Function sends nvlist over the given capability.
+ */
+#ifdef WITH_CASPER
+int cap_send_nvlist(const cap_channel_t *chan, const nvlist_t *nvl);
+#else
+#define cap_send_nvlist(chan, nvl) (0)
+#endif
+
+/*
+ * Function receives nvlist over the given capability.
+ */
+#ifdef WITH_CASPER
+nvlist_t *cap_recv_nvlist(const cap_channel_t *chan);
+#else
+#define cap_recv_nvlist(chan) (nvlist_create(chan->cch_flags))
+#endif
+
+/*
+ * Function sends the given nvlist, destroys it and receives new nvlist in
+ * response over the given capability.
+ */
+#ifdef WITH_CASPER
+nvlist_t *cap_xfer_nvlist(const cap_channel_t *chan, nvlist_t *nvl);
+#else
+static inline nvlist_t *
+cap_xfer_nvlist(const cap_channel_t *chan, nvlist_t *nvl)
+{
+
+ nvlist_destroy(nvl);
+ return (nvlist_create(channel_nvlist_flags(chan)));
+}
+#endif
+
+__END_DECLS
+
+#endif /* !_LIBCASPER_H_ */
diff --git a/lib/libcasper/libcasper/libcasper_impl.c b/lib/libcasper/libcasper/libcasper_impl.c
new file mode 100644
index 000000000000..d457816e013b
--- /dev/null
+++ b/lib/libcasper/libcasper/libcasper_impl.c
@@ -0,0 +1,71 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 The FreeBSD Foundation
+ * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdlib.h>
+
+#include "libcasper_impl.h"
+
+bool
+fd_is_valid(int fd)
+{
+
+ return (fcntl(fd, F_GETFL) != -1 || errno != EBADF);
+}
+
+void
+fd_fix_environment(int *fdp)
+{
+ int nullfd, nfd;
+
+ if (*fdp > STDERR_FILENO)
+ return;
+
+ nullfd = open(_PATH_DEVNULL, O_RDWR);
+ if (nullfd == -1)
+ errx(1, "Unable to open %s", _PATH_DEVNULL);
+
+ while (*fdp <= STDERR_FILENO) {
+ nfd = dup(*fdp);
+ if (nfd == -1)
+ errx(1, "Unable to secure fd");
+ if (dup2(nullfd, *fdp) == -1)
+ errx(1, "Unable to secure fd");
+ *fdp = nfd;
+ }
+
+ close(nullfd);
+}
+
diff --git a/lib/libcasper/libcasper/libcasper_impl.h b/lib/libcasper/libcasper/libcasper_impl.h
new file mode 100644
index 000000000000..5f0aacf2afa8
--- /dev/null
+++ b/lib/libcasper/libcasper/libcasper_impl.h
@@ -0,0 +1,84 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 The FreeBSD Foundation
+ * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _LIBCASPER_IMPL_H_
+#define _LIBCASPER_IMPL_H_
+
+#include <stdbool.h>
+
+#include "libcasper.h"
+#include "libcasper_service.h"
+
+struct service;
+struct service_connection;
+
+bool fd_is_valid(int fd);
+void fd_fix_environment(int *fdp);
+
+/* Private service functions. */
+struct service *service_alloc(const char *name,
+ service_limit_func_t *limitfunc,
+ service_command_func_t *commandfunc, uint64_t flags);
+void service_free(struct service *service);
+void service_message(struct service *service,
+ struct service_connection *sconn);
+void service_start(struct service *service, int sock, int procfd);
+const char *service_name(struct service *service);
+int service_get_channel_flags(struct service *service);
+
+/* Private service connection functions. */
+struct service_connection *service_connection_add(struct service *service,
+ int sock, const nvlist_t *limits);
+void service_connection_remove(
+ struct service *service,
+ struct service_connection *sconn);
+int service_connection_clone(
+ struct service *service,
+ struct service_connection *sconn);
+struct service_connection *service_connection_first(
+ struct service *service);
+struct service_connection *service_connection_next(
+ struct service_connection *sconn);
+cap_channel_t *service_connection_get_chan(
+ const struct service_connection *sconn);
+int service_connection_get_sock(
+ const struct service_connection *sconn);
+const nvlist_t *service_connection_get_limits(
+ const struct service_connection *sconn);
+void service_connection_set_limits(
+ struct service_connection *sconn,
+ nvlist_t *limits);
+
+/* Private libcasper functions. */
+void casper_main_loop(int fd);
+
+#endif /* !_LIBCASPER_IMPL_H_ */
diff --git a/lib/libcasper/libcasper/libcasper_service.3 b/lib/libcasper/libcasper/libcasper_service.3
new file mode 100644
index 000000000000..201c19c70234
--- /dev/null
+++ b/lib/libcasper/libcasper/libcasper_service.3
@@ -0,0 +1,119 @@
+.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd November 15, 2021
+.Dt LIBCASPER 3
+.Os
+.Sh NAME
+.Nm CREATE_SERVICE
+.Nd "casper service declaration macro"
+.Sh LIBRARY
+.Lb libcasper
+.Sh SYNOPSIS
+.In sys/nv.h
+.In libcasper.h
+.In libcasper_service.h
+.Bd -literal
+typedef int service_limit_func_t(const nvlist_t *, const nvlist_t *);
+
+typedef int service_command_func_t(const char *, const nvlist_t *, nvlist_t *,
+ nvlist_t *);
+
+.Ed
+.Fn CREATE_SERVICE "name" "limit_func" "command_func" "flags"
+.Sh DESCRIPTION
+The
+.Nm CREATE_SERVICE
+macro is used to create a new casper service.
+The
+.Fa name
+is a string containing the service name, which will be used in the
+.Xr cap_service_open 3 ,
+function to identify it.
+.Pp
+The
+.Fa limit_func
+is a function of type
+.Li service_limit_func_t
+where the first argument of the function contains an
+.Xr nvlist 9 ,
+old service limits and
+the second argument contains the new limits.
+If the service was not limited then the old limits will be set to
+.Dv NULL .
+This function must not allow the extension of service limits.
+The
+.Fa command_func
+is a function of type
+.Li service_command_func_t
+where the first argument is the name of the command that should be executed.
+The first
+.Xr nvlist 9
+contains the current limits and the second contains an
+.Xr nvlist 9
+with the current request.
+The last argument contains a return value
+.Xr nvlist 9
+which contains the response from casper.
+.Pp
+The
+.Fa flags
+argument defines the limits of the service.
+The supported flags are:
+.Bl -ohang -offset indent
+.It CASPER_SERVICE_STDIO
+The casper service has access to the stdio descriptors from the process it was
+spawned from.
+.It CASPER_SERVICE_FD
+The casper service has access to all of the descriptors,
+besides the stdio descriptors,
+from the process it was spawned from.
+.It CASPER_SERVICE_NO_UNIQ_LIMITS
+The whole casper communication is using an
+.Xr nvlist 9
+with the
+.Dv NV_FLAG_NO_UNIQUE
+flag.
+.El
+.Sh SEE ALSO
+.Xr cap_enter 2 ,
+.Xr libcasper 3 ,
+.Xr capsicum 4 ,
+.Xr nv 9
+.Sh HISTORY
+The
+.Nm libcasper
+library first appeared in
+.Fx 10.3 .
+.Sh AUTHORS
+The
+.Nm libcasper
+library was implemented by
+.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net
+under sponsorship from the FreeBSD Foundation.
+The
+.Nm libcasper
+new architecture was implemented by
+.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org
+.
diff --git a/lib/libcasper/libcasper/libcasper_service.c b/lib/libcasper/libcasper/libcasper_service.c
new file mode 100644
index 000000000000..df58f48d78eb
--- /dev/null
+++ b/lib/libcasper/libcasper/libcasper_service.c
@@ -0,0 +1,289 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * Copyright (c) 2017 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * This software was developed by SRI International and the University of
+ * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
+ * ("CTSRD"), as part of the DARPA CRASH research programme.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/nv.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "libcasper_impl.h"
+#include "zygote.h"
+
+struct casper_service {
+ struct service *cs_service;
+ TAILQ_ENTRY(casper_service) cs_next;
+};
+
+static TAILQ_HEAD(, casper_service) casper_services =
+ TAILQ_HEAD_INITIALIZER(casper_services);
+
+#define CORE_CASPER_NAME "core.casper"
+#define CSERVICE_IS_CORE(service) \
+ (strcmp(service_name(service->cs_service), CORE_CASPER_NAME) == 0)
+
+static struct casper_service *
+service_find(const char *name)
+{
+ struct casper_service *casserv;
+
+ TAILQ_FOREACH(casserv, &casper_services, cs_next) {
+ if (strcmp(service_name(casserv->cs_service), name) == 0)
+ break;
+ }
+ return (casserv);
+}
+
+struct casper_service *
+service_register(const char *name, service_limit_func_t *limitfunc,
+ service_command_func_t *commandfunc, uint64_t flags)
+{
+ struct casper_service *casserv;
+
+ if (commandfunc == NULL)
+ return (NULL);
+ if (name == NULL || name[0] == '\0')
+ return (NULL);
+ if (service_find(name) != NULL)
+ return (NULL);
+
+ casserv = malloc(sizeof(*casserv));
+ if (casserv == NULL)
+ return (NULL);
+
+ casserv->cs_service = service_alloc(name, limitfunc, commandfunc,
+ flags);
+ if (casserv->cs_service == NULL) {
+ free(casserv);
+ return (NULL);
+ }
+ TAILQ_INSERT_TAIL(&casper_services, casserv, cs_next);
+
+ return (casserv);
+}
+
+static bool
+casper_allowed_service(const nvlist_t *limits, const char *service)
+{
+
+ if (limits == NULL)
+ return (true);
+
+ if (nvlist_exists_null(limits, service))
+ return (true);
+
+ return (false);
+}
+
+static int
+casper_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const char *name;
+ int type;
+ void *cookie;
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
+ if (type != NV_TYPE_NULL)
+ return (EINVAL);
+ if (!casper_allowed_service(oldlimits, name))
+ return (ENOTCAPABLE);
+ }
+
+ return (0);
+}
+
+void
+service_execute(int chanfd)
+{
+ struct casper_service *casserv;
+ struct service *service;
+ const char *servname;
+ nvlist_t *nvl;
+ int procfd;
+
+ nvl = nvlist_recv(chanfd, 0);
+ if (nvl == NULL)
+ _exit(1);
+ if (!nvlist_exists_string(nvl, "service"))
+ _exit(1);
+ servname = nvlist_get_string(nvl, "service");
+ casserv = service_find(servname);
+ if (casserv == NULL)
+ _exit(1);
+ service = casserv->cs_service;
+ procfd = nvlist_take_descriptor(nvl, "procfd");
+ nvlist_destroy(nvl);
+
+ service_start(service, chanfd, procfd);
+ /* Not reached. */
+ _exit(1);
+}
+
+static int
+casper_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ struct casper_service *casserv;
+ const char *servname;
+ nvlist_t *nvl;
+ int chanfd, procfd, error;
+
+ if (strcmp(cmd, "open") != 0)
+ return (EINVAL);
+ if (!nvlist_exists_string(nvlin, "service"))
+ return (EINVAL);
+
+ servname = nvlist_get_string(nvlin, "service");
+ casserv = service_find(servname);
+ if (casserv == NULL)
+ return (ENOENT);
+
+ if (!casper_allowed_service(limits, servname))
+ return (ENOTCAPABLE);
+
+ if (zygote_clone_service_execute(&chanfd, &procfd) == -1)
+ return (errno);
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "service", servname);
+ nvlist_move_descriptor(nvl, "procfd", procfd);
+ if (nvlist_send(chanfd, nvl) == -1) {
+ error = errno;
+ nvlist_destroy(nvl);
+ close(chanfd);
+ return (error);
+ }
+ nvlist_destroy(nvl);
+
+ nvlist_move_descriptor(nvlout, "chanfd", chanfd);
+ nvlist_add_number(nvlout, "chanflags",
+ service_get_channel_flags(casserv->cs_service));
+
+ return (0);
+}
+
+static void
+service_register_core(int fd)
+{
+ struct casper_service *casserv;
+ struct service_connection *sconn;
+
+ casserv = service_register(CORE_CASPER_NAME, casper_limit,
+ casper_command, 0);
+ sconn = service_connection_add(casserv->cs_service, fd, NULL);
+ if (sconn == NULL) {
+ close(fd);
+ abort();
+ }
+}
+
+void
+casper_main_loop(int fd)
+{
+ fd_set fds;
+ struct casper_service *casserv;
+ struct service_connection *sconn, *sconntmp;
+ int sock, maxfd, ret;
+
+ if (zygote_init() < 0)
+ _exit(1);
+
+ /*
+ * Register core services.
+ */
+ service_register_core(fd);
+
+ for (;;) {
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+ maxfd = -1;
+ TAILQ_FOREACH(casserv, &casper_services, cs_next) {
+ /* We handle only core services. */
+ if (!CSERVICE_IS_CORE(casserv))
+ continue;
+ for (sconn = service_connection_first(casserv->cs_service);
+ sconn != NULL;
+ sconn = service_connection_next(sconn)) {
+ sock = service_connection_get_sock(sconn);
+ FD_SET(sock, &fds);
+ maxfd = sock > maxfd ? sock : maxfd;
+ }
+ }
+ if (maxfd == -1) {
+ /* Nothing to do. */
+ _exit(0);
+ }
+ maxfd++;
+
+
+ assert(maxfd <= (int)FD_SETSIZE);
+ ret = select(maxfd, &fds, NULL, NULL, NULL);
+ assert(ret == -1 || ret > 0); /* select() cannot timeout */
+ if (ret == -1) {
+ if (errno == EINTR)
+ continue;
+ _exit(1);
+ }
+
+ TAILQ_FOREACH(casserv, &casper_services, cs_next) {
+ /* We handle only core services. */
+ if (!CSERVICE_IS_CORE(casserv))
+ continue;
+ for (sconn = service_connection_first(casserv->cs_service);
+ sconn != NULL; sconn = sconntmp) {
+ /*
+ * Prepare for connection to be removed from
+ * the list on failure.
+ */
+ sconntmp = service_connection_next(sconn);
+ sock = service_connection_get_sock(sconn);
+ if (FD_ISSET(sock, &fds)) {
+ service_message(casserv->cs_service,
+ sconn);
+ }
+ }
+ }
+ }
+}
diff --git a/lib/libcasper/libcasper/libcasper_service.h b/lib/libcasper/libcasper/libcasper_service.h
new file mode 100644
index 000000000000..79de7420253d
--- /dev/null
+++ b/lib/libcasper/libcasper/libcasper_service.h
@@ -0,0 +1,65 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 The FreeBSD Foundation
+ * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _LIBCASPER_SERVICE_H_
+#define _LIBCASPER_SERVICE_H_
+
+#ifndef _NVLIST_T_DECLARED
+#define _NVLIST_T_DECLARED
+struct nvlist;
+
+typedef struct nvlist nvlist_t;
+#endif
+
+#define CASPER_SERVICE_STDIO 0x00000001
+#define CASPER_SERVICE_FD 0x00000002
+#define CASPER_SERVICE_NO_UNIQ_LIMITS 0x00000004
+
+typedef int service_limit_func_t(const nvlist_t *, const nvlist_t *);
+typedef int service_command_func_t(const char *cmd, const nvlist_t *,
+ nvlist_t *, nvlist_t *);
+
+struct casper_service *service_register(const char *name,
+ service_limit_func_t *limitfunc, service_command_func_t *commandfunc,
+ uint64_t flags);
+
+#define __constructor __attribute__((constructor))
+#define CREATE_SERVICE(name, limit_func, command_func, flags) \
+ static __constructor void \
+ init_casper_service(void) \
+ { \
+ \
+ (void)service_register(name, limit_func, command_func, \
+ flags); \
+ }
+
+#endif /* !_LIBCASPER_SERVICE_H_ */
diff --git a/lib/libcasper/libcasper/service.c b/lib/libcasper/libcasper/service.c
new file mode 100644
index 000000000000..70418db50085
--- /dev/null
+++ b/lib/libcasper/libcasper/service.c
@@ -0,0 +1,472 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 The FreeBSD Foundation
+ * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/nv.h>
+
+#include <assert.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "libcasper.h"
+#include "libcasper_impl.h"
+
+/*
+ * Currently there is only one service_connection per service.
+ * In the future we may want multiple connections from multiple clients
+ * per one service instance, but it has to be carefully designed.
+ * The problem is that we may restrict/sandbox service instance according
+ * to the limits provided. When new connection comes in with different
+ * limits we won't be able to access requested resources.
+ * Not to mention one process will serve to multiple mutually untrusted
+ * clients and compromise of this service instance by one of its clients
+ * can lead to compromise of the other clients.
+ */
+
+/*
+ * Client connections to the given service.
+ */
+#define SERVICE_CONNECTION_MAGIC 0x5e91c0ec
+struct service_connection {
+ int sc_magic;
+ cap_channel_t *sc_chan;
+ nvlist_t *sc_limits;
+ TAILQ_ENTRY(service_connection) sc_next;
+};
+
+#define SERVICE_MAGIC 0x5e91ce
+struct service {
+ int s_magic;
+ char *s_name;
+ uint64_t s_flags;
+ service_limit_func_t *s_limit;
+ service_command_func_t *s_command;
+ TAILQ_HEAD(, service_connection) s_connections;
+};
+
+struct service *
+service_alloc(const char *name, service_limit_func_t *limitfunc,
+ service_command_func_t *commandfunc, uint64_t flags)
+{
+ struct service *service;
+
+ service = malloc(sizeof(*service));
+ if (service == NULL)
+ return (NULL);
+ service->s_name = strdup(name);
+ if (service->s_name == NULL) {
+ free(service);
+ return (NULL);
+ }
+ service->s_limit = limitfunc;
+ service->s_command = commandfunc;
+ service->s_flags = flags;
+ TAILQ_INIT(&service->s_connections);
+ service->s_magic = SERVICE_MAGIC;
+
+ return (service);
+}
+
+void
+service_free(struct service *service)
+{
+ struct service_connection *sconn;
+
+ assert(service->s_magic == SERVICE_MAGIC);
+
+ service->s_magic = 0;
+ while ((sconn = service_connection_first(service)) != NULL)
+ service_connection_remove(service, sconn);
+ free(service->s_name);
+ free(service);
+}
+
+struct service_connection *
+service_connection_add(struct service *service, int sock,
+ const nvlist_t *limits)
+{
+ struct service_connection *sconn;
+ int serrno;
+
+ assert(service->s_magic == SERVICE_MAGIC);
+
+ sconn = malloc(sizeof(*sconn));
+ if (sconn == NULL)
+ return (NULL);
+ sconn->sc_chan = cap_wrap(sock,
+ service_get_channel_flags(service));
+ if (sconn->sc_chan == NULL) {
+ serrno = errno;
+ free(sconn);
+ errno = serrno;
+ return (NULL);
+ }
+ if (limits == NULL) {
+ sconn->sc_limits = NULL;
+ } else {
+ sconn->sc_limits = nvlist_clone(limits);
+ if (sconn->sc_limits == NULL) {
+ serrno = errno;
+ (void)cap_unwrap(sconn->sc_chan, NULL);
+ free(sconn);
+ errno = serrno;
+ return (NULL);
+ }
+ }
+ sconn->sc_magic = SERVICE_CONNECTION_MAGIC;
+ TAILQ_INSERT_TAIL(&service->s_connections, sconn, sc_next);
+ return (sconn);
+}
+
+void
+service_connection_remove(struct service *service,
+ struct service_connection *sconn)
+{
+
+ assert(service->s_magic == SERVICE_MAGIC);
+ assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
+
+ TAILQ_REMOVE(&service->s_connections, sconn, sc_next);
+ sconn->sc_magic = 0;
+ nvlist_destroy(sconn->sc_limits);
+ cap_close(sconn->sc_chan);
+ free(sconn);
+}
+
+int
+service_connection_clone(struct service *service,
+ struct service_connection *sconn)
+{
+ struct service_connection *newsconn;
+ int serrno, sock[2];
+
+ if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sock) < 0)
+ return (-1);
+
+ newsconn = service_connection_add(service, sock[0],
+ service_connection_get_limits(sconn));
+ if (newsconn == NULL) {
+ serrno = errno;
+ close(sock[0]);
+ close(sock[1]);
+ errno = serrno;
+ return (-1);
+ }
+
+ return (sock[1]);
+}
+
+struct service_connection *
+service_connection_first(struct service *service)
+{
+ struct service_connection *sconn;
+
+ assert(service->s_magic == SERVICE_MAGIC);
+
+ sconn = TAILQ_FIRST(&service->s_connections);
+ assert(sconn == NULL ||
+ sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
+ return (sconn);
+}
+
+struct service_connection *
+service_connection_next(struct service_connection *sconn)
+{
+
+ assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
+
+ sconn = TAILQ_NEXT(sconn, sc_next);
+ assert(sconn == NULL ||
+ sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
+ return (sconn);
+}
+
+cap_channel_t *
+service_connection_get_chan(const struct service_connection *sconn)
+{
+
+ assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
+
+ return (sconn->sc_chan);
+}
+
+int
+service_connection_get_sock(const struct service_connection *sconn)
+{
+
+ assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
+
+ return (cap_sock(sconn->sc_chan));
+}
+
+const nvlist_t *
+service_connection_get_limits(const struct service_connection *sconn)
+{
+
+ assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
+
+ return (sconn->sc_limits);
+}
+
+void
+service_connection_set_limits(struct service_connection *sconn,
+ nvlist_t *limits)
+{
+
+ assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
+
+ nvlist_destroy(sconn->sc_limits);
+ sconn->sc_limits = limits;
+}
+
+void
+service_message(struct service *service, struct service_connection *sconn)
+{
+ nvlist_t *nvlin, *nvlout;
+ const char *cmd;
+ int error, flags;
+
+ flags = 0;
+ if ((service->s_flags & CASPER_SERVICE_NO_UNIQ_LIMITS) != 0)
+ flags = NV_FLAG_NO_UNIQUE;
+
+ nvlin = cap_recv_nvlist(service_connection_get_chan(sconn));
+ if (nvlin == NULL) {
+ service_connection_remove(service, sconn);
+ return;
+ }
+
+ error = EDOOFUS;
+ nvlout = nvlist_create(flags);
+
+ cmd = nvlist_get_string(nvlin, "cmd");
+ if (strcmp(cmd, "limit_set") == 0) {
+ nvlist_t *nvllim;
+
+ nvllim = nvlist_take_nvlist(nvlin, "limits");
+ if (service->s_limit == NULL) {
+ error = EOPNOTSUPP;
+ } else {
+ error = service->s_limit(
+ service_connection_get_limits(sconn), nvllim);
+ }
+ if (error == 0) {
+ service_connection_set_limits(sconn, nvllim);
+ /* Function consumes nvllim. */
+ } else {
+ nvlist_destroy(nvllim);
+ }
+ } else if (strcmp(cmd, "limit_get") == 0) {
+ const nvlist_t *nvllim;
+
+ nvllim = service_connection_get_limits(sconn);
+ if (nvllim != NULL)
+ nvlist_add_nvlist(nvlout, "limits", nvllim);
+ else
+ nvlist_add_null(nvlout, "limits");
+ error = 0;
+ } else if (strcmp(cmd, "clone") == 0) {
+ int sock;
+
+ sock = service_connection_clone(service, sconn);
+ if (sock == -1) {
+ error = errno;
+ } else {
+ nvlist_move_descriptor(nvlout, "sock", sock);
+ error = 0;
+ }
+ } else {
+ error = service->s_command(cmd,
+ service_connection_get_limits(sconn), nvlin, nvlout);
+ }
+
+ nvlist_destroy(nvlin);
+ nvlist_add_number(nvlout, "error", (uint64_t)error);
+
+ if (cap_send_nvlist(service_connection_get_chan(sconn), nvlout) == -1)
+ service_connection_remove(service, sconn);
+
+ nvlist_destroy(nvlout);
+}
+
+static int
+fd_add(fd_set *fdsp, int maxfd, int fd)
+{
+
+ FD_SET(fd, fdsp);
+ return (fd > maxfd ? fd : maxfd);
+}
+
+const char *
+service_name(struct service *service)
+{
+
+ assert(service->s_magic == SERVICE_MAGIC);
+ return (service->s_name);
+}
+
+int
+service_get_channel_flags(struct service *service)
+{
+ int flags;
+
+ assert(service->s_magic == SERVICE_MAGIC);
+ flags = 0;
+
+ if ((service->s_flags & CASPER_SERVICE_NO_UNIQ_LIMITS) != 0)
+ flags |= CASPER_NO_UNIQ;
+
+ return (flags);
+}
+
+static void
+stdnull(void)
+{
+ int fd;
+
+ fd = open(_PATH_DEVNULL, O_RDWR);
+ if (fd == -1)
+ errx(1, "Unable to open %s", _PATH_DEVNULL);
+
+ if (setsid() == -1)
+ errx(1, "Unable to detach from session");
+
+ if (dup2(fd, STDIN_FILENO) == -1)
+ errx(1, "Unable to cover stdin");
+ if (dup2(fd, STDOUT_FILENO) == -1)
+ errx(1, "Unable to cover stdout");
+ if (dup2(fd, STDERR_FILENO) == -1)
+ errx(1, "Unable to cover stderr");
+
+ if (fd > STDERR_FILENO)
+ close(fd);
+}
+
+static void
+service_clean(int *sockp, int *procfdp, uint64_t flags)
+{
+ int fd, maxfd, minfd;
+
+ fd_fix_environment(sockp);
+ fd_fix_environment(procfdp);
+
+ assert(*sockp > STDERR_FILENO);
+ assert(*procfdp > STDERR_FILENO);
+ assert(*sockp != *procfdp);
+
+ if ((flags & CASPER_SERVICE_STDIO) == 0)
+ stdnull();
+
+ if ((flags & CASPER_SERVICE_FD) == 0) {
+ if (*procfdp > *sockp) {
+ maxfd = *procfdp;
+ minfd = *sockp;
+ } else {
+ maxfd = *sockp;
+ minfd = *procfdp;
+ }
+
+ for (fd = STDERR_FILENO + 1; fd < maxfd; fd++) {
+ if (fd != minfd)
+ close(fd);
+ }
+ closefrom(maxfd + 1);
+ }
+}
+
+void
+service_start(struct service *service, int sock, int procfd)
+{
+ struct service_connection *sconn, *sconntmp;
+ fd_set fds;
+ int maxfd, nfds;
+
+ assert(service != NULL);
+ assert(service->s_magic == SERVICE_MAGIC);
+ setproctitle("%s", service->s_name);
+ service_clean(&sock, &procfd, service->s_flags);
+
+ if (service_connection_add(service, sock, NULL) == NULL)
+ _exit(1);
+
+ for (;;) {
+ FD_ZERO(&fds);
+ maxfd = -1;
+ for (sconn = service_connection_first(service); sconn != NULL;
+ sconn = service_connection_next(sconn)) {
+ maxfd = fd_add(&fds, maxfd,
+ service_connection_get_sock(sconn));
+ }
+
+ assert(maxfd >= 0);
+ assert(maxfd + 1 <= (int)FD_SETSIZE);
+ nfds = select(maxfd + 1, &fds, NULL, NULL, NULL);
+ if (nfds < 0) {
+ if (errno != EINTR)
+ _exit(1);
+ continue;
+ } else if (nfds == 0) {
+ /* Timeout. */
+ abort();
+ }
+
+ for (sconn = service_connection_first(service); sconn != NULL;
+ sconn = sconntmp) {
+ /*
+ * Prepare for connection to be removed from the list
+ * on failure.
+ */
+ sconntmp = service_connection_next(sconn);
+ if (FD_ISSET(service_connection_get_sock(sconn), &fds))
+ service_message(service, sconn);
+ }
+ if (service_connection_first(service) == NULL) {
+ /*
+ * No connections left, exiting.
+ */
+ break;
+ }
+ }
+
+ _exit(0);
+}
diff --git a/lib/libcasper/libcasper/zygote.c b/lib/libcasper/libcasper/zygote.c
new file mode 100644
index 000000000000..976e391d8dcb
--- /dev/null
+++ b/lib/libcasper/libcasper/zygote.c
@@ -0,0 +1,219 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * Copyright (c) 2017 Robert N. M. Watson
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * All rights reserved.
+ * This software was developed by SRI International and the University of
+ * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
+ * ("CTSRD"), as part of the DARPA CRASH research programme.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/capsicum.h>
+#include <sys/procdesc.h>
+#include <sys/socket.h>
+#include <sys/nv.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "libcasper_impl.h"
+#include "zygote.h"
+
+/* Zygote info. */
+static int zygote_sock = -1;
+
+#define ZYGOTE_SERVICE_EXECUTE 1
+
+int
+zygote_clone(uint64_t funcidx, int *chanfdp, int *procfdp)
+{
+ nvlist_t *nvl;
+ int error;
+
+ if (zygote_sock == -1) {
+ /* Zygote didn't start. */
+ errno = ENXIO;
+ return (-1);
+ }
+
+ nvl = nvlist_create(0);
+ nvlist_add_number(nvl, "funcidx", funcidx);
+ nvl = nvlist_xfer(zygote_sock, nvl, 0);
+ if (nvl == NULL)
+ return (-1);
+ if (nvlist_exists_number(nvl, "error")) {
+ error = (int)nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ errno = error;
+ return (-1);
+ }
+
+ *chanfdp = nvlist_take_descriptor(nvl, "chanfd");
+ *procfdp = nvlist_take_descriptor(nvl, "procfd");
+
+ nvlist_destroy(nvl);
+ return (0);
+}
+
+int
+zygote_clone_service_execute(int *chanfdp, int *procfdp)
+{
+
+ return (zygote_clone(ZYGOTE_SERVICE_EXECUTE, chanfdp, procfdp));
+}
+
+/*
+ * This function creates sandboxes on-demand whoever has access to it via
+ * 'sock' socket. Function sends two descriptors to the caller: process
+ * descriptor of the sandbox and socket pair descriptor for communication
+ * between sandbox and its owner.
+ */
+static void
+zygote_main(int *sockp)
+{
+ int error, procfd;
+ int chanfd[2];
+ nvlist_t *nvlin, *nvlout;
+ uint64_t funcidx;
+ zygote_func_t *func;
+ pid_t pid;
+
+ fd_fix_environment(sockp);
+
+ assert(*sockp > STDERR_FILENO);
+
+ setproctitle("zygote");
+
+ for (;;) {
+ nvlin = nvlist_recv(*sockp, 0);
+ if (nvlin == NULL) {
+ if (errno == ENOTCONN) {
+ /* Casper exited. */
+ _exit(0);
+ }
+ continue;
+ }
+ funcidx = nvlist_get_number(nvlin, "funcidx");
+ nvlist_destroy(nvlin);
+
+ switch (funcidx) {
+ case ZYGOTE_SERVICE_EXECUTE:
+ func = service_execute;
+ break;
+ default:
+ _exit(0);
+ }
+
+ /*
+ * Someone is requesting a new process, create one.
+ */
+ procfd = -1;
+ chanfd[0] = -1;
+ chanfd[1] = -1;
+ error = 0;
+ if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0,
+ chanfd) == -1) {
+ error = errno;
+ goto send;
+ }
+ pid = pdfork(&procfd, 0);
+ switch (pid) {
+ case -1:
+ /* Failure. */
+ error = errno;
+ break;
+ case 0:
+ /* Child. */
+ close(*sockp);
+ close(chanfd[0]);
+ func(chanfd[1]);
+ /* NOTREACHED */
+ _exit(1);
+ default:
+ /* Parent. */
+ close(chanfd[1]);
+ break;
+ }
+send:
+ nvlout = nvlist_create(0);
+ if (error != 0) {
+ nvlist_add_number(nvlout, "error", (uint64_t)error);
+ if (chanfd[0] >= 0)
+ close(chanfd[0]);
+ if (procfd >= 0)
+ close(procfd);
+ } else {
+ nvlist_move_descriptor(nvlout, "chanfd", chanfd[0]);
+ nvlist_move_descriptor(nvlout, "procfd", procfd);
+ }
+ (void)nvlist_send(*sockp, nvlout);
+ nvlist_destroy(nvlout);
+ }
+ /* NOTREACHED */
+}
+
+int
+zygote_init(void)
+{
+ int serrno, sp[2];
+ pid_t pid;
+
+ if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sp) == -1)
+ return (-1);
+
+ pid = fork();
+ switch (pid) {
+ case -1:
+ /* Failure. */
+ serrno = errno;
+ close(sp[0]);
+ close(sp[1]);
+ errno = serrno;
+ return (-1);
+ case 0:
+ /* Child. */
+ close(sp[0]);
+ zygote_main(&sp[1]);
+ /* NOTREACHED */
+ abort();
+ default:
+ /* Parent. */
+ zygote_sock = sp[0];
+ close(sp[1]);
+ return (0);
+ }
+ /* NOTREACHED */
+}
diff --git a/lib/libcasper/libcasper/zygote.h b/lib/libcasper/libcasper/zygote.h
new file mode 100644
index 000000000000..871da6f04182
--- /dev/null
+++ b/lib/libcasper/libcasper/zygote.h
@@ -0,0 +1,47 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _ZYGOTE_H_
+#define _ZYGOTE_H_
+
+typedef void zygote_func_t(int);
+
+int zygote_init(void);
+int zygote_clone(uint64_t funcidx, int *chanfdp, int *procfdp);
+int zygote_clone_service_execute(int *chanfdp, int *procfdp);
+
+/*
+ * Functions reachable via zygote_clone().
+ */
+zygote_func_t service_execute;
+
+#endif /* !_ZYGOTE_H_ */
diff --git a/lib/libcasper/services/Makefile b/lib/libcasper/services/Makefile
new file mode 100644
index 000000000000..c86b199f879b
--- /dev/null
+++ b/lib/libcasper/services/Makefile
@@ -0,0 +1,16 @@
+.include <src.opts.mk>
+
+SUBDIR= cap_dns
+SUBDIR+= cap_fileargs
+SUBDIR+= cap_grp
+SUBDIR+= cap_net
+SUBDIR+= cap_netdb
+SUBDIR+= cap_pwd
+SUBDIR+= cap_sysctl
+SUBDIR+= cap_syslog
+
+SUBDIR.${MK_TESTS}+= tests
+
+SUBDIR_PARALLEL=
+
+.include <bsd.subdir.mk>
diff --git a/lib/libcasper/services/Makefile.depend b/lib/libcasper/services/Makefile.depend
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/lib/libcasper/services/Makefile.depend
diff --git a/lib/libcasper/services/Makefile.inc b/lib/libcasper/services/Makefile.inc
new file mode 100644
index 000000000000..01b5f23410c8
--- /dev/null
+++ b/lib/libcasper/services/Makefile.inc
@@ -0,0 +1 @@
+.include "../Makefile.inc"
diff --git a/lib/libcasper/services/cap_dns/Makefile b/lib/libcasper/services/cap_dns/Makefile
new file mode 100644
index 000000000000..4b11c97d29e5
--- /dev/null
+++ b/lib/libcasper/services/cap_dns/Makefile
@@ -0,0 +1,31 @@
+SHLIBDIR?= /lib
+
+.include <src.opts.mk>
+
+PACKAGE= runtime
+
+SHLIB_MAJOR= 2
+INCSDIR?= ${INCLUDEDIR}/casper
+
+.if ${MK_CASPER} != "no"
+SHLIB= cap_dns
+
+SRCS= cap_dns.c
+.endif
+
+INCS= cap_dns.h
+
+LIBADD= nv
+
+CFLAGS+=-I${.CURDIR}
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+MAN+= cap_dns.3
+
+MLINKS+=cap_dns.3 libcap_dns.3
+MLINKS+=cap_dns.3 cap_dns_type_limit.3
+MLINKS+=cap_dns.3 cap_dns_family_limit.3
+
+.include <bsd.lib.mk>
diff --git a/lib/libcasper/services/cap_dns/Makefile.depend b/lib/libcasper/services/cap_dns/Makefile.depend
new file mode 100644
index 000000000000..02bae00eb04d
--- /dev/null
+++ b/lib/libcasper/services/cap_dns/Makefile.depend
@@ -0,0 +1,17 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_dns/cap_dns.3 b/lib/libcasper/services/cap_dns/cap_dns.3
new file mode 100644
index 000000000000..d6bbb6bd1263
--- /dev/null
+++ b/lib/libcasper/services/cap_dns/cap_dns.3
@@ -0,0 +1,241 @@
+.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd August 15, 2020
+.Dt CAP_DNS 3
+.Os
+.Sh NAME
+.Nm cap_getaddrinfo ,
+.Nm cap_getnameinfo ,
+.Nm cap_gethostbyname ,
+.Nm cap_gethostbyname2 ,
+.Nm cap_gethostbyaddr ,
+.Nm cap_dns_type_limit ,
+.Nm cap_dns_family_limit
+.Nd "library for getting network host entry in capability mode"
+.Sh LIBRARY
+.Lb libcap_dns
+.Sh SYNOPSIS
+.In sys/nv.h
+.In libcasper.h
+.In casper/cap_dns.h
+.Ft int
+.Fn cap_getaddrinfo "cap_channel_t *chan" "const char *hostname" "const char *servname" "const struct addrinfo *hints" "struct addrinfo **res"
+.Ft int
+.Fn cap_getnameinfo "cap_channel_t *chan" "const struct sockaddr *sa" "socklen_t salen" "char *host" "size_t hostlen" "char *serv" "size_t servlen" "int flags"
+.Ft "struct hostent *"
+.Fn cap_gethostbyname "const cap_channel_t *chan" "const char *name"
+.Ft "struct hostent *"
+.Fn cap_gethostbyname2 "const cap_channel_t *chan" "const char *name" "int af"
+.Ft "struct hostent *"
+.Fn cap_gethostbyaddr "const cap_channel_t *chan" "const void *addr" "socklen_t len" "int af"
+.Ft "int"
+.Fn cap_dns_type_limit "cap_channel_t *chan" "const char * const *types" "size_t ntypes"
+.Ft "int"
+.Fn cap_dns_family_limit "const cap_channel_t *chan" "const int *families" "size_t nfamilies"
+.Sh DESCRIPTION
+.Bf -symbolic
+This service is obsolete and
+.Xr cap_net 3
+should be used instead.
+The
+.Fn cap_getaddrinfo ,
+and
+.Fn cap_getnameinfo ,
+functions are preferred over the
+.Fn cap_gethostbyname ,
+.Fn cap_gethostbyname2 ,
+and
+.Fn cap_gethostbyaddr
+functions.
+.Ef
+.Pp
+The functions
+.Fn cap_gethostbyname ,
+.Fn cap_gethostbyname2 ,
+.Fn cep_gethostbyaddr
+and
+.Fn cap_getnameinfo
+are respectively equivalent to
+.Xr gethostbyname 3 ,
+.Xr gethostbyname2 3 ,
+.Xr gethostbyaddr 3
+and
+.Xr getnameinfo 3
+except that the connection to the
+.Nm system.dns
+service needs to be provided.
+.Pp
+The
+.Fn cap_dns_type_limit
+function limits the functions allowed in the service.
+The
+.Fa types
+variable can be set to
+.Dv ADDR2NAME
+or
+.Dv NAME2ADDR .
+See the
+.Sx LIMITS
+section for more details.
+The
+.Fa ntpyes
+variable contains the number of
+.Fa types
+provided.
+.Pp
+The
+.Fn cap_dns_family_limit
+functions allows to limit address families.
+For details see
+.Sx LIMITS .
+The
+.Fa nfamilies
+variable contains the number of
+.Fa families
+provided.
+.Sh LIMITS
+The preferred way of setting limits is to use the
+.Fn cap_dns_type_limit
+and
+.Fn cap_dns_family_limit
+functions, but the limits of service can be set also using
+.Xr cap_limit_set 3 .
+The
+.Xr nvlist 9
+for that function can contain the following values and types:
+.Bl -ohang -offset indent
+.It type ( NV_TYPE_STRING )
+The
+.Va type
+can have two values:
+.Dv ADDR2NAME
+or
+.Dv NAME2ADDR .
+The
+.Dv ADDR2NAME
+means that reverse DNS lookups are allowed with
+.Fn cap_getnameinfo
+and
+.Fn cap_gethostbyaddr
+functions.
+In case when
+.Va type
+is set to
+.Dv NAME2ADDR
+the name resolution is allowed with
+.Fn cap_getaddrinfo ,
+.Fn cap_gethostbyname ,
+and
+.Fn cap_gethostbyname2
+functions.
+.It family ( NV_TYPE_NUMBER )
+The
+.Va family
+limits service to one of the address families (e.g.
+.Dv AF_INET , AF_INET6 ,
+etc.).
+.El
+.Sh EXAMPLES
+The following example first opens a capability to casper and then uses this
+capability to create the
+.Nm system.dns
+casper service and uses it to resolve an IP address.
+.Bd -literal
+cap_channel_t *capcas, *capdns;
+int familylimit, error;
+const char *ipstr = "127.0.0.1";
+const char *typelimit = "ADDR2NAME";
+char hname[NI_MAXHOST];
+struct addrinfo hints, *res;
+
+/* Open capability to Casper. */
+capcas = cap_init();
+if (capcas == NULL)
+ err(1, "Unable to contact Casper");
+
+/* Cache NLA for gai_strerror. */
+caph_cache_catpages();
+
+/* Enter capability mode sandbox. */
+if (caph_enter() < 0)
+ err(1, "Unable to enter capability mode");
+
+/* Use Casper capability to create capability to the system.dns service. */
+capdns = cap_service_open(capcas, "system.dns");
+if (capdns == NULL)
+ err(1, "Unable to open system.dns service");
+
+/* Close Casper capability, we don't need it anymore. */
+cap_close(capcas);
+
+/* Limit system.dns to reserve IPv4 addresses */
+familylimit = AF_INET;
+if (cap_dns_family_limit(capdns, &familylimit, 1) < 0)
+ err(1, "Unable to limit access to the system.dns service");
+
+/* Convert IP address in C-string to struct sockaddr. */
+memset(&hints, 0, sizeof(hints));
+hints.ai_family = familylimit;
+hints.ai_flags = AI_NUMERICHOST;
+error = cap_getaddrinfo(capdns, ipstr, NULL, &hints, &res);
+if (error != 0)
+ errx(1, "cap_getaddrinfo(): %s: %s", ipstr, gai_strerror(error));
+
+/* Limit system.dns to reverse DNS lookups. */
+if (cap_dns_type_limit(capdns, &typelimit, 1) < 0)
+ err(1, "Unable to limit access to the system.dns service");
+
+/* Find hostname for the given IP address. */
+error = cap_getnameinfo(capdns, res->ai_addr, res->ai_addrlen, hname, sizeof(hname),
+ NULL, 0, 0);
+if (error != 0)
+ errx(1, "cap_getnameinfo(): %s: %s", ipstr, gai_strerror(error));
+
+printf("Name associated with %s is %s.\\n", ipstr, hname);
+.Ed
+.Sh SEE ALSO
+.Xr cap_enter 2 ,
+.Xr caph_enter 3 ,
+.Xr err 3 ,
+.Xr gethostbyaddr 3 ,
+.Xr gethostbyname 3 ,
+.Xr gethostbyname2 3 ,
+.Xr getnameinfo 3 ,
+.Xr capsicum 4 ,
+.Xr nv 9
+.Sh HISTORY
+The
+.Nm cap_dns
+service first appeared in
+.Fx 10.3 .
+.Sh AUTHORS
+The
+.Nm cap_dns
+service was implemented by
+.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net
+under sponsorship from the FreeBSD Foundation.
+.Pp
+This manual page was written by
+.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org .
diff --git a/lib/libcasper/services/cap_dns/cap_dns.c b/lib/libcasper/services/cap_dns/cap_dns.c
new file mode 100644
index 000000000000..8681f0baef40
--- /dev/null
+++ b/lib/libcasper/services/cap_dns/cap_dns.c
@@ -0,0 +1,767 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012-2013 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcasper.h>
+#include <libcasper_service.h>
+
+#include "cap_dns.h"
+
+static struct hostent hent;
+
+static void
+hostent_free(struct hostent *hp)
+{
+ unsigned int ii;
+
+ free(hp->h_name);
+ hp->h_name = NULL;
+ if (hp->h_aliases != NULL) {
+ for (ii = 0; hp->h_aliases[ii] != NULL; ii++)
+ free(hp->h_aliases[ii]);
+ free(hp->h_aliases);
+ hp->h_aliases = NULL;
+ }
+ if (hp->h_addr_list != NULL) {
+ for (ii = 0; hp->h_addr_list[ii] != NULL; ii++)
+ free(hp->h_addr_list[ii]);
+ free(hp->h_addr_list);
+ hp->h_addr_list = NULL;
+ }
+}
+
+static struct hostent *
+hostent_unpack(const nvlist_t *nvl, struct hostent *hp)
+{
+ unsigned int ii, nitems;
+ char nvlname[64];
+ int n;
+
+ hostent_free(hp);
+
+ hp->h_name = strdup(nvlist_get_string(nvl, "name"));
+ if (hp->h_name == NULL)
+ goto fail;
+ hp->h_addrtype = (int)nvlist_get_number(nvl, "addrtype");
+ hp->h_length = (int)nvlist_get_number(nvl, "length");
+
+ nitems = (unsigned int)nvlist_get_number(nvl, "naliases");
+ hp->h_aliases = calloc(nitems + 1, sizeof(hp->h_aliases[0]));
+ if (hp->h_aliases == NULL)
+ goto fail;
+ for (ii = 0; ii < nitems; ii++) {
+ n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ hp->h_aliases[ii] =
+ strdup(nvlist_get_string(nvl, nvlname));
+ if (hp->h_aliases[ii] == NULL)
+ goto fail;
+ }
+ hp->h_aliases[ii] = NULL;
+
+ nitems = (unsigned int)nvlist_get_number(nvl, "naddrs");
+ hp->h_addr_list = calloc(nitems + 1, sizeof(hp->h_addr_list[0]));
+ if (hp->h_addr_list == NULL)
+ goto fail;
+ for (ii = 0; ii < nitems; ii++) {
+ hp->h_addr_list[ii] = malloc(hp->h_length);
+ if (hp->h_addr_list[ii] == NULL)
+ goto fail;
+ n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ bcopy(nvlist_get_binary(nvl, nvlname, NULL),
+ hp->h_addr_list[ii], hp->h_length);
+ }
+ hp->h_addr_list[ii] = NULL;
+
+ return (hp);
+fail:
+ hostent_free(hp);
+ h_errno = NO_RECOVERY;
+ return (NULL);
+}
+
+struct hostent *
+cap_gethostbyname(cap_channel_t *chan, const char *name)
+{
+
+ return (cap_gethostbyname2(chan, name, AF_INET));
+}
+
+struct hostent *
+cap_gethostbyname2(cap_channel_t *chan, const char *name, int type)
+{
+ struct hostent *hp;
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "gethostbyname");
+ nvlist_add_number(nvl, "family", (uint64_t)type);
+ nvlist_add_string(nvl, "name", name);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL) {
+ h_errno = NO_RECOVERY;
+ return (NULL);
+ }
+ if (nvlist_get_number(nvl, "error") != 0) {
+ h_errno = (int)nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ return (NULL);
+ }
+
+ hp = hostent_unpack(nvl, &hent);
+ nvlist_destroy(nvl);
+ return (hp);
+}
+
+struct hostent *
+cap_gethostbyaddr(cap_channel_t *chan, const void *addr, socklen_t len,
+ int type)
+{
+ struct hostent *hp;
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "gethostbyaddr");
+ nvlist_add_binary(nvl, "addr", addr, (size_t)len);
+ nvlist_add_number(nvl, "family", (uint64_t)type);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL) {
+ h_errno = NO_RECOVERY;
+ return (NULL);
+ }
+ if (nvlist_get_number(nvl, "error") != 0) {
+ h_errno = (int)nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ return (NULL);
+ }
+ hp = hostent_unpack(nvl, &hent);
+ nvlist_destroy(nvl);
+ return (hp);
+}
+
+static struct addrinfo *
+addrinfo_unpack(const nvlist_t *nvl)
+{
+ struct addrinfo *ai;
+ const void *addr;
+ size_t addrlen;
+ const char *canonname;
+
+ addr = nvlist_get_binary(nvl, "ai_addr", &addrlen);
+ ai = malloc(sizeof(*ai) + addrlen);
+ if (ai == NULL)
+ return (NULL);
+ ai->ai_flags = (int)nvlist_get_number(nvl, "ai_flags");
+ ai->ai_family = (int)nvlist_get_number(nvl, "ai_family");
+ ai->ai_socktype = (int)nvlist_get_number(nvl, "ai_socktype");
+ ai->ai_protocol = (int)nvlist_get_number(nvl, "ai_protocol");
+ ai->ai_addrlen = (socklen_t)addrlen;
+ canonname = dnvlist_get_string(nvl, "ai_canonname", NULL);
+ if (canonname != NULL) {
+ ai->ai_canonname = strdup(canonname);
+ if (ai->ai_canonname == NULL) {
+ free(ai);
+ return (NULL);
+ }
+ } else {
+ ai->ai_canonname = NULL;
+ }
+ ai->ai_addr = (void *)(ai + 1);
+ bcopy(addr, ai->ai_addr, addrlen);
+ ai->ai_next = NULL;
+
+ return (ai);
+}
+
+int
+cap_getaddrinfo(cap_channel_t *chan, const char *hostname, const char *servname,
+ const struct addrinfo *hints, struct addrinfo **res)
+{
+ struct addrinfo *firstai, *prevai, *curai;
+ unsigned int ii;
+ const nvlist_t *nvlai;
+ char nvlname[64];
+ nvlist_t *nvl;
+ int error, n;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "getaddrinfo");
+ if (hostname != NULL)
+ nvlist_add_string(nvl, "hostname", hostname);
+ if (servname != NULL)
+ nvlist_add_string(nvl, "servname", servname);
+ if (hints != NULL) {
+ nvlist_add_number(nvl, "hints.ai_flags",
+ (uint64_t)hints->ai_flags);
+ nvlist_add_number(nvl, "hints.ai_family",
+ (uint64_t)hints->ai_family);
+ nvlist_add_number(nvl, "hints.ai_socktype",
+ (uint64_t)hints->ai_socktype);
+ nvlist_add_number(nvl, "hints.ai_protocol",
+ (uint64_t)hints->ai_protocol);
+ }
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (EAI_MEMORY);
+ if (nvlist_get_number(nvl, "error") != 0) {
+ error = (int)nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ return (error);
+ }
+
+ nvlai = NULL;
+ firstai = prevai = curai = NULL;
+ for (ii = 0; ; ii++) {
+ n = snprintf(nvlname, sizeof(nvlname), "res%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ if (!nvlist_exists_nvlist(nvl, nvlname))
+ break;
+ nvlai = nvlist_get_nvlist(nvl, nvlname);
+ curai = addrinfo_unpack(nvlai);
+ if (curai == NULL)
+ break;
+ if (prevai != NULL)
+ prevai->ai_next = curai;
+ else if (firstai == NULL)
+ firstai = curai;
+ prevai = curai;
+ }
+ nvlist_destroy(nvl);
+ if (curai == NULL && nvlai != NULL) {
+ if (firstai == NULL)
+ freeaddrinfo(firstai);
+ return (EAI_MEMORY);
+ }
+
+ *res = firstai;
+ return (0);
+}
+
+int
+cap_getnameinfo(cap_channel_t *chan, const struct sockaddr *sa, socklen_t salen,
+ char *host, size_t hostlen, char *serv, size_t servlen, int flags)
+{
+ nvlist_t *nvl;
+ int error;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "getnameinfo");
+ nvlist_add_number(nvl, "hostlen", (uint64_t)hostlen);
+ nvlist_add_number(nvl, "servlen", (uint64_t)servlen);
+ nvlist_add_binary(nvl, "sa", sa, (size_t)salen);
+ nvlist_add_number(nvl, "flags", (uint64_t)flags);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (EAI_MEMORY);
+ if (nvlist_get_number(nvl, "error") != 0) {
+ error = (int)nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ return (error);
+ }
+
+ if (host != NULL && nvlist_exists_string(nvl, "host"))
+ strlcpy(host, nvlist_get_string(nvl, "host"), hostlen);
+ if (serv != NULL && nvlist_exists_string(nvl, "serv"))
+ strlcpy(serv, nvlist_get_string(nvl, "serv"), servlen);
+ nvlist_destroy(nvl);
+ return (0);
+}
+
+static void
+limit_remove(nvlist_t *limits, const char *prefix)
+{
+ const char *name;
+ size_t prefixlen;
+ void *cookie;
+
+ prefixlen = strlen(prefix);
+again:
+ cookie = NULL;
+ while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) {
+ if (strncmp(name, prefix, prefixlen) == 0) {
+ nvlist_free(limits, name);
+ goto again;
+ }
+ }
+}
+
+int
+cap_dns_type_limit(cap_channel_t *chan, const char * const *types,
+ size_t ntypes)
+{
+ nvlist_t *limits;
+ unsigned int i;
+ char nvlname[64];
+ int n;
+
+ if (cap_limit_get(chan, &limits) < 0)
+ return (-1);
+ if (limits == NULL)
+ limits = nvlist_create(0);
+ else
+ limit_remove(limits, "type");
+ for (i = 0; i < ntypes; i++) {
+ n = snprintf(nvlname, sizeof(nvlname), "type%u", i);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_add_string(limits, nvlname, types[i]);
+ }
+ return (cap_limit_set(chan, limits));
+}
+
+int
+cap_dns_family_limit(cap_channel_t *chan, const int *families,
+ size_t nfamilies)
+{
+ nvlist_t *limits;
+ unsigned int i;
+ char nvlname[64];
+ int n;
+
+ if (cap_limit_get(chan, &limits) < 0)
+ return (-1);
+ if (limits == NULL)
+ limits = nvlist_create(0);
+ else
+ limit_remove(limits, "family");
+ for (i = 0; i < nfamilies; i++) {
+ n = snprintf(nvlname, sizeof(nvlname), "family%u", i);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_add_number(limits, nvlname, (uint64_t)families[i]);
+ }
+ return (cap_limit_set(chan, limits));
+}
+
+/*
+ * Service functions.
+ */
+static bool
+dns_allowed_type(const nvlist_t *limits, const char *type)
+{
+ const char *name;
+ bool notypes;
+ void *cookie;
+
+ if (limits == NULL)
+ return (true);
+
+ notypes = true;
+ cookie = NULL;
+ while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) {
+ if (strncmp(name, "type", sizeof("type") - 1) != 0)
+ continue;
+ notypes = false;
+ if (strcmp(nvlist_get_string(limits, name), type) == 0)
+ return (true);
+ }
+
+ /* If there are no types at all, allow any type. */
+ if (notypes)
+ return (true);
+
+ return (false);
+}
+
+static bool
+dns_allowed_family(const nvlist_t *limits, int family)
+{
+ const char *name;
+ bool nofamilies;
+ void *cookie;
+
+ if (limits == NULL)
+ return (true);
+
+ nofamilies = true;
+ cookie = NULL;
+ while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) {
+ if (strncmp(name, "family", sizeof("family") - 1) != 0)
+ continue;
+ nofamilies = false;
+ if (family == AF_UNSPEC)
+ continue;
+ if (nvlist_get_number(limits, name) == (uint64_t)family)
+ return (true);
+ }
+
+ /* If there are no families at all, allow any family. */
+ if (nofamilies)
+ return (true);
+
+ return (false);
+}
+
+static void
+hostent_pack(const struct hostent *hp, nvlist_t *nvl)
+{
+ unsigned int ii;
+ char nvlname[64];
+ int n;
+
+ nvlist_add_string(nvl, "name", hp->h_name);
+ nvlist_add_number(nvl, "addrtype", (uint64_t)hp->h_addrtype);
+ nvlist_add_number(nvl, "length", (uint64_t)hp->h_length);
+
+ if (hp->h_aliases == NULL) {
+ nvlist_add_number(nvl, "naliases", 0);
+ } else {
+ for (ii = 0; hp->h_aliases[ii] != NULL; ii++) {
+ n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_add_string(nvl, nvlname, hp->h_aliases[ii]);
+ }
+ nvlist_add_number(nvl, "naliases", (uint64_t)ii);
+ }
+
+ if (hp->h_addr_list == NULL) {
+ nvlist_add_number(nvl, "naddrs", 0);
+ } else {
+ for (ii = 0; hp->h_addr_list[ii] != NULL; ii++) {
+ n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_add_binary(nvl, nvlname, hp->h_addr_list[ii],
+ (size_t)hp->h_length);
+ }
+ nvlist_add_number(nvl, "naddrs", (uint64_t)ii);
+ }
+}
+
+static int
+dns_gethostbyname(const nvlist_t *limits, const nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ struct hostent *hp;
+ int family;
+
+ if (!dns_allowed_type(limits, "NAME2ADDR") &&
+ !dns_allowed_type(limits, "NAME"))
+ return (NO_RECOVERY);
+
+ family = (int)nvlist_get_number(nvlin, "family");
+
+ if (!dns_allowed_family(limits, family))
+ return (NO_RECOVERY);
+
+ hp = gethostbyname2(nvlist_get_string(nvlin, "name"), family);
+ if (hp == NULL)
+ return (h_errno);
+ hostent_pack(hp, nvlout);
+ return (0);
+}
+
+static int
+dns_gethostbyaddr(const nvlist_t *limits, const nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ struct hostent *hp;
+ const void *addr;
+ size_t addrsize;
+ int family;
+
+ if (!dns_allowed_type(limits, "ADDR2NAME") &&
+ !dns_allowed_type(limits, "ADDR"))
+ return (NO_RECOVERY);
+
+ family = (int)nvlist_get_number(nvlin, "family");
+
+ if (!dns_allowed_family(limits, family))
+ return (NO_RECOVERY);
+
+ addr = nvlist_get_binary(nvlin, "addr", &addrsize);
+ hp = gethostbyaddr(addr, (socklen_t)addrsize, family);
+ if (hp == NULL)
+ return (h_errno);
+ hostent_pack(hp, nvlout);
+ return (0);
+}
+
+static int
+dns_getnameinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
+{
+ struct sockaddr_storage sast;
+ const void *sabin;
+ char *host, *serv;
+ size_t sabinsize, hostlen, servlen;
+ socklen_t salen;
+ int error, flags;
+
+ if (!dns_allowed_type(limits, "ADDR2NAME") &&
+ !dns_allowed_type(limits, "ADDR"))
+ return (NO_RECOVERY);
+
+ error = 0;
+ host = serv = NULL;
+ memset(&sast, 0, sizeof(sast));
+
+ hostlen = (size_t)nvlist_get_number(nvlin, "hostlen");
+ servlen = (size_t)nvlist_get_number(nvlin, "servlen");
+
+ if (hostlen > 0) {
+ host = calloc(1, hostlen);
+ if (host == NULL) {
+ error = EAI_MEMORY;
+ goto out;
+ }
+ }
+ if (servlen > 0) {
+ serv = calloc(1, servlen);
+ if (serv == NULL) {
+ error = EAI_MEMORY;
+ goto out;
+ }
+ }
+
+ sabin = nvlist_get_binary(nvlin, "sa", &sabinsize);
+ if (sabinsize > sizeof(sast)) {
+ error = EAI_FAIL;
+ goto out;
+ }
+
+ memcpy(&sast, sabin, sabinsize);
+ salen = (socklen_t)sabinsize;
+
+ if ((sast.ss_family != AF_INET ||
+ salen != sizeof(struct sockaddr_in)) &&
+ (sast.ss_family != AF_INET6 ||
+ salen != sizeof(struct sockaddr_in6))) {
+ error = EAI_FAIL;
+ goto out;
+ }
+
+ if (!dns_allowed_family(limits, (int)sast.ss_family)) {
+ error = NO_RECOVERY;
+ goto out;
+ }
+
+ flags = (int)nvlist_get_number(nvlin, "flags");
+
+ error = getnameinfo((struct sockaddr *)&sast, salen, host, hostlen,
+ serv, servlen, flags);
+ if (error != 0)
+ goto out;
+
+ if (host != NULL)
+ nvlist_move_string(nvlout, "host", host);
+ if (serv != NULL)
+ nvlist_move_string(nvlout, "serv", serv);
+out:
+ if (error != 0) {
+ free(host);
+ free(serv);
+ }
+ return (error);
+}
+
+static nvlist_t *
+addrinfo_pack(const struct addrinfo *ai)
+{
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_number(nvl, "ai_flags", (uint64_t)ai->ai_flags);
+ nvlist_add_number(nvl, "ai_family", (uint64_t)ai->ai_family);
+ nvlist_add_number(nvl, "ai_socktype", (uint64_t)ai->ai_socktype);
+ nvlist_add_number(nvl, "ai_protocol", (uint64_t)ai->ai_protocol);
+ nvlist_add_binary(nvl, "ai_addr", ai->ai_addr, (size_t)ai->ai_addrlen);
+ if (ai->ai_canonname != NULL)
+ nvlist_add_string(nvl, "ai_canonname", ai->ai_canonname);
+
+ return (nvl);
+}
+
+static int
+dns_getaddrinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
+{
+ struct addrinfo hints, *hintsp, *res, *cur;
+ const char *hostname, *servname;
+ char nvlname[64];
+ nvlist_t *elem;
+ unsigned int ii;
+ int error, family, n;
+
+ if (!dns_allowed_type(limits, "NAME2ADDR") &&
+ !dns_allowed_type(limits, "NAME"))
+ return (NO_RECOVERY);
+
+ hostname = dnvlist_get_string(nvlin, "hostname", NULL);
+ servname = dnvlist_get_string(nvlin, "servname", NULL);
+ if (nvlist_exists_number(nvlin, "hints.ai_flags")) {
+ hints.ai_flags = (int)nvlist_get_number(nvlin,
+ "hints.ai_flags");
+ hints.ai_family = (int)nvlist_get_number(nvlin,
+ "hints.ai_family");
+ hints.ai_socktype = (int)nvlist_get_number(nvlin,
+ "hints.ai_socktype");
+ hints.ai_protocol = (int)nvlist_get_number(nvlin,
+ "hints.ai_protocol");
+ hints.ai_addrlen = 0;
+ hints.ai_addr = NULL;
+ hints.ai_canonname = NULL;
+ hints.ai_next = NULL;
+ hintsp = &hints;
+ family = hints.ai_family;
+ } else {
+ hintsp = NULL;
+ family = AF_UNSPEC;
+ }
+
+ if (!dns_allowed_family(limits, family))
+ return (NO_RECOVERY);
+
+ error = getaddrinfo(hostname, servname, hintsp, &res);
+ if (error != 0)
+ goto out;
+
+ for (cur = res, ii = 0; cur != NULL; cur = cur->ai_next, ii++) {
+ elem = addrinfo_pack(cur);
+ n = snprintf(nvlname, sizeof(nvlname), "res%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_move_nvlist(nvlout, nvlname, elem);
+ }
+
+ freeaddrinfo(res);
+ error = 0;
+out:
+ return (error);
+}
+
+static bool
+limit_has_entry(const nvlist_t *limits, const char *prefix)
+{
+ const char *name;
+ size_t prefixlen;
+ void *cookie;
+
+ if (limits == NULL)
+ return (false);
+
+ prefixlen = strlen(prefix);
+
+ cookie = NULL;
+ while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) {
+ if (strncmp(name, prefix, prefixlen) == 0)
+ return (true);
+ }
+
+ return (false);
+}
+
+static int
+dns_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const char *name;
+ void *cookie;
+ int nvtype;
+ bool hastype, hasfamily;
+
+ hastype = false;
+ hasfamily = false;
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &nvtype, &cookie)) != NULL) {
+ if (nvtype == NV_TYPE_STRING) {
+ const char *type;
+
+ if (strncmp(name, "type", sizeof("type") - 1) != 0)
+ return (EINVAL);
+ type = nvlist_get_string(newlimits, name);
+ if (strcmp(type, "ADDR2NAME") != 0 &&
+ strcmp(type, "NAME2ADDR") != 0 &&
+ strcmp(type, "ADDR") != 0 &&
+ strcmp(type, "NAME") != 0) {
+ return (EINVAL);
+ }
+ if (!dns_allowed_type(oldlimits, type))
+ return (ENOTCAPABLE);
+ hastype = true;
+ } else if (nvtype == NV_TYPE_NUMBER) {
+ int family;
+
+ if (strncmp(name, "family", sizeof("family") - 1) != 0)
+ return (EINVAL);
+ family = (int)nvlist_get_number(newlimits, name);
+ if (!dns_allowed_family(oldlimits, family))
+ return (ENOTCAPABLE);
+ hasfamily = true;
+ } else {
+ return (EINVAL);
+ }
+ }
+
+ /*
+ * If the new limit doesn't mention type or family we have to
+ * check if the current limit does have those. Missing type or
+ * family in the limit means that all types or families are
+ * allowed.
+ */
+ if (!hastype) {
+ if (limit_has_entry(oldlimits, "type"))
+ return (ENOTCAPABLE);
+ }
+ if (!hasfamily) {
+ if (limit_has_entry(oldlimits, "family"))
+ return (ENOTCAPABLE);
+ }
+
+ return (0);
+}
+
+static int
+dns_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ int error;
+
+ if (strcmp(cmd, "gethostbyname") == 0)
+ error = dns_gethostbyname(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "gethostbyaddr") == 0)
+ error = dns_gethostbyaddr(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "getnameinfo") == 0)
+ error = dns_getnameinfo(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "getaddrinfo") == 0)
+ error = dns_getaddrinfo(limits, nvlin, nvlout);
+ else
+ error = NO_RECOVERY;
+
+ return (error);
+}
+
+CREATE_SERVICE("system.dns", dns_limit, dns_command, 0);
diff --git a/lib/libcasper/services/cap_dns/cap_dns.h b/lib/libcasper/services/cap_dns/cap_dns.h
new file mode 100644
index 000000000000..556cac1158d2
--- /dev/null
+++ b/lib/libcasper/services/cap_dns/cap_dns.h
@@ -0,0 +1,131 @@
+/*-
+ * Copyright (c) 2012 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _CAP_DNS_H_
+#define _CAP_DNS_H_
+
+#ifdef HAVE_CASPER
+#define WITH_CASPER
+#endif
+
+#include <sys/cdefs.h>
+#include <sys/socket.h> /* socklen_t */
+
+/*
+ * Pull these in if we're just inlining calls to the underlying
+ * libc functions.
+ */
+#ifndef WITH_CASPER
+#include <sys/types.h>
+#include <netdb.h>
+#endif /* WITH_CASPER */
+
+struct addrinfo;
+struct hostent;
+
+#ifdef WITH_CASPER
+__BEGIN_DECLS
+
+struct hostent *cap_gethostbyname(cap_channel_t *chan, const char *name);
+struct hostent *cap_gethostbyname2(cap_channel_t *chan, const char *name,
+ int type);
+struct hostent *cap_gethostbyaddr(cap_channel_t *chan, const void *addr,
+ socklen_t len, int type);
+
+int cap_getaddrinfo(cap_channel_t *chan, const char *hostname,
+ const char *servname, const struct addrinfo *hints, struct addrinfo **res);
+int cap_getnameinfo(cap_channel_t *chan, const struct sockaddr *sa,
+ socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen,
+ int flags);
+
+int cap_dns_type_limit(cap_channel_t *chan, const char * const *types,
+ size_t ntypes);
+int cap_dns_family_limit(cap_channel_t *chan, const int *families,
+ size_t nfamilies);
+
+__END_DECLS
+#else
+
+static inline struct hostent *
+cap_gethostbyname(cap_channel_t *chan __unused, const char *name)
+{
+
+ return (gethostbyname(name));
+}
+
+static inline struct hostent *
+cap_gethostbyname2(cap_channel_t *chan __unused, const char *name, int type)
+{
+
+ return (gethostbyname2(name, type));
+}
+
+static inline struct hostent *
+cap_gethostbyaddr(cap_channel_t *chan __unused, const void *addr,
+ socklen_t len, int type)
+{
+
+ return (gethostbyaddr(addr, len, type));
+}
+
+static inline int cap_getaddrinfo(cap_channel_t *chan __unused,
+ const char *hostname, const char *servname, const struct addrinfo *hints,
+ struct addrinfo **res)
+{
+
+ return (getaddrinfo(hostname, servname, hints, res));
+}
+
+static inline int cap_getnameinfo(cap_channel_t *chan __unused,
+ const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen,
+ char *serv, size_t servlen, int flags)
+{
+
+ return (getnameinfo(sa, salen, host, hostlen, serv, servlen, flags));
+}
+
+static inline int
+cap_dns_type_limit(cap_channel_t *chan __unused,
+ const char * const *types __unused,
+ size_t ntypes __unused)
+{
+
+ return (0);
+}
+
+static inline int
+cap_dns_family_limit(cap_channel_t *chan __unused,
+ const int *families __unused,
+ size_t nfamilies __unused)
+{
+
+ return (0);
+}
+#endif /* WITH_CASPER */
+
+#endif /* !_CAP_DNS_H_ */
diff --git a/lib/libcasper/services/cap_dns/tests/Makefile b/lib/libcasper/services/cap_dns/tests/Makefile
new file mode 100644
index 000000000000..c7306581a803
--- /dev/null
+++ b/lib/libcasper/services/cap_dns/tests/Makefile
@@ -0,0 +1,12 @@
+.include <src.opts.mk>
+
+ATF_TESTS_C= dns_test
+
+.if ${MK_CASPER} != "no"
+LIBADD+= casper
+LIBADD+= cap_dns
+CFLAGS+=-DWITH_CASPER
+.endif
+LIBADD+= nv
+
+.include <bsd.test.mk>
diff --git a/lib/libcasper/services/cap_dns/tests/Makefile.depend b/lib/libcasper/services/cap_dns/tests/Makefile.depend
new file mode 100644
index 000000000000..560f3166bc47
--- /dev/null
+++ b/lib/libcasper/services/cap_dns/tests/Makefile.depend
@@ -0,0 +1,20 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/atf/libatf-c \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcasper/services/cap_dns \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_dns/tests/dns_test.c b/lib/libcasper/services/cap_dns/tests/dns_test.c
new file mode 100644
index 000000000000..56d867e474f5
--- /dev/null
+++ b/lib/libcasper/services/cap_dns/tests/dns_test.c
@@ -0,0 +1,703 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/capsicum.h>
+#include <sys/nv.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcasper.h>
+#include <casper/cap_dns.h>
+
+#include <atf-c.h>
+
+#define GETHOSTBYNAME 0x01
+#define GETHOSTBYNAME2_AF_INET 0x02
+#define GETHOSTBYNAME2_AF_INET6 0x04
+#define GETHOSTBYADDR_AF_INET 0x08
+#define GETHOSTBYADDR_AF_INET6 0x10
+#define GETADDRINFO_AF_UNSPEC 0x20
+#define GETADDRINFO_AF_INET 0x40
+#define GETADDRINFO_AF_INET6 0x80
+
+static bool
+addrinfo_compare(struct addrinfo *ai0, struct addrinfo *ai1)
+{
+ struct addrinfo *at0, *at1;
+
+ if (ai0 == NULL && ai1 == NULL)
+ return (true);
+ if (ai0 == NULL || ai1 == NULL)
+ return (false);
+
+ at0 = ai0;
+ at1 = ai1;
+ while (true) {
+ if ((at0->ai_flags == at1->ai_flags) &&
+ (at0->ai_family == at1->ai_family) &&
+ (at0->ai_socktype == at1->ai_socktype) &&
+ (at0->ai_protocol == at1->ai_protocol) &&
+ (at0->ai_addrlen == at1->ai_addrlen) &&
+ (memcmp(at0->ai_addr, at1->ai_addr,
+ at0->ai_addrlen) == 0)) {
+ if (at0->ai_canonname != NULL &&
+ at1->ai_canonname != NULL) {
+ if (strcmp(at0->ai_canonname,
+ at1->ai_canonname) != 0) {
+ return (false);
+ }
+ }
+
+ if (at0->ai_canonname == NULL &&
+ at1->ai_canonname != NULL) {
+ return (false);
+ }
+ if (at0->ai_canonname != NULL &&
+ at1->ai_canonname == NULL) {
+ return (false);
+ }
+
+ if (at0->ai_next == NULL && at1->ai_next == NULL)
+ return (true);
+ if (at0->ai_next == NULL || at1->ai_next == NULL)
+ return (false);
+
+ at0 = at0->ai_next;
+ at1 = at1->ai_next;
+ } else {
+ return (false);
+ }
+ }
+
+ /* NOTREACHED */
+ fprintf(stderr, "Dead code reached in addrinfo_compare()\n");
+ exit(1);
+}
+
+static bool
+hostent_aliases_compare(char **aliases0, char **aliases1)
+{
+ int i0, i1;
+
+ if (aliases0 == NULL && aliases1 == NULL)
+ return (true);
+ if (aliases0 == NULL || aliases1 == NULL)
+ return (false);
+
+ for (i0 = 0; aliases0[i0] != NULL; i0++) {
+ for (i1 = 0; aliases1[i1] != NULL; i1++) {
+ if (strcmp(aliases0[i0], aliases1[i1]) == 0)
+ break;
+ }
+ if (aliases1[i1] == NULL)
+ return (false);
+ }
+
+ return (true);
+}
+
+static bool
+hostent_addr_list_compare(char **addr_list0, char **addr_list1, int length)
+{
+ int i0, i1;
+
+ if (addr_list0 == NULL && addr_list1 == NULL)
+ return (true);
+ if (addr_list0 == NULL || addr_list1 == NULL)
+ return (false);
+
+ for (i0 = 0; addr_list0[i0] != NULL; i0++) {
+ for (i1 = 0; addr_list1[i1] != NULL; i1++) {
+ if (memcmp(addr_list0[i0], addr_list1[i1], length) == 0)
+ break;
+ }
+ if (addr_list1[i1] == NULL)
+ return (false);
+ }
+
+ return (true);
+}
+
+static bool
+hostent_compare(const struct hostent *hp0, const struct hostent *hp1)
+{
+
+ if (hp0 == NULL && hp1 != NULL)
+ return (true);
+
+ if (hp0 == NULL || hp1 == NULL)
+ return (false);
+
+ if (hp0->h_name != NULL || hp1->h_name != NULL) {
+ if (hp0->h_name == NULL || hp1->h_name == NULL)
+ return (false);
+ if (strcmp(hp0->h_name, hp1->h_name) != 0)
+ return (false);
+ }
+
+ if (!hostent_aliases_compare(hp0->h_aliases, hp1->h_aliases))
+ return (false);
+ if (!hostent_aliases_compare(hp1->h_aliases, hp0->h_aliases))
+ return (false);
+
+ if (hp0->h_addrtype != hp1->h_addrtype)
+ return (false);
+
+ if (hp0->h_length != hp1->h_length)
+ return (false);
+
+ if (!hostent_addr_list_compare(hp0->h_addr_list, hp1->h_addr_list,
+ hp0->h_length)) {
+ return (false);
+ }
+ if (!hostent_addr_list_compare(hp1->h_addr_list, hp0->h_addr_list,
+ hp0->h_length)) {
+ return (false);
+ }
+
+ return (true);
+}
+
+static void
+runtest(cap_channel_t *capdns, unsigned int expected)
+{
+ unsigned int result;
+ struct addrinfo *ais, *aic, hints, *hintsp;
+ struct hostent *hps, *hpc;
+ struct in_addr ip4;
+ struct in6_addr ip6;
+ int caperr, syserr;
+
+ result = 0;
+
+ hps = gethostbyname("example.com");
+ if (hps == NULL) {
+ fprintf(stderr, "Unable to resolve %s IPv4.\n", "example.com");
+ } else {
+ hpc = cap_gethostbyname(capdns, "example.com");
+ if (hostent_compare(hps, hpc))
+ result |= GETHOSTBYNAME;
+ }
+
+ hps = gethostbyname2("example.com", AF_INET);
+ if (hps == NULL) {
+ fprintf(stderr, "Unable to resolve %s IPv4.\n", "example.com");
+ } else {
+ hpc = cap_gethostbyname2(capdns, "example.com", AF_INET);
+ if (hostent_compare(hps, hpc))
+ result |= GETHOSTBYNAME2_AF_INET;
+ }
+
+ hps = gethostbyname2("example.com", AF_INET6);
+ if (hps == NULL) {
+ fprintf(stderr, "Unable to resolve %s IPv6.\n", "example.com");
+ } else {
+ hpc = cap_gethostbyname2(capdns, "example.com", AF_INET6);
+ if (hostent_compare(hps, hpc))
+ result |= GETHOSTBYNAME2_AF_INET6;
+ }
+
+ hints.ai_flags = 0;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = 0;
+ hints.ai_protocol = 0;
+ hints.ai_addrlen = 0;
+ hints.ai_addr = NULL;
+ hints.ai_canonname = NULL;
+ hints.ai_next = NULL;
+
+ hintsp = &hints;
+
+ syserr = getaddrinfo("freebsd.org", "25", hintsp, &ais);
+ if (syserr != 0) {
+ fprintf(stderr,
+ "Unable to issue [system] getaddrinfo() for AF_UNSPEC: %s\n",
+ gai_strerror(syserr));
+ } else {
+ caperr = cap_getaddrinfo(capdns, "freebsd.org", "25", hintsp,
+ &aic);
+ if (caperr == 0) {
+ if (addrinfo_compare(ais, aic))
+ result |= GETADDRINFO_AF_UNSPEC;
+ freeaddrinfo(ais);
+ freeaddrinfo(aic);
+ }
+ }
+
+ hints.ai_family = AF_INET;
+ syserr = getaddrinfo("freebsd.org", "25", hintsp, &ais);
+ if (syserr != 0) {
+ fprintf(stderr,
+ "Unable to issue [system] getaddrinfo() for AF_UNSPEC: %s\n",
+ gai_strerror(syserr));
+ } else {
+ caperr = cap_getaddrinfo(capdns, "freebsd.org", "25", hintsp,
+ &aic);
+ if (caperr == 0) {
+ if (addrinfo_compare(ais, aic))
+ result |= GETADDRINFO_AF_INET;
+ freeaddrinfo(ais);
+ freeaddrinfo(aic);
+ }
+ }
+
+ hints.ai_family = AF_INET6;
+ syserr = getaddrinfo("freebsd.org", "25", hintsp, &ais);
+ if (syserr != 0) {
+ fprintf(stderr,
+ "Unable to issue [system] getaddrinfo() for AF_UNSPEC: %s\n",
+ gai_strerror(syserr));
+ } else {
+ caperr = cap_getaddrinfo(capdns, "freebsd.org", "25", hintsp,
+ &aic);
+ if (caperr == 0) {
+ if (addrinfo_compare(ais, aic))
+ result |= GETADDRINFO_AF_INET6;
+ freeaddrinfo(ais);
+ freeaddrinfo(aic);
+ }
+ }
+
+ /* XXX: hardcoded addresses for "google-public-dns-a.google.com". */
+#define GOOGLE_DNS_IPV4 "8.8.8.8"
+#define GOOGLE_DNS_IPV6 "2001:4860:4860::8888"
+
+ inet_pton(AF_INET, GOOGLE_DNS_IPV4, &ip4);
+ hps = gethostbyaddr(&ip4, sizeof(ip4), AF_INET);
+ if (hps == NULL) {
+ fprintf(stderr, "Unable to resolve %s.\n", GOOGLE_DNS_IPV4);
+ } else {
+ hpc = cap_gethostbyaddr(capdns, &ip4, sizeof(ip4), AF_INET);
+ if (hostent_compare(hps, hpc))
+ result |= GETHOSTBYADDR_AF_INET;
+ }
+
+ inet_pton(AF_INET6, GOOGLE_DNS_IPV6, &ip6);
+ hps = gethostbyaddr(&ip6, sizeof(ip6), AF_INET6);
+ if (hps == NULL) {
+ fprintf(stderr, "Unable to resolve %s.\n", GOOGLE_DNS_IPV6);
+ } else {
+ hpc = cap_gethostbyaddr(capdns, &ip6, sizeof(ip6), AF_INET6);
+ if (hostent_compare(hps, hpc)) {
+ caperr = h_errno;
+ result |= GETHOSTBYADDR_AF_INET6;
+ }
+ }
+
+ ATF_REQUIRE_MSG(result == expected,
+ "expected 0x%x, got 0x%x", expected, result);
+}
+
+static cap_channel_t *
+cap_dns_init(void)
+{
+ cap_channel_t *capcas, *capdns;
+
+ capcas = cap_init();
+ ATF_REQUIRE(capcas != NULL);
+
+ capdns = cap_service_open(capcas, "system.dns");
+ ATF_REQUIRE(capdns != NULL);
+
+ cap_close(capcas);
+
+ return (capdns);
+}
+
+ATF_TC(dns_no_limits);
+ATF_TC_HEAD(dns_no_limits, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(dns_no_limits, tc)
+{
+ cap_channel_t *capdns;
+
+ capdns = cap_dns_init();
+
+ runtest(capdns,
+ (GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYNAME2_AF_INET6 |
+ GETHOSTBYADDR_AF_INET | GETHOSTBYADDR_AF_INET6 |
+ GETADDRINFO_AF_UNSPEC | GETADDRINFO_AF_INET |
+ GETADDRINFO_AF_INET6));
+
+ cap_close(capdns);
+}
+
+ATF_TC(dns_all_limits);
+ATF_TC_HEAD(dns_all_limits, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(dns_all_limits, tc)
+{
+ cap_channel_t *capdns;
+ const char *types[2];
+ int families[2];
+
+ capdns = cap_dns_init();
+
+ types[0] = "NAME2ADDR";
+ types[1] = "ADDR2NAME";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
+ families[0] = AF_INET;
+ families[1] = AF_INET6;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, NULL, 0) == -1);
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, NULL, 0) == -1);
+
+ runtest(capdns,
+ (GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYNAME2_AF_INET6 |
+ GETHOSTBYADDR_AF_INET | GETHOSTBYADDR_AF_INET6 |
+ GETADDRINFO_AF_INET | GETADDRINFO_AF_INET6));
+
+ cap_close(capdns);
+}
+
+ATF_TC(dns_name_limit);
+ATF_TC_HEAD(dns_name_limit, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(dns_name_limit, tc)
+{
+ cap_channel_t *capdns;
+ const char *types[2];
+ int families[2];
+
+ capdns = cap_dns_init();
+
+ types[0] = "NAME2ADDR";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
+ types[1] = "ADDR2NAME";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 2) == -1);
+ types[0] = "ADDR2NAME";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 1) == -1);
+ families[0] = AF_INET;
+ families[1] = AF_INET6;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
+
+ runtest(capdns,
+ (GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYNAME2_AF_INET6 |
+ GETADDRINFO_AF_INET | GETADDRINFO_AF_INET6));
+
+ cap_close(capdns);
+}
+
+ATF_TC(dns_addr_limit);
+ATF_TC_HEAD(dns_addr_limit, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(dns_addr_limit, tc)
+{
+ cap_channel_t *capdns;
+ const char *types[2];
+ int families[2];
+
+ capdns = cap_dns_init();
+
+ types[0] = "ADDR2NAME";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
+ types[1] = "NAME2ADDR";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 2) == -1);
+ types[0] = "NAME2ADDR";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 1) == -1);
+ families[0] = AF_INET;
+ families[1] = AF_INET6;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
+
+ runtest(capdns,
+ (GETHOSTBYADDR_AF_INET | GETHOSTBYADDR_AF_INET6));
+
+ cap_close(capdns);
+}
+
+ATF_TC(dns_inet_limit);
+ATF_TC_HEAD(dns_inet_limit, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(dns_inet_limit, tc)
+{
+ cap_channel_t *capdns;
+ const char *types[2];
+ int families[2];
+
+ capdns = cap_dns_init();
+
+ types[0] = "NAME2ADDR";
+ types[1] = "ADDR2NAME";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
+ families[0] = AF_INET;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
+ families[1] = AF_INET6;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 2) == -1);
+ families[0] = AF_INET6;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 1) == -1);
+
+ runtest(capdns,
+ (GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYADDR_AF_INET |
+ GETADDRINFO_AF_INET));
+
+ cap_close(capdns);
+}
+
+ATF_TC(dns_inet6_limit);
+ATF_TC_HEAD(dns_inet6_limit, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(dns_inet6_limit, tc)
+{
+ cap_channel_t *capdns;
+ const char *types[2];
+ int families[2];
+
+ capdns = cap_dns_init();
+
+ types[0] = "NAME2ADDR";
+ types[1] = "ADDR2NAME";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
+ families[0] = AF_INET6;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
+ families[1] = AF_INET;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 2) == -1);
+ families[0] = AF_INET;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 1) == -1);
+
+ runtest(capdns,
+ (GETHOSTBYNAME2_AF_INET6 | GETHOSTBYADDR_AF_INET6 |
+ GETADDRINFO_AF_INET6));
+
+ cap_close(capdns);
+}
+
+ATF_TC(dns_name_inet_limit);
+ATF_TC_HEAD(dns_name_inet_limit, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(dns_name_inet_limit, tc)
+{
+ cap_channel_t *capdns;
+ const char *types[2];
+ int families[2];
+
+ capdns = cap_dns_init();
+
+ types[0] = "NAME2ADDR";
+ types[1] = "ADDR2NAME";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
+ families[0] = AF_INET;
+ families[1] = AF_INET6;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
+ types[0] = "NAME2ADDR";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
+ types[1] = "ADDR2NAME";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 2) == -1);
+ types[0] = "ADDR2NAME";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 1) == -1);
+ families[0] = AF_INET;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
+ families[1] = AF_INET6;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 2) == -1);
+ families[0] = AF_INET6;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 1) == -1);
+
+ runtest(capdns,
+ (GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETADDRINFO_AF_INET));
+
+ cap_close(capdns);
+}
+
+ATF_TC(dns_name_inet6_limit);
+ATF_TC_HEAD(dns_name_inet6_limit, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(dns_name_inet6_limit, tc)
+{
+ cap_channel_t *capdns;
+ const char *types[2];
+ int families[2];
+
+ capdns = cap_dns_init();
+
+ types[0] = "NAME2ADDR";
+ types[1] = "ADDR2NAME";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
+ families[0] = AF_INET6;
+ families[1] = AF_INET;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
+ types[0] = "NAME2ADDR";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
+ types[1] = "ADDR2NAME";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 2) == -1);
+ types[0] = "ADDR2NAME";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 1) == -1);
+ families[0] = AF_INET6;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
+ families[1] = AF_INET;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 2) == -1);
+ families[0] = AF_INET;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 1) == -1);
+
+ runtest(capdns,
+ (GETHOSTBYNAME2_AF_INET6 | GETADDRINFO_AF_INET6));
+
+ cap_close(capdns);
+}
+
+ATF_TC(dns_addr_inet_limit);
+ATF_TC_HEAD(dns_addr_inet_limit, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(dns_addr_inet_limit, tc)
+{
+ cap_channel_t *capdns;
+ const char *types[2];
+ int families[2];
+
+ capdns = cap_dns_init();
+
+ types[0] = "NAME2ADDR";
+ types[1] = "ADDR2NAME";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
+ families[0] = AF_INET;
+ families[1] = AF_INET6;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
+ types[0] = "ADDR2NAME";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
+ types[1] = "NAME2ADDR";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 2) == -1);
+ types[0] = "NAME2ADDR";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 1) == -1);
+ families[0] = AF_INET;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
+ families[1] = AF_INET6;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 2) == -1);
+ families[0] = AF_INET6;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 1) == -1);
+
+ runtest(capdns, GETHOSTBYADDR_AF_INET);
+
+ cap_close(capdns);
+}
+
+ATF_TC(dns_addr_inet6_limit);
+ATF_TC_HEAD(dns_addr_inet6_limit, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(dns_addr_inet6_limit, tc)
+{
+ cap_channel_t *capdns;
+ const char *types[2];
+ int families[2];
+
+ capdns = cap_dns_init();
+
+ types[0] = "NAME2ADDR";
+ types[1] = "ADDR2NAME";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0);
+ families[0] = AF_INET6;
+ families[1] = AF_INET;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0);
+ types[0] = "ADDR2NAME";
+ ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0);
+ types[1] = "NAME2ADDR";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 2) == -1);
+ types[0] = "NAME2ADDR";
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_type_limit(capdns, types, 1) == -1);
+ families[0] = AF_INET6;
+ ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0);
+ families[1] = AF_INET;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 2) == -1);
+ families[0] = AF_INET;
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE,
+ cap_dns_family_limit(capdns, families, 1) == -1);
+
+ runtest(capdns, GETHOSTBYADDR_AF_INET6);
+
+ cap_close(capdns);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, dns_no_limits);
+ ATF_TP_ADD_TC(tp, dns_all_limits);
+ ATF_TP_ADD_TC(tp, dns_name_limit);
+ ATF_TP_ADD_TC(tp, dns_addr_limit);
+ ATF_TP_ADD_TC(tp, dns_inet_limit);
+ ATF_TP_ADD_TC(tp, dns_inet6_limit);
+ ATF_TP_ADD_TC(tp, dns_name_inet_limit);
+ ATF_TP_ADD_TC(tp, dns_name_inet6_limit);
+ ATF_TP_ADD_TC(tp, dns_addr_inet_limit);
+ ATF_TP_ADD_TC(tp, dns_addr_inet6_limit);
+
+ return atf_no_error();
+}
diff --git a/lib/libcasper/services/cap_fileargs/Makefile b/lib/libcasper/services/cap_fileargs/Makefile
new file mode 100644
index 000000000000..2c52d0887a48
--- /dev/null
+++ b/lib/libcasper/services/cap_fileargs/Makefile
@@ -0,0 +1,38 @@
+SHLIBDIR?= /lib
+
+.include <src.opts.mk>
+
+PACKAGE= runtime
+
+SHLIB_MAJOR= 1
+INCSDIR?= ${INCLUDEDIR}/casper
+
+.if ${MK_CASPER} != "no"
+SHLIB= cap_fileargs
+
+SRCS= cap_fileargs.c
+.endif
+
+INCS= cap_fileargs.h
+
+LIBADD= nv
+
+CFLAGS+=-I${.CURDIR}
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+MAN+= cap_fileargs.3
+
+MLINKS+=cap_fileargs.3 libcap_fileargs.3
+MLINKS+=cap_fileargs.3 fileargs_cinit.3
+MLINKS+=cap_fileargs.3 fileargs_cinitnv.3
+MLINKS+=cap_fileargs.3 fileargs_fopen.3
+MLINKS+=cap_fileargs.3 fileargs_free.3
+MLINKS+=cap_fileargs.3 fileargs_init.3
+MLINKS+=cap_fileargs.3 fileargs_initnv.3
+MLINKS+=cap_fileargs.3 fileargs_lstat.3
+MLINKS+=cap_fileargs.3 fileargs_open.3
+MLINKS+=cap_fileargs.3 fileargs_realpath.3
+
+.include <bsd.lib.mk>
diff --git a/lib/libcasper/services/cap_fileargs/Makefile.depend b/lib/libcasper/services/cap_fileargs/Makefile.depend
new file mode 100644
index 000000000000..02bae00eb04d
--- /dev/null
+++ b/lib/libcasper/services/cap_fileargs/Makefile.depend
@@ -0,0 +1,17 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_fileargs/cap_fileargs.3 b/lib/libcasper/services/cap_fileargs/cap_fileargs.3
new file mode 100644
index 000000000000..6a69fe7e1f4a
--- /dev/null
+++ b/lib/libcasper/services/cap_fileargs/cap_fileargs.3
@@ -0,0 +1,300 @@
+.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd August 8, 2025
+.Dt CAP_FILEARGS 3
+.Os
+.Sh NAME
+.Nm cap_fileargs ,
+.Nm fileargs_cinit ,
+.Nm fileargs_cinitnv ,
+.Nm fileargs_init ,
+.Nm fileargs_initnv ,
+.Nm fileargs_free ,
+.Nm fileargs_lstat ,
+.Nm fileargs_open ,
+.Nm fileargs_fopen
+.Nd "library for handling files in capability mode"
+.Sh SYNOPSIS
+.Lb libcap_fileargs
+.In sys/nv.h
+.In libcasper.h
+.In casper/cap_fileargs.h
+.Ft "fileargs_t *"
+.Fn fileargs_init "int argc" "char *argv[]" "int flags" "mode_t mode" "cap_rights_t *rightsp" "int operations"
+.Ft "fileargs_t *"
+.Fn fileargs_cinit "cap_channel_t *cas" "int argc" "char *argv[]" "int flags" "mode_t mode" "cap_rights_t *rightsp" "int operations"
+.Ft "fileargs_t *"
+.Fn fileargs_cinitnv "cap_channel_t *cas" "nvlist_t *limits"
+.Ft "fileargs_t *"
+.Fn fileargs_initnv "nvlist_t *limits"
+.Ft "void"
+.Fn fileargs_free "fileargs_t *fa"
+.Ft "int"
+.Fn fileargs_lstat "fileargs_t *fa" "const char *path" "struct stat *sb"
+.Ft "int"
+.Fn fileargs_open "fileargs_t *fa" "const char *name"
+.Ft "FILE *"
+.Fn fileargs_fopen "fileargs_t *fa" "const char *name" "const char *mode"
+.Ft "char *"
+.Fn fileargs_realpath "fileargs_t *fa" "const char *pathname" "char *reserved_path"
+.Sh DESCRIPTION
+The
+.Nm
+library is used to simplify Capsicumizing tools that are using file system.
+The idea behind the library is that we pass the remaining arguments from
+.Fa argv
+(with count specified by
+.Fa argc )
+which contains the list of files that should be opened by the program.
+The library creates a service that will serve those files.
+.Pp
+The function
+.Fn fileargs_init
+creates a service to the
+.Nm system.fileargs .
+The
+.Fa argv
+contains a list of files that should be opened.
+The argument can be set to
+.Dv NULL
+to create no service and prohibit all files from being opened.
+The
+.Fa argc
+argument contains the number of files passed to the program.
+The
+.Fa flags
+argument specifies whether files can be opened for execution, for reading,
+and/or for writing.
+The
+.Fa mode
+argument specifies the permissions to use when creating new files if the
+.Dv O_CREAT
+flag is set.
+For more information about the
+.Fa flags
+and
+.Fa mode
+arguments, see
+.Xr open 2 .
+The
+.Fa rightsp
+argument specifies the capability rights that will be applied to restrict
+access to the files.
+For more information about capability rights, see
+.Xr cap_rights_init 3 .
+The
+.Fa operations
+argument specifies which operations are permitted when using
+.Nm system.fileargs .
+The following flags can be combined to form the
+.Fa operations
+value:
+.Bl -ohang -offset indent
+.It FA_OPEN
+Allow
+.Fn fileargs_open
+and
+.Fn fileargs_fopen .
+.It FA_LSTAT
+Allow
+.Fn fileargs_lstat .
+.It FA_REALPATH
+Allow
+.Fn fileargs_realpath .
+.El
+.Pp
+The function
+.Fn fileargs_cinit
+behaves identically to
+.Fn fileargs_init ,
+but requires an existing Casper connection to be passed as an argument.
+.Pp
+The functions
+.Fn fileargs_initnv
+and
+.Fn fileargs_cinitnv
+are equivalent to
+.Fn fileargs_init
+and
+.Fn fileargs_cinit
+respectively, but take their arguments in the form of an
+.Xr nvlist 9
+structure.
+See the
+.Sx LIMITS
+section for details on the expected argument types and values.
+.Pp
+The
+.Fn fileargs_free
+function closes the connection to the
+.Nm system.fileargs
+service and frees all associated data structures.
+The function safely handles
+.Dv NULL
+arguments.
+.Pp
+The function
+.Fn fileargs_lstat
+provides the same functionality as
+.Xr lstat 2 .
+.Pp
+The functions
+.Fn fileargs_open
+and
+.Fn fileargs_fopen
+behave identically to
+.Xr open 2
+and
+.Xr fopen 3
+respectively, but retrieve their arguments from the
+.Va fileargs_t
+structure.
+.Pp
+The function
+.Fn fileargs_realpath
+provides the same functionality as the standard C library function
+.Xr realpath 3 ,
+resolving all symbolic links and references in a pathname.
+.Pp
+The following functions are reentrant but require synchronization for
+thread safety:
+.Fn fileargs_open ,
+.Fn fileargs_lstat ,
+.Fn fileargs_realpath ,
+.Fn fileargs_cinitnv ,
+.Fn fileargs_initnv ,
+and
+.Fn fileargs_fopen .
+Multiple threads can call these functions safely only if they use different
+.Vt cap_channel_t
+arguments or proper synchronization mechanisms.
+.Sh LIMITS
+This section describes the required and optional arguments that must be
+passed to
+.Fa system.fileargs
+via the
+.Fn fileargs_initnv
+and
+.Fn fileargs_cinitnv
+functions using an
+.Xr nvlist 9
+structure.
+.Pp
+The following arguments are required:
+.Bl -ohang -offset indent
+.It flags Pq Dv NV_TYPE_NUMBER
+Specifies access permissions for opened files.
+.It mode Pq Dv NV_TYPE_NUMBER
+Required when the
+.Dv O_CREATE
+flag is set in
+.Va flags .
+Specifies the permissions to use when creating new files.
+.It operations Pq Dv NV_TYPE_NUMBER
+Specifies which operations are allowed for
+.Fa system.fileargs .
+See the description of the
+.Va operations
+argument in
+.Fn fileargs_init
+for possible values.
+.El
+.Pp
+The following arguments are optional in the
+.Xr nvlist 9
+structure:
+.Bl -ohang -offset indent
+.It cap_rights Pq Dv NV_TYPE_BINARY
+The
+.Va cap_rights
+argument specifies the capability rights that will be applied to restrict
+access to opened files.
+.It filenames Pq Dv NV_TYPE_NULL
+Multiple
+.Dv NV_TYPE_NULL
+elements can be provided, where each element's name represents a file
+path that is allowed to be opened.
+.El
+.Sh EXAMPLES
+.Bd -literal
+int ch, fd, i;
+cap_rights_t rights;
+fileargs_t *fa;
+
+while ((ch = getopt(argc, argv, "h")) != -1) {
+ switch (ch) {
+ case 'h':
+ default:
+ usage();
+ }
+}
+
+argc -= optind;
+argv += optind;
+
+/* Create capability to the system.fileargs service. */
+fa = fileargs_init(argc, argv, O_RDONLY, 0,
+ cap_rights_init(&rights, CAP_READ), FA_OPEN);
+if (fa == NULL)
+ err(1, "unable to open system.fileargs service");
+
+/* Enter capability mode sandbox. */
+if (cap_enter() < 0 && errno != ENOSYS)
+ err(1, "unable to enter capability mode");
+
+/* Open files. */
+for (i = 0; i < argc; i++) {
+ fd = fileargs_open(fa, argv[i]);
+ if (fd < 0)
+ err(1, "unable to open file %s", argv[i]);
+ printf("File %s opened in capability mode\en", argv[i]);
+ close(fd);
+}
+
+fileargs_free(fa);
+.Ed
+.Sh SEE ALSO
+.Xr cap_enter 2 ,
+.Xr lstat 2 ,
+.Xr open 2 ,
+.Xr cap_rights_init 3 ,
+.Xr err 3 ,
+.Xr fopen 3 ,
+.Xr getopt 3 ,
+.Xr realpath 3 ,
+.Xr capsicum 4 ,
+.Xr nv 9
+.Sh HISTORY
+The
+.Nm
+service first appeared in
+.Fx 10.3 .
+.Sh AUTHORS
+.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org
+.Sh BUGS
+The
+.Nm
+service is considered experimental and should be thoroughly evaluated
+for risks before deploying in production environments.
diff --git a/lib/libcasper/services/cap_fileargs/cap_fileargs.c b/lib/libcasper/services/cap_fileargs/cap_fileargs.c
new file mode 100644
index 000000000000..233b47ede613
--- /dev/null
+++ b/lib/libcasper/services/cap_fileargs/cap_fileargs.c
@@ -0,0 +1,737 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2018-2021 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/capsicum.h>
+#include <sys/sysctl.h>
+#include <sys/cnv.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcasper.h>
+#include <libcasper_service.h>
+
+#include "cap_fileargs.h"
+
+#define CACHE_SIZE 128
+
+#define FILEARGS_MAGIC 0xFA00FA00
+
+struct fileargs {
+ uint32_t fa_magic;
+ nvlist_t *fa_cache;
+ cap_channel_t *fa_chann;
+ int fa_fdflags;
+};
+
+static int
+fileargs_get_lstat_cache(fileargs_t *fa, const char *name, struct stat *sb)
+{
+ const nvlist_t *nvl;
+ size_t size;
+ const void *buf;
+
+ assert(fa != NULL);
+ assert(fa->fa_magic == FILEARGS_MAGIC);
+ assert(name != NULL);
+
+ if (fa->fa_cache == NULL)
+ return (-1);
+
+ nvl = dnvlist_get_nvlist(fa->fa_cache, name, NULL);
+ if (nvl == NULL)
+ return (-1);
+
+ if (!nvlist_exists_binary(nvl, "stat")) {
+ return (-1);
+ }
+
+ buf = nvlist_get_binary(nvl, "stat", &size);
+ assert(size == sizeof(*sb));
+ memcpy(sb, buf, size);
+
+ return (0);
+}
+
+static int
+fileargs_get_fd_cache(fileargs_t *fa, const char *name)
+{
+ int fd;
+ const nvlist_t *nvl;
+ nvlist_t *tnvl;
+
+ assert(fa != NULL);
+ assert(fa->fa_magic == FILEARGS_MAGIC);
+ assert(name != NULL);
+
+ if (fa->fa_cache == NULL)
+ return (-1);
+
+ if ((fa->fa_fdflags & O_CREAT) != 0)
+ return (-1);
+
+ nvl = dnvlist_get_nvlist(fa->fa_cache, name, NULL);
+ if (nvl == NULL)
+ return (-1);
+
+ tnvl = nvlist_take_nvlist(fa->fa_cache, name);
+
+ if (!nvlist_exists_descriptor(tnvl, "fd")) {
+ nvlist_destroy(tnvl);
+ return (-1);
+ }
+
+ fd = nvlist_take_descriptor(tnvl, "fd");
+ nvlist_destroy(tnvl);
+
+ if ((fa->fa_fdflags & O_CLOEXEC) != O_CLOEXEC) {
+ if (fcntl(fd, F_SETFD, fa->fa_fdflags) == -1) {
+ close(fd);
+ return (-1);
+ }
+ }
+
+ return (fd);
+}
+
+static void
+fileargs_set_cache(fileargs_t *fa, nvlist_t *nvl)
+{
+
+ nvlist_destroy(fa->fa_cache);
+ fa->fa_cache = nvl;
+}
+
+static nvlist_t*
+fileargs_fetch(fileargs_t *fa, const char *name, const char *cmd)
+{
+ nvlist_t *nvl;
+ int serrno;
+
+ assert(fa != NULL);
+ assert(name != NULL);
+
+ nvl = nvlist_create(NV_FLAG_NO_UNIQUE);
+ nvlist_add_string(nvl, "cmd", cmd);
+ nvlist_add_string(nvl, "name", name);
+
+ nvl = cap_xfer_nvlist(fa->fa_chann, nvl);
+ if (nvl == NULL)
+ return (NULL);
+
+ if (nvlist_get_number(nvl, "error") != 0) {
+ serrno = (int)nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ errno = serrno;
+ return (NULL);
+ }
+
+ return (nvl);
+}
+
+static nvlist_t *
+fileargs_create_limit(int argc, const char * const *argv, int flags,
+ mode_t mode, cap_rights_t *rightsp, int operations)
+{
+ nvlist_t *limits;
+ int i;
+
+ limits = nvlist_create(NV_FLAG_NO_UNIQUE);
+ if (limits == NULL)
+ return (NULL);
+
+ nvlist_add_number(limits, "flags", flags);
+ nvlist_add_number(limits, "operations", operations);
+ if (rightsp != NULL) {
+ nvlist_add_binary(limits, "cap_rights", rightsp,
+ sizeof(*rightsp));
+ }
+ if ((flags & O_CREAT) != 0)
+ nvlist_add_number(limits, "mode", (uint64_t)mode);
+
+ for (i = 0; i < argc; i++) {
+ if (strlen(argv[i]) >= MAXPATHLEN) {
+ nvlist_destroy(limits);
+ errno = ENAMETOOLONG;
+ return (NULL);
+ }
+ nvlist_add_null(limits, argv[i]);
+ }
+
+ return (limits);
+}
+
+static fileargs_t *
+fileargs_create(cap_channel_t *chan, int fdflags)
+{
+ fileargs_t *fa;
+
+ fa = malloc(sizeof(*fa));
+ if (fa != NULL) {
+ fa->fa_cache = NULL;
+ fa->fa_chann = chan;
+ fa->fa_fdflags = fdflags;
+ fa->fa_magic = FILEARGS_MAGIC;
+ }
+
+ return (fa);
+}
+
+fileargs_t *
+fileargs_init(int argc, char *argv[], int flags, mode_t mode,
+ cap_rights_t *rightsp, int operations)
+{
+ nvlist_t *limits;
+
+ if (argc <= 0 || argv == NULL) {
+ return (fileargs_create(NULL, 0));
+ }
+
+ limits = fileargs_create_limit(argc, (const char * const *)argv, flags,
+ mode, rightsp, operations);
+ if (limits == NULL)
+ return (NULL);
+
+ return (fileargs_initnv(limits));
+}
+
+fileargs_t *
+fileargs_cinit(cap_channel_t *cas, int argc, char *argv[], int flags,
+ mode_t mode, cap_rights_t *rightsp, int operations)
+{
+ nvlist_t *limits;
+
+ if (argc <= 0 || argv == NULL) {
+ return (fileargs_create(NULL, 0));
+ }
+
+ limits = fileargs_create_limit(argc, (const char * const *)argv, flags,
+ mode, rightsp, operations);
+ if (limits == NULL)
+ return (NULL);
+
+ return (fileargs_cinitnv(cas, limits));
+}
+
+fileargs_t *
+fileargs_initnv(nvlist_t *limits)
+{
+ cap_channel_t *cas;
+ fileargs_t *fa;
+
+ if (limits == NULL) {
+ return (fileargs_create(NULL, 0));
+ }
+
+ cas = cap_init();
+ if (cas == NULL) {
+ nvlist_destroy(limits);
+ return (NULL);
+ }
+
+ fa = fileargs_cinitnv(cas, limits);
+ cap_close(cas);
+
+ return (fa);
+}
+
+fileargs_t *
+fileargs_cinitnv(cap_channel_t *cas, nvlist_t *limits)
+{
+ cap_channel_t *chann;
+ fileargs_t *fa;
+ int flags, ret, serrno;
+
+ assert(cas != NULL);
+
+ if (limits == NULL) {
+ return (fileargs_create(NULL, 0));
+ }
+
+ chann = NULL;
+ fa = NULL;
+
+ chann = cap_service_open(cas, "system.fileargs");
+ if (chann == NULL) {
+ nvlist_destroy(limits);
+ return (NULL);
+ }
+
+ flags = nvlist_get_number(limits, "flags");
+ (void)nvlist_get_number(limits, "operations");
+
+ /* Limits are consumed no need to free them. */
+ ret = cap_limit_set(chann, limits);
+ if (ret < 0)
+ goto out;
+
+ fa = fileargs_create(chann, flags);
+ if (fa == NULL)
+ goto out;
+
+ return (fa);
+out:
+ serrno = errno;
+ if (chann != NULL)
+ cap_close(chann);
+ errno = serrno;
+ return (NULL);
+}
+
+int
+fileargs_open(fileargs_t *fa, const char *name)
+{
+ int fd;
+ nvlist_t *nvl;
+ char *cmd;
+
+ assert(fa != NULL);
+ assert(fa->fa_magic == FILEARGS_MAGIC);
+
+ if (name == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if (fa->fa_chann == NULL) {
+ errno = ENOTCAPABLE;
+ return (-1);
+ }
+
+ fd = fileargs_get_fd_cache(fa, name);
+ if (fd != -1)
+ return (fd);
+
+ nvl = fileargs_fetch(fa, name, "open");
+ if (nvl == NULL)
+ return (-1);
+
+ fd = nvlist_take_descriptor(nvl, "fd");
+ cmd = nvlist_take_string(nvl, "cmd");
+ if (strcmp(cmd, "cache") == 0)
+ fileargs_set_cache(fa, nvl);
+ else
+ nvlist_destroy(nvl);
+ free(cmd);
+
+ return (fd);
+}
+
+FILE *
+fileargs_fopen(fileargs_t *fa, const char *name, const char *mode)
+{
+ int fd;
+
+ if ((fd = fileargs_open(fa, name)) < 0) {
+ return (NULL);
+ }
+
+ return (fdopen(fd, mode));
+}
+
+int
+fileargs_lstat(fileargs_t *fa, const char *name, struct stat *sb)
+{
+ nvlist_t *nvl;
+ const void *buf;
+ size_t size;
+ char *cmd;
+
+ assert(fa != NULL);
+ assert(fa->fa_magic == FILEARGS_MAGIC);
+
+ if (name == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if (sb == NULL) {
+ errno = EFAULT;
+ return (-1);
+ }
+
+ if (fa->fa_chann == NULL) {
+ errno = ENOTCAPABLE;
+ return (-1);
+ }
+
+ if (fileargs_get_lstat_cache(fa, name, sb) != -1)
+ return (0);
+
+ nvl = fileargs_fetch(fa, name, "lstat");
+ if (nvl == NULL)
+ return (-1);
+
+ buf = nvlist_get_binary(nvl, "stat", &size);
+ assert(size == sizeof(*sb));
+ memcpy(sb, buf, size);
+
+ cmd = nvlist_take_string(nvl, "cmd");
+ if (strcmp(cmd, "cache") == 0)
+ fileargs_set_cache(fa, nvl);
+ else
+ nvlist_destroy(nvl);
+ free(cmd);
+
+ return (0);
+}
+
+char *
+fileargs_realpath(fileargs_t *fa, const char *pathname, char *reserved_path)
+{
+ nvlist_t *nvl;
+ char *ret;
+
+ assert(fa != NULL);
+ assert(fa->fa_magic == FILEARGS_MAGIC);
+
+ if (pathname == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if (fa->fa_chann == NULL) {
+ errno = ENOTCAPABLE;
+ return (NULL);
+ }
+
+ nvl = fileargs_fetch(fa, pathname, "realpath");
+ if (nvl == NULL)
+ return (NULL);
+
+ if (reserved_path != NULL) {
+ ret = reserved_path;
+ strcpy(reserved_path,
+ nvlist_get_string(nvl, "realpath"));
+ } else {
+ ret = nvlist_take_string(nvl, "realpath");
+ }
+ nvlist_destroy(nvl);
+
+ return (ret);
+}
+
+void
+fileargs_free(fileargs_t *fa)
+{
+
+ if (fa == NULL)
+ return;
+
+ assert(fa->fa_magic == FILEARGS_MAGIC);
+
+ nvlist_destroy(fa->fa_cache);
+ if (fa->fa_chann != NULL) {
+ cap_close(fa->fa_chann);
+ }
+ explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic));
+ free(fa);
+}
+
+cap_channel_t *
+fileargs_unwrap(fileargs_t *fa, int *flags)
+{
+ cap_channel_t *chan;
+
+ if (fa == NULL)
+ return (NULL);
+
+ assert(fa->fa_magic == FILEARGS_MAGIC);
+
+ chan = fa->fa_chann;
+ if (flags != NULL) {
+ *flags = fa->fa_fdflags;
+ }
+
+ nvlist_destroy(fa->fa_cache);
+ explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic));
+ free(fa);
+
+ return (chan);
+}
+
+fileargs_t *
+fileargs_wrap(cap_channel_t *chan, int fdflags)
+{
+
+ if (chan == NULL) {
+ return (NULL);
+ }
+
+ return (fileargs_create(chan, fdflags));
+}
+
+/*
+ * Service functions.
+ */
+
+static const char *lastname;
+static void *cacheposition;
+static bool allcached;
+static const cap_rights_t *caprightsp;
+static int capflags;
+static int allowed_operations;
+static mode_t capmode;
+
+static int
+open_file(const char *name)
+{
+ int fd, serrno;
+
+ if ((capflags & O_CREAT) == 0)
+ fd = open(name, capflags);
+ else
+ fd = open(name, capflags, capmode);
+ if (fd < 0)
+ return (-1);
+
+ if (caprightsp != NULL) {
+ if (cap_rights_limit(fd, caprightsp) < 0 && errno != ENOSYS) {
+ serrno = errno;
+ close(fd);
+ errno = serrno;
+ return (-1);
+ }
+ }
+
+ return (fd);
+}
+
+static void
+fileargs_add_cache(nvlist_t *nvlout, const nvlist_t *limits,
+ const char *current_name)
+{
+ int type, i, fd;
+ void *cookie;
+ nvlist_t *new;
+ const char *fname;
+ struct stat sb;
+
+ if ((capflags & O_CREAT) != 0) {
+ allcached = true;
+ return;
+ }
+
+ cookie = cacheposition;
+ for (i = 0; i < CACHE_SIZE + 1; i++) {
+ fname = nvlist_next(limits, &type, &cookie);
+ if (fname == NULL) {
+ cacheposition = NULL;
+ lastname = NULL;
+ allcached = true;
+ return;
+ }
+ /* We doing that to catch next element name. */
+ if (i == CACHE_SIZE) {
+ break;
+ }
+
+ if (type != NV_TYPE_NULL) {
+ i--;
+ continue;
+ }
+ if (current_name != NULL &&
+ strcmp(fname, current_name) == 0) {
+ current_name = NULL;
+ i--;
+ continue;
+ }
+
+ new = nvlist_create(NV_FLAG_NO_UNIQUE);
+ if ((allowed_operations & FA_OPEN) != 0) {
+ fd = open_file(fname);
+ if (fd < 0) {
+ i--;
+ nvlist_destroy(new);
+ continue;
+ }
+ nvlist_move_descriptor(new, "fd", fd);
+ }
+ if ((allowed_operations & FA_LSTAT) != 0) {
+ if (lstat(fname, &sb) < 0) {
+ i--;
+ nvlist_destroy(new);
+ continue;
+ }
+ nvlist_add_binary(new, "stat", &sb, sizeof(sb));
+ }
+
+ nvlist_move_nvlist(nvlout, fname, new);
+ }
+ cacheposition = cookie;
+ lastname = fname;
+}
+
+static bool
+fileargs_allowed(const nvlist_t *limits, const nvlist_t *request, int operation)
+{
+ const char *name;
+
+ if ((allowed_operations & operation) == 0)
+ return (false);
+
+ name = dnvlist_get_string(request, "name", NULL);
+ if (name == NULL)
+ return (false);
+
+ /* Fast path. */
+ if (lastname != NULL && strcmp(name, lastname) == 0)
+ return (true);
+
+ if (!nvlist_exists_null(limits, name))
+ return (false);
+
+ return (true);
+}
+
+static int
+fileargs_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+
+ if (oldlimits != NULL)
+ return (ENOTCAPABLE);
+
+ capflags = (int)dnvlist_get_number(newlimits, "flags", 0);
+ allowed_operations = (int)dnvlist_get_number(newlimits, "operations", 0);
+ if ((capflags & O_CREAT) != 0)
+ capmode = (mode_t)nvlist_get_number(newlimits, "mode");
+ else
+ capmode = 0;
+
+ caprightsp = dnvlist_get_binary(newlimits, "cap_rights", NULL, NULL, 0);
+
+ return (0);
+}
+
+static int
+fileargs_command_lstat(const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ int error;
+ const char *name;
+ struct stat sb;
+
+ if (limits == NULL)
+ return (ENOTCAPABLE);
+
+ if (!fileargs_allowed(limits, nvlin, FA_LSTAT))
+ return (ENOTCAPABLE);
+
+ name = nvlist_get_string(nvlin, "name");
+
+ error = lstat(name, &sb);
+ if (error < 0)
+ return (errno);
+
+ if (!allcached && (lastname == NULL ||
+ strcmp(name, lastname) == 0)) {
+ nvlist_add_string(nvlout, "cmd", "cache");
+ fileargs_add_cache(nvlout, limits, name);
+ } else {
+ nvlist_add_string(nvlout, "cmd", "lstat");
+ }
+ nvlist_add_binary(nvlout, "stat", &sb, sizeof(sb));
+ return (0);
+}
+
+static int
+fileargs_command_realpath(const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ const char *pathname;
+ char *resolvedpath;
+
+ if (limits == NULL)
+ return (ENOTCAPABLE);
+
+ if (!fileargs_allowed(limits, nvlin, FA_REALPATH))
+ return (ENOTCAPABLE);
+
+ pathname = nvlist_get_string(nvlin, "name");
+ resolvedpath = realpath(pathname, NULL);
+ if (resolvedpath == NULL)
+ return (errno);
+
+ nvlist_move_string(nvlout, "realpath", resolvedpath);
+ return (0);
+}
+
+static int
+fileargs_command_open(const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ int fd;
+ const char *name;
+
+ if (limits == NULL)
+ return (ENOTCAPABLE);
+
+ if (!fileargs_allowed(limits, nvlin, FA_OPEN))
+ return (ENOTCAPABLE);
+
+ name = nvlist_get_string(nvlin, "name");
+
+ fd = open_file(name);
+ if (fd < 0)
+ return (errno);
+
+ if (!allcached && (lastname == NULL ||
+ strcmp(name, lastname) == 0)) {
+ nvlist_add_string(nvlout, "cmd", "cache");
+ fileargs_add_cache(nvlout, limits, name);
+ } else {
+ nvlist_add_string(nvlout, "cmd", "open");
+ }
+ nvlist_move_descriptor(nvlout, "fd", fd);
+ return (0);
+}
+
+static int
+fileargs_command(const char *cmd, const nvlist_t *limits,
+ nvlist_t *nvlin, nvlist_t *nvlout)
+{
+
+ if (strcmp(cmd, "open") == 0)
+ return (fileargs_command_open(limits, nvlin, nvlout));
+ if (strcmp(cmd, "lstat") == 0)
+ return (fileargs_command_lstat(limits, nvlin, nvlout));
+ if (strcmp(cmd, "realpath") == 0)
+ return (fileargs_command_realpath(limits, nvlin, nvlout));
+
+ return (EINVAL);
+}
+
+CREATE_SERVICE("system.fileargs", fileargs_limit, fileargs_command,
+ CASPER_SERVICE_FD | CASPER_SERVICE_STDIO | CASPER_SERVICE_NO_UNIQ_LIMITS);
diff --git a/lib/libcasper/services/cap_fileargs/cap_fileargs.h b/lib/libcasper/services/cap_fileargs/cap_fileargs.h
new file mode 100644
index 000000000000..8207671d9753
--- /dev/null
+++ b/lib/libcasper/services/cap_fileargs/cap_fileargs.h
@@ -0,0 +1,153 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _FILEARGS_H_
+#define _FILEARGS_H_
+
+#include <sys/cdefs.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+
+#include <stdbool.h>
+
+#define FA_OPEN 1
+#define FA_LSTAT 2
+#define FA_REALPATH 4
+
+#ifdef WITH_CASPER
+struct fileargs;
+typedef struct fileargs fileargs_t;
+struct stat;
+
+__BEGIN_DECLS
+
+fileargs_t *fileargs_init(int argc, char *argv[], int flags, mode_t mode,
+ cap_rights_t *rightsp, int operations);
+fileargs_t *fileargs_cinit(cap_channel_t *cas, int argc, char *argv[],
+ int flags, mode_t mode, cap_rights_t *rightsp, int operations);
+fileargs_t *fileargs_initnv(nvlist_t *limits);
+fileargs_t *fileargs_cinitnv(cap_channel_t *cas, nvlist_t *limits);
+int fileargs_lstat(fileargs_t *fa, const char *name, struct stat *sb);
+int fileargs_open(fileargs_t *fa, const char *name);
+char *fileargs_realpath(fileargs_t *fa, const char *pathname,
+ char *reserved_path);
+void fileargs_free(fileargs_t *fa);
+FILE *fileargs_fopen(fileargs_t *fa, const char *name, const char *mode);
+
+fileargs_t *fileargs_wrap(cap_channel_t *chan, int fdflags);
+cap_channel_t *fileargs_unwrap(fileargs_t *fa, int *fdflags);
+
+__END_DECLS
+
+#else
+typedef struct fileargs {
+ int fa_flags;
+ mode_t fa_mode;
+} fileargs_t;
+
+static inline fileargs_t *
+fileargs_init(int argc __unused, char *argv[] __unused, int flags, mode_t mode,
+ cap_rights_t *rightsp __unused, int operations __unused) {
+ fileargs_t *fa;
+
+ fa = malloc(sizeof(*fa));
+ if (fa != NULL) {
+ fa->fa_flags = flags;
+ fa->fa_mode = mode;
+ }
+
+ return (fa);
+}
+
+static inline fileargs_t *
+fileargs_cinit(cap_channel_t *cas __unused, int argc, char *argv[], int flags,
+ mode_t mode, cap_rights_t *rightsp, int operations)
+{
+
+ return (fileargs_init(argc, argv, flags, mode, rightsp, operations));
+}
+
+static inline fileargs_t *
+fileargs_initnv(nvlist_t *limits)
+{
+ fileargs_t *fa;
+
+ fa = fileargs_init(0, NULL,
+ nvlist_get_number(limits, "flags"),
+ dnvlist_get_number(limits, "mode", 0),
+ NULL,
+ nvlist_get_number(limits, "operations"));
+ nvlist_destroy(limits);
+
+ return (fa);
+}
+
+static inline fileargs_t *
+fileargs_cinitnv(cap_channel_t *cas __unused, nvlist_t *limits)
+{
+
+ return (fileargs_initnv(limits));
+}
+
+#define fileargs_lstat(fa, name, sb) \
+ lstat(name, sb)
+#define fileargs_open(fa, name) \
+ open(name, fa->fa_flags, fa->fa_mode)
+#define fileargs_realpath(fa, pathname, reserved_path) \
+ realpath(pathname, reserved_path)
+
+static inline
+FILE *fileargs_fopen(fileargs_t *fa, const char *name, const char *mode)
+{
+ (void) fa;
+ return (fopen(name, mode));
+}
+#define fileargs_free(fa) (free(fa))
+
+static inline fileargs_t *
+fileargs_wrap(cap_channel_t *chan, int fdflags)
+{
+
+ cap_close(chan);
+ return (fileargs_init(0, NULL, fdflags, 0, NULL, 0));
+}
+
+static inline cap_channel_t *
+fileargs_unwrap(fileargs_t *fa, int *fdflags)
+{
+
+ if (fdflags != NULL) {
+ *fdflags = fa->fa_flags;
+ }
+ fileargs_free(fa);
+ return (cap_init());
+}
+
+#endif
+
+#endif /* !_FILEARGS_H_ */
diff --git a/lib/libcasper/services/cap_fileargs/tests/Makefile b/lib/libcasper/services/cap_fileargs/tests/Makefile
new file mode 100644
index 000000000000..02f02cceab8e
--- /dev/null
+++ b/lib/libcasper/services/cap_fileargs/tests/Makefile
@@ -0,0 +1,16 @@
+.include <src.opts.mk>
+
+ATF_TESTS_C= fileargs_test
+
+CFLAGS+= -I${SRCTOP}/tests
+
+.if ${MK_CASPER} != "no"
+LIBADD+= casper
+LIBADD+= cap_fileargs
+CFLAGS+=-DWITH_CASPER
+.endif
+LIBADD+= nv
+
+WARNS?= 3
+
+.include <bsd.test.mk>
diff --git a/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c b/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c
new file mode 100644
index 000000000000..088edaaa294d
--- /dev/null
+++ b/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c
@@ -0,0 +1,777 @@
+/*-
+ * Copyright (c) 2021 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/capsicum.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <atf-c.h>
+
+#include <libcasper.h>
+#include <casper/cap_fileargs.h>
+
+#include "freebsd_test_suite/macros.h"
+
+#define MAX_FILES 200
+
+static char *files[MAX_FILES];
+static int fds[MAX_FILES];
+
+#define TEST_FILE "/etc/passwd"
+
+static void
+check_capsicum(void)
+{
+ ATF_REQUIRE_FEATURE("security_capabilities");
+ ATF_REQUIRE_FEATURE("security_capability_mode");
+}
+
+static void
+prepare_files(size_t num, bool create)
+{
+ const char template[] = "testsfiles.XXXXXXXX";
+ size_t i;
+
+ for (i = 0; i < num; i++) {
+ files[i] = calloc(1, sizeof(template));
+ ATF_REQUIRE(files[i] != NULL);
+ strncpy(files[i], template, sizeof(template) - 1);
+
+ if (create) {
+ fds[i] = mkstemp(files[i]);
+ ATF_REQUIRE(fds[i] >= 0);
+ } else {
+ fds[i] = -1;
+ ATF_REQUIRE(mktemp(files[i]) != NULL);
+ }
+ }
+}
+
+static void
+clear_files(void)
+{
+ size_t i;
+
+
+ for (i = 0; files[i] != NULL; i++) {
+ unlink(files[i]);
+ free(files[i]);
+ if (fds[i] != -1)
+ close(fds[i]);
+ }
+}
+
+static int
+test_file_open(fileargs_t *fa, const char *file, int *fdp)
+{
+ int fd;
+
+ fd = fileargs_open(fa, file);
+ if (fd < 0)
+ return (errno);
+
+ if (fdp != NULL) {
+ *fdp = fd;
+ }
+
+ return (0);
+}
+
+static int
+test_file_fopen(fileargs_t *fa, const char *file, const char *mode,
+ FILE **retfile)
+{
+ FILE *pfile;
+
+ pfile = fileargs_fopen(fa, file, mode);
+ if (pfile == NULL)
+ return (errno);
+
+ if (retfile != NULL) {
+ *retfile = pfile;
+ }
+
+ return (0);
+}
+
+static int
+test_file_lstat(fileargs_t *fa, const char *file)
+{
+ struct stat fasb, origsb;
+ bool equals;
+
+ if (fileargs_lstat(fa, file, &fasb) < 0)
+ return (errno);
+
+ ATF_REQUIRE(lstat(file, &origsb) == 0);
+
+ equals = true;
+ equals &= (origsb.st_dev == fasb.st_dev);
+ equals &= (origsb.st_ino == fasb.st_ino);
+ equals &= (origsb.st_nlink == fasb.st_nlink);
+ equals &= (origsb.st_flags == fasb.st_flags);
+ equals &= (memcmp(&origsb.st_ctim, &fasb.st_ctim,
+ sizeof(fasb.st_ctim)) == 0);
+ equals &= (memcmp(&origsb.st_birthtim, &fasb.st_birthtim,
+ sizeof(fasb.st_birthtim)) == 0);
+ if (!equals) {
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
+static int
+test_file_realpath_static(fileargs_t *fa, const char *file)
+{
+ char fapath[PATH_MAX], origpath[PATH_MAX];
+
+ if (fileargs_realpath(fa, file, fapath) == NULL)
+ return (errno);
+
+ ATF_REQUIRE(realpath(file, origpath) != NULL);
+
+ if (strcmp(fapath, origpath) != 0)
+ return (EINVAL);
+
+ return (0);
+}
+
+static int
+test_file_realpath_alloc(fileargs_t *fa, const char *file)
+{
+ char *fapath, *origpath;
+ int serrno;
+
+ fapath = fileargs_realpath(fa, file, NULL);
+ if (fapath == NULL)
+ return (errno);
+
+ origpath = realpath(file, NULL);
+ ATF_REQUIRE(origpath != NULL);
+
+ serrno = 0;
+ if (strcmp(fapath, origpath) != 0)
+ serrno = EINVAL;
+
+ free(fapath);
+ free(origpath);
+
+ return (serrno);
+}
+
+static int
+test_file_realpath(fileargs_t *fa, const char *file)
+{
+ int serrno;
+
+ serrno = test_file_realpath_static(fa, file);
+ if (serrno != 0)
+ return serrno;
+
+ return (test_file_realpath_alloc(fa, file));
+}
+
+static int
+test_file_mode(int fd, int mode)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0)
+ return (errno);
+
+ if ((flags & O_ACCMODE) != mode)
+ return (errno);
+
+ return (0);
+}
+
+static bool
+test_file_cap(int fd, cap_rights_t *rights)
+{
+ cap_rights_t fdrights;
+
+ ATF_REQUIRE(cap_rights_get(fd, &fdrights) == 0);
+
+ return (cap_rights_contains(&fdrights, rights));
+}
+
+static int
+test_file_write(int fd)
+{
+ char buf;
+
+ buf = 't';
+ if (write(fd, &buf, sizeof(buf)) != sizeof(buf)) {
+ return (errno);
+ }
+
+ return (0);
+}
+
+static int
+test_file_read(int fd)
+{
+ char buf;
+
+ if (read(fd, &buf, sizeof(buf)) < 0) {
+ return (errno);
+ }
+
+ return (0);
+}
+
+static int
+test_file_fwrite(FILE *pfile)
+{
+ char buf;
+
+ buf = 't';
+ if (fwrite(&buf, sizeof(buf), 1, pfile) != sizeof(buf))
+ return (errno);
+
+ return (0);
+}
+
+static int
+test_file_fread(FILE *pfile)
+{
+ char buf;
+ int ret, serrno;
+
+ errno = 0;
+ ret = fread(&buf, sizeof(buf), 1, pfile);
+ serrno = errno;
+ if (ret < 0) {
+ return (serrno);
+ } else if (ret == 0 && feof(pfile) == 0) {
+ return (serrno != 0 ? serrno : EINVAL);
+ }
+
+ return (0);
+}
+
+ATF_TC_WITH_CLEANUP(fileargs__open_read);
+ATF_TC_HEAD(fileargs__open_read, tc) {}
+ATF_TC_BODY(fileargs__open_read, tc)
+{
+ cap_rights_t rights, norights;
+ fileargs_t *fa;
+ size_t i;
+ int fd;
+
+ check_capsicum();
+
+ prepare_files(MAX_FILES, true);
+
+ cap_rights_init(&rights, CAP_READ, CAP_FCNTL);
+ cap_rights_init(&norights, CAP_WRITE);
+ fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights,
+ FA_OPEN);
+ ATF_REQUIRE(fa != NULL);
+
+ for (i = 0; i < MAX_FILES; i++) {
+ /* ALLOWED */
+ /* We open file twice to check if we can. */
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0);
+ ATF_REQUIRE(close(fd) == 0);
+
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0);
+ ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0);
+ ATF_REQUIRE(test_file_cap(fd, &rights) == true);
+ ATF_REQUIRE(test_file_read(fd) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_cap(fd, &norights) == false);
+ ATF_REQUIRE(test_file_write(fd) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE);
+
+ /* CLOSE */
+ ATF_REQUIRE(close(fd) == 0);
+ }
+}
+ATF_TC_CLEANUP(fileargs__open_read, tc)
+{
+ clear_files();
+}
+
+ATF_TC_WITH_CLEANUP(fileargs__open_write);
+ATF_TC_HEAD(fileargs__open_write, tc) {}
+ATF_TC_BODY(fileargs__open_write, tc)
+{
+ cap_rights_t rights, norights;
+ fileargs_t *fa;
+ size_t i;
+ int fd;
+
+ check_capsicum();
+
+ prepare_files(MAX_FILES, true);
+
+ cap_rights_init(&rights, CAP_WRITE, CAP_FCNTL);
+ cap_rights_init(&norights, CAP_READ);
+ fa = fileargs_init(MAX_FILES, files, O_WRONLY, 0, &rights,
+ FA_OPEN);
+ ATF_REQUIRE(fa != NULL);
+
+ for (i = 0; i < MAX_FILES; i++) {
+ /* ALLOWED */
+ /* We open file twice to check if we can. */
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0);
+ ATF_REQUIRE(close(fd) == 0);
+
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0);
+ ATF_REQUIRE(test_file_mode(fd, O_WRONLY) == 0);
+ ATF_REQUIRE(test_file_cap(fd, &rights) == true);
+ ATF_REQUIRE(test_file_write(fd) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_cap(fd, &norights) == false);
+ ATF_REQUIRE(test_file_read(fd) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE);
+
+ /* CLOSE */
+ ATF_REQUIRE(close(fd) == 0);
+ }
+}
+ATF_TC_CLEANUP(fileargs__open_write, tc)
+{
+ clear_files();
+}
+
+ATF_TC_WITH_CLEANUP(fileargs__open_create);
+ATF_TC_HEAD(fileargs__open_create, tc) {}
+ATF_TC_BODY(fileargs__open_create, tc)
+{
+ cap_rights_t rights, norights;
+ fileargs_t *fa;
+ size_t i;
+ int fd;
+
+ check_capsicum();
+
+ prepare_files(MAX_FILES, false);
+
+ cap_rights_init(&rights, CAP_WRITE, CAP_FCNTL, CAP_READ);
+ cap_rights_init(&norights, CAP_FCHMOD);
+ fa = fileargs_init(MAX_FILES, files, O_RDWR | O_CREAT, 666,
+ &rights, FA_OPEN);
+ ATF_REQUIRE(fa != NULL);
+
+ for (i = 0; i < MAX_FILES; i++) {
+ /* ALLOWED */
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0);
+
+ ATF_REQUIRE(test_file_mode(fd, O_RDWR) == 0);
+ ATF_REQUIRE(test_file_cap(fd, &rights) == true);
+ ATF_REQUIRE(test_file_write(fd) == 0);
+ ATF_REQUIRE(test_file_read(fd) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_cap(fd, &norights) == false);
+ ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE);
+
+ /* CLOSE */
+ ATF_REQUIRE(close(fd) == 0);
+ }
+}
+ATF_TC_CLEANUP(fileargs__open_create, tc)
+{
+ clear_files();
+}
+
+ATF_TC_WITH_CLEANUP(fileargs__open_with_casper);
+ATF_TC_HEAD(fileargs__open_with_casper, tc) {}
+ATF_TC_BODY(fileargs__open_with_casper, tc)
+{
+ cap_channel_t *capcas;
+ cap_rights_t rights;
+ fileargs_t *fa;
+ size_t i;
+ int fd;
+
+ check_capsicum();
+
+ prepare_files(MAX_FILES, true);
+
+ capcas = cap_init();
+ ATF_REQUIRE(capcas != NULL);
+
+ cap_rights_init(&rights, CAP_READ);
+ fa = fileargs_cinit(capcas, MAX_FILES, files, O_RDONLY, 0, &rights,
+ FA_OPEN);
+ ATF_REQUIRE(fa != NULL);
+
+ for (i = 0; i < MAX_FILES; i++) {
+ /* ALLOWED */
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0);
+ ATF_REQUIRE(test_file_read(fd) == 0);
+
+ /* CLOSE */
+ ATF_REQUIRE(close(fd) == 0);
+ }
+}
+ATF_TC_CLEANUP(fileargs__open_with_casper, tc)
+{
+ clear_files();
+}
+
+ATF_TC_WITH_CLEANUP(fileargs__fopen_read);
+ATF_TC_HEAD(fileargs__fopen_read, tc) {}
+ATF_TC_BODY(fileargs__fopen_read, tc)
+{
+ cap_rights_t rights, norights;
+ fileargs_t *fa;
+ size_t i;
+ FILE *pfile;
+ int fd;
+
+ check_capsicum();
+
+ prepare_files(MAX_FILES, true);
+
+ cap_rights_init(&rights, CAP_READ, CAP_FCNTL);
+ cap_rights_init(&norights, CAP_WRITE);
+ fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights,
+ FA_OPEN);
+ ATF_REQUIRE(fa != NULL);
+
+ for (i = 0; i < MAX_FILES; i++) {
+ /* ALLOWED */
+ /* We fopen file twice to check if we can. */
+ ATF_REQUIRE(test_file_fopen(fa, files[i], "r", &pfile) == 0);
+ ATF_REQUIRE(fclose(pfile) == 0);
+
+ ATF_REQUIRE(test_file_fopen(fa, files[i], "r", &pfile) == 0);
+ fd = fileno(pfile);
+ ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0);
+ ATF_REQUIRE(test_file_cap(fd, &rights) == true);
+ ATF_REQUIRE(test_file_fread(pfile) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_fopen(fa, TEST_FILE, "r", NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_file_cap(fd, &norights) == false);
+ ATF_REQUIRE(test_file_fwrite(pfile) == EBADF);
+ ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE);
+
+ /* CLOSE */
+ ATF_REQUIRE(fclose(pfile) == 0);
+ }
+}
+ATF_TC_CLEANUP(fileargs__fopen_read, tc)
+{
+ clear_files();
+}
+
+ATF_TC_WITH_CLEANUP(fileargs__fopen_write);
+ATF_TC_HEAD(fileargs__fopen_write, tc) {}
+ATF_TC_BODY(fileargs__fopen_write, tc)
+{
+ cap_rights_t rights, norights;
+ fileargs_t *fa;
+ size_t i;
+ FILE *pfile;
+ int fd;
+
+ check_capsicum();
+
+ prepare_files(MAX_FILES, true);
+
+ cap_rights_init(&rights, CAP_WRITE, CAP_FCNTL);
+ cap_rights_init(&norights, CAP_READ);
+ fa = fileargs_init(MAX_FILES, files, O_WRONLY, 0, &rights,
+ FA_OPEN);
+ ATF_REQUIRE(fa != NULL);
+
+ for (i = 0; i < MAX_FILES; i++) {
+ /* ALLOWED */
+ /* We fopen file twice to check if we can. */
+ ATF_REQUIRE(test_file_fopen(fa, files[i], "w", &pfile) == 0);
+ ATF_REQUIRE(fclose(pfile) == 0);
+
+ ATF_REQUIRE(test_file_fopen(fa, files[i], "w", &pfile) == 0);
+ fd = fileno(pfile);
+ ATF_REQUIRE(test_file_mode(fd, O_WRONLY) == 0);
+ ATF_REQUIRE(test_file_cap(fd, &rights) == true);
+ ATF_REQUIRE(test_file_fwrite(pfile) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_fopen(fa, TEST_FILE, "w", NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_file_cap(fd, &norights) == false);
+ ATF_REQUIRE(test_file_fread(pfile) == EBADF);
+ ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE);
+
+ /* CLOSE */
+ ATF_REQUIRE(fclose(pfile) == 0);
+ }
+}
+ATF_TC_CLEANUP(fileargs__fopen_write, tc)
+{
+ clear_files();
+}
+
+ATF_TC_WITH_CLEANUP(fileargs__fopen_create);
+ATF_TC_HEAD(fileargs__fopen_create, tc) {}
+ATF_TC_BODY(fileargs__fopen_create, tc)
+{
+ cap_rights_t rights;
+ fileargs_t *fa;
+ size_t i;
+ FILE *pfile;
+ int fd;
+
+ check_capsicum();
+
+ prepare_files(MAX_FILES, false);
+
+ cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_FCNTL);
+ fa = fileargs_init(MAX_FILES, files, O_RDWR | O_CREAT, 0, &rights,
+ FA_OPEN);
+ ATF_REQUIRE(fa != NULL);
+
+ for (i = 0; i < MAX_FILES; i++) {
+ /* ALLOWED */
+ /* We fopen file twice to check if we can. */
+ ATF_REQUIRE(test_file_fopen(fa, files[i], "w+", &pfile) == 0);
+ fd = fileno(pfile);
+ ATF_REQUIRE(test_file_mode(fd, O_RDWR) == 0);
+ ATF_REQUIRE(test_file_cap(fd, &rights) == true);
+ ATF_REQUIRE(test_file_fwrite(pfile) == 0);
+ ATF_REQUIRE(test_file_fread(pfile) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_fopen(fa, TEST_FILE, "w+", NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE);
+
+ /* CLOSE */
+ ATF_REQUIRE(fclose(pfile) == 0);
+ }
+}
+ATF_TC_CLEANUP(fileargs__fopen_create, tc)
+{
+ clear_files();
+}
+
+ATF_TC_WITH_CLEANUP(fileargs__lstat);
+ATF_TC_HEAD(fileargs__lstat, tc) {}
+ATF_TC_BODY(fileargs__lstat, tc)
+{
+ fileargs_t *fa;
+ size_t i;
+ int fd;
+
+ check_capsicum();
+
+ prepare_files(MAX_FILES, true);
+
+ fa = fileargs_init(MAX_FILES, files, 0, 0, NULL, FA_LSTAT);
+ ATF_REQUIRE(fa != NULL);
+
+ for (i = 0; i < MAX_FILES; i++) {
+ /* ALLOWED */
+ ATF_REQUIRE(test_file_lstat(fa, files[i]) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_lstat(fa, TEST_FILE) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_open(fa, TEST_FILE, &fd) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE);
+ }
+}
+ATF_TC_CLEANUP(fileargs__lstat, tc)
+{
+ clear_files();
+}
+
+ATF_TC_WITH_CLEANUP(fileargs__realpath);
+ATF_TC_HEAD(fileargs__realpath, tc) {}
+ATF_TC_BODY(fileargs__realpath, tc)
+{
+ fileargs_t *fa;
+ size_t i;
+ int fd;
+
+ prepare_files(MAX_FILES, true);
+
+ fa = fileargs_init(MAX_FILES, files, 0, 0, NULL, FA_REALPATH);
+ ATF_REQUIRE(fa != NULL);
+
+ for (i = 0; i < MAX_FILES; i++) {
+ /* ALLOWED */
+ ATF_REQUIRE(test_file_realpath(fa, files[i]) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_lstat(fa, TEST_FILE) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_open(fa, TEST_FILE, &fd) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE);
+ }
+}
+ATF_TC_CLEANUP(fileargs__realpath, tc)
+{
+ clear_files();
+}
+
+ATF_TC_WITH_CLEANUP(fileargs__open_lstat);
+ATF_TC_HEAD(fileargs__open_lstat, tc) {}
+ATF_TC_BODY(fileargs__open_lstat, tc)
+{
+ cap_rights_t rights, norights;
+ fileargs_t *fa;
+ size_t i;
+ int fd;
+
+ check_capsicum();
+
+ prepare_files(MAX_FILES, true);
+
+ cap_rights_init(&rights, CAP_READ, CAP_FCNTL);
+ cap_rights_init(&norights, CAP_WRITE);
+ fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights,
+ FA_OPEN | FA_LSTAT);
+ ATF_REQUIRE(fa != NULL);
+
+ for (i = 0; i < MAX_FILES; i++) {
+ /* ALLOWED */
+ /* We open file twice to check if we can. */
+ ATF_REQUIRE(test_file_lstat(fa, files[i]) == 0);
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0);
+ ATF_REQUIRE(close(fd) == 0);
+
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0);
+ ATF_REQUIRE(test_file_lstat(fa, files[i]) == 0);
+ ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0);
+ ATF_REQUIRE(test_file_cap(fd, &rights) == true);
+ ATF_REQUIRE(test_file_read(fd) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_cap(fd, &norights) == false);
+ ATF_REQUIRE(test_file_write(fd) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE);
+
+ /* CLOSE */
+ ATF_REQUIRE(close(fd) == 0);
+ }
+}
+ATF_TC_CLEANUP(fileargs__open_lstat, tc)
+{
+ clear_files();
+}
+
+ATF_TC_WITH_CLEANUP(fileargs__open_realpath);
+ATF_TC_HEAD(fileargs__open_realpath, tc) {}
+ATF_TC_BODY(fileargs__open_realpath, tc)
+{
+ cap_rights_t rights, norights;
+ fileargs_t *fa;
+ size_t i;
+ int fd;
+
+ check_capsicum();
+
+ prepare_files(MAX_FILES, true);
+
+ cap_rights_init(&rights, CAP_READ, CAP_FCNTL);
+ cap_rights_init(&norights, CAP_WRITE);
+ fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights,
+ FA_OPEN | FA_REALPATH);
+ ATF_REQUIRE(fa != NULL);
+
+ for (i = 0; i < MAX_FILES; i++) {
+ /* ALLOWED */
+ /* We open file twice to check if we can. */
+ ATF_REQUIRE(test_file_realpath(fa, files[i]) == 0);
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0);
+ ATF_REQUIRE(close(fd) == 0);
+
+ ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0);
+ ATF_REQUIRE(test_file_realpath(fa, files[i]) == 0);
+ ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0);
+ ATF_REQUIRE(test_file_cap(fd, &rights) == true);
+ ATF_REQUIRE(test_file_read(fd) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_cap(fd, &norights) == false);
+ ATF_REQUIRE(test_file_write(fd) == ENOTCAPABLE);
+ ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE);
+
+ /* CLOSE */
+ ATF_REQUIRE(close(fd) == 0);
+ }
+}
+ATF_TC_CLEANUP(fileargs__open_realpath, tc)
+{
+ clear_files();
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, fileargs__open_create);
+ ATF_TP_ADD_TC(tp, fileargs__open_read);
+ ATF_TP_ADD_TC(tp, fileargs__open_write);
+ ATF_TP_ADD_TC(tp, fileargs__open_with_casper);
+
+ ATF_TP_ADD_TC(tp, fileargs__fopen_create);
+ ATF_TP_ADD_TC(tp, fileargs__fopen_read);
+ ATF_TP_ADD_TC(tp, fileargs__fopen_write);
+
+ ATF_TP_ADD_TC(tp, fileargs__lstat);
+
+ ATF_TP_ADD_TC(tp, fileargs__realpath);
+
+ ATF_TP_ADD_TC(tp, fileargs__open_lstat);
+ ATF_TP_ADD_TC(tp, fileargs__open_realpath);
+
+ return (atf_no_error());
+}
diff --git a/lib/libcasper/services/cap_grp/Makefile b/lib/libcasper/services/cap_grp/Makefile
new file mode 100644
index 000000000000..a921dfa87e7c
--- /dev/null
+++ b/lib/libcasper/services/cap_grp/Makefile
@@ -0,0 +1,44 @@
+SHLIBDIR?= /lib
+
+.include <src.opts.mk>
+
+PACKAGE= runtime
+
+SHLIB_MAJOR= 1
+INCSDIR?= ${INCLUDEDIR}/casper
+
+.if ${MK_CASPER} != "no"
+SHLIB= cap_grp
+
+SRCS= cap_grp.c
+.endif
+
+INCS= cap_grp.h
+
+LIBADD= nv
+
+CFLAGS+=-I${.CURDIR}
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+MAN+= cap_grp.3
+
+MLINKS+=cap_grp.3 libcap_grp.3
+MLINKS+=cap_grp.3 cap_getgrent.3
+MLINKS+=cap_grp.3 cap_getgrnam.3
+MLINKS+=cap_grp.3 cap_getgrgid.3
+MLINKS+=cap_grp.3 cap_getgrent_r.3
+MLINKS+=cap_grp.3 cap_getgrnam_r.3
+MLINKS+=cap_grp.3 cap_getgrgid_r.3
+MLINKS+=cap_grp.3 cap_setgroupent.3
+MLINKS+=cap_grp.3 cap_setgrent.3
+MLINKS+=cap_grp.3 cap_endgrent.3
+MLINKS+=cap_grp.3 cap_grp_limit_cmds.3
+MLINKS+=cap_grp.3 cap_grp_limit_fields.3
+MLINKS+=cap_grp.3 cap_grp_limit_groups.3
+
+.include <bsd.lib.mk>
+
+# GCC 13 complains incorrectly about free after failed realloc: GCC bug #110501
+CFLAGS.cap_grp.c+= ${NO_WUSE_AFTER_FREE}
diff --git a/lib/libcasper/services/cap_grp/Makefile.depend b/lib/libcasper/services/cap_grp/Makefile.depend
new file mode 100644
index 000000000000..02bae00eb04d
--- /dev/null
+++ b/lib/libcasper/services/cap_grp/Makefile.depend
@@ -0,0 +1,17 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_grp/cap_grp.3 b/lib/libcasper/services/cap_grp/cap_grp.3
new file mode 100644
index 000000000000..578d8edffbfa
--- /dev/null
+++ b/lib/libcasper/services/cap_grp/cap_grp.3
@@ -0,0 +1,236 @@
+.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd December 6, 2023
+.Dt CAP_GRP 3
+.Os
+.Sh NAME
+.Nm cap_getgrent ,
+.Nm cap_getgrnam ,
+.Nm cap_getgrgid ,
+.Nm cap_getgrent_r ,
+.Nm cap_getgrnam_r ,
+.Nm cap_getgrgid_r ,
+.Nm cap_setgroupent ,
+.Nm cap_setgrent ,
+.Nm cap_endgrent ,
+.Nm cap_grp_limit_cmds ,
+.Nm cap_grp_limit_fields ,
+.Nm cap_grp_limit_groups
+.Nd "library for group database operations in capability mode"
+.Sh LIBRARY
+.Lb libcap_grp
+.Sh SYNOPSIS
+.In sys/nv.h
+.In libcasper.h
+.In casper/cap_grp.h
+.Ft "struct group *"
+.Fn cap_getgrent "cap_channel_t *chan"
+.Ft "struct group *"
+.Fn cap_getgrnam "cap_channel_t *chan" "const char *name"
+.Ft "struct group *"
+.Fn cap_getgrgid "cap_channel_t *chan" "gid_t gid"
+.Ft "int"
+.Fn cap_getgrent_r "cap_channel_t *chan" "struct group *grp" "char *buffer" "size_t bufsize" "struct group **result"
+.Ft "int"
+.Fn cap_getgrnam_r "cap_channel_t *chan" "const char *name" "struct group *grp" "char *buffer" "size_t bufsize" "struct group **result"
+.Ft int
+.Fn cap_getgrgid_r "cap_channel_t *chan" "gid_t gid" "struct group *grp" "char *buffer" "size_t bufsize" "struct group **result"
+.Ft int
+.Fn cap_setgroupent "cap_channel_t *chan" "int stayopen"
+.Ft int
+.Fn cap_setgrent "cap_channel_t *chan"
+.Ft void
+.Fn cap_endgrent "cap_channel_t *chan"
+.Ft int
+.Fn cap_grp_limit_cmds "cap_channel_t *chan" "const char * const *cmds" "size_t ncmds"
+.Ft int
+.Fn cap_grp_limit_fields "cap_channel_t *chan" "const char * const *fields" "size_t nfields"
+.Ft int
+.Fn cap_grp_limit_groups "cap_channel_t *chan" "const char * const *names" "size_t nnames" "const gid_t *gids" "size_t ngids"
+.Sh DESCRIPTION
+The functions
+.Fn cap_getgrent ,
+.Fn cap_getgrnam ,
+.Fn cap_getgrgid ,
+.Fn cap_getgrent_r ,
+.Fn cap_getgrnam_r ,
+.Fn cap_getgrgid_r ,
+.Fn cap_setgroupent ,
+.Fn cap_setgrent ,
+and
+.Fn cap_endgrent
+are respectively equivalent to
+.Xr getgrent 3 ,
+.Xr getgrnam 3 ,
+.Xr getgrgid 3 ,
+.Xr getgrent_r 3 ,
+.Xr getgrnam_r 3 ,
+.Xr getgrgid_r 3 ,
+.Xr setgroupent 3 ,
+.Xr setgrent 3 ,
+and
+.Xr endgrent 3
+except that the connection to the
+.Nm system.grp
+service needs to be provided.
+.Pp
+The
+.Fn cap_grp_limit_cmds
+function limits the functions allowed in the service.
+The
+.Fa cmds
+variable can be set to
+.Dv getgrent ,
+.Dv getgrnam ,
+.Dv getgrgid ,
+.Dv getgrent_r ,
+.Dv getgrnam_r ,
+.Dv getgrgid_r ,
+.Dv setgroupent ,
+.Dv setgrent ,
+or
+.Dv endgrent
+which will allow to use the function associated with the name.
+The
+.Fa ncmds
+variable contains the number of
+.Fa cmds
+provided.
+.Pp
+The
+.Fn cap_grp_limit_fields
+function allows limit fields returned in the structure
+.Vt group .
+The
+.Fa fields
+variable can be set to
+.Dv gr_name
+.Dv gr_passwd
+.Dv gr_gid
+or
+.Dv gr_mem .
+The field which was set as the limit will be returned, while the rest of the
+values not set this way will have default values.
+The
+.Fa nfields
+variable contains the number of
+.Fa fields
+provided.
+.Pp
+The
+.Fn cap_grp_limit_groups
+function allows to limit access to groups.
+The
+.Fa names
+variable allows to limit groups by name and the
+.Fa gids
+variable by the group number.
+The
+.Fa nnames
+and
+.Fa ngids
+variables provide numbers of limited names and gids.
+.Pp
+All of these functions are reentrant but not thread-safe.
+That is, they may be called from separate threads only with different
+.Vt cap_channel_t
+arguments or with synchronization.
+.Sh EXAMPLES
+The following example first opens a capability to casper and then uses this
+capability to create the
+.Nm system.grp
+casper service and uses it to get a group name.
+.Bd -literal
+cap_channel_t *capcas, *capgrp;
+const char *cmds[] = { "getgrgid" };
+const char *fields[] = { "gr_name" };
+const gid_t gid[] = { 1 };
+struct group *group;
+
+/* Open capability to Casper. */
+capcas = cap_init();
+if (capcas == NULL)
+ err(1, "Unable to contact Casper");
+
+/* Enter capability mode sandbox. */
+if (cap_enter() < 0 && errno != ENOSYS)
+ err(1, "Unable to enter capability mode");
+
+/* Use Casper capability to create capability to the system.grp service. */
+capgrp = cap_service_open(capcas, "system.grp");
+if (capgrp == NULL)
+ err(1, "Unable to open system.grp service");
+
+/* Close Casper capability, we don't need it anymore. */
+cap_close(capcas);
+
+/* Limit service to one single function. */
+if (cap_grp_limit_cmds(capgrp, cmds, nitems(cmds)))
+ err(1, "Unable to limit access to system.grp service");
+
+/* Limit service to one field as we only need name of the group. */
+if (cap_grp_limit_fields(capgrp, fields, nitems(fields)))
+ err(1, "Unable to limit access to system.grp service");
+
+/* Limit service to one gid. */
+if (cap_grp_limit_groups(capgrp, NULL, 0, gid, nitems(gid)))
+ err(1, "Unable to limit access to system.grp service");
+
+group = cap_getgrgid(capgrp, gid[0]);
+if (group == NULL)
+ err(1, "Unable to get name of group");
+
+printf("GID %d is associated with name %s.\\n", gid[0], group->gr_name);
+
+cap_close(capgrp);
+.Ed
+.Sh SEE ALSO
+.Xr cap_enter 2 ,
+.Xr endgrent 3 ,
+.Xr err 3 ,
+.Xr getgrent 3 ,
+.Xr getgrent_r 3 ,
+.Xr getgrgid 3 ,
+.Xr getgrgid_r 3 ,
+.Xr getgrnam 3 ,
+.Xr getgrnam_r 3 ,
+.Xr setgrent 3 ,
+.Xr setgroupent 3 ,
+.Xr capsicum 4 ,
+.Xr nv 9
+.Sh HISTORY
+The
+.Nm cap_grp
+service first appeared in
+.Fx 10.3 .
+.Sh AUTHORS
+The
+.Nm cap_grp
+service was implemented by
+.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net
+under sponsorship from the FreeBSD Foundation.
+.Pp
+This manual page was written by
+.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org .
diff --git a/lib/libcasper/services/cap_grp/cap_grp.c b/lib/libcasper/services/cap_grp/cap_grp.c
new file mode 100644
index 000000000000..025ce00adf56
--- /dev/null
+++ b/lib/libcasper/services/cap_grp/cap_grp.c
@@ -0,0 +1,788 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+#include <sys/param.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <grp.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libcasper.h>
+#include <libcasper_service.h>
+
+#include "cap_grp.h"
+
+static struct group ggrp;
+static char *gbuffer;
+static size_t gbufsize;
+
+static int
+group_resize(void)
+{
+ char *buf;
+
+ if (gbufsize == 0)
+ gbufsize = 1024;
+ else
+ gbufsize *= 2;
+
+ buf = gbuffer;
+ gbuffer = realloc(buf, gbufsize);
+ if (gbuffer == NULL) {
+ free(buf);
+ gbufsize = 0;
+ return (ENOMEM);
+ }
+ memset(gbuffer, 0, gbufsize);
+
+ return (0);
+}
+
+static int
+group_unpack_string(const nvlist_t *nvl, const char *fieldname, char **fieldp,
+ char **bufferp, size_t *bufsizep)
+{
+ const char *str;
+ size_t len;
+
+ str = nvlist_get_string(nvl, fieldname);
+ len = strlcpy(*bufferp, str, *bufsizep);
+ if (len >= *bufsizep)
+ return (ERANGE);
+ *fieldp = *bufferp;
+ *bufferp += len + 1;
+ *bufsizep -= len + 1;
+
+ return (0);
+}
+
+static int
+group_unpack_members(const nvlist_t *nvl, char ***fieldp, char **bufferp,
+ size_t *bufsizep)
+{
+ const char *mem;
+ char **outstrs, *str, nvlname[64];
+ size_t nmem, datasize, strsize;
+ unsigned int ii;
+ int n;
+
+ if (!nvlist_exists_number(nvl, "gr_nmem")) {
+ datasize = _ALIGNBYTES + sizeof(char *);
+ if (datasize >= *bufsizep)
+ return (ERANGE);
+ outstrs = (char **)_ALIGN(*bufferp);
+ outstrs[0] = NULL;
+ *fieldp = outstrs;
+ *bufferp += datasize;
+ *bufsizep -= datasize;
+ return (0);
+ }
+
+ nmem = (size_t)nvlist_get_number(nvl, "gr_nmem");
+ datasize = _ALIGNBYTES + sizeof(char *) * (nmem + 1);
+ for (ii = 0; ii < nmem; ii++) {
+ n = snprintf(nvlname, sizeof(nvlname), "gr_mem[%u]", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ mem = dnvlist_get_string(nvl, nvlname, NULL);
+ if (mem == NULL)
+ return (EINVAL);
+ datasize += strlen(mem) + 1;
+ }
+
+ if (datasize >= *bufsizep)
+ return (ERANGE);
+
+ outstrs = (char **)_ALIGN(*bufferp);
+ str = (char *)outstrs + sizeof(char *) * (nmem + 1);
+ for (ii = 0; ii < nmem; ii++) {
+ n = snprintf(nvlname, sizeof(nvlname), "gr_mem[%u]", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ mem = nvlist_get_string(nvl, nvlname);
+ strsize = strlen(mem) + 1;
+ memcpy(str, mem, strsize);
+ outstrs[ii] = str;
+ str += strsize;
+ }
+ assert(ii == nmem);
+ outstrs[ii] = NULL;
+
+ *fieldp = outstrs;
+ *bufferp += datasize;
+ *bufsizep -= datasize;
+
+ return (0);
+}
+
+static int
+group_unpack(const nvlist_t *nvl, struct group *grp, char *buffer,
+ size_t bufsize)
+{
+ int error;
+
+ if (!nvlist_exists_string(nvl, "gr_name"))
+ return (EINVAL);
+
+ explicit_bzero(grp, sizeof(*grp));
+
+ error = group_unpack_string(nvl, "gr_name", &grp->gr_name, &buffer,
+ &bufsize);
+ if (error != 0)
+ return (error);
+ error = group_unpack_string(nvl, "gr_passwd", &grp->gr_passwd, &buffer,
+ &bufsize);
+ if (error != 0)
+ return (error);
+ grp->gr_gid = (gid_t)nvlist_get_number(nvl, "gr_gid");
+ error = group_unpack_members(nvl, &grp->gr_mem, &buffer, &bufsize);
+ if (error != 0)
+ return (error);
+
+ return (0);
+}
+
+static int
+cap_getgrcommon_r(cap_channel_t *chan, const char *cmd, const char *name,
+ gid_t gid, struct group *grp, char *buffer, size_t bufsize,
+ struct group **result)
+{
+ nvlist_t *nvl;
+ bool getgr_r;
+ int error;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", cmd);
+ if (strcmp(cmd, "getgrent") == 0 || strcmp(cmd, "getgrent_r") == 0) {
+ /* Add nothing. */
+ } else if (strcmp(cmd, "getgrnam") == 0 ||
+ strcmp(cmd, "getgrnam_r") == 0) {
+ nvlist_add_string(nvl, "name", name);
+ } else if (strcmp(cmd, "getgrgid") == 0 ||
+ strcmp(cmd, "getgrgid_r") == 0) {
+ nvlist_add_number(nvl, "gid", (uint64_t)gid);
+ } else {
+ abort();
+ }
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL) {
+ assert(errno != 0);
+ *result = NULL;
+ return (errno);
+ }
+ error = (int)nvlist_get_number(nvl, "error");
+ if (error != 0) {
+ nvlist_destroy(nvl);
+ *result = NULL;
+ return (error);
+ }
+
+ if (!nvlist_exists_string(nvl, "gr_name")) {
+ /* Not found. */
+ nvlist_destroy(nvl);
+ *result = NULL;
+ return (0);
+ }
+
+ getgr_r = (strcmp(cmd, "getgrent_r") == 0 ||
+ strcmp(cmd, "getgrnam_r") == 0 || strcmp(cmd, "getgrgid_r") == 0);
+
+ for (;;) {
+ error = group_unpack(nvl, grp, buffer, bufsize);
+ if (getgr_r || error != ERANGE)
+ break;
+ assert(buffer == gbuffer);
+ assert(bufsize == gbufsize);
+ error = group_resize();
+ if (error != 0)
+ break;
+ /* Update pointers after resize. */
+ buffer = gbuffer;
+ bufsize = gbufsize;
+ }
+
+ nvlist_destroy(nvl);
+
+ if (error == 0)
+ *result = grp;
+ else
+ *result = NULL;
+
+ return (error);
+}
+
+static struct group *
+cap_getgrcommon(cap_channel_t *chan, const char *cmd, const char *name,
+ gid_t gid)
+{
+ struct group *result;
+ int error, serrno;
+
+ serrno = errno;
+
+ error = cap_getgrcommon_r(chan, cmd, name, gid, &ggrp, gbuffer,
+ gbufsize, &result);
+ if (error != 0) {
+ errno = error;
+ return (NULL);
+ }
+
+ errno = serrno;
+
+ return (result);
+}
+
+struct group *
+cap_getgrent(cap_channel_t *chan)
+{
+
+ return (cap_getgrcommon(chan, "getgrent", NULL, 0));
+}
+
+struct group *
+cap_getgrnam(cap_channel_t *chan, const char *name)
+{
+
+ return (cap_getgrcommon(chan, "getgrnam", name, 0));
+}
+
+struct group *
+cap_getgrgid(cap_channel_t *chan, gid_t gid)
+{
+
+ return (cap_getgrcommon(chan, "getgrgid", NULL, gid));
+}
+
+int
+cap_getgrent_r(cap_channel_t *chan, struct group *grp, char *buffer,
+ size_t bufsize, struct group **result)
+{
+
+ return (cap_getgrcommon_r(chan, "getgrent_r", NULL, 0, grp, buffer,
+ bufsize, result));
+}
+
+int
+cap_getgrnam_r(cap_channel_t *chan, const char *name, struct group *grp,
+ char *buffer, size_t bufsize, struct group **result)
+{
+
+ return (cap_getgrcommon_r(chan, "getgrnam_r", name, 0, grp, buffer,
+ bufsize, result));
+}
+
+int
+cap_getgrgid_r(cap_channel_t *chan, gid_t gid, struct group *grp, char *buffer,
+ size_t bufsize, struct group **result)
+{
+
+ return (cap_getgrcommon_r(chan, "getgrgid_r", NULL, gid, grp, buffer,
+ bufsize, result));
+}
+
+int
+cap_setgroupent(cap_channel_t *chan, int stayopen)
+{
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "setgroupent");
+ nvlist_add_bool(nvl, "stayopen", stayopen != 0);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (0);
+ if (nvlist_get_number(nvl, "error") != 0) {
+ errno = nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ return (0);
+ }
+ nvlist_destroy(nvl);
+
+ return (1);
+}
+
+int
+cap_setgrent(cap_channel_t *chan)
+{
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "setgrent");
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (0);
+ if (nvlist_get_number(nvl, "error") != 0) {
+ errno = nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ return (0);
+ }
+ nvlist_destroy(nvl);
+
+ return (1);
+}
+
+void
+cap_endgrent(cap_channel_t *chan)
+{
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "endgrent");
+ /* Ignore any errors, we have no way to report them. */
+ nvlist_destroy(cap_xfer_nvlist(chan, nvl));
+}
+
+int
+cap_grp_limit_cmds(cap_channel_t *chan, const char * const *cmds, size_t ncmds)
+{
+ nvlist_t *limits, *nvl;
+ unsigned int i;
+
+ if (cap_limit_get(chan, &limits) < 0)
+ return (-1);
+ if (limits == NULL) {
+ limits = nvlist_create(0);
+ } else {
+ if (nvlist_exists_nvlist(limits, "cmds"))
+ nvlist_free_nvlist(limits, "cmds");
+ }
+ nvl = nvlist_create(0);
+ for (i = 0; i < ncmds; i++)
+ nvlist_add_null(nvl, cmds[i]);
+ nvlist_move_nvlist(limits, "cmds", nvl);
+ return (cap_limit_set(chan, limits));
+}
+
+int
+cap_grp_limit_fields(cap_channel_t *chan, const char * const *fields,
+ size_t nfields)
+{
+ nvlist_t *limits, *nvl;
+ unsigned int i;
+
+ if (cap_limit_get(chan, &limits) < 0)
+ return (-1);
+ if (limits == NULL) {
+ limits = nvlist_create(0);
+ } else {
+ if (nvlist_exists_nvlist(limits, "fields"))
+ nvlist_free_nvlist(limits, "fields");
+ }
+ nvl = nvlist_create(0);
+ for (i = 0; i < nfields; i++)
+ nvlist_add_null(nvl, fields[i]);
+ nvlist_move_nvlist(limits, "fields", nvl);
+ return (cap_limit_set(chan, limits));
+}
+
+int
+cap_grp_limit_groups(cap_channel_t *chan, const char * const *names,
+ size_t nnames, const gid_t *gids, size_t ngids)
+{
+ nvlist_t *limits, *groups;
+ unsigned int i;
+ char nvlname[64];
+ int n;
+
+ if (cap_limit_get(chan, &limits) < 0)
+ return (-1);
+ if (limits == NULL) {
+ limits = nvlist_create(0);
+ } else {
+ if (nvlist_exists_nvlist(limits, "groups"))
+ nvlist_free_nvlist(limits, "groups");
+ }
+ groups = nvlist_create(0);
+ for (i = 0; i < ngids; i++) {
+ n = snprintf(nvlname, sizeof(nvlname), "gid%u", i);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_add_number(groups, nvlname, (uint64_t)gids[i]);
+ }
+ for (i = 0; i < nnames; i++) {
+ n = snprintf(nvlname, sizeof(nvlname), "gid%u", i);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_add_string(groups, nvlname, names[i]);
+ }
+ nvlist_move_nvlist(limits, "groups", groups);
+ return (cap_limit_set(chan, limits));
+}
+
+/*
+ * Service functions.
+ */
+static bool
+grp_allowed_cmd(const nvlist_t *limits, const char *cmd)
+{
+
+ if (limits == NULL)
+ return (true);
+
+ /*
+ * If no limit was set on allowed commands, then all commands
+ * are allowed.
+ */
+ if (!nvlist_exists_nvlist(limits, "cmds"))
+ return (true);
+
+ limits = nvlist_get_nvlist(limits, "cmds");
+ return (nvlist_exists_null(limits, cmd));
+}
+
+static int
+grp_allowed_cmds(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const char *name;
+ void *cookie;
+ int type;
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
+ if (type != NV_TYPE_NULL)
+ return (EINVAL);
+ if (!grp_allowed_cmd(oldlimits, name))
+ return (ENOTCAPABLE);
+ }
+
+ return (0);
+}
+
+static bool
+grp_allowed_group(const nvlist_t *limits, const char *gname, gid_t gid)
+{
+ const char *name;
+ void *cookie;
+ int type;
+
+ if (limits == NULL)
+ return (true);
+
+ /*
+ * If no limit was set on allowed groups, then all groups are allowed.
+ */
+ if (!nvlist_exists_nvlist(limits, "groups"))
+ return (true);
+
+ limits = nvlist_get_nvlist(limits, "groups");
+ cookie = NULL;
+ while ((name = nvlist_next(limits, &type, &cookie)) != NULL) {
+ switch (type) {
+ case NV_TYPE_NUMBER:
+ if (gid != (gid_t)-1 &&
+ nvlist_get_number(limits, name) == (uint64_t)gid) {
+ return (true);
+ }
+ break;
+ case NV_TYPE_STRING:
+ if (gname != NULL &&
+ strcmp(nvlist_get_string(limits, name),
+ gname) == 0) {
+ return (true);
+ }
+ break;
+ default:
+ abort();
+ }
+ }
+
+ return (false);
+}
+
+static int
+grp_allowed_groups(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const char *name, *gname;
+ void *cookie;
+ gid_t gid;
+ int type;
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
+ switch (type) {
+ case NV_TYPE_NUMBER:
+ gid = (gid_t)nvlist_get_number(newlimits, name);
+ gname = NULL;
+ break;
+ case NV_TYPE_STRING:
+ gid = (gid_t)-1;
+ gname = nvlist_get_string(newlimits, name);
+ break;
+ default:
+ return (EINVAL);
+ }
+ if (!grp_allowed_group(oldlimits, gname, gid))
+ return (ENOTCAPABLE);
+ }
+
+ return (0);
+}
+
+static bool
+grp_allowed_field(const nvlist_t *limits, const char *field)
+{
+
+ if (limits == NULL)
+ return (true);
+
+ /*
+ * If no limit was set on allowed fields, then all fields are allowed.
+ */
+ if (!nvlist_exists_nvlist(limits, "fields"))
+ return (true);
+
+ limits = nvlist_get_nvlist(limits, "fields");
+ return (nvlist_exists_null(limits, field));
+}
+
+static int
+grp_allowed_fields(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const char *name;
+ void *cookie;
+ int type;
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
+ if (type != NV_TYPE_NULL)
+ return (EINVAL);
+ if (!grp_allowed_field(oldlimits, name))
+ return (ENOTCAPABLE);
+ }
+
+ return (0);
+}
+
+static bool
+grp_pack(const nvlist_t *limits, const struct group *grp, nvlist_t *nvl)
+{
+ char nvlname[64];
+ int n;
+
+ if (grp == NULL)
+ return (true);
+
+ /*
+ * If either name or GID is allowed, we allow it.
+ */
+ if (!grp_allowed_group(limits, grp->gr_name, grp->gr_gid))
+ return (false);
+
+ if (grp_allowed_field(limits, "gr_name"))
+ nvlist_add_string(nvl, "gr_name", grp->gr_name);
+ else
+ nvlist_add_string(nvl, "gr_name", "");
+ if (grp_allowed_field(limits, "gr_passwd"))
+ nvlist_add_string(nvl, "gr_passwd", grp->gr_passwd);
+ else
+ nvlist_add_string(nvl, "gr_passwd", "");
+ if (grp_allowed_field(limits, "gr_gid"))
+ nvlist_add_number(nvl, "gr_gid", (uint64_t)grp->gr_gid);
+ else
+ nvlist_add_number(nvl, "gr_gid", (uint64_t)-1);
+ if (grp_allowed_field(limits, "gr_mem") && grp->gr_mem[0] != NULL) {
+ unsigned int ngroups;
+
+ for (ngroups = 0; grp->gr_mem[ngroups] != NULL; ngroups++) {
+ n = snprintf(nvlname, sizeof(nvlname), "gr_mem[%u]",
+ ngroups);
+ assert(n > 0 && n < (ssize_t)sizeof(nvlname));
+ nvlist_add_string(nvl, nvlname, grp->gr_mem[ngroups]);
+ }
+ nvlist_add_number(nvl, "gr_nmem", (uint64_t)ngroups);
+ }
+
+ return (true);
+}
+
+static int
+grp_getgrent(const nvlist_t *limits, const nvlist_t *nvlin __unused,
+ nvlist_t *nvlout)
+{
+ struct group *grp;
+
+ for (;;) {
+ errno = 0;
+ grp = getgrent();
+ if (errno != 0)
+ return (errno);
+ if (grp_pack(limits, grp, nvlout))
+ return (0);
+ }
+
+ /* NOTREACHED */
+}
+
+static int
+grp_getgrnam(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
+{
+ struct group *grp;
+ const char *name;
+
+ if (!nvlist_exists_string(nvlin, "name"))
+ return (EINVAL);
+ name = nvlist_get_string(nvlin, "name");
+ assert(name != NULL);
+
+ errno = 0;
+ grp = getgrnam(name);
+ if (errno != 0)
+ return (errno);
+
+ (void)grp_pack(limits, grp, nvlout);
+
+ return (0);
+}
+
+static int
+grp_getgrgid(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
+{
+ struct group *grp;
+ gid_t gid;
+
+ if (!nvlist_exists_number(nvlin, "gid"))
+ return (EINVAL);
+
+ gid = (gid_t)nvlist_get_number(nvlin, "gid");
+
+ errno = 0;
+ grp = getgrgid(gid);
+ if (errno != 0)
+ return (errno);
+
+ (void)grp_pack(limits, grp, nvlout);
+
+ return (0);
+}
+
+static int
+grp_setgroupent(const nvlist_t *limits __unused, const nvlist_t *nvlin,
+ nvlist_t *nvlout __unused)
+{
+ int stayopen;
+
+ if (!nvlist_exists_bool(nvlin, "stayopen"))
+ return (EINVAL);
+
+ stayopen = nvlist_get_bool(nvlin, "stayopen") ? 1 : 0;
+
+ return (setgroupent(stayopen) == 0 ? EFAULT : 0);
+}
+
+static int
+grp_setgrent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused,
+ nvlist_t *nvlout __unused)
+{
+
+ setgrent();
+
+ return (0);
+}
+
+static int
+grp_endgrent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused,
+ nvlist_t *nvlout __unused)
+{
+
+ endgrent();
+
+ return (0);
+}
+
+static int
+grp_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const nvlist_t *limits;
+ const char *name;
+ void *cookie;
+ int error, type;
+
+ if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "cmds") &&
+ !nvlist_exists_nvlist(newlimits, "cmds")) {
+ return (ENOTCAPABLE);
+ }
+ if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "fields") &&
+ !nvlist_exists_nvlist(newlimits, "fields")) {
+ return (ENOTCAPABLE);
+ }
+ if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "groups") &&
+ !nvlist_exists_nvlist(newlimits, "groups")) {
+ return (ENOTCAPABLE);
+ }
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
+ if (type != NV_TYPE_NVLIST)
+ return (EINVAL);
+ limits = nvlist_get_nvlist(newlimits, name);
+ if (strcmp(name, "cmds") == 0)
+ error = grp_allowed_cmds(oldlimits, limits);
+ else if (strcmp(name, "fields") == 0)
+ error = grp_allowed_fields(oldlimits, limits);
+ else if (strcmp(name, "groups") == 0)
+ error = grp_allowed_groups(oldlimits, limits);
+ else
+ error = EINVAL;
+ if (error != 0)
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+grp_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ int error;
+
+ if (!grp_allowed_cmd(limits, cmd))
+ return (ENOTCAPABLE);
+
+ if (strcmp(cmd, "getgrent") == 0 || strcmp(cmd, "getgrent_r") == 0)
+ error = grp_getgrent(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "getgrnam") == 0 || strcmp(cmd, "getgrnam_r") == 0)
+ error = grp_getgrnam(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "getgrgid") == 0 || strcmp(cmd, "getgrgid_r") == 0)
+ error = grp_getgrgid(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "setgroupent") == 0)
+ error = grp_setgroupent(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "setgrent") == 0)
+ error = grp_setgrent(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "endgrent") == 0)
+ error = grp_endgrent(limits, nvlin, nvlout);
+ else
+ error = EINVAL;
+
+ return (error);
+}
+
+CREATE_SERVICE("system.grp", grp_limit, grp_command, 0);
diff --git a/lib/libcasper/services/cap_grp/cap_grp.h b/lib/libcasper/services/cap_grp/cap_grp.h
new file mode 100644
index 000000000000..9be2e7a78dd5
--- /dev/null
+++ b/lib/libcasper/services/cap_grp/cap_grp.h
@@ -0,0 +1,93 @@
+/*-
+ * Copyright (c) 2013 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _CAP_GRP_H_
+#define _CAP_GRP_H_
+
+#ifdef HAVE_CASPER
+#define WITH_CASPER
+#endif
+
+#include <sys/cdefs.h>
+
+#ifdef WITH_CASPER
+__BEGIN_DECLS
+
+struct group *cap_getgrent(cap_channel_t *chan);
+struct group *cap_getgrnam(cap_channel_t *chan, const char *name);
+struct group *cap_getgrgid(cap_channel_t *chan, gid_t gid);
+
+int cap_getgrent_r(cap_channel_t *chan, struct group *grp, char *buffer,
+ size_t bufsize, struct group **result);
+int cap_getgrnam_r(cap_channel_t *chan, const char *name, struct group *grp,
+ char *buffer, size_t bufsize, struct group **result);
+int cap_getgrgid_r(cap_channel_t *chan, gid_t gid, struct group *grp,
+ char *buffer, size_t bufsize, struct group **result);
+
+int cap_setgroupent(cap_channel_t *chan, int stayopen);
+int cap_setgrent(cap_channel_t *chan);
+void cap_endgrent(cap_channel_t *chan);
+
+int cap_grp_limit_cmds(cap_channel_t *chan, const char * const *cmds,
+ size_t ncmds);
+int cap_grp_limit_fields(cap_channel_t *chan, const char * const *fields,
+ size_t nfields);
+int cap_grp_limit_groups(cap_channel_t *chan, const char * const *names,
+ size_t nnames, const gid_t *gids, size_t ngids);
+
+__END_DECLS
+
+#else
+#define cap_getgrent(chan) getgrent()
+#define cap_getgrnam(chan, name) getgrnam(name)
+#define cap_getgrgid(chan, gid) getgrgid(gid)
+
+#define cap_setgroupent(chan, stayopen) etgroupent(stayopen)
+#define endgrent(chan) endgrent()
+static inline int
+cap_setgrent(cap_channel_t *chan __unused)
+{
+
+ setgrent();
+ return(0);
+}
+
+#define cap_getgrent_r(chan, grp, buffer, bufsize, result) \
+ getgrent_r(grp, buffer, bufsize, result)
+#define cap_getgrnam_r(chan, name, grp, buffer, bufsize, result) \
+ getgrnam_r(name, grp, buffer, bufsize, result)
+#define cap_getgrgid_r(chan, gid, grp, buffer, bufsize, result) \
+ getgrgid_r(gid, grp, buffer, bufsize, result)
+
+#define cap_grp_limit_cmds(chan, cmds, ncmds) (0)
+#define cap_grp_limit_fields(chan, fields, nfields) (0)
+#define cap_grp_limit_groups(chan, names, nnames, gids, ngids) (0)
+
+#endif
+
+#endif /* !_CAP_GRP_H_ */
diff --git a/lib/libcasper/services/cap_grp/tests/Makefile b/lib/libcasper/services/cap_grp/tests/Makefile
new file mode 100644
index 000000000000..f57d58972c52
--- /dev/null
+++ b/lib/libcasper/services/cap_grp/tests/Makefile
@@ -0,0 +1,12 @@
+.include <src.opts.mk>
+
+TAP_TESTS_C= grp_test
+
+.if ${MK_CASPER} != "no"
+LIBADD+= casper
+LIBADD+= cap_grp
+CFLAGS+=-DWITH_CASPER
+.endif
+LIBADD+= nv
+
+.include <bsd.test.mk>
diff --git a/lib/libcasper/services/cap_grp/tests/Makefile.depend b/lib/libcasper/services/cap_grp/tests/Makefile.depend
new file mode 100644
index 000000000000..fd331b1176a3
--- /dev/null
+++ b/lib/libcasper/services/cap_grp/tests/Makefile.depend
@@ -0,0 +1,18 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcasper/services/cap_grp \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_grp/tests/grp_test.c b/lib/libcasper/services/cap_grp/tests/grp_test.c
new file mode 100644
index 000000000000..d7e9cf12954a
--- /dev/null
+++ b/lib/libcasper/services/cap_grp/tests/grp_test.c
@@ -0,0 +1,1556 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/capsicum.h>
+#include <sys/nv.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcasper.h>
+
+#include <casper/cap_grp.h>
+
+static int ntest = 1;
+
+#define CHECK(expr) do { \
+ if ((expr)) \
+ printf("ok %d %s:%u\n", ntest, __FILE__, __LINE__); \
+ else \
+ printf("not ok %d %s:%u\n", ntest, __FILE__, __LINE__); \
+ fflush(stdout); \
+ ntest++; \
+} while (0)
+#define CHECKX(expr) do { \
+ if ((expr)) { \
+ printf("ok %d %s:%u\n", ntest, __FILE__, __LINE__); \
+ } else { \
+ printf("not ok %d %s:%u\n", ntest, __FILE__, __LINE__); \
+ exit(1); \
+ } \
+ fflush(stdout); \
+ ntest++; \
+} while (0)
+
+#define GID_WHEEL 0
+#define GID_OPERATOR 5
+
+#define GETGRENT0 0x0001
+#define GETGRENT1 0x0002
+#define GETGRENT2 0x0004
+#define GETGRENT (GETGRENT0 | GETGRENT1 | GETGRENT2)
+#define GETGRENT_R0 0x0008
+#define GETGRENT_R1 0x0010
+#define GETGRENT_R2 0x0020
+#define GETGRENT_R (GETGRENT_R0 | GETGRENT_R1 | GETGRENT_R2)
+#define GETGRNAM 0x0040
+#define GETGRNAM_R 0x0080
+#define GETGRGID 0x0100
+#define GETGRGID_R 0x0200
+#define SETGRENT 0x0400
+
+static bool
+group_mem_compare(char **mem0, char **mem1)
+{
+ int i0, i1;
+
+ if (mem0 == NULL && mem1 == NULL)
+ return (true);
+ if (mem0 == NULL || mem1 == NULL)
+ return (false);
+
+ for (i0 = 0; mem0[i0] != NULL; i0++) {
+ for (i1 = 0; mem1[i1] != NULL; i1++) {
+ if (strcmp(mem0[i0], mem1[i1]) == 0)
+ break;
+ }
+ if (mem1[i1] == NULL)
+ return (false);
+ }
+
+ return (true);
+}
+
+static bool
+group_compare(const struct group *grp0, const struct group *grp1)
+{
+
+ if (grp0 == NULL && grp1 == NULL)
+ return (true);
+ if (grp0 == NULL || grp1 == NULL)
+ return (false);
+
+ if (strcmp(grp0->gr_name, grp1->gr_name) != 0)
+ return (false);
+
+ if (grp0->gr_passwd != NULL || grp1->gr_passwd != NULL) {
+ if (grp0->gr_passwd == NULL || grp1->gr_passwd == NULL)
+ return (false);
+ if (strcmp(grp0->gr_passwd, grp1->gr_passwd) != 0)
+ return (false);
+ }
+
+ if (grp0->gr_gid != grp1->gr_gid)
+ return (false);
+
+ if (!group_mem_compare(grp0->gr_mem, grp1->gr_mem))
+ return (false);
+
+ return (true);
+}
+
+static unsigned int
+runtest_cmds(cap_channel_t *capgrp)
+{
+ char bufs[1024], bufc[1024];
+ unsigned int result;
+ struct group *grps, *grpc;
+ struct group sts, stc;
+
+ result = 0;
+
+ (void)setgrent();
+ if (cap_setgrent(capgrp) == 1)
+ result |= SETGRENT;
+
+ grps = getgrent();
+ grpc = cap_getgrent(capgrp);
+ if (group_compare(grps, grpc)) {
+ result |= GETGRENT0;
+ grps = getgrent();
+ grpc = cap_getgrent(capgrp);
+ if (group_compare(grps, grpc))
+ result |= GETGRENT1;
+ }
+
+ getgrent_r(&sts, bufs, sizeof(bufs), &grps);
+ cap_getgrent_r(capgrp, &stc, bufc, sizeof(bufc), &grpc);
+ if (group_compare(grps, grpc)) {
+ result |= GETGRENT_R0;
+ getgrent_r(&sts, bufs, sizeof(bufs), &grps);
+ cap_getgrent_r(capgrp, &stc, bufc, sizeof(bufc), &grpc);
+ if (group_compare(grps, grpc))
+ result |= GETGRENT_R1;
+ }
+
+ (void)setgrent();
+ if (cap_setgrent(capgrp) == 1)
+ result |= SETGRENT;
+
+ getgrent_r(&sts, bufs, sizeof(bufs), &grps);
+ cap_getgrent_r(capgrp, &stc, bufc, sizeof(bufc), &grpc);
+ if (group_compare(grps, grpc))
+ result |= GETGRENT_R2;
+
+ grps = getgrent();
+ grpc = cap_getgrent(capgrp);
+ if (group_compare(grps, grpc))
+ result |= GETGRENT2;
+
+ grps = getgrnam("wheel");
+ grpc = cap_getgrnam(capgrp, "wheel");
+ if (group_compare(grps, grpc)) {
+ grps = getgrnam("operator");
+ grpc = cap_getgrnam(capgrp, "operator");
+ if (group_compare(grps, grpc))
+ result |= GETGRNAM;
+ }
+
+ getgrnam_r("wheel", &sts, bufs, sizeof(bufs), &grps);
+ cap_getgrnam_r(capgrp, "wheel", &stc, bufc, sizeof(bufc), &grpc);
+ if (group_compare(grps, grpc)) {
+ getgrnam_r("operator", &sts, bufs, sizeof(bufs), &grps);
+ cap_getgrnam_r(capgrp, "operator", &stc, bufc, sizeof(bufc),
+ &grpc);
+ if (group_compare(grps, grpc))
+ result |= GETGRNAM_R;
+ }
+
+ grps = getgrgid(GID_WHEEL);
+ grpc = cap_getgrgid(capgrp, GID_WHEEL);
+ if (group_compare(grps, grpc)) {
+ grps = getgrgid(GID_OPERATOR);
+ grpc = cap_getgrgid(capgrp, GID_OPERATOR);
+ if (group_compare(grps, grpc))
+ result |= GETGRGID;
+ }
+
+ getgrgid_r(GID_WHEEL, &sts, bufs, sizeof(bufs), &grps);
+ cap_getgrgid_r(capgrp, GID_WHEEL, &stc, bufc, sizeof(bufc), &grpc);
+ if (group_compare(grps, grpc)) {
+ getgrgid_r(GID_OPERATOR, &sts, bufs, sizeof(bufs), &grps);
+ cap_getgrgid_r(capgrp, GID_OPERATOR, &stc, bufc, sizeof(bufc),
+ &grpc);
+ if (group_compare(grps, grpc))
+ result |= GETGRGID_R;
+ }
+
+ return (result);
+}
+
+static void
+test_cmds(cap_channel_t *origcapgrp)
+{
+ cap_channel_t *capgrp;
+ const char *cmds[7], *fields[4], *names[5];
+ gid_t gids[5];
+
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+
+ names[0] = "wheel";
+ names[1] = "daemon";
+ names[2] = "kmem";
+ names[3] = "sys";
+ names[4] = "operator";
+
+ gids[0] = 0;
+ gids[1] = 1;
+ gids[2] = 2;
+ gids[3] = 3;
+ gids[4] = 5;
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names: wheel, daemon, kmem, sys, operator
+ * gids:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == 0);
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0);
+ CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R |
+ GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names:
+ * gids: 0, 1, 2, 3, 5
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == 0);
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0);
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R |
+ GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: getgrent, getgrent_r, getgrnam, getgrnam_r,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names: wheel, daemon, kmem, sys, operator
+ * gids:
+ * Disallow:
+ * cmds: setgrent
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cap_setgrent(capgrp);
+
+ cmds[0] = "getgrent";
+ cmds[1] = "getgrent_r";
+ cmds[2] = "getgrnam";
+ cmds[3] = "getgrnam_r";
+ cmds[4] = "getgrgid";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "setgrent";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (GETGRENT0 | GETGRENT1 | GETGRENT_R0 |
+ GETGRENT_R1 | GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: getgrent, getgrent_r, getgrnam, getgrnam_r,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names:
+ * gids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: setgrent
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cap_setgrent(capgrp);
+
+ cmds[0] = "getgrent";
+ cmds[1] = "getgrent_r";
+ cmds[2] = "getgrnam";
+ cmds[3] = "getgrnam_r";
+ cmds[4] = "getgrgid";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "setgrent";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (GETGRENT0 | GETGRENT1 | GETGRENT_R0 |
+ GETGRENT_R1 | GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent_r, getgrnam, getgrnam_r,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names: wheel, daemon, kmem, sys, operator
+ * gids:
+ * Disallow:
+ * cmds: getgrent
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent_r";
+ cmds[2] = "getgrnam";
+ cmds[3] = "getgrnam_r";
+ cmds[4] = "getgrgid";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrent";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0);
+ CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT_R2 |
+ GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent_r, getgrnam, getgrnam_r,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names:
+ * gids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getgrent
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent_r";
+ cmds[2] = "getgrnam";
+ cmds[3] = "getgrnam_r";
+ cmds[4] = "getgrgid";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrent";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0);
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT_R2 |
+ GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrnam, getgrnam_r,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names: wheel, daemon, kmem, sys, operator
+ * gids:
+ * Disallow:
+ * cmds: getgrent_r
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrnam";
+ cmds[3] = "getgrnam_r";
+ cmds[4] = "getgrgid";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrent_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT0 | GETGRENT1 |
+ GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrnam, getgrnam_r,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names:
+ * gids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getgrent_r
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrnam";
+ cmds[3] = "getgrnam_r";
+ cmds[4] = "getgrgid";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrent_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT0 | GETGRENT1 |
+ GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrent_r, getgrnam_r,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names: wheel, daemon, kmem, sys, operator
+ * gids:
+ * Disallow:
+ * cmds: getgrnam
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam_r";
+ cmds[4] = "getgrgid";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrnam";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0);
+ CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R |
+ GETGRNAM_R | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrent_r, getgrnam_r,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names:
+ * gids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getgrnam
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam_r";
+ cmds[4] = "getgrgid";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrnam";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0);
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R |
+ GETGRNAM_R | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrent_r, getgrnam,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names: wheel, daemon, kmem, sys, operator
+ * gids:
+ * Disallow:
+ * cmds: getgrnam_r
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrgid";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrnam_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R |
+ GETGRNAM | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrent_r, getgrnam,
+ * getgrgid, getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names:
+ * gids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getgrnam_r
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrgid";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrnam_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R |
+ GETGRNAM | GETGRGID | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r,
+ * getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names: wheel, daemon, kmem, sys, operator
+ * gids:
+ * Disallow:
+ * cmds: getgrgid
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrgid";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0);
+ CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R |
+ GETGRNAM | GETGRNAM_R | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r,
+ * getgrgid_r
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names:
+ * gids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getgrgid
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrgid";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0);
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R |
+ GETGRNAM | GETGRNAM_R | GETGRGID_R));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r,
+ * getgrgid
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names: wheel, daemon, kmem, sys, operator
+ * gids:
+ * Disallow:
+ * cmds: getgrgid_r
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R |
+ GETGRNAM | GETGRNAM_R | GETGRGID));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r,
+ * getgrgid
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ * groups:
+ * names:
+ * gids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getgrgid_r
+ * fields:
+ * groups:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0);
+ cmds[0] = "setgrent";
+ cmds[1] = "getgrent";
+ cmds[2] = "getgrent_r";
+ cmds[3] = "getgrnam";
+ cmds[4] = "getgrnam_r";
+ cmds[5] = "getgrgid";
+ cmds[6] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getgrgid_r";
+ CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0);
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R |
+ GETGRNAM | GETGRNAM_R | GETGRGID));
+
+ cap_close(capgrp);
+}
+
+#define GR_NAME 0x01
+#define GR_PASSWD 0x02
+#define GR_GID 0x04
+#define GR_MEM 0x08
+
+static unsigned int
+group_fields(const struct group *grp)
+{
+ unsigned int result;
+
+ result = 0;
+
+ if (grp->gr_name != NULL && grp->gr_name[0] != '\0')
+ result |= GR_NAME;
+
+ if (grp->gr_passwd != NULL && grp->gr_passwd[0] != '\0')
+ result |= GR_PASSWD;
+
+ if (grp->gr_gid != (gid_t)-1)
+ result |= GR_GID;
+
+ if (grp->gr_mem != NULL && grp->gr_mem[0] != NULL)
+ result |= GR_MEM;
+
+ return (result);
+}
+
+static bool
+runtest_fields(cap_channel_t *capgrp, unsigned int expected)
+{
+ char buf[1024];
+ struct group *grp;
+ struct group st;
+
+ (void)cap_setgrent(capgrp);
+ grp = cap_getgrent(capgrp);
+ if (group_fields(grp) != expected)
+ return (false);
+
+ (void)cap_setgrent(capgrp);
+ cap_getgrent_r(capgrp, &st, buf, sizeof(buf), &grp);
+ if (group_fields(grp) != expected)
+ return (false);
+
+ grp = cap_getgrnam(capgrp, "wheel");
+ if (group_fields(grp) != expected)
+ return (false);
+
+ cap_getgrnam_r(capgrp, "wheel", &st, buf, sizeof(buf), &grp);
+ if (group_fields(grp) != expected)
+ return (false);
+
+ grp = cap_getgrgid(capgrp, GID_WHEEL);
+ if (group_fields(grp) != expected)
+ return (false);
+
+ cap_getgrgid_r(capgrp, GID_WHEEL, &st, buf, sizeof(buf), &grp);
+ if (group_fields(grp) != expected)
+ return (false);
+
+ return (true);
+}
+
+static void
+test_fields(cap_channel_t *origcapgrp)
+{
+ cap_channel_t *capgrp;
+ const char *fields[4];
+
+ /* No limits. */
+
+ CHECK(runtest_fields(origcapgrp, GR_NAME | GR_PASSWD | GR_GID | GR_MEM));
+
+ /*
+ * Allow:
+ * fields: gr_name, gr_passwd, gr_gid, gr_mem
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0);
+
+ CHECK(runtest_fields(capgrp, GR_NAME | GR_PASSWD | GR_GID | GR_MEM));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * fields: gr_passwd, gr_gid, gr_mem
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ fields[0] = "gr_passwd";
+ fields[1] = "gr_gid";
+ fields[2] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 3) == 0);
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(capgrp, GR_PASSWD | GR_GID | GR_MEM));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * fields: gr_name, gr_gid, gr_mem
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ fields[0] = "gr_name";
+ fields[1] = "gr_gid";
+ fields[2] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 3) == 0);
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "gr_passwd";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(capgrp, GR_NAME | GR_GID | GR_MEM));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * fields: gr_name, gr_passwd, gr_mem
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 3) == 0);
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "gr_gid";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(capgrp, GR_NAME | GR_PASSWD | GR_MEM));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * fields: gr_name, gr_passwd, gr_gid
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 3) == 0);
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(capgrp, GR_NAME | GR_PASSWD | GR_GID));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * fields: gr_name, gr_passwd
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0);
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "gr_gid";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(capgrp, GR_NAME | GR_PASSWD));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * fields: gr_name, gr_gid
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ fields[0] = "gr_name";
+ fields[1] = "gr_gid";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0);
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(capgrp, GR_NAME | GR_GID));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * fields: gr_name, gr_mem
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ fields[0] = "gr_name";
+ fields[1] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0);
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "gr_passwd";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(capgrp, GR_NAME | GR_MEM));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * fields: gr_passwd, gr_gid
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ fields[0] = "gr_passwd";
+ fields[1] = "gr_gid";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0);
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(capgrp, GR_PASSWD | GR_GID));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * fields: gr_passwd, gr_mem
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ fields[0] = "gr_passwd";
+ fields[1] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0);
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "gr_gid";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(capgrp, GR_PASSWD | GR_MEM));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * fields: gr_gid, gr_mem
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ fields[0] = "gr_gid";
+ fields[1] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0);
+ fields[0] = "gr_name";
+ fields[1] = "gr_passwd";
+ fields[2] = "gr_gid";
+ fields[3] = "gr_mem";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "gr_passwd";
+ CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(capgrp, GR_GID | GR_MEM));
+
+ cap_close(capgrp);
+}
+
+static bool
+runtest_groups(cap_channel_t *capgrp, const char **names, const gid_t *gids,
+ size_t ngroups)
+{
+ char buf[1024];
+ struct group *grp;
+ struct group st;
+ unsigned int i, got;
+
+ (void)cap_setgrent(capgrp);
+ got = 0;
+ for (;;) {
+ grp = cap_getgrent(capgrp);
+ if (grp == NULL)
+ break;
+ got++;
+ for (i = 0; i < ngroups; i++) {
+ if (strcmp(names[i], grp->gr_name) == 0 &&
+ gids[i] == grp->gr_gid) {
+ break;
+ }
+ }
+ if (i == ngroups)
+ return (false);
+ }
+ if (got != ngroups)
+ return (false);
+
+ (void)cap_setgrent(capgrp);
+ got = 0;
+ for (;;) {
+ cap_getgrent_r(capgrp, &st, buf, sizeof(buf), &grp);
+ if (grp == NULL)
+ break;
+ got++;
+ for (i = 0; i < ngroups; i++) {
+ if (strcmp(names[i], grp->gr_name) == 0 &&
+ gids[i] == grp->gr_gid) {
+ break;
+ }
+ }
+ if (i == ngroups)
+ return (false);
+ }
+ if (got != ngroups)
+ return (false);
+
+ for (i = 0; i < ngroups; i++) {
+ grp = cap_getgrnam(capgrp, names[i]);
+ if (grp == NULL)
+ return (false);
+ }
+
+ for (i = 0; i < ngroups; i++) {
+ cap_getgrnam_r(capgrp, names[i], &st, buf, sizeof(buf), &grp);
+ if (grp == NULL)
+ return (false);
+ }
+
+ for (i = 0; i < ngroups; i++) {
+ grp = cap_getgrgid(capgrp, gids[i]);
+ if (grp == NULL)
+ return (false);
+ }
+
+ for (i = 0; i < ngroups; i++) {
+ cap_getgrgid_r(capgrp, gids[i], &st, buf, sizeof(buf), &grp);
+ if (grp == NULL)
+ return (false);
+ }
+
+ return (true);
+}
+
+static void
+test_groups(cap_channel_t *origcapgrp)
+{
+ cap_channel_t *capgrp;
+ const char *names[5];
+ gid_t gids[5];
+
+ /*
+ * Allow:
+ * groups:
+ * names: wheel, daemon, kmem, sys, tty
+ * gids:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ names[0] = "wheel";
+ names[1] = "daemon";
+ names[2] = "kmem";
+ names[3] = "sys";
+ names[4] = "tty";
+ CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0);
+ gids[0] = 0;
+ gids[1] = 1;
+ gids[2] = 2;
+ gids[3] = 3;
+ gids[4] = 4;
+
+ CHECK(runtest_groups(capgrp, names, gids, 5));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * groups:
+ * names: kmem, sys, tty
+ * gids:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ names[0] = "kmem";
+ names[1] = "sys";
+ names[2] = "tty";
+ CHECK(cap_grp_limit_groups(capgrp, names, 3, NULL, 0) == 0);
+ names[3] = "daemon";
+ CHECK(cap_grp_limit_groups(capgrp, names, 4, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "daemon";
+ CHECK(cap_grp_limit_groups(capgrp, names, 1, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "kmem";
+ gids[0] = 2;
+ gids[1] = 3;
+ gids[2] = 4;
+
+ CHECK(runtest_groups(capgrp, names, gids, 3));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * groups:
+ * names: wheel, kmem, tty
+ * gids:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ names[0] = "wheel";
+ names[1] = "kmem";
+ names[2] = "tty";
+ CHECK(cap_grp_limit_groups(capgrp, names, 3, NULL, 0) == 0);
+ names[3] = "daemon";
+ CHECK(cap_grp_limit_groups(capgrp, names, 4, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "daemon";
+ CHECK(cap_grp_limit_groups(capgrp, names, 1, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "wheel";
+ gids[0] = 0;
+ gids[1] = 2;
+ gids[2] = 4;
+
+ CHECK(runtest_groups(capgrp, names, gids, 3));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * groups:
+ * names:
+ * gids: 2, 3, 4
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ names[0] = "kmem";
+ names[1] = "sys";
+ names[2] = "tty";
+ gids[0] = 2;
+ gids[1] = 3;
+ gids[2] = 4;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 3) == 0);
+ gids[3] = 0;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ gids[0] = 0;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 1) == -1 &&
+ errno == ENOTCAPABLE);
+ gids[0] = 2;
+
+ CHECK(runtest_groups(capgrp, names, gids, 3));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * groups:
+ * names:
+ * gids: 0, 2, 4
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ names[0] = "wheel";
+ names[1] = "kmem";
+ names[2] = "tty";
+ gids[0] = 0;
+ gids[1] = 2;
+ gids[2] = 4;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 3) == 0);
+ gids[3] = 1;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ gids[0] = 1;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 1) == -1 &&
+ errno == ENOTCAPABLE);
+ gids[0] = 0;
+
+ CHECK(runtest_groups(capgrp, names, gids, 3));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * groups:
+ * names: kmem
+ * gids:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ names[0] = "kmem";
+ CHECK(cap_grp_limit_groups(capgrp, names, 1, NULL, 0) == 0);
+ names[1] = "daemon";
+ CHECK(cap_grp_limit_groups(capgrp, names, 2, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "daemon";
+ CHECK(cap_grp_limit_groups(capgrp, names, 1, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "kmem";
+ gids[0] = 2;
+
+ CHECK(runtest_groups(capgrp, names, gids, 1));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * groups:
+ * names: wheel, tty
+ * gids:
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ names[0] = "wheel";
+ names[1] = "tty";
+ CHECK(cap_grp_limit_groups(capgrp, names, 2, NULL, 0) == 0);
+ names[2] = "daemon";
+ CHECK(cap_grp_limit_groups(capgrp, names, 3, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "daemon";
+ CHECK(cap_grp_limit_groups(capgrp, names, 1, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "wheel";
+ gids[0] = 0;
+ gids[1] = 4;
+
+ CHECK(runtest_groups(capgrp, names, gids, 2));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * groups:
+ * names:
+ * gids: 2
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ names[0] = "kmem";
+ gids[0] = 2;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 1) == 0);
+ gids[1] = 1;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 2) == -1 &&
+ errno == ENOTCAPABLE);
+ gids[0] = 1;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 1) == -1 &&
+ errno == ENOTCAPABLE);
+ gids[0] = 2;
+
+ CHECK(runtest_groups(capgrp, names, gids, 1));
+
+ cap_close(capgrp);
+
+ /*
+ * Allow:
+ * groups:
+ * names:
+ * gids: 0, 4
+ */
+ capgrp = cap_clone(origcapgrp);
+ CHECK(capgrp != NULL);
+
+ names[0] = "wheel";
+ names[1] = "tty";
+ gids[0] = 0;
+ gids[1] = 4;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 2) == 0);
+ gids[2] = 1;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 3) == -1 &&
+ errno == ENOTCAPABLE);
+ gids[0] = 1;
+ CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 1) == -1 &&
+ errno == ENOTCAPABLE);
+ gids[0] = 0;
+
+ CHECK(runtest_groups(capgrp, names, gids, 2));
+
+ cap_close(capgrp);
+}
+
+int
+main(void)
+{
+ cap_channel_t *capcas, *capgrp;
+
+ printf("1..199\n");
+ fflush(stdout);
+
+ capcas = cap_init();
+ CHECKX(capcas != NULL);
+
+ capgrp = cap_service_open(capcas, "system.grp");
+ CHECKX(capgrp != NULL);
+
+ cap_close(capcas);
+
+ /* No limits. */
+
+ CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R |
+ GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R));
+
+ test_cmds(capgrp);
+
+ test_fields(capgrp);
+
+ test_groups(capgrp);
+
+ cap_close(capgrp);
+
+ exit(0);
+}
diff --git a/lib/libcasper/services/cap_net/Makefile b/lib/libcasper/services/cap_net/Makefile
new file mode 100644
index 000000000000..1ba35a674a05
--- /dev/null
+++ b/lib/libcasper/services/cap_net/Makefile
@@ -0,0 +1,46 @@
+SHLIBDIR?= /lib
+
+.include <src.opts.mk>
+
+PACKAGE=libcasper
+
+SHLIB_MAJOR= 1
+INCSDIR?= ${INCLUDEDIR}/casper
+
+.if ${MK_CASPER} != "no"
+SHLIB= cap_net
+
+SRCS= cap_net.c
+.endif
+
+INCS= cap_net.h
+
+LIBADD= nv
+
+CFLAGS+=-I${.CURDIR}
+CFLAGS+=-DWITH_CASPER
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+MAN+= cap_net.3
+
+MLINKS+=cap_net.3 libcap_net.3
+MLINKS+=cap_net.3 cap_bind.3
+MLINKS+=cap_net.3 cap_connect.3
+MLINKS+=cap_net.3 cap_net_free.3
+MLINKS+=cap_net.3 cap_net_limit.3
+MLINKS+=cap_net.3 cap_net_limit_addr2name.3
+MLINKS+=cap_net.3 cap_net_limit_addr2name_family.3
+MLINKS+=cap_net.3 cap_net_limit_bind.3
+MLINKS+=cap_net.3 cap_net_limit_connect.3
+MLINKS+=cap_net.3 cap_net_limit_init.3
+MLINKS+=cap_net.3 cap_net_limit_name2addr.3
+MLINKS+=cap_net.3 cap_net_limit_name2addr_family.3
+MLINKS+=cap_net.3 cap_getaddrinfo.3
+MLINKS+=cap_net.3 cap_gethostbyaddr.3
+MLINKS+=cap_net.3 cap_gethostbyname.3
+MLINKS+=cap_net.3 cap_gethostbyname2.3
+MLINKS+=cap_net.3 cap_getnameinfo.3
+
+.include <bsd.lib.mk>
diff --git a/lib/libcasper/services/cap_net/Makefile.depend b/lib/libcasper/services/cap_net/Makefile.depend
new file mode 100644
index 000000000000..02bae00eb04d
--- /dev/null
+++ b/lib/libcasper/services/cap_net/Makefile.depend
@@ -0,0 +1,17 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_net/cap_net.3 b/lib/libcasper/services/cap_net/cap_net.3
new file mode 100644
index 000000000000..e322222e866f
--- /dev/null
+++ b/lib/libcasper/services/cap_net/cap_net.3
@@ -0,0 +1,292 @@
+.\" Copyright (c) 2020 Mariusz Zaborski <oshogbo@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd December 6, 2023
+.Dt CAP_NET 3
+.Os
+.Sh NAME
+.Nm cap_bind ,
+.Nm cap_connect ,
+.Nm cap_getaddrinfo ,
+.Nm cap_gethostbyaddr ,
+.Nm cap_gethostbyname ,
+.Nm cap_gethostbyname2 ,
+.Nm cap_getnameinfo ,
+.Nm cap_net_free ,
+.Nm cap_net_limit ,
+.Nm cap_net_limit_addr2name ,
+.Nm cap_net_limit_addr2name_family ,
+.Nm cap_net_limit_bind ,
+.Nm cap_net_limit_connect ,
+.Nm cap_net_limit_init ,
+.Nm cap_net_limit_name2addr ,
+.Nm cap_net_limit_name2addr_family ,
+.Nd "library for networking in capability mode"
+.Sh LIBRARY
+.Lb libcap_net
+.Sh SYNOPSIS
+.In sys/nv.h
+.In libcasper.h
+.In casper/cap_net.h
+.Ft int
+.Fn cap_bind "cap_channel_t *chan" "int s" "const struct sockaddr *addr" "socklen_t addrlen"
+.Ft int
+.Fn cap_connect "cap_channel_t *chan" "int s" "const struct sockaddr *name" "socklen_t namelen"
+.Ft int
+.Fn cap_getaddrinfo "cap_channel_t *chan" "const char *hostname" "const char *servname" "const struct addrinfo *hints" "struct addrinfo **res"
+.Ft int
+.Fn cap_getnameinfo "cap_channel_t *chan" "const struct sockaddr *sa" "socklen_t salen" "char *host" "size_t hostlen" "char *serv" "size_t servlen" "int flags"
+.Ft "struct hostent *"
+.Fn cap_gethostbyname "const cap_channel_t *chan" "const char *name"
+.Ft "struct hostent *"
+.Fn cap_gethostbyname2 "const cap_channel_t *chan" "const char *name" "int af"
+.Ft "struct hostent *"
+.Fn cap_gethostbyaddr "const cap_channel_t *chan" "const void *addr" "socklen_t len" "int af"
+.Ft "cap_net_limit_t *"
+.Fn cap_net_limit_init "cap_channel_t *chan" "uint64_t mode"
+.Ft int
+.Fn cap_net_limit "cap_net_limit_t *limit"
+.Ft void
+.Fn cap_net_free "cap_net_limit_t *limit"
+.Ft "cap_net_limit_t *"
+.Fn cap_net_limit_addr2name_family "cap_net_limit_t *limit" "int *family" "size_t size"
+.Ft "cap_net_limit_t *"
+.Fn cap_net_limit_addr2name "cap_net_limit_t *limit" "const struct sockaddr *sa" "socklen_t salen"
+.Ft "cap_net_limit_t *"
+.Fn cap_net_limit_name2addr_family "cap_net_limit_t *limit" "int *family" "size_t size"
+.Ft "cap_net_limit_t *"
+.Fn cap_net_limit_name2addr "cap_net_limit_t *limit" "const char *name" "const char *serv"
+.Ft "cap_net_limit_t *"
+.Fn cap_net_limit_connect "cap_net_limit_t *limit" "const struct sockaddr *sa" "socklen_t salen"
+.Ft "cap_net_limit_t *"
+.Fn cap_net_limit_bind "cap_net_limit_t *limit" "const struct sockaddr *sa" "socklen_t salen"
+.Sh DESCRIPTION
+The functions
+.Fn cap_bind ,
+.Fn cap_connect ,
+.Fn cap_getaddrinfo ,
+.Fn cap_getnameinfo ,
+.Fn cap_gethostbyname ,
+.Fn cap_gethostbyname2 ,
+and
+.Fn cap_gethostbyaddr
+provide a set of APIs equivalent to
+.Xr bind 2 ,
+.Xr connect 2 ,
+.Xr getaddrinfo 3 ,
+.Xr getnameinfo 3 ,
+.Xr gethostbyname 3 ,
+.Xr gethostbyname2 3 ,
+and
+.Xr gethostbyaddr 3
+except that a connection to the
+.Nm system.net
+service needs to be provided.
+.Pp
+These functions, as well as
+.Fn cap_net_limit ,
+are reentrant but not thread-safe.
+That is, they may be called from separate threads only with different
+.Vt cap_channel_t
+arguments or with synchronization.
+.Sh LIMITS
+By default, the cap_net capability provides unrestricted access to the network
+namespace.
+Applications typically only require access to a small portion of the network
+namespace:
+The
+.Fn cap_net_limit
+function can be used to restrict access to the network.
+The
+.Fn cap_net_limit_init
+returns an opaque limit handle used to store a list of capabilities.
+The
+.Fa mode
+restricts the functionality of the service.
+Modes are encoded using the following flags:
+.Pp
+.Bd -literal -offset indent -compact
+CAPNET_ADDR2NAME reverse DNS lookups are allowed with
+ cap_getnameinfo
+CAPNET_NAME2ADDR name resolution is allowed with
+ cap_getaddrinfo
+CAPNET_DEPRECATED_ADDR2NAME reverse DNS lookups are allowed with
+ cap_gethostbyaddr
+CAPNET_DEPRECATED_NAME2ADDR name resolution is allowed with
+ cap_gethostbyname and cap_gethostbyname2
+CAPNET_BIND bind syscall is allowed
+CAPNET_CONNECT connect syscall is allowed
+CAPNET_CONNECTDNS connect syscall is allowed to the values
+ returned from previous call to
+ the cap_getaddrinfo or cap_gethostbyname
+.Ed
+.Pp
+.Fn cap_net_limit_addr2name_family
+limits the
+.Fn cap_getnameinfo
+and
+.Fn cap_gethostbyaddr
+to do reverse DNS lookups to specific family (AF_INET, AF_INET6, etc.)
+.Pp
+.Fn cap_net_limit_addr2name
+limits the
+.Fn cap_getnameinfo
+and
+.Fn cap_gethostbyaddr
+to do reverse DNS lookups only on those specific structures.
+.Pp
+.Fn cap_net_limit_name2addr_family
+limits the
+.Fn cap_getaddrinfo ,
+.Fn cap_gethostbyname
+and
+.Fn cap_gethostbyname2
+to do the name resolution on specific family (AF_INET, AF_INET6, etc.)
+.Pp
+.Fn cap_net_limit_addr2name
+restricts
+.Fn cap_getaddrinfo ,
+.Fn cap_gethostbyname
+and
+.Fn cap_gethostbyname2
+to a set of domains.
+.Pp
+.Fn cap_net_limit_bind
+limits
+.Fn cap_bind
+to bind only on those specific structures.
+.Pp
+.Fn cap_net_limit_connect
+limits
+.Fn cap_connect
+to connect only on those specific structures.
+If the CAPNET_CONNECTDNS is set the limits are extended to the values returned
+by
+.Fn cap_getaddrinfo ,
+.Fn cap_gethostbyname
+and
+.Fn cap_gethostbyname2
+In case of the
+.Fn cap_getaddrinfo
+the restriction is strict.
+In case of the
+.Fn cap_gethostbyname
+and
+.Fn cap_gethostbyname2
+any port will be accepted in the
+.Fn cap_connect
+function.
+.Pp
+The
+.Fn cap_net_limit
+will consume and apply the limits.
+.Pp
+Once a set of limits is applied, subsequent calls to
+.Fn cap_net_limit
+will fail unless the new set is a subset of the current set.
+.Pp
+If the
+.Fn cap_net_limit
+was not called the rights may be freed using
+.Fn cap_net_free .
+Multiple calls to
+.Fn cap_net_limit_addr2name_family ,
+.Fn cap_net_limit_addr2name ,
+.Fn cap_net_limit_name2addr_family ,
+.Fn cap_net_limit_name2addr ,
+.Fn cap_net_limit_connect ,
+and
+.Fn cap_net_limit_bind
+is supported, each call is extending preview capabilities.
+.Sh EXAMPLES
+The following example first opens a capability to casper and then uses this
+capability to create the
+.Nm system.net
+casper service and uses it to resolve a host and connect to it.
+.Bd -literal
+cap_channel_t *capcas, *capnet;
+cap_net_limit_t *limit;
+int familylimit, error, s;
+const char *host = "example.com";
+struct addrinfo hints, *res;
+
+/* Open capability to Casper. */
+capcas = cap_init();
+if (capcas == NULL)
+ err(1, "Unable to contact Casper");
+
+/* Cache NLA for gai_strerror. */
+caph_cache_catpages();
+
+/* Enter capability mode sandbox. */
+if (caph_enter_casper() < 0)
+ err(1, "Unable to enter capability mode");
+
+/* Use Casper capability to create capability to the system.net service. */
+capnet = cap_service_open(capcas, "system.net");
+if (capnet == NULL)
+ err(1, "Unable to open system.net service");
+
+/* Close Casper capability. */
+cap_close(capcas);
+
+/* Limit system.net to reserve IPv4 addresses, to host example.com . */
+limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR | CAPNET_CONNECTDNS);
+if (limit == NULL)
+ err(1, "Unable to create limits.");
+cap_net_limit_name2addr(limit, host, "80");
+familylimit = AF_INET;
+cap_net_limit_name2addr_family(limit, &familylimit, 1);
+if (cap_net_limit(limit) < 0)
+ err(1, "Unable to apply limits.");
+
+/* Find IP addresses for the given host. */
+memset(&hints, 0, sizeof(hints));
+hints.ai_family = AF_INET;
+hints.ai_socktype = SOCK_STREAM;
+
+error = cap_getaddrinfo(capnet, host, "80", &hints, &res);
+if (error != 0)
+ errx(1, "cap_getaddrinfo(): %s: %s", host, gai_strerror(error));
+
+s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+if (s < 0)
+ err(1, "Unable to create socket");
+
+if (cap_connect(capnet, s, res->ai_addr, res->ai_addrlen) < 0)
+ err(1, "Unable to connect to host");
+.Ed
+.Sh SEE ALSO
+.Xr bind 2 ,
+.Xr cap_enter 2 ,
+.Xr connect 2 ,
+.Xr caph_enter 3 ,
+.Xr err 3 ,
+.Xr gethostbyaddr 3 ,
+.Xr gethostbyname 3 ,
+.Xr gethostbyname2 3 ,
+.Xr getnameinfo 3 ,
+.Xr capsicum 4 ,
+.Xr nv 9
+.Sh AUTHORS
+.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org
diff --git a/lib/libcasper/services/cap_net/cap_net.c b/lib/libcasper/services/cap_net/cap_net.c
new file mode 100644
index 000000000000..5887fe3c407e
--- /dev/null
+++ b/lib/libcasper/services/cap_net/cap_net.c
@@ -0,0 +1,1438 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/cnv.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcasper.h>
+#include <libcasper_service.h>
+
+#include "cap_net.h"
+
+#define CAPNET_MASK (CAPNET_ADDR2NAME | CAPNET_NAME2ADDR \
+ CAPNET_DEPRECATED_ADDR2NAME | CAPNET_DEPRECATED_NAME2ADDR | \
+ CAPNET_CONNECT | CAPNET_BIND | CAPNET_CONNECTDNS)
+
+/*
+ * Defines for the names of the limits.
+ * XXX: we should convert all string constats to this to avoid typos.
+ */
+#define LIMIT_NV_BIND "bind"
+#define LIMIT_NV_CONNECT "connect"
+#define LIMIT_NV_ADDR2NAME "addr2name"
+#define LIMIT_NV_NAME2ADDR "name2addr"
+
+struct cap_net_limit {
+ cap_channel_t *cnl_chan;
+ uint64_t cnl_mode;
+ nvlist_t *cnl_addr2name;
+ nvlist_t *cnl_name2addr;
+ nvlist_t *cnl_connect;
+ nvlist_t *cnl_bind;
+};
+
+static struct hostent hent;
+
+static void
+hostent_free(struct hostent *hp)
+{
+ unsigned int ii;
+
+ free(hp->h_name);
+ hp->h_name = NULL;
+ if (hp->h_aliases != NULL) {
+ for (ii = 0; hp->h_aliases[ii] != NULL; ii++)
+ free(hp->h_aliases[ii]);
+ free(hp->h_aliases);
+ hp->h_aliases = NULL;
+ }
+ if (hp->h_addr_list != NULL) {
+ for (ii = 0; hp->h_addr_list[ii] != NULL; ii++)
+ free(hp->h_addr_list[ii]);
+ free(hp->h_addr_list);
+ hp->h_addr_list = NULL;
+ }
+}
+
+static struct hostent *
+hostent_unpack(const nvlist_t *nvl, struct hostent *hp)
+{
+ unsigned int ii, nitems;
+ char nvlname[64];
+ int n;
+
+ hostent_free(hp);
+
+ hp->h_name = strdup(nvlist_get_string(nvl, "name"));
+ if (hp->h_name == NULL)
+ goto fail;
+ hp->h_addrtype = (int)nvlist_get_number(nvl, "addrtype");
+ hp->h_length = (int)nvlist_get_number(nvl, "length");
+
+ nitems = (unsigned int)nvlist_get_number(nvl, "naliases");
+ hp->h_aliases = calloc(nitems + 1, sizeof(hp->h_aliases[0]));
+ if (hp->h_aliases == NULL)
+ goto fail;
+ for (ii = 0; ii < nitems; ii++) {
+ n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ hp->h_aliases[ii] =
+ strdup(nvlist_get_string(nvl, nvlname));
+ if (hp->h_aliases[ii] == NULL)
+ goto fail;
+ }
+ hp->h_aliases[ii] = NULL;
+
+ nitems = (unsigned int)nvlist_get_number(nvl, "naddrs");
+ hp->h_addr_list = calloc(nitems + 1, sizeof(hp->h_addr_list[0]));
+ if (hp->h_addr_list == NULL)
+ goto fail;
+ for (ii = 0; ii < nitems; ii++) {
+ hp->h_addr_list[ii] = malloc(hp->h_length);
+ if (hp->h_addr_list[ii] == NULL)
+ goto fail;
+ n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ bcopy(nvlist_get_binary(nvl, nvlname, NULL),
+ hp->h_addr_list[ii], hp->h_length);
+ }
+ hp->h_addr_list[ii] = NULL;
+
+ return (hp);
+fail:
+ hostent_free(hp);
+ h_errno = NO_RECOVERY;
+ return (NULL);
+}
+
+static int
+request_cb(cap_channel_t *chan, const char *name, int s,
+ const struct sockaddr *saddr, socklen_t len)
+{
+ nvlist_t *nvl;
+ int serrno;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", name);
+ nvlist_add_descriptor(nvl, "s", s);
+ nvlist_add_binary(nvl, "saddr", saddr, len);
+
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (-1);
+
+ if (nvlist_get_number(nvl, "error") != 0) {
+ serrno = (int)nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ errno = serrno;
+ return (-1);
+ }
+
+ s = dup2(s, nvlist_get_descriptor(nvl, "s"));
+ nvlist_destroy(nvl);
+
+ return (s == -1 ? -1 : 0);
+}
+
+int
+cap_bind(cap_channel_t *chan, int s, const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+
+ return (request_cb(chan, LIMIT_NV_BIND, s, addr, addrlen));
+}
+
+int
+cap_connect(cap_channel_t *chan, int s, const struct sockaddr *name,
+ socklen_t namelen)
+{
+
+ return (request_cb(chan, LIMIT_NV_CONNECT, s, name, namelen));
+}
+
+
+struct hostent *
+cap_gethostbyname(cap_channel_t *chan, const char *name)
+{
+
+ return (cap_gethostbyname2(chan, name, AF_INET));
+}
+
+struct hostent *
+cap_gethostbyname2(cap_channel_t *chan, const char *name, int af)
+{
+ struct hostent *hp;
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "gethostbyname");
+ nvlist_add_number(nvl, "family", (uint64_t)af);
+ nvlist_add_string(nvl, "name", name);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL) {
+ h_errno = NO_RECOVERY;
+ return (NULL);
+ }
+ if (nvlist_get_number(nvl, "error") != 0) {
+ h_errno = (int)nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ return (NULL);
+ }
+
+ hp = hostent_unpack(nvl, &hent);
+ nvlist_destroy(nvl);
+ return (hp);
+}
+
+struct hostent *
+cap_gethostbyaddr(cap_channel_t *chan, const void *addr, socklen_t len,
+ int af)
+{
+ struct hostent *hp;
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "gethostbyaddr");
+ nvlist_add_binary(nvl, "addr", addr, (size_t)len);
+ nvlist_add_number(nvl, "family", (uint64_t)af);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL) {
+ h_errno = NO_RECOVERY;
+ return (NULL);
+ }
+ if (nvlist_get_number(nvl, "error") != 0) {
+ h_errno = (int)nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ return (NULL);
+ }
+ hp = hostent_unpack(nvl, &hent);
+ nvlist_destroy(nvl);
+ return (hp);
+}
+
+static struct addrinfo *
+addrinfo_unpack(const nvlist_t *nvl)
+{
+ struct addrinfo *ai;
+ const void *addr;
+ size_t addrlen;
+ const char *canonname;
+
+ addr = nvlist_get_binary(nvl, "ai_addr", &addrlen);
+ ai = malloc(sizeof(*ai) + addrlen);
+ if (ai == NULL)
+ return (NULL);
+ ai->ai_flags = (int)nvlist_get_number(nvl, "ai_flags");
+ ai->ai_family = (int)nvlist_get_number(nvl, "ai_family");
+ ai->ai_socktype = (int)nvlist_get_number(nvl, "ai_socktype");
+ ai->ai_protocol = (int)nvlist_get_number(nvl, "ai_protocol");
+ ai->ai_addrlen = (socklen_t)addrlen;
+ canonname = dnvlist_get_string(nvl, "ai_canonname", NULL);
+ if (canonname != NULL) {
+ ai->ai_canonname = strdup(canonname);
+ if (ai->ai_canonname == NULL) {
+ free(ai);
+ return (NULL);
+ }
+ } else {
+ ai->ai_canonname = NULL;
+ }
+ ai->ai_addr = (void *)(ai + 1);
+ bcopy(addr, ai->ai_addr, addrlen);
+ ai->ai_next = NULL;
+
+ return (ai);
+}
+
+int
+cap_getaddrinfo(cap_channel_t *chan, const char *hostname, const char *servname,
+ const struct addrinfo *hints, struct addrinfo **res)
+{
+ struct addrinfo *firstai, *prevai, *curai;
+ unsigned int ii;
+ const nvlist_t *nvlai;
+ char nvlname[64];
+ nvlist_t *nvl;
+ int error, serrno, n;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "getaddrinfo");
+ if (hostname != NULL)
+ nvlist_add_string(nvl, "hostname", hostname);
+ if (servname != NULL)
+ nvlist_add_string(nvl, "servname", servname);
+ if (hints != NULL) {
+ nvlist_add_number(nvl, "hints.ai_flags",
+ (uint64_t)hints->ai_flags);
+ nvlist_add_number(nvl, "hints.ai_family",
+ (uint64_t)hints->ai_family);
+ nvlist_add_number(nvl, "hints.ai_socktype",
+ (uint64_t)hints->ai_socktype);
+ nvlist_add_number(nvl, "hints.ai_protocol",
+ (uint64_t)hints->ai_protocol);
+ }
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (EAI_MEMORY);
+ if (nvlist_get_number(nvl, "error") != 0) {
+ error = (int)nvlist_get_number(nvl, "error");
+ serrno = dnvlist_get_number(nvl, "errno", 0);
+ nvlist_destroy(nvl);
+ errno = (error == EAI_SYSTEM) ? serrno : 0;
+ return (error);
+ }
+
+ nvlai = NULL;
+ firstai = prevai = curai = NULL;
+ for (ii = 0; ; ii++) {
+ n = snprintf(nvlname, sizeof(nvlname), "res%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ if (!nvlist_exists_nvlist(nvl, nvlname))
+ break;
+ nvlai = nvlist_get_nvlist(nvl, nvlname);
+ curai = addrinfo_unpack(nvlai);
+ if (curai == NULL) {
+ nvlist_destroy(nvl);
+ return (EAI_MEMORY);
+ }
+ if (prevai != NULL)
+ prevai->ai_next = curai;
+ else
+ firstai = curai;
+ prevai = curai;
+ }
+ nvlist_destroy(nvl);
+ if (curai == NULL && nvlai != NULL) {
+ if (firstai == NULL)
+ freeaddrinfo(firstai);
+ return (EAI_MEMORY);
+ }
+
+ *res = firstai;
+ return (0);
+}
+
+int
+cap_getnameinfo(cap_channel_t *chan, const struct sockaddr *sa, socklen_t salen,
+ char *host, size_t hostlen, char *serv, size_t servlen, int flags)
+{
+ nvlist_t *nvl;
+ int error, serrno;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "getnameinfo");
+ nvlist_add_number(nvl, "hostlen", (uint64_t)hostlen);
+ nvlist_add_number(nvl, "servlen", (uint64_t)servlen);
+ nvlist_add_binary(nvl, "sa", sa, (size_t)salen);
+ nvlist_add_number(nvl, "flags", (uint64_t)flags);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (EAI_MEMORY);
+ if (nvlist_get_number(nvl, "error") != 0) {
+ error = (int)nvlist_get_number(nvl, "error");
+ serrno = dnvlist_get_number(nvl, "errno", 0);
+ nvlist_destroy(nvl);
+ errno = (error == EAI_SYSTEM) ? serrno : 0;
+ return (error);
+ }
+
+ if (host != NULL && nvlist_exists_string(nvl, "host"))
+ strlcpy(host, nvlist_get_string(nvl, "host"), hostlen);
+ if (serv != NULL && nvlist_exists_string(nvl, "serv"))
+ strlcpy(serv, nvlist_get_string(nvl, "serv"), servlen);
+ nvlist_destroy(nvl);
+ return (0);
+}
+
+cap_net_limit_t *
+cap_net_limit_init(cap_channel_t *chan, uint64_t mode)
+{
+ cap_net_limit_t *limit;
+
+ limit = calloc(1, sizeof(*limit));
+ if (limit != NULL) {
+ limit->cnl_mode = mode;
+ limit->cnl_chan = chan;
+ limit->cnl_addr2name = nvlist_create(0);
+ limit->cnl_name2addr = nvlist_create(0);
+ limit->cnl_connect = nvlist_create(0);
+ limit->cnl_bind = nvlist_create(0);
+ }
+
+ return (limit);
+}
+
+static void
+pack_limit(nvlist_t *lnvl, const char *name, nvlist_t *limit)
+{
+
+ if (!nvlist_empty(limit)) {
+ nvlist_move_nvlist(lnvl, name, limit);
+ } else {
+ nvlist_destroy(limit);
+ }
+}
+
+int
+cap_net_limit(cap_net_limit_t *limit)
+{
+ nvlist_t *lnvl;
+ cap_channel_t *chan;
+
+ lnvl = nvlist_create(0);
+ nvlist_add_number(lnvl, "mode", limit->cnl_mode);
+
+ pack_limit(lnvl, LIMIT_NV_ADDR2NAME, limit->cnl_addr2name);
+ pack_limit(lnvl, LIMIT_NV_NAME2ADDR, limit->cnl_name2addr);
+ pack_limit(lnvl, LIMIT_NV_CONNECT, limit->cnl_connect);
+ pack_limit(lnvl, LIMIT_NV_BIND, limit->cnl_bind);
+
+ chan = limit->cnl_chan;
+ free(limit);
+
+ return (cap_limit_set(chan, lnvl));
+}
+
+void
+cap_net_free(cap_net_limit_t *limit)
+{
+
+ if (limit == NULL)
+ return;
+
+ nvlist_destroy(limit->cnl_addr2name);
+ nvlist_destroy(limit->cnl_name2addr);
+ nvlist_destroy(limit->cnl_connect);
+ nvlist_destroy(limit->cnl_bind);
+
+ free(limit);
+}
+
+static void
+pack_family(nvlist_t *nvl, int *family, size_t size)
+{
+ size_t i;
+
+ i = 0;
+ if (!nvlist_exists_number_array(nvl, "family")) {
+ uint64_t val;
+
+ val = family[0];
+ nvlist_add_number_array(nvl, "family", &val, 1);
+ i += 1;
+ }
+
+ for (; i < size; i++) {
+ nvlist_append_number_array(nvl, "family", family[i]);
+ }
+}
+
+static void
+pack_sockaddr(nvlist_t *res, const struct sockaddr *sa, socklen_t salen)
+{
+ nvlist_t *nvl;
+
+ if (!nvlist_exists_nvlist(res, "sockaddr")) {
+ nvl = nvlist_create(NV_FLAG_NO_UNIQUE);
+ } else {
+ nvl = nvlist_take_nvlist(res, "sockaddr");
+ }
+
+ nvlist_add_binary(nvl, "", sa, salen);
+ nvlist_move_nvlist(res, "sockaddr", nvl);
+}
+
+cap_net_limit_t *
+cap_net_limit_addr2name_family(cap_net_limit_t *limit, int *family, size_t size)
+{
+
+ pack_family(limit->cnl_addr2name, family, size);
+ return (limit);
+}
+
+cap_net_limit_t *
+cap_net_limit_name2addr_family(cap_net_limit_t *limit, int *family, size_t size)
+{
+
+ pack_family(limit->cnl_name2addr, family, size);
+ return (limit);
+}
+
+cap_net_limit_t *
+cap_net_limit_name2addr(cap_net_limit_t *limit, const char *host,
+ const char *serv)
+{
+ nvlist_t *nvl;
+
+ if (!nvlist_exists_nvlist(limit->cnl_name2addr, "hosts")) {
+ nvl = nvlist_create(NV_FLAG_NO_UNIQUE);
+ } else {
+ nvl = nvlist_take_nvlist(limit->cnl_name2addr, "hosts");
+ }
+
+ nvlist_add_string(nvl,
+ host != NULL ? host : "",
+ serv != NULL ? serv : "");
+
+ nvlist_move_nvlist(limit->cnl_name2addr, "hosts", nvl);
+ return (limit);
+}
+
+cap_net_limit_t *
+cap_net_limit_addr2name(cap_net_limit_t *limit, const struct sockaddr *sa,
+ socklen_t salen)
+{
+
+ pack_sockaddr(limit->cnl_addr2name, sa, salen);
+ return (limit);
+}
+
+
+cap_net_limit_t *
+cap_net_limit_connect(cap_net_limit_t *limit, const struct sockaddr *sa,
+ socklen_t salen)
+{
+
+ pack_sockaddr(limit->cnl_connect, sa, salen);
+ return (limit);
+}
+
+cap_net_limit_t *
+cap_net_limit_bind(cap_net_limit_t *limit, const struct sockaddr *sa,
+ socklen_t salen)
+{
+
+ pack_sockaddr(limit->cnl_bind, sa, salen);
+ return (limit);
+}
+
+/*
+ * Service functions.
+ */
+
+static nvlist_t *capdnscache;
+
+static void
+net_add_sockaddr_to_cache(struct sockaddr *sa, socklen_t salen, bool deprecated)
+{
+ void *cookie;
+
+ if (capdnscache == NULL) {
+ capdnscache = nvlist_create(NV_FLAG_NO_UNIQUE);
+ } else {
+ /* Lets keep it clean. Look for dups. */
+ cookie = NULL;
+ while (nvlist_next(capdnscache, NULL, &cookie) != NULL) {
+ const void *data;
+ size_t size;
+
+ assert(cnvlist_type(cookie) == NV_TYPE_BINARY);
+
+ data = cnvlist_get_binary(cookie, &size);
+ if (salen != size)
+ continue;
+ if (memcmp(data, sa, size) == 0)
+ return;
+ }
+ }
+
+ nvlist_add_binary(capdnscache, deprecated ? "d" : "", sa, salen);
+}
+
+static void
+net_add_hostent_to_cache(const char *address, size_t asize, int family)
+{
+
+ if (family != AF_INET && family != AF_INET6)
+ return;
+
+ if (family == AF_INET6) {
+ struct sockaddr_in6 connaddr;
+
+ memset(&connaddr, 0, sizeof(connaddr));
+ connaddr.sin6_family = AF_INET6;
+ memcpy((char *)&connaddr.sin6_addr, address, asize);
+ connaddr.sin6_port = 0;
+
+ net_add_sockaddr_to_cache((struct sockaddr *)&connaddr,
+ sizeof(connaddr), true);
+ } else {
+ struct sockaddr_in connaddr;
+
+ memset(&connaddr, 0, sizeof(connaddr));
+ connaddr.sin_family = AF_INET;
+ memcpy((char *)&connaddr.sin_addr.s_addr, address, asize);
+ connaddr.sin_port = 0;
+
+ net_add_sockaddr_to_cache((struct sockaddr *)&connaddr,
+ sizeof(connaddr), true);
+ }
+}
+
+static bool
+net_allowed_mode(const nvlist_t *limits, uint64_t mode)
+{
+
+ if (limits == NULL)
+ return (true);
+
+ return ((nvlist_get_number(limits, "mode") & mode) == mode);
+}
+
+static bool
+net_allowed_family(const nvlist_t *limits, int family)
+{
+ const uint64_t *allowedfamily;
+ size_t i, allsize;
+
+ if (limits == NULL)
+ return (true);
+
+ /* If there are no familes at all, allow any mode. */
+ if (!nvlist_exists_number_array(limits, "family"))
+ return (true);
+
+ allowedfamily = nvlist_get_number_array(limits, "family", &allsize);
+ for (i = 0; i < allsize; i++) {
+ /* XXX: what with AF_UNSPEC? */
+ if (allowedfamily[i] == (uint64_t)family) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+static bool
+net_allowed_bsaddr_impl(const nvlist_t *salimits, const void *saddr,
+ size_t saddrsize)
+{
+ void *cookie;
+ const void *limit;
+ size_t limitsize;
+
+ cookie = NULL;
+ while (nvlist_next(salimits, NULL, &cookie) != NULL) {
+ limit = cnvlist_get_binary(cookie, &limitsize);
+
+ if (limitsize != saddrsize) {
+ continue;
+ }
+ if (memcmp(limit, saddr, limitsize) == 0) {
+ return (true);
+ }
+
+ /*
+ * In case of deprecated version (gethostbyname) we have to
+ * ignore port, because there is no such info in the hostent.
+ * Suporting only AF_INET and AF_INET6.
+ */
+ if (strcmp(cnvlist_name(cookie), "d") != 0 ||
+ (saddrsize != sizeof(struct sockaddr_in) &&
+ saddrsize != sizeof(struct sockaddr_in6))) {
+ continue;
+ }
+ if (saddrsize == sizeof(struct sockaddr_in)) {
+ const struct sockaddr_in *saddrptr;
+ struct sockaddr_in sockaddr;
+
+ saddrptr = (const struct sockaddr_in *)saddr;
+ memcpy(&sockaddr, limit, sizeof(sockaddr));
+ sockaddr.sin_port = saddrptr->sin_port;
+
+ if (memcmp(&sockaddr, saddr, saddrsize) == 0) {
+ return (true);
+ }
+ } else if (saddrsize == sizeof(struct sockaddr_in6)) {
+ const struct sockaddr_in6 *saddrptr;
+ struct sockaddr_in6 sockaddr;
+
+ saddrptr = (const struct sockaddr_in6 *)saddr;
+ memcpy(&sockaddr, limit, sizeof(sockaddr));
+ sockaddr.sin6_port = saddrptr->sin6_port;
+
+ if (memcmp(&sockaddr, saddr, saddrsize) == 0) {
+ return (true);
+ }
+ }
+ }
+
+ return (false);
+}
+
+static bool
+net_allowed_bsaddr(const nvlist_t *limits, const void *saddr, size_t saddrsize)
+{
+
+ if (limits == NULL)
+ return (true);
+
+ if (!nvlist_exists_nvlist(limits, "sockaddr"))
+ return (true);
+
+ return (net_allowed_bsaddr_impl(nvlist_get_nvlist(limits, "sockaddr"),
+ saddr, saddrsize));
+}
+
+static bool
+net_allowed_hosts(const nvlist_t *limits, const char *name, const char *srvname)
+{
+ void *cookie;
+ const nvlist_t *hlimits;
+ const char *testname, *testsrvname;
+
+ if (limits == NULL) {
+ return (true);
+ }
+
+ /* If there are no hosts at all, allow any. */
+ if (!nvlist_exists_nvlist(limits, "hosts")) {
+ return (true);
+ }
+
+ cookie = NULL;
+ testname = (name == NULL ? "" : name);
+ testsrvname = (srvname == NULL ? "" : srvname);
+ hlimits = nvlist_get_nvlist(limits, "hosts");
+ while (nvlist_next(hlimits, NULL, &cookie) != NULL) {
+ if (strcmp(cnvlist_name(cookie), "") != 0 &&
+ strcmp(cnvlist_name(cookie), testname) != 0) {
+ continue;
+ }
+
+ if (strcmp(cnvlist_get_string(cookie), "") != 0 &&
+ strcmp(cnvlist_get_string(cookie), testsrvname) != 0) {
+ continue;
+ }
+
+ return (true);
+ }
+
+ return (false);
+}
+
+static void
+hostent_pack(const struct hostent *hp, nvlist_t *nvl, bool addtocache)
+{
+ unsigned int ii;
+ char nvlname[64];
+ int n;
+
+ nvlist_add_string(nvl, "name", hp->h_name);
+ nvlist_add_number(nvl, "addrtype", (uint64_t)hp->h_addrtype);
+ nvlist_add_number(nvl, "length", (uint64_t)hp->h_length);
+
+ if (hp->h_aliases == NULL) {
+ nvlist_add_number(nvl, "naliases", 0);
+ } else {
+ for (ii = 0; hp->h_aliases[ii] != NULL; ii++) {
+ n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_add_string(nvl, nvlname, hp->h_aliases[ii]);
+ }
+ nvlist_add_number(nvl, "naliases", (uint64_t)ii);
+ }
+
+ if (hp->h_addr_list == NULL) {
+ nvlist_add_number(nvl, "naddrs", 0);
+ } else {
+ for (ii = 0; hp->h_addr_list[ii] != NULL; ii++) {
+ n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_add_binary(nvl, nvlname, hp->h_addr_list[ii],
+ (size_t)hp->h_length);
+ if (addtocache) {
+ net_add_hostent_to_cache(hp->h_addr_list[ii],
+ hp->h_length, hp->h_addrtype);
+ }
+ }
+ nvlist_add_number(nvl, "naddrs", (uint64_t)ii);
+ }
+}
+
+static int
+net_gethostbyname(const nvlist_t *limits, const nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ struct hostent *hp;
+ int family;
+ const nvlist_t *funclimit;
+ const char *name;
+ bool dnscache;
+
+ if (!net_allowed_mode(limits, CAPNET_DEPRECATED_NAME2ADDR))
+ return (ENOTCAPABLE);
+
+ dnscache = net_allowed_mode(limits, CAPNET_CONNECTDNS);
+ funclimit = NULL;
+ if (limits != NULL) {
+ funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_NAME2ADDR,
+ NULL);
+ }
+
+ family = (int)nvlist_get_number(nvlin, "family");
+ if (!net_allowed_family(funclimit, family))
+ return (ENOTCAPABLE);
+
+ name = nvlist_get_string(nvlin, "name");
+ if (!net_allowed_hosts(funclimit, name, ""))
+ return (ENOTCAPABLE);
+
+ hp = gethostbyname2(name, family);
+ if (hp == NULL)
+ return (h_errno);
+ hostent_pack(hp, nvlout, dnscache);
+ return (0);
+}
+
+static int
+net_gethostbyaddr(const nvlist_t *limits, const nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ struct hostent *hp;
+ const void *addr;
+ size_t addrsize;
+ int family;
+ const nvlist_t *funclimit;
+
+ if (!net_allowed_mode(limits, CAPNET_DEPRECATED_ADDR2NAME))
+ return (ENOTCAPABLE);
+
+ funclimit = NULL;
+ if (limits != NULL) {
+ funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_ADDR2NAME,
+ NULL);
+ }
+
+ family = (int)nvlist_get_number(nvlin, "family");
+ if (!net_allowed_family(funclimit, family))
+ return (ENOTCAPABLE);
+
+ addr = nvlist_get_binary(nvlin, "addr", &addrsize);
+ if (!net_allowed_bsaddr(funclimit, addr, addrsize))
+ return (ENOTCAPABLE);
+
+ hp = gethostbyaddr(addr, (socklen_t)addrsize, family);
+ if (hp == NULL)
+ return (h_errno);
+ hostent_pack(hp, nvlout, false);
+ return (0);
+}
+
+static int
+net_getnameinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
+{
+ struct sockaddr_storage sast;
+ const void *sabin;
+ char *host, *serv;
+ size_t sabinsize, hostlen, servlen;
+ socklen_t salen;
+ int error, serrno, flags;
+ const nvlist_t *funclimit;
+
+ host = serv = NULL;
+ if (!net_allowed_mode(limits, CAPNET_ADDR2NAME)) {
+ serrno = ENOTCAPABLE;
+ error = EAI_SYSTEM;
+ goto out;
+ }
+ funclimit = NULL;
+ if (limits != NULL) {
+ funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_ADDR2NAME,
+ NULL);
+ }
+ error = 0;
+ memset(&sast, 0, sizeof(sast));
+
+ hostlen = (size_t)nvlist_get_number(nvlin, "hostlen");
+ servlen = (size_t)nvlist_get_number(nvlin, "servlen");
+
+ if (hostlen > 0) {
+ host = calloc(1, hostlen);
+ if (host == NULL) {
+ error = EAI_MEMORY;
+ goto out;
+ }
+ }
+ if (servlen > 0) {
+ serv = calloc(1, servlen);
+ if (serv == NULL) {
+ error = EAI_MEMORY;
+ goto out;
+ }
+ }
+
+ sabin = nvlist_get_binary(nvlin, "sa", &sabinsize);
+ if (sabinsize > sizeof(sast)) {
+ error = EAI_FAIL;
+ goto out;
+ }
+ if (!net_allowed_bsaddr(funclimit, sabin, sabinsize)) {
+ serrno = ENOTCAPABLE;
+ error = EAI_SYSTEM;
+ goto out;
+ }
+
+ memcpy(&sast, sabin, sabinsize);
+ salen = (socklen_t)sabinsize;
+
+ if ((sast.ss_family != AF_INET ||
+ salen != sizeof(struct sockaddr_in)) &&
+ (sast.ss_family != AF_INET6 ||
+ salen != sizeof(struct sockaddr_in6))) {
+ error = EAI_FAIL;
+ goto out;
+ }
+
+ if (!net_allowed_family(funclimit, (int)sast.ss_family)) {
+ serrno = ENOTCAPABLE;
+ error = EAI_SYSTEM;
+ goto out;
+ }
+
+ flags = (int)nvlist_get_number(nvlin, "flags");
+
+ error = getnameinfo((struct sockaddr *)&sast, salen, host, hostlen,
+ serv, servlen, flags);
+ serrno = errno;
+ if (error != 0)
+ goto out;
+
+ if (host != NULL)
+ nvlist_move_string(nvlout, "host", host);
+ if (serv != NULL)
+ nvlist_move_string(nvlout, "serv", serv);
+out:
+ if (error != 0) {
+ free(host);
+ free(serv);
+ if (error == EAI_SYSTEM)
+ nvlist_add_number(nvlout, "errno", serrno);
+ }
+ return (error);
+}
+
+static nvlist_t *
+addrinfo_pack(const struct addrinfo *ai)
+{
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_number(nvl, "ai_flags", (uint64_t)ai->ai_flags);
+ nvlist_add_number(nvl, "ai_family", (uint64_t)ai->ai_family);
+ nvlist_add_number(nvl, "ai_socktype", (uint64_t)ai->ai_socktype);
+ nvlist_add_number(nvl, "ai_protocol", (uint64_t)ai->ai_protocol);
+ nvlist_add_binary(nvl, "ai_addr", ai->ai_addr, (size_t)ai->ai_addrlen);
+ if (ai->ai_canonname != NULL)
+ nvlist_add_string(nvl, "ai_canonname", ai->ai_canonname);
+
+ return (nvl);
+}
+
+static int
+net_getaddrinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
+{
+ struct addrinfo hints, *hintsp, *res, *cur;
+ const char *hostname, *servname;
+ char nvlname[64];
+ nvlist_t *elem;
+ unsigned int ii;
+ int error, serrno, family, n;
+ const nvlist_t *funclimit;
+ bool dnscache;
+
+ if (!net_allowed_mode(limits, CAPNET_NAME2ADDR)) {
+ serrno = ENOTCAPABLE;
+ error = EAI_SYSTEM;
+ goto out;
+ }
+ dnscache = net_allowed_mode(limits, CAPNET_CONNECTDNS);
+ funclimit = NULL;
+ if (limits != NULL) {
+ funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_NAME2ADDR,
+ NULL);
+ }
+
+ hostname = dnvlist_get_string(nvlin, "hostname", NULL);
+ servname = dnvlist_get_string(nvlin, "servname", NULL);
+ if (nvlist_exists_number(nvlin, "hints.ai_flags")) {
+ hints.ai_flags = (int)nvlist_get_number(nvlin,
+ "hints.ai_flags");
+ hints.ai_family = (int)nvlist_get_number(nvlin,
+ "hints.ai_family");
+ hints.ai_socktype = (int)nvlist_get_number(nvlin,
+ "hints.ai_socktype");
+ hints.ai_protocol = (int)nvlist_get_number(nvlin,
+ "hints.ai_protocol");
+ hints.ai_addrlen = 0;
+ hints.ai_addr = NULL;
+ hints.ai_canonname = NULL;
+ hints.ai_next = NULL;
+ hintsp = &hints;
+ family = hints.ai_family;
+ } else {
+ hintsp = NULL;
+ family = AF_UNSPEC;
+ }
+
+ if (!net_allowed_family(funclimit, family)) {
+ errno = ENOTCAPABLE;
+ error = EAI_SYSTEM;
+ goto out;
+ }
+ if (!net_allowed_hosts(funclimit, hostname, servname)) {
+ errno = ENOTCAPABLE;
+ error = EAI_SYSTEM;
+ goto out;
+ }
+ error = getaddrinfo(hostname, servname, hintsp, &res);
+ serrno = errno;
+ if (error != 0) {
+ goto out;
+ }
+
+ for (cur = res, ii = 0; cur != NULL; cur = cur->ai_next, ii++) {
+ elem = addrinfo_pack(cur);
+ n = snprintf(nvlname, sizeof(nvlname), "res%u", ii);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_move_nvlist(nvlout, nvlname, elem);
+ if (dnscache) {
+ net_add_sockaddr_to_cache(cur->ai_addr,
+ cur->ai_addrlen, false);
+ }
+ }
+
+ freeaddrinfo(res);
+ error = 0;
+out:
+ if (error == EAI_SYSTEM)
+ nvlist_add_number(nvlout, "errno", serrno);
+ return (error);
+}
+
+static int
+net_bind(const nvlist_t *limits, nvlist_t *nvlin, nvlist_t *nvlout)
+{
+ int socket, serrno;
+ const void *saddr;
+ size_t len;
+ const nvlist_t *funclimit;
+
+ if (!net_allowed_mode(limits, CAPNET_BIND))
+ return (ENOTCAPABLE);
+ funclimit = NULL;
+ if (limits != NULL)
+ funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_BIND, NULL);
+
+ saddr = nvlist_get_binary(nvlin, "saddr", &len);
+
+ if (!net_allowed_bsaddr(funclimit, saddr, len))
+ return (ENOTCAPABLE);
+
+ socket = nvlist_take_descriptor(nvlin, "s");
+ if (bind(socket, saddr, len) < 0) {
+ serrno = errno;
+ close(socket);
+ return (serrno);
+ }
+
+ nvlist_move_descriptor(nvlout, "s", socket);
+
+ return (0);
+}
+
+static int
+net_connect(const nvlist_t *limits, nvlist_t *nvlin, nvlist_t *nvlout)
+{
+ int socket, serrno;
+ const void *saddr;
+ const nvlist_t *funclimit;
+ size_t len;
+ bool conn, conndns, allowed;
+
+ conn = net_allowed_mode(limits, CAPNET_CONNECT);
+ conndns = net_allowed_mode(limits, CAPNET_CONNECTDNS);
+
+ if (!conn && !conndns)
+ return (ENOTCAPABLE);
+
+ funclimit = NULL;
+ if (limits != NULL)
+ funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_CONNECT, NULL);
+
+ saddr = nvlist_get_binary(nvlin, "saddr", &len);
+ allowed = false;
+
+ if (conn && net_allowed_bsaddr(funclimit, saddr, len)) {
+ allowed = true;
+ }
+ if (conndns && capdnscache != NULL &&
+ net_allowed_bsaddr_impl(capdnscache, saddr, len)) {
+ allowed = true;
+ }
+
+ if (allowed == false) {
+ return (ENOTCAPABLE);
+ }
+
+ socket = dup(nvlist_get_descriptor(nvlin, "s"));
+ if (connect(socket, saddr, len) < 0) {
+ serrno = errno;
+ close(socket);
+ return (serrno);
+ }
+
+ nvlist_move_descriptor(nvlout, "s", socket);
+
+ return (0);
+}
+
+static bool
+verify_only_sa_newlimts(const nvlist_t *oldfunclimits,
+ const nvlist_t *newfunclimit)
+{
+ void *cookie;
+
+ cookie = NULL;
+ while (nvlist_next(newfunclimit, NULL, &cookie) != NULL) {
+ void *sacookie;
+
+ if (strcmp(cnvlist_name(cookie), "sockaddr") != 0)
+ return (false);
+
+ if (cnvlist_type(cookie) != NV_TYPE_NVLIST)
+ return (false);
+
+ sacookie = NULL;
+ while (nvlist_next(cnvlist_get_nvlist(cookie), NULL,
+ &sacookie) != NULL) {
+ const void *sa;
+ size_t sasize;
+
+ if (cnvlist_type(sacookie) != NV_TYPE_BINARY)
+ return (false);
+
+ sa = cnvlist_get_binary(sacookie, &sasize);
+ if (!net_allowed_bsaddr(oldfunclimits, sa, sasize))
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+static bool
+verify_bind_newlimts(const nvlist_t *oldlimits,
+ const nvlist_t *newfunclimit)
+{
+ const nvlist_t *oldfunclimits;
+
+ oldfunclimits = NULL;
+ if (oldlimits != NULL) {
+ oldfunclimits = dnvlist_get_nvlist(oldlimits, LIMIT_NV_BIND,
+ NULL);
+ }
+
+ return (verify_only_sa_newlimts(oldfunclimits, newfunclimit));
+}
+
+
+static bool
+verify_connect_newlimits(const nvlist_t *oldlimits,
+ const nvlist_t *newfunclimit)
+{
+ const nvlist_t *oldfunclimits;
+
+ oldfunclimits = NULL;
+ if (oldlimits != NULL) {
+ oldfunclimits = dnvlist_get_nvlist(oldlimits, LIMIT_NV_CONNECT,
+ NULL);
+ }
+
+ return (verify_only_sa_newlimts(oldfunclimits, newfunclimit));
+}
+
+static bool
+verify_addr2name_newlimits(const nvlist_t *oldlimits,
+ const nvlist_t *newfunclimit)
+{
+ void *cookie;
+ const nvlist_t *oldfunclimits;
+
+ oldfunclimits = NULL;
+ if (oldlimits != NULL) {
+ oldfunclimits = dnvlist_get_nvlist(oldlimits,
+ LIMIT_NV_ADDR2NAME, NULL);
+ }
+
+ cookie = NULL;
+ while (nvlist_next(newfunclimit, NULL, &cookie) != NULL) {
+ if (strcmp(cnvlist_name(cookie), "sockaddr") == 0) {
+ void *sacookie;
+
+ if (cnvlist_type(cookie) != NV_TYPE_NVLIST)
+ return (false);
+
+ sacookie = NULL;
+ while (nvlist_next(cnvlist_get_nvlist(cookie), NULL,
+ &sacookie) != NULL) {
+ const void *sa;
+ size_t sasize;
+
+ if (cnvlist_type(sacookie) != NV_TYPE_BINARY)
+ return (false);
+
+ sa = cnvlist_get_binary(sacookie, &sasize);
+ if (!net_allowed_bsaddr(oldfunclimits, sa,
+ sasize)) {
+ return (false);
+ }
+ }
+ } else if (strcmp(cnvlist_name(cookie), "family") == 0) {
+ size_t i, sfamilies;
+ const uint64_t *families;
+
+ if (cnvlist_type(cookie) != NV_TYPE_NUMBER_ARRAY)
+ return (false);
+
+ families = cnvlist_get_number_array(cookie, &sfamilies);
+ for (i = 0; i < sfamilies; i++) {
+ if (!net_allowed_family(oldfunclimits,
+ families[i])) {
+ return (false);
+ }
+ }
+ } else {
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+static bool
+verify_name2addr_newlimits(const nvlist_t *oldlimits,
+ const nvlist_t *newfunclimit)
+{
+ void *cookie;
+ const nvlist_t *oldfunclimits;
+
+ oldfunclimits = NULL;
+ if (oldlimits != NULL) {
+ oldfunclimits = dnvlist_get_nvlist(oldlimits,
+ LIMIT_NV_NAME2ADDR, NULL);
+ }
+
+ cookie = NULL;
+ while (nvlist_next(newfunclimit, NULL, &cookie) != NULL) {
+ if (strcmp(cnvlist_name(cookie), "hosts") == 0) {
+ void *hostcookie;
+
+ if (cnvlist_type(cookie) != NV_TYPE_NVLIST)
+ return (false);
+
+ hostcookie = NULL;
+ while (nvlist_next(cnvlist_get_nvlist(cookie), NULL,
+ &hostcookie) != NULL) {
+ if (cnvlist_type(hostcookie) != NV_TYPE_STRING)
+ return (false);
+
+ if (!net_allowed_hosts(oldfunclimits,
+ cnvlist_name(hostcookie),
+ cnvlist_get_string(hostcookie))) {
+ return (false);
+ }
+ }
+ } else if (strcmp(cnvlist_name(cookie), "family") == 0) {
+ size_t i, sfamilies;
+ const uint64_t *families;
+
+ if (cnvlist_type(cookie) != NV_TYPE_NUMBER_ARRAY)
+ return (false);
+
+ families = cnvlist_get_number_array(cookie, &sfamilies);
+ for (i = 0; i < sfamilies; i++) {
+ if (!net_allowed_family(oldfunclimits,
+ families[i])) {
+ return (false);
+ }
+ }
+ } else {
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+static int
+net_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const char *name;
+ void *cookie;
+ bool hasmode, hasconnect, hasbind, hasaddr2name, hasname2addr;
+
+ /*
+ * Modes:
+ * ADDR2NAME:
+ * getnameinfo
+ * DEPRECATED_ADDR2NAME:
+ * gethostbyaddr
+ *
+ * NAME2ADDR:
+ * getaddrinfo
+ * DEPRECATED_NAME2ADDR:
+ * gethostbyname
+ *
+ * Limit scheme:
+ * mode : NV_TYPE_NUMBER
+ * connect : NV_TYPE_NVLIST
+ * sockaddr : NV_TYPE_NVLIST
+ * "" : NV_TYPE_BINARY
+ * ... : NV_TYPE_BINARY
+ * bind : NV_TYPE_NVLIST
+ * sockaddr : NV_TYPE_NVLIST
+ * "" : NV_TYPE_BINARY
+ * ... : NV_TYPE_BINARY
+ * addr2name : NV_TYPE_NVLIST
+ * family : NV_TYPE_NUMBER_ARRAY
+ * sockaddr : NV_TYPE_NVLIST
+ * "" : NV_TYPE_BINARY
+ * ... : NV_TYPE_BINARY
+ * name2addr : NV_TYPE_NVLIST
+ * family : NV_TYPE_NUMBER
+ * hosts : NV_TYPE_NVLIST
+ * host : servname : NV_TYPE_STRING
+ */
+
+ hasmode = false;
+ hasconnect = false;
+ hasbind = false;
+ hasaddr2name = false;
+ hasname2addr = false;
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, NULL, &cookie)) != NULL) {
+ if (strcmp(name, "mode") == 0) {
+ if (cnvlist_type(cookie) != NV_TYPE_NUMBER) {
+ return (NO_RECOVERY);
+ }
+ if (!net_allowed_mode(oldlimits,
+ cnvlist_get_number(cookie))) {
+ return (ENOTCAPABLE);
+ }
+ hasmode = true;
+ continue;
+ }
+
+ if (cnvlist_type(cookie) != NV_TYPE_NVLIST) {
+ return (NO_RECOVERY);
+ }
+
+ if (strcmp(name, LIMIT_NV_BIND) == 0) {
+ hasbind = true;
+ if (!verify_bind_newlimts(oldlimits,
+ cnvlist_get_nvlist(cookie))) {
+ return (ENOTCAPABLE);
+ }
+ } else if (strcmp(name, LIMIT_NV_CONNECT) == 0) {
+ hasconnect = true;
+ if (!verify_connect_newlimits(oldlimits,
+ cnvlist_get_nvlist(cookie))) {
+ return (ENOTCAPABLE);
+ }
+ } else if (strcmp(name, LIMIT_NV_ADDR2NAME) == 0) {
+ hasaddr2name = true;
+ if (!verify_addr2name_newlimits(oldlimits,
+ cnvlist_get_nvlist(cookie))) {
+ return (ENOTCAPABLE);
+ }
+ } else if (strcmp(name, LIMIT_NV_NAME2ADDR) == 0) {
+ hasname2addr = true;
+ if (!verify_name2addr_newlimits(oldlimits,
+ cnvlist_get_nvlist(cookie))) {
+ return (ENOTCAPABLE);
+ }
+ }
+ }
+
+ /* Mode is required. */
+ if (!hasmode)
+ return (ENOTCAPABLE);
+
+ /*
+ * If the new limit doesn't mention mode or family we have to
+ * check if the current limit does have those. Missing mode or
+ * family in the limit means that all modes or families are
+ * allowed.
+ */
+ if (oldlimits == NULL)
+ return (0);
+ if (!hasbind && nvlist_exists(oldlimits, LIMIT_NV_BIND))
+ return (ENOTCAPABLE);
+ if (!hasconnect && nvlist_exists(oldlimits, LIMIT_NV_CONNECT))
+ return (ENOTCAPABLE);
+ if (!hasaddr2name && nvlist_exists(oldlimits, LIMIT_NV_ADDR2NAME))
+ return (ENOTCAPABLE);
+ if (!hasname2addr && nvlist_exists(oldlimits, LIMIT_NV_NAME2ADDR))
+ return (ENOTCAPABLE);
+ return (0);
+}
+
+static int
+net_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+
+ if (strcmp(cmd, "bind") == 0)
+ return (net_bind(limits, nvlin, nvlout));
+ else if (strcmp(cmd, "connect") == 0)
+ return (net_connect(limits, nvlin, nvlout));
+ else if (strcmp(cmd, "gethostbyname") == 0)
+ return (net_gethostbyname(limits, nvlin, nvlout));
+ else if (strcmp(cmd, "gethostbyaddr") == 0)
+ return (net_gethostbyaddr(limits, nvlin, nvlout));
+ else if (strcmp(cmd, "getnameinfo") == 0)
+ return (net_getnameinfo(limits, nvlin, nvlout));
+ else if (strcmp(cmd, "getaddrinfo") == 0)
+ return (net_getaddrinfo(limits, nvlin, nvlout));
+
+ return (EINVAL);
+}
+
+CREATE_SERVICE("system.net", net_limit, net_command, 0);
diff --git a/lib/libcasper/services/cap_net/cap_net.h b/lib/libcasper/services/cap_net/cap_net.h
new file mode 100644
index 000000000000..75f936954199
--- /dev/null
+++ b/lib/libcasper/services/cap_net/cap_net.h
@@ -0,0 +1,159 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _CAP_NETWORK_H_
+#define _CAP_NETWORK_H_
+
+#ifdef HAVE_CASPER
+#define WITH_CASPER
+#endif
+
+#include <sys/dnv.h>
+#include <sys/nv.h>
+
+#include <sys/socket.h>
+
+struct addrinfo;
+struct hostent;
+
+struct cap_net_limit;
+typedef struct cap_net_limit cap_net_limit_t;
+
+#define CAPNET_ADDR2NAME (0x01)
+#define CAPNET_NAME2ADDR (0x02)
+#define CAPNET_DEPRECATED_ADDR2NAME (0x04)
+#define CAPNET_DEPRECATED_NAME2ADDR (0x08)
+#define CAPNET_CONNECT (0x10)
+#define CAPNET_BIND (0x20)
+#define CAPNET_CONNECTDNS (0x40)
+
+#ifdef WITH_CASPER
+/* Capability functions. */
+int cap_bind(cap_channel_t *chan, int s, const struct sockaddr *addr,
+ socklen_t addrlen);
+int cap_connect(cap_channel_t *chan, int s, const struct sockaddr *name,
+ socklen_t namelen);
+
+int cap_getaddrinfo(cap_channel_t *chan, const char *hostname,
+ const char *servname, const struct addrinfo *hints, struct addrinfo **res);
+int cap_getnameinfo(cap_channel_t *chan, const struct sockaddr *sa,
+ socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen,
+ int flags);
+
+/* Limit functions. */
+cap_net_limit_t *cap_net_limit_init(cap_channel_t *chan, uint64_t mode);
+int cap_net_limit(cap_net_limit_t *limit);
+void cap_net_free(cap_net_limit_t *limit);
+
+cap_net_limit_t *cap_net_limit_addr2name_family(cap_net_limit_t *limit,
+ int *family, size_t size);
+cap_net_limit_t *cap_net_limit_addr2name(cap_net_limit_t *limit,
+ const struct sockaddr *sa, socklen_t salen);
+
+cap_net_limit_t *cap_net_limit_name2addr_family(cap_net_limit_t *limit,
+ int *family, size_t size);
+cap_net_limit_t *cap_net_limit_name2addr(cap_net_limit_t *limit,
+ const char *name, const char *serv);
+
+cap_net_limit_t *cap_net_limit_connect(cap_net_limit_t *limit,
+ const struct sockaddr *sa, socklen_t salen);
+
+cap_net_limit_t *cap_net_limit_bind(cap_net_limit_t *limit,
+ const struct sockaddr *sa, socklen_t salen);
+
+/* Deprecated functions. */
+struct hostent *cap_gethostbyname(cap_channel_t *chan, const char *name);
+struct hostent *cap_gethostbyname2(cap_channel_t *chan, const char *name,
+ int af);
+struct hostent *cap_gethostbyaddr(cap_channel_t *chan, const void *addr,
+ socklen_t len, int af);
+#else
+/* Capability functions. */
+#define cap_bind(chan, ...) bind(__VA_ARGS__)
+#define cap_connect(chan, ...) connect(__VA_ARGS__)
+#define cap_getaddrinfo(chan, ...) getaddrinfo(__VA_ARGS__)
+#define cap_getnameinfo(chan, ...) getnameinfo(__VA_ARGS__)
+
+/* Limit functions. */
+#define cap_net_limit_init(chan, ...) ((cap_net_limit_t *)malloc(8))
+#define cap_net_free(...) free(__VA_ARGS__)
+static inline int
+cap_net_limit(cap_net_limit_t *limit)
+{
+ free(limit);
+ return (0);
+}
+
+static inline cap_net_limit_t *
+cap_net_limit_addr2name_family(cap_net_limit_t *limit,
+ int *family __unused, size_t size __unused)
+{
+ return (limit);
+}
+
+static inline cap_net_limit_t *
+cap_net_limit_addr2name(cap_net_limit_t *limit,
+ const struct sockaddr *sa __unused, socklen_t salen __unused)
+{
+ return (limit);
+}
+
+static inline cap_net_limit_t *
+cap_net_limit_name2addr_family(cap_net_limit_t *limit,
+ int *family __unused, size_t size __unused)
+{
+ return (limit);
+}
+
+static inline cap_net_limit_t *
+cap_net_limit_name2addr(cap_net_limit_t *limit,
+ const char *name __unused, const char *serv __unused)
+{
+ return (limit);
+}
+
+static inline cap_net_limit_t *
+cap_net_limit_connect(cap_net_limit_t *limit,
+ const struct sockaddr *sa __unused, socklen_t salen __unused)
+{
+ return (limit);
+}
+
+static inline cap_net_limit_t *
+cap_net_limit_bind(cap_net_limit_t *limit,
+ const struct sockaddr *sa __unused, socklen_t salen __unused)
+{
+ return (limit);
+}
+
+/* Deprecated functions. */
+#define cap_gethostbyname(chan, ...) gethostbyname(__VA_ARGS__)
+#define cap_gethostbyname2(chan, ...) gethostbyname2(__VA_ARGS__)
+#define cap_gethostbyaddr(chan, ...) gethostbyaddr(__VA_ARGS__)
+#endif
+
+#endif /* !_CAP_NETWORK_H_ */
diff --git a/lib/libcasper/services/cap_net/tests/Makefile b/lib/libcasper/services/cap_net/tests/Makefile
new file mode 100644
index 000000000000..5798d7385440
--- /dev/null
+++ b/lib/libcasper/services/cap_net/tests/Makefile
@@ -0,0 +1,14 @@
+.include <src.opts.mk>
+
+ATF_TESTS_C= net_test
+
+.if ${MK_CASPER} != "no"
+LIBADD+= casper
+LIBADD+= cap_net
+CFLAGS+=-DWITH_CASPER
+.endif
+LIBADD+= nv
+
+WARNS?= 3
+
+.include <bsd.test.mk>
diff --git a/lib/libcasper/services/cap_net/tests/net_test.c b/lib/libcasper/services/cap_net/tests/net_test.c
new file mode 100644
index 000000000000..adf5773233c8
--- /dev/null
+++ b/lib/libcasper/services/cap_net/tests/net_test.c
@@ -0,0 +1,1487 @@
+/*-
+ * Copyright (c) 2020 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <netdb.h>
+
+#include <atf-c.h>
+
+#include <libcasper.h>
+#include <casper/cap_net.h>
+
+#define TEST_DOMAIN_0 "example.com"
+#define TEST_DOMAIN_1 "freebsd.org"
+#define TEST_IPV4 "1.1.1.1"
+#define TEST_IPV6 "2001:4860:4860::8888"
+#define TEST_BIND_IPV4 "127.0.0.1"
+#define TEST_PORT 80
+#define TEST_PORT_STR "80"
+
+static cap_channel_t *
+create_network_service(void)
+{
+ cap_channel_t *capcas, *capnet;
+
+ capcas = cap_init();
+ ATF_REQUIRE(capcas != NULL);
+
+ capnet = cap_service_open(capcas, "system.net");
+ ATF_REQUIRE(capnet != NULL);
+
+ cap_close(capcas);
+ return (capnet);
+}
+
+static int
+test_getnameinfo_v4(cap_channel_t *chan, int family, const char *ip)
+{
+ struct sockaddr_in ipaddr;
+ char capfn[MAXHOSTNAMELEN];
+ char origfn[MAXHOSTNAMELEN];
+ int capret, sysret;
+
+ memset(&ipaddr, 0, sizeof(ipaddr));
+ ipaddr.sin_family = family;
+ inet_pton(family, ip, &ipaddr.sin_addr);
+
+ capret = cap_getnameinfo(chan, (struct sockaddr *)&ipaddr, sizeof(ipaddr),
+ capfn, sizeof(capfn), NULL, 0, NI_NAMEREQD);
+ if (capret != 0 && capret == ENOTCAPABLE)
+ return (ENOTCAPABLE);
+
+ sysret = getnameinfo((struct sockaddr *)&ipaddr, sizeof(ipaddr), origfn,
+ sizeof(origfn), NULL, 0, NI_NAMEREQD);
+ if (sysret != 0) {
+ atf_tc_skip("getnameinfo(%s) failed: %s",
+ ip, gai_strerror(sysret));
+ }
+ ATF_REQUIRE(capret == 0);
+ ATF_REQUIRE(strcmp(origfn, capfn) == 0);
+
+ return (0);
+}
+
+static int
+test_getnameinfo_v6(cap_channel_t *chan, const char *ip)
+{
+ struct sockaddr_in6 ipaddr;
+ char capfn[MAXHOSTNAMELEN];
+ char origfn[MAXHOSTNAMELEN];
+ int capret, sysret;
+
+ memset(&ipaddr, 0, sizeof(ipaddr));
+ ipaddr.sin6_family = AF_INET6;
+ inet_pton(AF_INET6, ip, &ipaddr.sin6_addr);
+
+ capret = cap_getnameinfo(chan, (struct sockaddr *)&ipaddr, sizeof(ipaddr),
+ capfn, sizeof(capfn), NULL, 0, NI_NAMEREQD);
+ if (capret != 0 && capret == ENOTCAPABLE)
+ return (ENOTCAPABLE);
+
+ sysret = getnameinfo((struct sockaddr *)&ipaddr, sizeof(ipaddr), origfn,
+ sizeof(origfn), NULL, 0, NI_NAMEREQD);
+ if (sysret != 0) {
+ atf_tc_skip("getnameinfo(%s) failed: %s",
+ ip, gai_strerror(sysret));
+ }
+ ATF_REQUIRE(capret == 0);
+ ATF_REQUIRE(strcmp(origfn, capfn) == 0);
+
+ return (0);
+}
+
+static int
+test_getnameinfo(cap_channel_t *chan, int family, const char *ip)
+{
+
+ if (family == AF_INET6) {
+ return (test_getnameinfo_v6(chan, ip));
+ }
+
+ return (test_getnameinfo_v4(chan, family, ip));
+}
+
+static int
+test_gethostbyaddr_v4(cap_channel_t *chan, int family, const char *ip)
+{
+ struct in_addr ipaddr;
+ struct hostent *caphp, *orighp;
+
+ memset(&ipaddr, 0, sizeof(ipaddr));
+ inet_pton(AF_INET, ip, &ipaddr);
+
+ caphp = cap_gethostbyaddr(chan, &ipaddr, sizeof(ipaddr), family);
+ if (caphp == NULL && h_errno == ENOTCAPABLE)
+ return (ENOTCAPABLE);
+
+ orighp = gethostbyaddr(&ipaddr, sizeof(ipaddr), family);
+ if (orighp == NULL)
+ atf_tc_skip("gethostbyaddr(%s) failed", ip);
+ ATF_REQUIRE(caphp != NULL);
+ ATF_REQUIRE(strcmp(orighp->h_name, caphp->h_name) == 0);
+
+ return (0);
+}
+
+static int
+test_gethostbyaddr_v6(cap_channel_t *chan, const char *ip)
+{
+ struct in6_addr ipaddr;
+ struct hostent *caphp, *orighp;
+
+ memset(&ipaddr, 0, sizeof(ipaddr));
+ inet_pton(AF_INET6, ip, &ipaddr);
+
+ caphp = cap_gethostbyaddr(chan, &ipaddr, sizeof(ipaddr), AF_INET6);
+ if (caphp == NULL && h_errno == ENOTCAPABLE)
+ return (ENOTCAPABLE);
+
+ orighp = gethostbyaddr(&ipaddr, sizeof(ipaddr), AF_INET6);
+ if (orighp == NULL)
+ atf_tc_skip("gethostbyaddr(%s) failed", ip);
+ ATF_REQUIRE(caphp != NULL);
+ ATF_REQUIRE(strcmp(orighp->h_name, caphp->h_name) == 0);
+
+ return (0);
+}
+
+static int
+test_gethostbyaddr(cap_channel_t *chan, int family, const char *ip)
+{
+
+ if (family == AF_INET6) {
+ return (test_gethostbyaddr_v6(chan, ip));
+ } else {
+ return (test_gethostbyaddr_v4(chan, family, ip));
+ }
+}
+
+static int
+test_getaddrinfo(cap_channel_t *chan, int family, const char *domain,
+ const char *servname)
+{
+ struct addrinfo hints, *capres, *origres, *res0, *res1;
+ bool found;
+ int capret, sysret;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+
+ capret = cap_getaddrinfo(chan, domain, servname, &hints, &capres);
+ if (capret != 0 && capret == ENOTCAPABLE)
+ return (capret);
+
+ sysret = getaddrinfo(domain, servname, &hints, &origres);
+ if (sysret != 0)
+ atf_tc_skip("getaddrinfo(%s) failed: %s",
+ domain, gai_strerror(sysret));
+ ATF_REQUIRE(capret == 0);
+
+ for (res0 = capres; res0 != NULL; res0 = res0->ai_next) {
+ found = false;
+ for (res1 = origres; res1 != NULL; res1 = res1->ai_next) {
+ if (res1->ai_addrlen == res0->ai_addrlen &&
+ memcmp(res1->ai_addr, res0->ai_addr,
+ res0->ai_addrlen) == 0) {
+ found = true;
+ break;
+ }
+ }
+ ATF_REQUIRE(found);
+ }
+
+ freeaddrinfo(capres);
+ freeaddrinfo(origres);
+ return (0);
+}
+
+static int
+test_gethostbyname(cap_channel_t *chan, int family, const char *domain)
+{
+ struct hostent *caphp, *orighp;
+
+ caphp = cap_gethostbyname2(chan, domain, family);
+ if (caphp == NULL && h_errno == ENOTCAPABLE)
+ return (h_errno);
+
+ orighp = gethostbyname2(domain, family);
+ if (orighp == NULL)
+ atf_tc_skip("gethostbyname2(%s) failed", domain);
+
+ ATF_REQUIRE(caphp != NULL);
+ ATF_REQUIRE(strcmp(caphp->h_name, orighp->h_name) == 0);
+ return (0);
+}
+
+static int
+test_bind(cap_channel_t *chan, const char *ip)
+{
+ struct sockaddr_in ipv4;
+ int capfd, ret, serrno;
+
+ capfd = socket(AF_INET, SOCK_STREAM, 0);
+ ATF_REQUIRE(capfd > 0);
+
+ memset(&ipv4, 0, sizeof(ipv4));
+ ipv4.sin_family = AF_INET;
+ inet_pton(AF_INET, ip, &ipv4.sin_addr);
+
+ ret = cap_bind(chan, capfd, (struct sockaddr *)&ipv4, sizeof(ipv4));
+ serrno = errno;
+ close(capfd);
+
+ return (ret < 0 ? serrno : 0);
+}
+
+static int
+test_connect(cap_channel_t *chan, const char *ip, unsigned short port)
+{
+ struct sockaddr_in ipv4;
+ int capfd, ret, serrno;
+
+ capfd = socket(AF_INET, SOCK_STREAM, 0);
+ ATF_REQUIRE(capfd >= 0);
+
+ memset(&ipv4, 0, sizeof(ipv4));
+ ipv4.sin_family = AF_INET;
+ ipv4.sin_port = htons(port);
+ inet_pton(AF_INET, ip, &ipv4.sin_addr);
+
+ ret = cap_connect(chan, capfd, (struct sockaddr *)&ipv4, sizeof(ipv4));
+ serrno = errno;
+ ATF_REQUIRE(close(capfd) == 0);
+
+ if (ret < 0 && serrno != ENOTCAPABLE) {
+ int sd;
+
+ /*
+ * If the connection failed, it might be because we can't reach
+ * the destination host. To check, try a plain connect() and
+ * see if it fails with the same error.
+ */
+ sd = socket(AF_INET, SOCK_STREAM, 0);
+ ATF_REQUIRE(sd >= 0);
+
+ memset(&ipv4, 0, sizeof(ipv4));
+ ipv4.sin_family = AF_INET;
+ ipv4.sin_port = htons(port);
+ inet_pton(AF_INET, ip, &ipv4.sin_addr);
+ ret = connect(sd, (struct sockaddr *)&ipv4, sizeof(ipv4));
+ ATF_REQUIRE(ret < 0);
+ ATF_REQUIRE_MSG(errno == serrno, "errno %d != serrno %d",
+ errno, serrno);
+ ATF_REQUIRE(close(sd) == 0);
+ atf_tc_skip("connect(%s:%d) failed: %s",
+ ip, port, strerror(serrno));
+ }
+
+ return (ret < 0 ? serrno : 0);
+}
+
+static void
+test_extend_mode(cap_channel_t *capnet, int current)
+{
+ cap_net_limit_t *limit;
+ const int rights[] = {
+ CAPNET_ADDR2NAME,
+ CAPNET_NAME2ADDR,
+ CAPNET_DEPRECATED_ADDR2NAME,
+ CAPNET_DEPRECATED_NAME2ADDR,
+ CAPNET_CONNECT,
+ CAPNET_BIND,
+ CAPNET_CONNECTDNS
+ };
+ size_t i;
+
+ for (i = 0; i < nitems(rights); i++) {
+ if (current == rights[i])
+ continue;
+
+ limit = cap_net_limit_init(capnet, current | rights[i]);
+ ATF_REQUIRE(limit != NULL);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+ }
+}
+
+ATF_TC(capnet__getnameinfo);
+ATF_TC_HEAD(capnet__getnameinfo, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__getnameinfo, tc)
+{
+ cap_channel_t *capnet;
+
+ capnet = create_network_service();
+
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) == 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__connect);
+ATF_TC_HEAD(capnet__connect, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__connect, tc)
+{
+ cap_channel_t *capnet;
+
+ capnet = create_network_service();
+
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__bind);
+ATF_TC_HEAD(capnet__bind, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__bind, tc)
+{
+ cap_channel_t *capnet;
+
+ capnet = create_network_service();
+
+ ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__getaddrinfo);
+ATF_TC_HEAD(capnet__getaddrinfo, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__getaddrinfo, tc)
+{
+ cap_channel_t *capnet;
+ struct addrinfo hints, *capres;
+
+ capnet = create_network_service();
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ ATF_REQUIRE(cap_getaddrinfo(capnet, TEST_IPV4, "80", &hints, &capres) ==
+ 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__gethostbyname);
+ATF_TC_HEAD(capnet__gethostbyname, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__gethostbyname, tc)
+{
+ cap_channel_t *capnet;
+
+ capnet = create_network_service();
+
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__gethostbyaddr);
+ATF_TC_HEAD(capnet__gethostbyaddr, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__gethostbyaddr, tc)
+{
+ cap_channel_t *capnet;
+
+ capnet = create_network_service();
+
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) == 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__getnameinfo_buffer);
+ATF_TC_HEAD(capnet__getnameinfo_buffer, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__getnameinfo_buffer, tc)
+{
+ cap_channel_t *chan;
+ struct sockaddr_in sin;
+ int ret;
+ struct {
+ char host[sizeof(TEST_IPV4)];
+ char host_canary;
+ char serv[sizeof(TEST_PORT_STR)];
+ char serv_canary;
+ } buffers;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(TEST_PORT);
+ ret = inet_pton(AF_INET, TEST_IPV4, &sin.sin_addr);
+ ATF_REQUIRE_EQ(1, ret);
+
+ memset(&buffers, '!', sizeof(buffers));
+
+ chan = create_network_service();
+ ret = cap_getnameinfo(chan, (struct sockaddr *)&sin, sizeof(sin),
+ buffers.host, sizeof(buffers.host),
+ buffers.serv, sizeof(buffers.serv),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ ATF_REQUIRE_EQ_MSG(0, ret, "%d", ret);
+
+ // Verify that cap_getnameinfo worked with minimally sized buffers.
+ ATF_CHECK_EQ(0, strcmp(TEST_IPV4, buffers.host));
+ ATF_CHECK_EQ(0, strcmp(TEST_PORT_STR, buffers.serv));
+
+ // Verify that cap_getnameinfo did not overflow the buffers.
+ ATF_CHECK_EQ('!', buffers.host_canary);
+ ATF_CHECK_EQ('!', buffers.serv_canary);
+
+ cap_close(chan);
+}
+
+ATF_TC(capnet__limits_addr2name_mode);
+ATF_TC_HEAD(capnet__limits_addr2name_mode, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_addr2name_mode, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+
+ capnet = create_network_service();
+
+ /* LIMIT */
+ limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ /* ALLOWED */
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE);
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == ENOTCAPABLE);
+
+ test_extend_mode(capnet, CAPNET_ADDR2NAME);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_addr2name_family);
+ATF_TC_HEAD(capnet__limits_addr2name_family, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_addr2name_family, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+ int family[] = { AF_INET6, AF_INET };
+
+ capnet = create_network_service();
+
+ /* Limit to AF_INET6 and AF_INET. */
+ limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_addr2name_family(limit, family, nitems(family));
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) == 0);
+
+ /* Limit to AF_INET6 and AF_INET. */
+ limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_addr2name_family(limit, &family[0], 1);
+ cap_net_limit_addr2name_family(limit, &family[1], 1);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) == 0);
+
+ /* Limit to AF_INET6. */
+ limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_addr2name_family(limit, family, 1);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) == 0);
+
+ /* Unable to set empty limits. Empty limits means full access. */
+ limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_addr2name);
+ATF_TC_HEAD(capnet__limits_addr2name, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_addr2name, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+ struct sockaddr_in ipaddrv4;
+ struct sockaddr_in6 ipaddrv6;
+
+ capnet = create_network_service();
+
+ /* Limit to TEST_IPV4 and TEST_IPV6. */
+ memset(&ipaddrv4, 0, sizeof(ipaddrv4));
+ memset(&ipaddrv6, 0, sizeof(ipaddrv6));
+
+ ipaddrv4.sin_family = AF_INET;
+ inet_pton(AF_INET, TEST_IPV4, &ipaddrv4.sin_addr);
+
+ ipaddrv6.sin6_family = AF_INET6;
+ inet_pton(AF_INET6, TEST_IPV6, &ipaddrv6.sin6_addr);
+
+ limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+
+ cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv4,
+ sizeof(ipaddrv4));
+ cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv6,
+ sizeof(ipaddrv6));
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) == 0);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, "127.0.0.1") ==
+ ENOTCAPABLE);
+
+ /* Limit to AF_INET. */
+ limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv4,
+ sizeof(ipaddrv4));
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, "127.0.0.1") ==
+ ENOTCAPABLE);
+
+ /* Unable to set empty limits. Empty limits means full access. */
+ limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_deprecated_addr2name_mode);
+ATF_TC_HEAD(capnet__limits_deprecated_addr2name_mode, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_deprecated_addr2name_mode, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+
+ capnet = create_network_service();
+
+ /* LIMIT */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ /* ALLOWED */
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE);
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == ENOTCAPABLE);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_deprecated_addr2name_family);
+ATF_TC_HEAD(capnet__limits_deprecated_addr2name_family, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_deprecated_addr2name_family, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+ int family[] = { AF_INET6, AF_INET };
+
+ capnet = create_network_service();
+
+ /* Limit to AF_INET6 and AF_INET. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_addr2name_family(limit, family, nitems(family));
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) == 0);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, PF_LINK, TEST_IPV4) ==
+ ENOTCAPABLE);
+
+ /* Limit to AF_INET6 and AF_INET. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_addr2name_family(limit, &family[0], 1);
+ cap_net_limit_addr2name_family(limit, &family[1], 1);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) == 0);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, PF_LINK, TEST_IPV4) ==
+ ENOTCAPABLE);
+
+ /* Limit to AF_INET6. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_addr2name_family(limit, family, 1);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) == 0);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, PF_LINK, TEST_IPV4) ==
+ ENOTCAPABLE);
+
+ /* Unable to set empty limits. Empty limits means full access. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_deprecated_addr2name);
+ATF_TC_HEAD(capnet__limits_deprecated_addr2name, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_deprecated_addr2name, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+ struct in_addr ipaddrv4;
+ struct in6_addr ipaddrv6;
+
+ capnet = create_network_service();
+
+ /* Limit to TEST_IPV4 and TEST_IPV6. */
+ memset(&ipaddrv4, 0, sizeof(ipaddrv4));
+ memset(&ipaddrv6, 0, sizeof(ipaddrv6));
+
+ inet_pton(AF_INET, TEST_IPV4, &ipaddrv4);
+ inet_pton(AF_INET6, TEST_IPV6, &ipaddrv6);
+
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+
+ cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv4,
+ sizeof(ipaddrv4));
+ cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv6,
+ sizeof(ipaddrv6));
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) == 0);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, "127.0.0.1") ==
+ ENOTCAPABLE);
+
+ /* Limit to AF_INET. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv4,
+ sizeof(ipaddrv4));
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, "127.0.0.1") ==
+ ENOTCAPABLE);
+
+ /* Unable to set empty limits. Empty limits means full access. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ cap_close(capnet);
+}
+
+
+ATF_TC(capnet__limits_name2addr_mode);
+ATF_TC_HEAD(capnet__limits_name2addr_mode, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_name2addr_mode, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+
+ capnet = create_network_service();
+
+ /* LIMIT */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ /* ALLOWED */
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(
+ test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == ENOTCAPABLE);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE);
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == ENOTCAPABLE);
+
+ test_extend_mode(capnet, CAPNET_ADDR2NAME);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_name2addr_hosts);
+ATF_TC_HEAD(capnet__limits_name2addr_hosts, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_name2addr_hosts, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+
+ capnet = create_network_service();
+
+ /* Limit to TEST_DOMAIN_0 and localhost only. */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ cap_net_limit_name2addr(limit, "localhost", NULL);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, "localhost", NULL) == 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, NULL) ==
+ ENOTCAPABLE);
+
+ /* Limit to TEST_DOMAIN_0 only. */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, "localhost", NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ 0);
+
+ /* Unable to set empty limits. Empty limits means full access. */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ /* Try to extend the limit. */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_1, NULL);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_1, NULL);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_name2addr_hosts_servnames_strict);
+ATF_TC_HEAD(capnet__limits_name2addr_hosts_servnames_strict, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_name2addr_hosts_servnames_strict, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+
+ capnet = create_network_service();
+
+ /*
+ * Limit to TEST_DOMAIN_0 and HTTP service.
+ */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, "http");
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, "http") ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, "snmp") ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, "http") ==
+ ENOTCAPABLE);
+
+ /* Unable to set empty limits. Empty limits means full access. */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_name2addr_hosts_servnames_mix);
+ATF_TC_HEAD(capnet__limits_name2addr_hosts_servnames_mix, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_name2addr_hosts_servnames_mix, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+
+ capnet = create_network_service();
+
+ /*
+ * Limit to TEST_DOMAIN_0 and any servnamex, and any domain with
+ * servname HTTP.
+ */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ cap_net_limit_name2addr(limit, NULL, "http");
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, "http") ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, "http") ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, "snmp") ==
+ ENOTCAPABLE);
+
+ /* Limit to HTTP servname only. */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, NULL, "http");
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, "http") ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, "http") ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, "snmp") ==
+ ENOTCAPABLE);
+
+ /* Unable to set empty limits. Empty limits means full access. */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_name2addr_family);
+ATF_TC_HEAD(capnet__limits_name2addr_family, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_name2addr_family, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+ int family[] = { AF_INET6, AF_INET };
+
+ capnet = create_network_service();
+
+ /* Limit to AF_INET and AF_INET6. */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ cap_net_limit_name2addr_family(limit, family, nitems(family));
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET6, TEST_DOMAIN_0, NULL) ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, PF_LINK, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+
+ /* Limit to AF_INET and AF_INET6. */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ cap_net_limit_name2addr_family(limit, &family[0], 1);
+ cap_net_limit_name2addr_family(limit, &family[1], 1);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET6, TEST_DOMAIN_0, NULL) ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, PF_LINK, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+
+ /* Limit to AF_INET6 only. */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ cap_net_limit_name2addr_family(limit, family, 1);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET6, TEST_DOMAIN_0, NULL) ==
+ 0);
+ ATF_REQUIRE(test_getaddrinfo(capnet, PF_LINK, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+
+ /* Unable to set empty limits. Empty limits means full access. */
+ limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_deprecated_name2addr_mode);
+ATF_TC_HEAD(capnet__limits_deprecated_name2addr_mode, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_deprecated_name2addr_mode, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+
+ capnet = create_network_service();
+
+ /* LIMIT */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ /* ALLOWED */
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE);
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == ENOTCAPABLE);
+
+ test_extend_mode(capnet, CAPNET_ADDR2NAME);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_deprecated_name2addr_hosts);
+ATF_TC_HEAD(capnet__limits_deprecated_name2addr_hosts, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_deprecated_name2addr_hosts, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+
+ capnet = create_network_service();
+
+ /* Limit to TEST_DOMAIN_0 and localhost only. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ cap_net_limit_name2addr(limit, "localhost", NULL);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0);
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, "localhost") == 0);
+ ATF_REQUIRE(
+ test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_1) == ENOTCAPABLE);
+
+ /* Limit to TEST_DOMAIN_0 only. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(
+ test_gethostbyname(capnet, AF_INET, "localhost") == ENOTCAPABLE);
+ ATF_REQUIRE(
+ test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_1) == ENOTCAPABLE);
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0);
+
+ /* Unable to set empty limits. Empty limits means full access. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_deprecated_name2addr_family);
+ATF_TC_HEAD(capnet__limits_deprecated_name2addr_family, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_deprecated_name2addr_family, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+ int family[] = { AF_INET6, AF_INET };
+
+ capnet = create_network_service();
+
+ /* Limit to AF_INET and AF_INET6. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ cap_net_limit_name2addr_family(limit, family, nitems(family));
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0);
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET6, TEST_DOMAIN_0) == 0);
+ ATF_REQUIRE(
+ test_gethostbyname(capnet, PF_LINK, TEST_DOMAIN_0) == ENOTCAPABLE);
+
+ /* Limit to AF_INET and AF_INET6. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ cap_net_limit_name2addr_family(limit, &family[0], 1);
+ cap_net_limit_name2addr_family(limit, &family[1], 1);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0);
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET6, TEST_DOMAIN_0) == 0);
+ ATF_REQUIRE(
+ test_gethostbyname(capnet, PF_LINK, TEST_DOMAIN_0) == ENOTCAPABLE);
+
+ /* Limit to AF_INET6 only. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+ cap_net_limit_name2addr_family(limit, family, 1);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(
+ test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == ENOTCAPABLE);
+ ATF_REQUIRE(test_gethostbyname(capnet, AF_INET6, TEST_DOMAIN_0) == 0);
+ ATF_REQUIRE(
+ test_gethostbyname(capnet, PF_LINK, TEST_DOMAIN_0) == ENOTCAPABLE);
+
+ /* Unable to set empty limits. Empty limits means full access. */
+ limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_bind_mode);
+ATF_TC_HEAD(capnet__limits_bind_mode, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_bind_mode, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+
+ capnet = create_network_service();
+
+ /* LIMIT */
+ limit = cap_net_limit_init(capnet, CAPNET_BIND);
+ ATF_REQUIRE(limit != NULL);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ /* ALLOWED */
+ ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(
+ test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == ENOTCAPABLE);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == ENOTCAPABLE);
+
+ test_extend_mode(capnet, CAPNET_ADDR2NAME);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_bind);
+ATF_TC_HEAD(capnet__limits_bind, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_bind, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+ struct sockaddr_in ipv4;
+
+ capnet = create_network_service();
+
+ limit = cap_net_limit_init(capnet, CAPNET_BIND);
+ ATF_REQUIRE(limit != NULL);
+
+ memset(&ipv4, 0, sizeof(ipv4));
+ ipv4.sin_family = AF_INET;
+ inet_pton(AF_INET, TEST_BIND_IPV4, &ipv4.sin_addr);
+
+ cap_net_limit_bind(limit, (struct sockaddr *)&ipv4, sizeof(ipv4));
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == 0);
+ ATF_REQUIRE(test_bind(capnet, "127.0.0.2") == ENOTCAPABLE);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_connect_mode);
+ATF_TC_HEAD(capnet__limits_connect_mode, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_connect_mode, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+
+ capnet = create_network_service();
+
+ /* LIMIT */
+ limit = cap_net_limit_init(capnet, CAPNET_CONNECT);
+ ATF_REQUIRE(limit != NULL);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ /* ALLOWED */
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(
+ test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == ENOTCAPABLE);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE);
+
+ test_extend_mode(capnet, CAPNET_ADDR2NAME);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_connect_dns_mode);
+ATF_TC_HEAD(capnet__limits_connect_dns_mode, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_connect_dns_mode, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+
+ capnet = create_network_service();
+
+ /* LIMIT */
+ limit = cap_net_limit_init(capnet, CAPNET_CONNECT | CAPNET_CONNECTDNS);
+ ATF_REQUIRE(limit != NULL);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ /* ALLOWED */
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == 0);
+
+ /* DISALLOWED */
+ ATF_REQUIRE(
+ test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == ENOTCAPABLE);
+ ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+ ENOTCAPABLE);
+ ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE);
+
+ test_extend_mode(capnet, CAPNET_ADDR2NAME);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_connect);
+ATF_TC_HEAD(capnet__limits_connect, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_connect, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+ struct sockaddr_in ipv4;
+
+ capnet = create_network_service();
+
+ /* Limit only to TEST_IPV4 on port 80 and 443. */
+ limit = cap_net_limit_init(capnet, CAPNET_CONNECT);
+ ATF_REQUIRE(limit != NULL);
+ memset(&ipv4, 0, sizeof(ipv4));
+ ipv4.sin_family = AF_INET;
+ ipv4.sin_port = htons(80);
+ inet_pton(AF_INET, TEST_IPV4, &ipv4.sin_addr);
+ cap_net_limit_connect(limit, (struct sockaddr *)&ipv4, sizeof(ipv4));
+
+ ipv4.sin_port = htons(443);
+ cap_net_limit_connect(limit, (struct sockaddr *)&ipv4, sizeof(ipv4));
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == 0);
+ ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 80) == ENOTCAPABLE);
+ ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE);
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 443) == 0);
+
+ /* Limit only to TEST_IPV4 on port 443. */
+ limit = cap_net_limit_init(capnet, CAPNET_CONNECT);
+ cap_net_limit_connect(limit, (struct sockaddr *)&ipv4, sizeof(ipv4));
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 433) == ENOTCAPABLE);
+ ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 80) == ENOTCAPABLE);
+ ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE);
+ ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 443) == 0);
+
+ /* Unable to set empty limits. Empty limits means full access. */
+ limit = cap_net_limit_init(capnet, CAPNET_CONNECT);
+ ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+ cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_connecttodns);
+ATF_TC_HEAD(capnet__limits_connecttodns, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_connecttodns, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+ struct addrinfo hints, *capres, *res;
+ int family[] = { AF_INET };
+ int error;
+
+ capnet = create_network_service();
+
+ limit = cap_net_limit_init(capnet, CAPNET_CONNECTDNS |
+ CAPNET_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_IPV4, "80");
+ cap_net_limit_name2addr_family(limit, family, 1);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE);
+ ATF_REQUIRE(cap_getaddrinfo(capnet, TEST_IPV4, "80", &hints, &capres) ==
+ 0);
+ ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE);
+
+ for (res = capres; res != NULL; res = res->ai_next) {
+ int s;
+
+ ATF_REQUIRE(res->ai_family == AF_INET);
+ ATF_REQUIRE(res->ai_socktype == SOCK_STREAM);
+
+ s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ ATF_REQUIRE(s >= 0);
+
+ error = cap_connect(capnet, s, res->ai_addr,
+ res->ai_addrlen);
+ if (error != 0 && errno != ENOTCAPABLE)
+ atf_tc_skip("unable to connect: %s", strerror(errno));
+ ATF_REQUIRE(error == 0);
+ ATF_REQUIRE(close(s) == 0);
+ }
+
+ freeaddrinfo(capres);
+ cap_close(capnet);
+}
+
+
+ATF_TC(capnet__limits_deprecated_connecttodns);
+ATF_TC_HEAD(capnet__limits_deprecated_connecttodns, tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_deprecated_connecttodns, tc)
+{
+ cap_channel_t *capnet;
+ cap_net_limit_t *limit;
+ struct hostent *caphp;
+ struct in_addr ipaddr;
+ struct sockaddr_in connaddr;
+ int family[] = { AF_INET };
+ int error, i;
+
+ capnet = create_network_service();
+
+ limit = cap_net_limit_init(capnet, CAPNET_CONNECTDNS |
+ CAPNET_DEPRECATED_NAME2ADDR);
+ ATF_REQUIRE(limit != NULL);
+ cap_net_limit_name2addr(limit, TEST_IPV4, NULL);
+ cap_net_limit_name2addr_family(limit, family, 1);
+ ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+ memset(&ipaddr, 0, sizeof(ipaddr));
+ inet_pton(AF_INET, TEST_IPV4, &ipaddr);
+
+ ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE);
+ caphp = cap_gethostbyname2(capnet, TEST_IPV4, AF_INET);
+ ATF_REQUIRE(caphp != NULL);
+ ATF_REQUIRE(caphp->h_addrtype == AF_INET);
+ ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE);
+
+ for (i = 0; caphp->h_addr_list[i] != NULL; i++) {
+ int s;
+
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ ATF_REQUIRE(s >= 0);
+
+ memset(&connaddr, 0, sizeof(connaddr));
+ connaddr.sin_family = AF_INET;
+ memcpy((char *)&connaddr.sin_addr.s_addr,
+ (char *)caphp->h_addr_list[i], caphp->h_length);
+ connaddr.sin_port = htons(80);
+
+ error = cap_connect(capnet, s, (struct sockaddr *)&connaddr,
+ sizeof(connaddr));
+ if (error != 0 && errno != ENOTCAPABLE)
+ atf_tc_skip("unable to connect: %s", strerror(errno));
+ ATF_REQUIRE(error == 0);
+ ATF_REQUIRE(close(s) == 0);
+ }
+
+ cap_close(capnet);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, capnet__connect);
+ ATF_TP_ADD_TC(tp, capnet__bind);
+ ATF_TP_ADD_TC(tp, capnet__getnameinfo);
+ ATF_TP_ADD_TC(tp, capnet__getaddrinfo);
+ ATF_TP_ADD_TC(tp, capnet__gethostbyname);
+ ATF_TP_ADD_TC(tp, capnet__gethostbyaddr);
+
+ ATF_TP_ADD_TC(tp, capnet__getnameinfo_buffer);
+
+ ATF_TP_ADD_TC(tp, capnet__limits_addr2name_mode);
+ ATF_TP_ADD_TC(tp, capnet__limits_addr2name_family);
+ ATF_TP_ADD_TC(tp, capnet__limits_addr2name);
+
+ ATF_TP_ADD_TC(tp, capnet__limits_deprecated_addr2name_mode);
+ ATF_TP_ADD_TC(tp, capnet__limits_deprecated_addr2name_family);
+ ATF_TP_ADD_TC(tp, capnet__limits_deprecated_addr2name);
+
+ ATF_TP_ADD_TC(tp, capnet__limits_name2addr_mode);
+ ATF_TP_ADD_TC(tp, capnet__limits_name2addr_hosts);
+ ATF_TP_ADD_TC(tp, capnet__limits_name2addr_hosts_servnames_strict);
+ ATF_TP_ADD_TC(tp, capnet__limits_name2addr_hosts_servnames_mix);
+ ATF_TP_ADD_TC(tp, capnet__limits_name2addr_family);
+
+ ATF_TP_ADD_TC(tp, capnet__limits_deprecated_name2addr_mode);
+ ATF_TP_ADD_TC(tp, capnet__limits_deprecated_name2addr_hosts);
+ ATF_TP_ADD_TC(tp, capnet__limits_deprecated_name2addr_family);
+
+ ATF_TP_ADD_TC(tp, capnet__limits_bind_mode);
+ ATF_TP_ADD_TC(tp, capnet__limits_bind);
+
+ ATF_TP_ADD_TC(tp, capnet__limits_connect_mode);
+ ATF_TP_ADD_TC(tp, capnet__limits_connect_dns_mode);
+ ATF_TP_ADD_TC(tp, capnet__limits_connect);
+
+ ATF_TP_ADD_TC(tp, capnet__limits_connecttodns);
+ ATF_TP_ADD_TC(tp, capnet__limits_deprecated_connecttodns);
+
+ return (atf_no_error());
+}
diff --git a/lib/libcasper/services/cap_netdb/Makefile b/lib/libcasper/services/cap_netdb/Makefile
new file mode 100644
index 000000000000..853052e78d04
--- /dev/null
+++ b/lib/libcasper/services/cap_netdb/Makefile
@@ -0,0 +1,33 @@
+SHLIBDIR?= /lib
+
+.include <src.opts.mk>
+
+PACKAGE= runtime
+
+SHLIB_MAJOR= 1
+INCSDIR?= ${INCLUDEDIR}/casper
+
+.if ${MK_CASPER} != "no"
+SHLIB= cap_netdb
+
+SRCS= cap_netdb.c
+.endif
+
+INCS= cap_netdb.h
+
+LIBADD= nv
+
+CFLAGS+=-I${.CURDIR}
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+MAN+= cap_netdb.3
+
+MLINKS+=cap_netdb.3 libcap_netdb.3
+MLINKS+=cap_netdb.3 cap_getprotobyname.3
+
+.include <bsd.lib.mk>
+
+# GCC 13 complains incorrectly about free after failed realloc: GCC bug #110501
+CFLAGS.cap_netdb.c+= ${NO_WUSE_AFTER_FREE}
diff --git a/lib/libcasper/services/cap_netdb/Makefile.depend b/lib/libcasper/services/cap_netdb/Makefile.depend
new file mode 100644
index 000000000000..02bae00eb04d
--- /dev/null
+++ b/lib/libcasper/services/cap_netdb/Makefile.depend
@@ -0,0 +1,17 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_netdb/cap_netdb.3 b/lib/libcasper/services/cap_netdb/cap_netdb.3
new file mode 100644
index 000000000000..1f587c2057e7
--- /dev/null
+++ b/lib/libcasper/services/cap_netdb/cap_netdb.3
@@ -0,0 +1,91 @@
+.\" Copyright (c) 2020 Ryan Moeller <freqlabs@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd December 6, 2023
+.Dt CAP_NETDB 3
+.Os
+.Sh NAME
+.Nm cap_getprotobyname ,
+.Nd "library for getting network proto entry in capability mode"
+.Sh LIBRARY
+.Lb libcap_netdb
+.Sh SYNOPSIS
+.In sys/nv.h
+.In libcasper.h
+.In casper/cap_netdb.h
+.Ft "struct protoent *"
+.Fn cap_getprotobyname "const cap_channel_t *chan" "const char *name"
+.Sh DESCRIPTION
+The function
+.Fn cap_getprotobyname
+is equivalent to
+.Xr getprotobyname 3
+except that the connection to the
+.Nm system.netdb
+service needs to be provided.
+It is reentrant but not thread-safe.
+That is, it may be called from separate threads only with different
+.Vt cap_channel_t
+arguments or with synchronization.
+.Sh EXAMPLES
+The following example first opens a capability to casper and then uses this
+capability to create the
+.Nm system.netdb
+casper service and uses it to look up a protocol by name.
+.Bd -literal
+cap_channel_t *capcas, *capnetdb;
+struct protoent *ent;
+
+/* Open capability to Casper. */
+capcas = cap_init();
+if (capcas == NULL)
+ err(1, "Unable to contact Casper");
+
+/* Enter capability mode sandbox. */
+if (caph_enter() < 0)
+ err(1, "Unable to enter capability mode");
+
+/* Use Casper capability to create capability to the system.netdb service. */
+capnetdb = cap_service_open(capcas, "system.netdb");
+if (capnetdb == NULL)
+ err(1, "Unable to open system.netdb service");
+
+/* Close Casper capability, we don't need it anymore. */
+cap_close(capcas);
+
+ent = cap_getprotobyname(capnetdb, "http");
+if (ent == NULL)
+ errx(1, "cap_getprotobyname failed to find http proto");
+.Ed
+.Sh SEE ALSO
+.Xr cap_enter 2 ,
+.Xr caph_enter 3 ,
+.Xr err 3 ,
+.Xr getprotobyname 3 ,
+.Xr capsicum 4 ,
+.Xr nv 9
+.Sh AUTHORS
+The
+.Nm cap_netdb
+service was implemented by
+.An Ryan Moeller Aq Mt freqlabs@FreeBSD.org .
diff --git a/lib/libcasper/services/cap_netdb/cap_netdb.c b/lib/libcasper/services/cap_netdb/cap_netdb.c
new file mode 100644
index 000000000000..7f46f9b4ff40
--- /dev/null
+++ b/lib/libcasper/services/cap_netdb/cap_netdb.c
@@ -0,0 +1,153 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Ryan Moeller <freqlabs@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcasper.h>
+#include <libcasper_service.h>
+
+#include "cap_netdb.h"
+
+static struct protoent *
+protoent_unpack(nvlist_t *nvl)
+{
+ struct protoent *pp;
+ char **aliases;
+ size_t n;
+
+ pp = malloc(sizeof(*pp));
+ if (pp == NULL) {
+ nvlist_destroy(nvl);
+ return (NULL);
+ }
+
+ pp->p_name = nvlist_take_string(nvl, "name");
+
+ aliases = nvlist_take_string_array(nvl, "aliases", &n);
+ pp->p_aliases = realloc(aliases, sizeof(char *) * (n + 1));
+ if (pp->p_aliases == NULL) {
+ while (n-- > 0)
+ free(aliases[n]);
+ free(aliases);
+ free(pp->p_name);
+ free(pp);
+ nvlist_destroy(nvl);
+ return (NULL);
+ }
+ pp->p_aliases[n] = NULL;
+
+ pp->p_proto = (int)nvlist_take_number(nvl, "proto");
+
+ nvlist_destroy(nvl);
+ return (pp);
+}
+
+struct protoent *
+cap_getprotobyname(cap_channel_t *chan, const char *name)
+{
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "getprotobyname");
+ nvlist_add_string(nvl, "name", name);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (NULL);
+ if (dnvlist_get_number(nvl, "error", 0) != 0) {
+ nvlist_destroy(nvl);
+ return (NULL);
+ }
+ return (protoent_unpack(nvl));
+}
+
+static void
+protoent_pack(const struct protoent *pp, nvlist_t *nvl)
+{
+ int n = 0;
+
+ nvlist_add_string(nvl, "name", pp->p_name);
+
+ while (pp->p_aliases[n] != NULL)
+ ++n;
+ nvlist_add_string_array(nvl, "aliases",
+ (const char * const *)pp->p_aliases, n);
+
+ nvlist_add_number(nvl, "proto", (uint64_t)pp->p_proto);
+}
+
+static int
+netdb_getprotobyname(const nvlist_t *limits __unused, const nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ const char *name;
+ struct protoent *pp;
+
+ name = dnvlist_get_string(nvlin, "name", NULL);
+ if (name == NULL)
+ return (EDOOFUS);
+
+ pp = getprotobyname(name);
+ if (pp == NULL)
+ return (EINVAL);
+
+ protoent_pack(pp, nvlout);
+ return (0);
+}
+
+static int
+netdb_limit(const nvlist_t *oldlimits __unused,
+ const nvlist_t *newlimits __unused)
+{
+
+ return (0);
+}
+
+static int
+netdb_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ int error;
+
+ if (strcmp(cmd, "getprotobyname") == 0)
+ error = netdb_getprotobyname(limits, nvlin, nvlout);
+ else
+ error = NO_RECOVERY;
+
+ return (error);
+}
+
+CREATE_SERVICE("system.netdb", netdb_limit, netdb_command, 0);
diff --git a/lib/libcasper/services/cap_netdb/cap_netdb.h b/lib/libcasper/services/cap_netdb/cap_netdb.h
new file mode 100644
index 000000000000..4a89797e33cc
--- /dev/null
+++ b/lib/libcasper/services/cap_netdb/cap_netdb.h
@@ -0,0 +1,47 @@
+/*-
+ * Copyright (c) 2020 Ryan Moeller <freqlabs@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _CAP_NETDB_H_
+#define _CAP_NETDB_H_
+
+#ifdef HAVE_CASPER
+#define WITH_CASPER
+#endif
+
+#include <sys/cdefs.h>
+
+#include <netdb.h>
+
+#ifdef WITH_CASPER
+__BEGIN_DECLS
+
+struct protoent *cap_getprotobyname(cap_channel_t *chan, const char *name);
+
+__END_DECLS
+#else
+#define cap_getprotobyname(chan, name) getprotobyname(name)
+#endif
+
+#endif /* !_CAP_NETDB_H_ */
diff --git a/lib/libcasper/services/cap_netdb/tests/Makefile b/lib/libcasper/services/cap_netdb/tests/Makefile
new file mode 100644
index 000000000000..bc5e04578323
--- /dev/null
+++ b/lib/libcasper/services/cap_netdb/tests/Makefile
@@ -0,0 +1,12 @@
+.include <src.opts.mk>
+
+ATF_TESTS_C= netdb_test
+
+.if ${MK_CASPER} != "no"
+LIBADD+= casper
+LIBADD+= cap_netdb
+CFLAGS+=-DWITH_CASPER
+.endif
+LIBADD+= nv
+
+.include <bsd.test.mk>
diff --git a/lib/libcasper/services/cap_netdb/tests/netdb_test.c b/lib/libcasper/services/cap_netdb/tests/netdb_test.c
new file mode 100644
index 000000000000..5b984a8f6bd2
--- /dev/null
+++ b/lib/libcasper/services/cap_netdb/tests/netdb_test.c
@@ -0,0 +1,91 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Ryan Moeller <freqlabs@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/capsicum.h>
+#include <sys/nv.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcasper.h>
+#include <casper/cap_netdb.h>
+
+#include <atf-c.h>
+
+static cap_channel_t *
+initcap(void)
+{
+ cap_channel_t *capcas, *capnetdb;
+
+ capcas = cap_init();
+ ATF_REQUIRE(capcas != NULL);
+
+ capnetdb = cap_service_open(capcas, "system.netdb");
+ ATF_REQUIRE(capnetdb != NULL);
+
+ cap_close(capcas);
+
+ return (capnetdb);
+}
+
+ATF_TC_WITHOUT_HEAD(cap_netdb__getprotobyname);
+ATF_TC_BODY(cap_netdb__getprotobyname, tc)
+{
+ cap_channel_t *capnetdb;
+ struct protoent *pp;
+ size_t n = 0;
+
+ capnetdb = initcap();
+
+ pp = cap_getprotobyname(capnetdb, "tcp");
+ ATF_REQUIRE(pp != NULL);
+
+ ATF_REQUIRE(pp->p_name != NULL);
+ ATF_REQUIRE(pp->p_aliases != NULL);
+ while (pp->p_aliases[n] != NULL)
+ ++n;
+ ATF_REQUIRE(n > 0);
+ ATF_REQUIRE(pp->p_proto != 0);
+
+ cap_close(capnetdb);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, cap_netdb__getprotobyname);
+
+ return (atf_no_error());
+}
diff --git a/lib/libcasper/services/cap_pwd/Makefile b/lib/libcasper/services/cap_pwd/Makefile
new file mode 100644
index 000000000000..a1e97845c736
--- /dev/null
+++ b/lib/libcasper/services/cap_pwd/Makefile
@@ -0,0 +1,44 @@
+SHLIBDIR?= /lib
+
+.include <src.opts.mk>
+
+PACKAGE= runtime
+
+SHLIB_MAJOR= 1
+INCSDIR?= ${INCLUDEDIR}/casper
+
+.if ${MK_CASPER} != "no"
+SHLIB= cap_pwd
+
+SRCS= cap_pwd.c
+.endif
+
+INCS= cap_pwd.h
+
+LIBADD= nv
+
+CFLAGS+=-I${.CURDIR}
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+MAN+= cap_pwd.3
+
+MLINKS+=cap_pwd.3 libcap_pwd.3
+MLINKS+=cap_pwd.3 cap_getpwent.3
+MLINKS+=cap_pwd.3 cap_getpwnam.3
+MLINKS+=cap_pwd.3 cap_getpwuid.3
+MLINKS+=cap_pwd.3 cap_getpwent_r.3
+MLINKS+=cap_pwd.3 cap_getpwnam_r.3
+MLINKS+=cap_pwd.3 cap_getpwuid_r.3
+MLINKS+=cap_pwd.3 cap_setpassent.3
+MLINKS+=cap_pwd.3 cap_setpwent.3
+MLINKS+=cap_pwd.3 cap_endpwent.3
+MLINKS+=cap_pwd.3 cap_pwd_limit_cmds.3
+MLINKS+=cap_pwd.3 cap_pwd_limit_fields.3
+MLINKS+=cap_pwd.3 cap_pwd_limit_users.3
+
+.include <bsd.lib.mk>
+
+# GCC 13 complains incorrectly about free after failed realloc: GCC bug #110501
+CFLAGS.cap_pwd.c+= ${NO_WUSE_AFTER_FREE}
diff --git a/lib/libcasper/services/cap_pwd/Makefile.depend b/lib/libcasper/services/cap_pwd/Makefile.depend
new file mode 100644
index 000000000000..02bae00eb04d
--- /dev/null
+++ b/lib/libcasper/services/cap_pwd/Makefile.depend
@@ -0,0 +1,17 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_pwd/cap_pwd.3 b/lib/libcasper/services/cap_pwd/cap_pwd.3
new file mode 100644
index 000000000000..b66a0cd083ba
--- /dev/null
+++ b/lib/libcasper/services/cap_pwd/cap_pwd.3
@@ -0,0 +1,242 @@
+.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd December 6, 2023
+.Dt CAP_PWD 3
+.Os
+.Sh NAME
+.Nm cap_getpwent ,
+.Nm cap_getpwnam ,
+.Nm cap_getpwuid ,
+.Nm cap_getpwent_r ,
+.Nm cap_getpwnam_r ,
+.Nm cap_getpwuid_r ,
+.Nm cap_setpassent ,
+.Nm cap_setpwent ,
+.Nm cap_endpwent ,
+.Nm cap_pwd_limit_cmds ,
+.Nm cap_pwd_limit_fields ,
+.Nm cap_pwd_limit_users
+.Nd "library for password database operations in capability mode"
+.Sh LIBRARY
+.Lb libcap_pwd
+.Sh SYNOPSIS
+.In libcasper.h
+.In casper/cap_pwd.h
+.Ft struct passwd *
+.Fn cap_getpwent "cap_channel_t *chan"
+.Ft struct passwd *
+.Fn cap_getpwnam "cap_channel_t *chan" "const char *login"
+.Ft struct passwd *
+.Fn cap_getpwuid "cap_channel_t *chan" "uid_t uid"
+.Ft int
+.Fn cap_getpwent_r "cap_channel_t *chan" "struct passwd *pwd" "char *buffer" "size_t bufsize" "struct passwd **result"
+.Ft int
+.Fn cap_getpwnam_r "cap_channel_t *chan" "const char *name" "struct passwd *pwd" "char *buffer" "size_t bufsize" "struct passwd **result"
+.Ft int
+.Fn cap_getpwuid_r "cap_channel_t *chan" "uid_t uid" "struct passwd *pwd" "char *buffer" "size_t bufsize" "struct passwd **result"
+.Ft int
+.Fn cap_setpassent "cap_channel_t *chan" "int stayopen"
+.Ft void
+.Fn cap_setpwent "cap_channel_t *chan"
+.Ft void
+.Fn cap_endpwent "cap_channel_t *chan"
+.Ft int
+.Fn cap_pwd_limit_cmds "cap_channel_t *chan" "const char * const *cmds" "size_t ncmds"
+.Ft int
+.Fn cap_pwd_limit_fields "cap_channel_t *chan" "const char * const *fields" "size_t nfields"
+.Ft int
+.Fn cap_pwd_limit_users "cap_channel_t *chan" "const char * const *names" "size_t nnames" "uid_t *uids" "size_t nuids"
+.Sh DESCRIPTION
+The functions
+.Fn cap_getpwent ,
+.Fn cap_getpwnam ,
+.Fn cap_getpwuid ,
+.Fn cap_getpwent_r ,
+.Fn cap_getpwnam_r ,
+.Fn cap_getpwuid_r ,
+.Fn cap_setpassent ,
+.Fn cap_setpwent ,
+and
+.Fn cap_endpwent
+are respectively equivalent to
+.Xr getpwent 3 ,
+.Xr getpwnam 3 ,
+.Xr getpwuid 3 ,
+.Xr getpwent_r 3 ,
+.Xr getpwnam_r 3 ,
+.Xr getpwuid_r 3 ,
+.Xr setpassent 3 ,
+.Xr setpwent 3 ,
+and
+.Xr cap_endpwent 3
+except that the connection to the
+.Nm system.pwd
+service needs to be provided.
+.Pp
+The
+.Fn cap_pwd_limit_cmds
+function limits the functions allowed in the service.
+The
+.Fa cmds
+variable can be set to
+.Dv getpwent ,
+.Dv getpwnam ,
+.Dv getpwuid ,
+.Dv getpwent_r ,
+.Dv getpwnam_r ,
+.Dv getpwuid_r ,
+.Dv setpassent ,
+.Dv setpwent ,
+or
+.Dv endpwent
+which will allow to use the function associated with the name.
+The
+.Fa ncmds
+variable contains the number of
+.Fa cmds
+provided.
+.Pp
+The
+.Fn cap_pwd_limit_fields
+function allows limit fields returned in the structure
+.Vt passwd .
+The
+.Fa fields
+variable can be set to
+.Dv pw_name ,
+.Dv pw_passwd ,
+.Dv pw_uid ,
+.Dv pw_gid ,
+.Dv pw_change ,
+.Dv pw_class ,
+.Dv pw_gecos ,
+.Dv pw_dir ,
+.Dv pw_shell ,
+.Dv pw_expire
+or
+.Dv pw_fields
+The field which was set as the limit will be returned, while the rest of the
+values not set this way will have default values.
+The
+.Fa nfields
+variable contains the number of
+.Fa fields
+provided.
+.Pp
+The
+.Fn cap_pwd_limit_users
+function allows to limit access to users.
+The
+.Fa names
+variable allows to limit users by name and the
+.Fa uids
+variable by the user number.
+The
+.Fa nnames
+and
+.Fa nuids
+variables provide numbers of limited names and uids.
+.Pp
+All of these functions are reentrant but not thread-safe.
+That is, they may be called from separate threads only with different
+.Vt cap_channel_t
+arguments or with synchronization.
+.Sh EXAMPLES
+The following example first opens a capability to casper and then uses this
+capability to create the
+.Nm system.pwd
+casper service and uses it to get a user name.
+.Bd -literal
+cap_channel_t *capcas, *cappwd;
+const char *cmds[] = { "getpwuid" };
+const char *fields[] = { "pw_name" };
+uid_t uid[] = { 1 };
+struct passwd *passwd;
+
+/* Open capability to Casper. */
+capcas = cap_init();
+if (capcas == NULL)
+ err(1, "Unable to contact Casper");
+
+/* Enter capability mode sandbox. */
+if (cap_enter() < 0 && errno != ENOSYS)
+ err(1, "Unable to enter capability mode");
+
+/* Use Casper capability to create capability to the system.pwd service. */
+cappwd = cap_service_open(capcas, "system.pwd");
+if (cappwd == NULL)
+ err(1, "Unable to open system.pwd service");
+
+/* Close Casper capability, we don't need it anymore. */
+cap_close(capcas);
+
+/* Limit service to one single function. */
+if (cap_pwd_limit_cmds(cappwd, cmds, nitems(cmds)))
+ err(1, "Unable to limit access to system.pwd service");
+
+/* Limit service to one field as we only need name of the user. */
+if (cap_pwd_limit_fields(cappwd, fields, nitems(fields)))
+ err(1, "Unable to limit access to system.pwd service");
+
+/* Limit service to one uid. */
+if (cap_pwd_limit_users(cappwd, NULL, 0, uid, nitems(uid)))
+ err(1, "Unable to limit access to system.pwd service");
+
+passwd = cap_getpwuid(cappwd, uid[0]);
+if (passwd == NULL)
+ err(1, "Unable to get name of user");
+
+printf("UID %d is associated with name %s.\\n", uid[0], passwd->pw_name);
+
+cap_close(cappwd);
+.Ed
+.Sh SEE ALSO
+.Xr cap_enter 2 ,
+.Xr endpwent 3 ,
+.Xr err 3 ,
+.Xr getpwent 3 ,
+.Xr getpwent_r 3 ,
+.Xr getpwnam 3 ,
+.Xr getpwnam_r 3 ,
+.Xr getpwuid 3 ,
+.Xr getpwuid_r 3 ,
+.Xr setpassent 3 ,
+.Xr setpwent 3 ,
+.Xr capsicum 4 ,
+.Xr nv 9
+.Sh HISTORY
+The
+.Nm cap_pwd
+service first appeared in
+.Fx 10.3 .
+.Sh AUTHORS
+The
+.Nm cap_pwd
+service was implemented by
+.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net
+under sponsorship from the FreeBSD Foundation.
+.Pp
+This manual page was written by
+.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org .
diff --git a/lib/libcasper/services/cap_pwd/cap_pwd.c b/lib/libcasper/services/cap_pwd/cap_pwd.c
new file mode 100644
index 000000000000..1b5963d8be3d
--- /dev/null
+++ b/lib/libcasper/services/cap_pwd/cap_pwd.c
@@ -0,0 +1,781 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/nv.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcasper.h>
+#include <libcasper_service.h>
+
+#include "cap_pwd.h"
+
+static struct passwd gpwd;
+static char *gbuffer;
+static size_t gbufsize;
+
+static int
+passwd_resize(void)
+{
+ char *buf;
+
+ if (gbufsize == 0)
+ gbufsize = 1024;
+ else
+ gbufsize *= 2;
+
+ buf = gbuffer;
+ gbuffer = realloc(buf, gbufsize);
+ if (gbuffer == NULL) {
+ free(buf);
+ gbufsize = 0;
+ return (ENOMEM);
+ }
+ memset(gbuffer, 0, gbufsize);
+
+ return (0);
+}
+
+static int
+passwd_unpack_string(const nvlist_t *nvl, const char *fieldname, char **fieldp,
+ char **bufferp, size_t *bufsizep)
+{
+ const char *str;
+ size_t len;
+
+ str = nvlist_get_string(nvl, fieldname);
+ len = strlcpy(*bufferp, str, *bufsizep);
+ if (len >= *bufsizep)
+ return (ERANGE);
+ *fieldp = *bufferp;
+ *bufferp += len + 1;
+ *bufsizep -= len + 1;
+
+ return (0);
+}
+
+static int
+passwd_unpack(const nvlist_t *nvl, struct passwd *pwd, char *buffer,
+ size_t bufsize)
+{
+ int error;
+
+ if (!nvlist_exists_string(nvl, "pw_name"))
+ return (EINVAL);
+
+ explicit_bzero(pwd, sizeof(*pwd));
+
+ error = passwd_unpack_string(nvl, "pw_name", &pwd->pw_name, &buffer,
+ &bufsize);
+ if (error != 0)
+ return (error);
+ pwd->pw_uid = (uid_t)nvlist_get_number(nvl, "pw_uid");
+ pwd->pw_gid = (gid_t)nvlist_get_number(nvl, "pw_gid");
+ pwd->pw_change = (time_t)nvlist_get_number(nvl, "pw_change");
+ error = passwd_unpack_string(nvl, "pw_passwd", &pwd->pw_passwd, &buffer,
+ &bufsize);
+ if (error != 0)
+ return (error);
+ error = passwd_unpack_string(nvl, "pw_class", &pwd->pw_class, &buffer,
+ &bufsize);
+ if (error != 0)
+ return (error);
+ error = passwd_unpack_string(nvl, "pw_gecos", &pwd->pw_gecos, &buffer,
+ &bufsize);
+ if (error != 0)
+ return (error);
+ error = passwd_unpack_string(nvl, "pw_dir", &pwd->pw_dir, &buffer,
+ &bufsize);
+ if (error != 0)
+ return (error);
+ error = passwd_unpack_string(nvl, "pw_shell", &pwd->pw_shell, &buffer,
+ &bufsize);
+ if (error != 0)
+ return (error);
+ pwd->pw_expire = (time_t)nvlist_get_number(nvl, "pw_expire");
+ pwd->pw_fields = (int)nvlist_get_number(nvl, "pw_fields");
+
+ return (0);
+}
+
+static int
+cap_getpwcommon_r(cap_channel_t *chan, const char *cmd, const char *login,
+ uid_t uid, struct passwd *pwd, char *buffer, size_t bufsize,
+ struct passwd **result)
+{
+ nvlist_t *nvl;
+ bool getpw_r;
+ int error;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", cmd);
+ if (strcmp(cmd, "getpwent") == 0 || strcmp(cmd, "getpwent_r") == 0) {
+ /* Add nothing. */
+ } else if (strcmp(cmd, "getpwnam") == 0 ||
+ strcmp(cmd, "getpwnam_r") == 0) {
+ nvlist_add_string(nvl, "name", login);
+ } else if (strcmp(cmd, "getpwuid") == 0 ||
+ strcmp(cmd, "getpwuid_r") == 0) {
+ nvlist_add_number(nvl, "uid", (uint64_t)uid);
+ } else {
+ abort();
+ }
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL) {
+ assert(errno != 0);
+ *result = NULL;
+ return (errno);
+ }
+ error = (int)nvlist_get_number(nvl, "error");
+ if (error != 0) {
+ nvlist_destroy(nvl);
+ *result = NULL;
+ return (error);
+ }
+
+ if (!nvlist_exists_string(nvl, "pw_name")) {
+ /* Not found. */
+ nvlist_destroy(nvl);
+ *result = NULL;
+ return (0);
+ }
+
+ getpw_r = (strcmp(cmd, "getpwent_r") == 0 ||
+ strcmp(cmd, "getpwnam_r") == 0 || strcmp(cmd, "getpwuid_r") == 0);
+
+ for (;;) {
+ error = passwd_unpack(nvl, pwd, buffer, bufsize);
+ if (getpw_r || error != ERANGE)
+ break;
+ assert(buffer == gbuffer);
+ assert(bufsize == gbufsize);
+ error = passwd_resize();
+ if (error != 0)
+ break;
+ /* Update pointers after resize. */
+ buffer = gbuffer;
+ bufsize = gbufsize;
+ }
+
+ nvlist_destroy(nvl);
+
+ if (error == 0)
+ *result = pwd;
+ else
+ *result = NULL;
+
+ return (error);
+}
+
+static struct passwd *
+cap_getpwcommon(cap_channel_t *chan, const char *cmd, const char *login,
+ uid_t uid)
+{
+ struct passwd *result;
+ int error, serrno;
+
+ serrno = errno;
+
+ error = cap_getpwcommon_r(chan, cmd, login, uid, &gpwd, gbuffer,
+ gbufsize, &result);
+ if (error != 0) {
+ errno = error;
+ return (NULL);
+ }
+
+ errno = serrno;
+
+ return (result);
+}
+
+struct passwd *
+cap_getpwent(cap_channel_t *chan)
+{
+
+ return (cap_getpwcommon(chan, "getpwent", NULL, 0));
+}
+
+struct passwd *
+cap_getpwnam(cap_channel_t *chan, const char *login)
+{
+
+ return (cap_getpwcommon(chan, "getpwnam", login, 0));
+}
+
+struct passwd *
+cap_getpwuid(cap_channel_t *chan, uid_t uid)
+{
+
+ return (cap_getpwcommon(chan, "getpwuid", NULL, uid));
+}
+
+int
+cap_getpwent_r(cap_channel_t *chan, struct passwd *pwd, char *buffer,
+ size_t bufsize, struct passwd **result)
+{
+
+ return (cap_getpwcommon_r(chan, "getpwent_r", NULL, 0, pwd, buffer,
+ bufsize, result));
+}
+
+int
+cap_getpwnam_r(cap_channel_t *chan, const char *name, struct passwd *pwd,
+ char *buffer, size_t bufsize, struct passwd **result)
+{
+
+ return (cap_getpwcommon_r(chan, "getpwnam_r", name, 0, pwd, buffer,
+ bufsize, result));
+}
+
+int
+cap_getpwuid_r(cap_channel_t *chan, uid_t uid, struct passwd *pwd, char *buffer,
+ size_t bufsize, struct passwd **result)
+{
+
+ return (cap_getpwcommon_r(chan, "getpwuid_r", NULL, uid, pwd, buffer,
+ bufsize, result));
+}
+
+int
+cap_setpassent(cap_channel_t *chan, int stayopen)
+{
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "setpassent");
+ nvlist_add_bool(nvl, "stayopen", stayopen != 0);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (0);
+ if (nvlist_get_number(nvl, "error") != 0) {
+ errno = nvlist_get_number(nvl, "error");
+ nvlist_destroy(nvl);
+ return (0);
+ }
+ nvlist_destroy(nvl);
+
+ return (1);
+}
+
+static void
+cap_set_end_pwent(cap_channel_t *chan, const char *cmd)
+{
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", cmd);
+ /* Ignore any errors, we have no way to report them. */
+ nvlist_destroy(cap_xfer_nvlist(chan, nvl));
+}
+
+void
+cap_setpwent(cap_channel_t *chan)
+{
+
+ cap_set_end_pwent(chan, "setpwent");
+}
+
+void
+cap_endpwent(cap_channel_t *chan)
+{
+
+ cap_set_end_pwent(chan, "endpwent");
+}
+
+int
+cap_pwd_limit_cmds(cap_channel_t *chan, const char * const *cmds, size_t ncmds)
+{
+ nvlist_t *limits, *nvl;
+ unsigned int i;
+
+ if (cap_limit_get(chan, &limits) < 0)
+ return (-1);
+ if (limits == NULL) {
+ limits = nvlist_create(0);
+ } else {
+ if (nvlist_exists_nvlist(limits, "cmds"))
+ nvlist_free_nvlist(limits, "cmds");
+ }
+ nvl = nvlist_create(0);
+ for (i = 0; i < ncmds; i++)
+ nvlist_add_null(nvl, cmds[i]);
+ nvlist_move_nvlist(limits, "cmds", nvl);
+ return (cap_limit_set(chan, limits));
+}
+
+int
+cap_pwd_limit_fields(cap_channel_t *chan, const char * const *fields,
+ size_t nfields)
+{
+ nvlist_t *limits, *nvl;
+ unsigned int i;
+
+ if (cap_limit_get(chan, &limits) < 0)
+ return (-1);
+ if (limits == NULL) {
+ limits = nvlist_create(0);
+ } else {
+ if (nvlist_exists_nvlist(limits, "fields"))
+ nvlist_free_nvlist(limits, "fields");
+ }
+ nvl = nvlist_create(0);
+ for (i = 0; i < nfields; i++)
+ nvlist_add_null(nvl, fields[i]);
+ nvlist_move_nvlist(limits, "fields", nvl);
+ return (cap_limit_set(chan, limits));
+}
+
+int
+cap_pwd_limit_users(cap_channel_t *chan, const char * const *names,
+ size_t nnames, uid_t *uids, size_t nuids)
+{
+ nvlist_t *limits, *users;
+ char nvlname[64];
+ unsigned int i;
+ int n;
+
+ if (cap_limit_get(chan, &limits) < 0)
+ return (-1);
+ if (limits == NULL) {
+ limits = nvlist_create(0);
+ } else {
+ if (nvlist_exists_nvlist(limits, "users"))
+ nvlist_free_nvlist(limits, "users");
+ }
+ users = nvlist_create(0);
+ for (i = 0; i < nuids; i++) {
+ n = snprintf(nvlname, sizeof(nvlname), "uid%u", i);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_add_number(users, nvlname, (uint64_t)uids[i]);
+ }
+ for (i = 0; i < nnames; i++) {
+ n = snprintf(nvlname, sizeof(nvlname), "name%u", i);
+ assert(n > 0 && n < (int)sizeof(nvlname));
+ nvlist_add_string(users, nvlname, names[i]);
+ }
+ nvlist_move_nvlist(limits, "users", users);
+ return (cap_limit_set(chan, limits));
+}
+
+
+/*
+ * Service functions.
+ */
+static bool
+pwd_allowed_cmd(const nvlist_t *limits, const char *cmd)
+{
+
+ if (limits == NULL)
+ return (true);
+
+ /*
+ * If no limit was set on allowed commands, then all commands
+ * are allowed.
+ */
+ if (!nvlist_exists_nvlist(limits, "cmds"))
+ return (true);
+
+ limits = nvlist_get_nvlist(limits, "cmds");
+ return (nvlist_exists_null(limits, cmd));
+}
+
+static int
+pwd_allowed_cmds(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const char *name;
+ void *cookie;
+ int type;
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
+ if (type != NV_TYPE_NULL)
+ return (EINVAL);
+ if (!pwd_allowed_cmd(oldlimits, name))
+ return (ENOTCAPABLE);
+ }
+
+ return (0);
+}
+
+static bool
+pwd_allowed_user(const nvlist_t *limits, const char *uname, uid_t uid)
+{
+ const char *name;
+ void *cookie;
+ int type;
+
+ if (limits == NULL)
+ return (true);
+
+ /*
+ * If no limit was set on allowed users, then all users are allowed.
+ */
+ if (!nvlist_exists_nvlist(limits, "users"))
+ return (true);
+
+ limits = nvlist_get_nvlist(limits, "users");
+ cookie = NULL;
+ while ((name = nvlist_next(limits, &type, &cookie)) != NULL) {
+ switch (type) {
+ case NV_TYPE_NUMBER:
+ if (uid != (uid_t)-1 &&
+ nvlist_get_number(limits, name) == (uint64_t)uid) {
+ return (true);
+ }
+ break;
+ case NV_TYPE_STRING:
+ if (uname != NULL &&
+ strcmp(nvlist_get_string(limits, name),
+ uname) == 0) {
+ return (true);
+ }
+ break;
+ default:
+ abort();
+ }
+ }
+
+ return (false);
+}
+
+static int
+pwd_allowed_users(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const char *name, *uname;
+ void *cookie;
+ uid_t uid;
+ int type;
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
+ switch (type) {
+ case NV_TYPE_NUMBER:
+ uid = (uid_t)nvlist_get_number(newlimits, name);
+ uname = NULL;
+ break;
+ case NV_TYPE_STRING:
+ uid = (uid_t)-1;
+ uname = nvlist_get_string(newlimits, name);
+ break;
+ default:
+ return (EINVAL);
+ }
+ if (!pwd_allowed_user(oldlimits, uname, uid))
+ return (ENOTCAPABLE);
+ }
+
+ return (0);
+}
+
+static bool
+pwd_allowed_field(const nvlist_t *limits, const char *field)
+{
+
+ if (limits == NULL)
+ return (true);
+
+ /*
+ * If no limit was set on allowed fields, then all fields are allowed.
+ */
+ if (!nvlist_exists_nvlist(limits, "fields"))
+ return (true);
+
+ limits = nvlist_get_nvlist(limits, "fields");
+ return (nvlist_exists_null(limits, field));
+}
+
+static int
+pwd_allowed_fields(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const char *name;
+ void *cookie;
+ int type;
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
+ if (type != NV_TYPE_NULL)
+ return (EINVAL);
+ if (!pwd_allowed_field(oldlimits, name))
+ return (ENOTCAPABLE);
+ }
+
+ return (0);
+}
+
+static bool
+pwd_pack(const nvlist_t *limits, const struct passwd *pwd, nvlist_t *nvl)
+{
+ int fields;
+
+ if (pwd == NULL)
+ return (true);
+
+ /*
+ * If either name or UID is allowed, we allow it.
+ */
+ if (!pwd_allowed_user(limits, pwd->pw_name, pwd->pw_uid))
+ return (false);
+
+ fields = pwd->pw_fields;
+
+ if (pwd_allowed_field(limits, "pw_name")) {
+ nvlist_add_string(nvl, "pw_name", pwd->pw_name);
+ } else {
+ nvlist_add_string(nvl, "pw_name", "");
+ fields &= ~_PWF_NAME;
+ }
+ if (pwd_allowed_field(limits, "pw_uid")) {
+ nvlist_add_number(nvl, "pw_uid", (uint64_t)pwd->pw_uid);
+ } else {
+ nvlist_add_number(nvl, "pw_uid", (uint64_t)-1);
+ fields &= ~_PWF_UID;
+ }
+ if (pwd_allowed_field(limits, "pw_gid")) {
+ nvlist_add_number(nvl, "pw_gid", (uint64_t)pwd->pw_gid);
+ } else {
+ nvlist_add_number(nvl, "pw_gid", (uint64_t)-1);
+ fields &= ~_PWF_GID;
+ }
+ if (pwd_allowed_field(limits, "pw_change")) {
+ nvlist_add_number(nvl, "pw_change", (uint64_t)pwd->pw_change);
+ } else {
+ nvlist_add_number(nvl, "pw_change", (uint64_t)0);
+ fields &= ~_PWF_CHANGE;
+ }
+ if (pwd_allowed_field(limits, "pw_passwd")) {
+ nvlist_add_string(nvl, "pw_passwd", pwd->pw_passwd);
+ } else {
+ nvlist_add_string(nvl, "pw_passwd", "");
+ fields &= ~_PWF_PASSWD;
+ }
+ if (pwd_allowed_field(limits, "pw_class")) {
+ nvlist_add_string(nvl, "pw_class", pwd->pw_class);
+ } else {
+ nvlist_add_string(nvl, "pw_class", "");
+ fields &= ~_PWF_CLASS;
+ }
+ if (pwd_allowed_field(limits, "pw_gecos")) {
+ nvlist_add_string(nvl, "pw_gecos", pwd->pw_gecos);
+ } else {
+ nvlist_add_string(nvl, "pw_gecos", "");
+ fields &= ~_PWF_GECOS;
+ }
+ if (pwd_allowed_field(limits, "pw_dir")) {
+ nvlist_add_string(nvl, "pw_dir", pwd->pw_dir);
+ } else {
+ nvlist_add_string(nvl, "pw_dir", "");
+ fields &= ~_PWF_DIR;
+ }
+ if (pwd_allowed_field(limits, "pw_shell")) {
+ nvlist_add_string(nvl, "pw_shell", pwd->pw_shell);
+ } else {
+ nvlist_add_string(nvl, "pw_shell", "");
+ fields &= ~_PWF_SHELL;
+ }
+ if (pwd_allowed_field(limits, "pw_expire")) {
+ nvlist_add_number(nvl, "pw_expire", (uint64_t)pwd->pw_expire);
+ } else {
+ nvlist_add_number(nvl, "pw_expire", (uint64_t)0);
+ fields &= ~_PWF_EXPIRE;
+ }
+ nvlist_add_number(nvl, "pw_fields", (uint64_t)fields);
+
+ return (true);
+}
+
+static int
+pwd_getpwent(const nvlist_t *limits, const nvlist_t *nvlin __unused,
+ nvlist_t *nvlout)
+{
+ struct passwd *pwd;
+
+ for (;;) {
+ errno = 0;
+ pwd = getpwent();
+ if (errno != 0)
+ return (errno);
+ if (pwd_pack(limits, pwd, nvlout))
+ return (0);
+ }
+
+ /* NOTREACHED */
+}
+
+static int
+pwd_getpwnam(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
+{
+ struct passwd *pwd;
+ const char *name;
+
+ if (!nvlist_exists_string(nvlin, "name"))
+ return (EINVAL);
+ name = nvlist_get_string(nvlin, "name");
+ assert(name != NULL);
+
+ errno = 0;
+ pwd = getpwnam(name);
+ if (errno != 0)
+ return (errno);
+
+ (void)pwd_pack(limits, pwd, nvlout);
+
+ return (0);
+}
+
+static int
+pwd_getpwuid(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
+{
+ struct passwd *pwd;
+ uid_t uid;
+
+ if (!nvlist_exists_number(nvlin, "uid"))
+ return (EINVAL);
+
+ uid = (uid_t)nvlist_get_number(nvlin, "uid");
+
+ errno = 0;
+ pwd = getpwuid(uid);
+ if (errno != 0)
+ return (errno);
+
+ (void)pwd_pack(limits, pwd, nvlout);
+
+ return (0);
+}
+
+static int
+pwd_setpassent(const nvlist_t *limits __unused, const nvlist_t *nvlin,
+ nvlist_t *nvlout __unused)
+{
+ int stayopen;
+
+ if (!nvlist_exists_bool(nvlin, "stayopen"))
+ return (EINVAL);
+
+ stayopen = nvlist_get_bool(nvlin, "stayopen") ? 1 : 0;
+
+ return (setpassent(stayopen) == 0 ? EFAULT : 0);
+}
+
+static int
+pwd_setpwent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused,
+ nvlist_t *nvlout __unused)
+{
+
+ setpwent();
+
+ return (0);
+}
+
+static int
+pwd_endpwent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused,
+ nvlist_t *nvlout __unused)
+{
+
+ endpwent();
+
+ return (0);
+}
+
+static int
+pwd_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const nvlist_t *limits;
+ const char *name;
+ void *cookie;
+ int error, type;
+
+ if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "cmds") &&
+ !nvlist_exists_nvlist(newlimits, "cmds")) {
+ return (ENOTCAPABLE);
+ }
+ if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "fields") &&
+ !nvlist_exists_nvlist(newlimits, "fields")) {
+ return (ENOTCAPABLE);
+ }
+ if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "users") &&
+ !nvlist_exists_nvlist(newlimits, "users")) {
+ return (ENOTCAPABLE);
+ }
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
+ if (type != NV_TYPE_NVLIST)
+ return (EINVAL);
+ limits = nvlist_get_nvlist(newlimits, name);
+ if (strcmp(name, "cmds") == 0)
+ error = pwd_allowed_cmds(oldlimits, limits);
+ else if (strcmp(name, "fields") == 0)
+ error = pwd_allowed_fields(oldlimits, limits);
+ else if (strcmp(name, "users") == 0)
+ error = pwd_allowed_users(oldlimits, limits);
+ else
+ error = EINVAL;
+ if (error != 0)
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+pwd_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ int error;
+
+ if (!pwd_allowed_cmd(limits, cmd))
+ return (ENOTCAPABLE);
+
+ if (strcmp(cmd, "getpwent") == 0 || strcmp(cmd, "getpwent_r") == 0)
+ error = pwd_getpwent(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "getpwnam") == 0 || strcmp(cmd, "getpwnam_r") == 0)
+ error = pwd_getpwnam(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "getpwuid") == 0 || strcmp(cmd, "getpwuid_r") == 0)
+ error = pwd_getpwuid(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "setpassent") == 0)
+ error = pwd_setpassent(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "setpwent") == 0)
+ error = pwd_setpwent(limits, nvlin, nvlout);
+ else if (strcmp(cmd, "endpwent") == 0)
+ error = pwd_endpwent(limits, nvlin, nvlout);
+ else
+ error = EINVAL;
+
+ return (error);
+}
+
+CREATE_SERVICE("system.pwd", pwd_limit, pwd_command, 0);
diff --git a/lib/libcasper/services/cap_pwd/cap_pwd.h b/lib/libcasper/services/cap_pwd/cap_pwd.h
new file mode 100644
index 000000000000..496beea26392
--- /dev/null
+++ b/lib/libcasper/services/cap_pwd/cap_pwd.h
@@ -0,0 +1,159 @@
+/*-
+ * Copyright (c) 2013 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _CAP_PWD_H_
+#define _CAP_PWD_H_
+
+#ifdef HAVE_CASPER
+#define WITH_CASPER
+#endif
+
+#include <sys/cdefs.h>
+
+#ifdef WITH_CASPER
+__BEGIN_DECLS
+
+struct passwd *cap_getpwent(cap_channel_t *chan);
+struct passwd *cap_getpwnam(cap_channel_t *chan, const char *login);
+struct passwd *cap_getpwuid(cap_channel_t *chan, uid_t uid);
+
+int cap_getpwent_r(cap_channel_t *chan, struct passwd *pwd, char *buffer,
+ size_t bufsize, struct passwd **result);
+int cap_getpwnam_r(cap_channel_t *chan, const char *name, struct passwd *pwd,
+ char *buffer, size_t bufsize, struct passwd **result);
+int cap_getpwuid_r(cap_channel_t *chan, uid_t uid, struct passwd *pwd,
+ char *buffer, size_t bufsize, struct passwd **result);
+
+int cap_setpassent(cap_channel_t *chan, int stayopen);
+void cap_setpwent(cap_channel_t *chan);
+void cap_endpwent(cap_channel_t *chan);
+
+int cap_pwd_limit_cmds(cap_channel_t *chan, const char * const *cmds,
+ size_t ncmds);
+int cap_pwd_limit_fields(cap_channel_t *chan, const char * const *fields,
+ size_t nfields);
+int cap_pwd_limit_users(cap_channel_t *chan, const char * const *names,
+ size_t nnames, uid_t *uids, size_t nuids);
+
+__END_DECLS
+
+#else
+
+static inline struct passwd *
+cap_getpwent(cap_channel_t *chan __unused)
+{
+
+ return (getpwent());
+}
+
+static inline struct passwd *
+cap_getpwnam(cap_channel_t *chan __unused, const char *login)
+{
+
+ return (getpwnam(login));
+}
+
+static inline struct passwd *
+cap_getpwuid(cap_channel_t *chan __unused, uid_t uid)
+{
+
+ return (getpwuid(uid));
+}
+
+static inline int
+cap_getpwent_r(cap_channel_t *chan __unused, struct passwd *pwd, char *buffer,
+ size_t bufsize, struct passwd **result)
+{
+
+ return (getpwent_r(pwd, buffer, bufsize, result));
+}
+
+static inline int
+cap_getpwnam_r(cap_channel_t *chan __unused, const char *name,
+ struct passwd *pwd, char *buffer, size_t bufsize, struct passwd **result)
+{
+
+ return (getpwnam_r(name, pwd, buffer, bufsize, result));
+}
+
+static inline int
+cap_getpwuid_r(cap_channel_t *chan __unused, uid_t uid, struct passwd *pwd,
+ char *buffer, size_t bufsize, struct passwd **result)
+{
+
+ return (getpwuid_r(uid, pwd, buffer, bufsize, result));
+}
+
+static inline int
+cap_setpassent(cap_channel_t *chan __unused, int stayopen)
+{
+
+ return (setpassent(stayopen));
+}
+
+static inline void
+cap_setpwent(cap_channel_t *chan __unused)
+{
+
+ return (setpwent());
+}
+
+static inline void
+cap_endpwent(cap_channel_t *chan __unused)
+{
+
+ return (endpwent());
+}
+
+static inline int
+cap_pwd_limit_cmds(cap_channel_t *chan __unused,
+ const char * const *cmds __unused, size_t ncmds __unused)
+{
+
+ return (0);
+}
+
+static inline int
+cap_pwd_limit_fields(cap_channel_t *chan __unused,
+ const char * const *fields __unused, size_t nfields __unused)
+{
+
+ return (0);
+}
+
+static inline int
+cap_pwd_limit_users(cap_channel_t *chan __unused,
+ const char * const *names __unused, size_t nnames __unused,
+ uid_t *uids __unused, size_t nuids __unused)
+{
+
+ return (0);
+}
+#endif
+
+#endif /* !_CAP_PWD_H_ */
diff --git a/lib/libcasper/services/cap_pwd/tests/Makefile b/lib/libcasper/services/cap_pwd/tests/Makefile
new file mode 100644
index 000000000000..79df81416d45
--- /dev/null
+++ b/lib/libcasper/services/cap_pwd/tests/Makefile
@@ -0,0 +1,12 @@
+.include <src.opts.mk>
+
+TAP_TESTS_C= pwd_test
+
+.if ${MK_CASPER} != "no"
+LIBADD+= casper
+LIBADD+= cap_pwd
+CFLAGS+=-DWITH_CASPER
+.endif
+LIBADD+= nv
+
+.include <bsd.test.mk>
diff --git a/lib/libcasper/services/cap_pwd/tests/Makefile.depend b/lib/libcasper/services/cap_pwd/tests/Makefile.depend
new file mode 100644
index 000000000000..e03864c8ad46
--- /dev/null
+++ b/lib/libcasper/services/cap_pwd/tests/Makefile.depend
@@ -0,0 +1,18 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcasper/services/cap_pwd \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_pwd/tests/pwd_test.c b/lib/libcasper/services/cap_pwd/tests/pwd_test.c
new file mode 100644
index 000000000000..68bb00650350
--- /dev/null
+++ b/lib/libcasper/services/cap_pwd/tests/pwd_test.c
@@ -0,0 +1,1538 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/capsicum.h>
+#include <sys/nv.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcasper.h>
+
+#include <casper/cap_pwd.h>
+
+static int ntest = 1;
+
+#define CHECK(expr) do { \
+ if ((expr)) \
+ printf("ok %d # %s:%u\n", ntest, __FILE__, __LINE__); \
+ else \
+ printf("not ok %d # %s:%u\n", ntest, __FILE__, __LINE__); \
+ fflush(stdout); \
+ ntest++; \
+} while (0)
+#define CHECKX(expr) do { \
+ if ((expr)) { \
+ printf("ok %d # %s:%u\n", ntest, __FILE__, __LINE__); \
+ } else { \
+ printf("not ok %d # %s:%u\n", ntest, __FILE__, __LINE__); \
+ exit(1); \
+ } \
+ fflush(stdout); \
+ ntest++; \
+} while (0)
+
+#define UID_ROOT 0
+#define UID_OPERATOR 2
+
+#define GETPWENT0 0x0001
+#define GETPWENT1 0x0002
+#define GETPWENT2 0x0004
+#define GETPWENT (GETPWENT0 | GETPWENT1 | GETPWENT2)
+#define GETPWENT_R0 0x0008
+#define GETPWENT_R1 0x0010
+#define GETPWENT_R2 0x0020
+#define GETPWENT_R (GETPWENT_R0 | GETPWENT_R1 | GETPWENT_R2)
+#define GETPWNAM 0x0040
+#define GETPWNAM_R 0x0080
+#define GETPWUID 0x0100
+#define GETPWUID_R 0x0200
+
+static bool
+passwd_compare(const struct passwd *pwd0, const struct passwd *pwd1)
+{
+
+ if (pwd0 == NULL && pwd1 == NULL)
+ return (true);
+ if (pwd0 == NULL || pwd1 == NULL)
+ return (false);
+
+ if (strcmp(pwd0->pw_name, pwd1->pw_name) != 0)
+ return (false);
+
+ if (pwd0->pw_passwd != NULL || pwd1->pw_passwd != NULL) {
+ if (pwd0->pw_passwd == NULL || pwd1->pw_passwd == NULL)
+ return (false);
+ if (strcmp(pwd0->pw_passwd, pwd1->pw_passwd) != 0)
+ return (false);
+ }
+
+ if (pwd0->pw_uid != pwd1->pw_uid)
+ return (false);
+
+ if (pwd0->pw_gid != pwd1->pw_gid)
+ return (false);
+
+ if (pwd0->pw_change != pwd1->pw_change)
+ return (false);
+
+ if (pwd0->pw_class != NULL || pwd1->pw_class != NULL) {
+ if (pwd0->pw_class == NULL || pwd1->pw_class == NULL)
+ return (false);
+ if (strcmp(pwd0->pw_class, pwd1->pw_class) != 0)
+ return (false);
+ }
+
+ if (pwd0->pw_gecos != NULL || pwd1->pw_gecos != NULL) {
+ if (pwd0->pw_gecos == NULL || pwd1->pw_gecos == NULL)
+ return (false);
+ if (strcmp(pwd0->pw_gecos, pwd1->pw_gecos) != 0)
+ return (false);
+ }
+
+ if (pwd0->pw_dir != NULL || pwd1->pw_dir != NULL) {
+ if (pwd0->pw_dir == NULL || pwd1->pw_dir == NULL)
+ return (false);
+ if (strcmp(pwd0->pw_dir, pwd1->pw_dir) != 0)
+ return (false);
+ }
+
+ if (pwd0->pw_shell != NULL || pwd1->pw_shell != NULL) {
+ if (pwd0->pw_shell == NULL || pwd1->pw_shell == NULL)
+ return (false);
+ if (strcmp(pwd0->pw_shell, pwd1->pw_shell) != 0)
+ return (false);
+ }
+
+ if (pwd0->pw_expire != pwd1->pw_expire)
+ return (false);
+
+ if (pwd0->pw_fields != pwd1->pw_fields)
+ return (false);
+
+ return (true);
+}
+
+static unsigned int
+runtest_cmds(cap_channel_t *cappwd)
+{
+ char bufs[1024], bufc[1024];
+ unsigned int result;
+ struct passwd *pwds, *pwdc;
+ struct passwd sts, stc;
+
+ result = 0;
+
+ setpwent();
+ cap_setpwent(cappwd);
+
+ pwds = getpwent();
+ pwdc = cap_getpwent(cappwd);
+ if (passwd_compare(pwds, pwdc)) {
+ result |= GETPWENT0;
+ pwds = getpwent();
+ pwdc = cap_getpwent(cappwd);
+ if (passwd_compare(pwds, pwdc))
+ result |= GETPWENT1;
+ }
+
+ getpwent_r(&sts, bufs, sizeof(bufs), &pwds);
+ cap_getpwent_r(cappwd, &stc, bufc, sizeof(bufc), &pwdc);
+ if (passwd_compare(pwds, pwdc)) {
+ result |= GETPWENT_R0;
+ getpwent_r(&sts, bufs, sizeof(bufs), &pwds);
+ cap_getpwent_r(cappwd, &stc, bufc, sizeof(bufc), &pwdc);
+ if (passwd_compare(pwds, pwdc))
+ result |= GETPWENT_R1;
+ }
+
+ setpwent();
+ cap_setpwent(cappwd);
+
+ getpwent_r(&sts, bufs, sizeof(bufs), &pwds);
+ cap_getpwent_r(cappwd, &stc, bufc, sizeof(bufc), &pwdc);
+ if (passwd_compare(pwds, pwdc))
+ result |= GETPWENT_R2;
+
+ pwds = getpwent();
+ pwdc = cap_getpwent(cappwd);
+ if (passwd_compare(pwds, pwdc))
+ result |= GETPWENT2;
+
+ pwds = getpwnam("root");
+ pwdc = cap_getpwnam(cappwd, "root");
+ if (passwd_compare(pwds, pwdc)) {
+ pwds = getpwnam("operator");
+ pwdc = cap_getpwnam(cappwd, "operator");
+ if (passwd_compare(pwds, pwdc))
+ result |= GETPWNAM;
+ }
+
+ getpwnam_r("root", &sts, bufs, sizeof(bufs), &pwds);
+ cap_getpwnam_r(cappwd, "root", &stc, bufc, sizeof(bufc), &pwdc);
+ if (passwd_compare(pwds, pwdc)) {
+ getpwnam_r("operator", &sts, bufs, sizeof(bufs), &pwds);
+ cap_getpwnam_r(cappwd, "operator", &stc, bufc, sizeof(bufc),
+ &pwdc);
+ if (passwd_compare(pwds, pwdc))
+ result |= GETPWNAM_R;
+ }
+
+ pwds = getpwuid(UID_ROOT);
+ pwdc = cap_getpwuid(cappwd, UID_ROOT);
+ if (passwd_compare(pwds, pwdc)) {
+ pwds = getpwuid(UID_OPERATOR);
+ pwdc = cap_getpwuid(cappwd, UID_OPERATOR);
+ if (passwd_compare(pwds, pwdc))
+ result |= GETPWUID;
+ }
+
+ getpwuid_r(UID_ROOT, &sts, bufs, sizeof(bufs), &pwds);
+ cap_getpwuid_r(cappwd, UID_ROOT, &stc, bufc, sizeof(bufc), &pwdc);
+ if (passwd_compare(pwds, pwdc)) {
+ getpwuid_r(UID_OPERATOR, &sts, bufs, sizeof(bufs), &pwds);
+ cap_getpwuid_r(cappwd, UID_OPERATOR, &stc, bufc, sizeof(bufc),
+ &pwdc);
+ if (passwd_compare(pwds, pwdc))
+ result |= GETPWUID_R;
+ }
+
+ return (result);
+}
+
+static void
+test_cmds(cap_channel_t *origcappwd)
+{
+ cap_channel_t *cappwd;
+ const char *cmds[7], *fields[10], *names[6];
+ uid_t uids[5];
+
+ fields[0] = "pw_name";
+ fields[1] = "pw_passwd";
+ fields[2] = "pw_uid";
+ fields[3] = "pw_gid";
+ fields[4] = "pw_change";
+ fields[5] = "pw_class";
+ fields[6] = "pw_gecos";
+ fields[7] = "pw_dir";
+ fields[8] = "pw_shell";
+ fields[9] = "pw_expire";
+
+ names[0] = "root";
+ names[1] = "toor";
+ names[2] = "daemon";
+ names[3] = "operator";
+ names[4] = "bin";
+ names[5] = "kmem";
+
+ uids[0] = 0;
+ uids[1] = 1;
+ uids[2] = 2;
+ uids[3] = 3;
+ uids[4] = 5;
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r,
+ * getpwuid, getpwuid_r
+ * users:
+ * names: root, toor, daemon, operator, bin, kmem
+ * uids:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == 0);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R |
+ GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r,
+ * getpwuid, getpwuid_r
+ * users:
+ * names:
+ * uids: 0, 1, 2, 3, 5
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == 0);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R |
+ GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: getpwent, getpwent_r, getpwnam, getpwnam_r,
+ * getpwuid, getpwuid_r
+ * users:
+ * names: root, toor, daemon, operator, bin, kmem
+ * uids:
+ * Disallow:
+ * cmds: setpwent
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cap_setpwent(cappwd);
+
+ cmds[0] = "getpwent";
+ cmds[1] = "getpwent_r";
+ cmds[2] = "getpwnam";
+ cmds[3] = "getpwnam_r";
+ cmds[4] = "getpwuid";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "setpwent";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT0 | GETPWENT1 | GETPWENT_R0 |
+ GETPWENT_R1 | GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: getpwent, getpwent_r, getpwnam, getpwnam_r,
+ * getpwuid, getpwuid_r
+ * users:
+ * names:
+ * uids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: setpwent
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cap_setpwent(cappwd);
+
+ cmds[0] = "getpwent";
+ cmds[1] = "getpwent_r";
+ cmds[2] = "getpwnam";
+ cmds[3] = "getpwnam_r";
+ cmds[4] = "getpwuid";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "setpwent";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT0 | GETPWENT1 | GETPWENT_R0 |
+ GETPWENT_R1 | GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent_r, getpwnam, getpwnam_r,
+ * getpwuid, getpwuid_r
+ * users:
+ * names: root, toor, daemon, operator, bin, kmem
+ * uids:
+ * Disallow:
+ * cmds: getpwent
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent_r";
+ cmds[2] = "getpwnam";
+ cmds[3] = "getpwnam_r";
+ cmds[4] = "getpwuid";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwent";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT_R2 |
+ GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent_r, getpwnam, getpwnam_r,
+ * getpwuid, getpwuid_r
+ * users:
+ * names:
+ * uids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getpwent
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent_r";
+ cmds[2] = "getpwnam";
+ cmds[3] = "getpwnam_r";
+ cmds[4] = "getpwuid";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwent";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT_R2 |
+ GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwnam, getpwnam_r,
+ * getpwuid, getpwuid_r
+ * users:
+ * names: root, toor, daemon, operator, bin, kmem
+ * uids:
+ * Disallow:
+ * cmds: getpwent_r
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwnam";
+ cmds[3] = "getpwnam_r";
+ cmds[4] = "getpwuid";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwent_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT0 | GETPWENT1 |
+ GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwnam, getpwnam_r,
+ * getpwuid, getpwuid_r
+ * users:
+ * names:
+ * uids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getpwent_r
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwnam";
+ cmds[3] = "getpwnam_r";
+ cmds[4] = "getpwuid";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwent_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT0 | GETPWENT1 |
+ GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwent_r, getpwnam_r,
+ * getpwuid, getpwuid_r
+ * users:
+ * names: root, toor, daemon, operator, bin, kmem
+ * uids:
+ * Disallow:
+ * cmds: getpwnam
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam_r";
+ cmds[4] = "getpwuid";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwnam";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R |
+ GETPWNAM_R | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwent_r, getpwnam_r,
+ * getpwuid, getpwuid_r
+ * users:
+ * names:
+ * uids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getpwnam
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam_r";
+ cmds[4] = "getpwuid";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwnam";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R |
+ GETPWNAM_R | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwent_r, getpwnam,
+ * getpwuid, getpwuid_r
+ * users:
+ * names: root, toor, daemon, operator, bin, kmem
+ * uids:
+ * Disallow:
+ * cmds: getpwnam_r
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwuid";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwnam_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R |
+ GETPWNAM | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwent_r, getpwnam,
+ * getpwuid, getpwuid_r
+ * users:
+ * names:
+ * uids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getpwnam_r
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwuid";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwnam_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R |
+ GETPWNAM | GETPWUID | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r,
+ * getpwuid_r
+ * users:
+ * names: root, toor, daemon, operator, bin, kmem
+ * uids:
+ * Disallow:
+ * cmds: getpwuid
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwuid";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R |
+ GETPWNAM | GETPWNAM_R | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r,
+ * getpwuid_r
+ * users:
+ * names:
+ * uids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getpwuid
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwuid";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R |
+ GETPWNAM | GETPWNAM_R | GETPWUID_R));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r,
+ * getpwuid
+ * users:
+ * names: root, toor, daemon, operator, bin, kmem
+ * uids:
+ * Disallow:
+ * cmds: getpwuid_r
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R |
+ GETPWNAM | GETPWNAM_R | GETPWUID));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r,
+ * getpwuid
+ * users:
+ * names:
+ * uids: 0, 1, 2, 3, 5
+ * Disallow:
+ * cmds: getpwuid_r
+ * users:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0);
+ cmds[0] = "setpwent";
+ cmds[1] = "getpwent";
+ cmds[2] = "getpwent_r";
+ cmds[3] = "getpwnam";
+ cmds[4] = "getpwnam_r";
+ cmds[5] = "getpwuid";
+ cmds[6] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE);
+ cmds[0] = "getpwuid_r";
+ CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE);
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0);
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R |
+ GETPWNAM | GETPWNAM_R | GETPWUID));
+
+ cap_close(cappwd);
+}
+
+#define PW_NAME _PWF_NAME
+#define PW_PASSWD _PWF_PASSWD
+#define PW_UID _PWF_UID
+#define PW_GID _PWF_GID
+#define PW_CHANGE _PWF_CHANGE
+#define PW_CLASS _PWF_CLASS
+#define PW_GECOS _PWF_GECOS
+#define PW_DIR _PWF_DIR
+#define PW_SHELL _PWF_SHELL
+#define PW_EXPIRE _PWF_EXPIRE
+
+static unsigned int
+passwd_fields(const struct passwd *pwd)
+{
+ unsigned int result;
+
+ result = 0;
+
+ if (pwd->pw_name != NULL && pwd->pw_name[0] != '\0')
+ result |= PW_NAME;
+// else
+// printf("No pw_name\n");
+
+ if (pwd->pw_passwd != NULL && pwd->pw_passwd[0] != '\0')
+ result |= PW_PASSWD;
+ else if ((pwd->pw_fields & _PWF_PASSWD) != 0)
+ result |= PW_PASSWD;
+// else
+// printf("No pw_passwd\n");
+
+ if (pwd->pw_uid != (uid_t)-1)
+ result |= PW_UID;
+// else
+// printf("No pw_uid\n");
+
+ if (pwd->pw_gid != (gid_t)-1)
+ result |= PW_GID;
+// else
+// printf("No pw_gid\n");
+
+ if (pwd->pw_change != 0 || (pwd->pw_fields & _PWF_CHANGE) != 0)
+ result |= PW_CHANGE;
+// else
+// printf("No pw_change\n");
+
+ if (pwd->pw_class != NULL && pwd->pw_class[0] != '\0')
+ result |= PW_CLASS;
+ else if ((pwd->pw_fields & _PWF_CLASS) != 0)
+ result |= PW_CLASS;
+// else
+// printf("No pw_class\n");
+
+ if (pwd->pw_gecos != NULL && pwd->pw_gecos[0] != '\0')
+ result |= PW_GECOS;
+ else if ((pwd->pw_fields & _PWF_GECOS) != 0)
+ result |= PW_GECOS;
+// else
+// printf("No pw_gecos\n");
+
+ if (pwd->pw_dir != NULL && pwd->pw_dir[0] != '\0')
+ result |= PW_DIR;
+ else if ((pwd->pw_fields & _PWF_DIR) != 0)
+ result |= PW_DIR;
+// else
+// printf("No pw_dir\n");
+
+ if (pwd->pw_shell != NULL && pwd->pw_shell[0] != '\0')
+ result |= PW_SHELL;
+ else if ((pwd->pw_fields & _PWF_SHELL) != 0)
+ result |= PW_SHELL;
+// else
+// printf("No pw_shell\n");
+
+ if (pwd->pw_expire != 0 || (pwd->pw_fields & _PWF_EXPIRE) != 0)
+ result |= PW_EXPIRE;
+// else
+// printf("No pw_expire\n");
+
+if (false && pwd->pw_fields != (int)result) {
+printf("fields=0x%x != result=0x%x\n", (const unsigned int)pwd->pw_fields, result);
+printf(" fields result\n");
+printf("PW_NAME %d %d\n", (pwd->pw_fields & PW_NAME) != 0, (result & PW_NAME) != 0);
+printf("PW_PASSWD %d %d\n", (pwd->pw_fields & PW_PASSWD) != 0, (result & PW_PASSWD) != 0);
+printf("PW_UID %d %d\n", (pwd->pw_fields & PW_UID) != 0, (result & PW_UID) != 0);
+printf("PW_GID %d %d\n", (pwd->pw_fields & PW_GID) != 0, (result & PW_GID) != 0);
+printf("PW_CHANGE %d %d\n", (pwd->pw_fields & PW_CHANGE) != 0, (result & PW_CHANGE) != 0);
+printf("PW_CLASS %d %d\n", (pwd->pw_fields & PW_CLASS) != 0, (result & PW_CLASS) != 0);
+printf("PW_GECOS %d %d\n", (pwd->pw_fields & PW_GECOS) != 0, (result & PW_GECOS) != 0);
+printf("PW_DIR %d %d\n", (pwd->pw_fields & PW_DIR) != 0, (result & PW_DIR) != 0);
+printf("PW_SHELL %d %d\n", (pwd->pw_fields & PW_SHELL) != 0, (result & PW_SHELL) != 0);
+printf("PW_EXPIRE %d %d\n", (pwd->pw_fields & PW_EXPIRE) != 0, (result & PW_EXPIRE) != 0);
+}
+
+//printf("result=0x%x\n", result);
+ return (result);
+}
+
+static bool
+runtest_fields(cap_channel_t *cappwd, unsigned int expected)
+{
+ char buf[1024];
+ struct passwd *pwd;
+ struct passwd st;
+
+//printf("expected=0x%x\n", expected);
+ cap_setpwent(cappwd);
+ pwd = cap_getpwent(cappwd);
+ if ((passwd_fields(pwd) & ~expected) != 0)
+ return (false);
+
+ cap_setpwent(cappwd);
+ cap_getpwent_r(cappwd, &st, buf, sizeof(buf), &pwd);
+ if ((passwd_fields(pwd) & ~expected) != 0)
+ return (false);
+
+ pwd = cap_getpwnam(cappwd, "root");
+ if ((passwd_fields(pwd) & ~expected) != 0)
+ return (false);
+
+ cap_getpwnam_r(cappwd, "root", &st, buf, sizeof(buf), &pwd);
+ if ((passwd_fields(pwd) & ~expected) != 0)
+ return (false);
+
+ pwd = cap_getpwuid(cappwd, UID_ROOT);
+ if ((passwd_fields(pwd) & ~expected) != 0)
+ return (false);
+
+ cap_getpwuid_r(cappwd, UID_ROOT, &st, buf, sizeof(buf), &pwd);
+ if ((passwd_fields(pwd) & ~expected) != 0)
+ return (false);
+
+ return (true);
+}
+
+static void
+test_fields(cap_channel_t *origcappwd)
+{
+ cap_channel_t *cappwd;
+ const char *fields[10];
+
+ /* No limits. */
+
+ CHECK(runtest_fields(origcappwd, PW_NAME | PW_PASSWD | PW_UID |
+ PW_GID | PW_CHANGE | PW_CLASS | PW_GECOS | PW_DIR | PW_SHELL |
+ PW_EXPIRE));
+
+ /*
+ * Allow:
+ * fields: pw_name, pw_passwd, pw_uid, pw_gid, pw_change, pw_class,
+ * pw_gecos, pw_dir, pw_shell, pw_expire
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ fields[0] = "pw_name";
+ fields[1] = "pw_passwd";
+ fields[2] = "pw_uid";
+ fields[3] = "pw_gid";
+ fields[4] = "pw_change";
+ fields[5] = "pw_class";
+ fields[6] = "pw_gecos";
+ fields[7] = "pw_dir";
+ fields[8] = "pw_shell";
+ fields[9] = "pw_expire";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0);
+
+ CHECK(runtest_fields(origcappwd, PW_NAME | PW_PASSWD | PW_UID |
+ PW_GID | PW_CHANGE | PW_CLASS | PW_GECOS | PW_DIR | PW_SHELL |
+ PW_EXPIRE));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * fields: pw_name, pw_passwd, pw_uid, pw_gid, pw_change
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ fields[0] = "pw_name";
+ fields[1] = "pw_passwd";
+ fields[2] = "pw_uid";
+ fields[3] = "pw_gid";
+ fields[4] = "pw_change";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 5) == 0);
+ fields[5] = "pw_class";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 6) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "pw_class";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(cappwd, PW_NAME | PW_PASSWD | PW_UID |
+ PW_GID | PW_CHANGE));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * fields: pw_class, pw_gecos, pw_dir, pw_shell, pw_expire
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ fields[0] = "pw_class";
+ fields[1] = "pw_gecos";
+ fields[2] = "pw_dir";
+ fields[3] = "pw_shell";
+ fields[4] = "pw_expire";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 5) == 0);
+ fields[5] = "pw_uid";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 6) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "pw_uid";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(cappwd, PW_CLASS | PW_GECOS | PW_DIR |
+ PW_SHELL | PW_EXPIRE));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * fields: pw_name, pw_uid, pw_change, pw_gecos, pw_shell
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ fields[0] = "pw_name";
+ fields[1] = "pw_uid";
+ fields[2] = "pw_change";
+ fields[3] = "pw_gecos";
+ fields[4] = "pw_shell";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 5) == 0);
+ fields[5] = "pw_class";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 6) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "pw_class";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(cappwd, PW_NAME | PW_UID | PW_CHANGE |
+ PW_GECOS | PW_SHELL));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * fields: pw_passwd, pw_gid, pw_class, pw_dir, pw_expire
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ fields[0] = "pw_passwd";
+ fields[1] = "pw_gid";
+ fields[2] = "pw_class";
+ fields[3] = "pw_dir";
+ fields[4] = "pw_expire";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 5) == 0);
+ fields[5] = "pw_uid";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 6) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "pw_uid";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(cappwd, PW_PASSWD | PW_GID | PW_CLASS |
+ PW_DIR | PW_EXPIRE));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * fields: pw_uid, pw_class, pw_shell
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ fields[0] = "pw_uid";
+ fields[1] = "pw_class";
+ fields[2] = "pw_shell";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 3) == 0);
+ fields[3] = "pw_change";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "pw_change";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(cappwd, PW_UID | PW_CLASS | PW_SHELL));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * fields: pw_change
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ fields[0] = "pw_change";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == 0);
+ fields[1] = "pw_uid";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 2) == -1 &&
+ errno == ENOTCAPABLE);
+ fields[0] = "pw_uid";
+ CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 &&
+ errno == ENOTCAPABLE);
+
+ CHECK(runtest_fields(cappwd, PW_CHANGE));
+
+ cap_close(cappwd);
+}
+
+static bool
+runtest_users(cap_channel_t *cappwd, const char **names, const uid_t *uids,
+ size_t nusers)
+{
+ char buf[1024];
+ struct passwd *pwd;
+ struct passwd st;
+ unsigned int i, got;
+
+ cap_setpwent(cappwd);
+ got = 0;
+ for (;;) {
+ pwd = cap_getpwent(cappwd);
+ if (pwd == NULL)
+ break;
+ got++;
+ for (i = 0; i < nusers; i++) {
+ if (strcmp(names[i], pwd->pw_name) == 0 &&
+ uids[i] == pwd->pw_uid) {
+ break;
+ }
+ }
+ if (i == nusers)
+ return (false);
+ }
+ if (got != nusers)
+ return (false);
+
+ cap_setpwent(cappwd);
+ got = 0;
+ for (;;) {
+ cap_getpwent_r(cappwd, &st, buf, sizeof(buf), &pwd);
+ if (pwd == NULL)
+ break;
+ got++;
+ for (i = 0; i < nusers; i++) {
+ if (strcmp(names[i], pwd->pw_name) == 0 &&
+ uids[i] == pwd->pw_uid) {
+ break;
+ }
+ }
+ if (i == nusers)
+ return (false);
+ }
+ if (got != nusers)
+ return (false);
+
+ for (i = 0; i < nusers; i++) {
+ pwd = cap_getpwnam(cappwd, names[i]);
+ if (pwd == NULL)
+ return (false);
+ }
+
+ for (i = 0; i < nusers; i++) {
+ cap_getpwnam_r(cappwd, names[i], &st, buf, sizeof(buf), &pwd);
+ if (pwd == NULL)
+ return (false);
+ }
+
+ for (i = 0; i < nusers; i++) {
+ pwd = cap_getpwuid(cappwd, uids[i]);
+ if (pwd == NULL)
+ return (false);
+ }
+
+ for (i = 0; i < nusers; i++) {
+ cap_getpwuid_r(cappwd, uids[i], &st, buf, sizeof(buf), &pwd);
+ if (pwd == NULL)
+ return (false);
+ }
+
+ return (true);
+}
+
+static void
+test_users(cap_channel_t *origcappwd)
+{
+ cap_channel_t *cappwd;
+ const char *names[6];
+ uid_t uids[6];
+
+ /*
+ * Allow:
+ * users:
+ * names: root, toor, daemon, operator, bin, tty
+ * uids:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ names[0] = "root";
+ names[1] = "toor";
+ names[2] = "daemon";
+ names[3] = "operator";
+ names[4] = "bin";
+ names[5] = "tty";
+ CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0);
+ uids[0] = 0;
+ uids[1] = 0;
+ uids[2] = 1;
+ uids[3] = 2;
+ uids[4] = 3;
+ uids[5] = 4;
+
+ CHECK(runtest_users(cappwd, names, uids, 6));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * users:
+ * names: daemon, operator, bin
+ * uids:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ names[0] = "daemon";
+ names[1] = "operator";
+ names[2] = "bin";
+ CHECK(cap_pwd_limit_users(cappwd, names, 3, NULL, 0) == 0);
+ names[3] = "tty";
+ CHECK(cap_pwd_limit_users(cappwd, names, 4, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "tty";
+ CHECK(cap_pwd_limit_users(cappwd, names, 1, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "daemon";
+ uids[0] = 1;
+ uids[1] = 2;
+ uids[2] = 3;
+
+ CHECK(runtest_users(cappwd, names, uids, 3));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * users:
+ * names: daemon, bin, tty
+ * uids:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ names[0] = "daemon";
+ names[1] = "bin";
+ names[2] = "tty";
+ CHECK(cap_pwd_limit_users(cappwd, names, 3, NULL, 0) == 0);
+ names[3] = "operator";
+ CHECK(cap_pwd_limit_users(cappwd, names, 4, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "operator";
+ CHECK(cap_pwd_limit_users(cappwd, names, 1, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "daemon";
+ uids[0] = 1;
+ uids[1] = 3;
+ uids[2] = 4;
+
+ CHECK(runtest_users(cappwd, names, uids, 3));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * users:
+ * names:
+ * uids: 1, 2, 3
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ names[0] = "daemon";
+ names[1] = "operator";
+ names[2] = "bin";
+ uids[0] = 1;
+ uids[1] = 2;
+ uids[2] = 3;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 3) == 0);
+ uids[3] = 4;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ uids[0] = 4;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 1) == -1 &&
+ errno == ENOTCAPABLE);
+ uids[0] = 1;
+
+ CHECK(runtest_users(cappwd, names, uids, 3));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * users:
+ * names:
+ * uids: 1, 3, 4
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ names[0] = "daemon";
+ names[1] = "bin";
+ names[2] = "tty";
+ uids[0] = 1;
+ uids[1] = 3;
+ uids[2] = 4;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 3) == 0);
+ uids[3] = 5;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 4) == -1 &&
+ errno == ENOTCAPABLE);
+ uids[0] = 5;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 1) == -1 &&
+ errno == ENOTCAPABLE);
+ uids[0] = 1;
+
+ CHECK(runtest_users(cappwd, names, uids, 3));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * users:
+ * names: bin
+ * uids:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ names[0] = "bin";
+ CHECK(cap_pwd_limit_users(cappwd, names, 1, NULL, 0) == 0);
+ names[1] = "operator";
+ CHECK(cap_pwd_limit_users(cappwd, names, 2, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "operator";
+ CHECK(cap_pwd_limit_users(cappwd, names, 1, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "bin";
+ uids[0] = 3;
+
+ CHECK(runtest_users(cappwd, names, uids, 1));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * users:
+ * names: daemon, tty
+ * uids:
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ names[0] = "daemon";
+ names[1] = "tty";
+ CHECK(cap_pwd_limit_users(cappwd, names, 2, NULL, 0) == 0);
+ names[2] = "operator";
+ CHECK(cap_pwd_limit_users(cappwd, names, 3, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "operator";
+ CHECK(cap_pwd_limit_users(cappwd, names, 1, NULL, 0) == -1 &&
+ errno == ENOTCAPABLE);
+ names[0] = "daemon";
+ uids[0] = 1;
+ uids[1] = 4;
+
+ CHECK(runtest_users(cappwd, names, uids, 2));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * users:
+ * names:
+ * uids: 3
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ names[0] = "bin";
+ uids[0] = 3;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 1) == 0);
+ uids[1] = 4;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 2) == -1 &&
+ errno == ENOTCAPABLE);
+ uids[0] = 4;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 1) == -1 &&
+ errno == ENOTCAPABLE);
+ uids[0] = 3;
+
+ CHECK(runtest_users(cappwd, names, uids, 1));
+
+ cap_close(cappwd);
+
+ /*
+ * Allow:
+ * users:
+ * names:
+ * uids: 1, 4
+ */
+ cappwd = cap_clone(origcappwd);
+ CHECK(cappwd != NULL);
+
+ names[0] = "daemon";
+ names[1] = "tty";
+ uids[0] = 1;
+ uids[1] = 4;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 2) == 0);
+ uids[2] = 3;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 3) == -1 &&
+ errno == ENOTCAPABLE);
+ uids[0] = 3;
+ CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 1) == -1 &&
+ errno == ENOTCAPABLE);
+ uids[0] = 1;
+
+ CHECK(runtest_users(cappwd, names, uids, 2));
+
+ cap_close(cappwd);
+}
+
+int
+main(void)
+{
+ cap_channel_t *capcas, *cappwd;
+
+ printf("1..188\n");
+ fflush(stdout);
+
+ capcas = cap_init();
+ CHECKX(capcas != NULL);
+
+ cappwd = cap_service_open(capcas, "system.pwd");
+ CHECKX(cappwd != NULL);
+
+ cap_close(capcas);
+
+ /* No limits. */
+
+ CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | GETPWNAM |
+ GETPWNAM_R | GETPWUID | GETPWUID_R));
+
+ test_cmds(cappwd);
+
+ test_fields(cappwd);
+
+ test_users(cappwd);
+
+ cap_close(cappwd);
+
+ exit(0);
+}
diff --git a/lib/libcasper/services/cap_sysctl/Makefile b/lib/libcasper/services/cap_sysctl/Makefile
new file mode 100644
index 000000000000..522313df4ffc
--- /dev/null
+++ b/lib/libcasper/services/cap_sysctl/Makefile
@@ -0,0 +1,34 @@
+SHLIBDIR?= /lib
+
+.include <src.opts.mk>
+
+PACKAGE= runtime
+
+SHLIB_MAJOR= 2
+INCSDIR?= ${INCLUDEDIR}/casper
+
+.if ${MK_CASPER} != "no"
+SHLIB= cap_sysctl
+
+SRCS= cap_sysctl.c
+.endif
+
+INCS= cap_sysctl.h
+
+LIBADD= nv
+
+CFLAGS+=-I${.CURDIR}
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+MAN+= cap_sysctl.3
+MLINKS+=cap_sysctl.3 libcap_sysctl.3 \
+ cap_sysctl.3 cap_sysctlbyname.3 \
+ cap_sysctl.3 cap_nametomib.3 \
+ cap_sysctl.3 cap_sysctl_limit_init.3 \
+ cap_sysctl.3 cap_sysctl_limit_mib.3 \
+ cap_sysctl.3 cap_sysctl_limit_name.3 \
+ cap_sysctl.3 cap_sysctl_limit.3
+
+.include <bsd.lib.mk>
diff --git a/lib/libcasper/services/cap_sysctl/Makefile.depend b/lib/libcasper/services/cap_sysctl/Makefile.depend
new file mode 100644
index 000000000000..02bae00eb04d
--- /dev/null
+++ b/lib/libcasper/services/cap_sysctl/Makefile.depend
@@ -0,0 +1,17 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_sysctl/cap_sysctl.3 b/lib/libcasper/services/cap_sysctl/cap_sysctl.3
new file mode 100644
index 000000000000..2c7a491a1f8b
--- /dev/null
+++ b/lib/libcasper/services/cap_sysctl/cap_sysctl.3
@@ -0,0 +1,227 @@
+.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd December 6, 2023
+.Dt CAP_SYSCTL 3
+.Os
+.Sh NAME
+.Nm cap_sysctl
+.Nd "library for getting or setting system information in capability mode"
+.Sh LIBRARY
+.Lb libcap_sysctl
+.Sh SYNOPSIS
+.In libcasper.h
+.In casper/cap_sysctl.h
+.Ft int
+.Fn cap_sysctl "cap_channel_t *chan" "const int *name" "u_int namelen" "void *oldp" "size_t *oldlenp" "const void *newp" "size_t newlen"
+.Ft int
+.Fn cap_sysctlbyname "cap_channel_t *chan" "const char *name" "void *oldp" "size_t *oldlenp" "const void *newp" "size_t newlen"
+.Ft int
+.Fn cap_sysctlnametomib "cap_channel_t *chan" "const char *name" "int *mibp" "size_t *sizep"
+.Ft cap_sysctl_limit_t *
+.Fn cap_sysctl_limit_init "cap_channel_t *chan"
+.Ft cap_sysctl_limit_t *
+.Fn cap_sysctl_limit_name "cap_sysctl_limit_t *limit" "const char *name" "int flags"
+.Ft cap_sysctl_limit_t *
+.Fn cap_sysctl_limit_mib "cap_sysctl_limit_t *limit" "const int *mibp" "u_int miblen" "int flags"
+.Ft int
+.Fn cap_sysctl_limit "cap_sysctl_limit_t *limit"
+.Sh DESCRIPTION
+The
+.Fn cap_sysctl ,
+.Fn cap_sysctlbyname
+and
+.Fn cap_sysctlnametomib
+functions are equivalent to
+.Xr sysctl 3 ,
+.Xr sysctlbyname 3
+and
+.Xr sysctlnametomib 3 ,
+except that they are implemented by the
+.Ql system.sysctl
+.Xr libcasper 3
+service and require a corresponding
+.Xr libcasper 3
+capability.
+.Pp
+All of these functions, with the exceptions of
+.Fn cap_sysctl_limit_init
+and
+.Fn cap_sysctl_limit_mib ,
+are reentrant but not thread-safe.
+That is, they may be called from separate threads only with different
+.Vt cap_channel_t
+arguments or with synchronization.
+.Sh LIMITS
+By default, the
+.Nm
+capability provides unrestricted access to the sysctl namespace.
+Applications typically only require access to a small number of sysctl
+variables; the
+.Fn cap_sysctl_limit
+interface can be used to restrict the sysctls that can be accessed using
+the
+.Nm
+capability.
+.Fn cap_sysctl_limit_init
+returns an opaque limit handle used to store a list of permitted sysctls
+and access rights.
+Rights are encoded using the following flags:
+.Pp
+.Bd -literal -offset indent -compact
+CAP_SYSCTL_READ allow reads of the sysctl variable
+CAP_SYSCTL_WRITE allow writes of the sysctl variable
+CAP_SYSCTL_RDWR allow reads and writes of the sysctl variable
+CAP_RECURSIVE permit access to any child of the sysctl variable
+.Ed
+.Pp
+The
+.Fn cap_sysctl_limit_name
+function adds the sysctl identified by
+.Ar name
+to the limit list, and
+.Fn cap_sysctl_limit_mib
+function adds the sysctl identified by
+.Ar mibp
+to the limit list.
+The access rights for the sysctl are specified in the
+.Ar flags
+parameter; at least one of
+.Dv CAP_SYSCTL_READ ,
+.Dv CAP_SYSCTL_WRITE
+and
+.Dv CAP_SYSCTL_RDWR
+must be specified.
+.Fn cap_sysctl_limit
+applies a set of sysctl limits to the capability, denying access to sysctl
+variables not belonging to the set.
+It consumes the limit handle.
+After either success or failure, the user must not access the handle again.
+.Pp
+Once a set of limits is applied, subsequent calls to
+.Fn cap_sysctl_limit
+will fail unless the new set is a subset of the current set.
+.Pp
+.Fn cap_sysctlnametomib
+will succeed so long as the named sysctl variable is present in the limit set,
+regardless of its access rights.
+When a sysctl variable name is added to a limit set, its MIB identifier is
+automatically added to the set.
+.Sh EXAMPLES
+The following example first opens a capability to casper, uses this
+capability to create the
+.Nm system.sysctl
+casper service, and then uses the
+.Nm
+capability to get the value of
+.Dv kern.trap_enotcap .
+.Bd -literal
+cap_channel_t *capcas, *capsysctl;
+const char *name = "kern.trap_enotcap";
+void *limit;
+size_t size;
+bool value;
+
+/* Open capability to Casper. */
+capcas = cap_init();
+if (capcas == NULL)
+ err(1, "Unable to contact Casper");
+
+/* Enter capability mode sandbox. */
+if (cap_enter() < 0 && errno != ENOSYS)
+ err(1, "Unable to enter capability mode");
+
+/* Use Casper capability to create capability to the system.sysctl service. */
+capsysctl = cap_service_open(capcas, "system.sysctl");
+if (capsysctl == NULL)
+ err(1, "Unable to open system.sysctl service");
+
+/* Close Casper capability, we don't need it anymore. */
+cap_close(capcas);
+
+/* Create limit for one MIB with read access only. */
+limit = cap_sysctl_limit_init(capsysctl);
+(void)cap_sysctl_limit_name(limit, name, CAP_SYSCTL_READ);
+
+/* Limit system.sysctl. */
+if (cap_sysctl_limit(limit) < 0)
+ err(1, "Unable to set limits");
+
+/* Fetch value. */
+size = sizeof(value);
+if (cap_sysctlbyname(capsysctl, name, &value, &size, NULL, 0) < 0)
+ err(1, "Unable to get value of sysctl");
+
+printf("The value of %s is %d.\\n", name, value);
+
+cap_close(capsysctl);
+.Ed
+.Sh RETURN VALUES
+.Fn cap_sysctl_limit_init
+will return a new limit handle on success or
+.Dv NULL
+on failure, and set
+.Va errno .
+.Fn cap_sysctl_limit_mib
+and
+.Fn cap_sysctl_limit_name
+will return the modified limit handle on success or
+.Dv NULL
+on failure and set
+.Va errno .
+After failure, the caller must not access the limit handle again.
+.Fn cap_sysctl_limit
+will return
+.Dv -1
+on failure and set
+.Va errno .
+.Fn cap_sysctl ,
+.Fn cap_sysctlbyname ,
+and
+.Fn cap_sysctlnametomib
+have the same return values as their non-capability-mode equivalents as
+documented in
+.Xr sysctl 3 .
+.Sh SEE ALSO
+.Xr cap_enter 2 ,
+.Xr err 3 ,
+.Xr sysctl 3 ,
+.Xr sysctlbyname 3 ,
+.Xr sysctlnametomib 3 ,
+.Xr capsicum 4 ,
+.Xr nv 9
+.Sh HISTORY
+The
+.Nm cap_sysctl
+service first appeared in
+.Fx 10.3 .
+.Sh AUTHORS
+The
+.Nm cap_sysctl
+service was implemented by
+.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net
+under sponsorship from the FreeBSD Foundation.
+.Pp
+This manual page was written by
+.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org .
diff --git a/lib/libcasper/services/cap_sysctl/cap_sysctl.c b/lib/libcasper/services/cap_sysctl/cap_sysctl.c
new file mode 100644
index 000000000000..c99fd74cb169
--- /dev/null
+++ b/lib/libcasper/services/cap_sysctl/cap_sysctl.c
@@ -0,0 +1,531 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013, 2018 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Portions of this software were developed by Mark Johnston
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/cnv.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+#include <sys/sysctl.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libcasper.h>
+#include <libcasper_service.h>
+
+#include "cap_sysctl.h"
+
+/*
+ * Limit interface.
+ */
+
+struct cap_sysctl_limit {
+ cap_channel_t *chan;
+ nvlist_t *nv;
+};
+
+cap_sysctl_limit_t *
+cap_sysctl_limit_init(cap_channel_t *chan)
+{
+ cap_sysctl_limit_t *limit;
+ int error;
+
+ limit = malloc(sizeof(*limit));
+ if (limit != NULL) {
+ limit->chan = chan;
+ limit->nv = nvlist_create(NV_FLAG_NO_UNIQUE);
+ if (limit->nv == NULL) {
+ error = errno;
+ free(limit);
+ limit = NULL;
+ errno = error;
+ }
+ }
+ return (limit);
+}
+
+cap_sysctl_limit_t *
+cap_sysctl_limit_name(cap_sysctl_limit_t *limit, const char *name, int flags)
+{
+ nvlist_t *lnv;
+ size_t mibsz;
+ int error, mib[CTL_MAXNAME];
+
+ lnv = nvlist_create(0);
+ if (lnv == NULL) {
+ error = errno;
+ if (limit->nv != NULL)
+ nvlist_destroy(limit->nv);
+ free(limit);
+ errno = error;
+ return (NULL);
+ }
+ nvlist_add_string(lnv, "name", name);
+ nvlist_add_number(lnv, "operation", flags);
+
+ mibsz = nitems(mib);
+ error = cap_sysctlnametomib(limit->chan, name, mib, &mibsz);
+ if (error == 0)
+ nvlist_add_binary(lnv, "mib", mib, mibsz * sizeof(int));
+
+ nvlist_move_nvlist(limit->nv, "limit", lnv);
+ return (limit);
+}
+
+cap_sysctl_limit_t *
+cap_sysctl_limit_mib(cap_sysctl_limit_t *limit, const int *mibp, u_int miblen,
+ int flags)
+{
+ nvlist_t *lnv;
+ int error;
+
+ lnv = nvlist_create(0);
+ if (lnv == NULL) {
+ error = errno;
+ if (limit->nv != NULL)
+ nvlist_destroy(limit->nv);
+ free(limit);
+ errno = error;
+ return (NULL);
+ }
+ nvlist_add_binary(lnv, "mib", mibp, miblen * sizeof(int));
+ nvlist_add_number(lnv, "operation", flags);
+ nvlist_add_nvlist(limit->nv, "limit", lnv);
+ return (limit);
+}
+
+int
+cap_sysctl_limit(cap_sysctl_limit_t *limit)
+{
+ cap_channel_t *chan;
+ nvlist_t *lnv;
+
+ chan = limit->chan;
+ lnv = limit->nv;
+ free(limit);
+
+ /* cap_limit_set(3) will always free the nvlist. */
+ return (cap_limit_set(chan, lnv));
+}
+
+/*
+ * Service interface.
+ */
+
+static int
+do_sysctl(cap_channel_t *chan, nvlist_t *nvl, void *oldp, size_t *oldlenp,
+ const void *newp, size_t newlen)
+{
+ const uint8_t *retoldp;
+ size_t oldlen;
+ int error;
+ uint8_t operation;
+
+ operation = 0;
+ if (oldlenp != NULL)
+ operation |= CAP_SYSCTL_READ;
+ if (newp != NULL)
+ operation |= CAP_SYSCTL_WRITE;
+ nvlist_add_number(nvl, "operation", (uint64_t)operation);
+ if (oldp == NULL && oldlenp != NULL)
+ nvlist_add_null(nvl, "justsize");
+ else if (oldlenp != NULL)
+ nvlist_add_number(nvl, "oldlen", (uint64_t)*oldlenp);
+ if (newp != NULL)
+ nvlist_add_binary(nvl, "newp", newp, newlen);
+
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL)
+ return (-1);
+ error = (int)dnvlist_get_number(nvl, "error", 0);
+ if (error != 0) {
+ nvlist_destroy(nvl);
+ errno = error;
+ return (-1);
+ }
+
+ if (oldp == NULL && oldlenp != NULL) {
+ *oldlenp = (size_t)nvlist_get_number(nvl, "oldlen");
+ } else if (oldp != NULL) {
+ retoldp = nvlist_get_binary(nvl, "oldp", &oldlen);
+ memcpy(oldp, retoldp, oldlen);
+ if (oldlenp != NULL)
+ *oldlenp = oldlen;
+ }
+
+ nvlist_destroy(nvl);
+
+ return (0);
+}
+
+int
+cap_sysctl(cap_channel_t *chan, const int *name, u_int namelen, void *oldp,
+ size_t *oldlenp, const void *newp, size_t newlen)
+{
+ nvlist_t *req;
+
+ req = nvlist_create(0);
+ nvlist_add_string(req, "cmd", "sysctl");
+ nvlist_add_binary(req, "mib", name, (size_t)namelen * sizeof(int));
+ return (do_sysctl(chan, req, oldp, oldlenp, newp, newlen));
+}
+
+int
+cap_sysctlbyname(cap_channel_t *chan, const char *name, void *oldp,
+ size_t *oldlenp, const void *newp, size_t newlen)
+{
+ nvlist_t *req;
+
+ req = nvlist_create(0);
+ nvlist_add_string(req, "cmd", "sysctlbyname");
+ nvlist_add_string(req, "name", name);
+ return (do_sysctl(chan, req, oldp, oldlenp, newp, newlen));
+}
+
+int
+cap_sysctlnametomib(cap_channel_t *chan, const char *name, int *mibp,
+ size_t *sizep)
+{
+ nvlist_t *req;
+ const void *mib;
+ size_t mibsz;
+ int error;
+
+ req = nvlist_create(0);
+ nvlist_add_string(req, "cmd", "sysctlnametomib");
+ nvlist_add_string(req, "name", name);
+ nvlist_add_number(req, "operation", 0);
+ nvlist_add_number(req, "size", (uint64_t)*sizep);
+
+ req = cap_xfer_nvlist(chan, req);
+ if (req == NULL)
+ return (-1);
+ error = (int)dnvlist_get_number(req, "error", 0);
+ if (error != 0) {
+ nvlist_destroy(req);
+ errno = error;
+ return (-1);
+ }
+
+ mib = nvlist_get_binary(req, "mib", &mibsz);
+ *sizep = mibsz / sizeof(int);
+
+ memcpy(mibp, mib, mibsz);
+
+ nvlist_destroy(req);
+
+ return (0);
+}
+
+/*
+ * Service implementation.
+ */
+
+/*
+ * Validate a sysctl description. This must consist of an nvlist with either a
+ * binary "mib" field or a string "name", and an operation.
+ */
+static int
+sysctl_valid(const nvlist_t *nvl, bool limit)
+{
+ const char *name;
+ void *cookie;
+ int type;
+ size_t size;
+ unsigned int field, fields;
+
+ /* NULL nvl is of course invalid. */
+ if (nvl == NULL)
+ return (EINVAL);
+ if (nvlist_error(nvl) != 0)
+ return (nvlist_error(nvl));
+
+#define HAS_NAME 0x01
+#define HAS_MIB 0x02
+#define HAS_ID (HAS_NAME | HAS_MIB)
+#define HAS_OPERATION 0x04
+
+ fields = 0;
+ cookie = NULL;
+ while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) {
+ if ((strcmp(name, "name") == 0 && type == NV_TYPE_STRING) ||
+ (strcmp(name, "mib") == 0 && type == NV_TYPE_BINARY)) {
+ if (strcmp(name, "mib") == 0) {
+ /* A MIB must be an array of integers. */
+ (void)cnvlist_get_binary(cookie, &size);
+ if (size % sizeof(int) != 0)
+ return (EINVAL);
+ field = HAS_MIB;
+ } else
+ field = HAS_NAME;
+
+ /*
+ * A limit may contain both a name and a MIB identifier.
+ */
+ if ((fields & field) != 0 ||
+ (!limit && (fields & HAS_ID) != 0))
+ return (EINVAL);
+ fields |= field;
+ } else if (strcmp(name, "operation") == 0) {
+ uint64_t mask, operation;
+
+ if (type != NV_TYPE_NUMBER)
+ return (EINVAL);
+
+ operation = cnvlist_get_number(cookie);
+
+ /*
+ * Requests can only include the RDWR flags; limits may
+ * also include the RECURSIVE flag.
+ */
+ mask = limit ? (CAP_SYSCTL_RDWR |
+ CAP_SYSCTL_RECURSIVE) : CAP_SYSCTL_RDWR;
+ if ((operation & ~mask) != 0 ||
+ (operation & CAP_SYSCTL_RDWR) == 0)
+ return (EINVAL);
+ /* Only one 'operation' can be present. */
+ if ((fields & HAS_OPERATION) != 0)
+ return (EINVAL);
+ fields |= HAS_OPERATION;
+ } else if (limit)
+ return (EINVAL);
+ }
+
+ if ((fields & HAS_OPERATION) == 0 || (fields & HAS_ID) == 0)
+ return (EINVAL);
+
+#undef HAS_OPERATION
+#undef HAS_ID
+#undef HAS_MIB
+#undef HAS_NAME
+
+ return (0);
+}
+
+static bool
+sysctl_allowed(const nvlist_t *limits, const nvlist_t *req)
+{
+ const nvlist_t *limit;
+ uint64_t op, reqop;
+ const char *lname, *name, *reqname;
+ void *cookie;
+ size_t lsize, reqsize;
+ const int *lmib, *reqmib;
+ int type;
+
+ if (limits == NULL)
+ return (true);
+
+ reqmib = dnvlist_get_binary(req, "mib", &reqsize, NULL, 0);
+ reqname = dnvlist_get_string(req, "name", NULL);
+ reqop = nvlist_get_number(req, "operation");
+
+ cookie = NULL;
+ while ((name = nvlist_next(limits, &type, &cookie)) != NULL) {
+ assert(type == NV_TYPE_NVLIST);
+
+ limit = cnvlist_get_nvlist(cookie);
+ op = nvlist_get_number(limit, "operation");
+ if ((reqop & op) != reqop)
+ continue;
+
+ if (reqname != NULL) {
+ lname = dnvlist_get_string(limit, "name", NULL);
+ if (lname == NULL)
+ continue;
+ if ((op & CAP_SYSCTL_RECURSIVE) == 0) {
+ if (strcmp(lname, reqname) != 0)
+ continue;
+ } else {
+ size_t namelen;
+
+ namelen = strlen(lname);
+ if (strncmp(lname, reqname, namelen) != 0)
+ continue;
+ if (reqname[namelen] != '.' &&
+ reqname[namelen] != '\0')
+ continue;
+ }
+ } else {
+ lmib = dnvlist_get_binary(limit, "mib", &lsize, NULL, 0);
+ if (lmib == NULL)
+ continue;
+ if (lsize > reqsize || ((op & CAP_SYSCTL_RECURSIVE) == 0 &&
+ lsize < reqsize))
+ continue;
+ if (memcmp(lmib, reqmib, lsize) != 0)
+ continue;
+ }
+
+ return (true);
+ }
+
+ return (false);
+}
+
+static int
+sysctl_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
+{
+ const nvlist_t *nvl;
+ const char *name;
+ void *cookie;
+ int error, type;
+
+ cookie = NULL;
+ while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
+ if (strcmp(name, "limit") != 0 || type != NV_TYPE_NVLIST)
+ return (EINVAL);
+ nvl = cnvlist_get_nvlist(cookie);
+ error = sysctl_valid(nvl, true);
+ if (error != 0)
+ return (error);
+ if (!sysctl_allowed(oldlimits, nvl))
+ return (ENOTCAPABLE);
+ }
+
+ return (0);
+}
+
+static int
+nametomib(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout)
+{
+ const char *name;
+ size_t size;
+ int error, *mibp;
+
+ if (!sysctl_allowed(limits, nvlin))
+ return (ENOTCAPABLE);
+
+ name = nvlist_get_string(nvlin, "name");
+ size = (size_t)nvlist_get_number(nvlin, "size");
+
+ mibp = malloc(size * sizeof(*mibp));
+ if (mibp == NULL)
+ return (ENOMEM);
+
+ error = sysctlnametomib(name, mibp, &size);
+ if (error != 0) {
+ error = errno;
+ free(mibp);
+ return (error);
+ }
+
+ nvlist_add_binary(nvlout, "mib", mibp, size * sizeof(*mibp));
+
+ return (0);
+}
+
+static int
+sysctl_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ const char *name;
+ const void *newp;
+ const int *mibp;
+ void *oldp;
+ uint64_t operation;
+ size_t oldlen, newlen, size;
+ size_t *oldlenp;
+ int error;
+
+ if (strcmp(cmd, "sysctlnametomib") == 0)
+ return (nametomib(limits, nvlin, nvlout));
+
+ if (strcmp(cmd, "sysctlbyname") != 0 && strcmp(cmd, "sysctl") != 0)
+ return (EINVAL);
+ error = sysctl_valid(nvlin, false);
+ if (error != 0)
+ return (error);
+ if (!sysctl_allowed(limits, nvlin))
+ return (ENOTCAPABLE);
+
+ operation = nvlist_get_number(nvlin, "operation");
+ if ((operation & CAP_SYSCTL_WRITE) != 0) {
+ if (!nvlist_exists_binary(nvlin, "newp"))
+ return (EINVAL);
+ newp = nvlist_get_binary(nvlin, "newp", &newlen);
+ assert(newp != NULL && newlen > 0);
+ } else {
+ newp = NULL;
+ newlen = 0;
+ }
+
+ if ((operation & CAP_SYSCTL_READ) != 0) {
+ if (nvlist_exists_null(nvlin, "justsize")) {
+ oldp = NULL;
+ oldlen = 0;
+ oldlenp = &oldlen;
+ } else {
+ if (!nvlist_exists_number(nvlin, "oldlen"))
+ return (EINVAL);
+ oldlen = (size_t)nvlist_get_number(nvlin, "oldlen");
+ if (oldlen == 0)
+ return (EINVAL);
+ oldp = calloc(1, oldlen);
+ if (oldp == NULL)
+ return (ENOMEM);
+ oldlenp = &oldlen;
+ }
+ } else {
+ oldp = NULL;
+ oldlen = 0;
+ oldlenp = NULL;
+ }
+
+ if (strcmp(cmd, "sysctlbyname") == 0) {
+ name = nvlist_get_string(nvlin, "name");
+ error = sysctlbyname(name, oldp, oldlenp, newp, newlen);
+ } else {
+ mibp = nvlist_get_binary(nvlin, "mib", &size);
+ error = sysctl(mibp, size / sizeof(*mibp), oldp, oldlenp, newp,
+ newlen);
+ }
+ if (error != 0) {
+ error = errno;
+ free(oldp);
+ return (error);
+ }
+
+ if ((operation & CAP_SYSCTL_READ) != 0) {
+ if (nvlist_exists_null(nvlin, "justsize"))
+ nvlist_add_number(nvlout, "oldlen", (uint64_t)oldlen);
+ else
+ nvlist_move_binary(nvlout, "oldp", oldp, oldlen);
+ }
+
+ return (0);
+}
+
+CREATE_SERVICE("system.sysctl", sysctl_limit, sysctl_command, 0);
diff --git a/lib/libcasper/services/cap_sysctl/cap_sysctl.h b/lib/libcasper/services/cap_sysctl/cap_sysctl.h
new file mode 100644
index 000000000000..51243128a683
--- /dev/null
+++ b/lib/libcasper/services/cap_sysctl/cap_sysctl.h
@@ -0,0 +1,123 @@
+/*-
+ * Copyright (c) 2013 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _CAP_SYSCTL_H_
+#define _CAP_SYSCTL_H_
+
+#ifdef HAVE_CASPER
+#define WITH_CASPER
+#endif
+
+#include <sys/cdefs.h>
+
+#define CAP_SYSCTL_READ 0x01
+#define CAP_SYSCTL_WRITE 0x02
+#define CAP_SYSCTL_RDWR (CAP_SYSCTL_READ | CAP_SYSCTL_WRITE)
+#define CAP_SYSCTL_RECURSIVE 0x04
+
+struct cap_sysctl_limit;
+typedef struct cap_sysctl_limit cap_sysctl_limit_t;
+
+#ifdef WITH_CASPER
+
+__BEGIN_DECLS
+
+int cap_sysctl(cap_channel_t *chan, const int *name, u_int namelen, void *oldp,
+ size_t *oldlenp, const void *newp, size_t newlen);
+int cap_sysctlbyname(cap_channel_t *chan, const char *name, void *oldp,
+ size_t *oldlenp, const void *newp, size_t newlen);
+int cap_sysctlnametomib(cap_channel_t *chan, const char *name, int *mibp,
+ size_t *sizep);
+
+cap_sysctl_limit_t *cap_sysctl_limit_init(cap_channel_t *);
+cap_sysctl_limit_t *cap_sysctl_limit_name(cap_sysctl_limit_t *limit,
+ const char *name, int flags);
+cap_sysctl_limit_t *cap_sysctl_limit_mib(cap_sysctl_limit_t *limit,
+ const int *mibp, u_int miblen, int flags);
+int cap_sysctl_limit(cap_sysctl_limit_t *limit);
+
+__END_DECLS
+
+#else /* !WITH_CASPER */
+static inline int
+cap_sysctl(cap_channel_t *chan __unused, const int *name, u_int namelen,
+ void *oldp, size_t *oldlenp, const void *newp, size_t newlen)
+{
+
+ return (sysctl(name, namelen, oldp, oldlenp, newp, newlen));
+}
+
+static inline int
+cap_sysctlbyname(cap_channel_t *chan __unused, const char *name,
+ void *oldp, size_t *oldlenp, const void *newp, size_t newlen)
+{
+
+ return (sysctlbyname(name, oldp, oldlenp, newp, newlen));
+}
+
+static inline int
+cap_sysctlnametomib(cap_channel_t *chan __unused, const char *name, int *mibp,
+ size_t *sizep)
+{
+
+ return (sysctlnametomib(name, mibp, sizep));
+}
+
+static inline cap_sysctl_limit_t *
+cap_sysctl_limit_init(cap_channel_t *limit __unused)
+{
+
+ return (NULL);
+}
+
+static inline cap_sysctl_limit_t *
+cap_sysctl_limit_name(cap_sysctl_limit_t *limit __unused,
+ const char *name __unused, int flags __unused)
+{
+
+ return (NULL);
+}
+
+static inline cap_sysctl_limit_t *
+cap_sysctl_limit_mib(cap_sysctl_limit_t *limit __unused,
+ const int *mibp __unused, u_int miblen __unused,
+ int flags __unused)
+{
+
+ return (NULL);
+}
+
+static inline int
+cap_sysctl_limit(cap_sysctl_limit_t *limit __unused)
+{
+
+ return (0);
+}
+#endif /* WITH_CASPER */
+
+#endif /* !_CAP_SYSCTL_H_ */
diff --git a/lib/libcasper/services/cap_sysctl/tests/Makefile b/lib/libcasper/services/cap_sysctl/tests/Makefile
new file mode 100644
index 000000000000..85bb0d28c389
--- /dev/null
+++ b/lib/libcasper/services/cap_sysctl/tests/Makefile
@@ -0,0 +1,17 @@
+.include <src.opts.mk>
+
+ATF_TESTS_C= sysctl_test
+
+.if ${MK_CASPER} != "no"
+LIBADD+= casper
+LIBADD+= cap_sysctl
+CFLAGS+=-DWITH_CASPER
+.endif
+LIBADD+= nv
+
+# cap_sysctl tests modify global sysctl values and read them back, so
+# cannot be run in parallel.
+TEST_METADATA.sysctl_test+= required_user="root" \
+ is_exclusive=true
+
+.include <bsd.test.mk>
diff --git a/lib/libcasper/services/cap_sysctl/tests/Makefile.depend b/lib/libcasper/services/cap_sysctl/tests/Makefile.depend
new file mode 100644
index 000000000000..1938a0318d2c
--- /dev/null
+++ b/lib/libcasper/services/cap_sysctl/tests/Makefile.depend
@@ -0,0 +1,19 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/atf/libatf-c \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcasper/services/cap_sysctl \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_sysctl/tests/sysctl_test.c b/lib/libcasper/services/cap_sysctl/tests/sysctl_test.c
new file mode 100644
index 000000000000..300333f11790
--- /dev/null
+++ b/lib/libcasper/services/cap_sysctl/tests/sysctl_test.c
@@ -0,0 +1,1689 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013, 2018 The FreeBSD Foundation
+ *
+ * This software was developed by Pawel Jakub Dawidek under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * Portions of this software were developed by Mark Johnston
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/capsicum.h>
+#include <sys/sysctl.h>
+#include <sys/nv.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcasper.h>
+#include <casper/cap_sysctl.h>
+
+#include <atf-c.h>
+
+/*
+ * We need some sysctls to perform the tests on.
+ * We remember their values and restore them afer the test is done.
+ */
+#define SYSCTL0_PARENT "kern"
+#define SYSCTL0_NAME "kern.sync_on_panic"
+#define SYSCTL0_FILE "./sysctl0"
+#define SYSCTL1_PARENT "debug"
+#define SYSCTL1_NAME "debug.minidump"
+#define SYSCTL1_FILE "./sysctl1"
+
+#define SYSCTL0_READ0 0x0001
+#define SYSCTL0_READ1 0x0002
+#define SYSCTL0_READ2 0x0004
+#define SYSCTL0_WRITE 0x0008
+#define SYSCTL0_READ_WRITE 0x0010
+#define SYSCTL1_READ0 0x0020
+#define SYSCTL1_READ1 0x0040
+#define SYSCTL1_READ2 0x0080
+#define SYSCTL1_WRITE 0x0100
+#define SYSCTL1_READ_WRITE 0x0200
+
+static void
+save_int_sysctl(const char *name, const char *file)
+{
+ ssize_t n;
+ size_t sz;
+ int error, fd, val;
+
+ sz = sizeof(val);
+ error = sysctlbyname(name, &val, &sz, NULL, 0);
+ ATF_REQUIRE_MSG(error == 0,
+ "sysctlbyname(%s): %s", name, strerror(errno));
+
+ fd = open(file, O_CREAT | O_WRONLY, 0600);
+ ATF_REQUIRE_MSG(fd >= 0, "open(%s): %s", file, strerror(errno));
+ n = write(fd, &val, sz);
+ ATF_REQUIRE(n >= 0 && (size_t)n == sz);
+ error = close(fd);
+ ATF_REQUIRE(error == 0);
+}
+
+static void
+restore_int_sysctl(const char *name, const char *file)
+{
+ ssize_t n;
+ size_t sz;
+ int error, fd, val;
+
+ fd = open(file, O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ sz = sizeof(val);
+ n = read(fd, &val, sz);
+ ATF_REQUIRE(n >= 0 && (size_t)n == sz);
+ error = unlink(file);
+ ATF_REQUIRE(error == 0);
+ error = close(fd);
+ ATF_REQUIRE(error == 0);
+
+ error = sysctlbyname(name, NULL, NULL, &val, sz);
+ ATF_REQUIRE_MSG(error == 0,
+ "sysctlbyname(%s): %s", name, strerror(errno));
+}
+
+static cap_channel_t *
+initcap(void)
+{
+ cap_channel_t *capcas, *capsysctl;
+
+ save_int_sysctl(SYSCTL0_NAME, SYSCTL0_FILE);
+ save_int_sysctl(SYSCTL1_NAME, SYSCTL1_FILE);
+
+ capcas = cap_init();
+ ATF_REQUIRE(capcas != NULL);
+
+ capsysctl = cap_service_open(capcas, "system.sysctl");
+ ATF_REQUIRE(capsysctl != NULL);
+
+ cap_close(capcas);
+
+ return (capsysctl);
+}
+
+static void
+cleanup(void)
+{
+
+ restore_int_sysctl(SYSCTL0_NAME, SYSCTL0_FILE);
+ restore_int_sysctl(SYSCTL1_NAME, SYSCTL1_FILE);
+}
+
+static unsigned int
+checkcaps(cap_channel_t *capsysctl)
+{
+ unsigned int result;
+ size_t len0, len1, oldsize;
+ int error, mib0[2], mib1[2], oldvalue, newvalue;
+
+ result = 0;
+
+ len0 = nitems(mib0);
+ ATF_REQUIRE(sysctlnametomib(SYSCTL0_NAME, mib0, &len0) == 0);
+ len1 = nitems(mib1);
+ ATF_REQUIRE(sysctlnametomib(SYSCTL1_NAME, mib1, &len1) == 0);
+
+ oldsize = sizeof(oldvalue);
+ if (cap_sysctlbyname(capsysctl, SYSCTL0_NAME, &oldvalue, &oldsize,
+ NULL, 0) == 0) {
+ if (oldsize == sizeof(oldvalue))
+ result |= SYSCTL0_READ0;
+ }
+ error = cap_sysctl(capsysctl, mib0, len0, &oldvalue, &oldsize, NULL, 0);
+ if ((result & SYSCTL0_READ0) != 0)
+ ATF_REQUIRE(error == 0);
+ else
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE, error != 0);
+
+ newvalue = 123;
+ if (cap_sysctlbyname(capsysctl, SYSCTL0_NAME, NULL, NULL, &newvalue,
+ sizeof(newvalue)) == 0) {
+ result |= SYSCTL0_WRITE;
+ }
+
+ if ((result & SYSCTL0_WRITE) != 0) {
+ oldsize = sizeof(oldvalue);
+ if (cap_sysctlbyname(capsysctl, SYSCTL0_NAME, &oldvalue,
+ &oldsize, NULL, 0) == 0) {
+ if (oldsize == sizeof(oldvalue) && oldvalue == 123)
+ result |= SYSCTL0_READ1;
+ }
+ }
+ newvalue = 123;
+ error = cap_sysctl(capsysctl, mib0, len0, NULL, NULL,
+ &newvalue, sizeof(newvalue));
+ if ((result & SYSCTL0_WRITE) != 0)
+ ATF_REQUIRE(error == 0);
+ else
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE, error != 0);
+
+ oldsize = sizeof(oldvalue);
+ newvalue = 4567;
+ if (cap_sysctlbyname(capsysctl, SYSCTL0_NAME, &oldvalue, &oldsize,
+ &newvalue, sizeof(newvalue)) == 0) {
+ if (oldsize == sizeof(oldvalue) && oldvalue == 123)
+ result |= SYSCTL0_READ_WRITE;
+ }
+
+ if ((result & SYSCTL0_READ_WRITE) != 0) {
+ oldsize = sizeof(oldvalue);
+ if (cap_sysctlbyname(capsysctl, SYSCTL0_NAME, &oldvalue,
+ &oldsize, NULL, 0) == 0) {
+ if (oldsize == sizeof(oldvalue) && oldvalue == 4567)
+ result |= SYSCTL0_READ2;
+ }
+ }
+
+ oldsize = sizeof(oldvalue);
+ if (cap_sysctlbyname(capsysctl, SYSCTL1_NAME, &oldvalue, &oldsize,
+ NULL, 0) == 0) {
+ if (oldsize == sizeof(oldvalue))
+ result |= SYSCTL1_READ0;
+ }
+ error = cap_sysctl(capsysctl, mib1, len1, &oldvalue, &oldsize, NULL, 0);
+ if ((result & SYSCTL1_READ0) != 0)
+ ATF_REQUIRE(error == 0);
+ else
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE, error != 0);
+
+ newvalue = 506;
+ if (cap_sysctlbyname(capsysctl, SYSCTL1_NAME, NULL, NULL, &newvalue,
+ sizeof(newvalue)) == 0) {
+ result |= SYSCTL1_WRITE;
+ }
+
+ if ((result & SYSCTL1_WRITE) != 0) {
+ newvalue = 506;
+ ATF_REQUIRE(cap_sysctl(capsysctl, mib1, len1, NULL, NULL,
+ &newvalue, sizeof(newvalue)) == 0);
+
+ oldsize = sizeof(oldvalue);
+ if (cap_sysctlbyname(capsysctl, SYSCTL1_NAME, &oldvalue,
+ &oldsize, NULL, 0) == 0) {
+ if (oldsize == sizeof(oldvalue) && oldvalue == 506)
+ result |= SYSCTL1_READ1;
+ }
+ }
+ newvalue = 506;
+ error = cap_sysctl(capsysctl, mib1, len1, NULL, NULL,
+ &newvalue, sizeof(newvalue));
+ if ((result & SYSCTL1_WRITE) != 0)
+ ATF_REQUIRE(error == 0);
+ else
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE, error != 0);
+
+ oldsize = sizeof(oldvalue);
+ newvalue = 7008;
+ if (cap_sysctlbyname(capsysctl, SYSCTL1_NAME, &oldvalue, &oldsize,
+ &newvalue, sizeof(newvalue)) == 0) {
+ if (oldsize == sizeof(oldvalue) && oldvalue == 506)
+ result |= SYSCTL1_READ_WRITE;
+ }
+
+ if ((result & SYSCTL1_READ_WRITE) != 0) {
+ oldsize = sizeof(oldvalue);
+ if (cap_sysctlbyname(capsysctl, SYSCTL1_NAME, &oldvalue,
+ &oldsize, NULL, 0) == 0) {
+ if (oldsize == sizeof(oldvalue) && oldvalue == 7008)
+ result |= SYSCTL1_READ2;
+ }
+ }
+
+ return (result);
+}
+
+ATF_TC_WITH_CLEANUP(cap_sysctl__operation);
+ATF_TC_HEAD(cap_sysctl__operation, tc)
+{
+}
+ATF_TC_BODY(cap_sysctl__operation, tc)
+{
+ cap_channel_t *capsysctl, *ocapsysctl;
+ void *limit;
+
+ ocapsysctl = initcap();
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/RDWR/RECURSIVE
+ * SYSCTL1_PARENT/RDWR/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, "foo.bar",
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, "foo.bar",
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 |
+ SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE |
+ SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE |
+ SYSCTL1_READ_WRITE));
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 |
+ SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE |
+ SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE |
+ SYSCTL1_READ_WRITE));
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE));
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE));
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL0_READ0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/RDWR/RECURSIVE
+ * SYSCTL1_NAME/RDWR/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 |
+ SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE |
+ SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE |
+ SYSCTL1_READ_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/RDWR
+ * SYSCTL1_PARENT/RDWR
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == 0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/RDWR
+ * SYSCTL1_NAME/RDWR
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 |
+ SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE |
+ SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE |
+ SYSCTL1_READ_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/RDWR
+ * SYSCTL1_PARENT/RDWR/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL1_READ0 | SYSCTL1_READ1 |
+ SYSCTL1_READ2 | SYSCTL1_WRITE | SYSCTL1_READ_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/RDWR
+ * SYSCTL1_NAME/RDWR/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 |
+ SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE |
+ SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE |
+ SYSCTL1_READ_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/READ/RECURSIVE
+ * SYSCTL1_PARENT/READ/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_READ0));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/READ/RECURSIVE
+ * SYSCTL1_NAME/READ/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_READ0));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/READ
+ * SYSCTL1_PARENT/READ
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == 0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/READ
+ * SYSCTL1_NAME/READ
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_READ0));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/READ
+ * SYSCTL1_PARENT/READ/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_READ0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/READ
+ * SYSCTL1_NAME/READ/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_READ0));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/WRITE/RECURSIVE
+ * SYSCTL1_PARENT/WRITE/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_WRITE | SYSCTL1_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/WRITE/RECURSIVE
+ * SYSCTL1_NAME/WRITE/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_WRITE | SYSCTL1_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/WRITE
+ * SYSCTL1_PARENT/WRITE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == 0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/WRITE
+ * SYSCTL1_NAME/WRITE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_WRITE | SYSCTL1_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/WRITE
+ * SYSCTL1_PARENT/WRITE/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_WRITE);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/WRITE
+ * SYSCTL1_NAME/WRITE/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_WRITE | SYSCTL1_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/READ/RECURSIVE
+ * SYSCTL1_PARENT/WRITE/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/READ/RECURSIVE
+ * SYSCTL1_NAME/WRITE/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/READ
+ * SYSCTL1_PARENT/WRITE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == 0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/READ
+ * SYSCTL1_NAME/WRITE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/READ
+ * SYSCTL1_PARENT/WRITE/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_WRITE);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_NAME/READ
+ * SYSCTL1_NAME/WRITE/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE));
+
+ cap_close(capsysctl);
+}
+ATF_TC_CLEANUP(cap_sysctl__operation, tc)
+{
+ cleanup();
+}
+
+ATF_TC_WITH_CLEANUP(cap_sysctl__names);
+ATF_TC_HEAD(cap_sysctl__names, tc)
+{
+}
+ATF_TC_BODY(cap_sysctl__names, tc)
+{
+ cap_channel_t *capsysctl, *ocapsysctl;
+ void *limit;
+
+ ocapsysctl = initcap();
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/READ/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL0_READ0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL1_NAME/READ/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_READ0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/WRITE/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL0_WRITE);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL1_NAME/WRITE/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_WRITE);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/RDWR/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 |
+ SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL1_NAME/RDWR/RECURSIVE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME,
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL1_READ0 | SYSCTL1_READ1 |
+ SYSCTL1_READ2 | SYSCTL1_WRITE | SYSCTL1_READ_WRITE));
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/READ
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == 0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL1_NAME/READ
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_READ0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/WRITE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == 0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL1_NAME/WRITE
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_WRITE);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL0_PARENT/RDWR
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == 0);
+
+ cap_close(capsysctl);
+
+ /*
+ * Allow:
+ * SYSCTL1_NAME/RDWR
+ */
+
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR);
+ (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE);
+
+ ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL1_READ0 | SYSCTL1_READ1 |
+ SYSCTL1_READ2 | SYSCTL1_WRITE | SYSCTL1_READ_WRITE));
+
+ cap_close(capsysctl);
+}
+ATF_TC_CLEANUP(cap_sysctl__names, tc)
+{
+ cleanup();
+}
+
+ATF_TC_WITH_CLEANUP(cap_sysctl__no_limits);
+ATF_TC_HEAD(cap_sysctl__no_limits, tc)
+{
+}
+ATF_TC_BODY(cap_sysctl__no_limits, tc)
+{
+ cap_channel_t *capsysctl;
+
+ capsysctl = initcap();
+
+ ATF_REQUIRE_EQ(checkcaps(capsysctl), (SYSCTL0_READ0 | SYSCTL0_READ1 |
+ SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE |
+ SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE |
+ SYSCTL1_READ_WRITE));
+}
+ATF_TC_CLEANUP(cap_sysctl__no_limits, tc)
+{
+ cleanup();
+}
+
+ATF_TC_WITH_CLEANUP(cap_sysctl__recursive_limits);
+ATF_TC_HEAD(cap_sysctl__recursive_limits, tc)
+{
+}
+ATF_TC_BODY(cap_sysctl__recursive_limits, tc)
+{
+ cap_channel_t *capsysctl, *ocapsysctl;
+ void *limit;
+ size_t len;
+ int mib[2], val = 420;
+
+ len = nitems(mib);
+ ATF_REQUIRE(sysctlnametomib(SYSCTL0_NAME, mib, &len) == 0);
+
+ ocapsysctl = initcap();
+
+ /*
+ * Make sure that we match entire components.
+ */
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, "ker",
+ CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE, cap_sysctlbyname(capsysctl, SYSCTL0_NAME,
+ NULL, NULL, &val, sizeof(val)));
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE, cap_sysctl(capsysctl, mib, len,
+ NULL, NULL, &val, sizeof(val)));
+
+ cap_close(capsysctl);
+
+ /*
+ * Verify that we check for CAP_SYSCTL_RECURSIVE.
+ */
+ capsysctl = cap_clone(ocapsysctl);
+ ATF_REQUIRE(capsysctl != NULL);
+
+ limit = cap_sysctl_limit_init(capsysctl);
+ (void)cap_sysctl_limit_name(limit, "kern", CAP_SYSCTL_RDWR);
+ ATF_REQUIRE(cap_sysctl_limit(limit) == 0);
+
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE, cap_sysctlbyname(capsysctl, SYSCTL0_NAME,
+ NULL, NULL, &val, sizeof(val)));
+ ATF_REQUIRE_ERRNO(ENOTCAPABLE, cap_sysctl(capsysctl, mib, len,
+ NULL, NULL, &val, sizeof(val)));
+
+ cap_close(capsysctl);
+}
+ATF_TC_CLEANUP(cap_sysctl__recursive_limits, tc)
+{
+ cleanup();
+}
+
+ATF_TC_WITH_CLEANUP(cap_sysctl__just_size);
+ATF_TC_HEAD(cap_sysctl__just_size, tc)
+{
+}
+ATF_TC_BODY(cap_sysctl__just_size, tc)
+{
+ cap_channel_t *capsysctl;
+ size_t len;
+ int mib0[2];
+
+ capsysctl = initcap();
+
+ len = nitems(mib0);
+ ATF_REQUIRE(sysctlnametomib(SYSCTL0_NAME, mib0, &len) == 0);
+
+ ATF_REQUIRE(cap_sysctlbyname(capsysctl, SYSCTL0_NAME,
+ NULL, &len, NULL, 0) == 0);
+ ATF_REQUIRE(len == sizeof(int));
+ ATF_REQUIRE(cap_sysctl(capsysctl, mib0, nitems(mib0),
+ NULL, &len, NULL, 0) == 0);
+ ATF_REQUIRE(len == sizeof(int));
+
+ cap_close(capsysctl);
+}
+ATF_TC_CLEANUP(cap_sysctl__just_size, tc)
+{
+ cleanup();
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, cap_sysctl__operation);
+ ATF_TP_ADD_TC(tp, cap_sysctl__names);
+ ATF_TP_ADD_TC(tp, cap_sysctl__no_limits);
+ ATF_TP_ADD_TC(tp, cap_sysctl__recursive_limits);
+ ATF_TP_ADD_TC(tp, cap_sysctl__just_size);
+
+ return (atf_no_error());
+}
diff --git a/lib/libcasper/services/cap_syslog/Makefile b/lib/libcasper/services/cap_syslog/Makefile
new file mode 100644
index 000000000000..88979d8bed23
--- /dev/null
+++ b/lib/libcasper/services/cap_syslog/Makefile
@@ -0,0 +1,30 @@
+SHLIBDIR?= /lib
+
+.include <src.opts.mk>
+
+PACKAGE= runtime
+
+SHLIB_MAJOR= 1
+INCSDIR?= ${INCLUDEDIR}/casper
+
+.if ${MK_CASPER} != "no"
+SHLIB= cap_syslog
+
+SRCS= cap_syslog.c
+.endif
+
+INCS= cap_syslog.h
+
+LIBADD= nv
+
+CFLAGS+=-I${.CURDIR}
+
+MAN+= cap_syslog.3
+
+MLINKS+= cap_syslog.3 libcap_syslog.3
+MLINKS+= cap_syslog.3 cap_vsyslog.3
+MLINKS+= cap_syslog.3 cap_openlog.3
+MLINKS+= cap_syslog.3 cap_closelog.3
+MLINKS+= cap_syslog.3 cap_setlogmask.3
+
+.include <bsd.lib.mk>
diff --git a/lib/libcasper/services/cap_syslog/Makefile.depend b/lib/libcasper/services/cap_syslog/Makefile.depend
new file mode 100644
index 000000000000..02bae00eb04d
--- /dev/null
+++ b/lib/libcasper/services/cap_syslog/Makefile.depend
@@ -0,0 +1,17 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcasper/libcasper \
+ lib/libcompiler_rt \
+ lib/libnv \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/services/cap_syslog/cap_syslog.3 b/lib/libcasper/services/cap_syslog/cap_syslog.3
new file mode 100644
index 000000000000..4d6463ef3f81
--- /dev/null
+++ b/lib/libcasper/services/cap_syslog/cap_syslog.3
@@ -0,0 +1,115 @@
+.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd December 6, 2023
+.Dt CAP_SYSLOG 3
+.Os
+.Sh NAME
+.Nm cap_syslog ,
+.Nm cap_vsyslog ,
+.Nm cap_openlog ,
+.Nm cap_closelog ,
+.Nm cap_setlogmask
+.Nd "library for syslog in capability mode"
+.Sh LIBRARY
+.Lb libcap_syslog
+.Sh SYNOPSIS
+.In libcasper.h
+.In casper/cap_syslog.h
+.Ft void
+.Fn cap_syslog "cap_channel_t *chan" "int pri" "const char *fmt" "..."
+.Ft void
+.Fn cap_vsyslog "cap_channel_t *chan" "int priority" "const char *fmt" "va_list ap"
+.Ft void
+.Fn cap_openlog "cap_channel_t *chan" "const char *ident" "int logopt" "int facility"
+.Ft void
+.Fn cap_closelog "cap_channel_t *chan"
+.Ft int
+.Fn cap_setlogmask "cap_channel_t *chan" "int maskpri"
+.Sh DESCRIPTION
+The functions
+.Fn cap_syslog
+.Fn cap_vsyslog
+.Fn cap_openlog
+.Fn cap_closelog
+.Fn cap_setlogmask
+are respectively equivalent to
+.Xr syslog 3 ,
+.Xr vsyslog 3 ,
+.Xr openlog 3 ,
+.Xr closelog 3 ,
+.Xr setlogmask 3
+except that the connection to the
+.Nm system.syslog
+service needs to be provided.
+.Pp
+All of these functions are reentrant but not thread-safe.
+That is, they may be called from separate threads only with different
+.Vt cap_channel_t
+arguments or with synchronization.
+.Sh EXAMPLES
+The following example first opens a capability to casper and then uses this
+capability to create the
+.Nm system.syslog
+casper service to log messages.
+.Bd -literal
+cap_channel_t *capcas, *capsyslog;
+
+/* Open capability to Casper. */
+capcas = cap_init();
+if (capcas == NULL)
+ err(1, "Unable to contact Casper");
+
+/* Enter capability mode sandbox. */
+if (cap_enter() < 0 && errno != ENOSYS)
+ err(1, "Unable to enter capability mode");
+
+/* Use Casper capability to create capability to the system.syslog service. */
+capsyslog = cap_service_open(capcas, "system.syslog");
+if (capsyslog == NULL)
+ err(1, "Unable to open system.syslog service");
+
+/* Close Casper capability, we don't need it anymore. */
+cap_close(capcas);
+
+/* Let's log something. */
+cap_syslog(capsyslog, LOG_NOTICE, "System logs from capability mode.");
+.Ed
+.Sh SEE ALSO
+.Xr cap_enter 2 ,
+.Xr closelog 3 ,
+.Xr err 3 ,
+.Xr openlog 3 ,
+.Xr setlogmask 3 ,
+.Xr syslog 3 ,
+.Xr vsyslog 3 ,
+.Xr capsicum 4 ,
+.Xr nv 9
+.Sh HISTORY
+The
+.Nm cap_syslog
+service first appeared in
+.Fx 10.3 .
+.Sh AUTHORS
+.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org
diff --git a/lib/libcasper/services/cap_syslog/cap_syslog.c b/lib/libcasper/services/cap_syslog/cap_syslog.c
new file mode 100644
index 000000000000..5434b7bb6bab
--- /dev/null
+++ b/lib/libcasper/services/cap_syslog/cap_syslog.c
@@ -0,0 +1,222 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2017 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/dnv.h>
+#include <sys/nv.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include <libcasper.h>
+#include <libcasper_service.h>
+
+#include "cap_syslog.h"
+
+#define CAP_SYSLOG_LIMIT 2048
+
+void
+cap_syslog(cap_channel_t *chan, int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ cap_vsyslog(chan, pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+cap_vsyslog(cap_channel_t *chan, int priority, const char *fmt, va_list ap)
+{
+ nvlist_t *nvl;
+ char message[CAP_SYSLOG_LIMIT];
+
+ (void)vsnprintf(message, sizeof(message), fmt, ap);
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "vsyslog");
+ nvlist_add_number(nvl, "priority", priority);
+ nvlist_add_string(nvl, "message", message);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL) {
+ return;
+ }
+ nvlist_destroy(nvl);
+}
+
+void
+cap_openlog(cap_channel_t *chan, const char *ident, int logopt, int facility)
+{
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "openlog");
+ if (ident != NULL) {
+ nvlist_add_string(nvl, "ident", ident);
+ }
+ nvlist_add_number(nvl, "logopt", logopt);
+ nvlist_add_number(nvl, "facility", facility);
+ if (logopt & LOG_PERROR) {
+ nvlist_add_descriptor(nvl, "stderr", STDERR_FILENO);
+ }
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL) {
+ return;
+ }
+ nvlist_destroy(nvl);
+}
+
+void
+cap_closelog(cap_channel_t *chan)
+{
+ nvlist_t *nvl;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "closelog");
+ nvl = cap_xfer_nvlist(chan, nvl);
+ if (nvl == NULL) {
+ return;
+ }
+ nvlist_destroy(nvl);
+}
+
+int
+cap_setlogmask(cap_channel_t *chan, int maskpri)
+{
+ nvlist_t *nvl;
+ int omask;
+
+ nvl = nvlist_create(0);
+ nvlist_add_string(nvl, "cmd", "setlogmask");
+ nvlist_add_number(nvl, "maskpri", maskpri);
+ nvl = cap_xfer_nvlist(chan, nvl);
+ omask = nvlist_get_number(nvl, "omask");
+
+ nvlist_destroy(nvl);
+
+ return (omask);
+}
+
+/*
+ * Service functions.
+ */
+
+static char *LogTag;
+static int prev_stderr = -1;
+
+static void
+slog_vsyslog(const nvlist_t *limits __unused, const nvlist_t *nvlin,
+ nvlist_t *nvlout __unused)
+{
+
+ syslog(nvlist_get_number(nvlin, "priority"), "%s",
+ nvlist_get_string(nvlin, "message"));
+}
+
+static void
+slog_openlog(const nvlist_t *limits __unused, const nvlist_t *nvlin,
+ nvlist_t *nvlout __unused)
+{
+ const char *ident;
+ uint64_t logopt;
+ int stderr_fd;
+
+ ident = dnvlist_get_string(nvlin, "ident", NULL);
+ if (ident != NULL) {
+ free(LogTag);
+ LogTag = strdup(ident);
+ }
+
+ logopt = nvlist_get_number(nvlin, "logopt");
+ if (logopt & LOG_PERROR) {
+ stderr_fd = dnvlist_get_descriptor(nvlin, "stderr", -1);
+ if (prev_stderr == -1)
+ prev_stderr = dup(STDERR_FILENO);
+ if (prev_stderr != -1)
+ (void)dup2(stderr_fd, STDERR_FILENO);
+ } else if (prev_stderr != -1) {
+ (void)dup2(prev_stderr, STDERR_FILENO);
+ close(prev_stderr);
+ prev_stderr = -1;
+ }
+ openlog(LogTag, logopt, nvlist_get_number(nvlin, "facility"));
+}
+
+static void
+slog_closelog(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused,
+ nvlist_t *nvlout __unused)
+{
+
+ closelog();
+
+ free(LogTag);
+ LogTag = NULL;
+
+ if (prev_stderr != -1) {
+ (void)dup2(prev_stderr, STDERR_FILENO);
+ close(prev_stderr);
+ prev_stderr = -1;
+ }
+}
+
+static void
+slog_setlogmask(const nvlist_t *limits __unused, const nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+ int omask;
+
+ omask = setlogmask(nvlist_get_number(nvlin, "maskpri"));
+ nvlist_add_number(nvlout, "omask", omask);
+}
+
+static int
+syslog_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
+ nvlist_t *nvlout)
+{
+
+ if (strcmp(cmd, "vsyslog") == 0) {
+ slog_vsyslog(limits, nvlin, nvlout);
+ } else if (strcmp(cmd, "openlog") == 0) {
+ slog_openlog(limits, nvlin, nvlout);
+ } else if (strcmp(cmd, "closelog") == 0) {
+ slog_closelog(limits, nvlin, nvlout);
+ } else if (strcmp(cmd, "setlogmask") == 0) {
+ slog_setlogmask(limits, nvlin, nvlout);
+ } else {
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
+CREATE_SERVICE("system.syslog", NULL, syslog_command, 0);
diff --git a/lib/libcasper/services/cap_syslog/cap_syslog.h b/lib/libcasper/services/cap_syslog/cap_syslog.h
new file mode 100644
index 000000000000..ac546664f16c
--- /dev/null
+++ b/lib/libcasper/services/cap_syslog/cap_syslog.h
@@ -0,0 +1,59 @@
+/*-
+ * Copyright (c) 2017 Mariusz Zaborski <oshogbo@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _CAP_SYSLOG_H_
+#define _CAP_SYSLOG_H_
+
+#include <sys/cdefs.h>
+
+#ifdef WITH_CASPER
+__BEGIN_DECLS
+
+void cap_syslog(cap_channel_t *chan, int pri,
+ const char *fmt, ...) __printflike(3, 4);
+void cap_vsyslog(cap_channel_t *chan, int priority, const char *fmt,
+ va_list ap) __printflike(3, 0);
+
+void cap_openlog(cap_channel_t *chan, const char *ident, int logopt,
+ int facility);
+void cap_closelog(cap_channel_t *chan);
+
+int cap_setlogmask(cap_channel_t *chan, int maskpri);
+
+__END_DECLS
+
+#else
+#define cap_syslog(chan, pri, ...) syslog(pri, __VA_ARGS__)
+#define cap_vsyslog(chan, pri, fmt, ap) vsyslog(pri, fmt, ap)
+
+#define cap_openlog(chan, ident, logopt, facility) \
+ openlog(ident, logopt, facility)
+#define cap_closelog(chan) closelog()
+
+#define cap_setlogmask(chan, maskpri) setlogmask(maskpri)
+#endif /* !WITH_CASPER */
+
+#endif /* !_CAP_SYSLOG_H_ */
diff --git a/lib/libcasper/services/tests/Makefile b/lib/libcasper/services/tests/Makefile
new file mode 100644
index 000000000000..29b1b564beca
--- /dev/null
+++ b/lib/libcasper/services/tests/Makefile
@@ -0,0 +1,4 @@
+.PATH: ${SRCTOP}/tests
+KYUAFILE= yes
+
+.include <bsd.test.mk>
diff --git a/lib/libcasper/services/tests/Makefile.depend b/lib/libcasper/services/tests/Makefile.depend
new file mode 100644
index 000000000000..11aba52f82cf
--- /dev/null
+++ b/lib/libcasper/services/tests/Makefile.depend
@@ -0,0 +1,10 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/lib/libcasper/tests/Makefile b/lib/libcasper/tests/Makefile
new file mode 100644
index 000000000000..29b1b564beca
--- /dev/null
+++ b/lib/libcasper/tests/Makefile
@@ -0,0 +1,4 @@
+.PATH: ${SRCTOP}/tests
+KYUAFILE= yes
+
+.include <bsd.test.mk>