/*	$OpenBSD: to.c,v 1.43 2019/09/19 16:00:59 gilles Exp $	*/

/*
 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
 * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
 * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "includes.h"

#include <sys/types.h>
#include <sys/queue.h>
#include <sys/tree.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/resource.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <event.h>
#include <fcntl.h>
#include <imsg.h>
#include <limits.h>
#include <inttypes.h>
#include <netdb.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "smtpd.h"
#include "log.h"

static const char *in6addr_to_text(const struct in6_addr *);
static int alias_is_filter(struct expandnode *, const char *, size_t);
static int alias_is_username(struct expandnode *, const char *, size_t);
static int alias_is_address(struct expandnode *, const char *, size_t);
static int alias_is_filename(struct expandnode *, const char *, size_t);
static int alias_is_include(struct expandnode *, const char *, size_t);
static int alias_is_error(struct expandnode *, const char *, size_t);

static int broken_inet_net_pton_ipv6(const char *, void *, size_t);

const char *
sockaddr_to_text(struct sockaddr *sa)
{
	static char	buf[NI_MAXHOST];

	if (getnameinfo(sa, SA_LEN(sa), buf, sizeof(buf), NULL, 0,
	    NI_NUMERICHOST))
		return ("(unknown)");
	else
		return (buf);
}

static const char *
in6addr_to_text(const struct in6_addr *addr)
{
	struct sockaddr_in6	sa_in6;
	uint16_t		tmp16;

	memset(&sa_in6, 0, sizeof(sa_in6));
#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN
	sa_in6.sin6_len = sizeof(sa_in6);
#endif
	sa_in6.sin6_family = AF_INET6;
	memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr));

	/* XXX thanks, KAME, for this ugliness... adopted from route/show.c */
	if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) ||
	    IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) {
		memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16));
		sa_in6.sin6_scope_id = ntohs(tmp16);
		sa_in6.sin6_addr.s6_addr[2] = 0;
		sa_in6.sin6_addr.s6_addr[3] = 0;
	}

	return (sockaddr_to_text((struct sockaddr *)&sa_in6));
}

int
text_to_mailaddr(struct mailaddr *maddr, const char *email)
{
	char *username;
	char *hostname;
	char  buffer[LINE_MAX];

	if (strlcpy(buffer, email, sizeof buffer) >= sizeof buffer)
		return 0;

	memset(maddr, 0, sizeof *maddr);

	username = buffer;
	hostname = strrchr(username, '@');

	if (hostname == NULL) {
		if (strlcpy(maddr->user, username, sizeof maddr->user)
		    >= sizeof maddr->user)
			return 0;
	}
	else if (username == hostname) {
		*hostname++ = '\0';
		if (strlcpy(maddr->domain, hostname, sizeof maddr->domain)
		    >= sizeof maddr->domain)
			return 0;
	}
	else {
		*hostname++ = '\0';
		if (strlcpy(maddr->user, username, sizeof maddr->user)
		    >= sizeof maddr->user)
			return 0;
		if (strlcpy(maddr->domain, hostname, sizeof maddr->domain)
		    >= sizeof maddr->domain)
			return 0;
	}

	return 1;
}

const char *
mailaddr_to_text(const struct mailaddr *maddr)
{
	static char  buffer[LINE_MAX];

	(void)strlcpy(buffer, maddr->user, sizeof buffer);
	(void)strlcat(buffer, "@", sizeof buffer);
	if (strlcat(buffer, maddr->domain, sizeof buffer) >= sizeof buffer)
		return NULL;

	return buffer;
}


const char *
sa_to_text(const struct sockaddr *sa)
{
	static char	 buf[NI_MAXHOST + 5];
	char		*p;

	buf[0] = '\0';
	p = buf;

	if (sa->sa_family == AF_LOCAL)
		(void)strlcpy(buf, "local", sizeof buf);
	else if (sa->sa_family == AF_INET) {
		in_addr_t addr;

		addr = ((const struct sockaddr_in *)sa)->sin_addr.s_addr;
		addr = ntohl(addr);
		(void)bsnprintf(p, NI_MAXHOST, "%d.%d.%d.%d",
		    (addr >> 24) & 0xff, (addr >> 16) & 0xff,
		    (addr >> 8) & 0xff, addr & 0xff);
	}
	else if (sa->sa_family == AF_INET6) {
		const struct sockaddr_in6 *in6;
		const struct in6_addr	*in6_addr;

		in6 = (const struct sockaddr_in6 *)sa;
		p = buf;
		in6_addr = &in6->sin6_addr;
		(void)bsnprintf(p, NI_MAXHOST, "[%s]", in6addr_to_text(in6_addr));
	}

	return (buf);
}

