/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 *
 * Copyright 2015 Thincast Technologies GmbH
 * Copyright 2015 Armin Novak <armin.novak@thincast.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <winpr/path.h>
#include <winpr/file.h>
#include <winpr/sysinfo.h>

#include <freerdp/crypto/certificate.h>

static int prepare(const char* currentFileV2, const char* legacyFileV2, const char* legacyFile)
{
	const char* legacy[] = { "someurl ff:11:22:dd\r\n", "otherurl aa:bb:cc:dd\r",
		                     "legacyurl aa:bb:cc:dd\n" };
	const char* hosts[] = { "#somecomment\r\n"
		                    "someurl 3389 ff:11:22:dd subject issuer\r\n"
		                    " \t#anothercomment\r\n"
		                    "otherurl\t3389\taa:bb:cc:dd\tsubject2\tissuer2\r" };
	FILE* fl = NULL;
	FILE* fc = NULL;
	size_t i;
	fc = winpr_fopen(currentFileV2, "w+");

	if (!fc)
		goto finish;

	fl = winpr_fopen(legacyFileV2, "w+");

	if (!fl)
		goto finish;

	for (i = 0; i < ARRAYSIZE(hosts); i++)
	{
		if (fwrite(hosts[i], strlen(hosts[i]), 1, fl) != 1 ||
		    fwrite(hosts[i], strlen(hosts[i]), 1, fc) != 1)
			goto finish;
	}

	fclose(fc);
	fc = NULL;
	fclose(fl);
	fl = NULL;
	fl = winpr_fopen(legacyFile, "w+");

	if (!fl)
		goto finish;

	for (i = 0; i < ARRAYSIZE(legacy); i++)
	{
		if (fwrite(legacy[i], strlen(legacy[i]), 1, fl) != 1)
			goto finish;
	}

	fclose(fl);
	return 0;
finish:

	if (fl)
		fclose(fl);

	if (fc)
		fclose(fc);

	return -1;
}

