/* Support of X.509 attribute certificates
 * Copyright (C) 2002 Ueli Galizzi, Ariane Seiler
 * Copyright (C) 2003 Martin Berner, Lukas Suter
 *
 * 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.
 *
 * RCSID $Id$
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <sys/types.h>

#include <freeswan.h>

#include "constants.h"
#include "defs.h"
#include "asn1.h"
#include <asn1/oid.h>
#include "ac.h"
#include "x509.h"
#include "crl.h"
#include "ca.h"
#include "certs.h"
#include "log.h"
#include "whack.h"
#include "fetch.h"

/* chained list of X.509 attribute certificates */

static x509acert_t *x509acerts   = NULL;

/* chained list of ietfAttributes */

static ietfAttrList_t *ietfAttributes = NULL;

/* ASN.1 definition of ietfAttrSyntax */

static const asn1Object_t ietfAttrSyntaxObjects[] =
{
  { 0, "ietfAttrSyntax",		ASN1_SEQUENCE,        ASN1_NONE }, /*  0 */
  { 1,   "policyAuthority",		ASN1_CONTEXT_C_0,     ASN1_OPT |
							      ASN1_BODY }, /*  1 */
  { 1,   "end opt",			ASN1_EOC,             ASN1_END  }, /*  2 */
  { 1,   "values",			ASN1_SEQUENCE,        ASN1_LOOP }, /*  3 */
  { 2,     "octets",			ASN1_OCTET_STRING,    ASN1_OPT |
							      ASN1_BODY }, /*  4 */
  { 2,     "end choice",		ASN1_EOC,             ASN1_END  }, /*  5 */
  { 2,     "oid",			ASN1_OID,	      ASN1_OPT |
							      ASN1_BODY }, /*  6 */
  { 2,     "end choice",		ASN1_EOC,             ASN1_END  }, /*  7 */
  { 2,     "string",			ASN1_UTF8STRING,      ASN1_OPT |
							      ASN1_BODY }, /*  8 */
  { 2,     "end choice",		ASN1_EOC,             ASN1_END  }, /*  9 */
  { 1,   "end loop",			ASN1_EOC,	      ASN1_END  }  /* 10 */
};

#define IETF_ATTR_OCTETS	 4
#define IETF_ATTR_OID		 6
#define IETF_ATTR_STRING	 8
#define IETF_ATTR_ROOF		11

/* ASN.1 definition of roleSyntax */

static const asn1Object_t roleSyntaxObjects[] =
{
  { 0, "roleSyntax",			ASN1_SEQUENCE,        ASN1_NONE }, /*  0 */
  { 1,   "roleAuthority",		ASN1_CONTEXT_C_0,     ASN1_OPT |
							      ASN1_OBJ  }, /*  1 */
  { 1,   "end opt",			ASN1_EOC,             ASN1_END  }, /*  2 */
  { 1,   "roleName",			ASN1_CONTEXT_C_1,     ASN1_OBJ  }  /*  3 */
};

#define ROLE_ROOF		4

/* ASN.1 definition of an X509 attribute certificate */

