/*
 *	Stonith module for Dell Remote Access Controller DRAC5 via telnet
 *
 *  Copyright (c) 2007 hepasoft oHG
 *  author: Thomas Paschy <th.paschy@hepasoft.biz>
 *
 *  Based strongly on original code from apcmaster.c
 *
 * 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
 *
 */

/*
 * Version string that is filled in by CVS
 */
static const char *version __attribute__ ((unused)) = "$Revision: 1.00 $"; 

#define	DEVICE	"STONITH-Dell DRAC5"

#define DOESNT_USE_STONITHKILLCOMM	1
#define DOESNT_USE_STONITHSCANLINE	1

#include "stonith_plugin_common.h"

#define PIL_PLUGIN              drac5
#define PIL_PLUGIN_S            "drac5"
#define PIL_PLUGINLICENSE 	LICENSE_LGPL
#define PIL_PLUGINLICENSEURL 	URL_LGPL
#include <pils/plugin.h>

#include "stonith_signal.h"

static StonithPlugin *	drac5_new(const char *);
static void		drac5_destroy(StonithPlugin *);
static const char **	drac5_get_confignames(StonithPlugin *);
static int		drac5_set_config(StonithPlugin *, StonithNVpair *);
static const char *	drac5_getinfo(StonithPlugin * s, int InfoType);
static int		drac5_status(StonithPlugin * );
static int		drac5_reset_req(StonithPlugin * s, int request, const char * host);
static char **		drac5_hostlist(StonithPlugin  *);

static struct stonith_ops drac5Ops ={
	drac5_new,		/* Create new STONITH object	*/
	drac5_destroy,		/* Destroy STONITH object	*/
	drac5_getinfo,		/* Return STONITH info string	*/
	drac5_get_confignames,	/* Get configuration parameters */
	drac5_set_config,	/* Set configuration */
	drac5_status,		/* Return STONITH device status	*/
	drac5_reset_req,		/* Request a reset */
	drac5_hostlist,		/* Return list of supported hosts */
};

PIL_PLUGIN_BOILERPLATE2("1.0", Debug);

static const PILPluginImports*  PluginImports;
static PILPlugin*               OurPlugin;
static PILInterface*		OurInterface;
static StonithImports*		OurImports;
static void*			interfprivate;

#include "stonith_expect_helpers.h"

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
	,	&drac5Ops
	,	NULL		/*close */
	,	&OurInterface
	,	(void*)&OurImports
	,	&interfprivate); 
}

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

/*
 *	I have an Dell PE1900 with DRAC5 Fw 1.20 (Build 07.03.02)
 *      I hope that Dell wont change the strings I'm looking for ;-)
 *	At the Moment it works just fine.
 */

struct pluginDevice {
	StonithPlugin	sp;
	const char *	pluginid;
	const char *	idinfo;
	pid_t		pid;
	int		rdfd;
	int		wrfd;
	char *		host;
        char *		user;
	char *		passwd;
};

static const char * pluginid = "DRAC5-Stonith";
static const char * NOTpluginID = "DRAC5-Stonith has been destroyed";

/*
 *	Different expect strings that we get from the DRAC5
 */

#define DRAC5STR	"Dell Remote Access Controller"

/* We use tha raw-socket Communication and so wie dont get this one
static struct Etoken EscapeChar[] =	{ {"Escape character is '^]'.", 0, 0}
					,	{NULL,0,0}};
*/
static struct Etoken login[] = 		{ {" login: ", 0, 0}, {NULL,0,0}};
static struct Etoken password[] =	{ {"Password: ", 0, 0} ,{NULL,0,0}};
static struct Etoken Prompt[] =	{ {"\044", 0, 0} ,{NULL,0,0}};
static struct Etoken LoginOK[] =	{ {DRAC5STR, 0, 0}
                    , {" login: ", 1, 0} ,{NULL,0,0}};
/* Accept either a CR/NL or an NL/CR */
static struct Etoken s_stopped[] = { {"stopped successfully", 0, 0} ,
				     {"already stopped",1,0},
				     {NULL,0,0}};
