From 987fc5cabbdae635942b0d6ee262fa1dcbc94cae Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Mon, 31 Aug 2009 11:37:55 -0400 Subject: [PATCH] lib-lda: Added LMTP client code. --HG-- branch : HEAD --- src/lib-lda/Makefile.am | 6 +- src/lib-lda/lmtp-client.c | 426 ++++++++++++++++++++++++++++++++++++++ src/lib-lda/lmtp-client.h | 35 ++++ 3 files changed, 465 insertions(+), 2 deletions(-) create mode 100644 src/lib-lda/lmtp-client.c create mode 100644 src/lib-lda/lmtp-client.h diff --git a/src/lib-lda/Makefile.am b/src/lib-lda/Makefile.am index f4b7811f87..5982142a77 100644 --- a/src/lib-lda/Makefile.am +++ b/src/lib-lda/Makefile.am @@ -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 index 0000000000..5b2dd1a16b --- /dev/null +++ b/src/lib-lda/lmtp-client.c @@ -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 + +#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 index 0000000000..76623fdb6a --- /dev/null +++ b/src/lib-lda/lmtp-client.h @@ -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 -- 2.47.3