/*	$OpenBSD: smtp_session.c,v 1.28 2008/12/18 15:11:21 jacekm Exp $	*/

/*
 * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
 * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.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 <sys/types.h>
#include <sys/queue.h>
#include <sys/tree.h>
#include <sys/param.h>
#include <sys/socket.h>

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

#include <ctype.h>
#include <errno.h>
#include <event.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "smtpd.h"

int		session_rfc5321_helo_handler(struct session *, char *);
int		session_rfc5321_ehlo_handler(struct session *, char *);
int		session_rfc5321_rset_handler(struct session *, char *);
int		session_rfc5321_noop_handler(struct session *, char *);
int		session_rfc5321_data_handler(struct session *, char *);
int		session_rfc5321_mail_handler(struct session *, char *);
int		session_rfc5321_rcpt_handler(struct session *, char *);
int		session_rfc5321_vrfy_handler(struct session *, char *);
int		session_rfc5321_expn_handler(struct session *, char *);
int		session_rfc5321_turn_handler(struct session *, char *);
int		session_rfc5321_help_handler(struct session *, char *);
int		session_rfc5321_quit_handler(struct session *, char *);
int		session_rfc5321_none_handler(struct session *, char *);

int		session_rfc1652_mail_handler(struct session *, char *);

int		session_rfc3207_stls_handler(struct session *, char *);

int		session_rfc4954_auth_handler(struct session *, char *);

void		session_command(struct session *, char *, char *);
int		session_set_path(struct path *, char *);
void		session_timeout(int, short, void *);
void		session_respond(struct session *, char *, ...)
		    __attribute__ ((format (printf, 2, 3)));
void		session_cleanup(struct session *);

struct session_timeout {
	enum session_state	state;
	time_t			timeout;
};

struct session_timeout rfc5321_timeouttab[] = {
	{ S_INIT,		300 },
	{ S_GREETED,		300 },
	{ S_HELO,		300 },
	{ S_MAIL,		300 },
	{ S_RCPT,		300 },
	{ S_DATA,		120 },
	{ S_DATACONTENT,	180 },
	{ S_DONE,		600 }
};

struct session_cmd {
	char	 *name;
	int		(*func)(struct session *, char *);
};

struct session_cmd rfc5321_cmdtab[] = {
	{ "helo",	session_rfc5321_helo_handler },
	{ "ehlo",	session_rfc5321_ehlo_handler },
	{ "rset",	session_rfc5321_rset_handler },
	{ "noop",	session_rfc5321_noop_handler },
	{ "data",	session_rfc5321_data_handler },
	{ "mail from",	session_rfc5321_mail_handler },
	{ "rcpt to",	session_rfc5321_rcpt_handler },
	{ "vrfy",	session_rfc5321_vrfy_handler },
	{ "expn",	session_rfc5321_expn_handler },
	{ "turn",	session_rfc5321_turn_handler },
	{ "help",	session_rfc5321_help_handler },
	{ "quit",	session_rfc5321_quit_handler }
};

struct session_cmd rfc1652_cmdtab[] = {
	{ "mail from",	session_rfc1652_mail_handler },
};

struct session_cmd rfc3207_cmdtab[] = {
	{ "starttls",	session_rfc3207_stls_handler }
};

struct session_cmd rfc4954_cmdtab[] = {
	{ "auth",	session_rfc4954_auth_handler }
};

int
session_rfc3207_stls_handler(struct session *s, char *args)
{
	if (s->s_state == S_GREETED) {
		session_respond(s, "503 Polite people say HELO first");
		return 1;
	}

	if (args != NULL) {
		session_respond(s, "501 No parameters allowed");
		return 1;
	}

	session_respond(s, "220 Ready to start TLS");

	s->s_state = S_TLS;

	return 1;
}

int
session_rfc4954_auth_handler(struct session *s, char *args)
{
	char	*method;
	char	*eom;
	struct session_auth_req req;

	if (s->s_state == S_GREETED) {
		session_respond(s, "503 Polite people say HELO first");
		return 1;
	}

	if (args == NULL) {
		session_respond(s, "501 No parameters given");
		return 1;
	}

	method = args;
	eom = strchr(args, ' ');
	if (eom == NULL)
		eom = strchr(args, '\t');
	if (eom != NULL)
		*eom++ = '\0';

	if (eom == NULL) {
		/* NEEDS_FIX - unsupported yet */
		session_respond(s, "501 Syntax error");
		return 1;
	}

	req.session_id = s->s_id;
	if (strlcpy(req.buffer, eom, sizeof(req.buffer)) >=
	    sizeof(req.buffer)) {
		session_respond(s, "501 Syntax error");
		return 1;
	}

	s->s_state = S_AUTH;

	imsg_compose(s->s_env->sc_ibufs[PROC_PARENT], IMSG_PARENT_AUTHENTICATE,
	    0, 0, -1, &req, sizeof(req));
	bufferevent_disable(s->s_bev, EV_READ);

	return 1;
}

