/* $USAGI: pfkey_v2_msg.c,v 1.29 2002/10/04 11:31:28 mk Exp $ */
/*
 * Copyright (C)2001 USAGI/WIDE Project
 * 
 * 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
 *
 * Authors:
 *   Kazunori MIYAZAWA <miyazawa@linux-ipv6.org> / USAGI
 *   Mitsuru KANDA <mk@linux-ipv6.org> / USAGI
 */
/*
 * pfkey_v2_msg.c is a program for PF_KEY version2 parsing routine, and utilities.
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/random.h>
#include <linux/net.h>
#include <linux/crypto.h>
#include <linux/ipsec.h>

#include <net/sock.h>
#include <linux/pfkeyv2.h>
#include <linux/pfkey.h>
#include <net/sadb.h>
#include <net/spd.h>
#ifdef CONFIG_IPCOMP
#include <net/ipcomp.h>
#endif

#include "pfkey_v2_msg.h"
#include "sockaddr_utils.h"

#define BUF_SIZE 64

int sadb_msg_sanity_check(struct sadb_msg* msg)
{
	int error = 0;

	if (!msg) {
		PFKEY_DEBUG("msg==NULL\n");
		error = -EINVAL;
		goto err;
	}

	if (!(msg->sadb_msg_version == PF_KEY_V2 &&
	      msg->sadb_msg_type > 0 && msg->sadb_msg_type <= SADB_MAX &&
	      /* msg->sadb_msg_satype >= 0 && */ msg->sadb_msg_satype <= SADB_SATYPE_MAX &&
	      msg->sadb_msg_reserved == 0))
		error = -EINVAL;

err:
#ifdef CONFIG_IPSEC_DEBUG
	if (error < 0)
		PFKEY_DEBUG("message failed sanity check\n");
#endif

	return error;
}

int sadb_address_to_sockaddr(const struct sadb_address *ext_msg, struct sockaddr* addr)
{
	int error = 0, addrlen = 0;
	struct sockaddr *tmp_addr;

	if (!ext_msg || !addr) {
		PFKEY_DEBUG("msg or addr is NULL\n");
		error = -EINVAL;
		goto err;
	}

	tmp_addr = (struct sockaddr *) (ext_msg + 1);
	addrlen = ext_msg->sadb_address_len - sizeof(struct sadb_address);

	switch (tmp_addr->sa_family) {
	case AF_INET:
		if (addrlen < sizeof(struct sockaddr_in)) {
			PFKEY_DEBUG("sadb_address_len=%d; too small for IPv4\n", addrlen);
			error = -EINVAL;
			goto err;
		}
		memcpy(addr, tmp_addr, sizeof(struct sockaddr_in)); 
		break;

	case AF_INET6:
		if (addrlen < sizeof(struct sockaddr_in6)) {
			PFKEY_DEBUG("sadb_address_len=%d; too small for IPv6\n", addrlen);
			error = -EINVAL;
			goto err;
		}
		memcpy(addr, tmp_addr, sizeof(struct sockaddr_in6)); 
		break;

	default:
		PFKEY_DEBUG("unknwon address family %d\n", tmp_addr->sa_family);
		error = -EINVAL;
		goto err;
	}

err:
#ifdef CONFIG_IPSEC_DEBUG
	if (error)
		PFKEY_DEBUG("error=%d\n", error);
#endif
	return error;
}

int sadb_identify_esp_algo(__u8 id, char **namep, unsigned int *keybitsp)
{
	unsigned int key_bits;
	char *name = NULL;

	switch (id) {
	case SADB_EALG_NULL:
		name = "null-ecb";
		key_bits = 0;
		break; 
	case SADB_EALG_DESCBC:
		name = "des-cbc";
		key_bits = ESP_DES_KEY_BITS;
		break;
	case SADB_EALG_3DESCBC:
		name = "3des-cbc";
		key_bits = ESP_3DES_KEY_BITS;
		break;
	case SADB_EALG_AES:
		name = "aes-cbc";
		key_bits = ESP_AES_KEY_BITS;
		break;
	case SADB_EALG_NONE:
	default:
		return -EINVAL;
	}

	if (namep)
		*namep = name;
	if (keybitsp)
		*keybitsp = key_bits;
	return 0;
}

