conf.c revision 1.6
1/*	$OpenBSD: conf.c,v 1.6 1999/02/26 03:34:26 niklas Exp $	*/
2/*	$EOM: conf.c,v 1.14 1999/02/25 11:38:47 niklas Exp $	*/
3
4/*
5 * Copyright (c) 1998 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/types.h>
39#include <sys/mman.h>
40#include <sys/queue.h>
41#include <sys/stat.h>
42#include <ctype.h>
43#include <fcntl.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47#include <unistd.h>
48
49#include "sysdep.h"
50
51#include "app.h"
52#include "conf.h"
53#include "log.h"
54
55/*
56 * Radix-64 Encoding.
57 */
58
59const u_int8_t bin2asc[] =
60        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
61
62const u_int8_t asc2bin[] =
63{
64  255, 255, 255, 255, 255, 255, 255, 255,
65  255, 255, 255, 255, 255, 255, 255, 255,
66  255, 255, 255, 255, 255, 255, 255, 255,
67  255, 255, 255, 255, 255, 255, 255, 255,
68  255, 255, 255, 255, 255, 255, 255, 255,
69  255, 255, 255,  62, 255, 255, 255,  63,
70   52,  53,  54,  55,  56,  57,  58,  59,
71   60,  61, 255, 255, 255, 255, 255, 255,
72  255,   0,   1,   2,   3,   4,   5,   6,
73    7,   8,   9,  10,  11,  12,  13,  14,
74   15,  16,  17,  18,  19,  20,  21,  22,
75   23,  24,  25, 255, 255, 255, 255, 255,
76  255,  26,  27,  28,  29,  30,  31,  32,
77   33,  34,  35,  36,  37,  38,  39,  40,
78   41,  42,  43,  44,  45,  46,  47,  48,
79   49,  50,  51, 255, 255, 255, 255, 255
80};
81
82struct conf_binding {
83  LIST_ENTRY (conf_binding) link;
84  char *section;
85  char *tag;
86  char *value;
87};
88
89char *conf_path = CONFIG_FILE;
90LIST_HEAD (conf_bindings, conf_binding) conf_bindings;
91
92static off_t conf_sz;
93static char *conf_addr;
94
95/*
96 * Insert a tag-value combination from LINE (the equal sign is at POS)
97 * into SECTION of our configuration database.
98 * XXX Should really be a hash table implementation.
99 */
100static void
101conf_set (char *section, char *line, int pos)
102{
103  struct conf_binding *node;
104  int i;
105
106  node = malloc (sizeof *node);
107  if (!node)
108    log_fatal ("conf_set: out of memory");
109  node->section = section;
110  node->tag = line;
111  for (i = 0; line[i] && i < pos; i++)
112    ;
113  line[i] = '\0';
114  if (conf_get_str (section, line))
115    {
116      log_print ("conf_set: duplicate tag [%s]:%s, ignoring...\n", section,
117		 line);
118      return;
119    }
120  node->value = line + pos + 1 + strspn (line + pos + 1, " \t");
121  LIST_INSERT_HEAD (&conf_bindings, node, link);
122  log_debug (LOG_MISC, 70, "(%s,%s)->%s", node->section, node->tag,
123	     node->value);
124}
125
126/*
127 * Parse the line LINE of SZ bytes.  Skip Comments, recognize section
128 * headers and feed tag-value pairs into our configuration database.
129 */
130static void
131conf_parse_line (char *line, size_t sz)
132{
133  char *cp = line;
134  int i;
135  static char *section = 0;
136  static int ln = 0;
137
138  ln++;
139
140  /* Lines starting with '#' or ';' are comments.  */
141  if (*line == '#' || *line == ';')
142    return;
143
144  /* '[section]' parsing...  */
145  if (*line == '[')
146    {
147      for (i = 1; i < sz; i++)
148	if (line[i] == ']')
149	  break;
150      if (i == sz)
151	{
152	  log_print ("conf_parse_line: %d:"
153		     "non-matched ']', ignoring until next section", ln);
154	  section = 0;
155	  return;
156	}
157      section = malloc (i);
158      strncpy (section, line + 1, i - 1);
159      section[i - 1] = '\0';
160      return;
161    }
162
163  /* Deal with assignments.  */
164  for (i = 0; i < sz; i++)
165    if (cp[i] == '=')
166      {
167	/* If no section, we are ignoring the lines.  */
168	if (!section)
169	  {
170	    log_print ("conf_parse_line: %d: ignoring line due to no section",
171		       ln);
172	    return;
173	  }
174	conf_set (section, line, i);
175	return;
176      }
177
178  /* Other non-empty lines are wierd.  */
179  i = strspn (line, " \t");
180  if (line[i])
181    log_print ("conf_parse_line: %d: syntax error", ln);
182
183  return;
184}
185
186/* Parse the mapped configuration file.  */
187static void
188conf_parse (void)
189{
190  char *cp = conf_addr;
191  char *conf_end = conf_addr + conf_sz;
192  char *line;
193
194  line = cp;
195  while (cp < conf_end)
196    {
197      if (*cp == '\n')
198	{
199	  /* Check for escaped newlines.  */
200	  if (cp > conf_addr && *(cp - 1) == '\\')
201	    *(cp - 1) = *cp = ' ';
202	  else
203	    {
204	      *cp = '\0';
205	      conf_parse_line (line, cp - line);
206	      line = cp + 1;
207	    }
208	}
209      cp++;
210    }
211  if (cp != line)
212    log_print ("conf_parse: last line non-terminated, ignored.");
213}
214
215/* Open the config file and map it into our address space, then parse it.  */
216void
217conf_init (void)
218{
219  int fd;
220  struct stat st;
221
222  /*
223   * Start by freeing potential existing configuration.
224   *
225   * XXX One could envision doing this late, surviving failures with just
226   * a warning log message that the new configuration did not get read
227   * and that the former one persists.
228   */
229  if (conf_addr)
230    {
231      while (LIST_FIRST (&conf_bindings))
232	LIST_REMOVE (LIST_FIRST (&conf_bindings), link);
233      free (conf_addr);
234    }
235
236  fd = open (conf_path, O_RDONLY);
237  if (fd == -1)
238    log_fatal ("open (\"%s\", O_RDONLY)", conf_path);
239  if (fstat (fd, &st) == -1)
240    log_fatal ("fstat (%d, &st)", fd);
241  conf_sz = st.st_size;
242  conf_addr = malloc (conf_sz);
243  if (!conf_addr)
244    log_fatal ("malloc (%d)", conf_sz);
245  /* XXX I assume short reads won't happen here.  */
246  if (read (fd, conf_addr, conf_sz) != conf_sz)
247    log_fatal ("read (%d, %p, %d)", fd, conf_addr, conf_sz);
248  close (fd);
249
250  LIST_INIT (&conf_bindings);
251  conf_parse ();
252
253#ifdef NEED_SYSDEP_APP
254  /* Let the application layer record on-demand keyed connections.  */
255  app_conf_init_hook ();
256#endif
257}
258
259/*
260 * Return the numeric value denoted by TAG in section SECTION or DEF
261 * if that tag does not exist.
262 */
263int
264conf_get_num (char *section, char *tag, int def)
265{
266  char *value = conf_get_str (section, tag);
267
268  if (value)
269      return atoi (value);
270  return def;
271}
272
273/* Validate X according to the range denoted by TAG in section SECTION.  */
274int
275conf_match_num (char *section, char *tag, int x)
276{
277  char *value = conf_get_str (section, tag);
278  int val, min, max, n;
279
280  if (!value)
281    return 0;
282  n = sscanf (value, "%d,%d:%d", &val, &min, &max);
283  switch (n)
284    {
285    case 1:
286      log_debug (LOG_MISC, 90, "conf_match_num: %s:%s %d==%d?", section, tag,
287		 val, x);
288      return x == val;
289    case 3:
290      log_debug (LOG_MISC, 90, "conf_match_num: %s:%s %d<=%d<=%d?", section,
291		 tag, min, x, max);
292      return min <= x && max >= x;
293    default:
294      log_error ("conf_match_num: section %s tag %s: invalid number spec %s",
295		 section, tag, value);
296    }
297  return 0;
298}
299
300/* Return the string value denoted by TAG in section SECTION.  */
301char *
302conf_get_str (char *section, char *tag)
303{
304  struct conf_binding *cb;
305
306  for (cb = LIST_FIRST (&conf_bindings); cb; cb = LIST_NEXT (cb, link))
307    if (strcasecmp (section, cb->section) == 0
308	&& strcasecmp (tag, cb->tag) == 0)
309      {
310	log_debug (LOG_MISC, 60, "conf_get_str: (%s, %s) -> %s", section,
311		   tag, cb->value);
312	return cb->value;
313      }
314  log_debug (LOG_MISC, 60,
315	     "conf_get_str: configuration value not found (%s, %s)", section,
316	     tag);
317  return 0;
318}
319
320struct conf_list *
321conf_get_list (char *section, char *tag)
322{
323  char *liststr = 0, *p, *field;
324  struct conf_list *list = 0;
325  struct conf_list_node *node;
326
327  list = malloc (sizeof *list);
328  if (!list)
329    goto cleanup;
330  TAILQ_INIT (&list->fields);
331  list->cnt = 0;
332  liststr = conf_get_str (section, tag);
333  if (!liststr)
334    goto cleanup;
335  liststr = strdup (liststr);
336  if (!liststr)
337    goto cleanup;
338  p = liststr;
339  while ((field = strsep (&p, ", \t")) != NULL)
340    {
341      if (*field == '\0')
342	{
343	  log_print ("conf_get_list: empty field, ignoring...");
344	  continue;
345	}
346      list->cnt++;
347      node = malloc (sizeof *node);
348      if (!node)
349	goto cleanup;
350      node->field = field;
351      TAILQ_INSERT_TAIL (&list->fields, node, link);
352    }
353  return list;
354
355 cleanup:
356  if (list)
357    conf_free_list (list);
358  if (liststr)
359    free (liststr);
360  return 0;
361}
362
363struct conf_list *
364conf_get_tag_list (char *section)
365{
366  struct conf_list *list = 0;
367  struct conf_list_node *node;
368  struct conf_binding *cb;
369
370  list = malloc (sizeof *list);
371  if (!list)
372    goto cleanup;
373  TAILQ_INIT (&list->fields);
374  list->cnt = 0;
375  for (cb = LIST_FIRST (&conf_bindings); cb; cb = LIST_NEXT (cb, link))
376    if (strcasecmp (section, cb->section) == 0)
377      {
378	list->cnt++;
379	node = malloc (sizeof *node);
380	if (!node)
381	  goto cleanup;
382	node->field = cb->tag;
383	TAILQ_INSERT_TAIL (&list->fields, node, link);
384      }
385  return list;
386
387 cleanup:
388  if (list)
389    conf_free_list (list);
390  return 0;
391}
392
393/* Decode a PEM encoded buffer.  */
394int
395conf_decode_base64(u_int8_t *out, u_int32_t *len, u_char *buf)
396{
397  u_int32_t c = 0;
398  u_int8_t c1, c2, c3, c4;
399
400  while (*buf)
401    {
402      if (*buf > 127 || (c1 = asc2bin[*buf]) == 255)
403	return 0;
404      buf++;
405
406      if (*buf > 127 || (c2 = asc2bin[*buf]) == 255)
407	return 0;
408      buf++;
409
410      if (*buf == '=')
411	{
412	  c3 = c4 = 0;
413	  c++;
414
415	  /* Check last four bit */
416	  if (c2 & 0xF)
417	    return 0;
418
419	  if (!strcmp (buf, "=="))
420	    buf++;
421	  else
422	    return 0;
423	}
424      else if (*buf > 127 || (c3 = asc2bin[*buf]) == 255)
425	return 0;
426      else
427	{
428	  if (*++buf == '=')
429	    {
430	      c4 = 0;
431	      c += 2;
432
433	      /* Check last two bit */
434	      if (c3 & 3)
435		return 0;
436
437	      if (strcmp(buf, "="))
438		return 0;
439
440	    }
441	  else if (*buf > 127 || (c4 = asc2bin[*buf]) == 255)
442	      return 0;
443	  else
444	      c += 3;
445	}
446
447      buf++;
448      *out++ = (c1 << 2) | (c2 >> 4);
449      *out++ = (c2 << 4) | (c3 >> 2);
450      *out++ = (c3 << 6) | c4;
451    }
452
453  *len = c;
454  return 1;
455
456}
457
458/* Read a line from a stream to the buffer.  */
459int
460conf_get_line (FILE *stream, char *buf, u_int32_t len)
461{
462  char c;
463
464  while (len-- > 1)
465    {
466      c = fgetc (stream);
467      if (c == '\n')
468	{
469	  *buf = 0;
470	  return 1;
471	}
472      else if (c == EOF)
473	break;
474
475      *buf++ = c;
476    }
477
478  *buf = 0;
479  return 0;
480}
481
482void
483conf_free_list (struct conf_list *list)
484{
485  while (TAILQ_FIRST (&list->fields))
486    TAILQ_REMOVE (&list->fields, TAILQ_FIRST (&list->fields), link);
487  free (list);
488}
489