/*
 * Algorithm info parsing and creation functions
 * Author: JuanJo Ciarlante <jjo-ipsec@mendoza.gov.ar>
 *
 * $Id: alg_info.c,v 1.3 2004/08/02 04:35:32 gerg 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.
 */
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <ctype.h>
#include <freeswan.h>
#include <pfkeyv2.h>

#include "alg_info.h"
#include "constants.h"
#ifndef NO_PLUTO
#include "defs.h"
#include "log.h"
#include "sha1.h"
#include "md5.h"
#include "crypto.h"
#include "kernel_alg.h"
#include "ike_alg.h"
#else
/*
 *	macros/functions for compilation without pluto (eg: spi for manual conns)
 */
#include <assert.h>
#define passert(x) assert(x)
extern int debug;	/* eg: spi.c */
#define DBG(cond, action)   { if (debug) { action ; } }
#define DBG_log(x, args...) fprintf(stderr, x "\n" , ##args);
#define alloc_thing(thing, name) alloc_bytes(sizeof (thing), name)
void * alloc_bytes(size_t size, const char *name) {
	void *p=malloc(size);
        if (p == NULL)
		fprintf(stderr, "unable to malloc %lu bytes for %s",
			(unsigned long) size, name);
	memset(p, '\0', size);
	return p;
}
#define pfreeany(ptr) free(ptr)
#endif /* NO_PLUTO */

/* sadb/ESP aa attrib converters */
int
alg_info_esp_aa2sadb(int auth)
{
	int sadb_aalg=0;
	switch(auth) {
		case AUTH_ALGORITHM_HMAC_MD5:
		case AUTH_ALGORITHM_HMAC_SHA1:
			sadb_aalg=auth+1;
			break;
		case AUTH_ALGORITHM_HMAC_SHA2_256:
		case AUTH_ALGORITHM_HMAC_SHA2_384:
		case AUTH_ALGORITHM_HMAC_SHA2_512:
		case AUTH_ALGORITHM_HMAC_RIPEMD:
			sadb_aalg=auth;
			break;
		default:
			/* loose ... */
			sadb_aalg=auth;
	}
	return sadb_aalg;
}

int /* __attribute__ ((unused)) */
alg_info_esp_sadb2aa(int sadb_aalg)
{
	int auth=0;
	switch(sadb_aalg) {
		case SADB_AALG_MD5HMAC:
		case SADB_AALG_SHA1HMAC:
			auth=sadb_aalg-1;
			break;
			/* since they are the same ...  :)  */
		case AUTH_ALGORITHM_HMAC_SHA2_256:
		case AUTH_ALGORITHM_HMAC_SHA2_384:
		case AUTH_ALGORITHM_HMAC_SHA2_512:
		case AUTH_ALGORITHM_HMAC_RIPEMD:
			auth=sadb_aalg;
			break;
		default:
			/* loose ... */
			auth=sadb_aalg;
	}
	return auth;
}

/*
 * 	Search enum_name array with in prefixed uppercase
 */
static int
enum_search_prefix (enum_names *ed, const char *prefix, const char *str, int strlength)
{
	char buf[64];
	char *ptr;
	int ret;
	int len=sizeof(buf)-1;	/* reserve space for final \0 */

	strcpy(buf,prefix);
	strcat(buf,str);
	ret=enum_search(ed, buf);
	return ret;
}
/*
 * 	Search enum_name array with in prefixed and postfixed uppercase
 */
static int
enum_search_ppfix (enum_names *ed, const char *prefix, const char *postfix, const char *str, int strlength)
{
	char buf[64];
	char *ptr;
	int ret;
	int len=sizeof(buf)-1;	/* reserve space for final \0 */

	strcpy(buf, prefix);
	strcat(buf, str);
	strcat(buf, postfix);
	DBG(DBG_CRYPT, DBG_log("%s() calling enum_search(%p, \"%s\")",
				__FUNCTION__, ed, buf));
	ret=enum_search(ed, buf);
	return ret;
}

#if 0
/*
 * 	Search enum_name array with in prefixed uppercase
 */
static int
enum_search_prefix (enum_names *ed, const char *prefix, const char *str, int strlength)
{
	char buf[64];
	char *ptr;
	int ret;
	int len=sizeof(buf)-1;	/* reserve space for final \0 */
	for (ptr=buf; *prefix; *ptr++=*prefix++, len--);
	while (strlength--&&len--&&*str) *ptr++=toupper(*str++);
	*ptr=0;
	DBG(DBG_CRYPT, DBG_log(__FUNCTION__ "() "
				"calling enum_search(%p, \"%s\")", ed, buf));
	ret=enum_search(ed, buf);
	return ret;
}
/*
 * 	Search enum_name array with in prefixed and postfixed uppercase
 */