int sadb_key_to_esp(const __u8 esp_algo, const struct sadb_key* ext_msg, struct ipsec_sa* sa_entry)
{
	int error = 0;
	char *algoname = NULL;
	unsigned int key_bits;
	struct cipher_implementation *ci = NULL;

	if (!sa_entry) {
		PFKEY_DEBUG("sa_entry is NULL\n");
		error = -EINVAL;
		goto err;
	}

	memset(&sa_entry->esp_algo, 0, sizeof(sa_entry->esp_algo));

	error = sadb_identify_esp_algo(esp_algo, &algoname, &key_bits);
	if (error < 0)
		goto err;

	PFKEY_DEBUG("ESP algorithm is %s (%u bits)\n", algoname, key_bits);

	/* Do not check key bits for variable key lenth ciphers */
	if (key_bits) {
		if (!ext_msg) {
			PFKEY_DEBUG("ext_msg is NULL\n");
			error = -EINVAL;
			goto err;
		}
		if (ext_msg->sadb_key_bits != key_bits) {
			PFKEY_DEBUG("%s: invalid key length: expected %u, got %u\n",
					algoname, key_bits, ext_msg->sadb_key_bits);
			error = -EINVAL;
			goto err;
		}
	} else if (ext_msg) {
		key_bits = ext_msg->sadb_key_bits;
	}

	ci = find_cipher_by_name(algoname, 1);
	if (!ci) {
		PFKEY_DEBUG("algorithm %u:%s is not supported\n", esp_algo, algoname);
		error = -EINVAL;
		goto err;
	}
	ci->lock();

	if (ci->ivsize > ESP_MAX_IV_LEN) {
		printk("ipsec: cipher IV size %u too big\n", ci->ivsize);
		error = -EINVAL;
		goto err;
	}

	sa_entry->esp_algo.algo = esp_algo;
	sa_entry->esp_algo.key_len = key_bits / OCTETBITS;
	sa_entry->esp_algo.cx = ci->realloc_context (NULL, ci, sa_entry->esp_algo.key_len);
	if (!sa_entry->esp_algo.cx) {
		error = -EINVAL;
		goto err;
	}
	sa_entry->esp_algo.cx->ci = ci;

	if (sa_entry->esp_algo.key_len) {
		unsigned int key_len = sa_entry->esp_algo.key_len;
		unsigned char *key;

		if (key_len > ext_msg->sadb_key_len - sizeof(*ext_msg)) {
			PFKEY_DEBUG("provided key too short\n");
			error = -EINVAL;
			goto err;
		}

		sa_entry->esp_algo.key = key = kmalloc(key_len, GFP_ATOMIC);
		if (!key) {
			PFKEY_DEBUG("could not allocate memory for key\n");
			error = -ENOMEM;
			goto err;
		}
		memset(key, 0, key_len);
		memcpy(key, (char *) (ext_msg + 1), key_len);

		memset(sa_entry->esp_algo.cx->keyinfo, 0, ci->key_schedule_size);

		error = ci->set_key(sa_entry->esp_algo.cx,
				(char *) (ext_msg + 1), key_len);
		if (error < 0) {
			printk(KERN_DEBUG "set_key failed, weak key?\n");
			goto err;
		}

		get_random_bytes(sa_entry->esp_algo.iv, ci->ivsize);
	}
err:	if (ci && error < 0) {
		if (sa_entry && sa_entry->esp_algo.cx) {
			ci->wipe_context(sa_entry->esp_algo.cx);
			ci->free_context(sa_entry->esp_algo.cx);
		}
		if (sa_entry->esp_algo.key)
			kfree(sa_entry->esp_algo.key);
		ci->unlock();
	}
	return error;

}

