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 * 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 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 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 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 #ifdef __FreeBSD__ 242 if (sandbox) { 243 cap_rights_t rights; 244 245 if (caph_limit_stdio() < 0) 246 err(EXIT_FAILURE, "capsicum"); 247 cap_rights_init(&rights, CAP_READ, CAP_FSTAT, CAP_SEEK); 248 for (n = 0; n < nconf_files; n++) { 249 if (fds[n] >= 0 && 250 caph_rights_limit(fds[n], &rights) < 0) 251 err(EXIT_FAILURE, "capsicum"); 252 } 253 if (caph_enter() < 0) 254 err(EXIT_FAILURE, "capsicum"); 255 } 256 #else 257 (void)sandbox; 258 #endif 259 260 processing = bsdconf_format_processing(format); 261 for (n = 0; n < nconf_files; n++) { 262 if (fds[n] < 0) 263 continue; 264 conf_scanidx = n; 265 if (bsdconf_fparse(NULL, fds[n], scan_cb, processing) != 0) 266 err(EXIT_FAILURE, "%s", conf_files[n]); 267 close(fds[n]); 268 } 269 270 free(fds); 271 return (EXIT_SUCCESS); 272 } 273