/* $Id: baytech.c,v 1.13.2.1 2004/04/20 08:50:06 alan Exp $ */
/*
 *	Stonith module for BayTech Remote Power Controllers (RPC-x devices)
 *
 *	Copyright (c) 2000 Alan Robertson <alanr@unix.sh>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <portability.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <libintl.h>
#include <sys/wait.h>
#include <glib.h>

#include <stonith/stonith.h>
#define PIL_PLUGINTYPE          STONITH_TYPE
#define PIL_PLUGINTYPE_S        STONITH_TYPE_S
#define PIL_PLUGIN              baytech
#define PIL_PLUGIN_S            "baytech"
#define PIL_PLUGINLICENSE 	LICENSE_LGPL
#define PIL_PLUGINLICENSEURL 	URL_LGPL
#include <pils/plugin.h>

#include "stonith_signal.h"

/*
 * baytechclosepi is called as part of unloading the baytech STONITH plugin.
 * If there was any global data allocated, or file descriptors opened, etc.
 * which is associated with the plugin, and not a single interface
 * in particular, here's our chance to clean it up.
 */

static void
baytechclosepi(PILPlugin*pi)
{
}


/*
 * baytechcloseintf called as part of shutting down the baytech STONITH
 * interface.  If there was any global data allocated, or file descriptors
 * opened, etc.  which is associated with the baytech implementation,
 * here's our chance to clean it up.
 */
static PIL_rc
baytechcloseintf(PILInterface* pi, void* pd)
{
	return PIL_OK;
}

static void *		baytech_new(void);
static void		baytech_destroy(Stonith *);
static int		baytech_set_config_file(Stonith *, const char * cfgname);
static int		baytech_set_config_info(Stonith *, const char * info);
static const char *	baytech_getinfo(Stonith * s, int InfoType);
static int		baytech_status(Stonith * );
static int		baytech_reset_req(Stonith * s, int request, const char * host);
static char **		baytech_hostlist(Stonith  *);
static void		baytech_free_hostlist(char **);

static struct stonith_ops baytechOps ={
	baytech_new,		/* Create new STONITH object	*/
	baytech_destroy,		/* Destroy STONITH object	*/
	baytech_set_config_file,	/* set configuration from file	*/
	baytech_set_config_info,	/* Get configuration from file	*/
	baytech_getinfo,		/* Return STONITH info string	*/
	baytech_status,			/* Return STONITH device status	*/
	baytech_reset_req,		/* Request a reset */
	baytech_hostlist,		/* Return list of supported hosts */
	baytech_free_hostlist		/* free above list */
};

PIL_PLUGIN_BOILERPLATE("1.0", Debug, baytechclosepi);
static const PILPluginImports*  PluginImports;
static PILPlugin*               OurPlugin;
static PILInterface*		OurInterface;
static StonithImports*		OurImports;
static void*			interfprivate;

#define LOG		PluginImports->log
#define MALLOC		PluginImports->alloc
#define STRDUP  	PluginImports->mstrdup
#define FREE		PluginImports->mfree
#define EXPECT_TOK	OurImports->ExpectToken
#define STARTPROC	OurImports->StartProcess

PIL_rc
PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports);

PIL_rc
PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports)
{
	/* Force the compiler to do a little type checking */
	(void)(PILPluginInitFun)PIL_PLUGIN_INIT;

	PluginImports = imports;
	OurPlugin = us;

	/* Register ourself as a plugin */
	imports->register_plugin(us, &OurPIExports);  

	/*  Register our interface implementation */
 	return imports->register_interface(us, PIL_PLUGINTYPE_S
	,	PIL_PLUGIN_S
	,	&baytechOps
	,	baytechcloseintf		/*close */
	,	&OurInterface
	,	(void*)&OurImports
	,	&interfprivate); 
}

#define	DEVICE	"BayTech power switch"

#define N_(text)	(text)
#define _(text)		dgettext(ST_TEXTDOMAIN, text)

/*
 *	I have an RPC-5.  This code has been tested with this switch.
 *
 *	The BayTech switches are quite nice, but the dialogues are a bit of a
 *	pain for mechanical parsing.
 */