int sadb_key_to_auth(const __u8 auth_algo, const struct sadb_key* ext_msg, struct ipsec_sa* sa_entry)
{
	int error = 0;
	char *algoname = NULL;
	__u16 digest_len = 0;
	struct digest_implementation *di = NULL;

	if (!(ext_msg&&sa_entry)) {
		PFKEY_DEBUG("msg or sa_entry is NULL\n");
		error = -EINVAL;
		goto err;
	}

	switch (auth_algo) {

	case SADB_AALG_MD5HMAC:
		algoname = "md5";
		digest_len = 12; /* 96 bit length */
		PFKEY_DEBUG("auth algorithm is HMAC-MD5\n");

		if(ext_msg->sadb_key_bits != AUTH_MD5HMAC_KEY_BITS){
			PFKEY_DEBUG("the key length is %d\n", ext_msg->sadb_key_bits);
			PFKEY_DEBUG("the key length is not match\n");
			error = -EINVAL;
			goto err;
		} 
		break;

	case SADB_AALG_SHA1HMAC:
		algoname = "sha1";
		digest_len = 12; /* 96 bit length */
		PFKEY_DEBUG("auth algorithm is HMAC-SHA1\n");

		if (ext_msg->sadb_key_bits != AUTH_SHA1HMAC_KEY_BITS) {
			PFKEY_DEBUG("the key length is %d\n", ext_msg->sadb_key_bits);
			PFKEY_DEBUG("the key length is not match\n");
			error = -EINVAL;
			goto err;
		} 
		break;

	case SADB_AALG_NONE:
	default:
		PFKEY_DEBUG("auth_algo is NONE or not supported one\n");
		error = -EINVAL;
		goto err;
	}

	di = find_digest_by_name(algoname, 0);
	if (!di) {
		PFKEY_DEBUG("algorithm %u:%s is not supported\n", auth_algo, algoname);
		error = -EINVAL;
		goto err;
	}
	di->lock();

	sa_entry->auth_algo.algo = auth_algo;
	sa_entry->auth_algo.key_len = (ext_msg->sadb_key_bits)/OCTETBITS;
	sa_entry->auth_algo.digest_len = digest_len;
	sa_entry->auth_algo.dx = di->realloc_context(NULL, di);
	if (!sa_entry->auth_algo.dx) {
		di->unlock();
		error = -EINVAL;
		goto err;
	}
	sa_entry->auth_algo.dx->di = di;

	sa_entry->auth_algo.key = kmalloc(sa_entry->auth_algo.key_len, GFP_ATOMIC);
	if (!sa_entry->auth_algo.key) {
		PFKEY_DEBUG("cannot allocate key\n");
		di->free_context(sa_entry->auth_algo.dx);
		di->unlock();
		error = -ENOMEM;
		goto err;
	}

	memcpy(sa_entry->auth_algo.key, (char *)ext_msg+sizeof(struct sadb_key),
			sa_entry->auth_algo.key_len);
err:
	return error;
}


int sadb_lifetime_to_lifetime(const struct sadb_lifetime* ext_msg, struct sa_lifetime* lifetime)
{
	int error = 0;

	if (!ext_msg || !lifetime) {
		PFKEY_DEBUG("param is NULL\n");
		error = -EINVAL;
		goto err;
	}

	lifetime->allocations 	= ext_msg->sadb_lifetime_allocations;
	lifetime->bytes 	= ext_msg->sadb_lifetime_bytes;
	lifetime->addtime	= ext_msg->sadb_lifetime_addtime;
	lifetime->usetime	= ext_msg->sadb_lifetime_usetime;

err:
	return error;
}

int lifetime_to_sadb_lifetime(struct sa_lifetime *lifetime, struct sadb_ext **ext_msg, int type)
{
	int error = 0;
	if (!lifetime || !ext_msg) {
		PFKEY_DEBUG("param is NULL\n");
		error = -EINVAL;
		goto err;
	}

	error = pfkey_lifetime_build(ext_msg, type,
					lifetime->allocations,
					lifetime->bytes,
					lifetime->addtime,
					lifetime->usetime);
err:
	return error;
}

int sadb_sa_to_ipsec_sa(int satype, struct sadb_sa *sa,
			struct sadb_address *src, struct sadb_address *dst,
			struct ipsec_sa **result)
{
	struct sockaddr_storage saddr, daddr;
	size_t slen, dlen;
	int error = 0;

	if (!sa)
		return -EINVAL;

	if (src) {
		error = sadb_address_to_sockaddr(src, (struct sockaddr *) &saddr);
		if (error < 0) {
			PFKEY_DEBUG("src address translation failed\n");
			goto out;
		}
		slen = src->sadb_address_prefixlen;
	} else {
		memset(&saddr, 0, sizeof(saddr));
		saddr.ss_family = AF_UNSPEC;
		slen = 0;
	}

	if (dst) {
		error = sadb_address_to_sockaddr(dst, (struct sockaddr *) &daddr);
		if (error < 0) {
			PFKEY_DEBUG("dst address translation failed\n");
			goto out;
		}
		dlen = dst->sadb_address_prefixlen;
	} else {
		memset(&daddr, 0, sizeof(daddr));
		daddr.ss_family = AF_UNSPEC;
		dlen = 0;
	}

	error = sadb_find_by_address_proto_spi(
		(struct sockaddr *) &saddr, slen,
		(struct sockaddr *) &daddr, dlen,
		satype, sa->sadb_sa_spi, result);

	if (error == -EEXIST)
		error = 0;
	else
		PFKEY_DEBUG("failed to find SA\n");

out:
	return error;
}

