/* This file is part of Mailfromd. Copyright (C) 2005-2018 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_send(io, "RSET"); 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]); }