/** * @brief Get the fully expanded name of a folder. * @note The folder hierarchy will be delimited with a "." character. * @param folders the inx object holding all of a user's magma folders. * @param target a pointer to the magma folder object to have its name expanded. * @return NULL on failure or a pointer to a managed string with the fully expanded name of the specified folder. */ stringer_t * magma_folder_name(inx_t *folders, magma_folder_t *target) { stringer_t *result; int_t recursion = 0; if (!folders || !target || !target->foldernum || st_empty(target->name)) { log_pedantic("Invalid folder context. Unable to construct the name."); return NULL; } else if (!(result = st_dupe_opts(MANAGED_T | JOINTED | HEAP, target->name))) { log_pedantic("We were unable to duplicate the folder name."); return NULL; } while (target && target->parent != 0 && recursion++ < FOLDER_RECURSION_LIMIT) { // Get the parent target. if ((target = magma_folder_find_number(folders, target->parent)) == NULL) { log_pedantic("There appears to be a folder with an invalid parent."); return result; } // Append the seperator and then the parent name. result = st_append(result, PLACER(".", 1)); result = st_append(result, target->name); } return result; }
/** * @brief Set the contents of the thread's mail cache. * @param messagenum the numerical id of the message to be cached. * @text a managed string containing the contents of the specified message to be cached. * @return This function returns no value. */ void mail_cache_set(uint64_t messagenum, stringer_t *text) { mail_cache_t *message; if (!(message = pthread_getspecific(mail_cache))) { if (!(message = mm_alloc(sizeof(mail_cache_t)))) { return; } if (pthread_setspecific(mail_cache, message) != 0) { log_pedantic("Unable to setup thread message cache."); mm_free(message); return; } } else { st_cleanup(message->text); } message->messagenum = messagenum; message->text = st_dupe_opts(MANAGED_T | HEAP | CONTIGUOUS, text); return; }
/** * @brief Create a new session for a given web connection. * @note The session stores the following data points: remote IP address, request path, application name, the specified http hostname, * the remote client's user agent string, the server's host number, a unique session id, the server's current timestamp, a randomly- * generated session key for authentication, and an encrypted token for the session returned to the user as a cookie. * @param con a pointer to the connection underlying the web session. * @param path a pointer to a managed string containing the pathname of the generating request (should be "/portal/camel"). * @param application a pointer to a managed string containing the name of the parent application of the session (should be "portal"). * @return NULL on failure or a pointer to a newly allocated session object for the specified connection. */ session_t *sess_create(connection_t *con, stringer_t *path, stringer_t *application) { session_t *output; multi_t key = { .type = M_TYPE_UINT64, .val.u64 = 0 }; if (!(output = mm_alloc(sizeof(session_t)))) { log_pedantic("Unable to allocate %zu bytes for a session context.", sizeof(session_t)); return NULL; } else if (pthread_mutex_init(&(output->lock), NULL) != 0) { log_pedantic("Unable to initialize reference lock for new user session."); mm_free(output); return NULL; } else if (!(output->compositions = inx_alloc(M_INX_LINKED, &sess_release_composition))) { log_pedantic("Unable to allocate space for user session's compositions."); mm_free(output); return NULL; } if (!(ip_copy(&(output->warden.ip), con_addr(con, MEMORYBUF(64)))) || (path && !(output->request.path = st_dupe_opts(MANAGED_T | HEAP | CONTIGUOUS, path))) || (application && !(output->request.application = st_dupe_opts(MANAGED_T | HEAP | CONTIGUOUS, application))) || (con->http.host && !(output->request.host = st_dupe_opts(MANAGED_T | HEAP | CONTIGUOUS, con->http.host))) || (con->http.agent && !(output->warden.agent = st_dupe_opts(MANAGED_T | HEAP | CONTIGUOUS, con->http.agent))) || !(output->warden.host = magma.host.number) || !(key.val.u64 = output->warden.number = sess_number()) || !(output->warden.stamp = time(NULL)) || !(output->warden.key = sess_key()) || !(output->warden.token = sess_token(output))) { log_pedantic("Unable to initialize the session warden context."); sess_destroy(output); return NULL; } output->request.httponly = true; output->request.secure = (con_secure(con) == 1 ? true : false); sess_ref_add(output); if (inx_insert(objects.sessions, key, output) != 1) { log_pedantic("Unable to insert the session into the global context."); sess_ref_dec(output); sess_destroy(output); return NULL; } return output; } /** * @brief Try to retrieve the session associated with a client connection's supplied cookie. * @param con a pointer to the connection object sending the cookie. * @param application a managed string containing the application associated with the session. * @param path a managed string containing the path associated with the session. * @param token the encrypted user token retrieved from the supplied http cookie. * @return 1 if the cookie was found and valid, or one of the following values on failure: * 0 = Session not found. * -1 = Server error. * -2 = Invalid token. * -3 = Security violation / incorrect user-agent. * -4 = Security violation / incorrect session key. * -5 = Security violation / incorrect source address. * -6 = Session terminated by logout. * -7 = Session timed out. */ int_t sess_get(connection_t *con, stringer_t *application, stringer_t *path, stringer_t *token) { uint64_t *numbers; scramble_t *scramble; stringer_t *binary, *encrypted; multi_t key = { .type = M_TYPE_UINT64, .val.u64 = 0 }; int_t result = 1; /// Most session attributes need simple equality comparison, except for timeout checking. Make sure not to validate against a stale session that should have already timed out (which will have to be determined dynamically). encrypted = zbase32_decode(token); scramble = scramble_import(encrypted); binary = scramble_decrypt(magma.secure.sessions, scramble); st_cleanup(encrypted); if (!binary) { return 0; } numbers = st_data_get(binary); // QUESTION: Is this necessary? doesn't inx_find() lock the inx? inx_lock_read(objects.sessions); key.val.u64 = *(numbers + 2); if ((con->http.session = inx_find(objects.sessions, key))) { sess_ref_add(con->http.session); } inx_unlock(objects.sessions); st_free(binary); // Return if we didn't find the session or user. if (!con->http.session || !con->http.session->user) { return 0; } // We need to do full validation against the cookie and associated session. // First, the cookie. if ((*numbers != con->http.session->warden.host) || (*(numbers + 1) != con->http.session->warden.stamp) || (*(numbers + 2) != con->http.session->warden.number)) { log_error("Received mismatched cookie for authenticated session { user = %s }", st_char_get(con->http.session->user->username)); result = -2; } else if (*(numbers + 3) != con->http.session->warden.key) { log_error("Cookie contained an incorrect session key { user = %s }", st_char_get(con->http.session->user->username)); result = -4; } else if (st_cmp_cs_eq(application, con->http.session->request.application)) { log_error("Cookie did not match session's application { user = %s }", st_char_get(con->http.session->user->username)); result = -2; } else if (st_cmp_cs_eq(path, con->http.session->request.path)) { log_error("Cookie did not match session's path { user = %s }", st_char_get(con->http.session->user->username)); result = -2; } else if (st_cmp_cs_eq(con->http.agent, con->http.session->warden.agent)) { log_error("Cookie contained a mismatched user agent { user = %s }", st_char_get(con->http.session->user->username)); result = -3; } else if (con->http.session->request.secure != (con_secure(con) ? 1 : 0)) { log_error("Cookie was submitted from a mismatched transport layer { user = %s }", st_char_get(con->http.session->user->username)); result = -5; } else if (!ip_address_equal(&(con->http.session->warden.ip), (ip_t *)con_addr(con, MEMORYBUF(64)))) { log_error("Cookie was submitted from a mismatched IP address { user = %s }", st_char_get(con->http.session->user->username)); result = -5; } // Finally, do comparisons to see that we haven't timed out. /* Did we expire? */ if (magma.http.session_timeout <= (time(NULL) - con->http.session->warden.stamp)) { log_pedantic("User submitted expired or invalidated cookie; marking for deletion { user = %s }", st_char_get(con->http.session->user->username)); result = -7; } // QUESTION: This destruction needs a second look. if (result < 0) { if (!inx_delete(objects.sessions, key)) { log_pedantic("Unexpected error occurred attempting to delete expired cookie { user = %s }", st_char_get(con->http.session->user->username)); } sess_ref_dec(con->http.session); //sess_destroy(con->http.session); con->http.session = NULL; } // Otherwise, if the last session status update is more than 10 minutes ago, check now to see if things are current. // QUESTION: Why is it 600 here and 120 elsewhere? else if ((time(NULL) - sess_refresh_stamp(con->http.session)) > 600) { sess_update(con->http.session); } return result; }
/** * @brief Set the value of a global config key. * @note This function will also free the value of the global config key if it has already previously been set. * @param setting a pointer to the global key to have its value adjusted. * @param value a managed string containing the new key value, or if NULL, the key's default value will be used. * @return true if the specified key's value was set successfully, or false on failure. */ bool_t config_value_set(magma_keys_t *setting, stringer_t *value) { bool_t result = true; /// LOW: Realtime blacklist domains are handled using custom code because we don't yet have a generic type to store lists. if (!st_cmp_cs_eq(NULLER(setting->name), PLACER("magma.smtp.blacklist", 20))) { // When the default values are assigned an empty string is passed in. Returning false then tricks the system into // thinking the default value is wrong, so we just return true to avoid the issue. if (st_empty(value)) { return true; } // Were using a fixed array, so if we run out of room we have to reject config. if (magma.smtp.blacklists.count >= MAGMA_BLACKLIST_INSTANCES) { log_critical("magma.smtp.blacklist is limited to %u %s and the configuration currently contains more than %u %s.", MAGMA_BLACKLIST_INSTANCES, MAGMA_BLACKLIST_INSTANCES == 1 ? "domain" : "domains", MAGMA_BLACKLIST_INSTANCES, MAGMA_BLACKLIST_INSTANCES == 1 ? "domain" : "domains"); return false; } // Make sure the targeted array slot is empty, and the string passed to us has data in it. If so duplicate the value and // and store it in the array. If anything fails, return false. else if (magma.smtp.blacklists.domain[magma.smtp.blacklists.count] || !(magma.smtp.blacklists.domain[magma.smtp.blacklists.count] = st_dupe_opts(MANAGED_T | CONTIGUOUS | HEAP, value))) { return false; } // Track the number of blacklist domains we have. magma.smtp.blacklists.count++; // Return true so the switch statement below doesn't corrupt the array. return true; } else if (!st_cmp_cs_eq(NULLER(setting->name), NULLER("magma.smtp.bypass_addr"))) { // When the default values are assigned an empty string is passed in. Returning false then tricks the system into // thinking the default value is wrong, so we just return true to avoid the issue. if (st_empty(value)) { return true; } if (!smtp_add_bypass_entry(value)) { log_critical("Unable to add smtp bypass entry { entry = %s }", st_char_get(value)); return false; } // Return true so the switch statement below doesn't corrupt the array. return true; } switch (setting->norm.type) { // Strings case (M_TYPE_NULLER): if (!ns_empty(*((char **)(setting->store)))) { ns_free(*((char **)(setting->store))); *((char **)(setting->store)) = NULL; } if (!st_empty(value)) *((char **)(setting->store)) = ns_import(st_char_get(value), st_length_get(value)); else if (!ns_empty(setting->norm.val.ns)) *((char **)(setting->store)) = ns_dupe(setting->norm.val.ns); break; case (M_TYPE_STRINGER): if (!st_empty(*((stringer_t **)(setting->store)))) { st_free(*((stringer_t **)(setting->store))); *((stringer_t **)(setting->store)) = NULL; } if (!st_empty(value)) *((stringer_t **)(setting->store)) = st_dupe_opts(MANAGED_T | CONTIGUOUS | HEAP, value); else if (!st_empty(setting->norm.val.st)) *((stringer_t **)(setting->store)) = st_dupe_opts(MANAGED_T | CONTIGUOUS | HEAP, setting->norm.val.st); break; // Booleans case (M_TYPE_BOOLEAN): if (!st_empty(value)) { if (!st_cmp_ci_eq(value, CONSTANT("true"))) *((bool_t *)(setting->store)) = true; else if (!st_cmp_ci_eq(value, CONSTANT("false"))) *((bool_t *)(setting->store)) = false; else { log_critical("Invalid value for %s.", setting->name); result = false; } } else *((bool_t *)(setting->store)) = setting->norm.val.binary; break; // Integers case (M_TYPE_INT8): if (!st_empty(value)) { if (!int8_conv_st(value, (int8_t *)(setting->store))) { log_critical("Invalid value for %s.", setting->name); result = false; } } else *((int8_t *)(setting->store)) = setting->norm.val.i8; break; case (M_TYPE_INT16): if (!st_empty(value)) { if (!uint16_conv_st(value, (uint16_t *)(setting->store))) { log_critical("Invalid value for %s.", setting->name); result = false; } } else *((int16_t *)(setting->store)) = setting->norm.val.u16; break; case (M_TYPE_INT32): if (!st_empty(value)) { if (!int32_conv_st(value, (int32_t *)(setting->store))) { log_critical("Invalid value for %s.", setting->name); result = false; } } else *((int32_t *)(setting->store)) = setting->norm.val.i32; break; case (M_TYPE_INT64): if (!st_empty(value)) { if (!int64_conv_st(value, (int64_t *)(setting->store))) { log_critical("Invalid value for %s.", setting->name); result = false; } } else *((int64_t *)(setting->store)) = setting->norm.val.i64; break; // Unsigned Integers case (M_TYPE_UINT8): if (!st_empty(value)) { if (!uint8_conv_st(value, (uint8_t *)(setting->store))) { log_critical("Invalid value for %s.", setting->name); result = false; } } else *((uint8_t *)(setting->store)) = setting->norm.val.u8; break; case (M_TYPE_UINT16): if (!st_empty(value)) { if (!uint16_conv_st(value, (uint16_t *)(setting->store))) { log_critical("Invalid value for %s.", setting->name); result = false; } } else *((uint16_t *)(setting->store)) = setting->norm.val.u16; break; case (M_TYPE_UINT32): if (!st_empty(value)) { if (!uint32_conv_st(value, (uint32_t *)(setting->store))) { log_critical("Invalid value for %s.", setting->name); result = false; } } else *((uint32_t *)(setting->store)) = setting->norm.val.u32; break; case (M_TYPE_UINT64): if (!st_empty(value)) { if (!uint64_conv_st(value, (uint64_t *)(setting->store))) { log_critical("Invalid value for %s.", setting->name); result = false; } } else *((uint64_t *)(setting->store)) = setting->norm.val.u64; break; default: log_critical("The %s setting definition is using an invalid type.", setting->name); result = false; break; } return result; }