int sadb_msg_detect_ext(struct sadb_msg* msg, struct sadb_ext **ext_msgs)
{
	unsigned int len = 0, msg_size = 0, ext_type;
	int error = 0;
	struct sadb_ext *ext_ptr = NULL;

	if (!msg || !ext_msgs) {
		PFKEY_DEBUG("msg or ext_msgs is NULL.\n");
		error = -EINVAL;
		goto err;
	}

	error = sadb_msg_sanity_check(msg);
	if (error) {
		error = -EINVAL;
		goto err;
	}

	ext_ptr = (struct sadb_ext*)((u8*)msg + sizeof(struct sadb_msg));
	len = (msg->sadb_msg_len * IPSEC_PFKEYv2_ALIGN) - sizeof(struct sadb_msg);

	while (len >= sizeof(*ext_ptr)) { 
		/*
		PFKEY_DEBUG("ext type %d\n", ext_ptr->sadb_ext_type);
		PFKEY_DEBUG("ext length %d\n", ext_ptr->sadb_ext_len);
		 */
		msg_size = (ext_ptr->sadb_ext_len) * IPSEC_PFKEYv2_ALIGN;
		ext_type = ext_ptr->sadb_ext_type;

		if (len < msg_size)
			break;	/* error will be set later */

		/* Unknown extensions must be ignored silently */
		if (ext_type < SADB_EXT_MAX) {
			/* there's no extension 0 */
			if (ext_type == SADB_RESERVED)
				break;
			/* No duplicate extensions allowed */
			if (ext_msgs[ext_type] != NULL)
				break;
			ext_msgs[ext_type] = ext_ptr;
		}
		ext_ptr = (struct sadb_ext*)((u8*)ext_ptr + msg_size); 
		len -= msg_size;
	}

	if (len)
		error = -EINVAL;

err:
#ifdef CONFIG_IPSEC_DEBUG
	pfkeyv2_print_extensions(ext_msgs);
	if (error)
		PFKEY_DEBUG("error=%d when parsing extensions\n", -error);
#endif
	return error;
}		

int sadb_msg_acquire_parse(struct sock *sk, struct sadb_msg *msg, struct sadb_msg **reply)
{
	int error = 0;
	struct sadb_ext *ext_msgs[SADB_EXT_MAX+1];

	if (!msg) {
		PFKEY_DEBUG("msg==NULL\n");
		error = -EINVAL;
		goto err;
	}

	memset(ext_msgs, 0, sizeof(ext_msgs));
	error = sadb_msg_detect_ext(msg, ext_msgs);

err:
	return error;
}

