/* keyedit.c - keyedit stuff
 *	Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
 *
 * This file is part of GnuPG.
 *
 * GnuPG is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * GnuPG 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>

#include "options.h"
#include "packet.h"
#include "errors.h"
#include "iobuf.h"
#include "keydb.h"
#include <gcrypt.h>
#include "util.h"
#include "main.h"
#include "trustdb.h"
#include "filter.h"
#include "ttyio.h"
#include "status.h"
#include "i18n.h"

static void show_prefs( KBNODE keyblock, PKT_user_id *uid );
static void show_key_with_all_names( KBNODE keyblock,
	    int only_marked, int with_fpr, int with_subkeys, int with_prefs );
static void show_key_and_fingerprint( KBNODE keyblock );
static void show_fingerprint( PKT_public_key *pk );
static int menu_adduid( KBNODE keyblock, KBNODE sec_keyblock );
static void menu_deluid( KBNODE pub_keyblock, KBNODE sec_keyblock );
static int  menu_delsig( KBNODE pub_keyblock );
static void menu_delkey( KBNODE pub_keyblock, KBNODE sec_keyblock );
static int menu_expire( KBNODE pub_keyblock, KBNODE sec_keyblock );
static int menu_select_uid( KBNODE keyblock, int idx );
static int menu_select_key( KBNODE keyblock, int idx );
static int count_uids( KBNODE keyblock );
static int count_uids_with_flag( KBNODE keyblock, unsigned flag );
static int count_keys_with_flag( KBNODE keyblock, unsigned flag );
static int count_selected_uids( KBNODE keyblock );
static int count_selected_keys( KBNODE keyblock );
static int menu_revsig( KBNODE keyblock );
static int menu_revkey( KBNODE pub_keyblock, KBNODE sec_keyblock );
static int enable_disable_key( KBNODE keyblock, int disable );

#define CONTROL_D ('D' - 'A' + 1)

#define NODFLG_BADSIG (1<<0)  /* bad signature */
#define NODFLG_NOKEY  (1<<1)  /* no public key */
#define NODFLG_SIGERR (1<<2)  /* other sig error */

#define NODFLG_MARK_A (1<<4)  /* temporary mark */

#define NODFLG_SELUID (1<<8)  /* indicate the selected userid */
#define NODFLG_SELKEY (1<<9)  /* indicate the selected key */
#define NODFLG_SELSIG (1<<10) /* indicate a selected signature */


struct sign_attrib {
    int non_exportable;
    struct revocation_reason_info *reason;
};




static int
get_keyblock_byname( KBNODE *keyblock, KBPOS *kbpos, const char *username )
{
    int rc;

    *keyblock = NULL;
    /* search the userid */
    rc = find_keyblock_byname( keyblock, username );
    if( rc ) {
	log_error(_("%s: user not found: %s\n"), username, gpg_errstr(rc) );
	return rc;
    }

    merge_keys_and_selfsig( *keyblock );

    return rc;
}


/****************
 * Print information about a signature, chek it and return true
 * if the signature is okay. NODE must be a signature packet.
 */
static int
print_and_check_one_sig( KBNODE keyblock, KBNODE node,
			 int *inv_sigs, int *no_key, int *oth_err,
			int *is_selfsig, int print_without_key )
{
    PKT_signature *sig = node->pkt->pkt.signature;
    int rc, sigrc;
    int is_rev = sig->sig_class == 0x30;

    switch( (rc = check_key_signature( keyblock, node, is_selfsig)) ) {
      case 0:
	node->flag &= ~(NODFLG_BADSIG|NODFLG_NOKEY|NODFLG_SIGERR);
	sigrc = '!';
	break;
      case GPGERR_BAD_SIGN:
	node->flag = NODFLG_BADSIG;
	sigrc = '-';
	if( inv_sigs )
	    ++*inv_sigs;
	break;
      case GPGERR_NO_PUBKEY:
	node->flag = NODFLG_NOKEY;
	sigrc = '?';
	if( no_key )
	    ++*no_key;
	break;
      default:
	node->flag = NODFLG_SIGERR;
	sigrc = '%';
	if( oth_err )
	    ++*oth_err;
	break;
    }
    if( sigrc != '?' || print_without_key ) {
	tty_printf("%s%c       %08lX %s   ",
		is_rev? "rev":"sig",
		sigrc, sig->keyid[1], datestr_from_sig(sig));
	if( sigrc == '%' )
	    tty_printf("[%s] ", gpg_errstr(rc) );
	else if( sigrc == '?' )
	    ;
	else if( *is_selfsig ) {
	    tty_printf( is_rev? _("[revocation]")
			      : _("[self-signature]") );
	}
	else {
	    size_t n;
	    char *p = get_user_id( sig->keyid, &n );
	    tty_print_utf8_string2( p, n, 40 );
	    gcry_free(p);
	}
	tty_printf("\n");
    }
    return (sigrc == '!');
}



/****************
 * Check the keysigs and set the flags to indicate errors.
 * Returns true if error found.
 */
static int
check_all_keysigs( KBNODE keyblock, int only_selected )
{
    KBNODE kbctx;
    KBNODE node;
    int inv_sigs = 0;
    int no_key = 0;
    int oth_err = 0;
    int has_selfsig = 0;
    int mis_selfsig = 0;
    int selected = !only_selected;
    int anyuid = 0;

    for( kbctx=NULL; (node=walk_kbnode( keyblock, &kbctx, 0)) ; ) {
	if( node->pkt->pkttype == PKT_USER_ID ) {
	    PKT_user_id *uid = node->pkt->pkt.user_id;

	    if( only_selected )
		selected = (node->flag & NODFLG_SELUID);
	    if( selected ) {
		tty_printf("uid  ");
		tty_print_utf8_string( uid->name, uid->len );
		tty_printf("\n");
		if( anyuid && !has_selfsig )
		    mis_selfsig++;
		has_selfsig = 0;
		anyuid = 1;
	    }
	}
	else if( selected && node->pkt->pkttype == PKT_SIGNATURE
		 && ( (node->pkt->pkt.signature->sig_class&~3) == 0x10
		     || node->pkt->pkt.signature->sig_class == 0x30 )  ) {
	    int selfsig;

	    if( print_and_check_one_sig( keyblock, node, &inv_sigs,
					&no_key, &oth_err, &selfsig, 0 ) ) {
		if( selfsig )
		    has_selfsig = 1;
	    }
	    /* Hmmm: should we update the trustdb here? */
	}
    }
    if( !has_selfsig )
	mis_selfsig++;
    if( inv_sigs == 1 )
	tty_printf(_("1 bad signature\n"), inv_sigs );
    else if( inv_sigs )
	tty_printf(_("%d bad signatures\n"), inv_sigs );
    if( no_key == 1 )
	tty_printf(_("1 signature not checked due to a missing key\n") );
    else if( no_key )
	tty_printf(_("%d signatures not checked due to missing keys\n"), no_key );
    if( oth_err == 1 )
	tty_printf(_("1 signature not checked due to an error\n") );
    else if( oth_err )
	tty_printf(_("%d signatures not checked due to errors\n"), oth_err );
    if( mis_selfsig == 1 )
	tty_printf(_("1 user ID without valid self-signature detected\n"));
    else if( mis_selfsig  )
	tty_printf(_("%d user IDs without valid self-signatures detected\n"),
								    mis_selfsig);

    return inv_sigs || no_key || oth_err || mis_selfsig;
}




static int
sign_mk_attrib( PKT_signature *sig, void *opaque )
{
    struct sign_attrib *attrib = opaque;
    byte buf[8];

    if( attrib->non_exportable ) {
	buf[0] = 0; /* not exportable */
	build_sig_subpkt( sig, SIGSUBPKT_EXPORTABLE, buf, 1 );
    }
    if( attrib->reason )
	revocation_reason_build_cb( sig, attrib->reason );

    return 0;
}



/****************
 * Loop over all locusr and and sign the uids after asking.
 * If no user id is marked, all user ids will be signed;
 * if some user_ids are marked those will be signed.
 */
