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