const char *
ss_to_text(const struct sockaddr_storage *ss)
{
	return (sa_to_text((const struct sockaddr*)ss));
}

const char *
time_to_text(time_t when)
{
	struct tm *lt;
	static char buf[40];
	char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
	char *month[] = {"Jan","Feb","Mar","Apr","May","Jun",
			 "Jul","Aug","Sep","Oct","Nov","Dec"};
	const char *tz;
	long offset;

	lt = localtime(&when);
	if (lt == NULL || when == 0)
		fatalx("time_to_text: localtime");

#if HAVE_STRUCT_TM_TM_GMTOFF
	offset = lt->tm_gmtoff;
	tz = lt->tm_zone;
#elif defined HAVE_DECL_ALTZONE && defined HAVE_DECL_TIMEZONE
	offset = lt->tm_isdst > 0 ? altzone : timezone;
	tz = lt->tm_isdst > 0 ? tzname[1] : tzname[0];
#endif

	/* We do not use strftime because it is subject to locale substitution*/
	if (!bsnprintf(buf, sizeof(buf),
	    "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)",
	    day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon],
	    lt->tm_year + 1900,
	    lt->tm_hour, lt->tm_min, lt->tm_sec,
	    offset >= 0 ? '+' : '-',
	    abs((int)offset / 3600),
	    abs((int)offset % 3600) / 60,
	    tz))
		fatalx("time_to_text: bsnprintf");

	return buf;
}

const char *
duration_to_text(time_t t)
{
	static char	dst[64];
	char		buf[64];
	int		h, m, s;
	long long	d;

	if (t == 0) {
		(void)strlcpy(dst, "0s", sizeof dst);
		return (dst);
	}

	dst[0] = '\0';
	if (t < 0) {
		(void)strlcpy(dst, "-", sizeof dst);
		t = -t;
	}

	s = t % 60;
	t /= 60;
	m = t % 60;
	t /= 60;
	h = t % 24;
	d = t / 24;

	if (d) {
		(void)snprintf(buf, sizeof buf, "%lldd", d);
		(void)strlcat(dst, buf, sizeof dst);
	}
	if (h) {
		(void)snprintf(buf, sizeof buf, "%dh", h);
		(void)strlcat(dst, buf, sizeof dst);
	}
	if (m) {
		(void)snprintf(buf, sizeof buf, "%dm", m);
		(void)strlcat(dst, buf, sizeof dst);
	}
	if (s) {
		(void)snprintf(buf, sizeof buf, "%ds", s);
		(void)strlcat(dst, buf, sizeof dst);
	}

	return (dst);
}

int
text_to_netaddr(struct netaddr *netaddr, const char *s)
{
	struct sockaddr_storage	ss;
	struct sockaddr_in	ssin;
	struct sockaddr_in6	ssin6;
	int			bits;
	char			buf[NI_MAXHOST];
	size_t			len;

	memset(&ssin, 0, sizeof(struct sockaddr_in));
	memset(&ssin6, 0, sizeof(struct sockaddr_in6));

	if (strncasecmp("IPv6:", s, 5) == 0)
		s += 5;

	bits = inet_net_pton(AF_INET, s, &ssin.sin_addr,
	    sizeof(struct in_addr));
	if (bits != -1) {
		ssin.sin_family = AF_INET;
		memcpy(&ss, &ssin, sizeof(ssin));
#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN
		ss.ss_len = sizeof(struct sockaddr_in);
#endif
	} else {
		if (s[0] != '[') {
			if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf)
				return 0;
		}
		else {
			s++;
			if (strncasecmp("IPv6:", s, 5) == 0)
				s += 5;
			if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf)
				return 0;
			if (buf[len-1] != ']')
				return 0;
			buf[len-1] = 0;
		}
		bits = inet_net_pton(AF_INET6, buf, &ssin6.sin6_addr,
		    sizeof(struct in6_addr));
		if (bits == -1) {
			if (errno != EAFNOSUPPORT)
				return 0;
			bits = broken_inet_net_pton_ipv6(buf, &ssin6.sin6_addr,
			    sizeof(struct in6_addr));
			if (bits == -1)
				return 0;
		}
		ssin6.sin6_family = AF_INET6;
		memcpy(&ss, &ssin6, sizeof(ssin6));
