/*
 * Copyright (c) 2003, 2004 Sun Microsystems, Inc.  All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * Redistribution of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * Redistribution in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind.
 * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
 * SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE
 * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.  IN NO EVENT WILL
 * SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA,
 * OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR
 * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF
 * LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 */
#define _SVID_SOURCE || _BSD_SOURCE || _XOPEN_SOURCE >= 500 || \
	_XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED || \
	/* Since glibc 2.12: */ _POSIX_C_SOURCE >= 200809L

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <ipmitool/helper.h>
#include <ipmitool/log.h>
#include <ipmitool/ipmi.h>
#include <ipmitool/ipmi_intf.h>
#include <ipmitool/ipmi_session.h>
#include <ipmitool/ipmi_main.h>

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#define EXEC_BUF_SIZE	2048
#define EXEC_ARG_SIZE	64
#define MAX_PORT	65535

extern const struct valstr ipmi_privlvl_vals[];
extern const struct valstr ipmi_authtype_session_vals[];

#ifdef HAVE_READLINE

/* avoid warnings errors due to non-ANSI type declarations in readline.h */
#define _FUNCTION_DEF
#define USE_VARARGS
#define PREFER_STDARG

#include <readline/readline.h>
#include <readline/history.h>
#define RL_PROMPT		"ipmitool> "
#define RL_TIMEOUT		30

static struct ipmi_intf * shell_intf;

/* This function attempts to keep lan sessions active
 * so they do not time out waiting for user input.  The
 * readline timeout is set to 1 second but lan session
 * timeout is ~60 seconds.
 */
static int rl_event_keepalive(void)
{
	static int internal_timer = 0;

	if (shell_intf == NULL)
		return -1;
	if (shell_intf->keepalive == NULL)
		return 0;
#if defined (RL_READLINE_VERSION) && RL_READLINE_VERSION >= 0x0402
	if (internal_timer++ < RL_TIMEOUT)
#else
	/* In readline < 4.2 keyboard timeout hardcoded to 0.1 second */
	if (internal_timer++ < RL_TIMEOUT * 10)
#endif
		return 0;

	internal_timer = 0;
	shell_intf->keepalive(shell_intf);

	return 0;
}

int ipmi_shell_main(struct ipmi_intf * intf, int argc, char ** argv)
{
	char *ptr, *pbuf, **ap, *__argv[EXEC_ARG_SIZE];
	int __argc, rc=0;

	rl_readline_name = "ipmitool";

	/* this essentially disables command completion
	 * until its implemented right, otherwise we get
	 * the current directory contents... */
	rl_bind_key('\t', rl_insert);

	if (intf->keepalive) {
		/* hook to keep lan sessions active */
		shell_intf = intf;
		rl_event_hook = rl_event_keepalive;
#if defined(RL_READLINE_VERSION) && RL_READLINE_VERSION >= 0x0402
		/* There is a bug in readline 4.2 and later (at least on FreeBSD and NetBSD):
		 * timeout equal or greater than 1 second causes an infinite loop. */
		rl_set_keyboard_input_timeout(1000 * 1000 - 1);
#endif
	}

	while ((pbuf = (char *)readline(RL_PROMPT)) != NULL) {
		if (strlen(pbuf) == 0) {
			free(pbuf);
			pbuf = NULL;
			continue;
		}
		if (strncmp(pbuf, "quit", 4) == 0 ||
		    strncmp(pbuf, "exit", 4) == 0) {
			free(pbuf);
			pbuf = NULL;
			return 0;
		}
		if (strncmp(pbuf, "help", 4) == 0 ||
		    strncmp(pbuf, "?", 1) == 0) {
			ipmi_cmd_print(intf->cmdlist);
			free(pbuf);
			pbuf = NULL;
			continue;
		}

		/* for the all-important up arrow :) */
		add_history(pbuf);

		/* change "" and '' with spaces in the middle to ~ */
		ptr = pbuf;
		while (*ptr != '\0') {
			if (*ptr == '"') {
				ptr++;
				while (*ptr != '"' && *ptr != '\0') {
					if (isspace((int)*ptr))
						*ptr = '~';
					ptr++;
				}
			}
			if (*ptr == '\'') {
				ptr++;
				while (*ptr != '\'' && *ptr != '\0') {
					if (isspace((int)*ptr))
						*ptr = '~';
					ptr++;
				}
			}
			ptr++;
		}

		__argc = 0;
		ap = __argv;

		for (*ap = strtok(pbuf, " \t");
		     *ap != NULL;
		     *ap = strtok(NULL, " \t")) {
			__argc++;

			ptr = *ap;
			if (*ptr == '\'') {
				memmove(ptr, ptr+1, strlen(ptr));
				while (*ptr != '\'' && *ptr != '\0') {
					if (*ptr == '~')
						*ptr = ' ';
					ptr++;
				}
				*ptr = '\0';
			}
			if (*ptr == '"') {
				memmove(ptr, ptr+1, strlen(ptr));
				while (*ptr != '"' && *ptr != '\0') {
					if (*ptr == '~')
						*ptr = ' ';
					ptr++;
				}
				*ptr = '\0';
			}

			if (**ap != '\0') {
				if (++ap >= &__argv[EXEC_ARG_SIZE])
					break;
			}
		}

		if (__argc && __argv[0])
			rc = ipmi_cmd_run(intf,
					  __argv[0],
					  __argc-1,
					  &(__argv[1]));

		free(pbuf);
		pbuf = NULL;
	}
	printf("\n");
	return rc;
}

