diff src/pop3/commands.c @ 1043:cacabd33c68a HEAD

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.
author Timo Sirainen <tss@iki.fi>
date Mon, 27 Jan 2003 07:45:47 +0200
parents
children 50d258907c99
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pop3/commands.c	Mon Jan 27 07:45:47 2003 +0200
@@ -0,0 +1,428 @@
+/* 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);
+}