static int
enum_search_ppfix (enum_names *ed, const char *prefix, const char *postfix, const char *str, int strlength)
{
	char buf[64];
	char *ptr;
	int ret;
	int len=sizeof(buf)-1;	/* reserve space for final \0 */
	for (ptr=buf; *prefix; *ptr++=*prefix++, len--);
	while (strlength--&&len--&&*str) *ptr++=toupper(*str++);
	while (len--&&*postfix) *ptr++=*postfix++;
	*ptr=0;
	DBG(DBG_CRYPT, DBG_log(__FUNCTION__ "() "
				"calling enum_search(%p, \"%s\")", ed, buf));
	ret=enum_search(ed, buf);
	return ret;
}
#endif
/*
 * 	Search esp_transformid_names for a match, eg:
 * 		"3des" <=> "ESP_3DES"
 */
#define ESP_MAGIC_ID 0x00ffff01 
static int
ealg_getbyname_esp(const char *const str, int len)
{
	int ret=-1;
	if (!str||!*str) 
		goto out;
	/* leave special case for eg:  "id248" string */
	if (strcmp("id", str)==0)
		return ESP_MAGIC_ID;
	ret=enum_search_prefix(&esp_transformid_names, "ESP_", str, len);
out:
	return ret;
}
/*
 * 	Search auth_alg_names for a match, eg:
 * 		"md5" <=> "AUTH_ALGORITHM_HMAC_MD5"
 */
static int
aalg_getbyname_esp(const char *const str, int len)
{
	int ret=-1;
	unsigned num;
	if (!str||!*str)
		goto out;
	ret=enum_search_prefix(&auth_alg_names,"AUTH_ALGORITHM_HMAC_",str,len);
	if (ret>=0) goto out;
	ret=enum_search_prefix(&auth_alg_names,"AUTH_ALGORITHM_",str,len);
	if (ret>=0) goto out;
	sscanf(str, "id%d%n", &ret, &num);
	if (ret >=0 && num!=strlen(str))
		ret=-1;
out:
	return ret;
}
static int
modp_getbyname_esp(const char *const str, int len)
{
	int ret=-1;
	if (!str||!*str)
		goto out;
	ret=enum_search_prefix(&oakley_group_names,"OAKLEY_GROUP_",str,len);
	if (ret>=0) goto out;
	ret=enum_search_ppfix(&oakley_group_names, "OAKLEY_GROUP_", " (extension)", str, len);
out:
	return ret;
}
void 
alg_info_free(struct alg_info *alg_info) {
	pfreeany(alg_info);
}
/*	
 *	Raw add routine: only checks for no duplicates		
 */
static void
__alg_info_esp_add (struct alg_info_esp *alg_info, int ealg_id, unsigned ek_bits, int aalg_id, unsigned ak_bits)
{
	struct esp_info *esp_info=alg_info->esp;
	unsigned cnt=alg_info->alg_info_cnt, i;
	/* 	check for overflows 	*/
	passert(cnt < elemsof(alg_info->esp));
	/*	dont add duplicates	*/
	for (i=0;i<cnt;i++)
		if (	esp_info[i].esp_ealg_id==ealg_id &&
			(!ek_bits || esp_info[i].esp_ealg_keylen==ek_bits) &&
			esp_info[i].esp_aalg_id==aalg_id &&
			(!ak_bits || esp_info[i].esp_aalg_keylen==ak_bits))
			return;
	esp_info[cnt].esp_ealg_id=ealg_id;
	esp_info[cnt].esp_ealg_keylen=ek_bits;
	esp_info[cnt].esp_aalg_id=aalg_id;
	esp_info[cnt].esp_aalg_keylen=ak_bits;
	/* sadb values */
	esp_info[cnt].encryptalg=ealg_id;
	esp_info[cnt].authalg=alg_info_esp_aa2sadb(aalg_id);
	alg_info->alg_info_cnt++;
	DBG(DBG_CRYPT, DBG_log("%s() ealg=%d aalg=%d cnt=%d", __FUNCTION__,
				ealg_id, aalg_id, alg_info->alg_info_cnt));
}

/*	
 *	Add ESP alg info _with_ logic (policy):
 */
