/**
 * Copyright (C) 2006 International Business Machines
 * Author(s): Michael A. Halcrow <mhalcrow@us.ibm.com>
 * 	      Trevor Highland <trevor.highland@gmail.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; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * 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 to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include "../include/ecryptfs.h"
#include "../include/decision_graph.h"

static struct param_node key_module_select_node = {
	.num_mnt_opt_names = 1,
	.mnt_opt_names = {"key"},
	.prompt = "Select key type to use for newly created files",
	.val_type = VAL_STR,
	.val = NULL,
	.display_opts = NULL,
	.default_val = NULL,
	.flags = DISPLAY_TRANSITION_NODE_VALS | ECHO_INPUT,
	.num_transitions = 0,
	.tl = {{0}}
};

static int
sig_param_node_callback(struct ecryptfs_ctx *ctx, struct param_node *node,
			struct val_node **head, void **foo)
{
	char *param;
	int rc = 0;

	if (!node->val) {
		node->tl[0].next_token = &key_module_select_node;
		goto out;
	}
	if (strcmp(node->val, "NULL") == 0) {
		node->tl[0].next_token = &key_module_select_node;
		goto out;
	}
	rc = asprintf(&param, "ecryptfs_sig=%s", node->val);
	if (rc == -1) {
		rc = -ENOMEM;
		syslog(LOG_ERR, "Out of memory\n");
		goto out;
	}
	stack_push(head, param);
out:
	return rc;
}

static struct param_node cipher_param_node;

static struct param_node root_param_node = {
	.num_mnt_opt_names = 1,
	.mnt_opt_names = {"sig"},
	.prompt = "Existing key signature",
	.val_type = VAL_STR,
	.val = NULL,
	.display_opts = NULL,
	.default_val = "NULL",
	.flags = 0,
	.num_transitions = 1,
	.tl = {{.val = "default",
		.pretty_val = "default",
		.next_token = &cipher_param_node,
		.trans_func = sig_param_node_callback}}
};

static int get_cipher(struct ecryptfs_ctx *ctx, struct param_node *node,
		      struct val_node **head, void **foo)
{
	char *temp, *val;
	char *prompt;
	int rc, i=1;
	struct ecryptfs_cipher_elem cipher_list_head;
	struct ecryptfs_cipher_elem *current;
	struct ecryptfs_cipher_elem *default_cipher;

	memset(&cipher_list_head, 0, sizeof(struct ecryptfs_cipher_elem));
	if (node->val) {
		rc = asprintf(&temp, "ecryptfs_cipher=%s", node->val);
		free(node->val);
		node->val = NULL;
		if (rc == -1)
			return MOUNT_ERROR;
		goto out;
	}
	if (ctx->verbosity == 0)
		return NULL_TOK;
	rc = ecryptfs_get_current_kernel_ciphers(&cipher_list_head);
	if (rc)
		goto out_error;
	current = cipher_list_head.next;

	asprintf(&prompt, "Cipher\n");
	while (current) {
		rc = asprintf(&temp,"%s%d) %s\n", prompt, i, current->user_name);
		if (rc == -1)
			goto out_error;
		i++;
		free(prompt);
		prompt = temp;
		current = current->next;
	}
	rc = ecryptfs_default_cipher(&default_cipher, &cipher_list_head);
	asprintf(&temp, "%sSelection [%s]", prompt, default_cipher->user_name);
	free(prompt);
	prompt = temp;

get_cipher:
	(ctx->get_string)(&val, prompt, ECHO_INPUT);

	i = atoi(val);
	if (i)
		current = cipher_list_head.next;
	while (current) {
		if (i == 1)
			break;
		current = current->next;
		i--;
	}
	if (current && i == 1) {
		rc = asprintf(&temp, "ecryptfs_key_bytes=%d", current->bytes);
		stack_push(head, temp);
		rc = asprintf(&temp, "ecryptfs_cipher=%s",
			      current->kernel_name);
		if (rc == -1)
			goto out_error;
	}
	else if (val[0] == '\0'){
		rc = asprintf(&temp, "ecryptfs_key_bytes=%d",
			      default_cipher->bytes);
		stack_push(head, temp);
		rc = asprintf(&temp, "ecryptfs_cipher=%s",
			      default_cipher->kernel_name);
		if (rc == -1)
			goto out_error;
	} else {
		goto get_cipher;
	}
	free(node->val);
	node->val = NULL;
	if (rc == -1)
		goto out_error;
out:
	ecryptfs_free_cipher_list(cipher_list_head);
	stack_push(head, temp);
	return NULL_TOK;
out_error:
	ecryptfs_free_cipher_list(cipher_list_head);
		return MOUNT_ERROR;
}

static int get_passthrough(struct ecryptfs_ctx *ctx, struct param_node *node,
			   struct val_node **head, void **foo)
{
	if (node->val && (*(node->val) == 'y')) {
		stack_push(head, "ecryptfs_passthrough");
	} else if (node->flags & PARAMETER_SET) {
		stack_push(head, "ecryptfs_passthrough");
		return 0;
	}
	free(node->val);
	return 0;
}

static int get_xattr(struct ecryptfs_ctx *ctx, struct param_node *node,
			   struct val_node **head, void **foo)
{
	if (node->val && (*(node->val) == 'y')) {
		stack_push(head, "ecryptfs_xattr_metadata");
	} else if (node->flags & PARAMETER_SET) {
		stack_push(head, "ecryptfs_xattr_metadata");
		return 0;
	}
	free(node->val);
	return 0;
}

static int get_encrypted_passthrough(struct ecryptfs_ctx *ctx,
				     struct param_node *node,
				     struct val_node **head, void **foo)
{
	if (node->val && (*(node->val) == 'y')) {
		stack_push(head, "ecryptfs_encrypted_view");
	} else if (node->flags & PARAMETER_SET) {
		stack_push(head, "ecryptfs_encrypted_view");
		return 0;
	}
	free(node->val);
	return 0;
}

static struct param_node end_param_node = {
	.num_mnt_opt_names = 1,
	.mnt_opt_names = {"cipher"},
	.prompt = "Select cipher",
	.val_type = VAL_STR,
	.val = NULL,
	.display_opts = NULL,
	.default_val = NULL,
	.flags = NO_VALUE,
	.num_transitions = 0,
	.tl = {{.val = "default",
		.pretty_val = "default",
		.next_token = NULL,
		.trans_func = get_cipher}}
};

static struct param_node encrypted_passthrough_param_node = {
	.num_mnt_opt_names = 1,
	.mnt_opt_names = {"encrypted_view"},
	.prompt = "Pass through encrypted versions of all files (y/n)",
	.val_type = VAL_STR,
	.val = NULL,
	.display_opts = NULL,
	.default_val = "n",
	.flags = ECHO_INPUT,
	.num_transitions = 1,
	.tl = {{.val = "default",
		.pretty_val = "default",
		.next_token = &end_param_node,
		.trans_func = get_encrypted_passthrough}}
};

static struct param_node xattr_param_node = {
	.num_mnt_opt_names = 1,
	.mnt_opt_names = {"xattr"},
	.prompt = "Write metadata to extended attribute region (y/n)",
	.val_type = VAL_STR,
	.val = NULL,
	.display_opts = NULL,
	.default_val = "n",
	.flags = ECHO_INPUT,
	.num_transitions = 1,
	.tl = {{.val = "default",
		.pretty_val = "default",
		.next_token = &end_param_node,
		.trans_func = get_xattr}}
};

static struct param_node passthrough_param_node = {
	.num_mnt_opt_names = 1,
	.mnt_opt_names = {"passthrough"},
	.prompt = "Enable plaintext passthrough (y/n)",
	.val_type = VAL_STR,
	.val = NULL,
	.display_opts = NULL,
	.default_val = NULL,
	.flags = ECHO_INPUT,
	.num_transitions = 1,
	.tl = {{.val = "default",
		.pretty_val = "default",
		.next_token = &end_param_node,
		.trans_func = get_passthrough}}
};

static struct param_node cipher_param_node = {
	.num_mnt_opt_names = 1,
	.mnt_opt_names = {"cipher"},
	.prompt = "Select cipher",
	.val_type = VAL_STR,
	.val = NULL,
	.display_opts = NULL,
	.default_val = NULL,
	.flags = NO_VALUE,
	.num_transitions = 1,
	.tl = {{.val = "default",
		.pretty_val = "default",
		.next_token = &end_param_node,
		.trans_func = get_cipher}}
};

static void
fill_in_decision_graph_based_on_version_support(struct param_node *root,
						uint32_t version)
{
	struct param_node *last_param_node = &cipher_param_node;

	ecryptfs_set_exit_param_on_graph(root, last_param_node);
	if (ecryptfs_supports_plaintext_passthrough(version)) {
		last_param_node->tl[0].next_token = &passthrough_param_node;
		last_param_node = &passthrough_param_node;
	}
	if (ecryptfs_supports_xattr(version)) {
		last_param_node->tl[0].next_token = &xattr_param_node;
		last_param_node = &xattr_param_node;
		last_param_node->tl[0].next_token =
			&encrypted_passthrough_param_node;
		last_param_node = &encrypted_passthrough_param_node;
	}
}

int ecryptfs_process_decision_graph(struct ecryptfs_ctx *ctx,
				    struct val_node **head, uint32_t version,

				    char *opts_str)
{
	struct ecryptfs_name_val_pair nvp_head;
	struct ecryptfs_name_val_pair rc_file_nvp_head;
	struct ecryptfs_pki_elem *pki_elem;
	int rc;

	/* Start with the key module type. Generate the options from
	 * the detected modules that are available */
	rc = ecryptfs_get_pki_list(ctx);
	if (rc) {
		syslog(LOG_ERR, "Error attempting to get key module list; "
		       "rc = [%d]\n", rc);
		goto out;
	}
	pki_elem = ctx->pki_list_head.next;
	while (pki_elem) {
		struct transition_node *trans_node;

		rc = pki_elem->ops.get_param_subgraph_trans_node(&trans_node,
								 version);
		if (rc) {
			pki_elem = pki_elem->next;
			continue;
		}
		rc = add_transition_node_to_param_node(&key_module_select_node,
						       trans_node);
		if (rc) {
			syslog(LOG_ERR, "Error attempting to add transition "
			       "node to param node; rc = [%d]\n", rc);
			goto out;
		}
		pki_elem = pki_elem->next;
	}
	fill_in_decision_graph_based_on_version_support(&key_module_select_node,
							version);
	memset(&nvp_head, 0, sizeof(struct ecryptfs_name_val_pair));
	memset(&rc_file_nvp_head, 0, sizeof(struct ecryptfs_name_val_pair));
	ecryptfs_parse_rc_file(&rc_file_nvp_head);
	rc = ecryptfs_parse_options(opts_str, &nvp_head);
	ecryptfs_nvp_list_union(&rc_file_nvp_head, &nvp_head);
	decision_graph_mount(ctx, head, &root_param_node, &rc_file_nvp_head);
out:
	return rc;
}