static int
sign_uids( KBNODE keyblock, STRLIST locusr, int *ret_modified, int local )
{
    int rc = 0;
    SK_LIST sk_list = NULL;
    SK_LIST sk_rover = NULL;
    PKT_secret_key *sk = NULL;
    KBNODE node, uidnode;
    PKT_public_key *primary_pk=NULL;
    int select_all = !count_selected_uids(keyblock);
    int upd_trust = 0;

    /* build a list of all signators */
    rc=build_sk_list( locusr, &sk_list, 0, 1 );
    if( rc )
	goto leave;

    /* loop over all signaturs */
    for( sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next ) {
	u32 sk_keyid[2];
	size_t n;
	char *p;

	/* we have to use a copy of the sk, because make_keysig_packet
	 * may remove the protection from sk and if we did other
	 * changes to the secret key, we would save the unprotected
	 * version */
	if( sk )
	    free_secret_key(sk);
	sk = copy_secret_key( NULL, sk_rover->sk );
	keyid_from_sk( sk, sk_keyid );
	/* set mark A for all selected user ids */
	for( node=keyblock; node; node = node->next ) {
	    if( select_all || (node->flag & NODFLG_SELUID) )
		node->flag |= NODFLG_MARK_A;
	    else
		node->flag &= ~NODFLG_MARK_A;
	}
	/* reset mark for uids which are already signed */
	uidnode = NULL;
	for( node=keyblock; node; node = node->next ) {
	    if( node->pkt->pkttype == PKT_USER_ID ) {
		uidnode = (node->flag & NODFLG_MARK_A)? node : NULL;
	    }
	    else if( uidnode && node->pkt->pkttype == PKT_SIGNATURE
		&& (node->pkt->pkt.signature->sig_class&~3) == 0x10 ) {
		if( sk_keyid[0] == node->pkt->pkt.signature->keyid[0]
		    && sk_keyid[1] == node->pkt->pkt.signature->keyid[1] ) {
		    /* Fixme: see whether there is a revocation in which
		     * case we should allow to sign it again. */
		    tty_printf(_("Already signed by key %08lX\n"),
							(ulong)sk_keyid[1] );
		    uidnode->flag &= ~NODFLG_MARK_A; /* remove mark */
		}
	    }
	}
	/* check whether any uids are left for signing */
	if( !count_uids_with_flag(keyblock, NODFLG_MARK_A) ) {
	    tty_printf(_("Nothing to sign with key %08lX\n"),
						  (ulong)sk_keyid[1] );
	    continue;
	}
	/* Ask whether we really should sign these user id(s) */
	tty_printf("\n");
	show_key_with_all_names( keyblock, 1, 1, 0, 0 );
	tty_printf("\n");
	tty_printf(_(
	     "Are you really sure that you want to sign this key\n"
	     "with your key: \""));
	p = get_user_id( sk_keyid, &n );
	tty_print_utf8_string( p, n );
	gcry_free(p); p = NULL;
	tty_printf("\"\n\n");

	if( local )
	    tty_printf(
		  _("The signature will be marked as non-exportable.\n\n"));


	if( opt.batch && opt.answer_yes )
	    ;
	else if( !cpr_get_answer_is_yes("sign_uid.okay", _("Really sign? ")) )
	    continue;
	/* now we can sign the user ids */
      reloop: /* (must use this, because we are modifing the list) */
	primary_pk = NULL;
	for( node=keyblock; node; node = node->next ) {
	    if( node->pkt->pkttype == PKT_PUBLIC_KEY )
		primary_pk = node->pkt->pkt.public_key;
	    else if( node->pkt->pkttype == PKT_USER_ID
		     && (node->flag & NODFLG_MARK_A) ) {
		PACKET *pkt;
		PKT_signature *sig;
		struct sign_attrib attrib;

		assert( primary_pk );
		memset( &attrib, 0, sizeof attrib );
		attrib.non_exportable = local;
		node->flag &= ~NODFLG_MARK_A;
		rc = make_keysig_packet( &sig, primary_pk,
					       node->pkt->pkt.user_id,
					       NULL,
					       sk,
					       0x10, 0,
					       sign_mk_attrib,
					       &attrib );
		if( rc ) {
		    log_error(_("signing failed: %s\n"), gpg_errstr(rc));
		    goto leave;
		}
		*ret_modified = 1; /* we changed the keyblock */
		upd_trust = 1;

		pkt = gcry_xcalloc( 1, sizeof *pkt );
		pkt->pkttype = PKT_SIGNATURE;
		pkt->pkt.signature = sig;
		insert_kbnode( node, new_kbnode(pkt), PKT_SIGNATURE );
		goto reloop;
	    }
	}
    } /* end loop over signators */
    if( upd_trust && primary_pk ) {
	rc = clear_trust_checked_flag( primary_pk );
    }


  leave:
    release_sk_list( sk_list );
    if( sk )
	free_secret_key(sk);
    return rc;
}



/****************
 * Change the passphrase of the primary and all secondary keys.
 * We use only one passphrase for all keys.
 */
static int
change_passphrase( KBNODE keyblock )
{
    int rc = 0;
    int changed=0;
    KBNODE node;
    PKT_secret_key *sk;
    char *passphrase = NULL;
    int no_primary_secrets = 0;

    node = find_kbnode( keyblock, PKT_SECRET_KEY );
    if( !node ) {
	log_error("Oops; secret key not found anymore!\n");
	goto leave;
    }
    sk = node->pkt->pkt.secret_key;

    switch( is_secret_key_protected( sk ) ) {
      case -1:
	rc = GPGERR_PUBKEY_ALGO;
	break;
      case 0:
	tty_printf(_("This key is not protected.\n"));
	break;
      default:
	if( sk->protect.s2k.mode == 1001 ) {
	    tty_printf(_("Secret parts of primary key are not available.\n"));
	    no_primary_secrets = 1;
	}
	else {
	    tty_printf(_("Key is protected.\n"));
	    rc = check_secret_key( sk, 0 );
	    if( !rc )
		passphrase = get_last_passphrase();
	}
	break;
    }

    /* unprotect all subkeys (use the supplied passphrase or ask)*/
    for(node=keyblock; !rc && node; node = node->next ) {
	if( node->pkt->pkttype == PKT_SECRET_SUBKEY ) {
	    PKT_secret_key *subsk = node->pkt->pkt.secret_key;
	    set_next_passphrase( passphrase );
	    rc = check_secret_key( subsk, 0 );
	    if( !rc && !passphrase )
		passphrase = get_last_passphrase();
	}
    }

    if( rc )
	tty_printf(_("Can't edit this key: %s\n"), gpg_errstr(rc));
    else {
	DEK *dek = NULL;
	STRING2KEY *s2k = gcry_xmalloc_secure( sizeof *s2k );

	tty_printf(_("Enter the new passphrase for this secret key.\n\n") );

	set_next_passphrase( NULL );
	for(;;) {
	    s2k->mode = opt.s2k_mode;
	    s2k->hash_algo = opt.s2k_digest_algo;
	    dek = passphrase_to_dek( NULL, 0, opt.s2k_cipher_algo, s2k, 2 );
	    if( !dek ) {
		tty_printf(_("passphrase not correctly repeated; try again.\n"));
	    }
	    else if( !dek->keylen ) {
		rc = 0;
		tty_printf(_( "You don't want a passphrase -"
			    " this is probably a *bad* idea!\n\n"));
		if( cpr_get_answer_is_yes("change_passwd.empty.okay",
			       _("Do you really want to do this? ")))
		    changed++;
		break;
	    }
	    else { /* okay */
		rc = 0;
		if( !no_primary_secrets ) {
		    sk->protect.algo = dek->algo;
		    sk->protect.s2k = *s2k;
		    rc = protect_secret_key( sk, dek );
		}
		for(node=keyblock; !rc && node; node = node->next ) {
		    if( node->pkt->pkttype == PKT_SECRET_SUBKEY ) {
			PKT_secret_key *subsk = node->pkt->pkt.secret_key;
			subsk->protect.algo = dek->algo;
			subsk->protect.s2k = *s2k;
			rc = protect_secret_key( subsk, dek );
		    }
		}
		if( rc )
		    log_error("protect_secret_key failed: %s\n", gpg_errstr(rc) );
		else
		    changed++;
		break;
	    }
	}
	gcry_free(s2k);
	gcry_free(dek);
    }

  leave:
    gcry_free( passphrase );
    set_next_passphrase( NULL );
    return changed && !rc;
}