#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN
		ss.ss_len = sizeof(struct sockaddr_in6);
#endif
	}

	netaddr->ss   = ss;
	netaddr->bits = bits;
	return 1;
}

int
text_to_relayhost(struct relayhost *relay, const char *s)
{
	static const struct schema {
		const char	*name;
		int		 tls;
		uint16_t	 flags;
		uint16_t	 port;
	} schemas [] = {
		/*
		 * new schemas should be *appended* otherwise the default
		 * schema index needs to be updated later in this function.
		 */
		{ "smtp://",		RELAY_TLS_OPPORTUNISTIC, 0,		25 },
		{ "smtp+tls://",	RELAY_TLS_STARTTLS,	 0,		25 },
		{ "smtp+notls://",	RELAY_TLS_NO,		 0,		25 },
		/* need to specify an explicit port for LMTP */
		{ "lmtp://",		RELAY_TLS_NO,		 RELAY_LMTP,	0 },
		{ "smtps://",		RELAY_TLS_SMTPS,	 0,		465 }
	};
	const char     *errstr = NULL;
	char	       *p, *q;
	char		buffer[1024];
	char	       *beg, *end;
	size_t		i;
	size_t		len;

	memset(buffer, 0, sizeof buffer);
	if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer)
		return 0;

	for (i = 0; i < nitems(schemas); ++i)
		if (strncasecmp(schemas[i].name, s,
		    strlen(schemas[i].name)) == 0)
			break;

	if (i == nitems(schemas)) {
		/* there is a schema, but it's not recognized */
		if (strstr(buffer, "://"))
			return 0;

		/* no schema, default to smtp:// */
		i = 0;
		p = buffer;
	}
	else
		p = buffer + strlen(schemas[i].name);

	relay->tls = schemas[i].tls;
	relay->flags = schemas[i].flags;
	relay->port = schemas[i].port;

	/* first, we extract the label if any */
	if ((q = strchr(p, '@')) != NULL) {
		*q = 0;
		if (strlcpy(relay->authlabel, p, sizeof (relay->authlabel))
		    >= sizeof (relay->authlabel))
			return 0;
		p = q + 1;
	}

	/* then, we extract the mail exchanger */
	beg = end = p;
	if (*beg == '[') {
		if ((end = strchr(beg, ']')) == NULL)
			return 0;
		/* skip ']', it has to be included in the relay hostname */
		++end;
		len = end - beg;
	}
	else {
		for (end = beg; *end; ++end)
			if (!isalnum((unsigned char)*end) &&
			    *end != '_' && *end != '.' && *end != '-')
				break;
		len = end - beg;
	}
	if (len >= sizeof relay->hostname)
		return 0;
	for (i = 0; i < len; ++i)
		relay->hostname[i] = beg[i];
	relay->hostname[i] = 0;

	/* finally, we extract the port */
	p = beg + len;
	if (*p == ':') {
		relay->port = strtonum(p+1, 1, IPPORT_HILASTAUTO, &errstr);
		if (errstr)
			return 0;
	}

	if (!valid_domainpart(relay->hostname))
		return 0;
	if ((relay->flags & RELAY_LMTP) && (relay->port == 0))
		return 0;
	if (relay->authlabel[0]) {
		/* disallow auth on non-tls scheme. */
		if (relay->tls != RELAY_TLS_STARTTLS &&
		    relay->tls != RELAY_TLS_SMTPS)
			return 0;
		relay->flags |= RELAY_AUTH;
	}

	return 1;
}

