1/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2/* config-parser-trivial.c  XML-library-agnostic configuration file parser
3 *                  Does not do includes or anything remotely complicated.
4 *
5 * Copyright (C) 2003, 2004, 2007 Red Hat, Inc.
6 *
7 * Licensed under the Academic Free License version 2.1
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 */
24
25#include <config.h>
26#include "config-parser-common.h"
27#include "config-parser-trivial.h"
28#include "utils.h"
29#include <dbus/dbus-list.h>
30#include <dbus/dbus-internals.h>
31#include <string.h>
32
33/**
34 * TRIVIAL parser for bus configuration file.
35 */
36struct BusConfigParser
37{
38  ElementType type;
39  DBusString user;                  /**< User the dbus-daemon runs as */
40  DBusString bus_type;              /**< Message bus type */
41  DBusString service_helper;        /**< Location of the setuid helper */
42  DBusList *service_dirs;           /**< Directories to look for services in */
43};
44
45static dbus_bool_t
46service_dirs_find_dir (DBusList **service_dirs,
47                       const char *dir)
48{
49  DBusList *link;
50
51  _dbus_assert (dir != NULL);
52
53  for (link = *service_dirs; link; link = _dbus_list_get_next_link(service_dirs, link))
54    {
55      const char *link_dir;
56
57      link_dir = (const char *)link->data;
58      if (strcmp (dir, link_dir) == 0)
59        return TRUE;
60    }
61
62  return FALSE;
63}
64
65static void
66service_dirs_append_link_unique_or_free (DBusList **service_dirs,
67                                         DBusList *dir_link)
68{
69  if (!service_dirs_find_dir (service_dirs, dir_link->data))
70    {
71      _dbus_list_append_link (service_dirs, dir_link);
72    }
73  else
74    {
75      dbus_free (dir_link->data);
76      _dbus_list_free_link (dir_link);
77    }
78}
79
80BusConfigParser*
81bus_config_parser_new (const DBusString             *basedir,
82                       dbus_bool_t                   is_toplevel,
83                       const BusConfigParser        *parent)
84{
85  BusConfigParser *parser;
86
87  parser = dbus_new0 (BusConfigParser, 1);
88  if (parser == NULL)
89    goto failed;
90
91  parser->type = ELEMENT_NONE;
92
93  /* init the lists */
94  parser->service_dirs = NULL;
95
96  /* init the strings */
97  if (!_dbus_string_init (&parser->user))
98    goto failed_parser;
99  if (!_dbus_string_init (&parser->bus_type))
100    goto failed_type;
101  if (!_dbus_string_init (&parser->service_helper))
102    goto failed_helper;
103
104  /* woot! */
105  return parser;
106
107/* argh. we have do do this carefully because of OOM */
108failed_helper:
109  _dbus_string_free (&parser->bus_type);
110failed_type:
111  _dbus_string_free (&parser->user);
112failed_parser:
113  dbus_free (parser);
114failed:
115  return NULL;
116}
117
118void
119bus_config_parser_unref (BusConfigParser *parser)
120{
121  _dbus_string_free (&parser->user);
122  _dbus_string_free (&parser->service_helper);
123  _dbus_string_free (&parser->bus_type);
124
125  _dbus_list_foreach (&parser->service_dirs,
126                      (DBusForeachFunction) dbus_free,
127                      NULL);
128
129  _dbus_list_clear (&parser->service_dirs);
130
131  dbus_free (parser);
132}
133
134dbus_bool_t
135bus_config_parser_check_doctype (BusConfigParser   *parser,
136                                 const char        *doctype,
137                                 DBusError         *error)
138{
139  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
140
141  if (strcmp (doctype, "busconfig") != 0)
142    {
143      dbus_set_error (error,
144                      DBUS_ERROR_FAILED,
145                      "Configuration file has the wrong document type %s",
146                      doctype);
147      return FALSE;
148    }
149  else
150    return TRUE;
151}
152
153dbus_bool_t
154bus_config_parser_start_element (BusConfigParser   *parser,
155                                 const char        *element_name,
156                                 const char       **attribute_names,
157                                 const char       **attribute_values,
158                                 DBusError         *error)
159{
160  /* we don't do processing of attribute names, we don't need to */
161  parser->type = bus_config_parser_element_name_to_type (element_name);
162
163  switch (parser->type)
164    {
165    case ELEMENT_SERVICEHELPER:
166    case ELEMENT_USER:
167    case ELEMENT_TYPE:
168      /* content about to be handled */
169      break;
170
171    case ELEMENT_STANDARD_SYSTEM_SERVICEDIRS:
172      {
173        DBusList *link;
174        DBusList *dirs;
175        dirs = NULL;
176
177        if (!_dbus_get_standard_system_servicedirs (&dirs))
178          {
179            BUS_SET_OOM (error);
180            return FALSE;
181          }
182
183          while ((link = _dbus_list_pop_first_link (&dirs)))
184            service_dirs_append_link_unique_or_free (&parser->service_dirs, link);
185        break;
186      }
187
188    default:
189      {
190        /* we really don't care about the others... */
191        _dbus_verbose (" START We dont care about '%s' type '%i'\n", element_name, parser->type);
192        break;
193      }
194    }
195  return TRUE;
196}
197
198dbus_bool_t
199bus_config_parser_end_element (BusConfigParser   *parser,
200                               const char               *element_name,
201                               DBusError                *error)
202{
203  /* we don't care */
204  return TRUE;
205}
206
207dbus_bool_t
208bus_config_parser_content (BusConfigParser   *parser,
209                           const DBusString  *content,
210                           DBusError         *error)
211{
212  DBusString content_sane;
213  dbus_bool_t retval;
214
215  retval = FALSE;
216
217  if (!_dbus_string_init (&content_sane))
218    {
219      BUS_SET_OOM (error);
220      goto out;
221    }
222  if (!_dbus_string_copy (content, 0, &content_sane, 0))
223    {
224      BUS_SET_OOM (error);
225      goto out_content;
226    }
227
228  /* rip out white space */
229  _dbus_string_chop_white (&content_sane);
230  if (_dbus_string_get_length (&content_sane) == 0)
231    {
232      /* optimise, there is no content */
233      retval = TRUE;
234      goto out_content;
235    }
236
237  switch (parser->type)
238    {
239    case ELEMENT_SERVICEDIR:
240      {
241      	char *cpath;
242
243        /* copy the sane data into a char array */
244        if (!_dbus_string_copy_data(&content_sane, &cpath))
245          {
246            BUS_SET_OOM (error);
247            goto out_content;
248          }
249
250        /* append the dynamic char string to service dirs */
251        if (!_dbus_list_append (&parser->service_dirs, cpath))
252          {
253            dbus_free (cpath);
254            BUS_SET_OOM (error);
255            goto out_content;
256          }
257      }
258      break;
259
260    case ELEMENT_SERVICEHELPER:
261      {
262        if (!_dbus_string_copy (&content_sane, 0, &parser->service_helper, 0))
263          {
264            BUS_SET_OOM (error);
265            goto out_content;
266          }
267      }
268      break;
269
270    case ELEMENT_USER:
271      {
272        if (!_dbus_string_copy (&content_sane, 0, &parser->user, 0))
273          {
274            BUS_SET_OOM (error);
275            goto out_content;
276          }
277      }
278      break;
279
280    case ELEMENT_TYPE:
281      {
282        if (!_dbus_string_copy (&content_sane, 0, &parser->bus_type, 0))
283          {
284            BUS_SET_OOM (error);
285            goto out_content;
286          }
287      }
288      break;
289    default:
290      {
291        /* we don't care about the others... really */
292        _dbus_verbose (" CONTENTS We dont care '%s' type '%i'\n", _dbus_string_get_const_data (&content_sane), parser->type);
293        break;
294      }
295    }
296
297  /* woot! */
298  retval = TRUE;
299
300out_content:
301  _dbus_string_free (&content_sane);
302out:
303  return retval;
304}
305
306dbus_bool_t
307bus_config_parser_finished (BusConfigParser   *parser,
308                            DBusError         *error)
309{
310  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
311  _dbus_verbose ("finished scanning!\n");
312  return TRUE;
313}
314
315const char*
316bus_config_parser_get_user (BusConfigParser *parser)
317{
318  return _dbus_string_get_const_data (&parser->user);
319}
320
321const char*
322bus_config_parser_get_type (BusConfigParser *parser)
323{
324  return _dbus_string_get_const_data (&parser->bus_type);
325}
326
327DBusList**
328bus_config_parser_get_service_dirs (BusConfigParser *parser)
329{
330  return &parser->service_dirs;
331}
332
333#ifdef DBUS_BUILD_TESTS
334#include <stdio.h>
335#include "test.h"
336
337typedef enum
338{
339  VALID,
340  INVALID,
341  UNKNOWN
342} Validity;
343
344static dbus_bool_t
345check_return_values (const DBusString *full_path)
346{
347  BusConfigParser *parser;
348  DBusError error;
349  dbus_bool_t retval;
350  const char *user;
351  const char *type;
352  DBusList **dirs;
353
354  dbus_error_init (&error);
355  retval = FALSE;
356
357  printf ("Testing values from: %s\n", _dbus_string_get_const_data (full_path));
358
359  parser = bus_config_load (full_path, TRUE, NULL, &error);
360  if (parser == NULL)
361    {
362      _DBUS_ASSERT_ERROR_IS_SET (&error);
363      if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
364        _dbus_verbose ("Failed to load valid file due to OOM\n");
365      goto finish;
366    }
367  _DBUS_ASSERT_ERROR_IS_CLEAR (&error);
368
369  /* check user return value is okay */
370  user = bus_config_parser_get_user (parser);
371  if (user == NULL)
372    {
373      _dbus_warn ("User was NULL!\n");
374      goto finish;
375    }
376#if 0
377  /* the username can be configured in configure.in so this test doesn't work */
378  if (strcmp (user, "dbus") != 0)
379    {
380      _dbus_warn ("User was invalid; '%s'!\n", user);
381      goto finish;
382    }
383  printf ("    <user>dbus</user> OKAY!\n");
384#endif
385
386  /* check type return value is okay */
387  type = bus_config_parser_get_type (parser);
388  if (type == NULL)
389    {
390      _dbus_warn ("Type was NULL!\n");
391      goto finish;
392    }
393  if (strcmp (type, "system") != 0)
394    {
395      _dbus_warn ("Type was invalid; '%s'!\n", user);
396      goto finish;
397    }
398  printf ("    <type>system</type> OKAY!\n");
399
400  /* check dirs return value is okay */
401  dirs = bus_config_parser_get_service_dirs (parser);
402  if (dirs == NULL)
403    {
404      _dbus_warn ("Service dirs are NULL!\n");
405      goto finish;
406    }
407  printf ("    <standard_system_service_dirs/> OKAY!\n");
408  /* NOTE: We tested the specific return values in the config-parser tests */
409
410  /* woohoo! */
411  retval = TRUE;
412finish:
413  if (parser != NULL)
414    bus_config_parser_unref (parser);
415  dbus_error_free (&error);
416  return retval;
417}
418
419static dbus_bool_t
420do_load (const DBusString *full_path,
421         Validity          validity,
422         dbus_bool_t       oom_possible)
423{
424  BusConfigParser *parser;
425  DBusError error;
426
427  dbus_error_init (&error);
428
429  parser = bus_config_load (full_path, TRUE, NULL, &error);
430  if (parser == NULL)
431    {
432      _DBUS_ASSERT_ERROR_IS_SET (&error);
433
434      if (oom_possible &&
435          dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
436        {
437          _dbus_verbose ("Failed to load valid file due to OOM\n");
438          dbus_error_free (&error);
439          return TRUE;
440        }
441      else if (validity == VALID)
442        {
443          _dbus_warn ("Failed to load valid file but still had memory: %s\n",
444                      error.message);
445
446          dbus_error_free (&error);
447          return FALSE;
448        }
449      else
450        {
451          dbus_error_free (&error);
452          return TRUE;
453        }
454    }
455  else
456    {
457      _DBUS_ASSERT_ERROR_IS_CLEAR (&error);
458
459      bus_config_parser_unref (parser);
460
461      if (validity == INVALID)
462        {
463          _dbus_warn ("Accepted invalid file\n");
464          return FALSE;
465        }
466
467      return TRUE;
468    }
469}
470
471typedef struct
472{
473  const DBusString *full_path;
474  Validity          validity;
475} LoaderOomData;
476
477static dbus_bool_t
478check_loader_oom_func (void *data)
479{
480  LoaderOomData *d = data;
481
482  return do_load (d->full_path, d->validity, TRUE);
483}
484
485static dbus_bool_t
486process_test_valid_subdir (const DBusString *test_base_dir,
487                           const char       *subdir,
488                           Validity          validity)
489{
490  DBusString test_directory;
491  DBusString filename;
492  DBusDirIter *dir;
493  dbus_bool_t retval;
494  DBusError error;
495
496  retval = FALSE;
497  dir = NULL;
498
499  if (!_dbus_string_init (&test_directory))
500    _dbus_assert_not_reached ("didn't allocate test_directory\n");
501
502  _dbus_string_init_const (&filename, subdir);
503
504  if (!_dbus_string_copy (test_base_dir, 0,
505                          &test_directory, 0))
506    _dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory");
507
508  if (!_dbus_concat_dir_and_file (&test_directory, &filename))
509    _dbus_assert_not_reached ("couldn't allocate full path");
510
511  _dbus_string_free (&filename);
512  if (!_dbus_string_init (&filename))
513    _dbus_assert_not_reached ("didn't allocate filename string\n");
514
515  dbus_error_init (&error);
516  dir = _dbus_directory_open (&test_directory, &error);
517  if (dir == NULL)
518    {
519      _dbus_warn ("Could not open %s: %s\n",
520                  _dbus_string_get_const_data (&test_directory),
521                  error.message);
522      dbus_error_free (&error);
523      goto failed;
524    }
525
526  if (validity == VALID)
527    printf ("Testing valid files:\n");
528  else if (validity == INVALID)
529    printf ("Testing invalid files:\n");
530  else
531    printf ("Testing unknown files:\n");
532
533 next:
534  while (_dbus_directory_get_next_file (dir, &filename, &error))
535    {
536      DBusString full_path;
537      LoaderOomData d;
538
539      if (!_dbus_string_init (&full_path))
540        _dbus_assert_not_reached ("couldn't init string");
541
542      if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
543        _dbus_assert_not_reached ("couldn't copy dir to full_path");
544
545      if (!_dbus_concat_dir_and_file (&full_path, &filename))
546        _dbus_assert_not_reached ("couldn't concat file to dir");
547
548      if (!_dbus_string_ends_with_c_str (&full_path, ".conf"))
549        {
550          _dbus_verbose ("Skipping non-.conf file %s\n",
551                         _dbus_string_get_const_data (&filename));
552          _dbus_string_free (&full_path);
553          goto next;
554        }
555
556      printf ("    %s\n", _dbus_string_get_const_data (&filename));
557
558      _dbus_verbose (" expecting %s\n",
559                     validity == VALID ? "valid" :
560                     (validity == INVALID ? "invalid" :
561                      (validity == UNKNOWN ? "unknown" : "???")));
562
563      d.full_path = &full_path;
564      d.validity = validity;
565
566      /* FIXME hackaround for an expat problem, see
567       * https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=124747
568       * http://freedesktop.org/pipermail/dbus/2004-May/001153.html
569       */
570      /* if (!_dbus_test_oom_handling ("config-loader", check_loader_oom_func, &d)) */
571      if (!check_loader_oom_func (&d))
572        _dbus_assert_not_reached ("test failed");
573
574      _dbus_string_free (&full_path);
575    }
576
577  if (dbus_error_is_set (&error))
578    {
579      _dbus_warn ("Could not get next file in %s: %s\n",
580                  _dbus_string_get_const_data (&test_directory),
581                  error.message);
582      dbus_error_free (&error);
583      goto failed;
584    }
585
586  retval = TRUE;
587
588 failed:
589
590  if (dir)
591    _dbus_directory_close (dir);
592  _dbus_string_free (&test_directory);
593  _dbus_string_free (&filename);
594
595  return retval;
596}
597
598/* convenience function, do not reuse outside of TEST */
599static dbus_bool_t
600make_full_path (const DBusString *test_data_dir,
601			          const char       *subdir,
602			          const char       *file,
603			          DBusString       *full_path)
604{
605  DBusString filename;
606  dbus_bool_t retval;
607
608  retval = FALSE;
609
610  if (!_dbus_string_init (full_path))
611    {
612      _dbus_warn ("couldn't allocate full path");
613      goto finish;
614    }
615
616  if (!_dbus_string_copy (test_data_dir, 0, full_path, 0))
617    {
618      _dbus_warn ("couldn't allocate full path");
619      goto finish;
620    }
621
622  _dbus_string_init_const (&filename, subdir);
623  if (!_dbus_concat_dir_and_file (full_path, &filename))
624    {
625      _dbus_warn ("couldn't allocate full path");
626      goto finish;
627    }
628  _dbus_string_free (&filename);
629
630  _dbus_string_init_const (&filename, file);
631  if (!_dbus_concat_dir_and_file (full_path, &filename))
632    {
633      _dbus_warn ("couldn't allocate full path");
634      goto finish;
635    }
636
637  /* woot! */
638  retval = TRUE;
639
640finish:
641  _dbus_string_free (&filename);
642  return retval;
643}
644
645static dbus_bool_t
646check_file_valid (DBusString *full_path,
647			            Validity    validity)
648{
649  dbus_bool_t retval;
650
651  if (validity == VALID)
652    printf ("Testing valid file:\n");
653  else if (validity == INVALID)
654    printf ("Testing invalid file:\n");
655  else
656    printf ("Testing unknown file:\n");
657
658  /* print the filename, just so we match the other output */
659  printf ("    %s\n", _dbus_string_get_const_data (full_path));
660
661  /* only test one file */
662  retval = do_load (full_path, validity, TRUE);
663
664  return retval;
665}
666
667dbus_bool_t
668bus_config_parser_trivial_test (const DBusString *test_data_dir)
669{
670  DBusString full_path;
671  dbus_bool_t retval;
672
673  retval = FALSE;
674
675  if (test_data_dir == NULL ||
676      _dbus_string_get_length (test_data_dir) == 0)
677    {
678      printf ("No test data\n");
679      return TRUE;
680    }
681
682  /* We already test default_session_servicedirs and default_system_servicedirs
683   * in bus_config_parser_test() */
684  if (!process_test_valid_subdir (test_data_dir, "valid-config-files", VALID))
685    goto finish;
686
687  /* we don't process all the invalid files, as the trivial parser can't hope
688   * to validate them all for all different syntaxes. We just check one broken
689   * file to see if junk is received */
690  if (!make_full_path (test_data_dir, "invalid-config-files", "not-well-formed.conf", &full_path))
691    goto finish;
692  if (!check_file_valid (&full_path, INVALID))
693    goto finish;
694  _dbus_string_free (&full_path);
695
696  /* just test if the check_file_valid works okay and we got sane values */
697  if (!make_full_path (test_data_dir, "valid-config-files", "system.conf", &full_path))
698    goto finish;
699  if (!check_file_valid (&full_path, VALID))
700    goto finish;
701  /* check to see if we got the correct values from the parser */
702  if (!check_return_values (&full_path))
703    goto finish;
704
705  /* woot! */
706  retval = TRUE;
707
708finish:
709  _dbus_string_free (&full_path);
710
711  /* we don't process equiv-config-files as we don't handle <include> */
712  return retval;
713}
714
715#endif /* DBUS_BUILD_TESTS */
716
717