conf.c revision 1.12
1/* $OpenBSD: conf.c,v 1.12 2000/04/07 22:06:44 niklas Exp $ */ 2/* $EOM: conf.c,v 1.21 2000/04/07 19:03:25 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_DBG ((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_DBG ((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_DBG ((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 ("conf_reinit: open (\"%s\", O_RDONLY) failed", conf_path); 319 return; 320 } 321 if (fstat (fd, &st) == -1) 322 { 323 log_error ("conf_reinit: 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 ("conf_reinit: 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 ("conf_reinit: read (%d, %p, %d) failed", fd, new_conf_addr, 337 sz); 338 goto fail; 339 } 340 close (fd); 341 342 trans = conf_begin (); 343 344 /* XXX Should we not care about errors and rollback? */ 345 conf_parse (trans, new_conf_addr, sz); 346 347 /* Free potential existing configuration. */ 348 if (conf_addr) 349 { 350 for (i = 0; i < sizeof conf_bindings / sizeof conf_bindings[0]; i++) 351 for (cb = LIST_FIRST (&conf_bindings[i]); cb; 352 cb = LIST_FIRST (&conf_bindings[i])) 353 conf_remove_now (cb->section, cb->tag); 354 free (conf_addr); 355 } 356 357 conf_end (trans, 1); 358 conf_addr = new_conf_addr; 359 return; 360 361 fail: 362 if (new_conf_addr) 363 free (new_conf_addr); 364 close (fd); 365} 366 367/* 368 * Return the numeric value denoted by TAG in section SECTION or DEF 369 * if that tag does not exist. 370 */ 371int 372conf_get_num (char *section, char *tag, int def) 373{ 374 char *value = conf_get_str (section, tag); 375 376 if (value) 377 return atoi (value); 378 return def; 379} 380 381/* Validate X according to the range denoted by TAG in section SECTION. */ 382int 383conf_match_num (char *section, char *tag, int x) 384{ 385 char *value = conf_get_str (section, tag); 386 int val, min, max, n; 387 388 if (!value) 389 return 0; 390 n = sscanf (value, "%d,%d:%d", &val, &min, &max); 391 switch (n) 392 { 393 case 1: 394 LOG_DBG ((LOG_MISC, 90, "conf_match_num: %s:%s %d==%d?", section, tag, 395 val, x)); 396 return x == val; 397 case 3: 398 LOG_DBG ((LOG_MISC, 90, "conf_match_num: %s:%s %d<=%d<=%d?", section, 399 tag, min, x, max)); 400 return min <= x && max >= x; 401 default: 402 log_error ("conf_match_num: section %s tag %s: invalid number spec %s", 403 section, tag, value); 404 } 405 return 0; 406} 407 408/* Return the string value denoted by TAG in section SECTION. */ 409char * 410conf_get_str (char *section, char *tag) 411{ 412 struct conf_binding *cb; 413 414 for (cb = LIST_FIRST (&conf_bindings[conf_hash (section)]); cb; 415 cb = LIST_NEXT (cb, link)) 416 if (strcasecmp (section, cb->section) == 0 417 && strcasecmp (tag, cb->tag) == 0) 418 { 419 LOG_DBG ((LOG_MISC, 60, "conf_get_str: [%s]:%s->%s", section, 420 tag, cb->value)); 421 return cb->value; 422 } 423 LOG_DBG ((LOG_MISC, 60, 424 "conf_get_str: configuration value not found [%s]:%s", section, 425 tag)); 426 return 0; 427} 428 429/* 430 * Build a list of string values out of the comma separated value denoted by 431 * TAG in SECTION. 432 */ 433struct conf_list * 434conf_get_list (char *section, char *tag) 435{ 436 char *liststr = 0, *p, *field; 437 struct conf_list *list = 0; 438 struct conf_list_node *node; 439 440 list = malloc (sizeof *list); 441 if (!list) 442 goto cleanup; 443 TAILQ_INIT (&list->fields); 444 list->cnt = 0; 445 liststr = conf_get_str (section, tag); 446 if (!liststr) 447 goto cleanup; 448 liststr = strdup (liststr); 449 if (!liststr) 450 goto cleanup; 451 p = liststr; 452 while ((field = strsep (&p, ", \t")) != NULL) 453 { 454 if (*field == '\0') 455 { 456 log_print ("conf_get_list: empty field, ignoring..."); 457 continue; 458 } 459 list->cnt++; 460 node = calloc (1, sizeof *node); 461 if (!node) 462 goto cleanup; 463 node->field = strdup (field); 464 if (!node->field) 465 goto cleanup; 466 TAILQ_INSERT_TAIL (&list->fields, node, link); 467 } 468 free (liststr); 469 return list; 470 471 cleanup: 472 if (list) 473 conf_free_list (list); 474 if (liststr) 475 free (liststr); 476 return 0; 477} 478 479struct conf_list * 480conf_get_tag_list (char *section) 481{ 482 struct conf_list *list = 0; 483 struct conf_list_node *node; 484 struct conf_binding *cb; 485 486 list = malloc (sizeof *list); 487 if (!list) 488 goto cleanup; 489 TAILQ_INIT (&list->fields); 490 list->cnt = 0; 491 for (cb = LIST_FIRST (&conf_bindings[conf_hash (section)]); cb; 492 cb = LIST_NEXT (cb, link)) 493 if (strcasecmp (section, cb->section) == 0) 494 { 495 list->cnt++; 496 node = calloc (1, sizeof *node); 497 if (!node) 498 goto cleanup; 499 node->field = strdup (cb->tag); 500 if (!node->field) 501 goto cleanup; 502 TAILQ_INSERT_TAIL (&list->fields, node, link); 503 } 504 return list; 505 506 cleanup: 507 if (list) 508 conf_free_list (list); 509 return 0; 510} 511 512/* Decode a PEM encoded buffer. */ 513int 514conf_decode_base64(u_int8_t *out, u_int32_t *len, u_char *buf) 515{ 516 u_int32_t c = 0; 517 u_int8_t c1, c2, c3, c4; 518 519 while (*buf) 520 { 521 if (*buf > 127 || (c1 = asc2bin[*buf]) == 255) 522 return 0; 523 buf++; 524 525 if (*buf > 127 || (c2 = asc2bin[*buf]) == 255) 526 return 0; 527 buf++; 528 529 if (*buf == '=') 530 { 531 c3 = c4 = 0; 532 c++; 533 534 /* Check last four bit */ 535 if (c2 & 0xF) 536 return 0; 537 538 if (!strcmp (buf, "==")) 539 buf++; 540 else 541 return 0; 542 } 543 else if (*buf > 127 || (c3 = asc2bin[*buf]) == 255) 544 return 0; 545 else 546 { 547 if (*++buf == '=') 548 { 549 c4 = 0; 550 c += 2; 551 552 /* Check last two bit */ 553 if (c3 & 3) 554 return 0; 555 556 if (strcmp(buf, "=")) 557 return 0; 558 559 } 560 else if (*buf > 127 || (c4 = asc2bin[*buf]) == 255) 561 return 0; 562 else 563 c += 3; 564 } 565 566 buf++; 567 *out++ = (c1 << 2) | (c2 >> 4); 568 *out++ = (c2 << 4) | (c3 >> 2); 569 *out++ = (c3 << 6) | c4; 570 } 571 572 *len = c; 573 return 1; 574 575} 576 577/* Read a line from a stream to the buffer. */ 578int 579conf_get_line (FILE *stream, char *buf, u_int32_t len) 580{ 581 char c; 582 583 while (len-- > 1) 584 { 585 c = fgetc (stream); 586 if (c == '\n') 587 { 588 *buf = 0; 589 return 1; 590 } 591 else if (c == EOF) 592 break; 593 594 *buf++ = c; 595 } 596 597 *buf = 0; 598 return 0; 599} 600 601void 602conf_free_list (struct conf_list *list) 603{ 604 struct conf_list_node *node = TAILQ_FIRST (&list->fields); 605 606 while (node) 607 { 608 TAILQ_REMOVE (&list->fields, node, link); 609 if (node->field) 610 free (node->field); 611 free (node); 612 node = TAILQ_FIRST (&list->fields); 613 } 614 free (list); 615} 616 617int 618conf_begin (void) 619{ 620 static int seq = 0; 621 622 return ++seq; 623} 624 625static struct conf_trans * 626conf_trans_node (int transaction, enum conf_op op) 627{ 628 struct conf_trans *node; 629 630 node = calloc (1, sizeof *node); 631 if (!node) 632 { 633 log_error ("conf_trans_node: calloc (1, %d) failed", sizeof *node); 634 return 0; 635 } 636 node->trans = transaction; 637 node->op = op; 638 TAILQ_INSERT_TAIL (&conf_trans_queue, node, link); 639 return node; 640} 641 642/* Queue a set operation. */ 643int 644conf_set (int transaction, char *section, char *tag, char *value, int override) 645{ 646 struct conf_trans *node; 647 648 node = conf_trans_node (transaction, CONF_SET); 649 if (!node) 650 return 1; 651 node->section = strdup (section); 652 if (!node->section) 653 { 654 log_error ("conf_set: strdup (\"%s\") failed", section); 655 goto fail; 656 } 657 node->tag = strdup (tag); 658 if (!node->tag) 659 { 660 log_error ("conf_set: strdup (\"%s\") failed", tag); 661 goto fail; 662 } 663 node->value = strdup (value); 664 if (!node->value) 665 { 666 log_error ("conf_set: strdup (\"%s\") failed", value); 667 goto fail; 668 } 669 node->override = override; 670 return 0; 671 672 fail: 673 if (node->tag) 674 free (node->tag); 675 if (node->section) 676 free (node->section); 677 if (node) 678 free (node); 679 return 1; 680} 681 682/* Queue a remove operation. */ 683int 684conf_remove (int transaction, char *section, char *tag) 685{ 686 struct conf_trans *node; 687 688 node = conf_trans_node (transaction, CONF_REMOVE); 689 if (!node) 690 goto fail; 691 node->section = strdup (section); 692 if (!node->section) 693 { 694 log_error ("conf_remove: strdup (\"%s\") failed", section); 695 goto fail; 696 } 697 node->tag = strdup (tag); 698 if (!node->tag) 699 { 700 log_error ("conf_remove: strdup (\"%s\") failed", tag); 701 goto fail; 702 } 703 return 0; 704 705 fail: 706 if (node->section) 707 free (node->section); 708 if (node) 709 free (node); 710 return 1; 711} 712 713/* Queue a remove section operation. */ 714int 715conf_remove_section (int transaction, char *section) 716{ 717 struct conf_trans *node; 718 719 node = conf_trans_node (transaction, CONF_REMOVE_SECTION); 720 if (!node) 721 goto fail; 722 node->section = strdup (section); 723 if (!node->section) 724 { 725 log_error ("conf_remove_section: strdup (\"%s\") failed", section); 726 goto fail; 727 } 728 return 0; 729 730 fail: 731 if (node) 732 free (node); 733 return 1; 734} 735 736/* Execute all queued operations for this transaction. Cleanup. */ 737int 738conf_end (int transaction, int commit) 739{ 740 struct conf_trans *node, *next; 741 742 for (node = TAILQ_FIRST (&conf_trans_queue); node; node = next) 743 { 744 next = TAILQ_NEXT (node, link); 745 if (node->trans == transaction) 746 { 747 if (commit) 748 switch (node->op) 749 { 750 case CONF_SET: 751 conf_set_now (node->section, node->tag, node->value, 752 node->override); 753 break; 754 case CONF_REMOVE: 755 conf_remove_now (node->section, node->tag); 756 break; 757 case CONF_REMOVE_SECTION: 758 conf_remove_section_now (node->section); 759 break; 760 default: 761 log_print ("conf_end: unknown operation: %d", node->op); 762 } 763 TAILQ_REMOVE (&conf_trans_queue, node, link); 764 free (node); 765 } 766 } 767 return 0; 768} 769