/*
 * encode_keychange.c
 *
 * Collect information to build a KeyChange encoding, per the textual
 * convention given in RFC 2274, Section 5.  Compute the value and
 * dump to stdout as a string of hex nibbles.
 *
 *
 * Passphrase material may come from many sources.  The following are
 * checked in order (see get_user_passphrases()):
 *	- Prompt always if -f is given.
 *	- Commandline arguments.
 *	- PASSPHRASE_FILE.
 *	- Prompts on stdout.   Use -P to turn off prompt tags.
 *
 *
 * FIX	Better name?
 * FIX	Change encode_keychange() to take random bits?
 * FIX	QUITFUN not quite appropriate here...
 * FIX	This is slow...
 */

#include <config.h>

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#if HAVE_WINSOCK_H
#include <winsock.h>
#endif

#include "asn1.h"
#include "snmp_api.h"
#include "tools.h"
#include "scapi.h"
#include "snmpv3.h"
#include "keytools.h"
#include "snmp_debug.h"
#include "callback.h"

#include "../snmplib/transform_oids.h"

#include <stdlib.h>

/*
 * Globals, &c...
 */
char *local_progname;
char *local_passphrase_filename;

#define NL	"\n"

#define USAGE	"Usage: %s [-fhPvV] -t (md5|sha1) [-O \"<old_passphrase>\"][-N \"<new_passphrase>\"][-E [0x]<engineID>]"

#define OPTIONLIST	"E:fhN:O:Pt:vVD"

#define PASSPHRASE_DIR		".snmp"
	/* Rooted at $HOME.
	 */
#define PASSPHRASE_FILE		"passphrase.ek"
	/* 
	 * Format: two lines containing old and new passphrases, nothing more.
	 * 	
	 * XXX	Add creature comforts like: comments and 
	 *	tokens identifying passphrases, separate directory check,
	 *	check in current directory (?), traverse a path of
	 *	directories (?)...
	 * FIX	Better name?
	 */


int	forcepassphrase		= 0,	/* Always prompt for passphrases. */
	promptindicator		= 1,	/* Output an indicator that input
					 *   is requested.		  */
	visible			= 0,	/* Echo passphrases to terminal.  */
	verbose			= 0;	/* Output progress to stderr. 	  */
size_t	engineid_len		= 0;

u_char	*engineid		= NULL;	/* Both input & final binary form.*/
char	*newpass		= NULL,
        *oldpass		= NULL;

char	*transform_type_input	= NULL;

oid	*transform_type		= NULL;	/* Type of HMAC hash to use.	  */



/*
 * Prototypes.
 */
void	usage_to_file(FILE *ofp);
void	usage_synopsis(FILE *ofp);
int     get_user_passphrases(void);
int	snmp_ttyecho(const int fd, const int echo);
char   *snmp_getpassphrase(const char *prompt, int fvisible);

#ifdef WIN32
#define HAVE_GETPASS 1
char *getpass( const char * prompt );
int isatty(int);
int _cputs(const char *);
int _getch(void);
#endif

/*******************************************************************-o-******
 */