static const asn1Object_t acObjects[] =
{
  { 0, "AttributeCertificate",		ASN1_SEQUENCE,        ASN1_OBJ  }, /*  0 */
  { 1,   "AttributeCertificateInfo",    ASN1_SEQUENCE,        ASN1_OBJ  }, /*  1 */
  { 2,	   "version",			ASN1_INTEGER,	      ASN1_DEF |
							      ASN1_BODY }, /*  2 */
  { 2,	   "holder",                    ASN1_SEQUENCE,	      ASN1_NONE }, /*  3 */
  { 3,	     "baseCertificateID",	ASN1_CONTEXT_C_0,     ASN1_OPT  }, /*  4 */
  { 4,	       "issuer",		ASN1_SEQUENCE,	      ASN1_OBJ  }, /*  5 */
  { 4,	       "serial",		ASN1_INTEGER,	      ASN1_BODY }, /*  6 */
  { 4,         "issuerUID",		ASN1_BIT_STRING,      ASN1_OPT |
                                                              ASN1_BODY }, /*  7 */
  { 4,         "end opt",		ASN1_EOC,             ASN1_END  }, /*  8 */
  { 3,       "end opt",			ASN1_EOC,             ASN1_END  }, /*  9 */
  { 3,	     "entityName",		ASN1_CONTEXT_C_1,     ASN1_OPT |
							      ASN1_OBJ  }, /* 10 */
  { 3,       "end opt",			ASN1_EOC,             ASN1_END  }, /* 11 */
  { 3,	     "objectDigestInfo",	ASN1_CONTEXT_C_2,     ASN1_OPT  }, /* 12 */
  { 4,	       "digestedObjectType",	ASN1_ENUMERATED,      ASN1_BODY }, /* 13*/
  { 4,	       "otherObjectTypeID",	ASN1_OID,    	      ASN1_OPT |
							      ASN1_BODY }, /* 14 */
  { 4,         "end opt",		ASN1_EOC,             ASN1_END  }, /* 15*/
  { 4,         "digestAlgorithm",	ASN1_EOC,             ASN1_RAW  }, /* 16 */
  { 3,       "end opt",			ASN1_EOC,             ASN1_END  }, /* 17 */
  { 2,	   "v2Form",			ASN1_CONTEXT_C_0,     ASN1_NONE }, /* 18 */
  { 3,	     "issuerName",		ASN1_SEQUENCE,        ASN1_OPT |
                                                              ASN1_OBJ  }, /* 19 */
  { 3,       "end opt",			ASN1_EOC,             ASN1_END  }, /* 20 */
  { 3,	     "baseCertificateID",	ASN1_CONTEXT_C_0,     ASN1_OPT  }, /* 21 */
  { 4,	       "issuerSerial",		ASN1_SEQUENCE,        ASN1_NONE }, /* 22 */
  { 5,	         "issuer",		ASN1_SEQUENCE,	      ASN1_OBJ  }, /* 23 */
  { 5,	  	 "serial",		ASN1_INTEGER,	      ASN1_BODY }, /* 24 */
  { 5,           "issuerUID",		ASN1_BIT_STRING,      ASN1_OPT |
                                                              ASN1_BODY }, /* 25 */
  { 5,           "end opt",		ASN1_EOC,             ASN1_END  }, /* 26 */
  { 3,       "end opt",			ASN1_EOC,             ASN1_END  }, /* 27 */
  { 3,       "objectDigestInfo",	ASN1_CONTEXT_C_1,     ASN1_OPT  }, /* 28 */
  { 4,	       "digestInfo",		ASN1_SEQUENCE,        ASN1_OBJ  }, /* 29 */
  { 5,  	 "digestedObjectType",	ASN1_ENUMERATED,      ASN1_BODY }, /* 30 */
  { 5,	  	 "otherObjectTypeID",	ASN1_OID,    	      ASN1_OPT |
							      ASN1_BODY }, /* 31 */
  { 5,           "end opt",		ASN1_EOC,             ASN1_END  }, /* 32 */
  { 5,           "digestAlgorithm",	ASN1_EOC,             ASN1_RAW  }, /* 33 */
  { 3,       "end opt",			ASN1_EOC,             ASN1_END  }, /* 34 */
  { 2,	   "signature",                 ASN1_EOC,             ASN1_RAW  }, /* 35 */
  { 2,	   "serialNumber",              ASN1_INTEGER,         ASN1_BODY }, /* 36 */
  { 2,	   "attrCertValidityPeriod",    ASN1_SEQUENCE,        ASN1_NONE }, /* 37 */
  { 3,	     "notBeforeTime",           ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 38 */
  { 3,	     "notAfterTime",            ASN1_GENERALIZEDTIME, ASN1_BODY }, /* 39 */
  { 2,	   "attributes",                ASN1_SEQUENCE,        ASN1_LOOP }, /* 40 */
  { 3,       "attribute",		ASN1_SEQUENCE,        ASN1_NONE }, /* 41 */
  { 4,         "type",			ASN1_OID,             ASN1_BODY }, /* 42 */
  { 4,         "values",		ASN1_SET, 	      ASN1_LOOP }, /* 43 */
  { 5,           "value",		ASN1_EOC, 	      ASN1_RAW  }, /* 44 */
  { 4, 	       "end loop",		ASN1_EOC,	      ASN1_END  }, /* 45 */
  { 2,     "end loop",			ASN1_EOC,             ASN1_END  }, /* 46 */
  { 2,     "extensions",		ASN1_SEQUENCE,        ASN1_LOOP }, /* 47 */
  { 3,       "extension",		ASN1_SEQUENCE,        ASN1_NONE }, /* 48 */
  { 4,         "extnID",		ASN1_OID,             ASN1_BODY }, /* 49 */
  { 4,         "critical",		ASN1_BOOLEAN,         ASN1_DEF |
							      ASN1_BODY }, /* 50 */
  { 4,         "extnValue",		ASN1_OCTET_STRING,    ASN1_BODY }, /* 51 */
  { 2,     "end loop",			ASN1_EOC,             ASN1_END  }, /* 52 */
  { 1,   "signatureAlgorithm",		ASN1_EOC,             ASN1_RAW  }, /* 53 */
  { 1,   "signatureValue",		ASN1_BIT_STRING,      ASN1_BODY }  /* 54 */
};

#define AC_OBJ_CERTIFICATE		 0
#define AC_OBJ_CERTIFICATE_INFO		 1
#define AC_OBJ_VERSION			 2
#define AC_OBJ_HOLDER_ISSUER		 5
#define AC_OBJ_HOLDER_SERIAL		 6
#define AC_OBJ_ENTITY_NAME		10
#define AC_OBJ_ISSUER_NAME		19
#define AC_OBJ_ISSUER			23
#define AC_OBJ_SIG_ALG			35
#define AC_OBJ_SERIAL_NUMBER		36
#define AC_OBJ_NOT_BEFORE		38
#define AC_OBJ_NOT_AFTER		39
#define AC_OBJ_ATTRIBUTE_TYPE		42
#define AC_OBJ_ATTRIBUTE_VALUE		44
#define AC_OBJ_EXTN_ID			49
#define AC_OBJ_CRITICAL			50
#define AC_OBJ_EXTN_VALUE		51
#define AC_OBJ_ALGORITHM		53
#define AC_OBJ_SIGNATURE		54
#define AC_OBJ_ROOF			55

const x509acert_t empty_ac = {
      NULL     , /* *next */
            0  , /* installed */
    { NULL, 0 }, /* certificate */
    { NULL, 0 }, /*   certificateInfo */
            1  , /*     version */
		 /*     holder */
		 /*       baseCertificateID */
    { NULL, 0 }, /*         holderIssuer */
    { NULL, 0 }, /*         holderSerial */
		 /*       entityName */
    { NULL, 0 }, /*         generalNames */
		 /*     v2Form */
    { NULL, 0 }, /*       issuerName */
                 /*     signature */
    OID_UNKNOWN, /*       sigAlg */
    { NULL, 0 }, /*     serialNumber */
                 /*     attrCertValidityPeriod */
            0  , /*       notBefore */
            0  , /*       notAfter */
		 /*     attributes */
      NULL     , /*       charging */
      NULL     , /*       groups */
		 /*     extensions */
    { NULL, 0 }, /*       authKeyID */
    { NULL, 0 }, /*       authKeySerialNumber */
      FALSE    , /*       noRevAvail */
		 /*   signatureAlgorithm */
    OID_UNKNOWN, /*     algorithm */
    { NULL, 0 }, /*   signature */
};


/*  compare two ietfAttributes, returns zero if a equals b
 *  negative/positive if a is earlier/later in the alphabet than b
 */
static int
cmp_ietfAttr(ietfAttr_t *a,ietfAttr_t *b)
{
    int cmp_len, len, cmp_value;

    /* cannot compare OID with STRING or OCTETS attributes */
    if (a->kind == IETF_ATTRIBUTE_OID && b->kind != IETF_ATTRIBUTE_OID)
	return 1;
	
    cmp_len = a->value.len - b->value.len;
    len = (cmp_len < 0)? a->value.len : b->value.len;
    cmp_value = memcmp(a->value.ptr, b->value.ptr, len);

    return (cmp_value == 0)? cmp_len : cmp_value;
}

/*
 *  add an ietfAttribute to the chained list
 */
static ietfAttr_t*
add_ietfAttr(ietfAttr_t *attr)
{
    ietfAttrList_t **listp = &ietfAttributes;
    ietfAttrList_t *list = *listp;
    int cmp = -1;

    while (list != NULL)
    {
	cmp = cmp_ietfAttr(attr, list->attr);
	if (cmp <= 0)
	    break;
	listp = &list->next;
	list = *listp;
    }

    if (cmp == 0)
    {
	/* attribute already exists, increase count */
	pfree(attr);
	list->attr->count++;
	return list->attr;
    }
    else
    {
	ietfAttrList_t *el = alloc_thing(ietfAttrList_t, "ietfAttrList");

	/* new attribute, unshare value */
	attr->value.ptr = clone_bytes(attr->value.ptr, attr->value.len
	    , "attr value");
	attr->count = 1;
	time(&attr->installed);

	el->attr = attr;
	el->next = list;
	*listp = el;

	return attr;
    }
}

/*
 * decodes a comma separated list of group attributes
 */
void
decode_groups(char *groups, ietfAttrList_t **listp)
{
    if (groups == NULL)
	return;

    while (strlen(groups) > 0)
    {
	char *end;
	char *next = strchr(groups, ',');

	if (next == NULL)
	   end = next = groups + strlen(groups);
	else
	   end = next++;

	/* eat preceeding whitespace */
	while (groups < end && *groups == ' ')
	    groups++;

	/* eat trailing whitespace */
	while (end > groups && *(end-1) == ' ')
	    end--;

	if (groups < end)
	{
	    ietfAttr_t *attr   = alloc_thing(ietfAttr_t, "ietfAttr");
	    ietfAttrList_t *el = alloc_thing(ietfAttrList_t, "ietfAttrList");

	    attr->kind  = IETF_ATTRIBUTE_STRING;
	    attr->value.ptr = groups;
	    attr->value.len = end - groups;
	    attr->count = 0;

	    el->attr = add_ietfAttr(attr);
	    el->next = *listp;
	    *listp = el;
	}

	groups = next;
    }
}

static bool
same_attribute(const ietfAttr_t *a, const ietfAttr_t *b)
{
    return (a->kind == b->kind && a->value.len == b->value.len
	   && memcmp(a->value.ptr, b->value.ptr, b->value.len) == 0);
}

bool
group_membership(const ietfAttrList_t *peer_list
	       , const char *conn
	       , const ietfAttrList_t *conn_list)
{
    if (conn_list == NULL)
	return TRUE;

    while (peer_list != NULL)
    {
	const ietfAttr_t *peer_attr = peer_list->attr;
	const ietfAttrList_t *list = conn_list;

	while (list != NULL)
	{
	    ietfAttr_t *conn_attr = list->attr;

	    if (same_attribute(conn_attr, peer_attr))
	    {
		DBG(DBG_CONTROL,
		    DBG_log("%s: peer matches group '%.*s'"
			, conn
			, (int)peer_attr->value.len, peer_attr->value.ptr)
		)
		return TRUE;
	    }
	    list = list->next;
	}
	peer_list = peer_list->next;
    }
	DBG(DBG_CONTROL,
	    DBG_log("%s: peer doesn't match any group", conn)
	)
    return FALSE;
}


void
unshare_ietfAttrList(ietfAttrList_t **listp)
{
    ietfAttrList_t *list = *listp;

    while (list != NULL)
    {
	ietfAttrList_t *el = alloc_thing(ietfAttrList_t, "ietfAttrList");

	el->attr = list->attr;
	el->attr->count++;
	el->next = NULL;
	*listp = el;
	listp = &el->next;
	list = list->next;
    }
}

/*
 * parses ietfAttrSyntax
 */
static ietfAttrList_t*
parse_ietfAttrSyntax(chunk_t blob, int level0)
{
    asn1_ctx_t ctx;
    chunk_t object;
    u_int level;
    int objectID = 0;

    ietfAttrList_t *list = NULL;

    asn1_init(&ctx, blob, level0, FALSE, DBG_RAW);

    while (objectID < IETF_ATTR_ROOF)
    {
	if (!extract_object(ietfAttrSyntaxObjects, &objectID, &object, &level, &ctx))
	     return NULL;

	switch (objectID)
	{
	case IETF_ATTR_OCTETS:
	case IETF_ATTR_OID:
	case IETF_ATTR_STRING:
	    {
		ietfAttr_t *attr   = alloc_thing(ietfAttr_t, "ietfAttr");
		ietfAttrList_t *el = alloc_thing(ietfAttrList_t, "ietfAttrList");

		attr->kind  = (objectID - IETF_ATTR_OCTETS) / 2;
		attr->value = object;
		attr->count = 0;

		el->attr = add_ietfAttr(attr);
		el->next = list;
		list = el;
	    }
	    break;
	default:
	    break;
	}
	objectID++;
    }
    return list;
}
/*
 * parses roleSyntax
 */
static void
parse_roleSyntax(chunk_t blob, int level0)
{
    asn1_ctx_t ctx;
    chunk_t object;
    u_int level;
    int objectID = 0;

    asn1_init(&ctx, blob, level0, FALSE, DBG_RAW);

    while (objectID < ROLE_ROOF)
    {
	if (!extract_object(roleSyntaxObjects, &objectID, &object, &level, &ctx))
	     return;

	switch (objectID) {
	default:
	    break;
	}
	objectID++;
    }
}

/*
 *  Parses an X.509 attribute certificate
 */
bool
parse_ac(chunk_t blob, x509acert_t *ac)
{
    asn1_ctx_t ctx;
    bool critical;
    chunk_t object;
    u_int level;
    int objectID = 0;
    int type = OID_UNKNOWN;
    int extn_oid = OID_UNKNOWN;

    asn1_init(&ctx, blob, 0, FALSE, DBG_RAW);

    while (objectID < AC_OBJ_ROOF) {

	if (!extract_object(acObjects, &objectID, &object, &level, &ctx))
	     return FALSE;

	/* those objects which will parsed further need the next higher level */
	level++;

	switch (objectID)
	{
	case AC_OBJ_CERTIFICATE:
	    ac->certificate = object;
	    break;
	case AC_OBJ_CERTIFICATE_INFO:
	    ac->certificateInfo = object;
	    break;
	case AC_OBJ_VERSION:
	    ac->version = (object.len) ? (1 + (u_int)*object.ptr) : 1;
	    DBG(DBG_PARSING,
		DBG_log("  v%d", ac->version);
	    )
	    if (ac->version != 2)
	    {
		plog("v%d attribute certificates are not supported"
		    , ac->version);
		return FALSE;
	    }
	    break;
	case AC_OBJ_HOLDER_ISSUER:
	    ac->holderIssuer = get_directoryName(object, level, FALSE);
	    break;
	case AC_OBJ_HOLDER_SERIAL:
	    ac->holderSerial = object;
	    break;
	case AC_OBJ_ENTITY_NAME:
	    ac->entityName = get_directoryName(object, level, TRUE);
	    break;
	case AC_OBJ_ISSUER_NAME:
	    ac->issuerName = get_directoryName(object, level, FALSE);
	    break;
	case AC_OBJ_SIG_ALG:
	    ac->sigAlg = parse_algorithmIdentifier(object, level, NULL);
	    break;
	case AC_OBJ_SERIAL_NUMBER:
	    ac->serialNumber = object;
	    break;
	case AC_OBJ_NOT_BEFORE:
	    ac->notBefore = asn1totime(&object, ASN1_GENERALIZEDTIME);
	    break;
	case AC_OBJ_NOT_AFTER:
	    ac->notAfter = asn1totime(&object, ASN1_GENERALIZEDTIME);
	    break;
	case AC_OBJ_ATTRIBUTE_TYPE:
	    type = known_oid(object);
	    break;
	case AC_OBJ_ATTRIBUTE_VALUE:
	    {
		switch (type) {
		case OID_AUTHENTICATION_INFO:
		    DBG(DBG_PARSING,
			DBG_log("  need to parse authenticationInfo")
		    )
		    break;
		case OID_ACCESS_IDENTITY:
		    DBG(DBG_PARSING,
			DBG_log("  need to parse accessIdentity")
		    )
		    break;
		case OID_CHARGING_IDENTITY:
		    ac->charging = parse_ietfAttrSyntax(object, level);
		    break;
		case OID_GROUP:
		    ac->groups = parse_ietfAttrSyntax(object, level);
		    break;
		case OID_ROLE:
		    parse_roleSyntax(object, level);
		    break;
		default:
		    break;
		}
	    }
	    break;
	case AC_OBJ_EXTN_ID:
	    extn_oid = known_oid(object);
	    break;
	case AC_OBJ_CRITICAL:
	    critical = object.len && *object.ptr;
	    DBG(DBG_PARSING,
		DBG_log("  %s",(critical)?"TRUE":"FALSE");
	    )
	    break;
	case AC_OBJ_EXTN_VALUE:
	    {
		switch (extn_oid) {
		case OID_CRL_DISTRIBUTION_POINTS:
		    DBG(DBG_PARSING,
			DBG_log("  need to parse crlDistributionPoints")
		    )
		    break;
		case OID_AUTHORITY_KEY_ID:
		    parse_authorityKeyIdentifier(object, level
			, &ac->authKeyID, &ac->authKeySerialNumber);
		    break;
		case OID_TARGET_INFORMATION:
		    DBG(DBG_PARSING,
			DBG_log("  need to parse targetInformation")
		    )
		    break;
		case OID_NO_REV_AVAIL:
		    ac->noRevAvail = TRUE;
		    break;
		default:
		    break;
		}
	    }
	    break;
	case AC_OBJ_ALGORITHM:
	    ac->algorithm = parse_algorithmIdentifier(object, level, NULL);
	    break;
	case AC_OBJ_SIGNATURE:
	    ac->signature = object;
	    break;

	default:
	    break;
	}
	objectID++;
    }
    time(&ac->installed);
    return TRUE;
}

/*
 *  release an ietfAttribute, free it if count reaches zero
 */
static void
release_ietfAttr(ietfAttr_t* attr)
{
    if (--attr->count == 0)
    {
	ietfAttrList_t **plist = &ietfAttributes;
	ietfAttrList_t *list = *plist;

	while (list->attr != attr)
	{
	    plist = &list->next;
	    list = *plist;
	}
        *plist = list->next;
	
	pfree(attr->value.ptr);
	pfree(attr);
	pfree(list);
    }
}

/*
 *  free an ietfAttrList
 */
void
free_ietfAttrList(ietfAttrList_t* list)
{
    while (list != NULL)
    {
	ietfAttrList_t *el = list;

	release_ietfAttr(el->attr);
	list = list->next;
	pfree(el);
    }
}

/*
 *  free a X.509 attribute certificate
 */
void
free_acert(x509acert_t *ac)
{
    if (ac != NULL)
    {
	free_ietfAttrList(ac->charging);
	free_ietfAttrList(ac->groups);
	pfreeany(ac->certificate.ptr);
	pfree(ac);
    }
}

/*
 *  free first X.509 attribute certificate in the chained list
 */
static void
free_first_acert(void)
{
    x509acert_t *first = x509acerts;
    x509acerts = first->next;
    free_acert(first);
}

/*
 * Free all attribute certificates in the chained list
 */
void
free_acerts(void)
{
    while (x509acerts != NULL)
	free_first_acert();
}

/*
 *  get a X.509 attribute certificate for a given holder
 */
x509acert_t*
get_x509acert(chunk_t issuer, chunk_t serial)
{
    x509acert_t *ac = x509acerts;
    x509acert_t *prev_ac = NULL;

    while (ac != NULL)
    {
	if (same_dn(issuer, ac->holderIssuer)
	&&  same_serial(serial, ac->holderSerial))
	{
	    if (ac!= x509acerts)
	    {
		/* bring the certificate up front */
		prev_ac->next = ac->next;
		ac->next = x509acerts;
		x509acerts = ac;
	    }
	    return ac;
	}
	prev_ac = ac;
	ac = ac->next;
    }
    return NULL;
}

/*
 *  add a X.509 attribute certificate to the chained list
 */
static void
add_acert(x509acert_t *ac)
{
    x509acert_t *old_ac = get_x509acert(ac->holderIssuer, ac->holderSerial);

    if (old_ac != NULL)
    {
	if (ac->notBefore >old_ac->notBefore)
	{
	    /* delete the old attribute cert */
	    free_first_acert();
	    DBG(DBG_CONTROL,
		DBG_log("attribute cert is newer - existing cert deleted")
	    )
	}
	else
	{
	    DBG(DBG_CONTROL,
		DBG_log("attribute cert is not newer - existing cert kept");
	    )
	    free_acert(ac);
	    return;
	}
    }
    plog("attribute cert added");

    /* insert new attribute cert at the root of the chain */
    ac->next = x509acerts;
    x509acerts = ac;
}

/* verify the validity of an attribute certificate by
 * checking the notBefore and notAfter dates
 */
static err_t
check_ac_validity(const x509acert_t *ac)
{
    time_t current_time;

    time(&current_time);
    DBG(DBG_CONTROL | DBG_PARSING,
	DBG_log("  not before  : %s", timetoa(&ac->notBefore, TRUE));
	DBG_log("  current time: %s", timetoa(&current_time, TRUE));
	DBG_log("  not after   : %s", timetoa(&ac->notAfter, TRUE));
    )

    if (current_time < ac->notBefore)
	return "attribute certificate is not valid yet";
    if (current_time > ac->notAfter)
	return "attribute certificate has expired";
    else
	return NULL;
}

/*
 * verifies a X.509 attribute certificate
 */
bool
verify_x509acert(x509acert_t *ac, bool strict)
{
    u_char buf[BUF_LEN];
    x509cert_t *aacert;
    err_t ugh = NULL;
    time_t valid_until = ac->notAfter;

    DBG(DBG_CONTROL,
	dntoa(buf, BUF_LEN, ac->entityName);
	DBG_log("holder: '%s'",buf);
	dntoa(buf, BUF_LEN, ac->issuerName);
	DBG_log("issuer: '%s'",buf);
    )
    
    ugh = check_ac_validity(ac);

    if (ugh != NULL)
    {
	plog("%s", ugh);
	return FALSE;
    }
    DBG(DBG_CONTROL,
	DBG_log("attribute certificate is valid")
    )

    lock_authcert_list("verify_x509acert");
    aacert = get_authcert(ac->issuerName, ac->authKeySerialNumber
	, ac->authKeyID, AUTH_AA);
    unlock_authcert_list("verify_x509acert");

    if (aacert == NULL)
    {
	plog("issuer aacert not found");
	return FALSE;
    }
    DBG(DBG_CONTROL,
	DBG_log("issuer aacert found")
    )

    if (!check_signature(ac->certificateInfo, ac->signature
			 , ac->algorithm, ac->algorithm, aacert))
    {
	plog("attribute certificate signature is invalid");
	return FALSE;
    }
    DBG(DBG_CONTROL,
	DBG_log("attribute certificate signature is valid");
    )

    return verify_x509cert(aacert, strict, &valid_until);
}

/*
 * Loads X.509 attribute certificates
 */
void
load_acerts(void)
{
    u_char buf[BUF_LEN];

    /* change directory to specified path */
    u_char *save_dir = getcwd(buf, BUF_LEN);

    if (!chdir(A_CERT_PATH))
    {
	struct dirent **filelist;
	int n;

	plog("Changing to directory '%s'",A_CERT_PATH);
	n = scandir(A_CERT_PATH, &filelist, file_select, alphasort);

	if (n > 0)
	{
	    while (n--)
	    {
		chunk_t blob = empty_chunk;
		bool pgp = FALSE;

		if (load_coded_file(filelist[n]->d_name, NULL, "acert", &blob, &pgp))
		{
		    x509acert_t *ac = alloc_thing(x509acert_t, "x509acert");
		    
		    *ac = empty_ac;

		    if (parse_ac(blob, ac)
		    && verify_x509acert(ac, FALSE))
			add_acert(ac);
		    else
			free_acert(ac);
		}
		free(filelist[n]);
	    }
	    free(filelist);
	}
    }
    /* restore directory path */
    chdir(save_dir);
}

/*
 * lists group attributes separated by commas on a single line
 */
void
format_groups(const ietfAttrList_t *list, char *buf, int len)
{
    bool first_group = TRUE;

    while (list != NULL && len > 0)
    {
	ietfAttr_t *attr = list->attr;

	if (attr->kind == IETF_ATTRIBUTE_OCTETS
	||  attr->kind == IETF_ATTRIBUTE_STRING)
	{
	    int written = snprintf(buf, len, "%s%.*s"
			    , (first_group)? "" : ", "
			    , (int)attr->value.len, attr->value.ptr);

	    first_group = FALSE;
	    
	    /* return value of snprintf() up to glibc 2.0.6 */
	    if (written < 0)
		break;

	    buf += written;
	    len -= written;
	}
	list = list->next;
    }
}

/*
 *  list all X.509 attribute certificates in the chained list
 */
void
list_acerts(bool utc)
{
    x509acert_t *ac = x509acerts;
    time_t now;

    /* determine the current time */
    time(&now);

    if (ac != NULL)
    {
	whack_log(RC_COMMENT, " ");
	whack_log(RC_COMMENT, "List of X.509 Attribute Certificates:");
	whack_log(RC_COMMENT, " ");
    }

    while (ac != NULL)
    {
	u_char buf[BUF_LEN];

	whack_log(RC_COMMENT, "%s",timetoa(&ac->installed, utc));
	if (ac->entityName.ptr != NULL)
	{
	    dntoa(buf, BUF_LEN, ac->entityName);
	    whack_log(RC_COMMENT, "       holder:   '%s'", buf);
	}
	if (ac->holderIssuer.ptr != NULL)
	{
	    dntoa(buf, BUF_LEN, ac->holderIssuer);
	    whack_log(RC_COMMENT, "       hissuer:  '%s'", buf);
	}
	if (ac->holderSerial.ptr != NULL)
	{
	    datatot(ac->holderSerial.ptr, ac->holderSerial.len, ':'
		, buf, BUF_LEN);
	    whack_log(RC_COMMENT, "       hserial:   %s", buf);
	}
	if (ac->groups != NULL)
	{
	    format_groups(ac->groups, buf, BUF_LEN);
	    whack_log(RC_COMMENT, "       groups:    %s", buf);
	}
	dntoa(buf, BUF_LEN, ac->issuerName);
	whack_log(RC_COMMENT, "       issuer:   '%s'", buf);
	datatot(ac->serialNumber.ptr, ac->serialNumber.len, ':'
	    , buf, BUF_LEN);
	whack_log(RC_COMMENT, "       serial:    %s", buf);
	whack_log(RC_COMMENT, "       validity:  not before %s %s",
		timetoa(&ac->notBefore, utc),
		(ac->notBefore < now)?"ok":"fatal (not valid yet)");
	whack_log(RC_COMMENT, "                  not after  %s %s",
		timetoa(&ac->notAfter, utc),
		check_expiry(ac->notAfter, ACERT_WARNING_INTERVAL, TRUE));
	if (ac->authKeyID.ptr != NULL)
	{
	    datatot(ac->authKeyID.ptr, ac->authKeyID.len, ':'
		, buf, BUF_LEN);
	    whack_log(RC_COMMENT, "       authkey:   %s", buf);
	}
	if (ac->authKeySerialNumber.ptr != NULL)
	{
	    datatot(ac->authKeySerialNumber.ptr, ac->authKeySerialNumber.len, ':'
		, buf, BUF_LEN);
	    whack_log(RC_COMMENT, "       aserial:   %s", buf);
	}

	ac = ac->next;
    }
}

/*
 *  list all group attributes in alphabetical order
 */
void
list_groups(bool utc)
{
    ietfAttrList_t *list = ietfAttributes;
    
    if (list != NULL)
    {
	whack_log(RC_COMMENT, " ");
	whack_log(RC_COMMENT, "List of Group Attributes:");
	whack_log(RC_COMMENT, " ");
    }

    while (list != NULL)
    {
	ietfAttr_t *attr = list->attr;

	whack_log(RC_COMMENT, "%s, count: %d", timetoa(&attr->installed, utc),
		attr->count);
	
	switch (attr->kind)
	{
	case IETF_ATTRIBUTE_OCTETS:
	case IETF_ATTRIBUTE_STRING:
	    whack_log(RC_COMMENT, "       %.*s", (int)attr->value.len, attr->value.ptr);
	    break;
	case IETF_ATTRIBUTE_OID:
	    whack_log(RC_COMMENT, "       OID");
	    break;
	default:
	    break;
        }

	list = list->next;
    }
}
