/*
 * 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-ip.org> / USAGI
 *   Mitsuru KANDA <mk@linux-ipv6.org> / USAGI
 */
/*
 * This is a parse routine for a message of SADB_UPDATE.
 */


#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/ipsec.h>

#include <linux/pfkeyv2.h>
#include <linux/pfkey.h>
#include <net/sadb.h>

#include "pfkey_v2_msg.h"

#define BUFSIZE 64

int sadb_msg_update_parse(struct sock *sk, struct sadb_msg* msg, struct sadb_msg **reply)
{
	int error = 0;
	__u32 spi = 0;
	struct sadb_ext *ext_msgs[SADB_EXT_MAX+1],
			*reply_ext_msgs[SADB_EXT_MAX+1];
	struct sadb_sa *sa = NULL;
	struct sadb_address *src = NULL;
	struct sadb_address *dst = NULL;
	struct ipsec_sa temp_sa;
	struct ipsec_sa *sa_entry = NULL;
	struct sockaddr_storage saddr, daddr, paddr;
	int larval;

	memset(&temp_sa, 0, sizeof(temp_sa));

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

	/* XXX check that the process issuing the UPDATE is the
	 * same that created this SA */

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

	error = sadb_msg_detect_ext(msg, ext_msgs);
	if (error)
		goto err;

	if (ext_msgs[SADB_EXT_SA] &&
	    ext_msgs[SADB_EXT_ADDRESS_SRC] &&
	    ext_msgs[SADB_EXT_ADDRESS_DST])
	{
		
		memset(&saddr, 0, sizeof(struct sockaddr_storage));
		memset(&daddr, 0, sizeof(struct sockaddr_storage));
		memset(&paddr, 0, sizeof(struct sockaddr_storage));

		sa  = (struct sadb_sa *) ext_msgs[SADB_EXT_SA];
		src = (struct sadb_address*)ext_msgs[SADB_EXT_ADDRESS_SRC];
		dst = (struct sadb_address*)ext_msgs[SADB_EXT_ADDRESS_DST];

		spi = sa->sadb_sa_spi;

		error = sadb_address_to_sockaddr(src, (struct sockaddr*)&saddr);

		if (error) {
			PFKEY_DEBUG("src translation failed\n");
			goto err;
		}
		error = sadb_address_to_sockaddr(dst, (struct sockaddr*)&daddr);
		if (error) {
			PFKEY_DEBUG("dst translation failed\n");
			goto err;
		}

		if (sa->sadb_sa_auth) {
			error = sadb_key_to_auth(sa->sadb_sa_auth,
				(struct sadb_key *) ext_msgs[SADB_EXT_KEY_AUTH],
				&temp_sa);
			if (error)
				goto err;
		}

		if (sa->sadb_sa_encrypt
		 && sa->sadb_sa_encrypt != SADB_EALG_NULL) {
			error = sadb_key_to_esp(sa->sadb_sa_encrypt,
				(struct sadb_key *) ext_msgs[SADB_EXT_KEY_ENCRYPT],
				&temp_sa);
			if (error)
				goto err;
		}

		if (ext_msgs[SADB_EXT_ADDRESS_PROXY]) { 
			PFKEY_DEBUG("PFKEY proxy translation not supported.\n");
			error = -EINVAL;
			goto err;
		}

		if (ext_msgs[SADB_EXT_LIFETIME_HARD]) {
			error = sadb_lifetime_to_lifetime((struct sadb_lifetime*)ext_msgs[SADB_EXT_LIFETIME_HARD],
					&temp_sa.lifetime_h);
			if (error) goto err;
		}

		if (ext_msgs[SADB_EXT_LIFETIME_SOFT]) {
			error = sadb_lifetime_to_lifetime((struct sadb_lifetime*)ext_msgs[SADB_EXT_LIFETIME_SOFT],
					&temp_sa.lifetime_s);
			if (error) goto err;
		}

		if (sa->sadb_sa_state != SADB_SASTATE_MATURE) {
			PFKEY_DEBUG("Incorrect SA state %d in SADB_UPDATE\n",
				sa->sadb_sa_state);
			error = -EINVAL;
			goto err;
		}

		error = sadb_find_by_address_proto_spi(
			(struct sockaddr *) &saddr, src->sadb_address_prefixlen,
			(struct sockaddr *) &daddr, dst->sadb_address_prefixlen,
			msg->sadb_msg_satype, spi, &sa_entry);
		if (error != -EEXIST) {
			PFKEY_DEBUG("could not find SA\n");
			goto err;
		}

		write_lock_bh(&sa_entry->lock);

		switch (sa_entry->state) {
		case SADB_SASTATE_LARVAL:
			larval = 1;
			break;
		case SADB_SASTATE_MATURE:
		case SADB_SASTATE_DYING:
			larval = 0;
			break;
		case SADB_SASTATE_DEAD:
		default:
			PFKEY_DEBUG("SADB_UPDATE on a dead SA: stop beating this dead horse\n");
			error = -EINVAL;
			goto err;
		}

	} else {
		PFKEY_DEBUG("necessary ext messages are not available\n");
		error = -EINVAL;
		goto err;
	}

	if (!larval) {
		/* XXX make sure key(s) and identities provided
		 * in the message match those of the existing SA */
	} else {
		/* Copy key(s) to SA */
		ipsec_sa_clear_auth(&sa_entry->auth_algo);
		sa_entry->auth_algo = temp_sa.auth_algo;
		memset(&temp_sa.auth_algo, 0, sizeof(temp_sa.auth_algo));

		ipsec_sa_clear_esp(&sa_entry->esp_algo);
		sa_entry->esp_algo = temp_sa.esp_algo;
		memset(&temp_sa.esp_algo, 0, sizeof(temp_sa.esp_algo));
	}
	sa_entry->state = SADB_SASTATE_MATURE;

	reply_ext_msgs[0] = (struct sadb_ext *) msg;
	reply_ext_msgs[SADB_EXT_SA] = ext_msgs[SADB_EXT_SA];
	reply_ext_msgs[SADB_EXT_ADDRESS_SRC] = ext_msgs[SADB_EXT_ADDRESS_SRC];
	reply_ext_msgs[SADB_EXT_ADDRESS_DST] = ext_msgs[SADB_EXT_ADDRESS_DST];

	if (ext_msgs[SADB_EXT_LIFETIME_HARD])
		reply_ext_msgs[SADB_EXT_LIFETIME_HARD] = ext_msgs[SADB_EXT_LIFETIME_HARD];

	if (ext_msgs[SADB_EXT_LIFETIME_SOFT])
		reply_ext_msgs[SADB_EXT_LIFETIME_SOFT] = ext_msgs[SADB_EXT_LIFETIME_SOFT];

	error = pfkey_msg_build(reply, reply_ext_msgs, EXT_BITS_OUT);

err:
	ipsec_sa_clear_auth(&temp_sa.auth_algo);
	ipsec_sa_clear_esp(&temp_sa.esp_algo);
	if (sa_entry) {
		write_unlock_bh(&sa_entry->lock);
		ipsec_sa_put(sa_entry);
	}
	return error;
}

