xref: /minix3/crypto/external/bsd/libsaslc/dist/src/parser.c (revision 0a6a1f1d05b60e214de2f05a7310ddd1f0e590e7)
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