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