diff --git a/g10/tdbdump.c b/g10/tdbdump.c index 5ea903f45..2c6f5c2fa 100644 --- a/g10/tdbdump.c +++ b/g10/tdbdump.c @@ -1,237 +1,237 @@ /* tdbdump.c * Copyright (C) 1998, 1999, 2000, 2001 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 3 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, see . */ #include #include #include #include #include #include #include #include #include #include #include "gpg.h" #include "../common/status.h" #include "../common/iobuf.h" #include "keydb.h" #include "../common/util.h" #include "trustdb.h" #include "options.h" #include "packet.h" #include "main.h" #include "../common/i18n.h" #include "tdbio.h" #define HEXTOBIN(x) ( (x) >= '0' && (x) <= '9' ? ((x)-'0') : \ (x) >= 'A' && (x) <= 'F' ? ((x)-'A'+10) : ((x)-'a'+10)) /* * Write a record; die on error. */ static void write_record (ctrl_t ctrl, TRUSTREC *rec) { int rc = tdbio_write_record (ctrl, rec); if( !rc ) return; log_error(_("trust record %lu, type %d: write failed: %s\n"), rec->recnum, rec->rectype, gpg_strerror (rc) ); tdbio_invalid(); } /* * Dump the entire trustdb to FP or only the entries of one key. */ void list_trustdb (ctrl_t ctrl, estream_t fp, const char *username) { TRUSTREC rec; (void)username; init_trustdb (ctrl, 0); /* For now we ignore the user ID. */ if (1) { ulong recnum; int i; es_fprintf (fp, "TrustDB: %s\n", tdbio_get_dbname ()); for (i = 9 + strlen (tdbio_get_dbname()); i > 0; i-- ) es_fputc ('-', fp); es_putc ('\n', fp); for (recnum=0; !tdbio_read_record (recnum, &rec, 0); recnum++) tdbio_dump_record (&rec, fp); } } /**************** * Print a list of all defined owner trust value. */ void export_ownertrust (ctrl_t ctrl) { TRUSTREC rec; ulong recnum; int i; byte *p; init_trustdb (ctrl, 0); es_printf (_("# List of assigned trustvalues, created %s\n" "# (Use \"gpg --import-ownertrust\" to restore them)\n"), asctimestamp( make_timestamp() ) ); for (recnum=0; !tdbio_read_record (recnum, &rec, 0); recnum++ ) { if (rec.rectype == RECTYPE_TRUST) { if (!rec.r.trust.ownertrust) continue; p = rec.r.trust.fingerprint; for (i=0; i < 20; i++, p++ ) es_printf("%02X", *p ); es_printf (":%u:\n", (unsigned int)rec.r.trust.ownertrust ); } } } void import_ownertrust (ctrl_t ctrl, const char *fname ) { estream_t fp; int is_stdin=0; char line[256]; char *p; size_t n, fprlen; unsigned int otrust; byte fpr[20]; int any = 0; int rc; init_trustdb (ctrl, 0); if( iobuf_is_pipe_filename (fname) ) { fp = es_stdin; fname = "[stdin]"; is_stdin = 1; } else if( !(fp = es_fopen( fname, "r" )) ) { log_error ( _("can't open '%s': %s\n"), fname, strerror(errno) ); return; } if (is_secured_file (es_fileno (fp))) { es_fclose (fp); gpg_err_set_errno (EPERM); log_error (_("can't open '%s': %s\n"), fname, strerror(errno) ); return; } while (es_fgets (line, DIM(line)-1, fp)) { TRUSTREC rec; if( !*line || *line == '#' ) continue; n = strlen(line); if( line[n-1] != '\n' ) { log_error (_("error in '%s': %s\n"), fname, _("line too long") ); /* ... or last line does not have a LF */ break; /* can't continue */ } for(p = line; *p && *p != ':' ; p++ ) if( !hexdigitp(p) ) break; if( *p != ':' ) { log_error (_("error in '%s': %s\n"), fname, _("colon missing") ); continue; } fprlen = p - line; if( fprlen != 32 && fprlen != 40 ) { log_error (_("error in '%s': %s\n"), fname, _("invalid fingerprint") ); continue; } if( sscanf(p, ":%u:", &otrust ) != 1 ) { log_error (_("error in '%s': %s\n"), fname, _("ownertrust value missing")); continue; } if( !otrust ) continue; /* no otrust defined - no need to update or insert */ /* convert the ascii fingerprint to binary */ for(p=line, fprlen=0; fprlen < 20 && *p != ':'; p += 2 ) fpr[fprlen++] = HEXTOBIN(p[0]) * 16 + HEXTOBIN(p[1]); while (fprlen < 20) fpr[fprlen++] = 0; - rc = tdbio_search_trust_byfpr (fpr, &rec); + rc = tdbio_search_trust_byfpr (ctrl, fpr, &rec); if( !rc ) { /* found: update */ if (rec.r.trust.ownertrust != otrust) { if (!opt.quiet) { if( rec.r.trust.ownertrust ) log_info("changing ownertrust from %u to %u\n", rec.r.trust.ownertrust, otrust ); else log_info("setting ownertrust to %u\n", otrust ); } rec.r.trust.ownertrust = otrust; write_record (ctrl, &rec); any = 1; } } else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { /* insert */ if (!opt.quiet) log_info("inserting ownertrust of %u\n", otrust ); memset (&rec, 0, sizeof rec); rec.recnum = tdbio_new_recnum (ctrl); rec.rectype = RECTYPE_TRUST; memcpy (rec.r.trust.fingerprint, fpr, 20); rec.r.trust.ownertrust = otrust; write_record (ctrl, &rec); any = 1; } else /* error */ log_error (_("error finding trust record in '%s': %s\n"), fname, gpg_strerror (rc)); } if (es_ferror (fp)) log_error ( _("read error in '%s': %s\n"), fname, strerror(errno) ); if (!is_stdin) es_fclose (fp); if (any) { revalidation_mark (ctrl); rc = tdbio_sync (); if (rc) log_error (_("trustdb: sync failed: %s\n"), gpg_strerror (rc) ); } } diff --git a/g10/tdbio.c b/g10/tdbio.c index 7314143af..1e454869d 100644 --- a/g10/tdbio.c +++ b/g10/tdbio.c @@ -1,1894 +1,1896 @@ /* tdbio.c - trust database I/O operations * Copyright (C) 1998-2002, 2012 Free Software Foundation, Inc. * Copyright (C) 1998-2015 Werner Koch * * 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 3 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, see . */ #include #include #include #include #include #include #include #include #include #include "gpg.h" #include "../common/status.h" #include "../common/iobuf.h" #include "../common/util.h" #include "options.h" #include "main.h" #include "../common/i18n.h" #include "trustdb.h" #include "tdbio.h" #if defined(HAVE_DOSISH_SYSTEM) && !defined(ftruncate) #define ftruncate chsize #endif #if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__) #define MY_O_BINARY O_BINARY #else #define MY_O_BINARY 0 #endif /* We use ERRNO despite that the cegcc provided open/read/write functions don't set ERRNO - at least show that ERRNO does not make sense. */ #ifdef HAVE_W32CE_SYSTEM #undef strerror #define strerror(a) ("[errno not available]") #endif /* * Yes, this is a very simple implementation. We should really * use a page aligned buffer and read complete pages. * To implement a simple trannsaction system, this is sufficient. */ typedef struct cache_ctrl_struct *CACHE_CTRL; struct cache_ctrl_struct { CACHE_CTRL next; struct { unsigned used:1; unsigned dirty:1; } flags; ulong recno; char data[TRUST_RECORD_LEN]; }; /* Size of the cache. The SOFT value is the general one. While in a transaction this may not be sufficient and thus we may increase it then up to the HARD limit. */ #define MAX_CACHE_ENTRIES_SOFT 200 #define MAX_CACHE_ENTRIES_HARD 10000 /* The cache is controlled by these variables. */ static CACHE_CTRL cache_list; static int cache_entries; static int cache_is_dirty; /* An object to pass information to cmp_krec_fpr. */ struct cmp_krec_fpr_struct { int pubkey_algo; const char *fpr; int fprlen; }; /* An object used to pass information to cmp_[s]dir. */ struct cmp_xdir_struct { int pubkey_algo; u32 keyid[2]; }; /* The name of the trustdb file. */ static char *db_name; /* The handle for locking the trustdb file and a counter to record how * often this lock has been taken. That counter is required becuase * dotlock does not implemen recursive locks. */ static dotlock_t lockhandle; static unsigned int is_locked; /* The file descriptor of the trustdb. */ static int db_fd = -1; /* A flag indicating that a transaction is active. */ /* static int in_transaction; Not yet used. */ static void open_db (void); static void create_hashtable (ctrl_t ctrl, TRUSTREC *vr, int type); /* * Take a lock on the trustdb file name. I a lock file can't be * created the function terminates the process. Except for a * different return code the function does nothing if the lock has * already been taken. * * Returns: True if lock already exists, False if the lock has * actually been taken. */ static int take_write_lock (void) { int rc; if (!lockhandle) lockhandle = dotlock_create (db_name, 0); if (!lockhandle) log_fatal ( _("can't create lock for '%s'\n"), db_name ); if (!is_locked) { if (dotlock_take (lockhandle, -1) ) log_fatal ( _("can't lock '%s'\n"), db_name ); rc = 0; } else rc = 1; if (opt.lock_once) is_locked = 1; else is_locked++; return rc; } /* * Release a lock from the trustdb file unless the global option * --lock-once has been used. */ static void release_write_lock (void) { if (opt.lock_once) return; /* Don't care; here IS_LOCKED is fixed to 1. */ if (!is_locked) { log_error ("Ooops, tdbio:release_write_lock with no lock held\n"); return; } if (--is_locked) return; if (dotlock_release (lockhandle)) log_error ("Oops, tdbio:release_write_locked failed\n"); } /************************************* ************* record cache ********** *************************************/ /* * Get the data from the record cache and return a pointer into that * cache. Caller should copy the returned data. NULL is returned on * a cache miss. */ static const char * get_record_from_cache (ulong recno) { CACHE_CTRL r; for (r = cache_list; r; r = r->next) { if (r->flags.used && r->recno == recno) return r->data; } return NULL; } /* * Write a cached item back to the trustdb file. * * Returns: 0 on success or an error code. */ static int write_cache_item (CACHE_CTRL r) { gpg_error_t err; int n; if (lseek (db_fd, r->recno * TRUST_RECORD_LEN, SEEK_SET) == -1) { err = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: lseek failed: %s\n"), r->recno, strerror (errno)); return err; } n = write (db_fd, r->data, TRUST_RECORD_LEN); if (n != TRUST_RECORD_LEN) { err = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"), r->recno, n, strerror (errno) ); return err; } r->flags.dirty = 0; return 0; } /* * Put data into the cache. This function may flush * some cache entries if the cache is filled up. * * Returns: 0 on success or an error code. */ static int put_record_into_cache (ulong recno, const char *data) { CACHE_CTRL r, unused; int dirty_count = 0; int clean_count = 0; /* See whether we already cached this one. */ for (unused = NULL, r = cache_list; r; r = r->next) { if (!r->flags.used) { if (!unused) unused = r; } else if (r->recno == recno) { if (!r->flags.dirty) { /* Hmmm: should we use a copy and compare? */ if (memcmp (r->data, data, TRUST_RECORD_LEN)) { r->flags.dirty = 1; cache_is_dirty = 1; } } memcpy (r->data, data, TRUST_RECORD_LEN); return 0; } if (r->flags.used) { if (r->flags.dirty) dirty_count++; else clean_count++; } } /* Not in the cache: add a new entry. */ if (unused) { /* Reuse this entry. */ r = unused; r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; cache_is_dirty = 1; cache_entries++; return 0; } /* See whether we reached the limit. */ if (cache_entries < MAX_CACHE_ENTRIES_SOFT) { /* No: Put into cache. */ r = xmalloc (sizeof *r); r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; r->next = cache_list; cache_list = r; cache_is_dirty = 1; cache_entries++; return 0; } /* Cache is full: discard some clean entries. */ if (clean_count) { int n; /* We discard a third of the clean entries. */ n = clean_count / 3; if (!n) n = 1; for (unused = NULL, r = cache_list; r; r = r->next) { if (r->flags.used && !r->flags.dirty) { if (!unused) unused = r; r->flags.used = 0; cache_entries--; if (!--n) break; } } /* Now put into the cache. */ log_assert (unused); r = unused; r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; cache_is_dirty = 1; cache_entries++; return 0; } /* No clean entries: We have to flush some dirty entries. */ #if 0 /* Transactions are not yet used. */ if (in_transaction) { /* But we can't do this while in a transaction. Thus we * increase the cache size instead. */ if (cache_entries < MAX_CACHE_ENTRIES_HARD) { if (opt.debug && !(cache_entries % 100)) log_debug ("increasing tdbio cache size\n"); r = xmalloc (sizeof *r); r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; r->next = cache_list; cache_list = r; cache_is_dirty = 1; cache_entries++; return 0; } /* Hard limit for the cache size reached. */ log_info (_("trustdb transaction too large\n")); return GPG_ERR_RESOURCE_LIMIT; } #endif if (dirty_count) { int n; /* Discard some dirty entries. */ n = dirty_count / 5; if (!n) n = 1; take_write_lock (); for (unused = NULL, r = cache_list; r; r = r->next) { if (r->flags.used && r->flags.dirty) { int rc; rc = write_cache_item (r); if (rc) return rc; if (!unused) unused = r; r->flags.used = 0; cache_entries--; if (!--n) break; } } release_write_lock (); /* Now put into the cache. */ log_assert (unused); r = unused; r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; cache_is_dirty = 1; cache_entries++; return 0; } /* We should never reach this. */ BUG(); } /* Return true if the cache is dirty. */ int tdbio_is_dirty() { return cache_is_dirty; } /* * Flush the cache. This cannot be used while in a transaction. */ int tdbio_sync() { CACHE_CTRL r; int did_lock = 0; if( db_fd == -1 ) open_db(); #if 0 /* Transactions are not yet used. */ if( in_transaction ) log_bug("tdbio: syncing while in transaction\n"); #endif if( !cache_is_dirty ) return 0; if (!take_write_lock ()) did_lock = 1; for( r = cache_list; r; r = r->next ) { if( r->flags.used && r->flags.dirty ) { int rc = write_cache_item( r ); if( rc ) return rc; } } cache_is_dirty = 0; if (did_lock) release_write_lock (); return 0; } #if 0 /* Not yet used. */ /* * Simple transactions system: * Everything between begin_transaction and end/cancel_transaction * is not immediately written but at the time of end_transaction. * * NOTE: The transaction code is disabled in the 1.2 branch, as it is * not yet used. */ int tdbio_begin_transaction () /* Not yet used. */ { int rc; if (in_transaction) log_bug ("tdbio: nested transactions\n"); /* Flush everything out. */ rc = tdbio_sync(); if (rc) return rc; in_transaction = 1; return 0; } int tdbio_end_transaction () /* Not yet used. */ { int rc; if (!in_transaction) log_bug ("tdbio: no active transaction\n"); take_write_lock (); gnupg_block_all_signals (); in_transaction = 0; rc = tdbio_sync(); gnupg_unblock_all_signals(); release_write_lock (); return rc; } int tdbio_cancel_transaction () /* Not yet used. */ { CACHE_CTRL r; if (!in_transaction) log_bug ("tdbio: no active transaction\n"); /* Remove all dirty marked entries, so that the original ones are * read back the next time. */ if (cache_is_dirty) { for (r = cache_list; r; r = r->next) { if (r->flags.used && r->flags.dirty) { r->flags.used = 0; cache_entries--; } } cache_is_dirty = 0; } in_transaction = 0; return 0; } #endif /* Not yet used. */ /******************************************************** **************** cached I/O functions ****************** ********************************************************/ /* The cleanup handler for this module. */ static void cleanup (void) { if (is_locked) { if (!dotlock_release (lockhandle)) is_locked = 0; } } /* * Update an existing trustdb record. The caller must call * tdbio_sync. * * Returns: 0 on success or an error code. */ int tdbio_update_version_record (ctrl_t ctrl) { TRUSTREC rec; int rc; memset (&rec, 0, sizeof rec); rc = tdbio_read_record (0, &rec, RECTYPE_VER); if (!rc) { rec.r.ver.created = make_timestamp(); rec.r.ver.marginals = opt.marginals_needed; rec.r.ver.completes = opt.completes_needed; rec.r.ver.cert_depth = opt.max_cert_depth; rec.r.ver.trust_model = opt.trust_model; rec.r.ver.min_cert_level = opt.min_cert_level; rc = tdbio_write_record (ctrl, &rec); } return rc; } /* * Create and write the trustdb version record. * This is called with the writelock activ. * Returns: 0 on success or an error code. */ static int create_version_record (ctrl_t ctrl) { TRUSTREC rec; int rc; memset (&rec, 0, sizeof rec); rec.r.ver.version = 3; rec.r.ver.created = make_timestamp (); rec.r.ver.marginals = opt.marginals_needed; rec.r.ver.completes = opt.completes_needed; rec.r.ver.cert_depth = opt.max_cert_depth; if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC) rec.r.ver.trust_model = opt.trust_model; else rec.r.ver.trust_model = TM_PGP; rec.r.ver.min_cert_level = opt.min_cert_level; rec.rectype = RECTYPE_VER; rec.recnum = 0; rc = tdbio_write_record (ctrl, &rec); if (!rc) tdbio_sync (); if (!rc) create_hashtable (ctrl, &rec, 0); return rc; } /* * Set the file name for the trustdb to NEW_DBNAME and if CREATE is * true create that file. If NEW_DBNAME is NULL a default name is * used, if the it does not contain a path component separator ('/') * the global GnuPG home directory is used. * * Returns: 0 on success or an error code. * * On the first call this function registers an atexit handler. * */ int tdbio_set_dbname (ctrl_t ctrl, const char *new_dbname, int create, int *r_nofile) { char *fname, *p; struct stat statbuf; static int initialized = 0; int save_slash; if (!initialized) { atexit (cleanup); initialized = 1; } *r_nofile = 0; if (!new_dbname) { fname = make_filename (gnupg_homedir (), "trustdb" EXTSEP_S GPGEXT_GPG, NULL); } else if (*new_dbname != DIRSEP_C ) { if (strchr (new_dbname, DIRSEP_C)) fname = make_filename (new_dbname, NULL); else fname = make_filename (gnupg_homedir (), new_dbname, NULL); } else { fname = xstrdup (new_dbname); } xfree (db_name); db_name = fname; /* Quick check for (likely) case where there already is a * trustdb.gpg. This check is not required in theory, but it helps * in practice avoiding costly operations of preparing and taking * the lock. */ if (!stat (fname, &statbuf) && statbuf.st_size > 0) { /* OK, we have the valid trustdb.gpg already. */ return 0; } else if (!create) { *r_nofile = 1; return 0; } /* Here comes: No valid trustdb.gpg AND CREATE==1 */ /* * Make sure the directory exists. This should be done before * acquiring the lock, which assumes the existence of the directory. */ p = strrchr (fname, DIRSEP_C); #if HAVE_W32_SYSTEM { /* Windows may either have a slash or a backslash. Take care of it. */ char *pp = strrchr (fname, '/'); if (!p || pp > p) p = pp; } #endif /*HAVE_W32_SYSTEM*/ log_assert (p); save_slash = *p; *p = 0; if (access (fname, F_OK)) { try_make_homedir (fname); if (access (fname, F_OK)) log_fatal (_("%s: directory does not exist!\n"), fname); } *p = save_slash; take_write_lock (); if (access (fname, R_OK) || stat (fname, &statbuf) || statbuf.st_size == 0) { FILE *fp; TRUSTREC rec; int rc; mode_t oldmask; #ifdef HAVE_W32CE_SYSTEM /* We know how the cegcc implementation of access works ;-). */ if (GetLastError () == ERROR_FILE_NOT_FOUND) gpg_err_set_errno (ENOENT); else gpg_err_set_errno (EIO); #endif /*HAVE_W32CE_SYSTEM*/ if (errno && errno != ENOENT) log_fatal ( _("can't access '%s': %s\n"), fname, strerror (errno)); oldmask = umask (077); if (is_secured_filename (fname)) { fp = NULL; gpg_err_set_errno (EPERM); } else fp = fopen (fname, "wb"); umask(oldmask); if (!fp) log_fatal (_("can't create '%s': %s\n"), fname, strerror (errno)); fclose (fp); db_fd = open (db_name, O_RDWR | MY_O_BINARY); if (db_fd == -1) log_fatal (_("can't open '%s': %s\n"), db_name, strerror (errno)); rc = create_version_record (ctrl); if (rc) log_fatal (_("%s: failed to create version record: %s"), fname, gpg_strerror (rc)); /* Read again to check that we are okay. */ if (tdbio_read_record (0, &rec, RECTYPE_VER)) log_fatal (_("%s: invalid trustdb created\n"), db_name); if (!opt.quiet) log_info (_("%s: trustdb created\n"), db_name); } release_write_lock (); return 0; } /* * Return the full name of the trustdb. */ const char * tdbio_get_dbname () { return db_name; } /* * Open the trustdb. This may only be called if it has not yet been * opened and after a successful call to tdbio_set_dbname. On return * the trustdb handle (DB_FD) is guaranteed to be open. */ static void open_db () { TRUSTREC rec; log_assert( db_fd == -1 ); #ifdef HAVE_W32CE_SYSTEM { DWORD prevrc = 0; wchar_t *wname = utf8_to_wchar (db_name); if (wname) { db_fd = (int)CreateFile (wname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); xfree (wname); } if (db_fd == -1) log_fatal ("can't open '%s': %d, %d\n", db_name, (int)prevrc, (int)GetLastError ()); } #else /*!HAVE_W32CE_SYSTEM*/ db_fd = open (db_name, O_RDWR | MY_O_BINARY ); if (db_fd == -1 && (errno == EACCES #ifdef EROFS || errno == EROFS #endif ) ) { /* Take care of read-only trustdbs. */ db_fd = open (db_name, O_RDONLY | MY_O_BINARY ); if (db_fd != -1 && !opt.quiet) log_info (_("Note: trustdb not writable\n")); } if ( db_fd == -1 ) log_fatal( _("can't open '%s': %s\n"), db_name, strerror(errno) ); #endif /*!HAVE_W32CE_SYSTEM*/ register_secured_file (db_name); /* Read the version record. */ if (tdbio_read_record (0, &rec, RECTYPE_VER ) ) log_fatal( _("%s: invalid trustdb\n"), db_name ); } /* * Append a new empty hashtable to the trustdb. TYPE gives the type * of the hash table. The only defined type is 0 for a trust hash. * On return the hashtable has been created, written, the version * record update, and the data flushed to the disk. On a fatal error * the function terminates the process. */ static void create_hashtable (ctrl_t ctrl, TRUSTREC *vr, int type) { TRUSTREC rec; off_t offset; ulong recnum; int i, n, rc; offset = lseek (db_fd, 0, SEEK_END); if (offset == -1) log_fatal ("trustdb: lseek to end failed: %s\n", strerror(errno)); recnum = offset / TRUST_RECORD_LEN; log_assert (recnum); /* This is will never be the first record. */ if (!type) vr->r.ver.trusthashtbl = recnum; /* Now write the records making up the hash table. */ n = (256+ITEMS_PER_HTBL_RECORD-1) / ITEMS_PER_HTBL_RECORD; for (i=0; i < n; i++, recnum++) { memset (&rec, 0, sizeof rec); rec.rectype = RECTYPE_HTBL; rec.recnum = recnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_fatal (_("%s: failed to create hashtable: %s\n"), db_name, gpg_strerror (rc)); } /* Update the version record and flush. */ rc = tdbio_write_record (ctrl, vr); if (!rc) rc = tdbio_sync (); if (rc) log_fatal (_("%s: error updating version record: %s\n"), db_name, gpg_strerror (rc)); } /* * Check whether open trustdb matches the global trust options given * for this process. On a read problem the process is terminated. * * Return: 1 for yes, 0 for no. */ int tdbio_db_matches_options() { static int yes_no = -1; if (yes_no == -1) { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER); if( rc ) log_fatal( _("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc) ); yes_no = vr.r.ver.marginals == opt.marginals_needed && vr.r.ver.completes == opt.completes_needed && vr.r.ver.cert_depth == opt.max_cert_depth && vr.r.ver.trust_model == opt.trust_model && vr.r.ver.min_cert_level == opt.min_cert_level; } return yes_no; } /* * Read and return the trust model identifier from the trustdb. On a * read problem the process is terminated. */ byte tdbio_read_model (void) { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER ); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc) ); return vr.r.ver.trust_model; } /* * Read and return the nextstamp value from the trustdb. On a read * problem the process is terminated. */ ulong tdbio_read_nextcheck () { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); return vr.r.ver.nextcheck; } /* * Write the STAMP nextstamp timestamp to the trustdb. On a read or * write problem the process is terminated. * * Return: True if the stamp actually changed. */ int tdbio_write_nextcheck (ctrl_t ctrl, ulong stamp) { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); if (vr.r.ver.nextcheck == stamp) return 0; vr.r.ver.nextcheck = stamp; rc = tdbio_write_record (ctrl, &vr); if (rc) log_fatal (_("%s: error writing version record: %s\n"), db_name, gpg_strerror (rc)); return 1; } /* * Return the record number of the trusthash table or create one if it * does not yet exist. On a read or write problem the process is * terminated. * * Return: record number */ static ulong -get_trusthashrec(void) +get_trusthashrec (ctrl_t ctrl) { static ulong trusthashtbl; /* Record number of the trust hashtable. */ + (void)ctrl; + if (!trusthashtbl) { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER ); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc) ); trusthashtbl = vr.r.ver.trusthashtbl; } return trusthashtbl; } /* * Update a hashtable in the trustdb. TABLE gives the start of the * table, KEY and KEYLEN are the key, NEWRECNUM is the record number * to insert into the table. * * Return: 0 on success or an error code. */ static int upd_hashtable (ctrl_t ctrl, ulong table, byte *key, int keylen, ulong newrecnum) { TRUSTREC lastrec, rec; ulong hashrec, item; int msb; int level = 0; int rc, i; hashrec = table; next_level: msb = key[level]; hashrec += msb / ITEMS_PER_HTBL_RECORD; rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL); if (rc) { log_error ("upd_hashtable: read failed: %s\n", gpg_strerror (rc)); return rc; } item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; if (!item) /* Insert a new item into the hash table. */ { rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = newrecnum; rc = tdbio_write_record (ctrl, &rec); if (rc) { log_error ("upd_hashtable: write htbl failed: %s\n", gpg_strerror (rc)); return rc; } } else if (item != newrecnum) /* Must do an update. */ { lastrec = rec; rc = tdbio_read_record (item, &rec, 0); if (rc) { log_error ("upd_hashtable: read item failed: %s\n", gpg_strerror (rc)); return rc; } if (rec.rectype == RECTYPE_HTBL) { hashrec = item; level++; if (level >= keylen) { log_error ("hashtable has invalid indirections.\n"); return GPG_ERR_TRUSTDB; } goto next_level; } else if (rec.rectype == RECTYPE_HLST) /* Extend the list. */ { /* Check whether the key is already in this list. */ for (;;) { for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (rec.r.hlst.rnum[i] == newrecnum) { return 0; /* Okay, already in the list. */ } } if (rec.r.hlst.next) { rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); if (rc) { log_error ("upd_hashtable: read hlst failed: %s\n", gpg_strerror (rc) ); return rc; } } else break; /* key is not in the list */ } /* Find the next free entry and put it in. */ for (;;) { for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (!rec.r.hlst.rnum[i]) { /* Empty slot found. */ rec.r.hlst.rnum[i] = newrecnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_error ("upd_hashtable: write hlst failed: %s\n", gpg_strerror (rc)); return rc; /* Done. */ } } if (rec.r.hlst.next) { /* read the next reord of the list. */ rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); if (rc) { log_error ("upd_hashtable: read hlst failed: %s\n", gpg_strerror (rc)); return rc; } } else { /* Append a new record to the list. */ rec.r.hlst.next = item = tdbio_new_recnum (ctrl); rc = tdbio_write_record (ctrl, &rec); if (rc) { log_error ("upd_hashtable: write hlst failed: %s\n", gpg_strerror (rc)); return rc; } memset (&rec, 0, sizeof rec); rec.rectype = RECTYPE_HLST; rec.recnum = item; rec.r.hlst.rnum[0] = newrecnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_error ("upd_hashtable: write ext hlst failed: %s\n", gpg_strerror (rc)); return rc; /* Done. */ } } /* end loop over list slots */ } else if (rec.rectype == RECTYPE_TRUST) /* Insert a list record. */ { if (rec.recnum == newrecnum) { return 0; } item = rec.recnum; /* Save number of key record. */ memset (&rec, 0, sizeof rec); rec.rectype = RECTYPE_HLST; rec.recnum = tdbio_new_recnum (ctrl); rec.r.hlst.rnum[0] = item; /* Old key record */ rec.r.hlst.rnum[1] = newrecnum; /* and new key record */ rc = tdbio_write_record (ctrl, &rec); if (rc) { log_error( "upd_hashtable: write new hlst failed: %s\n", gpg_strerror (rc) ); return rc; } /* Update the hashtable record. */ lastrec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = rec.recnum; rc = tdbio_write_record (ctrl, &lastrec); if (rc) log_error ("upd_hashtable: update htbl failed: %s\n", gpg_strerror (rc)); return rc; /* Ready. */ } else { log_error ("hashtbl %lu: %lu/%d points to an invalid record %lu\n", table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item); if (opt.verbose > 1) list_trustdb (ctrl, es_stderr, NULL); return GPG_ERR_TRUSTDB; } } return 0; } /* * Drop an entry from a hashtable. TABLE gives the start of the * table, KEY and KEYLEN are the key. * * Return: 0 on success or an error code. */ static int drop_from_hashtable (ctrl_t ctrl, ulong table, byte *key, int keylen, ulong recnum) { TRUSTREC rec; ulong hashrec, item; int msb; int level = 0; int rc, i; hashrec = table; next_level: msb = key[level]; hashrec += msb / ITEMS_PER_HTBL_RECORD; rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL ); if (rc) { log_error ("drop_from_hashtable: read failed: %s\n", gpg_strerror (rc)); return rc; } item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; if (!item) return 0; /* Not found - forget about it. */ if (item == recnum) /* Table points direct to the record. */ { rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = 0; rc = tdbio_write_record (ctrl, &rec); if (rc) log_error ("drop_from_hashtable: write htbl failed: %s\n", gpg_strerror (rc)); return rc; } rc = tdbio_read_record (item, &rec, 0); if (rc) { log_error ("drop_from_hashtable: read item failed: %s\n", gpg_strerror (rc)); return rc; } if (rec.rectype == RECTYPE_HTBL) { hashrec = item; level++; if (level >= keylen) { log_error ("hashtable has invalid indirections.\n"); return GPG_ERR_TRUSTDB; } goto next_level; } if (rec.rectype == RECTYPE_HLST) { for (;;) { for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (rec.r.hlst.rnum[i] == recnum) { rec.r.hlst.rnum[i] = 0; /* Mark as free. */ rc = tdbio_write_record (ctrl, &rec); if (rc) log_error("drop_from_hashtable: write htbl failed: %s\n", gpg_strerror (rc)); return rc; } } if (rec.r.hlst.next) { rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); if (rc) { log_error ("drop_from_hashtable: read hlst failed: %s\n", gpg_strerror (rc)); return rc; } } else return 0; /* Key not in table. */ } } log_error ("hashtbl %lu: %lu/%d points to wrong record %lu\n", table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item); return GPG_ERR_TRUSTDB; } /* * Lookup a record via the hashtable TABLE by (KEY,KEYLEN) and return * the result in REC. The return value of CMP() should be True if the * record is the desired one. * * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. */ static gpg_error_t lookup_hashtable (ulong table, const byte *key, size_t keylen, int (*cmpfnc)(const void*, const TRUSTREC *), const void *cmpdata, TRUSTREC *rec ) { int rc; ulong hashrec, item; int msb; int level = 0; if (!table) { rc = gpg_error (GPG_ERR_INV_RECORD); log_error("lookup_hashtable failed: %s\n", "request for record 0"); return rc; } hashrec = table; next_level: msb = key[level]; hashrec += msb / ITEMS_PER_HTBL_RECORD; rc = tdbio_read_record (hashrec, rec, RECTYPE_HTBL); if (rc) { log_error("lookup_hashtable failed: %s\n", gpg_strerror (rc) ); return rc; } item = rec->r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; if (!item) return gpg_error (GPG_ERR_NOT_FOUND); rc = tdbio_read_record (item, rec, 0); if (rc) { log_error( "hashtable read failed: %s\n", gpg_strerror (rc) ); return rc; } if (rec->rectype == RECTYPE_HTBL) { hashrec = item; level++; if (level >= keylen) { log_error ("hashtable has invalid indirections\n"); return GPG_ERR_TRUSTDB; } goto next_level; } else if (rec->rectype == RECTYPE_HLST) { for (;;) { int i; for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (rec->r.hlst.rnum[i]) { TRUSTREC tmp; rc = tdbio_read_record (rec->r.hlst.rnum[i], &tmp, 0); if (rc) { log_error ("lookup_hashtable: read item failed: %s\n", gpg_strerror (rc)); return rc; } if ((*cmpfnc)(cmpdata, &tmp)) { *rec = tmp; return 0; } } } if (rec->r.hlst.next) { rc = tdbio_read_record (rec->r.hlst.next, rec, RECTYPE_HLST); if (rc) { log_error ("lookup_hashtable: read hlst failed: %s\n", gpg_strerror (rc) ); return rc; } } else return gpg_error (GPG_ERR_NOT_FOUND); } } if ((*cmpfnc)(cmpdata, rec)) return 0; /* really found */ return gpg_error (GPG_ERR_NOT_FOUND); /* no: not found */ } /* * Update the trust hash table TR or create the table if it does not * exist. * * Return: 0 on success or an error code. */ static int update_trusthashtbl (ctrl_t ctrl, TRUSTREC *tr) { - return upd_hashtable (ctrl, get_trusthashrec (), + return upd_hashtable (ctrl, get_trusthashrec (ctrl), tr->r.trust.fingerprint, 20, tr->recnum); } /* * Dump the trustdb record REC to stream FP. */ void tdbio_dump_record (TRUSTREC *rec, estream_t fp) { int i; ulong rnum = rec->recnum; es_fprintf (fp, "rec %5lu, ", rnum); switch (rec->rectype) { case 0: es_fprintf (fp, "blank\n"); break; case RECTYPE_VER: es_fprintf (fp, "version, td=%lu, f=%lu, m/c/d=%d/%d/%d tm=%d mcl=%d nc=%lu (%s)\n", rec->r.ver.trusthashtbl, rec->r.ver.firstfree, rec->r.ver.marginals, rec->r.ver.completes, rec->r.ver.cert_depth, rec->r.ver.trust_model, rec->r.ver.min_cert_level, rec->r.ver.nextcheck, strtimestamp(rec->r.ver.nextcheck) ); break; case RECTYPE_FREE: es_fprintf (fp, "free, next=%lu\n", rec->r.free.next); break; case RECTYPE_HTBL: es_fprintf (fp, "htbl,"); for (i=0; i < ITEMS_PER_HTBL_RECORD; i++) es_fprintf (fp, " %lu", rec->r.htbl.item[i]); es_putc ('\n', fp); break; case RECTYPE_HLST: es_fprintf (fp, "hlst, next=%lu,", rec->r.hlst.next); for (i=0; i < ITEMS_PER_HLST_RECORD; i++) es_fprintf (fp, " %lu", rec->r.hlst.rnum[i]); es_putc ('\n', fp); break; case RECTYPE_TRUST: es_fprintf (fp, "trust "); for (i=0; i < 20; i++) es_fprintf (fp, "%02X", rec->r.trust.fingerprint[i]); es_fprintf (fp, ", ot=%d, d=%d, vl=%lu\n", rec->r.trust.ownertrust, rec->r.trust.depth, rec->r.trust.validlist); break; case RECTYPE_VALID: es_fprintf (fp, "valid "); for (i=0; i < 20; i++) es_fprintf(fp, "%02X", rec->r.valid.namehash[i]); es_fprintf (fp, ", v=%d, next=%lu\n", rec->r.valid.validity, rec->r.valid.next); break; default: es_fprintf (fp, "unknown type %d\n", rec->rectype ); break; } } /* * Read the record with number RECNUM into the structure REC. If * EXPECTED is not 0 reading any other record type will return an * error. * * Return: 0 on success or an error code. */ int tdbio_read_record (ulong recnum, TRUSTREC *rec, int expected) { byte readbuf[TRUST_RECORD_LEN]; const byte *buf, *p; gpg_error_t err = 0; int n, i; if (db_fd == -1) open_db (); buf = get_record_from_cache( recnum ); if (!buf) { if (lseek (db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1) { err = gpg_error_from_syserror (); log_error (_("trustdb: lseek failed: %s\n"), strerror (errno)); return err; } n = read (db_fd, readbuf, TRUST_RECORD_LEN); if (!n) { return gpg_error (GPG_ERR_EOF); } else if (n != TRUST_RECORD_LEN) { err = gpg_error_from_syserror (); log_error (_("trustdb: read failed (n=%d): %s\n"), n, strerror(errno)); return err; } buf = readbuf; } rec->recnum = recnum; rec->dirty = 0; p = buf; rec->rectype = *p++; if (expected && rec->rectype != expected) { log_error ("%lu: read expected rec type %d, got %d\n", recnum, expected, rec->rectype); return gpg_error (GPG_ERR_TRUSTDB); } p++; /* Skip reserved byte. */ switch (rec->rectype) { case 0: /* unused (free) record */ break; case RECTYPE_VER: /* version record */ if (memcmp(buf+1, GPGEXT_GPG, 3)) { log_error (_("%s: not a trustdb file\n"), db_name ); err = gpg_error (GPG_ERR_TRUSTDB); } else { p += 2; /* skip "gpg" */ rec->r.ver.version = *p++; rec->r.ver.marginals = *p++; rec->r.ver.completes = *p++; rec->r.ver.cert_depth = *p++; rec->r.ver.trust_model = *p++; rec->r.ver.min_cert_level = *p++; p += 2; rec->r.ver.created = buf32_to_ulong(p); p += 4; rec->r.ver.nextcheck = buf32_to_ulong(p); p += 4; p += 4; p += 4; rec->r.ver.firstfree = buf32_to_ulong(p); p += 4; p += 4; rec->r.ver.trusthashtbl = buf32_to_ulong(p); if (recnum) { log_error( _("%s: version record with recnum %lu\n"), db_name, (ulong)recnum ); err = gpg_error (GPG_ERR_TRUSTDB); } else if (rec->r.ver.version != 3) { log_error( _("%s: invalid file version %d\n"), db_name, rec->r.ver.version ); err = gpg_error (GPG_ERR_TRUSTDB); } } break; case RECTYPE_FREE: rec->r.free.next = buf32_to_ulong(p); break; case RECTYPE_HTBL: for (i=0; i < ITEMS_PER_HTBL_RECORD; i++) { rec->r.htbl.item[i] = buf32_to_ulong(p); p += 4; } break; case RECTYPE_HLST: rec->r.hlst.next = buf32_to_ulong(p); p += 4; for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { rec->r.hlst.rnum[i] = buf32_to_ulong(p); p += 4; } break; case RECTYPE_TRUST: memcpy (rec->r.trust.fingerprint, p, 20); p+=20; rec->r.trust.ownertrust = *p++; rec->r.trust.depth = *p++; rec->r.trust.min_ownertrust = *p++; p++; rec->r.trust.validlist = buf32_to_ulong(p); break; case RECTYPE_VALID: memcpy (rec->r.valid.namehash, p, 20); p+=20; rec->r.valid.validity = *p++; rec->r.valid.next = buf32_to_ulong(p); p += 4; rec->r.valid.full_count = *p++; rec->r.valid.marginal_count = *p++; break; default: log_error ("%s: invalid record type %d at recnum %lu\n", db_name, rec->rectype, (ulong)recnum); err = gpg_error (GPG_ERR_TRUSTDB); break; } return err; } /* * Write the record from the struct REC. * * Return: 0 on success or an error code. */ int tdbio_write_record (ctrl_t ctrl, TRUSTREC *rec) { byte buf[TRUST_RECORD_LEN]; byte *p; int rc = 0; int i; ulong recnum = rec->recnum; if (db_fd == -1) open_db (); memset (buf, 0, TRUST_RECORD_LEN); p = buf; *p++ = rec->rectype; p++; switch (rec->rectype) { case 0: /* unused record */ break; case RECTYPE_VER: /* version record */ if (recnum) BUG (); memcpy(p-1, GPGEXT_GPG, 3 ); p += 2; *p++ = rec->r.ver.version; *p++ = rec->r.ver.marginals; *p++ = rec->r.ver.completes; *p++ = rec->r.ver.cert_depth; *p++ = rec->r.ver.trust_model; *p++ = rec->r.ver.min_cert_level; p += 2; ulongtobuf(p, rec->r.ver.created); p += 4; ulongtobuf(p, rec->r.ver.nextcheck); p += 4; p += 4; p += 4; ulongtobuf(p, rec->r.ver.firstfree ); p += 4; p += 4; ulongtobuf(p, rec->r.ver.trusthashtbl ); p += 4; break; case RECTYPE_FREE: ulongtobuf(p, rec->r.free.next); p += 4; break; case RECTYPE_HTBL: for (i=0; i < ITEMS_PER_HTBL_RECORD; i++) { ulongtobuf( p, rec->r.htbl.item[i]); p += 4; } break; case RECTYPE_HLST: ulongtobuf( p, rec->r.hlst.next); p += 4; for (i=0; i < ITEMS_PER_HLST_RECORD; i++ ) { ulongtobuf( p, rec->r.hlst.rnum[i]); p += 4; } break; case RECTYPE_TRUST: memcpy (p, rec->r.trust.fingerprint, 20); p += 20; *p++ = rec->r.trust.ownertrust; *p++ = rec->r.trust.depth; *p++ = rec->r.trust.min_ownertrust; p++; ulongtobuf( p, rec->r.trust.validlist); p += 4; break; case RECTYPE_VALID: memcpy (p, rec->r.valid.namehash, 20); p += 20; *p++ = rec->r.valid.validity; ulongtobuf( p, rec->r.valid.next); p += 4; *p++ = rec->r.valid.full_count; *p++ = rec->r.valid.marginal_count; break; default: BUG(); } rc = put_record_into_cache (recnum, buf); if (rc) ; else if (rec->rectype == RECTYPE_TRUST) rc = update_trusthashtbl (ctrl, rec); return rc; } /* * Delete the record at record number RECNUm from the trustdb. * * Return: 0 on success or an error code. */ int tdbio_delete_record (ctrl_t ctrl, ulong recnum) { TRUSTREC vr, rec; int rc; /* Must read the record fist, so we can drop it from the hash tables */ rc = tdbio_read_record (recnum, &rec, 0); if (rc) ; else if (rec.rectype == RECTYPE_TRUST) { - rc = drop_from_hashtable (ctrl, get_trusthashrec(), + rc = drop_from_hashtable (ctrl, get_trusthashrec (ctrl), rec.r.trust.fingerprint, 20, rec.recnum); } if (rc) return rc; /* Now we can change it to a free record. */ rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); rec.recnum = recnum; rec.rectype = RECTYPE_FREE; rec.r.free.next = vr.r.ver.firstfree; vr.r.ver.firstfree = recnum; rc = tdbio_write_record (ctrl, &rec); if (!rc) rc = tdbio_write_record (ctrl, &vr); return rc; } /* * Create a new record and return its record number. */ ulong tdbio_new_recnum (ctrl_t ctrl) { off_t offset; ulong recnum; TRUSTREC vr, rec; int rc; /* Look for unused records. */ rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal( _("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); if (vr.r.ver.firstfree) { recnum = vr.r.ver.firstfree; rc = tdbio_read_record (recnum, &rec, RECTYPE_FREE); if (rc) log_fatal (_("%s: error reading free record: %s\n"), db_name, gpg_strerror (rc)); /* Update dir record. */ vr.r.ver.firstfree = rec.r.free.next; rc = tdbio_write_record (ctrl, &vr); if (rc) log_fatal (_("%s: error writing dir record: %s\n"), db_name, gpg_strerror (rc)); /* Zero out the new record. */ memset (&rec, 0, sizeof rec); rec.rectype = 0; /* Mark as unused record (actually already done my the memset). */ rec.recnum = recnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_fatal (_("%s: failed to zero a record: %s\n"), db_name, gpg_strerror (rc)); } else /* Not found - append a new record. */ { offset = lseek (db_fd, 0, SEEK_END); if (offset == (off_t)(-1)) log_fatal ("trustdb: lseek to end failed: %s\n", strerror (errno)); recnum = offset / TRUST_RECORD_LEN; log_assert (recnum); /* This will never be the first record */ /* We must write a record, so that the next call to this * function returns another recnum. */ memset (&rec, 0, sizeof rec); rec.rectype = 0; /* unused record */ rec.recnum = recnum; rc = 0; if (lseek( db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1) { rc = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: lseek failed: %s\n"), recnum, strerror (errno)); } else { int n; n = write (db_fd, &rec, TRUST_RECORD_LEN); if (n != TRUST_RECORD_LEN) { rc = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"), recnum, n, gpg_strerror (rc)); } } if (rc) log_fatal (_("%s: failed to append a record: %s\n"), db_name, gpg_strerror (rc)); } return recnum ; } /* Helper function for tdbio_search_trust_byfpr. */ static int cmp_trec_fpr ( const void *fpr, const TRUSTREC *rec ) { return (rec->rectype == RECTYPE_TRUST && !memcmp (rec->r.trust.fingerprint, fpr, 20)); } /* * Given a 20 byte FINGERPRINT search its trust record and return * that at REC. * * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. */ gpg_error_t -tdbio_search_trust_byfpr (const byte *fingerprint, TRUSTREC *rec) +tdbio_search_trust_byfpr (ctrl_t ctrl, const byte *fingerprint, TRUSTREC *rec) { int rc; /* Locate the trust record using the hash table */ - rc = lookup_hashtable (get_trusthashrec(), fingerprint, 20, + rc = lookup_hashtable (get_trusthashrec (ctrl), fingerprint, 20, cmp_trec_fpr, fingerprint, rec ); return rc; } /* * Given a primary public key object PK search its trust record and * return that at REC. * * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. */ gpg_error_t -tdbio_search_trust_bypk (PKT_public_key *pk, TRUSTREC *rec) +tdbio_search_trust_bypk (ctrl_t ctrl, PKT_public_key *pk, TRUSTREC *rec) { byte fingerprint[MAX_FINGERPRINT_LEN]; size_t fingerlen; fingerprint_from_pk( pk, fingerprint, &fingerlen ); for (; fingerlen < 20; fingerlen++) fingerprint[fingerlen] = 0; - return tdbio_search_trust_byfpr (fingerprint, rec); + return tdbio_search_trust_byfpr (ctrl, fingerprint, rec); } /* * Terminate the process with a message about a corrupted trustdb. */ void tdbio_invalid (void) { log_error (_("Error: The trustdb is corrupted.\n")); how_to_fix_the_trustdb (); g10_exit (2); } diff --git a/g10/tdbio.h b/g10/tdbio.h index beaa30876..267a37970 100644 --- a/g10/tdbio.h +++ b/g10/tdbio.h @@ -1,119 +1,121 @@ /* tdbio.h - Trust database I/O functions * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2012 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 3 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, see . */ #ifndef G10_TDBIO_H #define G10_TDBIO_H #include "../common/host2net.h" #define TRUST_RECORD_LEN 40 #define SIGS_PER_RECORD ((TRUST_RECORD_LEN-10)/5) #define ITEMS_PER_HTBL_RECORD ((TRUST_RECORD_LEN-2)/4) #define ITEMS_PER_HLST_RECORD ((TRUST_RECORD_LEN-6)/5) #define ITEMS_PER_PREF_RECORD (TRUST_RECORD_LEN-10) #if ITEMS_PER_PREF_RECORD % 2 #error ITEMS_PER_PREF_RECORD must be even #endif #define MAX_LIST_SIGS_DEPTH 20 #define RECTYPE_VER 1 #define RECTYPE_HTBL 10 #define RECTYPE_HLST 11 #define RECTYPE_TRUST 12 #define RECTYPE_VALID 13 #define RECTYPE_FREE 254 struct trust_record { int rectype; int mark; int dirty; /* for now only used internal by functions */ struct trust_record *next; /* help pointer to build lists in memory */ ulong recnum; union { struct { /* version record: */ byte version; /* should be 3 */ byte marginals; byte completes; byte cert_depth; byte trust_model; byte min_cert_level; ulong created; /* timestamp of trustdb creation */ ulong nextcheck; /* timestamp of next scheduled check */ ulong reserved; ulong reserved2; ulong firstfree; ulong reserved3; ulong trusthashtbl; } ver; struct { /* free record */ ulong next; } free; struct { ulong item[ITEMS_PER_HTBL_RECORD]; } htbl; struct { ulong next; ulong rnum[ITEMS_PER_HLST_RECORD]; /* of another record */ } hlst; struct { byte fingerprint[20]; byte ownertrust; byte depth; ulong validlist; byte min_ownertrust; } trust; struct { byte namehash[20]; ulong next; byte validity; byte full_count; byte marginal_count; } valid; } r; }; typedef struct trust_record TRUSTREC; /*-- tdbio.c --*/ int tdbio_update_version_record (ctrl_t ctrl); int tdbio_set_dbname (ctrl_t ctrl, const char *new_dbname, int create, int *r_nofile); const char *tdbio_get_dbname(void); void tdbio_dump_record( TRUSTREC *rec, estream_t fp ); int tdbio_read_record( ulong recnum, TRUSTREC *rec, int expected ); int tdbio_write_record (ctrl_t ctrl, TRUSTREC *rec); int tdbio_db_matches_options(void); byte tdbio_read_model(void); ulong tdbio_read_nextcheck (void); int tdbio_write_nextcheck (ctrl_t ctrl, ulong stamp); int tdbio_is_dirty(void); int tdbio_sync(void); int tdbio_begin_transaction(void); int tdbio_end_transaction(void); int tdbio_cancel_transaction(void); int tdbio_delete_record (ctrl_t ctrl, ulong recnum); ulong tdbio_new_recnum (ctrl_t ctrl); -gpg_error_t tdbio_search_trust_byfpr (const byte *fingerprint, TRUSTREC *rec); -gpg_error_t tdbio_search_trust_bypk (PKT_public_key *pk, TRUSTREC *rec); +gpg_error_t tdbio_search_trust_byfpr (ctrl_t ctrl, const byte *fingerprint, + TRUSTREC *rec); +gpg_error_t tdbio_search_trust_bypk (ctrl_t ctrl, PKT_public_key *pk, + TRUSTREC *rec); void tdbio_how_to_fix (void); void tdbio_invalid(void); #endif /*G10_TDBIO_H*/ diff --git a/g10/trustdb.c b/g10/trustdb.c index 0a98c129f..2c2d2394a 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -1,2257 +1,2257 @@ /* trustdb.c * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * 2008, 2012 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 3 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, see . */ #include #include #include #include #ifndef DISABLE_REGEX #include #include #endif /* !DISABLE_REGEX */ #include "gpg.h" #include "../common/status.h" #include "../common/iobuf.h" #include "keydb.h" #include "../common/util.h" #include "options.h" #include "packet.h" #include "main.h" #include "../common/mbox-util.h" #include "../common/i18n.h" #include "tdbio.h" #include "trustdb.h" #include "tofu.h" typedef struct key_item **KeyHashTable; /* see new_key_hash_table() */ /* * Structure to keep track of keys, this is used as an array wherre * the item right after the last one has a keyblock set to NULL. * Maybe we can drop this thing and replace it by key_item */ struct key_array { KBNODE keyblock; }; /* Control information for the trust DB. */ static struct { int init; int level; char *dbname; int no_trustdb; } trustdb_args; /* Some globals. */ static struct key_item *user_utk_list; /* temp. used to store --trusted-keys */ static struct key_item *utk_list; /* all ultimately trusted keys */ static int pending_check_trustdb; static int validate_keys (ctrl_t ctrl, int interactive); /********************************************** ************* some helpers ******************* **********************************************/ static struct key_item * new_key_item (void) { struct key_item *k; k = xmalloc_clear (sizeof *k); return k; } static void release_key_items (struct key_item *k) { struct key_item *k2; for (; k; k = k2) { k2 = k->next; xfree (k->trust_regexp); xfree (k); } } #define KEY_HASH_TABLE_SIZE 1024 /* * For fast keylook up we need a hash table. Each byte of a KeyID * should be distributed equally over the 256 possible values (except * for v3 keyIDs but we consider them as not important here). So we * can just use 10 bits to index a table of KEY_HASH_TABLE_SIZE key items. * Possible optimization: Do not use key_items but other hash_table when the * duplicates lists get too large. */ static KeyHashTable new_key_hash_table (void) { struct key_item **tbl; tbl = xmalloc_clear (KEY_HASH_TABLE_SIZE * sizeof *tbl); return tbl; } static void release_key_hash_table (KeyHashTable tbl) { int i; if (!tbl) return; for (i=0; i < KEY_HASH_TABLE_SIZE; i++) release_key_items (tbl[i]); xfree (tbl); } /* * Returns: True if the keyID is in the given hash table */ static int test_key_hash_table (KeyHashTable tbl, u32 *kid) { struct key_item *k; for (k = tbl[(kid[1] % KEY_HASH_TABLE_SIZE)]; k; k = k->next) if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) return 1; return 0; } /* * Add a new key to the hash table. The key is identified by its key ID. */ static void add_key_hash_table (KeyHashTable tbl, u32 *kid) { int i = kid[1] % KEY_HASH_TABLE_SIZE; struct key_item *k, *kk; for (k = tbl[i]; k; k = k->next) if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) return; /* already in table */ kk = new_key_item (); kk->kid[0] = kid[0]; kk->kid[1] = kid[1]; kk->next = tbl[i]; tbl[i] = kk; } /* * Release a key_array */ static void release_key_array ( struct key_array *keys ) { struct key_array *k; if (keys) { for (k=keys; k->keyblock; k++) release_kbnode (k->keyblock); xfree (keys); } } /********************************************* ********** Initialization ***************** *********************************************/ /* * Used to register extra ultimately trusted keys - this has to be done * before initializing the validation module. * FIXME: Should be replaced by a function to add those keys to the trustdb. */ void tdb_register_trusted_keyid (u32 *keyid) { struct key_item *k; k = new_key_item (); k->kid[0] = keyid[0]; k->kid[1] = keyid[1]; k->next = user_utk_list; user_utk_list = k; } void tdb_register_trusted_key( const char *string ) { gpg_error_t err; KEYDB_SEARCH_DESC desc; err = classify_user_id (string, &desc, 1); if (err || desc.mode != KEYDB_SEARCH_MODE_LONG_KID ) { log_error(_("'%s' is not a valid long keyID\n"), string ); return; } register_trusted_keyid(desc.u.kid); } /* * Helper to add a key to the global list of ultimately trusted keys. * Returns: true = inserted, false = already in list. */ static int add_utk (u32 *kid) { struct key_item *k; if (tdb_keyid_is_utk (kid)) return 0; k = new_key_item (); k->kid[0] = kid[0]; k->kid[1] = kid[1]; k->ownertrust = TRUST_ULTIMATE; k->next = utk_list; utk_list = k; if( opt.verbose > 1 ) log_info(_("key %s: accepted as trusted key\n"), keystr(kid)); return 1; } /**************** * Verify that all our secret keys are usable and put them into the utk_list. */ static void verify_own_keys (ctrl_t ctrl) { TRUSTREC rec; ulong recnum; int rc; struct key_item *k; if (utk_list) return; /* scan the trustdb to find all ultimately trusted keys */ for (recnum=1; !tdbio_read_record (recnum, &rec, 0); recnum++ ) { if ( rec.rectype == RECTYPE_TRUST && (rec.r.trust.ownertrust & TRUST_MASK) == TRUST_ULTIMATE) { byte *fpr = rec.r.trust.fingerprint; int fprlen; u32 kid[2]; /* Problem: We do only use fingerprints in the trustdb but * we need the keyID here to indetify the key; we can only * use that ugly hack to distinguish between 16 and 20 * butes fpr - it does not work always so we better change * the whole validation code to only work with * fingerprints */ fprlen = (!fpr[16] && !fpr[17] && !fpr[18] && !fpr[19])? 16:20; keyid_from_fingerprint (ctrl, fpr, fprlen, kid); if (!add_utk (kid)) log_info(_("key %s occurs more than once in the trustdb\n"), keystr(kid)); } } /* Put any --trusted-key keys into the trustdb */ for (k = user_utk_list; k; k = k->next) { if ( add_utk (k->kid) ) { /* not yet in trustDB as ultimately trusted */ PKT_public_key pk; memset (&pk, 0, sizeof pk); rc = get_pubkey (ctrl, &pk, k->kid); if (rc) log_info(_("key %s: no public key for trusted key - skipped\n"), keystr(k->kid)); else { tdb_update_ownertrust (ctrl, &pk, ((tdb_get_ownertrust (ctrl, &pk, 0) & ~TRUST_MASK) | TRUST_ULTIMATE )); release_public_key_parts (&pk); } log_info (_("key %s marked as ultimately trusted\n"),keystr(k->kid)); } } /* release the helper table table */ release_key_items (user_utk_list); user_utk_list = NULL; return; } /* Returns whether KID is on the list of ultimately trusted keys. */ int tdb_keyid_is_utk (u32 *kid) { struct key_item *k; for (k = utk_list; k; k = k->next) if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) return 1; return 0; } /* Return the list of ultimately trusted keys. */ struct key_item * tdb_utks (void) { return utk_list; } /********************************************* *********** TrustDB stuff ******************* *********************************************/ /* * Read a record but die if it does not exist */ static void read_record (ulong recno, TRUSTREC *rec, int rectype ) { int rc = tdbio_read_record (recno, rec, rectype); if (rc) { log_error(_("trust record %lu, req type %d: read failed: %s\n"), recno, rec->rectype, gpg_strerror (rc) ); tdbio_invalid(); } if (rectype != rec->rectype) { log_error(_("trust record %lu is not of requested type %d\n"), rec->recnum, rectype); tdbio_invalid(); } } /* * Write a record and die on error */ static void write_record (ctrl_t ctrl, TRUSTREC *rec) { int rc = tdbio_write_record (ctrl, rec); if (rc) { log_error(_("trust record %lu, type %d: write failed: %s\n"), rec->recnum, rec->rectype, gpg_strerror (rc) ); tdbio_invalid(); } } /* * sync the TrustDb and die on error */ static void do_sync(void) { int rc = tdbio_sync (); if(rc) { log_error (_("trustdb: sync failed: %s\n"), gpg_strerror (rc) ); g10_exit(2); } } const char * trust_model_string (int model) { switch (model) { case TM_CLASSIC: return "classic"; case TM_PGP: return "pgp"; case TM_EXTERNAL: return "external"; case TM_TOFU: return "tofu"; case TM_TOFU_PGP: return "tofu+pgp"; case TM_ALWAYS: return "always"; case TM_DIRECT: return "direct"; default: return "unknown"; } } /**************** * Perform some checks over the trustdb * level 0: only open the db * 1: used for initial program startup */ int setup_trustdb( int level, const char *dbname ) { /* just store the args */ if( trustdb_args.init ) return 0; trustdb_args.level = level; trustdb_args.dbname = dbname? xstrdup(dbname): NULL; return 0; } void how_to_fix_the_trustdb () { const char *name = trustdb_args.dbname; if (!name) name = "trustdb.gpg"; log_info (_("You may try to re-create the trustdb using the commands:\n")); log_info (" cd %s\n", default_homedir ()); log_info (" %s --export-ownertrust > otrust.tmp\n", GPG_NAME); #ifdef HAVE_W32_SYSTEM log_info (" del %s\n", name); #else log_info (" rm %s\n", name); #endif log_info (" %s --import-ownertrust < otrust.tmp\n", GPG_NAME); log_info (_("If that does not work, please consult the manual\n")); } /* Initialize the trustdb. With NO_CREATE set a missing trustdb is * not an error and the function won't terminate the process on error; * in that case 0 is returned if there is a trustdb or an error code * if no trustdb is available. */ gpg_error_t init_trustdb (ctrl_t ctrl, int no_create) { int level = trustdb_args.level; const char* dbname = trustdb_args.dbname; if( trustdb_args.init ) return 0; trustdb_args.init = 1; if(level==0 || level==1) { int rc = tdbio_set_dbname (ctrl, dbname, (!no_create && level), &trustdb_args.no_trustdb); if (no_create && trustdb_args.no_trustdb) { /* No trustdb found and the caller asked us not to create * it. Return an error and set the initialization state * back so that we always test for an existing trustdb. */ trustdb_args.init = 0; return gpg_error (GPG_ERR_ENOENT); } if (rc) log_fatal("can't init trustdb: %s\n", gpg_strerror (rc) ); } else BUG(); if(opt.trust_model==TM_AUTO) { /* Try and set the trust model off of whatever the trustdb says it is. */ opt.trust_model=tdbio_read_model(); /* Sanity check this ;) */ if(opt.trust_model != TM_CLASSIC && opt.trust_model != TM_PGP && opt.trust_model != TM_TOFU_PGP && opt.trust_model != TM_TOFU && opt.trust_model != TM_EXTERNAL) { log_info(_("unable to use unknown trust model (%d) - " "assuming %s trust model\n"),opt.trust_model,"pgp"); opt.trust_model = TM_PGP; } if(opt.verbose) log_info(_("using %s trust model\n"), trust_model_string (opt.trust_model)); } if (opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC || opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP) { /* Verify the list of ultimately trusted keys and move the --trusted-keys list there as well. */ if(level==1) verify_own_keys (ctrl); if(!tdbio_db_matches_options()) pending_check_trustdb=1; } return 0; } /* Check whether we have a trust database, initializing it if necessary if the trust model is not 'always trust'. Returns true if we do have a usable trust database. */ int have_trustdb (ctrl_t ctrl) { return !init_trustdb (ctrl, opt.trust_model == TM_ALWAYS); } /**************** * Recreate the WoT but do not ask for new ownertrusts. Special * feature: In batch mode and without a forced yes, this is only done * when a check is due. This can be used to run the check from a crontab */ void check_trustdb (ctrl_t ctrl) { init_trustdb (ctrl, 0); if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC || opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU) { if (opt.batch && !opt.answer_yes) { ulong scheduled; scheduled = tdbio_read_nextcheck (); if (!scheduled) { log_info (_("no need for a trustdb check\n")); return; } if (scheduled > make_timestamp ()) { log_info (_("next trustdb check due at %s\n"), strtimestamp (scheduled)); return; } } validate_keys (ctrl, 0); } else log_info (_("no need for a trustdb check with '%s' trust model\n"), trust_model_string(opt.trust_model)); } /* * Recreate the WoT. */ void update_trustdb (ctrl_t ctrl) { init_trustdb (ctrl, 0); if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC || opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU) validate_keys (ctrl, 1); else log_info (_("no need for a trustdb update with '%s' trust model\n"), trust_model_string(opt.trust_model)); } void tdb_revalidation_mark (ctrl_t ctrl) { init_trustdb (ctrl, 0); if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return; /* We simply set the time for the next check to 1 (far back in 1970) so that a --update-trustdb will be scheduled. */ if (tdbio_write_nextcheck (ctrl, 1)) do_sync (); pending_check_trustdb = 1; } int trustdb_pending_check(void) { return pending_check_trustdb; } /* If the trustdb is dirty, and we're interactive, update it. Otherwise, check it unless no-auto-check-trustdb is set. */ void tdb_check_or_update (ctrl_t ctrl) { if (trustdb_pending_check ()) { if (opt.interactive) update_trustdb (ctrl); else if (!opt.no_auto_check_trustdb) check_trustdb (ctrl); } } void read_trust_options (ctrl_t ctrl, byte *trust_model, ulong *created, ulong *nextcheck, byte *marginals, byte *completes, byte *cert_depth, byte *min_cert_level) { TRUSTREC opts; init_trustdb (ctrl, 0); if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) memset (&opts, 0, sizeof opts); else read_record (0, &opts, RECTYPE_VER); if(trust_model) *trust_model=opts.r.ver.trust_model; if(created) *created=opts.r.ver.created; if(nextcheck) *nextcheck=opts.r.ver.nextcheck; if(marginals) *marginals=opts.r.ver.marginals; if(completes) *completes=opts.r.ver.completes; if(cert_depth) *cert_depth=opts.r.ver.cert_depth; if(min_cert_level) *min_cert_level=opts.r.ver.min_cert_level; } /*********************************************** *********** Ownertrust et al. **************** ***********************************************/ static int read_trust_record (ctrl_t ctrl, PKT_public_key *pk, TRUSTREC *rec) { int rc; init_trustdb (ctrl, 0); - rc = tdbio_search_trust_bypk (pk, rec); + rc = tdbio_search_trust_bypk (ctrl, pk, rec); if (rc) { if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND) log_error ("trustdb: searching trust record failed: %s\n", gpg_strerror (rc)); return rc; } if (rec->rectype != RECTYPE_TRUST) { log_error ("trustdb: record %lu is not a trust record\n", rec->recnum); return GPG_ERR_TRUSTDB; } return 0; } /* * Return the assigned ownertrust value for the given public key. The * key should be the primary key. If NO_CREATE is set a missing * trustdb will not be created. This comes for example handy when we * want to print status lines (DECRYPTION_KEY) which carry ownertrust * values but we usually use --always-trust. */ unsigned int tdb_get_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int no_create) { TRUSTREC rec; gpg_error_t err; if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return TRUST_UNKNOWN; /* If the caller asked not to create a trustdb we call init_trustdb * directly and allow it to fail with an error code for a * non-existing trustdb. */ if (no_create && init_trustdb (ctrl, 1)) return TRUST_UNKNOWN; err = read_trust_record (ctrl, pk, &rec); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) return TRUST_UNKNOWN; /* no record yet */ if (err) { tdbio_invalid (); return TRUST_UNKNOWN; /* actually never reached */ } return rec.r.trust.ownertrust; } unsigned int tdb_get_min_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int no_create) { TRUSTREC rec; gpg_error_t err; if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return TRUST_UNKNOWN; /* If the caller asked not to create a trustdb we call init_trustdb * directly and allow it to fail with an error code for a * non-existing trustdb. */ if (no_create && init_trustdb (ctrl, 1)) return TRUST_UNKNOWN; err = read_trust_record (ctrl, pk, &rec); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) return TRUST_UNKNOWN; /* no record yet */ if (err) { tdbio_invalid (); return TRUST_UNKNOWN; /* actually never reached */ } return rec.r.trust.min_ownertrust; } /* * Set the trust value of the given public key to the new value. * The key should be a primary one. */ void tdb_update_ownertrust (ctrl_t ctrl, PKT_public_key *pk, unsigned int new_trust ) { TRUSTREC rec; gpg_error_t err; if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return; err = read_trust_record (ctrl, pk, &rec); if (!err) { if (DBG_TRUST) log_debug ("update ownertrust from %u to %u\n", (unsigned int)rec.r.trust.ownertrust, new_trust ); if (rec.r.trust.ownertrust != new_trust) { rec.r.trust.ownertrust = new_trust; write_record (ctrl, &rec); tdb_revalidation_mark (ctrl); do_sync (); } } else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* no record yet - create a new one */ size_t dummy; if (DBG_TRUST) log_debug ("insert ownertrust %u\n", new_trust ); memset (&rec, 0, sizeof rec); rec.recnum = tdbio_new_recnum (ctrl); rec.rectype = RECTYPE_TRUST; fingerprint_from_pk (pk, rec.r.trust.fingerprint, &dummy); rec.r.trust.ownertrust = new_trust; write_record (ctrl, &rec); tdb_revalidation_mark (ctrl); do_sync (); } else { tdbio_invalid (); } } static void update_min_ownertrust (ctrl_t ctrl, u32 *kid, unsigned int new_trust) { PKT_public_key *pk; TRUSTREC rec; gpg_error_t err; if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return; pk = xmalloc_clear (sizeof *pk); err = get_pubkey (ctrl, pk, kid); if (err) { log_error (_("public key %s not found: %s\n"), keystr (kid), gpg_strerror (err)); xfree (pk); return; } err = read_trust_record (ctrl, pk, &rec); if (!err) { if (DBG_TRUST) log_debug ("key %08lX%08lX: update min_ownertrust from %u to %u\n", (ulong)kid[0],(ulong)kid[1], (unsigned int)rec.r.trust.min_ownertrust, new_trust ); if (rec.r.trust.min_ownertrust != new_trust) { rec.r.trust.min_ownertrust = new_trust; write_record (ctrl, &rec); tdb_revalidation_mark (ctrl); do_sync (); } } else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* no record yet - create a new one */ size_t dummy; if (DBG_TRUST) log_debug ("insert min_ownertrust %u\n", new_trust ); memset (&rec, 0, sizeof rec); rec.recnum = tdbio_new_recnum (ctrl); rec.rectype = RECTYPE_TRUST; fingerprint_from_pk (pk, rec.r.trust.fingerprint, &dummy); rec.r.trust.min_ownertrust = new_trust; write_record (ctrl, &rec); tdb_revalidation_mark (ctrl); do_sync (); } else { tdbio_invalid (); } free_public_key (pk); } /* * Clear the ownertrust and min_ownertrust values. * * Return: True if a change actually happened. */ int tdb_clear_ownertrusts (ctrl_t ctrl, PKT_public_key *pk) { TRUSTREC rec; gpg_error_t err; init_trustdb (ctrl, 0); if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return 0; err = read_trust_record (ctrl, pk, &rec); if (!err) { if (DBG_TRUST) { log_debug ("clearing ownertrust (old value %u)\n", (unsigned int)rec.r.trust.ownertrust); log_debug ("clearing min_ownertrust (old value %u)\n", (unsigned int)rec.r.trust.min_ownertrust); } if (rec.r.trust.ownertrust || rec.r.trust.min_ownertrust) { rec.r.trust.ownertrust = 0; rec.r.trust.min_ownertrust = 0; write_record (ctrl, &rec); tdb_revalidation_mark (ctrl); do_sync (); return 1; } } else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) { tdbio_invalid (); } return 0; } /* * Note: Caller has to do a sync */ static void update_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid, int depth, int validity) { TRUSTREC trec, vrec; gpg_error_t err; ulong recno; namehash_from_uid(uid); err = read_trust_record (ctrl, pk, &trec); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) { tdbio_invalid (); return; } if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* No record yet - create a new one. */ size_t dummy; memset (&trec, 0, sizeof trec); trec.recnum = tdbio_new_recnum (ctrl); trec.rectype = RECTYPE_TRUST; fingerprint_from_pk (pk, trec.r.trust.fingerprint, &dummy); trec.r.trust.ownertrust = 0; } /* locate an existing one */ recno = trec.r.trust.validlist; while (recno) { read_record (recno, &vrec, RECTYPE_VALID); if ( !memcmp (vrec.r.valid.namehash, uid->namehash, 20) ) break; recno = vrec.r.valid.next; } if (!recno) /* insert a new validity record */ { memset (&vrec, 0, sizeof vrec); vrec.recnum = tdbio_new_recnum (ctrl); vrec.rectype = RECTYPE_VALID; memcpy (vrec.r.valid.namehash, uid->namehash, 20); vrec.r.valid.next = trec.r.trust.validlist; trec.r.trust.validlist = vrec.recnum; } vrec.r.valid.validity = validity; vrec.r.valid.full_count = uid->help_full_count; vrec.r.valid.marginal_count = uid->help_marginal_count; write_record (ctrl, &vrec); trec.r.trust.depth = depth; write_record (ctrl, &trec); } /*********************************************** ********* Query trustdb values ************** ***********************************************/ /* Return true if key is disabled. Note that this is usually used via the pk_is_disabled macro. */ int tdb_cache_disabled_value (ctrl_t ctrl, PKT_public_key *pk) { gpg_error_t err; TRUSTREC trec; int disabled = 0; if (pk->flags.disabled_valid) return pk->flags.disabled; init_trustdb (ctrl, 0); if (trustdb_args.no_trustdb) return 0; /* No trustdb => not disabled. */ err = read_trust_record (ctrl, pk, &trec); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) { tdbio_invalid (); goto leave; } if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* No record found, so assume not disabled. */ goto leave; } if ((trec.r.trust.ownertrust & TRUST_FLAG_DISABLED)) disabled = 1; /* Cache it for later so we don't need to look at the trustdb every time */ pk->flags.disabled = disabled; pk->flags.disabled_valid = 1; leave: return disabled; } void tdb_check_trustdb_stale (ctrl_t ctrl) { static int did_nextcheck=0; init_trustdb (ctrl, 0); if (trustdb_args.no_trustdb) return; /* No trustdb => can't be stale. */ if (!did_nextcheck && (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC || opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU)) { ulong scheduled; did_nextcheck = 1; scheduled = tdbio_read_nextcheck (); if ((scheduled && scheduled <= make_timestamp ()) || pending_check_trustdb) { if (opt.no_auto_check_trustdb) { pending_check_trustdb = 1; if (!opt.quiet) log_info (_("please do a --check-trustdb\n")); } else { if (!opt.quiet) log_info (_("checking the trustdb\n")); validate_keys (ctrl, 0); } } } } /* * Return the validity information for KB/PK (at least one of them * must be non-NULL). This is the core of get_validity. If SIG is * not NULL, then the trust is being evaluated in the context of the * provided signature. This is used by the TOFU code to record * statistics. */ unsigned int tdb_get_validity_core (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid, PKT_public_key *main_pk, PKT_signature *sig, int may_ask) { TRUSTREC trec, vrec; gpg_error_t err = 0; ulong recno; #ifdef USE_TOFU unsigned int tofu_validity = TRUST_UNKNOWN; int free_kb = 0; #endif unsigned int validity = TRUST_UNKNOWN; if (kb && pk) log_assert (keyid_cmp (pk_main_keyid (pk), pk_main_keyid (kb->pkt->pkt.public_key)) == 0); if (! pk) { log_assert (kb); pk = kb->pkt->pkt.public_key; } #ifndef USE_TOFU (void)sig; (void)may_ask; #endif init_trustdb (ctrl, 0); /* If we have no trustdb (which also means it has not been created) and the trust-model is always, we don't know the validity - return immediately. If we won't do that the tdbio code would try to open the trustdb and run into a fatal error. */ if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return TRUST_UNKNOWN; check_trustdb_stale (ctrl); if(opt.trust_model==TM_DIRECT) { /* Note that this happens BEFORE any user ID stuff is checked. The direct trust model applies to keys as a whole. */ validity = tdb_get_ownertrust (ctrl, main_pk, 0); goto leave; } #ifdef USE_TOFU if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP) { kbnode_t n = NULL; strlist_t user_id_list = NULL; int done = 0; /* If the caller didn't supply a user id then use all uids. */ if (! uid) { if (! kb) { kb = get_pubkeyblock (ctrl, main_pk->keyid); free_kb = 1; } n = kb; } if (DBG_TRUST && sig && sig->signers_uid) log_debug ("TOFU: only considering user id: '%s'\n", sig->signers_uid); while (!done && (uid || (n = find_next_kbnode (n, PKT_USER_ID)))) { PKT_user_id *user_id; int expired = 0; if (uid) { user_id = uid; /* If the caller specified a user id, then we only process the specified user id and are done after the first iteration. */ done = 1; } else user_id = n->pkt->pkt.user_id; if (user_id->attrib_data) /* Skip user attributes. */ continue; if (sig && sig->signers_uid) /* Make sure the UID matches. */ { char *email = mailbox_from_userid (user_id->name); if (!email || !*email || strcmp (sig->signers_uid, email) != 0) { if (DBG_TRUST) log_debug ("TOFU: skipping user id '%s', which does" " not match the signer's email ('%s')\n", email, sig->signers_uid); xfree (email); continue; } xfree (email); } /* If the user id is revoked or expired, then skip it. */ if (user_id->flags.revoked || user_id->flags.expired) { if (DBG_TRUST) { char *s; if (user_id->flags.revoked && user_id->flags.expired) s = "revoked and expired"; else if (user_id->flags.revoked) s = "revoked"; else s = "expire"; log_debug ("TOFU: Ignoring %s user id (%s)\n", s, user_id->name); } if (user_id->flags.revoked) continue; expired = 1; } add_to_strlist (&user_id_list, user_id->name); user_id_list->flags = expired; } /* Process the user ids in the order they appear in the key block. */ strlist_rev (&user_id_list); /* It only makes sense to observe any signature before getting the validity. This is because if the current signature results in a conflict, then we damn well want to take that into account. */ if (sig) { err = tofu_register_signature (ctrl, main_pk, user_id_list, sig->digest, sig->digest_len, sig->timestamp, "unknown"); if (err) { log_error ("TOFU: error registering signature: %s\n", gpg_strerror (err)); tofu_validity = TRUST_UNKNOWN; } } if (! err) tofu_validity = tofu_get_validity (ctrl, main_pk, user_id_list, may_ask); free_strlist (user_id_list); if (free_kb) release_kbnode (kb); } #endif /*USE_TOFU*/ if (opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_CLASSIC || opt.trust_model == TM_PGP) { err = read_trust_record (ctrl, main_pk, &trec); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) { tdbio_invalid (); return 0; } if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* No record found. */ validity = TRUST_UNKNOWN; goto leave; } /* Loop over all user IDs */ recno = trec.r.trust.validlist; validity = 0; while (recno) { read_record (recno, &vrec, RECTYPE_VALID); if(uid) { /* If a user ID is given we return the validity for that user ID ONLY. If the namehash is not found, then there is no validity at all (i.e. the user ID wasn't signed). */ if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0) { validity=(vrec.r.valid.validity & TRUST_MASK); break; } } else { /* If no user ID is given, we take the maximum validity over all user IDs */ if (validity < (vrec.r.valid.validity & TRUST_MASK)) validity = (vrec.r.valid.validity & TRUST_MASK); } recno = vrec.r.valid.next; } if ((trec.r.trust.ownertrust & TRUST_FLAG_DISABLED)) { validity |= TRUST_FLAG_DISABLED; pk->flags.disabled = 1; } else pk->flags.disabled = 0; pk->flags.disabled_valid = 1; } leave: #ifdef USE_TOFU validity = tofu_wot_trust_combine (tofu_validity, validity); #else /*!USE_TOFU*/ validity &= TRUST_MASK; if (validity == TRUST_NEVER) /* TRUST_NEVER trumps everything else. */ validity |= TRUST_NEVER; if (validity == TRUST_EXPIRED) /* TRUST_EXPIRED trumps everything but TRUST_NEVER. */ validity |= TRUST_EXPIRED; #endif /*!USE_TOFU*/ if (opt.trust_model != TM_TOFU && pending_check_trustdb) validity |= TRUST_FLAG_PENDING_CHECK; return validity; } static void get_validity_counts (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid) { TRUSTREC trec, vrec; ulong recno; if(pk==NULL || uid==NULL) BUG(); namehash_from_uid(uid); uid->help_marginal_count=uid->help_full_count=0; init_trustdb (ctrl, 0); if(read_trust_record (ctrl, pk, &trec)) return; /* loop over all user IDs */ recno = trec.r.trust.validlist; while (recno) { read_record (recno, &vrec, RECTYPE_VALID); if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0) { uid->help_marginal_count=vrec.r.valid.marginal_count; uid->help_full_count=vrec.r.valid.full_count; /* es_printf("Fetched marginal %d, full %d\n",uid->help_marginal_count,uid->help_full_count); */ break; } recno = vrec.r.valid.next; } } void list_trust_path( const char *username ) { (void)username; } /**************** * Enumerate all keys, which are needed to build all trust paths for * the given key. This function does not return the key itself or * the ultimate key (the last point in cerificate chain). Only * certificate chains which ends up at an ultimately trusted key * are listed. If ownertrust or validity is not NULL, the corresponding * value for the returned LID is also returned in these variable(s). * * 1) create a void pointer and initialize it to NULL * 2) pass this void pointer by reference to this function. * Set lid to the key you want to enumerate and pass it by reference. * 3) call this function as long as it does not return -1 * to indicate EOF. LID does contain the next key used to build the web * 4) Always call this function a last time with LID set to NULL, * so that it can free its context. * * Returns: -1 on EOF or the level of the returned LID */ int enum_cert_paths( void **context, ulong *lid, unsigned *ownertrust, unsigned *validity ) { (void)context; (void)lid; (void)ownertrust; (void)validity; return -1; } /**************** * Print the current path */ void enum_cert_paths_print (void **context, FILE *fp, int refresh, ulong selected_lid) { (void)context; (void)fp; (void)refresh; (void)selected_lid; } /**************************************** *********** NEW NEW NEW **************** ****************************************/ static int ask_ownertrust (ctrl_t ctrl, u32 *kid, int minimum) { PKT_public_key *pk; int rc; int ot; pk = xmalloc_clear (sizeof *pk); rc = get_pubkey (ctrl, pk, kid); if (rc) { log_error (_("public key %s not found: %s\n"), keystr(kid), gpg_strerror (rc) ); return TRUST_UNKNOWN; } if(opt.force_ownertrust) { log_info("force trust for key %s to %s\n", keystr(kid),trust_value_to_string(opt.force_ownertrust)); tdb_update_ownertrust (ctrl, pk, opt.force_ownertrust); ot=opt.force_ownertrust; } else { ot=edit_ownertrust (ctrl, pk, 0); if(ot>0) ot = tdb_get_ownertrust (ctrl, pk, 0); else if(ot==0) ot = minimum?minimum:TRUST_UNDEFINED; else ot = -1; /* quit */ } free_public_key( pk ); return ot; } 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 void dump_key_array (int depth, struct key_array *keys) { struct key_array *kar; for (kar=keys; kar->keyblock; kar++) { KBNODE node = kar->keyblock; u32 kid[2]; keyid_from_pk(node->pkt->pkt.public_key, kid); es_printf ("%d:%08lX%08lX:K::%c::::\n", depth, (ulong)kid[0], (ulong)kid[1], '?'); for (; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { int len = node->pkt->pkt.user_id->len; if (len > 30) len = 30; es_printf ("%d:%08lX%08lX:U:::%c:::", depth, (ulong)kid[0], (ulong)kid[1], (node->flag & 4)? 'f': (node->flag & 2)? 'm': (node->flag & 1)? 'q':'-'); es_write_sanitized (es_stdout, node->pkt->pkt.user_id->name, len, ":", NULL); es_putc (':', es_stdout); es_putc ('\n', es_stdout); } } } } static void store_validation_status (ctrl_t ctrl, int depth, kbnode_t keyblock, KeyHashTable stored) { KBNODE node; int status; int any = 0; for (node=keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; if (node->flag & 4) status = TRUST_FULLY; else if (node->flag & 2) status = TRUST_MARGINAL; else if (node->flag & 1) status = TRUST_UNDEFINED; else status = 0; if (status) { update_validity (ctrl, keyblock->pkt->pkt.public_key, uid, depth, status); mark_keyblock_seen(stored,keyblock); any = 1; } } } if (any) do_sync (); } /* Returns a sanitized copy of the regexp (which might be "", but not NULL). */ #ifndef DISABLE_REGEX /* Operator charactors except '.' and backslash. See regex(7) on BSD. */ #define REGEXP_OPERATOR_CHARS "^[$()|*+?{" static char * sanitize_regexp(const char *old) { size_t start=0,len=strlen(old),idx=0; int escaped=0,standard_bracket=0; char *new=xmalloc((len*2)+1); /* enough to \-escape everything if we have to */ /* There are basically two commonly-used regexps here. GPG and most versions of PGP use "<[^>]+[@.]example\.com>$" and PGP (9) command line uses "example.com" (i.e. whatever the user specifies, and we can't expect users know to use "\." instead of "."). So here are the rules: we're allowed to start with "<[^>]+[@.]" and end with ">$" or start and end with nothing. In between, the only legal regex character is ".", and everything else gets escaped. Part of the gotcha here is that some regex packages allow more than RFC-4880 requires. For example, 4880 has no "{}" operator, but GNU regex does. Commenting removes these operators from consideration. A possible future enhancement is to use commenting to effectively back off a given regex to the Henry Spencer syntax in 4880. -dshaw */ /* Are we bracketed between "<[^>]+[@.]" and ">$" ? */ if(len>=12 && strncmp(old,"<[^>]+[@.]",10)==0 && old[len-2]=='>' && old[len-1]=='$') { strcpy(new,"<[^>]+[@.]"); idx=strlen(new); standard_bracket=1; start+=10; len-=2; } /* Walk the remaining characters and ensure that everything that is left is not an operational regex character. */ for(;start$", then it was escaping the ">" and is fine. If the regexp actually ended with the bare "\", then it's an illegal regexp and regcomp should kick it out. */ if(standard_bracket) strcat(new,">$"); return new; } #endif /*!DISABLE_REGEX*/ /* Used by validate_one_keyblock to confirm a regexp within a trust signature. Returns 1 for match, and 0 for no match or regex error. */ static int check_regexp(const char *expr,const char *string) { #ifdef DISABLE_REGEX (void)expr; (void)string; /* When DISABLE_REGEX is defined, assume all regexps do not match. */ return 0; #else int ret; char *regexp; regexp=sanitize_regexp(expr); #ifdef __riscos__ ret=riscos_check_regexp(expr, string, DBG_TRUST); #else { regex_t pat; ret=regcomp(&pat,regexp,REG_ICASE|REG_NOSUB|REG_EXTENDED); if(ret==0) { ret=regexec(&pat,string,0,NULL,0); regfree(&pat); } ret=(ret==0); } #endif if(DBG_TRUST) log_debug("regexp '%s' ('%s') on '%s': %s\n", regexp,expr,string,ret?"YES":"NO"); xfree(regexp); return ret; #endif } /* * Return true if the key is signed by one of the keys in the given * key ID list. User IDs with a valid signature are marked by node * flags as follows: * flag bit 0: There is at least one signature * 1: There is marginal confidence that this is a legitimate uid * 2: There is full confidence that this is a legitimate uid. * 8: Used for internal purposes. * 9: Ditto (in mark_usable_uid_certs()) * 10: Ditto (ditto) * This function assumes that all kbnode flags are cleared on entry. */ static int validate_one_keyblock (ctrl_t ctrl, kbnode_t kb, struct key_item *klist, u32 curtime, u32 *next_expire) { struct key_item *kr; KBNODE node, uidnode=NULL; PKT_user_id *uid=NULL; PKT_public_key *pk = kb->pkt->pkt.public_key; u32 main_kid[2]; int issigned=0, any_signed = 0; keyid_from_pk(pk, main_kid); for (node=kb; node; node = node->next) { /* A bit of discussion here: is it better for the web of trust to be built among only self-signed uids? On the one hand, a self-signed uid is a statement that the key owner definitely intended that uid to be there, but on the other hand, a signed (but not self-signed) uid does carry trust, of a sort, even if it is a statement being made by people other than the key owner "through" the uids on the key owner's key. I'm going with the latter. However, if the user ID was explicitly revoked, or passively allowed to expire, that should stop validity through the user ID until it is resigned. -dshaw */ if (node->pkt->pkttype == PKT_USER_ID && !node->pkt->pkt.user_id->flags.revoked && !node->pkt->pkt.user_id->flags.expired) { if (uidnode && issigned) { if (uid->help_full_count >= opt.completes_needed || uid->help_marginal_count >= opt.marginals_needed ) uidnode->flag |= 4; else if (uid->help_full_count || uid->help_marginal_count) uidnode->flag |= 2; uidnode->flag |= 1; any_signed = 1; } uidnode = node; uid=uidnode->pkt->pkt.user_id; /* If the selfsig is going to expire... */ if(uid->expiredate && uid->expiredate<*next_expire) *next_expire = uid->expiredate; issigned = 0; get_validity_counts (ctrl, pk, uid); mark_usable_uid_certs (ctrl, kb, uidnode, main_kid, klist, curtime, next_expire); } else if (node->pkt->pkttype == PKT_SIGNATURE && (node->flag & (1<<8)) && uid) { /* Note that we are only seeing unrevoked sigs here */ PKT_signature *sig = node->pkt->pkt.signature; kr = is_in_klist (klist, sig); /* If the trust_regexp does not match, it's as if the sig did not exist. This is safe for non-trust sigs as well since we don't accept a regexp on the sig unless it's a trust sig. */ if (kr && (!kr->trust_regexp || !(opt.trust_model == TM_PGP || opt.trust_model == TM_TOFU_PGP) || (uidnode && check_regexp(kr->trust_regexp, uidnode->pkt->pkt.user_id->name)))) { /* Are we part of a trust sig chain? We always favor the latest trust sig, rather than the greater or lesser trust sig or value. I could make a decent argument for any of these cases, but this seems to be what PGP does, and I'd like to be compatible. -dms */ if ((opt.trust_model == TM_PGP || opt.trust_model == TM_TOFU_PGP) && sig->trust_depth && pk->trust_timestamp <= sig->timestamp) { unsigned char depth; /* If the depth on the signature is less than the chain currently has, then use the signature depth so we don't increase the depth beyond what the signer wanted. If the depth on the signature is more than the chain currently has, then use the chain depth so we use as much of the signature depth as the chain will permit. An ultimately trusted signature can restart the depth to whatever level it likes. */ if (sig->trust_depth < kr->trust_depth || kr->ownertrust == TRUST_ULTIMATE) depth = sig->trust_depth; else depth = kr->trust_depth; if (depth) { if(DBG_TRUST) log_debug ("trust sig on %s, sig depth is %d," " kr depth is %d\n", uidnode->pkt->pkt.user_id->name, sig->trust_depth, kr->trust_depth); /* If we got here, we know that: this is a trust sig. it's a newer trust sig than any previous trust sig on this key (not uid). it is legal in that it was either generated by an ultimate key, or a key that was part of a trust chain, and the depth does not violate the original trust sig. if there is a regexp attached, it matched successfully. */ if (DBG_TRUST) log_debug ("replacing trust value %d with %d and " "depth %d with %d\n", pk->trust_value,sig->trust_value, pk->trust_depth,depth); pk->trust_value = sig->trust_value; pk->trust_depth = depth-1; /* If the trust sig contains a regexp, record it on the pk for the next round. */ if (sig->trust_regexp) pk->trust_regexp = sig->trust_regexp; } } if (kr->ownertrust == TRUST_ULTIMATE) uid->help_full_count = opt.completes_needed; else if (kr->ownertrust == TRUST_FULLY) uid->help_full_count++; else if (kr->ownertrust == TRUST_MARGINAL) uid->help_marginal_count++; issigned = 1; } } } if (uidnode && issigned) { if (uid->help_full_count >= opt.completes_needed || uid->help_marginal_count >= opt.marginals_needed ) uidnode->flag |= 4; else if (uid->help_full_count || uid->help_marginal_count) uidnode->flag |= 2; uidnode->flag |= 1; any_signed = 1; } return any_signed; } static int search_skipfnc (void *opaque, u32 *kid, int dummy_uid_no) { (void)dummy_uid_no; return test_key_hash_table ((KeyHashTable)opaque, kid); } /* * Scan all keys and return a key_array of all suitable keys from * kllist. The caller has to pass keydb handle so that we don't use * to create our own. Returns either a key_array or NULL in case of * an error. No results found are indicated by an empty array. * Caller hast to release the returned array. */ static struct key_array * validate_key_list (ctrl_t ctrl, KEYDB_HANDLE hd, KeyHashTable full_trust, struct key_item *klist, u32 curtime, u32 *next_expire) { KBNODE keyblock = NULL; struct key_array *keys = NULL; size_t nkeys, maxkeys; int rc; KEYDB_SEARCH_DESC desc; maxkeys = 1000; keys = xmalloc ((maxkeys+1) * sizeof *keys); nkeys = 0; rc = keydb_search_reset (hd); if (rc) { log_error ("keydb_search_reset failed: %s\n", gpg_strerror (rc)); xfree (keys); return NULL; } memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FIRST; desc.skipfnc = search_skipfnc; desc.skipfncvalue = full_trust; rc = keydb_search (hd, &desc, 1, NULL); if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { keys[nkeys].keyblock = NULL; return keys; } if (rc) { log_error ("keydb_search(first) failed: %s\n", gpg_strerror (rc)); goto die; } desc.mode = KEYDB_SEARCH_MODE_NEXT; /* change mode */ do { PKT_public_key *pk; rc = keydb_get_keyblock (hd, &keyblock); if (rc) { log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc)); goto die; } if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY) { log_debug ("ooops: invalid pkttype %d encountered\n", keyblock->pkt->pkttype); dump_kbnode (keyblock); release_kbnode(keyblock); continue; } /* prepare the keyblock for further processing */ merge_keys_and_selfsig (ctrl, keyblock); clear_kbnode_flags (keyblock); pk = keyblock->pkt->pkt.public_key; if (pk->has_expired || pk->flags.revoked) { /* it does not make sense to look further at those keys */ mark_keyblock_seen (full_trust, keyblock); } else if (validate_one_keyblock (ctrl, keyblock, klist, curtime, next_expire)) { KBNODE node; if (pk->expiredate && pk->expiredate >= curtime && pk->expiredate < *next_expire) *next_expire = pk->expiredate; if (nkeys == maxkeys) { maxkeys += 1000; keys = xrealloc (keys, (maxkeys+1) * sizeof *keys); } keys[nkeys++].keyblock = keyblock; /* Optimization - if all uids are fully trusted, then we never need to consider this key as a candidate again. */ for (node=keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & 4)) break; if(node==NULL) mark_keyblock_seen (full_trust, keyblock); keyblock = NULL; } release_kbnode (keyblock); keyblock = NULL; } while (!(rc = keydb_search (hd, &desc, 1, NULL))); if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND) { log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc)); goto die; } keys[nkeys].keyblock = NULL; return keys; die: keys[nkeys].keyblock = NULL; release_key_array (keys); return NULL; } /* Caller must sync */ static void reset_trust_records (ctrl_t ctrl) { TRUSTREC rec; ulong recnum; int count = 0, nreset = 0; for (recnum=1; !tdbio_read_record (recnum, &rec, 0); recnum++ ) { if(rec.rectype==RECTYPE_TRUST) { count++; if(rec.r.trust.min_ownertrust) { rec.r.trust.min_ownertrust=0; write_record (ctrl, &rec); } } else if(rec.rectype==RECTYPE_VALID && ((rec.r.valid.validity&TRUST_MASK) || rec.r.valid.marginal_count || rec.r.valid.full_count)) { rec.r.valid.validity &= ~TRUST_MASK; rec.r.valid.marginal_count=rec.r.valid.full_count=0; nreset++; write_record (ctrl, &rec); } } if (opt.verbose) { log_info (ngettext("%d key processed", "%d keys processed", count), count); log_printf (ngettext(" (%d validity count cleared)\n", " (%d validity counts cleared)\n", nreset), nreset); } } /* * Run the key validation procedure. * * This works this way: * Step 1: Find all ultimately trusted keys (UTK). * mark them all as seen and put them into klist. * Step 2: loop max_cert_times * Step 3: if OWNERTRUST of any key in klist is undefined * ask user to assign ownertrust * Step 4: Loop over all keys in the keyDB which are not marked seen * Step 5: if key is revoked or expired * mark key as seen * continue loop at Step 4 * Step 6: For each user ID of that key signed by a key in klist * Calculate validity by counting trusted signatures. * Set validity of user ID * Step 7: If any signed user ID was found * mark key as seen * End Loop * Step 8: Build a new klist from all fully trusted keys from step 6 * End Loop * Ready * */ static int validate_keys (ctrl_t ctrl, int interactive) { int rc = 0; int quit=0; struct key_item *klist = NULL; struct key_item *k; struct key_array *keys = NULL; struct key_array *kar; KEYDB_HANDLE kdb = NULL; KBNODE node; int depth; int ot_unknown, ot_undefined, ot_never, ot_marginal, ot_full, ot_ultimate; KeyHashTable stored,used,full_trust; u32 start_time, next_expire; /* Make sure we have all sigs cached. TODO: This is going to require some architectural re-thinking, as it is agonizingly slow. Perhaps combine this with reset_trust_records(), or only check the caches on keys that are actually involved in the web of trust. */ keydb_rebuild_caches (ctrl, 0); kdb = keydb_new (); if (!kdb) return gpg_error_from_syserror (); start_time = make_timestamp (); next_expire = 0xffffffff; /* set next expire to the year 2106 */ stored = new_key_hash_table (); used = new_key_hash_table (); full_trust = new_key_hash_table (); reset_trust_records (ctrl); /* Fixme: Instead of always building a UTK list, we could just build it * here when needed */ if (!utk_list) { if (!opt.quiet) log_info (_("no ultimately trusted keys found\n")); goto leave; } /* mark all UTKs as used and fully_trusted and set validity to ultimate */ for (k=utk_list; k; k = k->next) { KBNODE keyblock; PKT_public_key *pk; keyblock = get_pubkeyblock (ctrl, k->kid); if (!keyblock) { log_error (_("public key of ultimately" " trusted key %s not found\n"), keystr(k->kid)); continue; } mark_keyblock_seen (used, keyblock); mark_keyblock_seen (stored, keyblock); mark_keyblock_seen (full_trust, keyblock); pk = keyblock->pkt->pkt.public_key; for (node=keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) update_validity (ctrl, pk, node->pkt->pkt.user_id, 0, TRUST_ULTIMATE); } if ( pk->expiredate && pk->expiredate >= start_time && pk->expiredate < next_expire) next_expire = pk->expiredate; release_kbnode (keyblock); do_sync (); } if (opt.trust_model == TM_TOFU) /* In the TOFU trust model, we only need to save the ultimately trusted keys. */ goto leave; klist = utk_list; if (!opt.quiet) log_info ("marginals needed: %d completes needed: %d trust model: %s\n", opt.marginals_needed, opt.completes_needed, trust_model_string (opt.trust_model)); for (depth=0; depth < opt.max_cert_depth; depth++) { int valids=0,key_count; /* See whether we should assign ownertrust values to the keys in klist. */ ot_unknown = ot_undefined = ot_never = 0; ot_marginal = ot_full = ot_ultimate = 0; for (k=klist; k; k = k->next) { int min=0; /* 120 and 60 are as per RFC2440 */ if(k->trust_value>=120) min=TRUST_FULLY; else if(k->trust_value>=60) min=TRUST_MARGINAL; if(min!=k->min_ownertrust) update_min_ownertrust (ctrl, k->kid,min); if (interactive && k->ownertrust == TRUST_UNKNOWN) { k->ownertrust = ask_ownertrust (ctrl, k->kid,min); if (k->ownertrust == (unsigned int)(-1)) { quit=1; goto leave; } } /* This can happen during transition from an old trustdb before trust sigs. It can also happen if a user uses two different versions of GnuPG or changes the --trust-model setting. */ if(k->ownertrustkid[0],(ulong)k->kid[1], trust_value_to_string(k->ownertrust), trust_value_to_string(min)); k->ownertrust=min; } if (k->ownertrust == TRUST_UNKNOWN) ot_unknown++; else if (k->ownertrust == TRUST_UNDEFINED) ot_undefined++; else if (k->ownertrust == TRUST_NEVER) ot_never++; else if (k->ownertrust == TRUST_MARGINAL) ot_marginal++; else if (k->ownertrust == TRUST_FULLY) ot_full++; else if (k->ownertrust == TRUST_ULTIMATE) ot_ultimate++; valids++; } /* Find all keys which are signed by a key in kdlist */ keys = validate_key_list (ctrl, kdb, full_trust, klist, start_time, &next_expire); if (!keys) { log_error ("validate_key_list failed\n"); rc = GPG_ERR_GENERAL; goto leave; } for (key_count=0, kar=keys; kar->keyblock; kar++, key_count++) ; /* Store the calculated valididation status somewhere */ if (opt.verbose > 1 && DBG_TRUST) dump_key_array (depth, keys); for (kar=keys; kar->keyblock; kar++) store_validation_status (ctrl, depth, kar->keyblock, stored); if (!opt.quiet) log_info (_("depth: %d valid: %3d signed: %3d" " trust: %d-, %dq, %dn, %dm, %df, %du\n"), depth, valids, key_count, ot_unknown, ot_undefined, ot_never, ot_marginal, ot_full, ot_ultimate ); /* Build a new kdlist from all fully valid keys in KEYS */ if (klist != utk_list) release_key_items (klist); klist = NULL; for (kar=keys; kar->keyblock; kar++) { for (node=kar->keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID && (node->flag & 4)) { u32 kid[2]; /* have we used this key already? */ keyid_from_pk (kar->keyblock->pkt->pkt.public_key, kid); if(test_key_hash_table(used,kid)==0) { /* Normally we add both the primary and subkey ids to the hash via mark_keyblock_seen, but since we aren't using this hash as a skipfnc, that doesn't matter here. */ add_key_hash_table (used,kid); k = new_key_item (); k->kid[0]=kid[0]; k->kid[1]=kid[1]; k->ownertrust = (tdb_get_ownertrust (ctrl, kar->keyblock->pkt->pkt.public_key, 0) & TRUST_MASK); k->min_ownertrust = tdb_get_min_ownertrust (ctrl, kar->keyblock->pkt->pkt.public_key, 0); k->trust_depth= kar->keyblock->pkt->pkt.public_key->trust_depth; k->trust_value= kar->keyblock->pkt->pkt.public_key->trust_value; if(kar->keyblock->pkt->pkt.public_key->trust_regexp) k->trust_regexp= xstrdup(kar->keyblock->pkt-> pkt.public_key->trust_regexp); k->next = klist; klist = k; break; } } } } release_key_array (keys); keys = NULL; if (!klist) break; /* no need to dive in deeper */ } leave: keydb_release (kdb); release_key_array (keys); if (klist != utk_list) release_key_items (klist); release_key_hash_table (full_trust); release_key_hash_table (used); release_key_hash_table (stored); if (!rc && !quit) /* mark trustDB as checked */ { int rc2; if (next_expire == 0xffffffff || next_expire < start_time ) tdbio_write_nextcheck (ctrl, 0); else { tdbio_write_nextcheck (ctrl, next_expire); if (!opt.quiet) log_info (_("next trustdb check due at %s\n"), strtimestamp (next_expire)); } rc2 = tdbio_update_version_record (ctrl); if (rc2) { log_error (_("unable to update trustdb version record: " "write failed: %s\n"), gpg_strerror (rc2)); tdbio_invalid (); } do_sync (); pending_check_trustdb = 0; } return rc; }