/*
 * Copyright (c) 2016 Google, Inc.  All Rights Reserved.
 *
 * Author: Eric Biggers <ebiggers@google.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "platform_defs.h"
#include "command.h"
#include "init.h"
#include "io.h"

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif

/*
 * We may have to declare the fscrypt ioctls ourselves because someone may be
 * compiling xfsprogs with old kernel headers.  And since some old versions of
 * <linux/fs.h> declared the policy struct and ioctl numbers but not the flags
 * and modes, our declarations must be split into two conditional blocks.
 */

/* Policy struct and ioctl numbers */
#ifndef FS_IOC_SET_ENCRYPTION_POLICY
#define FS_KEY_DESCRIPTOR_SIZE  8

struct fscrypt_policy {
	__u8 version;
	__u8 contents_encryption_mode;
	__u8 filenames_encryption_mode;
	__u8 flags;
	__u8 master_key_descriptor[FS_KEY_DESCRIPTOR_SIZE];
} __attribute__((packed));

#define FS_IOC_SET_ENCRYPTION_POLICY	_IOR('f', 19, struct fscrypt_policy)
#define FS_IOC_GET_ENCRYPTION_PWSALT	_IOW('f', 20, __u8[16])
#define FS_IOC_GET_ENCRYPTION_POLICY	_IOW('f', 21, struct fscrypt_policy)
#endif /* FS_IOC_SET_ENCRYPTION_POLICY */

/* Policy flags and encryption modes */
#ifndef FS_ENCRYPTION_MODE_AES_256_XTS
#define FS_POLICY_FLAGS_PAD_4		0x00
#define FS_POLICY_FLAGS_PAD_8		0x01
#define FS_POLICY_FLAGS_PAD_16		0x02
#define FS_POLICY_FLAGS_PAD_32		0x03
#define FS_POLICY_FLAGS_PAD_MASK	0x03
#define FS_POLICY_FLAGS_VALID		0x03

#define FS_ENCRYPTION_MODE_INVALID	0
#define FS_ENCRYPTION_MODE_AES_256_XTS	1
#define FS_ENCRYPTION_MODE_AES_256_GCM	2
#define FS_ENCRYPTION_MODE_AES_256_CBC	3
#define FS_ENCRYPTION_MODE_AES_256_CTS	4
#endif /* FS_ENCRYPTION_MODE_AES_256_XTS */

static cmdinfo_t get_encpolicy_cmd;
static cmdinfo_t set_encpolicy_cmd;

static void
set_encpolicy_help(void)
{
	printf(_(
"\n"
" assign an encryption policy to the currently open file\n"
"\n"
" Examples:\n"
" 'set_encpolicy' - assign policy with default key [0000000000000000]\n"
" 'set_encpolicy 0000111122223333' - assign policy with specified key\n"
"\n"
" -c MODE -- contents encryption mode\n"
" -n MODE -- filenames encryption mode\n"
" -f FLAGS -- policy flags\n"
" -v VERSION -- version of policy structure\n"
"\n"
" MODE can be numeric or one of the following predefined values:\n"
"    AES-256-XTS, AES-256-CTS, AES-256-GCM, AES-256-CBC\n"
" FLAGS and VERSION must be numeric.\n"
"\n"
" Note that it's only possible to set an encryption policy on an empty\n"
" directory.  It's then inherited by new files and subdirectories.\n"
"\n"));
}

static const struct {
	__u8 mode;
	const char *name;
} available_modes[] = {
	{FS_ENCRYPTION_MODE_AES_256_XTS, "AES-256-XTS"},
	{FS_ENCRYPTION_MODE_AES_256_CTS, "AES-256-CTS"},
	{FS_ENCRYPTION_MODE_AES_256_GCM, "AES-256-GCM"},
	{FS_ENCRYPTION_MODE_AES_256_CBC, "AES-256-CBC"},
};

static bool
parse_byte_value(const char *arg, __u8 *value_ret)
{
	long value;
	char *tmp;

	value = strtol(arg, &tmp, 0);
	if (value < 0 || value > 255 || tmp == arg || *tmp != '\0')
		return false;
	*value_ret = value;
	return true;
}

static bool
parse_mode(const char *arg, __u8 *mode_ret)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(available_modes); i++) {
		if (strcmp(arg, available_modes[i].name) == 0) {
			*mode_ret = available_modes[i].mode;
			return true;
		}
	}

	return parse_byte_value(arg, mode_ret);
}

static const char *
mode2str(__u8 mode)
{
	static char buf[32];
	int i;

	for (i = 0; i < ARRAY_SIZE(available_modes); i++)
		if (mode == available_modes[i].mode)
			return available_modes[i].name;

	sprintf(buf, "0x%02x", mode);
	return buf;
}

static const char *
keydesc2str(__u8 master_key_descriptor[FS_KEY_DESCRIPTOR_SIZE])
{
	static char buf[2 * FS_KEY_DESCRIPTOR_SIZE + 1];
	int i;

	for (i = 0; i < FS_KEY_DESCRIPTOR_SIZE; i++)
		sprintf(&buf[2 * i], "%02x", master_key_descriptor[i]);

	return buf;
}

