/** * @brief Merge a list of smtp message headers into a single string, preceded by the leading text and followed by the trailing text. * @param headers an inx holder containing a collection of header string data to be merged together. * @param leading a managed string containing text that will lead each header line. * @param trailing a managed string containing text that will trail each header line. * @return NULL on failure or a managed string containing the merged headers on success. */ stringer_t * portal_smtp_merge_headers(inx_t *headers, stringer_t *leading, stringer_t *trailing) { stringer_t *result = NULL, *tmp, *current; inx_cursor_t *cursor; if (!headers || !leading || !trailing) { return NULL; } if (!(cursor = inx_cursor_alloc(headers))) { return NULL; } while ((current = inx_cursor_value_next(cursor))) { if (!(tmp = st_merge("ssss", result, leading, current, trailing))) { inx_cursor_free(cursor); st_cleanup(result); return NULL; } result = tmp; } inx_cursor_free(cursor); // We should at least return an empty managed string if we have a valid inx holder but no data in it. if (!result) { result = st_alloc(0); } return result; }
bool_t check_inx_cursor_sthread(check_inx_opt_t *opts) { void *val; inx_t *inx; multi_t key; inx_cursor_t *cursor; uint64_t rnum, num = 0; if (!opts || !(inx = opts->inx) || !(cursor = inx_cursor_alloc(opts->inx))) { return false; } while (!(val = inx_cursor_value_next(cursor))) { key = inx_cursor_key_active(cursor); uint64_conv_st(val, &rnum); if (key.type != M_TYPE_UINT64 || key.val.u64 != rnum) { inx_cursor_free(cursor); return false; } num++; } inx_cursor_free(cursor); // Make sure we iterated through more than 90% of possible values num. if (num >= ((INX_CHECK_OBJECTS * INX_CHECK_MTHREADS) * 0.9)) { return false; } return true; }
/** * @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; }
// QUESTION: Shouldn't failure return -1? int_t magma_folder_children(inx_t *folders, uint64_t target) { int_t result = 0; inx_cursor_t *cursor; magma_folder_t *active = NULL; if (!target || !(cursor = inx_cursor_alloc(folders))) { return 0; } while ((active = inx_cursor_value_next(cursor)) && !result) { if (active->parent == target) { result++; } } inx_cursor_free(cursor); return result; }
/** * Verifies that we can read all of the data out of the storage tank correctly. * * @param check_collection The collection of sums to verify. * @return Returns true only if all of the objects in the linked list match the expected hash value. */ bool_t check_tokyo_tank_cleanup(inx_t *check_collection) { inx_cursor_t *cursor; check_tank_obj_t *obj; if (!(cursor = inx_cursor_alloc(check_collection))) { return false; } while (status() && (obj = inx_cursor_value_next(cursor))) { if (!tank_delete(TANK_CHECK_DATA_HNUM, obj->tnum, TANK_CHECK_DATA_UNUM, obj->onum)) { log_info("%lu - tank_delete error", obj->onum); inx_cursor_free(cursor); return false; } } inx_cursor_free(cursor); return true; }
/** * @brief Populate a contact entry with its details from the database. * @param contact a pointer to the contact entry object that will be updated. * @return -1 on failure or 1 on success. */ int_t contact_details_fetch(contact_t *contact) { row_t *row; table_t *result; MYSQL_BIND parameters[1]; contact_detail_t *record; multi_t key = { .type = M_TYPE_STRINGER, .val.st = NULL }; mm_wipe(parameters, sizeof(parameters)); if (!contact || !contact->contactnum) { log_pedantic("Invalid data passed to contact details fetch."); return -1; } // Contact Number parameters[0].buffer_type = MYSQL_TYPE_LONGLONG; parameters[0].buffer_length = sizeof(uint64_t); parameters[0].buffer = &(contact->contactnum); parameters[0].is_unsigned = true; if (!(result = stmt_get_result(stmts.select_contact_details, parameters))) { log_pedantic("Unable to fetch the contact details."); return -1; } // Loop through each of the row and create a contact record. When were finished we'll fetch the contact details for each record found. while ((row = res_row_next(result))) { if (!(record = contact_detail_alloc(PLACER(res_field_block(row, 0), res_field_length(row, 0)), PLACER(res_field_block(row, 1), res_field_length(row, 1)), res_field_uint64(row, 2))) || !(key.val.st = record->key) || !inx_insert(contact->details, key, record)) { log_info("The index refused to accept a contact record. { contact = %lu }", res_field_uint64(row, 0)); if (record) { contact_detail_free(record); } res_table_free(result); return -1; } } res_table_free(result); return 1; } /** * @brief Retrieve all of a user's contact entries in a specified contacts folder from the database. * @param usernum the numerical id of the user whose contacts will be retrieved. * @param folder a pointer to the contact folder which will have its contents listed. * @return -1 on failure or 1 on success. */ int_t contacts_fetch(uint64_t usernum, contact_folder_t *folder) { row_t *row; table_t *result; contact_t *record; inx_cursor_t *cursor; MYSQL_BIND parameters[2]; multi_t key = { .type = M_TYPE_UINT64, .val.u64 = 0 }; mm_wipe(parameters, sizeof(parameters)); if (!usernum || !folder || !folder->foldernum) { log_pedantic("Invalid data passed for contact fetch."); return -1; } // Usernum parameters[0].buffer_type = MYSQL_TYPE_LONGLONG; parameters[0].buffer_length = sizeof(uint64_t); parameters[0].buffer = &(usernum); parameters[0].is_unsigned = true; // Folder Number parameters[1].buffer_type = MYSQL_TYPE_LONGLONG; parameters[1].buffer_length = sizeof(uint64_t); parameters[1].buffer = &(folder->foldernum); parameters[1].is_unsigned = true; if (!(result = stmt_get_result(stmts.select_contacts, parameters))) { log_pedantic("Unable to fetch the folder contacts."); return -1; } // Loop through each of the row and create a contact record. When were finished we'll fetch the contact details for each record found. while ((row = res_row_next(result))) { if (!(record = contact_alloc(res_field_uint64(row, 0), PLACER(res_field_block(row, 1), res_field_length(row, 1)))) || !(key.val.u64 = record->contactnum) || !inx_insert(folder->records, key, record)) { log_info("The index refused to accept a contact record. { contact = %lu }", res_field_uint64(row, 0)); if (record) contact_free(record); res_table_free(result); return -1; } } res_table_free(result); /// LOW: Should we bother with error checking? if ((cursor = inx_cursor_alloc(folder->records))) { while ((record = inx_cursor_value_next(cursor))) { contact_details_fetch(record); } inx_cursor_free(cursor); } return 1; }
/** * Verifies that we can read all of the data out of the storage tank correctly. * * @param check_collection The collection of sums to verify. * @return Returns true only if all of the objects in the linked list match the expected hash value. */ bool_t check_tokyo_tank_verify(inx_t *check_collection) { stringer_t *data; check_tank_obj_t *obj; inx_cursor_t *cursor; if (!(cursor = inx_cursor_alloc(check_collection))) { return false; } while (status() && (obj = inx_cursor_value_next(cursor))) { if (!(data = tank_load(TANK_CHECK_DATA_HNUM, obj->tnum, TANK_CHECK_DATA_UNUM, obj->onum))) { log_info("%lu - tank_get error", obj->onum); inx_cursor_free(cursor); return false; } if (obj->adler32 != hash_adler32(st_data_get(data), st_length_get(data))) { log_info("%lu - adler32 error", obj->onum); inx_cursor_free(cursor); st_free(data); return false; } if (obj->fletcher32 != hash_fletcher32(st_data_get(data), st_length_get(data))) { log_info("%lu - fletcher32 error", obj->onum); inx_cursor_free(cursor); st_free(data); return false; } if (obj->crc32 != hash_crc32(st_data_get(data), st_length_get(data))) { log_info("%lu - crc32 error", obj->onum); inx_cursor_free(cursor); st_free(data); return false; } if (obj->crc64 != hash_crc64(st_data_get(data), st_length_get(data))) { log_info("%lu - crc64 error", obj->onum); inx_cursor_free(cursor); st_free(data); return false; } if (obj->murmur32 != hash_murmur32(st_data_get(data), st_length_get(data))) { log_info("%lu - murmur32 error", obj->onum); inx_cursor_free(cursor); st_free(data); return false; } if (obj->murmur64 != hash_murmur64(st_data_get(data), st_length_get(data))) { log_info("%lu - murmur64 error", obj->onum); inx_cursor_free(cursor); st_free(data); return false; } st_free(data); } inx_cursor_free(cursor); return true; }
/** * 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 Send (relay) a message composed by a user via a portal session. * @see smtp_relay_message() - a lot of logic borrowed from here. * @param from a pointer to a managed string containing the email address specified as the From address. * @param to a pointer to a managed string containing the destination email address of the message. * @param data a pointer to a managed string containing the raw data of the mail message. * @param send_size if greater than 0, specify the optional SIZE parameter to the MAIL FROM command. * @param errmsg the address of a pointer to a null-terminated string that will be set to a descriptive error message on failure. * @return true if the mail message was sent successfully, or false otherwise. */ bool_t portal_smtp_relay_message(stringer_t *from, inx_t *to, stringer_t *data, size_t send_size, chr_t **errmsg) { inx_cursor_t *cursor; stringer_t *to_address; client_t *client; int_t state, nsentto = 0; if (!from || !to || !data || !errmsg) { *errmsg = "Unexpected internal failure occurred while sending message."; return false; } /*if (mail_add_outbound_headers(con) != 1) { log_pedantic("Could not add the outbound headers."); return false; }*/ // Open the connection to the SMTP server. if (!(client = smtp_client_connect(0))) { *errmsg = "Encountered transport error with relay server."; return false; } // Send HELO. if (smtp_client_send_helo(client)!= 1) { *errmsg = "Handshake with relay server failed."; smtp_client_close(client); return false; } // Send MAIL FROM. if ((state = smtp_client_send_mailfrom(client, from, send_size)) != 1) { *errmsg = "Error occurred while sending From field to email server."; smtp_client_close(client); return false; } // Send the RCPT TO command. if (!(cursor = inx_cursor_alloc(to))) { *errmsg = "Internal occurred while expanding recipient list."; smtp_client_close(client); return false; } while ((to_address = inx_cursor_value_next(cursor))) { if ((state = smtp_client_send_rcptto(client, to_address)) != 1) { *errmsg = "Error occurred while specifying recipient to email server."; smtp_client_close(client); inx_cursor_free(cursor); return false; } nsentto++; } inx_cursor_free(cursor); if (!nsentto) { *errmsg = "Mail message could not be sent without recipient."; smtp_client_close(client); return false; } // Send the the message. if ((state = smtp_client_send_data(client, data)) != 1) { *errmsg = "Error occurred while sending email message body."; smtp_client_close(client); return false; } // Store the result. //*result = st_dupe_opts(MANAGED_T | CONTIGUOUS | HEAP, &(client->line)); smtp_client_close(client); // TODO: smtp_update_transmission_stats() needs to be called here. return true; }
/** * @brief Create the data of an outbound smtp message that will be specified with the smtp DATA command. * @param attachments an optional inx holder containing a list of attachments to be included in the message. * @param from a managed string containing the email address sending the message. * @param to an inx holder containing a list of To: email recipients as managed strings. * @param cc an inx holder containing a list of 0 or more CC: recipients as managed strings. * @param bcc an inx holder containing a list of 0 or more BCC: recipients as managed strings. * @param subject a managed string containing the subject of the message. * @param body_plain an optional managed string containing the plain text body of the message. * @param body_html an optional managed string containing the html-formatted body of the message. * @return NULL on failure or a managed string containing the packaged outbound smtp message data on success. */ stringer_t * portal_smtp_create_data(inx_t *attachments, stringer_t *from, inx_t *to, inx_t *cc, inx_t *bcc, stringer_t *subject, stringer_t *body_plain, stringer_t *body_html) { inx_cursor_t *cursor = NULL; array_t *all_attachments = NULL; attachment_t *attachment; stringer_t *result = NULL, *boundary = NULL, *mime_data, *tmp; size_t nattached = 0; if (attachments && (!(cursor = inx_cursor_alloc(attachments)))) { log_pedantic("Unable to read message attachments."); return NULL; } else if (attachments) { while ((attachment = inx_cursor_value_next(cursor))) { // We are creating an array of all the attachment data. It needs to be ARRAY_TYPE_POINTER so deallocating the array doesn't free the underlying data. if (attachment->filedata && !ar_append(&all_attachments, ARRAY_TYPE_POINTER, attachment->filedata)) { log_pedantic("Unable to parse message attachments."); inx_cursor_free(cursor); if (all_attachments) ar_free(all_attachments); return NULL; } } if (all_attachments) { nattached = ar_length_get(all_attachments); } } if (!nattached) { if (cursor) { inx_cursor_free(cursor); } if (!(result = mail_mime_get_smtp_envelope(from, to, cc, bcc, subject, boundary, false))) { log_pedantic("Unable to generate smtp envelope for outbound mail."); return NULL; } } else { // TODO: This should really happen after encoding is performed, but the chances of a collision are still infinitesimal. // Now that we have all the attachment data in an array, we can generate a unique boundary string. if (!(boundary = mail_mime_generate_boundary(all_attachments))) { log_pedantic("Unable to generate boundary for MIME attachments."); inx_cursor_free(cursor); ar_free(all_attachments); return NULL; } ar_free(all_attachments); // Get the envelope for a message that has attachments. if (!(result = mail_mime_get_smtp_envelope(from, to, cc, bcc, subject, boundary, true))) { log_pedantic("Unable to generate smtp envelope for outbound mail."); inx_cursor_free(cursor); st_free(boundary); return NULL; } // Now that we have the envelope, the first content is the actual email body. if (!(tmp = st_merge("snsnsn", result, "--------------", boundary, "\r\nContent-Type: text-plain\r\nContent-Transfer-Encoding: 7bit\r\n\r\n", body_plain, "\r\n"))) { log_pedantic("Unable to pack message body into outbound message."); inx_cursor_free(cursor); st_free(boundary); return NULL; } st_free(result); result = tmp; // Now go through each attachment, encode it, and append it to the envelope. inx_cursor_reset(cursor); while ((attachment = inx_cursor_value_next(cursor))) { if (!(mime_data = mail_mime_encode_part(attachment->filedata, attachment->filename, boundary))) { log_pedantic("Unable to mime encode part for message attachment."); inx_cursor_free(cursor); st_free(boundary); return NULL; } tmp = st_merge("ss", result, mime_data); st_free(mime_data); st_free(result); if (!tmp) { log_pedantic("Unable to allocate space for portal smtp message."); inx_cursor_free(cursor); return NULL; } result = tmp; } inx_cursor_free(cursor); // One final boundary at the end. tmp = st_merge("snsn", result, "\r\n--------------", boundary, "--"); st_free(result); if (!tmp) { log_pedantic("Unable to allocate space for portal smtp message."); return NULL; } result = tmp; } return result; }
/** * @brief Load all magma configuration options specified by the user on the command line. * @note Each key/value pair extracted from the database is submitted to the following logic: * If a config option was loaded from the database, the key must allow it to be configurable via the database. * Check to see that any key that has previously been set is allowed to be overwritten. * If the key is required, it may not contain an empty value. * Finally, this function sets the appropriate magma key corresponding to the config key. * All leftover keys not matched to global magma keys will be configured via servers, relay, and cache server options. * @return true if all database config options were parsed and evaluated successfully, or false on failure. */ bool_t config_load_cmdline_settings(void) { multi_t name; magma_keys_t *key; inx_cursor_t *cursor; nvp_t *config_pairs = NULL; stringer_t *value; // If not set, then bail out. if (!cmdline_config_data) return true; // Load the command line options and convert them into a name/value pair structure. if (!(config_pairs = nvp_alloc())) { st_free(cmdline_config_data); return false; } else if (nvp_parse(config_pairs, cmdline_config_data) < 0) { nvp_free(config_pairs); st_free(cmdline_config_data); return false; } else if (!(cursor = inx_cursor_alloc(config_pairs->pairs))) { nvp_free(config_pairs); st_free(cmdline_config_data); return false; } // Our command line config data won't be necessary anymore. st_free(cmdline_config_data); // Run through all of the magma_keys and see if there is a matching name/value pair. while (!mt_is_empty(name = inx_cursor_key_next(cursor))) { value = inx_cursor_value_active(cursor); if ((key = config_key_lookup(name.val.st))) { // Make sure the setting can be provided via the configuration file. if (!key->file && value) { log_critical("%s cannot be changed using command line option.", key->name); inx_cursor_free(cursor); nvp_free(config_pairs); return false; } // Make sure the required magma_keys are not set to NULL. else if (key->required && st_empty(value)) { log_critical("%s requires a legal value.", key->name); inx_cursor_free(cursor); nvp_free(config_pairs); return false; } // Attempt to set the value. else if (!config_value_set(key, value)) { inx_cursor_free(cursor); nvp_free(config_pairs); return false; } // If a legit value was provided, then record that we've set this parameter. key->set = true; } // If we haven't had a match yet, check if its a server param. else if (name.val.st && !st_cmp_ci_starts(name.val.st, CONSTANT("magma.servers"))) { servers_config(name.val.st, value); } // If we haven't had a match yet, check if its a relay instance. else if (name.val.st && !st_cmp_ci_starts(name.val.st, CONSTANT("magma.relay"))) { relay_config(name.val.st, value); } else if (name.val.st && !st_cmp_ci_starts(name.val.st, CONSTANT("magma.iface.cache.host"))) { cache_config(name.val.st, value); } else { log_critical("%.*s is not a valid setting.", st_length_int(name.val.st), st_char_get(name.val.st)); inx_cursor_free(cursor); nvp_free(config_pairs); return false; } } inx_cursor_free(cursor); nvp_free(config_pairs); return true; }