/*
 * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Niels Provos.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * identity.c:
 * handling identity choices and creation of the before mentioned.
 */

#ifndef lint
static char rcsid[] = "$Id: identity.c,v 1.5 1998/03/04 11:43:29 provos Exp $";
#endif

#define _IDENTITY_C_

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <md5.h>
#include <gmp.h>
#include <sha1.h>

#ifdef	 linux
#include "linux.h"
#endif

#include "config.h"
#include "photuris.h"
#include "state.h"
#include "attributes.h"
#include "modulus.h"
#include "exchange.h"
#include "identity.h"
#include "buffer.h"
#include "scheme.h"
#include "errlog.h"

#ifdef NEED_STRSEP
#include "strsep.h"
#endif

static struct identity *idob = NULL;

static union {
     MD5_CTX md5ctx;
     SHA1_CTX sha1ctx;
} Ctx, Ctx2;

/* Identity transforms */
/* XXX - argh, cast the funtions */

static struct idxform idxform[] = {
     { HASH_MD5, 5, MD5_SIZE, (void *)&Ctx.md5ctx, 
       sizeof(MD5_CTX), (void *)&Ctx2.md5ctx,
       (void (*)(void *))MD5Init, 
       (void (*)(void *, unsigned char *, unsigned int))MD5Update, 
       (void (*)(unsigned char *, void *))MD5Final },
     { HASH_SHA1, 6, SHA1_SIZE, (void *)&Ctx.sha1ctx, 
       sizeof(SHA1_CTX), (void *)&Ctx2.sha1ctx,
       (void (*)(void *))SHA1Init, 
       (void (*)(void *, unsigned char *, unsigned int))SHA1Update, 
       (void (*)(unsigned char *, void *))SHA1Final },
};