int
session_rfc1652_mail_handler(struct session *s, char *args)
{
	char *body;

	if (s->s_state == S_GREETED) {
		session_respond(s, "503 Polite people say HELO first");
		return 1;
	}

	body = strrchr(args, ' ');
	if (body != NULL) {
		*body++ = '\0';

		if (strcasecmp("body=7bit", body) == 0) {
			s->s_flags &= ~F_8BITMIME;
		}

		else if (strcasecmp("body=8bitmime", body) != 0) {
			session_respond(s, "503 Invalid BODY");
			return 1;
		}

		return session_rfc5321_mail_handler(s, args);
	}

	return 0;
}

int
session_rfc5321_helo_handler(struct session *s, char *args)
{
	void	*p;
	char	 addrbuf[INET6_ADDRSTRLEN];

	if (args == NULL) {
		session_respond(s, "501 HELO requires domain address");
		return 1;
	}

	if (strlcpy(s->s_msg.session_helo, args, sizeof(s->s_msg.session_helo))
	    >= sizeof(s->s_msg.session_helo)) {
		session_respond(s, "501 Invalid domain name");
		return 1;
	}

	s->s_state = S_HELO;
	s->s_flags = 0;

	if (s->s_ss.ss_family == PF_INET) {
		struct sockaddr_in *ssin = (struct sockaddr_in *)&s->s_ss;
		p = &ssin->sin_addr.s_addr;
	}
	if (s->s_ss.ss_family == PF_INET6) {
		struct sockaddr_in6 *ssin6 = (struct sockaddr_in6 *)&s->s_ss;
		p = &ssin6->sin6_addr.s6_addr;
	}

	bzero(addrbuf, sizeof (addrbuf));
	inet_ntop(s->s_ss.ss_family, p, addrbuf, sizeof (addrbuf));

	session_respond(s, "250 %s Hello %s [%s%s], pleased to meet you",
	    s->s_env->sc_hostname, args,
	    s->s_ss.ss_family == PF_INET ? "" : "IPv6:", addrbuf);

	return 1;
}

int
session_rfc5321_ehlo_handler(struct session *s, char *args)
{
	void	*p;
	char	 addrbuf[INET6_ADDRSTRLEN];

	if (args == NULL) {
		session_respond(s, "501 EHLO requires domain address");
		return 1;
	}

	if (strlcpy(s->s_msg.session_helo, args, sizeof(s->s_msg.session_helo))
	    >= sizeof(s->s_msg.session_helo)) {
		session_respond(s, "501 Invalid domain name");
		return 1;
	}

	s->s_state = S_HELO;
	s->s_flags = F_EHLO;
	s->s_flags |= F_8BITMIME;

	if (s->s_ss.ss_family == PF_INET) {
		struct sockaddr_in *ssin = (struct sockaddr_in *)&s->s_ss;
		p = &ssin->sin_addr.s_addr;
	}
	if (s->s_ss.ss_family == PF_INET6) {
		struct sockaddr_in6 *ssin6 = (struct sockaddr_in6 *)&s->s_ss;
		p = &ssin6->sin6_addr.s6_addr;
	}

	bzero(addrbuf, sizeof (addrbuf));
	inet_ntop(s->s_ss.ss_family, p, addrbuf, sizeof (addrbuf));
	session_respond(s, "250-%s Hello %s [%s%s], pleased to meet you",
	    s->s_env->sc_hostname, args,
	    s->s_ss.ss_family == PF_INET ? "" : "IPv6:", addrbuf);

	session_respond(s, "250-8BITMIME");

	/* only advertise starttls if listener can support it */
	if (s->s_l->flags & F_STARTTLS)
		session_respond(s, "250-STARTTLS");

	/* only advertise auth if session is secure */
	/*
	if (s->s_flags & F_SECURE)
		session_respond(s, "250-AUTH %s", "PLAIN");
	 */
	session_respond(s, "250 HELP");

	return 1;
}

