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