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