842 lines
17 KiB
Plaintext
Raw Permalink Normal View History

/* $OpenBSD: parse.y,v 1.78 2021/10/15 15:01:28 naddy Exp $ */
/*
* Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
* Copyright (c) 2001 Markus Friedl. All rights reserved.
* Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
* Copyright (c) 2001 Theo de Raadt. All rights reserved.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
%{
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include "ntpd.h"
TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
static struct file {
TAILQ_ENTRY(file) entry;
FILE *stream;
char *name;
int lineno;
int errors;
} *file, *topfile;
struct file *pushfile(const char *);
int popfile(void);
int yyparse(void);
int yylex(void);
int yyerror(const char *, ...)
__attribute__((__format__ (printf, 1, 2)))
__attribute__((__nonnull__ (1)));
int kw_cmp(const void *, const void *);
int lookup(char *);
int lgetc(int);
int lungetc(int);
int findeol(void);
struct sockaddr_in query_addr4;
struct sockaddr_in6 query_addr6;
int poolseqnum;
struct opts {
int weight;
int correction;
int stratum;
int rtable;
int trusted;
char *refstr;
} opts;
void opts_default(void);
typedef struct {
union {
int64_t number;
char *string;
struct ntp_addr_wrap *addr;
struct opts opts;
} v;
int lineno;
} YYSTYPE;
%}
%token LISTEN ON CONSTRAINT CONSTRAINTS FROM QUERY TRUSTED
%token SERVER SERVERS SENSOR CORRECTION RTABLE REFID STRATUM WEIGHT
%token ERROR
%token <v.string> STRING
%token <v.number> NUMBER
%type <v.addr> address url urllist
%type <v.opts> listen_opts listen_opts_l listen_opt
%type <v.opts> server_opts server_opts_l server_opt
%type <v.opts> sensor_opts sensor_opts_l sensor_opt
%type <v.opts> correction
%type <v.opts> rtable
%type <v.opts> refid
%type <v.opts> stratum
%type <v.opts> weight
%type <v.opts> trusted
%%
grammar : /* empty */
| grammar '\n'
| grammar main '\n'
| grammar error '\n' { file->errors++; }
;
main : LISTEN ON address listen_opts {
struct listen_addr *la;
struct ntp_addr *h, *next;
if ((h = $3->a) == NULL &&
(host_dns($3->name, 0, &h) == -1 || !h)) {
yyerror("could not resolve \"%s\"", $3->name);
free($3->name);
free($3);
YYERROR;
}
for (; h != NULL; h = next) {
next = h->next;
la = calloc(1, sizeof(struct listen_addr));
if (la == NULL)
fatal("listen on calloc");
la->fd = -1;
la->rtable = $4.rtable;
memcpy(&la->sa, &h->ss,
sizeof(struct sockaddr_storage));
TAILQ_INSERT_TAIL(&conf->listen_addrs, la,
entry);
free(h);
}
free($3->name);
free($3);
}
| QUERY FROM STRING {
struct sockaddr_in sin4;
struct sockaddr_in6 sin6;
memset(&sin4, 0, sizeof(sin4));
sin4.sin_family = AF_INET;
sin4.sin_len = sizeof(struct sockaddr_in);
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_len = sizeof(struct sockaddr_in6);
if (inet_pton(AF_INET, $3, &sin4.sin_addr) == 1)
memcpy(&query_addr4, &sin4, sin4.sin_len);
else if (inet_pton(AF_INET6, $3, &sin6.sin6_addr) == 1)
memcpy(&query_addr6, &sin6, sin6.sin6_len);
else {
yyerror("invalid IPv4 or IPv6 address: %s\n",
$3);
free($3);
YYERROR;
}
free($3);
}
| SERVERS address server_opts {
struct ntp_peer *p;
struct ntp_addr *h, *next;
h = $2->a;
do {
if (h != NULL) {
next = h->next;
if (h->ss.ss_family != AF_INET &&
h->ss.ss_family != AF_INET6) {
yyerror("IPv4 or IPv6 address "
"or hostname expected");
free(h);
free($2->name);
free($2);
YYERROR;
}
h->next = NULL;
} else
next = NULL;
p = new_peer();
p->weight = $3.weight;
p->trusted = $3.trusted;
conf->trusted_peers = conf->trusted_peers ||
$3.trusted;
p->query_addr4 = query_addr4;
p->query_addr6 = query_addr6;
p->addr = h;
p->addr_head.a = h;
p->addr_head.pool = ++poolseqnum;
p->addr_head.name = strdup($2->name);
if (p->addr_head.name == NULL)
fatal(NULL);
if (p->addr != NULL)
p->state = STATE_DNS_DONE;
TAILQ_INSERT_TAIL(&conf->ntp_peers, p, entry);
h = next;
} while (h != NULL);
free($2->name);
free($2);
}
| SERVER address server_opts {
struct ntp_peer *p;
struct ntp_addr *h, *next;
p = new_peer();
for (h = $2->a; h != NULL; h = next) {
next = h->next;
if (h->ss.ss_family != AF_INET &&
h->ss.ss_family != AF_INET6) {
yyerror("IPv4 or IPv6 address "
"or hostname expected");
free(h);
free(p);
free($2->name);
free($2);
YYERROR;
}
h->next = p->addr;
p->addr = h;
}
p->weight = $3.weight;
p->trusted = $3.trusted;
conf->trusted_peers = conf->trusted_peers ||
$3.trusted;
p->query_addr4 = query_addr4;
p->query_addr6 = query_addr6;
p->addr_head.a = p->addr;
p->addr_head.pool = 0;
p->addr_head.name = strdup($2->name);
if (p->addr_head.name == NULL)
fatal(NULL);
if (p->addr != NULL)
p->state = STATE_DNS_DONE;
TAILQ_INSERT_TAIL(&conf->ntp_peers, p, entry);
free($2->name);
free($2);
}
| CONSTRAINTS FROM url {
struct constraint *p;
struct ntp_addr *h, *next;
h = $3->a;
do {
if (h != NULL) {
next = h->next;
if (h->ss.ss_family != AF_INET &&
h->ss.ss_family != AF_INET6) {
yyerror("IPv4 or IPv6 address "
"or hostname expected");
free(h);
free($3->name);
free($3->path);
free($3);
YYERROR;
}
h->next = NULL;
} else
next = NULL;
p = new_constraint();
p->addr = h;
p->addr_head.a = h;
p->addr_head.pool = ++poolseqnum;
p->addr_head.name = strdup($3->name);
p->addr_head.path = strdup($3->path);
if (p->addr_head.name == NULL ||
p->addr_head.path == NULL)
fatal(NULL);
if (p->addr != NULL)
p->state = STATE_DNS_DONE;
constraint_add(p);
h = next;
} while (h != NULL);
free($3->name);
free($3);
}
| CONSTRAINT FROM urllist {
struct constraint *p;
struct ntp_addr *h, *next;
p = new_constraint();
for (h = $3->a; h != NULL; h = next) {
next = h->next;
if (h->ss.ss_family != AF_INET &&
h->ss.ss_family != AF_INET6) {
yyerror("IPv4 or IPv6 address "
"or hostname expected");
free(h);
free(p);
free($3->name);
free($3->path);
free($3);
YYERROR;
}
h->next = p->addr;
p->addr = h;
}
p->addr_head.a = p->addr;
p->addr_head.pool = 0;
p->addr_head.name = strdup($3->name);
p->addr_head.path = strdup($3->path);
if (p->addr_head.name == NULL ||
p->addr_head.path == NULL)
fatal(NULL);
if (p->addr != NULL)
p->state = STATE_DNS_DONE;
constraint_add(p);
free($3->name);
free($3);
}
| SENSOR STRING sensor_opts {
struct ntp_conf_sensor *s;
s = new_sensor($2);
s->weight = $3.weight;
s->correction = $3.correction;
s->refstr = $3.refstr;
s->stratum = $3.stratum;
s->trusted = $3.trusted;
conf->trusted_sensors = conf->trusted_sensors ||
$3.trusted;
free($2);
TAILQ_INSERT_TAIL(&conf->ntp_conf_sensors, s, entry);
}
;
address : STRING {
if (($$ = calloc(1, sizeof(struct ntp_addr_wrap))) ==
NULL)
fatal(NULL);
host($1, &$$->a);
$$->name = $1;
}
;
urllist : urllist address {
struct ntp_addr *p, *q = NULL;
struct in_addr ina;
struct in6_addr in6a;
if (inet_pton(AF_INET, $2->name, &ina) != 1 &&
inet_pton(AF_INET6, $2->name, &in6a) != 1) {
yyerror("url can only be followed by IP "
"addresses");
free($2->name);
free($2);
YYERROR;
}
p = $2->a;
while (p != NULL) {
q = p;
p = p->next;
}
if (q != NULL) {
q->next = $1->a;
$1->a = $2->a;
free($2);
}
$$ = $1;
}
| url {
$$ = $1;
}
;
url : STRING {
char *hname, *path;
if (($$ = calloc(1, sizeof(struct ntp_addr_wrap))) ==
NULL)
fatal("calloc");
if (strncmp("https://", $1,
strlen("https://")) != 0) {
host($1, &$$->a);
$$->name = $1;
} else {
hname = $1 + strlen("https://");
path = hname + strcspn(hname, "/\\");
if (*path != '\0') {
if (($$->path = strdup(path)) == NULL)
fatal("strdup");
*path = '\0';
}
host(hname, &$$->a);
if (($$->name = strdup(hname)) == NULL)
fatal("strdup");
}
if ($$->path == NULL &&
($$->path = strdup("/")) == NULL)
fatal("strdup");
}
;
listen_opts : { opts_default(); }
listen_opts_l
{ $$ = opts; }
| { opts_default(); $$ = opts; }
;
listen_opts_l : listen_opts_l listen_opt
| listen_opt
;
listen_opt : rtable
;
server_opts : { opts_default(); }
server_opts_l
{ $$ = opts; }
| { opts_default(); $$ = opts; }
;
server_opts_l : server_opts_l server_opt
| server_opt
;
server_opt : weight
| trusted
;
sensor_opts : { opts_default(); }
sensor_opts_l
{ $$ = opts; }
| { opts_default(); $$ = opts; }
;
sensor_opts_l : sensor_opts_l sensor_opt
| sensor_opt
;
sensor_opt : correction
| refid
| stratum
| weight
| trusted
;
correction : CORRECTION NUMBER {
if ($2 < -127000000 || $2 > 127000000) {
yyerror("correction must be between "
"-127000000 and 127000000 microseconds");
YYERROR;
}
opts.correction = $2;
}
;
refid : REFID STRING {
size_t l = strlen($2);
if (l < 1 || l > 4) {
yyerror("refid must be 1 to 4 characters");
free($2);
YYERROR;
}
opts.refstr = $2;
}
;
stratum : STRATUM NUMBER {
if ($2 < 1 || $2 > 15) {
yyerror("stratum must be between "
"1 and 15");
YYERROR;
}
opts.stratum = $2;
}
;
weight : WEIGHT NUMBER {
if ($2 < 1 || $2 > 10) {
yyerror("weight must be between 1 and 10");
YYERROR;
}
opts.weight = $2;
}
rtable : RTABLE NUMBER {
if ($2 < 0 || $2 > RT_TABLEID_MAX) {
yyerror("rtable must be between 1"
" and RT_TABLEID_MAX");
YYERROR;
}
opts.rtable = $2;
}
;
trusted : TRUSTED {
opts.trusted = 1;
}
%%
void
opts_default(void)
{
memset(&opts, 0, sizeof opts);
opts.weight = 1;
opts.stratum = 1;
}
struct keywords {
const char *k_name;
int k_val;
};
int
yyerror(const char *fmt, ...)
{
va_list ap;
char *msg;
file->errors++;
va_start(ap, fmt);
if (vasprintf(&msg, fmt, ap) == -1)
fatalx("yyerror vasprintf");
va_end(ap);
log_warnx("%s:%d: %s", file->name, yylval.lineno, msg);
free(msg);
return (0);
}
int
kw_cmp(const void *k, const void *e)
{
return (strcmp(k, ((const struct keywords *)e)->k_name));
}
int
lookup(char *s)
{
/* this has to be sorted always */
static const struct keywords keywords[] = {
{ "constraint", CONSTRAINT},
{ "constraints", CONSTRAINTS},
{ "correction", CORRECTION},
{ "from", FROM},
{ "listen", LISTEN},
{ "on", ON},
{ "query", QUERY},
{ "refid", REFID},
{ "rtable", RTABLE},
{ "sensor", SENSOR},
{ "server", SERVER},
{ "servers", SERVERS},
{ "stratum", STRATUM},
{ "trusted", TRUSTED},
{ "weight", WEIGHT}
};
const struct keywords *p;
p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
sizeof(keywords[0]), kw_cmp);
if (p)
return (p->k_val);
else
return (STRING);
}
#define MAXPUSHBACK 128
char *parsebuf;
int parseindex;
char pushback_buffer[MAXPUSHBACK];
int pushback_index = 0;
int
lgetc(int quotec)
{
int c, next;
if (parsebuf) {
/* Read character from the parsebuffer instead of input. */
if (parseindex >= 0) {
c = (unsigned char)parsebuf[parseindex++];
if (c != '\0')
return (c);
parsebuf = NULL;
} else
parseindex++;
}
if (pushback_index)
return ((unsigned char)pushback_buffer[--pushback_index]);
if (quotec) {
if ((c = getc(file->stream)) == EOF) {
yyerror("reached end of file while parsing "
"quoted string");
if (file == topfile || popfile() == EOF)
return (EOF);
return (quotec);
}
return (c);
}
while ((c = getc(file->stream)) == '\\') {
next = getc(file->stream);
if (next != '\n') {
c = next;
break;
}
yylval.lineno = file->lineno;
file->lineno++;
}
while (c == EOF) {
if (file == topfile || popfile() == EOF)
return (EOF);
c = getc(file->stream);
}
return (c);
}
int
lungetc(int c)
{
if (c == EOF)
return (EOF);
if (parsebuf) {
parseindex--;
if (parseindex >= 0)
return (c);
}
if (pushback_index + 1 >= MAXPUSHBACK)
return (EOF);
pushback_buffer[pushback_index++] = c;
return (c);
}
int
findeol(void)
{
int c;
parsebuf = NULL;
/* skip to either EOF or the first real EOL */
while (1) {
if (pushback_index)
c = (unsigned char)pushback_buffer[--pushback_index];
else
c = lgetc(0);
if (c == '\n') {
file->lineno++;
break;
}
if (c == EOF)
break;
}
return (ERROR);
}
int
yylex(void)
{
char buf[8096];
char *p;
int quotec, next, c;
int token;
p = buf;
while ((c = lgetc(0)) == ' ' || c == '\t')
; /* nothing */
yylval.lineno = file->lineno;
if (c == '#')
while ((c = lgetc(0)) != '\n' && c != EOF)
; /* nothing */
switch (c) {
case '\'':
case '"':
quotec = c;
while (1) {
if ((c = lgetc(quotec)) == EOF)
return (0);
if (c == '\n') {
file->lineno++;
continue;
} else if (c == '\\') {
if ((next = lgetc(quotec)) == EOF)
return (0);
if (next == quotec || next == ' ' ||
next == '\t')
c = next;
else if (next == '\n') {
file->lineno++;
continue;
} else
lungetc(next);
} else if (c == quotec) {
*p = '\0';
break;
} else if (c == '\0') {
yyerror("syntax error");
return (findeol());
}
if (p + 1 >= buf + sizeof(buf) - 1) {
yyerror("string too long");
return (findeol());
}
*p++ = c;
}
yylval.v.string = strdup(buf);
if (yylval.v.string == NULL)
fatal("yylex: strdup");
return (STRING);
}
#define allowed_to_end_number(x) \
(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
if (c == '-' || isdigit(c)) {
do {
*p++ = c;
if ((size_t)(p-buf) >= sizeof(buf)) {
yyerror("string too long");
return (findeol());
}
} while ((c = lgetc(0)) != EOF && isdigit(c));
lungetc(c);
if (p == buf + 1 && buf[0] == '-')
goto nodigits;
if (c == EOF || allowed_to_end_number(c)) {
const char *errstr = NULL;
*p = '\0';
yylval.v.number = strtonum(buf, LLONG_MIN,
LLONG_MAX, &errstr);
if (errstr) {
yyerror("\"%s\" invalid number: %s",
buf, errstr);
return (findeol());
}
return (NUMBER);
} else {
nodigits:
while (p > buf + 1)
lungetc((unsigned char)*--p);
c = (unsigned char)*--p;
if (c == '-')
return (c);
}
}
#define allowed_in_string(x) \
(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
x != '{' && x != '}' && x != '<' && x != '>' && \
x != '!' && x != '=' && x != '/' && x != '#' && \
x != ','))
if (isalnum(c) || c == ':' || c == '_' || c == '*') {
do {
*p++ = c;
if ((size_t)(p-buf) >= sizeof(buf)) {
yyerror("string too long");
return (findeol());
}
} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
lungetc(c);
*p = '\0';
if ((token = lookup(buf)) == STRING)
if ((yylval.v.string = strdup(buf)) == NULL)
fatal("yylex: strdup");
return (token);
}
if (c == '\n') {
yylval.lineno = file->lineno;
file->lineno++;
}
if (c == EOF)
return (0);
return (c);
}
struct file *
pushfile(const char *name)
{
struct file *nfile;
if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
log_warn("%s", __func__);
return (NULL);
}
if ((nfile->name = strdup(name)) == NULL) {
log_warn("%s", __func__);
free(nfile);
return (NULL);
}
if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
log_warn("%s: %s", __func__, nfile->name);
free(nfile->name);
free(nfile);
return (NULL);
}
nfile->lineno = 1;
TAILQ_INSERT_TAIL(&files, nfile, entry);
return (nfile);
}
int
popfile(void)
{
struct file *prev;
if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
prev->errors += file->errors;
TAILQ_REMOVE(&files, file, entry);
fclose(file->stream);
free(file->name);
free(file);
file = prev;
return (file ? 0 : EOF);
}
int
parse_config(const char *filename, struct ntpd_conf *xconf)
{
int errors = 0;
conf = xconf;
TAILQ_INIT(&conf->listen_addrs);
TAILQ_INIT(&conf->ntp_peers);
TAILQ_INIT(&conf->ntp_conf_sensors);
TAILQ_INIT(&conf->constraints);
if ((file = pushfile(filename)) == NULL) {
return (-1);
}
topfile = file;
yyparse();
errors = file->errors;
popfile();
return (errors ? -1 : 0);
}