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