]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-lda: Added LMTP client code.
authorTimo Sirainen <tss@iki.fi>
Mon, 31 Aug 2009 15:37:55 +0000 (11:37 -0400)
committerTimo Sirainen <tss@iki.fi>
Mon, 31 Aug 2009 15:37:55 +0000 (11:37 -0400)
--HG--
branch : HEAD

src/lib-lda/Makefile.am
src/lib-lda/lmtp-client.c [new file with mode: 0644]
src/lib-lda/lmtp-client.h [new file with mode: 0644]

index f4b7811f877ad9c797541d0472f0b6ebe2b94827..5982142a770e19e1394e010ec3856d50dc8c24b8 100644 (file)
@@ -8,15 +8,17 @@ AM_CPPFLAGS = \
        -I$(top_srcdir)/src/lib-storage
 
 liblda_a_SOURCES = \
-       lda-settings.c \
        duplicate.c \
+       lda-settings.c \
+       lmtp-client.c \
        mail-deliver.c \
        mail-send.c \
        smtp-client.c
 
 headers = \
-       lda-settings.h \
        duplicate.h \
+       lda-settings.h \
+       lmtp-client.h \
        mail-deliver.h \
        mail-send.h \
        smtp-client.h
diff --git a/src/lib-lda/lmtp-client.c b/src/lib-lda/lmtp-client.c
new file mode 100644 (file)
index 0000000..5b2dd1a
--- /dev/null
@@ -0,0 +1,426 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "network.h"
+#include "istream.h"
+#include "ostream.h"
+#include "lmtp-client.h"
+
+#include <ctype.h>
+
+#define LMTP_MAX_LINE_LEN 1024
+
+enum lmtp_input_state {
+       LMTP_INPUT_STATE_GREET,
+       LMTP_INPUT_STATE_LHLO,
+       LMTP_INPUT_STATE_MAIL_FROM,
+       LMTP_INPUT_STATE_RCPT_TO,
+       LMTP_INPUT_STATE_DATA_CONTINUE,
+       LMTP_INPUT_STATE_DATA
+};
+
+struct lmtp_rcpt {
+       const char *address;
+       lmtp_callback_t *rcpt_to_callback;
+       lmtp_callback_t *data_callback;
+       void *context;
+
+       unsigned int data_called:1;
+       unsigned int failed:1;
+};
+
+struct lmtp_client {
+       pool_t pool;
+       const char *mail_from;
+
+       const char *my_hostname;
+       const char *host;
+       struct ip_addr ip;
+       unsigned int port;
+       enum lmtp_client_protocol protocol;
+       enum lmtp_input_state input_state;
+
+       struct istream *input;
+       struct ostream *output;
+       struct io *io;
+       int fd;
+
+       ARRAY_DEFINE(recipients, struct lmtp_rcpt);
+       unsigned int rcpt_next_receive_idx;
+       unsigned int rcpt_next_data_idx;
+       unsigned int rcpt_next_send_idx;
+       struct istream *data_input;
+       unsigned char output_last;
+};
+
+static void lmtp_client_send_rcpts(struct lmtp_client *client);
+
+struct lmtp_client *
+lmtp_client_init(const char *mail_from, const char *my_hostname)
+{
+       struct lmtp_client *client;
+       pool_t pool;
+
+       i_assert(*mail_from == '<');
+
+       pool = pool_alloconly_create("lmtp client", 512);
+       client = p_new(pool, struct lmtp_client, 1);
+       client->pool = pool;
+       client->mail_from = p_strdup(pool, mail_from);
+       client->my_hostname = p_strdup(pool, my_hostname);
+       client->fd = -1;
+       p_array_init(&client->recipients, pool, 16);
+       return client;
+}
+
+static void lmtp_client_close(struct lmtp_client *client)
+{
+       if (client->io != NULL)
+               io_remove(&client->io);
+       if (client->input != NULL)
+               i_stream_unref(&client->input);
+       if (client->output != NULL)
+               o_stream_unref(&client->output);
+       if (client->fd != -1) {
+               net_disconnect(client->fd);
+               client->fd = -1;
+       }
+       if (client->data_input != NULL)
+               i_stream_unref(&client->data_input);
+}
+
+void lmtp_client_deinit(struct lmtp_client **_client)
+{
+       struct lmtp_client *client = *_client;
+
+       *_client = NULL;
+
+       lmtp_client_close(client);
+       pool_unref(&client->pool);
+}
+
+static void lmtp_client_fail(struct lmtp_client *client, const char *line)
+{
+       struct lmtp_rcpt *recipients;
+       unsigned int i, count;
+
+       recipients = array_get_modifiable(&client->recipients, &count);
+       for (i = client->rcpt_next_receive_idx; i < count; i++) {
+               recipients[i].rcpt_to_callback(FALSE, line,
+                                              recipients[i].context);
+               recipients[i].failed = TRUE;
+       }
+       for (i = client->rcpt_next_data_idx; i < count; i++) {
+               if (!recipients[i].failed) {
+                       recipients[i].data_callback(FALSE, line,
+                                                   recipients[i].context);
+               }
+       }
+       lmtp_client_close(client);
+}
+
+static bool
+lmtp_client_rcpt_next(struct lmtp_client *client, const char *line)
+{
+       struct lmtp_rcpt *recipients;
+       unsigned int i, count;
+       bool success, all_sent;
+
+       success = line[0] == '2';
+
+       recipients = array_get_modifiable(&client->recipients, &count);
+       for (i = client->rcpt_next_receive_idx; i < count; i++) {
+               recipients[i].failed = !success;
+               recipients[i].rcpt_to_callback(success, line,
+                                              recipients[i].context);
+       }
+       all_sent = i == client->rcpt_next_receive_idx;
+       client->rcpt_next_receive_idx = i;
+       return all_sent && client->data_input != NULL;
+}
+
+static int
+lmtp_client_data_next(struct lmtp_client *client, const char *line)
+{
+       struct lmtp_rcpt *rcpt;
+       bool last;
+
+       rcpt = array_idx_modifiable(&client->recipients,
+                                   client->rcpt_next_data_idx);
+       rcpt->failed = line[0] != '2';
+       last = ++client->rcpt_next_data_idx == array_count(&client->recipients);
+
+       rcpt->data_callback(!rcpt->failed, line, rcpt->context);
+       return last ? -1 : 0;
+}
+
+static void lmtp_client_send_data(struct lmtp_client *client)
+{
+       const unsigned char *data;
+       unsigned char add;
+       size_t i, size;
+       int ret;
+
+       while ((ret = i_stream_read_data(client->data_input,
+                                        &data, &size, 0)) > 0) {
+               add = '\0';
+               for (i = 0; i < size; i++) {
+                       if (data[i] == '\n') {
+                               if ((i == 0 && client->output_last != '\r') ||
+                                   (i > 0 && data[i-1] != '\r')) {
+                                       /* missing CR */
+                                       add = '\r';
+                                       break;
+                               }
+                       } else if (data[i] == '.' &&
+                                  ((i == 0 && client->output_last == '\n') ||
+                                   (i > 0 && data[i-1] == '\n'))) {
+                               /* escape the dot */
+                               add = '.';
+                               break;
+                       }
+               }
+
+               if (i > 0) {
+                       if (o_stream_send(client->output, data, i) < 0)
+                               break;
+                       client->output_last = data[i-1];
+                       i_stream_skip(client->data_input, i);
+               }
+
+               if (o_stream_get_buffer_used_size(client->output) >= 4096) {
+                       if ((ret = o_stream_flush(client->output)) < 0)
+                               break;
+                       if (ret == 0) {
+                               /* continue later */
+                               return;
+                       }
+               }
+
+               if (add != '\0') {
+                       if (o_stream_send(client->output, &add, 1) < 0)
+                               break;
+
+                       client->output_last = add;
+               }
+       }
+       if (ret == 0 || ret == -2) {
+               /* -2 can happen with tee istreams */
+               return;
+       }
+
+       if (client->output_last != '\n') {
+               /* didn't end with CRLF */
+               (void)o_stream_send(client->output, "\r\n", 2);
+       }
+       (void)o_stream_send(client->output, ".\r\n", 3);
+}
+
+static void lmtp_client_send_handshake(struct lmtp_client *client)
+{
+       o_stream_cork(client->output);
+       switch (client->protocol) {
+       case LMTP_CLIENT_PROTOCOL_LMTP:
+               o_stream_send_str(client->output,
+                       t_strdup_printf("LHLO %s\r\n", client->my_hostname));
+               break;
+       case LMTP_CLIENT_PROTOCOL_SMTP:
+               o_stream_send_str(client->output,
+                       t_strdup_printf("EHLO %s\r\n", client->my_hostname));
+               break;
+       }
+       o_stream_send_str(client->output,
+               t_strdup_printf("MAIL FROM:%s\r\n", client->mail_from));
+       o_stream_uncork(client->output);
+}
+
+static int lmtp_input_get_reply_code(const char *line, int *reply_code_r)
+{
+       if (!i_isdigit(line[0]) || !i_isdigit(line[1]) || !i_isdigit(line[2]))
+               return -1;
+
+       *reply_code_r = (line[0]-'0') * 100 +
+               (line[1]-'0') * 10 +
+               (line[2]-'0');
+
+       if (line[3] == ' ') {
+               /* final reply */
+               return 1;
+       } else if (line[3] == '-') {
+               /* multiline reply. just ignore it. */
+               return 0;
+       } else {
+               /* invalid input */
+               return -1;
+       }
+}
+
+static int lmtp_client_input_line(struct lmtp_client *client, const char *line)
+{
+       int ret, reply_code = 0;
+
+       if ((ret = lmtp_input_get_reply_code(line, &reply_code)) <= 0) {
+               if (ret == 0)
+                       return 0;
+               lmtp_client_fail(client, line);
+               return -1;
+       }
+
+       switch (client->input_state) {
+       case LMTP_INPUT_STATE_GREET:
+               if (reply_code != 220) {
+                       lmtp_client_fail(client, line);
+                       return -1;
+               }
+               lmtp_client_send_handshake(client);
+               client->input_state++;
+               break;
+       case LMTP_INPUT_STATE_LHLO:
+       case LMTP_INPUT_STATE_MAIL_FROM:
+               if (reply_code != 250) {
+                       lmtp_client_fail(client, line);
+                       return -1;
+               }
+               client->input_state++;
+               lmtp_client_send_rcpts(client);
+               break;
+       case LMTP_INPUT_STATE_RCPT_TO:
+               if (!lmtp_client_rcpt_next(client, line))
+                       break;
+               /* fall through */
+               client->input_state++;
+       case LMTP_INPUT_STATE_DATA_CONTINUE:
+               /* Start sending DATA */
+               if (strncmp(line, "354", 3) != 0) {
+                       lmtp_client_fail(client, line);
+                       return -1;
+               }
+               client->input_state++;
+               o_stream_cork(client->output);
+               lmtp_client_send_data(client);
+               o_stream_uncork(client->output);
+               break;
+       case LMTP_INPUT_STATE_DATA:
+               /* DATA replies */
+               if (lmtp_client_data_next(client, line) < 0)
+                       return -1;
+               break;
+       }
+       return 0;
+}
+
+static void lmtp_client_input(struct lmtp_client *client)
+{
+       const char *line;
+
+       while ((line = i_stream_read_next_line(client->input)) != NULL) {
+               if (lmtp_client_input_line(client, line) < 0)
+                       return;
+       }
+
+       if (client->input->stream_errno != 0) {
+               errno = client->input->stream_errno;
+               i_error("lmtp client: read() failed: %m");
+       }
+}
+
+static void lmtp_client_wait_connect(struct lmtp_client *client)
+{
+       int err;
+
+       err = net_geterror(client->fd);
+       if (err != 0) {
+               i_error("lmtp client: connect(%s, %u) failed: %s",
+                       client->host, client->port, strerror(err));
+               lmtp_client_fail(client, NULL);
+               return;
+       }
+       io_remove(&client->io);
+       client->io = io_add(client->fd, IO_READ, lmtp_client_input, client);
+       lmtp_client_input(client);
+}
+
+static int lmtp_client_output(struct lmtp_client *client)
+{
+       int ret;
+
+       o_stream_cork(client->output);
+       if ((ret = o_stream_flush(client->output)) < 0)
+               lmtp_client_fail(client, NULL);
+       else if (client->input_state == LMTP_INPUT_STATE_DATA)
+               lmtp_client_send_data(client);
+       o_stream_uncork(client->output);
+       return ret;
+}
+
+int lmtp_client_connect_tcp(struct lmtp_client *client,
+                           enum lmtp_client_protocol protocol,
+                           const char *host, unsigned int port)
+{
+       client->host = p_strdup(client->pool, host);
+       client->port = port;
+       client->protocol = protocol;
+
+       if (net_addr2ip(host, &client->ip) < 0) {
+               i_error("lmtp client: %s is not a valid IP", host);
+               return -1;
+       }
+
+       client->fd = net_connect_ip(&client->ip, port, NULL);
+       if (client->fd == -1) {
+               i_error("lmtp client: connect(%s, %u) failed: %m", host, port);
+               return -1;
+       }
+       client->input =
+               i_stream_create_fd(client->fd, LMTP_MAX_LINE_LEN, FALSE);
+       client->output = o_stream_create_fd(client->fd, (size_t)-1, FALSE);
+       o_stream_set_flush_callback(client->output, lmtp_client_output, client);
+       /* we're already sending data in ostream, so can't use IO_WRITE here */
+       client->io = io_add(client->fd, IO_READ,
+                           lmtp_client_wait_connect, client);
+       client->input_state = LMTP_INPUT_STATE_GREET;
+       return 0;
+}
+
+static void lmtp_client_send_rcpts(struct lmtp_client *client)
+{
+       const struct lmtp_rcpt *rcpt;
+       unsigned int i, count;
+
+       rcpt = array_get(&client->recipients, &count);
+       for (i = client->rcpt_next_send_idx; i < count; i++) {
+               o_stream_send_str(client->output,
+                       t_strdup_printf("RCPT TO:<%s>\r\n", rcpt[i].address));
+       }
+       client->rcpt_next_send_idx = i;
+}
+
+void lmtp_client_add_rcpt(struct lmtp_client *client, const char *address,
+                         lmtp_callback_t *rcpt_to_callback,
+                         lmtp_callback_t *data_callback, void *context)
+{
+       struct lmtp_rcpt *rcpt;
+
+       rcpt = array_append_space(&client->recipients);
+       rcpt->address = p_strdup(client->pool, address);
+       rcpt->rcpt_to_callback = rcpt_to_callback;
+       rcpt->data_callback = data_callback;
+       rcpt->context = context;
+
+       if (client->input_state == LMTP_INPUT_STATE_RCPT_TO)
+               lmtp_client_send_rcpts(client);
+}
+
+void lmtp_client_send(struct lmtp_client *client, struct istream *data_input)
+{
+       client->data_input = data_input;
+       o_stream_send_str(client->output, "DATA\r\n");
+}
+
+void lmtp_client_send_more(struct lmtp_client *client)
+{
+       if (client->input_state == LMTP_INPUT_STATE_DATA)
+               lmtp_client_send_data(client);
+}
diff --git a/src/lib-lda/lmtp-client.h b/src/lib-lda/lmtp-client.h
new file mode 100644 (file)
index 0000000..76623fd
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef LMTP_CLIENT_H
+#define LMTP_CLIENT_H
+
+/* LMTP/SMTP client code. */
+
+enum lmtp_client_protocol {
+       LMTP_CLIENT_PROTOCOL_LMTP,
+       LMTP_CLIENT_PROTOCOL_SMTP
+};
+
+/* reply contains the reply coming from remote server, or NULL
+   if it's a connection error. */
+typedef void lmtp_callback_t(bool success, const char *reply, void *context);
+
+struct lmtp_client *
+lmtp_client_init(const char *mail_from, const char *my_hostname);
+void lmtp_client_deinit(struct lmtp_client **client);
+
+int lmtp_client_connect_tcp(struct lmtp_client *client,
+                           enum lmtp_client_protocol protocol,
+                           const char *host, unsigned int port);
+
+/* Add recipient to the session. rcpt_to_callback is called once LMTP server
+   replies with RCPT TO. If RCPT TO was a succees, data_callback is called
+   when DATA replies. */
+void lmtp_client_add_rcpt(struct lmtp_client *client, const char *address,
+                         lmtp_callback_t *rcpt_to_callback,
+                         lmtp_callback_t *data_callback, void *context);
+/* Start sending input stream as DATA. */
+void lmtp_client_send(struct lmtp_client *client, struct istream *data_input);
+/* Call this function whenever input stream can potentially be read forward.
+   This is useful with non-blocking istreams and tee-istreams. */
+void lmtp_client_send_more(struct lmtp_client *client);
+
+#endif