/****************
 * There are some keys out (due to a bug in gnupg), where the sequence
 * of the packets is wrong.  This function fixes that.
 * Returns: true if the keyblock has been fixed.
 *
 * Note:  This function does not work if there is more than one user ID.
 */
static int
fix_keyblock( KBNODE keyblock )
{
    KBNODE node, last, subkey;
    int fixed=0;

    /* locate key signatures of class 0x10..0x13 behind sub key packets */
    for( subkey=last=NULL, node = keyblock; node;
					    last=node, node = node->next ) {
	switch( node->pkt->pkttype ) {
	  case PKT_PUBLIC_SUBKEY:
	  case PKT_SECRET_SUBKEY:
	    if( !subkey )
		subkey = last; /* actually it is the one before the subkey */
	    break;
	  case PKT_SIGNATURE:
	    if( subkey ) {
		PKT_signature *sig = node->pkt->pkt.signature;
		if( sig->sig_class >= 0x10 && sig->sig_class <= 0x13 ) {
		    log_info(_(
			"moving a key signature to the correct place\n"));
		    last->next = node->next;
		    node->next = subkey->next;
		    subkey->next = node;
		    node = last;
		    fixed=1;
		}
	    }
	    break;
	  default: break;
	}
    }

    return fixed;
}

/****************
 * Menu driven key editor.  If sign_mode is true semi-automatical signing
 * will be performed. commands are ignore in this case
 *
 * Note: to keep track of some selection we use node->mark MARKBIT_xxxx.
 */