static void
alg_info_esp_add (struct alg_info *alg_info, int ealg_id, int ek_bits, int aalg_id, int ak_bits)
{
	/*	Policy: default to 3DES */
	if (ealg_id==0)
		ealg_id=ESP_3DES;
	if (ealg_id>0) {
		if (aalg_id>0)
			__alg_info_esp_add((struct alg_info_esp *)alg_info,
					ealg_id, ek_bits,
					aalg_id, ak_bits);
		else {
			/*	Policy: default to MD5 and SHA1 */
			__alg_info_esp_add((struct alg_info_esp *)alg_info,
					ealg_id, ek_bits, \
					AUTH_ALGORITHM_HMAC_MD5, ak_bits);
			__alg_info_esp_add((struct alg_info_esp *)alg_info,
					ealg_id, ek_bits, \
					AUTH_ALGORITHM_HMAC_SHA1, ak_bits);
		}
	}
}

/**************************************
 *
 *	IKE alg
 *
 *************************************/
/*
 * 	Search oakley_enc_names for a match, eg:
 * 		"3des_cbc" <=> "OAKLEY_3DES_CBC"
 */
static int
ealg_getbyname_ike(const char *const str, int len)
{
	int ret=-1;
	if (!str||!*str)
		goto out;
	ret=enum_search_prefix(&oakley_enc_names,"OAKLEY_",str,len);
	if (ret>=0) goto out;
	ret=enum_search_ppfix(&oakley_enc_names, "OAKLEY_", "_CBC", str, len);
out:
	return ret;
}
/*
 * 	Search  oakley_hash_names for a match, eg:
 * 		"md5" <=> "OAKLEY_MD5"
 */
static int
aalg_getbyname_ike(const char *const str, int len)
{
	int ret=-1;
	unsigned num;
	if (!str||!*str)
		goto out;
	ret=enum_search_prefix(&oakley_hash_names,"OAKLEY_",str,len);
	if (ret>=0) goto out;
	sscanf(str, "id%d%n", &ret, &num);
	if (ret >=0 && num!=strlen(str))
		ret=-1;
out:
	return ret;
}
/*
 * 	Search oakley_group_names for a match, eg:
 * 		"modp1024" <=> "OAKLEY_GROUP_MODP1024"
 */
static int
modp_getbyname_ike(const char *const str, int len)
{
	int ret=-1;
	if (!str||!*str)
		goto out;
	ret=enum_search_prefix(&oakley_group_names,"OAKLEY_GROUP_",str,len);
	if (ret>=0) goto out;
	ret=enum_search_ppfix(&oakley_group_names, "OAKLEY_GROUP_", " (extension)", str, len);
out:
	return ret;
}
static void
__alg_info_ike_add (struct alg_info_ike *alg_info, int ealg_id, unsigned ek_bits, int aalg_id, unsigned ak_bits, int modp_id)
{
	struct ike_info *ike_info=alg_info->ike;
	unsigned cnt=alg_info->alg_info_cnt, i;
	/* 	check for overflows 	*/
	passert(cnt < elemsof(alg_info->ike));
	/*	dont add duplicates	*/
	for (i=0;i<cnt;i++)
		if (	ike_info[i].ike_ealg==ealg_id &&
			(!ek_bits || ike_info[i].ike_eklen==ek_bits) &&
			ike_info[i].ike_halg==aalg_id &&
			(!ak_bits || ike_info[i].ike_hklen==ak_bits) &&
			ike_info[i].ike_modp==modp_id
		   )
			return;
	ike_info[cnt].ike_ealg=ealg_id;
	ike_info[cnt].ike_eklen=ek_bits;
	ike_info[cnt].ike_halg=aalg_id;
	ike_info[cnt].ike_hklen=ak_bits;
	ike_info[cnt].ike_modp=modp_id;
	alg_info->alg_info_cnt++;
	DBG(DBG_CRYPT, DBG_log("%s() ealg=%d aalg=%d modp_id=%d, cnt=%d",
				__FUNCTION__,
				ealg_id, aalg_id, modp_id,
				alg_info->alg_info_cnt));
}

/*	
 * 	Proposals will be built by looping over default_ike_groups array and
 * 	merging alg_info (ike_info) contents
 */

#ifndef NO_PLUTO
static int default_ike_groups[] = { 
	OAKLEY_GROUP_MODP1536,
	OAKLEY_GROUP_MODP1024
};

/*	
 *	Add IKE alg info _with_ logic (policy):
 */
