/*
 * Copyright (C) 2009 Tobias Brunner
 * Copyright (C) 2005-2009 Martin Willi
 * Copyright (C) 2005 Jan Hutter
 * Hochschule fuer Technik Rapperswil
 *
 * 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.
 */

#define _GNU_SOURCE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>

#include "identification.h"

#include <asn1/oid.h>
#include <asn1/asn1.h>
#include <crypto/hashers/hasher.h>

ENUM_BEGIN(id_match_names, ID_MATCH_NONE, ID_MATCH_MAX_WILDCARDS,
	"MATCH_NONE",
	"MATCH_ANY",
	"MATCH_MAX_WILDCARDS");
ENUM_NEXT(id_match_names, ID_MATCH_PERFECT, ID_MATCH_PERFECT, ID_MATCH_MAX_WILDCARDS,
	"MATCH_PERFECT");
ENUM_END(id_match_names, ID_MATCH_PERFECT);

ENUM_BEGIN(id_type_names, ID_ANY, ID_KEY_ID,
	"ID_ANY",
	"ID_IPV4_ADDR",
	"ID_FQDN",
	"ID_RFC822_ADDR",
	"ID_IPV4_ADDR_SUBNET",
	"ID_IPV6_ADDR",
	"ID_IPV6_ADDR_SUBNET",
	"ID_IPV4_ADDR_RANGE",
	"ID_IPV6_ADDR_RANGE",
	"ID_DER_ASN1_DN",
	"ID_DER_ASN1_GN",
	"ID_KEY_ID");
ENUM_NEXT(id_type_names, ID_DER_ASN1_GN_URI, ID_MYID, ID_KEY_ID,
	"ID_DER_ASN1_GN_URI"
	"ID_IETF_ATTR_STRING"
	"ID_MYID");
ENUM_END(id_type_names, ID_MYID);

/**
 * coding of X.501 distinguished name
 */
typedef struct {
	const u_char *name;
	int oid;
	u_char type;
} x501rdn_t;

static const x501rdn_t x501rdns[] = {
	{"ND", 				OID_NAME_DISTINGUISHER,		ASN1_PRINTABLESTRING},
	{"UID", 			OID_PILOT_USERID,			ASN1_PRINTABLESTRING},
	{"DC", 				OID_PILOT_DOMAIN_COMPONENT, ASN1_PRINTABLESTRING},
	{"CN",				OID_COMMON_NAME,			ASN1_PRINTABLESTRING},
	{"S", 				OID_SURNAME,				ASN1_PRINTABLESTRING},
	{"SN", 				OID_SERIAL_NUMBER,			ASN1_PRINTABLESTRING},
	{"serialNumber", 	OID_SERIAL_NUMBER,			ASN1_PRINTABLESTRING},
	{"C", 				OID_COUNTRY,				ASN1_PRINTABLESTRING},
	{"L", 				OID_LOCALITY,				ASN1_PRINTABLESTRING},
	{"ST",				OID_STATE_OR_PROVINCE,		ASN1_PRINTABLESTRING},
	{"O", 				OID_ORGANIZATION,			ASN1_PRINTABLESTRING},
	{"OU", 				OID_ORGANIZATION_UNIT,		ASN1_PRINTABLESTRING},
	{"T", 				OID_TITLE,					ASN1_PRINTABLESTRING},
	{"D", 				OID_DESCRIPTION,			ASN1_PRINTABLESTRING},
	{"N", 				OID_NAME,					ASN1_PRINTABLESTRING},
	{"G", 				OID_GIVEN_NAME,				ASN1_PRINTABLESTRING},
	{"I", 				OID_INITIALS,				ASN1_PRINTABLESTRING},
	{"ID", 				OID_UNIQUE_IDENTIFIER,		ASN1_PRINTABLESTRING},
	{"EN", 				OID_EMPLOYEE_NUMBER,		ASN1_PRINTABLESTRING},
	{"employeeNumber",	OID_EMPLOYEE_NUMBER,		ASN1_PRINTABLESTRING},
	{"E", 				OID_EMAIL_ADDRESS,			ASN1_IA5STRING},
	{"Email", 			OID_EMAIL_ADDRESS,			ASN1_IA5STRING},
	{"emailAddress",	OID_EMAIL_ADDRESS,			ASN1_IA5STRING},
	{"UN", 				OID_UNSTRUCTURED_NAME,		ASN1_IA5STRING},
	{"unstructuredName",OID_UNSTRUCTURED_NAME,		ASN1_IA5STRING},
	{"TCGID", 			OID_TCGID,					ASN1_PRINTABLESTRING}
};