int
main(int argc, char **argv)
{
	int	  rval		= SNMPERR_SUCCESS;
	size_t	  oldKu_len	= SNMP_MAXBUF_SMALL,
		  newKu_len	= SNMP_MAXBUF_SMALL,
		  oldkul_len	= SNMP_MAXBUF_SMALL,
		  newkul_len	= SNMP_MAXBUF_SMALL,
		  keychange_len	= SNMP_MAXBUF_SMALL;

	char      *s = NULL;
	u_char	  oldKu[SNMP_MAXBUF_SMALL],
		  newKu[SNMP_MAXBUF_SMALL],
		  oldkul[SNMP_MAXBUF_SMALL],
		  newkul[SNMP_MAXBUF_SMALL],
		  keychange[SNMP_MAXBUF_SMALL];

	int       i;
	int       arg = 1;

 	local_progname = argv[0];
	local_passphrase_filename = (char *)malloc(sizeof(PASSPHRASE_DIR) +
					   sizeof(PASSPHRASE_FILE) + 4);
	sprintf(local_passphrase_filename, "%s/%s",
					   PASSPHRASE_DIR, PASSPHRASE_FILE);


 
	/*
	 * Parse.
	 */
	for(; (arg < argc) && (argv[arg][0] == '-') ; arg++){
		switch(argv[arg][1]){
		case 'D':  snmp_set_do_debugging(1);	break;
		case 'E':  engineid = (u_char *)argv[++arg];	break;
		case 'f':  forcepassphrase = 1;		break;
		case 'N':  newpass = argv[++arg];	break;
		case 'O':  oldpass = argv[++arg];	break;
		case 'P':  promptindicator = 0;		break;
		case 't':  transform_type_input = argv[++arg];	break;
		case 'v':  verbose = 1;			break;
		case 'V':  visible = 1;			break;
		case 'h':
			rval = 0;
		default:
			usage_to_file(stdout);
			exit(rval);
		}
	}

	if ( !transform_type_input ) {
		fprintf(stderr, "The -t option is mandatory.\n");
		usage_synopsis(stdout);
		exit(1000);
	}



	/*
	 * Convert and error check transform_type.
	 */
	if ( !strcmp(transform_type_input, "md5") ) {
		transform_type = usmHMACMD5AuthProtocol;

	} else if ( !strcmp(transform_type_input, "sha1") ) {
		transform_type = usmHMACSHA1AuthProtocol;

	} else {
		fprintf(stderr,
			"Unrecognized hash transform: \"%s\".\n",
			transform_type_input);
		usage_synopsis(stderr);
		QUITFUN(rval = SNMPERR_GENERR, main_quit);
	}

	if (verbose) {
		fprintf(stderr, "Hash:\t\t%s\n",
			(transform_type == usmHMACMD5AuthProtocol)
				? "usmHMACMD5AuthProtocol"
				: "usmHMACSHA1AuthProtocol"
			);
	}



	/* 
	 * Build engineID.  Accept hex engineID as the bits
	 * "in-and-of-themselves", otherwise create an engineID with the
	 * given string as text.
	 *
	 * If no engineID is given, lookup the first IP address for the
	 * localhost and use that (see setup_engineID()).
	 */
	if ( engineid && (tolower(*(engineid+1)) == 'x') ) {
		engineid_len = hex_to_binary2(	engineid+2,
						strlen((char *)engineid)-2,
						(char **) &engineid);
                DEBUGMSGTL(("encode_keychange","engineIDLen: %d\n", engineid_len));
	} else {
		engineid_len = setup_engineID(&engineid, (char *)engineid);

	} 

#ifdef SNMP_TESTING_CODE
	if (verbose) {
		fprintf(stderr, "EngineID:\t%s\n",
			/* XXX = */ dump_snmpEngineID(engineid, &engineid_len));
	}
#endif


	/*
	 * Get passphrases from user.
	 */
	rval = get_user_passphrases();
	QUITFUN(rval, main_quit);

	if ( strlen(oldpass) < USM_LENGTH_P_MIN ) {
		fprintf(stderr, "Old passphrase must be greater than %d "
				"characters in length.\n",
				USM_LENGTH_P_MIN);
		QUITFUN(rval = SNMPERR_GENERR, main_quit);

	} else if ( strlen(newpass) < USM_LENGTH_P_MIN ) {
		fprintf(stderr, "New passphrase must be greater than %d "
				"characters in length.\n",
				USM_LENGTH_P_MIN);
		QUITFUN(rval = SNMPERR_GENERR, main_quit);
	}

	if (verbose) {
		fprintf(stderr,
			"Old passphrase:\t%s\nNew passphrase:\t%s\n",
			oldpass, newpass);
	}



	/* 
	 * Compute Ku and Kul's from old and new passphrases, then
	 * compute the keychange string & print it out.
	 */
	rval = sc_init();
	QUITFUN(rval, main_quit);


	rval = generate_Ku(	transform_type, USM_LENGTH_OID_TRANSFORM,
				(u_char *)oldpass, strlen(oldpass),
				oldKu, &oldKu_len);
	QUITFUN(rval, main_quit);


	rval = generate_Ku(	transform_type, USM_LENGTH_OID_TRANSFORM,
				(u_char *)newpass, strlen(newpass),
				newKu, &newKu_len);
	QUITFUN(rval, main_quit);


        DEBUGMSGTL(("encode_keychange", "EID (%d): ", engineid_len));
        for(i=0; i < (int)engineid_len; i++)
          DEBUGMSGTL(("encode_keychange", "%02x",(int) (engineid[i])));
        DEBUGMSGTL(("encode_keychange","\n"));

        DEBUGMSGTL(("encode_keychange", "old Ku (%d) (from %s): ", oldKu_len, oldpass));
        for(i=0; i < (int)oldKu_len; i++)
          DEBUGMSGTL(("encode_keychange", "%02x",(int) (oldKu[i])));
        DEBUGMSGTL(("encode_keychange","\n"));

	rval = generate_kul(	transform_type, USM_LENGTH_OID_TRANSFORM,
				engineid, engineid_len,
				oldKu, oldKu_len,
				oldkul, &oldkul_len);
	QUITFUN(rval, main_quit);


        DEBUGMSGTL(("encode_keychange", "generating old Kul (%d) (from Ku): ", oldkul_len));
        for(i=0; i < (int)oldkul_len; i++)
          DEBUGMSGTL(("encode_keychange", "%02x",(int) (oldkul[i])));
        DEBUGMSGTL(("encode_keychange","\n"));

	rval = generate_kul(	transform_type, USM_LENGTH_OID_TRANSFORM,
				engineid, engineid_len,
				newKu, newKu_len,
				newkul, &newkul_len);
	QUITFUN(rval, main_quit);
        
        DEBUGMSGTL(("encode_keychange", "generating new Kul (%d) (from Ku): ", oldkul_len));
        for(i=0; i < (int)newkul_len; i++)
          DEBUGMSGTL(("encode_keychange", "%02x",newkul[i]));
        DEBUGMSGTL(("encode_keychange","\n"));

	rval = encode_keychange(transform_type, USM_LENGTH_OID_TRANSFORM,
				oldkul, oldkul_len,
				newkul, newkul_len,
				keychange, &keychange_len);
	QUITFUN(rval, main_quit);



	binary_to_hex(keychange, keychange_len, &s);
	printf("%s%s\n",
		(verbose) ? "KeyChange string:\t" : "", /* XXX stdout */
		s);


	/*
	 * Cleanup.
	 */
main_quit:
	snmp_call_callbacks(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_SHUTDOWN,
                            NULL);
        

	SNMP_ZERO(oldpass,	strlen(oldpass));
	SNMP_ZERO(newpass,	strlen(newpass));

	SNMP_ZERO(oldKu,	oldKu_len);
	SNMP_ZERO(newKu,	newKu_len);

	SNMP_ZERO(oldkul,	oldkul_len);
	SNMP_ZERO(newkul,	newkul_len);

	SNMP_ZERO(s, strlen(s));

	return rval;

} /* end main() */