static void
alg_info_ike_add (struct alg_info *alg_info, int ealg_id, int ek_bits, int aalg_id, int ak_bits, int modp_id)
{
	int i=0, n_groups;
	n_groups=elemsof(default_ike_groups);
	/* if specified modp_id avoid loop over default_ike_groups */
	if (modp_id) {
		n_groups=0;
		goto in_loop;
	}
	
	for (;n_groups--;i++) {
		modp_id=default_ike_groups[i];
in_loop:
		/*	Policy: default to 3DES */
		if (ealg_id==0)
			ealg_id=OAKLEY_3DES_CBC;
		if (ealg_id>0) {
			if (aalg_id>0)
				__alg_info_ike_add((struct alg_info_ike *)alg_info,
						ealg_id, ek_bits,
						aalg_id, ak_bits,
						modp_id);
			else {
				/*	Policy: default to MD5 and SHA */
				__alg_info_ike_add((struct alg_info_ike *)alg_info,
						ealg_id, ek_bits, \
						OAKLEY_MD5, ak_bits, modp_id);
				__alg_info_ike_add((struct alg_info_ike *)alg_info,
						ealg_id, ek_bits, \
						OAKLEY_SHA, ak_bits, modp_id);
			}
		}
	}
}
#endif /* NO_PLUTO */

/*	
 *	Creates a new alg_info by parsing passed string		
 */
enum parser_state_esp {
	ST_INI,
	ST_EA,		/* encrypt algo   */
	ST_EA_END,	
	ST_EK,		/* enc. key length */
	ST_EK_END,
	ST_AA,		/* auth algo */
	ST_AA_END,
	ST_AK,		/* auth. key length */
	ST_AK_END,
	ST_MODP,	/* modp spec */
	ST_FLAG_STRICT,
	ST_END,
	ST_EOF,
	ST_ERR
};
static const char *parser_state_esp_names[] = {
	"ST_INI",
	"ST_EA",
	"ST_EA_END",	
	"ST_EK",
	"ST_EK_END",
	"ST_AA",
	"ST_AA_END",
	"ST_AK",
	"ST_AK_END",
	"ST_MOPD",
	"ST_FLAG_STRICT",
	"ST_END",
	"ST_EOF",
	"ST_ERR"
};
static const char *parser_state_name_esp(enum parser_state_esp state) {
	return parser_state_esp_names[state];
}
/* XXX:jjo to implement different parser for ESP and IKE */
struct parser_context {
	unsigned state, old_state;
	unsigned protoid;
	char ealg_buf[16];
	char aalg_buf[16];
	char modp_buf[16];
	int (*ealg_getbyname)(const char *const str, int len);
	int (*aalg_getbyname)(const char *const str, int len);
	int (*modp_getbyname)(const char *const str, int len);
	char *ealg_str;
	char *aalg_str;
	char *modp_str;
	int eklen;
	int aklen;
	int ch;
	const char *err;
};
static inline void parser_set_state(struct parser_context *p_ctx, enum parser_state_esp state) {
	if (state!=p_ctx->state) {
		p_ctx->old_state=p_ctx->state;
		p_ctx->state=state;
	}
	
}
static int 
parser_machine(struct parser_context *p_ctx)
{
	int ch=p_ctx->ch;
	/* special 'absolute' cases */
	p_ctx->err="No error.";

	/* chars that end algo strings */
	switch(ch){
		case 0:		/* end-of-string */
		case '!':	/* flag as strict algo list */
		case ',':	/* algo string separator */
			switch(p_ctx->state) {
				case ST_EA:
				case ST_EK:
				case ST_AA:
				case ST_AK: 
				case ST_MODP:
				case ST_FLAG_STRICT:
					{
					enum parser_state_esp next_state=0;
					switch(ch) {
						case 0:   next_state=ST_EOF;break;
						case ',': next_state=ST_END;break;
						case '!': next_state=ST_FLAG_STRICT;break;
					}
					/* ch? parser_set_state(p_ctx, ST_END) : parser_set_state(p_ctx, ST_EOF) ; */
					parser_set_state(p_ctx, next_state);
					goto out;
				}
				default:
					p_ctx->err="String ended with invalid char";
					goto err;
			}
	}
re_eval:
	switch(p_ctx->state) {
		case ST_INI:
			if (isspace(ch))
				break;
			if (isalnum(ch)) {
				*(p_ctx->ealg_str++)=ch;
				parser_set_state(p_ctx, ST_EA);
				break;
			}
			p_ctx->err="No alphanum. char initially found";
			goto err;
		case ST_EA:
			if (isalpha(ch) || ch == '_') {
				*(p_ctx->ealg_str++)=ch;
				break;
			}
			if (isdigit(ch)) {
				/* bravely switch to enc keylen */
				*(p_ctx->ealg_str)=0;
				parser_set_state(p_ctx, ST_EK);
				goto re_eval;
			}
			if (ch=='-') {
				*(p_ctx->ealg_str)=0;
				parser_set_state(p_ctx, ST_EA_END);
				break;
			}
			p_ctx->err="No valid char found after enc alg string";
			goto err;
		case ST_EA_END:
			if (isdigit(ch)) {
				/* bravely switch to enc keylen */
				parser_set_state(p_ctx, ST_EK);
				goto re_eval;
			}
			if (isalpha(ch)) {
				parser_set_state(p_ctx, ST_AA);
				goto re_eval;
			}
			p_ctx->err="No alphanum char found after enc alg separator";
			goto err;
		case ST_EK:
			if (ch=='-') {
				parser_set_state(p_ctx, ST_EK_END);
				break;
			}
			if (isdigit(ch)) {
				p_ctx->eklen=p_ctx->eklen*10+ch-'0';
				break;
			}
			p_ctx->err="Non digit or valid separator found while reading enc keylen";
			goto err;
		case ST_EK_END:
			if (isalpha(ch)) {
				parser_set_state(p_ctx, ST_AA);
				goto re_eval;
			}
			p_ctx->err="Non alpha char found after enc keylen end separator";
			goto err;
		case ST_AA:
			if (ch=='-') {
				*(p_ctx->aalg_str++)=0;
				parser_set_state(p_ctx, ST_AA_END);
				break;
			}
			if (isalnum(ch) || ch=='_') {
				*(p_ctx->aalg_str++)=ch;
				break;
			}
			p_ctx->err="Non alphanum or valid separator found in auth string";
			goto err;
		case ST_AA_END:
			if (isdigit(ch)) {
				parser_set_state(p_ctx, ST_AK);
				goto re_eval;
			}
			/* Only allow modpXXXX string if we have
			 * a modp_getbyname method
			 */
			if ((p_ctx->modp_getbyname) && isalpha(ch)) {
				parser_set_state(p_ctx, ST_MODP);
				goto re_eval;
			}
			p_ctx->err="Non initial digit found for auth keylen";
			goto err;
		case ST_AK:
			if (ch=='-') {
				parser_set_state(p_ctx, ST_AK_END);
				break;
			}
			if (isdigit(ch)) {
				p_ctx->aklen=p_ctx->aklen*10+ch-'0';
				break;
			}
			p_ctx->err="Non digit found for auth keylen";
			goto err;
		case ST_AK_END:
			/* Only allow modpXXXX string if we have
			 * a modp_getbyname method
			 */
			if ((p_ctx->modp_getbyname) && isalpha(ch)) {
				parser_set_state(p_ctx, ST_MODP);
				goto re_eval;
			}
			p_ctx->err="Non alpha char found after auth keylen";
			goto err;
		case ST_MODP:
			if (isalnum(ch)) {
				*(p_ctx->modp_str++)=ch;
				break;
			}
			p_ctx->err="Non alphanum char found after in modp string";
			goto err;
		case ST_FLAG_STRICT:
			if (ch == 0) {
				parser_set_state(p_ctx, ST_END);
			}
			p_ctx->err="Flags character(s) must be at end of whole string";
			goto err;

		/* XXX */
		case ST_END:
		case ST_EOF:
		case ST_ERR:
		/* XXX */
			break;
	}
out:
	return p_ctx->state;
err:
	parser_set_state(p_ctx, ST_ERR);
	return ST_ERR;
}
/*	
 *	Must be called for each "new" char, with new
 *	character in ctx.ch
 */