/**
 * maximum number of RDNs in atodn()
 */
#define RDN_MAX			20


typedef struct private_identification_t private_identification_t;

/**
 * Private data of an identification_t object.
 */
struct private_identification_t {
	/**
	 * Public interface.
	 */
	identification_t public;

	/**
	 * Encoded representation of this ID.
	 */
	chunk_t encoded;

	/**
	 * Type of this ID.
	 */
	id_type_t type;
};

/**
 * Enumerator over RDNs
 */
typedef struct {
	/* implements enumerator interface */
	enumerator_t public;
	/* next set to parse, if any */
	chunk_t sets;
	/* next sequence in set, if any */
	chunk_t seqs;
} rdn_enumerator_t;

METHOD(enumerator_t, rdn_enumerate, bool,
	rdn_enumerator_t *this, chunk_t *oid, u_char *type, chunk_t *data)
{
	chunk_t rdn;

	/* a DN contains one or more SET, each containing one or more SEQUENCES,
	 * each containing a OID/value RDN */
	if (!this->seqs.len)
	{
		/* no SEQUENCEs in current SET, parse next SET */
		if (asn1_unwrap(&this->sets, &this->seqs) != ASN1_SET)
		{
			return FALSE;
		}
	}
	if (asn1_unwrap(&this->seqs, &rdn) == ASN1_SEQUENCE &&
		asn1_unwrap(&rdn, oid) == ASN1_OID)
	{
		int t = asn1_unwrap(&rdn, data);

		if (t != ASN1_INVALID)
		{
			*type = t;
			return TRUE;
		}
	}
	return FALSE;
}

/**
 * Create an enumerator over all RDNs (oid, string type, data) of a DN
 */
static enumerator_t* create_rdn_enumerator(chunk_t dn)
{
	rdn_enumerator_t *e;

	INIT(e,
		.public = {
			.enumerate = (void*)_rdn_enumerate,
			.destroy = (void*)free,
		},
	);

	/* a DN is a SEQUENCE, get the first SET of it */
	if (asn1_unwrap(&dn, &e->sets) == ASN1_SEQUENCE)
	{
		e->seqs = chunk_empty;
		return &e->public;
	}
	free(e);
	return enumerator_create_empty();
}

/**
 * Part enumerator over RDNs
 */
typedef struct {
	/* implements enumerator interface */
	enumerator_t public;
	/* inner RDN enumerator */
	enumerator_t *inner;
} rdn_part_enumerator_t;

METHOD(enumerator_t, rdn_part_enumerate, bool,
	rdn_part_enumerator_t *this, id_part_t *type, chunk_t *data)
{
	int i, known_oid, strtype;
	chunk_t oid, inner_data;
	static const struct {
		int oid;
		id_part_t type;
	} oid2part[] = {
		{OID_COMMON_NAME,		ID_PART_RDN_CN},
		{OID_SURNAME,			ID_PART_RDN_S},
		{OID_SERIAL_NUMBER,		ID_PART_RDN_SN},
		{OID_COUNTRY,			ID_PART_RDN_C},
		{OID_LOCALITY,			ID_PART_RDN_L},
		{OID_STATE_OR_PROVINCE,	ID_PART_RDN_ST},
		{OID_ORGANIZATION,		ID_PART_RDN_O},
		{OID_ORGANIZATION_UNIT,	ID_PART_RDN_OU},
		{OID_TITLE,				ID_PART_RDN_T},
		{OID_DESCRIPTION,		ID_PART_RDN_D},
		{OID_NAME,				ID_PART_RDN_N},
		{OID_GIVEN_NAME,		ID_PART_RDN_G},
		{OID_INITIALS,			ID_PART_RDN_I},
		{OID_UNIQUE_IDENTIFIER,	ID_PART_RDN_ID},
		{OID_EMAIL_ADDRESS,		ID_PART_RDN_E},
		{OID_EMPLOYEE_NUMBER,	ID_PART_RDN_EN},
	};

	while (this->inner->enumerate(this->inner, &oid, &strtype, &inner_data))
	{
		known_oid = asn1_known_oid(oid);
		for (i = 0; i < countof(oid2part); i++)
		{
			if (oid2part[i].oid == known_oid)
			{
				*type = oid2part[i].type;
				*data = inner_data;
				return TRUE;
			}
		}
	}
	return FALSE;
}

