/*
 * hb_api: Server-side heartbeat API code
 *
 * Copyright (C) 2000 Alan Robertson <alanr@unix.sh>
 * Copyright (C) 2000 Marcelo Tosatti <marcelo@conectiva.com.br>
 *
 * Thanks to Conectiva S.A. for sponsoring Marcelo Tosatti work
 *
 * 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.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>
 *
 */
/*
 * This comment is very much aged, as the rest of the heartbeat code.
 * The api is using a socket now.  There is no more "fifo directory", there is
 * only one single "control" fifo left, which may still be necessary for
 * certain shell scripts.
 *
 *	A little about the API FIFO structure...
 *
 *	We have two kinds of API clients:  casual and named
 *
 *	Casual clients just attach and listen in to messages, and ask
 *	the status of things. Casual clients are typically used as status
 *	agents, or debugging agents.
 *
 *	They can't send messages, and they are known only by their PID.
 *	Anyone in the group that owns the casual FIFO directory can use
 *	the casual API.  Casual clients create and delete their own
 *	FIFOs for the API (or are cleaned up after by heartbeat ;-))
 *	Hence, the casual client FIFO directory must be group writable,
 *	and sticky.
 *
 *	Named clients attach and listen in to messages, and they are also
 *	allowed to send messages to the other clients in the cluster with
 *	the same name. Named clients typically provide persistent services
 *	in the cluster.  A cluster manager would be an example
 *	of such a persistent service.
 *
 *	Their FIFOs are pre-created for them, and they neither create nor
 *	delete them - nor should they be able to.
 *	The named client FIFO directory must not be writable by group or other.
 *
 *	We deliver messages from named clients to clients in the cluster
 *	which are registered with the same name.  Each named client
 *	also receives the messages it sends.  I could allow them to send
 *	to any other service that they want, but right now that's overridden.
 *	We mark each packet with the service name that the packet came from.
 *
 *	A client can only register for a given name if their userid is the
 *	owner of the named FIFO for that name.
 *
 *	If a client has permissions to snoop on packets (debug mode),
 *	then they are allowed to receive all packets, but otherwise only
 *	clients registered with the same name will receive these messages.
 *
 *	It is important to make sure that each named client FIFO is owned by the
 *	same UID on each machine.
 */

#include <lha_internal.h>
#include <sys/time.h>
#define	time FOOtime
#include <glib.h>
#undef time
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <heartbeat.h>
#include <ha_msg.h>
#include <hb_api.h>
#include <hb_api_core.h>
#include <hb_config.h>
#include <hb_resource.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <clplumbing/cl_poll.h>
#include <clplumbing/cl_signal.h>
#include <clplumbing/netstring.h>
#include <clplumbing/cpulimits.h>
#include "hb_signal.h"

