parse.y revision 1.18
1/*	$OpenBSD: parse.y,v 1.18 2007/01/25 19:40:08 niallo Exp $	*/
2
3/*
4 * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@spootnik.org>
5 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
6 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
7 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
8 * Copyright (c) 2001 Markus Friedl.  All rights reserved.
9 * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
10 * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
11 *
12 * Permission to use, copy, modify, and distribute this software for any
13 * purpose with or without fee is hereby granted, provided that the above
14 * copyright notice and this permission notice appear in all copies.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
17 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
19 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
21 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
22 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 */
24
25%{
26#include <sys/types.h>
27#include <sys/socket.h>
28#include <sys/queue.h>
29#include <netinet/in.h>
30#include <net/if.h>
31#include <arpa/inet.h>
32#include <arpa/nameser.h>
33
34#include <ctype.h>
35#include <err.h>
36#include <errno.h>
37#include <event.h>
38#include <limits.h>
39#include <stdarg.h>
40#include <stdio.h>
41#include <netdb.h>
42#include <string.h>
43
44#include "hoststated.h"
45
46struct hoststated			*conf = NULL;
47static FILE			*fin = NULL;
48static int			 lineno = 1;
49static int			 errors = 0;
50const char			*infile;
51char				*start_state;
52objid_t				 last_service_id = 0;
53objid_t				 last_table_id = 0;
54objid_t				 last_host_id = 0;
55
56static struct service		*service = NULL;
57static struct table		*table = NULL;
58
59int	 yyerror(const char *, ...);
60int	 yyparse(void);
61int	 kw_cmp(const void *, const void *);
62int	 lookup(char *);
63int	 lgetc(FILE *, int *);
64int	 lungetc(int);
65int	 findeol(void);
66int	 yylex(void);
67
68TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
69struct sym {
70	TAILQ_ENTRY(sym)	 entries;
71	int			 used;
72	int			 persist;
73	char			*nam;
74	char			*val;
75};
76
77int		 symset(const char *, const char *, int);
78char		*symget(const char *);
79int		 cmdline_symset(char *);
80
81struct address	*host_v4(const char *);
82struct address	*host_v6(const char *);
83int		 host_dns(const char *, struct addresslist *,
84		    int, in_port_t, const char *);
85int		 host(const char *, struct addresslist *,
86		    int, in_port_t, const char *);
87
88typedef struct {
89	union {
90		u_int32_t	 number;
91		char		*string;
92		struct host	*host;
93		struct timeval	 tv;
94	} v;
95	int lineno;
96} YYSTYPE;
97
98%}
99
100%token	SERVICE TABLE BACKUP HOST REAL
101%token  CHECK HTTP TCP ICMP EXTERNAL
102%token  TIMEOUT CODE DIGEST PORT TAG INTERFACE
103%token	VIRTUAL IP INTERVAL DISABLE STICKYADDR
104%token	SEND EXPECT NOTHING
105%token	ERROR
106%token	<v.string>	STRING
107%type	<v.string>	interface
108%type	<v.number>	number port
109%type	<v.host>	host
110%type	<v.tv>		timeout
111
112%%
113
114grammar		: /* empty */
115		| grammar '\n'
116		| grammar varset '\n'
117		| grammar main '\n'
118		| grammar service '\n'
119		| grammar table '\n'
120		| grammar error '\n'		{ errors++; }
121		;
122
123number		: STRING	{
124			const char	*estr;
125
126			$$ = strtonum($1, 0, UINT_MAX, &estr);
127			if (estr) {
128				yyerror("cannot parse number %s : %s",
129				    $1, estr);
130				free($1);
131				YYERROR;
132			}
133			free($1);
134		}
135		;
136
137port		: PORT STRING	{
138			const char	*estr;
139			struct servent	*servent;
140
141			$$ = strtonum($2, 1, USHRT_MAX, &estr);
142			if (estr) {
143				if (errno == ERANGE) {
144					yyerror("port %s is out of range", $2);
145					free($2);
146					YYERROR;
147				}
148				servent = getservbyname($2, "tcp");
149				if (servent == NULL) {
150					yyerror("port %s is invalid", $2);
151					free($2);
152					YYERROR;
153				}
154				$$ = servent->s_port;
155			} else
156				$$ = htons($$);
157			free($2);
158		}
159		| PORT HTTP {
160			struct servent	*servent;
161
162			servent = getservbyname("http", "tcp");
163			if (servent == NULL)
164				$$ = htons(80);
165			else
166				$$ = servent->s_port;
167		}
168		;
169
170varset		: STRING '=' STRING	{
171			if (symset($1, $3, 0) == -1)
172				fatal("cannot store variable");
173			free($1);
174			free($3);
175		}
176		;
177
178sendbuf		: NOTHING		{
179			table->sendbuf = NULL;
180		}
181		| STRING		{
182			table->sendbuf = strdup($1);
183			if (table->sendbuf == NULL)
184				fatal("out of memory");
185			free($1);
186		}
187		;
188
189main		: INTERVAL number	{ conf->interval.tv_sec = $2; }
190		| TIMEOUT timeout	{
191			bcopy(&$2, &conf->timeout, sizeof(struct timeval));
192		}
193		;
194
195service		: SERVICE STRING	{
196			struct service *srv;
197
198			TAILQ_FOREACH(srv, &conf->services, entry)
199				if (!strcmp(srv->name, $2))
200					break;
201			if (srv != NULL) {
202				yyerror("service %s defined twice", $2);
203				free($2);
204				YYERROR;
205			}
206			if ((srv = calloc(1, sizeof (*srv))) == NULL)
207				fatal("out of memory");
208
209			if (strlcpy(srv->name, $2, sizeof(srv->name)) >=
210			    sizeof(srv->name)) {
211				yyerror("service name truncated");
212				YYERROR;
213			}
214			free($2);
215			srv->id = last_service_id++;
216			if (last_service_id == UINT_MAX) {
217				yyerror("too many services defined");
218				YYERROR;
219			}
220			service = srv;
221		} '{' optnl serviceopts_l '}'	{
222			if (service->table == NULL) {
223				yyerror("service %s has no table",
224				    service->name);
225				YYERROR;
226			}
227			if (TAILQ_EMPTY(&service->virts)) {
228				yyerror("service %s has no virtual ip",
229				    service->name);
230				YYERROR;
231			}
232			conf->servicecount++;
233			if (service->backup == NULL)
234				service->backup = &conf->empty_table;
235			else if (service->backup->port !=
236			    service->table->port) {
237				yyerror("service %s uses two different ports "
238				    "for its table and backup table",
239				    service->name);
240				YYERROR;
241			}
242
243			if (!(service->flags & F_DISABLE))
244				service->flags |= F_ADD;
245			TAILQ_INSERT_HEAD(&conf->services, service, entry);
246		}
247		;
248
249serviceopts_l	: serviceopts_l serviceoptsl nl
250		| serviceoptsl optnl
251		;
252
253serviceoptsl	: TABLE STRING	{
254			struct table *tb;
255
256			TAILQ_FOREACH(tb, &conf->tables, entry)
257				if (!strcmp(tb->name, $2))
258					break;
259			if (tb == NULL) {
260				yyerror("no such table: %s", $2);
261				free($2);
262				YYERROR;
263			} else {
264				service->table = tb;
265				service->table->serviceid = service->id;
266				service->table->flags |= F_USED;
267				free($2);
268			}
269		}
270		| BACKUP TABLE STRING	{
271			struct table *tb;
272
273			if (service->backup) {
274				yyerror("backup already specified");
275				free($3);
276				YYERROR;
277			}
278
279			TAILQ_FOREACH(tb, &conf->tables, entry)
280				if (!strcmp(tb->name, $3))
281					break;
282
283			if (tb == NULL) {
284				yyerror("no such table: %s", $3);
285				free($3);
286				YYERROR;
287			} else {
288				service->backup = tb;
289				service->backup->serviceid = service->id;
290				service->backup->flags |= (F_USED|F_BACKUP);
291				free($3);
292			}
293		}
294		| VIRTUAL IP STRING port interface {
295			if (host($3, &service->virts,
296				 SRV_MAX_VIRTS, $4, $5) <= 0) {
297				yyerror("invalid virtual ip: %s", $3);
298				free($3);
299				free($5);
300				YYERROR;
301			}
302			free($3);
303			free($5);
304		}
305		| DISABLE			{ service->flags |= F_DISABLE; }
306		| STICKYADDR			{ service->flags |= F_STICKY; }
307		| TAG STRING {
308			if (strlcpy(service->tag, $2, sizeof(service->tag)) >=
309			    sizeof(service->tag)) {
310				yyerror("service tag name truncated");
311				free($2);
312				YYERROR;
313			}
314			free($2);
315		}
316		;
317
318table		: TABLE STRING	{
319			struct table *tb;
320
321			TAILQ_FOREACH(tb, &conf->tables, entry)
322				if (!strcmp(tb->name, $2))
323					break;
324			if (tb != NULL) {
325				yyerror("table %s defined twice");
326				free($2);
327				YYERROR;
328			}
329
330			if ((tb = calloc(1, sizeof (*tb))) == NULL)
331				fatal("out of memory");
332
333			if (strlcpy(tb->name, $2, sizeof(tb->name)) >=
334			    sizeof(tb->name)) {
335				yyerror("table name truncated");
336				YYERROR;
337			}
338			tb->id = last_table_id++;
339			bcopy(&conf->timeout, &tb->timeout,
340			    sizeof(struct timeval));
341			if (last_table_id == UINT_MAX) {
342				yyerror("too many tables defined");
343				YYERROR;
344			}
345			free($2);
346			table = tb;
347		} '{' optnl tableopts_l '}'	{
348			if (table->port == 0) {
349				yyerror("table %s has no port", table->name);
350				YYERROR;
351			}
352			if (TAILQ_EMPTY(&table->hosts)) {
353				yyerror("table %s has no hosts", table->name);
354				YYERROR;
355			}
356			if (table->check == CHECK_NOCHECK) {
357				yyerror("table %s has no check", table->name);
358				YYERROR;
359			}
360			conf->tablecount++;
361			TAILQ_INSERT_HEAD(&conf->tables, table, entry);
362		}
363		;
364
365tableopts_l	: tableopts_l tableoptsl nl
366		| tableoptsl optnl
367		;
368
369tableoptsl	: host			{
370			$1->tableid = table->id;
371			$1->tablename = table->name;
372			TAILQ_INSERT_HEAD(&table->hosts, $1, entry);
373		}
374		| TIMEOUT timeout	{
375			bcopy(&$2, &table->timeout, sizeof(struct timeval));
376		}
377		| CHECK ICMP		{
378			table->check = CHECK_ICMP;
379		}
380		| CHECK TCP		{
381			table->check = CHECK_TCP;
382		}
383		| CHECK HTTP STRING CODE number {
384			table->check = CHECK_HTTP_CODE;
385			table->retcode = $5;
386			asprintf(&table->sendbuf, "HEAD %s HTTP/1.0\r\n\r\n",
387			    $3);
388			free($3);
389			if (table->sendbuf == NULL)
390				fatal("out of memory");
391		}
392		| CHECK HTTP STRING DIGEST STRING {
393			table->check = CHECK_HTTP_DIGEST;
394			asprintf(&table->sendbuf, "GET %s HTTP/1.0\r\n\r\n",
395			    $3);
396			free($3);
397			if (table->sendbuf == NULL)
398				fatal("out of memory");
399			if (strlcpy(table->digest, $5,
400			    sizeof(table->digest)) >= sizeof(table->digest)) {
401				yyerror("http digest truncated");
402				free($5);
403				YYERROR;
404			}
405			free($5);
406		}
407		| CHECK SEND sendbuf EXPECT STRING {
408			table->check = CHECK_SEND_EXPECT;
409			if (strlcpy(table->exbuf, $5, sizeof(table->exbuf))
410			    >= sizeof(table->exbuf)) {
411				yyerror("yyparse: expect buffer truncated");
412				free($5);
413				YYERROR;
414			}
415			free($5);
416		}
417		| REAL port {
418			table->port = $2;
419		}
420		| DISABLE			{ table->flags |= F_DISABLE; }
421		;
422
423interface	: /*empty*/		{ $$ = NULL; }
424		| INTERFACE STRING	{ $$ = $2; }
425		;
426
427host		: HOST STRING {
428			struct host *r;
429			struct address *a;
430			struct addresslist al;
431
432			if ((r = calloc(1, sizeof(*r))) == NULL)
433				fatal("out of memory");
434
435			TAILQ_INIT(&al);
436			if (host($2, &al, 1, 0, NULL) <= 0) {
437				yyerror("invalid host %s", $2);
438				free($2);
439				YYERROR;
440			}
441			a = TAILQ_FIRST(&al);
442			memcpy(&r->ss, &a->ss, sizeof(r->ss));
443			free(a);
444
445			if (strlcpy(r->name, $2, sizeof(r->name)) >=
446			    sizeof(r->name)) {
447				yyerror("host name truncated");
448				free($2);
449				YYERROR;
450			} else {
451				r->id = last_host_id++;
452				if (last_host_id == UINT_MAX) {
453					yyerror("too many hosts defined");
454					YYERROR;
455				}
456				free($2);
457				$$ = r;
458			}
459		}
460		;
461
462timeout		: number
463		{
464			$$.tv_sec = $1 / 1000;
465			$$.tv_usec = ($1 % 1000) * 1000;
466		}
467		;
468
469optnl		: '\n' optnl
470		|
471		;
472
473nl		: '\n' optnl
474		;
475
476%%
477
478struct keywords {
479	const char	*k_name;
480	int		 k_val;
481};
482
483int
484yyerror(const char *fmt, ...)
485{
486	va_list	ap;
487
488	errors = 1;
489	va_start(ap, fmt);
490	fprintf(stderr, "%s:%d: ", infile, yylval.lineno);
491	vfprintf(stderr, fmt, ap);
492	fprintf(stderr, "\n");
493	va_end(ap);
494	return (0);
495}
496
497int
498kw_cmp(const void *k, const void *e)
499{
500
501	return (strcmp(k, ((const struct keywords *)e)->k_name));
502}
503
504int
505lookup(char *s)
506{
507	/* this has to be sorted always */
508	static const struct keywords keywords[] = {
509		{ "backup",		BACKUP },
510		{ "check",		CHECK },
511		{ "code",		CODE },
512		{ "digest",		DIGEST },
513		{ "disable",		DISABLE },
514		{ "expect",		EXPECT },
515		{ "external",		EXTERNAL },
516		{ "host",		HOST },
517		{ "http",		HTTP },
518		{ "icmp",		ICMP },
519		{ "interface",		INTERFACE },
520		{ "interval",		INTERVAL },
521		{ "ip",			IP },
522		{ "nothing",		NOTHING },
523		{ "port",		PORT },
524		{ "real",		REAL },
525		{ "send",		SEND },
526		{ "service",		SERVICE },
527		{ "sticky-address",	STICKYADDR },
528		{ "table",		TABLE },
529		{ "tag",		TAG },
530		{ "tcp",		TCP },
531		{ "timeout",		TIMEOUT },
532		{ "virtual",		VIRTUAL }
533	};
534	const struct keywords	*p;
535
536	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
537	    sizeof(keywords[0]), kw_cmp);
538
539	if (p)
540		return (p->k_val);
541	else
542		return (STRING);
543}
544
545#define MAXPUSHBACK	128
546
547char	*parsebuf;
548int	 parseindex;
549char	 pushback_buffer[MAXPUSHBACK];
550int	 pushback_index = 0;
551
552int
553lgetc(FILE *f, int *keep)
554{
555	int	c, next;
556
557	*keep = 0;
558	if (parsebuf) {
559		/* Read character from the parsebuffer instead of input. */
560		if (parseindex >= 0) {
561			c = parsebuf[parseindex++];
562			if (c != '\0')
563				return (c);
564			parsebuf = NULL;
565		} else
566			parseindex++;
567	}
568
569	if (pushback_index)
570		return (pushback_buffer[--pushback_index]);
571
572	while ((c = getc(f)) == '\\') {
573		next = getc(f);
574		if (next == 'n') {
575			*keep = 1;
576			c = '\n';
577			break;
578		} else if (next == 'r') {
579			*keep = 1;
580			c = '\r';
581			break;
582		} else if (next != '\n') {
583			c = next;
584			break;
585		}
586		yylval.lineno = lineno;
587		lineno++;
588	}
589	if (c == '\t' || c == ' ') {
590		/* Compress blanks to a single space. */
591		do {
592			c = getc(f);
593		} while (c == '\t' || c == ' ');
594		ungetc(c, f);
595		c = ' ';
596	}
597
598	return (c);
599}
600
601int
602lungetc(int c)
603{
604	if (c == EOF)
605		return (EOF);
606	if (parsebuf) {
607		parseindex--;
608		if (parseindex >= 0)
609			return (c);
610	}
611	if (pushback_index < MAXPUSHBACK-1)
612		return (pushback_buffer[pushback_index++] = c);
613	else
614		return (EOF);
615}
616
617int
618findeol(void)
619{
620	int	c;
621	int	k;
622
623	parsebuf = NULL;
624	pushback_index = 0;
625
626	/* skip to either EOF or the first real EOL */
627	while (1) {
628		c = lgetc(fin, &k);
629		if (c == '\n' && k == 0) {
630			lineno++;
631			break;
632		}
633		if (c == EOF)
634			break;
635	}
636	return (ERROR);
637}
638
639int
640yylex(void)
641{
642	char	 buf[8096];
643	char	*p, *val;
644	int	 endc, c;
645	int	 token;
646	int	 keep;
647
648top:
649	p = buf;
650	while ((c = lgetc(fin, &keep)) == ' ')
651		; /* nothing */
652
653	yylval.lineno = lineno;
654	if (c == '#')
655		do {
656			while ((c = lgetc(fin, &keep)) != '\n' && c != EOF)
657				; /* nothing */
658		} while (keep == 1);
659	if (c == '$' && parsebuf == NULL) {
660		while (1) {
661			if ((c = lgetc(fin, &keep)) == EOF)
662				return (0);
663
664			if (p + 1 >= buf + sizeof(buf) - 1) {
665				yyerror("string too long");
666				return (findeol());
667			}
668			if (isalnum(c) || c == '_') {
669				*p++ = (char)c;
670				continue;
671			}
672			*p = '\0';
673			lungetc(c);
674			break;
675		}
676		val = symget(buf);
677		if (val == NULL) {
678			yyerror("macro '%s' not defined", buf);
679			return (findeol());
680		}
681		parsebuf = val;
682		parseindex = 0;
683		goto top;
684	}
685
686	switch (c) {
687	case '\'':
688	case '"':
689		endc = c;
690		while (1) {
691			if ((c = lgetc(fin, &keep)) == EOF)
692				return (0);
693			if (c == endc) {
694				*p = '\0';
695				break;
696			}
697			if (c == '\n' && keep == 0) {
698				lineno++;
699				continue;
700			}
701			if (p + 1 >= buf + sizeof(buf) - 1) {
702				yyerror("string too long");
703				return (findeol());
704			}
705			*p++ = (char)c;
706		}
707		yylval.v.string = strdup(buf);
708		if (yylval.v.string == NULL)
709			errx(1, "yylex: strdup");
710		return (STRING);
711	}
712
713#define allowed_in_string(x) \
714	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
715	x != '{' && x != '}' && \
716	x != '!' && x != '=' && x != '#' && \
717	x != ','))
718
719	if (isalnum(c) || c == ':' || c == '_') {
720		do {
721			*p++ = c;
722			if ((unsigned)(p-buf) >= sizeof(buf)) {
723				yyerror("string too long");
724				return (findeol());
725			}
726		} while ((c = lgetc(fin, &keep)) != EOF &&
727		    (allowed_in_string(c)));
728		lungetc(c);
729		*p = '\0';
730		if ((token = lookup(buf)) == STRING)
731			if ((yylval.v.string = strdup(buf)) == NULL)
732				err(1, "yylex: strdup");
733		return (token);
734	}
735	if (c == '\n') {
736		yylval.lineno = lineno;
737		lineno++;
738	}
739	if (c == EOF)
740		return (0);
741	return (c);
742}
743
744int
745parse_config(struct hoststated *x_conf, const char *filename, int opts)
746{
747	struct sym	*sym, *next;
748
749	conf = x_conf;
750
751	TAILQ_INIT(&conf->services);
752	TAILQ_INIT(&conf->tables);
753	memset(&conf->empty_table, 0, sizeof(conf->empty_table));
754	conf->empty_table.id = EMPTY_TABLE;
755	conf->empty_table.flags |= F_DISABLE;
756	(void)strlcpy(conf->empty_table.name, "empty",
757	    sizeof(conf->empty_table.name));
758
759	conf->timeout.tv_sec = CHECK_TIMEOUT / 1000;
760	conf->timeout.tv_usec = (CHECK_TIMEOUT % 1000) * 1000;
761	conf->interval.tv_sec = CHECK_INTERVAL;
762	conf->interval.tv_usec = 0;
763	conf->opts = opts;
764
765	if ((fin = fopen(filename, "r")) == NULL) {
766		warn("%s", filename);
767		return (0);
768	}
769	infile = filename;
770	setservent(1);
771	yyparse();
772	endservent();
773	fclose(fin);
774
775	/* Free macros and check which have not been used. */
776	for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
777		next = TAILQ_NEXT(sym, entries);
778		if ((conf->opts & HOSTSTATED_OPT_VERBOSE) && !sym->used)
779			fprintf(stderr, "warning: macro '%s' not "
780			    "used\n", sym->nam);
781		if (!sym->persist) {
782			free(sym->nam);
783			free(sym->val);
784			TAILQ_REMOVE(&symhead, sym, entries);
785			free(sym);
786		}
787	}
788
789	if (TAILQ_EMPTY(&conf->services)) {
790		log_warnx("no services, nothing to do");
791		errors++;
792	}
793
794	if (timercmp(&conf->timeout, &conf->interval, >=)) {
795		log_warnx("global timeout exceeds interval");
796		errors++;
797	}
798
799	/* Verify that every table is used */
800	TAILQ_FOREACH(table, &conf->tables, entry) {
801		if (!(table->flags & F_USED)) {
802			log_warnx("unused table: %s", table->name);
803			errors++;
804		}
805		if (timercmp(&table->timeout, &conf->interval, >=)) {
806			log_warnx("table timeout exceeds interval: %s",
807			    table->name);
808			errors++;
809		}
810	}
811
812	if (errors) {
813		bzero(&conf, sizeof (*conf));
814		return (-1);
815	}
816
817	return (0);
818}
819
820int
821symset(const char *nam, const char *val, int persist)
822{
823	struct sym	*sym;
824
825	for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
826	    sym = TAILQ_NEXT(sym, entries))
827		;	/* nothing */
828
829	if (sym != NULL) {
830		if (sym->persist == 1)
831			return (0);
832		else {
833			free(sym->nam);
834			free(sym->val);
835			TAILQ_REMOVE(&symhead, sym, entries);
836			free(sym);
837		}
838	}
839	if ((sym = calloc(1, sizeof(*sym))) == NULL)
840		return (-1);
841
842	sym->nam = strdup(nam);
843	if (sym->nam == NULL) {
844		free(sym);
845		return (-1);
846	}
847	sym->val = strdup(val);
848	if (sym->val == NULL) {
849		free(sym->nam);
850		free(sym);
851		return (-1);
852	}
853	sym->used = 0;
854	sym->persist = persist;
855	TAILQ_INSERT_TAIL(&symhead, sym, entries);
856	return (0);
857}
858
859int
860cmdline_symset(char *s)
861{
862	char	*sym, *val;
863	int	ret;
864	size_t	len;
865
866	if ((val = strrchr(s, '=')) == NULL)
867		return (-1);
868
869	len = strlen(s) - strlen(val) + 1;
870	if ((sym = malloc(len)) == NULL)
871		errx(1, "cmdline_symset: malloc");
872
873	strlcpy(sym, s, len);
874
875	ret = symset(sym, val + 1, 1);
876	free(sym);
877
878	return (ret);
879}
880
881char *
882symget(const char *nam)
883{
884	struct sym	*sym;
885
886	TAILQ_FOREACH(sym, &symhead, entries)
887		if (strcmp(nam, sym->nam) == 0) {
888			sym->used = 1;
889			return (sym->val);
890		}
891	return (NULL);
892}
893
894struct address *
895host_v4(const char *s)
896{
897	struct in_addr		 ina;
898	struct sockaddr_in	*sain;
899	struct address		*h;
900
901	bzero(&ina, sizeof(ina));
902	if (inet_pton(AF_INET, s, &ina) != 1)
903		return (NULL);
904
905	if ((h = calloc(1, sizeof(*h))) == NULL)
906		fatal(NULL);
907	sain = (struct sockaddr_in *)&h->ss;
908	sain->sin_len = sizeof(struct sockaddr_in);
909	sain->sin_family = AF_INET;
910	sain->sin_addr.s_addr = ina.s_addr;
911
912	return (h);
913}
914
915struct address *
916host_v6(const char *s)
917{
918	struct in6_addr		 ina6;
919	struct sockaddr_in6	*sin6;
920	struct address		*h;
921
922	bzero(&ina6, sizeof(ina6));
923	if (inet_pton(AF_INET6, s, &ina6) != 1)
924		return (NULL);
925
926	if ((h = calloc(1, sizeof(*h))) == NULL)
927		fatal(NULL);
928	sin6 = (struct sockaddr_in6 *)&h->ss;
929	sin6->sin6_len = sizeof(struct sockaddr_in6);
930	sin6->sin6_family = AF_INET6;
931	memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6));
932
933	return (h);
934}
935
936int
937host_dns(const char *s, struct addresslist *al, int max,
938	 in_port_t port, const char *ifname)
939{
940	struct addrinfo		 hints, *res0, *res;
941	int			 error, cnt = 0;
942	struct sockaddr_in	*sain;
943	struct sockaddr_in6	*sin6;
944	struct address		*h;
945
946	bzero(&hints, sizeof(hints));
947	hints.ai_family = PF_UNSPEC;
948	hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
949	error = getaddrinfo(s, NULL, &hints, &res0);
950	if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
951		return (0);
952	if (error) {
953		log_warnx("host_dns: could not parse \"%s\": %s", s,
954		    gai_strerror(error));
955		return (-1);
956	}
957
958	for (res = res0; res && cnt < max; res = res->ai_next) {
959		if (res->ai_family != AF_INET &&
960		    res->ai_family != AF_INET6)
961			continue;
962		if ((h = calloc(1, sizeof(*h))) == NULL)
963			fatal(NULL);
964
965		h->port = port;
966		if (ifname != NULL) {
967			if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
968			    sizeof(h->ifname))
969				log_warnx("host_dns: interface name truncated");
970			return (-1);
971		}
972		h->ss.ss_family = res->ai_family;
973		if (res->ai_family == AF_INET) {
974			sain = (struct sockaddr_in *)&h->ss;
975			sain->sin_len = sizeof(struct sockaddr_in);
976			sain->sin_addr.s_addr = ((struct sockaddr_in *)
977			    res->ai_addr)->sin_addr.s_addr;
978		} else {
979			sin6 = (struct sockaddr_in6 *)&h->ss;
980			sin6->sin6_len = sizeof(struct sockaddr_in6);
981			memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
982			    res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
983		}
984
985		TAILQ_INSERT_HEAD(al, h, entry);
986		cnt++;
987	}
988	if (cnt == max && res) {
989		log_warnx("host_dns: %s resolves to more than %d hosts",
990		    s, max);
991	}
992	freeaddrinfo(res0);
993	return (cnt);
994}
995
996int
997host(const char *s, struct addresslist *al, int max,
998    in_port_t port, const char *ifname)
999{
1000	struct address *h;
1001
1002	h = host_v4(s);
1003
1004	/* IPv6 address? */
1005	if (h == NULL)
1006		h = host_v6(s);
1007
1008	if (h != NULL) {
1009		h->port = port;
1010		if (ifname != NULL) {
1011			if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
1012			    sizeof(h->ifname)) {
1013				log_warnx("host: interface name truncated");
1014				return (-1);
1015			}
1016		}
1017
1018		TAILQ_INSERT_HEAD(al, h, entry);
1019		return (1);
1020	}
1021
1022	return (host_dns(s, al, max, port, ifname));
1023}
1024