/*
* 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 <errno.h>
#include <ctype.h>
#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
**
** <value> ::= <name> | "{" <value> [, <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 <value> */
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
**
** <entry> ::= <keyword> [<name>] "{" <entries> "}" [";"]
** <option-name> "=" <value> ";"
**
** 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:
/* <name> "=" ("{" <values> "}" [";"] | <value> ";" ) */
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:
/* <keyword> <name> "{" <entries> "}" [";"] */
/* 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;
}
syntax highlighted by Code2HTML, v. 0.9.1