/*
 * The Initial Developer of the Original Code is International
 * Business Machines Corporation. Portions created by IBM
 * Corporation are Copyright (C) 2005 International Business
 * Machines Corporation. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the Common Public License as published by
 * IBM Corporation; either version 1 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
 * Common Public License for more details.
 *
 * You should have received a copy of the Common Public License
 * along with this program; if not, a copy can be viewed at
 * http://www.opensource.org/licenses/cpl1.0.php.
 */

#include "config.h"
#include <unistd.h>
#include <stdlib.h>
#include <trousers/tss.h>
#include <trousers/trousers.h>

#include "tpm_tspi.h"
#include "tpm_utils.h"

BOOL useUnicode = FALSE;

static const struct option sGenLongOpts[] = {
	{ "help", no_argument, NULL, 'h' },
	{ "version", no_argument, NULL, 'v' },
	{ "log", required_argument, NULL, 'l' },
	{ "unicode", no_argument, NULL, 'u' },
};

static const char *pszGenShortOpts = "hvl:u";

void initIntlSys( ) {

	setlocale( LC_ALL, "" );
	bindtextdomain( PACKAGE, LOCALEDIR );
	textdomain( PACKAGE );
}

int
genericOptHandler( int a_iNumArgs, char **a_pszArgs,
		   const char *a_pszShortOpts,
		   struct option *a_sLongOpts, int a_iNumOpts,
		   CmdOptParser a_tCmdOptParser, CmdHelpFunction a_tCmdHelpFunction ) {

	CmdHelpFunction  tCmdHelp = ( a_tCmdHelpFunction ) ? a_tCmdHelpFunction
							   : logCmdHelp;

	char  szShortOpts[strlen( pszGenShortOpts )
			  + ( ( a_pszShortOpts == NULL ) ? 0 : strlen( a_pszShortOpts ) )
			  + 1];

	int            iNumGenLongOpts = sizeof( sGenLongOpts ) / sizeof( struct option );
	struct option  sLongOpts[iNumGenLongOpts + a_iNumOpts + 1];

	int  iOpt;
	int  rc;

	strcpy( szShortOpts, pszGenShortOpts);
	if ( a_pszShortOpts )
		strcat( szShortOpts, a_pszShortOpts );

	__memset( sLongOpts, 0, sizeof( sLongOpts ) );
	memcpy( sLongOpts, sGenLongOpts, sizeof( sGenLongOpts ) );
	if ( a_sLongOpts ) {
		memcpy( sLongOpts + iNumGenLongOpts,
			a_sLongOpts,
			a_iNumOpts * sizeof( struct option ) );
	}

	while ( ( iOpt = getopt_long( a_iNumArgs, a_pszArgs,
					szShortOpts, sLongOpts, NULL ) ) != -1 ) {

		switch ( iOpt ) {
			case 'h':
				tCmdHelp( a_pszArgs[0] );
				return -1;

			case 'v':
				logMsg( _("%s version: %s\n"), a_pszArgs[0], CMD_VERSION );
				return -1;

			case 'l':
				if ( !optarg ) {
					tCmdHelp( a_pszArgs[0] );
					return -1;
				}

				if ( strcmp( optarg, LOG_NONE ) == 0 )
					iLogLevel = LOG_LEVEL_NONE;
				else if ( strcmp( optarg, LOG_ERROR ) == 0 )
					iLogLevel = LOG_LEVEL_ERROR;
				else if ( strcmp( optarg, LOG_INFO ) == 0 )
					iLogLevel = LOG_LEVEL_INFO;
				else if ( strcmp( optarg, LOG_DEBUG ) == 0 )
					iLogLevel = LOG_LEVEL_DEBUG;
				else {
					logMsg( _("Valid log levels are: %s, %s, %s, %s\n"),
						LOG_NONE,
						LOG_ERROR,
						LOG_INFO,
						LOG_DEBUG );
					tCmdHelp( a_pszArgs[0] );
					return -1;
				}
				break;
			case 'u':
				useUnicode = TRUE;
				break;
			case '?':
				tCmdHelp( a_pszArgs[0] );
				return -1;

			default:
				if ( !a_tCmdOptParser )
					return -1;

				rc = a_tCmdOptParser( iOpt, optarg );
				if ( rc != 0 )
					return rc;
				break;
		}
	}

	return 0;
}