int
session_rfc5321_rset_handler(struct session *s, char *args)
{
	s->s_msg.rcptcount = 0;
	s->s_state = S_HELO;
	session_respond(s, "250 Reset state");

	return 1;
}

int
session_rfc5321_noop_handler(struct session *s, char *args)
{
	session_respond(s, "250 OK");

	return 1;
}

int
session_rfc5321_mail_handler(struct session *s, char *args)
{
	char buffer[MAX_PATH_SIZE];

	if (s->s_state == S_GREETED) {
		session_respond(s, "503 Polite people say HELO first");
		return 1;
	}

	if (strlcpy(buffer, args, sizeof(buffer)) >= sizeof(buffer)) {
		session_respond(s, "553 Sender address syntax error");
		return 1;
	}

	if (! session_set_path(&s->s_msg.sender, buffer)) {
		/* No need to even transmit to MFA, path is invalid */
		session_respond(s, "553 Sender address syntax error");
		return 1;
	}

	session_cleanup(s);
	s->s_state = S_MAILREQUEST;
	s->s_msg.rcptcount = 0;
	s->s_msg.id = s->s_id;
	s->s_msg.session_id = s->s_id;
	s->s_msg.session_ss = s->s_ss;

	log_debug("session_mail_handler: sending notification to mfa");

	imsg_compose(s->s_env->sc_ibufs[PROC_MFA], IMSG_MFA_MAIL,
	    0, 0, -1, &s->s_msg, sizeof(s->s_msg));
	bufferevent_disable(s->s_bev, EV_READ);
	return 1;
}

int
session_rfc5321_rcpt_handler(struct session *s, char *args)
{
	char buffer[MAX_PATH_SIZE];
	struct message_recipient	mr;

	if (s->s_state == S_GREETED) {
		session_respond(s, "503 Polite people say HELO first");
		return 1;
	}

	if (s->s_state == S_HELO) {
		session_respond(s, "503 Need MAIL before RCPT");
		return 1;
	}

	bzero(&mr, sizeof(mr));

	if (strlcpy(buffer, args, sizeof(buffer)) >= sizeof(buffer)) {
		session_respond(s, "553 Recipient address syntax error");
		return 1;
	}

	if (! session_set_path(&mr.path, buffer)) {
		/* No need to even transmit to MFA, path is invalid */
		session_respond(s, "553 Recipient address syntax error");
		return 1;
	}

	mr.id = s->s_msg.id;
	s->s_state = S_RCPTREQUEST;
	mr.ss = s->s_ss;

	imsg_compose(s->s_env->sc_ibufs[PROC_MFA], IMSG_MFA_RCPT,
	    0, 0, -1, &mr, sizeof(mr));
	bufferevent_disable(s->s_bev, EV_READ);
	return 1;
}

int
session_rfc5321_quit_handler(struct session *s, char *args)
{
	session_respond(s, "221 %s Closing connection", s->s_env->sc_hostname);

	s->s_flags |= F_QUIT;

	return 1;
}

int
session_rfc5321_data_handler(struct session *s, char *args)
{
	if (s->s_state == S_GREETED) {
		session_respond(s, "503 Polite people say HELO first");
		return 1;
	}

	if (s->s_state == S_HELO) {
		session_respond(s, "503 Need MAIL before DATA");
		return 1;
	}

	if (s->s_state == S_MAIL) {
		session_respond(s, "503 Need RCPT before DATA");
		return 1;
	}

	s->s_state = S_DATAREQUEST;
	session_pickup(s, NULL);
	return 1;
}

int
session_rfc5321_vrfy_handler(struct session *s, char *args)
{
	session_respond(s, "252 Cannot VRFY; try RCPT to attempt delivery");

	return 1;
}

int
session_rfc5321_expn_handler(struct session *s, char *args)
{
	session_respond(s, "502 Sorry, we do not allow this operation");

	return 1;
}

int
session_rfc5321_turn_handler(struct session *s, char *args)
{
	session_respond(s, "502 Sorry, we do not allow this operation");

	return 1;
}

int
session_rfc5321_help_handler(struct session *s, char *args)
{
	session_respond(s, "214- This is OpenSMTPD");
	session_respond(s, "214- To report bugs in the implementation, please "
	    "contact bugs@openbsd.org");
	session_respond(s, "214- with full details");
	session_respond(s, "214 End of HELP info");

	return 1;
}

