int client_output(struct client *client) { int ret; i_assert(!client->destroyed); client->last_output = ioloop_time; timeout_reset(client->to_idle); if (client->to_idle_output != NULL) timeout_reset(client->to_idle_output); o_stream_cork(client->output); if ((ret = o_stream_flush(client->output)) < 0) { client_destroy(client, NULL); return 1; } client_output_commands(client); (void)cmd_sync_delayed(client); o_stream_uncork(client->output); imap_refresh_proctitle(); if (client->disconnected) client_destroy(client, NULL); else client_continue_pending_input(client); return ret; }
void client_command_free(struct client_command_context **_cmd) { struct client_command_context *cmd = *_cmd; struct client *client = cmd->client; enum client_command_state state = cmd->state; *_cmd = NULL; i_assert(client->output_cmd_lock == NULL); /* reset input idle time because command output might have taken a long time and we don't want to disconnect client immediately then */ client->last_input = ioloop_time; timeout_reset(client->to_idle); if (cmd->cancel) { cmd->cancel = FALSE; client_send_tagline(cmd, "NO Command cancelled."); } if (!cmd->param_error) client->bad_counter = 0; if (client->input_lock == cmd) client->input_lock = NULL; if (client->mailbox_change_lock == cmd) client->mailbox_change_lock = NULL; if (client->free_parser == NULL) { imap_parser_reset(cmd->parser); client->free_parser = cmd->parser; } else if (cmd->parser != NULL) { imap_parser_unref(&cmd->parser); } client->command_queue_size--; DLLIST_REMOVE(&client->command_queue, cmd); cmd = NULL; if (client->command_queue == NULL) { /* no commands left in the queue, we can clear the pool */ p_clear(client->command_pool); if (client->to_idle_output != NULL) timeout_remove(&client->to_idle_output); } imap_client_notify_command_freed(client); imap_refresh_proctitle(); /* if command finished from external event, check input for more unhandled commands since we may not be executing from client_input or client_output. */ if (state == CLIENT_COMMAND_STATE_WAIT_EXTERNAL && !client->disconnected) { client_add_missing_io(client); if (client->to_delayed_input == NULL) { client->to_delayed_input = timeout_add(0, client_input, client); } } }
void client_input(struct client *client) { struct client_command_context *cmd; struct ostream *output = client->output; ssize_t bytes; i_assert(client->io != NULL); client->last_input = ioloop_time; timeout_reset(client->to_idle); if (client->to_delayed_input != NULL) timeout_remove(&client->to_delayed_input); bytes = i_stream_read(client->input); if (bytes == -1) { /* disconnected */ client_destroy(client, NULL); return; } o_stream_ref(output); o_stream_cork(output); if (!client_handle_input(client) && bytes == -2) { /* parameter word is longer than max. input buffer size. this is most likely an error, so skip the new data until newline is found. */ client->input_skip_line = TRUE; cmd = client->input_lock != NULL ? client->input_lock : client_command_new(client); cmd->param_error = TRUE; client_send_command_error(cmd, "Too long argument."); client_command_free(&cmd); } o_stream_uncork(output); o_stream_unref(&output); imap_refresh_proctitle(); if (client->disconnected) client_destroy(client, NULL); else client_continue_pending_input(client); }
static bool client_command_input(struct client_command_context *cmd) { struct client *client = cmd->client; struct command *command; if (cmd->func != NULL) { /* command is being executed - continue it */ if (command_exec(cmd)) { /* command execution was finished */ client_command_free(&cmd); client_add_missing_io(client); return TRUE; } return client_handle_unfinished_cmd(cmd); } if (cmd->tag == NULL) { cmd->tag = imap_parser_read_word(cmd->parser); if (cmd->tag == NULL) return FALSE; /* need more data */ cmd->tag = p_strdup(cmd->pool, cmd->tag); } if (cmd->name == NULL) { cmd->name = imap_parser_read_word(cmd->parser); if (cmd->name == NULL) return FALSE; /* need more data */ /* UID commands are a special case. better to handle them here. */ if (!cmd->uid && strcasecmp(cmd->name, "UID") == 0) { cmd->uid = TRUE; cmd->name = imap_parser_read_word(cmd->parser); if (cmd->name == NULL) return FALSE; /* need more data */ } cmd->name = !cmd->uid ? p_strdup(cmd->pool, cmd->name) : p_strconcat(cmd->pool, "UID ", cmd->name, NULL); imap_refresh_proctitle(); } client->input_skip_line = TRUE; if (cmd->name[0] == '\0') { /* command not given - cmd->func is already NULL. */ } else if ((command = command_find(cmd->name)) != NULL) { cmd->func = command->func; cmd->cmd_flags = command->flags; if (client_command_is_ambiguous(cmd)) { /* do nothing until existing commands are finished */ i_assert(cmd->state == CLIENT_COMMAND_STATE_WAIT_INPUT); cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY; io_remove(&client->io); return FALSE; } } if (cmd->func == NULL) { /* unknown command */ client_send_command_error(cmd, "Unknown command."); cmd->param_error = TRUE; client_command_free(&cmd); return TRUE; } else { i_assert(!client->disconnected); return client_command_input(cmd); } }
struct client *client_create(int fd_in, int fd_out, const char *session_id, struct mail_user *user, struct mail_storage_service_user *service_user, const struct imap_settings *set) { const struct mail_storage_settings *mail_set; struct client *client; const char *ident; pool_t pool; bool explicit_capability = FALSE; /* always use nonblocking I/O */ net_set_nonblock(fd_in, TRUE); net_set_nonblock(fd_out, TRUE); pool = pool_alloconly_create("imap client", 2048); client = p_new(pool, struct client, 1); client->pool = pool; client->v = imap_client_vfuncs; client->set = set; client->service_user = service_user; client->session_id = p_strdup(pool, session_id); client->fd_in = fd_in; client->fd_out = fd_out; client->input = i_stream_create_fd(fd_in, set->imap_max_line_length, FALSE); client->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE); o_stream_set_no_error_handling(client->output, TRUE); i_stream_set_name(client->input, "<imap client>"); o_stream_set_name(client->output, "<imap client>"); o_stream_set_flush_callback(client->output, client_output, client); p_array_init(&client->module_contexts, client->pool, 5); client->io = io_add_istream(client->input, client_input, client); client->last_input = ioloop_time; client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS, client_idle_timeout, client); client->command_pool = pool_alloconly_create(MEMPOOL_GROWING"client command", 1024*2); client->user = user; client->notify_count_changes = TRUE; client->notify_flag_changes = TRUE; mail_namespaces_set_storage_callbacks(user->namespaces, &mail_storage_callbacks, client); client->capability_string = str_new(client->pool, sizeof(CAPABILITY_STRING)+64); if (*set->imap_capability == '\0') str_append(client->capability_string, CAPABILITY_STRING); else if (*set->imap_capability != '+') { explicit_capability = TRUE; str_append(client->capability_string, set->imap_capability); } else { str_append(client->capability_string, CAPABILITY_STRING); str_append_c(client->capability_string, ' '); str_append(client->capability_string, set->imap_capability + 1); } if (user->fuzzy_search && !explicit_capability) { /* Enable FUZZY capability only when it actually has a chance of working */ str_append(client->capability_string, " SEARCH=FUZZY"); } mail_set = mail_user_set_get_storage_set(user); if (mail_set->mailbox_list_index && !explicit_capability) { /* NOTIFY is enabled only when mailbox list indexes are enabled, although even that doesn't necessarily guarantee it always */ str_append(client->capability_string, " NOTIFY"); } if (*set->imap_urlauth_host != '\0' && *mail_set->mail_attribute_dict != '\0') { /* Enable URLAUTH capability only when dict is configured correctly */ client_init_urlauth(client); if (!explicit_capability) str_append(client->capability_string, " URLAUTH URLAUTH=BINARY"); } if (set->imap_metadata && *mail_set->mail_attribute_dict != '\0') { client->imap_metadata_enabled = TRUE; if (!explicit_capability) str_append(client->capability_string, " METADATA"); } if (!explicit_capability && user_has_special_use_mailboxes(user)) { /* Advertise SPECIAL-USE only if there are actually some SPECIAL-USE flags in mailbox configuration. */ str_append(client->capability_string, " SPECIAL-USE"); } ident = mail_user_get_anvil_userip_ident(client->user); if (ident != NULL) { master_service_anvil_send(master_service, t_strconcat( "CONNECT\t", my_pid, "\timap/", ident, "\n", NULL)); client->anvil_sent = TRUE; } imap_client_count++; DLLIST_PREPEND(&imap_clients, client); if (hook_client_created != NULL) hook_client_created(&client); imap_refresh_proctitle(); return client; }
static void client_default_destroy(struct client *client, const char *reason) { struct client_command_context *cmd; const char *cmd_status = ""; i_assert(!client->destroyed); client->destroyed = TRUE; if (!client->disconnected) { client->disconnected = TRUE; if (reason == NULL) { reason = io_stream_get_disconnect_reason(client->input, client->output); cmd_status = client_get_commands_status(client); } i_info("%s%s %s", reason, cmd_status, client_stats(client)); } i_stream_close(client->input); o_stream_close(client->output); /* finish off all the queued commands. */ if (client->output_cmd_lock != NULL) client_command_cancel(&client->output_cmd_lock); while (client->command_queue != NULL) { cmd = client->command_queue; client_command_cancel(&cmd); } /* handle the input_lock command last. it might have been waiting on other queued commands (although we probably should just drop the command at that point since it hasn't started running. but this may change in future). */ if (client->input_lock != NULL) client_command_cancel(&client->input_lock); if (client->mailbox != NULL) { client_search_updates_free(client); mailbox_free(&client->mailbox); } if (client->notify_ctx != NULL) imap_notify_deinit(&client->notify_ctx); if (client->urlauth_ctx != NULL) imap_urlauth_deinit(&client->urlauth_ctx); if (client->anvil_sent) { master_service_anvil_send(master_service, t_strconcat( "DISCONNECT\t", my_pid, "\timap/", mail_user_get_anvil_userip_ident(client->user), "\n", NULL)); } mail_user_unref(&client->user); if (client->free_parser != NULL) imap_parser_unref(&client->free_parser); if (client->io != NULL) io_remove(&client->io); if (client->to_idle_output != NULL) timeout_remove(&client->to_idle_output); if (client->to_delayed_input != NULL) timeout_remove(&client->to_delayed_input); timeout_remove(&client->to_idle); i_stream_destroy(&client->input); o_stream_destroy(&client->output); net_disconnect(client->fd_in); if (client->fd_in != client->fd_out) net_disconnect(client->fd_out); if (array_is_created(&client->search_saved_uidset)) array_free(&client->search_saved_uidset); if (array_is_created(&client->search_updates)) array_free(&client->search_updates); pool_unref(&client->command_pool); mail_storage_service_user_free(&client->service_user); imap_client_count--; DLLIST_REMOVE(&imap_clients, client); pool_unref(&client->pool); master_service_client_connection_destroyed(master_service); imap_refresh_proctitle(); }
static int imap_master_client_input_args(struct connection *conn, const char *const *args, int fd_client, pool_t pool) { struct imap_master_client *client = (struct imap_master_client *)conn; struct client *imap_client; struct mail_storage_service_input input; struct imap_master_input master_input; const char *error; int ret; if (imap_master_client_parse_input(args, pool, &input, &master_input, &error) < 0) { i_error("imap-master: Failed to parse client input: %s", error); o_stream_nsend_str(conn->output, t_strdup_printf( "-Failed to parse client input: %s\n", error)); i_close_fd(&fd_client); return -1; } if (imap_master_client_verify(&master_input, fd_client, &error) < 0) { i_error("imap-master: Failed to verify client input: %s", error); o_stream_nsend_str(conn->output, t_strdup_printf( "-Failed to verify client input: %s\n", error)); i_close_fd(&fd_client); return -1; } /* Send a success notification before we start anything that lasts potentially a long time. imap-hibernate process is waiting for us to answer. Even if we fail later, we log the error anyway. */ o_stream_nsend_str(conn->output, "+\n"); (void)o_stream_flush(conn->output); /* NOTE: before client_create_from_input() on failures we need to close fd_client, but afterward it gets closed by client_destroy() */ ret = client_create_from_input(&input, fd_client, fd_client, &imap_client, &error); if (ret < 0) { i_error("imap-master(%s): Failed to create client: %s", input.username, error); i_close_fd(&fd_client); return -1; } client->imap_client_created = TRUE; if (client_create_finish(imap_client, &error) < 0) { i_error("imap-master(%s): %s", input.username, error); client_destroy(imap_client, error); return -1; } /* log prefix is set at this point, so we don't need to add the username anymore to the log messages */ o_stream_nsend(imap_client->output, master_input.client_output->data, master_input.client_output->used); if (master_input.client_input->used > 0 && !i_stream_add_data(imap_client->input, master_input.client_input->data, master_input.client_input->used)) { i_error("imap-master: Couldn't add %"PRIuSIZE_T " bytes to client's input stream", master_input.client_input->used); client_destroy(imap_client, "Client initialization failed"); return -1; } imap_client->state_import_bad_idle_done = master_input.state_import_bad_idle_done; imap_client->state_import_idle_continue = master_input.state_import_idle_continue; if (imap_client->user->mail_debug) { if (imap_client->state_import_bad_idle_done) i_debug("imap-master: Unhibernated because IDLE was stopped with BAD command"); else if (imap_client->state_import_idle_continue) i_debug("imap-master: Unhibernated to send mailbox changes"); else i_debug("imap-master: Unhibernated because IDLE was stopped with DONE"); } ret = imap_state_import_internal(imap_client, master_input.state->data, master_input.state->used, &error); if (ret <= 0) { i_error("imap-master: Failed to import client state: %s", error); client_destroy(imap_client, "Client state initialization failed"); return -1; } if (master_input.tag != NULL) imap_state_import_idle_cmd_tag(imap_client, master_input.tag); /* make sure all pending input gets handled */ i_assert(imap_client->to_delayed_input == NULL); if (master_input.client_input->used > 0) { if (imap_client->user->mail_debug) { i_debug("imap-master: Pending client input: '%s'", str_sanitize(str_c(master_input.client_input), 128)); } imap_client->to_delayed_input = timeout_add(0, client_input, imap_client); } imap_refresh_proctitle(); /* we'll always disconnect the client afterwards */ return -1; }