#else  /* HAVE_READLINE */

int
ipmi_shell_main(struct ipmi_intf * intf, int argc, char ** argv)
{
	lprintf(LOG_ERR, "Compiled without readline, shell is disabled");
	return -1;
}

#endif /* HAVE_READLINE */

int ipmi_echo_main(struct ipmi_intf * intf, int argc, char ** argv)
{
	int i;

	for (i=0; i<argc; i++) {
		printf("%s ", argv[i]);
	}
	printf("\n");

	return 0;
}

static void
ipmi_set_usage(void)
{
	lprintf(LOG_NOTICE, "Usage: set <option> <value>\n");
	lprintf(LOG_NOTICE, "Options are:");
	lprintf(LOG_NOTICE, "    hostname <host>        Session hostname");
	lprintf(LOG_NOTICE, "    username <user>        Session username");
	lprintf(LOG_NOTICE, "    password <pass>        Session password");
	lprintf(LOG_NOTICE, "    privlvl <level>        Session privilege level force");
	lprintf(LOG_NOTICE, "    authtype <type>        Authentication type force");
	lprintf(LOG_NOTICE, "    localaddr <addr>       Local IPMB address");
	lprintf(LOG_NOTICE, "    targetaddr <addr>      Remote target IPMB address");
	lprintf(LOG_NOTICE, "    port <port>            Remote RMCP port");
	lprintf(LOG_NOTICE, "    csv [level]            enable output in comma separated format");
	lprintf(LOG_NOTICE, "    verbose [level]        Verbose level");
	lprintf(LOG_NOTICE, "");
}

int ipmi_set_main(struct ipmi_intf * intf, int argc, char ** argv)
{
	if (argc == 0 || strncmp(argv[0], "help", 4) == 0) {
		ipmi_set_usage();
		return -1;
	}

	/* these options can have no arguments */
	if (strncmp(argv[0], "verbose", 7) == 0) {
		if (argc > 1) {
			if (str2int(argv[1], &verbose) != 0) {
				lprintf(LOG_ERR,
						"Given verbose '%s' argument is invalid.",
						argv[1]);
				return (-1);
			}
		} else {
			verbose = verbose + 1;
		}
		return 0;
	}
	if (strncmp(argv[0], "csv", 3) == 0) {
		if (argc > 1) {
			if (str2int(argv[1], &csv_output) != 0) {
				lprintf(LOG_ERR,
						"Given csv '%s' argument is invalid.",
						argv[1]);
				return (-1);
			}
		} else {
			csv_output = 1;
		}
		return 0;
	}

	/* the rest need an argument */
	if (argc == 1) {
		ipmi_set_usage();
		return -1;
	}

	if (strncmp(argv[0], "host", 4) == 0 ||
	    strncmp(argv[0], "hostname", 8) == 0) {
		ipmi_intf_session_set_hostname(intf, argv[1]);
		if (intf->session == NULL) {
			lprintf(LOG_ERR, "Failed to set session hostname.");
			return (-1);
		}
		printf("Set session hostname to %s\n",
				intf->ssn_params.hostname);
	}
	else if (strncmp(argv[0], "user", 4) == 0 ||
		 strncmp(argv[0], "username", 8) == 0) {
		ipmi_intf_session_set_username(intf, argv[1]);
		if (intf->session == NULL) {
			lprintf(LOG_ERR, "Failed to set session username.");
			return (-1);
		}
		printf("Set session username to %s\n",
				intf->ssn_params.username);
	}
	else if (strncmp(argv[0], "pass", 4) == 0 ||
		 strncmp(argv[0], "password", 8) == 0) {
		ipmi_intf_session_set_password(intf, argv[1]);
		if (intf->session == NULL) {
			lprintf(LOG_ERR, "Failed to set session password.");
			return (-1);
		}
		printf("Set session password\n");
	}
	else if (strncmp(argv[0], "authtype", 8) == 0) {
		int authtype;
		authtype = str2val(argv[1], ipmi_authtype_session_vals);
		if (authtype == 0xFF) {
			lprintf(LOG_ERR, "Invalid authtype: %s",
					argv[1]);
			return (-1);
		}
		ipmi_intf_session_set_authtype(intf, authtype);
		if (intf->session == NULL) {
			lprintf(LOG_ERR, "Failed to set session authtype.");
			return (-1);
		}
		printf("Set session authtype to %s\n",
		       val2str(intf->ssn_params.authtype_set,
				   ipmi_authtype_session_vals));
	}
	else if (strncmp(argv[0], "privlvl", 7) == 0) {
		int privlvl;
		privlvl = str2val(argv[1], ipmi_privlvl_vals);
		if (privlvl == 0xFF) {
			lprintf(LOG_ERR, "Invalid privilege level: %s",
					argv[1]);
			return (-1);
		}
		ipmi_intf_session_set_privlvl(intf, privlvl);
		if (intf->session == NULL) {
			lprintf(LOG_ERR,
					"Failed to set session privilege level.");
			return (-1);
		}
		printf("Set session privilege level to %s\n",
		       val2str(intf->ssn_params.privlvl,
				   ipmi_privlvl_vals));
	}
	else if (strncmp(argv[0], "port", 4) == 0) {
		int port = 0;
		if (str2int(argv[1], &port) != 0 || port > MAX_PORT) {
			lprintf(LOG_ERR, "Given port '%s' is invalid.",
					argv[1]);
			return (-1);
		}
		ipmi_intf_session_set_port(intf, port);
		if (intf->session == NULL) {
			lprintf(LOG_ERR, "Failed to set session port.");
			return (-1);
		}
		printf("Set session port to %d\n", intf->ssn_params.port);
	}
	else if (strncmp(argv[0], "localaddr", 9) == 0) {
		uint8_t my_addr = 0;
		if (str2uchar(argv[1], &my_addr) != 0) {
			lprintf(LOG_ERR, "Given localaddr '%s' is invalid.",
					argv[1]);
			return (-1);
		}
		intf->my_addr = my_addr;
		printf("Set local IPMB address to 0x%02x\n", intf->my_addr);
	}
	else if (strncmp(argv[0], "targetaddr", 10) == 0) {
		uint8_t target_addr = 0;
		if (str2uchar(argv[1], &target_addr) != 0) {
			lprintf(LOG_ERR, "Given targetaddr '%s' is invalid.",
					argv[1]);
			return (-1);
		}
		intf->target_addr = target_addr;
		printf("Set remote IPMB address to 0x%02x\n", intf->target_addr);
	}
	else {
		ipmi_set_usage();
		return -1;
	}
	return 0;
}

