xref: /netbsd-src/crypto/external/bsd/libsaslc/dist/src/parser.c (revision 4bd8c591b0afe56d3646d6269717ae45b5a1c8f2)
1*4bd8c591Sshm /* $NetBSD: parser.c,v 1.5 2015/08/08 12:34:33 shm Exp $ */
2231558cbSagc 
3231558cbSagc /* Copyright (c) 2010 The NetBSD Foundation, Inc.
4231558cbSagc  * All rights reserved.
5231558cbSagc  *
6231558cbSagc  * This code is derived from software contributed to The NetBSD Foundation
7231558cbSagc  * by Mateusz Kocielski.
8231558cbSagc  *
9231558cbSagc  * Redistribution and use in source and binary forms, with or without
10231558cbSagc  * modification, are permitted provided that the following conditions
11231558cbSagc  * are met:
12231558cbSagc  * 1. Redistributions of source code must retain the above copyright
13231558cbSagc  *    notice, this list of conditions and the following disclaimer.
14231558cbSagc  * 2. Redistributions in binary form must reproduce the above copyright
15231558cbSagc  *    notice, this list of conditions and the following disclaimer in the
16231558cbSagc  *    documentation and/or other materials provided with the distribution.
17231558cbSagc  * 3. All advertising materials mentioning features or use of this software
18231558cbSagc  *    must display the following acknowledgement:
19231558cbSagc  *  	  This product includes software developed by the NetBSD
20231558cbSagc  *  	  Foundation, Inc. and its contributors.
21231558cbSagc  * 4. Neither the name of The NetBSD Foundation nor the names of its
22231558cbSagc  *    contributors may be used to endorse or promote products derived
23231558cbSagc  *    from this software without specific prior written permission.
24231558cbSagc  *
25231558cbSagc  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
26231558cbSagc  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27231558cbSagc  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28231558cbSagc  * PURPOSE ARE DISCLAIMED.	IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
29231558cbSagc  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30231558cbSagc  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31231558cbSagc  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32231558cbSagc  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33231558cbSagc  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34231558cbSagc  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35231558cbSagc  * POSSIBILITY OF SUCH DAMAGE.
36231558cbSagc  */
3719c14409Schristos #include <sys/cdefs.h>
38*4bd8c591Sshm __RCSID("$NetBSD: parser.c,v 1.5 2015/08/08 12:34:33 shm Exp $");
39beea8b97Schristos 
40beea8b97Schristos #include <sys/stat.h>
41beea8b97Schristos #include <sys/syslimits.h>	/* for PATH_MAX */
42231558cbSagc 
4319c14409Schristos #include <ctype.h>
4419c14409Schristos #include <err.h>
4519c14409Schristos #include <errno.h>
4619c14409Schristos #include <saslc.h>
47231558cbSagc #include <stdio.h>
48231558cbSagc #include <stdlib.h>
49231558cbSagc #include <string.h>
50231558cbSagc 
5119c14409Schristos #include "dict.h"
5219c14409Schristos #include "msg.h"
5319c14409Schristos #include "parser.h"
5419c14409Schristos #include "saslc_private.h"
5519c14409Schristos 
5619c14409Schristos #define SASLC__COMMENT_CHAR	'#'
5719c14409Schristos 
5819c14409Schristos /* config file location defines */
5919c14409Schristos #define SASLC__CONFIG_PATH		"/etc/saslc.d"
6019c14409Schristos #define SASLC__CONFIG_MAIN_FILE		"saslc"
6119c14409Schristos #define SASLC__CONFIG_MECH_DIRECTORY	"mech"
6219c14409Schristos #define SASLC__CONFIG_SUFFIX		".conf"
6319c14409Schristos #define SASLC__DEFAULT_APPNAME		"saslc"
64231558cbSagc 
65231558cbSagc /* token types */
66231558cbSagc enum {
6719c14409Schristos 	TOKEN_KEY,		/* option (key) */
68231558cbSagc 	TOKEN_STRING,		/* quoted string */
69231558cbSagc 	TOKEN_NUM,		/* number */
7019c14409Schristos 	TOKEN_COMMENT,		/* comment character */
71231558cbSagc 	TOKEN_UNKNOWN		/* unknown */
72231558cbSagc };
73231558cbSagc 
7419c14409Schristos /* token structure */
75231558cbSagc typedef struct saslc__token_t {
76231558cbSagc 	int type;		/**< token type */
7719c14409Schristos 	char *val;		/**< token string value */
78231558cbSagc } saslc__token_t;
79231558cbSagc 
8019c14409Schristos static inline char *
skip_WS(char * p)8119c14409Schristos skip_WS(char *p)
82231558cbSagc {
83231558cbSagc 
8419c14409Schristos 	while (*p == ' ' || *p == '\t')
8519c14409Schristos 		p++;
8619c14409Schristos 	return p;
87231558cbSagc }
88231558cbSagc 
89231558cbSagc /**
90231558cbSagc  * @brief gets token from string c and updates pointer position.
91231558cbSagc  * @param c pointer to string
9219c14409Schristos  * @return token on success, NULL on failure (e.g. at end of string).
9319c14409Schristos  * On success, c is updated to point on next token.  It's position is
9419c14409Schristos  * undefined on failure.
9519c14409Schristos  *
9619c14409Schristos  * Note: A legal key begins with an isalpha(3) character and is
9719c14409Schristos  * followed by isalnum(3) or '_' characters.
98231558cbSagc  */
99231558cbSagc static saslc__token_t *
saslc__parse_get_token(char ** c)10019c14409Schristos saslc__parse_get_token(char **c)
101231558cbSagc {
102231558cbSagc 	saslc__token_t *token;
10319c14409Schristos 	char *e;
104231558cbSagc 
10519c14409Schristos 	*c = skip_WS(*c);
106231558cbSagc 	if (**c == '\0')
107231558cbSagc 		return NULL;
108231558cbSagc 
10919c14409Schristos 	if ((token = calloc(1, sizeof(*token))) == NULL)
110231558cbSagc 		return NULL;
111231558cbSagc 
112231558cbSagc 	token->val = *c;
113231558cbSagc 
11419c14409Schristos 	if (**c == SASLC__COMMENT_CHAR)
11519c14409Schristos 		token->type = TOKEN_COMMENT;
11619c14409Schristos 
11719c14409Schristos 	else if (**c == '\"')
118231558cbSagc 		token->type = TOKEN_STRING;
119231558cbSagc 
12019c14409Schristos 	else if (isdigit((unsigned char)**c))
121231558cbSagc 		token->type = TOKEN_NUM;
122231558cbSagc 
12319c14409Schristos 	else if (isalpha((unsigned char)**c))
12419c14409Schristos 		token->type = TOKEN_KEY;
125231558cbSagc 
12619c14409Schristos 	else
12719c14409Schristos 		token->type = TOKEN_UNKNOWN;
128231558cbSagc 
12919c14409Schristos 	switch (token->type) {
13019c14409Schristos 	case TOKEN_COMMENT:
131231558cbSagc 		break;
13219c14409Schristos 	case TOKEN_NUM:
13319c14409Schristos 		errno = 0;
13419c14409Schristos 		(void)strtoll(*c, &e, 0);
13519c14409Schristos 		if (errno != 0)
13619c14409Schristos 			goto err;
13719c14409Schristos 		*c = e;
13819c14409Schristos 		break;
13919c14409Schristos 	case TOKEN_KEY:
14019c14409Schristos 		(*c)++;
14119c14409Schristos 		while (isalnum((unsigned char)**c) || **c == '_')
142231558cbSagc 			(*c)++;
143231558cbSagc 		break;
144231558cbSagc 	case TOKEN_STRING:
14519c14409Schristos 		token->val++;	/* skip initial '\"' */
14619c14409Schristos 		(*c)++;
14719c14409Schristos 		/*
14819c14409Schristos 		 * XXX: should we allow escapes inside the string?
14919c14409Schristos 		 */
150231558cbSagc 		while (**c != '\0' && **c != '\"')
151231558cbSagc 			(*c)++;
15219c14409Schristos 		if (**c != '\"')
15319c14409Schristos 			goto err;
15419c14409Schristos 		**c = '\0';	/* kill trailing '\"' */
155231558cbSagc 		(*c)++;
156231558cbSagc 		break;
15719c14409Schristos 	case TOKEN_UNKNOWN:
15819c14409Schristos 		goto err;
159231558cbSagc 	}
160231558cbSagc 
16119c14409Schristos 	if (isspace((unsigned char)**c))
16219c14409Schristos 		*(*c)++ = '\0';
16319c14409Schristos 	else if (**c == SASLC__COMMENT_CHAR)
164231558cbSagc 		**c = '\0';
16519c14409Schristos 	else if (**c != '\0')
16619c14409Schristos 		goto err;
167231558cbSagc 
168231558cbSagc 	return token;
16919c14409Schristos  err:
17019c14409Schristos 	free(token);
171231558cbSagc 	return NULL;
172231558cbSagc }
173231558cbSagc 
174231558cbSagc /**
175231558cbSagc  * @brief parses line and store result in dict.
176231558cbSagc  * @param line input line
177231558cbSagc  * @param dict dictionary in which parsed options will be stored
178231558cbSagc  * @return 0 on success, -1 on failure.
179231558cbSagc  */
180231558cbSagc static int
saslc__parse_line(char * line,saslc__dict_t * dict)181231558cbSagc saslc__parse_line(char *line, saslc__dict_t *dict)
182231558cbSagc {
18319c14409Schristos 	saslc__dict_result_t rv;
18419c14409Schristos 	saslc__token_t *t;
18519c14409Schristos 	char *key;
186231558cbSagc 
18719c14409Schristos 	key = NULL;
18819c14409Schristos 	while ((t = saslc__parse_get_token(&line)) != NULL) {
189*4bd8c591Sshm 		if (t->type == TOKEN_COMMENT) {
190*4bd8c591Sshm 			free(t);
19119c14409Schristos 			break;
192*4bd8c591Sshm 		}
193231558cbSagc 
19419c14409Schristos 		if (key == NULL) {  /* get the key */
19519c14409Schristos 			if (t->type != TOKEN_KEY)
19619c14409Schristos 				goto err;
19719c14409Schristos 			key = t->val;
19819c14409Schristos 		}
19919c14409Schristos 		else {  /* get the value and insert in dictionary */
20019c14409Schristos 			if (t->type != TOKEN_STRING && t->type != TOKEN_NUM)
20119c14409Schristos 				goto err;
20219c14409Schristos 			rv = saslc__dict_insert(dict, key, t->val);
20319c14409Schristos 			if (rv != DICT_OK && rv != DICT_KEYEXISTS)
20419c14409Schristos 				goto err;
20519c14409Schristos 			key = NULL;
20619c14409Schristos 		}
20719c14409Schristos 		free(t);
20819c14409Schristos 	}
20919c14409Schristos 	if (*line != '\0')	/* processed entire line */
210231558cbSagc 		return -1;
21119c14409Schristos 	if (key != NULL)	/* completed key/value cycle */
21219c14409Schristos 		return -1;
21319c14409Schristos 	return 0;
21419c14409Schristos  err:
21519c14409Schristos 	free(t);
21619c14409Schristos 	return -1;
217231558cbSagc }
218231558cbSagc 
219231558cbSagc /**
220231558cbSagc  * @brief parses file and store result in dict
221231558cbSagc  * @param ctx saslc context
222231558cbSagc  * @param path path to the file
223231558cbSagc  * @param dict dictionary in which parsed options will be stored
224231558cbSagc  * @return 0 on success, -1 on failure.
225231558cbSagc  */
226231558cbSagc static int
saslc__parse_file(saslc_t * ctx,char * path,saslc__dict_t * dict)227231558cbSagc saslc__parse_file(saslc_t *ctx, char *path, saslc__dict_t *dict)
228231558cbSagc {
22919c14409Schristos 	FILE *fp;
23019c14409Schristos 	char *buf, *lbuf;
23119c14409Schristos 	size_t len;
23219c14409Schristos 	int rv;
233231558cbSagc 
23419c14409Schristos 	if ((fp = fopen(path, "r")) == NULL) {
23519c14409Schristos 		/* Don't fail if we can't open the file. */
23619c14409Schristos 		saslc__msg_dbg("%s: fopen: %s: %s", __func__, path,
23719c14409Schristos 		    strerror(errno));
238231558cbSagc 		return 0;
239231558cbSagc 	}
24019c14409Schristos 	saslc__msg_dbg("%s: parsing: \"%s\"", __func__, path);
24119c14409Schristos 	rv = 0;
24219c14409Schristos 	lbuf = NULL;
24319c14409Schristos 	while ((buf = fgetln(fp, &len)) != NULL) {
24419c14409Schristos 		if (buf[len - 1] == '\n')
24519c14409Schristos 			buf[len - 1] = '\0';
24619c14409Schristos 		else {
24719c14409Schristos 			if ((lbuf = malloc(len + 1)) == NULL) {
24819c14409Schristos 				saslc__error_set(ERR(ctx), ERROR_NOMEM, NULL);
24919c14409Schristos 				rv = -1;
250231558cbSagc 				break;
25119c14409Schristos 			}
25219c14409Schristos 			memcpy(lbuf, buf, len);
25319c14409Schristos 			lbuf[len] = '\0';
25419c14409Schristos 			buf = lbuf;
25519c14409Schristos 		}
25619c14409Schristos 		if (saslc__parse_line(buf, dict) == -1) {
257231558cbSagc 			saslc__error_set(ERR(ctx), ERROR_PARSE,
258231558cbSagc 			    "can't parse file");
259231558cbSagc 			rv = -1;
260231558cbSagc 			break;
261231558cbSagc 		}
26219c14409Schristos 		if (lbuf != NULL) {
26319c14409Schristos 			free(lbuf);
26419c14409Schristos 			lbuf = NULL;
26519c14409Schristos 		}
26619c14409Schristos 	}
26719c14409Schristos 	if (lbuf != NULL)
26819c14409Schristos 		free(lbuf);
26919c14409Schristos 
27019c14409Schristos 	fclose(fp);
27119c14409Schristos 	return rv;
272231558cbSagc }
273231558cbSagc 
27419c14409Schristos /**
27519c14409Schristos  * @brief determine if a string indicates true or not.
27619c14409Schristos  * @return true if the string is "true", "yes", or any nonzero
27719c14409Schristos  * integer; false otherwise.
27819c14409Schristos  *
27919c14409Schristos  * XXX: does this really belong here?  Used in parser.c and xsess.c.
28019c14409Schristos  */
28119c14409Schristos bool
saslc__parser_is_true(const char * str)28219c14409Schristos saslc__parser_is_true(const char *str)
28319c14409Schristos {
28419c14409Schristos 	static const char *true_str[] = {
28519c14409Schristos 		"true",
28619c14409Schristos 		"yes"
28719c14409Schristos 	};
28819c14409Schristos 	char *e;
28919c14409Schristos 	size_t i;
29019c14409Schristos 	long int val;
29119c14409Schristos 
29219c14409Schristos 	if (str == NULL)
29319c14409Schristos 		return false;
29419c14409Schristos 
29519c14409Schristos 	val = strtol(str, &e, 0);
29619c14409Schristos 	if (*str != '\0' && *e == '\0')
29719c14409Schristos 		return val != 0;
29819c14409Schristos 
29919c14409Schristos 	for (i = 0; i < __arraycount(true_str); i++)
30019c14409Schristos 		if (strcasecmp(str, true_str[i]) == 0)
30119c14409Schristos 			return true;
30219c14409Schristos 
30319c14409Schristos 	return false;
304231558cbSagc }
305231558cbSagc 
306231558cbSagc /**
307231558cbSagc  * @brief parse configuration files. By default function reads
308231558cbSagc  * files from /etc/saslc.d/saslc/ directory if appname is not setup. Otherwise
309231558cbSagc  * function uses /etc/saslc.d/[appname]/ directory. /etc/saslc.d/ is default
310231558cbSagc  * directory which stores configuration for all applications, but can be
311231558cbSagc  * overwritten by SASLC_CONFIG variable in environment.
312231558cbSagc  * @param ctx saslc context
313231558cbSagc  * @return 0 on success, -1 on failure.
314231558cbSagc  */
315231558cbSagc int
saslc__parser_config(saslc_t * ctx)316231558cbSagc saslc__parser_config(saslc_t *ctx)
317231558cbSagc {
31819c14409Schristos 	char path[PATH_MAX + 1];
31919c14409Schristos 	struct stat sb;
320231558cbSagc 	saslc__mech_list_node_t *mech_node;
32119c14409Schristos 	const char *config_path, *debug, *appname;
322231558cbSagc 
32319c14409Schristos 	config_path = ctx->pathname;
32419c14409Schristos 	if (config_path == NULL)
32519c14409Schristos 		config_path = getenv(SASLC_ENV_CONFIG);
32619c14409Schristos 	if (config_path == NULL)
327231558cbSagc 		config_path = SASLC__CONFIG_PATH;
328231558cbSagc 
32919c14409Schristos 	if (stat(config_path, &sb) == -1 || !S_ISDIR(sb.st_mode)) {
33019c14409Schristos 		/* XXX: should this be fatal or silently ignored? */
33119c14409Schristos 		saslc__msg_err("%s: stat: config_path='%s': %s", __func__,
33219c14409Schristos 		    config_path, strerror(errno));
33319c14409Schristos 		return 0;
33419c14409Schristos 	}
33519c14409Schristos 
336231558cbSagc 	if ((appname = ctx->appname) == NULL)
337231558cbSagc 		appname = SASLC__DEFAULT_APPNAME;
338231558cbSagc 
33919c14409Schristos 	/* parse global config file */
340231558cbSagc 	snprintf(path, sizeof(path), "%s/%s/%s%s", config_path,
341231558cbSagc 	    appname, SASLC__CONFIG_MAIN_FILE, SASLC__CONFIG_SUFFIX);
34219c14409Schristos 	if (saslc__parse_file(ctx, path, ctx->prop) == -1)
343231558cbSagc 		return -1;
344231558cbSagc 
34519c14409Schristos 	/* XXX: check this as early as possible! */
34619c14409Schristos 	debug = saslc__dict_get(ctx->prop, SASLC_PROP_DEBUG);
34719c14409Schristos 	if (debug != NULL)
34819c14409Schristos 		saslc_debug = saslc__parser_is_true(debug);
34919c14409Schristos 
35019c14409Schristos 	/* parse mechanism config files */
35119c14409Schristos 	LIST_FOREACH(mech_node, ctx->mechanisms, nodes) {
352231558cbSagc 		snprintf(path, sizeof(path), "%s/%s/%s/%s%s",
353231558cbSagc 		    config_path, appname, SASLC__CONFIG_MECH_DIRECTORY,
354231558cbSagc 		    mech_node->mech->name, SASLC__CONFIG_SUFFIX);
35519c14409Schristos 		if (saslc__parse_file(ctx, path, mech_node->prop) == -1)
356231558cbSagc 			return -1;
357231558cbSagc 	}
358231558cbSagc 
359231558cbSagc 	return 0;
360231558cbSagc }
361