struct BayTech {
	const char *			BTid;
	char *				idinfo;
	char *				unitid;
	const struct BayTechModelInfo*	modelinfo;
	pid_t				pid;
	int				rdfd;
	int				wrfd;
	int				config;
	char *				device;
	char *				user;
	char *				passwd;
};

struct BayTechModelInfo {
	const char *	type;		/* Baytech model info */
	size_t		socklen;	/* Length of socket name string */
	struct Etoken *	expect;		/* Expect string before outlet list */
};

static int		parse_socket_line(struct BayTech*,const char *
,			int *, char *);

static const char * BTid = "BayTech-Stonith";
static const char * NOTbtid = "Hey, dummy this has been destroyed (BayTech)";

#define	ISBAYTECH(i)	(((i)!= NULL && (i)->pinfo != NULL)	\
	&& ((struct BayTech *)(i->pinfo))->BTid == BTid)

#ifndef MALLOCT
#	define MALLOCT(t) ((t *)(MALLOC(sizeof(t))))
#endif

#define	ISCONFIGED(i)	(ISBAYTECH(i) && ((struct BayTech *)(i->pinfo))->config)

#define WHITESPACE	" \t\n\r\f"

#define	REPLSTR(s,v)	{					\
			if ((s) != NULL) {			\
				FREE(s);			\
				(s)=NULL;			\
			}					\
			(s) = STRDUP(v);			\
			if ((s) == NULL) {			\
				syslog(LOG_ERR, _("out of memory"));\
			}					\
			}

/*
 *	Different expect strings that we get from the Baytech
 *	Remote Power Controllers...
 */

#define BAYTECHASSOC	"Bay Technical Associates"

static struct Etoken EscapeChar[] =	{ {"Escape character is '^]'.", 0, 0}
					,	{NULL,0,0}};
static struct Etoken BayTechAssoc[] =	{ {BAYTECHASSOC, 0, 0}, {NULL,0,0}};
static struct Etoken UnitId[] =		{ {"Unit ID: ", 0, 0}, {NULL,0,0}};
static struct Etoken login[] =		{ {"username>", 0, 0} ,{NULL,0,0}};
static struct Etoken password[] =	{ {"password>", 0, 0}
					, {"username>", 0, 0} ,{NULL,0,0}};
static struct Etoken Selection[] =	{ {"election>", 0, 0} ,{NULL,0,0}};
static struct Etoken RPC[] =		{ {"RPC", 0, 0} ,{NULL,0,0}};
static struct Etoken LoginOK[] =	{ {"RPC", 0, 0}, {"Invalid password", 1, 0}
					,	{NULL,0,0}};
static struct Etoken GTSign[] =		{ {">", 0, 0} ,{NULL,0,0}};
static struct Etoken Menu[] =		{ {"Menu:", 0, 0} ,{NULL,0,0}};
static struct Etoken Temp[] =		{ {"emperature: ", 0, 0}
					,	{NULL,0,0}};
static struct Etoken Break[] =		{ {"reaker: ", 0, 0}
					,	{NULL,0,0}};
static struct Etoken PowerApplied[] =	{ {"ower applied to outlet", 0, 0}
					,	{NULL,0,0}};
/* Accept either a CR/NL or an NL/CR */
static struct Etoken CRNL[] =		{ {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}};

/* We may get a notice about rebooting, or a request for confirmation */
static struct Etoken Rebooting[] =	{ {"ebooting selected outlet", 0, 0}
					,	{"(Y/N)>", 1, 0}
					,	{"already off.", 2, 0}
					,	{NULL,0,0}};


static struct BayTechModelInfo ModelInfo [] = {
	{"RPC-5", 18, Temp},	/* This first model will be the default */
	{"RPC-3", 10, Break},	
	{"RPC-3A", 10, Break},
	{NULL, 0, NULL},
};

static int	RPCLookFor(struct BayTech* bt, struct Etoken * tlist, int timeout);
static int	RPC_connect_device(struct BayTech * bt);
static int	RPCLogin(struct BayTech * bt);
static int	RPCRobustLogin(struct BayTech * bt);
static int	RPCNametoOutlet(struct BayTech*, const char * name);
static int	RPCReset(struct BayTech*, int unitnum, const char * rebootid);
static int	RPCScanLine(struct BayTech* bt, int timeout, char * buf, int max);
static int	RPCLogout(struct BayTech * bt);
static void	RPCkillcomm(struct BayTech * bt);


