/*
 * ipsec_alg to linux cryptoapi GLUE
 *
 * Authors: CODE.ar TEAM
 *      Harpo MAxx <harpo@linuxmendoza.org.ar>
 *      JuanJo Ciarlante <jjo-ipsec@mendoza.gov.ar>
 *      Luciano Ruete <docemeses@softhome.net>
 *
 * ipsec_alg_cryptoapi.c,v 1.1.2.1 2003/11/21 18:12:23 jjo Exp
 *
 * 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.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * 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.
 *
 * Example usage:
 *   modinfo -p ipsec_cryptoapi   (quite useful info, including supported algos)
 *   modprobe ipsec_cryptoapi
 *   modprobe ipsec_cryptoapi test=1
 *   modprobe ipsec_cryptoapi excl=1                     (exclusive cipher/algo)
 *   modprobe ipsec_cryptoapi noauto=1  aes=1 twofish=1  (only these ciphers)
 *   modprobe ipsec_cryptoapi aes=128,128                (force these keylens)
 *   modprobe ipsec_cryptoapi des_ede3=0                 (everything but 3DES)
 */
#ifndef AUTOCONF_INCLUDED
#include <linux/config.h>
#endif
#include <linux/version.h>

/*
 *	special case: ipsec core modular with this static algo inside:
 *	must avoid MODULE magic for this file
 */
#if CONFIG_IPSEC_MODULE && CONFIG_IPSEC_ALG_CRYPTOAPI
#undef MODULE
#endif

#include <linux/module.h>
#include <linux/init.h>

#include <linux/kernel.h>       /* printk() */
#include <linux/errno.h>        /* error codes */
#include <linux/types.h>        /* size_t */
#include <linux/string.h>

/* Check if __exit is defined, if not null it */
#ifndef __exit
#define __exit
#endif

/* warn the innocent */
#if !defined (CONFIG_CRYPTO) && !defined (CONFIG_CRYPTO_MODULE)
#warning "No linux CryptoAPI found, install 2.4.22+ or 2.6.x"
#define NO_CRYPTOAPI_SUPPORT
#endif
/*	Low freeswan header coupling	*/
#include "libreswan/ipsec_alg.h"

#include <linux/crypto.h>
#ifdef CRYPTO_API_VERSION_CODE
#warning \
	"Old CryptoAPI is not supported. Only linux-2.4.22+ or linux-2.6.x are supported"
#define NO_CRYPTOAPI_SUPPORT
#endif

#ifdef NO_CRYPTOAPI_SUPPORT
#warning "Building an unusable module :P"
/* Catch old CryptoAPI by not allowing module to load */
IPSEC_ALG_MODULE_INIT( ipsec_cryptoapi_init ){
	printk(KERN_WARNING "ipsec_cryptoapi.o was not built on stock Linux CryptoAPI (2.4.22+ or 2.6.x), not loading.\n");
	return -EINVAL;
}
#else
#include <asm/scatterlist.h>
#include <asm/pgtable.h>
#include <linux/mm.h>

#define CIPHERNAME_AES          "aes"
#define CIPHERNAME_3DES         "des3_ede"
#define CIPHERNAME_CAST         "cast5"
#define CIPHERNAME_SERPENT      "serpent"
#define CIPHERNAME_TWOFISH      "twofish"

#define ESP_3DES                3
#define ESP_AES                 12
#define ESP_CAST                6       /* quite constant :) */
#define ESP_SERPENT             252     /* from ipsec drafts */
#define ESP_TWOFISH             253     /* from ipsec drafts */

#define AH_MD5                  2
#define AH_SHA                  3
#define DIGESTNAME_MD5          "md5"
#define DIGESTNAME_SHA1         "sha1"

MODULE_AUTHOR("Juanjo Ciarlante, Harpo MAxx, Luciano Ruete");
static int debug = 0;
static int test = 0;
static int excl = 0;
#ifdef module_param
module_param(debug, int, 0664);
module_param(test, int, 0664);
module_param(excl, int, 0664);
#else
MODULE_PARM(debug, "i");
MODULE_PARM(test, "i");
MODULE_PARM(excl, "i");
#endif

static int noauto = 0;
#ifdef module_param
module_param(noauto, int, 0664);
#else
MODULE_PARM(noauto, "i");
#endif
MODULE_PARM_DESC(noauto, "Don't try all known algos, just setup enabled ones");