/*******************************************************************-o-******
 */
void
usage_synopsis(FILE *ofp)
{
	fprintf(ofp, USAGE "\n\
\n\
    -E [0x]<engineID>		EngineID used for kul generation.\n\
    -f				Force passphrases to be read from stdin.\n\
    -h				Help.\n\
    -N \"<new_passphrase>\"	Passphrase used to generate new Ku.\n\
    -O \"<old_passphrase>\"	Passphrase used to generate old Ku.\n\
    -P				Turn off prompt indicators.\n\
    -t md5 | sha1		HMAC hash transform type.\n\
    -v				Verbose.\n\
    -V				Visible.  Echo passphrases to terminal.\n\
		"
		NL, local_progname);

}  /* end usage_synopsis() */

void
usage_to_file(FILE *ofp)
{
	char	*s;

	usage_synopsis(ofp);

	fprintf(ofp,
		"\n\
    Only -t is mandatory.  The transform is used to convert P=>Ku, convert\n\
    Ku=>Kul, and to hash the old Kul with the random bits.\n\
\n\
    Passphrase will be taken from the first successful source as follows:\n\
	a) Commandline options,\n\
	b) The file \"%s/%s\",\n\
	c) stdin  -or-  User input from the terminal.\n\
\n\
    -f will require reading from the stdin/terminal, ignoring a) and b).\n\
    -P will prevent prompts for passphrases to stdout from being printed.\n\
\n\
    <engineID> is intepreted as a hex string when preceeded by \"0x\",\n\
    otherwise it is created to contain \"text\".  If nothing is given,\n\
    <engineID> is constructed from the first IP address for the local host.\n\
		"
		NL, (s = getenv("HOME"))?s:"$HOME", local_passphrase_filename);


/* FIX -- make this possible?
    -r [0x]<random_bits>	Random bits used in KeyChange XOR.

    <engineID> and <random_bits> are intepreted as hex strings when
    preceeded by \"0x\", otherwise <engineID> is created to contain \"text\"
    and <random_bits> are the same as the ascii input.

    <random_bits> will be generated by SCAPI if not given.  If value is
    too long, it will be truncated; if too short, the remainder will be
    filled in with zeros.
 */

}  /* end usage() */


