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