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