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