static int des_ede3[] = { -1, -1 };
static int aes[] = { -1, -1 };
static int cast[] = { -1, -1 };
static int serpent[] = { -1, -1 };
static int twofish[] = { -1, -1 };

#ifdef module_param_array
module_param_array(des_ede3, int, NULL, 0);
module_param_array(aes, int, NULL, 0);
module_param_array(cast, int, NULL, 0);
module_param_array(serpent, int, NULL, 0);
module_param_array(twofish, int, NULL, 0);
#else
MODULE_PARM(des_ede3, "1-2i");
MODULE_PARM(aes, "1-2i");
MODULE_PARM(cast, "1-2i");
MODULE_PARM(serpent, "1-2i");
MODULE_PARM(twofish, "1-2i");
#endif
MODULE_PARM_DESC(des_ede3, "0: disable | 1: force_enable | min,max: dontuse");
MODULE_PARM_DESC(aes, "0: disable | 1: force_enable | min,max: keybitlens");
MODULE_PARM_DESC(cast, "0: disable | 1: force_enable | min,max: keybitlens");
MODULE_PARM_DESC(serpent,
		 "0: disable | 1: force_enable | min,max: keybitlens");
MODULE_PARM_DESC(twofish,
		 "0: disable | 1: force_enable | min,max: keybitlens");

struct ipsec_alg_capi_cipher {
	const char *ciphername; /* cryptoapi's ciphername */
	unsigned blocksize;
	unsigned short minbits;
	unsigned short maxbits;
	int *parm;                      /* lkm param for this cipher */
	struct ipsec_alg_enc alg;       /* note it's not a pointer */
};
static struct ipsec_alg_capi_cipher alg_capi_carray[] = {
	{ CIPHERNAME_AES,     16, 128, 256, aes, { ixt_alg_id: ESP_AES, } },
	{ CIPHERNAME_TWOFISH, 16, 128, 256, twofish,
	  { ixt_alg_id: ESP_TWOFISH, } },
	{ CIPHERNAME_SERPENT, 16, 128, 256, serpent,
	  { ixt_alg_id: ESP_SERPENT, } },
	{ CIPHERNAME_CAST,     8, 128, 128, cast, { ixt_alg_id: ESP_CAST, } },
	{ CIPHERNAME_3DES,     8, 192, 192, des_ede3,
	  { ixt_alg_id: ESP_3DES, } },
	{ NULL, 0, 0, 0, NULL, {} }
};
#ifdef NOT_YET
struct ipsec_alg_capi_digest {
	const char *digestname;         /* cryptoapi's digestname */
	struct digest_implementation *di;
	struct ipsec_alg_auth alg;      /* note it's not a pointer */
};
static struct ipsec_alg_capi_cipher alg_capi_darray[] = {
	{ DIGESTNAME_MD5,     NULL, { ixt_alg_id: AH_MD5, } },
	{ DIGESTNAME_SHA1,    NULL, { ixt_alg_id: AH_SHA, } },
	{ NULL, NULL, {} }
};
#endif
/*
 *      "generic" linux cryptoapi setup_cipher() function
 */
int setup_cipher(const char *ciphername)
{
	return crypto_alg_available(ciphername, 0);
}

/*
 *      setups ipsec_alg_capi_cipher "hyper" struct components, calling
 *      register_ipsec_alg for cointaned ipsec_alg object
 */
static void _capi_destroy_key(struct ipsec_alg_enc *alg, __u8 *key_e);
static __u8 * _capi_new_key(struct ipsec_alg_enc *alg, const __u8 *key,
			    size_t keylen);
static int _capi_cbc_encrypt(struct ipsec_alg_enc *alg, __u8 * key_e,
			     __u8 * in, int ilen, const __u8 * iv,
			     int encrypt);

