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