void
keyedit_menu( const char *username, STRLIST locusr, STRLIST commands,
						    int sign_mode )
{
    enum cmdids { cmdNONE = 0,
	   cmdQUIT, cmdHELP, cmdFPR, cmdLIST, cmdSELUID, cmdCHECK, cmdSIGN,
	   cmdLSIGN, cmdREVSIG, cmdREVKEY, cmdDELSIG,
	   cmdDEBUG, cmdSAVE, cmdADDUID, cmdDELUID, cmdADDKEY, cmdDELKEY,
	   cmdTOGGLE, cmdSELKEY, cmdPASSWD, cmdTRUST, cmdPREF, cmdEXPIRE,
	   cmdENABLEKEY, cmdDISABLEKEY,
	   cmdINVCMD, cmdNOP };
    static struct { const char *name;
		    enum cmdids id;
		    int need_sk;
		    int not_with_sk;
		    int signmode;
		    const char *desc;
		  } cmds[] = {
	{ N_("quit")    , cmdQUIT      , 0,0,1, N_("quit this menu") },
	{ N_("q")       , cmdQUIT      , 0,0,1, NULL   },
	{ N_("save")    , cmdSAVE      , 0,0,1, N_("save and quit") },
	{ N_("help")    , cmdHELP      , 0,0,1, N_("show this help") },
	{    "?"        , cmdHELP      , 0,0,1, NULL   },
	{ N_("fpr")     , cmdFPR       , 0,0,1, N_("show fingerprint") },
	{ N_("list")    , cmdLIST      , 0,0,1, N_("list key and user IDs") },
	{ N_("l")       , cmdLIST      , 0,0,1, NULL   },
	{ N_("uid")     , cmdSELUID    , 0,0,1, N_("select user ID N") },
	{ N_("key")     , cmdSELKEY    , 0,0,0, N_("select secondary key N") },
	{ N_("check")   , cmdCHECK     , 0,0,1, N_("list signatures") },
	{ N_("c")       , cmdCHECK     , 0,0,1, NULL },
	{ N_("sign")    , cmdSIGN      , 0,1,1, N_("sign the key") },
	{ N_("s")       , cmdSIGN      , 0,1,1, NULL },
	{ N_("lsign")   , cmdLSIGN     , 0,1,1, N_("sign the key locally") },
	{ N_("debug")   , cmdDEBUG     , 0,0,0, NULL },
	{ N_("adduid")  , cmdADDUID    , 1,1,0, N_("add a user ID") },
	{ N_("deluid")  , cmdDELUID    , 0,1,0, N_("delete user ID") },
	{ N_("addkey")  , cmdADDKEY    , 1,1,0, N_("add a secondary key") },
	{ N_("delkey")  , cmdDELKEY    , 0,1,0, N_("delete a secondary key") },
	{ N_("delsig")  , cmdDELSIG    , 0,1,0, N_("delete signatures") },
	{ N_("expire")  , cmdEXPIRE    , 1,1,0, N_("change the expire date") },
	{ N_("toggle")  , cmdTOGGLE    , 1,0,0, N_("toggle between secret "
						   "and public key listing") },
	{ N_("t"     )  , cmdTOGGLE    , 1,0,0, NULL },
	{ N_("pref")    , cmdPREF      , 0,1,0, N_("list preferences") },
	{ N_("passwd")  , cmdPASSWD    , 1,1,0, N_("change the passphrase") },
	{ N_("trust")   , cmdTRUST     , 0,1,0, N_("change the ownertrust") },
	{ N_("revsig")  , cmdREVSIG    , 0,1,0, N_("revoke signatures") },
	{ N_("revkey")  , cmdREVKEY    , 1,1,0, N_("revoke a secondary key") },
	{ N_("disable") , cmdDISABLEKEY, 0,1,0, N_("disable a key") },
	{ N_("enable")  , cmdENABLEKEY , 0,1,0, N_("enable a key") },

    { NULL, cmdNONE } };
    enum cmdids cmd = 0;
    int rc = 0;
    KBNODE keyblock = NULL;
    KBPOS keyblockpos;
    KBNODE sec_keyblock = NULL;
    KBNODE cur_keyblock;
    char *answer = NULL;
    int redisplay = 1;
    int modified = 0;
    int sec_modified = 0;
    int toggle;
    int have_commands = !!commands;


    if( opt.batch && !have_commands ) {
	log_error(_("can't do that in batchmode\n"));
	goto leave;
    }

    if( sign_mode ) {
	commands = NULL;
	append_to_strlist( &commands, sign_mode == 1? "sign":"lsign" );
	have_commands = 1;
    }


    if( !sign_mode ) {
	/* first try to locate it as secret key */
	rc = find_secret_keyblock_byname( &sec_keyblock, username );
        if( rc && rc != GPGERR_NO_SECKEY ) 
            log_debug("%s: secret keyblock read problem: %s\n",
						username, gpg_errstr(rc));
	if( !rc ) {
	    merge_keys_and_selfsig( sec_keyblock );
	    if( fix_keyblock( sec_keyblock ) )
		sec_modified++;
	}
    }

    /* and now get the public key */
    rc = get_keyblock_byname( &keyblock, &keyblockpos, username );
    if( rc )
	goto leave;
    if( fix_keyblock( keyblock ) )
	modified++;
    if( collapse_uids( &keyblock ) )
	modified++;

    if( sec_keyblock ) { /* check that they match */
	/* fixme: check that they both match */
	tty_printf(_("Secret key is available.\n"));
    }

    toggle = 0;
    cur_keyblock = keyblock;
    for(;;) { /* main loop */
	int i, arg_number;
	char *p;

	tty_printf("\n");
	if( redisplay ) {
	    show_key_with_all_names( cur_keyblock, 0, 0, 1, 0 );
	    tty_printf("\n");
	    redisplay = 0;
	}
	do {
	    gcry_free(answer);
	    if( have_commands ) {
		if( commands ) {
		    answer = gcry_xstrdup( commands->d );
		    commands = commands->next;
		}
		else if( opt.batch ) {
		    answer = gcry_xstrdup("quit");
		}
		else
		    have_commands = 0;
	    }
	    if( !have_commands ) {
		answer = cpr_get("keyedit.prompt", _("Command> "));
		cpr_kill_prompt();
	    }
	    trim_spaces(answer);
	} while( *answer == '#' );

	arg_number = 0; /* Yes, here is the init which egcc complains about*/
	if( !*answer )
	    cmd = cmdLIST;
	else if( *answer == CONTROL_D )
	    cmd = cmdQUIT;
	else if( isdigit( *answer ) ) {
	    cmd = cmdSELUID;
	    arg_number = atoi(answer);
	}
	else {
	    if( (p=strchr(answer,' ')) ) {
		*p++ = 0;
		trim_spaces(answer);
		trim_spaces(p);
		arg_number = atoi(p);
	    }

	    for(i=0; cmds[i].name; i++ ) {
		if( !stricmp( answer, cmds[i].name ) )
		    break;
	    }
	    if( sign_mode && !cmds[i].signmode )
		cmd = cmdINVCMD;
	    else if( cmds[i].need_sk && !sec_keyblock ) {
		tty_printf(_("Need the secret key to do this.\n"));
		cmd = cmdNOP;
	    }
	    else if( cmds[i].not_with_sk && sec_keyblock && toggle ) {
		tty_printf(_("Please use the command \"toggle\" first.\n"));
		cmd = cmdNOP;
	    }
	    else
		cmd = cmds[i].id;
	}
	switch( cmd )  {
	  case cmdHELP:
	    for(i=0; cmds[i].name; i++ ) {
		if( sign_mode && !cmds[i].signmode )
		    ;
		else if( cmds[i].need_sk && !sec_keyblock )
		    ; /* skip if we do not have the secret key */
		else if( cmds[i].desc )
		    tty_printf("%-10s %s\n", cmds[i].name, _(cmds[i].desc) );
	    }
	    break;

	  case cmdLIST:
	    redisplay = 1;
	    break;

	  case cmdFPR:
	    show_key_and_fingerprint( keyblock );
	    break;

	  case cmdSELUID:
	    if( menu_select_uid( cur_keyblock, arg_number ) )
		redisplay = 1;
	    break;

	  case cmdSELKEY:
	    if( menu_select_key( cur_keyblock, arg_number ) )
		redisplay = 1;
	    break;

	  case cmdCHECK:
	    /* we can only do this with the public key becuase the
	     * check functions can't cope with secret keys and it
	     * is questionable whether this would make sense at all */
	    check_all_keysigs( keyblock, count_selected_uids(keyblock) );
	    break;

	  case cmdSIGN: /* sign (only the public key) */
	  case cmdLSIGN: /* sign (only the public key) */
	    if( count_uids(keyblock) > 1 && !count_selected_uids(keyblock) ) {
		if( !cpr_get_answer_is_yes("keyedit.sign_all.okay",
					   _("Really sign all user IDs? ")) ) {
		    tty_printf(_("Hint: Select the user IDs to sign\n"));
		    break;
		}
	    }
	    if( !sign_uids( keyblock, locusr, &modified, cmd == cmdLSIGN )
		&& sign_mode )
		goto do_cmd_save;
	    /* Actually we should do a update_trust_record() here so that
	     * the trust gets displayed correctly. however this is not possible
	     * because we would have to save the keyblock first - something
	     * we don't want to do without an explicit save command.
	     */
	    break;

	  case cmdDEBUG:
	    dump_kbnode( cur_keyblock );
	    break;

	  case cmdTOGGLE:
	    toggle = !toggle;
	    cur_keyblock = toggle? sec_keyblock : keyblock;
	    redisplay = 1;
	    break;

	  case cmdADDUID:
	    if( menu_adduid( keyblock, sec_keyblock ) ) {
		redisplay = 1;
		sec_modified = modified = 1;
		/* must update the trustdb already here, so that preferences
		 * get listed correctly */
		rc = update_trust_record( keyblock, 0, NULL );
		if( rc ) {
		    log_error(_("update of trustdb failed: %s\n"),
				gpg_errstr(rc) );
		    rc = 0;
		}
	    }
	    break;

	  case cmdDELUID: {
		int n1;

		if( !(n1=count_selected_uids(keyblock)) )
		    tty_printf(_("You must select at least one user ID.\n"));
		else if( count_uids(keyblock) - n1 < 1 )
		    tty_printf(_("You can't delete the last user ID!\n"));
		else if( cpr_get_answer_is_yes(
			    "keyedit.remove.uid.okay",
			n1 > 1? _("Really remove all selected user IDs? ")
			      : _("Really remove this user ID? ")
		       ) ) {
		    menu_deluid( keyblock, sec_keyblock );
		    redisplay = 1;
		    modified = 1;
		    if( sec_keyblock )
		       sec_modified = 1;
		}
	    }
	    break;

	  case cmdDELSIG: {
		int n1;

		if( !(n1=count_selected_uids(keyblock)) )
		    tty_printf(_("You must select at least one user ID.\n"));
		else if( menu_delsig( keyblock ) ) {
		    /* no redisplay here, because it may scroll away some
		     * status output of delsig */
		    modified = 1;
		}
	    }
	    break;

	  case cmdADDKEY:
	    if( generate_subkeypair( keyblock, sec_keyblock ) ) {
		redisplay = 1;
		sec_modified = modified = 1;
	    }
	    break;


	  case cmdDELKEY: {
		int n1;

		if( !(n1=count_selected_keys( keyblock )) )
		    tty_printf(_("You must select at least one key.\n"));
		else if( sec_keyblock && !cpr_get_answer_is_yes(
			    "keyedit.remove.subkey.okay",
		       n1 > 1?
			_("Do you really want to delete the selected keys? "):
			_("Do you really want to delete this key? ")
		       ))
		    ;
		else {
		    menu_delkey( keyblock, sec_keyblock );
		    redisplay = 1;
		    modified = 1;
		    if( sec_keyblock )
		       sec_modified = 1;
		}
	    }
	    break;

	  case cmdREVKEY: {
		int n1;

		if( !(n1=count_selected_keys( keyblock )) )
		    tty_printf(_("You must select at least one key.\n"));
		else if( sec_keyblock && !cpr_get_answer_is_yes(
			    "keyedit.revoke.subkey.okay",
		       n1 > 1?
			_("Do you really want to revoke the selected keys? "):
			_("Do you really want to revoke this key? ")
		       ))
		    ;
		else {
		    if( menu_revkey( keyblock, sec_keyblock ) ) {
			modified = 1;
			/*sec_modified = 1;*/
		    }
		    redisplay = 1;
		}
	    }
	    break;

	  case cmdEXPIRE:
	    if( menu_expire( keyblock, sec_keyblock ) ) {
		merge_keys_and_selfsig( sec_keyblock );
		merge_keys_and_selfsig( keyblock );
		sec_modified = 1;
		modified = 1;
		redisplay = 1;
	    }
	    break;

	  case cmdPASSWD:
	    if( change_passphrase( sec_keyblock ) )
		sec_modified = 1;
	    break;

	  case cmdTRUST:
	    show_key_with_all_names( keyblock, 0, 0, 1, 0 );
	    tty_printf("\n");
	    if( edit_ownertrust( find_kbnode( keyblock,
		      PKT_PUBLIC_KEY )->pkt->pkt.public_key->local_id, 1 ) )
		redisplay = 1;
	    /* we don't need to set modified here, as the trustvalues
	     * are updated immediately */
	    break;

	  case cmdPREF:
	    show_key_with_all_names( keyblock, 0, 0, 0, 1 );
	    break;

	  case cmdNOP:
	    break;

	  case cmdREVSIG:
	    if( menu_revsig( keyblock ) ) {
		redisplay = 1;
		modified = 1;
	    }
	    break;

	  case cmdENABLEKEY:
	  case cmdDISABLEKEY:
	    if( enable_disable_key( keyblock, cmd == cmdDISABLEKEY ) ) {
		redisplay = 1;
		modified = 1;
	    }
	    break;

	  case cmdQUIT:
	    if( have_commands )
		goto leave;
	    if( !modified && !sec_modified )
		goto leave;
	    if( !cpr_get_answer_is_yes("keyedit.save.okay",
					_("Save changes? ")) ) {
		if( cpr_enabled()
		    || cpr_get_answer_is_yes("keyedit.cancel.okay",
					     _("Quit without saving? ")) )
		    goto leave;
		break;
	    }
	    /* fall thru */
	  case cmdSAVE:
	  do_cmd_save:
	    if( modified || sec_modified  ) {
		if( modified ) {
		    rc = update_keyblock( keyblock );
		    if( rc ) {
			log_error(_("update failed: %s\n"), gpg_errstr(rc) );
			break;
		    }
		}
		if( sec_modified ) {
		    rc = update_keyblock( sec_keyblock );
		    if( rc ) {
			log_error(_("update secret failed: %s\n"),
							    gpg_errstr(rc) );
			break;
		    }
		}
	    }
	    else
		tty_printf(_("Key not changed so no update needed.\n"));
	    /* TODO: we should keep track whether we have changed
	     *	     something relevant to the trustdb */
	    if( !modified && sign_mode )
		rc = 0; /* we can skip at least in this case */
	    else
		rc = update_trust_record( keyblock, 0, NULL );
	    if( rc )
		log_error(_("update of trustdb failed: %s\n"),
			    gpg_errstr(rc) );
	    goto leave;

	  case cmdINVCMD:
	  default:
	    tty_printf("\n");
	    tty_printf(_("Invalid command  (try \"help\")\n"));
	    break;
	}
    } /* end main loop */

  leave:
    release_kbnode( keyblock );
    release_kbnode( sec_keyblock );
    gcry_free(answer);
}