static struct Etoken s_started[] = { {"started successfully", 0, 0} ,
				     {"already started",1,0},
				     {NULL,0,0}};

/* If wie Reset a system by the time the system is allways stopped */
/* we get a message which discribes this situation and our */
/* machine wont start */
static struct Etoken s_reset[] = { {"reset successfully", 0, 0} ,
                                   {"currently stopped",1,0},
                                   {NULL,0,0}};

static struct Etoken s_properties[] = { {"Properties:", 0, 0} ,{NULL,0,0}};

#include "stonith_config_xml.h"

static const char *drac5XML = 
  XML_PARAMETERS_BEGIN
    XML_IPADDR_PARM
    XML_LOGIN_PARM
    XML_PASSWD_PARM
  XML_PARAMETERS_END;

static int	DRAC5_connect_device(struct pluginDevice * ms);
static int	DRAC5Login(struct pluginDevice * ms);
static int	DRAC5RobustLogin(struct pluginDevice * ms);
static int	DRAC5Logout(struct pluginDevice * ms);


/* Login to the DRAC5 */
static int
DRAC5Login(struct pluginDevice * ms)
{
        /*EXPECT(ms->rdfd, EscapeChar, 10);*/

  	/* 
	 * We should be looking at something like this:
         *	" login: " 
	 */
	EXPECT(ms->rdfd, login, 10);
	SEND(ms->wrfd, ms->user);       
	SEND(ms->wrfd, "\r");

	/* Expect "Password: " */
	EXPECT(ms->rdfd, password, 10);
	SEND(ms->wrfd, ms->passwd);
	SEND(ms->wrfd, "\r");
 
	switch (StonithLookFor(ms->rdfd, LoginOK, 10)) {

		case 0:	/* Good! */
			LOG(PIL_INFO, "%s Successful login to %s.", ms->idinfo, ms->host);
			break;

		case 1:	/* Uh-oh - bad password */
			LOG(PIL_CRIT, "%s Invalid password for <%s/%s>.", ms->idinfo, ms->host, ms->user);
			return(S_ACCESS);

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

	return(S_OK);
}

/* Attempt to login up to 5 times... */
static int
DRAC5RobustLogin(struct pluginDevice * ms)
{
	int rc = S_OOPS;
	int j = 0;

	for ( ; ; ) {
	  if (DRAC5_connect_device(ms) == S_OK) {	
		rc = DRAC5Login(ms);
		if (rc == S_OK) {
			break;
		}
	  }
	  if ((++j) == 5) {
 		break;
	  } else {
		sleep(1);
	  }
	}

	return rc;
}

/* Log out of the DRAC5 */

static 
int DRAC5Logout(struct pluginDevice* ms)
{
	int	rc;

	SEND(ms->wrfd, "\r");
	
	/* Expect "$ " */
	rc = StonithLookFor(ms->rdfd, Prompt, 5);

	SEND(ms->rdfd, "exit\r");

	LOG(PIL_INFO, "%s Successful logout from %s.", ms->idinfo, ms->host);

	close(ms->wrfd);
	close(ms->rdfd);
	ms->wrfd = ms->rdfd = -1;

	return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS));
}

static int
drac5_status(StonithPlugin  *s)
{
	struct pluginDevice*	ms;
	int	rc;

	ERRIFNOTCONFIGED(s,S_OOPS);

	ms = (struct pluginDevice*) s;

	if ((rc = DRAC5RobustLogin(ms) != S_OK)) {
		LOG(PIL_CRIT, "%s Cannot log into <%s/%s>.", ms->idinfo, ms->host, ms->user);
		return(rc);
	}

	/* Expect "$" */
	SEND(ms->wrfd, "\r");
	EXPECT(ms->rdfd, Prompt, 5);
	SEND(ms->wrfd, "smclp show /system1\r");
        if (StonithLookFor(ms->rdfd, s_properties, 10) < 0) {
		sleep(1);
		LOG(PIL_INFO, "%s Status of %s ->FAILED.", ms->idinfo, ms->host);
		return (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS);
	}
	else
	{
		LOG(PIL_INFO, "%s Status of %s ->OK.", ms->idinfo, ms->host);
	}
	return(DRAC5Logout(ms));
}

