diff --git a/g10/ChangeLog b/g10/ChangeLog index 9d11f1c6b..cb40e3e75 100644 --- a/g10/ChangeLog +++ b/g10/ChangeLog @@ -1,5 +1,20 @@ +2001-09-27 Werner Koch + + * pkclist.c (do_edit_ownertrust): Allow settin of ultimate trust. + + * trustdb.c (mark_keyblock_seen): New. + (make_key_array): Use it to mark the subkeys too. + (validate_keys): Store validity for ultimatly trusted keys. + 2001-09-26 Werner Koch + * pkclist.c (check_signatures_trust, do_we_trust): Removed the + invocation of add_ownertrust. Minor changes to the wording. + (add_ownertrust, add_ownertrust_cb): Removed. + + * trustdb.c (get_validity): Allow to lookup the validity using a + subkey. + * trustdb.c (new_key_hash_table): Increased the table size to 1024 and changed the masks accordingly. (validate): Changed stats printing. @@ -121,7 +136,7 @@ 2001-09-14 Werner Koch * parse-packet.c (dump_sig_subpkt): List key server preferences - and show the revocable flag correctly. Contributed by Davod Shaw. + and show the revocable flag correctly. Contributed by David Shaw. 2001-09-09 Werner Koch @@ -653,7 +668,7 @@ * cipher.c (write_header): Set extralen. * build-packet.c (do_encrypted): Use extralen instead of const 10. (do_encrypted_mdc): Ditto. - * parse-packet.c (parse_encrypted): Set extralen to 0 becuase we + * parse-packet.c (parse_encrypted): Set extralen to 0 because we don't know it here. 2001-03-30 Werner Koch diff --git a/g10/helptext.c b/g10/helptext.c index 77497b6ce..fb919dc44 100644 --- a/g10/helptext.c +++ b/g10/helptext.c @@ -49,6 +49,13 @@ static struct helptexts { const char *key; const char *help; } helptexts[] = { "to do with the (implicitly created) web-of-certificates." )}, +{ "edit_ownertrust.set_ultimate.okay", N_( + "To build the Web-of-Trust, GnuPG needs to know which keys are\n" + "ultimately trusted - those are usually the keys for which you have\n" + "access to the secret key. Answer \"yes\" to set this key to\n" + "ultimately trusted\n" +)}, + { "revoked_key.override", N_( "If you want to use this revoked key anyway, answer \"yes\"." )}, @@ -203,14 +210,6 @@ static struct helptexts { const char *key; const char *help; } helptexts[] = { "self-signatures fill be advanced by one second.\n" )}, -{ "keyedit.trust.set_ultimate.okay", N_( - "To build the Web-of-Trust, GnuPG needs to know which keys are\n" - "ultimately trusted - those are usually the keys for which you have\n" - "access to the secret key. Answer \"yes\" to set this key to\n" - "ultimately trusted; if you choose not to do so, you will then be\n" - "taken to the regular ownertrust menu.\n" -)}, - { "passphrase.enter", N_( "" diff --git a/g10/keyedit.c b/g10/keyedit.c index 9c55b555d..770b2cdfb 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -928,19 +928,6 @@ keyedit_menu( const char *username, STRLIST locusr, STRLIST commands, case cmdTRUST: show_key_with_all_names( keyblock, 0, 0, 1, 0 ); tty_printf("\n"); - if ( sec_keyblock - && cpr_get_answer_is_yes( - "keyedit.trust.set_ultimate.okay", - _("Do you want to set this key to ultimately trusted? "))) { - PKT_public_key *pk = keyblock->pkt->pkt.public_key; - - update_ownertrust (pk, - ((get_ownertrust (pk) & ~TRUST_MASK) - | TRUST_ULTIMATE )); - redisplay = 1; - break; - } - if( edit_ownertrust( find_kbnode( keyblock, PKT_PUBLIC_KEY )->pkt->pkt.public_key, 1 ) ) redisplay = 1; diff --git a/g10/pkclist.c b/g10/pkclist.c index af5813231..52310282c 100644 --- a/g10/pkclist.c +++ b/g10/pkclist.c @@ -219,11 +219,12 @@ show_paths (const PKT_public_key *pk, int only_first ) /**************** * mode: 0 = standard * 1 = Without key info and additional menu option 'm' + * this does also add an option to set the key to ultimately trusted. * Returns: * -2 = nothing changed - caller should show some additional info * -1 = quit operation * 0 = nothing changed - * 1 = new ownertrust now ion new_trust + * 1 = new ownertrust now in new_trust */ static int do_edit_ownertrust (PKT_public_key *pk, int mode, @@ -257,15 +258,17 @@ do_edit_ownertrust (PKT_public_key *pk, int mode, print_fingerprint (pk, NULL, 2); tty_printf("\n"); } - tty_printf(_( + tty_printf (_( "Please decide how far you trust this user to correctly\n" "verify other users' keys (by looking at passports,\n" - "checking fingerprints from different sources...)?\n\n" - " 1 = Don't know\n" - " 2 = I do NOT trust\n" - " 3 = I trust marginally\n" - " 4 = I trust fully\n" - " s = please show me more information\n") ); + "checking fingerprints from different sources...)?\n\n")); + tty_printf (_(" %d = Don't know\n"), 1); + tty_printf (_(" %d = I do NOT trust\n"), 2); + tty_printf (_(" %d = I trust marginally\n"), 3); + tty_printf (_(" %d = I trust fully\n"), 4); + if (mode) + tty_printf (_(" %d = I trust ultimately\n"), 5); + tty_printf (_(" s = please show me more information\n") ); if( mode ) tty_printf(_(" m = back to the main menu\n")); else @@ -282,20 +285,29 @@ do_edit_ownertrust (PKT_public_key *pk, int mode, did_help = 0; else if( *p && p[1] ) ; - else if( !p[1] && (*p >= '1' && *p <= '4') ) + else if( !p[1] && (*p >= '1' && *p <= (mode?'5':'4')) ) { - unsigned trust; + unsigned int trust; switch( *p ) { case '1': trust = TRUST_UNDEFINED; break; case '2': trust = TRUST_NEVER ; break; case '3': trust = TRUST_MARGINAL ; break; case '4': trust = TRUST_FULLY ; break; + case '5': trust = TRUST_ULTIMATE ; break; default: BUG(); } - *new_trust = trust; - changed = 1; - break; + if (trust == TRUST_ULTIMATE + && !cpr_get_answer_is_yes ("edit_ownertrust.set_ultimate.okay", + _("Do you really want to set this key" + " to ultimate trust? "))) + ; /* no */ + else + { + *new_trust = trust; + changed = 1; + break; + } } else if( *p == ans[0] || *p == ans[1] ) { @@ -350,73 +362,37 @@ edit_ownertrust (PKT_public_key *pk, int mode ) } } -static int -add_ownertrust_cb (PKT_public_key *pk ) -{ - unsigned int trust; - int rc = do_edit_ownertrust (pk, 0, &trust, 0 ); - - if( rc == 1 ) - return trust & TRUST_MASK; - return rc > 0? 0 : rc; -} - -/**************** - * Try to add some more owner trusts (interactive) - * This function presents all the signator in a certificate - * chain who have no ownertrust value assigned. - * Returns: -1 if no ownertrust were added. - */ -static int -add_ownertrust( PKT_public_key *pk, int *quit, int *trustlevel ) -{ - int rc; - unsigned flags = 0; - -#warning This function does not make sense anymore - *quit = 0; - *trustlevel = 0; - tty_printf( -_("Could not find a valid trust path to the key. Let's see whether we\n" - "can assign some missing owner trust values.\n\n")); - - *trustlevel = get_validity ( pk, NULL); - - if( !(flags & 1) ) - tty_printf(_("No path leading to one of our keys found.\n\n") ); - else if( !(flags & 2) ) - tty_printf(_("No certificates with undefined trust found.\n\n") ); - else if( !(flags & 4) ) - tty_printf(_("No trust values changed.\n\n") ); - - return (flags & 4)? 0:-1; -} /**************** * Check whether we can trust this pk which has a trustlevel of TRUSTLEVEL - * Returns: true if we trust. Might change the trustlevel + * Returns: true if we trust. */ static int -do_we_trust( PKT_public_key *pk, int *trustlevel ) +do_we_trust( PKT_public_key *pk, unsigned int *trustlevel ) { - int rc; - int did_add = 0; - int trustmask = 0; + unsigned int trustmask = 0; - retry: + /* FIXME: get_pubkey_byname already checks the validity and won't + * return keys which are either expired or revoked - so these + * question here won't get triggered. We have to find a solution + * for this. It might make sense to have a function in getkey.c + * which does only the basic checks and returns even revoked and + * expired keys. This fnction could then also returhn a list of + * keys if the speicified name is ambiguous + */ if( (*trustlevel & TRUST_FLAG_REVOKED) ) { log_info(_("key %08lX: key has been revoked!\n"), (ulong)keyid_from_pk( pk, NULL) ); show_revocation_reason( pk ); if( opt.batch ) - return 0; + return 0; /* no */ if( !cpr_get_answer_is_yes("revoked_key.override", _("Use this key anyway? ")) ) - return 0; + return 0; /* no */ trustmask |= TRUST_FLAG_REVOKED; } - else if( (*trustlevel & TRUST_FLAG_SUB_REVOKED) ) { + if( (*trustlevel & TRUST_FLAG_SUB_REVOKED) ) { log_info(_("key %08lX: subkey has been revoked!\n"), (ulong)keyid_from_pk( pk, NULL) ); show_revocation_reason( pk ); @@ -433,43 +409,25 @@ do_we_trust( PKT_public_key *pk, int *trustlevel ) if( opt.always_trust) { if( opt.verbose ) log_info("No trust check due to --always-trust option\n"); - /* The problem with this, is that EXPIRE can't be checked as - * this needs to insert a new key into the trustdb first and - * we don't want that - IS this still true? */ return 1; } - switch( (*trustlevel & TRUST_MASK) ) { - case TRUST_UNKNOWN: /* No pubkey in trustDB: Insert and check again */ - *trustlevel = get_validity (pk, NULL); - *trustlevel &= ~trustmask; - if( *trustlevel == TRUST_UNKNOWN || *trustlevel == TRUST_EXPIRED ) { - log_debug("do_we_trust: oops at %d\n", __LINE__ ); - return 0; - } - return do_we_trust( pk, trustlevel ); - case TRUST_EXPIRED: log_info(_("%08lX: key has expired\n"), (ulong)keyid_from_pk( pk, NULL) ); return 0; /* no */ + default: + log_error ("invalid trustlevel %u returned from validation layer\n", + *trustlevel); + /* fall thru */ + case TRUST_UNKNOWN: case TRUST_UNDEFINED: - if( opt.batch || opt.answer_no ) - log_info(_("%08lX: no info to calculate a trust probability\n"), - (ulong)keyid_from_pk( pk, NULL) ); - else { - int quit; - - rc = add_ownertrust( pk, &quit, trustlevel ); - *trustlevel &= ~trustmask; - if( !rc && !did_add && !quit ) { - did_add = 1; - goto retry; - } - } - return 0; + log_info(_("%08lX: There is no indication that this key " + "really belongs to the owner\n"), + (ulong)keyid_from_pk( pk, NULL) ); + return 0; /* no */ case TRUST_NEVER: log_info(_("%08lX: We do NOT trust this key\n"), @@ -491,8 +449,6 @@ do_we_trust( PKT_public_key *pk, int *trustlevel ) if( opt.verbose ) log_info(_("This key belongs to us\n")); return 1; /* yes */ - - default: BUG(); } return 1; /* yes */ @@ -505,7 +461,7 @@ do_we_trust( PKT_public_key *pk, int *trustlevel ) * key anyway. */ static int -do_we_trust_pre( PKT_public_key *pk, int trustlevel ) +do_we_trust_pre( PKT_public_key *pk, unsigned int trustlevel ) { int rc; @@ -515,7 +471,8 @@ do_we_trust_pre( PKT_public_key *pk, int trustlevel ) return 0; if( (trustlevel & TRUST_FLAG_SUB_REVOKED) && !rc ) return 0; - else if( !opt.batch && !rc ) { + + if( !opt.batch && !rc ) { char *p; u32 keyid[2]; size_t n; @@ -540,7 +497,7 @@ do_we_trust_pre( PKT_public_key *pk, int trustlevel ) _("Use this key anyway? ")) ) rc = 1; - /* Hmmm: Should we set a flag to tell the user the user about + /* Hmmm: Should we set a flag to tell the user about * his decision the next time he encrypts for this recipient? */ } @@ -561,117 +518,105 @@ do_we_trust_pre( PKT_public_key *pk, int trustlevel ) int check_signatures_trust( PKT_signature *sig ) { - PKT_public_key *pk = m_alloc_clear( sizeof *pk ); - int trustlevel; - int did_add = 0; - int rc=0; + PKT_public_key *pk = m_alloc_clear( sizeof *pk ); + unsigned int trustlevel; + int did_add = 0; + int rc=0; - - if( opt.always_trust ) { - if( !opt.quiet ) - log_info(_("WARNING: Using untrusted key!\n")); - if (opt.with_fingerprint) - print_fingerprint (pk, NULL, 1); - rc = 0; - goto leave; - } - - - rc = get_pubkey( pk, sig->keyid ); - if( rc ) { /* this should not happen */ - log_error("Ooops; the key vanished - can't check the trust\n"); - rc = G10ERR_NO_PUBKEY; - goto leave; - } - - trustlevel = get_validity (pk, NULL); - - retry: - if( (trustlevel & TRUST_FLAG_REVOKED) ) { - write_status( STATUS_KEYREVOKED ); - log_info(_("WARNING: This key has been revoked by its owner!\n")); - log_info(_(" This could mean that the signature is forgery.\n")); - show_revocation_reason( pk ); - } - else if( (trustlevel & TRUST_FLAG_SUB_REVOKED) ) { - write_status( STATUS_KEYREVOKED ); - log_info(_("WARNING: This subkey has been revoked by its owner!\n")); - show_revocation_reason( pk ); - } - - - switch( (trustlevel & TRUST_MASK) ) { - case TRUST_UNKNOWN: /* No pubkey in trustDB: Insert and check again */ - trustlevel = get_validity (pk, NULL); - if( trustlevel == TRUST_UNKNOWN || trustlevel == TRUST_EXPIRED ) - BUG(); - goto retry; - - case TRUST_EXPIRED: - log_info(_("Note: This key has expired!\n")); + if ( opt.always_trust) + { + if( !opt.quiet ) + log_info(_("WARNING: Using untrusted key!\n")); + if (opt.with_fingerprint) print_fingerprint (pk, NULL, 1); - break; - - case TRUST_UNDEFINED: - if( did_add || opt.batch || opt.answer_no ) { - write_status( STATUS_TRUST_UNDEFINED ); - log_info(_( - "WARNING: This key is not certified with a trusted signature!\n")); - log_info(_( - " There is no indication that the " - "signature belongs to the owner.\n" )); - print_fingerprint (pk, NULL, 1); - } - else { - int quit; - rc = add_ownertrust( pk, &quit, &trustlevel ); - if( rc || quit ) { - did_add = 1; - rc = 0; - } - goto retry; - } - break; - - case TRUST_NEVER: - write_status( STATUS_TRUST_NEVER ); - log_info(_("WARNING: We do NOT trust this key!\n")); - log_info(_(" The signature is probably a FORGERY.\n")); - if (opt.with_fingerprint) - print_fingerprint (pk, NULL, 1); - rc = G10ERR_BAD_SIGN; - break; - - case TRUST_MARGINAL: - write_status( STATUS_TRUST_MARGINAL ); - log_info(_( - "WARNING: This key is not certified with sufficiently trusted signatures!\n" - )); - log_info(_( - " It is not certain that the signature belongs to the owner.\n" - )); - print_fingerprint (pk, NULL, 1); - break; - - case TRUST_FULLY: - write_status( STATUS_TRUST_FULLY ); - if (opt.with_fingerprint) - print_fingerprint (pk, NULL, 1); - break; - - case TRUST_ULTIMATE: - write_status( STATUS_TRUST_ULTIMATE ); - if (opt.with_fingerprint) - print_fingerprint (pk, NULL, 1); - break; - - default: BUG(); + goto leave; } + rc = get_pubkey( pk, sig->keyid ); + if (rc) + { /* this should not happen */ + log_error("Ooops; the key vanished - can't check the trust\n"); + rc = G10ERR_NO_PUBKEY; + goto leave; + } - leave: - free_public_key( pk ); - return rc; + trustlevel = get_validity (pk, NULL); + + if ( (trustlevel & TRUST_FLAG_REVOKED) ) + { + write_status( STATUS_KEYREVOKED ); + log_info(_("WARNING: This key has been revoked by its owner!\n")); + log_info(_(" This could mean that the signature is forgery.\n")); + show_revocation_reason( pk ); + } + else if ((trustlevel & TRUST_FLAG_SUB_REVOKED) ) + { + write_status( STATUS_KEYREVOKED ); + log_info(_("WARNING: This subkey has been revoked by its owner!\n")); + show_revocation_reason( pk ); + } + + if ((trustlevel & TRUST_FLAG_DISABLED)) + log_info (_("Note: This key has been disabled.\n")); + + switch ( (trustlevel & TRUST_MASK) ) + { + case TRUST_EXPIRED: + log_info(_("Note: This key has expired!\n")); + print_fingerprint (pk, NULL, 1); + break; + + default: + log_error ("invalid trustlevel %u returned from validation layer\n", + trustlevel); + /* fall thru */ + case TRUST_UNKNOWN: + case TRUST_UNDEFINED: + if( did_add || opt.batch || opt.answer_no ) { + write_status( STATUS_TRUST_UNDEFINED ); + log_info(_("WARNING: This key is not certified with" + " a trusted signature!\n")); + log_info(_(" There is no indication that the " + "signature belongs to the owner.\n" )); + print_fingerprint (pk, NULL, 1); + } + break; + + case TRUST_NEVER: + /* currently we won't get that status */ + write_status( STATUS_TRUST_NEVER ); + log_info(_("WARNING: We do NOT trust this key!\n")); + log_info(_(" The signature is probably a FORGERY.\n")); + if (opt.with_fingerprint) + print_fingerprint (pk, NULL, 1); + rc = G10ERR_BAD_SIGN; + break; + + case TRUST_MARGINAL: + write_status( STATUS_TRUST_MARGINAL ); + log_info(_("WARNING: This key is not certified with" + " sufficiently trusted signatures!\n")); + log_info(_(" It is not certain that the" + " signature belongs to the owner.\n" )); + print_fingerprint (pk, NULL, 1); + break; + + case TRUST_FULLY: + write_status( STATUS_TRUST_FULLY ); + if (opt.with_fingerprint) + print_fingerprint (pk, NULL, 1); + break; + + case TRUST_ULTIMATE: + write_status( STATUS_TRUST_ULTIMATE ); + if (opt.with_fingerprint) + print_fingerprint (pk, NULL, 1); + break; + } + + leave: + free_public_key( pk ); + return rc; } diff --git a/g10/trustdb.c b/g10/trustdb.c index 6f3de849d..9714bf938 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -165,7 +165,6 @@ add_key_hash_table (KeyHashTable tbl, u32 *kid) tbl[(kid[1] & 0x03ff)] = kk; } - /* * Release a key_array */ @@ -671,6 +670,8 @@ get_validity (PKT_public_key *pk, const byte *namehash) int rc; ulong recno; unsigned int validity; + u32 kid[2]; + PKT_public_key *main_pk; init_trustdb (); if (!did_nextcheck) @@ -690,14 +691,33 @@ get_validity (PKT_public_key *pk, const byte *namehash) } } - rc = read_trust_record (pk, &trec); + keyid_from_pk (pk, kid); + if (pk->main_keyid[0] != kid[0] || pk->main_keyid[1] != kid[1]) + { /* this is a subkey - get the mainkey */ + main_pk = m_alloc_clear (sizeof *main_pk); + rc = get_pubkey (main_pk, pk->main_keyid); + if (rc) + { + log_error ("error getting main key %08lX of subkey %08lX: %s\n", + (ulong)pk->main_keyid[1], (ulong)kid[1], g10_errstr(rc)); + validity = TRUST_UNKNOWN; + goto leave; + } + } + else + main_pk = pk; + + rc = read_trust_record (main_pk, &trec); if (rc && rc != -1) { tdbio_invalid (); return 0; } if (rc == -1) /* no record found */ - return TRUST_UNKNOWN; + { + validity = TRUST_UNKNOWN; + goto leave; + } /* loop over all user IDs */ recno = trec.r.trust.validlist; @@ -719,13 +739,18 @@ get_validity (PKT_public_key *pk, const byte *namehash) validity |= TRUST_FLAG_DISABLED; /* set some flags direct from the key */ - if (pk->is_revoked) + if (main_pk->is_revoked) validity |= TRUST_FLAG_REVOKED; + if (main_pk != pk && pk->is_revoked) + validity |= TRUST_FLAG_SUB_REVOKED; /* Note: expiration is a trust value and not a flag - don't know why * I initially designed it that way */ - if (pk->has_expired) + if (main_pk->has_expired || pk->has_expired) validity = (validity & ~TRUST_MASK) | TRUST_EXPIRED; + leave: + if (main_pk != pk) + free_public_key (main_pk); return validity; } @@ -825,10 +850,25 @@ ask_ownertrust (u32 *kid) } +static void +mark_keyblock_seen (KeyHashTable tbl, KBNODE node) +{ + for ( ;node; node = node->next ) + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) + { + u32 aki[2]; + + keyid_from_pk (node->pkt->pkt.public_key, aki); + add_key_hash_table (tbl, aki); + } +} + + static int search_skipfnc (void *opaque, u32 *kid) { - return test_key_hash_table ((KeyHashTable)opaque, kid); + return test_key_hash_table ((KeyHashTable)opaque, kid); } /* @@ -882,7 +922,6 @@ make_key_array (KEYDB_HANDLE hd, KeyHashTable visited, do { PKT_public_key *pk; - u32 kid[2]; rc = keydb_get_keyblock (hd, &keyblock); if (rc) @@ -905,12 +944,10 @@ make_key_array (KEYDB_HANDLE hd, KeyHashTable visited, merge_keys_and_selfsig (keyblock); clear_kbnode_flags (keyblock); pk = keyblock->pkt->pkt.public_key; - keyid_from_pk (pk, kid); /*(cheap: should already be cached in the pk)*/ - if (pk->has_expired || pk->is_revoked) { /* it does not make sense to look further at those keys */ - add_key_hash_table (visited, kid); + mark_keyblock_seen (visited, keyblock); } else if (cmpfnc (keyblock, cmpval)) { @@ -920,7 +957,7 @@ make_key_array (KEYDB_HANDLE hd, KeyHashTable visited, } keys[nkeys++].keyblock = keyblock; /* This key is signed - don't check it again */ - add_key_hash_table (visited, kid); + mark_keyblock_seen (visited, keyblock); } else release_kbnode (keyblock); @@ -1256,8 +1293,38 @@ validate_keys (int interactive) goto leave; } + /* mark all UTKs as visited and set validity to ultimate */ for (k=utk_list; k; k = k->next) - add_key_hash_table (visited, k->kid); + { + KBNODE keyblock; + + keyblock = get_pubkeyblock (k->kid); + if (!keyblock) + { + log_error (_("public key of ultimately" + " trusted key %08lX not found\n"), (ulong)k->kid[1]); + continue; + } + mark_keyblock_seen (visited, keyblock); + for (node=keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_USER_ID) + { + byte namehash[20]; + PKT_user_id *uid = node->pkt->pkt.user_id; + + if( uid->photo ) + rmd160_hash_buffer (namehash, uid->photo, uid->photolen); + else + rmd160_hash_buffer (namehash, uid->name, uid->len ); + update_validity (keyblock->pkt->pkt.public_key, + namehash, 0, TRUST_ULTIMATE); + } + } + release_kbnode (keyblock); + do_sync (); + } + klist = utk_list; kdb = keydb_new (0); diff --git a/g10/trustdb.h b/g10/trustdb.h index 672d85241..e9c62670c 100644 --- a/g10/trustdb.h +++ b/g10/trustdb.h @@ -33,7 +33,7 @@ #define TRUST_ULTIMATE 6 /* u: ultimately trusted */ /* trust values not covered by the mask */ #define TRUST_FLAG_REVOKED 32 /* r: revoked */ -#define TRUST_FLAG_SUB_REVOKED 64 +#define TRUST_FLAG_SUB_REVOKED 64 /* r: revoked but for subkeys */ #define TRUST_FLAG_DISABLED 128 /* d: key/uid disabled */