/* this defined for HPUX aCC because the aCC doesn't drop the */
/* snmp_parse_args.c functionality if compile with -g, PKY */

void usage(void)
{
    usage_to_file(stdout);    
}





/*******************************************************************-o-******
 * get_user_passphrases
 *
 * Returns:
 *	SNMPERR_SUCCESS		Success.
 *	SNMPERR_GENERR		Otherwise.
 *
 *
 * Acquire new and old passphrases from the user:
 *
 *	+ Always prompt if 'forcepassphrase' is set.
 *	+ Use given arguments if they are defined.
 *	+ Otherwise read file format from PASSPHRASE_FILE.
 *		Sanity check existence and permissions of the path.
 *		ASSUME for now that PASSPHRASE_FILE is rooted only at $HOME.
 *	+ Otherwise prompt user for passphrase(s).
 *		Echo input if 'visible' is set.
 *		Turning off 'promptindicator' makes piping in input cleaner.
 *
 * NOTE Only using forcepassphrase mandates taking both passphrases
 * from the same source.  Otherwise processing continues until both 
 * passphrases are defined.
 */
int
get_user_passphrases(void)
{
	int		 rval = SNMPERR_SUCCESS;
	size_t		 len;

	char		*obuf = NULL,
			*nbuf = NULL;
        
	char		 path[SNMP_MAXBUF],
			 buf[SNMP_MAXBUF],
			*s    = NULL;

	struct stat	 statbuf;
	FILE		*fp;



	/*
	 * Allow prompts to the user to override all other sources.
	 * Nothing to do otherwise if oldpass and newpass are already defined.
	 */
	if ( forcepassphrase )		goto get_user_passphrases_prompt;
	if ( oldpass && newpass )	goto get_user_passphrases_quit;



	/*
	 * Read passphrases out of PASSPHRASE_FILE.  Sanity check the
	 * path for existence and access first.  Refuse to read
	 * if the permissions are wrong.
	 */
	s = getenv("HOME");
	sprintf(path, "%s/%s", s, PASSPHRASE_DIR);

							/* Test directory. */
	if ( stat(path, &statbuf) < 0 ) {
		fprintf(stderr, "Cannot access directory \"%s\".\n", path);
		QUITFUN(rval = SNMPERR_GENERR, get_user_passphrases_quit);
#ifndef WIN32
	} else if ( statbuf.st_mode & (S_IRWXG|S_IRWXO) ) {
		fprintf(stderr,
		    "Directory \"%s\" is accessible by group or world.\n",
		    path);
		QUITFUN(rval = SNMPERR_GENERR, get_user_passphrases_quit);
#endif /* !WIN32 */
	}

							/* Test file. */
	sprintf(path, "%s/%s", s, local_passphrase_filename);
	if ( stat(path, &statbuf) < 0 ) {
		fprintf(stderr, "Cannot access file \"%s\".\n", path);
		QUITFUN(rval = SNMPERR_GENERR, get_user_passphrases_quit);
#ifndef WIN32
	} else if ( statbuf.st_mode & (S_IRWXG|S_IRWXO) ) {
		fprintf(stderr,
			"File \"%s\" is accessible by group or world.\n", path);
		QUITFUN(rval = SNMPERR_GENERR, get_user_passphrases_quit);
#endif /* !WIN32 */
	}

							/* Open the file. */
	if ( (fp = fopen(path, "r")) == NULL ) {
		fprintf(stderr, "Cannot open \"%s\".", path);
		QUITFUN(rval = SNMPERR_GENERR, get_user_passphrases_quit);
	}

							/* Read 1st line. */
	if ( !fgets(buf, sizeof(buf), fp) ) {		
		if ( verbose ) {
			fprintf(stderr, 
				"Passphrase file \"%s\" is empty...\n", path);
		}
		goto get_user_passphrases_prompt;

	} else if ( !oldpass ) {
		len = strlen(buf);
		if ( buf[len-1] == '\n' )	buf[--len] = '\0';
		oldpass = (char *)calloc(1,len+1);
		memcpy(oldpass, buf, len+1);
	}
							/* Read 2nd line. */
	if ( !fgets(buf, sizeof(buf), fp) ) {		
		if ( verbose ) {
			fprintf(stderr, 
				"Only one line in file \"%s\"...\n", path);
		}

	} else if ( !newpass ) {
		len = strlen(buf);
		if ( buf[len-1] == '\n' )	buf[--len] = '\0';
		newpass = (char *)calloc(1,len+1);
		memcpy(newpass, buf, len+1);
	}

	if ( oldpass && newpass )	goto get_user_passphrases_quit;



	/*
	 * Prompt the user for passphrase entry.  Visible prompts
	 * may be omitted, and invisible entry may turned off.
	 */
get_user_passphrases_prompt:
	if ( forcepassphrase ) {
		oldpass = newpass = NULL;
	}

	if ( ! oldpass ) {
		oldpass = obuf
			= snmp_getpassphrase(
			    (promptindicator) ? "Old passphrase: " : "",
			    visible);
	}
	if ( ! newpass ) {
		newpass = nbuf
			= snmp_getpassphrase(
			    (promptindicator) ? "New passphrase: " : "",
			    visible);
	}



	/*
	 * Check that both passphrases were defined.
	 */
	if ( oldpass && newpass ) {
		goto get_user_passphrases_quit;
	} else {
		rval = SNMPERR_GENERR;
	}


get_user_passphrases_quit:
	SNMP_ZERO(buf, SNMP_MAXBUF);

	if ( obuf != oldpass ) {
		SNMP_ZERO(obuf, strlen(obuf));
		SNMP_FREE(obuf);
	}
	if ( nbuf != newpass ) {
		SNMP_ZERO(nbuf, strlen(nbuf));
		SNMP_FREE(nbuf);
	}

	return rval;

}  /* end get_user_passphrases() */