/*
 *	Return the list of hosts (outlet names) for the devices on this MS unit
 */
char **
drac5_hostlist(StonithPlugin * s)
{
	struct pluginDevice *drac5d;
	char **hl;

	ERRIFNOTCONFIGED(s,NULL);

	drac5d = (struct pluginDevice *) s;

	hl = OurImports->StringToHostList(drac5d->host);
	if (hl == NULL) {
		LOG(PIL_CRIT, "%s: out of memory", __FUNCTION__);
	} else {
		g_strdown(hl[0]);
	}

	return(hl);
}

/*
 *	Connect to the given DRAC host.
 */
static int
DRAC5_connect_device(struct pluginDevice * ms)
{
	int fd = OurImports->OpenStreamSocket(ms->host
	,	TELNET_PORT, TELNET_SERVICE);

	if (fd < 0) {
		return(S_OOPS);
	}
	ms->rdfd = ms->wrfd = fd;
	return(S_OK);
}

/*
 *	Reset the given host on this Stonith device.  
 */
static int
drac5_reset_req(StonithPlugin * s, int request, const char * host)
{
	int	rc = 0;
	int	lorc = 0;
	struct pluginDevice*	ms;

	ERRIFNOTCONFIGED(s,S_OOPS);

	ms = (struct pluginDevice*) s;

	if ((rc = DRAC5RobustLogin(ms)) != S_OK) {
		LOG(PIL_CRIT, "%s Cannot log into %s.", ms->idinfo, ms->host);
		return(rc);
	}else{
		switch(request) {

#if defined(ST_POWERON) && defined(ST_POWEROFF)
		case ST_POWERON:
			SEND(ms->wrfd, "smclp start /system1\r");
			switch (StonithLookFor(ms->rdfd, s_started, 10)) {
				case 0:
					/* All Right!  Command done. Life is Good! */
					LOG(PIL_INFO, "%s Power to <%s> is turned on.", ms->idinfo, host);
					break;
				case 1:
					LOG(PIL_INFO, "%s Power to <%s> is already turned on.", ms->idinfo, host);
					break;
				default:
					LOG(PIL_INFO, "%s Power to <%s> couldt not turned on.", ms->idinfo, host);
					break;
			}
			break;
		case ST_POWEROFF:
			SEND(ms->wrfd, "smclp stop /system1\r");
			switch (StonithLookFor(ms->rdfd, s_stopped, 10)) {
				case 0:
					/* All Right!  Command done. Life is Good! */
					LOG(PIL_INFO, "%s Power to <%s> is turned off.", ms->idinfo, host);
					break;
				case 1:
					LOG(PIL_INFO, "%s Power to <%s> is already turned off.", ms->idinfo, host);
					break;
				default:
					LOG(PIL_INFO, "%s Power to <%s> couldt not turned off.", ms->idinfo, host);
					break;
			}
			break;
#endif
		case ST_GENERIC_RESET:
			SEND(ms->wrfd, "\r");
			EXPECT(ms->rdfd, Prompt, 5);
			SEND(ms->wrfd, "smclp reset /system1\r");
			switch (StonithLookFor(ms->rdfd, s_reset, 10)) {
				case 0: /* All Right!  Command done. Life is Good! */
					LOG(PIL_INFO, "%s Host %s being rebooted by DRAC (ST_GENERIC_RESET).", ms->idinfo, host);
					rc = S_OK;
					break;
				case 1: /* Machine is currently stopped! */
					/* so we try to start it */
					SEND(ms->wrfd, "\r");
					EXPECT(ms->rdfd, Prompt, 5);
					SEND(ms->wrfd, "smclp start /system1\r");
					if (StonithLookFor(ms->rdfd, s_started, 10) < 0) {
						LOG(PIL_CRIT, "%s Power to <%s> is NOT turned on (ST_GENERIC_RESET).", ms->idinfo, host);
						rc = S_ACCESS;
					}
					else
					{
						LOG(PIL_INFO, "%s Power to <%s> is turned on (ST_GENERIC_RESET).", ms->idinfo, host);
						rc = S_OK;
					}
					break;
				default:
					LOG(PIL_INFO, "%s Host <%s> NOT being rebooted (ST_GENERIC_RESET).", ms->idinfo, host);
					rc = S_ACCESS;
			}
			break;
		default:
			rc = S_INVAL;
			break;
		}
	}

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

/*
 *	Get the configuration parameters names
 */
static const char **
drac5_get_confignames(StonithPlugin * s)
{
	static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_PASSWD, NULL};
	return ret;
}

