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