view src/imap/client.c @ 91:dc0891523276 HEAD

bugfix for sync fix
author Timo Sirainen <tss@iki.fi>
date Thu, 29 Aug 2002 01:42:00 +0300
parents 82b7de533f98
children a1204e882bc7
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "common.h"
#include "iobuffer.h"
#include "commands.h"

#include <stdlib.h>

/* max. size of one parameter in line */
#define MAX_INBUF_SIZE 8192

/* If we can't send a buffer in a minute, disconnect the client */
#define CLIENT_OUTPUT_TIMEOUT (60*1000)

/* Disconnect client when it sends too many bad commands */
#define CLIENT_MAX_BAD_COMMANDS 20

/* Disconnect client after idling this many seconds */
#define CLIENT_IDLE_TIMEOUT (60*30)

static Client *my_client; /* we don't need more than one currently */
static Timeout to_idle;

static void client_input(Client *client);

static void client_send_timeout(Client *client)
{
	io_buffer_close(client->inbuf);
	io_buffer_close(client->outbuf);
}

Client *client_create(int hin, int hout, int socket, MailStorage *storage)
{
	Client *client;

	client = i_new(Client, 1);
	client->socket = socket;
	client->inbuf = io_buffer_create(hin, default_pool, 0,
					 MAX_INBUF_SIZE);
	client->outbuf = io_buffer_create(hout, default_pool, 0, 0);

	io_buffer_set_send_blocking(client->outbuf, 4096,
				    CLIENT_OUTPUT_TIMEOUT,
				    (TimeoutFunc) client_send_timeout, client);

	client->inbuf->file = !socket;
	client->outbuf->file = !socket;

	client->io = io_add(socket, IO_READ, (IOFunc) client_input, client);
	client->parser = imap_parser_create(client->inbuf, client->outbuf);
        client->last_input = ioloop_time;

	client->storage = storage;

	i_assert(my_client == NULL);
	my_client = client;
	return client;
}

void client_destroy(Client *client)
{
	if (client->mailbox != NULL)
		client->mailbox->close(client->mailbox);
	mail_storage_destroy(client->storage);

	imap_parser_destroy(client->parser);
	io_remove(client->io);

	io_buffer_destroy(client->inbuf);
	io_buffer_destroy(client->outbuf);

	i_free(client);

	/* quit the program */
	my_client = NULL;
	io_loop_stop(ioloop);
}

void client_disconnect(Client *client)
{
	io_buffer_close(client->inbuf);
	io_buffer_close(client->outbuf);
}

void client_send_line(Client *client, const char *data)
{
	unsigned char *buf;
	unsigned int len;

	if (client->outbuf->closed)
		return;

	len = strlen(data);

	buf = io_buffer_get_space(client->outbuf, len+2);
	if (buf != NULL) {
		memcpy(buf, data, len);
		buf[len++] = '\r'; buf[len++] = '\n';

		/* Returns error only if we disconnected -
		   we don't need to do anything about it. */
		(void)io_buffer_send_buffer(client->outbuf, len);
	} else {
		/* not enough space in output buffer, send this directly.
		   will block. */
		io_buffer_send(client->outbuf, data, len);
		io_buffer_send(client->outbuf, "\r\n", 2);
	}
}

void client_send_tagline(Client *client, const char *data)
{
	const char *tag = client->cmd_tag;
	unsigned char *buf;
	unsigned int taglen, len;

	if (client->outbuf->closed)
		return;

	if (tag == NULL || *tag == '\0')
		tag = "*";

	taglen = strlen(tag);
	len = strlen(data);

	buf = io_buffer_get_space(client->outbuf, taglen+1+len+2);
	if (buf != NULL) {
		memcpy(buf, tag, taglen); buf[taglen] = ' ';
		buf += taglen+1;

		memcpy(buf, data, len); buf += len;
		buf[0] = '\r'; buf[1] = '\n';

		(void)io_buffer_send_buffer(client->outbuf, taglen+1+len+2);
	} else {
		const char *str;

		str = t_strconcat(tag, " ", data, "\r\n", NULL);
		(void)io_buffer_send(client->outbuf, str, strlen(str));
	}
}

void client_send_command_error(Client *client, const char *msg)
{
	const char *error;

	if (msg == NULL)
		error = "BAD Error in IMAP command.";
	else
		error = t_strconcat("BAD Error in IMAP command: ", msg, NULL);

	client->cmd_error = TRUE;
	client_send_tagline(client, error);

	if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
		client_send_line(client,
				 "* BYE Too many invalid IMAP commands.");
		client_disconnect(client);
	}
}