static int	RPC_parse_config_info(struct BayTech* bt, const char * info);
#if defined(ST_POWERON) && defined(ST_POWEROFF)
static int	RPC_onoff(struct BayTech*, int unitnum, const char * unitid
,		int request);
#endif

/*
 *	We do these things a lot.  Here are a few shorthand macros.
 */

#define	SEND(s)	(write(bt->wrfd, (s), strlen(s)))

#define	EXPECT(p,t)	{						\
			if (RPCLookFor(bt, p, t) < 0)			\
				return(errno == ETIMEDOUT			\
			?	S_TIMEOUT : S_OOPS);			\
			}

#define	NULLEXPECT(p,t)	{						\
				if (RPCLookFor(bt, p, t) < 0)		\
					return(NULL);			\
			}

#define	SNARF(s, to)	{						\
				if (RPCScanLine(bt,to,(s),sizeof(s))	\
				!=	S_OK)				\
					return(S_OOPS);			\
			}

#define	NULLSNARF(s, to)	{					\
				if (RPCScanLine(bt,to,(s),sizeof(s))	\
				!=	S_OK)				\
					return(NULL);			\
				}

/* Look for any of the given patterns.  We don't care which */

#define	MAXSAVE	512
static int
RPCLookFor(struct BayTech* bt, struct Etoken * tlist, int timeout)
{
	int	rc;
	char	savebuf[MAXSAVE];
	savebuf[MAXSAVE-1] = EOS;
	savebuf[0] = EOS;

	if ((rc = EXPECT_TOK(bt->rdfd, tlist, timeout, savebuf, MAXSAVE)) < 0){
		syslog(LOG_ERR, _("Did not find string: '%s' from " DEVICE ".")
		,	tlist[0].string);
		syslog(LOG_ERR, _("Got '%s' from " DEVICE " instead.")
		,	savebuf);
		RPCkillcomm(bt);
		return(-1);
	}
#if 0
	syslog(LOG_INFO
	,	"BayTech: Expecting [%s] Received [%s]"
	,	tlist[0].string
	,	savebuf);
#endif
	return(rc);
}

/* Read and return the rest of the line */

static int
RPCScanLine(struct BayTech* bt, int timeout, char * buf, int max)
{
	if (EXPECT_TOK(bt->rdfd, CRNL, timeout, buf, max) < 0) {
		syslog(LOG_ERR, ("Could not read line from " DEVICE "."));
		RPCkillcomm(bt);
		bt->pid = -1;
		return(S_OOPS);
	}
	return(S_OK);
}

/* Login to the Baytech Remote Power Controller (RPC) */

static int
RPCLogin(struct BayTech * bt)
{
	char		IDinfo[128];
	static char	IDbuf[128];
	char *		idptr = IDinfo;
	char *		delim;
	int		j;


	EXPECT(EscapeChar, 10);
	/* Look for the unit type info */
	if (EXPECT_TOK(bt->rdfd, BayTechAssoc, 2, IDinfo
	,	sizeof(IDinfo)) < 0) {
		syslog(LOG_ERR, _("No initial response from " DEVICE "."));
		RPCkillcomm(bt);
		return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS);
	}
	idptr += strspn(idptr, WHITESPACE);
	/*
	 * We should be looking at something like this:
         *	RPC-5 Telnet Host
    	 *	Revision F 4.22, (C) 1999
    	 *	Bay Technical Associates
	 */

	/* Truncate the result after the RPC-5 part */
	if ((delim = strchr(idptr, ' ')) != NULL) {
		*delim = EOS;
	}
	snprintf(IDbuf, sizeof(IDbuf), "BayTech %s", idptr);
	REPLSTR(bt->idinfo, IDbuf);

	bt->modelinfo = &ModelInfo[0];

	for (j=0; ModelInfo[j].type != NULL; ++j) {
		/*
		 * TIMXXX - 
		 * Look at device ID as this really describes the model.
		 */
		if (strcasecmp(ModelInfo[j].type, idptr) == 0) {
			bt->modelinfo = &ModelInfo[j];
			break;
		}
	}

	/* Look for the unit id info */
	EXPECT(UnitId, 10);
	SNARF(IDbuf, 2);
	delim = IDbuf + strcspn(IDbuf, WHITESPACE);
	*delim = EOS;
	REPLSTR(bt->unitid, IDbuf);

	/* Expect "username>" */
	EXPECT(login, 2);

	SEND(bt->user);
	SEND("\r");

	/* Expect "password>" */

	switch (RPCLookFor(bt, password, 5)) {
		case 0:	/* Good! */
			break;

		case 1:	/* OOPS!  got another username prompt */
			syslog(LOG_ERR, _("Invalid username for " DEVICE "."));
			return(S_ACCESS);

		default:
			return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS);
	}

	SEND(bt->passwd);
	SEND("\r");

	/* Expect "RPC-x Menu" */

	switch (RPCLookFor(bt, LoginOK, 5)) {

		case 0:	/* Good! */
			break;

		case 1:	/* Uh-oh - bad password */
			syslog(LOG_ERR, _("Invalid password for " DEVICE "."));
			return(S_ACCESS);

		default:
			RPCkillcomm(bt);
			return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS);
	}
	EXPECT(Menu, 2);

	return(S_OK);
}

