pop3: Don't crash at startup if mailbox is empty.
1 /* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */
10 #include "var-expand.h"
11 #include "mail-storage.h"
13 #include "mail-search-build.h"
14 #include "mail-namespace.h"
19 /* max. length of input command line (spec says 512) */
20 #define MAX_INBUF_SIZE 2048
22 /* Stop reading input when output buffer has this many bytes. Once the buffer
23 size has dropped to half of it, start reading input again. */
24 #define OUTBUF_THROTTLE_SIZE 4096
26 /* Disconnect client when it sends too many bad commands in a row */
27 #define CLIENT_MAX_BAD_COMMANDS 20
29 /* Disconnect client after idling this many milliseconds */
30 #define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000)
31 /* If client starts idling for this many milliseconds, commit the current
32 transaction. This allows the mailbox to become unlocked. */
33 #define CLIENT_COMMIT_TIMEOUT_MSECS (10*1000)
35 static struct client *my_client; /* we don't need more than one currently */
37 static void client_input(struct client *client);
38 static int client_output(struct client *client);
40 static void client_commit_timeout(struct client *client)
42 if (client->cmd != NULL) {
43 /* Can't commit while commands are running */
47 (void)mailbox_transaction_commit(&client->trans);
48 client->trans = mailbox_transaction_begin(client->mailbox, 0);
51 static void client_idle_timeout(struct client *client)
53 if (client->cmd != NULL) {
54 client_destroy(client,
55 "Disconnected for inactivity in reading our output");
57 client_send_line(client, "-ERR Disconnected for inactivity.");
58 client_destroy(client, "Disconnected for inactivity");
62 static bool init_mailbox(struct client *client, const char **error_r)
64 struct mail_search_args *search_args;
65 struct mailbox_transaction_context *t;
66 struct mail_search_context *ctx;
67 struct mailbox_status status;
69 buffer_t *message_sizes_buf;
70 uint32_t failed_uid = 0;
73 bool failed, expunged;
75 message_sizes_buf = buffer_create_dynamic(default_pool, 512);
77 search_args = mail_search_build_init();
78 mail_search_build_add_all(search_args);
80 for (i = 0; i < 2; i++) {
82 if (mailbox_sync(client->mailbox, MAILBOX_SYNC_FLAG_FULL_READ,
83 STATUS_UIDVALIDITY, &status) < 0) {
84 client_send_storage_error(client);
87 client->uid_validity = status.uidvalidity;
89 t = mailbox_transaction_begin(client->mailbox, 0);
90 ctx = mailbox_search_init(t, search_args, NULL);
92 client->last_seen = 0;
93 client->total_size = 0;
94 buffer_set_used_size(message_sizes_buf, 0);
97 mail = mail_alloc(t, MAIL_FETCH_VIRTUAL_SIZE, NULL);
98 while (mailbox_search_next(ctx, mail) > 0) {
99 if (mail_get_virtual_size(mail, &size) < 0) {
100 expunged = mail->expunged;
102 if (failed_uid == mail->uid) {
103 i_error("Getting size of message "
104 "UID=%u failed", mail->uid);
107 failed_uid = mail->uid;
111 if ((mail_get_flags(mail) & MAIL_SEEN) != 0)
112 client->last_seen = mail->seq;
113 client->total_size += size;
115 buffer_append(message_sizes_buf, &size, sizeof(size));
117 client->messages_count =
118 message_sizes_buf->used / sizeof(uoff_t);
121 if (mailbox_search_deinit(&ctx) < 0 || (failed && !expunged)) {
122 client_send_storage_error(client);
123 (void)mailbox_transaction_commit(&t);
129 client->message_sizes =
130 buffer_free_without_data(&message_sizes_buf);
131 mail_search_args_unref(&search_args);
135 /* well, sync and try again. we might have cached virtual
136 sizes, make sure they get committed. */
137 (void)mailbox_transaction_commit(&t);
139 mail_search_args_unref(&search_args);
142 client_send_line(client,
143 "-ERR [IN-USE] Couldn't sync mailbox.");
144 *error_r = "Can't sync mailbox: Messages keep getting expunged";
146 struct mail_storage *storage = client->inbox_ns->storage;
147 enum mail_error error;
149 *error_r = mail_storage_get_last_error(storage, &error);
151 buffer_free(&message_sizes_buf);
155 struct client *client_create(int fd_in, int fd_out, struct mail_user *user)
157 struct mail_storage *storage;
159 struct client *client;
160 enum mailbox_open_flags flags;
162 enum mail_error error;
164 /* always use nonblocking I/O */
165 net_set_nonblock(fd_in, TRUE);
166 net_set_nonblock(fd_out, TRUE);
168 client = i_new(struct client, 1);
169 client->fd_in = fd_in;
170 client->fd_out = fd_out;
171 client->input = i_stream_create_fd(fd_in, MAX_INBUF_SIZE, FALSE);
172 client->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
173 o_stream_set_flush_callback(client->output, client_output, client);
175 client->io = io_add(fd_in, IO_READ, client_input, client);
176 client->last_input = ioloop_time;
177 client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS,
178 client_idle_timeout, client);
180 client->to_commit = timeout_add(CLIENT_COMMIT_TIMEOUT_MSECS,
181 client_commit_timeout, client);
187 client->inbox_ns = mail_namespace_find(user->namespaces, &inbox);
188 if (client->inbox_ns == NULL) {
189 client_send_line(client, "-ERR No INBOX namespace for user.");
190 client_destroy(client, "No INBOX namespace for user.");
194 storage = client->inbox_ns->storage;
196 flags = MAILBOX_OPEN_POP3_SESSION;
198 flags |= MAILBOX_OPEN_KEEP_RECENT;
200 flags |= MAILBOX_OPEN_KEEP_LOCKED;
201 client->mailbox = mailbox_open(&storage, "INBOX", NULL, flags);
202 if (client->mailbox == NULL) {
203 errmsg = t_strdup_printf("Couldn't open INBOX: %s",
204 mail_storage_get_last_error(storage,
206 i_error("%s", errmsg);
207 client_send_line(client, "-ERR [IN-USE] %s", errmsg);
208 client_destroy(client, "Couldn't open INBOX");
212 if (!init_mailbox(client, &errmsg)) {
213 i_error("Couldn't init INBOX: %s", errmsg);
214 client_destroy(client, "Mailbox init failed");
218 if (!no_flag_updates && client->messages_count > 0)
219 client->seen_bitmask = i_malloc(MSGS_BITMASK_SIZE(client));
221 i_assert(my_client == NULL);
224 if (hook_client_created != NULL)
225 hook_client_created(&client);
229 static const char *client_stats(struct client *client)
231 static struct var_expand_table static_tab[] = {
232 { 'p', NULL, "top_bytes" },
233 { 't', NULL, "top_count" },
234 { 'b', NULL, "retr_bytes" },
235 { 'r', NULL, "retr_count" },
236 { 'd', NULL, "deleted_count" },
237 { 'm', NULL, "message_count" },
238 { 's', NULL, "message_bytes" },
239 { 'i', NULL, "input" },
240 { 'o', NULL, "output" },
243 struct var_expand_table *tab;
246 tab = t_malloc(sizeof(static_tab));
247 memcpy(tab, static_tab, sizeof(static_tab));
249 tab[0].value = dec2str(client->top_bytes);
250 tab[1].value = dec2str(client->top_count);
251 tab[2].value = dec2str(client->retr_bytes);
252 tab[3].value = dec2str(client->retr_count);
253 tab[4].value = dec2str(client->expunged_count);
254 tab[5].value = dec2str(client->messages_count);
255 tab[6].value = dec2str(client->total_size);
256 tab[7].value = dec2str(client->input->v_offset);
257 tab[8].value = dec2str(client->output->offset);
259 str = t_str_new(128);
260 var_expand(str, logout_format, tab);
264 static const char *client_get_disconnect_reason(struct client *client)
266 errno = client->input->stream_errno != 0 ?
267 client->input->stream_errno :
268 client->output->stream_errno;
269 return errno == 0 || errno == EPIPE ? "Connection closed" :
270 t_strdup_printf("Connection closed: %m");
273 void client_destroy(struct client *client, const char *reason)
275 if (client->seen_change_count > 0)
276 client_update_mails(client);
278 if (!client->disconnected) {
280 reason = client_get_disconnect_reason(client);
281 i_info("%s %s", reason, client_stats(client));
284 if (client->cmd != NULL) {
285 /* deinitialize command */
286 i_stream_close(client->input);
287 o_stream_close(client->output);
289 i_assert(client->cmd == NULL);
291 if (client->trans != NULL) {
292 /* client didn't QUIT, but we still want to save any changes
293 done in this transaction. especially the cached virtual
295 (void)mailbox_transaction_commit(&client->trans);
297 if (client->mailbox != NULL)
298 mailbox_close(&client->mailbox);
299 mail_user_unref(&client->user);
301 i_free(client->message_sizes);
302 i_free(client->deleted_bitmask);
303 i_free(client->seen_bitmask);
305 if (client->io != NULL)
306 io_remove(&client->io);
307 timeout_remove(&client->to_idle);
308 if (client->to_commit != NULL)
309 timeout_remove(&client->to_commit);
311 i_stream_destroy(&client->input);
312 o_stream_destroy(&client->output);
314 if (close(client->fd_in) < 0)
315 i_error("close(client in) failed: %m");
316 if (client->fd_in != client->fd_out) {
317 if (close(client->fd_out) < 0)
318 i_error("close(client out) failed: %m");
323 /* quit the program */
325 io_loop_stop(ioloop);
328 void client_disconnect(struct client *client, const char *reason)
330 if (client->disconnected)
333 client->disconnected = TRUE;
334 i_info("Disconnected: %s %s", reason, client_stats(client));
336 (void)o_stream_flush(client->output);
338 i_stream_close(client->input);
339 o_stream_close(client->output);
342 int client_send_line(struct client *client, const char *fmt, ...)
347 if (client->output->closed)
355 str = t_str_new(256);
356 str_vprintfa(str, fmt, va);
357 str_append(str, "\r\n");
359 ret = o_stream_send(client->output,
360 str_data(str), str_len(str));
361 i_assert(ret < 0 || (size_t)ret == str_len(str));
364 if (o_stream_get_buffer_used_size(client->output) <
365 OUTBUF_THROTTLE_SIZE) {
367 client->last_output = ioloop_time;
370 if (client->io != NULL) {
371 /* no more input until client has read
373 io_remove(&client->io);
375 /* If someone happens to flush output,
376 we want to get our IO handler back in
378 o_stream_set_flush_pending(client->output,
388 void client_send_storage_error(struct client *client)
390 enum mail_error error;
392 if (mailbox_is_inconsistent(client->mailbox)) {
393 client_send_line(client, "-ERR Mailbox is in inconsistent "
394 "state, please relogin.");
395 client_disconnect(client, "Mailbox is in inconsistent state.");
399 client_send_line(client, "-ERR %s",
400 mail_storage_get_last_error(client->inbox_ns->storage,
404 bool client_handle_input(struct client *client)
409 o_stream_cork(client->output);
410 while (!client->output->closed &&
411 (line = i_stream_next_line(client->input)) != NULL) {
412 args = strchr(line, ' ');
417 ret = client_command_execute(client, line,
418 args != NULL ? args : "");
421 client->bad_counter = 0;
422 if (client->cmd != NULL) {
423 o_stream_set_flush_pending(client->output,
425 client->waiting_input = TRUE;
428 } else if (++client->bad_counter > CLIENT_MAX_BAD_COMMANDS) {
429 client_send_line(client, "-ERR Too many bad commands.");
430 client_disconnect(client, "Too many bad commands.");
433 o_stream_uncork(client->output);
435 if (client->output->closed) {
436 client_destroy(client, NULL);
442 static void client_input(struct client *client)
444 if (client->cmd != NULL) {
445 /* we're still processing a command. wait until it's
447 io_remove(&client->io);
448 client->waiting_input = TRUE;
452 client->waiting_input = FALSE;
453 client->last_input = ioloop_time;
454 timeout_reset(client->to_idle);
455 if (client->to_commit != NULL)
456 timeout_reset(client->to_commit);
458 switch (i_stream_read(client->input)) {
461 client_destroy(client, NULL);
464 /* line too long, kill it */
465 client_send_line(client, "-ERR Input line too long.");
466 client_destroy(client, "Input line too long");
470 (void)client_handle_input(client);
473 static int client_output(struct client *client)
477 if ((ret = o_stream_flush(client->output)) < 0) {
478 client_destroy(client, NULL);
482 client->last_output = ioloop_time;
483 timeout_reset(client->to_idle);
484 if (client->to_commit != NULL)
485 timeout_reset(client->to_commit);
487 if (client->cmd != NULL) {
488 o_stream_cork(client->output);
490 o_stream_uncork(client->output);
493 if (client->cmd == NULL) {
494 if (o_stream_get_buffer_used_size(client->output) <
495 OUTBUF_THROTTLE_SIZE/2 && client->io == NULL) {
496 /* enable input again */
497 client->io = io_add(i_stream_get_fd(client->input),
498 IO_READ, client_input, client);
500 if (client->io != NULL && client->waiting_input)
501 client_input(client);
504 return client->cmd == NULL;
507 void clients_init(void)
512 void clients_deinit(void)
514 if (my_client != NULL) {
515 client_send_line(my_client, "-ERR Server shutting down.");
516 client_destroy(my_client, "Server shutting down");