int
init_identities(char *name, struct identity *root)
{
     FILE *fp;
     char *p, *p2, *file = secret_file;
     struct identity *tmp, **ob;
     struct passwd *pwd;
     struct stat sb;
     int type;

     if (name != NULL) {
	  ob = (struct identity **)&root->object;
	  file = name;
     } else
	  ob = &idob;

     if (lstat(file, &sb) == -1) {
	  log_error(1, "lstat() on %s in init_identities()", file);
	  return -1;
     }
     if (((sb.st_mode & S_IFMT) & ~S_IFREG)) {
	  log_error(0, "no regular file %s in init_identities()", file);
	  return -1;
     }
     fp = fopen(file, "r");
     if (fp == (FILE *) NULL)
     {
	  log_error(1, "no hash secrets file %s", file);
	  return -1;
     }

#ifdef DEBUG
     if (name == NULL)
	  printf("[Reading identities + secrets]\n");
#endif

     while(fgets(buffer, BUFFER_SIZE,fp)) {
	  p=buffer;
	  while(isspace(*p))  /* Get rid of leading spaces */
	       p++;
	  if(*p == '#')       /* Ignore comments */
	       continue;
	  if(!strlen(p))
	       continue;

	  if (!strncmp(p, IDENT_LOCAL, strlen(IDENT_LOCAL))) {
	       type = ID_LOCAL;
	       p += strlen(IDENT_LOCAL);
	  } else if (!strncmp(p, IDENT_LOCALPAIR, strlen(IDENT_LOCALPAIR))) {
	       type = ID_LOCALPAIR;
	       p += strlen(IDENT_LOCALPAIR);
	  } else if (!strncmp(p, IDENT_REMOTE, strlen(IDENT_REMOTE))) {
	       type = ID_REMOTE;
	       p += strlen(IDENT_REMOTE);
	  } else if (!strncmp(p, IDENT_LOOKUP, strlen(IDENT_LOOKUP))) {
	       type = ID_LOOKUP;
	       p += strlen(IDENT_LOOKUP);
	  } else {
	       log_error(0, "Unkown tag %s in %s", p, file);
	       continue;
	  }
		
	  if ((tmp = identity_new()) == NULL) {
	       log_error(0, "identity_new() in init_identities()");
	       continue;
	  }

	  p2 = p;
	  if (!isspace(*p2))
	       continue;
	  
	  /* Tokens are braced with "token" */
	  if((p=strsep(&p2, "\"\'")) == NULL || 
	     (p=strsep(&p2, "\"\'")) == NULL)
	       continue;

	  tmp->type = type;
	  tmp->tag = strdup(p);
	  tmp->root = root;

	  switch(type) {
	  case ID_LOCAL:
	  case ID_REMOTE:
	       if (type == ID_REMOTE) {
		    /* Search for duplicates */
		    if (identity_find(idob, tmp->tag, ID_REMOTE) != NULL) {
			 log_error(0, "Duplicate id \"%s\" found in %s",
				   tmp->tag, name != NULL ? name : "root");
			 identity_value_reset(tmp);
			 continue;
		    }
	       }
	       /* Tokens are braced with "token" */
	       if((p=strsep(&p2, "\"\'")) == NULL || 
		  (p=strsep(&p2, "\"\'")) == NULL) {
		    identity_value_reset(tmp);
		    continue;
	       }
	       tmp->object = strdup(p);
	       break;
	  case ID_LOCALPAIR:
	       /* Tokens are braced with "token" */
	       if((p=strsep(&p2, "\"\'")) == NULL || 
		  (p=strsep(&p2, "\"\'")) == NULL) {
		    identity_value_reset(tmp);
		    continue;
	       }
	       tmp->pairid = strdup(p);
	       /* Tokens are braced with "token" */
	       if((p=strsep(&p2, "\"\'")) == NULL || 
		  (p=strsep(&p2, "\"\'")) == NULL) {
		    identity_value_reset(tmp);
		    continue;
	       }
	       tmp->object = strdup(p);
	       break;
	  case ID_LOOKUP:
	       if (name != NULL) {
		    log_error(0, "lookup in user file %s in init_identities()",
			      name);
		    continue;
	       }
	       while(isspace(*p2)) p2++;

	       while(isspace(p2[strlen(p2)-1]))
		    p2[strlen(p2)-1] = 0;

	       if ((pwd = getpwnam(p2)) == NULL) {
		    log_error(1, "getpwnam() in init_identities()");
		    identity_value_reset(tmp);
		    continue;
	       } else {
		    char *dir = calloc(strlen(PHOTURIS_USER_SECRET)+
				       strlen(pwd->pw_dir) + 2,
				       sizeof(char));

		    /* This is the user name */
		    tmp->pairid = strdup(p2);

		    if (dir == NULL) {
			 log_error(1, "calloc() in init_identities()");
			 identity_value_reset(tmp);
			 continue;
		    }
		    sprintf(dir,"%s/%s", pwd->pw_dir, PHOTURIS_USER_SECRET);
		    if (init_identities(dir, (struct identity *)tmp) == -1) {
			 free(dir);
			 identity_value_reset(tmp);
			 continue;
		    }

		    free(dir);
	       }
	       break;
	  }
	  identity_insert(ob, tmp);
     }
     fclose(fp);

     return 0;
}

/* 
 * Get shared symmetric keys and identity, put the values in
 * the state object. If a SPI User ident is given, we look up
 * the matching remote secret.
 */

