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