/* $OpenBSD: parse.y,v 1.43 2004/01/28 23:31:28 henning Exp $ */ /* * Copyright (c) 2002, 2003, 2004 Henning Brauer * 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 #include #include #include #include #include #include #include #include #include #include #include "bgpd.h" #include "mrt.h" #include "session.h" static struct bgpd_config *conf; static struct mrt_head *mrtconf; static struct network_head *netconf; static struct peer *peer_l; static struct peer *curpeer; static struct peer *curgroup; static FILE *fin = NULL; static int lineno = 1; static int errors = 0; static int pdebug = 1; char *infile; int yyerror(const char *, ...); int yyparse(void); int kw_cmp(const void *, const void *); int lookup(char *); int lgetc(FILE *); int lungetc(int); int findeol(void); int yylex(void); struct peer *alloc_peer(void); struct peer *new_peer(void); struct peer *new_group(void); int add_mrtconfig(enum mrt_type, char *, time_t); TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); struct sym { TAILQ_ENTRY(sym) entries; int used; int persist; char *nam; char *val; }; int symset(const char *, const char *, int); char *symget(const char *); int atoul(char *, u_long *); typedef struct { union { u_int32_t number; char *string; struct in_addr addr; } v; int lineno; } YYSTYPE; %} %token AS ROUTERID HOLDTIME YMIN LISTEN ON FIBUPDATE %token GROUP NEIGHBOR NETWORK %token REMOTEAS DESCR LOCALADDR MULTIHOP PASSIVE MAXPREFIX ANNOUNCE %token DUMP MSG IN TABLE %token LOG UPDATES %token TCP MD5SIG PASSWORD KEY %token ERROR %token STRING %type number optnumber yesno %type string %type address %% grammar : /* empty */ | grammar '\n' | grammar conf_main '\n' | grammar varset '\n' | grammar neighbor '\n' | grammar group '\n' | grammar error '\n' { errors++; } ; number : STRING { u_long ulval; if (atoul($1, &ulval) == -1) { yyerror("%s is not a number", $1); YYERROR; } else $$ = ulval; } ; string : string STRING { if (asprintf(&$$, "%s %s", $1, $2) == -1) fatal("string: asprintf"); free($1); free($2); } | STRING ; yesno : STRING { if (!strcmp($1, "yes")) $$ = 1; else if (!strcmp($1, "no")) $$ = 0; else YYERROR; } ; varset : STRING '=' string { if (conf->opts & BGPD_OPT_VERBOSE) printf("%s = \"%s\"\n", $1, $3); if (symset($1, $3, 0) == -1) fatal("cannot store variable"); } ; conf_main : AS number { conf->as = $2; } | ROUTERID address { conf->bgpid = $2.s_addr; } | HOLDTIME number { if ($2 < MIN_HOLDTIME) { yyerror("holdtime must be at least %u", MIN_HOLDTIME); YYERROR; } conf->holdtime = $2; } | HOLDTIME YMIN number { if ($3 < MIN_HOLDTIME) { yyerror("holdtime min must be at least %u", MIN_HOLDTIME); YYERROR; } conf->min_holdtime = $3; } | LISTEN ON address { conf->listen_addr.sin_addr.s_addr = $3.s_addr; } | FIBUPDATE yesno { if ($2 == 0) conf->flags |= BGPD_FLAG_NO_FIB_UPDATE; else conf->flags &= ~BGPD_FLAG_NO_FIB_UPDATE; } | LOG UPDATES { conf->log |= BGPD_LOG_UPDATES; } | DUMP MSG STRING IN STRING optnumber { int action; if (!strcmp($3, "all")) action = MRT_ALL_IN; else if (!strcmp($3, "filtered")) action = MRT_FILTERED_IN; else { yyerror("unknown mrt msg dump type"); YYERROR; } if (add_mrtconfig(action, $5, $6) == -1) YYERROR; } | DUMP TABLE STRING optnumber { if (add_mrtconfig(MRT_TABLE_DUMP, $3, $4) == -1) YYERROR; } | NETWORK address '/' number { struct network *n; if ((n = calloc(1, sizeof(struct network))) == NULL) fatal("new_network"); n->net.prefix.af = AF_INET; n->net.prefix.v4 = $2; if ($4 > 32) { yyerror("invalid netmask"); YYERROR; } n->net.prefixlen = $4; TAILQ_INSERT_TAIL(netconf, n, network_l); } ; address : STRING { int n; if ((n = inet_pton(AF_INET, $1, &$$)) == -1) { yyerror("inet_pton: %s", strerror(errno)); YYERROR; } if (n == 0) { yyerror("could not parse address spec %s", $1); YYERROR; } } ; optnl : '\n' optnl | ; nl : '\n' optnl /* one newline or more */ ; optnumber : /* empty */ { $$ = 0; } | number ; neighbor : NEIGHBOR address optnl '{' optnl { curpeer = new_peer(); curpeer->conf.remote_addr.af = AF_INET; curpeer->conf.remote_addr.v4.s_addr = $2.s_addr; } peeropts_l '}' { curpeer->next = peer_l; peer_l = curpeer; curpeer = NULL; } ; group : GROUP string optnl '{' optnl { curgroup = curpeer = new_group(); if (strlcpy(curgroup->conf.group, $2, sizeof(curgroup->conf.group)) >= sizeof(curgroup->conf.group)) { yyerror("group name \"%s\" too long: max %u", $2, sizeof(curgroup->conf.group) - 1); YYERROR; } } groupopts_l '}' { free(curgroup); curgroup = NULL; } ; groupopts_l : groupopts_l groupoptsl | groupoptsl ; groupoptsl : peeropts nl | neighbor nl | error nl ; peeropts_l : peeropts_l peeroptsl | peeroptsl ; peeroptsl : peeropts nl | error nl ; peeropts : REMOTEAS number { curpeer->conf.remote_as = $2; } | DESCR string { if (strlcpy(curpeer->conf.descr, $2, sizeof(curpeer->conf.descr)) >= sizeof(curpeer->conf.descr)) { yyerror("descr \"%s\" too long: max %u", $2, sizeof(curpeer->conf.descr) - 1); YYERROR; } free($2); } | LOCALADDR address { curpeer->conf.local_addr.af = AF_INET; curpeer->conf.local_addr.v4.s_addr = $2.s_addr; } | MULTIHOP number { if ($2 < 2 || $2 > 255) { yyerror("invalid multihop distance %d", $2); YYERROR; } curpeer->conf.distance = $2; } | PASSIVE { curpeer->conf.passive = 1; } | HOLDTIME number { if ($2 < MIN_HOLDTIME) { yyerror("holdtime must be at least %u", MIN_HOLDTIME); YYERROR; } curpeer->conf.holdtime = $2; } | HOLDTIME YMIN number { if ($3 < MIN_HOLDTIME) { yyerror("holdtime min must be at least %u", MIN_HOLDTIME); YYERROR; } curpeer->conf.min_holdtime = $3; } | ANNOUNCE STRING { if (!strcmp($2, "self")) curpeer->conf.announce_type = ANNOUNCE_SELF; else if (!strcmp($2, "none")) curpeer->conf.announce_type = ANNOUNCE_NONE; else if (!strcmp($2, "all")) curpeer->conf.announce_type = ANNOUNCE_ALL; else { yyerror("unknown announcement type"); YYERROR; } } | MAXPREFIX number { curpeer->conf.max_prefix = $2; } | TCP MD5SIG PASSWORD string { strlcpy(curpeer->conf.tcp_md5_key, $4, sizeof(curpeer->conf.tcp_md5_key)); } | TCP MD5SIG KEY string { unsigned i; char s[3]; if (strlen($4) / 2 >= sizeof(curpeer->conf.tcp_md5_key)) { yyerror("key too long"); YYERROR; } if (strlen($4) % 2) { yyerror("key must be of even length"); YYERROR; } for (i = 0; i < strlen($4) / 2; i++) { s[0] = $4[2*i]; s[1] = $4[2*i + 1]; s[2] = 0; if (!isxdigit(s[0]) || !isxdigit(s[1])) { yyerror("key must be specified in hex"); YYERROR; } curpeer->conf.tcp_md5_key[i] = strtoul(s, NULL, 16); } } ; %% struct keywords { const char *k_name; int k_val; }; int yyerror(const char *fmt, ...) { va_list ap; char *nfmt; errors = 1; va_start(ap, fmt); if (asprintf(&nfmt, "%s:%d: %s", infile, yylval.lineno, fmt) == -1) fatalx("yyerror asprintf"); vlog(LOG_CRIT, nfmt, ap); va_end(ap); free(nfmt); 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[] = { { "AS", AS}, { "announce", ANNOUNCE}, { "descr", DESCR}, { "dump", DUMP}, { "fib-update", FIBUPDATE}, { "group", GROUP}, { "holdtime", HOLDTIME}, { "in", IN}, { "key", KEY}, { "listen", LISTEN}, { "local-address", LOCALADDR}, { "log", LOG}, { "max-prefix", MAXPREFIX}, { "md5sig", MD5SIG}, { "min", YMIN}, { "msg", MSG}, { "multihop", MULTIHOP}, { "neighbor", NEIGHBOR}, { "network", NETWORK}, { "on", ON}, { "passive", PASSIVE}, { "password", PASSWORD}, { "remote-as", REMOTEAS}, { "router-id", ROUTERID}, { "table", TABLE}, { "tcp", TCP}, { "updates", UPDATES}, }; const struct keywords *p; p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), sizeof(keywords[0]), kw_cmp); if (p) { if (pdebug > 1) fprintf(stderr, "%s: %d\n", s, p->k_val); return (p->k_val); } else { if (pdebug > 1) fprintf(stderr, "string: %s\n", s); return (STRING); } } #define MAXPUSHBACK 128 char *parsebuf; int parseindex; char pushback_buffer[MAXPUSHBACK]; int pushback_index = 0; int lgetc(FILE *f) { int c, next; if (parsebuf) { /* Read character from the parsebuffer instead of input. */ if (parseindex >= 0) { c = parsebuf[parseindex++]; if (c != '\0') return (c); parsebuf = NULL; } else parseindex++; } if (pushback_index) return (pushback_buffer[--pushback_index]); while ((c = getc(f)) == '\\') { next = getc(f); if (next != '\n') { if (isspace(next)) yyerror("whitespace after \\"); ungetc(next, f); break; } yylval.lineno = lineno; lineno++; } if (c == '\t' || c == ' ') { /* Compress blanks to a single space. */ do { c = getc(f); } while (c == '\t' || c == ' '); ungetc(c, f); c = ' '; } return (c); } int lungetc(int c) { if (c == EOF) return (EOF); if (parsebuf) { parseindex--; if (parseindex >= 0) return (c); } if (pushback_index < MAXPUSHBACK-1) return (pushback_buffer[pushback_index++] = c); else return (EOF); } int findeol(void) { int c; parsebuf = NULL; pushback_index = 0; /* skip to either EOF or the first real EOL */ while (1) { c = lgetc(fin); if (c == '\n') { lineno++; break; } if (c == EOF) break; } return (ERROR); } int yylex(void) { char buf[8096]; char *p, *val; int endc, c; int token; top: p = buf; while ((c = lgetc(fin)) == ' ') ; /* nothing */ yylval.lineno = lineno; if (c == '#') while ((c = lgetc(fin)) != '\n' && c != EOF) ; /* nothing */ if (c == '$' && parsebuf == NULL) { while (1) { if ((c = lgetc(fin)) == EOF) return (0); if (p + 1 >= buf + sizeof(buf) - 1) { yyerror("string too long"); return (findeol()); } if (isalnum(c) || c == '_') { *p++ = (char)c; continue; } *p = '\0'; lungetc(c); break; } val = symget(buf); if (val == NULL) { yyerror("macro '%s' not defined", buf); return (findeol()); } parsebuf = val; parseindex = 0; goto top; } switch (c) { case '\'': case '"': endc = c; while (1) { if ((c = lgetc(fin)) == EOF) return (0); if (c == endc) { *p = '\0'; break; } if (c == '\n') { lineno++; continue; } if (p + 1 >= buf + sizeof(buf) - 1) { yyerror("string too long"); return (findeol()); } *p++ = (char)c; } yylval.v.string = strdup(buf); if (yylval.v.string == NULL) fatal("yylex: strdup"); return (STRING); } #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 == '_') { do { *p++ = c; if ((unsigned)(p-buf) >= sizeof(buf)) { yyerror("string too long"); return (findeol()); } } while ((c = lgetc(fin)) != EOF && (allowed_in_string(c))); lungetc(c); *p = '\0'; token = lookup(buf); yylval.v.string = strdup(buf); if (yylval.v.string == NULL) fatal("yylex: strdup"); return (token); } if (c == '\n') { yylval.lineno = lineno; lineno++; } if (c == EOF) return (0); return (c); } int parse_config(char *filename, struct bgpd_config *xconf, struct mrt_head *xmconf, struct peer **xpeers, struct network_head* nc) { struct sym *sym, *next; if ((conf = calloc(1, sizeof(struct bgpd_config))) == NULL) fatal(NULL); if ((mrtconf = calloc(1, sizeof(struct mrt_head))) == NULL) fatal(NULL); LIST_INIT(mrtconf); netconf = nc; TAILQ_INIT(netconf); peer_l = NULL; curpeer = NULL; curgroup = NULL; lineno = 1; errors = 0; conf->listen_addr.sin_len = sizeof(conf->listen_addr); conf->listen_addr.sin_family = AF_INET; conf->listen_addr.sin_addr.s_addr = INADDR_ANY; conf->listen_addr.sin_port = htons(BGP_PORT); if ((fin = fopen(filename, "r")) == NULL) { warn("%s", filename); free(conf); free(mrtconf); return (-1); } infile = filename; yyparse(); fclose(fin); /* Free macros and check which have not been used. */ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { next = TAILQ_NEXT(sym, entries); if ((conf->opts & BGPD_OPT_VERBOSE2) && !sym->used) fprintf(stderr, "warning: macro '%s' not " "used\n", sym->nam); if (!sym->persist) { free(sym->nam); free(sym->val); TAILQ_REMOVE(&symhead, sym, entries); free(sym); } } errors += merge_config(xconf, conf, peer_l); errors += mrt_mergeconfig(xmconf, mrtconf); *xpeers = peer_l; free(conf); free(mrtconf); return (errors ? -1 : 0); } int symset(const char *nam, const char *val, int persist) { struct sym *sym; for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); sym = TAILQ_NEXT(sym, entries)) ; /* nothing */ if (sym != NULL) { if (sym->persist == 1) return (0); else { free(sym->nam); free(sym->val); TAILQ_REMOVE(&symhead, sym, entries); free(sym); } } if ((sym = calloc(1, sizeof(*sym))) == NULL) return (-1); sym->nam = strdup(nam); if (sym->nam == NULL) { free(sym); return (-1); } sym->val = strdup(val); if (sym->val == NULL) { free(sym->nam); free(sym); return (-1); } sym->used = 0; sym->persist = persist; TAILQ_INSERT_TAIL(&symhead, sym, entries); return (0); } int cmdline_symset(char *s) { char *sym, *val; int ret; size_t len; if ((val = strrchr(s, '=')) == NULL) return (-1); len = strlen(s) - strlen(val) + 1; if ((sym = malloc(len)) == NULL) fatal("cmdline_symset: malloc"); strlcpy(sym, s, len); ret = symset(sym, val + 1, 1); free(sym); return (ret); } char * symget(const char *nam) { struct sym *sym; TAILQ_FOREACH(sym, &symhead, entries) if (strcmp(nam, sym->nam) == 0) { sym->used = 1; return (sym->val); } return (NULL); } int atoul(char *s, u_long *ulvalp) { u_long ulval; char *ep; errno = 0; ulval = strtoul(s, &ep, 0); if (s[0] == '\0' || *ep != '\0') return (-1); if (errno == ERANGE && ulval == ULONG_MAX) return (-1); *ulvalp = ulval; return (0); } struct peer * alloc_peer(void) { struct peer *p; if ((p = calloc(1, sizeof(struct peer))) == NULL) fatal("new_peer"); /* some sane defaults */ p->state = STATE_NONE; p->next = NULL; p->conf.distance = 1; p->conf.announce_type = ANNOUNCE_SELF; p->conf.max_prefix = ULONG_MAX; return (p); } struct peer * new_peer(void) { struct peer *p; p = alloc_peer(); if (curgroup != NULL) { memcpy(p, curgroup, sizeof(struct peer)); if (strlcpy(p->conf.group, curgroup->conf.group, sizeof(p->conf.group)) >= sizeof(p->conf.group)) fatalx("new_peer group strlcpy"); if (strlcpy(p->conf.descr, curgroup->conf.descr, sizeof(p->conf.descr)) >= sizeof(p->conf.descr)) fatalx("new_peer descr strlcpy"); } p->state = STATE_NONE; p->next = NULL; p->conf.distance = 1; p->conf.announce_type = ANNOUNCE_SELF; p->conf.max_prefix = ULONG_MAX; return (p); } struct peer * new_group(void) { return (alloc_peer()); } int add_mrtconfig(enum mrt_type type, char *name, time_t timeout) { struct mrt *m, *n; LIST_FOREACH(m, mrtconf, list) { if (m->conf.type == type) { yyerror("only one mrtdump per type allowed."); return (-1); } } if ((n = calloc(1, sizeof(struct mrt))) == NULL) fatal("add_mrtconfig"); n->conf.type = type; n->msgbuf.sock = -1; if (strlcpy(n->name, name, sizeof(n->name)) >= sizeof(n->name)) { yyerror("filename \"%s\" too long: max %u", name, sizeof(n->name) - 1); free(n); return (-1); } n->ReopenTimerInterval = timeout; LIST_INSERT_HEAD(mrtconf, n, list); return (0); }