static int
RPCRobustLogin(struct BayTech * bt)
{
	int	rc=S_OOPS;
	int	j;

	for (j=0; j < 20 && rc != S_OK; ++j) {

		if (bt->pid > 0) {
			RPCkillcomm(bt);
		}

		if (RPC_connect_device(bt) != S_OK) {
			RPCkillcomm(bt);
			continue;
		}

		rc = RPCLogin(bt);
	}
	return rc;
}

/* Log out of the Baytech RPC */

static int
RPCLogout(struct BayTech* bt)
{
	int	rc;

	/* Make sure we're in the right menu... */
	SEND("\r");

	/* Expect "Selection>" */
	rc = RPCLookFor(bt, Selection, 5);

	/* Option 6 is Logout */
	SEND("6\r");

	close(bt->wrfd);
	close(bt->rdfd);
	bt->wrfd = bt->rdfd = -1;
	RPCkillcomm(bt);
	return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS));
}
static void
RPCkillcomm(struct BayTech* bt)
{
	if (bt->pid > 0) {
		STONITH_KILL(bt->pid, SIGKILL);
		(void)waitpid(bt->pid, NULL, 0);
		bt->pid = -1;
	}
}

/* Reset (power-cycle) the given outlet number */
static int
RPCReset(struct BayTech* bt, int unitnum, const char * rebootid)
{
	char		unum[32];


	SEND("\r");

	/* Make sure we're in the top level menu */

	/* Expect "RPC-x Menu" */
	EXPECT(RPC, 5);
	EXPECT(Menu, 5);

	/* OK.  Request sub-menu 1 (Outlet Control) */
	SEND("1\r");

	/* Verify that we're in the sub-menu */

	/* Expect: "RPC-x>" */
	EXPECT(RPC, 5);
	EXPECT(GTSign, 5);


	/* Send REBOOT command for given outlet */
	snprintf(unum, sizeof(unum), "REBOOT %d\r", unitnum);
	SEND(unum);

	/* Expect "ebooting "... or "(Y/N)" (if confirmation turned on) */

	retry:
	switch (RPCLookFor(bt, Rebooting, 5)) {
		case 0: /* Got "Rebooting" Do nothing */
			break;

		case 1: /* Got that annoying command confirmation :-( */
			SEND("Y\r");
			goto retry;

		case 2:	/* Outlet is turned off */
			syslog(LOG_ERR, _("Host %s is OFF."), rebootid);
			return(S_ISOFF);

		default:
			return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS);
	}
	syslog(LOG_INFO, _("Host %s being rebooted."), rebootid);

	/* Expect "ower applied to outlet" */
	if (RPCLookFor(bt, PowerApplied, 30) < 0) {
		return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS);
	}

	/* All Right!  Power is back on.  Life is Good! */

	syslog(LOG_INFO, _("Power restored to host %s."), rebootid);

	/* Expect: "RPC-x>" */
	EXPECT(RPC,5);
	EXPECT(GTSign, 5);

	/* Pop back to main menu */
	SEND("MENU\r");
	return(S_OK);
}

