bool_t check_inx_append_helper(inx_t *inx) { void *val; chr_t snum[64]; bool_t outcome = true; multi_t last = mt_get_null(), key = mt_get_null(); uint64_t offset = (uint64_t)thread_get_thread_id() * INX_CHECK_OBJECTS; key = mt_set_type(key, M_TYPE_UINT64); last = mt_set_type(last, M_TYPE_UINT64); // Add to the index, alternating insert and then append, while occasionally truncating and/or deleting. for (uint64_t i = 0; status() && outcome && i < INX_CHECK_OBJECTS; i++) { key.val.u64 = offset + i; snprintf(snum, 64, "%lu", offset + i); inx_lock_write(inx); if (!(val = ns_dupe(snum))) { outcome = false; } else if (i % 4 == 0) { if (!inx_insert(inx, key, val)) { outcome = false; ns_free(val); } else { last = mt_dupe(key); } } else if (i % 2 == 0) { if (!inx_append(inx, key, val)) { outcome = false; ns_free(val); } else { last = mt_dupe(key); } } else { if (!inx_append(inx, key, val)) { outcome = false; ns_free(val); } inx_delete(inx, key); inx_delete(inx, last); last.val.u64 = 0; } if (i == 73) inx_truncate(inx); inx_unlock(inx); } inx_lock_write(inx); inx_truncate(inx); inx_unlock(inx); return outcome; }
/** * @brief Fetch a user's config collection from the database. * @param collection a pointer to the specified user config collection object (with usernum field set by the caller) to be populated from the database. * @return true on success or false on failure. */ bool_t user_config_fetch(user_config_t *collection) { row_t *row; table_t *result; MYSQL_BIND parameters[1]; user_config_entry_t *record; multi_t key = { .type = M_TYPE_STRINGER, .val.st = NULL }; mm_wipe(parameters, sizeof(parameters)); if (!collection || !collection->usernum) { log_pedantic("Invalid data passed to user config fetch."); return false; } // User Number parameters[0].buffer_type = MYSQL_TYPE_LONGLONG; parameters[0].buffer_length = sizeof(uint64_t); parameters[0].buffer = &(collection->usernum); parameters[0].is_unsigned = true; if (!(result = stmt_get_result(stmts.select_user_config, parameters))) { log_pedantic("Unable to fetch the user config entries."); return false; } // Loop through each row and create a user config entry. while ((row = res_row_next(result))) { if (!(record = user_config_entry_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(collection->entries, key, record)) { log_info("The index refused to accept a user config entry. { user = %lu }", res_field_uint64(row, 0)); if (record) { user_config_entry_free(record); } res_table_free(result); return false; } } res_table_free(result); return true; }
bool_t check_inx_sthread(check_inx_opt_t *opts) { void *val; multi_t key; uint64_t rnum; char snum[64]; if (!opts || !opts->inx) { return false; } for (uint64_t i = rnum = 0; status() && i < INX_CHECK_OBJECTS; i++) { rnum += (rand_get_uint64()) + 1; snprintf(snum, 64, "%lu", rnum); if (!(val = ns_dupe(snum))) { return false; } mm_wipe(&key, sizeof(multi_t)); key.val.u64 = rnum; key.type = M_TYPE_UINT64; if (!inx_insert(opts->inx, key, val)) { mm_free(val); return false; } // This should trigger a delete operation every after every 255 inserts. if (i && ((i % UCHAR_MAX) == 0) && !inx_delete(opts->inx, key)) { return false; } } return true; }
/** * @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 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; }
/** * Recursively loads the files from a directory and stores them using the tank interface. * * @param location The directory path to search for files. * @return Returns false if an error occurs, otherwise true. */ bool_t check_tokyo_tank_load(char *location, inx_t *check_collection, check_tank_opt_t *opts) { int fd; multi_t key; DIR *working; struct stat info; check_tank_obj_t *obj; struct dirent *entry; char file[1024], *buffer; if (!(working = opendir(location))) { log_info("Unable to open the data path. {location = %s}", location); return false; } while (status() && (entry = readdir(working))) { // Reset. errno = 0; bzero(file, 1024); bzero(&info, sizeof(struct stat)); // Build an absolute path. snprintf(file, 1024, "%s%s%s", location, "/", entry->d_name); // If we hit a directory, recursively call the load function. if (entry->d_type == DT_DIR && *(entry->d_name) != '.') { if (!check_tokyo_tank_load(file, check_collection, opts)) { return false; } } // Otherwise if its a regular file try storing it. else if (entry->d_type == DT_REG && *(entry->d_name) != '.') { // Read the file. if ((fd = open(file, O_RDONLY)) < 0) { log_info("%s - open error", file); closedir(working); return false; } // How big is the file? if (fstat(fd, &info) != 0) { log_info("%s - stat error", file); closedir(working); close(fd); return false; } // Allocate a buffer. if (!(buffer = mm_alloc(info.st_size + 1))) { log_info("%s - malloc error", file); closedir(working); close(fd); return false; } // Clear the buffer. memset(buffer, 0, info.st_size + 1); // Read the file. if (read(fd, buffer, info.st_size) != info.st_size) { log_info("%s - read error", file); closedir(working); mm_free(buffer); close(fd); return false; } close(fd); // Data used for verification. if (!(obj = mm_alloc(sizeof(check_tank_obj_t)))) { log_info("check_tank allocation failed for the file %s", file); closedir(working); mm_free(buffer); return false; } obj->adler32 = hash_adler32(buffer, info.st_size); obj->fletcher32 = hash_fletcher32(buffer, info.st_size); obj->crc32 = hash_crc32(buffer, info.st_size); obj->crc64 = hash_crc64(buffer, info.st_size); obj->murmur32 = hash_murmur32(buffer, info.st_size); obj->murmur64 = hash_murmur64(buffer, info.st_size); // Request the next storage tank. obj->tnum = tank_cycle(); // Try storing the file data. if (!(obj->onum = tank_store(TANK_CHECK_DATA_HNUM, obj->tnum, TANK_CHECK_DATA_UNUM, PLACER(buffer, info.st_size), opts->engine))) { log_info("tank_store failed for the file %s", file); closedir(working); mm_free(buffer); mm_free(obj); return false; } mm_free(buffer); key = mt_set_type(key, M_TYPE_UINT64); key.val.u64 = obj->onum; if (!inx_insert(check_collection, key, obj)) { log_info("inx_insert failed for the file %s", file); closedir(working); mm_free(obj); return false; } } } closedir(working); return true; }
/** * @brief Parse a json string array into a linked list of managed strings. * @param json a json object containing an array of strings. * @param nout if not NULL, an optional pointer to a size_t to receive the item count of the json string array. * @return NULL on failure, or a pointer to an inx holder containing the specified json array contents as a collection of managed strings. */ inx_t * portal_parse_json_str_array (json_t *json, size_t *nout) { inx_t *result; stringer_t *istr; const chr_t *str; size_t count; multi_t key = { .type = M_TYPE_UINT64, .val.u64 = 0 }; if (!json) { log_pedantic("Portal cannot parse null json array."); return NULL; // The json object must be an array. } else if (!json_is_array(json)) { log_pedantic("Portal cannot parse mistyped json array."); return NULL; } else if (!(result = inx_alloc(M_INX_LINKED, st_free))) { log_error("Portal could not allocate space to parse json array."); return NULL; } // If it's an empty array, return right away. if (!(count = json_array_size_d(json))) { if (nout) { *nout = 0; } return result; } // Before starting a SQL transaction check that all of the array values are positive integer values. for (size_t i = 0; i < count; i++) { if (!json_is_string(json_array_get_d(json, i))) { log_pedantic("Portal cannot parse json string array with non-string element."); inx_free(result); return NULL; } if (!(str = json_string_value_d(json_array_get_d(json, i)))) { log_pedantic("Portal encountered json string array with NULL element."); inx_free(result); return NULL; } if (!(istr = st_import(str, ns_length_get(str)))) { log_error("Portal could not import string from json array."); inx_free(result); return NULL; } key.val.u64 = i+1; if (!inx_insert(result, key, istr)) { log_error("Portal could not prepare data from json array."); inx_free(result); return NULL; } } if (nout) { *nout = count; } return result; } /** * @brief Parse the context of a requested folder. * @note If no context is specified, the "mail" context is assumed (PORTAL_ENDPOINT_CONTEXT_MAIL). * @param context a managed string containing the context (supported values are "mail", "contacts", "settings", and "help"). * @return PORTAL_ENDPOINT_CONTEXT_INVALID on failure, or the flag of the determined context on success. */ int_t portal_parse_context(stringer_t *context) { int_t result = PORTAL_ENDPOINT_CONTEXT_MAIL; if (!context) { return result; } // Parse the context. if (!st_cmp_ci_eq(context, PLACER("mail", 4))) { result = PORTAL_ENDPOINT_CONTEXT_MAIL; } else if (!st_cmp_ci_eq(context, PLACER("contacts", 8))) { result = PORTAL_ENDPOINT_CONTEXT_CONTACTS; } else if (!st_cmp_ci_eq(context, PLACER("settings", 8))) { result = PORTAL_ENDPOINT_CONTEXT_SETTINGS; } else if (!st_cmp_ci_eq(context, PLACER("help", 4))) { result = PORTAL_ENDPOINT_CONTEXT_HELP; } else { result = PORTAL_ENDPOINT_CONTEXT_INVALID; } return result; }