/****************
 * show preferences of a public keyblock.
 */
static void
show_prefs( KBNODE keyblock, PKT_user_id *uid )
{
    KBNODE node = find_kbnode( keyblock, PKT_PUBLIC_KEY );
    PKT_public_key *pk;
    byte *p;
    int i;
    size_t n;
    byte namehash[20];

    if( !node )
	return; /* is a secret keyblock */
    pk = node->pkt->pkt.public_key;
    if( !pk->local_id ) {
	log_error("oops: no LID\n");
	return;
    }

    if( uid->photo ) {
	gcry_md_hash_buffer( GCRY_MD_RMD160, namehash, uid->photo,
						       uid->photolen );
    }
    else {
	gcry_md_hash_buffer( GCRY_MD_RMD160, namehash, uid->name, uid->len );
    }

    p = get_pref_data( pk->local_id, namehash, &n );
    if( !p )
	return;

    tty_printf("    ");
    for(i=0; i < n; i+=2 ) {
	if( p[i] )
	    tty_printf( " %c%d", p[i] == PREFTYPE_SYM    ? 'S' :
				 p[i] == PREFTYPE_HASH	 ? 'H' :
				 p[i] == PREFTYPE_COMPR  ? 'Z' : '?', p[i+1]);
    }
    tty_printf("\n");

    gcry_free(p);
}


/****************
 * Display the key a the user ids, if only_marked is true, do only
 * so for user ids with mark A flag set and dont display the index number
 */
static void
show_key_with_all_names( KBNODE keyblock, int only_marked,
			 int with_fpr, int with_subkeys, int with_prefs )
{
    KBNODE node;
    int i, rc;

    /* the keys */
    for( node = keyblock; node; node = node->next ) {
	if( node->pkt->pkttype == PKT_PUBLIC_KEY
	    || (with_subkeys && node->pkt->pkttype == PKT_PUBLIC_SUBKEY) ) {
	    PKT_public_key *pk = node->pkt->pkt.public_key;
	    int otrust=0, trust=0;

	    if( node->pkt->pkttype == PKT_PUBLIC_KEY ) {
		/* do it here, so that debug messages don't clutter the
		 * output */
		trust = query_trust_info(pk, NULL);
		otrust = get_ownertrust_info( pk->local_id );
	    }

	    tty_printf(_("%s%c %4u%c/%08lX  created: %s expires: %s"),
			  node->pkt->pkttype == PKT_PUBLIC_KEY? "pub":"sub",
			  (node->flag & NODFLG_SELKEY)? '*':' ',
			  nbits_from_pk( pk ),
			  pubkey_letter( pk->pubkey_algo ),
			  (ulong)keyid_from_pk(pk,NULL),
			  datestr_from_pk(pk),
			  expirestr_from_pk(pk) );
	    if( node->pkt->pkttype == PKT_PUBLIC_KEY ) {
		tty_printf(_(" trust: %c/%c"), otrust, trust );
		if( node->pkt->pkttype == PKT_PUBLIC_KEY
		    && (get_ownertrust( pk->local_id )&TRUST_FLAG_DISABLED)) {
		    tty_printf("\n*** ");
		    tty_printf(_("This key has been disabled"));
		}

		if( with_fpr  ) {
		    tty_printf("\n");
		    show_fingerprint( pk );
		}
	    }
	    tty_printf("\n");
	}
	else if( node->pkt->pkttype == PKT_SECRET_KEY
	    || (with_subkeys && node->pkt->pkttype == PKT_SECRET_SUBKEY) ) {
	    PKT_secret_key *sk = node->pkt->pkt.secret_key;
	    tty_printf(_("%s%c %4u%c/%08lX  created: %s expires: %s"),
			  node->pkt->pkttype == PKT_SECRET_KEY? "sec":"ssb",
			  (node->flag & NODFLG_SELKEY)? '*':' ',
			  nbits_from_sk( sk ),
			  pubkey_letter( sk->pubkey_algo ),
			  (ulong)keyid_from_sk(sk,NULL),
			  datestr_from_sk(sk),
			  expirestr_from_sk(sk) );
	    tty_printf("\n");
	}
	else if( with_subkeys && node->pkt->pkttype == PKT_SIGNATURE
		 && node->pkt->pkt.signature->sig_class == 0x28       ) {
	    PKT_signature *sig = node->pkt->pkt.signature;

	    rc = check_key_signature( keyblock, node, NULL );
	    if( !rc )
		tty_printf( _("rev! subkey has been revoked: %s\n"),
			    datestr_from_sig( sig ) );
	    else if( rc == GPGERR_BAD_SIGN )
		tty_printf( _("rev- faked revocation found\n") );
	    else if( rc )
		tty_printf( _("rev? problem checking revocation: %s\n"),
							 gpg_errstr(rc) );
	}
    }
    /* the user ids */
    i = 0;
    for( node = keyblock; node; node = node->next ) {
	if( node->pkt->pkttype == PKT_USER_ID ) {
	    PKT_user_id *uid = node->pkt->pkt.user_id;
	    ++i;
	    if( !only_marked || (only_marked && (node->flag & NODFLG_MARK_A))){
		if( only_marked )
		   tty_printf("     ");
		else if( node->flag & NODFLG_SELUID )
		   tty_printf("(%d)* ", i);
		else
		   tty_printf("(%d)  ", i);
		tty_print_utf8_string( uid->name, uid->len );
		tty_printf("\n");
		if( with_prefs )
		    show_prefs( keyblock, uid );
	    }
	}
    }
}

static void
show_key_and_fingerprint( KBNODE keyblock )
{
    KBNODE node;
    PKT_public_key *pk = NULL;

    for( node = keyblock; node; node = node->next ) {
	if( node->pkt->pkttype == PKT_PUBLIC_KEY ) {
	    pk = node->pkt->pkt.public_key;
	    tty_printf("pub  %4u%c/%08lX %s ",
			  nbits_from_pk( pk ),
			  pubkey_letter( pk->pubkey_algo ),
			  (ulong)keyid_from_pk(pk,NULL),
			  datestr_from_pk(pk) );
	}
	else if( node->pkt->pkttype == PKT_USER_ID ) {
	    PKT_user_id *uid = node->pkt->pkt.user_id;
	    tty_print_utf8_string( uid->name, uid->len );
	    break;
	}
    }
    tty_printf("\n");
    if( pk )
	show_fingerprint( pk );
}


static void
show_fingerprint( PKT_public_key *pk )
{
    byte array[MAX_FINGERPRINT_LEN], *p;
    size_t i, n;

    fingerprint_from_pk( pk, array, &n );
    p = array;
    tty_printf(_("             Fingerprint:"));
    if( n == 20 ) {
	for(i=0; i < n ; i++, i++, p += 2 ) {
	    if( i == 10 )
		tty_printf(" ");
	    tty_printf(" %02X%02X", *p, p[1] );
	}
    }
    else {
	for(i=0; i < n ; i++, p++ ) {
	    if( i && !(i%8) )
		tty_printf(" ");
	    tty_printf(" %02X", *p );
	}
    }
    tty_printf("\n");
}


/****************
 * Ask for a new user id , do the selfsignature and put it into
 * both keyblocks.
 * Return true if there is a new user id
 */
