/**
 * Copyright (C) 2006 International Business Machines
 * Author(s): Michael C. Thompson <mcthomps@us.ibm.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 <gcrypt.h>
#include <keyutils.h>
#ifndef S_SPLINT_S
#include <stdio.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include "../include/ecryptfs.h"

#ifndef ENOKEY
#warning ENOKEY is not defined in your errno.h; setting it to 126
#define ENOKEY 126
#endif

/**
 * This is the common functionality used to put a password generated key into
 * the keyring, shared by both non-interactive and interactive signature
 * generation code.
 *
 * Returns 0 on add, 1 on pre-existed, negative on failure.
 */
int add_passphrase_key_to_keyring(char *auth_tok_sig, char *passphrase,
				  char *salt)
{
	int rc;
	char session_key_encryption_key[ECRYPTFS_MAX_KEY_BYTES];
	struct ecryptfs_auth_tok *auth_tok = NULL;

	if ((rc = generate_passphrase_sig(auth_tok_sig, passphrase, salt,
					  session_key_encryption_key))) {
		rc = (rc < 0) ? rc : rc * -1;
		goto out;
	}
	rc = (int)keyctl_search(KEY_SPEC_USER_KEYRING, "user", auth_tok_sig, 0);
	if (rc != -1) { /* we already have this key in keyring; we're done */
		rc = 1;
		goto out;
	} else if ((rc == -1) && (errno != ENOKEY)) {
		int errnum = errno;

		fprintf(stderr, "keyctl_search failed: %s errno=[%d]\n",
			strerror(errnum), errnum);
		rc = (errnum < 0) ? errnum : errnum * -1;
		goto out;
	}
	auth_tok = malloc(sizeof(struct ecryptfs_auth_tok));
	if (!auth_tok) {
		fprintf(stderr, "Unable to allocate memory for auth_tok\n");
		rc = -ENOMEM;
		goto out;
	}
	if ((rc = generate_payload(auth_tok, auth_tok_sig, salt,
				   session_key_encryption_key))) {
		fprintf(stderr, "Error generating payload for auth tok key; "
				"rc = [%d]\n", rc);
		rc = (rc < 0) ? rc : rc * -1;
		goto out_wipe;
	}
	rc = add_key("user", auth_tok_sig, (void *)auth_tok,
		     sizeof(struct ecryptfs_auth_tok), KEY_SPEC_USER_KEYRING);
	if (rc == -1) {
		int errnum = errno;

		fprintf(stderr, "Error adding key with sig [%s]; rc = [%d] "
			"\%s\"\n", auth_tok_sig, rc, strerror(errnum));
		rc = (errnum < 0) ? errnum : errnum * -1;
		goto out_wipe;
	}
	rc = 0;
out_wipe:
	memset(auth_tok, 0, sizeof(auth_tok));
out:
	free(auth_tok);
	return rc;
}

/**
 * This is the common functionality used to put a public key derived key into
 * the keyring, shared by both non-interactive and interactive signature
 * generation code, as well as the ecrypts manager tool.
 *
 * Returns 0 on add, 1 on pre-existed, negative on failure.
 */
int add_public_key_key_to_keyring(char *auth_tok_sig,
				  struct ecryptfs_pki_elem *selected_pki)
{
	int pki_data_len, rc;
	struct ecryptfs_auth_tok *auth_tok;

	pki_data_len = (selected_pki->ops.get_pki_data_length)
			(&selected_pki->nvp_head);
	auth_tok = malloc(sizeof(struct ecryptfs_auth_tok) + pki_data_len);
	if (!auth_tok) {
		rc = -ENOMEM;
		goto out;
	}
	rc = generate_private_payload(auth_tok, selected_pki,
				      auth_tok_sig, pki_data_len);
	if (rc) {
		fprintf(stderr, "Error initializing public key\n");
		goto out;
	}
	rc = (int)keyctl_search(KEY_SPEC_USER_KEYRING, "user", auth_tok_sig, 0);
	if (rc != -1) { /* we already have this key in keyring; we're done */
		rc = 1;
		goto out;
	}
	rc = add_key("user", auth_tok_sig, (void *)auth_tok,
		     sizeof(struct ecryptfs_auth_tok) + pki_data_len,
		     KEY_SPEC_USER_KEYRING);
	if (rc < 0)
		fprintf(stderr, "Error adding key with sig [%s]; rc ="
				" [%d]\n", auth_tok_sig, rc);
	else rc = 0;
out:
	memset(auth_tok, 0, sizeof(struct ecryptfs_auth_tok) + pki_data_len);
	free(auth_tok);
	return rc;
}