/*******************************************************************-o-******
 * snmp_ttyecho
 *
 * Parameters:
 *	fd	Descriptor of terminal on which to toggle echoing.
 *	echo	TRUE if echoing should be on; FALSE otherwise.
 *      
 * Returns:
 *	Previous value of echo setting.
 *
 *
 * FIX	Put HAVE_TCGETATTR in autoconf?
 */
#ifndef HAVE_GETPASS
#ifdef HAVE_TCGETATTR
#include <termios.h>
int
snmp_ttyecho(const int fd, const int echo)
{
	struct termios tio;
	int was_echo;


	if (!isatty(fd))
		return (-1);
	tcgetattr(fd, &tio);
	was_echo = (tio.c_lflag & ECHO) != 0;
	if (echo)
		tio.c_lflag |= (ECHO | ECHONL);
	else
		tio.c_lflag &= ~(ECHO | ECHONL);
	tcsetattr(fd, TCSANOW, &tio);

	return (was_echo);

}  /* end snmp_ttyecho() */

#else
#include <sgtty.h>
int
snmp_ttyecho(const int fd, const int echo)
{
	struct sgttyb ttyparams;
	int was_echo;


	if (!isatty(fd))
		was_echo = -1;
	else {
		ioctl(fd, TIOCGETP, &ttyparams);
		was_echo = (ttyparams.sg_flags & ECHO) != 0;
		if (echo)
			ttyparams.sg_flags = ttyparams.sg_flags | ECHO;
		else
			ttyparams.sg_flags = ttyparams.sg_flags & ~ECHO;
		ioctl(fd, TIOCSETP, &ttyparams);
	}

	return (was_echo);

}  /* end snmp_ttyecho() */
#endif						/* HAVE_TCGETATTR */
#endif						/* HAVE_GETPASS */




