Exemple #1
0
static void
queue_envelope_cache_update(struct envelope *e)
{
	struct envelope *cached;

	if ((cached = tree_get(&evpcache_tree, e->id)) == NULL) {
		queue_envelope_cache_add(e);
		stat_increment("queue.evpcache.update.missed", 1);
	} else {
		TAILQ_REMOVE(&evpcache_list, cached, entry);
		*cached = *e;
		TAILQ_INSERT_HEAD(&evpcache_list, cached, entry);
		stat_increment("queue.evpcache.update.hit", 1);
	}
}
Exemple #2
0
static int
queue_ram_envelope_create(uint32_t msgid, const char *buf, size_t len,
    uint64_t *evpid)
{
	struct qr_envelope	*evp;
	struct qr_message	*msg;

	if ((msg = get_message(msgid)) == NULL)
		return (0);

	do {
		*evpid = queue_generate_evpid(msgid);
	} while (tree_check(&msg->envelopes, *evpid));
	evp = calloc(1, sizeof *evp);
	if (evp == NULL) {
		log_warn("warn: queue-ram: calloc");
		return (0);
	}
	evp->len = len;
	evp->buf = malloc(len);
	if (evp->buf == NULL) {
		log_warn("warn: queue-ram: malloc");
		free(evp);
		return (0);
	}
	memmove(evp->buf, buf, len);
	tree_xset(&msg->envelopes, *evpid, evp);
	stat_increment("queue.ram.envelope.size", len);
	return (1);
}
Exemple #3
0
static int
queue_ram_envelope_update(uint64_t evpid, const char *buf, size_t len)
{
	struct qr_envelope	*evp;
	struct qr_message	*msg;
	void			*tmp;

	if ((msg = get_message(evpid_to_msgid(evpid))) == NULL)
		return (0);

	if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) {
		log_warn("warn: queue-ram: not found");
		return (0);
	}
	tmp = malloc(len);
	if (tmp == NULL) {
		log_warn("warn: queue-ram: malloc");
		return (0);
	}
	memmove(tmp, buf, len);
	free(evp->buf);
	evp->len = len;
	evp->buf = tmp;
	stat_decrement("queue.ram.envelope.size", evp->len);
	stat_increment("queue.ram.envelope.size", len);
	return (1);
}
Exemple #4
0
/* ARGSUSED */
void
control_accept(int listenfd, short event, void *arg)
{
	int			 connfd;
	socklen_t		 len;
	struct sockaddr_un	 sun;
	struct ctl_conn		*c;

	len = sizeof(sun);
	if ((connfd = accept(listenfd, (struct sockaddr *)&sun, &len)) == -1) {
		if (errno == EINTR || errno == ECONNABORTED)
			return;
		fatal("control_accept: accept");
	}

	session_socket_blockmode(connfd, BM_NONBLOCK);

	if ((c = calloc(1, sizeof(*c))) == NULL)
		fatal(NULL);
	imsg_init(&c->iev.ibuf, connfd);
	c->iev.handler = control_dispatch_ext;
	c->iev.events = EV_READ;
	event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events,
	    c->iev.handler, NULL);
	event_add(&c->iev.ev, NULL);
	TAILQ_INSERT_TAIL(&ctl_conns, c, entry);

	if (stat_increment(STATS_CONTROL_SESSION) >= env->sc_maxconn) {
		log_warnx("ctl client limit hit, disabling new connections");
		event_del(&control_state.ev);
	}
}
Exemple #5
0
static void
session_read_data(struct session *s, char *line)
{
	size_t datalen;
	size_t len;
	size_t i;

	if (strcmp(line, ".") == 0) {
		s->s_datalen = ftell(s->datafp);
		if (! safe_fclose(s->datafp))
			s->s_dstatus |= DS_TEMPFAILURE;
		s->datafp = NULL;

		if (s->s_dstatus & DS_PERMFAILURE) {
			session_respond(s, "554 5.0.0 Transaction failed");
			session_enter_state(s, S_HELO);
		} else if (s->s_dstatus & DS_TEMPFAILURE) {
			session_respond(s, "421 4.0.0 Temporary failure");
			session_enter_state(s, S_QUIT);
			stat_increment("smtp.tempfail", 1);
		} else {
			session_imsg(s, PROC_QUEUE, IMSG_QUEUE_COMMIT_MESSAGE,
			    0, 0, -1, &s->s_msg, sizeof(s->s_msg));
			session_enter_state(s, S_DONE);
		}
		return;
	}

	/* Don't waste resources on message if it's going to bin anyway. */
	if (s->s_dstatus & (DS_PERMFAILURE|DS_TEMPFAILURE))
		return;

	/* "If the first character is a period and there are other characters
	 *  on the line, the first character is deleted." [4.5.2]
	 */
	if (*line == '.')
		line++;

	len = strlen(line);

	/* If size of data overflows a size_t or exceeds max size allowed
	 * for a message, set permanent failure.
	 */
	datalen = ftell(s->datafp);
	if (SIZE_MAX - datalen < len + 1 ||
	    datalen + len + 1 > env->sc_maxsize) {
		s->s_dstatus |= DS_PERMFAILURE;
		return;
	}

	if (! (s->s_flags & F_8BITMIME)) {
		for (i = 0; i < len; ++i)
			if (line[i] & 0x80)
				line[i] = line[i] & 0x7f;
	}

	if (fprintf(s->datafp, "%s\n", line) != (int)len + 1)
		s->s_dstatus |= DS_TEMPFAILURE;
}
Exemple #6
0
static void
session_line(struct session *s, char *line, size_t len)
{
	struct submit_status ss;

	if (s->s_state != S_DATACONTENT)
		log_trace(TRACE_SMTP, "smtp: %p: <<< %s", s, line);

	switch (s->s_state) {
	case S_AUTH_INIT:
		if (s->s_dstatus & DS_TEMPFAILURE)
			goto tempfail;
		session_rfc4954_auth_plain(s, line);
		break;

	case S_AUTH_USERNAME:
	case S_AUTH_PASSWORD:
		if (s->s_dstatus & DS_TEMPFAILURE)
			goto tempfail;
		session_rfc4954_auth_login(s, line);
		break;

	case S_GREETED:
	case S_HELO:
	case S_MAIL:
	case S_RCPT:
		if (s->s_dstatus & DS_TEMPFAILURE)
			goto tempfail;
		session_command(s, line);
		break;

	case S_DATACONTENT:
		if (env->filtermask & FILTER_DATALINE) {
			bzero(&ss, sizeof(ss));
			ss.id = s->s_id;
			if (strlcpy(ss.u.dataline, line,
				sizeof(ss.u.dataline)) >= sizeof(ss.u.dataline))
				fatal("session_line: data truncation");

			session_imsg(s, PROC_MFA, IMSG_MFA_DATALINE,
			    0, 0, -1, &ss, sizeof(ss));
		} else {
			/* no filtering */
			session_read_data(s, line);
		}
		break;

	default:
		log_debug("session_read: %i", s->s_state);
		fatalx("session_read: unexpected state");
	}

	return;

tempfail:
	session_respond(s, "421 4.0.0 Service temporarily unavailable");
	stat_increment("smtp.tempfail", 1);
	session_enter_state(s, S_QUIT);
}
Exemple #7
0
void
mta_session(struct mta_relay *relay, struct mta_route *route)
{
	struct mta_session	*s;
	struct timeval		 tv;

	mta_session_init();

	s = xcalloc(1, sizeof *s, "mta_session");
	s->id = generate_uid();
	s->relay = relay;
	s->route = route;
	s->io.sock = -1;

	if (relay->flags & RELAY_SSL && relay->flags & RELAY_AUTH)
		s->flags |= MTA_USE_AUTH;
	if (relay->pki_name)
		s->flags |= MTA_USE_CERT;
	if (relay->flags & RELAY_LMTP)
		s->flags |= MTA_LMTP;
	switch (relay->flags & (RELAY_SSL|RELAY_TLS_OPTIONAL)) {
		case RELAY_SSL:
			s->flags |= MTA_FORCE_ANYSSL;
			s->flags |= MTA_WANT_SECURE;
			break;
		case RELAY_SMTPS:
			s->flags |= MTA_FORCE_SMTPS;
			s->flags |= MTA_WANT_SECURE;
			break;
		case RELAY_STARTTLS:
			s->flags |= MTA_FORCE_TLS;
			s->flags |= MTA_WANT_SECURE;
			break;
		case RELAY_TLS_OPTIONAL:
			/* do not force anything, try tls then smtp */
			break;
		default:
			s->flags |= MTA_FORCE_PLAIN;
	}

	log_debug("debug: mta: %p: spawned for relay %s", s,
	    mta_relay_to_text(relay));
	stat_increment("mta.session", 1);

	if (route->dst->ptrname || route->dst->lastptrquery) {
		/* We want to delay the connection since to always notify
		 * the relay asynchronously.
		 */
		tv.tv_sec = 0;
		tv.tv_usec = 0;
		evtimer_set(&s->io.ev, mta_start, s);
		evtimer_add(&s->io.ev, &tv);
	} else if (waitq_wait(&route->dst->ptrname, mta_on_ptr, s)) {
		dns_query_ptr(s->id, s->route->dst->sa);
		tree_xset(&wait_ptr, s->id, s);
		s->flags |= MTA_WAIT;
	}
}
Exemple #8
0
int
queue_envelope_load(uint64_t evpid, struct envelope *ep)
{
	const char	*e;
	char		 evpbuf[sizeof(struct envelope)];
	size_t		 evplen;
	struct envelope	*cached;

	if ((env->sc_queue_flags & QUEUE_EVPCACHE) &&
	    (cached = tree_get(&evpcache_tree, evpid))) {
		*ep = *cached;
		stat_increment("queue.evpcache.load.hit", 1);
		return (1);
	}

	ep->id = evpid;
	profile_enter("queue_envelope_load");
	evplen = handler_envelope_load(ep->id, evpbuf, sizeof evpbuf);
	profile_leave();

	log_trace(TRACE_QUEUE,
	    "queue-backend: queue_envelope_load(%016"PRIx64") -> %zu",
	    evpid, evplen);

	if (evplen == 0)
		return (0);

	if (queue_envelope_load_buffer(ep, evpbuf, evplen)) {
		if ((e = envelope_validate(ep)) == NULL) {
			ep->id = evpid;
			if (env->sc_queue_flags & QUEUE_EVPCACHE) {
				queue_envelope_cache_add(ep);
				stat_increment("queue.evpcache.load.missed", 1);
			}
			return (1);
		}
		log_debug("debug: invalid envelope %016" PRIx64 ": %s",
		    ep->id, e);
	}

	(void)queue_message_corrupt(evpid_to_msgid(evpid));
	return (0);
}
Exemple #9
0
static struct mta_route *
mta_route_for(struct envelope *e)
{
	struct ssl		ssl;
	struct mta_route	key, *route;

	bzero(&key, sizeof key);

	key.flags = e->agent.mta.relay.flags;
	if (e->agent.mta.relay.flags & ROUTE_BACKUP) {
		key.hostname = e->dest.domain;
		key.backupname = e->agent.mta.relay.hostname;
	} else if (e->agent.mta.relay.hostname[0]) {
		key.hostname = e->agent.mta.relay.hostname;
		key.flags |= ROUTE_MX;
	} else
		key.hostname = e->dest.domain;
	key.port = e->agent.mta.relay.port;
	key.cert = e->agent.mta.relay.cert;
	if (!key.cert[0])
		key.cert = NULL;
	key.auth = e->agent.mta.relay.authmap;
	if (!key.auth[0])
		key.auth = NULL;

	if ((route = SPLAY_FIND(mta_route_tree, &routes, &key)) == NULL) {
		route = xcalloc(1, sizeof *route, "mta_route");
		TAILQ_INIT(&route->tasks);
		route->id = generate_uid();
		route->flags = key.flags;
		route->hostname = xstrdup(key.hostname, "mta: hostname");
		route->backupname = key.backupname ?
		    xstrdup(key.backupname, "mta: backupname") : NULL;
		route->port = key.port;
		route->cert = key.cert ? xstrdup(key.cert, "mta: cert") : NULL;
		route->auth = key.auth ? xstrdup(key.auth, "mta: auth") : NULL;
		if (route->cert) {
			strlcpy(ssl.ssl_name, route->cert, sizeof(ssl.ssl_name));
			route->ssl = SPLAY_FIND(ssltree, env->sc_ssl, &ssl);
		}
		SPLAY_INSERT(mta_route_tree, &routes, route);

		route->maxconn = MTA_MAXCONN;
		route->maxmail = MTA_MAXMAIL;
		route->maxrcpt = MTA_MAXRCPT;

		log_trace(TRACE_MTA, "mta: new %s", mta_route_to_text(route));
		stat_increment("mta.route", 1);
	} else {
		log_trace(TRACE_MTA, "mta: reusing %s", mta_route_to_text(route));
	}

	return (route);
}
Exemple #10
0
static void
queue_envelope_cache_add(struct envelope *e)
{
	struct envelope *cached;

	while (tree_count(&evpcache_tree) >= env->sc_queue_evpcache_size)
		queue_envelope_cache_del(TAILQ_LAST(&evpcache_list, evplst)->id);

	cached = xcalloc(1, sizeof *cached, "queue_envelope_cache_add");
	*cached = *e;
	TAILQ_INSERT_HEAD(&evpcache_list, cached, entry);
	tree_xset(&evpcache_tree, e->id, cached);
	stat_increment("queue.evpcache.size", 1);
}
Exemple #11
0
static int
queue_ram_message_commit(uint32_t msgid, const char *path)
{
	struct qr_message	*msg;
	struct stat		 sb;
	size_t			 n;
	FILE			*f;
	int			 ret;

	if ((msg = tree_get(&messages, msgid)) == NULL) {
		log_warnx("warn: queue-ram: msgid not found");
		return (0);
	}

	f = fopen(path, "rb");
	if (f == NULL) {
		log_warn("warn: queue-ram: fopen: %s", path);
		return (0);
	}
	if (fstat(fileno(f), &sb) == -1) {
		log_warn("warn: queue-ram: fstat");
		fclose(f);
		return (0);
	}

	msg->len = sb.st_size;
	msg->buf = malloc(msg->len);
	if (msg->buf == NULL) {
		log_warn("warn: queue-ram: malloc");
		fclose(f);
		return (0);
	}

	ret = 0;
	n = fread(msg->buf, 1, msg->len, f);
	if (ferror(f))
		log_warn("warn: queue-ram: fread");
	else if ((off_t)n != sb.st_size)
		log_warnx("warn: queue-ram: bad read");
	else {
		ret = 1;
		stat_increment("queue.ram.message.size", msg->len);
	}
	fclose(f);

	return (ret);
}
Exemple #12
0
static void
queue_bounce(struct envelope *e, struct delivery_bounce *d)
{
	struct envelope	b;

	b = *e;
	b.type = D_BOUNCE;
	b.agent.bounce = *d;
	b.retry = 0;
	b.lasttry = 0;
	b.creation = time(NULL);
	b.expire = 3600 * 24 * 7;

	if (e->dsn_notify & DSN_NEVER)
		return;

	if (b.id == 0)
		log_warnx("warn: queue_bounce: evpid=0");
	if (evpid_to_msgid(b.id) == 0)
		log_warnx("warn: queue_bounce: msgid=0, evpid=%016"PRIx64,
			b.id);
	if (e->type == D_BOUNCE) {
		log_warnx("warn: queue: double bounce!");
	} else if (e->sender.user[0] == '\0') {
		log_warnx("warn: queue: no return path!");
	} else if (!queue_envelope_create(&b)) {
		log_warnx("warn: queue: cannot bounce!");
	} else {
		log_debug("debug: queue: bouncing evp:%016" PRIx64
		    " as evp:%016" PRIx64, e->id, b.id);

		m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1);
		m_add_envelope(p_scheduler, &b);
		m_close(p_scheduler);

		m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, 0, 0, -1);
		m_add_msgid(p_scheduler, evpid_to_msgid(b.id));
		m_close(p_scheduler);

		stat_increment("queue.bounce", 1);
	}
}
Exemple #13
0
void
mta_session(struct mta_route *route)
{
	struct mta_session	*session;

	session = xcalloc(1, sizeof *session, "mta_session");
	session->id = generate_uid();
	session->route = route;
	session->state = MTA_INIT;
	session->io.sock = -1;
	tree_xset(&sessions, session->id, session);
	TAILQ_INIT(&session->hosts);

	if (route->flags & ROUTE_MX)
		session->flags |= MTA_FORCE_MX;
	if (route->flags & ROUTE_SSL && route->flags & ROUTE_AUTH)
		session->flags |= MTA_USE_AUTH;
	if (route->cert)
		session->flags |= MTA_USE_CERT;
	switch (route->flags & ROUTE_SSL) {
		case ROUTE_SSL:
			session->flags |= MTA_FORCE_ANYSSL;
			break;
		case ROUTE_SMTPS:
			session->flags |= MTA_FORCE_SMTPS;
			break;
		case ROUTE_STARTTLS:
			/* STARTTLS is tried by default */
			break;
		default:
			session->flags |= MTA_ALLOW_PLAIN;
	}

	log_debug("mta: %p: spawned for %s", session, mta_route_to_text(route));
	stat_increment("mta.session", 1);
	mta_enter_state(session, MTA_INIT);
}
Exemple #14
0
void
mta_imsg(struct imsgev *iev, struct imsg *imsg)
{
	struct mta_route	*route;
	struct mta_batch2	*batch;
	struct mta_task		*task;
	struct envelope		*e;
	struct ssl		*ssl;
	uint64_t		 id;

	if (iev->proc == PROC_QUEUE) {
		switch (imsg->hdr.type) {

		case IMSG_BATCH_CREATE:
			id = *(uint64_t*)(imsg->data);
			batch = xmalloc(sizeof *batch, "mta_batch");
			batch->id = id;
			tree_init(&batch->tasks);
			tree_xset(&batches, batch->id, batch);
			log_trace(TRACE_MTA,
			    "mta: batch:%016" PRIx64 " created", batch->id);
			return;

		case IMSG_BATCH_APPEND:
			e = xmemdup(imsg->data, sizeof *e, "mta:envelope");
			route = mta_route_for(e);
			batch = tree_xget(&batches, e->batch_id);

			if ((task = tree_get(&batch->tasks, route->id)) == NULL) {
				log_trace(TRACE_MTA, "mta: new task for %s",
				    mta_route_to_text(route));
				task = xmalloc(sizeof *task, "mta_task");
				TAILQ_INIT(&task->envelopes);
				task->route = route;
				tree_xset(&batch->tasks, route->id, task);
				task->msgid = evpid_to_msgid(e->id);
				task->sender = e->sender;
				route->refcount += 1;
			}

			/* Technically, we could handle that by adding a msg
			 * level, but the batch sent by the scheduler should
			 * be valid.
			 */
			if (task->msgid != evpid_to_msgid(e->id))
				errx(1, "msgid mismatch in batch");

			/* XXX honour route->maxrcpt */
			TAILQ_INSERT_TAIL(&task->envelopes, e, entry);
			stat_increment("mta.envelope", 1);
			log_debug("mta: received evp:%016" PRIx64 " for <%s@%s>",
			    e->id, e->dest.user, e->dest.domain);
			return;

		case IMSG_BATCH_CLOSE:
			id = *(uint64_t*)(imsg->data);
			batch = tree_xpop(&batches, id);
			log_trace(TRACE_MTA, "mta: batch:%016" PRIx64 " closed",
			    batch->id);
			/* for all tasks, queue them on there route */
			while (tree_poproot(&batch->tasks, &id, (void**)&task)) {
				if (id != task->route->id)
					errx(1, "route id mismatch!");
				task->route->refcount -= 1;
				task->route->ntask += 1;
				TAILQ_INSERT_TAIL(&task->route->tasks, task, entry);
				stat_increment("mta.task", 1);
				mta_route_drain(task->route);
			}
			free(batch);
			return;

		case IMSG_QUEUE_MESSAGE_FD:
			mta_session_imsg(iev, imsg);
			return;
		}
	}

	if (iev->proc == PROC_LKA) {
		switch (imsg->hdr.type) {
		case IMSG_LKA_SECRET:
		case IMSG_DNS_HOST:
		case IMSG_DNS_HOST_END:
		case IMSG_DNS_PTR:
			mta_session_imsg(iev, imsg);
			return;
		}
	}

	if (iev->proc == PROC_PARENT) {
		switch (imsg->hdr.type) {
		case IMSG_CONF_START:
			if (env->sc_flags & SMTPD_CONFIGURING)
				return;
			env->sc_flags |= SMTPD_CONFIGURING;
			env->sc_ssl = xcalloc(1, sizeof *env->sc_ssl, "mta:sc_ssl");
			return;

		case IMSG_CONF_SSL:
			if (!(env->sc_flags & SMTPD_CONFIGURING))
				return;
			ssl = xmemdup(imsg->data, sizeof *ssl, "mta:ssl");
			ssl->ssl_cert = xstrdup((char*)imsg->data + sizeof *ssl,
			    "mta:ssl_cert");
			ssl->ssl_key = xstrdup((char*)imsg->data +
			    sizeof *ssl + ssl->ssl_cert_len, "mta:ssl_key");
			SPLAY_INSERT(ssltree, env->sc_ssl, ssl);
			return;

		case IMSG_CONF_END:
			if (!(env->sc_flags & SMTPD_CONFIGURING))
				return;
			env->sc_flags &= ~SMTPD_CONFIGURING;
			return;

		case IMSG_CTL_VERBOSE:
			log_verbose(*(int *)imsg->data);
			return;
		}
	}

	errx(1, "mta_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type));
}
Exemple #15
0
static void
mta_enter_state(struct mta_session *s, int newstate)
{
	struct mta_envelope	 *e;
	size_t			 envid_sz;
	int			 oldstate;
	ssize_t			 q;
	char			 ibuf[SMTPD_MAXLINESIZE];
	char			 obuf[SMTPD_MAXLINESIZE];
	int			 offset;

    again:
	oldstate = s->state;

	log_trace(TRACE_MTA, "mta: %p: %s -> %s", s,
	    mta_strstate(oldstate),
	    mta_strstate(newstate));

	s->state = newstate;

	/* don't try this at home! */
#define mta_enter_state(_s, _st) do { newstate = _st; goto again; } while (0)

	switch (s->state) {
	case MTA_INIT:
	case MTA_BANNER:
		break;

	case MTA_EHLO:
		s->ext = 0;
		mta_send(s, "EHLO %s", s->helo);
		break;

	case MTA_HELO:
		s->ext = 0;
		mta_send(s, "HELO %s", s->helo);
		break;

	case MTA_LHLO:
		s->ext = 0;
		mta_send(s, "LHLO %s", s->helo);
		break;

	case MTA_STARTTLS:
		if (s->flags & MTA_TLS) /* already started */
			mta_enter_state(s, MTA_AUTH);
		else if ((s->ext & MTA_EXT_STARTTLS) == 0) {
			if (s->flags & MTA_FORCE_TLS || s->flags & MTA_WANT_SECURE) {
				mta_error(s, "TLS required but not supported by remote host");
				mta_connect(s);
			}
			else
				/* server doesn't support starttls, do not use it */
				mta_enter_state(s, MTA_AUTH);
		}
		else
			mta_send(s, "STARTTLS");
		break;

	case MTA_AUTH:
		if (s->relay->secret && s->flags & MTA_TLS) {
			if (s->ext & MTA_EXT_AUTH) {
				if (s->ext & MTA_EXT_AUTH_PLAIN) {
					mta_enter_state(s, MTA_AUTH_PLAIN);
					break;
				}
				if (s->ext & MTA_EXT_AUTH_LOGIN) {
					mta_enter_state(s, MTA_AUTH_LOGIN);
					break;
				}
				log_debug("debug: mta: %p: no supported AUTH method on session", s);
				mta_error(s, "no supported AUTH method");
			}
			else {
				log_debug("debug: mta: %p: AUTH not advertised on session", s);
				mta_error(s, "AUTH not advertised");
			}
		}
		else if (s->relay->secret) {
			log_debug("debug: mta: %p: not using AUTH on non-TLS "
			    "session", s);
			mta_error(s, "Refuse to AUTH over unsecure channel");
			mta_connect(s);
		} else {
			mta_enter_state(s, MTA_READY);
		}
		break;

	case MTA_AUTH_PLAIN:
		mta_send(s, "AUTH PLAIN %s", s->relay->secret);
		break;

	case MTA_AUTH_LOGIN:
		mta_send(s, "AUTH LOGIN");
		break;

	case MTA_AUTH_LOGIN_USER:
		memset(ibuf, 0, sizeof ibuf);
		if (base64_decode(s->relay->secret, (unsigned char *)ibuf,
				  sizeof(ibuf)-1) == -1) {
			log_debug("debug: mta: %p: credentials too large on session", s);
			mta_error(s, "Credentials too large");
			break;
		}

		memset(obuf, 0, sizeof obuf);
		base64_encode((unsigned char *)ibuf + 1, strlen(ibuf + 1), obuf, sizeof obuf);
		mta_send(s, "%s", obuf);

		memset(ibuf, 0, sizeof ibuf);
		memset(obuf, 0, sizeof obuf);
		break;

	case MTA_AUTH_LOGIN_PASS:
		memset(ibuf, 0, sizeof ibuf);
		if (base64_decode(s->relay->secret, (unsigned char *)ibuf,\
				  sizeof(ibuf)-1) == -1) {
			log_debug("debug: mta: %p: credentials too large on session", s);
			mta_error(s, "Credentials too large");
			break;
		}

		offset = strlen(ibuf+1)+2;
		memset(obuf, 0, sizeof obuf);
		base64_encode((unsigned char *)ibuf + offset, strlen(ibuf + offset), obuf, sizeof obuf);
		mta_send(s, "%s", obuf);

		memset(ibuf, 0, sizeof ibuf);
		memset(obuf, 0, sizeof obuf);
		break;

	case MTA_READY:
		/* Ready to send a new mail */
		if (s->ready == 0) {
			s->ready = 1;
			s->relay->nconn_ready += 1;
			mta_route_ok(s->relay, s->route);
		}

		if (s->msgtried >= MAX_TRYBEFOREDISABLE) {
			log_info("smtp-out: Remote host seems to reject all mails on session %016"PRIx64,
			    s->id);
			mta_route_down(s->relay, s->route);
			mta_enter_state(s, MTA_QUIT);
			break;
		}

		if (s->msgcount >= s->relay->limits->max_mail_per_session) {
			log_debug("debug: mta: "
			    "%p: cannot send more message to relay %s", s,
			    mta_relay_to_text(s->relay));
			mta_enter_state(s, MTA_QUIT);
			break;
		}

		s->task = mta_route_next_task(s->relay, s->route);
		if (s->task == NULL) {
			log_debug("debug: mta: %p: no task for relay %s",
			    s, mta_relay_to_text(s->relay));

			if (s->relay->nconn > 1 ||
			    s->hangon >= s->relay->limits->sessdelay_keepalive) {
				mta_enter_state(s, MTA_QUIT);
				break;
			}

			log_debug("mta: debug: last connection: hanging on for %llds",
			    (long long)(s->relay->limits->sessdelay_keepalive -
			    s->hangon));
			s->flags |= MTA_HANGON;
			runq_schedule(hangon, time(NULL) + 1, NULL, s);
			break;
		}

		log_debug("debug: mta: %p: handling next task for relay %s", s,
			    mta_relay_to_text(s->relay));

		stat_increment("mta.task.running", 1);

		m_create(p_queue, IMSG_QUEUE_MESSAGE_FD, 0, 0, -1);
		m_add_id(p_queue, s->id);
		m_add_msgid(p_queue, s->task->msgid);
		m_close(p_queue);

		tree_xset(&wait_fd, s->id, s);
		s->flags |= MTA_WAIT;
		break;

	case MTA_MAIL:
		if (s->currevp == NULL)
			s->currevp = TAILQ_FIRST(&s->task->envelopes);

		e = s->currevp;
		s->hangon = 0;
		s->msgtried++;
		envid_sz = strlen(e->dsn_envid);
		if (s->ext & MTA_EXT_DSN) {
			mta_send(s, "MAIL FROM:<%s> %s%s %s%s",
			    s->task->sender,
			    e->dsn_ret ? "RET=" : "",
			    e->dsn_ret ? dsn_strret(e->dsn_ret) : "",
			    envid_sz ? "ENVID=" : "",
			    envid_sz ? e->dsn_envid : "");
		} else
			mta_send(s, "MAIL FROM:<%s>", s->task->sender);
		break;

	case MTA_RCPT:
		if (s->currevp == NULL)
			s->currevp = TAILQ_FIRST(&s->task->envelopes);

		e = s->currevp;
		if (s->ext & MTA_EXT_DSN) {
			mta_send(s, "RCPT TO:<%s> %s%s %s%s",
			    e->dest,
			    e->dsn_notify ? "NOTIFY=" : "",
			    e->dsn_notify ? dsn_strnotify(e->dsn_notify) : "",
			    e->dsn_orcpt ? "ORCPT=" : "",
			    e->dsn_orcpt ? e->dsn_orcpt : "");
		} else
			mta_send(s, "RCPT TO:<%s>", e->dest);

		s->rcptcount++;
		break;

	case MTA_DATA:
		fseek(s->datafp, 0, SEEK_SET);
		mta_send(s, "DATA");
		break;

	case MTA_BODY:
		if (s->datafp == NULL) {
			log_trace(TRACE_MTA, "mta: %p: end-of-file", s);
			mta_enter_state(s, MTA_EOM);
			break;
		}

		if ((q = mta_queue_data(s)) == -1) {
			s->flags |= MTA_FREE;
			break;
		}
		if (q == 0) {
			mta_enter_state(s, MTA_BODY);
			break;
		}

		log_trace(TRACE_MTA, "mta: %p: >>> [...%zi bytes...]", s, q);
		break;

	case MTA_EOM:
		mta_send(s, ".");
		break;

	case MTA_LMTP_EOM:
		/* LMTP reports status of each delivery, so enable read */
		io_set_read(&s->io);
		break;

	case MTA_RSET:
		if (s->datafp) {
			fclose(s->datafp);
			s->datafp = NULL;
		}
		mta_send(s, "RSET");
		break;

	case MTA_QUIT:
		mta_send(s, "QUIT");
		break;

	default:
		fatalx("mta_enter_state: unknown state");
	}
#undef mta_enter_state
}
Exemple #16
0
void
session_respond(struct session *s, char *fmt, ...)
{
	va_list	 ap;
	int	 n, delay;
	char	 buf[SMTP_LINE_MAX];

	va_start(ap, fmt);
	n = vsnprintf(buf, sizeof buf, fmt, ap);
	va_end(ap);
	if (n == -1 || n >= SMTP_LINE_MAX)
		fatal("session_respond: line too long");
	if (n < 4)
		fatal("session_respond: response too short");

	log_trace(TRACE_SMTP, "smtp: %p: >>> %s", s, buf);

	iobuf_xfqueue(&s->s_iobuf, "session_respond", "%s\r\n", buf);

	/*
	 * Log failures.  Might be annoying in the long term, but it is a good
	 * development aid for now.
	 */
	switch (buf[0]) {
	case '5':
	case '4':
		log_info("%08x: from=<%s@%s>, relay=%s [%s], stat=LocalError (%.*s)",
		    evpid_to_msgid(s->s_msg.id),
		    s->s_msg.sender.user, s->s_msg.sender.domain,
		    s->s_hostname, ss_to_text(&s->s_ss),
		    n, buf);
		break;
	}

	/* Detect multi-line response. */
	switch (buf[3]) {
	case '-':
		return;
	case ' ':
		break;
	default:
		fatalx("session_respond: invalid response");
	}

	/*
	 * Deal with request flooding; avoid letting response rate keep up
	 * with incoming request rate.
	 */
	s->s_nresp[s->s_state]++;

	if (s->s_state == S_RCPT)
		delay = 0;
	else if ((n = s->s_nresp[s->s_state] - FAST_RESPONSES) > 0)
		delay = MIN(1 << (n - 1), MAX_RESPONSE_DELAY);
	else
		delay = 0;

	if (delay > 0) {
		struct timeval tv = { delay, 0 };

		io_pause(&s->s_io, IO_PAUSE_OUT);
		stat_increment("smtp.delays", 1);

		/* in case session_respond is called multiple times */
		evtimer_del(&s->s_ev);
		evtimer_set(&s->s_ev, session_respond_delayed, s);
		evtimer_add(&s->s_ev, &tv);
	}
}
Exemple #17
0
void
session_io(struct io *io, int evt)
{
	struct session	*s = io->arg;
	void		*ssl;
	char		*line;
	size_t		 len;

	log_trace(TRACE_IO, "smtp: %p: %s %s", s, io_strevent(evt), io_strio(io));

	switch(evt) {

	case IO_TLSREADY:
		s->s_flags |= F_SECURE;
		if (s->s_l->flags & F_SMTPS)
			stat_increment("smtp.smtps", 1);
		if (s->s_l->flags & F_STARTTLS)
			stat_increment("smtp.tls", 1);
		if (s->s_state == S_INIT) {
			io_set_write(&s->s_io);
			session_respond(s, SMTPD_BANNER, env->sc_hostname);
		}
		session_enter_state(s, S_GREETED);
		break;

	case IO_DATAIN:
	    nextline:
		line = iobuf_getline(&s->s_iobuf, &len);
		if ((line == NULL && iobuf_len(&s->s_iobuf) >= SMTP_LINE_MAX) ||
		    (line && len >= SMTP_LINE_MAX)) {
			session_respond(s, "500 5.0.0 Line too long");
			session_enter_state(s, S_QUIT);
			io_set_write(io);
			return;
		}

		if (line == NULL) {
			iobuf_normalize(&s->s_iobuf);
			return;
		}

		if (s->s_state == S_DATACONTENT && strcmp(line, ".")) {
			/* more data to come */
			session_line(s, line, len);
			goto nextline;
		}

		/* pipelining not supported */
		if (iobuf_len(&s->s_iobuf)) {
			session_respond(s, "500 5.0.0 Pipelining not supported");
			session_enter_state(s, S_QUIT);
			io_set_write(io);
			return;
		}

		session_line(s, line, len);
		iobuf_normalize(&s->s_iobuf);
		io_set_write(io);
		break;

	case IO_LOWAT:
		if (s->s_state == S_QUIT) {
			session_destroy(s, "done");
			break;
		}

		io_set_read(io);

		/* wait for the client to start tls */
		if (s->s_state == S_TLS) {
			ssl = ssl_smtp_init(s->s_l->ssl_ctx);
			io_start_tls(io, ssl);
		}
		break;

	case IO_TIMEOUT:
		session_destroy(s, "timeout");
		break;

	case IO_DISCONNECTED:
		session_destroy(s, "disconnected");
		break;

	case IO_ERROR:
		session_destroy(s, "error");
		break;

	default:
		fatal("session_io()");
	}
}
Exemple #18
0
static int
runner_process_batch(enum delivery_type type, u_int64_t evpid)
{
	struct envelope evp;
	void *batch;
	int fd;

	batch = scheduler->batch(evpid);
	switch (type) {
	case D_BOUNCE:
		while (scheduler->fetch(batch, &evpid)) {
			if (! queue_envelope_load(evpid, &evp))
				goto end;

			evp.lasttry = time(NULL);
			imsg_compose_event(env->sc_ievs[PROC_QUEUE],
			    IMSG_SMTP_ENQUEUE, PROC_SMTP, 0, -1, &evp,
			    sizeof evp);
			scheduler->schedule(evpid);
		}
		stat_increment(STATS_RUNNER);
		stat_increment(STATS_RUNNER_BOUNCES);
		break;
		
	case D_MDA:
		scheduler->fetch(batch, &evpid);
		if (! queue_envelope_load(evpid, &evp))
			goto end;
		
		evp.lasttry = time(NULL);
		fd = queue_message_fd_r(evpid_to_msgid(evpid));
		imsg_compose_event(env->sc_ievs[PROC_QUEUE],
		    IMSG_MDA_SESS_NEW, PROC_MDA, 0, fd, &evp,
		    sizeof evp);
		scheduler->schedule(evpid);

		stat_increment(STATS_RUNNER);
		stat_increment(STATS_MDA_SESSION);
		break;

	case D_MTA: {
		struct mta_batch mta_batch;

		/* FIXME */
		if (! scheduler->fetch(batch, &evpid))
			goto end;
		if (! queue_envelope_load(evpid,
				&evp))
			goto end;

		bzero(&mta_batch, sizeof mta_batch);
		mta_batch.id    = arc4random();
		mta_batch.relay = evp.agent.mta.relay;

		imsg_compose_event(env->sc_ievs[PROC_QUEUE],
		    IMSG_BATCH_CREATE, PROC_MTA, 0, -1, &mta_batch,
		    sizeof mta_batch);

		while (scheduler->fetch(batch, &evpid)) {
			if (! queue_envelope_load(evpid,
				&evp))
				goto end;
			evp.lasttry = time(NULL); /* FIXME */
			evp.batch_id = mta_batch.id;

			imsg_compose_event(env->sc_ievs[PROC_QUEUE],
			    IMSG_BATCH_APPEND, PROC_MTA, 0, -1, &evp,
			    sizeof evp);

			scheduler->schedule(evpid);
			stat_increment(STATS_RUNNER);
		}

		imsg_compose_event(env->sc_ievs[PROC_QUEUE],
		    IMSG_BATCH_CLOSE, PROC_MTA, 0, -1, &mta_batch,
		    sizeof mta_batch);

		stat_increment(STATS_MTA_SESSION);
		break;
	}
		
	default:
		fatalx("runner_process_batchqueue: unknown type");
	}

end:
	scheduler->close(batch);
	return 1;
}
Exemple #19
0
void
session_pickup(struct session *s, struct submit_status *ss)
{
	void	*ssl;

	s->s_flags &= ~F_WAITIMSG;

	if ((ss != NULL && ss->code == 421) ||
	    (s->s_dstatus & DS_TEMPFAILURE)) {
		stat_increment("smtp.tempfail", 1);
		session_respond(s, "421 Service temporarily unavailable");
		session_enter_state(s, S_QUIT);
		io_reload(&s->s_io);
		return;
	}

	switch (s->s_state) {

	case S_CONNECTED:
		session_enter_state(s, S_INIT);
		s->s_msg.session_id = s->s_id;
		s->s_msg.ss = s->s_ss;
		session_imsg(s, PROC_MFA, IMSG_MFA_CONNECT, 0, 0, -1,
			     &s->s_msg, sizeof(s->s_msg));
		break;

	case S_INIT:
		if (ss->code != 250) {
			session_destroy(s, "rejected by filter");
			return;
		}

		if (s->s_l->flags & F_SMTPS) {
			ssl = ssl_smtp_init(s->s_l->ssl_ctx);
			io_set_read(&s->s_io);
			io_start_tls(&s->s_io, ssl);
			return;
		}

		session_respond(s, SMTPD_BANNER, env->sc_hostname);
		session_enter_state(s, S_GREETED);
		break;

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

	case S_RSET:
		session_respond(s, "250 2.0.0 Reset state");
		session_enter_state(s, S_HELO);
		break;

	case S_HELO:
		if (ss->code != 250) {
			session_enter_state(s, S_GREETED);
			session_respond(s, "%d Helo rejected", ss->code);
			break;
		}

		session_respond(s, "250%c%s Hello %s [%s], pleased to meet you",
		    (s->s_flags & F_EHLO) ? '-' : ' ',
		    env->sc_hostname, s->s_msg.helo, ss_to_text(&s->s_ss));

		if (s->s_flags & F_EHLO) {
			/* unconditionnal extensions go first */
			session_respond(s, "250-8BITMIME");
			session_respond(s, "250-ENHANCEDSTATUSCODES");

			/* XXX - we also want to support reading SIZE from MAIL parameters */
			session_respond(s, "250-SIZE %zu", env->sc_maxsize);

			if (ADVERTISE_TLS(s))
				session_respond(s, "250-STARTTLS");

			if (ADVERTISE_AUTH(s))
				session_respond(s, "250-AUTH PLAIN LOGIN");
			session_respond(s, "250 HELP");
		}
		break;

	case S_MAIL_MFA:
		if (ss->code != 250) {
			session_enter_state(s, S_HELO);
			session_respond(s, "%d Sender rejected", ss->code);
			break;
		}

		session_enter_state(s, S_MAIL_QUEUE);
		s->s_msg.sender = ss->u.maddr;

		session_imsg(s, PROC_QUEUE, IMSG_QUEUE_CREATE_MESSAGE, 0, 0, -1,
		    &s->s_msg, sizeof(s->s_msg));
		break;

	case S_MAIL_QUEUE:
		session_enter_state(s, S_MAIL);
		session_respond(s, "%d 2.1.0 Sender ok", ss->code);
		break;

	case S_RCPT_MFA:
		/* recipient was not accepted */
		if (ss->code != 250) {
			/* We do not have a valid recipient, downgrade state */
			if (s->rcptcount == 0)
				session_enter_state(s, S_MAIL);
			else
				session_enter_state(s, S_RCPT);
			session_respond(s, "%d 5.0.0 Recipient rejected: %s@%s", ss->code,
			    s->s_msg.rcpt.user,
			    s->s_msg.rcpt.domain);
			break;
		}

		session_enter_state(s, S_RCPT);
		s->rcptcount++;
		s->s_msg.dest = ss->u.maddr;
		session_respond(s, "%d 2.0.0 Recipient ok", ss->code);
		break;

	case S_DATA_QUEUE:
		session_enter_state(s, S_DATACONTENT);
		session_respond(s, "354 Enter mail, end with \".\" on a line by"
		    " itself");

		fprintf(s->datafp, "Received: from %s (%s [%s])\n",
		    s->s_msg.helo, s->s_hostname, ss_to_text(&s->s_ss));
		fprintf(s->datafp, "\tby %s (OpenSMTPD) with %sSMTP id %08x",
		    env->sc_hostname, s->s_flags & F_EHLO ? "E" : "",
		    evpid_to_msgid(s->s_msg.id));

		if (s->s_flags & F_SECURE) {
			fprintf(s->datafp, "\n\t(version=%s cipher=%s bits=%d)",
			    SSL_get_cipher_version(s->s_io.ssl),
			    SSL_get_cipher_name(s->s_io.ssl),
			    SSL_get_cipher_bits(s->s_io.ssl, NULL));
		}
		if (s->rcptcount == 1)
			fprintf(s->datafp, "\n\tfor <%s@%s>; ",
			    s->s_msg.rcpt.user,
			    s->s_msg.rcpt.domain);
		else
			fprintf(s->datafp, ";\n\t");

		fprintf(s->datafp, "%s\n", time_to_text(time(NULL)));
		break;

	case S_DATACONTENT:
		if (ss->code != 250)
			s->s_dstatus |= DS_PERMFAILURE;
		session_read_data(s, ss->u.dataline);
		break;

	case S_DONE:
		session_respond(s, "250 2.0.0 %08x Message accepted for delivery",
		    evpid_to_msgid(s->s_msg.id));
		log_info("%08x: from=<%s%s%s>, size=%ld, nrcpts=%zu, proto=%s, "
		    "relay=%s [%s]",
		    evpid_to_msgid(s->s_msg.id),
		    s->s_msg.sender.user,
		    s->s_msg.sender.user[0] == '\0' ? "" : "@",
		    s->s_msg.sender.domain,
		    s->s_datalen,
		    s->rcptcount,
		    s->s_flags & F_EHLO ? "ESMTP" : "SMTP",
		    s->s_hostname,
		    ss_to_text(&s->s_ss));

		session_enter_state(s, S_HELO);
		s->s_msg.id = 0;
		bzero(&s->s_nresp, sizeof(s->s_nresp));
		break;

	default:
		fatal("session_pickup: unknown state");
	}

	io_reload(&s->s_io);
}
Exemple #20
0
Term* run_name_search(NameSearch* params)
{
    stat_increment(NameSearch);

    if (is_null(&params->name) || string_equals(&params->name, ""))
        return NULL;

    Block* block = params->block;

    if (block == NULL)
        return NULL;

    int position = 0;
    
    if (is_symbol(&params->position) && as_symbol(&params->position) == s_last)
        position = block->length();
    else
        position = as_int(&params->position);

    if (position > block->length())
        position = block->length();

    // Look for an exact match.
    for (int i = position - 1; i >= 0; i--) {

        stat_increment(NameSearchStep);

        Term* term = block->get(i);
        if (term == NULL)
            continue;

        if (equals(&term->nameValue, &params->name)
                && fits_lookup_type(term, params->lookupType)
                && (params->ordinal == -1 || term->uniqueOrdinal == params->ordinal))
            return term;

        // If this term exposes its names, then search inside the nested block.
        // (Deprecated, I think).
        if (term->nestedContents != NULL && exposes_nested_names(term)) {
            NameSearch nestedSearch;
            nestedSearch.block = term->nestedContents;
            set_value(&nestedSearch.name, &params->name);
            set_symbol(&nestedSearch.position, s_last);
            nestedSearch.ordinal = -1;
            nestedSearch.lookupType = params->lookupType;
            nestedSearch.searchParent = false;
            Term* found = run_name_search(&nestedSearch);
            if (found != NULL)
                return found;
        }

        #if 0
        // Check for an 'import' statement. If found, continue this search in the designated module.
        if (term->function == FUNCS.require && term->boolProp(s_Syntax_Import, false)) {
            Block* module = find_module_for_require_statement(term);
            if (module != NULL) {
                NameSearch moduleSearch;
                moduleSearch.block = module;
                set_value(&moduleSearch.name, &params->name);
                set_symbol(&moduleSearch.position, s_last);
                moduleSearch.ordinal = -1;
                moduleSearch.lookupType = params->lookupType;
                moduleSearch.searchParent = false;
                Term* found = run_name_search(&moduleSearch);
                if (found != NULL)
                    return found;
            }
        }
        #endif
    }

    // Did not find in the local block. Possibly continue this search upwards.
    
    if (!params->searchParent)
        return NULL;

    // Possibly take this search to the builtins block.
    if ((get_parent_block(block) == NULL) || is_module(block)) {
        NameSearch builtinsSearch;
        builtinsSearch.block = find_builtins_block(block);
        set_value(&builtinsSearch.name, &params->name);
        set_symbol(&builtinsSearch.position, s_last);
        builtinsSearch.lookupType = params->lookupType;
        builtinsSearch.ordinal = -1;
        builtinsSearch.searchParent = false;
        return run_name_search(&builtinsSearch);
    }

    // Search parent

    // The choice of position is a little weird. For regular name searches,
    // we start at the parent term's position (ie, search all the terms that
    // came before the parent).
    //
    // For a LookupFunction search, start at the bottom of the branch. It's okay
    // for a term to use a function that occurs after the term.
    
    NameSearch parentSearch;

    Term* parentTerm = block->owningTerm;
    if (parentTerm == NULL)
        return NULL;

    parentSearch.block = parentTerm->owningBlock;
    if (params->lookupType == s_LookupFunction)
        set_symbol(&parentSearch.position, s_last);
    else
        set_int(&parentSearch.position, parentTerm->index + 1);

    set_value(&parentSearch.name, &params->name);
    parentSearch.lookupType = params->lookupType;
    parentSearch.ordinal = -1;
    parentSearch.searchParent = true;
    return run_name_search(&parentSearch);
}
Exemple #21
0
static void
mta_enter_state(struct mta_session *s, int newstate)
{
	int			 oldstate;
	struct secret		 secret;
	struct mta_route	*route;
	struct mta_host		*host;
	struct sockaddr		*sa;
	int			 max_reuse;
	ssize_t			 q;

#ifdef VALGRIND
	bzero(&batch, sizeof(batch));
#endif

    again:
	oldstate = s->state;

	log_trace(TRACE_MTA, "mta: %p: %s -> %s", s,
	    mta_strstate(oldstate),
	    mta_strstate(newstate));

	s->state = newstate;

	/* don't try this at home! */
#define mta_enter_state(_s, _st) do { newstate = _st; goto again; } while(0)

	switch (s->state) {
	case MTA_INIT:
		if (s->route->auth)
			mta_enter_state(s, MTA_SECRET);
		else
			mta_enter_state(s, MTA_MX);
		break;

	case MTA_DATA:
		/*
		 * Obtain message body fd.
		 */
		imsg_compose_event(env->sc_ievs[PROC_QUEUE],
		    IMSG_QUEUE_MESSAGE_FD, s->task->msgid, 0, -1,
		    &s->id, sizeof(s->id));
		break;

	case MTA_SECRET:
		/*
		 * Lookup AUTH secret.
		 */
		bzero(&secret, sizeof(secret));
		secret.id = s->id;
		strlcpy(secret.mapname, s->route->auth, sizeof(secret.mapname));
		strlcpy(secret.host, s->route->hostname, sizeof(secret.host));
		imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_SECRET,
		    0, 0, -1, &secret, sizeof(secret));  
		break;

	case MTA_MX:
		/*
		 * Lookup MX record.
		 */
		if (s->flags & MTA_FORCE_MX) /* XXX */
			dns_query_host(s->route->hostname, s->route->port, s->id);
		else
			dns_query_mx(s->route->hostname, s->route->backupname, 0, s->id);
		break;

	case MTA_CONNECT:
		/*
		 * Connect to the MX.
		 */
	
		/* cleanup previous connection if any */
		iobuf_clear(&s->iobuf);
		io_clear(&s->io);

		if (s->flags & MTA_FORCE_ANYSSL)
			max_reuse = 2;
		else
			max_reuse = 1;

		/* pick next mx */
		while ((host = TAILQ_FIRST(&s->hosts))) {
			if (host->used == max_reuse) {
				TAILQ_REMOVE(&s->hosts, host, entry);
				free(host);
				continue;
			}
			host->used++;

			log_debug("mta: %p: connecting to %s...", s,
				ss_to_text(&host->sa));
			sa = (struct sockaddr *)&host->sa;

			if (s->route->port)
				sa_set_port(sa, s->route->port);
			else if ((s->flags & MTA_FORCE_ANYSSL) && host->used == 1)
				sa_set_port(sa, 465);
			else if (s->flags & MTA_FORCE_SMTPS)
				sa_set_port(sa, 465);
			else
				sa_set_port(sa, 25);

			iobuf_xinit(&s->iobuf, 0, 0, "mta_enter_state");
			io_init(&s->io, -1, s, mta_io, &s->iobuf);
			io_set_timeout(&s->io, 10000);
			if (io_connect(&s->io, sa, NULL) == -1) {
				log_debug("mta: %p: connection failed: %s", s,
				    strerror(errno));
				iobuf_clear(&s->iobuf);
				/*
				 * This error is most likely a "no route",
				 * so there is no need to try the same
				 * relay again.
				 */
				TAILQ_REMOVE(&s->hosts, host, entry);
				free(host);
				continue;
			}
			return;
		}
		/* tried them all? */
		mta_route_error(s->route, "150 Can not connect to MX");
		mta_enter_state(s, MTA_DONE);
		break;

	case MTA_DONE:
		/*
		 * Kill the mta session.
		 */
		log_debug("mta: %p: session done", s);
		io_clear(&s->io);
		iobuf_clear(&s->iobuf);
		if (s->task)
			fatalx("current task should have been deleted already");
		if (s->datafp)
			fclose(s->datafp);
		s->datafp = NULL;
		while ((host = TAILQ_FIRST(&s->hosts))) {
			TAILQ_REMOVE(&s->hosts, host, entry);
			free(host);
		}
		route = s->route;
		tree_xpop(&sessions, s->id);
		free(s);
		stat_decrement("mta.session", 1);
		mta_route_collect(route);
		break;

	case MTA_SMTP_BANNER:
		/* just wait for banner */
		s->is_reading = 1;
		io_set_read(&s->io);
		break;

	case MTA_SMTP_EHLO:
		s->ext = 0;
		mta_send(s, "EHLO %s", env->sc_hostname);
		break;

	case MTA_SMTP_HELO:
		s->ext = 0;
		mta_send(s, "HELO %s", env->sc_hostname);
		break;

	case MTA_SMTP_STARTTLS:
		if (s->flags & MTA_TLS) /* already started */
			mta_enter_state(s, MTA_SMTP_AUTH);
		else if ((s->ext & MTA_EXT_STARTTLS) == 0)
			/* server doesn't support starttls, do not use it */
			mta_enter_state(s, MTA_SMTP_AUTH);
		else
			mta_send(s, "STARTTLS");
		break;

	case MTA_SMTP_AUTH:
		if (s->secret && s->flags & MTA_TLS)
			mta_send(s, "AUTH PLAIN %s", s->secret);
		else if (s->secret) {
			log_debug("mta: %p: not using AUTH on non-TLS session",
			    s);
			mta_enter_state(s, MTA_CONNECT);
		} else {
			mta_enter_state(s, MTA_SMTP_READY);
		}
		break;

	case MTA_SMTP_READY:
		/* ready to send a new mail */
		if (s->ready == 0) {
			s->ready = 1;
			mta_route_ok(s->route);
		}
		if (s->msgcount >= s->route->maxmail) {
			log_debug("mta: %p: cannot send more message to %s", s,
			    mta_route_to_text(s->route));
			mta_enter_state(s, MTA_SMTP_QUIT);
		} else if ((s->task = TAILQ_FIRST(&s->route->tasks))) {
			log_debug("mta: %p: handling next task for %s", s,
			    mta_route_to_text(s->route));
			TAILQ_REMOVE(&s->route->tasks, s->task, entry);
			s->route->ntask -= 1;
			s->task->session = s;
			stat_decrement("mta.task", 1);
			stat_increment("mta.task.running", 1);
			mta_enter_state(s, MTA_DATA);
		} else {
			log_debug("mta: %p: no pending task for %s", s,
			    mta_route_to_text(s->route));
			/* XXX stay open for a while? */
			mta_enter_state(s, MTA_SMTP_QUIT);
		}
		break;

	case MTA_SMTP_MAIL:
		if (s->task->sender.user[0] && s->task->sender.domain[0])
			mta_send(s, "MAIL FROM: <%s@%s>",
			    s->task->sender.user, s->task->sender.domain);
		else
			mta_send(s, "MAIL FROM: <>");
		break;

	case MTA_SMTP_RCPT:
		if (s->currevp == NULL)
			s->currevp = TAILQ_FIRST(&s->task->envelopes);
		mta_send(s, "RCPT TO: <%s@%s>",
		    s->currevp->dest.user,
		    s->currevp->dest.domain);
		break;

	case MTA_SMTP_DATA:
		fseek(s->datafp, 0, SEEK_SET);
		mta_send(s, "DATA");
		break;

	case MTA_SMTP_BODY:
		if (s->datafp == NULL) {
			log_trace(TRACE_MTA, "mta: %p: end-of-file", s);
			mta_enter_state(s, MTA_SMTP_DONE);
			break;
		}

		if ((q = mta_queue_data(s)) == -1) {
			mta_enter_state(s, MTA_DONE);
			break;
		}

		log_trace(TRACE_MTA, "mta: %p: >>> [...%zi bytes...]", s, q);
		break;

	case MTA_SMTP_DONE:
		mta_send(s, ".");
		break;

	case MTA_SMTP_QUIT:
		mta_send(s, "QUIT");
		break;

	case MTA_SMTP_RSET:
		mta_send(s, "RSET");
		break;

	default:
		fatalx("mta_enter_state: unknown state");
	}
#undef mta_enter_state
}