uint64_t
text_to_evpid(const char *s)
{
	uint64_t ulval;
	char	 *ep;

	errno = 0;
	ulval = strtoull(s, &ep, 16);
	if (s[0] == '\0' || *ep != '\0')
		return 0;
	if (errno == ERANGE && ulval == ULLONG_MAX)
		return 0;
	if (ulval == 0)
		return 0;
	return (ulval);
}

uint32_t
text_to_msgid(const char *s)
{
	uint64_t ulval;
	char	 *ep;

	errno = 0;
	ulval = strtoull(s, &ep, 16);
	if (s[0] == '\0' || *ep != '\0')
		return 0;
	if (errno == ERANGE && ulval == ULLONG_MAX)
		return 0;
	if (ulval == 0)
		return 0;
	if (ulval > 0xffffffff)
		return 0;
	return (ulval & 0xffffffff);
}

const char *
rule_to_text(struct rule *r)
{
	static char buf[4096];

	memset(buf, 0, sizeof buf);
	(void)strlcpy(buf, "match", sizeof buf);
	if (r->flag_tag) {
		if (r->flag_tag < 0)
			(void)strlcat(buf, " !", sizeof buf);
		(void)strlcat(buf, " tag ", sizeof buf);
		(void)strlcat(buf, r->table_tag, sizeof buf);
	}

	if (r->flag_from) {
		if (r->flag_from < 0)
			(void)strlcat(buf, " !", sizeof buf);
		if (r->flag_from_socket)
			(void)strlcat(buf, " from socket", sizeof buf);
		if (r->flag_from_rdns) {
			(void)strlcat(buf, " from rdns", sizeof buf);
			if (r->table_from) {
				(void)strlcat(buf, " ", sizeof buf);
				(void)strlcat(buf, r->table_from, sizeof buf);
			}
		}
		else if (strcmp(r->table_from, "<anyhost>") == 0)
			(void)strlcat(buf, " from any", sizeof buf);
		else if (strcmp(r->table_from, "<localhost>") == 0)
			(void)strlcat(buf, " from local", sizeof buf);
		else {
			(void)strlcat(buf, " from src ", sizeof buf);
			(void)strlcat(buf, r->table_from, sizeof buf);
		}
	}

	if (r->flag_for) {
		if (r->flag_for < 0)
			(void)strlcat(buf, " !", sizeof buf);
		if (strcmp(r->table_for, "<anydestination>") == 0)
			(void)strlcat(buf, " for any", sizeof buf);
		else if (strcmp(r->table_for, "<localnames>") == 0)
			(void)strlcat(buf, " for local", sizeof buf);
		else {
			(void)strlcat(buf, " for domain ", sizeof buf);
			(void)strlcat(buf, r->table_for, sizeof buf);
		}
	}

	if (r->flag_smtp_helo) {
		if (r->flag_smtp_helo < 0)
			(void)strlcat(buf, " !", sizeof buf);
		(void)strlcat(buf, " helo ", sizeof buf);
		(void)strlcat(buf, r->table_smtp_helo, sizeof buf);
	}

	if (r->flag_smtp_auth) {
		if (r->flag_smtp_auth < 0)
			(void)strlcat(buf, " !", sizeof buf);
		(void)strlcat(buf, " auth", sizeof buf);
		if (r->table_smtp_auth) {
			(void)strlcat(buf, " ", sizeof buf);
			(void)strlcat(buf, r->table_smtp_auth, sizeof buf);
		}
	}

	if (r->flag_smtp_starttls) {
		if (r->flag_smtp_starttls < 0)
			(void)strlcat(buf, " !", sizeof buf);
		(void)strlcat(buf, " tls", sizeof buf);
	}

	if (r->flag_smtp_mail_from) {
		if (r->flag_smtp_mail_from < 0)
			(void)strlcat(buf, " !", sizeof buf);
		(void)strlcat(buf, " mail-from ", sizeof buf);
		(void)strlcat(buf, r->table_smtp_mail_from, sizeof buf);
	}

	if (r->flag_smtp_rcpt_to) {
		if (r->flag_smtp_rcpt_to < 0)
			(void)strlcat(buf, " !", sizeof buf);
		(void)strlcat(buf, " rcpt-to ", sizeof buf);
		(void)strlcat(buf, r->table_smtp_rcpt_to, sizeof buf);
	}
	(void)strlcat(buf, " action ", sizeof buf);
	if (r->reject)
		(void)strlcat(buf, "reject", sizeof buf);
	else
		(void)strlcat(buf, r->dispatcher, sizeof buf);
	return buf;
}