static int setup_ipsec_alg_capi_cipher(struct ipsec_alg_capi_cipher *cptr)
{
	int ret;

	cptr->alg.ixt_version = IPSEC_ALG_VERSION;
	cptr->alg.ixt_module = THIS_MODULE;
	atomic_set(&cptr->alg.ixt_refcnt, 0);
	fill_and_terminate(cptr->alg.ixt_name, cptr->ciphername,
		sizeof(cptr->alg.ixt_name));

	cptr->alg.ixt_blocksize = cptr->blocksize;
	cptr->alg.ixt_keyminbits = cptr->minbits;
	cptr->alg.ixt_keymaxbits = cptr->maxbits;
	cptr->alg.ixt_state = 0;
	if (excl)
		cptr->alg.ixt_state |= IPSEC_ALG_ST_EXCL;
	cptr->alg.ixt_e_keylen = cptr->alg.ixt_keymaxbits / 8;
	cptr->alg.ixt_e_ctx_size = 0;
	cptr->alg.ixt_alg_type = IPSEC_ALG_TYPE_ENCRYPT;
	cptr->alg.ixt_e_new_key = _capi_new_key;
	cptr->alg.ixt_e_destroy_key = _capi_destroy_key;
	cptr->alg.ixt_e_cbc_encrypt = _capi_cbc_encrypt;
	cptr->alg.ixt_data = cptr;

	ret = register_ipsec_alg_enc(&cptr->alg);
	printk("setup_ipsec_alg_capi_cipher(): "
	       "alg_type=%d alg_id=%d name=%s "
	       "keyminbits=%d keymaxbits=%d, ret=%d\n",
	       cptr->alg.ixt_alg_type,
	       cptr->alg.ixt_alg_id,
	       cptr->alg.ixt_name,
	       cptr->alg.ixt_keyminbits,
	       cptr->alg.ixt_keymaxbits,
	       ret);
	return ret;
}
/*
 *      called in ipsec_sa_wipe() time, will destroy key contexts
 *      and do 1 unbind()
 */
static void _capi_destroy_key(struct ipsec_alg_enc *alg, __u8 *key_e)
{
	struct crypto_tfm *tfm = (struct crypto_tfm*)key_e;

	if (debug > 0)
		printk(KERN_DEBUG "klips_debug: _capi_destroy_key:"
		       "name=%s key_e=%p\n",
		       alg->ixt_name, key_e);
	if (!key_e) {
		printk(KERN_ERR "klips_debug: _capi_destroy_key:"
		       "name=%s NULL key_e!\n",
		       alg->ixt_name);
		return;
	}
	crypto_free_tfm(tfm);
}

/*
 *      create new key context, need alg->ixt_data to know which
 *      (of many) cipher inside this module is the target
 */
static __u8 *_capi_new_key(struct ipsec_alg_enc *alg, const __u8 *key,
			   size_t keylen)
{
	struct ipsec_alg_capi_cipher *cptr;
	struct crypto_tfm *tfm = NULL;

	cptr = alg->ixt_data;
	if (!cptr) {
		printk(KERN_ERR "_capi_new_key(): "
		       "NULL ixt_data (?!) for \"%s\" algo\n",
		       alg->ixt_name);
		goto err;
	}
	if (debug > 0)
		printk(KERN_DEBUG "klips_debug:_capi_new_key:"
		       "name=%s cptr=%p key=%p keysize=%d\n",
		       alg->ixt_name, cptr, key, keylen);

	/*
	 *	alloc tfm
	 */
	tfm = crypto_alloc_tfm(cptr->ciphername, CRYPTO_TFM_MODE_CBC);
	if (!tfm) {
		printk(KERN_ERR "_capi_new_key(): "
		       "NULL tfm for \"%s\" cryptoapi (\"%s\") algo\n",
		       alg->ixt_name, cptr->ciphername);
		goto err;
	}
	if (crypto_cipher_setkey(tfm, key, keylen) < 0) {
		printk(KERN_ERR "_capi_new_key(): "
		       "failed new_key() for \"%s\" cryptoapi algo (keylen=%d)\n",
		       alg->ixt_name, keylen);
		crypto_free_tfm(tfm);
		tfm = NULL;
	}
err:
	if (debug > 0)
		printk(KERN_DEBUG "klips_debug:_capi_new_key:"
		       "name=%s key=%p keylen=%d tfm=%p\n",
		       alg->ixt_name, key, keylen, tfm);
	return (__u8 *) tfm;
}
/*
 *      core encryption function: will use cx->ci to call actual cipher's
 *      cbc function
 */
