imapc-connection.c revision a20216560f2642d52044ac1cf4f7822a43653a87
/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "ioloop.h"
#include "network.h"
#include "istream.h"
#include "ostream.h"
#include "base64.h"
#include "write-full.h"
#include "str.h"
#include "dns-lookup.h"
#include "iostream-rawlog.h"
#include "iostream-ssl.h"
#include "imap-quote.h"
#include "imap-util.h"
#include "imap-parser.h"
#include "imapc-client-private.h"
#include "imapc-connection.h"
#include <unistd.h>
#include <ctype.h>
/* IMAP protocol requires activity at least every 30 minutes */
enum imapc_input_state {
};
struct imapc_command_stream {
unsigned int pos;
};
struct imapc_command {
unsigned int send_pos;
unsigned int tag;
enum imapc_command_flags flags;
struct imapc_connection *conn;
/* If non-NULL, points to the mailbox where this command should be
executed */
struct imapc_client_mailbox *box;
void *context;
/* This is the IDLE command */
unsigned int idle:1;
/* Waiting for '+' literal reply before we can continue */
unsigned int wait_for_literal:1;
};
struct imapc_connection_literal {
char *temp_path;
int fd;
const struct imap_arg *parent_arg;
unsigned int list_idx;
};
struct imapc_connection {
struct imapc_client *client;
char *name;
int refcount;
int fd;
struct imap_parser *parser;
struct ssl_iostream *ssl_iostream;
enum imapc_input_state input_state;
unsigned int cur_tag;
enum imapc_connection_state state;
enum imapc_capability capabilities;
char **capabilities_list;
void *login_context;
/* commands pending in queue to be sent */
/* commands that have been sent, waiting for their tagged reply */
unsigned int ips_count, prev_connect_idx;
struct imapc_connection_literal literal;
unsigned int idling:1;
unsigned int idle_stopping:1;
unsigned int idle_plus_waiting:1;
unsigned int handshake_failed:1;
};
struct imapc_connection *
{
struct imapc_connection *conn;
return conn;
}
{
}
{
return;
}
{
}
{
/* we're only once moving the to_output to the main ioloop,
since timeout moves currently also reset the timeout.
(the rest of the times this is a no-op) */
}
}
{
unsigned int i;
}
}
static void
bool keep_retriable)
{
unsigned int i;
for (i = 0; i < array_count(cmd_array); ) {
if (keep_retriable &&
cmd->wait_for_literal = 0;
i++;
} else {
}
}
}
static void
bool keep_retriable)
{
struct imapc_command_reply reply;
/* need to move all the waiting commands to send queue */
&conn->cmd_send_queue);
&conn->cmd_wait_list);
}
/* abort the commands. we'll do it here later so that if the
callback recurses us back here we don't crash */
"Disconnected from server";
}
}
{
}
static void
const struct imapc_command_reply *reply)
{
if (login_callback == NULL)
return;
}
enum imapc_connection_state state)
{
struct imapc_command_reply reply;
switch (state) {
"Disconnected from server";
break;
default:
break;
}
}
{
struct imapc_arg_file *lfile;
i_error("imapc: close(literal file) failed: %m");
}
}
static void
{
}
}
{
return;
}
{
}
{
else
}
const char *fmt, ...)
{
i_error("imapc(%s): Server sent invalid input: %s",
}
const struct imap_arg **parent_arg_r,
unsigned int *idx_r)
{
const char *name;
unsigned int count;
return TRUE;
}
return FALSE;
}
static int
{
const char *path;
const struct imap_arg *parent_arg;
unsigned int idx;
if (size <= IMAPC_MAX_INLINE_LITERAL_SIZE ||
/* read the literal directly into parser */
return 0;
}
return -1;
return 1;
}
{
struct imapc_arg_file *lfile;
const unsigned char *data;
return 1;
if (size > 0) {
i_error("imapc(%s): write(%s) failed: %m",
return -1;
}
}
return 0;
/* finished */
return 1;
}
static int
const struct imap_arg **imap_args_r)
{
bool fatal;
int ret;
return ret;
if (ret == -2) {
/* need more data */
return 0;
}
if (ret < 0) {
return -1;
}
*imap_args_r) <= 0) {
return 2;
}
}
return 1;
}
static int
const struct imap_arg **imap_args_r)
{
const unsigned char *data;
int ret;
;
if (ret > 0) {
else
i_panic("imapc: Missing LF from input line");
}
return ret;
}
static int
const char *value)
{
const char *const *tmp;
unsigned int i;
i_debug("imapc(%s): Server capabilities: %s",
}
conn->capabilities = 0;
const struct imapc_capability_name *cap =
break;
}
}
}
"CAPABILITY list is missing IMAP4REV1");
return -1;
}
return 0;
}
static int
{
return -1;
}
/* QRESYNC: SELECTing another mailbox */
}
}
return 0;
}
static int
const char *text,
{
const char *p, *value;
if (p == NULL) {
return -1;
}
} else {
}
}
static int
{
const char *text;
return 0;
if (*text != '[') {
if (*text == '\0') {
"Missing text in resp-text");
return -1;
}
return 0;
}
}
static bool need_literal(const char *str)
{
unsigned int i;
for (i = 0; str[i] != '\0'; i++) {
unsigned char c = str[i];
if ((c & 0x80) != 0 || c == '\r' || c == '\n')
return TRUE;
}
return FALSE;
}
{
}
void *context)
{
else {
i_error("imapc(%s): Authentication failed: %s",
}
return;
}
}
static const char *
{
} else {
}
}
{
struct imapc_command *cmd;
i_debug("imapc(%s): Authenticating as %s",
} else {
i_debug("imapc(%s): Authenticating as %s for user %s",
}
}
conn);
/* We can use LOGIN command */
} else {
}
}
static void
void *context)
{
return;
}
if (imapc_connection_ssl_init(conn) < 0)
else
}
{
struct imapc_command *cmd;
i_error("imapc(%s): Requested STARTTLS, "
"but server doesn't support it",
return;
}
conn);
return;
}
}
static void
void *context)
{
} else if (conn->capabilities == 0) {
"Capabilities not returned by server");
} else {
}
}
{
struct imapc_command *cmd;
int ret;
return ret;
return -1;
if (conn->capabilities == 0) {
/* capabilities weren't sent in the banner. ask for them. */
conn);
} else {
}
return 1;
}
{
struct imap_parser *parser;
struct imapc_untagged_reply reply;
int ret;
/* input banner */
return 0;
"Banner doesn't begin with OK: %s", name);
return -1;
}
return 1;
}
return ret;
return -1;
}
imap_args++;
/* <seq> <event> */
"Invalid untagged reply");
return -1;
}
imap_args++;
}
&reply.resp_text_value) < 0)
return -1;
return -1;
}
}
/* the callback may disconnect and destroy the parser */
return 1;
}
{
struct imapc_command *const *cmds;
unsigned int cmds_count;
const char *line;
return 0;
if (conn->idle_plus_waiting) {
/* "+ idling" reply for IDLE command */
/* no timeouting while IDLEing */
/* reply for literal */
} else {
return -1;
}
return 1;
}
static void
const struct imapc_command_reply *reply)
{
}
{
unsigned int i, count;
const char *p;
struct imap_parser *parser;
struct imapc_command_reply reply;
return 0;
else {
*linep = '\0';
}
else {
"Invalid state in tagged reply: %u %s %s",
return -1;
}
/* get resp-text */
&reply.resp_text_value) < 0)
return -1;
} else {
}
/* find the command. it's either the first command in send queue
(literal failed) or somewhere in wait list. */
} else {
for (i = 0; i < count; i++) {
break;
}
}
}
"Unknown tag in a reply: %u %s %s",
return -1;
}
i_error("imapc(%s): Command '%s' failed with BAD: %u %s",
}
return 1;
}
{
const char *tag;
int ret = -1;
switch (conn->input_state) {
case IMAPC_INPUT_STATE_NONE:
return 0;
} else {
"Invalid command tag: %s", tag);
ret = -1;
} else {
}
}
break;
case IMAPC_INPUT_STATE_PLUS:
break;
break;
case IMAPC_INPUT_STATE_TAGGED:
break;
}
return ret;
}
{
const char *errstr;
/* we need to read as much as we can with SSL streams to avoid
hanging */
if (ret < 0) {
/* disconnected */
i_error("imapc(%s): Server disconnected unexpectedly",
} else if (!conn->handshake_failed) {
i_error("imapc(%s): Server disconnected: %s",
}
}
}
static int imapc_connection_ssl_handshaked(void *context)
{
i_error("imapc(%s): SSL certificate not received",
} else {
i_error("imapc(%s): Received invalid SSL certificate",
}
i_error("imapc(%s): SSL certificate doesn't match host name",
} else {
i_debug("imapc(%s): SSL handshake successful",
}
return 0;
}
return -1;
}
{
struct ssl_iostream_settings ssl_set;
const char *source;
return -1;
}
/* recreate rawlog after STARTTLS */
}
&conn->ssl_iostream) < 0) {
i_error("imapc(%s): Couldn't initialize SSL client",
return -1;
}
conn);
return -1;
}
}
return 0;
}
{
int err;
if (err != 0) {
i_error("imapc(%s): connect(%s, %u) failed: %s",
return;
}
if (imapc_connection_ssl_init(conn) < 0)
}
}
{
i_error("imapc(%s): connect(%s, %u) timed out after %u seconds",
IMAPC_CONNECT_TIMEOUT_MSECS/1000);
break;
i_error("imapc(%s): Authentication timed out after %u seconds",
break;
default:
i_unreached();
}
}
static void
void *context)
{
}
{
struct imapc_command *cmd;
else
}
{
int fd;
if (fd == -1) {
return;
}
}
conn);
}
}
static void
void *context)
{
i_error("imapc(%s): dns_lookup(%s) failed: %s",
return;
}
}
void *login_context)
{
struct dns_lookup_settings dns_set;
return;
}
} else {
}
}
{
int ret = 1;
return;
T_BEGIN {
} T_END;
}
}
static struct imapc_command *
{
static unsigned int cmd_tag_counter = 0;
struct imapc_command *cmd;
if (++cmd_tag_counter == 0)
return cmd;
}
{
struct imapc_command_stream *stream;
}
}
static bool
unsigned int *value_r)
{
/* data should contain "{size}\r\n" and pos points after \n */
return FALSE;
pos -= 4;
do {
pos--;
return FALSE;
return TRUE;
}
struct imapc_command *cmd)
{
/* everything sent. move command to wait list. */
/* send the next command in queue */
}
static struct imapc_command_stream *
{
struct imapc_command_stream *stream;
return NULL;
return NULL;
return stream;
}
struct imapc_command *cmd)
{
struct imapc_command_stream *stream;
return -1;
/* we're sending the stream now */
return 0;
}
/* finished with the stream */
return 1;
}
{
/* server will send a [CLOSED] once selected mailbox is
closed */
} else {
/* we'll have to assume that all the future untagged messages
are for the mailbox we're selecting */
}
}
{
struct imapc_command_reply reply;
const unsigned char *p, *data;
int ret;
if (count == 0)
return;
/* wait until we're fully connected */
return;
}
if (cmd->wait_for_literal) {
/* wait until we received '+' */
return;
}
/* non-mailbox command */
return;
}
/* shouldn't normally happen */
return;
}
return;
/* skip over the literal. we can also get here from
AUTHENTICATE command, which doesn't use a literal */
}
}
do {
/* keep going for LITERAL+ command */
p[-1] == '\r' && p[-2] == '}' && p[-3] == '+');
} else {
}
}
{
struct imapc_command *const *cmds;
unsigned int count;
i_error("imapc(%s): Command '%s' timed out, disconnecting",
}
{
}
}
{
/* pre-login commands get inserted before everything else */
return;
}
/* add timeout for commands if there's not one yet
(pre-login has its own timeout) */
}
}
/* reopening the mailbox. add it before other
queued commands. */
} else {
}
}
{
struct imapc_command *const *cmds;
unsigned int count;
int ret;
return 1;
if (count > 0) {
!cmds[0]->wait_for_literal) {
/* we're sending a stream. send more. */
}
}
return ret;
}
struct imapc_command *
{
struct imapc_command *cmd;
return cmd;
}
enum imapc_command_flags flags)
{
}
struct imapc_client_mailbox *box)
{
}
{
}
{
}
{
unsigned int i;
for (i = 0; cmd_fmt[i] != '\0'; i++) {
if (cmd_fmt[i] != '%') {
continue;
}
switch (cmd_fmt[++i]) {
case '\0':
i_unreached();
case 'u': {
break;
}
case 'p': {
struct imapc_command_stream *s;
size = 0;
break;
}
case 's': {
if (!need_literal(arg))
IMAPC_CAPABILITY_LITERALPLUS) != 0) {
} else {
}
break;
}
case '1': {
/* %1s - no quoting */
break;
}
}
}
}
{
}
enum imapc_capability
{
return conn->capabilities;
}
{
}
}
struct imapc_client_mailbox *
{
return conn->selecting_box;
return conn->selected_box;
}
static void
void *context)
{
}
{
struct imapc_command *cmd;
return;
}