void
session_command(struct session *s, char *cmd, char *args)
{
	int	i;

	if (!(s->s_flags & F_EHLO))
		goto rfc5321;

	/* RFC 1652 - 8BITMIME */
	for (i = 0; i < (int)(sizeof(rfc1652_cmdtab) / sizeof(struct session_cmd)); ++i)
		if (strcasecmp(rfc1652_cmdtab[i].name, cmd) == 0)
			break;
	if (i < (int)(sizeof(rfc1652_cmdtab) / sizeof(struct session_cmd))) {
		if (rfc1652_cmdtab[i].func(s, args))
			return;
	}

	/* RFC 3207 - STARTTLS */
	for (i = 0; i < (int)(sizeof(rfc3207_cmdtab) / sizeof(struct session_cmd)); ++i)
		if (strcasecmp(rfc3207_cmdtab[i].name, cmd) == 0)
			break;
	if (i < (int)(sizeof(rfc3207_cmdtab) / sizeof(struct session_cmd))) {
		if (rfc3207_cmdtab[i].func(s, args))
			return;
	}

	/* RFC 4954 - AUTH */
	/*
	for (i = 0; i < (int)(sizeof(rfc4954_cmdtab) / sizeof(struct session_cmd)); ++i)
		if (strcasecmp(rfc4954_cmdtab[i].name, cmd) == 0)
			break;
	if (i < (int)(sizeof(rfc4954_cmdtab) / sizeof(struct session_cmd))) {
		if (rfc4954_cmdtab[i].func(s, args))
			return;
	}
	*/

rfc5321:
	/* RFC 5321 - SMTP */
	for (i = 0; i < (int)(sizeof(rfc5321_cmdtab) / sizeof(struct session_cmd)); ++i)
		if (strcasecmp(rfc5321_cmdtab[i].name, cmd) == 0)
			break;
	if (i < (int)(sizeof(rfc5321_cmdtab) / sizeof(struct session_cmd))) {
		if (rfc5321_cmdtab[i].func(s, args))
			return;
	}

	session_respond(s, "500 Command unrecognized");
}

void
session_pickup(struct session *s, struct submit_status *ss)
{
	if (s == NULL)
		fatal("session_pickup: desynchronized");

	bufferevent_enable(s->s_bev, EV_READ);

	if (ss != NULL && ss->code == 421)
		goto tempfail;

	switch (s->s_state) {
	case S_INIT:
		s->s_state = S_GREETED;
		log_debug("session_pickup: greeting client");
		session_respond(s, SMTPD_BANNER, s->s_env->sc_hostname);
		break;

	case S_GREETED:
	case S_HELO:
		break;

	case S_TLS:
		bufferevent_disable(s->s_bev, EV_READ|EV_WRITE);
		s->s_state = S_GREETED;
		ssl_session_init(s);
		break;

	case S_AUTH:
		if (s->s_flags & F_AUTHENTICATED)
			session_respond(s, "235 Authentication succeeded");
		else
			session_respond(s, "535 Authentication failed");
		break;

	case S_MAILREQUEST:
		/* sender was not accepted, downgrade state */
		if (ss->code != 250) {
			s->s_state = S_HELO;
			session_respond(s, "%d Sender rejected", ss->code);
			return;
		}

		s->s_state = S_MAIL;
		s->s_msg.sender = ss->u.path;

		imsg_compose(s->s_env->sc_ibufs[PROC_QUEUE],
		    IMSG_QUEUE_CREATE_MESSAGE, 0, 0, -1, &s->s_msg,
		    sizeof(s->s_msg));
		bufferevent_disable(s->s_bev, EV_READ);
		break;

	case S_MAIL:

		session_respond(s, "%d Sender ok", ss->code);

		break;

	case S_RCPTREQUEST:
		/* recipient was not accepted */
		if (ss->code != 250) {
			/* We do not have a valid recipient, downgrade state */
			if (s->s_msg.rcptcount == 0)
				s->s_state = S_MAIL;
			else
				s->s_state = S_RCPT;
			session_respond(s, "%d Recipient rejected", ss->code);
			return;
		}

		s->s_state = S_RCPT;
		s->s_msg.rcptcount++;
		s->s_msg.recipient = ss->u.path;
		imsg_compose(s->s_env->sc_ibufs[PROC_QUEUE],
		    IMSG_QUEUE_SUBMIT_ENVELOPE, 0, 0, -1, &s->s_msg,
		    sizeof(s->s_msg));
		bufferevent_disable(s->s_bev, EV_READ);
		break;

	case S_RCPT:
		session_respond(s, "%d Recipient ok", ss->code);
		break;

	case S_DATAREQUEST:
		s->s_state = S_DATA;
		imsg_compose(s->s_env->sc_ibufs[PROC_QUEUE],
		    IMSG_QUEUE_MESSAGE_FILE, 0, 0, -1, &s->s_msg,
		    sizeof(s->s_msg));
		bufferevent_disable(s->s_bev, EV_READ);
		break;

	case S_DATA:
		if (s->s_msg.datafp == NULL)
			goto tempfail;

		s->s_state = S_DATACONTENT;
		session_respond(s, "354 Enter mail, end with \".\" on a line by"
		    " itself");
		break;

	case S_DONE:
		s->s_state = S_HELO;

		session_respond(s, "250 %s Message accepted for delivery",
		    s->s_msg.message_id);

		break;

	default:
		log_debug("session_pickup: state value: %d", s->s_state);
		fatal("session_pickup: unknown state");
		break;
	}

	return;

tempfail:
	s->s_flags |= F_QUIT;
	session_respond(s, "421 Service temporarily unavailable");
	return;
}

