File: [local] / src / usr.sbin / smtpd / smtp_client.c (download)
Revision 1.17, Wed Dec 28 21:30:18 2022 UTC (17 months ago) by jmc
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, HEAD Changes since 1.16: +2 -2 lines
spelling fixes; from paul tagliamonte
any parts of his diff not taken are noted on tech
|
/* $OpenBSD: smtp_client.c,v 1.17 2022/12/28 21:30:18 jmc Exp $ */
/*
* Copyright (c) 2018 Eric Faurot <eric@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/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <resolv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "log.h"
#include "ioev.h"
#include "smtp.h"
#define TRACE_SMTPCLT 2
#define TRACE_IO 3
enum {
STATE_INIT = 0,
STATE_BANNER,
STATE_EHLO,
STATE_HELO,
STATE_LHLO,
STATE_STARTTLS,
STATE_AUTH,
STATE_AUTH_PLAIN,
STATE_AUTH_LOGIN,
STATE_AUTH_LOGIN_USER,
STATE_AUTH_LOGIN_PASS,
STATE_READY,
STATE_MAIL,
STATE_RCPT,
STATE_DATA,
STATE_BODY,
STATE_EOM,
STATE_RSET,
STATE_QUIT,
STATE_LAST
};
#define base64_encode __b64_ntop
#define base64_decode __b64_pton
#define FLAG_TLS 0x01
#define FLAG_TLS_VERIFIED 0x02
#define SMTP_EXT_STARTTLS 0x01
#define SMTP_EXT_PIPELINING 0x02
#define SMTP_EXT_AUTH 0x04
#define SMTP_EXT_AUTH_PLAIN 0x08
#define SMTP_EXT_AUTH_LOGIN 0x10
#define SMTP_EXT_DSN 0x20
#define SMTP_EXT_SIZE 0x40
struct smtp_client {
void *tag;
struct smtp_params params;
int state;
int flags;
int ext;
size_t ext_size;
struct io *io;
char *reply;
size_t replysz;
struct smtp_mail *mail;
int rcptidx;
int rcptok;
};
void log_trace_verbose(int);
void log_trace(int, const char *, ...)
__attribute__((format (printf, 2, 3)));
static void smtp_client_io(struct io *, int, void *);
static void smtp_client_free(struct smtp_client *);
static void smtp_client_state(struct smtp_client *, int);
static void smtp_client_abort(struct smtp_client *, int, const char *);
static void smtp_client_cancel(struct smtp_client *, int, const char *);
static void smtp_client_sendcmd(struct smtp_client *, char *, ...);
static void smtp_client_sendbody(struct smtp_client *);
static int smtp_client_readline(struct smtp_client *);
static int smtp_client_replycat(struct smtp_client *, const char *);
static void smtp_client_response(struct smtp_client *, const char *);
static void smtp_client_mail_abort(struct smtp_client *);
static void smtp_client_mail_status(struct smtp_client *, const char *);
static void smtp_client_rcpt_status(struct smtp_client *, struct smtp_rcpt *, const char *);
static const char *strstate[STATE_LAST] = {
"INIT",
"BANNER",
"EHLO",
"HELO",
"LHLO",
"STARTTLS",
"AUTH",
"AUTH_PLAIN",
"AUTH_LOGIN",
"AUTH_LOGIN_USER",
"AUTH_LOGIN_PASS",
"READY",
"MAIL",
"RCPT",
"DATA",
"BODY",
"EOM",
"RSET",
"QUIT",
};
struct smtp_client *
smtp_connect(const struct smtp_params *params, void *tag)
{
struct smtp_client *proto;
proto = calloc(1, sizeof *proto);
if (proto == NULL)
return NULL;
memmove(&proto->params, params, sizeof(*params));
proto->tag = tag;
proto->io = io_new();
if (proto->io == NULL) {
free(proto);
return NULL;
}
io_set_callback(proto->io, smtp_client_io, proto);
io_set_timeout(proto->io, proto->params.timeout);
if (io_connect(proto->io, proto->params.dst, proto->params.src) == -1) {
smtp_client_abort(proto, FAIL_CONN, io_error(proto->io));
return NULL;
}
return proto;
}
void
smtp_cert_verified(struct smtp_client *proto, int verified)
{
if (verified == CERT_OK)
proto->flags |= FLAG_TLS_VERIFIED;
else if (proto->params.tls_verify) {
errno = EAUTH;
smtp_client_abort(proto, FAIL_CONN,
"Invalid server certificate");
return;
}
io_resume(proto->io, IO_IN);
if (proto->state == STATE_INIT)
smtp_client_state(proto, STATE_BANNER);
else {
/* Clear extensions before re-issueing an EHLO command. */
proto->ext = 0;
smtp_client_state(proto, STATE_EHLO);
}
}
void
smtp_set_tls(struct smtp_client *proto, void *ctx)
{
io_connect_tls(proto->io, ctx, proto->params.tls_servname);
}
void
smtp_quit(struct smtp_client *proto)
{
if (proto->state != STATE_READY)
fatalx("connection is not ready");
smtp_client_state(proto, STATE_QUIT);
}
void
smtp_sendmail(struct smtp_client *proto, struct smtp_mail *mail)
{
if (proto->state != STATE_READY)
fatalx("connection is not ready");
proto->mail = mail;
smtp_client_state(proto, STATE_MAIL);
}
static void
smtp_client_free(struct smtp_client *proto)
{
if (proto->mail)
fatalx("current task should have been deleted already");
smtp_closed(proto->tag, proto);
if (proto->io)
io_free(proto->io);
free(proto->reply);
free(proto);
}
/*
* End the session immediately.
*/
static void
smtp_client_abort(struct smtp_client *proto, int err, const char *reason)
{
smtp_failed(proto->tag, proto, err, reason);
if (proto->mail)
smtp_client_mail_abort(proto);
smtp_client_free(proto);
}
/*
* Properly close the session.
*/
static void
smtp_client_cancel(struct smtp_client *proto, int err, const char *reason)
{
if (proto->mail)
fatal("not supposed to have a mail");
smtp_failed(proto->tag, proto, err, reason);
smtp_client_state(proto, STATE_QUIT);
}
static void
smtp_client_state(struct smtp_client *proto, int newstate)
{
struct smtp_rcpt *rcpt;
char ibuf[LINE_MAX], obuf[LINE_MAX];
size_t n;
int oldstate;
if (proto->reply)
proto->reply[0] = '\0';
again:
oldstate = proto->state;
proto->state = newstate;
log_trace(TRACE_SMTPCLT, "%p: %s -> %s", proto,
strstate[oldstate],
strstate[newstate]);
/* don't try this at home! */
#define smtp_client_state(_s, _st) do { newstate = _st; goto again; } while (0)
switch (proto->state) {
case STATE_BANNER:
io_set_read(proto->io);
break;
case STATE_EHLO:
smtp_client_sendcmd(proto, "EHLO %s", proto->params.helo);
break;
case STATE_HELO:
smtp_client_sendcmd(proto, "HELO %s", proto->params.helo);
break;
case STATE_LHLO:
smtp_client_sendcmd(proto, "LHLO %s", proto->params.helo);
break;
case STATE_STARTTLS:
if (proto->params.tls_req == TLS_NO || proto->flags & FLAG_TLS)
smtp_client_state(proto, STATE_AUTH);
else if (proto->ext & SMTP_EXT_STARTTLS)
smtp_client_sendcmd(proto, "STARTTLS");
else if (proto->params.tls_req == TLS_FORCE)
smtp_client_cancel(proto, FAIL_IMPL,
"TLS not supported by remote host");
else
smtp_client_state(proto, STATE_AUTH);
break;
case STATE_AUTH:
if (!proto->params.auth_user)
smtp_client_state(proto, STATE_READY);
else if ((proto->flags & FLAG_TLS) == 0)
smtp_client_cancel(proto, FAIL_IMPL,
"Authentication requires TLS");
else if ((proto->ext & SMTP_EXT_AUTH) == 0)
smtp_client_cancel(proto, FAIL_IMPL,
"AUTH not supported by remote host");
else if (proto->ext & SMTP_EXT_AUTH_PLAIN)
smtp_client_state(proto, STATE_AUTH_PLAIN);
else if (proto->ext & SMTP_EXT_AUTH_LOGIN)
smtp_client_state(proto, STATE_AUTH_LOGIN);
else
smtp_client_cancel(proto, FAIL_IMPL,
"No supported AUTH method");
break;
case STATE_AUTH_PLAIN:
(void)strlcpy(ibuf, "-", sizeof(ibuf));
(void)strlcat(ibuf, proto->params.auth_user, sizeof(ibuf));
if (strlcat(ibuf, ":", sizeof(ibuf)) >= sizeof(ibuf)) {
errno = EMSGSIZE;
smtp_client_cancel(proto, FAIL_INTERNAL,
"credentials too large");
break;
}
n = strlcat(ibuf, proto->params.auth_pass, sizeof(ibuf));
if (n >= sizeof(ibuf)) {
errno = EMSGSIZE;
smtp_client_cancel(proto, FAIL_INTERNAL,
"credentials too large");
break;
}
*strchr(ibuf, ':') = '\0';
ibuf[0] = '\0';
if (base64_encode(ibuf, n, obuf, sizeof(obuf)) == -1) {
errno = EMSGSIZE;
smtp_client_cancel(proto, FAIL_INTERNAL,
"credentials too large");
break;
}
smtp_client_sendcmd(proto, "AUTH PLAIN %s", obuf);
explicit_bzero(ibuf, sizeof ibuf);
explicit_bzero(obuf, sizeof obuf);
break;
case STATE_AUTH_LOGIN:
smtp_client_sendcmd(proto, "AUTH LOGIN");
break;
case STATE_AUTH_LOGIN_USER:
if (base64_encode(proto->params.auth_user,
strlen(proto->params.auth_user), obuf,
sizeof(obuf)) == -1) {
errno = EMSGSIZE;
smtp_client_cancel(proto, FAIL_INTERNAL,
"credentials too large");
break;
}
smtp_client_sendcmd(proto, "%s", obuf);
explicit_bzero(obuf, sizeof obuf);
break;
case STATE_AUTH_LOGIN_PASS:
if (base64_encode(proto->params.auth_pass,
strlen(proto->params.auth_pass), obuf,
sizeof(obuf)) == -1) {
errno = EMSGSIZE;
smtp_client_cancel(proto, FAIL_INTERNAL,
"credentials too large");
break;
}
smtp_client_sendcmd(proto, "%s", obuf);
explicit_bzero(obuf, sizeof obuf);
break;
case STATE_READY:
smtp_ready(proto->tag, proto);
break;
case STATE_MAIL:
if (proto->ext & SMTP_EXT_DSN)
smtp_client_sendcmd(proto, "MAIL FROM:<%s>%s%s%s%s",
proto->mail->from,
proto->mail->dsn_ret ? " RET=" : "",
proto->mail->dsn_ret ? proto->mail->dsn_ret : "",
proto->mail->dsn_envid ? " ENVID=" : "",
proto->mail->dsn_envid ? proto->mail->dsn_envid : "");
else
smtp_client_sendcmd(proto, "MAIL FROM:<%s>",
proto->mail->from);
break;
case STATE_RCPT:
if (proto->rcptidx == proto->mail->rcptcount) {
smtp_client_state(proto, STATE_DATA);
break;
}
rcpt = &proto->mail->rcpt[proto->rcptidx];
if (proto->ext & SMTP_EXT_DSN)
smtp_client_sendcmd(proto, "RCPT TO:<%s>%s%s%s%s",
rcpt->to,
rcpt->dsn_notify ? " NOTIFY=" : "",
rcpt->dsn_notify ? rcpt->dsn_notify : "",
rcpt->dsn_orcpt ? " ORCPT=" : "",
rcpt->dsn_orcpt ? rcpt->dsn_orcpt : "");
else
smtp_client_sendcmd(proto, "RCPT TO:<%s>", rcpt->to);
break;
case STATE_DATA:
if (proto->rcptok == 0) {
smtp_client_mail_abort(proto);
smtp_client_state(proto, STATE_RSET);
}
else
smtp_client_sendcmd(proto, "DATA");
break;
case STATE_BODY:
fseek(proto->mail->fp, 0, SEEK_SET);
smtp_client_sendbody(proto);
break;
case STATE_EOM:
smtp_client_sendcmd(proto, ".");
break;
case STATE_RSET:
smtp_client_sendcmd(proto, "RSET");
break;
case STATE_QUIT:
smtp_client_sendcmd(proto, "QUIT");
break;
default:
fatalx("%s: bad state %d", __func__, proto->state);
}
#undef smtp_client_state
}
/*
* Handle a response to an SMTP command
*/
static void
smtp_client_response(struct smtp_client *proto, const char *line)
{
struct smtp_rcpt *rcpt;
int i, seen;
switch (proto->state) {
case STATE_BANNER:
if (line[0] != '2')
smtp_client_abort(proto, FAIL_RESP, line);
else if (proto->params.lmtp)
smtp_client_state(proto, STATE_LHLO);
else
smtp_client_state(proto, STATE_EHLO);
break;
case STATE_EHLO:
if (line[0] != '2') {
/*
* Either rejected or not implemented. If we want to
* use EHLO extensions, report an SMTP error.
* Otherwise, fallback to using HELO.
*/
if ((proto->params.tls_req == TLS_FORCE) ||
(proto->params.auth_user))
smtp_client_cancel(proto, FAIL_RESP, line);
else
smtp_client_state(proto, STATE_HELO);
break;
}
smtp_client_state(proto, STATE_STARTTLS);
break;
case STATE_HELO:
if (line[0] != '2')
smtp_client_cancel(proto, FAIL_RESP, line);
else
smtp_client_state(proto, STATE_READY);
break;
case STATE_LHLO:
if (line[0] != '2')
smtp_client_cancel(proto, FAIL_RESP, line);
else
smtp_client_state(proto, STATE_READY);
break;
case STATE_STARTTLS:
if (line[0] != '2') {
if ((proto->params.tls_req == TLS_FORCE) ||
(proto->params.auth_user)) {
smtp_client_cancel(proto, FAIL_RESP, line);
break;
}
smtp_client_state(proto, STATE_AUTH);
}
else
smtp_require_tls(proto->tag, proto);
break;
case STATE_AUTH_PLAIN:
if (line[0] != '2')
smtp_client_cancel(proto, FAIL_RESP, line);
else
smtp_client_state(proto, STATE_READY);
break;
case STATE_AUTH_LOGIN:
if (strncmp(line, "334 ", 4))
smtp_client_cancel(proto, FAIL_RESP, line);
else
smtp_client_state(proto, STATE_AUTH_LOGIN_USER);
break;
case STATE_AUTH_LOGIN_USER:
if (strncmp(line, "334 ", 4))
smtp_client_cancel(proto, FAIL_RESP, line);
else
smtp_client_state(proto, STATE_AUTH_LOGIN_PASS);
break;
case STATE_AUTH_LOGIN_PASS:
if (line[0] != '2')
smtp_client_cancel(proto, FAIL_RESP, line);
else
smtp_client_state(proto, STATE_READY);
break;
case STATE_MAIL:
if (line[0] != '2') {
smtp_client_mail_status(proto, line);
smtp_client_state(proto, STATE_RSET);
}
else
smtp_client_state(proto, STATE_RCPT);
break;
case STATE_RCPT:
rcpt = &proto->mail->rcpt[proto->rcptidx++];
if (line[0] != '2')
smtp_client_rcpt_status(proto, rcpt, line);
else {
proto->rcptok++;
smtp_client_state(proto, STATE_RCPT);
}
break;
case STATE_DATA:
if (line[0] != '2' && line[0] != '3') {
smtp_client_mail_status(proto, line);
smtp_client_state(proto, STATE_RSET);
}
else
smtp_client_state(proto, STATE_BODY);
break;
case STATE_EOM:
if (proto->params.lmtp) {
/*
* LMTP reports a status of each accepted RCPT.
* Report status for the first pending RCPT and read
* more lines if another rcpt needs a status.
*/
for (i = 0, seen = 0; i < proto->mail->rcptcount; i++) {
rcpt = &proto->mail->rcpt[i];
if (rcpt->done)
continue;
if (seen) {
io_set_read(proto->io);
return;
}
smtp_client_rcpt_status(proto,
&proto->mail->rcpt[i], line);
seen = 1;
}
}
smtp_client_mail_status(proto, line);
smtp_client_state(proto, STATE_READY);
break;
case STATE_RSET:
if (line[0] != '2')
smtp_client_cancel(proto, FAIL_RESP, line);
else
smtp_client_state(proto, STATE_READY);
break;
case STATE_QUIT:
smtp_client_free(proto);
break;
default:
fatalx("%s: bad state %d", __func__, proto->state);
}
}
static void
smtp_client_io(struct io *io, int evt, void *arg)
{
struct smtp_client *proto = arg;
log_trace(TRACE_IO, "%p: %s %s", proto, io_strevent(evt), io_strio(io));
switch (evt) {
case IO_CONNECTED:
if (proto->params.tls_req == TLS_SMTPS) {
io_set_write(io);
smtp_require_tls(proto->tag, proto);
}
else
smtp_client_state(proto, STATE_BANNER);
break;
case IO_TLSREADY:
proto->flags |= FLAG_TLS;
if (proto->state == STATE_INIT)
smtp_client_state(proto, STATE_BANNER);
else {
/* Clear extensions before re-issueing an EHLO command. */
proto->ext = 0;
smtp_client_state(proto, STATE_EHLO);
}
break;
case IO_DATAIN:
while (smtp_client_readline(proto))
;
break;
case IO_LOWAT:
if (proto->state == STATE_BODY)
smtp_client_sendbody(proto);
else
io_set_read(io);
break;
case IO_TIMEOUT:
errno = ETIMEDOUT;
smtp_client_abort(proto, FAIL_CONN, "Connection timeout");
break;
case IO_ERROR:
smtp_client_abort(proto, FAIL_CONN, io_error(io));
break;
case IO_DISCONNECTED:
smtp_client_abort(proto, FAIL_CONN, io_error(io));
break;
default:
fatalx("%s: bad event %d", __func__, evt);
}
}
/*
* return 1 if a new line is expected.
*/
static int
smtp_client_readline(struct smtp_client *proto)
{
const char *e;
size_t len;
char *line, *msg, *p;
int cont;
line = io_getline(proto->io, &len);
if (line == NULL) {
if (io_datalen(proto->io) >= proto->params.linemax)
smtp_client_abort(proto, FAIL_PROTO, "Line too long");
return 0;
}
/* Strip trailing '\r' */
if (len && line[len - 1] == '\r')
line[--len] = '\0';
log_trace(TRACE_SMTPCLT, "%p: <<< %s", proto, line);
/* Validate SMTP */
if (len > 3) {
msg = line + 4;
cont = (line[3] == '-');
} else if (len == 3) {
msg = line + 3;
cont = 0;
} else {
smtp_client_abort(proto, FAIL_PROTO, "Response too short");
return 0;
}
/* Validate reply code. */
if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) ||
!isdigit((unsigned char)line[2])) {
smtp_client_abort(proto, FAIL_PROTO, "Invalid reply code");
return 0;
}
/* Validate reply message. */
for (p = msg; *p; p++)
if (!isprint((unsigned char)*p)) {
smtp_client_abort(proto, FAIL_PROTO,
"Non-printable characters in response");
return 0;
}
/* Read extensions. */
if (proto->state == STATE_EHLO) {
if (strcmp(msg, "STARTTLS") == 0)
proto->ext |= SMTP_EXT_STARTTLS;
else if (strncmp(msg, "AUTH ", 5) == 0) {
proto->ext |= SMTP_EXT_AUTH;
if ((p = strstr(msg, " PLAIN")) &&
(*(p+6) == '\0' || *(p+6) == ' '))
proto->ext |= SMTP_EXT_AUTH_PLAIN;
if ((p = strstr(msg, " LOGIN")) &&
(*(p+6) == '\0' || *(p+6) == ' '))
proto->ext |= SMTP_EXT_AUTH_LOGIN;
}
else if (strcmp(msg, "PIPELINING") == 0)
proto->ext |= SMTP_EXT_PIPELINING;
else if (strcmp(msg, "DSN") == 0)
proto->ext |= SMTP_EXT_DSN;
else if (strncmp(msg, "SIZE ", 5) == 0) {
proto->ext_size = strtonum(msg + 5, 0, SIZE_T_MAX, &e);
if (e == NULL)
proto->ext |= SMTP_EXT_SIZE;
}
}
if (smtp_client_replycat(proto, line) == -1) {
smtp_client_abort(proto, FAIL_INTERNAL, NULL);
return 0;
}
if (cont)
return 1;
if (io_datalen(proto->io)) {
/*
* There should be no pending data after a response is read,
* except for the multiple status lines after a LMTP message.
* It can also happen with pipelineing, but we don't do that
* for now.
*/
if (!(proto->params.lmtp && proto->state == STATE_EOM)) {
smtp_client_abort(proto, FAIL_PROTO, "Trailing data");
return 0;
}
}
io_set_write(proto->io);
smtp_client_response(proto, proto->reply);
return 0;
}
/*
* Concatenate the given response line.
*/
static int
smtp_client_replycat(struct smtp_client *proto, const char *line)
{
size_t len;
char *tmp;
int first;
if (proto->reply && proto->reply[0]) {
/*
* If the line is the continuation of an multi-line response,
* skip the status and ESC parts. First, skip the status, then
* skip the separator amd ESC if found.
*/
first = 0;
line += 3;
if (line[0]) {
line += 1;
if (isdigit((unsigned char)line[0]) && line[1] == '.' &&
isdigit((unsigned char)line[2]) && line[3] == '.' &&
isdigit((unsigned char)line[4]) &&
isspace((unsigned char)line[5]))
line += 5;
}
} else
first = 1;
if (proto->reply) {
len = strlcat(proto->reply, line, proto->replysz);
if (len < proto->replysz)
return 0;
}
else
len = strlen(line);
if (len > proto->params.ibufmax) {
errno = EMSGSIZE;
return -1;
}
/* Allocate by multiples of 2^8 */
len += (len % 256) ? (256 - (len % 256)) : 0;
tmp = realloc(proto->reply, len);
if (tmp == NULL)
return -1;
if (proto->reply == NULL)
tmp[0] = '\0';
proto->reply = tmp;
proto->replysz = len;
(void)strlcat(proto->reply, line, proto->replysz);
/* Replace the separator with a space for the first line. */
if (first && proto->reply[3])
proto->reply[3] = ' ';
return 0;
}
static void
smtp_client_sendbody(struct smtp_client *proto)
{
ssize_t len;
size_t sz = 0, total, w;
char *ln = NULL;
int n;
total = io_queued(proto->io);
w = 0;
while (total < proto->params.obufmax) {
if ((len = getline(&ln, &sz, proto->mail->fp)) == -1)
break;
if (ln[len - 1] == '\n')
ln[len - 1] = '\0';
n = io_printf(proto->io, "%s%s\r\n", *ln == '.'?".":"", ln);
if (n == -1) {
free(ln);
smtp_client_abort(proto, FAIL_INTERNAL, NULL);
return;
}
total += n;
w += n;
}
free(ln);
if (ferror(proto->mail->fp)) {
smtp_client_abort(proto, FAIL_INTERNAL, "Cannot read message");
return;
}
log_trace(TRACE_SMTPCLT, "%p: >>> [...%zd bytes...]", proto, w);
if (feof(proto->mail->fp))
smtp_client_state(proto, STATE_EOM);
}
static void
smtp_client_sendcmd(struct smtp_client *proto, char *fmt, ...)
{
va_list ap;
char *p;
int len;
va_start(ap, fmt);
len = vasprintf(&p, fmt, ap);
va_end(ap);
if (len == -1) {
smtp_client_abort(proto, FAIL_INTERNAL, NULL);
return;
}
log_trace(TRACE_SMTPCLT, "mta: %p: >>> %s", proto, p);
len = io_printf(proto->io, "%s\r\n", p);
free(p);
if (len == -1)
smtp_client_abort(proto, FAIL_INTERNAL, NULL);
}
static void
smtp_client_mail_status(struct smtp_client *proto, const char *status)
{
int i;
for (i = 0; i < proto->mail->rcptcount; i++)
smtp_client_rcpt_status(proto, &proto->mail->rcpt[i], status);
smtp_done(proto->tag, proto, proto->mail);
proto->mail = NULL;
}
static void
smtp_client_mail_abort(struct smtp_client *proto)
{
smtp_done(proto->tag, proto, proto->mail);
proto->mail = NULL;
}
static void
smtp_client_rcpt_status(struct smtp_client *proto, struct smtp_rcpt *rcpt, const char *line)
{
struct smtp_status status;
if (rcpt->done)
return;
rcpt->done = 1;
status.rcpt = rcpt;
status.cmd = strstate[proto->state];
status.status = line;
smtp_status(proto->tag, proto, &status);
}