bool_t check_string_dupe(char *type, uint32_t check) { size_t len; stringer_t *s, *d; if (!(s = st_alloc(check, st_length_int(constant)))) { return false; } len = snprintf(st_char_get(s), st_length_int(constant) + 1, "%.*s", st_length_int(constant), st_char_get(constant)); if (check & MAPPED_T || check & MANAGED_T) { st_length_set(s, len); } if (!(d = st_dupe(s))) { st_free(s); return false; } log_print("%28.28s = %.*s", type, st_length_int(d), st_char_get(d)); if (memcmp(st_char_get(d), st_char_get(s), st_length_get(s))) { st_free(s); st_free(d); return false; } st_free(s); st_free(d); return true; }
bool_t check_string_dupe(uint32_t check) { size_t len; stringer_t *s, *d; if (!(s = st_alloc_opts(check, st_length_int(string_check_constant)))) { return false; } len = snprintf(st_char_get(s), st_length_int(string_check_constant) + 1, "%.*s", st_length_int(string_check_constant), st_char_get(string_check_constant)); if ((check & MAPPED_T) || (check & MANAGED_T)) { st_length_set(s, len); } if (!(d = st_dupe(s))) { st_free(s); return false; } if (memcmp(st_char_get(d), st_char_get(s), st_length_get(s))) { st_free(s); st_free(d); return false; } st_free(s); st_free(d); return true; }
/** * @brief Acquire a named lock, with synchronization provided via memcached. * @see cache_silent_add() * @note The lock will be held for a maximum of 10 minutes, and failed locking attempts will be retried * periodically for a maxmimum of 1 minute before returing failure. * @param key a managed string containing the name of the lock to be acquired. * @return -1 on general failure, 0 on memcached failure, or 1 on success. */ int_t lock_get(stringer_t *key) { uint64_t value; stringer_t *lock = MANAGEDBUF(128); int_t success, iterations = MAGMA_LOCK_TIMEOUT; // const struct timespec delay = { .tv_sec = 0, .tv_nsec = 1000000000 }; const struct timespec delay = { .tv_sec = 1, .tv_nsec = 0 }; // Build the key. if (st_empty(key) || st_sprint(lock, "%.*s.lock", st_length_int(key), st_char_get(key)) <= 0) { log_pedantic("Unable generate the accessor for the cluster lock."); return -1; } // Build the lock value. value = time(NULL); do { // Keep the lock for ten minutes. if ((success = cache_silent_add(lock, PLACER(&value, sizeof(uint64_t)), MAGMA_LOCK_EXPIRATION)) != 1) { nanosleep(&delay, NULL); } } while (success != 1 && iterations--); #ifdef MAGMA_PEDANTIC if (success != 1) log_pedantic("Unable to obtain a cluster lock for %.*s.", st_length_int(lock), st_char_get(lock)); #endif return success; } /** * @brief Release a named lock, with synchronization provided via memcached. * @see cache_delete() * @note The lock will be held for 10 seconds, and locking attempts will occur periodically for 60 seconds prior to failure. * @param key a managed string containing the name of the lock to be released. * @return -1 on general failure, 0 on memcached failure, or 1 on success. */ void lock_release(stringer_t *key) { stringer_t *lock = MANAGEDBUF(128); // Build the key. if (st_empty(key) || st_sprint(lock, "%.*s.lock", st_length_int(key), st_char_get(key)) <= 0) { log_pedantic("Unable generate the accessor for the cluster lock."); return; } /// LOW: At some point we should add logic to check whether this cluster node even owns the lock before /// blindly deleting the lock. cache_delete(lock); return; }
/** * @brief Increment the serial number for an object in memcached. * @param type the serial type to be queried (OBJECT_USER, OBJECT_CONFIG, OBJECT_FOLDERS, OBJECT_MESSAGES, or OBJECT_CONTACTS). * @param num the specific object identifier. * @return 0 on failure or the new serial number of the requested object. */ uint64_t serial_increment(uint64_t type, uint64_t num) { uint64_t result = 0; stringer_t *key, *prefix; // Build retrieval key. if (!(prefix = serial_prefix(type)) || !(key = st_aprint("magma.%.*s.%lu", st_length_int(prefix), st_char_get(prefix), num))) { log_pedantic("Unable to build %.*s serial key.", st_length_int(prefix), st_char_get(prefix)); return 0; } // Increment the key. result = cache_increment(key, 1, 1, 2592000); st_free(key); return result; }
/** * @brief Get the serial number (checkpoint value) for an object from memcached. * @param type the serial type to be queried (OBJECT_USER, OBJECT_CONFIG, OBJECT_FOLDERS, OBJECT_MESSAGES, or OBJECT_CONTACTS). * @param num the specific object identifier. * @return 0 on failure or the serial number of the requested object. */ uint64_t serial_get(uint64_t type, uint64_t num) { uint64_t result = 0; stringer_t *key, *prefix; // Build retrieval key. // QUESTION: This is used a few times and definitely should be its own function. if (!(prefix = serial_prefix(type)) || !(key = st_aprint("magma.%.*s.%lu", st_length_int(prefix), st_char_get(prefix), num))) { log_pedantic("Unable to build %.*s serial key.", st_length_int(prefix), st_char_get(prefix)); return 0; } // Get the key value. The increment functions store the value in binary form, so we must use them to access the value, even if we aren't incrementing the value. result = cache_increment(key, 0, 0, 2592000); st_free(key); return result; }
bool_t check_string_import(void) { stringer_t *s; if (!(s = st_import(st_data_get(constant), st_length_int(constant)))) { return false; } log_print("%28.28s = %.*s", "duplicate", st_length_int(s), st_char_get(s)); if (memcmp(st_char_get(s), st_char_get(constant), st_length_get(constant))) { st_free(s); return false; } st_free(s); return true; }
/** * @brief Reset the serial number to 1 for an object in memcached. * @param type the serial type to be queried (OBJECT_USER, OBJECT_CONFIG, OBJECT_FOLDERS, OBJECT_MESSAGES, or OBJECT_CONTACTS). * @param num the specific object identifier. * @return 0 on failure or the new value of the cached serial number (1) on success. */ uint64_t serial_reset(uint64_t type, uint64_t num) { uint64_t result = 0; stringer_t *key, *prefix; // Build key. if (!(prefix = serial_prefix(type)) || !(key = st_aprint("magma.%.*s.%lu", st_length_int(prefix), st_char_get(prefix), num))) { log_pedantic("Unable to build %.*s serial key.", st_length_int(prefix), st_char_get(prefix)); return 0; } // If were able to set the key, return the value one. if (cache_set(key, CONSTANT("1"), 2592000) == 1) { result = 1; } st_free(key); return result; }
/** * @brief Insert a spam signature training link into a mail message. * @see mail_modify_part() * @param message the mail message object of the message to be modified. * @param server the server object of the web server where the teacher application is hosted. * @param signum the spam signature number referenced by the teacher url. * @param sigkey the spam signature key for client verification in the teacher app. * @param disposition if 0, the message disposition is "innocent"; otherwise, the disposition specifies "spam". * @return This function returns no value. */ void mail_signature_add(mail_message_t *message, server_t *server, uint64_t signum, uint64_t sigkey, int_t disposition) { stringer_t *part; part = PLACER(st_char_get(message->text), st_length_get(message->text)); if ((mail_modify_part(server, message, part, signum, sigkey, disposition, 0)) == 0) { log_pedantic("------ MESSAGE ---------\n%.*s------------------", st_length_int(message->text), st_char_get(message->text)); } return; }
bool_t check_string_import(void) { stringer_t *s; if (!(s = st_import(st_data_get(string_check_constant), st_length_int(string_check_constant)))) { return false; } if (memcmp(st_char_get(s), st_char_get(string_check_constant), st_length_get(string_check_constant))) { st_free(s); return false; } st_free(s); return true; }
/** * @brief Return the fully qualified local file path of a stored mail message for a specified message number and server. * @param number the mail message id. * @param server the hostname of the server where the message data resides or if NULL, the default server. * @return NULL on failure, or a pointer to a null-terminated string containing the absolute file path of the specified message. */ chr_t * mail_message_path(uint64_t number, chr_t *server) { chr_t *result; if (!(result = ns_alloc(1024))) { log_pedantic("Unable to allocate a buffer of %i bytes for the storage path.", 1024); return NULL; } // The default storage server. if (!server) { server = st_char_get(magma.storage.active); } // Build the message path. if ((snprintf(result, 1024, "%.*s/%s/%lu/%lu/%lu/%lu/%lu", st_length_int(magma.storage.root), st_char_get(magma.storage.root), server, number / 32768 / 32768 / 32768 / 32768, number / 32768 / 32768 / 32768 , number / 32768 / 32768, number / 32768, number)) <= 0) { log_pedantic("Unable to create the message path."); ns_free(result); return NULL; } return result; }
/** * @brief Store a mail message, with its meta-information in the database, and the contents persisted to disk. * @note The stored message is always compressed, but only encrypted if the user's public key is suppplied. * @param usernum the numerical id of the user to which the message belongs. * @param pubkey if not NULL, a public key that will be used to encrypt the message for the intended user. * @param foldernum the folder # that will contain the message. * @param status a pointer to the status flags value for the message, which will be updated if the message is to be encrypted. * @param signum the spam signature for the message. * @param sigkey the spam key for the message. * @param message a managed string containing the raw body of the message. * @return 0 on failure, or the newly inserted id of the message in the database on success. * */ uint64_t mail_store_message(uint64_t usernum, stringer_t *pubkey, uint64_t foldernum, uint32_t *status, uint64_t signum, uint64_t sigkey, stringer_t *message) { chr_t *path; cryptex_t *encrypted = NULL; compress_t *reduced; uint64_t messagenum; int64_t transaction, ret; size_t write_len; uint8_t fflags = FMESSAGE_OPT_COMPRESSED; uchr_t *write_data; bool_t store_result; // Compress the message. if (!(reduced = compress_lzo(message))) { log_error("An error occurred while attempting to compress a message with %zu bytes.", st_length_get(message)); return 0; } // Next, encrypt the message if necessary. if (pubkey) { *status |= MAIL_STATUS_ENCRYPTED; if (!(encrypted = ecies_encrypt(pubkey, ECIES_PUBLIC_BINARY, reduced, compress_total_length(reduced)))) { log_pedantic("Unable to decrypt mail message."); compress_free(reduced); return 0; } compress_free(reduced); write_data = (uchr_t *)encrypted; write_len = cryptex_total_length(encrypted); fflags |= FMESSAGE_OPT_ENCRYPTED; } else { write_data = (uchr_t *)reduced; write_len = compress_total_length(reduced); } // Begin the transaction. if ((transaction = tran_start()) < 0) { log_error("Could not start a transaction. {start = %li}", transaction); if (encrypted) { cryptex_free(encrypted); } else { compress_free(reduced); } return 0; } // Insert a record into the database. if ((messagenum = mail_db_insert_message(usernum, foldernum, *status, st_length_int(message), signum, sigkey, transaction)) == 0) { log_pedantic("Could not create a record in the database. mail_db_insert_message = 0"); tran_rollback(transaction); if (encrypted) { cryptex_free(encrypted); } else { compress_free(reduced); } return 0; } // Now attempt to save everything to disk. store_result = mail_store_message_data(messagenum, fflags, write_data, write_len, &path); if (encrypted) { cryptex_free(encrypted); } else { compress_free(reduced); } // If storage failed, fail out. if (!store_result || !path) { log_pedantic("Failed to store user's message to disk."); tran_rollback(transaction); if (path) { unlink(path); ns_free(path); } return 0; } // Commit the transaction. if ((ret = tran_commit(transaction))) { log_error("Could not commit the transaction. { commit = %li }", ret); unlink(path); ns_free(path); return 0; } ns_free(path); return messagenum; }
/** * @brief Perform client command processing on an established imap session. * @note This function will read the next line of user input, parse the command, and then attempt to execute it with the appropriate handler. * @param con a pointer to the connection object underlying the imap session. * @return This function returns no value. */ void imap_process(connection_t *con) { int_t state; command_t *command, client = { .function = NULL }; // If the connection indicates an error occurred, or the socket was closed by the client we send the connection to the logout function. if (((state = con_read_line(con, false)) < 0) || (state == -2)) { con->command = NULL; enqueue(&imap_logout, con); return; } else if (pl_empty(con->network.line) && ((con->protocol.spins++) + con->protocol.violations) > con->server->violations.cutoff) { con->command = NULL; enqueue(&imap_logout, con); return; } else if (pl_empty(con->network.line)) { con->command = NULL; enqueue(&imap_process, con); return; } // Parse the line into its tag and command elements. if ((state = imap_command_parser(con)) < 0) { // Try to be helpful about the parsing error. if (state == -1) { con_write_bl(con, "* BAD Unable to parse the command tag.\r\n", 40); } else if (state == -2) { con_print(con, "%.*s BAD Unable to parse the command.\r\n", st_length_int(con->imap.tag), st_char_get(con->imap.tag)); } else { con_print(con, "%.*s BAD The command arguments were submitted using an invalid syntax.\r\n", st_length_int(con->imap.tag), st_char_get(con->imap.tag)); } // If the client keeps breaking rules drop them. if (((con->protocol.spins++) + con->protocol.violations) > con->server->violations.cutoff) { con->command = NULL; enqueue(&imap_logout, con); return; } // Requeue and hope the next line of data is useful. con->command = NULL; enqueue(&imap_process, con); return; } client.string = st_char_get(con->imap.command); client.length = st_length_get(con->imap.command); if ((command = bsearch(&client, imap_commands, sizeof(imap_commands) / sizeof(imap_commands[0]), sizeof(command_t), imap_compare))) { con->command = command; con->protocol.spins = 0; if (command->function == &imap_logout) { enqueue(command->function, con); } else { requeue(command->function, &imap_requeue, con); } } else { con->command = NULL; requeue(&imap_invalid, &imap_requeue, con); } return; }
/** * @brief Check to see that a client from a given IP address hasn't exceeded its daily quota of contact requests. * @note Each IP address will be limited to at most 2 contact requests in any 24-hour period. * @param con a pointer to the connection object of the remote host making the contact request. * @param branch a null-terminated string specifying where the contact request was directed ("Abuse" or "Contact"). * @return true if the specified connection failed the abuse check or false if it did not. */ bool_t contact_abuse_checks(connection_t *con, chr_t *branch) { stringer_t *key = MANAGEDBUF(64), *ip = NULL; // Build the key. if (!(ip = con_addr_presentation(con, ip)) && st_sprint(key, "magma.web.contact.history.%.*s", st_length_int(ip), st_char_get(ip)) > 0 && cache_get_u64(key) >= 2) { contact_print_message(con, branch, "To prevent abuse, our system only allows you to submit two letters to our team in a twenty-four hour period. If you need " "to submit another message please return in twenty-four hours and try again"); return true; } return false; }
/** * @brief Increment the contact abuse history counter for an IP address. * @param con a pointer to the connection object of the remote host making the contact request. * @return This function returns no value. */ void contact_abuse_increment_history(connection_t *con) { stringer_t *key = MANAGEDBUF(64), *ip = NULL; // Build the key. if (!(ip = con_addr_presentation(con, ip)) && st_sprint(key, "magma.web.contact.history.%.*s", st_length_int(ip), st_char_get(ip)) > 0) { cache_increment(key, 1, 1, 86400); } return; }
/** * @brief Read data from a network connection, and store the data in the connection context buffer. * @return -1 on general failure, -2 if the connection was reset, or the amount of data that was read. */ int64_t client_read(client_t *client) { int_t counter = 0; ssize_t bytes = 0; bool_t blocking = true; stringer_t *error = NULL; #ifdef MAGMA_PEDANTIC int_t local = 0; stringer_t *ip = NULL, *cipher = NULL; #endif if (!client || client->sockd == -1 || client_status(client) < 0) { return -1; } // Check for data past the current line buffer. else if (pl_length_get(client->line) && st_length_get(client->buffer) > pl_length_get(client->line)) { // Move the unused data to the front of the buffer. mm_move(st_data_get(client->buffer), st_data_get(client->buffer) + pl_length_get(client->line), st_length_get(client->buffer) - pl_length_get(client->line)); // Update the length. st_length_set(client->buffer, st_length_get(client->buffer) - pl_length_get(client->line)); // Clear the line buffer. client->line = pl_null(); } // Otherwise reset the buffer and line lengths to zero. else { st_length_set(client->buffer, 0); client->line = pl_null(); } // Loop until the buffer has data or we get an error. do { blocking = st_length_get(client->buffer) ? false : true; // Read bytes off the network. If data is already in the buffer this should be a non-blocking read operation so we can // return the already buffered data without delay. if (client->tls) { // If bytes is zero or below and the library isn't asking for another read, then an error occurred. bytes = tls_read(client->tls, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), blocking); // If zero bytes were read, or a negative value was returned to indicate an error, call tls_erorr(), which will return // NULL if the error can be safely ignored. Otherwise log the output for debug purposes. if (bytes <= 0 && (error = tls_error(client->tls, bytes, MANAGEDBUF(512)))) { #ifdef MAGMA_PEDANTIC cipher = tls_cipher(client->tls, MANAGEDBUF(128)); ip = ip_presentation(client->ip, MANAGEDBUF(INET6_ADDRSTRLEN)); log_pedantic("TLS client read operation failed. { ip = %.*s / %.*s / result = %zi%s%.*s }", st_length_int(ip), st_char_get(ip), st_length_int(cipher), st_char_get(cipher), bytes, (error ? " / " : ""), st_length_int(error), st_char_get(error)); #endif client->status = -1; return -1; } // This will occur when the read operation results in a 0, or negative value, but TLS error returns NULL to // indicate it was a transient error. For transient errors we simply set bytes equal to 0 so the read call gets retried. else if (bytes <= 0) { bytes = 0; } } else { errno = 0; bytes = recv(client->sockd, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), (blocking ? 0 : MSG_DONTWAIT)); // Check for errors on non-SSL reads in the traditional way. if (bytes <= 0 && tcp_status(client->sockd)) { #ifdef MAGMA_PEDANTIC local = errno; ip = ip_presentation(client->ip, MANAGEDBUF(INET6_ADDRSTRLEN)); log_pedantic("TCP client read operation failed. { ip = %.*s / result = %zi / error = %i / message = %s }", st_length_int(ip), st_char_get(ip), bytes, local, strerror_r(local, MEMORYBUF(1024), 1024)); #endif client->status = -1; return -1; } } // We actually read in data, so we need to update the buffer to reflect the amount of data it currently holds. if (bytes > 0) { st_length_set(client->buffer, st_length_get(client->buffer) + bytes); } } while (blocking && counter++ < 128 && !st_length_get(client->buffer) && status()); // If there is data in the buffer process it. Otherwise if the buffer is empty and the connection appears to be closed // (as indicated by a return value of 0), then return -1 to let the caller know the connection is dead. if (st_length_get(client->buffer)) { client->status = 1; } else if (!bytes) { client->status = 2; return -2; } return st_length_get(client->buffer); }
void imap_command_log_safe(stringer_t *line) { uchr_t *stream, *copy; size_t len, loc = 0; int_t i; if (st_empty_out(line, &stream, &len)) { return; } // A command string that doesn't even contain "LOGIN" is inherently "safe". if (!st_search_ci(line, PLACER("LOGIN", 5), &loc) || !loc) { log_info("%.*s", st_length_int(line), st_char_get(line)); return; } // The LOGIN command should have been preceded by a whitespace. if (!chr_whitespace(stream[loc-1])) { log_info("%.*s", st_length_int(line), st_char_get(line)); return; } // There should be should only be ONE more non-whitespace tag before the LOGIN command. for (i = loc-1; i >= 0; i--) { if (!chr_whitespace(stream[i])) { break; } } if (i < 0) { log_info("%.*s", st_length_int(line), st_char_get(line)); return; } while (i >= 0) { if (chr_whitespace(stream[i])) { log_info("%.*s", st_length_int(line), st_char_get(line)); return; } i--; } if (!(copy = mm_dupe(stream, len))) { return; } // Skip past "LOGIN" ... i = loc + 5; // and the trailing spaces. while ((i < len) && chr_whitespace(stream[i])) { i++; } if (i == len) { log_info("%.*s", st_length_int(line), st_char_get(line)); mm_free(copy); return; } // The next parameter is the username. Skip past that as well. while ((i < len) && !chr_whitespace(stream[i])) { i++; } while ((i < len) && chr_whitespace(stream[i])) { i++; } if (i == len) { log_info("%.*s", st_length_int(line), st_char_get(line)); mm_free(copy); return; } for(;i < len; i++) { copy[i] = '*'; } log_info("%.*s", (int)len, copy); mm_free(copy); 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; }
/** * @brief Update a user config entry in the database, or insert it if it does not already exist. * @param usernum the numerical id of the user to whom the specified config entry belongs. * @param key a pointer to a managed string containing the name of the config entry to be updated or inserted. * @param value a pointer to a managed string containing the value of the specified config entry key. * @param flags a bitmask of flags for the config entry (USER_CONF_STATUS_CRITICAL is supported). * @return 2 if the config entry was updated, 1 if a new config key was inserted into the database, 0 if no update was necessary, or -1 on general failure. */ int_t user_config_upsert(uint64_t usernum, stringer_t *key, stringer_t *value, uint64_t flags) { uint64_t affected; MYSQL_BIND parameters[4]; mm_wipe(parameters, sizeof(parameters)); // User Number parameters[0].buffer_type = MYSQL_TYPE_LONGLONG; parameters[0].buffer_length = sizeof(uint64_t); parameters[0].buffer = &usernum; parameters[0].is_unsigned = true; // Key parameters[1].buffer_type = MYSQL_TYPE_STRING; parameters[1].buffer_length = st_length_get(key); parameters[1].buffer = st_char_get(key); // Value parameters[2].buffer_type = MYSQL_TYPE_STRING; parameters[2].buffer_length = st_length_get(value); parameters[2].buffer = st_char_get(value); // Flags parameters[3].buffer_type = MYSQL_TYPE_LONGLONG; parameters[3].buffer_length = sizeof(uint64_t); parameters[3].buffer = &flags; parameters[3].is_unsigned = true; if (!(affected = stmt_exec_affected(stmts.upsert_user_config, parameters)) == (my_ulonglong)-1) { log_pedantic("The user config upsert triggered an error. { user = %lu / key = %.*s }", usernum, st_length_int(key), st_char_get(key)); return -1; } log_check(affected > 2); return (int_t)affected; }
int main(void) { if (!mm_sec_start()) { log_print("Secure memory pool did not start correctly."); return 1; } else if (sizeof(stringer_t) != 4) { log_print("The string options variable should be 4 bytes/32 bits."); return 1; } /*chr_t *data = "Hello world."; size_t len = ns_length_get(data); stringer_t *place = PLACER(data, len); log_print("%.*s\n%.*s\n", st_length_int(PLACER(data, len)), st_char_get(PLACER(data, len)), st_length_int((stringer_t *)place), st_char_get((stringer_t *)place)); if (st_cmp_cs_eq(PLACER(st_data_get(constant), st_length_get(constant)), constant) || memcmp("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", st_data_get(constant), st_length_get(constant))) exit(1);*/ // Since were intentionally going to trigger errors, disable standard out while doing so. log_disable(); // Check that we can't specify a length greater than the available buffer. if (!check_string_logic(MANAGED_T | CONTIGUOUS | HEAP) || !check_string_logic(MANAGED_T | JOINTED | HEAP) || !check_string_logic(MAPPED_T | JOINTED | HEAP)) { log_print("--------------------------------------- LOGIC -------------------------------------------\n"); log_print("String logic checks failed."); return 1; } log_enable(); log_print("--------------------------------------- CONSTANT ----------------------------------------\n%28.28s = %.*s", "constant[fixed+contiguous]", st_length_int(constant), st_char_get(constant)); log_print("--------------------------------------- IMPORT ------------------------------------------"); if (!check_string_import()) { log_print("Import check failed."); return 1; } // Begin allocation checks. log_print("--------------------------------------- ALLOCATION --------------------------------------"); if (!check_string_alloc("nuller[heap+contiguous]", NULLER_T | CONTIGUOUS | HEAP) || !check_string_alloc("block[heap+contiguous]", BLOCK_T | CONTIGUOUS | HEAP) || !check_string_alloc("managed[heap+contiguous]", MANAGED_T | CONTIGUOUS | HEAP)) { log_print("Standard allocation checks failed."); return 1; } if (!check_string_alloc("nuller[heap+jointed]", NULLER_T | JOINTED | HEAP) || !check_string_alloc("block[heap+jointed]", BLOCK_T | JOINTED | HEAP) || !check_string_alloc("managed[heap+jointed]", MANAGED_T | JOINTED | HEAP) || !check_string_alloc("mapped[heap+jointed]", MAPPED_T | JOINTED | HEAP)) { log_print("Jointed allocation checks failed."); return 1; } if (!check_string_alloc("nuller[secure+contiguous]", NULLER_T | CONTIGUOUS | SECURE) || !check_string_alloc("block[secure+contiguous]", BLOCK_T | CONTIGUOUS | SECURE) || !check_string_alloc("managed[secure+contiguous]", MANAGED_T | CONTIGUOUS | SECURE)) { log_print("Secure allocation of contiguous types failed."); return 1; } if (!check_string_alloc("nuller[secure+jointed]", NULLER_T | JOINTED | SECURE) || !check_string_alloc("block[secure+jointed]", BLOCK_T | JOINTED | SECURE) || !check_string_alloc("managed[secure+jointed]", MANAGED_T | JOINTED | SECURE) || !check_string_alloc("mapped[secure+jointed]", MAPPED_T | JOINTED | SECURE)) { log_print("Secure allocation of jointed types failed."); return 1; } // Begin reallocation checks. log_print("-------------------------------------- REALLOCATION -------------------------------------"); if (!check_string_realloc("nuller[heap+contiguous]", NULLER_T | CONTIGUOUS | HEAP) || !check_string_realloc("block[heap+contiguous]", BLOCK_T | CONTIGUOUS | HEAP) || !check_string_realloc("managed[heap+contiguous]", MANAGED_T | CONTIGUOUS | HEAP)) { log_print("Standard reallocation checks failed."); return 1; } if (!check_string_realloc("nuller[heap+jointed]", NULLER_T | JOINTED | HEAP) || !check_string_realloc("block[heap+jointed]", BLOCK_T | JOINTED | HEAP) || !check_string_realloc("managed[heap+jointed]", MANAGED_T | JOINTED | HEAP) || !check_string_realloc("mapped[heap+jointed]", MAPPED_T | JOINTED | HEAP)) { log_print("Jointed reallocation checks failed."); return 1; } if (!check_string_realloc("nuller[secure+contiguous]", NULLER_T | CONTIGUOUS | SECURE) || !check_string_realloc("block[secure+contiguous]", BLOCK_T | CONTIGUOUS | SECURE) || !check_string_realloc("managed[secure+contiguous]", MANAGED_T | CONTIGUOUS | SECURE)) { log_print("Secure reallocation of contiguous types failed."); return 1; } if (!check_string_realloc("nuller[secure+jointed]", NULLER_T | JOINTED | SECURE) || !check_string_realloc("block[secure+jointed]", BLOCK_T | JOINTED | SECURE) || !check_string_realloc("managed[secure+jointed]", MANAGED_T | JOINTED | SECURE) || !check_string_realloc("mapped[secure+jointed]", MAPPED_T | JOINTED | SECURE)) { log_print("Secure reallocation of jointed types failed."); return 1; } // Begin duplication checks. log_print("-------------------------------------- DUPLICATION --------------------------------------"); if (!check_string_dupe("nuller[heap+contiguous]", NULLER_T | CONTIGUOUS | HEAP) || !check_string_dupe("block[heap+contiguous]", BLOCK_T | CONTIGUOUS | HEAP) || !check_string_dupe("managed[heap+contiguous]", MANAGED_T | CONTIGUOUS | HEAP)) { log_print("Standard duplication checks failed."); return 1; } if (!check_string_dupe("nuller[heap+jointed]", NULLER_T | JOINTED | HEAP) || !check_string_dupe("block[heap+jointed]", BLOCK_T | JOINTED | HEAP) || !check_string_dupe( "managed[heap+jointed]", MANAGED_T | JOINTED | HEAP) || !check_string_dupe("mapped[heap+jointed]", MAPPED_T | JOINTED | HEAP)) { log_print("Jointed duplication checks failed."); return 1; } if (!check_string_dupe("nuller[secure+contiguous]", NULLER_T | CONTIGUOUS | SECURE) || !check_string_dupe("block[secure+contiguous]", BLOCK_T | CONTIGUOUS | SECURE) || !check_string_dupe("managed[secure+contiguous]", MANAGED_T | CONTIGUOUS | SECURE)) { log_print("Secure duplication of contiguous types failed."); return 1; } if (!check_string_dupe("nuller[secure+jointed]", NULLER_T | JOINTED | SECURE) || !check_string_dupe("block[secure+jointed]", BLOCK_T | JOINTED | SECURE) || !check_string_dupe("managed[secure+jointed]", MANAGED_T | JOINTED | SECURE) || !check_string_dupe("mapped[secure+jointed]", MAPPED_T | JOINTED | SECURE)) { log_print("Secure duplication of jointed types failed."); return 1; } log_print("-------------------------------------- MERGE --------------------------------------------"); if (!check_string_merge()) { log_print("The merge function failed."); return 1; } log_print("-------------------------------------- PRINT --------------------------------------------"); if (!check_string_print()) { log_print("The print function failed."); return 1; } mm_sec_stop(); log_print("---------------------------------------- FINISHED ---------------------------------------"); return 0; }
/** * @brief Delete the specified contact detail of a contact entry from the database. * @param contactnum the numerical id of the contact entry to have the specified detail removed. * @param key a managed string containing the name of the contact detail to be removed from the entry. * @return -1 on error, 0 if no matching detail was found in the database, or 1 if the delete operation was successful. */ int_t contact_detail_delete(uint64_t contactnum, stringer_t *key) { int64_t affected; MYSQL_BIND parameters[4]; mm_wipe(parameters, sizeof(parameters)); // Contact Number parameters[0].buffer_type = MYSQL_TYPE_LONGLONG; parameters[0].buffer_length = sizeof(uint64_t); parameters[0].buffer = &contactnum; parameters[0].is_unsigned = true; // Key parameters[1].buffer_type = MYSQL_TYPE_STRING; parameters[1].buffer_length = st_length_get(key); parameters[1].buffer = st_char_get(key); if ((affected = stmt_exec_affected(stmts.delete_contact_details, parameters)) == -1) { log_pedantic("The contact detail deletion request triggered an error. { contact = %lu / key = %.*s }", contactnum, st_length_int(key), st_char_get(key)); return -1; } log_check(affected > 2); return (int_t)affected; }
/** * @brief Log the contents of a magma configuration option. * @param key a pointer to the magma configuration key to be dumped. * @return This function returns no value. */ void config_output_value(magma_keys_t *key) { switch (key->norm.type) { case (M_TYPE_NULLER): if (ns_empty(*((char **)(key->store)))) log_info("%s = NULL", key->name); else log_info("%s = %s", key->name, *((char **)(key->store))); break; case (M_TYPE_STRINGER): // Intercept the blacklist config key-> if (!st_cmp_cs_eq(NULLER(key->name), PLACER("magma.smtp.blacklist", 20))) { if (!magma.smtp.blacklists.count) { log_info("%s = NULL",key->name); } for (uint32_t j = 0; j < magma.smtp.blacklists.count; j++) { log_info("%s = %.*s",key->name, st_length_int(magma.smtp.blacklists.domain[j]), st_char_get(magma.smtp.blacklists.domain[j])); } } else if (st_empty(*((stringer_t **)(key->store)))) log_info("%s = NULL", key->name); else log_info("%s = %.*s", key->name, st_length_int(*((stringer_t **)(key->store))), st_char_get(*((stringer_t **)(key->store)))); break; case (M_TYPE_BOOLEAN): log_info("%s = %s", key->name, (*((bool_t *)(key->store)) ? "true" : "false")); break; case (M_TYPE_INT8): log_info("%s = %hhi", key->name, *((int8_t *)(key->store))); break; case (M_TYPE_INT16): log_info("%s = %hi", key->name, *((int16_t *)(key->store))); break; case (M_TYPE_INT32): log_info("%s = %i", key->name, *((int32_t *)(key->store))); break; case (M_TYPE_INT64): log_info("%s = %li", key->name, *((int64_t *)(key->store))); break; case (M_TYPE_UINT8): log_info("%s = %hhu", key->name, *((uint8_t *)(key->store))); break; case (M_TYPE_UINT16): log_info("%s = %hu", key->name, *((uint16_t *)(key->store))); break; case (M_TYPE_UINT32): log_info("%s = %u", key->name, *((uint32_t *)(key->store))); break; case (M_TYPE_UINT64): log_info("%s = %lu", key->name, *((uint64_t *)(key->store))); break; default: log_pedantic("Unexpected type. {type = %u}", key->norm.type); break; } return; }
/** * @brief Output a key name and value in a generic way. * @param prefix a pointer to a null-terminated string containing an optional prefix to be printed before the supplied key name. * @param name a pointer to a null-terminated string containing the name of the key being output. * @param type an M_TYPE value specifying the multi-type of the key value. * @param val a void pointer to the value of the specified key, which will be printed in accordance with the supplied multi-type. * @param required a boolean value specifying whether the specified key is a required configuration option. * @return This function returns no value. */ void config_output_value_generic(chr_t *prefix, chr_t *name, M_TYPE type, void *val, bool_t required) { chr_t *reqstr = ""; if (!prefix) { prefix = ""; } if (required) { reqstr = "*"; } switch (type) { case (M_TYPE_NULLER): if (ns_empty(*((char **)(val)))) log_info("%s%s%s = NULL", prefix, name, reqstr); else log_info("%s%s%s = %s", prefix, name, reqstr, *((char **)(val))); break; case (M_TYPE_STRINGER): // Intercept the blacklist config key-> if (!st_cmp_cs_eq(NULLER(name), PLACER("magma.smtp.blacklist", 20))) { if (!magma.smtp.blacklists.count) { log_info("%s%s%s = NULL", prefix, name, reqstr); } for (uint32_t j = 0; j < magma.smtp.blacklists.count; j++) { log_info("%s%s%s = %.*s", prefix, name, reqstr, st_length_int(magma.smtp.blacklists.domain[j]), st_char_get(magma.smtp.blacklists.domain[j])); } } else if (st_empty(*((stringer_t **)(val)))) log_info("%s%s%s = NULL", prefix, name, reqstr); else log_info("%s%s%s = %.*s", prefix, name, reqstr, st_length_int(*((stringer_t **)(val))), st_char_get(*((stringer_t **)(val)))); break; case (M_TYPE_ENUM): if (!st_cmp_cs_eq(NULLER(name), CONSTANT(".protocol"))) { if (*((M_PROTOCOL *)((char *)val)) == MOLTEN) log_info("%s%s%s = MOLTEN", prefix, name, reqstr); else if (*((M_PROTOCOL *)((char *)val)) == HTTP) log_info("%s%s%s = HTTP", prefix, name, reqstr); else if (*((M_PROTOCOL *)((char *)val)) == POP) log_info("%s%s%s = POP", prefix, name, reqstr); else if (*((M_PROTOCOL *)((char *)val)) == IMAP) log_info("%s%s%s = IMAP", prefix, name, reqstr); else if (*((M_PROTOCOL *)((char *)val)) == SMTP) log_info("%s%s%s = SMTP", prefix, name, reqstr); else if (*((M_PROTOCOL *)((char *)val)) == SUBMISSION) log_info("%s%s%s = SUBMISSION", prefix, name, reqstr); else if (*((M_PROTOCOL *)((char *)val)) == EMPTY) log_info("%s%s%s = EMPTY", prefix, name, reqstr); else log_info("%s%s%s = [UNKNOWN]", prefix, name, reqstr); } else if (!st_cmp_cs_eq(NULLER(name), CONSTANT(".network.type"))) { if (*((M_PORT *)((char *)val)) == TCP_PORT) log_info("%s%s%s = TCP", prefix, name, reqstr); else if (*((M_PORT *)((char *)val)) == SSL_PORT) log_info("%s%s%s = SSL", prefix, name, reqstr); else log_info("%s%s%s = [UNKNOWN]", prefix, name, reqstr); } break; case (M_TYPE_BOOLEAN): log_info("%s%s%s = %s", prefix, name, reqstr, (*((bool_t *)(val)) ? "true" : "false")); break; case (M_TYPE_INT8): log_info("%s%s%s = %hhi", prefix, name, reqstr, *((int8_t *)(val))); break; case (M_TYPE_INT16): log_info("%s%s%s = %hi", prefix, name, reqstr, *((int16_t *)(val))); break; case (M_TYPE_INT32): log_info("%s%s%s = %i", prefix, name, reqstr, *((int32_t *)(val))); break; case (M_TYPE_INT64): log_info("%s%s%s = %li", prefix, name, reqstr, *((int64_t *)(val))); break; case (M_TYPE_UINT8): log_info("%s%s%s = %hhu", prefix, name, reqstr, *((uint8_t *)(val))); break; case (M_TYPE_UINT16): log_info("%s%s%s = %hu", prefix, name, reqstr, *((uint16_t *)(val))); break; case (M_TYPE_UINT32): log_info("%s%s%s = %u", prefix, name, reqstr, *((uint32_t *)(val))); break; case (M_TYPE_UINT64): log_info("%s%s%s = %lu", prefix, name, reqstr, *((uint64_t *)(val))); break; default: log_pedantic("Unexpected type. {type = %u}", type); break; } return; }
/** * @brief Load all magma configuration options present in the database. * @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_database_settings(void) { row_t *row; uint64_t rows; magma_keys_t *key; table_t *database_pairs; stringer_t *value, *name; if (!(magma.host.number = config_fetch_host_number()) || !(database_pairs = config_fetch_settings())) { return false; } // Loop through each of the row returned. rows = res_row_count(database_pairs); for (uint64_t i = 0; i < rows && (row = res_row_get(database_pairs, i)); i++) { name = PLACER(res_field_block(row, 0), res_field_length(row, 0)); value = PLACER(res_field_block(row, 1), res_field_length(row, 1)); if ((key = config_key_lookup(name))) { // Make sure the setting can be provided via the database. if (!key->database) { log_critical("%s cannot be changed using the database.", key->name); res_table_free(database_pairs); return false; } // Make sure the setting can be provided via the database. else if (key->set && !key->overwrite) { log_critical("%s has already been set and cannot be overwritten.", key->name); res_table_free(database_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); res_table_free(database_pairs); return false; } // Attempt to set the value. else if (!config_value_set(key, value)) { res_table_free(database_pairs); return false; } // 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 (!st_cmp_ci_starts(name, CONSTANT("magma.servers"))) { servers_config(name, value); } // If we haven't had a match yet, check if its a relay instance. else if (!st_cmp_ci_starts(name, CONSTANT("magma.relay"))) { relay_config(name, value); } else if (!st_cmp_ci_starts(name, CONSTANT("magma.iface.cache.host"))) { cache_config(name, value); } // Otherwise if we still haven't matched a value, report an error. else { log_critical("%.*s is not a valid setting.", st_length_int(name), st_char_get(name)); res_table_free(database_pairs); return false; } } res_table_free(database_pairs); return true; }
bool_t check_string_realloc(uint32_t check) { size_t len; stringer_t *s, *swap; if (!(s = st_alloc_opts(check, 1)) || !(swap = st_realloc(s, st_length_get(string_check_constant)))) { if (s) { st_free(s); } return false; } // Since mapped allocations are page aligned, we reallocate to a larger than needed size to ensure an actual reallocation occurs. else if ((check & MAPPED_T) && !(swap = st_realloc(s, st_length_get(string_check_constant) + (getpagesize() * 128)))) { if (s) { st_free(s); } return false; } // Jointed strings allow reallocation without changing the address of s, of if the string is jointed and s changes, error out. else if ((check & JOINTED) && swap != s) { st_free(swap); st_free(s); return false; } // Contiguous strings will require the address of s to change, so if it doesn't error out. Except for the mapped type, since the jointed flag doesn't apply to it. else if (!(check & JOINTED) && !(check & MAPPED_T) && swap == s) { st_free(s); return false; } // For contiguous types, free the original string and replace it with the value of swap. else if (swap != s) { st_free(s); s = swap; } len = snprintf(st_char_get(s), st_length_int(string_check_constant) + 1, "%.*s", st_length_int(string_check_constant), st_char_get(string_check_constant)); if ((check & MAPPED_T) || (check & MANAGED_T)) { st_length_set(s, len); } else if (memcmp(st_char_get(s), st_char_get(string_check_constant), st_length_get(string_check_constant))) { return false; } // Enlarge a buffer by a factor of 128 and make sure the data it contained wasn't changed. For managed or mapped strings, multiply the available space. if ((check & (MANAGED_T | MAPPED_T)) && !(swap = st_realloc(s, st_avail_get(s) * 128))) { st_free(s); return false; } // For other string types we multiply the space used by the string since the original allocation size isn't tracked. else if (!(check & (MANAGED_T | MAPPED_T)) && !(swap = st_realloc(s, st_length_get(s) * 128))) { st_free(s); return false; } // Since mapped allocations are page aligned, we reallocate to a larger than needed size to ensure an actual reallocation occurs. else if ((check & MAPPED_T) && !(swap = st_realloc(s, st_length_get(string_check_constant) + (getpagesize() * 128)))) { if (s) { st_free(s); } return false; } // Jointed strings allow reallocation without changing the address of s, of if the string is jointed and s changes, error out. else if ((check & JOINTED) && swap != s) { st_free(swap); st_free(s); return false; } // Contiguous strings will require the address of s to change, so if it doesn't error out. Except for the mapped type, since the jointed flag doesn't apply to it. else if (((check & JOINTED) ^ JOINTED) && !(check & MAPPED_T) && swap == s) { st_free(s); return false; } // For contiguous types, free the original string and replace it with the value of swap. else if (swap != s) { st_free(s); s = swap; } else if (memcmp(st_char_get(s), st_char_get(string_check_constant), st_length_get(string_check_constant))) { return false; } // Now we shrink the buffer back to the bare minimum and check the data one final time. if (!(swap = st_realloc(s, st_length_get(string_check_constant)))) { if (s) { st_free(s); } return false; } else if (swap != s) { st_free(s); s = swap; } st_free(s); return true; }
/** * @brief Create the on-disk directory structure necessary to hold a given message's file data. * @param number the mail message id. * @param server the hostname of the server where the message data resides or if NULL, the default server. * @return true on success or false on failure. */ bool_t mail_create_directory(uint64_t number, chr_t *server) { DIR *dir; chr_t dirpath[1024]; if (!number) { return false; } // The default storage server. if (!server) { server = st_char_get(magma.storage.active); } // Check for the base NFS directory. snprintf(dirpath, 1024, "%.*s/%s", st_length_int(magma.storage.root), st_char_get(magma.storage.root), server); if (!(dir = opendir(dirpath))) { log_error("It appears that the path %s is invalid.", dirpath); return false; } else { closedir(dir); } // Check the first level. snprintf(dirpath, 1024, "%.*s/%s/%lu", st_length_int(magma.storage.root), st_char_get(magma.storage.root), server, number / 32768 / 32768 / 32768 / 32768); if (!(dir = opendir(dirpath))) { if (mkdir(dirpath, S_IRWXU) != 0) { log_error("An error occurred while attempting to create the directory %s.", dirpath); return false; } } else { closedir(dir); } // Check the second level. snprintf(dirpath, 1024, "%.*s/%s/%lu/%lu", st_length_int(magma.storage.root), st_char_get(magma.storage.root), server, number / 32768 / 32768 / 32768 / 32768, number / 32768 / 32768 / 32768); if (!(dir = opendir(dirpath))) { if (mkdir(dirpath, S_IRWXU) != 0) { log_error("An error occurred while attempting to create the directory %s.", dirpath); return false; } } else { closedir(dir); } // Check the third level. snprintf(dirpath, 1024, "%.*s/%s/%lu/%lu/%lu", st_length_int(magma.storage.root), st_char_get(magma.storage.root), server, number / 32768 / 32768 / 32768 / 32768, number / 32768 / 32768 / 32768, number / 32768 / 32768 ); if (!(dir = opendir(dirpath))) { if (mkdir(dirpath, S_IRWXU) != 0) { log_error("An error occurred while attempting to create the directory %s.", dirpath); return false; } } else { closedir(dir); } // Check the fourth level. snprintf(dirpath, 1024, "%.*s/%s/%lu/%lu/%lu/%lu", st_length_int(magma.storage.root), st_char_get(magma.storage.root), server, number / 32768 / 32768 / 32768 / 32768, number / 32768 / 32768 / 32768, number / 32768 / 32768, number / 32768 ); if (!(dir = opendir(dirpath))) { if (mkdir(dirpath, S_IRWXU) != 0) { log_error("An error occurred while attempting to create the directory %s.", dirpath); return false; } } else { closedir(dir); } return true; }
/** * @brief Read a line of input from a network client session. * @return -1 on general failure, -2 if the connection was reset, or the length of the current line of input, including the trailing new line character. */ int64_t client_read_line(client_t *client) { ssize_t bytes = 0; int_t counter = 0; stringer_t *error = NULL; bool_t blocking = true, line = false; #ifdef MAGMA_PEDANTIC int_t local = 0; stringer_t *ip = NULL, *cipher = NULL; #endif if (!client || client->sockd == -1) { if (client) client->status = 1; return -1; } // Check for data past the current line buffer. else if (pl_length_get(client->line) && st_length_get(client->buffer) > pl_length_get(client->line)) { // Move the unused data to the front of the buffer. mm_move(st_data_get(client->buffer), st_data_get(client->buffer) + pl_length_get(client->line), st_length_get(client->buffer) - pl_length_get(client->line)); // Update the length. st_length_set(client->buffer, st_length_get(client->buffer) - pl_length_get(client->line)); // Check whether the data we just moved contains a complete line. if (!pl_empty((client->line = line_pl_st(client->buffer, 0)))) { client->status = 1; return pl_length_get(client->line); } } // Otherwise reset the buffer and line lengths to zero. else { st_length_set(client->buffer, 0); client->line = pl_null(); } // Loop until we get a complete line, an error, or the buffer is filled. do { // Read bytes off the network. Skip past any existing data in the buffer. if (client->tls) { // If bytes is zero or below and the library isn't asking for another read, then an error occurred. bytes = tls_read(client->tls, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), blocking); // If zero bytes were read, or a negative value was returned to indicate an error, call tls_erorr(), which will return // NULL if the error can be safely ignored. Otherwise log the output for debug purposes. if (bytes <= 0 && (error = tls_error(client->tls, bytes, MANAGEDBUF(512)))) { #ifdef MAGMA_PEDANTIC cipher = tls_cipher(client->tls, MANAGEDBUF(128)); ip = ip_presentation(client->ip, MANAGEDBUF(INET6_ADDRSTRLEN)); log_pedantic("TLS client read operation failed. { ip = %.*s / %.*s / result = %zi%s%.*s }", st_length_int(ip), st_char_get(ip), st_length_int(cipher), st_char_get(cipher), bytes, (error ? " / " : ""), st_length_int(error), st_char_get(error)); #endif client->status = -1; return -1; } // This will occur when the read operation results in a 0, or negative value, but TLS error returns NULL to // indicate it was a transient error. For transient errors we simply set bytes equal to 0 so the read call gets retried. else if (bytes <= 0) { bytes = 0; } } else { errno = 0; bytes = recv(client->sockd, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), (blocking ? 0 : MSG_DONTWAIT)); // Check for errors on non-SSL reads in the traditional way. if (bytes <= 0 && tcp_status(client->sockd)) { #ifdef MAGMA_PEDANTIC local = errno; ip = ip_presentation(client->ip, MANAGEDBUF(INET6_ADDRSTRLEN)); log_pedantic("TCP client read operation failed. { ip = %.*s / result = %zi / error = %i / message = %s }", st_length_int(ip), st_char_get(ip), bytes, local, strerror_r(local, MEMORYBUF(1024), 1024)); #endif client->status = -1; return -1; } } // We actually read in data, so we need to update the buffer to reflect the amount of data it currently holds. if (bytes > 0) { st_length_set(client->buffer, st_length_get(client->buffer) + bytes); } // Check whether we have a complete line before checking whether the connection was closed. if (!st_empty(client->buffer) && !pl_empty((client->line = line_pl_st(client->buffer, 0)))) { line = true; } } while (!line && counter++ < 128 && st_length_get(client->buffer) != st_avail_get(client->buffer) && status()); if (st_length_get(client->buffer) > 0) { client->status = 1; } return pl_length_get(client->line); }
bool_t check_string_print(void) { uint64_t total; bool_t result = true; stringer_t *strings[14]; mm_set(strings, 0, sizeof(strings)); strings[0] = st_aprint_opts(NULLER_T | CONTIGUOUS | HEAP, "%.*s", st_length_int(string_check_constant), st_char_get(string_check_constant)); strings[1] = st_aprint_opts(NULLER_T | JOINTED | HEAP, "%.*s", st_length_int(strings[0]), st_char_get(strings[0])); strings[2] = st_aprint_opts(BLOCK_T | CONTIGUOUS | HEAP, "%.*s", st_length_int(strings[1]), st_char_get(strings[1])); strings[3] = st_aprint_opts(BLOCK_T | JOINTED | HEAP, "%.*s", st_length_int(strings[2]), st_char_get(strings[2])); strings[4] = st_aprint_opts(MANAGED_T | CONTIGUOUS | HEAP, "%.*s", st_length_int(strings[3]), st_char_get(strings[3])); strings[5] = st_aprint_opts(MANAGED_T | JOINTED | HEAP, "%.*s", st_length_int(strings[4]), st_char_get(strings[4])); strings[6] = st_aprint_opts(MAPPED_T | JOINTED | HEAP, "%.*s", st_length_int(strings[5]), st_char_get(strings[5])); strings[7] = st_aprint_opts(NULLER_T | CONTIGUOUS | SECURE, "%.*s", st_length_int(strings[6]), st_char_get(strings[6])); strings[8] = st_aprint_opts(NULLER_T | JOINTED | SECURE, "%.*s", st_length_int(strings[7]), st_char_get(strings[7])); strings[9] = st_aprint_opts(BLOCK_T | CONTIGUOUS | SECURE, "%.*s", st_length_int(strings[8]), st_char_get(strings[8])); strings[10] = st_aprint_opts(BLOCK_T | JOINTED | SECURE, "%.*s", st_length_int(strings[9]), st_char_get(strings[9])); strings[11] = st_aprint_opts(MANAGED_T | CONTIGUOUS | SECURE, "%.*s", st_length_int(strings[10]), st_char_get(strings[10])); strings[12] = st_aprint_opts(MANAGED_T | JOINTED | SECURE, "%.*s", st_length_int(strings[11]), st_char_get(strings[11])); strings[13] = st_aprint_opts(MAPPED_T | JOINTED | SECURE, "%.*s", st_length_int(strings[12]), st_char_get(strings[12])); for (int i = 0; i < 14 && strings[i]; i++) { for (unsigned int j = total = 0; strings[i] && j < st_length_get(strings[i]); j++) { total += *(st_char_get(strings[i]) + j); } if (total != 5366) { result = false; } } for (int i = 0; i < 14; i++) { if (strings[i]) st_free(strings[i]); } 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; }
/** * @brief Update a specified contact detail in the database, or insert it if it does not exist. * @param contactnum the numerical id of the contact entry to be modified. * @param key a managed string containing the name of the contact detail to be updated. * @param value a managed string containing the new value of the specified contact detail. * @param flags a bitmask of flags to be associated with the specified contact entry detail. * @return -1 on error, 0 if no update was necessary, 1 if a new contact detail was inserted into the database, or 2 if the * specified contact detail was updated successfully. */ int_t contact_detail_upsert(uint64_t contactnum, stringer_t *key, stringer_t *value, uint64_t flags) { int64_t affected; MYSQL_BIND parameters[4]; mm_wipe(parameters, sizeof(parameters)); // Contact Number parameters[0].buffer_type = MYSQL_TYPE_LONGLONG; parameters[0].buffer_length = sizeof(uint64_t); parameters[0].buffer = &contactnum; parameters[0].is_unsigned = true; // Key parameters[1].buffer_type = MYSQL_TYPE_STRING; parameters[1].buffer_length = st_length_get(key); parameters[1].buffer = st_char_get(key); // Value parameters[2].buffer_type = MYSQL_TYPE_STRING; parameters[2].buffer_length = st_length_get(value); parameters[2].buffer = st_char_get(value); // Flags parameters[3].buffer_type = MYSQL_TYPE_LONGLONG; parameters[3].buffer_length = sizeof(uint64_t); parameters[3].buffer = &flags; parameters[3].is_unsigned = true; if ((affected = stmt_exec_affected(stmts.upsert_contact_detail, parameters)) == -1) { log_pedantic("The contact detail upsert triggered an error. { contact = %lu / key = %.*s }", contactnum, st_length_int(key), st_char_get(key)); return -1; } log_check(affected > 2); return (int_t)affected; }
/** * @brief Return a zero-length placer pointing to NULL data. * @return a zero-length placer pointing to NULL data. */ placer_t pl_null(void) { return (placer_t){ .opts = PLACER_T | JOINTED | STACK | FOREIGNDATA, .data = NULL, .length = 0 }; } /** * @brief Return a placer wrapping a data buffer of given size. * @param data a pointer to the data to be wrapped. * @param len the length, in bytes, of the data. * @return a placer pointing to the specified data. */ placer_t pl_init(void *data, size_t len) { return (placer_t){ .opts = PLACER_T | JOINTED | STACK | FOREIGNDATA, .data = data, .length = len }; } placer_t pl_clone(placer_t place) { return (pl_init(place.data, place.length)); } placer_t pl_set(placer_t place, placer_t set) { return (placer_t){ .opts = place.opts, .data = set.data, .length = set.length }; } /** * @brief Get a pointer to the data referenced by a placer. * @param place the input placer. * @return NULL on failure or a pointer to the block of data associated with the specified placer on success. */ void * pl_data_get(placer_t place) { return st_data_get((stringer_t *)&place); } /** * @brief Get a character pointer to the data referenced by a placer. * @param place the input placer. * @return NULL on failure or a a character pointer to the block of data associated with the specified placer on success. */ chr_t * pl_char_get(placer_t place) { return st_char_get((stringer_t *)&place); } /** * @brief Get the length, in bytes, of a placer as an integer. * @param place the input placer. * @return the size, in bytes, of the specified placer. */ int_t pl_length_int(placer_t place) { return st_length_int((stringer_t *)&place); } /** * @brief Get the length, in bytes, of a placer. * @param place the input placer. * @return the size, in bytes, of the specified placer. */ size_t pl_length_get(placer_t place) { return st_length_get((stringer_t *)&place); } /** * @brief Determine whether or not the specified placer is empty. * @param place the input placer. * @return true if the placer is empty or zero-length, or false otherwise. */ bool_t pl_empty(placer_t place) { return st_empty((stringer_t *)&place); } /** * @brief Determine if a placer begins with a specified character. * @param place the input placer. * @param c the character to be compared with the first byte of the placer's data. * @return true if the placer begins with the given character or false otherwise. */ bool_t pl_starts_with_char(placer_t place, chr_t c) { if (pl_empty(place)) { return false; } if (*(pl_char_get(place)) == c) { return true; } return false; } /** * @brief Advance the placer one character forward beyond an expected character. * @param place the input placer. * @param more if true, the placer must contain more data, and vice versa. * @return true if more was true and the placer contains more data, or if more was false and the placer ended; false otherwise. */ bool_t pl_inc(placer_t *place, bool_t more) { if (pl_empty(*place)) { return false; } place->length--; place->data = (chr_t *)place->data + 1; return (more == (place->length > 0)); }