static void
parser_init(struct parser_context *p_ctx, unsigned protoid)
{
	memset(p_ctx, 0, sizeof (*p_ctx));
	p_ctx->protoid=protoid; /* XXX: jjo */
	p_ctx->protoid=PROTO_IPSEC_ESP;
	p_ctx->ealg_str=p_ctx->ealg_buf;
	p_ctx->aalg_str=p_ctx->aalg_buf;
	p_ctx->modp_str=p_ctx->modp_buf;
	p_ctx->state=ST_INI;
	switch(protoid) {
		case PROTO_ISAKMP:
			p_ctx->ealg_getbyname=ealg_getbyname_ike;
			p_ctx->aalg_getbyname=aalg_getbyname_ike;
			p_ctx->modp_getbyname=modp_getbyname_ike;
			break;
		case PROTO_IPSEC_ESP:
			p_ctx->ealg_getbyname=ealg_getbyname_esp;
			p_ctx->aalg_getbyname=aalg_getbyname_esp;
			break;
	}
}
static int
parser_alg_info_add(struct parser_context *p_ctx, struct alg_info *alg_info)
{
	int ealg_id, aalg_id;
	int modp_id = 0;
#ifndef NO_PLUTO
	const struct oakley_group_desc *gd;
#endif
	ealg_id=aalg_id=0;
	if (*p_ctx->ealg_buf) {
		ealg_id=p_ctx->ealg_getbyname(p_ctx->ealg_buf, strlen(p_ctx->ealg_buf));
		if (ealg_id==ESP_MAGIC_ID) {
			ealg_id=p_ctx->eklen;
			p_ctx->eklen=0;
		}
		if (ealg_id<0) {
			p_ctx->err="enc_alg not found";
			goto out;
		}
		DBG(DBG_CRYPT, DBG_log("%s() ealg_getbyname(\"%s\")=%d",
				__FUNCTION__,
				p_ctx->ealg_buf,
				ealg_id));
	}
	if (*p_ctx->aalg_buf) {
		aalg_id=p_ctx->aalg_getbyname(p_ctx->aalg_buf, strlen(p_ctx->aalg_buf));
		if (aalg_id<0) {
			p_ctx->err="hash_alg not found";
			goto out;
		}
		DBG(DBG_CRYPT, DBG_log("%s() aalg_getbyname(\"%s\")=%d",
				__FUNCTION__,
				p_ctx->aalg_buf,
				aalg_id));
	}
	if (p_ctx->modp_getbyname && *p_ctx->modp_buf) {
		modp_id=p_ctx->modp_getbyname(p_ctx->modp_buf, strlen(p_ctx->modp_buf));
		if (modp_id<0) {
			p_ctx->err="modp group not found";
			goto out;
		}
		DBG(DBG_CRYPT, DBG_log("%s() modp_getbyname(\"%s\")=%d",
				__FUNCTION__,
				p_ctx->modp_buf,
				modp_id));
	}
	switch(alg_info->alg_info_protoid) {
		case PROTO_IPSEC_ESP:
			alg_info_esp_add(alg_info,
					ealg_id, p_ctx->eklen,
					aalg_id, p_ctx->aklen);
			break;
#ifndef NO_PLUTO
		case PROTO_ISAKMP:
			if (modp_id && !(gd=lookup_group(modp_id))) {
				p_ctx->err="found modp group id, but not supported";
				goto out;
			}
			alg_info_ike_add(alg_info,
					ealg_id, p_ctx->eklen,
					aalg_id, p_ctx->aklen,
					modp_id);
			break;
#endif
		default:
			goto out;
	}
	return 0;
out:
	return -1;
}
static int
alg_info_parse_str (struct alg_info *alg_info, const char *alg_str, const char **err_p)
{
	struct parser_context ctx;
	int ret;
	const char *ptr;
	static char err_buf[256];
	*err_buf=0;
	parser_init(&ctx, alg_info->alg_info_protoid);
	if (err_p) *err_p=NULL;
	/* use default if nul esp string */
	if (!*alg_str) {
		switch (alg_info->alg_info_protoid) {
#ifndef NO_PLUTO
			case PROTO_ISAKMP:
				alg_info_ike_add(alg_info, 0, 0, 0, 0, 0);
				return 0;
#endif
			case PROTO_IPSEC_ESP:
				alg_info_esp_add(alg_info, 0, 0, 0, 0);
				return 0;
			default:
				/* IMPOSSIBLE */
				passert(alg_info->alg_info_protoid);
		}
	}

	for(ret=0,ptr=alg_str;ret<ST_EOF;) {
		ctx.ch=*ptr++;
		ret= parser_machine(&ctx);
		switch(ret) {
			case ST_FLAG_STRICT:
				alg_info->alg_info_flags |= ALG_INFO_F_STRICT;
				break;
			case ST_END:
			case ST_EOF:
				DBG(DBG_CRYPT, DBG_log("%s() "
				"ealg_buf=%s aalg_buf=%s"
				"eklen=%d  aklen=%d",
				__FUNCTION__,
				ctx.ealg_buf, ctx.aalg_buf,
				ctx.eklen, ctx.aklen));
				if (parser_alg_info_add(&ctx, alg_info)<0) {
					snprintf(err_buf, sizeof(err_buf),
					"%s, enc_alg=\"%s\", auth_alg=\"%s\", "
					"modp=\"%s\"",
					ctx.err,
					ctx.ealg_buf,
					ctx.aalg_buf,
					ctx.modp_buf);
					goto err;
				}
				/* zero out for next run (ST_END) */
				parser_init(&ctx, alg_info->alg_info_protoid);
				break;
			case ST_ERR:
				snprintf(err_buf, sizeof(err_buf),
					"%s, "
					"just after \"%.*s\""
					" (old_state=%s)",
					ctx.err,
					ptr-alg_str-1, alg_str ,
					parser_state_name_esp(ctx.old_state) );

				goto err;
			default:
				if (!ctx.ch) break;
		}
	}
	return 0;
err:
	if (err_p) {
		*err_p=err_buf;
	}
	return -1;
}
struct alg_info_esp *
alg_info_esp_create_from_str (const char *alg_str, const char **err_p)
{
	struct alg_info_esp *alg_info_esp;
	char esp_buf[256];
	static char err_buf[256];
	char *pfs_name;
	int ret =0;
	/*
	 * 	alg_info storage should be sized dynamically
	 * 	but this may require 2passes to know
	 * 	transform count in advance.
	 */
	alg_info_esp=alloc_thing (struct alg_info_esp, "alg_info_esp");
	if (!alg_info_esp) goto out;
	pfs_name=index (alg_str, ';');
	if (pfs_name) {
		memcpy(esp_buf, alg_str, pfs_name-alg_str);
		esp_buf[pfs_name-alg_str] = 0;
		alg_str=esp_buf;
		pfs_name++;
		/* if pfs strings AND first char is not '0' */
		if (*pfs_name && pfs_name[0]!='0') {
			ret=modp_getbyname_esp(pfs_name, strlen(pfs_name));
			if (ret<0) {
				/* Bomb if pfsgroup not found */
				DBG(DBG_CRYPT, DBG_log("%s(): "
					"pfsgroup \"%s\" not found",
					__FUNCTION__,
					pfs_name));
				if (*err_p) {
					snprintf(err_buf, sizeof(err_buf),
						"pfsgroup \"%s\" not found",
						pfs_name);
					*err_p=err_buf;
				}
				goto out;
			}
			alg_info_esp->esp_pfsgroup=ret;
		}
	}
	alg_info_esp->alg_info_protoid=PROTO_IPSEC_ESP;
	ret=alg_info_parse_str((struct alg_info *)alg_info_esp, alg_str, err_p) ;
out:
	if (ret<0)
	{
		pfreeany(alg_info_esp);
		alg_info_esp=NULL;
	}
	return alg_info_esp;

}
#ifndef NO_PLUTO
struct alg_info_ike *
alg_info_ike_create_from_str (const char *alg_str, const char **err_p)
{
	struct alg_info_ike *alg_info_ike;
	/*
	 * 	alg_info storage should be sized dynamically
	 * 	but this may require 2passes to know
	 * 	transform count in advance.
	 */
	alg_info_ike=alloc_thing (struct alg_info_ike, "alg_info_ike");
	if (!alg_info_ike) goto out;
	alg_info_ike->alg_info_protoid=PROTO_ISAKMP;
	if (alg_info_parse_str((struct alg_info *)alg_info_ike,
					alg_str, err_p) < 0)
	{
		pfreeany(alg_info_ike);
		alg_info_ike=NULL;
	}
out:
	return alg_info_ike;
}
#endif
/*
 * 	alg_info struct can be shared by
 * 	several connections instances,
 * 	handle free() with ref_cnts
 */
