Mercurial > dovecot > core-2.2
view src/pop3/client.c @ 5500:4862cb37106c HEAD
Moved namespace handling to lib-storage. Beginnings of namespace support for
non-IMAP parts of Dovecot.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Tue, 03 Apr 2007 11:34:27 +0300 |
parents | bbe25ef34f03 |
children | 74d3236313c1 |
line wrap: on
line source
/* Copyright (C) 2002 Timo Sirainen */ #include "common.h" #include "buffer.h" #include "ioloop.h" #include "network.h" #include "istream.h" #include "ostream.h" #include "str.h" #include "var-expand.h" #include "mail-storage.h" #include "commands.h" #include "mail-search.h" #include "mail-namespace.h" #include <stdlib.h> #include <unistd.h> /* max. length of input command line (spec says 512) */ #define MAX_INBUF_SIZE 2048 /* Stop reading input when output buffer has this many bytes. Once the buffer size has dropped to half of it, start reading input again. */ #define OUTBUF_THROTTLE_SIZE 4096 /* If we can't send anything for 10 minutes, disconnect the client */ #define CLIENT_OUTPUT_TIMEOUT (10*60) /* 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 (10*60) 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(struct client *client); static int client_output(struct client *client); static int sync_mailbox(struct mailbox *box, struct mailbox_status *status) { struct mailbox_sync_context *ctx; struct mailbox_sync_rec sync_rec; ctx = mailbox_sync_init(box, MAILBOX_SYNC_FLAG_FULL_READ); while (mailbox_sync_next(ctx, &sync_rec) > 0) ; return mailbox_sync_deinit(&ctx, STATUS_UIDVALIDITY, status); } static int init_mailbox(struct client *client) { struct mail_search_arg search_arg; struct mailbox_transaction_context *t; struct mail_search_context *ctx; struct mailbox_status status; struct mail *mail; buffer_t *message_sizes_buf; int i; bool failed; message_sizes_buf = buffer_create_dynamic(default_pool, 512); memset(&search_arg, 0, sizeof(search_arg)); search_arg.type = SEARCH_ALL; for (i = 0; i < 2; i++) { if (sync_mailbox(client->mailbox, &status) < 0) { client_send_storage_error(client); break; } client->uid_validity = status.uidvalidity; t = mailbox_transaction_begin(client->mailbox, 0); ctx = mailbox_search_init(t, NULL, &search_arg, NULL); client->last_seen = 0; client->total_size = 0; buffer_set_used_size(message_sizes_buf, 0); failed = FALSE; mail = mail_alloc(t, MAIL_FETCH_VIRTUAL_SIZE, NULL); while (mailbox_search_next(ctx, mail) > 0) { uoff_t size = mail_get_virtual_size(mail); if (size == (uoff_t)-1) { failed = TRUE; break; } if ((mail_get_flags(mail) & MAIL_SEEN) != 0) client->last_seen = mail->seq; client->total_size += size; buffer_append(message_sizes_buf, &size, sizeof(size)); } client->messages_count = message_sizes_buf->used / sizeof(uoff_t); mail_free(&mail); if (mailbox_search_deinit(&ctx) < 0) { client_send_storage_error(client); mailbox_transaction_rollback(&t); break; } if (!failed) { client->trans = t; client->message_sizes = buffer_free_without_data(message_sizes_buf); return TRUE; } /* well, sync and try again */ mailbox_transaction_rollback(&t); } if (i == 2) client_send_line(client, "-ERR [IN-USE] Couldn't sync mailbox."); buffer_free(message_sizes_buf); return FALSE; } struct client *client_create(int fd_in, int fd_out, struct mail_namespace *namespaces) { struct mail_storage *storage; const char *inbox; struct client *client; enum mailbox_open_flags flags; const char *errmsg; bool syntax_error, temporary_error; /* always use nonblocking I/O */ net_set_nonblock(fd_in, TRUE); net_set_nonblock(fd_out, TRUE); client = i_new(struct client, 1); client->fd_in = fd_in; client->fd_out = fd_out; client->input = i_stream_create_file(fd_in, default_pool, MAX_INBUF_SIZE, FALSE); client->output = o_stream_create_file(fd_out, default_pool, (size_t)-1, FALSE); o_stream_set_flush_callback(client->output, client_output, client); client->io = io_add(fd_in, IO_READ, client_input, client); client->last_input = ioloop_time; client->namespaces = namespaces; inbox = "INBOX"; client->inbox_ns = mail_namespace_find(namespaces, &inbox); if (client->inbox_ns == NULL) { client_send_line(client, "-ERR No INBOX namespace for user."); client_destroy(client, "No INBOX namespace for user."); return NULL; } storage = client->inbox_ns->storage; mail_storage_set_callbacks(storage, &mail_storage_callbacks, client); flags = 0; if (no_flag_updates) flags |= MAILBOX_OPEN_KEEP_RECENT; if (lock_session) flags |= MAILBOX_OPEN_KEEP_LOCKED; client->mailbox = mailbox_open(storage, "INBOX", NULL, flags); if (client->mailbox == NULL) { errmsg = t_strdup_printf("Couldn't open INBOX: %s", mail_storage_get_last_error(storage, &syntax_error, &temporary_error)); i_error("%s", errmsg); client_send_line(client, "-ERR [IN-USE] %s", errmsg); client_destroy(client, "Couldn't open INBOX"); return NULL; } if (!init_mailbox(client)) { i_error("Couldn't init INBOX: %s", mail_storage_get_last_error(storage, &syntax_error, &temporary_error)); client_destroy(client, "Mailbox init failed"); return NULL; } i_assert(my_client == NULL); my_client = client; if (hook_client_created != NULL) hook_client_created(&client); return client; } static const char *client_stats(struct client *client) { static struct var_expand_table static_tab[] = { { 'p', NULL }, { 't', NULL }, { 'b', NULL }, { 'r', NULL }, { 'd', NULL }, { 'm', NULL }, { 's', NULL }, { '\0', NULL } }; struct var_expand_table *tab; string_t *str; tab = t_malloc(sizeof(static_tab)); memcpy(tab, static_tab, sizeof(static_tab)); tab[0].value = dec2str(client->top_bytes); tab[1].value = dec2str(client->top_count); tab[2].value = dec2str(client->retr_bytes); tab[3].value = dec2str(client->retr_count); tab[4].value = dec2str(client->expunged_count); tab[5].value = dec2str(client->messages_count); tab[6].value = dec2str(client->total_size); str = t_str_new(128); var_expand(str, logout_format, tab); return str_c(str); } void client_destroy(struct client *client, const char *reason) { if (!client->disconnected) { if (reason == NULL) reason = "Disconnected"; i_info("%s %s", reason, client_stats(client)); } if (client->cmd != NULL) { /* deinitialize command */ i_stream_close(client->input); o_stream_close(client->output); client->cmd(client); i_assert(client->cmd == NULL); } if (client->trans != NULL) { /* client didn't QUIT, but we still want to save any changes done in this transaction. especially the cached virtual message sizes. */ (void)mailbox_transaction_commit(&client->trans, 0); } if (client->mailbox != NULL) mailbox_close(&client->mailbox); mail_namespaces_deinit(&client->namespaces); i_free(client->message_sizes); i_free(client->deleted_bitmask); if (client->io != NULL) io_remove(&client->io); i_stream_destroy(&client->input); o_stream_destroy(&client->output); if (close(client->fd_in) < 0) i_error("close(client in) failed: %m"); if (client->fd_in != client->fd_out) { if (close(client->fd_out) < 0) i_error("close(client out) failed: %m"); } i_free(client); /* quit the program */ my_client = NULL; io_loop_stop(ioloop); } void client_disconnect(struct client *client, const char *reason) { if (client->disconnected) return; client->disconnected = TRUE; i_info("Disconnected: %s %s", reason, client_stats(client)); (void)o_stream_flush(client->output); i_stream_close(client->input); o_stream_close(client->output); } int client_send_line(struct client *client, const char *fmt, ...) { va_list va; string_t *str; ssize_t ret; if (client->output->closed) return -1; t_push(); va_start(va, fmt); str = t_str_new(256); str_vprintfa(str, fmt, va); str_append(str, "\r\n"); ret = o_stream_send(client->output, str_data(str), str_len(str)); if (ret >= 0) { i_assert((size_t)ret == str_len(str)); if (o_stream_get_buffer_used_size(client->output) < OUTBUF_THROTTLE_SIZE) { ret = 1; client->last_output = ioloop_time; } else { ret = 0; if (client->io != NULL) { /* no more input until client has read our output */ io_remove(&client->io); /* If someone happens to flush output, we want to get our IO handler back in flush callback */ o_stream_set_flush_pending(client->output, TRUE); } } } va_end(va); t_pop(); return (int)ret; } void client_send_storage_error(struct client *client) { const char *error; bool syntax, temporary_error; if (mailbox_is_inconsistent(client->mailbox)) { client_send_line(client, "-ERR Mailbox is in inconsistent " "state, please relogin."); client_disconnect(client, "Mailbox is in inconsistent state."); return; } error = mail_storage_get_last_error(client->inbox_ns->storage, &syntax, &temporary_error); client_send_line(client, "-ERR %s", error != NULL ? error : "BUG: Unknown error"); } static void client_input(struct client *client) { char *line, *args; int ret; if (client->cmd != NULL) { /* we're still processing a command. wait until it's finished. */ io_remove(&client->io); client->waiting_input = TRUE; return; } client->waiting_input = FALSE; client->last_input = ioloop_time; switch (i_stream_read(client->input)) { case -1: /* disconnected */ client_destroy(client, NULL); return; case -2: /* line too long, kill it */ client_send_line(client, "-ERR Input line too long."); client_destroy(client, "Input line too long"); 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++ = '\0'; t_push(); ret = client_command_execute(client, line, args != NULL ? args : ""); t_pop(); if (ret >= 0) { client->bad_counter = 0; if (client->cmd != NULL) { o_stream_set_flush_pending(client->output, TRUE); client->waiting_input = TRUE; break; } } else if (++client->bad_counter > CLIENT_MAX_BAD_COMMANDS) { client_send_line(client, "-ERR Too many bad commands."); client_disconnect(client, "Too many bad commands."); } } o_stream_uncork(client->output); if (client->output->closed) client_destroy(client, NULL); } static int client_output(struct client *client) { int ret; if ((ret = o_stream_flush(client->output)) < 0) { client_destroy(client, NULL); return 1; } client->last_output = ioloop_time; if (client->cmd != NULL) { o_stream_cork(client->output); client->cmd(client); o_stream_uncork(client->output); } if (client->cmd == NULL) { if (o_stream_get_buffer_used_size(client->output) < OUTBUF_THROTTLE_SIZE/2 && client->io == NULL) { /* enable input again */ client->io = io_add(i_stream_get_fd(client->input), IO_READ, client_input, client); } if (client->io != NULL && client->waiting_input) client_input(client); } return client->cmd == NULL; } static void idle_timeout(void *context __attr_unused__) { if (my_client == NULL) return; if (my_client->cmd != NULL) { if (ioloop_time - my_client->last_output >= CLIENT_OUTPUT_TIMEOUT) { client_destroy(my_client, "Disconnected for inactivity " "in reading our output"); } } else { if (ioloop_time - my_client->last_input >= CLIENT_IDLE_TIMEOUT) { client_send_line(my_client, "-ERR Disconnected for inactivity."); client_destroy(my_client, "Disconnected for inactivity"); } } } 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, "Server shutting down"); } timeout_remove(&to_idle); }