1 /* 2 * Copyright (c) 2002-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 #include <errno.h> 9 #include <fcntl.h> 10 #include <fnmatch.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <unistd.h> 15 16 #include "bsdconf.h" 17 #include "bsdconf_internal.h" 18 19 /* 20 * Search for a config option (struct bsdconf_option) in the array of config 21 * options, returning the struct whose directive matches the given parameter. 22 * If no match is found, NULL is returned. 23 * 24 * This is to eliminate dependency on the index position of an item in the 25 * array, since the index position is more apt to be changed as code grows. 26 */ 27 struct bsdconf_option * 28 bsdconf_get_option(struct bsdconf_option options[], const char *directive) 29 { 30 uint32_t n; 31 32 if (options == NULL || directive == NULL) 33 return (NULL); 34 35 for (n = 0; options[n].directive != NULL; n++) 36 if (strcmp(options[n].directive, directive) == 0) 37 return (&options[n]); 38 39 return (NULL); 40 } 41 42 /* 43 * Strip one layer of surrounding double-quotes from `value' in place (the 44 * parser preserves quotes so that values round-trip losslessly; see 45 * bsdconf_fparse() below). Returns `value' for convenience. If the value is 46 * not a quoted string, it is returned unmodified. 47 */ 48 char * 49 bsdconf_unquote(char *value) 50 { 51 size_t len; 52 53 if (value == NULL || (len = strlen(value)) < 2) 54 return (value); 55 if (value[0] != '"' || value[len - 1] != '"') 56 return (value); 57 58 memmove(value, value + 1, len - 2); 59 value[len - 2] = '\0'; 60 61 return (value); 62 } 63 64 /* 65 * Copy the remaining contents of the open file descriptor `fd' to an 66 * unlinked temporary file and return a seekable descriptor referencing it 67 * (which the caller must close(2); the backing storage is reclaimed then). 68 * Exported for callers that need a seekable snapshot of a pipe or socket. 69 * Returns the new descriptor on success; otherwise returns -1 and errno 70 * should be consulted. 71 */ 72 int 73 bsdconf_spool(int fd) 74 { 75 FILE *tmp; 76 int error; 77 int newfd; 78 int tfd; 79 ssize_t r; 80 char buf[8192]; 81 82 if ((tmp = tmpfile()) == NULL) 83 return (-1); 84 tfd = fileno(tmp); 85 86 for (;;) { 87 r = read(fd, buf, sizeof(buf)); 88 if (r < 0) { 89 if (errno == EINTR) 90 continue; 91 goto fail; 92 } 93 if (r == 0) 94 break; 95 if (bsdconf_writeall(tfd, buf, (size_t)r) != 0) 96 goto fail; 97 } 98 if (lseek(tfd, 0, SEEK_SET) == -1) 99 goto fail; 100 101 /* Detach the descriptor from the stream before closing it */ 102 if ((newfd = dup(tfd)) == -1) 103 goto fail; 104 fclose(tmp); 105 return (newfd); 106 107 fail: 108 error = errno; /* preserve errno across fclose(3) */ 109 fclose(tmp); 110 errno = error; 111 return (-1); 112 } 113 114 /* 115 * Invoke the unknown-directive call-back with a stack-local option that 116 * carries the statement's assignment operator (there is no matched 117 * options[] slot to hang it on). Returns the call-back's result. 118 */ 119 static int 120 bsdconf_call_unknown(int (*unknown)(struct bsdconf_option *option, 121 uint32_t line, char *directive, char *value), enum bsdconf_op op, 122 uint32_t dline, char *directive, char *value) 123 { 124 struct bsdconf_option unk; 125 126 memset(&unk, 0, sizeof(unk)); 127 unk.op = op; 128 return (unknown(&unk, dline, directive, value)); 129 } 130 131 /* 132 * Parse the configuration data on the open file descriptor `fd' and execute 133 * the `parse' call-back functions for any directives defined by the array of 134 * config options (first argument). 135 * 136 * For unknown directives that are encountered, you can optionally pass a 137 * call-back function for the third argument to be called for unknowns. 138 * 139 * The descriptor is read into a bounded in-memory buffer (see 140 * bsdconf_slurp()) and then scanned as an array of characters with 141 * bsdconf_scan(), the same tokenizer used by bsdconf_put(). The descriptor 142 * need not be seekable; a pipe or socket is read to EOF subject to the 143 * BSDCONF_MAX_BYTES cap. The descriptor is left positioned at end-of-file 144 * (non-seekable input is left drained) and remains open (the caller retains 145 * ownership). 146 * 147 * Returns zero on success; otherwise returns -1 (or the non-zero result of a 148 * call-back) and errno should be consulted. 149 */ 150 int 151 bsdconf_fparse(struct bsdconf_option options[], int fd, 152 int (*unknown)(struct bsdconf_option *option, uint32_t line, 153 char *directive, char *value), uint16_t processing_options) 154 { 155 bool bequals; 156 bool bsemicolon; 157 bool case_sensitive; 158 bool found; 159 bool operator_equals; 160 bool require_equals; 161 bool strict_equals; 162 char *buf = NULL; 163 char *directive = NULL; 164 char *t; 165 char *value = NULL; 166 enum bsdconf_op op; 167 int error; 168 int rv = 0; 169 int sverrno; 170 size_t buflen = 0; 171 size_t dsize = 0; 172 size_t i = 0; 173 size_t n; 174 size_t vsize = 0; 175 struct bsdconf_stmt st; 176 uint32_t line = 1; 177 unsigned int x; 178 179 /* Sanity check: if no options and no unknown function, return */ 180 if (options == NULL && unknown == NULL) { 181 errno = EINVAL; 182 return (-1); 183 } 184 185 if ((buf = bsdconf_slurp(fd, &buflen)) == NULL) 186 return (-1); 187 188 /* Processing options */ 189 bequals = processing_options & BSDCONF_BREAK_ON_EQUALS; 190 bsemicolon = processing_options & BSDCONF_BREAK_ON_SEMICOLON; 191 case_sensitive = processing_options & BSDCONF_CASE_SENSITIVE; 192 operator_equals = processing_options & BSDCONF_OPERATOR_EQUALS; 193 require_equals = processing_options & BSDCONF_REQUIRE_EQUALS; 194 strict_equals = processing_options & BSDCONF_STRICT_EQUALS; 195 196 while (bsdconf_scan(buf, buflen, &i, &line, bequals, bsemicolon, 197 strict_equals, operator_equals, &st)) { 198 n = st.dir_end - st.dir_start; 199 if (directive == NULL || n > dsize) { 200 if ((t = realloc(directive, n + 1)) == NULL) 201 goto fail; 202 directive = t; 203 dsize = n; 204 } 205 memcpy(directive, buf + st.dir_start, n); 206 directive[n] = '\0'; 207 208 op = st.op; 209 if (!case_sensitive) 210 bsdconf_strtolower(directive); 211 212 if (!st.have_value) { 213 if (value == NULL && (value = malloc(1)) == NULL) 214 goto fail; 215 value[0] = '\0'; 216 goto call_function; 217 } 218 219 n = st.val_end - st.val_start; 220 if (value == NULL || n > vsize) { 221 if ((t = realloc(value, n + 1)) == NULL) 222 goto fail; 223 value = t; 224 vsize = n; 225 } 226 memcpy(value, buf + st.val_start, n); 227 value[n] = '\0'; 228 229 /* Escape the escaped quotes (see bsdconf_string.c) */ 230 x = bsdconf_strcount(value, "\\\""); 231 if (x != 0 && (n + x) > vsize) { 232 if ((t = realloc(value, n + x + 1)) == NULL) 233 goto fail; 234 value = t; 235 vsize = n + x; 236 } 237 if (bsdconf_replaceall(value, vsize + 1, "\\\"", "\\\\\"") < 0) 238 goto fail; 239 240 /* Remove all escaped newline characters */ 241 if (bsdconf_replaceall(value, vsize + 1, "\\\n", "") < 0) 242 goto fail; 243 244 /* Resolve escape sequences */ 245 bsdconf_strunexpand(value, value); 246 247 call_function: 248 /* Abort if we're seeking only assignments */ 249 if (require_equals && !st.have_equals) { 250 errno = EINVAL; 251 goto fail; 252 } 253 254 found = 0; 255 256 /* 257 * Report the statement's assignment operator through a 258 * stack-local option when invoking the unknown call-back 259 * (there is no matched options[] slot to hang it on). 260 */ 261 if (options == NULL && unknown != NULL) { 262 error = bsdconf_call_unknown(unknown, op, st.line, 263 directive, value); 264 if (error != 0) { 265 rv = error; 266 goto cleanup; 267 } 268 continue; 269 } 270 271 /* Loop through the array looking for a match */ 272 for (n = 0; options[n].directive != NULL; n++) { 273 error = fnmatch(options[n].directive, directive, 274 FNM_NOESCAPE); 275 if (error == 0) { 276 found = 1; 277 /* Call function for array index item */ 278 options[n].op = op; 279 if (options[n].parse != NULL) { 280 error = options[n].parse(&options[n], 281 st.line, directive, value); 282 if (error != 0) { 283 rv = error; 284 goto cleanup; 285 } 286 } 287 } else if (error != FNM_NOMATCH) { 288 /* An error has occurred */ 289 errno = EINVAL; 290 goto fail; 291 } 292 } 293 if (!found && unknown != NULL) { 294 /* 295 * No match was found for the value we read from the 296 * file; call function designated for unknown values. 297 */ 298 error = bsdconf_call_unknown(unknown, op, st.line, 299 directive, value); 300 if (error != 0) { 301 rv = error; 302 goto cleanup; 303 } 304 } 305 } 306 307 goto cleanup; 308 309 fail: 310 rv = -1; 311 312 cleanup: 313 sverrno = errno; /* preserve errno across free(3) */ 314 free(buf); 315 free(directive); 316 free(value); 317 errno = sverrno; 318 319 return (rv); 320 } 321 322 /* 323 * Parse the configuration file at `path' and execute the `parse' call-back 324 * functions for any directives defined by the array of config options (first 325 * argument). This is a convenience wrapper around bsdconf_fparse() above. 326 * 327 * Returns zero on success; otherwise returns -1 (or the non-zero result of a 328 * call-back) and errno should be consulted. 329 */ 330 int 331 bsdconf_parse(struct bsdconf_option options[], const char *path, 332 int (*unknown)(struct bsdconf_option *option, uint32_t line, 333 char *directive, char *value), uint16_t processing_options) 334 { 335 int error; 336 int fd; 337 338 /* Sanity check: if no options and no unknown function, return */ 339 if (path == NULL || (options == NULL && unknown == NULL)) { 340 errno = EINVAL; 341 return (-1); 342 } 343 344 /* Open the file */ 345 if ((fd = open(path, O_RDONLY)) < 0) 346 return (-1); 347 348 error = bsdconf_fparse(options, fd, unknown, processing_options); 349 350 close(fd); 351 return (error); 352 } 353