void 
alg_info_addref(struct alg_info *alg_info)
{
	if (alg_info != NULL) {
		alg_info->ref_cnt++;
		DBG(DBG_CRYPT, DBG_log("%s() alg_info->ref_cnt=%d",
				__FUNCTION__,  alg_info->ref_cnt));
	}
}
void
alg_info_delref(struct alg_info **alg_info_p)
{
	struct alg_info *alg_info=*alg_info_p;
	if (alg_info != NULL) {
		passert(alg_info->ref_cnt != 0);
		alg_info->ref_cnt--;
		DBG(DBG_CRYPT, DBG_log("%s() alg_info->ref_cnt=%d",
				__FUNCTION__, alg_info->ref_cnt));
		if (alg_info->ref_cnt==0) {
			DBG(DBG_CRYPT, DBG_log("%s() freeing alg_info",
					__FUNCTION__));
			alg_info_free(alg_info);
		}
		*alg_info_p=NULL;
	}
}

/*	snprint already parsed transform list (alg_info)	*/
int
alg_info_snprint(char *buf, int buflen, struct alg_info *alg_info)
{
	char *ptr=buf;
	int np=0;
	struct esp_info *esp_info;
#ifndef NO_PLUTO
	struct ike_info *ike_info;
#endif
	int cnt;
	/* ptr+=sprintf(buf, "{ealg[eklen],aalg[aklen]} = "); */
	ptr=buf;
	switch(alg_info->alg_info_protoid) {
		case PROTO_IPSEC_ESP: 
		{
			struct alg_info_esp *alg_info_esp=(struct alg_info_esp *)alg_info;
			ALG_INFO_ESP_FOREACH(alg_info_esp, esp_info, cnt) {
				np=snprintf(ptr, buflen, "%d_%03d-%d, "
						, esp_info->esp_ealg_id
						, esp_info->esp_ealg_keylen
						, esp_info->esp_aalg_id);
				ptr+=np;
				buflen-=np;
			}
			if (alg_info_esp->esp_pfsgroup) {
				np=snprintf(ptr, buflen, "; pfsgroup=%d; "
					, alg_info_esp->esp_pfsgroup);
				ptr+=np;
				buflen-=np;
			}
			break;
	        }
#ifndef NO_PLUTO
		case PROTO_ISAKMP:
			ALG_INFO_IKE_FOREACH((struct alg_info_ike *)alg_info, ike_info, cnt) {
				np=snprintf(ptr, buflen, "%d_%03d-%d-%d, ",
						ike_info->ike_ealg,
						ike_info->ike_eklen,
						ike_info->ike_halg,
						ike_info->ike_modp);
				ptr+=np;
				buflen-=np;
			}
			break;
#endif
		default:
			np=snprintf(buf, buflen, "INVALID protoid=%d\n",
					alg_info->alg_info_protoid);
			goto out;
	}
	ptr+=sprintf(ptr, "flags=%s",
			alg_info->alg_info_flags&ALG_INFO_F_STRICT?
			"strict":"-strict");
out:
	return ptr-buf;
}

