/** * @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; }
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 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; }