1/*-
2 * Copyright (c) 2013 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Pawel Jakub Dawidek under sponsorship from
6 * the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD$");
32
33#include <sys/types.h>
34#include <sys/sysctl.h>
35#include <sys/nv.h>
36
37#include <assert.h>
38#include <errno.h>
39#include <stdlib.h>
40#include <string.h>
41
42#include <libcasper.h>
43#include <libcasper_service.h>
44
45#include "cap_sysctl.h"
46
47int
48cap_sysctlbyname(cap_channel_t *chan, const char *name, void *oldp,
49    size_t *oldlenp, const void *newp, size_t newlen)
50{
51	nvlist_t *nvl;
52	const uint8_t *retoldp;
53	uint8_t operation;
54	size_t oldlen;
55
56	operation = 0;
57	if (oldp != NULL)
58		operation |= CAP_SYSCTL_READ;
59	if (newp != NULL)
60		operation |= CAP_SYSCTL_WRITE;
61
62	nvl = nvlist_create(0);
63	nvlist_add_string(nvl, "cmd", "sysctl");
64	nvlist_add_string(nvl, "name", name);
65	nvlist_add_number(nvl, "operation", (uint64_t)operation);
66	if (oldp == NULL && oldlenp != NULL)
67		nvlist_add_null(nvl, "justsize");
68	else if (oldlenp != NULL)
69		nvlist_add_number(nvl, "oldlen", (uint64_t)*oldlenp);
70	if (newp != NULL)
71		nvlist_add_binary(nvl, "newp", newp, newlen);
72	nvl = cap_xfer_nvlist(chan, nvl, 0);
73	if (nvl == NULL)
74		return (-1);
75	if (nvlist_get_number(nvl, "error") != 0) {
76		errno = (int)nvlist_get_number(nvl, "error");
77		nvlist_destroy(nvl);
78		return (-1);
79	}
80
81	if (oldp == NULL && oldlenp != NULL) {
82		*oldlenp = (size_t)nvlist_get_number(nvl, "oldlen");
83	} else if (oldp != NULL) {
84		retoldp = nvlist_get_binary(nvl, "oldp", &oldlen);
85		memcpy(oldp, retoldp, oldlen);
86		if (oldlenp != NULL)
87			*oldlenp = oldlen;
88	}
89	nvlist_destroy(nvl);
90
91	return (0);
92}
93
94/*
95 * Service functions.
96 */
97static int
98sysctl_check_one(const nvlist_t *nvl, bool islimit)
99{
100	const char *name;
101	void *cookie;
102	int type;
103	unsigned int fields;
104
105	/* NULL nvl is of course invalid. */
106	if (nvl == NULL)
107		return (EINVAL);
108	if (nvlist_error(nvl) != 0)
109		return (nvlist_error(nvl));
110
111#define	HAS_NAME	0x01
112#define	HAS_OPERATION	0x02
113
114	fields = 0;
115	cookie = NULL;
116	while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) {
117		/* We accept only one 'name' and one 'operation' in nvl. */
118		if (strcmp(name, "name") == 0) {
119			if (type != NV_TYPE_STRING)
120				return (EINVAL);
121			/* Only one 'name' can be present. */
122			if ((fields & HAS_NAME) != 0)
123				return (EINVAL);
124			fields |= HAS_NAME;
125		} else if (strcmp(name, "operation") == 0) {
126			uint64_t operation;
127
128			if (type != NV_TYPE_NUMBER)
129				return (EINVAL);
130			/*
131			 * We accept only CAP_SYSCTL_READ and
132			 * CAP_SYSCTL_WRITE flags.
133			 */
134			operation = nvlist_get_number(nvl, name);
135			if ((operation & ~(CAP_SYSCTL_RDWR)) != 0)
136				return (EINVAL);
137			/* ...but there has to be at least one of them. */
138			if ((operation & (CAP_SYSCTL_RDWR)) == 0)
139				return (EINVAL);
140			/* Only one 'operation' can be present. */
141			if ((fields & HAS_OPERATION) != 0)
142				return (EINVAL);
143			fields |= HAS_OPERATION;
144		} else if (islimit) {
145			/* If this is limit, there can be no other fields. */
146			return (EINVAL);
147		}
148	}
149
150	/* Both fields has to be there. */
151	if (fields != (HAS_NAME | HAS_OPERATION))
152		return (EINVAL);
153
154#undef	HAS_OPERATION
155#undef	HAS_NAME
156
157	return (0);
158}
159
160static bool
161sysctl_allowed(const nvlist_t *limits, const char *chname, uint64_t choperation)
162{
163	uint64_t operation;
164	const char *name;
165	void *cookie;
166	int type;
167
168	if (limits == NULL)
169		return (true);
170
171	cookie = NULL;
172	while ((name = nvlist_next(limits, &type, &cookie)) != NULL) {
173		assert(type == NV_TYPE_NUMBER);
174
175		operation = nvlist_get_number(limits, name);
176		if ((operation & choperation) != choperation)
177			continue;
178
179		if ((operation & CAP_SYSCTL_RECURSIVE) == 0) {
180			if (strcmp(name, chname) != 0)
181				continue;
182		} else {
183			size_t namelen;
184
185			namelen = strlen(name);
186			if (strncmp(name, chname, namelen) != 0)
187				continue;
188			if (chname[namelen] != '.' && chname[namelen] != '\0')
189				continue;
190		}
191
192		return (true);
193	}
194
195	return (false);
196}
197
198static int
199sysctl_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits)
200{
201	const char *name;
202	void *cookie;
203	uint64_t operation;
204	int type;
205
206	cookie = NULL;
207	while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) {
208		if (type != NV_TYPE_NUMBER)
209			return (EINVAL);
210		operation = nvlist_get_number(newlimits, name);
211		if ((operation & ~(CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) != 0)
212			return (EINVAL);
213		if ((operation & (CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE)) == 0)
214			return (EINVAL);
215		if (!sysctl_allowed(oldlimits, name, operation))
216			return (ENOTCAPABLE);
217	}
218
219	return (0);
220}
221
222static int
223sysctl_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin,
224    nvlist_t *nvlout)
225{
226	const char *name;
227	const void *newp;
228	void *oldp;
229	uint64_t operation;
230	size_t oldlen, newlen;
231	size_t *oldlenp;
232	int error;
233
234	if (strcmp(cmd, "sysctl") != 0)
235		return (EINVAL);
236	error = sysctl_check_one(nvlin, false);
237	if (error != 0)
238		return (error);
239
240	name = nvlist_get_string(nvlin, "name");
241	operation = nvlist_get_number(nvlin, "operation");
242	if (!sysctl_allowed(limits, name, operation))
243		return (ENOTCAPABLE);
244
245	if ((operation & CAP_SYSCTL_WRITE) != 0) {
246		if (!nvlist_exists_binary(nvlin, "newp"))
247			return (EINVAL);
248		newp = nvlist_get_binary(nvlin, "newp", &newlen);
249		assert(newp != NULL && newlen > 0);
250	} else {
251		newp = NULL;
252		newlen = 0;
253	}
254
255	if ((operation & CAP_SYSCTL_READ) != 0) {
256		if (nvlist_exists_null(nvlin, "justsize")) {
257			oldp = NULL;
258			oldlen = 0;
259			oldlenp = &oldlen;
260		} else {
261			if (!nvlist_exists_number(nvlin, "oldlen"))
262				return (EINVAL);
263			oldlen = (size_t)nvlist_get_number(nvlin, "oldlen");
264			if (oldlen == 0)
265				return (EINVAL);
266			oldp = calloc(1, oldlen);
267			if (oldp == NULL)
268				return (ENOMEM);
269			oldlenp = &oldlen;
270		}
271	} else {
272		oldp = NULL;
273		oldlen = 0;
274		oldlenp = NULL;
275	}
276
277	if (sysctlbyname(name, oldp, oldlenp, newp, newlen) == -1) {
278		error = errno;
279		free(oldp);
280		return (error);
281	}
282
283	if ((operation & CAP_SYSCTL_READ) != 0) {
284		if (nvlist_exists_null(nvlin, "justsize"))
285			nvlist_add_number(nvlout, "oldlen", (uint64_t)oldlen);
286		else
287			nvlist_move_binary(nvlout, "oldp", oldp, oldlen);
288	}
289
290	return (0);
291}
292
293CREATE_SERVICE("system.sysctl", sysctl_limit, sysctl_command, 0);
294