/* Authors: Christopher Ashworth <cashworth@tresys.com>
 *          Caleb Case <ccase@tresys.com>
 *          Chris PeBenito <cpebenito@tresys.com>
 *
 * Copyright (C) 2006 Tresys Technology, LLC
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*  The purpose of this file is to provide unit tests of the functions in:
 *
 *  libsemanage/src/semanage_store.c
 *
 */

#include "handle.h"
#include "semanage_store.h"

#include "utilities.h"
#include "test_semanage_store.h"

#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <CUnit/Basic.h>

semanage_handle_t *sh = NULL;
const char *rootpath = "./test-policy";
const char *polpath = "./test-policy/store/";
const char *readlockpath = "./test-policy/store/semanage.read.LOCK";
const char *translockpath = "./test-policy/store/semanage.trans.LOCK";
const char *actpath = "./test-policy/store/active";
const char *modpath = "./test-policy/store/active/modules";

/* The suite initialization function.
 * Returns zero on success, non-zero otherwise.
 */
int semanage_store_test_init(void)
{
	int err;

	/* create directories */
	err = mkdir(rootpath, S_IRUSR | S_IWUSR | S_IXUSR);
	if (err != 0)
		return -1;

	err = mkdir(polpath, S_IRUSR | S_IWUSR | S_IXUSR);
	if (err != 0)
		return -1;

	err = mkdir(actpath, S_IRUSR | S_IWUSR | S_IXUSR);
	if (err != 0)
		return -1;

	err = mkdir(modpath, S_IRUSR | S_IWUSR | S_IXUSR);
	if (err != 0)
		return -1;

	/* initialize the handle */
	sh = semanage_handle_create();
	if (sh == NULL)
		return -1;

	/* hide error messages */
	sh->msg_callback = test_msg_handler;

	/* use our own policy store */
	free(sh->conf->store_path);
	sh->conf->store_path = strdup("store");

	/* initialize paths */
	err = semanage_check_init(sh, rootpath);
	if (err != 0)
		return -1;

	return 0;
}

/* The suite cleanup function.
 * Returns zero on success, non-zero otherwise.
 */
int semanage_store_test_cleanup(void)
{
	int err;

	/* remove the test policy directories */
	err = rmdir(modpath);
	if (err != 0)
		return -1;

	err = rmdir(actpath);
	if (err != 0)
		return -1;

	err = rmdir(polpath);
	if (err != 0)
		return -1;

	err = rmdir(rootpath);
	if (err != 0)
		return -1;

	/* cleanup the handle */
	semanage_handle_destroy(sh);
	return 0;
}

/* Adds all the tests needed for this suite. 
 */
int semanage_store_add_tests(CU_pSuite suite)
{
	if (NULL ==
	    CU_add_test(suite, "semanage_store_access_check",
			test_semanage_store_access_check)) {
		CU_cleanup_registry();
		return CU_get_error();
	}

	if (NULL ==
	    CU_add_test(suite, "semanage_get_lock", test_semanage_get_lock)) {
		CU_cleanup_registry();
		return CU_get_error();
	}

	if (NULL ==
	    CU_add_test(suite, "semanage_nc_sort", test_semanage_nc_sort)) {
		CU_cleanup_registry();
		return CU_get_error();
	}

	return 0;
}

/* Tests the semanage_store_access_check function in semanage_store.c
 */
void test_semanage_store_access_check(void)
{
	int err;

	/* create lock file */
	err = mknod(readlockpath, S_IRUSR | S_IWUSR, S_IFREG);

	/* check with permissions 000 */
	err = chmod(modpath, 0);
	CU_ASSERT(err == 0);
	err = chmod(readlockpath, 0);
	CU_ASSERT(err == 0);
	err = chmod(polpath, 0);
	CU_ASSERT(err == 0);

	err = semanage_store_access_check();
	CU_ASSERT(err == -1);

	/* check with permissions 500 */
	err = chmod(polpath, S_IRUSR | S_IXUSR);
	CU_ASSERT(err == 0);
	err = chmod(readlockpath, S_IRUSR);
	CU_ASSERT(err == 0);
	err = chmod(modpath, S_IRUSR | S_IXUSR);
	CU_ASSERT(err == 0);

	err = semanage_store_access_check();
	CU_ASSERT(err == SEMANAGE_CAN_READ);

	/* check with permissions 700 */
	err = chmod(polpath, S_IRUSR | S_IWUSR | S_IXUSR);
	CU_ASSERT(err == 0);
	err = chmod(readlockpath, S_IRUSR | S_IWUSR);
	CU_ASSERT(err == 0);
	err = chmod(modpath, S_IRUSR | S_IWUSR | S_IXUSR);
	CU_ASSERT(err == 0);

	err = semanage_store_access_check();
	CU_ASSERT(err == SEMANAGE_CAN_WRITE);

	/* check with lock file 000 and others 500 */
	err = chmod(polpath, S_IRUSR | S_IXUSR);
	CU_ASSERT(err == 0);
	err = chmod(readlockpath, 0);
	CU_ASSERT(err == 0);
	err = chmod(modpath, S_IRUSR | S_IXUSR);
	CU_ASSERT(err == 0);

	err = semanage_store_access_check();
	CU_ASSERT(err == 0);

	/* check with lock file 000 and others 700 */
	err = chmod(polpath, S_IRUSR | S_IWUSR | S_IXUSR);
	CU_ASSERT(err == 0);
	err = chmod(readlockpath, 0);
	CU_ASSERT(err == 0);
	err = chmod(modpath, S_IRUSR | S_IWUSR | S_IXUSR);
	CU_ASSERT(err == 0);

	err = semanage_store_access_check();
	CU_ASSERT(err == 0);

	/* remove lock file */
	err = remove(readlockpath);
	CU_ASSERT(err == 0);

	/* check with no lock file and 000 */
	err = chmod(modpath, 0);
	CU_ASSERT(err == 0);
	err = chmod(polpath, 0);
	CU_ASSERT(err == 0);

	err = semanage_store_access_check();
	CU_ASSERT(err == -1);

	/* check with no lock file and 500 */
	err = chmod(polpath, S_IRUSR | S_IXUSR);
	CU_ASSERT(err == 0);
	err = chmod(modpath, S_IRUSR | S_IXUSR);
	CU_ASSERT(err == 0);

	err = semanage_store_access_check();
	CU_ASSERT(err == 0);

	/* check with no lock file but write in polpath */
	err = chmod(polpath, S_IRUSR | S_IWUSR | S_IXUSR);
	CU_ASSERT(err == 0);

	err = semanage_store_access_check();
	CU_ASSERT(err == SEMANAGE_CAN_READ);

	/* check with no lock file and 700 */
	err = chmod(polpath, S_IRUSR | S_IWUSR | S_IXUSR);
	CU_ASSERT(err == 0);
	err = chmod(modpath, S_IRUSR | S_IWUSR | S_IXUSR);
	CU_ASSERT(err == 0);

	err = semanage_store_access_check();
	CU_ASSERT(err == SEMANAGE_CAN_WRITE);
}