void * __no_optimize
__memset(void *s, int c, size_t n)
{
	return memset(s, c, n);
}

/*
 * This function should be called when you are done with a password
 * the above getPasswd function to properly clean up.
 */
void shredPasswd( char *a_pszPasswd ) {

	if ( a_pszPasswd ) {
		__memset( a_pszPasswd, 0, strlen( a_pszPasswd ) );
		free( a_pszPasswd );
	}
}

/*
 * You must free the memory passed back to you when you are finished.
 * Loop will always terminate by the second pass.
 * Safest use of getpass is to zero the memory as soon as possible.
 */
char *getPlainPasswd(const char *a_pszPrompt, BOOL a_bConfirm) {
	int len;
	return _getPasswd(a_pszPrompt, &len, a_bConfirm, FALSE);
}

#ifndef TSS_LIB_IS_12
char *getPasswd(const char *a_pszPrompt, int* a_iLen, 
		BOOL a_bConfirm) {
	return _getPasswd( a_pszPrompt, a_iLen, a_bConfirm, useUnicode);
}
#endif
char *_getPasswd(const char *a_pszPrompt, int* a_iLen, 
		BOOL a_bConfirm, BOOL a_bUseUnicode) {

	char *pszPrompt = (char *)a_pszPrompt;
	char *pszPasswd = NULL;
	char *pszRetPasswd = NULL;

	do {
		// Get password value from user - this is a static buffer
		// and should never be freed
		pszPasswd = getpass( pszPrompt );
		if (!pszPasswd && pszRetPasswd) {
			shredPasswd( pszRetPasswd );
			return NULL;
		}

		// If this is confirmation pass check for match
		if ( pszRetPasswd ) {
			// Matched work complete
			if ( strcmp( pszPasswd, pszRetPasswd ) == 0)
				goto out;

			// No match clean-up
			logMsg( _("Passwords didn't match\n") );

			// pszPasswd will be cleaned up at out label
			shredPasswd( pszRetPasswd );
			pszRetPasswd = NULL;
			goto out;
		}

		// Save this passwd for next pass and/or return val
		pszRetPasswd = strdup( pszPasswd );
		if ( !pszRetPasswd )
			goto out;

		pszPrompt = _("Confirm password: ");
	} while (a_bConfirm);

out:
	if (pszRetPasswd) {
		*a_iLen = strlen(pszRetPasswd);

		if (a_bUseUnicode) {
			shredPasswd(pszRetPasswd);
			pszRetPasswd = (char *)Trspi_Native_To_UNICODE((BYTE *)pszPasswd, (unsigned int *)a_iLen);
		}
	}

	// pszPasswd is a static buffer, just clear it
	if ( pszPasswd )
		__memset( pszPasswd, 0, strlen( pszPasswd ) );

	return pszRetPasswd;
}

/*
 * You must free the memory passed back to you when you are finished.
 */
char *getReply( const char *a_pszPrompt, int a_iMaxLen ) {

	char *pszReply  = NULL;
	int   iReplyLen = a_iMaxLen + 2; // Room for newline and trailing zero

	if ( iReplyLen <= 0 )
		goto out;

	pszReply = (char *)calloc( iReplyLen, 1 );
	if ( !pszReply )
		goto out;

	logMsg( "%s", a_pszPrompt );
	pszReply = fgets( pszReply, iReplyLen, stdin );
	if ( !pszReply )
		goto out;

	// Be certain that a complete line was read
	if ( ( pszReply[ a_iMaxLen ] != '\n' ) && ( pszReply[ a_iMaxLen ] != '\0' ) ) {
		free( pszReply );
		pszReply = NULL;
		goto out;
	}

	for ( iReplyLen -= 1; iReplyLen >= 0; iReplyLen-- ) {
		if ( pszReply[ iReplyLen ] == '\0' )
			continue;

		if ( pszReply[ iReplyLen ] == '\n' )
			pszReply[ iReplyLen ] = '\0';
		break;
	}

out:
	return pszReply;
}