/*******************************************************************-o-******
 * snmp_getpassphrase
 *
 * Parameters:
 *	*prompt		(May be NULL.)
 *	 bvisible	TRUE means echo back user input.
 *      
 * Returns:
 *	Pointer to newly allocated, null terminated string containing
 *		passphrase  -OR-
 *	NULL on error.
 *
 *
 * Prompt stdin for a string (or passphrase).  Return a copy of the 
 * input in a null terminated string.
 *
 * FIX	Put HAVE_GETPASS in autoconf.
 */
char *
snmp_getpassphrase(const char *prompt, int bvisible)
{
	int		 ti = 0;
	size_t		 len;

	char		*bufp = NULL;
	static char	 buffer[SNMP_MAXBUF];

	FILE		*ofp = stdout;


	/*
	 * Query stdin for a passphrase.
	 */
#ifdef HAVE_GETPASS
	if ( isatty(0) ) {
		return getpass( (prompt) ? prompt : "" );
	}
#endif

	fputs( (prompt) ? prompt : "", ofp );

	if ( !bvisible ) {
		ti = snmp_ttyecho(0, 0);
	}
	
	fgets(buffer, sizeof(buffer), stdin);

	if ( !bvisible ) {
		ti = snmp_ttyecho(0, ti);
		fputs( "\n", ofp );
	}


	/*
	 * Copy the input and zero out the read-in buffer.
	 */
	len = strlen(buffer);
	if ( buffer[len-1] == '\n' )	buffer[--len] = '\0';

	bufp = (char *)calloc(1,len+1);
	memcpy(bufp, buffer, len+1);

	SNMP_ZERO(buffer, SNMP_MAXBUF);


	return bufp;

}  /* end snmp_getpassphrase() */

#ifdef WIN32

int	snmp_ttyecho(const int fd, const int echo) { return 0; }

/*
 * stops at the first newline, carrier return, or backspace.
 * WARNING! _getch does NOT read <Ctrl-C>
 */
char *getpass( const char * prompt )
{
	static char pbuf[128];
	int ch, lim;

	_cputs(prompt);
	for (ch=0, lim=0; ch != '\n' && lim < sizeof(pbuf)-1; )
	{
		ch = _getch();  /* look ma, no echo ! */
		if (ch == '\r' || ch == '\n' || ch == '\b')
			break;
		pbuf[lim++] = ch;
	}
	pbuf[lim] = '\0';
	puts("\n");

	return pbuf;
}
#endif /* WIN32 */