int client_read_args(Client *client, unsigned int count, unsigned int flags,
		     ImapArg **args)
{
	int ret;

	ret = imap_parser_read_args(client->parser, count, flags, args);
	if ((unsigned int) ret == count) {
		/* all parameters read successfully */
		return TRUE;
	} else if (ret == -2) {
		/* need more data */
		return FALSE;
	} else {
		/* error, or missing arguments */
		client_send_command_error(client,
					  "Missing or invalid arguments.");
		return FALSE;
	}
}

int client_read_string_args(Client *client, unsigned int count, ...)
{
	ImapArg *imap_args;
	va_list va;
	const char *str;
	unsigned int i;

	if (!client_read_args(client, count, 0, &imap_args))
		return FALSE;

	va_start(va, count);
	for (i = 0; i < count; i++) {
		const char **ret = va_arg(va, const char **);

		str = imap_arg_string(&imap_args[i]);
		if (str == NULL) {
			client_send_command_error(client, "Missing arguments.");
			va_end(va);
			return FALSE;
		}

		if (ret != NULL)
			*ret = str;
	}
	va_end(va);

	return TRUE;
}

static void client_reset_command(Client *client)
{
	client->cmd_tag = NULL;
	client->cmd_name = NULL;
	client->cmd_func = NULL;
	client->cmd_error = FALSE;
	client->cmd_uid = FALSE;

        imap_parser_reset(client->parser);
}

static void client_command_finished(Client *client)
{
	client->inbuf_skip_line = TRUE;
        client_reset_command(client);
}

/* Skip incoming data until newline is found,
   returns TRUE if newline was found. */
static int client_skip_line(Client *client)
{
	unsigned char *data;
	unsigned int i, data_size;

	/* get the beginning of data in input buffer */
	data = io_buffer_get_data(client->inbuf, &data_size);

	for (i = 0; i < data_size; i++) {
		if (data[i] == '\n') {
			client->inbuf_skip_line = FALSE;
			io_buffer_skip(client->inbuf, i+1);
			break;
		}
	}

	return !client->inbuf_skip_line;
}

static int client_handle_input(Client *client)
{
        if (client->cmd_func != NULL) {
		/* command is being executed - continue it */
		if (client->cmd_func(client)) {
			/* command is finished */
			client_command_finished(client);
			return TRUE;
		}
		return FALSE;
	}

	if (client->inbuf_skip_line) {
		/* we're just waiting for new line.. */
		if (!client_skip_line(client))
			return FALSE;

		/* got the newline */
		client_reset_command(client);

		/* pass through to parse next command */
	}

	if (client->cmd_tag == NULL) {
                client->cmd_tag = imap_parser_read_word(client->parser);
		if (client->cmd_tag == NULL)
			return FALSE; /* need more data */
	}

	if (client->cmd_name == NULL) {
                client->cmd_name = imap_parser_read_word(client->parser);
		if (client->cmd_name == NULL)
			return FALSE; /* need more data */
	}

	if (client->cmd_name == '\0') {
		/* command not given - cmd_func is already NULL. */
	} else {
		/* find the command function */
		client->cmd_func = client_command_find(client->cmd_name);
	}

	if (client->cmd_func == NULL) {
		/* unknown command */
		client_send_command_error(client, t_strconcat(
			"Unknown command '", client->cmd_name, "'", NULL));
		client_command_finished(client);
	} else {
		if (client->cmd_func(client) || client->cmd_error) {
			/* command execution was finished */
			client_command_finished(client);
		}
	}

	return TRUE;
}

static void client_input(Client *client)
{
	client->last_input = ioloop_time;

	switch (io_buffer_read(client->inbuf)) {
	case -1:
		/* disconnected */
		client_destroy(client);
		return;
	case -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->inbuf_skip_line = TRUE;

		client_send_command_error(client, "Too long argument.");
		client_command_finished(client);
		break;
	}

	io_buffer_cork(client->outbuf);
	while (client_handle_input(client))
		;
	io_buffer_send_flush(client->outbuf);

	if (client->outbuf->closed)
		client_destroy(client);
}

static void idle_timeout(void *context __attr_unused__,
			 Timeout timeout __attr_unused__)
{
	if (my_client == NULL)
		return;

	if (ioloop_time - my_client->last_input >= CLIENT_IDLE_TIMEOUT) {
		client_send_line(my_client,
				 "* BYE Disconnected for inactivity.");
		client_destroy(my_client);
	}
}

void clients_init(void)
{
	my_client = NULL;
	to_idle = timeout_add(10000, idle_timeout, NULL);
}

void clients_deinit(void)
{
	if (my_client != NULL)
		client_destroy(my_client);

	timeout_remove(to_idle);
}