METHOD(enumerator_t, rdn_part_enumerator_destroy, void,
	rdn_part_enumerator_t *this)
{
	this->inner->destroy(this->inner);
	free(this);
}

METHOD(identification_t, create_part_enumerator, enumerator_t*,
	private_identification_t *this)
{
	switch (this->type)
	{
		case ID_DER_ASN1_DN:
		{
			rdn_part_enumerator_t *e;

			INIT(e,
				.inner = create_rdn_enumerator(this->encoded),
				.public = {
					.enumerate = (void*)_rdn_part_enumerate,
					.destroy = _rdn_part_enumerator_destroy,
				},
			);
			return &e->public;
		}
		case ID_RFC822_ADDR:
			/* TODO */
		case ID_FQDN:
			/* TODO */
		default:
			return enumerator_create_empty();
	}
}

/**
 * Print a DN with all its RDN in a buffer to present it to the user
 */
static void dntoa(chunk_t dn, char *buf, size_t len)
{
	enumerator_t *e;
	chunk_t oid_data, data, printable;
	u_char type;
	int oid, written;
	bool finished = FALSE;

	e = create_rdn_enumerator(dn);
	while (e->enumerate(e, &oid_data, &type, &data))
	{
		oid = asn1_known_oid(oid_data);

		if (oid == OID_UNKNOWN)
		{
			written = snprintf(buf, len, "%#B=", &oid_data);
		}
		else
		{
			written = snprintf(buf, len,"%s=", oid_names[oid].name);
		}
		if (written < 0 || written >= len)
		{
			break;
		}
		buf += written;
		len -= written;

		chunk_printable(data, &printable, '?');
		written = snprintf(buf, len, "%.*s", printable.len, printable.ptr);
		chunk_free(&printable);
		if (written < 0 || written >= len)
		{
			break;
		}
		buf += written;
		len -= written;

		if (data.ptr + data.len != dn.ptr + dn.len)
		{
			written = snprintf(buf, len, ", ");
			if (written < 0 || written >= len)
			{
				break;
			}
			buf += written;
			len -= written;
		}
		else
		{
			finished = TRUE;
			break;
		}
	}
	if (!finished)
	{
		snprintf(buf, len, "(invalid ID_DER_ASN1_DN)");
	}
	e->destroy(e);
}

/**
 * Converts an LDAP-style human-readable ASCII-encoded
 * ASN.1 distinguished name into binary DER-encoded format
 */