int
get_secrets(struct stateob *st, int mode)
{
     u_int8_t local_ident[MAX_IDENT];
     u_int8_t local_secret[MAX_IDENT_SECRET];
     u_int8_t remote_secret[MAX_IDENT_SECRET]; 

     struct identity *id, *root = idob;

     local_ident[0] = '\0';
     local_secret[0] = '\0';
     remote_secret[0] = '\0';

     /* 
      * Remote secret first, if we find the remote secret in 
      * a user secret file, we restrict our local searches
      * to that tree.
      */

     if(st->uSPIident != NULL && st->uSPIsecret == NULL && 
	(mode & ID_REMOTE)) {
	  int skip;

	  if (st->uSPIident[0] == 255 && st->uSPIident[1] == 255) 
	       skip = 8; 
	  else if (st->uSPIident[0] == 255) 
	       skip = 4; 
	  else 
	       skip = 2; 

	  id = identity_find(root, st->uSPIident+skip, ID_REMOTE);
	  if (id != NULL) {
               strncpy(remote_secret, id->object, MAX_IDENT_SECRET-1); 
               remote_secret[MAX_IDENT_SECRET-1] = '\0';  
 
	       if (id->root)
		    root = (struct identity *)id->root->object;
	  }
     }
     
     if (st->user != NULL && 
	 (id = identity_find(idob, st->user, ID_LOOKUP)) != NULL) {
	  /* User keying */
	  id = identity_find((struct identity *)id->object, NULL, ID_LOCAL);
     } else
	  id = NULL;

     if (id == NULL) {
	  /* Host keying */
	  id = identity_find(root, NULL, ID_LOCAL);
     }

      if (id != NULL && (mode & (ID_LOCAL|ID_LOCALPAIR))) {
	  /* Namespace: root->tag + user->tag */
	  if (id->root) {
	       strncpy(local_ident, id->root->tag, MAX_IDENT-1);
	       local_ident[MAX_IDENT-1] = '\0';
	  }
	  strncpy(local_ident+strlen(local_ident), id->tag, 
		  MAX_IDENT-1-strlen(local_ident));
	  local_ident[MAX_IDENT_SECRET-1] = '\0'; 

	  strncpy(local_secret, id->object, MAX_IDENT_SECRET-1);
	  local_secret[MAX_IDENT_SECRET-1] = '\0'; 
     }
     if (st->uSPIident != NULL && st->oSPIident == NULL && 
	 (mode & (ID_LOCAL|ID_LOCALPAIR))) {
	  int skip;
	  if (st->uSPIident[0] == 255 && st->uSPIident[1] == 255)
	       skip = 8;
	  else if (st->uSPIident[0] == 255)
	       skip = 4;
	  else
	       skip = 2;

	  id = identity_find(root, st->uSPIident+skip, ID_LOCALPAIR);
	  if (id != NULL) {
	       local_ident[0] = '\0';
	       /* Namespace: root->tag + user->tag */
	       if (id->root) {
		    strncpy(local_ident, id->root->tag, MAX_IDENT-1);
		    local_ident[MAX_IDENT-1] = '\0';
	       }
	       strncpy(local_ident+strlen(local_ident), id->pairid, 
		       MAX_IDENT-1-strlen(local_ident));
               local_ident[MAX_IDENT-1] = '\0'; 
 
               strncpy(local_secret, id->object, MAX_IDENT_SECRET-1); 
               local_secret[MAX_IDENT_SECRET-1] = '\0';  
	  }
     }
	  
     if((strlen(remote_secret) == 0 && (mode & ID_REMOTE)) ||
	(strlen(local_ident) == 0 && (mode & (ID_LOCAL|ID_LOCALPAIR))) ) {
	  log_error(0, "Can't find identities or secrets in get_secrets()");
	  return -1;
     }

     if(st->oSPIident == NULL && (mode & (ID_LOCAL|ID_LOCALPAIR))) {
	  st->oSPIident = calloc(2+strlen(local_ident)+1,sizeof(u_int8_t));
	  if(st->oSPIident == NULL)
	       return -1; 
	  strcpy(st->oSPIident+2,local_ident);
	  st->oSPIident[0] = ((strlen(local_ident)+1) >> 5) & 0xFF;
	  st->oSPIident[1] = ((strlen(local_ident)+1) << 3) & 0xFF;

	  st->oSPIsecretsize = strlen(local_secret);
	  st->oSPIsecret = calloc(st->oSPIsecretsize,sizeof(u_int8_t));
	  if(st->oSPIsecret == NULL)
	       return -1; 
	  strncpy(st->oSPIsecret, local_secret, st->oSPIsecretsize);
     }
     if(st->uSPIident != NULL && st->uSPIsecret == NULL && 
	(mode & ID_REMOTE)) {
	  st->uSPIsecretsize = strlen(remote_secret);
          st->uSPIsecret = calloc(st->uSPIsecretsize,sizeof(u_int8_t)); 
          if(st->uSPIsecret == NULL) 
               return -1;  
          strncpy(st->uSPIsecret, remote_secret, st->uSPIsecretsize); 
     } 
     return 0;
}

