conf.c revision 1.10
1/* $OpenBSD: conf.c,v 1.10 1999/08/05 22:41:08 niklas Exp $ */ 2/* $EOM: conf.c,v 1.19 1999/08/05 14:57:59 niklas Exp $ */ 3 4/* 5 * Copyright (c) 1998, 1999 Niklas Hallqvist. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by Ericsson Radio Systems. 18 * 4. The name of the author may not be used to endorse or promote products 19 * derived from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33/* 34 * This code was written under funding by Ericsson Radio Systems. 35 */ 36 37#include <sys/param.h> 38#include <sys/mman.h> 39#include <sys/queue.h> 40#include <sys/stat.h> 41#include <ctype.h> 42#include <fcntl.h> 43#include <stdio.h> 44#include <stdlib.h> 45#include <string.h> 46#include <unistd.h> 47 48#include "sysdep.h" 49 50#include "app.h" 51#include "conf.h" 52#include "log.h" 53 54struct conf_trans { 55 TAILQ_ENTRY (conf_trans) link; 56 int trans; 57 enum conf_op { CONF_SET, CONF_REMOVE, CONF_REMOVE_SECTION } op; 58 char *section; 59 char *tag; 60 char *value; 61 int override; 62}; 63 64TAILQ_HEAD (conf_trans_head, conf_trans) conf_trans_queue; 65 66/* 67 * Radix-64 Encoding. 68 */ 69const u_int8_t bin2asc[] = 70 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 71 72const u_int8_t asc2bin[] = 73{ 74 255, 255, 255, 255, 255, 255, 255, 255, 75 255, 255, 255, 255, 255, 255, 255, 255, 76 255, 255, 255, 255, 255, 255, 255, 255, 77 255, 255, 255, 255, 255, 255, 255, 255, 78 255, 255, 255, 255, 255, 255, 255, 255, 79 255, 255, 255, 62, 255, 255, 255, 63, 80 52, 53, 54, 55, 56, 57, 58, 59, 81 60, 61, 255, 255, 255, 255, 255, 255, 82 255, 0, 1, 2, 3, 4, 5, 6, 83 7, 8, 9, 10, 11, 12, 13, 14, 84 15, 16, 17, 18, 19, 20, 21, 22, 85 23, 24, 25, 255, 255, 255, 255, 255, 86 255, 26, 27, 28, 29, 30, 31, 32, 87 33, 34, 35, 36, 37, 38, 39, 40, 88 41, 42, 43, 44, 45, 46, 47, 48, 89 49, 50, 51, 255, 255, 255, 255, 255 90}; 91 92struct conf_binding { 93 LIST_ENTRY (conf_binding) link; 94 char *section; 95 char *tag; 96 char *value; 97}; 98 99char *conf_path = CONFIG_FILE; 100LIST_HEAD (conf_bindings, conf_binding) conf_bindings[256]; 101 102static char *conf_addr; 103 104static __inline__ u_int8_t 105conf_hash (char *s) 106{ 107 u_int8_t hash = 0; 108 109 while (*s) 110 { 111 hash = ((hash << 1) | (hash >> 7)) ^ tolower (*s); 112 s++; 113 } 114 return hash; 115} 116 117/* 118 * Insert a tag-value combination from LINE (the equal sign is at POS) 119 */ 120static int 121conf_remove_now (char *section, char *tag) 122{ 123 struct conf_binding *cb, *next; 124 125 for (cb = LIST_FIRST (&conf_bindings[conf_hash (section)]); cb; cb = next) 126 { 127 next = LIST_NEXT (cb, link); 128 if (strcasecmp (cb->section, section) == 0 129 && strcasecmp (cb->tag, tag) == 0) 130 { 131 LIST_REMOVE (cb, link); 132 log_debug (LOG_MISC, 70, "[%s]:%s->%s removed", section, tag, 133 cb->value); 134 free (cb->section); 135 free (cb->tag); 136 free (cb->value); 137 free (cb); 138 return 0; 139 } 140 } 141 return 1; 142} 143 144static int 145conf_remove_section_now (char *section) 146{ 147 struct conf_binding *cb, *next; 148 int unseen = 1; 149 150 for (cb = LIST_FIRST (&conf_bindings[conf_hash (section)]); cb; cb = next) 151 { 152 next = LIST_NEXT (cb, link); 153 if (strcasecmp (cb->section, section) == 0) 154 { 155 unseen = 0; 156 LIST_REMOVE (cb, link); 157 log_debug (LOG_MISC, 70, "[%s]:%s->%s removed", section, cb->tag, 158 cb->value); 159 free (cb->section); 160 free (cb->tag); 161 free (cb->value); 162 free (cb); 163 } 164 } 165 return unseen; 166} 167 168/* 169 * Insert a tag-value combination from LINE (the equal sign is at POS) 170 * into SECTION of our configuration database. 171 */ 172static int 173conf_set_now (char *section, char *tag, char *value, int override) 174{ 175 struct conf_binding *node = 0; 176 177 if (override) 178 conf_remove_now (section, tag); 179 else if (conf_get_str (section, tag)) 180 { 181 log_print ("conf_set: duplicate tag [%s]:%s, ignoring...\n", section, 182 tag); 183 return 1; 184 } 185 186 node = calloc (1, sizeof *node); 187 if (!node) 188 { 189 log_error ("conf_set: calloc (1, %d) failed", sizeof *node); 190 return 1; 191 } 192 node->section = section; 193 node->tag = tag; 194 node->value = value; 195 196 LIST_INSERT_HEAD (&conf_bindings[conf_hash (section)], node, link); 197 log_debug (LOG_MISC, 70, "[%s]:%s->%s", node->section, node->tag, 198 node->value); 199 return 0; 200} 201 202/* 203 * Parse the line LINE of SZ bytes. Skip Comments, recognize section 204 * headers and feed tag-value pairs into our configuration database. 205 */ 206static void 207conf_parse_line (int trans, char *line, size_t sz) 208{ 209 char *cp = line; 210 int i; 211 static char *section = 0; 212 static int ln = 0; 213 214 ln++; 215 216 /* Lines starting with '#' or ';' are comments. */ 217 if (*line == '#' || *line == ';') 218 return; 219 220 /* '[section]' parsing... */ 221 if (*line == '[') 222 { 223 for (i = 1; i < sz; i++) 224 if (line[i] == ']') 225 break; 226 if (i == sz) 227 { 228 log_print ("conf_parse_line: %d:" 229 "non-matched ']', ignoring until next section", ln); 230 section = 0; 231 return; 232 } 233 section = malloc (i); 234 strncpy (section, line + 1, i - 1); 235 section[i - 1] = '\0'; 236 return; 237 } 238 239 /* Deal with assignments. */ 240 for (i = 0; i < sz; i++) 241 if (cp[i] == '=') 242 { 243 /* If no section, we are ignoring the lines. */ 244 if (!section) 245 { 246 log_print ("conf_parse_line: %d: ignoring line due to no section", 247 ln); 248 return; 249 } 250 line[strcspn (line, " \t=")] = '\0'; 251 /* XXX Perhaps should we not ignore errors? */ 252 conf_set (trans, section, line, 253 line + i + 1 + strspn (line + i + 1, " \t"), 0); 254 return; 255 } 256 257 /* Other non-empty lines are wierd. */ 258 i = strspn (line, " \t"); 259 if (line[i]) 260 log_print ("conf_parse_line: %d: syntax error", ln); 261 262 return; 263} 264 265/* Parse the mapped configuration file. */ 266static void 267conf_parse (int trans, char *buf, size_t sz) 268{ 269 char *cp = buf; 270 char *bufend = buf + sz; 271 char *line; 272 273 line = cp; 274 while (cp < bufend) 275 { 276 if (*cp == '\n') 277 { 278 /* Check for escaped newlines. */ 279 if (cp > buf && *(cp - 1) == '\\') 280 *(cp - 1) = *cp = ' '; 281 else 282 { 283 *cp = '\0'; 284 conf_parse_line (trans, line, cp - line); 285 line = cp + 1; 286 } 287 } 288 cp++; 289 } 290 if (cp != line) 291 log_print ("conf_parse: last line non-terminated, ignored."); 292} 293 294void 295conf_init (void) 296{ 297 int i; 298 299 for (i = 0; i < sizeof conf_bindings / sizeof conf_bindings[0]; i++) 300 LIST_INIT (&conf_bindings[i]); 301 TAILQ_INIT (&conf_trans_queue); 302 conf_reinit (); 303} 304 305/* Open the config file and map it into our address space, then parse it. */ 306void 307conf_reinit (void) 308{ 309 struct conf_binding *cb = 0; 310 int fd, i, trans; 311 struct stat st; 312 off_t sz; 313 char *new_conf_addr = 0; 314 315 fd = open (conf_path, O_RDONLY); 316 if (fd == -1) 317 { 318 log_error ("open (\"%s\", O_RDONLY) failed", conf_path); 319 return; 320 } 321 if (fstat (fd, &st) == -1) 322 { 323 log_error ("fstat (%d, &st) failed", fd); 324 goto fail; 325 } 326 sz = st.st_size; 327 new_conf_addr = malloc (sz); 328 if (!new_conf_addr) 329 { 330 log_error ("malloc (%d) failed", sz); 331 goto fail; 332 } 333 /* XXX I assume short reads won't happen here. */ 334 if (read (fd, new_conf_addr, sz) != sz) 335 { 336 log_error ("read (%d, %p, %d) failed", fd, new_conf_addr, sz); 337 goto fail; 338 } 339 close (fd); 340 341 trans = conf_begin (); 342 343 /* XXX Should we not care about errors and rollback? */ 344 conf_parse (trans, new_conf_addr, sz); 345 346 /* Free potential existing configuration. */ 347 if (conf_addr) 348 { 349 for (i = 0; i < sizeof conf_bindings / sizeof conf_bindings[0]; i++) 350 for (cb = LIST_FIRST (&conf_bindings[i]); cb; 351 cb = LIST_FIRST (&conf_bindings[i])) 352 conf_remove_now (cb->section, cb->tag); 353 free (conf_addr); 354 } 355 356 conf_end (trans, 1); 357 conf_addr = new_conf_addr; 358 return; 359 360 fail: 361 if (new_conf_addr) 362 free (new_conf_addr); 363 close (fd); 364} 365 366/* 367 * Return the numeric value denoted by TAG in section SECTION or DEF 368 * if that tag does not exist. 369 */ 370int 371conf_get_num (char *section, char *tag, int def) 372{ 373 char *value = conf_get_str (section, tag); 374 375 if (value) 376 return atoi (value); 377 return def; 378} 379 380/* Validate X according to the range denoted by TAG in section SECTION. */ 381int 382conf_match_num (char *section, char *tag, int x) 383{ 384 char *value = conf_get_str (section, tag); 385 int val, min, max, n; 386 387 if (!value) 388 return 0; 389 n = sscanf (value, "%d,%d:%d", &val, &min, &max); 390 switch (n) 391 { 392 case 1: 393 log_debug (LOG_MISC, 90, "conf_match_num: %s:%s %d==%d?", section, tag, 394 val, x); 395 return x == val; 396 case 3: 397 log_debug (LOG_MISC, 90, "conf_match_num: %s:%s %d<=%d<=%d?", section, 398 tag, min, x, max); 399 return min <= x && max >= x; 400 default: 401 log_error ("conf_match_num: section %s tag %s: invalid number spec %s", 402 section, tag, value); 403 } 404 return 0; 405} 406 407/* Return the string value denoted by TAG in section SECTION. */ 408char * 409conf_get_str (char *section, char *tag) 410{ 411 struct conf_binding *cb; 412 413 for (cb = LIST_FIRST (&conf_bindings[conf_hash (section)]); cb; 414 cb = LIST_NEXT (cb, link)) 415 if (strcasecmp (section, cb->section) == 0 416 && strcasecmp (tag, cb->tag) == 0) 417 { 418 log_debug (LOG_MISC, 60, "conf_get_str: [%s]:%s->%s", section, 419 tag, cb->value); 420 return cb->value; 421 } 422 log_debug (LOG_MISC, 60, 423 "conf_get_str: configuration value not found [%s]:%s", section, 424 tag); 425 return 0; 426} 427 428/* 429 * Build a list of string values out of the comma separated value denoted by 430 * TAG in SECTION. 431 */ 432struct conf_list * 433conf_get_list (char *section, char *tag) 434{ 435 char *liststr = 0, *p, *field; 436 struct conf_list *list = 0; 437 struct conf_list_node *node; 438 439 list = malloc (sizeof *list); 440 if (!list) 441 goto cleanup; 442 TAILQ_INIT (&list->fields); 443 list->cnt = 0; 444 liststr = conf_get_str (section, tag); 445 if (!liststr) 446 goto cleanup; 447 liststr = strdup (liststr); 448 if (!liststr) 449 goto cleanup; 450 p = liststr; 451 while ((field = strsep (&p, ", \t")) != NULL) 452 { 453 if (*field == '\0') 454 { 455 log_print ("conf_get_list: empty field, ignoring..."); 456 continue; 457 } 458 list->cnt++; 459 node = calloc (1, sizeof *node); 460 if (!node) 461 goto cleanup; 462 node->field = strdup (field); 463 if (!node->field) 464 goto cleanup; 465 TAILQ_INSERT_TAIL (&list->fields, node, link); 466 } 467 free (liststr); 468 return list; 469 470 cleanup: 471 if (list) 472 conf_free_list (list); 473 if (liststr) 474 free (liststr); 475 return 0; 476} 477 478struct conf_list * 479conf_get_tag_list (char *section) 480{ 481 struct conf_list *list = 0; 482 struct conf_list_node *node; 483 struct conf_binding *cb; 484 485 list = malloc (sizeof *list); 486 if (!list) 487 goto cleanup; 488 TAILQ_INIT (&list->fields); 489 list->cnt = 0; 490 for (cb = LIST_FIRST (&conf_bindings[conf_hash (section)]); cb; 491 cb = LIST_NEXT (cb, link)) 492 if (strcasecmp (section, cb->section) == 0) 493 { 494 list->cnt++; 495 node = calloc (1, sizeof *node); 496 if (!node) 497 goto cleanup; 498 node->field = strdup (cb->tag); 499 if (!node->field) 500 goto cleanup; 501 TAILQ_INSERT_TAIL (&list->fields, node, link); 502 } 503 return list; 504 505 cleanup: 506 if (list) 507 conf_free_list (list); 508 return 0; 509} 510 511/* Decode a PEM encoded buffer. */ 512int 513conf_decode_base64(u_int8_t *out, u_int32_t *len, u_char *buf) 514{ 515 u_int32_t c = 0; 516 u_int8_t c1, c2, c3, c4; 517 518 while (*buf) 519 { 520 if (*buf > 127 || (c1 = asc2bin[*buf]) == 255) 521 return 0; 522 buf++; 523 524 if (*buf > 127 || (c2 = asc2bin[*buf]) == 255) 525 return 0; 526 buf++; 527 528 if (*buf == '=') 529 { 530 c3 = c4 = 0; 531 c++; 532 533 /* Check last four bit */ 534 if (c2 & 0xF) 535 return 0; 536 537 if (!strcmp (buf, "==")) 538 buf++; 539 else 540 return 0; 541 } 542 else if (*buf > 127 || (c3 = asc2bin[*buf]) == 255) 543 return 0; 544 else 545 { 546 if (*++buf == '=') 547 { 548 c4 = 0; 549 c += 2; 550 551 /* Check last two bit */ 552 if (c3 & 3) 553 return 0; 554 555 if (strcmp(buf, "=")) 556 return 0; 557 558 } 559 else if (*buf > 127 || (c4 = asc2bin[*buf]) == 255) 560 return 0; 561 else 562 c += 3; 563 } 564 565 buf++; 566 *out++ = (c1 << 2) | (c2 >> 4); 567 *out++ = (c2 << 4) | (c3 >> 2); 568 *out++ = (c3 << 6) | c4; 569 } 570 571 *len = c; 572 return 1; 573 574} 575 576/* Read a line from a stream to the buffer. */ 577int 578conf_get_line (FILE *stream, char *buf, u_int32_t len) 579{ 580 char c; 581 582 while (len-- > 1) 583 { 584 c = fgetc (stream); 585 if (c == '\n') 586 { 587 *buf = 0; 588 return 1; 589 } 590 else if (c == EOF) 591 break; 592 593 *buf++ = c; 594 } 595 596 *buf = 0; 597 return 0; 598} 599 600void 601conf_free_list (struct conf_list *list) 602{ 603 struct conf_list_node *node = TAILQ_FIRST (&list->fields); 604 605 while (node) 606 { 607 TAILQ_REMOVE (&list->fields, node, link); 608 if (node->field) 609 free (node->field); 610 free (node); 611 node = TAILQ_FIRST (&list->fields); 612 } 613 free (list); 614} 615 616int 617conf_begin (void) 618{ 619 static int seq = 0; 620 621 return ++seq; 622} 623 624static struct conf_trans * 625conf_trans_node (int transaction, enum conf_op op) 626{ 627 struct conf_trans *node; 628 629 node = calloc (1, sizeof *node); 630 if (!node) 631 { 632 log_error ("conf_trans_node: calloc (1, %d) failed", sizeof *node); 633 return 0; 634 } 635 node->trans = transaction; 636 node->op = op; 637 TAILQ_INSERT_TAIL (&conf_trans_queue, node, link); 638 return node; 639} 640 641/* Queue a set operation. */ 642int 643conf_set (int transaction, char *section, char *tag, char *value, int override) 644{ 645 struct conf_trans *node; 646 647 node = conf_trans_node (transaction, CONF_SET); 648 if (!node) 649 return 1; 650 node->section = strdup (section); 651 if (!node->section) 652 { 653 log_error ("conf_set: strdup (\"%s\") failed", section); 654 goto fail; 655 } 656 node->tag = strdup (tag); 657 if (!node->tag) 658 { 659 log_error ("conf_set: strdup (\"%s\") failed", tag); 660 goto fail; 661 } 662 node->value = strdup (value); 663 if (!node->value) 664 { 665 log_error ("conf_set: strdup (\"%s\") failed", value); 666 goto fail; 667 } 668 node->override = override; 669 return 0; 670 671 fail: 672 if (node->tag) 673 free (node->tag); 674 if (node->section) 675 free (node->section); 676 if (node) 677 free (node); 678 return 1; 679} 680 681/* Queue a remove operation. */ 682int 683conf_remove (int transaction, char *section, char *tag) 684{ 685 struct conf_trans *node; 686 687 node = conf_trans_node (transaction, CONF_REMOVE); 688 if (!node) 689 goto fail; 690 node->section = strdup (section); 691 if (!node->section) 692 { 693 log_error ("conf_remove: strdup (\"%s\") failed", section); 694 goto fail; 695 } 696 node->tag = strdup (tag); 697 if (!node->tag) 698 { 699 log_error ("conf_remove: strdup (\"%s\") failed", tag); 700 goto fail; 701 } 702 return 0; 703 704 fail: 705 if (node->section) 706 free (node->section); 707 if (node) 708 free (node); 709 return 1; 710} 711 712/* Queue a remove section operation. */ 713int 714conf_remove_section (int transaction, char *section) 715{ 716 struct conf_trans *node; 717 718 node = conf_trans_node (transaction, CONF_REMOVE_SECTION); 719 if (!node) 720 goto fail; 721 node->section = strdup (section); 722 if (!node->section) 723 { 724 log_error ("conf_remove_section: strdup (\"%s\") failed", section); 725 goto fail; 726 } 727 return 0; 728 729 fail: 730 if (node) 731 free (node); 732 return 1; 733} 734 735/* Execute all queued operations for this transaction. Cleanup. */ 736int 737conf_end (int transaction, int commit) 738{ 739 struct conf_trans *node, *next; 740 741 for (node = TAILQ_FIRST (&conf_trans_queue); node; node = next) 742 { 743 next = TAILQ_NEXT (node, link); 744 if (node->trans == transaction) 745 { 746 if (commit) 747 switch (node->op) 748 { 749 case CONF_SET: 750 conf_set_now (node->section, node->tag, node->value, 751 node->override); 752 break; 753 case CONF_REMOVE: 754 conf_remove_now (node->section, node->tag); 755 break; 756 case CONF_REMOVE_SECTION: 757 conf_remove_section_now (node->section); 758 break; 759 default: 760 log_print ("conf_end: unknown operation: %d", node->op); 761 } 762 TAILQ_REMOVE (&conf_trans_queue, node, link); 763 free (node); 764 } 765 } 766 return 0; 767} 768