static int
get_encpolicy_f(int argc, char **argv)
{
	struct fscrypt_policy policy;

	if (ioctl(file->fd, FS_IOC_GET_ENCRYPTION_POLICY, &policy) < 0) {
		fprintf(stderr, "%s: failed to get encryption policy: %s\n",
			file->name, strerror(errno));
		exitcode = 1;
		return 0;
	}

	printf("Encryption policy for %s:\n", file->name);
	printf("\tPolicy version: %u\n", policy.version);
	printf("\tMaster key descriptor: %s\n",
	       keydesc2str(policy.master_key_descriptor));
	printf("\tContents encryption mode: %u (%s)\n",
	       policy.contents_encryption_mode,
	       mode2str(policy.contents_encryption_mode));
	printf("\tFilenames encryption mode: %u (%s)\n",
	       policy.filenames_encryption_mode,
	       mode2str(policy.filenames_encryption_mode));
	printf("\tFlags: 0x%02x\n", policy.flags);
	return 0;
}

static int
set_encpolicy_f(int argc, char **argv)
{
	int c;
	struct fscrypt_policy policy;

	/* Initialize the policy structure with default values */
	memset(&policy, 0, sizeof(policy));
	policy.contents_encryption_mode = FS_ENCRYPTION_MODE_AES_256_XTS;
	policy.filenames_encryption_mode = FS_ENCRYPTION_MODE_AES_256_CTS;
	policy.flags = FS_POLICY_FLAGS_PAD_16;

	/* Parse options */
	while ((c = getopt(argc, argv, "c:n:f:v:")) != EOF) {
		switch (c) {
		case 'c':
			if (!parse_mode(optarg,
					&policy.contents_encryption_mode)) {
				fprintf(stderr, "invalid contents encryption "
					"mode: %s\n", optarg);
				return 0;
			}
			break;
		case 'n':
			if (!parse_mode(optarg,
					&policy.filenames_encryption_mode)) {
				fprintf(stderr, "invalid filenames encryption "
					"mode: %s\n", optarg);
				return 0;
			}
			break;
		case 'f':
			if (!parse_byte_value(optarg, &policy.flags)) {
				fprintf(stderr, "invalid flags: %s\n", optarg);
				return 0;
			}
			break;
		case 'v':
			if (!parse_byte_value(optarg, &policy.version)) {
				fprintf(stderr, "invalid policy version: %s\n",
					optarg);
				return 0;
			}
			break;
		default:
			return command_usage(&set_encpolicy_cmd);
		}
	}
	argc -= optind;
	argv += optind;

	if (argc > 1)
		return command_usage(&set_encpolicy_cmd);

	/* Parse key descriptor if specified */
	if (argc > 0) {
		const char *keydesc = argv[0];
		char *tmp;
		unsigned long long x;
		int i;

		if (strlen(keydesc) != FS_KEY_DESCRIPTOR_SIZE * 2) {
			fprintf(stderr, "invalid key descriptor: %s\n",
				keydesc);
			return 0;
		}

		x = strtoull(keydesc, &tmp, 16);
		if (tmp == keydesc || *tmp != '\0') {
			fprintf(stderr, "invalid key descriptor: %s\n",
				keydesc);
			return 0;
		}

		for (i = 0; i < FS_KEY_DESCRIPTOR_SIZE; i++) {
			policy.master_key_descriptor[i] = x >> 56;
			x <<= 8;
		}
	}

	/* Set the encryption policy */
	if (ioctl(file->fd, FS_IOC_SET_ENCRYPTION_POLICY, &policy) < 0) {
		fprintf(stderr, "%s: failed to set encryption policy: %s\n",
			file->name, strerror(errno));
		exitcode = 1;
		return 0;
	}

	return 0;
}

void
encrypt_init(void)
{
	get_encpolicy_cmd.name = "get_encpolicy";
	get_encpolicy_cmd.cfunc = get_encpolicy_f;
	get_encpolicy_cmd.argmin = 0;
	get_encpolicy_cmd.argmax = 0;
	get_encpolicy_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK;
	get_encpolicy_cmd.oneline =
		_("display the encryption policy of the current file");

	set_encpolicy_cmd.name = "set_encpolicy";
	set_encpolicy_cmd.cfunc = set_encpolicy_f;
	set_encpolicy_cmd.args =
		_("[-c mode] [-n mode] [-f flags] [-v version] [keydesc]");
	set_encpolicy_cmd.argmin = 0;
	set_encpolicy_cmd.argmax = -1;
	set_encpolicy_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK;
	set_encpolicy_cmd.oneline =
		_("assign an encryption policy to the current file");
	set_encpolicy_cmd.help = set_encpolicy_help;

	add_command(&get_encpolicy_cmd);
	add_command(&set_encpolicy_cmd);
}