#ifndef NO_PLUTO
int
alg_info_snprint_esp(char *buf, int buflen, struct alg_info_esp *alg_info)
{
	char *ptr=buf;
	int ret;
	struct esp_info *esp_info;
	int cnt;
	int eklen, aklen;
	/* ptr+=sprintf(buf, "{ealg[eklen],aalg[aklen]} = "); */
	ptr=buf;
	*ptr=0;
	ALG_INFO_ESP_FOREACH(alg_info, esp_info, cnt) {
		if (kernel_alg_esp_enc_ok(esp_info->esp_ealg_id, 0, NULL) &&
			(kernel_alg_esp_auth_ok(esp_info->esp_aalg_id, NULL))) {
		eklen=esp_info->esp_ealg_keylen;
		if (!eklen) 
			eklen=kernel_alg_esp_enc_keylen(esp_info->esp_ealg_id)*BITS_PER_BYTE;
		aklen=esp_info->esp_aalg_keylen;
		if (!aklen) 
			aklen=kernel_alg_esp_auth_keylen(esp_info->esp_aalg_id)*BITS_PER_BYTE;
		ret=snprintf(ptr, buflen, "%d/%03d-%d/%03d, ",
				esp_info->esp_ealg_id,
				eklen,
				esp_info->esp_aalg_id,
				aklen);
		ptr+=ret;
		buflen-=ret;
		}
	}
	return buflen;
}