int
choose_identity(struct stateob *st, u_int8_t *packet, u_int16_t *size,
		 u_int8_t *attributes, u_int16_t attribsize)
{
     u_int16_t rsize, asize, tmp;
     attrib_t *ob;
     int mode = 0;
     rsize = *size;

     /* XXX - preference of identity choice ? */
     tmp = 0;
     while(attribsize>0) {
	  /* Check if we support this identity choice */
	  if ((ob = getattrib(*attributes)) != NULL &&
	      (ob->type & AT_ID))
	       break;

	  if(attribsize -(*(attributes+1)+2) > attribsize) {
	       attribsize=0;
	       break;
	  }
	  attribsize -= *(attributes+1)+2;
	  attributes += *(attributes+1)+2;
     }

     if(attribsize == 0) {
	  log_error(0, "No identity choice found in offered attributes "
		    "in choose_identity()");
	  return -1;
     }
     
     if(rsize < *(attributes+1)+2)
	  return -1;

     asize = *(attributes+1)+2;
     rsize -= asize;
     bcopy(attributes, packet, asize);

     /* Now put identity in state object */
     if (st->oSPIidentchoice == NULL) {
	  if ((st->oSPIidentchoice = calloc(asize, sizeof(u_int8_t))) == NULL)
	       return -1;
	  bcopy(attributes, st->oSPIidentchoice, asize);
	  st->oSPIidentchoicesize = asize;
     }

     packet += asize;

     /* Choose identity and secrets for Owner and User */
     if (st->uSPIsecret == NULL && st->uSPIident != NULL)
	  mode |= ID_REMOTE;
     if (st->oSPIsecret == NULL)
	  mode |= ID_LOCAL;
     if(get_secrets(st, mode) == -1)
	  return -1;

     /* oSPIident is varpre already */
     tmp = varpre2octets(st->oSPIident);
     if(rsize < tmp)
	  return -1;
     
     bcopy(st->oSPIident, packet, tmp);

     *size = asize + tmp;

     return 0;
}


u_int16_t
get_identity_verification_size(struct stateob *st, u_int8_t *choice)
{
     struct idxform *hash;

     if ((hash = get_hash_id(*choice)) == NULL) {
	  log_error(0, "Unknown identity choice: %d\n", *choice);
	  return 0;
     }

     return hash->hashsize+2;
}

/*
 * Gets a hash corresponding with a Photuris ID
 */

struct idxform *get_hash_id(int id)
{
     int i;
     for (i=0; i<sizeof(idxform)/sizeof(idxform[0]); i++)
	  if (id == idxform[i].id)
	       return &idxform[i];
     return NULL;
}

struct idxform *get_hash(enum hashes hashtype)
{
     int i;
     for (i=0; i<sizeof(idxform)/sizeof(idxform[0]); i++)
	  if (hashtype == idxform[i].type)
	       return &idxform[i];
     log_error(0, "Unkown hash type: %d in get_hash()", hashtype);
     return NULL;
}

int
create_verification_key(struct stateob *st, u_int8_t *buffer, u_int16_t *size,
			int owner)
{
     struct idxform *hash;
     int id = owner ? *(st->oSPIidentchoice) : *(st->uSPIidentchoice);

     if ((hash = get_hash_id(id)) == NULL) {
	  log_error(0, "Unkown identity choice %d in create_verification_key", id);
          return -1; 
     }

     if (*size < hash->hashsize)
	  return -1;

     hash->Init(hash->ctx);
     if (owner)
	  hash->Update(hash->ctx, st->oSPIsecret, st->oSPIsecretsize);
     else
	  hash->Update(hash->ctx, st->uSPIsecret, st->uSPIsecretsize);

     hash->Update(hash->ctx, st->shared, st->sharedsize);
     hash->Final(buffer, hash->ctx);
     *size = hash->hashsize;

     return 0;
}

int
create_identity_verification(struct stateob *st, u_int8_t *buffer, 
			     u_int8_t *packet, u_int16_t size)
{
     int hash_size;
     struct idxform *hash;

     if ((hash = get_hash_id(*(st->oSPIidentchoice))) == NULL) {
	  log_error(0, "Unkown identity choice %d in create_verification_key",
		    *(st->oSPIidentchoice));
          return 0; 
     }

     hash_size = idsign(st, hash, buffer+2, packet,size);

     if(hash_size) {
	  /* Create varpre number from digest */
	  buffer[0] = hash_size >> 5 & 0xFF;
	  buffer[1] = hash_size << 3 & 0xFF;

	  if(st->oSPIidentver != NULL)
	       free(st->oSPIidentver);

	  st->oSPIidentver = calloc(hash_size+2,sizeof(u_int8_t));
	  if(st->oSPIidentver == NULL) {
	       log_error(1, "Not enough memory in create_identity_verification()", 0);
	       return 0;
	  }

	  bcopy(buffer, st->oSPIidentver, hash_size+2);
	  st->oSPIidentversize = hash_size+2;

	  state_save_verification(st, st->oSPIidentver, hash_size+2);
     }
     return hash_size+2;
}

