Beispiel #1
0
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;
}
Beispiel #2
0
/**
 * @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;
}
Beispiel #4
0
/**
 * @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;
}
Beispiel #5
0
/**
 * @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;
}
Beispiel #7
0
/**
 * @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;
}