static status_t atodn(char *src, chunk_t *dn)
{
	/* finite state machine for atodn */
	typedef enum {
		SEARCH_OID =	0,
		READ_OID =		1,
		SEARCH_NAME =	2,
		READ_NAME =		3,
		UNKNOWN_OID =	4
	} state_t;

	chunk_t oid  = chunk_empty;
	chunk_t name = chunk_empty;
	chunk_t rdns[RDN_MAX];
	int rdn_count = 0;
	int dn_len = 0;
	int whitespace = 0;
	int i = 0;
	asn1_t rdn_type;
	state_t state = SEARCH_OID;
	status_t status = SUCCESS;

	do
	{
		switch (state)
		{
			case SEARCH_OID:
				if (*src != ' ' && *src != '/' && *src !=  ',')
				{
					oid.ptr = src;
					oid.len = 1;
					state = READ_OID;
				}
				break;
			case READ_OID:
				if (*src != ' ' && *src != '=')
				{
					oid.len++;
				}
				else
				{
					bool found = FALSE;

					for (i = 0; i < countof(x501rdns); i++)
					{
						if (strlen(x501rdns[i].name) == oid.len &&
							strncasecmp(x501rdns[i].name, oid.ptr, oid.len) == 0)
						{
							found = TRUE;
							break;
						}
					}
					if (!found)
					{
						status = NOT_SUPPORTED;
						state = UNKNOWN_OID;
						break;
					}
					/* reset oid and change state */
					oid = chunk_empty;
					state = SEARCH_NAME;
				}
				break;
			case SEARCH_NAME:
				if (*src != ' ' && *src != '=')
				{
					name.ptr = src;
					name.len = 1;
					whitespace = 0;
					state = READ_NAME;
				}
				break;
			case READ_NAME:
				if (*src != ',' && *src != '/' && *src != '\0')
				{
					name.len++;
					if (*src == ' ')
						whitespace++;
					else
						whitespace = 0;
				}
				else
				{
					name.len -= whitespace;
					rdn_type = (x501rdns[i].type == ASN1_PRINTABLESTRING
								&& !asn1_is_printablestring(name))
								? ASN1_T61STRING : x501rdns[i].type;

					if (rdn_count < RDN_MAX)
					{
						chunk_t rdn_oid;

						rdn_oid = asn1_build_known_oid(x501rdns[i].oid);
						if (rdn_oid.len)
						{
							rdns[rdn_count] =
									asn1_wrap(ASN1_SET, "m",
										asn1_wrap(ASN1_SEQUENCE, "mm",
											rdn_oid,
											asn1_wrap(rdn_type, "c", name)
										)
									);
							dn_len += rdns[rdn_count++].len;
						}
						else
						{
							status = INVALID_ARG;
						}
					}
					else
					{
						status = OUT_OF_RES;
					}
					/* reset name and change state */
					name = chunk_empty;
					state = SEARCH_OID;
				}
				break;
			case UNKNOWN_OID:
				break;
		}
	} while (*src++ != '\0');

	/* build the distinguished name sequence */
	{
		int i;
		u_char *pos = asn1_build_object(dn, ASN1_SEQUENCE, dn_len);

		for (i = 0; i < rdn_count; i++)
		{
			memcpy(pos, rdns[i].ptr, rdns[i].len);
			pos += rdns[i].len;
			free(rdns[i].ptr);
		}
	}

	if (status != SUCCESS)
	{
		free(dn->ptr);
		*dn = chunk_empty;
	}
	return status;
}

METHOD(identification_t, get_encoding, chunk_t,
	private_identification_t *this)
{
	return this->encoded;
}

METHOD(identification_t, get_type, id_type_t,
	private_identification_t *this)
{
	return this->type;
}

METHOD(identification_t, contains_wildcards_dn, bool,
	private_identification_t *this)
{
	enumerator_t *enumerator;
	bool contains = FALSE;
	id_part_t type;
	chunk_t data;

	enumerator = create_part_enumerator(this);
	while (enumerator->enumerate(enumerator, &type, &data))
	{
		if (data.len == 1 && data.ptr[0] == '*')
		{
			contains = TRUE;
			break;
		}
	}
	enumerator->destroy(enumerator);
	return contains;
}

METHOD(identification_t, contains_wildcards_memchr, bool,
	private_identification_t *this)
{
	return memchr(this->encoded.ptr, '*', this->encoded.len) != NULL;
}

METHOD(identification_t, equals_binary, bool,
	private_identification_t *this, identification_t *other)
{
	if (this->type == other->get_type(other))
	{
		if (this->type == ID_ANY)
		{
			return TRUE;
		}
		return chunk_equals(this->encoded, other->get_encoding(other));
	}
	return FALSE;
}

/**
 * Compare to DNs, for equality if wc == NULL, for match otherwise
 */