int sadb_msg_register_parse(struct sock *sk, struct sadb_msg *msg, struct sadb_msg **reply)
{
	int error = 0;
	uint  alg_num = 0;
	struct sadb_alg algs[5];
	struct digest_implementation *di = NULL;
	struct cipher_implementation *ci = NULL;
	struct sadb_ext *reply_ext_msgs[SADB_EXT_MAX+1];
	int satype;

	if (!sk || !msg) {
		PFKEY_DEBUG("msg or sk is NULL\n");
		error = -EINVAL;
		PFKEY_DEBUG("msg or *reply is NULL\n");
		goto err; 	
	}

	error = sadb_msg_sanity_check(msg);
	if (error)
		goto err;

	memset(reply_ext_msgs, 0, sizeof(reply_ext_msgs));
	memset(algs, 0, sizeof(algs));

	satype = msg->sadb_msg_satype;
	if (satype <= 0 || satype > SADB_SATYPE_MAX) {
		error = -EINVAL;
		goto err;
	}

	write_lock_bh(&pfkey_sk_lock);
	/*
	 register a socket with the proper SA type's socket list
	 it will be release in pfkey_release process (file:pfkey_v2.c).
	*/
	error = pfkey_list_insert_socket(sk->socket, &pfkey_registered_sockets[msg->sadb_msg_satype]);
	write_unlock_bh(&pfkey_sk_lock);
	if (error)
		goto err;

	PFKEY_DEBUG("socket=%p registered, SA type is %d\n", sk->socket, msg->sadb_msg_satype);

	reply_ext_msgs[0] = (struct sadb_ext*)msg;

	if (msg->sadb_msg_satype == SADB_SATYPE_AH || msg->sadb_msg_satype == SADB_SATYPE_ESP) {
		di = find_digest_by_name("md5", 0 /* atomic */);
		if (di) {
			algs[alg_num].sadb_alg_id = SADB_AALG_MD5HMAC;
			algs[alg_num].sadb_alg_ivlen = 0;
			algs[alg_num].sadb_alg_minbits = AUTH_MD5HMAC_KEY_BITS;
			algs[alg_num].sadb_alg_maxbits = AUTH_MD5HMAC_KEY_BITS;
			algs[alg_num].sadb_alg_reserved = 0;
			alg_num++;
			di = NULL;	
		}
	
		di = find_digest_by_name("sha1", 0 /* atomic */);
		if (di) {
			algs[alg_num].sadb_alg_id = SADB_AALG_SHA1HMAC;
			algs[alg_num].sadb_alg_ivlen = 0;
			algs[alg_num].sadb_alg_minbits = AUTH_SHA1HMAC_KEY_BITS;
			algs[alg_num].sadb_alg_maxbits = AUTH_SHA1HMAC_KEY_BITS;
			algs[alg_num].sadb_alg_reserved = 0;
			alg_num++;
			di = NULL;
		}

		if (msg->sadb_msg_satype == SADB_SATYPE_AH) {
			error = pfkey_supported_build(&reply_ext_msgs[SADB_EXT_SUPPORTED_AUTH],
						SADB_EXT_SUPPORTED_AUTH,
						alg_num,
						algs);
			if (error) goto free_ext_finish;
		}

	}

	if (msg->sadb_msg_satype == SADB_SATYPE_ESP) {
		ci = find_cipher_by_name("des-cbc", 1 /* atomic */);
		if (ci) {
			ci->lock();
			algs[alg_num].sadb_alg_id = SADB_EALG_DESCBC;
			algs[alg_num].sadb_alg_ivlen = ci->ivsize;
			algs[alg_num].sadb_alg_minbits = ESP_DES_KEY_BITS;
			algs[alg_num].sadb_alg_maxbits = ESP_DES_KEY_BITS;
			algs[alg_num].sadb_alg_reserved = 0;
			alg_num++;
			ci->unlock();
			ci = NULL;
		}

		ci = find_cipher_by_name("3des-cbc", 1 /* atomic */);
		if (ci) {
			ci->lock();
			algs[alg_num].sadb_alg_id = SADB_EALG_3DESCBC;
			algs[alg_num].sadb_alg_ivlen = ci->ivsize;
			algs[alg_num].sadb_alg_minbits = ESP_3DES_KEY_BITS;
			algs[alg_num].sadb_alg_maxbits = ESP_3DES_KEY_BITS;
			algs[alg_num].sadb_alg_reserved = 0;
			alg_num++;
			ci->unlock();
			ci = NULL;
		}
		ci = find_cipher_by_name("aes-cbc", 1 /* atomic */);
		if (ci) {
			ci->lock();
			algs[alg_num].sadb_alg_id = SADB_EALG_AES;
			algs[alg_num].sadb_alg_ivlen = ci->ivsize;
			algs[alg_num].sadb_alg_minbits = ESP_AES_KEY_BITS;
			algs[alg_num].sadb_alg_maxbits = ESP_AES_KEY_BITS;
			algs[alg_num].sadb_alg_reserved = 0;
			alg_num++;
			ci->unlock();
			ci = NULL;
		}

		error = pfkey_supported_build(&reply_ext_msgs[SADB_EXT_SUPPORTED_ENCRYPT],
						SADB_EXT_SUPPORTED_ENCRYPT,
						alg_num,
						algs);
		if (error) goto free_ext_finish;

	}

	pfkey_msg_build(reply, reply_ext_msgs, EXT_BITS_OUT);

free_ext_finish:

	if (reply_ext_msgs[SADB_EXT_SUPPORTED_AUTH])
		kfree(reply_ext_msgs[SADB_EXT_SUPPORTED_AUTH]);

	if (reply_ext_msgs[SADB_EXT_SUPPORTED_ENCRYPT])
		kfree(reply_ext_msgs[SADB_EXT_SUPPORTED_ENCRYPT]);


err:

	return error;
}

int sadb_msg_expire_parse(struct sock *sk, struct sadb_msg *msg, struct sadb_msg **reply)
{
	int error = -EINVAL;

#if 0 /* kernel never receive SADB_EXPIRE message */

	struct sadb_ext *ext_msgs[SADB_EXT_MAX+1];

	if (!msg) {
		PFKEY_DEBUG("msg==NULL\n");
		error = -EINVAL;
		goto err;
	}

	memset(ext_msgs, 0, sizeof(ext_msgs));
	error = sadb_msg_detect_ext(msg, ext_msgs);

err:
#endif /* kernel never receive SADB_EXPIRE message */

	return error;
}