#if defined(ST_POWERON) && defined(ST_POWEROFF)
static int
RPC_onoff(struct BayTech* bt, int unitnum, const char * unitid, int req)
{
	char		unum[32];

	const char *	onoff = (req == ST_POWERON ? "on" : "off");
	int	rc;


	if ((rc = RPCRobustLogin(bt) != S_OK)) {
		syslog(LOG_ERR, _("Cannot log into " DEVICE "."));
		return(rc);
	}
	SEND("\r");

	/* Make sure we're in the top level menu */

	/* Expect "RPC-x Menu" */
	EXPECT(RPC, 5);
	EXPECT(Menu, 5);

	/* OK.  Request sub-menu 1 (Outlet Control) */
	SEND("1\r");

	/* Verify that we're in the sub-menu */

	/* Expect: "RPC-x>" */
	EXPECT(RPC, 5);
	EXPECT(GTSign, 5);


	/* Send ON/OFF command for given outlet */
	snprintf(unum, sizeof(unum), "%s %d\r"
	,	onoff, unitnum);
	SEND(unum);

	/* Expect "RPC->x "... or "(Y/N)" (if confirmation turned on) */

	if (RPCLookFor(bt, RPC, 10) == 1) {
		/* They've turned on that annoying command confirmation :-( */
		SEND("Y\r");
		EXPECT(RPC, 10);
	}

	EXPECT(GTSign, 10);

	/* All Right!  Command done. Life is Good! */
	syslog(LOG_NOTICE, _("Power to host %s turned %s."), unitid, onoff);
	/* Pop back to main menu */
	SEND("MENU\r");
	return(S_OK);
}
#endif /* defined(ST_POWERON) && defined(ST_POWEROFF) */

/*
 *	Map the given host name into an (AC) Outlet number on the power strip
 */

static int
RPCNametoOutlet(struct BayTech* bt, const char * name)
{
	char	NameMapping[128];
	int	sockno;
	char	sockname[32];
	int	ret = -1;



	/* Verify that we're in the top-level menu */
	SEND("\r");

	/* Expect "RPC-x Menu" */
	EXPECT(RPC, 5);
	EXPECT(Menu, 5);


	/* OK.  Request sub-menu 1 (Outlet Control) */
	SEND("1\r");

	/* Verify that we're in the sub-menu */

	/* Expect: "RPC-x>" */
	EXPECT(RPC, 5);
	EXPECT(GTSign, 5);

	/* The status command output contains mapping of hosts to outlets */
	SEND("STATUS\r");

	/* Expect: "emperature:" so we can skip over it... */
	EXPECT(bt->modelinfo->expect, 5);
	EXPECT(CRNL, 5);

	/* Looks Good!  Parse the status output */

	do {
		char *	last;
		NameMapping[0] = EOS;
		SNARF(NameMapping, 5);

		if (!parse_socket_line(bt, NameMapping, &sockno, sockname)) {
			continue;
		}

		last = sockname+bt->modelinfo->socklen;
		*last = EOS;
		--last;

		/* Strip off trailing blanks */
		for(; last > sockname; --last) {
			if (*last == ' ') {
				*last = EOS;
			}else{
				break;
			}
		}
		g_strdown(sockname);
		if (strcmp(name, sockname) == 0) {
			ret = sockno;
		}
	} while (strlen(NameMapping) > 2 && ret < 0);

	/* Pop back out to the top level menu */
	SEND("MENU\r");
	return(ret);
}

static int
baytech_status(Stonith  *s)
{
	struct BayTech*	bt;
	int	rc;

	if (!ISBAYTECH(s)) {
		syslog(LOG_ERR, "invalid argument to RPC_status");
		return(S_OOPS);
	}
	if (!ISCONFIGED(s)) {
		syslog(LOG_ERR
		,	"unconfigured stonith object in RPC_status");
		return(S_OOPS);
	}
	bt = (struct BayTech*) s->pinfo;

	if ((rc = RPCRobustLogin(bt) != S_OK)) {
		syslog(LOG_ERR, _("Cannot log into " DEVICE "."));
		return(rc);
	}

	/* Verify that we're in the top-level menu */
	SEND("\r");

	/* Expect "RPC-x Menu" */
	EXPECT(RPC, 5);
	EXPECT(Menu, 5);

	return(RPCLogout(bt));
}
/*
 *	Return the list of hosts (outlet names) for the devices on this BayTech unit
 */

