[dovecot-cvs] dovecot/src/pop3 .cvsignore,NONE,1.1 Makefile.am,NONE,1.1 client.c,NONE,1.1 client.h,NONE,1.1 commands.c,NONE,1.1 commands.h,NONE,1.1 common.h,NONE,1.1 mail-storage-callbacks.c,NONE,1.1 main.c,NONE,1.1

cras at procontrol.fi cras at procontrol.fi
Mon Jan 27 07:45:49 EET 2003


Update of /home/cvs/dovecot/src/pop3
In directory danu:/tmp/cvs-serv13972/src/pop3

Added Files:
	.cvsignore Makefile.am client.c client.h commands.c commands.h 
	common.h mail-storage-callbacks.c main.c 
Log Message:
Initial code for POP3 server. RETR isn't working right yet, there's some
syncing problems to figure out (pop3 wants to keep the mailbox locked) and
the whole pop3-login process is still missing.



--- NEW FILE: .cvsignore ---
*.la
*.lo
*.o
.deps
.libs
Makefile
Makefile.in
so_locations
pop3

--- NEW FILE: Makefile.am ---
pkglibexecdir = $(libexecdir)/dovecot

pkglibexec_PROGRAMS = pop3

INCLUDES = \
	-I$(top_srcdir)/src/lib \
	-I$(top_srcdir)/src/lib-mail \
	-I$(top_srcdir)/src/lib-imap \
	-I$(top_srcdir)/src/lib-storage

pop3_LDADD = \
	../lib-storage/register/libstorage-register.a \
	../lib-storage/index/maildir/libstorage_maildir.a \
	../lib-storage/index/mbox/libstorage_mbox.a \
	../lib-storage/index/libstorage_index.a \
	../lib-storage/libstorage.a \
	../lib-index/maildir/libstorage_index_maildir.a \
	../lib-index/mbox/libstorage_index_mbox.a \
	../lib-index/libstorage_index.a \
	../lib-storage/subscription-file/libstorage_subscription_file.a \
	../lib-imap/libimap.a \
	../lib-mail/libmail.a \
	../lib-charset/libcharset.a \
	../lib/liblib.a

pop3_SOURCES = \
	client.c \
	commands.c \
	mail-storage-callbacks.c \
	main.c

noinst_HEADERS = \
	client.h \
	commands.h \
	common.h

--- NEW FILE: client.c ---
/* Copyright (C) 2002 Timo Sirainen */

#include "common.h"
#include "ioloop.h"
#include "network.h"
#include "istream.h"
#include "ostream.h"
#include "mail-storage.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 in a row */
#define CLIENT_MAX_BAD_COMMANDS 20

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

extern struct mail_storage_callbacks mail_storage_callbacks;

static struct client *my_client; /* we don't need more than one currently */
static struct timeout *to_idle;

static void client_input(void *context);

static void client_output_timeout(void *context)
{
	struct client *client = context;

	i_stream_close(client->input);
	o_stream_close(client->output);
}

struct client *client_create(int hin, int hout, struct mailbox *mailbox)
{
	struct client *client;
	struct mailbox_status status;

	client = i_new(struct client, 1);
	client->input = i_stream_create_file(hin, default_pool,
					     MAX_INBUF_SIZE, FALSE);
	client->output = o_stream_create_file(hout, default_pool, 4096,
					      IO_PRIORITY_DEFAULT, FALSE);

	/* set timeout for sending data */
	o_stream_set_blocking(client->output, CLIENT_OUTPUT_TIMEOUT,
			      client_output_timeout, client);

	client->io = io_add(hin, IO_READ, client_input, client);
        client->last_input = ioloop_time;

	client->storage = mailbox->storage;
	client->mailbox = mailbox;

	mailbox->storage->set_callbacks(mailbox->storage,
					&mail_storage_callbacks, client);

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

	if (!mailbox->get_status(mailbox, STATUS_MESSAGES, &status)) {
		client_destroy(client);
		return NULL;
	}
	client->messages_count = status.messages;

	return client;
}

