/*
 * iSNS message handling functions
 *
 * Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com>
 *
 *
 */

#include <stdlib.h>
#include <string.h>
#include <sys/time.h>	/* for timercmp */
#include <unistd.h>	/* gethostname */
#include <ctype.h>
#include <libisns/isns.h>
#include <libisns/attrs.h>
#include <libisns/message.h>
#include "socket.h"
#include <libisns/util.h>

/* iSCSI qualified names include the year and
 * month in which the domain was assigned.
 * See RFC 3720, section 3.2.6.3.1.
 * That's one of these wonderful committee
 * type of ideas that makes it hard for everyone,
 * from coder to sysadmin.
 * Since we have no way of finding out here,
 * we fake it by assigning a date before the
 * dawn of time.
 */
#ifndef IQNPREFIX
#define DUMMY_IQN_PREFIX	"iqn.1967-12."
#else
#define DUMMY_IQN_PREFIX IQNPREFIX
#endif

static uint32_t		isns_xid = 1;

/*
 * Initialize a message object
 */
isns_message_t *
__isns_alloc_message(uint32_t xid, size_t size, void (*destroy)(isns_message_t *))
{
	isns_message_t	*msg;

	isns_assert(size >= sizeof(*msg));
	msg = isns_calloc(1, size);

	isns_list_init(&msg->im_list);
	msg->im_users = 1;
	msg->im_xid = xid;
	msg->im_destroy = destroy;

	return msg;
}

static int
__isns_message_init(isns_message_t *msg,
			uint16_t function, uint16_t flags,
			size_t payload_len)
{
	struct isns_hdr *hdr = &msg->im_header;

	/* Pad to multiple of 4 octets */
	payload_len = (payload_len + 3) & ~3UL;

	/* For now, we don't do segmentation */
	if (payload_len > ISNS_MAX_PDU_SIZE)
		return 0;

	/* msg->im_header is in host byte order */
	hdr->i_version = ISNS_VERSION;
	hdr->i_function = function;
	hdr->i_flags = flags;
	hdr->i_length = payload_len;
	hdr->i_xid = msg->im_xid;
	hdr->i_seq = 0;

	/* Allocate buffer and reserve room for header */
	msg->im_payload = buf_alloc(sizeof(*hdr) + payload_len);
	buf_push(msg->im_payload, sizeof(*hdr));

	return 1;
}

/*
 * Allocate a message object.
 */
static isns_message_t *
__isns_create_message(uint32_t xid, uint16_t function, uint16_t flags)
{
	isns_message_t *msg;

	msg = __isns_alloc_message(xid, sizeof(*msg), NULL);
	__isns_message_init(msg, function, flags, ISNS_MAX_MESSAGE);

	return msg;
}

/*
 * Allocate a request message
 */
isns_message_t *
isns_create_message(uint16_t function, uint16_t flags)
{
	return __isns_create_message(isns_xid++, function, flags);
}

/*
 * Allocate a response message
 */
isns_message_t *
isns_create_reply(const isns_message_t *msg)
{
	uint16_t function = msg->im_header.i_function;;
	isns_message_t	*resp;

	resp = __isns_create_message(msg->im_xid, function | 0x8000, ISNS_F_SERVER);
	resp->im_addr = msg->im_addr;
	resp->im_addrlen = msg->im_addrlen;

	/* Default to ISNS_SUCCESS */
	buf_put32(resp->im_payload, ISNS_SUCCESS);

	return resp;
}

/*
 * Delete a message
 */
void
isns_message_release(isns_message_t *msg)
{
	if (msg == NULL)
		return;

	isns_assert(msg->im_users);
	if (--(msg->im_users))
		return;

	if (msg->im_destroy)
		msg->im_destroy(msg);
	if (msg->im_payload)
		buf_free(msg->im_payload);
	isns_principal_free(msg->im_security);

	isns_list_del(&msg->im_list);
	isns_free(msg);
}

/*
 * Extract the status from a reply message
 */
int
isns_message_status(isns_message_t *msg)
{
	uint32_t	status;

	if (!(msg->im_header.i_function & 0x8000)
	 || !buf_get32(msg->im_payload, &status))
		return ISNS_MESSAGE_FORMAT_ERROR;
	return status;
}

/*
 * Obtain the socket on which the message was received.
 */
isns_socket_t *
isns_message_socket(const isns_message_t *msg)
{
	return msg->im_socket;
}