int sadb_msg_flush_parse(struct sock *sk, struct sadb_msg* msg, struct sadb_msg **reply)
{
	int error = 0;

	if (!msg) {
		PFKEY_DEBUG("msg==NULL\n");
		error = -EINVAL;
		goto err;
	}

	error = sadb_msg_sanity_check(msg);

	if (error) {
		PFKEY_DEBUG("message is invalid\n");
		goto err;
	}

	switch(msg->sadb_msg_satype) {
	case SADB_SATYPE_UNSPEC:
		sadb_clear_db(); 
		break;
	default:
		sadb_flush_sa(msg->sadb_msg_satype);
		break;
	}

	*reply = kmalloc(sizeof(struct sadb_msg), GFP_KERNEL);
	memcpy(*reply, msg, sizeof(struct sadb_msg));
err:
	return error;
}

int sadb_msg_flush_sp_parse(struct sock *sk, struct sadb_msg* msg, struct sadb_msg **reply)
{
	int error = 0;

	if (!msg) {
		PFKEY_DEBUG("msg==NULL\n");
		error = -EINVAL;
		goto err;
	}

	error = sadb_msg_sanity_check(msg);

	if (error) {
		PFKEY_DEBUG("message is invalid\n");
		goto err;
	}

	spd_clear_db();

	*reply = kmalloc(sizeof(struct sadb_msg), GFP_KERNEL);
	memcpy(*reply, msg, sizeof(struct sadb_msg));
err:
	return error;
}

int sadb_msg_dump_parse(struct sock *sk, struct sadb_msg* msg, struct sadb_msg **reply)
{
	int error = 0;
	struct sadb_ext *ext_msgs[SADB_EXT_MAX+1];

	if (!msg) {
		PFKEY_DEBUG("msg==NULL\n");
		error = -EINVAL;
		goto err;
	}

	memset(ext_msgs, 0, sizeof(ext_msgs));
	error = sadb_msg_detect_ext(msg, ext_msgs);

err:
	return error;
}

int sadb_msg_send_expire(struct ipsec_sa *sa)
{
	int i=0, error = 0;
	struct sadb_msg *msg = NULL;
	struct sadb_ext *ext_msgs[SADB_EXT_MAX+1];
	struct socket_list *pfkey_socketsp = NULL;

	if (!sa) {
		PFKEY_DEBUG("sa is NULL\n");
		return -EINVAL;
	}

	memset(ext_msgs, 0, sizeof(ext_msgs));

	error = pfkey_msg_hdr_build(&ext_msgs[0],
				    SADB_EXPIRE,
				    sa->ipsec_proto,
				    0,
				    0,
				    0);
	if (error) {
		PFKEY_DEBUG("pfkey_msg_hdr_build is failed\n");
		goto free_ext_finish;
	}

	error = pfkey_sa_build(&ext_msgs[SADB_EXT_SA],
				   SADB_EXT_SA,
				   sa->spi,
				   64,
				   sa->state,
				   sa->auth_algo.algo,
				   sa->esp_algo.algo,
				   0); /* TODO: add pfs flag to struct ipsec_sa */
	if (error) {
		PFKEY_DEBUG("pfkey_sa_build is failed\n");
		goto free_ext_finish;
	}

	error = pfkey_lifetime_build(&ext_msgs[SADB_EXT_LIFETIME_CURRENT],
					 SADB_EXT_LIFETIME_CURRENT,
					 sa->lifetime_c.allocations,
					 sa->lifetime_c.bytes,
					 sa->lifetime_c.addtime,
					 sa->lifetime_c.usetime);
	if (error) {
		PFKEY_DEBUG("pfkey_lifetime_build is failed\n");
		goto free_ext_finish;
	}

	switch(sa->state) {
	case SADB_SASTATE_DEAD:
		error = pfkey_lifetime_build(&ext_msgs[SADB_EXT_LIFETIME_HARD],
						 SADB_EXT_LIFETIME_HARD,
						 sa->lifetime_h.allocations,
						 sa->lifetime_h.bytes,
						 sa->lifetime_h.addtime,
						 sa->lifetime_h.usetime);
		if (error) {
			PFKEY_DEBUG("pfkey_liftime_build(hard) is failed\n");
			goto free_ext_finish;
		}
		break;

	case SADB_SASTATE_DYING:
		error = pfkey_lifetime_build(&ext_msgs[SADB_EXT_LIFETIME_SOFT],
						 SADB_EXT_LIFETIME_SOFT,
						 sa->lifetime_s.allocations,
						 sa->lifetime_s.bytes,
						 sa->lifetime_s.addtime,
						 sa->lifetime_s.usetime);
		if (error) {
			PFKEY_DEBUG("pfkey_lifetime_build(soft) is failed\n");
			goto free_ext_finish;
		}
		break;

	case SADB_SASTATE_LARVAL:
	case SADB_SASTATE_MATURE:
	default:
		error = -EINVAL;
		goto free_ext_finish;

	}
	
	error = pfkey_address_build(&ext_msgs[SADB_EXT_ADDRESS_SRC],
					SADB_EXT_ADDRESS_SRC,
					sa->proto,
					sa->prefixlen_s,
					(struct sockaddr*)&sa->src);
	if (error) goto free_ext_finish;

	error = pfkey_address_build(&ext_msgs[SADB_EXT_ADDRESS_DST],
					SADB_EXT_ADDRESS_DST,
					sa->proto,
					sa->prefixlen_d,
					(struct sockaddr*)&sa->dst);
	if (error) goto free_ext_finish;

	error = pfkey_msg_build(&msg, ext_msgs, EXT_BITS_OUT);

	write_lock_bh(&pfkey_sk_lock);
	for (pfkey_socketsp = pfkey_open_sockets;
		pfkey_socketsp;
			pfkey_socketsp = pfkey_socketsp->next)
	{
		pfkey_upmsg(pfkey_socketsp->socketp, msg);
	}
	write_unlock_bh(&pfkey_sk_lock);

	kfree(msg);

free_ext_finish:
	for (i=0; i<SADB_MAX+1; i++) {
		if (ext_msgs[i]) {
			kfree(ext_msgs[i]);
		}
	}

	return 0;

}

