conf.c revision 1.11
1/*	$OpenBSD: conf.c,v 1.11 2000/02/25 17:23:38 niklas Exp $	*/
2/*	$EOM: conf.c,v 1.20 2000/02/20 19:58:36 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 ("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_DBG ((LOG_MISC, 90, "conf_match_num: %s:%s %d==%d?", section, tag,
394		val, x));
395      return x == val;
396    case 3:
397      LOG_DBG ((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_DBG ((LOG_MISC, 60, "conf_get_str: [%s]:%s->%s", section,
419		  tag, cb->value));
420	return cb->value;
421      }
422  LOG_DBG ((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