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