static char **
baytech_hostlist(Stonith  *s)
{
	char		NameMapping[128];
	char*		NameList[64];
	unsigned int	numnames = 0;
	char **		ret = NULL;
	struct BayTech*	bt;


	if (!ISBAYTECH(s)) {
		syslog(LOG_ERR, "invalid argument to baytech_hostlist");
		return(NULL);
	}
	if (!ISCONFIGED(s)) {
		syslog(LOG_ERR
		,	"unconfigured stonith object in baytech_hostlist");
		return(NULL);
	}
	bt = (struct BayTech*) s->pinfo;



	if (RPCRobustLogin(bt) != S_OK) {
		syslog(LOG_ERR, _("Cannot log into " DEVICE "."));
		return(NULL);
	}

	/* Verify that we're in the top-level menu */
	SEND("\r");

	/* Expect "RPC-x Menu" */
	NULLEXPECT(RPC, 5);
	NULLEXPECT(Menu, 5);


	/* OK.  Request sub-menu 1 (Outlet Control) */
	SEND("1\r");

	/* Verify that we're in the sub-menu */

	/* Expect: "RPC-x>" */
	NULLEXPECT(RPC, 5);
	NULLEXPECT(GTSign, 5);

	/* The status command output contains mapping of hosts to outlets */
	SEND("STATUS\r");

	/* Expect: "emperature:" so we can skip over it... */
	NULLEXPECT(bt->modelinfo->expect, 5);
	NULLEXPECT(CRNL, 5);

	/* Looks Good!  Parse the status output */

	do {
		int	sockno;
		char	sockname[64];
		char *	last;
		char *	nm;

		NameMapping[0] = EOS;

		NULLSNARF(NameMapping, 5);

		if (!parse_socket_line(bt, NameMapping, &sockno, sockname)) {
			continue;
		}

		last = sockname+bt->modelinfo->socklen;
		*last = EOS;
		--last;

		/* Strip off trailing blanks */
		for(; last > sockname; --last) {
			if (*last == ' ') {
				*last = EOS;
			}else{
				break;
			}
		}
		if (numnames >= DIMOF(NameList)-1) {
			break;
		}
		if ((nm = (char*)STRDUP(sockname)) == NULL) {
			syslog(LOG_ERR, "out of memory");
			return(NULL);
		}
		g_strdown(nm);
		NameList[numnames] = nm;
		++numnames;
		NameList[numnames] = NULL;
	} while (strlen(NameMapping) > 2);

	/* Pop back out to the top level menu */
	SEND("MENU\r");
	if (numnames >= 1) {
		ret = (char **)MALLOC((numnames+1)*sizeof(char*));
		if (ret == NULL) {
			syslog(LOG_ERR, "out of memory");
		}else{
			memcpy(ret, NameList, (numnames+1)*sizeof(char*));
		}
	}
	(void)RPCLogout(bt);
	return(ret);
}

static void
baytech_free_hostlist (char ** hlist)
{
	char **	hl = hlist;
	if (hl == NULL) {
		return;
	}
	while (*hl) {
		FREE(*hl);
		*hl = NULL;
		++hl;
	}
	FREE(hlist);
}


/*
 *	Parse the given configuration information, and stash it away...
 */

static int
RPC_parse_config_info(struct BayTech* bt, const char * info)
{
	static char dev[1024];
	static char user[1024];
	static char passwd[1024];

	if (bt->config) {
		return(S_OOPS);
	}


	if (sscanf(info, "%s %s %[^\n\r\t]", dev, user, passwd) == 3
	&&	strlen(passwd) > 1) {

		if ((bt->device = STRDUP(dev)) == NULL) {
			syslog(LOG_ERR, "out of memory");
			return(S_OOPS);
		}
		if ((bt->user = STRDUP(user)) == NULL) {
			FREE(bt->device);
			bt->device=NULL;
			syslog(LOG_ERR, "out of memory");
			return(S_OOPS);
		}
		if ((bt->passwd = STRDUP(passwd)) == NULL) {
			FREE(bt->user);
			bt->user=NULL;
			FREE(bt->device);
			bt->device=NULL;
			syslog(LOG_ERR, "out of memory");
			return(S_OOPS);
		}
		bt->config = 1;
		return(S_OK);
	}
	return(S_BADCONFIG);
}

