1 /*
2  * conf.c - Configuration text file parsing definition
3  */
4 
5 /***********************************************************************
6  *  Copyright © 2004-2006 Rémi Denis-Courmont.                         *
7  *  This program is free software; you can redistribute and/or modify  *
8  *  it under the terms of the GNU General Public License as published  *
9  *  by the Free Software Foundation; version 2 of the license, or (at  *
10  *  your option) any later version.                                    *
11  *                                                                     *
12  *  This program is distributed in the hope that it will be useful,    *
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of     *
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               *
15  *  See the GNU General Public License for more details.               *
16  *                                                                     *
17  *  You should have received a copy of the GNU General Public License  *
18  *  along with this program; if not, you can get it from:              *
19  *  http://www.gnu.org/copyleft/gpl.html                               *
20  ***********************************************************************/
21 
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25 
26 #include <gettext.h>
27 #include <assert.h>
28 
29 #include <stdio.h>
30 #include <stdlib.h> // malloc(), free()
31 #include <stdarg.h>
32 #include <inttypes.h>
33 #include <string.h>
34 #include <stdbool.h>
35 
36 #include <errno.h>
37 #include <syslog.h>
38 
39 #include <sys/types.h>
40 #include <sys/socket.h> // AF_INET, SOCK_DGRAM
41 #include <netinet/in.h>
42 #include <netdb.h>
43 #include <libteredo/teredo.h>
44 
45 #include "miredo.h"
46 #include "conf.h"
47 
48 struct setting
49 {
50 	char *name;
51 	char *value;
52 	unsigned line;
53 	struct setting *next;
54 };
55 
56 
57 struct miredo_conf
58 {
59 	struct setting *head, *tail;
60 	miredo_conf_logger logger;
61 	void *logger_data;
62 };
63 
64 
miredo_conf_create(miredo_conf_logger logger,void * opaque)65 miredo_conf *miredo_conf_create (miredo_conf_logger logger, void *opaque)
66 {
67 	miredo_conf *conf = (miredo_conf *)malloc (sizeof (*conf));
68 	if (conf == NULL)
69 		return NULL;
70 
71 	conf->head = conf->tail = NULL;
72 	conf->logger = logger;
73 	conf->logger_data = opaque;
74 	return conf;
75 }
76 
77 
miredo_conf_destroy(miredo_conf * conf)78 void miredo_conf_destroy (miredo_conf *conf)
79 {
80 	assert (conf != NULL);
81 	miredo_conf_clear (conf, 0);
82 	free (conf);
83 }
84 
85 
86 static
87 #ifdef __GNUC__
88 __attribute__ ((format (printf, 2, 3)))
89 #endif
90 void
LogError(miredo_conf * conf,const char * fmt,...)91 LogError (miredo_conf *conf, const char *fmt, ...)
92 {
93 	assert (conf != NULL);
94 	assert (fmt != NULL);
95 
96 	if (conf->logger == NULL)
97 		return;
98 
99 	va_list ap;
100 
101 	va_start (ap, fmt);
102 	conf->logger (conf->logger_data, true, fmt, ap);
103 	va_end (ap);
104 }
105 
106 
107 static void
108 #ifdef __GNUC__
109 __attribute__ ((format (printf, 2, 3)))
110 #endif
LogWarning(miredo_conf * conf,const char * fmt,...)111 LogWarning (miredo_conf *conf, const char *fmt, ...)
112 {
113 	assert (conf != NULL);
114 	assert (fmt != NULL);
115 
116 	if (conf->logger == NULL)
117 		return;
118 
119 	va_list ap;
120 
121 	va_start (ap, fmt);
122 	conf->logger (conf->logger_data, false, fmt, ap);
123 	va_end (ap);
124 }
125 
126 
miredo_conf_clear(miredo_conf * conf,int show)127 void miredo_conf_clear (miredo_conf *conf, int show)
128 {
129 	/* lock here */
130 	struct setting *ptr = conf->head;
131 
132 	conf->head = NULL;
133 	/* unlock here */
134 
135 	while (ptr != NULL)
136 	{
137 		struct setting *buf = ptr->next;
138 		if (show > 0)
139 		{
140 			LogWarning (conf, _("Superfluous directive %s at line %u"),
141 			            ptr->name, ptr->line);
142 			show--;
143 		}
144 		free (ptr->name);
145 		free (ptr->value);
146 		free (ptr);
147 		ptr = buf;
148 	}
149 }
150 
151 
152 /**
153  * Adds a setting.
154  * @return false if memory is missing.
155  */
156 static bool
miredo_conf_set(miredo_conf * conf,const char * name,const char * value,unsigned line)157 miredo_conf_set (miredo_conf *conf, const char *name, const char *value,
158                  unsigned line)
159 {
160 	assert (conf != NULL);
161 	assert (name != NULL);
162 	assert (value != NULL);
163 
164 	struct setting *parm =
165 		(struct setting *)malloc (sizeof (struct setting));
166 
167 	if (parm != NULL)
168 	{
169 		parm->name = strdup (name);
170 		if (parm->name != NULL)
171 		{
172 			parm->value = strdup (value);
173 			if (parm->value != NULL)
174 			{
175 				parm->line = line;
176 				parm->next = NULL;
177 
178 				/* lock here */
179 				if (conf->head == NULL)
180 					conf->head = parm;
181 				else
182 				{
183 					assert (conf->tail != NULL);
184 					conf->tail->next = parm;
185 				}
186 				conf->tail = parm;
187 				/* unlock here */
188 
189 				return true;
190 			}
191 			free (parm->name);
192 		}
193 		free (parm);
194 	}
195 
196 	LogError (conf, _("Error (%s): %s"), "strdup", strerror (errno));
197 	return false;
198 }
199 
200 
201 /*
202  * Looks up a setting by name.
203  * @return NULL if not found.
204  * Otherwise, return value must be free()d by caller.
205  */
miredo_conf_get(miredo_conf * conf,const char * name,unsigned * line)206 char *miredo_conf_get (miredo_conf *conf, const char *name, unsigned *line)
207 {
208 	for (struct setting *p = conf->head, *prev = NULL; p != NULL; p = p->next)
209 	{
210 		if (strcasecmp (p->name, name) == 0)
211 		{
212 			char *buf = p->value;
213 
214 			if (line != NULL)
215 				*line = p->line;
216 
217 			if (prev != NULL)
218 				prev->next = p->next;
219 			else
220 				conf->head = p->next;
221 
222 			free (p->name);
223 			free (p);
224 			return buf;
225 		}
226 		prev = p;
227 	}
228 
229 	return NULL;
230 }
231 
232 
miredo_conf_read_FILE(miredo_conf * conf,FILE * stream)233 static bool miredo_conf_read_FILE (miredo_conf *conf, FILE *stream)
234 {
235 	char lbuf[1056];
236 	unsigned line = 0;
237 
238 	while (fgets (lbuf, sizeof (lbuf), stream) != NULL)
239 	{
240 		size_t len = strlen (lbuf) - 1;
241 		line++;
242 
243 		if (lbuf[len] != '\n')
244 		{
245 			while (fgetc (stream) != '\n')
246 				if (feof (stream) || ferror (stream))
247 					break;
248 
249 			LogWarning (conf, _("Skipped overly long line %u"), line);
250 			continue;
251 		}
252 
253 		lbuf[len] = '\0';
254 		char nbuf[32], vbuf[1024];
255 
256 		switch (sscanf (lbuf, " %31s %1023s", nbuf, vbuf))
257 		{
258 			case 2:
259 				if ((*nbuf != '#') // comment
260 				 && !miredo_conf_set (conf, nbuf, vbuf, line))
261 					return false;
262 				break;
263 
264 			case 1:
265 				if (*nbuf != '#')
266 					LogWarning (conf, _("Ignoring line %u: %s"),
267 					            line, nbuf);
268 				break;
269 		}
270 	}
271 
272 	if (ferror (stream))
273 	{
274 		LogError (conf, _("Error reading configuration file: %s"),
275 		          strerror (errno));
276 		return false;
277 	}
278 	return true;
279 }
280 
281 
282 /* Parses a file.
283  *
284  * @return false on I/O error, true on success.
285  */
miredo_conf_read_file(miredo_conf * conf,const char * path)286 bool miredo_conf_read_file (miredo_conf *conf, const char *path)
287 {
288 	assert (path != NULL);
289 
290 	FILE *stream = fopen (path, "r");
291 	if (stream != NULL)
292 	{
293 		bool ret = miredo_conf_read_FILE (conf, stream);
294 		fclose (stream);
295 		return ret;
296 	}
297 
298 	LogError (conf, _("Error opening configuration file %s: %s"), path,
299 	          strerror (errno));
300 	return false;
301 }
302 
303 
304 /**
305  * Looks up an unsigned 16-bits integer. Returns false if the
306  * setting was found but incorrectly formatted.
307  *
308  * If the setting was not found value, returns true and leave
309  * *value unchanged.
310  */
miredo_conf_get_int16(miredo_conf * conf,const char * name,uint16_t * value,unsigned * line)311 bool miredo_conf_get_int16 (miredo_conf *conf, const char *name,
312                             uint16_t *value, unsigned *line)
313 {
314 	char *val = miredo_conf_get (conf, name, line);
315 
316 	if (val == NULL)
317 		return true;
318 
319 	char *end;
320 	unsigned long l;
321 
322 	l = strtoul (val, &end, 0);
323 
324 	if ((*end) || (l > 65535))
325 	{
326 		LogError (conf, _("Invalid integer value \"%s\" for %s: %s"),
327 		          val, name, strerror (errno));
328 		free (val);
329 		return false;
330 	}
331 	*value = (uint16_t)l;
332 	free (val);
333 	return true;
334 }
335 
336 
337 #if 0
338 /* This is supposedly bad for DSO (but we are not a DSO atm) */
339 static const char *true_strings[] = { "yes", "true", "on", "enabled", NULL };
340 static const char *false_strings[] =
341 	{ "no", "false", "off", "disabled", NULL };
342 
343 bool miredo_conf_get_bool (miredo_conf *conf, const char *name,
344                            bool *value, unsigned *line)
345 {
346 	char *val = miredo_conf_get (conf, name, line);
347 
348 	if (val == NULL)
349 		return true;
350 	else
351 	{
352 		// check if value is a number
353 		long l;
354 		char *end;
355 
356 		l = strtol (val, &end, 0);
357 
358 		if (*end == '\0') // success
359 		{
360 			*value = (l != 0);
361 			free (val);
362 			return true;
363 		}
364 	}
365 
366 	for (const char **ptr = true_strings; *ptr != NULL; ptr++)
367 		if (!strcasecmp (val, *ptr))
368 		{
369 			*value = true;
370 			free (val);
371 			return true;
372 		}
373 
374 	for (const char **ptr = false_strings; *ptr != NULL; ptr++)
375 		if (!strcasecmp (val, *ptr))
376 		{
377 			*value = false;
378 			free (val);
379 			return true;
380 		}
381 
382 	LogError (conf, _("Invalid boolean value \"%s\" for %s"), val, name);
383 	free (val);
384 	return false;
385 }
386 #endif
387 
388 /* Utilities function */
389 
390 /**
391  * Looks up an IPv4 address (network byte order) associated with hostname.
392  */
GetIPv4ByName(const char * hostname,uint32_t * ipv4)393 int GetIPv4ByName (const char *hostname, uint32_t *ipv4)
394 {
395 	struct addrinfo help =
396 	{
397 		.ai_family = AF_INET,
398 		.ai_socktype = SOCK_DGRAM,
399 		.ai_protocol = IPPROTO_UDP
400 	}, *res;
401 
402 	int check = getaddrinfo (hostname, NULL, &help, &res);
403 	if (check)
404 		return check;
405 
406 	*ipv4 = ((const struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr;
407 	freeaddrinfo (res);
408 	return 0;
409 }
410 
411 
miredo_conf_parse_IPv4(miredo_conf * conf,const char * name,uint32_t * ipv4)412 bool miredo_conf_parse_IPv4 (miredo_conf *conf, const char *name,
413                              uint32_t *ipv4)
414 {
415 	unsigned line;
416 	char *val = miredo_conf_get (conf, name, &line);
417 
418 	if (val == NULL)
419 		return true;
420 
421 	int check = GetIPv4ByName (val, ipv4);
422 
423 	if (check)
424 	{
425 		LogError (conf, _("Invalid hostname \"%s\" at line %u: %s"),
426 		          val, line, gai_strerror (check));
427 		free (val);
428 		return false;
429 	}
430 
431 	free (val);
432 	return true;
433 }
434 
435 
miredo_conf_parse_IPv6(miredo_conf * conf,const char * name,struct in6_addr * value)436 bool miredo_conf_parse_IPv6 (miredo_conf *conf, const char *name,
437                              struct in6_addr *value)
438 {
439 	unsigned line;
440 	char *val = miredo_conf_get (conf, name, &line);
441 
442 	if (val == NULL)
443 		return true;
444 
445 	struct addrinfo help =
446 	{
447 		.ai_family = AF_INET6,
448 		.ai_socktype = SOCK_DGRAM,
449 		.ai_protocol = IPPROTO_UDP
450 	}, *res;
451 
452 	int check = getaddrinfo (val, NULL, &help, &res);
453 
454 	if (check)
455 	{
456 		LogError (conf, _("Invalid hostname \"%s\" at line %u: %s"),
457 		          val, line, gai_strerror (check));
458 		free (val);
459 		return false;
460 	}
461 
462 	memcpy (value, &((const struct sockaddr_in6*)(res->ai_addr))->sin6_addr,
463 	        sizeof (struct in6_addr));
464 
465 	freeaddrinfo (res);
466 	free (val);
467 	return true;
468 }
469 
470 
miredo_conf_parse_teredo_prefix(miredo_conf * conf,const char * name,uint32_t * value)471 bool miredo_conf_parse_teredo_prefix (miredo_conf *conf, const char *name,
472                                       uint32_t *value)
473 {
474 	union teredo_addr addr;
475 	memset (&addr, 0, sizeof (addr));
476 	addr.teredo.prefix = *value;
477 
478 	if (miredo_conf_parse_IPv6 (conf, name, &addr.ip6))
479 	{
480 		if (!is_valid_teredo_prefix (addr.teredo.prefix))
481 		{
482 			LogError (conf, _("Invalid Teredo IPv6 prefix: %x::/32"),
483 			          addr.teredo.prefix);
484 			return false;
485 		}
486 
487 		*value = addr.teredo.prefix;
488 		return true;
489 	}
490 	return false;
491 }
492 
493 
494 static const struct miredo_conf_syslog_facility
495 {
496 	const char *str;
497 	int facility;
498 } facilities[] =
499 {
500 #ifdef LOG_AUTH
501 	{ "auth",	LOG_AUTH },
502 #endif
503 #ifdef LOG_AUTHPRIV
504 	{ "authpriv",	LOG_AUTHPRIV },
505 #endif
506 #ifdef LOG_CRON
507 	{ "cron",	LOG_CRON },
508 #endif
509 #ifdef LOG_DAEMON
510 	{ "daemon",	LOG_DAEMON },
511 #endif
512 #ifdef LOG_FTP
513 	{ "ftp",	LOG_FTP },
514 #endif
515 #ifdef LOG_KERN
516 	{ "kern",	LOG_KERN },
517 #endif
518 	{ "local0",	LOG_LOCAL0 },
519 	{ "local1",	LOG_LOCAL1 },
520 	{ "local2",	LOG_LOCAL2 },
521 	{ "local3",	LOG_LOCAL3 },
522 	{ "local4",	LOG_LOCAL4 },
523 	{ "local5",	LOG_LOCAL5 },
524 	{ "local6",	LOG_LOCAL6 },
525 	{ "local7",	LOG_LOCAL7 },
526 #ifdef LOG_LPR
527 	{ "lpr",	LOG_LPR },
528 #endif
529 #ifdef LOG_MAIL
530 	{ "mail",	LOG_MAIL },
531 #endif
532 #ifdef LOG_NEWS
533 	{ "news",	LOG_NEWS },
534 #endif
535 #ifdef LOG_SYSLOG
536 	{ "syslog",	LOG_SYSLOG },
537 #endif
538 	{ "user",	LOG_USER },
539 #ifdef LOG_UUCP
540 	{ "uucp",	LOG_UUCP },
541 #endif
542 	{ NULL,		0 }
543 };
544 
545 
miredo_conf_parse_syslog_facility(miredo_conf * conf,const char * name,int * facility)546 bool miredo_conf_parse_syslog_facility (miredo_conf *conf, const char *name,
547                                         int *facility)
548 {
549 	unsigned line;
550 	char *str = miredo_conf_get (conf, name, &line);
551 
552 	if (str == NULL)
553 		return true;
554 
555 	for (const struct miredo_conf_syslog_facility *ptr = facilities;
556 	     ptr->str != NULL; ptr++)
557 	{
558 		if (!strcasecmp (str, ptr->str))
559 		{
560 			*facility = ptr->facility;
561 			free (str);
562 			return true;
563 		}
564 	}
565 
566 	LogError (conf, _("Unknown syslog facility \"%s\" at line %u"), str,
567 	          line);
568 	free (str);
569 	return false;
570 }
571