1274116Sdteske/*- 2293619Sdteske * Copyright (c) 2002-2015 Devin Teske <dteske@FreeBSD.org> 3274116Sdteske * All rights reserved. 4274116Sdteske * 5274116Sdteske * Redistribution and use in source and binary forms, with or without 6274116Sdteske * modification, are permitted provided that the following conditions 7274116Sdteske * are met: 8274116Sdteske * 1. Redistributions of source code must retain the above copyright 9274116Sdteske * notice, this list of conditions and the following disclaimer. 10274116Sdteske * 2. Redistributions in binary form must reproduce the above copyright 11274116Sdteske * notice, this list of conditions and the following disclaimer in the 12274116Sdteske * documentation and/or other materials provided with the distribution. 13274116Sdteske * 14274116Sdteske * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15274116Sdteske * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16274116Sdteske * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17274116Sdteske * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18274116Sdteske * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19274116Sdteske * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20274116Sdteske * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21274116Sdteske * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22274116Sdteske * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23274116Sdteske * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24274116Sdteske * SUCH DAMAGE. 25274116Sdteske */ 26274116Sdteske 27274116Sdteske#include <sys/cdefs.h> 28274116Sdteske__FBSDID("$FreeBSD: releng/10.3/lib/libfigpar/figpar.c 293619 2016-01-09 23:33:44Z dteske $"); 29274116Sdteske 30274116Sdteske#include <sys/param.h> 31274116Sdteske 32274116Sdteske#include <ctype.h> 33274116Sdteske#include <errno.h> 34274116Sdteske#include <fcntl.h> 35274116Sdteske#include <fnmatch.h> 36274116Sdteske#include <stdlib.h> 37274116Sdteske#include <string.h> 38274116Sdteske#include <unistd.h> 39274116Sdteske 40274116Sdteske#include "figpar.h" 41274116Sdteske#include "string_m.h" 42274116Sdteske 43293619Sdteskestruct figpar_config figpar_dummy_config = {0, NULL, {0}, NULL}; 44274116Sdteske 45274116Sdteske/* 46293619Sdteske * Search for config option (struct figpar_config) in the array of config 47293619Sdteske * options, returning the struct whose directive matches the given parameter. 48293619Sdteske * If no match is found, a pointer to the static dummy array (above) is 49293619Sdteske * returned. 50274116Sdteske * 51274116Sdteske * This is to eliminate dependency on the index position of an item in the 52274116Sdteske * array, since the index position is more apt to be changed as code grows. 53274116Sdteske */ 54293619Sdteskestruct figpar_config * 55293619Sdteskeget_config_option(struct figpar_config options[], const char *directive) 56274116Sdteske{ 57274116Sdteske uint32_t n; 58274116Sdteske 59274116Sdteske /* Check arguments */ 60274116Sdteske if (options == NULL || directive == NULL) 61293619Sdteske return (&figpar_dummy_config); 62274116Sdteske 63274116Sdteske /* Loop through the array, return the index of the first match */ 64274116Sdteske for (n = 0; options[n].directive != NULL; n++) 65274116Sdteske if (strcmp(options[n].directive, directive) == 0) 66274116Sdteske return (&(options[n])); 67274116Sdteske 68274116Sdteske /* Re-initialize the dummy variable in case it was written to */ 69293619Sdteske figpar_dummy_config.directive = NULL; 70293619Sdteske figpar_dummy_config.type = 0; 71293619Sdteske figpar_dummy_config.action = NULL; 72293619Sdteske figpar_dummy_config.value.u_num = 0; 73274116Sdteske 74293619Sdteske return (&figpar_dummy_config); 75274116Sdteske} 76274116Sdteske 77274116Sdteske/* 78274116Sdteske * Parse the configuration file at `path' and execute the `action' call-back 79274116Sdteske * functions for any directives defined by the array of config options (first 80274116Sdteske * argument). 81274116Sdteske * 82274116Sdteske * For unknown directives that are encountered, you can optionally pass a 83274116Sdteske * call-back function for the third argument to be called for unknowns. 84274116Sdteske * 85274116Sdteske * Returns zero on success; otherwise returns -1 and errno should be consulted. 86274116Sdteske*/ 87274116Sdteskeint 88293619Sdteskeparse_config(struct figpar_config options[], const char *path, 89293619Sdteske int (*unknown)(struct figpar_config *option, uint32_t line, 90293619Sdteske char *directive, char *value), uint16_t processing_options) 91274116Sdteske{ 92274116Sdteske uint8_t bequals; 93274116Sdteske uint8_t bsemicolon; 94274116Sdteske uint8_t case_sensitive; 95274116Sdteske uint8_t comment = 0; 96274116Sdteske uint8_t end; 97274116Sdteske uint8_t found; 98274116Sdteske uint8_t have_equals = 0; 99274116Sdteske uint8_t quote; 100274116Sdteske uint8_t require_equals; 101274116Sdteske uint8_t strict_equals; 102274116Sdteske char p[2]; 103274116Sdteske char *directive; 104274116Sdteske char *t; 105274116Sdteske char *value; 106274116Sdteske int error; 107274116Sdteske int fd; 108274116Sdteske ssize_t r = 1; 109274116Sdteske uint32_t dsize; 110274116Sdteske uint32_t line = 1; 111274116Sdteske uint32_t n; 112274116Sdteske uint32_t vsize; 113274116Sdteske uint32_t x; 114274116Sdteske off_t charpos; 115274116Sdteske off_t curpos; 116274116Sdteske char rpath[PATH_MAX]; 117274116Sdteske 118274116Sdteske /* Sanity check: if no options and no unknown function, return */ 119274116Sdteske if (options == NULL && unknown == NULL) 120274116Sdteske return (-1); 121274116Sdteske 122274116Sdteske /* Processing options */ 123293619Sdteske bequals = (processing_options & FIGPAR_BREAK_ON_EQUALS) == 0 ? 0 : 1; 124293619Sdteske bsemicolon = 125293619Sdteske (processing_options & FIGPAR_BREAK_ON_SEMICOLON) == 0 ? 0 : 1; 126293619Sdteske case_sensitive = 127293619Sdteske (processing_options & FIGPAR_CASE_SENSITIVE) == 0 ? 0 : 1; 128293619Sdteske require_equals = 129293619Sdteske (processing_options & FIGPAR_REQUIRE_EQUALS) == 0 ? 0 : 1; 130293619Sdteske strict_equals = 131293619Sdteske (processing_options & FIGPAR_STRICT_EQUALS) == 0 ? 0 : 1; 132274116Sdteske 133274116Sdteske /* Initialize strings */ 134274116Sdteske directive = value = 0; 135274116Sdteske vsize = dsize = 0; 136274116Sdteske 137274116Sdteske /* Resolve the file path */ 138274116Sdteske if (realpath(path, rpath) == 0) 139274116Sdteske return (-1); 140274116Sdteske 141274116Sdteske /* Open the file */ 142274116Sdteske if ((fd = open(rpath, O_RDONLY)) < 0) 143274116Sdteske return (-1); 144274116Sdteske 145274116Sdteske /* Read the file until EOF */ 146274116Sdteske while (r != 0) { 147274116Sdteske r = read(fd, p, 1); 148274116Sdteske 149274116Sdteske /* skip to the beginning of a directive */ 150274116Sdteske while (r != 0 && (isspace(*p) || *p == '#' || comment || 151274116Sdteske (bsemicolon && *p == ';'))) { 152274116Sdteske if (*p == '#') 153274116Sdteske comment = 1; 154274116Sdteske else if (*p == '\n') { 155274116Sdteske comment = 0; 156274116Sdteske line++; 157274116Sdteske } 158274116Sdteske r = read(fd, p, 1); 159274116Sdteske } 160274116Sdteske /* Test for EOF; if EOF then no directive was found */ 161274116Sdteske if (r == 0) { 162274116Sdteske close(fd); 163274116Sdteske return (0); 164274116Sdteske } 165274116Sdteske 166274116Sdteske /* Get the current offset */ 167274116Sdteske curpos = lseek(fd, 0, SEEK_CUR) - 1; 168274116Sdteske if (curpos == -1) { 169274116Sdteske close(fd); 170274116Sdteske return (-1); 171274116Sdteske } 172274116Sdteske 173274116Sdteske /* Find the length of the directive */ 174274116Sdteske for (n = 0; r != 0; n++) { 175274116Sdteske if (isspace(*p)) 176274116Sdteske break; 177274116Sdteske if (bequals && *p == '=') { 178274116Sdteske have_equals = 1; 179274116Sdteske break; 180274116Sdteske } 181274116Sdteske if (bsemicolon && *p == ';') 182274116Sdteske break; 183274116Sdteske r = read(fd, p, 1); 184274116Sdteske } 185274116Sdteske 186274116Sdteske /* Test for EOF, if EOF then no directive was found */ 187274116Sdteske if (n == 0 && r == 0) { 188274116Sdteske close(fd); 189274116Sdteske return (0); 190274116Sdteske } 191274116Sdteske 192274116Sdteske /* Go back to the beginning of the directive */ 193274116Sdteske error = (int)lseek(fd, curpos, SEEK_SET); 194274116Sdteske if (error == (curpos - 1)) { 195274116Sdteske close(fd); 196274116Sdteske return (-1); 197274116Sdteske } 198274116Sdteske 199274116Sdteske /* Allocate and read the directive into memory */ 200274116Sdteske if (n > dsize) { 201274116Sdteske if ((directive = realloc(directive, n + 1)) == NULL) { 202274116Sdteske close(fd); 203274116Sdteske return (-1); 204274116Sdteske } 205274116Sdteske dsize = n; 206274116Sdteske } 207274116Sdteske r = read(fd, directive, n); 208274116Sdteske 209274116Sdteske /* Advance beyond the equals sign if appropriate/desired */ 210274116Sdteske if (bequals && *p == '=') { 211274116Sdteske if (lseek(fd, 1, SEEK_CUR) != -1) 212274116Sdteske r = read(fd, p, 1); 213274116Sdteske if (strict_equals && isspace(*p)) 214274116Sdteske *p = '\n'; 215274116Sdteske } 216274116Sdteske 217274116Sdteske /* Terminate the string */ 218274116Sdteske directive[n] = '\0'; 219274116Sdteske 220274116Sdteske /* Convert directive to lower case before comparison */ 221274116Sdteske if (!case_sensitive) 222274116Sdteske strtolower(directive); 223274116Sdteske 224274116Sdteske /* Move to what may be the start of the value */ 225274116Sdteske if (!(bsemicolon && *p == ';') && 226274116Sdteske !(strict_equals && *p == '=')) { 227274116Sdteske while (r != 0 && isspace(*p) && *p != '\n') 228274116Sdteske r = read(fd, p, 1); 229274116Sdteske } 230274116Sdteske 231274116Sdteske /* An equals sign may have stopped us, should we eat it? */ 232274116Sdteske if (r != 0 && bequals && *p == '=' && !strict_equals) { 233274116Sdteske have_equals = 1; 234274116Sdteske r = read(fd, p, 1); 235274116Sdteske while (r != 0 && isspace(*p) && *p != '\n') 236274116Sdteske r = read(fd, p, 1); 237274116Sdteske } 238274116Sdteske 239274116Sdteske /* If no value, allocate a dummy value and jump to action */ 240274116Sdteske if (r == 0 || *p == '\n' || *p == '#' || 241274116Sdteske (bsemicolon && *p == ';')) { 242274116Sdteske /* Initialize the value if not already done */ 243274116Sdteske if (value == NULL && (value = malloc(1)) == NULL) { 244274116Sdteske close(fd); 245274116Sdteske return (-1); 246274116Sdteske } 247274116Sdteske value[0] = '\0'; 248274116Sdteske goto call_function; 249274116Sdteske } 250274116Sdteske 251274116Sdteske /* Get the current offset */ 252274116Sdteske curpos = lseek(fd, 0, SEEK_CUR) - 1; 253274116Sdteske if (curpos == -1) { 254274116Sdteske close(fd); 255274116Sdteske return (-1); 256274116Sdteske } 257274116Sdteske 258274116Sdteske /* Find the end of the value */ 259274116Sdteske quote = 0; 260274116Sdteske end = 0; 261274116Sdteske while (r != 0 && end == 0) { 262274116Sdteske /* Advance to the next character if we know we can */ 263274116Sdteske if (*p != '\"' && *p != '#' && *p != '\n' && 264274116Sdteske (!bsemicolon || *p != ';')) { 265274116Sdteske r = read(fd, p, 1); 266274116Sdteske continue; 267274116Sdteske } 268274116Sdteske 269274116Sdteske /* 270274116Sdteske * If we get this far, we've hit an end-key 271274116Sdteske */ 272274116Sdteske 273274116Sdteske /* Get the current offset */ 274274116Sdteske charpos = lseek(fd, 0, SEEK_CUR) - 1; 275274116Sdteske if (charpos == -1) { 276274116Sdteske close(fd); 277274116Sdteske return (-1); 278274116Sdteske } 279274116Sdteske 280274116Sdteske /* 281274116Sdteske * Go back so we can read the character before the key 282274116Sdteske * to check if the character is escaped (which means we 283274116Sdteske * should continue). 284274116Sdteske */ 285274116Sdteske error = (int)lseek(fd, -2, SEEK_CUR); 286274116Sdteske if (error == -3) { 287274116Sdteske close(fd); 288274116Sdteske return (-1); 289274116Sdteske } 290274116Sdteske r = read(fd, p, 1); 291274116Sdteske 292274116Sdteske /* 293274116Sdteske * Count how many backslashes there are (an odd number 294274116Sdteske * means the key is escaped, even means otherwise). 295274116Sdteske */ 296274116Sdteske for (n = 1; *p == '\\'; n++) { 297274116Sdteske /* Move back another offset to read */ 298274116Sdteske error = (int)lseek(fd, -2, SEEK_CUR); 299274116Sdteske if (error == -3) { 300274116Sdteske close(fd); 301274116Sdteske return (-1); 302274116Sdteske } 303274116Sdteske r = read(fd, p, 1); 304274116Sdteske } 305274116Sdteske 306274116Sdteske /* Move offset back to the key and read it */ 307274116Sdteske error = (int)lseek(fd, charpos, SEEK_SET); 308274116Sdteske if (error == (charpos - 1)) { 309274116Sdteske close(fd); 310274116Sdteske return (-1); 311274116Sdteske } 312274116Sdteske r = read(fd, p, 1); 313274116Sdteske 314274116Sdteske /* 315274116Sdteske * If an even number of backslashes was counted meaning 316274116Sdteske * key is not escaped, we should evaluate what to do. 317274116Sdteske */ 318274116Sdteske if ((n & 1) == 1) { 319274116Sdteske switch (*p) { 320274116Sdteske case '\"': 321274116Sdteske /* 322274116Sdteske * Flag current sequence of characters 323274116Sdteske * to follow as being quoted (hashes 324274116Sdteske * are not considered comments). 325274116Sdteske */ 326274116Sdteske quote = !quote; 327274116Sdteske break; 328274116Sdteske case '#': 329274116Sdteske /* 330274116Sdteske * If we aren't in a quoted series, we 331274116Sdteske * just hit an inline comment and have 332274116Sdteske * found the end of the value. 333274116Sdteske */ 334274116Sdteske if (!quote) 335274116Sdteske end = 1; 336274116Sdteske break; 337274116Sdteske case '\n': 338274116Sdteske /* 339274116Sdteske * Newline characters must always be 340274116Sdteske * escaped, whether inside a quoted 341274116Sdteske * series or not, otherwise they 342274116Sdteske * terminate the value. 343274116Sdteske */ 344274116Sdteske end = 1; 345274116Sdteske case ';': 346274116Sdteske if (!quote && bsemicolon) 347274116Sdteske end = 1; 348274116Sdteske break; 349274116Sdteske } 350274116Sdteske } else if (*p == '\n') 351274116Sdteske /* Escaped newline character. increment */ 352274116Sdteske line++; 353274116Sdteske 354274116Sdteske /* Advance to the next character */ 355274116Sdteske r = read(fd, p, 1); 356274116Sdteske } 357274116Sdteske 358274116Sdteske /* Get the current offset */ 359274116Sdteske charpos = lseek(fd, 0, SEEK_CUR) - 1; 360274116Sdteske if (charpos == -1) { 361274116Sdteske close(fd); 362274116Sdteske return (-1); 363274116Sdteske } 364274116Sdteske 365274116Sdteske /* Get the length of the value */ 366274116Sdteske n = (uint32_t)(charpos - curpos); 367274116Sdteske if (r != 0) /* more to read, but don't read ending key */ 368274116Sdteske n--; 369274116Sdteske 370274116Sdteske /* Move offset back to the beginning of the value */ 371274116Sdteske error = (int)lseek(fd, curpos, SEEK_SET); 372274116Sdteske if (error == (curpos - 1)) { 373274116Sdteske close(fd); 374274116Sdteske return (-1); 375274116Sdteske } 376274116Sdteske 377274116Sdteske /* Allocate and read the value into memory */ 378274116Sdteske if (n > vsize) { 379274116Sdteske if ((value = realloc(value, n + 1)) == NULL) { 380274116Sdteske close(fd); 381274116Sdteske return (-1); 382274116Sdteske } 383274116Sdteske vsize = n; 384274116Sdteske } 385274116Sdteske r = read(fd, value, n); 386274116Sdteske 387274116Sdteske /* Terminate the string */ 388274116Sdteske value[n] = '\0'; 389274116Sdteske 390274116Sdteske /* Cut trailing whitespace off by termination */ 391274116Sdteske t = value + n; 392274116Sdteske while (isspace(*--t)) 393274116Sdteske *t = '\0'; 394274116Sdteske 395274116Sdteske /* Escape the escaped quotes (replaceall is in string_m.c) */ 396274116Sdteske x = strcount(value, "\\\""); /* in string_m.c */ 397274116Sdteske if (x != 0 && (n + x) > vsize) { 398274116Sdteske if ((value = realloc(value, n + x + 1)) == NULL) { 399274116Sdteske close(fd); 400274116Sdteske return (-1); 401274116Sdteske } 402274116Sdteske vsize = n + x; 403274116Sdteske } 404274116Sdteske if (replaceall(value, "\\\"", "\\\\\"") < 0) { 405274116Sdteske /* Replace operation failed for some unknown reason */ 406274116Sdteske close(fd); 407274116Sdteske return (-1); 408274116Sdteske } 409274116Sdteske 410274116Sdteske /* Remove all new line characters */ 411274116Sdteske if (replaceall(value, "\\\n", "") < 0) { 412274116Sdteske /* Replace operation failed for some unknown reason */ 413274116Sdteske close(fd); 414274116Sdteske return (-1); 415274116Sdteske } 416274116Sdteske 417274116Sdteske /* Resolve escape sequences */ 418274116Sdteske strexpand(value); /* in string_m.c */ 419274116Sdteske 420274116Sdteskecall_function: 421274116Sdteske /* Abort if we're seeking only assignments */ 422274116Sdteske if (require_equals && !have_equals) 423274116Sdteske return (-1); 424274116Sdteske 425274116Sdteske found = have_equals = 0; /* reset */ 426274116Sdteske 427274116Sdteske /* If there are no options defined, call unknown and loop */ 428274116Sdteske if (options == NULL && unknown != NULL) { 429274116Sdteske error = unknown(NULL, line, directive, value); 430274116Sdteske if (error != 0) { 431274116Sdteske close(fd); 432274116Sdteske return (error); 433274116Sdteske } 434274116Sdteske continue; 435274116Sdteske } 436274116Sdteske 437274116Sdteske /* Loop through the array looking for a match for the value */ 438274116Sdteske for (n = 0; options[n].directive != NULL; n++) { 439274116Sdteske error = fnmatch(options[n].directive, directive, 440274116Sdteske FNM_NOESCAPE); 441274116Sdteske if (error == 0) { 442274116Sdteske found = 1; 443274116Sdteske /* Call function for array index item */ 444274116Sdteske if (options[n].action != NULL) { 445274116Sdteske error = options[n].action( 446274116Sdteske &options[n], 447274116Sdteske line, directive, value); 448274116Sdteske if (error != 0) { 449274116Sdteske close(fd); 450274116Sdteske return (error); 451274116Sdteske } 452274116Sdteske } 453274116Sdteske } else if (error != FNM_NOMATCH) { 454274116Sdteske /* An error has occurred */ 455274116Sdteske close(fd); 456274116Sdteske return (-1); 457274116Sdteske } 458274116Sdteske } 459274116Sdteske if (!found && unknown != NULL) { 460274116Sdteske /* 461274116Sdteske * No match was found for the value we read from the 462274116Sdteske * file; call function designated for unknown values. 463274116Sdteske */ 464274116Sdteske error = unknown(NULL, line, directive, value); 465274116Sdteske if (error != 0) { 466274116Sdteske close(fd); 467274116Sdteske return (error); 468274116Sdteske } 469274116Sdteske } 470274116Sdteske } 471274116Sdteske 472274116Sdteske close(fd); 473274116Sdteske return (0); 474274116Sdteske} 475