#include <krb5.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>

#define TKTTIMELEFT     60*10   /* ten minutes */

static int verify_creds()
{
	krb5_context	kcontext;
	krb5_ccache		ccache;
	krb5_error_code	kres;

	kres = krb5_init_context(&kcontext);
	if( kres == 0 )
	{
		kres = krb5_cc_default( kcontext, &ccache );
		if( kres == 0 )
		{
			krb5_principal	user_princ;

			kres = krb5_cc_get_principal( kcontext, ccache, &user_princ );
			if( kres == 0 )
				krb5_free_principal( kcontext, user_princ );
			krb5_cc_close( kcontext, ccache );
		}
		krb5_free_context(kcontext);
	}
	return kres;
}

static void get_init_creds_opt_init( krb5_get_init_creds_opt *outOptions )
{
    krb5_preauthtype    preauth[] = { KRB5_PADATA_ENC_TIMESTAMP };
    krb5_enctype        etypes[] = {ENCTYPE_DES_CBC_MD5, ENCTYPE_DES_CBC_CRC};
    memset( outOptions, 0, sizeof(*outOptions) );
    krb5_get_init_creds_opt_init(outOptions);
    krb5_get_init_creds_opt_set_address_list(outOptions, NULL);
    krb5_get_init_creds_opt_set_etype_list( outOptions, etypes, sizeof(etypes)/sizeof(krb5_enctype) );
    krb5_get_init_creds_opt_set_preauth_list(outOptions, preauth, sizeof(preauth)/sizeof(krb5_preauthtype) );
}

typedef void * kbrccache_t;
#define CCACHE_PREFIX_DEFAULT "MEMORY:C_"

static kbrccache_t userinitcontext(
	const char * user, const char * domain, const char * passwd, const char * cachename, int initialize,
	int * outError )
{
	krb5_context	kcontext = 0;
	krb5_ccache		kcache = 0;
	krb5_creds		kcreds;
	krb5_principal	kme = 0;
	krb5_error_code	kres;
	char *			pPass = strdup( passwd );
	char *			pName = NULL;
	char *			pCacheName = NULL;
	int				numCreds = 0;

	memset( &kcreds, 0, sizeof(kcreds) );
	kres = krb5_init_context( &kcontext );
	if( kres )
		goto return_error;
	if( domain )
		kres = krb5_build_principal( kcontext, &kme, strlen(domain), domain, user, 0 );
	else
		kres = krb5_parse_name( kcontext, user, &kme );
	if( kres )
		goto fail;
	krb5_unparse_name( kcontext, kme, &pName );
	if( cachename )
	{
		pCacheName = malloc( strlen( pName ) + strlen( cachename ) + 1 );
		if( pCacheName == NULL )
		{
			kres = KRB5_CC_NOMEM;
			goto fail;
		}
		strcpy( pCacheName, cachename );
		strcat( pCacheName, pName );
		kres = krb5_cc_resolve( kcontext, pCacheName, &kcache );
		if( kres )
		{
			kres = krb5_cc_resolve( kcontext, CCACHE_PREFIX_DEFAULT, &kcache );
			if( kres == 0 )
				pCacheName = strdup(CCACHE_PREFIX_DEFAULT);
		}
	}
	else
	{
		kres = krb5_cc_default( kcontext, &kcache );
		pCacheName = strdup( krb5_cc_get_name( kcontext, kcache ) );
	}
	if( kres )
	{
		krb5_free_context(kcontext);
		goto return_error;
	}
	if( initialize )
		krb5_cc_initialize( kcontext, kcache, kme );
	if( kres == 0 && user && passwd )
	{
		long timeneeded = time(0L) +TKTTIMELEFT;
		int have_credentials = 0;
		krb5_cc_cursor cc_curs = NULL;
		numCreds = 0;
		if( (kres=krb5_cc_start_seq_get(kcontext, kcache, &cc_curs)) >= 0 )
		{
			while( (kres=krb5_cc_next_cred(kcontext, kcache, &cc_curs, &kcreds))== 0)
			{
				numCreds++;
				if( krb5_principal_compare( kcontext, kme, kcreds.client ) )
				{
					if( kcreds.ticket_flags & TKT_FLG_INITIAL && kcreds.times.endtime>timeneeded )
						have_credentials = 1;
				}
				krb5_free_cred_contents( kcontext, &kcreds );
				if( have_credentials )
					break;
			}
			krb5_cc_end_seq_get( kcontext, kcache, &cc_curs );
		}
		else
		{
			const char * errmsg = error_message(kres);
			fprintf( stderr, "%s user init(%s): %s\n", "setpass", pName, errmsg );
		}
		if( kres != 0 || have_credentials == 0 )
		{
			krb5_get_init_creds_opt options;
			get_init_creds_opt_init(&options);
/*
** no valid credentials - get new ones
*/
			kres = krb5_get_init_creds_password( kcontext, &kcreds, kme, pPass,
					NULL /*prompter*/, 
					NULL /*data*/,
					0 /*starttime*/,
					0 /*in_tkt_service*/,
					&options /*options*/ );
			if( kres == 0 )
			{
				if( numCreds <= 0 )
					kres = krb5_cc_initialize( kcontext, kcache, kme );
				if( kres == 0 )
					kres = krb5_cc_store_cred( kcontext, kcache, &kcreds );
				if( kres == 0 )
					have_credentials = 1;
			}
		}
#ifdef NOTUSED
		if( have_credentials )
		{
			int mstat;
			kres = gss_krb5_ccache_name( &mstat, pCacheName, NULL );
			if( getenv( ENV_DEBUG_LDAPKERB ) )
				fprintf( stderr, "gss credentials cache set to %s(%d)\n", pCacheName, kres );
		}
#endif
		krb5_cc_close( kcontext, kcache );
	}
fail:
	if( kres )
	{
			const char * errmsg = error_message(kres);
			fprintf( stderr, "%s user init(%s): %s\n", "setpass", pName, errmsg );
	}
	krb5_free_principal( kcontext, kme );
	krb5_free_cred_contents( kcontext, &kcreds );
	if( pName )
		free( pName );
	free(pPass);
	krb5_free_context(kcontext);

return_error:
	if( kres )
	{
		if( pCacheName )
		{
			free(pCacheName);
			pCacheName = NULL;
		}
	}
	if( outError )
		*outError = kres;
	return pCacheName;
}