/*
 * Obtain the message's security context
 */
isns_security_t *
isns_message_security(const isns_message_t *msg)
{
	if (!msg->im_socket)
		return NULL;
	return msg->im_socket->is_security;
}

unsigned int
isns_message_function(const isns_message_t *msg)
{
	return msg->im_header.i_function;
}

/*
 * Reset the response message, and encode isns_error
 * status
 */
void
isns_message_set_error(isns_message_t *msg, uint32_t status)
{
	/* Clear the buffer. This just resets head + tail */
	buf_clear(msg->im_payload);

	/* Now move past the header, and overwrite the
	 * status word. */
	buf_push(msg->im_payload, sizeof(struct isns_hdr));
	buf_put32(msg->im_payload, status);
}

/*
 * Message queue handling. Most related functions are
 * in message.h
 */
void
isns_message_queue_move(isns_message_queue_t *dstq,
		isns_message_t *msg)
{
	unsigned int	src_ref = 0;

	/* If the message was on a different queue,
	 * the source queue will hold a reference
	 * to it. Account for that and fix up the
	 * refcount after we've appended it to the
	 * destination queue. */
	if (isns_message_unlink(msg))
		src_ref = 1;

	isns_message_queue_append(dstq, msg);
	msg->im_users -= src_ref;
}

/*
 * Insert a messsage into a queue sorted by resend timeout
 */
void
isns_message_queue_insert_sorted(isns_message_queue_t *q,
		int sort, isns_message_t *msg)
{
	isns_list_t	*pos;
	isns_message_t	*__m;

	isns_assert(msg->im_queue == NULL);
	if (sort == ISNS_MQ_SORT_RESEND_TIMEOUT) {
		isns_message_queue_foreach(q, pos, __m) {
			if (timercmp(&msg->im_resend_timeout,
				     &__m->im_resend_timeout, <))
				break;
		}
	} else {
		isns_message_queue_append(q, msg);
		return;
	}

	/* Insert before pos */
	__isns_list_insert(pos->prev, &msg->im_list, pos);
	q->imq_count++;

	msg->im_queue = q;
	msg->im_users++;
}

/*
 * Message queue handling
 */
void
isns_message_queue_destroy(isns_message_queue_t *q)
{
	isns_message_t	*msg;

	while ((msg = isns_message_dequeue(q)) != NULL)
		isns_message_release(msg);
}

/*
 * Find a message with matching xid and address.
 * (address, alen) may be NULL.
 */
isns_message_t *
isns_message_queue_find(isns_message_queue_t *q, uint32_t xid,
		const struct sockaddr_storage *addr, socklen_t alen)
{
	isns_message_t	*msg;
	isns_list_t	*pos;

	isns_message_queue_foreach(q, pos, msg) {
		if (msg->im_xid != xid)
			continue;
		if (alen == 0)
			return msg;

		if (msg->im_addrlen == alen
		 && !memcmp(&msg->im_addr, addr, alen))
			return msg;
	}

	return NULL;
}

/*
 * Convert a hostname into an iSCSI qualified name
 * We omit the dismbiguating YYYY-MM infix because
 * we have no way of finding out, short of bothering
 * whois.
 */
static char *
__revert_fqdn(const char *prefix, const char *__fqdn, const char *suffix)
{
	static char	namebuf[1024] = { '\0' };
	char		*fqdn, *result = NULL;
	int		pos, count = 0;

	if (prefix)
		strcpy(namebuf, prefix);
	pos = strlen(namebuf);

	fqdn = isns_strdup(__fqdn);
	while (1) {
		char	*dot, *comp;
		int	comp_len;

		if ((dot = strrchr(fqdn, '.')) != NULL) {
			*dot++ = '\0';
			comp = dot;
		} else {
			comp = fqdn;
		}

		if (*comp == '\0')
			continue;
		comp_len = strlen(comp);
		if (pos + comp_len + 2 > (int)sizeof(namebuf)) {
			isns_error("%s: FQDN too long\n", __FUNCTION__);
			goto out;
		}
		if (count++)
			namebuf[pos++] = '.';
		strcpy(namebuf + pos, comp);
		pos += comp_len;

		if (dot == NULL)
			break;
	}

	if (suffix) {
		int	sfx_len = strlen(suffix);

		if (pos + sfx_len + 2 > (int)sizeof(namebuf)) {
			isns_error("%s: name too long\n", __FUNCTION__);
			goto out;
		}
		namebuf[pos++] = ':';
		strcpy(namebuf + pos, suffix);
		pos += sfx_len;
	}

	result = isns_strdup(namebuf);

out:	isns_free(fqdn);
	return result;
}