int
alg_info_snprint_ike(char *buf, int buflen, struct alg_info_ike *alg_info)
{
	char *ptr=buf;
	int ret;
	struct ike_info *ike_info;
	int cnt;
	int eklen, aklen;
	struct encrypt_desc *enc_desc;
	struct hash_desc *hash_desc;
	/* ptr+=sprintf(buf, "{ealg[eklen],aalg[aklen]} = "); */
	ptr=buf;
	*ptr=0;
	ALG_INFO_IKE_FOREACH(alg_info, ike_info, cnt) {
		if (ike_alg_enc_present(ike_info->ike_ealg) &&
			(ike_alg_hash_present(ike_info->ike_halg))) {
		enc_desc=ike_alg_get_encrypter(ike_info->ike_ealg);
		passert(enc_desc != NULL);
		hash_desc=ike_alg_get_hasher(ike_info->ike_halg);
		passert(hash_desc != NULL);

		eklen=ike_info->ike_eklen;
		if (!eklen) 
			eklen=enc_desc->keydeflen;
		aklen=ike_info->ike_hklen;
		if (!aklen) 
			aklen=hash_desc->hash_digest_size * BITS_PER_BYTE;
		ret=snprintf(ptr, buflen, "%d_%03d-%d_%03d-%d, ",
				ike_info->ike_ealg,
				eklen,
				ike_info->ike_halg,
				aklen,
				ike_info->ike_modp);
		ptr+=ret;
		buflen-=ret;
		}
	}
	return buflen;
}
#endif /* NO_PLUTO */
