/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
#include "imap-common.h"
#include "ioloop.h"
#include "llist.h"
#include "str.h"
#include "hostpid.h"
#include "net.h"
#include "iostream.h"
#include "iostream-rawlog.h"
#include "istream.h"
#include "ostream.h"
#include "time-util.h"
#include "var-expand.h"
#include "master-service.h"
#include "imap-resp-code.h"
#include "imap-util.h"
#include "imap-urlauth.h"
#include "mail-error.h"
#include "mail-namespace.h"
#include "mail-storage-service.h"
#include "mail-autoexpunge.h"
#include "imap-state.h"
#include "imap-search.h"
#include "imap-notify.h"
#include "imap-commands.h"
#include <unistd.h>
/* If the last command took longer than this to run, log statistics on
where the time was spent. */
extern struct mail_storage_callbacks mail_storage_callbacks;
extern struct imap_client_vfuncs imap_client_vfuncs;
unsigned int imap_client_count = 0;
"wait-input",
"wait-output",
"wait-external",
"wait-unambiguity",
"wait-sync",
"done"
};
{
}
{
}
{
/*
* We have to iterate over namespace and mailbox *settings* since
* the namespaces haven't been set up yet. The namespaces haven't
* been set up so that we don't hold up the OK response to LOGIN
* when using slow lib-storage backends.
*/
/* no namespaces => no special use flags */
return FALSE;
/* no mailboxes => no special use flags */
continue;
return TRUE;
}
}
return FALSE;
}
struct mail_storage_service_user *service_user,
const struct imap_settings *set,
const struct smtp_submit_settings *smtp_set)
{
const char *ident;
/* always use nonblocking I/O */
client->v = imap_client_vfuncs;
}
} else {
}
else
if (user->fuzzy_search) {
/* Enable FUZZY capability only when it actually has
a chance of working */
}
if (mail_set->mailbox_list_index) {
/* NOTIFY is enabled only when mailbox list indexes are
enabled, although even that doesn't necessarily guarantee
it always */
}
/* Enable URLAUTH capability only when dict is
configured correctly */
}
}
if (user_has_special_use_mailboxes(user)) {
/* Advertise SPECIAL-USE only if there are actually some
SPECIAL-USE flags in mailbox configuration. */
}
if (ident != NULL) {
}
if (hook_client_created != NULL)
return client;
}
{
return -1;
return 0;
}
{
bool cmd_ret;
/* a bit kludgy check: cancel command only if it has context
set. currently only append command matches this check. all
other commands haven't even started the processing yet. */
break;
/* fall through */
break;
/* commands haven't started yet */
break;
i_unreached();
}
if (!cmd_ret) {
} else {
}
}
{
};
const char *error;
i_error("Failed to expand imap_logout_format=%s: %s",
}
}
{
}
static void
const struct client_command_stats *stats,
const char *wait_condition,
{
unsigned int msecs_in_ioloop;
if (wait_condition[0] != '\0')
if (stats->lock_wait_usecs > 0) {
}
if (buffered_size > 0)
}
{
if (client->logged_out)
return "";
return " (No commands sent)";
/* client disconnected without sending LOGOUT. if the last command
took over 1 second to run, log it. */
last_run_secs%1000);
}
}
{
const char *cond_str;
return client_get_last_command_status(client);
/* (parts of a) tag were received, but not yet
the command name */
continue;
}
}
return client_get_last_command_status(client);
cond_str = "input";
cond_str = "output";
else
cond_str = "nothing";
}
{
}
{
/* finish off all the queued commands. */
}
/* handle the input_lock command last. it might have been waiting on
other queued commands (although we probably should just drop the
command at that point since it hasn't started running. but this may
change in future). */
if (client->anvil_sent) {
"\n", NULL));
}
/* Autoexpunging might run for a long time. Disconnect the client
before it starts, and refresh proctitle so it's clear that it's
doing autoexpunging. We've also sent DISCONNECT to anvil already,
because this is background work and shouldn't really be counted
as an active IMAP session for the user.
Don't autoexpunge if the client is hibernated - it shouldn't be any
different from the non-hibernating IDLE case. For frequent
hibernations it could also be doing unnecessarily much work. */
if (!client->hibernated) {
}
mail_storage_callbacks notifications that write to the ostream. */
}
{
}
{
if (client->disconnected)
return;
}
{
}
{
/* require a single capability at a time (feels cleaner) */
/* explicit capability - don't change it */
return;
}
}
{
}
{
return -1;
return -1;
/* buffer full, try flushing */
}
return 1;
}
static void
{
unsigned int msecs_since_cmd;
return;
if (msecs_since_cmd > 0) {
}
}
{
}
static void
{
return;
tag = "*";
T_BEGIN {
} T_END;
}
static int
{
return 1;
}
const char *msg)
{
switch (parse_error) {
case IMAP_PARSE_ERROR_NONE:
i_unreached();
return;
default:
break;
}
}
else {
}
"Too many invalid IMAP commands.");
}
/* client_read_args() failures rely on this being set, so that the
command processing is stopped even while command function returns
FALSE. */
}
{
}
{
int ret;
/* all parameters read successfully */
str_truncate(str, 0);
return TRUE;
} else if (ret == -2) {
/* need more data */
/* disconnected */
}
return FALSE;
} else {
/* error, or missing arguments */
"Missing arguments");
return FALSE;
}
}
unsigned int count, ...)
{
const char *str;
unsigned int i;
return FALSE;
for (i = 0; i < count; i++) {
if (IMAP_ARG_IS_EOL(&imap_args[i])) {
break;
}
break;
}
}
return i == count;
}
static struct client_command_context *
enum command_flags flags,
{
return cmd;
}
return NULL;
}
{
return TRUE;
if (cmd->search_save_result_used) {
/* if there are pending commands that update the search
save result, wait */
if (old_cmd->search_save_result)
return TRUE;
}
}
/* there must be no other command running that uses the
selected mailbox */
/* no existing command must be breaking sequences */
/* if existing command uses sequences, we'll have to block */
} else {
return FALSE;
}
/* don't do anything until syncing is finished */
return TRUE;
}
/* don't do anything until mailbox is fully
return TRUE;
}
return FALSE;
}
if (broken_client) {
"Command pipelining results in ambiguity.");
}
return TRUE;
}
{
return cmd;
}
{
}
static struct client_command_context *
{
} else {
}
return cmd;
}
{
}
{
/* reset input idle time because command output might have taken a
long time and we don't want to disconnect client immediately then */
}
if (!cmd->param_error)
client->bad_counter = 0;
}
} else {
}
}
/* no commands left in the queue, we can clear the pool */
}
/* if command finished from external event, check input for more
unhandled commands since we may not be executing from client_input
or client_output. */
if (state == CLIENT_COMMAND_STATE_WAIT_EXTERNAL &&
!client->disconnected) {
}
}
}
{
unsigned int unfinished_count = 0;
/* We need to be reading input for this command.
However, if there is already an output lock for
another command we'll wait for it to finish first.
This is needed because if there are any literals
we'd need to send "+ OK" responses. */
break;
break;
break;
break;
else {
/* we have an output callback, which will be
called soon and it'll run cmd_sync_delayed().
FIXME: is this actually wanted? */
}
break;
i_unreached();
}
}
}
{
/* there's a command that has locked the input */
return TRUE;
/* the command is waiting for existing ambiguity causing
commands to finish. */
if (client_command_is_ambiguous(cmd)) {
/* we could be waiting for existing sync to finish */
if (!cmd_sync_delayed(client))
return FALSE;
return FALSE;
}
}
return TRUE;
}
{
/* this function is called at the end of I/O callbacks (and only there).
fix up the command states and verify that they're correct. */
while (client_remove_pending_unambiguity(client)) {
/* if there's unread data in buffer, handle it. */
break;
if (!ret)
break;
}
}
/* Skip incoming data until newline is found,
returns TRUE if newline was found. */
{
const unsigned char *data;
for (i = 0; i < data_size; i++) {
if (data[i] == '\n') {
i++;
break;
}
}
return !client->input_skip_line;
}
{
"Disconnected for inactivity in reading our output");
}
{
/* need more input */
return FALSE;
}
/* waiting for something */
/* this is mainly for APPEND. */
}
return TRUE;
}
/* output is blocking, we can execute more commands */
/* disconnect sooner if client isn't reading our output */
}
return TRUE;
}
{
/* command is being executed - continue it */
if (command_exec(cmd)) {
/* command execution was finished */
return TRUE;
}
return client_handle_unfinished_cmd(cmd);
}
return FALSE; /* need more data */
}
return FALSE; /* need more data */
/* UID commands are a special case. better to handle them
here. */
return FALSE; /* need more data */
}
}
/* command not given - cmd->func is already NULL. */
if (client_command_is_ambiguous(cmd)) {
/* do nothing until existing commands are finished */
return FALSE;
}
}
/* unknown command */
return TRUE;
} else {
return client_command_input(cmd);
}
}
{
*remove_io_r = FALSE;
/* we can't send literal "+ OK" replies if output is
locked by another command. */
*remove_io_r = TRUE;
return FALSE;
}
}
if (client->input_skip_line) {
/* first eat the previous command line */
if (!client_skip_line(client))
return FALSE;
}
/* don't bother creating a new client command before there's at least
some input */
return FALSE;
/* beginning a new command */
/* wait for some of the commands to finish */
*remove_io_r = TRUE;
return FALSE;
}
}
{
do {
T_BEGIN {
} T_END;
if (ret)
if (remove_io)
else
if (!handled_commands)
return FALSE;
/* finished handling all commands. sync them all at once now. */
/* the command may be waiting for previous command to sync. */
}
return TRUE;
}
{
if (bytes == -1) {
/* disconnected */
return;
}
/* parameter word is longer than max. input buffer size.
this is most likely an error, so skip the new data
until newline is found. */
}
if (client->disconnected)
else
}
{
bool finished;
/* continue processing command */
if (!finished)
(void)client_handle_unfinished_cmd(cmd);
else {
/* command execution was finished */
}
}
{
/* mark all commands non-executed */
}
/* 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 */
if (!cmd->temp_executed &&
break;
}
}
/* all commands executed */
break;
}
}
}
{
int ret;
return 1;
}
(void)cmd_sync_delayed(client);
else {
/* corking is added automatically by ostream-file. we need to
uncork here before client_check_command_hangs() is called,
because otherwise it can assert-crash due to ioloop not
having IO_WRITE callback set for the ostream. */
}
return ret;
}
{
/* search only commands that were added before this command
(commands are prepended to the queue, so they're after ourself) */
if (old_cmd->search_save_result)
break;
}
return FALSE;
/* ambiguity, wait until it's over */
return TRUE;
}
{
int ret;
return 0;
return 0;
/* CONDSTORE being enabled while mailbox is selected.
Notify client of the latest HIGHESTMODSEQ. */
if (ret == 0) {
}
}
if (ret < 0) {
}
return ret;
}
struct imap_search_update *
unsigned int *idx_r)
{
unsigned int i, count;
return NULL;
for (i = 0; i < count; i++) {
*idx_r = i;
return &updates[i];
}
}
return NULL;
}
{
return;
}
void clients_destroy_all(void)
{
while (imap_clients != NULL) {
}
}
};