int TestKnownHosts(int argc, char* argv[])
{
	int rc = -1;
	rdpSettings current;
	rdpSettings legacy;
	rdpCertificateData* data = NULL;
	rdpCertificateStore* store = NULL;
	char* currentFileV2 = NULL;
	char* legacyFileV2 = NULL;
	char* legacyFile = NULL;
	char* subject = NULL;
	char* issuer = NULL;
	char* fp = NULL;
	char sname[8192];
	char dname[8192];
	SYSTEMTIME systemTime;
	WINPR_UNUSED(argc);
	WINPR_UNUSED(argv);
	GetSystemTime(&systemTime);
	sprintf_s(sname, sizeof(sname),
	          "TestKnownHostsCurrent-%04" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16
	          "%02" PRIu16 "%04" PRIu16,
	          systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour,
	          systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds);
	sprintf_s(dname, sizeof(dname),
	          "TestKnownHostsLegacy-%04" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16
	          "%02" PRIu16 "%04" PRIu16,
	          systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour,
	          systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds);

	current.ConfigPath = GetKnownSubPath(KNOWN_PATH_TEMP, sname);
	legacy.ConfigPath = GetKnownSubPath(KNOWN_PATH_TEMP, dname);

	if (!PathFileExistsA(current.ConfigPath))
	{
		if (!CreateDirectoryA(current.ConfigPath, NULL))
		{
			fprintf(stderr, "Could not create %s!\n", current.ConfigPath);
			goto finish;
		}
	}

	if (!PathFileExistsA(legacy.ConfigPath))
	{
		if (!CreateDirectoryA(legacy.ConfigPath, NULL))
		{
			fprintf(stderr, "Could not create %s!\n", legacy.ConfigPath);
			goto finish;
		}
	}

	currentFileV2 = GetCombinedPath(current.ConfigPath, "known_hosts2");

	if (!currentFileV2)
	{
		fprintf(stderr, "Could not get file path!\n");
		goto finish;
	}

	legacyFileV2 = GetCombinedPath(legacy.ConfigPath, "known_hosts2");

	if (!legacyFileV2)
	{
		fprintf(stderr, "Could not get file path!\n");
		goto finish;
	}

	legacyFile = GetCombinedPath(legacy.ConfigPath, "known_hosts");

	if (!legacyFile)
	{
		fprintf(stderr, "Could not get file path!\n");
		goto finish;
	}

	store = certificate_store_new(&current);

	if (!store)
	{
		fprintf(stderr, "Could not create certificate store!\n");
		goto finish;
	}

	if (prepare(currentFileV2, legacyFileV2, legacyFile))
		goto finish;

	/* Test if host is found in current file. */
	data = certificate_data_new("someurl", 3389, "subject", "issuer", "ff:11:22:dd");

	if (!data)
	{
		fprintf(stderr, "Could not create certificate data!\n");
		goto finish;
	}

	if (0 != certificate_data_match(store, data))
	{
		fprintf(stderr, "Could not find data in v2 file!\n");
		goto finish;
	}

	/* Test if we can read out the old fingerprint. */
	if (!certificate_get_stored_data(store, data, &subject, &issuer, &fp))
	{
		fprintf(stderr, "Could not read old fingerprint!\n");
		goto finish;
	}

	printf("Got %s, %s '%s'\n", subject, issuer, fp);
	free(subject);
	free(issuer);
	free(fp);
	subject = NULL;
	issuer = NULL;
	fp = NULL;
	certificate_data_free(data);
	/* Test if host not found in current file. */
	data = certificate_data_new("somehost", 1234, "", "", "ff:aa:bb:cc");

	if (!data)
	{
		fprintf(stderr, "Could not create certificate data!\n");
		goto finish;
	}

	if (0 == certificate_data_match(store, data))
	{
		fprintf(stderr, "Invalid host found in v2 file!\n");
		goto finish;
	}

	/* Test if we read out the old fingerprint fails. */
	if (certificate_get_stored_data(store, data, &subject, &issuer, &fp))
	{
		fprintf(stderr, "Read out not existing old fingerprint succeeded?!\n");
		goto finish;
	}

	certificate_data_free(data);
	/* Test host add current file. */
	data = certificate_data_new("somehost", 1234, "", "", "ff:aa:bb:cc");

	if (!data)
	{
		fprintf(stderr, "Could not create certificate data!\n");
		goto finish;
	}

	if (!certificate_data_print(store, data))
	{
		fprintf(stderr, "Could not add host to file!\n");
		goto finish;
	}

	if (0 != certificate_data_match(store, data))
	{
		fprintf(stderr, "Could not find host written in v2 file!\n");
		goto finish;
	}

	certificate_data_free(data);
	/* Test host replace current file. */
	data = certificate_data_new("somehost", 1234, "", "", "ff:aa:bb:dd:ee");

	if (!data)
	{
		fprintf(stderr, "Could not create certificate data!\n");
		goto finish;
	}

	if (!certificate_data_replace(store, data))
	{
		fprintf(stderr, "Could not replace data!\n");
		goto finish;
	}

	if (0 != certificate_data_match(store, data))
	{
		fprintf(stderr, "Invalid host found in v2 file!\n");
		goto finish;
	}

	certificate_data_free(data);
	/* Test host replace invalid entry in current file. */
	data = certificate_data_new("somehostXXXX", 1234, "", "", "ff:aa:bb:dd:ee");

	if (!data)
	{
		fprintf(stderr, "Could not create certificate data!\n");
		goto finish;
	}

	if (certificate_data_replace(store, data))
	{
		fprintf(stderr, "Invalid return for replace invalid entry!\n");
		goto finish;
	}

	if (0 == certificate_data_match(store, data))
	{
		fprintf(stderr, "Invalid host found in v2 file!\n");
		goto finish;
	}

	certificate_data_free(data);
	certificate_store_free(store);
	store = certificate_store_new(&legacy);

	if (!store)
	{
		fprintf(stderr, "could not create certificate store!\n");
		goto finish;
	}

	/* test if host found in legacy file. */
	data = certificate_data_new("legacyurl", 1234, "", "", "aa:bb:cc:dd");

	if (!data)
	{
		fprintf(stderr, "Could not create certificate data!\n");
		goto finish;
	}

	if (0 != certificate_data_match(store, data))
	{
		fprintf(stderr, "Could not find host in file!\n");
		goto finish;
	}

	certificate_data_free(data);
	/* test if host not found. */
	data = certificate_data_new("somehost-not-in-file", 1234, "", "", "ff:aa:bb:cc");

	if (!data)
	{
		fprintf(stderr, "Could not create certificate data!\n");
		goto finish;
	}

	if (0 == certificate_data_match(store, data))
	{
		fprintf(stderr, "Invalid host found in file!\n");
		goto finish;
	}

	rc = 0;
finish:
	free(current.ConfigPath);
	free(legacy.ConfigPath);

	if (store)
		certificate_store_free(store);

	if (data)
		certificate_data_free(data);

	DeleteFileA(currentFileV2);
	// RemoveDirectoryA(current.ConfigPath);
	DeleteFileA(legacyFileV2);
	DeleteFileA(legacyFile);
	// RemoveDirectoryA(legacy.ConfigPath);
	free(currentFileV2);
	free(legacyFileV2);
	free(legacyFile);
	free(subject);
	free(issuer);
	free(fp);
	return rc;
}