static int
menu_adduid( KBNODE pub_keyblock, KBNODE sec_keyblock )
{
    PKT_user_id *uid;
    PKT_public_key *pk=NULL;
    PKT_secret_key *sk=NULL;
    PKT_signature *sig=NULL;
    PACKET *pkt;
    KBNODE node;
    KBNODE pub_where=NULL, sec_where=NULL;
    int rc;

    uid = generate_user_id();
    if( !uid )
	return 0;

    for( node = pub_keyblock; node; pub_where = node, node = node->next ) {
	if( node->pkt->pkttype == PKT_PUBLIC_KEY )
	    pk = node->pkt->pkt.public_key;
	else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY )
	    break;
    }
    if( !node ) /* no subkey */
	pub_where = NULL;
    for( node = sec_keyblock; node; sec_where = node, node = node->next ) {
	if( node->pkt->pkttype == PKT_SECRET_KEY )
	    sk = copy_secret_key( NULL, node->pkt->pkt.secret_key);
	else if( node->pkt->pkttype == PKT_SECRET_SUBKEY )
	    break;
    }
    if( !node ) /* no subkey */
	sec_where = NULL;
    assert(pk && sk );

    rc = make_keysig_packet( &sig, pk, uid, NULL, sk, 0x13, 0,
			     keygen_add_std_prefs, pk );
    free_secret_key( sk );
    if( rc ) {
	log_error("signing failed: %s\n", gpg_errstr(rc) );
	free_user_id(uid);
	return 0;
    }

    /* insert/append to secret keyblock */
    pkt = gcry_xcalloc( 1, sizeof *pkt );
    pkt->pkttype = PKT_USER_ID;
    pkt->pkt.user_id = copy_user_id(NULL, uid);
    node = new_kbnode(pkt);
    if( sec_where )
	insert_kbnode( sec_where, node, 0 );
    else
	add_kbnode( sec_keyblock, node );
    pkt = gcry_xcalloc( 1, sizeof *pkt );
    pkt->pkttype = PKT_SIGNATURE;
    pkt->pkt.signature = copy_signature(NULL, sig);
    if( sec_where )
	insert_kbnode( node, new_kbnode(pkt), 0 );
    else
	add_kbnode( sec_keyblock, new_kbnode(pkt) );
    /* insert/append to public keyblock */
    pkt = gcry_xcalloc( 1, sizeof *pkt );
    pkt->pkttype = PKT_USER_ID;
    pkt->pkt.user_id = uid;
    node = new_kbnode(pkt);
    if( pub_where )
	insert_kbnode( pub_where, node, 0 );
    else
	add_kbnode( pub_keyblock, node );
    pkt = gcry_xcalloc( 1, sizeof *pkt );
    pkt->pkttype = PKT_SIGNATURE;
    pkt->pkt.signature = copy_signature(NULL, sig);
    if( pub_where )
	insert_kbnode( node, new_kbnode(pkt), 0 );
    else
	add_kbnode( pub_keyblock, new_kbnode(pkt) );
    return 1;
}


/****************
 * Remove all selceted userids from the keyrings
 */
static void
menu_deluid( KBNODE pub_keyblock, KBNODE sec_keyblock )
{
    KBNODE node;
    int selected=0;

    for( node = pub_keyblock; node; node = node->next ) {
	if( node->pkt->pkttype == PKT_USER_ID ) {
	    selected = node->flag & NODFLG_SELUID;
	    if( selected ) {
		delete_kbnode( node );
		if( sec_keyblock ) {
		    KBNODE snode;
		    int s_selected = 0;
		    PKT_user_id *uid = node->pkt->pkt.user_id;
		    for( snode = sec_keyblock; snode; snode = snode->next ) {
			if( snode->pkt->pkttype == PKT_USER_ID ) {
			    PKT_user_id *suid = snode->pkt->pkt.user_id;

			    s_selected =
				(uid->len == suid->len
				 && !memcmp( uid->name, suid->name, uid->len));
			    if( s_selected )
				delete_kbnode( snode );
			}
			else if( s_selected
				 && snode->pkt->pkttype == PKT_SIGNATURE )
			    delete_kbnode( snode );
			else if( snode->pkt->pkttype == PKT_SECRET_SUBKEY )
			    s_selected = 0;
		    }
		}
	    }
	}
	else if( selected && node->pkt->pkttype == PKT_SIGNATURE )
	    delete_kbnode( node );
	else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY )
	    selected = 0;
    }
    commit_kbnode( &pub_keyblock );
    if( sec_keyblock )
	commit_kbnode( &sec_keyblock );
}


static int
menu_delsig( KBNODE pub_keyblock )
{
    KBNODE node;
    PKT_user_id *uid = NULL;
    int changed=0;

    for( node = pub_keyblock; node; node = node->next ) {
	if( node->pkt->pkttype == PKT_USER_ID ) {
	    uid = (node->flag & NODFLG_SELUID)? node->pkt->pkt.user_id : NULL;
	}
	else if( uid && node->pkt->pkttype == PKT_SIGNATURE ) {
	   int okay, valid, selfsig, inv_sig, no_key, other_err;

	    tty_printf("uid  ");
	    tty_print_utf8_string( uid->name, uid->len );
	    tty_printf("\n");

	   okay = inv_sig = no_key = other_err = 0;
	    valid = print_and_check_one_sig( pub_keyblock, node,
					    &inv_sig, &no_key, &other_err,
					    &selfsig, 1 );

	   if( valid )
	       okay = cpr_get_answer_yes_no_quit(
		   "keyedit.delsig.valid",
		   _("Delete this good signature? (y/N/q)"));
	   else if( inv_sig || other_err )
	       okay = cpr_get_answer_yes_no_quit(
		   "keyedit.delsig.invalid",
		   _("Delete this invalid signature? (y/N/q)"));
	   else if( no_key )
	       okay = cpr_get_answer_yes_no_quit(
		   "keyedit.delsig.unknown",
		   _("Delete this unknown signature? (y/N/q)"));

	    if( okay == -1 )
		break;
	   if( okay && selfsig && !cpr_get_answer_is_yes(
			       "keyedit.delsig.selfsig",
			      _("Really delete this self-signature? (y/N)") ))
		okay = 0;
	    if( okay ) {
		delete_kbnode( node );
		changed++;
	    }

	}
	else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY )
	    uid = NULL;
    }

    if( changed ) {
	commit_kbnode( &pub_keyblock );
	tty_printf( changed == 1? _("Deleted %d signature.\n")
				: _("Deleted %d signatures.\n"), changed );
    }
    else
	tty_printf( _("Nothing deleted.\n") );

    return changed;
}


/****************
 * Remove some of the secondary keys
 */
static void
menu_delkey( KBNODE pub_keyblock, KBNODE sec_keyblock )
{
    KBNODE node;
    int selected=0;

    for( node = pub_keyblock; node; node = node->next ) {
	if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) {
	    selected = node->flag & NODFLG_SELKEY;
	    if( selected ) {
		delete_kbnode( node );
		if( sec_keyblock ) {
		    KBNODE snode;
		    int s_selected = 0;
		    u32 ki[2];

		    keyid_from_pk( node->pkt->pkt.public_key, ki );
		    for( snode = sec_keyblock; snode; snode = snode->next ) {
			if( snode->pkt->pkttype == PKT_SECRET_SUBKEY ) {
			    u32 ki2[2];

			    keyid_from_sk( snode->pkt->pkt.secret_key, ki2 );
			    s_selected = (ki[0] == ki2[0] && ki[1] == ki2[1]);
			    if( s_selected )
				delete_kbnode( snode );
			}
			else if( s_selected
				 && snode->pkt->pkttype == PKT_SIGNATURE )
			    delete_kbnode( snode );
			else
			    s_selected = 0;
		    }
		}
	    }
	}
	else if( selected && node->pkt->pkttype == PKT_SIGNATURE )
	    delete_kbnode( node );
	else
	    selected = 0;
    }
    commit_kbnode( &pub_keyblock );
    if( sec_keyblock )
	commit_kbnode( &sec_keyblock );
}



