xref: /openbsd-src/usr.sbin/smtpd/table.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: table.c,v 1.23 2016/01/04 13:30:20 jung Exp $	*/
2 
3 /*
4  * Copyright (c) 2013 Eric Faurot <eric@openbsd.org>
5  * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/types.h>
21 #include <sys/queue.h>
22 #include <sys/tree.h>
23 #include <sys/socket.h>
24 #include <sys/stat.h>
25 
26 #include <netinet/in.h>
27 #include <arpa/inet.h>
28 #include <net/if.h>
29 
30 #include <ctype.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <event.h>
34 #include <imsg.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <netdb.h>
38 #include <limits.h>
39 #include <string.h>
40 #include <unistd.h>
41 
42 #include "smtpd.h"
43 #include "log.h"
44 
45 struct table_backend *table_backend_lookup(const char *);
46 
47 extern struct table_backend table_backend_static;
48 extern struct table_backend table_backend_db;
49 extern struct table_backend table_backend_getpwnam;
50 extern struct table_backend table_backend_proc;
51 
52 static const char * table_service_name(enum table_service);
53 static const char * table_backend_name(struct table_backend *);
54 static const char * table_dump_lookup(enum table_service, union lookup *);
55 static int parse_sockaddr(struct sockaddr *, int, const char *);
56 
57 static unsigned int last_table_id = 0;
58 
59 struct table_backend *
60 table_backend_lookup(const char *backend)
61 {
62 	if (!strcmp(backend, "static") || !strcmp(backend, "file"))
63 		return &table_backend_static;
64 	if (!strcmp(backend, "db"))
65 		return &table_backend_db;
66 	if (!strcmp(backend, "getpwnam"))
67 		return &table_backend_getpwnam;
68 	if (!strcmp(backend, "proc"))
69 		return &table_backend_proc;
70 	return NULL;
71 }
72 
73 static const char *
74 table_backend_name(struct table_backend *backend)
75 {
76 	if (backend == &table_backend_static)
77 		return "static";
78 	if (backend == &table_backend_db)
79 		return "db";
80 	if (backend == &table_backend_getpwnam)
81 		return "getpwnam";
82 	if (backend == &table_backend_proc)
83 		return "proc";
84 	return "???";
85 }
86 
87 static const char *
88 table_service_name(enum table_service s)
89 {
90 	switch (s) {
91 	case K_NONE:		return "NONE";
92 	case K_ALIAS:		return "ALIAS";
93 	case K_DOMAIN:		return "DOMAIN";
94 	case K_CREDENTIALS:	return "CREDENTIALS";
95 	case K_NETADDR:		return "NETADDR";
96 	case K_USERINFO:	return "USERINFO";
97 	case K_SOURCE:		return "SOURCE";
98 	case K_MAILADDR:	return "MAILADDR";
99 	case K_ADDRNAME:	return "ADDRNAME";
100 	case K_MAILADDRMAP:	return "MAILADDRMAP";
101 	default:		return "???";
102 	}
103 }
104 
105 struct table *
106 table_find(const char *name, const char *tag)
107 {
108 	char buf[LINE_MAX];
109 
110 	if (tag == NULL)
111 		return dict_get(env->sc_tables_dict, name);
112 
113 	if ((size_t)snprintf(buf, sizeof(buf), "%s#%s", name, tag) >= sizeof(buf)) {
114 		log_warnx("warn: table name too long: %s#%s", name, tag);
115 		return (NULL);
116 	}
117 
118 	return dict_get(env->sc_tables_dict, buf);
119 }
120 
121 int
122 table_lookup(struct table *table, struct dict *params, const char *key, enum table_service kind,
123     union lookup *lk)
124 {
125 	int	r;
126 	char	lkey[1024];
127 
128 	if (table->t_backend->lookup == NULL)
129 		return (-1);
130 
131 	if (!lowercase(lkey, key, sizeof lkey)) {
132 		log_warnx("warn: lookup key too long: %s", key);
133 		return -1;
134 	}
135 
136 	r = table->t_backend->lookup(table->t_handle, params, lkey, kind, lk);
137 
138 	if (r == 1)
139 		log_trace(TRACE_LOOKUP, "lookup: %s \"%s\" as %s in table %s:%s -> %s%s%s",
140 		    lk ? "lookup" : "check",
141 		    lkey,
142 		    table_service_name(kind),
143 		    table_backend_name(table->t_backend),
144 		    table->t_name,
145 		    lk ? "\"" : "",
146 		    (lk) ? table_dump_lookup(kind, lk): "found",
147 		    lk ? "\"" : "");
148 	else
149 		log_trace(TRACE_LOOKUP, "lookup: %s \"%s\" as %s in table %s:%s -> %d",
150 		    lk ? "lookup" : "check",
151 		    lkey,
152 		    table_service_name(kind),
153 		    table_backend_name(table->t_backend),
154 		    table->t_name,
155 		    r);
156 
157 	return (r);
158 }
159 
160 int
161 table_fetch(struct table *table, struct dict *params, enum table_service kind, union lookup *lk)
162 {
163 	int 	r;
164 
165 	if (table->t_backend->fetch == NULL)
166 		return (-1);
167 
168 	r = table->t_backend->fetch(table->t_handle, params, kind, lk);
169 
170 	if (r == 1)
171 		log_trace(TRACE_LOOKUP, "lookup: fetch %s from table %s:%s -> %s%s%s",
172 		    table_service_name(kind),
173 		    table_backend_name(table->t_backend),
174 		    table->t_name,
175 		    lk ? "\"" : "",
176 		    (lk) ? table_dump_lookup(kind, lk): "found",
177 		    lk ? "\"" : "");
178 	else
179 		log_trace(TRACE_LOOKUP, "lookup: fetch %s from table %s:%s -> %d",
180 		    table_service_name(kind),
181 		    table_backend_name(table->t_backend),
182 		    table->t_name,
183 		    r);
184 
185 	return (r);
186 }
187 
188 struct table *
189 table_create(const char *backend, const char *name, const char *tag,
190     const char *config)
191 {
192 	struct table		*t;
193 	struct table_backend	*tb;
194 	char			 buf[LINE_MAX];
195 	char			 path[LINE_MAX];
196 	size_t			 n;
197 	struct stat		 sb;
198 
199 	if (name && tag) {
200 		if ((size_t)snprintf(buf, sizeof(buf), "%s#%s", name, tag) >=
201 		    sizeof(buf))
202 			fatalx("table_create: name too long \"%s#%s\"",
203 			    name, tag);
204 		name = buf;
205 	}
206 
207 	if (name && table_find(name, NULL))
208 		fatalx("table_create: table \"%s\" already defined", name);
209 
210 	if ((tb = table_backend_lookup(backend)) == NULL) {
211 		if ((size_t)snprintf(path, sizeof(path), PATH_LIBEXEC"/table-%s",
212 			backend) >= sizeof(path)) {
213 			fatalx("table_create: path too long \""
214 			    PATH_LIBEXEC"/table-%s\"", backend);
215 		}
216 		if (stat(path, &sb) == 0) {
217 			tb = table_backend_lookup("proc");
218 			(void)strlcpy(path, backend, sizeof(path));
219 			if (config) {
220 				(void)strlcat(path, ":", sizeof(path));
221 				if (strlcat(path, config, sizeof(path))
222 				    >= sizeof(path))
223 					fatalx("table_create: config file path too long");
224 			}
225 			config = path;
226 		}
227 	}
228 
229 	if (tb == NULL)
230 		fatalx("table_create: backend \"%s\" does not exist", backend);
231 
232 	t = xcalloc(1, sizeof(*t), "table_create");
233 	t->t_backend = tb;
234 
235 	/* XXX */
236 	/*
237 	 * until people forget about it, "file" really means "static"
238 	 */
239 	if (!strcmp(backend, "file"))
240 		backend = "static";
241 
242 	if (config) {
243 		if (strlcpy(t->t_config, config, sizeof t->t_config)
244 		    >= sizeof t->t_config)
245 			fatalx("table_create: table config \"%s\" too large",
246 			    t->t_config);
247 	}
248 
249 	if (strcmp(backend, "static") != 0)
250 		t->t_type = T_DYNAMIC;
251 
252 	if (name == NULL)
253 		(void)snprintf(t->t_name, sizeof(t->t_name), "<dynamic:%u>",
254 		    last_table_id++);
255 	else {
256 		n = strlcpy(t->t_name, name, sizeof(t->t_name));
257 		if (n >= sizeof(t->t_name))
258 			fatalx("table_create: table name too long");
259 	}
260 
261 	dict_init(&t->t_dict);
262 	dict_set(env->sc_tables_dict, t->t_name, t);
263 
264 	return (t);
265 }
266 
267 void
268 table_destroy(struct table *t)
269 {
270 	void	*p = NULL;
271 
272 	while (dict_poproot(&t->t_dict, (void **)&p))
273 		free(p);
274 
275 	dict_xpop(env->sc_tables_dict, t->t_name);
276 	free(t);
277 }
278 
279 int
280 table_config(struct table *t)
281 {
282 	if (t->t_backend->config == NULL)
283 		return (1);
284 	return (t->t_backend->config(t));
285 }
286 
287 void
288 table_add(struct table *t, const char *key, const char *val)
289 {
290 	char	lkey[1024], *old;
291 
292 	if (t->t_type & T_DYNAMIC)
293 		fatalx("table_add: cannot add to table");
294 
295 	if (!lowercase(lkey, key, sizeof lkey)) {
296 		log_warnx("warn: lookup key too long: %s", key);
297 		return;
298 	}
299 
300 	old = dict_set(&t->t_dict, lkey, val ? xstrdup(val, "table_add") : NULL);
301 	if (old) {
302 		log_warnx("warn: duplicate key \"%s\" in static table \"%s\"",
303 		    lkey, t->t_name);
304 		free(old);
305 	}
306 }
307 
308 int
309 table_check_type(struct table *t, uint32_t mask)
310 {
311 	return t->t_type & mask;
312 }
313 
314 int
315 table_check_service(struct table *t, uint32_t mask)
316 {
317 	return t->t_backend->services & mask;
318 }
319 
320 int
321 table_check_use(struct table *t, uint32_t tmask, uint32_t smask)
322 {
323 	return table_check_type(t, tmask) && table_check_service(t, smask);
324 }
325 
326 int
327 table_open(struct table *t)
328 {
329 	t->t_handle = NULL;
330 	if (t->t_backend->open == NULL)
331 		return (1);
332 	t->t_handle = t->t_backend->open(t);
333 	if (t->t_handle == NULL)
334 		return (0);
335 	return (1);
336 }
337 
338 void
339 table_close(struct table *t)
340 {
341 	if (t->t_backend->close)
342 		t->t_backend->close(t->t_handle);
343 }
344 
345 int
346 table_update(struct table *t)
347 {
348 	if (t->t_backend->update == NULL)
349 		return (1);
350 	return (t->t_backend->update(t));
351 }
352 
353 
354 /*
355  * quick reminder:
356  * in *_match() s1 comes from session, s2 comes from table
357  */
358 
359 int
360 table_domain_match(const char *s1, const char *s2)
361 {
362 	return hostname_match(s1, s2);
363 }
364 
365 int
366 table_mailaddr_match(const char *s1, const char *s2)
367 {
368 	struct mailaddr m1;
369 	struct mailaddr m2;
370 
371 	if (!text_to_mailaddr(&m1, s1))
372 		return 0;
373 	if (!text_to_mailaddr(&m2, s2))
374 		return 0;
375 	return mailaddr_match(&m1, &m2);
376 }
377 
378 static int table_match_mask(struct sockaddr_storage *, struct netaddr *);
379 static int table_inet4_match(struct sockaddr_in *, struct netaddr *);
380 static int table_inet6_match(struct sockaddr_in6 *, struct netaddr *);
381 
382 int
383 table_netaddr_match(const char *s1, const char *s2)
384 {
385 	struct netaddr n1;
386 	struct netaddr n2;
387 
388 	if (strcasecmp(s1, s2) == 0)
389 		return 1;
390 	if (!text_to_netaddr(&n1, s1))
391 		return 0;
392 	if (!text_to_netaddr(&n2, s2))
393 		return 0;
394 	if (n1.ss.ss_family != n2.ss.ss_family)
395 		return 0;
396 	if (n1.ss.ss_len != n2.ss.ss_len)
397 		return 0;
398 	return table_match_mask(&n1.ss, &n2);
399 }
400 
401 static int
402 table_match_mask(struct sockaddr_storage *ss, struct netaddr *ssmask)
403 {
404 	if (ss->ss_family == AF_INET)
405 		return table_inet4_match((struct sockaddr_in *)ss, ssmask);
406 
407 	if (ss->ss_family == AF_INET6)
408 		return table_inet6_match((struct sockaddr_in6 *)ss, ssmask);
409 
410 	return (0);
411 }
412 
413 static int
414 table_inet4_match(struct sockaddr_in *ss, struct netaddr *ssmask)
415 {
416 	in_addr_t mask;
417 	int i;
418 
419 	/* a.b.c.d/8 -> htonl(0xff000000) */
420 	mask = 0;
421 	for (i = 0; i < ssmask->bits; ++i)
422 		mask = (mask >> 1) | 0x80000000;
423 	mask = htonl(mask);
424 
425 	/* (addr & mask) == (net & mask) */
426 	if ((ss->sin_addr.s_addr & mask) ==
427 	    (((struct sockaddr_in *)ssmask)->sin_addr.s_addr & mask))
428 		return 1;
429 
430 	return 0;
431 }
432 
433 static int
434 table_inet6_match(struct sockaddr_in6 *ss, struct netaddr *ssmask)
435 {
436 	struct in6_addr	*in;
437 	struct in6_addr	*inmask;
438 	struct in6_addr	 mask;
439 	int		 i;
440 
441 	memset(&mask, 0, sizeof(mask));
442 	for (i = 0; i < ssmask->bits / 8; i++)
443 		mask.s6_addr[i] = 0xff;
444 	i = ssmask->bits % 8;
445 	if (i)
446 		mask.s6_addr[ssmask->bits / 8] = 0xff00 >> i;
447 
448 	in = &ss->sin6_addr;
449 	inmask = &((struct sockaddr_in6 *)&ssmask->ss)->sin6_addr;
450 
451 	for (i = 0; i < 16; i++) {
452 		if ((in->s6_addr[i] & mask.s6_addr[i]) !=
453 		    (inmask->s6_addr[i] & mask.s6_addr[i]))
454 			return (0);
455 	}
456 
457 	return (1);
458 }
459 
460 void
461 table_dump_all(void)
462 {
463 	struct table	*t;
464 	void		*iter, *i2;
465 	const char 	*key, *sep;
466 	char		*value;
467 	char		 buf[1024];
468 
469 	iter = NULL;
470 	while (dict_iter(env->sc_tables_dict, &iter, NULL, (void **)&t)) {
471 		i2 = NULL;
472 		sep = "";
473  		buf[0] = '\0';
474 		if (t->t_type & T_DYNAMIC) {
475 			(void)strlcat(buf, "DYNAMIC", sizeof(buf));
476 			sep = ",";
477 		}
478 		if (t->t_type & T_LIST) {
479 			(void)strlcat(buf, sep, sizeof(buf));
480 			(void)strlcat(buf, "LIST", sizeof(buf));
481 			sep = ",";
482 		}
483 		if (t->t_type & T_HASH) {
484 			(void)strlcat(buf, sep, sizeof(buf));
485 			(void)strlcat(buf, "HASH", sizeof(buf));
486 			sep = ",";
487 		}
488 		log_debug("TABLE \"%s\" type=%s config=\"%s\"",
489 		    t->t_name, buf, t->t_config);
490 		while(dict_iter(&t->t_dict, &i2, &key, (void**)&value)) {
491 			if (value)
492 				log_debug("	\"%s\" -> \"%s\"", key, value);
493 			else
494 				log_debug("	\"%s\"", key);
495 		}
496 	}
497 }
498 
499 void
500 table_open_all(void)
501 {
502 	struct table	*t;
503 	void		*iter;
504 
505 	iter = NULL;
506 	while (dict_iter(env->sc_tables_dict, &iter, NULL, (void **)&t))
507 		if (!table_open(t))
508 			fatalx("failed to open table %s", t->t_name);
509 }
510 
511 void
512 table_close_all(void)
513 {
514 	struct table	*t;
515 	void		*iter;
516 
517 	iter = NULL;
518 	while (dict_iter(env->sc_tables_dict, &iter, NULL, (void **)&t))
519 		table_close(t);
520 }
521 
522 int
523 table_parse_lookup(enum table_service service, const char *key,
524     const char *line, union lookup *lk)
525 {
526 	char	buffer[LINE_MAX], *p;
527 	size_t	len;
528 
529 	len = strlen(line);
530 
531 	switch (service) {
532 	case K_ALIAS:
533 		lk->expand = calloc(1, sizeof(*lk->expand));
534 		if (lk->expand == NULL)
535 			return (-1);
536 		if (!expand_line(lk->expand, line, 1)) {
537 			expand_free(lk->expand);
538 			return (-1);
539 		}
540 		return (1);
541 
542 	case K_DOMAIN:
543 		if (strlcpy(lk->domain.name, line, sizeof(lk->domain.name))
544 		    >= sizeof(lk->domain.name))
545 			return (-1);
546 		return (1);
547 
548 	case K_CREDENTIALS:
549 
550 		/* credentials are stored as user:password */
551 		if (len < 3)
552 			return (-1);
553 
554 		/* too big to fit in a smtp session line */
555 		if (len >= LINE_MAX)
556 			return (-1);
557 
558 		p = strchr(line, ':');
559 		if (p == NULL) {
560 			if (strlcpy(lk->creds.username, key, sizeof (lk->creds.username))
561 			    >= sizeof (lk->creds.username))
562 				return (-1);
563 			if (strlcpy(lk->creds.password, line, sizeof(lk->creds.password))
564 			    >= sizeof(lk->creds.password))
565 				return (-1);
566 			return (1);
567 		}
568 
569 		if (p == line || p == line + len - 1)
570 			return (-1);
571 
572 		memmove(lk->creds.username, line, p - line);
573 		lk->creds.username[p - line] = '\0';
574 
575 		if (strlcpy(lk->creds.password, p+1, sizeof(lk->creds.password))
576 		    >= sizeof(lk->creds.password))
577 			return (-1);
578 
579 		return (1);
580 
581 	case K_NETADDR:
582 		if (!text_to_netaddr(&lk->netaddr, line))
583 			return (-1);
584 		return (1);
585 
586 	case K_USERINFO:
587 		if (!bsnprintf(buffer, sizeof(buffer), "%s:%s", key, line))
588 			return (-1);
589 		if (!text_to_userinfo(&lk->userinfo, buffer))
590 			return (-1);
591  		return (1);
592 
593 	case K_SOURCE:
594 		if (parse_sockaddr((struct sockaddr *)&lk->source.addr,
595 		    PF_UNSPEC, line) == -1)
596 			return (-1);
597 		return (1);
598 
599 	case K_MAILADDR:
600 		if (!text_to_mailaddr(&lk->mailaddr, line))
601 			return (-1);
602 		return (1);
603 
604 	case K_MAILADDRMAP:
605 		lk->maddrmap = calloc(1, sizeof(*lk->maddrmap));
606 		if (lk->maddrmap == NULL)
607 			return (-1);
608 		maddrmap_init(lk->maddrmap);
609 		if (!mailaddr_line(lk->maddrmap, line)) {
610 			maddrmap_free(lk->maddrmap);
611 			return (-1);
612 		}
613 		return (1);
614 
615 	case K_ADDRNAME:
616 		if (parse_sockaddr((struct sockaddr *)&lk->addrname.addr,
617 		    PF_UNSPEC, key) == -1)
618 			return (-1);
619 		if (strlcpy(lk->addrname.name, line, sizeof(lk->addrname.name))
620 		    >= sizeof(lk->addrname.name))
621 			return (-1);
622 		return (1);
623 
624 	default:
625 		return (-1);
626 	}
627 }
628 
629 static const char *
630 table_dump_lookup(enum table_service s, union lookup *lk)
631 {
632 	static char	buf[LINE_MAX];
633 	int		ret;
634 
635 	switch (s) {
636 	case K_NONE:
637 		break;
638 
639 	case K_ALIAS:
640 		expand_to_text(lk->expand, buf, sizeof(buf));
641 		break;
642 
643 	case K_DOMAIN:
644 		ret = snprintf(buf, sizeof(buf), "%s", lk->domain.name);
645 		if (ret == -1 || (size_t)ret >= sizeof (buf))
646 			goto err;
647 		break;
648 
649 	case K_CREDENTIALS:
650 		ret = snprintf(buf, sizeof(buf), "%s:%s",
651 		    lk->creds.username, lk->creds.password);
652 		if (ret == -1 || (size_t)ret >= sizeof (buf))
653 			goto err;
654 		break;
655 
656 	case K_NETADDR:
657 		ret = snprintf(buf, sizeof(buf), "%s/%d",
658 		    sockaddr_to_text((struct sockaddr *)&lk->netaddr.ss),
659 		    lk->netaddr.bits);
660 		if (ret == -1 || (size_t)ret >= sizeof (buf))
661 			goto err;
662 		break;
663 
664 	case K_USERINFO:
665 		ret = snprintf(buf, sizeof(buf), "%s:%d:%d:%s",
666 		    lk->userinfo.username,
667 		    lk->userinfo.uid,
668 		    lk->userinfo.gid,
669 		    lk->userinfo.directory);
670 		if (ret == -1 || (size_t)ret >= sizeof (buf))
671 			goto err;
672 		break;
673 
674 	case K_SOURCE:
675 		ret = snprintf(buf, sizeof(buf), "%s",
676 		    ss_to_text(&lk->source.addr));
677 		if (ret == -1 || (size_t)ret >= sizeof (buf))
678 			goto err;
679 		break;
680 
681 	case K_MAILADDR:
682 		ret = snprintf(buf, sizeof(buf), "%s@%s",
683 		    lk->mailaddr.user,
684 		    lk->mailaddr.domain);
685 		if (ret == -1 || (size_t)ret >= sizeof (buf))
686 			goto err;
687 		break;
688 
689 	case K_ADDRNAME:
690 		ret = snprintf(buf, sizeof(buf), "%s",
691 		    lk->addrname.name);
692 		if (ret == -1 || (size_t)ret >= sizeof (buf))
693 			goto err;
694 		break;
695 
696 	default:
697 		break;
698 	}
699 
700 	return (buf);
701 
702 err:
703 	return (NULL);
704 }
705 
706 
707 static int
708 parse_sockaddr(struct sockaddr *sa, int family, const char *str)
709 {
710 	struct in_addr		 ina;
711 	struct in6_addr		 in6a;
712 	struct sockaddr_in	*sin;
713 	struct sockaddr_in6	*sin6;
714 	char			*cp, *str2;
715 	const char		*errstr;
716 
717 	switch (family) {
718 	case PF_UNSPEC:
719 		if (parse_sockaddr(sa, PF_INET, str) == 0)
720 			return (0);
721 		return parse_sockaddr(sa, PF_INET6, str);
722 
723 	case PF_INET:
724 		if (inet_pton(PF_INET, str, &ina) != 1)
725 			return (-1);
726 
727 		sin = (struct sockaddr_in *)sa;
728 		memset(sin, 0, sizeof *sin);
729 		sin->sin_len = sizeof(struct sockaddr_in);
730 		sin->sin_family = PF_INET;
731 		sin->sin_addr.s_addr = ina.s_addr;
732 		return (0);
733 
734 	case PF_INET6:
735 		if (strncasecmp("ipv6:", str, 5) == 0)
736 			str += 5;
737 		cp = strchr(str, SCOPE_DELIMITER);
738 		if (cp) {
739 			str2 = strdup(str);
740 			if (str2 == NULL)
741 				return (-1);
742 			str2[cp - str] = '\0';
743 			if (inet_pton(PF_INET6, str2, &in6a) != 1) {
744 				free(str2);
745 				return (-1);
746 			}
747 			cp++;
748 			free(str2);
749 		} else if (inet_pton(PF_INET6, str, &in6a) != 1)
750 			return (-1);
751 
752 		sin6 = (struct sockaddr_in6 *)sa;
753 		memset(sin6, 0, sizeof *sin6);
754 		sin6->sin6_len = sizeof(struct sockaddr_in6);
755 		sin6->sin6_family = PF_INET6;
756 		sin6->sin6_addr = in6a;
757 
758 		if (cp == NULL)
759 			return (0);
760 
761 		if (IN6_IS_ADDR_LINKLOCAL(&in6a) ||
762 		    IN6_IS_ADDR_MC_LINKLOCAL(&in6a) ||
763 		    IN6_IS_ADDR_MC_INTFACELOCAL(&in6a))
764 			if ((sin6->sin6_scope_id = if_nametoindex(cp)))
765 				return (0);
766 
767 		sin6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr);
768 		if (errstr)
769 			return (-1);
770 		return (0);
771 
772 	default:
773 		break;
774 	}
775 
776 	return (-1);
777 }
778