/*
 *	Set the configuration parameters
 */
static int
drac5_set_config(StonithPlugin * s, StonithNVpair * list)
{
	struct pluginDevice* sd = (struct pluginDevice *)s;
	int		rc;
	StonithNamesToGet	namestocopy [] =
	{	{ST_IPADDR,	NULL}
	,	{ST_LOGIN,	NULL}
	,	{ST_PASSWD,	NULL}
	,	{NULL,		NULL}
	};

	ERRIFWRONGDEV(s, S_OOPS);
	if (sd->sp.isconfigured) {
		return S_OOPS;
	}

	if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) {
		return rc;
	}
	sd->host = namestocopy[0].s_value;
	sd->user = namestocopy[1].s_value;
	sd->passwd = namestocopy[2].s_value;

	return(S_OK);
}

static const char *
drac5_getinfo(StonithPlugin * s, int reqtype)
{
	struct pluginDevice* ms;
	const char *		ret;

	ERRIFWRONGDEV(s,NULL);

	/*
	 *	We look in the ST_TEXTDOMAIN catalog for our messages
	 */
	ms = (struct pluginDevice *)s;

	switch (reqtype) {
		case ST_DEVICEID:
			ret = ms->idinfo;
			break;

		case ST_DEVICENAME:		/* Which particular device? */
			ret = ms->host;
			break;

		case ST_DEVICEDESCR:
			ret = _("Dell DRAC5 (via telnet)\n"
 			"NOTE: The DRAC accepts only two (telnet)\n"
			"connection/session a time (by default). When two session are active,\n"
			"subsequent attempt to connect to the DRAC5"
			" will fail.");
			break;

		case ST_DEVICEURL:
			ret = "http://www.dell.com/";
			break;

		case ST_CONF_XML:		/* XML metadata */
			ret = drac5XML;
			break;

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

/*
 *	DRAC5 Stonith destructor...
 */
static void
drac5_destroy(StonithPlugin *s)
{
	struct pluginDevice* ms;

	VOIDERRIFWRONGDEV(s);

	ms = (struct pluginDevice *)s;

	ms->pluginid = NOTpluginID;
	if (ms->rdfd >= 0) {
		close(ms->rdfd);
		ms->rdfd = -1;
	}
	if (ms->wrfd >= 0) {
		close(ms->wrfd);
		ms->wrfd = -1;
	}
	if (ms->host != NULL) {
		FREE(ms->host);
		ms->host = NULL;
	}
	if (ms->user != NULL) {
		FREE(ms->user);
		ms->user = NULL;
	}
	if (ms->passwd != NULL) {
		FREE(ms->passwd);
		ms->passwd = NULL;
	}
	FREE(ms);
}

/* Create a new DRAC5 Stonith device. */
static StonithPlugin *
drac5_new(const char *subplugin)
{
	struct pluginDevice*	ms = MALLOCT(struct pluginDevice);

	if (ms == NULL) {
		LOG(PIL_CRIT, "out of memory");
		return(NULL);
	}
	memset(ms, 0, sizeof(*ms));
	ms->pluginid = pluginid;
	ms->pid = -1;
	ms->rdfd = -1;
	ms->wrfd = -1;
	ms->user   = NULL;
	ms->host   = NULL;
	ms->passwd = NULL;
	ms->idinfo = DEVICE;
	ms->sp.s_ops = &drac5Ops;

	return(&(ms->sp));
}
