/*
 * Copyright 2012 <James.Bottomley@HansenPartnership.com>
 *
 * see COPYING file
 *
 * Simple elf loader based on Intel TianoCore
 */

#include <efi.h>
#include <efilib.h>

#include <simple_file.h>
#include <pecoff.h>
#include <sha256.h>
#include <variables.h>
#include <console.h>
#include <efiauthenticated.h>
#include <guid.h>
#include <execute.h>

CHAR16 *loader = L"\\linux-loader.efi";

/* get the user's permission to boot the image */
int ask_to_boot(void)
{
	return console_yes_no( (CHAR16 *[]) {
		L"WARNING: This Binary is unsigned (and should be a Linux boot loader)",
		L"",
		L"Are you sure you wish to run an unsigned binary",
		L"in a secure environment?",
		L"",
		L"To avoid this question in future place the platform into setup mode",
		L"See http://www.linuxfoundation.org/uefi",
		L"And reboot.",
		NULL,
	});
}
/* Get the user's permission to install the image signature */
static int
ask_install_keys(void)
{
	/* first check to see if the key is already present */
	return console_yes_no( (CHAR16 *[]){ 
		L"You are in Setup Mode",
		L"",
		L"Do you wish me to install the signature",
		L"of the binary into the allowed signatures database?",
		L"",
		L"If you say \"yes\" here, the platform will no longer ask permission",
		L"to run the binary on every boot",
		NULL
	});
}

EFI_STATUS
efi_main (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
	EFI_STATUS efi_status;
	UINT8 SecureBoot = 0, SetupMode = 0;
	UINTN DataSize = sizeof(SecureBoot);
	EFI_FILE *file;
	EFI_LOADED_IMAGE *li;
	EFI_DEVICE_PATH *loadpath = NULL;
	CHAR16 *PathName = NULL;
	EFI_HANDLE loader_handle;

	InitializeLib(image, systab);

	efi_status = RT->GetVariable(L"SecureBoot", &GV_GUID, NULL, &DataSize, &SecureBoot);

	if (efi_status != EFI_SUCCESS) {
		Print(L"Not a Secure Boot Platform %d\n", efi_status);
	} else	if (!SecureBoot) {
		Print(L"Secure Boot Disabled\n");
		DataSize = sizeof(SetupMode);
	}

	RT->GetVariable(L"SetupMode", &GV_GUID, NULL, &DataSize, &SetupMode);

	efi_status = BS->HandleProtocol(image, &IMAGE_PROTOCOL, (VOID **)&li);
	if (efi_status != EFI_SUCCESS) {
		Print(L"Failed to init loaded image protocol: %d\n", efi_status);
		return efi_status;
	}

	efi_status = generate_path(loader, li, &loadpath, &PathName);
	if (efi_status != EFI_SUCCESS) {
		Print(L"Unable to generate load path for %s: %d\n", loader,
		      efi_status);
		return efi_status;
	}

	if (!SetupMode) {
		efi_status = BS->LoadImage(FALSE, image, loadpath, NULL,
					   0, &loader_handle);
		if (efi_status == EFI_SUCCESS) {
			/* Image validates - start it */
			Print(L"Starting file via StartImage\n");
			BS->StartImage(loader_handle, NULL, NULL);
			BS->UnloadImage(loader_handle);
			return EFI_SUCCESS;
		} else {
			Print(L"Failed to load the image: %d\n", efi_status);
		}
	}

	if (SecureBoot) {
		if (ask_to_boot() == 0) {
			/* user told us not to boot this */
			Print(L"Refusing to boot %s\n", loader);
			return EFI_ACCESS_DENIED;
		}
		Print(L"Booting %s with Present User Authorisation\n", loader);
	}
	efi_status = simple_file_open(image, loader, &file, EFI_FILE_MODE_READ);
	if (efi_status != EFI_SUCCESS) {
		Print(L"Failed to open %s\n", loader);
		return efi_status;
	}

	/* We're in setup mode and the User asked us to add the signature
	 * of this binary to the authorized signatures database */
	if (SetupMode) {
		UINT8 hash[SHA256_DIGEST_SIZE];
		int i;

		sha256_get_pecoff_digest(image, loader, hash);
		Print(L"HASH IS ");
		for (i=0; i<SHA256_DIGEST_SIZE; i++)
			Print(L"%02x", hash[i]);
		Print(L"\n");

		if (find_in_variable_esl(L"db", SIG_DB, hash, SHA256_DIGEST_SIZE) == EFI_SUCCESS)
			goto dont_ask;

		if (ask_install_keys()) {
			UINT8 sig[sizeof(EFI_SIGNATURE_LIST) + sizeof(EFI_SIGNATURE_DATA) - 1 + SHA256_DIGEST_SIZE];
			EFI_SIGNATURE_LIST *l = (void *)sig;
			EFI_SIGNATURE_DATA *d = (void *)(sig + sizeof(EFI_SIGNATURE_LIST));
			SetMem(sig, sizeof(sig), 0);
			l->SignatureType = EFI_CERT_SHA256_GUID;
			l->SignatureListSize = sizeof(sig);
			l->SignatureSize = 16 +32; /* UEFI defined */
			CopyMem(&d->SignatureData, hash, sizeof(hash));

			efi_status = SetSecureVariable(L"db", sig, sizeof(sig), SIG_DB, EFI_VARIABLE_APPEND_WRITE, 0);
			if (efi_status != EFI_SUCCESS) {
				Print(L"Failed to add signature to db: %s\n", efi_status);
				return efi_status;
			}
		}
	dont_ask:
		;
	}

	efi_status = pecoff_execute_image(file, loader, image, systab);
	simple_file_close(file);

	return efi_status;
}
