client.c revision ee32cb2843bf6be232e418591100f0297c7ead83
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
c25356d5978632df6203437e1953bcb29e0c736fTimo Sirainen
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen#include "common.h"
d23c747de9d33966483fbdd41f08ad7766da7c5cTimo Sirainen#include "ioloop.h"
d23c747de9d33966483fbdd41f08ad7766da7c5cTimo Sirainen#include "llist.h"
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen#include "str.h"
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen#include "network.h"
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include "istream.h"
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include "ostream.h"
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include "var-expand.h"
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include "commands.h"
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include "mail-namespace.h"
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen#include <stdlib.h>
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen#include <unistd.h>
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen
296dca49e4fe6046e0328c67ef1cf4b8077dec9cTimo Sirainenextern struct mail_storage_callbacks mail_storage_callbacks;
7fd51f7b0b4d990ec3cfef4e60ee685bf9fb32deTimo Sirainen
7fd51f7b0b4d990ec3cfef4e60ee685bf9fb32deTimo Sirainenstatic struct client *my_client; /* we don't need more than one currently */
7fd51f7b0b4d990ec3cfef4e60ee685bf9fb32deTimo Sirainen
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainenstatic bool client_handle_input(struct client *client);
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainen
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainenstatic void client_idle_timeout(struct client *client)
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainen{
296dca49e4fe6046e0328c67ef1cf4b8077dec9cTimo Sirainen if (client->output_lock == NULL)
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainen client_send_line(client, "* BYE Disconnected for inactivity.");
4ac2d38239cea8090154e17faefd77de5a71d882Timo Sirainen client_destroy(client, "Disconnected for inactivity");
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainen}
4ac2d38239cea8090154e17faefd77de5a71d882Timo Sirainen
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainenstruct client *client_create(int fd_in, int fd_out,
32e1554df9abca74fef0af2ba2e4c37e90a06cd0Timo Sirainen struct mail_namespace *namespaces)
eb64c3586d854cddd693f0b811d897399076a441Timo Sirainen{
32e1554df9abca74fef0af2ba2e4c37e90a06cd0Timo Sirainen struct client *client;
a988c3fd9251806e38931a011aaa4006dd081cbdTimo Sirainen
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen /* always use nonblocking I/O */
b337d3b6871b878d6467d7d8ed600433af5da5a1Timo Sirainen net_set_nonblock(fd_in, TRUE);
b337d3b6871b878d6467d7d8ed600433af5da5a1Timo Sirainen net_set_nonblock(fd_out, TRUE);
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen
60b42c6dfdf9edcca8a96b380ef9a0adc60c2464Timo Sirainen client = i_new(struct client, 1);
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen client->fd_in = fd_in;
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen client->fd_out = fd_out;
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen client->input = i_stream_create_fd(fd_in, imap_max_line_length, FALSE);
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen client->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
16db188cfddce117500a161302f17ae691b4500eTimo Sirainen
296dca49e4fe6046e0328c67ef1cf4b8077dec9cTimo Sirainen o_stream_set_flush_callback(client->output, client_output, client);
296dca49e4fe6046e0328c67ef1cf4b8077dec9cTimo Sirainen
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen client->io = io_add(fd_in, IO_READ, client_input, client);
2b95b7a9f4f06e7640ef431d9e6efc2423cacf1aTimo Sirainen client->last_input = ioloop_time;
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS,
6145bd3b17b9135b77b0b42409a0cc3fa0d1b946Timo Sirainen client_idle_timeout, client);
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen
3e0bae44b65f5c46989fcef3d1e07203f496327eTimo Sirainen client->command_pool = pool_alloconly_create("client command", 1024*12);
296dca49e4fe6046e0328c67ef1cf4b8077dec9cTimo Sirainen client->namespaces = namespaces;
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen while (namespaces != NULL) {
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen mail_storage_set_callbacks(namespaces->storage,
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen &mail_storage_callbacks, client);
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen namespaces = namespaces->next;
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen }
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen i_assert(my_client == NULL);
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen my_client = client;
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen if (hook_client_created != NULL)
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen hook_client_created(&client);
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen return client;
af3f857bb3166ed99595e11a9d18e5b5cc670e1aTimo Sirainen}
67c47dbb3fde79218320fd38a45c33f61bbf3012Timo Sirainen
c58906589cafc32df4c04ffbef933baadd3f2276Timo Sirainenvoid client_command_cancel(struct client_command_context *cmd)
47ede56f4e6eebfe631a1f0febf74d7adcdbcd00Timo Sirainen{
47ede56f4e6eebfe631a1f0febf74d7adcdbcd00Timo Sirainen bool cmd_ret;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen
c396c5cdd510d09aa35875ccfd643c5c21ed1f89Timo Sirainen cmd->cancel = TRUE;
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen cmd_ret = cmd->func == NULL ? TRUE : cmd->func(cmd);
6145bd3b17b9135b77b0b42409a0cc3fa0d1b946Timo Sirainen if (!cmd_ret && cmd->state != CLIENT_COMMAND_STATE_DONE) {
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen if (cmd->client->output->closed)
0dffa25d211be541ee3c953b23566a1a990789dfTimo Sirainen i_panic("command didn't cancel itself: %s", cmd->name);
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen } else {
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen client_command_free(cmd);
a64adf62fa33f2463a86f990217b0c9078531a40Timo Sirainen }
d9250ee7e2815bb2116134b58f7c860578148d6cTimo Sirainen}
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen
092018b35bb1dc5bd61848a38189fe6ac8f791ddTimo Sirainenstatic const char *client_stats(struct client *client)
7327394e30c1020b9a2a49c72a7e3d0f7803e680Timo Sirainen{
7327394e30c1020b9a2a49c72a7e3d0f7803e680Timo Sirainen static struct var_expand_table static_tab[] = {
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen { 'i', NULL },
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen { 'o', NULL },
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen { '\0', NULL }
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen };
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen struct var_expand_table *tab;
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen string_t *str;
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen tab = t_malloc(sizeof(static_tab));
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen memcpy(tab, static_tab, sizeof(static_tab));
90804278df6586cceaf1b1b07a44713c01694048Timo Sirainen
6145bd3b17b9135b77b0b42409a0cc3fa0d1b946Timo Sirainen tab[0].value = dec2str(client->input->v_offset);
6145bd3b17b9135b77b0b42409a0cc3fa0d1b946Timo Sirainen tab[1].value = dec2str(client->output->offset);
6145bd3b17b9135b77b0b42409a0cc3fa0d1b946Timo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen str = t_str_new(128);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen var_expand(str, logout_format, tab);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen return str_c(str);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen}
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainenstatic const char *client_get_disconnect_reason(struct client *client)
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen{
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen errno = client->input->stream_errno != 0 ?
c8cf8a605e0ddea7cb36fe04551aeca5090e684bTimo Sirainen client->input->stream_errno :
c8cf8a605e0ddea7cb36fe04551aeca5090e684bTimo Sirainen client->output->stream_errno;
c8cf8a605e0ddea7cb36fe04551aeca5090e684bTimo Sirainen return errno == 0 || errno == EPIPE ? "Connection closed" :
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen t_strdup_printf("Connection closed: %m");
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen}
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
ef50336eefcb9ba99f73c6af37420eaf8857a39bTimo Sirainenvoid client_destroy(struct client *client, const char *reason)
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen{
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen i_assert(!client->destroyed);
14376e0584c178306c400750352869cf2aaf6feeTimo Sirainen client->destroyed = TRUE;
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen
68a4946b12583b88fa802e52ebee45cd96056772Timo Sirainen if (!client->disconnected) {
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen client->disconnected = TRUE;
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen if (reason == NULL)
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen reason = client_get_disconnect_reason(client);
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen i_info("%s %s", reason, client_stats(client));
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen }
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen i_stream_close(client->input);
288d6ef592719f2be3cad9f034e9be05f9839785Timo Sirainen o_stream_close(client->output);
288d6ef592719f2be3cad9f034e9be05f9839785Timo Sirainen
288d6ef592719f2be3cad9f034e9be05f9839785Timo Sirainen /* finish off all the queued commands. */
288d6ef592719f2be3cad9f034e9be05f9839785Timo Sirainen if (client->output_lock != NULL)
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen client_command_cancel(client->output_lock);
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen if (client->input_lock != NULL)
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen client_command_cancel(client->input_lock);
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen while (client->command_queue != NULL)
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen client_command_cancel(client->command_queue);
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen if (client->mailbox != NULL)
32e1554df9abca74fef0af2ba2e4c37e90a06cd0Timo Sirainen mailbox_close(&client->mailbox);
32e1554df9abca74fef0af2ba2e4c37e90a06cd0Timo Sirainen mail_namespaces_deinit(&client->namespaces);
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen if (client->free_parser != NULL)
9b706b345064ce8e8a657f54633f009a101298eaTimo Sirainen imap_parser_destroy(&client->free_parser);
a988c3fd9251806e38931a011aaa4006dd081cbdTimo Sirainen if (client->io != NULL)
a988c3fd9251806e38931a011aaa4006dd081cbdTimo Sirainen io_remove(&client->io);
c0a87e5f3316a57e6f915882fa1951d0fbb74a61Timo Sirainen if (client->to_idle_output != NULL)
bb1da4a6320eec11890c852e74a08868837e7be3Timo Sirainen timeout_remove(&client->to_idle_output);
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen timeout_remove(&client->to_idle);
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen
9874ad56b94788297fdac4eae7cba5d651b48222Timo Sirainen i_stream_destroy(&client->input);
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen o_stream_destroy(&client->output);
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen if (close(client->fd_in) < 0)
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen i_error("close(client in) failed: %m");
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen if (client->fd_in != client->fd_out) {
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen if (close(client->fd_out) < 0)
957d09e495c33ad1180f82152e5e87e6b51ab04bTimo Sirainen i_error("close(client out) failed: %m");
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainen }
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen pool_unref(&client->command_pool);
2615df45a8027948a474abe5e817b34b0499c171Timo Sirainen i_free(client);
a3f6d0302a83270253ff9d2ebd4fea0003e9ac95Timo Sirainen
14b551180cb4ac7acac8b048d8d6d7278541d1f6Timo Sirainen /* quit the program */
14b551180cb4ac7acac8b048d8d6d7278541d1f6Timo Sirainen my_client = NULL;
14b551180cb4ac7acac8b048d8d6d7278541d1f6Timo Sirainen io_loop_stop(ioloop);
14b551180cb4ac7acac8b048d8d6d7278541d1f6Timo Sirainen}
14b551180cb4ac7acac8b048d8d6d7278541d1f6Timo Sirainen
e8490a52a1bc71bc53034e68f464435684ad810fTimo Sirainenvoid client_disconnect(struct client *client, const char *reason)
{
i_assert(reason != NULL);
if (client->disconnected)
return;
i_info("Disconnected: %s %s", reason, client_stats(client));
client->disconnected = TRUE;
(void)o_stream_flush(client->output);
i_stream_close(client->input);
o_stream_close(client->output);
}
void client_disconnect_with_error(struct client *client, const char *msg)
{
client_send_line(client, t_strconcat("* BYE ", msg, NULL));
client_disconnect(client, msg);
}
int client_send_line(struct client *client, const char *data)
{
struct const_iovec iov[2];
if (client->output->closed)
return -1;
iov[0].iov_base = data;
iov[0].iov_len = strlen(data);
iov[1].iov_base = "\r\n";
iov[1].iov_len = 2;
if (o_stream_sendv(client->output, iov, 2) < 0)
return -1;
client->last_output = ioloop_time;
if (o_stream_get_buffer_used_size(client->output) >=
CLIENT_OUTPUT_OPTIMAL_SIZE) {
/* buffer full, try flushing */
return o_stream_flush(client->output);
}
return 1;
}
void client_send_tagline(struct client_command_context *cmd, const char *data)
{
struct client *client = cmd->client;
const char *tag = cmd->tag;
if (client->output->closed || cmd->cancel)
return;
if (tag == NULL || *tag == '\0')
tag = "*";
(void)o_stream_send_str(client->output, tag);
(void)o_stream_send(client->output, " ", 1);
(void)o_stream_send_str(client->output, data);
(void)o_stream_send(client->output, "\r\n", 2);
client->last_output = ioloop_time;
}
void client_send_command_error(struct client_command_context *cmd,
const char *msg)
{
struct client *client = cmd->client;
const char *error, *cmd_name;
bool fatal;
if (msg == NULL) {
msg = imap_parser_get_error(cmd->parser, &fatal);
if (fatal) {
client_disconnect_with_error(client, msg);
return;
}
}
if (cmd->tag == NULL)
error = t_strconcat("BAD Error in IMAP tag: ", msg, NULL);
else if (cmd->name == NULL)
error = t_strconcat("BAD Error in IMAP command: ", msg, NULL);
else {
cmd_name = t_str_ucase(cmd->name);
error = t_strconcat("BAD Error in IMAP command ",
cmd_name, ": ", msg, NULL);
}
client_send_tagline(cmd, error);
if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
client_disconnect_with_error(client,
"Too many invalid IMAP commands.");
}
cmd->param_error = TRUE;
/* client_read_args() failures rely on this being set, so that the
command processing is stopped even while command function returns
FALSE. */
cmd->state = CLIENT_COMMAND_STATE_DONE;
}
bool client_read_args(struct client_command_context *cmd, unsigned int count,
unsigned int flags, const struct imap_arg **args_r)
{
int ret;
i_assert(count <= INT_MAX);
ret = imap_parser_read_args(cmd->parser, count, flags, args_r);
if (ret >= (int)count) {
/* all parameters read successfully */
i_assert(cmd->client->input_lock == NULL ||
cmd->client->input_lock == cmd);
cmd->client->input_lock = NULL;
return TRUE;
} else if (ret == -2) {
/* need more data */
if (cmd->client->input->closed) {
/* disconnected */
cmd->state = CLIENT_COMMAND_STATE_DONE;
}
return FALSE;
} else {
/* error, or missing arguments */
client_send_command_error(cmd, ret < 0 ? NULL :
"Missing arguments");
return FALSE;
}
}
bool client_read_string_args(struct client_command_context *cmd,
unsigned int count, ...)
{
const struct imap_arg *imap_args;
va_list va;
const char *str;
unsigned int i;
if (!client_read_args(cmd, count, 0, &imap_args))
return FALSE;
va_start(va, count);
for (i = 0; i < count; i++) {
const char **ret = va_arg(va, const char **);
if (imap_args[i].type == IMAP_ARG_EOL) {
client_send_command_error(cmd, "Missing arguments.");
break;
}
str = imap_arg_string(&imap_args[i]);
if (str == NULL) {
client_send_command_error(cmd, "Invalid arguments.");
break;
}
if (ret != NULL)
*ret = str;
}
va_end(va);
return i == count;
}
static struct client_command_context *
client_command_find_with_flags(struct client_command_context *new_cmd,
enum command_flags flags)
{
struct client_command_context *cmd;
cmd = new_cmd->client->command_queue;
for (; cmd != NULL; cmd = cmd->next) {
if (cmd != new_cmd && (cmd->cmd_flags & flags) != 0)
return cmd;
}
return NULL;
}
static bool client_command_check_ambiguity(struct client_command_context *cmd)
{
enum command_flags flags;
bool broken_client = FALSE;
if ((cmd->cmd_flags & COMMAND_FLAG_USES_SEQS) != 0) {
/* no existing command must be breaking sequences */
flags = COMMAND_FLAG_BREAKS_SEQS;
broken_client = TRUE;
} else if ((cmd->cmd_flags & COMMAND_FLAG_BREAKS_SEQS) != 0) {
/* if existing command uses sequences, we'll have to block */
flags = COMMAND_FLAG_USES_SEQS;
} else {
return FALSE;
}
if (client_command_find_with_flags(cmd, flags) == NULL) {
if (cmd->client->syncing) {
/* don't do anything until syncing is finished */
return TRUE;
}
if (cmd->client->changing_mailbox) {
/* don't do anything until mailbox is fully
opened/closed */
return TRUE;
}
return FALSE;
}
if (broken_client) {
client_send_line(cmd->client,
"* BAD Command pipelining results in ambiguity.");
}
return TRUE;
}
static struct client_command_context *
client_command_new(struct client *client)
{
struct client_command_context *cmd;
cmd = p_new(client->command_pool, struct client_command_context, 1);
cmd->client = client;
cmd->pool = client->command_pool;
if (client->free_parser != NULL) {
cmd->parser = client->free_parser;
client->free_parser = NULL;
} else {
cmd->parser = imap_parser_create(client->input, client->output,
imap_max_line_length);
}
DLLIST_PREPEND(&client->command_queue, cmd);
client->command_queue_size++;
return cmd;
}
void client_command_free(struct client_command_context *cmd)
{
struct client *client = cmd->client;
/* reset input idle time because command output might have taken a
long time and we don't want to disconnect client immediately then */
client->last_input = ioloop_time;
timeout_reset(client->to_idle);
if (cmd->cancel) {
cmd->cancel = FALSE;
client_send_tagline(cmd, "NO Command cancelled.");
}
if (!cmd->param_error)
client->bad_counter = 0;
if (client->input_lock == cmd)
client->input_lock = NULL;
if (client->output_lock == cmd)
client->output_lock = NULL;
if (client->free_parser != NULL)
imap_parser_destroy(&cmd->parser);
else {
imap_parser_reset(cmd->parser);
client->free_parser = cmd->parser;
}
client->command_queue_size--;
DLLIST_REMOVE(&client->command_queue, cmd);
cmd = NULL;
if (client->command_queue == NULL) {
/* no commands left in the queue, we can clear the pool */
p_clear(client->command_pool);
if (client->to_idle_output != NULL)
timeout_remove(&client->to_idle_output);
}
}
static void client_add_missing_io(struct client *client)
{
if (client->io == NULL && !client->disconnected) {
client->io = io_add(client->fd_in,
IO_READ, client_input, client);
}
}
void client_continue_pending_input(struct client **_client)
{
struct client *client = *_client;
size_t size;
i_assert(!client->handling_input);
if (client->disconnected) {
if (!client->destroyed)
client_destroy(client, NULL);
*_client = NULL;
return;
}
if (client->input_lock != NULL) {
/* there's a command that has locked the input */
struct client_command_context *cmd = client->input_lock;
if (cmd->state != CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY)
return;
/* the command is waiting for existing ambiguity causing
commands to finish. */
if (client_command_check_ambiguity(cmd))
return;
cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT;
}
client_add_missing_io(client);
/* if there's unread data in buffer, handle it. */
(void)i_stream_get_data(client->input, &size);
if (size > 0)
(void)client_handle_input(client);
}
/* Skip incoming data until newline is found,
returns TRUE if newline was found. */
static bool client_skip_line(struct client *client)
{
const unsigned char *data;
size_t i, data_size;
data = i_stream_get_data(client->input, &data_size);
for (i = 0; i < data_size; i++) {
if (data[i] == '\n') {
client->input_skip_line = FALSE;
i++;
break;
}
}
i_stream_skip(client->input, i);
return !client->input_skip_line;
}
static void client_idle_output_timeout(struct client *client)
{
client_destroy(client,
"Disconnected for inactivity in reading our output");
}
bool client_handle_unfinished_cmd(struct client_command_context *cmd)
{
if (cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT) {
/* need more input */
return FALSE;
}
if (cmd->state != CLIENT_COMMAND_STATE_WAIT_OUTPUT) {
/* waiting for something */
if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC) {
/* this is mainly for APPEND. */
client_add_missing_io(cmd->client);
}
return TRUE;
}
/* output is blocking, we can execute more commands */
o_stream_set_flush_pending(cmd->client->output, TRUE);
if (cmd->client->to_idle_output == NULL) {
/* disconnect sooner if client isn't reading our output */
cmd->client->to_idle_output =
timeout_add(CLIENT_OUTPUT_TIMEOUT_MSECS,
client_idle_output_timeout, cmd->client);
}
return TRUE;
}
static bool client_command_input(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct command *command;
if (cmd->func != NULL) {
/* command is being executed - continue it */
if (cmd->func(cmd) || cmd->state == CLIENT_COMMAND_STATE_DONE) {
/* command execution was finished */
client_command_free(cmd);
client_add_missing_io(client);
return TRUE;
}
return client_handle_unfinished_cmd(cmd);
}
if (cmd->tag == NULL) {
cmd->tag = imap_parser_read_word(cmd->parser);
if (cmd->tag == NULL)
return FALSE; /* need more data */
cmd->tag = p_strdup(cmd->pool, cmd->tag);
}
if (cmd->name == NULL) {
cmd->name = imap_parser_read_word(cmd->parser);
if (cmd->name == NULL)
return FALSE; /* need more data */
cmd->name = p_strdup(cmd->pool, cmd->name);
}
client->input_skip_line = TRUE;
if (cmd->name == '\0') {
/* command not given - cmd_func is already NULL. */
} else if ((command = command_find(cmd->name)) != NULL) {
cmd->func = command->func;
cmd->cmd_flags = command->flags;
if (client_command_check_ambiguity(cmd)) {
/* do nothing until existing commands are finished */
i_assert(cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT);
cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
io_remove(&client->io);
return FALSE;
}
}
if (cmd->func == NULL) {
/* unknown command */
client_send_command_error(cmd, "Unknown command.");
cmd->param_error = TRUE;
client_command_free(cmd);
return TRUE;
} else {
i_assert(!client->disconnected);
return client_command_input(cmd);
}
}
static bool client_handle_next_command(struct client *client, bool *remove_io_r)
{
size_t size;
*remove_io_r = FALSE;
if (client->input_lock != NULL) {
if (client->input_lock->state ==
CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY) {
*remove_io_r = TRUE;
return FALSE;
}
return client_command_input(client->input_lock);
}
if (client->input_skip_line) {
/* first eat the previous command line */
if (!client_skip_line(client))
return FALSE;
client->input_skip_line = FALSE;
}
/* don't bother creating a new client command before there's at least
some input */
(void)i_stream_get_data(client->input, &size);
if (size == 0)
return FALSE;
/* beginning a new command */
if (client->command_queue_size >= CLIENT_COMMAND_QUEUE_MAX_SIZE ||
client->output_lock != NULL) {
/* wait for some of the commands to finish */
*remove_io_r = TRUE;
return FALSE;
}
client->input_lock = client_command_new(client);
return client_command_input(client->input_lock);
}
static bool client_handle_input(struct client *client)
{
bool ret, remove_io, handled_commands = FALSE;
client->handling_input = TRUE;
do {
T_BEGIN {
ret = client_handle_next_command(client, &remove_io);
} T_END;
if (ret)
handled_commands = TRUE;
} while (ret && !client->disconnected && client->io != NULL);
client->handling_input = FALSE;
if (client->output->closed) {
client_destroy(client, NULL);
return TRUE;
} else {
if (remove_io)
io_remove(&client->io);
else
client_add_missing_io(client);
if (!handled_commands)
return FALSE;
ret = cmd_sync_delayed(client);
if (ret)
client_continue_pending_input(&client);
return TRUE;
}
}
void client_input(struct client *client)
{
struct client_command_context *cmd;
struct ostream *output = client->output;
ssize_t bytes;
i_assert(client->io != NULL);
client->last_input = ioloop_time;
timeout_reset(client->to_idle);
bytes = i_stream_read(client->input);
if (bytes == -1) {
/* disconnected */
client_destroy(client, NULL);
return;
}
o_stream_ref(output);
o_stream_cork(output);
if (!client_handle_input(client) && bytes == -2) {
/* parameter word is longer than max. input buffer size.
this is most likely an error, so skip the new data
until newline is found. */
client->input_skip_line = TRUE;
cmd = client->input_lock != NULL ? client->input_lock :
client_command_new(client);
cmd->param_error = TRUE;
client_send_command_error(cmd, "Too long argument.");
client_command_free(cmd);
}
o_stream_uncork(output);
o_stream_unref(&output);
}
static void client_output_cmd(struct client_command_context *cmd)
{
bool finished;
/* continue processing command */
finished = cmd->func(cmd) || cmd->state == CLIENT_COMMAND_STATE_DONE;
if (!finished)
(void)client_handle_unfinished_cmd(cmd);
else {
/* command execution was finished */
client_command_free(cmd);
}
}
int client_output(struct client *client)
{
struct client_command_context *cmd;
int ret;
i_assert(!client->destroyed);
client->last_output = ioloop_time;
timeout_reset(client->to_idle);
if (client->to_idle_output != NULL)
timeout_reset(client->to_idle_output);
if ((ret = o_stream_flush(client->output)) < 0) {
client_destroy(client, NULL);
return 1;
}
/* mark all commands non-executed */
for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next)
cmd->temp_executed = FALSE;
o_stream_cork(client->output);
if (client->output_lock != NULL) {
client->output_lock->temp_executed = TRUE;
client_output_cmd(client->output_lock);
}
while (client->output_lock == NULL) {
/* go through the entire commands list every time in case
multiple commands were freed. temp_executed keeps track of
which messages we've called so far */
cmd = client->command_queue;
for (; cmd != NULL; cmd = cmd->next) {
if (!cmd->temp_executed &&
cmd->state == CLIENT_COMMAND_STATE_WAIT_OUTPUT) {
cmd->temp_executed = TRUE;
client_output_cmd(cmd);
break;
}
}
if (cmd == NULL) {
/* all commands executed */
break;
}
}
if (client->output->closed) {
client_destroy(client, NULL);
return 1;
} else {
(void)cmd_sync_delayed(client);
o_stream_uncork(client->output);
client_continue_pending_input(&client);
return ret;
}
}
void client_enable(struct client *client, enum mailbox_feature features)
{
struct mailbox_status status;
if ((client->enabled_features & features) == features)
return;
client->enabled_features |= features;
if (client->mailbox == NULL)
return;
mailbox_enable(client->mailbox, features);
if ((features & MAILBOX_FEATURE_CONDSTORE) != 0) {
/* CONDSTORE being enabled while mailbox is selected.
Notify client of the latest HIGHESTMODSEQ. */
mailbox_get_status(client->mailbox,
STATUS_HIGHESTMODSEQ, &status);
client_send_line(client, t_strdup_printf(
"* OK [HIGHESTMODSEQ %llu]",
(unsigned long long)status.highest_modseq));
}
}
void clients_init(void)
{
my_client = NULL;
}
void clients_deinit(void)
{
if (my_client != NULL) {
client_send_line(my_client, "* BYE Server shutting down.");
client_destroy(my_client, "Server shutting down");
}
}