static bool compare_dn(chunk_t t_dn, chunk_t o_dn, int *wc)
{
	enumerator_t *t, *o;
	chunk_t t_oid, o_oid, t_data, o_data;
	u_char t_type, o_type;
	bool t_next, o_next, finished = FALSE;

	if (wc)
	{
		*wc = 0;
	}
	else
	{
		if (t_dn.len != o_dn.len)
		{
			return FALSE;
		}
	}
	/* try a binary compare */
	if (memeq(t_dn.ptr, o_dn.ptr, t_dn.len))
	{
		return TRUE;
	}

	t = create_rdn_enumerator(t_dn);
	o = create_rdn_enumerator(o_dn);
	while (TRUE)
	{
		t_next = t->enumerate(t, &t_oid, &t_type, &t_data);
		o_next = o->enumerate(o, &o_oid, &o_type, &o_data);

		if (!o_next && !t_next)
		{
			break;
		}
		finished = FALSE;
		if (o_next != t_next)
		{
			break;
		}
		if (!chunk_equals(t_oid, o_oid))
		{
			break;
		}
		if (wc && o_data.len == 1 && o_data.ptr[0] == '*')
		{
			(*wc)++;
		}
		else
		{
			if (t_data.len != o_data.len)
			{
				break;
			}
			if (t_type == o_type &&
				(t_type == ASN1_PRINTABLESTRING ||
				 (t_type == ASN1_IA5STRING &&
				  asn1_known_oid(t_oid) == OID_EMAIL_ADDRESS)))
			{	/* ignore case for printableStrings and email RDNs */
				if (strncasecmp(t_data.ptr, o_data.ptr, t_data.len) != 0)
				{
					break;
				}
			}
			else
			{	/* respect case and length for everything else */
				if (!memeq(t_data.ptr, o_data.ptr, t_data.len))
				{
					break;
				}
			}
		}
		/* the enumerator returns FALSE on parse error, we are finished
		 * if we have reached the end of the DN only */
		if ((t_data.ptr + t_data.len == t_dn.ptr + t_dn.len) &&
			(o_data.ptr + o_data.len == o_dn.ptr + o_dn.len))
		{
			finished = TRUE;
		}
	}
	t->destroy(t);
	o->destroy(o);
	return finished;
}

METHOD(identification_t, equals_dn, bool,
	private_identification_t *this, identification_t *other)
{
	return compare_dn(this->encoded, other->get_encoding(other), NULL);
}

METHOD(identification_t, equals_strcasecmp,  bool,
	private_identification_t *this, identification_t *other)
{
	chunk_t encoded = other->get_encoding(other);

	/* we do some extra sanity checks to check for invalid IDs with a
	 * terminating null in it. */
	if (this->encoded.len == encoded.len &&
		memchr(this->encoded.ptr, 0, this->encoded.len) == NULL &&
		memchr(encoded.ptr, 0, encoded.len) == NULL &&
		strncasecmp(this->encoded.ptr, encoded.ptr, this->encoded.len) == 0)
	{
		return TRUE;
	}
	return FALSE;
}

METHOD(identification_t, matches_binary, id_match_t,
	private_identification_t *this, identification_t *other)
{
	if (other->get_type(other) == ID_ANY)
	{
		return ID_MATCH_ANY;
	}
	if (this->type == other->get_type(other) &&
		chunk_equals(this->encoded, other->get_encoding(other)))
	{
		return ID_MATCH_PERFECT;
	}
	return ID_MATCH_NONE;
}

METHOD(identification_t, matches_string, id_match_t,
	private_identification_t *this, identification_t *other)
{
	chunk_t encoded = other->get_encoding(other);
	u_int len = encoded.len;

	if (other->get_type(other) == ID_ANY)
	{
		return ID_MATCH_ANY;
	}
	if (this->type != other->get_type(other))
	{
		return ID_MATCH_NONE;
	}
	/* try a equals check first */
	if (equals_strcasecmp(this, other))
	{
		return ID_MATCH_PERFECT;
	}
	if (len == 0 || this->encoded.len < len)
	{
		return ID_MATCH_NONE;
	}

	/* check for single wildcard at the head of the string */
	if (*encoded.ptr == '*')
	{
		/* single asterisk matches any string */
		if (len-- == 1)
		{	/* not better than ID_ANY */
			return ID_MATCH_ANY;
		}
		if (strncasecmp(this->encoded.ptr + this->encoded.len - len,
						encoded.ptr + 1, len) == 0)
		{
			return ID_MATCH_ONE_WILDCARD;
		}
	}
	return ID_MATCH_NONE;
}

METHOD(identification_t, matches_any, id_match_t,
	private_identification_t *this, identification_t *other)
{
	if (other->get_type(other) == ID_ANY)
	{
		return ID_MATCH_ANY;
	}
	return ID_MATCH_NONE;
}

METHOD(identification_t, matches_dn, id_match_t,
	private_identification_t *this, identification_t *other)
{
	int wc;

	if (other->get_type(other) == ID_ANY)
	{
		return ID_MATCH_ANY;
	}

	if (this->type == other->get_type(other))
	{
		if (compare_dn(this->encoded, other->get_encoding(other), &wc))
		{
			wc = min(wc, ID_MATCH_ONE_WILDCARD - ID_MATCH_MAX_WILDCARDS);
			return ID_MATCH_PERFECT - wc;
		}
	}
	return ID_MATCH_NONE;
}

