Mercurial > dovecot > original-hg > dovecot-1.2
view src/pop3/commands.c @ 6429:65c69a53a7be HEAD
Replaced my Copyright notices. The year range always ends with 2007 now.
My name was replaced with "Dovecot authors". In many cases I didn't really
even own the copyright, so this is more correct.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sun, 16 Sep 2007 14:34:22 +0300 |
parents | 6a64e64fa3a3 |
children | 1a3604c8ee05 |
line wrap: on
line source
/* Copyright (c) 2002-2007 Dovecot authors, see the included COPYING file */ #include "common.h" #include "istream.h" #include "ostream.h" #include "str.h" #include "var-expand.h" #include "message-size.h" #include "mail-storage.h" #include "mail-search.h" #include "capability.h" #include "commands.h" #define MSGS_BITMASK_SIZE(client) \ ((client->messages_count + (CHAR_BIT-1)) / CHAR_BIT) 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 == 0 || num > client->messages_count) { client_send_line(client, "-ERR There's no message %u.", num); return NULL; } num--; 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 int cmd_capa(struct client *client, const char *args ATTR_UNUSED) { client_send_line(client, "+OK\r\n"POP3_CAPABILITY_REPLY"."); return 1; } static int cmd_dele(struct client *client, const char *args) { unsigned int msgnum; if (get_msgnum(client, args, &msgnum) == NULL) return 0; 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->deleted_count++; client->deleted_size += client->message_sizes[msgnum]; client_send_line(client, "+OK Marked to be deleted."); return 1; } struct cmd_list_context { unsigned int msgnum; }; static void cmd_list_callback(struct client *client) { struct cmd_list_context *ctx = client->cmd_context; int ret = 1; for (; ctx->msgnum != client->messages_count; ctx->msgnum++) { if (ret == 0) { /* buffer full */ return; } if (client->deleted) { if (client->deleted_bitmask[ctx->msgnum / CHAR_BIT] & (1 << (ctx->msgnum % CHAR_BIT))) continue; } ret = client_send_line(client, "%u %"PRIuUOFF_T, ctx->msgnum+1, client->message_sizes[ctx->msgnum]); if (ret < 0) break; } client_send_line(client, "."); i_free(ctx); client->cmd = NULL; } static int cmd_list(struct client *client, const char *args) { struct cmd_list_context *ctx; if (*args == '\0') { ctx = i_new(struct cmd_list_context, 1); client_send_line(client, "+OK %u messages:", client->messages_count - client->deleted_count); client->cmd = cmd_list_callback; client->cmd_context = ctx; cmd_list_callback(client); } else { unsigned int msgnum; if (get_msgnum(client, args, &msgnum) == NULL) return 0; client_send_line(client, "+OK %u %"PRIuUOFF_T, msgnum+1, client->message_sizes[msgnum]); } return 1; } static int cmd_last(struct client *client, const char *args ATTR_UNUSED) { client_send_line(client, "+OK %u", client->last_seen); return 1; } static int cmd_noop(struct client *client, const char *args ATTR_UNUSED) { client_send_line(client, "+OK"); return 1; } static bool expunge_mails(struct client *client) { struct mail_search_arg search_arg; struct mail_search_seqset seqset; struct mail_search_context *ctx; struct mail *mail; uint32_t idx; if (client->deleted_bitmask == NULL) return TRUE; if (mailbox_is_readonly(client->mailbox)) { /* silently ignore */ return TRUE; } memset(&seqset, 0, sizeof(seqset)); memset(&search_arg, 0, sizeof(search_arg)); seqset.seq1 = 1; seqset.seq2 = client->messages_count; search_arg.type = SEARCH_SEQSET; search_arg.value.seqset = &seqset; ctx = mailbox_search_init(client->trans, NULL, &search_arg, NULL); mail = mail_alloc(client->trans, 0, NULL); while (mailbox_search_next(ctx, mail) > 0) { idx = mail->seq - 1; if ((client->deleted_bitmask[idx / CHAR_BIT] & 1 << (idx % CHAR_BIT)) != 0) { mail_expunge(mail); client->expunged_count++; } } mail_free(&mail); return mailbox_search_deinit(&ctx) == 0; } static int cmd_quit(struct client *client, const char *args ATTR_UNUSED) { if (client->deleted) { if (!expunge_mails(client)) { client_send_storage_error(client); client_disconnect(client, "Storage error during logout."); return 1; } } if (mailbox_transaction_commit(&client->trans, MAILBOX_SYNC_FLAG_FULL_WRITE) < 0) { client_send_storage_error(client); client_disconnect(client, "Storage error during logout."); return 1; } if (!client->deleted) client_send_line(client, "+OK Logging out."); else client_send_line(client, "+OK Logging out, messages deleted."); client_disconnect(client, "Logged out"); return 1; } struct fetch_context { struct mail_search_context *search_ctx; struct mail *mail; struct istream *stream; uoff_t body_lines; struct mail_search_arg search_arg; struct mail_search_seqset seqset; unsigned char last; bool cr_skipped, in_body; }; static void fetch_deinit(struct fetch_context *ctx) { (void)mailbox_search_deinit(&ctx->search_ctx); mail_free(&ctx->mail); i_free(ctx); } static void fetch_callback(struct client *client) { struct fetch_context *ctx = client->cmd_context; const unsigned char *data; unsigned char add; size_t i, size; int ret; while ((ctx->body_lines > 0 || !ctx->in_body) && i_stream_read_data(ctx->stream, &data, &size, 0) > 0) { if (size > 4096) size = 4096; add = '\0'; for (i = 0; i < size; i++) { if ((data[i] == '\r' || data[i] == '\n') && !ctx->in_body) { if (i == 0 && (ctx->last == '\0' || ctx->last == '\n')) ctx->in_body = TRUE; else if (i > 0 && data[i-1] == '\n') ctx->in_body = TRUE; } if (data[i] == '\n') { if ((i == 0 && ctx->last != '\r') || (i > 0 && data[i-1] != '\r')) { /* missing CR */ add = '\r'; break; } if (ctx->in_body) { if (--ctx->body_lines == 0) { i++; break; } } } else if (data[i] == '.' && ((i == 0 && ctx->last == '\n') || (i > 0 && data[i-1] == '\n'))) { /* escape the dot */ add = '.'; break; } else if (data[i] == '\0' && (client_workarounds & WORKAROUND_OUTLOOK_NO_NULS) != 0) { add = 0x80; break; } } if (i > 0) { if (o_stream_send(client->output, data, i) < 0) break; ctx->last = data[i-1]; i_stream_skip(ctx->stream, i); } if (o_stream_get_buffer_used_size(client->output) >= 4096) { if ((ret = o_stream_flush(client->output)) < 0) break; if (ret == 0) { /* continue later */ return; } } if (add != '\0') { if (o_stream_send(client->output, &add, 1) < 0) break; ctx->last = add; if (add == 0x80) i_stream_skip(ctx->stream, 1); } } if (ctx->last != '\n') { /* didn't end with CRLF */ (void)o_stream_send(client->output, "\r\n", 2); } if (!ctx->in_body && (client_workarounds & WORKAROUND_OE_NS_EOH) != 0) { /* Add the missing end of headers line. */ (void)o_stream_send(client->output, "\r\n", 2); } *client->byte_counter += client->output->offset - client->byte_counter_offset; client->byte_counter = NULL; client_send_line(client, "."); fetch_deinit(ctx); client->cmd = NULL; } static void fetch(struct client *client, unsigned int msgnum, uoff_t body_lines) { struct fetch_context *ctx; ctx = i_new(struct fetch_context, 1); ctx->seqset.seq1 = ctx->seqset.seq2 = msgnum+1; ctx->search_arg.type = SEARCH_SEQSET; ctx->search_arg.value.seqset = &ctx->seqset; ctx->search_ctx = mailbox_search_init(client->trans, NULL, &ctx->search_arg, NULL); ctx->mail = mail_alloc(client->trans, MAIL_FETCH_STREAM_HEADER | MAIL_FETCH_STREAM_BODY, NULL); if (mailbox_search_next(ctx->search_ctx, ctx->mail) <= 0 || mail_get_stream(ctx->mail, NULL, NULL, &ctx->stream) < 0) { client_send_line(client, "-ERR Message not found."); fetch_deinit(ctx); return; } if (body_lines == (uoff_t)-1 && !no_flag_updates) { if ((mail_get_flags(ctx->mail) & MAIL_SEEN) == 0) { /* mark the message seen with RETR command */ (void)mail_update_flags(ctx->mail, MODIFY_ADD, MAIL_SEEN); } } ctx->body_lines = body_lines; if (body_lines == (uoff_t)-1) { client_send_line(client, "+OK %"PRIuUOFF_T" octets", client->message_sizes[msgnum]); } else { client_send_line(client, "+OK"); ctx->body_lines++; /* internally we count the empty line too */ } client->cmd = fetch_callback; client->cmd_context = ctx; fetch_callback(client); } static int cmd_retr(struct client *client, const char *args) { unsigned int msgnum; if (get_msgnum(client, args, &msgnum) == NULL) return 0; if (client->last_seen <= msgnum) client->last_seen = msgnum+1; client->retr_count++; client->byte_counter = &client->retr_bytes; client->byte_counter_offset = client->output->offset; fetch(client, msgnum, (uoff_t)-1); return 1; } static int cmd_rset(struct client *client, const char *args ATTR_UNUSED) { struct mail_search_context *search_ctx; struct mail *mail; struct mail_search_arg search_arg; struct mail_search_seqset seqset; client->last_seen = 0; if (client->deleted) { client->deleted = FALSE; memset(client->deleted_bitmask, 0, MSGS_BITMASK_SIZE(client)); client->deleted_count = 0; client->deleted_size = 0; } if (enable_last_command) { /* remove all \Seen flags (as specified by RFC 1460) */ memset(&seqset, 0, sizeof(seqset)); memset(&search_arg, 0, sizeof(search_arg)); seqset.seq1 = 1; seqset.seq2 = client->messages_count; search_arg.type = SEARCH_SEQSET; search_arg.value.seqset = &seqset; search_ctx = mailbox_search_init(client->trans, NULL, &search_arg, NULL); mail = mail_alloc(client->trans, 0, NULL); while (mailbox_search_next(search_ctx, mail) > 0) mail_update_flags(mail, MODIFY_REMOVE, MAIL_SEEN); mail_free(&mail); (void)mailbox_search_deinit(&search_ctx); } else { /* forget all our seen flag updates. FIXME: is this needed? it loses data added to cache file */ mailbox_transaction_rollback(&client->trans); client->trans = mailbox_transaction_begin(client->mailbox, 0); } client_send_line(client, "+OK"); return 1; } static int cmd_stat(struct client *client, const char *args ATTR_UNUSED) { client_send_line(client, "+OK %u %"PRIuUOFF_T, client-> messages_count - client->deleted_count, client->total_size - client->deleted_size); return 1; } static int cmd_top(struct client *client, const char *args) { unsigned int msgnum; uoff_t max_lines; args = get_msgnum(client, args, &msgnum); if (args == NULL) return 0; if (get_size(client, args, &max_lines) == NULL) return 0; client->top_count++; client->byte_counter = &client->top_bytes; client->byte_counter_offset = client->output->offset; fetch(client, msgnum, max_lines); return 1; } struct cmd_uidl_context { struct mail_search_context *search_ctx; struct mail *mail; unsigned int message; struct mail_search_arg search_arg; struct mail_search_seqset seqset; }; static bool list_uids_iter(struct client *client, struct cmd_uidl_context *ctx) { static struct var_expand_table static_tab[] = { { 'v', NULL }, { 'u', NULL }, { 'm', NULL }, { 'f', NULL }, { '\0', NULL } }; struct var_expand_table *tab; string_t *str; const char *uidl; int ret; bool found = FALSE; tab = t_malloc(sizeof(static_tab)); memcpy(tab, static_tab, sizeof(static_tab)); tab[0].value = t_strdup_printf("%u", client->uid_validity); str = str_new(default_pool, 128); while (mailbox_search_next(ctx->search_ctx, ctx->mail) > 0) { if (client->deleted) { uint32_t idx = ctx->mail->seq - 1; if (client->deleted_bitmask[idx / CHAR_BIT] & (1 << (idx % CHAR_BIT))) continue; } found = TRUE; t_push(); if ((uidl_keymask & UIDL_UID) != 0) tab[1].value = dec2str(ctx->mail->uid); if ((uidl_keymask & UIDL_MD5) != 0) { if (mail_get_special(ctx->mail, MAIL_FETCH_HEADER_MD5, &tab[2].value) < 0 || *tab[2].value == '\0') { /* broken */ i_fatal("UIDL: Header MD5 not found"); } } if ((uidl_keymask & UIDL_FILE_NAME) != 0) { if (mail_get_special(ctx->mail, MAIL_FETCH_UIDL_FILE_NAME, &tab[3].value) < 0 || *tab[3].value == '\0') { /* broken */ i_fatal("UIDL: File name not found"); } } str_truncate(str, 0); str_printfa(str, ctx->message == 0 ? "%u " : "+OK %u ", ctx->mail->seq); if (reuse_xuidl && mail_get_first_header(ctx->mail, "X-UIDL", &uidl) > 0) str_append(str, uidl); else var_expand(str, uidl_format, tab); ret = client_send_line(client, "%s", str_c(str)); t_pop(); if (ret < 0) break; if (ret == 0 && ctx->message == 0) { /* output is being buffered, continue when there's more space */ str_free(&str); return 0; } } str_free(&str); /* finished */ mail_free(&ctx->mail); (void)mailbox_search_deinit(&ctx->search_ctx); client->cmd = NULL; if (ctx->message == 0) client_send_line(client, "."); i_free(ctx); return found; } static void cmd_uidl_callback(struct client *client) { struct cmd_uidl_context *ctx = client->cmd_context; (void)list_uids_iter(client, ctx); } static struct cmd_uidl_context * cmd_uidl_init(struct client *client, unsigned int message) { struct cmd_uidl_context *ctx; enum mail_fetch_field wanted_fields; ctx = i_new(struct cmd_uidl_context, 1); if (message == 0) { ctx->seqset.seq1 = 1; ctx->seqset.seq2 = client->messages_count; } else { ctx->message = message; ctx->seqset.seq1 = ctx->seqset.seq2 = message; } ctx->search_arg.type = SEARCH_SEQSET; ctx->search_arg.value.seqset = &ctx->seqset; wanted_fields = 0; if ((uidl_keymask & UIDL_MD5) != 0) wanted_fields |= MAIL_FETCH_HEADER_MD5; ctx->search_ctx = mailbox_search_init(client->trans, NULL, &ctx->search_arg, NULL); ctx->mail = mail_alloc(client->trans, wanted_fields, NULL); if (message == 0) { client->cmd = cmd_uidl_callback; client->cmd_context = ctx; } return ctx; } static int cmd_uidl(struct client *client, const char *args) { struct cmd_uidl_context *ctx; if (*args == '\0') { client_send_line(client, "+OK"); ctx = cmd_uidl_init(client, 0); list_uids_iter(client, ctx); } else { unsigned int msgnum; if (get_msgnum(client, args, &msgnum) == NULL) return 0; ctx = cmd_uidl_init(client, msgnum+1); if (!list_uids_iter(client, ctx)) client_send_line(client, "-ERR Message not found."); } return 1; } int client_command_execute(struct client *client, const char *name, const char *args) { /* keep the command uppercased */ name = t_str_ucase(name); while (*args == ' ') args++; switch (*name) { case 'C': if (strcmp(name, "CAPA") == 0) return cmd_capa(client, args); break; case 'D': if (strcmp(name, "DELE") == 0) return cmd_dele(client, args); break; case 'L': if (strcmp(name, "LIST") == 0) return cmd_list(client, args); if (strcmp(name, "LAST") == 0 && enable_last_command) return cmd_last(client, args); break; case 'N': if (strcmp(name, "NOOP") == 0) return cmd_noop(client, args); break; case 'Q': if (strcmp(name, "QUIT") == 0) return cmd_quit(client, args); break; case 'R': if (strcmp(name, "RETR") == 0) return cmd_retr(client, args); if (strcmp(name, "RSET") == 0) return cmd_rset(client, args); break; case 'S': if (strcmp(name, "STAT") == 0) return cmd_stat(client, args); break; case 'T': if (strcmp(name, "TOP") == 0) return cmd_top(client, args); break; case 'U': if (strcmp(name, "UIDL") == 0) return cmd_uidl(client, args); break; } client_send_line(client, "-ERR Unknown command: %s", name); return -1; }