void
session_init(struct listener *l, struct session *s)
{
	s->s_state = S_INIT;
	s->s_env = l->env;
	s->s_l = l;
	s->s_id = queue_generate_id();
	strlcpy(s->s_hostname, "<unknown>", MAXHOSTNAMELEN);
	strlcpy(s->s_msg.session_hostname, s->s_hostname, MAXHOSTNAMELEN);

	SPLAY_INSERT(sessiontree, &s->s_env->sc_sessions, s);

	imsg_compose(s->s_env->sc_ibufs[PROC_LKA], IMSG_LKA_HOST,
	    0, 0, -1, s, sizeof(struct session));

	if ((s->s_bev = bufferevent_new(s->s_fd, session_read, session_write,
	    session_error, s)) == NULL)
		fatal(NULL);

	if (l->flags & F_SSMTP) {
		log_debug("session_init: initializing ssl");
		ssl_session_init(s);
		return;
	}

	session_pickup(s, NULL);
}

void
session_read(struct bufferevent *bev, void *p)
{
	struct session	*s = p;
	char		*line;
	char		*ep;
	char		*args;

read:
	s->s_tm = time(NULL);
	line = evbuffer_readline(bev->input);
	if (line == NULL) {
		if (s->s_state != S_DATACONTENT)
			bufferevent_disable(s->s_bev, EV_READ);
		return;
	}

	if (s->s_state == S_DATACONTENT) {
		/*log_debug("content: %s", line);*/
		if (strcmp(line, ".") == 0) {
			s->s_state = S_DONE;
			fclose(s->s_msg.datafp);
			s->s_msg.datafp = NULL;

			bufferevent_disable(s->s_bev, EV_READ);

			if (s->s_msg.status & S_MESSAGE_PERMFAILURE) {
				session_respond(s, "554 Transaction failed");
				free(line);
				return;
			}
			session_msg_submit(s);
			free(line);
			return;
		} else {
			size_t i;
			size_t len;

			len = strlen(line);
			fwrite(line, 1, len, s->s_msg.datafp);
			fwrite("\n", 1, 1, s->s_msg.datafp);
			fflush(s->s_msg.datafp);

			if (! (s->s_flags & F_8BITMIME)) {
				for (i = 0; i < len; ++i) {
					if (line[i] & 0x80) {
						s->s_msg.status |= S_MESSAGE_PERMFAILURE;
						strlcpy(s->s_msg.session_errorline,
						    "8BIT data transfered over 7BIT limited channel",
							sizeof s->s_msg.session_errorline);
					}
				}
			}
			free(line);
		}
		goto read;
	}

	if ((ep = strchr(line, ':')) == NULL)
		ep = strchr(line, ' ');
	if (ep != NULL) {
		*ep = '\0';
		args = ++ep;
		while (isspace((int)*args))
			args++;
	} else
		args = NULL;
	log_debug("command: %s\targs: %s", line, args);
	session_command(s, line, args);
	free(line);
	return;
}

void
session_write(struct bufferevent *bev, void *p)
{
	struct session	*s = p;

	if (!(s->s_flags & F_QUIT)) {
		
		if (s->s_state == S_TLS)
			session_pickup(s, NULL);

		return;
	}

	session_destroy(s);
}