/**
 * Described in header.
 */
int identification_printf_hook(char *dst, size_t len, printf_hook_spec_t *spec,
							   const void *const *args)
{
	private_identification_t *this = *((private_identification_t**)(args[0]));
	chunk_t proper;
	char buf[512];

	if (this == NULL)
	{
		return print_in_hook(dst, len, "%*s", spec->width, "(null)");
	}

	switch (this->type)
	{
		case ID_ANY:
			snprintf(buf, sizeof(buf), "%%any");
			break;
		case ID_IPV4_ADDR:
			if (this->encoded.len < sizeof(struct in_addr) ||
				inet_ntop(AF_INET, this->encoded.ptr, buf, sizeof(buf)) == NULL)
			{
				snprintf(buf, sizeof(buf), "(invalid ID_IPV4_ADDR)");
			}
			break;
		case ID_IPV6_ADDR:
			if (this->encoded.len < sizeof(struct in6_addr) ||
				inet_ntop(AF_INET6, this->encoded.ptr, buf, INET6_ADDRSTRLEN) == NULL)
			{
				snprintf(buf, sizeof(buf), "(invalid ID_IPV6_ADDR)");
			}
			break;
		case ID_FQDN:
		case ID_RFC822_ADDR:
		case ID_DER_ASN1_GN_URI:
		case ID_IETF_ATTR_STRING:
			chunk_printable(this->encoded, &proper, '?');
			snprintf(buf, sizeof(buf), "%.*s", proper.len, proper.ptr);
			chunk_free(&proper);
			break;
		case ID_DER_ASN1_DN:
			dntoa(this->encoded, buf, sizeof(buf));
			break;
		case ID_DER_ASN1_GN:
			snprintf(buf, sizeof(buf), "(ASN.1 general Name");
			break;
		case ID_KEY_ID:
			if (chunk_printable(this->encoded, NULL, '?') &&
				this->encoded.len != HASH_SIZE_SHA1)
			{	/* fully printable, use ascii version */
				snprintf(buf, sizeof(buf), "%.*s",
						 this->encoded.len, this->encoded.ptr);
			}
			else
			{	/* not printable, hex dump */
				snprintf(buf, sizeof(buf), "%#B", &this->encoded);
			}
			break;
		case ID_MYID:
			snprintf(buf, sizeof(buf), "%%myid");
			break;
		default:
			snprintf(buf, sizeof(buf), "(unknown ID type: %d)", this->type);
			break;
	}
	if (spec->minus)
	{
		return print_in_hook(dst, len, "%-*s", spec->width, buf);
	}
	return print_in_hook(dst, len, "%*s", spec->width, buf);
}

METHOD(identification_t, clone_, identification_t*,
	private_identification_t *this)
{
	private_identification_t *clone = malloc_thing(private_identification_t);

	memcpy(clone, this, sizeof(private_identification_t));
	if (this->encoded.len)
	{
		clone->encoded = chunk_clone(this->encoded);
	}
	return &clone->public;
}

METHOD(identification_t, destroy, void,
	private_identification_t *this)
{
	chunk_free(&this->encoded);
	free(this);
}

/**
 * Generic constructor used for the other constructors.
 */
static private_identification_t *identification_create(id_type_t type)
{
	private_identification_t *this;

	INIT(this,
		.public = {
			.get_encoding = _get_encoding,
			.get_type = _get_type,
			.create_part_enumerator = _create_part_enumerator,
			.clone = _clone_,
			.destroy = _destroy,
		},
		.type = type,
	);

	switch (type)
	{
		case ID_ANY:
			this->public.matches = _matches_any;
			this->public.equals = _equals_binary;
			this->public.contains_wildcards = return_true;
			break;
		case ID_FQDN:
		case ID_RFC822_ADDR:
			this->public.matches = _matches_string;
			this->public.equals = _equals_strcasecmp;
			this->public.contains_wildcards = _contains_wildcards_memchr;
			break;
		case ID_DER_ASN1_DN:
			this->public.equals = _equals_dn;
			this->public.matches = _matches_dn;
			this->public.contains_wildcards = _contains_wildcards_dn;
			break;
		default:
			this->public.equals = _equals_binary;
			this->public.matches = _matches_binary;
			this->public.contains_wildcards = return_false;
			break;
	}
	return this;
}

/*
 * Described in header.
 */
