/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "buffer.h"
#include "array.h"
#include "str.h"
#include "llist.h"
#include "istream.h"
#include "ostream.h"
#include "ostream-dot.h"
#include "smtp-common.h"
#include "smtp-syntax.h"
#include "smtp-params.h"
#include "smtp-client-private.h"
/*
*
*/
static const char *smtp_client_command_label
(struct smtp_client_command *cmd)
{
const unsigned char *p, *pend;
return "[plug]";
if (!cmd->has_stream)
return "[empty]";
}
for (;p < pend; p++) {
if (*p == ' ' || *p == '\r' || *p == '\n')
break;
}
}
/*
* Logging
*/
const char *format, ...)
{
i_debug("%s-client: conn %s: command %s: %s",
}
}
const char *format, ...)
{
i_error("%s-client: conn %s: command %s: %s",
}
/*
*
*/
static struct smtp_client_command *
{
return cmd;
}
struct smtp_client_command *
{
}
struct smtp_client_command *
struct smtp_client_command *after)
{
return cmd;
}
{
}
{
return;
"(%u commands pending, %u commands queued)",
}
const char *name)
{
const unsigned char *data;
return FALSE;
/* ignore CRLF, which is added at command submission */
data_len -= 2;
}
return FALSE;
}
{
return;
}
{
return;
}
}
{
bool disconnected =
bool waslocked =
return;
} else {
}
switch (state) {
if (!disconnected) {
/* it is being sent; cannot truly abort it now */
break;
}
/* fall through */
/* not yet sent */
break;
if (!disconnected) {
/* we're expecting a reply; cannot truly abort it now */
break;
}
break;
default:
break;
}
}
/* can only destroy it when it is not pending */
}
}
const struct smtp_reply *reply)
{
return;
}
}
{
}
unsigned int cmds_list_count)
{
unsigned int count, i;
return;
i_assert(cmds_list_count > 0);
/* copy the array and reference the commands to be robust against more
than one command disappearing from the list */
}
for (i = 0; i < count; i++) {
/* fail the reply */
/* drop our reference */
}
}
const struct smtp_reply *reply)
{
unsigned int count, i;
return;
i_assert(cmds_list_count > 0);
/* copy the array and reference the commands to be robust against more
than one command disappearing from the list */
}
for (i = 0; i < count; i++) {
/* fail the reply */
/* drop our reference */
}
}
{
}
{
}
unsigned int replies)
{
}
static void
{
}
}
static int
{
int ret;
if (cmd->stream_dot) {
}
/* we're sending the stream now */
switch (res) {
/* finished with the stream */
"Finished reading payload stream");
/* this concludes the dot stream with CRLF.CRLF */
return -1;
}
if (ret == 0)
return 0;
}
/* done sending payload */
return 1;
return 0;
/* the provided payload stream is broken;
fail this command separately */
"Broken payload stream");
/* we're in the middle of sending a command, so the connection
will also have to be aborted */
"Broken payload stream");
return -1;
/* normal connection failure */
return -1;
}
i_unreached();
}
{
const char *data;
int ret;
for (;;) {
/* check whether we can send anything */
return 0;
return 0;
}
/* wait until we're fully connected */
"Connection not ready [state=%s]",
return 0;
}
/* cannot pipeline; wait for reply */
return 0;
}
/* cannot pipeline with previous command;
wait for reply */
"Pipeline blocked");
return 0;
}
}
if (sent < 0) {
return -1;
}
"Blocked while sending");
return 0;
}
}
}
if (ret < 0)
return -1;
"Blocked while sending payload");
return 0;
}
/* everything sent. move command to wait list. */
}
return 0;
}
static void
{
SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST, "Disconnected");
}
static void
{
} else {
}
}
}
void
struct smtp_client_command *after)
{
/* Add commands to send queue for delayed failure reply
from ioloop */
}
"Submitted, but disconnected");
return;
}
/* pre-login commands get inserted before everything else */
return;
}
/* not in the send queue anymore; just prepend */
} else {
/* insert after indicated command */
}
/* insert at beginning of queue for priority commands */
} else {
/* just append at end of queue */
}
}
{
}
{
}
const char *cmd_str)
{
}
const char *cmd_fmt, ...)
{
}
void
{
}
void
{
int ret;
if (ret < 0) {
"i_stream_get_size(%s) failed: %s",
}
/* size must be known if stream is to be sent in chunks */
cmd->stream_size = 0;
}
}
int
const struct smtp_reply *reply)
{
bool finished;
"(%u commands pending, %u commands queued)",
if (finished) {
}
if (finished) {
}
return 1;
}
{
}
/*
* Standard commands
*/
/* NOTE: Pipelining is only enabled for certain commands:
From RFC 2920, Section 3.1:
Once the client SMTP has confirmed that support exists for the
pipelining extension, the client SMTP may then elect to transmit
groups of SMTP commands in batches without waiting for a response to
each individual command. In particular, the commands RSET, MAIL FROM,
SEND FROM, SOML FROM, SAML FROM, and RCPT TO can all appear anywhere
in a pipelined command group. The EHLO, DATA, VRFY, EXPN, TURN,
QUIT, and NOOP commands can only appear as the last command in a
group since their success or failure produces a change of state which
the client SMTP must accommodate. (NOOP is included in this group so
it can be used as a synchronization point.)
Additional commands added by other SMTP extensions may only appear as
the last command in a group unless otherwise specified by the
extensions that define the commands.
*/
/* NOOP */
struct smtp_client_command *
struct smtp_client_connection *conn,
struct smtp_client_command *after,
{
return cmd;
}
struct smtp_client_command *
struct smtp_client_connection *conn,
{
}
/* VRFY */
struct smtp_client_command *
struct smtp_client_connection *conn,
struct smtp_client_command *after,
void *context)
{
return cmd;
}
struct smtp_client_command *
struct smtp_client_connection *conn,
void *context)
{
}
/* RSET */
struct smtp_client_command *
struct smtp_client_connection *conn,
struct smtp_client_command *after,
{
return cmd;
}
struct smtp_client_command *
struct smtp_client_connection *conn,
{
}
/* MAIL FROM: */
struct smtp_client_command *
struct smtp_client_connection *conn,
struct smtp_client_command *after,
const struct smtp_address *from,
const struct smtp_params_mail *params,
{
}
return cmd;
}
struct smtp_client_command *
struct smtp_client_connection *conn,
const struct smtp_address *from,
const struct smtp_params_mail *params,
{
}
/* RCPT TO: */
struct smtp_client_command *
struct smtp_client_connection *conn,
struct smtp_client_command *after,
const struct smtp_address *to,
const struct smtp_params_rcpt *params,
{
}
return cmd;
}
struct smtp_client_command *
struct smtp_client_connection *conn,
const struct smtp_address *from,
const struct smtp_params_rcpt *params,
{
}
/* DATA or BDAT */
struct _cmd_data_context {
};
static void
struct smtp_client_command *after);
{
/* abort the main (possibly unsubmitted) data command */
}
}
{
unsigned int count, i;
/* drop all pending commands */
for (i = 0; i < count; i++) {
}
}
{
/* the main (possibly unsubmitted) data command got aborted */
}
const struct smtp_reply *reply)
{
/* fail the main (possibly unsubmitted) data command so that
the caller gets notified */
}
}
void *context)
{
unsigned int count;
/* got DATA reply; one command must be pending */
/* submit second stage: which is a command with only a stream */
/* nothing else to do, so drop the context already */
} else {
/* error */
}
}
void *context)
{
/* got BDAT reply, so there must be ones pending */
/* error */
return;
}
/* drop the command from the list */
/* send more BDAT commands if necessary */
/* all of the BDAT commands finished already */
}
}
{
/* send more BDAT commands if possible */
}
static int
{
int ret;
if (ret < 0) {
"Failed to read DATA stream: %s",
"Broken payload stream");
return -1;
}
}
return 0;
}
static void
struct smtp_client_command *after)
{
unsigned int count;
/* finished or aborted */
return;
}
/* pipeline management: determine where to submit the next command */
} else if (count > 0) {
}
if (data_size > 0) {
} else {
/* previous BDAT command not completely sent */
return;
}
return;
}
/* Keep sending more chunks until pipeline is filled to the limit */
while (data_size > max_chunk_size ||
_cmd_bdat_cb, ctx);
/* pipeline full */
/* data stream size known:
record where we left off */
}
return;
}
}
/* data stream size known:
record where we left off */
/* more to read */
}
return;
}
/* the last chunk, which may actually be empty */
/* submit final command */
/* all of the previous BDAT commands got replies already */
}
}
struct smtp_client_command *
struct smtp_client_connection *conn,
struct smtp_client_command *after,
void *context)
{
/* create the final command early for reference by the caller;
it will not be submitted for now. The DATA command is handled in
two stages (== command submissions), the BDAT command in one or more. */
/* create context in the final command's pool */
/* capture abort event with our context */
/* DATA */
/* Data stream is sent in one go in the second stage. Since the data
is sent in a '<CRLF>.<CRLF>'-terminated stream, it size is not
relevant here. */
/* Submit the initial DATA command */
} else {
/* BDAT */
/* The data stream is sent in multiple chunks. Either the size of the
data stream is known or it is not. These cases are handled a little
differently. */
/* size is known */
} else {
/* size is unknown */
}
/* Send the first BDAT command(s) */
}
return cmd_data;
}
struct smtp_client_command *
struct smtp_client_connection *conn,
{
}