int 
verify_identity_verification(struct stateob *st, u_int8_t *buffer,  
			     u_int8_t *packet, u_int16_t size) 
{ 
     struct idxform *hash;

     if ((hash = get_hash_id(*(st->uSPIidentchoice))) == NULL) {
	  log_error(0, "Unkown identity choice %d in create_verification_key",
		    *(st->uSPIidentchoice));
          return 0; 
     }

     if (varpre2octets(buffer) != hash->hashsize +2)
	  return 0;

     state_save_verification(st, buffer, hash->hashsize+2);

     return idverify(st, hash, buffer+2, packet, size); 
} 


int
idsign(struct stateob *st, struct idxform *hash, u_int8_t *signature,  
       u_int8_t *packet, u_int16_t psize) 
{
     u_int8_t key[HASH_MAX];
     u_int16_t keylen = HASH_MAX;

     create_verification_key(st, key, &keylen, 1); /* Owner direction */

     hash->Init(hash->ctx);

     /* Our verification key */
     hash->Update(hash->ctx, key, keylen); 
     /* Key fill */
     hash->Final(NULL, hash->ctx);

     /* 
      * Hash Cookies, type, lifetime + spi fields +
      * SPI owner Identity Choice + Identity 
      */
     hash->Update(hash->ctx, packet, IDENTITY_MESSAGE_MIN + 
		  st->oSPIidentchoicesize + varpre2octets(st->oSPIident));

     if(st->uSPIident != NULL) {
	  hash->Update(hash->ctx, st->uSPIidentver, st->uSPIidentversize);
     }

     /* Hash attribute choice, padding */
     packet += IDENTITY_MESSAGE_MIN;
     psize -= IDENTITY_MESSAGE_MIN + packet[1] + 2;
     packet += packet[1] + 2;
     psize -= varpre2octets(packet) + 2 + hash->hashsize;
     packet += varpre2octets(packet) + 2 + hash->hashsize;

     hash->Update(hash->ctx, packet, psize);

     /* Our exchange value */
     hash->Update(hash->ctx, st->oSPITBV, 3);
     hash->Update(hash->ctx, st->exchangevalue, st->exchangesize); 
     hash->Update(hash->ctx, st->oSPIoattrib, st->oSPIoattribsize);

     /* Their exchange value */
     hash->Update(hash->ctx, st->uSPITBV, 3);
     hash->Update(hash->ctx, st->texchange, st->texchangesize);
     hash->Update(hash->ctx, st->uSPIoattrib, st->uSPIoattribsize);

     /* Responder offered schemes */
     hash->Update(hash->ctx, st->roschemes, st->roschemesize);

     /* Data fill */
     hash->Final(NULL, hash->ctx);

     /* And finally the trailing key */
     hash->Update(hash->ctx, key, keylen);

     hash->Final(signature, hash->ctx);

     return hash->hashsize;
}