int
text_to_userinfo(struct userinfo *userinfo, const char *s)
{
	char		buf[PATH_MAX];
	char	       *p;
	const char     *errstr;

	memset(buf, 0, sizeof buf);
	p = buf;
	while (*s && *s != ':')
		*p++ = *s++;
	if (*s++ != ':')
		goto error;

	if (strlcpy(userinfo->username, buf,
		sizeof userinfo->username) >= sizeof userinfo->username)
		goto error;

	memset(buf, 0, sizeof buf);
	p = buf;
	while (*s && *s != ':')
		*p++ = *s++;
	if (*s++ != ':')
		goto error;
	userinfo->uid = strtonum(buf, 0, UID_MAX, &errstr);
	if (errstr)
		goto error;

	memset(buf, 0, sizeof buf);
	p = buf;
	while (*s && *s != ':')
		*p++ = *s++;
	if (*s++ != ':')
		goto error;
	userinfo->gid = strtonum(buf, 0, GID_MAX, &errstr);
	if (errstr)
		goto error;

	if (strlcpy(userinfo->directory, s,
		sizeof userinfo->directory) >= sizeof userinfo->directory)
		goto error;

	return 1;

error:
	return 0;
}

int
text_to_credentials(struct credentials *creds, const char *s)
{
	char   *p;
	char	buffer[LINE_MAX];
	size_t	offset;

	p = strchr(s, ':');
	if (p == NULL) {
		creds->username[0] = '\0';
		if (strlcpy(creds->password, s, sizeof creds->password)
		    >= sizeof creds->password)
			return 0;
		return 1;
	}

	offset = p - s;

	memset(buffer, 0, sizeof buffer);
	if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer)
		return 0;
	p = buffer + offset;
	*p = '\0';

	if (strlcpy(creds->username, buffer, sizeof creds->username)
	    >= sizeof creds->username)
		return 0;
	if (strlcpy(creds->password, p+1, sizeof creds->password)
	    >= sizeof creds->password)
		return 0;

	return 1;
}

int
text_to_expandnode(struct expandnode *expandnode, const char *s)
{
	size_t	l;

	l = strlen(s);
	if (alias_is_error(expandnode, s, l) ||
	    alias_is_include(expandnode, s, l) ||
	    alias_is_filter(expandnode, s, l) ||
	    alias_is_filename(expandnode, s, l) ||
	    alias_is_address(expandnode, s, l) ||
	    alias_is_username(expandnode, s, l))
		return (1);

	return (0);
}

const char *
expandnode_to_text(struct expandnode *expandnode)
{
	switch (expandnode->type) {
	case EXPAND_FILTER:
	case EXPAND_FILENAME:
	case EXPAND_INCLUDE:
	case EXPAND_ERROR:
	case EXPAND_USERNAME:
		return expandnode->u.user;
	case EXPAND_ADDRESS:
		return mailaddr_to_text(&expandnode->u.mailaddr);
	case EXPAND_INVALID:
		break;
	}

	return NULL;
}

