xref: /minix3/crypto/external/bsd/libsaslc/dist/src/parser.c (revision ebfedea0ce5bbe81e252ddf32d732e40fb633fae)
1 /* $NetBSD: parser.c,v 1.4 2011/02/12 23:21:32 christos 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.4 2011/02/12 23:21:32 christos 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 *
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 *
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
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 			break;
191 
192 		if (key == NULL) {  /* get the key */
193 			if (t->type != TOKEN_KEY)
194 				goto err;
195 			key = t->val;
196 		}
197 		else {  /* get the value and insert in dictionary */
198 			if (t->type != TOKEN_STRING && t->type != TOKEN_NUM)
199 				goto err;
200 			rv = saslc__dict_insert(dict, key, t->val);
201 			if (rv != DICT_OK && rv != DICT_KEYEXISTS)
202 				goto err;
203 			key = NULL;
204 		}
205 		free(t);
206 	}
207 	if (*line != '\0')	/* processed entire line */
208 		return -1;
209 	if (key != NULL)	/* completed key/value cycle */
210 		return -1;
211 	return 0;
212  err:
213 	free(t);
214 	return -1;
215 }
216 
217 /**
218  * @brief parses file and store result in dict
219  * @param ctx saslc context
220  * @param path path to the file
221  * @param dict dictionary in which parsed options will be stored
222  * @return 0 on success, -1 on failure.
223  */
224 static int
225 saslc__parse_file(saslc_t *ctx, char *path, saslc__dict_t *dict)
226 {
227 	FILE *fp;
228 	char *buf, *lbuf;
229 	size_t len;
230 	int rv;
231 
232 	if ((fp = fopen(path, "r")) == NULL) {
233 		/* Don't fail if we can't open the file. */
234 		saslc__msg_dbg("%s: fopen: %s: %s", __func__, path,
235 		    strerror(errno));
236 		return 0;
237 	}
238 	saslc__msg_dbg("%s: parsing: \"%s\"", __func__, path);
239 	rv = 0;
240 	lbuf = NULL;
241 	while ((buf = fgetln(fp, &len)) != NULL) {
242 		if (buf[len - 1] == '\n')
243 			buf[len - 1] = '\0';
244 		else {
245 			if ((lbuf = malloc(len + 1)) == NULL) {
246 				saslc__error_set(ERR(ctx), ERROR_NOMEM, NULL);
247 				rv = -1;
248 				break;
249 			}
250 			memcpy(lbuf, buf, len);
251 			lbuf[len] = '\0';
252 			buf = lbuf;
253 		}
254 		if (saslc__parse_line(buf, dict) == -1) {
255 			saslc__error_set(ERR(ctx), ERROR_PARSE,
256 			    "can't parse file");
257 			rv = -1;
258 			break;
259 		}
260 		if (lbuf != NULL) {
261 			free(lbuf);
262 			lbuf = NULL;
263 		}
264 	}
265 	if (lbuf != NULL)
266 		free(lbuf);
267 
268 	fclose(fp);
269 	return rv;
270 }
271 
272 /**
273  * @brief determine if a string indicates true or not.
274  * @return true if the string is "true", "yes", or any nonzero
275  * integer; false otherwise.
276  *
277  * XXX: does this really belong here?  Used in parser.c and xsess.c.
278  */
279 bool
280 saslc__parser_is_true(const char *str)
281 {
282 	static const char *true_str[] = {
283 		"true",
284 		"yes"
285 	};
286 	char *e;
287 	size_t i;
288 	long int val;
289 
290 	if (str == NULL)
291 		return false;
292 
293 	val = strtol(str, &e, 0);
294 	if (*str != '\0' && *e == '\0')
295 		return val != 0;
296 
297 	for (i = 0; i < __arraycount(true_str); i++)
298 		if (strcasecmp(str, true_str[i]) == 0)
299 			return true;
300 
301 	return false;
302 }
303 
304 /**
305  * @brief parse configuration files. By default function reads
306  * files from /etc/saslc.d/saslc/ directory if appname is not setup. Otherwise
307  * function uses /etc/saslc.d/[appname]/ directory. /etc/saslc.d/ is default
308  * directory which stores configuration for all applications, but can be
309  * overwritten by SASLC_CONFIG variable in environment.
310  * @param ctx saslc context
311  * @return 0 on success, -1 on failure.
312  */
313 int
314 saslc__parser_config(saslc_t *ctx)
315 {
316 	char path[PATH_MAX + 1];
317 	struct stat sb;
318 	saslc__mech_list_node_t *mech_node;
319 	const char *config_path, *debug, *appname;
320 
321 	config_path = ctx->pathname;
322 	if (config_path == NULL)
323 		config_path = getenv(SASLC_ENV_CONFIG);
324 	if (config_path == NULL)
325 		config_path = SASLC__CONFIG_PATH;
326 
327 	if (stat(config_path, &sb) == -1 || !S_ISDIR(sb.st_mode)) {
328 		/* XXX: should this be fatal or silently ignored? */
329 		saslc__msg_err("%s: stat: config_path='%s': %s", __func__,
330 		    config_path, strerror(errno));
331 		return 0;
332 	}
333 
334 	if ((appname = ctx->appname) == NULL)
335 		appname = SASLC__DEFAULT_APPNAME;
336 
337 	/* parse global config file */
338 	snprintf(path, sizeof(path), "%s/%s/%s%s", config_path,
339 	    appname, SASLC__CONFIG_MAIN_FILE, SASLC__CONFIG_SUFFIX);
340 	if (saslc__parse_file(ctx, path, ctx->prop) == -1)
341 		return -1;
342 
343 	/* XXX: check this as early as possible! */
344 	debug = saslc__dict_get(ctx->prop, SASLC_PROP_DEBUG);
345 	if (debug != NULL)
346 		saslc_debug = saslc__parser_is_true(debug);
347 
348 	/* parse mechanism config files */
349 	LIST_FOREACH(mech_node, ctx->mechanisms, nodes) {
350 		snprintf(path, sizeof(path), "%s/%s/%s/%s%s",
351 		    config_path, appname, SASLC__CONFIG_MECH_DIRECTORY,
352 		    mech_node->mech->name, SASLC__CONFIG_SUFFIX);
353 		if (saslc__parse_file(ctx, path, mech_node->prop) == -1)
354 			return -1;
355 	}
356 
357 	return 0;
358 }
359