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