void
session_destroy(struct session *s)
{
	session_cleanup(s);

	log_debug("session_destroy: killing client: %p", s);
	close(s->s_fd);

	if (s->s_bev != NULL) {
		bufferevent_free(s->s_bev);
	}
	ssl_session_destroy(s);

	SPLAY_REMOVE(sessiontree, &s->s_env->sc_sessions, s);
	bzero(s, sizeof(*s));
	free(s);
}

void
session_cleanup(struct session *s)
{
	if (s->s_msg.datafp != NULL) {
		fclose(s->s_msg.datafp);
		s->s_msg.datafp = NULL;
	}

	if (s->s_msg.message_id[0] != '\0') {
		imsg_compose(s->s_env->sc_ibufs[PROC_QUEUE],
		    IMSG_QUEUE_REMOVE_MESSAGE, 0, 0, -1, &s->s_msg,
		    sizeof(s->s_msg));
		s->s_msg.message_id[0] = '\0';
		s->s_msg.message_uid[0] = '\0';
	}
}

void
session_error(struct bufferevent *bev, short event, void *p)
{
	struct session	*s = p;

	session_destroy(s);
}

void
session_msg_submit(struct session *s)
{
	imsg_compose(s->s_env->sc_ibufs[PROC_QUEUE],
	    IMSG_QUEUE_COMMIT_MESSAGE, 0, 0, -1, &s->s_msg,
	    sizeof(s->s_msg));
	s->s_state = S_DONE;
}

int
session_cmp(struct session *s1, struct session *s2)
{
	/*
	 * do not return u_int64_t's
	 */
	if (s1->s_id < s2->s_id)
		return (-1);

	if (s1->s_id > s2->s_id)
		return (1);

	return (0);
}

int
session_set_path(struct path *path, char *line)
{
	size_t len;
	char *username;
	char *hostname;

	len = strlen(line);
	if (*line != '<' || line[len - 1] != '>')
		return 0;
	line[len - 1] = '\0';

	username = line + 1;
	hostname = strchr(username, '@');

	if (username[0] == '\0') {
		*path->user = '\0';
		*path->domain = '\0';
		return 1;
	}

	if (hostname == NULL) {
		if (strcasecmp(username, "postmaster") != 0)
			return 0;
		hostname = "localhost";
	} else {
		*hostname++ = '\0';
	}

	if (strlcpy(path->user, username, sizeof(path->user))
	    >= MAX_LOCALPART_SIZE)
		return 0;

	if (strlcpy(path->domain, hostname, sizeof(path->domain))
	    >= MAX_DOMAINPART_SIZE)
		return 0;

	return 1;
}

void
session_timeout(int fd, short event, void *p)
{
	struct smtpd		*env = p;
	struct session		*sessionp;
	struct session		*rmsession;
	struct timeval		 tv;
	time_t			 tm;
	u_int8_t		 i;

	tm = time(NULL);
	rmsession = NULL;
	SPLAY_FOREACH(sessionp, sessiontree, &env->sc_sessions) {

		if (rmsession != NULL) {
			session_destroy(rmsession);
			rmsession = NULL;
		}

		for (i = 0; i < sizeof (rfc5321_timeouttab) /
			 sizeof(struct session_timeout); ++i)
			if (rfc5321_timeouttab[i].state == sessionp->s_state)
				break;

		if (i == sizeof (rfc5321_timeouttab) / sizeof (struct session_timeout)) {
			if (tm - SMTPD_SESSION_TIMEOUT < sessionp->s_tm)
				continue;
		}
		else if (tm - rfc5321_timeouttab[i].timeout < sessionp->s_tm) {
				continue;
		}

		rmsession = sessionp;
	}

	if (rmsession != NULL)
		session_destroy(rmsession);

	tv.tv_sec = 1;
	tv.tv_usec = 0;
	evtimer_add(&env->sc_ev, &tv);
}

void
session_respond(struct session *s, char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	if (evbuffer_add_vprintf(EVBUFFER_OUTPUT(s->s_bev), fmt, ap) == -1 ||
	    evbuffer_add_printf(EVBUFFER_OUTPUT(s->s_bev), "\r\n") == -1)
		fatal("session_respond: evbuffer_add_vprintf failed");
	va_end(ap);

	bufferevent_enable(s->s_bev, EV_WRITE);
}

SPLAY_GENERATE(sessiontree, session, s_nodes, session_cmp);