/*
 * Initialize all names
 */
int
isns_init_names(void)
{
	if (isns_config.ic_iqn_prefix == NULL) {
		isns_config.ic_iqn_prefix = DUMMY_IQN_PREFIX;
	}
	if (isns_config.ic_host_name == NULL) {
		char	namebuf[1024], *fqdn;

		if (gethostname(namebuf, sizeof(namebuf)) < 0) {
			isns_error("gehostname: %m\n");
			return 0;
		}
		fqdn = isns_get_canon_name(namebuf);
		if (fqdn == NULL) {
			/* FIXME: we could get some unique value here
			 * such as the IP address, and concat that
			 * with iqn.2005-01.org.open-iscsi.ip for the
			 * source name.
			 */
			isns_error("Unable to get fully qualified hostname\n");
			return 0;
		}
		isns_config.ic_host_name = fqdn;
	}

	if (isns_config.ic_auth_name == NULL) {
		isns_config.ic_auth_name = isns_config.ic_host_name;
	}

	if (isns_config.ic_entity_name == NULL) {
		isns_config.ic_entity_name = isns_config.ic_auth_name;
	}

	if (isns_config.ic_source_name == NULL) {
		isns_config.ic_source_name = __revert_fqdn(
				isns_config.ic_iqn_prefix,
				isns_config.ic_host_name,
				isns_config.ic_source_suffix);
		if (isns_config.ic_source_name == NULL) {
			isns_error("Unable to build source name\n");
			return 0;
		}
	}

	return 1;
}

/*
 * Match a source name to a pattern (which is really just
 * the entity identifier, usually).
 *
 * If the pattern is of the form "match:rev-fqdn", the
 * source name must match
 *	iqn.[YYYY-MM.]<rev-fqdn>
 * optionally followed by dot, colon or hyphen and arbitrary
 * text.
 *
 * If the pattern does not start with "match:", the source name
 * must match the pattern literally (case insensitively).
 */
int
isns_source_pattern_match(const char *pattern, const char *source)
{
	unsigned int	rev_len;

	isns_debug_message("%s(%s, %s)\n",
			__FUNCTION__, pattern, source);

	if (!strcmp(pattern, "*"))
		return 1;

	if (strncmp(pattern, "match:", 6))
		return !strcasecmp(pattern, source);
	pattern += 6;

	if (strncasecmp(source, "iqn.", 4))
		return 0;
	source += 4;

	rev_len = strlen(pattern);
	if (strncasecmp(source, pattern, rev_len)) {
		/* See if the next component is YYYY-MM */
		if (!(isdigit(source[0])
		   && isdigit(source[1])
		   && isdigit(source[2])
		   && isdigit(source[3])
		   && source[4] == '-'
		   && isdigit(source[5])
		   && isdigit(source[6])
		   && source[7] == '.'))
			return 0;
		source += 8;

		if (strncasecmp(source, pattern, rev_len))
			return 0;
	}

	source += rev_len;
	if (source[0] != '.'
	 && source[0] != ':'
	 && source[0] != '-'
	 && source[0] != '\0')
		return 0;

	return 1;
}

/*
 * This really just reverts the FQDN so it can
 * be used in isns_source_entity_match
 */
char *
isns_build_source_pattern(const char *fqdn)
{
	return __revert_fqdn("match:", fqdn, NULL);
}

/*
 * Manage source objects
 */
static isns_source_t *
__isns_source_create(isns_attr_t *name_attr)
{
	isns_source_t	*source = isns_calloc(1, sizeof(*source));

	source->is_users = 1;
	source->is_attr = name_attr;
	return source;
}

isns_source_t *
isns_source_create(isns_attr_t *name_attr)
{
	if (name_attr->ia_tag_id != ISNS_TAG_ISCSI_NAME
	 && name_attr->ia_tag_id != ISNS_TAG_FC_PORT_NAME_WWPN)
		return NULL;

	name_attr->ia_users++;
	return __isns_source_create(name_attr);
}