static int
menu_expire( KBNODE pub_keyblock, KBNODE sec_keyblock )
{
    int n1, signumber, rc;
    u32 expiredate;
    int mainkey=0;
    PKT_secret_key *sk;    /* copy of the main sk */
    PKT_public_key *main_pk, *sub_pk;
    PKT_user_id *uid;
    KBNODE node;
    u32 keyid[2];

    if( count_selected_keys( sec_keyblock ) ) {
	tty_printf(_("Please remove selections from the secret keys.\n"));
	return 0;
    }

    n1 = count_selected_keys( pub_keyblock );
    if( n1 > 1 ) {
	tty_printf(_("Please select at most one secondary key.\n"));
	return 0;
    }
    else if( n1 )
	tty_printf(_("Changing expiration time for a secondary key.\n"));
    else {
	tty_printf(_("Changing expiration time for the primary key.\n"));
	mainkey=1;
    }

    expiredate = ask_expiredate();
    node = find_kbnode( sec_keyblock, PKT_SECRET_KEY );
    sk = copy_secret_key( NULL, node->pkt->pkt.secret_key);

    /* Now we can actually change the self signature(s) */
    main_pk = sub_pk = NULL;
    uid = NULL;
    signumber = 0;
    for( node=pub_keyblock; node; node = node->next ) {
	if( node->pkt->pkttype == PKT_PUBLIC_KEY ) {
	    main_pk = node->pkt->pkt.public_key;
	    keyid_from_pk( main_pk, keyid );
	    main_pk->expiredate = expiredate;
	}
	else if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY
		 && (node->flag & NODFLG_SELKEY ) ) {
	    sub_pk = node->pkt->pkt.public_key;
	    sub_pk->expiredate = expiredate;
	}
	else if( node->pkt->pkttype == PKT_USER_ID )
	    uid = node->pkt->pkt.user_id;
	else if( main_pk && node->pkt->pkttype == PKT_SIGNATURE
		 && (mainkey || sub_pk )  ) {
	    PKT_signature *sig = node->pkt->pkt.signature;
	    if( keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
		&& (	(mainkey && uid && (sig->sig_class&~3) == 0x10)
		     || (!mainkey && sig->sig_class == 0x18)  ) ) {
		/* this is a selfsignature which is to be replaced */
		PKT_signature *newsig;
		PACKET *newpkt;
		KBNODE sn;
		int signumber2 = 0;

		signumber++;

		if( (mainkey && main_pk->version < 4)
		    || (!mainkey && sub_pk->version < 4 ) ) {
		    log_info(_(
			"You can't change the expiration date of a v3 key\n"));
		    free_secret_key( sk );
		    return 0;
		}

		/* find the corresponding secret self-signature */
		for( sn=sec_keyblock; sn; sn = sn->next ) {
		    if( sn->pkt->pkttype == PKT_SIGNATURE ) {
			PKT_signature *b = sn->pkt->pkt.signature;
			if( keyid[0] == b->keyid[0] && keyid[1] == b->keyid[1]
			    && sig->sig_class == b->sig_class
			    && ++signumber2 == signumber )
			    break;
		    }
		}
		if( !sn )
		    log_info(_("No corresponding signature in secret ring\n"));

		/* create new self signature */
		if( mainkey )
		    rc = make_keysig_packet( &newsig, main_pk, uid, NULL,
					     sk, 0x13, 0,
					     keygen_add_std_prefs, main_pk );
		else
		    rc = make_keysig_packet( &newsig, main_pk, NULL, sub_pk,
					     sk, 0x18, 0,
					     keygen_add_key_expire, sub_pk );
		if( rc ) {
		    log_error("make_keysig_packet failed: %s\n",
						    gpg_errstr(rc));
		    free_secret_key( sk );
		    return 0;
		}
		/* replace the packet */
		newpkt = gcry_xcalloc( 1, sizeof *newpkt );
		newpkt->pkttype = PKT_SIGNATURE;
		newpkt->pkt.signature = newsig;
		free_packet( node->pkt );
		gcry_free( node->pkt );
		node->pkt = newpkt;
		if( sn ) {
		    newpkt = gcry_xcalloc( 1, sizeof *newpkt );
		    newpkt->pkttype = PKT_SIGNATURE;
		    newpkt->pkt.signature = copy_signature( NULL, newsig );
		    free_packet( sn->pkt );
		    gcry_free( sn->pkt );
		    sn->pkt = newpkt;
		}
		sub_pk = NULL;
	    }
	}
    }

    free_secret_key( sk );
    return 1;
}


/****************
 * Select one user id or remove all selection if index is 0.
 * Returns: True if the selection changed;
 */
static int
menu_select_uid( KBNODE keyblock, int idx )
{
    KBNODE node;
    int i;

    /* first check that the index is valid */
    if( idx ) {
	for( i=0, node = keyblock; node; node = node->next ) {
	    if( node->pkt->pkttype == PKT_USER_ID ) {
		if( ++i == idx )
		    break;
	    }
	}
	if( !node ) {
	    tty_printf(_("No user ID with index %d\n"), idx );
	    return 0;
	}
    }
    else { /* reset all */
	for( i=0, node = keyblock; node; node = node->next ) {
	    if( node->pkt->pkttype == PKT_USER_ID )
		node->flag &= ~NODFLG_SELUID;
	}
	return 1;
    }
    /* and toggle the new index */
    for( i=0, node = keyblock; node; node = node->next ) {
	if( node->pkt->pkttype == PKT_USER_ID ) {
	    if( ++i == idx ) {
		if( (node->flag & NODFLG_SELUID) )
		    node->flag &= ~NODFLG_SELUID;
		else
		    node->flag |= NODFLG_SELUID;
	    }
	}
    }

    return 1;
}

/****************
 * Select secondary keys
 * Returns: True if the selection changed;
 */
static int
menu_select_key( KBNODE keyblock, int idx )
{
    KBNODE node;
    int i;

    /* first check that the index is valid */
    if( idx ) {
	for( i=0, node = keyblock; node; node = node->next ) {
	    if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY
		|| node->pkt->pkttype == PKT_SECRET_SUBKEY ) {
		if( ++i == idx )
		    break;
	    }
	}
	if( !node ) {
	    tty_printf(_("No secondary key with index %d\n"), idx );
	    return 0;
	}
    }
    else { /* reset all */
	for( i=0, node = keyblock; node; node = node->next ) {
	    if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY
		|| node->pkt->pkttype == PKT_SECRET_SUBKEY )
		node->flag &= ~NODFLG_SELKEY;
	}
	return 1;
    }
    /* and set the new index */
    for( i=0, node = keyblock; node; node = node->next ) {
	if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY
	    || node->pkt->pkttype == PKT_SECRET_SUBKEY ) {
	    if( ++i == idx ) {
		if( (node->flag & NODFLG_SELKEY) )
		    node->flag &= ~NODFLG_SELKEY;
		else
		    node->flag |= NODFLG_SELKEY;
	    }
	}
    }

    return 1;
}


static int
count_uids_with_flag( KBNODE keyblock, unsigned flag )
{
    KBNODE node;
    int i=0;

    for( node = keyblock; node; node = node->next )
	if( node->pkt->pkttype == PKT_USER_ID && (node->flag & flag) )
	    i++;
    return i;
}

static int
count_keys_with_flag( KBNODE keyblock, unsigned flag )
{
    KBNODE node;
    int i=0;

    for( node = keyblock; node; node = node->next )
	if( ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY
	      || node->pkt->pkttype == PKT_SECRET_SUBKEY)
	    && (node->flag & flag) )
	    i++;
    return i;
}

static int
count_uids( KBNODE keyblock )
{
    KBNODE node;
    int i=0;

    for( node = keyblock; node; node = node->next )
	if( node->pkt->pkttype == PKT_USER_ID )
	    i++;
    return i;
}


/****************
 * Returns true if there is at least one selected user id
 */
static int
count_selected_uids( KBNODE keyblock )
{
    return count_uids_with_flag( keyblock, NODFLG_SELUID);
}

static int
count_selected_keys( KBNODE keyblock )
{
    return count_keys_with_flag( keyblock, NODFLG_SELKEY);
}

/*
 * Ask whether the signature should be revoked.  If the user commits this,
 * flag bit MARK_A is set on the signature and the user ID.
 */