void client_destroy(struct client *client)
{
	o_stream_flush(client->output);

	if (client->mailbox != NULL)
		client->mailbox->close(client->mailbox);
	mail_storage_destroy(client->storage);

	if (client->deleted_bitmask != NULL)
		i_free(client->deleted_bitmask);

	io_remove(client->io);

	i_stream_unref(client->input);
	o_stream_unref(client->output);

	i_free(client);

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

void client_disconnect(struct client *client)
{
	o_stream_flush(client->output);

	i_stream_close(client->input);
	o_stream_close(client->output);
}

void client_send_line(struct client *client, const char *fmt, ...)
{
	va_list va;

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

	va_start(va, fmt);
	(void)o_stream_send_str(client->output, t_strdup_vprintf(fmt, va));
	(void)o_stream_send(client->output, "\r\n", 2);
	va_end(va);
}

static void client_input(void *context)
{
	struct client *client = context;
	char *line, *args;

	client->last_input = ioloop_time;

	switch (i_stream_read(client->input)) {
	case -1:
		/* disconnected */
		client_destroy(client);
		return;
	case -2:
		/* line too long, kill it */
		client_send_line(client, "-ERR Input line too long.");
		client_destroy(client);
		return;
	}

	o_stream_cork(client->output);
	while (!client->output->closed &&
	       (line = i_stream_next_line(client->input)) != NULL) {
		args = strchr(line, ' ');
		if (args == NULL)
			args = "";
		else
			*args++ = '\0';

		client_command_execute(client, line, args);
	}
	o_stream_flush(client->output);

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

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

	if (ioloop_time - my_client->last_input >= CLIENT_IDLE_TIMEOUT) {
		client_send_line(my_client,
				 "-ERR 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_send_line(my_client, "-ERR Server shutting down.");
		client_destroy(my_client);
	}

	timeout_remove(to_idle);
}

--- NEW FILE: client.h ---
#ifndef __CLIENT_H
#define __CLIENT_H

struct mail_storage;

struct client {
	int socket;
	struct io *io;
	struct istream *input;
	struct ostream *output;

	struct mail_storage *storage;
	struct mailbox *mailbox;

	time_t last_input;
	unsigned int bad_counter;

	unsigned int messages_count;
	unsigned char *deleted_bitmask;

	unsigned int deleted:1;
};

/* Create new client with specified input/output handles. socket specifies
   if the handle is a socket. */
struct client *client_create(int hin, int hout, struct mailbox *mailbox);
void client_destroy(struct client *client);

/* Disconnect client connection */
void client_disconnect(struct client *client);

/* Send a line of data to client */
void client_send_line(struct client *client, const char *fmt, ...)
	__attr_format__(2, 3);

void clients_init(void);
void clients_deinit(void);

#endif

--- NEW FILE: commands.c ---
/* Copyright (C) 2002 Timo Sirainen */

#include "common.h"
#include "str.h"
#include "message-size.h"
#include "mail-storage.h"
#include "commands.h"

#define MSGS_BITMASK_SIZE(client) \
	((client->messages_count + (CHAR_BIT-1)) / CHAR_BIT)

static void client_send_storage_error(struct client *client)
{
	const char *error;

	error = client->storage->get_last_error(client->storage, NULL);
	client_send_line(client, "-ERR %s", error);
}

static const char *get_msgnum(struct client *client, const char *args,
			      unsigned int *msgnum)
{
	unsigned int num, last_num;

	num = 0;
	while (*args != '\0' && *args != ' ') {
		if (*args < '0' || *args > '9') {
			client_send_line(client,
				"-ERR Invalid message number: %s", args);
			return NULL;
		}

		last_num = num;
		num = num*10 + (*args - '0');
		if (num < last_num) {
			client_send_line(client,
				"-ERR Message number too large: %s", args);
			return NULL;
		}
		args++;
	}

	if (num > client->messages_count) {
		client_send_line(client,
				 "-ERR There's only %u messages.",
				 client->messages_count);
		return NULL;
	}

	if (client->deleted) {
		if (client->deleted_bitmask[num / CHAR_BIT] &
		    (1 << (num % CHAR_BIT))) {
			client_send_line(client, "-ERR Message is deleted.");
			return NULL;
		}
	}

	while (*args == ' ') args++;

	*msgnum = num;
	return args;
}

static const char *get_size(struct client *client, const char *args,
			    uoff_t *size)
{
	uoff_t num, last_num;

	num = 0;
	while (*args != '\0' && *args != ' ') {
		if (*args < '0' || *args > '9') {
			client_send_line(client, "-ERR Invalid size: %s",
					 args);
			return NULL;
		}

		last_num = num;
		num = num*10 + (*args - '0');
		if (num < last_num) {
			client_send_line(client, "-ERR Size too large: %s",
					 args);
			return NULL;
		}
		args++;
	}

	while (*args == ' ') args++;

	*size = num;
	return args;
}

static void cmd_dele(struct client *client, const char *args)
{
	unsigned int msgnum;

	if (get_msgnum(client, args, &msgnum) == NULL)
		return;

	if (!client->deleted) {
		client->deleted_bitmask = i_malloc(MSGS_BITMASK_SIZE(client));
		client->deleted = TRUE;
	}

	client->deleted_bitmask[msgnum / CHAR_BIT] |= 1 << (msgnum % CHAR_BIT);
	client_send_line(client, "+OK Marked to be deleted.");
}

static void list_sizes(struct client *client, unsigned int message)
{
	struct mail_fetch_context *ctx;
	struct mail *mail;
	const char *messageset;
	int found = FALSE;

	if (client->messages_count == 0 && message == 0)
		return;

	messageset = message == 0 ?
		t_strdup_printf("1:%u", client->messages_count) :
		t_strdup_printf("%u", message);

	ctx = client->mailbox->fetch_init(client->mailbox, MAIL_FETCH_SIZE,
					  NULL, messageset, FALSE);
	if (ctx == NULL) {
		client_send_storage_error(client);
		return;
	}

	while ((mail = client->mailbox->fetch_next(ctx)) != NULL) {
		uoff_t size = mail->get_size(mail);

		client_send_line(client, message == 0 ? "%u %"PRIuUOFF_T :
				 "+OK %u %"PRIuUOFF_T, mail->seq, size);
		found = TRUE;
	}

	(void)client->mailbox->fetch_deinit(ctx, NULL);

	if (!found && message != 0)
		client_send_line(client, "-ERR Message not found.");
}

static void cmd_list(struct client *client, const char *args)
{
	if (*args == '\0') {
		client_send_line(client, "+OK %u messages:",
				 client->messages_count);
		list_sizes(client, 0);
		client_send_line(client, ".");
	} else {
		unsigned int msgnum;

		if (get_msgnum(client, args, &msgnum) != NULL)
			list_sizes(client, msgnum);
	}
}

static void cmd_noop(struct client *client, const char *args __attr_unused__)
{
	client_send_line(client, "+OK");
}

static void cmd_quit(struct client *client, const char *args __attr_unused__)
{
	unsigned int first, last, msgnum, max, i, j;
	struct mail_full_flags flags;
	string_t *set;

	if (!client->deleted) {
		client_send_line(client, "+OK Logging out.");
		client_disconnect(client);
		return;
	}

	set = t_str_new(1024);
	first = last = 0; msgnum = 1;
	max = MSGS_BITMASK_SIZE(client);
	for (i = 0; i < max; i++) {
		if (client->deleted_bitmask[i] == 0) {
                        msgnum += CHAR_BIT;
			continue;
		}

		for (j = 0; j < CHAR_BIT; j++, msgnum++) {
			if ((client->deleted_bitmask[i] & (1 << j)) == 0)
				continue;

			if (last == msgnum-1 && last != 0)
				last++;
			else {
				if (first == last)
					str_printfa(set, ",%u", first);
				else
					str_printfa(set, ",%u:%u", first, last);
				first = last = msgnum;
			}
		}
	}

	if (first != 0) {
		if (first == last)
			str_printfa(set, ",%u", first);
		else
			str_printfa(set, ",%u:%u", first, last);
	}

	if (str_len(set) == 0)
		client_send_line(client, "+OK Logging out.");
	else if (client->mailbox->update_flags(client->mailbox, str_c(set),
					       FALSE, &flags, MODIFY_ADD,
					       FALSE, NULL) &&
		 client->mailbox->expunge(client->mailbox, FALSE))
		client_send_line(client, "+OK Logging out, messages deleted.");
	else
		client_send_storage_error(client);

	client_disconnect(client);
}

static void fetch(struct client *client, unsigned int msgnum, uoff_t max_lines)
{
	struct mail_fetch_context *ctx;
	struct mail *mail;
	struct istream *stream;
	struct message_size hdr_size, body_size;

	ctx = client->mailbox->fetch_init(client->mailbox,
					  MAIL_FETCH_STREAM_HEADER |
					  MAIL_FETCH_STREAM_BODY,
					  NULL, t_strdup_printf("%u", msgnum),
					  FALSE);
	if (ctx == NULL) {
		client_send_storage_error(client);
		return;
	}

	mail = client->mailbox->fetch_next(ctx);
	if (mail == NULL)
		client_send_line(client, "-ERR Message not found.");
	else {
		stream = mail->get_stream(mail, &hdr_size, &body_size);
		message_size_add(&body_size, &hdr_size);

		client_send_line(client, "+OK %"PRIuUOFF_T" octets",
				 body_size.virtual_size);

		// FIXME: "." lines needs to be escaped
		// FIXME: and send only max_lines
		message_send(client->output, stream, &body_size, 0, (uoff_t)-1);
	}

	(void)client->mailbox->fetch_deinit(ctx, NULL);
}

static void cmd_retr(struct client *client, const char *args)
{
	unsigned int msgnum;

	if (get_msgnum(client, args, &msgnum) != NULL)
		fetch(client, msgnum, (uoff_t)-1);
}

static void cmd_rset(struct client *client, const char *args __attr_unused__)
{
	if (client->deleted) {
		client->deleted = FALSE;
		memset(client->deleted_bitmask, 0, MSGS_BITMASK_SIZE(client));
	}

	client_send_line(client, "+OK");
}

static void cmd_stat(struct client *client, const char *args __attr_unused__)
{
	struct mail_fetch_context *ctx;
	struct mail *mail;
	uoff_t size, total_size;
	const char *messageset;

	if (client->messages_count == 0) {
		client_send_line(client, "+OK 0 0");
		return;
	}

	messageset = t_strdup_printf("1:%u", client->messages_count);
	ctx = client->mailbox->fetch_init(client->mailbox, MAIL_FETCH_SIZE,
					  NULL, messageset, FALSE);
	if (ctx == NULL) {
		client_send_storage_error(client);
		return;
	}

	total_size = 0;
	while ((mail = client->mailbox->fetch_next(ctx)) != NULL) {
		size = mail->get_size(mail);
		if (size != (uoff_t)-1)
			total_size += size;
	}

	(void)client->mailbox->fetch_deinit(ctx, NULL);

	client_send_line(client, "+OK %u %"PRIuUOFF_T,
			 client->messages_count, total_size);
}

static void cmd_top(struct client *client, const char *args)
{
	unsigned int msgnum;
	uoff_t max_lines;

	if (get_msgnum(client, args, &msgnum) != NULL &&
	    get_size(client, args, &max_lines))
		fetch(client, msgnum, max_lines);
}

static void list_uids(struct client *client, unsigned int message)
{
	struct mail_fetch_context *ctx;
	struct mail *mail;
	const char *messageset;
	int found = FALSE;

	if (client->messages_count == 0 && message == 0)
		return;

	messageset = message == 0 ?
		t_strdup_printf("1:%u", client->messages_count) :
		t_strdup_printf("%u", message);

	ctx = client->mailbox->fetch_init(client->mailbox, 0,
					  NULL, messageset, FALSE);
	if (ctx == NULL) {
		client_send_storage_error(client);
		return;
	}

	while ((mail = client->mailbox->fetch_next(ctx)) != NULL) {
		client_send_line(client, message == 0 ?
				 "%u %u" : "+OK %u %u", mail->seq, mail->uid);
		found = TRUE;
	}

	(void)client->mailbox->fetch_deinit(ctx, NULL);

	if (!found && message != 0)
		client_send_line(client, "-ERR Message not found.");
}

static void cmd_uidl(struct client *client, const char *args)
{
	if (*args == '\0') {
		client_send_line(client, "+OK");
		list_uids(client, 0);
		client_send_line(client, ".");
	} else {
		unsigned int msgnum;

		if (get_msgnum(client, args, &msgnum) != NULL)
			list_uids(client, msgnum);
	}
}

void client_command_execute(struct client *client,
			    const char *name, const char *args)
{
	/* keep the command uppercased */
	name = str_ucase(t_strdup_noconst(name));

	while (*args == ' ') args++;

	switch (*name) {
	case 'D':
		if (strcmp(name, "DELE") == 0) {
			cmd_dele(client, args);
			return;
		}
		break;
	case 'L':
		if (strcmp(name, "LIST") == 0) {
			cmd_list(client, args);
			return;
		}
		break;
	case 'N':
		if (strcmp(name, "NOOP") == 0) {
			cmd_noop(client, args);
			return;
		}
		break;
	case 'Q':
		if (strcmp(name, "QUIT") == 0) {
			cmd_quit(client, args);
			return;
		}
		break;
	case 'R':
		if (strcmp(name, "RETR") == 0) {
			cmd_retr(client, args);
			return;
		}
		if (strcmp(name, "RSET") == 0) {
			cmd_rset(client, args);
			return;
		}
		break;
	case 'S':
		if (strcmp(name, "STAT") == 0) {
			cmd_stat(client, args);
			return;
		}
		break;
	case 'T':
		if (strcmp(name, "TOP") == 0) {
			cmd_top(client, args);
			return;
		}
		break;
	case 'U':
		if (strcmp(name, "UIDL") == 0) {
			cmd_uidl(client, args);
			return;
		}
		break;
	}

	client_send_line(client, "-ERR Unknown command: %s", name);
}

--- NEW FILE: commands.h ---
#ifndef __COMMANDS_H
#define __COMMANDS_H

void client_command_execute(struct client *client,
			    const char *name, const char *args);

#endif

--- NEW FILE: common.h ---
#ifndef __COMMON_H
#define __COMMON_H

#include "lib.h"
#include "client.h"

/* max. number of IMAP argument elements to accept. The maximum memory usage
   for command from user is around MAX_INBUF_SIZE * MAX_IMAP_ARG_ELEMENTS */
#define MAX_IMAP_ARG_ELEMENTS 128

extern struct ioloop *ioloop;

#endif

--- NEW FILE: mail-storage-callbacks.c ---
/* Copyright (C) 2002 Timo Sirainen */

#include "common.h"
#include "imap-util.h"
#include "mail-storage.h"

static void alert_no_diskspace(struct mailbox *mailbox __attr_unused__,
			       void *context __attr_unused__)
{
}

static void notify_ok(struct mailbox *mailbox __attr_unused__,
		      const char *text __attr_unused__,
		      void *context __attr_unused__)
{
}

static void notify_no(struct mailbox *mailbox __attr_unused__,
		      const char *text __attr_unused__,
		      void *context __attr_unused__)
{
}

static void expunge(struct mailbox *mailbox __attr_unused__,
		    unsigned int seq __attr_unused__,
		    void *context __attr_unused__)
{
}

static void update_flags(struct mailbox *mailbox __attr_unused__,
			 unsigned int seq __attr_unused__,
			 unsigned int uid __attr_unused__,
			 enum mail_flags flags __attr_unused__,
			 const char *custom_flags[] __attr_unused__,
			 unsigned int custom_flags_count __attr_unused__,
			 void *context __attr_unused__)
{
}

static void new_messages(struct mailbox *mailbox __attr_unused__,
			 unsigned int messages_count __attr_unused__,
			 unsigned int recent_count __attr_unused__,
			 void *context __attr_unused__)
{
}

static void new_custom_flags(struct mailbox *mailbox __attr_unused__,
			     const char *custom_flags[] __attr_unused__,
			     unsigned int custom_flags_count __attr_unused__,
			     void *context __attr_unused__)
{
}

struct mail_storage_callbacks mail_storage_callbacks = {
	alert_no_diskspace,
	notify_ok,
	notify_no,
	expunge,
	update_flags,
	new_messages,
	new_custom_flags
};

--- NEW FILE: main.c ---
/* Copyright (C) 2002-2003 Timo Sirainen */

#include "common.h"
#include "ioloop.h"
#include "lib-signals.h"
#include "restrict-access.h"
#include "fd-close-on-exec.h"
#include "process-title.h"
#include "mail-storage.h"

#include <stdlib.h>
#include <syslog.h>

#define IS_STANDALONE() \
        (getenv("LOGGED_IN") == NULL)

struct ioloop *ioloop;
static char log_prefix[128]; /* syslog() needs this to be permanent */

static void sig_quit(int signo __attr_unused__)
{
	io_loop_stop(ioloop);
}

static void open_logfile(void)
{
	const char *user;

	user = getenv("USER");
	if (user == NULL) user = "??";
	if (strlen(user) >= sizeof(log_prefix)-6) {
		/* quite a long user name, cut it */
		user = t_strndup(user, sizeof(log_prefix)-6-2);
		user = t_strconcat(user, "..", NULL);
	}
	i_snprintf(log_prefix, sizeof(log_prefix), "pop3(%s)", user);

	if (getenv("USE_SYSLOG") != NULL)
		i_set_failure_syslog(log_prefix, LOG_NDELAY, LOG_MAIL);
	else {
		/* log to file or stderr */
		i_set_failure_file(getenv("LOGFILE"), log_prefix);
	}

	if (getenv("INFOLOGFILE") != NULL)
		i_set_info_file(getenv("INFOLOGFILE"));

	i_set_failure_timestamp_format(getenv("LOGSTAMP"));
}

static void drop_privileges(void)
{
	/* Log file or syslog opening probably requires roots */
	open_logfile();

	restrict_access_by_env(!IS_STANDALONE());
}

static void main_init(void)
{
	struct client *client;
	struct mail_storage *storage;
	struct mailbox *mailbox;
	const char *mail;

	lib_init_signals(sig_quit);

	if (getenv("USER") == NULL)
		i_fatal("USER environment missing");

	mail_storage_register_all();
	clients_init();

	mail = getenv("MAIL");
	if (mail == NULL) {
		/* support also maildir-specific environment */
		mail = getenv("MAILDIR");
		if (mail != NULL)
			mail = t_strconcat("maildir:", mail, NULL);
	}

	storage = mail_storage_create_with_data(mail, getenv("USER"));
	if (storage == NULL) {
		/* failed */
		if (mail != NULL && *mail != '\0')
			i_fatal("Failed to create storage with data: %s", mail);
		else {
			const char *home;

			home = getenv("HOME");
			if (home == NULL) home = "not set";

			i_fatal("MAIL environment missing and "
				"autodetection failed (home %s)", home);
		}
	}

	mailbox = storage->open_mailbox(storage, "INBOX", FALSE, FALSE);
	if (mailbox == NULL)
		i_fatal("No INBOX for user");

	client = client_create(0, 1, mailbox);
}

static void main_deinit(void)
{
	/* warn about being killed because of some signal, except SIGINT (^C)
	   which is too common at least while testing :) */
	if (lib_signal_kill != 0 && lib_signal_kill != 2)
		i_warning("Killed with signal %d", lib_signal_kill);

	clients_deinit();

	closelog();
}

int main(int argc __attr_unused__, char *argv[], char *envp[])
{
#ifdef DEBUG
	if (getenv("LOGGED_IN") != NULL) {
		fd_debug_verify_leaks(3, 1024);
		putenv("DISABLE_ALARMHUP=1"); /* annoying when debugging */
	}
#endif
	/* NOTE: we start rooted, so keep the code minimal until
	   restrict_access_by_env() is called */
	lib_init();
	drop_privileges();

        process_title_init(argv, envp);
	ioloop = io_loop_create(system_pool);

	main_init();
        io_loop_run(ioloop);
	main_deinit();

	io_loop_destroy(ioloop);
	lib_deinit();

	return 0;
}




More information about the dovecot-cvs mailing list