isns_source_t *
isns_source_from_object(const isns_object_t *node)
{
	isns_attr_t	*attr;

	if (!(attr = isns_storage_node_key_attr(node)))
		return NULL;
	return isns_source_create(attr);
}

isns_source_t *
isns_source_create_iscsi(const char *name)
{
	isns_value_t	var = ISNS_VALUE_INIT(string, (char *) name);
	isns_attr_t	*attr;

	attr = isns_attr_alloc(ISNS_TAG_ISCSI_NAME, NULL, &var);
	return __isns_source_create(attr);
}

/*
 * This is used to attach a dummy source to iSNS responses
 * until I fixed up all the code that relies on msg->is_source
 * to be valid all the time.
 */
isns_source_t *
isns_source_dummy(void)
{
	static isns_source_t *dummy = NULL;

	if (!dummy)
		dummy = isns_source_create_iscsi(".dummy.");
	return isns_source_get(dummy);
}

uint32_t
isns_source_type(const isns_source_t *source)
{
	return source->is_attr->ia_tag_id;
}

const char *
isns_source_name(const isns_source_t *source)
{
	return source->is_attr->ia_value.iv_string;
}

isns_attr_t *
isns_source_attr(const isns_source_t *source)
{
	return source->is_attr;
}

/*
 * Obtain an additional reference on the source object
 */
isns_source_t *
isns_source_get(isns_source_t *source)
{
	if (source)
		source->is_users++;
	return source;
}

/*
 * Look up the node corresponding to this source name
 * When we get here, we have already verified that the
 * client is permitted (by policy) to use this source node.
 */
int
isns_source_set_node(isns_source_t *source, isns_db_t *db)
{
	isns_object_t	*node, *entity;
	uint32_t	node_type;

	if (source->is_node)
		return 1;

	if (db == NULL)
		return 0;

	node = isns_db_lookup_source_node(db, source);
	if (node == NULL)
		return 0;

	if (!isns_object_get_uint32(node, ISNS_TAG_ISCSI_NODE_TYPE, &node_type))
		node_type = 0;

	source->is_node = node;
	source->is_node_type = node_type;

	if ((entity = isns_object_get_entity(node)) != NULL)
		source->is_entity = isns_object_get(entity);
	return 1;
}

void
isns_source_set_entity(isns_source_t *source, isns_object_t *obj)
{
	if (obj)
		isns_object_get(obj);
	isns_object_release(source->is_entity);
	source->is_entity = obj;
}

/*
 * Release a reference on the source object
 */
void
isns_source_release(isns_source_t *source)
{
	if (source && --source->is_users == 0) {
		isns_attr_release(source->is_attr);
		isns_object_release(source->is_node);
		isns_object_release(source->is_entity);
		memset(source, 0xa5, sizeof(*source));
		isns_free(source);
	}
}

/*
 * Compare two source objects
 */
int
isns_source_match(const isns_source_t *a,
		const isns_source_t *b)
{
	if (a && b)
		return isns_attr_match(a->is_attr, b->is_attr);
	return 0;
}

/*
 * Encode/decode source object
 */
int
isns_source_encode(buf_t *bp, const isns_source_t *source)
{
	if (source == NULL) {
		isns_attr_t nil = ISNS_ATTR_INIT(ISNS_TAG_DELIMITER, nil, 0);
		
		return isns_attr_encode(bp, &nil);
	}
	return isns_attr_encode(bp, source->is_attr);
}

int
isns_source_decode(buf_t *bp, isns_source_t **result)
{
	isns_attr_t	*attr;
	int		status;

	status = isns_attr_decode(bp, &attr);
	if (status == ISNS_SUCCESS) {
		/*
		 * 5.6.1
		 * The Source Attribute uniquely identifies the source of the
		 * message.  Valid Source Attribute types are shown below.
		 *
		 *	Valid Source Attributes
		 *	-----------------------
		 *	iSCSI Name
		 *	FC Port Name WWPN
		 */
		switch (attr->ia_tag_id) {
#if 0
		case ISNS_TAG_DELIMITER:
			*result = NULL;
			break;
#endif

		case ISNS_TAG_ISCSI_NAME:
			*result = __isns_source_create(attr);
			break;

		case ISNS_TAG_FC_PORT_NAME_WWPN:
			*result = __isns_source_create(attr);
			break;

		default:
			isns_attr_release(attr);
			return ISNS_SOURCE_UNKNOWN;
		}
	}
	return status;
}