identification_t *identification_create_from_string(char *string)
{
	private_identification_t *this;
	chunk_t encoded;

	if (string == NULL)
	{
		string = "%any";
	}
	if (strchr(string, '=') != NULL)
	{
		/* we interpret this as an ASCII X.501 ID_DER_ASN1_DN.
		 * convert from LDAP style or openssl x509 -subject style to ASN.1 DN
		 */
		if (atodn(string, &encoded) == SUCCESS)
		{
			this = identification_create(ID_DER_ASN1_DN);
			this->encoded = encoded;
		}
		else
		{
			this = identification_create(ID_KEY_ID);
			this->encoded = chunk_clone(chunk_create(string, strlen(string)));
		}
		return &this->public;
	}
	else if (strchr(string, '@') == NULL)
	{
		if (streq(string, "%any")
		||  streq(string, "%any6")
		||	streq(string, "0.0.0.0")
		||	streq(string, "*")
		||	streq(string, "::")
		||	streq(string, "0::0"))
		{
			/* any ID will be accepted */
			this = identification_create(ID_ANY);
			return &this->public;
		}
		else
		{
			if (strchr(string, ':') == NULL)
			{
				struct in_addr address;
				chunk_t chunk = {(void*)&address, sizeof(address)};

				if (inet_pton(AF_INET, string, &address) > 0)
				{	/* is IPv4 */
					this = identification_create(ID_IPV4_ADDR);
					this->encoded = chunk_clone(chunk);
				}
				else
				{	/* not IPv4, mostly FQDN */
					this = identification_create(ID_FQDN);
					this->encoded = chunk_create(strdup(string), strlen(string));
				}
				return &this->public;
			}
			else
			{
				struct in6_addr address;
				chunk_t chunk = {(void*)&address, sizeof(address)};

				if (inet_pton(AF_INET6, string, &address) > 0)
				{	/* is IPv6 */
					this = identification_create(ID_IPV6_ADDR);
					this->encoded = chunk_clone(chunk);
				}
				else
				{	/* not IPv4/6 fallback to KEY_ID */
					this = identification_create(ID_KEY_ID);
					this->encoded = chunk_create(strdup(string), strlen(string));
				}
				return &this->public;
			}
		}
	}
	else
	{
		if (*string == '@')
		{
			if (*(string + 1) == '#')
			{
				this = identification_create(ID_KEY_ID);
				string += 2;
				this->encoded = chunk_from_hex(
									chunk_create(string, strlen(string)), NULL);
				return &this->public;
			}
			else
			{
				this = identification_create(ID_FQDN);
				string += 1;
				this->encoded = chunk_create(strdup(string), strlen(string));
				return &this->public;
			}
		}
		else
		{
			this = identification_create(ID_RFC822_ADDR);
			this->encoded = chunk_create(strdup(string), strlen(string));
			return &this->public;
		}
	}
}

/*
 * Described in header.
 */
identification_t * identification_create_from_data(chunk_t data)
{
	char buf[data.len + 1];

	/* use string constructor */
	snprintf(buf, sizeof(buf), "%.*s", data.len, data.ptr);
	return identification_create_from_string(buf);
}

/*
 * Described in header.
 */
identification_t *identification_create_from_encoding(id_type_t type,
													  chunk_t encoded)
{
	private_identification_t *this = identification_create(type);

	/* apply encoded chunk */
	if (type != ID_ANY)
	{
		this->encoded = chunk_clone(encoded);
	}
	return &(this->public);
}

/*
 * Described in header.
 */
identification_t *identification_create_from_sockaddr(sockaddr_t *sockaddr)
{
	switch (sockaddr->sa_family)
	{
		case AF_INET:
		{
			struct in_addr *addr = &(((struct sockaddr_in*)sockaddr)->sin_addr);

			return identification_create_from_encoding(ID_IPV4_ADDR,
					chunk_create((u_char*)addr, sizeof(struct in_addr)));
		}
		case AF_INET6:
		{
			struct in6_addr *addr = &(((struct sockaddr_in6*)sockaddr)->sin6_addr);

			return identification_create_from_encoding(ID_IPV6_ADDR,
					chunk_create((u_char*)addr, sizeof(struct in6_addr)));
		}
		default:
		{
			private_identification_t *this = identification_create(ID_ANY);

			return &(this->public);
		}
	}
}