1 /*	$OpenBSD: to.c,v 1.44 2019/11/12 20:21:46 gilles Exp $	*/
2 
3 /*
4  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
5  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
6  * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
7  *
8  * Permission to use, copy, modify, and distribute this software for any
9  * purpose with or without fee is hereby granted, provided that the above
10  * copyright notice and this permission notice appear in all copies.
11  *
12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19  */
20 
21 #include "includes.h"
22 
23 #include <sys/types.h>
24 #include <sys/queue.h>
25 #include <sys/tree.h>
26 #include <sys/socket.h>
27 #include <sys/stat.h>
28 #include <sys/resource.h>
29 
30 #include <netinet/in.h>
31 #include <arpa/inet.h>
32 
33 #include <ctype.h>
34 #include <err.h>
35 #include <errno.h>
36 #include <event.h>
37 #include <fcntl.h>
38 #include <imsg.h>
39 #include <limits.h>
40 #include <inttypes.h>
41 #include <netdb.h>
42 #include <pwd.h>
43 #include <stdarg.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <time.h>
48 #include <unistd.h>
49 
50 #include "smtpd.h"
51 #include "log.h"
52 
53 static const char *in6addr_to_text(const struct in6_addr *);
54 static int alias_is_filter(struct expandnode *, const char *, size_t);
55 static int alias_is_username(struct expandnode *, const char *, size_t);
56 static int alias_is_address(struct expandnode *, const char *, size_t);
57 static int alias_is_filename(struct expandnode *, const char *, size_t);
58 static int alias_is_include(struct expandnode *, const char *, size_t);
59 static int alias_is_error(struct expandnode *, const char *, size_t);
60 
61 static int broken_inet_net_pton_ipv6(const char *, void *, size_t);
62 
63 const char *
sockaddr_to_text(struct sockaddr * sa)64 sockaddr_to_text(struct sockaddr *sa)
65 {
66 	static char	buf[NI_MAXHOST];
67 
68 	if (getnameinfo(sa, SA_LEN(sa), buf, sizeof(buf), NULL, 0,
69 	    NI_NUMERICHOST))
70 		return ("(unknown)");
71 	else
72 		return (buf);
73 }
74 
75 static const char *
in6addr_to_text(const struct in6_addr * addr)76 in6addr_to_text(const struct in6_addr *addr)
77 {
78 	struct sockaddr_in6	sa_in6;
79 	uint16_t		tmp16;
80 
81 	memset(&sa_in6, 0, sizeof(sa_in6));
82 #ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN
83 	sa_in6.sin6_len = sizeof(sa_in6);
84 #endif
85 	sa_in6.sin6_family = AF_INET6;
86 	memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr));
87 
88 	/* XXX thanks, KAME, for this ugliness... adopted from route/show.c */
89 	if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) ||
90 	    IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) {
91 		memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16));
92 		sa_in6.sin6_scope_id = ntohs(tmp16);
93 		sa_in6.sin6_addr.s6_addr[2] = 0;
94 		sa_in6.sin6_addr.s6_addr[3] = 0;
95 	}
96 
97 	return (sockaddr_to_text((struct sockaddr *)&sa_in6));
98 }
99 
100 int
text_to_mailaddr(struct mailaddr * maddr,const char * email)101 text_to_mailaddr(struct mailaddr *maddr, const char *email)
102 {
103 	char *username;
104 	char *hostname;
105 	char  buffer[LINE_MAX];
106 
107 	if (strlcpy(buffer, email, sizeof buffer) >= sizeof buffer)
108 		return 0;
109 
110 	memset(maddr, 0, sizeof *maddr);
111 
112 	username = buffer;
113 	hostname = strrchr(username, '@');
114 
115 	if (hostname == NULL) {
116 		if (strlcpy(maddr->user, username, sizeof maddr->user)
117 		    >= sizeof maddr->user)
118 			return 0;
119 	}
120 	else if (username == hostname) {
121 		*hostname++ = '\0';
122 		if (strlcpy(maddr->domain, hostname, sizeof maddr->domain)
123 		    >= sizeof maddr->domain)
124 			return 0;
125 	}
126 	else {
127 		*hostname++ = '\0';
128 		if (strlcpy(maddr->user, username, sizeof maddr->user)
129 		    >= sizeof maddr->user)
130 			return 0;
131 		if (strlcpy(maddr->domain, hostname, sizeof maddr->domain)
132 		    >= sizeof maddr->domain)
133 			return 0;
134 	}
135 
136 	return 1;
137 }
138 
139 const char *
mailaddr_to_text(const struct mailaddr * maddr)140 mailaddr_to_text(const struct mailaddr *maddr)
141 {
142 	static char  buffer[LINE_MAX];
143 
144 	(void)strlcpy(buffer, maddr->user, sizeof buffer);
145 	(void)strlcat(buffer, "@", sizeof buffer);
146 	if (strlcat(buffer, maddr->domain, sizeof buffer) >= sizeof buffer)
147 		return NULL;
148 
149 	return buffer;
150 }
151 
152 
153 const char *
sa_to_text(const struct sockaddr * sa)154 sa_to_text(const struct sockaddr *sa)
155 {
156 	static char	 buf[NI_MAXHOST + 5];
157 	char		*p;
158 
159 	buf[0] = '\0';
160 	p = buf;
161 
162 	if (sa->sa_family == AF_LOCAL)
163 		(void)strlcpy(buf, "local", sizeof buf);
164 	else if (sa->sa_family == AF_INET) {
165 		in_addr_t addr;
166 
167 		addr = ((const struct sockaddr_in *)sa)->sin_addr.s_addr;
168 		addr = ntohl(addr);
169 		(void)bsnprintf(p, NI_MAXHOST, "%d.%d.%d.%d",
170 		    (addr >> 24) & 0xff, (addr >> 16) & 0xff,
171 		    (addr >> 8) & 0xff, addr & 0xff);
172 	}
173 	else if (sa->sa_family == AF_INET6) {
174 		const struct sockaddr_in6 *in6;
175 		const struct in6_addr	*in6_addr;
176 
177 		in6 = (const struct sockaddr_in6 *)sa;
178 		p = buf;
179 		in6_addr = &in6->sin6_addr;
180 		(void)bsnprintf(p, NI_MAXHOST, "[%s]", in6addr_to_text(in6_addr));
181 	}
182 
183 	return (buf);
184 }
185 
186 const char *
ss_to_text(const struct sockaddr_storage * ss)187 ss_to_text(const struct sockaddr_storage *ss)
188 {
189 	return (sa_to_text((const struct sockaddr*)ss));
190 }
191 
192 const char *
time_to_text(time_t when)193 time_to_text(time_t when)
194 {
195 	struct tm *lt;
196 	static char buf[40];
197 	char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
198 	char *month[] = {"Jan","Feb","Mar","Apr","May","Jun",
199 			 "Jul","Aug","Sep","Oct","Nov","Dec"};
200 	const char *tz;
201 	long offset;
202 
203 	lt = localtime(&when);
204 	if (lt == NULL || when == 0)
205 		fatalx("time_to_text: localtime");
206 
207 #if HAVE_STRUCT_TM_TM_GMTOFF
208 	offset = lt->tm_gmtoff;
209 	tz = lt->tm_zone;
210 #elif defined HAVE_DECL_ALTZONE && defined HAVE_DECL_TIMEZONE
211 	offset = lt->tm_isdst > 0 ? altzone : timezone;
212 	tz = lt->tm_isdst > 0 ? tzname[1] : tzname[0];
213 #endif
214 
215 	/* We do not use strftime because it is subject to locale substitution*/
216 	if (!bsnprintf(buf, sizeof(buf),
217 	    "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)",
218 	    day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon],
219 	    lt->tm_year + 1900,
220 	    lt->tm_hour, lt->tm_min, lt->tm_sec,
221 	    offset >= 0 ? '+' : '-',
222 	    abs((int)offset / 3600),
223 	    abs((int)offset % 3600) / 60,
224 	    tz))
225 		fatalx("time_to_text: bsnprintf");
226 
227 	return buf;
228 }
229 
230 const char *
duration_to_text(time_t t)231 duration_to_text(time_t t)
232 {
233 	static char	dst[64];
234 	char		buf[64];
235 	int		h, m, s;
236 	long long	d;
237 
238 	if (t == 0) {
239 		(void)strlcpy(dst, "0s", sizeof dst);
240 		return (dst);
241 	}
242 
243 	dst[0] = '\0';
244 	if (t < 0) {
245 		(void)strlcpy(dst, "-", sizeof dst);
246 		t = -t;
247 	}
248 
249 	s = t % 60;
250 	t /= 60;
251 	m = t % 60;
252 	t /= 60;
253 	h = t % 24;
254 	d = t / 24;
255 
256 	if (d) {
257 		(void)snprintf(buf, sizeof buf, "%lldd", d);
258 		(void)strlcat(dst, buf, sizeof dst);
259 	}
260 	if (h) {
261 		(void)snprintf(buf, sizeof buf, "%dh", h);
262 		(void)strlcat(dst, buf, sizeof dst);
263 	}
264 	if (m) {
265 		(void)snprintf(buf, sizeof buf, "%dm", m);
266 		(void)strlcat(dst, buf, sizeof dst);
267 	}
268 	if (s) {
269 		(void)snprintf(buf, sizeof buf, "%ds", s);
270 		(void)strlcat(dst, buf, sizeof dst);
271 	}
272 
273 	return (dst);
274 }
275 
276 int
text_to_netaddr(struct netaddr * netaddr,const char * s)277 text_to_netaddr(struct netaddr *netaddr, const char *s)
278 {
279 	struct sockaddr_storage	ss;
280 	struct sockaddr_in	ssin;
281 	struct sockaddr_in6	ssin6;
282 	int			bits;
283 	char			buf[NI_MAXHOST];
284 	size_t			len;
285 
286 	memset(&ssin, 0, sizeof(struct sockaddr_in));
287 	memset(&ssin6, 0, sizeof(struct sockaddr_in6));
288 
289 	if (strncasecmp("IPv6:", s, 5) == 0)
290 		s += 5;
291 
292 	bits = inet_net_pton(AF_INET, s, &ssin.sin_addr,
293 	    sizeof(struct in_addr));
294 	if (bits != -1) {
295 		ssin.sin_family = AF_INET;
296 		memcpy(&ss, &ssin, sizeof(ssin));
297 #ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN
298 		ss.ss_len = sizeof(struct sockaddr_in);
299 #endif
300 	} else {
301 		if (s[0] != '[') {
302 			if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf)
303 				return 0;
304 		}
305 		else {
306 			s++;
307 			if (strncasecmp("IPv6:", s, 5) == 0)
308 				s += 5;
309 			if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf)
310 				return 0;
311 			if (buf[len-1] != ']')
312 				return 0;
313 			buf[len-1] = 0;
314 		}
315 		bits = inet_net_pton(AF_INET6, buf, &ssin6.sin6_addr,
316 		    sizeof(struct in6_addr));
317 		if (bits == -1) {
318 			if (errno != EAFNOSUPPORT)
319 				return 0;
320 			bits = broken_inet_net_pton_ipv6(buf, &ssin6.sin6_addr,
321 			    sizeof(struct in6_addr));
322 			if (bits == -1)
323 				return 0;
324 		}
325 		ssin6.sin6_family = AF_INET6;
326 		memcpy(&ss, &ssin6, sizeof(ssin6));
327 #ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN
328 		ss.ss_len = sizeof(struct sockaddr_in6);
329 #endif
330 	}
331 
332 	netaddr->ss   = ss;
333 	netaddr->bits = bits;
334 	return 1;
335 }
336 
337 int
text_to_relayhost(struct relayhost * relay,const char * s)338 text_to_relayhost(struct relayhost *relay, const char *s)
339 {
340 	static const struct schema {
341 		const char	*name;
342 		int		 tls;
343 		uint16_t	 flags;
344 		uint16_t	 port;
345 	} schemas [] = {
346 		/*
347 		 * new schemas should be *appended* otherwise the default
348 		 * schema index needs to be updated later in this function.
349 		 */
350 		{ "smtp://",		RELAY_TLS_OPPORTUNISTIC, 0,		25 },
351 		{ "smtp+tls://",	RELAY_TLS_STARTTLS,	 0,		25 },
352 		{ "smtp+notls://",	RELAY_TLS_NO,		 0,		25 },
353 		/* need to specify an explicit port for LMTP */
354 		{ "lmtp://",		RELAY_TLS_NO,		 RELAY_LMTP,	0 },
355 		{ "smtps://",		RELAY_TLS_SMTPS,	 0,		465 }
356 	};
357 	const char     *errstr = NULL;
358 	char	       *p, *q;
359 	char		buffer[1024];
360 	char	       *beg, *end;
361 	size_t		i;
362 	size_t		len;
363 
364 	memset(buffer, 0, sizeof buffer);
365 	if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer)
366 		return 0;
367 
368 	for (i = 0; i < nitems(schemas); ++i)
369 		if (strncasecmp(schemas[i].name, s,
370 		    strlen(schemas[i].name)) == 0)
371 			break;
372 
373 	if (i == nitems(schemas)) {
374 		/* there is a schema, but it's not recognized */
375 		if (strstr(buffer, "://"))
376 			return 0;
377 
378 		/* no schema, default to smtp:// */
379 		i = 0;
380 		p = buffer;
381 	}
382 	else
383 		p = buffer + strlen(schemas[i].name);
384 
385 	relay->tls = schemas[i].tls;
386 	relay->flags = schemas[i].flags;
387 	relay->port = schemas[i].port;
388 
389 	/* first, we extract the label if any */
390 	if ((q = strchr(p, '@')) != NULL) {
391 		*q = 0;
392 		if (strlcpy(relay->authlabel, p, sizeof (relay->authlabel))
393 		    >= sizeof (relay->authlabel))
394 			return 0;
395 		p = q + 1;
396 	}
397 
398 	/* then, we extract the mail exchanger */
399 	beg = end = p;
400 	if (*beg == '[') {
401 		if ((end = strchr(beg, ']')) == NULL)
402 			return 0;
403 		/* skip ']', it has to be included in the relay hostname */
404 		++end;
405 		len = end - beg;
406 	}
407 	else {
408 		for (end = beg; *end; ++end)
409 			if (!isalnum((unsigned char)*end) &&
410 			    *end != '_' && *end != '.' && *end != '-')
411 				break;
412 		len = end - beg;
413 	}
414 	if (len >= sizeof relay->hostname)
415 		return 0;
416 	for (i = 0; i < len; ++i)
417 		relay->hostname[i] = beg[i];
418 	relay->hostname[i] = 0;
419 
420 	/* finally, we extract the port */
421 	p = beg + len;
422 	if (*p == ':') {
423 		relay->port = strtonum(p+1, 1, IPPORT_HILASTAUTO, &errstr);
424 		if (errstr)
425 			return 0;
426 	}
427 
428 	if (!valid_domainpart(relay->hostname))
429 		return 0;
430 	if ((relay->flags & RELAY_LMTP) && (relay->port == 0))
431 		return 0;
432 	if (relay->authlabel[0]) {
433 		/* disallow auth on non-tls scheme. */
434 		if (relay->tls != RELAY_TLS_STARTTLS &&
435 		    relay->tls != RELAY_TLS_SMTPS)
436 			return 0;
437 		relay->flags |= RELAY_AUTH;
438 	}
439 
440 	return 1;
441 }
442 
443 uint64_t
text_to_evpid(const char * s)444 text_to_evpid(const char *s)
445 {
446 	uint64_t ulval;
447 	char	 *ep;
448 
449 	errno = 0;
450 	ulval = strtoull(s, &ep, 16);
451 	if (s[0] == '\0' || *ep != '\0')
452 		return 0;
453 	if (errno == ERANGE && ulval == ULLONG_MAX)
454 		return 0;
455 	if (ulval == 0)
456 		return 0;
457 	return (ulval);
458 }
459 
460 uint32_t
text_to_msgid(const char * s)461 text_to_msgid(const char *s)
462 {
463 	uint64_t ulval;
464 	char	 *ep;
465 
466 	errno = 0;
467 	ulval = strtoull(s, &ep, 16);
468 	if (s[0] == '\0' || *ep != '\0')
469 		return 0;
470 	if (errno == ERANGE && ulval == ULLONG_MAX)
471 		return 0;
472 	if (ulval == 0)
473 		return 0;
474 	if (ulval > 0xffffffff)
475 		return 0;
476 	return (ulval & 0xffffffff);
477 }
478 
479 const char *
rule_to_text(struct rule * r)480 rule_to_text(struct rule *r)
481 {
482 	static char buf[4096];
483 
484 	memset(buf, 0, sizeof buf);
485 	(void)strlcpy(buf, "match", sizeof buf);
486 	if (r->flag_tag) {
487 		if (r->flag_tag < 0)
488 			(void)strlcat(buf, " !", sizeof buf);
489 		(void)strlcat(buf, " tag ", sizeof buf);
490 		(void)strlcat(buf, r->table_tag, sizeof buf);
491 	}
492 
493 	if (r->flag_from) {
494 		if (r->flag_from < 0)
495 			(void)strlcat(buf, " !", sizeof buf);
496 		if (r->flag_from_socket)
497 			(void)strlcat(buf, " from socket", sizeof buf);
498 		else if (r->flag_from_rdns) {
499 			(void)strlcat(buf, " from rdns", sizeof buf);
500 			if (r->table_from) {
501 				(void)strlcat(buf, " ", sizeof buf);
502 				(void)strlcat(buf, r->table_from, sizeof buf);
503 			}
504 		}
505 		else if (strcmp(r->table_from, "<anyhost>") == 0)
506 			(void)strlcat(buf, " from any", sizeof buf);
507 		else if (strcmp(r->table_from, "<localhost>") == 0)
508 			(void)strlcat(buf, " from local", sizeof buf);
509 		else {
510 			(void)strlcat(buf, " from src ", sizeof buf);
511 			(void)strlcat(buf, r->table_from, sizeof buf);
512 		}
513 	}
514 
515 	if (r->flag_for) {
516 		if (r->flag_for < 0)
517 			(void)strlcat(buf, " !", sizeof buf);
518 		if (strcmp(r->table_for, "<anydestination>") == 0)
519 			(void)strlcat(buf, " for any", sizeof buf);
520 		else if (strcmp(r->table_for, "<localnames>") == 0)
521 			(void)strlcat(buf, " for local", sizeof buf);
522 		else {
523 			(void)strlcat(buf, " for domain ", sizeof buf);
524 			(void)strlcat(buf, r->table_for, sizeof buf);
525 		}
526 	}
527 
528 	if (r->flag_smtp_helo) {
529 		if (r->flag_smtp_helo < 0)
530 			(void)strlcat(buf, " !", sizeof buf);
531 		(void)strlcat(buf, " helo ", sizeof buf);
532 		(void)strlcat(buf, r->table_smtp_helo, sizeof buf);
533 	}
534 
535 	if (r->flag_smtp_auth) {
536 		if (r->flag_smtp_auth < 0)
537 			(void)strlcat(buf, " !", sizeof buf);
538 		(void)strlcat(buf, " auth", sizeof buf);
539 		if (r->table_smtp_auth) {
540 			(void)strlcat(buf, " ", sizeof buf);
541 			(void)strlcat(buf, r->table_smtp_auth, sizeof buf);
542 		}
543 	}
544 
545 	if (r->flag_smtp_starttls) {
546 		if (r->flag_smtp_starttls < 0)
547 			(void)strlcat(buf, " !", sizeof buf);
548 		(void)strlcat(buf, " tls", sizeof buf);
549 	}
550 
551 	if (r->flag_smtp_mail_from) {
552 		if (r->flag_smtp_mail_from < 0)
553 			(void)strlcat(buf, " !", sizeof buf);
554 		(void)strlcat(buf, " mail-from ", sizeof buf);
555 		(void)strlcat(buf, r->table_smtp_mail_from, sizeof buf);
556 	}
557 
558 	if (r->flag_smtp_rcpt_to) {
559 		if (r->flag_smtp_rcpt_to < 0)
560 			(void)strlcat(buf, " !", sizeof buf);
561 		(void)strlcat(buf, " rcpt-to ", sizeof buf);
562 		(void)strlcat(buf, r->table_smtp_rcpt_to, sizeof buf);
563 	}
564 	(void)strlcat(buf, " action ", sizeof buf);
565 	if (r->reject)
566 		(void)strlcat(buf, "reject", sizeof buf);
567 	else
568 		(void)strlcat(buf, r->dispatcher, sizeof buf);
569 	return buf;
570 }
571 
572 
573 int
text_to_userinfo(struct userinfo * userinfo,const char * s)574 text_to_userinfo(struct userinfo *userinfo, const char *s)
575 {
576 	char		buf[PATH_MAX];
577 	char	       *p;
578 	const char     *errstr;
579 
580 	memset(buf, 0, sizeof buf);
581 	p = buf;
582 	while (*s && *s != ':')
583 		*p++ = *s++;
584 	if (*s++ != ':')
585 		goto error;
586 
587 	if (strlcpy(userinfo->username, buf,
588 		sizeof userinfo->username) >= sizeof userinfo->username)
589 		goto error;
590 
591 	memset(buf, 0, sizeof buf);
592 	p = buf;
593 	while (*s && *s != ':')
594 		*p++ = *s++;
595 	if (*s++ != ':')
596 		goto error;
597 	userinfo->uid = strtonum(buf, 0, UID_MAX, &errstr);
598 	if (errstr)
599 		goto error;
600 
601 	memset(buf, 0, sizeof buf);
602 	p = buf;
603 	while (*s && *s != ':')
604 		*p++ = *s++;
605 	if (*s++ != ':')
606 		goto error;
607 	userinfo->gid = strtonum(buf, 0, GID_MAX, &errstr);
608 	if (errstr)
609 		goto error;
610 
611 	if (strlcpy(userinfo->directory, s,
612 		sizeof userinfo->directory) >= sizeof userinfo->directory)
613 		goto error;
614 
615 	return 1;
616 
617 error:
618 	return 0;
619 }
620 
621 int
text_to_credentials(struct credentials * creds,const char * s)622 text_to_credentials(struct credentials *creds, const char *s)
623 {
624 	char   *p;
625 	char	buffer[LINE_MAX];
626 	size_t	offset;
627 
628 	p = strchr(s, ':');
629 	if (p == NULL) {
630 		creds->username[0] = '\0';
631 		if (strlcpy(creds->password, s, sizeof creds->password)
632 		    >= sizeof creds->password)
633 			return 0;
634 		return 1;
635 	}
636 
637 	offset = p - s;
638 
639 	memset(buffer, 0, sizeof buffer);
640 	if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer)
641 		return 0;
642 	p = buffer + offset;
643 	*p = '\0';
644 
645 	if (strlcpy(creds->username, buffer, sizeof creds->username)
646 	    >= sizeof creds->username)
647 		return 0;
648 	if (strlcpy(creds->password, p+1, sizeof creds->password)
649 	    >= sizeof creds->password)
650 		return 0;
651 
652 	return 1;
653 }
654 
655 int
text_to_expandnode(struct expandnode * expandnode,const char * s)656 text_to_expandnode(struct expandnode *expandnode, const char *s)
657 {
658 	size_t	l;
659 
660 	l = strlen(s);
661 	if (alias_is_error(expandnode, s, l) ||
662 	    alias_is_include(expandnode, s, l) ||
663 	    alias_is_filter(expandnode, s, l) ||
664 	    alias_is_filename(expandnode, s, l) ||
665 	    alias_is_address(expandnode, s, l) ||
666 	    alias_is_username(expandnode, s, l))
667 		return (1);
668 
669 	return (0);
670 }
671 
672 const char *
expandnode_to_text(struct expandnode * expandnode)673 expandnode_to_text(struct expandnode *expandnode)
674 {
675 	switch (expandnode->type) {
676 	case EXPAND_FILTER:
677 	case EXPAND_FILENAME:
678 	case EXPAND_INCLUDE:
679 	case EXPAND_ERROR:
680 	case EXPAND_USERNAME:
681 		return expandnode->u.user;
682 	case EXPAND_ADDRESS:
683 		return mailaddr_to_text(&expandnode->u.mailaddr);
684 	case EXPAND_INVALID:
685 		break;
686 	}
687 
688 	return NULL;
689 }
690 
691 /******/
692 static int
alias_is_filter(struct expandnode * alias,const char * line,size_t len)693 alias_is_filter(struct expandnode *alias, const char *line, size_t len)
694 {
695 	int	v = 0;
696 
697 	if (*line == '"')
698 		v = 1;
699 	if (*(line+v) == '|') {
700 		if (strlcpy(alias->u.buffer, line + v + 1,
701 		    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
702 			return 0;
703 		if (v) {
704 			v = strlen(alias->u.buffer);
705 			if (v == 0)
706 				return (0);
707 			if (alias->u.buffer[v-1] != '"')
708 				return (0);
709 			alias->u.buffer[v-1] = '\0';
710 		}
711 		alias->type = EXPAND_FILTER;
712 		return (1);
713 	}
714 	return (0);
715 }
716 
717 static int
alias_is_username(struct expandnode * alias,const char * line,size_t len)718 alias_is_username(struct expandnode *alias, const char *line, size_t len)
719 {
720 	memset(alias, 0, sizeof *alias);
721 
722 	if (strlcpy(alias->u.user, line,
723 	    sizeof(alias->u.user)) >= sizeof(alias->u.user))
724 		return 0;
725 
726 	while (*line) {
727 		if (!isalnum((unsigned char)*line) &&
728 		    *line != '_' && *line != '.' && *line != '-' && *line != '+')
729 			return 0;
730 		++line;
731 	}
732 
733 	alias->type = EXPAND_USERNAME;
734 	return 1;
735 }
736 
737 static int
alias_is_address(struct expandnode * alias,const char * line,size_t len)738 alias_is_address(struct expandnode *alias, const char *line, size_t len)
739 {
740 	char *domain;
741 
742 	memset(alias, 0, sizeof *alias);
743 
744 	if (len < 3)	/* x@y */
745 		return 0;
746 
747 	domain = strchr(line, '@');
748 	if (domain == NULL)
749 		return 0;
750 
751 	/* @ cannot start or end an address */
752 	if (domain == line || domain == line + len - 1)
753 		return 0;
754 
755 	/* scan pre @ for disallowed chars */
756 	*domain++ = '\0';
757 	(void)strlcpy(alias->u.mailaddr.user, line, sizeof(alias->u.mailaddr.user));
758 	(void)strlcpy(alias->u.mailaddr.domain, domain,
759 	    sizeof(alias->u.mailaddr.domain));
760 
761 	while (*line) {
762 		char allowedset[] = "!#$%*/?|^{}`~&'+-=_.";
763 		if (!isalnum((unsigned char)*line) &&
764 		    strchr(allowedset, *line) == NULL)
765 			return 0;
766 		++line;
767 	}
768 
769 	while (*domain) {
770 		char allowedset[] = "-.";
771 		if (!isalnum((unsigned char)*domain) &&
772 		    strchr(allowedset, *domain) == NULL)
773 			return 0;
774 		++domain;
775 	}
776 
777 	alias->type = EXPAND_ADDRESS;
778 	return 1;
779 }
780 
781 static int
alias_is_filename(struct expandnode * alias,const char * line,size_t len)782 alias_is_filename(struct expandnode *alias, const char *line, size_t len)
783 {
784 	memset(alias, 0, sizeof *alias);
785 
786 	if (*line != '/')
787 		return 0;
788 
789 	if (strlcpy(alias->u.buffer, line,
790 	    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
791 		return 0;
792 	alias->type = EXPAND_FILENAME;
793 	return 1;
794 }
795 
796 static int
alias_is_include(struct expandnode * alias,const char * line,size_t len)797 alias_is_include(struct expandnode *alias, const char *line, size_t len)
798 {
799 	size_t skip;
800 
801 	memset(alias, 0, sizeof *alias);
802 
803 	if (strncasecmp(":include:", line, 9) == 0)
804 		skip = 9;
805 	else if (strncasecmp("include:", line, 8) == 0)
806 		skip = 8;
807 	else
808 		return 0;
809 
810 	if (!alias_is_filename(alias, line + skip, len - skip))
811 		return 0;
812 
813 	alias->type = EXPAND_INCLUDE;
814 	return 1;
815 }
816 
817 static int
alias_is_error(struct expandnode * alias,const char * line,size_t len)818 alias_is_error(struct expandnode *alias, const char *line, size_t len)
819 {
820 	size_t	skip;
821 
822 	memset(alias, 0, sizeof *alias);
823 
824 	if (strncasecmp(":error:", line, 7) == 0)
825 		skip = 7;
826 	else if (strncasecmp("error:", line, 6) == 0)
827 		skip = 6;
828 	else
829 		return 0;
830 
831 	if (strlcpy(alias->u.buffer, line + skip,
832 	    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
833 		return 0;
834 
835 	if (strlen(alias->u.buffer) < 5)
836 		return 0;
837 
838 	/* [45][0-9]{2} [a-zA-Z0-9].* */
839 	if (alias->u.buffer[3] != ' ' ||
840 	    !isalnum((unsigned char)alias->u.buffer[4]) ||
841 	    (alias->u.buffer[0] != '4' && alias->u.buffer[0] != '5') ||
842 	    !isdigit((unsigned char)alias->u.buffer[1]) ||
843 	    !isdigit((unsigned char)alias->u.buffer[2]))
844 		return 0;
845 
846 	alias->type = EXPAND_ERROR;
847 	return 1;
848 }
849 
850 static int
broken_inet_net_pton_ipv6(const char * src,void * dst,size_t size)851 broken_inet_net_pton_ipv6(const char *src, void *dst, size_t size)
852 {
853 	int	ret;
854 	int	bits;
855 	char	buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255:255:255:255/128")];
856 	char		*sep;
857 	const char	*errstr;
858 
859 	if (strlcpy(buf, src, sizeof buf) >= sizeof buf) {
860 		errno = EMSGSIZE;
861 		return (-1);
862 	}
863 
864 	sep = strchr(buf, '/');
865 	if (sep != NULL)
866 		*sep++ = '\0';
867 
868 	ret = inet_pton(AF_INET6, buf, dst);
869 	if (ret != 1)
870 		return (-1);
871 
872 	if (sep == NULL)
873 		return 128;
874 
875 	bits = strtonum(sep, 0, 128, &errstr);
876 	if (errstr)
877 		return (-1);
878 
879 	return bits;
880 }
881