/* Tests the semanage_get_lock functions in semanage_store.c
 */
void test_semanage_get_lock(void)
{
	int err;

	/* attempt to get an active lock */
	err = semanage_get_active_lock(sh);
	CU_ASSERT(err == 0);

	/* attempt to get the lock again */
	err = semanage_get_active_lock(sh);
	CU_ASSERT(err == 0);

	/* attempt to release the active lock */
	semanage_release_active_lock(sh);

	/* attempt to get an active lock */
	err = semanage_get_active_lock(sh);
	CU_ASSERT(err == 0);

	/* attempt to release the active lock */
	semanage_release_active_lock(sh);

	/* attempt to get a trans lock */
	err = semanage_get_trans_lock(sh);
	CU_ASSERT(err == 0);

	/* attempt to get the lock again */
	err = semanage_get_trans_lock(sh);
	CU_ASSERT(err == 0);

	/* attempt to release the trans lock */
	semanage_release_trans_lock(sh);

	/* attempt to get a trans lock */
	err = semanage_get_trans_lock(sh);
	CU_ASSERT(err == 0);

	/* attempt to release the trans lock */
	semanage_release_trans_lock(sh);

	/* remove the lock files */
	err = remove(readlockpath);
	CU_ASSERT(err == 0);
	err = remove(translockpath);
	CU_ASSERT(err == 0);
}

/* Tests the semanage_nc_sort function in semanage_store.c
 */
void test_semanage_nc_sort(void)
{
	char *source_buf, *sorted_buf = NULL, *good_buf, *bad_buf;
	size_t source_buf_len, sorted_buf_len, good_buf_len, bad_buf_len;
	int sourcefd, goodfd, badfd, err;
	struct stat sb;

	/* open source file */
	sourcefd = open("nc_sort_unsorted", O_RDONLY);
	if (sourcefd < 0) {
		CU_FAIL("Missing nc_sort_unsorted test file.");
		return;
	}
	fstat(sourcefd, &sb);
	source_buf_len = sb.st_size;
	source_buf =
	    (char *)mmap(NULL, source_buf_len, PROT_READ, MAP_PRIVATE, sourcefd,
			 0);

	/* open good result file */
	goodfd = open("nc_sort_sorted", O_RDONLY);
	if (goodfd < 0) {
		CU_FAIL("Missing nc_sort_sorted test file.");
		goto out2;
	}
	fstat(goodfd, &sb);
	good_buf_len = sb.st_size;
	good_buf =
	    (char *)mmap(NULL, good_buf_len, PROT_READ, MAP_PRIVATE, goodfd, 0);

	/* open malformed source file (missing priorities) */
	badfd = open("nc_sort_malformed", O_RDONLY);
	if (badfd < 0) {
		CU_FAIL("Missing nc_sort_malformed test file.");
		goto out1;
	}
	fstat(badfd, &sb);
	bad_buf_len = sb.st_size;
	bad_buf =
	    (char *)mmap(NULL, bad_buf_len, PROT_READ, MAP_PRIVATE, badfd, 0);

	/* sort test file */
	err =
	    semanage_nc_sort(sh, source_buf, source_buf_len, &sorted_buf,
			     &sorted_buf_len);
	CU_ASSERT_FALSE(err);
	CU_ASSERT_STRING_EQUAL(sorted_buf, good_buf);

	/* reset for reuse in next test */
	free(sorted_buf);
	sorted_buf = NULL;

	/* sort malformed source file */
	err =
	    semanage_nc_sort(sh, bad_buf, bad_buf_len, &sorted_buf,
			     &sorted_buf_len);
	CU_ASSERT_EQUAL(err, -1);

	free(sorted_buf);

	munmap(bad_buf, bad_buf_len);
	close(badfd);
      out1:
	munmap(good_buf, good_buf_len);
	close(goodfd);
      out2:
	munmap(source_buf, source_buf_len);
	close(sourcefd);
}