/* Definitions of API query handlers */
static int api_ping_iflist(const struct ha_msg *msg, struct node_info *node, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_signoff(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_setfilter(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_setsignal(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_nodelist(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_nodestatus(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_nodeweight(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_nodesite(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_nodetype(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_ifstatus(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_iflist(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_clientstatus(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_num_nodes(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_get_parameter(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_get_resources(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_get_uuid(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_get_nodename(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
static int api_set_sendqlen(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason);
gboolean ProcessAnAPIRequest(client_proc_t *client);

struct api_query_handler query_handler_list[] = {
	{API_SIGNOFF, api_signoff},
	{API_SETFILTER, api_setfilter},
	{API_SETSIGNAL, api_setsignal},
	{API_NODELIST, api_nodelist},
	{API_NODESTATUS, api_nodestatus},
	{API_NODEWEIGHT, api_nodeweight},
	{API_NODESITE, api_nodesite},
	{API_NODETYPE, api_nodetype},
	{API_IFSTATUS, api_ifstatus},
	{API_IFLIST, api_iflist},
	{API_CLIENTSTATUS, api_clientstatus},
	{API_NUMNODES, api_num_nodes},
	{API_GETPARM, api_get_parameter},
	{API_GETRESOURCES, api_get_resources},
	{API_GETUUID, api_get_uuid},
	{API_GETNAME, api_get_nodename},
	{API_SET_SENDQLEN, api_set_sendqlen}
};

extern int UseOurOwnPoll;
int debug_client_count = 0;
int total_client_count = 0;
client_proc_t *client_list = NULL;	/* List of all our API clients */
			/* TRUE when any client output still pending */
extern struct node_info *curnode;

static unsigned long client_generation = 0;
#define MAX_CLIENT_GEN 64

static void api_process_request(client_proc_t *client, struct ha_msg *msg);
static void api_send_client_msg(client_proc_t *client, struct ha_msg *msg);
static void api_send_client_status(client_proc_t *client, const char *status, const char *reason);
static void api_remove_client_int(client_proc_t *client, const char *rsn);
static int api_add_client(client_proc_t *chan, struct ha_msg *msg);
static void G_remove_client(gpointer Client);
static gboolean APIclients_input_dispatch(IPC_Channel *chan, gpointer udata);
static void api_process_registration_msg(client_proc_t *, struct ha_msg *);
static gboolean api_check_client_authorization(client_proc_t *client);
static int create_seq_snapshot_table(GHashTable **ptable);
static void destroy_seq_snapshot_table(GHashTable *table);
extern GHashTable *APIAuthorization;

struct seq_snapshot {
	seqno_t generation;
	seqno_t last_seq;
};

static int
should_msg_sendto_client(client_proc_t *client, struct ha_msg *msg)
{
	GHashTable *table;
	struct node_info *thisnode = NULL;
	const char *from;
	cl_uuid_t fromuuid;
	struct seq_snapshot *snapshot;
	const char *cseq;
	const char *cgen;
	seqno_t seq;
	seqno_t gen;
	int ret = 0;
	const char *type;
	struct seqtrack *t;

	if (!client || !msg) {
		cl_log(LOG_ERR, "should_msg_sendto_client: invalid arguemts");
		return FALSE;
	}

	from = ha_msg_value(msg, F_ORIG);
	cseq = ha_msg_value(msg, F_SEQ);
	cgen = ha_msg_value(msg, F_HBGENERATION);

	if (!from || !cseq || !cgen) {
		/* some local generated status messages,
		 * e.g. node dead status message,
		 * return yes */
		return TRUE;
	}

	if (sscanf(cseq, "%lx", &seq) <= 0 || sscanf(cgen, "%lx", &gen) <= 0) {
		cl_log(LOG_ERR, "should_msg_sendto_client: wrong seq/gen format");
		return FALSE;
	}

	if (seq < 0 || gen < 0) {
		cl_log(LOG_ERR, "should_msg_sendto_client: wrong seq/gen number");
		return FALSE;
	}

	cl_get_uuid(msg, F_ORIGUUID, &fromuuid);
	thisnode = lookup_tables(from, &fromuuid);
	if (thisnode == NULL) {
		cl_log(LOG_ERR, "should_msg_sendto_client: node not found in table");
		return FALSE;
	}

	t = &thisnode->track;

	/* if uuid is not found, then it always passes the first restriction */
	if (cl_uuid_is_null(&fromuuid)
	    || (table = client->seq_snapshot_table) == NULL
	    || (snapshot = (struct seq_snapshot *) g_hash_table_lookup(table, &fromuuid)) == NULL) {
		goto nextstep;
	}

	ret = gen > snapshot->generation || (gen == snapshot->generation && seq >= snapshot->last_seq);

	/* check if there is any retransmission going on if not, we can delete this item */
	if (t->nmissing == 0) {
		if (ANYDEBUG) {
			cl_log(LOG_DEBUG, "Removing one entry in seq snapshot hash table for node %s", thisnode->nodename);
		}
		if (!g_hash_table_remove(table, &fromuuid)) {
			cl_log(LOG_ERR, "should_msg_sendto_client: g_hash_table_remove failed");
			return FALSE;
		}
		free(snapshot);

		if (g_hash_table_size(table) == 0) {
			if (ANYDEBUG) {
				cl_log(LOG_DEBUG, "destroying the seq snapshot hash table");
			}
			g_hash_table_destroy(table);
			client->seq_snapshot_table = NULL;
		}
	}

	if (ret == 0) {
		/* hmmmm.... this message is dropped */
		cl_log(LOG_WARNING, "message is dropped ");
		cl_log_message(LOG_WARNING, msg);
		return FALSE;
	}

nextstep:
	/* We only worry about the ordering of certain types of messages
	 * and then only when they arrive out of order.
	 * Basically we implement a barrier at the receipt of each
	 * message of this type.
	 */
	if ((type = ha_msg_value(msg, F_TYPE)) == NULL) {
		cl_log(LOG_ERR, "no type field found");
		return FALSE;
	}

	if (strcmp(type, T_APICLISTAT) != 0 || t->nmissing == 0) {
		return TRUE;
	}

	if (seq > t->first_missing_seq) {
		/* We cannot deliver the message now, queue it */
		struct ha_msg *copymsg = ha_msg_copy(msg);
		if (!copymsg) {
			cl_log(LOG_ERR, "msg copy failed");
			return FALSE;
		}
		t->client_status_msg_queue = g_list_append(t->client_status_msg_queue, copymsg);
		if (ANYDEBUG) {
			cl_log(LOG_DEBUG, "one entry added to client_status_msg_queue for node %s", thisnode->nodename);
		}
		return FALSE;
	}

	return TRUE;
}

/*
 *	One client pointer per input FIFO.  It's indexed by file descriptor, so
 *	it's not densely populated.  We use this in conjunction with select(2)
 */

/*
 * The original structure of this code was due to
 * Marcelo Tosatti <marcelo@conectiva.com.br>
 *
 * It has been significantly and repeatedly mangled into nearly unrecognizable
 * oblivion by Alan Robertson <alanr@unix.sh>
 *
 */
/*
 *	Monitor messages.  Pass them along to interested clients (if any)
 */
void
api_heartbeat_monitor(struct ha_msg *msg, int msgtype, const char *iface)
{
	const char *clientid;
	client_proc_t *client;
	client_proc_t *nextclient;

	/* This kicks out most messages, since debug clients are rare */

	if ((msgtype & DEBUGTREATMENTS) != 0 && debug_client_count <= 0) {
		return;
	}

	/* Verify that we understand what kind of message we've got here */

	if ((msgtype & ALLTREATMENTS) != msgtype || msgtype == 0) {
		cl_log(LOG_ERR, "heartbeat_monitor: unknown msgtype [%d]", msgtype);
		return;
	}

	/* See who this message is addressed to (if anyone) */

	clientid = ha_msg_value(msg, F_TOID);

	for (client = client_list; client != NULL; client = nextclient) {
		/*
		 * "client" might be removed by api_send_client_msg()
		 * so, we'd better fetch the next client now!
		 */
		nextclient = client->next;

		/* Is this message addressed to us? */
		if (clientid != NULL && strcmp(clientid, client->client_id) != 0) {
			continue;
		}
		if (client->chan->ch_status != IPC_CONNECT) {
			continue;
		}

		/* Is this one of the types of messages we're interested in? */

		if ((msgtype & client->desired_types) != 0) {
			if (should_msg_sendto_client(client, msg)) {
				api_send_client_msg(client, msg);
			} else {
				/* This happens when join/leave messages is
				 * received but there are messages before 
				 * that are missing. The join/leave messages
				 * will be queued and not delivered until all
				 * messages before them are received and 
				 * delivered. 
				 */

				/* do nothing */
			}
			if (client->removereason && !client->isindispatch) {
				if (ANYDEBUG) {
					cl_log(LOG_DEBUG, "%s: client is %s", __FUNCTION__, client->client_id);
				}
				api_remove_client_pid(client->pid, client->removereason);
			}
		}

		/* If this is addressed to us, then no one else should get it */
		if (clientid != NULL) {
			break;	/* No one else should get it */
		}
	}
}

/*
 *	Periodically clean up after dead clients...
 *	In case we somehow miss them...
 */
gboolean
api_audit_clients(gpointer p)
{
	client_proc_t *client;
	client_proc_t *nextclient;

	for (client = client_list; client != NULL; client = nextclient) {
		nextclient = client->next;

		if (CL_KILL(client->pid, 0) < 0 && errno == ESRCH) {
			cl_log(LOG_INFO, "api_audit_clients: client %ld died", (long)client->pid);
			client->removereason = NULL;
			api_remove_client_pid(client->pid, "died-audit");
			client = NULL;
		}
	}
	return TRUE;
}

/**********************************************************************
 * API_SETFILTER: Set the types of messages we want to see
 **********************************************************************/
static int
api_setfilter(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *cfmask;
	unsigned mask;
/*
 *	Record the types of messages desired by this client
 *		(desired_types)
 */
	if ((cfmask = ha_msg_value(msg, F_FILTERMASK)) == NULL
	    || (sscanf(cfmask, "%x", &mask) != 1)
	    || (mask & ALLTREATMENTS) == 0) {
		*failreason = "EINVAL";
		return I_API_BADREQ;
	}

	if ((client->desired_types & DEBUGTREATMENTS) == 0 && (mask & DEBUGTREATMENTS) != 0) {
		/* Only allowed to root and to our uid */
		if (client->uid != 0 && client->uid != getuid()) {
			*failreason = "EPERM";
			return I_API_BADREQ;
		}
		++debug_client_count;
	} else if ((client->desired_types & DEBUGTREATMENTS) != 0 && (mask & DEBUGTREATMENTS) == 0) {
		--debug_client_count;
	}
	client->desired_types = mask;
	return I_API_RET;
}

/**********************************************************************
 * API_SIGNOFF: Sign off as a client
 **********************************************************************/

static int
api_signoff(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	/* We send them no reply */
	if (ANYDEBUG) {
		cl_log(LOG_DEBUG, "Signing client %ld off", (long)client->pid);
	}
	if (client->seq_snapshot_table) {
		destroy_seq_snapshot_table(client->seq_snapshot_table);
		client->seq_snapshot_table = NULL;
	}

	client->removereason = API_SIGNOFF;
	return I_API_IGN;
}

/**********************************************************************
 * API_SETSIGNAL: Record the type of signal they want us to send.
 **********************************************************************/

static int
api_setsignal(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *csignal;
	unsigned oursig;

	if ((csignal = ha_msg_value(msg, F_SIGNAL)) == NULL || (sscanf(csignal, "%u", &oursig) != 1)) {
		return I_API_BADREQ;
	}
	/* Validate the signal number in the message ... */
	if (oursig < 0 || oursig == SIGKILL || oursig == SIGSTOP || oursig >= 32) {
		/* These can't be caught (or is a bad signal). */
		*failreason = "EINVAL";
		return I_API_BADREQ;
	}

	client->signal = oursig;
	return I_API_RET;
}

/***********************************************************************
 * API_NODELIST: List the nodes in the cluster
 **********************************************************************/


static int
msg_mod_nodetype(struct ha_msg *resp, const int nodetype)
{
	const char *ntype;
	if (nodetype == PINGNODE_I)
		ntype = PINGNODE;
	else if (nodetype == NORMALNODE_I)
		ntype = NORMALNODE;
	else
		ntype = UNKNOWNNODE;

	return ha_msg_mod(resp, F_NODETYPE, ntype);
}

static int
msg_mod_nodestatus(struct ha_msg *resp, const struct node_info *node)
{
	const char *status = NULL;
	if (node->saved_status_msg)
		status = ha_msg_value(node->saved_status_msg, F_STATUS);
	if (!status)
		status = node->status;
	return ha_msg_mod(resp, F_STATUS, status);
}

static int
api_nodelist(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	int j;
	int last = config->nodecount - 1;

	for (j = 0; j <= last; ++j) {
		struct node_info *node = &config->nodes[j];
		if (ha_msg_mod(resp, F_NODENAME, node->nodename) != HA_OK) {
			cl_log(LOG_ERR, "api_nodelist: cannot mod nodename");
			return I_API_IGN;
		}
		/* Also add node type and status. */
		if (msg_mod_nodetype(resp, node->nodetype) != HA_OK) {
			cl_log(LOG_ERR, "api_nodelist: cannot mod nodetype");
			return I_API_IGN;
		}
		if (msg_mod_nodestatus(resp, node) != HA_OK) {
			cl_log(LOG_ERR, "api_nodelist: cannot mod status");
			return I_API_IGN;
		}
		if (ha_msg_mod(resp, F_APIRESULT, (j == last ? API_OK : API_MORE)) != HA_OK) {
			cl_log(LOG_ERR, "api_nodelist: cannot mod apiresult");
			return I_API_IGN;
		}
		api_send_client_msg(client, resp);
	}
	return I_API_IGN;
}

/**********************************************************************
 * API_NODESTATUS: Return the status of the given node
 *********************************************************************/

static int
api_nodestatus(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *cnode;
	struct node_info *node;

	if ((cnode = ha_msg_value(msg, F_NODENAME)) == NULL || (node = lookup_node(cnode)) == NULL) {
		*failreason = "EINVAL";
		return I_API_BADREQ;
	}
	if (msg_mod_nodestatus(resp, node) != HA_OK) {
		cl_log(LOG_ERR, "api_nodestatus: cannot mod status");
		return I_API_IGN;
	}
	return I_API_RET;
}

/**********************************************************************
 * API_NODEWEIGHT: Return the weight of the given node
 *********************************************************************/

static int
api_nodeweight(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *cnode;
	struct node_info *node;

	if ((cnode = ha_msg_value(msg, F_NODENAME)) == NULL || (node = lookup_node(cnode)) == NULL) {
		*failreason = "EINVAL";
		return I_API_BADREQ;
	}

	if (ha_msg_add_int(resp, F_WEIGHT, node->weight) != HA_OK) {
		cl_log(LOG_ERR, "api_nodeweight: cannot add field");
		return I_API_IGN;
	}
	return I_API_RET;
}

/**********************************************************************
 * API_NODESITE: Return the site of the given node
 *********************************************************************/

static int
api_nodesite(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *cnode;
	struct node_info *node;

	if ((cnode = ha_msg_value(msg, F_NODENAME)) == NULL || (node = lookup_node(cnode)) == NULL) {
		*failreason = "EINVAL";
		return I_API_BADREQ;
	}
	if (ha_msg_add(resp, F_SITE, node->site) != HA_OK) {
		cl_log(LOG_ERR, "api_nodesite: cannot add field");
		return I_API_IGN;
	}
	return I_API_RET;
}

/**********************************************************************
 * API_NODETYPE: Return the type of the given node
 *********************************************************************/

static int
api_nodetype(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *cnode;
	struct node_info *node;

	if ((cnode = ha_msg_value(msg, F_NODENAME)) == NULL || (node = lookup_node(cnode)) == NULL) {
		*failreason = "EINVAL";
		return I_API_BADREQ;
	}
	if (msg_mod_nodetype(resp, node->nodetype) != HA_OK) {
		cl_log(LOG_ERR, "api_nodetype: cannot mod nodetype");
		return I_API_IGN;
	}
	return I_API_RET;
}

/**********************************************************************
 * API_IFLIST: List the interfaces for the given machine
 *********************************************************************/

static int
api_iflist(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	struct link *lnk;
	int j;
	int last = config->nodecount - 1;
	const char *cnode;
	struct node_info *node;

	if ((cnode = ha_msg_value(msg, F_NODENAME)) == NULL || (node = lookup_node(cnode)) == NULL) {
		*failreason = "EINVAL";
		return I_API_BADREQ;
	}
	if (node->nodetype == PINGNODE_I) {
		return api_ping_iflist(msg, node, resp, client, failreason);
	}

	/* Find last link... */
	for (j = 0; (lnk = &node->links[j], lnk->name); ++j) {
		last = j;
	}
	/* Don't report on ping links */
	if (node->links[last].isping) {
		--last;
	}

	for (j = 0; j <= last; ++j) {
		if (node->links[j].isping) {
			continue;
		}
		if (ha_msg_mod(resp, F_IFNAME, node->links[j].name) != HA_OK) {
			cl_log(LOG_ERR, "api_iflist: cannot mod ifname");
			return I_API_IGN;
		}
		if (ha_msg_mod(resp, F_STATUS, node->links[j].status) != HA_OK) {
			cl_log(LOG_ERR, "api_iflist: cannot mod ifstatus");
			return I_API_IGN;
		}
		if (ha_msg_mod(resp, F_APIRESULT, (j == last ? API_OK : API_MORE)) != HA_OK) {
			cl_log(LOG_ERR, "api_iflist: cannot mod apiresult");
			return I_API_IGN;
		}
		api_send_client_msg(client, resp);
	}
	return I_API_IGN;
}

static int
api_ping_iflist(const struct ha_msg *msg, struct node_info *node, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	int j;
	struct link *lnk;

	for (j = 0; (lnk = &node->links[j], lnk->name); ++j) {
		if (strcmp(lnk->name, node->nodename) == 0) {
			if (ha_msg_mod(resp, F_IFNAME, lnk->name) != HA_OK) {
				cl_log(LOG_ERR, "api_ping_iflist: cannot mod ifname");
				return I_API_IGN;
			}
			if (ha_msg_mod(resp, F_STATUS, lnk->status) != HA_OK) {
				cl_log(LOG_ERR, "api_ping_iflist: cannot mod ifstatus");
				return I_API_IGN;
			}
			if (ha_msg_mod(resp, F_APIRESULT, API_OK) != HA_OK) {
				cl_log(LOG_ERR, "api_ping_iflist: cannot mod apiresult");
				return I_API_IGN;
			}
			return I_API_RET;
		}
	}
	*failreason = "UhOh";
	return I_API_BADREQ;
}

/**********************************************************************
 * API_IFSTATUS: Return the status of the given interface...
 *********************************************************************/

static int
api_ifstatus(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *cnode;
	struct node_info *node;
	const char *ciface;
	struct link *iface;

	if ((cnode = ha_msg_value(msg, F_NODENAME)) == NULL
	    || (node = lookup_node(cnode)) == NULL
	    || (ciface = ha_msg_value(msg, F_IFNAME)) == NULL
	    || (iface = lookup_iface(node, ciface)) == NULL) {
		*failreason = "EINVAL";
		return I_API_BADREQ;
	}
	if (ha_msg_mod(resp, F_STATUS, iface->status) != HA_OK) {
		cl_log(LOG_ERR, "api_ifstatus: cannot add field/1");
		cl_log(LOG_ERR, "name: %s, value: %s (if=%s)", F_STATUS, iface->status, ciface);
		return I_API_IGN;
	}
	return I_API_RET;
}

/**********************************************************************
 * API_CLIENTSTATUS: Return the status of the given client on a node
 *********************************************************************/

static int
api_clientstatus(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *cnode;
	const char *cname;
	const char *our_clientid;
	struct node_info *node;
	struct ha_msg *m;
	int ret = HA_FAIL;

	if ((cnode = ha_msg_value(msg, F_NODENAME)) == NULL
	    || (cname = ha_msg_value(msg, F_CLIENTNAME)) == NULL
	    || (our_clientid = ha_msg_value(msg, F_FROMID)) == NULL
	    || (node = lookup_node(cnode)) == NULL) {
		*failreason = "EINVAL";
		return I_API_BADREQ;
	}
	if (ha_msg_add(resp, F_SUBTYPE, T_RCSTATUS) != HA_OK) {
		ha_log(LOG_ERR, "api_clientstatus: cannot add field");
		*failreason = "ENOMEM";
		return I_API_BADREQ;
	}
	/* returns client status on local node */
	if (strcmp(cnode, curnode->nodename) == 0) {

		if (find_client(cname, NULL) != NULL)
			ret = ha_msg_add(resp, F_CLIENTSTATUS, ONLINESTATUS);
		else
			ret = ha_msg_add(resp, F_CLIENTSTATUS, OFFLINESTATUS);

		if (ret != HA_OK) {
			ha_log(LOG_ERR, "api_clientstatus: cannot add field");
			*failreason = "ENOMEM";
			return I_API_BADREQ;
		}
		return I_API_RET;
	}

	if (strcmp(node->status, ACTIVESTATUS) != 0) {
		if (ha_msg_add(resp, F_CLIENTSTATUS, OFFLINESTATUS) != HA_OK) {
			ha_log(LOG_ERR, "api_clientstatus: cannot add field");
			*failreason = "ENOMEM";
			return I_API_BADREQ;
		}
		return I_API_RET;
	}
	if ((m = ha_msg_new(0)) == NULL
	    || ha_msg_add(m, F_TYPE, T_QCSTATUS) != HA_OK
	    || ha_msg_add(m, F_TO, cnode) != HA_OK
	    || ha_msg_add(m, F_CLIENTNAME, cname) != HA_OK
	    || ha_msg_add(m, F_FROMID, our_clientid) != HA_OK) {

		ha_log(LOG_ERR, "api_clientstatus: cannot add field");
		*failreason = "ENOMEM";
		return I_API_BADREQ;
	}
	if (send_cluster_msg(m) != HA_OK) {
		ha_log(LOG_ERR, "api_clientstatus: send_cluster_msg failed");
		*failreason = "ECOMM";
		return I_API_BADREQ;
	}
	/*
	 * Here we return I_API_IGN because currently we don't know
	 * the answer yet.
	 */
	return I_API_IGN;
}

/**********************************************************************
 * API_NUM_NODES: Return the number of normal nodes
 *********************************************************************/

static int
api_num_nodes(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	int ret;
	int num_nodes = 0;
	int i;

	for (i = 0; i < config->nodecount; i++) {
		if (config->nodes[i].nodetype == NORMALNODE_I) {
			num_nodes++;
		}
	}

	ret = ha_msg_add_int(resp, F_NUMNODES, num_nodes);
	if (ret != HA_OK) {
		cl_log(LOG_ERR, "%s: adding num_nodes field failed", __FUNCTION__);
		*failreason = "adding msg field failed";
		return I_API_BADREQ;
	}

	return I_API_RET;

}

/**********************************************************************
 * API_GET_PARAMETER: Return the value of the given parameter...
 *********************************************************************/

static int
api_get_parameter(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *pname;
	const char *pvalue;

	if ((pname = ha_msg_value(msg, F_PNAME)) == NULL) {
		*failreason = "EINVAL";
		return I_API_BADREQ;
	}
	/* "crm" is the deprecated alias to "pacemaker" */
	if (!strcmp(KEY_REL2, pname))
		pname = KEY_PACEMAKER;
	if ((pvalue = GetParameterValue(pname)) != NULL) {
		if (ha_msg_mod(resp, F_PVALUE, pvalue) != HA_OK) {
			cl_log(LOG_ERR, "api_parameter: cannot add " F_PVALUE " field to message");
		}
	}
	return I_API_RET;
}

static int
api_get_resources(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *ret;

	if (!DoManageResources) {
		*failreason = "Resource is managed by crm. Use crm tool to query resource";
		return I_API_BADREQ;
	}

	ret = hb_rsc_resource_state();
	if (ha_msg_mod(resp, F_RESOURCES, ret) != HA_OK) {
		cl_log(LOG_ERR, "api_get_resources: cannot add " F_RESOURCES " field to message");
	}
	return I_API_RET;
}

static int
api_get_uuid(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *query_nodename;
	cl_uuid_t uuid;

	if ((query_nodename = ha_msg_value(msg, F_QUERYNAME)) == NULL) {
		*failreason = "no query node name found";
		return I_API_BADREQ;
	}

	if (nodename2uuid(query_nodename, &uuid) != HA_OK) {
		return I_API_RET;
	}

	if (cl_msg_moduuid(resp, F_QUERYUUID, &uuid) != HA_OK) {
		cl_log(LOG_ERR, "api_get_uuid: cannnot add" F_QUERYUUID " field to message");
		return I_API_RET;
	}

	return I_API_RET;
}

static int
api_get_nodename(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	const char *nodename;
	cl_uuid_t query_uuid;

	if (cl_get_uuid(msg, F_QUERYUUID, &query_uuid) != HA_OK) {
		*failreason = "no query node name found";
		return I_API_BADREQ;
	}

	if ((nodename = uuid2nodename(&query_uuid)) == NULL) {
		cl_log(LOG_ERR, "api_get_nodename: nodename not found in map table");
		return I_API_RET;
	}

	if (ha_msg_mod(resp, F_QUERYNAME, nodename) != HA_OK) {
		cl_log(LOG_ERR, "api_get_nodename: cannnot add" F_QUERYNAME " field to message");
		return I_API_RET;
	}

	return I_API_RET;

}

static int
api_set_sendqlen(const struct ha_msg *msg, struct ha_msg *resp, client_proc_t *client, const char **failreason)
{
	int length;
	int ret = ha_msg_value_int(msg, F_SENDQLEN, &length);

	if (ret != HA_OK) {
		cl_log(LOG_ERR, "api_set_sendqlen: getting field F_SENDQLEN failed");
		return I_API_IGN;
	}

	if (length <= 0) {
		cl_log(LOG_ERR, "api_set_sendqlen: invalid length value(%d)", length);
		return I_API_IGN;
	}

	cl_log(LOG_INFO, "the send queue length from heartbeat to client %s is set to %d", client->client_id, length);

	client->chan->ops->set_send_qlen(client->chan, length);

	return I_API_IGN;

}

static int
add_client_gen(client_proc_t *client, struct ha_msg *msg)
{
	char buf[MAX_CLIENT_GEN];

	memset(buf, 0, MAX_CLIENT_GEN);
	snprintf(buf, MAX_CLIENT_GEN, "%d", client->cligen);
	return ha_msg_mod(msg, F_CLIENT_GENERATION, buf);
}

/*
 * Process an API request message from one of our clients
 */
static void
api_process_request(client_proc_t *fromclient, struct ha_msg *msg)
{
	const char *msgtype;
	const char *reqtype;
	const char *fromid;
	const char *pid;
	client_proc_t *client;
	struct ha_msg *resp = NULL;
	const char *failreason = NULL;
	int x;

	if (msg == NULL || (msgtype = ha_msg_value(msg, F_TYPE)) == NULL) {
		cl_log(LOG_ERR, "api_process_request: bad message type");
		goto freeandexit;
	}

	/* Things that aren't T_APIREQ are general packet xmit requests... */
	if (strcmp(msgtype, T_APIREQ) != 0) {

		/* Only named clients can send out packets to clients */

		if (fromclient->iscasual) {
			cl_log(LOG_INFO, "api_process_request: general message from casual client!");
			/* Bad Client! */
			fromclient->removereason = "badclient";
			goto freeandexit;
		}

		/* We put their client ID info in the packet as the F_FROMID */
		if (ha_msg_mod(msg, F_FROMID, fromclient->client_id) != HA_OK) {
			cl_log(LOG_ERR, "api_process_request: cannot add F_FROMID field");
			goto freeandexit;
		}
		/* Is this too restrictive? */
		/* We also put their client ID info in the packet as F_TOID */

		/*
		 * N.B.:  This restriction exists because of security concerns
		 * It would be imprudent to remove it without a lot
		 * of thought and proof of safety.   In fact, as of right
		 * now, without some enhancements, it would be quite unsafe.
		 *
		 * The right way to talk to something that's has an API
		 * and is running on every machine is probably to use it's
		 * API, and not talk crosstalk through the core heartbeat
		 * messaging service, and compromise interface safety for
		 * every application using the API.
		 * (ALR - 17 January 2004)
		 */

		if (ha_msg_mod(msg, F_TOID, fromclient->client_id) != HA_OK) {
			cl_log(LOG_ERR, "api_process_request: cannot add F_TOID field");
			goto freeandexit;
		}
		if (DEBUGDETAILS) {
			cl_log(LOG_DEBUG, "Sending API message to cluster...");
			cl_log_message(LOG_DEBUG, msg);
		}

		if (add_client_gen(fromclient, msg) != HA_OK) {
			cl_log(LOG_ERR, "api_process_request: add client generation to ha_msg failed");
			ha_msg_del(msg);
			msg = NULL;
			return;
		}

		if (send_cluster_msg(msg) != HA_OK) {
			cl_log(LOG_ERR, "api_process_request: cannot forward message to cluster");
		}
		msg = NULL;
		return;
	}

	/* It must be a T_APIREQ request */

	fromid = ha_msg_value(msg, F_FROMID);
	pid = ha_msg_value(msg, F_PID);
	reqtype = ha_msg_value(msg, F_APIREQ);

	if ((fromid == NULL && pid == NULL) || reqtype == NULL) {
		cl_log(LOG_ERR, "api_process_request: no fromid/pid/reqtype in message.");
		goto freeandexit;
	}

	/*
	 * Create the response message
	 */
	if ((resp = ha_msg_new(4)) == NULL) {
		cl_log(LOG_ERR, "api_process_request: out of memory/1");
		goto freeandexit;
	}

	/* API response messages are of type T_APIRESP */
	if (ha_msg_add(resp, F_TYPE, T_APIRESP) != HA_OK) {
		cl_log(LOG_ERR, "api_process_request: cannot add field/2");
		goto freeandexitresp;
	}
	/* Echo back the type of API request we're responding to */
	if (ha_msg_add(resp, F_APIREQ, reqtype) != HA_OK) {
		cl_log(LOG_ERR, "api_process_request: cannot add field/3");
		goto freeandexitresp;
	}

	if ((client = find_client(fromid, pid)) == NULL) {
		cl_log(LOG_ERR, "api_process_request: msg from non-client");
		goto freeandexitresp;
	}

	/* See if they correctly stated their client id information... */
	if (client != fromclient) {
		cl_log(LOG_ERR, "Client mismatch! (impersonation?)");
		cl_log(LOG_INFO, "pids (%ld vs %ld), Client IDs (%s vs %s)", (long)client->pid, (long)fromclient->pid, client->client_id,
		       fromclient->client_id);
		goto freeandexitresp;
	}

	for (x = 0; x < DIMOF(query_handler_list); x++) {

		int ret;

		if (strcmp(reqtype, query_handler_list[x].queryname) == 0) {
			ret = query_handler_list[x].handler(msg, resp, client, &failreason);
			switch (ret) {
			case I_API_IGN:
				goto freeandexitresp;
			case I_API_RET:
				if (ha_msg_mod(resp, F_APIRESULT, API_OK)
				    != HA_OK) {
					cl_log(LOG_ERR, "api_process_request: cannot add field/8.1");
					goto freeandexitresp;
				}
				api_send_client_msg(client, resp);
				goto freeandexitresp;

			case I_API_BADREQ:
				goto bad_req;
			}
		}
	}

	/********************************************************************
	 * Unknown request type...
	 ********************************************************************/
	cl_log(LOG_ERR, "Unknown API request");

	/* Common error return handling */
bad_req:
	cl_log(LOG_ERR, "api_process_request: bad request [%s]", reqtype);
	cl_log_message(LOG_ERR, msg);
	if (ha_msg_add(resp, F_APIRESULT, API_BADREQ) != HA_OK) {
		cl_log(LOG_ERR, "api_process_request: cannot add field/11");
		goto freeandexitresp;
	}
	if (failreason) {
		if (ha_msg_add(resp, F_COMMENT, failreason) != HA_OK) {
			cl_log(LOG_ERR, "api_process_request: cannot add failreason");
		}
	}
	api_send_client_msg(client, resp);
freeandexitresp:
	ha_msg_del(resp);
	resp = NULL;
freeandexit:
	if (msg != NULL) {
		ha_msg_del(msg);
		msg = NULL;
	}
}

/* Process a registration request from a potential client */
void
process_registerevent(IPC_Channel *chan, gpointer user_data)
{
	client_proc_t *client;

	if ((client = MALLOCT(client_proc_t)) == NULL) {
		cl_log(LOG_ERR, "unable to add client [no memory]");
		chan->ops->destroy(chan);
		return;
	}
	if (ANYDEBUG) {
		cl_log(LOG_DEBUG, "process_registerevent() {");
	}

	/* FIXME do we want to set a different default send_qlen,
	 * as per some configuration directive? */
	/* Zap! */
	memset(client, 0, sizeof(*client));
	client->pid = 0;
	client->desired_types = DEFAULTREATMENT;
	client->signal = 0;
	client->chan = chan;
	client->gsource = G_main_add_IPC_Channel(PRI_CLIENTMSG, chan, FALSE, APIclients_input_dispatch, client, G_remove_client);
	G_main_setdescription((GSource *)client->gsource, "API client");
	G_main_setmaxdispatchdelay((GSource *)client->gsource, config->heartbeat_ms);
	G_main_setmaxdispatchtime((GSource *)client->gsource, 100);
	if (ANYDEBUG) {
		cl_log(LOG_DEBUG, "client->gsource = 0x%lx", (unsigned long)client->gsource);
	}

	client->next = client_list;
	client_list = client;
	total_client_count++;
	if (ANYDEBUG) {
		cl_log(LOG_DEBUG, "}/*process_registerevent*/;");
	}
}

static void
destroy_pair(gpointer key, gpointer value, gpointer user_data)
{
	if (value) {
		free(value);
	}
}

static void
destroy_seq_snapshot_table(GHashTable *table)
{
	if (ANYDEBUG) {
		cl_log(LOG_DEBUG, "Destroying seq snapshot hash table");
	}

	if (table) {
		g_hash_table_foreach(table, destroy_pair, NULL);
		g_hash_table_destroy(table);
	}
	return;
}

static int
create_seq_snapshot_table(GHashTable **ptable)
{
	GHashTable *table = NULL;
	int i;

	if (!ptable) {
		cl_log(LOG_ERR, "create_seq_snapshot_table: nvalid arguments");
		return HA_FAIL;
	}

	*ptable = NULL;
	for (i = 0; i < config->nodecount; i++) {

		struct node_info *node = &config->nodes[i];
		struct seqtrack *t = &node->track;

		if (cl_uuid_is_null(&node->uuid)) {
			continue;
		}

		if (t->nmissing > 0) {
			struct seq_snapshot *snapshot;

			snapshot = (struct seq_snapshot *)
			    malloc(sizeof(struct seq_snapshot));

			if (snapshot == NULL) {
				cl_log(LOG_ERR, "allocating memory for seq_snapshot failed");
				return HA_FAIL;
			}

			snapshot->last_seq = t->last_seq;
			snapshot->generation = t->generation;

			if (table == NULL) {
				if (ANYDEBUG) {
					cl_log(LOG_DEBUG, "Creating seq snapshot hash table");
				}
				table = g_hash_table_new(uuid_hash, uuid_equal);
				if (table == NULL) {
					cl_log(LOG_ERR, "creating hashtable for seq_snapshot failed");
					return HA_FAIL;
				}
			}
			if (ANYDEBUG) {
				cl_log(LOG_DEBUG, "Creating one entry in seq snapshot hash table for node %s", node->nodename);
			}

			g_hash_table_insert(table, &node->uuid, snapshot);

		} else {
			if (ANYDEBUG) {
				cl_log(LOG_DEBUG, "create_seq_snapshot_table: no missing packets found for node %s", node->nodename);
			}
		}
	}

	*ptable = table;

	return HA_OK;
}

/*
 *	Register a new client.
 */
static void
api_process_registration_msg(client_proc_t *client, struct ha_msg *msg)
{
	const char *msgtype;
	const char *reqtype;
	const char *fromid;
	const char *pid;
	struct ha_msg *resp = NULL;
	client_proc_t *fcli = NULL;
	const char *failreason = NULL;
	const char *api_retcode = API_OK;

	char deadtime[64];
	char keepalive[64];
	char logfacility[64];
	char hbversion[64];

	/* set the client generation */
	client->cligen = client_generation++;

	if (msg == NULL
	    || (msgtype = ha_msg_value(msg, F_TYPE)) == NULL
	    || (reqtype = ha_msg_value(msg, F_APIREQ)) == NULL
	    || strcmp(msgtype, T_APIREQ) != 0
	    || strcmp(reqtype, API_SIGNON) != 0) {
		cl_log(LOG_ERR, "api_process_registration_msg: bad message");
		cl_log_message(LOG_ERR, msg);
		goto del_msg;
	}
	fromid = ha_msg_value(msg, F_FROMID);
	pid = ha_msg_value(msg, F_PID);

	if (fromid == NULL && pid == NULL) {
		cl_log(LOG_ERR, "api_process_registration_msg: no fromid in msg");
		goto del_msg;
	}
	if (DEBUGDETAILS) {
		cl_log(LOG_DEBUG, "api_process_registration_msg(%s, %s, %s)",
			msgtype, pid, (fromid == NULL ? "nullfrom" : fromid));
	}

	/*
	 *      Create the response message
	 */
	if ((resp = ha_msg_new(10)) == NULL) {
		cl_log(LOG_ERR, "api_process_registration_msg: out of memory/1");
		goto del_msg;
	}
	if (ha_msg_add(resp, F_TYPE, T_APIRESP) != HA_OK) {
		cl_log(LOG_ERR, "api_process_registration_msg: cannot add field/2");
		goto del_rsp_and_msg;
	}
	if (ha_msg_add(resp, F_APIREQ, reqtype) != HA_OK) {
		cl_log(LOG_ERR, "api_process_registration_msg: cannot add field/3");
		goto del_rsp_and_msg;
	}

	client->pid = atoi(pid);
	/*
	 *      Sign 'em up.
	 */
	if (!api_add_client(client, msg)) {
		cl_log(LOG_ERR, "api_process_registration_msg: cannot add client(%s)", client->client_id);
		failreason = client->removereason;
		if (!failreason)
			failreason = "cannot add client";
	}

	/* Make sure we can find them in the table... */
	if (failreason == NULL && (fcli = find_client(fromid, pid)) == NULL) {
		cl_log(LOG_ERR, "api_process_registration_msg: cannot find client");
		failreason = "cannot locate client";
	}
	if (failreason == NULL && fcli != client) {
		cl_log(LOG_ERR, "api_process_registration_msg: found wrong client");
		failreason = "found wrong client";
	}

	/* Hopefully, everything went well.
	 * Now create a table to record sequence/generation number
	 * for each node if necessary
	 */

	client->seq_snapshot_table = NULL;
	if (failreason == NULL && create_seq_snapshot_table(&client->seq_snapshot_table) != HA_OK) {
		cl_log(LOG_ERR, "api_process_registration_msg: creating seq snapshot table failed");
		failreason = "cannot create sequence snapshot table";
	}
	if (failreason != NULL) {
		ha_msg_add(resp, F_COMMENT, failreason);
		api_retcode = API_BADREQ;
	}
	if (ha_msg_mod(resp, F_APIRESULT, api_retcode) != HA_OK) {
		cl_log(LOG_ERR, "api_process_registration_msg: cannot add field/4");
		goto del_rsp_and_msg;
		return;
	}

	snprintf(deadtime, sizeof(deadtime), "%lx", config->deadtime_ms);
	snprintf(keepalive, sizeof(keepalive), "%lx", config->heartbeat_ms);
	snprintf(logfacility, sizeof(logfacility), "%d", config->log_facility);
	snprintf(hbversion, sizeof(hbversion), "%s (%s)",
		GetParameterValue(KEY_HBVERSION), get_hg_version());

	/* Add deadtime and keepalive time to the response */
	if ((ha_msg_add(resp, F_DEADTIME, deadtime) != HA_OK)
	    || (ha_msg_add(resp, F_KEEPALIVE, keepalive) != HA_OK)
	    || (ha_msg_mod(resp, F_NODENAME, localnodename) != HA_OK)
	    || (ha_msg_mod(resp, KEY_HBVERSION, hbversion) != HA_OK)
	    || (ha_msg_mod(resp, KEY_PACEMAKER, GetParameterValue(KEY_PACEMAKER)) != HA_OK)
	    || (ha_msg_add(resp, F_LOGFACILITY, logfacility) != HA_OK)) {
		cl_log(LOG_ERR, "api_process_registration_msg: cannot add field/4");
		goto del_rsp_and_msg;
	}
	if (ANYDEBUG) {
		cl_log(LOG_DEBUG, "Signing on API client %ld (%s)",
			(long)client->pid, (client->iscasual ? "'casual'" : client->client_id));
	}
	api_send_client_msg(client, resp);
del_rsp_and_msg:
	if (resp != NULL) {
		ha_msg_del(resp);
		resp = NULL;
	}
del_msg:
	if (msg != NULL) {
		ha_msg_del(msg);
		msg = NULL;
	}
}

static void
api_send_client_status(client_proc_t *client, const char *status, const char *reason)
{
	struct ha_msg *msg;
	if (client->iscasual) {
		return;
	}

	/*
	 * Create the status message
	 */
	if ((msg = ha_msg_new(4)) == NULL) {
		cl_log(LOG_ERR, "api_send_client_status: out of memory/1");
		return;
	}

	if (ha_msg_add(msg, F_TYPE, T_APICLISTAT) != HA_OK
	    || ha_msg_add(msg, F_STATUS, status) != HA_OK
	    || ha_msg_add(msg, F_FROMID, client->client_id) != HA_OK
	    || ha_msg_add(msg, F_TOID, client->client_id) != HA_OK
	    || ha_msg_add(msg, F_ORIG, curnode->nodename) != HA_OK
	    || (reason != NULL && ha_msg_add(msg, F_COMMENT, reason) != HA_OK)) {
		cl_log(LOG_ERR, "api_send_client_status: cannot add fields");
		ha_msg_del(msg);
		msg = NULL;
		return;
	}

	if (add_client_gen(client, msg) != HA_OK) {
		cl_log(LOG_ERR, "api_send_client_status: cannot add client generation");
		ha_msg_del(msg);
		msg = NULL;
		return;
	}

	if (strcmp(status, LEAVESTATUS) == 0) {
		/* Make sure they know they're signed off... */
		api_send_client_msg(client, msg);
	}

	if (send_cluster_msg(msg) != HA_OK) {
		cl_log(LOG_ERR, "api_send_client_status: cannot send message to cluster");
	}
	msg = NULL;
}

/*
 *	Send a message to a client process.
 */
static void
api_send_client_msg(client_proc_t *client, struct ha_msg *msg)
{
	if (msg2ipcchan(msg, client->chan) != HA_OK) {
		if (!client->removereason) {
			if (client->chan->failreason[0] == EOS) {
				client->removereason = "sendfail";
			} else {
				client->removereason = client->chan->failreason;
			}
		}
	}

	if (CL_KILL(client->pid, client->signal) < 0 && errno == ESRCH) {
		if (ANYDEBUG) {
			cl_log(LOG_DEBUG, "api_send_client: client %ld died", (long)client->pid);
		}
		if (!client->removereason) {
			client->removereason = "died";
		}
	}
}

int
api_remove_client_pid(pid_t c_pid, const char *reason)
{
	char cpid[20];
	int backlog_processed = 0;
	client_proc_t *client;

	snprintf(cpid, sizeof(cpid) - 1, "%d", c_pid);
	if ((client = find_client(NULL, cpid)) == NULL) {
		return 0;
	}

	client->removereason = reason;
	client->isindispatch = TRUE;	/* avoid recursion */
	while (client->chan
	       && client->chan->recv_queue->current_qlen > 0
	       && (!reason || strcmp(reason, API_SIGNOFF) != 0)) {

		ProcessAnAPIRequest(client);
		if ((++backlog_processed & 0x3f) == 0) {
			/* FIXME backlog processing should be done from a
			 * dedicated backlog processing callback from the
			 * mainloop, so we won't stall other callback for too
			 * long. For now, just try to avoid being killed by our
			 * busy-loop protection. */
			cl_cpu_limit_update();
		}
	}
	client->isindispatch = FALSE;

	G_main_del_IPC_Channel(client->gsource);
	/* Should trigger G_remove_client (below) */
	return 1;
}

static void
G_remove_client(gpointer Client)
{
	client_proc_t *client = Client;
	const char *reason;

	reason = client->removereason ? client->removereason : "?";
	if (ANYDEBUG) {
		cl_log(LOG_DEBUG, "G_remove_client(pid=%d, reason='%s' gsource=0x%lx) {",
			client->pid, reason, (unsigned long)client->gsource);
	}

	api_remove_client_int(client, reason);
	if (ANYDEBUG) {
		cl_log(LOG_DEBUG, "}/*G_remove_client;*/");
	}
}

/*
 *	Make this client no longer a client ;-)
 *	Should only be called by G_remove_client().
 *	G_remove_client gets called by the API code when the API object
 *	gets removed. It can also get called by G_main_del_fd().
 */

static void
api_remove_client_int(client_proc_t *req, const char *reason)
{
	client_proc_t *prev = NULL;
	client_proc_t *client;

	--total_client_count;

	if ((req->desired_types & DEBUGTREATMENTS) != 0) {
		--debug_client_count;
	}

	/* Locate the client data structure in our list */

	for (client = client_list; client != NULL; client = client->next) {
		/* Is this the client? */
		if (client->pid == req->pid) {
			if (ANYDEBUG) {
				const char *id = client->iscasual ? "casual" : client->client_id;
				cl_log(LOG_DEBUG, "api_remove_client_int: removing '%s' pid [%ld] reason: %s",
					id, (long)req->pid, reason);
			}
			if (prev == NULL) {
				client_list = client->next;
			} else {
				prev->next = client->next;
			}

			break;
		}
		prev = client;
	}

	if (req == client) {

		api_send_client_status(req, LEAVESTATUS, reason);
		/* Zap! */
		memset(client, 0, sizeof(*client));
		free(client);
		client = NULL;

	} else {
		cl_log(LOG_ERR, "api_remove_client_int: could not find pid [%ld]", (long)req->pid);
	}

	return;
}

/* Validate client credentials against the real world */

static gboolean
api_check_client_credentials(client_proc_t *client, uid_t uid, gid_t gid)
{
	IPC_Auth auth;
	guint id;
	int one = 1;
	gboolean result = TRUE;

	int auth_result = IPC_FAIL;
	GHashTable *uidlist = g_hash_table_new(g_direct_hash, g_direct_equal);

	id = (guint) uid;
	g_hash_table_insert(uidlist, GUINT_TO_POINTER(id), &one);
	auth.uid = uidlist;
	auth.gid = NULL;
	auth_result = client->chan->ops->verify_auth(client->chan, &auth);

	if (auth_result != IPC_OK) {
		result = FALSE;

	} else {
		GHashTable *gidlist = g_hash_table_new(g_direct_hash, g_direct_equal);
		id = (guint) gid;
		g_hash_table_insert(gidlist, GUINT_TO_POINTER(id), &one);
		auth.uid = NULL;
		auth.gid = gidlist;

		auth_result = client->chan->ops->verify_auth(client->chan, &auth);

		if (auth_result != IPC_OK && auth_result != IPC_BROKEN) {
			result = FALSE;
		}
		g_hash_table_destroy(gidlist);
	}
	g_hash_table_destroy(uidlist);

	return result;
}

/*
 *	Add the process described in this message to our list of clients.
 *
 *	The following fields are used:
 *	F_PID:		Mandantory.  The client process id.
 *	F_FROMID:	The client's identifying handle.
 *			If omitted, it defaults to the F_PID field as a
 *			decimal integer.
 */
static int
api_add_client(client_proc_t *client, struct ha_msg *msg)
{
	pid_t pid = 0;
	const char *cpid;
	const char *fromid;
	const char *cgid = NULL;
	const char *cuid = NULL;
	long luid = -1;
	long lgid = -1;
	int uid = -1;
	int gid = -1;

	if ((cpid = ha_msg_value(msg, F_PID)) != NULL) {
		pid = atoi(cpid);
	}
	if (pid <= 0 || (CL_KILL(pid, 0) < 0 && errno == ESRCH)) {
		client->removereason = "bad pid";
		cl_log(LOG_WARNING, "api_add_client: bad pid [%ld]", (long)pid);
		return FALSE;
	}

	fromid = ha_msg_value(msg, F_FROMID);

	if (find_client(fromid, NULL) != NULL) {
		cl_log(LOG_WARNING, "duplicate client add request [%s] [%s]",
			(fromid ? fromid : "(nullfromid)"),
			(cpid ? cpid : "(nullcpid)"));
		client->removereason = "duplicate add request";
		return FALSE;
	}

	if (fromid != NULL) {
		strncpy(client->client_id, fromid, sizeof(client->client_id));
		if (atoi(client->client_id) == pid) {
			client->iscasual = 1;
		} else {
			client->iscasual = 0;
		}
	} else {
		snprintf(client->client_id, sizeof(client->client_id)
			 , "%d", pid);
		client->iscasual = 1;
	}

	/* Encourage better realtime behavior by heartbeat */
	client->chan->ops->set_recv_qlen(client->chan, 0);

	if ((cuid = ha_msg_value(msg, F_UID)) == NULL
	    || (cgid = ha_msg_value(msg, F_GID)) == NULL
	    || sscanf(cuid, "%ld", &luid) != 1
	    || sscanf(cgid, "%ld", &lgid) != 1) {

		cl_log_message(LOG_ERR, msg);
		client->removereason = "invalid id info";
		cl_log(LOG_ERR, "Client user/group id is incorrect [%s] => %ld [%s] => %ld",
			cuid == NULL ? "<null>" : cuid, luid, cgid == NULL ? "<null>" : cgid, lgid);
		return FALSE;
	}

	uid = (uid_t)luid;
	gid = (gid_t)lgid;

	if (!api_check_client_credentials(client, uid, gid)) {
		client->removereason = "incorrect/false credentials";
		return FALSE;
	}
	client->uid = uid;
	client->gid = gid;
	if (api_check_client_authorization(client)) {
		api_send_client_status(client, JOINSTATUS, API_SIGNON);
	} else {
		cl_log(LOG_WARNING, "Client [%s] pid %d failed authorization [%s]",
			client->client_id, pid, client->removereason);
		return FALSE;
	}
	return TRUE;
}

static gboolean
api_check_client_authorization(client_proc_t *client)
{
	gpointer gauth = NULL;
	IPC_Auth *auth;
	int auth_result = IPC_FAIL;

	/* If client is casual, or type of client not in authorization table
	 * then default to the "default" authorization category.
	 * otherwise, use the client type's authorization list
	 */
	if (client->iscasual) {
		gauth = g_hash_table_lookup(APIAuthorization, "anon");
		if (gauth == NULL) {
			client->removereason = "apiauth anon missing";
			cl_log(LOG_ERR, "NO auth found for anonymous");
			return FALSE;
		}

	} else if ((gauth = g_hash_table_lookup(APIAuthorization, client->client_id)) == NULL) {
		if ((gauth = g_hash_table_lookup(APIAuthorization, "default")) == NULL) {
			client->removereason = "no default client auth";
			return FALSE;
		}
	}

	auth = gauth;
	if ((long)auth->gid == (long)-1L) {
		cl_log(LOG_DEBUG, "Darn!  -1 gid ptr in api_check_client_authorization");
		abort();
	}
	if (ANYDEBUG) {
		cl_log(LOG_DEBUG, "Checking client authorization for client %s (%ld:%ld)",
			client->client_id, (long)client->uid, (long)client->gid);
	}

	auth_result = client->chan->ops->verify_auth(client->chan, auth);

	if (auth_result == IPC_OK) {
#ifndef GETPID_INCONSISTENT
		if (client->chan->farside_pid > 0) {
			if (client->chan->farside_pid != client->pid) {
				client->removereason = "pid mismatch";
				cl_log(LOG_INFO, "PID mismatch: %d vs farside_pid: %d",
					client->pid, client->chan->farside_pid);
				return FALSE;
			}
		}
#endif
		return TRUE;

	} else if (auth_result == IPC_BROKEN) {
		return TRUE;
	}

	client->removereason = "client failed authorization";
	return FALSE;
}

/*
 *	Find the client that goes with this client id/pid
 */
client_proc_t *
find_client(const char *fromid, const char *cpid)
{
	pid_t pid = -1;
	client_proc_t *client;

	if (cpid != NULL) {
		pid = atoi(cpid);
	}

	for (client = client_list; client != NULL; client = client->next) {
		if (cpid && client->pid == pid) {
			return (client);
		}
		if (fromid && strcmp(fromid, client->client_id) == 0) {
			return (client);
		}
	}
	return (NULL);
}

static gboolean
APIclients_input_dispatch(IPC_Channel *chan, gpointer user_data)
{
	client_proc_t *client = user_data;
	gboolean ret = TRUE;

	hb_signal_process_pending();
	if (DEBUGDETAILS) {
		cl_log(LOG_DEBUG, "APIclients_input_dispatch() {");
	}
	if (chan != client->chan) {
		/* Bad boojum! */
		cl_log(LOG_ERR, "APIclients_input_dispatch chan mismatch");
		ret = FALSE;
		goto getout;
	}
	if (client->removereason) {
		ret = FALSE;
		goto getout;
	}

	/* Process a single API client request */
	client->isindispatch = TRUE;
	ProcessAnAPIRequest(client);
	client->isindispatch = FALSE;

	if (client->removereason) {
		ret = FALSE;
		goto getout;
	}

getout:
	if (DEBUGDETAILS) {
		cl_log(LOG_DEBUG, "return %d;", ret);
		cl_log(LOG_DEBUG, "}/*APIclients_input_dispatch*/;");
	}
	return ret;
}

static gboolean all_clients_running = TRUE;
gboolean
all_clients_pause(void)
{
	client_proc_t *client;

	if (!all_clients_running) {
		return TRUE;
	}

	cl_log(LOG_INFO, "all clients are now paused");

	for (client = client_list; client != NULL; client = client->next) {
		G_main_IPC_Channel_pause(client->gsource);

	}
	all_clients_running = FALSE;
	return TRUE;
}

gboolean
all_clients_resume(void)
{
	client_proc_t *client;

	if (all_clients_running) {
		return TRUE;
	}

	cl_log(LOG_INFO, "all clients are now resumed");

	for (client = client_list; client != NULL; client = client->next) {
		G_main_IPC_Channel_resume(client->gsource);
	}

	all_clients_running = TRUE;

	return TRUE;
}

gboolean
ProcessAnAPIRequest(client_proc_t *client)
{
	struct ha_msg *msg;
	static int consecutive_failures = 0;
	gboolean rc = FALSE;

	if (DEBUGDETAILS) {
		cl_log(LOG_DEBUG, "ProcessAnAPIRequest() {");
	}

	if (!client->chan->ops->is_message_pending(client->chan)) {
		goto getout;
	}

	/* See if we can read the message */
	if ((msg = msgfromIPC(client->chan, 0)) == NULL) {

		/* EOF? */
		if (!IPC_ISRCONN(client->chan)) {
			if (ANYDEBUG) {
				cl_log(LOG_DEBUG, "EOF from client pid %ld", (long)client->pid);
			}
			if (!client->removereason) {
				client->removereason = "EOF";
			}
			goto getout;
		}

		/* None of the above... */
		cl_log(LOG_INFO, "No message from pid %ld", (long)client->pid);
		++consecutive_failures;
		/*
		 * This used to happen because of EOF,
		 * which is now handled above.  This is
		 * good protection to have anyway ;-)
		 */
		if (consecutive_failures >= 10) {
			cl_log(LOG_ERR, "Removing client pid %ld", (long)client->pid);
			client->removereason = "noinput";
			consecutive_failures = 0;
		}
		goto getout;
	}
	consecutive_failures = 0;

	/* Process the API request message... */
	api_heartbeat_monitor(msg, APICALL, "<api>");

	/* First message must be a registration msg */
	if (client->pid == 0) {
		api_process_registration_msg(client, msg);
	} else {
		api_process_request(client, msg);
	}
	msg = NULL;
	rc = TRUE;

	if (!all_clients_running) {
		/* This is a new client,
		 * this allows a client to sign on but further action will be blocked
		 */
		G_main_IPC_Channel_pause(client->gsource);
		rc = FALSE;
	}

getout:
	/* May have gotten a message from 'client' */
	if (CL_KILL(client->pid, 0) < 0 && errno == ESRCH) {
		/* Oops... he's dead */
		if (ANYDEBUG) {
			cl_log(LOG_DEBUG, "Client pid %ld died (input)", (long)client->pid);
		}
		client->removereason = "died";
	}
	if (DEBUGDETAILS) {
		cl_log(LOG_DEBUG, "\treturn %s;", (rc ? "TRUE" : "FALSE"));
		cl_log(LOG_DEBUG, "}/*ProcessAnAPIRequest*/;");
	}

	return rc;
}