static int init_creds()
{
	char user[512];
	char * password = NULL;
	int result;

	user[0] = 0;
	result = -1;

	for(;;)
	{
		while( user[0] == 0 )
		{
			int userlen;
			printf( "Username: ");
			fflush(stdout);
			if( fgets( user, sizeof(user), stdin ) == NULL )
				return -1;
			userlen = strlen( user);
			if( userlen < 2 )
				continue;
			user[userlen-1] = 0;	/* get rid of the newline */
			break;
		}
		{
			kbrccache_t usercontext;
			password = getpass( "Password: ");
			if( ! password )
				return -1;
			result = 0;
			usercontext = userinitcontext( user, NULL, password, NULL, 1, &result );
			if( usercontext )
				break;
		}
	}
	return result;
}

int main( int argc, char ** argv )
{
	char * new_password = NULL;
	char * new_password2;
	krb5_context	kcontext;
	krb5_error_code	kerr;
	krb5_principal	target_principal;


	if( argc < 2 )
	{
		fprintf( stderr, "Usage: setpass user@REALM\n");
		exit(1);
	}

/*
** verify credentials -
*/
	if( verify_creds() )
		init_creds();
	if( verify_creds() )
	{
		fprintf( stderr, "No user credentials available\n");
		exit(1);
	}
/*
** check the principal name -
*/
	krb5_init_context(&kcontext);
	kerr = krb5_parse_name( kcontext, argv[1], &target_principal );

	{
		char * pname = NULL;
		kerr = krb5_unparse_name( kcontext, target_principal, &pname );
		printf( "Changing password for %s:\n", pname);
		fflush( stdout );
		free( pname );
	}
/*
** get the new password -
*/
	while( !new_password )
	{
		new_password = getpass("Enter new password: ");
		new_password2 = getpass("Verify new password: ");
		if( strcmp( new_password, new_password2 ) )
		{
			printf("Passwords do not match\n");
			free( new_password );
			free( new_password2 );
			continue;
		}
	}
/*
** change the password -
*/
	fprintf( stderr, "the password is %s\n", new_password );

	{
		int pw_result;
		krb5_ccache ccache;
		krb5_data	pw_res_string, res_string;

		kerr = krb5_cc_default( kcontext, &ccache );
		if( kerr == 0 )
		{
			kerr = krb5_set_password_using_ccache(kcontext, ccache, new_password, target_principal,
						&pw_result, &pw_res_string, &res_string );
			if( kerr )
				fprintf( stderr, "Failed: %s\n", error_message(kerr) );
			else
			{
				if( pw_result )
				{
					fprintf( stderr, "Failed(%d)", pw_result );
					if( pw_res_string.length > 0 )
						fprintf( stderr, ": %s", pw_res_string.data);
					if( res_string.length > 0 )
						fprintf( stderr, " %s", res_string.data);
					fprintf( stderr, "\n");
				}
			}
		}
	}
	return(0);
}
