1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include <sys/types.h>
33 #include <sys/capsicum.h>
34 #include <sys/sysctl.h>
35 #include <sys/cnv.h>
36 #include <sys/dnv.h>
37 #include <sys/nv.h>
38 
39 #include <assert.h>
40 #include <errno.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 
45 #include <libcasper.h>
46 #include <libcasper_service.h>
47 
48 #include "cap_fileargs.h"
49 
50 #define CACHE_SIZE	128
51 
52 #define FILEARGS_MAGIC	0xFA00FA00
53 
54 struct fileargs {
55 	uint32_t	 fa_magic;
56 	nvlist_t	*fa_cache;
57 	cap_channel_t	*fa_chann;
58 	int		 fa_fdflags;
59 };
60 
61 static int
62 fileargs_get_cache(fileargs_t *fa, const char *name)
63 {
64 	int fd;
65 	const nvlist_t *nvl;
66 	nvlist_t *tnvl;
67 
68 	assert(fa != NULL);
69 	assert(fa->fa_magic == FILEARGS_MAGIC);
70 	assert(name != NULL);
71 
72 	if (fa->fa_cache == NULL)
73 		return (-1);
74 
75 	if ((fa->fa_fdflags & O_CREAT) != 0)
76 		return (-1);
77 
78 	nvl = dnvlist_get_nvlist(fa->fa_cache, name, NULL);
79 	if (nvl == NULL)
80 		return (-1);
81 
82 	tnvl = nvlist_take_nvlist(fa->fa_cache, name);
83 	fd = nvlist_take_descriptor(tnvl, "fd");
84 	nvlist_destroy(tnvl);
85 
86 	if ((fa->fa_fdflags & O_CLOEXEC) != O_CLOEXEC) {
87 		if (fcntl(fd, F_SETFD, fa->fa_fdflags) == -1) {
88 			close(fd);
89 			return (-1);
90 		}
91 	}
92 
93 	return (fd);
94 }
95 
96 static void
97 fileargs_set_cache(fileargs_t *fa, nvlist_t *nvl)
98 {
99 
100 	nvlist_destroy(fa->fa_cache);
101 	fa->fa_cache = nvl;
102 }
103 
104 static nvlist_t*
105 fileargs_fetch(fileargs_t *fa, const char *name)
106 {
107 	nvlist_t *nvl;
108 	int serrno;
109 
110 	assert(fa != NULL);
111 	assert(name != NULL);
112 
113 	nvl = nvlist_create(NV_FLAG_NO_UNIQUE);
114 	nvlist_add_string(nvl, "cmd", "open");
115 	nvlist_add_string(nvl, "name", name);
116 
117 	nvl = cap_xfer_nvlist(fa->fa_chann, nvl);
118 	if (nvl == NULL)
119 		return (NULL);
120 
121 	if (nvlist_get_number(nvl, "error") != 0) {
122 		serrno = (int)nvlist_get_number(nvl, "error");
123 		nvlist_destroy(nvl);
124 		errno = serrno;
125 		return (NULL);
126 	}
127 
128 	return (nvl);
129 }
130 
131 static nvlist_t *
132 fileargs_create_limit(int argc, const char * const *argv, int flags,
133     mode_t mode, cap_rights_t *rightsp)
134 {
135 	nvlist_t *limits;
136 	int i;
137 
138 	limits = nvlist_create(NV_FLAG_NO_UNIQUE);
139 	if (limits == NULL)
140 		return (NULL);
141 
142 	nvlist_add_number(limits, "flags", flags);
143 	if (rightsp != NULL) {
144 		nvlist_add_binary(limits, "cap_rights", rightsp,
145 		    sizeof(*rightsp));
146 	}
147 	if ((flags & O_CREAT) != 0)
148 		nvlist_add_number(limits, "mode", (uint64_t)mode);
149 
150 	for (i = 0; i < argc; i++) {
151 		nvlist_add_null(limits, argv[i]);
152 	}
153 
154 	return (limits);
155 }
156 
157 static fileargs_t *
158 fileargs_create(cap_channel_t *chan, int fdflags)
159 {
160 	fileargs_t *fa;
161 
162 	fa = malloc(sizeof(*fa));
163 	if (fa != NULL) {
164 		fa->fa_cache = NULL;
165 		fa->fa_chann = chan;
166 		fa->fa_fdflags = fdflags;
167 		fa->fa_magic = FILEARGS_MAGIC;
168 	}
169 
170 	return (fa);
171 }
172 
173 fileargs_t *
174 fileargs_init(int argc, char *argv[], int flags, mode_t mode,
175     cap_rights_t *rightsp)
176 {
177 	nvlist_t *limits;
178 
179 	if (argc <= 0 || argv == NULL) {
180 		return (fileargs_create(NULL, 0));
181 	}
182 
183 	limits = fileargs_create_limit(argc, (const char * const *)argv, flags,
184 	   mode, rightsp);
185 	if (limits == NULL)
186 		return (NULL);
187 
188 	return (fileargs_initnv(limits));
189 }
190 
191 fileargs_t *
192 fileargs_cinit(cap_channel_t *cas, int argc, char *argv[], int flags,
193      mode_t mode, cap_rights_t *rightsp)
194 {
195 	nvlist_t *limits;
196 
197 	if (argc <= 0 || argv == NULL) {
198 		return (fileargs_create(NULL, 0));
199 	}
200 
201 	limits = fileargs_create_limit(argc, (const char * const *)argv, flags,
202 	   mode, rightsp);
203 	if (limits == NULL)
204 		return (NULL);
205 
206 	return (fileargs_cinitnv(cas, limits));
207 }
208 
209 fileargs_t *
210 fileargs_initnv(nvlist_t *limits)
211 {
212         cap_channel_t *cas;
213 	fileargs_t *fa;
214 
215 	if (limits == NULL) {
216 		return (fileargs_create(NULL, 0));
217 	}
218 
219         cas = cap_init();
220         if (cas == NULL) {
221 		nvlist_destroy(limits);
222                 return (NULL);
223 	}
224 
225         fa = fileargs_cinitnv(cas, limits);
226         cap_close(cas);
227 
228 	return (fa);
229 }
230 
231 fileargs_t *
232 fileargs_cinitnv(cap_channel_t *cas, nvlist_t *limits)
233 {
234 	cap_channel_t *chann;
235 	fileargs_t *fa;
236 	int serrno, ret;
237 	int flags;
238 
239 	assert(cas != NULL);
240 
241 	if (limits == NULL) {
242 		return (fileargs_create(NULL, 0));
243 	}
244 
245 	chann = NULL;
246 	fa = NULL;
247 
248 	chann = cap_service_open(cas, "system.fileargs");
249 	if (chann == NULL) {
250 		nvlist_destroy(limits);
251 		return (NULL);
252 	}
253 
254 	flags = nvlist_get_number(limits, "flags");
255 
256 	/* Limits are consumed no need to free them. */
257 	ret = cap_limit_set(chann, limits);
258 	if (ret < 0)
259 		goto out;
260 
261 	fa = fileargs_create(chann, flags);
262 	if (fa == NULL)
263 		goto out;
264 
265 	return (fa);
266 out:
267 	serrno = errno;
268 	if (chann != NULL)
269 		cap_close(chann);
270 	errno = serrno;
271 	return (NULL);
272 }
273 
274 int
275 fileargs_open(fileargs_t *fa, const char *name)
276 {
277 	int fd;
278 	nvlist_t *nvl;
279 	char *cmd;
280 
281 	assert(fa != NULL);
282 	assert(fa->fa_magic == FILEARGS_MAGIC);
283 
284 	if (name == NULL) {
285 		errno = EINVAL;
286 		return (-1);
287 	}
288 
289 	if (fa->fa_chann == NULL) {
290 		errno = ENOTCAPABLE;
291 		return (-1);
292 	}
293 
294 	fd = fileargs_get_cache(fa, name);
295 	if (fd != -1)
296 		return (fd);
297 
298 	nvl = fileargs_fetch(fa, name);
299 	if (nvl == NULL)
300 		return (-1);
301 
302 	fd = nvlist_take_descriptor(nvl, "fd");
303 	cmd = nvlist_take_string(nvl, "cmd");
304 	if (strcmp(cmd, "cache") == 0)
305 		fileargs_set_cache(fa, nvl);
306 	else
307 		nvlist_destroy(nvl);
308 	free(cmd);
309 
310 	return (fd);
311 }
312 
313 FILE *
314 fileargs_fopen(fileargs_t *fa, const char *name, const char *mode)
315 {
316 	int fd;
317 
318 	if ((fd = fileargs_open(fa, name)) < 0) {
319 		return (NULL);
320 	}
321 
322 	return (fdopen(fd, mode));
323 }
324 
325 void
326 fileargs_free(fileargs_t *fa)
327 {
328 
329 	if (fa == NULL)
330 		return;
331 
332 	assert(fa->fa_magic == FILEARGS_MAGIC);
333 
334 	nvlist_destroy(fa->fa_cache);
335 	if (fa->fa_chann != NULL) {
336 		cap_close(fa->fa_chann);
337 	}
338 	explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic));
339 	free(fa);
340 }
341 
342 /*
343  * Service functions.
344  */
345 
346 static const char *lastname;
347 static void *cacheposition;
348 static bool allcached;
349 static const cap_rights_t *caprightsp;
350 static int capflags;
351 static mode_t capmode;
352 
353 static int
354 open_file(const char *name)
355 {
356 	int fd, serrno;
357 
358 	if ((capflags & O_CREAT) == 0)
359 		fd = open(name, capflags);
360 	else
361 		fd = open(name, capflags, capmode);
362 	if (fd < 0)
363 		return (-1);
364 
365 	if (caprightsp != NULL) {
366 		if (cap_rights_limit(fd, caprightsp) < 0 && errno != ENOSYS) {
367 			serrno = errno;
368 			close(fd);
369 			errno = serrno;
370 			return (-1);
371 		}
372 	}
373 
374 	return (fd);
375 }
376 
377 static void
378 fileargs_add_cache(nvlist_t *nvlout, const nvlist_t *limits,
379     const char *curent_name)
380 {
381 	int type, i, fd;
382 	void *cookie;
383 	nvlist_t *new;
384 	const char *fname;
385 
386 	if ((capflags & O_CREAT) != 0) {
387 		allcached = true;
388 		return;
389 	}
390 
391 	cookie = cacheposition;
392 	for (i = 0; i < CACHE_SIZE + 1; i++) {
393 		fname = nvlist_next(limits, &type, &cookie);
394 		if (fname == NULL) {
395 			cacheposition = NULL;
396 			lastname = NULL;
397 			allcached = true;
398 			return;
399 		}
400 		/* We doing that to catch next element name. */
401 		if (i == CACHE_SIZE) {
402 			break;
403 		}
404 
405 		if (type != NV_TYPE_NULL ||
406 		    (curent_name != NULL && strcmp(fname, curent_name) == 0)) {
407 			curent_name = NULL;
408 			i--;
409 			continue;
410 		}
411 
412 		fd = open_file(fname);
413 		if (fd < 0) {
414 			i--;
415 			continue;
416 		}
417 
418 		new = nvlist_create(NV_FLAG_NO_UNIQUE);
419 		nvlist_move_descriptor(new, "fd", fd);
420 		nvlist_add_nvlist(nvlout, fname, new);
421 	}
422 	cacheposition = cookie;
423 	lastname = fname;
424 }
425 
426 static bool
427 fileargs_allowed(const nvlist_t *limits, const nvlist_t *request)
428 {
429 	const char *name;
430 
431 	name = dnvlist_get_string(request, "name", NULL);
432 	if (name == NULL)
433 		return (false);
434 
435 	/* Fast path. */
436 	if (lastname != NULL && strcmp(name, lastname) == 0)
437 		return (true);
438 
439 	if (!nvlist_exists_null(limits, name))
440 		return (false);
441 
442 	return (true);
443 }
444 
445 static int
446 fileargs_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
447 {
448 
449 	if (oldlimits != NULL)
450 		return (ENOTCAPABLE);
451 
452 	capflags = (int)dnvlist_get_number(newlimits, "flags", 0);
453 	if ((capflags & O_CREAT) != 0)
454 		capmode = (mode_t)nvlist_get_number(newlimits, "mode");
455 	else
456 		capmode = 0;
457 
458 	caprightsp = dnvlist_get_binary(newlimits, "cap_rights", NULL, NULL, 0);
459 
460 	return (0);
461 }
462 
463 static int
464 fileargs_command_open(const nvlist_t *limits, nvlist_t *nvlin,
465     nvlist_t *nvlout)
466 {
467 	int fd;
468 	const char *name;
469 
470 	if (limits == NULL)
471 		return (ENOTCAPABLE);
472 
473 	if (!fileargs_allowed(limits, nvlin))
474 		return (ENOTCAPABLE);
475 
476 	name = nvlist_get_string(nvlin, "name");
477 
478 	fd = open_file(name);
479 	if (fd < 0)
480 		return (errno);
481 
482 	if (!allcached && (lastname == NULL ||
483 	    strcmp(name, lastname) == 0)) {
484 		nvlist_add_string(nvlout, "cmd", "cache");
485 		fileargs_add_cache(nvlout, limits, name);
486 	} else {
487 		nvlist_add_string(nvlout, "cmd", "open");
488 	}
489 	nvlist_move_descriptor(nvlout, "fd", fd);
490 	return (0);
491 }
492 
493 static int
494 fileargs_command(const char *cmd, const nvlist_t *limits,
495     nvlist_t *nvlin, nvlist_t *nvlout)
496 {
497 
498 	if (strcmp(cmd, "open") == 0)
499 		return (fileargs_command_open(limits, nvlin, nvlout));
500 
501 	return (EINVAL);
502 }
503 
504 CREATE_SERVICE("system.fileargs", fileargs_limit, fileargs_command,
505     CASPER_SERVICE_FD | CASPER_SERVICE_STDIO | CASPER_SERVICE_NO_UNIQ_LIMITS);
506