int sadb_msg_send_acquire(struct ipsec_sa *sa)
{
	int i=0, error = 0;
	uint  comb_num = 0;
	struct sadb_comb combs[5];
	struct digest_implementation *di = NULL;
	struct cipher_implementation *ci = NULL;
	struct sadb_msg *msg = NULL;
	struct sadb_ext *ext_msgs[SADB_EXT_MAX+1];
	struct socket_list *pfkey_socketsp = NULL;

	memset(ext_msgs, 0, sizeof(ext_msgs));
	memset(combs, 0, sizeof(combs));

	if (sa->ipsec_proto == SADB_SATYPE_AH || sa->ipsec_proto == SADB_SATYPE_ESP) {
		di = find_digest_by_name("md5", 0 /* atomic */);
		if (di) {
			combs[comb_num].sadb_comb_auth = SADB_AALG_MD5HMAC;
			combs[comb_num].sadb_comb_auth_minbits = AUTH_MD5HMAC_KEY_BITS;
			combs[comb_num].sadb_comb_auth_maxbits = AUTH_MD5HMAC_KEY_BITS;
			combs[comb_num].sadb_comb_reserved = 0;
			combs[comb_num].sadb_comb_soft_allocations = 0;
			combs[comb_num].sadb_comb_hard_allocations = 0;
			combs[comb_num].sadb_comb_soft_bytes = 0;
			combs[comb_num].sadb_comb_hard_bytes = 0;
			combs[comb_num].sadb_comb_soft_addtime = 57600;
			combs[comb_num].sadb_comb_hard_addtime = 86400;
			combs[comb_num].sadb_comb_soft_usetime = 57600;
			combs[comb_num].sadb_comb_hard_usetime = 86400;
			comb_num++;
			di = NULL;	
		}
	
		di = find_digest_by_name("sha1", 0 /* atomic */);
		if (di) {
			combs[comb_num].sadb_comb_auth = SADB_AALG_SHA1HMAC;
			combs[comb_num].sadb_comb_auth_minbits = AUTH_SHA1HMAC_KEY_BITS;
			combs[comb_num].sadb_comb_auth_maxbits = AUTH_SHA1HMAC_KEY_BITS;
			combs[comb_num].sadb_comb_reserved = 0;
			combs[comb_num].sadb_comb_soft_allocations = 0;
			combs[comb_num].sadb_comb_hard_allocations = 0;
			combs[comb_num].sadb_comb_soft_bytes = 0;
			combs[comb_num].sadb_comb_hard_bytes = 0;
			combs[comb_num].sadb_comb_soft_addtime = 57600;
			combs[comb_num].sadb_comb_hard_addtime = 86400;
			combs[comb_num].sadb_comb_soft_usetime = 57600;
			combs[comb_num].sadb_comb_hard_usetime = 86400;
			comb_num++;
			di = NULL;
		}
	}

	if (sa->ipsec_proto == SADB_SATYPE_ESP) {
		ci = find_cipher_by_name("des-cbc", 1 /* atomic */);
		if (ci) {
			combs[comb_num].sadb_comb_encrypt = SADB_EALG_DESCBC;
			combs[comb_num].sadb_comb_encrypt_minbits = ESP_DES_KEY_BITS;
			combs[comb_num].sadb_comb_encrypt_maxbits = ESP_DES_KEY_BITS;
			combs[comb_num].sadb_comb_reserved = 0;
			combs[comb_num].sadb_comb_soft_allocations = 0;
			combs[comb_num].sadb_comb_hard_allocations = 0;
			combs[comb_num].sadb_comb_soft_bytes = 0;
			combs[comb_num].sadb_comb_hard_bytes = 0;
			combs[comb_num].sadb_comb_soft_addtime = 57600;
			combs[comb_num].sadb_comb_hard_addtime = 86400;
			combs[comb_num].sadb_comb_soft_usetime = 57600;
			combs[comb_num].sadb_comb_hard_usetime = 86400;
			comb_num++;
			ci = NULL;
		}

		ci = find_cipher_by_name("3des-cbc", 1 /* atomic */);
		if (ci) {
			combs[comb_num].sadb_comb_encrypt = SADB_EALG_3DESCBC;
			combs[comb_num].sadb_comb_encrypt_minbits = ESP_3DES_KEY_BITS;
			combs[comb_num].sadb_comb_encrypt_maxbits = ESP_3DES_KEY_BITS;
			combs[comb_num].sadb_comb_reserved = 0;
			combs[comb_num].sadb_comb_soft_allocations = 0;
			combs[comb_num].sadb_comb_hard_allocations = 0;
			combs[comb_num].sadb_comb_soft_bytes = 0;
			combs[comb_num].sadb_comb_hard_bytes = 0;
			combs[comb_num].sadb_comb_soft_addtime = 57600;
			combs[comb_num].sadb_comb_hard_addtime = 86400;
			combs[comb_num].sadb_comb_soft_usetime = 57600;
			combs[comb_num].sadb_comb_hard_usetime = 86400;
			comb_num++;
			ci = NULL;
		}

		ci = find_cipher_by_name("aes-cbc", 1 /* atomic */);
		if (ci) {
			combs[comb_num].sadb_comb_encrypt = SADB_EALG_AES;
			combs[comb_num].sadb_comb_encrypt_minbits = ESP_AES_KEY_BITS;
			combs[comb_num].sadb_comb_encrypt_maxbits = ESP_AES_KEY_BITS;
			combs[comb_num].sadb_comb_reserved = 0;
			combs[comb_num].sadb_comb_soft_allocations = 0;
			combs[comb_num].sadb_comb_hard_allocations = 0;
			combs[comb_num].sadb_comb_soft_bytes = 0;
			combs[comb_num].sadb_comb_hard_bytes = 0;
			combs[comb_num].sadb_comb_soft_addtime = 57600;
			combs[comb_num].sadb_comb_hard_addtime = 86400;
			combs[comb_num].sadb_comb_soft_usetime = 57600;
			combs[comb_num].sadb_comb_hard_usetime = 86400;
			comb_num++;
			ci = NULL;
		}
	}

	error = pfkey_msg_hdr_build(&ext_msgs[0],
				    SADB_ACQUIRE,
				    sa->ipsec_proto,
				    0,
				    0,
				    0);
	if (error) {
		PFKEY_DEBUG("pfkey_msg_hdr_build is failed\n");
		goto free_ext_finish;
	}

	error = pfkey_address_build(&ext_msgs[SADB_EXT_ADDRESS_SRC],
					SADB_EXT_ADDRESS_SRC,
					sa->proto,
					sa->prefixlen_s,
					(struct sockaddr*)&sa->src);
	if (error) goto free_ext_finish;

	error = pfkey_address_build(&ext_msgs[SADB_EXT_ADDRESS_DST],
					SADB_EXT_ADDRESS_DST,
					sa->proto,
					sa->prefixlen_d,
					(struct sockaddr*)&sa->dst);
	if (error) goto free_ext_finish;

	error = pfkey_prop_build(&ext_msgs[SADB_EXT_PROPOSAL],
					64,
					comb_num,
					combs);
	if (error) goto free_ext_finish;

	/* XXX fixme: include src and dst identity if given */

	error = pfkey_msg_build(&msg, ext_msgs, EXT_BITS_OUT);

	write_lock_bh(&pfkey_sk_lock);
	for (pfkey_socketsp = pfkey_registered_sockets[sa->ipsec_proto];
		pfkey_socketsp;
			pfkey_socketsp = pfkey_socketsp->next)
	{
		pfkey_upmsg(pfkey_socketsp->socketp, msg);
	}
	write_unlock_bh(&pfkey_sk_lock);

	kfree(msg);
free_ext_finish:

	for (i=0; i<SADB_MAX+1; i++) {
		if (ext_msgs[i]) {
			kfree(ext_msgs[i]);
		}
	}

	return 0;
}
