1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2013 The FreeBSD Foundation
5 * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org>
6 * All rights reserved.
7 *
8 * This software was developed by Pawel Jakub Dawidek under sponsorship from
9 * the FreeBSD Foundation.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/types.h>
34#include <sys/queue.h>
35#include <sys/socket.h>
36#include <sys/nv.h>
37
38#include <assert.h>
39#include <dirent.h>
40#include <err.h>
41#include <errno.h>
42#include <fcntl.h>
43#include <paths.h>
44#include <stdbool.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <strings.h>
49#include <unistd.h>
50
51#include "libcasper.h"
52#include "libcasper_impl.h"
53
54/*
55 * Currently there is only one service_connection per service.
56 * In the future we may want multiple connections from multiple clients
57 * per one service instance, but it has to be carefully designed.
58 * The problem is that we may restrict/sandbox service instance according
59 * to the limits provided. When new connection comes in with different
60 * limits we won't be able to access requested resources.
61 * Not to mention one process will serve to multiple mutually untrusted
62 * clients and compromise of this service instance by one of its clients
63 * can lead to compromise of the other clients.
64 */
65
66/*
67 * Client connections to the given service.
68 */
69#define	SERVICE_CONNECTION_MAGIC	0x5e91c0ec
70struct service_connection {
71	int		 sc_magic;
72	cap_channel_t	*sc_chan;
73	nvlist_t	*sc_limits;
74	TAILQ_ENTRY(service_connection) sc_next;
75};
76
77#define	SERVICE_MAGIC	0x5e91ce
78struct service {
79	int			 s_magic;
80	char			*s_name;
81	uint64_t		 s_flags;
82	service_limit_func_t	*s_limit;
83	service_command_func_t	*s_command;
84	TAILQ_HEAD(, service_connection) s_connections;
85};
86
87struct service *
88service_alloc(const char *name, service_limit_func_t *limitfunc,
89    service_command_func_t *commandfunc, uint64_t flags)
90{
91	struct service *service;
92
93	service = malloc(sizeof(*service));
94	if (service == NULL)
95		return (NULL);
96	service->s_name = strdup(name);
97	if (service->s_name == NULL) {
98		free(service);
99		return (NULL);
100	}
101	service->s_limit = limitfunc;
102	service->s_command = commandfunc;
103	service->s_flags = flags;
104	TAILQ_INIT(&service->s_connections);
105	service->s_magic = SERVICE_MAGIC;
106
107	return (service);
108}
109
110void
111service_free(struct service *service)
112{
113	struct service_connection *sconn;
114
115	assert(service->s_magic == SERVICE_MAGIC);
116
117	service->s_magic = 0;
118	while ((sconn = service_connection_first(service)) != NULL)
119		service_connection_remove(service, sconn);
120	free(service->s_name);
121	free(service);
122}
123
124struct service_connection *
125service_connection_add(struct service *service, int sock,
126    const nvlist_t *limits)
127{
128	struct service_connection *sconn;
129	int serrno;
130
131	assert(service->s_magic == SERVICE_MAGIC);
132
133	sconn = malloc(sizeof(*sconn));
134	if (sconn == NULL)
135		return (NULL);
136	sconn->sc_chan = cap_wrap(sock,
137	    service_get_channel_flags(service));
138	if (sconn->sc_chan == NULL) {
139		serrno = errno;
140		free(sconn);
141		errno = serrno;
142		return (NULL);
143	}
144	if (limits == NULL) {
145		sconn->sc_limits = NULL;
146	} else {
147		sconn->sc_limits = nvlist_clone(limits);
148		if (sconn->sc_limits == NULL) {
149			serrno = errno;
150			(void)cap_unwrap(sconn->sc_chan, NULL);
151			free(sconn);
152			errno = serrno;
153			return (NULL);
154		}
155	}
156	sconn->sc_magic = SERVICE_CONNECTION_MAGIC;
157	TAILQ_INSERT_TAIL(&service->s_connections, sconn, sc_next);
158	return (sconn);
159}
160
161void
162service_connection_remove(struct service *service,
163    struct service_connection *sconn)
164{
165
166	assert(service->s_magic == SERVICE_MAGIC);
167	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
168
169	TAILQ_REMOVE(&service->s_connections, sconn, sc_next);
170	sconn->sc_magic = 0;
171	nvlist_destroy(sconn->sc_limits);
172	cap_close(sconn->sc_chan);
173	free(sconn);
174}
175
176int
177service_connection_clone(struct service *service,
178    struct service_connection *sconn)
179{
180	struct service_connection *newsconn;
181	int serrno, sock[2];
182
183	if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sock) < 0)
184		return (-1);
185
186	newsconn = service_connection_add(service, sock[0],
187	    service_connection_get_limits(sconn));
188	if (newsconn == NULL) {
189		serrno = errno;
190		close(sock[0]);
191		close(sock[1]);
192		errno = serrno;
193		return (-1);
194	}
195
196	return (sock[1]);
197}
198
199struct service_connection *
200service_connection_first(struct service *service)
201{
202	struct service_connection *sconn;
203
204	assert(service->s_magic == SERVICE_MAGIC);
205
206	sconn = TAILQ_FIRST(&service->s_connections);
207	assert(sconn == NULL ||
208	    sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
209	return (sconn);
210}
211
212struct service_connection *
213service_connection_next(struct service_connection *sconn)
214{
215
216	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
217
218	sconn = TAILQ_NEXT(sconn, sc_next);
219	assert(sconn == NULL ||
220	    sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
221	return (sconn);
222}
223
224cap_channel_t *
225service_connection_get_chan(const struct service_connection *sconn)
226{
227
228	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
229
230	return (sconn->sc_chan);
231}
232
233int
234service_connection_get_sock(const struct service_connection *sconn)
235{
236
237	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
238
239	return (cap_sock(sconn->sc_chan));
240}
241
242const nvlist_t *
243service_connection_get_limits(const struct service_connection *sconn)
244{
245
246	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
247
248	return (sconn->sc_limits);
249}
250
251void
252service_connection_set_limits(struct service_connection *sconn,
253    nvlist_t *limits)
254{
255
256	assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC);
257
258	nvlist_destroy(sconn->sc_limits);
259	sconn->sc_limits = limits;
260}
261
262void
263service_message(struct service *service, struct service_connection *sconn)
264{
265	nvlist_t *nvlin, *nvlout;
266	const char *cmd;
267	int error, flags;
268
269	flags = 0;
270	if ((service->s_flags & CASPER_SERVICE_NO_UNIQ_LIMITS) != 0)
271		flags = NV_FLAG_NO_UNIQUE;
272
273	nvlin = cap_recv_nvlist(service_connection_get_chan(sconn));
274	if (nvlin == NULL) {
275		service_connection_remove(service, sconn);
276		return;
277	}
278
279	error = EDOOFUS;
280	nvlout = nvlist_create(flags);
281
282	cmd = nvlist_get_string(nvlin, "cmd");
283	if (strcmp(cmd, "limit_set") == 0) {
284		nvlist_t *nvllim;
285
286		nvllim = nvlist_take_nvlist(nvlin, "limits");
287		if (service->s_limit == NULL) {
288			error = EOPNOTSUPP;
289		} else {
290			error = service->s_limit(
291			    service_connection_get_limits(sconn), nvllim);
292		}
293		if (error == 0) {
294			service_connection_set_limits(sconn, nvllim);
295			/* Function consumes nvllim. */
296		} else {
297			nvlist_destroy(nvllim);
298		}
299	} else if (strcmp(cmd, "limit_get") == 0) {
300		const nvlist_t *nvllim;
301
302		nvllim = service_connection_get_limits(sconn);
303		if (nvllim != NULL)
304			nvlist_add_nvlist(nvlout, "limits", nvllim);
305		else
306			nvlist_add_null(nvlout, "limits");
307		error = 0;
308	} else if (strcmp(cmd, "clone") == 0) {
309		int sock;
310
311		sock = service_connection_clone(service, sconn);
312		if (sock == -1) {
313			error = errno;
314		} else {
315			nvlist_move_descriptor(nvlout, "sock", sock);
316			error = 0;
317		}
318	} else {
319		error = service->s_command(cmd,
320		    service_connection_get_limits(sconn), nvlin, nvlout);
321	}
322
323	nvlist_destroy(nvlin);
324	nvlist_add_number(nvlout, "error", (uint64_t)error);
325
326	if (cap_send_nvlist(service_connection_get_chan(sconn), nvlout) == -1)
327		service_connection_remove(service, sconn);
328
329	nvlist_destroy(nvlout);
330}
331
332static int
333fd_add(fd_set *fdsp, int maxfd, int fd)
334{
335
336	FD_SET(fd, fdsp);
337	return (fd > maxfd ? fd : maxfd);
338}
339
340const char *
341service_name(struct service *service)
342{
343
344	assert(service->s_magic == SERVICE_MAGIC);
345	return (service->s_name);
346}
347
348int
349service_get_channel_flags(struct service *service)
350{
351	int flags;
352
353	assert(service->s_magic == SERVICE_MAGIC);
354	flags = 0;
355
356	if ((service->s_flags & CASPER_SERVICE_NO_UNIQ_LIMITS) != 0)
357		flags |= CASPER_NO_UNIQ;
358
359	return (flags);
360}
361
362static void
363stdnull(void)
364{
365	int fd;
366
367	fd = open(_PATH_DEVNULL, O_RDWR);
368	if (fd == -1)
369		errx(1, "Unable to open %s", _PATH_DEVNULL);
370
371	if (setsid() == -1)
372		errx(1, "Unable to detach from session");
373
374	if (dup2(fd, STDIN_FILENO) == -1)
375		errx(1, "Unable to cover stdin");
376	if (dup2(fd, STDOUT_FILENO) == -1)
377		errx(1, "Unable to cover stdout");
378	if (dup2(fd, STDERR_FILENO) == -1)
379		errx(1, "Unable to cover stderr");
380
381	if (fd > STDERR_FILENO)
382		close(fd);
383}
384
385static void
386service_clean(int *sockp, int *procfdp, uint64_t flags)
387{
388	int fd, maxfd, minfd;
389
390	fd_fix_environment(sockp);
391	fd_fix_environment(procfdp);
392
393	assert(*sockp > STDERR_FILENO);
394	assert(*procfdp > STDERR_FILENO);
395	assert(*sockp != *procfdp);
396
397	if ((flags & CASPER_SERVICE_STDIO) == 0)
398		stdnull();
399
400	if ((flags & CASPER_SERVICE_FD) == 0) {
401		if (*procfdp > *sockp) {
402			maxfd = *procfdp;
403			minfd = *sockp;
404		} else {
405			maxfd = *sockp;
406			minfd = *procfdp;
407		}
408
409		for (fd = STDERR_FILENO + 1; fd < maxfd; fd++) {
410			if (fd != minfd)
411				close(fd);
412		}
413		closefrom(maxfd + 1);
414	}
415}
416
417void
418service_start(struct service *service, int sock, int procfd)
419{
420	struct service_connection *sconn, *sconntmp;
421	fd_set fds;
422	int maxfd, nfds;
423
424	assert(service != NULL);
425	assert(service->s_magic == SERVICE_MAGIC);
426	setproctitle("%s", service->s_name);
427	service_clean(&sock, &procfd, service->s_flags);
428
429	if (service_connection_add(service, sock, NULL) == NULL)
430		_exit(1);
431
432	for (;;) {
433		FD_ZERO(&fds);
434		maxfd = -1;
435		for (sconn = service_connection_first(service); sconn != NULL;
436		    sconn = service_connection_next(sconn)) {
437			maxfd = fd_add(&fds, maxfd,
438			    service_connection_get_sock(sconn));
439		}
440
441		assert(maxfd >= 0);
442		assert(maxfd + 1 <= (int)FD_SETSIZE);
443		nfds = select(maxfd + 1, &fds, NULL, NULL, NULL);
444		if (nfds < 0) {
445			if (errno != EINTR)
446				_exit(1);
447			continue;
448		} else if (nfds == 0) {
449			/* Timeout. */
450			abort();
451		}
452
453		for (sconn = service_connection_first(service); sconn != NULL;
454		    sconn = sconntmp) {
455			/*
456			 * Prepare for connection to be removed from the list
457			 * on failure.
458			 */
459			sconntmp = service_connection_next(sconn);
460			if (FD_ISSET(service_connection_get_sock(sconn), &fds))
461				service_message(service, sconn);
462		}
463		if (service_connection_first(service) == NULL) {
464			/*
465			 * No connections left, exiting.
466			 */
467			break;
468		}
469	}
470
471	_exit(0);
472}
473