summaryrefslogtreecommitdiffstats
path: root/smtp-bdat.c
diff options
context:
space:
mode:
Diffstat (limited to 'smtp-bdat.c')
-rw-r--r--smtp-bdat.c314
1 files changed, 314 insertions, 0 deletions
diff --git a/smtp-bdat.c b/smtp-bdat.c
new file mode 100644
index 0000000..c9c77d9
--- /dev/null
+++ b/smtp-bdat.c
@@ -0,0 +1,314 @@
+/*
+ * This file is part of libESMTP, a library for submission of RFC 2822
+ * formatted electronic mail messages using the SMTP protocol described
+ * in RFC 2821.
+ *
+ * Copyright (C) 2001,2002 Brian Stafford <brian@stafford.uklinux.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* Support for the SMTP BDAT verb (CHUNKING). The code for processing
+ headers is duplicated from the DATA verb's code. Make sure to keep
+ bug fixes in sync with that code. Message body and response
+ processing is specific to the BDAT code and bears no resemblance to
+ that of the DATA command. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef USE_CHUNKING
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <missing.h> /* declarations for missing library functions */
+
+#include "libesmtp-private.h"
+#include "message-source.h"
+#include "siobuf.h"
+#include "concatenate.h"
+#include "headers.h"
+#include "protocol.h"
+
+/* Read the message from the application using the callback.
+ Break into chunks and copy to the server. */
+void
+cmd_bdat (siobuf_t conn, smtp_session_t session)
+{
+ const char *line, *header, *chunk;
+ int c, len;
+ struct catbuf headers;
+
+ sio_set_timeout (conn, session->transfer_timeout);
+
+ /* Arrange to read the current message from the application. */
+ msg_source_set_cb (session->msg_source,
+ session->current_message->cb,
+ session->current_message->cb_arg);
+
+ /* Arrange *not* to have the message contents monitored. This is
+ purely to avoid overwhelming the application with data. */
+ sio_set_monitorcb (conn, NULL, NULL);
+
+ /* Make sure we read the message from the beginning and get
+ the header processing right. */
+ msg_rewind (session->msg_source);
+ reset_header_table (session->current_message);
+
+ /* Initialise a buffer for the message headers. */
+ cat_init (&headers, 1024);
+
+ /* Read and process header lines from the application.
+ This step in processing
+ i) removes headers provided by the application that should not be
+ present in the message, e.g. Return-Path: which is added by
+ an MTA during delivery but should not be present in a message
+ being submitted or which is in transit.
+ ii) copies certain headers verbatim, e.g. MIME headers.
+ iii) alters the content of certain headers. This will happen
+ according to library options set up by the application.
+ */
+ errno = 0;
+ while ((line = msg_gets (session->msg_source, &len, 0)) != NULL)
+ {
+ /* Header processing stops at a line containing only CRLF */
+ if (len == 2 && line[0] == '\r' && line[1] == '\n')
+ break;
+
+ /* Check for continuation lines indicated by a blank or tab
+ at the start of the next line. */
+ while ((c = msg_nextc (session->msg_source)) != -1)
+ {
+ if (c != ' ' && c != '\t')
+ break;
+ line = msg_gets (session->msg_source, &len, 1);
+ if (line == NULL)
+ goto break_2;
+ }
+
+ /* Line points to one or more lines of text forming an RFC 2822
+ header. */
+
+ /* Header processing. This function takes the "raw" header from
+ the application and returns a header which is to be written
+ to the remote MTA. If header is NULL this header has been
+ deleted by the library. If header == line it is passed
+ unchanged otherwise, header must be freed after use. */
+ header = process_header (session->current_message, line, &len);
+ if (header != NULL)
+ {
+ /* Notify byte count to the application. */
+ if (session->event_cb != NULL)
+ (*session->event_cb) (session, SMTP_EV_MESSAGEDATA,
+ session->event_cb_arg,
+ session->current_message, len);
+
+ /* During data transfer, if we are monitoring the message
+ headers, call the monitor callback directly, once per header.
+ We don't bother with monitoring the dot stuffing. Also set
+ the value of the writing parameter to 2 so that the app can
+ distinguish headers from data written in the sio_ package. */
+ if (session->monitor_cb && session->monitor_cb_headers)
+ (*session->monitor_cb) (header, len, SMTP_CB_HEADERS,
+ session->monitor_cb_arg);
+
+ /* Accumulate the header line into a buffer */
+ concatenate (&headers, header, len);
+ }
+ errno = 0;
+ }
+break_2:
+ if (errno != 0)
+ {
+ /* An error occurred during processing. The only thing that can
+ be done is to drop the connection to the server since SMTP has
+ no way to recover gracefully from client errors while transferring
+ the message. */
+ set_errno (errno);
+ session->cmd_state = session->rsp_state = -1;
+ return;
+ }
+
+ /* Now send missing headers. This completes the processing started
+ above. If the application did not supply certain headers that
+ should be present, the library will supply them here, e.g. Date:,
+ Message-Id: or To:/Cc:/Bcc: headers. In the most extreme case the
+ application might just send a CRLF followed by the message body.
+ Libesmtp will then provide all the necessary headers. */
+ while ((header = missing_header (session->current_message, &len)) != NULL)
+ {
+ /* Notify byte count to the application. */
+ if (session->event_cb != NULL)
+ (*session->event_cb) (session, SMTP_EV_MESSAGEDATA,
+ session->event_cb_arg,
+ session->current_message, len);
+
+ if (session->monitor_cb && session->monitor_cb_headers)
+ (*session->monitor_cb) (header, len, SMTP_CB_HEADERS,
+ session->monitor_cb_arg);
+ /* Accumulate the header line into a buffer */
+ concatenate (&headers, header, len);
+ }
+
+ /* Terminate headers */
+ concatenate (&headers, "\r\n", 2);
+
+ /* ``headers'' now contains the message headers. Transfer them in a
+ BDAT command and move to the next state. */
+ session->bdat_abort_pipeline = 0;
+ session->bdat_last_issued = 0;
+ session->bdat_pipelined = 1;
+ chunk = cat_buffer (&headers, &len);
+ sio_printf (conn, "BDAT %d\r\n", len);
+ sio_write (conn, chunk, len);
+ cat_free (&headers);
+ session->cmd_state = S_bdat2;
+}
+
+void
+rsp_bdat (siobuf_t conn, smtp_session_t session)
+{
+ rsp_bdat2 (conn, session);
+}
+
+void
+cmd_bdat2 (siobuf_t conn, smtp_session_t session)
+{
+ const char *chunk;
+ int len;
+
+ /* N.B. the BDAT chunk size is set by the amount of buffering
+ provided by the application callback. An application is not
+ advised to read a message line by line in the callback.
+ Instead it should buffer the message by a "reasonable" amount,
+ say, 2Kb. */
+ errno = 0;
+ chunk = msg_getb (session->msg_source, &len);
+ if (chunk != NULL)
+ {
+ /* Notify byte count to the application. */
+ if (session->event_cb != NULL)
+ (*session->event_cb) (session, SMTP_EV_MESSAGEDATA,
+ session->event_cb_arg,
+ session->current_message, len);
+ sio_printf (conn, "BDAT %d\r\n", len);
+ sio_write (conn, chunk, len);
+
+ /* BDAT commands may be pipelined. Check if a a previous BDAT has
+ failed and stop pipelining if necessary. */
+ session->cmd_state = session->bdat_abort_pipeline ? -1 : S_bdat2;
+ }
+ else
+ {
+ /* Workaround - M$ Exchange is broken wrt RFC 3030 */
+ if (session->extensions & EXT_XEXCH50)
+ sio_write (conn, "BDAT 2 LAST\r\n\r\n", -1);
+ else
+ sio_write (conn, "BDAT 0 LAST\r\n", -1);
+ sio_set_timeout (conn, session->data2_timeout);
+ session->bdat_last_issued = 1;
+ session->cmd_state = -1;
+ }
+ session->bdat_pipelined += 1;
+ if (errno != 0)
+ {
+ set_errno (errno);
+ session->cmd_state = session->rsp_state = -1;
+ }
+}
+
+void
+rsp_bdat2 (siobuf_t conn, smtp_session_t session)
+{
+ int code;
+ smtp_message_t message;
+ smtp_recipient_t recipient;
+
+ message = session->current_message;
+ code = read_smtp_response (conn, session, &message->message_status, NULL);
+
+ session->bdat_pipelined -= 1;
+ if (code == 2)
+ {
+ if (session->bdat_pipelined > 0 || !session->bdat_last_issued)
+ session->rsp_state = S_bdat2;
+ else
+ {
+ /* Mark all the recipients complete for which the MTA has accepted
+ responsibility for delivery. */
+ for (recipient = session->current_message->recipients;
+ recipient != NULL;
+ recipient = recipient->next)
+ if (!recipient->complete
+ && recipient->status.code >= 200
+ && recipient->status.code <= 299)
+ recipient->complete = 1;
+
+ /* Notify `message sent' */
+ if (session->event_cb != NULL)
+ (*session->event_cb) (session, SMTP_EV_MESSAGESENT,
+ session->event_cb_arg,
+ session->current_message);
+
+ if (next_message (session))
+ session->rsp_state = initial_transaction_state (session);
+ else
+ session->rsp_state = S_quit;
+ }
+ }
+ else
+ {
+ /* Stop further BDAT commands from being issued. */
+ session->bdat_abort_pipeline = 1;
+
+ if (session->bdat_pipelined > 0)
+ session->rsp_state = S_bdat2;
+ else
+ {
+ /* Mark all the recipients complete. This message cannot be
+ accepted for any recipients. */
+ if (code == 5)
+ for (recipient = session->current_message->recipients;
+ recipient != NULL;
+ recipient = recipient->next)
+ recipient->complete = 1;
+
+ /* Notify `message sent' */
+ if (session->event_cb != NULL)
+ (*session->event_cb) (session, SMTP_EV_MESSAGESENT,
+ session->event_cb_arg,
+ session->current_message);
+
+ /* RFC 3030 is explicit that when the BDAT command fails
+ the server state is indeterminate and that a RSET
+ command must be issued. If there are no more messages or
+ an unexpected status is received, just QUIT. */
+ if (!(code == 4 || code == 5))
+ {
+ set_error (SMTP_ERR_INVALID_RESPONSE_STATUS);
+ session->rsp_state = S_quit;
+ }
+ else if (next_message (session))
+ session->rsp_state = S_rset;
+ else
+ session->rsp_state = S_quit;
+ }
+ }
+}
+#endif