Clarify license declaration
[platform/upstream/dbus.git] / bus / config-loader-expat.c
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* config-loader-expat.c  expat XML loader
3  *
4  * Copyright (C) 2003 Red Hat, Inc.
5  *
6  * Licensed under the Academic Free License version 2.1
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  *
22  */
23
24 #include <config.h>
25 #include "config-parser.h"
26 #include <dbus/dbus-internals.h>
27 #include <expat.h>
28
29 static XML_Memory_Handling_Suite memsuite;
30
31 typedef struct
32 {
33   BusConfigParser *parser;
34   const char *filename;
35   DBusString content;
36   DBusError *error;
37   dbus_bool_t failed;
38 } ExpatParseContext;
39
40 static dbus_bool_t
41 process_content (ExpatParseContext *context)
42 {
43   if (context->failed)
44     return FALSE;
45
46   if (_dbus_string_get_length (&context->content) > 0)
47     {
48       if (!bus_config_parser_content (context->parser,
49                                       &context->content,
50                                       context->error))
51         {
52           context->failed = TRUE;
53           return FALSE;
54         }
55       _dbus_string_set_length (&context->content, 0);
56     }
57
58   return TRUE;
59 }
60
61 static void
62 expat_StartElementHandler (void            *userData,
63                            const XML_Char  *name,
64                            const XML_Char **atts)
65 {
66   ExpatParseContext *context = userData;
67   int i;
68   char **names;
69   char **values;
70
71   /* Expat seems to suck and can't abort the parse if we
72    * throw an error. Expat 2.0 is supposed to fix this.
73    */
74   if (context->failed)
75     return;
76
77   if (!process_content (context))
78     return;
79
80   /* "atts" is key, value, key, value, NULL */
81   for (i = 0; atts[i] != NULL; ++i)
82     ; /* nothing */
83
84   _dbus_assert (i % 2 == 0);
85   names = dbus_new0 (char *, i / 2 + 1);
86   values = dbus_new0 (char *, i / 2 + 1);
87
88   if (names == NULL || values == NULL)
89     {
90       dbus_set_error (context->error, DBUS_ERROR_NO_MEMORY, NULL);
91       context->failed = TRUE;
92       dbus_free (names);
93       dbus_free (values);
94       return;
95     }
96
97   i = 0;
98   while (atts[i] != NULL)
99     {
100       _dbus_assert (i % 2 == 0);
101       names [i / 2] = (char*) atts[i];
102       values[i / 2] = (char*) atts[i+1];
103
104       i += 2;
105     }
106
107   if (!bus_config_parser_start_element (context->parser,
108                                         name,
109                                         (const char **) names,
110                                         (const char **) values,
111                                         context->error))
112     {
113       dbus_free (names);
114       dbus_free (values);
115       context->failed = TRUE;
116       return;
117     }
118
119   dbus_free (names);
120   dbus_free (values);
121 }
122
123 static void
124 expat_EndElementHandler (void           *userData,
125                          const XML_Char *name)
126 {
127   ExpatParseContext *context = userData;
128
129   if (!process_content (context))
130     return;
131
132   if (!bus_config_parser_end_element (context->parser,
133                                       name,
134                                       context->error))
135     {
136       context->failed = TRUE;
137       return;
138     }
139 }
140
141 /* s is not 0 terminated. */
142 static void
143 expat_CharacterDataHandler (void           *userData,
144                             const XML_Char *s,
145                             int             len)
146 {
147   ExpatParseContext *context = userData;
148   if (context->failed)
149     return;
150
151   if (!_dbus_string_append_len (&context->content,
152                                 s, len))
153     {
154       dbus_set_error (context->error, DBUS_ERROR_NO_MEMORY, NULL);
155       context->failed = TRUE;
156       return;
157     }
158 }
159
160
161 BusConfigParser*
162 bus_config_load (const DBusString      *file,
163                  dbus_bool_t            is_toplevel,
164                  const BusConfigParser *parent,
165                  DBusError             *error)
166 {
167   XML_Parser expat;
168   const char *filename;
169   BusConfigParser *parser;
170   ExpatParseContext context;
171   DBusString dirname;
172   
173   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
174
175   parser = NULL;
176   expat = NULL;
177   context.error = error;
178   context.failed = FALSE;
179
180   filename = _dbus_string_get_const_data (file);
181
182   if (!_dbus_string_init (&context.content))
183     {
184       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
185       return NULL;
186     }
187
188   if (!_dbus_string_init (&dirname))
189     {
190       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
191       _dbus_string_free (&context.content);
192       return NULL;
193     }
194
195   memsuite.malloc_fcn = dbus_malloc;
196   memsuite.realloc_fcn = dbus_realloc;
197   memsuite.free_fcn = dbus_free;
198
199   expat = XML_ParserCreate_MM ("UTF-8", &memsuite, NULL);
200   if (expat == NULL)
201     {
202       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
203       goto failed;
204     }
205
206   /* We do not need protection against hash collisions (CVE-2012-0876)
207    * because we are only parsing trusted XML; and if we let Expat block
208    * waiting for the CSPRNG to be initialized, as it does by default to
209    * defeat CVE-2012-0876, it can cause timeouts during early boot on
210    * entropy-starved embedded devices.
211    *
212    * TODO: When Expat gets a more explicit API for this than
213    * XML_SetHashSalt, check for that too, and use it preferentially.
214    * https://github.com/libexpat/libexpat/issues/91 */
215 #if defined(HAVE_XML_SETHASHSALT)
216   /* Any nonzero number will do. https://xkcd.com/221/ */
217   XML_SetHashSalt (expat, 4);
218 #endif
219
220   if (!_dbus_string_get_dirname (file, &dirname))
221     {
222       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
223       goto failed;
224     }
225   
226   parser = bus_config_parser_new (&dirname, is_toplevel, parent);
227   if (parser == NULL)
228     {
229       dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
230       goto failed;
231     }
232   context.parser = parser;
233
234   XML_SetUserData (expat, &context);
235   XML_SetElementHandler (expat,
236                          expat_StartElementHandler,
237                          expat_EndElementHandler);
238   XML_SetCharacterDataHandler (expat,
239                                expat_CharacterDataHandler);
240
241   {
242     DBusString data;
243     const char *data_str;
244
245     if (!_dbus_string_init (&data))
246       {
247         dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
248         goto failed;
249       }
250
251     if (!_dbus_file_get_contents (&data, file, error))
252       {
253         _dbus_string_free (&data);
254         goto failed;
255       }
256
257     data_str = _dbus_string_get_const_data (&data);
258
259     if (XML_Parse (expat, data_str, _dbus_string_get_length (&data), TRUE) == XML_STATUS_ERROR)
260       {
261         if (context.error != NULL &&
262             !dbus_error_is_set (context.error))
263           {
264             enum XML_Error e;
265
266             e = XML_GetErrorCode (expat);
267             if (e == XML_ERROR_NO_MEMORY)
268               dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
269             else
270               dbus_set_error (error, DBUS_ERROR_FAILED,
271                               "Error in file %s, line %lu, column %lu: %s\n",
272                               filename,
273                               /* The XML_Size type varies according to
274                                * build options, so cast to something we can
275                                * cope with. */
276                               (unsigned long) XML_GetCurrentLineNumber (expat),
277                               (unsigned long) XML_GetCurrentColumnNumber (expat),
278                               XML_ErrorString (e));
279           }
280
281         _dbus_string_free (&data);
282         goto failed;
283       }
284
285     _dbus_string_free (&data);
286
287     if (context.failed)
288       goto failed;
289   }
290
291   if (!bus_config_parser_finished (parser, error))
292     goto failed;
293
294   _dbus_string_free (&dirname);
295   _dbus_string_free (&context.content);
296   XML_ParserFree (expat);
297
298   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
299   return parser;
300
301  failed:
302   _DBUS_ASSERT_ERROR_IS_SET (error);
303
304   _dbus_string_free (&dirname);
305   _dbus_string_free (&context.content);
306   if (expat)
307     XML_ParserFree (expat);
308   if (parser)
309     bus_config_parser_unref (parser);
310   return NULL;
311 }