/* * Copyright (c) 2004 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ #include "sm/generic.h" SM_RCSID("@(#)$Id: sm-conf-parse.c,v 1.12 2006/01/09 19:06:25 ca Exp $") #if SM_LIBCONF_ALONE #include #include #include "sm-conf.h" #else /* SM_LIBCONF_ALONE */ #include "sm/error.h" #include "sm/ctype.h" #include "sm/memops.h" #include "sm/sm-conf.h" #endif /* SM_LIBCONF_ALONE */ #include "sm-conf-state.h" #include "sm-conf-token.h" #include "sm-conf-node.h" #include "sm-conf-parse.h" /* SM-CONF-PARSE.C -- semantic analysis of the configuration file. */ #define SM_CONF_ERROR_MAX 15 /* ** SM_CONF_PARSE_VALUE -- parse a value ** ** ::= | "{" [, ...] "}" ** ** Parameters: ** smc -- a configuration file handle. ** value_out -- output parameter ** ** Returns: ** 0 on success, ** a Unix errno or a negative error code on error. */ static int sm_conf_parse_value(sm_conf_T *smc, sm_conf_node_T **value_out) { sm_conf_token_T tok; enum sm_conf_token_type_E token_type; sm_conf_node_T *list, *value; int err; int saw_comma; char tbuf[SM_CONF_TOKEN_BUFFER_SIZE]; char ebuf[SM_CONF_ERROR_BUFFER_SIZE]; SM_IS_CONF(smc); SM_REQUIRE(value_out != NULL); *value_out = NULL; sm_memzero(&tok, sizeof(tok)); tok.sm_magic = SM_CONF_TOKEN_MAGIC; switch (sm_conf_token(smc, &tok, 0)) { case SM_CONF_TOKEN_STRING: case SM_CONF_TOKEN_ATOM: *value_out = sm_conf_value_new(smc, tok.sct_text, tok.sct_text_n, &tok); if (*value_out == NULL) return ENOMEM; return 0; case SM_CONF_TOKEN_OBRACE: break; case SM_CONF_TOKEN_ERROR: SM_ASSERT(tok.sct_error != 0); err = sm_conf_error_add(smc, "%s:%d: " "scanner error while reading value: %s: %s", smc->smc_name, tok.sct_line, sm_conf_token_string(&tok, tbuf, sizeof tbuf), sm_conf_strerror(tok.sct_error, ebuf, sizeof ebuf)); SM_ASSERT(*value_out == NULL); if ( (err == 0) && (err = tok.sct_error) == 0) err = SM_CONF_ERR_SYNTAX; return err; default: err = sm_conf_error_add(smc, "%s:%d: expected value, got %s", smc->smc_name, tok.sct_line, sm_conf_token_string(&tok, tbuf, sizeof tbuf)); return err == 0 ? SM_CONF_ERR_SYNTAX : err; } /* parsed a "{" */ if ((list = sm_conf_list_new(smc, &tok)) == NULL) return ENOMEM; do { /* parse list member */ if ((err = sm_conf_parse_value(smc, &value)) != 0) { sm_conf_node_destroy(smc, list); return err; } SM_ASSERT(value != NULL); sm_conf_list_append(smc, list, value); /* consume commas */ saw_comma = 0; while ( (token_type = sm_conf_token_lookahead(smc)) == SM_CONF_TOKEN_COMMA) { (void)sm_conf_token(smc, &tok, 0); saw_comma++; } if ( token_type == SM_CONF_TOKEN_ERROR || (token_type != SM_CONF_TOKEN_CBRACE && saw_comma != 1)) { sm_conf_node_destroy(smc, list); token_type = sm_conf_token(smc, &tok, 0); if (token_type == SM_CONF_TOKEN_ERROR) { SM_ASSERT(tok.sct_error != 0); err = sm_conf_error_add(smc, "%s:%d: " "scanner error while reading value: " "%s: %s", smc->smc_name, tok.sct_line, sm_conf_token_string(&tok, tbuf, sizeof tbuf), sm_conf_strerror(tok.sct_error, ebuf, sizeof ebuf)); if (err == 0) err = tok.sct_error; } else if (saw_comma > 1) err = sm_conf_error_add(smc, "%s:%d: " "expected list element, got comma", smc->smc_name, tok.sct_line); else err = sm_conf_error_add(smc, "%s:%d: " "expected comma between list elements, " "got %s", smc->smc_name, tok.sct_line, sm_conf_token_string(&tok, tbuf, sizeof tbuf)); return err != 0 ? err : SM_CONF_ERR_SYNTAX; } } while (token_type != SM_CONF_TOKEN_CBRACE); /* Consume "}" */ (void)sm_conf_token(smc, &tok, 0); *value_out = list; SM_ASSERT(*value_out != NULL); return 0; } /* ** SM_CONF_PARSE_IS_KEYWORD -- check whether token is a keyword (syntax only) ** Implement keyword policy beyond what the tokenizer requires. ** ** Parameters: ** token -- token to check. ** ** Returns: ** true iff token is a keyword */ static bool sm_conf_parse_is_keyword(sm_conf_token_T const *token) { size_t i; SM_IS_CONF_TOKEN(token); if ( token->sct_type != SM_CONF_TOKEN_ATOM || token->sct_text_n <= 0) return false; if (*token->sct_text != '_' && !ISALPHA(*token->sct_text)) return false; for (i = 1; i < token->sct_text_n; i++) if (token->sct_text[i] != '_' && !ISALNUM(token->sct_text[i])) return false; return true; } /* ** SM_CONF_PARSE_ENTRY -- parse an entry ** ** ::= [] "{" "}" [";"] ** "=" ";" ** ** Parameters: ** smc -- a configuration file handle. ** container -- node ** ** Returns: ** 0 on success, ** a Unix errno or a negative error code on error. */ static int sm_conf_parse_entry(sm_conf_T *smc, sm_conf_node_T *container) { sm_conf_token_T first, second; enum sm_conf_token_type_E token_type; sm_conf_node_T *value; int err; char tbuf[SM_CONF_TOKEN_BUFFER_SIZE]; char ebuf[SM_CONF_ERROR_BUFFER_SIZE]; int value_is_compound; sm_memzero(&first, sizeof(first)); sm_memzero(&second, sizeof(second)); first.sm_magic = SM_CONF_TOKEN_MAGIC; second.sm_magic = SM_CONF_TOKEN_MAGIC; switch (token_type = sm_conf_token(smc, &first, SM_CONF_TOKEN_FLAG_IDENTIFIER)) { case SM_CONF_TOKEN_EOF: return SM_CONF_ERR_EOF; case SM_CONF_TOKEN_STRING: case SM_CONF_TOKEN_ATOM: if (!sm_conf_parse_is_keyword(&first)) { err = sm_conf_error_add(smc, "%s:%d: " "%s is not a valid identifier or keyword", smc->smc_name, first.sct_line, sm_conf_token_string(&first, tbuf, sizeof tbuf)); if (err != 0) return err; /* continue to find more errors in the same pass. */ } break; case SM_CONF_TOKEN_ERROR: SM_ASSERT(first.sct_error != 0); err = sm_conf_error_add(smc, "%s:%d: " "scanner error while reading name or keyword: %s: %s", smc->smc_name, first.sct_line, sm_conf_token_string(&first, tbuf, sizeof tbuf), sm_conf_strerror(first.sct_error, ebuf, sizeof ebuf)); return err == 0 ? first.sct_error : err; default: if ( token_type == SM_CONF_TOKEN_CBRACE && smc->smc_error_head != NULL) /* ** If we already had some errors, leave this one ** alone -- it's probably just us losing context. */ return SM_CONF_ERR_SYNTAX; err = sm_conf_error_add(smc, "%s:%d: " "expected name or keyword, got %s", smc->smc_name, first.sct_line, sm_conf_token_string(&first, tbuf, sizeof tbuf)); return err == 0 ? SM_CONF_ERR_SYNTAX : err; } second.sct_text = NULL; second.sct_text_n = 0; value = NULL; err = 0; token_type = sm_conf_token_lookahead(smc); if (token_type != SM_CONF_TOKEN_OBRACE) token_type = sm_conf_token(smc, &second, SM_CONF_TOKEN_FLAG_IDENTIFIER); switch (token_type) { case SM_CONF_TOKEN_EQUAL: /* "=" ("{" "}" [";"] | ";" ) */ value_is_compound = sm_conf_token_lookahead(smc) == SM_CONF_TOKEN_OBRACE; if ((err = sm_conf_parse_value(smc, &value)) != 0) { if (err == SM_CONF_ERR_EOF) { err = sm_conf_error_add(smc, "%s:%d: " "EOF after \"=\" -- expected value", smc->smc_name, first.sct_line); if (err == 0) err = SM_CONF_ERR_SYNTAX; } return err; } sm_conf_section_append(smc, container, first.sct_text, first.sct_text_n, value); value = NULL; if (sm_conf_token_lookahead(smc) == SM_CONF_TOKEN_SEMI) (void)sm_conf_token(smc, &first, 0); else if (!value_is_compound) err = sm_conf_error_add(smc, "%s:%d: ';' expected", smc->smc_name, first.sct_line); break; case SM_CONF_TOKEN_STRING: case SM_CONF_TOKEN_ATOM: /* "{" "}" [";"] */ /* Fall through */ case SM_CONF_TOKEN_OBRACE: value = sm_conf_section_new(smc, first.sct_text, first.sct_text_n, second.sct_text, second.sct_text_n, &first); if (value == NULL) return ENOMEM; /* consume "{" */ if (sm_conf_token(smc, &first, 0) != SM_CONF_TOKEN_OBRACE) { err = sm_conf_error_add(smc, "%s:%d: " "missing '=' before, or '{' after, %s", smc->smc_name, first.sct_line, sm_conf_token_string(&second, tbuf, sizeof tbuf)); if (err == 0) err = SM_CONF_ERR_SYNTAX; break; } while (sm_conf_token_lookahead(smc) != SM_CONF_TOKEN_CBRACE) if ((err = sm_conf_parse_entry(smc, value)) != 0) break; if (err != 0) { if (err == SM_CONF_ERR_EOF) { err = sm_conf_error_add(smc, "%s:%d: " "\"{\" without matching \"}\"", smc->smc_name, first.sct_line); if (err == 0) err = SM_CONF_ERR_SYNTAX; return err; } break; } /* consume "}" */ (void)sm_conf_token(smc, &second, 0); sm_conf_section_append(smc, container, NULL, 0, value); value = NULL; /* consume optional empty trailing [";"] */ if (sm_conf_token_lookahead(smc) == SM_CONF_TOKEN_SEMI) (void)sm_conf_token(smc, &first, 0); break; case SM_CONF_TOKEN_ERROR: SM_ASSERT(second.sct_error != 0); err = sm_conf_error_add(smc, "%s:%d: " "scanner error reading %s: %s", smc->smc_name, second.sct_line, sm_conf_token_string(&second, tbuf, sizeof tbuf), sm_conf_strerror(second.sct_error, ebuf, sizeof ebuf)); if (err == 0) err = second.sct_error; break; default: err = sm_conf_error_add(smc, "%s:%d: expected '{' or '=' " "after a name or keyword, got %s", smc->smc_name, second.sct_line, sm_conf_token_string(&second, tbuf, sizeof tbuf)); return err == 0 ? SM_CONF_ERR_SYNTAX : err; } return err; } /* ** SM_CONF_PARSE -- parse what we read from a configuration file. ** ** Parameters: ** smc -- a configuration file handle. ** ** Returns: ** 0 on success, ** a Unix errno or a negative error code on error. */ int sm_conf_parse(sm_conf_T *smc) { int err; sm_conf_node_T *root; root = sm_conf_section_new(smc, NULL, 0, NULL, 0, NULL); if (root == NULL) return ENOMEM; err = 0; for (;;) { int e; if ((e = sm_conf_parse_entry(smc, root)) == SM_CONF_ERR_EOF) break; if (e != 0) { sm_conf_token_T tok; enum sm_conf_token_type_E type; sm_memzero(&tok, sizeof(tok)); tok.sm_magic = SM_CONF_TOKEN_MAGIC; if (err == 0) err = e; if (e != SM_CONF_ERR_SYNTAX) break; if (smc->smc_error_n > SM_CONF_ERROR_MAX) break; /* Skip past the next semicolon or open brace */ while ( (type = sm_conf_token(smc, &tok, 0)) != SM_CONF_TOKEN_EOF && type != SM_CONF_TOKEN_SEMI && type != SM_CONF_TOKEN_OBRACE) ; if (type == SM_CONF_TOKEN_EOF) break; } } if (err == 0 && smc->smc_error_n != 0) err = SM_CONF_ERR_SYNTAX; if (err == 0) { sm_conf_node_terminate(smc, root); smc->smc_root = root; } else sm_conf_node_destroy(smc, root); return err; }