static void
ask_revoke_sig( KBNODE keyblock, KBNODE node )
{
    PKT_signature *sig = node->pkt->pkt.signature;
    KBNODE unode = find_prev_kbnode( keyblock, node, PKT_USER_ID );

    if( !unode ) {
	log_error("Oops: no user ID for signature\n");
	return;
    }

    tty_printf(_("user ID: \""));
    tty_print_utf8_string( unode->pkt->pkt.user_id->name,
			   unode->pkt->pkt.user_id->len );
    tty_printf(_("\"\nsigned with your key %08lX at %s\n"),
		sig->keyid[1], datestr_from_sig(sig) );

    if( cpr_get_answer_is_yes("ask_revoke_sig.one",
	 _("Create a revocation certificate for this signature? (y/N)")) ) {
	node->flag |= NODFLG_MARK_A;
	unode->flag |= NODFLG_MARK_A;
    }
}

/****************
 * Display all user ids of the current public key together with signatures
 * done by one of our keys.  Then walk over all this sigs and ask the user
 * whether he wants to revoke this signature.
 * Return: True when the keyblock has changed.
 */
static int
menu_revsig( KBNODE keyblock )
{
    PKT_signature *sig;
    PKT_public_key *primary_pk;
    KBNODE node;
    int changed = 0;
    int upd_trust = 0;
    int rc, any;
    struct revocation_reason_info *reason = NULL;

    /* FIXME: detect duplicates here  */
    tty_printf(_("You have signed these user IDs:\n"));
    for( node = keyblock; node; node = node->next ) {
	node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A);
	if( node->pkt->pkttype == PKT_USER_ID ) {
	    PKT_user_id *uid = node->pkt->pkt.user_id;
	    /* Hmmm: Should we show only UIDs with a signature? */
	    tty_printf("     ");
	    tty_print_utf8_string( uid->name, uid->len );
	    tty_printf("\n");
	}
	else if( node->pkt->pkttype == PKT_SIGNATURE
		&& ((sig = node->pkt->pkt.signature),
		     !seckey_available( sig->keyid )  ) ) {
	    if( (sig->sig_class&~3) == 0x10 ) {
		tty_printf(_("   signed by %08lX at %s\n"),
			    sig->keyid[1], datestr_from_sig(sig) );
		node->flag |= NODFLG_SELSIG;
	    }
	    else if( sig->sig_class == 0x30 ) {
		tty_printf(_("   revoked by %08lX at %s\n"),
			    sig->keyid[1], datestr_from_sig(sig) );
	    }
	}
    }

    /* ask */
    for( node = keyblock; node; node = node->next ) {
	if( !(node->flag & NODFLG_SELSIG) )
	    continue;
	ask_revoke_sig( keyblock, node );
    }

    /* present selected */
    any = 0;
    for( node = keyblock; node; node = node->next ) {
	if( !(node->flag & NODFLG_MARK_A) )
	    continue;
	if( !any ) {
	    any = 1;
	    tty_printf(_("You are about to revoke these signatures:\n"));
	}
	if( node->pkt->pkttype == PKT_USER_ID ) {
	    PKT_user_id *uid = node->pkt->pkt.user_id;
	    tty_printf("     ");
	    tty_print_utf8_string( uid->name, uid->len );
	    tty_printf("\n");
	}
	else if( node->pkt->pkttype == PKT_SIGNATURE ) {
	    sig = node->pkt->pkt.signature;
	    tty_printf(_("   signed by %08lX at %s\n"),
			    sig->keyid[1], datestr_from_sig(sig) );
	}
    }
    if( !any )
	return 0; /* none selected */

    if( !cpr_get_answer_is_yes("ask_revoke_sig.okay",
	 _("Really create the revocation certificates? (y/N)")) )
	return 0; /* forget it */

    reason = ask_revocation_reason( 0, 1, 0 );
    if( !reason ) { /* user decided to cancel */
	return 0;
    }

    /* now we can sign the user ids */
  reloop: /* (must use this, because we are modifing the list) */
    primary_pk = keyblock->pkt->pkt.public_key;
    for( node=keyblock; node; node = node->next ) {
	KBNODE unode;
	PACKET *pkt;
	struct sign_attrib attrib;
	PKT_secret_key *sk;

	if( !(node->flag & NODFLG_MARK_A)
	    || node->pkt->pkttype != PKT_SIGNATURE )
	    continue;
	unode = find_prev_kbnode( keyblock, node, PKT_USER_ID );
	assert( unode ); /* we already checked this */

	memset( &attrib, 0, sizeof attrib );
	attrib.reason = reason;

	node->flag &= ~NODFLG_MARK_A;
	sk = gcry_xcalloc_secure( 1, sizeof *sk );
	if( get_seckey( sk, node->pkt->pkt.signature->keyid ) ) {
	    log_info(_("no secret key\n"));
	    continue;
	}
	rc = make_keysig_packet( &sig, primary_pk,
				       unode->pkt->pkt.user_id,
				       NULL,
				       sk,
				       0x30, 0,
				       sign_mk_attrib,
				       &attrib );
	free_secret_key(sk);
	if( rc ) {
	    log_error(_("signing failed: %s\n"), gpg_errstr(rc));
	    release_revocation_reason_info( reason );
	    return changed;
	}
	changed = 1; /* we changed the keyblock */
	upd_trust = 1;

	pkt = gcry_xcalloc( 1, sizeof *pkt );
	pkt->pkttype = PKT_SIGNATURE;
	pkt->pkt.signature = sig;
	insert_kbnode( unode, new_kbnode(pkt), 0 );
	goto reloop;
    }

    if( upd_trust )
	clear_trust_checked_flag( primary_pk );
    release_revocation_reason_info( reason );
    return changed;
}

/****************
 * Revoke some of the secondary keys.
 * Hmmm: Should we add a revocation to the secret keyring too?
 *	 Does its all make sense to duplicate most of the information?
 */
static int
menu_revkey( KBNODE pub_keyblock, KBNODE sec_keyblock )
{
    PKT_public_key *mainpk;
    KBNODE node;
    int changed = 0;
    int upd_trust = 0;
    int rc;
    struct revocation_reason_info *reason = NULL;

    reason = ask_revocation_reason( 1, 0, 0 );
    if( !reason ) { /* user decided to cancel */
	return 0;
    }


  reloop: /* (better this way because we are modifing the keyring) */
    mainpk = pub_keyblock->pkt->pkt.public_key;
    for( node = pub_keyblock; node; node = node->next ) {
	if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY
	    && (node->flag & NODFLG_SELKEY) ) {
	    PACKET *pkt;
	    PKT_signature *sig;
	    PKT_secret_key *sk;
	    PKT_public_key *subpk = node->pkt->pkt.public_key;
	    struct sign_attrib attrib;

	    memset( &attrib, 0, sizeof attrib );
	    attrib.reason = reason;

	    node->flag &= ~NODFLG_SELKEY;
	    sk = copy_secret_key( NULL, sec_keyblock->pkt->pkt.secret_key );
	    rc = make_keysig_packet( &sig, mainpk, NULL, subpk, sk, 0x28, 0,
				       sign_mk_attrib,
				       &attrib );
	    free_secret_key(sk);
	    if( rc ) {
		log_error(_("signing failed: %s\n"), gpg_errstr(rc));
		release_revocation_reason_info( reason );
		return changed;
	    }
	    changed = 1; /* we changed the keyblock */
	    upd_trust = 1;

	    pkt = gcry_xcalloc( 1, sizeof *pkt );
	    pkt->pkttype = PKT_SIGNATURE;
	    pkt->pkt.signature = sig;
	    insert_kbnode( node, new_kbnode(pkt), 0 );
	    goto reloop;
	}
    }
    commit_kbnode( &pub_keyblock );
    /*commit_kbnode( &sec_keyblock );*/

    if( upd_trust )
	clear_trust_checked_flag( mainpk );

    release_revocation_reason_info( reason );
    return changed;
}


static int
enable_disable_key( KBNODE keyblock, int disable )
{
    ulong lid = find_kbnode( keyblock, PKT_PUBLIC_KEY )
			    ->pkt->pkt.public_key->local_id;
    unsigned int trust, newtrust;

    /* Note: Because the keys have beed displayed, we have
     * ensured that local_id has been set */
    trust = newtrust = get_ownertrust( lid );
    newtrust &= ~TRUST_FLAG_DISABLED;
    if( disable )
	newtrust |= TRUST_FLAG_DISABLED;
    if( trust == newtrust )
	return 0; /* already in that state */
    if( !update_ownertrust( lid, newtrust ) )
	return 1;
    return 0;
}