int ipmi_exec_main(struct ipmi_intf * intf, int argc, char ** argv)
{
	FILE * fp;
	char buf[EXEC_BUF_SIZE];
	char * ptr, * tok, * ret, * tmp;
	int __argc, i, r;
	char * __argv[EXEC_ARG_SIZE];
	int rc=0;

	if (argc < 1) {
		lprintf(LOG_ERR, "Usage: exec <filename>");
		return -1;
	}

	fp = ipmi_open_file_read(argv[0]);
	if (fp == NULL)
		return -1;

	while (feof(fp) == 0) {
		ret = fgets(buf, EXEC_BUF_SIZE, fp);
		if (ret == NULL)
			continue;

		/* clip off optional comment tail indicated by # */
		ptr = strchr(buf, '#');
		if (ptr)
			*ptr = '\0';
		else
			ptr = buf + strlen(buf);

		/* change "" and '' with spaces in the middle to ~ */
		ptr = buf;
		while (*ptr != '\0') {
			if (*ptr == '"') {
				ptr++;
				while (*ptr != '"' && *ptr != '\0') {
					if (isspace((int)*ptr))
						*ptr = '~';
					ptr++;
				}
			}
			if (*ptr == '\'') {
				ptr++;
				while (*ptr != '\'' && *ptr != '\0') {
					if (isspace((int)*ptr))
						*ptr = '~';
					ptr++;
				}
			}
			ptr++;
		}

		/* clip off trailing and leading whitespace */
		ptr--;
		while (isspace((int)*ptr) && ptr >= buf)
			*ptr-- = '\0';
		ptr = buf;
		while (isspace((int)*ptr))
			ptr++;
		if (strlen(ptr) == 0)
			continue;

		/* parse it and make argument list */
		__argc = 0;
		for (tok = strtok(ptr, " "); tok != NULL; tok = strtok(NULL, " ")) {
			if (__argc < EXEC_ARG_SIZE) {
				__argv[__argc++] = strdup(tok);
				if (__argv[__argc-1] == NULL) {
					lprintf(LOG_ERR, "ipmitool: malloc failure");
					if (fp) {
						fclose(fp);
						fp = NULL;
					}
					return -1;
				}
				tmp = __argv[__argc-1];
				if (*tmp == '\'') {
					memmove(tmp, tmp+1, strlen(tmp));
					while (*tmp != '\'' && *tmp != '\0') {
						if (*tmp == '~')
							*tmp = ' ';
						tmp++;
					}
					*tmp = '\0';
				}
				if (*tmp == '"') {
					memmove(tmp, tmp+1, strlen(tmp));
					while (*tmp != '"' && *tmp != '\0') {
						if (*tmp == '~')
							*tmp = ' ';
						tmp++;
					}
					*tmp = '\0';
				}
			}
		}

		/* now run the command, save the result if not successful */
		r = ipmi_cmd_run(intf, __argv[0], __argc-1, &(__argv[1]));
		if (r != 0)
			rc = r;

		/* free argument list */
		for (i=0; i<__argc; i++) {
			if (__argv[i] != NULL) {
				free(__argv[i]);
				__argv[i] = NULL;
			}
		}
	}

	fclose(fp);
	return rc;
}
