commands.c revision e20e638805c4bd54e039891a3e92760b1dfa189a
2454dfa32c93c20a8522c6ed42fe057baaac9f9aStephan Bosch/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#include "lib.h"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#include "ioloop.h"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#include "array.h"
c2ee17c9c263efdc9c0a339c4836c3d43f5cd3d9Sascha Wilde#include "str.h"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#include "istream.h"
cda217260716cfd8d8ec5e56f91708c64c140538Timo Sirainen#include "ostream.h"
d09be27cc4d98d23ba6ae78f13248945a28f9090Timo Sirainen#include "istream-dot.h"
7a7d2aa11e46195e2d92d6c337d7e78052a5ce67Timo Sirainen#include "safe-mkstemp.h"
d00ae137b6772f0b047cc98cb153f11c5246f82bTimo Sirainen#include "master-service.h"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#include "auth-master.h"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#include "mail-storage-service.h"
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen#include "index/raw/raw-storage.h"
c2ee17c9c263efdc9c0a339c4836c3d43f5cd3d9Sascha Wilde#include "lda-settings.h"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#include "mail-deliver.h"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#include "main.h"
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen#include "client.h"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#include "commands.h"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#include "lmtp-proxy.h"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#include <stdlib.h>
f325d795b52ce2053f914072b22ebca9c4f0dc7eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#define ERRSTR_TEMP_MAILBOX_FAIL "451 4.3.0 <%s> Temporary internal error"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#define ERRSTR_TEMP_USERDB_FAIL "451 4.3.0 <%s> Temporary user lookup failure"
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen#define LMTP_PROXY_DEFAULT_TIMEOUT_MSECS (1000*30)
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainenint cmd_lhlo(struct client *client, const char *args ATTR_UNUSED)
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen{
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen client_state_reset(client);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen client_send_line(client, "250-%s", client->my_domain);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client_send_line(client, "250-8BITMIME");
f7fe4f81c0887196a1f938d83ae1cdba03cfad85Josef 'Jeff' Sipek client_send_line(client, "250-ENHANCEDSTATUSCODES");
708efcd8581258763289b95cde119ca9423641d8Timo Sirainen client_send_line(client, "250 PIPELINING");
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return 0;
139cbf8d9e1cc0c65d985f525756fe47a7bfada6Timo Sirainen}
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainenint cmd_mail(struct client *client, const char *args)
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen{
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen const char *addr, *const *argv;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen unsigned int len;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen if (client->state.mail_from != NULL) {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client_send_line(client, "503 5.5.1 MAIL already given");
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen return 0;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen }
d29e35631075b8c172ce781b85fd1cdaf0a400fdTimo Sirainen
d29e35631075b8c172ce781b85fd1cdaf0a400fdTimo Sirainen argv = t_strsplit(args, " ");
0f17bb103602d0c4394e3784cb96d788530fc79eTimo Sirainen if (argv == NULL)
0f17bb103602d0c4394e3784cb96d788530fc79eTimo Sirainen addr = "";
87ca4b209c10954826b878da165d303d9b4dc5a2Timo Sirainen else {
708efcd8581258763289b95cde119ca9423641d8Timo Sirainen addr = argv[0];
708efcd8581258763289b95cde119ca9423641d8Timo Sirainen argv++;
708efcd8581258763289b95cde119ca9423641d8Timo Sirainen }
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen len = strlen(addr);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen if (strncasecmp(addr, "FROM:<", 6) != 0 || addr[len-1] != '>') {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client_send_line(client, "501 5.5.4 Invalid parameters");
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return 0;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen }
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
c8cf8a605e0ddea7cb36fe04551aeca5090e684bTimo Sirainen for (; *argv != NULL; argv++) {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen if (strcasecmp(*argv, "BODY=7BIT") == 0)
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client->mail_body_7bit = TRUE;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen else if (strcasecmp(*argv, "BODY=8BITMIME") == 0)
c8cf8a605e0ddea7cb36fe04551aeca5090e684bTimo Sirainen client->mail_body_8bitmime = TRUE;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen else {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client_send_line(client,
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen "501 5.5.4 Unsupported options");
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return 0;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen }
139cbf8d9e1cc0c65d985f525756fe47a7bfada6Timo Sirainen }
f6d63a21010540d3ddf08f2e7664ffca3ea70489Timo Sirainen
139cbf8d9e1cc0c65d985f525756fe47a7bfada6Timo Sirainen client->state.mail_from =
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen p_strndup(client->state_pool, addr + 6, len - 7);
139cbf8d9e1cc0c65d985f525756fe47a7bfada6Timo Sirainen p_array_init(&client->state.rcpt_to, client->state_pool, 64);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client_send_line(client, "250 2.1.0 OK");
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return 0;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen}
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainenstatic bool rcpt_is_duplicate(struct client *client, const char *name)
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen{
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen const struct mail_recipient *rcpt;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen array_foreach(&client->state.rcpt_to, rcpt) {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen if (strcmp(rcpt->name, name) == 0)
dc912088f84c263db1609435c2f5d7cb29bf1a33Timo Sirainen return TRUE;
a5bb2908b44c8bf5ce41160a64c67fb840a20006Timo Sirainen }
a5bb2908b44c8bf5ce41160a64c67fb840a20006Timo Sirainen return FALSE;
a5bb2908b44c8bf5ce41160a64c67fb840a20006Timo Sirainen}
a5bb2908b44c8bf5ce41160a64c67fb840a20006Timo Sirainen
a5bb2908b44c8bf5ce41160a64c67fb840a20006Timo Sirainenstatic bool
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainenclient_proxy_rcpt_parse_fields(struct lmtp_proxy_settings *set,
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen const char *const *args, const char **address)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen{
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen const char *p, *key, *value;
8d90e4f9a8f79f79c393aca23d0a897471dc2d8fTimo Sirainen bool proxying = FALSE, port_set = FALSE;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen for (; *args != NULL; args++) {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen p = strchr(*args, '=');
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen if (p == NULL) {
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen key = *args;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen value = "";
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen } else {
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen key = t_strdup_until(*args, p);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen value = p + 1;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen }
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen
7fc0f80480063a9d4cb9e8c07b50db2a5627799eTimo Sirainen if (strcmp(key, "proxy") == 0)
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen proxying = TRUE;
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen else if (strcmp(key, "host") == 0)
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen set->host = value;
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen else if (strcmp(key, "port") == 0) {
708efcd8581258763289b95cde119ca9423641d8Timo Sirainen set->port = atoi(value);
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen port_set = TRUE;
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen } else if (strcmp(key, "proxy_timeout") == 0)
7fc0f80480063a9d4cb9e8c07b50db2a5627799eTimo Sirainen set->timeout_msecs = atoi(value)*1000;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen else if (strcmp(key, "protocol") == 0) {
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen if (strcmp(value, "lmtp") == 0)
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen set->protocol = LMTP_CLIENT_PROTOCOL_LMTP;
c2ee17c9c263efdc9c0a339c4836c3d43f5cd3d9Sascha Wilde else if (strcmp(value, "smtp") == 0) {
c2ee17c9c263efdc9c0a339c4836c3d43f5cd3d9Sascha Wilde set->protocol = LMTP_CLIENT_PROTOCOL_SMTP;
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen if (!port_set)
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen set->port = 25;
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen } else {
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen i_error("proxy: Unknown protocol %s", value);
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen return FALSE;
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen }
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen } else if (strcmp(key, "user") == 0) {
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen /* changing the username */
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen *address = value;
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen } else {
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen /* just ignore it */
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen }
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen }
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen if (proxying && set->host == NULL) {
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen i_error("proxy: host not given");
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return FALSE;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen }
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen return proxying;
f7fe4f81c0887196a1f938d83ae1cdba03cfad85Josef 'Jeff' Sipek}
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainenstatic bool
d29e35631075b8c172ce781b85fd1cdaf0a400fdTimo Sirainenclient_proxy_is_ourself(const struct client *client,
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen const struct lmtp_proxy_settings *set)
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen{
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen struct ip_addr ip;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen if (set->port != client->local_port)
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen return FALSE;
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen if (net_addr2ip(set->host, &ip) < 0)
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return FALSE;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen if (!net_ip_compare(&ip, &client->local_ip))
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen return FALSE;
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen return TRUE;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen}
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainenstatic bool client_proxy_rcpt(struct client *client, const char *address)
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen{
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen struct auth_master_connection *auth_conn;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen struct lmtp_proxy_settings set;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen struct auth_user_info info;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen const char *args, *const *fields, *orig_address = address;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen pool_t pool;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen int ret;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen memset(&info, 0, sizeof(info));
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen info.service = master_service_get_name(master_service);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen info.local_ip = client->local_ip;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen info.remote_ip = client->remote_ip;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen info.local_port = client->local_port;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen info.remote_port = client->remote_port;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen pool = pool_alloconly_create("auth lookup", 1024);
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen auth_conn = mail_storage_service_multi_get_auth_conn(multi_service);
35083063d0e432d0cf78206b5929750e613ad772Timo Sirainen ret = auth_master_pass_lookup(auth_conn, address, &info,
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen pool, &fields);
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen if (ret <= 0) {
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen pool_unref(&pool);
35083063d0e432d0cf78206b5929750e613ad772Timo Sirainen if (ret < 0) {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client_send_line(client, ERRSTR_TEMP_USERDB_FAIL,
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen address);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return TRUE;
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen } else {
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen /* user not found from passdb. try userdb also. */
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen return FALSE;
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen }
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen }
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen memset(&set, 0, sizeof(set));
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen set.port = client->local_port;
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen set.protocol = LMTP_CLIENT_PROTOCOL_LMTP;
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen set.timeout_msecs = LMTP_PROXY_DEFAULT_TIMEOUT_MSECS;
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen
b1c42176a65dbe9c83a0af766e6bd8315530f3a5Timo Sirainen if (!client_proxy_rcpt_parse_fields(&set, fields, &address)) {
d927bc5618696157fc55eb1f11b5cab05400ed52Timo Sirainen /* not proxying this user */
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen pool_unref(&pool);
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen return FALSE;
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen }
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen if (strcmp(address, orig_address) == 0 &&
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client_proxy_is_ourself(client, &set)) {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen i_error("Proxying to <%s> loops to itself", address);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client_send_line(client, "554 5.4.6 Proxying loops to itself");
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen pool_unref(&pool);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return FALSE;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen }
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen if (client->proxy == NULL) {
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen client->proxy = lmtp_proxy_init(client->set->hostname,
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen client->output);
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen if (client->mail_body_8bitmime)
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen args = " BODY=8BITMIME";
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen else if (client->mail_body_7bit)
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen args = " BODY=7BIT";
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen else
4b1781e4c64be52e25b5994e5242dbe696cc7d29Timo Sirainen args = "";
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen lmtp_proxy_mail_from(client->proxy, t_strdup_printf(
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen "<%s>%s", client->state.mail_from, args));
0671e0ae0cfd8d5d671a0c2a75a070c8e2a39fecTimo Sirainen }
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen if (lmtp_proxy_add_rcpt(client->proxy, address, &set) < 0)
d927bc5618696157fc55eb1f11b5cab05400ed52Timo Sirainen client_send_line(client, ERRSTR_TEMP_REMOTE_FAILURE);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen else
d927bc5618696157fc55eb1f11b5cab05400ed52Timo Sirainen client_send_line(client, "250 2.1.5 OK");
d927bc5618696157fc55eb1f11b5cab05400ed52Timo Sirainen pool_unref(&pool);
d927bc5618696157fc55eb1f11b5cab05400ed52Timo Sirainen return TRUE;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen}
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainenint cmd_rcpt(struct client *client, const char *args)
acfda38b75d0f0e899ef692fef01593bd56ed85eTimo Sirainen{
acfda38b75d0f0e899ef692fef01593bd56ed85eTimo Sirainen struct mail_recipient rcpt;
acfda38b75d0f0e899ef692fef01593bd56ed85eTimo Sirainen struct mail_storage_service_input input;
acfda38b75d0f0e899ef692fef01593bd56ed85eTimo Sirainen const char *name, *error = NULL, *addr, *const *argv;
acfda38b75d0f0e899ef692fef01593bd56ed85eTimo Sirainen unsigned int len;
acfda38b75d0f0e899ef692fef01593bd56ed85eTimo Sirainen int ret = 0;
acfda38b75d0f0e899ef692fef01593bd56ed85eTimo Sirainen
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen if (client->state.mail_from == NULL) {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client_send_line(client, "503 5.5.1 MAIL needed first");
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen return 0;
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen }
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen argv = t_strsplit(args, " ");
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen if (argv == NULL)
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen addr = "";
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen else {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen addr = argv[0];
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen argv++;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen }
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen len = strlen(addr);
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen if (strncasecmp(addr, "TO:<", 4) != 0 || addr[len-1] != '>') {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client_send_line(client, "501 5.5.4 Invalid parameters");
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return 0;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen }
677cc0d62b41c7506246bf1b992a400a11896474Timo Sirainen
677cc0d62b41c7506246bf1b992a400a11896474Timo Sirainen memset(&rcpt, 0, sizeof(rcpt));
691f802ef6ec2105079d420ba26b21088402c6daTimo Sirainen name = t_strndup(addr + 4, len - 5);
d09be27cc4d98d23ba6ae78f13248945a28f9090Timo Sirainen
645d76bbb742c74795a0b35fe159451c4afad8d8Timo Sirainen if (rcpt_is_duplicate(client, name)) {
f534c0d42f1470fca8e4ff3493c94927bf600260Timo Sirainen client_send_line(client, "250 2.1.5 OK, ignoring duplicate");
bff606130fa332dbf837569f922028b68f45fd61Timo Sirainen return 0;
39993536eaef0a23954105e41040dcf88afd2e7eTimo Sirainen }
1453e7c587b98f93e1434aa0b147933948dbb50fTimo Sirainen
1453e7c587b98f93e1434aa0b147933948dbb50fTimo Sirainen if (*argv != NULL) {
1453e7c587b98f93e1434aa0b147933948dbb50fTimo Sirainen client_send_line(client, "501 5.5.4 Unsupported options");
1453e7c587b98f93e1434aa0b147933948dbb50fTimo Sirainen return 0;
1453e7c587b98f93e1434aa0b147933948dbb50fTimo Sirainen }
1453e7c587b98f93e1434aa0b147933948dbb50fTimo Sirainen
1453e7c587b98f93e1434aa0b147933948dbb50fTimo Sirainen if (client->try_proxying) {
1453e7c587b98f93e1434aa0b147933948dbb50fTimo Sirainen if (client_proxy_rcpt(client, name))
1453e7c587b98f93e1434aa0b147933948dbb50fTimo Sirainen return 0;
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen }
1453e7c587b98f93e1434aa0b147933948dbb50fTimo Sirainen
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen memset(&input, 0, sizeof(input));
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen input.service = "lmtp";
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen input.module = "lda";
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen input.username = name;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen input.local_ip = client->local_ip;
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen input.remote_ip = client->remote_ip;
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen ret = mail_storage_service_multi_lookup(multi_service, &input,
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen client->state_pool,
c4c9be10781e1a16b3b001dc6b0461c4640da101Timo Sirainen &rcpt.multi_user, &error);
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen if (ret < 0) {
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen i_error("User lookup failed: %s", error);
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen client_send_line(client, ERRSTR_TEMP_USERDB_FAIL, name);
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen return 0;
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen }
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen if (ret == 0) {
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen client_send_line(client,
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen "550 5.1.1 <%s> User doesn't exist", name);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return 0;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen }
1ba47b1a31e60c533631c8810400b365f785870aTimo Sirainen
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen rcpt.name = p_strdup(client->state_pool, name);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen array_append(&client->state.rcpt_to, &rcpt, 1);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen client_send_line(client, "250 2.1.5 OK");
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen return 0;
9762e4f86950549c8186c7d3d4fa4a6b533ea848Timo Sirainen}
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainen
5b82f3b2f544cf891a390083f1bcf60409be20b8Timo Sirainenint cmd_quit(struct client *client, const char *args ATTR_UNUSED)
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen{
fedb7a111ba2102edce8e55a1ba77cf907c6add9Timo Sirainen client_destroy(client, "221 2.0.0", "Logged out");
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return -1;
0f5dc4da3982053036be65190e44bf28a67b1ca2Timo Sirainen}
7fc0f80480063a9d4cb9e8c07b50db2a5627799eTimo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainenint cmd_vrfy(struct client *client, const char *args ATTR_UNUSED)
4ed1c0fedf33299264a319b2c4e0fe2465ea321bTimo Sirainen{
4ed1c0fedf33299264a319b2c4e0fe2465ea321bTimo Sirainen client_send_line(client, "252 2.3.3 Try RCPT instead");
4ed1c0fedf33299264a319b2c4e0fe2465ea321bTimo Sirainen return 0;
4ed1c0fedf33299264a319b2c4e0fe2465ea321bTimo Sirainen}
60b42c6dfdf9edcca8a96b380ef9a0adc60c2464Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenint cmd_rset(struct client *client, const char *args ATTR_UNUSED)
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen{
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen client_state_reset(client);
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen client_send_line(client, "250 2.0.0 OK");
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen return 0;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen}
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainenint cmd_noop(struct client *client, const char *args ATTR_UNUSED)
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen{
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen client_send_line(client, "250 2.0.0 OK");
d29e35631075b8c172ce781b85fd1cdaf0a400fdTimo Sirainen return 0;
d29e35631075b8c172ce781b85fd1cdaf0a400fdTimo Sirainen}
d29e35631075b8c172ce781b85fd1cdaf0a400fdTimo Sirainen
d29e35631075b8c172ce781b85fd1cdaf0a400fdTimo Sirainenstatic int
d29e35631075b8c172ce781b85fd1cdaf0a400fdTimo Sirainenclient_deliver(struct client *client, const struct mail_recipient *rcpt,
d29e35631075b8c172ce781b85fd1cdaf0a400fdTimo Sirainen struct mail *src_mail)
d29e35631075b8c172ce781b85fd1cdaf0a400fdTimo Sirainen{
e9081684ea0808ed427cfd2624656d5f22fbb225Timo Sirainen struct mail_deliver_context dctx;
e9081684ea0808ed427cfd2624656d5f22fbb225Timo Sirainen struct mail_storage *storage;
7b64db32b95286235612eebb5d37d296a49306f7Timo Sirainen void **sets;
e9081684ea0808ed427cfd2624656d5f22fbb225Timo Sirainen const char *error;
e9081684ea0808ed427cfd2624656d5f22fbb225Timo Sirainen enum mail_error mail_error;
e9081684ea0808ed427cfd2624656d5f22fbb225Timo Sirainen int ret;
e9081684ea0808ed427cfd2624656d5f22fbb225Timo Sirainen
cc2c73be39dfe988f52c0370667e3882d01c63a2Timo Sirainen i_set_failure_prefix(t_strdup_printf("lmtp(%s): ", rcpt->name));
cc2c73be39dfe988f52c0370667e3882d01c63a2Timo Sirainen if (mail_storage_service_multi_next(multi_service, rcpt->multi_user,
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen &client->state.dest_user,
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen &error) < 0) {
7b64db32b95286235612eebb5d37d296a49306f7Timo Sirainen i_error("%s", error);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen client_send_line(client, ERRSTR_TEMP_MAILBOX_FAIL, rcpt->name);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen return -1;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen }
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen sets = mail_storage_service_multi_user_get_set(rcpt->multi_user);
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen memset(&dctx, 0, sizeof(dctx));
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen dctx.pool = pool_alloconly_create("mail delivery", 1024);
512f492f410fdaefb2f58e19c2b067ef20fb4adfTimo Sirainen dctx.set = sets[1];
31327a74b86728e201fcedd0acaecf69d077bf1dTimo Sirainen dctx.src_mail = src_mail;
31327a74b86728e201fcedd0acaecf69d077bf1dTimo Sirainen dctx.src_envelope_sender = client->state.mail_from;
31327a74b86728e201fcedd0acaecf69d077bf1dTimo Sirainen dctx.dest_user = client->state.dest_user;
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen dctx.dest_addr = rcpt->name;
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen dctx.dest_mailbox_name = "INBOX";
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen dctx.save_dest_mail = array_count(&client->state.rcpt_to) > 1 &&
708efcd8581258763289b95cde119ca9423641d8Timo Sirainen client->state.first_saved_mail == NULL;
708efcd8581258763289b95cde119ca9423641d8Timo Sirainen
949fa97a4ab5c62e4db73c3973e35ae3b73a2b23Timo Sirainen if (mail_deliver(&dctx, &storage) == 0) {
949fa97a4ab5c62e4db73c3973e35ae3b73a2b23Timo Sirainen if (dctx.dest_mail != NULL) {
708efcd8581258763289b95cde119ca9423641d8Timo Sirainen i_assert(client->state.first_saved_mail == NULL);
94ce7e7700cda14a8342cb08e7285507b4b531daTimo Sirainen client->state.first_saved_mail = dctx.dest_mail;
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen }
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen client_send_line(client, "250 2.0.0 <%s> Saved", rcpt->name);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen ret = 0;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen } else if (storage == NULL) {
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen /* This shouldn't happen */
1a5573ebc32fae2fe576ec544e1781323c1db609Timo Sirainen i_error("BUG: Saving failed to unknown storage");
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen client_send_line(client, ERRSTR_TEMP_MAILBOX_FAIL,
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen rcpt->name);
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen ret = -1;
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen } else {
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen error = mail_storage_get_last_error(storage, &mail_error);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen if (mail_error == MAIL_ERROR_NOSPACE) {
c6afd726060aae56b6622c6c52aec10231c4bf1cTimo Sirainen client_send_line(client, "%s <%s> %s",
563273bdac80393af63b9520cbf4d24cc0efd028Timo Sirainen dctx.set->quota_full_tempfail ?
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen "452 4.2.2" : "552 5.2.2",
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen rcpt->name, error);
d00ae137b6772f0b047cc98cb153f11c5246f82bTimo Sirainen } else {
857c471c13ca215f4be9dd4b336b742b8d434e31Timo Sirainen client_send_line(client, "451 4.2.0 <%s> %s",
857c471c13ca215f4be9dd4b336b742b8d434e31Timo Sirainen rcpt->name, error);
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen }
4654f788834c9d7920a351306b89cf5d1c21772eTimo Sirainen ret = -1;
}
pool_unref(&dctx.pool);
return ret;
}
static bool client_deliver_next(struct client *client, struct mail *src_mail)
{
const struct mail_recipient *rcpts;
unsigned int count;
int ret;
rcpts = array_get(&client->state.rcpt_to, &count);
while (client->state.rcpt_idx < count) {
ret = client_deliver(client, &rcpts[client->state.rcpt_idx],
src_mail);
i_set_failure_prefix("lmtp: ");
client->state.rcpt_idx++;
if (ret == 0)
return TRUE;
/* failed. try the next one. */
if (client->state.dest_user != NULL)
mail_user_unref(&client->state.dest_user);
}
return FALSE;
}
static void client_rcpt_fail_all(struct client *client)
{
const struct mail_recipient *rcpt;
array_foreach(&client->state.rcpt_to, rcpt)
client_send_line(client, ERRSTR_TEMP_MAILBOX_FAIL, rcpt->name);
}
static struct istream *client_get_input(struct client *client)
{
struct istream *input;
if (client->state.mail_data_output != NULL) {
o_stream_unref(&client->state.mail_data_output);
input = i_stream_create_fd(client->state.mail_data_fd,
MAIL_READ_FULL_BLOCK_SIZE, FALSE);
i_stream_set_init_buffer_size(input, MAIL_READ_FULL_BLOCK_SIZE);
} else {
input = i_stream_create_from_data(client->state.mail_data->data,
client->state.mail_data->used);
}
return input;
}
static int client_open_raw_mail(struct client *client, struct istream *input)
{
static const char *wanted_headers[] = {
"From", "To", "Message-ID", "Subject", "Return-Path",
NULL
};
struct mailbox_list *raw_list = client->raw_mail_user->namespaces->list;
struct mailbox *box;
struct raw_mailbox *raw_box;
struct mailbox_header_lookup_ctx *headers_ctx;
enum mail_error error;
client->state.raw_box = box =
mailbox_alloc(raw_list, "Dovecot Delivery Mail", input,
MAILBOX_FLAG_NO_INDEX_FILES);
if (mailbox_open(box) < 0 ||
mailbox_sync(box, 0, 0, NULL) < 0) {
i_error("Can't open delivery mail as raw: %s",
mail_storage_get_last_error(box->storage, &error));
mailbox_close(&box);
client_rcpt_fail_all(client);
return -1;
}
raw_box = (struct raw_mailbox *)box;
raw_box->envelope_sender = client->state.mail_from;
client->state.raw_trans = mailbox_transaction_begin(box, 0);
headers_ctx = mailbox_header_lookup_init(box, wanted_headers);
client->state.raw_mail = mail_alloc(client->state.raw_trans,
0, headers_ctx);
mailbox_header_lookup_unref(&headers_ctx);
mail_set_seq(client->state.raw_mail, 1);
return 0;
}
static void
client_input_data_write_local(struct client *client, struct istream *input)
{
struct mail *src_mail;
if (client_open_raw_mail(client, input) < 0)
return;
/* save the message to the first recipient's mailbox */
src_mail = client->state.raw_mail;
if (!client_deliver_next(client, src_mail))
return;
if (client->state.first_saved_mail == NULL)
mail_user_unref(&client->state.dest_user);
else
src_mail = client->state.first_saved_mail;
/* use the first saved message to save it elsewhere too.
this might allow hard linking the files. */
while (client_deliver_next(client, src_mail))
mail_user_unref(&client->state.dest_user);
if (client->state.first_saved_mail != NULL) {
struct mail *mail = client->state.first_saved_mail;
struct mailbox_transaction_context *trans = mail->transaction;
struct mailbox *box = trans->box;
struct mail_user *user = box->storage->user;
mail_free(&mail);
mailbox_transaction_rollback(&trans);
mailbox_close(&box);
mail_user_unref(&user);
}
}
static void client_input_data_finish(struct client *client)
{
if (client->io != NULL)
io_remove(&client->io);
client->io = io_add(client->fd_in, IO_READ, client_input, client);
client_state_reset(client);
if (i_stream_have_bytes_left(client->input))
client_input_handle(client);
}
static void client_proxy_finish(void *context)
{
struct client *client = context;
lmtp_proxy_deinit(&client->proxy);
client_input_data_finish(client);
}
static bool client_input_data_write(struct client *client)
{
struct istream *input;
bool ret = TRUE;
i_stream_destroy(&client->dot_input);
input = client_get_input(client);
client_input_data_write_local(client, input);
if (client->proxy != NULL) {
lmtp_proxy_start(client->proxy, input,
client_proxy_finish, client);
ret = FALSE;
}
i_stream_unref(&input);
return ret;
}
static int client_input_add_file(struct client *client,
const unsigned char *data, size_t size)
{
string_t *path;
int fd;
if (client->state.mail_data_output != NULL) {
/* continue writing to file */
if (o_stream_send(client->state.mail_data_output,
data, size) != (ssize_t)size)
return -1;
return 0;
}
/* move everything to a temporary file. FIXME: it really shouldn't
be in /tmp.. */
path = t_str_new(256);
str_append(path, "/tmp/dovecot.lmtp.");
fd = safe_mkstemp_hostpid(path, 0600, (uid_t)-1, (gid_t)-1);
if (fd == -1)
return -1;
/* we just want the fd, unlink it */
if (unlink(str_c(path)) < 0) {
/* shouldn't happen.. */
i_error("unlink(%s) failed: %m", str_c(path));
(void)close(fd);
return -1;
}
client->state.mail_data_fd = fd;
client->state.mail_data_output = o_stream_create_fd_file(fd, 0, FALSE);
o_stream_cork(client->state.mail_data_output);
if (o_stream_send(client->state.mail_data_output,
data, size) != (ssize_t)size)
return -1;
return 0;
}
static int
client_input_add(struct client *client, const unsigned char *data, size_t size)
{
if (client->state.mail_data->used + size <=
CLIENT_MAIL_DATA_MAX_INMEMORY_SIZE) {
buffer_append(client->state.mail_data, data, size);
return 0;
} else {
return client_input_add_file(client, data, size);
}
}
static void client_input_data_handle(struct client *client)
{
const unsigned char *data;
size_t size;
ssize_t ret;
while ((ret = i_stream_read(client->dot_input)) > 0 || ret == -2) {
data = i_stream_get_data(client->dot_input, &size);
if (client_input_add(client, data, size) < 0) {
client_destroy(client, "451 4.3.0",
"Temporary internal failure");
return;
}
i_stream_skip(client->dot_input, size);
}
if (ret == 0)
return;
if (client_input_data_write(client))
client_input_data_finish(client);
}
static void client_input_data(struct client *client)
{
if (client_input_read(client) < 0)
return;
client_input_data_handle(client);
}
int cmd_data(struct client *client, const char *args ATTR_UNUSED)
{
if (client->state.mail_from == NULL) {
client_send_line(client, "503 5.5.1 MAIL needed first");
return 0;
}
if (array_count(&client->state.rcpt_to) == 0 && client->proxy == NULL) {
client_send_line(client, "554 5.5.1 No valid recipients");
return 0;
}
i_assert(client->state.mail_data == NULL);
client->state.mail_data = buffer_create_dynamic(default_pool, 1024*64);
i_assert(client->dot_input == NULL);
client->dot_input = i_stream_create_dot(client->input, TRUE);
client_send_line(client, "354 OK");
io_remove(&client->io);
if (array_count(&client->state.rcpt_to) == 0) {
lmtp_proxy_start(client->proxy, client->dot_input,
client_proxy_finish, client);
i_stream_unref(&client->dot_input);
} else {
client->io = io_add(client->fd_in, IO_READ,
client_input_data, client);
client_input_data_handle(client);
}
return -1;
}