/******/
static int
alias_is_filter(struct expandnode *alias, const char *line, size_t len)
{
	int	v = 0;

	if (*line == '"')
		v = 1;
	if (*(line+v) == '|') {
		if (strlcpy(alias->u.buffer, line + v + 1,
		    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
			return 0;
		if (v) {
			v = strlen(alias->u.buffer);
			if (v == 0)
				return (0);
			if (alias->u.buffer[v-1] != '"')
				return (0);
			alias->u.buffer[v-1] = '\0';
		}
		alias->type = EXPAND_FILTER;
		return (1);
	}
	return (0);
}

static int
alias_is_username(struct expandnode *alias, const char *line, size_t len)
{
	memset(alias, 0, sizeof *alias);

	if (strlcpy(alias->u.user, line,
	    sizeof(alias->u.user)) >= sizeof(alias->u.user))
		return 0;

	while (*line) {
		if (!isalnum((unsigned char)*line) &&
		    *line != '_' && *line != '.' && *line != '-' && *line != '+')
			return 0;
		++line;
	}

	alias->type = EXPAND_USERNAME;
	return 1;
}

static int
alias_is_address(struct expandnode *alias, const char *line, size_t len)
{
	char *domain;

	memset(alias, 0, sizeof *alias);

	if (len < 3)	/* x@y */
		return 0;

	domain = strchr(line, '@');
	if (domain == NULL)
		return 0;

	/* @ cannot start or end an address */
	if (domain == line || domain == line + len - 1)
		return 0;

	/* scan pre @ for disallowed chars */
	*domain++ = '\0';
	(void)strlcpy(alias->u.mailaddr.user, line, sizeof(alias->u.mailaddr.user));
	(void)strlcpy(alias->u.mailaddr.domain, domain,
	    sizeof(alias->u.mailaddr.domain));

	while (*line) {
		char allowedset[] = "!#$%*/?|^{}`~&'+-=_.";
		if (!isalnum((unsigned char)*line) &&
		    strchr(allowedset, *line) == NULL)
			return 0;
		++line;
	}

	while (*domain) {
		char allowedset[] = "-.";
		if (!isalnum((unsigned char)*domain) &&
		    strchr(allowedset, *domain) == NULL)
			return 0;
		++domain;
	}

	alias->type = EXPAND_ADDRESS;
	return 1;
}

static int
alias_is_filename(struct expandnode *alias, const char *line, size_t len)
{
	memset(alias, 0, sizeof *alias);

	if (*line != '/')
		return 0;

	if (strlcpy(alias->u.buffer, line,
	    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
		return 0;
	alias->type = EXPAND_FILENAME;
	return 1;
}

static int
alias_is_include(struct expandnode *alias, const char *line, size_t len)
{
	size_t skip;

	memset(alias, 0, sizeof *alias);

	if (strncasecmp(":include:", line, 9) == 0)
		skip = 9;
	else if (strncasecmp("include:", line, 8) == 0)
		skip = 8;
	else
		return 0;

	if (!alias_is_filename(alias, line + skip, len - skip))
		return 0;

	alias->type = EXPAND_INCLUDE;
	return 1;
}

static int
alias_is_error(struct expandnode *alias, const char *line, size_t len)
{
	size_t	skip;

	memset(alias, 0, sizeof *alias);

	if (strncasecmp(":error:", line, 7) == 0)
		skip = 7;
	else if (strncasecmp("error:", line, 6) == 0)
		skip = 6;
	else
		return 0;

	if (strlcpy(alias->u.buffer, line + skip,
	    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
		return 0;

	if (strlen(alias->u.buffer) < 5)
		return 0;

	/* [45][0-9]{2} [a-zA-Z0-9].* */
	if (alias->u.buffer[3] != ' ' ||
	    !isalnum((unsigned char)alias->u.buffer[4]) ||
	    (alias->u.buffer[0] != '4' && alias->u.buffer[0] != '5') ||
	    !isdigit((unsigned char)alias->u.buffer[1]) ||
	    !isdigit((unsigned char)alias->u.buffer[2]))
		return 0;

	alias->type = EXPAND_ERROR;
	return 1;
}

static int
broken_inet_net_pton_ipv6(const char *src, void *dst, size_t size)
{
	int	ret;
	int	bits;
	char	buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255:255:255:255/128")];
	char		*sep;
	const char	*errstr;

	if (strlcpy(buf, src, sizeof buf) >= sizeof buf) {
		errno = EMSGSIZE;
		return (-1);
	}

	sep = strchr(buf, '/');
	if (sep != NULL)
		*sep++ = '\0';

	ret = inet_pton(AF_INET6, buf, dst);
	if (ret != 1)
		return (-1);

	if (sep == NULL)
		return 128;

	bits = strtonum(sep, 0, 128, &errstr);
	if (errstr)
		return (-1);

	return bits;
}