int
idverify(struct stateob *st, struct idxform *hash, u_int8_t *signature,   
	 u_int8_t *packet, u_int16_t psize)
{
     u_int8_t digest[HASH_MAX];
     u_int8_t key[HASH_MAX];
     u_int16_t keylen = HASH_MAX;
     struct identity_message *p = (struct identity_message *)packet;

     create_verification_key(st, key, &keylen, 0); /* User direction */
 
     hash->Init(hash->ctx); 
 
     /* Their verification key */
     hash->Update(hash->ctx, key, keylen); 
     /* Key fill */
     hash->Final(NULL, hash->ctx);
 
     /* 
      * Hash Cookies, type, lifetime + spi fields +
      * SPI owner Identity Choice + Identity 
      */
     hash->Update(hash->ctx, packet, IDENTITY_MESSAGE_MIN +
		  st->uSPIidentchoicesize + varpre2octets(st->uSPIident)); 

     /* Determine if the sender knew our secret already */
     if(p->type != IDENTITY_REQUEST) {
	  hash->Update(hash->ctx, st->oSPIidentver, st->oSPIidentversize); 
     }
 
     packet += IDENTITY_MESSAGE_MIN;
     psize -= IDENTITY_MESSAGE_MIN + packet[1] + 2;
     packet += packet[1] + 2;
     psize -= varpre2octets(packet) + 2 + hash->hashsize;
     packet += varpre2octets(packet) + 2 + hash->hashsize;
     hash->Update(hash->ctx, packet, psize);

     /* Their exchange value */ 
     hash->Update(hash->ctx, st->uSPITBV, 3);
     hash->Update(hash->ctx, st->texchange, st->texchangesize); 
     hash->Update(hash->ctx, st->uSPIoattrib, st->uSPIoattribsize); 
 
     /* Our exchange value */ 
     hash->Update(hash->ctx, st->oSPITBV, 3);
     hash->Update(hash->ctx, st->exchangevalue, st->exchangesize);  
     hash->Update(hash->ctx, st->oSPIoattrib, st->oSPIoattribsize); 

     /* Responder offered schemes */
     hash->Update(hash->ctx, st->roschemes, st->roschemesize); 
 
     /* Data fill */
     hash->Final(NULL, hash->ctx); 

     /* And finally the trailing key */
     hash->Update(hash->ctx, key, keylen);

     hash->Final(digest, hash->ctx); 

     return !bcmp(digest, signature, hash->hashsize);
}

/* Functions for handling the linked list of identities */

int
identity_insert(struct identity **idob, struct identity *ob)
{
     struct identity *tmp;

     ob->next = NULL;

     if(*idob == NULL) {
	  *idob = ob;
	  return 1;
     }
     
     tmp=*idob;
     while(tmp->next!=NULL)
	  tmp = tmp->next;

     tmp->next = ob;
     return 1;
}

int
identity_unlink(struct identity **idob, struct identity *ob)
{
     struct identity *tmp;
     if(*idob == ob) {
	  *idob = ob->next;
	  free(ob);
	  return 1;
     }

     for(tmp=*idob; tmp!=NULL; tmp=tmp->next) {
	  if(tmp->next==ob) {
	       tmp->next=ob->next;
	       free(ob);
	       return 1;
	  }
     }
     return 0;
}

struct identity *
identity_new(void)
{
     struct identity *p;

     if((p = calloc(1, sizeof(struct identity)))==NULL)
	  return NULL;

     return p;
}

int
identity_value_reset(struct identity *ob)
{ 
     if (ob->tag != NULL)
	  free(ob->tag);
     if (ob->pairid != NULL)
	  free(ob->pairid);
     if (ob->object != NULL)
	  free(ob->object);

     return 1;
}

/* 
 * find the state ob with matching address
 */

struct identity *
identity_root(void)
{
     return idob;
}

/* On ID_LOOKUP match pairid, on ID_LOCAL only match type */

struct identity *
identity_find(struct identity *idob, char *id, int type)
{
     struct identity *tmp = idob, *p;
     while(tmp!=NULL) {
          if(((type == ID_LOCAL && id == NULL) ||
	     (type != ID_LOOKUP && !strcmp(id, tmp->tag)) ||
	     (type == ID_LOOKUP && tmp->pairid != NULL && !strcmp(id, tmp->pairid))) &&
	     type == tmp->type)
		    return tmp;
	  if (tmp->type == ID_LOOKUP && tmp->object != NULL) {
	       p = identity_find((struct identity *)tmp->object, id, type);
	       if (p != NULL)
		    return p;
	  }
	  tmp = tmp->next;
     }
     return NULL;
}

void
identity_cleanup(struct identity **root)
{
     struct identity *p;
     struct identity *tmp;

     if (root == NULL)
	  tmp = idob;
     else
	  tmp = *root;

     while(tmp!=NULL) {
	  if (tmp->type == ID_LOOKUP)
	       identity_cleanup((struct identity **)&tmp->object);
	  p = tmp;
	  tmp = tmp->next;
	  identity_value_reset(p);
	  free(p);
     }

     if (root != NULL)
	  *root = NULL;
     else
	  idob = NULL;
}
