diff options
author | Glen Barber <gjb@FreeBSD.org> | 2016-03-02 16:14:46 +0000 |
---|---|---|
committer | Glen Barber <gjb@FreeBSD.org> | 2016-03-02 16:14:46 +0000 |
commit | 52259a98adba7622f236db46a330e61df0c84fb1 (patch) | |
tree | 9c83d6fb30867514fbcff33f80605a1fb118d720 /lib/libcasper | |
parent | aef2f6ad2e64655fcc1ab4f9b26e1c7eb20bbe00 (diff) | |
parent | 39c44571c032ce86b43e87de4cc716ea696c265d (diff) | |
download | src-52259a98adba7622f236db46a330e61df0c84fb1.tar.gz src-52259a98adba7622f236db46a330e61df0c84fb1.zip |
MFH
Sponsored by: The FreeBSD Foundation
Notes
Notes:
svn path=/projects/release-pkg/; revision=296318
Diffstat (limited to 'lib/libcasper')
36 files changed, 4679 insertions, 146 deletions
diff --git a/lib/libcasper/Makefile b/lib/libcasper/Makefile index 1058878cc1fc..97cf1df329d6 100644 --- a/lib/libcasper/Makefile +++ b/lib/libcasper/Makefile @@ -1,20 +1,8 @@ # $FreeBSD$ -PACKAGE= lib${LIB} -LIB= casper +SUBDIR= libcasper +SUBDIR+= services -SHLIB_MAJOR= 0 -SHLIBDIR?= /lib +SUBDIR_PARALLEL= -SRCS= libcasper.c -INCS= libcasper.h - -LIBADD= capsicum nv pjdlog - -CFLAGS+=-I${.CURDIR} -CFLAGS+=-I${.CURDIR}/../libpjdlog -CFLAGS+=-I${.CURDIR}/../../sbin/casper - -WARNS?= 6 - -.include <bsd.lib.mk> +.include <bsd.subdir.mk> diff --git a/lib/libcasper/Makefile.inc b/lib/libcasper/Makefile.inc new file mode 100644 index 000000000000..265f86d1ed55 --- /dev/null +++ b/lib/libcasper/Makefile.inc @@ -0,0 +1,3 @@ +# $FreeBSD$ + +.include "../Makefile.inc" diff --git a/lib/libcasper/libcasper/Makefile b/lib/libcasper/libcasper/Makefile new file mode 100644 index 000000000000..c8eface43627 --- /dev/null +++ b/lib/libcasper/libcasper/Makefile @@ -0,0 +1,37 @@ +# $FreeBSD$ + +PACKAGE=${LIB} +LIB= casper + +SHLIB_MAJOR= 0 +SHLIBDIR?= /lib + +SRCS= libcasper.c +SRCS+= libcasper_impl.c +SRCS+= libcasper_service.c +SRCS+= service.c +SRCS+= zygote.c + +INCS= libcasper.h +INCS+= libcasper_service.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +MAN+= libcasper.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 + +.include <bsd.lib.mk> diff --git a/lib/libcasper/Makefile.depend b/lib/libcasper/libcasper/Makefile.depend index 529a2cf21c66..4f7989bd2e64 100644 --- a/lib/libcasper/Makefile.depend +++ b/lib/libcasper/libcasper/Makefile.depend @@ -8,10 +8,8 @@ DIRDEPS = \ include/xlocale \ lib/${CSU_DIR} \ lib/libc \ - lib/libcapsicum \ lib/libcompiler_rt \ lib/libnv \ - lib/libpjdlog \ .include <dirdeps.mk> diff --git a/lib/libcasper/libcasper/libcasper.3 b/lib/libcasper/libcasper/libcasper.3 new file mode 100644 index 000000000000..220b5b3ffd52 --- /dev/null +++ b/lib/libcasper/libcasper/libcasper.3 @@ -0,0 +1,295 @@ +.\" Copyright (c) 2013 The FreeBSD Foundation +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd February 25, 2016 +.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 +.In libcasper.h +.In nv.h +.Ft "cap_channel_t *" +.Fn cap_init "void" +.Ft "cap_channel_t *" +.Fn cap_wrap "int sock" +.Ft "int" +.Fn cap_unwrap "cap_channel_t *chan" +.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" "int flags" +.Ft "nvlist_t *" +.Fn cap_xfer_nvlist "const cap_channel_t *chan" "nvlist_t *nvl" "int flags" +.Ft "cap_channel_t *" +.Fn cap_service_open "const cap_channel_t *chan" "const char *name" +.Sh DESCRIPTION +The +.Nm libcapsicum +library allows to manage application capabilities through the casper process. +.Pp +The application capability (represented by the +.Vt cap_channel_t +type) is a communication channel between the caller and the casper process +daemon or an instance of one of its services. +A capability to the casper process obtained with the +.Fn cap_init +function allows to create capabilities to casper's services via the +.Fn cap_service_open +function. +.Pp +The +.Fn cap_init +function opens capability to the casper process. +.Pp +The +.Fn cap_wrap +function creates +.Vt cap_channel_t +based on the given socket. +The function is used when capability is inherited through +.Xr execve 2 +or send over +.Xr unix 4 +domain socket as a regular file descriptor and has to be represented as +.Vt cap_channel_t +again. +.Pp +The +.Fn cap_unwrap +function is the opposite of the +.Fn cap_wrap +function. +It frees the +.Vt cap_channel_t +structure and returns +.Xr unix 4 +domain socket associated with it. +.Pp +The +.Fn cap_clone +function clones the given capability. +.Pp +The +.Fn cap_close +function closes the given capability. +.Pp +The +.Fn cap_sock +function returns +.Xr unix 4 +domain socket descriptor associated with the given capability for use with +system calls like +.Xr kevent 2 , +.Xr poll 2 +and +.Xr select 2 . +.Pp +The +.Fn cap_limit_get +function stores current limits of the given capability in the +.Fa limitsp +argument. +If the function return +.Va 0 +and +.Dv NULL +is stored in +.Fa limitsp +it means there are no limits set. +.Pp +The +.Fn cap_limit_set +function sets limits for the given capability. +The limits are provided as nvlist. +The exact format depends on the service the capability represents. +.Pp +The +.Fn cap_send_nvlist +function sends the given nvlist over the given capability. +This is low level interface to communicate with casper services. +Most services should provide higher level API. +.Pp +The +.Fn cap_recv_nvlist +function receives the given nvlist over the given capability. +The +.Fa flags +argument defines what type the top nvlist is expected to be. +If the nvlist flags do not match the flags passed to +.Fn cap_recv_nvlist , +the nvlist will not be returned. +.Pp +The +.Fn cap_xfer_nvlist +function sends the given nvlist, destroys it and receives new nvlist in +response over the given capability. +The +.Fa flags +argument defines what type the top nvlist is expected to be. +If the nvlist flags do not match the flags passed to +.Fn cap_xfer_nvlist , +the nvlist will not be returned. +It does not matter if the function succeeds or fails, the nvlist given +for sending will always be destroyed once the function returns. +.Pp +The +.Fn cap_service_open +function opens casper service of the given name through casper capability +obtained via the +.Fn cap_init +function. +The function returns capability that provides access to opened service. +.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 EXAMPLES +The following example first opens capability to the casper then using this +capability creates new capability to the +.Nm system.dns +casper service and uses the latter capability to resolve IP address. +.Bd -literal +cap_channel_t *capcas, *capdns; +nvlist_t *limits; +const char *ipstr = "127.0.0.1"; +struct in_addr ip; +struct hostent *hp; + +/* Open capability to the 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.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 reverse DNS lookups and IPv4 addresses. */ +limits = nvlist_create(0); +nvlist_add_string(limits, "type", "ADDR"); +nvlist_add_number(limits, "family", (uint64_t)AF_INET); +if (cap_limit_set(capdns, limits) < 0) + err(1, "Unable to limit access to the system.dns service"); + +/* Convert IP address in C-string to in_addr. */ +if (!inet_aton(ipstr, &ip)) + errx(1, "Unable to parse IP address %s.", ipstr); + +/* Find hostname for the given IP address. */ +hp = cap_gethostbyaddr(capdns, (const void *)&ip, sizeof(ip), AF_INET); +if (hp == NULL) + errx(1, "No name associated with %s.", ipstr); + +printf("Name associated with %s is %s.\\n", ipstr, hp->h_name); +.Ed +.Sh SEE ALSO +.Xr cap_enter 2 , +.Xr execve 2 , +.Xr kevent 2 , +.Xr poll 2 , +.Xr select 2 , +.Xr cap_gethostbyaddr 3 , +.Xr err 3 , +.Xr gethostbyaddr 3 , +.Xr inet_aton 3 , +.Xr nv 3 , +.Xr capsicum 4 , +.Xr unix 4 +.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..f23d8b18c861 --- /dev/null +++ b/lib/libcasper/libcasper/libcasper.c @@ -0,0 +1,337 @@ +/*- + * 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/cdefs.h> +__FBSDID("$FreeBSD$"); + +#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" + +/* + * 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; +}; + +static bool +cap_add_pd(cap_channel_t *chan, int pd) +{ + + if (!fd_is_valid(pd)) + return (false); + chan->cch_pd = pd; + return (true); +} + +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) { + /* Parent. */ + close(sock[0]); + casper_main_loop(sock[1]); + /* NOTREACHED. */ + } else if (pid > 0) { + /* Child. */ + close(sock[1]); + chan = cap_wrap(sock[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) +{ + cap_channel_t *chan; + + if (!fd_is_valid(sock)) + return (NULL); + + chan = malloc(sizeof(*chan)); + if (chan != NULL) { + chan->cch_sock = sock; + chan->cch_pd = -1; + chan->cch_magic = CAP_CHANNEL_MAGIC; + } + + return (chan); +} + +int +cap_unwrap(cap_channel_t *chan) +{ + 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); + 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(0); + nvlist_add_string(nvl, "cmd", "clone"); + nvl = cap_xfer_nvlist(chan, nvl, 0); + 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); + 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(0); + nvlist_add_string(nvlmsg, "cmd", "limit_set"); + nvlist_add_nvlist(nvlmsg, "limits", limits); + nvlmsg = cap_xfer_nvlist(chan, nvlmsg, 0); + 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(0); + nvlist_add_string(nvlmsg, "cmd", "limit_get"); + nvlmsg = cap_xfer_nvlist(chan, nvlmsg, 0); + 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, int flags) +{ + + assert(chan != NULL); + assert(chan->cch_magic == CAP_CHANNEL_MAGIC); + + return (nvlist_recv(chan->cch_sock, flags)); +} + +nvlist_t * +cap_xfer_nvlist(const cap_channel_t *chan, nvlist_t *nvl, int flags) +{ + + assert(chan != NULL); + assert(chan->cch_magic == CAP_CHANNEL_MAGIC); + + return (nvlist_xfer(chan->cch_sock, nvl, flags)); +} + +cap_channel_t * +cap_service_open(const cap_channel_t *chan, const char *name) +{ + cap_channel_t *newchan; + nvlist_t *nvl; + int sock, error; + + sock = -1; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "open"); + nvlist_add_string(nvl, "service", name); + nvl = cap_xfer_nvlist(chan, nvl, 0); + 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"); + assert(sock >= 0); + nvlist_destroy(nvl); + nvl = NULL; + newchan = cap_wrap(sock); + 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(0); + 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..2d6f15825a39 --- /dev/null +++ b/lib/libcasper/libcasper/libcasper.h @@ -0,0 +1,115 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#ifndef _LIBCASPER_H_ +#define _LIBCASPER_H_ + +#include <sys/types.h> + +#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 +struct cap_channel; + +typedef struct cap_channel cap_channel_t; +#endif + +/* + * The functions opens unrestricted communication channel to Casper. + */ +cap_channel_t *cap_init(void); + +/* + * The functions to communicate with service. + */ +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); + +/* + * The function creates cap_channel_t based on the given socket. + */ +cap_channel_t *cap_wrap(int sock); + +/* + * The function returns communication socket and frees cap_channel_t. + */ +int cap_unwrap(cap_channel_t *chan); + +/* + * The function clones the given capability. + */ +cap_channel_t *cap_clone(const cap_channel_t *chan); + +/* + * The function closes the given capability. + */ +void cap_close(cap_channel_t *chan); + +/* + * The function returns socket descriptor associated with the given + * cap_channel_t for use with select(2)/kqueue(2)/etc. + */ +int cap_sock(const cap_channel_t *chan); + +/* + * The function limits the given capability. + * It always destroys 'limits' on return. + */ +int cap_limit_set(const cap_channel_t *chan, nvlist_t *limits); + +/* + * The function returns current limits of the given capability. + */ +int cap_limit_get(const cap_channel_t *chan, nvlist_t **limitsp); + +/* + * Function sends nvlist over the given capability. + */ +int cap_send_nvlist(const cap_channel_t *chan, const nvlist_t *nvl); +/* + * Function receives nvlist over the given capability. + */ +nvlist_t *cap_recv_nvlist(const cap_channel_t *chan, int flags); +/* + * Function sends the given nvlist, destroys it and receives new nvlist in + * response over the given capability. + */ +nvlist_t *cap_xfer_nvlist(const cap_channel_t *chan, nvlist_t *nvl, int flags); + +#endif /* !_LIBCASPER_H_ */ diff --git a/lib/libcasper/libcasper_impl.h b/lib/libcasper/libcasper/libcasper_impl.c index 320f21abb601..57d489943252 100644 --- a/lib/libcasper/libcasper_impl.h +++ b/lib/libcasper/libcasper/libcasper_impl.c @@ -1,5 +1,6 @@ /*- * 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 @@ -29,18 +30,15 @@ * $FreeBSD$ */ -#ifndef _LIBCASPER_IMPL_H_ -#define _LIBCASPER_IMPL_H_ +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> -#include "libcasper.h" +#include "libcasper_impl.h" -struct service; -struct service_connection; +bool +fd_is_valid(int fd) +{ -struct service * service_alloc(const char *name, - service_limit_func_t *limitfunc, service_command_func_t *commandfunc); -void service_free(struct service *service); - -void service_message(struct service *service, struct service_connection *sconn); - -#endif /* !_LIBCASPER_IMPL_H_ */ + return (fcntl(fd, F_GETFL) != -1 || errno != EBADF); +} diff --git a/lib/libcasper/libcasper/libcasper_impl.h b/lib/libcasper/libcasper/libcasper_impl.h new file mode 100644 index 000000000000..bf2e6cee3bc0 --- /dev/null +++ b/lib/libcasper/libcasper/libcasper_impl.h @@ -0,0 +1,82 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#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); + +/* Private service functions. */ +struct service *service_alloc(const char *name, + service_limit_func_t *limitfunc, + service_command_func_t *commandfunc); +void service_free(struct service *service); +void service_message(struct service *service, + struct service_connection *sconn); +void service_start(struct service *service, int sock); +const char *service_name(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.c b/lib/libcasper/libcasper/libcasper_service.c new file mode 100644 index 000000000000..5b1c7c852478 --- /dev/null +++ b/lib/libcasper/libcasper/libcasper_service.c @@ -0,0 +1,277 @@ +/*- + * 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#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) +{ + 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); + 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); +} + +static void +service_execute(int chanfd) +{ + struct service *service; + nvlist_t *nvl; + int procfd; + + nvl = nvlist_recv(chanfd, 0); + if (nvl == NULL) + exit(1); + service = (struct service *)(uintptr_t)nvlist_take_number(nvl, + "service"); + //XXX: We should remove this? + procfd = nvlist_take_descriptor(nvl, "procfd"); + nvlist_destroy(nvl); + + service_start(service, chanfd); + /* 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_number(nvl, "service", + (uint64_t)(uintptr_t)casserv->cs_service); + 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); + + 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); + 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.h b/lib/libcasper/libcasper/libcasper_service.h index 91d602737f9c..4efbeb24d345 100644 --- a/lib/libcasper/libcasper.h +++ b/lib/libcasper/libcasper/libcasper_service.h @@ -1,5 +1,6 @@ /*- * 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 @@ -29,8 +30,8 @@ * $FreeBSD$ */ -#ifndef _LIBCASPER_H_ -#define _LIBCASPER_H_ +#ifndef _LIBCASPER_SERVICE_H_ +#define _LIBCASPER_SERVICE_H_ #ifndef _NVLIST_T_DECLARED #define _NVLIST_T_DECLARED @@ -39,32 +40,21 @@ struct nvlist; typedef struct nvlist nvlist_t; #endif -#define PARENT_FILENO 3 -#define EXECUTABLE_FILENO 4 -#define PROC_FILENO 5 - -struct service; -struct service_connection; - 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 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); +struct casper_service *service_register(const char *name, + service_limit_func_t *limitfunc, service_command_func_t *commandfunc); -int service_start(const char *name, int sock, service_limit_func_t *limitfunc, - service_command_func_t *commandfunc, int argc, char *argv[]); +#define __constructor __attribute__((constructor)) +#define CREATE_SERVICE(name, limit_func, command_func) \ + static __constructor void \ + init_casper_service(void) \ + { \ + \ + (void)service_register(name, limit_func, \ + command_func); \ + } -#endif /* !_LIBCASPER_H_ */ +#endif /* !_LIBCASPER_SERVICE_H_ */ diff --git a/lib/libcasper/libcasper.c b/lib/libcasper/libcasper/service.c index e241888aa95e..e02d7d90f0d6 100644 --- a/lib/libcasper/libcasper.c +++ b/lib/libcasper/libcasper/service.c @@ -1,5 +1,6 @@ /*- * 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 @@ -31,11 +32,8 @@ __FBSDID("$FreeBSD$"); #include <sys/types.h> -#include <sys/capsicum.h> #include <sys/queue.h> #include <sys/socket.h> -#include <sys/stat.h> -#include <sys/un.h> #include <sys/nv.h> #include <assert.h> @@ -50,10 +48,8 @@ __FBSDID("$FreeBSD$"); #include <strings.h> #include <unistd.h> -#include <libcapsicum.h> -#include <libcasper.h> -#include <libcasper_impl.h> -#include <pjdlog.h> +#include "libcasper.h" +#include "libcasper_impl.h" /* * Currently there is only one service_connection per service. @@ -114,7 +110,7 @@ service_free(struct service *service) { struct service_connection *sconn; - PJDLOG_ASSERT(service->s_magic == SERVICE_MAGIC); + assert(service->s_magic == SERVICE_MAGIC); service->s_magic = 0; while ((sconn = service_connection_first(service)) != NULL) @@ -130,17 +126,14 @@ service_connection_add(struct service *service, int sock, struct service_connection *sconn; int serrno; - PJDLOG_ASSERT(service->s_magic == SERVICE_MAGIC); + assert(service->s_magic == SERVICE_MAGIC); sconn = malloc(sizeof(*sconn)); - if (sconn == NULL) { - pjdlog_error("Unable to allocate memory for service connection."); + if (sconn == NULL) return (NULL); - } sconn->sc_chan = cap_wrap(sock); if (sconn->sc_chan == NULL) { serrno = errno; - pjdlog_error("Unable to wrap communication channel."); free(sconn); errno = serrno; return (NULL); @@ -151,7 +144,6 @@ service_connection_add(struct service *service, int sock, sconn->sc_limits = nvlist_clone(limits); if (sconn->sc_limits == NULL) { serrno = errno; - pjdlog_error("Unable to clone limits."); (void)cap_unwrap(sconn->sc_chan); free(sconn); errno = serrno; @@ -168,8 +160,8 @@ service_connection_remove(struct service *service, struct service_connection *sconn) { - PJDLOG_ASSERT(service->s_magic == SERVICE_MAGIC); - PJDLOG_ASSERT(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + 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; @@ -206,10 +198,10 @@ service_connection_first(struct service *service) { struct service_connection *sconn; - PJDLOG_ASSERT(service->s_magic == SERVICE_MAGIC); + assert(service->s_magic == SERVICE_MAGIC); sconn = TAILQ_FIRST(&service->s_connections); - PJDLOG_ASSERT(sconn == NULL || + assert(sconn == NULL || sconn->sc_magic == SERVICE_CONNECTION_MAGIC); return (sconn); } @@ -218,10 +210,10 @@ struct service_connection * service_connection_next(struct service_connection *sconn) { - PJDLOG_ASSERT(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); sconn = TAILQ_NEXT(sconn, sc_next); - PJDLOG_ASSERT(sconn == NULL || + assert(sconn == NULL || sconn->sc_magic == SERVICE_CONNECTION_MAGIC); return (sconn); } @@ -230,7 +222,7 @@ cap_channel_t * service_connection_get_chan(const struct service_connection *sconn) { - PJDLOG_ASSERT(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); return (sconn->sc_chan); } @@ -239,7 +231,7 @@ int service_connection_get_sock(const struct service_connection *sconn) { - PJDLOG_ASSERT(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); return (cap_sock(sconn->sc_chan)); } @@ -248,7 +240,7 @@ const nvlist_t * service_connection_get_limits(const struct service_connection *sconn) { - PJDLOG_ASSERT(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); return (sconn->sc_limits); } @@ -258,38 +250,12 @@ service_connection_set_limits(struct service_connection *sconn, nvlist_t *limits) { - PJDLOG_ASSERT(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); nvlist_destroy(sconn->sc_limits); sconn->sc_limits = limits; } -#if 0 -static void -casper_message_connection(struct service *service, const nvlist_t *nvl) -{ - - service_connection_add(&service->s_connections, - nvlist_get_descriptor(nvl, "sock")); -} - -static void -casper_message(const cap_channel_t *capcas, struct service *service) -{ - const char *cmd; - nvlist_t *nvl; - - nvl = cap_recv_nvlist(capcas, 0); - if (nvl == NULL) - pjdlog_exit(1, "Unable to receive message from Casper"); - cmd = nvlist_get_string(nvl, "cmd"); - if (strcmp(cmd, "connection") == 0) - casper_message_connection(service, nvl); - else - PJDLOG_ABORT("Unknown command from Casper: %s.", cmd); -} -#endif - void service_message(struct service *service, struct service_connection *sconn) { @@ -299,12 +265,6 @@ service_message(struct service *service, struct service_connection *sconn) nvlin = cap_recv_nvlist(service_connection_get_chan(sconn), 0); if (nvlin == NULL) { - if (errno == ENOTCONN) { - pjdlog_debug(1, "Connection closed by the client."); - } else { - pjdlog_errno(LOG_ERR, - "Unable to receive message from client"); - } service_connection_remove(service, sconn); return; } @@ -313,15 +273,16 @@ service_message(struct service *service, struct service_connection *sconn) nvlout = nvlist_create(0); cmd = nvlist_get_string(nvlin, "cmd"); - pjdlog_debug(1, "Command received from client: %s.", cmd); - if (pjdlog_debug_get() >= 2) - nvlist_fdump(nvlin, stderr); if (strcmp(cmd, "limit_set") == 0) { nvlist_t *nvllim; nvllim = nvlist_take_nvlist(nvlin, "limits"); - error = service->s_limit(service_connection_get_limits(sconn), - nvllim); + 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. */ @@ -354,14 +315,9 @@ service_message(struct service *service, struct service_connection *sconn) nvlist_destroy(nvlin); nvlist_add_number(nvlout, "error", (uint64_t)error); - pjdlog_debug(1, "Sending reply to client (error=%d).", error); - if (pjdlog_debug_get() >= 2) - nvlist_fdump(nvlout, stderr); - if (cap_send_nvlist(service_connection_get_chan(sconn), nvlout) == -1) { - pjdlog_errno(LOG_ERR, "Unable to send message to client"); + if (cap_send_nvlist(service_connection_get_chan(sconn), nvlout) == -1) service_connection_remove(service, sconn); - } nvlist_destroy(nvlout); } @@ -374,28 +330,26 @@ fd_add(fd_set *fdsp, int maxfd, int fd) return (fd > maxfd ? fd : maxfd); } -int -service_start(const char *name, int sock, service_limit_func_t *limitfunc, - service_command_func_t *commandfunc, int argc, char *argv[]) +const char * +service_name(struct service *service) { - struct service *service; - struct service_connection *sconn, *sconntmp; - fd_set fds; - int maxfd, nfds, serrno; - assert(argc == 2); + assert(service->s_magic == SERVICE_MAGIC); + return (service->s_name); +} - pjdlog_init(PJDLOG_MODE_STD); - pjdlog_debug_set(atoi(argv[1])); +void +service_start(struct service *service, int sock) +{ + struct service_connection *sconn, *sconntmp; + fd_set fds; + int maxfd, nfds; - service = service_alloc(name, limitfunc, commandfunc); - if (service == NULL) - return (errno); - if (service_connection_add(service, sock, NULL) == NULL) { - serrno = errno; - service_free(service); - return (serrno); - } + assert(service != NULL); + assert(service->s_magic == SERVICE_MAGIC); + setproctitle("%s", service->s_name); + if (service_connection_add(service, sock, NULL) == NULL) + exit(1); for (;;) { FD_ZERO(&fds); @@ -406,17 +360,16 @@ service_start(const char *name, int sock, service_limit_func_t *limitfunc, service_connection_get_sock(sconn)); } - PJDLOG_ASSERT(maxfd >= 0); - PJDLOG_ASSERT(maxfd + 1 <= (int)FD_SETSIZE); + assert(maxfd >= 0); + assert(maxfd + 1 <= (int)FD_SETSIZE); nfds = select(maxfd + 1, &fds, NULL, NULL, NULL); if (nfds < 0) { if (errno != EINTR) - pjdlog_errno(LOG_ERR, "select() failed"); + exit(1); continue; } else if (nfds == 0) { /* Timeout. */ - PJDLOG_ABORT("select() timeout"); - continue; + abort(); } for (sconn = service_connection_first(service); sconn != NULL; @@ -437,5 +390,5 @@ service_start(const char *name, int sock, service_limit_func_t *limitfunc, } } - return (0); + exit(0); } diff --git a/lib/libcasper/libcasper/zygote.c b/lib/libcasper/libcasper/zygote.c new file mode 100644 index 000000000000..e554a3eee761 --- /dev/null +++ b/lib/libcasper/libcasper/zygote.c @@ -0,0 +1,223 @@ +/*- + * 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#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 <paths.h> +#include <stdbool.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> + +#include "zygote.h" + +/* Zygote info. */ +static int zygote_sock = -1; + +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"); + + close(fd); +} + +int +zygote_clone(zygote_func_t *func, 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, "func", (uint64_t)(uintptr_t)func); + 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); +} + +/* + * 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 sock) +{ + int error, fd, procfd; + int chanfd[2]; + nvlist_t *nvlin, *nvlout; + zygote_func_t *func; + pid_t pid; + + assert(sock > STDERR_FILENO); + + setproctitle("zygote"); + + stdnull(); + for (fd = STDERR_FILENO + 1; fd < sock; fd++) + close(fd); + closefrom(sock + 1); + + for (;;) { + nvlin = nvlist_recv(sock, 0); + if (nvlin == NULL) { + if (errno == ENOTCONN) { + /* Casper exited. */ + exit(0); + } + continue; + } + func = (zygote_func_t *)(uintptr_t)nvlist_get_number(nvlin, + "func"); + nvlist_destroy(nvlin); + + /* + * 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(sock); + 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(sock, 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..e147287e1dbc --- /dev/null +++ b/lib/libcasper/libcasper/zygote.h @@ -0,0 +1,41 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#ifndef _ZYGOTE_H_ +#define _ZYGOTE_H_ + +typedef void zygote_func_t(int); + +int zygote_init(void); +int zygote_clone(zygote_func_t *func, int *chanfdp, int *procfdp); + +#endif /* !_ZYGOTE_H_ */ diff --git a/lib/libcasper/services/Makefile b/lib/libcasper/services/Makefile new file mode 100644 index 000000000000..c80e58932f1a --- /dev/null +++ b/lib/libcasper/services/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +SUBDIR= cap_dns +SUBDIR+= cap_grp +SUBDIR+= cap_pwd +SUBDIR+= cap_random +SUBDIR+= cap_sysctl + +SUBDIR_PARALLEL= + +.include <bsd.subdir.mk> diff --git a/lib/libcasper/services/Makefile.inc b/lib/libcasper/services/Makefile.inc new file mode 100644 index 000000000000..265f86d1ed55 --- /dev/null +++ b/lib/libcasper/services/Makefile.inc @@ -0,0 +1,3 @@ +# $FreeBSD$ + +.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..2582fe89b14c --- /dev/null +++ b/lib/libcasper/services/cap_dns/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +PACKAGE=libcasper +LIB= cap_dns + +SHLIB_MAJOR= 0 +SHLIBDIR?= /lib/casper +INCSDIR?= ${INCLUDEDIR}/casper + +SRCS= cap_dns.c + +INCS= cap_dns.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +.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..fcd81696889c --- /dev/null +++ b/lib/libcasper/services/cap_dns/Makefile.depend @@ -0,0 +1,20 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + 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.c b/lib/libcasper/services/cap_dns/cap_dns.c new file mode 100644 index 000000000000..873af4cdcdde --- /dev/null +++ b/lib/libcasper/services/cap_dns/cap_dns.c @@ -0,0 +1,761 @@ +/*- + * Copyright (c) 2012-2013 The FreeBSD Foundation + * 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/cdefs.h> +__FBSDID("$FreeBSD$"); + +#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(sizeof(hp->h_aliases[0]), nitems + 1); + 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(sizeof(hp->h_addr_list[0]), nitems + 1); + 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, 0); + 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, 0); + 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, 0); + 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, 0); + 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 + 1); + if (serv != NULL && nvlist_exists_string(nvl, "serv")) + strlcpy(serv, nvlist_get_string(nvl, "serv"), servlen + 1); + 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, "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, "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, "NAME")) + 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 + 1); + if (host == NULL) { + error = EAI_MEMORY; + goto out; + } + } + if (servlen > 0) { + serv = calloc(1, servlen + 1); + 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, "ADDR")) + 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; + 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, "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); 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..ea3754255286 --- /dev/null +++ b/lib/libcasper/services/cap_dns/cap_dns.h @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * 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. + * + * $FreeBSD$ + */ + +#ifndef _CAP_DNS_H_ +#define _CAP_DNS_H_ + +#include <sys/socket.h> /* socklen_t */ + +struct addrinfo; +struct hostent; + +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); + +#endif /* !_CAP_DNS_H_ */ diff --git a/lib/libcasper/services/cap_grp/Makefile b/lib/libcasper/services/cap_grp/Makefile new file mode 100644 index 000000000000..195b08d25777 --- /dev/null +++ b/lib/libcasper/services/cap_grp/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +PACKAGE=libcasper +LIB= cap_grp + +SHLIB_MAJOR= 0 +SHLIBDIR?= /lib/casper +INCSDIR?= ${INCLUDEDIR}/casper + +SRCS= cap_grp.c + +INCS= cap_grp.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +.include <bsd.lib.mk> diff --git a/lib/libcasper/services/cap_grp/Makefile.depend b/lib/libcasper/services/cap_grp/Makefile.depend new file mode 100644 index 000000000000..fcd81696889c --- /dev/null +++ b/lib/libcasper/services/cap_grp/Makefile.depend @@ -0,0 +1,20 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + 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.c b/lib/libcasper/services/cap_grp/cap_grp.c new file mode 100644 index 000000000000..d358cea95882 --- /dev/null +++ b/lib/libcasper/services/cap_grp/cap_grp.c @@ -0,0 +1,787 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * 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/cdefs.h> +__FBSDID("$FreeBSD$"); + +#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); + + memset(grp, 0, 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, 0); + 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, 0); + 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, 0); + 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, 0)); +} + +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, 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) +{ + + return (setgrent() == 0 ? EFAULT : 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); 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..68595c145e23 --- /dev/null +++ b/lib/libcasper/services/cap_grp/cap_grp.h @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * 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. + * + * $FreeBSD$ + */ + +#ifndef _CAP_GRP_H_ +#define _CAP_GRP_H_ + +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, gid_t *gids, size_t ngids); + +#endif /* !_CAP_GRP_H_ */ diff --git a/lib/libcasper/services/cap_pwd/Makefile b/lib/libcasper/services/cap_pwd/Makefile new file mode 100644 index 000000000000..3e604bd54c53 --- /dev/null +++ b/lib/libcasper/services/cap_pwd/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +PACKAGE=libcasper +LIB= cap_pwd + +SHLIB_MAJOR= 0 +SHLIBDIR?= /lib/casper +INCSDIR?= ${INCLUDEDIR}/casper + +SRCS= cap_pwd.c + +INCS= cap_pwd.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +.include <bsd.lib.mk> diff --git a/lib/libcasper/services/cap_pwd/Makefile.depend b/lib/libcasper/services/cap_pwd/Makefile.depend new file mode 100644 index 000000000000..fcd81696889c --- /dev/null +++ b/lib/libcasper/services/cap_pwd/Makefile.depend @@ -0,0 +1,20 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + 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.c b/lib/libcasper/services/cap_pwd/cap_pwd.c new file mode 100644 index 000000000000..254e2441426b --- /dev/null +++ b/lib/libcasper/services/cap_pwd/cap_pwd.c @@ -0,0 +1,783 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * 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/cdefs.h> +__FBSDID("$FreeBSD$"); + +#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); + + memset(pwd, 0, 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, 0); + 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, 0); + 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, 0)); +} + +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); 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..a75fba74f972 --- /dev/null +++ b/lib/libcasper/services/cap_pwd/cap_pwd.h @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * 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. + * + * $FreeBSD$ + */ + +#ifndef _CAP_PWD_H_ +#define _CAP_PWD_H_ + +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); + +#endif /* !_CAP_PWD_H_ */ diff --git a/lib/libcasper/services/cap_random/Makefile b/lib/libcasper/services/cap_random/Makefile new file mode 100644 index 000000000000..97a27beb134a --- /dev/null +++ b/lib/libcasper/services/cap_random/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +PACKAGE=libcasper +LIB= cap_random + +SHLIB_MAJOR= 0 +SHLIBDIR?= /lib/casper +INCSDIR?= ${INCLUDEDIR}/casper + +SRCS= cap_random.c + +INCS= cap_random.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +.include <bsd.lib.mk> diff --git a/lib/libcasper/services/cap_random/Makefile.depend b/lib/libcasper/services/cap_random/Makefile.depend new file mode 100644 index 000000000000..fcd81696889c --- /dev/null +++ b/lib/libcasper/services/cap_random/Makefile.depend @@ -0,0 +1,20 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + 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_random/cap_random.c b/lib/libcasper/services/cap_random/cap_random.c new file mode 100644 index 000000000000..d19fe960ea3a --- /dev/null +++ b/lib/libcasper/services/cap_random/cap_random.c @@ -0,0 +1,117 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * 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/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/nv.h> + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libcasper.h> +#include <libcasper_service.h> + +#include "cap_random.h" + +#define MAXSIZE (1024 * 1024) + +int +cap_random_buf(cap_channel_t *chan, void *buf, size_t nbytes) +{ + nvlist_t *nvl; + const void *randbuf; + uint8_t *ptr; + size_t left, randbufsize; + + left = nbytes; + ptr = buf; + + while (left > 0) { + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "generate"); + nvlist_add_number(nvl, "size", + (uint64_t)(left > MAXSIZE ? MAXSIZE : left)); + nvl = cap_xfer_nvlist(chan, nvl, 0); + if (nvl == NULL) + return (-1); + if (nvlist_get_number(nvl, "error") != 0) { + errno = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + return (-1); + } + + randbuf = nvlist_get_binary(nvl, "data", &randbufsize); + memcpy(ptr, randbuf, randbufsize); + + nvlist_destroy(nvl); + + ptr += randbufsize; + assert(left >= randbufsize); + left -= randbufsize; + } + + return (0); +} + +/* + * Service functions. + */ + +static int +random_command(const char *cmd, const nvlist_t *limits __unused, + nvlist_t *nvlin, nvlist_t *nvlout) +{ + void *data; + size_t size; + + if (strcmp(cmd, "generate") != 0) + return (EINVAL); + if (!nvlist_exists_number(nvlin, "size")) + return (EINVAL); + + size = (size_t)nvlist_get_number(nvlin, "size"); + if (size == 0 || size > MAXSIZE) + return (EINVAL); + + data = malloc(size); + if (data == NULL) + return (ENOMEM); + + arc4random_buf(data, size); + + nvlist_move_binary(nvlout, "data", data, size); + + return (0); +} + +CREATE_SERVICE("system.random", NULL, random_command); diff --git a/lib/libcasper/services/cap_random/cap_random.h b/lib/libcasper/services/cap_random/cap_random.h new file mode 100644 index 000000000000..2039b5fe8460 --- /dev/null +++ b/lib/libcasper/services/cap_random/cap_random.h @@ -0,0 +1,37 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * 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. + * + * $FreeBSD$ + */ + +#ifndef _CAP_RANDOM_H_ +#define _CAP_RANDOM_H_ + +int cap_random_buf(cap_channel_t *chan, void *buf, size_t nbytes); + +#endif /* !_CAP_RANDOM_H_ */ diff --git a/lib/libcasper/services/cap_sysctl/Makefile b/lib/libcasper/services/cap_sysctl/Makefile new file mode 100644 index 000000000000..7501f37ab4e0 --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +PACKAGE=libcasper +LIB= cap_sysctl + +SHLIB_MAJOR= 0 +SHLIBDIR?= /lib/casper +INCSDIR?= ${INCLUDEDIR}/casper + +SRCS= cap_sysctl.c + +INCS= cap_sysctl.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +.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..fcd81696889c --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/Makefile.depend @@ -0,0 +1,20 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + 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.c b/lib/libcasper/services/cap_sysctl/cap_sysctl.c new file mode 100644 index 000000000000..7611d647f11a --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/cap_sysctl.c @@ -0,0 +1,293 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * 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/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/sysctl.h> +#include <sys/nv.h> + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <libcasper.h> +#include <libcasper_service.h> + +#include "cap_sysctl.h" + +int +cap_sysctlbyname(cap_channel_t *chan, const char *name, void *oldp, + size_t *oldlenp, const void *newp, size_t newlen) +{ + nvlist_t *nvl; + const uint8_t *retoldp; + uint8_t operation; + size_t oldlen; + + operation = 0; + if (oldp != NULL) + operation |= CAP_SYSCTL_READ; + if (newp != NULL) + operation |= CAP_SYSCTL_WRITE; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "sysctl"); + nvlist_add_string(nvl, "name", name); + 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, 0); + if (nvl == NULL) + return (-1); + if (nvlist_get_number(nvl, "error") != 0) { + errno = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + 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); +} + +/* + * Service functions. + */ +static int +sysctl_check_one(const nvlist_t *nvl, bool islimit) +{ + const char *name; + void *cookie; + int type; + unsigned int 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_OPERATION 0x02 + + fields = 0; + cookie = NULL; + while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) { + /* We accept only one 'name' and one 'operation' in nvl. */ + if (strcmp(name, "name") == 0) { + if (type != NV_TYPE_STRING) + return (EINVAL); + /* Only one 'name' can be present. */ + if ((fields & HAS_NAME) != 0) + return (EINVAL); + fields |= HAS_NAME; + } else if (strcmp(name, "operation") == 0) { + uint64_t operation; + + if (type != NV_TYPE_NUMBER) + return (EINVAL); + /* + * We accept only CAP_SYSCTL_READ and + * CAP_SYSCTL_WRITE flags. + */ + operation = nvlist_get_number(nvl, name); + if ((operation & ~(CAP_SYSCTL_RDWR)) != 0) + return (EINVAL); + /* ...but there has to be at least one of them. */ + if ((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 (islimit) { + /* If this is limit, there can be no other fields. */ + return (EINVAL); + } + } + + /* Both fields has to be there. */ + if (fields != (HAS_NAME | HAS_OPERATION)) + return (EINVAL); + +#undef HAS_OPERATION +#undef HAS_NAME + + return (0); +} + +static bool +sysctl_allowed(const nvlist_t *limits, const char *chname, uint64_t choperation) +{ + uint64_t operation; + const char *name; + void *cookie; + int type; + + if (limits == NULL) + return (true); + + cookie = NULL; + while ((name = nvlist_next(limits, &type, &cookie)) != NULL) { + assert(type == NV_TYPE_NUMBER); + + operation = nvlist_get_number(limits, name); + if ((operation & choperation) != choperation) + continue; + + if ((operation & CAP_SYSCTL_RECURSIVE) == 0) { + if (strcmp(name, chname) != 0) + continue; + } else { + size_t namelen; + + namelen = strlen(name); + if (strncmp(name, chname, namelen) != 0) + continue; + if (chname[namelen] != '.' && chname[namelen] != '\0') + continue; + } + + return (true); + } + + return (false); +} + +static int +sysctl_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + uint64_t operation; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NUMBER) + return (EINVAL); + operation = nvlist_get_number(newlimits, name); + if ((operation & ~(CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) != 0) + return (EINVAL); + if ((operation & (CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) == 0) + return (EINVAL); + if (!sysctl_allowed(oldlimits, name, operation)) + return (ENOTCAPABLE); + } + + 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; + void *oldp; + uint64_t operation; + size_t oldlen, newlen; + size_t *oldlenp; + int error; + + if (strcmp(cmd, "sysctl") != 0) + return (EINVAL); + error = sysctl_check_one(nvlin, false); + if (error != 0) + return (error); + + name = nvlist_get_string(nvlin, "name"); + operation = nvlist_get_number(nvlin, "operation"); + if (!sysctl_allowed(limits, name, operation)) + return (ENOTCAPABLE); + + 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 (sysctlbyname(name, oldp, oldlenp, newp, newlen) == -1) { + 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); 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..2af6d2157695 --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/cap_sysctl.h @@ -0,0 +1,43 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * 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. + * + * $FreeBSD$ + */ + +#ifndef _CAP_SYSCTL_H_ +#define _CAP_SYSCTL_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 + +int cap_sysctlbyname(cap_channel_t *chan, const char *name, void *oldp, + size_t *oldlenp, const void *newp, size_t newlen); + +#endif /* !_CAP_SYSCTL_H_ */ |