static int _capi_cbc_encrypt(struct ipsec_alg_enc *alg, __u8 * key_e,
			     __u8 * in, int ilen, const __u8 * iv, int encrypt)
{
	int error = 0;
	struct crypto_tfm *tfm = (struct crypto_tfm *)key_e;
	struct scatterlist sg = {
		.page = virt_to_page(in),
		.offset = (unsigned long)(in) % PAGE_SIZE,
		.length = ilen,
	};

	if (debug > 1)
		printk(KERN_DEBUG "klips_debug:_capi_cbc_encrypt:"
		       "key_e=%p "
		       "in=%p out=%p ilen=%d iv=%p encrypt=%d\n",
		       key_e,
		       in, in, ilen, iv, encrypt);
	crypto_cipher_set_iv(tfm, iv, crypto_tfm_alg_ivsize(tfm));
	if (encrypt)
		error = crypto_cipher_encrypt(tfm, &sg, &sg, ilen);
	else
		error = crypto_cipher_decrypt(tfm, &sg, &sg, ilen);
	if (debug > 1)
		printk(KERN_DEBUG "klips_debug:_capi_cbc_encrypt:"
		       "error=%d\n",
		       error);
	return (error < 0) ? error : ilen;
}
/*
 *      main initialization loop: for each cipher in list, do
 *      1) setup cryptoapi cipher else continue
 *      2) register ipsec_alg object
 */
static int setup_cipher_list(struct ipsec_alg_capi_cipher *clist)
{
	struct ipsec_alg_capi_cipher *cptr;

	/* foreach cipher in list ... */
	for (cptr = clist; cptr->ciphername; cptr++) {
		/*
		 * see if cipher has been disabled (0) or
		 * if noauto set and not enabled (1)
		 */
		if (cptr->parm[0] == 0 || (noauto && cptr->parm[0] < 0)) {
			if (debug > 0)
				printk(KERN_INFO "setup_cipher_list(): "
				       "ciphername=%s skipped at user request: "
				       "noauto=%d parm[0]=%d parm[1]=%d\n",
				       cptr->ciphername,
				       noauto,
				       cptr->parm[0],
				       cptr->parm[1]);
			continue;
		}
		/*
		 *      use a local ci to avoid touching cptr->ci,
		 *      if register ipsec_alg success then bind cipher
		 */
		if ( setup_cipher(cptr->ciphername) ) {
			if (debug > 0)
				printk(KERN_DEBUG "klips_debug:"
				       "setup_cipher_list():"
				       "ciphername=%s found\n",
				       cptr->ciphername);
			if (setup_ipsec_alg_capi_cipher(cptr) == 0) {

			} else {
				printk(KERN_ERR "klips_debug:"
				       "setup_cipher_list():"
				       "ciphername=%s failed ipsec_alg_register\n",
				       cptr->ciphername);
			}
		} else {
			if (debug > 0)
				printk(KERN_INFO "setup_cipher_list(): lookup for ciphername=%s: not found\n",
					cptr->ciphername);
		}
	}
	return 0;
}
/*
 *      deregister ipsec_alg objects and unbind ciphers
 */
static int unsetup_cipher_list(struct ipsec_alg_capi_cipher *clist)
{
	struct ipsec_alg_capi_cipher *cptr;

	/* foreach cipher in list ... */
	for (cptr = clist; cptr->ciphername; cptr++) {
		if (cptr->alg.ixt_state & IPSEC_ALG_ST_REGISTERED)
			unregister_ipsec_alg_enc(&cptr->alg);
	}
	return 0;
}
/*
 *      test loop for registered algos
 */
static int test_cipher_list(struct ipsec_alg_capi_cipher *clist)
{
	int test_ret;
	struct ipsec_alg_capi_cipher *cptr;

	/* foreach cipher in list ... */
	for (cptr = clist; cptr->ciphername; cptr++) {
		if (cptr->alg.ixt_state & IPSEC_ALG_ST_REGISTERED) {
			test_ret = ipsec_alg_test(
				cptr->alg.ixt_alg_type,
				cptr->alg.ixt_alg_id,
				test);
			printk("test_cipher_list(alg_type=%d alg_id=%d): test_ret=%d\n",
				cptr->alg.ixt_alg_type,
				cptr->alg.ixt_alg_id,
				test_ret);
		}
	}
	return 0;
}

IPSEC_ALG_MODULE_INIT( ipsec_cryptoapi_init ){
	int ret, test_ret;

	if ((ret = setup_cipher_list(alg_capi_carray)) < 0)
		return -EPROTONOSUPPORT;

	if (ret == 0 && test)
		test_ret = test_cipher_list(alg_capi_carray);
	return ret;
}
IPSEC_ALG_MODULE_EXIT( ipsec_cryptoapi_fini ){
	unsetup_cipher_list(alg_capi_carray);
}
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif

EXPORT_NO_SYMBOLS;
#endif /* NO_CRYPTOAPI_SUPPORT */
