1 /* $NetBSD: parser.c,v 1.5 2015/08/08 12:34:33 shm Exp $ */
2 
3 /* Copyright (c) 2010 The NetBSD Foundation, Inc.
4  * All rights reserved.
5  *
6  * This code is derived from software contributed to The NetBSD Foundation
7  * by Mateusz Kocielski.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *  	  This product includes software developed by the NetBSD
20  *  	  Foundation, Inc. and its contributors.
21  * 4. Neither the name of The NetBSD Foundation nor the names of its
22  *    contributors may be used to endorse or promote products derived
23  *    from this software without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
26  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28  * PURPOSE ARE DISCLAIMED.	IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
29  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  * POSSIBILITY OF SUCH DAMAGE.
36  */
37 #include <sys/cdefs.h>
38 __RCSID("$NetBSD: parser.c,v 1.5 2015/08/08 12:34:33 shm Exp $");
39 
40 #include <sys/stat.h>
41 #include <sys/syslimits.h>	/* for PATH_MAX */
42 
43 #include <ctype.h>
44 #include <err.h>
45 #include <errno.h>
46 #include <saslc.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 
51 #include "dict.h"
52 #include "msg.h"
53 #include "parser.h"
54 #include "saslc_private.h"
55 
56 #define SASLC__COMMENT_CHAR	'#'
57 
58 /* config file location defines */
59 #define SASLC__CONFIG_PATH		"/etc/saslc.d"
60 #define SASLC__CONFIG_MAIN_FILE		"saslc"
61 #define SASLC__CONFIG_MECH_DIRECTORY	"mech"
62 #define SASLC__CONFIG_SUFFIX		".conf"
63 #define SASLC__DEFAULT_APPNAME		"saslc"
64 
65 /* token types */
66 enum {
67 	TOKEN_KEY,		/* option (key) */
68 	TOKEN_STRING,		/* quoted string */
69 	TOKEN_NUM,		/* number */
70 	TOKEN_COMMENT,		/* comment character */
71 	TOKEN_UNKNOWN		/* unknown */
72 };
73 
74 /* token structure */
75 typedef struct saslc__token_t {
76 	int type;		/**< token type */
77 	char *val;		/**< token string value */
78 } saslc__token_t;
79 
80 static inline char *
skip_WS(char * p)81 skip_WS(char *p)
82 {
83 
84 	while (*p == ' ' || *p == '\t')
85 		p++;
86 	return p;
87 }
88 
89 /**
90  * @brief gets token from string c and updates pointer position.
91  * @param c pointer to string
92  * @return token on success, NULL on failure (e.g. at end of string).
93  * On success, c is updated to point on next token.  It's position is
94  * undefined on failure.
95  *
96  * Note: A legal key begins with an isalpha(3) character and is
97  * followed by isalnum(3) or '_' characters.
98  */
99 static saslc__token_t *
saslc__parse_get_token(char ** c)100 saslc__parse_get_token(char **c)
101 {
102 	saslc__token_t *token;
103 	char *e;
104 
105 	*c = skip_WS(*c);
106 	if (**c == '\0')
107 		return NULL;
108 
109 	if ((token = calloc(1, sizeof(*token))) == NULL)
110 		return NULL;
111 
112 	token->val = *c;
113 
114 	if (**c == SASLC__COMMENT_CHAR)
115 		token->type = TOKEN_COMMENT;
116 
117 	else if (**c == '\"')
118 		token->type = TOKEN_STRING;
119 
120 	else if (isdigit((unsigned char)**c))
121 		token->type = TOKEN_NUM;
122 
123 	else if (isalpha((unsigned char)**c))
124 		token->type = TOKEN_KEY;
125 
126 	else
127 		token->type = TOKEN_UNKNOWN;
128 
129 	switch (token->type) {
130 	case TOKEN_COMMENT:
131 		break;
132 	case TOKEN_NUM:
133 		errno = 0;
134 		(void)strtoll(*c, &e, 0);
135 		if (errno != 0)
136 			goto err;
137 		*c = e;
138 		break;
139 	case TOKEN_KEY:
140 		(*c)++;
141 		while (isalnum((unsigned char)**c) || **c == '_')
142 			(*c)++;
143 		break;
144 	case TOKEN_STRING:
145 		token->val++;	/* skip initial '\"' */
146 		(*c)++;
147 		/*
148 		 * XXX: should we allow escapes inside the string?
149 		 */
150 		while (**c != '\0' && **c != '\"')
151 			(*c)++;
152 		if (**c != '\"')
153 			goto err;
154 		**c = '\0';	/* kill trailing '\"' */
155 		(*c)++;
156 		break;
157 	case TOKEN_UNKNOWN:
158 		goto err;
159 	}
160 
161 	if (isspace((unsigned char)**c))
162 		*(*c)++ = '\0';
163 	else if (**c == SASLC__COMMENT_CHAR)
164 		**c = '\0';
165 	else if (**c != '\0')
166 		goto err;
167 
168 	return token;
169  err:
170 	free(token);
171 	return NULL;
172 }
173 
174 /**
175  * @brief parses line and store result in dict.
176  * @param line input line
177  * @param dict dictionary in which parsed options will be stored
178  * @return 0 on success, -1 on failure.
179  */
180 static int
saslc__parse_line(char * line,saslc__dict_t * dict)181 saslc__parse_line(char *line, saslc__dict_t *dict)
182 {
183 	saslc__dict_result_t rv;
184 	saslc__token_t *t;
185 	char *key;
186 
187 	key = NULL;
188 	while ((t = saslc__parse_get_token(&line)) != NULL) {
189 		if (t->type == TOKEN_COMMENT) {
190 			free(t);
191 			break;
192 		}
193 
194 		if (key == NULL) {  /* get the key */
195 			if (t->type != TOKEN_KEY)
196 				goto err;
197 			key = t->val;
198 		}
199 		else {  /* get the value and insert in dictionary */
200 			if (t->type != TOKEN_STRING && t->type != TOKEN_NUM)
201 				goto err;
202 			rv = saslc__dict_insert(dict, key, t->val);
203 			if (rv != DICT_OK && rv != DICT_KEYEXISTS)
204 				goto err;
205 			key = NULL;
206 		}
207 		free(t);
208 	}
209 	if (*line != '\0')	/* processed entire line */
210 		return -1;
211 	if (key != NULL)	/* completed key/value cycle */
212 		return -1;
213 	return 0;
214  err:
215 	free(t);
216 	return -1;
217 }
218 
219 /**
220  * @brief parses file and store result in dict
221  * @param ctx saslc context
222  * @param path path to the file
223  * @param dict dictionary in which parsed options will be stored
224  * @return 0 on success, -1 on failure.
225  */
226 static int
saslc__parse_file(saslc_t * ctx,char * path,saslc__dict_t * dict)227 saslc__parse_file(saslc_t *ctx, char *path, saslc__dict_t *dict)
228 {
229 	FILE *fp;
230 	char *buf, *lbuf;
231 	size_t len;
232 	int rv;
233 
234 	if ((fp = fopen(path, "r")) == NULL) {
235 		/* Don't fail if we can't open the file. */
236 		saslc__msg_dbg("%s: fopen: %s: %s", __func__, path,
237 		    strerror(errno));
238 		return 0;
239 	}
240 	saslc__msg_dbg("%s: parsing: \"%s\"", __func__, path);
241 	rv = 0;
242 	lbuf = NULL;
243 	while ((buf = fgetln(fp, &len)) != NULL) {
244 		if (buf[len - 1] == '\n')
245 			buf[len - 1] = '\0';
246 		else {
247 			if ((lbuf = malloc(len + 1)) == NULL) {
248 				saslc__error_set(ERR(ctx), ERROR_NOMEM, NULL);
249 				rv = -1;
250 				break;
251 			}
252 			memcpy(lbuf, buf, len);
253 			lbuf[len] = '\0';
254 			buf = lbuf;
255 		}
256 		if (saslc__parse_line(buf, dict) == -1) {
257 			saslc__error_set(ERR(ctx), ERROR_PARSE,
258 			    "can't parse file");
259 			rv = -1;
260 			break;
261 		}
262 		if (lbuf != NULL) {
263 			free(lbuf);
264 			lbuf = NULL;
265 		}
266 	}
267 	if (lbuf != NULL)
268 		free(lbuf);
269 
270 	fclose(fp);
271 	return rv;
272 }
273 
274 /**
275  * @brief determine if a string indicates true or not.
276  * @return true if the string is "true", "yes", or any nonzero
277  * integer; false otherwise.
278  *
279  * XXX: does this really belong here?  Used in parser.c and xsess.c.
280  */
281 bool
saslc__parser_is_true(const char * str)282 saslc__parser_is_true(const char *str)
283 {
284 	static const char *true_str[] = {
285 		"true",
286 		"yes"
287 	};
288 	char *e;
289 	size_t i;
290 	long int val;
291 
292 	if (str == NULL)
293 		return false;
294 
295 	val = strtol(str, &e, 0);
296 	if (*str != '\0' && *e == '\0')
297 		return val != 0;
298 
299 	for (i = 0; i < __arraycount(true_str); i++)
300 		if (strcasecmp(str, true_str[i]) == 0)
301 			return true;
302 
303 	return false;
304 }
305 
306 /**
307  * @brief parse configuration files. By default function reads
308  * files from /etc/saslc.d/saslc/ directory if appname is not setup. Otherwise
309  * function uses /etc/saslc.d/[appname]/ directory. /etc/saslc.d/ is default
310  * directory which stores configuration for all applications, but can be
311  * overwritten by SASLC_CONFIG variable in environment.
312  * @param ctx saslc context
313  * @return 0 on success, -1 on failure.
314  */
315 int
saslc__parser_config(saslc_t * ctx)316 saslc__parser_config(saslc_t *ctx)
317 {
318 	char path[PATH_MAX + 1];
319 	struct stat sb;
320 	saslc__mech_list_node_t *mech_node;
321 	const char *config_path, *debug, *appname;
322 
323 	config_path = ctx->pathname;
324 	if (config_path == NULL)
325 		config_path = getenv(SASLC_ENV_CONFIG);
326 	if (config_path == NULL)
327 		config_path = SASLC__CONFIG_PATH;
328 
329 	if (stat(config_path, &sb) == -1 || !S_ISDIR(sb.st_mode)) {
330 		/* XXX: should this be fatal or silently ignored? */
331 		saslc__msg_err("%s: stat: config_path='%s': %s", __func__,
332 		    config_path, strerror(errno));
333 		return 0;
334 	}
335 
336 	if ((appname = ctx->appname) == NULL)
337 		appname = SASLC__DEFAULT_APPNAME;
338 
339 	/* parse global config file */
340 	snprintf(path, sizeof(path), "%s/%s/%s%s", config_path,
341 	    appname, SASLC__CONFIG_MAIN_FILE, SASLC__CONFIG_SUFFIX);
342 	if (saslc__parse_file(ctx, path, ctx->prop) == -1)
343 		return -1;
344 
345 	/* XXX: check this as early as possible! */
346 	debug = saslc__dict_get(ctx->prop, SASLC_PROP_DEBUG);
347 	if (debug != NULL)
348 		saslc_debug = saslc__parser_is_true(debug);
349 
350 	/* parse mechanism config files */
351 	LIST_FOREACH(mech_node, ctx->mechanisms, nodes) {
352 		snprintf(path, sizeof(path), "%s/%s/%s/%s%s",
353 		    config_path, appname, SASLC__CONFIG_MECH_DIRECTORY,
354 		    mech_node->mech->name, SASLC__CONFIG_SUFFIX);
355 		if (saslc__parse_file(ctx, path, mech_node->prop) == -1)
356 			return -1;
357 	}
358 
359 	return 0;
360 }
361