/* This file is part of Mailfromd.
Copyright (C) 2005-2020 Sergey Poznyakoff
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "libmf.h"
#include "srvcfg.h"
#include "callout.h"
#include "callout-dbgmod.h"
#define SMTP_MAJOR(c) ((c)/100)
#define SMTP_PARSEHLO 0x01
#define CAPA_VRFY 0x02
struct smtp_io_data {
char *id; /* I/O id */
char *email;
char *ehlo;
char *mailfrom;
time_t timeout[SMTP_NUM_TIMEOUT];
smtp_io_callback_t callback;
void *callback_closure;
mu_stream_t stream; /* I/O stream */
mu_opool_t pool; /* Opool for keeping commands/replies */
char *command; /* Last issued command */
char *reply; /* Last received reply */
char *start; /* First line of the reply, if it was multiline */
size_t nlines; /* Number of lines in the reply */
char buf[128]; /* Input buffer */
size_t level; /* Number of bytes in buf */
int code; /* Reply code */
int esmtp_capa;
};
struct smtp_io_data *
smtp_io_create(const char *id, time_t timeout[], smtp_io_callback_t callback,
void *closure)
{
struct smtp_io_data *iop = mu_zalloc(sizeof(*iop));
if (!id)
iop->id = mu_strdup("null");
else {
size_t len;
iop->id = mu_strdup(id);
len = strlen(iop->id);
if (len > 2 && strcmp(iop->id + len - 2, ": ") == 0)
iop->id[len - 2] = 0;
}
memcpy(&iop->timeout, timeout, sizeof(iop->timeout));
iop->callback = callback;
iop->callback_closure = closure;
mu_opool_create(&iop->pool, MU_OPOOL_ENOMEMABRT);
iop->start = iop->command = iop->reply = NULL;
iop->nlines = 0;
iop->level = 0;
return iop;
}
void
smtp_io_set_timeouts(struct smtp_io_data *iop, time_t *to)
{
memcpy(&iop->timeout, to, sizeof(iop->timeout));
}
static void
replstr(char **pdst, const char *str)
{
if (*pdst)
free(*pdst);
*pdst = mu_strdup(str);
}
void
smtp_io_init(struct smtp_io_data *iop)
{
iop->start = iop->command = iop->reply = NULL;
iop->level = 0;
}
void
smtp_io_setup_callout(struct smtp_io_data *iop,
const char *email,
const char *ehlo,
const char *mailfrom)
{
replstr(&iop->email, email);
replstr(&iop->ehlo, ehlo);
if (!mailfrom || !*mailfrom)
mailfrom = "<>";
replstr(&iop->mailfrom, mailfrom);
}
void
smtp_io_free(struct smtp_io_data *iop)
{
if (iop->stream) {
mu_stream_close (iop->stream);
mu_stream_destroy(&iop->stream);
}
mu_opool_destroy(&iop->pool);
free(iop->id);
free(iop->email);
free(iop->ehlo);
free(iop->mailfrom);
free(iop);
}
static int
smtp_wait(struct smtp_io_data *iop, int flags, struct timeout_ctl *tctl)
{
return mf_stream_wait(iop->stream, flags, tctl);
}
static int
smtp_send(struct smtp_io_data *iop, const char *command)
{
size_t len = strlen(command);
struct timeout_ctl tctl;
init_timeout_ctl (&tctl, io_timeout);
iop->reply = NULL; /* Clear reply for logging purposes */
do {
size_t nb;
int rc;
UPDATE_TTW(tctl);
rc = mu_stream_write(iop->stream, command, len, &nb);
if (rc == 0) {
if (nb == 0) {
mu_error(_("%s: stream_write: wrote 0 bytes"),
iop->id);
return -1;
}
len -= nb;
command += nb;
} else if (rc == EAGAIN) {
rc = smtp_wait(iop, MU_STREAM_READY_WR, &tctl);
if (rc) {
mu_error(_("%s: smtp_wait failed: %s"),
iop->id, mu_strerror(rc));
return -1;
}
continue;
} else {
mu_error("%s: mu_stream_write: %s",
iop->id, mu_strerror (rc));
return -1;
}
} while (len > 0);
return 0;
}
static int
smtp_send2(struct smtp_io_data *iop, const char *command, const char *arg)
{
mu_opool_appendz(iop->pool, command);
if (arg)
mu_opool_appendz(iop->pool, arg);
mu_opool_appendz(iop->pool, "\r\n");
mu_opool_append_char(iop->pool, 0);
iop->command = mu_opool_finish(iop->pool, NULL);
return smtp_send(iop, iop->command);
}
static int
smtp_send3(struct smtp_io_data *iop, const char *command,
const char *arg1, const char *arg2)
{
mu_opool_appendz(iop->pool, command);
mu_opool_appendz(iop->pool, arg1);
mu_opool_appendz(iop->pool, arg2);
mu_opool_appendz(iop->pool, "\r\n");
mu_opool_append_char(iop->pool, 0);
iop->command = mu_opool_finish(iop->pool, NULL);
return smtp_send(iop, iop->command);
}
static int
smtp_recvline(struct smtp_io_data *iop, enum smtp_timeout to)
{
struct timeout_ctl tctl;
init_timeout_ctl(&tctl, iop->timeout[to]);
for (;;) {
char *p;
UPDATE_TTW(tctl);
if (iop->level == 0) {
int rc = mu_stream_read(iop->stream,
iop->buf, sizeof iop->buf,
&iop->level);
if (rc == 0) {
if (iop->level == 0) {
mu_error(_("%s: stream_read: read 0 bytes"), iop->id);
return -1;
}
} else if (rc == EAGAIN) {
rc = smtp_wait(iop,
MU_STREAM_READY_RD, &tctl);
if (rc) {
mu_error(_("%s: smtp_wait failed: %s"),
iop->id, mu_strerror(rc));
return -1;
}
continue;
} else {
mu_error("%s: mu_stream_read: %s",
iop->id, mu_strerror (rc));
return -1;
}
}
p = memchr(iop->buf, '\n', iop->level);
if (!p) {
mu_opool_append(iop->pool, iop->buf, iop->level);
iop->level = 0;
continue;
} else {
size_t len = p - iop->buf + 1;
mu_opool_append(iop->pool, iop->buf, len);
mu_opool_append_char(iop->pool, 0);
iop->reply = mu_opool_finish(iop->pool, NULL);
iop->level -= len;
memmove(iop->buf, iop->buf + len, iop->level);
break;
}
}
return 0;
}
static int
smtp_recv(struct smtp_io_data *iop, enum smtp_timeout to)
{
char *p;
iop->start = NULL;
iop->nlines = 0;
do {
int code;
int rc = smtp_recvline(iop, to);
if (rc)
return -1;
code = strtoul(iop->reply, &p, 0);
if (p - iop->reply != 3 || (*p != '-' && *p != ' ')) {
mu_error(_("%s: unexpected reply from server: %s"),
iop->id, iop->reply);
return -1;
} else if (!iop->start) {
iop->start = iop->reply;
iop->code = code;
} else if (iop->code != code) {
mu_error(_("%s: unexpected reply code from server: %d"),
iop->id, code);
return -1;
}
iop->nlines++;
if ((iop->esmtp_capa & SMTP_PARSEHLO) && iop->nlines > 1) {
if (strncmp(iop->reply + 4, "VRFY", 4) == 0 &&
(iop->reply[8] == '\r' || iop->reply[8] == '\n')) {
iop->esmtp_capa |= CAPA_VRFY;
iop->esmtp_capa &= ~SMTP_PARSEHLO;
}
}
} while (*p == '-');
iop->esmtp_capa &= ~SMTP_PARSEHLO;
mu_opool_clear(iop->pool);
return 0;
}
/* Return the first line (terminated by \n or \r\n) from STR,
or the word "nothing" if STR is NULL.
Desctructive version: modifies STR. */
static char *
first_line_of(char *str)
{
if (str) {
size_t len = strcspn(str, "\r\n");
str[len] = 0;
return str;
}
return "nothing";
}
const char *
smtp_last_sent(struct smtp_io_data *iop)
{
return first_line_of(iop->command);
}
const char *
smtp_last_received(struct smtp_io_data *iop)
{
return first_line_of(iop->start);
}
const char *
smtp_io_id(struct smtp_io_data *iop)
{
return iop->id;
}
const char *
smtp_io_email(struct smtp_io_data *iop)
{
return iop->email;
}
/* Milter-specific functions */
static mf_status
reset(struct smtp_io_data *io)
{
smtp_send2(io, "RSET", NULL);
if (smtp_recv(io, smtp_timeout_rset))
return mf_timeout;
else if (SMTP_MAJOR(io->code) != 2)
return SMTP_MAJOR(io->code) == 4 ?
mf_temp_failure : mf_failure;
return mf_success;
}
static mf_status
esmtp_vrfy(struct smtp_io_data *io)
{
smtp_send2(io, "VRFY ", io->email);
if (smtp_recv(io, smtp_timeout_rcpt))
/* FIXME: Need a separate timeout? */
return mf_timeout;
if (io->code / 10 == 25)
return mf_success;
else if (SMTP_MAJOR(io->code) == 5)
return mf_not_found;
return mf_failure;
}
static mf_status
callout_io(struct smtp_io_data *io, const char *hostname, mu_address_t addr)
{
size_t i;
size_t mailcount;
mf_status status;
if (io->callback)
io->callback(io->callback_closure, "INIT", hostname);
mu_address_get_count(addr, &mailcount);
if (smtp_recv(io, smtp_timeout_initial))
return mf_timeout;
if (io->callback)
io->callback(io->callback_closure, "GRTNG",
smtp_last_received(io));
if (SMTP_MAJOR(io->code) != 2)
return SMTP_MAJOR(io->code) == 4 ?
mf_temp_failure : mf_not_found;
smtp_send2(io, "EHLO ", io->ehlo);
if (enable_vrfy)
io->esmtp_capa |= SMTP_PARSEHLO;
if (smtp_recv(io, smtp_timeout_helo))
return mf_timeout;
if (SMTP_MAJOR(io->code) == 5) {
/* Let's try HELO, then */
smtp_send2(io, "HELO ", io->ehlo);
if (smtp_recv(io, smtp_timeout_helo))
return mf_not_found;
}
if (io->callback)
io->callback(io->callback_closure,
"HELO", smtp_last_received(io));
if (SMTP_MAJOR(io->code) != 2)
return SMTP_MAJOR(io->code) == 4 ?
mf_temp_failure : mf_not_found;
if (io->esmtp_capa & CAPA_VRFY) {
status = esmtp_vrfy(io);
if (mf_resolved(status))
return status;
}
status = mf_success;
for (i = 1; i <= mailcount; i++) {
const char *fromaddr;
mu_address_sget_email(addr, i, &fromaddr);
smtp_send3(io, "MAIL FROM:<", fromaddr, ">");
if (smtp_recv(io, smtp_timeout_mail))
return mf_timeout;
else if (SMTP_MAJOR(io->code) != 2) {
if (SMTP_MAJOR(io->code) == 4) {
if (reset(io) != mf_success) {
/* RSET must always return 250
If it does not, there's no
use talking to this host
any more */
return mf_failure;
} else
status = mf_temp_failure;
} else
status = mf_not_found;
} else {
status = mf_success;
break;
}
}
if (status != mf_success)
return status;
smtp_send3(io, "RCPT TO:<", io->email, ">");
if (smtp_recv(io, smtp_timeout_rcpt))
return mf_timeout;
else if (SMTP_MAJOR(io->code) != 2)
return SMTP_MAJOR(io->code) == 4 ?
mf_temp_failure : mf_not_found;
return mf_success;
}
static int
create_transcript_stream (mu_stream_t *pstream, struct smtp_io_data *io)
{
int rc;
mu_stream_t stream = *pstream;
mu_stream_t dstr, xstr;
char *fltargs[3] = { "INLINE-COMMENT", };
rc = mu_dbgstream_create (&dstr, MU_DIAG_DEBUG);
if (rc) {
mu_error (_("cannot create debug stream: %s; "
"transcript disabled"),
mu_strerror (rc));
return rc;
}
mu_asprintf (&fltargs[1], "%s: ", io->id);
fltargs[2] = NULL;
rc = mu_filter_create_args (&xstr, dstr,
"INLINE-COMMENT",
2, (const char**)fltargs,
MU_FILTER_ENCODE, MU_STREAM_WRITE);
free (fltargs[1]);
if (rc == 0) {
mu_stream_unref(dstr);
dstr = xstr;
mu_stream_set_buffer (dstr, mu_buffer_line, 0);
} else
mu_error (_("cannot create transcript filter"
"stream: %s"), mu_strerror (rc));
rc = mu_xscript_stream_create (&xstr, stream, dstr, NULL);
if (rc)
mu_error (_("cannot create transcript stream: %s; "
"transcript disabled"),
mu_strerror (rc));
else {
mu_stream_unref (stream);
*pstream = xstr;
}
return rc;
}
mf_status
smtp_io_open(struct smtp_io_data *io, const char *hostname)
{
int rc;
mu_stream_t stream;
struct timeout_ctl tctl;
struct mu_sockaddr_hints hints;
struct mu_sockaddr *address, *srcaddr;
memset(&hints, 0, sizeof hints);
hints.family = AF_INET;
hints.socktype = SOCK_STREAM;
hints.protocol = IPPROTO_TCP;
hints.port = 25;
rc = mu_sockaddr_from_node(&address, hostname, NULL, &hints);
if (rc) {
mu_error(_("cannot convert %s to sockaddr: %s"),
hostname, mu_strerror(rc));
return mf_failure;
}
mu_sockaddr_copy (&srcaddr, source_address);
rc = mu_tcp_stream_create_from_sa(&stream, address, source_address,
MU_STREAM_NONBLOCK);
if (rc && !(rc == EAGAIN || rc == EINPROGRESS)) {
mu_error(_("%s: cannot connect to `%s': %s"),
io->id, hostname,
mu_strerror(rc));
mu_sockaddr_free(srcaddr);
return mf_failure;
}
mu_stream_set_buffer (stream, mu_buffer_line, 0);
init_timeout_ctl(&tctl, io->timeout[smtp_timeout_connect]);
while (rc) {
if ((rc == EAGAIN || rc == EINPROGRESS) && tctl.timeout) {
rc = mf_stream_wait(stream, MU_STREAM_READY_WR,
&tctl);
if (rc == 0) {
UPDATE_TTW(tctl);
rc = mu_stream_open(stream);
continue;
}
}
mu_error("%s: stream_open(%s): %s",
io->id, hostname, mu_strerror(rc));
mu_stream_destroy(&stream);
return mf_timeout;
}
mu_debug(MF_SOURCE_CALLOUT, MU_DEBUG_TRACE9, ("stream opened"));
if (smtp_transcript)
create_transcript_stream(&stream, io);
io->stream = stream;
return mf_success;
}
void
smtp_io_close(struct smtp_io_data *io)
{
if (io->stream) {
mu_stream_close(io->stream);
mu_stream_destroy(&io->stream);
}
}
mf_status
callout_host(struct smtp_io_data *io, const char *hostname)
{
int rc;
mf_status status = mf_success;
mu_address_t addr;
const char *mailfrom;
mu_debug(MF_SOURCE_CALLOUT, MU_DEBUG_TRACE5,
("email = %s, hostname = %s",
io->email, hostname));
smtp_io_init(io);
status = smtp_io_open(io, hostname);
if (status != mf_success)
return status;
/* FIXME-MU: compensate for mailutils deficiency */
mailfrom = (io->mailfrom[0] == 0) ? "<>" : io->mailfrom;
rc = mu_address_create(&addr, mailfrom);
if (rc) {
mu_error(_("%s: cannot create address `%s': %s"),
io->id, mailfrom, mu_strerror(rc));
return mf_timeout;
}
status = callout_io(io, hostname, addr);
mu_address_destroy(&addr);
if (io->callback) {
io->callback(io->callback_closure,
"SENT", smtp_last_sent(io));
io->callback(io->callback_closure,
"RECV", smtp_last_received(io));
}
mu_debug(MF_SOURCE_CALLOUT, MU_DEBUG_TRACE0,
("%s: verification of <%s> finished with status: %s; sent \"%s\", got \"%s\"",
io->id,
smtp_io_email(io),
mf_status_str(status),
smtp_last_sent(io),
smtp_last_received(io)));
smtp_send2(io, "QUIT", NULL);
smtp_recv(io, smtp_timeout_quit);
smtp_io_close(io);
return status;
}
mf_status
callout_mx(struct smtp_io_data *iop, const char *hostname, int *pcount)
{
int i;
struct dns_reply reply;
mf_status rc, mxstat;
mxstat = dns_to_mf_status(mx_lookup(hostname, 0, &reply));
if (pcount)
*pcount = 0;
switch (mxstat) {
case mf_success:
mu_debug(MF_SOURCE_CALLOUT, MU_DEBUG_TRACE1,
("Checking MX servers for %s", iop->email));
rc = mf_not_found;
for (i = 0; i < reply.count; i++) {
rc = callout_host(iop, reply.data.str[i]);
if (mf_resolved(rc))
break;
}
if (pcount)
*pcount = reply.count;
dns_reply_free(&reply);
break;
default:
rc = mxstat;
break;
}
return rc;
}
/* Method "strict". Verifies whether EMAIL is understood either by
host CLIENT_ADDR or one of MX servers of its domain */
mf_status
callout_strict(struct smtp_io_data *iop, const char *hostname)
{
mf_status rc;
rc = callout_host(iop, hostname);
if (!mf_resolved(rc)) {
int mxcount;
mf_status mx_stat;
mx_stat = callout_mx(iop, hostname, &mxcount);
if (!(mx_stat == mf_not_found && mxcount == 0)
&& (mf_resolved(mx_stat)
|| mx_stat == mf_timeout
|| mx_stat == mf_temp_failure))
rc = mx_stat;
}
return rc;
}
mf_status
callout_standard(struct smtp_io_data *iop)
{
int rc;
char *p = strchr(iop->email, '@');
if (p == NULL) {
mu_error(_("%s: invalid address: %s"), iop->id, iop->email);
rc = mf_not_found;
} else {
int mxcount;
p++;
rc = callout_mx(iop, p, &mxcount);
if (rc != mf_success && mxcount == 0) {
mf_status host_stat;
host_stat = callout_host(iop, p);
if (mf_resolved(host_stat)
|| host_stat == mf_timeout
|| host_stat == mf_temp_failure)
rc = host_stat;
}
}
return rc;
}
static char *modnames[] = {
#define __DBGMOD_C_ARRAY
# include "callout-dbgmod.h"
#undef __DBGMOD_C_ARRAY
NULL
};
mu_debug_handle_t callout_debug_handle;
void
libcallout_init()
{
int i;
callout_debug_handle = mu_debug_register_category (modnames[0]);
for (i = 1; modnames[i]; i++)
mu_debug_register_category (modnames[i]);
}