/** * @brief Get a message, in response to a POP3 DELE command. * @param con the POP3 client connection issuing the command. * @return This function returns no value. */ void pop_dele(connection_t *con) { uint64_t number; meta_message_t *active = NULL; if (con->pop.session_state != 1) { pop_invalid(con); return; } // A message number is a required argument. if (!pop_num_parse(con, &number, true)) { con_write_bl(con, "-ERR The delete command requires a numeric argument.\r\n", 54); return; } meta_user_wlock(con->pop.user); if (!(active = pop_get_message(con->pop.user->messages, number))) { con_write_bl(con, "-ERR Message not found.\r\n", 25); } else if ((active->status & MAIL_STATUS_HIDDEN) == MAIL_STATUS_HIDDEN) { con_write_bl(con, "-ERR Message already deleted.\r\n", 31); } else { active->status += MAIL_STATUS_HIDDEN; con_write_bl(con, "+OK Message marked for deletion.\r\n", 34); } meta_user_unlock(con->pop.user); return; }
/** * @brief Get the UIDL for a message or collection of messages, in response to a POP3 UIDL command. * @param con the POP3 client connection issuing the command. * @return This function returns no value. */ void pop_uidl(connection_t *con) { uint64_t number; inx_cursor_t *cursor; meta_message_t *active; bool_t result; if (con->pop.session_state != 1) { pop_invalid(con); return; } // Get the argument, if any. result = pop_num_parse(con, &number, false); meta_user_rlock(con->pop.user); // Output all of the messages that aren't deleted or appended. if (!result) { number = 1; con_print(con, "+OK %llu messages total.\r\n", pop_total_messages(con->pop.user->messages)); if (con->pop.user->messages && (cursor = inx_cursor_alloc(con->pop.user->messages))) { while ((active = inx_cursor_value_next(cursor))) { if ((active->status & (MAIL_STATUS_APPENDED | MAIL_STATUS_HIDDEN)) == 0) { con_print(con, "%llu %llu\r\n", number++, active->messagenum); } else if ((active->status & MAIL_STATUS_APPENDED) == 0) { number++; } } inx_cursor_free(cursor); } con_write_bl(con, ".\r\n", 3); } // Output a specific message. else { if (!(active = pop_get_message(con->pop.user->messages, number))) { con_write_bl(con, "-ERR Message not found.\r\n", 25); } else if ((active->status & MAIL_STATUS_HIDDEN) == MAIL_STATUS_HIDDEN) { con_write_bl(con, "-ERR Message marked for deletion.\r\n", 35); } else { con_print(con, "+OK %llu %llu\r\n", number, active->messagenum); } } meta_user_unlock(con->pop.user); return; }
void imap_session_destroy(connection_t *con) { inx_cursor_t *cursor; meta_message_t *active; meta_user_wlock(con->imap.user); // If a folder was selected, clear the recent flag before closing the mailbox. if (con->imap.session_state == 1 && con->imap.user && con->imap.selected && !con->imap.read_only && (cursor = inx_cursor_alloc(con->imap.user->messages))) { while ((active = inx_cursor_value_next(cursor))) { if (active->foldernum == con->imap.selected && (active->status & MAIL_STATUS_RECENT) == MAIL_STATUS_RECENT) { active->status = (active->status | MAIL_STATUS_RECENT) ^ MAIL_STATUS_RECENT; } } inx_cursor_free(cursor); } meta_user_unlock(con->imap.user); // Is there a user session. if (con->imap.user) { if (con->imap.username) { meta_remove(con->imap.username, META_PROT_IMAP); } } st_cleanup(con->imap.username); st_cleanup(con->imap.tag); st_cleanup(con->imap.command); if (con->imap.arguments) { ar_free(con->imap.arguments); } mail_cache_reset(); return; }
/** * @brief Get the sequence number of the last read message, in response to a POP3 LAST command. * @see pop_get_last() * @param con the POP3 client connection issuing the command. * @return This function returns no value. */ void pop_last(connection_t *con) { uint64_t number = 0; if (con->pop.session_state != 1) { pop_invalid(con); return; } meta_user_rlock(con->pop.user); number = pop_get_last(con->pop.user->messages); meta_user_unlock(con->pop.user); // Print the information. con_print(con, "+OK %llu\r\n", number); return; }
/** * @brief Display a user's message statistics, in response to a POP3 STAT command. * @see pop_total_messages(), pop_total_size() * @param con the POP3 client connection issuing the command. * @return This function returns no value. */ void pop_stat(connection_t *con) { uint64_t count, size; if (con->pop.session_state != 1) { pop_invalid(con); return; } meta_user_rlock(con->pop.user); count = pop_total_messages(con->pop.user->messages); size = pop_total_size(con->pop.user->messages); meta_user_unlock(con->pop.user); // Print the information. con_print(con, "+OK %llu %llu\r\n", count, size); return; }
/** * @brief Retrieve a user's message, in response to a POP3 RETR command. * @note This function will fail if a deleted message was specified by the user. * @param con the POP3 client connection issuing the command. * @return This function returns no value. */ void pop_retr(connection_t *con) { uint64_t number; meta_message_t *meta; mail_message_t *message; if (con->pop.session_state != 1) { pop_invalid(con); return; } // Which message are we getting. if (!pop_num_parse(con, &number, true)) { con_write_bl(con, "-ERR The retrieve command requires a numeric argument.\r\n", 56); return; } meta_user_rlock(con->pop.user); // Get the message. if (!(meta = pop_get_message(con->pop.user->messages, number))) { meta_user_unlock(con->pop.user); con_write_bl(con, "-ERR Message not found.\r\n", 25); return; } // Check for deletion. if ((meta->status & MAIL_STATUS_HIDDEN) == MAIL_STATUS_HIDDEN) { meta_user_unlock(con->pop.user); con_write_bl(con, "-ERR This message has been marked for deletion.\r\n", 49); return; } // Load the message and spit back the right number of lines. if (!(message = mail_load_message(meta, con->pop.user, con->server, 1))) { meta_user_unlock(con->pop.user); con_write_bl(con, "-ERR The message you requested could not be loaded into memory. It has either been " "deleted by another connection or is corrupted.\r\n", 131); return; } meta_user_unlock(con->pop.user); // Dot stuff the message. st_replace(&(message->text), PLACER("\n.", 2), PLACER("\n..", 3)); // Tell the client to prepare for a message. The size is strictly informational. con_print(con, "+OK %u characters follow.\r\n", st_length_get(message->text)); // We use raw socket IO because it is much faster when writing large amounts of data. con_write_st(con, message->text); // If the message didn't end with a line break, spit two. if (*(st_char_get(message->text) + st_length_get(message->text) - 1) == '\n') { con_write_bl(con, ".\r\n", 3); } else { con_write_bl(con, "\r\n.\r\n", 5); } mail_destroy(message); return; }
/** * @brief Accept and verify a password for POP3 authentication. * @note This command is only allowed for sessions which have not yet been authenticated, but which have already supplied a username. * If the username/password combo was validated, the account information is retrieved and checked to see if it is locked. * After successful authentication, this function will prohibit insecure connections for any user configured to use SSL only, * and enforce the existence of only one POP3 session at a time. * Finally, the database Log table for this user's POP3 access is updated, and all the user's messages are retrieved. * @param con the POP3 client connection issuing the command. * @return This function returns no value. */ void pop_pass(connection_t *con) { int_t state; credential_t *cred; stringer_t *password, *username; if (con->pop.session_state != 0) { pop_invalid(con); return; } // The user must come before the PASS command. if (st_empty(con->pop.username)) { con_write_bl(con, "-ERR You must supply a username first.\r\n", 40); return; } // If they didn't pass in a valid password. if (!(password = pop_pass_parse(con))) { con_write_bl(con, "-ERR Invalid PASS command.\r\n", 28); return; } // Hash the password. // First we need to get the regular username from the fully qualified one. if (!(username = credential_username(con->pop.username))) { con_write_bl(con, "-ERR Internal server error. Please try again later.\r\n", 53); st_wipe(password); st_free(password); } st_free(con->pop.username); con->pop.username = username; if (!(cred = credential_alloc_auth(con->pop.username, password))) { con_write_bl(con, "-ERR Internal server error. Please try again later.\r\n", 53); st_wipe(password); st_free(password); return; } st_wipe(password); st_free(password); // Pull the user info out. state = meta_get(con->pop.username, cred->auth.domain, cred->auth.password, cred->auth.key, META_PROT_POP, META_GET_MESSAGES, &(con->pop.user)); // Securely delete this information, as these are the keys to the castle. credential_free(cred); // Not found, or invalid password. if (state == 0) { con_write_bl(con, "-ERR The username and password combination is invalid.\r\n", 56); return; } // Internal error. else if (state < 0 || !con->pop.user) { con_write_bl(con, "-ERR [SYS/TEMP] Internal server error. Please try again later.\r\n", 64); return; } // Locks else if (con->pop.user->lock_status != 0) { // What type of lock is it. if (con->pop.user->lock_status == 1) { con_write_bl(con, "-ERR [SYS/PERM] This account has been administratively locked.\r\n", 64); } else if (con->pop.user->lock_status == 2) { con_write_bl(con, "-ERR [SYS/PERM] This account has been locked for inactivity.\r\n", 62); } else if (con->pop.user->lock_status == 3) { con_write_bl(con, "-ERR [SYS/PERM] This account has been locked on suspicion of abuse.\r\n", 69); } else if (con->pop.user->lock_status == 4) { con_write_bl(con, "-ERR [SYS/PERM] This account has been locked at the request of the user.\r\n", 74); } else { con_write_bl(con, "-ERR [SYS/PERM] This account has been locked.\r\n", 47); } con->pop.user = NULL; meta_remove(con->pop.username, META_PROT_POP); return; } // SSL check. else if ((con->pop.user->flags & META_USER_SSL) == META_USER_SSL && con_secure(con) != 1) { con->pop.user = NULL; meta_remove(con->pop.username, META_PROT_POP); con_write_bl(con, "-ERR [SYS/PERM] This user account is configured to require that all POP sessions be connected over SSL.\r\n", 105); return; } // Single session check. else if (con->pop.user->refs.pop != 1) { con->pop.user = NULL; meta_remove(con->pop.username, META_PROT_POP); con_write_bl(con, "-ERR [IN-USE] This account is being used by another session. Please try again in a few minutes.\r\n", 97); return; } // Debug logging. log_pedantic("User %.*s logged in from %s via POP. {poprefs = %lu, imaprefs = %lu, messages = %lu}", st_length_int(con->pop.username), st_char_get(con->pop.username), st_char_get(con_addr_presentation(con, MANAGEDBUF(256))), con->pop.user->refs.pop, con->pop.user->refs.imap, con->pop.user->messages ? inx_count(con->pop.user->messages) : 0); // Update the log and unlock the session. meta_data_update_log(con->pop.user, META_PROT_POP); meta_user_wlock(con->pop.user); meta_messages_login_update(con->pop.user, META_LOCKED); meta_user_unlock(con->pop.user); // Update session state. con->pop.session_state = 1; // Tell the client everything worked. con_write_bl(con, "+OK Password accepted.\r\n", 24); return; }
/** * Returns 0 if the selected folder wasn't modified, or 1 if things changed and the updated status should be sent to the client. A -1 * is used to indicate the update check encountered a problem and should be retried later. */ int_t imap_session_update(connection_t *con) { int_t result = 0; inx_cursor_t *cursor; meta_message_t *active; uint64_t recent = 0, exists = 0, checkpoint; // Check for the right state. if (con->imap.session_state != 1 || con->imap.user == NULL || con->imap.selected == 0) { return -1; } if ((checkpoint = serial_get(OBJECT_USER, con->imap.user->usernum)) != con->imap.user_checkpoint) { meta_user_wlock(con->imap.user); // Update the user preferences. if (checkpoint != con->imap.user->serials.user) { meta_user_update(con->imap.user, META_LOCKED); } // Store the new checkpoint. con->imap.user_checkpoint = con->imap.user->serials.user; meta_user_unlock(con->imap.user); } if ((checkpoint = serial_get(OBJECT_FOLDERS, con->imap.user->usernum)) != con->imap.folders_checkpoint) { meta_user_wlock(con->imap.user); // Update the list of folders. if (checkpoint != con->imap.user->serials.folders) { meta_folders_update(con->imap.user, META_LOCKED); } // Store the new checkpoint. con->imap.folders_checkpoint = con->imap.user->serials.folders; meta_user_unlock(con->imap.user); } if ((checkpoint = serial_get(OBJECT_MESSAGES, con->imap.user->usernum)) != con->imap.messages_checkpoint) { meta_user_wlock(con->imap.user); if (checkpoint != con->imap.user->serials.messages) { meta_messages_update(con->imap.user, META_LOCKED); } // If there is a selected folder, scan the status. if ((cursor = inx_cursor_alloc(con->imap.user->messages))) { while ((active = inx_cursor_value_next(cursor))) { if (active->foldernum == con->imap.selected && (active->status & MAIL_STATUS_RECENT) == MAIL_STATUS_RECENT) { recent++; exists++; } else if (active->foldernum == con->imap.selected) { exists++; } } inx_cursor_free(cursor); } // If the folder has changed, output the current status. if (con->imap.messages_recent != recent || con->imap.messages_total != exists) { con->imap.messages_recent = recent; con->imap.messages_total = exists; result = 1; } // Store the new checkpoint. con->imap.messages_checkpoint = con->imap.user->serials.messages; meta_user_unlock(con->imap.user); } return result; }
/** * @brief Lookup user and return their meta user object. * * @note If the user is not found in the local session cache, the session will be constructed using the database, and then cached. * * @param usernum the numeric identifier for the user account. * @param username the official username stored in the database. * @param salt the user specific salt value. * @param master the user account's master encryption key which will be used to unlock the private storage key. * @param verification the verification token. * @param protocol a set of protocol specifying the protocol used by the calling function. Values can be META_PROT_NONE, * META_PROT_SMTP, META_PROT_POP, META_PROT_IMAP, META_PROT_WEB, or META_PROT_GENERIC. * @param get a set of protocol specifying the data to be retrieved (META_GET_NONE, META_GET_MESSAGES, * META_GET_FOLDERS, or META_GET_CONTACTS) * @param output the address of a meta user object that will store a pointer to the result of the lookup. * * @return -1 on error, 0 on success, 1 for an authentication issue. */ int_t meta_get(uint64_t usernum, stringer_t *username, stringer_t *salt, stringer_t *master, stringer_t *verification, META_PROTOCOL protocol, META_GET get, meta_user_t **output) { int_t state; meta_user_t *user = NULL; // If the auth structure is empty, or the usernum is invalid, return an error immediately. if (!usernum || !st_populated(username, master, verification)) { log_pedantic("Invalid parameters were used to get the meta data object."); return -1; } // Pull the user context using the usernum, or add an empty context if it doesn't exist. if (!(user = meta_inx_find(usernum, protocol))) { log_pedantic("Could not find an existing user object, nor could we create one."); return -1; } meta_user_wlock(user); // Pull the user information. if ((state = meta_update_user(user, META_LOCKED)) < 0) { meta_user_unlock(user); meta_inx_remove(usernum, protocol); return state; } // The auth_t object should have checked the verification token already, but we check here just to be sure. else if (st_empty(user->verification) || st_cmp_cs_eq(verification, user->verification)) { meta_user_unlock(user); meta_inx_remove(usernum, protocol); return 1; } // Are we supposed to get the realm keys. if ((get & META_GET_KEYS) && meta_update_realms(user, salt, master, META_LOCKED) < 0) { meta_user_unlock(user); meta_inx_remove(usernum, protocol); return -1; } // Are we supposed to get the mailbox keys. if ((get & META_GET_KEYS) && meta_update_keys(user, META_LOCKED) < 0) { // If key decryption fails, then the master key is likely invalid, so we need to ensure we don't cache an invalid // realm key as a result. st_cleanup(user->realm.mail); user->realm.mail = NULL; meta_user_unlock(user); meta_inx_remove(usernum, protocol); return -1; } // Are we supposed to get the mailbox aliases. if ((get & META_GET_ALIASES) && meta_update_aliases(user, META_LOCKED) < 0) { meta_user_unlock(user); meta_inx_remove(usernum, protocol); return -1; } // Are we supposed to get the messages. if ((get & META_GET_MESSAGES) && meta_messages_update(user, META_LOCKED) < 0) { meta_user_unlock(user); meta_inx_remove(usernum, protocol); return -1; } if ((get & META_GET_FOLDERS) && meta_update_message_folders(user, META_LOCKED) < 0) { meta_user_unlock(user); meta_inx_remove(usernum, protocol); return -1; } // Are we supposed to update the folders. if ((get & META_GET_FOLDERS) && meta_update_folders(user, META_LOCKED) < 0) { meta_user_unlock(user); meta_inx_remove(usernum, protocol); return -1; } // Are we supposed to update the folders. if ((get & META_GET_CONTACTS) && meta_update_contacts(user, META_LOCKED) < 0) { meta_user_unlock(user); meta_inx_remove(usernum, protocol); return -1; } *output = user; meta_user_unlock(user); return 0; }