/*
 *	Connect to the given BayTech device.  We should add serial support here
 *	eventually...
 */
static int
RPC_connect_device(struct BayTech * bt)
{
	char	TelnetCommand[256];

	snprintf(TelnetCommand, sizeof(TelnetCommand)
	,	"exec telnet %s 2>/dev/null", bt->device);

	bt->pid=STARTPROC(TelnetCommand, &bt->rdfd, &bt->wrfd);
	if (bt->pid <= 0) {
		return(S_OOPS);
	}
	return(S_OK);
}

/*
 *	Reset the given host on this Stonith device.
 */
static int
baytech_reset_req(Stonith * s, int request, const char * host)
{
	int	rc = 0;
	int	lorc = 0;
	struct BayTech*	bt;

	if (!ISBAYTECH(s)) {
		syslog(LOG_ERR, "invalid argument to baytech_reset");
		return(S_OOPS);
	}
	if (!ISCONFIGED(s)) {
		syslog(LOG_ERR
		,	"unconfigured stonith object in baytech_reset");
		return(S_OOPS);
	}
	bt = (struct BayTech*) s->pinfo;


	if ((rc = RPCRobustLogin(bt)) != S_OK) {
		syslog(LOG_ERR, _("Cannot log into " DEVICE "."));
	}else{
		int	noutlet;
		noutlet = RPCNametoOutlet(bt, host);

		if (noutlet < 1) {
			syslog(LOG_WARNING, _("%s %s "
			"doesn't control host [%s]."), bt->idinfo
			,	bt->unitid, host);
			RPCkillcomm(bt);
			return(S_BADHOST);
		}
		switch(request) {

#if defined(ST_POWERON) && defined(ST_POWEROFF)
		case ST_POWERON:
		case ST_POWEROFF:
			rc = RPC_onoff(bt, noutlet, host, request);
			break;
#endif
		case ST_GENERIC_RESET:
			rc = RPCReset(bt, noutlet, host);
			break;
		default:
			rc = S_INVAL;
			break;
		}
	}

	lorc = RPCLogout(bt);
	RPCkillcomm(bt);

	return(rc != S_OK ? rc : lorc);
}

/*
 *	Parse the information in the given configuration file,
 *	and stash it away...
 */
static int
baytech_set_config_file(Stonith* s, const char * configname)
{
	FILE *	cfgfile;

	char	RPCid[256];

	struct BayTech*	bt;

	if (!ISBAYTECH(s)) {
		syslog(LOG_ERR, "invalid argument to baytech_set");
		return(S_OOPS);
	}
	bt = (struct BayTech*) s->pinfo;

	if ((cfgfile = fopen(configname, "r")) == NULL)  {
		syslog(LOG_ERR, _("Cannot open %s"), configname);
		return(S_BADCONFIG);
	}
	while (fgets(RPCid, sizeof(RPCid), cfgfile) != NULL){
		if (*RPCid == '#' || *RPCid == '\n' || *RPCid == EOS) {
			continue;
		}
		return(RPC_parse_config_info(bt, RPCid));
	}
	return(S_BADCONFIG);
}

/*
 *	Parse the config information in the given string, and stash it away...
 */
static int
baytech_set_config_info(Stonith* s, const char * info)
{
	struct BayTech* bt;

	if (!ISBAYTECH(s)) {
		syslog(LOG_ERR, "baytech_set_config_info: invalid argument");
		return(S_OOPS);
	}
	bt = (struct BayTech *)s->pinfo;

	return(RPC_parse_config_info(bt, info));
}

