xref: /freebsd/usr.sbin/sysconf/sysconf_scan.c (revision 3fe5961a0b708da599d42cbb6b5e4f030c28ea45)
1 /*
2  * Copyright (c) 2013-2026 Devin Teske <dteske@FreeBSD.org>
3  * Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org>
4  *
5  * SPDX-License-Identifier: BSD-2-Clause
6  */
7 
8 /*
9  * Ordered conf-file scanning into effective values, trails, and dumps.
10  */
11 
12 #include <ctype.h>
13 
14 #include "sysconf_priv.h"
15 
16 /*
17  * Apply a make(1)-style assignment operator to the running effective value
18  * of a directive. `+=' appends with a single space (as make(1) does);
19  * `?=' keeps the existing value when already defined; `=', `:=', and `!='
20  * replace. Returns newly allocated storage, or NULL on allocation failure.
21  * `old' may be NULL when the directive has not been seen yet.
22  */
23 char *
make_apply(enum bsdconf_op op,const char * old,const char * piece)24 make_apply(enum bsdconf_op op, const char *old, const char *piece)
25 {
26 	size_t len;
27 	char *out;
28 
29 	if (piece == NULL)
30 		piece = "";
31 
32 	switch (op) {
33 	case BSDCONF_OP_APPEND:
34 		if (old == NULL || *old == '\0')
35 			return (strdup(piece));
36 		if (*piece == '\0')
37 			return (strdup(old));
38 		len = strlen(old) + 1 + strlen(piece) + 1;
39 		if ((out = malloc(len)) == NULL)
40 			return (NULL);
41 		snprintf(out, len, "%s %s", old, piece);
42 		return (out);
43 	case BSDCONF_OP_COND:
44 		if (old != NULL)
45 			return (strdup(old));
46 		return (strdup(piece));
47 	case BSDCONF_OP_EXPAND:
48 	case BSDCONF_OP_SHELL:
49 	case BSDCONF_OP_ASSIGN:
50 	case BSDCONF_OP_DEFAULT:
51 	default:
52 		return (strdup(piece));
53 	}
54 }
55 
56 /*
57  * Record `directive'/`value' in the dump union (for `-a'). For formats
58  * without make(1) operators a later assignment overrides an earlier one
59  * while retaining its original position. With BSDCONF_OPERATOR_EQUALS the
60  * operator is honored: `+=' accumulates, `?=' keeps a prior definition,
61  * and `=' / `:=' / `!=' replace -- matching make(1)'s effective value.
62  * Returns zero on success; -1 (errno set) on allocation failure.
63  */
64 static int
dump_record(const char * directive,const char * value,enum bsdconf_op op,uint32_t line)65 dump_record(const char *directive, const char *value, enum bsdconf_op op,
66     uint32_t line)
67 {
68 	char *merged;
69 	char *vcopy;
70 	size_t n;
71 	struct dumpent *tmp;
72 
73 	if ((vcopy = strdup(value)) == NULL)
74 		return (-1);
75 	bsdconf_unquote(vcopy);
76 
77 	for (n = 0; n < ndumps; n++) {
78 		if (strcmp(dumps[n].name, directive) != 0)
79 			continue;
80 		if ((bsdconf_format_processing(format) &
81 		    BSDCONF_OPERATOR_EQUALS) != 0) {
82 			merged = make_apply(op, dumps[n].value, vcopy);
83 			if (merged == NULL) {
84 				free(vcopy);
85 				return (-1);
86 			}
87 			free(dumps[n].value);
88 			dumps[n].value = merged;
89 		} else {
90 			free(dumps[n].value);
91 			dumps[n].value = vcopy;
92 			vcopy = NULL; /* owned by dumps[n].value */
93 		}
94 		dumps[n].srcidx = (int)conf_scanidx;
95 		dumps[n].line = line;
96 		if (trail_push(&dumps[n].trail, &dumps[n].ntrail,
97 		    conf_scanidx, line, op,
98 		    vcopy != NULL ? vcopy : dumps[n].value,
99 		    dumps[n].value) != 0) {
100 			free(vcopy);
101 			return (-1);
102 		}
103 		free(vcopy);
104 		return (0);
105 	}
106 
107 	if (ndumps >= dumpsize) {
108 		dumpsize = (dumpsize == 0) ? 64 : dumpsize << 1;
109 		tmp = realloc(dumps, dumpsize * sizeof(*dumps));
110 		if (tmp == NULL) {
111 			free(vcopy);
112 			return (-1);
113 		}
114 		dumps = tmp;
115 	}
116 	if ((dumps[ndumps].name = strdup(directive)) == NULL) {
117 		free(vcopy);
118 		return (-1);
119 	}
120 	dumps[ndumps].value = vcopy;
121 	dumps[ndumps].srcidx = (int)conf_scanidx;
122 	dumps[ndumps].line = line;
123 	dumps[ndumps].trail = NULL;
124 	dumps[ndumps].ntrail = 0;
125 	if (trail_push(&dumps[ndumps].trail, &dumps[ndumps].ntrail,
126 	    conf_scanidx, line, op, vcopy, vcopy) != 0)
127 		return (-1);
128 	ndumps++;
129 	return (0);
130 }
131 
132 /*
133  * bsdconf_fparse() call-back; record each statement against the requests
134  * (and, with `-a', the dump union). For loader/sysctl/generic a later
135  * assignment overrides an earlier one -- both within a file and across the
136  * ordered file list. For make(1)-syntax targets the statement's operator
137  * (passed via option->op) drives accumulation so `foo=1' followed by
138  * `foo+=2' yields the effective value `1 2', as make -V reports.
139  */
140 static int
scan_cb(struct bsdconf_option * option,uint32_t line,char * directive,char * value)141 scan_cb(struct bsdconf_option *option, uint32_t line,
142     char *directive, char *value)
143 {
144 	char *copy;
145 	char *merged;
146 	enum bsdconf_op file_op;
147 	unsigned int n;
148 
149 	file_op = (option != NULL) ? option->op : BSDCONF_OP_ASSIGN;
150 	if (file_op == BSDCONF_OP_DEFAULT)
151 		file_op = BSDCONF_OP_ASSIGN;
152 
153 	for (n = 0; n < nreqs; n++) {
154 		if (strcmp(reqs[n].name, directive) != 0)
155 			continue;
156 		/*
157 		 * The defaults file is read-only: it informs the effective
158 		 * value seen by reads, checks, and list edits, but can
159 		 * never satisfy a removal (nothing there may be removed).
160 		 */
161 		if (reqs[n].remove && (int)conf_scanidx == defaults_idx)
162 			continue;
163 		if ((copy = strdup(value)) == NULL)
164 			return (-1);
165 		bsdconf_unquote(copy);
166 
167 		/*
168 		 * An identical make(1) `+=value' line already in the file
169 		 * satisfies a pending `name+=value' write or check.
170 		 */
171 		if (reqs[n].op == BSDCONF_OP_APPEND &&
172 		    reqs[n].newvalue != NULL &&
173 		    file_op == BSDCONF_OP_APPEND &&
174 		    strcmp(copy, reqs[n].newvalue) == 0)
175 			reqs[n].append_present = 1;
176 
177 		if ((bsdconf_format_processing(format) &
178 		    BSDCONF_OPERATOR_EQUALS) != 0) {
179 			if (make_strike_p(&reqs[n]) &&
180 			    make_assign_push(&reqs[n], file_op, copy,
181 			    line) != 0) {
182 				free(copy);
183 				return (-1);
184 			}
185 			merged = make_apply(file_op, reqs[n].value, copy);
186 			if (merged == NULL) {
187 				free(copy);
188 				return (-1);
189 			}
190 			free(reqs[n].value);
191 			reqs[n].value = merged;
192 			if (trail_push(&reqs[n].trail, &reqs[n].ntrail,
193 			    conf_scanidx, line, file_op, copy,
194 			    reqs[n].value) != 0) {
195 				free(copy);
196 				return (-1);
197 			}
198 			free(copy);
199 		} else {
200 			free(reqs[n].value);
201 			reqs[n].value = copy;
202 			if (trail_push(&reqs[n].trail, &reqs[n].ntrail,
203 			    conf_scanidx, line, file_op, copy, copy) != 0)
204 				return (-1);
205 		}
206 		reqs[n].found = 1;
207 		reqs[n].srcidx = (int)conf_scanidx;
208 		reqs[n].line = line;
209 		reqs[n].infile[conf_scanidx] = 1;
210 	}
211 
212 	if (dump_all && dump_record(directive, value, file_op, line) != 0)
213 		return (-1);
214 
215 	return (0);
216 }
217 
218 /*
219  * Parse every existing backing file in deterministic order, feeding each
220  * statement through scan_cb(). With `sandbox' set (pure read operations),
221  * all descriptors are opened up front and the remainder of the pass runs
222  * inside a Capsicum sandbox on FreeBSD. Returns EXIT_SUCCESS or exits on
223  * hard errors.
224  */
225 int
scan_pass(int sandbox)226 scan_pass(int sandbox)
227 {
228 	int *fds;
229 	size_t n;
230 	uint16_t processing;
231 
232 	if ((fds = calloc(nconf_files, sizeof(*fds))) == NULL)
233 		err(EXIT_FAILURE, NULL);
234 
235 	for (n = 0; n < nconf_files; n++) {
236 		fds[n] = open(conf_files[n], O_RDONLY);
237 		if (fds[n] < 0 && errno != ENOENT)
238 			err(EXIT_FAILURE, "%s", conf_files[n]);
239 
240 		/*
241 		 * Spool input that cannot seek (a fifo or /dev/stdin named
242 		 * by `-f') before the sandbox slams shut: the library would
243 		 * otherwise spool lazily inside bsdconf_fparse(), where
244 		 * creating the temporary is no longer permitted.
245 		 */
246 		if (fds[n] >= 0 && lseek(fds[n], 0, SEEK_CUR) == -1) {
247 			int sfd;
248 
249 			if (errno != ESPIPE ||
250 			    (sfd = bsdconf_spool(fds[n])) == -1)
251 				err(EXIT_FAILURE, "%s", conf_files[n]);
252 			close(fds[n]);
253 			fds[n] = sfd;
254 		}
255 	}
256 
257 #ifdef __FreeBSD__
258 	if (sandbox) {
259 		cap_rights_t rights;
260 
261 		if (caph_limit_stdio() < 0)
262 			err(EXIT_FAILURE, "capsicum");
263 		cap_rights_init(&rights, CAP_READ, CAP_FSTAT, CAP_SEEK);
264 		for (n = 0; n < nconf_files; n++) {
265 			if (fds[n] >= 0 &&
266 			    caph_rights_limit(fds[n], &rights) < 0)
267 				err(EXIT_FAILURE, "capsicum");
268 		}
269 		if (caph_enter() < 0)
270 			err(EXIT_FAILURE, "capsicum");
271 	}
272 #else
273 	(void)sandbox;
274 #endif
275 
276 	processing = bsdconf_format_processing(format);
277 	for (n = 0; n < nconf_files; n++) {
278 		if (fds[n] < 0)
279 			continue;
280 		conf_scanidx = n;
281 		if (bsdconf_fparse(NULL, fds[n], scan_cb, processing) != 0)
282 			err(EXIT_FAILURE, "%s", conf_files[n]);
283 		close(fds[n]);
284 	}
285 
286 	free(fds);
287 	return (EXIT_SUCCESS);
288 }
289