1/*-
2 * Copyright (c) 2012 The FreeBSD Foundation
3 * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
4 * All rights reserved.
5 *
6 * This software was developed by Pawel Jakub Dawidek under sponsorship from
7 * the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD$");
33
34#include <sys/types.h>
35#include <sys/queue.h>
36#include <sys/socket.h>
37#include <sys/nv.h>
38
39#include <assert.h>
40#include <errno.h>
41#include <stdbool.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46
47#include "libcasper_impl.h"
48#include "zygote.h"
49
50struct casper_service {
51	struct service			*cs_service;
52	TAILQ_ENTRY(casper_service)	 cs_next;
53};
54
55static TAILQ_HEAD(, casper_service) casper_services =
56    TAILQ_HEAD_INITIALIZER(casper_services);
57
58#define	CORE_CASPER_NAME		"core.casper"
59#define	CSERVICE_IS_CORE(service)	\
60	(strcmp(service_name(service->cs_service), CORE_CASPER_NAME) == 0)
61
62static struct casper_service *
63service_find(const char *name)
64{
65	struct casper_service *casserv;
66
67	TAILQ_FOREACH(casserv, &casper_services, cs_next) {
68		if (strcmp(service_name(casserv->cs_service), name) == 0)
69			break;
70	}
71	return (casserv);
72}
73
74struct casper_service *
75service_register(const char *name, service_limit_func_t *limitfunc,
76   service_command_func_t *commandfunc, uint64_t flags)
77{
78	struct casper_service *casserv;
79
80	if (commandfunc == NULL)
81		return (NULL);
82	if (name == NULL || name[0] == '\0')
83		return (NULL);
84	if (service_find(name) != NULL)
85		return (NULL);
86
87	casserv = malloc(sizeof(*casserv));
88	if (casserv == NULL)
89		return (NULL);
90
91	casserv->cs_service = service_alloc(name, limitfunc, commandfunc,
92	    flags);
93	if (casserv->cs_service == NULL) {
94		free(casserv);
95		return (NULL);
96	}
97	TAILQ_INSERT_TAIL(&casper_services, casserv, cs_next);
98
99	return (casserv);
100}
101
102static bool
103casper_allowed_service(const nvlist_t *limits, const char *service)
104{
105
106	if (limits == NULL)
107		return (true);
108
109	if (nvlist_exists_null(limits, service))
110		return (true);
111
112	return (false);
113}
114
115static int
116casper_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
117{
118	const char *name;
119	int type;
120	void *cookie;
121
122	cookie = NULL;
123	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
124		if (type != NV_TYPE_NULL)
125			return (EINVAL);
126		if (!casper_allowed_service(oldlimits, name))
127			return (ENOTCAPABLE);
128	}
129
130	return (0);
131}
132
133static void
134service_execute(int chanfd)
135{
136	struct service *service;
137	nvlist_t *nvl;
138	int procfd;
139
140	nvl = nvlist_recv(chanfd, 0);
141	if (nvl == NULL)
142		exit(1);
143	service = (struct service *)(uintptr_t)nvlist_take_number(nvl,
144	    "service");
145	procfd = nvlist_take_descriptor(nvl, "procfd");
146	nvlist_destroy(nvl);
147
148	service_start(service, chanfd, procfd);
149	/* Not reached. */
150	exit(1);
151}
152
153static int
154casper_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
155    nvlist_t *nvlout)
156{
157	struct casper_service *casserv;
158	const char *servname;
159	nvlist_t *nvl;
160	int chanfd, procfd, error;
161
162	if (strcmp(cmd, "open") != 0)
163		return (EINVAL);
164	if (!nvlist_exists_string(nvlin, "service"))
165		return (EINVAL);
166
167	servname = nvlist_get_string(nvlin, "service");
168	casserv = service_find(servname);
169	if (casserv == NULL)
170		return (ENOENT);
171
172	if (!casper_allowed_service(limits, servname))
173		return (ENOTCAPABLE);
174
175	if (zygote_clone(service_execute, &chanfd, &procfd) == -1)
176		return (errno);
177
178	nvl = nvlist_create(0);
179	nvlist_add_number(nvl, "service",
180	    (uint64_t)(uintptr_t)casserv->cs_service);
181	nvlist_move_descriptor(nvl, "procfd", procfd);
182	if (nvlist_send(chanfd, nvl) == -1) {
183		error = errno;
184		nvlist_destroy(nvl);
185		close(chanfd);
186		return (error);
187	}
188	nvlist_destroy(nvl);
189
190	nvlist_move_descriptor(nvlout, "chanfd", chanfd);
191
192	return (0);
193}
194
195static void
196service_register_core(int fd)
197{
198	struct casper_service *casserv;
199	struct service_connection *sconn;
200
201	casserv = service_register(CORE_CASPER_NAME, casper_limit,
202	    casper_command, 0);
203	sconn = service_connection_add(casserv->cs_service, fd, NULL);
204	if (sconn == NULL) {
205		close(fd);
206		abort();
207	}
208}
209
210void
211casper_main_loop(int fd)
212{
213	fd_set fds;
214	struct casper_service *casserv;
215	struct service_connection *sconn, *sconntmp;
216	int sock, maxfd, ret;
217
218	if (zygote_init() < 0)
219		exit(1);
220
221	/*
222	 * Register core services.
223	 */
224	service_register_core(fd);
225
226	for (;;) {
227		FD_ZERO(&fds);
228		FD_SET(fd, &fds);
229		maxfd = -1;
230		TAILQ_FOREACH(casserv, &casper_services, cs_next) {
231			/* We handle only core services. */
232			if (!CSERVICE_IS_CORE(casserv))
233				continue;
234			for (sconn = service_connection_first(casserv->cs_service);
235			    sconn != NULL;
236			    sconn = service_connection_next(sconn)) {
237				sock = service_connection_get_sock(sconn);
238				FD_SET(sock, &fds);
239				maxfd = sock > maxfd ? sock : maxfd;
240			}
241		}
242		if (maxfd == -1) {
243			/* Nothing to do. */
244			exit(0);
245		}
246		maxfd++;
247
248
249		assert(maxfd <= (int)FD_SETSIZE);
250		ret = select(maxfd, &fds, NULL, NULL, NULL);
251		assert(ret == -1 || ret > 0);	/* select() cannot timeout */
252		if (ret == -1) {
253			if (errno == EINTR)
254				continue;
255			exit(1);
256		}
257
258		TAILQ_FOREACH(casserv, &casper_services, cs_next) {
259			/* We handle only core services. */
260			if (!CSERVICE_IS_CORE(casserv))
261				continue;
262			for (sconn = service_connection_first(casserv->cs_service);
263			    sconn != NULL; sconn = sconntmp) {
264				/*
265				 * Prepare for connection to be removed from
266				 * the list on failure.
267				 */
268				sconntmp = service_connection_next(sconn);
269				sock = service_connection_get_sock(sconn);
270				if (FD_ISSET(sock, &fds)) {
271					service_message(casserv->cs_service,
272					    sconn);
273				}
274			}
275		}
276	}
277}
278