static const char *
baytech_getinfo(Stonith * s, int reqtype)
{
	struct BayTech* bt;
	const char *		ret;

	if (!ISBAYTECH(s)) {
		syslog(LOG_ERR, "RPC_idinfo: invalid argument");
		return NULL;
	}
	/*
	 *	We look in the ST_TEXTDOMAIN catalog for our messages
	 */
	bt = (struct BayTech *)s->pinfo;

	switch (reqtype) {

		case ST_DEVICEID:		/* Exactly what type of device? */
			ret = bt->idinfo;
			break;

		case ST_DEVICENAME:		/* Which particular individual device? */
			ret = bt->device;
			break;

		case ST_CONF_INFO_SYNTAX:
			ret = _("IP-address login password\n"
			"The IP-address and login are white-space delimited.");
			break;

		case ST_CONF_FILE_SYNTAX:
			ret = _("IP-address login password\n"
			"The IP-address and login are white-space delimited.  "
			"All three items must be on one line.  "
			"Blank lines and lines beginning with # are ignored");
			break;

		case ST_DEVICEDESCR:		/* Description of device type */
			ret = _("Bay Technical Associates (Baytech) RPC "
			"series power switches (via telnet).\n"
			"The RPC-5, RPC-3 and RPC-3A switches are well tested.");
			break;

		case ST_DEVICEURL:		/* Manufacturer's web site */
			ret = "http://www.baytech.net/";
			break;

		default:
			ret = NULL;
			break;
	}
	return ret;
}

/*
 *	Baytech Stonith destructor...
 */
static void
baytech_destroy(Stonith *s)
{
	struct BayTech* bt;

	if (!ISBAYTECH(s)) {
		syslog(LOG_ERR, "baytech_del: invalid argument");
		return;
	}
	bt = (struct BayTech *)s->pinfo;

	bt->BTid = NOTbtid;
	RPCkillcomm(bt);
	if (bt->rdfd >= 0) {
		close(bt->rdfd);
		bt->rdfd = -1;
	}
	if (bt->wrfd >= 0) {
		close(bt->wrfd);
		bt->wrfd = -1;
	}
	if (bt->device != NULL) {
		FREE(bt->device);
		bt->device = NULL;
	}
	if (bt->user != NULL) {
		FREE(bt->user);
		bt->user = NULL;
	}
	if (bt->passwd != NULL) {
		FREE(bt->passwd);
		bt->passwd = NULL;
	}
	if (bt->idinfo != NULL) {
		FREE(bt->idinfo);
		bt->idinfo = NULL;
	}
	if (bt->unitid != NULL) {
		FREE(bt->unitid);
		bt->unitid = NULL;
	}
}

/* Create a new BayTech Stonith device. */

static void *
baytech_new(void)
{
	struct BayTech*	bt = MALLOCT(struct BayTech);

	if (bt == NULL) {
		syslog(LOG_ERR, "out of memory");
		return(NULL);
	}
	memset(bt, 0, sizeof(*bt));
	bt->BTid = BTid;
	bt->pid = -1;
	bt->rdfd = -1;
	bt->wrfd = -1;
	bt->config = 0;
	bt->user = NULL;
	bt->device = NULL;
	bt->passwd = NULL;
	bt->idinfo = NULL;
	bt->unitid = NULL;
	REPLSTR(bt->idinfo, DEVICE);
	bt->modelinfo = &ModelInfo[0];

	return((void *)bt);
}

static int
parse_socket_line(struct BayTech * bt,	const char *NameMapping
,	int *sockno, char *sockname)
{
#if 0
	char format[64];
	snprintf(format, sizeof(format), "%%7d       %%%dc"
	,	bt->modelinfo->socklen);
	/* 7 digits, 7 blanks, then 'socklen' characters */
	/* [0-6]: digits, NameMapping[13] begins the sockname */
	/* NameMapping strlen must be >= socklen + 14 */

	if (sscanf(NameMapping, format, sockno, sockname) != 2) {
		return FALSE;
	}
#else
#	define	OFFSET 14

	if (sscanf(NameMapping, "%7d", sockno) != 1
	||	strlen(NameMapping) < OFFSET+bt->modelinfo->socklen) {
		return FALSE;
	}
	strncpy(sockname, NameMapping+OFFSET, bt->modelinfo->socklen);
	sockname[bt->modelinfo->socklen] = EOS;
#endif
	return TRUE;
}


syntax highlighted by Code2HTML, v. 0.9.1