Imported Upstream version 2.1.17 upstream/2.1.17
authorDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 9 Feb 2021 07:00:16 +0000 (16:00 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 9 Feb 2021 07:00:16 +0000 (16:00 +0900)
209 files changed:
AUTHORS
COPYING.other [new file with mode: 0644]
Makefile.am
NEWS
README
agent/agent.h
agent/gpg-agent.c
autogen.rc
build-aux/append-signature.sh [new file with mode: 0755]
build-aux/gitlog-to-changelog
build-aux/speedo.mk
build-aux/speedo/w32/inst.nsi
build-aux/speedo/w32/pkg-copyright.txt
common/argparse.c
common/homedir.c
common/iobuf.c
common/iobuf.h
common/miscellaneous.c
common/openpgp-oid.c
common/sysutils.c
common/sysutils.h
common/util.h
configure.ac
dirmngr/Makefile.am
dirmngr/dirmngr.c
dirmngr/dirmngr.h
dirmngr/dns-stuff.c
dirmngr/dns-stuff.h
dirmngr/dns.c [new file with mode: 0644]
dirmngr/dns.h [new file with mode: 0644]
dirmngr/http.c
dirmngr/ks-engine-hkp.c
dirmngr/ldap-wrapper.c
dirmngr/loadswdb.c
dirmngr/server.c
dirmngr/t-dns-stuff.c
doc/DETAILS
doc/Makefile.am
doc/debugging.texi
doc/dirmngr.texi
doc/examples/gpgconf.conf
doc/faq.org
doc/gpg.texi
doc/gpgsm.texi
doc/howto-create-a-server-cert.texi
doc/tools.texi
g10/armor.c
g10/call-agent.c
g10/cpr.c
g10/decrypt.c
g10/distsigkey.gpg
g10/export.c
g10/getkey.c
g10/gpg.c
g10/gpgv.c
g10/import.c
g10/keydb.c
g10/keydb.h
g10/keyedit.c
g10/keygen.c
g10/keylist.c
g10/keyserver.c
g10/main.h
g10/mainproc.c
g10/options.h
g10/packet.h
g10/parse-packet.c
g10/passphrase.c
g10/photoid.c
g10/pkclist.c
g10/plaintext.c
g10/revoke.c
g10/sign.c
g10/test-stubs.c
g10/tofu.c
g10/tofu.h
g10/trust.c
g10/trustdb.c
g10/trustdb.h
po/ca.po
po/cs.po
po/da.po
po/de.po
po/el.po
po/eo.po
po/es.po
po/et.po
po/fi.po
po/fr.po
po/gl.po
po/hu.po
po/id.po
po/it.po
po/ja.po
po/nb.po
po/pl.po
po/pt.po
po/ro.po
po/ru.po
po/sk.po
po/sv.po
po/tr.po
po/uk.po
po/zh_CN.po
po/zh_TW.po
scd/apdu.c
scd/app-openpgp.c
scd/scdaemon.c
scd/scdaemon.h
sm/call-agent.c
sm/decrypt.c
sm/gpgsm.c
tests/Makefile.am
tests/gpgme/Makefile.am [new file with mode: 0644]
tests/gpgme/gpgme-defs.scm [new file with mode: 0644]
tests/gpgme/run-tests.scm [new file with mode: 0644]
tests/gpgme/setup.scm [new file with mode: 0644]
tests/gpgme/wrap.scm [new file with mode: 0644]
tests/gpgscm/ffi.scm
tests/gpgscm/init.scm
tests/gpgscm/lib.scm
tests/gpgscm/main.c
tests/gpgscm/opdefines.h
tests/gpgscm/repl.scm
tests/gpgscm/scheme-private.h
tests/gpgscm/scheme.c
tests/gpgscm/scheme.h
tests/gpgscm/tests.scm
tests/migrations/Makefile.am
tests/migrations/common.scm
tests/migrations/run-tests.scm [new file with mode: 0644]
tests/migrations/setup.scm [new file with mode: 0644]
tests/openpgp/4gb-packet.scm
tests/openpgp/Makefile.am
tests/openpgp/clearsig.scm
tests/openpgp/compression.scm [new file with mode: 0755]
tests/openpgp/conventional-mdc.scm
tests/openpgp/conventional.scm
tests/openpgp/decrypt-multifile.scm [new file with mode: 0755]
tests/openpgp/defs.scm
tests/openpgp/delete-keys.scm [new file with mode: 0755]
tests/openpgp/ecc.scm
tests/openpgp/enarmor.scm [new file with mode: 0755]
tests/openpgp/encrypt-dsa.scm
tests/openpgp/encrypt-multifile.scm [new file with mode: 0755]
tests/openpgp/encrypt.scm
tests/openpgp/export.scm
tests/openpgp/genkey1024.scm
tests/openpgp/gpgtar.scm
tests/openpgp/gpgv-forged-keyring.scm
tests/openpgp/import-revocation-certificate.scm [new file with mode: 0644]
tests/openpgp/import.scm
tests/openpgp/issue2015.scm
tests/openpgp/issue2346.scm
tests/openpgp/issue2419.scm
tests/openpgp/key-selection.scm
tests/openpgp/mds.scm
tests/openpgp/multisig.scm
tests/openpgp/quick-key-manipulation.scm
tests/openpgp/run-tests.scm
tests/openpgp/samplekeys/authenticate-only.pub.asc [new file with mode: 0644]
tests/openpgp/samplekeys/authenticate-only.sec.asc [new file with mode: 0644]
tests/openpgp/samplemsgs/revoke-2D727CC768697734.asc [new file with mode: 0644]
tests/openpgp/setup.scm
tests/openpgp/signencrypt.scm
tests/openpgp/sigs.scm
tests/openpgp/ssh-export.scm [new file with mode: 0755]
tests/openpgp/ssh-import.scm [moved from tests/openpgp/ssh.scm with 94% similarity]
tests/openpgp/tofu-2183839A-1.txt [deleted file]
tests/openpgp/tofu-BC15C85A-1.txt [deleted file]
tests/openpgp/tofu-EE37CF96-1.txt [deleted file]
tests/openpgp/tofu-keys-secret.asc [deleted file]
tests/openpgp/tofu-keys.asc [deleted file]
tests/openpgp/tofu.scm
tests/openpgp/tofu/conflicting/1C005AF3-1.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/1C005AF3-2.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/1C005AF3-3.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/1C005AF3-4.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/1C005AF3-5.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/1C005AF3-secret.gpg [new file with mode: 0644]
tests/openpgp/tofu/conflicting/1C005AF3.gpg [new file with mode: 0644]
tests/openpgp/tofu/conflicting/B662E42F-1.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/B662E42F-2.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/B662E42F-3.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/B662E42F-4.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/B662E42F-5.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/B662E42F-secret.gpg [new file with mode: 0644]
tests/openpgp/tofu/conflicting/B662E42F.gpg [new file with mode: 0644]
tests/openpgp/tofu/conflicting/BE04EB2B-1.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/BE04EB2B-2.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/BE04EB2B-3.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/BE04EB2B-4.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/BE04EB2B-5.txt [new file with mode: 0644]
tests/openpgp/tofu/conflicting/BE04EB2B-secret.gpg [new file with mode: 0644]
tests/openpgp/tofu/conflicting/BE04EB2B.gpg [new file with mode: 0644]
tests/openpgp/tofu/conflicting/README [new file with mode: 0644]
tests/openpgp/verify-multifile.scm [new file with mode: 0755]
tests/openpgp/verify.scm
tools/Makefile.am
tools/call-dirmngr.c
tools/call-dirmngr.h
tools/gpg-wks-client.c
tools/gpg-wks-server.c
tools/gpg-wks.h
tools/gpgconf-comp.c
tools/gpgconf.c
tools/gpgconf.h
tools/mime-maker.c
tools/wks-util.c

diff --git a/AUTHORS b/AUTHORS
index 2bbacd9..50b2095 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -31,6 +31,7 @@ List of Copyright holders
   Copyright (C) 1998-2003 Hallvard B. Furuseth.
   Copyright (C) 1992-1996 Regents of the University of Michigan.
   Copyright (C) 2000 Dimitrios Souflis
+  Copyright (C) 2008,2009,2010,2012-2016 William Ahern
 
 
 Authors with a FSF copyright assignment
@@ -191,6 +192,9 @@ Kyle Butt <kylebutt@gmail.com>
 Stefan Tomanek <tomanek@internet-sicherheit.de>
 2014-01-30:20140129234449.GY30808@zirkel.wertarbyte.de:
 
+Tobias Mueller <muelli@cryptobitch.de>
+2016-11-23:1479937342.11180.3.camel@cryptobitch.de:
+
 Werner Koch <wk@gnupg.org>
 2013-03-29:87620ahchj.fsf@vigenere.g10code.de:
 
@@ -217,9 +221,10 @@ Alexandre Julliard.
 The gpg-zip documentation is based on the manpage for gpg-zip, written
 by Colin Tuckley and Daniel Leidert for the GNU/Debian distribution.
 
+The DNS resolver code is libdns by William Ahern; see COPYING.other.
+
 The test driver is based on TinySCHEME by Dimitrios Souflis and
-available under a permissive license.  For the terms see the file
-tests/gpgscm/LICENSE.TinySCHEME.
+available under a permissive license; see COPYING.other.
 
 
 Copyright
@@ -231,9 +236,10 @@ or later.
 Note that some files are under a combination of the GNU Lesser General
 Public License, version 3 and the GNU General Public License, version
 2.  A few files carry an all permissive license note as found at the
-bottom of this file.  Some small files are distributed under the
-Creative Commons Zero (CC0-1.0-Universal) license which basically puts
-them into the public domain.
+bottom of this file.  A few files are distributed under permissive
+licenses as listed in the file COPYING.other.  Some other small files
+are distributed under the Creative Commons Zero license (see file
+COPYING.CC0) which basically puts them into the public domain.
 
 
 =========
diff --git a/COPYING.other b/COPYING.other
new file mode 100644 (file)
index 0000000..797f78c
--- /dev/null
@@ -0,0 +1,60 @@
+# COPYING.other                                            -*- org -*-
+#+TITLE: List of code with permissive licenses as used by GnuPG.
+#+STARTUP: showall
+
+* DNS resolver (dirmngr/dns.c)
+
+  dns.c - Recursive, Reentrant DNS Resolver.
+  --------------------------------------------------------------------------
+  Copyright (c) 2008, 2009, 2010, 2012-2016  William Ahern
+
+  Permission is hereby granted, free of charge, to any person obtaining a
+  copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to permit
+  persons to whom the Software is furnished to do so, subject to the
+  following conditions:
+
+  The above copyright notice and this permission notice shall be included
+  in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+  NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+  USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+* TinySCHEME (tests/gpgscm/LICENSE.TinySCHEME)
+
+  Copyright (c) 2000, Dimitrios Souflis
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are
+  met:
+
+  Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+
+  Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+  Neither the name of Dimitrios Souflis nor the names of the
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
+  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
index 5eeac11..735c72b 100644 (file)
@@ -26,7 +26,8 @@ GITLOG_TO_CHANGELOG=gitlog-to-changelog
 
 EXTRA_DIST = build-aux/config.rpath build-aux/potomo autogen.sh autogen.rc \
             ChangeLog-2011 po/ChangeLog-2011 build-aux/ChangeLog-2011     \
-            VERSION README.GIT build-aux/gitlog-to-changelog COPYING.CC0  \
+            VERSION README.GIT build-aux/gitlog-to-changelog              \
+            COPYING.CC0 COPYING.other                      \
             build-aux/git-log-fix build-aux/git-log-footer \
             build-aux/getswdb.sh                           \
             build-aux/speedo.mk                            \
diff --git a/NEWS b/NEWS
index 0eccb91..5633c55 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,39 @@
+Noteworthy changes in version 2.1.17 (2016-12-20)
+-------------------------------------------------
+
+ * gpg: By default new keys expire after 2 years.
+
+ * gpg: New command --quick-set-expire to conveniently change the
+   expiration date of keys.
+
+ * gpg: Option and command names have been changed for easier
+   comprehension.  The old names are still available as aliases.
+
+ * gpg: Improved the TOFU trust model.
+
+ * gpg: New option --default-new-key-algo.
+
+ * scd: Support OpenPGP card V3 for RSA.
+
+ * dirmngr: Support for the ADNS library has been removed.  Instead
+   William Ahern's Libdns is now source included and used on all
+   platforms.  This enables Tor support on all platforms.  The new
+   option --standard-resolver can be used to disable this code at
+   runtime.  In case of build problems the new configure option
+   --disable-libdns can be used to build without Libdns.
+
+ * dirmngr: Lazily launch ldap reaper thread.
+
+ * tools: New options --check and --status-fd for gpg-wks-client.
+
+ * The UTF-8 byte order mark is now skipped when reading conf files.
+
+ * Fixed many bugs and regressions.
+
+ * Major improvements to the test suite.  For example it is possible
+   to run the external test suite of GPGME.
+
+
 Noteworthy changes in version 2.1.16 (2016-11-18)
 -------------------------------------------------
 
diff --git a/README b/README
index c8b166b..18cc316 100644 (file)
--- a/README
+++ b/README
 
   We are currently maintaining three branches of GnuPG:
 
-  - 2.1 (i.e. this release) is the latest development with a lot of
+  - 2.1 (i.e. this release) is the latest stable version with a lot of
     new features.
 
-  - 2.0 is the current stable version for general use.
+  - 2.0 is an often used stable version.  This branch will reach
+    end-of-life on 2017-12-31.
 
   - 1.4 is the old standalone version which is most suitable for older
     or embedded platforms.
   Add other options as needed.
 
 
+*** Systems without a full C99 compiler
+
+  If you run into problems with your compiler complaining about dns.c
+  you may use
+
+    ./configure --disable-libdns
+
+  Add other options as needed.
+
+
 * MIGRATION from 1.4 or 2.0 to 2.1
 
   The major change in 2.1 is gpg-agent taking care of the OpenPGP
index 2dfbf5c..89dc46d 100644 (file)
@@ -168,7 +168,6 @@ struct
 
 
 /* Bit values for the --debug option.  */
-#define DBG_COMMAND_VALUE 1    /* debug commands i/o */
 #define DBG_MPI_VALUE    2     /* debug mpi details */
 #define DBG_CRYPTO_VALUE  4    /* debug low level crypto */
 #define DBG_MEMORY_VALUE  32   /* debug memory allocation stuff */
@@ -178,7 +177,6 @@ struct
 #define DBG_IPC_VALUE     1024  /* Enable Assuan debugging.  */
 
 /* Test macros for the debug option.  */
-#define DBG_COMMAND (opt.debug & DBG_COMMAND_VALUE)
 #define DBG_CRYPTO  (opt.debug & DBG_CRYPTO_VALUE)
 #define DBG_MEMORY  (opt.debug & DBG_MEMORY_VALUE)
 #define DBG_CACHE   (opt.debug & DBG_CACHE_VALUE)
index 1433f7f..f4ed6c5 100644 (file)
@@ -251,7 +251,6 @@ static ARGPARSE_OPTS opts[] = {
 /* The list of supported debug flags.  */
 static struct debug_flags_s debug_flags [] =
   {
-    { DBG_COMMAND_VALUE, "command"  },
     { DBG_MPI_VALUE    , "mpi"     },
     { DBG_CRYPTO_VALUE , "crypto"  },
     { DBG_MEMORY_VALUE , "memory"  },
@@ -520,10 +519,9 @@ set_debug (void)
   else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
     opt.debug = DBG_IPC_VALUE;
   else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
-    opt.debug = DBG_IPC_VALUE|DBG_COMMAND_VALUE;
+    opt.debug = DBG_IPC_VALUE;
   else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
-    opt.debug = (DBG_IPC_VALUE|DBG_COMMAND_VALUE
-                 |DBG_CACHE_VALUE);
+    opt.debug = (DBG_IPC_VALUE | DBG_CACHE_VALUE);
   else if (!strcmp (debug_level, "guru") || numok)
     {
       opt.debug = ~0;
@@ -1055,7 +1053,7 @@ main (int argc, char **argv )
     }
 
   /* Initialize the secure memory. */
-  gcry_control (GCRYCTL_INIT_SECMEM, 32768, 0);
+  gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
   maybe_setuid = 0;
 
   /*
@@ -1473,8 +1471,6 @@ main (int argc, char **argv )
       pid_t pid;
 #endif
 
-      initialize_modules ();
-
       /* Remove the DISPLAY variable so that a pinentry does not
          default to a specific display.  There is still a default
          display when gpg-agent was started using --display or a
@@ -1534,10 +1530,15 @@ main (int argc, char **argv )
         parent_pid = getpid ();
 
       fflush (NULL);
+
 #ifdef HAVE_W32_SYSTEM
+
       (void)csh_style;
       (void)nodetach;
+      initialize_modules ();
+
 #else /*!HAVE_W32_SYSTEM*/
+
       pid = fork ();
       if (pid == (pid_t)-1)
         {
@@ -2826,8 +2827,19 @@ handle_connections (gnupg_fd_t listen_fd,
             break; /* ready */
 
           /* Do not accept new connections but keep on running the
-             loop to cope with the timer events.  */
+           * loop to cope with the timer events.
+           *
+           * Note that we do not close the listening socket because a
+           * client trying to connect to that socket would instead
+           * restart a new dirmngr instance - which is unlikely the
+           * intention of a shutdown. */
           FD_ZERO (&fdset);
+          nfd = -1;
+          if (my_inotify_fd != -1)
+            {
+              FD_SET (my_inotify_fd, &fdset);
+              nfd = my_inotify_fd;
+            }
        }
 
       /* POSIX says that fd_set should be implemented as a structure,
index 8cb7eed..3694817 100644 (file)
@@ -22,7 +22,6 @@ case "$myhost" in
       --with-zlib=@SYSROOT@
       --with-regex=@SYSROOT@
       --with-npth-prefix=@SYSROOT@
-      --with-adns=@SYSROOT@
       --disable-g13
       "
     ;;
diff --git a/build-aux/append-signature.sh b/build-aux/append-signature.sh
new file mode 100755 (executable)
index 0000000..714d286
--- /dev/null
@@ -0,0 +1,108 @@
+#!/bin/sh
+# Append a signature to an existing detached signature.
+# Copyright (C) 2016 g10 Code GmbH
+#
+# This file is free software; as a special exception the author gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+set -e
+PGM="$(basename $0)"
+GPGV=gpgv
+
+# Prints usage information.
+usage()
+{
+    cat <<EOF
+Usage: $PGM TARBALL NEWSIGNATURE
+Append a signature to an existing detached signature.
+Options:
+    --verbose          Print some extra information.
+    --help             Print this help.
+EOF
+    exit $1
+}
+
+#
+# Parse options
+#
+verbose=""
+while [ $# -gt 0 ]; do
+    case "$1" in
+       # Set up `optarg'.
+       --*=*)
+           optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'`
+           ;;
+       *)
+           optarg=""
+           ;;
+    esac
+
+    case $1 in
+        --help|-h)
+           usage 0
+           ;;
+        --verbose|-v)
+            verbose="-v"
+            ;;
+        --)
+            break
+            ;;
+       -*)
+           usage 1 1>&2
+           ;;
+        *)
+            break;
+            ;;
+    esac
+    shift
+done
+
+if [ $# -ne 2 ]; then
+    usage 1 1>&2
+fi
+tarball="$1"
+tarballsig="$1".sig
+newsig="$2"
+
+[ -n "$verbose" ] && echo "tarball: $tarball"
+[ -n "$verbose" ] && echo "sig ...: $tarballsig"
+[ -n "$verbose" ] && echo "newsig : $newsig"
+
+if ! $GPGV --version >/dev/null 2>/dev/null ; then
+    echo "${PGM}: Command \"gpgv\" is not installed" >&2
+    exit 1
+fi
+
+distsigkey="/usr/local/share/gnupg/distsigkey.gpg"
+if [ ! -f "$distsigkey" ]; then
+    distsigkey="/usr/share/gnupg/distsigkey.gpg"
+fi
+if [ ! -f "$distsigkey" ]; then
+    echo "${PGM}: File \"$distsigkey\" is not installed" >&2
+    exit 1
+fi
+
+if ! $GPGV $verbose --keyring "$distsigkey" \
+           -- "$tarballsig" "$tarball" 2>/dev/null ; then
+    echo "${PGM}: Existing signature '$tarballsig' does not verify" >&2
+    exit 1
+fi
+
+if ! $GPGV $verbose --keyring "$distsigkey" \
+           -- "$newsig" "$tarball" 2>/dev/null; then
+    echo "${PGM}: New signature '$newsig' does not verify" >&2
+    exit 1
+fi
+
+cat "$newsig" >> "$tarballsig"
+
+if ! $GPGV $verbose --keyring "$distsigkey" \
+           -- "$tarballsig" "$tarball"; then
+    echo "${PGM}: Update signature '$tarballsig' does not verify" >&2
+    exit 1
+fi
index 5cf071f..24a3d72 100755 (executable)
@@ -294,9 +294,8 @@ sub parse_amend_file($)
       $prev_date_line = $date_line;
       @prev_coauthors = @coauthors;
 
-      # Omit "Co-authored-by..." and "Signed-off-by..." lines.
-      @line = grep !/^Signed-off-by: .*>$/, @line;
-      @line = grep !/^Co-authored-by: /, @line;
+      # Omit keyword lines like "Signed-off-by:" or "GnuPG-bug-id:"
+      @line = grep !/^[A-Z][A-Za-z]+-[a-z-]+: /, @line;
 
       # Remove everything after a line with 2 dashes at the beginning.
       if ($tear_off)
index fbe258c..8a366e6 100644 (file)
@@ -52,12 +52,13 @@ SPEEDO_MK := $(realpath $(lastword $(MAKEFILE_LIST)))
 help:
        @echo 'usage: make -f speedo.mk TARGET'
        @echo '       with TARGET being one of:'
-       @echo '  help           This help'
-       @echo '  native         Native build of the GnuPG core'
-       @echo '  native-gui     Ditto but with pinentry and GPA'
-       @echo '  w32-installer  Build a Windows installer'
-       @echo '  w32-source     Pack a source archive'
-       @echo '  w32-release    Build a Windows release'
+       @echo '  help               This help'
+       @echo '  native             Native build of the GnuPG core'
+       @echo '  native-gui         Ditto but with pinentry and GPA'
+       @echo '  w32-installer      Build a Windows installer'
+       @echo '  w32-source         Pack a source archive'
+       @echo '  w32-release        Build a Windows release'
+       @echo '  w32-sign-installer Sign the installer'
        @echo
        @echo 'You may append INSTALL_PREFIX=<dir> for native builds.'
        @echo 'Prepend TARGET with "git-" to build from GIT repos.'
@@ -109,6 +110,10 @@ w32-release: check-tools
        $(SPEEDOMAKE) TARGETOS=w32 WHAT=release    WITH_GUI=0 SELFCHECK=0 \
                                                    installer-from-source
 
+w32-sign-installer: check-tools
+       $(SPEEDOMAKE) TARGETOS=w32 WHAT=release    WITH_GUI=0 SELFCHECK=0 \
+                                                   sign-installer
+
 w32-release-offline: check-tools
        $(SPEEDOMAKE) TARGETOS=w32 WHAT=release    WITH_GUI=0 SELFCHECK=0 \
          CUSTOM_SWDB=1 pkgrep=${HOME}/b pkg10rep=${HOME}/b  \
@@ -148,6 +153,9 @@ INST_NAME=gnupg-w32
 # Use this to override the installaion directory for native builds.
 INSTALL_PREFIX=none
 
+# The Authenticode key used to sign the Windows installer
+AUTHENTICODE_KEY=${HOME}/.gnupg/g10code-authenticode-key.p12
+
 
 # Directory names.
 # They must be absolute, as we switch directories pretty often.
@@ -1162,6 +1170,18 @@ installer: all w32_insthelpers $(w32src)/inst-options.ini $(bdir)/README.txt
                    $(extra_installer_options) $(w32src)/inst.nsi
        @echo "Ready: $(idir)/$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).exe"
 
+
+define MKSWDB_commands
+ ( pref="#+macro: gnupg21_w32_" ;\
+   echo "$${pref}ver  $(INST_VERSION)_$(BUILD_DATESTR)"  ;\
+   echo "$${pref}date $(2)" ;\
+   echo "$${pref}size $$(wc -c <$(1)|awk '{print int($$1/1024)}')k";\
+   echo "$${pref}sha1 $$(sha1sum <$(1)|cut -d' ' -f1)" ;\
+   echo "$${pref}sha2 $$(sha256sum <$(1)|cut -d' ' -f1)" ;\
+ ) | tee $(1).swdb
+endef
+
+
 # Build the installer from the source tarball.
 installer-from-source: dist-source
        (set -e;\
@@ -1173,17 +1193,36 @@ installer-from-source: dist-source
          $(MAKE) -f build-aux/speedo.mk this-w32-installer SELFCHECK=0;\
         reldate="$$(date -u +%Y-%m-%d)" ;\
         exefile="$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).exe" ;\
-        mv "PLAY/inst/$$exefile" ../.. ;\
+        cp "PLAY/inst/$$exefile" ../.. ;\
+        exefile="../../$$exefile" ;\
+        $(call MKSWDB_commands,$${exefile},$${reldate}); \
+       )
+
+# This target repeats some of the installer-from-source steps but it
+# is intended to be called interactively, so that the passphrase can be
+# entered.
+sign-installer:
+       @(set -e; \
+        cd PLAY-release; \
+        cd $(INST_NAME)-$(INST_VERSION); \
+        reldate="$$(date -u +%Y-%m-%d)" ;\
+        exefile="$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).exe" ;\
+        echo "speedo: /*" ;\
+        echo "speedo:  * Signing installer" ;\
+        echo "speedo:  * Key: $(AUTHENTICODE_KEY)";\
+        echo "speedo:  */" ;\
+        osslsigncode sign -pkcs12 $(AUTHENTICODE_KEY) -askpass \
+            -h sha256 -in "PLAY/inst/$$exefile" -out "../../$$exefile" ;\
         exefile="../../$$exefile" ;\
-        ( pref="#+macro: gnupg21_w32_" ;\
-         echo "$${pref}ver  $(INST_VERSION)_$(BUILD_DATESTR)"  ;\
-         echo "$${pref}date $${reldate}" ;\
-         echo "$${pref}size $$(wc -c <$$exefile|awk '{print int($$1/1024)}')k";\
-        echo "$${pref}sha1 $$(sha1sum <$$exefile|cut -d' ' -f1)" ;\
-        echo "$${pref}sha2 $$(sha256sum <$$exefile|cut -d' ' -f1)" ;\
-        ) | tee $$exefile.swdb ;\
+        $(call MKSWDB_commands,$${exefile},$${reldate}); \
+        echo "speedo: /*" ;\
+        echo "speedo:  * Verification result" ;\
+        echo "speedo:  */" ;\
+         osslsigncode verify $${exefile} \
        )
 
+
+
 endif
 # }}} W32
 
index 24d5d4d..164e26b 100644 (file)
@@ -545,15 +545,16 @@ Section "-gnupginst"
   # If we are reinstalling, try to kill a possible running gpa using
   # an already installed gpa.
   ifFileExists "$INSTDIR\bin\launch-gpa.exe"  0 no_uiserver
-    ExecWait '"$INSTDIR\bin\launch-gpa" --stop-server'
+    nsExec::ExecToLog '"$INSTDIR\bin\launch-gpa" "--stop-server"'
 
   no_uiserver:
 
   # If we are reinstalling, try to kill a possible running agent using
   # an already installed gpgconf.
+
   ifFileExists "$INSTDIR\bin\gpgconf.exe"  0 no_gpgconf
-    ExecWait '"$INSTDIR\bin\gpgconf" --kill dirmngr'
-    ExecWait '"$INSTDIR\bin\gpgconf" --kill gpg-agent'
+    nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "dirmngr"'
+    nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "gpg-agent"'
 
   no_gpgconf:
 
@@ -976,7 +977,7 @@ ${If} ${RunningX64}
   # RegDLL can't be used for 64 bit and InstallLib seems to be a
   # registry hack.
   ClearErrors
-  ExecWait '"$SYSDIR\regsvr32" /s "$INSTDIR\bin\gpgex6.dll"'
+  nsExec::ExecToLog '"$SYSDIR\regsvr32" "/s" "$INSTDIR\bin\gpgex6.dll"'
   ifErrors 0 +2
      MessageBox MB_OK "$(T_GPGEX_RegFailed) (64 bit)"
 
@@ -1007,11 +1008,11 @@ SectionEnd
 
 Section "-un.gnupglast"
   ifFileExists "$INSTDIR\bin\launch-gpa.exe"  0 no_uiserver
-    ExecWait '"$INSTDIR\bin\launch-gpa" --stop-server'
+    nsExec::ExecToLog '"$INSTDIR\bin\launch-gpa" "--stop-server"'
   no_uiserver:
   ifFileExists "$INSTDIR\bin\gpgconf.exe"  0 no_gpgconf
-    ExecWait '"$INSTDIR\bin\gpgconf" --kill gpg-agent'
-    ExecWait '"$INSTDIR\bin\gpgconf" --kill dirmngr'
+    nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "gpg-agent"'
+    nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "dirmngr"'
   no_gpgconf:
 SectionEnd
 
@@ -1021,7 +1022,7 @@ Section "-un.gpgex"
   Delete /REBOOTOK "$INSTDIR\bin\gpgex.dll"
 
 ${If} ${RunningX64}
-  ExecWait '"$SYSDIR\regsvr32" /u /s "$INSTDIR\bin\gpgex6.dll"'
+  nsExec::ExecToLog '"$SYSDIR\regsvr32" "/u" "/s" "$INSTDIR\bin\gpgex6.dll"'
   Delete /REBOOTOK "$INSTDIR\bin\gpgex6.dll"
 ${EndIf}
 SectionEnd
index daf2881..a630c8e 100644 (file)
@@ -19,6 +19,7 @@ GnuPG is
   Copyright (C) 1998-2003 Hallvard B. Furuseth.
   Copyright (C) 1992-1996 Regents of the University of Michigan.
   Copyright (C) 2000 Dimitrios Souflis
+  Copyright (C) 2008,2009,2010,2012-2016 William Ahern
 
   GnuPG is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
@@ -145,6 +146,30 @@ TinySCHEME is part of the GnuPG package and is
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
+libdns is part of the GnuPG package and is
+
+  Copyright (c) 2008, 2009, 2010, 2012-2016  William Ahern
+
+  Permission is hereby granted, free of charge, to any person obtaining a
+  copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to permit
+  persons to whom the Software is furnished to do so, subject to the
+  following conditions:
+
+  The above copyright notice and this permission notice shall be included
+  in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+  NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+  USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
 SQLite has
 
   been put into the public-domain by its author D. Richard Hipp:
index 240fdce..dce725a 100644 (file)
@@ -568,17 +568,35 @@ optfile_parse (FILE *fp, const char *filename, unsigned *lineno,
   char *buffer = NULL;
   size_t buflen = 0;
   int in_alias=0;
+  int unread_buf[3];  /* We use an int so that we can store EOF.  */
+  int unread_buf_count = 0;
 
   if (!fp) /* Divert to to arg_parse() in this case.  */
     return arg_parse (arg, opts);
 
   initialize (arg, filename, lineno);
 
+  /* If the LINENO is zero we assume that we are at the start of a
+   * file and we skip over a possible Byte Order Mark.  */
+  if (!*lineno)
+    {
+      unread_buf[0] = getc (fp);
+      unread_buf[1] = getc (fp);
+      unread_buf[2] = getc (fp);
+      if (unread_buf[0] != 0xef
+          || unread_buf[1] != 0xbb
+          || unread_buf[2] != 0xbf)
+        unread_buf_count = 3;
+    }
+
   /* Find the next keyword.  */
   state = i = 0;
   for (;;)
     {
-      c = getc (fp);
+      if (unread_buf_count)
+        c = unread_buf[3 - unread_buf_count--];
+      else
+        c = getc (fp);
       if (c == '\n' || c== EOF )
         {
           if ( c != EOF )
index 59b7135..6b40bb6 100644 (file)
@@ -884,15 +884,60 @@ get_default_pinentry_name (int reset)
 }
 
 
+/* If set, 'gnupg_module_name' returns modules from that build
+ * directory.  */
+static char *gnupg_build_directory;
+
+/* For sanity checks.  */
+static int gnupg_module_name_called;
+
+
+/* Set NEWDIR as the new build directory.  This will make
+ * 'gnupg_module_name' return modules from that build directory.  Must
+ * be called before any invocation of 'gnupg_module_name', and must
+ * not be called twice.  It can be used by test suites to make sure
+ * the components from the build directory are used instead of
+ * potentially outdated installed ones.  */
+void
+gnupg_set_builddir (const char *newdir)
+{
+  log_assert (! gnupg_module_name_called);
+  log_assert (! gnupg_build_directory);
+  gnupg_build_directory = xtrystrdup (newdir);
+}
+
+
+/* If no build directory has been configured, try to set it from the
+ * environment.  We only do this in development builds to avoid
+ * increasing the set of influential environment variables and hence
+ * the attack surface of production builds.  */
+static void
+gnupg_set_builddir_from_env (void)
+{
+#ifdef IS_DEVELOPMENT_VERSION
+  if (gnupg_build_directory)
+    return;
+
+  gnupg_build_directory = getenv ("GNUPG_BUILDDIR");
+#endif
+}
+
+
 /* Return the file name of a helper tool.  WHICH is one of the
    GNUPG_MODULE_NAME_foo constants.  */
 const char *
 gnupg_module_name (int which)
 {
-#define X(a,b) do {                                                     \
+  gnupg_set_builddir_from_env ();
+  gnupg_module_name_called = 1;
+
+#define X(a,b,c) do {                                                   \
     static char *name;                                                  \
     if (!name)                                                          \
-      name = xstrconcat (gnupg_ ## a (), DIRSEP_S b EXEEXT_S, NULL);    \
+      name = gnupg_build_directory                                      \
+        ? xstrconcat (gnupg_build_directory,                            \
+                      DIRSEP_S b DIRSEP_S c EXEEXT_S, NULL)             \
+        : xstrconcat (gnupg_ ## a (), DIRSEP_S c EXEEXT_S, NULL);       \
     return name;                                                        \
   } while (0)
 
@@ -902,7 +947,7 @@ gnupg_module_name (int which)
 #ifdef GNUPG_DEFAULT_AGENT
       return GNUPG_DEFAULT_AGENT;
 #else
-      X(bindir, "gpg-agent");
+      X(bindir, "agent", "gpg-agent");
 #endif
 
     case GNUPG_MODULE_NAME_PINENTRY:
@@ -916,55 +961,57 @@ gnupg_module_name (int which)
 #ifdef GNUPG_DEFAULT_SCDAEMON
       return GNUPG_DEFAULT_SCDAEMON;
 #else
-      X(libexecdir, "scdaemon");
+      X(libexecdir, "scd", "scdaemon");
 #endif
 
     case GNUPG_MODULE_NAME_DIRMNGR:
 #ifdef GNUPG_DEFAULT_DIRMNGR
       return GNUPG_DEFAULT_DIRMNGR;
 #else
-      X(bindir, DIRMNGR_NAME);
+      X(bindir, "dirmngr", DIRMNGR_NAME);
 #endif
 
     case GNUPG_MODULE_NAME_PROTECT_TOOL:
 #ifdef GNUPG_DEFAULT_PROTECT_TOOL
       return GNUPG_DEFAULT_PROTECT_TOOL;
 #else
-      X(libexecdir, "gpg-protect-tool");
+      X(libexecdir, "agent", "gpg-protect-tool");
 #endif
 
     case GNUPG_MODULE_NAME_DIRMNGR_LDAP:
 #ifdef GNUPG_DEFAULT_DIRMNGR_LDAP
       return GNUPG_DEFAULT_DIRMNGR_LDAP;
 #else
-      X(libexecdir, "dirmngr_ldap");
+      X(libexecdir, "dirmngr", "dirmngr_ldap");
 #endif
 
     case GNUPG_MODULE_NAME_CHECK_PATTERN:
-      X(libexecdir, "gpg-check-pattern");
+      X(libexecdir, "tools", "gpg-check-pattern");
 
     case GNUPG_MODULE_NAME_GPGSM:
-      X(bindir, "gpgsm");
+      X(bindir, "sm", "gpgsm");
 
     case GNUPG_MODULE_NAME_GPG:
 #if USE_GPG2_HACK
-      X(bindir, GPG_NAME "2");
-#else
-      X(bindir, GPG_NAME);
+      if (! gnupg_build_directory)
+        X(bindir, "g10", GPG_NAME "2");
+      else
 #endif
+        X(bindir, "g10", GPG_NAME);
 
     case GNUPG_MODULE_NAME_GPGV:
 #if USE_GPG2_HACK
-      X(bindir, GPG_NAME "v2");
-#else
-      X(bindir, GPG_NAME "v");
+      if (! gnupg_build_directory)
+        X(bindir, "g10", GPG_NAME "v2");
+      else
 #endif
+        X(bindir, "g10", GPG_NAME "v");
 
     case GNUPG_MODULE_NAME_CONNECT_AGENT:
-      X(bindir, "gpg-connect-agent");
+      X(bindir, "tools", "gpg-connect-agent");
 
     case GNUPG_MODULE_NAME_GPGCONF:
-      X(bindir, "gpgconf");
+      X(bindir, "tools", "gpgconf");
 
     default:
       BUG ();
index ed90bd7..d346027 100644 (file)
@@ -155,11 +155,6 @@ typedef struct
 block_filter_ctx_t;
 
 
-/* Global flag to tell whether special file names are enabled.  See
-   gpg.c for an explanation of these file names.  FIXME: This does not
-   belong in the iobuf subsystem. */
-static int special_names_enabled;
-
 /* Local prototypes.  */
 static int underflow (iobuf_t a, int clear_pending_eof);
 static int underflow_target (iobuf_t a, int clear_pending_eof, size_t target);
@@ -1237,41 +1232,16 @@ iobuf_temp_with_content (const char *buffer, size_t length)
   return a;
 }
 
-void
-iobuf_enable_special_filenames (int yes)
-{
-  special_names_enabled = yes;
-}
-
-
-/* See whether the filename has the form "-&nnnn", where n is a
-   non-zero number.  Returns this number or -1 if it is not the
-   case.  */
-static int
-check_special_filename (const char *fname)
-{
-  if (special_names_enabled && fname && *fname == '-' && fname[1] == '&')
-    {
-      int i;
-
-      fname += 2;
-      for (i = 0; digitp (fname+i); i++)
-       ;
-      if (!fname[i])
-       return atoi (fname);
-    }
-  return -1;
-}
-
 
 int
 iobuf_is_pipe_filename (const char *fname)
 {
   if (!fname || (*fname=='-' && !fname[1]) )
     return 1;
-  return check_special_filename (fname) != -1;
+  return check_special_filename (fname, 0, 1) != -1;
 }
 
+
 static iobuf_t
 do_open (const char *fname, int special_filenames,
         int use, const char *opentype, int mode700)
@@ -1304,7 +1274,8 @@ do_open (const char *fname, int special_filenames,
     }
   else if (!fname)
     return NULL;
-  else if (special_filenames && (fd = check_special_filename (fname)) != -1)
+  else if (special_filenames
+           && (fd = check_special_filename (fname, 0, 1)) != -1)
     return iobuf_fdopen (translate_file_handle (fd, use == IOBUF_INPUT ? 0 : 1),
                         opentype);
   else
index 4fa5660..22e02da 100644 (file)
@@ -258,16 +258,10 @@ struct iobuf_struct
 #endif
 EXTERN_UNLESS_MAIN_MODULE int iobuf_debug_mode;
 
-/* Whether iobuf_open, iobuf_create and iobuf_is_pipefilename
-   recognize special filenames.  Special filenames are of the form
-   "-&nnnn" where n is a positive integer.  The integer corresponds to
-   a file descriptor.  Note: these functions always recognize the
-   special filename '-', which corresponds to standard input.  */
-void iobuf_enable_special_filenames (int yes);
 
 /* Returns whether the specified filename corresponds to a pipe.  In
    particular, this function checks if FNAME is "-" and, if special
-   filenames are enabled (see iobuf_enable_special_filenames), whether
+   filenames are enabled (see check_special_filename), whether
    FNAME is a special filename.  */
 int  iobuf_is_pipe_filename (const char *fname);
 
index 9924943..c988975 100644 (file)
@@ -480,6 +480,8 @@ gnupg_compare_version (const char *a, const char *b)
  *          supplied variable is updated by the parsed flags.
  *
  * If STRING is NULL the enabled debug flags are printed.
+ *
+ * See doc/DETAILS for a summary of used debug options.
  */
 int
 parse_debug_flag (const char *string, unsigned int *debugvar,
index 270bdf1..e7c68f2 100644 (file)
@@ -411,17 +411,21 @@ openpgp_enum_curves (int *iterp)
 }
 
 
-/* Return the Libgcrypt name for for the gpg curve NAME if supported.
- * If R_ALGO is not NULL the required OpenPGP public key algo or 0 is
- * stored at that address.  NULL is returned if the curev is not
- * supported. */
+/* Return the Libgcrypt name for the gpg curve NAME if supported.  If
+ * R_ALGO is not NULL the required OpenPGP public key algo or 0 is
+ * stored at that address.  If R_NBITS is not NULL the nominal bitsize
+ * of the curves is stored there.  NULL is returned if the curve is
+ * not supported. */
 const char *
-openpgp_is_curve_supported (const char *name, int *r_algo)
+openpgp_is_curve_supported (const char *name, int *r_algo,
+                            unsigned int *r_nbits)
 {
   int idx;
 
   if (r_algo)
     *r_algo = 0;
+  if (r_nbits)
+    *r_nbits = 0;
   for (idx = 0; idx < DIM (oidtable) && oidtable[idx].name; idx++)
     {
       if ((!strcmp (name, oidtable[idx].name)
@@ -430,6 +434,8 @@ openpgp_is_curve_supported (const char *name, int *r_algo)
         {
           if (r_algo)
             *r_algo = oidtable[idx].pubkey_algo;
+          if (r_nbits)
+            *r_nbits = oidtable[idx].nbits;
           return oidtable[idx].name;
         }
     }
index c7df872..e67420f 100644 (file)
@@ -1,7 +1,7 @@
 /* sysutils.c -  system helpers
  * Copyright (C) 1991-2001, 2003-2004,
  *               2006-2008  Free Software Foundation, Inc.
- * Copyright (C) 2013-2014 Werner Koch
+ * Copyright (C) 2013-2016 Werner Koch
  *
  * This file is part of GnuPG.
  *
 
 #define tohex(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'A'))
 
+/* Flag to tell whether special file names are enabled.  See gpg.c for
+ * an explanation of these file names.  */
+static int allow_special_filenames;
+
 
 static GPGRT_INLINE gpg_error_t
 my_error_from_syserror (void)
@@ -168,6 +172,13 @@ enable_core_dumps (void)
 }
 
 
+/* Allow the use of special "-&nnn" style file names.  */
+void
+enable_special_filenames (void)
+{
+  allow_special_filenames = 1;
+}
+
 
 /* Return a string which is used as a kind of process ID.  */
 const byte *
@@ -402,6 +413,29 @@ translate_sys2libc_fd_int (int fd, int for_write)
 }
 
 
+/* Check whether FNAME has the form "-&nnnn", where N is a non-zero
+ * number.  Returns this number or -1 if it is not the case.  If the
+ * caller wants to use the file descriptor for writing FOR_WRITE shall
+ * be set to 1.  If NOTRANSLATE is set the Windows spefic mapping is
+ * not done. */
+int
+check_special_filename (const char *fname, int for_write, int notranslate)
+{
+  if (allow_special_filenames
+      && fname && *fname == '-' && fname[1] == '&')
+    {
+      int i;
+
+      fname += 2;
+      for (i=0; digitp (fname+i); i++ )
+        ;
+      if (!fname[i])
+        return notranslate? atoi (fname)
+          /**/            : translate_sys2libc_fd_int (atoi (fname), for_write);
+    }
+  return -1;
+}
+
 
 /* Replacement for tmpfile().  This is required because the tmpfile
    function of Windows' runtime library is broken, insecure, ignores
index fef6ba1..a9316d7 100644 (file)
@@ -50,6 +50,7 @@ typedef int gnupg_fd_t;
 void trap_unaligned (void);
 int  disable_core_dumps (void);
 int  enable_core_dumps (void);
+void enable_special_filenames (void);
 const unsigned char *get_session_marker (size_t *rlen);
 unsigned int get_uint_nonce (void);
 /*int check_permissions (const char *path,int extension,int checkonly);*/
@@ -57,6 +58,7 @@ void gnupg_sleep (unsigned int seconds);
 void gnupg_usleep (unsigned int usecs);
 int translate_sys2libc_fd (gnupg_fd_t fd, int for_write);
 int translate_sys2libc_fd_int (int fd, int for_write);
+int check_special_filename (const char *fname, int for_write, int notranslate);
 FILE *gnupg_tmpfile (void);
 void gnupg_reopen_std (const char *pgmname);
 void gnupg_allow_set_foregound_window (pid_t pid);
index 2f82fb0..f7a53e1 100644 (file)
  * libgpg-error version.  Define them here.
  * Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21)
  */
+#if GPG_ERROR_VERSION_NUMBER < 0x011a00 /* 1.26 */
+# define GPG_ERR_UNKNOWN_FLAG     309
+# define GPG_ERR_INV_ORDER       310
+# define GPG_ERR_ALREADY_FETCHED  311
+# define GPG_ERR_TRY_LATER        312
+# define GPG_ERR_SYSTEM_BUG      666
+# define GPG_ERR_DNS_UNKNOWN     711
+# define GPG_ERR_DNS_SECTION     712
+# define GPG_ERR_DNS_ADDRESS     713
+# define GPG_ERR_DNS_NO_QUERY    714
+# define GPG_ERR_DNS_NO_ANSWER   715
+# define GPG_ERR_DNS_CLOSED      716
+# define GPG_ERR_DNS_VERIFY      717
+# define GPG_ERR_DNS_TIMEOUT     718
+#endif
 
 
 /* Hash function used with libksba. */
@@ -210,7 +225,8 @@ int openpgp_oid_is_cv25519 (gcry_mpi_t a);
 const char *openpgp_curve_to_oid (const char *name, unsigned int *r_nbits);
 const char *openpgp_oid_to_curve (const char *oid, int canon);
 const char *openpgp_enum_curves (int *idxp);
-const char *openpgp_is_curve_supported (const char *name, int *r_algo);
+const char *openpgp_is_curve_supported (const char *name,
+                                        int *r_algo, unsigned int *r_nbits);
 
 
 /*-- homedir.c --*/
@@ -247,6 +263,7 @@ char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info);
 #define GNUPG_MODULE_NAME_GPGV          12
 const char *gnupg_module_name (int which);
 void gnupg_module_name_flush_some (void);
+void gnupg_set_builddir (const char *newdir);
 
 
 
@@ -265,7 +282,7 @@ const char *gnupg_messages_locale_name (void);
    logging subsystem. */
 void setup_libgcrypt_logging (void);
 
-/* Print an out of core emssage and die.  */
+/* Print an out of core message and die.  */
 void xoutofcore (void);
 
 /* Same as estream_asprintf but die on memory failure.  */
index 1b77a45..6630610 100644 (file)
@@ -28,7 +28,7 @@ min_automake_version="1.14"
 m4_define([mym4_package],[gnupg])
 m4_define([mym4_major], [2])
 m4_define([mym4_minor], [1])
-m4_define([mym4_micro], [16])
+m4_define([mym4_micro], [17])
 
 # To start a new development series, i.e a new major or minor number
 # you need to mark an arbitrary commit before the first beta release
@@ -102,7 +102,7 @@ have_gnutls=no
 have_sqlite=no
 have_npth=no
 have_libusb=no
-have_adns=no
+have_system_resolver=no
 gnupg_have_ldap="n/a"
 
 use_zip=yes
@@ -110,6 +110,7 @@ use_bzip2=yes
 use_exec=yes
 use_trust_models=yes
 use_tofu=yes
+use_libdns=yes
 card_support=yes
 use_ccid_driver=auto
 dirmngr_auto_start=yes
@@ -269,6 +270,16 @@ if test "$use_trust_models" = no && test "$use_tofu" = yes; then
     AC_MSG_ERROR([both --disable-trust-models and --enable-tofu given])
 fi
 
+AC_MSG_CHECKING([whether to enable libdns])
+AC_ARG_ENABLE(libdns,
+                AC_HELP_STRING([--disable-libdns],
+                               [do not build with libdns support]),
+              use_libdns=$enableval, use_libdns=yes)
+AC_MSG_RESULT($use_libdns)
+if test x"$use_libdns" = xyes ; then
+    AC_DEFINE(USE_LIBDNS, 1, [Build with integrated libdns support])
+fi
+AM_CONDITIONAL(USE_LIBDNS, test "$use_libdns" = yes)
 
 
 #
@@ -995,97 +1006,39 @@ AC_CHECK_FUNC(setsockopt, , AC_CHECK_LIB(socket, setsockopt,
 
 
 #
-# Check for ADNS.
-#
-_cppflags="${CPPFLAGS}"
-_ldflags="${LDFLAGS}"
-AC_ARG_WITH(adns,
-            AC_HELP_STRING([--with-adns=DIR],
-                           [look for the adns library in DIR]),
-            [if test -d "$withval"; then
-               CPPFLAGS="${CPPFLAGS} -I$withval/include"
-               LDFLAGS="${LDFLAGS} -L$withval/lib"
-             fi])
-if test "$with_adns" != "no"; then
-  AC_CHECK_HEADERS(adns.h,AC_CHECK_LIB(adns, adns_init_strcfg,[have_adns=yes]))
-  AC_CHECK_FUNCS(adns_free)
-  AC_MSG_CHECKING([if adns supports adns_if_tormode])
-  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
-    #include <adns.h>
-    adns_initflags flags = adns_if_tormode;
-  ]],[])],[adns_if_tormode=yes],[adns_if_tormode=no])
-  AC_MSG_RESULT($adns_if_tormode)
-  if test x"$adns_if_tormode" = xyes; then
-    AC_DEFINE(HAVE_ADNS_IF_TORMODE,1,[define if adns_if_tormode is available])
-    if test "$show_tor_support" != "no"; then
-      show_tor_support=yes
-    fi
-  fi
-fi
-CPPFLAGS=${_cppflags}
-LDFLAGS=${_ldflags}
-if test "$have_adns" = "yes"; then
-  ADNSLIBS="-ladns"
-fi
-
-
+# Check standard resolver functions.
 #
-# Now try for the resolver functions so we can use DNS for SRV, PA and CERT.
-#
-AC_ARG_ENABLE(dns-srv,
-              AC_HELP_STRING([--disable-dns-srv],
-                             [disable the use of DNS SRV in HKP and HTTP]),
-              use_dns_srv=$enableval,use_dns_srv=yes)
-
-AC_ARG_ENABLE(dns-cert,
-   AC_HELP_STRING([--disable-dns-cert],
-       [disable the use of CERT records in DNS]),
-   use_dns_cert=$enableval,use_dns_cert=yes)
-
-if test x"$use_dns_srv" = xyes || test x"$use_dns_cert" = xyes; then
+if test "$build_dirmngr" = "yes"; then
   _dns_save_libs=$LIBS
   LIBS=""
 
-  if test x"$have_adns" = xyes ; then
-    # We prefer ADNS.
-    DNSLIBS="$ADNSLIBS"
-    AC_DEFINE(USE_ADNS,1,[Use ADNS as resolver library.])
-
-    if test x"$use_dns_srv" = xyes ; then
-        AC_DEFINE(USE_DNS_SRV,1)
-    fi
+  # Find the system resolver which can always be enabled with
+  # the dirmngr option --standard-resolver.
 
-    if test x"$use_dns_cert" = xyes ; then
-        AC_DEFINE(USE_DNS_CERT,1,[define to use DNS CERT])
-    fi
-  else
-    # With no ADNS find the system resolver.
+  # the double underscore thing is a glibc-ism?
+  AC_SEARCH_LIBS(res_query,resolv bind,,
+                 AC_SEARCH_LIBS(__res_query,resolv bind,,have_resolver=no))
+  AC_SEARCH_LIBS(dn_expand,resolv bind,,
+                 AC_SEARCH_LIBS(__dn_expand,resolv bind,,have_resolver=no))
 
-    # the double underscore thing is a glibc-ism?
-    AC_SEARCH_LIBS(res_query,resolv bind,,
-                   AC_SEARCH_LIBS(__res_query,resolv bind,,have_resolver=no))
-    AC_SEARCH_LIBS(dn_expand,resolv bind,,
-                   AC_SEARCH_LIBS(__dn_expand,resolv bind,,have_resolver=no))
+  # macOS renames dn_skipname into res_9_dn_skipname in <resolv.h>,
+  # and for some reason fools us into believing we don't need
+  # -lresolv even if we do.  Since the test program checking for the
+  # symbol does not include <resolv.h>, we need to check for the
+  # renamed symbol explicitly.
+  AC_SEARCH_LIBS(res_9_dn_skipname,resolv bind,,
+      AC_SEARCH_LIBS(dn_skipname,resolv bind,,
+          AC_SEARCH_LIBS(__dn_skipname,resolv bind,,have_resolver=no)))
 
-    # macOS renames dn_skipname into res_9_dn_skipname in <resolv.h>,
-    # and for some reason fools us into believing we don't need
-    # -lresolv even if we do.  Since the test program checking for the
-    # symbol does not include <resolv.h>, we need to check for the
-    # renamed symbol explicitly.
-    AC_SEARCH_LIBS(res_9_dn_skipname,resolv bind,,
-        AC_SEARCH_LIBS(dn_skipname,resolv bind,,
-            AC_SEARCH_LIBS(__dn_skipname,resolv bind,,have_resolver=no)))
-
-    if test x"$have_resolver" != xno ; then
+  if test x"$have_resolver" != xno ; then
 
       # Make sure that the BIND 4 resolver interface is workable before
       # enabling any code that calls it.  At some point I'll rewrite the
       # code to use the BIND 8 resolver API.
-      # We might also want to use adns instead.  Problem with ADNS is that
-      # it does not support v6.
+      # We might also want to use libdns instead.
 
-      AC_MSG_CHECKING([whether the resolver is usable])
-      AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <sys/types.h>
+    AC_MSG_CHECKING([whether the resolver is usable])
+    AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <sys/types.h>
 #include <netinet/in.h>
 #include <arpa/nameser.h>
 #include <resolv.h>]],
@@ -1094,15 +1047,15 @@ if test x"$use_dns_srv" = xyes || test x"$use_dns_cert" = xyes; then
   dn_skipname(0,0);
   dn_expand(0,0,0,0,0);
 ]])],have_resolver=yes,have_resolver=no)
-      AC_MSG_RESULT($have_resolver)
+    AC_MSG_RESULT($have_resolver)
 
-      # This is Apple-specific and somewhat bizarre as they changed the
-      # define in bind 8 for some reason.
+    # This is Apple-specific and somewhat bizarre as they changed the
+    # define in bind 8 for some reason.
 
-      if test x"$have_resolver" != xyes ; then
-         AC_MSG_CHECKING(
-             [whether I can make the resolver usable with BIND_8_COMPAT])
-        AC_LINK_IFELSE([AC_LANG_PROGRAM([[#define BIND_8_COMPAT
+    if test x"$have_resolver" != xyes ; then
+      AC_MSG_CHECKING(
+           [whether I can make the resolver usable with BIND_8_COMPAT])
+      AC_LINK_IFELSE([AC_LANG_PROGRAM([[#define BIND_8_COMPAT
 #include <sys/types.h>
 #include <netinet/in.h>
 #include <arpa/nameser.h>
@@ -1111,27 +1064,33 @@ if test x"$use_dns_srv" = xyes || test x"$use_dns_cert" = xyes; then
   res_query("foo.bar",C_IN,T_A,answer,PACKETSZ);
   dn_skipname(0,0); dn_expand(0,0,0,0,0);
 ]])],[have_resolver=yes ; need_compat=yes])
-         AC_MSG_RESULT($have_resolver)
-      fi
+      AC_MSG_RESULT($have_resolver)
     fi
+  fi
 
-    if test x"$have_resolver" = xyes ; then
-      DNSLIBS=$LIBS
-
-      if test x"$use_dns_srv" = xyes ; then
-         AC_DEFINE(USE_DNS_SRV,1,[define to use DNS SRV])
-      fi
-
-      if test x"$use_dns_cert" = xyes ; then
-        AC_DEFINE(USE_DNS_CERT,1,[define to use DNS CERT])
-      fi
+  if test x"$have_resolver" = xyes ; then
+    AC_DEFINE(HAVE_SYSTEM_RESOLVER,1,[The system's resolver is usable.])
+    DNSLIBS="$DNSLIBS $LIBS"
+    if test x"$need_compat" = xyes ; then
+      AC_DEFINE(BIND_8_COMPAT,1,[an Apple OSXism])
+    fi
+    if test "$use_libdns" = yes; then
+     show_tor_support=yes
+    fi
+  elif test "$use_libdns" = yes; then
+    show_tor_support=yes
+  else
+    AC_MSG_WARN([[
+***
+*** The system's DNS resolver is not usable.
+*** Dirmngr functionality is limited.
+***]])
+    show_tor_support="${show_tor_support} (no system resolver)"
+  fi
 
-      if test x"$need_compat" = xyes ; then
-        AC_DEFINE(BIND_8_COMPAT,1,[an Apple OSXism])
-      fi
-    else
-      use_dns_srv=no
-      use_dns_cert=no
+  if test "$have_w32_system" = yes; then
+    if test "$use_libdns" = yes; then
+      DNSLIBS="$DNSLIBS -liphlpapi"
     fi
   fi
 
@@ -1140,8 +1099,6 @@ fi
 
 AC_SUBST(DNSLIBS)
 
-AM_CONDITIONAL(USE_DNS_SRV, test x"$use_dns_srv" = xyes)
-
 
 #
 # Check for LDAP
@@ -1575,6 +1532,7 @@ AC_SUBST(W32SOCKLIBS)
 #
 # Setup gcc specific options
 #
+USE_C99_CFLAGS=
 AC_MSG_NOTICE([checking for cc features])
 if test "$GCC" = yes; then
     mycflags=
@@ -1642,8 +1600,14 @@ if test "$GCC" = yes; then
     fi
 
     CFLAGS="$mycflags $mycflags_save"
+    if test "$use_libdns" = yes; then
+       # dirmngr/dns.{c,h} require C99 and GNU extensions.  */
+       USE_C99_CFLAGS="-std=gnu99"
+    fi
 fi
 
+AC_SUBST(USE_C99_CFLAGS)
+
 
 #
 # This is handy for debugging so the compiler doesn't rearrange
@@ -1930,6 +1894,7 @@ tests/Makefile
 tests/gpgscm/Makefile
 tests/openpgp/Makefile
 tests/migrations/Makefile
+tests/gpgme/Makefile
 tests/pkits/Makefile
 g10/gpg.w32-manifest
 ])
@@ -1963,7 +1928,6 @@ echo "
         Dirmngr auto start:  $dirmngr_auto_start
         Readline support:    $gnupg_cv_have_readline
         LDAP support:        $gnupg_have_ldap
-        DNS SRV support:     $use_dns_srv
         TLS support:         $use_tls_library
         TOFU support:        $use_tofu
         Tor support:         $show_tor_support
index 2a18a50..d3f89bc 100644 (file)
@@ -35,7 +35,8 @@ AM_CPPFLAGS = -I$(top_srcdir)/common
 
 include $(top_srcdir)/am/cmacros.am
 
-AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS)     \
+AM_CFLAGS = $(USE_C99_CFLAGS) \
+            $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS)     \
             $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(NTBTLS_CFLAGS)                \
             $(LIBGNUTLS_CFLAGS)
 
@@ -64,6 +65,10 @@ dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \
        ks-action.c ks-action.h ks-engine.h \
        ks-engine-hkp.c ks-engine-http.c ks-engine-finger.c ks-engine-kdns.c
 
+if USE_LIBDNS
+dirmngr_SOURCES += dns.c dns.h
+endif
+
 if USE_LDAP
 dirmngr_SOURCES += ldapserver.h ldapserver.c ldap.c w32-ldap-help.h \
                    ldap-wrapper.h ldap-parse-uri.c ldap-parse-uri.h \
@@ -103,6 +108,9 @@ dirmngr_client_LDFLAGS = $(extra_bin_ldflags)
 
 
 t_common_src = t-support.h
+if USE_LIBDNS
+t_common_src += dns.c dns.h
+endif
 t_common_ldadd = $(libcommon) $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \
                  $(GPG_ERROR_LIBS) $(NETLIBS) \
                  $(NTBTLS_LIBS) $(LIBGNUTLS_LIBS) \
@@ -130,8 +138,8 @@ endif
 
 
 # http tests
-t_http_SOURCES = t-http.c http.c dns-stuff.c
-t_http_CFLAGS  = -DWITHOUT_NPTH=1 \
+t_http_SOURCES = $(t_common_src) t-http.c http.c dns-stuff.c
+t_http_CFLAGS  = -DWITHOUT_NPTH=1  $(USE_C99_CFLAGS) \
                 $(LIBGCRYPT_CFLAGS) $(NTBTLS_CFLAGS) $(LIBGNUTLS_CFLAGS) \
                  $(GPG_ERROR_CFLAGS)
 t_http_LDADD   = $(t_common_ldadd) \
@@ -141,13 +149,13 @@ t_ldap_parse_uri_SOURCES = \
        t-ldap-parse-uri.c ldap-parse-uri.c ldap-parse-uri.h \
         http.c dns-stuff.c \
         $(ldap_url) $(t_common_src)
-t_ldap_parse_uri_CFLAGS = -DWITHOUT_NPTH=1 \
+t_ldap_parse_uri_CFLAGS = -DWITHOUT_NPTH=1  $(USE_C99_CFLAGS) \
                          $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS)
 t_ldap_parse_uri_LDADD = $(ldaplibs) $(t_common_ldadd) $(DNSLIBS)
 
-t_dns_stuff_CFLAGS = -DWITHOUT_NPTH=1 \
+t_dns_stuff_CFLAGS = -DWITHOUT_NPTH=1  $(USE_C99_CFLAGS) \
                     $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS)
-t_dns_stuff_SOURCES = t-dns-stuff.c dns-stuff.c
+t_dns_stuff_SOURCES = $(t_common_src) t-dns-stuff.c dns-stuff.c
 t_dns_stuff_LDADD   = $(t_common_ldadd) $(DNSLIBS)
 
 $(PROGRAMS) : $(libcommon) $(libcommonpth)
index 7214d49..5ee589e 100644 (file)
@@ -140,6 +140,9 @@ enum cmd_and_opt_values {
   oKeyServer,
   oNameServer,
   oDisableCheckOwnSocket,
+  oStandardResolver,
+  oRecursiveResolver,
+  oResolverTimeout,
   aTest
 };
 
@@ -236,6 +239,9 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_s (oHTTPWrapperProgram, "http-wrapper-program", "@"),
   ARGPARSE_s_n (oHonorHTTPProxy, "honor-http-proxy", "@"),
   ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"),
+  ARGPARSE_s_n (oStandardResolver, "standard-resolver", "@"),
+  ARGPARSE_s_n (oRecursiveResolver, "recursive-resolver", "@"),
+  ARGPARSE_s_i (oResolverTimeout, "resolver-timeout", "@"),
 
   ARGPARSE_group (302,N_("@\n(See the \"info\" manual for a complete listing "
                          "of all commands and options)\n")),
@@ -253,6 +259,8 @@ static struct debug_flags_s debug_flags [] =
     { DBG_MEMSTAT_VALUE, "memstat" },
     { DBG_HASHING_VALUE, "hashing" },
     { DBG_IPC_VALUE    , "ipc"     },
+    { DBG_DNS_VALUE    , "dns"     },
+    { DBG_NETWORK_VALUE, "network" },
     { DBG_LOOKUP_VALUE , "lookup"  },
     { 77, NULL } /* 77 := Do not exit on "help" or "?".  */
   };
@@ -543,6 +551,8 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
       FREE_STRLIST (opt.keyserver);
       /* Note: We do not allow resetting of opt.use_tor at runtime.  */
       disable_check_own_socket = 0;
+      enable_standard_resolver (0);
+      set_dns_timeout (0);
       return 1;
     }
 
@@ -617,6 +627,9 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
 
     case oUseTor: opt.use_tor = 1; break;
 
+    case oStandardResolver: enable_standard_resolver (1); break;
+    case oRecursiveResolver: enable_recursive_resolver (1); break;
+
     case oKeyServer:
       if (*pargs->r.ret_str)
         add_to_strlist (&opt.keyserver, pargs->r.ret_str);
@@ -626,10 +639,16 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
       set_dns_nameserver (pargs->r.ret_str);
       break;
 
+    case oResolverTimeout:
+      set_dns_timeout (pargs->r.ret_int);
+      break;
+
     default:
       return 0; /* Not handled. */
     }
 
+  set_dns_verbose (opt.verbose, !!DBG_DNS);
+
   return 1; /* Handled. */
 }
 
@@ -989,9 +1008,6 @@ main (int argc, char **argv)
       thread_init ();
       cert_cache_init ();
       crl_cache_init ();
-#if USE_LDAP
-      ldap_wrapper_launch_thread ();
-#endif /*USE_LDAP*/
       http_register_netactivity_cb (netactivity_action);
       start_command_handler (ASSUAN_INVALID_FD);
       shutdown_reaper ();
@@ -1027,12 +1043,8 @@ main (int argc, char **argv)
       thread_init ();
       cert_cache_init ();
       crl_cache_init ();
-#if USE_LDAP
-      ldap_wrapper_launch_thread ();
-#endif /*USE_LDAP*/
       http_register_netactivity_cb (netactivity_action);
       handle_connections (3);
-      assuan_sock_close (3);
       shutdown_reaper ();
     }
 #endif /*HAVE_W32_SYSTEM*/
@@ -1238,12 +1250,8 @@ main (int argc, char **argv)
       thread_init ();
       cert_cache_init ();
       crl_cache_init ();
-#if USE_LDAP
-      ldap_wrapper_launch_thread ();
-#endif /*USE_LDAP*/
       http_register_netactivity_cb (netactivity_action);
       handle_connections (fd);
-      assuan_sock_close (fd);
       shutdown_reaper ();
     }
   else if (cmd == aListCRLs)
@@ -1251,9 +1259,6 @@ main (int argc, char **argv)
       /* Just list the CRL cache and exit. */
       if (argc)
         wrong_args ("--list-crls");
-#if USE_LDAP
-      ldap_wrapper_launch_thread ();
-#endif /*USE_LDAP*/
       crl_cache_init ();
       crl_cache_list (es_stdout);
     }
@@ -1267,9 +1272,6 @@ main (int argc, char **argv)
       thread_init ();
       cert_cache_init ();
       crl_cache_init ();
-#if USE_LDAP
-      ldap_wrapper_launch_thread ();
-#endif /*USE_LDAP*/
       if (!argc)
         rc = crl_cache_load (&ctrlbuf, NULL);
       else
@@ -1293,9 +1295,6 @@ main (int argc, char **argv)
       thread_init ();
       cert_cache_init ();
       crl_cache_init ();
-#if USE_LDAP
-      ldap_wrapper_launch_thread ();
-#endif /*USE_LDAP*/
       rc = crl_fetch (&ctrlbuf, argv[0], &reader);
       if (rc)
         log_error (_("fetching CRL from '%s' failed: %s\n"),
@@ -1384,6 +1383,9 @@ main (int argc, char **argv)
 
       es_printf ("use-tor:%lu:\n", flags | GC_OPT_FLAG_NONE);
       es_printf ("keyserver:%lu:\n", flags | GC_OPT_FLAG_NONE);
+      es_printf ("nameserver:%lu:\n", flags | GC_OPT_FLAG_NONE);
+      es_printf ("resolver-timeout:%lu:%u\n",
+                 flags | GC_OPT_FLAG_DEFAULT, 0);
     }
   cleanup ();
   return !!rc;
@@ -1395,6 +1397,7 @@ cleanup (void)
 {
   crl_cache_deinit ();
   cert_cache_deinit (1);
+  reload_dns_stuff (1);
 
 #if USE_LDAP
   ldapserver_list_free (opt.ldapservers);
@@ -1701,6 +1704,7 @@ dirmngr_sighup_action (void)
   crl_cache_deinit ();
   cert_cache_init ();
   crl_cache_init ();
+  reload_dns_stuff (0);
 }
 
 
@@ -1943,7 +1947,8 @@ my_inotify_is_name (int fd, const char *name)
 #endif /*HAVE_INOTIFY_INIT*/
 
 
-/* Main loop in daemon mode. */
+/* Main loop in daemon mode.  Note that LISTEN_FD will be owned by
+ * this function. */
 static void
 handle_connections (assuan_fd_t listen_fd)
 {
@@ -1960,9 +1965,7 @@ handle_connections (assuan_fd_t listen_fd)
   struct timespec curtime;
   struct timespec timeout;
   int saved_errno;
-#ifdef HAVE_INOTIFY_INIT
-  int my_inotify_fd;
-#endif /*HAVE_INOTIFY_INIT*/
+  int my_inotify_fd = -1;
 
   npth_attr_init (&tattr);
   npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
@@ -2006,14 +2009,12 @@ handle_connections (assuan_fd_t listen_fd)
   FD_ZERO (&fdset);
   FD_SET (FD2INT (listen_fd), &fdset);
   nfd = FD2INT (listen_fd);
-#ifdef HAVE_INOTIFY_INIT
   if (my_inotify_fd != -1)
     {
       FD_SET (my_inotify_fd, &fdset);
       if (my_inotify_fd > nfd)
         nfd = my_inotify_fd;
     }
-#endif /*HAVE_INOTIFY_INIT*/
 
   npth_clock_gettime (&abstime);
   abstime.tv_sec += TIMERTICK_INTERVAL;
@@ -2028,8 +2029,21 @@ handle_connections (assuan_fd_t listen_fd)
             break; /* ready */
 
           /* Do not accept new connections but keep on running the
-             loop to cope with the timer events.  */
+           * loop to cope with the timer events.
+           *
+           * Note that we do not close the listening socket because a
+           * client trying to connect to that socket would instead
+           * restart a new dirmngr instance - which is unlikely the
+           * intention of a shutdown. */
+          /* assuan_sock_close (listen_fd); */
+          /* listen_fd = -1; */
           FD_ZERO (&fdset);
+          nfd = -1;
+          if (my_inotify_fd != -1)
+            {
+              FD_SET (my_inotify_fd, &fdset);
+              nfd = my_inotify_fd;
+            }
        }
 
       /* Take a copy of the fdset.  */
@@ -2125,6 +2139,8 @@ handle_connections (assuan_fd_t listen_fd)
     close (my_inotify_fd);
 #endif /*HAVE_INOTIFY_INIT*/
   npth_attr_destroy (&tattr);
+  if (listen_fd != -1)
+    assuan_sock_close (fd);
   cleanup ();
   log_info ("%s %s stopped\n", strusage(11), strusage(13));
 }
index da1c4be..9a87878 100644 (file)
@@ -136,19 +136,23 @@ struct
 
 #define DBG_X509_VALUE    1    /* debug x.509 parsing */
 #define DBG_CRYPTO_VALUE  4    /* debug low level crypto */
+#define DBG_DNS_VALUE     16    /* debug DNS calls.  */
 #define DBG_MEMORY_VALUE  32   /* debug memory allocation stuff */
 #define DBG_CACHE_VALUE   64   /* debug the caching */
 #define DBG_MEMSTAT_VALUE 128  /* show memory statistics */
 #define DBG_HASHING_VALUE 512  /* debug hashing operations */
 #define DBG_IPC_VALUE     1024  /* debug assuan communication */
+#define DBG_NETWORK_VALUE 2048  /* debug network I/O.  */
 #define DBG_LOOKUP_VALUE  8192  /* debug lookup details */
 
 #define DBG_X509    (opt.debug & DBG_X509_VALUE)
 #define DBG_CRYPTO  (opt.debug & DBG_CRYPTO_VALUE)
+#define DBG_DNS     (opt.debug & DBG_DNS_VALUE)
 #define DBG_MEMORY  (opt.debug & DBG_MEMORY_VALUE)
 #define DBG_CACHE   (opt.debug & DBG_CACHE_VALUE)
 #define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
 #define DBG_IPC     (opt.debug & DBG_IPC_VALUE)
+#define DBG_NETWORK (opt.debug & DBG_NETWORK_VALUE)
 #define DBG_LOOKUP  (opt.debug & DBG_LOOKUP_VALUE)
 
 /* A simple list of certificate references. */
index 6849af4..491fcce 100644 (file)
@@ -1,6 +1,6 @@
 /* dns-stuff.c - DNS related code including CERT RR (rfc-4398)
  * Copyright (C) 2003, 2005, 2006, 2009 Free Software Foundation, Inc.
- * Copyright (C) 2005, 2006, 2009, 2015 Werner Koch
+ * Copyright (C) 2005, 2006, 2009, 2015. 2016 Werner Koch
  *
  * This file is part of GnuPG.
  *
 #include <config.h>
 #include <sys/types.h>
 #ifdef HAVE_W32_SYSTEM
+# define WIN32_LEAN_AND_MEAN
 # ifdef HAVE_WINSOCK2_H
 #  include <winsock2.h>
 # endif
 # include <windows.h>
+# include <iphlpapi.h>
 #else
-# include <netinet/in.h>
-# include <arpa/nameser.h>
-# include <resolv.h>
+# if HAVE_SYSTEM_RESOLVER
+#  include <netinet/in.h>
+#  include <arpa/nameser.h>
+#  include <resolv.h>
+# endif
 # include <netdb.h>
 #endif
 #include <string.h>
 #include <unistd.h>
-#ifdef USE_ADNS
-# include <adns.h>
-#endif
 
-#if !defined(HAVE_GETADDRINFO) && !defined(USE_ADNS)
-# error Either getaddrinfo or the ADNS library is required.
+
+/* William Ahern's DNS library, included as a source copy.  */
+#ifdef USE_LIBDNS
+# include "dns.h"
 #endif
 
+/* dns.c has a dns_p_free but it is not exported.  We use our own
+ * wrapper here so that we do not accidentally use xfree which would
+ * be wrong for dns.c allocated data.  */
+#define dns_free(a)  free ((a))
+
+
 #ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth.  */
 # undef USE_NPTH
 #endif
@@ -58,6 +67,7 @@
 # include <npth.h>
 #endif
 
+#include "./dirmngr-err.h"
 #include "util.h"
 #include "host2net.h"
 #include "dns-stuff.h"
 # define AI_ADDRCONFIG 0
 #endif
 
-/* Provide a replacement function for older ADNS versions.  */
-#ifndef HAVE_ADNS_FREE
-# define adns_free(a) free ((a))
-#endif
-
 /* Not every installation has gotten around to supporting SRVs or
    CERTs yet... */
 #ifndef T_SRV
 # define T_CERT 37
 #endif
 
-/* ADNS has no support for CERT yet. */
-#define my_adns_r_cert 37
+/* The standard SOCKS and TOR ports.  */
+#define SOCKS_PORT 1080
+#define TOR_PORT   9050
+#define TOR_PORT2  9150   /* (Used by the Tor browser) */
 
 
-/* The default nameserver used with ADNS in Tor mode.  */
+/* The default nameserver used in Tor mode.  */
 #define DEFAULT_NAMESERVER "8.8.8.8"
 
+/* The default timeout in seconds for libdns requests.  */
+#define DEFAULT_TIMEOUT 30
+
+
+/* Two flags to enable verbose and debug mode.  */
+static int opt_verbose;
+static int opt_debug;
+
+/* The timeout in seconds for libdns requests.  */
+static int opt_timeout;
+
+/* If set force the use of the standard resolver.  */
+static int standard_resolver;
+
+/* If set use recursive resolver when available. */
+static int recursive_resolver;
 
 /* If set Tor mode shall be used.  */
 static int tor_mode;
@@ -109,35 +132,113 @@ static int tor_mode;
   (40 should be sufficient for v6 but we add some extra for a scope.) */
 static char tor_nameserver[40+20];
 
-/* A string to hold the credentials presented to Tor.  */
-#ifdef USE_ADNS
-static char tor_credentials[50];
+/* Two strings to hold the credentials presented to Tor.  */
+static char tor_socks_user[30];
+static char tor_socks_password[20];
+
+
+#ifdef USE_LIBDNS
+/* Libdns gobal data.  */
+struct libdns_s
+{
+  struct dns_resolv_conf *resolv_conf;
+  struct dns_hosts *hosts;
+  struct dns_hints *hints;
+
+  struct sockaddr_storage socks_host;
+} libdns;
+
+/* If this flag is set, libdns shall be reinited for the next use.  */
+static int libdns_reinit_pending;
+
+/* The Tor port to be used.  */
+static int libdns_tor_port;
+
+#endif /*USE_LIBDNS*/
+
+
+/* Calling this function with YES set to True forces the use of the
+ * standard resolver even if dirmngr has been built with support for
+ * an alternative resolver.  */
+void
+enable_standard_resolver (int yes)
+{
+  standard_resolver = yes;
+}
+
+
+/* Return true if the standard resolver is used.  */
+int
+standard_resolver_p (void)
+{
+  return standard_resolver;
+}
+
+
+/* Calling this function with YES switches libdns into recursive mode.
+ * It has no effect on the standard resolver.  */
+void
+enable_recursive_resolver (int yes)
+{
+  recursive_resolver = yes;
+  libdns_reinit_pending = 1;
+}
+
+
+/* Return true iff the recursive resolver is used.  */
+int
+recursive_resolver_p (void)
+{
+#if USE_LIBDNS
+  return !standard_resolver && recursive_resolver;
+#else
+  return 0;
 #endif
+}
+
 
 /* Sets the module in Tor mode.  Returns 0 is this is possible or an
    error code.  */
 gpg_error_t
 enable_dns_tormode (int new_circuit)
 {
-  (void) new_circuit;
-
-#if defined(USE_DNS_CERT) && defined(USE_ADNS)
-# if HAVE_ADNS_IF_TORMODE
-   if (!*tor_credentials || new_circuit)
-     {
-       static unsigned int counter;
-
-       gpgrt_snprintf (tor_credentials, sizeof tor_credentials,
-                       "dirmngr-%lu:p%u",
-                       (unsigned long)getpid (), counter);
-       counter++;
-     }
-   tor_mode = 1;
-   return 0;
-# endif
-#endif
+  if (!*tor_socks_user || new_circuit)
+    {
+      static unsigned int counter;
 
-  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+      gpgrt_snprintf (tor_socks_user, sizeof tor_socks_user,
+                      "dirmngr-%lu", (unsigned long)getpid ());
+      gpgrt_snprintf (tor_socks_password, sizeof tor_socks_password,
+                      "p%u", counter);
+      counter++;
+    }
+  tor_mode = 1;
+  return 0;
+}
+
+
+/* Set verbosity and debug mode for this module. */
+void
+set_dns_verbose (int verbose, int debug)
+{
+  opt_verbose = verbose;
+  opt_debug = debug;
+}
+
+
+/* Set the timeout for libdns requests to SECONDS.  A value of 0 sets
+ * the default timeout and values are capped at 10 minutes.  */
+void
+set_dns_timeout (int seconds)
+{
+  if (!seconds)
+    seconds = DEFAULT_TIMEOUT;
+  else if (seconds < 1)
+    seconds = 1;
+  else if (seconds > 600)
+    seconds = 600;
+
+  opt_timeout = seconds;
 }
 
 
@@ -150,6 +251,8 @@ set_dns_nameserver (const char *ipaddr)
   strncpy (tor_nameserver, ipaddr? ipaddr : DEFAULT_NAMESERVER,
            sizeof tor_nameserver -1);
   tor_nameserver[sizeof tor_nameserver -1] = 0;
+  libdns_reinit_pending = 1;
+  libdns_tor_port = 0;  /* Start again with the default port.  */
 }
 
 
@@ -166,6 +269,25 @@ free_dns_addrinfo (dns_addrinfo_t ai)
 }
 
 
+#ifndef HAVE_W32_SYSTEM
+/* Return H_ERRNO mapped to a gpg-error code.  Will never return 0. */
+static gpg_error_t
+get_h_errno_as_gpg_error (void)
+{
+  gpg_err_code_t ec;
+
+  switch (h_errno)
+    {
+    case HOST_NOT_FOUND: ec = GPG_ERR_UNKNOWN_HOST; break;
+    case TRY_AGAIN:      ec = GPG_ERR_TRY_LATER; break;
+    case NO_RECOVERY:    ec = GPG_ERR_SERVER_FAILED; break;
+    case NO_DATA:        ec = GPG_ERR_NO_DATA; break;
+    default:             ec = GPG_ERR_UNKNOWN_ERRNO; break;
+    }
+  return gpg_error (ec);
+}
+#endif /*!HAVE_W32_SYSTEM*/
+
 static gpg_error_t
 map_eai_to_gpg_error (int ec)
 {
@@ -195,162 +317,439 @@ map_eai_to_gpg_error (int ec)
   return err;
 }
 
-#ifdef USE_ADNS
+
+#ifdef USE_LIBDNS
 static gpg_error_t
-map_adns_status_to_gpg_error (adns_status status)
+libdns_error_to_gpg_error (int serr)
 {
   gpg_err_code_t ec;
 
-  switch (status)
+  switch (serr)
     {
-    /* case adns_s_netunreach: ec = GPG_ERR_ENETUNREACH; break; */
-    default: ec = GPG_ERR_GENERAL; break;
+    case 0: ec = 0; break;
+
+    case DNS_ENOBUFS:  ec = GPG_ERR_BUFFER_TOO_SHORT; break;
+    case DNS_EILLEGAL: ec = GPG_ERR_INV_OBJ; break;
+    case DNS_EORDER:   ec = GPG_ERR_INV_ORDER; break;
+    case DNS_ESECTION: ec = GPG_ERR_DNS_SECTION; break;
+    case DNS_EUNKNOWN: ec = GPG_ERR_DNS_UNKNOWN; break;
+    case DNS_EADDRESS: ec = GPG_ERR_DNS_ADDRESS; break;
+    case DNS_ENOQUERY: ec = GPG_ERR_DNS_NO_QUERY; break;
+    case DNS_ENOANSWER:ec = GPG_ERR_DNS_NO_ANSWER; break;
+    case DNS_EFETCHED: ec = GPG_ERR_ALREADY_FETCHED; break;
+    case DNS_ESERVICE: ec = GPG_ERR_NOT_SUPPORTED; break;
+    case DNS_ENONAME:  ec = GPG_ERR_NO_NAME; break;
+    case DNS_EFAIL:    ec = GPG_ERR_SERVER_FAILED; break;
+    case DNS_ECONNFIN: ec = GPG_ERR_DNS_CLOSED; break;
+    case DNS_EVERIFY:  ec = GPG_ERR_DNS_VERIFY; break;
+
+    default:
+      if (serr >= 0)
+        ec = gpg_err_code_from_errno (serr);
+      else
+        ec = GPG_ERR_DNS_UNKNOWN;
+      break;
     }
   return gpg_error (ec);
 }
-#endif /*USE_ADNS*/
+#endif /*USE_LIBDNS*/
 
 
-#ifdef USE_ADNS
-/* Init ADNS and store the new state at R_STATE.  Returns 0 on
-   success; prints an error message and returns an error code on
-   failure.  */
+#ifdef USE_LIBDNS
+/* Initialize libdns.  Returns 0 on success; prints a diagnostic and
+ * returns an error code on failure.  */
 static gpg_error_t
-my_adns_init (adns_state *r_state)
+libdns_init (void)
 {
-  gpg_error_t err = 0;
-  int ret;
+  gpg_error_t err;
+  struct libdns_s ld;
+  int derr;
+  char *cfgstr = NULL;
 
-  if (tor_mode)
+  if (libdns.resolv_conf)
+    return 0; /* Already initialized.  */
+
+  memset (&ld, 0, sizeof ld);
+
+  ld.resolv_conf = dns_resconf_open (&derr);
+  if (!ld.resolv_conf)
     {
-      char *cfgstr;
+      err = libdns_error_to_gpg_error (derr);
+      log_error ("failed to allocate DNS resconf object: %s\n",
+                 gpg_strerror (err));
+      goto leave;
+    }
 
+  if (tor_mode)
+    {
       if (!*tor_nameserver)
         set_dns_nameserver (NULL);
 
-      cfgstr = xtryasprintf ("nameserver %s\n"
-                             "options adns_tormode adns_sockscred:%s",
-                             tor_nameserver, tor_credentials);
+      if (!libdns_tor_port)
+        libdns_tor_port = TOR_PORT;
+
+      cfgstr = xtryasprintf ("[%s]:53", tor_nameserver);
+      if (!cfgstr)
+        err = gpg_error_from_syserror ();
+      else
+        err = libdns_error_to_gpg_error
+          (dns_resconf_pton (&ld.resolv_conf->nameserver[0], cfgstr));
+      if (err)
+        log_error ("failed to set nameserver '%s': %s\n",
+                   cfgstr, gpg_strerror (err));
+      if (err)
+        goto leave;
+
+      ld.resolv_conf->options.tcp = DNS_RESCONF_TCP_SOCKS;
+
+      xfree (cfgstr);
+      cfgstr = xtryasprintf ("[%s]:%d", "127.0.0.1", libdns_tor_port);
       if (!cfgstr)
         err = gpg_error_from_syserror ();
       else
+        err = libdns_error_to_gpg_error
+          (dns_resconf_pton (&ld.socks_host, cfgstr));
+      if (err)
         {
-          ret = adns_init_strcfg (r_state, adns_if_debug /*adns_if_noerrprint*/, NULL, cfgstr);
-          if (ret)
-            err = gpg_error_from_errno (ret);
-          xfree (cfgstr);
+          log_error ("failed to set socks server '%s': %s\n",
+                     cfgstr, gpg_strerror (err));
+          goto leave;
+        }
+    }
+  else
+    {
+#ifdef HAVE_W32_SYSTEM
+      ULONG ninfo_len;
+      PFIXED_INFO ninfo;
+      PIP_ADDR_STRING pip;
+      int idx;
+
+      ninfo_len = 2048;
+      ninfo = xtrymalloc (ninfo_len);
+      if (!ninfo)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+
+      if (GetNetworkParams (ninfo, &ninfo_len))
+        {
+          log_error ("GetNetworkParms failed: %s\n", w32_strerror (-1));
+          err = gpg_error (GPG_ERR_GENERAL);
+          xfree (ninfo);
+          goto leave;
+        }
+
+      for (idx=0, pip = &(ninfo->DnsServerList);
+           pip && idx < DIM (ld.resolv_conf->nameserver);
+           pip = pip->Next)
+        {
+          if (opt_debug)
+            log_debug ("dns: dnsserver[%d] '%s'\n", idx, pip->IpAddress.String);
+          err = libdns_error_to_gpg_error
+            (dns_resconf_pton (&ld.resolv_conf->nameserver[idx],
+                               pip->IpAddress.String));
+          if (err)
+            log_error ("failed to set nameserver[%d] '%s': %s\n",
+                       idx, pip->IpAddress.String, gpg_strerror (err));
+          else
+            idx++;
+        }
+      xfree (ninfo);
+
+#else /* Unix */
+      const char *fname;
+
+      fname = "/etc/resolv.conf";
+      err = libdns_error_to_gpg_error
+        (dns_resconf_loadpath (ld.resolv_conf, fname));
+      if (err)
+        {
+          log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err));
+          goto leave;
+        }
+
+      fname = "/etc/nsswitch.conf";
+      err = libdns_error_to_gpg_error
+        (dns_nssconf_loadpath (ld.resolv_conf, fname));
+      if (err)
+        {
+          log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err));
+          goto leave;
         }
+
+#endif /* Unix */
+    }
+
+  ld.hosts = dns_hosts_open (&derr);
+  if (!ld.hosts)
+    {
+      log_error ("failed to load hosts file: %s\n", gpg_strerror (err));
+      err = libdns_error_to_gpg_error (derr);
+      goto leave;
+    }
+
+  /* dns_hints_local for stub mode, dns_hints_root for recursive.  */
+  ld.hints = (recursive_resolver
+              ? dns_hints_root  (ld.resolv_conf, &derr)
+              : dns_hints_local (ld.resolv_conf, &derr));
+  if (!ld.hints)
+    {
+      log_error ("failed to load DNS hints: %s\n", gpg_strerror (err));
+      err = libdns_error_to_gpg_error (derr);
+      goto leave;
+    }
+
+  /* All fine.  Make the data global.  */
+  libdns = ld;
+
+ leave:
+  xfree (cfgstr);
+  return err;
+}
+#endif /*USE_LIBDNS*/
+
+
+#ifdef USE_LIBDNS
+/* Deinitialize libdns.  */
+static void
+libdns_deinit (void)
+{
+  struct libdns_s ld;
+
+  if (!libdns.resolv_conf)
+    return; /* Not initialized.  */
+
+  ld = libdns;
+  memset (&libdns, 0, sizeof libdns);
+  dns_hints_close (ld.hints);
+  dns_hosts_close (ld.hosts);
+  dns_resconf_close (ld.resolv_conf);
+}
+#endif /*USE_LIBDNS*/
+
+
+/* SIGHUP action handler for this module.  With FORCE set objects are
+ * all immediately released. */
+void
+reload_dns_stuff (int force)
+{
+  if (force)
+    {
+#ifdef USE_LIBDNS
+      libdns_deinit ();
+#endif
+      libdns_reinit_pending = 0;
     }
   else
+    libdns_reinit_pending = 1;
+}
+
+
+#ifdef USE_LIBDNS
+/*
+ * Initialize libdns if needed and open a dns_resolver context.
+ * Returns 0 on success and stores the new context at R_RES.  On
+ * failure an error code is returned and NULL stored at R_RES.
+ */
+static gpg_error_t
+libdns_res_open (struct dns_resolver **r_res)
+{
+  gpg_error_t err;
+  struct dns_resolver *res;
+  int derr;
+
+  *r_res = NULL;
+
+  if (libdns_reinit_pending)
     {
-      ret = adns_init (r_state, adns_if_noerrprint, NULL);
-      if (ret)
-        err = gpg_error_from_errno (ret);
+      libdns_reinit_pending = 0;
+      libdns_deinit ();
     }
 
+  err = libdns_init ();
   if (err)
+    return err;
+
+  if (!opt_timeout)
+    set_dns_timeout (0);
+
+  res = dns_res_open (libdns.resolv_conf, libdns.hosts, libdns.hints, NULL,
+                      dns_opts (.socks_host     = &libdns.socks_host,
+                                .socks_user     = tor_socks_user,
+                                .socks_password = tor_socks_password ),
+                      &derr);
+  if (!res)
+    return libdns_error_to_gpg_error (derr);
+
+  *r_res = res;
+  return 0;
+}
+#endif /*USE_LIBDNS*/
+
+
+#ifdef USE_LIBDNS
+/* Helper to test whether we need totry again after having swicthed
+ * the Tor port.  */
+static int
+libdns_switch_port_p (gpg_error_t err)
+{
+  if (tor_mode && gpg_err_code (err) == GPG_ERR_ECONNREFUSED
+      && libdns_tor_port == TOR_PORT)
     {
-      log_error ("error initializing adns: %s\n", gpg_strerror (err));
-      return err;
+      /* Switch port and try again.  */
+      if (opt_debug)
+        log_debug ("dns: switching from SOCKS port %d to %d\n",
+                   TOR_PORT, TOR_PORT2);
+      libdns_tor_port = TOR_PORT2;
+      libdns_reinit_pending = 1;
+      return 1;
     }
   return 0;
 }
-#endif /*USE_ADNS*/
+#endif /*USE_LIBDNS*/
 
 
-#ifdef USE_ADNS
-/* Resolve a name using the ADNS library.  See resolve_dns_name for
-   the description.  */
+#ifdef USE_LIBDNS
+/* Wrapper around dns_res_submit.  */
 static gpg_error_t
-resolve_name_adns (const char *name, unsigned short port,
-                   int want_family, int want_socktype,
-                   dns_addrinfo_t *r_dai, char **r_canonname)
+libdns_res_submit (struct dns_resolver *res, const char *qname,
+                   enum dns_type qtype, enum dns_class qclass)
 {
-  gpg_error_t err = 0;
-  int ret;
+  return libdns_error_to_gpg_error (dns_res_submit (res, qname, qtype, qclass));
+}
+#endif /*USE_LIBDNS*/
+
+
+#ifdef USE_LIBDNS
+/* Standard event handling loop.  */
+gpg_error_t
+libdns_res_wait (struct dns_resolver *res)
+{
+  gpg_error_t err;
+
+  while ((err = libdns_error_to_gpg_error (dns_res_check (res)))
+         && gpg_err_code (err) == GPG_ERR_EAGAIN)
+    {
+      if (dns_res_elapsed (res) > opt_timeout)
+        {
+          err = gpg_error (GPG_ERR_DNS_TIMEOUT);
+          break;
+        }
+
+      my_unprotect ();
+      dns_res_poll (res, 1);
+      my_protect ();
+    }
+
+  return err;
+}
+#endif /*USE_LIBDNS*/
+
+
+#ifdef USE_LIBDNS
+static gpg_error_t
+resolve_name_libdns (const char *name, unsigned short port,
+                     int want_family, int want_socktype,
+                     dns_addrinfo_t *r_dai, char **r_canonname)
+{
+  gpg_error_t err;
   dns_addrinfo_t daihead = NULL;
   dns_addrinfo_t dai;
-  adns_state state;
-  adns_answer *answer = NULL;
-  int count;
-
-  (void)want_family;
+  struct dns_resolver *res = NULL;
+  struct dns_addrinfo *ai = NULL;
+  struct addrinfo hints;
+  struct addrinfo *ent;
+  char portstr_[21];
+  char *portstr = NULL;
+  int derr;
 
   *r_dai = NULL;
   if (r_canonname)
     *r_canonname = NULL;
 
-  if (want_socktype != SOCK_STREAM && want_socktype != SOCK_DGRAM)
-    return gpg_error (GPG_ERR_ESOCKTNOSUPPORT);
-
-  err = my_adns_init (&state);
-  if (err)
-    return err;
+  memset (&hints, 0, sizeof hints);
+  hints.ai_family = want_family;
+  hints.ai_socktype = want_socktype;
+  hints.ai_flags = AI_ADDRCONFIG;
+  if (r_canonname)
+    hints.ai_flags |= AI_CANONNAME;
 
-  my_unprotect ();
-  ret = adns_synchronous (state, name, adns_r_addr,
-                          adns_qf_quoteok_query, &answer);
-  my_protect ();
-  if (ret)
+  if (port)
     {
-      err = gpg_error (gpg_err_code_from_errno (ret));
-      log_error ("DNS query failed: %s\n", gpg_strerror (err));
-      goto leave;
+      snprintf (portstr_, sizeof portstr_, "%hu", port);
+      portstr = portstr_;
     }
 
-  err = gpg_error (GPG_ERR_NOT_FOUND);
-  if (answer->status != adns_s_ok || answer->type != adns_r_addr)
+  err = libdns_res_open (&res);
+  if (err)
+    goto leave;
+
+  ai = dns_ai_open (name, portstr, 0, &hints, res, &derr);
+  if (!ai)
     {
-      err = map_adns_status_to_gpg_error (answer->status);
-      if (gpg_err_code (err) == GPG_ERR_GENERAL)
-        err = gpg_error (GPG_ERR_NOT_FOUND);
-      log_error ("DNS query returned an error: %s (%s)\n",
-                 adns_strerror (answer->status),
-                 adns_errabbrev (answer->status));
+      err = libdns_error_to_gpg_error (derr);
       goto leave;
     }
 
-  if (r_canonname && answer->cname)
+  /* Loop over all records.  */
+  for (;;)
     {
-      *r_canonname = xtrystrdup (answer->cname);
-      if (!*r_canonname)
+      err = libdns_error_to_gpg_error (dns_ai_nextent (&ent, ai));
+      if (gpg_err_code (err) == GPG_ERR_ENOENT)
         {
-          err = gpg_error_from_syserror ();
-          goto leave;
+          if (daihead)
+            err = 0; /* We got some results, we're good.  */
+          break; /* Ready.  */
         }
-    }
+      if (gpg_err_code (err) == GPG_ERR_EAGAIN)
+        {
+          if (dns_ai_elapsed (ai) > opt_timeout)
+            {
+              err = gpg_error (GPG_ERR_DNS_TIMEOUT);
+              goto leave;
+            }
 
-  for (count = 0; count < answer->nrrs; count++)
-    {
-      int len;
-      adns_rr_addr *addr;
+          my_unprotect ();
+          dns_ai_poll (ai, 1);
+          my_protect ();
+          continue;
+        }
+      if (err)
+        goto leave;
 
-      len  = answer->rrs.addr[count].len;
-      addr = &answer->rrs.addr[count];
-      if (addr->addr.sa.sa_family != AF_INET6
-          && addr->addr.sa.sa_family != AF_INET)
-        continue;
+      if (r_canonname && ! *r_canonname && ent && ent->ai_canonname)
+        {
+          *r_canonname = xtrystrdup (ent->ai_canonname);
+          if (!*r_canonname)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+        }
 
-      dai = xtrymalloc (sizeof *dai + len - 1);
-      if (!dai)
+      dai = xtrymalloc (sizeof *dai + ent->ai_addrlen -1);
+      if (dai == NULL)
         {
           err = gpg_error_from_syserror ();
           goto leave;
         }
-      dai->family = addr->addr.sa.sa_family;
-      dai->socktype = want_socktype == SOCK_STREAM? SOCK_STREAM : SOCK_DGRAM;
-      dai->protocol = want_socktype == SOCK_STREAM? IPPROTO_TCP : IPPROTO_UDP;
-      dai->addrlen = len;
-      memcpy (dai->addr, &addr->addr.sa, len);
-      ((struct sockaddr_in *) dai->addr)->sin_port = htons (port);
+
+      dai->family = ent->ai_family;
+      dai->socktype = ent->ai_socktype;
+      dai->protocol = ent->ai_protocol;
+      dai->addrlen = ent->ai_addrlen;
+      memcpy (dai->addr, ent->ai_addr, ent->ai_addrlen);
       dai->next = daihead;
       daihead = dai;
-      err = 0;
-    }
+
+      xfree (ent);
+  }
 
  leave:
-  adns_free (answer);
-  adns_finish (state);
+  dns_ai_close (ai);
+  dns_res_close (res);
+
   if (err)
     {
       if (r_canonname)
@@ -362,12 +761,12 @@ resolve_name_adns (const char *name, unsigned short port,
     }
   else
     *r_dai = daihead;
+
   return err;
 }
-#endif /*USE_ADNS*/
+#endif /*USE_LIBDNS*/
 
 
-#ifndef USE_ADNS
 /* Resolve a name using the standard system function.  */
 static gpg_error_t
 resolve_name_standard (const char *name, unsigned short port,
@@ -472,7 +871,6 @@ resolve_name_standard (const char *name, unsigned short port,
     *r_dai = daihead;
   return err;
 }
-#endif /*!USE_ADNS*/
 
 
 /* Resolve an address using the standard system function.  */
@@ -551,13 +949,24 @@ resolve_dns_name (const char *name, unsigned short port,
                   int want_family, int want_socktype,
                   dns_addrinfo_t *r_ai, char **r_canonname)
 {
-#ifdef USE_ADNS
-  return resolve_name_adns (name, port, want_family, want_socktype,
-                            r_ai, r_canonname);
-#else
-  return resolve_name_standard (name, port, want_family, want_socktype,
-                                r_ai, r_canonname);
-#endif
+  gpg_error_t err;
+
+#ifdef USE_LIBDNS
+  if (!standard_resolver)
+    {
+      err = resolve_name_libdns (name, port, want_family, want_socktype,
+                                  r_ai, r_canonname);
+      if (err && libdns_switch_port_p (err))
+        err = resolve_name_libdns (name, port, want_family, want_socktype,
+                                   r_ai, r_canonname);
+    }
+  else
+#endif /*USE_LIBDNS*/
+    err = resolve_name_standard (name, port, want_family, want_socktype,
+                                 r_ai, r_canonname);
+  if (opt_debug)
+    log_debug ("dns: resolve_dns_name(%s): %s\n", name, gpg_strerror (err));
+  return err;
 }
 
 
@@ -565,11 +974,7 @@ gpg_error_t
 resolve_dns_addr (const struct sockaddr *addr, int addrlen,
                   unsigned int flags, char **r_name)
 {
-#ifdef USE_ADNS_disabled_for_now
-  return resolve_addr_adns (addr, addrlen, flags, r_name);
-#else
   return resolve_addr_standard (addr, addrlen, flags, r_name);
-#endif
 }
 
 
@@ -654,180 +1059,193 @@ is_onion_address (const char *name)
 }
 
 
-/* Returns 0 on success or an error code.  If a PGP CERT record was
-   found, the malloced data is returned at (R_KEY, R_KEYLEN) and
-   the other return parameters are set to NULL/0.  If an IPGP CERT
-   record was found the fingerprint is stored as an allocated block at
-   R_FPR and its length at R_FPRLEN; an URL is is allocated as a
-   string and returned at R_URL.  If WANT_CERTTYPE is 0 this function
-   returns the first CERT found with a supported type; it is expected
-   that only one CERT record is used.  If WANT_CERTTYPE is one of the
-   supported certtypes only records with this certtype are considered
-   and the first found is returned.  (R_KEY,R_KEYLEN) are optional. */
-gpg_error_t
-get_dns_cert (const char *name, int want_certtype,
-              void **r_key, size_t *r_keylen,
-              unsigned char **r_fpr, size_t *r_fprlen, char **r_url)
+/* libdns version of get_dns_cert.  */
+#ifdef USE_LIBDNS
+static gpg_error_t
+get_dns_cert_libdns (const char *name, int want_certtype,
+                     void **r_key, size_t *r_keylen,
+                     unsigned char **r_fpr, size_t *r_fprlen, char **r_url)
 {
-#ifdef USE_DNS_CERT
-#ifdef USE_ADNS
   gpg_error_t err;
-  int ret;
-  adns_state state;
-  adns_answer *answer = NULL;
-  unsigned int ctype;
-  int count;
+  struct dns_resolver *res = NULL;
+  struct dns_packet *ans = NULL;
+  struct dns_rr rr;
+  struct dns_rr_i rri;
+  char host[DNS_D_MAXNAME + 1];
+  int derr;
+  int qtype;
+
+  /* Gte the query type from WANT_CERTTYPE (which in general indicates
+   * the subtype we want). */
+  qtype = (want_certtype < DNS_CERTTYPE_RRBASE
+           ? T_CERT
+           : (want_certtype - DNS_CERTTYPE_RRBASE));
+
+
+  err = libdns_res_open (&res);
+  if (err)
+    goto leave;
 
-  if (r_key)
-    *r_key = NULL;
-  if (r_keylen)
-    *r_keylen = 0;
-  *r_fpr = NULL;
-  *r_fprlen = 0;
-  *r_url = NULL;
+  if (dns_d_anchor (host, sizeof host, name, strlen (name)) >= sizeof host)
+    {
+      err = gpg_error (GPG_ERR_ENAMETOOLONG);
+      goto leave;
+    }
 
-  err = my_adns_init (&state);
+  err = libdns_res_submit (res, name, qtype, DNS_C_IN);
   if (err)
-    return err;
+    goto leave;
 
-  my_unprotect ();
-  ret = adns_synchronous (state, name,
-                          (adns_r_unknown
-                           | (want_certtype < DNS_CERTTYPE_RRBASE
-                              ? my_adns_r_cert
-                              : (want_certtype - DNS_CERTTYPE_RRBASE))),
-                          adns_qf_quoteok_query, &answer);
-  my_protect ();
-  if (ret)
+  err = libdns_res_wait (res);
+  if (err)
+    goto leave;
+
+  ans = dns_res_fetch (res, &derr);
+  if (!ans)
     {
-      err = gpg_error (gpg_err_code_from_errno (ret));
-      /* log_error ("DNS query failed: %s\n", gpg_strerror (err)); */
-      adns_finish (state);
-      return err;
+      err = libdns_error_to_gpg_error (derr);
+      goto leave;
     }
-  if (answer->status != adns_s_ok)
+
+  /* Check the rcode.  */
+  switch (dns_p_rcode (ans))
     {
-      /* log_error ("DNS query returned an error: %s (%s)\n", */
-      /*            adns_strerror (answer->status), */
-      /*            adns_errabbrev (answer->status)); */
-      err = map_adns_status_to_gpg_error (answer->status);
-      if (gpg_err_code (err) == GPG_ERR_GENERAL)
-        err = gpg_error (GPG_ERR_NOT_FOUND);
-      goto leave;
+    case DNS_RC_NOERROR: break;
+    case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break;
+    default: err = GPG_ERR_SERVER_FAILED; break;
     }
+  if (err)
+    goto leave;
+
+  memset (&rri, 0, sizeof rri);
+  dns_rr_i_init (&rri, ans);
+  rri.section = DNS_S_ALL & ~DNS_S_QD;
+  rri.name    = host;
+  rri.type    = qtype;
 
   err = gpg_error (GPG_ERR_NOT_FOUND);
-  for (count = 0; count < answer->nrrs; count++)
+  while (dns_rr_grep (&rr, 1, &rri, ans, &derr))
     {
-      int datalen = answer->rrs.byteblock[count].len;
-      const unsigned char *data = answer->rrs.byteblock[count].data;
-
-      /* First check for our generic RR hack.  */
-      if (datalen
-          && want_certtype >= DNS_CERTTYPE_RRBASE
-          && ((want_certtype - DNS_CERTTYPE_RRBASE)
-              == (answer->type & ~adns_r_unknown)))
+      unsigned char *rp  = ans->data + rr.rd.p;
+      unsigned short len = rr.rd.len;
+      u16 subtype;
+
+       if (!len)
         {
-          /* Found the requested record - return it.  */
-          *r_key = xtrymalloc (datalen);
-          if (!*r_key)
-            err = gpg_error_from_syserror ();
-          else
-            {
-              memcpy (*r_key, data, datalen);
-              *r_keylen = datalen;
-              err = 0;
-            }
-          goto leave;
+          /* Definitely too short - skip.  */
         }
-
-      if (datalen < 5)
-        continue;  /* Truncated CERT record - skip.  */
-
-      ctype = buf16_to_uint (data);
-      /* (key tag and algorithm fields are not required.) */
-      data += 5;
-      datalen -= 5;
-
-      if (want_certtype && want_certtype != ctype)
-        ; /* Not of the requested certtype.  */
-      else if (ctype == DNS_CERTTYPE_PGP && datalen >= 11 && r_key && r_keylen)
+      else if (want_certtype >= DNS_CERTTYPE_RRBASE
+          && rr.type == (want_certtype - DNS_CERTTYPE_RRBASE)
+          && r_key)
         {
-          /* CERT type is PGP.  Gpg checks for a minimum length of 11,
-             thus we do the same.  */
-          *r_key = xtrymalloc (datalen);
+          *r_key = xtrymalloc (len);
           if (!*r_key)
             err = gpg_error_from_syserror ();
           else
             {
-              memcpy (*r_key, data, datalen);
-              *r_keylen = datalen;
+              memcpy (*r_key, rp, len);
+              *r_keylen = len;
               err = 0;
             }
           goto leave;
         }
-      else if (ctype == DNS_CERTTYPE_IPGP && datalen && datalen < 1023
-               && datalen >= data[0] + 1 && r_fpr && r_fprlen && r_url)
+      else if (want_certtype >= DNS_CERTTYPE_RRBASE)
+        {
+          /* We did not found the requested RR - skip. */
+        }
+      else if (rr.type == T_CERT && len > 5)
         {
-          /* CERT type is IPGP.  We made sure that the data is
-             plausible and that the caller requested this
-             information.  */
-          *r_fprlen = data[0];
-          if (*r_fprlen)
+          /* We got a CERT type.   */
+          subtype = buf16_to_u16 (rp);
+          rp += 2; len -= 2;
+
+          /* Skip the CERT key tag and algo which we don't need.  */
+          rp += 3; len -= 3;
+
+          if (want_certtype && want_certtype != subtype)
+            ; /* Not the requested subtype - skip.  */
+          else if (subtype == DNS_CERTTYPE_PGP && len && r_key && r_keylen)
             {
-              *r_fpr = xtrymalloc (*r_fprlen);
-              if (!*r_fpr)
+              /* PGP subtype */
+              *r_key = xtrymalloc (len);
+              if (!*r_key)
+                err = gpg_error_from_syserror ();
+              else
                 {
-                  err = gpg_error_from_syserror ();
-                  goto leave;
+                  memcpy (*r_key, rp, len);
+                  *r_keylen = len;
+                  err = 0;
                 }
-              memcpy (*r_fpr, data + 1, *r_fprlen);
+              goto leave;
             }
-          else
-            *r_fpr = NULL;
-
-          if (datalen > *r_fprlen + 1)
+          else if (subtype == DNS_CERTTYPE_IPGP
+                   && len && len < 1023 && len >= rp[0] + 1)
             {
-              *r_url = xtrymalloc (datalen - (*r_fprlen + 1) + 1);
-              if (!*r_url)
+              /* IPGP type */
+              *r_fprlen = rp[0];
+              if (*r_fprlen)
                 {
-                  err = gpg_error_from_syserror ();
-                  xfree (*r_fpr);
-                  *r_fpr = NULL;
-                  goto leave;
+                  *r_fpr = xtrymalloc (*r_fprlen);
+                  if (!*r_fpr)
+                    {
+                      err = gpg_error_from_syserror ();
+                      goto leave;
+                    }
+                  memcpy (*r_fpr, rp+1, *r_fprlen);
+                }
+              else
+                *r_fpr = NULL;
+
+              if (len > *r_fprlen + 1)
+                {
+                  *r_url = xtrymalloc (len - (*r_fprlen + 1) + 1);
+                  if (!*r_url)
+                    {
+                      err = gpg_error_from_syserror ();
+                      xfree (*r_fpr);
+                      *r_fpr = NULL;
+                      goto leave;
+                    }
+                  memcpy (*r_url, rp + *r_fprlen + 1, len - (*r_fprlen + 1));
+                  (*r_url)[len - (*r_fprlen + 1)] = 0;
                 }
-              memcpy (*r_url,
-                      data + (*r_fprlen + 1), datalen - (*r_fprlen + 1));
-              (*r_url)[datalen - (*r_fprlen + 1)] = '\0';
+              else
+                *r_url = NULL;
+
+              err = 0;
+              goto leave;
             }
           else
-            *r_url = NULL;
-
-          err = 0;
-          goto leave;
+            {
+              /* Unknown subtype or record too short - skip.  */
+            }
+        }
+      else
+        {
+          /* Not a requested type - skip.  */
         }
     }
 
  leave:
-  adns_free (answer);
-  adns_finish (state);
+  dns_free (ans);
+  dns_res_close (res);
   return err;
+}
+#endif /*USE_LIBDNS*/
 
-#else /*!USE_ADNS*/
 
+/* Standard resolver version of get_dns_cert.  */
+static gpg_error_t
+get_dns_cert_standard (const char *name, int want_certtype,
+                       void **r_key, size_t *r_keylen,
+                       unsigned char **r_fpr, size_t *r_fprlen, char **r_url)
+{
+#ifdef HAVE_SYSTEM_RESOLVER
   gpg_error_t err;
   unsigned char *answer;
   int r;
   u16 count;
 
-  if (r_key)
-    *r_key = NULL;
-  if (r_keylen)
-    *r_keylen = 0;
-  *r_fpr = NULL;
-  *r_fprlen = 0;
-  *r_url = NULL;
-
   /* Allocate a 64k buffer which is the limit for an DNS response.  */
   answer = xtrymalloc (65536);
   if (!answer)
@@ -971,7 +1389,7 @@ get_dns_cert (const char *name, int want_certtype,
                   if (dlen > *r_fprlen + 1)
                     {
                       *r_url = xtrymalloc (dlen - (*r_fprlen + 1) + 1);
-                      if (!*r_fpr)
+                      if (!*r_url)
                         {
                           err = gpg_error_from_syserror ();
                           xfree (*r_fpr);
@@ -1004,9 +1422,38 @@ get_dns_cert (const char *name, int want_certtype,
   xfree (answer);
   return err;
 
-#endif /*!USE_ADNS */
-#else /* !USE_DNS_CERT */
+#else /*!HAVE_SYSTEM_RESOLVER*/
+
   (void)name;
+  (void)want_certtype;
+  (void)r_key;
+  (void)r_keylen;
+  (void)r_fpr;
+  (void)r_fprlen;
+  (void)r_url;
+  return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+#endif /*!HAVE_SYSTEM_RESOLVER*/
+}
+
+
+/* Returns 0 on success or an error code.  If a PGP CERT record was
+   found, the malloced data is returned at (R_KEY, R_KEYLEN) and
+   the other return parameters are set to NULL/0.  If an IPGP CERT
+   record was found the fingerprint is stored as an allocated block at
+   R_FPR and its length at R_FPRLEN; an URL is is allocated as a
+   string and returned at R_URL.  If WANT_CERTTYPE is 0 this function
+   returns the first CERT found with a supported type; it is expected
+   that only one CERT record is used.  If WANT_CERTTYPE is one of the
+   supported certtypes only records with this certtype are considered
+   and the first found is returned.  (R_KEY,R_KEYLEN) are optional. */
+gpg_error_t
+get_dns_cert (const char *name, int want_certtype,
+              void **r_key, size_t *r_keylen,
+              unsigned char **r_fpr, size_t *r_fprlen, char **r_url)
+{
+  gpg_error_t err;
+
   if (r_key)
     *r_key = NULL;
   if (r_keylen)
@@ -1015,11 +1462,26 @@ get_dns_cert (const char *name, int want_certtype,
   *r_fprlen = 0;
   *r_url = NULL;
 
-  return gpg_error (GPG_ERR_NOT_SUPPORTED);
-#endif
+#ifdef USE_LIBDNS
+  if (!standard_resolver)
+    {
+      err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen,
+                                 r_fpr, r_fprlen, r_url);
+      if (err && libdns_switch_port_p (err))
+        err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen,
+                                   r_fpr, r_fprlen, r_url);
+    }
+  else
+#endif /*USE_LIBDNS*/
+    err = get_dns_cert_standard (name, want_certtype, r_key, r_keylen,
+                                 r_fpr, r_fprlen, r_url);
+
+  if (opt_debug)
+    log_debug ("dns: get_dns_cert(%s): %s\n", name, gpg_strerror (err));
+  return err;
 }
 
-#ifdef USE_DNS_SRV
+
 static int
 priosort(const void *a,const void *b)
 {
@@ -1033,170 +1495,260 @@ priosort(const void *a,const void *b)
 }
 
 
-int
-getsrv (const char *name,struct srventry **list)
+/* Libdns based helper for getsrv.  Note that it is expected that NULL
+ * is stored at the address of LIST and 0 is stored at the address of
+ * R_COUNT.  */
+#ifdef USE_LIBDNS
+static gpg_error_t
+getsrv_libdns (const char *name, struct srventry **list, unsigned int *r_count)
 {
-  int srvcount=0;
+  gpg_error_t err;
+  struct dns_resolver *res = NULL;
+  struct dns_packet *ans = NULL;
+  struct dns_rr rr;
+  struct dns_rr_i rri;
+  char host[DNS_D_MAXNAME + 1];
+  int derr;
+  unsigned int srvcount = 0;
+
+  err = libdns_res_open (&res);
+  if (err)
+    goto leave;
+
+  if (dns_d_anchor (host, sizeof host, name, strlen (name)) >= sizeof host)
+    {
+      err = gpg_error (GPG_ERR_ENAMETOOLONG);
+      goto leave;
+    }
+
+  err = libdns_res_submit (res, name, DNS_T_SRV, DNS_C_IN);
+  if (err)
+    goto leave;
+
+  err = libdns_res_wait (res);
+  if (err)
+    goto leave;
+
+  ans = dns_res_fetch (res, &derr);
+  if (!ans)
+    {
+      err = libdns_error_to_gpg_error (derr);
+      goto leave;
+    }
+
+  /* Check the rcode.  */
+  switch (dns_p_rcode (ans))
+    {
+    case DNS_RC_NOERROR: break;
+    case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break;
+    default: err = GPG_ERR_SERVER_FAILED; break;
+    }
+  if (err)
+    goto leave;
+
+  memset (&rri, 0, sizeof rri);
+  dns_rr_i_init (&rri, ans);
+  rri.section = DNS_S_ALL & ~DNS_S_QD;
+  rri.name       = host;
+  rri.type       = DNS_T_SRV;
+
+  while (dns_rr_grep (&rr, 1, &rri, ans, &derr))
+    {
+      struct dns_srv dsrv;
+      struct srventry *srv;
+      struct srventry *newlist;
+
+      err = libdns_error_to_gpg_error (dns_srv_parse(&dsrv, &rr, ans));
+      if (err)
+        goto leave;
+
+      newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry));
+      if (!newlist)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+      *list = newlist;
+      memset (&(*list)[srvcount], 0, sizeof(struct srventry));
+      srv = &(*list)[srvcount];
+      srvcount++;
+      srv->priority = dsrv.priority;
+      srv->weight   = dsrv.weight;
+      srv->port     = dsrv.port;
+      mem2str (srv->target, dsrv.target, sizeof srv->target);
+    }
+
+  *r_count = srvcount;
+
+ leave:
+  if (err)
+    {
+      xfree (*list);
+      *list = NULL;
+    }
+  dns_free (ans);
+  dns_res_close (res);
+  return err;
+}
+#endif /*USE_LIBDNS*/
+
+
+/* Standard resolver based helper for getsrv.  Note that it is
+ * expected that NULL is stored at the address of LIST and 0 is stored
+ * at the address of R_COUNT.  */
+static gpg_error_t
+getsrv_standard (const char *name,
+                 struct srventry **list, unsigned int *r_count)
+{
+#ifdef HAVE_SYSTEM_RESOLVER
+  union {
+    unsigned char ans[2048];
+    HEADER header[1];
+  } res;
+  unsigned char *answer = res.ans;
+  HEADER *header = res.header;
+  unsigned char *pt, *emsg;
+  int r, rc;
+  u16 dlen;
+  unsigned int srvcount = 0;
   u16 count;
-  int i, rc;
 
+  /* Do not allow a query using the standard resolver in Tor mode.  */
+  if (tor_mode)
+    return gpg_error (GPG_ERR_NOT_ENABLED);
+
+  my_unprotect ();
+  r = res_query (name, C_IN, T_SRV, answer, sizeof res.ans);
+  my_protect ();
+  if (r < 0)
+    return get_h_errno_as_gpg_error ();
+  if (r < sizeof (HEADER))
+    return gpg_error (GPG_ERR_SERVER_FAILED);
+  if (r > sizeof res.ans)
+    return gpg_error (GPG_ERR_SYSTEM_BUG);
+  if (header->rcode != NOERROR || !(count=ntohs (header->ancount)))
+    return gpg_error (GPG_ERR_NO_NAME); /* Error or no record found.  */
+
+  emsg = &answer[r];
+  pt = &answer[sizeof(HEADER)];
+
+  /* Skip over the query */
+  rc = dn_skipname (pt, emsg);
+  if (rc == -1)
+    goto fail;
+
+  pt += rc + QFIXEDSZ;
+
+  while (count-- > 0 && pt < emsg)
+    {
+      struct srventry *srv;
+      u16 type, class;
+      struct srventry *newlist;
+
+      newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry));
+      if (!newlist)
+        goto fail;
+      *list = newlist;
+      memset (&(*list)[srvcount], 0, sizeof(struct srventry));
+      srv = &(*list)[srvcount];
+      srvcount++;
+
+      rc = dn_skipname (pt, emsg); /* The name we just queried for.  */
+      if (rc == -1)
+        goto fail;
+      pt += rc;
+
+      /* Truncated message? */
+      if ((emsg-pt) < 16)
+        goto fail;
+
+      type = buf16_to_u16 (pt);
+      pt += 2;
+      /* We asked for SRV and got something else !? */
+      if (type != T_SRV)
+        goto fail;
+
+      class = buf16_to_u16 (pt);
+      pt += 2;
+      /* We asked for IN and got something else !? */
+      if (class != C_IN)
+        goto fail;
+
+      pt += 4; /* ttl */
+      dlen = buf16_to_u16 (pt);
+      pt += 2;
+
+      srv->priority = buf16_to_ushort (pt);
+      pt += 2;
+      srv->weight = buf16_to_ushort (pt);
+      pt += 2;
+      srv->port = buf16_to_ushort (pt);
+      pt += 2;
+
+      /* Get the name.  2782 doesn't allow name compression, but
+       * dn_expand still works to pull the name out of the packet. */
+      rc = dn_expand (answer, emsg, pt, srv->target, sizeof srv->target);
+      if (rc == 1 && srv->target[0] == 0) /* "." */
+        {
+          xfree(*list);
+          *list = NULL;
+          return 0;
+        }
+      if (rc == -1)
+        goto fail;
+      pt += rc;
+      /* Corrupt packet? */
+      if (dlen != rc+6)
+        goto fail;
+    }
+
+  *r_count = srvcount;
+  return 0;
+
+ fail:
+  xfree (*list);
   *list = NULL;
+  return gpg_error (GPG_ERR_GENERAL);
 
-#ifdef USE_ADNS
-  {
-    adns_state state;
-    adns_answer *answer = NULL;
+#else /*!HAVE_SYSTEM_RESOLVER*/
 
-    if (my_adns_init (&state))
-      return -1;
+  (void)name;
+  (void)list;
+  (void)r_count;
+  return gpg_error (GPG_ERR_NOT_SUPPORTED);
 
-    my_unprotect ();
-    rc = adns_synchronous (state, name, adns_r_srv, adns_qf_quoteok_query,
-                           &answer);
-    my_protect ();
-    if (rc)
-      {
-        log_error ("DNS query failed: %s\n", strerror (rc));
-        adns_finish (state);
-        return -1;
-      }
-    if (answer->status != adns_s_ok
-        || answer->type != adns_r_srv || !answer->nrrs)
-      {
-        log_error ("DNS query returned an error or no records: %s (%s)\n",
-                   adns_strerror (answer->status),
-                   adns_errabbrev (answer->status));
-        adns_free (answer);
-        adns_finish (state);
-        return 0;
-      }
+#endif /*!HAVE_SYSTEM_RESOLVER*/
+}
 
-    for (count = 0; count < answer->nrrs; count++)
-      {
-        struct srventry *srv = NULL;
-        struct srventry *newlist;
-
-        if (strlen (answer->rrs.srvha[count].ha.host) >= sizeof srv->target)
-          {
-            log_info ("hostname in SRV record too long - skipped\n");
-            continue;
-          }
-
-        newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry));
-        if (!newlist)
-          goto fail;
-        *list = newlist;
-        memset (&(*list)[srvcount], 0, sizeof(struct srventry));
-        srv = &(*list)[srvcount];
-        srvcount++;
-
-        srv->priority = answer->rrs.srvha[count].priority;
-        srv->weight   = answer->rrs.srvha[count].weight;
-        srv->port     = answer->rrs.srvha[count].port;
-        strcpy (srv->target, answer->rrs.srvha[count].ha.host);
-      }
 
-    adns_free (answer);
-    adns_finish (state);
-  }
-#else /*!USE_ADNS*/
-  {
-    union {
-      unsigned char ans[2048];
-      HEADER header[1];
-    } res;
-    unsigned char *answer = res.ans;
-    HEADER *header = res.header;
-    unsigned char *pt, *emsg;
-    int r;
-    u16 dlen;
-
-    /* Do not allow a query using the standard resolver in Tor mode.  */
-    if (tor_mode)
-      return -1;
-
-    my_unprotect ();
-    r = res_query (name, C_IN, T_SRV, answer, sizeof answer);
-    my_protect ();
-    if (r < sizeof (HEADER) || r > sizeof answer
-        || header->rcode != NOERROR || !(count=ntohs (header->ancount)))
-      return 0; /* Error or no record found.  */
-
-    emsg = &answer[r];
-    pt = &answer[sizeof(HEADER)];
-
-    /* Skip over the query */
-    rc = dn_skipname (pt, emsg);
-    if (rc == -1)
-      goto fail;
-
-    pt += rc + QFIXEDSZ;
-
-    while (count-- > 0 && pt < emsg)
-      {
-        struct srventry *srv=NULL;
-        u16 type,class;
-        struct srventry *newlist;
-
-        newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry));
-        if (!newlist)
-          goto fail;
-        *list = newlist;
-        memset(&(*list)[srvcount],0,sizeof(struct srventry));
-        srv=&(*list)[srvcount];
-        srvcount++;
-
-        rc = dn_skipname(pt,emsg); /* the name we just queried for */
-        if (rc == -1)
-          goto fail;
-        pt+=rc;
-
-        /* Truncated message? */
-        if((emsg-pt)<16)
-          goto fail;
-
-        type = buf16_to_u16 (pt);
-        pt += 2;
-        /* We asked for SRV and got something else !? */
-        if(type!=T_SRV)
-          goto fail;
-
-        class = buf16_to_u16 (pt);
-        pt += 2;
-        /* We asked for IN and got something else !? */
-        if(class!=C_IN)
-          goto fail;
-
-        pt += 4; /* ttl */
-        dlen = buf16_to_u16 (pt);
-        pt += 2;
-
-        srv->priority = buf16_to_ushort (pt);
-        pt += 2;
-        srv->weight = buf16_to_ushort (pt);
-        pt += 2;
-        srv->port = buf16_to_ushort (pt);
-        pt += 2;
-
-        /* Get the name.  2782 doesn't allow name compression, but
-           dn_expand still works to pull the name out of the
-           packet. */
-        rc = dn_expand(answer,emsg,pt,srv->target, sizeof srv->target);
-        if (rc == 1 && srv->target[0] == 0) /* "." */
-          {
-            xfree(*list);
-            *list = NULL;
-            return 0;
-          }
-        if (rc == -1)
-          goto fail;
-        pt += rc;
-        /* Corrupt packet? */
-        if (dlen != rc+6)
-          goto fail;
-      }
-  }
-#endif /*!USE_ADNS*/
+/* Note that we do not return NONAME but simply store 0 at R_COUNT.  */
+gpg_error_t
+get_dns_srv (const char *name, struct srventry **list, unsigned int *r_count)
+{
+  gpg_error_t err;
+  unsigned int srvcount;
+  int i;
+
+  *list = NULL;
+  *r_count = 0;
+  srvcount = 0;
+#ifdef USE_LIBDNS
+  if (!standard_resolver)
+    {
+      err = getsrv_libdns (name, list, &srvcount);
+      if (err && libdns_switch_port_p (err))
+        err = getsrv_libdns (name, list, &srvcount);
+    }
+  else
+#endif /*USE_LIBDNS*/
+    err = getsrv_standard (name, list, &srvcount);
+
+  if (err)
+    {
+      if (gpg_err_code (err) == GPG_ERR_NO_NAME)
+        err = 0;
+      goto leave;
+    }
 
   /* Now we have an array of all the srv records. */
 
@@ -1271,126 +1823,190 @@ getsrv (const char *name,struct srventry **list)
         }
     }
 
-  return srvcount;
+ leave:
+  if (opt_debug)
+    {
+      if (err)
+        log_debug ("dns: getsrv(%s): %s\n", name, gpg_strerror (err));
+      else
+        log_debug ("dns: getsrv(%s) -> %u records\n", name, srvcount);
+    }
+  if (!err)
+    *r_count = srvcount;
+  return err;
+}
+
 
- fail:
-  xfree(*list);
-  *list=NULL;
-  return -1;
+\f
+#ifdef USE_LIBDNS
+/* libdns version of get_dns_cname.  */
+gpg_error_t
+get_dns_cname_libdns (const char *name, char **r_cname)
+{
+  gpg_error_t err;
+  struct dns_resolver *res;
+  struct dns_packet *ans = NULL;
+  struct dns_cname cname;
+  int derr;
+
+  err = libdns_res_open (&res);
+  if (err)
+    goto leave;
+
+  err = libdns_res_submit (res, name, DNS_T_CNAME, DNS_C_IN);
+  if (err)
+    goto leave;
+
+  err = libdns_res_wait (res);
+  if (err)
+    goto leave;
+
+  ans = dns_res_fetch (res, &derr);
+  if (!ans)
+    {
+      err = libdns_error_to_gpg_error (derr);
+      goto leave;
+    }
+
+  /* Check the rcode.  */
+  switch (dns_p_rcode (ans))
+    {
+    case DNS_RC_NOERROR: break;
+    case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break;
+    default: err = GPG_ERR_SERVER_FAILED; break;
+    }
+  if (err)
+    goto leave;
+
+  /* Parse the result into CNAME.  */
+  err = libdns_error_to_gpg_error (dns_p_study (ans));
+  if (err)
+    goto leave;
+
+  if (!dns_d_cname (&cname, sizeof cname, name, strlen (name), ans, &derr))
+    {
+      err = libdns_error_to_gpg_error (derr);
+      goto leave;
+    }
+
+  /* Copy result.  */
+  *r_cname = xtrystrdup (cname.host);
+  if (!*r_cname)
+    err = gpg_error_from_syserror ();
+
+ leave:
+  dns_free (ans);
+  dns_res_close (res);
+  return err;
 }
-#endif /*USE_DNS_SRV*/
+#endif /*USE_LIBDNS*/
 
 
+/* Standard resolver version of get_dns_cname.  */
 gpg_error_t
-get_dns_cname (const char *name, char **r_cname)
+get_dns_cname_standard (const char *name, char **r_cname)
 {
+#ifdef HAVE_SYSTEM_RESOLVER
   gpg_error_t err;
   int rc;
+  union {
+    unsigned char ans[2048];
+    HEADER header[1];
+  } res;
+  unsigned char *answer = res.ans;
+  HEADER *header = res.header;
+  unsigned char *pt, *emsg;
+  int r;
+  char *cname;
+  int cnamesize = 1025;
+  u16 count;
 
-  *r_cname = NULL;
-
-#ifdef USE_ADNS
-  {
-    adns_state state;
-    adns_answer *answer = NULL;
+  /* Do not allow a query using the standard resolver in Tor mode.  */
+  if (tor_mode)
+    return -1;
 
-    if (my_adns_init (&state))
-      return gpg_error (GPG_ERR_GENERAL);
+  my_unprotect ();
+  r = res_query (name, C_IN, T_CERT, answer, sizeof res.ans);
+  my_protect ();
+  if (r < 0)
+    return get_h_errno_as_gpg_error ();
+  if (r < sizeof (HEADER))
+    return gpg_error (GPG_ERR_SERVER_FAILED);
+  if (r > sizeof res.ans)
+    return gpg_error (GPG_ERR_SYSTEM_BUG);
+  if (header->rcode != NOERROR || !(count=ntohs (header->ancount)))
+    return gpg_error (GPG_ERR_NO_NAME); /* Error or no record found.  */
+  if (count != 1)
+    return gpg_error (GPG_ERR_SERVER_FAILED);
+
+  emsg = &answer[r];
+  pt = &answer[sizeof(HEADER)];
+  rc = dn_skipname (pt, emsg);
+  if (rc == -1)
+    return gpg_error (GPG_ERR_SERVER_FAILED);
+
+  pt += rc + QFIXEDSZ;
+  if (pt >= emsg)
+    return gpg_error (GPG_ERR_SERVER_FAILED);
+
+  rc = dn_skipname (pt, emsg);
+  if (rc == -1)
+    return gpg_error (GPG_ERR_SERVER_FAILED);
+  pt += rc + 2 + 2 + 4;
+  if (pt+2 >= emsg)
+    return gpg_error (GPG_ERR_SERVER_FAILED);
+  pt += 2;  /* Skip rdlen */
+
+  cname = xtrymalloc (cnamesize);
+  if (!cname)
+    return gpg_error_from_syserror ();
 
-    my_unprotect ();
-    rc = adns_synchronous (state, name, adns_r_cname, adns_qf_quoteok_query,
-                           &answer);
-    my_protect ();
-    if (rc)
-      {
-        err = gpg_error (gpg_err_code_from_errno (rc));
-        log_error ("DNS query failed: %s\n", gpg_strerror (err));
-        adns_finish (state);
-        return err;
-      }
-    if (answer->status != adns_s_ok
-        || answer->type != adns_r_cname || answer->nrrs != 1)
-      {
-        err = map_adns_status_to_gpg_error (answer->status);
-        log_error ("DNS query returned an error or no records: %s (%s)\n",
-                   adns_strerror (answer->status),
-                   adns_errabbrev (answer->status));
-        adns_free (answer);
-        adns_finish (state);
-        return err;
-      }
-    *r_cname = xtrystrdup (answer->rrs.str[0]);
-    if (!*r_cname)
+  rc = dn_expand (answer, emsg, pt, cname, cnamesize -1);
+  if (rc == -1)
+    {
+      xfree (cname);
+      return gpg_error (GPG_ERR_SERVER_FAILED);
+    }
+  *r_cname = xtryrealloc (cname, strlen (cname)+1);
+  if (!*r_cname)
+    {
       err = gpg_error_from_syserror ();
-    else
-      err = 0;
+      xfree (cname);
+      return err;
+    }
+  return 0;
 
-    adns_free (answer);
-    adns_finish (state);
-    return err;
-  }
-#else /*!USE_ADNS*/
-  {
-    union {
-      unsigned char ans[2048];
-      HEADER header[1];
-    } res;
-    unsigned char *answer = res.ans;
-    HEADER *header = res.header;
-    unsigned char *pt, *emsg;
-    int r;
-    char *cname;
-    int cnamesize = 1025;
-    u16 count;
-
-    /* Do not allow a query using the standard resolver in Tor mode.  */
-    if (tor_mode)
-      return -1;
-
-    r = res_query (name, C_IN, T_CERT, answer, sizeof answer);
-    if (r < sizeof (HEADER) || r > sizeof answer)
-      return gpg_error (GPG_ERR_SERVER_FAILED);
-    if (header->rcode != NOERROR || !(count=ntohs (header->ancount)))
-      return gpg_error (GPG_ERR_NO_NAME); /* Error or no record found.  */
-    if (count != 1)
-      return gpg_error (GPG_ERR_SERVER_FAILED);
+#else /*!HAVE_SYSTEM_RESOLVER*/
 
-    emsg = &answer[r];
-    pt = &answer[sizeof(HEADER)];
-    rc = dn_skipname (pt, emsg);
-    if (rc == -1)
-      return gpg_error (GPG_ERR_SERVER_FAILED);
+  (void)name;
+  (void)r_cname;
+  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
 
-    pt += rc + QFIXEDSZ;
-    if (pt >= emsg)
-      return gpg_error (GPG_ERR_SERVER_FAILED);
+#endif /*!HAVE_SYSTEM_RESOLVER*/
+}
 
-    rc = dn_skipname (pt, emsg);
-    if (rc == -1)
-      return gpg_error (GPG_ERR_SERVER_FAILED);
-    pt += rc + 2 + 2 + 4;
-    if (pt+2 >= emsg)
-      return gpg_error (GPG_ERR_SERVER_FAILED);
-    pt += 2;  /* Skip rdlen */
 
-    cname = xtrymalloc (cnamesize);
-    if (!cname)
-      return gpg_error_from_syserror ();
+gpg_error_t
+get_dns_cname (const char *name, char **r_cname)
+{
+  gpg_error_t err;
+
+  *r_cname = NULL;
 
-    rc = dn_expand (answer, emsg, pt, cname, cnamesize -1);
-    if (rc == -1)
-      {
-        xfree (cname);
-        return gpg_error (GPG_ERR_SERVER_FAILED);
-      }
-    *r_cname = xtryrealloc (cname, strlen (cname)+1);
-    if (!*r_cname)
-      {
-        err = gpg_error_from_syserror ();
-        xfree (cname);
-        return err;
-      }
-    return 0;
-  }
-#endif /*!USE_ADNS*/
+#ifdef USE_LIBDNS
+  if (!standard_resolver)
+    {
+      err = get_dns_cname_libdns (name, r_cname);
+      if (err && libdns_switch_port_p (err))
+        err = get_dns_cname_libdns (name, r_cname);
+      return err;
+    }
+#endif /*USE_LIBDNS*/
+
+  err = get_dns_cname_standard (name, r_cname);
+  if (opt_debug)
+    log_debug ("get_dns_cname(%s)%s%s\n", name,
+               err ? ": " : " -> ",
+               err ? gpg_strerror (err) : *r_cname);
+  return err;
 }
index 10e6d8d..0a4a4de 100644 (file)
@@ -92,6 +92,27 @@ struct srventry
 };
 
 
+/* Set verbosity and debug mode for this module. */
+void set_dns_verbose (int verbose, int debug);
+
+/* Set the timeout for libdns requests to SECONDS.  */
+void set_dns_timeout (int seconds);
+
+/* Calling this function with YES set to True forces the use of the
+ * standard resolver even if dirmngr has been built with support for
+ * an alternative resolver.  */
+void enable_standard_resolver (int yes);
+
+/* Return true if the standard resolver is used.  */
+int standard_resolver_p (void);
+
+/* Calling this function with YES switches libdns into recursive mode.
+ * It has no effect on the standard resolver.  */
+void enable_recursive_resolver (int yes);
+
+/* Return true iff the recursive resolver is used.  */
+int recursive_resolver_p (void);
+
 /* Calling this function switches the DNS code into Tor mode if
    possibe.  Return 0 on success.  */
 gpg_error_t enable_dns_tormode (int new_circuit);
@@ -101,6 +122,8 @@ gpg_error_t enable_dns_tormode (int new_circuit);
    next DNS query.  Note that this is only used in Tor mode.  */
 void set_dns_nameserver (const char *ipaddr);
 
+/* SIGHUP action handler for this module.  */
+void reload_dns_stuff (int force);
 
 void free_dns_addrinfo (dns_addrinfo_t ai);
 
@@ -128,8 +151,9 @@ gpg_error_t get_dns_cert (const char *name, int want_certtype,
                           unsigned char **r_fpr, size_t *r_fprlen,
                           char **r_url);
 
-
-int getsrv (const char *name,struct srventry **list);
+/* Return an array of SRV records.  */
+gpg_error_t get_dns_srv (const char *name,
+                         struct srventry **list, unsigned int *r_count);
 
 
 #endif /*GNUPG_DIRMNGR_DNS_STUFF_H*/
diff --git a/dirmngr/dns.c b/dirmngr/dns.c
new file mode 100644 (file)
index 0000000..4b61b72
--- /dev/null
@@ -0,0 +1,11246 @@
+/* ==========================================================================
+ * dns.c - Recursive, Reentrant DNS Resolver.
+ * --------------------------------------------------------------------------
+ * Copyright (c) 2008, 2009, 2010, 2012-2016  William Ahern
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit
+ * persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * ==========================================================================
+ */
+#if HAVE_CONFIG_H
+#include "config.h"
+#elif !defined _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <limits.h>            /* INT_MAX */
+#include <stdarg.h>            /* va_list va_start va_end */
+#include <stddef.h>            /* offsetof() */
+#ifdef _WIN32
+/* JW: This breaks our mingw build: #define uint32_t unsigned int */
+#else
+#include <stdint.h>            /* uint32_t */
+#endif
+#include <stdlib.h>            /* malloc(3) realloc(3) free(3) rand(3) random(3) arc4random(3) */
+#include <stdio.h>             /* FILE fopen(3) fclose(3) getc(3) rewind(3) vsnprintf(3) */
+#include <string.h>            /* memcpy(3) strlen(3) memmove(3) memchr(3) memcmp(3) strchr(3) strsep(3) strcspn(3) */
+#include <strings.h>           /* strcasecmp(3) strncasecmp(3) */
+#include <ctype.h>             /* isspace(3) isdigit(3) */
+#include <time.h>              /* time_t time(2) difftime(3) */
+#include <signal.h>            /* SIGPIPE sigemptyset(3) sigaddset(3) sigpending(2) sigprocmask(2) pthread_sigmask(3) sigtimedwait(2) */
+#include <errno.h>             /* errno EINVAL ENOENT */
+#undef NDEBUG
+#include <assert.h>            /* assert(3) */
+
+#if _WIN32
+#ifndef FD_SETSIZE
+#define FD_SETSIZE 1024
+#endif
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/time.h>          /* gettimeofday(2) */
+#include <sys/types.h>         /* FD_SETSIZE socklen_t */
+#include <sys/select.h>                /* FD_ZERO FD_SET fd_set select(2) */
+#include <sys/socket.h>                /* AF_INET AF_INET6 AF_UNIX struct sockaddr struct sockaddr_in struct sockaddr_in6 socket(2) */
+#if defined(AF_UNIX)
+#include <sys/un.h>            /* struct sockaddr_un */
+#endif
+#include <fcntl.h>             /* F_SETFD F_GETFL F_SETFL O_NONBLOCK fcntl(2) */
+#include <unistd.h>            /* _POSIX_THREADS gethostname(3) close(2) */
+#include <poll.h>              /* POLLIN POLLOUT */
+#include <netinet/in.h>                /* struct sockaddr_in struct sockaddr_in6 */
+#include <arpa/inet.h>         /* inet_pton(3) inet_ntop(3) htons(3) ntohs(3) */
+#include <netdb.h>             /* struct addrinfo */
+#endif
+
+#include "dns.h"
+
+
+/*
+ * C O M P I L E R  V E R S I O N  &  F E A T U R E  D E T E C T I O N
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_GNUC_2VER(M, m, p) (((M) * 10000) + ((m) * 100) + (p))
+#define DNS_GNUC_PREREQ(M, m, p) (__GNUC__ > 0 && DNS_GNUC_2VER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) >= DNS_GNUC_2VER((M), (m), (p)))
+
+#define DNS_MSC_2VER(M, m, p) ((((M) + 6) * 10000000) + ((m) * 1000000) + (p))
+#define DNS_MSC_PREREQ(M, m, p) (_MSC_VER_FULL > 0 && _MSC_VER_FULL >= DNS_MSC_2VER((M), (m), (p)))
+
+#define DNS_SUNPRO_PREREQ(M, m, p) (__SUNPRO_C > 0 && __SUNPRO_C >= 0x ## M ## m ## p)
+
+#if defined __has_builtin
+#define dns_has_builtin(x) __has_builtin(x)
+#else
+#define dns_has_builtin(x) 0
+#endif
+
+#if defined __has_extension
+#define dns_has_extension(x) __has_extension(x)
+#else
+#define dns_has_extension(x) 0
+#endif
+
+#ifndef HAVE___ASSUME
+#define HAVE___ASSUME DNS_MSC_PREREQ(8,0,0)
+#endif
+
+#ifndef HAVE___BUILTIN_TYPES_COMPATIBLE_P
+#define HAVE___BUILTIN_TYPES_COMPATIBLE_P (DNS_GNUC_PREREQ(3,1,1) || __clang__)
+#endif
+
+#ifndef HAVE___BUILTIN_UNREACHABLE
+#define HAVE___BUILTIN_UNREACHABLE (DNS_GNUC_PREREQ(4,5,0) || dns_has_builtin(__builtin_unreachable))
+#endif
+
+#ifndef HAVE_PRAGMA_MESSAGE
+#define HAVE_PRAGMA_MESSAGE (DNS_GNUC_PREREQ(4,4,0) || __clang__ || _MSC_VER)
+#endif
+
+
+/*
+ * C O M P I L E R  A N N O T A T I O N S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#if __GNUC__
+#define DNS_NOTUSED __attribute__((unused))
+#define DNS_NORETURN __attribute__((noreturn))
+#else
+#define DNS_NOTUSED
+#define DNS_NORETURN
+#endif
+
+#if __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+#pragma clang diagnostic ignored "-Wmissing-field-initializers"
+#elif DNS_GNUC_PREREQ(4,6,0)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+
+
+/*
+ * S T A N D A R D  M A C R O S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#if HAVE___BUILTIN_TYPES_COMPATIBLE_P
+#define dns_same_type(a, b, def) __builtin_types_compatible_p(__typeof__ (a), __typeof__ (b))
+#else
+#define dns_same_type(a, b, def) (def)
+#endif
+#define dns_isarray(a) (!dns_same_type((a), (&(a)[0]), 0))
+/* NB: "_" field silences Sun Studio "zero-sized struct/union" error diagnostic */
+#define dns_inline_assert(cond) ((void)(sizeof (struct { int:-!(cond); int _; })))
+
+#if HAVE___ASSUME
+#define dns_assume(cond) __assume(cond)
+#elif HAVE___BUILTIN_UNREACHABLE
+#define dns_assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
+#else
+#define dns_assume(cond) do { (void)(cond); } while (0)
+#endif
+
+#ifndef lengthof
+#define lengthof(a) (dns_inline_assert(dns_isarray(a)), (sizeof (a) / sizeof (a)[0]))
+#endif
+
+#ifndef endof
+#define endof(a) (dns_inline_assert(dns_isarray(a)), &(a)[lengthof((a))])
+#endif
+
+
+/*
+ * M I S C E L L A N E O U S  C O M P A T
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#if _WIN32 || _WIN64
+#define PRIuZ "Iu"
+#else
+#define PRIuZ "zu"
+#endif
+
+#ifndef DNS_THREAD_SAFE
+#if (defined _REENTRANT || defined _THREAD_SAFE) && _POSIX_THREADS > 0
+#define DNS_THREAD_SAFE 1
+#else
+#define DNS_THREAD_SAFE 0
+#endif
+#endif
+
+#ifndef HAVE__STATIC_ASSERT
+#define HAVE__STATIC_ASSERT \
+       (dns_has_extension(c_static_assert) || DNS_GNUC_PREREQ(4,6,0) || \
+        __C11FEATURES__ || __STDC_VERSION__ >= 201112L)
+#endif
+
+#ifndef HAVE_STATIC_ASSERT
+#if DNS_GNUC_PREREQ(0,0,0) && !DNS_GNUC_PREREQ(4,6,0)
+#define HAVE_STATIC_ASSERT 0 /* glibc doesn't check GCC version */
+#else
+#define HAVE_STATIC_ASSERT (defined static_assert)
+#endif
+#endif
+
+#if HAVE_STATIC_ASSERT
+#define dns_static_assert(cond, msg) static_assert(cond, msg)
+#elif HAVE__STATIC_ASSERT
+#define dns_static_assert(cond, msg) _Static_assert(cond, msg)
+#else
+#define dns_static_assert(cond, msg) extern char DNS_PP_XPASTE(dns_assert_, __LINE__)[sizeof (int[1 - 2*!(cond)])]
+#endif
+
+
+/*
+ * D E B U G  M A C R O S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+int *dns_debug_p(void) {
+       static int debug;
+
+       return &debug;
+} /* dns_debug_p() */
+
+#if DNS_DEBUG
+
+#undef DNS_DEBUG
+#define DNS_DEBUG dns_debug
+
+#define DNS_SAY_(fmt, ...) \
+       do { if (DNS_DEBUG > 0) fprintf(stderr, fmt "%.1s", __func__, __LINE__, __VA_ARGS__); } while (0)
+#define DNS_SAY(...) DNS_SAY_("@@ (%s:%d) " __VA_ARGS__, "\n")
+#define DNS_HAI DNS_SAY("HAI")
+
+#define DNS_SHOW_(P, fmt, ...) do {                                    \
+       if (DNS_DEBUG > 1) {                                            \
+       fprintf(stderr, "@@ BEGIN * * * * * * * * * * * *\n");          \
+       fprintf(stderr, "@@ " fmt "%.0s\n", __VA_ARGS__);               \
+       dns_p_dump((P), stderr);                                        \
+       fprintf(stderr, "@@ END * * * * * * * * * * * * *\n\n");        \
+       }                                                               \
+} while (0)
+
+#define DNS_SHOW(...)  DNS_SHOW_(__VA_ARGS__, "")
+
+#else /* !DNS_DEBUG */
+
+#undef DNS_DEBUG
+#define DNS_DEBUG 0
+
+#define DNS_SAY(...)
+#define DNS_HAI
+#define DNS_SHOW(...)
+
+#endif /* DNS_DEBUG */
+
+#define DNS_CARP(...) DNS_SAY(__VA_ARGS__)
+
+
+/*
+ * V E R S I O N  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+const char *dns_vendor(void) {
+       return DNS_VENDOR;
+} /* dns_vendor() */
+
+
+int dns_v_rel(void) {
+       return DNS_V_REL;
+} /* dns_v_rel() */
+
+
+int dns_v_abi(void) {
+       return DNS_V_ABI;
+} /* dns_v_abi() */
+
+
+int dns_v_api(void) {
+       return DNS_V_API;
+} /* dns_v_api() */
+
+
+/*
+ * E R R O R  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#if _WIN32
+
+#define DNS_EINTR      WSAEINTR
+#define DNS_EINPROGRESS        WSAEINPROGRESS
+#define DNS_EISCONN    WSAEISCONN
+#define DNS_EWOULDBLOCK        WSAEWOULDBLOCK
+#define DNS_EALREADY   WSAEALREADY
+#define DNS_EAGAIN     EAGAIN
+#define DNS_ETIMEDOUT  WSAETIMEDOUT
+
+#define dns_syerr()    ((int)GetLastError())
+#define dns_soerr()    ((int)WSAGetLastError())
+
+#else
+
+#define DNS_EINTR      EINTR
+#define DNS_EINPROGRESS        EINPROGRESS
+#define DNS_EISCONN    EISCONN
+#define DNS_EWOULDBLOCK        EWOULDBLOCK
+#define DNS_EALREADY   EALREADY
+#define DNS_EAGAIN     EAGAIN
+#define DNS_ETIMEDOUT  ETIMEDOUT
+
+#define dns_syerr()    errno
+#define dns_soerr()    errno
+
+#endif
+
+
+const char *dns_strerror(int error) {
+       switch (error) {
+       case DNS_ENOBUFS:
+               return "DNS packet buffer too small";
+       case DNS_EILLEGAL:
+               return "Illegal DNS RR name or data";
+       case DNS_EORDER:
+               return "Attempt to push RR out of section order";
+       case DNS_ESECTION:
+               return "Invalid section specified";
+       case DNS_EUNKNOWN:
+               return "Unknown DNS error";
+       case DNS_EADDRESS:
+               return "Invalid textual address form";
+       case DNS_ENOQUERY:
+               return "Bad execution state (missing query packet)";
+       case DNS_ENOANSWER:
+               return "Bad execution state (missing answer packet)";
+       case DNS_EFETCHED:
+               return "Answer already fetched";
+       case DNS_ESERVICE:
+               return "The service passed was not recognized for the specified socket type";
+       case DNS_ENONAME:
+               return "The name does not resolve for the supplied parameters";
+       case DNS_EFAIL:
+               return "A non-recoverable error occurred when attempting to resolve the name";
+       case DNS_ECONNFIN:
+               return "Connection closed";
+       case DNS_EVERIFY:
+               return "Reply failed verification";
+       default:
+               return strerror(error);
+       } /* switch() */
+} /* dns_strerror() */
+
+
+/*
+ * A T O M I C  R O U T I N E S
+ *
+ * Use GCC's __atomic built-ins if possible. Unlike the __sync built-ins, we
+ * can use the preprocessor to detect API and, more importantly, ISA
+ * support. We want to avoid linking headaches where the API depends on an
+ * external library if the ISA (e.g. i386) doesn't support lockless
+ * operation.
+ *
+ * TODO: Support C11's atomic API. Although that may require some finesse
+ * with how we define some public types, such as dns_atomic_t and struct
+ * dns_resolv_conf.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef HAVE___ATOMIC_FETCH_ADD
+#define HAVE___ATOMIC_FETCH_ADD (defined __ATOMIC_RELAXED)
+#endif
+
+#ifndef HAVE___ATOMIC_FETCH_SUB
+#define HAVE___ATOMIC_FETCH_SUB HAVE___ATOMIC_FETCH_ADD
+#endif
+
+#ifndef DNS_ATOMIC_FETCH_ADD
+#if HAVE___ATOMIC_FETCH_ADD && __GCC_ATOMIC_LONG_LOCK_FREE == 2
+#define DNS_ATOMIC_FETCH_ADD(i) __atomic_fetch_add((i), 1, __ATOMIC_RELAXED)
+#else
+#pragma message("no atomic_fetch_add available")
+#define DNS_ATOMIC_FETCH_ADD(i) ((*(i))++)
+#endif
+#endif
+
+#ifndef DNS_ATOMIC_FETCH_SUB
+#if HAVE___ATOMIC_FETCH_SUB && __GCC_ATOMIC_LONG_LOCK_FREE == 2
+#define DNS_ATOMIC_FETCH_SUB(i) __atomic_fetch_sub((i), 1, __ATOMIC_RELAXED)
+#else
+#pragma message("no atomic_fetch_sub available")
+#define DNS_ATOMIC_FETCH_SUB(i) ((*(i))--)
+#endif
+#endif
+
+static inline unsigned dns_atomic_fetch_add(dns_atomic_t *i) {
+       return DNS_ATOMIC_FETCH_ADD(i);
+} /* dns_atomic_fetch_add() */
+
+
+static inline unsigned dns_atomic_fetch_sub(dns_atomic_t *i) {
+       return DNS_ATOMIC_FETCH_SUB(i);
+} /* dns_atomic_fetch_sub() */
+
+
+/*
+ * C R Y P T O  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/*
+ * P R N G
+ */
+
+#ifndef DNS_RANDOM
+#if defined(HAVE_ARC4RANDOM)   \
+ || defined(__OpenBSD__)       \
+ || defined(__FreeBSD__)       \
+ || defined(__NetBSD__)                \
+ || defined(__APPLE__)
+#define DNS_RANDOM     arc4random
+#elif __linux
+#define DNS_RANDOM     random
+#else
+#define DNS_RANDOM     rand
+#endif
+#endif
+
+#define DNS_RANDOM_arc4random  1
+#define DNS_RANDOM_random      2
+#define DNS_RANDOM_rand                3
+#define DNS_RANDOM_RAND_bytes  4
+
+#define DNS_RANDOM_OPENSSL     (DNS_RANDOM_RAND_bytes == DNS_PP_XPASTE(DNS_RANDOM_, DNS_RANDOM))
+
+#if DNS_RANDOM_OPENSSL
+#include <openssl/rand.h>
+#endif
+
+static unsigned dns_random_(void) {
+#if DNS_RANDOM_OPENSSL
+       unsigned r;
+       _Bool ok;
+
+       ok = (1 == RAND_bytes((unsigned char *)&r, sizeof r));
+       assert(ok && "1 == RAND_bytes()");
+
+       return r;
+#else
+       return DNS_RANDOM();
+#endif
+} /* dns_random_() */
+
+dns_random_f **dns_random_p(void) {
+       static dns_random_f *random_f = &dns_random_;
+
+       return &random_f;
+} /* dns_random_p() */
+
+
+/*
+ * P E R M U T A T I O N  G E N E R A T O R
+ */
+
+#define DNS_K_TEA_KEY_SIZE     16
+#define DNS_K_TEA_BLOCK_SIZE   8
+#define DNS_K_TEA_CYCLES       32
+#define DNS_K_TEA_MAGIC                0x9E3779B9U
+
+struct dns_k_tea {
+       uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)];
+       unsigned cycles;
+}; /* struct dns_k_tea */
+
+
+static void dns_k_tea_init(struct dns_k_tea *tea, uint32_t key[], unsigned cycles) {
+       memcpy(tea->key, key, sizeof tea->key);
+
+       tea->cycles     = (cycles)? cycles : DNS_K_TEA_CYCLES;
+} /* dns_k_tea_init() */
+
+
+static void dns_k_tea_encrypt(struct dns_k_tea *tea, uint32_t v[], uint32_t *w) {
+       uint32_t y, z, sum, n;
+
+       y       = v[0];
+       z       = v[1];
+       sum     = 0;
+
+       for (n = 0; n < tea->cycles; n++) {
+               sum     += DNS_K_TEA_MAGIC;
+               y       += ((z << 4) + tea->key[0]) ^ (z + sum) ^ ((z >> 5) + tea->key[1]);
+               z       += ((y << 4) + tea->key[2]) ^ (y + sum) ^ ((y >> 5) + tea->key[3]);
+       }
+
+       w[0]    = y;
+       w[1]    = z;
+
+       return /* void */;
+} /* dns_k_tea_encrypt() */
+
+
+/*
+ * Permutation generator, based on a Luby-Rackoff Feistel construction.
+ *
+ * Specifically, this is a generic balanced Feistel block cipher using TEA
+ * (another block cipher) as the pseudo-random function, F. At best it's as
+ * strong as F (TEA), notwithstanding the seeding. F could be AES, SHA-1, or
+ * perhaps Bernstein's Salsa20 core; I am naively trying to keep things
+ * simple.
+ *
+ * The generator can create a permutation of any set of numbers, as long as
+ * the size of the set is an even power of 2. This limitation arises either
+ * out of an inherent property of balanced Feistel constructions, or by my
+ * own ignorance. I'll tackle an unbalanced construction after I wrap my
+ * head around Schneier and Kelsey's paper.
+ *
+ * CAVEAT EMPTOR. IANAC.
+ */
+#define DNS_K_PERMUTOR_ROUNDS  8
+
+struct dns_k_permutor {
+       unsigned stepi, length, limit;
+       unsigned shift, mask, rounds;
+
+       struct dns_k_tea tea;
+}; /* struct dns_k_permutor */
+
+
+static inline unsigned dns_k_permutor_powof(unsigned n) {
+       unsigned m, i = 0;
+
+       for (m = 1; m < n; m <<= 1, i++)
+               ;;
+
+       return i;
+} /* dns_k_permutor_powof() */
+
+static void dns_k_permutor_init(struct dns_k_permutor *p, unsigned low, unsigned high) {
+       uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)];
+       unsigned width, i;
+
+       p->stepi        = 0;
+
+       p->length       = (high - low) + 1;
+       p->limit        = high;
+
+       width           = dns_k_permutor_powof(p->length);
+       width           += width % 2;
+
+       p->shift        = width / 2;
+       p->mask         = (1U << p->shift) - 1;
+       p->rounds       = DNS_K_PERMUTOR_ROUNDS;
+
+       for (i = 0; i < lengthof(key); i++)
+               key[i]  = dns_random();
+
+       dns_k_tea_init(&p->tea, key, 0);
+
+       return /* void */;
+} /* dns_k_permutor_init() */
+
+
+static unsigned dns_k_permutor_F(struct dns_k_permutor *p, unsigned k, unsigned x) {
+       uint32_t in[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)], out[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)];
+
+       memset(in, '\0', sizeof in);
+
+       in[0]   = k;
+       in[1]   = x;
+
+       dns_k_tea_encrypt(&p->tea, in, out);
+
+       return p->mask & out[0];
+} /* dns_k_permutor_F() */
+
+
+static unsigned dns_k_permutor_E(struct dns_k_permutor *p, unsigned n) {
+       unsigned l[2], r[2];
+       unsigned i;
+
+       i       = 0;
+       l[i]    = p->mask & (n >> p->shift);
+       r[i]    = p->mask & (n >> 0);
+
+       do {
+               l[(i + 1) % 2]  = r[i % 2];
+               r[(i + 1) % 2]  = l[i % 2] ^ dns_k_permutor_F(p, i, r[i % 2]);
+
+               i++;
+       } while (i < p->rounds - 1);
+
+       return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
+} /* dns_k_permutor_E() */
+
+
+DNS_NOTUSED static unsigned dns_k_permutor_D(struct dns_k_permutor *p, unsigned n) {
+       unsigned l[2], r[2];
+       unsigned i;
+
+       i               = p->rounds - 1;
+       l[i % 2]        = p->mask & (n >> p->shift);
+       r[i % 2]        = p->mask & (n >> 0);
+
+       do {
+               i--;
+
+               r[i % 2]        = l[(i + 1) % 2];
+               l[i % 2]        = r[(i + 1) % 2] ^ dns_k_permutor_F(p, i, l[(i + 1) % 2]);
+       } while (i > 0);
+
+       return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
+} /* dns_k_permutor_D() */
+
+
+static unsigned dns_k_permutor_step(struct dns_k_permutor *p) {
+       unsigned n;
+
+       do {
+               n       = dns_k_permutor_E(p, p->stepi++);
+       } while (n >= p->length);
+
+       return n + (p->limit + 1 - p->length);
+} /* dns_k_permutor_step() */
+
+
+/*
+ * Simple permutation box. Useful for shuffling rrsets from an iterator.
+ * Uses AES s-box to provide good diffusion.
+ *
+ * Seems to pass muster under runs test.
+ *
+ * $ for i in 0 1 2 3 4 5 6 7 8 9; do ./dns shuffle-16 > /tmp/out; done
+ * $ R -q -f /dev/stdin 2>/dev/null <<-EOF | awk '/p-value/{ print $8 }'
+ *     library(lawstat)
+ *     runs.test(scan(file="/tmp/out"))
+ * EOF
+ */
+static unsigned short dns_k_shuffle16(unsigned short n, unsigned s) {
+       static const unsigned char sbox[256] =
+       { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
+         0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+         0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
+         0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+         0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
+         0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+         0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
+         0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+         0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
+         0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+         0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
+         0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+         0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
+         0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+         0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
+         0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+         0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
+         0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+         0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
+         0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+         0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
+         0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+         0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
+         0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+         0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
+         0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+         0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
+         0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+         0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
+         0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+         0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
+         0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
+       unsigned char a, b;
+       unsigned i;
+
+       a = 0xff & (n >> 0);
+       b = 0xff & (n >> 8);
+
+       for (i = 0; i < 4; i++) {
+               a ^= 0xff & s;
+               a = sbox[a] ^ b;
+               b = sbox[b] ^ a;
+               s >>= 8;
+       }
+
+       return ((0xff00 & (a << 8)) | (0x00ff & (b << 0)));
+} /* dns_k_shuffle16() */
+
+/*
+ * S T A T E  M A C H I N E  R O U T I N E S
+ *
+ * Application code should define DNS_SM_RESTORE and DNS_SM_SAVE, and the
+ * local variable pc.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_SM_ENTER \
+       do { \
+       static const int pc0 = __LINE__; \
+       DNS_SM_RESTORE; \
+       switch (pc0 + pc) { \
+       case __LINE__: (void)0
+
+#define DNS_SM_SAVE_AND_DO(do_statement) \
+       do { \
+               pc = __LINE__ - pc0; \
+               DNS_SM_SAVE; \
+               do_statement; \
+               case __LINE__: (void)0; \
+       } while (0)
+
+#define DNS_SM_YIELD(rv) \
+       DNS_SM_SAVE_AND_DO(return (rv))
+
+#define DNS_SM_EXIT \
+       do { goto leave; } while (0)
+
+#define DNS_SM_LEAVE \
+       leave: (void)0; \
+       DNS_SM_SAVE_AND_DO(break); \
+       } \
+       } while (0)
+
+/*
+ * U T I L I T Y  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_MAXINTERVAL 300
+
+struct dns_clock {
+       time_t sample, elapsed;
+}; /* struct dns_clock */
+
+static void dns_begin(struct dns_clock *clk) {
+       clk->sample = time(0);
+       clk->elapsed = 0;
+} /* dns_begin() */
+
+static time_t dns_elapsed(struct dns_clock *clk) {
+       time_t curtime;
+
+       if ((time_t)-1 == time(&curtime))
+               return clk->elapsed;
+
+       if (curtime > clk->sample)
+               clk->elapsed += (time_t)DNS_PP_MIN(difftime(curtime, clk->sample), DNS_MAXINTERVAL);
+
+       clk->sample = curtime;
+
+       return clk->elapsed;
+} /* dns_elapsed() */
+
+
+DNS_NOTUSED static size_t dns_strnlen(const char *src, size_t m) {
+       size_t n = 0;
+
+       while (*src++ && n < m)
+               ++n;
+
+       return n;
+} /* dns_strnlen() */
+
+
+DNS_NOTUSED static size_t dns_strnlcpy(char *dst, size_t lim, const char *src, size_t max) {
+       size_t len = dns_strnlen(src, max), n;
+
+       if (lim > 0) {
+               n = DNS_PP_MIN(lim - 1, len);
+               memcpy(dst, src, n);
+               dst[n] = '\0';
+       }
+
+       return len;
+} /* dns_strnlcpy() */
+
+
+#define DNS_HAVE_SOCKADDR_UN (defined AF_UNIX && !defined _WIN32)
+
+static size_t dns_af_len(int af) {
+       static const size_t table[AF_MAX]       = {
+               [AF_INET6]      = sizeof (struct sockaddr_in6),
+               [AF_INET]       = sizeof (struct sockaddr_in),
+#if DNS_HAVE_SOCKADDR_UN
+               [AF_UNIX]       = sizeof (struct sockaddr_un),
+#endif
+       };
+
+       return table[af];
+} /* dns_af_len() */
+
+#define dns_sa_family(sa)      (((struct sockaddr *)(sa))->sa_family)
+
+#define dns_sa_len(sa)         dns_af_len(dns_sa_family(sa))
+
+
+#define DNS_SA_NOPORT  &dns_sa_noport
+static unsigned short dns_sa_noport;
+
+static unsigned short *dns_sa_port(int af, void *sa) {
+       switch (af) {
+       case AF_INET6:
+               return &((struct sockaddr_in6 *)sa)->sin6_port;
+       case AF_INET:
+               return &((struct sockaddr_in *)sa)->sin_port;
+       default:
+               return DNS_SA_NOPORT;
+       }
+} /* dns_sa_port() */
+
+
+static void *dns_sa_addr(int af, const void *sa, socklen_t *size) {
+       switch (af) {
+       case AF_INET6: {
+               struct in6_addr *in6 = &((struct sockaddr_in6 *)sa)->sin6_addr;
+
+               if (size)
+                       *size = sizeof *in6;
+
+               return in6;
+       }
+       case AF_INET: {
+               struct in_addr *in = &((struct sockaddr_in *)sa)->sin_addr;
+
+               if (size)
+                       *size = sizeof *in;
+
+               return in;
+       }
+       default:
+               if (size)
+                       *size = 0;
+
+               return 0;
+       }
+} /* dns_sa_addr() */
+
+
+#if DNS_HAVE_SOCKADDR_UN
+#define DNS_SUNPATHMAX (sizeof ((struct sockaddr_un *)0)->sun_path)
+#endif
+
+DNS_NOTUSED static void *dns_sa_path(void *sa, socklen_t *size) {
+       switch (dns_sa_family(sa)) {
+#if DNS_HAVE_SOCKADDR_UN
+       case AF_UNIX: {
+               char *path = ((struct sockaddr_un *)sa)->sun_path;
+
+               if (size)
+                       *size = dns_strnlen(path, DNS_SUNPATHMAX);
+
+               return path;
+       }
+#endif
+       default:
+               if (size)
+                       *size = 0;
+
+               return NULL;
+       }
+} /* dns_sa_path() */
+
+
+static int dns_sa_cmp(void *a, void *b) {
+       int cmp, af;
+
+       if ((cmp = dns_sa_family(a) - dns_sa_family(b)))
+               return cmp;
+
+       switch ((af = dns_sa_family(a))) {
+       case AF_INET: {
+               struct in_addr *a4, *b4;
+
+               if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b))))
+                       return cmp;
+
+               a4 = dns_sa_addr(af, a, NULL);
+               b4 = dns_sa_addr(af, b, NULL);
+
+               if (ntohl(a4->s_addr) < ntohl(b4->s_addr))
+                       return -1;
+               if (ntohl(a4->s_addr) > ntohl(b4->s_addr))
+                       return 1;
+
+               return 0;
+       }
+       case AF_INET6: {
+               struct in6_addr *a6, *b6;
+               size_t i;
+
+               if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b))))
+                       return cmp;
+
+               a6 = dns_sa_addr(af, a, NULL);
+               b6 = dns_sa_addr(af, b, NULL);
+
+               /* XXX: do we need to use in6_clearscope()? */
+               for (i = 0; i < sizeof a6->s6_addr; i++) {
+                       if ((cmp = a6->s6_addr[i] - b6->s6_addr[i]))
+                               return cmp;
+               }
+
+               return 0;
+       }
+#if DNS_HAVE_SOCKADDR_UN
+       case AF_UNIX: {
+               char a_path[DNS_SUNPATHMAX + 1], b_path[sizeof a_path];
+
+               dns_strnlcpy(a_path, sizeof a_path, dns_sa_path(a, NULL), DNS_SUNPATHMAX);
+               dns_strnlcpy(b_path, sizeof b_path, dns_sa_path(b, NULL), DNS_SUNPATHMAX);
+
+               return strcmp(a_path, b_path);
+       }
+#endif
+       default:
+               return -1;
+       }
+} /* dns_sa_cmp() */
+
+
+#if _WIN32
+static int dns_inet_pton(int af, const void *src, void *dst) {
+       union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u;
+
+       u.sin.sin_family        = af;
+
+       if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &(int){ sizeof u }))
+               return -1;
+
+       switch (af) {
+       case AF_INET6:
+               *(struct in6_addr *)dst = u.sin6.sin6_addr;
+
+               return 1;
+       case AF_INET:
+               *(struct in_addr *)dst  = u.sin.sin_addr;
+
+               return 1;
+       default:
+               return 0;
+       }
+} /* dns_inet_pton() */
+
+static const char *dns_inet_ntop(int af, const void *src, void *dst, unsigned long lim) {
+       union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u;
+
+       /* NOTE: WSAAddressToString will print .sin_port unless zeroed. */
+       memset(&u, 0, sizeof u);
+
+       u.sin.sin_family        = af;
+
+       switch (af) {
+       case AF_INET6:
+               u.sin6.sin6_addr        = *(struct in6_addr *)src;
+               break;
+       case AF_INET:
+               u.sin.sin_addr          = *(struct in_addr *)src;
+
+               break;
+       default:
+               return 0;
+       }
+
+       if (0 != WSAAddressToStringA((struct sockaddr *)&u, dns_sa_len(&u), (void *)0, dst, &lim))
+               return 0;
+
+       return dst;
+} /* dns_inet_ntop() */
+#else
+#define dns_inet_pton(...)     inet_pton(__VA_ARGS__)
+#define dns_inet_ntop(...)     inet_ntop(__VA_ARGS__)
+#endif
+
+
+static dns_error_t dns_pton(int af, const void *src, void *dst) {
+       switch (dns_inet_pton(af, src, dst)) {
+       case 1:
+               return 0;
+       case -1:
+               return dns_soerr();
+       default:
+               return DNS_EADDRESS;
+       }
+} /* dns_pton() */
+
+
+static dns_error_t dns_ntop(int af, const void *src, void *dst, unsigned long lim) {
+       return (dns_inet_ntop(af, src, dst, lim))? 0 : dns_soerr();
+} /* dns_ntop() */
+
+
+size_t dns_strlcpy(char *dst, const char *src, size_t lim) {
+       char *d         = dst;
+       char *e         = &dst[lim];
+       const char *s   = src;
+
+       if (d < e) {
+               do {
+                       if ('\0' == (*d++ = *s++))
+                               return s - src - 1;
+               } while (d < e);
+
+               d[-1]   = '\0';
+       }
+
+       while (*s++ != '\0')
+               ;;
+
+       return s - src - 1;
+} /* dns_strlcpy() */
+
+
+size_t dns_strlcat(char *dst, const char *src, size_t lim) {
+       char *d = memchr(dst, '\0', lim);
+       char *e = &dst[lim];
+       const char *s = src;
+       const char *p;
+
+       if (d && d < e) {
+               do {
+                       if ('\0' == (*d++ = *s++))
+                               return d - dst - 1;
+               } while (d < e);
+
+               d[-1] = '\0';
+       }
+
+       p = s;
+
+       while (*s++ != '\0')
+               ;;
+
+       return lim + (s - p - 1);
+} /* dns_strlcat() */
+
+
+static void *dns_reallocarray(void *p, size_t nmemb, size_t size, dns_error_t *error) {
+       void *rp;
+
+       if (nmemb > 0 && SIZE_MAX / nmemb < size) {
+               *error = EOVERFLOW;
+               return NULL;
+       }
+
+       if (!(rp = realloc(p, nmemb * size)))
+               *error = (errno)? errno : EINVAL;
+
+       return rp;
+} /* dns_reallocarray() */
+
+
+#if _WIN32
+
+static char *dns_strsep(char **sp, const char *delim) {
+       char *p;
+
+       if (!(p = *sp))
+               return 0;
+
+       *sp += strcspn(p, delim);
+
+       if (**sp != '\0') {
+               **sp = '\0';
+               ++*sp;
+       } else
+               *sp = NULL;
+
+       return p;
+} /* dns_strsep() */
+
+#else
+#define dns_strsep(...)        strsep(__VA_ARGS__)
+#endif
+
+
+#if _WIN32
+#define strcasecmp(...)                _stricmp(__VA_ARGS__)
+#define strncasecmp(...)       _strnicmp(__VA_ARGS__)
+#endif
+
+
+static inline _Bool dns_isalpha(unsigned char c) {
+       return isalpha(c);
+} /* dns_isalpha() */
+
+static inline _Bool dns_isdigit(unsigned char c) {
+       return isdigit(c);
+} /* dns_isdigit() */
+
+static inline _Bool dns_isalnum(unsigned char c) {
+       return isalnum(c);
+} /* dns_isalnum() */
+
+static inline _Bool dns_isspace(unsigned char c) {
+       return isspace(c);
+} /* dns_isspace() */
+
+static inline _Bool dns_isgraph(unsigned char c) {
+       return isgraph(c);
+} /* dns_isgraph() */
+
+
+static int dns_poll(int fd, short events, int timeout) {
+       fd_set rset, wset;
+
+       if (!events)
+               return 0;
+
+        if (fd < 0 || (unsigned)fd >= FD_SETSIZE)
+          return EINVAL;
+
+       FD_ZERO(&rset);
+       FD_ZERO(&wset);
+
+       if (events & DNS_POLLIN)
+               FD_SET(fd, &rset);
+
+       if (events & DNS_POLLOUT)
+               FD_SET(fd, &wset);
+
+       select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &(struct timeval){ timeout, 0 } : NULL);
+
+       return 0;
+} /* dns_poll() */
+
+
+#if !_WIN32
+DNS_NOTUSED static int dns_sigmask(int how, const sigset_t *set, sigset_t *oset) {
+#if DNS_THREAD_SAFE
+       return pthread_sigmask(how, set, oset);
+#else
+       return (0 == sigprocmask(how, set, oset))? 0 : errno;
+#endif
+} /* dns_sigmask() */
+#endif
+
+
+static size_t dns_send(int fd, const void *src, size_t len, int flags, dns_error_t *error) {
+       long n = send(fd, src, len, flags);
+
+       if (n < 0) {
+               *error = dns_soerr();
+               return 0;
+       } else {
+               *error = 0;
+               return n;
+       }
+} /* dns_send() */
+
+static size_t dns_recv(int fd, void *dst, size_t lim, int flags, dns_error_t *error) {
+       long n = recv(fd, dst, lim, flags);
+
+       if (n < 0) {
+               *error = dns_soerr();
+               return 0;
+       } else if (n == 0) {
+               *error = (lim > 0)? DNS_ECONNFIN : EINVAL;
+               return 0;
+       } else {
+               *error = 0;
+               return n;
+       }
+} /* dns_recv() */
+
+static size_t dns_send_nopipe(int fd, const void *src, size_t len, int flags, dns_error_t *_error) {
+#if _WIN32 || !defined SIGPIPE || defined SO_NOSIGPIPE
+       return dns_send(fd, src, len, flags, _error);
+#elif defined MSG_NOSIGNAL
+       return dns_send(fd, src, len, (flags|MSG_NOSIGNAL), _error);
+#elif _POSIX_REALTIME_SIGNALS > 0 /* require sigtimedwait */
+       /*
+        * SIGPIPE handling similar to the approach described in
+        * http://krokisplace.blogspot.com/2010/02/suppressing-sigpipe-in-library.html
+        */
+       sigset_t pending, blocked, piped;
+       size_t count;
+       int error;
+
+       sigemptyset(&pending);
+       sigpending(&pending);
+
+       if (!sigismember(&pending, SIGPIPE)) {
+               sigemptyset(&piped);
+               sigaddset(&piped, SIGPIPE);
+               sigemptyset(&blocked);
+
+               if ((error = dns_sigmask(SIG_BLOCK, &piped, &blocked)))
+                       goto error;
+       }
+
+       count = dns_send(fd, src, len, flags, &error);
+
+       if (!sigismember(&pending, SIGPIPE)) {
+               int saved = error;
+
+               if (!count && error == EPIPE) {
+                       while (-1 == sigtimedwait(&piped, NULL, &(struct timespec){ 0, 0 }) && errno == EINTR)
+                               ;;
+               }
+
+               if ((error = dns_sigmask(SIG_SETMASK, &blocked, NULL)))
+                       goto error;
+
+               error = saved;
+       }
+
+       *_error = error;
+       return count;
+error:
+       *_error = error;
+       return 0;
+#else
+#error "unable to suppress SIGPIPE"
+       return dns_send(fd, src, len, flags, _error);
+#endif
+} /* dns_send_nopipe() */
+
+
+static dns_error_t dns_connect(int fd, const struct sockaddr *addr, socklen_t addrlen) {
+       if (0 != connect(fd, addr, addrlen))
+               return dns_soerr();
+       return 0;
+} /* dns_connect() */
+
+
+#define DNS_FOPEN_STDFLAGS "rwabt+"
+
+static dns_error_t dns_fopen_addflag(char *dst, const char *src, size_t lim, int fc) {
+       char *p = dst, *pe = dst + lim;
+
+       /* copy standard flags */
+       while (*src && strchr(DNS_FOPEN_STDFLAGS, *src)) {
+               if (!(p < pe))
+                       return ENOMEM;
+               *p++ = *src++;
+       }
+
+       /* append flag to standard flags */
+       if (!(p < pe))
+               return ENOMEM;
+       *p++ = fc;
+
+       /* copy remaining mode string, including '\0' */
+       do {
+               if (!(p < pe))
+                       return ENOMEM;
+       } while ((*p++ = *src++));
+
+       return 0;
+} /* dns_fopen_addflag() */
+
+static FILE *dns_fopen(const char *path, const char *mode, dns_error_t *_error) {
+       FILE *fp;
+       char mode_cloexec[32];
+       int error;
+
+       assert(path && mode && *mode);
+       if (!*path) {
+               error = EINVAL;
+               goto error;
+       }
+
+#if _WIN32 || _WIN64
+       if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'N')))
+               goto error;
+       if (!(fp = fopen(path, mode_cloexec)))
+               goto syerr;
+#else
+       if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'e')))
+               goto error;
+       if (!(fp = fopen(path, mode_cloexec))) {
+               if (errno != EINVAL)
+                       goto syerr;
+               if (!(fp = fopen(path, mode)))
+                       goto syerr;
+       }
+#endif
+
+       return fp;
+syerr:
+       error = dns_syerr();
+error:
+       *_error = error;
+
+       return NULL;
+} /* dns_fopen() */
+
+
+struct dns_hxd_lines_i {
+       int pc;
+       size_t p;
+};
+
+#define DNS_SM_RESTORE \
+       do { \
+               pc = state->pc; \
+               sp = src + state->p; \
+               se = src + len; \
+       } while (0)
+#define DNS_SM_SAVE \
+       do { \
+               state->p = sp - src; \
+               state->pc = pc; \
+       } while (0)
+
+static size_t dns_hxd_lines(void *dst, size_t lim, const unsigned char *src, size_t len, struct dns_hxd_lines_i *state) {
+       static const unsigned char hex[] = "0123456789abcdef";
+       static const unsigned char tmpl[] = "                                                    |                |\n";
+       unsigned char ln[sizeof tmpl];
+       const unsigned char *sp, *se;
+       unsigned char *h, *g;
+       unsigned i, n;
+       int pc;
+
+       DNS_SM_ENTER;
+
+       while (sp < se) {
+               memcpy(ln, tmpl, sizeof ln);
+
+               h = &ln[2];
+               g = &ln[53];
+
+               for (n = 0; n < 2; n++) {
+                       for (i = 0; i < 8 && se - sp > 0; i++, sp++) {
+                               h[0] = hex[0x0f & (*sp >> 4)];
+                               h[1] = hex[0x0f & (*sp >> 0)];
+                               h += 3;
+
+                               *g++ = (dns_isgraph(*sp))? *sp : '.';
+                       }
+
+                       h++;
+               }
+
+               n = dns_strlcpy(dst, (char *)ln, lim);
+               DNS_SM_YIELD(n);
+       }
+
+       DNS_SM_EXIT;
+       DNS_SM_LEAVE;
+
+       return 0;
+}
+
+#undef DNS_SM_SAVE
+#undef DNS_SM_RESTORE
+
+/*
+ * A R I T H M E T I C  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_CHECK_OVERFLOW(error, r, f, ...) \
+       do { \
+               uintmax_t _r; \
+               *(error) = f(&_r, __VA_ARGS__); \
+               *(r) = _r; \
+       } while (0)
+
+static dns_error_t dns_clamp_overflow(uintmax_t *r, uintmax_t n, uintmax_t clamp) {
+       if (n > clamp) {
+               *r = clamp;
+               return ERANGE;
+       } else {
+               *r = n;
+               return 0;
+       }
+} /* dns_clamp_overflow() */
+
+static dns_error_t dns_add_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) {
+       if (~a < b) {
+               *r = DNS_PP_MIN(clamp, ~UINTMAX_C(0));
+               return ERANGE;
+       } else {
+               return dns_clamp_overflow(r, a + b, clamp);
+       }
+} /* dns_add_overflow() */
+
+static dns_error_t dns_mul_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) {
+       if (a > 0 && UINTMAX_MAX / a < b) {
+               *r = DNS_PP_MIN(clamp, ~UINTMAX_C(0));
+               return ERANGE;
+       } else {
+               return dns_clamp_overflow(r, a * b, clamp);
+       }
+} /* dns_mul_overflow() */
+
+/*
+ * F I X E D - S I Z E D  B U F F E R  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_B_INIT(src, n) { \
+       (unsigned char *)(src), \
+       (unsigned char *)(src), \
+       (unsigned char *)(src) + (n), \
+}
+
+#define DNS_B_FROM(src, n) DNS_B_INIT((src), (n))
+#define DNS_B_INTO(src, n) DNS_B_INIT((src), (n))
+
+struct dns_buf {
+       const unsigned char *base;
+       unsigned char *p;
+       const unsigned char *pe;
+       dns_error_t error;
+       size_t overflow;
+}; /* struct dns_buf */
+
+static inline size_t
+dns_b_tell(struct dns_buf *b)
+{
+       return b->p - b->base;
+}
+
+static inline dns_error_t
+dns_b_setoverflow(struct dns_buf *b, size_t n, dns_error_t error)
+{
+       b->overflow += n;
+       return b->error = error;
+}
+
+DNS_NOTUSED static struct dns_buf *
+dns_b_into(struct dns_buf *b, void *src, size_t n)
+{
+       *b = (struct dns_buf)DNS_B_INTO(src, n);
+
+       return b;
+}
+
+static dns_error_t
+dns_b_putc(struct dns_buf *b, unsigned char uc)
+{
+       if (!(b->p < b->pe))
+               return dns_b_setoverflow(b, 1, DNS_ENOBUFS);
+
+       *b->p++ = uc;
+
+       return 0;
+}
+
+static dns_error_t
+dns_b_pputc(struct dns_buf *b, unsigned char uc, size_t p)
+{
+       size_t pe = b->pe - b->base;
+       if (pe <= p)
+               return dns_b_setoverflow(b, p - pe + 1, DNS_ENOBUFS);
+
+       *((unsigned char *)b->base + p) = uc;
+
+       return 0;
+}
+
+static inline dns_error_t
+dns_b_put16(struct dns_buf *b, uint16_t u)
+{
+       return dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0);
+}
+
+static inline dns_error_t
+dns_b_pput16(struct dns_buf *b, uint16_t u, size_t p)
+{
+       if (dns_b_pputc(b, u >> 8, p) || dns_b_pputc(b, u >> 0, p + 1))
+               return b->error;
+
+       return 0;
+}
+
+DNS_NOTUSED static inline dns_error_t
+dns_b_put32(struct dns_buf *b, uint32_t u)
+{
+       return dns_b_putc(b, u >> 24), dns_b_putc(b, u >> 16),
+           dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0);
+}
+
+static dns_error_t
+dns_b_put(struct dns_buf *b, const void *src, size_t len)
+{
+       size_t n = DNS_PP_MIN((size_t)(b->pe - b->p), len);
+
+       memcpy(b->p, src, n);
+       b->p += n;
+
+       if (n < len)
+               return dns_b_setoverflow(b, len - n, DNS_ENOBUFS);
+
+       return 0;
+}
+
+static dns_error_t
+dns_b_puts(struct dns_buf *b, const void *src)
+{
+       return dns_b_put(b, src, strlen(src));
+}
+
+DNS_NOTUSED static inline dns_error_t
+dns_b_fmtju(struct dns_buf *b, const uintmax_t u, const unsigned width)
+{
+       size_t digits, padding, overflow;
+       uintmax_t r;
+       unsigned char *tp, *te, tc;
+
+       digits = 0;
+       r = u;
+       do {
+               digits++;
+               r /= 10;
+       } while (r);
+
+       padding = width - DNS_PP_MIN(digits, width);
+       overflow = (digits + padding) - DNS_PP_MIN((size_t)(b->pe - b->p), (digits + padding));
+
+       while (padding--) {
+               dns_b_putc(b, '0');
+       }
+
+       digits = 0;
+       tp = b->p;
+       r = u;
+       do {
+               if (overflow < ++digits)
+                       dns_b_putc(b, '0' + (r % 10));
+               r /= 10;
+       } while (r);
+
+       te = b->p;
+       while (tp < te) {
+               tc = *--te;
+               *te = *tp;
+               *tp++ = tc;
+       }
+
+       return b->error;
+}
+
+static void
+dns_b_popc(struct dns_buf *b)
+{
+       if (b->overflow && !--b->overflow)
+               b->error = 0;
+       if (b->p > b->base)
+               b->p--;
+}
+
+static inline const char *
+dns_b_tolstring(struct dns_buf *b, size_t *n)
+{
+       if (b->p < b->pe) {
+               *b->p = '\0';
+               *n = b->p - b->base;
+
+               return (const char *)b->base;
+       } else if (b->p > b->base) {
+               if (b->p[-1] != '\0') {
+                       dns_b_setoverflow(b, 1, DNS_ENOBUFS);
+                       b->p[-1] = '\0';
+               }
+               *n = &b->p[-1] - b->base;
+
+               return (const char *)b->base;
+       } else {
+               *n = 0;
+
+               return "";
+       }
+}
+
+static inline const char *
+dns_b_tostring(struct dns_buf *b)
+{
+       size_t n;
+       return dns_b_tolstring(b, &n);
+}
+
+static inline size_t
+dns_b_strlen(struct dns_buf *b)
+{
+       size_t n;
+       dns_b_tolstring(b, &n);
+       return n;
+}
+
+static inline size_t
+dns_b_strllen(struct dns_buf *b)
+{
+       size_t n = dns_b_strlen(b);
+       return n + b->overflow;
+}
+
+DNS_NOTUSED static const struct dns_buf *
+dns_b_from(const struct dns_buf *b, const void *src, size_t n)
+{
+       *(struct dns_buf *)b = (struct dns_buf)DNS_B_FROM(src, n);
+
+       return b;
+}
+
+static inline int
+dns_b_getc(const struct dns_buf *_b, const int eof)
+{
+       struct dns_buf *b = (struct dns_buf *)_b;
+
+       if (!(b->p < b->pe))
+               return dns_b_setoverflow(b, 1, DNS_EILLEGAL), eof;
+
+       return *b->p++;
+}
+
+static inline intmax_t
+dns_b_get16(const struct dns_buf *b, const intmax_t eof)
+{
+       intmax_t n;
+
+       n = (dns_b_getc(b, 0) << 8);
+       n |= (dns_b_getc(b, 0) << 0);
+
+       return (!b->overflow)? n : eof;
+}
+
+DNS_NOTUSED static inline intmax_t
+dns_b_get32(const struct dns_buf *b, const intmax_t eof)
+{
+       intmax_t n;
+
+       n = (dns_b_get16(b, 0) << 16);
+       n |= (dns_b_get16(b, 0) << 0);
+
+       return (!b->overflow)? n : eof;
+}
+
+static inline dns_error_t
+dns_b_move(struct dns_buf *dst, const struct dns_buf *_src, size_t n)
+{
+       struct dns_buf *src = (struct dns_buf *)_src;
+       size_t src_n = DNS_PP_MIN((size_t)(src->pe - src->p), n);
+       size_t src_r = n - src_n;
+
+       dns_b_put(dst, src->p, src_n);
+       src->p += src_n;
+
+       if (src_r)
+               return dns_b_setoverflow(src, src_r, DNS_EILLEGAL);
+
+       return dst->error;
+}
+
+/*
+ * T I M E  R O U T I N E S
+ *
+ * Most functions still rely on the older time routines defined in the
+ * utility routines section, above.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_TIME_C(n) UINT64_C(n)
+#define DNS_TIME_INF (~DNS_TIME_C(0))
+
+typedef uint64_t dns_time_t;
+typedef dns_time_t dns_microseconds_t;
+
+static dns_error_t dns_time_add(dns_time_t *r, dns_time_t a, dns_time_t b) {
+       int error;
+       DNS_CHECK_OVERFLOW(&error, r, dns_add_overflow, a, b, DNS_TIME_INF);
+       return error;
+}
+
+static dns_error_t dns_time_mul(dns_time_t *r, dns_time_t a, dns_time_t b) {
+       int error;
+       DNS_CHECK_OVERFLOW(&error, r, dns_mul_overflow, a, b, DNS_TIME_INF);
+       return error;
+}
+
+static dns_error_t dns_time_diff(dns_time_t *r, dns_time_t a, dns_time_t b) {
+       if (a < b) {
+               *r = DNS_TIME_C(0);
+               return ERANGE;
+       } else {
+               *r = a - b;
+               return 0;
+       }
+}
+
+static dns_microseconds_t dns_ts2us(const struct timespec *ts, _Bool rup) {
+       if (ts) {
+               dns_time_t sec = DNS_PP_MAX(0, ts->tv_sec);
+               dns_time_t nsec = DNS_PP_MAX(0, ts->tv_nsec);
+               dns_time_t usec = nsec / 1000;
+               dns_microseconds_t r;
+
+               if (rup && nsec % 1000 > 0)
+                       usec++;
+               dns_time_mul(&r, sec, DNS_TIME_C(1000000));
+               dns_time_add(&r, r, usec);
+
+               return r;
+       } else {
+               return DNS_TIME_INF;
+       }
+} /* dns_ts2us() */
+
+static struct timespec *dns_tv2ts(struct timespec *ts, const struct timeval *tv) {
+       if (tv) {
+               ts->tv_sec = tv->tv_sec;
+               ts->tv_nsec = tv->tv_usec * 1000;
+
+               return ts;
+       } else {
+               return NULL;
+       }
+} /* dns_tv2ts() */
+
+static size_t dns_utime_print(void *_dst, size_t lim, dns_microseconds_t us) {
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+
+       dns_b_fmtju(&dst, us / 1000000, 1);
+       dns_b_putc(&dst, '.');
+       dns_b_fmtju(&dst, us % 1000000, 6);
+
+       return dns_b_strllen(&dst);
+} /* dns_utime_print() */
+
+/*
+ * P A C K E T  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+unsigned dns_p_count(struct dns_packet *P, enum dns_section section) {
+       unsigned count;
+
+       switch (section) {
+       case DNS_S_QD:
+               return ntohs(dns_header(P)->qdcount);
+       case DNS_S_AN:
+               return ntohs(dns_header(P)->ancount);
+       case DNS_S_NS:
+               return ntohs(dns_header(P)->nscount);
+       case DNS_S_AR:
+               return ntohs(dns_header(P)->arcount);
+       default:
+               count = 0;
+
+               if (section & DNS_S_QD)
+                       count += ntohs(dns_header(P)->qdcount);
+               if (section & DNS_S_AN)
+                       count += ntohs(dns_header(P)->ancount);
+               if (section & DNS_S_NS)
+                       count += ntohs(dns_header(P)->nscount);
+               if (section & DNS_S_AR)
+                       count += ntohs(dns_header(P)->arcount);
+
+               return count;
+       }
+} /* dns_p_count() */
+
+
+struct dns_packet *dns_p_init(struct dns_packet *P, size_t size) {
+       if (!P)
+               return 0;
+
+       assert(size >= offsetof(struct dns_packet, data) + 12);
+
+       memset(P, 0, sizeof *P);
+       P->size = size - offsetof(struct dns_packet, data);
+       P->end  = 12;
+
+       memset(P->data, '\0', 12);
+
+       return P;
+} /* dns_p_init() */
+
+
+static struct dns_packet *dns_p_reset(struct dns_packet *P) {
+       return dns_p_init(P, offsetof(struct dns_packet, data) + P->size);
+} /* dns_p_reset() */
+
+
+static unsigned short dns_p_qend(struct dns_packet *P) {
+       unsigned short qend     = 12;
+       unsigned i, count       = dns_p_count(P, DNS_S_QD);
+
+       for (i = 0; i < count && qend < P->end; i++) {
+               if (P->end == (qend = dns_d_skip(qend, P)))
+                       goto invalid;
+
+               if (P->end - qend < 4)
+                       goto invalid;
+
+               qend    += 4;
+       }
+
+       return DNS_PP_MIN(qend, P->end);
+invalid:
+       return P->end;
+} /* dns_p_qend() */
+
+
+struct dns_packet *dns_p_make(size_t len, int *error) {
+       struct dns_packet *P;
+       size_t size = dns_p_calcsize(len);
+
+       if (!(P = dns_p_init(malloc(size), size)))
+               *error = dns_syerr();
+
+       return P;
+} /* dns_p_make() */
+
+
+static void dns_p_free(struct dns_packet *P) {
+       free(P);
+} /* dns_p_free() */
+
+
+/* convience routine to free any existing packet before storing new packet */
+static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) {
+       dns_p_free(*dst);
+
+       *dst = src;
+
+       return src;
+} /* dns_p_setptr() */
+
+
+static struct dns_packet *dns_p_movptr(struct dns_packet **dst, struct dns_packet **src) {
+       dns_p_setptr(dst, *src);
+
+       *src = NULL;
+
+       return *dst;
+} /* dns_p_movptr() */
+
+
+int dns_p_grow(struct dns_packet **P) {
+       struct dns_packet *tmp;
+       size_t size;
+       int error;
+
+       if (!*P) {
+               if (!(*P = dns_p_make(DNS_P_QBUFSIZ, &error)))
+                       return error;
+
+               return 0;
+       }
+
+       size = dns_p_sizeof(*P);
+       size |= size >> 1;
+       size |= size >> 2;
+       size |= size >> 4;
+       size |= size >> 8;
+       size++;
+
+       if (size > 65536)
+               return DNS_ENOBUFS;
+
+       if (!(tmp = realloc(*P, dns_p_calcsize(size))))
+               return dns_syerr();
+
+       tmp->size = size;
+       *P = tmp;
+
+       return 0;
+} /* dns_p_grow() */
+
+
+struct dns_packet *dns_p_copy(struct dns_packet *P, const struct dns_packet *P0) {
+       if (!P)
+               return 0;
+
+       P->end  = DNS_PP_MIN(P->size, P0->end);
+
+       memcpy(P->data, P0->data, P->end);
+
+       return P;
+} /* dns_p_copy() */
+
+
+struct dns_packet *dns_p_merge(struct dns_packet *A, enum dns_section Amask, struct dns_packet *B, enum dns_section Bmask, int *error_) {
+       size_t bufsiz = DNS_PP_MIN(65535, ((A)? A->end : 0) + ((B)? B->end : 0));
+       struct dns_packet *M;
+       enum dns_section section;
+       struct dns_rr rr, mr;
+       int error, copy;
+
+       if (!A && B) {
+               A = B;
+               Amask = Bmask;
+               B = 0;
+       }
+
+merge:
+       if (!(M = dns_p_make(bufsiz, &error)))
+               goto error;
+
+       for (section = DNS_S_QD; (DNS_S_ALL & section); section <<= 1) {
+               if (A && (section & Amask)) {
+                       dns_rr_foreach(&rr, A, .section = section) {
+                               if ((error = dns_rr_copy(M, &rr, A)))
+                                       goto error;
+                       }
+               }
+
+               if (B && (section & Bmask)) {
+                       dns_rr_foreach(&rr, B, .section = section) {
+                               copy = 1;
+
+                               dns_rr_foreach(&mr, M, .type = rr.type, .section = DNS_S_ALL) {
+                                       if (!(copy = dns_rr_cmp(&rr, B, &mr, M)))
+                                               break;
+                               }
+
+                               if (copy && (error = dns_rr_copy(M, &rr, B)))
+                                       goto error;
+                       }
+               }
+       }
+
+       return M;
+error:
+       dns_p_setptr(&M, NULL);
+
+       if (error == DNS_ENOBUFS && bufsiz < 65535) {
+               bufsiz = DNS_PP_MIN(65535, bufsiz * 2);
+
+               goto merge;
+       }
+
+       *error_ = error;
+
+       return 0;
+} /* dns_p_merge() */
+
+
+static unsigned short dns_l_skip(unsigned short, const unsigned char *, size_t);
+
+void dns_p_dictadd(struct dns_packet *P, unsigned short dn) {
+       unsigned short lp, lptr, i;
+
+       lp      = dn;
+
+       while (lp < P->end) {
+               if (0xc0 == (0xc0 & P->data[lp]) && P->end - lp >= 2 && lp != dn) {
+                       lptr    = ((0x3f & P->data[lp + 0]) << 8)
+                               | ((0xff & P->data[lp + 1]) << 0);
+
+                       for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) {
+                               if (P->dict[i] == lptr) {
+                                       P->dict[i]      = dn;
+
+                                       return;
+                               }
+                       }
+               }
+
+               lp      = dns_l_skip(lp, P->data, P->end);
+       }
+
+       for (i = 0; i < lengthof(P->dict); i++) {
+               if (!P->dict[i]) {
+                       P->dict[i]      = dn;
+
+                       break;
+               }
+       }
+} /* dns_p_dictadd() */
+
+
+int dns_p_push(struct dns_packet *P, enum dns_section section, const void *dn, size_t dnlen, enum dns_type type, enum dns_class class, unsigned ttl, const void *any) {
+       size_t end = P->end;
+       int error;
+
+       if ((error = dns_d_push(P, dn, dnlen)))
+               goto error;
+
+       if (P->size - P->end < 4)
+               goto nobufs;
+
+       P->data[P->end++] = 0xff & (type >> 8);
+       P->data[P->end++] = 0xff & (type >> 0);
+
+       P->data[P->end++] = 0xff & (class >> 8);
+       P->data[P->end++] = 0xff & (class >> 0);
+
+       if (section == DNS_S_QD)
+               goto update;
+
+       if (P->size - P->end < 6)
+               goto nobufs;
+
+       if (type != DNS_T_OPT)
+               ttl = DNS_PP_MIN(ttl, 0x7fffffffU);
+       P->data[P->end++] = ttl >> 24;
+       P->data[P->end++] = ttl >> 16;
+       P->data[P->end++] = ttl >> 8;
+       P->data[P->end++] = ttl >> 0;
+
+       if ((error = dns_any_push(P, (union dns_any *)any, type)))
+               goto error;
+
+update:
+       switch (section) {
+       case DNS_S_QD:
+               if (dns_p_count(P, DNS_S_AN|DNS_S_NS|DNS_S_AR))
+                       goto order;
+
+               if (!P->memo.qd.base && (error = dns_p_study(P)))
+                       goto error;
+
+               dns_header(P)->qdcount = htons(ntohs(dns_header(P)->qdcount) + 1);
+
+               P->memo.qd.end  = P->end;
+               P->memo.an.base = P->end;
+               P->memo.an.end  = P->end;
+               P->memo.ns.base = P->end;
+               P->memo.ns.end  = P->end;
+               P->memo.ar.base = P->end;
+               P->memo.ar.end  = P->end;
+
+               break;
+       case DNS_S_AN:
+               if (dns_p_count(P, DNS_S_NS|DNS_S_AR))
+                       goto order;
+
+               if (!P->memo.an.base && (error = dns_p_study(P)))
+                       goto error;
+
+               dns_header(P)->ancount = htons(ntohs(dns_header(P)->ancount) + 1);
+
+               P->memo.an.end  = P->end;
+               P->memo.ns.base = P->end;
+               P->memo.ns.end  = P->end;
+               P->memo.ar.base = P->end;
+               P->memo.ar.end  = P->end;
+
+               break;
+       case DNS_S_NS:
+               if (dns_p_count(P, DNS_S_AR))
+                       goto order;
+
+               if (!P->memo.ns.base && (error = dns_p_study(P)))
+                       goto error;
+
+               dns_header(P)->nscount = htons(ntohs(dns_header(P)->nscount) + 1);
+
+               P->memo.ns.end  = P->end;
+               P->memo.ar.base = P->end;
+               P->memo.ar.end  = P->end;
+
+               break;
+       case DNS_S_AR:
+               if (!P->memo.ar.base && (error = dns_p_study(P)))
+                       goto error;
+
+               dns_header(P)->arcount = htons(ntohs(dns_header(P)->arcount) + 1);
+
+               P->memo.ar.end = P->end;
+
+               if (type == DNS_T_OPT && !P->memo.opt.p) {
+                       P->memo.opt.p = end;
+                       P->memo.opt.maxudp = class;
+                       P->memo.opt.ttl = ttl;
+               }
+
+               break;
+       default:
+               error = DNS_ESECTION;
+
+               goto error;
+       } /* switch() */
+
+       return 0;
+nobufs:
+       error = DNS_ENOBUFS;
+
+       goto error;
+order:
+       error = DNS_EORDER;
+
+       goto error;
+error:
+       P->end = end;
+
+       return error;
+} /* dns_p_push() */
+
+#define DNS_SM_RESTORE do { pc = state->pc; error = state->error; } while (0)
+#define DNS_SM_SAVE do { state->error = error; state->pc = pc; } while (0)
+
+struct dns_p_lines_i {
+       int pc;
+       enum dns_section section;
+       struct dns_rr rr;
+       int error;
+};
+
+static size_t dns_p_lines_fmt(void *dst, size_t lim, dns_error_t *_error, const char *fmt, ...) {
+       va_list ap;
+       int error = 0, n;
+
+       va_start(ap, fmt);
+       if ((n = vsnprintf(dst, lim, fmt, ap)) < 0)
+               error = errno;
+       va_end(ap);
+
+       *_error = error;
+       return DNS_PP_MAX(n, 0);
+} /* dns_p_lines_fmt() */
+
+#define DNS_P_LINE(...) \
+       do { \
+               len = dns_p_lines_fmt(dst, lim, &error, __VA_ARGS__); \
+               if (len == 0 && error) \
+                       goto error; \
+               DNS_SM_YIELD(len); \
+       } while (0)
+
+static size_t dns_p_lines(void *dst, size_t lim, dns_error_t *_error, struct dns_packet *P, struct dns_rr_i *I, struct dns_p_lines_i *state) {
+       int error, pc;
+       size_t len;
+
+       *_error = 0;
+
+       DNS_SM_ENTER;
+
+       DNS_P_LINE(";; [HEADER]\n");
+       DNS_P_LINE(";;    qid : %d\n", ntohs(dns_header(P)->qid));
+       DNS_P_LINE(";;     qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr);
+       DNS_P_LINE(";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode);
+       DNS_P_LINE(";;     aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa);
+       DNS_P_LINE(";;     tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc);
+       DNS_P_LINE(";;     rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd);
+       DNS_P_LINE(";;     ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra);
+       DNS_P_LINE(";;  rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P));
+
+       while (dns_rr_grep(&state->rr, 1, I, P, &error)) {
+               if (state->section != state->rr.section) {
+                       DNS_P_LINE("\n");
+                       DNS_P_LINE(";; [%s:%d]\n", dns_strsection(state->rr.section), dns_p_count(P, state->rr.section));
+               }
+
+               if (!(len = dns_rr_print(dst, lim, &state->rr, P, &error)))
+                       goto error;
+               dns_strlcat(dst, "\n", lim);
+               DNS_SM_YIELD(len + 1);
+
+               state->section = state->rr.section;
+       }
+
+       if (error)
+               goto error;
+
+       DNS_SM_EXIT;
+error:
+       for (;;) {
+               *_error = error;
+               DNS_SM_YIELD(0);
+       }
+
+       DNS_SM_LEAVE;
+
+       *_error = 0;
+       return 0;
+} /* dns_p_lines() */
+
+#undef DNS_P_LINE
+#undef DNS_SM_SAVE
+#undef DNS_SM_RESTORE
+
+static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) {
+       struct dns_p_lines_i lines = { 0 };
+       char line[sizeof (union dns_any) * 2];
+       size_t len;
+       int error;
+
+       while ((len = dns_p_lines(line, sizeof line, &error, P, I, &lines))) {
+               if (len < sizeof line) {
+                       fwrite(line, 1, len, fp);
+               } else {
+                       fwrite(line, 1, sizeof line - 1, fp);
+                       fputc('\n', fp);
+               }
+       }
+} /* dns_p_dump3() */
+
+
+void dns_p_dump(struct dns_packet *P, FILE *fp) {
+       dns_p_dump3(P, dns_rr_i_new(P, .section = 0), fp);
+} /* dns_p_dump() */
+
+
+static void dns_s_unstudy(struct dns_s_memo *m)
+       { m->base = 0; m->end = 0; }
+
+static void dns_m_unstudy(struct dns_p_memo *m) {
+       dns_s_unstudy(&m->qd);
+       dns_s_unstudy(&m->an);
+       dns_s_unstudy(&m->ns);
+       dns_s_unstudy(&m->ar);
+       m->opt.p = 0;
+       m->opt.maxudp = 0;
+       m->opt.ttl = 0;
+} /* dns_m_unstudy() */
+
+static int dns_s_study(struct dns_s_memo *m, enum dns_section section, unsigned short base, struct dns_packet *P) {
+       unsigned short count, rp;
+
+       count = dns_p_count(P, section);
+
+       for (rp = base; count && rp < P->end; count--)
+               rp = dns_rr_skip(rp, P);
+
+       m->base = base;
+       m->end  = rp;
+
+       return 0;
+} /* dns_s_study() */
+
+static int dns_m_study(struct dns_p_memo *m, struct dns_packet *P) {
+       struct dns_rr rr;
+       int error;
+
+       if ((error = dns_s_study(&m->qd, DNS_S_QD, 12, P)))
+               goto error;
+       if ((error = dns_s_study(&m->an, DNS_S_AN, m->qd.end, P)))
+               goto error;
+       if ((error = dns_s_study(&m->ns, DNS_S_NS, m->an.end, P)))
+               goto error;
+       if ((error = dns_s_study(&m->ar, DNS_S_AR, m->ns.end, P)))
+               goto error;
+
+       m->opt.p = 0;
+       m->opt.maxudp = 0;
+       m->opt.ttl = 0;
+       dns_rr_foreach(&rr, P, .type = DNS_T_OPT, .section = DNS_S_AR) {
+               m->opt.p = rr.dn.p;
+               m->opt.maxudp = rr.class;
+               m->opt.ttl = rr.ttl;
+               break;
+       }
+
+       return 0;
+error:
+       dns_m_unstudy(m);
+
+       return error;
+} /* dns_m_study() */
+
+int dns_p_study(struct dns_packet *P) {
+       return dns_m_study(&P->memo, P);
+} /* dns_p_study() */
+
+
+enum dns_rcode dns_p_rcode(struct dns_packet *P) {
+       return 0xfff & ((P->memo.opt.ttl >> 20) | dns_header(P)->rcode);
+} /* dns_p_rcode() */
+
+
+/*
+ * Q U E R Y  P A C K E T  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_Q_RD    0x1 /* recursion desired */
+#define DNS_Q_EDNS0 0x2 /* include OPT RR */
+
+static dns_error_t
+dns_q_make2(struct dns_packet **_Q, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass, int qflags)
+{
+       struct dns_packet *Q = NULL;
+       int error;
+
+       if (dns_p_movptr(&Q, _Q)) {
+               dns_p_reset(Q);
+       } else if (!(Q = dns_p_make(DNS_P_QBUFSIZ, &error))) {
+               goto error;
+       }
+
+       if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, qtype, qclass, 0, 0)))
+               goto error;
+
+       dns_header(Q)->rd = !!(qflags & DNS_Q_RD);
+
+       if (qflags & DNS_Q_EDNS0) {
+               struct dns_opt opt = DNS_OPT_INIT(&opt);
+
+               opt.version = 0; /* RFC 6891 version */
+               opt.maxudp = 4096;
+
+               if ((error = dns_p_push(Q, DNS_S_AR, ".", 1, DNS_T_OPT, dns_opt_class(&opt), dns_opt_ttl(&opt), &opt)))
+                       goto error;
+       }
+
+       *_Q = Q;
+
+       return 0;
+error:
+       dns_p_free(Q);
+
+       return error;
+}
+
+static dns_error_t
+dns_q_make(struct dns_packet **Q, const char *qname, enum dns_type qtype, enum dns_class qclass, int qflags)
+{
+       return dns_q_make2(Q, qname, strlen(qname), qtype, qclass, qflags);
+}
+
+static dns_error_t
+dns_q_remake(struct dns_packet **Q, int qflags)
+{
+       char qname[DNS_D_MAXNAME + 1];
+       size_t qlen;
+       struct dns_rr rr;
+       int error;
+
+       assert(Q && *Q);
+       if ((error = dns_rr_parse(&rr, 12, *Q)))
+               return error;
+       if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, *Q, &error)))
+               return error;
+       if (qlen >= sizeof qname)
+               return DNS_EILLEGAL;
+       return dns_q_make2(Q, qname, qlen, rr.type, rr.class, qflags);
+}
+
+/*
+ * D O M A I N  N A M E  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef DNS_D_MAXPTRS
+#define DNS_D_MAXPTRS  127     /* Arbitrary; possible, valid depth is something like packet size / 2 + fudge. */
+#endif
+
+static size_t dns_l_expand(unsigned char *dst, size_t lim, unsigned short src, unsigned short *nxt, const unsigned char *data, size_t end) {
+       unsigned short len;
+       unsigned nptrs  = 0;
+
+retry:
+       if (src >= end)
+               goto invalid;
+
+       switch (0x03 & (data[src] >> 6)) {
+       case 0x00:
+               len     = (0x3f & (data[src++]));
+
+               if (end - src < len)
+                       goto invalid;
+
+               if (lim > 0) {
+                       memcpy(dst, &data[src], DNS_PP_MIN(lim, len));
+
+                       dst[DNS_PP_MIN(lim - 1, len)]   = '\0';
+               }
+
+               *nxt    = src + len;
+
+               return len;
+       case 0x01:
+               goto invalid;
+       case 0x02:
+               goto invalid;
+       case 0x03:
+               if (++nptrs > DNS_D_MAXPTRS)
+                       goto invalid;
+
+               if (end - src < 2)
+                       goto invalid;
+
+               src     = ((0x3f & data[src + 0]) << 8)
+                       | ((0xff & data[src + 1]) << 0);
+
+               goto retry;
+       } /* switch() */
+
+       /* NOT REACHED */
+invalid:
+       *nxt    = end;
+
+       return 0;
+} /* dns_l_expand() */
+
+
+static unsigned short dns_l_skip(unsigned short src, const unsigned char *data, size_t end) {
+       unsigned short len;
+
+       if (src >= end)
+               goto invalid;
+
+       switch (0x03 & (data[src] >> 6)) {
+       case 0x00:
+               len     = (0x3f & (data[src++]));
+
+               if (end - src < len)
+                       goto invalid;
+
+               return (len)? src + len : end;
+       case 0x01:
+               goto invalid;
+       case 0x02:
+               goto invalid;
+       case 0x03:
+               return end;
+       } /* switch() */
+
+       /* NOT REACHED */
+invalid:
+       return end;
+} /* dns_l_skip() */
+
+
+static _Bool dns_d_isanchored(const void *_src, size_t len) {
+       const unsigned char *src = _src;
+       return len > 0 && src[len - 1] == '.';
+} /* dns_d_isanchored() */
+
+
+static size_t dns_d_ndots(const void *_src, size_t len) {
+       const unsigned char *p = _src, *pe = p + len;
+       size_t ndots = 0;
+
+       while ((p = memchr(p, '.', pe - p))) {
+               ndots++;
+               p++;
+       }
+
+       return ndots;
+} /* dns_d_ndots() */
+
+
+static size_t dns_d_trim(void *dst_, size_t lim, const void *src_, size_t len, int flags) {
+       unsigned char *dst = dst_;
+       const unsigned char *src = src_;
+       size_t dp = 0, sp = 0;
+       int lc;
+
+       /* trim any leading dot(s) */
+       while (sp < len && src[sp] == '.')
+               sp++;
+
+       for (lc = 0; sp < len; lc = src[sp++]) {
+               /* trim extra dot(s) */
+               if (src[sp] == '.' && lc == '.')
+                       continue;
+
+               if (dp < lim)
+                       dst[dp] = src[sp];
+
+               dp++;
+       }
+
+       if ((flags & DNS_D_ANCHOR) && lc != '.') {
+               if (dp < lim)
+                       dst[dp] = '.';
+
+               dp++;
+       }
+
+       if (lim > 0)
+               dst[DNS_PP_MIN(dp, lim - 1)] = '\0';
+
+       return dp;
+} /* dns_d_trim() */
+
+
+char *dns_d_init(void *dst, size_t lim, const void *src, size_t len, int flags) {
+       if (flags & DNS_D_TRIM) {
+               dns_d_trim(dst, lim, src, len, flags);
+       } if (flags & DNS_D_ANCHOR) {
+               dns_d_anchor(dst, lim, src, len);
+       } else {
+               memmove(dst, src, DNS_PP_MIN(lim, len));
+
+               if (lim > 0)
+                       ((char *)dst)[DNS_PP_MIN(len, lim - 1)] = '\0';
+       }
+
+       return dst;
+} /* dns_d_init() */
+
+
+size_t dns_d_anchor(void *dst, size_t lim, const void *src, size_t len) {
+       if (len == 0)
+               return 0;
+
+       memmove(dst, src, DNS_PP_MIN(lim, len));
+
+       if (((const char *)src)[len - 1] != '.') {
+               if (len < lim)
+                       ((char *)dst)[len]      = '.';
+               len++;
+       }
+
+       if (lim > 0)
+               ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0';
+
+       return len;
+} /* dns_d_anchor() */
+
+
+size_t dns_d_cleave(void *dst, size_t lim, const void *src, size_t len) {
+       const char *dot;
+
+       /* XXX: Skip any leading dot. Handles cleaving root ".". */
+       if (len == 0 || !(dot = memchr((const char *)src + 1, '.', len - 1)))
+               return 0;
+
+       len     -= dot - (const char *)src;
+
+       /* XXX: Unless root, skip the label's trailing dot. */
+       if (len > 1) {
+               src     = ++dot;
+               len--;
+       } else
+               src     = dot;
+
+       memmove(dst, src, DNS_PP_MIN(lim, len));
+
+       if (lim > 0)
+               ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0';
+
+       return len;
+} /* dns_d_cleave() */
+
+
+size_t dns_d_comp(void *dst_, size_t lim, const void *src_, size_t len, struct dns_packet *P, int *error) {
+       struct { unsigned char *b; size_t p, x; } dst, src;
+       unsigned char ch        = '.';
+
+       dst.b   = dst_;
+       dst.p   = 0;
+       dst.x   = 1;
+
+       src.b   = (unsigned char *)src_;
+       src.p   = 0;
+       src.x   = 0;
+
+       while (src.x < len) {
+               ch      = src.b[src.x];
+
+               if (ch == '.') {
+                       if (dst.p < lim)
+                               dst.b[dst.p]    = (0x3f & (src.x - src.p));
+
+                       dst.p   = dst.x++;
+                       src.p   = ++src.x;
+               } else {
+                       if (dst.x < lim)
+                               dst.b[dst.x]    = ch;
+
+                       dst.x++;
+                       src.x++;
+               }
+       } /* while() */
+
+       if (src.x > src.p) {
+               if (dst.p < lim)
+                       dst.b[dst.p]    = (0x3f & (src.x - src.p));
+
+               dst.p   = dst.x;
+       }
+
+       if (dst.p > 1) {
+               if (dst.p < lim)
+                       dst.b[dst.p]    = 0x00;
+
+               dst.p++;
+       }
+
+#if 1
+       if (dst.p < lim) {
+               struct { unsigned char label[DNS_D_MAXLABEL + 1]; size_t len; unsigned short p, x, y; } a, b;
+               unsigned i;
+
+               a.p     = 0;
+
+               while ((a.len = dns_l_expand(a.label, sizeof a.label, a.p, &a.x, dst.b, lim))) {
+                       for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) {
+                               b.p     = P->dict[i];
+
+                               while ((b.len = dns_l_expand(b.label, sizeof b.label, b.p, &b.x, P->data, P->end))) {
+                                       a.y     = a.x;
+                                       b.y     = b.x;
+
+                                       while (a.len && b.len && 0 == strcasecmp((char *)a.label, (char *)b.label)) {
+                                               a.len = dns_l_expand(a.label, sizeof a.label, a.y, &a.y, dst.b, lim);
+                                               b.len = dns_l_expand(b.label, sizeof b.label, b.y, &b.y, P->data, P->end);
+                                       }
+
+                                       if (a.len == 0 && b.len == 0 && b.p <= 0x3fff) {
+                                               dst.b[a.p++]    = 0xc0
+                                                               | (0x3f & (b.p >> 8));
+                                               dst.b[a.p++]    = (0xff & (b.p >> 0));
+
+                                               /* silence static analyzers */
+                                               dns_assume(a.p > 0);
+
+                                               return a.p;
+                                       }
+
+                                       b.p     = b.x;
+                               } /* while() */
+                       } /* for() */
+
+                       a.p     = a.x;
+               } /* while() */
+       } /* if () */
+#endif
+
+       if (!dst.p)
+               *error = DNS_EILLEGAL;
+
+       return dst.p;
+} /* dns_d_comp() */
+
+
+unsigned short dns_d_skip(unsigned short src, struct dns_packet *P) {
+       unsigned short len;
+
+       while (src < P->end) {
+               switch (0x03 & (P->data[src] >> 6)) {
+               case 0x00:      /* FOLLOWS */
+                       len     = (0x3f & P->data[src++]);
+
+                       if (0 == len) {
+/* success ==> */              return src;
+                       } else if (P->end - src > len) {
+                               src     += len;
+
+                               break;
+                       } else
+                               goto invalid;
+
+                       /* NOT REACHED */
+               case 0x01:      /* RESERVED */
+                       goto invalid;
+               case 0x02:      /* RESERVED */
+                       goto invalid;
+               case 0x03:      /* POINTER */
+                       if (P->end - src < 2)
+                               goto invalid;
+
+                       src     += 2;
+
+/* success ==> */      return src;
+               } /* switch() */
+       } /* while() */
+
+invalid:
+       return P->end;
+} /* dns_d_skip() */
+
+
+#include <stdio.h>
+
+size_t dns_d_expand(void *dst, size_t lim, unsigned short src, struct dns_packet *P, int *error) {
+       size_t dstp     = 0;
+       unsigned nptrs  = 0;
+       unsigned char len;
+
+       while (src < P->end) {
+               switch ((0x03 & (P->data[src] >> 6))) {
+               case 0x00:      /* FOLLOWS */
+                       len     = (0x3f & P->data[src]);
+
+                       if (0 == len) {
+                               if (dstp == 0) {
+                                       if (dstp < lim)
+                                               ((unsigned char *)dst)[dstp]    = '.';
+
+                                       dstp++;
+                               }
+
+                               /* NUL terminate */
+                               if (lim > 0)
+                                       ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)]       = '\0';
+
+/* success ==> */              return dstp;
+                       }
+
+                       src++;
+
+                       if (P->end - src < len)
+                               goto toolong;
+
+                       if (dstp < lim)
+                               memcpy(&((unsigned char *)dst)[dstp], &P->data[src], DNS_PP_MIN(len, lim - dstp));
+
+                       src     += len;
+                       dstp    += len;
+
+                       if (dstp < lim)
+                               ((unsigned char *)dst)[dstp]    = '.';
+
+                       dstp++;
+
+                       nptrs   = 0;
+
+                       continue;
+               case 0x01:      /* RESERVED */
+                       goto reserved;
+               case 0x02:      /* RESERVED */
+                       goto reserved;
+               case 0x03:      /* POINTER */
+                       if (++nptrs > DNS_D_MAXPTRS)
+                               goto toolong;
+
+                       if (P->end - src < 2)
+                               goto toolong;
+
+                       src     = ((0x3f & P->data[src + 0]) << 8)
+                               | ((0xff & P->data[src + 1]) << 0);
+
+                       continue;
+               } /* switch() */
+       } /* while() */
+
+toolong:
+       *error  = DNS_EILLEGAL;
+
+       if (lim > 0)
+               ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)]       = '\0';
+
+       return 0;
+reserved:
+       *error  = DNS_EILLEGAL;
+
+       if (lim > 0)
+               ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)]       = '\0';
+
+       return 0;
+} /* dns_d_expand() */
+
+
+int dns_d_push(struct dns_packet *P, const void *dn, size_t len) {
+       size_t lim      = P->size - P->end;
+       unsigned dp     = P->end;
+       int error       = DNS_EILLEGAL; /* silence compiler */
+
+       len     = dns_d_comp(&P->data[dp], lim, dn, len, P, &error);
+
+       if (len == 0)
+               return error;
+       if (len > lim)
+               return DNS_ENOBUFS;
+
+       P->end  += len;
+
+       dns_p_dictadd(P, dp);
+
+       return 0;
+} /* dns_d_push() */
+
+
+size_t dns_d_cname(void *dst, size_t lim, const void *dn, size_t len, struct dns_packet *P, int *error_) {
+       char host[DNS_D_MAXNAME + 1];
+       struct dns_rr_i i;
+       struct dns_rr rr;
+       unsigned depth;
+       int error;
+
+       if (sizeof host <= dns_d_anchor(host, sizeof host, dn, len))
+               { error = ENAMETOOLONG; goto error; }
+
+       for (depth = 0; depth < 7; depth++) {
+               dns_rr_i_init(memset(&i, 0, sizeof i), P);
+
+               i.section       = DNS_S_ALL & ~DNS_S_QD;
+               i.name          = host;
+               i.type          = DNS_T_CNAME;
+
+               if (!dns_rr_grep(&rr, 1, &i, P, &error))
+                       break;
+
+               if ((error = dns_cname_parse((struct dns_cname *)host, &rr, P)))
+                       goto error;
+       }
+
+       return dns_strlcpy(dst, host, lim);
+error:
+       *error_ = error;
+
+       return 0;
+} /* dns_d_cname() */
+
+
+/*
+ * R E S O U R C E  R E C O R D  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+int dns_rr_copy(struct dns_packet *P, struct dns_rr *rr, struct dns_packet *Q) {
+       unsigned char dn[DNS_D_MAXNAME + 1];
+       union dns_any any;
+       size_t len;
+       int error;
+
+       if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, Q, &error)))
+               return error;
+       else if (len >= sizeof dn)
+               return DNS_EILLEGAL;
+
+       if (rr->section != DNS_S_QD && (error = dns_any_parse(dns_any_init(&any, sizeof any), rr, Q)))
+               return error;
+
+       return dns_p_push(P, rr->section, dn, len, rr->type, rr->class, rr->ttl, &any);
+} /* dns_rr_copy() */
+
+
+int dns_rr_parse(struct dns_rr *rr, unsigned short src, struct dns_packet *P) {
+       unsigned short p        = src;
+
+       if (src >= P->end)
+               goto invalid;
+
+       rr->dn.p   = p;
+       rr->dn.len = (p = dns_d_skip(p, P)) - rr->dn.p;
+
+       if (P->end - p < 4)
+               goto invalid;
+
+       rr->type = ((0xff & P->data[p + 0]) << 8)
+                | ((0xff & P->data[p + 1]) << 0);
+
+       rr->class = ((0xff & P->data[p + 2]) << 8)
+                 | ((0xff & P->data[p + 3]) << 0);
+
+       p += 4;
+
+       if (src < dns_p_qend(P)) {
+               rr->section = DNS_S_QUESTION;
+
+               rr->ttl    = 0;
+               rr->rd.p   = 0;
+               rr->rd.len = 0;
+
+               return 0;
+       }
+
+       if (P->end - p < 4)
+               goto invalid;
+
+       rr->ttl = ((0xff & P->data[p + 0]) << 24)
+               | ((0xff & P->data[p + 1]) << 16)
+               | ((0xff & P->data[p + 2]) << 8)
+               | ((0xff & P->data[p + 3]) << 0);
+       if (rr->type != DNS_T_OPT)
+               rr->ttl = DNS_PP_MIN(rr->ttl, 0x7fffffffU);
+
+       p += 4;
+
+       if (P->end - p < 2)
+               goto invalid;
+
+       rr->rd.len = ((0xff & P->data[p + 0]) << 8)
+                  | ((0xff & P->data[p + 1]) << 0);
+       rr->rd.p = p + 2;
+
+       p += 2;
+
+       if (P->end - p < rr->rd.len)
+               goto invalid;
+
+       return 0;
+invalid:
+       return DNS_EILLEGAL;
+} /* dns_rr_parse() */
+
+
+static unsigned short dns_rr_len(const unsigned short src, struct dns_packet *P) {
+       unsigned short rp, rdlen;
+
+       rp      = dns_d_skip(src, P);
+
+       if (P->end - rp < 4)
+               return P->end - src;
+
+       rp      += 4;   /* TYPE, CLASS */
+
+       if (rp <= dns_p_qend(P))
+               return rp - src;
+
+       if (P->end - rp < 6)
+               return P->end - src;
+
+       rp      += 6;   /* TTL, RDLEN */
+
+       rdlen   = ((0xff & P->data[rp - 2]) << 8)
+               | ((0xff & P->data[rp - 1]) << 0);
+
+       if (P->end - rp < rdlen)
+               return P->end - src;
+
+       rp      += rdlen;
+
+       return rp - src;
+} /* dns_rr_len() */
+
+
+unsigned short dns_rr_skip(unsigned short src, struct dns_packet *P) {
+       return src + dns_rr_len(src, P);
+} /* dns_rr_skip() */
+
+
+static enum dns_section dns_rr_section(unsigned short src, struct dns_packet *P) {
+       enum dns_section section;
+       unsigned count, index;
+       unsigned short rp;
+
+       if (src >= P->memo.qd.base && src < P->memo.qd.end)
+               return DNS_S_QD;
+       if (src >= P->memo.an.base && src < P->memo.an.end)
+               return DNS_S_AN;
+       if (src >= P->memo.ns.base && src < P->memo.ns.end)
+               return DNS_S_NS;
+       if (src >= P->memo.ar.base && src < P->memo.ar.end)
+               return DNS_S_AR;
+
+       /* NOTE: Possibly bad memoization. Try it the hard-way. */
+
+       for (rp = 12, index = 0; rp < src && rp < P->end; index++)
+               rp = dns_rr_skip(rp, P);
+
+       section = DNS_S_QD;
+       count   = dns_p_count(P, section);
+
+       while (index >= count && section <= DNS_S_AR) {
+               section <<= 1;
+               count += dns_p_count(P, section);
+       }
+
+       return DNS_S_ALL & section;
+} /* dns_rr_section() */
+
+
+static enum dns_type dns_rr_type(unsigned short src, struct dns_packet *P) {
+       struct dns_rr rr;
+       int error;
+
+       if ((error = dns_rr_parse(&rr, src, P)))
+               return 0;
+
+       return rr.type;
+} /* dns_rr_type() */
+
+
+int dns_rr_cmp(struct dns_rr *r0, struct dns_packet *P0, struct dns_rr *r1, struct dns_packet *P1) {
+       char host0[DNS_D_MAXNAME + 1], host1[DNS_D_MAXNAME + 1];
+       union dns_any any0, any1;
+       int cmp, error;
+       size_t len;
+
+       if ((cmp = r0->type - r1->type))
+               return cmp;
+
+       if ((cmp = r0->class - r1->class))
+               return cmp;
+
+       /*
+        * FIXME: Do label-by-label comparison to handle illegally long names?
+        */
+
+       if (!(len = dns_d_expand(host0, sizeof host0, r0->dn.p, P0, &error))
+       ||  len >= sizeof host0)
+               return -1;
+
+       if (!(len = dns_d_expand(host1, sizeof host1, r1->dn.p, P1, &error))
+       ||  len >= sizeof host1)
+               return 1;
+
+       if ((cmp = strcasecmp(host0, host1)))
+               return cmp;
+
+       if (DNS_S_QD & (r0->section | r1->section)) {
+               if (r0->section == r1->section)
+                       return 0;
+
+               return (r0->section == DNS_S_QD)? -1 : 1;
+       }
+
+       if ((error = dns_any_parse(&any0, r0, P0)))
+               return -1;
+
+       if ((error = dns_any_parse(&any1, r1, P1)))
+               return 1;
+
+       return dns_any_cmp(&any0, r0->type, &any1, r1->type);
+} /* dns_rr_cmp() */
+
+
+static _Bool dns_rr_exists(struct dns_rr *rr0, struct dns_packet *P0, struct dns_packet *P1) {
+       struct dns_rr rr1;
+
+       dns_rr_foreach(&rr1, P1, .section = rr0->section, .type = rr0->type) {
+               if (0 == dns_rr_cmp(rr0, P0, &rr1, P1))
+                       return 1;
+       }
+
+       return 0;
+} /* dns_rr_exists() */
+
+
+static unsigned short dns_rr_offset(struct dns_rr *rr) {
+       return rr->dn.p;
+} /* dns_rr_offset() */
+
+
+static _Bool dns_rr_i_match(struct dns_rr *rr, struct dns_rr_i *i, struct dns_packet *P) {
+       if (i->section && !(rr->section & i->section))
+               return 0;
+
+       if (i->type && rr->type != i->type && i->type != DNS_T_ALL)
+               return 0;
+
+       if (i->class && rr->class != i->class && i->class != DNS_C_ANY)
+               return 0;
+
+       if (i->name) {
+               char dn[DNS_D_MAXNAME + 1];
+               size_t len;
+               int error;
+
+               if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, P, &error))
+               ||  len >= sizeof dn)
+                       return 0;
+
+               if (0 != strcasecmp(dn, i->name))
+                       return 0;
+       }
+
+       if (i->data && i->type && rr->section > DNS_S_QD) {
+               union dns_any rd;
+               int error;
+
+               if ((error = dns_any_parse(&rd, rr, P)))
+                       return 0;
+
+               if (0 != dns_any_cmp(&rd, rr->type, i->data, i->type))
+                       return 0;
+       }
+
+       return 1;
+} /* dns_rr_i_match() */
+
+
+static unsigned short dns_rr_i_start(struct dns_rr_i *i, struct dns_packet *P) {
+       unsigned short rp;
+       struct dns_rr r0, rr;
+       int error;
+
+       if ((i->section & DNS_S_QD) && P->memo.qd.base)
+               rp = P->memo.qd.base;
+       else if ((i->section & DNS_S_AN) && P->memo.an.base)
+               rp = P->memo.an.base;
+       else if ((i->section & DNS_S_NS) && P->memo.ns.base)
+               rp = P->memo.ns.base;
+       else if ((i->section & DNS_S_AR) && P->memo.ar.base)
+               rp = P->memo.ar.base;
+       else
+               rp = 12;
+
+       for (; rp < P->end; rp = dns_rr_skip(rp, P)) {
+               if ((error = dns_rr_parse(&rr, rp, P)))
+                       continue;
+
+               rr.section = dns_rr_section(rp, P);
+
+               if (!dns_rr_i_match(&rr, i, P))
+                       continue;
+
+               r0 = rr;
+
+               goto lower;
+       }
+
+       return P->end;
+lower:
+       if (i->sort == &dns_rr_i_packet)
+               return dns_rr_offset(&r0);
+
+       while ((rp = dns_rr_skip(rp, P)) < P->end) {
+               if ((error = dns_rr_parse(&rr, rp, P)))
+                       continue;
+
+               rr.section = dns_rr_section(rp, P);
+
+               if (!dns_rr_i_match(&rr, i, P))
+                       continue;
+
+               if (i->sort(&rr, &r0, i, P) < 0)
+                       r0 = rr;
+       }
+
+       return dns_rr_offset(&r0);
+} /* dns_rr_i_start() */
+
+
+static unsigned short dns_rr_i_skip(unsigned short rp, struct dns_rr_i *i, struct dns_packet *P) {
+       struct dns_rr r0, r1, rr;
+       int error;
+
+       if ((error = dns_rr_parse(&r0, rp, P)))
+               return P->end;
+
+       r0.section = dns_rr_section(rp, P);
+
+       rp = (i->sort == &dns_rr_i_packet)? dns_rr_skip(rp, P) : 12;
+
+       for (; rp < P->end; rp = dns_rr_skip(rp, P)) {
+               if ((error = dns_rr_parse(&rr, rp, P)))
+                       continue;
+
+               rr.section = dns_rr_section(rp, P);
+
+               if (!dns_rr_i_match(&rr, i, P))
+                       continue;
+
+               if (i->sort(&rr, &r0, i, P) <= 0)
+                       continue;
+
+               r1 = rr;
+
+               goto lower;
+       }
+
+       return P->end;
+lower:
+       if (i->sort == &dns_rr_i_packet)
+               return dns_rr_offset(&r1);
+
+       while ((rp = dns_rr_skip(rp, P)) < P->end) {
+               if ((error = dns_rr_parse(&rr, rp, P)))
+                       continue;
+
+               rr.section = dns_rr_section(rp, P);
+
+               if (!dns_rr_i_match(&rr, i, P))
+                       continue;
+
+               if (i->sort(&rr, &r0, i, P) <= 0)
+                       continue;
+
+               if (i->sort(&rr, &r1, i, P) >= 0)
+                       continue;
+
+               r1 = rr;
+       }
+
+       return dns_rr_offset(&r1);
+} /* dns_rr_i_skip() */
+
+
+int dns_rr_i_packet(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
+       (void)i;
+       (void)P;
+
+       return (int)a->dn.p - (int)b->dn.p;
+} /* dns_rr_i_packet() */
+
+
+int dns_rr_i_order(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
+       int cmp;
+
+       (void)i;
+
+       if ((cmp = a->section - b->section))
+               return cmp;
+
+       if (a->type != b->type)
+               return (int)a->dn.p - (int)b->dn.p;
+
+       return dns_rr_cmp(a, P, b, P);
+} /* dns_rr_i_order() */
+
+
+int dns_rr_i_shuffle(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
+       int cmp;
+
+       (void)i;
+       (void)P;
+
+       while (!i->state.regs[0])
+               i->state.regs[0]        = dns_random();
+
+       if ((cmp = a->section - b->section))
+               return cmp;
+
+       return dns_k_shuffle16(a->dn.p, i->state.regs[0]) - dns_k_shuffle16(b->dn.p, i->state.regs[0]);
+} /* dns_rr_i_shuffle() */
+
+
+struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *i, struct dns_packet *P) {
+       static const struct dns_rr_i i_initializer;
+
+       (void)P;
+
+       i->state        = i_initializer.state;
+       i->saved        = i->state;
+
+       return i;
+} /* dns_rr_i_init() */
+
+
+unsigned dns_rr_grep(struct dns_rr *rr, unsigned lim, struct dns_rr_i *i, struct dns_packet *P, int *error_) {
+       unsigned count  = 0;
+       int error;
+
+       switch (i->state.exec) {
+       case 0:
+               if (!i->sort)
+                       i->sort = &dns_rr_i_packet;
+
+               i->state.next   = dns_rr_i_start(i, P);
+               i->state.exec++;
+
+               /* FALL THROUGH */
+       case 1:
+               while (count < lim && i->state.next < P->end) {
+                       if ((error = dns_rr_parse(rr, i->state.next, P)))
+                               goto error;
+
+                       rr->section     = dns_rr_section(i->state.next, P);
+
+                       rr++;
+                       count++;
+                       i->state.count++;
+
+                       i->state.next   = dns_rr_i_skip(i->state.next, i, P);
+               } /* while() */
+
+               break;
+       } /* switch() */
+
+       return count;
+error:
+       *error_ = error;
+
+       return count;
+} /* dns_rr_grep() */
+
+
+size_t dns_rr_print(void *_dst, size_t lim, struct dns_rr *rr, struct dns_packet *P, int *_error) {
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+       union dns_any any;
+       size_t n;
+       int error;
+
+       if (rr->section == DNS_S_QD)
+               dns_b_putc(&dst, ';');
+
+       if (!(n = dns_d_expand(any.ns.host, sizeof any.ns.host, rr->dn.p, P, &error)))
+               goto error;
+       dns_b_put(&dst, any.ns.host, DNS_PP_MIN(n, sizeof any.ns.host - 1));
+
+       if (rr->section != DNS_S_QD) {
+               dns_b_putc(&dst, ' ');
+               dns_b_fmtju(&dst, rr->ttl, 0);
+       }
+
+       dns_b_putc(&dst, ' ');
+       dns_b_puts(&dst, dns_strclass(rr->class));
+       dns_b_putc(&dst, ' ');
+       dns_b_puts(&dst, dns_strtype(rr->type));
+
+       if (rr->section == DNS_S_QD)
+               goto epilog;
+
+       dns_b_putc(&dst, ' ');
+
+       if ((error = dns_any_parse(dns_any_init(&any, sizeof any), rr, P)))
+               goto error;
+
+       n = dns_any_print(dst.p, dst.pe - dst.p, &any, rr->type);
+       dst.p += DNS_PP_MIN(n, (size_t)(dst.pe - dst.p));
+epilog:
+       return dns_b_strllen(&dst);
+error:
+       *_error = error;
+
+       return 0;
+} /* dns_rr_print() */
+
+
+int dns_a_parse(struct dns_a *a, struct dns_rr *rr, struct dns_packet *P) {
+       unsigned long addr;
+
+       if (rr->rd.len != 4)
+               return DNS_EILLEGAL;
+
+       addr    = ((0xffU & P->data[rr->rd.p + 0]) << 24)
+               | ((0xffU & P->data[rr->rd.p + 1]) << 16)
+               | ((0xffU & P->data[rr->rd.p + 2]) << 8)
+               | ((0xffU & P->data[rr->rd.p + 3]) << 0);
+
+       a->addr.s_addr  = htonl(addr);
+
+       return 0;
+} /* dns_a_parse() */
+
+
+int dns_a_push(struct dns_packet *P, struct dns_a *a) {
+       unsigned long addr;
+
+       if (P->size - P->end < 6)
+               return DNS_ENOBUFS;
+
+       P->data[P->end++]       = 0x00;
+       P->data[P->end++]       = 0x04;
+
+       addr    = ntohl(a->addr.s_addr);
+
+       P->data[P->end++]       = 0xffU & (addr >> 24);
+       P->data[P->end++]       = 0xffU & (addr >> 16);
+       P->data[P->end++]       = 0xffU & (addr >> 8);
+       P->data[P->end++]       = 0xffU & (addr >> 0);
+
+       return 0;
+} /* dns_a_push() */
+
+
+size_t dns_a_arpa(void *_dst, size_t lim, const struct dns_a *a) {
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+       unsigned long octets = ntohl(a->addr.s_addr);
+       unsigned i;
+
+       for (i = 0; i < 4; i++) {
+               dns_b_fmtju(&dst, 0xff & octets, 0);
+               dns_b_putc(&dst, '.');
+               octets >>= 8;
+       }
+
+       dns_b_puts(&dst, "in-addr.arpa.");
+
+       return dns_b_strllen(&dst);
+} /* dns_a_arpa() */
+
+
+int dns_a_cmp(const struct dns_a *a, const struct dns_a *b) {
+       if (ntohl(a->addr.s_addr) < ntohl(b->addr.s_addr))
+               return -1;
+       if (ntohl(a->addr.s_addr) > ntohl(b->addr.s_addr))
+               return 1;
+
+       return 0;
+} /* dns_a_cmp() */
+
+
+size_t dns_a_print(void *dst, size_t lim, struct dns_a *a) {
+       char addr[INET_ADDRSTRLEN + 1]  = "0.0.0.0";
+
+       dns_inet_ntop(AF_INET, &a->addr, addr, sizeof addr);
+
+       return dns_strlcpy(dst, addr, lim);
+} /* dns_a_print() */
+
+
+int dns_aaaa_parse(struct dns_aaaa *aaaa, struct dns_rr *rr, struct dns_packet *P) {
+       if (rr->rd.len != sizeof aaaa->addr.s6_addr)
+               return DNS_EILLEGAL;
+
+       memcpy(aaaa->addr.s6_addr, &P->data[rr->rd.p], sizeof aaaa->addr.s6_addr);
+
+       return 0;
+} /* dns_aaaa_parse() */
+
+
+int dns_aaaa_push(struct dns_packet *P, struct dns_aaaa *aaaa) {
+       if (P->size - P->end < 2 + sizeof aaaa->addr.s6_addr)
+               return DNS_ENOBUFS;
+
+       P->data[P->end++]       = 0x00;
+       P->data[P->end++]       = 0x10;
+
+       memcpy(&P->data[P->end], aaaa->addr.s6_addr, sizeof aaaa->addr.s6_addr);
+
+       P->end  += sizeof aaaa->addr.s6_addr;
+
+       return 0;
+} /* dns_aaaa_push() */
+
+
+int dns_aaaa_cmp(const struct dns_aaaa *a, const struct dns_aaaa *b) {
+       unsigned i;
+       int cmp;
+
+       for (i = 0; i < lengthof(a->addr.s6_addr); i++) {
+               if ((cmp = (a->addr.s6_addr[i] - b->addr.s6_addr[i])))
+                       return cmp;
+       }
+
+       return 0;
+} /* dns_aaaa_cmp() */
+
+
+size_t dns_aaaa_arpa(void *_dst, size_t lim, const struct dns_aaaa *aaaa) {
+       static const unsigned char hex[16] = "0123456789abcdef";
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+       unsigned nyble;
+       int i, j;
+
+       for (i = sizeof aaaa->addr.s6_addr - 1; i >= 0; i--) {
+               nyble = aaaa->addr.s6_addr[i];
+
+               for (j = 0; j < 2; j++) {
+                       dns_b_putc(&dst, hex[0x0f & nyble]);
+                       dns_b_putc(&dst, '.');
+                       nyble >>= 4;
+               }
+       }
+
+       dns_b_puts(&dst, "ip6.arpa.");
+
+       return dns_b_strllen(&dst);
+} /* dns_aaaa_arpa() */
+
+
+size_t dns_aaaa_print(void *dst, size_t lim, struct dns_aaaa *aaaa) {
+       char addr[INET6_ADDRSTRLEN + 1] = "::";
+
+       dns_inet_ntop(AF_INET6, &aaaa->addr, addr, sizeof addr);
+
+       return dns_strlcpy(dst, addr, lim);
+} /* dns_aaaa_print() */
+
+
+int dns_mx_parse(struct dns_mx *mx, struct dns_rr *rr, struct dns_packet *P) {
+       size_t len;
+       int error;
+
+       if (rr->rd.len < 3)
+               return DNS_EILLEGAL;
+
+       mx->preference  = (0xff00 & (P->data[rr->rd.p + 0] << 8))
+                       | (0x00ff & (P->data[rr->rd.p + 1] << 0));
+
+       if (!(len = dns_d_expand(mx->host, sizeof mx->host, rr->rd.p + 2, P, &error)))
+               return error;
+       else if (len >= sizeof mx->host)
+               return DNS_EILLEGAL;
+
+       return 0;
+} /* dns_mx_parse() */
+
+
+int dns_mx_push(struct dns_packet *P, struct dns_mx *mx) {
+       size_t end, len;
+       int error;
+
+       if (P->size - P->end < 5)
+               return DNS_ENOBUFS;
+
+       end     = P->end;
+       P->end  += 2;
+
+       P->data[P->end++]       = 0xff & (mx->preference >> 8);
+       P->data[P->end++]       = 0xff & (mx->preference >> 0);
+
+       if ((error = dns_d_push(P, mx->host, strlen(mx->host))))
+               goto error;
+
+       len     = P->end - end - 2;
+
+       P->data[end + 0]        = 0xff & (len >> 8);
+       P->data[end + 1]        = 0xff & (len >> 0);
+
+       return 0;
+error:
+       P->end  = end;
+
+       return error;
+} /* dns_mx_push() */
+
+
+int dns_mx_cmp(const struct dns_mx *a, const struct dns_mx *b) {
+       int cmp;
+
+       if ((cmp = a->preference - b->preference))
+               return cmp;
+
+       return strcasecmp(a->host, b->host);
+} /* dns_mx_cmp() */
+
+
+size_t dns_mx_print(void *_dst, size_t lim, struct dns_mx *mx) {
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+
+       dns_b_fmtju(&dst, mx->preference, 0);
+       dns_b_putc(&dst, ' ');
+       dns_b_puts(&dst, mx->host);
+
+       return dns_b_strllen(&dst);
+} /* dns_mx_print() */
+
+
+size_t dns_mx_cname(void *dst, size_t lim, struct dns_mx *mx) {
+       return dns_strlcpy(dst, mx->host, lim);
+} /* dns_mx_cname() */
+
+
+int dns_ns_parse(struct dns_ns *ns, struct dns_rr *rr, struct dns_packet *P) {
+       size_t len;
+       int error;
+
+       if (!(len = dns_d_expand(ns->host, sizeof ns->host, rr->rd.p, P, &error)))
+               return error;
+       else if (len >= sizeof ns->host)
+               return DNS_EILLEGAL;
+
+       return 0;
+} /* dns_ns_parse() */
+
+
+int dns_ns_push(struct dns_packet *P, struct dns_ns *ns) {
+       size_t end, len;
+       int error;
+
+       if (P->size - P->end < 3)
+               return DNS_ENOBUFS;
+
+       end     = P->end;
+       P->end  += 2;
+
+       if ((error = dns_d_push(P, ns->host, strlen(ns->host))))
+               goto error;
+
+       len     = P->end - end - 2;
+
+       P->data[end + 0]        = 0xff & (len >> 8);
+       P->data[end + 1]        = 0xff & (len >> 0);
+
+       return 0;
+error:
+       P->end  = end;
+
+       return error;
+} /* dns_ns_push() */
+
+
+int dns_ns_cmp(const struct dns_ns *a, const struct dns_ns *b) {
+       return strcasecmp(a->host, b->host);
+} /* dns_ns_cmp() */
+
+
+size_t dns_ns_print(void *dst, size_t lim, struct dns_ns *ns) {
+       return dns_strlcpy(dst, ns->host, lim);
+} /* dns_ns_print() */
+
+
+size_t dns_ns_cname(void *dst, size_t lim, struct dns_ns *ns) {
+       return dns_strlcpy(dst, ns->host, lim);
+} /* dns_ns_cname() */
+
+
+int dns_cname_parse(struct dns_cname *cname, struct dns_rr *rr, struct dns_packet *P) {
+       return dns_ns_parse((struct dns_ns *)cname, rr, P);
+} /* dns_cname_parse() */
+
+
+int dns_cname_push(struct dns_packet *P, struct dns_cname *cname) {
+       return dns_ns_push(P, (struct dns_ns *)cname);
+} /* dns_cname_push() */
+
+
+int dns_cname_cmp(const struct dns_cname *a, const struct dns_cname *b) {
+       return strcasecmp(a->host, b->host);
+} /* dns_cname_cmp() */
+
+
+size_t dns_cname_print(void *dst, size_t lim, struct dns_cname *cname) {
+       return dns_ns_print(dst, lim, (struct dns_ns *)cname);
+} /* dns_cname_print() */
+
+
+size_t dns_cname_cname(void *dst, size_t lim, struct dns_cname *cname) {
+       return dns_strlcpy(dst, cname->host, lim);
+} /* dns_cname_cname() */
+
+
+int dns_soa_parse(struct dns_soa *soa, struct dns_rr *rr, struct dns_packet *P) {
+       struct { void *dst; size_t lim; } dn[] =
+               { { soa->mname, sizeof soa->mname },
+                 { soa->rname, sizeof soa->rname } };
+       unsigned *ts[] =
+               { &soa->serial, &soa->refresh, &soa->retry, &soa->expire, &soa->minimum };
+       unsigned short rp;
+       unsigned i, j, n;
+       int error;
+
+       /* MNAME / RNAME */
+       if ((rp = rr->rd.p) >= P->end)
+               return DNS_EILLEGAL;
+
+       for (i = 0; i < lengthof(dn); i++) {
+               if (!(n = dns_d_expand(dn[i].dst, dn[i].lim, rp, P, &error)))
+                       return error;
+               else if (n >= dn[i].lim)
+                       return DNS_EILLEGAL;
+
+               if ((rp = dns_d_skip(rp, P)) >= P->end)
+                       return DNS_EILLEGAL;
+       }
+
+       /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */
+       for (i = 0; i < lengthof(ts); i++) {
+               for (j = 0; j < 4; j++, rp++) {
+                       if (rp >= P->end)
+                               return DNS_EILLEGAL;
+
+                       *ts[i]  <<= 8;
+                       *ts[i]  |= (0xff & P->data[rp]);
+               }
+       }
+
+       return 0;
+} /* dns_soa_parse() */
+
+
+int dns_soa_push(struct dns_packet *P, struct dns_soa *soa) {
+       void *dn[]      = { soa->mname, soa->rname };
+       unsigned ts[]   = { (0xffffffff & soa->serial),
+                           (0x7fffffff & soa->refresh),
+                           (0x7fffffff & soa->retry),
+                           (0x7fffffff & soa->expire),
+                           (0xffffffff & soa->minimum) };
+       unsigned i, j;
+       size_t end, len;
+       int error;
+
+       end     = P->end;
+
+       if ((P->end += 2) >= P->size)
+               goto toolong;
+
+       /* MNAME / RNAME */
+       for (i = 0; i < lengthof(dn); i++) {
+               if ((error = dns_d_push(P, dn[i], strlen(dn[i]))))
+                       goto error;
+       }
+
+       /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */
+       for (i = 0; i < lengthof(ts); i++) {
+               if ((P->end += 4) >= P->size)
+                       goto toolong;
+
+               for (j = 1; j <= 4; j++) {
+                       P->data[P->end - j]     = (0xff & ts[i]);
+                       ts[i]                   >>= 8;
+               }
+       }
+
+       len                     = P->end - end - 2;
+       P->data[end + 0]        = (0xff & (len >> 8));
+       P->data[end + 1]        = (0xff & (len >> 0));
+
+       return 0;
+toolong:
+       error   = DNS_ENOBUFS;
+
+       /* FALL THROUGH */
+error:
+       P->end  = end;
+
+       return error;
+} /* dns_soa_push() */
+
+
+int dns_soa_cmp(const struct dns_soa *a, const struct dns_soa *b) {
+       int cmp;
+
+       if ((cmp = strcasecmp(a->mname, b->mname)))
+               return cmp;
+
+       if ((cmp = strcasecmp(a->rname, b->rname)))
+               return cmp;
+
+       if (a->serial > b->serial)
+               return -1;
+       else if (a->serial < b->serial)
+               return 1;
+
+       if (a->refresh > b->refresh)
+               return -1;
+       else if (a->refresh < b->refresh)
+               return 1;
+
+       if (a->retry > b->retry)
+               return -1;
+       else if (a->retry < b->retry)
+               return 1;
+
+       if (a->expire > b->expire)
+               return -1;
+       else if (a->expire < b->expire)
+               return 1;
+
+       if (a->minimum > b->minimum)
+               return -1;
+       else if (a->minimum < b->minimum)
+               return 1;
+
+       return 0;
+} /* dns_soa_cmp() */
+
+
+size_t dns_soa_print(void *_dst, size_t lim, struct dns_soa *soa) {
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+
+       dns_b_puts(&dst, soa->mname);
+       dns_b_putc(&dst, ' ');
+       dns_b_puts(&dst, soa->rname);
+       dns_b_putc(&dst, ' ');
+       dns_b_fmtju(&dst, soa->serial, 0);
+       dns_b_putc(&dst, ' ');
+       dns_b_fmtju(&dst, soa->refresh, 0);
+       dns_b_putc(&dst, ' ');
+       dns_b_fmtju(&dst, soa->retry, 0);
+       dns_b_putc(&dst, ' ');
+       dns_b_fmtju(&dst, soa->expire, 0);
+       dns_b_putc(&dst, ' ');
+       dns_b_fmtju(&dst, soa->minimum, 0);
+
+       return dns_b_strllen(&dst);
+} /* dns_soa_print() */
+
+
+int dns_srv_parse(struct dns_srv *srv, struct dns_rr *rr, struct dns_packet *P) {
+       unsigned short rp;
+       unsigned i;
+       size_t n;
+       int error;
+
+       memset(srv, '\0', sizeof *srv);
+
+       rp      = rr->rd.p;
+
+       if (rr->rd.len < 7)
+               return DNS_EILLEGAL;
+
+       for (i = 0; i < 2; i++, rp++) {
+               srv->priority   <<= 8;
+               srv->priority   |= (0xff & P->data[rp]);
+       }
+
+       for (i = 0; i < 2; i++, rp++) {
+               srv->weight     <<= 8;
+               srv->weight     |= (0xff & P->data[rp]);
+       }
+
+       for (i = 0; i < 2; i++, rp++) {
+               srv->port       <<= 8;
+               srv->port       |= (0xff & P->data[rp]);
+       }
+
+       if (!(n = dns_d_expand(srv->target, sizeof srv->target, rp, P, &error)))
+               return error;
+       else if (n >= sizeof srv->target)
+               return DNS_EILLEGAL;
+
+       return 0;
+} /* dns_srv_parse() */
+
+
+int dns_srv_push(struct dns_packet *P, struct dns_srv *srv) {
+       size_t end, len;
+       int error;
+
+       end     = P->end;
+
+       if (P->size - P->end < 2)
+               goto toolong;
+
+       P->end  += 2;
+
+       if (P->size - P->end < 6)
+               goto toolong;
+
+       P->data[P->end++]       = 0xff & (srv->priority >> 8);
+       P->data[P->end++]       = 0xff & (srv->priority >> 0);
+
+       P->data[P->end++]       = 0xff & (srv->weight >> 8);
+       P->data[P->end++]       = 0xff & (srv->weight >> 0);
+
+       P->data[P->end++]       = 0xff & (srv->port >> 8);
+       P->data[P->end++]       = 0xff & (srv->port >> 0);
+
+       if (0 == (len = dns_d_comp(&P->data[P->end], P->size - P->end, srv->target, strlen(srv->target), P, &error)))
+               goto error;
+       else if (P->size - P->end < len)
+               goto toolong;
+
+       P->end  += len;
+
+       if (P->end > 65535)
+               goto toolong;
+
+       len     = P->end - end - 2;
+
+       P->data[end + 0]        = 0xff & (len >> 8);
+       P->data[end + 1]        = 0xff & (len >> 0);
+
+       return 0;
+toolong:
+       error   = DNS_ENOBUFS;
+
+       /* FALL THROUGH */
+error:
+       P->end  = end;
+
+       return error;
+} /* dns_srv_push() */
+
+
+int dns_srv_cmp(const struct dns_srv *a, const struct dns_srv *b) {
+       int cmp;
+
+       if ((cmp = a->priority - b->priority))
+               return cmp;
+
+       /*
+        * FIXME: We need some sort of random seed to implement the dynamic
+        * weighting required by RFC 2782.
+        */
+       if ((cmp = a->weight - b->weight))
+               return cmp;
+
+       if ((cmp = a->port - b->port))
+               return cmp;
+
+       return strcasecmp(a->target, b->target);
+} /* dns_srv_cmp() */
+
+
+size_t dns_srv_print(void *_dst, size_t lim, struct dns_srv *srv) {
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+
+       dns_b_fmtju(&dst, srv->priority, 0);
+       dns_b_putc(&dst, ' ');
+       dns_b_fmtju(&dst, srv->weight, 0);
+       dns_b_putc(&dst, ' ');
+       dns_b_fmtju(&dst, srv->port, 0);
+       dns_b_putc(&dst, ' ');
+       dns_b_puts(&dst, srv->target);
+
+       return dns_b_strllen(&dst);
+} /* dns_srv_print() */
+
+
+size_t dns_srv_cname(void *dst, size_t lim, struct dns_srv *srv) {
+       return dns_strlcpy(dst, srv->target, lim);
+} /* dns_srv_cname() */
+
+
+unsigned int dns_opt_ttl(const struct dns_opt *opt) {
+       unsigned int ttl = 0;
+
+       ttl |= (0xffU & opt->rcode) << 24;
+       ttl |= (0xffU & opt->version) << 16;
+       ttl |= (0xffffU & opt->flags) << 0;
+
+       return ttl;
+} /* dns_opt_ttl() */
+
+
+unsigned short dns_opt_class(const struct dns_opt *opt) {
+       return opt->maxudp;
+} /* dns_opt_class() */
+
+
+struct dns_opt *dns_opt_init(struct dns_opt *opt, size_t size) {
+       assert(size >= offsetof(struct dns_opt, data));
+
+       opt->size = size - offsetof(struct dns_opt, data);
+       opt->len  = 0;
+
+       opt->rcode   = 0;
+       opt->version = 0;
+       opt->maxudp  = 0;
+
+       return opt;
+} /* dns_opt_init() */
+
+
+static union dns_any *dns_opt_initany(union dns_any *any, size_t size) {
+       return dns_opt_init(&any->opt, size), any;
+} /* dns_opt_initany() */
+
+
+int dns_opt_parse(struct dns_opt *opt, struct dns_rr *rr, struct dns_packet *P) {
+       const struct dns_buf src = DNS_B_FROM(&P->data[rr->rd.p], rr->rd.len);
+       struct dns_buf dst = DNS_B_INTO(opt->data, opt->size);
+       int error;
+
+       opt->rcode = 0xfff & ((rr->ttl >> 20) | dns_header(P)->rcode);
+       opt->version = 0xff & (rr->ttl >> 16);
+       opt->flags = 0xffff & rr->ttl;
+       opt->maxudp = 0xffff & rr->class;
+
+       while (src.p < src.pe) {
+               int code, len;
+
+               if (-1 == (code = dns_b_get16(&src, -1)))
+                       return src.error;
+               if (-1 == (len = dns_b_get16(&src, -1)))
+                       return src.error;
+
+               switch (code) {
+               default:
+                       dns_b_put16(&dst, code);
+                       dns_b_put16(&dst, len);
+                       if ((error = dns_b_move(&dst, &src, len)))
+                               return error;
+                       break;
+               }
+       }
+
+       return 0;
+} /* dns_opt_parse() */
+
+
+int dns_opt_push(struct dns_packet *P, struct dns_opt *opt) {
+       const struct dns_buf src = DNS_B_FROM(opt->data, opt->len);
+       struct dns_buf dst = DNS_B_INTO(&P->data[P->end], (P->size - P->end));
+       int error;
+
+       /* rdata length (see below) */
+       if ((error = dns_b_put16(&dst, 0)))
+               goto error;
+
+       /* ... push known options here */
+
+       /* push opaque option data */
+       if ((error = dns_b_move(&dst, &src, (size_t)(src.pe - src.p))))
+               goto error;
+
+       /* rdata length */
+       if ((error = dns_b_pput16(&dst, dns_b_tell(&dst) - 2, 0)))
+               goto error;
+
+#if !DNS_DEBUG_OPT_FORMERR
+       P->end += dns_b_tell(&dst);
+#endif
+
+       return 0;
+error:
+       return error;
+} /* dns_opt_push() */
+
+
+int dns_opt_cmp(const struct dns_opt *a, const struct dns_opt *b) {
+       (void)a;
+       (void)b;
+
+       return -1;
+} /* dns_opt_cmp() */
+
+
+size_t dns_opt_print(void *_dst, size_t lim, struct dns_opt *opt) {
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+       size_t p;
+
+       dns_b_putc(&dst, '"');
+
+       for (p = 0; p < opt->len; p++) {
+               dns_b_putc(&dst, '\\');
+               dns_b_fmtju(&dst, opt->data[p], 3);
+       }
+
+       dns_b_putc(&dst, '"');
+
+       return dns_b_strllen(&dst);
+} /* dns_opt_print() */
+
+
+int dns_ptr_parse(struct dns_ptr *ptr, struct dns_rr *rr, struct dns_packet *P) {
+       return dns_ns_parse((struct dns_ns *)ptr, rr, P);
+} /* dns_ptr_parse() */
+
+
+int dns_ptr_push(struct dns_packet *P, struct dns_ptr *ptr) {
+       return dns_ns_push(P, (struct dns_ns *)ptr);
+} /* dns_ptr_push() */
+
+
+size_t dns_ptr_qname(void *dst, size_t lim, int af, void *addr) {
+       switch (af) {
+       case AF_INET6:
+               return dns_aaaa_arpa(dst, lim, addr);
+       case AF_INET:
+               return dns_a_arpa(dst, lim, addr);
+       default: {
+               struct dns_a a;
+               a.addr.s_addr = INADDR_NONE;
+               return dns_a_arpa(dst, lim, &a);
+       }
+       }
+} /* dns_ptr_qname() */
+
+
+int dns_ptr_cmp(const struct dns_ptr *a, const struct dns_ptr *b) {
+       return strcasecmp(a->host, b->host);
+} /* dns_ptr_cmp() */
+
+
+size_t dns_ptr_print(void *dst, size_t lim, struct dns_ptr *ptr) {
+       return dns_ns_print(dst, lim, (struct dns_ns *)ptr);
+} /* dns_ptr_print() */
+
+
+size_t dns_ptr_cname(void *dst, size_t lim, struct dns_ptr *ptr) {
+       return dns_strlcpy(dst, ptr->host, lim);
+} /* dns_ptr_cname() */
+
+
+int dns_sshfp_parse(struct dns_sshfp *fp, struct dns_rr *rr, struct dns_packet *P) {
+       unsigned p = rr->rd.p, pe = rr->rd.p + rr->rd.len;
+
+       if (pe - p < 2)
+               return DNS_EILLEGAL;
+
+       fp->algo = P->data[p++];
+       fp->type = P->data[p++];
+
+       switch (fp->type) {
+       case DNS_SSHFP_SHA1:
+               if (pe - p < sizeof fp->digest.sha1)
+                       return DNS_EILLEGAL;
+
+               memcpy(fp->digest.sha1, &P->data[p], sizeof fp->digest.sha1);
+
+               break;
+       default:
+               break;
+       } /* switch() */
+
+       return 0;
+} /* dns_sshfp_parse() */
+
+
+int dns_sshfp_push(struct dns_packet *P, struct dns_sshfp *fp) {
+       unsigned p = P->end, pe = P->size, n;
+
+       if (pe - p < 4)
+               return DNS_ENOBUFS;
+
+       p += 2;
+       P->data[p++] = 0xff & fp->algo;
+       P->data[p++] = 0xff & fp->type;
+
+       switch (fp->type) {
+       case DNS_SSHFP_SHA1:
+               if (pe - p < sizeof fp->digest.sha1)
+                       return DNS_ENOBUFS;
+
+               memcpy(&P->data[p], fp->digest.sha1, sizeof fp->digest.sha1);
+               p += sizeof fp->digest.sha1;
+
+               break;
+       default:
+               return DNS_EILLEGAL;
+       } /* switch() */
+
+       n = p - P->end - 2;
+       P->data[P->end++] = 0xff & (n >> 8);
+       P->data[P->end++] = 0xff & (n >> 0);
+       P->end = p;
+
+       return 0;
+} /* dns_sshfp_push() */
+
+
+int dns_sshfp_cmp(const struct dns_sshfp *a, const struct dns_sshfp *b) {
+       int cmp;
+
+       if ((cmp = a->algo - b->algo) || (cmp = a->type - b->type))
+               return cmp;
+
+       switch (a->type) {
+       case DNS_SSHFP_SHA1:
+               return memcmp(a->digest.sha1, b->digest.sha1, sizeof a->digest.sha1);
+       default:
+               return 0;
+       } /* switch() */
+
+       /* NOT REACHED */
+} /* dns_sshfp_cmp() */
+
+
+size_t dns_sshfp_print(void *_dst, size_t lim, struct dns_sshfp *fp) {
+       static const unsigned char hex[16] = "0123456789abcdef";
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+       size_t i;
+
+       dns_b_fmtju(&dst, fp->algo, 0);
+       dns_b_putc(&dst, ' ');
+       dns_b_fmtju(&dst, fp->type, 0);
+       dns_b_putc(&dst, ' ');
+
+       switch (fp->type) {
+       case DNS_SSHFP_SHA1:
+               for (i = 0; i < sizeof fp->digest.sha1; i++) {
+                       dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 4)]);
+                       dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 0)]);
+               }
+
+               break;
+       default:
+               dns_b_putc(&dst, '0');
+
+               break;
+       } /* switch() */
+
+       return dns_b_strllen(&dst);
+} /* dns_sshfp_print() */
+
+
+struct dns_txt *dns_txt_init(struct dns_txt *txt, size_t size) {
+       assert(size > offsetof(struct dns_txt, data));
+
+       txt->size       = size - offsetof(struct dns_txt, data);
+       txt->len        = 0;
+
+       return txt;
+} /* dns_txt_init() */
+
+
+static union dns_any *dns_txt_initany(union dns_any *any, size_t size) {
+       /* NB: union dns_any is already initialized as struct dns_txt */
+       (void)size;
+       return any;
+} /* dns_txt_initany() */
+
+
+int dns_txt_parse(struct dns_txt *txt, struct dns_rr *rr, struct dns_packet *P) {
+       struct { unsigned char *b; size_t p, end; } dst, src;
+       unsigned n;
+
+       dst.b   = txt->data;
+       dst.p   = 0;
+       dst.end = txt->size;
+
+       src.b   = P->data;
+       src.p   = rr->rd.p;
+       src.end = src.p + rr->rd.len;
+
+       while (src.p < src.end) {
+               n       = 0xff & P->data[src.p++];
+
+               if (src.end - src.p < n || dst.end - dst.p < n)
+                       return DNS_EILLEGAL;
+
+               memcpy(&dst.b[dst.p], &src.b[src.p], n);
+
+               dst.p   += n;
+               src.p   += n;
+       }
+
+       txt->len        = dst.p;
+
+       return 0;
+} /* dns_txt_parse() */
+
+
+int dns_txt_push(struct dns_packet *P, struct dns_txt *txt) {
+       struct { unsigned char *b; size_t p, end; } dst, src;
+       unsigned n;
+
+       dst.b   = P->data;
+       dst.p   = P->end;
+       dst.end = P->size;
+
+       src.b   = txt->data;
+       src.p   = 0;
+       src.end = txt->len;
+
+       if (dst.end - dst.p < 2)
+               return DNS_ENOBUFS;
+
+       n       = txt->len + ((txt->len + 254) / 255);
+
+       dst.b[dst.p++]  = 0xff & (n >> 8);
+       dst.b[dst.p++]  = 0xff & (n >> 0);
+
+       while (src.p < src.end) {
+               n       = DNS_PP_MIN(255, src.end - src.p);
+
+               if (dst.p >= dst.end)
+                       return DNS_ENOBUFS;
+
+               dst.b[dst.p++]  = n;
+
+               if (dst.end - dst.p < n)
+                       return DNS_ENOBUFS;
+
+               memcpy(&dst.b[dst.p], &src.b[src.p], n);
+
+               dst.p   += n;
+               src.p   += n;
+       }
+
+       P->end  = dst.p;
+
+       return 0;
+} /* dns_txt_push() */
+
+
+int dns_txt_cmp(const struct dns_txt *a, const struct dns_txt *b) {
+       (void)a;
+       (void)b;
+
+       return -1;
+} /* dns_txt_cmp() */
+
+
+size_t dns_txt_print(void *_dst, size_t lim, struct dns_txt *txt) {
+       struct dns_buf src = DNS_B_FROM(txt->data, txt->len);
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+       unsigned i;
+
+       if (src.p < src.pe) {
+               do {
+                       dns_b_putc(&dst, '"');
+
+                       for (i = 0; i < 256 && src.p < src.pe; i++, src.p++) {
+                               if (*src.p < 32 || *src.p > 126 || *src.p == '"' || *src.p == '\\') {
+                                       dns_b_putc(&dst, '\\');
+                                       dns_b_fmtju(&dst, *src.p, 3);
+                               } else {
+                                       dns_b_putc(&dst, *src.p);
+                               }
+                       }
+
+                       dns_b_putc(&dst, '"');
+                       dns_b_putc(&dst, ' ');
+               } while (src.p < src.pe);
+
+               dns_b_popc(&dst);
+       } else {
+               dns_b_putc(&dst, '"');
+               dns_b_putc(&dst, '"');
+       }
+
+       return dns_b_strllen(&dst);
+} /* dns_txt_print() */
+
+
+static const struct dns_rrtype {
+       enum dns_type type;
+       const char *name;
+       union dns_any *(*init)(union dns_any *, size_t);
+       int (*parse)();
+       int (*push)();
+       int (*cmp)();
+       size_t (*print)();
+       size_t (*cname)();
+} dns_rrtypes[]        = {
+       { DNS_T_A,      "A",      0,                 &dns_a_parse,      &dns_a_push,      &dns_a_cmp,      &dns_a_print,      0,                },
+       { DNS_T_AAAA,   "AAAA",   0,                 &dns_aaaa_parse,   &dns_aaaa_push,   &dns_aaaa_cmp,   &dns_aaaa_print,   0,                },
+       { DNS_T_MX,     "MX",     0,                 &dns_mx_parse,     &dns_mx_push,     &dns_mx_cmp,     &dns_mx_print,     &dns_mx_cname,    },
+       { DNS_T_NS,     "NS",     0,                 &dns_ns_parse,     &dns_ns_push,     &dns_ns_cmp,     &dns_ns_print,     &dns_ns_cname,    },
+       { DNS_T_CNAME,  "CNAME",  0,                 &dns_cname_parse,  &dns_cname_push,  &dns_cname_cmp,  &dns_cname_print,  &dns_cname_cname, },
+       { DNS_T_SOA,    "SOA",    0,                 &dns_soa_parse,    &dns_soa_push,    &dns_soa_cmp,    &dns_soa_print,    0,                },
+       { DNS_T_SRV,    "SRV",    0,                 &dns_srv_parse,    &dns_srv_push,    &dns_srv_cmp,    &dns_srv_print,    &dns_srv_cname,   },
+       { DNS_T_OPT,    "OPT",    &dns_opt_initany,  &dns_opt_parse,    &dns_opt_push,    &dns_opt_cmp,    &dns_opt_print,    0,                },
+       { DNS_T_PTR,    "PTR",    0,                 &dns_ptr_parse,    &dns_ptr_push,    &dns_ptr_cmp,    &dns_ptr_print,    &dns_ptr_cname,   },
+       { DNS_T_TXT,    "TXT",    &dns_txt_initany,  &dns_txt_parse,    &dns_txt_push,    &dns_txt_cmp,    &dns_txt_print,    0,                },
+       { DNS_T_SPF,    "SPF",    &dns_txt_initany,  &dns_txt_parse,    &dns_txt_push,    &dns_txt_cmp,    &dns_txt_print,    0,                },
+       { DNS_T_SSHFP,  "SSHFP",  0,                 &dns_sshfp_parse,  &dns_sshfp_push,  &dns_sshfp_cmp,  &dns_sshfp_print,  0,                },
+       { DNS_T_AXFR,   "AXFR",   0,                 0,                 0,                0,               0,                 0,                },
+}; /* dns_rrtypes[] */
+
+static const struct dns_rrtype *dns_rrtype(enum dns_type type) {
+       const struct dns_rrtype *t;
+
+       for (t = dns_rrtypes; t < endof(dns_rrtypes); t++) {
+               if (t->type == type && t->parse) {
+                       return t;
+               }
+       }
+
+       return NULL;
+} /* dns_rrtype() */
+
+
+union dns_any *dns_any_init(union dns_any *any, size_t size) {
+       dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type");
+       return (union dns_any *)dns_txt_init(&any->rdata, size);
+} /* dns_any_init() */
+
+
+static size_t dns_any_sizeof(union dns_any *any) {
+       dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type");
+       return offsetof(struct dns_txt, data) + any->rdata.size;
+} /* dns_any_sizeof() */
+
+static union dns_any *dns_any_reinit(union dns_any *any, const struct dns_rrtype *t) {
+       return (t->init)? t->init(any, dns_any_sizeof(any)) : any;
+} /* dns_any_reinit() */
+
+int dns_any_parse(union dns_any *any, struct dns_rr *rr, struct dns_packet *P) {
+       const struct dns_rrtype *t;
+
+       if ((t = dns_rrtype(rr->type)))
+               return t->parse(dns_any_reinit(any, t), rr, P);
+
+       if (rr->rd.len > any->rdata.size)
+               return DNS_EILLEGAL;
+
+       memcpy(any->rdata.data, &P->data[rr->rd.p], rr->rd.len);
+       any->rdata.len  = rr->rd.len;
+
+       return 0;
+} /* dns_any_parse() */
+
+
+int dns_any_push(struct dns_packet *P, union dns_any *any, enum dns_type type) {
+       const struct dns_rrtype *t;
+
+       if ((t = dns_rrtype(type)))
+               return t->push(P, any);
+
+       if (P->size - P->end < any->rdata.len + 2)
+               return DNS_ENOBUFS;
+
+       P->data[P->end++]       = 0xff & (any->rdata.len >> 8);
+       P->data[P->end++]       = 0xff & (any->rdata.len >> 0);
+
+       memcpy(&P->data[P->end], any->rdata.data, any->rdata.len);
+       P->end  += any->rdata.len;
+
+       return 0;
+} /* dns_any_push() */
+
+
+int dns_any_cmp(const union dns_any *a, enum dns_type x, const union dns_any *b, enum dns_type y) {
+       const struct dns_rrtype *t;
+       int cmp;
+
+       if ((cmp = x - y))
+               return cmp;
+
+       if ((t = dns_rrtype(x)))
+               return t->cmp(a, b);
+
+       return -1;
+} /* dns_any_cmp() */
+
+
+size_t dns_any_print(void *_dst, size_t lim, union dns_any *any, enum dns_type type) {
+       const struct dns_rrtype *t;
+       struct dns_buf src, dst;
+
+       if ((t = dns_rrtype(type)))
+               return t->print(_dst, lim, any);
+
+       dns_b_from(&src, any->rdata.data, any->rdata.len);
+       dns_b_into(&dst, _dst, lim);
+
+       dns_b_putc(&dst, '"');
+
+       while (src.p < src.pe) {
+               dns_b_putc(&dst, '\\');
+               dns_b_fmtju(&dst, *src.p++, 3);
+       }
+
+       dns_b_putc(&dst, '"');
+
+       return dns_b_strllen(&dst);
+} /* dns_any_print() */
+
+
+size_t dns_any_cname(void *dst, size_t lim, union dns_any *any, enum dns_type type) {
+       const struct dns_rrtype *t;
+
+       if ((t = dns_rrtype(type)) && t->cname)
+               return t->cname(dst, lim, any);
+
+       return 0;
+} /* dns_any_cname() */
+
+
+/*
+ * E V E N T  T R A C I N G  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+#include <float.h> /* DBL_MANT_DIG */
+#include <inttypes.h> /* PRIu64 */
+
+/* for default trace ID generation try to fit in lua_Number, usually double */
+#define DNS_TRACE_ID_BITS DNS_PP_MIN(DBL_MANT_DIG, (sizeof (dns_trace_id_t) * CHAR_BIT)) /* assuming FLT_RADIX == 2 */
+#define DNS_TRACE_ID_MASK (((DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)) - 1) | (DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)))
+#define DNS_TRACE_ID_PRI PRIu64
+
+static inline dns_trace_id_t dns_trace_mkid(void) {
+       dns_trace_id_t id = 0;
+       unsigned r; /* return type of dns_random() */
+       const size_t id_bit = sizeof id * CHAR_BIT;
+       const size_t r_bit = sizeof r * CHAR_BIT;
+
+       for (size_t n = 0; n < id_bit; n += r_bit) {
+               r = dns_random();
+               id <<= r_bit;
+               id |= r;
+       }
+
+       return DNS_TRACE_ID_MASK & id;
+}
+
+struct dns_trace {
+       dns_atomic_t refcount;
+
+       FILE *fp;
+       dns_trace_id_t id;
+
+       struct {
+               struct dns_trace_cname {
+                       char host[DNS_D_MAXNAME + 1];
+                       struct sockaddr_storage addr;
+               } base[4];
+               size_t p;
+       } cnames;
+};
+
+static void dns_te_initname(struct sockaddr_storage *ss, int fd, int (*f)(int, struct sockaddr *, socklen_t *)) {
+       socklen_t n = sizeof *ss;
+
+       if (0 != f(fd, (struct sockaddr *)ss, &n))
+               goto unspec;
+
+       if (n > sizeof *ss)
+               goto unspec;
+
+       return;
+unspec:
+       memset(ss, '\0', sizeof *ss);
+       ss->ss_family = AF_UNSPEC;
+}
+
+static void dns_te_initnames(struct sockaddr_storage *local, struct sockaddr_storage *remote, int fd) {
+       dns_te_initname(local, fd, &getsockname);
+       dns_te_initname(remote, fd, &getpeername);
+}
+
+static struct dns_trace_event *dns_te_init(struct dns_trace_event *te, int type) {
+       /* NB: silence valgrind */
+       memset(te, '\0', offsetof(struct dns_trace_event, data));
+       te->type = type;
+       return te;
+}
+
+int dns_trace_abi(void) {
+       return DNS_TRACE_ABI;
+}
+
+struct dns_trace *dns_trace_open(FILE *fp, dns_error_t *error) {
+       static const struct dns_trace trace_initializer = { .refcount = 1 };
+       struct dns_trace *trace;
+
+       if (!(trace = malloc(sizeof *trace)))
+               goto syerr;
+
+       *trace = trace_initializer;
+
+       if (fp) {
+               trace->fp = fp;
+       } else if (!(fp = tmpfile())) {
+               goto syerr;
+       }
+
+       trace->id = dns_trace_mkid();
+
+       return trace;
+syerr:
+       *error = dns_syerr();
+
+       dns_trace_close(trace);
+
+       return NULL;
+} /* dns_trace_open() */
+
+void dns_trace_close(struct dns_trace *trace) {
+       if (!trace || 1 != dns_trace_release(trace))
+               return;
+
+       if (trace->fp)
+               fclose(trace->fp);
+       free(trace);
+} /* dns_trace_close() */
+
+dns_refcount_t dns_trace_acquire(struct dns_trace *trace) {
+       return dns_atomic_fetch_add(&trace->refcount);
+} /* dns_trace_acquire() */
+
+static struct dns_trace *dns_trace_acquire_p(struct dns_trace *trace) {
+       return (trace)? dns_trace_acquire(trace), trace : NULL;
+} /* dns_trace_acquire_p() */
+
+dns_refcount_t dns_trace_release(struct dns_trace *trace) {
+       return dns_atomic_fetch_sub(&trace->refcount);
+} /* dns_trace_release() */
+
+dns_trace_id_t dns_trace_id(struct dns_trace *trace) {
+       return trace->id;
+} /* dns_trace_id() */
+
+dns_trace_id_t dns_trace_setid(struct dns_trace *trace, dns_trace_id_t id) {
+       trace->id = (id)? id : dns_trace_mkid();
+       return trace->id;
+} /* dns_trace_setid() */
+
+struct dns_trace_event *dns_trace_get(struct dns_trace *trace, struct dns_trace_event **tp, dns_error_t *error) {
+       return dns_trace_fget(tp, trace->fp, error);
+} /* dns_trace_get() */
+
+dns_error_t dns_trace_put(struct dns_trace *trace, const struct dns_trace_event *te, const void *data, size_t datasize) {
+       return dns_trace_fput(te, data, datasize, trace->fp);
+} /* dns_trace_put() */
+
+struct dns_trace_event *dns_trace_tag(struct dns_trace *trace, struct dns_trace_event *te) {
+       struct timeval tv;
+
+       te->id = trace->id;
+       gettimeofday(&tv, NULL);
+       dns_tv2ts(&te->ts, &tv);
+       te->abi = DNS_TRACE_ABI;
+
+       return te;
+} /* dns_trace_tag() */
+
+static dns_error_t dns_trace_tag_and_put(struct dns_trace *trace, struct dns_trace_event *te, const void *data, size_t datasize) {
+       return dns_trace_put(trace, dns_trace_tag(trace, te), data, datasize);
+} /* dns_trace_tag_and_put() */
+
+struct dns_trace_event *dns_trace_fget(struct dns_trace_event **tp, FILE *fp, dns_error_t *error) {
+       const size_t headsize = offsetof(struct dns_trace_event, data);
+       struct dns_trace_event tmp, *te;
+       size_t n;
+
+       errno = 0;
+       if (!(n = fread(&tmp, 1, headsize, fp)))
+               goto none;
+       if (n < offsetof(struct dns_trace_event, data))
+               goto some;
+
+       if (!(te = realloc(*tp, DNS_PP_MAX(headsize, tmp.size)))) {
+               *error = errno;
+               return NULL;
+       }
+
+       *tp = te;
+       memcpy(te, &tmp, offsetof(struct dns_trace_event, data));
+
+       if (dns_te_datasize(te)) {
+               errno = 0;
+               if (!(n = fread(te->data, 1, dns_te_datasize(te), fp)))
+                       goto none;
+               if (n < dns_te_datasize(te))
+                       goto some;
+       }
+
+       return te;
+none:
+       *error = (ferror(fp))? errno : 0;
+       return NULL;
+some:
+       *error = 0;
+       return NULL;
+}
+
+dns_error_t dns_trace_fput(const struct dns_trace_event *te, const void *data, size_t datasize, FILE *fp) {
+       size_t headsize = offsetof(struct dns_trace_event, data);
+       struct dns_trace_event tmp;
+
+       memcpy(&tmp, te, headsize);
+       tmp.size = headsize + datasize;
+
+       /* NB: ignore seek error as fp might not point to a regular file */
+       (void)fseek(fp, 0, SEEK_END);
+
+       if (fwrite(&tmp, 1, headsize, fp) < headsize)
+               return errno;
+       if (fwrite(data, 1, datasize, fp) < datasize)
+               return errno;
+       if (fflush(fp))
+               return errno;
+
+       return 0;
+}
+
+static void dns_trace_setcname(struct dns_trace *trace, const char *host, const struct sockaddr *addr) {
+       struct dns_trace_cname *cname;
+       if (!trace || !trace->fp)
+               return;
+
+       cname = &trace->cnames.base[trace->cnames.p];
+       dns_strlcpy(cname->host, host, sizeof cname->host);
+       memcpy(&cname->addr, addr, DNS_PP_MIN(dns_sa_len(addr), sizeof cname->addr));
+
+       trace->cnames.p = (trace->cnames.p + 1) % lengthof(trace->cnames.base);
+}
+
+static const char *dns_trace_cname(struct dns_trace *trace, const struct sockaddr *addr) {
+       if (!trace || !trace->fp)
+               return NULL;
+
+       /* NB: start search from the write cursor to  */
+       for (const struct dns_trace_cname *cname = trace->cnames.base; cname < endof(trace->cnames.base); cname++) {
+               if (0 == dns_sa_cmp((struct sockaddr *)addr, (struct sockaddr *)&cname->addr))
+                       return cname->host;
+       }
+
+       return NULL;
+}
+
+static void dns_trace_res_submit(struct dns_trace *trace, const char *qname, enum dns_type qtype, enum dns_class qclass, int error) {
+       struct dns_trace_event te;
+       if (!trace || !trace->fp)
+               return;
+
+       dns_te_init(&te, DNS_TE_RES_SUBMIT);
+       dns_strlcpy(te.res_submit.qname, qname, sizeof te.res_submit.qname);
+       te.res_submit.qtype = qtype;
+       te.res_submit.qclass = qclass;
+       te.res_submit.error = error;
+       dns_trace_tag_and_put(trace, &te, NULL, 0);
+}
+
+static void dns_trace_res_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) {
+       struct dns_trace_event te;
+       const void *data;
+       size_t datasize;
+       if (!trace || !trace->fp)
+               return;
+
+       dns_te_init(&te, DNS_TE_RES_FETCH);
+       data = (packet)? packet->data : NULL;
+       datasize = (packet)? packet->end : 0;
+       te.res_fetch.error = error;
+       dns_trace_tag_and_put(trace, &te, data, datasize);
+}
+
+static void dns_trace_so_submit(struct dns_trace *trace, const struct dns_packet *packet, const struct sockaddr *haddr, int error) {
+       struct dns_trace_event te;
+       const char *cname;
+       if (!trace || !trace->fp)
+               return;
+
+       dns_te_init(&te, DNS_TE_SO_SUBMIT);
+       memcpy(&te.so_submit.haddr, haddr, DNS_PP_MIN(dns_sa_len(haddr), sizeof te.so_submit.haddr));
+       if ((cname = dns_trace_cname(trace, haddr)))
+               dns_strlcpy(te.so_submit.hname, cname, sizeof te.so_submit.hname);
+       te.so_submit.error = error;
+       dns_trace_tag_and_put(trace, &te, packet->data, packet->end);
+}
+
+static void dns_trace_so_verify(struct dns_trace *trace, const struct dns_packet *packet, int error) {
+       struct dns_trace_event te;
+       if (!trace || !trace->fp)
+               return;
+
+       dns_te_init(&te, DNS_TE_SO_VERIFY);
+       te.so_verify.error = error;
+       dns_trace_tag_and_put(trace, &te, packet->data, packet->end);
+}
+
+static void dns_trace_so_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) {
+       struct dns_trace_event te;
+       const void *data;
+       size_t datasize;
+       if (!trace || !trace->fp)
+               return;
+
+       dns_te_init(&te, DNS_TE_SO_FETCH);
+       data = (packet)? packet->data : NULL;
+       datasize = (packet)? packet->end : 0;
+       te.so_fetch.error = error;
+       dns_trace_tag_and_put(trace, &te, data, datasize);
+}
+
+static void dns_trace_sys_connect(struct dns_trace *trace, int fd, int socktype, const struct sockaddr *dst, int error) {
+       struct dns_trace_event te;
+       if (!trace || !trace->fp)
+               return;
+
+       dns_te_init(&te, DNS_TE_SYS_CONNECT);
+       dns_te_initname(&te.sys_connect.src, fd, &getsockname);
+       memcpy(&te.sys_connect.dst, dst, DNS_PP_MIN(dns_sa_len(dst), sizeof te.sys_connect.dst));
+       te.sys_connect.socktype = socktype;
+       te.sys_connect.error = error;
+       dns_trace_tag_and_put(trace, &te, NULL, 0);
+}
+
+static void dns_trace_sys_send(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) {
+       struct dns_trace_event te;
+       if (!trace || !trace->fp)
+               return;
+
+       dns_te_init(&te, DNS_TE_SYS_SEND);
+       dns_te_initnames(&te.sys_send.src, &te.sys_send.dst, fd);
+       te.sys_send.socktype = socktype;
+       te.sys_send.error = error;
+       dns_trace_tag_and_put(trace, &te, data, datasize);
+}
+
+static void dns_trace_sys_recv(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) {
+       struct dns_trace_event te;
+       if (!trace || !trace->fp)
+               return;
+
+       dns_te_init(&te, DNS_TE_SYS_RECV);
+       dns_te_initnames(&te.sys_recv.dst, &te.sys_recv.src, fd);
+       te.sys_recv.socktype = socktype;
+       te.sys_recv.error = error;
+       dns_trace_tag_and_put(trace, &te, data, datasize);
+}
+
+static dns_error_t dns_trace_dump_packet(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) {
+       struct dns_packet *packet = NULL;
+       char *line = NULL, *p;
+       size_t size = 1, skip = 0;
+       struct dns_rr_i records;
+       struct dns_p_lines_i lines;
+       size_t len, count;
+       int error;
+
+       if (!(packet = dns_p_make(datasize, &error)))
+               goto error;
+
+       memcpy(packet->data, data, datasize);
+       packet->end = datasize;
+       (void)dns_p_study(packet);
+resize:
+       if (!(p = dns_reallocarray(line, size, 2, &error)))
+               goto error;
+       line = p;
+       size *= 2;
+
+       memset(&records, 0, sizeof records);
+       memset(&lines, 0, sizeof lines);
+       count = 0;
+
+       while ((len = dns_p_lines(line, size, &error, packet, &records, &lines))) {
+               if (!(len < size)) {
+                       skip = count;
+                       goto resize;
+               } else if (skip <= count) {
+                       fputs(prefix, fp);
+                       fwrite(line, 1, len, fp);
+               }
+               count++;
+       }
+
+       if (error)
+               goto error;
+
+       error = 0;
+error:
+       free(line);
+       dns_p_free(packet);
+
+       return error;
+}
+
+static dns_error_t dns_trace_dump_data(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) {
+       struct dns_hxd_lines_i lines = { 0 };
+       char line[128];
+       size_t len;
+
+       while ((len = dns_hxd_lines(line, sizeof line, data, datasize, &lines))) {
+               if (len >= sizeof line)
+                       return EOVERFLOW; /* shouldn't be possible */
+               fputs(prefix, fp);
+               fwrite(line, 1, len, fp);
+       }
+
+       return 0;
+}
+
+static dns_error_t dns_trace_dump_addr(struct dns_trace *trace, const char *prefix, const struct sockaddr_storage *ss, FILE *fp) {
+       const void *addr;
+       const char *path;
+       socklen_t len;
+       int error;
+
+       if ((addr = dns_sa_addr(ss->ss_family, (struct sockaddr *)ss, NULL))) {
+               char ip[INET6_ADDRSTRLEN + 1];
+
+               if ((error = dns_ntop(ss->ss_family, addr, ip, sizeof ip)))
+                       return error;
+               fprintf(fp, "%s%s\n", prefix, ip);
+       } else if ((path = dns_sa_path((struct sockaddr *)ss, &len))) {
+               fprintf(fp, "%sunix:%.*s", prefix, (int)len, path);
+       } else {
+               return EINVAL;
+       }
+
+       return 0;
+}
+
+static dns_error_t dns_trace_dump_meta(struct dns_trace *trace, const char *prefix, const struct dns_trace_event *te, dns_microseconds_t elapsed, FILE *fp) {
+       char time_s[48], elapsed_s[48];
+
+       dns_utime_print(time_s, sizeof time_s, dns_ts2us(&te->ts, 0));
+       dns_utime_print(elapsed_s, sizeof elapsed_s, elapsed);
+
+       fprintf(fp, "%sid: %"DNS_TRACE_ID_PRI"\n", prefix, te->id);
+       fprintf(fp, "%sts: %s (%s)\n", prefix, time_s, elapsed_s);
+       fprintf(fp, "%sabi: 0x%x (0x%x)\n", prefix, te->abi, DNS_TRACE_ABI);
+       return 0;
+}
+
+static dns_error_t dns_trace_dump_error(struct dns_trace *trace, const char *prefix, int error, FILE *fp) {
+       fprintf(fp, "%s%d (%s)\n", prefix, error, (error)? dns_strerror(error) : "none");
+       return 0;
+}
+
+dns_error_t dns_trace_dump(struct dns_trace *trace, FILE *fp) {
+       struct dns_trace_event *te = NULL;
+       struct {
+               dns_trace_id_t id;
+               dns_microseconds_t begin, elapsed;
+       } state = { 0 };
+       int error;
+
+       if (!trace || !trace->fp)
+               return EINVAL;
+
+       if (0 != fseek(trace->fp, 0, SEEK_SET))
+               goto syerr;
+
+       while (dns_trace_fget(&te, trace->fp, &error)) {
+               size_t datasize = dns_te_datasize(te);
+               const unsigned char *data = (datasize)? te->data : NULL;
+
+               if (state.id != te->id) {
+                       state.id = te->id;
+                       state.begin = dns_ts2us(&te->ts, 0);
+               }
+               dns_time_diff(&state.elapsed, dns_ts2us(&te->ts, 0), state.begin);
+
+               switch(te->type) {
+               case DNS_TE_RES_SUBMIT:
+                       fprintf(fp, "dns_res_submit:\n");
+                       dns_trace_dump_meta(trace, "  ", te, state.elapsed, fp);
+                       fprintf(fp, "  qname: %s\n", te->res_submit.qname);
+                       fprintf(fp, "  qtype: %s\n", dns_strtype(te->res_submit.qtype));
+                       fprintf(fp, "  qclass: %s\n", dns_strclass(te->res_submit.qclass));
+                       dns_trace_dump_error(trace, "  error: ", te->res_submit.error, fp);
+                       break;
+               case DNS_TE_RES_FETCH:
+                       fprintf(fp, "dns_res_fetch:\n");
+                       dns_trace_dump_meta(trace, "  ", te, state.elapsed, fp);
+                       dns_trace_dump_error(trace, "  error: ", te->res_fetch.error, fp);
+
+                       if (data) {
+                               fprintf(fp, "  packet: |\n");
+                               if ((error = dns_trace_dump_packet(trace, "    ", data, datasize, fp)))
+                                       goto error;
+                               fprintf(fp, "  data: |\n");
+                               if ((error = dns_trace_dump_data(trace, "    ", data, datasize, fp)))
+                                       goto error;
+                       }
+
+                       break;
+               case DNS_TE_SO_SUBMIT:
+                       fprintf(fp, "dns_so_submit:\n");
+                       dns_trace_dump_meta(trace, "  ", te, state.elapsed, fp);
+                       fprintf(fp, "  hname: %s\n", te->so_submit.hname);
+                       dns_trace_dump_addr(trace, "  haddr: ", &te->so_submit.haddr, fp);
+                       dns_trace_dump_error(trace, "  error: ", te->so_submit.error, fp);
+
+                       if (data) {
+                               fprintf(fp, "  packet: |\n");
+                               if ((error = dns_trace_dump_packet(trace, "    ", data, datasize, fp)))
+                                       goto error;
+                               fprintf(fp, "  data: |\n");
+                               if ((error = dns_trace_dump_data(trace, "    ", data, datasize, fp)))
+                                       goto error;
+                       }
+
+                       break;
+               case DNS_TE_SO_VERIFY:
+                       fprintf(fp, "dns_so_verify:\n");
+                       dns_trace_dump_meta(trace, "  ", te, state.elapsed, fp);
+                       dns_trace_dump_error(trace, "  error: ", te->so_verify.error, fp);
+
+                       if (data) {
+                               fprintf(fp, "  packet: |\n");
+                               if ((error = dns_trace_dump_packet(trace, "    ", data, datasize, fp)))
+                                       goto error;
+                               fprintf(fp, "  data: |\n");
+                               if ((error = dns_trace_dump_data(trace, "    ", data, datasize, fp)))
+                                       goto error;
+                       }
+
+                       break;
+               case DNS_TE_SO_FETCH:
+                       fprintf(fp, "dns_so_fetch:\n");
+                       dns_trace_dump_meta(trace, "  ", te, state.elapsed, fp);
+                       dns_trace_dump_error(trace, "  error: ", te->so_fetch.error, fp);
+
+                       if (data) {
+                               fprintf(fp, "  packet: |\n");
+                               if ((error = dns_trace_dump_packet(trace, "    ", data, datasize, fp)))
+                                       goto error;
+                               fprintf(fp, "  data: |\n");
+                               if ((error = dns_trace_dump_data(trace, "    ", data, datasize, fp)))
+                                       goto error;
+                       }
+
+                       break;
+               case DNS_TE_SYS_CONNECT: {
+                       int socktype = te->sys_connect.socktype;
+                       fprintf(fp, "dns_sys_connect:\n");
+                       dns_trace_dump_meta(trace, "  ", te, state.elapsed, fp);
+                       dns_trace_dump_addr(trace, "  src: ", &te->sys_connect.src, fp);
+                       dns_trace_dump_addr(trace, "  dst: ", &te->sys_connect.dst, fp);
+                       fprintf(fp, "  socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
+                       dns_trace_dump_error(trace, "  error: ", te->sys_connect.error, fp);
+
+                       break;
+               }
+               case DNS_TE_SYS_SEND: {
+                       int socktype = te->sys_send.socktype;
+                       fprintf(fp, "dns_sys_send:\n");
+                       dns_trace_dump_meta(trace, "  ", te, state.elapsed, fp);
+                       dns_trace_dump_addr(trace, "  src: ", &te->sys_send.src, fp);
+                       dns_trace_dump_addr(trace, "  dst: ", &te->sys_send.dst, fp);
+                       fprintf(fp, "  socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
+                       dns_trace_dump_error(trace, "  error: ", te->sys_send.error, fp);
+
+                       if (data) {
+                               fprintf(fp, "  data: |\n");
+                               if ((error = dns_trace_dump_data(trace, "    ", data, datasize, fp)))
+                                       goto error;
+                       }
+
+                       break;
+               }
+               case DNS_TE_SYS_RECV: {
+                       int socktype = te->sys_recv.socktype;
+                       fprintf(fp, "dns_sys_recv:\n");
+                       dns_trace_dump_meta(trace, "  ", te, state.elapsed, fp);
+                       dns_trace_dump_addr(trace, "  src: ", &te->sys_recv.src, fp);
+                       dns_trace_dump_addr(trace, "  dst: ", &te->sys_recv.dst, fp);
+                       fprintf(fp, "  socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
+                       dns_trace_dump_error(trace, "  error: ", te->sys_recv.error, fp);
+
+                       if (data) {
+                               fprintf(fp, "  data: |\n");
+                               if ((error = dns_trace_dump_data(trace, "    ", data, datasize, fp)))
+                                       goto error;
+                       }
+
+                       break;
+               }
+               default:
+                       fprintf(fp, "unknown(0x%.2x):\n", te->type);
+                       dns_trace_dump_meta(trace, "  ", te, state.elapsed, fp);
+
+                       if (data) {
+                               fprintf(fp, "  data: |\n");
+                               if ((error = dns_trace_dump_data(trace, "    ", data, datasize, fp)))
+                                       goto error;
+                       }
+
+                       break;
+               }
+       }
+
+       goto epilog;
+syerr:
+       error = errno;
+error:
+       (void)0;
+epilog:
+       free(te);
+
+       return error;
+}
+
+/*
+ * H O S T S  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_hosts {
+       struct dns_hosts_entry {
+               char host[DNS_D_MAXNAME + 1];
+               char arpa[73 + 1];
+
+               int af;
+
+               union {
+                       struct in_addr a4;
+                       struct in6_addr a6;
+               } addr;
+
+               _Bool alias;
+
+               struct dns_hosts_entry *next;
+       } *head, **tail;
+
+       dns_atomic_t refcount;
+}; /* struct dns_hosts */
+
+
+struct dns_hosts *dns_hosts_open(int *error) {
+       static const struct dns_hosts hosts_initializer = { .refcount = 1 };
+       struct dns_hosts *hosts;
+
+       if (!(hosts = malloc(sizeof *hosts)))
+               goto syerr;
+
+       *hosts  = hosts_initializer;
+
+       hosts->tail     = &hosts->head;
+
+       return hosts;
+syerr:
+       *error  = dns_syerr();
+
+       free(hosts);
+
+       return 0;
+} /* dns_hosts_open() */
+
+
+void dns_hosts_close(struct dns_hosts *hosts) {
+       struct dns_hosts_entry *ent, *xnt;
+
+       if (!hosts || 1 != dns_hosts_release(hosts))
+               return;
+
+       for (ent = hosts->head; ent; ent = xnt) {
+               xnt     = ent->next;
+
+               free(ent);
+       }
+
+       free(hosts);
+
+       return;
+} /* dns_hosts_close() */
+
+
+dns_refcount_t dns_hosts_acquire(struct dns_hosts *hosts) {
+       return dns_atomic_fetch_add(&hosts->refcount);
+} /* dns_hosts_acquire() */
+
+
+dns_refcount_t dns_hosts_release(struct dns_hosts *hosts) {
+       return dns_atomic_fetch_sub(&hosts->refcount);
+} /* dns_hosts_release() */
+
+
+struct dns_hosts *dns_hosts_mortal(struct dns_hosts *hosts) {
+       if (hosts)
+               dns_hosts_release(hosts);
+
+       return hosts;
+} /* dns_hosts_mortal() */
+
+
+struct dns_hosts *dns_hosts_local(int *error_) {
+       struct dns_hosts *hosts;
+       int error;
+
+       if (!(hosts = dns_hosts_open(&error)))
+               goto error;
+
+       if ((error = dns_hosts_loadpath(hosts, "/etc/hosts")))
+               goto error;
+
+       return hosts;
+error:
+       *error_ = error;
+
+       dns_hosts_close(hosts);
+
+       return 0;
+} /* dns_hosts_local() */
+
+
+#define dns_hosts_issep(ch)    (dns_isspace(ch))
+#define dns_hosts_iscom(ch)    ((ch) == '#' || (ch) == ';')
+
+int dns_hosts_loadfile(struct dns_hosts *hosts, FILE *fp) {
+       struct dns_hosts_entry ent;
+       char word[DNS_PP_MAX(INET6_ADDRSTRLEN, DNS_D_MAXNAME) + 1];
+       unsigned wp, wc, skip;
+       int ch, error;
+
+       rewind(fp);
+
+       do {
+               memset(&ent, '\0', sizeof ent);
+               wc      = 0;
+               skip    = 0;
+
+               do {
+                       memset(word, '\0', sizeof word);
+                       wp      = 0;
+
+                       while (EOF != (ch = fgetc(fp)) && ch != '\n') {
+                               skip    |= !!dns_hosts_iscom(ch);
+
+                               if (skip)
+                                       continue;
+
+                               if (dns_hosts_issep(ch))
+                                       break;
+
+                               if (wp < sizeof word - 1)
+                                       word[wp]        = ch;
+                               wp++;
+                       }
+
+                       if (!wp)
+                               continue;
+
+                       wc++;
+
+                       switch (wc) {
+                       case 0:
+                               break;
+                       case 1:
+                               ent.af  = (strchr(word, ':'))? AF_INET6 : AF_INET;
+                               skip    = (1 != dns_inet_pton(ent.af, word, &ent.addr));
+
+                               break;
+                       default:
+                               if (!wp)
+                                       break;
+
+                               dns_d_anchor(ent.host, sizeof ent.host, word, wp);
+
+                               if ((error = dns_hosts_insert(hosts, ent.af, &ent.addr, ent.host, (wc > 2))))
+                                       return error;
+
+                               break;
+                       } /* switch() */
+               } while (ch != EOF && ch != '\n');
+       } while (ch != EOF);
+
+       return 0;
+} /* dns_hosts_loadfile() */
+
+
+int dns_hosts_loadpath(struct dns_hosts *hosts, const char *path) {
+       FILE *fp;
+       int error;
+
+       if (!(fp = dns_fopen(path, "rt", &error)))
+               return error;
+
+       error = dns_hosts_loadfile(hosts, fp);
+
+       fclose(fp);
+
+       return error;
+} /* dns_hosts_loadpath() */
+
+
+int dns_hosts_dump(struct dns_hosts *hosts, FILE *fp) {
+       struct dns_hosts_entry *ent, *xnt;
+       char addr[INET6_ADDRSTRLEN + 1];
+       unsigned i;
+
+       for (ent = hosts->head; ent; ent = xnt) {
+               xnt     = ent->next;
+
+               dns_inet_ntop(ent->af, &ent->addr, addr, sizeof addr);
+
+               fputs(addr, fp);
+
+               for (i = strlen(addr); i < INET_ADDRSTRLEN; i++)
+                       fputc(' ', fp);
+
+               fputc(' ', fp);
+
+               fputs(ent->host, fp);
+               fputc('\n', fp);
+       }
+
+       return 0;
+} /* dns_hosts_dump() */
+
+
+int dns_hosts_insert(struct dns_hosts *hosts, int af, const void *addr, const void *host, _Bool alias) {
+       struct dns_hosts_entry *ent;
+       int error;
+
+       if (!(ent = malloc(sizeof *ent)))
+               goto syerr;
+
+       dns_d_anchor(ent->host, sizeof ent->host, host, strlen(host));
+
+       switch ((ent->af = af)) {
+       case AF_INET6:
+               memcpy(&ent->addr.a6, addr, sizeof ent->addr.a6);
+
+               dns_aaaa_arpa(ent->arpa, sizeof ent->arpa, addr);
+
+               break;
+       case AF_INET:
+               memcpy(&ent->addr.a4, addr, sizeof ent->addr.a4);
+
+               dns_a_arpa(ent->arpa, sizeof ent->arpa, addr);
+
+               break;
+       default:
+               error   = EINVAL;
+
+               goto error;
+       } /* switch() */
+
+       ent->alias      = alias;
+
+       ent->next       = 0;
+       *hosts->tail    = ent;
+       hosts->tail     = &ent->next;
+
+       return 0;
+syerr:
+       error   = dns_syerr();
+error:
+       free(ent);
+
+       return error;
+} /* dns_hosts_insert() */
+
+
+struct dns_packet *dns_hosts_query(struct dns_hosts *hosts, struct dns_packet *Q, int *error_) {
+       struct dns_packet *P    = dns_p_new(512);
+       struct dns_packet *A    = 0;
+       struct dns_rr rr;
+       struct dns_hosts_entry *ent;
+       int error, af;
+       char qname[DNS_D_MAXNAME + 1];
+       size_t qlen;
+
+       if ((error = dns_rr_parse(&rr, 12, Q)))
+               goto error;
+
+       if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, Q, &error)))
+               goto error;
+       else if (qlen >= sizeof qname)
+               goto toolong;
+
+       if ((error = dns_p_push(P, DNS_S_QD, qname, qlen, rr.type, rr.class, 0, 0)))
+               goto error;
+
+       switch (rr.type) {
+       case DNS_T_PTR:
+               for (ent = hosts->head; ent; ent = ent->next) {
+                       if (ent->alias || 0 != strcasecmp(qname, ent->arpa))
+                               continue;
+
+                       if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, ent->host)))
+                               goto error;
+               }
+
+               break;
+       case DNS_T_AAAA:
+               af      = AF_INET6;
+
+               goto loop;
+       case DNS_T_A:
+               af      = AF_INET;
+
+loop:          for (ent = hosts->head; ent; ent = ent->next) {
+                       if (ent->af != af || 0 != strcasecmp(qname, ent->host))
+                               continue;
+
+                       if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, &ent->addr)))
+                               goto error;
+               }
+
+               break;
+       default:
+               break;
+       } /* switch() */
+
+
+       if (!(A = dns_p_copy(dns_p_make(P->end, &error), P)))
+               goto error;
+
+       return A;
+toolong:
+       error = DNS_EILLEGAL;
+error:
+       *error_ = error;
+
+       dns_p_free(A);
+
+       return 0;
+} /* dns_hosts_query() */
+
+
+/*
+ * R E S O L V . C O N F  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_resolv_conf *dns_resconf_open(int *error) {
+       static const struct dns_resolv_conf resconf_initializer = {
+               .lookup = "bf",
+               .family = { AF_INET, AF_INET6 },
+               .options = { .ndots = 1, .timeout = 5, .attempts = 2, .tcp = DNS_RESCONF_TCP_ENABLE, },
+               .iface = { .ss_family = AF_INET },
+       };
+       struct dns_resolv_conf *resconf;
+       struct sockaddr_in *sin;
+
+       if (!(resconf = malloc(sizeof *resconf)))
+               goto syerr;
+
+       *resconf = resconf_initializer;
+
+       sin = (struct sockaddr_in *)&resconf->nameserver[0];
+       sin->sin_family      = AF_INET;
+       sin->sin_addr.s_addr = INADDR_ANY;
+       sin->sin_port        = htons(53);
+#if defined(SA_LEN)
+       sin->sin_len         = sizeof *sin;
+#endif
+
+       if (0 != gethostname(resconf->search[0], sizeof resconf->search[0]))
+               goto syerr;
+
+       dns_d_anchor(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0]));
+       dns_d_cleave(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0]));
+
+       /*
+        * XXX: If gethostname() returned a string without any label
+        *      separator, then search[0][0] should be NUL.
+        */
+
+       dns_resconf_acquire(resconf);
+
+       return resconf;
+syerr:
+       *error  = dns_syerr();
+
+       free(resconf);
+
+       return 0;
+} /* dns_resconf_open() */
+
+
+void dns_resconf_close(struct dns_resolv_conf *resconf) {
+       if (!resconf || 1 != dns_resconf_release(resconf))
+               return /* void */;
+
+       free(resconf);
+} /* dns_resconf_close() */
+
+
+dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *resconf) {
+       return dns_atomic_fetch_add(&resconf->_.refcount);
+} /* dns_resconf_acquire() */
+
+
+dns_refcount_t dns_resconf_release(struct dns_resolv_conf *resconf) {
+       return dns_atomic_fetch_sub(&resconf->_.refcount);
+} /* dns_resconf_release() */
+
+
+struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *resconf) {
+       if (resconf)
+               dns_resconf_release(resconf);
+
+       return resconf;
+} /* dns_resconf_mortal() */
+
+
+struct dns_resolv_conf *dns_resconf_local(int *error_) {
+       struct dns_resolv_conf *resconf;
+       int error;
+
+       if (!(resconf = dns_resconf_open(&error)))
+               goto error;
+
+       if ((error = dns_resconf_loadpath(resconf, "/etc/resolv.conf"))) {
+               /*
+                * NOTE: Both the glibc and BIND9 resolvers ignore a missing
+                * /etc/resolv.conf, defaulting to a nameserver of
+                * 127.0.0.1. See also dns_hints_insert_resconf, and the
+                * default initialization of nameserver[0] in
+                * dns_resconf_open.
+                */
+               if (error != ENOENT)
+                       goto error;
+       }
+
+       if ((error = dns_nssconf_loadpath(resconf, "/etc/nsswitch.conf"))) {
+               if (error != ENOENT)
+                       goto error;
+       }
+
+       return resconf;
+error:
+       *error_ = error;
+
+       dns_resconf_close(resconf);
+
+       return 0;
+} /* dns_resconf_local() */
+
+
+struct dns_resolv_conf *dns_resconf_root(int *error) {
+       struct dns_resolv_conf *resconf;
+
+       if ((resconf = dns_resconf_local(error)))
+               resconf->options.recurse = 1;
+
+       return resconf;
+} /* dns_resconf_root() */
+
+
+static time_t dns_resconf_timeout(const struct dns_resolv_conf *resconf) {
+       return (time_t)DNS_PP_MIN(INT_MAX, resconf->options.timeout);
+} /* dns_resconf_timeout() */
+
+
+enum dns_resconf_keyword {
+       DNS_RESCONF_NAMESERVER,
+       DNS_RESCONF_DOMAIN,
+       DNS_RESCONF_SEARCH,
+       DNS_RESCONF_LOOKUP,
+       DNS_RESCONF_FILE,
+       DNS_RESCONF_BIND,
+       DNS_RESCONF_CACHE,
+       DNS_RESCONF_FAMILY,
+       DNS_RESCONF_INET4,
+       DNS_RESCONF_INET6,
+       DNS_RESCONF_OPTIONS,
+       DNS_RESCONF_EDNS0,
+       DNS_RESCONF_NDOTS,
+       DNS_RESCONF_TIMEOUT,
+       DNS_RESCONF_ATTEMPTS,
+       DNS_RESCONF_ROTATE,
+       DNS_RESCONF_RECURSE,
+       DNS_RESCONF_SMART,
+       DNS_RESCONF_TCP,
+       DNS_RESCONF_TCPx,
+       DNS_RESCONF_INTERFACE,
+       DNS_RESCONF_ZERO,
+       DNS_RESCONF_ONE,
+       DNS_RESCONF_ENABLE,
+       DNS_RESCONF_ONLY,
+       DNS_RESCONF_DISABLE,
+}; /* enum dns_resconf_keyword */
+
+static enum dns_resconf_keyword dns_resconf_keyword(const char *word) {
+       static const char *words[]      = {
+               [DNS_RESCONF_NAMESERVER]        = "nameserver",
+               [DNS_RESCONF_DOMAIN]            = "domain",
+               [DNS_RESCONF_SEARCH]            = "search",
+               [DNS_RESCONF_LOOKUP]            = "lookup",
+               [DNS_RESCONF_FILE]              = "file",
+               [DNS_RESCONF_BIND]              = "bind",
+               [DNS_RESCONF_CACHE]             = "cache",
+               [DNS_RESCONF_FAMILY]            = "family",
+               [DNS_RESCONF_INET4]             = "inet4",
+               [DNS_RESCONF_INET6]             = "inet6",
+               [DNS_RESCONF_OPTIONS]           = "options",
+               [DNS_RESCONF_EDNS0]             = "edns0",
+               [DNS_RESCONF_ROTATE]            = "rotate",
+               [DNS_RESCONF_RECURSE]           = "recurse",
+               [DNS_RESCONF_SMART]             = "smart",
+               [DNS_RESCONF_TCP]               = "tcp",
+               [DNS_RESCONF_INTERFACE]         = "interface",
+               [DNS_RESCONF_ZERO]              = "0",
+               [DNS_RESCONF_ONE]               = "1",
+               [DNS_RESCONF_ENABLE]            = "enable",
+               [DNS_RESCONF_ONLY]              = "only",
+               [DNS_RESCONF_DISABLE]           = "disable",
+       };
+       unsigned i;
+
+       for (i = 0; i < lengthof(words); i++) {
+               if (words[i] && 0 == strcasecmp(words[i], word))
+                       return i;
+       }
+
+       if (0 == strncasecmp(word, "ndots:", sizeof "ndots:" - 1))
+               return DNS_RESCONF_NDOTS;
+
+       if (0 == strncasecmp(word, "timeout:", sizeof "timeout:" - 1))
+               return DNS_RESCONF_TIMEOUT;
+
+       if (0 == strncasecmp(word, "attempts:", sizeof "attempts:" - 1))
+               return DNS_RESCONF_ATTEMPTS;
+
+       if (0 == strncasecmp(word, "tcp:", sizeof "tcp:" - 1))
+               return DNS_RESCONF_TCPx;
+
+       return -1;
+} /* dns_resconf_keyword() */
+
+
+/** OpenBSD-style "[1.2.3.4]:53" nameserver syntax */
+int dns_resconf_pton(struct sockaddr_storage *ss, const char *src) {
+       struct { char buf[128], *p; } addr = { "", addr.buf };
+       unsigned short port = 0;
+       int ch, af = AF_INET, error;
+
+       while ((ch = *src++)) {
+               switch (ch) {
+               case ' ':
+                       /* FALL THROUGH */
+               case '\t':
+                       break;
+               case '[':
+                       break;
+               case ']':
+                       while ((ch = *src++)) {
+                               if (dns_isdigit(ch)) {
+                                       port *= 10;
+                                       port += ch - '0';
+                               }
+                       }
+
+                       goto inet;
+               case ':':
+                       af = AF_INET6;
+
+                       /* FALL THROUGH */
+               default:
+                       if (addr.p < endof(addr.buf) - 1)
+                               *addr.p++ = ch;
+
+                       break;
+               } /* switch() */
+       } /* while() */
+inet:
+
+       if ((error = dns_pton(af, addr.buf, dns_sa_addr(af, ss, NULL))))
+               return error;
+
+       port = (!port)? 53 : port;
+       *dns_sa_port(af, ss) = htons(port);
+       dns_sa_family(ss) = af;
+
+       return 0;
+} /* dns_resconf_pton() */
+
+#define dns_resconf_issep(ch)  (dns_isspace(ch) || (ch) == ',')
+#define dns_resconf_iscom(ch)  ((ch) == '#' || (ch) == ';')
+
+int dns_resconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) {
+       unsigned sa_count       = 0;
+       char words[6][DNS_D_MAXNAME + 1];
+       unsigned wp, wc, i, j, n;
+       int ch, error;
+
+       rewind(fp);
+
+       do {
+               memset(words, '\0', sizeof words);
+               wp      = 0;
+               wc      = 0;
+
+               while (EOF != (ch = getc(fp)) && ch != '\n') {
+                       if (dns_resconf_issep(ch)) {
+                               if (wp > 0) {
+                                       wp      = 0;
+
+                                       if (++wc >= lengthof(words))
+                                               goto skip;
+                               }
+                       } else if (dns_resconf_iscom(ch)) {
+skip:
+                               do {
+                                       ch      = getc(fp);
+                               } while (ch != EOF && ch != '\n');
+
+                               break;
+                       } else if (wp < sizeof words[wc] - 1) {
+                               words[wc][wp++] = ch;
+                       } else {
+                               wp = 0; /* drop word */
+                               goto skip;
+                       }
+               }
+
+               if (wp > 0)
+                       wc++;
+
+               if (wc < 2)
+                       continue;
+
+               switch (dns_resconf_keyword(words[0])) {
+               case DNS_RESCONF_NAMESERVER:
+                       if (sa_count >= lengthof(resconf->nameserver))
+                               continue;
+
+                       if ((error = dns_resconf_pton(&resconf->nameserver[sa_count], words[1])))
+                               continue;
+
+                       sa_count++;
+
+                       break;
+               case DNS_RESCONF_DOMAIN:
+               case DNS_RESCONF_SEARCH:
+                       memset(resconf->search, '\0', sizeof resconf->search);
+
+                       for (i = 1, j = 0; i < wc && j < lengthof(resconf->search); i++, j++)
+                               dns_d_anchor(resconf->search[j], sizeof resconf->search[j], words[i], strlen(words[i]));
+
+                       break;
+               case DNS_RESCONF_LOOKUP:
+                       for (i = 1, j = 0; i < wc && j < lengthof(resconf->lookup); i++) {
+                               switch (dns_resconf_keyword(words[i])) {
+                               case DNS_RESCONF_FILE:
+                                       resconf->lookup[j++]    = 'f';
+
+                                       break;
+                               case DNS_RESCONF_BIND:
+                                       resconf->lookup[j++]    = 'b';
+
+                                       break;
+                               case DNS_RESCONF_CACHE:
+                                       resconf->lookup[j++]    = 'c';
+
+                                       break;
+                               default:
+                                       break;
+                               } /* switch() */
+                       } /* for() */
+
+                       break;
+               case DNS_RESCONF_FAMILY:
+                       for (i = 1, j = 0; i < wc && j < lengthof(resconf->family); i++) {
+                               switch (dns_resconf_keyword(words[i])) {
+                               case DNS_RESCONF_INET4:
+                                       resconf->family[j++]    = AF_INET;
+
+                                       break;
+                               case DNS_RESCONF_INET6:
+                                       resconf->family[j++]    = AF_INET6;
+
+                                       break;
+                               default:
+                                       break;
+                               }
+                       }
+
+                       break;
+               case DNS_RESCONF_OPTIONS:
+                       for (i = 1; i < wc; i++) {
+                               switch (dns_resconf_keyword(words[i])) {
+                               case DNS_RESCONF_EDNS0:
+                                       resconf->options.edns0  = 1;
+
+                                       break;
+                               case DNS_RESCONF_NDOTS:
+                                       for (j = sizeof "ndots:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
+                                               n       *= 10;
+                                               n       += words[i][j] - '0';
+                                       } /* for() */
+
+                                       resconf->options.ndots  = n;
+
+                                       break;
+                               case DNS_RESCONF_TIMEOUT:
+                                       for (j = sizeof "timeout:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
+                                               n       *= 10;
+                                               n       += words[i][j] - '0';
+                                       } /* for() */
+
+                                       resconf->options.timeout        = n;
+
+                                       break;
+                               case DNS_RESCONF_ATTEMPTS:
+                                       for (j = sizeof "attempts:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
+                                               n       *= 10;
+                                               n       += words[i][j] - '0';
+                                       } /* for() */
+
+                                       resconf->options.attempts       = n;
+
+                                       break;
+                               case DNS_RESCONF_ROTATE:
+                                       resconf->options.rotate         = 1;
+
+                                       break;
+                               case DNS_RESCONF_RECURSE:
+                                       resconf->options.recurse        = 1;
+
+                                       break;
+                               case DNS_RESCONF_SMART:
+                                       resconf->options.smart          = 1;
+
+                                       break;
+                               case DNS_RESCONF_TCP:
+                                       resconf->options.tcp            = DNS_RESCONF_TCP_ONLY;
+
+                                       break;
+                               case DNS_RESCONF_TCPx:
+                                       switch (dns_resconf_keyword(&words[i][sizeof "tcp:" - 1])) {
+                                       case DNS_RESCONF_ENABLE:
+                                               resconf->options.tcp    = DNS_RESCONF_TCP_ENABLE;
+
+                                               break;
+                                       case DNS_RESCONF_ONE:
+                                       case DNS_RESCONF_ONLY:
+                                               resconf->options.tcp    = DNS_RESCONF_TCP_ONLY;
+
+                                               break;
+                                       case DNS_RESCONF_ZERO:
+                                       case DNS_RESCONF_DISABLE:
+                                               resconf->options.tcp    = DNS_RESCONF_TCP_DISABLE;
+
+                                               break;
+                                       default:
+                                               break;
+                                       } /* switch() */
+
+                                       break;
+                               default:
+                                       break;
+                               } /* switch() */
+                       } /* for() */
+
+                       break;
+               case DNS_RESCONF_INTERFACE:
+                       for (i = 0, n = 0; dns_isdigit(words[2][i]); i++) {
+                               n       *= 10;
+                               n       += words[2][i] - '0';
+                       }
+
+                       dns_resconf_setiface(resconf, words[1], n);
+
+                       break;
+               default:
+                       break;
+               } /* switch() */
+       } while (ch != EOF);
+
+       return 0;
+} /* dns_resconf_loadfile() */
+
+
+int dns_resconf_loadpath(struct dns_resolv_conf *resconf, const char *path) {
+       FILE *fp;
+       int error;
+
+       if (!(fp = dns_fopen(path, "rt", &error)))
+               return error;
+
+       error = dns_resconf_loadfile(resconf, fp);
+
+       fclose(fp);
+
+       return error;
+} /* dns_resconf_loadpath() */
+
+
+struct dns_anyconf {
+       char *token[16];
+       unsigned count;
+       char buffer[1024], *tp, *cp;
+}; /* struct dns_anyconf */
+
+
+static void dns_anyconf_reset(struct dns_anyconf *cf) {
+       cf->count = 0;
+       cf->tp = cf->cp = cf->buffer;
+} /* dns_anyconf_reset() */
+
+
+static int dns_anyconf_push(struct dns_anyconf *cf) {
+       if (!(cf->cp < endof(cf->buffer) && cf->count < lengthof(cf->token)))
+               return ENOMEM;
+
+       *cf->cp++ = '\0';
+       cf->token[cf->count++] = cf->tp;
+       cf->tp = cf->cp;
+
+       return 0;
+} /* dns_anyconf_push() */
+
+
+static void dns_anyconf_pop(struct dns_anyconf *cf) {
+       if (cf->count > 0) {
+               --cf->count;
+               cf->tp = cf->cp = cf->token[cf->count];
+               cf->token[cf->count] = 0;
+       }
+} /* dns_anyconf_pop() */
+
+
+static int dns_anyconf_addc(struct dns_anyconf *cf, int ch) {
+       if (!(cf->cp < endof(cf->buffer)))
+               return ENOMEM;
+
+       *cf->cp++ = ch;
+
+       return 0;
+} /* dns_anyconf_addc() */
+
+
+static _Bool dns_anyconf_match(const char *pat, int mc) {
+       _Bool match;
+       int pc;
+
+       if (*pat == '^') {
+               match = 0;
+               ++pat;
+       } else {
+               match = 1;
+       }
+
+       while ((pc = *(const unsigned char *)pat++)) {
+               switch (pc) {
+               case '%':
+                       if (!(pc = *(const unsigned char *)pat++))
+                               return !match;
+
+                       switch (pc) {
+                       case 'a':
+                               if (dns_isalpha(mc))
+                                       return match;
+                               break;
+                       case 'd':
+                               if (dns_isdigit(mc))
+                                       return match;
+                               break;
+                       case 'w':
+                               if (dns_isalnum(mc))
+                                       return match;
+                               break;
+                       case 's':
+                               if (dns_isspace(mc))
+                                       return match;
+                               break;
+                       default:
+                               if (mc == pc)
+                                       return match;
+                               break;
+                       } /* switch() */
+
+                       break;
+               default:
+                       if (mc == pc)
+                               return match;
+                       break;
+               } /* switch() */
+       } /* while() */
+
+       return !match;
+} /* dns_anyconf_match() */
+
+
+static int dns_anyconf_peek(FILE *fp) {
+       int ch;
+       ch = getc(fp);
+       ungetc(ch, fp);
+       return ch;
+} /* dns_anyconf_peek() */
+
+
+static size_t dns_anyconf_skip(const char *pat, FILE *fp) {
+       size_t count = 0;
+       int ch;
+
+       while (EOF != (ch = getc(fp))) {
+               if (dns_anyconf_match(pat, ch)) {
+                       count++;
+                       continue;
+               }
+
+               ungetc(ch, fp);
+
+               break;
+       }
+
+       return count;
+} /* dns_anyconf_skip() */
+
+
+static size_t dns_anyconf_scan(struct dns_anyconf *cf, const char *pat, FILE *fp, int *error) {
+       size_t len;
+       int ch;
+
+       while (EOF != (ch = getc(fp))) {
+               if (dns_anyconf_match(pat, ch)) {
+                       if ((*error = dns_anyconf_addc(cf, ch)))
+                               return 0;
+
+                       continue;
+               } else {
+                       ungetc(ch, fp);
+
+                       break;
+               }
+       }
+
+       if ((len = cf->cp - cf->tp)) {
+               if ((*error = dns_anyconf_push(cf)))
+                       return 0;
+
+               return len;
+       } else {
+               *error = 0;
+
+               return 0;
+       }
+} /* dns_anyconf_scan() */
+
+
+DNS_NOTUSED static void dns_anyconf_dump(struct dns_anyconf *cf, FILE *fp) {
+       unsigned i;
+
+       fprintf(fp, "tokens:");
+
+       for (i = 0; i < cf->count; i++) {
+               fprintf(fp, " %s", cf->token[i]);
+       }
+
+       fputc('\n', fp);
+} /* dns_anyconf_dump() */
+
+
+enum dns_nssconf_keyword {
+       DNS_NSSCONF_INVALID = 0,
+       DNS_NSSCONF_HOSTS   = 1,
+       DNS_NSSCONF_SUCCESS,
+       DNS_NSSCONF_NOTFOUND,
+       DNS_NSSCONF_UNAVAIL,
+       DNS_NSSCONF_TRYAGAIN,
+       DNS_NSSCONF_CONTINUE,
+       DNS_NSSCONF_RETURN,
+       DNS_NSSCONF_FILES,
+       DNS_NSSCONF_DNS,
+       DNS_NSSCONF_MDNS,
+
+       DNS_NSSCONF_LAST,
+}; /* enum dns_nssconf_keyword */
+
+static enum dns_nssconf_keyword dns_nssconf_keyword(const char *word) {
+       static const char *list[] = {
+               [DNS_NSSCONF_HOSTS]    = "hosts",
+               [DNS_NSSCONF_SUCCESS]  = "success",
+               [DNS_NSSCONF_NOTFOUND] = "notfound",
+               [DNS_NSSCONF_UNAVAIL]  = "unavail",
+               [DNS_NSSCONF_TRYAGAIN] = "tryagain",
+               [DNS_NSSCONF_CONTINUE] = "continue",
+               [DNS_NSSCONF_RETURN]   = "return",
+               [DNS_NSSCONF_FILES]    = "files",
+               [DNS_NSSCONF_DNS]      = "dns",
+               [DNS_NSSCONF_MDNS]     = "mdns",
+       };
+       unsigned i;
+
+       for (i = 1; i < lengthof(list); i++) {
+               if (list[i] && 0 == strcasecmp(list[i], word))
+                       return i;
+       }
+
+       return DNS_NSSCONF_INVALID;
+} /* dns_nssconf_keyword() */
+
+
+static enum dns_nssconf_keyword dns_nssconf_c2k(int ch) {
+       static const char map[] = {
+               ['S'] = DNS_NSSCONF_SUCCESS,
+               ['N'] = DNS_NSSCONF_NOTFOUND,
+               ['U'] = DNS_NSSCONF_UNAVAIL,
+               ['T'] = DNS_NSSCONF_TRYAGAIN,
+               ['C'] = DNS_NSSCONF_CONTINUE,
+               ['R'] = DNS_NSSCONF_RETURN,
+               ['f'] = DNS_NSSCONF_FILES,
+               ['F'] = DNS_NSSCONF_FILES,
+               ['d'] = DNS_NSSCONF_DNS,
+               ['D'] = DNS_NSSCONF_DNS,
+               ['b'] = DNS_NSSCONF_DNS,
+               ['B'] = DNS_NSSCONF_DNS,
+               ['m'] = DNS_NSSCONF_MDNS,
+               ['M'] = DNS_NSSCONF_MDNS,
+       };
+
+       return (ch >= 0 && ch < (int)lengthof(map))? map[ch] : DNS_NSSCONF_INVALID;
+} /* dns_nssconf_c2k() */
+
+
+DNS_PRAGMA_PUSH
+DNS_PRAGMA_QUIET
+
+static int dns_nssconf_k2c(int k) {
+       static const char map[DNS_NSSCONF_LAST] = {
+               [DNS_NSSCONF_SUCCESS]  = 'S',
+               [DNS_NSSCONF_NOTFOUND] = 'N',
+               [DNS_NSSCONF_UNAVAIL]  = 'U',
+               [DNS_NSSCONF_TRYAGAIN] = 'T',
+               [DNS_NSSCONF_CONTINUE] = 'C',
+               [DNS_NSSCONF_RETURN]   = 'R',
+               [DNS_NSSCONF_FILES]    = 'f',
+               [DNS_NSSCONF_DNS]      = 'b',
+               [DNS_NSSCONF_MDNS]     = 'm',
+       };
+
+       return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : '?') : '?';
+} /* dns_nssconf_k2c() */
+
+static const char *dns_nssconf_k2s(int k) {
+       static const char *const map[DNS_NSSCONF_LAST] = {
+               [DNS_NSSCONF_SUCCESS]  = "SUCCESS",
+               [DNS_NSSCONF_NOTFOUND] = "NOTFOUND",
+               [DNS_NSSCONF_UNAVAIL]  = "UNAVAIL",
+               [DNS_NSSCONF_TRYAGAIN] = "TRYAGAIN",
+               [DNS_NSSCONF_CONTINUE] = "continue",
+               [DNS_NSSCONF_RETURN]   = "return",
+               [DNS_NSSCONF_FILES]    = "files",
+               [DNS_NSSCONF_DNS]      = "dns",
+               [DNS_NSSCONF_MDNS]     = "mdns",
+       };
+
+       return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : "") : "";
+} /* dns_nssconf_k2s() */
+
+DNS_PRAGMA_POP
+
+
+int dns_nssconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) {
+       enum dns_nssconf_keyword source, status, action;
+       char lookup[sizeof resconf->lookup] = "", *lp;
+       struct dns_anyconf cf;
+       size_t i;
+       int error;
+
+       while (!feof(fp) && !ferror(fp)) {
+               dns_anyconf_reset(&cf);
+
+               dns_anyconf_skip("%s", fp);
+
+               if (!dns_anyconf_scan(&cf, "%w_", fp, &error))
+                       goto nextent;
+
+               if (DNS_NSSCONF_HOSTS != dns_nssconf_keyword(cf.token[0]))
+                       goto nextent;
+
+               dns_anyconf_pop(&cf);
+
+               if (!dns_anyconf_skip(": \t", fp))
+                       goto nextent;
+
+               *(lp = lookup) = '\0';
+
+               while (dns_anyconf_scan(&cf, "%w_", fp, &error)) {
+                       dns_anyconf_skip(" \t", fp);
+
+                       if ('[' == dns_anyconf_peek(fp)) {
+                               dns_anyconf_skip("[ \t", fp);
+
+                               while (dns_anyconf_scan(&cf, "%w_", fp, &error)) {
+                                       dns_anyconf_skip("= \t", fp);
+                                       if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) {
+                                               dns_anyconf_pop(&cf); /* discard status */
+                                               dns_anyconf_skip("^#;]\n", fp); /* skip to end of criteria */
+                                               break;
+                                       }
+                                       dns_anyconf_skip(" \t", fp);
+                               }
+
+                               dns_anyconf_skip("] \t", fp);
+                       }
+
+                       if ((size_t)(endof(lookup) - lp) < cf.count + 1) /* +1 for '\0' */
+                               goto nextsrc;
+
+                       source = dns_nssconf_keyword(cf.token[0]);
+
+                       switch (source) {
+                       case DNS_NSSCONF_DNS:
+                       case DNS_NSSCONF_MDNS:
+                       case DNS_NSSCONF_FILES:
+                               *lp++ = dns_nssconf_k2c(source);
+                               break;
+                       default:
+                               goto nextsrc;
+                       }
+
+                       for (i = 1; i + 1 < cf.count; i += 2) {
+                               status = dns_nssconf_keyword(cf.token[i]);
+                               action = dns_nssconf_keyword(cf.token[i + 1]);
+
+                               switch (status) {
+                               case DNS_NSSCONF_SUCCESS:
+                               case DNS_NSSCONF_NOTFOUND:
+                               case DNS_NSSCONF_UNAVAIL:
+                               case DNS_NSSCONF_TRYAGAIN:
+                                       *lp++ = dns_nssconf_k2c(status);
+                                       break;
+                               default:
+                                       continue;
+                               }
+
+                               switch (action) {
+                               case DNS_NSSCONF_CONTINUE:
+                               case DNS_NSSCONF_RETURN:
+                                       break;
+                               default:
+                                       action = (status == DNS_NSSCONF_SUCCESS)
+                                              ? DNS_NSSCONF_RETURN
+                                              : DNS_NSSCONF_CONTINUE;
+                                       break;
+                               }
+
+                               *lp++ = dns_nssconf_k2c(action);
+                       }
+nextsrc:
+                       *lp = '\0';
+                       dns_anyconf_reset(&cf);
+               }
+nextent:
+               dns_anyconf_skip("^\n", fp);
+       }
+
+       if (*lookup)
+               strncpy(resconf->lookup, lookup, sizeof resconf->lookup);
+
+       return 0;
+} /* dns_nssconf_loadfile() */
+
+
+int dns_nssconf_loadpath(struct dns_resolv_conf *resconf, const char *path) {
+       FILE *fp;
+       int error;
+
+       if (!(fp = dns_fopen(path, "rt", &error)))
+               return error;
+
+       error = dns_nssconf_loadfile(resconf, fp);
+
+       fclose(fp);
+
+       return error;
+} /* dns_nssconf_loadpath() */
+
+
+struct dns_nssconf_source {
+       enum dns_nssconf_keyword source, success, notfound, unavail, tryagain;
+}; /* struct dns_nssconf_source */
+
+typedef unsigned dns_nssconf_i;
+
+static inline int dns_nssconf_peek(const struct dns_resolv_conf *resconf, dns_nssconf_i state) {
+       return (state < lengthof(resconf->lookup) && resconf->lookup[state])? resconf->lookup[state] : 0;
+} /* dns_nssconf_peek() */
+
+static _Bool dns_nssconf_next(struct dns_nssconf_source *src, const struct dns_resolv_conf *resconf, dns_nssconf_i *state) {
+       int source, status, action;
+
+       src->source = DNS_NSSCONF_INVALID;
+       src->success = DNS_NSSCONF_RETURN;
+       src->notfound = DNS_NSSCONF_CONTINUE;
+       src->unavail = DNS_NSSCONF_CONTINUE;
+       src->tryagain = DNS_NSSCONF_CONTINUE;
+
+       while ((source = dns_nssconf_peek(resconf, *state))) {
+               source = dns_nssconf_c2k(source);
+               ++*state;
+
+               switch (source) {
+               case DNS_NSSCONF_FILES:
+               case DNS_NSSCONF_DNS:
+               case DNS_NSSCONF_MDNS:
+                       src->source = source;
+                       break;
+               default:
+                       continue;
+               }
+
+               while ((status = dns_nssconf_peek(resconf, *state)) && (action = dns_nssconf_peek(resconf, *state + 1))) {
+                       status = dns_nssconf_c2k(status);
+                       action = dns_nssconf_c2k(action);
+
+                       switch (action) {
+                       case DNS_NSSCONF_RETURN:
+                       case DNS_NSSCONF_CONTINUE:
+                               break;
+                       default:
+                               goto done;
+                       }
+
+                       switch (status) {
+                       case DNS_NSSCONF_SUCCESS:
+                               src->success = action;
+                               break;
+                       case DNS_NSSCONF_NOTFOUND:
+                               src->notfound = action;
+                               break;
+                       case DNS_NSSCONF_UNAVAIL:
+                               src->unavail = action;
+                               break;
+                       case DNS_NSSCONF_TRYAGAIN:
+                               src->tryagain = action;
+                               break;
+                       default:
+                               goto done;
+                       }
+
+                       *state += 2;
+               }
+
+               break;
+       }
+done:
+       return src->source != DNS_NSSCONF_INVALID;
+} /* dns_nssconf_next() */
+
+
+static int dns_nssconf_dump_status(int status, int action, unsigned *count, FILE *fp) {
+       switch (status) {
+       case DNS_NSSCONF_SUCCESS:
+               if (action == DNS_NSSCONF_RETURN)
+                       return 0;
+               break;
+       default:
+               if (action == DNS_NSSCONF_CONTINUE)
+                       return 0;
+               break;
+       }
+
+       fputc(' ', fp);
+
+       if (!*count)
+               fputc('[', fp);
+
+       fprintf(fp, "%s=%s", dns_nssconf_k2s(status), dns_nssconf_k2s(action));
+
+       ++*count;
+
+       return 0;
+} /* dns_nssconf_dump_status() */
+
+
+int dns_nssconf_dump(struct dns_resolv_conf *resconf, FILE *fp) {
+       struct dns_nssconf_source src;
+       dns_nssconf_i i = 0;
+
+       fputs("hosts:", fp);
+
+       while (dns_nssconf_next(&src, resconf, &i)) {
+               unsigned n = 0;
+
+               fprintf(fp, " %s", dns_nssconf_k2s(src.source));
+
+               dns_nssconf_dump_status(DNS_NSSCONF_SUCCESS, src.success, &n, fp);
+               dns_nssconf_dump_status(DNS_NSSCONF_NOTFOUND, src.notfound, &n, fp);
+               dns_nssconf_dump_status(DNS_NSSCONF_UNAVAIL, src.unavail, &n, fp);
+               dns_nssconf_dump_status(DNS_NSSCONF_TRYAGAIN, src.tryagain, &n, fp);
+
+               if (n)
+                       fputc(']', fp);
+       }
+
+       fputc('\n', fp);
+
+       return 0;
+} /* dns_nssconf_dump() */
+
+
+int dns_resconf_setiface(struct dns_resolv_conf *resconf, const char *addr, unsigned short port) {
+       int af = (strchr(addr, ':'))? AF_INET6 : AF_INET;
+       int error;
+
+       if ((error = dns_pton(af, addr, dns_sa_addr(af, &resconf->iface, NULL))))
+               return error;
+
+       *dns_sa_port(af, &resconf->iface)       = htons(port);
+       resconf->iface.ss_family                = af;
+
+       return 0;
+} /* dns_resconf_setiface() */
+
+
+#define DNS_SM_RESTORE \
+       do { \
+               pc = 0xff & (*state >> 0); \
+               srchi = 0xff & (*state >> 8); \
+               ndots = 0xff & (*state >> 16); \
+       } while (0)
+
+#define DNS_SM_SAVE \
+       do { \
+               *state = ((0xff & pc) << 0) \
+                      | ((0xff & srchi) << 8) \
+                      | ((0xff & ndots) << 16); \
+       } while (0)
+
+size_t dns_resconf_search(void *dst, size_t lim, const void *qname, size_t qlen, struct dns_resolv_conf *resconf, dns_resconf_i_t *state) {
+       unsigned pc, srchi, ndots, len;
+
+       DNS_SM_ENTER;
+
+       /* if FQDN then return as-is and finish */
+       if (dns_d_isanchored(qname, qlen)) {
+               len = dns_d_anchor(dst, lim, qname, qlen);
+               DNS_SM_YIELD(len);
+               DNS_SM_EXIT;
+       }
+
+       ndots = dns_d_ndots(qname, qlen);
+
+       if (ndots >= resconf->options.ndots) {
+               len = dns_d_anchor(dst, lim, qname, qlen);
+               DNS_SM_YIELD(len);
+       }
+
+       while (srchi < lengthof(resconf->search) && resconf->search[srchi][0]) {
+               struct dns_buf buf = DNS_B_INTO(dst, lim);
+               const char *dn = resconf->search[srchi++];
+
+               dns_b_put(&buf, qname, qlen);
+               dns_b_putc(&buf, '.');
+               dns_b_puts(&buf, dn);
+               if (!dns_d_isanchored(dn, strlen(dn)))
+                       dns_b_putc(&buf, '.');
+               len = dns_b_strllen(&buf);
+               DNS_SM_YIELD(len);
+       }
+
+       if (ndots < resconf->options.ndots) {
+               len = dns_d_anchor(dst, lim, qname, qlen);
+               DNS_SM_YIELD(len);
+       }
+
+       DNS_SM_LEAVE;
+
+       return dns_strlcpy(dst, "", lim);
+} /* dns_resconf_search() */
+
+#undef DNS_SM_SAVE
+#undef DNS_SM_RESTORE
+
+
+int dns_resconf_dump(struct dns_resolv_conf *resconf, FILE *fp) {
+       unsigned i;
+       int af;
+
+       for (i = 0; i < lengthof(resconf->nameserver) && (af = resconf->nameserver[i].ss_family) != AF_UNSPEC; i++) {
+               char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]";
+               unsigned short port;
+
+               dns_inet_ntop(af, dns_sa_addr(af, &resconf->nameserver[i], NULL), addr, sizeof addr);
+               port = ntohs(*dns_sa_port(af, &resconf->nameserver[i]));
+
+               if (port == 53)
+                       fprintf(fp, "nameserver %s\n", addr);
+               else
+                       fprintf(fp, "nameserver [%s]:%hu\n", addr, port);
+       }
+
+
+       fprintf(fp, "search");
+
+       for (i = 0; i < lengthof(resconf->search) && resconf->search[i][0]; i++)
+               fprintf(fp, " %s", resconf->search[i]);
+
+       fputc('\n', fp);
+
+
+       fputs("; ", fp);
+       dns_nssconf_dump(resconf, fp);
+
+       fprintf(fp, "lookup");
+
+       for (i = 0; i < lengthof(resconf->lookup) && resconf->lookup[i]; i++) {
+               switch (resconf->lookup[i]) {
+               case 'b':
+                       fprintf(fp, " bind"); break;
+               case 'f':
+                       fprintf(fp, " file"); break;
+               case 'c':
+                       fprintf(fp, " cache"); break;
+               }
+       }
+
+       fputc('\n', fp);
+
+
+       fprintf(fp, "options ndots:%u timeout:%u attempts:%u", resconf->options.ndots, resconf->options.timeout, resconf->options.attempts);
+
+       if (resconf->options.edns0)
+               fprintf(fp, " edns0");
+       if (resconf->options.rotate)
+               fprintf(fp, " rotate");
+       if (resconf->options.recurse)
+               fprintf(fp, " recurse");
+       if (resconf->options.smart)
+               fprintf(fp, " smart");
+
+       switch (resconf->options.tcp) {
+       case DNS_RESCONF_TCP_ENABLE:
+               break;
+       case DNS_RESCONF_TCP_ONLY:
+               fprintf(fp, " tcp");
+               break;
+       case DNS_RESCONF_TCP_SOCKS:
+               fprintf(fp, " tcp:socks");
+               break;
+       case DNS_RESCONF_TCP_DISABLE:
+               fprintf(fp, " tcp:disable");
+               break;
+       }
+
+       fputc('\n', fp);
+
+
+       if ((af = resconf->iface.ss_family) != AF_UNSPEC) {
+               char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]";
+
+               dns_inet_ntop(af, dns_sa_addr(af, &resconf->iface, NULL), addr, sizeof addr);
+
+               fprintf(fp, "interface %s %hu\n", addr, ntohs(*dns_sa_port(af, &resconf->iface)));
+       }
+
+       return 0;
+} /* dns_resconf_dump() */
+
+
+/*
+ * H I N T  S E R V E R  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_hints_soa {
+       unsigned char zone[DNS_D_MAXNAME + 1];
+
+       struct {
+               struct sockaddr_storage ss;
+               unsigned priority;
+       } addrs[16];
+
+       unsigned count;
+
+       struct dns_hints_soa *next;
+}; /* struct dns_hints_soa */
+
+
+struct dns_hints {
+       dns_atomic_t refcount;
+
+       struct dns_hints_soa *head;
+}; /* struct dns_hints */
+
+
+struct dns_hints *dns_hints_open(struct dns_resolv_conf *resconf, int *error) {
+       static const struct dns_hints H_initializer;
+       struct dns_hints *H;
+
+       (void)resconf;
+
+       if (!(H = malloc(sizeof *H)))
+               goto syerr;
+
+       *H      = H_initializer;
+
+       dns_hints_acquire(H);
+
+       return H;
+syerr:
+       *error  = dns_syerr();
+
+       free(H);
+
+       return 0;
+} /* dns_hints_open() */
+
+
+void dns_hints_close(struct dns_hints *H) {
+       struct dns_hints_soa *soa, *nxt;
+
+       if (!H || 1 != dns_hints_release(H))
+               return /* void */;
+
+       for (soa = H->head; soa; soa = nxt) {
+               nxt     = soa->next;
+
+               free(soa);
+       }
+
+       free(H);
+
+       return /* void */;
+} /* dns_hints_close() */
+
+
+dns_refcount_t dns_hints_acquire(struct dns_hints *H) {
+       return dns_atomic_fetch_add(&H->refcount);
+} /* dns_hints_acquire() */
+
+
+dns_refcount_t dns_hints_release(struct dns_hints *H) {
+       return dns_atomic_fetch_sub(&H->refcount);
+} /* dns_hints_release() */
+
+
+struct dns_hints *dns_hints_mortal(struct dns_hints *hints) {
+       if (hints)
+               dns_hints_release(hints);
+
+       return hints;
+} /* dns_hints_mortal() */
+
+
+struct dns_hints *dns_hints_local(struct dns_resolv_conf *resconf, int *error_) {
+       struct dns_hints *hints         = 0;
+       int error;
+
+       if (resconf)
+               dns_resconf_acquire(resconf);
+       else if (!(resconf = dns_resconf_local(&error)))
+               goto error;
+
+       if (!(hints = dns_hints_open(resconf, &error)))
+               goto error;
+
+       error   = 0;
+
+       if (0 == dns_hints_insert_resconf(hints, ".", resconf, &error) && error)
+               goto error;
+
+       dns_resconf_close(resconf);
+
+       return hints;
+error:
+       *error_ = error;
+
+       dns_resconf_close(resconf);
+       dns_hints_close(hints);
+
+       return 0;
+} /* dns_hints_local() */
+
+
+struct dns_hints *dns_hints_root(struct dns_resolv_conf *resconf, int *error_) {
+       static const struct {
+               int af;
+               char addr[INET6_ADDRSTRLEN];
+       } root_hints[] = {
+               { AF_INET,      "198.41.0.4"            },      /* A.ROOT-SERVERS.NET. */
+               { AF_INET6,     "2001:503:ba3e::2:30"   },      /* A.ROOT-SERVERS.NET. */
+               { AF_INET,      "192.228.79.201"        },      /* B.ROOT-SERVERS.NET. */
+               { AF_INET6,     "2001:500:84::b"        },      /* B.ROOT-SERVERS.NET. */
+               { AF_INET,      "192.33.4.12"           },      /* C.ROOT-SERVERS.NET. */
+               { AF_INET6,     "2001:500:2::c"         },      /* C.ROOT-SERVERS.NET. */
+               { AF_INET,      "199.7.91.13"           },      /* D.ROOT-SERVERS.NET. */
+               { AF_INET6,     "2001:500:2d::d"        },      /* D.ROOT-SERVERS.NET. */
+               { AF_INET,      "192.203.230.10"        },      /* E.ROOT-SERVERS.NET. */
+               { AF_INET,      "192.5.5.241"           },      /* F.ROOT-SERVERS.NET. */
+               { AF_INET6,     "2001:500:2f::f"        },      /* F.ROOT-SERVERS.NET. */
+               { AF_INET,      "192.112.36.4"          },      /* G.ROOT-SERVERS.NET. */
+               { AF_INET,      "128.63.2.53"           },      /* H.ROOT-SERVERS.NET. */
+               { AF_INET6,     "2001:500:1::803f:235"  },      /* H.ROOT-SERVERS.NET. */
+               { AF_INET,      "192.36.148.17"         },      /* I.ROOT-SERVERS.NET. */
+               { AF_INET6,     "2001:7FE::53"          },      /* I.ROOT-SERVERS.NET. */
+               { AF_INET,      "192.58.128.30"         },      /* J.ROOT-SERVERS.NET. */
+               { AF_INET6,     "2001:503:c27::2:30"    },      /* J.ROOT-SERVERS.NET. */
+               { AF_INET,      "193.0.14.129"          },      /* K.ROOT-SERVERS.NET. */
+               { AF_INET6,     "2001:7FD::1"           },      /* K.ROOT-SERVERS.NET. */
+               { AF_INET,      "199.7.83.42"           },      /* L.ROOT-SERVERS.NET. */
+               { AF_INET6,     "2001:500:3::42"        },      /* L.ROOT-SERVERS.NET. */
+               { AF_INET,      "202.12.27.33"          },      /* M.ROOT-SERVERS.NET. */
+               { AF_INET6,     "2001:DC3::35"          },      /* M.ROOT-SERVERS.NET. */
+       };
+       struct dns_hints *hints         = 0;
+       struct sockaddr_storage ss;
+       unsigned i;
+       int error, af;
+
+       if (!(hints = dns_hints_open(resconf, &error)))
+               goto error;
+
+       for (i = 0; i < lengthof(root_hints); i++) {
+               af      = root_hints[i].af;
+
+               if ((error = dns_pton(af, root_hints[i].addr, dns_sa_addr(af, &ss, NULL))))
+                       goto error;
+
+               *dns_sa_port(af, &ss)   = htons(53);
+               ss.ss_family            = af;
+
+               if ((error = dns_hints_insert(hints, ".", (struct sockaddr *)&ss, 1)))
+                       goto error;
+       }
+
+       return hints;
+error:
+       *error_ = error;
+
+       dns_hints_close(hints);
+
+       return 0;
+} /* dns_hints_root() */
+
+
+static struct dns_hints_soa *dns_hints_fetch(struct dns_hints *H, const char *zone) {
+       struct dns_hints_soa *soa;
+
+       for (soa = H->head; soa; soa = soa->next) {
+               if (0 == strcasecmp(zone, (char *)soa->zone))
+                       return soa;
+       }
+
+       return 0;
+} /* dns_hints_fetch() */
+
+
+int dns_hints_insert(struct dns_hints *H, const char *zone, const struct sockaddr *sa, unsigned priority) {
+       static const struct dns_hints_soa soa_initializer;
+       struct dns_hints_soa *soa;
+       unsigned i;
+
+       if (!(soa = dns_hints_fetch(H, zone))) {
+               if (!(soa = malloc(sizeof *soa)))
+                       return dns_syerr();
+               *soa = soa_initializer;
+               dns_strlcpy((char *)soa->zone, zone, sizeof soa->zone);
+
+               soa->next = H->head;
+               H->head = soa;
+       }
+
+       i = soa->count % lengthof(soa->addrs);
+
+       memcpy(&soa->addrs[i].ss, sa, dns_sa_len(sa));
+
+       soa->addrs[i].priority = DNS_PP_MAX(1, priority);
+
+       if (soa->count < lengthof(soa->addrs))
+               soa->count++;
+
+       return 0;
+} /* dns_hints_insert() */
+
+
+static _Bool dns_hints_isinaddr_any(const void *sa) {
+       struct in_addr *addr;
+
+       if (dns_sa_family(sa) != AF_INET)
+               return 0;
+
+       addr = dns_sa_addr(AF_INET, sa, NULL);
+       return addr->s_addr == htonl(INADDR_ANY);
+}
+
+unsigned dns_hints_insert_resconf(struct dns_hints *H, const char *zone, const struct dns_resolv_conf *resconf, int *error_) {
+       unsigned i, n, p;
+       int error;
+
+       for (i = 0, n = 0, p = 1; i < lengthof(resconf->nameserver) && resconf->nameserver[i].ss_family != AF_UNSPEC; i++, n++) {
+               union { struct sockaddr_in sin; } tmp;
+               struct sockaddr *ns;
+
+               /*
+                * dns_resconf_open initializes nameserver[0] to INADDR_ANY.
+                *
+                * Traditionally the semantics of 0.0.0.0 meant the default
+                * interface, which evolved to mean the loopback interface.
+                * See comment block preceding resolv/res_init.c:res_init in
+                * glibc 2.23. As of 2.23, glibc no longer translates
+                * 0.0.0.0 despite the code comment, but it does default to
+                * 127.0.0.1 when no nameservers are present.
+                *
+                * BIND9 as of 9.10.3 still translates 0.0.0.0 to 127.0.0.1.
+                * See lib/lwres/lwconfig.c:lwres_create_addr and the
+                * convert_zero flag. 127.0.0.1 is also the default when no
+                * nameservers are present.
+                */
+               if (dns_hints_isinaddr_any(&resconf->nameserver[i])) {
+                       memcpy(&tmp.sin, &resconf->nameserver[i], sizeof tmp.sin);
+                       tmp.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+                       ns = (struct sockaddr *)&tmp.sin;
+               } else {
+                       ns = (struct sockaddr *)&resconf->nameserver[i];
+               }
+
+               if ((error = dns_hints_insert(H, zone, ns, p)))
+                       goto error;
+
+               p += !resconf->options.rotate;
+       }
+
+       return n;
+error:
+       *error_ = error;
+
+       return n;
+} /* dns_hints_insert_resconf() */
+
+
+static int dns_hints_i_cmp(unsigned a, unsigned b, struct dns_hints_i *i, struct dns_hints_soa *soa) {
+       int cmp;
+
+       if ((cmp = soa->addrs[a].priority - soa->addrs[b].priority))
+               return cmp;
+
+       return dns_k_shuffle16(a, i->state.seed) - dns_k_shuffle16(b, i->state.seed);
+} /* dns_hints_i_cmp() */
+
+
+static unsigned dns_hints_i_start(struct dns_hints_i *i, struct dns_hints_soa *soa) {
+       unsigned p0, p;
+
+       p0      = 0;
+
+       for (p = 1; p < soa->count; p++) {
+               if (dns_hints_i_cmp(p, p0, i, soa) < 0)
+                       p0      = p;
+       }
+
+       return p0;
+} /* dns_hints_i_start() */
+
+
+static unsigned dns_hints_i_skip(unsigned p0, struct dns_hints_i *i, struct dns_hints_soa *soa) {
+       unsigned pZ, p;
+
+       for (pZ = 0; pZ < soa->count; pZ++) {
+               if (dns_hints_i_cmp(pZ, p0, i, soa) > 0)
+                       goto cont;
+       }
+
+       return soa->count;
+cont:
+       for (p = pZ + 1; p < soa->count; p++) {
+               if (dns_hints_i_cmp(p, p0, i, soa) <= 0)
+                       continue;
+
+               if (dns_hints_i_cmp(p, pZ, i, soa) >= 0)
+                       continue;
+
+               pZ      = p;
+       }
+
+
+       return pZ;
+} /* dns_hints_i_skip() */
+
+
+static struct dns_hints_i *dns_hints_i_init(struct dns_hints_i *i, struct dns_hints *hints) {
+       static const struct dns_hints_i i_initializer;
+       struct dns_hints_soa *soa;
+
+       i->state        = i_initializer.state;
+
+       do {
+               i->state.seed   = dns_random();
+       } while (0 == i->state.seed);
+
+       if ((soa = dns_hints_fetch(hints, i->zone))) {
+               i->state.next   = dns_hints_i_start(i, soa);
+       }
+
+       return i;
+} /* dns_hints_i_init() */
+
+
+unsigned dns_hints_grep(struct sockaddr **sa, socklen_t *sa_len, unsigned lim, struct dns_hints_i *i, struct dns_hints *H) {
+       struct dns_hints_soa *soa;
+       unsigned n;
+
+       if (!(soa = dns_hints_fetch(H, i->zone)))
+               return 0;
+
+       n       = 0;
+
+       while (i->state.next < soa->count && n < lim) {
+               *sa     = (struct sockaddr *)&soa->addrs[i->state.next].ss;
+               *sa_len = dns_sa_len(*sa);
+
+               sa++;
+               sa_len++;
+               n++;
+
+               i->state.next   = dns_hints_i_skip(i->state.next, i, soa);
+       }
+
+       return n;
+} /* dns_hints_grep() */
+
+
+struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q, int *error_) {
+       struct dns_packet *A, *P;
+       struct dns_rr rr;
+       char zone[DNS_D_MAXNAME + 1];
+       size_t zlen;
+       struct dns_hints_i i;
+       struct sockaddr *sa;
+       socklen_t slen;
+       int error;
+
+       if (!dns_rr_grep(&rr, 1, dns_rr_i_new(Q, .section = DNS_S_QUESTION), Q, &error))
+               goto error;
+
+       if (!(zlen = dns_d_expand(zone, sizeof zone, rr.dn.p, Q, &error)))
+               goto error;
+       else if (zlen >= sizeof zone)
+               goto toolong;
+
+       P                       = dns_p_new(512);
+       dns_header(P)->qr       = 1;
+
+       if ((error = dns_rr_copy(P, &rr, Q)))
+               goto error;
+
+       if ((error = dns_p_push(P, DNS_S_AUTHORITY, ".", strlen("."), DNS_T_NS, DNS_C_IN, 0, "hints.local.")))
+               goto error;
+
+       do {
+               i.zone  = zone;
+
+               dns_hints_i_init(&i, hints);
+
+               while (dns_hints_grep(&sa, &slen, 1, &i, hints)) {
+                       int af          = sa->sa_family;
+                       int rtype       = (af == AF_INET6)? DNS_T_AAAA : DNS_T_A;
+
+                       if ((error = dns_p_push(P, DNS_S_ADDITIONAL, "hints.local.", strlen("hints.local."), rtype, DNS_C_IN, 0, dns_sa_addr(af, sa, NULL))))
+                               goto error;
+               }
+       } while ((zlen = dns_d_cleave(zone, sizeof zone, zone, zlen)));
+
+       if (!(A = dns_p_copy(dns_p_make(P->end, &error), P)))
+               goto error;
+
+       return A;
+toolong:
+       error = DNS_EILLEGAL;
+error:
+       *error_ = error;
+
+       return 0;
+} /* dns_hints_query() */
+
+
+/** ugly hack to support specifying ports other than 53 in resolv.conf. */
+static unsigned short dns_hints_port(struct dns_hints *hints, int af, void *addr) {
+       struct dns_hints_soa *soa;
+       void *addrsoa;
+       socklen_t addrlen;
+       unsigned short port;
+       unsigned i;
+
+       for (soa = hints->head; soa; soa = soa->next) {
+               for (i = 0; i < soa->count; i++) {
+                       if (af != soa->addrs[i].ss.ss_family)
+                               continue;
+
+                       if (!(addrsoa = dns_sa_addr(af, &soa->addrs[i].ss, &addrlen)))
+                               continue;
+
+                       if (memcmp(addr, addrsoa, addrlen))
+                               continue;
+
+                       port = *dns_sa_port(af, &soa->addrs[i].ss);
+
+                       return (port)? port : htons(53);
+               }
+       }
+
+       return htons(53);
+} /* dns_hints_port() */
+
+
+int dns_hints_dump(struct dns_hints *hints, FILE *fp) {
+       struct dns_hints_soa *soa;
+       char addr[INET6_ADDRSTRLEN];
+       unsigned i;
+       int af, error;
+
+       for (soa = hints->head; soa; soa = soa->next) {
+               fprintf(fp, "ZONE \"%s\"\n", soa->zone);
+
+               for (i = 0; i < soa->count; i++) {
+                       af = soa->addrs[i].ss.ss_family;
+
+                       if ((error = dns_ntop(af, dns_sa_addr(af, &soa->addrs[i].ss, NULL), addr, sizeof addr)))
+                               return error;
+
+                       fprintf(fp, "\t(%d) [%s]:%hu\n", (int)soa->addrs[i].priority, addr, ntohs(*dns_sa_port(af, &soa->addrs[i].ss)));
+               }
+       }
+
+       return 0;
+} /* dns_hints_dump() */
+
+
+/*
+ * C A C H E  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+static dns_refcount_t dns_cache_acquire(struct dns_cache *cache) {
+       return dns_atomic_fetch_add(&cache->_.refcount);
+} /* dns_cache_acquire() */
+
+
+static dns_refcount_t dns_cache_release(struct dns_cache *cache) {
+       return dns_atomic_fetch_sub(&cache->_.refcount);
+} /* dns_cache_release() */
+
+
+static struct dns_packet *dns_cache_query(struct dns_packet *query, struct dns_cache *cache, int *error) {
+       (void)query;
+       (void)cache;
+       (void)error;
+
+       return NULL;
+} /* dns_cache_query() */
+
+
+static int dns_cache_submit(struct dns_packet *query, struct dns_cache *cache) {
+       (void)query;
+       (void)cache;
+
+       return 0;
+} /* dns_cache_submit() */
+
+
+static int dns_cache_check(struct dns_cache *cache) {
+       (void)cache;
+
+       return 0;
+} /* dns_cache_check() */
+
+
+static struct dns_packet *dns_cache_fetch(struct dns_cache *cache, int *error) {
+       (void)cache;
+       (void)error;
+
+       return NULL;
+} /* dns_cache_fetch() */
+
+
+static int dns_cache_pollfd(struct dns_cache *cache) {
+       (void)cache;
+
+       return -1;
+} /* dns_cache_pollfd() */
+
+
+static short dns_cache_events(struct dns_cache *cache) {
+       (void)cache;
+
+       return 0;
+} /* dns_cache_events() */
+
+
+static void dns_cache_clear(struct dns_cache *cache) {
+       (void)cache;
+
+       return;
+} /* dns_cache_clear() */
+
+
+struct dns_cache *dns_cache_init(struct dns_cache *cache) {
+       static const struct dns_cache c_init = {
+               .acquire = &dns_cache_acquire,
+               .release = &dns_cache_release,
+               .query   = &dns_cache_query,
+               .submit  = &dns_cache_submit,
+               .check   = &dns_cache_check,
+               .fetch   = &dns_cache_fetch,
+               .pollfd  = &dns_cache_pollfd,
+               .events  = &dns_cache_events,
+               .clear   = &dns_cache_clear,
+               ._ = { .refcount = 1, },
+       };
+
+       *cache = c_init;
+
+       return cache;
+} /* dns_cache_init() */
+
+
+void dns_cache_close(struct dns_cache *cache) {
+       if (cache)
+               cache->release(cache);
+} /* dns_cache_close() */
+
+
+/*
+ * S O C K E T  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+static void dns_socketclose(int *fd, const struct dns_options *opts) {
+       if (opts && opts->closefd.cb)
+               opts->closefd.cb(fd, opts->closefd.arg);
+
+       if (*fd != -1) {
+#if _WIN32
+               closesocket(*fd);
+#else
+               close(*fd);
+#endif
+               *fd     = -1;
+       }
+} /* dns_socketclose() */
+
+
+#ifndef HAVE_IOCTLSOCKET
+#define HAVE_IOCTLSOCKET (_WIN32 || _WIN64)
+#endif
+
+#ifndef HAVE_SOCK_CLOEXEC
+#define HAVE_SOCK_CLOEXEC (defined SOCK_CLOEXEC)
+#endif
+
+#ifndef HAVE_SOCK_NONBLOCK
+#define HAVE_SOCK_NONBLOCK (defined SOCK_NONBLOCK)
+#endif
+
+#define DNS_SO_MAXTRY  7
+
+static int dns_socket(struct sockaddr *local, int type, int *error_) {
+       int fd = -1, flags, error;
+#if defined FIONBIO
+       unsigned long opt;
+#endif
+
+       flags = 0;
+#if HAVE_SOCK_CLOEXEC
+       flags |= SOCK_CLOEXEC;
+#endif
+#if HAVE_SOCK_NONBLOCK
+       flags |= SOCK_NONBLOCK;
+#endif
+       if (-1 == (fd = socket(local->sa_family, type|flags, 0)))
+               goto soerr;
+
+#if defined F_SETFD && !HAVE_SOCK_CLOEXEC
+       if (-1 == fcntl(fd, F_SETFD, 1))
+               goto syerr;
+#endif
+
+#if defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK
+       if (-1 == (flags = fcntl(fd, F_GETFL)))
+               goto syerr;
+       if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK))
+               goto syerr;
+#elif defined FIONBIO && HAVE_IOCTLSOCKET
+       opt = 1;
+       if (0 != ioctlsocket(fd, FIONBIO, &opt))
+               goto soerr;
+#endif
+
+#if defined SO_NOSIGPIPE
+       if (type != SOCK_DGRAM) {
+               if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, sizeof (int)))
+                       goto soerr;
+       }
+#endif
+
+       if (local->sa_family != AF_INET && local->sa_family != AF_INET6)
+               return fd;
+
+       if (type != SOCK_DGRAM)
+               return fd;
+
+       /*
+        * FreeBSD, Linux, OpenBSD, OS X, and Solaris use random ports by
+        * default. Though the ephemeral range is quite small on OS X
+        * (49152-65535 on 10.10) and Linux (32768-60999 on 4.4.0, Ubuntu
+        * Xenial). See also RFC 6056.
+        *
+        * TODO: Optionally rely on the kernel to select a random port.
+        */
+       if (*dns_sa_port(local->sa_family, local) == 0) {
+               struct sockaddr_storage tmp;
+               unsigned i, port;
+
+               memcpy(&tmp, local, dns_sa_len(local));
+
+               for (i = 0; i < DNS_SO_MAXTRY; i++) {
+                       port = 1025 + (dns_random() % 64510);
+
+                       *dns_sa_port(tmp.ss_family, &tmp) = htons(port);
+
+                       if (0 == bind(fd, (struct sockaddr *)&tmp, dns_sa_len(&tmp)))
+                               return fd;
+               }
+
+               /* NB: continue to next bind statement */
+       }
+
+       if (0 == bind(fd, local, dns_sa_len(local)))
+               return fd;
+
+       /* FALL THROUGH */
+soerr:
+       error = dns_soerr();
+
+       goto error;
+#if (defined F_SETFD && !HAVE_SOCK_CLOEXEC) || (defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK)
+syerr:
+       error = dns_syerr();
+
+       goto error;
+#endif
+error:
+       *error_ = error;
+
+       dns_socketclose(&fd, NULL);
+
+       return -1;
+} /* dns_socket() */
+
+
+enum {
+       DNS_SO_UDP_INIT = 1,
+       DNS_SO_UDP_CONN,
+       DNS_SO_UDP_SEND,
+       DNS_SO_UDP_RECV,
+       DNS_SO_UDP_DONE,
+
+       DNS_SO_TCP_INIT,
+       DNS_SO_TCP_CONN,
+       DNS_SO_TCP_SEND,
+       DNS_SO_TCP_RECV,
+       DNS_SO_TCP_DONE,
+
+       DNS_SO_SOCKS_INIT,
+       DNS_SO_SOCKS_CONN,
+       DNS_SO_SOCKS_HELLO_SEND,
+       DNS_SO_SOCKS_HELLO_RECV,
+       DNS_SO_SOCKS_AUTH_SEND,
+       DNS_SO_SOCKS_AUTH_RECV,
+       DNS_SO_SOCKS_REQUEST_PREPARE,
+       DNS_SO_SOCKS_REQUEST_SEND,
+       DNS_SO_SOCKS_REQUEST_RECV,
+       DNS_SO_SOCKS_REQUEST_RECV_V6,
+       DNS_SO_SOCKS_HANDSHAKE_DONE,
+};
+
+struct dns_socket {
+       struct dns_options opts;
+
+       int udp;
+       int tcp;
+
+       int *old;
+       unsigned onum, olim;
+
+       int type;
+
+       struct sockaddr_storage local, remote;
+
+       struct dns_k_permutor qids;
+
+       struct dns_stat stat;
+
+       struct dns_trace *trace;
+
+       /*
+        * NOTE: dns_so_reset() zeroes everything from here down.
+        */
+       int state;
+
+       unsigned short qid;
+       char qname[DNS_D_MAXNAME + 1];
+       size_t qlen;
+       enum dns_type qtype;
+       enum dns_class qclass;
+
+       struct dns_packet *query;
+       size_t qout;
+
+       /* During a SOCKS handshake the query is temporarily stored
+        * here.  */
+       struct dns_packet *query_backup;
+
+       struct dns_clock elapsed;
+
+       struct dns_packet *answer;
+       size_t alen, apos;
+}; /* struct dns_socket */
+
+
+/*
+ * NOTE: Actual closure delayed so that kqueue(2) and epoll(2) callers have
+ * a chance to recognize a state change after installing a persistent event
+ * and where sequential descriptors with the same integer value returned
+ * from _pollfd() would be ambiguous. See dns_so_closefds().
+ */
+static int dns_so_closefd(struct dns_socket *so, int *fd) {
+       int error;
+
+       if (*fd == -1)
+               return 0;
+
+       if (so->opts.closefd.cb) {
+               if ((error = so->opts.closefd.cb(fd, so->opts.closefd.arg))) {
+                       return error;
+               } else if (*fd == -1)
+                       return 0;
+       }
+
+       if (!(so->onum < so->olim)) {
+               unsigned olim = DNS_PP_MAX(4, so->olim * 2);
+               void *old;
+
+               if (!(old = realloc(so->old, sizeof so->old[0] * olim)))
+                       return dns_syerr();
+
+               so->old  = old;
+               so->olim = olim;
+       }
+
+       so->old[so->onum++] = *fd;
+       *fd = -1;
+
+       return 0;
+} /* dns_so_closefd() */
+
+
+#define DNS_SO_CLOSE_UDP 0x01
+#define DNS_SO_CLOSE_TCP 0x02
+#define DNS_SO_CLOSE_OLD 0x04
+#define DNS_SO_CLOSE_ALL (DNS_SO_CLOSE_UDP|DNS_SO_CLOSE_TCP|DNS_SO_CLOSE_OLD)
+
+static void dns_so_closefds(struct dns_socket *so, int which) {
+       if (DNS_SO_CLOSE_UDP & which)
+               dns_socketclose(&so->udp, &so->opts);
+       if (DNS_SO_CLOSE_TCP & which)
+               dns_socketclose(&so->tcp, &so->opts);
+       if (DNS_SO_CLOSE_OLD & which) {
+               unsigned i;
+               for (i = 0; i < so->onum; i++)
+                       dns_socketclose(&so->old[i], &so->opts);
+               so->onum = 0;
+               free(so->old);
+               so->old  = 0;
+               so->olim = 0;
+       }
+} /* dns_so_closefds() */
+
+
+static void dns_so_destroy(struct dns_socket *);
+
+static struct dns_socket *dns_so_init(struct dns_socket *so, const struct sockaddr *local, int type, const struct dns_options *opts, int *error) {
+       static const struct dns_socket so_initializer = { .opts = DNS_OPTS_INITIALIZER, .udp = -1, .tcp = -1, };
+
+       *so             = so_initializer;
+       so->type        = type;
+
+       if (opts)
+               so->opts = *opts;
+
+       if (local)
+               memcpy(&so->local, local, dns_sa_len(local));
+
+       if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, error)))
+               goto error;
+
+       dns_k_permutor_init(&so->qids, 1, 65535);
+
+       return so;
+error:
+       dns_so_destroy(so);
+
+       return 0;
+} /* dns_so_init() */
+
+
+struct dns_socket *dns_so_open(const struct sockaddr *local, int type, const struct dns_options *opts, int *error) {
+       struct dns_socket *so;
+
+       if (!(so = malloc(sizeof *so)))
+               goto syerr;
+
+       if (!dns_so_init(so, local, type, opts, error))
+               goto error;
+
+       return so;
+syerr:
+       *error  = dns_syerr();
+error:
+       dns_so_close(so);
+
+       return 0;
+} /* dns_so_open() */
+
+
+static void dns_so_destroy(struct dns_socket *so) {
+       dns_so_reset(so);
+       dns_so_closefds(so, DNS_SO_CLOSE_ALL);
+       dns_trace_close(so->trace);
+} /* dns_so_destroy() */
+
+
+void dns_so_close(struct dns_socket *so) {
+       if (!so)
+               return;
+
+       dns_so_destroy(so);
+
+       free(so);
+} /* dns_so_close() */
+
+
+void dns_so_reset(struct dns_socket *so) {
+       dns_p_setptr(&so->answer, NULL);
+
+       memset(&so->state, '\0', sizeof *so - offsetof(struct dns_socket, state));
+} /* dns_so_reset() */
+
+
+unsigned short dns_so_mkqid(struct dns_socket *so) {
+       return dns_k_permutor_step(&so->qids);
+} /* dns_so_mkqid() */
+
+
+#define DNS_SO_MINBUF  768
+
+static int dns_so_newanswer(struct dns_socket *so, size_t len) {
+       size_t size     = offsetof(struct dns_packet, data) + DNS_PP_MAX(len, DNS_SO_MINBUF);
+       void *p;
+
+       if (!(p = realloc(so->answer, size)))
+               return dns_syerr();
+
+       so->answer      = dns_p_init(p, size);
+
+       return 0;
+} /* dns_so_newanswer() */
+
+
+int dns_so_submit(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host) {
+       struct dns_rr rr;
+       int error = DNS_EUNKNOWN;
+
+       dns_so_reset(so);
+
+       if ((error = dns_rr_parse(&rr, 12, Q)))
+               goto error;
+
+       if (!(so->qlen = dns_d_expand(so->qname, sizeof so->qname, rr.dn.p, Q, &error)))
+               goto error;
+       /*
+        * NOTE: Don't bail if expansion is too long; caller may be
+        * intentionally sending long names. However, we won't be able to
+        * verify it on return.
+        */
+
+       so->qtype       = rr.type;
+       so->qclass      = rr.class;
+
+       if ((error = dns_so_newanswer(so, (Q->memo.opt.maxudp)? Q->memo.opt.maxudp : DNS_SO_MINBUF)))
+               goto syerr;
+
+       memcpy(&so->remote, host, dns_sa_len(host));
+
+       so->query       = Q;
+       so->qout        = 0;
+
+       dns_begin(&so->elapsed);
+
+       if (dns_header(so->query)->qid == 0)
+               dns_header(so->query)->qid      = dns_so_mkqid(so);
+
+       so->qid         = dns_header(so->query)->qid;
+       so->state       = (so->opts.socks_host && so->opts.socks_host->ss_family) ? DNS_SO_SOCKS_INIT :
+               (so->type == SOCK_STREAM)? DNS_SO_TCP_INIT : DNS_SO_UDP_INIT;
+
+       so->stat.queries++;
+       dns_trace_so_submit(so->trace, Q, host, 0);
+
+       return 0;
+syerr:
+       error   = dns_syerr();
+error:
+       dns_so_reset(so);
+       dns_trace_so_submit(so->trace, Q, host, error);
+       return error;
+} /* dns_so_submit() */
+
+
+static int dns_so_verify(struct dns_socket *so, struct dns_packet *P) {
+       char qname[DNS_D_MAXNAME + 1];
+       size_t qlen;
+       struct dns_rr rr;
+       int error = -1;
+
+       if (P->end < 12)
+               goto reject;
+
+       if (so->qid != dns_header(P)->qid)
+               goto reject;
+
+       if (!dns_p_count(P, DNS_S_QD))
+               goto reject;
+
+       if (0 != dns_rr_parse(&rr, 12, P))
+               goto reject;
+
+       if (rr.type != so->qtype || rr.class != so->qclass)
+               goto reject;
+
+       if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, P, &error)))
+               goto error;
+       else if (qlen >= sizeof qname || qlen != so->qlen)
+               goto reject;
+
+       if (0 != strcasecmp(so->qname, qname))
+               goto reject;
+
+       dns_trace_so_verify(so->trace, P, 0);
+
+       return 0;
+reject:
+       error = DNS_EVERIFY;
+error:
+       DNS_SHOW(P, "rejecting packet (%s)", dns_strerror(error));
+       dns_trace_so_verify(so->trace, P, error);
+
+       return error;
+} /* dns_so_verify() */
+
+
+static _Bool dns_so_tcp_keep(struct dns_socket *so) {
+       struct sockaddr_storage remote;
+
+       if (so->tcp == -1)
+               return 0;
+
+       if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &(socklen_t){ sizeof remote }))
+               return 0;
+
+       return 0 == dns_sa_cmp(&remote, &so->remote);
+} /* dns_so_tcp_keep() */
+
+
+/* Convenience functions for sending non-DNS data.  */
+
+/* Set up everything for sending LENGTH octets.  Returns the buffer
+   for the data.  */
+static unsigned char *dns_so_tcp_send_buffer(struct dns_socket *so, size_t length) {
+       /* Skip the length octets, we are not doing DNS.  */
+       so->qout = 2;
+       so->query->end = length;
+       return so->query->data;
+}
+
+/* Set up everything for receiving LENGTH octets.  */
+static void dns_so_tcp_recv_expect(struct dns_socket *so, size_t length) {
+       /* Skip the length octets, we are not doing DNS.  */
+       so->apos = 2;
+       so->alen = length;
+}
+
+/* Returns the buffer containing the received data.  */
+static unsigned char *dns_so_tcp_recv_buffer(struct dns_socket *so) {
+       return so->answer->data;
+}
+
+
+#if defined __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warray-bounds"
+#endif
+
+static int dns_so_tcp_send(struct dns_socket *so) {
+       unsigned char *qsrc;
+       size_t qend;
+       int error;
+       size_t n;
+
+       so->query->data[-2] = 0xff & (so->query->end >> 8);
+       so->query->data[-1] = 0xff & (so->query->end >> 0);
+
+       qend = so->query->end + 2;
+
+       while (so->qout < qend) {
+               qsrc = &so->query->data[-2] + so->qout;
+               n = dns_send_nopipe(so->tcp, (void *)qsrc, qend - so->qout, 0, &error);
+               dns_trace_sys_send(so->trace, so->tcp, SOCK_STREAM, qsrc, n, error);
+               if (error)
+                       return error;
+               so->qout += n;
+               so->stat.tcp.sent.bytes += n;
+       }
+
+       so->stat.tcp.sent.count++;
+
+       return 0;
+} /* dns_so_tcp_send() */
+
+
+static int dns_so_tcp_recv(struct dns_socket *so) {
+       unsigned char *asrc;
+       size_t aend, alen, n;
+       int error;
+
+       aend = so->alen + 2;
+
+       while (so->apos < aend) {
+               asrc = &so->answer->data[-2];
+
+               n = dns_recv(so->tcp, (void *)&asrc[so->apos], aend - so->apos, 0, &error);
+               dns_trace_sys_recv(so->trace, so->tcp, SOCK_STREAM, &asrc[so->apos], n, error);
+               if (error)
+                       return error;
+
+               so->apos += n;
+               so->stat.tcp.rcvd.bytes += n;
+
+               if (so->alen == 0 && so->apos >= 2) {
+                       alen = ((0xff & so->answer->data[-2]) << 8)
+                            | ((0xff & so->answer->data[-1]) << 0);
+
+                       if ((error = dns_so_newanswer(so, alen)))
+                               return error;
+
+                       so->alen = alen;
+                       aend = alen + 2;
+               }
+       }
+
+       so->answer->end = so->alen;
+       so->stat.tcp.rcvd.count++;
+
+       return 0;
+} /* dns_so_tcp_recv() */
+
+#if __clang__
+#pragma clang diagnostic pop
+#endif
+
+
+int dns_so_check(struct dns_socket *so) {
+       int error;
+       size_t n;
+       unsigned char *buffer;
+
+retry:
+       switch (so->state) {
+       case DNS_SO_UDP_INIT:
+               so->state++;
+       case DNS_SO_UDP_CONN:
+               error = dns_connect(so->udp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote));
+               dns_trace_sys_connect(so->trace, so->udp, SOCK_DGRAM, (struct sockaddr *)&so->remote, error);
+               if (error)
+                       goto error;
+
+               so->state++;
+       case DNS_SO_UDP_SEND:
+               n = dns_send(so->udp, (void *)so->query->data, so->query->end, 0, &error);
+               dns_trace_sys_send(so->trace, so->udp, SOCK_DGRAM, so->query->data, n, error);
+               if (error)
+                       goto error;
+
+               so->stat.udp.sent.bytes += n;
+               so->stat.udp.sent.count++;
+
+               so->state++;
+       case DNS_SO_UDP_RECV:
+               n = dns_recv(so->udp, (void *)so->answer->data, so->answer->size, 0, &error);
+               dns_trace_sys_recv(so->trace, so->udp, SOCK_DGRAM, so->answer->data, n, error);
+               if (error)
+                       goto error;
+
+               so->answer->end = n;
+               so->stat.udp.rcvd.bytes += n;
+               so->stat.udp.rcvd.count++;
+
+               if ((error = dns_so_verify(so, so->answer)))
+                       goto trash;
+
+               so->state++;
+       case DNS_SO_UDP_DONE:
+               if (!dns_header(so->answer)->tc || so->type == SOCK_DGRAM)
+                       return 0;
+
+               so->state++;
+       case DNS_SO_TCP_INIT:
+               if (dns_so_tcp_keep(so)) {
+                       so->state = DNS_SO_TCP_SEND;
+
+                       goto retry;
+               }
+
+               if ((error = dns_so_closefd(so, &so->tcp)))
+                       goto error;
+
+               if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error)))
+                       goto error;
+
+               so->state++;
+       case DNS_SO_TCP_CONN:
+               error = dns_connect(so->tcp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote));
+               dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)&so->remote, error);
+               if (error && error != DNS_EISCONN)
+                       goto error;
+
+               so->state++;
+       case DNS_SO_TCP_SEND:
+               if ((error = dns_so_tcp_send(so)))
+                       goto error;
+
+               so->state++;
+       case DNS_SO_TCP_RECV:
+               if ((error = dns_so_tcp_recv(so)))
+                       goto error;
+
+               so->state++;
+       case DNS_SO_TCP_DONE:
+               /* close unless DNS_RESCONF_TCP_ONLY (see dns_res_tcp2type) */
+               if (so->type != SOCK_STREAM) {
+                       if ((error = dns_so_closefd(so, &so->tcp)))
+                               goto error;
+               }
+
+               if ((error = dns_so_verify(so, so->answer)))
+                       goto error;
+
+               return 0;
+       case DNS_SO_SOCKS_INIT:
+               if ((error = dns_so_closefd(so, &so->tcp)))
+                       goto error;
+
+               if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error)))
+                       goto error;
+
+               so->state++;
+       case DNS_SO_SOCKS_CONN: {
+               unsigned char method;
+
+               error = dns_connect(so->tcp, (struct sockaddr *)so->opts.socks_host, dns_sa_len(so->opts.socks_host));
+               dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)so->opts.socks_host, error);
+               if (error && error != DNS_EISCONN)
+                       goto error;
+
+               /* We need to do a handshake with the SOCKS server,
+                * but the query is already in the buffer.  Move it
+                * out of the way.  */
+               dns_p_movptr(&so->query_backup, &so->query);
+
+               /* Create a new buffer for the handshake.  */
+               dns_p_grow(&so->query);
+
+               /* Negotiate method.  */
+               buffer = dns_so_tcp_send_buffer(so, 3);
+               buffer[0] = 5; /* RFC-1928 VER field.  */
+               buffer[1] = 1; /* NMETHODS */
+               if (so->opts.socks_user)
+                       method = 2;  /* Method: username/password authentication. */
+               else
+                       method = 0;  /* Method: No authentication required. */
+               buffer[2] = method;
+
+               so->state++;
+       }
+       case DNS_SO_SOCKS_HELLO_SEND:
+               if ((error = dns_so_tcp_send(so)))
+                       goto error;
+
+               dns_so_tcp_recv_expect(so, 2);
+               so->state++;
+       case DNS_SO_SOCKS_HELLO_RECV: {
+               unsigned char method;
+
+               if ((error = dns_so_tcp_recv(so)))
+                       goto error;
+
+               buffer = dns_so_tcp_recv_buffer(so);
+               method = so->opts.socks_user ? 2 : 0;
+               if (buffer[0] != 5 || buffer[1] != method) {
+                       /* Socks server returned wrong version or does
+                          not support our requested method.  */
+                       error = ENOTSUP; /* Fixme: Is there a better errno? */
+                       goto error;
+               }
+
+               if (method == 0) {
+                       /* No authentication, go ahead and send the
+                          request.  */
+                       so->state = DNS_SO_SOCKS_REQUEST_PREPARE;
+                       goto retry;
+               }
+
+               /* Prepare username/password sub-negotiation.  */
+               if (! so->opts.socks_password) {
+                       error = EINVAL; /* No password given.  */
+                       goto error;
+               } else {
+                       size_t buflen, ulen, plen;
+
+                       ulen = strlen(so->opts.socks_user);
+                       plen = strlen(so->opts.socks_password);
+                       if (!ulen || ulen > 255 || !plen || plen > 255) {
+                               error = EINVAL; /* Credentials too long or too short.  */
+                               goto error;
+                       }
+
+                       buffer = dns_so_tcp_send_buffer(so, 3 + ulen + plen);
+                       buffer[0] = 1; /* VER of the sub-negotiation. */
+                       buffer[1] = (unsigned char) ulen;
+                       buflen = 2;
+                       memcpy (buffer+buflen, so->opts.socks_user, ulen);
+                       buflen += ulen;
+                       buffer[buflen++] = (unsigned char) plen;
+                       memcpy (buffer+buflen, so->opts.socks_password, plen);
+               }
+
+               so->state++;
+       }
+       case DNS_SO_SOCKS_AUTH_SEND:
+               if ((error = dns_so_tcp_send(so)))
+                       goto error;
+
+               /* Skip the two length octets, and receive two octets.  */
+               dns_so_tcp_recv_expect(so, 2);
+
+               so->state++;
+       case DNS_SO_SOCKS_AUTH_RECV:
+               if ((error = dns_so_tcp_recv(so)))
+                       goto error;
+
+               buffer = dns_so_tcp_recv_buffer(so);
+               if (buffer[0] != 1) {
+                       /* SOCKS server returned wrong version.  */
+                       error = EPROTO;
+                       goto error;
+               }
+               if (buffer[1]) {
+                       /* SOCKS server denied access.  */
+                       error = EACCES;
+                       goto error;
+               }
+
+               so->state++;
+       case DNS_SO_SOCKS_REQUEST_PREPARE:
+               /* Send request details (rfc-1928, 4).  */
+               buffer = dns_so_tcp_send_buffer(so, so->remote.ss_family == AF_INET6 ? 22 : 10);
+               buffer[0] = 5; /* VER  */
+               buffer[1] = 1; /* CMD = CONNECT  */
+               buffer[2] = 0; /* RSV  */
+               if (so->remote.ss_family == AF_INET6) {
+                       struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&so->remote;
+
+                       buffer[3] = 4; /* ATYP = IPv6 */
+                       memcpy (buffer+ 4, &addr_in6->sin6_addr.s6_addr, 16); /* DST.ADDR */
+                       memcpy (buffer+20, &addr_in6->sin6_port, 2);          /* DST.PORT */
+               } else {
+                       struct sockaddr_in *addr_in = (struct sockaddr_in *)&so->remote;
+
+                       buffer[3] = 1; /* ATYP = IPv4 */
+                       memcpy (buffer+4, &addr_in->sin_addr.s_addr, 4); /* DST.ADDR */
+                       memcpy (buffer+8, &addr_in->sin_port, 2);        /* DST.PORT */
+               }
+
+               so->state++;
+       case DNS_SO_SOCKS_REQUEST_SEND:
+               if ((error = dns_so_tcp_send(so)))
+                       goto error;
+
+               /* Expect ten octets.  This is the length of the
+                * response assuming a IPv4 address is used.  */
+               dns_so_tcp_recv_expect(so, 10);
+               so->state++;
+       case DNS_SO_SOCKS_REQUEST_RECV:
+               if ((error = dns_so_tcp_recv(so)))
+                       goto error;
+
+               buffer = dns_so_tcp_recv_buffer(so);
+               if (buffer[0] != 5 || buffer[2] != 0) {
+                       /* Socks server returned wrong version or the
+                          reserved field is not zero.  */
+                       error = EPROTO;
+                       goto error;
+               }
+               if (buffer[1]) {
+                       switch (buffer[1]) {
+                       case 0x01: /* general SOCKS server failure.  */
+                               error = ENETDOWN;
+                               break;
+                       case 0x02: /* connection not allowed by ruleset.  */
+                               error = EACCES;
+                               break;
+                       case 0x03: /* Network unreachable */
+                               error = ENETUNREACH;
+                               break;
+                       case 0x04: /* Host unreachable */
+                               error = EHOSTUNREACH;
+                               break;
+                       case 0x05: /* Connection refused */
+                               error = ECONNREFUSED;
+                               break;
+                       case 0x06: /* TTL expired */
+                               error = ETIMEDOUT;
+                               break;
+                       case 0x08: /* Address type not supported */
+                               error = EPROTONOSUPPORT;
+                               break;
+                       case 0x07: /* Command not supported */
+                       default:
+                               error = ENOTSUP; /* Fixme: Is there a better error? */
+                               break;
+                       }
+                       goto error;
+               }
+
+               if (buffer[3] == 1) {
+                       /* This was indeed an IPv4 address.  */
+                       so->state = DNS_SO_SOCKS_HANDSHAKE_DONE;
+                       goto retry;
+               }
+
+               if (buffer[3] != 4) {
+                       error = ENOTSUP;
+                       goto error;
+               }
+
+               /* Expect receive twelve octets.  This accounts for
+                * the remaining bytes assuming an IPv6 address is
+                * used.  */
+               dns_so_tcp_recv_expect(so, 12);
+               so->state++;
+       case DNS_SO_SOCKS_REQUEST_RECV_V6:
+               if ((error = dns_so_tcp_recv(so)))
+                       goto error;
+
+               so->state++;
+       case DNS_SO_SOCKS_HANDSHAKE_DONE:
+               /* We have not way to store the actual address used by
+                * the server.  Then again, we don't really care.  */
+
+               /* Restore the query.  */
+               dns_p_movptr(&so->query, &so->query_backup);
+
+               /* Reset cursors.  */
+               so->qout = 0;
+               so->apos = 0;
+               so->alen = 0;
+
+               /* SOCKS handshake is done.  Proceed with the
+                * lookup.  */
+               so->state = DNS_SO_TCP_SEND;
+               goto retry;
+       default:
+               error   = DNS_EUNKNOWN;
+
+               goto error;
+       } /* switch() */
+
+trash:
+       DNS_CARP("discarding packet");
+       goto retry;
+error:
+       switch (error) {
+       case DNS_EINTR:
+               goto retry;
+       case DNS_EINPROGRESS:
+               /* FALL THROUGH */
+       case DNS_EALREADY:
+               /* FALL THROUGH */
+#if DNS_EWOULDBLOCK != DNS_EAGAIN
+       case DNS_EWOULDBLOCK:
+               /* FALL THROUGH */
+#endif
+               error   = DNS_EAGAIN;
+
+               break;
+       } /* switch() */
+
+       return error;
+} /* dns_so_check() */
+
+
+struct dns_packet *dns_so_fetch(struct dns_socket *so, int *error) {
+       struct dns_packet *answer;
+
+       switch (so->state) {
+       case DNS_SO_UDP_DONE:
+       case DNS_SO_TCP_DONE:
+               answer          = so->answer;
+               so->answer      = 0;
+               dns_trace_so_fetch(so->trace, answer, 0);
+
+               return answer;
+       default:
+               *error  = DNS_EUNKNOWN;
+               dns_trace_so_fetch(so->trace, NULL, *error);
+
+               return 0;
+       }
+} /* dns_so_fetch() */
+
+
+struct dns_packet *dns_so_query(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host, int *error_) {
+       struct dns_packet *A;
+       int error;
+
+       if (!so->state) {
+               if ((error = dns_so_submit(so, Q, host)))
+                       goto error;
+       }
+
+       if ((error = dns_so_check(so)))
+               goto error;
+
+       if (!(A = dns_so_fetch(so, &error)))
+               goto error;
+
+       dns_so_reset(so);
+
+       return A;
+error:
+       *error_ = error;
+
+       return 0;
+} /* dns_so_query() */
+
+
+time_t dns_so_elapsed(struct dns_socket *so) {
+       return dns_elapsed(&so->elapsed);
+} /* dns_so_elapsed() */
+
+
+void dns_so_clear(struct dns_socket *so) {
+       dns_so_closefds(so, DNS_SO_CLOSE_OLD);
+} /* dns_so_clear() */
+
+
+static int dns_so_events2(struct dns_socket *so, enum dns_events type) {
+       int events = 0;
+
+       switch (so->state) {
+       case DNS_SO_UDP_CONN:
+       case DNS_SO_UDP_SEND:
+               events |= DNS_POLLOUT;
+
+               break;
+       case DNS_SO_UDP_RECV:
+               events |= DNS_POLLIN;
+
+               break;
+       case DNS_SO_TCP_CONN:
+       case DNS_SO_TCP_SEND:
+               events |= DNS_POLLOUT;
+
+               break;
+       case DNS_SO_TCP_RECV:
+               events |= DNS_POLLIN;
+
+               break;
+       } /* switch() */
+
+       switch (type) {
+       case DNS_LIBEVENT:
+               return DNS_POLL2EV(events);
+       default:
+               return events;
+       } /* switch() */
+} /* dns_so_events2() */
+
+
+int dns_so_events(struct dns_socket *so) {
+       return dns_so_events2(so, so->opts.events);
+} /* dns_so_events() */
+
+
+int dns_so_pollfd(struct dns_socket *so) {
+       switch (so->state) {
+       case DNS_SO_UDP_CONN:
+       case DNS_SO_UDP_SEND:
+       case DNS_SO_UDP_RECV:
+               return so->udp;
+       case DNS_SO_TCP_CONN:
+       case DNS_SO_TCP_SEND:
+       case DNS_SO_TCP_RECV:
+               return so->tcp;
+       } /* switch() */
+
+       return -1;
+} /* dns_so_pollfd() */
+
+
+int dns_so_poll(struct dns_socket *so, int timeout) {
+       return dns_poll(dns_so_pollfd(so), dns_so_events2(so, DNS_SYSPOLL), timeout);
+} /* dns_so_poll() */
+
+
+const struct dns_stat *dns_so_stat(struct dns_socket *so) {
+       return &so->stat;
+} /* dns_so_stat() */
+
+
+struct dns_trace *dns_so_trace(struct dns_socket *so) {
+       return so->trace;
+} /* dns_so_trace() */
+
+
+void dns_so_settrace(struct dns_socket *so, struct dns_trace *trace) {
+       struct dns_trace *otrace = so->trace;
+       so->trace = dns_trace_acquire_p(trace);
+       dns_trace_close(otrace);
+} /* dns_so_settrace() */
+
+
+/*
+ * R E S O L V E R  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+enum dns_res_state {
+       DNS_R_INIT,
+       DNS_R_GLUE,
+       DNS_R_SWITCH,           /* (B)IND, (F)ILE, (C)ACHE */
+
+       DNS_R_FILE,             /* Lookup in local hosts database */
+
+       DNS_R_CACHE,            /* Lookup in application cache */
+       DNS_R_SUBMIT,
+       DNS_R_CHECK,
+       DNS_R_FETCH,
+
+       DNS_R_BIND,             /* Lookup in the network */
+       DNS_R_SEARCH,
+       DNS_R_HINTS,
+       DNS_R_ITERATE,
+       DNS_R_FOREACH_NS,
+       DNS_R_RESOLV0_NS,       /* Prologue: Setup next frame and recurse */
+       DNS_R_RESOLV1_NS,       /* Epilog: Inspect answer */
+       DNS_R_FOREACH_A,
+       DNS_R_QUERY_A,
+       DNS_R_CNAME0_A,
+       DNS_R_CNAME1_A,
+
+       DNS_R_FINISH,
+       DNS_R_SMART0_A,
+       DNS_R_SMART1_A,
+       DNS_R_DONE,
+       DNS_R_SERVFAIL,
+}; /* enum dns_res_state */
+
+
+#define DNS_R_MAXDEPTH 8
+#define DNS_R_ENDFRAME (DNS_R_MAXDEPTH - 1)
+
+struct dns_resolver {
+       struct dns_socket so;
+
+       struct dns_resolv_conf *resconf;
+       struct dns_hosts *hosts;
+       struct dns_hints *hints;
+       struct dns_cache *cache;
+       struct dns_trace *trace;
+
+       dns_atomic_t refcount;
+
+       /* Reset zeroes everything below here. */
+
+       char qname[DNS_D_MAXNAME + 1];
+       size_t qlen;
+
+       enum dns_type qtype;
+       enum dns_class qclass;
+
+       struct dns_clock elapsed;
+
+       dns_resconf_i_t search;
+
+       struct dns_rr_i smart;
+
+       struct dns_packet *nodata; /* answer if nothing better */
+
+       unsigned sp;
+
+       struct dns_res_frame {
+               enum dns_res_state state;
+
+               int error;
+               int which;      /* (B)IND, (F)ILE; index into resconf->lookup */
+               int qflags;
+
+               unsigned attempts;
+
+               struct dns_packet *query, *answer, *hints;
+
+               struct dns_rr_i hints_i, hints_j;
+               struct dns_rr hints_ns, ans_cname;
+       } stack[DNS_R_MAXDEPTH];
+}; /* struct dns_resolver */
+
+
+static int dns_res_tcp2type(int tcp) {
+       switch (tcp) {
+       case DNS_RESCONF_TCP_ONLY:
+       case DNS_RESCONF_TCP_SOCKS:
+               return SOCK_STREAM;
+       case DNS_RESCONF_TCP_DISABLE:
+               return SOCK_DGRAM;
+       default:
+               return 0;
+       }
+} /* dns_res_tcp2type() */
+
+struct dns_resolver *dns_res_open(struct dns_resolv_conf *resconf, struct dns_hosts *hosts, struct dns_hints *hints, struct dns_cache *cache, const struct dns_options *opts, int *_error) {
+       static const struct dns_resolver R_initializer
+               = { .refcount = 1, };
+       struct dns_resolver *R  = 0;
+       int type, error;
+
+       /*
+        * Grab ref count early because the caller may have passed us a mortal
+        * reference, and we want to do the right thing if we return early
+        * from an error.
+        */
+       if (resconf)
+               dns_resconf_acquire(resconf);
+       if (hosts)
+               dns_hosts_acquire(hosts);
+       if (hints)
+               dns_hints_acquire(hints);
+       if (cache)
+               dns_cache_acquire(cache);
+
+       /*
+        * Don't try to load it ourselves because a NULL object might be an
+        * error from, say, dns_resconf_root(), and loading
+        * dns_resconf_local() by default would create undesirable surpises.
+        */
+       if (!resconf || !hosts || !hints) {
+               if (!*_error)
+                       *_error = EINVAL;
+               goto _error;
+       }
+
+       if (!(R = malloc(sizeof *R)))
+               goto syerr;
+
+       *R      = R_initializer;
+       type    = dns_res_tcp2type(resconf->options.tcp);
+
+       if (!dns_so_init(&R->so, (struct sockaddr *)&resconf->iface, type, opts, &error))
+               goto error;
+
+       R->resconf      = resconf;
+       R->hosts        = hosts;
+       R->hints        = hints;
+       R->cache        = cache;
+
+       return R;
+syerr:
+       error   = dns_syerr();
+error:
+       *_error = error;
+_error:
+       dns_res_close(R);
+
+       dns_resconf_close(resconf);
+       dns_hosts_close(hosts);
+       dns_hints_close(hints);
+       dns_cache_close(cache);
+
+       return 0;
+} /* dns_res_open() */
+
+
+struct dns_resolver *dns_res_stub(const struct dns_options *opts, int *error) {
+       struct dns_resolv_conf *resconf = 0;
+       struct dns_hosts *hosts         = 0;
+       struct dns_hints *hints         = 0;
+       struct dns_resolver *res        = 0;
+
+       if (!(resconf = dns_resconf_local(error)))
+               goto epilog;
+
+       if (!(hosts = dns_hosts_local(error)))
+               goto epilog;
+
+       if (!(hints = dns_hints_local(resconf, error)))
+               goto epilog;
+
+       if (!(res = dns_res_open(resconf, hosts, hints, NULL, opts, error)))
+               goto epilog;
+
+epilog:
+       dns_resconf_close(resconf);
+       dns_hosts_close(hosts);
+       dns_hints_close(hints);
+
+       return res;
+} /* dns_res_stub() */
+
+
+static void dns_res_frame_destroy(struct dns_resolver *R, struct dns_res_frame *frame) {
+       (void)R;
+
+       dns_p_setptr(&frame->query, NULL);
+       dns_p_setptr(&frame->answer, NULL);
+       dns_p_setptr(&frame->hints, NULL);
+} /* dns_res_frame_destroy() */
+
+
+static void dns_res_frame_init(struct dns_resolver *R, struct dns_res_frame *frame) {
+       memset(frame, '\0', sizeof *frame);
+
+       /*
+        * NB: Can be invoked from dns_res_open, before R->resconf has been
+        * initialized.
+        */
+       if (R->resconf) {
+               if (!R->resconf->options.recurse)
+                       frame->qflags |= DNS_Q_RD;
+               if (R->resconf->options.edns0)
+                       frame->qflags |= DNS_Q_EDNS0;
+       }
+} /* dns_res_frame_init() */
+
+
+static void dns_res_frame_reset(struct dns_resolver *R, struct dns_res_frame *frame) {
+       dns_res_frame_destroy(R, frame);
+       dns_res_frame_init(R, frame);
+} /* dns_res_frame_reset() */
+
+
+static dns_error_t dns_res_frame_prepare(struct dns_resolver *R, struct dns_res_frame *F, const char *qname, enum dns_type qtype, enum dns_class qclass) {
+       struct dns_packet *P = NULL;
+
+       if (!(F < endof(R->stack)))
+               return DNS_EUNKNOWN;
+
+       dns_p_movptr(&P, &F->query);
+       dns_res_frame_reset(R, F);
+       dns_p_movptr(&F->query, &P);
+
+       return dns_q_make(&F->query, qname, qtype, qclass, F->qflags);
+} /* dns_res_frame_prepare() */
+
+
+void dns_res_reset(struct dns_resolver *R) {
+       unsigned i;
+
+       dns_so_reset(&R->so);
+       dns_p_setptr(&R->nodata, NULL);
+
+       for (i = 0; i < lengthof(R->stack); i++)
+               dns_res_frame_destroy(R, &R->stack[i]);
+
+       memset(&R->qname, '\0', sizeof *R - offsetof(struct dns_resolver, qname));
+
+       for (i = 0; i < lengthof(R->stack); i++)
+               dns_res_frame_init(R, &R->stack[i]);
+} /* dns_res_reset() */
+
+
+void dns_res_close(struct dns_resolver *R) {
+       if (!R || 1 < dns_res_release(R))
+               return;
+
+       dns_res_reset(R);
+
+       dns_so_destroy(&R->so);
+
+       dns_hints_close(R->hints);
+       dns_hosts_close(R->hosts);
+       dns_resconf_close(R->resconf);
+       dns_cache_close(R->cache);
+       dns_trace_close(R->trace);
+
+       free(R);
+} /* dns_res_close() */
+
+
+dns_refcount_t dns_res_acquire(struct dns_resolver *R) {
+       return dns_atomic_fetch_add(&R->refcount);
+} /* dns_res_acquire() */
+
+
+dns_refcount_t dns_res_release(struct dns_resolver *R) {
+       return dns_atomic_fetch_sub(&R->refcount);
+} /* dns_res_release() */
+
+
+struct dns_resolver *dns_res_mortal(struct dns_resolver *res) {
+       if (res)
+               dns_res_release(res);
+       return res;
+} /* dns_res_mortal() */
+
+
+static struct dns_packet *dns_res_merge(struct dns_packet *P0, struct dns_packet *P1, int *error_) {
+       size_t bufsiz   = P0->end + P1->end;
+       struct dns_packet *P[3] = { P0, P1, 0 };
+       struct dns_rr rr[3];
+       int error, copy, i;
+       enum dns_section section;
+
+retry:
+       if (!(P[2] = dns_p_make(bufsiz, &error)))
+               goto error;
+
+       dns_rr_foreach(&rr[0], P[0], .section = DNS_S_QD) {
+               if ((error = dns_rr_copy(P[2], &rr[0], P[0])))
+                       goto error;
+       }
+
+       for (section = DNS_S_AN; (DNS_S_ALL & section); section <<= 1) {
+               for (i = 0; i < 2; i++) {
+                       dns_rr_foreach(&rr[i], P[i], .section = section) {
+                               copy    = 1;
+
+                               dns_rr_foreach(&rr[2], P[2], .type = rr[i].type, .section = (DNS_S_ALL & ~DNS_S_QD)) {
+                                       if (0 == dns_rr_cmp(&rr[i], P[i], &rr[2], P[2])) {
+                                               copy    = 0;
+
+                                               break;
+                                       }
+                               }
+
+                               if (copy && (error = dns_rr_copy(P[2], &rr[i], P[i]))) {
+                                       if (error == DNS_ENOBUFS && bufsiz < 65535) {
+                                               dns_p_setptr(&P[2], NULL);
+
+                                               bufsiz  = DNS_PP_MAX(65535, bufsiz * 2);
+
+                                               goto retry;
+                                       }
+
+                                       goto error;
+                               }
+                       } /* foreach(rr) */
+               } /* foreach(packet) */
+       } /* foreach(section) */
+
+       return P[2];
+error:
+       *error_ = error;
+
+       dns_p_free(P[2]);
+
+       return 0;
+} /* dns_res_merge() */
+
+
+static struct dns_packet *dns_res_glue(struct dns_resolver *R, struct dns_packet *Q) {
+       struct dns_packet *P    = dns_p_new(512);
+       char qname[DNS_D_MAXNAME + 1];
+       size_t qlen;
+       enum dns_type qtype;
+       struct dns_rr rr;
+       unsigned sp;
+       int error;
+
+       if (!(qlen = dns_d_expand(qname, sizeof qname, 12, Q, &error))
+       ||  qlen >= sizeof qname)
+               return 0;
+
+       if (!(qtype = dns_rr_type(12, Q)))
+               return 0;
+
+       if ((error = dns_p_push(P, DNS_S_QD, qname, strlen(qname), qtype, DNS_C_IN, 0, 0)))
+               return 0;
+
+       for (sp = 0; sp <= R->sp; sp++) {
+               if (!R->stack[sp].answer)
+                       continue;
+
+               dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = qtype, .section = (DNS_S_ALL & ~DNS_S_QD)) {
+                       rr.section      = DNS_S_AN;
+
+                       if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer)))
+                               return 0;
+               }
+       }
+
+       if (dns_p_count(P, DNS_S_AN) > 0)
+               goto copy;
+
+       /* Otherwise, look for a CNAME */
+       for (sp = 0; sp <= R->sp; sp++) {
+               if (!R->stack[sp].answer)
+                       continue;
+
+               dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = DNS_T_CNAME, .section = (DNS_S_ALL & ~DNS_S_QD)) {
+                       rr.section      = DNS_S_AN;
+
+                       if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer)))
+                               return 0;
+               }
+       }
+
+       if (!dns_p_count(P, DNS_S_AN))
+               return 0;
+
+copy:
+       return dns_p_copy(dns_p_make(P->end, &error), P);
+} /* dns_res_glue() */
+
+
+/*
+ * Sort NS records by three criteria:
+ *
+ *     1) Whether glue is present.
+ *     2) Whether glue record is original or of recursive lookup.
+ *     3) Randomly shuffle records which share the above criteria.
+ *
+ * NOTE: Assumes only NS records passed, AND ASSUMES no new NS records will
+ *       be added during an iteration.
+ *
+ * FIXME: Only groks A glue, not AAAA glue.
+ */
+static int dns_res_nameserv_cmp(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
+       _Bool glued[2] = { 0 };
+       struct dns_rr x = { 0 }, y = { 0 };
+       struct dns_ns ns;
+       int cmp, error;
+
+       if (!(error = dns_ns_parse(&ns, a, P)))
+               glued[0] = !!dns_rr_grep(&x, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error);
+
+       if (!(error = dns_ns_parse(&ns, b, P)))
+               glued[1] = !!dns_rr_grep(&y, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error);
+
+       if ((cmp = glued[1] - glued[0])) {
+               return cmp;
+       } else if ((cmp = (dns_rr_offset(&y) < i->args[0]) - (dns_rr_offset(&x) < i->args[0]))) {
+               return cmp;
+       } else {
+               return dns_rr_i_shuffle(a, b, i, P);
+       }
+} /* dns_res_nameserv_cmp() */
+
+
+#define dgoto(sp, i)   \
+       do { R->stack[(sp)].state = (i); goto exec; } while (0)
+
+static int dns_res_exec(struct dns_resolver *R) {
+       struct dns_res_frame *F;
+       struct dns_packet *P;
+       union {
+               char host[DNS_D_MAXNAME + 1];
+               char name[DNS_D_MAXNAME + 1];
+               struct dns_ns ns;
+               struct dns_cname cname;
+       } u;
+       size_t len;
+       struct dns_rr rr;
+       int error;
+
+exec:
+
+       F       = &R->stack[R->sp];
+
+       switch (F->state) {
+       case DNS_R_INIT:
+               F->state++;
+       case DNS_R_GLUE:
+               if (R->sp == 0)
+                       dgoto(R->sp, DNS_R_SWITCH);
+
+               if (!F->query)
+                       goto noquery;
+
+               if (!(F->answer = dns_res_glue(R, F->query)))
+                       dgoto(R->sp, DNS_R_SWITCH);
+
+               if (!(len = dns_d_expand(u.name, sizeof u.name, 12, F->query, &error)))
+                       goto error;
+               else if (len >= sizeof u.name)
+                       goto toolong;
+
+               dns_rr_foreach(&rr, F->answer, .name = u.name, .type = dns_rr_type(12, F->query), .section = DNS_S_AN) {
+                       dgoto(R->sp, DNS_R_FINISH);
+               }
+
+               dns_rr_foreach(&rr, F->answer, .name = u.name, .type = DNS_T_CNAME, .section = DNS_S_AN) {
+                       F->ans_cname    = rr;
+
+                       dgoto(R->sp, DNS_R_CNAME0_A);
+               }
+
+               F->state++;
+       case DNS_R_SWITCH:
+               while (F->which < (int)sizeof R->resconf->lookup && R->resconf->lookup[F->which]) {
+                       switch (R->resconf->lookup[F->which++]) {
+                       case 'b': case 'B':
+                               dgoto(R->sp, DNS_R_BIND);
+                       case 'f': case 'F':
+                               dgoto(R->sp, DNS_R_FILE);
+                       case 'c': case 'C':
+                               if (R->cache)
+                                       dgoto(R->sp, DNS_R_CACHE);
+
+                               break;
+                       default:
+                               break;
+                       }
+               }
+
+               /*
+                * FIXME: Examine more closely whether our logic is correct
+                * and DNS_R_SERVFAIL is the correct default response.
+                *
+                * Case 1: We got here because we never got an answer on the
+                *   wire. All queries timed-out and we reached maximum
+                *   attempts count. See DNS_R_FOREACH_NS. In that case
+                *   DNS_R_SERVFAIL is the correct state, unless we want to
+                *   return DNS_ETIMEDOUT.
+                *
+                * Case 2: We were a stub resolver and got an unsatisfactory
+                *   answer (empty ANSWER section) which caused us to jump
+                *   back to DNS_R_SEARCH and ultimately to DNS_R_SWITCH. We
+                *   return the answer returned from the wire, which we
+                *   stashed in R->nodata.
+                *
+                * Case 3: We reached maximum attempts count as in case #1,
+                *   but never got an authoritative response which caused us
+                *   to short-circuit. See end of DNS_R_QUERY_A case. We
+                *   should probably prepare R->nodata as in case #2.
+                */
+               if (R->sp == 0 && R->nodata) { /* XXX: can we just return nodata regardless? */
+                       dns_p_movptr(&F->answer, &R->nodata);
+                       dgoto(R->sp, DNS_R_FINISH);
+               }
+
+               dgoto(R->sp, DNS_R_SERVFAIL);
+       case DNS_R_FILE:
+               if (R->sp > 0) {
+                       if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error)))
+                               goto error;
+
+                       if (dns_p_count(F->answer, DNS_S_AN) > 0)
+                               dgoto(R->sp, DNS_R_FINISH);
+
+                       dns_p_setptr(&F->answer, NULL);
+               } else {
+                       R->search = 0;
+
+                       while ((len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) {
+                               if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags)))
+                                       goto error;
+
+                               if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error)))
+                                       goto error;
+
+                               if (dns_p_count(F->answer, DNS_S_AN) > 0)
+                                       dgoto(R->sp, DNS_R_FINISH);
+
+                               dns_p_setptr(&F->answer, NULL);
+                       }
+               }
+
+               dgoto(R->sp, DNS_R_SWITCH);
+       case DNS_R_CACHE:
+               error = 0;
+
+               if (!F->query && (error = dns_q_make(&F->query, R->qname, R->qtype, R->qclass, F->qflags)))
+                       goto error;
+
+               if (dns_p_setptr(&F->answer, R->cache->query(F->query, R->cache, &error))) {
+                       if (dns_p_count(F->answer, DNS_S_AN) > 0)
+                               dgoto(R->sp, DNS_R_FINISH);
+
+                       dns_p_setptr(&F->answer, NULL);
+
+                       dgoto(R->sp, DNS_R_SWITCH);
+               } else if (error)
+                       goto error;
+
+               F->state++;
+       case DNS_R_SUBMIT:
+               if ((error = R->cache->submit(F->query, R->cache)))
+                       goto error;
+
+               F->state++;
+       case DNS_R_CHECK:
+               if ((error = R->cache->check(R->cache)))
+                       goto error;
+
+               F->state++;
+       case DNS_R_FETCH:
+               error = 0;
+
+               if (dns_p_setptr(&F->answer, R->cache->fetch(R->cache, &error))) {
+                       if (dns_p_count(F->answer, DNS_S_AN) > 0)
+                               dgoto(R->sp, DNS_R_FINISH);
+
+                       dns_p_setptr(&F->answer, NULL);
+
+                       dgoto(R->sp, DNS_R_SWITCH);
+               } else if (error)
+                       goto error;
+
+               dgoto(R->sp, DNS_R_SWITCH);
+       case DNS_R_BIND:
+               if (R->sp > 0) {
+                       if (!F->query)
+                               goto noquery;
+
+                       dgoto(R->sp, DNS_R_HINTS);
+               }
+
+               R->search = 0;
+
+               F->state++;
+       case DNS_R_SEARCH:
+               /*
+                * XXX: We probably should only apply the domain search
+                * algorithm if R->sp == 0.
+                */
+               if (!(len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search)))
+                       dgoto(R->sp, DNS_R_SWITCH);
+
+               if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags)))
+                       goto error;
+
+               F->state++;
+       case DNS_R_HINTS:
+               if (!dns_p_setptr(&F->hints, dns_hints_query(R->hints, F->query, &error)))
+                       goto error;
+
+               F->state++;
+       case DNS_R_ITERATE:
+               dns_rr_i_init(&F->hints_i, F->hints);
+
+               F->hints_i.section      = DNS_S_AUTHORITY;
+               F->hints_i.type         = DNS_T_NS;
+               F->hints_i.sort         = &dns_res_nameserv_cmp;
+               F->hints_i.args[0]      = F->hints->end;
+
+               F->state++;
+       case DNS_R_FOREACH_NS:
+               dns_rr_i_save(&F->hints_i);
+
+               /* Load our next nameserver host. */
+               if (!dns_rr_grep(&F->hints_ns, 1, &F->hints_i, F->hints, &error)) {
+                       if (++F->attempts < R->resconf->options.attempts)
+                               dgoto(R->sp, DNS_R_ITERATE);
+
+                       dgoto(R->sp, DNS_R_SWITCH);
+               }
+
+               dns_rr_i_init(&F->hints_j, F->hints);
+
+               /* Assume there are glue records */
+               dgoto(R->sp, DNS_R_FOREACH_A);
+       case DNS_R_RESOLV0_NS:
+               /* Have we reached our max depth? */
+               if (&F[1] >= endof(R->stack))
+                       dgoto(R->sp, DNS_R_FOREACH_NS);
+
+               if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints)))
+                       goto error;
+               if ((error = dns_res_frame_prepare(R, &F[1], u.ns.host, DNS_T_A, DNS_C_IN)))
+                       goto error;
+
+               F->state++;
+
+               dgoto(++R->sp, DNS_R_INIT);
+       case DNS_R_RESOLV1_NS:
+               if (!(len = dns_d_expand(u.host, sizeof u.host, 12, F[1].query, &error)))
+                       goto error;
+               else if (len >= sizeof u.host)
+                       goto toolong;
+
+               dns_rr_foreach(&rr, F[1].answer, .name = u.host, .type = DNS_T_A, .section = (DNS_S_ALL & ~DNS_S_QD)) {
+                       rr.section      = DNS_S_AR;
+
+                       if ((error = dns_rr_copy(F->hints, &rr, F[1].answer)))
+                               goto error;
+
+                       dns_rr_i_rewind(&F->hints_i);   /* Now there's glue. */
+               }
+
+               dgoto(R->sp, DNS_R_FOREACH_NS);
+       case DNS_R_FOREACH_A: {
+               struct dns_a a;
+               struct sockaddr_in sin;
+
+               /*
+                * NOTE: Iterator initialized in DNS_R_FOREACH_NS because
+                * this state is re-entrant, but we need to reset
+                * .name to a valid pointer each time.
+                */
+               if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints)))
+                       goto error;
+
+               F->hints_j.name         = u.ns.host;
+               F->hints_j.type         = DNS_T_A;
+               F->hints_j.section      = DNS_S_ALL & ~DNS_S_QD;
+
+               if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
+                       if (!dns_rr_i_count(&F->hints_j))
+                               dgoto(R->sp, DNS_R_RESOLV0_NS);
+
+                       dgoto(R->sp, DNS_R_FOREACH_NS);
+               }
+
+               if ((error = dns_a_parse(&a, &rr, F->hints)))
+                       goto error;
+
+               memset(&sin, '\0', sizeof sin); /* NB: silence valgrind */
+               sin.sin_family  = AF_INET;
+               sin.sin_addr    = a.addr;
+               if (R->sp == 0)
+                       sin.sin_port = dns_hints_port(R->hints, AF_INET, &sin.sin_addr);
+               else
+                       sin.sin_port = htons(53);
+
+               if (DNS_DEBUG) {
+                       char addr[INET_ADDRSTRLEN + 1];
+                       dns_a_print(addr, sizeof addr, &a);
+                       dns_header(F->query)->qid = dns_so_mkqid(&R->so);
+                       DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp);
+               }
+
+               dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin);
+
+               if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin)))
+                       goto error;
+
+               F->state++;
+       }
+       case DNS_R_QUERY_A:
+               if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf))
+                       dgoto(R->sp, DNS_R_FOREACH_A);
+
+               if ((error = dns_so_check(&R->so)))
+                       goto error;
+
+               if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error)))
+                       goto error;
+
+               if (DNS_DEBUG) {
+                       DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp);
+               }
+
+               if (dns_p_rcode(F->answer) == DNS_RC_FORMERR ||
+                   dns_p_rcode(F->answer) == DNS_RC_NOTIMP ||
+                   dns_p_rcode(F->answer) == DNS_RC_BADVERS) {
+                       /* Temporarily disable EDNS0 and try again. */
+                       if (F->qflags & DNS_Q_EDNS0) {
+                               F->qflags &= ~DNS_Q_EDNS0;
+                               if ((error = dns_q_remake(&F->query, F->qflags)))
+                                       goto error;
+
+                               dgoto(R->sp, DNS_R_FOREACH_A);
+                       }
+               }
+
+               if ((error = dns_rr_parse(&rr, 12, F->query)))
+                       goto error;
+
+               if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error)))
+                       goto error;
+               else if (len >= sizeof u.name)
+                       goto toolong;
+
+               dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) {
+                       dgoto(R->sp, DNS_R_FINISH);     /* Found */
+               }
+
+               dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) {
+                       F->ans_cname    = rr;
+
+                       dgoto(R->sp, DNS_R_CNAME0_A);
+               }
+
+               /*
+                * XXX: The condition here should probably check whether
+                * R->sp == 0, because DNS_R_SEARCH runs regardless of
+                * options.recurse. See DNS_R_BIND.
+                */
+               if (!R->resconf->options.recurse) {
+                       /* Make first answer our tentative answer */
+                       if (!R->nodata)
+                               dns_p_movptr(&R->nodata, &F->answer);
+
+                       dgoto(R->sp, DNS_R_SEARCH);
+               }
+
+               dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) {
+                       dns_p_movptr(&F->hints, &F->answer);
+
+                       dgoto(R->sp, DNS_R_ITERATE);
+               }
+
+               /* XXX: Should this go further up? */
+               if (dns_header(F->answer)->aa)
+                       dgoto(R->sp, DNS_R_FINISH);
+
+               /* XXX: Should we copy F->answer to R->nodata? */
+
+               dgoto(R->sp, DNS_R_FOREACH_A);
+       case DNS_R_CNAME0_A:
+               if (&F[1] >= endof(R->stack))
+                       dgoto(R->sp, DNS_R_FINISH);
+
+               if ((error = dns_cname_parse(&u.cname, &F->ans_cname, F->answer)))
+                       goto error;
+               if ((error = dns_res_frame_prepare(R, &F[1], u.cname.host, dns_rr_type(12, F->query), DNS_C_IN)))
+                       goto error;
+
+               F->state++;
+
+               dgoto(++R->sp, DNS_R_INIT);
+       case DNS_R_CNAME1_A:
+               if (!(P = dns_res_merge(F->answer, F[1].answer, &error)))
+                       goto error;
+
+               dns_p_setptr(&F->answer, P);
+
+               dgoto(R->sp, DNS_R_FINISH);
+       case DNS_R_FINISH:
+               if (!F->answer)
+                       goto noanswer;
+
+               if (!R->resconf->options.smart || R->sp > 0)
+                       dgoto(R->sp, DNS_R_DONE);
+
+               R->smart.section        = DNS_S_AN;
+               R->smart.type           = R->qtype;
+
+               dns_rr_i_init(&R->smart, F->answer);
+
+               F->state++;
+       case DNS_R_SMART0_A:
+               if (&F[1] >= endof(R->stack))
+                       dgoto(R->sp, DNS_R_DONE);
+
+               while (dns_rr_grep(&rr, 1, &R->smart, F->answer, &error)) {
+                       union {
+                               struct dns_ns ns;
+                               struct dns_mx mx;
+                               struct dns_srv srv;
+                       } rd;
+                       const char *qname;
+                       enum dns_type qtype;
+                       enum dns_class qclass;
+
+                       switch (rr.type) {
+                       case DNS_T_NS:
+                               if ((error = dns_ns_parse(&rd.ns, &rr, F->answer)))
+                                       goto error;
+
+                               qname   = rd.ns.host;
+                               qtype   = DNS_T_A;
+                               qclass  = DNS_C_IN;
+
+                               break;
+                       case DNS_T_MX:
+                               if ((error = dns_mx_parse(&rd.mx, &rr, F->answer)))
+                                       goto error;
+
+                               qname   = rd.mx.host;
+                               qtype   = DNS_T_A;
+                               qclass  = DNS_C_IN;
+
+                               break;
+                       case DNS_T_SRV:
+                               if ((error = dns_srv_parse(&rd.srv, &rr, F->answer)))
+                                       goto error;
+
+                               qname   = rd.srv.target;
+                               qtype   = DNS_T_A;
+                               qclass  = DNS_C_IN;
+
+                               break;
+                       default:
+                               continue;
+                       } /* switch() */
+
+                       if ((error = dns_res_frame_prepare(R, &F[1], qname, qtype, qclass)))
+                               goto error;
+
+                       F->state++;
+
+                       dgoto(++R->sp, DNS_R_INIT);
+               } /* while() */
+
+               /*
+                * NOTE: SMTP specification says to fallback to A record.
+                *
+                * XXX: Should we add a mock MX answer?
+                */
+               if (R->qtype == DNS_T_MX && R->smart.state.count == 0) {
+                       if ((error = dns_res_frame_prepare(R, &F[1], R->qname, DNS_T_A, DNS_C_IN)))
+                               goto error;
+
+                       R->smart.state.count++;
+                       F->state++;
+
+                       dgoto(++R->sp, DNS_R_INIT);
+               }
+
+               dgoto(R->sp, DNS_R_DONE);
+       case DNS_R_SMART1_A:
+               if (!F[1].answer)
+                       goto noanswer;
+
+               /*
+                * FIXME: For CNAME chains (which are typically illegal in
+                * this context), we should rewrite the record host name
+                * to the original smart qname. All the user cares about
+                * is locating that A/AAAA record.
+                */
+               dns_rr_foreach(&rr, F[1].answer, .section = DNS_S_AN, .type = DNS_T_A) {
+                       rr.section      = DNS_S_AR;
+
+                       if (dns_rr_exists(&rr, F[1].answer, F->answer))
+                               continue;
+
+                       while ((error = dns_rr_copy(F->answer, &rr, F[1].answer))) {
+                               if (error != DNS_ENOBUFS)
+                                       goto error;
+                               if ((error = dns_p_grow(&F->answer)))
+                                       goto error;
+                       }
+               }
+
+               dgoto(R->sp, DNS_R_SMART0_A);
+       case DNS_R_DONE:
+               if (!F->answer)
+                       goto noanswer;
+
+               if (R->sp > 0)
+                       dgoto(--R->sp, F[-1].state);
+
+               break;
+       case DNS_R_SERVFAIL:
+               if (!dns_p_setptr(&F->answer, dns_p_make(DNS_P_QBUFSIZ, &error)))
+                       goto error;
+
+               dns_header(F->answer)->qr       = 1;
+               dns_header(F->answer)->rcode    = DNS_RC_SERVFAIL;
+
+               if ((error = dns_p_push(F->answer, DNS_S_QD, R->qname, strlen(R->qname), R->qtype, R->qclass, 0, 0)))
+                       goto error;
+
+               dgoto(R->sp, DNS_R_DONE);
+       default:
+               error   = EINVAL;
+
+               goto error;
+       } /* switch () */
+
+       return 0;
+noquery:
+       error = DNS_ENOQUERY;
+
+       goto error;
+noanswer:
+       error = DNS_ENOANSWER;
+
+       goto error;
+toolong:
+       error = DNS_EILLEGAL;
+
+       /* FALL THROUGH */
+error:
+       return error;
+} /* dns_res_exec() */
+
+#undef goto
+
+
+void dns_res_clear(struct dns_resolver *R) {
+       switch (R->stack[R->sp].state) {
+       case DNS_R_CHECK:
+               R->cache->clear(R->cache);
+               break;
+       default:
+               dns_so_clear(&R->so);
+               break;
+       }
+} /* dns_res_clear() */
+
+
+static int dns_res_events2(struct dns_resolver *R, enum dns_events type) {
+       int events;
+
+       switch (R->stack[R->sp].state) {
+       case DNS_R_CHECK:
+               events = R->cache->events(R->cache);
+
+               return (type == DNS_LIBEVENT)? DNS_POLL2EV(events) : events;
+       default:
+               return dns_so_events2(&R->so, type);
+       }
+} /* dns_res_events2() */
+
+
+int dns_res_events(struct dns_resolver *R) {
+       return dns_res_events2(R, R->so.opts.events);
+} /* dns_res_events() */
+
+
+int dns_res_pollfd(struct dns_resolver *R) {
+       switch (R->stack[R->sp].state) {
+       case DNS_R_CHECK:
+               return R->cache->pollfd(R->cache);
+       default:
+               return dns_so_pollfd(&R->so);
+       }
+} /* dns_res_pollfd() */
+
+
+time_t dns_res_timeout(struct dns_resolver *R) {
+       time_t elapsed;
+
+       switch (R->stack[R->sp].state) {
+#if 0
+       case DNS_R_QUERY_AAAA:
+#endif
+       case DNS_R_QUERY_A:
+               elapsed = dns_so_elapsed(&R->so);
+
+               if (elapsed <= dns_resconf_timeout(R->resconf))
+                       return R->resconf->options.timeout - elapsed;
+
+               break;
+       default:
+               break;
+       } /* switch() */
+
+       /*
+        * NOTE: We're not in a pollable state, or the user code hasn't
+        * called dns_res_check properly. The calling code is probably
+        * broken. Put them into a slow-burn pattern.
+        */
+       return 1;
+} /* dns_res_timeout() */
+
+
+time_t dns_res_elapsed(struct dns_resolver *R) {
+       return dns_elapsed(&R->elapsed);
+} /* dns_res_elapsed() */
+
+
+int dns_res_poll(struct dns_resolver *R, int timeout) {
+       return dns_poll(dns_res_pollfd(R), dns_res_events2(R, DNS_SYSPOLL), timeout);
+} /* dns_res_poll() */
+
+
+int dns_res_submit2(struct dns_resolver *R, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass) {
+       dns_res_reset(R);
+
+       /* Don't anchor; that can conflict with searchlist generation. */
+       dns_d_init(R->qname, sizeof R->qname, qname, (R->qlen = qlen), 0);
+
+       R->qtype        = qtype;
+       R->qclass       = qclass;
+
+       dns_begin(&R->elapsed);
+
+       dns_trace_res_submit(R->trace, R->qname, R->qtype, R->qclass, 0);
+
+       return 0;
+} /* dns_res_submit2() */
+
+
+int dns_res_submit(struct dns_resolver *R, const char *qname, enum dns_type qtype, enum dns_class qclass) {
+       return dns_res_submit2(R, qname, strlen(qname), qtype, qclass);
+} /* dns_res_submit() */
+
+
+int dns_res_check(struct dns_resolver *R) {
+       int error;
+
+       if (R->stack[0].state != DNS_R_DONE) {
+               if ((error = dns_res_exec(R)))
+                       return error;
+       }
+
+       return 0;
+} /* dns_res_check() */
+
+
+struct dns_packet *dns_res_fetch(struct dns_resolver *R, int *_error) {
+       struct dns_packet *P = NULL;
+       int error;
+
+       if (R->stack[0].state != DNS_R_DONE) {
+               error = DNS_EUNKNOWN;
+               goto error;
+       }
+
+       if (!dns_p_movptr(&P, &R->stack[0].answer)) {
+               error = DNS_EFETCHED;
+               goto error;
+       }
+
+       dns_trace_res_fetch(R->trace, P, 0);
+
+       return P;
+error:
+       *_error = error;
+       dns_trace_res_fetch(R->trace, NULL, error);
+       return NULL;
+} /* dns_res_fetch() */
+
+
+static struct dns_packet *dns_res_fetch_and_study(struct dns_resolver *R, int *_error) {
+       struct dns_packet *P = NULL;
+       int error;
+
+       if (!(P = dns_res_fetch(R, &error)))
+               goto error;
+       if ((error = dns_p_study(P)))
+               goto error;
+
+       return P;
+error:
+       *_error = error;
+
+       dns_p_free(P);
+
+       return NULL;
+} /* dns_res_fetch_and_study() */
+
+
+struct dns_packet *dns_res_query(struct dns_resolver *res, const char *qname, enum dns_type qtype, enum dns_class qclass, int timeout, int *error_) {
+       int error;
+
+       if ((error = dns_res_submit(res, qname, qtype, qclass)))
+               goto error;
+
+       while ((error = dns_res_check(res))) {
+               if (dns_res_elapsed(res) > timeout)
+                       error = DNS_ETIMEDOUT;
+
+               if (error != DNS_EAGAIN)
+                       goto error;
+
+               if ((error = dns_res_poll(res, 1)))
+                       goto error;
+       }
+
+       return dns_res_fetch(res, error_);
+error:
+       *error_ = error;
+
+       return 0;
+} /* dns_res_query() */
+
+
+const struct dns_stat *dns_res_stat(struct dns_resolver *res) {
+       return dns_so_stat(&res->so);
+} /* dns_res_stat() */
+
+
+void dns_res_sethints(struct dns_resolver *res, struct dns_hints *hints) {
+       dns_hints_acquire(hints); /* acquire first in case same hints object */
+       dns_hints_close(res->hints);
+       res->hints = hints;
+} /* dns_res_sethints() */
+
+
+struct dns_trace *dns_res_trace(struct dns_resolver *res) {
+       return res->trace;
+} /* dns_res_trace() */
+
+
+void dns_res_settrace(struct dns_resolver *res, struct dns_trace *trace) {
+       struct dns_trace *otrace = res->trace;
+       res->trace = dns_trace_acquire_p(trace);
+       dns_trace_close(otrace);
+       dns_so_settrace(&res->so, trace);
+} /* dns_res_settrace() */
+
+
+/*
+ * A D D R I N F O  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_addrinfo {
+       struct addrinfo hints;
+       struct dns_resolver *res;
+       struct dns_trace *trace;
+
+       char qname[DNS_D_MAXNAME + 1];
+       enum dns_type qtype;
+       unsigned short qport, port;
+
+       struct {
+               unsigned long todo;
+               int state;
+               int atype;
+               enum dns_type qtype;
+       } af;
+
+       struct dns_packet *answer;
+       struct dns_packet *glue;
+
+       struct dns_rr_i i, g;
+       struct dns_rr rr;
+
+       char cname[DNS_D_MAXNAME + 1];
+       char i_cname[DNS_D_MAXNAME + 1], g_cname[DNS_D_MAXNAME + 1];
+
+       int g_depth;
+
+       int state;
+       int found;
+
+       struct dns_stat st;
+}; /* struct dns_addrinfo */
+
+
+#define DNS_AI_AFMAX 32
+#define DNS_AI_AF2INDEX(af) (1UL << ((af) - 1))
+
+static inline unsigned long dns_ai_af2index(int af) {
+       dns_static_assert(dns_same_type(unsigned long, DNS_AI_AF2INDEX(1), 1), "internal type mismatch");
+       dns_static_assert(dns_same_type(unsigned long, ((struct dns_addrinfo *)0)->af.todo, 1), "internal type mismatch");
+
+       return (af > 0 && af <= DNS_AI_AFMAX)? DNS_AI_AF2INDEX(af) : 0;
+}
+
+static int dns_ai_setaf(struct dns_addrinfo *ai, int af, int qtype) {
+       ai->af.atype = af;
+       ai->af.qtype = qtype;
+
+       ai->af.todo &= ~dns_ai_af2index(af);
+
+       return af;
+} /* dns_ai_setaf() */
+
+#define DNS_SM_RESTORE \
+       do { pc = 0xff & (ai->af.state >> 0); i = 0xff & (ai->af.state >> 8); } while (0)
+#define DNS_SM_SAVE \
+       do { ai->af.state = ((0xff & pc) << 0) | ((0xff & i) << 8); } while (0)
+
+static int dns_ai_nextaf(struct dns_addrinfo *ai) {
+       int i, pc;
+
+       dns_static_assert(AF_UNSPEC == 0, "AF_UNSPEC constant not 0");
+       dns_static_assert(AF_INET <= DNS_AI_AFMAX, "AF_INET constant too large");
+       dns_static_assert(AF_INET6 <= DNS_AI_AFMAX, "AF_INET6 constant too large");
+
+       DNS_SM_ENTER;
+
+       if (ai->res) {
+               /*
+                * NB: On OpenBSD, at least, the types of entries resolved
+                * is the intersection of the /etc/resolv.conf families and
+                * the families permitted by the .ai_type hint. So if
+                * /etc/resolv.conf has "family inet4" and .ai_type
+                * is AF_INET6, then the address ::1 will return 0 entries
+                * even if AI_NUMERICHOST is specified in .ai_flags.
+                */
+               while (i < (int)lengthof(ai->res->resconf->family)) {
+                       int af = ai->res->resconf->family[i++];
+
+                       if (af == AF_UNSPEC) {
+                               DNS_SM_EXIT;
+                       } else if (af < 0 || af > DNS_AI_AFMAX) {
+                               continue;
+                       } else if (!(DNS_AI_AF2INDEX(af) & ai->af.todo)) {
+                               continue;
+                       } else if (af == AF_INET) {
+                               DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A));
+                       } else if (af == AF_INET6) {
+                               DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA));
+                       }
+               }
+       } else {
+               /*
+                * NB: If we get here than AI_NUMERICFLAGS should be set and
+                * order shouldn't matter.
+                */
+               if (DNS_AI_AF2INDEX(AF_INET) & ai->af.todo)
+                       DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A));
+               if (DNS_AI_AF2INDEX(AF_INET6) & ai->af.todo)
+                       DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA));
+       }
+
+       DNS_SM_LEAVE;
+
+       return dns_ai_setaf(ai, AF_UNSPEC, 0);
+} /* dns_ai_nextaf() */
+
+#undef DNS_SM_RESTORE
+#undef DNS_SM_SAVE
+
+static enum dns_type dns_ai_qtype(struct dns_addrinfo *ai) {
+       return (ai->qtype)? ai->qtype : ai->af.qtype;
+} /* dns_ai_qtype() */
+
+/* JW: This is not defined on mingw.  */
+#ifndef AI_NUMERICSERV
+#define AI_NUMERICSERV 0
+#endif
+
+static dns_error_t dns_ai_parseport(unsigned short *port, const char *serv, const struct addrinfo *hints) {
+       const char *cp = serv;
+       unsigned long n = 0;
+
+       while (*cp >= '0' && *cp <= '9' && n < 65536) {
+               n *= 10;
+               n += *cp++ - '0';
+       }
+
+       if (*cp == '\0') {
+               if (cp == serv || n >= 65536)
+                       return DNS_ESERVICE;
+
+               *port = n;
+
+               return 0;
+       }
+
+       if (hints->ai_flags & AI_NUMERICSERV)
+               return DNS_ESERVICE;
+
+       /* TODO: try getaddrinfo(NULL, serv, { .ai_flags = AI_NUMERICSERV }) */
+
+       return DNS_ESERVICE;
+} /* dns_ai_parseport() */
+
+
+struct dns_addrinfo *dns_ai_open(const char *host, const char *serv, enum dns_type qtype, const struct addrinfo *hints, struct dns_resolver *res, int *_error) {
+       static const struct dns_addrinfo ai_initializer;
+       struct dns_addrinfo *ai;
+       int error;
+
+       if (res) {
+               dns_res_acquire(res);
+       } else if (!(hints->ai_flags & AI_NUMERICHOST)) {
+               /*
+                * NOTE: it's assumed that *_error is set from a previous
+                * API function call, such as dns_res_stub(). Should change
+                * this semantic, but it's applied elsewhere, too.
+                */
+               if (!*_error)
+                       *_error = EINVAL;
+               return NULL;
+       }
+
+       if (!(ai = malloc(sizeof *ai)))
+               goto syerr;
+
+       *ai = ai_initializer;
+       ai->hints = *hints;
+
+       ai->res = res;
+       res = NULL;
+
+       if (sizeof ai->qname <= dns_strlcpy(ai->qname, host, sizeof ai->qname))
+               { error = ENAMETOOLONG; goto error; }
+
+       ai->qtype = qtype;
+       ai->qport = 0;
+
+       if (serv && (error = dns_ai_parseport(&ai->qport, serv, hints)))
+               goto error;
+       ai->port = ai->qport;
+
+       /*
+        * FIXME: If an explicit A or AAAA record type conflicts with
+        * .ai_family or with resconf.family (i.e. AAAA specified but
+        * AF_INET6 not in interection of .ai_family and resconf.family),
+        * then what?
+        */
+       switch (ai->qtype) {
+       case DNS_T_A:
+               ai->af.todo = DNS_AI_AF2INDEX(AF_INET);
+               break;
+       case DNS_T_AAAA:
+               ai->af.todo = DNS_AI_AF2INDEX(AF_INET6);
+               break;
+       default: /* 0, MX, SRV, etc */
+               switch (ai->hints.ai_family) {
+               case AF_UNSPEC:
+                       ai->af.todo = DNS_AI_AF2INDEX(AF_INET) | DNS_AI_AF2INDEX(AF_INET6);
+                       break;
+               case AF_INET:
+                       ai->af.todo = DNS_AI_AF2INDEX(AF_INET);
+                       break;
+               case AF_INET6:
+                       ai->af.todo = DNS_AI_AF2INDEX(AF_INET6);
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       return ai;
+syerr:
+       error = dns_syerr();
+error:
+       *_error = error;
+
+       dns_ai_close(ai);
+       dns_res_close(res);
+
+       return NULL;
+} /* dns_ai_open() */
+
+
+void dns_ai_close(struct dns_addrinfo *ai) {
+       if (!ai)
+               return;
+
+       dns_res_close(ai->res);
+       dns_trace_close(ai->trace);
+
+       if (ai->answer != ai->glue)
+               dns_p_free(ai->glue);
+
+       dns_p_free(ai->answer);
+       free(ai);
+} /* dns_ai_close() */
+
+
+static int dns_ai_setent(struct addrinfo **ent, union dns_any *any, enum dns_type type, struct dns_addrinfo *ai) {
+       struct sockaddr *saddr;
+       struct sockaddr_in sin;
+       struct sockaddr_in6 sin6;
+       const char *cname;
+       size_t clen;
+
+       switch (type) {
+       case DNS_T_A:
+               saddr   = memset(&sin, '\0', sizeof sin);
+
+               sin.sin_family  = AF_INET;
+               sin.sin_port    = htons(ai->port);
+
+               memcpy(&sin.sin_addr, any, sizeof sin.sin_addr);
+
+               break;
+       case DNS_T_AAAA:
+               saddr   = memset(&sin6, '\0', sizeof sin6);
+
+               sin6.sin6_family        = AF_INET6;
+               sin6.sin6_port          = htons(ai->port);
+
+               memcpy(&sin6.sin6_addr, any, sizeof sin6.sin6_addr);
+
+               break;
+       default:
+               return EINVAL;
+       } /* switch() */
+
+       if (ai->hints.ai_flags & AI_CANONNAME) {
+               cname   = (*ai->cname)? ai->cname : ai->qname;
+               clen    = strlen(cname);
+       } else {
+               cname   = NULL;
+               clen    = 0;
+       }
+
+       if (!(*ent = malloc(sizeof **ent + dns_sa_len(saddr) + ((ai->hints.ai_flags & AI_CANONNAME)? clen + 1 : 0))))
+               return dns_syerr();
+
+       memset(*ent, '\0', sizeof **ent);
+
+       (*ent)->ai_family       = saddr->sa_family;
+       (*ent)->ai_socktype     = ai->hints.ai_socktype;
+       (*ent)->ai_protocol     = ai->hints.ai_protocol;
+
+       (*ent)->ai_addr         = memcpy((unsigned char *)*ent + sizeof **ent, saddr, dns_sa_len(saddr));
+       (*ent)->ai_addrlen      = dns_sa_len(saddr);
+
+       if (ai->hints.ai_flags & AI_CANONNAME)
+               (*ent)->ai_canonname    = memcpy((unsigned char *)*ent + sizeof **ent + dns_sa_len(saddr), cname, clen + 1);
+
+       ai->found++;
+
+       return 0;
+} /* dns_ai_setent() */
+
+
+enum dns_ai_state {
+       DNS_AI_S_INIT,
+       DNS_AI_S_NEXTAF,
+       DNS_AI_S_NUMERIC,
+       DNS_AI_S_SUBMIT,
+       DNS_AI_S_CHECK,
+       DNS_AI_S_FETCH,
+       DNS_AI_S_FOREACH_I,
+       DNS_AI_S_INIT_G,
+       DNS_AI_S_ITERATE_G,
+       DNS_AI_S_FOREACH_G,
+       DNS_AI_S_SUBMIT_G,
+       DNS_AI_S_CHECK_G,
+       DNS_AI_S_FETCH_G,
+       DNS_AI_S_DONE,
+}; /* enum dns_ai_state */
+
+#define dns_ai_goto(which)     do { ai->state = (which); goto exec; } while (0)
+
+int dns_ai_nextent(struct addrinfo **ent, struct dns_addrinfo *ai) {
+       struct dns_packet *ans, *glue;
+       struct dns_rr rr;
+       char qname[DNS_D_MAXNAME + 1];
+       union dns_any any;
+       size_t qlen, clen;
+       int error;
+
+       *ent = 0;
+
+exec:
+
+       switch (ai->state) {
+       case DNS_AI_S_INIT:
+               ai->state++;
+       case DNS_AI_S_NEXTAF:
+               if (!dns_ai_nextaf(ai))
+                       dns_ai_goto(DNS_AI_S_DONE);
+
+               ai->state++;
+       case DNS_AI_S_NUMERIC:
+               if (1 == dns_inet_pton(AF_INET, ai->qname, &any.a)) {
+                       if (ai->af.atype == AF_INET) {
+                               ai->state = DNS_AI_S_NEXTAF;
+                               return dns_ai_setent(ent, &any, DNS_T_A, ai);
+                       } else {
+                               dns_ai_goto(DNS_AI_S_NEXTAF);
+                       }
+               }
+
+               if (1 == dns_inet_pton(AF_INET6, ai->qname, &any.aaaa)) {
+                       if (ai->af.atype == AF_INET6) {
+                               ai->state = DNS_AI_S_NEXTAF;
+                               return dns_ai_setent(ent, &any, DNS_T_AAAA, ai);
+                       } else {
+                               dns_ai_goto(DNS_AI_S_NEXTAF);
+                       }
+               }
+
+               if (ai->hints.ai_flags & AI_NUMERICHOST)
+                       dns_ai_goto(DNS_AI_S_NEXTAF);
+
+               ai->state++;
+       case DNS_AI_S_SUBMIT:
+               assert(ai->res);
+
+               if ((error = dns_res_submit(ai->res, ai->qname, dns_ai_qtype(ai), DNS_C_IN)))
+                       return error;
+
+               ai->state++;
+       case DNS_AI_S_CHECK:
+               if ((error = dns_res_check(ai->res)))
+                       return error;
+
+               ai->state++;
+       case DNS_AI_S_FETCH:
+               if (!(ans = dns_res_fetch_and_study(ai->res, &error)))
+                       return error;
+               if (ai->glue != ai->answer)
+                       dns_p_free(ai->glue);
+               ai->glue = dns_p_movptr(&ai->answer, &ans);
+
+               /* Search generator may have changed the qname. */
+               if (!(qlen = dns_d_expand(qname, sizeof qname, 12, ai->answer, &error)))
+                       return error;
+               else if (qlen >= sizeof qname)
+                       return DNS_EILLEGAL;
+               if (!dns_d_cname(ai->cname, sizeof ai->cname, qname, qlen, ai->answer, &error))
+                       return error;
+
+               dns_strlcpy(ai->i_cname, ai->cname, sizeof ai->i_cname);
+               dns_rr_i_init(&ai->i, ai->answer);
+               ai->i.section = DNS_S_AN;
+               ai->i.name    = ai->i_cname;
+               ai->i.type    = dns_ai_qtype(ai);
+               ai->i.sort    = &dns_rr_i_order;
+
+               ai->state++;
+       case DNS_AI_S_FOREACH_I:
+               if (!dns_rr_grep(&rr, 1, &ai->i, ai->answer, &error))
+                       dns_ai_goto(DNS_AI_S_NEXTAF);
+
+               if ((error = dns_any_parse(&any, &rr, ai->answer)))
+                       return error;
+
+               ai->port = ai->qport;
+
+               switch (rr.type) {
+               case DNS_T_A:
+               case DNS_T_AAAA:
+                       return dns_ai_setent(ent, &any, rr.type, ai);
+               default:
+                       if (!(clen = dns_any_cname(ai->cname, sizeof ai->cname, &any, rr.type)))
+                               dns_ai_goto(DNS_AI_S_FOREACH_I);
+
+                       /*
+                        * Find the "real" canonical name. Some authorities
+                        * publish aliases where an RFC defines a canonical
+                        * name. We trust that the resolver followed any
+                        * CNAME chains on it's own, regardless of whether
+                        * the "smart" option is enabled.
+                        */
+                       if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->cname, clen, ai->answer, &error))
+                               return error;
+
+                       if (rr.type == DNS_T_SRV)
+                               ai->port = any.srv.port;
+
+                       break;
+               } /* switch() */
+
+               ai->state++;
+       case DNS_AI_S_INIT_G:
+               ai->g_depth = 0;
+
+               ai->state++;
+       case DNS_AI_S_ITERATE_G:
+               dns_strlcpy(ai->g_cname, ai->cname, sizeof ai->g_cname);
+               dns_rr_i_init(&ai->g, ai->glue);
+               ai->g.section = DNS_S_ALL & ~DNS_S_QD;
+               ai->g.name    = ai->g_cname;
+               ai->g.type    = ai->af.qtype;
+
+               ai->state++;
+       case DNS_AI_S_FOREACH_G:
+               if (!dns_rr_grep(&rr, 1, &ai->g, ai->glue, &error)) {
+                       if (dns_rr_i_count(&ai->g) > 0)
+                               dns_ai_goto(DNS_AI_S_FOREACH_I);
+                       else
+                               dns_ai_goto(DNS_AI_S_SUBMIT_G);
+               }
+
+               if ((error = dns_any_parse(&any, &rr, ai->glue)))
+                       return error;
+
+               return dns_ai_setent(ent, &any, rr.type, ai);
+       case DNS_AI_S_SUBMIT_G:
+               /* skip if already queried */
+               if (dns_rr_grep(&rr, 1, dns_rr_i_new(ai->glue, .section = DNS_S_QD, .name = ai->g.name, .type = ai->g.type), ai->glue, &error))
+                       dns_ai_goto(DNS_AI_S_FOREACH_I);
+               /* skip if we recursed (CNAME chains should have been handled in the resolver) */
+               if (++ai->g_depth > 1)
+                       dns_ai_goto(DNS_AI_S_FOREACH_I);
+
+               if ((error = dns_res_submit(ai->res, ai->g.name, ai->g.type, DNS_C_IN)))
+                       return error;
+
+               ai->state++;
+       case DNS_AI_S_CHECK_G:
+               if ((error = dns_res_check(ai->res)))
+                       return error;
+
+               ai->state++;
+       case DNS_AI_S_FETCH_G:
+               if (!(ans = dns_res_fetch_and_study(ai->res, &error)))
+                       return error;
+
+               glue = dns_p_merge(ai->glue, DNS_S_ALL, ans, DNS_S_ALL, &error);
+               dns_p_setptr(&ans, NULL);
+               if (!glue)
+                       return error;
+
+               if (ai->glue != ai->answer)
+                       dns_p_free(ai->glue);
+               ai->glue = glue;
+
+               if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->g.name, strlen(ai->g.name), ai->glue, &error))
+                       dns_ai_goto(DNS_AI_S_FOREACH_I);
+
+               dns_ai_goto(DNS_AI_S_ITERATE_G);
+       case DNS_AI_S_DONE:
+               if (ai->found) {
+                       return ENOENT; /* TODO: Just return 0 */
+               } else if (ai->answer) {
+                       switch (dns_p_rcode(ai->answer)) {
+                       case DNS_RC_NOERROR:
+                               /* FALL THROUGH */
+                       case DNS_RC_NXDOMAIN:
+                               return DNS_ENONAME;
+                       default:
+                               return DNS_EFAIL;
+                       }
+               } else {
+                       return DNS_EFAIL;
+               }
+       default:
+               return EINVAL;
+       } /* switch() */
+} /* dns_ai_nextent() */
+
+
+time_t dns_ai_elapsed(struct dns_addrinfo *ai) {
+       return (ai->res)? dns_res_elapsed(ai->res) : 0;
+} /* dns_ai_elapsed() */
+
+
+void dns_ai_clear(struct dns_addrinfo *ai) {
+       if (ai->res)
+               dns_res_clear(ai->res);
+} /* dns_ai_clear() */
+
+
+int dns_ai_events(struct dns_addrinfo *ai) {
+       return (ai->res)? dns_res_events(ai->res) : 0;
+} /* dns_ai_events() */
+
+
+int dns_ai_pollfd(struct dns_addrinfo *ai) {
+       return (ai->res)? dns_res_pollfd(ai->res) : -1;
+} /* dns_ai_pollfd() */
+
+
+time_t dns_ai_timeout(struct dns_addrinfo *ai) {
+       return (ai->res)? dns_res_timeout(ai->res) : 0;
+} /* dns_ai_timeout() */
+
+
+int dns_ai_poll(struct dns_addrinfo *ai, int timeout) {
+       return (ai->res)? dns_res_poll(ai->res, timeout) : 0;
+} /* dns_ai_poll() */
+
+
+size_t dns_ai_print(void *_dst, size_t lim, struct addrinfo *ent, struct dns_addrinfo *ai) {
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+       char addr[DNS_PP_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1];
+
+       dns_b_puts(&dst, "[ ");
+       dns_b_puts(&dst, ai->qname);
+       dns_b_puts(&dst, " IN ");
+       if (ai->qtype) {
+               dns_b_puts(&dst, dns_strtype(ai->qtype));
+       } else if (ent->ai_family == AF_INET) {
+               dns_b_puts(&dst, dns_strtype(DNS_T_A));
+       } else if (ent->ai_family == AF_INET6) {
+               dns_b_puts(&dst, dns_strtype(DNS_T_AAAA));
+       } else {
+               dns_b_puts(&dst, "0");
+       }
+       dns_b_puts(&dst, " ]\n");
+
+       dns_b_puts(&dst, ".ai_family    = ");
+       switch (ent->ai_family) {
+       case AF_INET:
+               dns_b_puts(&dst, "AF_INET");
+               break;
+       case AF_INET6:
+               dns_b_puts(&dst, "AF_INET6");
+               break;
+       default:
+               dns_b_fmtju(&dst, ent->ai_family, 0);
+               break;
+       }
+       dns_b_putc(&dst, '\n');
+
+       dns_b_puts(&dst, ".ai_socktype  = ");
+       switch (ent->ai_socktype) {
+       case SOCK_STREAM:
+               dns_b_puts(&dst, "SOCK_STREAM");
+               break;
+       case SOCK_DGRAM:
+               dns_b_puts(&dst, "SOCK_DGRAM");
+               break;
+       default:
+               dns_b_fmtju(&dst, ent->ai_socktype, 0);
+               break;
+       }
+       dns_b_putc(&dst, '\n');
+
+       dns_inet_ntop(dns_sa_family(ent->ai_addr), dns_sa_addr(dns_sa_family(ent->ai_addr), ent->ai_addr, NULL), addr, sizeof addr);
+       dns_b_puts(&dst, ".ai_addr      = [");
+       dns_b_puts(&dst, addr);
+       dns_b_puts(&dst, "]:");
+       dns_b_fmtju(&dst, ntohs(*dns_sa_port(dns_sa_family(ent->ai_addr), ent->ai_addr)), 0);
+       dns_b_putc(&dst, '\n');
+
+       dns_b_puts(&dst, ".ai_canonname = ");
+       dns_b_puts(&dst, (ent->ai_canonname)? ent->ai_canonname : "[NULL]");
+       dns_b_putc(&dst, '\n');
+
+       return dns_b_strllen(&dst);
+} /* dns_ai_print() */
+
+
+const struct dns_stat *dns_ai_stat(struct dns_addrinfo *ai) {
+       return (ai->res)? dns_res_stat(ai->res) : &ai->st;
+} /* dns_ai_stat() */
+
+
+struct dns_trace *dns_ai_trace(struct dns_addrinfo *ai) {
+       return ai->trace;
+} /* dns_ai_trace() */
+
+
+void dns_ai_settrace(struct dns_addrinfo *ai, struct dns_trace *trace) {
+       struct dns_trace *otrace = ai->trace;
+       ai->trace = dns_trace_acquire_p(trace);
+       dns_trace_close(otrace);
+       if (ai->res)
+               dns_res_settrace(ai->res, trace);
+} /* dns_ai_settrace() */
+
+
+/*
+ * M I S C E L L A N E O U S  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+static const struct {
+       char name[16];
+       enum dns_section type;
+} dns_sections[] = {
+       { "QUESTION",   DNS_S_QUESTION },
+       { "QD",         DNS_S_QUESTION },
+       { "ANSWER",     DNS_S_ANSWER },
+       { "AN",         DNS_S_ANSWER },
+       { "AUTHORITY",  DNS_S_AUTHORITY },
+       { "NS",         DNS_S_AUTHORITY },
+       { "ADDITIONAL", DNS_S_ADDITIONAL },
+       { "AR",         DNS_S_ADDITIONAL },
+};
+
+const char *(dns_strsection)(enum dns_section section, void *_dst, size_t lim) {
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+       unsigned i;
+
+       for (i = 0; i < lengthof(dns_sections); i++) {
+               if (dns_sections[i].type & section) {
+                       dns_b_puts(&dst, dns_sections[i].name);
+                       section &= ~dns_sections[i].type;
+                       if (section)
+                               dns_b_putc(&dst, '|');
+               }
+       }
+
+       if (section || dst.p == dst.base)
+               dns_b_fmtju(&dst, (0xffff & section), 0);
+
+       return dns_b_tostring(&dst);
+} /* dns_strsection() */
+
+
+enum dns_section dns_isection(const char *src) {
+       enum dns_section section = 0;
+       char sbuf[128];
+       char *name, *next;
+       unsigned i;
+
+       dns_strlcpy(sbuf, src, sizeof sbuf);
+       next = sbuf;
+
+       while ((name = dns_strsep(&next, "|+, \t"))) {
+               for (i = 0; i < lengthof(dns_sections); i++) {
+                       if (!strcasecmp(dns_sections[i].name, name)) {
+                               section |= dns_sections[i].type;
+                               break;
+                       }
+               }
+       }
+
+       return section;
+} /* dns_isection() */
+
+
+static const struct {
+       char name[8];
+       enum dns_class type;
+} dns_classes[] = {
+       { "IN", DNS_C_IN },
+};
+
+const char *(dns_strclass)(enum dns_class type, void *_dst, size_t lim) {
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+       unsigned i;
+
+       for (i = 0; i < lengthof(dns_classes); i++) {
+               if (dns_classes[i].type == type) {
+                       dns_b_puts(&dst, dns_classes[i].name);
+                       break;
+               }
+       }
+
+       if (dst.p == dst.base)
+               dns_b_fmtju(&dst, (0xffff & type), 0);
+
+       return dns_b_tostring(&dst);
+} /* dns_strclass() */
+
+
+enum dns_class dns_iclass(const char *name) {
+       unsigned i, class;
+
+       for (i = 0; i < lengthof(dns_classes); i++) {
+               if (!strcasecmp(dns_classes[i].name, name))
+                       return dns_classes[i].type;
+       }
+
+       class = 0;
+       while (dns_isdigit(*name)) {
+               class *= 10;
+               class += *name++ - '0';
+       }
+
+       return DNS_PP_MIN(class, 0xffff);
+} /* dns_iclass() */
+
+
+const char *(dns_strtype)(enum dns_type type, void *_dst, size_t lim) {
+       struct dns_buf dst = DNS_B_INTO(_dst, lim);
+       unsigned i;
+
+       for (i = 0; i < lengthof(dns_rrtypes); i++) {
+               if (dns_rrtypes[i].type == type) {
+                       dns_b_puts(&dst, dns_rrtypes[i].name);
+                       break;
+               }
+       }
+
+       if (dst.p == dst.base)
+               dns_b_fmtju(&dst, (0xffff & type), 0);
+
+       return dns_b_tostring(&dst);
+} /* dns_strtype() */
+
+
+enum dns_type dns_itype(const char *name) {
+       unsigned i, type;
+
+       for (i = 0; i < lengthof(dns_rrtypes); i++) {
+               if (!strcasecmp(dns_rrtypes[i].name, name))
+                       return dns_rrtypes[i].type;
+       }
+
+       type = 0;
+       while (dns_isdigit(*name)) {
+               type *= 10;
+               type += *name++ - '0';
+       }
+
+       return DNS_PP_MIN(type, 0xffff);
+} /* dns_itype() */
+
+
+static char dns_opcodes[16][16] = {
+       [DNS_OP_QUERY]  = "QUERY",
+       [DNS_OP_IQUERY] = "IQUERY",
+       [DNS_OP_STATUS] = "STATUS",
+       [DNS_OP_NOTIFY] = "NOTIFY",
+       [DNS_OP_UPDATE] = "UPDATE",
+};
+
+static const char *dns__strcode(int code, volatile char *dst, size_t lim) {
+       char _tmp[48] = "";
+       struct dns_buf tmp;
+       size_t p;
+
+       assert(lim > 0);
+       dns_b_fmtju(dns_b_into(&tmp, _tmp, DNS_PP_MIN(sizeof _tmp, lim - 1)), code, 0);
+
+       /* copy downwards so first byte is copied last (see below) */
+       p = (size_t)(tmp.p - tmp.base);
+       dst[p] = '\0';
+       while (p--)
+               dst[p] = _tmp[p];
+
+       return (const char *)dst;
+}
+
+const char *dns_stropcode(enum dns_opcode opcode) {
+       opcode = (unsigned)opcode % lengthof(dns_opcodes);
+
+       if ('\0' == dns_opcodes[opcode][0])
+               return dns__strcode(opcode, dns_opcodes[opcode], sizeof dns_opcodes[opcode]);
+
+       return dns_opcodes[opcode];
+} /* dns_stropcode() */
+
+
+enum dns_opcode dns_iopcode(const char *name) {
+       unsigned opcode;
+
+       for (opcode = 0; opcode < lengthof(dns_opcodes); opcode++) {
+               if (!strcasecmp(name, dns_opcodes[opcode]))
+                       return opcode;
+       }
+
+       opcode = 0;
+       while (dns_isdigit(*name)) {
+               opcode *= 10;
+               opcode += *name++ - '0';
+       }
+
+       return DNS_PP_MIN(opcode, 0x0f);
+} /* dns_iopcode() */
+
+
+static char dns_rcodes[32][16] = {
+       [DNS_RC_NOERROR]  = "NOERROR",
+       [DNS_RC_FORMERR]  = "FORMERR",
+       [DNS_RC_SERVFAIL] = "SERVFAIL",
+       [DNS_RC_NXDOMAIN] = "NXDOMAIN",
+       [DNS_RC_NOTIMP]   = "NOTIMP",
+       [DNS_RC_REFUSED]  = "REFUSED",
+       [DNS_RC_YXDOMAIN] = "YXDOMAIN",
+       [DNS_RC_YXRRSET]  = "YXRRSET",
+       [DNS_RC_NXRRSET]  = "NXRRSET",
+       [DNS_RC_NOTAUTH]  = "NOTAUTH",
+       [DNS_RC_NOTZONE]  = "NOTZONE",
+       /* EDNS(0) extended RCODEs ... */
+       [DNS_RC_BADVERS]  = "BADVERS",
+};
+
+const char *dns_strrcode(enum dns_rcode rcode) {
+       rcode = (unsigned)rcode % lengthof(dns_rcodes);
+
+       if ('\0' == dns_rcodes[rcode][0])
+               return dns__strcode(rcode, dns_rcodes[rcode], sizeof dns_rcodes[rcode]);
+
+       return dns_rcodes[rcode];
+} /* dns_strrcode() */
+
+
+enum dns_rcode dns_ircode(const char *name) {
+       unsigned rcode;
+
+       for (rcode = 0; rcode < lengthof(dns_rcodes); rcode++) {
+               if (!strcasecmp(name, dns_rcodes[rcode]))
+                       return rcode;
+       }
+
+       rcode = 0;
+       while (dns_isdigit(*name)) {
+               rcode *= 10;
+               rcode += *name++ - '0';
+       }
+
+       return DNS_PP_MIN(rcode, 0xfff);
+} /* dns_ircode() */
+
+
+\f
+/*
+ * C O M M A N D - L I N E / R E G R E S S I O N  R O U T I N E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+#if DNS_MAIN
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <ctype.h>
+
+#if _WIN32
+#include <getopt.h>
+#endif
+
+#if !_WIN32
+#include <err.h>
+#endif
+
+
+struct {
+       struct {
+               const char *path[8];
+               unsigned count;
+       } resconf, nssconf, hosts, cache;
+
+       const char *qname;
+       enum dns_type qtype;
+
+       int (*sort)();
+
+       const char *trace;
+
+       int verbose;
+
+       struct {
+               struct dns_resolv_conf *resconf;
+               struct dns_hosts *hosts;
+               struct dns_trace *trace;
+       } memo;
+
+       struct sockaddr_storage socks_host;
+       const char *socks_user;
+       const char *socks_password;
+} MAIN = {
+       .sort   = &dns_rr_i_packet,
+};
+
+
+static void hexdump(const unsigned char *src, size_t len, FILE *fp) {
+       struct dns_hxd_lines_i lines = { 0 };
+       char line[128];
+
+       while (dns_hxd_lines(line, sizeof line, src, len, &lines)) {
+               fputs(line, fp);
+       }
+} /* hexdump() */
+
+
+DNS_NORETURN static void panic(const char *fmt, ...) {
+       va_list ap;
+
+       va_start(ap, fmt);
+
+#if _WIN32
+       vfprintf(stderr, fmt, ap);
+
+       exit(EXIT_FAILURE);
+#else
+       verrx(EXIT_FAILURE, fmt, ap);
+#endif
+} /* panic() */
+
+#define panic_(fn, ln, fmt, ...)       \
+       panic(fmt "%0s", (fn), (ln), __VA_ARGS__)
+#define panic(...)                     \
+       panic_(__func__, __LINE__, "(%s:%d) " __VA_ARGS__, "")
+
+
+static void *grow(unsigned char *p, size_t size) {
+       void *tmp;
+
+       if (!(tmp = realloc(p, size)))
+               panic("realloc(%"PRIuZ"): %s", size, dns_strerror(errno));
+
+       return tmp;
+} /* grow() */
+
+
+static size_t add(size_t a, size_t b) {
+       if (~a < b)
+               panic("%"PRIuZ" + %"PRIuZ": integer overflow", a, b);
+
+       return a + b;
+} /* add() */
+
+
+static size_t append(unsigned char **dst, size_t osize, const void *src, size_t len) {
+       size_t size = add(osize, len);
+
+       *dst = grow(*dst, size);
+       memcpy(*dst + osize, src, len);
+
+       return size;
+} /* append() */
+
+
+static size_t slurp(unsigned char **dst, size_t osize, FILE *fp, const char *path) {
+       size_t size = osize;
+       unsigned char buf[1024];
+       size_t count;
+
+       while ((count = fread(buf, 1, sizeof buf, fp)))
+               size = append(dst, size, buf, count);
+
+       if (ferror(fp))
+               panic("%s: %s", path, dns_strerror(errno));
+
+       return size;
+} /* slurp() */
+
+
+static struct dns_resolv_conf *resconf(void) {
+       struct dns_resolv_conf **resconf = &MAIN.memo.resconf;
+       const char *path;
+       unsigned i;
+       int error;
+
+       if (*resconf)
+               return *resconf;
+
+       if (!(*resconf = dns_resconf_open(&error)))
+               panic("dns_resconf_open: %s", dns_strerror(error));
+
+       if (!MAIN.resconf.count)
+               MAIN.resconf.path[MAIN.resconf.count++] = "/etc/resolv.conf";
+
+       for (i = 0; i < MAIN.resconf.count; i++) {
+               path    = MAIN.resconf.path[i];
+
+               if (0 == strcmp(path, "-"))
+                       error   = dns_resconf_loadfile(*resconf, stdin);
+               else
+                       error   = dns_resconf_loadpath(*resconf, path);
+
+               if (error)
+                       panic("%s: %s", path, dns_strerror(error));
+       }
+
+       for (i = 0; i < MAIN.nssconf.count; i++) {
+               path    = MAIN.nssconf.path[i];
+
+               if (0 == strcmp(path, "-"))
+                       error   = dns_nssconf_loadfile(*resconf, stdin);
+               else
+                       error   = dns_nssconf_loadpath(*resconf, path);
+
+               if (error)
+                       panic("%s: %s", path, dns_strerror(error));
+       }
+
+       if (!MAIN.nssconf.count) {
+               path = "/etc/nsswitch.conf";
+
+               if (!(error = dns_nssconf_loadpath(*resconf, path)))
+                       MAIN.nssconf.path[MAIN.nssconf.count++] = path;
+               else if (error != ENOENT)
+                       panic("%s: %s", path, dns_strerror(error));
+       }
+
+       return *resconf;
+} /* resconf() */
+
+
+static struct dns_hosts *hosts(void) {
+       struct dns_hosts **hosts = &MAIN.memo.hosts;
+       const char *path;
+       unsigned i;
+       int error;
+
+       if (*hosts)
+               return *hosts;
+
+       if (!MAIN.hosts.count) {
+               MAIN.hosts.path[MAIN.hosts.count++]     = "/etc/hosts";
+
+               /* Explicitly test dns_hosts_local() */
+               if (!(*hosts = dns_hosts_local(&error)))
+                       panic("%s: %s", "/etc/hosts", dns_strerror(error));
+
+               return *hosts;
+       }
+
+       if (!(*hosts = dns_hosts_open(&error)))
+               panic("dns_hosts_open: %s", dns_strerror(error));
+
+       for (i = 0; i < MAIN.hosts.count; i++) {
+               path    = MAIN.hosts.path[i];
+
+               if (0 == strcmp(path, "-"))
+                       error   = dns_hosts_loadfile(*hosts, stdin);
+               else
+                       error   = dns_hosts_loadpath(*hosts, path);
+
+               if (error)
+                       panic("%s: %s", path, dns_strerror(error));
+       }
+
+       return *hosts;
+} /* hosts() */
+
+
+#if DNS_CACHE
+#include "cache.h"
+
+static struct dns_cache *cache(void) {
+       static struct cache *cache;
+       const char *path;
+       unsigned i;
+       int error;
+
+       if (cache)
+               return cache_resi(cache);
+       if (!MAIN.cache.count)
+               return NULL;
+
+       if (!(cache = cache_open(&error)))
+               panic("%s: %s", MAIN.cache.path[0], dns_strerror(error));
+
+       for (i = 0; i < MAIN.cache.count; i++) {
+               path = MAIN.cache.path[i];
+
+               if (!strcmp(path, "-")) {
+                       if ((error = cache_loadfile(cache, stdin, NULL, 0)))
+                               panic("%s: %s", path, dns_strerror(error));
+               } else if ((error = cache_loadpath(cache, path, NULL, 0)))
+                       panic("%s: %s", path, dns_strerror(error));
+       }
+
+       return cache_resi(cache);
+} /* cache() */
+#else
+static struct dns_cache *cache(void) { return NULL; }
+#endif
+
+
+static struct dns_trace *trace(const char *mode) {
+       static char omode[64] = "";
+       struct dns_trace **trace = &MAIN.memo.trace;
+       FILE *fp;
+       int error;
+
+       if (*trace && 0 == strcmp(omode, mode))
+               return *trace;
+       if (!MAIN.trace)
+               return NULL;
+
+       if (!(fp = fopen(MAIN.trace, mode)))
+               panic("%s: %s", MAIN.trace, strerror(errno));
+       dns_trace_close(*trace);
+       if (!(*trace = dns_trace_open(fp, &error)))
+               panic("%s: %s", MAIN.trace, dns_strerror(error));
+       dns_strlcpy(omode, mode, sizeof omode);
+
+       return *trace;
+}
+
+
+static void print_packet(struct dns_packet *P, FILE *fp) {
+       dns_p_dump3(P, dns_rr_i_new(P, .sort = MAIN.sort), fp);
+
+       if (MAIN.verbose > 2)
+               hexdump(P->data, P->end, fp);
+} /* print_packet() */
+
+
+static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
+       struct dns_packet *P    = dns_p_new(512);
+       struct dns_packet *Q    = dns_p_new(512);
+       enum dns_section section;
+       struct dns_rr rr;
+       int error;
+       union dns_any any;
+       char pretty[sizeof any * 2];
+       size_t len;
+
+       P->end  = fread(P->data, 1, P->size, stdin);
+
+       fputs(";; [HEADER]\n", stdout);
+       fprintf(stdout, ";;     qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr);
+       fprintf(stdout, ";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode);
+       fprintf(stdout, ";;     aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa);
+       fprintf(stdout, ";;     tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc);
+       fprintf(stdout, ";;     rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd);
+       fprintf(stdout, ";;     ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra);
+       fprintf(stdout, ";;  rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P));
+
+       section = 0;
+
+       dns_rr_foreach(&rr, P, .sort = MAIN.sort) {
+               if (section != rr.section)
+                       fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(P, rr.section));
+
+               if ((len = dns_rr_print(pretty, sizeof pretty, &rr, P, &error)))
+                       fprintf(stdout, "%s\n", pretty);
+
+               dns_rr_copy(Q, &rr, P);
+
+               section = rr.section;
+       }
+
+       fputs("; ; ; ; ; ; ; ;\n\n", stdout);
+
+       section = 0;
+
+#if 0
+       dns_rr_foreach(&rr, Q, .name = "ns8.yahoo.com.") {
+#else
+       struct dns_rr rrset[32];
+       struct dns_rr_i *rri    = dns_rr_i_new(Q, .name = dns_d_new("ns8.yahoo.com", DNS_D_ANCHOR), .sort = MAIN.sort);
+       unsigned rrcount        = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error);
+
+       for (unsigned i = 0; i < rrcount; i++) {
+               rr      = rrset[i];
+#endif
+               if (section != rr.section)
+                       fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(Q, rr.section));
+
+               if ((len = dns_rr_print(pretty, sizeof pretty, &rr, Q, &error)))
+                       fprintf(stdout, "%s\n", pretty);
+
+               section = rr.section;
+       }
+
+       if (MAIN.verbose > 1) {
+               fprintf(stderr, "orig:%"PRIuZ"\n", P->end);
+               hexdump(P->data, P->end, stdout);
+
+               fprintf(stderr, "copy:%"PRIuZ"\n", Q->end);
+               hexdump(Q->data, Q->end, stdout);
+       }
+
+       return 0;
+} /* parse_packet() */
+
+
+static int parse_domain(int argc, char *argv[]) {
+       char *dn;
+
+       dn      = (argc > 1)? argv[1] : "f.l.google.com";
+
+       printf("[%s]\n", dn);
+
+       dn      = dns_d_new(dn);
+
+       do {
+               puts(dn);
+       } while (dns_d_cleave(dn, strlen(dn) + 1, dn, strlen(dn)));
+
+       return 0;
+} /* parse_domain() */
+
+
+static int trim_domain(int argc, char **argv) {
+       for (argc--, argv++; argc > 0; argc--, argv++) {
+               char name[DNS_D_MAXNAME + 1];
+
+               dns_d_trim(name, sizeof name, *argv, strlen(*argv), DNS_D_ANCHOR);
+
+               puts(name);
+       }
+
+       return 0;
+} /* trim_domain() */
+
+
+static int expand_domain(int argc, char *argv[]) {
+       unsigned short rp = 0;
+       unsigned char *src = NULL;
+       unsigned char *dst;
+       struct dns_packet *pkt;
+       size_t lim = 0, len;
+       int error;
+
+       if (argc > 1)
+               rp = atoi(argv[1]);
+
+       len = slurp(&src, 0, stdin, "-");
+
+       if (!(pkt = dns_p_make(len, &error)))
+               panic("malloc(%"PRIuZ"): %s", len, dns_strerror(error));
+
+       memcpy(pkt->data, src, len);
+       pkt->end = len;
+
+       lim = 1;
+       dst = grow(NULL, lim);
+
+       while (lim <= (len = dns_d_expand(dst, lim, rp, pkt, &error))) {
+               lim = add(len, 1);
+               dst = grow(dst, lim);
+       }
+
+       if (!len)
+               panic("expand: %s", dns_strerror(error));
+
+       fwrite(dst, 1, len, stdout);
+       fflush(stdout);
+
+       free(src);
+       free(dst);
+       free(pkt);
+
+       return 0;
+} /* expand_domain() */
+
+
+static int show_resconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
+       unsigned i;
+
+       resconf(); /* load it */
+
+       fputs("; SOURCES\n", stdout);
+
+       for (i = 0; i < MAIN.resconf.count; i++)
+               fprintf(stdout, ";   %s\n", MAIN.resconf.path[i]);
+
+       for (i = 0; i < MAIN.nssconf.count; i++)
+               fprintf(stdout, ";   %s\n", MAIN.nssconf.path[i]);
+
+       fputs(";\n", stdout);
+
+       dns_resconf_dump(resconf(), stdout);
+
+       return 0;
+} /* show_resconf() */
+
+
+static int show_nssconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
+       unsigned i;
+
+       resconf();
+
+       fputs("# SOURCES\n", stdout);
+
+       for (i = 0; i < MAIN.resconf.count; i++)
+               fprintf(stdout, "#   %s\n", MAIN.resconf.path[i]);
+
+       for (i = 0; i < MAIN.nssconf.count; i++)
+               fprintf(stdout, "#   %s\n", MAIN.nssconf.path[i]);
+
+       fputs("#\n", stdout);
+
+       dns_nssconf_dump(resconf(), stdout);
+
+       return 0;
+} /* show_nssconf() */
+
+
+static int show_hosts(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
+       unsigned i;
+
+       hosts();
+
+       fputs("# SOURCES\n", stdout);
+
+       for (i = 0; i < MAIN.hosts.count; i++)
+               fprintf(stdout, "#   %s\n", MAIN.hosts.path[i]);
+
+       fputs("#\n", stdout);
+
+       dns_hosts_dump(hosts(), stdout);
+
+       return 0;
+} /* show_hosts() */
+
+
+static int query_hosts(int argc, char *argv[]) {
+       struct dns_packet *Q    = dns_p_new(512);
+       struct dns_packet *A;
+       char qname[DNS_D_MAXNAME + 1];
+       size_t qlen;
+       int error;
+
+       if (!MAIN.qname)
+               MAIN.qname      = (argc > 1)? argv[1] : "localhost";
+       if (!MAIN.qtype)
+               MAIN.qtype      = DNS_T_A;
+
+       hosts();
+
+       if (MAIN.qtype == DNS_T_PTR && !strstr(MAIN.qname, "arpa")) {
+               union { struct in_addr a; struct in6_addr a6; } addr;
+               int af  = (strchr(MAIN.qname, ':'))? AF_INET6 : AF_INET;
+
+               if ((error = dns_pton(af, MAIN.qname, &addr)))
+                       panic("%s: %s", MAIN.qname, dns_strerror(error));
+
+               qlen    = dns_ptr_qname(qname, sizeof qname, af, &addr);
+       } else
+               qlen    = dns_strlcpy(qname, MAIN.qname, sizeof qname);
+
+       if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, MAIN.qtype, DNS_C_IN, 0, 0)))
+               panic("%s: %s", qname, dns_strerror(error));
+
+       if (!(A = dns_hosts_query(hosts(), Q, &error)))
+               panic("%s: %s", qname, dns_strerror(error));
+
+       print_packet(A, stdout);
+
+       free(A);
+
+       return 0;
+} /* query_hosts() */
+
+
+static int search_list(int argc, char *argv[]) {
+       const char *qname       = (argc > 1)? argv[1] : "f.l.google.com";
+       unsigned long i         = 0;
+       char name[DNS_D_MAXNAME + 1];
+
+       printf("[%s]\n", qname);
+
+       while (dns_resconf_search(name, sizeof name, qname, strlen(qname), resconf(), &i))
+               puts(name);
+
+       return 0;
+} /* search_list() */
+
+
+static int permute_set(int argc, char *argv[]) {
+       unsigned lo, hi, i;
+       struct dns_k_permutor p;
+
+       hi      = (--argc > 0)? atoi(argv[argc]) : 8;
+       lo      = (--argc > 0)? atoi(argv[argc]) : 0;
+
+       fprintf(stderr, "[%u .. %u]\n", lo, hi);
+
+       dns_k_permutor_init(&p, lo, hi);
+
+       for (i = lo; i <= hi; i++)
+               fprintf(stdout, "%u\n", dns_k_permutor_step(&p));
+//             printf("%u -> %u -> %u\n", i, dns_k_permutor_E(&p, i), dns_k_permutor_D(&p, dns_k_permutor_E(&p, i)));
+
+       return 0;
+} /* permute_set() */
+
+
+static int shuffle_16(int argc, char *argv[]) {
+       unsigned n, r;
+
+       if (--argc > 0) {
+               n = 0xffff & atoi(argv[argc]);
+               r = (--argc > 0)? (unsigned)atoi(argv[argc]) : dns_random();
+
+               fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r));
+       } else {
+               r = dns_random();
+
+               for (n = 0; n < 65536; n++)
+                       fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r));
+       }
+
+       return 0;
+} /* shuffle_16() */
+
+
+static int dump_random(int argc, char *argv[]) {
+       unsigned char b[32];
+       unsigned i, j, n, r;
+
+       n       = (argc > 1)? atoi(argv[1]) : 32;
+
+       while (n) {
+               i       = 0;
+
+               do {
+                       r       = dns_random();
+
+                       for (j = 0; j < sizeof r && i < n && i < sizeof b; i++, j++) {
+                               b[i]    = 0xff & r;
+                               r       >>= 8;
+                       }
+               } while (i < n && i < sizeof b);
+
+               hexdump(b, i, stdout);
+
+               n       -= i;
+       }
+
+       return 0;
+} /* dump_random() */
+
+
+static int send_query(int argc, char *argv[]) {
+       struct dns_packet *A, *Q        = dns_p_new(512);
+       char host[INET6_ADDRSTRLEN + 1];
+       struct sockaddr_storage ss;
+       struct dns_socket *so;
+       int error, type;
+
+       if (argc > 1) {
+               ss.ss_family    = (strchr(argv[1], ':'))? AF_INET6 : AF_INET;
+
+               if ((error = dns_pton(ss.ss_family, argv[1], dns_sa_addr(ss.ss_family, &ss, NULL))))
+                       panic("%s: %s", argv[1], dns_strerror(error));
+
+               *dns_sa_port(ss.ss_family, &ss) = htons(53);
+       } else
+               memcpy(&ss, &resconf()->nameserver[0], dns_sa_len(&resconf()->nameserver[0]));
+
+       if (!dns_inet_ntop(ss.ss_family, dns_sa_addr(ss.ss_family, &ss, NULL), host, sizeof host))
+               panic("bad host address, or none provided");
+
+       if (!MAIN.qname)
+               MAIN.qname      = "ipv6.google.com";
+       if (!MAIN.qtype)
+               MAIN.qtype      = DNS_T_AAAA;
+
+       if ((error = dns_p_push(Q, DNS_S_QD, MAIN.qname, strlen(MAIN.qname), MAIN.qtype, DNS_C_IN, 0, 0)))
+               panic("dns_p_push: %s", dns_strerror(error));
+
+       dns_header(Q)->rd       = 1;
+
+       if (strstr(argv[0], "udp"))
+               type    = SOCK_DGRAM;
+       else if (strstr(argv[0], "tcp"))
+               type    = SOCK_STREAM;
+       else
+               type    = dns_res_tcp2type(resconf()->options.tcp);
+
+       fprintf(stderr, "querying %s for %s IN %s\n", host, MAIN.qname, dns_strtype(MAIN.qtype));
+
+       if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, dns_opts(), &error)))
+               panic("dns_so_open: %s", dns_strerror(error));
+
+       while (!(A = dns_so_query(so, Q, (struct sockaddr *)&ss, &error))) {
+               if (error != DNS_EAGAIN)
+                       panic("dns_so_query: %s (%d)", dns_strerror(error), error);
+               if (dns_so_elapsed(so) > 10)
+                       panic("query timed-out");
+
+               dns_so_poll(so, 1);
+       }
+
+       print_packet(A, stdout);
+
+       dns_so_close(so);
+
+       return 0;
+} /* send_query() */
+
+
+static int print_arpa(int argc, char *argv[]) {
+       const char *ip  = (argc > 1)? argv[1] : "::1";
+       int af          = (strchr(ip, ':'))? AF_INET6 : AF_INET;
+       union { struct in_addr a4; struct in6_addr a6; } addr;
+       char host[DNS_D_MAXNAME + 1];
+
+       if (1 != dns_inet_pton(af, ip, &addr) || 0 == dns_ptr_qname(host, sizeof host, af, &addr))
+               panic("%s: invalid address", ip);
+
+       fprintf(stdout, "%s\n", host);
+
+       return 0;
+} /* print_arpa() */
+
+
+static int show_hints(int argc, char *argv[]) {
+       struct dns_hints *(*load)(struct dns_resolv_conf *, int *);
+       const char *which, *how, *who;
+       struct dns_hints *hints;
+       int error;
+
+       which   = (argc > 1)? argv[1] : "local";
+       how     = (argc > 2)? argv[2] : "plain";
+       who     = (argc > 3)? argv[3] : "google.com";
+
+       load    = (0 == strcmp(which, "local"))
+               ? &dns_hints_local
+               : &dns_hints_root;
+
+       if (!(hints = load(resconf(), &error)))
+               panic("%s: %s", argv[0], dns_strerror(error));
+
+       if (0 == strcmp(how, "plain")) {
+               dns_hints_dump(hints, stdout);
+       } else {
+               struct dns_packet *query, *answer;
+
+               query   = dns_p_new(512);
+
+               if ((error = dns_p_push(query, DNS_S_QUESTION, who, strlen(who), DNS_T_A, DNS_C_IN, 0, 0)))
+                       panic("%s: %s", who, dns_strerror(error));
+
+               if (!(answer = dns_hints_query(hints, query, &error)))
+                       panic("%s: %s", who, dns_strerror(error));
+
+               print_packet(answer, stdout);
+
+               free(answer);
+       }
+
+       dns_hints_close(hints);
+
+       return 0;
+} /* show_hints() */
+
+
+static int resolve_query(int argc DNS_NOTUSED, char *argv[]) {
+       _Bool recurse = !!strstr(argv[0], "recurse");
+       struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local;
+       struct dns_resolver *R;
+       struct dns_packet *ans;
+       const struct dns_stat *st;
+       int error;
+
+       if (!MAIN.qname)
+               MAIN.qname = "www.google.com";
+       if (!MAIN.qtype)
+               MAIN.qtype = DNS_T_A;
+
+       resconf()->options.recurse = recurse;
+
+       if (!(R = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(),
+                              dns_opts(.socks_host=&MAIN.socks_host,
+                                       .socks_user=MAIN.socks_user,
+                                       .socks_password=MAIN.socks_password), &error)))
+               panic("%s: %s", MAIN.qname, dns_strerror(error));
+
+       dns_res_settrace(R, trace("w+b"));
+
+       if ((error = dns_res_submit(R, MAIN.qname, MAIN.qtype, DNS_C_IN)))
+               panic("%s: %s", MAIN.qname, dns_strerror(error));
+
+       while ((error = dns_res_check(R))) {
+               if (error != DNS_EAGAIN)
+                       panic("dns_res_check: %s (%d)", dns_strerror(error), error);
+               if (dns_res_elapsed(R) > 30)
+                       panic("query timed-out");
+
+               dns_res_poll(R, 1);
+       }
+
+       ans = dns_res_fetch(R, &error);
+       print_packet(ans, stdout);
+       free(ans);
+
+       st = dns_res_stat(R);
+       putchar('\n');
+       printf(";; queries:  %"PRIuZ"\n", st->queries);
+       printf(";; udp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.sent.count, st->udp.sent.bytes);
+       printf(";; udp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.rcvd.count, st->udp.rcvd.bytes);
+       printf(";; tcp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.sent.count, st->tcp.sent.bytes);
+       printf(";; tcp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.rcvd.count, st->tcp.rcvd.bytes);
+
+       dns_res_close(R);
+
+       return 0;
+} /* resolve_query() */
+
+
+static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) {
+       _Bool recurse = !!strstr(argv[0], "recurse");
+       struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local;
+       struct dns_resolver *res = NULL;
+       struct dns_addrinfo *ai = NULL;
+       struct addrinfo ai_hints = { .ai_family = PF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_CANONNAME };
+       struct addrinfo *ent;
+       char pretty[512];
+       int error;
+
+       if (!MAIN.qname)
+               MAIN.qname = "www.google.com";
+       /* NB: MAIN.qtype of 0 means obey hints.ai_family */
+
+       resconf()->options.recurse = recurse;
+
+       if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), dns_opts(), &error)))
+               panic("%s: %s", MAIN.qname, dns_strerror(error));
+
+       if (!(ai = dns_ai_open(MAIN.qname, "80", MAIN.qtype, &ai_hints, res, &error)))
+               panic("%s: %s", MAIN.qname, dns_strerror(error));
+
+       dns_ai_settrace(ai, trace("w+b"));
+
+       do {
+               switch (error = dns_ai_nextent(&ent, ai)) {
+               case 0:
+                       dns_ai_print(pretty, sizeof pretty, ent, ai);
+
+                       fputs(pretty, stdout);
+
+                       free(ent);
+
+                       break;
+               case ENOENT:
+                       break;
+               case DNS_EAGAIN:
+                       if (dns_ai_elapsed(ai) > 30)
+                               panic("query timed-out");
+
+                       dns_ai_poll(ai, 1);
+
+                       break;
+               default:
+                       panic("dns_ai_nextent: %s (%d)", dns_strerror(error), error);
+               }
+       } while (error != ENOENT);
+
+       dns_res_close(res);
+       dns_ai_close(ai);
+
+       return 0;
+} /* resolve_addrinfo() */
+
+
+static int dump_trace(int argc DNS_NOTUSED, char *argv[]) {
+       int error;
+
+       if (!MAIN.trace)
+               panic("no trace file specified");
+
+       if ((error = dns_trace_dump(trace("r"), stdout)))
+               panic("dump_trace: %s", dns_strerror(error));
+
+       return 0;
+} /* dump_trace() */
+
+
+static int echo_port(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
+       union {
+               struct sockaddr sa;
+               struct sockaddr_in sin;
+       } port;
+       int fd;
+
+       memset(&port, 0, sizeof port);
+       port.sin.sin_family = AF_INET;
+       port.sin.sin_port = htons(5354);
+       port.sin.sin_addr.s_addr = inet_addr("127.0.0.1");
+
+       if (-1 == (fd = socket(PF_INET, SOCK_DGRAM, 0)))
+               panic("socket: %s", strerror(errno));
+
+       if (0 != bind(fd, &port.sa, sizeof port.sa))
+               panic("127.0.0.1:5353: %s", dns_strerror(errno));
+
+       for (;;) {
+               struct dns_packet *pkt = dns_p_new(512);
+               struct sockaddr_storage ss;
+               socklen_t slen = sizeof ss;
+               ssize_t count;
+#if defined(MSG_WAITALL) /* MinGW issue */
+               int rflags = MSG_WAITALL;
+#else
+               int rflags = 0;
+#endif
+
+               count = recvfrom(fd, (char *)pkt->data, pkt->size, rflags, (struct sockaddr *)&ss, &slen);
+
+               if (!count || count < 0)
+                       panic("recvfrom: %s", strerror(errno));
+
+               pkt->end = count;
+
+               dns_p_dump(pkt, stdout);
+
+               (void)sendto(fd, (char *)pkt->data, pkt->end, 0, (struct sockaddr *)&ss, slen);
+       }
+
+       return 0;
+} /* echo_port() */
+
+
+static int isection(int argc, char *argv[]) {
+       const char *name = (argc > 1)? argv[1] : "";
+       int type;
+
+       type = dns_isection(name);
+       name = dns_strsection(type);
+
+       printf("%s (%d)\n", name, type);
+
+       return 0;
+} /* isection() */
+
+
+static int iclass(int argc, char *argv[]) {
+       const char *name = (argc > 1)? argv[1] : "";
+       int type;
+
+       type = dns_iclass(name);
+       name = dns_strclass(type);
+
+       printf("%s (%d)\n", name, type);
+
+       return 0;
+} /* iclass() */
+
+
+static int itype(int argc, char *argv[]) {
+       const char *name = (argc > 1)? argv[1] : "";
+       int type;
+
+       type = dns_itype(name);
+       name = dns_strtype(type);
+
+       printf("%s (%d)\n", name, type);
+
+       return 0;
+} /* itype() */
+
+
+static int iopcode(int argc, char *argv[]) {
+       const char *name = (argc > 1)? argv[1] : "";
+       int type;
+
+       type = dns_iopcode(name);
+       name = dns_stropcode(type);
+
+       printf("%s (%d)\n", name, type);
+
+       return 0;
+} /* iopcode() */
+
+
+static int ircode(int argc, char *argv[]) {
+       const char *name = (argc > 1)? argv[1] : "";
+       int type;
+
+       type = dns_ircode(name);
+       name = dns_strrcode(type);
+
+       printf("%s (%d)\n", name, type);
+
+       return 0;
+} /* ircode() */
+
+
+#define SIZE1(x) { DNS_PP_STRINGIFY(x), sizeof (x) }
+#define SIZE2(x, ...) SIZE1(x), SIZE1(__VA_ARGS__)
+#define SIZE3(x, ...) SIZE1(x), SIZE2(__VA_ARGS__)
+#define SIZE4(x, ...) SIZE1(x), SIZE3(__VA_ARGS__)
+#define SIZE(...) DNS_PP_CALL(DNS_PP_XPASTE(SIZE, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__)
+
+static int sizes(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
+       static const struct { const char *name; size_t size; } type[] = {
+               SIZE(struct dns_header, struct dns_packet, struct dns_rr, struct dns_rr_i),
+               SIZE(struct dns_a, struct dns_aaaa, struct dns_mx, struct dns_ns),
+               SIZE(struct dns_cname, struct dns_soa, struct dns_ptr, struct dns_srv),
+               SIZE(struct dns_sshfp, struct dns_txt, union dns_any),
+               SIZE(struct dns_resolv_conf, struct dns_hosts, struct dns_hints, struct dns_hints_i),
+               SIZE(struct dns_options, struct dns_socket, struct dns_resolver, struct dns_addrinfo),
+               SIZE(struct dns_cache), SIZE(size_t), SIZE(void *), SIZE(long)
+       };
+       unsigned i, max;
+
+       for (i = 0, max = 0; i < lengthof(type); i++)
+               max = DNS_PP_MAX(max, strlen(type[i].name));
+
+       for (i = 0; i < lengthof(type); i++)
+               printf("%*s : %"PRIuZ"\n", max, type[i].name, type[i].size);
+
+       return 0;
+} /* sizes() */
+
+
+static const struct { const char *cmd; int (*run)(); const char *help; } cmds[] = {
+       { "parse-packet",       &parse_packet,          "parse binary packet from stdin" },
+       { "parse-domain",       &parse_domain,          "anchor and iteratively cleave domain" },
+       { "trim-domain",        &trim_domain,           "trim and anchor domain name" },
+       { "expand-domain",      &expand_domain,         "expand domain at offset NN in packet from stdin" },
+       { "show-resconf",       &show_resconf,          "show resolv.conf data" },
+       { "show-hosts",         &show_hosts,            "show hosts data" },
+       { "show-nssconf",       &show_nssconf,          "show nsswitch.conf data" },
+       { "query-hosts",        &query_hosts,           "query A, AAAA or PTR in hosts data" },
+       { "search-list",        &search_list,           "generate query search list from domain" },
+       { "permute-set",        &permute_set,           "generate random permutation -> (0 .. N or N .. M)" },
+       { "shuffle-16",         &shuffle_16,            "simple 16-bit permutation" },
+       { "dump-random",        &dump_random,           "generate random bytes" },
+       { "send-query",         &send_query,            "send query to host" },
+       { "send-query-udp",     &send_query,            "send udp query to host" },
+       { "send-query-tcp",     &send_query,            "send tcp query to host" },
+       { "print-arpa",         &print_arpa,            "print arpa. zone name of address" },
+       { "show-hints",         &show_hints,            "print hints: show-hints [local|root] [plain|packet]" },
+       { "resolve-stub",       &resolve_query,         "resolve as stub resolver" },
+       { "resolve-recurse",    &resolve_query,         "resolve as recursive resolver" },
+       { "addrinfo-stub",      &resolve_addrinfo,      "resolve through getaddrinfo clone" },
+       { "addrinfo-recurse",   &resolve_addrinfo,      "resolve through getaddrinfo clone" },
+/*     { "resolve-nameinfo",   &resolve_query,         "resolve as recursive resolver" }, */
+       { "dump-trace",         &dump_trace,            "dump the contents of a trace file" },
+       { "echo",               &echo_port,             "server echo mode, for nmap fuzzing" },
+       { "isection",           &isection,              "parse section string" },
+       { "iclass",             &iclass,                "parse class string" },
+       { "itype",              &itype,                 "parse type string" },
+       { "iopcode",            &iopcode,               "parse opcode string" },
+       { "ircode",             &ircode,                "parse rcode string" },
+       { "sizes",              &sizes,                 "print data structure sizes" },
+};
+
+
+static void print_usage(const char *progname, FILE *fp) {
+       static const char *usage        =
+               " [OPTIONS] COMMAND [ARGS]\n"
+               "  -c PATH   Path to resolv.conf\n"
+               "  -n PATH   Path to nsswitch.conf\n"
+               "  -l PATH   Path to local hosts\n"
+               "  -z PATH   Path to zone cache\n"
+               "  -q QNAME  Query name\n"
+               "  -t QTYPE  Query type\n"
+               "  -s HOW    Sort records\n"
+               "  -S ADDR   Address of SOCKS server to use\n"
+               "  -P PORT   Port of SOCKS server to use\n"
+               "  -A USER:PASSWORD Credentials for the SOCKS server\n"
+               "  -f PATH   Path to trace file\n"
+               "  -v        Be more verbose (-vv show packets; -vvv hexdump packets)\n"
+               "  -V        Print version info\n"
+               "  -h        Print this usage message\n"
+               "\n";
+       unsigned i, n, m;
+
+       fputs(progname, fp);
+       fputs(usage, fp);
+
+       for (i = 0, m = 0; i < lengthof(cmds); i++) {
+               if (strlen(cmds[i].cmd) > m)
+                       m       = strlen(cmds[i].cmd);
+       }
+
+       for (i = 0; i < lengthof(cmds); i++) {
+               fprintf(fp, "  %s  ", cmds[i].cmd);
+
+               for (n = strlen(cmds[i].cmd); n < m; n++)
+                       putc(' ', fp);
+
+               fputs(cmds[i].help, fp);
+               putc('\n', fp);
+       }
+
+       fputs("\nReport bugs to William Ahern <william@25thandClement.com>\n", fp);
+} /* print_usage() */
+
+
+static void print_version(const char *progname, FILE *fp) {
+       fprintf(fp, "%s (dns.c) %.8X\n", progname, dns_v_rel());
+       fprintf(fp, "vendor  %s\n", dns_vendor());
+       fprintf(fp, "release %.8X\n", dns_v_rel());
+       fprintf(fp, "abi     %.8X\n", dns_v_abi());
+       fprintf(fp, "api     %.8X\n", dns_v_api());
+} /* print_version() */
+
+
+static void main_exit(void) {
+       dns_trace_close(MAIN.memo.trace);
+       MAIN.memo.trace = NULL;
+       dns_hosts_close(MAIN.memo.hosts);
+       MAIN.memo.hosts = NULL;
+       dns_resconf_close(MAIN.memo.resconf);
+       MAIN.memo.resconf = NULL;
+} /* main_exit() */
+
+int main(int argc, char **argv) {
+       extern int optind;
+       extern char *optarg;
+       const char *progname    = argv[0];
+       unsigned i;
+       int ch;
+
+       atexit(&main_exit);
+
+       while (-1 != (ch = getopt(argc, argv, "q:t:c:n:l:z:s:S:P:A:f:vVh"))) {
+               switch (ch) {
+               case 'c':
+                       assert(MAIN.resconf.count < lengthof(MAIN.resconf.path));
+
+                       MAIN.resconf.path[MAIN.resconf.count++] = optarg;
+
+                       break;
+               case 'n':
+                       assert(MAIN.nssconf.count < lengthof(MAIN.nssconf.path));
+
+                       MAIN.nssconf.path[MAIN.nssconf.count++] = optarg;
+
+                       break;
+               case 'l':
+                       assert(MAIN.hosts.count < lengthof(MAIN.hosts.path));
+
+                       MAIN.hosts.path[MAIN.hosts.count++]     = optarg;
+
+                       break;
+               case 'z':
+                       assert(MAIN.cache.count < lengthof(MAIN.cache.path));
+
+                       MAIN.cache.path[MAIN.cache.count++]     = optarg;
+
+                       break;
+               case 'q':
+                       MAIN.qname      = optarg;
+
+                       break;
+               case 't':
+                       for (i = 0; i < lengthof(dns_rrtypes); i++) {
+                               if (0 == strcasecmp(dns_rrtypes[i].name, optarg))
+                                       { MAIN.qtype = dns_rrtypes[i].type; break; }
+                       }
+
+                       if (MAIN.qtype)
+                               break;
+
+                       for (i = 0; dns_isdigit(optarg[i]); i++) {
+                               MAIN.qtype      *= 10;
+                               MAIN.qtype      += optarg[i] - '0';
+                       }
+
+                       if (!MAIN.qtype)
+                               panic("%s: invalid query type", optarg);
+
+                       break;
+               case 's':
+                       if (0 == strcasecmp(optarg, "packet"))
+                               MAIN.sort       = &dns_rr_i_packet;
+                       else if (0 == strcasecmp(optarg, "shuffle"))
+                               MAIN.sort       = &dns_rr_i_shuffle;
+                       else if (0 == strcasecmp(optarg, "order"))
+                               MAIN.sort       = &dns_rr_i_order;
+                       else
+                               panic("%s: invalid sort method", optarg);
+
+                       break;
+               case 'S': {
+                       dns_error_t error;
+                       struct dns_resolv_conf *conf = resconf();
+                       conf->options.tcp = DNS_RESCONF_TCP_SOCKS;
+
+                       MAIN.socks_host.ss_family = (strchr(optarg, ':')) ? AF_INET6 : AF_INET;
+                       if ((error = dns_pton(MAIN.socks_host.ss_family, optarg,
+                                             dns_sa_addr(MAIN.socks_host.ss_family,
+                                                         &MAIN.socks_host, NULL))))
+                               panic("%s: %s", optarg, dns_strerror(error));
+
+                       *dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(1080);
+
+                       break;
+               }
+               case 'P':
+                       if (! MAIN.socks_host.ss_family)
+                               panic("-P without prior -S");
+                       *dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(atoi(optarg));
+
+                       break;
+               case 'A': {
+                       char *password;
+                       if (! MAIN.socks_host.ss_family)
+                               panic("-A without prior -S");
+                       if (! (password = strchr(optarg, ':')))
+                               panic("Usage: -A USER:PASSWORD");
+                       *password = 0;
+                       password += 1;
+                       MAIN.socks_user = optarg;
+                       MAIN.socks_password = password;
+                       break;
+               }
+               case 'f':
+                       MAIN.trace = optarg;
+
+                       break;
+               case 'v':
+                       dns_debug = ++MAIN.verbose;
+
+                       break;
+               case 'V':
+                       print_version(progname, stdout);
+
+                       return 0;
+               case 'h':
+                       print_usage(progname, stdout);
+
+                       return 0;
+               default:
+                       print_usage(progname, stderr);
+
+                       return EXIT_FAILURE;
+               } /* switch() */
+       } /* while() */
+
+       argc    -= optind;
+       argv    += optind;
+
+       for (i = 0; i < lengthof(cmds) && argv[0]; i++) {
+               if (0 == strcmp(cmds[i].cmd, argv[0]))
+                       return cmds[i].run(argc, argv);
+       }
+
+       print_usage(progname, stderr);
+
+       return EXIT_FAILURE;
+} /* main() */
+
+
+#endif /* DNS_MAIN */
+
+
+/*
+ * pop file-scoped compiler annotations
+ */
+#if __clang__
+#pragma clang diagnostic pop
+#elif DNS_GNUC_PREREQ(4,6,0)
+#pragma GCC diagnostic pop
+#endif
diff --git a/dirmngr/dns.h b/dirmngr/dns.h
new file mode 100644 (file)
index 0000000..30d0b45
--- /dev/null
@@ -0,0 +1,1365 @@
+/* ==========================================================================
+ * dns.h - Recursive, Reentrant DNS Resolver.
+ * --------------------------------------------------------------------------
+ * Copyright (c) 2009, 2010, 2012-2015  William Ahern
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit
+ * persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * ==========================================================================
+ */
+#ifndef DNS_H
+#define DNS_H
+
+#include <stddef.h>            /* size_t offsetof() */
+#include <stdint.h>            /* uint64_t */
+#include <stdio.h>             /* FILE */
+#include <string.h>            /* strlen(3) */
+#include <time.h>              /* struct timespec time_t */
+
+#if _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/param.h>         /* BYTE_ORDER BIG_ENDIAN _BIG_ENDIAN */
+#include <sys/types.h>         /* socklen_t */
+#include <sys/socket.h>                /* struct socket */
+#include <poll.h>              /* POLLIN POLLOUT */
+#include <netinet/in.h>                /* struct in_addr struct in6_addr */
+#include <netdb.h>             /* struct addrinfo */
+#endif
+
+
+/*
+ * V I S I B I L I T Y
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef DNS_PUBLIC
+#define DNS_PUBLIC
+#endif
+
+
+/*
+ * V E R S I O N
+ *
+ * Vendor: Entity for which versions numbers are relevant. (If forking
+ * change DNS_VENDOR to avoid confusion.)
+ *
+ * Three versions:
+ *
+ * REL Official "release"--bug fixes, new features, etc.
+ * ABI Changes to existing object sizes or parameter types
+ * API Changes that might effect application source.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_VENDOR "william@25thandClement.com"
+
+#define DNS_V_REL  0x20160809
+#define DNS_V_ABI  0x20160608
+#define DNS_V_API  0x20160809
+
+
+DNS_PUBLIC const char *dns_vendor(void);
+
+DNS_PUBLIC int dns_v_rel(void);
+DNS_PUBLIC int dns_v_abi(void);
+DNS_PUBLIC int dns_v_api(void);
+
+
+/*
+ * E R R O R S
+ *
+ * Errors and exceptions are always returned through an int. This should
+ * hopefully make integration easier in the majority of circumstances, and
+ * also cut down on useless compiler warnings.
+ *
+ * System and library errors are returned together. POSIX guarantees that
+ * all system errors are positive integers. Library errors are always
+ * negative integers in the range DNS_EBASE to DNS_ELAST, with the high bits
+ * set to the three magic ASCII characters "dns".
+ *
+ * dns_strerror() returns static English string descriptions of all known
+ * errors, and punts the remainder to strerror(3).
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_EBASE -(('d' << 24) | ('n' << 16) | ('s' << 8) | 64)
+
+#define dns_error_t int /* for documentation only */
+
+enum dns_errno {
+       DNS_ENOBUFS = DNS_EBASE,
+       DNS_EILLEGAL,
+       DNS_EORDER,
+       DNS_ESECTION,
+       DNS_EUNKNOWN,
+       DNS_EADDRESS,
+       DNS_ENOQUERY,
+       DNS_ENOANSWER,
+       DNS_EFETCHED,
+       DNS_ESERVICE, /* EAI_SERVICE */
+       DNS_ENONAME,  /* EAI_NONAME */
+       DNS_EFAIL,    /* EAI_FAIL */
+       DNS_ECONNFIN,
+       DNS_EVERIFY,
+       DNS_ELAST,
+}; /* dns_errno */
+
+DNS_PUBLIC const char *dns_strerror(dns_error_t);
+
+DNS_PUBLIC int *dns_debug_p(void);
+
+#define dns_debug (*dns_debug_p()) /* was extern int dns_debug before 20160523 API */
+
+
+/*
+ * C O M P I L E R  A N N O T A T I O N S
+ *
+ * GCC with -Wextra, and clang by default, complain about overrides in
+ * initializer lists. Overriding previous member initializers is well
+ * defined behavior in C. dns.c relies on this behavior to define default,
+ * overrideable member values when instantiating configuration objects.
+ *
+ * dns_quietinit() guards a compound literal expression with pragmas to
+ * silence these shrill warnings. This alleviates the burden of requiring
+ * third-party projects to adjust their compiler flags.
+ *
+ * NOTE: If you take the address of the compound literal, take the address
+ * of the transformed expression, otherwise the compound literal lifetime is
+ * tied to the scope of the GCC statement expression.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#if defined __clang__
+#define DNS_PRAGMA_PUSH _Pragma("clang diagnostic push")
+#define DNS_PRAGMA_QUIET _Pragma("clang diagnostic ignored \"-Winitializer-overrides\"")
+#define DNS_PRAGMA_POP _Pragma("clang diagnostic pop")
+
+#define dns_quietinit(...) \
+       DNS_PRAGMA_PUSH DNS_PRAGMA_QUIET __VA_ARGS__ DNS_PRAGMA_POP
+#elif (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ > 4
+#define DNS_PRAGMA_PUSH _Pragma("GCC diagnostic push")
+#define DNS_PRAGMA_QUIET _Pragma("GCC diagnostic ignored \"-Woverride-init\"")
+#define DNS_PRAGMA_POP _Pragma("GCC diagnostic pop")
+
+/* GCC parses the _Pragma operator less elegantly than clang. */
+#define dns_quietinit(...) \
+       __extension__ ({ DNS_PRAGMA_PUSH DNS_PRAGMA_QUIET __VA_ARGS__; DNS_PRAGMA_POP })
+#else
+#define DNS_PRAGMA_PUSH
+#define DNS_PRAGMA_QUIET
+#define DNS_PRAGMA_POP
+#define dns_quietinit(...) __VA_ARGS__
+#endif
+
+#if defined __GNUC__
+#define DNS_PRAGMA_EXTENSION __extension__
+#else
+#define DNS_PRAGMA_EXTENSION
+#endif
+
+
+/*
+ * E V E N T S  I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#if defined(POLLIN)
+#define DNS_POLLIN POLLIN
+#else
+#define DNS_POLLIN  1
+#endif
+
+#if defined(POLLOUT)
+#define DNS_POLLOUT POLLOUT
+#else
+#define DNS_POLLOUT 2
+#endif
+
+
+/*
+ * See Application Interface below for configuring libevent bitmasks instead
+ * of poll(2) bitmasks.
+ */
+#define DNS_EVREAD  2
+#define DNS_EVWRITE 4
+
+
+#define DNS_POLL2EV(set) \
+       (((set) & DNS_POLLIN)? DNS_EVREAD : 0) | (((set) & DNS_POLLOUT)? DNS_EVWRITE : 0)
+
+#define DNS_EV2POLL(set) \
+       (((set) & DNS_EVREAD)? DNS_POLLIN : 0) | (((set) & DNS_EVWRITE)? DNS_POLLOUT : 0)
+
+
+/*
+ * E N U M E R A T I O N  I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+enum dns_section {
+       DNS_S_QD                = 0x01,
+#define DNS_S_QUESTION         DNS_S_QD
+
+       DNS_S_AN                = 0x02,
+#define DNS_S_ANSWER           DNS_S_AN
+
+       DNS_S_NS                = 0x04,
+#define DNS_S_AUTHORITY                DNS_S_NS
+
+       DNS_S_AR                = 0x08,
+#define DNS_S_ADDITIONAL       DNS_S_AR
+
+       DNS_S_ALL               = 0x0f
+}; /* enum dns_section */
+
+
+enum dns_class {
+       DNS_C_IN        = 1,
+
+       DNS_C_ANY       = 255
+}; /* enum dns_class */
+
+
+enum dns_type {
+       DNS_T_A         = 1,
+       DNS_T_NS        = 2,
+       DNS_T_CNAME     = 5,
+       DNS_T_SOA       = 6,
+       DNS_T_PTR       = 12,
+       DNS_T_MX        = 15,
+       DNS_T_TXT       = 16,
+       DNS_T_AAAA      = 28,
+       DNS_T_SRV       = 33,
+       DNS_T_OPT       = 41,
+       DNS_T_SSHFP     = 44,
+       DNS_T_SPF       = 99,
+       DNS_T_AXFR      = 252,
+
+       DNS_T_ALL       = 255
+}; /* enum dns_type */
+
+
+enum dns_opcode {
+       DNS_OP_QUERY    = 0,
+       DNS_OP_IQUERY   = 1,
+       DNS_OP_STATUS   = 2,
+       DNS_OP_NOTIFY   = 4,
+       DNS_OP_UPDATE   = 5,
+}; /* dns_opcode */
+
+
+enum dns_rcode {
+       DNS_RC_NOERROR  = 0,
+       DNS_RC_FORMERR  = 1,
+       DNS_RC_SERVFAIL = 2,
+       DNS_RC_NXDOMAIN = 3,
+       DNS_RC_NOTIMP   = 4,
+       DNS_RC_REFUSED  = 5,
+       DNS_RC_YXDOMAIN = 6,
+       DNS_RC_YXRRSET  = 7,
+       DNS_RC_NXRRSET  = 8,
+       DNS_RC_NOTAUTH  = 9,
+       DNS_RC_NOTZONE  = 10,
+
+       /* EDNS(0) extended RCODEs */
+       DNS_RC_BADVERS = 16,
+}; /* dns_rcode */
+
+
+/*
+ * NOTE: These string functions need a small buffer in case the literal
+ * integer value needs to be printed and returned. UNLESS this buffer is
+ * SPECIFIED, the returned string has ONLY BLOCK SCOPE.
+ */
+#define DNS_STRMAXLEN 47 /* "QUESTION|ANSWER|AUTHORITY|ADDITIONAL" */
+
+DNS_PUBLIC const char *dns_strsection(enum dns_section, void *, size_t);
+#define dns_strsection3(a, b, c) \
+                               dns_strsection((a), (b), (c))
+#define dns_strsection1(a)     dns_strsection((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1)
+#define dns_strsection(...)    DNS_PP_CALL(DNS_PP_XPASTE(dns_strsection, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__)
+
+DNS_PUBLIC enum dns_section dns_isection(const char *);
+
+DNS_PUBLIC const char *dns_strclass(enum dns_class, void *, size_t);
+#define dns_strclass3(a, b, c) dns_strclass((a), (b), (c))
+#define dns_strclass1(a)       dns_strclass((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1)
+#define dns_strclass(...)      DNS_PP_CALL(DNS_PP_XPASTE(dns_strclass, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__)
+
+DNS_PUBLIC enum dns_class dns_iclass(const char *);
+
+DNS_PUBLIC const char *dns_strtype(enum dns_type, void *, size_t);
+#define dns_strtype3(a, b, c)  dns_strtype((a), (b), (c))
+#define dns_strtype1(a)                dns_strtype((a), (char [DNS_STRMAXLEN + 1]){ 0 }, DNS_STRMAXLEN + 1)
+#define dns_strtype(...)       DNS_PP_CALL(DNS_PP_XPASTE(dns_strtype, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__)
+
+DNS_PUBLIC enum dns_type dns_itype(const char *);
+
+DNS_PUBLIC const char *dns_stropcode(enum dns_opcode);
+
+DNS_PUBLIC enum dns_opcode dns_iopcode(const char *);
+
+DNS_PUBLIC const char *dns_strrcode(enum dns_rcode);
+
+DNS_PUBLIC enum dns_rcode dns_ircode(const char *);
+
+
+/*
+ * A T O M I C  I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+typedef unsigned long dns_atomic_t;
+
+typedef unsigned long dns_refcount_t; /* must be same value type as dns_atomic_t */
+
+
+/*
+ * C R Y P T O  I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+typedef unsigned dns_random_f(void);
+
+DNS_PUBLIC dns_random_f **dns_random_p(void);
+
+#define dns_random (*dns_random_p()) /* was extern unsigned (*dns_random)(void) before 20160523 API */
+
+
+/*
+ * P A C K E T  I N T E R F A C E
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_header {
+               unsigned qid:16;
+
+#if (defined BYTE_ORDER && BYTE_ORDER == BIG_ENDIAN) || (defined __sun && defined _BIG_ENDIAN)
+               unsigned qr:1;
+               unsigned opcode:4;
+               unsigned aa:1;
+               unsigned tc:1;
+               unsigned rd:1;
+
+               unsigned ra:1;
+               unsigned unused:3;
+               unsigned rcode:4;
+#else
+               unsigned rd:1;
+               unsigned tc:1;
+               unsigned aa:1;
+               unsigned opcode:4;
+               unsigned qr:1;
+
+               unsigned rcode:4;
+               unsigned unused:3;
+               unsigned ra:1;
+#endif
+
+               unsigned qdcount:16;
+               unsigned ancount:16;
+               unsigned nscount:16;
+               unsigned arcount:16;
+}; /* struct dns_header */
+
+#define dns_header(p)  (&(p)->header)
+
+
+#ifndef DNS_P_QBUFSIZ
+#define DNS_P_QBUFSIZ  dns_p_calcsize(256 + 4)
+#endif
+
+#ifndef DNS_P_DICTSIZE
+#define DNS_P_DICTSIZE 16
+#endif
+
+struct dns_packet {
+       unsigned short dict[DNS_P_DICTSIZE];
+
+       struct dns_p_memo {
+               struct dns_s_memo {
+                       unsigned short base, end;
+               } qd, an, ns, ar;
+
+               struct {
+                       unsigned short p;
+                       unsigned short maxudp;
+                       unsigned ttl;
+               } opt;
+       } memo;
+
+       struct { struct dns_packet *cqe_next, *cqe_prev; } cqe;
+
+       size_t size, end;
+
+       int:16; /* tcp padding */
+
+       DNS_PRAGMA_EXTENSION union {
+               struct dns_header header;
+               unsigned char data[1];
+       };
+}; /* struct dns_packet */
+
+#define dns_p_calcsize(n)      (offsetof(struct dns_packet, data) + DNS_PP_MAX(12, (n)))
+
+#define dns_p_sizeof(P)                dns_p_calcsize((P)->end)
+
+/** takes size of maximum desired payload */
+#define dns_p_new(n)           (dns_p_init((struct dns_packet *)&(union { unsigned char b[dns_p_calcsize((n))]; struct dns_packet p; }){ { 0 } }, dns_p_calcsize((n))))
+
+/** takes size of entire packet structure as allocated */
+DNS_PUBLIC struct dns_packet *dns_p_init(struct dns_packet *, size_t);
+
+/** takes size of maximum desired payload */
+DNS_PUBLIC struct dns_packet *dns_p_make(size_t, int *);
+
+DNS_PUBLIC int dns_p_grow(struct dns_packet **);
+
+DNS_PUBLIC struct dns_packet *dns_p_copy(struct dns_packet *, const struct dns_packet *);
+
+#define dns_p_opcode(P)                (dns_header(P)->opcode)
+
+DNS_PUBLIC enum dns_rcode dns_p_rcode(struct dns_packet *);
+
+DNS_PUBLIC unsigned dns_p_count(struct dns_packet *, enum dns_section);
+
+DNS_PUBLIC int dns_p_push(struct dns_packet *, enum dns_section, const void *, size_t, enum dns_type, enum dns_class, unsigned, const void *);
+
+DNS_PUBLIC void dns_p_dictadd(struct dns_packet *, unsigned short);
+
+DNS_PUBLIC struct dns_packet *dns_p_merge(struct dns_packet *, enum dns_section, struct dns_packet *, enum dns_section, int *);
+
+DNS_PUBLIC void dns_p_dump(struct dns_packet *, FILE *);
+
+DNS_PUBLIC int dns_p_study(struct dns_packet *);
+
+
+/*
+ * D O M A I N  N A M E  I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_D_MAXLABEL 63      /* + 1 '\0' */
+#define DNS_D_MAXNAME  255     /* + 1 '\0' */
+
+#define DNS_D_ANCHOR   1       /* anchor domain w/ root "." */
+#define DNS_D_CLEAVE   2       /* cleave sub-domain */
+#define DNS_D_TRIM     4       /* remove superfluous dots */
+
+#define dns_d_new3(a, b, f)    dns_d_init(&(char[DNS_D_MAXNAME + 1]){ 0 }, DNS_D_MAXNAME + 1, (a), (b), (f))
+#define dns_d_new2(a, f)       dns_d_new3((a), strlen((a)), (f))
+#define dns_d_new1(a)          dns_d_new3((a), strlen((a)), DNS_D_ANCHOR)
+#define dns_d_new(...)         DNS_PP_CALL(DNS_PP_XPASTE(dns_d_new, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__)
+
+DNS_PUBLIC char *dns_d_init(void *, size_t, const void *, size_t, int);
+
+DNS_PUBLIC size_t dns_d_anchor(void *, size_t, const void *, size_t);
+
+DNS_PUBLIC size_t dns_d_cleave(void *, size_t, const void *, size_t);
+
+DNS_PUBLIC size_t dns_d_comp(void *, size_t, const void *, size_t, struct dns_packet *, int *);
+
+DNS_PUBLIC size_t dns_d_expand(void *, size_t, unsigned short, struct dns_packet *, int *);
+
+DNS_PUBLIC unsigned short dns_d_skip(unsigned short, struct dns_packet *);
+
+DNS_PUBLIC int dns_d_push(struct dns_packet *, const void *, size_t);
+
+DNS_PUBLIC size_t dns_d_cname(void *, size_t, const void *, size_t, struct dns_packet *, int *error);
+
+
+/*
+ * R E S O U R C E  R E C O R D  I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_rr {
+       enum dns_section section;
+
+       struct {
+               unsigned short p;
+               unsigned short len;
+       } dn;
+
+       enum dns_type type;
+       enum dns_class class;
+       unsigned ttl;
+
+       struct {
+               unsigned short p;
+               unsigned short len;
+       } rd;
+}; /* struct dns_rr */
+
+
+DNS_PUBLIC int dns_rr_copy(struct dns_packet *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_rr_parse(struct dns_rr *, unsigned short, struct dns_packet *);
+
+DNS_PUBLIC unsigned short dns_rr_skip(unsigned short, struct dns_packet *);
+
+DNS_PUBLIC int dns_rr_cmp(struct dns_rr *, struct dns_packet *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC size_t dns_rr_print(void *, size_t, struct dns_rr *, struct dns_packet *, int *);
+
+
+#define dns_rr_i_new(P, ...) \
+       dns_rr_i_init(&dns_quietinit((struct dns_rr_i){ 0, __VA_ARGS__ }), (P))
+
+struct dns_rr_i {
+       enum dns_section section;
+       const void *name;
+       enum dns_type type;
+       enum dns_class class;
+       const void *data;
+
+       int follow;
+
+       int (*sort)(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *);
+       unsigned args[2];
+
+       struct {
+               unsigned short next;
+               unsigned short count;
+
+               unsigned exec;
+               unsigned regs[2];
+       } state, saved;
+}; /* struct dns_rr_i */
+
+DNS_PUBLIC int dns_rr_i_packet(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *);
+
+DNS_PUBLIC int dns_rr_i_order(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *);
+
+DNS_PUBLIC int dns_rr_i_shuffle(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *);
+
+DNS_PUBLIC struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *, struct dns_packet *);
+
+#define dns_rr_i_save(i)       ((i)->saved = (i)->state)
+#define dns_rr_i_rewind(i)     ((i)->state = (i)->saved)
+#define dns_rr_i_count(i)      ((i)->state.count)
+
+DNS_PUBLIC unsigned dns_rr_grep(struct dns_rr *, unsigned, struct dns_rr_i *, struct dns_packet *, int *);
+
+#define dns_rr_foreach_(rr, P, ...)    \
+       for (struct dns_rr_i DNS_PP_XPASTE(i, __LINE__) = *dns_rr_i_new((P), __VA_ARGS__); dns_rr_grep((rr), 1, &DNS_PP_XPASTE(i, __LINE__), (P), &(int){ 0 }); )
+
+#define dns_rr_foreach(...)    dns_rr_foreach_(__VA_ARGS__)
+
+
+/*
+ * A  R E S O U R C E  R E C O R D
+ */
+
+struct dns_a {
+       struct in_addr addr;
+}; /* struct dns_a */
+
+DNS_PUBLIC int dns_a_parse(struct dns_a *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_a_push(struct dns_packet *, struct dns_a *);
+
+DNS_PUBLIC int dns_a_cmp(const struct dns_a *, const struct dns_a *);
+
+DNS_PUBLIC size_t dns_a_print(void *, size_t, struct dns_a *);
+
+DNS_PUBLIC size_t dns_a_arpa(void *, size_t, const struct dns_a *);
+
+
+/*
+ * AAAA  R E S O U R C E  R E C O R D
+ */
+
+struct dns_aaaa {
+       struct in6_addr addr;
+}; /* struct dns_aaaa */
+
+DNS_PUBLIC int dns_aaaa_parse(struct dns_aaaa *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_aaaa_push(struct dns_packet *, struct dns_aaaa *);
+
+DNS_PUBLIC int dns_aaaa_cmp(const struct dns_aaaa *, const struct dns_aaaa *);
+
+DNS_PUBLIC size_t dns_aaaa_print(void *, size_t, struct dns_aaaa *);
+
+DNS_PUBLIC size_t dns_aaaa_arpa(void *, size_t, const struct dns_aaaa *);
+
+
+/*
+ * MX  R E S O U R C E  R E C O R D
+ */
+
+struct dns_mx {
+       unsigned short preference;
+       char host[DNS_D_MAXNAME + 1];
+}; /* struct dns_mx */
+
+DNS_PUBLIC int dns_mx_parse(struct dns_mx *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_mx_push(struct dns_packet *, struct dns_mx *);
+
+DNS_PUBLIC int dns_mx_cmp(const struct dns_mx *, const struct dns_mx *);
+
+DNS_PUBLIC size_t dns_mx_print(void *, size_t, struct dns_mx *);
+
+DNS_PUBLIC size_t dns_mx_cname(void *, size_t, struct dns_mx *);
+
+
+/*
+ * NS  R E S O U R C E  R E C O R D
+ */
+
+struct dns_ns {
+       char host[DNS_D_MAXNAME + 1];
+}; /* struct dns_ns */
+
+DNS_PUBLIC int dns_ns_parse(struct dns_ns *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_ns_push(struct dns_packet *, struct dns_ns *);
+
+DNS_PUBLIC int dns_ns_cmp(const struct dns_ns *, const struct dns_ns *);
+
+DNS_PUBLIC size_t dns_ns_print(void *, size_t, struct dns_ns *);
+
+DNS_PUBLIC size_t dns_ns_cname(void *, size_t, struct dns_ns *);
+
+
+/*
+ * CNAME  R E S O U R C E  R E C O R D
+ */
+
+struct dns_cname {
+       char host[DNS_D_MAXNAME + 1];
+}; /* struct dns_cname */
+
+DNS_PUBLIC int dns_cname_parse(struct dns_cname *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_cname_push(struct dns_packet *, struct dns_cname *);
+
+DNS_PUBLIC int dns_cname_cmp(const struct dns_cname *, const struct dns_cname *);
+
+DNS_PUBLIC size_t dns_cname_print(void *, size_t, struct dns_cname *);
+
+DNS_PUBLIC size_t dns_cname_cname(void *, size_t, struct dns_cname *);
+
+
+/*
+ * SOA  R E S O U R C E  R E C O R D
+ */
+
+struct dns_soa {
+       char mname[DNS_D_MAXNAME + 1];
+       char rname[DNS_D_MAXNAME + 1];
+       unsigned serial, refresh, retry, expire, minimum;
+}; /* struct dns_soa */
+
+DNS_PUBLIC int dns_soa_parse(struct dns_soa *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_soa_push(struct dns_packet *, struct dns_soa *);
+
+DNS_PUBLIC int dns_soa_cmp(const struct dns_soa *, const struct dns_soa *);
+
+DNS_PUBLIC size_t dns_soa_print(void *, size_t, struct dns_soa *);
+
+
+/*
+ * PTR  R E S O U R C E  R E C O R D
+ */
+
+struct dns_ptr {
+       char host[DNS_D_MAXNAME + 1];
+}; /* struct dns_ptr */
+
+DNS_PUBLIC int dns_ptr_parse(struct dns_ptr *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_ptr_push(struct dns_packet *, struct dns_ptr *);
+
+DNS_PUBLIC int dns_ptr_cmp(const struct dns_ptr *, const struct dns_ptr *);
+
+DNS_PUBLIC size_t dns_ptr_print(void *, size_t, struct dns_ptr *);
+
+DNS_PUBLIC size_t dns_ptr_cname(void *, size_t, struct dns_ptr *);
+
+DNS_PUBLIC size_t dns_ptr_qname(void *, size_t, int, void *);
+
+
+/*
+ * SRV  R E S O U R C E  R E C O R D
+ */
+
+struct dns_srv {
+       unsigned short priority;
+       unsigned short weight;
+       unsigned short port;
+       char target[DNS_D_MAXNAME + 1];
+}; /* struct dns_srv */
+
+DNS_PUBLIC int dns_srv_parse(struct dns_srv *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_srv_push(struct dns_packet *, struct dns_srv *);
+
+DNS_PUBLIC int dns_srv_cmp(const struct dns_srv *, const struct dns_srv *);
+
+DNS_PUBLIC size_t dns_srv_print(void *, size_t, struct dns_srv *);
+
+DNS_PUBLIC size_t dns_srv_cname(void *, size_t, struct dns_srv *);
+
+
+/*
+ * OPT  R E S O U R C E  R E C O R D
+ */
+
+#ifndef DNS_OPT_MINDATA
+#define DNS_OPT_MINDATA 256
+#endif
+
+#define DNS_OPT_DNSSEC  0x8000
+
+struct dns_opt {
+       enum dns_rcode rcode;
+       unsigned char version;
+       unsigned short flags;
+
+       union {
+               unsigned short maxsize; /* deprecated as confusing */
+               unsigned short maxudp; /* maximum UDP payload size */
+       };
+
+       size_t size, len;
+       unsigned char data[DNS_OPT_MINDATA];
+}; /* struct dns_opt */
+
+#define DNS_OPT_INIT(opt) { .size = sizeof (*opt) - offsetof(struct dns_opt, data) }
+
+DNS_PUBLIC struct dns_opt *dns_opt_init(struct dns_opt *, size_t);
+
+DNS_PUBLIC int dns_opt_parse(struct dns_opt *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_opt_push(struct dns_packet *, struct dns_opt *);
+
+DNS_PUBLIC int dns_opt_cmp(const struct dns_opt *, const struct dns_opt *);
+
+DNS_PUBLIC size_t dns_opt_print(void *, size_t, struct dns_opt *);
+
+DNS_PUBLIC unsigned int dns_opt_ttl(const struct dns_opt *);
+
+DNS_PUBLIC unsigned short dns_opt_class(const struct dns_opt *);
+
+DNS_PUBLIC dns_error_t dns_opt_data_push(struct dns_opt *, unsigned char, unsigned short, const void *);
+
+
+/*
+ * SSHFP  R E S O U R C E  R E C O R D
+ */
+
+struct dns_sshfp {
+       enum dns_sshfp_key {
+               DNS_SSHFP_RSA = 1,
+               DNS_SSHFP_DSA = 2,
+       } algo;
+
+       enum dns_sshfp_digest {
+               DNS_SSHFP_SHA1 = 1,
+       } type;
+
+       union {
+               unsigned char sha1[20];
+       } digest;
+}; /* struct dns_sshfp */
+
+DNS_PUBLIC int dns_sshfp_parse(struct dns_sshfp *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_sshfp_push(struct dns_packet *, struct dns_sshfp *);
+
+DNS_PUBLIC int dns_sshfp_cmp(const struct dns_sshfp *, const struct dns_sshfp *);
+
+DNS_PUBLIC size_t dns_sshfp_print(void *, size_t, struct dns_sshfp *);
+
+
+/*
+ * TXT  R E S O U R C E  R E C O R D
+ */
+
+#ifndef DNS_TXT_MINDATA
+#define DNS_TXT_MINDATA        1024
+#endif
+
+struct dns_txt {
+       size_t size, len;
+       unsigned char data[DNS_TXT_MINDATA];
+}; /* struct dns_txt */
+
+DNS_PUBLIC struct dns_txt *dns_txt_init(struct dns_txt *, size_t);
+
+DNS_PUBLIC int dns_txt_parse(struct dns_txt *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_txt_push(struct dns_packet *, struct dns_txt *);
+
+DNS_PUBLIC int dns_txt_cmp(const struct dns_txt *, const struct dns_txt *);
+
+DNS_PUBLIC size_t dns_txt_print(void *, size_t, struct dns_txt *);
+
+
+/*
+ * ANY  R E S O U R C E  R E C O R D
+ */
+
+union dns_any {
+       struct dns_a a;
+       struct dns_aaaa aaaa;
+       struct dns_mx mx;
+       struct dns_ns ns;
+       struct dns_cname cname;
+       struct dns_soa soa;
+       struct dns_ptr ptr;
+       struct dns_srv srv;
+       struct dns_opt opt;
+       struct dns_sshfp sshfp;
+       struct dns_txt txt, spf, rdata;
+}; /* union dns_any */
+
+#define DNS_ANY_INIT(any) { .rdata = { .size = sizeof *(any) - offsetof(struct dns_txt, data) } }
+
+DNS_PUBLIC union dns_any *dns_any_init(union dns_any *, size_t);
+
+DNS_PUBLIC int dns_any_parse(union dns_any *, struct dns_rr *, struct dns_packet *);
+
+DNS_PUBLIC int dns_any_push(struct dns_packet *, union dns_any *, enum dns_type);
+
+DNS_PUBLIC int dns_any_cmp(const union dns_any *, enum dns_type, const union dns_any *, enum dns_type);
+
+DNS_PUBLIC size_t dns_any_print(void *, size_t, union dns_any *, enum dns_type);
+
+DNS_PUBLIC size_t dns_any_cname(void *, size_t, union dns_any *, enum dns_type);
+
+
+/*
+ * H O S T S  I N T E R F A C E
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_hosts;
+
+DNS_PUBLIC struct dns_hosts *dns_hosts_open(int *);
+
+DNS_PUBLIC void dns_hosts_close(struct dns_hosts *);
+
+DNS_PUBLIC dns_refcount_t dns_hosts_acquire(struct dns_hosts *);
+
+DNS_PUBLIC dns_refcount_t dns_hosts_release(struct dns_hosts *);
+
+DNS_PUBLIC struct dns_hosts *dns_hosts_mortal(struct dns_hosts *);
+
+DNS_PUBLIC struct dns_hosts *dns_hosts_local(int *);
+
+DNS_PUBLIC int dns_hosts_loadfile(struct dns_hosts *, FILE *);
+
+DNS_PUBLIC int dns_hosts_loadpath(struct dns_hosts *, const char *);
+
+DNS_PUBLIC int dns_hosts_dump(struct dns_hosts *, FILE *);
+
+DNS_PUBLIC int dns_hosts_insert(struct dns_hosts *, int, const void *, const void *, _Bool);
+
+DNS_PUBLIC struct dns_packet *dns_hosts_query(struct dns_hosts *, struct dns_packet *, int *);
+
+
+/*
+ * R E S O L V . C O N F  I N T E R F A C E
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_resolv_conf {
+       struct sockaddr_storage nameserver[3];
+
+       char search[4][DNS_D_MAXNAME + 1];
+
+       /* (f)ile, (b)ind, (c)ache */
+       char lookup[4 * (1 + (4 * 2))];
+
+       /* getaddrinfo family by preference order ("inet4", "inet6") */
+       int family[3];
+
+       struct {
+               _Bool edns0;
+
+               unsigned ndots;
+
+               unsigned timeout;
+
+               unsigned attempts;
+
+               _Bool rotate;
+
+               _Bool recurse;
+
+               _Bool smart;
+
+               enum {
+                       DNS_RESCONF_TCP_ENABLE,
+                       DNS_RESCONF_TCP_ONLY,
+                       DNS_RESCONF_TCP_SOCKS,
+                       DNS_RESCONF_TCP_DISABLE,
+               } tcp;
+       } options;
+
+       struct sockaddr_storage iface;
+
+       struct { /* PRIVATE */
+               dns_atomic_t refcount;
+       } _;
+}; /* struct dns_resolv_conf */
+
+DNS_PUBLIC struct dns_resolv_conf *dns_resconf_open(int *);
+
+DNS_PUBLIC void dns_resconf_close(struct dns_resolv_conf *);
+
+DNS_PUBLIC dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *);
+
+DNS_PUBLIC dns_refcount_t dns_resconf_release(struct dns_resolv_conf *);
+
+DNS_PUBLIC struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *);
+
+DNS_PUBLIC struct dns_resolv_conf *dns_resconf_local(int *);
+
+DNS_PUBLIC struct dns_resolv_conf *dns_resconf_root(int *);
+
+DNS_PUBLIC int dns_resconf_pton(struct sockaddr_storage *, const char *);
+
+DNS_PUBLIC int dns_resconf_loadfile(struct dns_resolv_conf *, FILE *);
+
+DNS_PUBLIC int dns_resconf_loadpath(struct dns_resolv_conf *, const char *);
+
+DNS_PUBLIC int dns_nssconf_loadfile(struct dns_resolv_conf *, FILE *);
+
+DNS_PUBLIC int dns_nssconf_loadpath(struct dns_resolv_conf *, const char *);
+
+DNS_PUBLIC int dns_resconf_dump(struct dns_resolv_conf *, FILE *);
+
+DNS_PUBLIC int dns_nssconf_dump(struct dns_resolv_conf *, FILE *);
+
+DNS_PUBLIC int dns_resconf_setiface(struct dns_resolv_conf *, const char *, unsigned short);
+
+typedef unsigned long dns_resconf_i_t;
+
+DNS_PUBLIC size_t dns_resconf_search(void *, size_t, const void *, size_t, struct dns_resolv_conf *, dns_resconf_i_t *);
+
+
+/*
+ * H I N T  S E R V E R  I N T E R F A C E
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_hints;
+
+DNS_PUBLIC struct dns_hints *dns_hints_open(struct dns_resolv_conf *, int *);
+
+DNS_PUBLIC void dns_hints_close(struct dns_hints *);
+
+DNS_PUBLIC dns_refcount_t dns_hints_acquire(struct dns_hints *);
+
+DNS_PUBLIC dns_refcount_t dns_hints_release(struct dns_hints *);
+
+DNS_PUBLIC struct dns_hints *dns_hints_mortal(struct dns_hints *);
+
+DNS_PUBLIC int dns_hints_insert(struct dns_hints *, const char *, const struct sockaddr *, unsigned);
+
+DNS_PUBLIC unsigned dns_hints_insert_resconf(struct dns_hints *, const char *, const struct dns_resolv_conf *, int *);
+
+DNS_PUBLIC struct dns_hints *dns_hints_local(struct dns_resolv_conf *, int *);
+
+DNS_PUBLIC struct dns_hints *dns_hints_root(struct dns_resolv_conf *, int *);
+
+DNS_PUBLIC struct dns_packet *dns_hints_query(struct dns_hints *, struct dns_packet *, int *);
+
+DNS_PUBLIC int dns_hints_dump(struct dns_hints *, FILE *);
+
+
+struct dns_hints_i {
+       const char *zone;
+
+       struct {
+               unsigned next;
+               unsigned seed;
+       } state;
+}; /* struct dns_hints_i */
+
+#define dns_hints_i_new(...)   (&(struct dns_hints_i){ __VA_ARGS__ })
+
+DNS_PUBLIC unsigned dns_hints_grep(struct sockaddr **, socklen_t *, unsigned, struct dns_hints_i *, struct dns_hints *);
+
+
+/*
+ * C A C H E  I N T E R F A C E
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_cache {
+       void *state;
+
+       dns_refcount_t (*acquire)(struct dns_cache *);
+       dns_refcount_t (*release)(struct dns_cache *);
+
+       struct dns_packet *(*query)(struct dns_packet *, struct dns_cache *, int *);
+
+       int (*submit)(struct dns_packet *, struct dns_cache *);
+       int (*check)(struct dns_cache *);
+       struct dns_packet *(*fetch)(struct dns_cache *, int *);
+
+       int (*pollfd)(struct dns_cache *);
+       short (*events)(struct dns_cache *);
+       void (*clear)(struct dns_cache *);
+
+       union {
+               long i;
+               void *p;
+       } arg[3];
+
+       struct { /* PRIVATE */
+               dns_atomic_t refcount;
+       } _;
+}; /* struct dns_cache */
+
+
+DNS_PUBLIC struct dns_cache *dns_cache_init(struct dns_cache *);
+
+DNS_PUBLIC void dns_cache_close(struct dns_cache *);
+
+
+/*
+ * A P P L I C A T I O N  I N T E R F A C E
+ *
+ * Options to change the behavior of the API. Applies across all the
+ * different components.
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_OPTS_INITIALIZER_ { 0, 0 }, 0, 0
+#define DNS_OPTS_INITIALIZER  { DNS_OPTS_INITIALIZER_ }
+#define DNS_OPTS_INIT(...)    { DNS_OPTS_INITIALIZER_, __VA_ARGS__ }
+
+#define dns_opts(...) (&dns_quietinit((struct dns_options)DNS_OPTS_INIT(__VA_ARGS__)))
+
+struct dns_options {
+       /*
+        * If the callback closes *fd, it must set it to -1. Otherwise, the
+        * descriptor is queued and lazily closed at object destruction or
+        * by an explicit call to _clear(). This allows safe use of
+        * kqueue(2), epoll(2), et al -style persistent events.
+        */
+       struct {
+               void *arg;
+               int (*cb)(int *fd, void *arg);
+       } closefd;
+
+       /* bitmask for _events() routines */
+       enum dns_events {
+               DNS_SYSPOLL,
+               DNS_LIBEVENT,
+       } events;
+
+       /* Use this SOCKS server.  */
+       const struct sockaddr_storage *socks_host;
+
+       /* Credentials for the SOCKS server (optional).  */
+       const char *socks_user;
+       const char *socks_password;
+}; /* struct dns_options */
+
+
+/*
+ * S T A T S  I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_stat {
+       size_t queries;
+
+       struct {
+               struct {
+                       size_t count, bytes;
+               } sent, rcvd;
+       } udp, tcp;
+}; /* struct dns_stat */
+
+
+/*
+ * S O C K E T  I N T E R F A C E
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_socket;
+
+DNS_PUBLIC struct dns_socket *dns_so_open(const struct sockaddr *, int, const struct dns_options *, int *error);
+
+DNS_PUBLIC void dns_so_close(struct dns_socket *);
+
+DNS_PUBLIC void dns_so_reset(struct dns_socket *);
+
+DNS_PUBLIC unsigned short dns_so_mkqid(struct dns_socket *so);
+
+DNS_PUBLIC struct dns_packet *dns_so_query(struct dns_socket *, struct dns_packet *, struct sockaddr *, int *);
+
+DNS_PUBLIC int dns_so_submit(struct dns_socket *, struct dns_packet *, struct sockaddr *);
+
+DNS_PUBLIC int dns_so_check(struct dns_socket *);
+
+DNS_PUBLIC struct dns_packet *dns_so_fetch(struct dns_socket *, int *);
+
+DNS_PUBLIC time_t dns_so_elapsed(struct dns_socket *);
+
+DNS_PUBLIC void dns_so_clear(struct dns_socket *);
+
+DNS_PUBLIC int dns_so_events(struct dns_socket *);
+
+DNS_PUBLIC int dns_so_pollfd(struct dns_socket *);
+
+DNS_PUBLIC int dns_so_poll(struct dns_socket *, int);
+
+DNS_PUBLIC const struct dns_stat *dns_so_stat(struct dns_socket *);
+
+DNS_PUBLIC struct dns_trace *dns_so_trace(struct dns_socket *);
+
+DNS_PUBLIC void dns_so_settrace(struct dns_socket *, struct dns_trace *);
+
+
+/*
+ * R E S O L V E R  I N T E R F A C E
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_resolver;
+
+DNS_PUBLIC struct dns_resolver *dns_res_open(struct dns_resolv_conf *, struct dns_hosts *hosts, struct dns_hints *, struct dns_cache *, const struct dns_options *, int *);
+
+DNS_PUBLIC struct dns_resolver *dns_res_stub(const struct dns_options *, int *);
+
+DNS_PUBLIC void dns_res_reset(struct dns_resolver *);
+
+DNS_PUBLIC void dns_res_close(struct dns_resolver *);
+
+DNS_PUBLIC dns_refcount_t dns_res_acquire(struct dns_resolver *);
+
+DNS_PUBLIC dns_refcount_t dns_res_release(struct dns_resolver *);
+
+DNS_PUBLIC struct dns_resolver *dns_res_mortal(struct dns_resolver *);
+
+DNS_PUBLIC int dns_res_submit(struct dns_resolver *, const char *, enum dns_type, enum dns_class);
+
+DNS_PUBLIC int dns_res_submit2(struct dns_resolver *, const char *, size_t, enum dns_type, enum dns_class);
+
+DNS_PUBLIC int dns_res_check(struct dns_resolver *);
+
+DNS_PUBLIC struct dns_packet *dns_res_fetch(struct dns_resolver *, int *);
+
+DNS_PUBLIC time_t dns_res_elapsed(struct dns_resolver *);
+
+DNS_PUBLIC void dns_res_clear(struct dns_resolver *);
+
+DNS_PUBLIC int dns_res_events(struct dns_resolver *);
+
+DNS_PUBLIC int dns_res_pollfd(struct dns_resolver *);
+
+DNS_PUBLIC time_t dns_res_timeout(struct dns_resolver *);
+
+DNS_PUBLIC int dns_res_poll(struct dns_resolver *, int);
+
+DNS_PUBLIC struct dns_packet *dns_res_query(struct dns_resolver *, const char *, enum dns_type, enum dns_class, int, int *);
+
+DNS_PUBLIC const struct dns_stat *dns_res_stat(struct dns_resolver *);
+
+DNS_PUBLIC void dns_res_sethints(struct dns_resolver *, struct dns_hints *);
+
+DNS_PUBLIC struct dns_trace *dns_res_trace(struct dns_resolver *);
+
+DNS_PUBLIC void dns_res_settrace(struct dns_resolver *, struct dns_trace *);
+
+
+/*
+ * A D D R I N F O  I N T E R F A C E
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+struct dns_addrinfo;
+
+DNS_PUBLIC struct dns_addrinfo *dns_ai_open(const char *, const char *, enum dns_type, const struct addrinfo *, struct dns_resolver *, int *);
+
+DNS_PUBLIC void dns_ai_close(struct dns_addrinfo *);
+
+DNS_PUBLIC int dns_ai_nextent(struct addrinfo **, struct dns_addrinfo *);
+
+DNS_PUBLIC size_t dns_ai_print(void *, size_t, struct addrinfo *, struct dns_addrinfo *);
+
+DNS_PUBLIC time_t dns_ai_elapsed(struct dns_addrinfo *);
+
+DNS_PUBLIC void dns_ai_clear(struct dns_addrinfo *);
+
+DNS_PUBLIC int dns_ai_events(struct dns_addrinfo *);
+
+DNS_PUBLIC int dns_ai_pollfd(struct dns_addrinfo *);
+
+DNS_PUBLIC time_t dns_ai_timeout(struct dns_addrinfo *);
+
+DNS_PUBLIC int dns_ai_poll(struct dns_addrinfo *, int);
+
+DNS_PUBLIC const struct dns_stat *dns_ai_stat(struct dns_addrinfo *);
+
+DNS_PUBLIC struct dns_trace *dns_ai_trace(struct dns_addrinfo *);
+
+DNS_PUBLIC void dns_ai_settrace(struct dns_addrinfo *, struct dns_trace *);
+
+
+/*
+ * Q U E R Y  T R A C I N G  I N T E R F A C E
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_TRACE_ID_C(n) UINT64_C(n)
+typedef uint64_t dns_trace_id_t;
+
+#define DNS_TRACE_ABI 0x20160803
+
+struct dns_trace_event {
+       enum {
+               DNS_TE_RES_SUBMIT = 1,
+               DNS_TE_RES_FETCH = 99,
+
+               DNS_TE_SO_SUBMIT = 100,
+               DNS_TE_SO_VERIFY,
+               DNS_TE_SO_FETCH = 199,
+
+               DNS_TE_SYS_CONNECT = 200,
+               DNS_TE_SYS_SEND,
+               DNS_TE_SYS_RECV,
+       } type;
+
+       size_t size;
+       dns_trace_id_t id;
+       struct timespec ts;
+       int abi;
+
+       union {
+               struct {
+                       char qname[DNS_D_MAXNAME + 1];
+                       enum dns_type qtype;
+                       enum dns_class qclass;
+                       int error;
+               } res_submit;
+
+               struct {
+                       int error;
+               } res_fetch;
+
+               struct {
+                       struct sockaddr_storage haddr;
+                       char hname[DNS_D_MAXNAME + 1];
+                       int error;
+               } so_submit;
+
+               struct {
+                       int error;
+               } so_verify;
+
+               struct {
+                       int error;
+               } so_fetch;
+
+               struct {
+                       struct sockaddr_storage src, dst;
+                       int socktype;
+                       dns_error_t error;
+               } sys_connect, sys_send, sys_recv;
+       };
+
+       unsigned char data[];
+};
+
+static inline size_t dns_te_datasize(const struct dns_trace_event *te) {
+       size_t n = offsetof(struct dns_trace_event, data);
+       return (n <= te->size)? te->size - n : 0;
+}
+
+struct dns_trace;
+
+DNS_PUBLIC int dns_trace_abi(void);
+
+DNS_PUBLIC struct dns_trace *dns_trace_open(FILE *, dns_error_t *);
+
+DNS_PUBLIC void dns_trace_close(struct dns_trace *);
+
+DNS_PUBLIC dns_refcount_t dns_trace_acquire(struct dns_trace *);
+
+DNS_PUBLIC dns_refcount_t dns_trace_release(struct dns_trace *);
+
+DNS_PUBLIC dns_trace_id_t dns_trace_id(struct dns_trace *);
+
+DNS_PUBLIC dns_trace_id_t dns_trace_setid(struct dns_trace *, dns_trace_id_t);
+
+DNS_PUBLIC struct dns_trace_event *dns_trace_get(struct dns_trace *, struct dns_trace_event **, dns_error_t *);
+
+DNS_PUBLIC struct dns_trace_event *dns_trace_tag(struct dns_trace *, struct dns_trace_event *);
+
+DNS_PUBLIC dns_error_t dns_trace_put(struct dns_trace *, const struct dns_trace_event *, const void *, size_t);
+
+DNS_PUBLIC dns_error_t dns_trace_dump(struct dns_trace *, FILE *);
+
+DNS_PUBLIC struct dns_trace_event *dns_trace_fget(struct dns_trace_event **, FILE *, dns_error_t *);
+
+DNS_PUBLIC dns_error_t dns_trace_fput(const struct dns_trace_event *, const void *, size_t, FILE *);
+
+
+/*
+ * U T I L I T Y  I N T E R F A C E S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+DNS_PUBLIC size_t dns_strlcpy(char *, const char *, size_t);
+
+DNS_PUBLIC size_t dns_strlcat(char *, const char *, size_t);
+
+
+/*
+ * M A C R O  M A G I C S
+ *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#define DNS_PP_MIN(a, b) (((a) < (b))? (a) : (b))
+#define DNS_PP_MAX(a, b) (((a) > (b))? (a) : (b))
+#define DNS_PP_NARG_(a, b, c, d, e, f, g, h, i, j, k, N,...) N
+#define DNS_PP_NARG(...)       DNS_PP_NARG_(__VA_ARGS__, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+#define DNS_PP_CALL(F, ...)    F(__VA_ARGS__)
+#define DNS_PP_PASTE(x, y)     x##y
+#define DNS_PP_XPASTE(x, y)    DNS_PP_PASTE(x, y)
+#define DNS_PP_STRINGIFY_(s)   #s
+#define DNS_PP_STRINGIFY(s)    DNS_PP_STRINGIFY_(s)
+#define DNS_PP_D1  0
+#define DNS_PP_D2  1
+#define DNS_PP_D3  2
+#define DNS_PP_D4  3
+#define DNS_PP_D5  4
+#define DNS_PP_D6  5
+#define DNS_PP_D7  6
+#define DNS_PP_D8  7
+#define DNS_PP_D9  8
+#define DNS_PP_D10 9
+#define DNS_PP_D11 10
+#define DNS_PP_DEC(N) DNS_PP_XPASTE(DNS_PP_D, N)
+
+#endif /* DNS_H */
index bc62c82..14d60df 100644 (file)
@@ -533,7 +533,13 @@ close_tls_session (http_session_t sess)
 {
   if (sess->tls_session)
     {
-# ifdef HTTP_USE_GNUTLS
+# if HTTP_USE_NTBTLS
+      /* FIXME!!
+         Possibly, ntbtls_get_transport and close those streams.
+         Somehow get SOCK to call my_socket_unref.
+      */
+      ntbtls_release (sess->tls_session);
+# elif HTTP_USE_GNUTLS
       my_socket_t sock = gnutls_transport_get_ptr (sess->tls_session);
       my_socket_unref (sock, NULL, NULL);
       gnutls_deinit (sess->tls_session);
@@ -601,6 +607,8 @@ http_session_new (http_session_t *r_session, const char *tls_priority,
   {
     (void)tls_priority;
 
+    /* ntbtls_set_debug (99, NULL, NULL); */
+
     err = ntbtls_new (&sess->tls_session, NTBTLS_CLIENT);
     if (err)
       {
@@ -1685,8 +1693,36 @@ send_request (http_t hd, const char *httphost, const char *auth,
 #if HTTP_USE_NTBTLS
   if (hd->uri->use_tls)
     {
+      estream_t in, out;
+
       my_socket_ref (hd->sock);
 
+      in = es_fdopen_nc (hd->sock->fd, "rb");
+      if (!in)
+        {
+          err = gpg_error_from_syserror ();
+          xfree (proxy_authstr);
+          return err;
+        }
+
+      out = es_fdopen_nc (hd->sock->fd, "wb");
+      if (!out)
+        {
+          err = gpg_error_from_syserror ();
+          es_fclose (in);
+          xfree (proxy_authstr);
+          return err;
+        }
+
+      err = ntbtls_set_transport (hd->session->tls_session, in, out);
+      if (err)
+        {
+          log_info ("TLS set_transport failed: %s <%s>\n",
+                    gpg_strerror (err), gpg_strsource (err));
+          xfree (proxy_authstr);
+          return err;
+        }
+
       while ((err = ntbtls_handshake (hd->session->tls_session)))
         {
           switch (err)
@@ -2285,7 +2321,7 @@ connect_server (const char *server, unsigned short port,
 {
   gpg_error_t err;
   assuan_fd_t sock = ASSUAN_INVALID_FD;
-  int srvcount = 0;
+  unsigned int srvcount = 0;
   int hostfound = 0;
   int anyhostaddr = 0;
   int srv, connected;
@@ -2323,7 +2359,6 @@ connect_server (const char *server, unsigned short port,
 #endif /*!HASSUAN_SOCK_TOR*/
     }
 
-#ifdef USE_DNS_SRV
   /* Do the SRV thing */
   if (srvtag)
     {
@@ -2342,15 +2377,15 @@ connect_server (const char *server, unsigned short port,
             {
               stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
                               "._tcp."), server);
-              srvcount = getsrv (srvname, &serverlist);
+              err = get_dns_srv (srvname, &serverlist, &srvcount);
+              if (err)
+                log_info ("getting SRV '%s' failed: %s\n",
+                          srvname, gpg_strerror (err));
               xfree (srvname);
+              /* Note that on error SRVCOUNT is zero.  */
             }
        }
     }
-#else
-  (void)flags;
-  (void)srvtag;
-#endif /*USE_DNS_SRV*/
 
   if (!serverlist)
     {
@@ -2513,7 +2548,17 @@ cookie_read (void *cookie, void *buffer, size_t size)
         size = c->content_length;
     }
 
-#ifdef HTTP_USE_GNUTLS
+#if HTTP_USE_NTBTLS
+  if (c->use_tls && c->session && c->session->tls_session)
+    {
+      estream_t in, out;
+
+      ntbtls_get_stream (c->session->tls_session, &in, &out);
+      nread = es_fread (buffer, 1, size, in);
+      log_debug ("TLS network read: %d/%u\n", nread, size);
+    }
+  else
+#elif HTTP_USE_GNUTLS
   if (c->use_tls && c->session && c->session->tls_session)
     {
     again:
@@ -2592,7 +2637,20 @@ cookie_write (void *cookie, const void *buffer_arg, size_t size)
   cookie_t c = cookie;
   int nwritten = 0;
 
-#ifdef HTTP_USE_GNUTLS
+#if HTTP_USE_NTBTLS
+  if (c->use_tls && c->session && c->session->tls_session)
+    {
+      estream_t in, out;
+
+      ntbtls_get_stream (c->session->tls_session, &in, &out);
+      if (size == 0)
+        es_fflush (out);
+      else
+        nwritten = es_fwrite (buffer, 1, size, out);
+      log_debug ("TLS network write: %d/%u\n", nwritten, size);
+    }
+  else
+#elif HTTP_USE_GNUTLS
   if (c->use_tls && c->session && c->session->tls_session)
     {
       int nleft = size;
@@ -2671,7 +2729,15 @@ cookie_close (void *cookie)
   if (!c)
     return 0;
 
-#ifdef HTTP_USE_GNUTLS
+#if HTTP_USE_NTBTLS
+  if (c->use_tls && c->session && c->session->tls_session)
+    {
+      /* FIXME!! Possibly call ntbtls_close_notify for close
+         of write stream.  */
+      my_socket_unref (c->sock, NULL, NULL);
+    }
+  else
+#elif HTTP_USE_GNUTLS
   if (c->use_tls && c->session && c->session->tls_session)
     my_socket_unref (c->sock, send_gnutls_bye, c->session->tls_session);
   else
index 3b5e75d..a6c22f8 100644 (file)
@@ -426,11 +426,9 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
       int refidx;
       int is_pool = 0;
       char *cname;
-#ifdef USE_DNS_SRV
       char *srvrecord;
       struct srventry *srvs;
-      int srvscount;
-#endif /* USE_DNS_SRV */
+      unsigned int srvscount;
 
       reftblsize = 100;
       reftbl = xtrymalloc (reftblsize * sizeof *reftbl);
@@ -447,7 +445,6 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
         }
       hi = hosttable[idx];
 
-#ifdef USE_DNS_SRV
       if (!is_ip_address (name))
         {
           /* Check for SRV records.  */
@@ -459,11 +456,10 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
               return err;
             }
 
-          srvscount = getsrv (srvrecord, &srvs);
+          err = get_dns_srv (srvrecord, &srvs, &srvscount);
           xfree (srvrecord);
-          if (srvscount < 0)
+          if (err)
             {
-              err = gpg_error_from_syserror ();
               xfree (reftbl);
               return err;
             }
@@ -488,7 +484,6 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
               xfree (srvs);
             }
         }
-#endif /* USE_DNS_SRV */
 
       /* Find all A records for this entry and put them into the pool
          list - if any.  */
index b9931a0..b313848 100644 (file)
@@ -654,8 +654,9 @@ ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[])
      only viable solutions are either to have another thread
      responsible for logging the messages or to add an option to the
      wrapper module to do the logging on its own.  Given that we anyway
-     need a way to rip the child process and this is best done using a
-     general ripping thread, that thread can do the logging too. */
+     need a way to reap the child process and this is best done using a
+     general reaping thread, that thread can do the logging too. */
+  ldap_wrapper_launch_thread ();
 
   *reader = NULL;
 
index bc56c2a..2d6bdc1 100644 (file)
@@ -272,7 +272,10 @@ dirmngr_load_swdb (ctrl_t ctrl, int force)
   /* Create the filename of the file with the keys. */
   keyfile_fname = make_filename_try (gnupg_datadir (), "distsigkey.gpg", NULL);
   if (!keyfile_fname)
-    goto leave;
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
 
   /* Fetch the swdb from the web.  */
   err = fetch_file (ctrl, "https://versions.gnupg.org/swdb.lst", &swdb);
index 0088498..a785238 100644 (file)
@@ -2309,13 +2309,19 @@ cmd_getinfo (assuan_context_t ctx, char *line)
     }
   else if (!strcmp (line, "dnsinfo"))
     {
-#if USE_ADNS && HAVE_ADNS_IF_TORMODE
-      assuan_set_okay_line (ctx, "- ADNS with Tor support");
-#elif USE_ADNS
-      assuan_set_okay_line (ctx, "- ADNS w/o Tor support");
+      if (standard_resolver_p ())
+        assuan_set_okay_line
+          (ctx, "- Forced use of System resolver (w/o Tor support)");
+      else
+        {
+#ifdef USE_LIBDNS
+          assuan_set_okay_line (ctx, (recursive_resolver_p ()
+                                      ? "- Libdns recursive resolver"
+                                      : "- Libdns stub resolver"));
 #else
-      assuan_set_okay_line (ctx, "- System resolver w/o Tor support");
+          assuan_set_okay_line (ctx, "- System resolver (w/o Tor support)");
 #endif
+        }
       err = 0;
     }
   else
index 5e8bf22..b087b5e 100644 (file)
@@ -33,6 +33,16 @@ static int verbose;
 static int debug;
 
 
+static void
+init_sockets (void)
+{
+#ifdef HAVE_W32_SYSTEM
+  WSADATA wsadat;
+
+  WSAStartup (0x202, &wsadat);
+#endif
+}
+
 
 int
 main (int argc, char **argv)
@@ -64,14 +74,16 @@ main (int argc, char **argv)
         {
           fputs ("usage: " PGM " [HOST]\n"
                  "Options:\n"
-                 "  --verbose         print timings etc.\n"
-                 "  --debug           flyswatter\n"
-                 "  --use-tor         use Tor\n"
-                 "  --new-circuit     use a new Tor circuit\n"
-                 "  --bracket         enclose v6 addresses in brackets\n"
-                 "  --cert            lookup a CERT RR\n"
-                 "  --srv             lookup a SRV RR\n"
-                 "  --cname           lookup a CNAME RR\n"
+                 "  --verbose           print timings etc.\n"
+                 "  --debug             flyswatter\n"
+                 "  --standard-resolver use the system's resolver\n"
+                 "  --use-tor           use Tor\n"
+                 "  --new-circuit       use a new Tor circuit\n"
+                 "  --bracket           enclose v6 addresses in brackets\n"
+                 "  --cert              lookup a CERT RR\n"
+                 "  --srv               lookup a SRV RR\n"
+                 "  --cname             lookup a CNAME RR\n"
+                 "  --timeout SECONDS   timeout after SECONDS\n"
                  , stdout);
           exit (0);
         }
@@ -96,6 +108,16 @@ main (int argc, char **argv)
           opt_new_circuit = 1;
           argc--; argv++;
         }
+      else if (!strcmp (*argv, "--standard-resolver"))
+        {
+          enable_standard_resolver (1);
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--recursive-resolver"))
+        {
+          enable_recursive_resolver (1);
+          argc--; argv++;
+        }
       else if (!strcmp (*argv, "--bracket"))
         {
           opt_bracket = 1;
@@ -116,6 +138,15 @@ main (int argc, char **argv)
           any_options = opt_cname = 1;
           argc--; argv++;
         }
+      else if (!strcmp (*argv, "--timeout"))
+        {
+          argc--; argv++;
+          if (argc)
+            {
+              set_dns_timeout (atoi (*argv));
+              argc--; argv++;
+            }
+        }
       else if (!strncmp (*argv, "--", 2))
         {
           fprintf (stderr, PGM ": unknown option '%s'\n", *argv);
@@ -136,6 +167,9 @@ main (int argc, char **argv)
       exit (1);
     }
 
+  set_dns_verbose (verbose, debug);
+  init_sockets ();
+
   if (opt_tor)
     {
       err = enable_dns_tormode (opt_new_circuit);
@@ -206,25 +240,32 @@ main (int argc, char **argv)
         {
           printf ("CNAME found: '%s'\n", cname);
         }
-
       xfree (cname);
     }
   else if (opt_srv)
     {
       struct srventry *srv;
-      int rc,i;
+      unsigned int count;
+      int i;
 
-      rc=getsrv (name? name : "_hkp._tcp.wwwkeys.pgp.net", &srv);
-      printf("Count=%d\n",rc);
-      for(i=0;i<rc;i++)
+      err = get_dns_srv (name? name : "_hkp._tcp.wwwkeys.pgp.net",
+                         &srv, &count);
+      if (err)
+        printf ("get_dns_srv failed: %s <%s>\n",
+                gpg_strerror (err), gpg_strsource (err));
+      else
         {
-          printf("priority=%-8hu  ",srv[i].priority);
-          printf("weight=%-8hu  ",srv[i].weight);
-          printf("port=%-5hu  ",srv[i].port);
-          printf("target=%s\n",srv[i].target);
-        }
+          printf ("count=%u\n",count);
+          for (i=0; i < count; i++)
+            {
+              printf("priority=%-8hu  ",srv[i].priority);
+              printf("weight=%-8hu  ",srv[i].weight);
+              printf("port=%-5hu  ",srv[i].port);
+              printf("target=%s\n",srv[i].target);
+            }
 
-      xfree(srv);
+          xfree(srv);
+        }
     }
   else /* Standard lookup.  */
     {
@@ -267,7 +308,7 @@ main (int argc, char **argv)
                                   (opt_bracket? DNS_WITHBRACKET:0),
                                   &host);
           if (err)
-            printf ("[resolve_dns_addr failed (2): %s]", gpg_strerror (err));
+            printf ("  [resolve_dns_addr failed (2): %s]", gpg_strerror (err));
           else
             {
               if (!is_ip_address (host))
@@ -280,6 +321,7 @@ main (int argc, char **argv)
       free_dns_addrinfo (aibuf);
     }
 
+  reload_dns_stuff (1); /* Release objects.  */
 
   return 0;
 }
index a264e53..568500e 100644 (file)
@@ -722,14 +722,22 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
     userid encoded in UTF-8 and percent escaped.  The fingerprint is
     identical for all TOFU_USER lines up to a NEWSIG line.
 
-*** TOFU_STATS <validity> <sign-count> 0 [<policy> [<tm1> <tm2> <tm3> <tm4>]]
+*** TOFU_STATS <MANY_ARGS>
 
     Statistics for the current user id.
 
-    Values for VALIDITY are:
-    - 0 :: conflict
-    - 1 :: key without history
-    - 2 :: key with too little history
+    The <MANY_ARGS> are the usual space delimited arguments.  Here we
+    have too many of them to fit on one printed line and thus they are
+    given on 3 printed lines:
+
+    : <summary> <sign-count> <encryption-count>
+    : [<policy> [<tm1> <tm2> <tm3> <tm4>
+    : [<validity> [<sign-days> <encrypt-days>]]]]
+
+    Values for SUMMARY are:
+    - 0 :: attention, an interaction with the user is required (conflict)
+    - 1 :: key with no verification/encryption history
+    - 2 :: key with little history
     - 3 :: key with enough history for basic trust
     - 4 :: key with a lot of history
 
@@ -739,14 +747,28 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
     - good    :: Policy is "good"
     - bad     :: Policy is "bad"
     - ask     :: Policy is "ask"
-    - unknown :: Policy is not known.
+    - unknown :: Policy is "unknown" (TOFU information does not
+                 contribute to the key's validity)
 
-    TM1 ist the time the first message was verified.  TM2 is the time
+    TM1 is the time the first message was verified.  TM2 is the time
     the most recent message was verified.  TM3 is the time the first
     message was encrypted.  TM4 is the most recent encryption. All may
     either be seconds since Epoch or an ISO time string
     (yyyymmddThhmmss).
 
+    VALIDITY is the same as SUMMARY with the exception that VALIDITY
+    doesn't reflect whether the key needs attention.  That is it never
+    takes on value 0.  Instead, if there is a conflict, VALIDITY still
+    reflects the key's validity (values: 1-4).
+
+    SIGN-COUNT and ENCRYPTION-COUNT are the number of messages that we
+    have seen that have been signed by this key / encryption to this
+    key.
+
+    SIGN-DAYS and ENCRYPTION-DAYS are similar, but the number of days
+    (in UTC) on which we have seen messages signed by this key /
+    encrypted to this key.
+
 *** TOFU_STATS_SHORT <long_string>
 
     Information about the TOFU binding for the signature.
@@ -994,7 +1016,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
     This may either be the specified mountpoint or one randomly
     chosen by g13.
 
-*** PINENTRY_LAUNCHED <pid>
+*** PINENTRY_LAUNCHED <pid>[:<extra>]
     This status line is emitted by gpg to notify a client that a
     Pinentry has been launched.  <pid> is the PID of the Pinentry.  It
     may be used to display a hint to the user but can't be used to
@@ -1306,6 +1328,43 @@ Status codes are:
 
 
 
+* Debug flags
+
+This tables gives the flag values for the --debug option along with
+the alternative names used by the components.
+
+|       | gpg     | gpgsm   | agent   | scd     | dirmngr | g13     | wks     |
+|-------+---------+---------+---------+---------+---------+---------+---------|
+|     1 | packet  | x509    |         |         | x509    | mount   | mime    |
+|     2 | mpi     | mpi     | mpi     | mpi     |         |         | parser  |
+|     4 | crypto  | crypto  | crypto  | crypto  | crypto  | crypto  | crypto  |
+|     8 | filter  |         |         |         |         |         |         |
+|    16 | iobuf   |         |         |         | dns     |         |         |
+|    32 | memory  | memory  | memory  | memory  | memory  | memory  | memory  |
+|    64 | cache   | cache   | cache   | cache   | cache   |         |         |
+|   128 | memstat | memstat | memstat | memstat | memstat | memstat | memstat |
+|   256 | trust   |         |         |         |         |         |         |
+|   512 | hashing | hashing | hashing | hashing | hashing |         |         |
+|  1024 | ipc     | ipc     | ipc     | ipc     | ipc     | ipc     | ipc     |
+|  2048 |         |         |         | cardio  | network |         |         |
+|  4096 | clock   |         |         | reader  |         |         |         |
+|  8192 | lookup  |         |         |         | lookup  |         |         |
+| 16384 | extprog |         |         |         |         |         | extprog |
+
+Description of some debug flags:
+
+  - cardio :: Used by scdaemon to trace the APDUs exchange with the
+              card.
+  - clock  :: Show execution times of certain functions.
+  - crypto :: Trace crypto operations.
+  - hashing :: Create files with the hashed data.
+  - ipc :: Trace the Assuan commands.
+  - mpi :: Show the values of the MPIs.
+  - reader :: Used by scdaemon to trace card reader related code.  For
+              example: Open and close reader.
+
+
+
 * Miscellaneous notes
 
 ** v3 fingerprints
index 53cd639..0c2f2c9 100644 (file)
@@ -28,6 +28,7 @@ examples = examples/README examples/scd-event examples/trustlist.txt  \
           examples/systemd-user/gpg-agent.service                      \
           examples/systemd-user/gpg-agent.socket                       \
           examples/systemd-user/gpg-agent-ssh.socket                   \
+          examples/systemd-user/gpg-agent-browser.socket               \
           examples/systemd-user/gpg-agent-extra.socket                 \
           examples/gpgconf.conf examples/pwpattern.list
 
@@ -39,6 +40,8 @@ helpfiles = help.txt help.be.txt help.ca.txt help.cs.txt              \
             help.pt_BR.txt help.ro.txt help.ru.txt help.sk.txt         \
             help.sv.txt help.tr.txt help.zh_CN.txt help.zh_TW.txt
 
+profiles =
+
 EXTRA_DIST = samplekeys.asc mksamplekeys com-certs.pem qualified.txt \
             gnupg-logo.eps gnupg-logo.pdf gnupg-logo.png gnupg-logo-tr.png \
             gnupg-module-overview.png gnupg-module-overview.pdf \
@@ -53,7 +56,7 @@ BUILT_SOURCES = gnupg-module-overview.png gnupg-module-overview.pdf \
 
 info_TEXINFOS = gnupg.texi
 
-dist_pkgdata_DATA =  $(helpfiles)
+dist_pkgdata_DATA =  $(helpfiles) $(profiles)
 
 nobase_dist_doc_DATA = FAQ DETAILS HACKING DCO TRANSLATE OpenPGP KEYSERVER \
                        $(examples)
index 7fde49a..42a1a15 100644 (file)
@@ -235,7 +235,8 @@ gpg: fatal: WriteConsole failed: Access denied
 @noindent
 The solution is to use the command @command{wineconsole}.
 
-Some operations like gen-key really want to talk to the console directly
+Some operations like @option{--generate-key} really want to talk to
+the console directly
 for increased security (for example to prevent the passphrase from
 appearing on the screen).  So, you should use @command{wineconsole}
 instead of @command{wine}, which will launch a windows console that
index be4e97a..5b4e68b 100644 (file)
@@ -142,10 +142,10 @@ per-user configuration file.  The default configuration file is named
 @item --homedir @var{dir}
 @opindex options
 Set the name of the home directory to @var{dir}.  This option is only
-effective when used on the command line.  The default os
+effective when used on the command line.  The default is
 the directory named @file{.gnupg} directly below the home directory
 of the user unless the environment variable @code{GNUPGHOME} has been set
-in which case its value will be used.  All kind of data is stored below
+in which case its value will be used.  Many kinds of data are stored within
 this directory.
 
 
@@ -244,6 +244,21 @@ this still leaks the DNS queries; e.g. to lookup the hosts in a
 keyserver pool.  Certain other features are disabled if this mode is
 active.
 
+@item --standard-resolver
+@opindex standard-resolver
+This option forces the use of the system's standard DNS resolver code.
+This is mainly used for debugging.  Note that on Windows a standard
+resolver is not used and all DNS access will return the error ``Not
+Implemented'' if this function is used.
+
+@item --recursive-resolver
+@opindex recursive-resolver
+When possible use a recursive resolver instead of a stub resolver.
+
+@item --resolver-timeout @var{n}
+Set the timeout for the DNS resolver to N seconds.  The default are 30
+seconds.
+
 @item --allow-version-check
 @opindex allow-version-check
 Allow Dirmngr to connect to @code{https://versions.gnupg.org} to get
@@ -286,8 +301,7 @@ In ``Tor mode'' Dirmngr uses a public resolver via Tor to resolve DNS
 names.  If the default public resolver, which is @code{8.8.8.8}, shall
 not be used a different one can be given using this option.  Note that
 a numerical IP address must be given (IPv6 or IPv4) and that no error
-checking is done for @var{ipaddr}.  DNS queries in Tor mode do only
-work if GnuPG as been build with ADNS support.
+checking is done for @var{ipaddr}.
 
 @item --disable-ldap
 @opindex disable-ldap
index ec8685a..f401602 100644 (file)
@@ -8,7 +8,7 @@
 # Empty lines and comment lines, indicated by a hash mark as first non
 # white space character, are ignored.  The line is separated by white
 # space into fields. The first field is used to match the user or
-# group and must start at the first column, the file is processes
+# group and must start at the first column, the file is processed
 # sequential until a matching rule is found.  A rule may contain
 # several lines; continuation lines are indicated by a indenting them.
 #
@@ -23,7 +23,7 @@
 #   *           - Matches any user.
 # All other variants are not defined and reserved for future use.
 #
-# <component> and <option> are as specified by gpgconf. 
+# <component> and <option> are as specified by gpgconf.
 # <flag> may be one of:
 #   default     - Delete the option so that the default is used.
 #   no-change   - Mark the field as non changeable by gpgconf.
@@ -35,7 +35,7 @@
 #         gpg-agent min-passphrase-len 6
 #
 # *       gpg-agent min-passphrase-len [no-change] 8
-#         gpg-agent min-passphrase-nonalpha [no-change] 1 
+#         gpg-agent min-passphrase-nonalpha [no-change] 1
 #         gpg-agent max-passphrase-days [no-change] 700
 #         gpg-agent enable-passphrase-history [no-change]
 #         gpg-agent enforce-passphrase-constraints [default]
@@ -44,7 +44,7 @@
 #         gpg-agent max-cache-ttl-ssh [no-change] 10800
 #         gpg-agent allow-mark-trusted [default]
 #         gpg-agent allow-mark-trusted [no-change]
-#         gpgsm     enable-ocsp           
+#         gpgsm     enable-ocsp
 #===========
 # All users in the group "staff" are allowed to change the value for
 # --allow-mark-trusted; gpgconf's default is not to allow a change
index 22d07f0..8fc3775 100644 (file)
@@ -1243,7 +1243,7 @@ update this FAQ in the next month.  See the section "Changes" for recent updates
 
     To generate a secret/public keypair, run:
 
-    : $ gpg --gen-key
+    : $ gpg --generate-key
 
     and choose the default values.
 
index e3756e9..469e548 100644 (file)
 @command{@gpgname} is the OpenPGP part of the GNU Privacy Guard (GnuPG). It
 is a tool to provide digital encryption and signing services using the
 OpenPGP standard. @command{@gpgname} features complete key management and
-all bells and whistles you can expect from a decent OpenPGP
+all the bells and whistles you would expect from a full OpenPGP
 implementation.
 
+There are two main versions of GnuPG: GnuPG 1.x and GnuPG 2.x.  GnuPG
+2.x supports modern encryption algorithms and thus should be preferred
+over GnuPG 1.x.  You only need to use GnuPG 1.x if your platform
+doesn't support GnuPG 2.x, or you need support for some features that
+GnuPG 2.x has deprecated, e.g., decrypting data created with PGP-2
+keys.
+
 @ifclear gpgtwohack
-Note that this version of GnuPG features all modern algorithms and
-should thus be preferred over older GnuPG versions.  If you are
-looking for version 1 of GnuPG, you may find that version installed
-under the name @command{gpg1}.
+If you are looking for version 1 of GnuPG, you may find that version
+installed under the name @command{gpg1}.
 @end ifclear
 @ifset gpgtwohack
-In contrast to the standalone command gpg from GnuPG 1.x, which
-might be better suited for server and embedded platforms, the 2.x
-version is commonly installed under the name @command{@gpgname} and
-targeted to the desktop as it requires several other modules to be
-installed.
+In contrast to the standalone command @command{gpg} from GnuPG 1.x,
+the 2.x version is commonly installed under the name
+@command{@gpgname}.
 @end ifset
 
 @manpause
@@ -106,16 +109,13 @@ Developer information:
 @section Commands
 
 Commands are not distinguished from options except for the fact that
-only one command is allowed.
+only one command is allowed.  Generally speaking, irrelevant options
+are silently ignored, and may not be checked for correctness.
 
-@command{@gpgname} may be run with no commands, in which case it will
+@command{@gpgname} may be run with no commands. In this case it will
 perform a reasonable action depending on the type of file it is given
 as input (an encrypted message is decrypted, a signature is verified,
-a file containing keys is listed).
-
-Please remember that option as well as command parsing stops as soon as
-a non-option is encountered, you can explicitly stop parsing by
-using the special option @option{--}.
+a file containing keys is listed, etc.).
 
 
 @menu
@@ -140,7 +140,7 @@ cannot abbreviate this command.
 @item --help
 @itemx -h
 @opindex help
-Print a usage message summarizing the most useful command line options.
+Print a usage message summarizing the most useful command-line options.
 Note that you cannot abbreviate this command.
 
 @item --warranty
@@ -166,22 +166,24 @@ abbreviate this command.
 @item --sign
 @itemx -s
 @opindex sign
-Make a signature. This command may be combined with @option{--encrypt}
-(for a signed and encrypted message), @option{--symmetric} (for a
-signed and symmetrically encrypted message), or @option{--encrypt} and
-@option{--symmetric} together (for a signed message that may be
-decrypted via a secret key or a passphrase).  The key to be used for
-signing is chosen by default or can be set with the
+Sign a message. This command may be combined with @option{--encrypt}
+(to sign and encrypt a message), @option{--symmetric} (to sign and
+symmetrically encrypt a message), or both @option{--encrypt} and
+@option{--symmetric} (to sign and encrypt a message that can be
+decrypted using a secret key or a passphrase).  The signing key is
+chosen by default or can be set explicitly using the
 @option{--local-user} and @option{--default-key} options.
 
-@item --clearsign
+@item --clear-sign
+@opindex clear-sign
+@itemx --clearsign
 @opindex clearsign
-Make a clear text signature.  The content in a clear text signature is
+Make a cleartext signature.  The content in a cleartext signature is
 readable without any special software. OpenPGP software is only needed
-to verify the signature.  Clear text signatures may modify end-of-line
+to verify the signature.  cleartext signatures may modify end-of-line
 whitespace for platform independence and are not intended to be
-reversible.  The key to be used for signing is chosen by default or
-can be set with the @option{--local-user} and @option{--default-key}
+reversible.  The signing key is chosen by default or can be set
+explicitly using the @option{--local-user} and @option{--default-key}
 options.
 
 
@@ -193,11 +195,11 @@ Make a detached signature.
 @item --encrypt
 @itemx -e
 @opindex encrypt
-Encrypt data. This command may be combined with @option{--sign} (for a
-signed and encrypted message), @option{--symmetric} (for a message that
-may be decrypted via a secret key or a passphrase), or @option{--sign}
-and @option{--symmetric} together (for a signed message that may be
-decrypted via a secret key or a passphrase).
+Encrypt data. This command may be combined with @option{--sign} (to
+sign and encrypt a message), @option{--symmetric} (to encrypt a
+message that can decrypted using a secret key or a passphrase), or
+@option{--sign} and @option{--symmetric} together (for a signed
+message that can be decrypted using a secret key or a passphrase).
 
 @item --symmetric
 @itemx -c
@@ -223,32 +225,33 @@ is specified) and write it to STDOUT (or the file specified with
 @option{--output}). If the decrypted file is signed, the signature is also
 verified. This command differs from the default operation, as it never
 writes to the filename which is included in the file and it rejects
-files which don't begin with an encrypted message.
+files that don't begin with an encrypted message.
 
 @item --verify
 @opindex verify
 Assume that the first argument is a signed file and verify it without
 generating any output.  With no arguments, the signature packet is
-read from STDIN.  If only a one argument is given, it is expected to
-be a complete signature.
+read from STDIN.  If only one argument is given, the specified file is
+expected to include a complete signature.
 
-With more than 1 argument, the first should be a detached signature
-and the remaining files make up the the signed data. To read the signed
-data from STDIN, use @samp{-} as the second filename.  For security
-reasons a detached signature cannot read the signed material from
-STDIN without denoting it in the above way.
+With more than one argument, the first argument should specify a file
+with a detached signature and the remaining files should contain the
+signed data. To read the signed data from STDIN, use @samp{-} as the
+second filename.  For security reasons, a detached signature will not
+read the signed material from STDIN if not explicitly specified.
 
 Note: If the option @option{--batch} is not used, @command{@gpgname}
-may assume that a single argument is a file with a detached signature
+may assume that a single argument is a file with a detached signature,
 and it will try to find a matching data file by stripping certain
 suffixes.  Using this historical feature to verify a detached
-signature is strongly discouraged; always specify the data file too.
+signature is strongly discouraged; you should always specify the data file
+explicitly.
 
-Note: When verifying a cleartext signature, @command{gpg} verifies
+Note: When verifying a cleartext signature, @command{@gpgname} verifies
 only what makes up the cleartext signed data and not any extra data
-outside of the cleartext signature or header lines following directly
+outside of the cleartext signature or the header lines directly following
 the dash marker line.  The option @code{--output} may be used to write
-out the actual signed data; but there are other pitfalls with this
+out the actual signed data, but there are other pitfalls with this
 format as well.  It is suggested to avoid cleartext signatures in
 favor of detached signatures.
 
@@ -277,24 +280,27 @@ Identical to @option{--multifile --decrypt}.
 @itemx -k
 @itemx --list-public-keys
 @opindex list-keys
-List all keys from the public keyrings, or just the keys given on the
-command line.
+List the specified keys.  If no keys are specified, then all keys from
+the configured public keyrings are listed.
 
-Avoid using the output of this command in scripts or other programs as
-it is likely to change as GnuPG changes.  See @option{--with-colons}
-for a machine-parseable key listing command that is appropriate for
-use in scripts and other programs.  Never use the regular output for
-scripts --- it is only for human consumption.
+Never use the output of this command in scripts or other programs.
+The output is intended only for humans and its format is likely to
+change.  The @option{--with-colons} option emits the output in a
+stable, machine-parseable format, which is intended for use by scripts
+and other programs.
 
 @item --list-secret-keys
 @itemx -K
 @opindex list-secret-keys
-List all keys from the secret keyrings, or just the ones given on the
-command line. A @code{#} after the letters @code{sec} means that the
-secret key is not usable (for example, if it was created via
-@option{--export-secret-subkeys}).  See also @option{--list-keys}.
-
-@item --list-sigs
+List the specified secret keys.  If no keys are specified, then all
+known secret keys are listed.  A @code{#} after the letters @code{sec}
+means that the secret key is not usable (for example, if it was
+exported using @option{--export-secret-subkeys}).  See also
+@option{--list-keys}.
+
+@item --list-signatures
+@opindex list-signatures
+@itemx --list-sigs
 @opindex list-sigs
 Same as @option{--list-keys}, but the signatures are listed too.
 This command has the same effect as
@@ -313,9 +319,11 @@ notation (see @option{--cert-notation}), "X" for an eXpired signature
 above to indicate trust signature levels (see the @option{--edit-key}
 command "tsign").
 
-@item --check-sigs
+@item --check-signatures
+@opindex check-signatures
+@itemx --check-sigs
 @opindex check-sigs
-Same as @option{--list-sigs}, but the signatures are verified.  Note
+Same as @option{--list-signatures}, but the signatures are verified.  Note
 that for performance reasons the revocation status of a signing key is
 not shown.
 This command has the same effect as
@@ -323,7 +331,7 @@ using @option{--list-keys} with @option{--with-sig-check}.
 
 The status of the verification is indicated by a flag directly following
 the "sig" tag (and thus before the flags described above for
-@option{--list-sigs}).  A "!" indicates that the signature has been
+@option{--list-signatures}).  A "!" indicates that the signature has been
 successfully verified, a "-" denotes a bad signature and a "%" is used
 if an error occurred while checking the signature (e.g. a non supported
 algorithm).
@@ -341,7 +349,7 @@ be used to locate a key.  Only public keys are listed.
 List all keys (or the specified ones) along with their
 fingerprints. This is the same output as @option{--list-keys} but with
 the additional output of a line with the fingerprint. May also be
-combined with @option{--list-sigs} or @option{--check-sigs}.  If this
+combined with @option{--list-signatures} or @option{--check-signatures}.  If this
 command is given twice, the fingerprints of all secondary keys are
 listed too.  This command also forces pretty printing of fingerprints
 if the keyid format has been set to "none".
@@ -354,7 +362,9 @@ values are dumped and not only their lengths.  Note that the output of
 this command may change with new releases.
 
 
-@item --card-edit
+@item --edit-card
+@opindex edit-card
+@itemx --card-edit
 @opindex card-edit
 Present a menu to work with a smartcard. The subcommand "help" provides
 an overview on available commands. For a detailed description, please
@@ -369,7 +379,7 @@ Show the content of the smart card.
 @opindex change-pin
 Present a menu to allow changing the PIN of a smartcard. This
 functionality is also available as the subcommand "passwd" with the
-@option{--card-edit} command.
+@option{--edit-card} command.
 
 @item --delete-keys @code{name}
 @itemx --delete-keys @code{name}
@@ -382,7 +392,7 @@ safeguard against accidental deletion of multiple keys.
 Remove key from the secret keyring. In batch mode the key must be
 specified by fingerprint.  The option @option{--yes} can be used to
 advice gpg-agent not to request a confirmation.  This extra
-pre-caution is done because @command{gpg} can't be sure that the
+pre-caution is done because @command{@gpgname} can't be sure that the
 secret key (as controlled by gpg-agent) is only used for the given
 OpenPGP public key.
 
@@ -408,7 +418,7 @@ Similar to @option{--export} but sends the keys to a keyserver.
 Fingerprints may be used instead of key IDs. Option @option{--keyserver}
 must be used to give the name of this keyserver. Don't send your
 complete keyring to a keyserver --- select only those keys which are new
-or changed by you.  If no key IDs are given, @command{gpg} does nothing.
+or changed by you.  If no key IDs are given, @command{@gpgname} does nothing.
 
 @item --export-secret-keys
 @itemx --export-secret-subkeys
@@ -417,21 +427,20 @@ or changed by you.  If no key IDs are given, @command{gpg} does nothing.
 Same as @option{--export}, but exports the secret keys instead.  The
 exported keys are written to STDOUT or to the file given with option
 @option{--output}.  This command is often used along with the option
-@option{--armor} to allow easy printing of the key for paper backup;
-however the external tool @command{paperkey} does a better job for
+@option{--armor} to allow for easy printing of the key for paper backup;
+however the external tool @command{paperkey} does a better job of
 creating backups on paper.  Note that exporting a secret key can be a
 security risk if the exported keys are sent over an insecure channel.
 
 The second form of the command has the special property to render the
 secret part of the primary key useless; this is a GNU extension to
 OpenPGP and other implementations can not be expected to successfully
-import such a key.  Its intended use is to generated a full key with
-an additional signing subkey on a dedicated machine and then using
-this command to export the key without the primary key to the main
-machine.
+import such a key.  Its intended use is in generating a full key with
+an additional signing subkey on a dedicated machine.  This command
+then exports the key without the primary key to the main machine.
 
 GnuPG may ask you to enter the passphrase for the key.  This is
-required because the internal protection method of the secret key is
+required, because the internal protection method of the secret key is
 different from the one specified by the OpenPGP protocol.
 
 @item --export-ssh-key
@@ -458,7 +467,9 @@ Most notable here is the @option{--import-options merge-only} option
 which does not insert new keys but does only the merging of new
 signatures, user-IDs and subkeys.
 
-@item --recv-keys @code{key IDs}
+@item --receive-keys @code{key IDs}
+@opindex receive-keys
+@itemx --recv-keys @code{key IDs}
 @opindex recv-keys
 Import the keys with the given key IDs from a keyserver. Option
 @option{--keyserver} must be used to give the name of this keyserver.
@@ -602,10 +613,10 @@ This section explains the main commands for key management.
 
 @table @gnupgtabopt
 
-@item --quick-gen-key @code{user-id} [@code{algo} [@code{usage} [@code{expire}]]]
-@opindex quick-gen-key
+@item --quick-generate-key @code{user-id} [@code{algo} [@code{usage} [@code{expire}]]]
+@opindex quick-generate-key
 This is a simple command to generate a standard key with one user id.
-In contrast to @option{--gen-key} the key is generated directly
+In contrast to @option{--generate-key} the key is generated directly
 without the need to answer a bunch of prompts.  Unless the option
 @option{--yes} is given, the key creation will be canceled if the
 given user id already exists in the keyring.
@@ -620,10 +631,16 @@ created and no prompts are shown.  To specify an expiration date but
 still create a primary and subkey use ``default'' or
 ``future-default'' for @code{algo} and ``default'' for @code{usage}.
 For a description of these optional arguments see the command
-@code{--quick-addkey}.  The @code{usage} accepts also the value
+@code{--quick-add-key}.  The @code{usage} accepts also the value
 ``cert'' which can be used to create a certification only primary key;
 the default is to a create certification and signing key.
 
+The @code{expire} argument can be used to specify an expiration date
+for the key.  Several formats are supported; commonly the ISO
+YYYY-MM-DD format is used.  The values ``never'', ``none'' can be used
+for no expiration date.  Not specifying a value, or using ``-''
+results in a key expiring in a reasonable default interval.
+
 If this command is used with @option{--batch},
 @option{--pinentry-mode} has been set to @code{loopback}, and one of
 the passphrase options (@option{--passphrase},
@@ -632,8 +649,14 @@ supplied passphrase is used for the new key and the agent does not ask
 for it.  To create a key without any protection @code{--passphrase ''}
 may be used.
 
-@item --quick-addkey @code{fpr} [@code{algo} [@code{usage} [@code{expire}]]]
-@opindex quick-addkey
+@item --quick-set-expire @code{fpr} @code{expire}
+@opindex quick-set-expire
+Directly set the expiration time of the primary key to @code{expire}.
+To remove the expiration time @code{0} can be used.
+
+
+@item --quick-add-key @code{fpr} [@code{algo} [@code{usage} [@code{expire}]]]
+@opindex quick-add-key
 Directly add a subkey to the key identified by the fingerprint
 @code{fpr}.  Without the optional arguments an encryption subkey is
 added.  If any of the arguments are given a more specific subkey is
@@ -663,24 +686,30 @@ for the subkey.  Several formats are supported; commonly the ISO
 YYYY-MM-DD format is used.  The values ``never'', ``none'', or ``-''
 can be used for no expiration date.
 
-@item --gen-key
+@item --generate-key
+@opindex generate-key
+@itemx --gen-key
 @opindex gen-key
 Generate a new key pair using the current default parameters.  This is
 the standard command to create a new key.  In addition to the key a
 revocation certificate is created and stored in the
 @file{openpgp-revocs.d} directory below the GnuPG home directory.
 
-@item --full-gen-key
-@opindex gen-key
+@item --full-generate-key
+@opindex full-generate-key
+@itemx --full-gen-key
+@opindex full-gen-key
 Generate a new key pair with dialogs for all options.  This is an
-extended version of @option{--gen-key}.
+extended version of @option{--generate-key}.
 
 There is also a feature which allows you to create keys in batch
 mode. See the manual section ``Unattended key generation'' on how
 to use this.
 
 
-@item --gen-revoke @code{name}
+@item --generate-revocation @code{name}
+@opindex generate-revocation
+@itemx --gen-revoke @code{name}
 @opindex gen-revoke
 Generate a revocation certificate for the complete key.  To only revoke
 a subkey or a key signature, use the @option{--edit} command.
@@ -695,7 +724,9 @@ published, which is best done by sending the key to a keyserver
 to a file which is then send to frequent communication partners.
 
 
-@item --desig-revoke @code{name}
+@item --generate-designated-revocation @code{name}
+@opindex generate-designated-revocation
+@itemx --desig-revoke @code{name}
 @opindex desig-revoke
 Generate a designated revocation certificate for a key. This allows a
 user (with the permission of the keyholder) to revoke someone else's
@@ -899,7 +930,8 @@ signing.
   @opindex keyedit:delkey
   Remove a subkey (secondary key). Note that it is not possible to retract
   a subkey, once it has been send to the public (i.e. to a keyserver).  In
-  that case you better use @code{revkey}.
+  that case you better use @code{revkey}.  Also note that this only
+  deletes the public part of a key.
 
   @item revkey
   @opindex keyedit:revkey
@@ -1038,16 +1070,16 @@ full flexibility of the "sign" subcommand from @option{--edit-key}.
 Its intended use is to help unattended key signing by utilizing a list
 of verified fingerprints.
 
-@item --quick-adduid  @var{user-id} @var{new-user-id}
-@opindex quick-adduid
+@item --quick-add-uid  @var{user-id} @var{new-user-id}
+@opindex quick-add-uid
 This command adds a new user id to an existing key.  In contrast to
 the interactive sub-command @code{adduid} of @option{--edit-key} the
 @var{new-user-id} is added verbatim with only leading and trailing
 white space removed, it is expected to be UTF-8 encoded, and no checks
 on its form are applied.
 
-@item --quick-revuid  @var{user-id} @var{user-id-to-revoke}
-@opindex quick-revuid
+@item --quick-revoke-uid  @var{user-id} @var{user-id-to-revoke}
+@opindex quick-revoke-uid
 This command revokes a User ID on an existing key.  It cannot be used
 to revoke the last User ID on key (some non-revoked User ID must
 remain), with revocation reason ``User ID is no longer valid''.  If
@@ -1055,7 +1087,9 @@ you want to specify a different revocation reason, or to supply
 supplementary revocation text, you should use the interactive
 sub-command @code{revuid} of @option{--edit-key}.
 
-@item --passwd @var{user_id}
+@item --change-passphrase @var{user_id}
+@opindex change-passphrase
+@itemx --passwd @var{user_id}
 @opindex passwd
 Change the passphrase of the secret key belonging to the certificate
 specified as @var{user_id}.  This is a shortcut for the sub-command
@@ -1159,7 +1193,11 @@ filename given on the command line, gpg might still need to read from
 STDIN (in particular if gpg figures that the input is a
 detached signature and no data file has been specified).  Thus if you
 do not want to feed data via STDIN, you should connect STDIN to
-@file{/dev/null}.
+g@file{/dev/null}.
+
+It is highly recommended to use this option along with the options
+@option{--status-fd} and @option{--with-colons} for any unattended of
+@command{gpg}.
 
 @item --no-tty
 @opindex no-tty
@@ -1180,7 +1218,7 @@ Assume "no" on most questions.
 @opindex list-options
 This is a space or comma delimited string that gives options used when
 listing keys and signatures (that is, @option{--list-keys},
-@option{--list-sigs}, @option{--list-public-keys},
+@option{--list-signatures}, @option{--list-public-keys},
 @option{--list-secret-keys}, and the @option{--edit-key} functions).
 Options can be prepended with a @option{no-} (after the two dashes) to
 give the opposite meaning.  The options are:
@@ -1189,7 +1227,7 @@ give the opposite meaning.  The options are:
 
   @item show-photos
   @opindex list-options:show-photos
-  Causes @option{--list-keys}, @option{--list-sigs},
+  Causes @option{--list-keys}, @option{--list-signatures},
   @option{--list-public-keys}, and @option{--list-secret-keys} to
   display any photo IDs attached to the key.  Defaults to no. See also
   @option{--photo-viewer}.  Does not work with @option{--with-colons}:
@@ -1205,7 +1243,7 @@ give the opposite meaning.  The options are:
 
   @item show-policy-urls
   @opindex list-options:show-policy-urls
-  Show policy URLs in the @option{--list-sigs} or @option{--check-sigs}
+  Show policy URLs in the @option{--list-signatures} or @option{--check-signatures}
   listings.  Defaults to no.
 
   @item show-notations
@@ -1215,12 +1253,12 @@ give the opposite meaning.  The options are:
   @opindex list-options:show-std-notations
   @opindex list-options:show-user-notations
   Show all, IETF standard, or user-defined signature notations in the
-  @option{--list-sigs} or @option{--check-sigs} listings. Defaults to no.
+  @option{--list-signatures} or @option{--check-signatures} listings. Defaults to no.
 
   @item show-keyserver-urls
   @opindex list-options:show-keyserver-urls
-  Show any preferred keyserver URL in the @option{--list-sigs} or
-  @option{--check-sigs} listings. Defaults to no.
+  Show any preferred keyserver URL in the @option{--list-signatures} or
+  @option{--check-signatures} listings. Defaults to no.
 
   @item show-uid-validity
   @opindex list-options:show-uid-validity
@@ -1242,8 +1280,8 @@ give the opposite meaning.  The options are:
 
   @item show-sig-expire
   @opindex list-options:show-sig-expire
-  Show signature expiration dates (if any) during @option{--list-sigs} or
-  @option{--check-sigs} listings. Defaults to no.
+  Show signature expiration dates (if any) during @option{--list-signatures} or
+  @option{--check-signatures} listings. Defaults to no.
 
   @item show-sig-subpackets
   @opindex list-options:show-sig-subpackets
@@ -1251,7 +1289,7 @@ give the opposite meaning.  The options are:
   optional argument list of the subpackets to list. If no argument is
   passed, list all subpackets. Defaults to no. This option is only
   meaningful when using @option{--with-colons} along with
-  @option{--list-sigs} or @option{--check-sigs}.
+  @option{--list-signatures} or @option{--check-signatures}.
 
 @end table
 
@@ -1320,7 +1358,7 @@ the opposite meaning. The options are:
 @itemx --disable-large-rsa
 @opindex enable-large-rsa
 @opindex disable-large-rsa
-With --gen-key and --batch, enable the creation of RSA secret keys as
+With --generate-key and --batch, enable the creation of RSA secret keys as
 large as 8192 bit.  Note: 8192 bit is more than is generally
 recommended.  These large keys don't significantly improve security,
 but they are more expensive to use, and their signatures and
@@ -1735,7 +1773,7 @@ This option is deprecated - please use the @option{--keyserver} in
 @file{dirmngr.conf} instead.
 
 Use @code{name} as your keyserver. This is the server that
-@option{--recv-keys}, @option{--send-keys}, and @option{--search-keys}
+@option{--receive-keys}, @option{--send-keys}, and @option{--search-keys}
 will communicate with to receive keys from, send keys to, and search for
 keys on. The format of the @code{name} is a URI:
 `scheme:[//]keyservername[:port]' The scheme is the type of keyserver:
@@ -1803,9 +1841,9 @@ are available for all keyserver types, some common options are:
   Tell the keyserver helper program how long (in seconds) to try and
   perform a keyserver action before giving up. Note that performing
   multiple actions at the same time uses this timeout value per action.
-  For example, when retrieving multiple keys via @option{--recv-keys}, the
+  For example, when retrieving multiple keys via @option{--receive-keys}, the
   timeout applies separately to each key retrieval, and not to the
-  @option{--recv-keys} command as a whole. Defaults to 30 seconds.
+  @option{--receive-keys} command as a whole. Defaults to 30 seconds.
 
   @item http-proxy=@code{value}
   This option is deprecated.
@@ -2028,7 +2066,7 @@ limited countermeasure against traffic analysis. If this option or
 @opindex recipient-file
 This option is similar to @option{--recipient} except that it
 encrypts to a key stored in the given file.  @var{file} must be the
-name of a file containing exactly one key.  @command{gpg} assumes that
+name of a file containing exactly one key.  @command{@gpgname} assumes that
 the key in this file is fully valid.
 
 @item --hidden-recipient-file @var{file}
@@ -2036,7 +2074,7 @@ the key in this file is fully valid.
 @opindex hidden-recipient-file
 This option is similar to @option{--hidden-recipient} except that it
 encrypts to a key stored in the given file.  @var{file} must be the
-name of a file containing exactly one key.  @command{gpg} assumes that
+name of a file containing exactly one key.  @command{@gpgname} assumes that
 the key in this file is fully valid.
 
 @item --encrypt-to @code{name}
@@ -2207,7 +2245,7 @@ opposite meaning. The options are:
   that this cannot completely repair the damaged key as some crucial data
   is removed by the keyserver, but it does at least give you back one
   subkey. Defaults to no for regular @option{--import} and to yes for
-  keyserver @option{--recv-keys}.
+  keyserver @option{--receive-keys}.
 
   @item import-show
   Show a listing of the key as imported right before it is stored.
@@ -2499,7 +2537,7 @@ to safely override the algorithm chosen by the recipient key
 preferences, as GPG will only select an algorithm that is usable by
 all recipients.  The most highly ranked digest algorithm in this list
 is also used when signing without encryption
-(e.g. @option{--clearsign} or @option{--sign}).
+(e.g. @option{--clear-sign} or @option{--sign}).
 
 @item --personal-compress-preferences @code{string}
 @opindex personal-compress-preferences
@@ -2744,7 +2782,7 @@ file @code{file}.
 @item --comment @code{string}
 @itemx --no-comments
 @opindex comment
-Use @code{string} as a comment string in clear text signatures and ASCII
+Use @code{string} as a comment string in cleartext signatures and ASCII
 armored messages or keys (see @option{--armor}). The default behavior is
 not to use a comment string. @option{--comment} may be repeated multiple
 times to get multiple comment strings. @option{--no-comments} removes
@@ -3162,6 +3200,13 @@ letter d (for days), w (for weeks), m (for months), or y (for years)
 (for example "2m" for two months, or "5y" for five years), or an
 absolute date in the form YYYY-MM-DD. Defaults to "0".
 
+@item --default-new-key-algo @var{string}
+@opindex default-new-key-algo @var{string}
+This option can be used to change the default algorithms for key
+generation.  Note that the advanced key generation commands can always
+be used to specify a key algorithm directly.  Please consult the
+source code to learn the syntax of @var{string}.
+
 @item --allow-secret-key-import
 @opindex allow-secret-key-import
 This is an obsolete option and is not used anywhere.
@@ -3228,7 +3273,7 @@ internally used by the @command{gpgconf} tool.
 @opindex gpgconf-test
 This is more or less dummy action.  However it parses the configuration
 file and returns with failure if the configuration file would prevent
-@command{gpg} from startup.  Thus it may be used to run a syntax check
+@command{@gpgname} from startup.  Thus it may be used to run a syntax check
 on the configuration file.
 
 @end table
@@ -3244,7 +3289,7 @@ on the configuration file.
 @item --show-photos
 @itemx --no-show-photos
 @opindex show-photos
-Causes @option{--list-keys}, @option{--list-sigs},
+Causes @option{--list-keys}, @option{--list-signatures},
 @option{--list-public-keys}, @option{--list-secret-keys}, and verifying
 a signature to also display the photo ID attached to the key, if
 any. See also @option{--photo-viewer}. These options are deprecated. Use
@@ -3264,7 +3309,7 @@ Identical to @option{--trust-model always}. This option is deprecated.
 @item --show-notation
 @itemx --no-show-notation
 @opindex show-notation
-Show signature notations in the @option{--list-sigs} or @option{--check-sigs} listings
+Show signature notations in the @option{--list-signatures} or @option{--check-signatures} listings
 as well as when verifying a signature with a notation in it. These
 options are deprecated. Use @option{--list-options [no-]show-notation}
 and/or @option{--verify-options [no-]show-notation} instead.
@@ -3272,7 +3317,7 @@ and/or @option{--verify-options [no-]show-notation} instead.
 @item --show-policy-url
 @itemx --no-show-policy-url
 @opindex show-policy-url
-Show policy URLs in the @option{--list-sigs} or @option{--check-sigs}
+Show policy URLs in the @option{--list-signatures} or @option{--check-signatures}
 listings as well as when verifying a signature with a policy URL in
 it. These options are deprecated. Use @option{--list-options
 [no-]show-policy-url} and/or @option{--verify-options
@@ -3435,8 +3480,8 @@ Operation is further controlled by a few environment variables:
 @item gpg -se -r @code{Bob} @code{file}
 sign and encrypt for user Bob
 
-@item gpg --clearsign @code{file}
-make a clear text signature
+@item gpg --clear-sign @code{file}
+make a cleartext signature
 
 @item gpg -sb @code{file}
 make a detached signature
@@ -3687,23 +3732,74 @@ already been reported to our bug tracker at http://bugs.gnupg.org .
 @node Unattended Usage of GPG
 @section Unattended Usage
 
-@command{gpg} is often used as a backend engine by other software.  To help
+@command{@gpgname} is often used as a backend engine by other software.  To help
 with this a machine interface has been defined to have an unambiguous
 way to do this.  The options @option{--status-fd} and @option{--batch}
 are almost always required for this.
 
 @menu
+* Programmatic use of GnuPG:: Programmatic use of GnuPG
+* Ephemeral home directories:: Ephemeral home directories
+* The quick key manipulation interface:: The quick key manipulation interface
 * Unattended GPG key generation::  Unattended key generation
 @end menu
 
 
+@node Programmatic use of GnuPG
+@subsection Programmatic use of GnuPG
+
+Please consider using GPGME instead of calling @command{@gpgname}
+directly.  GPGME offers a stable, backend-independent interface for
+many cryptographic operations.  It supports OpenPGP and S/MIME, and
+also allows interaction with various GnuPG components.
+
+GPGME provides a C-API, and comes with bindings for C++, Qt, and
+Python.  Bindings for other languages are available.
+
+@node Ephemeral home directories
+@subsection Ephemeral home directories
+
+Sometimes you want to contain effects of some operation, for example
+you want to import a key to inspect it, but you do not want this key
+to be added to your keyring.  In earlier versions of GnuPG, it was
+possible to specify alternate keyring files for both public and secret
+keys.  In modern GnuPG versions, however, we changed how secret keys
+are stored in order to better protect secret key material, and it was
+not possible to preserve this interface.
+
+The preferred way to do this is to use ephemeral home directories.
+This technique works across all versions of GnuPG.
+
+Create a temporary directory, create (or copy) a configuration that
+meets your needs, make @command{@gpgname} use this directory either
+using the environment variable @var{GNUPGHOME}, or the option
+@option{--homedir}.  GPGME supports this too on a per-context basis,
+by modifying the engine info of contexts.  Now execute whatever
+operation you like, import and export key material as necessary.  Once
+finished, you can delete the directory.  All GnuPG backend services
+that were started will detect this and shut down.
+
+@node The quick key manipulation interface
+@subsection The quick key manipulation interface
+
+Recent versions of GnuPG have an interface to manipulate keys without
+using the interactive command @option{--edit-key}.  This interface was
+added mainly for the benefit of GPGME (please consider using GPGME,
+see the manual subsection ``Programmatic use of GnuPG'').  This
+interface is described in the subsection ``How to manage your keys''.
+
 @node Unattended GPG key generation
 @subsection Unattended key generation
 
-The command @option{--gen-key} may be used along with the option
-@option{--batch} for unattended key generation.  The parameters are
-either read from stdin or given as a file on the command line.
-The format of the parameter file is as follows:
+The command @option{--generate-key} may be used along with the option
+@option{--batch} for unattended key generation.  This is the most
+flexible way of generating keys, but it is also the most complex one.
+Consider using the quick key manipulation interface described in the
+previous subsection ``The quick key manipulation interface''.
+
+The parameters for the key are either read from stdin or given as a
+file on the command line.  The format of the parameter file is as
+follows:
 
 @itemize @bullet
   @item Text only, line length is limited to about 1000 characters.
@@ -3746,16 +3842,21 @@ Perform the key generation.  Note that an implicit commit is done at
 the next @asis{Key-Type} parameter.
 
 @item %pubring @var{filename}
-@itemx %secring @var{filename}
 Do not write the key to the default or commandline given keyring but
 to @var{filename}.  This must be given before the first commit to take
 place, duplicate specification of the same filename is ignored, the
 last filename before a commit is used.  The filename is used until a
 new filename is used (at commit points) and all keys are written to
 that file. If a new filename is given, this file is created (and
-overwrites an existing one).  For GnuPG versions prior to 2.1, both
-control statements must be given. For GnuPG 2.1 and later
-@samp{%secring} is a no-op.
+overwrites an existing one).
+
+See the previous subsection ``Ephemeral home directories'' for a more
+robust way to contain side-effects.
+
+@item %secring @var{filename}
+This option is a no-op for GnuPG 2.1 and later.
+
+See the previous subsection ``Ephemeral home directories''.
 
 @item %ask-passphrase
 @itemx %no-ask-passphrase
@@ -3873,8 +3974,9 @@ generation to associate a key parameter block with a status line.
 @end table
 
 @noindent
-Here is an example on how to create a key:
+Here is an example on how to create a key in an ephemeral home directory:
 @smallexample
+$ export GNUPGHOME="$(mktemp -d)"
 $ cat >foo <<EOF
      %echo Generating a basic OpenPGP key
      Key-Type: DSA
@@ -3886,23 +3988,21 @@ $ cat >foo <<EOF
      Name-Email: joe@@foo.bar
      Expire-Date: 0
      Passphrase: abc
-     %pubring foo.pub
-     %secring foo.sec
      # Do a commit here, so that we can later print "done" :-)
      %commit
      %echo done
 EOF
-$ @gpgname --batch --gen-key foo
+$ @gpgname --batch --generate-key foo
  [...]
-$ @gpgname --no-default-keyring --secret-keyring ./foo.sec \
-       --keyring ./foo.pub --list-secret-keys
-/home/wk/work/gnupg-stable/scratch/foo.sec
-------------------------------------------
-sec  1024D/915A878D 2000-03-09 Joe Tester (with stupid passphrase) <joe@@foo.bar>
-ssb  1024g/8F70E2C0 2000-03-09
+$ @gpgname --list-secret-keys
+/tmp/tmp.0NQxB74PEf/pubring.kbx
+-------------------------------
+sec   dsa1024 2016-12-16 [SCA]
+      768E895903FC1C44045C8CB95EEBDB71E9E849D0
+uid           [ultimate] Joe Tester (with stupid passphrase) <joe@@foo.bar>
+ssb   elg1024 2016-12-16 [E]
 @end smallexample
 
-
 @noindent
 If you want to create a key with the default algorithms you would use
 these parameters:
@@ -3915,8 +4015,6 @@ these parameters:
      Name-Email: joe@@foo.bar
      Expire-Date: 0
      Passphrase: abc
-     %pubring foo.pub
-     %secring foo.sec
      # Do a commit here, so that we can later print "done" :-)
      %commit
      %echo done
index e3e852c..b92eaea 100644 (file)
@@ -165,7 +165,9 @@ use @samp{--help} to get a list of supported operations.
 @subsection How to manage the certificates and keys
 
 @table @gnupgtabopt
-@item --gen-key
+@item --generate-key
+@opindex generate-key
+@itemx --gen-key
 @opindex gen-key
 This command allows the creation of a certificate signing request or a
 self-signed certificate.  It is commonly used along with the
@@ -283,7 +285,9 @@ Read information about the private keys from the smartcard and import
 the certificates from there.  This command utilizes the @command{gpg-agent}
 and in turn the @command{scdaemon}.
 
-@item --passwd @var{user_id}
+@item --change-passphrase @var{user_id}
+@opindex change-passphrase
+@itemx --passwd @var{user_id}
 @opindex passwd
 Change the passphrase of the private key belonging to the certificate
 specified as @var{user_id}.  Note, that changing the passphrase/PIN of a
@@ -1005,7 +1009,7 @@ this is a missing certificate.
 @node CSR and certificate creation
 @subsection CSR and certificate creation
 
-The command @option{--gen-key} may be used along with the option
+The command @option{--generate-key} may be used along with the option
 @option{--batch} to either create a certificate signing request (CSR)
 or an X.509 certificate.  This is controlled by a parameter file; the
 format of this file is as follows:
index ea0ce71..55f1a91 100644 (file)
@@ -12,7 +12,7 @@ is no need for a configuration file, you may simply enter:
 
 @cartouche
 @example
-  $ gpgsm --gen-key >example.com.cert-req.pem
+  $ gpgsm --generate-key >example.com.cert-req.pem
   Please select what kind of key you want:
      (1) RSA
      (2) Existing key
index b1ed615..d321b69 100644 (file)
@@ -279,6 +279,15 @@ Change the options of the component @var{component}.
 @item --check-options @var{component}
 Check the options for the component @var{component}.
 
+@item --apply-profile @var{file}
+Apply the configuration settings listed in @var{file} to the
+configuration files.  If @var{file} has no suffix and no slashes the
+command first tries to read a file with the suffix @code{.prf} from
+the the data directory (@code{gpgconf --list-dirs datadir}) before it
+reads the file verbatim.  A profile is divided into sections using the
+bracketed  component name.  Each section then lists the option which
+shall go into the respective configuration file.
+
 @item --apply-defaults
 Update all configuration files with values taken from the global
 configuration file (usually @file{/etc/gnupg/gpgconf.conf}).
index c80e902..58d8e01 100644 (file)
@@ -1026,17 +1026,20 @@ armor_filter( void *opaque, int control,
     if( control == IOBUFCTRL_UNDERFLOW && afx->inp_bypass ) {
        n = 0;
        if( afx->buffer_len ) {
+            /* Copy the data from AFX->BUFFER to BUF.  */
            for(; n < size && afx->buffer_pos < afx->buffer_len; n++ )
                buf[n++] = afx->buffer[afx->buffer_pos++];
            if( afx->buffer_pos >= afx->buffer_len )
                afx->buffer_len = 0;
        }
+        /* If there is still space in BUF, read directly into it.  */
        for(; n < size; n++ ) {
            if( (c=iobuf_get(a)) == -1 )
                break;
            buf[n] = c & 0xff;
        }
        if( !n )
+            /* We didn't get any data.  EOF.  */
            rc = -1;
        *ret_len = n;
     }
index 1d4bd66..335e12a 100644 (file)
@@ -624,7 +624,8 @@ learn_status_cb (void *opaque, const char *line)
         parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10);
       else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
                || algo == PUBKEY_ALGO_EDDSA)
-        parm->key_attr[keyno].curve = openpgp_is_curve_supported (line+n, NULL);
+        parm->key_attr[keyno].curve = openpgp_is_curve_supported (line + n,
+                                                                  NULL, NULL);
     }
   else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11)
            && strchr("1234", keyword[11]))
@@ -659,7 +660,7 @@ agent_scd_learn (struct agent_card_info_s *info, int force)
      care about the data returned.  If the card has already been
      initialized, this is a very fast command.  The main reason we
      need to do this here is to handle a card removed case so that an
-     "l" command in --card-edit can be used to show ta newly inserted
+     "l" command in --edit-card can be used to show ta newly inserted
      card.  We request the openpgp card because that is what we
      expect. */
   rc = assuan_transact (agent_ctx, "SCD SERIALNO openpgp",
index c20c3f4..0133cad 100644 (file)
--- a/g10/cpr.c
+++ b/g10/cpr.c
@@ -405,7 +405,7 @@ myread(int fd, void *buf, size_t count)
 
 /* Request a string from the client over the command-fd.  If GETBOOL
    is set the function returns a static string (do not free) if the
-   netered value was true or NULL if the entered value was false.  */
+   entered value was true or NULL if the entered value was false.  */
 static char *
 do_get_from_fd ( const char *keyword, int hidden, int getbool )
 {
index 751b7be..c99f064 100644 (file)
@@ -180,7 +180,6 @@ void
 decrypt_messages (ctrl_t ctrl, int nfiles, char *files[])
 {
   IOBUF fp;
-  armor_filter_context_t *afx = NULL;
   progress_filter_context_t *pfx;
   char *p, *output = NULL;
   int rc=0,use_stdin=0;
@@ -254,8 +253,11 @@ decrypt_messages (ctrl_t ctrl, int nfiles, char *files[])
         {
           if (use_armor_filter(fp))
             {
-              afx = new_armor_context ();
-              push_armor_filter ( afx, fp );
+              armor_filter_context_t *afx = new_armor_context ();
+              rc = push_armor_filter (afx, fp);
+              if (rc)
+                log_error("failed to push armor filter");
+              release_armor_context (afx);
             }
         }
       rc = proc_packets (ctrl,NULL, fp);
@@ -275,6 +277,5 @@ decrypt_messages (ctrl_t ctrl, int nfiles, char *files[])
     }
 
   set_next_passphrase(NULL);
-  release_armor_context (afx);
   release_progress_context (pfx);
 }
index 8ad154a..17de53f 100644 (file)
Binary files a/g10/distsigkey.gpg and b/g10/distsigkey.gpg differ
index 6a5597c..ad42b41 100644 (file)
@@ -1144,9 +1144,9 @@ print_status_exported (PKT_public_key *pk)
 /*
  * Receive a secret key from agent specified by HEXGRIP.
  *
- * Since the key data from agant is encrypted, decrypt it by CIPHERHD.
- * Then, parse the decrypted key data in transfer format, and put
- * secret parameters into PK.
+ * Since the key data from the agent is encrypted, decrypt it using
+ * CIPHERHD context.  Then, parse the decrypted key data into transfer
+ * format, and put secret parameters into PK.
  *
  * If CLEARTEXT is 0, store the secret key material
  * passphrase-protected.  Otherwise, store secret key material in the
index f0e33c5..e39de28 100644 (file)
@@ -1543,7 +1543,7 @@ pubkey_cmp (ctrl_t ctrl, const char *name, struct pubkey_cmp_cookie *old,
 
       new->uid = scopy_user_id (uid);
       new->validity =
-        get_validity (ctrl, &new->key, uid, NULL, 0) & TRUST_MASK;
+        get_validity (ctrl, new_keyblock, &new->key, uid, NULL, 0) & TRUST_MASK;
       new->valid = 1;
 
       if (! old->valid)
@@ -2178,7 +2178,7 @@ getkey_next (getkey_ctx_t ctx, PKT_public_key *pk, kbnode_t *ret_keyblock)
   if (pk && ret_keyblock == NULL)
       ret_keyblock = &keyblock;
 
-  rc = lookup (ctx, ret_keyblock, &found_key, ctx->want_secret);
+  rc = lookup (ctx, ret_keyblock, pk ? &found_key : NULL, ctx->want_secret);
   if (!rc && pk)
     {
       log_assert (found_key);
@@ -4153,10 +4153,8 @@ parse_auto_key_locate (char *options)
        akl->type = AKL_LDAP;
       else if (ascii_strcasecmp (tok, "keyserver") == 0)
        akl->type = AKL_KEYSERVER;
-#ifdef USE_DNS_CERT
       else if (ascii_strcasecmp (tok, "cert") == 0)
        akl->type = AKL_CERT;
-#endif
       else if (ascii_strcasecmp (tok, "pka") == 0)
        akl->type = AKL_PKA;
       else if (ascii_strcasecmp (tok, "dane") == 0)
index c54facb..8c5b505 100644 (file)
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -123,6 +123,7 @@ enum cmd_and_opt_values
     aQuickAddUid,
     aQuickAddKey,
     aQuickRevUid,
+    aQuickSetExpire,
     aListConfig,
     aListGcryptConfig,
     aGPGConfList,
@@ -407,6 +408,7 @@ enum cmd_and_opt_values
     oPrintDANERecords,
     oTOFUDefaultPolicy,
     oTOFUDBFormat,
+    oDefaultNewKeyAlgo,
     oWeakDigest,
     oUnwrap,
     oOnlySignTextIDs,
@@ -422,7 +424,8 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_group (300, N_("@Commands:\n ")),
 
   ARGPARSE_c (aSign, "sign", N_("make a signature")),
-  ARGPARSE_c (aClearsign, "clearsign", N_("make a clear text signature")),
+  ARGPARSE_c (aClearsign, "clear-sign", N_("make a clear text signature")),
+  ARGPARSE_c (aClearsign, "clearsign", "@"),
   ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")),
   ARGPARSE_c (aEncr, "encrypt",   N_("encrypt data")),
   ARGPARSE_c (aEncrFiles, "encrypt-files", "@"),
@@ -434,22 +437,35 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_c (aVerifyFiles, "verify-files" , "@" ),
   ARGPARSE_c (aListKeys, "list-keys", N_("list keys")),
   ARGPARSE_c (aListKeys, "list-public-keys", "@" ),
-  ARGPARSE_c (aListSigs, "list-sigs", N_("list keys and signatures")),
-  ARGPARSE_c (aCheckKeys, "check-sigs",N_("list and check key signatures")),
+  ARGPARSE_c (aListSigs, "list-signatures", N_("list keys and signatures")),
+  ARGPARSE_c (aListSigs, "list-sigs", "@"),
+  ARGPARSE_c (aCheckKeys, "check-signatures",
+             N_("list and check key signatures")),
+  ARGPARSE_c (aCheckKeys, "check-sigs", "@"),
   ARGPARSE_c (oFingerprint, "fingerprint", N_("list keys and fingerprints")),
   ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")),
-  ARGPARSE_c (aKeygen,     "gen-key",
+  ARGPARSE_c (aKeygen,     "generate-key",
               N_("generate a new key pair")),
-  ARGPARSE_c (aQuickKeygen, "quick-gen-key" ,
+  ARGPARSE_c (aKeygen,     "gen-key", "@"),
+  ARGPARSE_c (aQuickKeygen, "quick-generate-key" ,
               N_("quickly generate a new key pair")),
-  ARGPARSE_c (aQuickAddUid,  "quick-adduid",
+  ARGPARSE_c (aQuickKeygen, "quick-gen-key", "@"),
+  ARGPARSE_c (aQuickAddUid,  "quick-add-uid",
               N_("quickly add a new user-id")),
+  ARGPARSE_c (aQuickAddUid,  "quick-adduid", "@"),
+  ARGPARSE_c (aQuickAddKey,  "quick-add-key", "@"),
   ARGPARSE_c (aQuickAddKey,  "quick-addkey", "@"),
-  ARGPARSE_c (aQuickRevUid,  "quick-revuid",
+  ARGPARSE_c (aQuickRevUid,  "quick-revoke-uid",
               N_("quickly revoke a user-id")),
-  ARGPARSE_c (aFullKeygen,  "full-gen-key" ,
+  ARGPARSE_c (aQuickRevUid,  "quick-revuid", "@"),
+  ARGPARSE_c (aQuickSetExpire,  "quick-set-expire",
+              N_("quickly set a new expiration date")),
+  ARGPARSE_c (aFullKeygen,  "full-generate-key" ,
               N_("full featured key pair generation")),
-  ARGPARSE_c (aGenRevoke, "gen-revoke",N_("generate a revocation certificate")),
+  ARGPARSE_c (aFullKeygen,  "full-gen-key", "@"),
+  ARGPARSE_c (aGenRevoke, "generate-revocation",
+             N_("generate a revocation certificate")),
+  ARGPARSE_c (aGenRevoke, "gen-revoke", "@"),
   ARGPARSE_c (aDeleteKeys,"delete-keys",
               N_("remove keys from the public keyring")),
   ARGPARSE_c (aDeleteSecretKeys, "delete-secret-keys",
@@ -462,11 +478,14 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_c (aLSignKey, "lsign-key"  ,N_("sign a key locally")),
   ARGPARSE_c (aEditKey,  "edit-key"   ,N_("sign or edit a key")),
   ARGPARSE_c (aEditKey,  "key-edit"   ,"@"),
-  ARGPARSE_c (aPasswd,   "passwd",     N_("change a passphrase")),
+  ARGPARSE_c (aPasswd,   "change-passphrase", N_("change a passphrase")),
+  ARGPARSE_c (aPasswd,   "passwd", "@"),
+  ARGPARSE_c (aDesigRevoke, "generate-designated-revocation", "@"),
   ARGPARSE_c (aDesigRevoke, "desig-revoke","@" ),
   ARGPARSE_c (aExport, "export"           , N_("export keys") ),
   ARGPARSE_c (aSendKeys, "send-keys"     , N_("export keys to a keyserver") ),
-  ARGPARSE_c (aRecvKeys, "recv-keys"     , N_("import keys from a keyserver") ),
+  ARGPARSE_c (aRecvKeys, "receive-keys" , N_("import keys from a keyserver") ),
+  ARGPARSE_c (aRecvKeys, "recv-keys"     , "@"),
   ARGPARSE_c (aSearchKeys, "search-keys" ,
               N_("search for keys on a keyserver") ),
   ARGPARSE_c (aRefreshKeys, "refresh-keys",
@@ -480,7 +499,8 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_c (aFastImport, "fast-import", "@"),
 #ifdef ENABLE_CARD_SUPPORT
   ARGPARSE_c (aCardStatus,  "card-status", N_("print the card status")),
-  ARGPARSE_c (aCardEdit,   "card-edit",  N_("change data on a card")),
+  ARGPARSE_c (aCardEdit,   "edit-card",  N_("change data on a card")),
+  ARGPARSE_c (aCardEdit,   "card-edit", "@"),
   ARGPARSE_c (aChangePIN,  "change-pin", N_("change a card's PIN")),
 #endif
   ARGPARSE_c (aListConfig, "list-config", "@"),
@@ -656,7 +676,7 @@ static ARGPARSE_OPTS opts[] = {
 
   ARGPARSE_group (303, N_("@\nExamples:\n\n"
     " -se -r Bob [file]          sign and encrypt for user Bob\n"
-    " --clearsign [file]         make a clear text signature\n"
+    " --clear-sign [file]        make a clear text signature\n"
     " --detach-sign [file]       make a detached signature\n"
     " --list-keys [names]        show keys\n"
     " --fingerprint [names]      show fingerprints\n")),
@@ -836,6 +856,8 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_n (oNoAllowMultipleMessages, "no-allow-multiple-messages", "@"),
   ARGPARSE_s_n (oAllowWeakDigestAlgos, "allow-weak-digest-algos", "@"),
 
+  ARGPARSE_s_s (oDefaultNewKeyAlgo, "default-new-key-algo", "@"),
+
   /* These two are aliases to help users of the PGP command line
      product use gpg with minimal pain.  Many commands are common
      already as they seem to have borrowed commands from us.  Now I'm
@@ -891,7 +913,6 @@ static struct debug_flags_s debug_flags [] =
     { DBG_MEMSTAT_VALUE, "memstat" },
     { DBG_TRUST_VALUE  , "trust"   },
     { DBG_HASHING_VALUE, "hashing" },
-    { DBG_CARD_IO_VALUE, "cardio"  },
     { DBG_IPC_VALUE    , "ipc"     },
     { DBG_CLOCK_VALUE  , "clock"   },
     { DBG_LOOKUP_VALUE , "lookup"  },
@@ -1751,6 +1772,15 @@ list_config(char *items)
          any=1;
        }
 
+      if(show_all || ascii_strcasecmp (name, "compressname") == 0)
+       {
+         es_printf ("cfg:compressname:");
+         print_algo_names (check_compress_algo,
+                           compress_algo_to_string);
+         es_printf ("\n");
+         any=1;
+       }
+
       if (show_all || !ascii_strcasecmp(name,"ccid-reader-id"))
        {
           /* We ignore this for GnuPG 1.4 backward compatibility.  */
@@ -1813,11 +1843,13 @@ gpgconf_list (const char *configfile)
   es_printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE);
   es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
   es_printf ("group:%lu:\n", GC_OPT_FLAG_NONE);
+  es_printf ("compliance:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, "gnupg");
+  es_printf ("default-new-key-algo:%lu:\n", GC_OPT_FLAG_NONE);
 
   /* The next one is an info only item and should match the macros at
      the top of keygen.c  */
   es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT,
-             "RSA-2048");
+             get_default_pubkey_algo ());
 
   xfree (configfile_esc);
 }
@@ -2546,6 +2578,7 @@ main (int argc, char **argv)
          case aQuickAddUid:
          case aQuickAddKey:
          case aQuickRevUid:
+         case aQuickSetExpire:
          case aExportOwnerTrust:
          case aImportOwnerTrust:
           case aRebuildKeydbCaches:
@@ -3304,9 +3337,11 @@ main (int argc, char **argv)
           case oAllowSecretKeyImport: /* obsolete */ break;
          case oTryAllSecrets: opt.try_all_secrets = 1; break;
           case oTrustedKey: register_trusted_key( pargs.r.ret_str ); break;
+
           case oEnableSpecialFilenames:
-            iobuf_enable_special_filenames (1);
+            enable_special_filenames ();
             break;
+
           case oNoExpensiveTrustChecks: opt.no_expensive_trust_checks=1; break;
           case oAutoCheckTrustDB: opt.no_auto_check_trustdb=0; break;
           case oNoAutoCheckTrustDB: opt.no_auto_check_trustdb=1; break;
@@ -3467,6 +3502,10 @@ main (int argc, char **argv)
 
           case oNoAutostart: opt.autostart = 0; break;
 
+         case oDefaultNewKeyAlgo:
+            opt.def_new_key_algo = pargs.r.ret_str;
+            break;
+
          case oNoop: break;
 
          default:
@@ -3727,7 +3766,7 @@ main (int argc, char **argv)
            cmdname="--sign --encrypt";
            break;
          case aClearsign:
-           cmdname="--clearsign";
+           cmdname="--clear-sign";
            break;
          case aDetachedSign:
            cmdname="--detach-sign";
@@ -3972,7 +4011,7 @@ main (int argc, char **argv)
 
       case aStore: /* only store the file */
        if( argc > 1 )
-           wrong_args(_("--store [filename]"));
+           wrong_args("--store [filename]");
        if( (rc = encrypt_store(fname)) )
           {
             write_status_failure ("store", rc);
@@ -3982,7 +4021,7 @@ main (int argc, char **argv)
        break;
       case aSym: /* encrypt the given file only with the symmetric cipher */
        if( argc > 1 )
-           wrong_args(_("--symmetric [filename]"));
+           wrong_args("--symmetric [filename]");
        if( (rc = encrypt_symmetric(fname)) )
           {
             write_status_failure ("symencrypt", rc);
@@ -3997,7 +4036,7 @@ main (int argc, char **argv)
        else
          {
            if( argc > 1 )
-             wrong_args(_("--encrypt [filename]"));
+             wrong_args("--encrypt [filename]");
            if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 0, NULL, -1)) )
               {
                 write_status_failure ("encrypt", rc);
@@ -4013,7 +4052,7 @@ main (int argc, char **argv)
           might work with 7, but alas, I don't have a copy to test
           with right now. */
        if( argc > 1 )
-         wrong_args(_("--symmetric --encrypt [filename]"));
+         wrong_args("--symmetric --encrypt [filename]");
        else if(opt.s2k_mode==0)
          log_error(_("you cannot use --symmetric --encrypt"
                      " with --s2k-mode 0\n"));
@@ -4039,7 +4078,7 @@ main (int argc, char **argv)
        }
        else {
            if( argc > 1 )
-               wrong_args(_("--sign [filename]"));
+               wrong_args("--sign [filename]");
            if( argc ) {
                sl = xmalloc_clear( sizeof *sl + strlen(fname));
                strcpy(sl->d, fname);
@@ -4055,7 +4094,7 @@ main (int argc, char **argv)
 
       case aSignEncr: /* sign and encrypt the given file */
        if( argc > 1 )
-           wrong_args(_("--sign --encrypt [filename]"));
+           wrong_args("--sign --encrypt [filename]");
        if( argc ) {
            sl = xmalloc_clear( sizeof *sl + strlen(fname));
            strcpy(sl->d, fname);
@@ -4073,7 +4112,7 @@ main (int argc, char **argv)
 
       case aSignEncrSym: /* sign and encrypt the given file */
        if( argc > 1 )
-           wrong_args(_("--symmetric --sign --encrypt [filename]"));
+           wrong_args("--symmetric --sign --encrypt [filename]");
        else if(opt.s2k_mode==0)
          log_error(_("you cannot use --symmetric --sign --encrypt"
                      " with --s2k-mode 0\n"));
@@ -4102,7 +4141,7 @@ main (int argc, char **argv)
 
       case aSignSym: /* sign and conventionally encrypt the given file */
        if (argc > 1)
-           wrong_args(_("--sign --symmetric [filename]"));
+           wrong_args("--sign --symmetric [filename]");
        rc = sign_symencrypt_file (ctrl, fname, locusr);
         if (rc)
           {
@@ -4114,11 +4153,11 @@ main (int argc, char **argv)
 
       case aClearsign: /* make a clearsig */
        if( argc > 1 )
-           wrong_args(_("--clearsign [filename]"));
+           wrong_args("--clear-sign [filename]");
        if( (rc = clearsign_file (ctrl, fname, locusr, NULL)) )
           {
             write_status_failure ("sign", rc);
-           log_error("%s: clearsign failed: %s\n",
+           log_error("%s: clear-sign failed: %s\n",
                       print_fname_stdin(fname), gpg_strerror (rc) );
           }
        break;
@@ -4144,7 +4183,7 @@ main (int argc, char **argv)
        else
          {
            if( argc > 1 )
-             wrong_args(_("--decrypt [filename]"));
+             wrong_args("--decrypt [filename]");
            if( (rc = decrypt_message (ctrl, fname) ))
               {
                 write_status_failure ("decrypt", rc);
@@ -4171,11 +4210,11 @@ main (int argc, char **argv)
 
       case aSignKey:
        if( argc != 1 )
-         wrong_args(_("--sign-key user-id"));
+         wrong_args("--sign-key user-id");
        /* fall through */
       case aLSignKey:
        if( argc != 1 )
-         wrong_args(_("--lsign-key user-id"));
+         wrong_args("--lsign-key user-id");
        /* fall through */
 
        sl=NULL;
@@ -4196,7 +4235,7 @@ main (int argc, char **argv)
 
       case aEditKey: /* Edit a key signature */
        if( !argc )
-           wrong_args(_("--edit-key user-id [commands]"));
+           wrong_args("--edit-key user-id [commands]");
        username = make_username( fname );
        if( argc > 1 ) {
            sl = NULL;
@@ -4212,7 +4251,7 @@ main (int argc, char **argv)
 
       case aPasswd:
         if (argc != 1)
-          wrong_args (_("--passwd <user-id>"));
+          wrong_args("--change-passphrase <user-id>");
         else
           {
             username = make_username (fname);
@@ -4265,7 +4304,7 @@ main (int argc, char **argv)
           const char *x_algo, *x_usage, *x_expire;
 
           if (argc < 1 || argc > 4)
-            wrong_args("--quick-gen-key USER-ID [ALGO [USAGE [EXPIRE]]]");
+            wrong_args("--quick-generate-key USER-ID [ALGO [USAGE [EXPIRE]]]");
           username = make_username (fname);
           argv++, argc--;
           x_algo = "";
@@ -4291,20 +4330,20 @@ main (int argc, char **argv)
       case aKeygen: /* generate a key */
        if( opt.batch ) {
            if( argc > 1 )
-               wrong_args("--gen-key [parameterfile]");
+               wrong_args("--generate-key [parameterfile]");
            generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0);
        }
        else {
             if (opt.command_fd != -1 && argc)
               {
                 if( argc > 1 )
-                  wrong_args("--gen-key [parameterfile]");
+                  wrong_args("--generate-key [parameterfile]");
 
                 opt.batch = 1;
                 generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0);
               }
             else if (argc)
-              wrong_args ("--gen-key");
+              wrong_args ("--generate-key");
             else
               generate_keypair (ctrl, 0, NULL, NULL, 0);
        }
@@ -4314,13 +4353,13 @@ main (int argc, char **argv)
        if (opt.batch)
           {
            if (argc > 1)
-              wrong_args ("--full-gen-key [parameterfile]");
+              wrong_args ("--full-generate-key [parameterfile]");
            generate_keypair (ctrl, 1, argc? *argv : NULL, NULL, 0);
           }
        else
           {
            if (argc)
-              wrong_args("--full-gen-key");
+              wrong_args("--full-generate-key");
            generate_keypair (ctrl, 1, NULL, NULL, 0);
        }
        break;
@@ -4330,7 +4369,7 @@ main (int argc, char **argv)
           const char *uid, *newuid;
 
           if (argc != 2)
-            wrong_args ("--quick-adduid USER-ID NEW-USER-ID");
+            wrong_args ("--quick-add-uid USER-ID NEW-USER-ID");
           uid = *argv++; argc--;
           newuid = *argv++; argc--;
           keyedit_quick_adduid (ctrl, uid, newuid);
@@ -4342,7 +4381,7 @@ main (int argc, char **argv)
           const char *x_fpr, *x_algo, *x_usage, *x_expire;
 
           if (argc < 1 || argc > 4)
-            wrong_args ("--quick-addkey FINGERPRINT [ALGO [USAGE [EXPIRE]]]");
+            wrong_args ("--quick-add-key FINGERPRINT [ALGO [USAGE [EXPIRE]]]");
           x_fpr = *argv++; argc--;
           x_algo = "";
           x_usage = "";
@@ -4368,13 +4407,25 @@ main (int argc, char **argv)
           const char *uid, *uidtorev;
 
           if (argc != 2)
-            wrong_args ("--quick-revuid USER-ID USER-ID-TO-REVOKE");
+            wrong_args ("--quick-revoke-uid USER-ID USER-ID-TO-REVOKE");
           uid = *argv++; argc--;
           uidtorev = *argv++; argc--;
           keyedit_quick_revuid (ctrl, uid, uidtorev);
         }
        break;
 
+      case aQuickSetExpire:
+        {
+          const char *x_fpr, *x_expire;
+
+          if (argc != 2)
+            wrong_args ("--quick-set-exipre FINGERPRINT EXPIRE");
+          x_fpr = *argv++; argc--;
+          x_expire = *argv++; argc--;
+          keyedit_quick_set_expire (ctrl, x_fpr, x_expire);
+        }
+       break;
+
       case aFastImport:
         opt.import_options |= IMPORT_FAST;
       case aImport:
@@ -4503,7 +4554,7 @@ main (int argc, char **argv)
 
       case aGenRevoke:
        if( argc != 1 )
-           wrong_args("--gen-revoke user-id");
+           wrong_args("--generate-revocation user-id");
        username =  make_username(*argv);
        gen_revoke( username );
        xfree( username );
@@ -4511,7 +4562,7 @@ main (int argc, char **argv)
 
       case aDesigRevoke:
        if (argc != 1)
-           wrong_args ("--desig-revoke user-id");
+           wrong_args ("--generate-designated-revocation user-id");
        username = make_username (*argv);
        gen_desig_revoke (ctrl, username, locusr);
        xfree (username);
@@ -4820,6 +4871,8 @@ main (int argc, char **argv)
              merge_keys_and_selfsig (kb);
              if (tofu_set_policy (ctrl, kb, policy))
                g10_exit (1);
+
+              release_kbnode (kb);
            }
 
           tofu_end_batch_update (ctrl);
@@ -4832,7 +4885,7 @@ main (int argc, char **argv)
       case aListPackets:
       default:
        if( argc > 1 )
-           wrong_args(_("[filename]"));
+           wrong_args("[filename]");
        /* Issue some output for the unix newbie */
        if (!fname && !opt.outfile
             && gnupg_isatty (fileno (stdin))
index d9f2898..bd16b39 100644 (file)
@@ -204,7 +204,7 @@ main( int argc, char **argv )
           break;
         case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break;
         case oEnableSpecialFilenames:
-          iobuf_enable_special_filenames (1);
+          enable_special_filenames ();
           break;
         default : pargs.err = ARGPARSE_PRINT_ERROR; break;
        }
@@ -292,19 +292,22 @@ check_trustdb_stale (ctrl_t ctrl)
 }
 
 int
-get_validity_info (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid)
+get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk,
+                   PKT_user_id *uid)
 {
   (void)ctrl;
+  (void)kb;
   (void)pk;
   (void)uid;
   return '?';
 }
 
 unsigned int
-get_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid,
+get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid,
               PKT_signature *sig, int may_ask)
 {
   (void)ctrl;
+  (void)kb;
   (void)pk;
   (void)uid;
   (void)sig;
@@ -713,3 +716,12 @@ tofu_end_batch_update (ctrl_t ctrl)
 {
   (void)ctrl;
 }
+
+gpg_error_t
+tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb)
+{
+  (void) ctrl;
+  (void) kb;
+
+  return 0;
+}
index 590959d..1ed11bf 100644 (file)
@@ -111,7 +111,8 @@ static int import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
                               struct import_stats_s *stats, int batch,
                               unsigned int options, int for_migration,
                               import_screener_t screener, void *screener_arg);
-static int import_revoke_cert (kbnode_t node, struct import_stats_s *stats);
+static int import_revoke_cert (ctrl_t ctrl,
+                               kbnode_t node, struct import_stats_s *stats);
 static int chk_self_sigs (kbnode_t keyblock, u32 *keyid, int *non_self);
 static int delete_inv_parts (kbnode_t keyblock,
                              u32 *keyid, unsigned int options);
@@ -562,7 +563,7 @@ import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats,
                                 screener, screener_arg);
       else if (keyblock->pkt->pkttype == PKT_SIGNATURE
                && keyblock->pkt->pkt.signature->sig_class == 0x20 )
-        rc = import_revoke_cert (keyblock, stats);
+        rc = import_revoke_cert (ctrl, keyblock, stats);
       else
         {
           log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype);
@@ -1642,7 +1643,7 @@ import_one (ctrl_t ctrl,
         {
           mod_key = 1;
           /* KEYBLOCK_ORIG has been updated; write */
-          rc = keydb_update_keyblock (hd, keyblock_orig);
+          rc = keydb_update_keyblock (ctrl, hd, keyblock_orig);
           if (rc)
             log_error (_("error writing keyring '%s': %s\n"),
                        keydb_get_resource_name (hd), gpg_strerror (rc) );
@@ -2288,7 +2289,7 @@ import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
  * Import a revocation certificate; this is a single signature packet.
  */
 static int
-import_revoke_cert (kbnode_t node, struct import_stats_s *stats)
+import_revoke_cert (ctrl_t ctrl, kbnode_t node, struct import_stats_s *stats)
 {
   PKT_public_key *pk = NULL;
   kbnode_t onode;
@@ -2379,7 +2380,7 @@ import_revoke_cert (kbnode_t node, struct import_stats_s *stats)
   insert_kbnode( keyblock, clone_kbnode(node), 0 );
 
   /* and write the keyblock back */
-  rc = keydb_update_keyblock (hd, keyblock );
+  rc = keydb_update_keyblock (ctrl, hd, keyblock );
   if (rc)
     log_error (_("error writing keyring '%s': %s\n"),
                keydb_get_resource_name (hd), gpg_strerror (rc) );
index 1467b2d..aab90e3 100644 (file)
@@ -1518,7 +1518,7 @@ build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf, u32 **r_sigstatus)
  * you should use keydb_push_found_state and keydb_pop_found_state to
  * save and restore it.  */
 gpg_error_t
-keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
+keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb)
 {
   gpg_error_t err;
   PKT_public_key *pk;
@@ -1542,6 +1542,10 @@ keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
   if (err)
     return err;
 
+#ifdef USE_TOFU
+  tofu_notice_key_changed (ctrl, kb);
+#endif
+
   memset (&desc, 0, sizeof (desc));
   fingerprint_from_pk (pk, desc.u.fpr, &len);
   if (len == 20)
index 815b17e..8daa9ee 100644 (file)
@@ -181,7 +181,7 @@ const char *keydb_get_resource_name (KEYDB_HANDLE hd);
 gpg_error_t keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb);
 
 /* Update the keyblock KB.  */
-gpg_error_t keydb_update_keyblock (KEYDB_HANDLE hd, kbnode_t kb);
+gpg_error_t keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb);
 
 /* Insert a keyblock into one of the underlying keyrings or keyboxes.  */
 gpg_error_t keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb);
@@ -433,6 +433,13 @@ keyid_cmp (const u32 *a, const u32 *b)
   return 0;
 }
 
+/* Return whether PK is a primary key.  */
+static int GPGRT_ATTR_UNUSED
+pk_is_primary (PKT_public_key *pk)
+{
+  return keyid_cmp (pk_keyid (pk), pk_main_keyid (pk)) == 0;
+}
+
 /* Copy the keyid in SRC to DEST and return DEST.  */
 u32 *keyid_copy (u32 *dest, const u32 *src);
 
index 795be05..dadf586 100644 (file)
@@ -69,7 +69,8 @@ static int menu_delsig (KBNODE pub_keyblock);
 static int menu_clean (KBNODE keyblock, int self_only);
 static void menu_delkey (KBNODE pub_keyblock);
 static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive);
-static int menu_expire (KBNODE pub_keyblock);
+static gpg_error_t menu_expire (kbnode_t pub_keyblock,
+                                int force_mainkey, u32 newexpiration);
 static int menu_changeusage (kbnode_t keyblock);
 static int menu_backsign (KBNODE pub_keyblock);
 static int menu_set_primary_uid (KBNODE pub_keyblock);
@@ -2599,7 +2600,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
          break;
 
        case cmdEXPIRE:
-         if (menu_expire (keyblock))
+         if (gpg_err_code (menu_expire (keyblock, 0, 0)) == GPG_ERR_TRUE)
            {
              merge_keys_and_selfsig (keyblock);
               run_subkey_warnings = 1;
@@ -2782,7 +2783,7 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
        case cmdSAVE:
          if (modified)
            {
-              err = keydb_update_keyblock (kdbhd, keyblock);
+              err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
               if (err)
                 {
                   log_error (_("update failed: %s\n"), gpg_strerror (err));
@@ -2936,7 +2937,7 @@ keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid)
 
   if (menu_adduid (ctrl, keyblock, 0, NULL, uidstring))
     {
-      err = keydb_update_keyblock (kdbhd, keyblock);
+      err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
       if (err)
         {
           log_error (_("update failed: %s\n"), gpg_strerror (err));
@@ -3039,7 +3040,7 @@ keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev)
                          gpg_strerror (err));
               goto leave;
             }
-          err = keydb_update_keyblock (kdbhd, keyblock);
+          err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
           if (err)
             {
               log_error (_("update failed: %s\n"), gpg_strerror (err));
@@ -3261,7 +3262,7 @@ keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids,
 
   if (modified)
     {
-      err = keydb_update_keyblock (kdbhd, keyblock);
+      err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
       if (err)
         {
           log_error (_("update failed: %s\n"), gpg_strerror (err));
@@ -3326,7 +3327,7 @@ keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr,
   /* Store.  */
   if (modified)
     {
-      err = keydb_update_keyblock (kdbhd, keyblock);
+      err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
       if (err)
         {
           log_error (_("update failed: %s\n"), gpg_strerror (err));
@@ -3342,6 +3343,86 @@ keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr,
 }
 
 
+/* Unattended expiration setting function for the main key.
+ *
+ */
+void
+keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr)
+{
+  gpg_error_t err;
+  kbnode_t keyblock;
+  KEYDB_HANDLE kdbhd;
+  int modified = 0;
+  PKT_public_key *pk;
+  u32 expire;
+
+#ifdef HAVE_W32_SYSTEM
+  /* See keyedit_menu for why we need this.  */
+  check_trustdb_stale (ctrl);
+#endif
+
+  /* We require a fingerprint because only this uniquely identifies a
+   * key and may thus be used to select a key for unattended
+   * expiration setting.  */
+  err = find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd);
+  if (err)
+    goto leave;
+
+  if (fix_keyblock (&keyblock))
+    modified++;
+
+  pk = keyblock->pkt->pkt.public_key;
+  if (pk->flags.revoked)
+    {
+      if (!opt.verbose)
+        show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
+      log_error ("%s%s", _("Key is revoked."), "\n");
+      err = gpg_error (GPG_ERR_CERT_REVOKED);
+      goto leave;
+    }
+
+
+  expire = parse_expire_string (expirestr);
+  if (expire == (u32)-1 )
+    {
+      log_error (_("'%s' is not a valid expiration time\n"), expirestr);
+      err = gpg_error (GPG_ERR_INV_VALUE);
+      goto leave;
+    }
+  if (expire)
+    expire += make_timestamp ();
+
+  /* Set the new expiration date.  */
+  err = menu_expire (keyblock, 1, expire);
+  if (gpg_err_code (err) == GPG_ERR_TRUE)
+    modified = 1;
+  else if (err)
+    goto leave;
+  es_fflush (es_stdout);
+
+  /* Store.  */
+  if (modified)
+    {
+      err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
+      if (err)
+        {
+          log_error (_("update failed: %s\n"), gpg_strerror (err));
+          goto leave;
+        }
+      if (update_trust)
+        revalidation_mark ();
+    }
+  else
+    log_info (_("Key not changed so no update needed.\n"));
+
+ leave:
+  release_kbnode (keyblock);
+  keydb_release (kdbhd);
+  if (err)
+    write_status_error ("set_expire", err);
+}
+
+
 \f
 static void
 tty_print_notations (int indent, PKT_signature * sig)
@@ -3585,7 +3666,7 @@ show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
            es_putc ('e', fp);
          else if (!(opt.fast_list_mode || opt.no_expensive_trust_checks))
            {
-             int trust = get_validity_info (ctrl, pk, NULL);
+             int trust = get_validity_info (ctrl, keyblock, pk, NULL);
              if (trust == 'u')
                ulti_hack = 1;
              es_putc (trust, fp);
@@ -3644,7 +3725,7 @@ show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
              int uid_validity;
 
              if (primary && !ulti_hack)
-               uid_validity = get_validity_info (ctrl, primary, uid);
+               uid_validity = get_validity_info (ctrl, keyblock, primary, uid);
              else
                uid_validity = 'u';
              es_fprintf (fp, "%c::::::::", uid_validity);
@@ -3819,7 +3900,7 @@ show_key_with_all_names (ctrl_t ctrl, estream_t fp,
 
              /* Show a warning once */
              if (!did_warn
-                 && (get_validity (ctrl, pk, NULL, NULL, 0)
+                 && (get_validity (ctrl, keyblock, pk, NULL, NULL, 0)
                      & TRUST_FLAG_PENDING_CHECK))
                {
                  did_warn = 1;
@@ -4736,36 +4817,50 @@ fail:
 }
 
 
-static int
-menu_expire (KBNODE pub_keyblock)
+/* With FORCE_MAINKEY cleared this function handles the interactive
+ * menu option "expire".  With FORCE_MAINKEY set this functions only
+ * sets the expiration date of the primary key to NEWEXPIRATION and
+ * avoid all interactivity.  Retirns 0 if nothing was done,
+ * GPG_ERR_TRUE if the key was modified, or any other error code. */
+static gpg_error_t
+menu_expire (kbnode_t pub_keyblock, int force_mainkey, u32 newexpiration)
 {
-  int n1, signumber, rc;
+  int signumber, rc;
   u32 expiredate;
   int mainkey = 0;
   PKT_public_key *main_pk, *sub_pk;
   PKT_user_id *uid;
-  KBNODE node;
+  kbnode_t node;
   u32 keyid[2];
 
-  n1 = count_selected_keys (pub_keyblock);
-  if (n1 > 1)
+  if (force_mainkey)
     {
-      if (!cpr_get_answer_is_yes
-          ("keyedit.expire_multiple_subkeys.okay",
-           _("Are you sure you want to change the"
-             " expiration time for multiple subkeys? (y/N) ")))
-       return 0;
+      mainkey = 1;
+      expiredate = newexpiration;
     }
-  else if (n1)
-    tty_printf (_("Changing expiration time for a subkey.\n"));
   else
     {
-      tty_printf (_("Changing expiration time for the primary key.\n"));
-      mainkey = 1;
-      no_primary_warning (pub_keyblock);
+      int n1 = count_selected_keys (pub_keyblock);
+      if (n1 > 1)
+        {
+          if (!cpr_get_answer_is_yes
+              ("keyedit.expire_multiple_subkeys.okay",
+               _("Are you sure you want to change the"
+                 " expiration time for multiple subkeys? (y/N) ")))
+            return gpg_error (GPG_ERR_CANCELED);;
+        }
+      else if (n1)
+        tty_printf (_("Changing expiration time for a subkey.\n"));
+      else
+        {
+          tty_printf (_("Changing expiration time for the primary key.\n"));
+          mainkey = 1;
+          no_primary_warning (pub_keyblock);
+        }
+
+      expiredate = ask_expiredate ();
     }
 
-  expiredate = ask_expiredate ();
 
   /* Now we can actually change the self-signature(s) */
   main_pk = sub_pk = NULL;
@@ -4781,7 +4876,7 @@ menu_expire (KBNODE pub_keyblock)
        }
       else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
        {
-          if (node->flag & NODFLG_SELKEY)
+          if ((node->flag & NODFLG_SELKEY) && !force_mainkey)
             {
               sub_pk = node->pkt->pkt.public_key;
               sub_pk->expiredate = expiredate;
@@ -4795,6 +4890,7 @@ menu_expire (KBNODE pub_keyblock)
               && (mainkey || sub_pk))
        {
          PKT_signature *sig = node->pkt->pkt.signature;
+
          if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
              && ((mainkey && uid
                   && uid->created && (sig->sig_class & ~3) == 0x10)
@@ -4812,7 +4908,7 @@ menu_expire (KBNODE pub_keyblock)
                {
                  log_info
                     (_("You can't change the expiration date of a v3 key\n"));
-                 return 0;
+                 return gpg_error (GPG_ERR_LEGACY_KEY);
                }
 
              if (mainkey)
@@ -4827,7 +4923,9 @@ menu_expire (KBNODE pub_keyblock)
                {
                  log_error ("make_keysig_packet failed: %s\n",
                             gpg_strerror (rc));
-                 return 0;
+                  if (gpg_err_code (rc) == GPG_ERR_TRUE)
+                    rc = GPG_ERR_GENERAL;
+                 return rc;
                }
 
              /* Replace the packet.  */
@@ -4843,7 +4941,7 @@ menu_expire (KBNODE pub_keyblock)
     }
 
   update_trust = 1;
-  return 1;
+  return gpg_error (GPG_ERR_TRUE);
 }
 
 
@@ -6304,7 +6402,8 @@ core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node,
               /* If the trustdb has an entry for this key+uid then the
                  trustdb needs an update. */
               if (!update_trust
-                  && ((get_validity (ctrl, pk, uid, NULL, 0) & TRUST_MASK)
+                  && ((get_validity (ctrl, keyblock, pk, uid, NULL, 0)
+                       & TRUST_MASK)
                       >= TRUST_UNDEFINED))
                 update_trust = 1;
 #endif /*!NO_TRUST_MODELS*/
index d249556..b4fddba 100644 (file)
 
 /* The default algorithms.  If you change them remember to change them
    also in gpg.c:gpgconf_list.  You should also check that the value
-   is inside the bounds enforced by ask_keysize and gen_xxx.  */
-#define DEFAULT_STD_ALGO       PUBKEY_ALGO_RSA
-#define DEFAULT_STD_KEYSIZE    2048
-#define DEFAULT_STD_KEYUSE     (PUBKEY_USAGE_CERT|PUBKEY_USAGE_SIG)
-#define DEFAULT_STD_CURVE      NULL
-#define DEFAULT_STD_SUBALGO    PUBKEY_ALGO_RSA
-#define DEFAULT_STD_SUBKEYSIZE 2048
-#define DEFAULT_STD_SUBKEYUSE  PUBKEY_USAGE_ENC
-#define DEFAULT_STD_SUBCURVE   NULL
-
-#define FUTURE_STD_ALGO        PUBKEY_ALGO_EDDSA
-#define FUTURE_STD_KEYSIZE     0
-#define FUTURE_STD_KEYUSE      (PUBKEY_USAGE_CERT|PUBKEY_USAGE_SIG)
-#define FUTURE_STD_CURVE       "Ed25519"
-#define FUTURE_STD_SUBALGO     PUBKEY_ALGO_ECDH
-#define FUTURE_STD_SUBKEYSIZE  0
-#define FUTURE_STD_SUBKEYUSE   PUBKEY_USAGE_ENC
-#define FUTURE_STD_SUBCURVE    "Curve25519"
+   is inside the bounds enforced by ask_keysize and gen_xxx.  See also
+   get_keysize_range which encodes the allowed ranges.  */
+#define DEFAULT_STD_KEY_PARAM  "rsa2048/cert,sign+rsa2048/encr"
+#define FUTURE_STD_KEY_PARAM   "ed25519/cert,sign+cv25519/encr"
+
+/* When generating keys using the streamlined key generation dialog,
+   use this as a default expiration interval.  */
+const char *default_expiration_interval = "2y";
 
 /* Flag bits used during key generation.  */
 #define KEYGEN_FLAG_NO_PROTECTION 1
@@ -157,6 +147,27 @@ static int write_keyblock (iobuf_t out, kbnode_t node);
 static gpg_error_t gen_card_key (int keyno, int algo, int is_primary,
                                  kbnode_t pub_root, u32 *timestamp,
                                  u32 expireval);
+static unsigned int get_keysize_range (int algo,
+                                       unsigned int *min, unsigned int *max);
+
+
+\f
+/* Return the algo string for a default new key.  */
+const char *
+get_default_pubkey_algo (void)
+{
+  if (opt.def_new_key_algo)
+    {
+      if (*opt.def_new_key_algo && !strchr (opt.def_new_key_algo, ':'))
+        return opt.def_new_key_algo;
+      /* To avoid checking that option every time we delay that until
+       * here.  The only thing we really need to make sure is that
+       * there is no colon in the string so that the --gpgconf-list
+       * command won't mess up its output.  */
+      log_info (_("invalid value for option '%s'\n"), "--default-new-key-algo");
+    }
+  return DEFAULT_STD_KEY_PARAM;
+}
 
 
 static void
@@ -1091,7 +1102,7 @@ ecckey_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp, int algo)
 {
   gpg_error_t err;
   gcry_sexp_t list, l2;
-  char *curve;
+  char *curve = NULL;
   int i;
   const char *oidstr;
   unsigned int nbits;
@@ -1160,6 +1171,7 @@ ecckey_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp, int algo)
     }
 
  leave:
+  xfree (curve);
   if (err)
     {
       for (i=0; i < 3; i++)
@@ -1602,7 +1614,7 @@ gen_rsa (int algo, unsigned int nbits, KBNODE pub_root,
   log_assert (is_RSA(algo));
 
   if (!nbits)
-    nbits = DEFAULT_STD_KEYSIZE;
+    nbits = get_keysize_range (algo, NULL, NULL);
 
   if (nbits < 1024)
     {
@@ -2056,36 +2068,46 @@ ask_algo (ctrl_t ctrl, int addmode, int *r_subkey_algo, unsigned int *r_usage,
 }
 
 
-static void
-get_keysize_range (int algo,
-                   unsigned int *min, unsigned int *def, unsigned int *max)
+static unsigned int
+get_keysize_range (int algo, unsigned int *min, unsigned int *max)
 {
-  *min = opt.compliance == CO_DE_VS ? 2048: 1024;
-  *def = DEFAULT_STD_KEYSIZE;
-  *max = 4096;
+  unsigned int def;
+  unsigned int dummy1, dummy2;
+
+  if (!min)
+    min = &dummy1;
+  if (!max)
+    max = &dummy2;
 
-  /* Deviations from the standard values.  */
   switch(algo)
     {
     case PUBKEY_ALGO_DSA:
       *min = opt.expert? 768 : 1024;
-      *def=2048;
       *max=3072;
+      def=2048;
       break;
 
     case PUBKEY_ALGO_ECDSA:
     case PUBKEY_ALGO_ECDH:
       *min=256;
-      *def=256;
       *max=521;
+      def=256;
       break;
 
     case PUBKEY_ALGO_EDDSA:
       *min=255;
-      *def=255;
       *max=441;
+      def=255;
+      break;
+
+    default:
+      *min = opt.compliance == CO_DE_VS ? 2048: 1024;
+      *max = 4096;
+      def = 2048;
       break;
     }
+
+  return def;
 }
 
 
@@ -2147,7 +2169,7 @@ ask_keysize (int algo, unsigned int primary_keysize)
   int for_subkey = !!primary_keysize;
   int autocomp = 0;
 
-  get_keysize_range (algo, &min, &def, &max);
+  def = get_keysize_range (algo, &min, &max);
 
   if (primary_keysize && !opt.expert)
     {
@@ -2854,6 +2876,301 @@ generate_user_id (KBNODE keyblock, const char *uidstr)
 }
 
 
+/* Helper for parse_key_parameter_string for one part of the
+ * specification string; i.e.  ALGO/FLAGS.  If STRING is NULL or empty
+ * success is returned.  On error an error code is returned.  Note
+ * that STRING may be modified by this function.  NULL may be passed
+ * for any parameter.  FOR_SUBKEY shall be true if this is used as a
+ * subkey.  */
+static gpg_error_t
+parse_key_parameter_part (char *string, int for_subkey,
+                          int *r_algo, unsigned int *r_size,
+                          unsigned int *r_keyuse,
+                          char const **r_curve)
+{
+  char *flags;
+  int algo;
+  char *endp;
+  const char *curve = NULL;
+  int ecdh_or_ecdsa = 0;
+  unsigned int size;
+  int keyuse;
+  int i;
+  const char *s;
+
+  if (!string || !*string)
+    return 0; /* Success.  */
+
+  flags = strchr (string, '/');
+  if (flags)
+    *flags++ = 0;
+
+  algo = 0;
+  if (strlen (string) >= 3 && (digitp (string+3) || !string[3]))
+    {
+      if (!ascii_memcasecmp (string, "rsa", 3))
+        algo = PUBKEY_ALGO_RSA;
+      else if (!ascii_memcasecmp (string, "dsa", 3))
+        algo = PUBKEY_ALGO_DSA;
+      else if (!ascii_memcasecmp (string, "elg", 3))
+        algo = PUBKEY_ALGO_ELGAMAL_E;
+    }
+  if (algo)
+    {
+      if (!string[3])
+        size = get_keysize_range (algo, NULL, NULL);
+      else
+        {
+          size = strtoul (string+3, &endp, 10);
+          if (size < 512 || size > 16384 || *endp)
+            return gpg_error (GPG_ERR_INV_VALUE);
+        }
+    }
+  else if ((curve = openpgp_is_curve_supported (string, &algo, &size)))
+    {
+      if (!algo)
+        {
+          algo = PUBKEY_ALGO_ECDH; /* Default ECC algorithm.  */
+          ecdh_or_ecdsa = 1;       /* We may need to switch the algo.  */
+        }
+    }
+  else
+    return gpg_error (GPG_ERR_UNKNOWN_CURVE);
+
+  /* Parse the flags.  */
+  keyuse = 0;
+  if (flags)
+    {
+      char **tokens = NULL;
+
+      tokens = strtokenize (flags, ",");
+      if (!tokens)
+        return gpg_error_from_syserror ();
+
+      for (i=0; (s = tokens[i]); i++)
+        {
+          if (!*s)
+            ;
+          else if (!ascii_strcasecmp (s, "sign"))
+            keyuse |= PUBKEY_USAGE_SIG;
+          else if (!ascii_strcasecmp (s, "encrypt")
+                   || !ascii_strcasecmp (s, "encr"))
+            keyuse |= PUBKEY_USAGE_ENC;
+          else if (!ascii_strcasecmp (s, "auth"))
+            keyuse |= PUBKEY_USAGE_AUTH;
+          else if (!ascii_strcasecmp (s, "cert"))
+            keyuse |= PUBKEY_USAGE_CERT;
+          else if (!ascii_strcasecmp (s, "ecdsa"))
+            {
+              if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
+                algo = PUBKEY_ALGO_ECDSA;
+              else
+                {
+                  xfree (tokens);
+                  return gpg_error (GPG_ERR_INV_FLAG);
+                }
+              ecdh_or_ecdsa = 0;
+            }
+          else if (!ascii_strcasecmp (s, "ecdh"))
+            {
+              if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
+                algo = PUBKEY_ALGO_ECDH;
+              else
+                {
+                  xfree (tokens);
+                  return gpg_error (GPG_ERR_INV_FLAG);
+                }
+              ecdh_or_ecdsa = 0;
+            }
+          else if (!ascii_strcasecmp (s, "eddsa"))
+            {
+              /* Not required but we allow it for consistency.  */
+              if (algo == PUBKEY_ALGO_EDDSA)
+                ;
+              else
+                {
+                  xfree (tokens);
+                  return gpg_error (GPG_ERR_INV_FLAG);
+                }
+            }
+          else
+            {
+              xfree (tokens);
+              return gpg_error (GPG_ERR_UNKNOWN_FLAG);
+            }
+        }
+
+      xfree (tokens);
+    }
+
+  /* If not yet decided switch between ecdh and ecdsa.  */
+  if (ecdh_or_ecdsa && keyuse)
+    algo = (keyuse & PUBKEY_USAGE_ENC)? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
+  else if (ecdh_or_ecdsa)
+    algo = for_subkey? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
+
+  /* Set or fix key usage.  */
+  if (!keyuse)
+    {
+      if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA
+          || algo == PUBKEY_ALGO_DSA)
+        keyuse = PUBKEY_USAGE_SIG;
+      else if (algo == PUBKEY_ALGO_RSA)
+        keyuse = for_subkey? PUBKEY_USAGE_ENC : PUBKEY_USAGE_SIG;
+      else
+        keyuse = PUBKEY_USAGE_ENC;
+    }
+  else if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA
+           || algo == PUBKEY_ALGO_DSA)
+    {
+      keyuse &= ~PUBKEY_USAGE_ENC; /* Forbid encryption.  */
+    }
+  else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ELGAMAL_E)
+    {
+      keyuse = PUBKEY_USAGE_ENC;   /* Allow only encryption.  */
+    }
+
+  /* Make sure a primary key can certify.  */
+  if (!for_subkey)
+    keyuse |= PUBKEY_USAGE_CERT;
+
+  /* Check that usage is actually possible.  */
+  if (/**/((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT))
+           && !pubkey_get_nsig (algo))
+       || ((keyuse & PUBKEY_USAGE_ENC)
+           && !pubkey_get_nenc (algo))
+       || (for_subkey && (keyuse & PUBKEY_USAGE_CERT)))
+    return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+
+  /* Return values.  */
+  if (r_algo)
+    *r_algo = algo;
+  if (r_size)
+    {
+      unsigned int min, def, max;
+
+      /* Make sure the keysize is in the allowed range.  */
+      def = get_keysize_range (algo, &min, &max);
+      if (!size)
+        size = def;
+      else if (size < min)
+        size = min;
+      else if (size > max)
+        size = max;
+
+      *r_size = fixup_keysize (size, algo, 1);
+    }
+  if (r_keyuse)
+    *r_keyuse = keyuse;
+  if (r_curve)
+    *r_curve = curve;
+
+  return 0;
+}
+
+/* Parse and return the standard key generation parameter.
+ * The string is expected to be in this format:
+ *
+ *   ALGO[/FLAGS][+SUBALGO[/FLAGS]]
+ *
+ * Here ALGO is a string in the same format as printed by the
+ * keylisting.  For example:
+ *
+ *   rsa3072 := RSA with 3072 bit.
+ *   dsa2048 := DSA with 2048 bit.
+ *   elg2048 := Elgamal with 2048 bit.
+ *   ed25519 := EDDSA using curve Ed25519.
+ *   cv25519 := ECDH using curve Curve25519.
+ *   nistp256:= ECDSA or ECDH using curve NIST P-256
+ *
+ * All strings with an unknown prefix are considered an elliptic
+ * curve.  Curves which have no implicit algorithm require that FLAGS
+ * is given to select whether ECDSA or ECDH is used; this can eoither
+ * be done using an algorithm keyword or usage keywords.
+ *
+ * FLAGS is a comma delimited string of keywords:
+ *
+ *   cert := Allow usage Certify
+ *   sign := Allow usage Sign
+ *   encr := Allow usage Encrypt
+ *   auth := Allow usage Authentication
+ *   encrypt := Alias for "encr"
+ *   ecdsa := Use algorithm ECDSA.
+ *   eddsa := Use algorithm EdDSA.
+ *   ecdh  := Use algorithm ECDH.
+ *
+ * There are several defaults and fallbacks depending on the
+ * algorithm.  PART can be used to select which part of STRING is
+ * used:
+ *   -1 := Both parts
+ *    0 := Only the part of the primary key
+ *    1 := If there is one part parse that one, if there are
+ *         two parts parse the second part.  Always return
+ *         in the args for the primary key (R_ALGO,....).
+ *
+ */
+gpg_error_t
+parse_key_parameter_string (const char *string, int part,
+                            int *r_algo, unsigned int *r_size,
+                            unsigned *r_keyuse,
+                            char const **r_curve,
+                            int *r_subalgo, unsigned int *r_subsize,
+                            unsigned *r_subkeyuse,
+                            char const **r_subcurve)
+{
+  gpg_error_t err = 0;
+  char *primary, *secondary;
+
+  if (r_algo)
+    *r_algo = 0;
+  if (r_size)
+    *r_size = 0;
+  if (r_keyuse)
+    *r_keyuse = 0;
+  if (r_curve)
+    *r_curve = NULL;
+  if (r_subalgo)
+    *r_subalgo = 0;
+  if (r_subsize)
+    *r_subsize = 0;
+  if (r_subkeyuse)
+    *r_subkeyuse = 0;
+  if (r_subcurve)
+    *r_subcurve = NULL;
+
+  if (!string || !*string
+      || !strcmp (string, "default") || !strcmp (string, "-"))
+    string = get_default_pubkey_algo ();
+  else if (!strcmp (string, "future-default"))
+    string = FUTURE_STD_KEY_PARAM;
+
+  primary = xstrdup (string);
+  secondary = strchr (primary, '+');
+  if (secondary)
+    *secondary++ = 0;
+  if (part == -1 || part == 0)
+    {
+      err = parse_key_parameter_part (primary, 0, r_algo, r_size,
+                                      r_keyuse, r_curve);
+      if (!err && part == -1)
+        err = parse_key_parameter_part (secondary, 1, r_subalgo, r_subsize,
+                                        r_subkeyuse, r_subcurve);
+    }
+  else if (part == 1)
+    {
+      /* If we have SECONDARY, use that part.  If there is only one
+       * part consider this to be the subkey algo.  */
+      err = parse_key_parameter_part (secondary? secondary : primary, 1,
+                                      r_algo, r_size, r_keyuse, r_curve);
+    }
+
+  xfree (primary);
+
+  return err;
+}
+
+
+
 /* Append R to the linked list PARA.  */
 static void
 append_to_parameter (struct para_data_s *para, struct para_data_s *r)
@@ -2926,8 +3243,15 @@ get_parameter_algo( struct para_data_s *para, enum para_name key,
   if (!ascii_strcasecmp (r->u.value, "default"))
     {
       /* Note: If you change this default algo, remember to change it
-         also in gpg.c:gpgconf_list.  */
-      i = DEFAULT_STD_ALGO;
+       * also in gpg.c:gpgconf_list.  */
+      /* FIXME: We only allow the algo here and have a separate thing
+       * for the curve etc.  That is a ugly but demanded for backward
+       * compatibility with the batch key generation.  It would be
+       * better to make full use of parse_key_parameter_string.  */
+      parse_key_parameter_string (NULL, 0,
+                                  &i, NULL, NULL, NULL,
+                                  NULL, NULL, NULL, NULL);
+
       if (r_default)
         *r_default = 1;
     }
@@ -2952,8 +3276,8 @@ get_parameter_algo( struct para_data_s *para, enum para_name key,
 
 
 /* Parse a usage string.  The usage keywords "auth", "sign", "encr"
- * may be elimited by space, tab, or comma.  On error -1 is returned
- * instead of the usage flags/  */
+ * may be delimited by space, tab, or comma.  On error -1 is returned
+ * instead of the usage flags.  */
 static int
 parse_usagestr (const char *usagestr)
 {
@@ -3633,31 +3957,35 @@ quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr,
       }
   }
 
+  if (!*expirestr || strcmp (expirestr, "-") == 0)
+    expirestr = default_expiration_interval;
 
   if ((!*algostr || !strcmp (algostr, "default")
        || !strcmp (algostr, "future-default"))
       && (!*usagestr || !strcmp (usagestr, "default")
           || !strcmp (usagestr, "-")))
     {
-      if (!strcmp (algostr, "future-default"))
-        {
-          para = quickgen_set_para (para, 0,
-                                    FUTURE_STD_ALGO, FUTURE_STD_KEYSIZE,
-                                    FUTURE_STD_CURVE, 0);
-          para = quickgen_set_para (para, 1,
-                                    FUTURE_STD_SUBALGO,  FUTURE_STD_SUBKEYSIZE,
-                                    FUTURE_STD_SUBCURVE, 0);
-        }
-      else
+      /* Use default key parameters.  */
+      int algo, subalgo;
+      unsigned int size, subsize;
+      unsigned int keyuse, subkeyuse;
+      const char *curve, *subcurve;
+
+      err = parse_key_parameter_string (algostr, -1,
+                                        &algo, &size, &keyuse, &curve,
+                                        &subalgo, &subsize, &subkeyuse,
+                                        &subcurve);
+      if (err)
         {
-          para = quickgen_set_para (para, 0,
-                                    DEFAULT_STD_ALGO, DEFAULT_STD_KEYSIZE,
-                                    DEFAULT_STD_CURVE, 0);
-          para = quickgen_set_para (para, 1,
-                                    DEFAULT_STD_SUBALGO, DEFAULT_STD_SUBKEYSIZE,
-                                    DEFAULT_STD_SUBCURVE, 0);
+          log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
+          goto leave;
         }
 
+      para = quickgen_set_para (para, 0, algo, size, curve, keyuse);
+      if (subalgo)
+        para = quickgen_set_para (para, 1,
+                                  subalgo, subsize, subcurve, subkeyuse);
+
       if (*expirestr)
         {
           u32 expire;
@@ -3736,6 +4064,7 @@ void
 generate_keypair (ctrl_t ctrl, int full, const char *fname,
                   const char *card_serialno, int card_backup_key)
 {
+  gpg_error_t err;
   unsigned int nbits;
   char *uid = NULL;
   int algo;
@@ -3768,14 +4097,14 @@ generate_keypair (ctrl_t ctrl, int full, const char *fname,
   if (card_serialno)
     {
 #ifdef ENABLE_CARD_SUPPORT
-      gpg_error_t err;
       struct agent_card_info_s info;
 
       memset (&info, 0, sizeof (info));
       err = agent_scd_getattr ("KEY-ATTR", &info);
       if (err)
         {
-          log_error (_("error getting current key info: %s\n"), gpg_strerror (err));
+          log_error (_("error getting current key info: %s\n"),
+                     gpg_strerror (err));
           return;
         }
 
@@ -3978,6 +4307,11 @@ generate_keypair (ctrl_t ctrl, int full, const char *fname,
     }
   else /* Default key generation.  */
     {
+      int subalgo;
+      unsigned int size, subsize;
+      unsigned int keyuse, subkeyuse;
+      const char *curve, *subcurve;
+
       tty_printf ( _("Note: Use \"%s %s\""
                      " for a full featured key generation dialog.\n"),
 #if USE_GPG2_HACK
@@ -3985,17 +4319,28 @@ generate_keypair (ctrl_t ctrl, int full, const char *fname,
 #else
                    GPG_NAME
 #endif
-                   , "--full-gen-key" );
-      para = quickgen_set_para (para, 0,
-                                DEFAULT_STD_ALGO, DEFAULT_STD_KEYSIZE,
-                                DEFAULT_STD_CURVE, 0);
-      para = quickgen_set_para (para, 1,
-                                DEFAULT_STD_SUBALGO, DEFAULT_STD_SUBKEYSIZE,
-                                DEFAULT_STD_SUBCURVE, 0);
+                   , "--full-generate-key" );
+
+      err = parse_key_parameter_string (NULL, -1,
+                                        &algo, &size, &keyuse, &curve,
+                                        &subalgo, &subsize,
+                                        &subkeyuse, &subcurve);
+      if (err)
+        {
+          log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
+          return;
+        }
+      para = quickgen_set_para (para, 0, algo, size, curve, keyuse);
+      if (subalgo)
+        para = quickgen_set_para (para, 1,
+                                  subalgo, subsize, subcurve, subkeyuse);
+
+
     }
 
 
-  expire = full? ask_expire_interval (0, NULL) : 0;
+  expire = full? ask_expire_interval (0, NULL)
+               : parse_expire_string (default_expiration_interval);
   r = xcalloc (1, sizeof *r + 20);
   r->key = pKEYEXPIRE;
   r->u.expire = expire;
@@ -4479,87 +4824,38 @@ parse_algo_usage_expire (ctrl_t ctrl, int for_subkey,
                          int *r_algo, unsigned int *r_usage, u32 *r_expire,
                          unsigned int *r_nbits, char **r_curve)
 {
+  gpg_error_t err;
   int algo;
   unsigned int use, nbits;
   u32 expire;
   int wantuse;
-  unsigned int min, def, max;
   const char *curve = NULL;
-  int eccalgo = 0;
 
   *r_curve = NULL;
 
   nbits = 0;
+
   /* Parse the algo string.  */
-  if (!algostr || !*algostr
-      || !strcmp (algostr, "default") || !strcmp (algostr, "-"))
-    {
-      algo  = for_subkey? DEFAULT_STD_SUBALGO    : DEFAULT_STD_ALGO;
-      use   = for_subkey? DEFAULT_STD_SUBKEYUSE  : DEFAULT_STD_KEYUSE;
-      nbits = for_subkey? DEFAULT_STD_SUBKEYSIZE : DEFAULT_STD_KEYSIZE;
-      curve = for_subkey? DEFAULT_STD_SUBCURVE   : DEFAULT_STD_CURVE;
-    }
-  else if (!strcmp (algostr, "future-default"))
-    {
-      algo  = for_subkey? FUTURE_STD_SUBALGO    : FUTURE_STD_ALGO;
-      use   = for_subkey? FUTURE_STD_SUBKEYUSE  : FUTURE_STD_KEYUSE;
-      nbits = for_subkey? FUTURE_STD_SUBKEYSIZE : FUTURE_STD_KEYSIZE;
-      curve = for_subkey? FUTURE_STD_SUBCURVE   : FUTURE_STD_CURVE;
-    }
-  else if (*algostr == '&' && strlen (algostr) == 41)
+  if (algostr && *algostr == '&' && strlen (algostr) == 41)
     {
       /* Take algo from existing key.  */
       algo = check_keygrip (ctrl, algostr+1);
       /* FIXME: We need the curve name as well.  */
       return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
     }
-  else if (!strncmp (algostr, "rsa", 3))
-    {
-      algo = PUBKEY_ALGO_RSA;
-      use = for_subkey? DEFAULT_STD_SUBKEYUSE : DEFAULT_STD_KEYUSE;
-      if (algostr[3])
-        nbits = atoi (algostr + 3);
-    }
-  else if (!strncmp (algostr, "elg", 3))
-    {
-      algo = PUBKEY_ALGO_ELGAMAL_E;
-      use = PUBKEY_USAGE_ENC;
-      if (algostr[3])
-        nbits = atoi (algostr + 3);
-    }
-  else if (!strncmp (algostr, "dsa", 3))
-    {
-      algo = PUBKEY_ALGO_DSA;
-      use = PUBKEY_USAGE_SIG;
-      if (algostr[3])
-        nbits = atoi (algostr + 3);
-    }
-  else if ((curve = openpgp_is_curve_supported (algostr, &algo)))
-    {
-      if (!algo)
-        {
-          algo = PUBKEY_ALGO_ECDH; /* Default ECC algorithm.  */
-          eccalgo = 1;  /* Remember - we may need to fix it up.  */
-        }
 
-      if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA)
-        use = PUBKEY_USAGE_SIG;
-      else
-        use = PUBKEY_USAGE_ENC;
-    }
-  else
-    return gpg_error (GPG_ERR_UNKNOWN_CURVE);
+  err = parse_key_parameter_string (algostr, for_subkey? 1 : 0,
+                                    &algo, &nbits, &use, &curve,
+                                    NULL, NULL, NULL, NULL);
+  if (err)
+    return err;
 
   /* Parse the usage string.  */
   if (!usagestr || !*usagestr
       || !strcmp (usagestr, "default") || !strcmp (usagestr, "-"))
-    ; /* Keep default usage */
+    ; /* Keep usage from parse_key_parameter_string.  */
   else if ((wantuse = parse_usagestr (usagestr)) != -1)
-    {
-      use = wantuse;
-      if (eccalgo && !(use & PUBKEY_USAGE_ENC))
-        algo = PUBKEY_ALGO_ECDSA; /* Switch from ECDH to ECDSA.  */
-    }
+    use = wantuse;
   else
     return gpg_error (GPG_ERR_INV_VALUE);
 
@@ -4567,7 +4863,9 @@ parse_algo_usage_expire (ctrl_t ctrl, int for_subkey,
   if (!for_subkey)
     use |= PUBKEY_USAGE_CERT;
 
-  /* Check that usage is possible.  */
+  /* Check that usage is possible.  NB: We have the same check in
+   * parse_key_parameter_string but need it here again in case the
+   * separate usage value has been given. */
   if (/**/((use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT))
            && !pubkey_get_nsig (algo))
        || ((use & PUBKEY_USAGE_ENC)
@@ -4580,17 +4878,6 @@ parse_algo_usage_expire (ctrl_t ctrl, int for_subkey,
   if (expire == (u32)-1 )
     return gpg_error (GPG_ERR_INV_VALUE);
 
-  /* Make sure the keysize is in the allowed range.  */
-  get_keysize_range (algo, &min, &def, &max);
-  if (!nbits)
-    nbits = def;
-  else if (nbits < min)
-    nbits = min;
-  else if (nbits > max)
-    nbits = max;
-
-  nbits = fixup_keysize (nbits, algo, 1);
-
   if (curve)
     {
       *r_curve = xtrystrdup (curve);
index 0523be0..a5fdc06 100644 (file)
@@ -1228,7 +1228,7 @@ list_keyblock_colon (ctrl_t ctrl, kbnode_t keyblock,
     trustletter_print = 0;
   else
     {
-      trustletter = get_validity_info (ctrl, pk, NULL);
+      trustletter = get_validity_info (ctrl, keyblock, pk, NULL);
       if (trustletter == 'u')
         ulti_hack = 1;
       trustletter_print = trustletter;
@@ -1309,7 +1309,7 @@ list_keyblock_colon (ctrl_t ctrl, kbnode_t keyblock,
          else if (ulti_hack)
             uid_validity = 'u';
           else
-            uid_validity = get_validity_info (ctrl, pk, uid);
+            uid_validity = get_validity_info (ctrl, keyblock, pk, uid);
 
           es_fputs (uid->attrib_data? "uat:":"uid:", es_stdout);
           if (uid_validity)
index 9148e7d..c7363c9 100644 (file)
@@ -2084,11 +2084,9 @@ keyserver_import_ldap (ctrl_t ctrl,
   struct keyserver_spec *keyserver;
   strlist_t list=NULL;
   int rc,hostlen=1;
-#ifdef USE_DNS_SRV
   struct srventry *srvlist=NULL;
   int srvcount,i;
   char srvname[MAXDNAME];
-#endif
 
   /* Parse out the domain */
   domain=strrchr(name,'@');
@@ -2102,7 +2100,6 @@ keyserver_import_ldap (ctrl_t ctrl,
   keyserver->host=xmalloc(1);
   keyserver->host[0]='\0';
 
-#ifdef USE_DNS_SRV
   snprintf(srvname,MAXDNAME,"_pgpkey-ldap._tcp.%s",domain);
 
   FIXME("network related - move to dirmngr or drop the code");
@@ -2130,7 +2127,6 @@ keyserver_import_ldap (ctrl_t ctrl,
     }
 
   free(srvlist);
-#endif
 
   /* If all else fails, do the PGP Universal trick of
      ldap://keys.(domain) */
index 63aec47..5ed501b 100644 (file)
@@ -295,9 +295,12 @@ void keyedit_quick_revuid (ctrl_t ctrl, const char *username,
                            const char *uidtorev);
 void keyedit_quick_sign (ctrl_t ctrl, const char *fpr,
                          strlist_t uids, strlist_t locusr, int local);
+void keyedit_quick_set_expire (ctrl_t ctrl,
+                               const char *fpr, const char *expirestr);
 void show_basic_key_info (KBNODE keyblock);
 
 /*-- keygen.c --*/
+const char *get_default_pubkey_algo (void);
 u32 parse_expire_string(const char *string);
 u32 ask_expire_interval(int object,const char *def_expire);
 u32 ask_expiredate(void);
index c1819f0..ac2ab03 100644 (file)
@@ -1015,8 +1015,13 @@ list_node (CTX c, kbnode_t node)
 
           keyid_from_pk( pk, keyid );
           if (pk->flags.primary)
-            c->trustletter = (opt.fast_list_mode?
-                              0 : get_validity_info (c->ctrl, pk, NULL));
+            c->trustletter = (opt.fast_list_mode
+                              ? 0
+                              : get_validity_info
+                                  (c->ctrl,
+                                   node->pkt->pkttype == PKT_PUBLIC_KEY
+                                   ? node : NULL,
+                                   pk, NULL));
           es_printf ("%s:", pk->flags.primary? "pub":"sub" );
           if (c->trustletter)
             es_putc (c->trustletter, es_stdout);
@@ -1973,8 +1978,8 @@ check_sig_and_print (CTX c, kbnode_t node)
             does not print a LF we need to compute the validity
             before calling that function.  */
           if ((opt.verify_options & VERIFY_SHOW_UID_VALIDITY))
-            valid = get_validity (c->ctrl, mainpk, un->pkt->pkt.user_id,
-                                  NULL, 0);
+            valid = get_validity (c->ctrl, keyblock, mainpk,
+                                  un->pkt->pkt.user_id, NULL, 0);
           else
             valid = 0; /* Not used.  */
 
@@ -2075,7 +2080,7 @@ check_sig_and_print (CTX c, kbnode_t node)
                       actually ask the user to update any trust
                       information.  */
                     valid = (trust_value_to_string
-                             (get_validity (c->ctrl, mainpk,
+                             (get_validity (c->ctrl, keyblock, mainpk,
                                             un->pkt->pkt.user_id, NULL, 0)));
                   log_printf (" [%s]\n",valid);
                 }
@@ -2218,7 +2223,7 @@ check_sig_and_print (CTX c, kbnode_t node)
       snprintf (buf, sizeof buf, "%08lX%08lX %d %d %02x %lu %d",
                 (ulong)sig->keyid[0], (ulong)sig->keyid[1],
                 sig->pubkey_algo, sig->digest_algo,
-                sig->sig_class, (ulong)sig->timestamp, rc);
+                sig->sig_class, (ulong)sig->timestamp, gpg_err_code (rc));
       write_status_text (STATUS_ERRSIG, buf);
       if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
         {
index 8ed2cdb..589b68e 100644 (file)
@@ -120,6 +120,8 @@ struct
   const char *agent_program;
   const char *dirmngr_program;
 
+  const char *def_new_key_algo;
+
   /* Options to be passed to the gpg-agent */
   session_env_t session_env;
   char *lc_ctype;
@@ -298,7 +300,6 @@ struct {
 #define DBG_TRUST_VALUE   256  /* debug the trustdb */
 #define DBG_HASHING_VALUE 512  /* debug hashing operations */
 #define DBG_IPC_VALUE     1024  /* debug assuan communication */
-#define DBG_CARD_IO_VALUE 2048  /* debug smart card I/O.  */
 #define DBG_CLOCK_VALUE   4096
 #define DBG_LOOKUP_VALUE  8192 /* debug the key lookup */
 #define DBG_EXTPROG_VALUE 16384 /* debug external program calls */
@@ -312,7 +313,6 @@ struct {
 #define DBG_TRUST  (opt.debug & DBG_TRUST_VALUE)
 #define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
 #define DBG_IPC     (opt.debug & DBG_IPC_VALUE)
-#define DBG_CARD_IO (opt.debug & DBG_CARD_IO_VALUE)
 #define DBG_IPC     (opt.debug & DBG_IPC_VALUE)
 #define DBG_CLOCK   (opt.debug & DBG_CLOCK_VALUE)
 #define DBG_LOOKUP  (opt.debug & DBG_LOOKUP_VALUE)
index 65f60a9..9780d93 100644 (file)
@@ -95,7 +95,9 @@ typedef struct
 typedef struct {
   /* RFC 4880: this must be 4.  */
   byte version;
-  /* The cipher algorithm used.  */
+  /* The cipher algorithm used to encrypt the session key.  (This may
+     be different from the algorithm that is used to encrypt the SED
+     packet.)  */
   byte cipher_algo;
   /* The string-to-key specifier.  */
   STRING2KEY s2k;
@@ -269,7 +271,7 @@ typedef struct
   struct user_attribute *attribs;
   int numattribs;
   /* If this is not NULL, the packet is a user attribute rather than a
-     user id.  (Serialized.)  */
+     user id (See RFC 4880 5.12).  (Serialized.)  */
   byte *attrib_data;
   /* The length of ATTRIB_DATA.  */
   unsigned long attrib_len;
index 38cfdd9..7f44ce5 100644 (file)
@@ -809,8 +809,8 @@ dump_hex_line (int c, int *i)
    decoded values are given as PKGTYPE and PKTLEN.
 
    If the packet is a partial body length packet (RFC 4880, Section
-   4.2.2.4), then iobuf_set_partial_block_mode should already have
-   been called on INP and PARTIAL should be set.
+   4.2.2.4), then iobuf_set_partial_block_modeiobuf_set_partial_block_mode
+   should already have been called on INP and PARTIAL should be set.
 
    If PARTIAL is set or PKTLEN is 0 and PKTTYPE is PKT_COMPRESSED,
    copy until the first EOF is encountered on INP.
@@ -3050,7 +3050,7 @@ parse_mdc (IOBUF inp, int pkttype, unsigned long pktlen,
 
 
 /*
- * This packet is internally generated by us (ibn armor.c) to transfer
+ * This packet is internally generated by us (in armor.c) to transfer
  * some information to the lower layer.  To make sure that this packet
  * is really a GPG faked one and not one coming from outside, we
  * first check that there is a unique tag in it.
index ccd232a..fb4ec4c 100644 (file)
@@ -319,8 +319,8 @@ passphrase_to_dek (int cipher_algo, STRING2KEY *s2k,
       log_assert (create && !nocache);
       /* This is used for the old rfc1991 mode
        * Note: This must match the code in encode.c with opt.rfc1991 set */
+      memset (&help_s2k, 0, sizeof (help_s2k));
       s2k = &help_s2k;
-      s2k->mode = 0;
       s2k->hash_algo = S2K_DIGEST_ALGO;
     }
 
index b61ed1b..8b193b3 100644 (file)
@@ -304,7 +304,7 @@ show_photos (ctrl_t ctrl, const struct user_attribute *attrs, int count,
 
   memset (&args, 0, sizeof(args));
   args.pk = pk;
-  args.validity_info = get_validity_info (ctrl, pk, uid);
+  args.validity_info = get_validity_info (ctrl, NULL, pk, uid);
   args.validity_string = get_validity_string (ctrl, pk, uid);
   namehash_from_uid (uid);
   args.namehash = uid->namehash;
index 51e8f27..288affc 100644 (file)
@@ -481,7 +481,7 @@ do_we_trust_pre( PKT_public_key *pk, unsigned int trustlevel )
 
       if ((trustlevel & TRUST_MASK) == TRUST_NEVER)
         tty_printf(
-          _("This key has is bad!  It has been marked as untrusted!  If you\n"
+          _("This key is bad!  It has been marked as untrusted!  If you\n"
             "*really* know what you are doing, you may answer the next\n"
             "question with yes.\n"));
       else
@@ -569,7 +569,7 @@ check_signatures_trust (ctrl_t ctrl, PKT_signature *sig)
     log_info(_("WARNING: this key might be revoked (revocation key"
               " not present)\n"));
 
-  trustlevel = get_validity (ctrl, pk, NULL, sig, 1);
+  trustlevel = get_validity (ctrl, NULL, pk, NULL, sig, 1);
 
   if ( (trustlevel & TRUST_FLAG_REVOKED) )
     {
@@ -872,7 +872,7 @@ find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use,
     {
       int trustlevel;
 
-      trustlevel = get_validity (ctrl, pk, pk->user_id, NULL, 1);
+      trustlevel = get_validity (ctrl, NULL, pk, pk->user_id, NULL, 1);
       if ( (trustlevel & TRUST_FLAG_DISABLED) )
         {
           /* Key has been disabled. */
@@ -1212,7 +1212,8 @@ build_pk_list (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list)
                 { /* Check validity of this key. */
                   int trustlevel;
 
-                  trustlevel = get_validity (ctrl, pk, pk->user_id, NULL, 1);
+                  trustlevel =
+                    get_validity (ctrl, NULL, pk, pk->user_id, NULL, 1);
                   if ( (trustlevel & TRUST_FLAG_DISABLED) )
                     {
                       tty_printf (_("Public key is disabled.\n") );
index bdf5592..40ce603 100644 (file)
@@ -108,9 +108,25 @@ get_output_file (const byte *embedded_name, int embedded_namelen,
     }
   else if (iobuf_is_pipe_filename (fname) || !*fname)
     {
-      /* No filename or "-" given; write to stdout. */
-      fp = es_stdout;
-      es_set_binary (fp);
+      /* Special file name, no filename, or "-" given; write to the
+       * file descriptor or to stdout. */
+      int fd;
+      char xname[64];
+
+      fd = check_special_filename (fname, 1, 0);
+      if (fd == -1)
+        {
+          /* Not a special filename, thus we want stdout.  */
+          fp = es_stdout;
+          es_set_binary (fp);
+        }
+      else if (!(fp = es_fdopen_nc (fd, "wb")))
+        {
+          err = gpg_error_from_syserror ();
+          snprintf (xname, sizeof xname, "[fd %d]", fd);
+          log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (err));
+          goto leave;
+        }
     }
   else
     {
index 68fc44a..591b641 100644 (file)
@@ -571,7 +571,8 @@ gen_standard_revoke (PKT_public_key *psk, const char *cache_nonce)
        "the secret key.  However, if the secret key is still accessible,\n"
        "it is better to generate a new revocation certificate and give\n"
        "a reason for the revocation.  For details see the description of\n"
-       "of the gpg command \"--gen-revoke\" in the GnuPG manual."),
+       "of the gpg command \"--generate-revocation\" in the "
+       "GnuPG manual."),
      _("To avoid an accidental use of this file, a colon has been inserted\n"
        "before the 5 dashes below.  Remove this colon with a text editor\n"
        "before importing and publishing this revocation certificate."));
index a391128..acc894c 100644 (file)
@@ -282,7 +282,9 @@ do_sign (PKT_public_key *pksk, PKT_signature *sig,
   sig->digest_algo = mdalgo;
   sig->digest_start[0] = dp[0];
   sig->digest_start[1] = dp[1];
+  mpi_release (sig->data[0]);
   sig->data[0] = NULL;
+  mpi_release (sig->data[1]);
   sig->data[1] = NULL;
 
 
@@ -1386,14 +1388,30 @@ sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr)
 
 
 /****************
- * Create a signature packet for the given public key certificate and
- * the user id and return it in ret_sig. User signature class SIGCLASS
- * user-id is not used (and may be NULL if sigclass is 0x20) If
- * DIGEST_ALGO is 0 the function selects an appropriate one.
- * SIGVERSION gives the minimal required signature packet version;
- * this is needed so that special properties like local sign are not
- * applied (actually: dropped) when a v3 key is used.  TIMESTAMP is
- * the timestamp to use for the signature. 0 means "now" */
+ * Create a v4 signature in *RET_SIG.
+ *
+ * PK is the primary key to sign (required for all sigs)
+ * UID is the user id to sign (required for 0x10..0x13, 0x30)
+ * SUBPK is subkey to sign (required for 0x18, 0x19, 0x28)
+ *
+ * PKSK is the signing key
+ *
+ * SIGCLASS is the type of signature to create.
+ *
+ * DIGEST_ALGO is the digest algorithm.  If it is 0 the function
+ * selects an appropriate one.
+ *
+ * TIMESTAMP is the timestamp to use for the signature. 0 means "now"
+ *
+ * DURATION is the amount of time (in seconds) until the signature
+ * expires.
+ *
+ * This function creates the following subpackets: issuer, created,
+ * and expire (if duration is not 0).  Additional subpackets can be
+ * added using MKSUBPKT, which is called after these subpackets are
+ * added and before the signature is generated.  OPAQUE is passed to
+ * MKSUBPKT.
+ */
 int
 make_keysig_packet (PKT_signature **ret_sig, PKT_public_key *pk,
                    PKT_user_id *uid, PKT_public_key *subpk,
index 8560f9d..8752f88 100644 (file)
@@ -98,19 +98,22 @@ check_trustdb_stale (ctrl_t ctrl)
 }
 
 int
-get_validity_info (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid)
+get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk,
+                   PKT_user_id *uid)
 {
   (void)ctrl;
+  (void)kb;
   (void)pk;
   (void)uid;
   return '?';
 }
 
 unsigned int
-get_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid,
+get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid,
               PKT_signature *sig, int may_ask)
 {
   (void)ctrl;
+  (void)kb;
   (void)pk;
   (void)uid;
   (void)sig;
@@ -517,3 +520,12 @@ tofu_end_batch_update (ctrl_t ctrl)
 {
   (void)ctrl;
 }
+
+gpg_error_t
+tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb)
+{
+  (void) ctrl;
+  (void) kb;
+
+  return 0;
+}
index 2c9d17c..2bded9e 100644 (file)
@@ -29,9 +29,6 @@
 #include <stdarg.h>
 #include <sqlite3.h>
 #include <time.h>
-#include <utime.h>
-#include <fcntl.h>
-#include <unistd.h>
 
 #include "gpg.h"
 #include "types.h"
 
 #define CONTROL_L ('L' - 'A' + 1)
 
-/* Number of signed messages required to indicate that enough history
- * is available for basic trust.  */
-#define BASIC_TRUST_THRESHOLD  10
-/* Number of signed messages required to indicate that a lot of
- * history is available.  */
-#define FULL_TRUST_THRESHOLD  100
+/* Number of days with signed / ecnrypted messages required to
+ * indicate that enough history is available for basic trust.  */
+#define BASIC_TRUST_THRESHOLD  4
+/* Number of days with signed / encrypted messages required to
+ * indicate that a lot of history is available.  */
+#define FULL_TRUST_THRESHOLD  21
 
 
-/* An struct with data pertaining to the tofu DB.
-
-   To initialize this data structure, call opendbs().  Cleanup is done
-   when the CTRL object is released.  To get a handle to a database,
-   use the getdb() function.  This will either return an existing
-   handle or open a new DB connection, as appropriate.  */
+/* A struct with data pertaining to the tofu DB.  There is one such
+   struct per session and it is cached in session's ctrl structure.
+   To initialize this or get the current singleton, call opendbs().
+   There is no need to explicitly release it; cleanup is done when the
+   CTRL object is released.  */
 struct tofu_dbs_s
 {
   sqlite3 *db;
@@ -79,14 +75,14 @@ struct tofu_dbs_s
 
     sqlite3_stmt *record_binding_get_old_policy;
     sqlite3_stmt *record_binding_update;
-    sqlite3_stmt *record_binding_update2;
     sqlite3_stmt *get_policy_select_policy_and_conflict;
     sqlite3_stmt *get_trust_bindings_with_this_email;
     sqlite3_stmt *get_trust_gather_other_user_ids;
     sqlite3_stmt *get_trust_gather_signature_stats;
     sqlite3_stmt *get_trust_gather_encryption_stats;
     sqlite3_stmt *register_already_seen;
-    sqlite3_stmt *register_insert;
+    sqlite3_stmt *register_signature;
+    sqlite3_stmt *register_encryption;
   } s;
 
   int in_batch_transaction;
@@ -116,8 +112,10 @@ struct tofu_dbs_s
 /* Local prototypes.  */
 static gpg_error_t end_transaction (ctrl_t ctrl, int only_batch);
 static char *email_from_user_id (const char *user_id);
-
-
+static int show_statistics (tofu_dbs_t dbs,
+                            const char *fingerprint, const char *email,
+                            enum tofu_policy policy,
+                            estream_t outfp, int only_status_fd, time_t now);
 \f
 const char *
 tofu_policy_str (enum tofu_policy policy)
@@ -182,8 +180,8 @@ begin_transaction (ctrl_t ctrl, int only_batch)
    * than 500 ms), to prevent starving other gpg processes, we drop
    * and retake the batch lock.
    *
-   * Note: if we wanted higher resolution, we could use
-   * npth_clock_gettime.  */
+   * Note: gnupg_get_time has a one second resolution, if we wanted a
+   * higher resolution, we could use npth_clock_gettime.  */
   if (/* No real transactions.  */
       dbs->in_transaction == 0
       /* There is an open batch transaction.  */
@@ -267,8 +265,8 @@ begin_transaction (ctrl_t ctrl, int only_batch)
 
 /* Commit a transaction.  If ONLY_BATCH is 1, then this only ends the
  * batch transaction if we have left batch mode.  If ONLY_BATCH is 2,
- * this ends any open batch transaction even if we are still in batch
- * mode.  */
+ * this commits any open batch transaction even if we are still in
+ * batch mode.  */
 static gpg_error_t
 end_transaction (ctrl_t ctrl, int only_batch)
 {
@@ -344,7 +342,7 @@ rollback_transaction (ctrl_t ctrl)
   log_assert (dbs);
   log_assert (dbs->in_transaction > 0);
 
-  /* Be careful to not any progress made by closed transactions in
+  /* Be careful to not undo any progress made by closed transactions in
      batch mode.  */
   rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
                            "rollback to inner%d;",
@@ -414,9 +412,12 @@ string_to_long (long *r_value, const char *string, long fallback, int line)
   if (errno || !(!strcmp (tail, ".0") || !*tail))
     {
       err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
-      log_debug ("%s:%d: "
-                 "strtol failed for DB returned string (tail=%.10s): %s\n",
-                 __FILE__, line, tail, gpg_strerror (err));
+      log_debug ("%s:%d: strtol failed for TOFU DB data; returned string"
+                 " (string='%.10s%s'; tail='%.10s%s'): %s\n",
+                 __FILE__, line,
+                 string, string && strlen(string) > 10 ? "..." : "",
+                 tail, tail && strlen(tail) > 10 ? "..." : "",
+                 gpg_strerror (err));
       *r_value = fallback;
     }
   else
@@ -442,9 +443,12 @@ string_to_ulong (unsigned long *r_value, const char *string,
   if (errno || !(!strcmp (tail, ".0") || !*tail))
     {
       err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
-      log_debug ("%s:%d: "
-                 "strtoul failed for DB returned string (tail=%.10s): %s\n",
-                 __FILE__, line, tail, gpg_strerror (err));
+      log_debug ("%s:%d: strtoul failed for TOFU DB data; returned string"
+                 " (string='%.10s%s'; tail='%.10s%s'): %s\n",
+                 __FILE__, line,
+                 string, string && strlen(string) > 10 ? "..." : "",
+                 tail, tail && strlen(tail) > 10 ? "..." : "",
+                 gpg_strerror (err));
       *r_value = fallback;
     }
   else
@@ -506,6 +510,152 @@ version_check_cb (void *cookie, int argc, char **argv, char **azColName)
   return 1;
 }
 
+static int
+check_utks (sqlite3 *db)
+{
+  int rc;
+  char *err = NULL;
+  struct key_item *utks;
+  struct key_item *ki;
+  int utk_count;
+  char *utks_string = NULL;
+  char keyid_str[16+1];
+  long utks_unchanged = 0;
+
+  /* An early version of the v1 format did not include the list of
+   * known ultimately trusted keys.
+   *
+   * This list is used to detect when the set of ultimately trusted
+   * keys changes.  We need to detect this to invalidate the effective
+   * policy, which can change if an ultimately trusted key is added or
+   * removed.  */
+  rc = sqlite3_exec (db,
+                     "create table if not exists ultimately_trusted_keys"
+                     " (keyid);\n",
+                     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("error creating 'ultimately_trusted_keys' TOFU table: %s\n"),
+                 err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+
+  utks = tdb_utks ();
+  for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++)
+    ;
+
+  if (utk_count)
+    {
+      /* Build a list of keyids of the form "XXX","YYY","ZZZ".  */
+      int len = (1 + 16 + 1 + 1) * utk_count;
+      int o = 0;
+
+      utks_string = xmalloc (len);
+      *utks_string = 0;
+      for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++)
+        {
+          utks_string[o ++] = '\'';
+          format_keyid (ki->kid, KF_LONG,
+                        keyid_str, sizeof (keyid_str));
+          memcpy (&utks_string[o], keyid_str, 16);
+          o += 16;
+          utks_string[o ++] = '\'';
+          utks_string[o ++] = ',';
+        }
+      utks_string[o - 1] = 0;
+      log_assert (o == len);
+    }
+
+  rc = gpgsql_exec_printf
+    (db, get_single_unsigned_long_cb, &utks_unchanged, &err,
+     "select"
+     /* Removed UTKs?  (Known UTKs in current UTKs.)  */
+     "  ((select count(*) from ultimately_trusted_keys"
+     "     where (keyid in (%s))) == %d)"
+     " and"
+     /* New UTKs?  */
+     "  ((select count(*) from ultimately_trusted_keys"
+     "     where keyid not in (%s)) == 0);",
+     utks_string ? utks_string : "",
+     utk_count,
+     utks_string ? utks_string : "");
+  xfree (utks_string);
+  if (rc)
+    {
+      log_error (_("TOFU DB error"));
+      print_further_info ("checking if ultimately trusted keys changed: %s",
+                         err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  if (utks_unchanged)
+    goto out;
+
+  if (DBG_TRUST)
+    log_debug ("TOFU: ultimately trusted keys changed.\n");
+
+  /* Given that the set of ultimately trusted keys
+   * changed, clear any cached policies.  */
+  rc = gpgsql_exec_printf
+    (db, NULL, NULL, &err,
+     "update bindings set effective_policy = %d;",
+     TOFU_POLICY_NONE);
+  if (rc)
+    {
+      log_error (_("TOFU DB error"));
+      print_further_info ("clearing cached policies: %s", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  /* Now, update the UTK table.  */
+  rc = sqlite3_exec (db,
+                     "drop table ultimately_trusted_keys;",
+                     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("TOFU DB error"));
+      print_further_info ("dropping ultimately_trusted_keys: %s", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  rc = sqlite3_exec (db,
+                     "create table if not exists"
+                     " ultimately_trusted_keys (keyid);\n",
+                     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("TOFU DB error"));
+      print_further_info ("creating ultimately_trusted_keys: %s", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  for (ki = utks; ki; ki = ki->next)
+    {
+      format_keyid (ki->kid, KF_LONG,
+                    keyid_str, sizeof (keyid_str));
+      rc = gpgsql_exec_printf
+        (db, NULL, NULL, &err,
+         "insert into ultimately_trusted_keys values ('%s');",
+         keyid_str);
+      if (rc)
+        {
+          log_error (_("TOFU DB error"));
+          print_further_info ("updating ultimately_trusted_keys: %s",
+                              err);
+          sqlite3_free (err);
+          goto out;
+        }
+    }
+
+ out:
+  return rc;
+}
 
 /* If the DB is new, initialize it.  Otherwise, check the DB's
    version.
@@ -682,15 +832,54 @@ initdb (sqlite3 *db)
     {
       /* Early version of the v1 format did not include the encryption
          table.  Add it.  */
-      sqlite3_exec (db,
-                    "create table if not exists encryptions"
-                    " (binding INTEGER NOT NULL,"
-                    "  time INTEGER);"
-                    "create index if not exists encryptions_binding"
-                    " on encryptions (binding);\n",
-                    NULL, NULL, &err);
+      rc = sqlite3_exec (db,
+                         "create table if not exists encryptions"
+                         " (binding INTEGER NOT NULL,"
+                         "  time INTEGER);"
+                         "create index if not exists encryptions_binding"
+                         " on encryptions (binding);\n",
+                         NULL, NULL, &err);
+      if (rc)
+        {
+         log_error (_("error creating 'encryptions' TOFU table: %s\n"),
+                    err);
+          sqlite3_free (err);
+        }
+    }
+  if (! rc)
+    {
+      /* The effective policy for a binding.  If a key is ultimately
+       * trusted, then the effective policy of all of its bindings is
+       * good.  Likewise if a key is signed by an ultimately trusted
+       * key, etc.  If the effective policy is NONE, then we need to
+       * recompute the effective policy.  Otherwise, the effective
+       * policy is considered to be up to date, i.e., effective_policy
+       * is a cache of the computed policy.  */
+      rc = gpgsql_exec_printf
+        (db, NULL, NULL, &err,
+         "alter table bindings"
+         " add column effective_policy INTEGER"
+         " DEFAULT %d"
+         " CHECK (effective_policy in (%d, %d, %d, %d, %d, %d));",
+         TOFU_POLICY_NONE,
+         TOFU_POLICY_NONE, TOFU_POLICY_AUTO, TOFU_POLICY_GOOD,
+         TOFU_POLICY_UNKNOWN, TOFU_POLICY_BAD, TOFU_POLICY_ASK);
+      if (rc)
+       {
+          if (rc == SQLITE_ERROR)
+            /* Almost certainly "duplicate column name", which we can
+             * safely ignore.  */
+            rc = 0;
+          else
+            log_error (_("adding column effective_policy to bindings DB: %s\n"),
+                       err);
+         sqlite3_free (err);
+       }
     }
 
+  if (! rc)
+    rc = check_utks (db);
+
   if (rc)
     {
       rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err);
@@ -724,21 +913,23 @@ busy_handler (void *cookie, int call_count)
 
   (void) call_count;
 
-  /* Update the lock file time stamp so that the current owner knows
-     that we want the lock.  */
+  /* Update the want-lock-file time stamp (specifically, the ctime) so
+   * that the current owner knows that we (well, someone) want the
+   * lock.  */
   if (dbs)
     {
       /* Note: we don't fail if we can't create the lock file: this
-         process will have to wait a bit longer, but otherwise nothing
-         horrible should happen.  */
+       * process will have to wait a bit longer, but otherwise nothing
+       * horrible should happen.  */
+
+      estream_t fp;
 
-      int fd = open (dbs->want_lock_file, O_CREAT|O_WRONLY|O_TRUNC,
-                     S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR);
-      if (fd == -1)
+      fp = es_fopen (dbs->want_lock_file, "w");
+      if (! fp)
         log_debug ("TOFU: Error opening '%s': %s\n",
                    dbs->want_lock_file, strerror (errno));
       else
-        close (fd);
+        es_fclose (fp);
     }
 
   /* Call again.  */
@@ -858,8 +1049,9 @@ get_single_long_cb2 (void *cookie, int argc, char **argv, char **azColName,
    If SHOW_OLD is set, the binding's old policy is displayed.  */
 static gpg_error_t
 record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
-               const char *user_id, enum tofu_policy policy,
-                const char *conflict,
+               const char *user_id,
+                enum tofu_policy policy, enum tofu_policy effective_policy,
+                const char *conflict, int set_conflict,
                 int show_old, time_t now)
 {
   char *fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
@@ -924,19 +1116,33 @@ record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
   rc = gpgsql_stepx
     (dbs->db, &dbs->s.record_binding_update, NULL, NULL, &err,
      "insert or replace into bindings\n"
-     " (oid, fingerprint, email, user_id, time, policy, conflict)\n"
+     " (oid, fingerprint, email, user_id, time,"
+     "  policy, conflict, effective_policy)\n"
      " values (\n"
      /* If we don't explicitly reuse the OID, then SQLite will
-       reallocate a new one.  We just need to search for the OID
-       based on the fingerprint and email since they are unique.  */
+      * reallocate a new one.  We just need to search for the OID
+      * based on the fingerprint and email since they are unique.  */
      "  (select oid from bindings where fingerprint = ? and email = ?),\n"
-     "  ?, ?, ?, ?, ?, ?);",
+     "  ?, ?, ?, ?, ?,"
+     /* If SET_CONFLICT is 0, then preserve conflict's current value.  */
+     "  case ?"
+     "    when 0 then"
+     "      (select conflict from bindings where fingerprint = ? and email = ?)"
+     "    else ?"
+     "  end,"
+     "  ?);",
+     /* oid subquery.  */
      GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
+     /* values 2 through 6.  */
      GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
      GPGSQL_ARG_STRING, user_id,
      GPGSQL_ARG_LONG_LONG, (long long) now,
      GPGSQL_ARG_INT, (int) policy,
+     /* conflict subquery.  */
+     GPGSQL_ARG_INT, set_conflict ? 1 : 0,
+     GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
      GPGSQL_ARG_STRING, conflict ? conflict : "",
+     GPGSQL_ARG_INT, (int) effective_policy,
      GPGSQL_ARG_END);
   if (rc)
     {
@@ -953,7 +1159,7 @@ record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
 }
 
 
-/* Collect the strings returned by a query in a simply string list.
+/* Collect the strings returned by a query in a simple string list.
    Any NULL values are converted to the empty string.
 
    If a result has 3 rows and each row contains two columns, then the
@@ -1098,123 +1304,6 @@ signature_stats_collect_cb (void *cookie, int argc, char **argv,
   return 0;
 }
 
-/* Convert from seconds to time units.
-
-   Note: T should already be a multiple of TIME_AGO_UNIT_SMALL or
-   TIME_AGO_UNIT_MEDIUM or TIME_AGO_UNIT_LARGE.  */
-signed long
-time_ago_scale (signed long t)
-{
-  if (t < TIME_AGO_UNIT_MEDIUM)
-    return t / TIME_AGO_UNIT_SMALL;
-  if (t < TIME_AGO_UNIT_LARGE)
-    return t / TIME_AGO_UNIT_MEDIUM;
-  return t / TIME_AGO_UNIT_LARGE;
-}
-
-
-/* Return the policy for the binding <FINGERPRINT, EMAIL> (email has
-   already been normalized) and any conflict information in *CONFLICT
-   if CONFLICT is not NULL.  Returns _tofu_GET_POLICY_ERROR if an error
-   occurs.  */
-static enum tofu_policy
-get_policy (tofu_dbs_t dbs, const char *fingerprint, const char *email,
-           char **conflict)
-{
-  int rc;
-  char *err = NULL;
-  strlist_t strlist = NULL;
-  enum tofu_policy policy = _tofu_GET_POLICY_ERROR;
-  long along;
-
-  /* Check if the <FINGERPRINT, EMAIL> binding is known
-     (TOFU_POLICY_NONE cannot appear in the DB.  Thus, if POLICY is
-     still TOFU_POLICY_NONE after executing the query, then the
-     result set was empty.)  */
-  rc = gpgsql_stepx (dbs->db, &dbs->s.get_policy_select_policy_and_conflict,
-                      strings_collect_cb2, &strlist, &err,
-                      "select policy, conflict from bindings\n"
-                      " where fingerprint = ? and email = ?",
-                      GPGSQL_ARG_STRING, fingerprint,
-                      GPGSQL_ARG_STRING, email,
-                      GPGSQL_ARG_END);
-  if (rc)
-    {
-      log_error (_("error reading TOFU database: %s\n"), err);
-      print_further_info ("checking for existing bad bindings");
-      sqlite3_free (err);
-      rc = gpg_error (GPG_ERR_GENERAL);
-      goto out;
-    }
-
-  if (strlist_length (strlist) == 0)
-    /* No results.  */
-    {
-      policy = TOFU_POLICY_NONE;
-      goto out;
-    }
-  else if (strlist_length (strlist) != 2)
-    /* The result has the wrong form.  */
-    {
-      log_error (_("error reading TOFU database: %s\n"),
-                 gpg_strerror (GPG_ERR_BAD_DATA));
-      print_further_info ("checking for existing bad bindings:"
-                          " expected 2 results, got %d\n",
-                          strlist_length (strlist));
-      goto out;
-    }
-
-  /* The result has the right form.  */
-
-  if (string_to_long (&along, strlist->d, 0, __LINE__))
-    {
-      log_error (_("error reading TOFU database: %s\n"),
-                 gpg_strerror (GPG_ERR_BAD_DATA));
-      print_further_info ("bad value for policy: %s", strlist->d);
-      goto out;
-    }
-  policy = along;
-
-  if (! (policy == TOFU_POLICY_AUTO
-        || policy == TOFU_POLICY_GOOD
-        || policy == TOFU_POLICY_UNKNOWN
-        || policy == TOFU_POLICY_BAD
-        || policy == TOFU_POLICY_ASK))
-    {
-      log_error (_("error reading TOFU database: %s\n"),
-                 gpg_strerror (GPG_ERR_DB_CORRUPTED));
-      print_further_info ("invalid value for policy (%d)", policy);
-      policy = _tofu_GET_POLICY_ERROR;
-      goto out;
-    }
-
-
-  /* If CONFLICT is set, then policy should be TOFU_POLICY_ASK.  But,
-     just in case, we do the check again here and ignore the conflict
-     if POLICY is not TOFU_POLICY_ASK.  */
-  if (conflict)
-    {
-      if (policy == TOFU_POLICY_ASK && *strlist->next->d)
-       *conflict = xstrdup (strlist->next->d);
-      else
-       *conflict = NULL;
-    }
-
- out:
-  log_assert (policy == _tofu_GET_POLICY_ERROR
-              || policy == TOFU_POLICY_NONE
-              || policy == TOFU_POLICY_AUTO
-              || policy == TOFU_POLICY_GOOD
-              || policy == TOFU_POLICY_UNKNOWN
-              || policy == TOFU_POLICY_BAD
-              || policy == TOFU_POLICY_ASK);
-
-  free_strlist (strlist);
-
-  return policy;
-}
-
-
 /* Format the first part of a conflict message and return that as a
  * malloced string.  */
 static char *
@@ -1244,8 +1333,11 @@ format_conflict_msg_part1 (int policy, strlist_t conflict_set,
   else if (policy == TOFU_POLICY_ASK && conflict_set->next)
     {
       int conflicts = strlist_length (conflict_set);
-      es_fprintf (fp, _("The email address \"%s\" is associated with %d keys!"),
-                  email, conflicts);
+      es_fprintf
+        (fp, ngettext("The email address \"%s\" is associated with %d key!",
+                      "The email address \"%s\" is associated with %d keys!",
+                      conflicts),
+         email, conflicts);
       if (opt.verbose)
         es_fprintf (fp,
                     _("  Since this binding's policy was 'auto', it has been "
@@ -1647,6 +1739,7 @@ ask_about_binding (ctrl_t ctrl,
       char *key = NULL;
       strlist_t binding;
       int seen_in_past = 0;
+      int encrypted = 1;
 
       es_fprintf (fp, _("Statistics for keys"
                         " with the email address \"%s\":\n"),
@@ -1660,6 +1753,14 @@ ask_about_binding (ctrl_t ctrl,
                      stats_iter->count);
 #endif
 
+          if (stats_iter->time_ago > 0 && encrypted)
+            {
+              /* We've change from the encrypted stats to the verified
+               * stats.  Reset SEEN_IN_PAST.  */
+              encrypted = 0;
+              seen_in_past = 0;
+            }
+
           if (! key || strcmp (key, stats_iter->fingerprint))
             {
               int this_key;
@@ -1698,6 +1799,9 @@ ask_about_binding (ctrl_t ctrl,
               xfree (key_pp);
 
               seen_in_past = 0;
+
+              show_statistics (dbs, stats_iter->fingerprint, email,
+                               TOFU_POLICY_ASK, NULL, 1, now);
             }
 
           if (labs(stats_iter->time_ago) == 1)
@@ -1709,47 +1813,92 @@ ask_about_binding (ctrl_t ctrl,
           seen_in_past += stats_iter->count;
 
           es_fputs ("    ", fp);
-          /* TANSLATORS: This string is concatenated with one of
-           * the day/week/month strings to form one sentence.  */
-          if (stats_iter->time_ago > 0)
-            es_fprintf (fp, ngettext("Verified %d message",
-                                     "Verified %d messages",
-                                     seen_in_past), seen_in_past);
-          else
-            es_fprintf (fp, ngettext("Encrypted %d message",
-                                     "Encrypted %d messages",
-                                     seen_in_past), seen_in_past);
 
           if (!stats_iter->count)
-            es_fputs (".", fp);
+            {
+              if (stats_iter->time_ago > 0)
+                es_fprintf (fp, ngettext("Verified %d message.",
+                                         "Verified %d messages.",
+                                         seen_in_past), seen_in_past);
+              else
+                es_fprintf (fp, ngettext("Encrypted %d message.",
+                                         "Encrypted %d messages.",
+                                         seen_in_past), seen_in_past);
+            }
           else if (labs(stats_iter->time_ago) == 2)
             {
-              es_fprintf (fp, "in the future.");
+              if (stats_iter->time_ago > 0)
+                es_fprintf (fp, ngettext("Verified %d message in the future.",
+                                         "Verified %d messages in the future.",
+                                         seen_in_past), seen_in_past);
+              else
+                es_fprintf (fp, ngettext("Encrypted %d message in the future.",
+                                         "Encrypted %d messages in the future.",
+                                         seen_in_past), seen_in_past);
               /* Reset it.  */
               seen_in_past = 0;
             }
           else
             {
               if (labs(stats_iter->time_ago) == 3)
-                es_fprintf (fp, ngettext(" over the past day.",
-                                         " over the past %d days.",
-                                         seen_in_past),
-                            TIME_AGO_SMALL_THRESHOLD
-                            / TIME_AGO_UNIT_SMALL);
+                {
+                  int days = 1 + stats_iter->time_ago / TIME_AGO_UNIT_SMALL;
+                  if (stats_iter->time_ago > 0)
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages verified over the past %d day: %d.",
+                                "Messages verified over the past %d days: %d.",
+                                days), days, seen_in_past);
+                  else
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages encrypted over the past %d day: %d.",
+                                "Messages encrypted over the past %d days: %d.",
+                                days), days, seen_in_past);
+                }
               else if (labs(stats_iter->time_ago) == 4)
-                es_fprintf (fp, ngettext(" over the past month.",
-                                         " over the past %d months.",
-                                         seen_in_past),
-                            TIME_AGO_MEDIUM_THRESHOLD
-                            / TIME_AGO_UNIT_MEDIUM);
+                {
+                  int months = 1 + stats_iter->time_ago / TIME_AGO_UNIT_MEDIUM;
+                  if (stats_iter->time_ago > 0)
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages verified over the past %d month: %d.",
+                                "Messages verified over the past %d months: %d.",
+                                months), months, seen_in_past);
+                  else
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages encrypted over the past %d month: %d.",
+                                "Messages encrypted over the past %d months: %d.",
+                                months), months, seen_in_past);
+                }
               else if (labs(stats_iter->time_ago) == 5)
-                es_fprintf (fp, ngettext(" over the past year.",
-                                         " over the past %d years.",
-                                         seen_in_past),
-                            TIME_AGO_LARGE_THRESHOLD
-                            / TIME_AGO_UNIT_LARGE);
+                {
+                  int years = 1 + stats_iter->time_ago / TIME_AGO_UNIT_LARGE;
+                  if (stats_iter->time_ago > 0)
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages verified over the past %d year: %d.",
+                                "Messages verified over the past %d years: %d.",
+                                years), years, seen_in_past);
+                  else
+                    es_fprintf
+                      (fp,
+                       ngettext("Messages encrypted over the past %d year: %d.",
+                                "Messages encrypted over the past %d years: %d.",
+                                years), years, seen_in_past);
+                }
               else if (labs(stats_iter->time_ago) == 6)
-                es_fprintf (fp, _(" in the past."));
+                {
+                  if (stats_iter->time_ago > 0)
+                    es_fprintf
+                      (fp, _("Messages verified in the past: %d."),
+                       seen_in_past);
+                  else
+                    es_fprintf
+                      (fp, _("Messages encrypted in the past: %d."),
+                       seen_in_past);
+                }
               else
                 log_assert (! "Broken SQL.\n");
             }
@@ -1859,7 +2008,7 @@ ask_about_binding (ctrl_t ctrl,
                 }
 
               if (record_binding (dbs, fingerprint, email, user_id,
-                                  *policy, NULL, 0, now))
+                                  *policy, TOFU_POLICY_NONE, NULL, 0, 0, now))
                 {
                   /* If there's an error registering the
                    * binding, don't save the signature.  */
@@ -2083,9 +2232,15 @@ build_conflict_set (tofu_dbs_t dbs,
     int j;
     strlist_t *prevp;
     strlist_t iter_next;
-    int die[conflict_set_count];
+    int *die;
 
-    memset (die, 0, sizeof (die));
+    log_assert (conflict_set_count > 0);
+    die = xtrycalloc (conflict_set_count, sizeof *die);
+    if (!die)
+      {
+        /*err = gpg_error_from_syserror ();*/
+        xoutofcore (); /* Fixme: Let the fucntion return an error.  */
+      }
 
     for (i = 0; i < conflict_set_count; i ++)
       {
@@ -2125,6 +2280,7 @@ build_conflict_set (tofu_dbs_t dbs,
     /* We shouldn't have removed the head.  */
     log_assert (conflict_set);
     log_assert (conflict_set_count >= 1);
+    xfree (die);
   }
   xfree (kb_all);
 
@@ -2147,152 +2303,162 @@ build_conflict_set (tofu_dbs_t dbs,
 }
 
 
-/* Return the trust level (TRUST_NEVER, etc.) for the binding
- * <FINGERPRINT, EMAIL> (email is already normalized).  If no policy
- * is registered, returns TOFU_POLICY_NONE.  If an error occurs,
- * returns _tofu_GET_TRUST_ERROR.
- *
- * PK is the public key object for FINGERPRINT.
- *
- * USER_ID is the unadulterated user id.
- *
- * If MAY_ASK is set, then we may interact with the user.  This is
- * necessary if there is a conflict or the binding's policy is
- * TOFU_POLICY_ASK.  In the case of a conflict, we set the new
- * conflicting binding's policy to TOFU_POLICY_ASK.  In either case,
- * we return TRUST_UNDEFINED.  Note: if MAY_ASK is set, then this
- * function must not be called while in a transaction!  */
+/* Return the effective policy for the binding <FINGERPRINT, EMAIL>
+ * (email has already been normalized) and any conflict information in
+ * *CONFLICT_SETP, if CONFLICT_SETP is not NULL.  Returns
+ * _tofu_GET_POLICY_ERROR if an error occurs.  */
 static enum tofu_policy
-get_trust (ctrl_t ctrl, PKT_public_key *pk,
-           const char *fingerprint, const char *email,
-          const char *user_id, int may_ask, time_t now)
+get_policy (tofu_dbs_t dbs, PKT_public_key *pk,
+            const char *fingerprint, const char *user_id, const char *email,
+           strlist_t *conflict_setp, time_t now)
 {
-  tofu_dbs_t dbs = ctrl->tofu.dbs;
-  int in_transaction = 0;
-  enum tofu_policy policy;
   int rc;
-  char *sqerr = NULL;
-  int change_conflicting_to_ask = 0;
+  char *err = NULL;
+  strlist_t results = NULL;
+  enum tofu_policy policy = _tofu_GET_POLICY_ERROR;
+  enum tofu_policy effective_policy_orig = TOFU_POLICY_NONE;
+  enum tofu_policy effective_policy = _tofu_GET_POLICY_ERROR;
+  long along;
+  char *conflict_orig = NULL;
+  char *conflict = NULL;
   strlist_t conflict_set = NULL;
   int conflict_set_count;
-  int trust_level = TRUST_UNKNOWN;
-  strlist_t iter;
-
-  log_assert (dbs);
 
-  if (may_ask)
-    log_assert (dbs->in_transaction == 0);
-
-  if (opt.batch)
-    may_ask = 0;
+  /* Check if the <FINGERPRINT, EMAIL> binding is known
+     (TOFU_POLICY_NONE cannot appear in the DB.  Thus, if POLICY is
+     still TOFU_POLICY_NONE after executing the query, then the
+     result set was empty.)  */
+  rc = gpgsql_stepx (dbs->db, &dbs->s.get_policy_select_policy_and_conflict,
+                      strings_collect_cb2, &results, &err,
+                      "select policy, conflict, effective_policy from bindings\n"
+                      " where fingerprint = ? and email = ?",
+                      GPGSQL_ARG_STRING, fingerprint,
+                      GPGSQL_ARG_STRING, email,
+                      GPGSQL_ARG_END);
+  if (rc)
+    {
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("reading the policy");
+      sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
+      goto out;
+    }
 
-  log_assert (keyid_cmp (pk_keyid (pk), pk_main_keyid (pk)) == 0);
+  if (strlist_length (results) == 0)
+    {
+      /* No results.  Use the defaults.  */
+      policy = TOFU_POLICY_NONE;
+      effective_policy = TOFU_POLICY_NONE;
+    }
+  else if (strlist_length (results) == 3)
+    {
+      /* Parse and sanity check the results.  */
 
-  /* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust
-     levels.  */
-  log_assert (_tofu_GET_TRUST_ERROR != TRUST_UNKNOWN
-              && _tofu_GET_TRUST_ERROR != TRUST_EXPIRED
-              && _tofu_GET_TRUST_ERROR != TRUST_UNDEFINED
-              && _tofu_GET_TRUST_ERROR != TRUST_NEVER
-              && _tofu_GET_TRUST_ERROR != TRUST_MARGINAL
-              && _tofu_GET_TRUST_ERROR != TRUST_FULLY
-              && _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE);
+      if (string_to_long (&along, results->d, 0, __LINE__))
+        {
+          log_error (_("error reading TOFU database: %s\n"),
+                     gpg_strerror (GPG_ERR_BAD_DATA));
+          print_further_info ("bad value for policy: %s", results->d);
+          goto out;
+        }
+      policy = along;
 
-  begin_transaction (ctrl, 0);
-  in_transaction = 1;
+      if (! (policy == TOFU_POLICY_AUTO
+             || policy == TOFU_POLICY_GOOD
+             || policy == TOFU_POLICY_UNKNOWN
+             || policy == TOFU_POLICY_BAD
+             || policy == TOFU_POLICY_ASK))
+        {
+          log_error (_("error reading TOFU database: %s\n"),
+                     gpg_strerror (GPG_ERR_DB_CORRUPTED));
+          print_further_info ("invalid value for policy (%d)", policy);
+          effective_policy = _tofu_GET_POLICY_ERROR;
+          goto out;
+        }
 
-  policy = get_policy (dbs, fingerprint, email, NULL);
-  {
-    /* See if the key is ultimately trusted.  If so, we're done.  */
-    u32 kid[2];
+      if (*results->next->d)
+        conflict = xstrdup (results->next->d);
 
-    keyid_from_pk (pk, kid);
-
-    if (tdb_keyid_is_utk (kid))
-      {
-        if (policy == TOFU_POLICY_NONE)
-          /* New binding.  */
-          {
-            if (record_binding (dbs, fingerprint, email, user_id,
-                                TOFU_POLICY_GOOD, NULL, 0, now) != 0)
-              {
-                log_error (_("error setting TOFU binding's trust level"
-                             " to %s\n"), "good");
-                trust_level = _tofu_GET_TRUST_ERROR;
-                goto out;
-              }
-          }
-
-        trust_level = TRUST_ULTIMATE;
-        goto out;
-      }
-  }
-
-  if (policy == TOFU_POLICY_AUTO)
-    {
-      policy = opt.tofu_default_policy;
-      if (DBG_TRUST)
-       log_debug ("TOFU: binding <key: %s, user id: %s>'s policy is"
-                   " auto (default: %s).\n",
-                  fingerprint, email,
-                  tofu_policy_str (opt.tofu_default_policy));
+      if (string_to_long (&along, results->next->next->d, 0, __LINE__))
+        {
+          log_error (_("error reading TOFU database: %s\n"),
+                     gpg_strerror (GPG_ERR_BAD_DATA));
+          print_further_info ("bad value for effective policy: %s",
+                              results->next->next->d);
+          goto out;
+        }
+      effective_policy = along;
+
+      if (! (effective_policy == TOFU_POLICY_NONE
+             || effective_policy == TOFU_POLICY_AUTO
+             || effective_policy == TOFU_POLICY_GOOD
+             || effective_policy == TOFU_POLICY_UNKNOWN
+             || effective_policy == TOFU_POLICY_BAD
+             || effective_policy == TOFU_POLICY_ASK))
+        {
+          log_error (_("error reading TOFU database: %s\n"),
+                     gpg_strerror (GPG_ERR_DB_CORRUPTED));
+          print_further_info ("invalid value for effective_policy (%d)",
+                              effective_policy);
+          effective_policy = _tofu_GET_POLICY_ERROR;
+          goto out;
+        }
     }
-  switch (policy)
+  else
     {
-    case TOFU_POLICY_AUTO:
-    case TOFU_POLICY_GOOD:
-    case TOFU_POLICY_UNKNOWN:
-    case TOFU_POLICY_BAD:
-      /* The saved judgement is auto -> auto, good, unknown or bad.
-       * We don't need to ask the user anything.  */
-      if (DBG_TRUST)
-       log_debug ("TOFU: Known binding <key: %s, user id: %s>'s policy: %s\n",
-                  fingerprint, email, tofu_policy_str (policy));
-      trust_level = tofu_policy_to_trust_level (policy);
+      /* The result has the wrong form.  */
+
+      log_error (_("error reading TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_BAD_DATA));
+      print_further_info ("reading policy: expected 3 columns, got %d\n",
+                          strlist_length (results));
       goto out;
+    }
 
-    case TOFU_POLICY_ASK:
-      /* We need to ask the user what to do.  Case #1 or #2 below.  */
-      break;
+  /* Save the effective policy and conflict so we know if we changed
+   * them.  */
+  effective_policy_orig = effective_policy;
+  conflict_orig = conflict;
 
-    case TOFU_POLICY_NONE:
-      /* The binding is new, we need to check for conflicts.  Case #3
-       * below.  */
-      break;
+  /* Unless there is a conflict, if the effective policy is cached,
+   * just return it.  The reason we don't do this when there is a
+   * conflict is because of the following scenario: assume A and B
+   * conflict and B has signed A's key.  Now, later we import A's
+   * signature on B.  We need to recheck A, but the signature was on
+   * B, i.e., when B changes, we invalidate B's effective policy, but
+   * we also need to invalidate A's effective policy.  Instead, we
+   * assume that conflicts are rare and don't optimize for them, which
+   * would complicate the code.  */
+  if (effective_policy != TOFU_POLICY_NONE && !conflict)
+    goto out;
 
-    case _tofu_GET_POLICY_ERROR:
-      trust_level = _tofu_GET_TRUST_ERROR;
+  /* If the user explicitly set the policy, then respect that.  */
+  if (policy != TOFU_POLICY_AUTO && policy != TOFU_POLICY_NONE)
+    {
+      effective_policy = policy;
       goto out;
-
-    default:
-      log_bug ("%s: Impossible value for policy (%d)\n", __func__, policy);
     }
 
+  /* Unless proven wrong, assume the effective policy is 'auto'.  */
+  effective_policy = TOFU_POLICY_AUTO;
 
-  /* We get here if:
-   *
-   *   1. The saved policy is auto and the default policy is ask
-   *      (get_policy() == TOFU_POLICY_AUTO
-   *       && opt.tofu_default_policy == TOFU_POLICY_ASK)
-   *
-   *   2. The saved policy is ask (either last time the user selected
-   *      accept once or reject once or there was a conflict and this
-   *      binding's policy was changed from auto to ask)
-   *      (policy == TOFU_POLICY_ASK), or,
-   *
-   *   3. We don't have a saved policy (policy == TOFU_POLICY_NONE)
-   *      (need to check for a conflict).
-   *
-   * In summary: POLICY is ask or none.
-   */
+  /* See if the key is ultimately trusted.  */
+  {
+    u32 kid[2];
 
-  /* Before continuing, see if the key is signed by an ultimately
-   * trusted key.  */
+    keyid_from_pk (pk, kid);
+    if (tdb_keyid_is_utk (kid))
+      {
+        effective_policy = TOFU_POLICY_GOOD;
+        goto out;
+      }
+  }
+
+  /* See if the key is signed by an ultimately trusted key.  */
   {
     int fingerprint_raw_len = strlen (fingerprint) / 2;
     char fingerprint_raw[fingerprint_raw_len];
     int len = 0;
-    int is_signed_by_utk = 0;
 
     if (fingerprint_raw_len != 20
         || ((len = hex2bin (fingerprint,
@@ -2319,41 +2485,33 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
           }
         else
           {
-            is_signed_by_utk = signed_by_utk (email, kb);
+            int is_signed_by_utk = signed_by_utk (email, kb);
             release_kbnode (kb);
+            if (is_signed_by_utk)
+              {
+                effective_policy = TOFU_POLICY_GOOD;
+                goto out;
+              }
           }
       }
-
-    if (is_signed_by_utk)
-      {
-        if (record_binding (dbs, fingerprint, email, user_id,
-                            TOFU_POLICY_GOOD, NULL, 0, now) != 0)
-          {
-            log_error (_("error setting TOFU binding's trust level"
-                         " to %s\n"), "good");
-            trust_level = _tofu_GET_TRUST_ERROR;
-          }
-        else
-          trust_level = TRUST_FULLY;
-
-        goto out;
-      }
   }
 
+  /* Check for any conflicts / see if a previously discovered conflict
+   * disappeared.  The latter can happen if the conflicting bindings
+   * are now cross signed, for instance.  */
 
-  /* Look for conflicts.  This is needed in all 3 cases.  */
   conflict_set = build_conflict_set (dbs, pk, fingerprint, email);
   conflict_set_count = strlist_length (conflict_set);
   if (conflict_set_count == 0)
     {
-      /* We should always at least have the current binding.  */
-      trust_level = _tofu_GET_TRUST_ERROR;
+      /* build_conflict_set should always at least return the current
+         binding.  Something went wrong.  */
+      effective_policy = _tofu_GET_POLICY_ERROR;
       goto out;
     }
 
   if (conflict_set_count == 1
-      && (conflict_set->flags & BINDING_NEW)
-      && opt.tofu_default_policy != TOFU_POLICY_ASK)
+      && (conflict_set->flags & BINDING_NEW))
     {
       /* We've never observed a binding with this email address and we
        * have a default policy, which is not to ask the user.  */
@@ -2366,16 +2524,7 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
        log_debug ("TOFU: New binding <key: %s, user id: %s>, no conflict.\n",
                   fingerprint, email);
 
-      if (record_binding (dbs, fingerprint, email, user_id,
-                         TOFU_POLICY_AUTO, NULL, 0, now) != 0)
-       {
-         log_error (_("error setting TOFU binding's trust level to %s\n"),
-                      "auto");
-         trust_level = _tofu_GET_TRUST_ERROR;
-         goto out;
-       }
-
-      trust_level = tofu_policy_to_trust_level (TOFU_POLICY_AUTO);
+      effective_policy = TOFU_POLICY_AUTO;
       goto out;
     }
 
@@ -2383,106 +2532,278 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
       && (conflict_set->flags & BINDING_CONFLICT))
     {
       /* No known conflicts now, but there was a conflict.  This means
-       * at somepoint, there was a conflict and we changed this
+       * at some point, there was a conflict and we changed this
        * binding's policy to ask and set the conflicting key.  The
        * conflict can go away if there is not a cross sig between the
        * two keys.  In this case, just silently clear the conflict and
        * reset the policy to auto.  */
 
-      log_assert (policy == TOFU_POLICY_ASK);
-
       if (DBG_TRUST)
         log_debug ("TOFU: binding <key: %s, user id: %s> had a conflict, but it's been resolved (probably via  cross sig).\n",
                    fingerprint, email);
 
-      if (record_binding (dbs, fingerprint, email, user_id,
-                         TOFU_POLICY_AUTO, NULL, 0, now) != 0)
-       log_error (_("error setting TOFU binding's trust level to %s\n"),
-                  "auto");
+      effective_policy = TOFU_POLICY_AUTO;
+      conflict = NULL;
 
-      trust_level = tofu_policy_to_trust_level (TOFU_POLICY_AUTO);
       goto out;
     }
 
-  /* We have a conflict.  Mark any conflicting bindings that have an
-   * automatic policy as now requiring confirmation.  Note: we delay
-   * this until after we ask for confirmation so that when the current
-   * policy is printed, it is correct.  */
-  change_conflicting_to_ask = 1;
-
-  if (! may_ask)
+  if (conflict_set_count == 1)
     {
-      log_assert (policy == TOFU_POLICY_NONE || policy == TOFU_POLICY_ASK);
-      if (policy == TOFU_POLICY_NONE)
-        {
-          /* We get here in the third case (no saved policy) and if
-           * there is a conflict.  */
-          if (record_binding (dbs, fingerprint, email, user_id,
-                              TOFU_POLICY_ASK,
-                              conflict_set && conflict_set->next
-                              ? conflict_set->next->d : NULL,
-                              0, now) != 0)
-            log_error (_("error setting TOFU binding's trust level to %s\n"),
-                       "ask");
-        }
+      /* No conflicts and never marked as conflicting.  */
+
+      log_assert (!conflict);
+
+      effective_policy = TOFU_POLICY_AUTO;
 
-      trust_level = TRUST_UNDEFINED;
       goto out;
     }
 
-  /* We can't be in a normal transaction in ask_about_binding.  */
-  end_transaction (ctrl, 0);
-  in_transaction = 0;
-
-  /* If we get here, we need to ask the user about the binding.  */
-  ask_about_binding (ctrl,
-                     &policy,
-                     &trust_level,
-                     conflict_set,
-                     fingerprint,
-                     email,
-                     user_id,
-                     now);
+  /* There is a conflicting key.  */
+  log_assert (conflict_set_count > 1);
+  effective_policy = TOFU_POLICY_ASK;
+  conflict = xstrdup (conflict_set->next->d);
 
  out:
+  log_assert (policy == _tofu_GET_POLICY_ERROR
+              || policy == TOFU_POLICY_NONE
+              || policy == TOFU_POLICY_AUTO
+              || policy == TOFU_POLICY_GOOD
+              || policy == TOFU_POLICY_UNKNOWN
+              || policy == TOFU_POLICY_BAD
+              || policy == TOFU_POLICY_ASK);
+  /* Everything but NONE.  */
+  log_assert (effective_policy == _tofu_GET_POLICY_ERROR
+              || effective_policy == TOFU_POLICY_AUTO
+              || effective_policy == TOFU_POLICY_GOOD
+              || effective_policy == TOFU_POLICY_UNKNOWN
+              || effective_policy == TOFU_POLICY_BAD
+              || effective_policy == TOFU_POLICY_ASK);
+
+  if (effective_policy != TOFU_POLICY_ASK && conflict)
+    conflict = NULL;
+
+  /* If we don't have a record of this binding, its effective policy
+   * changed, or conflict changed, update the DB.  */
+  if (effective_policy != _tofu_GET_POLICY_ERROR
+      && (/* New binding.  */
+          policy == TOFU_POLICY_NONE
+          /* effective_policy changed.  */
+          || effective_policy != effective_policy_orig
+          /* conflict changed.  */
+          || (conflict != conflict_orig
+              && (!conflict || !conflict_orig
+                  || strcmp (conflict, conflict_orig) != 0))))
+    {
+      if (record_binding (dbs, fingerprint, email, user_id,
+                          policy == TOFU_POLICY_NONE ? TOFU_POLICY_AUTO : policy,
+                          effective_policy, conflict, 1, 0, now) != 0)
+        log_error (_("error setting TOFU binding's policy"
+                     " to %s\n"), tofu_policy_str (policy));
+    }
 
-  if (change_conflicting_to_ask)
+  /* If the caller wants the set of conflicts, return it.  */
+  if (effective_policy == TOFU_POLICY_ASK && conflict_setp)
+    {
+      if (! conflict_set)
+        conflict_set = build_conflict_set (dbs, pk, fingerprint, email);
+      *conflict_setp = conflict_set;
+    }
+  else
     {
-      /* Mark any conflicting bindings that have an automatic policy as
-       * now requiring confirmation.  */
+      free_strlist (conflict_set);
 
-      if (! in_transaction)
-        {
-          begin_transaction (ctrl, 0);
-          in_transaction = 1;
-        }
+      if (conflict_setp)
+        *conflict_setp = NULL;
+    }
+
+  xfree (conflict_orig);
+  if (conflict != conflict_orig)
+    xfree (conflict);
+  free_strlist (results);
+
+  return effective_policy;
+}
+
+
+/* Return the trust level (TRUST_NEVER, etc.) for the binding
+ * <FINGERPRINT, EMAIL> (email is already normalized).  If no policy
+ * is registered, returns TOFU_POLICY_NONE.  If an error occurs,
+ * returns _tofu_GET_TRUST_ERROR.
+ *
+ * PK is the public key object for FINGERPRINT.
+ *
+ * USER_ID is the unadulterated user id.
+ *
+ * If MAY_ASK is set, then we may interact with the user.  This is
+ * necessary if there is a conflict or the binding's policy is
+ * TOFU_POLICY_ASK.  In the case of a conflict, we set the new
+ * conflicting binding's policy to TOFU_POLICY_ASK.  In either case,
+ * we return TRUST_UNDEFINED.  Note: if MAY_ASK is set, then this
+ * function must not be called while in a transaction!  */
+static enum tofu_policy
+get_trust (ctrl_t ctrl, PKT_public_key *pk,
+           const char *fingerprint, const char *email,
+          const char *user_id, int may_ask, time_t now)
+{
+  tofu_dbs_t dbs = ctrl->tofu.dbs;
+  int in_transaction = 0;
+  enum tofu_policy policy;
+  int rc;
+  char *sqerr = NULL;
+  strlist_t conflict_set = NULL;
+  int trust_level = TRUST_UNKNOWN;
+  strlist_t iter;
+
+  log_assert (dbs);
+
+  if (may_ask)
+    log_assert (dbs->in_transaction == 0);
+
+  if (opt.batch)
+    may_ask = 0;
+
+  log_assert (pk_is_primary (pk));
 
-      /* If we weren't allowed to ask, also update this key as
-       * conflicting with itself.  */
-      for (iter = may_ask ? conflict_set->next : conflict_set;
-           iter; iter = iter->next)
+  /* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust
+     levels.  */
+  log_assert (_tofu_GET_TRUST_ERROR != TRUST_UNKNOWN
+              && _tofu_GET_TRUST_ERROR != TRUST_EXPIRED
+              && _tofu_GET_TRUST_ERROR != TRUST_UNDEFINED
+              && _tofu_GET_TRUST_ERROR != TRUST_NEVER
+              && _tofu_GET_TRUST_ERROR != TRUST_MARGINAL
+              && _tofu_GET_TRUST_ERROR != TRUST_FULLY
+              && _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE);
+
+  /* If the key is ultimately trusted, there is nothing to do.  */
+  {
+    u32 kid[2];
+
+    keyid_from_pk (pk, kid);
+    if (tdb_keyid_is_utk (kid))
+      {
+        trust_level = TRUST_ULTIMATE;
+        goto out;
+      }
+  }
+
+  begin_transaction (ctrl, 0);
+  in_transaction = 1;
+
+  policy = get_policy (dbs, pk, fingerprint, user_id, email, &conflict_set, now);
+  if (policy == TOFU_POLICY_AUTO)
+    {
+      policy = opt.tofu_default_policy;
+      if (DBG_TRUST)
+       log_debug ("TOFU: binding <key: %s, user id: %s>'s policy is"
+                   " auto (default: %s).\n",
+                  fingerprint, email,
+                  tofu_policy_str (opt.tofu_default_policy));
+    }
+  switch (policy)
+    {
+    case TOFU_POLICY_AUTO:
+    case TOFU_POLICY_GOOD:
+    case TOFU_POLICY_UNKNOWN:
+    case TOFU_POLICY_BAD:
+      /* The saved judgement is auto -> auto, good, unknown or bad.
+       * We don't need to ask the user anything.  */
+      if (DBG_TRUST)
+       log_debug ("TOFU: Known binding <key: %s, user id: %s>'s policy: %s\n",
+                  fingerprint, email, tofu_policy_str (policy));
+      trust_level = tofu_policy_to_trust_level (policy);
+      goto out;
+
+    case TOFU_POLICY_ASK:
+      /* We need to ask the user what to do.  */
+      break;
+
+    case _tofu_GET_POLICY_ERROR:
+      trust_level = _tofu_GET_TRUST_ERROR;
+      goto out;
+
+    default:
+      log_bug ("%s: Impossible value for policy (%d)\n", __func__, policy);
+    }
+
+
+  /* We get here if:
+   *
+   *   1. The saved policy is auto and the default policy is ask
+   *      (get_policy() == TOFU_POLICY_AUTO
+   *       && opt.tofu_default_policy == TOFU_POLICY_ASK)
+   *
+   *   2. The saved policy is ask (either last time the user selected
+   *      accept once or reject once or there was a conflict and this
+   *      binding's policy was changed from auto to ask)
+   *      (policy == TOFU_POLICY_ASK).
+   */
+  log_assert (policy == TOFU_POLICY_ASK);
+
+  if (may_ask)
+    {
+      /* We can't be in a normal transaction in ask_about_binding.  */
+      end_transaction (ctrl, 0);
+      in_transaction = 0;
+
+      /* If we get here, we need to ask the user about the binding.  */
+      ask_about_binding (ctrl,
+                         &policy,
+                         &trust_level,
+                         conflict_set,
+                         fingerprint,
+                         email,
+                         user_id,
+                         now);
+    }
+  else
+    {
+      for (iter = conflict_set; iter; iter = iter->next)
+        show_statistics (dbs, iter->d, email,
+                         TOFU_POLICY_ASK, NULL, 1, now);
+
+      trust_level = TRUST_UNDEFINED;
+    }
+
+  /* Mark any conflicting bindings that have an automatic policy as
+   * now requiring confirmation.  Note: we do this after we ask for
+   * confirmation so that when the current policy is printed, it is
+   * correct.  */
+  if (! in_transaction)
+    {
+      begin_transaction (ctrl, 0);
+      in_transaction = 1;
+    }
+
+  /* The conflict set should always contain at least one element:
+   * the current key.  */
+  log_assert (conflict_set);
+
+  for (iter = conflict_set->next; iter; iter = iter->next)
+    {
+      /* We don't immediately set the effective policy to 'ask,
+         because  */
+      rc = gpgsql_exec_printf
+        (dbs->db, NULL, NULL, &sqerr,
+         "update bindings set effective_policy = %d, conflict = %Q"
+         " where email = %Q and fingerprint = %Q and effective_policy != %d;",
+         TOFU_POLICY_NONE, fingerprint,
+         email, iter->d, TOFU_POLICY_ASK);
+      if (rc)
         {
-          rc = gpgsql_exec_printf
-            (dbs->db, NULL, NULL, &sqerr,
-             "update bindings set policy = %d, conflict = %Q"
-             " where email = %Q and fingerprint = %Q and policy = %d;",
-             TOFU_POLICY_ASK, fingerprint,
-             email, iter->d, TOFU_POLICY_AUTO);
-          if (rc)
-            {
-              log_error (_("error changing TOFU policy: %s\n"), sqerr);
-              print_further_info ("binding: <key: %s, user id: %s>",
-                                  fingerprint, user_id);
-              sqlite3_free (sqerr);
-              sqerr = NULL;
-              rc = gpg_error (GPG_ERR_GENERAL);
-            }
-          else if (DBG_TRUST)
-            log_debug ("Set %s to conflict with %s\n",
-                       iter->d, fingerprint);
+          log_error (_("error changing TOFU policy: %s\n"), sqerr);
+          print_further_info ("binding: <key: %s, user id: %s>",
+                              fingerprint, user_id);
+          sqlite3_free (sqerr);
+          sqerr = NULL;
+          rc = gpg_error (GPG_ERR_GENERAL);
         }
+      else if (DBG_TRUST)
+        log_debug ("Set %s to conflict with %s\n",
+                   iter->d, fingerprint);
     }
 
+ out:
   if (in_transaction)
     end_transaction (ctrl, 0);
 
@@ -2493,7 +2814,7 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
 
 
 /* Return a malloced string of the form
- *    "7 months, 1 day, 5 minutes, 0 seconds"
+ *    "7~months"
  * The caller should replace all '~' in the returned string by a space
  * and also free the returned string.
  *
@@ -2503,127 +2824,46 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
 static char *
 time_ago_str (long long int t)
 {
-  estream_t fp;
-  int years = 0;
-  int months = 0;
-  int days = 0;
-  int hours = 0;
-  int minutes = 0;
-  int seconds = 0;
-
-  /* The number of units that we've printed so far.  */
-  int count = 0;
-  /* The first unit that we printed (year = 0, month = 1,
-     etc.).  */
-  int first = -1;
-  /* The current unit.  */
-  int i = 0;
-
-  char *str;
-
   /* It would be nice to use a macro to do this, but gettext
      works on the unpreprocessed code.  */
 #define MIN_SECS (60)
 #define HOUR_SECS (60 * MIN_SECS)
 #define DAY_SECS (24 * HOUR_SECS)
+#define WEEK_SECS (7 * DAY_SECS)
 #define MONTH_SECS (30 * DAY_SECS)
 #define YEAR_SECS (365 * DAY_SECS)
 
-  if (t > YEAR_SECS)
+  if (t > 2 * YEAR_SECS)
     {
-      years = t / YEAR_SECS;
-      t -= years * YEAR_SECS;
+      long long int c = t / YEAR_SECS;
+      return xtryasprintf (ngettext("%lld~year", "%lld~years", c), c);
     }
-  if (t > MONTH_SECS)
+  if (t > 2 * MONTH_SECS)
     {
-      months = t / MONTH_SECS;
-      t -= months * MONTH_SECS;
+      long long int c = t / MONTH_SECS;
+      return xtryasprintf (ngettext("%lld~month", "%lld~months", c), c);
     }
-  if (t > DAY_SECS)
+  if (t > 2 * WEEK_SECS)
     {
-      days = t / DAY_SECS;
-      t -= days * DAY_SECS;
+      long long int c = t / WEEK_SECS;
+      return xtryasprintf (ngettext("%lld~week", "%lld~weeks", c), c);
     }
-  if (t > HOUR_SECS)
+  if (t > 2 * DAY_SECS)
     {
-      hours = t / HOUR_SECS;
-      t -= hours * HOUR_SECS;
+      long long int c = t / DAY_SECS;
+      return xtryasprintf (ngettext("%lld~day", "%lld~days", c), c);
     }
-  if (t > MIN_SECS)
+  if (t > 2 * HOUR_SECS)
     {
-      minutes = t / MIN_SECS;
-      t -= minutes * MIN_SECS;
+      long long int c = t / HOUR_SECS;
+      return xtryasprintf (ngettext("%lld~hour", "%lld~hours", c), c);
     }
-  seconds = t;
-
-#undef MIN_SECS
-#undef HOUR_SECS
-#undef DAY_SECS
-#undef MONTH_SECS
-#undef YEAR_SECS
-
-  fp = es_fopenmem (0, "rw,samethread");
-  if (! fp)
-    log_fatal ("error creating memory stream: %s\n",
-               gpg_strerror (gpg_error_from_syserror()));
-
-  if (years)
-    {
-      /* TRANSLATORS: The tilde ('~') is used here to indicate a
-       * non-breakable space  */
-      es_fprintf (fp, ngettext("%d~year", "%d~years", years), years);
-      count ++;
-      first = i;
-    }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && months)
-    {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~month", "%d~months", months), months);
-      count ++;
-      first = i;
-    }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && days)
+  if (t > 2 * MIN_SECS)
     {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~day", "%d~days", days), days);
-      count ++;
-      first = i;
+      long long int c = t / MIN_SECS;
+      return xtryasprintf (ngettext("%lld~minute", "%lld~minutes", c), c);
     }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && hours)
-    {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~hour", "%d~hours", hours), hours);
-      count ++;
-      first = i;
-    }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0 && minutes)
-    {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~minute", "%d~minutes", minutes), minutes);
-      count ++;
-      first = i;
-    }
-  i ++;
-  if ((first == -1 || i - first <= 3) && count <= 0)
-    {
-      if (count)
-        es_fprintf (fp, ", ");
-      es_fprintf (fp, ngettext("%d~second", "%d~seconds", seconds), seconds);
-    }
-
-  es_fputc (0, fp);
-  if (es_fclose_snatch (fp, (void **) &str, NULL))
-    log_fatal ("error snatching memory stream\n");
-
-  return str;
+  return xtryasprintf (ngettext("%lld~second", "%lld~seconds", t), t);
 }
 
 
@@ -2635,64 +2875,77 @@ write_stats_status (estream_t fp,
                     unsigned long signature_count,
                     unsigned long signature_first_seen,
                     unsigned long signature_most_recent,
+                    unsigned long signature_days,
                     unsigned long encryption_count,
                     unsigned long encryption_first_done,
-                    unsigned long encryption_most_recent)
+                    unsigned long encryption_most_recent,
+                    unsigned long encryption_days)
 {
-  const char *validity;
-  unsigned long messages;
+  int summary;
+  int validity;
+  unsigned long days;
 
   /* Use the euclidean distance (m = sqrt(a^2 + b^2)) rather then the
      sum of the magnitudes (m = a + b) to ensure a balance between
      verified signatures and encrypted messages.  */
-  messages = sqrtu32 (signature_count * signature_count
-                      + encryption_count * encryption_count);
-
-  if (messages < 1)
-    validity = "1"; /* Key without history.  */
-  else if (messages < 2 * BASIC_TRUST_THRESHOLD)
-    validity = "2"; /* Key with too little history.  */
-  else if (messages < 2 * FULL_TRUST_THRESHOLD)
-    validity = "3"; /* Key with enough history for basic trust.  */
+  days = sqrtu32 (signature_days * signature_days
+                  + encryption_days * encryption_days);
+
+  if (days < 1)
+    validity = 1; /* Key without history.  */
+  else if (days < 2 * BASIC_TRUST_THRESHOLD)
+    validity = 2; /* Key with too little history.  */
+  else if (days < 2 * FULL_TRUST_THRESHOLD)
+    validity = 3; /* Key with enough history for basic trust.  */
   else
-    validity = "4"; /* Key with a lot of history.  */
+    validity = 4; /* Key with a lot of history.  */
+
+  if (policy == TOFU_POLICY_ASK)
+    summary = 0; /* Key requires attention.  */
+  else
+    summary = validity;
 
   if (fp)
     {
-      es_fprintf (fp, "tfs:1:%s:%lu:%lu:%s:%lu:%lu:%lu:%lu:\n",
-                  validity, signature_count, encryption_count,
+      es_fprintf (fp, "tfs:1:%d:%lu:%lu:%s:%lu:%lu:%lu:%lu:%d:%lu:%lu:\n",
+                  summary, signature_count, encryption_count,
                   tofu_policy_str (policy),
                   signature_first_seen, signature_most_recent,
-                  encryption_first_done, encryption_most_recent);
+                  encryption_first_done, encryption_most_recent,
+                  validity, signature_days, encryption_days);
     }
   else
     {
       write_status_printf (STATUS_TOFU_STATS,
-                           "%s %lu %lu %s %lu %lu %lu %lu",
-                           validity,
+                           "%d %lu %lu %s %lu %lu %lu %lu %d %lu %lu",
+                           summary,
                            signature_count,
                            encryption_count,
                            tofu_policy_str (policy),
                            signature_first_seen,
                            signature_most_recent,
                            encryption_first_done,
-                           encryption_most_recent);
+                           encryption_most_recent,
+                           validity,
+                           signature_days, encryption_days);
     }
 }
 
 /* Note: If OUTFP is not NULL, this function merely prints a "tfs" record
- * to OUTFP.  In this case USER_ID is not required.
+ * to OUTFP.
+ *
+ * POLICY is the key's policy (as returned by get_policy).
  *
- * Returns whether the caller should call show_warning after iterating
- * over all user ids.
+ * Returns 0 if if ONLY_STATUS_FD is set.  Otherwise, returns whether
+ * the caller should call show_warning after iterating over all user
+ * ids.
  */
 static int
-show_statistics (tofu_dbs_t dbs, const char *fingerprint,
-                const char *email, const char *user_id,
-                estream_t outfp, time_t now)
+show_statistics (tofu_dbs_t dbs,
+                 const char *fingerprint, const char *email,
+                 enum tofu_policy policy,
+                estream_t outfp, int only_status_fd, time_t now)
 {
-  enum tofu_policy policy = get_policy (dbs, fingerprint, email, NULL);
-
   char *fingerprint_pp;
   int rc;
   strlist_t strlist = NULL;
@@ -2701,13 +2954,16 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
   unsigned long signature_first_seen = 0;
   unsigned long signature_most_recent = 0;
   unsigned long signature_count = 0;
+  unsigned long signature_days = 0;
   unsigned long encryption_first_done = 0;
   unsigned long encryption_most_recent = 0;
   unsigned long encryption_count = 0;
+  unsigned long encryption_days = 0;
 
   int show_warning = 0;
 
-  (void) user_id;
+  if (only_status_fd && ! is_status_enabled ())
+    return 0;
 
   fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
 
@@ -2727,18 +2983,38 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
       rc = gpg_error (GPG_ERR_GENERAL);
       goto out;
     }
+  rc = gpgsql_exec_printf
+    (dbs->db, strings_collect_cb, &strlist, &err,
+     "select count (*) from\n"
+     "  (select round(signatures.time / (24 * 60 * 60)) day\n"
+     "    from signatures\n"
+     "    left join bindings on signatures.binding = bindings.oid\n"
+     "    where fingerprint = %Q and email = %Q\n"
+     "    group by day);",
+     fingerprint, email);
+  if (rc)
+    {
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("getting signature statistics (by day)");
+      sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
+      goto out;
+    }
 
   if (strlist)
     {
-      /* We expect exactly 3 elements.  */
+      /* We expect exactly 4 elements.  */
       log_assert (strlist->next);
       log_assert (strlist->next->next);
-      log_assert (! strlist->next->next->next);
+      log_assert (strlist->next->next->next);
+      log_assert (! strlist->next->next->next->next);
 
-      string_to_ulong (&signature_count, strlist->d, -1, __LINE__);
-      string_to_ulong (&signature_first_seen, strlist->next->d, -1, __LINE__);
-      string_to_ulong (&signature_most_recent,
+      string_to_ulong (&signature_days, strlist->d, -1, __LINE__);
+      string_to_ulong (&signature_count, strlist->next->d, -1, __LINE__);
+      string_to_ulong (&signature_first_seen,
                        strlist->next->next->d, -1, __LINE__);
+      string_to_ulong (&signature_most_recent,
+                       strlist->next->next->next->d, -1, __LINE__);
 
       free_strlist (strlist);
       strlist = NULL;
@@ -2760,18 +3036,38 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
       rc = gpg_error (GPG_ERR_GENERAL);
       goto out;
     }
+  rc = gpgsql_exec_printf
+    (dbs->db, strings_collect_cb, &strlist, &err,
+     "select count (*) from\n"
+     "  (select round(encryptions.time / (24 * 60 * 60)) day\n"
+     "    from encryptions\n"
+     "    left join bindings on encryptions.binding = bindings.oid\n"
+     "    where fingerprint = %Q and email = %Q\n"
+     "    group by day);",
+     fingerprint, email);
+  if (rc)
+    {
+      log_error (_("error reading TOFU database: %s\n"), err);
+      print_further_info ("getting encryption statistics (by day)");
+      sqlite3_free (err);
+      rc = gpg_error (GPG_ERR_GENERAL);
+      goto out;
+    }
 
   if (strlist)
     {
-      /* We expect exactly 3 elements.  */
+      /* We expect exactly 4 elements.  */
       log_assert (strlist->next);
       log_assert (strlist->next->next);
-      log_assert (! strlist->next->next->next);
+      log_assert (strlist->next->next->next);
+      log_assert (! strlist->next->next->next->next);
 
-      string_to_ulong (&encryption_count, strlist->d, -1, __LINE__);
-      string_to_ulong (&encryption_first_done, strlist->next->d, -1, __LINE__);
-      string_to_ulong (&encryption_most_recent,
+      string_to_ulong (&encryption_days, strlist->d, -1, __LINE__);
+      string_to_ulong (&encryption_count, strlist->next->d, -1, __LINE__);
+      string_to_ulong (&encryption_first_done,
                        strlist->next->next->d, -1, __LINE__);
+      string_to_ulong (&encryption_most_recent,
+                       strlist->next->next->next->d, -1, __LINE__);
 
       free_strlist (strlist);
       strlist = NULL;
@@ -2785,11 +3081,13 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
                       signature_count,
                       signature_first_seen,
                       signature_most_recent,
+                      signature_days,
                       encryption_count,
                       encryption_first_done,
-                      encryption_most_recent);
+                      encryption_most_recent,
+                      encryption_days);
 
-  if (!outfp)
+  if (!outfp && !only_status_fd)
     {
       estream_t fp;
       char *msg;
@@ -2799,56 +3097,55 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
         log_fatal ("error creating memory stream: %s\n",
                    gpg_strerror (gpg_error_from_syserror()));
 
-      es_fprintf (fp, _("%s: "), email);
-
-      if (signature_count == 0)
-        {
-          es_fprintf (fp, _("Verified %ld signatures"), 0L);
-          es_fputc ('\n', fp);
-        }
-      else
+      if (signature_count == 0 && encryption_count == 0)
         {
-          char *first_seen_ago_str = time_ago_str (now - signature_first_seen);
-
-          /* TRANSLATORS: The final %s is replaced by a string like
-             "7 months, 1 day, 5 minutes, 0 seconds". */
           es_fprintf (fp,
-                      ngettext("Verified %ld signature in the past %s",
-                               "Verified %ld signatures in the past %s",
-                               signature_count),
-                      signature_count, first_seen_ago_str);
-
-          xfree (first_seen_ago_str);
-        }
-
-      if (encryption_count == 0)
-        {
-          es_fprintf (fp, _(", and encrypted %ld messages"), 0L);
+                      _("%s: Verified 0~signatures and encrypted 0~messages."),
+                      email);
         }
       else
         {
-          char *first_done_ago_str = time_ago_str (now - encryption_first_done);
+          if (signature_count == 0)
+            es_fprintf (fp, _("%s: Verified 0 signatures."), email);
+          else
+            {
+              /* TRANSLATORS: The final %s is replaced by a string like
+                 "7~months". */
+              char *ago_str = time_ago_str (now - signature_first_seen);
+              es_fprintf
+                (fp,
+                 ngettext("%s: Verified %ld~signature in the past %s.",
+                          "%s: Verified %ld~signatures in the past %s.",
+                          signature_count),
+                 email, signature_count, ago_str);
+              xfree (ago_str);
+            }
 
-          /* TRANSLATORS: The final %s is replaced by a string like
-             "7 months, 1 day, 5 minutes, 0 seconds". */
-          es_fprintf (fp,
-                      ngettext(", and encrypted %ld message in the past %s",
-                               ", and encrypted %ld messages in the past %s",
-                               encryption_count),
-                      encryption_count, first_done_ago_str);
+          es_fputs ("  ", fp);
 
-          xfree (first_done_ago_str);
+          if (encryption_count == 0)
+            es_fprintf (fp, _("Encrypted 0 messages."));
+          else
+            {
+              char *ago_str = time_ago_str (now - encryption_first_done);
+
+              /* TRANSLATORS: The final %s is replaced by a string like
+                 "7~months". */
+              es_fprintf (fp,
+                          ngettext("Encrypted %ld~message in the past %s.",
+                                   "Encrypted %ld~messages in the past %s.",
+                                   encryption_count),
+                          encryption_count, ago_str);
+              xfree (ago_str);
+            }
         }
 
       if (opt.verbose)
         {
           es_fputs ("  ", fp);
-          es_fputc ('(', fp);
-          es_fprintf (fp, _("policy: %s"), tofu_policy_str (policy));
-          es_fputs (").\n", fp);
+          es_fprintf (fp, _("(policy: %s)"), tofu_policy_str (policy));
         }
-      else
-        es_fputs (".\n", fp);
+      es_fputs ("\n", fp);
 
 
       {
@@ -3009,7 +3306,7 @@ tofu_register_signature (ctrl_t ctrl,
   if (rc)
     return rc;
 
-  log_assert (keyid_cmp (pk_keyid (pk), pk_main_keyid (pk)) == 0);
+  log_assert (pk_is_primary (pk));
 
   sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
   fingerprint = hexfingerprint (pk, NULL, 0);
@@ -3093,7 +3390,7 @@ tofu_register_signature (ctrl_t ctrl,
           log_assert (c == 0);
 
           rc = gpgsql_stepx
-            (dbs->db, &dbs->s.register_insert, NULL, NULL, &err,
+            (dbs->db, &dbs->s.register_signature, NULL, NULL, &err,
              "insert into signatures\n"
              " (binding, sig_digest, origin, sig_time, time)\n"
              " values\n"
@@ -3155,13 +3452,13 @@ tofu_register_encryption (ctrl_t ctrl,
     }
 
   if (/* We need the key block to find the primary key.  */
-      keyid_cmp (pk_keyid (pk), pk_main_keyid (pk)) != 0
+      ! pk_is_primary (pk)
       /* We need the key block to find all user ids.  */
       || ! user_id_list)
     kb = get_pubkeyblock (pk->keyid);
 
   /* Make sure PK is a primary key.  */
-  if (keyid_cmp (pk_keyid (pk), pk_main_keyid (pk)) != 0)
+  if (! pk_is_primary (pk))
     pk = kb->pkt->pkt.public_key;
 
   if (! user_id_list)
@@ -3182,7 +3479,7 @@ tofu_register_encryption (ctrl_t ctrl,
       free_user_id_list = 1;
 
       if (! user_id_list)
-        log_info (_("WARNING: Encrypting to %s, which has no"
+        log_info (_("WARNING: Encrypting to %s, which has no "
                     "non-revoked user ids.\n"),
                   keystr (pk->keyid));
     }
@@ -3209,7 +3506,7 @@ tofu_register_encryption (ctrl_t ctrl,
         }
 
       rc = gpgsql_stepx
-        (dbs->db, &dbs->s.register_insert, NULL, NULL, &err,
+        (dbs->db, &dbs->s.register_encryption, NULL, NULL, &err,
          "insert into encryptions\n"
          " (binding, time)\n"
          " values\n"
@@ -3318,6 +3615,7 @@ tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
   tofu_dbs_t dbs;
   char *fingerprint;
   char *email;
+  enum tofu_policy policy;
 
   if (!*user_id)
     return 0;  /* No TOFU stats possible for an empty ID.  */
@@ -3332,8 +3630,9 @@ tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
 
   fingerprint = hexfingerprint (pk, NULL, 0);
   email = email_from_user_id (user_id);
+  policy = get_policy (dbs, pk, fingerprint, user_id, email, NULL, now);
 
-  show_statistics (dbs, fingerprint, email, user_id, fp, now);
+  show_statistics (dbs, fingerprint, email, policy, fp, 0, now);
 
   xfree (email);
   xfree (fingerprint);
@@ -3408,8 +3707,13 @@ tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
         bindings_valid ++;
 
       if (may_ask && tl != TRUST_ULTIMATE && tl != TRUST_EXPIRED)
-        need_warning |=
-          show_statistics (dbs, fingerprint, email, user_id->d, NULL, now);
+        {
+          enum tofu_policy policy =
+            get_policy (dbs, pk, fingerprint, user_id->d, email, NULL, now);
+
+          need_warning |=
+            show_statistics (dbs, fingerprint, email, policy, NULL, 0, now);
+        }
 
       if (tl == TRUST_NEVER)
         trust_level = TRUST_NEVER;
@@ -3485,7 +3789,7 @@ tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
   if (DBG_TRUST)
     log_debug ("Setting TOFU policy for %s to %s\n",
               keystr (pk->keyid), tofu_policy_str (policy));
-  if (keyid_cmp (pk_main_keyid (pk), pk_keyid (pk)) != 0)
+  if (! pk_is_primary (pk))
     log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__);
 
   fingerprint = hexfingerprint (pk, NULL, 0);
@@ -3509,7 +3813,7 @@ tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
       email = email_from_user_id (user_id->name);
 
       err = record_binding (dbs, fingerprint, email, user_id->name,
-                            policy, NULL, 1, now);
+                            policy, TOFU_POLICY_NONE, NULL, 0, 1, now);
       if (err)
         {
           log_error (_("error setting policy for key %s, user id \"%s\": %s"),
@@ -3530,23 +3834,6 @@ tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
   return err;
 }
 
-/* Set the TOFU policy for all non-revoked user ids in the KEY with
-   the key id KEYID to POLICY.
-
-   If no key is available with the specified key id, then this
-   function returns GPG_ERR_NO_PUBKEY.
-
-   Returns 0 on success and an error code otherwise.  */
-gpg_error_t
-tofu_set_policy_by_keyid (ctrl_t ctrl, u32 *keyid, enum tofu_policy policy)
-{
-  kbnode_t keyblock = get_pubkeyblock (keyid);
-  if (! keyblock)
-    return gpg_error (GPG_ERR_NO_PUBKEY);
-
-  return tofu_set_policy (ctrl, keyblock, policy);
-}
-
 /* Return the TOFU policy for the specified binding in *POLICY.  If no
    policy has been set for the binding, sets *POLICY to
    TOFU_POLICY_NONE.
@@ -3558,13 +3845,13 @@ gpg_error_t
 tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
                 enum tofu_policy *policy)
 {
+  time_t now = gnupg_get_time ();
   tofu_dbs_t dbs;
   char *fingerprint;
   char *email;
 
   /* Make sure PK is a primary key.  */
-  log_assert (pk->main_keyid[0] == pk->keyid[0]
-              && pk->main_keyid[1] == pk->keyid[1]);
+  log_assert (pk_is_primary (pk));
 
   dbs = opendbs (ctrl);
   if (! dbs)
@@ -3578,7 +3865,7 @@ tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
 
   email = email_from_user_id (user_id->name);
 
-  *policy = get_policy (dbs, fingerprint, email, NULL);
+  *policy = get_policy (dbs, pk, fingerprint, user_id->name, email, NULL, now);
 
   xfree (email);
   xfree (fingerprint);
@@ -3586,3 +3873,40 @@ tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
     return gpg_error (GPG_ERR_GENERAL);
   return 0;
 }
+
+gpg_error_t
+tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb)
+{
+  tofu_dbs_t dbs;
+  PKT_public_key *pk;
+  char *fingerprint;
+  char *sqlerr = NULL;
+  int rc;
+
+  /* Make sure PK is a primary key.  */
+  setup_main_keyids (kb);
+  pk = kb->pkt->pkt.public_key;
+  log_assert (pk_is_primary (pk));
+
+  dbs = opendbs (ctrl);
+  if (! dbs)
+    {
+      log_error (_("error opening TOFU database: %s\n"),
+                 gpg_strerror (GPG_ERR_GENERAL));
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  fingerprint = hexfingerprint (pk, NULL, 0);
+
+  rc = gpgsql_stepx (dbs->db, NULL, NULL, NULL, &sqlerr,
+                     "update bindings set effective_policy = ?"
+                     " where fingerprint = ?;",
+                     GPGSQL_ARG_INT, (int) TOFU_POLICY_NONE,
+                     GPGSQL_ARG_STRING, fingerprint,
+                     GPGSQL_ARG_END);
+  xfree (fingerprint);
+
+  if (rc == _tofu_GET_POLICY_ERROR)
+    return gpg_error (GPG_ERR_GENERAL);
+  return 0;
+}
index f114443..7b1beea 100644 (file)
@@ -120,11 +120,6 @@ int tofu_get_validity (ctrl_t ctrl,
    POLICY.  */
 gpg_error_t tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy);
 
-/* Set the TOFU policy for all non-revoked users in the key with the
-   key id KEYID to POLICY.  */
-gpg_error_t tofu_set_policy_by_keyid (ctrl_t ctrl,
-                                      u32 *keyid, enum tofu_policy policy);
-
 /* Return the TOFU policy for the specified binding in *POLICY.  */
 gpg_error_t tofu_get_policy (ctrl_t ctrl,
                              PKT_public_key *pk, PKT_user_id *user_id,
@@ -139,4 +134,9 @@ void tofu_end_batch_update (ctrl_t ctrl);
 /* Release all of the resources associated with a DB meta-handle.  */
 void tofu_closedbs (ctrl_t ctrl);
 
+/* Whenever a key is modified (e.g., a user id is added or revoked, a
+ * new signature, etc.), this function should be called to cause TOFU
+ * to update its world view.  */
+gpg_error_t tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb);
+
 #endif /*G10_TOFU_H*/
index 2a829b8..080926a 100644 (file)
@@ -151,7 +151,7 @@ uid_trust_string_fixed (ctrl_t ctrl, PKT_public_key *key, PKT_user_id *uid)
     return                         _("[ expired]");
   else if(key)
     {
-      switch (get_validity (ctrl, key, uid, NULL, 0) & TRUST_MASK)
+      switch (get_validity (ctrl, NULL, key, uid, NULL, 0) & TRUST_MASK)
         {
         case TRUST_UNKNOWN:   return _("[ unknown]");
         case TRUST_EXPIRED:   return _("[ expired]");
@@ -297,12 +297,13 @@ check_or_update_trustdb (ctrl_t ctrl)
 
 
 /*
- * Return the validity information for PK.  If the namehash is not
- * NULL, the validity of the corresponding user ID is returned,
- * otherwise, a reasonable value for the entire key is returned.
+ * Return the validity information for KB/PK (at least one must be
+ * non-NULL).  If the namehash is not NULL, the validity of the
+ * corresponding user ID is returned, otherwise, a reasonable value
+ * for the entire key is returned.
  */
 unsigned int
-get_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid,
+get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid,
               PKT_signature *sig, int may_ask)
 {
   int rc;
@@ -310,6 +311,16 @@ get_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid,
   u32 kid[2];
   PKT_public_key *main_pk;
 
+  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;
+    }
+
   if (uid)
     namehash_from_uid (uid);
 
@@ -317,17 +328,22 @@ get_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid,
   if (pk->main_keyid[0] != kid[0] || pk->main_keyid[1] != kid[1])
     {
       /* This is a subkey - get the mainkey. */
-      main_pk = xmalloc_clear (sizeof *main_pk);
-      rc = get_pubkey (main_pk, pk->main_keyid);
-      if (rc)
+      if (kb)
+        main_pk = kb->pkt->pkt.public_key;
+      else
         {
-         char *tempkeystr = xstrdup (keystr (pk->main_keyid));
-          log_error ("error getting main key %s of subkey %s: %s\n",
-                     tempkeystr, keystr (kid), gpg_strerror (rc));
-         xfree (tempkeystr);
-          validity = TRUST_UNKNOWN;
-          goto leave;
-       }
+          main_pk = xmalloc_clear (sizeof *main_pk);
+          rc = get_pubkey (main_pk, pk->main_keyid);
+          if (rc)
+            {
+              char *tempkeystr = xstrdup (keystr (pk->main_keyid));
+              log_error ("error getting main key %s of subkey %s: %s\n",
+                         tempkeystr, keystr (kid), gpg_strerror (rc));
+              xfree (tempkeystr);
+              validity = TRUST_UNKNOWN;
+              goto leave;
+            }
+        }
     }
   else
     main_pk = pk;
@@ -335,7 +351,7 @@ get_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid,
 #ifdef NO_TRUST_MODELS
   validity = TRUST_UNKNOWN;
 #else
-  validity = tdb_get_validity_core (ctrl, pk, uid, main_pk, sig, may_ask);
+  validity = tdb_get_validity_core (ctrl, kb, pk, uid, main_pk, sig, may_ask);
 #endif
 
  leave:
@@ -350,21 +366,28 @@ get_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid,
     validity = ((validity & (~TRUST_MASK | TRUST_FLAG_PENDING_CHECK))
                 | TRUST_EXPIRED);
 
-  if (main_pk != pk)
+  if (main_pk != pk && !kb)
     free_public_key (main_pk);
   return validity;
 }
 
 
 int
-get_validity_info (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid)
+get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk,
+                   PKT_user_id *uid)
 {
   int trustlevel;
 
+  if (kb && pk)
+    log_assert (keyid_cmp (pk_main_keyid (pk),
+                           pk_main_keyid (kb->pkt->pkt.public_key)) == 0);
+
+  if (! pk && kb)
+    pk = kb->pkt->pkt.public_key;
   if (!pk)
     return '?';  /* Just in case a NULL PK is passed.  */
 
-  trustlevel = get_validity (ctrl, pk, uid, NULL, 0);
+  trustlevel = get_validity (ctrl, kb, pk, uid, NULL, 0);
   if ((trustlevel & TRUST_FLAG_REVOKED))
     return 'r';
   return trust_letter (trustlevel);
@@ -379,7 +402,7 @@ get_validity_string (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid)
   if (!pk)
     return "err";  /* Just in case a NULL PK is passed.  */
 
-  trustlevel = get_validity (ctrl, pk, uid, NULL, 0);
+  trustlevel = get_validity (ctrl, NULL, pk, uid, NULL, 0);
   if ((trustlevel & TRUST_FLAG_REVOKED))
     return _("revoked");
   return trust_value_to_string (trustlevel);
index edae6ef..d402cb2 100644 (file)
@@ -324,6 +324,13 @@ tdb_keyid_is_utk (u32 *kid)
 
   return 0;
 }
+
+/* Return the list of ultimately trusted keys.  */
+struct key_item *
+tdb_utks (void)
+{
+  return utk_list;
+}
 \f
 /*********************************************
  *********** TrustDB stuff *******************
@@ -976,13 +983,15 @@ tdb_check_trustdb_stale (ctrl_t ctrl)
 }
 
 /*
- * Return the validity information for PK.  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.
+ * 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,
@@ -995,6 +1004,17 @@ tdb_get_validity_core (ctrl_t ctrl,
   unsigned int tofu_validity = TRUST_UNKNOWN;
 #endif
   unsigned int validity = TRUST_UNKNOWN;
+  int free_kb = 0;
+
+  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;
@@ -1023,14 +1043,20 @@ tdb_get_validity_core (ctrl_t ctrl,
 #ifdef USE_TOFU
   if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
     {
-      kbnode_t kb = NULL;
       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)
-       kb = n = get_pubkeyblock (main_pk->keyid);
+        {
+          if (! kb)
+            {
+              kb = get_pubkeyblock (main_pk->keyid);
+              free_kb = 1;
+            }
+          n = kb;
+        }
 
       if (DBG_TRUST && sig && sig->signers_uid)
         log_debug ("TOFU: only considering user id: '%s'\n",
@@ -1125,7 +1151,8 @@ tdb_get_validity_core (ctrl_t ctrl,
                                            may_ask);
 
       free_strlist (user_id_list);
-      release_kbnode (kb);
+      if (free_kb)
+        release_kbnode (kb);
     }
 #endif /*USE_TOFU*/
 
index 77aa79d..6081d10 100644 (file)
@@ -94,9 +94,11 @@ void revalidation_mark (void);
 void check_trustdb_stale (ctrl_t ctrl);
 void check_or_update_trustdb (ctrl_t ctrl);
 
-unsigned int get_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid,
+unsigned int get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk,
+                           PKT_user_id *uid,
                           PKT_signature *sig, int may_ask);
-int get_validity_info (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid);
+int get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk,
+                       PKT_user_id *uid);
 const char *get_validity_string (ctrl_t ctrl,
                                  PKT_public_key *pk, PKT_user_id *uid);
 
@@ -117,6 +119,9 @@ void tdb_register_trusted_keyid (u32 *keyid);
 void tdb_register_trusted_key (const char *string);
 /* Returns whether KID is on the list of ultimately trusted keys.  */
 int tdb_keyid_is_utk (u32 *kid);
+/* Return the list of ultimately trusted keys.  The caller must not
+ * modify this list nor must it free the list.  */
+struct key_item *tdb_utks (void);
 void check_trustdb (ctrl_t ctrl);
 void update_trustdb (ctrl_t ctrl);
 int setup_trustdb( int level, const char *dbname );
@@ -132,7 +137,7 @@ void tdb_check_or_update (ctrl_t ctrl);
 
 int tdb_cache_disabled_value (PKT_public_key *pk);
 
-unsigned int tdb_get_validity_core (ctrl_t ctrl,
+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);
index 9df52ad..56c6d97 100644 (file)
--- a/po/ca.po
+++ b/po/ca.po
@@ -1995,6 +1995,11 @@ msgstr "genera un nou parell de claus"
 msgid "quickly revoke a user-id"
 msgstr "genera un nou parell de claus"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "genera un nou parell de claus"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2111,12 +2116,22 @@ msgstr ""
 # Crec q (A)lice (orig.), (B)ob (dest.), etc. són noms usats pel Zimmerman
 # en el manual original de PGP.  A, B, C...  ivb
 # En efecte. Idem per a Mallory més endavant. Els deixe com a l'original. jm
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2125,7 +2140,7 @@ msgstr ""
 "Exemples:\n"
 "\n"
 " -se -r Bob [fitxer]        signa i xifra per a l'usuari Bob\n"
-" --clearsign [fitxer]       crea una signatura en text clar\n"
+" --clear-sign [fitxer]       crea una signatura en text clar\n"
 " --detach-sign [fitxer]     crea una signatura separada\n"
 " --list-keys [noms]         mostra claus\n"
 " --fingerprint [noms]       mostra empremtes digitals\n"
@@ -2517,23 +2532,10 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "AVÍS: s'han donat destinataris (-r) sense usar xifratge de clau pública\n"
 
-msgid "--store [filename]"
-msgstr "--store [nom_del_fitxer]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [nom_del_fitxer]"
-
 #, fuzzy, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "ha fallat el desxifratge: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [nom_del_fitxer]"
-
-#, fuzzy
-msgid "--symmetric --encrypt [filename]"
-msgstr "--sign --encrypt [nom_del_fitxer]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2541,16 +2543,6 @@ msgstr ""
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "no podeu usar %s mentre esteu en mode %s\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [nom_del_fitxer]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [nom_del_fitxer]"
-
-#, fuzzy
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--sign --encrypt [nom_del_fitxer]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2558,28 +2550,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "no podeu usar %s mentre esteu en mode %s\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [nom_del_fitxer]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [nom_del_fitxer]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [nom_del_fitxer]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key user-id"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key user-id"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key user-id [ordres]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key user-id"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "l'enviament al servidor de claus ha fallat: %s\n"
@@ -2626,9 +2596,6 @@ msgstr "error en la creació de la contrasenya: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[nom_del_fitxer]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Endavant, escriviu el missatge...\n"
 
@@ -3689,6 +3656,10 @@ msgstr "Usuari inexistent.\n"
 msgid "Nothing to sign.\n"
 msgstr "No hi ha res que signar amb la clau %08lX\n"
 
+#, fuzzy, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "%s no és un joc de caràcters vàlid\n"
+
 msgid "Digest: "
 msgstr "Resum: "
 
@@ -5218,7 +5189,7 @@ msgstr ""
 
 #, fuzzy
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5478,7 +5449,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5889,6 +5860,13 @@ msgstr ""
 "Algoritmes suportats:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "error en la creació de la contrasenya: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "error mentre s'enviava a «%s»: %s\n"
 
@@ -5903,6 +5881,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: error en escriure el registre de directoris: %s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "error mentre s'enviava a «%s»: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "error en la lectura de «%s»: %s\n"
 
@@ -5916,8 +5902,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5965,38 +5953,74 @@ msgid "this key"
 msgstr "llista claus"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|algo [fitxers]|imprimeix resums de missatges"
+msgstr[1] "|algo [fitxers]|imprimeix resums de missatges"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "xifrat amb %lu contrasenyes\n"
+msgstr[1] "xifrat amb %lu contrasenyes\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|algo [fitxers]|imprimeix resums de missatges"
+msgstr[1] "|algo [fitxers]|imprimeix resums de missatges"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|algo [fitxers]|imprimeix resums de missatges"
 msgstr[1] "|algo [fitxers]|imprimeix resums de missatges"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|algo [fitxers]|imprimeix resums de missatges"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -6023,78 +6047,87 @@ msgid "resetting keydb: %s\n"
 msgstr "error mentre s'escrivia l'anell «%s»: %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "error en la lectura de «%s»: %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "error en la creació de la contrasenya: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 # Werner FIXME: use ngettext. jm
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "S'han esborrat %d signatures.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|algo [fitxers]|imprimeix resums de missatges"
 msgstr[1] "|algo [fitxers]|imprimeix resums de missatges"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "xifrat amb %lu contrasenyes\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|algo [fitxers]|imprimeix resums de missatges"
 msgstr[1] "|algo [fitxers]|imprimeix resums de missatges"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Política: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -6130,7 +6163,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "error mentre s'enviava a «%s»: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8863,6 +8896,14 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "error en la lectura de «%s»: %s\n"
+
+#, fuzzy, c-format
+msgid "error parsing '%s'\n"
+msgstr "error en la lectura de «%s»: %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8881,6 +8922,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -9077,6 +9121,54 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [nom_del_fitxer]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [nom_del_fitxer]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [nom_del_fitxer]"
+
+#, fuzzy
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--sign --encrypt [nom_del_fitxer]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [nom_del_fitxer]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [nom_del_fitxer]"
+
+#, fuzzy
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [nom_del_fitxer]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [nom_del_fitxer]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [nom_del_fitxer]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [nom_del_fitxer]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key user-id"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key user-id"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key user-id [ordres]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key user-id"
+
+#~ msgid "[filename]"
+#~ msgstr "[nom_del_fitxer]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "no s'ha pogut eliminar el bloc de claus: %s\n"
index 56e61d9..47611b3 100644 (file)
--- a/po/cs.po
+++ b/po/cs.po
@@ -1838,6 +1838,11 @@ msgstr "rychle přidat novou identitu uživatele"
 msgid "quickly revoke a user-id"
 msgstr "rychle přidat novou identitu uživatele"
 
+#, fuzzy
+#| msgid "quickly generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "rychle vytvořit nový pár klíčů"
+
 msgid "full featured key pair generation"
 msgstr "komplexní vytvoření páru klíčů"
 
@@ -1945,19 +1950,29 @@ msgstr ""
 "@\n"
 "(Pro kompletní seznam všech příkazů a možností použijte manuálové stránky.)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
 msgstr ""
 "@\n"
 " -se -r Bob [soubor]        podepsat a zašifrovat pro uživatele Bob\n"
-" --clearsign [soubor]       vytvořit podpis čitelného dokumentu\n"
+" --clear-sign [soubor]       vytvořit podpis čitelného dokumentu\n"
 " --detach-sign [soubor]     vytvořit podpis oddělený od dokumentu\n"
 " --list-keys [jména]        vypsat klíče\n"
 " --fingerprint [jména]      vypsat otisky\n"
@@ -2335,22 +2350,10 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "VAROVÁNÍ: specifikován adresát (-r) bez použití šifrování s veřejným klíčem\n"
 
-msgid "--store [filename]"
-msgstr "--store [jméno souboru]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [jméno souboru]"
-
 #, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "symetrické šifrování „%s“ se nepovedlo: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [jméno souboru]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [jméno souboru]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "nelze použít --symmetric --encrypt s příkazem --s2k-mode 0\n"
 
@@ -2358,15 +2361,6 @@ msgstr "nelze použít --symmetric --encrypt s příkazem --s2k-mode 0\n"
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "nelze použít --symmetric --encrypt v módu %s\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [jméno souboru]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [jméno souboru]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [jméno souboru]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr "nelze použít --symmetric --sign --encrypt s příkazem --s2k-mode 0\n"
 
@@ -2374,27 +2368,6 @@ msgstr "nelze použít --symmetric --sign --encrypt s příkazem --s2k-mode 0\n"
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "nelze použít --symmetric --sign --encrypt v módu %s\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [jméno souboru]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [jméno souboru]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [jméno souboru]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key id_uživatele"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key id_uživatele"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key id_uživatele [příkazy]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd <id-uživatele>"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "odeslání na keyserver se nezdařilo: %s\n"
@@ -2440,9 +2413,6 @@ msgstr "chyba při rozboru názvu klíče „%s“: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr "„%s“ nevypadá jako platné ID klíče, otisk klíče nebo keygrip\n"
 
-msgid "[filename]"
-msgstr "[jméno souboru]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Začněte psát svou zprávu…\n"
 
@@ -3418,6 +3388,11 @@ msgstr "Žádný identifikátor uživatele neodpovídá."
 msgid "Nothing to sign.\n"
 msgstr "Nic na podepsání.\n"
 
+#, fuzzy, c-format
+#| msgid "'%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "„%s“ není platná doba expirace podpisu\n"
+
 msgid "Digest: "
 msgstr "Hash: "
 
@@ -4865,7 +4840,7 @@ msgstr "kořenový certifikát byl nyní označen za důvěryhodný\n"
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5116,7 +5091,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 "Použijte jej pro odvolání tohoto klíče v případě zneužití nebo ztráty\n"
 "soukromého klíče. Avšak bude-li soukromý klíč stále přístupný, bude\n"
@@ -5527,6 +5502,14 @@ msgid "unsupported TOFU database version: %s\n"
 msgstr "nepodporovaný algoritmus: %s"
 
 #, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "chyba při vytváření dočasného souboru: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error sending data: %s\n"
 msgid "error reading TOFU database: %s\n"
 msgstr "chyba při odesílání dat: %s\n"
@@ -5542,6 +5525,15 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "chyba při inicializaci čtecího objektu: %s\n"
 
 #, fuzzy, c-format
+#| msgid "error sending data: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "chyba při odesílání dat: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error opening '%s': %s\n"
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "chyba při otevírání „%s“: %s\n"
@@ -5557,8 +5549,11 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5610,43 +5605,87 @@ msgstr "vypsat seznam klíčů"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "vypsat hash zprávy"
+msgstr[1] "vypsat hash zprávy"
+msgstr[2] "vypsat hash zprávy"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "zašifrováno s heslem %lu\n"
+msgstr[1] "zašifrováno s heslem %lu\n"
+msgstr[2] "zašifrováno s heslem %lu\n"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "vypsat hash zprávy"
+msgstr[1] "vypsat hash zprávy"
+msgstr[2] "vypsat hash zprávy"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "vypsat hash zprávy"
 msgstr[1] "vypsat hash zprávy"
 msgstr[2] "vypsat hash zprávy"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Messages encrypted in the past: %d."
+msgstr "vypsat hash zprávy"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5675,7 +5714,7 @@ msgstr "chyba při zápisu klíče: %s\n"
 
 #, fuzzy, c-format
 #| msgid "error setting OCSP target: %s\n"
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "chyba při nastavování cíle OCSP: %s\n"
 
 #, fuzzy, c-format
@@ -5683,80 +5722,90 @@ msgstr "chyba při nastavování cíle OCSP: %s\n"
 msgid "error changing TOFU policy: %s\n"
 msgstr "chyba při vytváření roury: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%s: "
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Smazáno %d podpisů.\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "vypsat hash zprávy"
 msgstr[1] "vypsat hash zprávy"
 msgstr[2] "vypsat hash zprávy"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "zašifrováno s heslem %lu\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "vypsat hash zprávy"
 msgstr[1] "vypsat hash zprávy"
 msgstr[2] "vypsat hash zprávy"
 
+#, fuzzy, c-format
+#| msgid "validity: %s"
+msgid "(policy: %s)"
+msgstr "platnost: %s"
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5794,7 +5843,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "chyba při odesílání dat: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8390,6 +8439,16 @@ msgstr "Selhalo externí ověření komponenty %s"
 msgid "Note that group specifications are ignored\n"
 msgstr "Vezměte na vědomí, že určení skupiny se ignoruje\n"
 
+#, fuzzy, c-format
+#| msgid "error closing '%s': %s\n"
+msgid "error closing '%s'\n"
+msgstr "chyba při uzavírání „%s“: %s\n"
+
+#, fuzzy, c-format
+#| msgid "error hashing '%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "chyba při výpočtu haše „%s“: %s\n"
+
 msgid "list all components"
 msgstr "vypsat všechny komponenty"
 
@@ -8408,6 +8467,11 @@ msgstr "|KOMPONENTA|zkontrolovat volby"
 msgid "apply global default values"
 msgstr "aplikovat globální implicitní hodnoty"
 
+#, fuzzy
+#| msgid "|FILE|take policy information from FILE"
+msgid "|FILE|update configuration files using FILE"
+msgstr "|SOUBOR|vzít politiky ze SOUBORU"
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "získat adresáře s nastavením @GPGCONF@"
 
@@ -8600,6 +8664,51 @@ msgstr ""
 "Syntaxe: gpg-check-pattern [volby] soubor_se_vzorem\n"
 "Prověří heslo zadané na vstupu proti souboru se vzory\n"
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [jméno souboru]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [jméno souboru]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [jméno souboru]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [jméno souboru]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [jméno souboru]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [jméno souboru]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [jméno souboru]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [jméno souboru]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [jméno souboru]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [jméno souboru]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key id_uživatele"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key id_uživatele"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key id_uživatele [příkazy]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd <id-uživatele>"
+
+#~ msgid "[filename]"
+#~ msgstr "[jméno souboru]"
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "výroba stínového klíče se nezdařila: %s\n"
 
index 018bab0..5185534 100644 (file)
--- a/po/da.po
+++ b/po/da.po
@@ -1951,6 +1951,11 @@ msgstr "opret et nyt nøglepar"
 msgid "quickly revoke a user-id"
 msgstr "opret et nyt nøglepar"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "opret et nyt nøglepar"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2058,12 +2063,22 @@ msgstr ""
 "@\n"
 "(Se manualsiden for en fuldstændig liste over alle kommandoer og tilvalg)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2072,7 +2087,7 @@ msgstr ""
 "Eksempler:\n"
 "\n"
 " -se -r Mikael [fil]        underskriv og krypter for bruger Mikael\n"
-" --clearsign [fil]          lav en ren tekstunderskrift\n"
+" --clear-sign [fil]          lav en ren tekstunderskrift\n"
 " --detach-sign [fil]        lav en separat underskrift\n"
 " --list-keys [navne]        vis nøgler\n"
 " --fingerprint [navne]      vis fingeraftryk\n"
@@ -2470,23 +2485,11 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "ADVARSEL: modtagere (-r) angivet uden brug af offentlig nøglekryptering\n"
 
-msgid "--store [filename]"
-msgstr "--store [filnavn]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [filnavn]"
-
 #, fuzzy, c-format
 #| msgid "symmetric encryption of `%s' failed: %s\n"
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "symmetrisk kryptering af »%s« mislykkedes: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [filnavn]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [filnavn]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "du kan ikke bruge --symmetric --encrypt med --s2k-mode 0\n"
 
@@ -2494,15 +2497,6 @@ msgstr "du kan ikke bruge --symmetric --encrypt med --s2k-mode 0\n"
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "du kan ikke bruge --symmetric --encrypt i tilstanden %s\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [filnavn]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [filnavn]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [filnavn]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr "du kan ikke bruge --symmetric --sign --encrypt med --s2k-mode 0\n"
 
@@ -2510,27 +2504,6 @@ msgstr "du kan ikke bruge --symmetric --sign --encrypt med --s2k-mode 0\n"
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "du kan ikke bruge --symmetric --sign --encrypt i tilstanden %s\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [filnavn]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [filnavn]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [filnavn]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key bruger-id"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key bruger-id"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key bruger-id [kommandoer]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd <bruger-id>"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "nøgleserver send mislykkedes: %s\n"
@@ -2578,9 +2551,6 @@ msgstr "fejl ved lagring af certifikat: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[filnavn]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Gå til sagen og skriv meddelelsen ...\n"
 
@@ -3607,6 +3577,11 @@ msgstr "Ingen sådan bruger-id.\n"
 msgid "Nothing to sign.\n"
 msgstr "Intet at underskrive med nøgle %s\n"
 
+#, fuzzy, c-format
+#| msgid "`%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "»%s« er ikke et gyldigt underskriftudløb\n"
+
 msgid "Digest: "
 msgstr "Sammendrag: "
 
@@ -5113,7 +5088,7 @@ msgstr "rodcertifikat er nu blevet markeret som troværdig\n"
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5373,7 +5348,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5775,6 +5750,14 @@ msgid "unsupported TOFU database version: %s\n"
 msgstr "ikke understøttet algoritme: %s"
 
 #, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "fejl ved oprettelse af midlertidig fil: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error sending %s command: %s\n"
 msgid "error reading TOFU database: %s\n"
 msgstr "fejl under afsendelse af %s-kommando: %s\n"
@@ -5790,6 +5773,15 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: fejl ved skrivning af mappepost: %s\n"
 
 #, fuzzy, c-format
+#| msgid "error sending %s command: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "fejl under afsendelse af %s-kommando: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error opening `%s': %s\n"
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "fejl ved åbning af »%s«: %s\n"
@@ -5805,8 +5797,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5857,38 +5851,77 @@ msgstr "vis nøgler"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "vis beskedsammendrag"
+msgstr[1] "vis beskedsammendrag"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "krypteret med %lu adgangsfraser\n"
+msgstr[1] "krypteret med %lu adgangsfraser\n"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "vis beskedsammendrag"
+msgstr[1] "vis beskedsammendrag"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "vis beskedsammendrag"
 msgstr[1] "vis beskedsammendrag"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Messages encrypted in the past: %d."
+msgstr "vis beskedsammendrag"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5917,7 +5950,7 @@ msgstr "fejl ved skrivning af nøgle: %s\n"
 
 #, fuzzy, c-format
 #| msgid "error storing flags: %s\n"
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "fejl ved lagring af flag: %s\n"
 
 #, fuzzy, c-format
@@ -5925,72 +5958,81 @@ msgstr "fejl ved lagring af flag: %s\n"
 msgid "error changing TOFU policy: %s\n"
 msgstr "fejl ved oprettelse af datakanal: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Slettede %d underskrifter.\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "vis beskedsammendrag"
 msgstr[1] "vis beskedsammendrag"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "krypteret med %lu adgangsfraser\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "vis beskedsammendrag"
 msgstr[1] "vis beskedsammendrag"
 
+#, fuzzy, c-format
+#| msgid "validity: %s"
+msgid "(policy: %s)"
+msgstr "validitet: %s"
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -6027,7 +6069,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "fejl under afsendelse af %s-kommando: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8894,6 +8936,16 @@ msgstr "Ekstern verifikation af komponent %s mislykkedes"
 msgid "Note that group specifications are ignored\n"
 msgstr "Bemærk at gruppespecifikationer ignoreres\n"
 
+#, fuzzy, c-format
+#| msgid "error closing %s: %s\n"
+msgid "error closing '%s'\n"
+msgstr "fejl ved lukning af %s: %s\n"
+
+#, fuzzy, c-format
+#| msgid "error in `%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "fejl i »%s«: %s\n"
+
 msgid "list all components"
 msgstr "vis alle komponenter"
 
@@ -8913,6 +8965,11 @@ msgid "apply global default values"
 msgstr "anvend globale standardværdier"
 
 #, fuzzy
+#| msgid "|FILE|take policy information from FILE"
+msgid "|FILE|update configuration files using FILE"
+msgstr "|FILE|tag politikinformation fra FIL"
+
+#, fuzzy
 #| msgid "get the configuration directories for gpgconf"
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "hent konfigurationsmapperne for gpgconf"
@@ -9119,6 +9176,51 @@ msgstr ""
 "Syntaks: gpg-check-pattern [tilvalg] mønsterfil\n"
 "Kontroller en adgangsfrase angivet på stdin mod mønsterfilen\n"
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [filnavn]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [filnavn]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [filnavn]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [filnavn]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [filnavn]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [filnavn]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [filnavn]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [filnavn]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [filnavn]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [filnavn]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key bruger-id"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key bruger-id"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key bruger-id [kommandoer]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd <bruger-id>"
+
+#~ msgid "[filename]"
+#~ msgstr "[filnavn]"
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "skygge for nøgle mislykkedes: %s\n"
 
index aad4cd1..feac198 100644 (file)
--- a/po/de.po
+++ b/po/de.po
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: gnupg-2.1.0\n"
 "Report-Msgid-Bugs-To: translations@gnupg.org\n"
-"PO-Revision-Date: 2016-11-18 15:40+0100\n"
+"PO-Revision-Date: 2016-12-20 11:14+0100\n"
 "Last-Translator: Werner Koch <wk@gnupg.org>\n"
 "Language-Team: German <de@li.org>\n"
 "Language: de\n"
@@ -1825,6 +1825,9 @@ msgstr "Schnell eine neue User-ID anfügen"
 msgid "quickly revoke a user-id"
 msgstr "Schnell eine User-ID widerrufen"
 
+msgid "quickly set a new expiration date"
+msgstr "Schnell ein neues Ablaufdatum setzen"
+
 msgid "full featured key pair generation"
 msgstr "Ein neues Schlüsselpaar erzeugen (alle Optionen)"
 
@@ -1934,7 +1937,7 @@ msgid ""
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -1943,7 +1946,7 @@ msgstr ""
 "Beispiele:\n"
 "\n"
 " -se -r Bob [Datei]         Signieren und verschlüsseln für Benutzer Bob\n"
-" --clearsign [Datei]        Eine Klartextsignatur erzeugen\n"
+" --clear-sign [Datei]       Eine Klartextsignatur erzeugen\n"
 " --detach-sign [Datei]      Eine abgetrennte Signatur erzeugen\n"
 " --list-keys [Namen]        Schlüssel anzeigen\n"
 " --fingerprint [Namen]      \"Fingerabdrücke\" anzeigen\n"
@@ -2312,22 +2315,10 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "WARNUNG: Empfänger (-r) angegeben ohne Verwendung von Public-Key-Verfahren\n"
 
-msgid "--store [filename]"
-msgstr "--store [Dateiname]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [Dateiname]"
-
 #, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "Symmetrische Entschlüsselung von `%s' fehlgeschlagen: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [Dateiname]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [Dateiname]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 "--symmetric --encrypt kann nicht zusammen mit --s2k-mode 0 verwendet werden\n"
@@ -2336,15 +2327,6 @@ msgstr ""
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "Im %s-Modus kann --symmetric --encrypt nicht verwendet werden.\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [Dateiname]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [Dateiname]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [Dateiname]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 "--symmetric --sign --encrypt kann nicht zusammen mit --s2k-mode 0 verwendet "
@@ -2355,27 +2337,6 @@ msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr ""
 "Im %s-Modus kann --symmetric --sign --encrypt nicht verwendet werden.\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [Dateiname]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [Dateiname]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [Dateiname]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key User-ID"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key User-ID"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key User-ID [Befehle]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd User-ID"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "Senden an Schlüsselserver fehlgeschlagen: %s\n"
@@ -2422,9 +2383,6 @@ msgstr ""
 "'%s\" sieht nicht nach einer gültigen Schlüssel-ID, einem Fingerabdruck oder "
 "einem \"Keygrip\" aus\n"
 
-msgid "[filename]"
-msgstr "[Dateiname]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Auf geht's - Botschaft eintippen ...\n"
 
@@ -3414,6 +3372,10 @@ msgstr "Keine passende User-ID"
 msgid "Nothing to sign.\n"
 msgstr "Nichts zu beglaubigen\n"
 
+#, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "'%s' ist kein gültiges Ablaufdatum\n"
+
 msgid "Digest: "
 msgstr "Digest: "
 
@@ -4866,24 +4828,20 @@ msgstr ""
 "Dieser Schlüssel gehört uns (da wir nämlich den geheimen Schlüssel dazu "
 "haben)\n"
 
-#, fuzzy, c-format
-#| msgid "root certificate has now been marked as trusted\n"
+#, c-format
 msgid "%s: This key is bad!  It has been marked as untrusted!\n"
-msgstr "Das Wurzelzertifikat wurde nun als vertrauenswürdig markiert\n"
+msgstr ""
+"%s: Der Schlüssel ist gefälscht!  Er wurde als nicht vertrauenswürdig "
+"markiert.\n"
 
-#, fuzzy
-#| msgid ""
-#| "It is NOT certain that the key belongs to the person named\n"
-#| "in the user ID.  If you *really* know what you are doing,\n"
-#| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
-"Es ist NICHT sicher, daß der Schlüssel zu dem in der User-ID\n"
-"Genannten gehört. Wenn Sie *wirklich* wissen, was Sie tun,\n"
-"können Sie die nächste Frage mit ja beantworten\n"
+"Der Schlüssel ist gefälscht!  Er wurde als als nicht vertrauenswürdig\n"
+"markiert.  Wenn Sie *wirklich* wissen, was Sie tun, können Sie die\n"
+"nächste Frage mit ja beantworten.\n"
 
 msgid ""
 "It is NOT certain that the key belongs to the person named\n"
@@ -5134,14 +5092,14 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 "Benutzen Sie es, um einen Schlüssel zu widerrufen, falls der private\n"
 "Schlüssel verloren wurde oder kompromittiert ist.  Falls jedoch auf\n"
 "den privaten Schlüssel noch zugegriffen werden kann, so ist es besser,\n"
 "ein neues Widerrufszertifikat zu erzeugen, um den Grund des Widerrufs\n"
 "mit angeben zu können.  Weitere Informationen finden Sie im GnuPG\n"
-"Handbuch unter der Beschreibung des gpg Kommandos \"--gen-revoke\"."
+"Handbuch unter der Beschreibung des gpg Kommandos \"--generate-revocation\"."
 
 msgid ""
 "To avoid an accidental use of this file, a colon has been inserted\n"
@@ -5536,6 +5494,14 @@ msgstr ""
 msgid "unsupported TOFU database version: %s\n"
 msgstr "Nicht unterstützte TOFU Datenbank Version: %s\n"
 
+#, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "Fehler beim Erstellen einer temporären Datei: %s\n"
+
+msgid "TOFU DB error"
+msgstr "TOFU (Trust on First Use) database error"
+
 #, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "Fehler beim Lesen der TOFU Datenbank: %s\n"
@@ -5548,6 +5514,15 @@ msgstr "Fehler beim Feststellen der TOFU Datenbank Version: %s\n"
 msgid "error initializing TOFU database: %s\n"
 msgstr "Fehler beim Initialisieren der TOFU Datenbank: %s\n"
 
+#, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "Fehler beim Erstellen einer temporären Datei: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
 #, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "Fehler beim Öffner der TOFU Datenbank '%s': %s\n"
@@ -5566,8 +5541,10 @@ msgstr "Die Email-Adresse \"%s\" ist mit einem Schlüssel assoziert:\n"
 #, fuzzy, c-format
 #| msgid "The email address \"%s\" is associated with %d key:\n"
 #| msgid_plural "The email address \"%s\" is associated with %d keys:\n"
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr "Die Email-Adresse \"%s\" ist mit einem Schlüssel assoziert:\n"
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] "Die Email-Adresse \"%s\" ist mit einem Schlüssel assoziert:\n"
+msgstr[1] "Die Email-Adresse \"%s\" ist mit einem Schlüssel assoziert:\n"
 
 #, fuzzy
 #| msgid ""
@@ -5620,47 +5597,100 @@ msgstr "dieser Schlüssel"
 
 #, fuzzy, c-format
 #| msgid "Verified %ld messages signed by \"%s\"."
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
 msgstr[0] "%ld überprüfte Nachrichten von \"%s\"."
 msgstr[1] "%ld überprüfte Nachrichten von \"%s\"."
 
-#, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
-msgstr[0] ""
-msgstr[1] ""
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "Verschlüsselt mit %lu Passphrases\n"
+msgstr[1] "Verschlüsselt mit %lu Passphrases\n"
+
+#, fuzzy, c-format
+#| msgid "%ld message signed in the future."
+#| msgid_plural "%ld messages signed in the future."
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "%ld Nachricht in der Zukunft signiert."
+msgstr[1] "%ld Nachrichten in der Zukunf signiert."
+
+#, fuzzy, c-format
+#| msgid "%ld message signed in the future."
+#| msgid_plural "%ld messages signed in the future."
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
+msgstr[0] "%ld Nachricht in der Zukunft signiert."
+msgstr[1] "%ld Nachrichten in der Zukunf signiert."
 
 #, fuzzy, c-format
 #| msgid " over the past %ld day."
 #| msgid_plural " over the past %ld days."
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
 msgstr[0] " innerhalb des letzten Tages."
 msgstr[1] " innerhalb der letzten %ld Tage."
 
 #, fuzzy, c-format
+#| msgid " over the past %ld day."
+#| msgid_plural " over the past %ld days."
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
+msgstr[0] " innerhalb des letzten Tages."
+msgstr[1] " innerhalb der letzten %ld Tage."
+
+#, fuzzy, c-format
+#| msgid " over the past %ld month."
+#| msgid_plural " over the past %ld months."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
+msgstr[0] " innerhalb des letzten Monats."
+msgstr[1] " innerhalb der letzten %ld Monate."
+
+#, fuzzy, c-format
 #| msgid " over the past %ld month."
 #| msgid_plural " over the past %ld months."
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] " innerhalb des letzten Monats."
 msgstr[1] " innerhalb der letzten %ld Monate."
 
 #, fuzzy, c-format
 #| msgid " over the past %ld day."
 #| msgid_plural " over the past %ld days."
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] " innerhalb des letzten Tages."
 msgstr[1] " innerhalb der letzten %ld Tage."
 
-#, fuzzy
+#, fuzzy, c-format
+#| msgid " over the past %ld day."
+#| msgid_plural " over the past %ld days."
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] " innerhalb des letzten Tages."
+msgstr[1] " innerhalb der letzten %ld Tage."
+
+#, fuzzy, c-format
 #| msgid " over the past %ld day."
 #| msgid_plural " over the past %ld days."
-msgid " in the past."
+msgid "Messages verified in the past: %d."
 msgstr " innerhalb des letzten Tages."
 
+#, fuzzy, c-format
+#| msgid ""
+#| "Verified %ld message signed by \"%s\"\n"
+#| "in the past %s."
+#| msgid_plural ""
+#| "Verified %ld messages signed by \"%s\"\n"
+#| "in the past %s."
+msgid "Messages encrypted in the past: %d."
+msgstr ""
+"%ld überprüfte Nachricht von \"%s\"\n"
+"in den letzten %s."
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5694,59 +5724,84 @@ msgstr ""
 msgid "resetting keydb: %s\n"
 msgstr "Fehler beim Schreiben des Schlüssels: %s\n"
 
-#, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+#, fuzzy, c-format
+#| msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "Fehler beim Setzen der TOFU Binding Vertrauensstufe auf %s\n"
 
 #, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "Fehler beim Ändern der TOFU Richtlinie: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
-#, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+#, fuzzy, c-format
+#| msgid "%d~year"
+#| msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] "%d~Jahr"
 msgstr[1] "%d~Jahre"
 
-#, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+#, fuzzy, c-format
+#| msgid "%d~month"
+#| msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] "%d~Monat"
 msgstr[1] "%d~Monate"
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#, fuzzy, c-format
+#| msgid "%d~day"
+#| msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] "%d~Tag"
 msgstr[1] "%d~Tage"
 
-#, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+#, fuzzy, c-format
+#| msgid "%d~hour"
+#| msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] "%d~Stunde"
 msgstr[1] "%d~Stunden"
 
-#, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+#, fuzzy, c-format
+#| msgid "%d~minute"
+#| msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] "%d~Minute"
 msgstr[1] "%d~Minuten"
 
-#, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+#, fuzzy, c-format
+#| msgid "%d~second"
+#| msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] "%d~Sekunde"
 msgstr[1] "%d~Sekunden"
 
-#, c-format
-msgid "%s: "
+#, fuzzy, c-format
+#| msgid "TOFU: few signatures %d message %s"
+#| msgid_plural "TOFU: few signatures %d messages %s"
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
+"WARNUNG: Falls sie glauben, mehr als %d mit diesem Schlüssel signierte\n"
+"Nachricht erhalten zu haben, so kann es sich bei diesem Schlüssel um\n"
+"eine Fälschung handeln!  Prüfen Sie die Email-Adresse genau auf kleine\n"
+"Variationen (z.B. zusätzliche Leerzeichen).  Falls Ihnen der Schlüssel\n"
+"suspekt erscheint, so benutzen Sie '%s' um den Schlüssel als Fälschung\n"
+"zu markieren."
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "%d Beglaubigungen entfernt.\n"
 
 #, fuzzy, c-format
@@ -5756,8 +5811,8 @@ msgstr "%d Beglaubigungen entfernt.\n"
 #| msgid_plural ""
 #| "Verified %ld messages signed by \"%s\"\n"
 #| "in the past %s."
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] ""
 "%ld überprüfte Nachricht von \"%s\"\n"
 "in den letzten %s."
@@ -5765,9 +5820,9 @@ msgstr[1] ""
 "%ld überprüfte Nachrichten von \"%s\"\n"
 "in den letzten %s."
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "Verschlüsselt mit %lu Passphrases\n"
 
 #, fuzzy, c-format
@@ -5777,8 +5832,8 @@ msgstr "Verschlüsselt mit %lu Passphrases\n"
 #| msgid_plural ""
 #| "Verified %ld messages signed by \"%s\"\n"
 #| "in the past %s."
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] ""
 "%ld überprüfte Nachricht von \"%s\"\n"
 "in den letzten %s."
@@ -5786,6 +5841,11 @@ msgstr[1] ""
 "%ld überprüfte Nachrichten von \"%s\"\n"
 "in den letzten %s."
 
+#, fuzzy, c-format
+#| msgid "policy: %s"
+msgid "(policy: %s)"
+msgstr "Richtlinie: %s"
+
 #, fuzzy
 #| msgid "Warning: we've have yet to see a message signed by this key!\n"
 msgid ""
@@ -5861,7 +5921,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "Fehler beim Öffnen der TOFU Datenbank: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8485,6 +8545,14 @@ msgstr "Die externe Überprüfung der Komponente %s war nicht erfolgreich"
 msgid "Note that group specifications are ignored\n"
 msgstr "Beachten Sie, daß Gruppenspezifiaktionen ignoriert werden\n"
 
+#, c-format
+msgid "error closing '%s'\n"
+msgstr "Fehler beim Schließen von '%s'\n"
+
+#, c-format
+msgid "error parsing '%s'\n"
+msgstr "Fehler beim Hashen von '%s'\n"
+
 msgid "list all components"
 msgstr "Liste aller Komponenten"
 
@@ -8503,6 +8571,9 @@ msgstr "|KOMPONENTE|Prüfe die Optionen"
 msgid "apply global default values"
 msgstr "Wende die gobalen Voreinstellungen an"
 
+msgid "|FILE|update configuration files using FILE"
+msgstr "|DATEI|Konfigurationsdateien mittels DATEI ändern"
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "Hole die Einstellungsverzeichnisse von @GPGCONF@"
 
@@ -8693,6 +8764,86 @@ msgstr ""
 "Syntax: gpg-check-pattern [optionen] Musterdatei\n"
 "Die von stdin gelesene Passphrase gegen die Musterdatei prüfen\n"
 
+#, fuzzy
+#~| msgid ""
+#~| "@\n"
+#~| "Examples:\n"
+#~| "\n"
+#~| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#~| " --clear-sign [file]        make a clear text signature\n"
+#~| " --detach-sign [file]       make a detached signature\n"
+#~| " --list-keys [names]        show keys\n"
+#~| " --fingerprint [names]      show fingerprints\n"
+#~ msgid ""
+#~ "@\n"
+#~ "Examples:\n"
+#~ "\n"
+#~ " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#~ " --clearsign [file]         make a clear text signature\n"
+#~ " --detach-sign [file]       make a detached signature\n"
+#~ " --list-keys [names]        show keys\n"
+#~ " --fingerprint [names]      show fingerprints\n"
+#~ msgstr ""
+#~ "@\n"
+#~ "Beispiele:\n"
+#~ "\n"
+#~ " -se -r Bob [Datei]         Signieren und verschlüsseln für Benutzer Bob\n"
+#~ " --clear-sign [Datei]       Eine Klartextsignatur erzeugen\n"
+#~ " --detach-sign [Datei]      Eine abgetrennte Signatur erzeugen\n"
+#~ " --list-keys [Namen]        Schlüssel anzeigen\n"
+#~ " --fingerprint [Namen]      \"Fingerabdrücke\" anzeigen\n"
+
+#~ msgid "--store [filename]"
+#~ msgstr "--store [Dateiname]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [Dateiname]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [Dateiname]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [Dateiname]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [Dateiname]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [Dateiname]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [Dateiname]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [Dateiname]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [Dateiname]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [Dateiname]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key User-ID"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key User-ID"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key User-ID [Befehle]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd User-ID"
+
+#~ msgid "[filename]"
+#~ msgstr "[Dateiname]"
+
+#, fuzzy
+#~| msgid " over the past %ld day."
+#~| msgid_plural " over the past %ld days."
+#~ msgid " in the past."
+#~ msgstr " innerhalb des letzten Tages."
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "\"Shadowing\" des Schlüssels schlug fehl: %s\n"
 
@@ -8713,11 +8864,6 @@ msgstr ""
 #~ msgid "Known user IDs associated with this key:\n"
 #~ msgstr "Bekannte, mit diesem Schlüssel assozierte, User-IDs:\n"
 
-#~ msgid "%ld message signed in the future."
-#~ msgid_plural "%ld messages signed in the future."
-#~ msgstr[0] "%ld Nachricht in der Zukunft signiert."
-#~ msgstr[1] "%ld Nachrichten in der Zukunf signiert."
-
 #~ msgid "%ld message signed"
 #~ msgid_plural "%ld messages signed"
 #~ msgstr[0] "%ld Nachricht signiert"
@@ -8824,23 +8970,6 @@ msgstr ""
 #~ msgstr[0] "%d Tag"
 #~ msgstr[1] "%d Tage"
 
-#~ msgid "TOFU: few signatures %d message %s"
-#~ msgid_plural "TOFU: few signatures %d messages %s"
-#~ msgstr[0] ""
-#~ "WARNUNG: Falls sie glauben, mehr als %d mit diesem Schlüssel signierte\n"
-#~ "Nachricht erhalten zu haben, so kann es sich bei diesem Schlüssel um\n"
-#~ "eine Fälschung handeln!  Prüfen Sie die Email-Adresse genau auf kleine\n"
-#~ "Variationen (z.B. zusätzliche Leerzeichen).  Falls Ihnen der Schlüssel\n"
-#~ "suspekt erscheint, so benutzen Sie '%s' um den Schlüssel als Fälschung\n"
-#~ "zu markieren."
-#~ msgstr[1] ""
-#~ "WARNUNG: Falls sie glauben, mehr als %d mit diesem Schlüssel signierte\n"
-#~ "Nachrichten erhalten zu haben, so kann es sich bei diesem Schlüssel um\n"
-#~ "eine Fälschung handeln!  Prüfen Sie die Email-Adresse genau auf kleine\n"
-#~ "Variationen (z.B. zusätzliche Leerzeichen).  Falls Ihnen der Schlüssel\n"
-#~ "suspekt erscheint, so benutzen Sie '%s' um den Schlüssel als Fälschung\n"
-#~ "zu markieren."
-
 #~ msgid "sending key %s to %s server %s\n"
 #~ msgstr "sende Schlüssel %s auf den %s-Server %s\n"
 
index 705cc78..14613f6 100644 (file)
--- a/po/el.po
+++ b/po/el.po
@@ -1925,6 +1925,11 @@ msgstr "δημιουργία ενός νέου ζεύγους κλειδιών"
 msgid "quickly revoke a user-id"
 msgstr "δημιουργία ενός νέου ζεύγους κλειδιών"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "δημιουργία ενός νέου ζεύγους κλειδιών"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2038,12 +2043,22 @@ msgstr ""
 "@\n"
 "(δείτε τη σελίδα man για μια πλήρη λίστα εντολών και επιλογών)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2052,7 +2067,7 @@ msgstr ""
 "Παραδείγματα:\n"
 "\n"
 " -se -r Bob [αρχείο]          υπογραφή και κρυπτογράφηση για το Bob\n"
-" --clearsign [αρχείο]         δημιουργία μη κρυπτογραφημένης υπογραφής\n"
+" --clear-sign [αρχείο]         δημιουργία μη κρυπτογραφημένης υπογραφής\n"
 " --detach-sign [αρχείο]       δημιουργία αποκομμένης υπογραφής\n"
 " --list-keys [ονόματα]        απεικόνιση κλειδιών\n"
 " --fingerprint [ονόματα]      απεικόνιση αποτυπωμάτων (fingerprints)\n"
@@ -2439,23 +2454,10 @@ msgstr ""
 "ΠΡΟΕΙΔΟΠΟΙΗΣΗ: δώθηκαν παραλήπτες (-r) χώρις χρήση κρυπτογράφησης\n"
 "δημοσίου κλειδιού\n"
 
-msgid "--store [filename]"
-msgstr "--store [όνομα αρχείου]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [όνομα αρχείου]"
-
 #, fuzzy, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "αποκρυπτογράφηση απέτυχε: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [όνομα αρχείου]"
-
-#, fuzzy
-msgid "--symmetric --encrypt [filename]"
-msgstr "--sign --encrypt [όνομα αρχείου]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2463,16 +2465,6 @@ msgstr ""
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "απαγορεύετε η χρήση του %s στην κατάσταση %s.\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [όνομα αρχείου]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [όνομα αρχείου]"
-
-#, fuzzy
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--sign --encrypt [όνομα αρχείου]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2480,28 +2472,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "απαγορεύετε η χρήση του %s στην κατάσταση %s.\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [όνομα αρχείου]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [όνομα αρχείου]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [όνομα αρχείου]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key user-id"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key user-id"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key user-id [εντολές]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key user-id"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "keyserver αποστολή απέτυχε: %s\n"
@@ -2548,9 +2518,6 @@ msgstr "σφάλμα στη δημιουργία της φράσης κλειδ
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[όνομα αρχείου]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Μπορείτε τώρα να εισαγάγετε το μήνυμα σας ...\n"
 
@@ -3595,6 +3562,10 @@ msgstr "Δεν υπάρχει αυτό το user ID.\n"
 msgid "Nothing to sign.\n"
 msgstr "Τίποτα για να υπογραφεί με το κλειδί  %08lX\n"
 
+#, fuzzy, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "το %s δεν είναι έγκυρο σετ χαρακτήρων\n"
+
 msgid "Digest: "
 msgstr "Περίληψη: "
 
@@ -5100,7 +5071,7 @@ msgstr ""
 
 #, fuzzy
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5360,7 +5331,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5762,6 +5733,13 @@ msgstr ""
 "Υποστηριζόμενοι αλγόριθμοι:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "σφάλμα στη δημιουργία της φράσης κλειδί: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "σφάλμα στη αποστολή προς το `%s': %s\n"
 
@@ -5776,6 +5754,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: σφάλμα στην εγγραφή της εγγραφής dir : %s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "σφάλμα στη αποστολή προς το `%s': %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "σφάλμα κατά την ανάγνωση του `%s': %s\n"
 
@@ -5789,8 +5775,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5838,38 +5826,74 @@ msgid "this key"
 msgstr "απεικόνιση της λίστας κλειδιών"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|αλγόρ [αρχεία]| απεικόνιση περιλήψεων των μηνυμάτων"
+msgstr[1] "|αλγόρ [αρχεία]| απεικόνιση περιλήψεων των μηνυμάτων"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "κρυπτογραφημένο με %lu φράσεις κλειδιά\n"
+msgstr[1] "κρυπτογραφημένο με %lu φράσεις κλειδιά\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|αλγόρ [αρχεία]| απεικόνιση περιλήψεων των μηνυμάτων"
+msgstr[1] "|αλγόρ [αρχεία]| απεικόνιση περιλήψεων των μηνυμάτων"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|αλγόρ [αρχεία]| απεικόνιση περιλήψεων των μηνυμάτων"
 msgstr[1] "|αλγόρ [αρχεία]| απεικόνιση περιλήψεων των μηνυμάτων"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|αλγόρ [αρχεία]| απεικόνιση περιλήψεων των μηνυμάτων"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5896,77 +5920,86 @@ msgid "resetting keydb: %s\n"
 msgstr "αδυναμία εγγραφής της κλειδοθήκης `%s': %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "σφάλμα κατά την ανάγνωση του `%s': %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "σφάλμα στη δημιουργία της φράσης κλειδί: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Διαγράφηκαν %d υπογραφές.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|αλγόρ [αρχεία]| απεικόνιση περιλήψεων των μηνυμάτων"
 msgstr[1] "|αλγόρ [αρχεία]| απεικόνιση περιλήψεων των μηνυμάτων"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "κρυπτογραφημένο με %lu φράσεις κλειδιά\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|αλγόρ [αρχεία]| απεικόνιση περιλήψεων των μηνυμάτων"
 msgstr[1] "|αλγόρ [αρχεία]| απεικόνιση περιλήψεων των μηνυμάτων"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Πολιτική: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -6002,7 +6035,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "σφάλμα στη αποστολή προς το `%s': %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8686,6 +8719,14 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "σφάλμα κατά την ανάγνωση του `%s': %s\n"
+
+#, fuzzy, c-format
+msgid "error parsing '%s'\n"
+msgstr "σφάλμα κατά την ανάγνωση του `%s': %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8704,6 +8745,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8900,6 +8944,54 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [όνομα αρχείου]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [όνομα αρχείου]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [όνομα αρχείου]"
+
+#, fuzzy
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--sign --encrypt [όνομα αρχείου]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [όνομα αρχείου]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [όνομα αρχείου]"
+
+#, fuzzy
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [όνομα αρχείου]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [όνομα αρχείου]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [όνομα αρχείου]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [όνομα αρχείου]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key user-id"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key user-id"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key user-id [εντολές]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key user-id"
+
+#~ msgid "[filename]"
+#~ msgstr "[όνομα αρχείου]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "διαγραφή block κλειδιών απέτυχε: %s\n"
index 20b047a..af5252e 100644 (file)
--- a/po/eo.po
+++ b/po/eo.po
@@ -1911,6 +1911,11 @@ msgstr "krei novan ŝlosilparon"
 msgid "quickly revoke a user-id"
 msgstr "krei novan ŝlosilparon"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "krei novan ŝlosilparon"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2024,12 +2029,22 @@ msgstr ""
 "@\n"
 "(Vidu la manpaĝon por kompleta listo de ĉiuj komandoj kaj opcioj)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2038,7 +2053,7 @@ msgstr ""
 "Ekzemploj:\n"
 "\n"
 " -se -r Bob [dosiero]       subskribi kaj ĉifri por uzanto Bob\n"
-" --clearsign [dosiero]      fari klartekstan subskribon\n"
+" --clear-sign [dosiero]      fari klartekstan subskribon\n"
 " --detach-sign [dosiero]    fari apartan subskribon\n"
 " --list-keys [nomoj]        montri ŝlosilojn\n"
 " --fingerprint [nomoj]      montri fingroŝpurojn\n"
@@ -2425,23 +2440,10 @@ msgstr "malsukcesis doni komencajn valorojn al fido-datenaro: %s\n"
 msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 
-msgid "--store [filename]"
-msgstr "--store [dosiero]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [dosiero]"
-
 #, fuzzy, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "malĉifrado malsukcesis: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [dosiero]"
-
-#, fuzzy
-msgid "--symmetric --encrypt [filename]"
-msgstr "--sign --encrypt [dosiero]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2449,16 +2451,6 @@ msgstr ""
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "Tiu komando ne eblas en la reĝimo %s.\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [dosiero]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [dosiero]"
-
-#, fuzzy
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--sign --encrypt [dosiero]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2466,28 +2458,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "Tiu komando ne eblas en la reĝimo %s.\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [dosiero]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [dosiero]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [dosiero]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key uzantidentigilo"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key uzantidentigilo"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key uzantidentigilo [komandoj]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key uzantidentigilo"
-
 #, fuzzy, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "Kreado de ŝlosiloj malsukcesis: %s\n"
@@ -2533,9 +2503,6 @@ msgstr "eraro dum kreado de pasfrazo: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[dosiero]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Ektajpu vian mesaĝon ...\n"
 
@@ -3582,6 +3549,10 @@ msgstr "Uzantidentigilo ne ekzistas.\n"
 msgid "Nothing to sign.\n"
 msgstr "Nenio por subskribi per ŝlosilo %08lX\n"
 
+#, fuzzy, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "%s ne estas valida signaro\n"
+
 msgid "Digest: "
 msgstr ""
 
@@ -5073,7 +5044,7 @@ msgstr ""
 
 #, fuzzy
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5328,7 +5299,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5711,6 +5682,13 @@ msgstr ""
 "Realigitaj metodoj:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "eraro dum kreado de pasfrazo: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "eraro dum sendo al '%s': %s\n"
 
@@ -5725,6 +5703,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: eraro dum skribo de dosieruja registro: %s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "eraro dum sendo al '%s': %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "eraro dum legado de '%s': %s\n"
 
@@ -5738,8 +5724,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5787,38 +5775,73 @@ msgid "this key"
 msgstr "listigi ŝlosilojn"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|metodo [dosieroj]|presi mesaĝo-kompendiojn"
+msgstr[1] "|metodo [dosieroj]|presi mesaĝo-kompendiojn"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "Ripetu pasfrazon\n"
+msgstr[1] "Ripetu pasfrazon\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|metodo [dosieroj]|presi mesaĝo-kompendiojn"
+msgstr[1] "|metodo [dosieroj]|presi mesaĝo-kompendiojn"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|metodo [dosieroj]|presi mesaĝo-kompendiojn"
 msgstr[1] "|metodo [dosieroj]|presi mesaĝo-kompendiojn"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|metodo [dosieroj]|presi mesaĝo-kompendiojn"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5845,76 +5868,85 @@ msgid "resetting keydb: %s\n"
 msgstr "eraro dum skribado de ŝlosilaro '%s': %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "eraro dum legado de '%s': %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "eraro dum kreado de pasfrazo: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Forviŝis %d subskribojn.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|metodo [dosieroj]|presi mesaĝo-kompendiojn"
 msgstr[1] "|metodo [dosieroj]|presi mesaĝo-kompendiojn"
 
-#, fuzzy, c-format
-msgid ", and encrypted %ld messages"
+#, fuzzy
+msgid "Encrypted 0 messages."
 msgstr "Ripetu pasfrazon\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|metodo [dosieroj]|presi mesaĝo-kompendiojn"
 msgstr[1] "|metodo [dosieroj]|presi mesaĝo-kompendiojn"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Gvidlinio: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5950,7 +5982,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "eraro dum sendo al '%s': %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8632,6 +8664,14 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "eraro dum legado de '%s': %s\n"
+
+#, fuzzy, c-format
+msgid "error parsing '%s'\n"
+msgstr "eraro dum legado de '%s': %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8650,6 +8690,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8845,6 +8888,54 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [dosiero]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [dosiero]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [dosiero]"
+
+#, fuzzy
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--sign --encrypt [dosiero]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [dosiero]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [dosiero]"
+
+#, fuzzy
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [dosiero]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [dosiero]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [dosiero]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [dosiero]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key uzantidentigilo"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key uzantidentigilo"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key uzantidentigilo [komandoj]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key uzantidentigilo"
+
+#~ msgid "[filename]"
+#~ msgstr "[dosiero]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "forviŝo de ŝlosilbloko malsukcesis: %s\n"
index 510dedf..258c164 100644 (file)
--- a/po/es.po
+++ b/po/es.po
@@ -1970,6 +1970,11 @@ msgstr "genera un nuevo par de claves"
 msgid "quickly revoke a user-id"
 msgstr "genera un nuevo par de claves"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "genera un nuevo par de claves"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2081,12 +2086,22 @@ msgstr ""
 "@\n"
 "(Véase en la página del manual la lista completo de órdenes y opciones)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2095,7 +2110,7 @@ msgstr ""
 "Ejemplos:\n"
 "\n"
 " -se -r Bob [fichero]       firma y cifra para el usuario Bob\n"
-" --clearsign [fichero]      hace una firma manteniendo el texto sin cifrar\n"
+" --clear-sign [fichero]      hace una firma manteniendo el texto sin cifrar\n"
 " --detach-sign [fichero]    hace una firma separada\n"
 " --list-keys [nombres]      muestra las claves\n"
 " --fingerprint [nombres]    muestra las huellas dactilares\n"
@@ -2495,23 +2510,11 @@ msgstr "inicialización de la base de datos de confianza fallida: %s\n"
 msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr "AVISO: se indicaron receptores (-r) sin clave pública de cifrado\n"
 
-msgid "--store [filename]"
-msgstr "--store [nombre_fichero]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [nombre_fichero]"
-
 #, fuzzy, c-format
 #| msgid "symmetric encryption of `%s' failed: %s\n"
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "el cifrado simétrico de `%s' falló: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [nombre_fichero]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [nombre_fichero]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "no puede usar --symetric --encrypt con --s2k-mode 0\n"
 
@@ -2519,15 +2522,6 @@ msgstr "no puede usar --symetric --encrypt con --s2k-mode 0\n"
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "no puede usar --symetric --encrypt en modo %s\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [nombre_fichero]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [nombre_fichero]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [nombre_fichero]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr "no puede usar --symetric --sign --encrypt con --s2k-mode 0\n"
 
@@ -2535,27 +2529,6 @@ msgstr "no puede usar --symetric --sign --encrypt con --s2k-mode 0\n"
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "no puede usar --symmetric --sign --encrypt en modo %s\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [nombre_fichero]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [nombre_fichero]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [nombre_fichero]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key id-usuario"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key id-usuario"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key id-usuario [órdenes]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd <id-usuario>"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "envío al servidor de claves fallido: %s\n"
@@ -2603,9 +2576,6 @@ msgstr "error almacenando certificado: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[nombre_fichero]"
-
 # Falta un espacio.
 # En español no se deja espacio antes de los puntos suspensivos
 # (Real Academia dixit) :)
@@ -3619,6 +3589,11 @@ msgstr "ID de usuario inexistente.\n"
 msgid "Nothing to sign.\n"
 msgstr "Nada que firmar con la clave %s\n"
 
+#, fuzzy, c-format
+#| msgid "`%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "`%s' no es una fecha de caducidad válida\n"
+
 msgid "Digest: "
 msgstr "Resumen: "
 
@@ -5110,7 +5085,7 @@ msgstr "certificado raíz marcado ahora como fiable\n"
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5366,7 +5341,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5769,6 +5744,14 @@ msgid "unsupported TOFU database version: %s\n"
 msgstr "algoritmo no disponible: %s"
 
 #, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "error creando fichero temporal: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error sending %s command: %s\n"
 msgid "error reading TOFU database: %s\n"
 msgstr "error enviando orden %s: %s\n"
@@ -5784,6 +5767,15 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: error escribiendo registro de directorio: %s\n"
 
 #, fuzzy, c-format
+#| msgid "error sending %s command: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "error enviando orden %s: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error opening `%s': %s\n"
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "error abriendo `%s': %s\n"
@@ -5799,8 +5791,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5851,38 +5845,74 @@ msgstr "lista claves"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
 msgstr[0] "imprime resúmenes de mensaje"
 msgstr[1] "imprime resúmenes de mensaje"
 
 #, fuzzy, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
 msgstr[0] "el algoritmo de resumen seleccionado no es válido\n"
 msgstr[1] "el algoritmo de resumen seleccionado no es válido\n"
 
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "imprime resúmenes de mensaje"
+msgstr[1] "imprime resúmenes de mensaje"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
+msgstr[0] "el algoritmo de resumen seleccionado no es válido\n"
+msgstr[1] "el algoritmo de resumen seleccionado no es válido\n"
+
+#, c-format
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "el algoritmo de resumen seleccionado no es válido\n"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5911,7 +5941,7 @@ msgstr "error escribiendo clave: %s\n"
 
 #, fuzzy, c-format
 #| msgid "error storing flags: %s\n"
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "error almacenando parámetros: %s\n"
 
 #, fuzzy, c-format
@@ -5919,72 +5949,80 @@ msgstr "error almacenando parámetros: %s\n"
 msgid "error changing TOFU policy: %s\n"
 msgstr "error creando tubería: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, fuzzy, c-format
 #| msgid "second"
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] "segundo"
 msgstr[1] "segundo"
 
-#, fuzzy, c-format
-#| msgid "%s: okay\n"
-msgid "%s: "
-msgstr "%s: bien\n"
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
+msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "%d firmas borradas\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "imprime resúmenes de mensaje"
 msgstr[1] "imprime resúmenes de mensaje"
 
-#, fuzzy, c-format
-msgid ", and encrypted %ld messages"
+#, fuzzy
+msgid "Encrypted 0 messages."
 msgstr "el algoritmo de resumen seleccionado no es válido\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "el algoritmo de resumen seleccionado no es válido\n"
 msgstr[1] "el algoritmo de resumen seleccionado no es válido\n"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Política: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -6021,7 +6059,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "error enviando orden %s: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8931,6 +8969,16 @@ msgstr "Verificación externa del componente %s fallida"
 msgid "Note that group specifications are ignored\n"
 msgstr "Note que las especificación de grupo se ignoran\n"
 
+#, fuzzy, c-format
+#| msgid "error closing %s: %s\n"
+msgid "error closing '%s'\n"
+msgstr "error cerrando %s: %s\n"
+
+#, fuzzy, c-format
+#| msgid "error in `%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "error en `%s': %s\n"
+
 msgid "list all components"
 msgstr "listar todos los componentes"
 
@@ -8950,6 +8998,11 @@ msgid "apply global default values"
 msgstr "aplicar valores globales por defecto"
 
 #, fuzzy
+#| msgid "|FILE|take policy information from FILE"
+msgid "|FILE|update configuration files using FILE"
+msgstr "|FICHERO|tomar política de información de FICHERO"
+
+#, fuzzy
 #| msgid "get the configuration directories for gpgconf"
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "obtener directorios de configuración para gpgconf"
@@ -9162,6 +9215,60 @@ msgstr ""
 "Compara frase contraseña dada en entrada estándar con un fichero de "
 "patrones\n"
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [nombre_fichero]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [nombre_fichero]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [nombre_fichero]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [nombre_fichero]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [nombre_fichero]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [nombre_fichero]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [nombre_fichero]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [nombre_fichero]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [nombre_fichero]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [nombre_fichero]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key id-usuario"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key id-usuario"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key id-usuario [órdenes]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd <id-usuario>"
+
+#~ msgid "[filename]"
+#~ msgstr "[nombre_fichero]"
+
+#, fuzzy
+#~| msgid "%s: okay\n"
+#~ msgid "%s: "
+#~ msgstr "%s: bien\n"
+
+#, fuzzy
+#~ msgid ", and encrypted %ld messages"
+#~ msgstr "el algoritmo de resumen seleccionado no es válido\n"
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "el sombreado de la clave falló: %s\n"
 
index 8a8e8c2..a7de525 100644 (file)
--- a/po/et.po
+++ b/po/et.po
@@ -1918,6 +1918,11 @@ msgstr "genereeri uus võtmepaar"
 msgid "quickly revoke a user-id"
 msgstr "genereeri uus võtmepaar"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "genereeri uus võtmepaar"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2031,12 +2036,22 @@ msgstr ""
 "@\n"
 "(Kõikide käskude ja võtmete täieliku kirjelduse leiate manualist)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2045,7 +2060,7 @@ msgstr ""
 "Näited:\n"
 "\n"
 " -se -r Bob [fail]          allkirjasta ja krüpti kasutajale Bob\n"
-" --clearsign [fail]         loo avateksti allkiri\n"
+" --clear-sign [fail]         loo avateksti allkiri\n"
 " --detach-sign [fail]       loo eraldiseisev allkiri\n"
 " --list-keys [nimed]        näita võtmeid\n"
 " --fingerprint [nimed]      näita sõrmejälgi\n"
@@ -2424,23 +2439,10 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "HOIATUS: määrati saajad (-r) aga ei kasutata avaliku võtme krüptograafiat\n"
 
-msgid "--store [filename]"
-msgstr "--store [failinimi]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [failinimi]"
-
 #, fuzzy, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "lahtikrüpteerimine ebaõnnestus: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [failinimi]"
-
-#, fuzzy
-msgid "--symmetric --encrypt [filename]"
-msgstr "--sign --encrypt [failinimi]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2448,16 +2450,6 @@ msgstr ""
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "%s ei ole moodis %s lubatud.\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [failinimi]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [failinimi]"
-
-#, fuzzy
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--sign --encrypt [failinimi]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2465,28 +2457,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "%s ei ole moodis %s lubatud.\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [failinimi]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [failinimi]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [failinimi]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key kasutaja-id"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key kasutaja-id"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key kasutaja-id [käsud]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key kasutaja-id"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "võtmeserverile saatmine ebaõnnestus: %s\n"
@@ -2533,9 +2503,6 @@ msgstr "viga parooli loomisel: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[failinimi]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Kirjutage nüüd oma teade ...\n"
 
@@ -3568,6 +3535,10 @@ msgstr "Tundmatu kasutaja ID.\n"
 msgid "Nothing to sign.\n"
 msgstr "Võtmega %08lX pole midagi allkirjastada\n"
 
+#, fuzzy, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "%s ei ole lubatud kooditabel\n"
+
 msgid "Digest: "
 msgstr "Teatelühend: "
 
@@ -5047,7 +5018,7 @@ msgstr ""
 
 #, fuzzy
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5302,7 +5273,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5688,6 +5659,13 @@ msgstr ""
 "Toetatud algoritmid:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "viga parooli loomisel: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "viga teate saatmisel serverile `%s': %s\n"
 
@@ -5702,6 +5680,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: viga kataloogikirje kirjutamisel: %s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "viga teate saatmisel serverile `%s': %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "viga `%s' lugemisel: %s\n"
 
@@ -5715,8 +5701,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5764,38 +5752,74 @@ msgid "this key"
 msgstr "näita võtmeid"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|algo [failid]|trüki teatelühendid"
+msgstr[1] "|algo [failid]|trüki teatelühendid"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "krüpteeritud kasutades %lu parooli\n"
+msgstr[1] "krüpteeritud kasutades %lu parooli\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|algo [failid]|trüki teatelühendid"
+msgstr[1] "|algo [failid]|trüki teatelühendid"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|algo [failid]|trüki teatelühendid"
 msgstr[1] "|algo [failid]|trüki teatelühendid"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|algo [failid]|trüki teatelühendid"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5822,77 +5846,86 @@ msgid "resetting keydb: %s\n"
 msgstr "viga võtmehoidlasse `%s' kirjutamisel: %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "viga `%s' lugemisel: %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "viga parooli loomisel: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Kustutatud %d allkirja.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|algo [failid]|trüki teatelühendid"
 msgstr[1] "|algo [failid]|trüki teatelühendid"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "krüpteeritud kasutades %lu parooli\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|algo [failid]|trüki teatelühendid"
 msgstr[1] "|algo [failid]|trüki teatelühendid"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Poliis: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5928,7 +5961,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "viga teate saatmisel serverile `%s': %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8604,6 +8637,14 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "viga `%s' lugemisel: %s\n"
+
+#, fuzzy, c-format
+msgid "error parsing '%s'\n"
+msgstr "viga `%s' lugemisel: %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8622,6 +8663,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8818,6 +8862,54 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [failinimi]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [failinimi]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [failinimi]"
+
+#, fuzzy
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--sign --encrypt [failinimi]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [failinimi]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [failinimi]"
+
+#, fuzzy
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [failinimi]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [failinimi]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [failinimi]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [failinimi]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key kasutaja-id"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key kasutaja-id"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key kasutaja-id [käsud]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key kasutaja-id"
+
+#~ msgid "[filename]"
+#~ msgstr "[failinimi]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "võtmebloki kustutamine ebaõnnestus: %s\n"
index d2eefc1..108ce7a 100644 (file)
--- a/po/fi.po
+++ b/po/fi.po
@@ -1933,6 +1933,11 @@ msgstr "luo uusi avainpari"
 msgid "quickly revoke a user-id"
 msgstr "luo uusi avainpari"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "luo uusi avainpari"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2047,12 +2052,22 @@ msgstr ""
 "(Katso täydellinen luettelo kaikista komennoista ja valitsimista man-"
 "sivuilta)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2060,7 +2075,7 @@ msgstr ""
 "@\n"
 "Esim:\n"
 " -se -r Pekka [tiedosto]    allekirjoita ja salaa Pekalle\n"
-" --clearsign [tiedosto]     tee tekstimuotoinen allekirjoitus\n"
+" --clear-sign [tiedosto]     tee tekstimuotoinen allekirjoitus\n"
 " --detach-sign [tiedosto]   tee erillinen allekirjoitus\n"
 " --list-keys [nimet]        näytä avaimet\n"
 " --fingerprint [nimet]      näytä sormenjäljet\n"
@@ -2440,23 +2455,10 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "VAROITUS: vastaanottajia (-r) annettu käyttämättä julkisen avaimen salausta\n"
 
-msgid "--store [filename]"
-msgstr "--store [tiedostonimi]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [tiedostonimi]"
-
 #, fuzzy, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "avaus epäonnistui: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [tiedostonimi]"
-
-#, fuzzy
-msgid "--symmetric --encrypt [filename]"
-msgstr "--sign --encrypt [tiedostonimi]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2464,16 +2466,6 @@ msgstr ""
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "valitsinta %s ei voi käyttää %s-tilassa\n"
 
-msgid "--sign [filename]"
-msgstr "--allekirjoita [tiedostonimi]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [tiedostonimi]"
-
-#, fuzzy
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--sign --encrypt [tiedostonimi]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2481,28 +2473,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "valitsinta %s ei voi käyttää %s-tilassa\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [tiedostonimi]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [tiedostonimi]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [tiedostonimi]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key käyttäjätunnus"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key käyttäjätunnus"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key käyttäjätunnus [komennot]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key käyttäjätunnus"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "avainpalvelimelle lähettäminen epäonnistui: %s\n"
@@ -2549,9 +2519,6 @@ msgstr "virhe luotaessa salasanaa: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[tiedostonimi]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Kirjoita viestisi...\n"
 
@@ -3589,6 +3556,10 @@ msgstr "Käyttäjätunnusta ei löydy.\n"
 msgid "Nothing to sign.\n"
 msgstr "Avaimelle %08lX ei löydy mitään mitä allekirjoittaa\n"
 
+#, fuzzy, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "%s ei kelpaa merkistöksi\n"
+
 msgid "Digest: "
 msgstr "Tiiviste: "
 
@@ -5091,7 +5062,7 @@ msgstr ""
 
 #, fuzzy
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5350,7 +5321,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5747,6 +5718,13 @@ msgstr ""
 "Tuetut algoritmit:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "virhe luotaessa salasanaa: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "virhe lähettäessä kohteeseen \"%s\": %s\n"
 
@@ -5761,6 +5739,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: virhe kirjoitettaessa hakemistotietuetta: %s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "virhe lähettäessä kohteeseen \"%s\": %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "virhe luettaessa tiedostoa \"%s\": %s\n"
 
@@ -5774,8 +5760,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5823,38 +5811,74 @@ msgid "this key"
 msgstr "näytä avaimet"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|algo [tiedostot]|tulosta viestien tiivisteet"
+msgstr[1] "|algo [tiedostot]|tulosta viestien tiivisteet"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "salattu %lu salasanalla\n"
+msgstr[1] "salattu %lu salasanalla\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|algo [tiedostot]|tulosta viestien tiivisteet"
+msgstr[1] "|algo [tiedostot]|tulosta viestien tiivisteet"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|algo [tiedostot]|tulosta viestien tiivisteet"
 msgstr[1] "|algo [tiedostot]|tulosta viestien tiivisteet"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|algo [tiedostot]|tulosta viestien tiivisteet"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5881,77 +5905,86 @@ msgid "resetting keydb: %s\n"
 msgstr "virhe kirjoitettaessa avainrenkaaseen \"%s\": %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "virhe luettaessa tiedostoa \"%s\": %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "virhe luotaessa salasanaa: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "%d allekirjoitusta poistettu.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|algo [tiedostot]|tulosta viestien tiivisteet"
 msgstr[1] "|algo [tiedostot]|tulosta viestien tiivisteet"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "salattu %lu salasanalla\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|algo [tiedostot]|tulosta viestien tiivisteet"
 msgstr[1] "|algo [tiedostot]|tulosta viestien tiivisteet"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Käytäntö: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5987,7 +6020,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "virhe lähettäessä kohteeseen \"%s\": %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8668,6 +8701,14 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "virhe luettaessa tiedostoa \"%s\": %s\n"
+
+#, fuzzy, c-format
+msgid "error parsing '%s'\n"
+msgstr "virhe luettaessa tiedostoa \"%s\": %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8686,6 +8727,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8882,6 +8926,54 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [tiedostonimi]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [tiedostonimi]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [tiedostonimi]"
+
+#, fuzzy
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--sign --encrypt [tiedostonimi]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--allekirjoita [tiedostonimi]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [tiedostonimi]"
+
+#, fuzzy
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [tiedostonimi]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [tiedostonimi]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [tiedostonimi]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [tiedostonimi]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key käyttäjätunnus"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key käyttäjätunnus"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key käyttäjätunnus [komennot]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key käyttäjätunnus"
+
+#~ msgid "[filename]"
+#~ msgstr "[tiedostonimi]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "avainlohkojen poisto epäonnistui: %s\n"
index 4cb138f..d1a82ff 100644 (file)
--- a/po/fr.po
+++ b/po/fr.po
@@ -1866,6 +1866,11 @@ msgstr "générer rapidement une nouvelle paire de clefs"
 msgid "quickly revoke a user-id"
 msgstr "générer rapidement une nouvelle paire de clefs"
 
+#, fuzzy
+#| msgid "quickly generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "générer rapidement une nouvelle paire de clefs"
+
 msgid "full featured key pair generation"
 msgstr "générer une paire de clefs complètes"
 
@@ -1971,12 +1976,22 @@ msgstr ""
 "(Consultez la page de manuel pour obtenir une liste complète des commandes\n"
 "et options)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -1985,7 +2000,7 @@ msgstr ""
 "Exemples :\n"
 "\n"
 " -se -r Alice [fichier]     signer et chiffrer pour l'utilisateur Alice\n"
-" --clearsign [fichier]      faire une signature en texte clair\n"
+" --clear-sign [fichier]      faire une signature en texte clair\n"
 " --detach-sign [fichier]    faire une signature détachée\n"
 " --list-keys [noms]         montrer les clefs\n"
 " --fingerprint [noms]       montrer les empreintes\n"
@@ -2373,22 +2388,10 @@ msgstr ""
 "Attention : les destinataires (-r) indiqués n'utilisent pas\n"
 "            de clef publique pour le chiffrement\n"
 
-msgid "--store [filename]"
-msgstr "--store [fichier]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [fichier]"
-
 #, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "échec du chiffrement symétrique de « %s » : %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [fichier]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [fichier]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "impossible d'utiliser --symmetric --encrypt avec --s2k-mode 0\n"
 
@@ -2396,15 +2399,6 @@ msgstr "impossible d'utiliser --symmetric --encrypt avec --s2k-mode 0\n"
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "impossible d'utiliser --symmetric --encrypt en mode %s\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [fichier]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [fichier]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [fichier]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr "impossible d'utiliser --symmetric --sign --encrypt avec --s2k-mode 0\n"
 
@@ -2412,27 +2406,6 @@ msgstr "impossible d'utiliser --symmetric --sign --encrypt avec --s2k-mode 0\n"
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "impossible d'utiliser --symmetric --sign --encrypt en mode %s\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [fichier]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [fichier]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [fichier]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key identité"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key identité"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key identité [commandes]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd <identité>"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "échec d'envoi vers le serveur de clefs : %s\n"
@@ -2479,9 +2452,6 @@ msgstr "erreur de chargement du certificat « %s » : %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[fichier]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Vous pouvez taper votre message…\n"
 
@@ -3473,6 +3443,11 @@ msgstr "Pas d’identités correspondantes."
 msgid "Nothing to sign.\n"
 msgstr "Rien à signer.\n"
 
+#, fuzzy, c-format
+#| msgid "'%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "« %s » n'est pas une date d'expiration de signature valable\n"
+
 msgid "Digest: "
 msgstr "Hachage : "
 
@@ -4971,7 +4946,7 @@ msgstr "le certificat racine a maintenant été marqué de confiance\n"
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5237,7 +5212,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 "Utilisez-le pour révoquer cette clef en cas de compromis ou de\n"
 "perte de la clef secrète. Cependant, si la clef secrète est\n"
@@ -5659,6 +5634,14 @@ msgid "unsupported TOFU database version: %s\n"
 msgstr "algorithme non pris en charge : %s"
 
 #, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "erreur de création du fichier temporaire : %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error sending data: %s\n"
 msgid "error reading TOFU database: %s\n"
 msgstr "erreur d'envoi de données : %s\n"
@@ -5674,6 +5657,15 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "erreur d'initialisation de l'objet lecteur : %s\n"
 
 #, fuzzy, c-format
+#| msgid "error sending data: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "erreur d'envoi de données : %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error opening '%s': %s\n"
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "erreur d'ouverture de « %s » : %s\n"
@@ -5689,8 +5681,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5741,38 +5735,77 @@ msgstr "afficher les clefs"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "indiquer les fonctions de hachage"
+msgstr[1] "indiquer les fonctions de hachage"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "chiffré avec %lu phrases secrètes\n"
+msgstr[1] "chiffré avec %lu phrases secrètes\n"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "indiquer les fonctions de hachage"
+msgstr[1] "indiquer les fonctions de hachage"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "indiquer les fonctions de hachage"
 msgstr[1] "indiquer les fonctions de hachage"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Messages encrypted in the past: %d."
+msgstr "indiquer les fonctions de hachage"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5801,7 +5834,7 @@ msgstr "erreur d'écriture la clef : %s\n"
 
 #, fuzzy, c-format
 #| msgid "error setting OCSP target: %s\n"
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "erreur de configuration de la cible OCSP : %s\n"
 
 #, fuzzy, c-format
@@ -5809,72 +5842,81 @@ msgstr "erreur de configuration de la cible OCSP : %s\n"
 msgid "error changing TOFU policy: %s\n"
 msgstr "erreur de création d'un tube : %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "%d signatures supprimées\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "indiquer les fonctions de hachage"
 msgstr[1] "indiquer les fonctions de hachage"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "chiffré avec %lu phrases secrètes\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "indiquer les fonctions de hachage"
 msgstr[1] "indiquer les fonctions de hachage"
 
+#, fuzzy, c-format
+#| msgid "validity: %s"
+msgid "(policy: %s)"
+msgstr "validité : %s"
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5911,7 +5953,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "erreur d'envoi de données : %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8623,6 +8665,16 @@ msgstr "Échec de vérification externe du composant %s"
 msgid "Note that group specifications are ignored\n"
 msgstr "Remarquez que les spécifications de groupe sont ignorées\n"
 
+#, fuzzy, c-format
+#| msgid "error closing '%s': %s\n"
+msgid "error closing '%s'\n"
+msgstr "erreur de fermeture de « %s » : %s\n"
+
+#, fuzzy, c-format
+#| msgid "error hashing '%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "erreur du hachage de « %s » : %s\n"
+
 msgid "list all components"
 msgstr "afficher tous les composants"
 
@@ -8641,6 +8693,11 @@ msgstr "|COMPOSANT|vérifier les options"
 msgid "apply global default values"
 msgstr "appliquer les valeurs par défaut globales"
 
+#, fuzzy
+#| msgid "|FILE|take policy information from FILE"
+msgid "|FILE|update configuration files using FILE"
+msgstr "|FICHIER|prendre renseignements de politique du FICHIER"
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "aff. répertoires de configuration pour @GPGCONF@"
 
@@ -8834,6 +8891,51 @@ msgstr ""
 "Vérifier une phrase secrète donnée sur l'entrée standard par rapport à "
 "ficmotif\n"
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [fichier]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [fichier]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [fichier]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [fichier]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [fichier]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [fichier]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [fichier]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [fichier]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [fichier]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [fichier]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key identité"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key identité"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key identité [commandes]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd <identité>"
+
+#~ msgid "[filename]"
+#~ msgstr "[fichier]"
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "échec de dissimulation de la clef : %s\n"
 
index 0b5d876..ff7663a 100644 (file)
--- a/po/gl.po
+++ b/po/gl.po
@@ -1926,6 +1926,11 @@ msgstr "xerar un novo par de chaves"
 msgid "quickly revoke a user-id"
 msgstr "xerar un novo par de chaves"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "xerar un novo par de chaves"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2039,12 +2044,22 @@ msgstr ""
 "@\n"
 "(Vexa a páxina man para un listado completo de comandos e opcións)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2434,23 +2449,10 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "AVISO: deronse destinatarios (-r) sen empregar cifrado de chave pública\n"
 
-msgid "--store [filename]"
-msgstr "--store [ficheiro]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [ficheiro]"
-
 #, fuzzy, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "o descifrado fallou: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [ficheiro]"
-
-#, fuzzy
-msgid "--symmetric --encrypt [filename]"
-msgstr "--sign --encrypt [ficheiro]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2458,16 +2460,6 @@ msgstr ""
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "non se pode empregar %s no modo %s\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [ficheiro]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [ficheiro]"
-
-#, fuzzy
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--sign --encrypt [ficheiro]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2475,28 +2467,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "non se pode empregar %s no modo %s\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [ficheiro]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [ficheiro]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [ficheiro]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key id-de-usuario"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key id-de-usuario"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key id-de-usuario [comandos]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key id-de-usuario"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "o envío ao servidor de chaves fallou: %s\n"
@@ -2543,9 +2513,6 @@ msgstr "erro ao crea-lo contrasinal: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[ficheiro]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Escriba a súa mensaxe ...\n"
 
@@ -3594,6 +3561,10 @@ msgstr "Non hai tal ID de usuario.\n"
 msgid "Nothing to sign.\n"
 msgstr "Nada que asinar coa chave %08lX\n"
 
+#, fuzzy, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "%s non é un xogo de caracteres válido\n"
+
 msgid "Digest: "
 msgstr "Resumo: "
 
@@ -5100,7 +5071,7 @@ msgstr ""
 
 #, fuzzy
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5359,7 +5330,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5754,6 +5725,13 @@ msgstr ""
 "Algoritmos soportados:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "erro ao crea-lo contrasinal: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "erro ao enviar a `%s': %s\n"
 
@@ -5768,6 +5746,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: erro ao escribi-lo rexistro de directorios: %s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "erro ao enviar a `%s': %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "erro lendo `%s': %s\n"
 
@@ -5781,8 +5767,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5830,38 +5818,74 @@ msgid "this key"
 msgstr "ve-la lista de chaves"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|algo [ficheiros]|visualizar resumos de mensaxes"
+msgstr[1] "|algo [ficheiros]|visualizar resumos de mensaxes"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "cifrado con %lu contrasinais\n"
+msgstr[1] "cifrado con %lu contrasinais\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|algo [ficheiros]|visualizar resumos de mensaxes"
+msgstr[1] "|algo [ficheiros]|visualizar resumos de mensaxes"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|algo [ficheiros]|visualizar resumos de mensaxes"
 msgstr[1] "|algo [ficheiros]|visualizar resumos de mensaxes"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|algo [ficheiros]|visualizar resumos de mensaxes"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5888,77 +5912,86 @@ msgid "resetting keydb: %s\n"
 msgstr "erro escribindo no chaveiro `%s': %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "erro lendo `%s': %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "erro ao crea-lo contrasinal: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Borradas %d sinaturas.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|algo [ficheiros]|visualizar resumos de mensaxes"
 msgstr[1] "|algo [ficheiros]|visualizar resumos de mensaxes"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "cifrado con %lu contrasinais\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|algo [ficheiros]|visualizar resumos de mensaxes"
 msgstr[1] "|algo [ficheiros]|visualizar resumos de mensaxes"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Normativa: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5994,7 +6027,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "erro ao enviar a `%s': %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8693,6 +8726,14 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "erro lendo `%s': %s\n"
+
+#, fuzzy, c-format
+msgid "error parsing '%s'\n"
+msgstr "erro lendo `%s': %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8711,6 +8752,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8909,6 +8953,54 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [ficheiro]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [ficheiro]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [ficheiro]"
+
+#, fuzzy
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--sign --encrypt [ficheiro]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [ficheiro]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [ficheiro]"
+
+#, fuzzy
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [ficheiro]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [ficheiro]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [ficheiro]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [ficheiro]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key id-de-usuario"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key id-de-usuario"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key id-de-usuario [comandos]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key id-de-usuario"
+
+#~ msgid "[filename]"
+#~ msgstr "[ficheiro]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "fallou o borrado do bloque de chaves: %s\n"
index 18228f6..f552b0e 100644 (file)
--- a/po/hu.po
+++ b/po/hu.po
@@ -1914,6 +1914,11 @@ msgstr "új kulcspár létrehozása"
 msgid "quickly revoke a user-id"
 msgstr "új kulcspár létrehozása"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "új kulcspár létrehozása"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2027,12 +2032,22 @@ msgstr ""
 "@\n"
 "(A parancsok és opciók teljes listáját a man oldalon tekintheti meg.)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2041,7 +2056,7 @@ msgstr ""
 "Példák:\n"
 "\n"
 " -se -r Bob [fájl]          titkosítás és aláírás Bob részére\n"
-" --clearsign [fájl]         olvasható szöveg aláírása\n"
+" --clear-sign [fájl]         olvasható szöveg aláírása\n"
 " --detach-sign [fájl]       különálló aláírás készítése\n"
 " --list-keys [nevek]        kulcsok kiíratása\n"
 " --fingerprint [nevek]      ujjlenyomatok kiíratása\n"
@@ -2422,23 +2437,10 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "FIGYELEM: Címzett megadva (-r), de nincs nyilvános kulcsú titkosítás!\n"
 
-msgid "--store [filename]"
-msgstr "--store [fájlnév]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [fájlnév]"
-
 #, fuzzy, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "Visszafejtés sikertelen: %s.\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [fájlnév]"
-
-#, fuzzy
-msgid "--symmetric --encrypt [filename]"
-msgstr "--sign --encrypt [fájlnév]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2446,16 +2448,6 @@ msgstr ""
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "Lehet, hogy nem használhatja %s-t %s módban!\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [fájlnév]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [fájlnév]"
-
-#, fuzzy
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--sign --encrypt [fájlnév]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2463,28 +2455,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "Lehet, hogy nem használhatja %s-t %s módban!\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [fájlnév]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [fájlnév]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [fájlnév]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key felh-azonosító"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key felh-azonosító"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key felh-azonosító [parancsok]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key felh-azonosító"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "Küldés a kulcsszerverre sikertelen: %s\n"
@@ -2531,9 +2501,6 @@ msgstr "Hiba a jelszó létrehozásakor: %s.\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[fájlnév]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Kezdheti gépelni az üzenetet...\n"
 
@@ -3567,6 +3534,10 @@ msgstr "Nincs ilyen felhasználói azonosító.\n"
 msgid "Nothing to sign.\n"
 msgstr "Nincs mit aláírni a %08lX kulccsal!\n"
 
+#, fuzzy, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "%s nem érvényes karakterkiosztás!\n"
+
 msgid "Digest: "
 msgstr "Kivonat: "
 
@@ -5068,7 +5039,7 @@ msgstr ""
 
 #, fuzzy
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5324,7 +5295,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5717,6 +5688,13 @@ msgstr ""
 "Támogatott algoritmusok:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "Hiba a jelszó létrehozásakor: %s.\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "Hiba %s-ra/-re küldéskor: %s\n"
 
@@ -5731,6 +5709,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: Hiba könyvtárrekord írásakor: %s.\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "Hiba %s-ra/-re küldéskor: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "Hiba \"%s\" olvasásakor: %s\n"
 
@@ -5744,8 +5730,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5793,38 +5781,74 @@ msgid "this key"
 msgstr "kulcsok listázása"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|algo [fájlok]|üzenet kivonatának kiírása"
+msgstr[1] "|algo [fájlok]|üzenet kivonatának kiírása"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "%lu jelszóval rejtjelezve\n"
+msgstr[1] "%lu jelszóval rejtjelezve\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|algo [fájlok]|üzenet kivonatának kiírása"
+msgstr[1] "|algo [fájlok]|üzenet kivonatának kiírása"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|algo [fájlok]|üzenet kivonatának kiírása"
 msgstr[1] "|algo [fájlok]|üzenet kivonatának kiírása"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|algo [fájlok]|üzenet kivonatának kiírása"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5851,77 +5875,86 @@ msgid "resetting keydb: %s\n"
 msgstr "Hiba a \"%s\" kulcskarika írásakor: %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "Hiba \"%s\" olvasásakor: %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "Hiba a jelszó létrehozásakor: %s.\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Töröltem %d aláírást.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|algo [fájlok]|üzenet kivonatának kiírása"
 msgstr[1] "|algo [fájlok]|üzenet kivonatának kiírása"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "%lu jelszóval rejtjelezve\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|algo [fájlok]|üzenet kivonatának kiírása"
 msgstr[1] "|algo [fájlok]|üzenet kivonatának kiírása"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Eljárásmód: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5957,7 +5990,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "Hiba %s-ra/-re küldéskor: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8635,6 +8668,14 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "Hiba \"%s\" olvasásakor: %s\n"
+
+#, fuzzy, c-format
+msgid "error parsing '%s'\n"
+msgstr "Hiba \"%s\" olvasásakor: %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8653,6 +8694,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8849,6 +8893,54 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [fájlnév]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [fájlnév]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [fájlnév]"
+
+#, fuzzy
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--sign --encrypt [fájlnév]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [fájlnév]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [fájlnév]"
+
+#, fuzzy
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [fájlnév]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [fájlnév]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [fájlnév]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [fájlnév]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key felh-azonosító"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key felh-azonosító"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key felh-azonosító [parancsok]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key felh-azonosító"
+
+#~ msgid "[filename]"
+#~ msgstr "[fájlnév]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "A kulcsblokk törlése sikertelen: %s.\n"
index fc9af08..dbc4155 100644 (file)
--- a/po/id.po
+++ b/po/id.po
@@ -1917,6 +1917,11 @@ msgstr "buat sepasang kunci baru"
 msgid "quickly revoke a user-id"
 msgstr "buat sepasang kunci baru"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "buat sepasang kunci baru"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2030,12 +2035,22 @@ msgstr ""
 "@\n"
 "(Lihat man page untuk daftar lengkap semua perintah dan option)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2044,7 +2059,7 @@ msgstr ""
 "Contoh:\n"
 "\n"
 " -se -r Bob [file]       tandai dan enkripsi untuk user Bob\n"
-" --clearsign [file]      buat signature berbentuk teks\n"
+" --clear-sign [file]      buat signature berbentuk teks\n"
 " --detach-sign [file]    buat signature detached\n"
 " --list-keys [nama]      tampilkan kunci\n"
 " --fingerprint [nama]    tampilkan fingerprint\n"
@@ -2427,23 +2442,10 @@ msgstr ""
 "Peringatan: penerima yang disebutkan (-r) tanpa menggunakan enkripsi public "
 "key \n"
 
-msgid "--store [filename]"
-msgstr "--store [namafile]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [namafile]"
-
 #, fuzzy, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "dekripsi gagal: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [namafile]"
-
-#, fuzzy
-msgid "--symmetric --encrypt [filename]"
-msgstr "--sign --encrypt [namafile]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2451,16 +2453,6 @@ msgstr ""
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "anda tidak boleh menggunakan %s saat dalam mode %s.\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [namafile]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [namafile]"
-
-#, fuzzy
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--sign --encrypt [namafile]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2468,28 +2460,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "anda tidak boleh menggunakan %s saat dalam mode %s.\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [namafile]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [namafile]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [namafile]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key id-user"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key id-user"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key id-user [perintah]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key id-user"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "Pengiriman keyserver gagal: %s\n"
@@ -2536,9 +2506,6 @@ msgstr "kesalahan penciptaan passphrase: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[namafile]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Teruskan dan ketikkan pesan anda ....\n"
 
@@ -3572,6 +3539,10 @@ msgstr "Tidak ada ID user tersebut.\n"
 msgid "Nothing to sign.\n"
 msgstr "Tidak ada yang ditandai dengan kunci %08lX\n"
 
+#, fuzzy, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "%s bukanlah set karakter yang valid\n"
+
 msgid "Digest: "
 msgstr "Digest: "
 
@@ -5062,7 +5033,7 @@ msgstr ""
 
 #, fuzzy
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5316,7 +5287,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5709,6 +5680,13 @@ msgstr ""
 "Algoritma yang didukung:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "kesalahan penciptaan passphrase: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "kesalahan mengirim ke `%s': %s\n"
 
@@ -5723,6 +5701,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: kesalahan menulis dir record: %s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "kesalahan mengirim ke `%s': %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "kesalahan membaca `%s': %s\n"
 
@@ -5736,8 +5722,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5785,38 +5773,74 @@ msgid "this key"
 msgstr "tampilkan kunci"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|algo [file]|cetak digest pesan"
+msgstr[1] "|algo [file]|cetak digest pesan"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "dienkripsi dengan passphrase %lu\n"
+msgstr[1] "dienkripsi dengan passphrase %lu\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|algo [file]|cetak digest pesan"
+msgstr[1] "|algo [file]|cetak digest pesan"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|algo [file]|cetak digest pesan"
 msgstr[1] "|algo [file]|cetak digest pesan"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|algo [file]|cetak digest pesan"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5843,77 +5867,86 @@ msgid "resetting keydb: %s\n"
 msgstr "kesalahan menulis keyring `%s': %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "kesalahan membaca `%s': %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "kesalahan penciptaan passphrase: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Menghapus %d signature.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|algo [file]|cetak digest pesan"
 msgstr[1] "|algo [file]|cetak digest pesan"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "dienkripsi dengan passphrase %lu\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|algo [file]|cetak digest pesan"
 msgstr[1] "|algo [file]|cetak digest pesan"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Kebijakan: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5949,7 +5982,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "kesalahan mengirim ke `%s': %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8626,6 +8659,14 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "kesalahan membaca `%s': %s\n"
+
+#, fuzzy, c-format
+msgid "error parsing '%s'\n"
+msgstr "kesalahan membaca `%s': %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8644,6 +8685,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8840,6 +8884,54 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [namafile]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [namafile]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [namafile]"
+
+#, fuzzy
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--sign --encrypt [namafile]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [namafile]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [namafile]"
+
+#, fuzzy
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [namafile]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [namafile]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [namafile]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [namafile]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key id-user"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key id-user"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key id-user [perintah]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key id-user"
+
+#~ msgid "[filename]"
+#~ msgstr "[namafile]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "gagal menghapus keyblok: %s\n"
index ea6c820..1d8b580 100644 (file)
--- a/po/it.po
+++ b/po/it.po
@@ -1924,6 +1924,11 @@ msgstr "genera una nuova coppia di chiavi"
 msgid "quickly revoke a user-id"
 msgstr "genera una nuova coppia di chiavi"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "genera una nuova coppia di chiavi"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2037,12 +2042,22 @@ msgstr ""
 "@\n"
 "(Vedi la man page per una lista completa di tutti i comandi e opzioni)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2051,7 +2066,7 @@ msgstr ""
 "Esempi:\n"
 "\n"
 " -se -r Bob [file]          firma e cifra per l'utente Bob\n"
-" --clearsign [file]         fai una firma mantenendo il testo in chiaro\n"
+" --clear-sign [file]         fai una firma mantenendo il testo in chiaro\n"
 " --detach-sign [file]       fai una firma separata\n"
 " --list-keys [nomi]         mostra le chiavi\n"
 " --fingerprint [nomi]       mostra le impronte digitali\n"
@@ -2433,23 +2448,10 @@ msgstr ""
 "ATTENZIONE: sono stati indicati dei destinatari (-r) senza usare la\n"
 "crittografia a chiave pubblica\n"
 
-msgid "--store [filename]"
-msgstr "--store [nomefile]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [nomefile]"
-
 #, fuzzy, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "decifratura fallita: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [nomefile]"
-
-#, fuzzy
-msgid "--symmetric --encrypt [filename]"
-msgstr "--sign --encrypt [nomefile]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2457,16 +2459,6 @@ msgstr ""
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "non è possibile usare %s in modalità %s\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [nomefile]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [nomefile]"
-
-#, fuzzy
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--sign --encrypt [nomefile]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2474,28 +2466,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "non è possibile usare %s in modalità %s\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [nomefile]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [nomefile]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [nomefile]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key user-id"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key user-id"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key user-id [comandi]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key user-id"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "invio al keyserver fallito: %s\n"
@@ -2542,9 +2512,6 @@ msgstr "errore nella creazione della passhprase: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[nomefile]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Vai avanti e scrivi il messaggio...\n"
 
@@ -3579,6 +3546,10 @@ msgstr "User ID inesistente.\n"
 msgid "Nothing to sign.\n"
 msgstr "Niente da firmare con la chiave %08lX\n"
 
+#, fuzzy, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "%s non è un set di caratteri valido\n"
+
 msgid "Digest: "
 msgstr "Digest: "
 
@@ -5086,7 +5057,7 @@ msgstr ""
 
 #, fuzzy
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5344,7 +5315,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5750,6 +5721,13 @@ msgstr ""
 "Algoritmi gestiti:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "errore nella creazione della passhprase: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "errore leggendo `%s': %s\n"
 
@@ -5764,6 +5742,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: errore durante la scrittura del dir record: %s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "errore leggendo `%s': %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "errore leggendo `%s': %s\n"
 
@@ -5777,8 +5763,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5826,38 +5814,74 @@ msgid "this key"
 msgstr "elenca le chiavi"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|algo [files]|stampa tutti i message digests"
+msgstr[1] "|algo [files]|stampa tutti i message digests"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "cifratto con %lu passphrase\n"
+msgstr[1] "cifratto con %lu passphrase\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|algo [files]|stampa tutti i message digests"
+msgstr[1] "|algo [files]|stampa tutti i message digests"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|algo [files]|stampa tutti i message digests"
 msgstr[1] "|algo [files]|stampa tutti i message digests"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|algo [files]|stampa tutti i message digests"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5884,77 +5908,86 @@ msgid "resetting keydb: %s\n"
 msgstr "errore scrivendo il portachiavi `%s': %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "errore leggendo `%s': %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "errore nella creazione della passhprase: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Cancellate %d firme.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|algo [files]|stampa tutti i message digests"
 msgstr[1] "|algo [files]|stampa tutti i message digests"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "cifratto con %lu passphrase\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|algo [files]|stampa tutti i message digests"
 msgstr[1] "|algo [files]|stampa tutti i message digests"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Policy: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5990,7 +6023,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "errore leggendo `%s': %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8670,6 +8703,14 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "errore leggendo `%s': %s\n"
+
+#, fuzzy, c-format
+msgid "error parsing '%s'\n"
+msgstr "errore leggendo `%s': %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8688,6 +8729,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8884,6 +8928,54 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [nomefile]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [nomefile]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [nomefile]"
+
+#, fuzzy
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--sign --encrypt [nomefile]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [nomefile]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [nomefile]"
+
+#, fuzzy
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [nomefile]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [nomefile]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [nomefile]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [nomefile]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key user-id"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key user-id"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key user-id [comandi]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key user-id"
+
+#~ msgid "[filename]"
+#~ msgstr "[nomefile]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "cancellazione del keyblock fallita: %s\n"
index e94e34f..bd143bf 100644 (file)
--- a/po/ja.po
+++ b/po/ja.po
@@ -8,9 +8,9 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: gnupg 2.1.15\n"
+"Project-Id-Version: gnupg 2.1.16\n"
 "Report-Msgid-Bugs-To: translations@gnupg.org\n"
-"PO-Revision-Date: 2016-09-02 16:58+0200\n"
+"PO-Revision-Date: 2016-12-17 16:29+0900\n"
 "Last-Translator: NIIBE Yutaka <gniibe@fsij.org>\n"
 "Language-Team: none\n"
 "Language: ja\n"
@@ -296,10 +296,8 @@ msgstr "デーモン・モードで実行 (バックグラウンド)"
 msgid "run in server mode (foreground)"
 msgstr "サーバ・モードで実行 (フォアグラウンド)"
 
-#, fuzzy
-#| msgid "run in server mode"
 msgid "run in supervised mode"
-msgstr "ã\82µã\83¼ã\83\90・モードで実行"
+msgstr "ã\82¹ã\83¼ã\83\91ã\83¼ã\83\90ã\82¤ã\82ºã\83\89・モードで実行"
 
 msgid "verbose"
 msgstr "冗長"
@@ -742,10 +740,9 @@ msgstr "警告: '%s'の安全でない所有 \"%s\"\n"
 msgid "Warning: unsafe permissions on %s \"%s\"\n"
 msgstr "警告: '%s'の安全でない許可 \"%s\"\n"
 
-#, fuzzy, c-format
-#| msgid "waiting for the agent to come up ... (%ds)\n"
+#, c-format
 msgid "waiting for file '%s' to become accessible ...\n"
-msgstr "agentの起動のため、%d秒待ちます\n"
+msgstr "ファイル'%s'がアクセスできるのを待ちます...\n"
 
 #, c-format
 msgid "renaming '%s' to '%s' failed: %s\n"
@@ -1769,6 +1766,9 @@ msgstr "すばやく新しいユーザIDを追加"
 msgid "quickly revoke a user-id"
 msgstr "すばやくユーザIDを失効"
 
+msgid "quickly set a new expiration date"
+msgstr "すばやく新しい有効期限を設定"
+
 msgid "full featured key pair generation"
 msgstr "全機能の鍵ペアを生成"
 
@@ -1877,7 +1877,7 @@ msgid ""
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -1886,7 +1886,7 @@ msgstr ""
 "例:\n"
 "\n"
 " -se -r Bob [ファイル]      ユーザBobへ署名と暗号化\n"
-" --clearsign [ファイル]     クリア・テクスト署名を作成\n"
+" --clear-sign [ファイル]     クリア・テクスト署名を作成\n"
 " --detach-sign [ファイル]   分遣署名を作成\n"
 " --list-keys [名前]         鍵を表示\n"
 " --fingerprint [名前]       フィンガープリントを表示\n"
@@ -2025,10 +2025,9 @@ msgstr "鍵の一覧に鍵リングの名前を表示する"
 msgid "show expiration dates during signature listings"
 msgstr "署名の一覧時に有効期限の日付を表示する"
 
-#, fuzzy, c-format
-#| msgid "invalid argument for option \"%.50s\"\n"
+#, c-format
 msgid "valid values for option '%s':\n"
-msgstr "オプション\"%.50s\"には無効な引数です\n"
+msgstr "オプション'%s'に有効な値:\n"
 
 #, c-format
 msgid "unknown TOFU policy '%s'\n"
@@ -2037,10 +2036,9 @@ msgstr "不明のTOFUポリシー'%s'\n"
 msgid "(use \"help\" to list choices)\n"
 msgstr "(選択肢の一覧には\"help\"を使ってください)\n"
 
-#, fuzzy, c-format
-#| msgid "invalid argument for option \"%.50s\"\n"
+#, c-format
 msgid "invalid value for option '%s'\n"
-msgstr "オプション\"%.50s\"には無効な引数です\n"
+msgstr "オプション'%s'に無効な値です\n"
 
 #, c-format
 msgid "Note: old default options file '%s' ignored\n"
@@ -2242,22 +2240,10 @@ msgstr "信用データベースの初期化に失敗しました: %s\n"
 msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr "*警告*: 公開鍵暗号を使わずに、受取人 (-r) を指定しています\n"
 
-msgid "--store [filename]"
-msgstr "--store [ファイル名]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [ファイル名]"
-
 #, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "'%s'の共通鍵暗号に失敗しました: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [ファイル名]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [ファイル名]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "--symmetric --encryptを--s2k-mode 0で使うことはできません\n"
 
@@ -2265,15 +2251,6 @@ msgstr "--symmetric --encryptを--s2k-mode 0で使うことはできません\n"
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "--symmetric --encryptを%sモードで使うことはできません\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [ファイル名]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [ファイル名]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [ファイル名]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr "--symmetric --sign --encryptを--s2k-mode 0で使うことはできません\n"
 
@@ -2281,27 +2258,6 @@ msgstr "--symmetric --sign --encryptを--s2k-mode 0で使うことはできま
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "--symmetric --sign --encryptを%sモードで使うことはできません\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [ファイル名]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [ファイル名]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [ファイル名]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key ユーザid"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key ユーザid"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key ユーザid [コマンド]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd <ユーザid>"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "鍵サーバへの送信に失敗しました: %s\n"
@@ -2346,9 +2302,6 @@ msgstr "鍵指定'%s'の構文解析エラー: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr "'%s'は有効な鍵ID, フィンガープリント、keygripではないようです。\n"
 
-msgid "[filename]"
-msgstr "[ファイル名]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "開始します。メッセージを打ってください ...\n"
 
@@ -3293,6 +3246,10 @@ msgstr "マッチするユーザIDはありません。"
 msgid "Nothing to sign.\n"
 msgstr "署名するものがありません。\n"
 
+#, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "'%s'は、有効な有効期限ではありません\n"
+
 msgid "Digest: "
 msgstr "ダイジェスト: "
 
@@ -4683,24 +4640,18 @@ msgstr "この鍵はたぶん本人のものです\n"
 msgid "This key belongs to us\n"
 msgstr "この鍵は自分のものです\n"
 
-#, fuzzy, c-format
-#| msgid "root certificate has now been marked as trusted\n"
+#, c-format
 msgid "%s: This key is bad!  It has been marked as untrusted!\n"
-msgstr "ルート証明書は信用すると今、マークされました\n"
+msgstr "%s: この鍵はダメです! 信用できないとマークされています!\n"
 
-#, fuzzy
-#| msgid ""
-#| "It is NOT certain that the key belongs to the person named\n"
-#| "in the user ID.  If you *really* know what you are doing,\n"
-#| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
-"ã\81\93ã\81®é\8dµã\81¯ã\80\81ã\81\93ã\81®ã\83¦ã\83¼ã\82¶IDã\82\92ã\81ªã\81®ã\82\8bæ\9c¬äººã\81®ã\82\82ã\81®ã\81\8bã\81©ã\81\86ã\81\8b確信ã\81§ã\81\8d\n"
-"ã\81¾ã\81\9bã\82\93ã\80\82ä»\8aã\81\8bã\82\89è¡\8cã\81\86ã\81\93ã\81¨ã\82\92ï¼\8aæ\9c¬å½\93ã\81«ï¼\8aç\90\86解ã\81\97ã\81¦ã\81\84ã\81ªã\81\84å ´å\90\88ã\81«ã\81¯ã\80\81\n"
-"次の質問にはnoと答えてください。\n"
+"ã\81\93ã\81®é\8dµã\81¯ã\80\81ã\83\80ã\83¡ã\81§ã\81\99! ä¿¡ç\94¨ã\81§ã\81\8dã\81ªã\81\84ã\81¨ã\83\9eã\83¼ã\82¯ã\81\95ã\82\8cã\81¦ã\81\84ã\81¾ã\81\99! *æ\9c¬å½\93ã\81«*\n"
+"ã\81ªã\81«ã\82\92ã\81\97ã\81¦ã\81\84ã\82\8bã\81®ã\81\8bå\88\86ã\81\8bã\81£ã\81¦ã\81\84ã\82\8bå ´å\90\88ã\81«ã\81¯ã\80\81次ã\81®è³ªå\95\8fã\81«ã\81¯ yes ã\81¨\n"
+"答えてください。\n"
 
 msgid ""
 "It is NOT certain that the key belongs to the person named\n"
@@ -4937,12 +4888,12 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 "秘密鍵のコンプロマイズや紛失の場合、これを使ってこの鍵を失効させます。\n"
 "しかし、秘密鍵がまだアクセス可能である場合、新しい失効証明書を生成し、\n"
 "失効の理由をつける方がよいでしょう。詳細は、GnuPGマニュアルのgpgコマンド \"--"
-"gen-revoke\"の記述をご覧ください。"
+"generate-revocation\"の記述をご覧ください。"
 
 msgid ""
 "To avoid an accidental use of this file, a colon has been inserted\n"
@@ -5306,6 +5257,13 @@ msgid "unsupported TOFU database version: %s\n"
 msgstr "サポートされていないTOFUデータベースバージョン: %s\n"
 
 #, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "'ultimately_trusted_keys' TOFUテーブル作成エラー: %s\n"
+
+msgid "TOFU DB error"
+msgstr "TOFU DBエラー"
+
+#, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "TOFUデータベースの読み込みエラー: %s\n"
 
@@ -5318,6 +5276,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "TOFUデータベースの初期化エラー: %s\n"
 
 #, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "'encryptions' TOFUデータベースの作成エラー: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr "バインディングDBにカラムeffective_policyを追加: %s\n"
+
+#, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "TOFUデータベースのオープンでエラー '%s': %s\n"
 
@@ -5325,42 +5291,34 @@ msgstr "TOFUデータベースのオープンでエラー '%s': %s\n"
 msgid "error updating TOFU database: %s\n"
 msgstr "TOFUデータベースの更新エラー: %s\n"
 
-#, fuzzy, c-format
-#| msgid "The email address \"%s\" is associated with %d key:\n"
-#| msgid_plural "The email address \"%s\" is associated with %d keys:\n"
+#, c-format
 msgid ""
 "This is the first time the email address \"%s\" is being used with key %s."
-msgstr "電子メールアドレス\"%s\"は%d個の鍵に結びつけられます:\n"
-
-#, fuzzy, c-format
-#| msgid "The email address \"%s\" is associated with %d key:\n"
-#| msgid_plural "The email address \"%s\" is associated with %d keys:\n"
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr "電子メールアドレス\"%s\"は%d個の鍵に結びつけられます:\n"
-
-#, fuzzy
-#| msgid ""
-#| "The key with fingerprint %s raised a conflict with the binding %s.  Since "
-#| "this binding's policy was 'auto', it was changed to 'ask'."
+msgstr "電子メールアドレス\"%s\"が鍵%sに使われたのはこれが最初です。"
+
+#, c-format
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] "電子メールアドレス\"%s\"は%d個の鍵に結びつけられます!"
+
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
-"鍵のフィンガープリント%sがバインディング%sと衝突しました。このバインディング"
-"ポリシーは'auto'だったので、'ask'に変更されました。"
+"  このバインディングポリシーは'auto'だったので、'ask'に変更されました。"
 
 #, c-format
 msgid ""
 "Please indicate whether this email address should be associated with key %s "
 "or whether you think someone is impersonating \"%s\"."
 msgstr ""
+"この電子メールアドレスが鍵 %s に結びつけられるべきか、あるいは誰かが\"%s\" に"
+"なりすましていると思うか指定してください。"
 
 #, c-format
 msgid "error gathering other user IDs: %s\n"
 msgstr "ほかのユーザIDの収集エラー: %s\n"
 
-#, fuzzy
-#| msgid "list key and user IDs"
 msgid "This key's user IDs:\n"
-msgstr "鍵とユーザIDの一覧"
+msgstr "この鍵のユーザID:\n"
 
 #, c-format
 msgid "policy: %s"
@@ -5380,48 +5338,68 @@ msgid "Statistics for keys with the email address \"%s\":\n"
 msgstr "この電子メールアドレス\"%s\"の鍵の統計:\n"
 
 msgid ", "
-msgstr ""
+msgstr ""
 
 msgid "this key"
 msgstr "この鍵"
 
-#, fuzzy, c-format
-#| msgid "Verified %ld messages signed by \"%s\"."
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
-msgstr[0] "%ld個のメッセージを検証しました(\"%s\"で署名されたもの)。"
+#, c-format
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "%d個のメッセージを検証しました。"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
-msgstr[0] ""
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "%d個のメッセージを暗号化しました。"
+
+#, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "%d個のメッセージが未来に検証されました。"
+
+#, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
+msgstr[0] "%d個のメッセージが未来に暗号化されました。"
+
+#, c-format
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] "過去%d日間に検証されたメッセージの数: %d."
 
-#, fuzzy, c-format
-#| msgid " over the past %ld day."
-#| msgid_plural " over the past %ld days."
-msgid " over the past day."
-msgid_plural " over the past %d days."
-msgstr[0] "過去%ld日に。"
-
-#, fuzzy, c-format
-#| msgid " over the past %ld month."
-#| msgid_plural " over the past %ld months."
-msgid " over the past month."
-msgid_plural " over the past %d months."
-msgstr[0] "過去%ld月に。"
-
-#, fuzzy, c-format
-#| msgid " over the past %ld day."
-#| msgid_plural " over the past %ld days."
-msgid " over the past year."
-msgid_plural " over the past %d years."
-msgstr[0] "過去%ld日に。"
-
-#, fuzzy
-#| msgid " over the past %ld day."
-#| msgid_plural " over the past %ld days."
-msgid " in the past."
-msgstr "過去%ld日に。"
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
+msgstr[0] "過去%d日間に暗号化されたメッセージの数: %d."
+
+#, c-format
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
+msgstr[0] "過去%d月の間に検証されたメッセージの数: %d."
+
+#, c-format
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
+msgstr[0] "過去%d月の間に暗号化されたメッセージの数: %d."
+
+#, c-format
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
+msgstr[0] "過去%d年間に検証されたメッセージの数: %d."
+
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] "過去%d年間に暗号化されたメッセージの数: %d."
+
+#, c-format
+msgid "Messages verified in the past: %d."
+msgstr "これまでに検証されたメッセージの数: %d."
+
+#, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "これまでに暗号化されたメッセージの数: %d."
 
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
@@ -5437,133 +5415,102 @@ msgstr "gGaAuUrRbB"
 
 msgid "(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? "
 msgstr ""
-"(G)ood-è\89¯, (A)ccept once-ä¸\80度ã\81 ã\81\91è\89¯, (U)nknown-ä¸\8dæ\98\8e, (R)eject once-ä¸\80度ã\81 ã\81\91"
-"否, (B)ad-ダメ? "
+"(G)ood-è\89¯, (A)ccept once-ä¸\80度ã\81 ã\81\91èª\8dã\82\81ã\82\8b, (U)nknown-ä¸\8dæ\98\8e, (R)eject once-ä¸\80度ã\81 "
+"否, (B)ad-ダメ? "
 
 msgid "Defaulting to unknown."
-msgstr ""
+msgstr "不明がデフォルトです。"
 
 msgid "TOFU db corruption detected.\n"
-msgstr ""
+msgstr "TOFU dbが壊れていることが検出されました。\n"
 
-#, fuzzy, c-format
-#| msgid "error writing key: %s\n"
+#, c-format
 msgid "resetting keydb: %s\n"
-msgstr "鍵の書き込みエラー: %s\n"
+msgstr "keydbをリセット: %s\n"
 
 #, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
-msgstr "TOFUã\83\87ã\83¼ã\82¿ã\83\99ã\83¼ã\82¹ã\81®ä¿¡ç\94¨ã\83¬ã\83\99ã\83«ã\81®è¨­å®\9aã\82¨ã\83©ã\83¼: %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
+msgstr "TOFUã\83\90ã\82¤ã\83³ã\83\87ã\82£ã\83³ã\82°ã\81®ã\83\9dã\83ªã\82·ã\83¼ã\82\92 %s ã\81«è¨­å®\9aã\82¨ã\83©ã\83¼\n"
 
 #, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "TOFUポリシーの作成エラー: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
-msgstr[0] "%d~年"
+msgid "%lld~year"
+msgid_plural "%lld~years"
+msgstr[0] "%lld年"
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
-msgstr[0] "%d~月"
+msgid "%lld~month"
+msgid_plural "%lld~months"
+msgstr[0] "%lld月"
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
-msgstr[0] "%d~月"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
+msgstr[0] "%lld週"
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
-msgstr[0] "%d~時間"
+msgid "%lld~day"
+msgid_plural "%lld~days"
+msgstr[0] "%lld日"
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
-msgstr[0] "%d~分"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
+msgstr[0] "%lld時間"
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
-msgstr[0] "%d~秒"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
+msgstr[0] "%lld分"
 
 #, c-format
-msgid "%s: "
-msgstr ""
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] "%lld秒"
 
-#, fuzzy, c-format
-#| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
-msgstr "%d個の署名を削除しました。\n"
-
-#, fuzzy, c-format
-#| msgid ""
-#| "Verified %ld message signed by \"%s\"\n"
-#| "in the past %s."
-#| msgid_plural ""
-#| "Verified %ld messages signed by \"%s\"\n"
-#| "in the past %s."
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
-msgstr[0] "メッセージを%ld回検証しました(\"%s\"で署名されたもの。かつて %s)。"
-
-#, fuzzy, c-format
-#| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
-msgstr "%lu 個のパスフレーズで暗号化\n"
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
+msgstr "%s: 0個の署名を検証、0個のメッセージを暗号化しました。"
+
+#, c-format
+msgid "%s: Verified 0 signatures."
+msgstr "%s: 0個の署名を検証しました。"
+
+#, c-format
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
+msgstr[0] "%s: 署名を%ld個検証しました(これまで %s に)。"
+
+msgid "Encrypted 0 messages."
+msgstr "0 個のメッセージを暗号化しました。"
+
+#, c-format
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
+msgstr[0] "メッセージを%ld個暗号化しました(これまで %s に)。"
+
+#, c-format
+msgid "(policy: %s)"
+msgstr "(ポリシー: %s)"
 
-#, fuzzy, c-format
-#| msgid ""
-#| "Verified %ld message signed by \"%s\"\n"
-#| "in the past %s."
-#| msgid_plural ""
-#| "Verified %ld messages signed by \"%s\"\n"
-#| "in the past %s."
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
-msgstr[0] "メッセージを%ld回検証しました(\"%s\"で署名されたもの。かつて %s)。"
-
-#, fuzzy
-#| msgid "Warning: we've have yet to see a message signed by this key!\n"
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
-msgstr "è­¦å\91\8a: ã\81\93ã\81®é\8dµã\81§ç½²å\90\8dã\81\95ã\82\8cã\81\9fã\83¡ã\83\83ã\82»ã\83¼ã\82¸ã\82\92æ¤\9c証ã\81\97ã\81\9fã\81\93ã\81¨ã\81¯ã\81\82ã\82\8aません!\n"
+msgstr "è­¦å\91\8a: ã\81\93ã\81®é\8dµã\81¨ã\83¦ã\83¼ã\82¶IDã\81§ç½²å\90\8dã\81\95ã\82\8cã\81\9fã\83¡ã\83\83ã\82»ã\83¼ã\82¸ã\81¯ä¸\80度ã\82\82è¦\8bã\81¦ません!\n"
 
-#, fuzzy
-#| msgid "Warning: we've only seen a single message signed by this key!\n"
 msgid ""
 "Warning: we've only seen one message signed using this key and user id!\n"
-msgstr ""
-"警告: この鍵で署名されたメッセージを検証したことは一度しかありません!\n"
+msgstr "警告: この鍵とユーザIDで署名されたメッセージは一度しか見てません!\n"
 
-#, fuzzy
-#| msgid "Warning: we've have yet to see a message signed by this key!\n"
 msgid "Warning: you have yet to encrypt a message to this key!\n"
-msgstr "è­¦å\91\8a: ã\81\93ã\81®é\8dµã\81§ç½²å\90\8dã\81\95ã\82\8cã\81\9fã\83¡ã\83\83ã\82»ã\83¼ã\82¸ã\82\92æ¤\9c証ã\81\97ã\81\9fã\81\93ã\81¨ã\81¯ありません!\n"
+msgstr "è­¦å\91\8a: ã\81\93ã\81®é\8dµã\81¸ã\81¯ä¸\80ã\81¤ã\82\82ã\83¡ã\83\83ã\82»ã\83¼ã\82¸ã\82\92æ\9a\97å\8f·å\8c\96ã\81\97ã\81\9fã\81\93ã\81¨ã\81\8cありません!\n"
 
-#, fuzzy
-#| msgid "Warning: we've only seen a single message signed by this key!\n"
 msgid "Warning: you have only encrypted one message to this key!\n"
-msgstr ""
-"警告: この鍵で署名されたメッセージを検証したことは一度しかありません!\n"
-
-#, fuzzy, c-format
-#| msgid ""
-#| "Warning: if you think you've seen more than %ld message signed by this "
-#| "key, then this key might be a forgery!  Carefully examine the email "
-#| "address for small variations.  If the key is suspect, then use\n"
-#| "  %s\n"
-#| "to mark it as being bad.\n"
-#| msgid_plural ""
-#| "Warning: if you think you've seen more than %ld messages signed by this "
-#| "key, then this key might be a forgery!  Carefully examine the email "
-#| "address for small variations.  If the key is suspect, then use\n"
-#| "  %s\n"
-#| "to mark it as being bad.\n"
+msgstr "警告: この鍵へは一つのメッセージしか暗号化したことがありません!\n"
+
+#, c-format
 msgid ""
 "Warning: if you think you've seen more signatures by this key and user id, "
 "then this key might be a forgery!  Carefully examine the email address for "
@@ -5577,9 +5524,9 @@ msgid_plural ""
 "  %s\n"
 "to mark it as being bad.\n"
 msgstr[0] ""
-"è­¦å\91\8a: ã\81\93ã\81®é\8dµã\81§ç½²å\90\8dã\81\95ã\82\8cã\81\9fã\83¡ã\83\83ã\82»ã\83¼ã\82¸ã\82\92%ldå\9b\9e以ä¸\8aè¦\8bã\81\9fã\81¨æ\80\9dã\81\86å ´å\90\88ã\80\81å\81½è\80\85ã\81®å\8f¯è\83½æ\80§ã\81\8cã\81\82"
-"ã\82\8aã\81¾ã\81\99! å°\91æ\95°ã\81®ã\83\90ã\83ªã\82¨ã\83¼ã\82·ã\83§ã\83³ã\81§ã\81\93ã\81®é\9b»å­\90ã\83¡ã\83¼ã\83«ã\82¢ã\83\89ã\83¬ã\82¹ã\82\92注æ\84\8fæ·±ã\81\8fæ¤\9cæ\9f»ã\81\97ã\81¦ã\81\8fã\81 ã\81\95"
-"い。この鍵が疑われる場合、\n"
+"è­¦å\91\8a: ã\81\93ã\81®é\8dµã\81¨ã\83¦ã\83¼ã\82¶IDã\81«ã\82\88ã\82\8bç½²å\90\8dã\82\92ã\82\82ã\81£ã\81¨è¦\8bã\81\9fã\81¨æ\80\9dã\81\86å ´å\90\88ã\80\81ã\81\93ã\81®é\8dµã\81¯å\81½ç\89©ã\81®å\8f¯è\83½æ\80§"
+"ã\81\8cã\81\82ã\82\8aã\81¾ã\81\99! å°\91æ\95°ã\81®ã\83\90ã\83ªã\82¨ã\83¼ã\82·ã\83§ã\83³ã\81§ã\81\93ã\81®é\9b»å­\90ã\83¡ã\83¼ã\83«ã\82¢ã\83\89ã\83¬ã\82¹ã\82\92注æ\84\8fæ·±ã\81\8fæ¤\9cæ\9f»ã\81\97ã\81¦ã\81\8f"
+"ã\81 ã\81\95ã\81\84ã\80\82ã\81\93ã\81®é\8dµã\81\8cç\96\91ã\82\8fã\82\8cã\82\8bå ´å\90\88ã\80\81\n"
 "  %s\n"
 "でダメとマークしてください。\n"
 
@@ -5588,13 +5535,13 @@ msgid "error opening TOFU database: %s\n"
 msgstr "TOFUデータベースのオープンでエラー: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
+"*警告*: %s に暗号化します。失効していないユーザIDが一つもないものです。\n"
 
-#, fuzzy, c-format
-#| msgid "error writing public keyring '%s': %s\n"
+#, c-format
 msgid "error setting policy for key %s, user id \"%s\": %s"
-msgstr "公開鍵リング'%s'の書き込みエラー: %s\n"
+msgstr "鍵%s, ユーザID \"%s\"のポリシーの設定エラー: %s"
 
 #, c-format
 msgid "'%s' is not a valid long keyID\n"
@@ -5837,10 +5784,8 @@ msgstr "応答にRSAのモジュラスが含まれていません\n"
 msgid "response does not contain the RSA public exponent\n"
 msgstr "応答にRSA公開指数が含まれていません\n"
 
-#, fuzzy
-#| msgid "response does not contain the EC public point\n"
 msgid "response does not contain the EC public key\n"
-msgstr "応答に楕円曲線の公開が含まれていません\n"
+msgstr "応答に楕円曲線の公開が含まれていません\n"
 
 msgid "response does not contain the public key data\n"
 msgstr "応答に公開鍵データが含まれていません\n"
@@ -7397,7 +7342,7 @@ msgid "allow sending OCSP requests"
 msgstr "OCSP要求の送信を認める"
 
 msgid "allow online software version check"
-msgstr ""
+msgstr "オンラインのソフトウェア・バージョン・チェックを許す"
 
 msgid "inhibit the use of HTTP"
 msgstr "HTTPの使用を禁止する"
@@ -8104,31 +8049,23 @@ msgstr "LDAPサーバ・リスト"
 msgid "Configuration for OCSP"
 msgstr "OCSPのコンフィグレーション"
 
-#, fuzzy
-#| msgid "GPG for OpenPGP"
 msgid "OpenPGP"
-msgstr "OpenPGPのためのGPG"
+msgstr "OpenPGP"
 
 msgid "Private Keys"
-msgstr ""
+msgstr "プライベート鍵"
 
-#, fuzzy
-#| msgid "Smartcard Daemon"
 msgid "Smartcards"
-msgstr "スマートカード・デーモン"
+msgstr "スマートカード"
 
-#, fuzzy
-#| msgid "GPG for S/MIME"
 msgid "S/MIME"
-msgstr "S/MIME のためのGPG"
+msgstr "S/MIME"
 
 msgid "Network"
-msgstr ""
+msgstr "ネットワーク"
 
-#, fuzzy
-#| msgid "PIN and Passphrase Entry"
 msgid "Passphrase Entry"
-msgstr "PINとパスフレーズの入力"
+msgstr "パスフレーズ入力"
 
 msgid "Component not suitable for launching"
 msgstr "コンポーネントが起動するために適切ではありません"
@@ -8140,6 +8077,14 @@ msgstr "コンポーネント%sの外部の検証が失敗しました"
 msgid "Note that group specifications are ignored\n"
 msgstr "グループ仕様は無視されていることに注意してください\n"
 
+#, c-format
+msgid "error closing '%s'\n"
+msgstr "'%s'でクローズのエラー\n"
+
+#, c-format
+msgid "error parsing '%s'\n"
+msgstr "'%s'でパーズのエラー\n"
+
 msgid "list all components"
 msgstr "すべてのコンポーネントをリストする"
 
@@ -8158,6 +8103,9 @@ msgstr "|COMPONENT|オプションをチェックする"
 msgid "apply global default values"
 msgstr "グローバル・デフォルト値を適用する"
 
+msgid "|FILE|update configuration files using FILE"
+msgstr "|FILE|FILEを使ってコンフィグレーション・ファイルを更新する"
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "@GPGCONF@のためにコンフィグレーション・ディレクトリを取得する"
 
@@ -8167,10 +8115,8 @@ msgstr "グローバルのコンフィグレーション・ファイルをリス
 msgid "check global configuration file"
 msgstr "グローバルのコンフィグレーション・ファイルをチェックする"
 
-#, fuzzy
-#| msgid "update the trust database"
 msgid "query the software version database"
-msgstr "信用データベースを更新"
+msgstr "ソフトウェア・バージョン・データベースに問い合わせる"
 
 msgid "reload all or a given component"
 msgstr "すべて、あるいは指定されたコンポーネントをリロードする"
@@ -8350,6 +8296,70 @@ msgstr ""
 "形式: gpg-check-pattern [オプション] パターンファイル\n"
 "パターンファイルに対して標準入力のパスフレーズを確認する\n"
 
+#~ msgid ""
+#~ "@\n"
+#~ "Examples:\n"
+#~ "\n"
+#~ " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#~ " --clearsign [file]         make a clear text signature\n"
+#~ " --detach-sign [file]       make a detached signature\n"
+#~ " --list-keys [names]        show keys\n"
+#~ " --fingerprint [names]      show fingerprints\n"
+#~ msgstr ""
+#~ "@\n"
+#~ "例:\n"
+#~ "\n"
+#~ " -se -r Bob [ファイル]      ユーザBobへ署名と暗号化\n"
+#~ " --clearsign [ファイル]     クリア・テクスト署名を作成\n"
+#~ " --detach-sign [ファイル]   分遣署名を作成\n"
+#~ " --list-keys [名前]         鍵を表示\n"
+#~ " --fingerprint [名前]       フィンガープリントを表示\n"
+
+#~ msgid "--store [filename]"
+#~ msgstr "--store [ファイル名]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [ファイル名]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [ファイル名]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [ファイル名]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [ファイル名]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [ファイル名]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [ファイル名]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [ファイル名]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [ファイル名]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [ファイル名]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key ユーザid"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key ユーザid"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key ユーザid [コマンド]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd <ユーザid>"
+
+#~ msgid "[filename]"
+#~ msgstr "[ファイル名]"
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "鍵のシャドウ化に失敗しました: %s\n"
 
@@ -8369,10 +8379,6 @@ msgstr ""
 #~ msgid "Known user IDs associated with this key:\n"
 #~ msgstr "この鍵に結びつけられた知られているユーザID:\n"
 
-#~ msgid "%ld message signed in the future."
-#~ msgid_plural "%ld messages signed in the future."
-#~ msgstr[0] "%ld個のメッセージが未来に署名されました。"
-
 #~ msgid "%ld message signed"
 #~ msgid_plural "%ld messages signed"
 #~ msgstr[0] "%ld個のメッセージに署名しました"
index c6bfb7f..ba27c10 100644 (file)
--- a/po/nb.po
+++ b/po/nb.po
@@ -1780,6 +1780,11 @@ msgstr "legg til en ny bruker-ID raskt"
 msgid "quickly revoke a user-id"
 msgstr "opphev bruker-ID raskt"
 
+#, fuzzy
+#| msgid "quickly generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "lag nytt nøkkelpar raskt"
+
 msgid "full featured key pair generation"
 msgstr "fullverdig generering av nøkkelpar"
 
@@ -1883,12 +1888,22 @@ msgstr ""
 "@\n"
 "(Se bruksanvisning for en fullstendig liste over alle kommandoer og valg)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -1897,7 +1912,7 @@ msgstr ""
 "Eksempler:\n"
 "\n"
 " -se -r Bob [fil]           signer og krypter for brukeren Bob\n"
-" --clearsign [fil]          lag klartekst-signatur\n"
+" --clear-sign [fil]          lag klartekst-signatur\n"
 " --detach-sign [fil]        lag adskilt signatur\n"
 " --list-keys [navn]         vis nøkler\n"
 " --fingerprint [navn]       vis fingeravtrykk\n"
@@ -2252,22 +2267,10 @@ msgstr "klarte ikke å starte tillitsdatabase (TrustDB): %s\n"
 msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr "ADVARSEL: mottakere (-r) valgt uten offentlig nøkkelkryptering\n"
 
-msgid "--store [filename]"
-msgstr "--store [filnavn]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [filnavn]"
-
 #, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "symmetrisk kryptering av «%s» mislyktes: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [filnavn]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [filnavn]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "du kan ikke bruke «--symmetric --encrypt» og «--s2k-mode 0» samtidig\n"
 
@@ -2275,15 +2278,6 @@ msgstr "du kan ikke bruke «--symmetric --encrypt» og «--s2k-mode 0» samtidig
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "du kan ikke velge «--symmtric» og «--encrypt» i «%s»-modus\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [filnavn]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [filnavn]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [filnavn]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 "du kan ikke bruke «--symmetric --sign --encrypt» og «--s2k-mode 0» samtidig\n"
@@ -2292,27 +2286,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "du kan ikke velge «--symmtric», «--sign» og «--encrypt» i «%s»-modus\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [filnavn]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [filnavn]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [filnavn]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key bruker-ID"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key bruker-ID"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key bruker-ID [kommandoer]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd <bruker-id>"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "sending til nøkkeltjener mislyktes: %s\n"
@@ -2359,9 +2332,6 @@ msgstr ""
 "«%s» ser hverken ut til å være en gyldig nøkkel-ID, fingeravtrykk eller "
 "nøkkelgrep\n"
 
-msgid "[filename]"
-msgstr "[filnavn]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Skriv inn melding …\n"
 
@@ -3333,6 +3303,11 @@ msgstr "Ingen treff på bruker-id-er."
 msgid "Nothing to sign.\n"
 msgstr "Ingenting å signere.\n"
 
+#, fuzzy, c-format
+#| msgid "'%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "«%s» er en ugyldig signatur-utløpstid\n"
+
 msgid "Digest: "
 msgstr "Kontrollsum: "
 
@@ -4763,7 +4738,7 @@ msgstr "rotsertifikat er nå merket som troverdig\n"
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5008,13 +4983,13 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 "Bruk det for å oppheve denne nøkkelen hvis den blir kompromittert\n"
 "eller hvis du mister den hemmelige nøkkelen. Hvis du fremdeles har\n"
 "tilgang til den hemmelige nøkkelen, er det lurt å heller lage et nytt\n"
 "opphevelsessertifikat og oppgi en grunn til opphevelsen.\n"
-"Se beskrivelse av kommandoen «--gen-revoke» i GnuPG-\n"
+"Se beskrivelse av kommandoen «--generate-revocation» i GnuPG-\n"
 "bruksanvisninga for detaljer."
 
 msgid ""
@@ -5394,6 +5369,14 @@ msgstr "feil under reversering av transaksjon i TOFU-database: %s\n"
 msgid "unsupported TOFU database version: %s\n"
 msgstr "TOFU-databaseversjon støttes ikke: %s\n"
 
+#, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "feil under oppretting av midlertidig fil: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
 #, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "feil under lesing av TOFU-database: %s\n"
@@ -5406,6 +5389,15 @@ msgstr "klarte ikke å kontrollere versjon av TOFU-database: %s\n"
 msgid "error initializing TOFU database: %s\n"
 msgstr "feil under oppstart av TOFU-database: %s\n"
 
+#, fuzzy, c-format
+#| msgid "error reading TOFU database: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "feil under lesing av TOFU-database: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
 #, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "feil under åpning av TOFU-database «%s»: %s\n"
@@ -5424,8 +5416,10 @@ msgstr "E-postadressen «%s» er tilknyttet %d nøkkel:\n"
 #, fuzzy, c-format
 #| msgid "The email address \"%s\" is associated with %d key:\n"
 #| msgid_plural "The email address \"%s\" is associated with %d keys:\n"
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr "E-postadressen «%s» er tilknyttet %d nøkkel:\n"
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] "E-postadressen «%s» er tilknyttet %d nøkkel:\n"
+msgstr[1] "E-postadressen «%s» er tilknyttet %d nøkkel:\n"
 
 #, fuzzy
 #| msgid ""
@@ -5477,47 +5471,100 @@ msgstr "denne nøkkelen"
 
 #, fuzzy, c-format
 #| msgid "Verified %ld messages signed by \"%s\"."
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
 msgstr[0] "Bekreftet %ld meldinger signert av «%s»."
 msgstr[1] "Bekreftet %ld meldinger signert av «%s»."
 
-#, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
-msgstr[0] ""
-msgstr[1] ""
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "kryptert med %lu passordfraser\n"
+msgstr[1] "kryptert med %lu passordfraser\n"
+
+#, fuzzy, c-format
+#| msgid "%ld message signed in the future."
+#| msgid_plural "%ld messages signed in the future."
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "%ld melding signert i fremtid."
+msgstr[1] "%ld meldinger signert i fremtid."
+
+#, fuzzy, c-format
+#| msgid "%ld message signed in the future."
+#| msgid_plural "%ld messages signed in the future."
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
+msgstr[0] "%ld melding signert i fremtid."
+msgstr[1] "%ld meldinger signert i fremtid."
+
+#, fuzzy, c-format
+#| msgid " over the past %ld day."
+#| msgid_plural " over the past %ld days."
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] " i løpet av gårsdagen (%ld)."
+msgstr[1] " i løpet av de %ld siste dagene."
 
 #, fuzzy, c-format
 #| msgid " over the past %ld day."
 #| msgid_plural " over the past %ld days."
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] " i løpet av gårsdagen (%ld)."
 msgstr[1] " i løpet av de %ld siste dagene."
 
 #, fuzzy, c-format
 #| msgid " over the past %ld month."
 #| msgid_plural " over the past %ld months."
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
+msgstr[0] " i løpet av den siste måneden (%ld)."
+msgstr[1] " i løpet av de siste %ld månedene."
+
+#, fuzzy, c-format
+#| msgid " over the past %ld month."
+#| msgid_plural " over the past %ld months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] " i løpet av den siste måneden (%ld)."
 msgstr[1] " i løpet av de siste %ld månedene."
 
 #, fuzzy, c-format
 #| msgid " over the past %ld day."
 #| msgid_plural " over the past %ld days."
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] " i løpet av gårsdagen (%ld)."
 msgstr[1] " i løpet av de %ld siste dagene."
 
-#, fuzzy
+#, fuzzy, c-format
+#| msgid " over the past %ld day."
+#| msgid_plural " over the past %ld days."
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] " i løpet av gårsdagen (%ld)."
+msgstr[1] " i løpet av de %ld siste dagene."
+
+#, fuzzy, c-format
 #| msgid " over the past %ld day."
 #| msgid_plural " over the past %ld days."
-msgid " in the past."
+msgid "Messages verified in the past: %d."
 msgstr " i løpet av gårsdagen (%ld)."
 
+#, fuzzy, c-format
+#| msgid ""
+#| "Verified %ld message signed by \"%s\"\n"
+#| "in the past %s."
+#| msgid_plural ""
+#| "Verified %ld messages signed by \"%s\"\n"
+#| "in the past %s."
+msgid "Messages encrypted in the past: %d."
+msgstr ""
+"Bekreftet %ld melding signert av «%s»\n"
+"siste %s."
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5544,59 +5591,78 @@ msgstr ""
 msgid "resetting keydb: %s\n"
 msgstr "feil under skriving av nøkkel: %s\n"
 
-#, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+#, fuzzy, c-format
+#| msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "feil under endring av tillitsnivå for TOFU-tilknytning til %s\n"
 
 #, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "feil under endring av TOFU-regler: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
-#, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+#, fuzzy, c-format
+#| msgid "%d~year"
+#| msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] "%d~år"
 msgstr[1] "%d~år"
 
-#, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+#, fuzzy, c-format
+#| msgid "%d~month"
+#| msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] "%d~måned"
 msgstr[1] "%d~måneder"
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#, fuzzy, c-format
+#| msgid "%d~day"
+#| msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] "%d~dag"
 msgstr[1] "%d~dager"
 
-#, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+#, fuzzy, c-format
+#| msgid "%d~hour"
+#| msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] "%d~time"
 msgstr[1] "%d~timer"
 
-#, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+#, fuzzy, c-format
+#| msgid "%d~minute"
+#| msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] "%d~minutt"
 msgstr[1] "%d~minutter"
 
-#, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+#, fuzzy, c-format
+#| msgid "%d~second"
+#| msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] "%d~sekund"
 msgstr[1] "%d~sekunder"
 
-#, c-format
-msgid "%s: "
-msgstr ""
+#, fuzzy, c-format
+#| msgid "TOFU: few signatures %d message %s"
+#| msgid_plural "TOFU: few signatures %d messages %s"
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
+msgstr "TOFU: få signaturer %d melding %s"
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Slettet %d signaturer.\n"
 
 #, fuzzy, c-format
@@ -5606,8 +5672,8 @@ msgstr "Slettet %d signaturer.\n"
 #| msgid_plural ""
 #| "Verified %ld messages signed by \"%s\"\n"
 #| "in the past %s."
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] ""
 "Bekreftet %ld melding signert av «%s»\n"
 "siste %s."
@@ -5615,9 +5681,9 @@ msgstr[1] ""
 "Bekreftet %ld meldinger signert av «%s»\n"
 "siste %s."
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "kryptert med %lu passordfraser\n"
 
 #, fuzzy, c-format
@@ -5627,8 +5693,8 @@ msgstr "kryptert med %lu passordfraser\n"
 #| msgid_plural ""
 #| "Verified %ld messages signed by \"%s\"\n"
 #| "in the past %s."
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] ""
 "Bekreftet %ld melding signert av «%s»\n"
 "siste %s."
@@ -5636,6 +5702,11 @@ msgstr[1] ""
 "Bekreftet %ld meldinger signert av «%s»\n"
 "siste %s."
 
+#, fuzzy, c-format
+#| msgid "policy: %s"
+msgid "(policy: %s)"
+msgstr "regelverk: %s"
+
 #, fuzzy
 #| msgid "Warning: we've have yet to see a message signed by this key!\n"
 msgid ""
@@ -5707,7 +5778,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "feil under åpning av TOFU-database: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8269,6 +8340,16 @@ msgstr "Ekstern bekreftelse av komponent %s mislyktes"
 msgid "Note that group specifications are ignored\n"
 msgstr "Merk at gruppespesifikasjoner blir ignorert\n"
 
+#, fuzzy, c-format
+#| msgid "error closing '%s': %s\n"
+msgid "error closing '%s'\n"
+msgstr "feil under lukking av «%s»: %s\n"
+
+#, fuzzy, c-format
+#| msgid "error hashing '%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "feil under summering av «%s»: %s\n"
+
 msgid "list all components"
 msgstr "vis alle komponenter"
 
@@ -8287,6 +8368,11 @@ msgstr "|KOMPONENT|kontroller valg"
 msgid "apply global default values"
 msgstr "bruk globale standardverdier"
 
+#, fuzzy
+#| msgid "|FILE|take policy information from FILE"
+msgid "|FILE|update configuration files using FILE"
+msgstr "|FIL|hent regler fra valgt FIL"
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "hent oppsettsmapper for @GPGCONF@"
 
@@ -8479,6 +8565,57 @@ msgstr ""
 "Syntaks: gpg-check-pattern [valg] mønsterfil\n"
 "Kontroller passordfrase oppgitt på standard innkanal mot valgt mønsterfil\n"
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [filnavn]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [filnavn]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [filnavn]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [filnavn]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [filnavn]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [filnavn]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [filnavn]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [filnavn]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [filnavn]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [filnavn]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key bruker-ID"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key bruker-ID"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key bruker-ID [kommandoer]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd <bruker-id>"
+
+#~ msgid "[filename]"
+#~ msgstr "[filnavn]"
+
+#, fuzzy
+#~| msgid " over the past %ld day."
+#~| msgid_plural " over the past %ld days."
+#~ msgid " in the past."
+#~ msgstr " i løpet av gårsdagen (%ld)."
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "skyggelegging av nøkkel mislyktes: %s\n"
 
@@ -8498,11 +8635,6 @@ msgstr ""
 #~ msgid "Known user IDs associated with this key:\n"
 #~ msgstr "Kjente bruker-id-er tilknyttet denne nøkkelen:\n"
 
-#~ msgid "%ld message signed in the future."
-#~ msgid_plural "%ld messages signed in the future."
-#~ msgstr[0] "%ld melding signert i fremtid."
-#~ msgstr[1] "%ld meldinger signert i fremtid."
-
 #~ msgid "%ld message signed"
 #~ msgid_plural "%ld messages signed"
 #~ msgstr[0] "%ld melding signert"
@@ -8607,11 +8739,6 @@ msgstr ""
 #~ msgstr[0] "%d dag"
 #~ msgstr[1] "%d dager"
 
-#~ msgid "TOFU: few signatures %d message %s"
-#~ msgid_plural "TOFU: few signatures %d messages %s"
-#~ msgstr[0] "TOFU: få signaturer %d melding %s"
-#~ msgstr[1] "TOFU: få signaturer %d meldinger %s"
-
 #~ msgid "can't gen prime with pbits=%u qbits=%u\n"
 #~ msgstr "du kan ikke lage primtall med pbits=%u qbits=%u\n"
 
index 6b77df7..8b750f3 100644 (file)
--- a/po/pl.po
+++ b/po/pl.po
@@ -1936,6 +1936,11 @@ msgstr "generacja nowej pary kluczy"
 msgid "quickly revoke a user-id"
 msgstr "generacja nowej pary kluczy"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "generacja nowej pary kluczy"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2045,12 +2050,22 @@ msgstr ""
 "@\n"
 "(Pełną listę poleceń i opcji można znaleźć w podręczniku systemowym.)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2059,7 +2074,7 @@ msgstr ""
 "Przykłady:\n"
 "\n"
 " -se -r Bob [plik]          podpisanie i zaszyfrowanie kluczem Boba\n"
-" --clearsign [plik]         podpisanie z pozostawieniem czytelności "
+" --clear-sign [plik]         podpisanie z pozostawieniem czytelności "
 "dokumentu\n"
 " --detach-sign [plik]       podpisanie z umieszczeniem podpisu w osobnym "
 "pliku\n"
@@ -2477,23 +2492,11 @@ msgstr "inicjowanie Bazy Zaufania nie powiodło się: %s\n"
 msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr "OSTRZEŻENIE: podano adresatów (-r) w działaniu które ich nie dotyczy\n"
 
-msgid "--store [filename]"
-msgstr "--store [plik]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [plik]"
-
 #, fuzzy, c-format
 #| msgid "symmetric encryption of `%s' failed: %s\n"
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "szyfrowanie symetryczne ,,%s'' nie powiodło się: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [plik]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [plik]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "nie można użyć --symmetric --encrypt wraz z --s2k-mode 0\n"
 
@@ -2501,15 +2504,6 @@ msgstr "nie można użyć --symmetric --encrypt wraz z --s2k-mode 0\n"
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "nie można użyć --symmetric --encrypt w trybie %s\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [plik]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [plik]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [plik]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr "nie można użyć --symmetric --sign --encrypt wraz z --s2k-mode 0\n"
 
@@ -2517,27 +2511,6 @@ msgstr "nie można użyć --symmetric --sign --encrypt wraz z --s2k-mode 0\n"
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "nie można użyć --symmetric --sign --encrypt w trybie %s\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [plik]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [plik]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [plik]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key nazwa użytkownika"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key nazwa użytkownika"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key nazwa użytkownika [polecenia]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd <id-użytkownika>"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "wysyłka do serwera kluczy nie powiodła się: %s\n"
@@ -2585,9 +2558,6 @@ msgstr "błąd zapisywania certyfikatu: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[nazwa pliku]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Wpisz tutaj swoją wiadomość ...\n"
 
@@ -3622,6 +3592,11 @@ msgstr "Brak takiego identyfikatora użytkownika.\n"
 msgid "Nothing to sign.\n"
 msgstr "Nie ma nic do podpisania kluczem %s\n"
 
+#, fuzzy, c-format
+#| msgid "`%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr ",,%s'' nie jest poprawnym czasem wygaśnięcia podpisu\n"
+
 msgid "Digest: "
 msgstr "Skrót: "
 
@@ -5137,7 +5112,7 @@ msgstr "główny certyfikat nie został oznaczony jako zaufany\n"
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5397,7 +5372,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5817,6 +5792,14 @@ msgid "unsupported TOFU database version: %s\n"
 msgstr "nieobsługiwany algorytm: %s"
 
 #, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "błąd tworzenia pliku tymczasowego: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error sending %s command: %s\n"
 msgid "error reading TOFU database: %s\n"
 msgstr "błąd wysyłania polecenia %s: %s\n"
@@ -5832,6 +5815,15 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: błąd zapisu wpisu katalogowego: %s\n"
 
 #, fuzzy, c-format
+#| msgid "error sending %s command: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "błąd wysyłania polecenia %s: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error opening `%s': %s\n"
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "błąd podczas otwierania ,,%s'': %s\n"
@@ -5847,8 +5839,11 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5900,43 +5895,87 @@ msgstr "lista kluczy"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "wypisanie skrótów wiadomości"
+msgstr[1] "wypisanie skrótów wiadomości"
+msgstr[2] "wypisanie skrótów wiadomości"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "zaszyfrowane za pomocą %lu haseł\n"
+msgstr[1] "zaszyfrowane za pomocą %lu haseł\n"
+msgstr[2] "zaszyfrowane za pomocą %lu haseł\n"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "wypisanie skrótów wiadomości"
+msgstr[1] "wypisanie skrótów wiadomości"
+msgstr[2] "wypisanie skrótów wiadomości"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "wypisanie skrótów wiadomości"
 msgstr[1] "wypisanie skrótów wiadomości"
 msgstr[2] "wypisanie skrótów wiadomości"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Messages encrypted in the past: %d."
+msgstr "wypisanie skrótów wiadomości"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5965,7 +6004,7 @@ msgstr "błąd zapisu klucza: %s\n"
 
 #, fuzzy, c-format
 #| msgid "error storing flags: %s\n"
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "błąd zapisywania flag: %s\n"
 
 #, fuzzy, c-format
@@ -5973,80 +6012,90 @@ msgstr "błąd zapisywania flag: %s\n"
 msgid "error changing TOFU policy: %s\n"
 msgstr "błąd tworzenia potoku: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#, c-format
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
 #, c-format
-msgid "%s: "
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "%d podpisów usuniętych.\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "wypisanie skrótów wiadomości"
 msgstr[1] "wypisanie skrótów wiadomości"
 msgstr[2] "wypisanie skrótów wiadomości"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "zaszyfrowane za pomocą %lu haseł\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "wypisanie skrótów wiadomości"
 msgstr[1] "wypisanie skrótów wiadomości"
 msgstr[2] "wypisanie skrótów wiadomości"
 
+#, fuzzy, c-format
+#| msgid "validity: %s"
+msgid "(policy: %s)"
+msgstr "poprawność: %s"
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -6084,7 +6133,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "błąd wysyłania polecenia %s: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8961,6 +9010,16 @@ msgstr "Zewnętrzna weryfikacja komponentu %s nie powiodła się"
 msgid "Note that group specifications are ignored\n"
 msgstr "Uwaga, określenia grup są ignorowane\n"
 
+#, fuzzy, c-format
+#| msgid "error closing %s: %s\n"
+msgid "error closing '%s'\n"
+msgstr "błąd zamykania %s: %s\n"
+
+#, fuzzy, c-format
+#| msgid "error in `%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "błąd w ,,%s'': %s\n"
+
 msgid "list all components"
 msgstr "lista wszystkich komponentów"
 
@@ -8980,6 +9039,11 @@ msgid "apply global default values"
 msgstr "zastosowanie globalnych wartości domyślnych"
 
 #, fuzzy
+#| msgid "|FILE|take policy information from FILE"
+msgid "|FILE|update configuration files using FILE"
+msgstr "|PLIK|pobranie informacji o polityce z PLIKU"
+
+#, fuzzy
 #| msgid "get the configuration directories for gpgconf"
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "katalogi konfiguracyjne programu gpgconf"
@@ -9186,6 +9250,51 @@ msgstr ""
 "Składnia: gpg-check-pattern [opcje] plik-wzorców\n"
 "Sprawdzanie hasła ze standardowego wejścia względem pliku wzorców\n"
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [plik]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [plik]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [plik]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [plik]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [plik]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [plik]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [plik]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [plik]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [plik]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [plik]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key nazwa użytkownika"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key nazwa użytkownika"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key nazwa użytkownika [polecenia]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd <id-użytkownika>"
+
+#~ msgid "[filename]"
+#~ msgstr "[nazwa pliku]"
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "zaciemnienie klucza nie powiodło się: %s\n"
 
index f0bbe97..733f8bf 100644 (file)
--- a/po/pt.po
+++ b/po/pt.po
@@ -1916,6 +1916,11 @@ msgstr "gerar um novo par de chaves"
 msgid "quickly revoke a user-id"
 msgstr "gerar um novo par de chaves"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "gerar um novo par de chaves"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2033,12 +2038,22 @@ msgstr ""
 "@\n"
 "(Veja a página man para uma lista completa de comandos e opções)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2047,7 +2062,7 @@ msgstr ""
 "Exemplos:\n"
 "\n"
 " -se -r Bob [ficheiro]      assinar e cifrar para o utilizador Bob\n"
-" --clearsign [ficheiro]     criar uma assinatura em texto puro\n"
+" --clear-sign [ficheiro]     criar uma assinatura em texto puro\n"
 " --detach-sign [ficheiro]   criar uma assinatura separada\n"
 " --list-keys [nomes]        mostrar chaves\n"
 " --fingerprint [nomes]      mostrar impressões digitais\n"
@@ -2428,23 +2443,10 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "AVISO: destinatários (-r) dados sem utilizar uma cifra de chave pública\n"
 
-msgid "--store [filename]"
-msgstr "--store [nome_do_ficheiro]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [nome_do_ficheiro]"
-
 #, fuzzy, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "decifragem falhou: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [nome_do_ficheiro]"
-
-#, fuzzy
-msgid "--symmetric --encrypt [filename]"
-msgstr "--sign --encrypt [nome_do_ficheiro]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2452,16 +2454,6 @@ msgstr ""
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "não pode utilizar %s enquanto estiver no modo %s\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [nome_do_ficheiro]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [nome_do_ficheiro]"
-
-#, fuzzy
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--sign --encrypt [nome_do_ficheiro]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2469,28 +2461,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "não pode utilizar %s enquanto estiver no modo %s\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [nome_do_ficheiro]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [nome_do_ficheiro]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [nome_do_ficheiro]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key id-utilizador"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key id-utilizador"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key id-utilizador [comandos]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key id-utilizador"
-
 #, fuzzy, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "A geração de chaves falhou: %s\n"
@@ -2536,9 +2506,6 @@ msgstr "erro na criação da frase secreta: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[nome_do_ficheiro]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Digite a sua mensagem ...\n"
 
@@ -3580,6 +3547,10 @@ msgstr "Identificador de utilizador inexistente.\n"
 msgid "Nothing to sign.\n"
 msgstr "Nada para assinar com a chave %08lX\n"
 
+#, fuzzy, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "%s não é um conjunto de caracteres válido\n"
+
 msgid "Digest: "
 msgstr "'Digest': "
 
@@ -5071,7 +5042,7 @@ msgstr ""
 
 #, fuzzy
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5329,7 +5300,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5717,6 +5688,13 @@ msgstr ""
 "Algoritmos suportados:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "erro na criação da frase secreta: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "erro ao enviar para `%s': %s\n"
 
@@ -5731,6 +5709,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: erro ao escrever registo de diretório: %s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "erro ao enviar para `%s': %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "erro na leitura de `%s': %s\n"
 
@@ -5744,8 +5730,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5793,38 +5781,73 @@ msgid "this key"
 msgstr "listar as chaves"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|algo [ficheiros]|imprimir \"digests\" de mensagens"
+msgstr[1] "|algo [ficheiros]|imprimir \"digests\" de mensagens"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "Repita a frase secreta\n"
+msgstr[1] "Repita a frase secreta\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|algo [ficheiros]|imprimir \"digests\" de mensagens"
+msgstr[1] "|algo [ficheiros]|imprimir \"digests\" de mensagens"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|algo [ficheiros]|imprimir \"digests\" de mensagens"
 msgstr[1] "|algo [ficheiros]|imprimir \"digests\" de mensagens"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|algo [ficheiros]|imprimir \"digests\" de mensagens"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5851,76 +5874,85 @@ msgid "resetting keydb: %s\n"
 msgstr "erro na escrita do porta-chaves `%s': %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "erro na leitura de `%s': %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "erro na criação da frase secreta: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "%d assinaturas removidas.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|algo [ficheiros]|imprimir \"digests\" de mensagens"
 msgstr[1] "|algo [ficheiros]|imprimir \"digests\" de mensagens"
 
-#, fuzzy, c-format
-msgid ", and encrypted %ld messages"
+#, fuzzy
+msgid "Encrypted 0 messages."
 msgstr "Repita a frase secreta\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|algo [ficheiros]|imprimir \"digests\" de mensagens"
 msgstr[1] "|algo [ficheiros]|imprimir \"digests\" de mensagens"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Política: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5956,7 +5988,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "erro ao enviar para `%s': %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8639,6 +8671,14 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "erro na leitura de `%s': %s\n"
+
+#, fuzzy, c-format
+msgid "error parsing '%s'\n"
+msgstr "erro na leitura de `%s': %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8657,6 +8697,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8852,6 +8895,54 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [nome_do_ficheiro]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [nome_do_ficheiro]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [nome_do_ficheiro]"
+
+#, fuzzy
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--sign --encrypt [nome_do_ficheiro]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [nome_do_ficheiro]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [nome_do_ficheiro]"
+
+#, fuzzy
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [nome_do_ficheiro]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [nome_do_ficheiro]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [nome_do_ficheiro]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [nome_do_ficheiro]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key id-utilizador"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key id-utilizador"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key id-utilizador [comandos]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key id-utilizador"
+
+#~ msgid "[filename]"
+#~ msgstr "[nome_do_ficheiro]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "remoção do bloco de chave falhou: %s\n"
index ea68dc3..054978a 100644 (file)
--- a/po/ro.po
+++ b/po/ro.po
@@ -1917,6 +1917,11 @@ msgstr "generează o nouă perechi de chei"
 msgid "quickly revoke a user-id"
 msgstr "generează o nouă perechi de chei"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "generează o nouă perechi de chei"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2030,12 +2035,22 @@ msgstr ""
 "@\n"
 "(Arată pagina man pentru o listă completă a comenzilor şi opţiunilor)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2044,7 +2059,7 @@ msgstr ""
 "Exemple:\n"
 "\n"
 " -se -r Dan [fişier]        semnează şi cifrează pentru utilizatorul Dan\n"
-" --clearsign [fişier]       crează o semnătură text în clar\n"
+" --clear-sign [fişier]       crează o semnătură text în clar\n"
 " --detach-sign [fişier]     crează o semnătură detaşată\n"
 " --list-keys [nume]         arată chei\n"
 " --fingerprint [nume]       arată amprente\n"
@@ -2459,23 +2474,11 @@ msgstr ""
 "AVERTISMENT: destinatari (-r) furnizaţi fără a folosi cifrare cu cheie "
 "publică\n"
 
-msgid "--store [filename]"
-msgstr "--store [nume_fişier]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [nume_fişier]"
-
 #, fuzzy, c-format
 #| msgid "symmetric encryption of `%s' failed: %s\n"
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "cifrarea simetrică a lui `%s' a eşuat: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [nume_fişier]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [nume_fişier]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "nu puteţi folosi --symmetric --encrypt cu --s2k-mode 0\n"
 
@@ -2483,15 +2486,6 @@ msgstr "nu puteţi folosi --symmetric --encrypt cu --s2k-mode 0\n"
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "nu puteţi folosi --symmetric --encrypt câtă vreme în modul %s\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [nume_fişier]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [nume_fişier]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [nume_fişier]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr "nu puteţi folosi --symmetric --sign --encrypt cu --s2k-mode 0\n"
 
@@ -2499,28 +2493,6 @@ msgstr "nu puteţi folosi --symmetric --sign --encrypt cu --s2k-mode 0\n"
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "nu puteţi folosi --symmetric --sign --encrypt câtă vreme în modul %s\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [nume_fişier]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [nume_fişier]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [nume_fişier]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key id-utilizator"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key id-utilizator"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key id-utilizator [comenzi]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key id-utilizator"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "trimitere server de chei eşuată: %s\n"
@@ -2567,9 +2539,6 @@ msgstr "eroare la obţinerea numărului serial: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[nume_fişier]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Daţi-i drumul şi scrieţi mesajul ...\n"
 
@@ -3583,6 +3552,11 @@ msgstr "Nu există acest ID utilizator.\n"
 msgid "Nothing to sign.\n"
 msgstr "Nimic de semnat cu cheia %s\n"
 
+#, fuzzy, c-format
+#| msgid "`%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "`%s' nu este expirare de semnătură validă\n"
+
 msgid "Digest: "
 msgstr "Rezumat: "
 
@@ -5088,7 +5062,7 @@ msgstr ""
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5344,7 +5318,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5746,6 +5720,13 @@ msgstr ""
 "Algoritmuri suportate:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "eroare la crearea frazei-parolă: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "eroare trimitere la `%s': %s\n"
 
@@ -5760,6 +5741,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: eroare scriere înregistrare dir: %s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "eroare trimitere la `%s': %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "eroare în `%s': %s\n"
 
@@ -5773,8 +5762,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5823,38 +5814,74 @@ msgid "this key"
 msgstr "enumeră chei"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|algo [fişiere]|afişează rezumate mesaje"
+msgstr[1] "|algo [fişiere]|afişează rezumate mesaje"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "cifrată cu %lu fraze-parolă\n"
+msgstr[1] "cifrată cu %lu fraze-parolă\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|algo [fişiere]|afişează rezumate mesaje"
+msgstr[1] "|algo [fişiere]|afişează rezumate mesaje"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|algo [fişiere]|afişează rezumate mesaje"
 msgstr[1] "|algo [fişiere]|afişează rezumate mesaje"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|algo [fişiere]|afişează rezumate mesaje"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5881,77 +5908,86 @@ msgid "resetting keydb: %s\n"
 msgstr "eroare la scrierea inelului de chei `%s': %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "eroare la citire `%s': %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "eroare la crearea frazei-parolă: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Am şters %d semnături.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|algo [fişiere]|afişează rezumate mesaje"
 msgstr[1] "|algo [fişiere]|afişează rezumate mesaje"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "cifrată cu %lu fraze-parolă\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|algo [fişiere]|afişează rezumate mesaje"
 msgstr[1] "|algo [fişiere]|afişează rezumate mesaje"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Politica: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5987,7 +6023,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "eroare trimitere la `%s': %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8705,6 +8741,15 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "eroare în `%s': %s\n"
+
+#, fuzzy, c-format
+#| msgid "error in `%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "eroare în `%s': %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8723,6 +8768,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8919,6 +8967,52 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [nume_fişier]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [nume_fişier]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [nume_fişier]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [nume_fişier]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [nume_fişier]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [nume_fişier]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [nume_fişier]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [nume_fişier]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [nume_fişier]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [nume_fişier]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key id-utilizator"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key id-utilizator"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key id-utilizator [comenzi]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key id-utilizator"
+
+#~ msgid "[filename]"
+#~ msgstr "[nume_fişier]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "citirea cheii publice a eşuat: %s\n"
index 53bb489..60f584a 100644 (file)
--- a/po/ru.po
+++ b/po/ru.po
@@ -1787,6 +1787,11 @@ msgstr "быстро добавить новый ID пользователя"
 msgid "quickly revoke a user-id"
 msgstr "быстро отозвать ID пользователя"
 
+#, fuzzy
+#| msgid "quickly generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "быстро создать новую пару ключей"
+
 msgid "full featured key pair generation"
 msgstr "создание полноценной пары ключей"
 
@@ -1890,12 +1895,22 @@ msgstr ""
 "@\n"
 "(Полный список команд и параметров см. на странице man)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -1904,7 +1919,7 @@ msgstr ""
 "Примеры:\n"
 "\n"
 " -se -r Вова [файл]          подписать и зашифровать для получателя Вова\n"
-" --clearsign [файл]         создать текстовую подпись\n"
+" --clear-sign [файл]         создать текстовую подпись\n"
 " --detach-sign [файл]       создать отделенную подпись\n"
 " --list-keys [имена]        показать ключи\n"
 " --fingerprint [имена]      показать отпечатки\n"
@@ -2269,22 +2284,10 @@ msgstr ""
 "ВНИМАНИЕ: получатели (-r) заданы без использования шифрования с открытым "
 "ключом\n"
 
-msgid "--store [filename]"
-msgstr "--store [файл]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [файл]"
-
 #, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "сбой симметричного шифрования '%s': %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [файл]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [файл]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "--symmetric --encrypt нельзя использовать совместно с --s2k-mode 0\n"
 
@@ -2292,15 +2295,6 @@ msgstr "--symmetric --encrypt нельзя использовать совмес
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "в режиме %s нельзя использовать --symmetric --encrypt\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [файл]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [файл]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [файл]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 "нельзя использовать --symmetric --sign --encrypt совместно с --s2k-mode 0\n"
@@ -2309,27 +2303,6 @@ msgstr ""
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "в режиме %s нельзя использовать --symmetric --sign --encrypt\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [файл]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [файл]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [файл]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key <ID пользователя>"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key <ID пользователя>"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key <ID пользователя> [команды]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd <ID пользователя>"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "сбой при отправке на сервер ключей: %s\n"
@@ -2374,9 +2347,6 @@ msgstr "ошибка синтаксического анализа специф
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr "'%s' не является идентификатором, отпечатком или кодом ключа\n"
 
-msgid "[filename]"
-msgstr "[файл]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Пишите сообщение ...\n"
 
@@ -3342,6 +3312,11 @@ msgstr "Нет подходящих ID пользователей."
 msgid "Nothing to sign.\n"
 msgstr "Подписывать нечего.\n"
 
+#, fuzzy, c-format
+#| msgid "'%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "'%s' - не допустимый срок действия подписи\n"
+
 msgid "Digest: "
 msgstr "Хеш: "
 
@@ -4775,8 +4750,13 @@ msgstr "Данный ключ принадлежит нам\n"
 msgid "%s: This key is bad!  It has been marked as untrusted!\n"
 msgstr "%s: Некачественный ключ! Он помечен как недоверенный!\n"
 
+#, fuzzy
+#| msgid ""
+#| "This key has is bad!  It has been marked as untrusted!  If you\n"
+#| "*really* know what you are doing, you may answer the next\n"
+#| "question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5018,12 +4998,12 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 "Пользуйтесь им для отзыва этого ключа в случае раскрытия или потери\n"
 "секретного ключа. Однако, если секретный ключ доступен, лучше создать\n"
 "новый сертификат с указанием причины отзыва. Подробности см. в описании\n"
-"команды gpg \"--gen-revoke\" в руководстве по GnuPG."
+"команды gpg \"--generate-revocation\" в руководстве по GnuPG."
 
 msgid ""
 "To avoid an accidental use of this file, a colon has been inserted\n"
@@ -5412,6 +5392,14 @@ msgstr "ошибка отката назад изменения в базе да
 msgid "unsupported TOFU database version: %s\n"
 msgstr "версия базы данных TOFU (не поддерживается): %s\n"
 
+#, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "ошибка создания временного файла: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
 #, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "ошибка чтения базы данных TOFU: %s\n"
@@ -5424,6 +5412,15 @@ msgstr "ошибка определения версии базы данных T
 msgid "error initializing TOFU database: %s\n"
 msgstr "ошибка инициализации базы данных TOFU: %s\n"
 
+#, fuzzy, c-format
+#| msgid "error reading TOFU database: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "ошибка чтения базы данных TOFU: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
 #, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "ошибка открытия базы данных TOFU '%s': %s\n"
@@ -5437,9 +5434,13 @@ msgid ""
 "This is the first time the email address \"%s\" is being used with key %s."
 msgstr "Адрес электронной почты \"%s\" используется с ключом %s впервые."
 
-#, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr "Адрес электронной почты \"%s\" связан с %d ключами!"
+#, fuzzy, c-format
+#| msgid "The email address \"%s\" is associated with %d keys!"
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] "Адрес электронной почты \"%s\" связан с %d ключами!"
+msgstr[1] "Адрес электронной почты \"%s\" связан с %d ключами!"
+msgstr[2] "Адрес электронной почты \"%s\" связан с %d ключами!"
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5486,43 +5487,107 @@ msgstr ", "
 msgid "this key"
 msgstr "этот ключ"
 
-#, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+#, fuzzy, c-format
+#| msgid "Verified %d message"
+#| msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
 msgstr[0] "Проверено %d сообщение"
 msgstr[1] "Проверены %d сообщения"
 msgstr[2] "Проверено %d сообщений"
 
-#, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+#, fuzzy, c-format
+#| msgid "Encrypted %d message"
+#| msgid_plural "Encrypted %d messages"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
 msgstr[0] "Зашифровано %d сообщение"
 msgstr[1] "Зашифрованы %d сообщения"
 msgstr[2] "Зашифровано %d сообщений"
 
-#, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+#, fuzzy, c-format
+#| msgid "Verified %d message"
+#| msgid_plural "Verified %d messages"
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "Проверено %d сообщение"
+msgstr[1] "Проверены %d сообщения"
+msgstr[2] "Проверено %d сообщений"
+
+#, fuzzy, c-format
+#| msgid ", and encrypted %ld message in the past %s"
+#| msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
+msgstr[0] ", зашифровано %ld сообщение за %s."
+msgstr[1] ", зашифрованы %ld сообщения за %s."
+msgstr[2] ", зашифровано %ld сообщений за %s."
+
+#, fuzzy, c-format
+#| msgid " over the past day."
+#| msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
 msgstr[0] " за %d прошедший день."
 msgstr[1] " за %d прошедших дня."
 msgstr[2] " за %d прошедших дней."
 
-#, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+#, fuzzy, c-format
+#| msgid " over the past day."
+#| msgid_plural " over the past %d days."
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
+msgstr[0] " за %d прошедший день."
+msgstr[1] " за %d прошедших дня."
+msgstr[2] " за %d прошедших дней."
+
+#, fuzzy, c-format
+#| msgid " over the past month."
+#| msgid_plural " over the past %d months."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] " за %d прошедший месяц."
 msgstr[1] " за %d прошедших месяца."
 msgstr[2] " за %d прошедших месяцев."
 
-#, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+#, fuzzy, c-format
+#| msgid " over the past month."
+#| msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
+msgstr[0] " за %d прошедший месяц."
+msgstr[1] " за %d прошедших месяца."
+msgstr[2] " за %d прошедших месяцев."
+
+#, fuzzy, c-format
+#| msgid " over the past year."
+#| msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] " за %d прошедший год."
 msgstr[1] " за %d прошедших дня."
 msgstr[2] " за %d прошедших дней."
 
-msgid " in the past."
-msgstr " в прошлом."
+#, fuzzy, c-format
+#| msgid " over the past year."
+#| msgid_plural " over the past %d years."
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] " за %d прошедший год."
+msgstr[1] " за %d прошедших дня."
+msgstr[2] " за %d прошедших дней."
+
+#, fuzzy, c-format
+#| msgid " over the past day."
+#| msgid_plural " over the past %d days."
+msgid "Messages verified in the past: %d."
+msgstr " за %d прошедший день."
+
+#, fuzzy, c-format
+#| msgid ", and encrypted %ld message in the past %s"
+#| msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Messages encrypted in the past: %d."
+msgstr ", зашифровано %ld сообщение за %s."
 
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
@@ -5556,84 +5621,114 @@ msgstr "Обнаружено повреждение базы данных TOFU.\
 msgid "resetting keydb: %s\n"
 msgstr "сброс базы данных ключей: %s\n"
 
-#, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+#, fuzzy, c-format
+#| msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "ошибка установки уровня доверия привязки TOFU в %s\n"
 
 #, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "ошибка при смене правила TOFU: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
-#, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+#, fuzzy, c-format
+#| msgid "%d~year"
+#| msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] "%d~год"
 msgstr[1] "%d~года"
 msgstr[2] "%d~лет"
 
-#, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+#, fuzzy, c-format
+#| msgid "%d~month"
+#| msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] "%d~месяц"
 msgstr[1] "%d~месяца"
 msgstr[2] "%d~месяцев"
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#, fuzzy, c-format
+#| msgid "%d~day"
+#| msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] "%d~день"
 msgstr[1] "%d~дня"
 msgstr[2] "%d~дней"
 
-#, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+#, fuzzy, c-format
+#| msgid "%d~hour"
+#| msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] "%d~час"
 msgstr[1] "%d~часа"
 msgstr[2] "%d~часов"
 
-#, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+#, fuzzy, c-format
+#| msgid "%d~minute"
+#| msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] "%d~минута"
 msgstr[1] "%d~минуты"
 msgstr[2] "%d~минут"
 
-#, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+#, fuzzy, c-format
+#| msgid "%d~second"
+#| msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] "%d~секунда"
 msgstr[1] "%d~секунды"
 msgstr[2] "%d~секунд"
 
 #, c-format
-msgid "%s: "
-msgstr "%s: "
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
+msgstr ""
 
-#, c-format
-msgid "Verified %ld signatures"
+#, fuzzy, c-format
+#| msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Проверено %ld подписей"
 
-#, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+#, fuzzy, c-format
+#| msgid "Verified %ld signature in the past %s"
+#| msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "Проверена %ld подпись за %s."
 msgstr[1] "Проверены %ld прописи за %s."
 msgstr[2] "Проверено %ld подписей за %s."
 
-#, c-format
-msgid ", and encrypted %ld messages"
-msgstr ", зашифровано %ld сообщений"
+#, fuzzy
+#| msgid "Encrypted %d message"
+#| msgid_plural "Encrypted %d messages"
+msgid "Encrypted 0 messages."
+msgstr "Зашифровано %d сообщение"
 
-#, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+#, fuzzy, c-format
+#| msgid ", and encrypted %ld message in the past %s"
+#| msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] ", зашифровано %ld сообщение за %s."
 msgstr[1] ", зашифрованы %ld сообщения за %s."
 msgstr[2] ", зашифровано %ld сообщений за %s."
 
+#, fuzzy, c-format
+#| msgid "policy: %s"
+msgid "(policy: %s)"
+msgstr "правило: %s"
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5690,8 +5785,9 @@ msgstr[2] ""
 msgid "error opening TOFU database: %s\n"
 msgstr "ошибка при открытии базы данных TOFU: %s\n"
 
-#, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+#, fuzzy, c-format
+#| msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 "ВНИМАНИЕ: Шифрование для ключа %s, у которого нет неотозванных "
 "идентификаторов пользователя.\n"
@@ -8301,6 +8397,16 @@ msgstr "Внешняя проверка компонента %s не прошл
 msgid "Note that group specifications are ignored\n"
 msgstr "Обратите внимание, что спецификации групп игнорируются\n"
 
+#, fuzzy, c-format
+#| msgid "error closing '%s': %s\n"
+msgid "error closing '%s'\n"
+msgstr "ошибка закрытия '%s': %s\n"
+
+#, fuzzy, c-format
+#| msgid "error hashing '%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "ошибка при получении хеша '%s': %s\n"
+
 msgid "list all components"
 msgstr "вывод списка всех компонентов"
 
@@ -8319,6 +8425,11 @@ msgstr "|COMPONENT|проверить параметры"
 msgid "apply global default values"
 msgstr "применить глобальные значения по умолчанию"
 
+#, fuzzy
+#| msgid "|FILE|take policy information from FILE"
+msgid "|FILE|update configuration files using FILE"
+msgstr "|FILE|взять информацию о правилах из файла FILE"
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "получить каталоги настроек для @GPGCONF@"
 
@@ -8510,6 +8621,60 @@ msgstr ""
 "Синтаксис: gpg-check-pattern [параметры] файл_образцов\n"
 "Проверить фразу-пароль, поступающую из stdin, по файлу образцов\n"
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [файл]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [файл]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [файл]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [файл]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [файл]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [файл]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [файл]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [файл]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [файл]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [файл]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key <ID пользователя>"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key <ID пользователя>"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key <ID пользователя> [команды]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd <ID пользователя>"
+
+#~ msgid "[filename]"
+#~ msgstr "[файл]"
+
+#~ msgid " in the past."
+#~ msgstr " в прошлом."
+
+#~ msgid "%s: "
+#~ msgstr "%s: "
+
+#~ msgid ", and encrypted %ld messages"
+#~ msgstr ", зашифровано %ld сообщений"
+
 #~ msgid "GPG Agent"
 #~ msgstr "Агент GPG"
 
index c44b6c3..5a41f8a 100644 (file)
--- a/po/sk.po
+++ b/po/sk.po
@@ -1922,6 +1922,11 @@ msgstr "vytvoriť nový pár kľúčov"
 msgid "quickly revoke a user-id"
 msgstr "vytvoriť nový pár kľúčov"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "vytvoriť nový pár kľúčov"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2040,12 +2045,22 @@ msgstr ""
 "(Použite manuálové stránky pre kompletný zoznam všetkých príkazov a "
 "možností)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2054,7 +2069,7 @@ msgstr ""
 "Príklady:\n"
 "\n"
 " -se -r Bob [súbor]        podpísať a zašifrovať pre užívateľa Bob\n"
-" --clearsign [súbor]       vytvoriť podpis čitateľného dokumentu\n"
+" --clear-sign [súbor]       vytvoriť podpis čitateľného dokumentu\n"
 " --detach-sign [súbor]     vytvoriť podpis oddelený od dokumentu\n"
 " --list-keys [mená]        vypísať kľúče\n"
 " --fingerprint [mená]      vypísať fingerprinty\n"
@@ -2436,23 +2451,10 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "VAROVANIE: daný adresát (-r) bez použitia šifrovania s verejným kľúčom\n"
 
-msgid "--store [filename]"
-msgstr "--store [meno súboru]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [meno súboru]"
-
 #, fuzzy, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "dešifrovanie zlyhalo: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [meno súboru]"
-
-#, fuzzy
-msgid "--symmetric --encrypt [filename]"
-msgstr "--sign --encrypt [meno súboru]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2462,16 +2464,6 @@ msgstr ""
 "použitie %s nie je v móde %s dovolené\n"
 "\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [meno súboru]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [meno súboru]"
-
-#, fuzzy
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--sign --encrypt [meno súboru]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 
@@ -2481,28 +2473,6 @@ msgstr ""
 "použitie %s nie je v móde %s dovolené\n"
 "\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [meno súboru]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [meno súboru]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [meno súboru]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key id užívateľa"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key id užívateľa"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key id užívateľa [príkazy]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key id užívateľa"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "nepodarilo poslať kľúč na server: %s\n"
@@ -2549,9 +2519,6 @@ msgstr "chyba pri vytváraní hesla: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[meno súboru]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Začnite písať svoju správu ...\n"
 
@@ -3591,6 +3558,10 @@ msgstr "Takýto identifikátor užívateľa neexistuje.\n"
 msgid "Nothing to sign.\n"
 msgstr "Nič na podpísanie kľúčom %08lX\n"
 
+#, fuzzy, c-format
+msgid "'%s' is not a valid expiration time\n"
+msgstr "%s nie je platná znaková sada\n"
+
 msgid "Digest: "
 msgstr "Digest: "
 
@@ -5082,7 +5053,7 @@ msgstr ""
 
 #, fuzzy
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5337,7 +5308,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5734,6 +5705,13 @@ msgstr ""
 "Podporované algoritmy:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "chyba pri vytváraní hesla: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "chyba pri posielaní na `%s': %s\n"
 
@@ -5748,6 +5726,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: chyba pri zápise adresárového záznamu: %s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "chyba pri posielaní na `%s': %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "chyba pri čítaní `%s': %s\n"
 
@@ -5761,8 +5747,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5810,38 +5798,74 @@ msgid "this key"
 msgstr "vypísať zoznam kľúčov"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|algo [súbory]|vypíš hash"
+msgstr[1] "|algo [súbory]|vypíš hash"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "zašifrované s %lu heslami\n"
+msgstr[1] "zašifrované s %lu heslami\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|algo [súbory]|vypíš hash"
+msgstr[1] "|algo [súbory]|vypíš hash"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|algo [súbory]|vypíš hash"
 msgstr[1] "|algo [súbory]|vypíš hash"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|algo [súbory]|vypíš hash"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5868,77 +5892,86 @@ msgid "resetting keydb: %s\n"
 msgstr "chyba pri zápise súboru kľúčov (keyring)  `%s': %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "chyba pri čítaní `%s': %s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "chyba pri vytváraní hesla: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Zmazaných %d podpisov.\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|algo [súbory]|vypíš hash"
 msgstr[1] "|algo [súbory]|vypíš hash"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "zašifrované s %lu heslami\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|algo [súbory]|vypíš hash"
 msgstr[1] "|algo [súbory]|vypíš hash"
 
+#, fuzzy, c-format
+#| msgid "Policy: "
+msgid "(policy: %s)"
+msgstr "Politika: "
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5974,7 +6007,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "chyba pri posielaní na `%s': %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8656,6 +8689,14 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "chyba pri čítaní `%s': %s\n"
+
+#, fuzzy, c-format
+msgid "error parsing '%s'\n"
+msgstr "chyba pri čítaní `%s': %s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8674,6 +8715,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8870,6 +8914,54 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [meno súboru]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [meno súboru]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [meno súboru]"
+
+#, fuzzy
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--sign --encrypt [meno súboru]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [meno súboru]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [meno súboru]"
+
+#, fuzzy
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [meno súboru]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [meno súboru]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [meno súboru]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [meno súboru]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key id užívateľa"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key id užívateľa"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key id užívateľa [príkazy]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key id užívateľa"
+
+#~ msgid "[filename]"
+#~ msgstr "[meno súboru]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "zmazanie bloku kľúča sa nepodarilo:  %s\n"
index de350a1..a863d39 100644 (file)
--- a/po/sv.po
+++ b/po/sv.po
@@ -1985,6 +1985,11 @@ msgstr "generera ett nytt nyckelpar"
 msgid "quickly revoke a user-id"
 msgstr "generera ett nytt nyckelpar"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "generera ett nytt nyckelpar"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2094,12 +2099,22 @@ msgstr ""
 "@\n"
 "(Se manualsidan för en fullständig lista över alla kommandon och flaggor)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2108,7 +2123,7 @@ msgstr ""
 "Exempel:\n"
 "\n"
 "-se -r Bosse [fil]          signera och kryptera för användaren Bosse\n"
-"--clearsign [fil]           skapa en klartextsignatur\n"
+"--clear-sign [fil]           skapa en klartextsignatur\n"
 "--detach-sign [fil]         skapa signatur i en separat fil\n"
 "--list-keys [namn]          visa nycklar\n"
 "--fingerprint [namn]        visa fingeravtryck\n"
@@ -2525,23 +2540,11 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "VARNING: mottagare (-r) angivna utan att använda publik nyckel-kryptering\n"
 
-msgid "--store [filename]"
-msgstr "--store [filnamn]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [filnamn]"
-
 #, fuzzy, c-format
 #| msgid "symmetric encryption of `%s' failed: %s\n"
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "symmetrisk kryptering av \"%s\" misslyckades: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [filnamn]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [filnamn]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "du kan inte använda --symmetric --encrypt med --s2k-mode 0\n"
 
@@ -2549,15 +2552,6 @@ msgstr "du kan inte använda --symmetric --encrypt med --s2k-mode 0\n"
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "du kan inte använda --symmetric --encrypt i %s-läget\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [filnamn]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [filnamn]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [filnamn]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr "du kan inte använda --symmetric --sign --encrypt med --s2k-mode 0\n"
 
@@ -2566,27 +2560,6 @@ msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr ""
 "du kan inte använda --symmetric --sign --encrypt när du är i %s-läget\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [filnamn]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [filnamn]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [filnamn]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key användaridentitet"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key användaridentitet"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key användaridentitet [kommandon]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd <användaridentitet>"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "sändning till nyckelservern misslyckades: %s\n"
@@ -2634,9 +2607,6 @@ msgstr "fel vid lagring av certifikat: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[filnamn]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Skriv ditt meddelande här ...\n"
 
@@ -3666,6 +3636,11 @@ msgstr "Ingen sådan användaridentitet.\n"
 msgid "Nothing to sign.\n"
 msgstr "Det finns inget att signera med nyckeln %s\n"
 
+#, fuzzy, c-format
+#| msgid "`%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "\"%s\" är inte ett giltigt utgångsdatum för en signatur\n"
+
 msgid "Digest: "
 msgstr "Sammandrag: "
 
@@ -5194,7 +5169,7 @@ msgstr "rotcertifikatet har nu markerats som betrott\n"
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5463,7 +5438,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5874,6 +5849,14 @@ msgid "unsupported TOFU database version: %s\n"
 msgstr "algoritmen stöds inte: %s"
 
 #, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "fel när temporärfil skapades: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error sending %s command: %s\n"
 msgid "error reading TOFU database: %s\n"
 msgstr "fel vid sändning av %s-kommando: %s\n"
@@ -5889,6 +5872,15 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: fel vid läsning av katalogpost: %s\n"
 
 #, fuzzy, c-format
+#| msgid "error sending %s command: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "fel vid sändning av %s-kommando: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error opening `%s': %s\n"
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "fel vid öppnandet av \"%s\": %s\n"
@@ -5904,8 +5896,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5956,38 +5950,77 @@ msgstr "lista nycklar"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "skriv ut kontrollsummor"
+msgstr[1] "skriv ut kontrollsummor"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "krypterad med %lu lösenfraser\n"
+msgstr[1] "krypterad med %lu lösenfraser\n"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "skriv ut kontrollsummor"
+msgstr[1] "skriv ut kontrollsummor"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "skriv ut kontrollsummor"
 msgstr[1] "skriv ut kontrollsummor"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Messages encrypted in the past: %d."
+msgstr "skriv ut kontrollsummor"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -6016,7 +6049,7 @@ msgstr "fel vid skrivning av nyckel: %s\n"
 
 #, fuzzy, c-format
 #| msgid "error storing flags: %s\n"
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "fel vid lagring av flaggor: %s\n"
 
 #, fuzzy, c-format
@@ -6024,72 +6057,81 @@ msgstr "fel vid lagring av flaggor: %s\n"
 msgid "error changing TOFU policy: %s\n"
 msgstr "fel när ett rör skapades: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Raderade %d signaturer.\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "skriv ut kontrollsummor"
 msgstr[1] "skriv ut kontrollsummor"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "krypterad med %lu lösenfraser\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "skriv ut kontrollsummor"
 msgstr[1] "skriv ut kontrollsummor"
 
+#, fuzzy, c-format
+#| msgid "validity: %s"
+msgid "(policy: %s)"
+msgstr "giltighet: %s"
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -6126,7 +6168,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "fel vid sändning av %s-kommando: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -9037,6 +9079,16 @@ msgstr "Extern validering av komponenten %s misslyckades"
 msgid "Note that group specifications are ignored\n"
 msgstr "Observera att gruppspecifikationer ignoreras\n"
 
+#, fuzzy, c-format
+#| msgid "error closing %s: %s\n"
+msgid "error closing '%s'\n"
+msgstr "fel vid stängning av %s: %s\n"
+
+#, fuzzy, c-format
+#| msgid "error in `%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "fel i \"%s\": %s\n"
+
 msgid "list all components"
 msgstr "lista alla komponenter"
 
@@ -9056,6 +9108,11 @@ msgid "apply global default values"
 msgstr "tillämpa globala standardvärden"
 
 #, fuzzy
+#| msgid "|FILE|take policy information from FILE"
+msgid "|FILE|update configuration files using FILE"
+msgstr "|FIL|hämta policyinformation från FIL"
+
+#, fuzzy
 #| msgid "get the configuration directories for gpgconf"
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "hämta konfigurationskatalogerna för gpgconf"
@@ -9264,6 +9321,51 @@ msgstr ""
 "Syntax: gpg-check-pattern [flaggor] mönsterfil\n"
 "Kontrollera en lösenfras angiven på standard in mot mönsterfilen\n"
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [filnamn]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [filnamn]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [filnamn]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [filnamn]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [filnamn]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [filnamn]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [filnamn]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [filnamn]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [filnamn]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [filnamn]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key användaridentitet"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key användaridentitet"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key användaridentitet [kommandon]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd <användaridentitet>"
+
+#~ msgid "[filename]"
+#~ msgstr "[filnamn]"
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "skuggning av nyckeln misslyckades: %s\n"
 
index 093de7a..9dac294 100644 (file)
--- a/po/tr.po
+++ b/po/tr.po
@@ -1930,6 +1930,11 @@ msgstr "yeni bir anahtar çifti üretir"
 msgid "quickly revoke a user-id"
 msgstr "yeni bir anahtar çifti üretir"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "yeni bir anahtar çifti üretir"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2039,12 +2044,22 @@ msgstr ""
 "@\n"
 "(Tüm komut ve seçeneklerin komple listesi için man sayfalarına bakın)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2053,7 +2068,7 @@ msgstr ""
 "Örnekler:\n"
 "\n"
 " -se -r Ali [dosya]         kullanıcı Ali için imzalar ve şifreler\n"
-" --clearsign [dosya]        açıkça okunabilir bir imza yapar\n"
+" --clear-sign [dosya]        açıkça okunabilir bir imza yapar\n"
 " --detach-sign [dosya]      bağımsız bir imza yapar\n"
 " --list-keys [isimler]      anahtarları listeler\n"
 " --fingerprint [isimler]    parmak izlerini gösterir\n"
@@ -2457,23 +2472,11 @@ msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr ""
 "UYARI: alıcılar (-r) genel anahtar şifrelemesi kullanılmadan belirtilmiş\n"
 
-msgid "--store [filename]"
-msgstr "--store [dosyaismi]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [dosyaismi]"
-
 #, fuzzy, c-format
 #| msgid "symmetric encryption of `%s' failed: %s\n"
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "`%s' için simetrik şifreleme başarısız: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [dosyaismi]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [dosyaismi]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "--s2k-mode 0 ile  --symmetric --encrypt kullanamazsınız\n"
 
@@ -2481,15 +2484,6 @@ msgstr "--s2k-mode 0 ile  --symmetric --encrypt kullanamazsınız\n"
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "%s kipindeyken  --symmetric --encrypt kullanamazsınız\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [dosyaismi]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [dosyaismi]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [dosyaismi]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr "--s2k-mode 0 ile --symmetric --sign --encrypt kullanamazsınız\n"
 
@@ -2497,28 +2491,6 @@ msgstr "--s2k-mode 0 ile --symmetric --sign --encrypt kullanamazsınız\n"
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "%s kipindeyken --symmetric --sign --encrypt kullanamazsınız.\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [DOSYA]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [dosyaismi]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [dosyaismi]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key kullanıcı-kimliği"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key kullanıcı-kimliği"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key kullanıcı-kimliği [komutlar]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key kullanıcı-kimliği"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "anahtar sunucusuna gönderim başarısızlığa uğradı: %s\n"
@@ -2566,9 +2538,6 @@ msgstr "serifika saklanırken hata: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[dosyaismi]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "İletinizi yazın ...\n"
 
@@ -3613,6 +3582,11 @@ msgstr "Böyle bir kullanıcı kimliği yok.\n"
 msgid "Nothing to sign.\n"
 msgstr "%s anahtarı ile imzalanacak hiçbir şey yok\n"
 
+#, fuzzy, c-format
+#| msgid "`%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "'%s' geçerli bir imza zamanaşımı değil\n"
+
 msgid "Digest: "
 msgstr "Özet: "
 
@@ -5138,7 +5112,7 @@ msgstr "kök sertifika artık güvenilir olarak imlenmiş oldu\n"
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5398,7 +5372,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5802,6 +5776,14 @@ msgid "unsupported TOFU database version: %s\n"
 msgstr "desteklenmeyen algoritma: %s"
 
 #, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "geçici dosya oluşturulurken hata: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error sending %s command: %s\n"
 msgid "error reading TOFU database: %s\n"
 msgstr "%s komutu gönderilirken hata: %s\n"
@@ -5817,6 +5799,15 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s: dizin kaydını yazma hatası: %s\n"
 
 #, fuzzy, c-format
+#| msgid "error sending %s command: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "%s komutu gönderilirken hata: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error opening `%s': %s\n"
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "'%s' açılırken hata: %s\n"
@@ -5832,8 +5823,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5884,38 +5877,77 @@ msgstr "anahtarları listeler"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "ileti özetlerini gösterir"
+msgstr[1] "ileti özetlerini gösterir"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "%lu anahtar parolası ile şifrelenmiş\n"
+msgstr[1] "%lu anahtar parolası ile şifrelenmiş\n"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "ileti özetlerini gösterir"
+msgstr[1] "ileti özetlerini gösterir"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "ileti özetlerini gösterir"
 msgstr[1] "ileti özetlerini gösterir"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Messages encrypted in the past: %d."
+msgstr "ileti özetlerini gösterir"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5944,7 +5976,7 @@ msgstr "anahtarı yazarken hata: %s\n"
 
 #, fuzzy, c-format
 #| msgid "error storing flags: %s\n"
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "bayraklar saklanırken hata: %s\n"
 
 #, fuzzy, c-format
@@ -5952,72 +5984,81 @@ msgstr "bayraklar saklanırken hata: %s\n"
 msgid "error changing TOFU policy: %s\n"
 msgstr "boru oluşturulurken hata: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "%d imza silindi.\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "ileti özetlerini gösterir"
 msgstr[1] "ileti özetlerini gösterir"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "%lu anahtar parolası ile şifrelenmiş\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "ileti özetlerini gösterir"
 msgstr[1] "ileti özetlerini gösterir"
 
+#, fuzzy, c-format
+#| msgid "validity: %s"
+msgid "(policy: %s)"
+msgstr "geçerliliği: %s"
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -6054,7 +6095,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "%s komutu gönderilirken hata: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8944,6 +8985,16 @@ msgstr "%s bileşeninin harici doğrulaması başarısız oldu"
 msgid "Note that group specifications are ignored\n"
 msgstr "Grup belirtimlerinin yoksayıldığına dikkat edin\n"
 
+#, fuzzy, c-format
+#| msgid "error closing %s: %s\n"
+msgid "error closing '%s'\n"
+msgstr "%s kapanırken hata: %s\n"
+
+#, fuzzy, c-format
+#| msgid "error in `%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "'%s' de hata: %s\n"
+
 msgid "list all components"
 msgstr "tüm bileşenleri listeler"
 
@@ -8963,6 +9014,11 @@ msgid "apply global default values"
 msgstr "öntanımlı küresel değerleri uygular"
 
 #, fuzzy
+#| msgid "|FILE|take policy information from FILE"
+msgid "|FILE|update configuration files using FILE"
+msgstr "|DOSYA|poliçe bilgisi DOSYAdan alınır"
+
+#, fuzzy
 #| msgid "get the configuration directories for gpgconf"
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "gpgconf için yapılandırma dizinlerini getirir"
@@ -9171,6 +9227,52 @@ msgstr ""
 "Standart girdiden verilen anahtar parolasını örüntü dosyasıyla "
 "karşılaştırır\n"
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [dosyaismi]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [dosyaismi]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [dosyaismi]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [dosyaismi]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [dosyaismi]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [dosyaismi]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [dosyaismi]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [DOSYA]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [dosyaismi]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [dosyaismi]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key kullanıcı-kimliği"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key kullanıcı-kimliği"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key kullanıcı-kimliği [komutlar]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key kullanıcı-kimliği"
+
+#~ msgid "[filename]"
+#~ msgstr "[dosyaismi]"
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "anahtar gölgelenemedi: %s\n"
 
index 65529e2..d05706d 100644 (file)
--- a/po/uk.po
+++ b/po/uk.po
@@ -1803,6 +1803,11 @@ msgstr "швидке додавання нового ідентифікатор
 msgid "quickly revoke a user-id"
 msgstr "швидке відкликання ідентифікатора користувача"
 
+#, fuzzy
+#| msgid "quickly generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "швидке створення пари ключів"
+
 msgid "full featured key pair generation"
 msgstr "повноцінне створення пари ключів"
 
@@ -1909,12 +1914,22 @@ msgstr ""
 "(Щоб ознайомитися зі списком команд і параметрів, скористайтеся сторінкою "
 "довідника (man))\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -1924,7 +1939,7 @@ msgstr ""
 "\n"
 " -se -r Bob [файл]          підписати і зашифрувати дані для користувача "
 "Bob\n"
-" --clearsign [файл]         створити текстовий підпис\n"
+" --clear-sign [файл]         створити текстовий підпис\n"
 " --detach-sign [файл]       створити від’єднаний підпис\n"
 " --list-keys [назви]        показати ключі\n"
 " --fingerprint [назви]      показати відбитки\n"
@@ -2307,22 +2322,10 @@ msgstr ""
 "УВАГА: отримувачів (-r) вказано без використання шифрування відкритим "
 "ключем\n"
 
-msgid "--store [filename]"
-msgstr "--store [назва файла]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [назва файла]"
-
 #, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "помилка під час спроби симетричного шифрування «%s»: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [назва файла]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [назва файла]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr ""
 "не можна використовувати комбінацію --symmetric --encrypt у режимі --s2k-"
@@ -2333,15 +2336,6 @@ msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr ""
 "не можна використовувати комбінацію --symmetric --encrypt у режимі %s\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [назва файла]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [назва файла]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [назва файла]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr ""
 "не можна використовувати комбінацію --symmetric --sign --encrypt у режимі --"
@@ -2353,27 +2347,6 @@ msgstr ""
 "не можна використовувати комбінацію --symmetric --sign --encrypt у режимі "
 "%s\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [назва файла]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [назва файла]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [назва файла]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key user-id"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key user-id"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key user-id [команди]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd <ідентифікатор-користувача>"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "помилка під час надсилання даних на сервер ключів: %s\n"
@@ -2418,9 +2391,6 @@ msgstr "помилка під час спроби обробки специфі
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr "«%s» не є коректним ідентифікатором ключа, відбитком або кодом\n"
 
-msgid "[filename]"
-msgstr "[назва файла]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "Почніть вводити ваше повідомлення...\n"
 
@@ -3403,6 +3373,11 @@ msgstr "Немає відповідних ідентифікаторів кор
 msgid "Nothing to sign.\n"
 msgstr "Нічого підписувати.\n"
 
+#, fuzzy, c-format
+#| msgid "'%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "«%s» не є коректним записом завершення строку дії підпису\n"
+
 msgid "Digest: "
 msgstr "Контрольна сума: "
 
@@ -4869,7 +4844,7 @@ msgstr "кореневий сертифікат було позначено як
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5117,14 +5092,14 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 "Скористайтеся цим для відкликання цього ключа у випадку його\n"
 "компрометації або втрати закритої частини. Втім, якщо доступ до\n"
 "закритого ключа не втрачено, краще створити новий сертифікат\n"
 "відкликання і вказати причину відкликання. Докладніший опис\n"
 "можна знайти у розділах підручника з GnuPG щодо команди\n"
-"gpg «--gen-revoke»."
+"gpg «--generate-revocation»."
 
 msgid ""
 "To avoid an accidental use of this file, a colon has been inserted\n"
@@ -5522,6 +5497,14 @@ msgstr "помилка під час скасовування змін у баз
 msgid "unsupported TOFU database version: %s\n"
 msgstr "непідтримувана версія бази даних TOFU: %s\n"
 
+#, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "помилка створення тимчасового файла: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
 #, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "помилка під час спроби читання бази даних TOFU: %s\n"
@@ -5534,6 +5517,15 @@ msgstr "помилка під час спроби визначення верс
 msgid "error initializing TOFU database: %s\n"
 msgstr "помилка під час спроби ініціалізації бази даних TOFU: %s\n"
 
+#, fuzzy, c-format
+#| msgid "error reading TOFU database: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "помилка під час спроби читання бази даних TOFU: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
 #, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "помилка під час спроби відкрити бази даних TOFU «%s»: %s\n"
@@ -5552,8 +5544,11 @@ msgstr "Адресу електронної пошти «%s» пов’язан
 #, fuzzy, c-format
 #| msgid "The email address \"%s\" is associated with %d key:\n"
 #| msgid_plural "The email address \"%s\" is associated with %d keys:\n"
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr "Адресу електронної пошти «%s» пов’язано із %d ключем:\n"
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] "Адресу електронної пошти «%s» пов’язано із %d ключем:\n"
+msgstr[1] "Адресу електронної пошти «%s» пов’язано із %d ключем:\n"
+msgstr[2] "Адресу електронної пошти «%s» пов’язано із %d ключем:\n"
 
 #, fuzzy
 #| msgid ""
@@ -5607,24 +5602,52 @@ msgstr "цей ключ"
 
 #, fuzzy, c-format
 #| msgid "Verified %ld messages signed by \"%s\"."
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
 msgstr[0] "Перевірено %ld повідомлень, підписаних «%s»."
 msgstr[1] "Перевірено %ld повідомлень, підписаних «%s»."
 msgstr[2] "Перевірено %ld повідомлень, підписаних «%s»."
 
-#, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "зашифровано за допомогою %lu паролів\n"
+msgstr[1] "зашифровано за допомогою %lu паролів\n"
+msgstr[2] "зашифровано за допомогою %lu паролів\n"
+
+#, fuzzy, c-format
+#| msgid "%ld message signed in the future."
+#| msgid_plural "%ld messages signed in the future."
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "%ld повідомлення підписано у майбутньому."
+msgstr[1] "%ld повідомлення підписано у майбутньому."
+msgstr[2] "%ld повідомлень підписано у майбутньому."
+
+#, fuzzy, c-format
+#| msgid "%ld message signed in the future."
+#| msgid_plural "%ld messages signed in the future."
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
+msgstr[0] "%ld повідомлення підписано у майбутньому."
+msgstr[1] "%ld повідомлення підписано у майбутньому."
+msgstr[2] "%ld повідомлень підписано у майбутньому."
+
+#, fuzzy, c-format
+#| msgid " over the past %ld day."
+#| msgid_plural " over the past %ld days."
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] " протягом %ld попереднього дня."
+msgstr[1] " протягом %ld попередніх днів."
+msgstr[2] " протягом %ld попередніх днів."
 
 #, fuzzy, c-format
 #| msgid " over the past %ld day."
 #| msgid_plural " over the past %ld days."
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] " протягом %ld попереднього дня."
 msgstr[1] " протягом %ld попередніх днів."
 msgstr[2] " протягом %ld попередніх днів."
@@ -5632,8 +5655,17 @@ msgstr[2] " протягом %ld попередніх днів."
 #, fuzzy, c-format
 #| msgid " over the past %ld month."
 #| msgid_plural " over the past %ld months."
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
+msgstr[0] " протягом %ld попереднього місяця."
+msgstr[1] " протягом %ld попередніх місяців."
+msgstr[2] " протягом %ld попередніх місяців."
+
+#, fuzzy, c-format
+#| msgid " over the past %ld month."
+#| msgid_plural " over the past %ld months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] " протягом %ld попереднього місяця."
 msgstr[1] " протягом %ld попередніх місяців."
 msgstr[2] " протягом %ld попередніх місяців."
@@ -5641,18 +5673,39 @@ msgstr[2] " протягом %ld попередніх місяців."
 #, fuzzy, c-format
 #| msgid " over the past %ld day."
 #| msgid_plural " over the past %ld days."
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] " протягом %ld попереднього дня."
 msgstr[1] " протягом %ld попередніх днів."
 msgstr[2] " протягом %ld попередніх днів."
 
-#, fuzzy
+#, fuzzy, c-format
 #| msgid " over the past %ld day."
 #| msgid_plural " over the past %ld days."
-msgid " in the past."
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] " протягом %ld попереднього дня."
+msgstr[1] " протягом %ld попередніх днів."
+msgstr[2] " протягом %ld попередніх днів."
+
+#, fuzzy, c-format
+#| msgid " over the past %ld day."
+#| msgid_plural " over the past %ld days."
+msgid "Messages verified in the past: %d."
 msgstr " протягом %ld попереднього дня."
 
+#, fuzzy, c-format
+#| msgid ""
+#| "Verified %ld message signed by \"%s\"\n"
+#| "in the past %s."
+#| msgid_plural ""
+#| "Verified %ld messages signed by \"%s\"\n"
+#| "in the past %s."
+msgid "Messages encrypted in the past: %d."
+msgstr ""
+"Перевірено %ld повідомлення, підписане «%s»,\n"
+"протягом такого строку: %s."
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5687,8 +5740,9 @@ msgstr ""
 msgid "resetting keydb: %s\n"
 msgstr "помилка під час спроби запису ключа: %s\n"
 
-#, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+#, fuzzy, c-format
+#| msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr ""
 "помилка під час спроби встановлення рівня довіри до прив’язки TOFU до %s\n"
 
@@ -5696,57 +5750,74 @@ msgstr ""
 msgid "error changing TOFU policy: %s\n"
 msgstr "помилка під час спроби змінити правила TOFU: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
-#, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+#, fuzzy, c-format
+#| msgid "%d~year"
+#| msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
 msgstr[0] "%d~рік"
 msgstr[1] "%d~роки"
 msgstr[2] "%d~років"
 
-#, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+#, fuzzy, c-format
+#| msgid "%d~month"
+#| msgid_plural "%d~months"
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] "%d~місяць"
 msgstr[1] "%d~місяці"
 msgstr[2] "%d~місяців"
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#, fuzzy, c-format
+#| msgid "%d~day"
+#| msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] "%d~день"
 msgstr[1] "%d~дні"
 msgstr[2] "%d~днів"
 
-#, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+#, fuzzy, c-format
+#| msgid "%d~hour"
+#| msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] "%d~година"
 msgstr[1] "%d~години"
 msgstr[2] "%d~годин"
 
-#, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+#, fuzzy, c-format
+#| msgid "%d~minute"
+#| msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] "%d~хвилина"
 msgstr[1] "%d~хвилини"
 msgstr[2] "%d~хвилин"
 
-#, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+#, fuzzy, c-format
+#| msgid "%d~second"
+#| msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] "%d~секунда"
 msgstr[1] "%d~секунди"
 msgstr[2] "%d~секунд"
 
 #, c-format
-msgid "%s: "
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "Вилучено %d підписів.\n"
 
 #, fuzzy, c-format
@@ -5756,8 +5827,8 @@ msgstr "Вилучено %d підписів.\n"
 #| msgid_plural ""
 #| "Verified %ld messages signed by \"%s\"\n"
 #| "in the past %s."
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] ""
 "Перевірено %ld повідомлення, підписане «%s»,\n"
 "протягом такого строку: %s."
@@ -5768,9 +5839,9 @@ msgstr[2] ""
 "Перевірено %ld повідомлень, підписаних «%s»,\n"
 "протягом такого строку: %s."
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "зашифровано за допомогою %lu паролів\n"
 
 #, fuzzy, c-format
@@ -5780,8 +5851,8 @@ msgstr "зашифровано за допомогою %lu паролів\n"
 #| msgid_plural ""
 #| "Verified %ld messages signed by \"%s\"\n"
 #| "in the past %s."
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] ""
 "Перевірено %ld повідомлення, підписане «%s»,\n"
 "протягом такого строку: %s."
@@ -5792,6 +5863,11 @@ msgstr[2] ""
 "Перевірено %ld повідомлень, підписаних «%s»,\n"
 "протягом такого строку: %s."
 
+#, fuzzy, c-format
+#| msgid "policy: %s"
+msgid "(policy: %s)"
+msgstr "правило: %s"
+
 #, fuzzy
 #| msgid "Warning: we've have yet to see a message signed by this key!\n"
 msgid ""
@@ -5866,7 +5942,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "помилка під час спроби відкрити бази даних TOFU: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8481,6 +8557,16 @@ msgstr "Помилка зовнішньої перевірки компонен
 msgid "Note that group specifications are ignored\n"
 msgstr "Зауважте, що специфікації груп буде проігноровано\n"
 
+#, fuzzy, c-format
+#| msgid "error closing '%s': %s\n"
+msgid "error closing '%s'\n"
+msgstr "помилка під час спроби закрити «%s»: %s\n"
+
+#, fuzzy, c-format
+#| msgid "error hashing '%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "помилка під час спроби хешування «%s»: %s\n"
+
 msgid "list all components"
 msgstr "показати список всіх компонентів"
 
@@ -8499,6 +8585,11 @@ msgstr "|COMPONENT|перевірити параметри"
 msgid "apply global default values"
 msgstr "застосувати загальні типові значення"
 
+#, fuzzy
+#| msgid "|FILE|take policy information from FILE"
+msgid "|FILE|update configuration files using FILE"
+msgstr "|FILE|взяти дані щодо правил з вказаного файла"
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "отримати назви каталогів налаштувань для @GPGCONF@"
 
@@ -8692,6 +8783,57 @@ msgstr ""
 "Синтаксис: gpg-check-pattern [параметри] файл_шаблонів\n"
 "Перевірити пароль, вказаний у stdin, за допомогою файла_шаблонів\n"
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [назва файла]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [назва файла]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [назва файла]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [назва файла]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [назва файла]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [назва файла]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [назва файла]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [назва файла]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [назва файла]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [назва файла]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key user-id"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key user-id"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key user-id [команди]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd <ідентифікатор-користувача>"
+
+#~ msgid "[filename]"
+#~ msgstr "[назва файла]"
+
+#, fuzzy
+#~| msgid " over the past %ld day."
+#~| msgid_plural " over the past %ld days."
+#~ msgid " in the past."
+#~ msgstr " протягом %ld попереднього дня."
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "помилка під час спроби хешування ключа: %s\n"
 
@@ -8711,12 +8853,6 @@ msgstr ""
 #~ msgid "Known user IDs associated with this key:\n"
 #~ msgstr "Відомі ідентифікатори користувачів, пов’язані із цим ключем:\n"
 
-#~ msgid "%ld message signed in the future."
-#~ msgid_plural "%ld messages signed in the future."
-#~ msgstr[0] "%ld повідомлення підписано у майбутньому."
-#~ msgstr[1] "%ld повідомлення підписано у майбутньому."
-#~ msgstr[2] "%ld повідомлень підписано у майбутньому."
-
 #~ msgid "%ld message signed"
 #~ msgid_plural "%ld messages signed"
 #~ msgstr[0] "підписано %ld повідомлення"
index 00025a9..1417627 100644 (file)
@@ -1887,6 +1887,11 @@ msgstr "生成一副新的密钥对"
 msgid "quickly revoke a user-id"
 msgstr "生成一副新的密钥对"
 
+#, fuzzy
+#| msgid "generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "生成一副新的密钥对"
+
 msgid "full featured key pair generation"
 msgstr ""
 
@@ -2000,12 +2005,22 @@ msgstr ""
 "@\n"
 "(请参考在线说明以获得所有命令和选项的完整清单)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -2014,7 +2029,7 @@ msgstr ""
 "范例:\n"
 "\n"
 " -se -r Bob [文件名]          为 Bob 这个收件人签名及加密\n"
-" --clearsign [文件名]         做出明文签名\n"
+" --clear-sign [文件名]         做出明文签名\n"
 " --detach-sign [文件名]       做出分离式签名\n"
 " --list-keys [某甲]           显示密钥\n"
 " --fingerprint [某甲]         显示指纹\n"
@@ -2404,23 +2419,11 @@ msgstr "初始化信任度数据库失败:%s\n"
 msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr "警告:给定了收件人(-r)但并未使用公钥加密\n"
 
-msgid "--store [filename]"
-msgstr "--store [文件名]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [文件名]"
-
 #, fuzzy, c-format
 #| msgid "symmetric encryption of `%s' failed: %s\n"
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "对称加密‘%s’失败:%s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [文件名]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [文件名]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "使用 --symmetric --encrypt 时不能使用 --s2k-mode 0\n"
 
@@ -2428,15 +2431,6 @@ msgstr "使用 --symmetric --encrypt 时不能使用 --s2k-mode 0\n"
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "您不能在 %s 模式下使用 --symmetric -encrypt\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [文件名]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [文件名]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [文件名]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr "使用 --symmetric --sign --encrypt 时不能使用 --s2k-mode 0\n"
 
@@ -2444,28 +2438,6 @@ msgstr "使用 --symmetric --sign --encrypt 时不能使用 --s2k-mode 0\n"
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "您不能在 %s 模式下使用 --symmetric --sign -encrypt\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [文件名]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [文件名]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [文件名]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key 用户标识"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key 用户标识"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key 用户标识 [指令]"
-
-#, fuzzy
-msgid "--passwd <user-id>"
-msgstr "--sign-key 用户标识"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "上传至公钥服务器失败:%s\n"
@@ -2512,9 +2484,6 @@ msgstr "取得当前密钥信息时出错:%s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[文件名]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "请开始键入您的报文……\n"
 
@@ -3500,6 +3469,11 @@ msgstr "没有这个用户标识。\n"
 msgid "Nothing to sign.\n"
 msgstr "没有东西可以让密钥 %s 签名\n"
 
+#, fuzzy, c-format
+#| msgid "`%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "‘%s’不是一个有效的签名过期日期\n"
+
 msgid "Digest: "
 msgstr "散列:"
 
@@ -4965,7 +4939,7 @@ msgstr ""
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5212,7 +5186,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 
 msgid ""
@@ -5592,6 +5566,13 @@ msgstr ""
 "支持的算法:\n"
 
 #, fuzzy, c-format
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "生成密码的时候发生错误:%s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error reading TOFU database: %s\n"
 msgstr "读取‘%s’时出错:%s\n"
 
@@ -5606,6 +5587,14 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "%s:写入目录记录时出错:%s\n"
 
 #, fuzzy, c-format
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "读取‘%s’时出错:%s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "‘%s’中出错:%s\n"
 
@@ -5619,8 +5608,10 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
+msgstr[1] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5669,38 +5660,74 @@ msgid "this key"
 msgstr "列出密钥"
 
 #, fuzzy, c-format
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
+msgstr[0] "|算法 [文件]|使用指定的散列算法打印报文散列值"
+msgstr[1] "|算法 [文件]|使用指定的散列算法打印报文散列值"
+
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "以 %lu 个密码加密\n"
+msgstr[1] "以 %lu 个密码加密\n"
+
+#, fuzzy, c-format
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "|算法 [文件]|使用指定的散列算法打印报文散列值"
+msgstr[1] "|算法 [文件]|使用指定的散列算法打印报文散列值"
+
+#, fuzzy, c-format
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
 msgstr[0] "|算法 [文件]|使用指定的散列算法打印报文散列值"
 msgstr[1] "|算法 [文件]|使用指定的散列算法打印报文散列值"
 
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 msgstr[1] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+msgid "Messages encrypted in the past: %d."
+msgstr "|算法 [文件]|使用指定的散列算法打印报文散列值"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5727,77 +5754,86 @@ msgid "resetting keydb: %s\n"
 msgstr "写入钥匙环‘%s’时出错: %s\n"
 
 #, fuzzy, c-format
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "读取‘%s’时出错:%s\n"
 
 #, fuzzy, c-format
 msgid "error changing TOFU policy: %s\n"
 msgstr "生成密码的时候发生错误:%s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] ""
 msgstr[1] ""
 
 #, c-format
-msgid "%s: "
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "已经删除了 %d 个签名。\n"
 
 #, fuzzy, c-format
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "|算法 [文件]|使用指定的散列算法打印报文散列值"
 msgstr[1] "|算法 [文件]|使用指定的散列算法打印报文散列值"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "以 %lu 个密码加密\n"
 
 #, fuzzy, c-format
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "|算法 [文件]|使用指定的散列算法打印报文散列值"
 msgstr[1] "|算法 [文件]|使用指定的散列算法打印报文散列值"
 
+#, fuzzy, c-format
+#| msgid "validity: %s"
+msgid "(policy: %s)"
+msgstr "有效性:%s"
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5833,7 +5869,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "读取‘%s’时出错:%s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8529,6 +8565,15 @@ msgstr ""
 msgid "Note that group specifications are ignored\n"
 msgstr ""
 
+#, fuzzy, c-format
+msgid "error closing '%s'\n"
+msgstr "‘%s’中出错:%s\n"
+
+#, fuzzy, c-format
+#| msgid "error in `%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "‘%s’中出错:%s\n"
+
 msgid "list all components"
 msgstr ""
 
@@ -8547,6 +8592,9 @@ msgstr ""
 msgid "apply global default values"
 msgstr ""
 
+msgid "|FILE|update configuration files using FILE"
+msgstr ""
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr ""
 
@@ -8743,6 +8791,52 @@ msgid ""
 "Check a passphrase given on stdin against the patternfile\n"
 msgstr ""
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [文件名]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [文件名]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [文件名]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [文件名]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [文件名]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [文件名]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [文件名]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [文件名]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [文件名]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [文件名]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key 用户标识"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key 用户标识"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key 用户标识 [指令]"
+
+#, fuzzy
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--sign-key 用户标识"
+
+#~ msgid "[filename]"
+#~ msgstr "[文件名]"
+
 #, fuzzy
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "无法读出公钥:%s\n"
index e4a9afb..0151cec 100644 (file)
@@ -1791,6 +1791,11 @@ msgstr "快速產生新的金鑰對"
 msgid "quickly revoke a user-id"
 msgstr "快速產生新的金鑰對"
 
+#, fuzzy
+#| msgid "quickly generate a new key pair"
+msgid "quickly set a new expiration date"
+msgstr "快速產生新的金鑰對"
+
 msgid "full featured key pair generation"
 msgstr "全能金鑰對生成"
 
@@ -1894,12 +1899,22 @@ msgstr ""
 "@\n"
 "(請參照線上說明頁面來取得所有命令和選項的完整清單)\n"
 
+#, fuzzy
+#| msgid ""
+#| "@\n"
+#| "Examples:\n"
+#| "\n"
+#| " -se -r Bob [file]          sign and encrypt for user Bob\n"
+#| " --clear-sign [file]         make a clear text signature\n"
+#| " --detach-sign [file]       make a detached signature\n"
+#| " --list-keys [names]        show keys\n"
+#| " --fingerprint [names]      show fingerprints\n"
 msgid ""
 "@\n"
 "Examples:\n"
 "\n"
 " -se -r Bob [file]          sign and encrypt for user Bob\n"
-" --clearsign [file]         make a clear text signature\n"
+" --clear-sign [file]        make a clear text signature\n"
 " --detach-sign [file]       make a detached signature\n"
 " --list-keys [names]        show keys\n"
 " --fingerprint [names]      show fingerprints\n"
@@ -1908,7 +1923,7 @@ msgstr ""
 "範例:\n"
 "\n"
 " -se -r Bob [檔案]          對 Bob 這個使用者簽署及加密\n"
-" --clearsign [檔案]         做出明文簽章\n"
+" --clear-sign [檔案]         做出明文簽章\n"
 " --detach-sign [檔案]       做出分離式簽章\n"
 " --list-keys [名字]         顯示金鑰\n"
 " --fingerprint [名字]       顯示指紋\n"
@@ -2265,22 +2280,10 @@ msgstr "信任資料庫啟始失敗: %s\n"
 msgid "WARNING: recipients (-r) given without using public key encryption\n"
 msgstr "警告: 給定的收件者 (-r) 未使用公鑰加密\n"
 
-msgid "--store [filename]"
-msgstr "--store [檔名]"
-
-msgid "--symmetric [filename]"
-msgstr "--symmetric [檔名]"
-
 #, c-format
 msgid "symmetric encryption of '%s' failed: %s\n"
 msgstr "'%s' 對稱式加密失敗: %s\n"
 
-msgid "--encrypt [filename]"
-msgstr "--encrypt [檔名]"
-
-msgid "--symmetric --encrypt [filename]"
-msgstr "--symmetric --encrypt [檔名]"
-
 msgid "you cannot use --symmetric --encrypt with --s2k-mode 0\n"
 msgstr "你不能在 --s2k-mode 0 中使用 --symmetric --encrypt\n"
 
@@ -2288,15 +2291,6 @@ msgstr "你不能在 --s2k-mode 0 中使用 --symmetric --encrypt\n"
 msgid "you cannot use --symmetric --encrypt while in %s mode\n"
 msgstr "你不能在 %s 模式中使用 --symmetric --encrypt\n"
 
-msgid "--sign [filename]"
-msgstr "--sign [檔名]"
-
-msgid "--sign --encrypt [filename]"
-msgstr "--sign --encrypt [檔名]"
-
-msgid "--symmetric --sign --encrypt [filename]"
-msgstr "--symmetric --sign --encrypt [檔名]"
-
 msgid "you cannot use --symmetric --sign --encrypt with --s2k-mode 0\n"
 msgstr "你不能在 --s2k-mode 0 中使用 --symmetric --sign --encrypt\n"
 
@@ -2304,27 +2298,6 @@ msgstr "你不能在 --s2k-mode 0 中使用 --symmetric --sign --encrypt\n"
 msgid "you cannot use --symmetric --sign --encrypt while in %s mode\n"
 msgstr "你不能在 %s 模式中使用 --symmetric --sign --encrypt\n"
 
-msgid "--sign --symmetric [filename]"
-msgstr "--sign --symmetric [檔名]"
-
-msgid "--clearsign [filename]"
-msgstr "--clearsign [檔名]"
-
-msgid "--decrypt [filename]"
-msgstr "--decrypt [檔名]"
-
-msgid "--sign-key user-id"
-msgstr "--sign-key 使用者ID"
-
-msgid "--lsign-key user-id"
-msgstr "--lsign-key 使用者ID"
-
-msgid "--edit-key user-id [commands]"
-msgstr "--edit-key 使用者ID [指令]"
-
-msgid "--passwd <user-id>"
-msgstr "--passwd 使用者ID"
-
 #, c-format
 msgid "keyserver send failed: %s\n"
 msgstr "送至金鑰伺服器失敗: %s\n"
@@ -2371,9 +2344,6 @@ msgstr "載入憑證 '%s' 時出錯: %s\n"
 msgid "'%s' does not appear to be a valid key ID, fingerprint or keygrip\n"
 msgstr ""
 
-msgid "[filename]"
-msgstr "[檔名]"
-
 msgid "Go ahead and type your message ...\n"
 msgstr "請開始輸入你的訊息 ...\n"
 
@@ -3335,6 +3305,11 @@ msgstr "沒有相符的使用者 ID."
 msgid "Nothing to sign.\n"
 msgstr "沒有東西可以簽署.\n"
 
+#, fuzzy, c-format
+#| msgid "'%s' is not a valid signature expiration\n"
+msgid "'%s' is not a valid expiration time\n"
+msgstr "'%s' 不是有效的簽章使用期限\n"
+
 msgid "Digest: "
 msgstr "摘要: "
 
@@ -4752,7 +4727,7 @@ msgstr "根憑證現在已標記為已信任\n"
 #| "in the user ID.  If you *really* know what you are doing,\n"
 #| "you may answer the next question with yes.\n"
 msgid ""
-"This key has is bad!  It has been marked as untrusted!  If you\n"
+"This key is bad!  It has been marked as untrusted!  If you\n"
 "*really* know what you are doing, you may answer the next\n"
 "question with yes.\n"
 msgstr ""
@@ -5001,7 +4976,7 @@ msgid ""
 "the secret key.  However, if the secret key is still accessible,\n"
 "it is better to generate a new revocation certificate and give\n"
 "a reason for the revocation.  For details see the description of\n"
-"of the gpg command \"--gen-revoke\" in the GnuPG manual."
+"of the gpg command \"--generate-revocation\" in the GnuPG manual."
 msgstr ""
 "萬一私鑰外流或遺失時, 可以用來撤銷金鑰.\n"
 "然而, 如果其實還可以存取私鑰, 建議更好的\n"
@@ -5381,6 +5356,14 @@ msgid "unsupported TOFU database version: %s\n"
 msgstr "未支援的演算法: %s"
 
 #, fuzzy, c-format
+#| msgid "error creating temporary file: %s\n"
+msgid "error creating 'ultimately_trusted_keys' TOFU table: %s\n"
+msgstr "建立暫存檔時出錯: %s\n"
+
+msgid "TOFU DB error"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error sending data: %s\n"
 msgid "error reading TOFU database: %s\n"
 msgstr "送出資料時出錯: %s\n"
@@ -5396,6 +5379,15 @@ msgid "error initializing TOFU database: %s\n"
 msgstr "啟始讀取程式物件時出錯: %s\n"
 
 #, fuzzy, c-format
+#| msgid "error sending data: %s\n"
+msgid "error creating 'encryptions' TOFU table: %s\n"
+msgstr "送出資料時出錯: %s\n"
+
+#, c-format
+msgid "adding column effective_policy to bindings DB: %s\n"
+msgstr ""
+
+#, fuzzy, c-format
 #| msgid "error opening '%s': %s\n"
 msgid "error opening TOFU database '%s': %s\n"
 msgstr "開啟 '%s' 時出錯: %s\n"
@@ -5411,8 +5403,9 @@ msgid ""
 msgstr ""
 
 #, c-format
-msgid "The email address \"%s\" is associated with %d keys!"
-msgstr ""
+msgid "The email address \"%s\" is associated with %d key!"
+msgid_plural "The email address \"%s\" is associated with %d keys!"
+msgstr[0] ""
 
 msgid "  Since this binding's policy was 'auto', it has been changed to 'ask'."
 msgstr ""
@@ -5462,33 +5455,67 @@ msgstr "列出金鑰"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %d message"
-msgid_plural "Verified %d messages"
+msgid "Verified %d message."
+msgid_plural "Verified %d messages."
 msgstr[0] "印出訊息摘要"
 
+#, fuzzy, c-format
+#| msgid "encrypted with %lu passphrases\n"
+msgid "Encrypted %d message."
+msgid_plural "Encrypted %d messages."
+msgstr[0] "已用 %lu 個密語加密了\n"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Verified %d message in the future."
+msgid_plural "Verified %d messages in the future."
+msgstr[0] "印出訊息摘要"
+
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Encrypted %d message in the future."
+msgid_plural "Encrypted %d messages in the future."
+msgstr[0] "印出訊息摘要"
+
+#, c-format
+msgid "Messages verified over the past %d day: %d."
+msgid_plural "Messages verified over the past %d days: %d."
+msgstr[0] ""
+
+#, c-format
+msgid "Messages encrypted over the past %d day: %d."
+msgid_plural "Messages encrypted over the past %d days: %d."
+msgstr[0] ""
+
 #, c-format
-msgid "Encrypted %d message"
-msgid_plural "Encrypted %d messages"
+msgid "Messages verified over the past %d month: %d."
+msgid_plural "Messages verified over the past %d months: %d."
 msgstr[0] ""
 
 #, c-format
-msgid " over the past day."
-msgid_plural " over the past %d days."
+msgid "Messages encrypted over the past %d month: %d."
+msgid_plural "Messages encrypted over the past %d months: %d."
 msgstr[0] ""
 
 #, c-format
-msgid " over the past month."
-msgid_plural " over the past %d months."
+msgid "Messages verified over the past %d year: %d."
+msgid_plural "Messages verified over the past %d years: %d."
 msgstr[0] ""
 
 #, c-format
-msgid " over the past year."
-msgid_plural " over the past %d years."
+msgid "Messages encrypted over the past %d year: %d."
+msgid_plural "Messages encrypted over the past %d years: %d."
 msgstr[0] ""
 
-msgid " in the past."
+#, c-format
+msgid "Messages verified in the past: %d."
 msgstr ""
 
+#, fuzzy, c-format
+#| msgid "print message digests"
+msgid "Messages encrypted in the past: %d."
+msgstr "印出訊息摘要"
+
 #. TRANSLATORS: Please translate the text found in the source
 #. * file below.  We don't directly internationalize that text so
 #. * that we can tweak it without breaking translations.
@@ -5517,7 +5544,7 @@ msgstr "寫入金鑰時出錯: %s\n"
 
 #, fuzzy, c-format
 #| msgid "error setting OCSP target: %s\n"
-msgid "error setting TOFU binding's trust level to %s\n"
+msgid "error setting TOFU binding's policy to %s\n"
 msgstr "設定 OCSP 目標時出錯: %s\n"
 
 #, fuzzy, c-format
@@ -5525,64 +5552,72 @@ msgstr "設定 OCSP 目標時出錯: %s\n"
 msgid "error changing TOFU policy: %s\n"
 msgstr "建立管道時出錯: %s\n"
 
-#. TRANSLATORS: The tilde ('~') is used here to indicate a
-#. * non-breakable space
 #, c-format
-msgid "%d~year"
-msgid_plural "%d~years"
+msgid "%lld~year"
+msgid_plural "%lld~years"
+msgstr[0] ""
+
+#, c-format
+msgid "%lld~month"
+msgid_plural "%lld~months"
 msgstr[0] ""
 
 #, c-format
-msgid "%d~month"
-msgid_plural "%d~months"
+msgid "%lld~week"
+msgid_plural "%lld~weeks"
 msgstr[0] ""
 
 #, c-format
-msgid "%d~day"
-msgid_plural "%d~days"
+msgid "%lld~day"
+msgid_plural "%lld~days"
 msgstr[0] ""
 
 #, c-format
-msgid "%d~hour"
-msgid_plural "%d~hours"
+msgid "%lld~hour"
+msgid_plural "%lld~hours"
 msgstr[0] ""
 
 #, c-format
-msgid "%d~minute"
-msgid_plural "%d~minutes"
+msgid "%lld~minute"
+msgid_plural "%lld~minutes"
 msgstr[0] ""
 
 #, c-format
-msgid "%d~second"
-msgid_plural "%d~seconds"
+msgid "%lld~second"
+msgid_plural "%lld~seconds"
 msgstr[0] ""
 
 #, c-format
-msgid "%s: "
+msgid "%s: Verified 0~signatures and encrypted 0~messages."
 msgstr ""
 
 #, fuzzy, c-format
 #| msgid "Deleted %d signatures.\n"
-msgid "Verified %ld signatures"
+msgid "%s: Verified 0 signatures."
 msgstr "已經刪除了 %d 份簽章.\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid "Verified %ld signature in the past %s"
-msgid_plural "Verified %ld signatures in the past %s"
+msgid "%s: Verified %ld~signature in the past %s."
+msgid_plural "%s: Verified %ld~signatures in the past %s."
 msgstr[0] "印出訊息摘要"
 
-#, fuzzy, c-format
+#, fuzzy
 #| msgid "encrypted with %lu passphrases\n"
-msgid ", and encrypted %ld messages"
+msgid "Encrypted 0 messages."
 msgstr "已用 %lu 個密語加密了\n"
 
 #, fuzzy, c-format
 #| msgid "print message digests"
-msgid ", and encrypted %ld message in the past %s"
-msgid_plural ", and encrypted %ld messages in the past %s"
+msgid "Encrypted %ld~message in the past %s."
+msgid_plural "Encrypted %ld~messages in the past %s."
 msgstr[0] "印出訊息摘要"
 
+#, fuzzy, c-format
+#| msgid "validity: %s"
+msgid "(policy: %s)"
+msgstr "有效性: %s"
+
 msgid ""
 "Warning: we have yet to see a message signed using this key and user id!\n"
 msgstr ""
@@ -5618,7 +5653,7 @@ msgid "error opening TOFU database: %s\n"
 msgstr "送出資料時出錯: %s\n"
 
 #, c-format
-msgid "WARNING: Encrypting to %s, which has nonon-revoked user ids.\n"
+msgid "WARNING: Encrypting to %s, which has no non-revoked user ids.\n"
 msgstr ""
 
 #, fuzzy, c-format
@@ -8167,6 +8202,16 @@ msgstr "元件 %s 的外部驗證失敗"
 msgid "Note that group specifications are ignored\n"
 msgstr "請注意群組規格已忽略\n"
 
+#, fuzzy, c-format
+#| msgid "error closing '%s': %s\n"
+msgid "error closing '%s'\n"
+msgstr "關閉 '%s' 時出錯: %s\n"
+
+#, fuzzy, c-format
+#| msgid "error hashing '%s': %s\n"
+msgid "error parsing '%s'\n"
+msgstr "計算 '%s' 的雜湊時出錯: %s\n"
+
 msgid "list all components"
 msgstr "列出所有的元件"
 
@@ -8185,6 +8230,11 @@ msgstr "|元件|檢查選項"
 msgid "apply global default values"
 msgstr "套用全域預設值"
 
+#, fuzzy
+#| msgid "|FILE|take policy information from FILE"
+msgid "|FILE|update configuration files using FILE"
+msgstr "|檔案|從指定檔案中取得原則資訊"
+
 msgid "get the configuration directories for @GPGCONF@"
 msgstr "取得 @GPGCONF@ 的組態目錄"
 
@@ -8377,6 +8427,51 @@ msgstr ""
 "語法: gpg-check-pattern [選項] 樣式檔案\n"
 "用樣式檔案來檢查由標準輸入給定的密語\n"
 
+#~ msgid "--store [filename]"
+#~ msgstr "--store [檔名]"
+
+#~ msgid "--symmetric [filename]"
+#~ msgstr "--symmetric [檔名]"
+
+#~ msgid "--encrypt [filename]"
+#~ msgstr "--encrypt [檔名]"
+
+#~ msgid "--symmetric --encrypt [filename]"
+#~ msgstr "--symmetric --encrypt [檔名]"
+
+#~ msgid "--sign [filename]"
+#~ msgstr "--sign [檔名]"
+
+#~ msgid "--sign --encrypt [filename]"
+#~ msgstr "--sign --encrypt [檔名]"
+
+#~ msgid "--symmetric --sign --encrypt [filename]"
+#~ msgstr "--symmetric --sign --encrypt [檔名]"
+
+#~ msgid "--sign --symmetric [filename]"
+#~ msgstr "--sign --symmetric [檔名]"
+
+#~ msgid "--clear-sign [filename]"
+#~ msgstr "--clear-sign [檔名]"
+
+#~ msgid "--decrypt [filename]"
+#~ msgstr "--decrypt [檔名]"
+
+#~ msgid "--sign-key user-id"
+#~ msgstr "--sign-key 使用者ID"
+
+#~ msgid "--lsign-key user-id"
+#~ msgstr "--lsign-key 使用者ID"
+
+#~ msgid "--edit-key user-id [commands]"
+#~ msgstr "--edit-key 使用者ID [指令]"
+
+#~ msgid "--passwd <user-id>"
+#~ msgstr "--passwd 使用者ID"
+
+#~ msgid "[filename]"
+#~ msgstr "[檔名]"
+
 #~ msgid "shadowing the key failed: %s\n"
 #~ msgstr "遮蔽金鑰時失敗: %s\n"
 
index 54f3b30..b32fe80 100644 (file)
@@ -3757,8 +3757,9 @@ send_le (int slot, int class, int ins, int p0, int p1,
 
   if (use_extended_length && (le > 256 || le < 0))
     {
-      result_buffer_size = le < 0? 4096 : le;
-      result_buffer = xtrymalloc (result_buffer_size + 10);
+      /* Two more bytes are needed for status bytes.  */
+      result_buffer_size = le < 0? 4096 : (le + 2);
+      result_buffer = xtrymalloc (result_buffer_size);
       if (!result_buffer)
         {
           xfree (apdu_buffer);
index d1c9efe..5fa4fd2 100644 (file)
@@ -197,8 +197,6 @@ struct app_local_s {
     unsigned int sm_algo:2;            /* Symmetric crypto algo for SM.  */
     unsigned int max_certlen_3:16;
     unsigned int max_get_challenge:16; /* Maximum size for get_challenge.  */
-    unsigned int max_cmd_data:16;      /* Maximum data size for a command.  */
-    unsigned int max_rsp_data:16;      /* Maximum size of a response.  */
   } extcap;
 
   /* Flags used to control the application.  */
@@ -325,7 +323,7 @@ get_cached_data (app_t app, int tag,
     }
 
   if (try_extlen && app->app_local->cardcap.ext_lc_le)
-    exmode = app->app_local->extcap.max_rsp_data;
+    exmode = app->app_local->extcap.max_certlen_3;
   else
     exmode = 0;
 
@@ -455,10 +453,7 @@ get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes,
 
   if (app->card_version > 0x0100 && data_objects[i].get_immediate_in_v11)
     {
-      if (data_objects[i].try_extlen && app->app_local->cardcap.ext_lc_le)
-        exmode = app->app_local->extcap.max_rsp_data;
-      else
-        exmode = 0;
+      exmode = 0;
       rc = iso7816_get_data (app->slot, exmode, tag, &buffer, &buflen);
       if (rc)
         {
@@ -922,6 +917,22 @@ send_key_attr (ctrl_t ctrl, app_t app, const char *keyword, int keyno)
 }
 
 
+#define RSA_SMALL_SIZE_KEY 1952
+#define RSA_SMALL_SIZE_OP  2048
+
+static int
+determine_rsa_response (app_t app, int keyno)
+{
+  int size;
+
+  size = 2 + 3 /* header */
+    + 4 /* tag+len */ + app->app_local->keyattr[keyno].rsa.n_bits/8
+    + 2 /* tag+len */ + app->app_local->keyattr[keyno].rsa.e_bits/8;
+
+  return size;
+}
+
+
 /* Implement the GETATTR command.  This is similar to the LEARN
    command but returns just one value via the status interface. */
 static gpg_error_t
@@ -1531,10 +1542,12 @@ get_public_key (app_t app, int keyno)
       int exmode, le_value;
 
       /* We may simply read the public key out of these cards.  */
-      if (app->app_local->cardcap.ext_lc_le)
+      if (app->app_local->cardcap.ext_lc_le
+          && app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA
+          && app->app_local->keyattr[keyno].rsa.n_bits > RSA_SMALL_SIZE_KEY)
         {
           exmode = 1;    /* Use extended length.  */
-          le_value = app->app_local->extcap.max_rsp_data;
+          le_value = determine_rsa_response (app, keyno);
         }
       else
         {
@@ -3441,7 +3454,7 @@ ecc_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **),
 
           memcpy (curve_name, tok, toklen);
           curve_name[toklen] = 0;
-          curve = openpgp_is_curve_supported (curve_name, NULL);
+          curve = openpgp_is_curve_supported (curve_name, NULL, NULL);
           xfree (curve_name);
         }
       else if (tok && toklen == 5 && !memcmp (tok, "flags", 5))
@@ -3769,12 +3782,11 @@ do_genkey (app_t app, ctrl_t ctrl,  const char *keynostr, unsigned int flags,
       if (keybits > 4096)
         return gpg_error (GPG_ERR_TOO_LARGE);
 
-      /* Test whether we will need extended length mode.  (1900 is an
-         arbitrary length which for sure fits into a short apdu.)  */
-      if (app->app_local->cardcap.ext_lc_le && keybits > 1900)
+      if (app->app_local->cardcap.ext_lc_le && keybits > RSA_SMALL_SIZE_KEY
+          && app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
         {
           exmode = 1;    /* Use extended length w/o a limit.  */
-          le_value = app->app_local->extcap.max_rsp_data;
+          le_value = determine_rsa_response (app, keyno);
           /* No need to check le_value because it comes from a 16 bit
              value and thus can't create an overflow on a 32 bit
              system.  */
@@ -4122,10 +4134,12 @@ do_sign (app_t app, const char *keyidstr, int hashalgo,
     }
 
 
-  if (app->app_local->cardcap.ext_lc_le)
+  if (app->app_local->cardcap.ext_lc_le
+      && app->app_local->keyattr[0].key_type == KEY_TYPE_RSA
+      && app->app_local->keyattr[0].rsa.n_bits > RSA_SMALL_SIZE_OP)
     {
       exmode = 1;    /* Use extended length.  */
-      le_value = app->app_local->extcap.max_rsp_data;
+      le_value = app->app_local->keyattr[0].rsa.n_bits / 8;
     }
   else
     {
@@ -4225,10 +4239,12 @@ do_auth (app_t app, const char *keyidstr,
     {
       int exmode, le_value;
 
-      if (app->app_local->cardcap.ext_lc_le)
+      if (app->app_local->cardcap.ext_lc_le
+          && app->app_local->keyattr[2].key_type == KEY_TYPE_RSA
+          && app->app_local->keyattr[2].rsa.n_bits > RSA_SMALL_SIZE_OP)
         {
           exmode = 1;    /* Use extended length.  */
-          le_value = app->app_local->extcap.max_rsp_data;
+          le_value = app->app_local->keyattr[2].rsa.n_bits / 8;
         }
       else
         {
@@ -4417,10 +4433,13 @@ do_decipher (app_t app, const char *keyidstr,
   else
     return gpg_error (GPG_ERR_INV_VALUE);
 
-  if (app->app_local->cardcap.ext_lc_le && indatalen > 254 )
+  if (app->app_local->cardcap.ext_lc_le
+      && (indatalen > 254
+          || (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA
+              && app->app_local->keyattr[1].rsa.n_bits > RSA_SMALL_SIZE_OP)))
     {
       exmode = 1;    /* Extended length w/o a limit.  */
-      le_value = app->app_local->extcap.max_rsp_data;
+      le_value = app->app_local->keyattr[1].rsa.n_bits / 8;
     }
   else if (app->app_local->cardcap.cmd_chaining && indatalen > 254)
     {
@@ -4578,8 +4597,6 @@ show_caps (struct app_local_s *s)
     log_printf (" (%s)", s->extcap.sm_algo==2? "3DES":
                 (s->extcap.sm_algo==2? "AES-128" : "AES-256"));
   log_info ("Max-Cert3-Len ..: %u\n", s->extcap.max_certlen_3);
-  log_info ("Max-Cmd-Data ...: %u\n", s->extcap.max_cmd_data);
-  log_info ("Max-Rsp-Data ...: %u\n", s->extcap.max_rsp_data);
   log_info ("Cmd-Chaining ...: %s\n", s->cardcap.cmd_chaining?"yes":"no");
   log_info ("Ext-Lc-Le ......: %s\n", s->cardcap.ext_lc_le?"yes":"no");
   log_info ("Status Indicator: %02X\n", s->status_indicator);
@@ -4883,8 +4900,6 @@ app_select_openpgp (app_t app)
           app->app_local->extcap.max_get_challenge
                                                = (buffer[2] << 8 | buffer[3]);
           app->app_local->extcap.max_certlen_3 = (buffer[4] << 8 | buffer[5]);
-          app->app_local->extcap.max_cmd_data  = (buffer[6] << 8 | buffer[7]);
-          app->app_local->extcap.max_rsp_data  = (buffer[8] << 8 | buffer[9]);
         }
       xfree (relptr);
 
index 064cae9..38e3c40 100644 (file)
@@ -165,7 +165,6 @@ static ARGPARSE_OPTS opts[] = {
 /* The list of supported debug flags.  */
 static struct debug_flags_s debug_flags [] =
   {
-    { DBG_COMMAND_VALUE, "command"  },
     { DBG_MPI_VALUE    , "mpi"     },
     { DBG_CRYPTO_VALUE , "crypto"  },
     { DBG_MEMORY_VALUE , "memory"  },
@@ -328,10 +327,9 @@ set_debug (const char *level)
   else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
     opt.debug = DBG_IPC_VALUE;
   else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
-    opt.debug = DBG_IPC_VALUE|DBG_COMMAND_VALUE;
+    opt.debug = DBG_IPC_VALUE;
   else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
-    opt.debug = (DBG_IPC_VALUE|DBG_COMMAND_VALUE
-                 |DBG_CACHE_VALUE|DBG_CARD_IO_VALUE);
+    opt.debug = (DBG_IPC_VALUE|DBG_CACHE_VALUE|DBG_CARD_IO_VALUE);
   else if (!strcmp (level, "guru") || numok)
     {
       opt.debug = ~0;
index 31e9c79..38aa490 100644 (file)
@@ -65,7 +65,6 @@ struct
 } opt;
 
 
-#define DBG_COMMAND_VALUE 1    /* debug commands i/o */
 #define DBG_MPI_VALUE    2     /* debug mpi details */
 #define DBG_CRYPTO_VALUE  4    /* debug low level crypto */
 #define DBG_MEMORY_VALUE  32   /* debug memory allocation stuff */
@@ -76,7 +75,6 @@ struct
 #define DBG_CARD_IO_VALUE 2048
 #define DBG_READER_VALUE  4096  /* Trace reader related functions.  */
 
-#define DBG_COMMAND (opt.debug & DBG_COMMAND_VALUE)
 #define DBG_CRYPTO  (opt.debug & DBG_CRYPTO_VALUE)
 #define DBG_MEMORY  (opt.debug & DBG_MEMORY_VALUE)
 #define DBG_CACHE   (opt.debug & DBG_CACHE_VALUE)
index 11e6ae7..16a2497 100644 (file)
@@ -234,6 +234,7 @@ gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
   rc = start_agent (ctrl);
   if (rc)
     return rc;
+  inq_parm.ctrl = ctrl;
   inq_parm.ctx = agent_ctx;
 
   if (digestlen*2 + 50 > DIM(line))
@@ -319,6 +320,7 @@ gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc,
   rc = start_agent (ctrl);
   if (rc)
     return rc;
+  inq_parm.ctrl = ctrl;
   inq_parm.ctx = agent_ctx;
 
   if (digestlen*2 + 50 > DIM(line))
@@ -583,6 +585,7 @@ gpgsm_agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
   rc = start_agent (ctrl);
   if (rc)
     return rc;
+  inq_parm.ctrl = ctrl;
   inq_parm.ctx = agent_ctx;
 
   rc = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL);
index 11c1cf8..a2907f6 100644 (file)
@@ -74,10 +74,12 @@ prepare_decryption (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
     log_printhex ("pkcs1 encoded session key:", seskey, seskeylen);
 
   n=0;
-  if (seskeylen == 24)
+  if (seskeylen == 24 || seskeylen == 16)
     {
-      /* Smells like a 3-des key.  This might happen because a SC has
-         already done the unpacking. */
+      /* Smells like a 3-DES or AES-128 key.  This might happen
+       * because a SC has already done the unpacking.  A better
+       * solution would be to test for this only after we triggered
+       * the GPG_ERR_INV_SESSION_KEY. */
     }
   else
     {
index c4fd1c2..34a9b96 100644 (file)
@@ -210,7 +210,8 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")),
   ARGPARSE_c (aListChain,   "list-chain",  N_("list certificate chain")),
   ARGPARSE_c (aFingerprint, "fingerprint", N_("list keys and fingerprints")),
-  ARGPARSE_c (aKeygen, "gen-key", N_("generate a new key pair")),
+  ARGPARSE_c (aKeygen, "generate-key", N_("generate a new key pair")),
+  ARGPARSE_c (aKeygen, "gen-key", "@"),
   ARGPARSE_c (aDeleteKey, "delete-keys",
               N_("remove keys from the public keyring")),
 /*ARGPARSE_c (aSendKeys, "send-keys", N_("export keys to a keyserver")),*/
@@ -230,7 +231,8 @@ static ARGPARSE_OPTS opts[] = {
               N_("pass a command to the dirmngr")),
   ARGPARSE_c (aCallProtectTool, "call-protect-tool",
               N_("invoke gpg-protect-tool")),
-  ARGPARSE_c (aPasswd, "passwd", N_("change a passphrase")),
+  ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")),
+  ARGPARSE_c (aPasswd, "passwd", "@"),
   ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
   ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
 
@@ -355,12 +357,6 @@ static ARGPARSE_OPTS opts[] = {
   "@\n(See the man page for a complete listing of all commands and options)\n"
   )),
 
-  ARGPARSE_group (303, N_("@\nExamples:\n\n"
-    " -se -r Bob [file]          sign and encrypt for user Bob\n"
-    " --clearsign [file]         make a clear text signature\n"
-    " --detach-sign [file]       make a detached signature\n"
-    " --list-keys [names]        show keys\n"
-    " --fingerprint [names]      show fingerprints\n"  )),
 
   /* Hidden options. */
   ARGPARSE_s_s (oCompliance, "compliance",   "@"),
@@ -404,9 +400,9 @@ static ARGPARSE_OPTS opts[] = {
 
   /* Command aliases.  */
   ARGPARSE_c (aListKeys, "list-key", "@"),
-  ARGPARSE_c (aListChain, "list-sig", "@"),
+  ARGPARSE_c (aListChain, "list-signatures", "@"),
   ARGPARSE_c (aListChain, "list-sigs", "@"),
-  ARGPARSE_c (aListChain, "check-sig", "@"),
+  ARGPARSE_c (aListChain, "check-signatures", "@"),
   ARGPARSE_c (aListChain, "check-sigs", "@"),
   ARGPARSE_c (aDeleteKey, "delete-key", "@"),
 
@@ -439,9 +435,6 @@ static int maybe_setuid = 1;
 static const char *debug_level;
 static unsigned int debug_value;
 
-/* Option --enable-special-filenames */
-static int allow_special_filenames;
-
 /* Default value for include-certs.  We need an extra macro for
    gpgconf-list because the variable will be changed by the command
    line option.
@@ -468,7 +461,6 @@ static void set_cmd (enum cmd_and_opt_values *ret_cmd,
                      enum cmd_and_opt_values new_cmd );
 
 static void emergency_cleanup (void);
-static int check_special_filename (const char *fname, int for_write);
 static int open_read (const char *filename);
 static estream_t open_es_fread (const char *filename, const char *mode);
 static estream_t open_es_fwrite (const char *filename);
@@ -1420,7 +1412,9 @@ main ( int argc, char **argv)
         case oNoRandomSeedFile: use_random_seed = 0; break;
         case oNoCommonCertsImport: no_common_certs_import = 1; break;
 
-        case oEnableSpecialFilenames: allow_special_filenames =1; break;
+        case oEnableSpecialFilenames:
+          enable_special_filenames ();
+          break;
 
         case oValidationModel: parse_validation_model (pargs.r.ret_str); break;
 
@@ -1719,6 +1713,7 @@ main ( int argc, char **argv)
        es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
        es_printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE);
         es_printf ("disable-crl-checks:%lu:\n", GC_OPT_FLAG_NONE);
+        es_printf ("enable-crl-checks:%lu:\n", GC_OPT_FLAG_NONE);
         es_printf ("disable-trusted-cert-crl-check:%lu:\n", GC_OPT_FLAG_NONE);
         es_printf ("enable-ocsp:%lu:\n", GC_OPT_FLAG_NONE);
         es_printf ("include-certs:%lu:%d:\n", GC_OPT_FLAG_DEFAULT,
@@ -1906,7 +1901,7 @@ main ( int argc, char **argv)
             else if (argc == 1) /* From file. */
               fpin = open_es_fread (*argv, "r");
             else
-              wrong_args ("--gen-key --batch [parmfile]");
+              wrong_args ("--generate-key --batch [parmfile]");
           }
 
         fpout = open_es_fwrite (opt.outfile?opt.outfile:"-");
@@ -1996,7 +1991,7 @@ main ( int argc, char **argv)
 
     case aPasswd:
       if (argc != 1)
-        wrong_args ("--passwd <key-Id>");
+        wrong_args ("--change-passphrase <key-Id>");
       else
         {
           int rc;
@@ -2107,25 +2102,6 @@ gpgsm_parse_validation_model (const char *model)
 }
 
 
-/* Check whether the filename has the form "-&nnnn", where n is a
-   non-zero number.  Returns this number or -1 if it is not the case.  */
-static int
-check_special_filename (const char *fname, int for_write)
-{
-  if (allow_special_filenames
-      && fname && *fname == '-' && fname[1] == '&' ) {
-    int i;
-
-    fname += 2;
-    for (i=0; isdigit (fname[i]); i++ )
-      ;
-    if ( !fname[i] )
-      return translate_sys2libc_fd_int (atoi (fname), for_write);
-  }
-  return -1;
-}
-
-
 
 /* Open the FILENAME for read and return the file descriptor.  Stop
    with an error message in case of problems.  "-" denotes stdin and
@@ -2140,7 +2116,7 @@ open_read (const char *filename)
       set_binary (stdin);
       return 0; /* stdin */
     }
-  fd = check_special_filename (filename, 0);
+  fd = check_special_filename (filename, 0, 0);
   if (fd != -1)
     return fd;
   fd = open (filename, O_RDONLY | O_BINARY);
@@ -2162,7 +2138,7 @@ open_es_fread (const char *filename, const char *mode)
   if (filename[0] == '-' && !filename[1])
     fd = fileno (stdin);
   else
-    fd = check_special_filename (filename, 0);
+    fd = check_special_filename (filename, 0, 0);
   if (fd != -1)
     {
       fp = es_fdopen_nc (fd, mode);
@@ -2200,7 +2176,7 @@ open_es_fwrite (const char *filename)
       return fp;
     }
 
-  fd = check_special_filename (filename, 1);
+  fd = check_special_filename (filename, 1, 0);
   if (fd != -1)
     {
       fp = es_fdopen_nc (fd, "wb");
index 2fbdc7f..c022e2a 100644 (file)
@@ -18,7 +18,7 @@
 
 ## Process this file with automake to produce Makefile.in
 
-SUBDIRS = gpgscm openpgp migrations pkits .
+SUBDIRS = gpgscm openpgp migrations gpgme pkits .
 
 GPGSM = ../sm/gpgsm
 
diff --git a/tests/gpgme/Makefile.am b/tests/gpgme/Makefile.am
new file mode 100644 (file)
index 0000000..d7fd87c
--- /dev/null
@@ -0,0 +1,60 @@
+# Makefile.am - For tests/gpgme
+# Copyright (C) 2016 g10 Code GmbH
+#
+# 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 <https://www.gnu.org/licenses/>.
+# Process this file with automake to create Makefile.in
+
+
+# Programs required before we can run these tests.
+required_pgms = ../../g10/gpg$(EXEEXT) ../../agent/gpg-agent$(EXEEXT) \
+                ../../tools/gpg-connect-agent$(EXEEXT) \
+               ../gpgscm/gpgscm$(EXEEXT)
+
+AM_CPPFLAGS = -I$(top_srcdir)/common
+include $(top_srcdir)/am/cmacros.am
+
+AM_CFLAGS =
+
+TMP ?= /tmp
+
+TESTS_ENVIRONMENT = LC_ALL=C \
+       EXEEXT=$(EXEEXT) \
+       PATH=../gpgscm:$(PATH) \
+       TMP=$(TMP) \
+       srcdir=$(abs_srcdir) \
+       objdir=$(abs_top_builddir) \
+       GPGSCM_PATH=$(abs_top_srcdir)/tests/gpgscm:$(abs_top_srcdir)/tests/openpgp:$(abs_top_srcdir)/tests/gpgme
+
+# XXX: Currently, one cannot override automake's 'check' target.  As a
+# workaround, we avoid defining 'TESTS', thus automake will not emit
+# the 'check' target.  For extra robustness, we merely define a
+# dependency on 'xcheck', so this hack should also work even if
+# automake would emit the 'check' target, as adding dependencies to
+# targets is okay.
+check: xcheck
+
+.PHONY: xcheck
+xcheck:
+       $(TESTS_ENVIRONMENT) $(abs_top_builddir)/tests/gpgscm/gpgscm \
+         $(abs_srcdir)/run-tests.scm $(TESTFLAGS) $(XTESTS)
+
+EXTRA_DIST = gpgme-defs.scm run-tests.scm setup.scm wrap.scm
+
+CLEANFILES = *.log
+
+# We need to depend on a couple of programs so that the tests don't
+# start before all programs are built.
+all-local: $(required_pgms)
diff --git a/tests/gpgme/gpgme-defs.scm b/tests/gpgme/gpgme-defs.scm
new file mode 100644 (file)
index 0000000..2490666
--- /dev/null
@@ -0,0 +1,167 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "defs.scm"))
+
+(define gpgme-srcdir (getenv "XTEST_GPGME_SRCDIR"))
+(when (string=? "" gpgme-srcdir)
+    (info
+     "SKIP: Environment variable 'XTEST_GPGME_SRCDIR' not set.  Please"
+     "point it to a recent GPGME source tree to run the GPGME test suite.")
+    (exit 0))
+
+(define (in-gpgme-srcdir . names)
+  (canonical-path (apply path-join (cons gpgme-srcdir names))))
+
+(define gpgme-builddir (getenv "XTEST_GPGME_BUILDDIR"))
+(when (string=? "" gpgme-builddir)
+    (info
+     "SKIP: Environment variable 'XTEST_GPGME_BUILDDIR' not set.  Please"
+     "point it to a recent GPGME build tree to run the GPGME test suite.")
+    (exit 0))
+
+;; Make sure that GPGME picks up our gpgconf.  This makes GPGME use
+;; and thus executes the tests with GnuPG components from the build
+;; tree.
+(setenv "PATH" (string-append (path-join (getenv "GNUPG_BUILDDIR") "tools")
+                             (string *pathsep*) (getenv "PATH")) #t)
+
+(define (create-file name content)
+  (letfd ((fd (open name (logior O_WRONLY O_CREAT O_BINARY) #o600)))
+    (display content (fdopen fd "wb"))))
+
+(define (create-gpgmehome . path)
+  (create-file "gpg.conf" "no-force-v3-sigs\n")
+  (create-file
+   "gpg-agent.conf"
+   (string-append "pinentry-program "
+                 (in-gpgme-srcdir "tests" "gpg" "pinentry") "\n"))
+  (mkdir "private-keys-v1.d" "-rwx")
+
+  (log "Storing private keys")
+  (for-each
+   (lambda (name)
+     (file-copy (apply in-gpgme-srcdir `(,@path ,name))
+               (path-join "private-keys-v1.d"
+                          (string-append name ".key"))))
+   '("13CD0F3BDF24BE53FE192D62F18737256FF6E4FD"
+     "76F7E2B35832976B50A27A282D9B87E44577EB66"
+     "A0747D5F9425E6664F4FFBEED20FBCA79FDED2BD"
+     "13CBE3758AFE42B5E5E2AE4CED27AFA455E3F87F"
+     "7A030357C0F253A5BBCD282FFC4E521B37558F5C"))
+
+  (log "Importing public demo and test keys")
+  (for-each
+   (lambda (file)
+     (call-check `(,@GPG --yes --import ,(apply in-gpgme-srcdir
+                                               `(,@path ,file)))))
+   (list "pubdemo.asc" "secdemo.asc"))
+  (stop-agent))
+
+;; Initialize the test environment, install appropriate configuration
+;; and start the agent, with the keys from the legacy test suite.
+(define (setup-gpgme-environment . path)
+  (if (member "--unpack-tarball" *args*)
+      (begin
+       (call-check `(,(tool 'gpgtar) --extract --directory=. ,(cadr *args*)))
+       (start-agent))
+      (apply create-gpgme-gpghome path)))
+
+;; Command line flag handling.  Returns the elements following KEY in
+;; ARGUMENTS up to the next argument, or #f if KEY is not in
+;; ARGUMENTS.
+(define (flag key arguments)
+  (cond
+   ((null? arguments)
+    #f)
+   ((string=? key (car arguments))
+    (let loop ((acc '())
+              (args (cdr arguments)))
+      (if (or (null? args) (string-prefix? (car args) "--"))
+         (reverse acc)
+         (loop (cons (car args) acc) (cdr args)))))
+   ((string=? "--" (car arguments))
+    #f)
+   (else
+    (flag key (cdr arguments)))))
+(assert (equal? (flag "--xxx" '("--yyy")) #f))
+(assert (equal? (flag "--xxx" '("--xxx")) '()))
+(assert (equal? (flag "--xxx" '("--xxx" "yyy")) '("yyy")))
+(assert (equal? (flag "--xxx" '("--xxx" "yyy" "zzz")) '("yyy" "zzz")))
+(assert (equal? (flag "--xxx" '("--xxx" "yyy" "zzz" "--")) '("yyy" "zzz")))
+(assert (equal? (flag "--xxx" '("--xxx" "yyy" "--" "zzz")) '("yyy")))
+(assert (equal? (flag "--" '("--" "xxx" "yyy" "--" "zzz")) '("xxx" "yyy")))
+
+(define (parse-makefile port key)
+  (define (is-continuation? tokens)
+    (string=? (last tokens) "\\"))
+  (define (valid-token? s)
+    (< 0 (string-length s)))
+  (define (drop-continuations tokens)
+    (let loop ((acc '()) (tks tokens))
+      (if (null? tks)
+         (reverse acc)
+         (loop (if (string=? "\\" (car tks))
+                   acc
+                   (cons (car tks) acc)) (cdr tks)))))
+  (let next ((acc '()) (found #f))
+    (let ((line (read-line port)))
+      (if (eof-object? line)
+         acc
+         (let ((tokens (filter valid-token?
+                               (string-splitp (string-trim char-whitespace?
+                                                           line)
+                                              char-whitespace? -1))))
+           (cond
+            ((or (null? tokens)
+                 (string-prefix? (car tokens) "#")
+                 (and (not found) (not (and (string=? key (car tokens))
+                                            (string=? "=" (cadr tokens))))))
+             (next acc found))
+            ((not found)
+             (assert (and (string=? key (car tokens))
+                          (string=? "=" (cadr tokens))))
+             (if (is-continuation? tokens)
+                 (next (drop-continuations (cddr tokens)) #t)
+                 (drop-continuations (cddr tokens))))
+            (else
+             (assert found)
+             (if (is-continuation? tokens)
+                 (next (append acc (drop-continuations tokens)) found)
+                 (append acc (drop-continuations tokens))))))))))
+
+(define (parse-makefile-expand filename expand key)
+  (define (variable? v)
+    (and (string-prefix? v "$(") (string-suffix? v ")")))
+
+  (let expand-all ((values (parse-makefile (open-input-file filename) key)))
+    (if (any variable? values)
+       (expand-all
+        (let expand-one ((acc '()) (v values))
+          (cond
+           ((null? v)
+            acc)
+           ((variable? (car v))
+            (let ((makefile (open-input-file filename))
+                  (key (substring (car v) 2 (- (string-length (car v)) 1))))
+              (expand-one (append acc (expand filename makefile key))
+                          (cdr v))))
+           (else
+            (expand-one (append acc (list (car v))) (cdr v))))))
+       values)))
diff --git a/tests/gpgme/run-tests.scm b/tests/gpgme/run-tests.scm
new file mode 100644 (file)
index 0000000..bce5584
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "gpgme-defs.scm"))
+
+(info "Running GPGME's test suite...")
+
+(define (gpgme-makefile-expand filename port key)
+  ;;(interactive-repl (current-environment))
+  (cond
+   ((string=? key "tests_unix")
+    (if *win32*
+       (parse-makefile port key)   ;; Use win32 definition.
+       (begin
+         (parse-makefile port key) ;; Skip win32 definition.
+         (parse-makefile port key))))
+   (else
+    (parse-makefile port key))))
+
+(define (all-tests filename key)
+  (parse-makefile-expand filename gpgme-makefile-expand key))
+
+(let* ((runner (if (member "--parallel" *args*)
+                  run-tests-parallel
+                  run-tests-sequential))
+       (tests (filter (lambda (arg) (not (string-prefix? arg "--"))) *args*)))
+  (runner
+   (test::scm "setup.scm" (in-srcdir "setup.scm") "--" "tests" "gpg")
+   (apply
+    append
+    (map (lambda (cmpnts)
+          (define (compiled? name)
+            (not (or (string-suffix? name ".py")
+                     (string-suffix? name ".test"))))
+          (define :path car)
+          (define :key cadr)
+          (define (find-test name)
+            (apply path-join
+                   `(,(if (compiled? name)
+                          gpgme-builddir
+                          gpgme-srcdir) ,@(:path cmpnts),name)))
+          (let ((makefile (apply path-join `(,gpgme-srcdir ,@(:path cmpnts)
+                                                           "Makefile.am"))))
+            (map (lambda (name)
+                   (apply test::scm
+                          `(,name ,(in-srcdir "wrap.scm") --executable
+                                  ,(find-test name)
+                                  -- ,@(:path cmpnts))))
+                 (if (null? tests) (all-tests makefile (:key cmpnts)) tests))))
+        '((("tests" "gpg") "c_tests")
+          ;; XXX: Not yet.
+          ;; (("lang" "python" "tests") "py_tests")
+          (("lang" "qt" "tests") "TESTS"))))))
diff --git a/tests/gpgme/setup.scm b/tests/gpgme/setup.scm
new file mode 100644 (file)
index 0000000..0116a74
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "gpgme-defs.scm"))
+
+(define tarball (flag "--create-tarball" *args*))
+(unless (and tarball (not (null? tarball)))
+       (error "Usage: setup.scm --create-tarball <file> ..."))
+
+(define components (flag "--" *args*))
+(unless (and components (not (null? components)))
+       (error "Usage: setup.scm --create-tarball " (cadr tarball)
+              " -- component [component ...]"))
+
+(with-temporary-working-directory
+ (setenv "GNUPGHOME" (getcwd) #t)
+ (apply create-gpgmehome components)
+ (stop-agent)
+ (call-check `(,(tool 'gpgtar) --create --output ,(car tarball) ".")))
diff --git a/tests/gpgme/wrap.scm b/tests/gpgme/wrap.scm
new file mode 100644 (file)
index 0000000..4f3ae7d
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "gpgme-defs.scm"))
+
+(define executable (flag "--executable" *args*))
+(unless (and executable (not (null? executable)))
+       (error "Usage: wrap.scm --executable <file> [args...]"))
+
+(setup-gpgme-environment "tests" "gpg")
+
+(setenv "abs_builddir" (getcwd) #t)
+(setenv "top_srcdir" gpgme-srcdir #t)
+(setenv "srcdir" (path-join gpgme-srcdir "tests" "gpg") #t)
+
+(define (run what)
+  (if (string-suffix? (car what) ".py")
+      (begin
+       (setenv "LD_LIBRARY_PATH"
+               (if (< 0 (string-length (getenv "LD_LIBRARY_PATH")))
+                   (string-append (path-join gpgme-builddir "src/.libs")
+                                  (string *pathsep*)
+                                  (getenv "LD_LIBRARY_PATH"))
+                   (path-join gpgme-builddir "src/.libs"))
+               #t)
+       (call-with-fds
+        `("/usr/bin/python"
+          ,(in-gpgme-srcdir "lang" "python" "tests" "run-tests.py")
+          --quiet
+          --interpreters=/usr/bin/python
+          --builddir ,(path-join gpgme-builddir "lang" "python" "tests")
+          ,@what)
+        STDIN_FILENO STDOUT_FILENO STDERR_FILENO))
+      (if #f 77 (call-with-fds what STDIN_FILENO STDOUT_FILENO STDERR_FILENO))))
+
+(let ((name (basename (car executable))))
+  (cond
+   ((string=? "t-keylist" name)
+    ;; This test assumes that 't-import' imported a key.
+    (log "Importing extra key...")
+    (call-check `(,@GPG --yes --import ,(in-srcdir "pubkey-1.asc"))))))
+
+(log "Running" (car executable))
+(exit (run executable))
index fb18538..b62fd1f 100644 (file)
 
 ;; Low-level mechanism to terminate the process.
 (ffi-define (_exit status))
-
-;; High-level mechanism to terminate the process is to throw an error
-;; of the form (*interpreter-exit* status).  This gives automatic
-;; resource management a chance to clean up.
-(define *interpreter-exit* (gensym))
-(define (throw . x)
-  (cond
-   ((more-handlers?)
-    (apply (pop-handler) x))
-   ((and (= 2 (length x)) (equal? *interpreter-exit* (car x)))
-    (*run-atexit-handlers*)
-    (_exit (cadr x)))
-   (else
-    (apply error x))))
-
-;; Terminate the process returning STATUS to the parent.
-(define (exit status)
-  (throw *interpreter-exit* status))
-
-;; A list of functions run at interpreter shutdown.
-(define *atexit-handlers* (list))
-
-;; Execute all these functions.
-(define (*run-atexit-handlers*)
-  (unless (null? *atexit-handlers*)
-         (let ((proc (car *atexit-handlers*)))
-           ;; Drop proc from the list so that it will not get
-           ;; executed again even if it raises an exception.
-           (set! *atexit-handlers* (cdr *atexit-handlers*))
-           (proc)
-           (*run-atexit-handlers*))))
-
-;; Register a function to be run at interpreter shutdown.
-(define (atexit proc)
-  (set! *atexit-handlers* (cons proc *atexit-handlers*)))
index f8fd71a..106afd5 100644 (file)
      `(define ,(cadr form)
           (call/cc (lambda (return) ,@(cddr form)))))
 
+;; Print the given history.
+(define (vm-history-print history)
+  (let loop ((n 0) (skip 0) (frames history))
+    (cond
+     ((null? frames)
+      #t)
+     ((> skip 0)
+      (loop 0 (- skip 1) (cdr frames)))
+     (else
+      (let ((f (car frames)))
+       (display n)
+       (display ": ")
+       (let ((tag (get-tag f)))
+         (unless (null? tag)
+                 (display (basename (car tag)))
+                 (display ":")
+                 (display (+ 1 (cdr tag)))
+                 (display ": ")))
+       (write f))
+       (newline)
+       (loop (+ n 1) skip (cdr frames))))))
+
 ;;;; Simple exception handling
 ;
 ;    Exceptions are caught as follows:
 ;    "Catch" establishes a scope spanning multiple call-frames until
 ;    another "catch" is encountered.  Within the recovery expression
 ;    the thrown exception is bound to *error*.  Errors can be rethrown
-;    using (apply throw *error*).
+;    using (rethrow *error*).
 ;
 ;    Exceptions are thrown with:
 ;
 (define (more-handlers?)
      (pair? *handlers*))
 
-(define (throw . x)
-     (if (more-handlers?)
-          (apply (pop-handler) x)
-          (apply error x)))
+;; This throws an exception.
+(define (throw message . args)
+  (throw' message args (cdr (*vm-history*))))
+
+;; This is used by the vm to throw exceptions.
+(define (throw' message args history)
+  (cond
+   ((more-handlers?)
+    ((pop-handler) message args history))
+   ((and args (list? args) (= 2 (length args))
+        (equal? *interpreter-exit* (car args)))
+    (*run-atexit-handlers*)
+    (quit (cadr args)))
+   (else
+    (display message)
+    (if args (begin
+             (display ": ")
+             (write args)))
+    (newline)
+    (vm-history-print history)
+    (quit 1))))
+
+;; Convenience function to rethrow the error.
+(define (rethrow e)
+  (apply throw' e))
 
 (macro (catch form)
      (let ((label (gensym)))
                     (pop-handler)
                     ,label)))))
 
-(define *error-hook* throw)
+;; Make the vm use throw'.
+(define *error-hook* throw')
+
+\f
+
+;; High-level mechanism to terminate the process is to throw an error
+;; of the form (*interpreter-exit* status).  This gives automatic
+;; resource management a chance to clean up.
+(define *interpreter-exit* (gensym))
+
+;; Terminate the process returning STATUS to the parent.
+(define (exit status)
+  (throw "interpreter exit" *interpreter-exit* status))
+
+;; A list of functions run at interpreter shutdown.
+(define *atexit-handlers* (list))
+
+;; Execute all these functions.
+(define (*run-atexit-handlers*)
+  (unless (null? *atexit-handlers*)
+         (let ((proc (car *atexit-handlers*)))
+           ;; Drop proc from the list so that it will not get
+           ;; executed again even if it raises an exception.
+           (set! *atexit-handlers* (cdr *atexit-handlers*))
+           (proc)
+           (*run-atexit-handlers*))))
+
+;; Register a function to be run at interpreter shutdown.
+(define (atexit proc)
+  (set! *atexit-handlers* (cons proc *atexit-handlers*)))
 
+\f
 
 ;;;;; Definition of MAKE-ENVIRONMENT, to be used with two-argument EVAL
 
index 4e19eae..6959aa4 100644 (file)
 ;; along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 (macro (assert form)
-  `(if (not ,(cadr form))
-       (begin
-        (display "Assertion failed: ")
-        (write (quote ,(cadr form)))
-        (newline)
-        (exit 1))))
+  (let ((tag (get-tag form)))
+    `(if (not ,(cadr form))
+        (throw ,(if (pair? tag)
+                    `(string-append ,(car tag) ":"
+                                    ,(number->string (+ 1 (cdr tag)))
+                                    ": Assertion failed: ")
+                    "Assertion failed: ")
+               (quote ,(cadr form))))))
 (assert #t)
+(assert (not #f))
 
 (define (filter pred lst)
   (cond ((null? lst) '())
 (assert (equal? #f (string-rindex "Hallo" #\a 2)))
 (assert (equal? #f (string-rindex "Hallo" #\.)))
 
-;; Split haystack at delimiter at most n times.
-(define (string-splitn haystack delimiter n)
+;; Split HAYSTACK at each character that makes PREDICATE true at most
+;; N times.
+(define (string-split-pln haystack predicate lookahead n)
   (let ((length (string-length haystack)))
-    (define (split acc delimiter offset n)
+    (define (split acc offset n)
       (if (>= offset length)
          (reverse acc)
-         (let ((i (string-index haystack delimiter offset)))
+         (let ((i (lookahead haystack offset)))
            (if (or (eq? i #f) (= 0 n))
                (reverse (cons (substring haystack offset length) acc))
                (split (cons (substring haystack offset i) acc)
-                      delimiter (+ i 1) (- n 1))))))
-    (split '() delimiter 0 n)))
+                      (+ i 1) (- n 1))))))
+    (split '() 0 n)))
+
+(define (string-indexp haystack offset predicate)
+  (cond
+   ((= (string-length haystack) offset)
+    #f)
+   ((predicate (string-ref haystack offset))
+    offset)
+   (else
+    (string-indexp haystack (+ 1 offset) predicate))))
+
+;; Split HAYSTACK at each character that makes PREDICATE true at most
+;; N times.
+(define (string-splitp haystack predicate n)
+  (string-split-pln haystack predicate
+                   (lambda (haystack offset)
+                     (string-indexp haystack offset predicate))
+                   n))
+(assert (equal? '("a" "b") (string-splitp "a b" char-whitespace? -1)))
+(assert (equal? '("a" "b") (string-splitp "a\tb" char-whitespace? -1)))
+(assert (equal? '("a" "" "b") (string-splitp "a \tb" char-whitespace? -1)))
+
+;; Split haystack at delimiter at most n times.
+(define (string-splitn haystack delimiter n)
+  (string-split-pln haystack
+                   (lambda (c) (char=? c delimiter))
+                   (lambda (haystack offset)
+                     (string-index haystack delimiter offset))
+                   n))
 (assert (= 2 (length (string-splitn "foo:bar:baz" #\: 1))))
 (assert (string=? "foo" (car (string-splitn "foo:bar:baz" #\: 1))))
 (assert (string=? "bar:baz" (cadr (string-splitn "foo:bar:baz" #\: 1))))
 ;; Trim the prefix of S containing only characters that make PREDICATE
 ;; true.
 (define (string-ltrim predicate s)
-  (let loop ((s' (string->list s)))
-    (if (predicate (car s'))
-       (loop (cdr s'))
-       (list->string s'))))
+  (if (string=? s "")
+      ""
+      (let loop ((s' (string->list s)))
+       (if (predicate (car s'))
+           (loop (cdr s'))
+           (list->string s')))))
+(assert (string=? "" (string-ltrim char-whitespace? "")))
 (assert (string=? "foo" (string-ltrim char-whitespace? "  foo")))
 
 ;; Trim the suffix of S containing only characters that make PREDICATE
 ;; true.
 (define (string-rtrim predicate s)
-  (let loop ((s' (reverse (string->list s))))
-    (if (predicate (car s'))
-       (loop (cdr s'))
-       (list->string (reverse s')))))
+  (if (string=? s "")
+      ""
+      (let loop ((s' (reverse (string->list s))))
+       (if (predicate (car s'))
+           (loop (cdr s'))
+           (list->string (reverse s'))))))
+(assert (string=? "" (string-rtrim char-whitespace? "")))
 (assert (string=? "foo" (string-rtrim char-whitespace? "foo    ")))
 
 ;; Trim both the prefix and suffix of S containing only characters
 ;; that make PREDICATE true.
 (define (string-trim predicate s)
   (string-ltrim predicate (string-rtrim predicate s)))
+(assert (string=? "" (string-trim char-whitespace? "")))
 (assert (string=? "foo" (string-trim char-whitespace? "        foo     ")))
 
 ;; Check if needle is contained in haystack.
         (apply read-char p)
         '()))))))
 
+(define (list->string-reversed lst)
+  (let* ((len (length lst))
+        (str (make-string len)))
+    (let loop ((i (- len 1))
+              (l lst))
+      (if (< i 0)
+         (begin
+           (assert (null? l))
+           str)
+         (begin
+           (string-set! str i (car l))
+           (loop (- i 1) (cdr l)))))))
+
 ;; Read a line from port P.
 (define (read-line . p)
-  (list->string
-   (let f ()
-     (let ((c (apply peek-char p)))
-       (cond
-       ((eof-object? c) '())
-       ((char=? c #\newline)
-        (apply read-char p)
-        '())
-       (else
-        (apply read-char p)
-        (cons c (f))))))))
+  (let loop ((acc '()))
+    (let ((c (apply peek-char p)))
+      (cond
+       ((eof-object? c)
+       (if (null? acc)
+           c ;; #eof
+           (list->string-reversed acc)))
+       ((char=? c #\newline)
+       (apply read-char p)
+       (list->string-reversed acc))
+       (else
+       (apply read-char p)
+       (loop (cons c acc)))))))
 
 ;; Read everything from port P.
 (define (read-all . p)
index 2f77ac5..c96dcf1 100644 (file)
@@ -150,7 +150,10 @@ load (scheme *sc, char *file_name,
 
         h = fopen (qualified_name, "r");
         if (h)
-          break;
+          {
+            err = 0;
+            break;
+          }
 
         if (n > 1)
           {
@@ -170,23 +173,23 @@ load (scheme *sc, char *file_name,
         fprintf (stderr,
                  "Consider using GPGSCM_PATH to specify the location "
                  "of the Scheme library.\n");
-      return err;
+      goto leave;
     }
   if (verbose > 1)
     fprintf (stderr, "Loading %s...\n", qualified_name);
   scheme_load_named_file (sc, h, qualified_name);
   fclose (h);
 
-  if (sc->retcode)
+  if (sc->retcode && sc->nesting)
     {
-      if (sc->nesting)
-        fprintf (stderr, "%s: Unbalanced parenthesis\n", qualified_name);
-      return gpg_error (GPG_ERR_GENERAL);
+      fprintf (stderr, "%s: Unbalanced parenthesis\n", qualified_name);
+      err = gpg_error (GPG_ERR_GENERAL);
     }
 
+ leave:
   if (file_name != qualified_name)
     free (qualified_name);
-  return 0;
+  return err;
 }
 
 \f
@@ -194,6 +197,7 @@ load (scheme *sc, char *file_name,
 int
 main (int argc, char **argv)
 {
+  int retcode;
   gpg_error_t err;
   char *argv0;
   ARGPARSE_ARGS pargs;
@@ -291,8 +295,9 @@ main (int argc, char **argv)
         log_fatal ("%s: %s", script, gpg_strerror (err));
     }
 
+  retcode = sc->retcode;
   scheme_load_string (sc, "(*run-atexit-handlers*)");
   scheme_deinit (sc);
   xfree (sc);
-  return EXIT_SUCCESS;
+  return retcode;
 }
index ceb4d0e..2d17720 100644 (file)
 #endif
     _OP_DEF(opexe_0, 0,                                0,  0,       0,                               OP_E0ARGS           )
     _OP_DEF(opexe_0, 0,                                0,  0,       0,                               OP_E1ARGS           )
+#if USE_HISTORY
+    _OP_DEF(opexe_0, 0,                                0,  0,       0,                               OP_CALLSTACK_POP    )
+#endif
+    _OP_DEF(opexe_0, 0,                                0,  0,       0,                               OP_APPLY_CODE       )
     _OP_DEF(opexe_0, 0,                                0,  0,       0,                               OP_APPLY            )
 #if USE_TRACING
     _OP_DEF(opexe_0, 0,                                0,  0,       0,                               OP_REAL_APPLY       )
     _OP_DEF(opexe_4, "list*",                          1,  INF_ARG, TST_NONE,                        OP_LIST_STAR        )
     _OP_DEF(opexe_4, "append",                         0,  INF_ARG, TST_NONE,                        OP_APPEND           )
 #if USE_PLIST
-    _OP_DEF(opexe_4, "put",                            3,  3,       TST_NONE,                        OP_PUT              )
-    _OP_DEF(opexe_4, "get",                            2,  2,       TST_NONE,                        OP_GET              )
+    _OP_DEF(opexe_4, "set-symbol-property!",           3,  3,       TST_SYMBOL TST_SYMBOL TST_ANY,   OP_SET_SYMBOL_PROPERTY )
+    _OP_DEF(opexe_4, "symbol-property",                2,  2,       TST_SYMBOL TST_SYMBOL,           OP_SYMBOL_PROPERTY  )
+#endif
+#if USE_TAGS
+    _OP_DEF(opexe_4, NULL,                             0,  0,       TST_NONE,                        OP_TAG_VALUE        )
+    _OP_DEF(opexe_4, "make-tagged-value",              2,  2,       TST_ANY TST_PAIR,                 OP_MK_TAGGED        )
+    _OP_DEF(opexe_4, "get-tag",                        1,  1,       TST_ANY,                         OP_GET_TAG          )
 #endif
     _OP_DEF(opexe_4, "quit",                           0,  1,       TST_NUMBER,                      OP_QUIT             )
     _OP_DEF(opexe_4, "gc",                             0,  0,       0,                               OP_GC               )
     _OP_DEF(opexe_6, "get-closure-code",               1,  1,       TST_NONE,                        OP_GET_CLOSURE      )
     _OP_DEF(opexe_6, "closure?",                       1,  1,       TST_NONE,                        OP_CLOSUREP         )
     _OP_DEF(opexe_6, "macro?",                         1,  1,       TST_NONE,                        OP_MACROP           )
+    _OP_DEF(opexe_6, "*vm-history*",                   0,  0,       TST_NONE,                        OP_VM_HISTORY       )
+
 #undef _OP_DEF
index 78b8151..84454dc 100644 (file)
                              (read (open-input-string next)))))
               (if (not (eof-object? c))
                   (begin
-                    (catch (echo "Error:" *error*)
+                    (catch (begin
+                             (display (car *error*))
+                             (when (and (cadr *error*)
+                                        (not (null? (cadr *error*))))
+                                   (display ": ")
+                                   (write (cadr *error*)))
+                             (newline)
+                             (vm-history-print (caddr *error*)))
                            (echo "    ===>" (eval c environment)))
                     (exit (loop ""))))
               (exit (loop next)))))))))
index aa78894..7f19a6e 100644 (file)
@@ -62,6 +62,34 @@ struct cell {
   } _object;
 };
 
+#if USE_HISTORY
+/* The history is a two-dimensional ring buffer.  A donut-shaped data
+ * structure.  This data structure is inspired by MIT/GNU Scheme.  */
+struct history {
+  /* Number of calls to store.  Must be a power of two.  */
+  size_t N;
+
+  /* Number of tail-calls to store in each call frame.  Must be a
+   * power of two.  */
+  size_t M;
+
+  /* Masks for fast index calculations.  */
+  size_t mask_N;
+  size_t mask_M;
+
+  /* A vector of size N containing calls.  */
+  pointer callstack;
+
+  /* A vector of size N containing vectors of size M containing tail
+   * calls.  */
+  pointer tailstacks;
+
+  /* Our current position.  */
+  size_t n;
+  size_t *m;
+};
+#endif
+
 struct scheme {
 /* arrays for segments */
 func_alloc malloc;
@@ -88,6 +116,11 @@ pointer envir;           /* stack register for current environment */
 pointer code;            /* register for current code */
 pointer dump;            /* stack register for next evaluation */
 
+#if USE_HISTORY
+struct history history;  /* we keep track of the call history for
+                          * error messages */
+#endif
+
 int interactive_repl;    /* are we in an interactive REPL? */
 
 struct cell _sink;
@@ -119,6 +152,12 @@ pointer SHARP_HOOK;  /* *sharp-hook* */
 pointer COMPILE_HOOK;  /* *compile-hook* */
 #endif
 
+#if USE_SMALL_INTEGERS
+/* A fixed allocation of small integers.  */
+void *integer_alloc;
+pointer integer_cells;
+#endif
+
 pointer free_cell;       /* pointer to top of free cells */
 long    fcells;          /* # of free cells */
 size_t  inhibit_gc;      /* nesting of gc_disable */
@@ -157,6 +196,7 @@ int tok;
 int print_flag;
 pointer value;
 int op;
+unsigned int flags;
 
 void *ext_data;     /* For the benefit of foreign functions */
 long gensym_cnt;
index ee8992e..a5b7691 100644 (file)
@@ -29,6 +29,7 @@
 
 #include <assert.h>
 #include <limits.h>
+#include <stdint.h>
 #include <float.h>
 #include <ctype.h>
 
@@ -165,6 +166,7 @@ type_to_string (enum scheme_types typ)
 #define ADJ 32
 #define TYPE_BITS 5
 #define T_MASKTYPE      31    /* 0000000000011111 */
+#define T_TAGGED      1024    /* 0000010000000000 */
 #define T_FINALIZE    2048    /* 0000100000000000 */
 #define T_SYNTAX      4096    /* 0001000000000000 */
 #define T_IMMUTABLE   8192    /* 0010000000000000 */
@@ -250,7 +252,7 @@ INTERFACE pointer set_cdr(pointer p, pointer q) { return cdr(p)=q; }
 INTERFACE INLINE int is_symbol(pointer p)   { return (type(p)==T_SYMBOL); }
 INTERFACE INLINE char *symname(pointer p)   { return strvalue(car(p)); }
 #if USE_PLIST
-SCHEME_EXPORT INLINE int hasprop(pointer p)     { return (typeflag(p)&T_SYMBOL); }
+SCHEME_EXPORT INLINE int hasprop(pointer p)     { return (is_symbol(p)); }
 #define symprop(p)       cdr(p)
 #endif
 
@@ -306,6 +308,14 @@ INTERFACE INLINE void setimmutable(pointer p) { typeflag(p) |= T_IMMUTABLE; }
 #define cadddr(p)        car(cdr(cdr(cdr(p))))
 #define cddddr(p)        cdr(cdr(cdr(cdr(p))))
 
+#if USE_HISTORY
+static pointer history_flatten(scheme *sc);
+static void history_mark(scheme *sc);
+#else
+# define history_mark(SC)      (void) 0
+# define history_flatten(SC)   (SC)->NIL
+#endif
+
 #if USE_CHAR_CLASSIFIERS
 static INLINE int Cisalpha(int c) { return isascii(c) && isalpha(c); }
 static INLINE int Cisdigit(int c) { return isascii(c) && isdigit(c); }
@@ -409,7 +419,7 @@ static void printatom(scheme *sc, pointer l, int f);
 static pointer mk_proc(scheme *sc, enum scheme_opcodes op);
 static pointer mk_closure(scheme *sc, pointer c, pointer e);
 static pointer mk_continuation(scheme *sc, pointer d);
-static pointer reverse(scheme *sc, pointer a);
+static pointer reverse(scheme *sc, pointer term, pointer list);
 static pointer reverse_in_place(scheme *sc, pointer term, pointer list);
 static pointer revappend(scheme *sc, pointer a, pointer b);
 static void dump_stack_mark(scheme *);
@@ -598,34 +608,100 @@ static long binary_decode(const char *s) {
  return x;
 }
 
+\f
+
+/* Tags are like property lists, but can be attached to arbitrary
+ * values.  */
+
+#if USE_TAGS
+
+static pointer
+mk_tagged_value(scheme *sc, pointer v, pointer tag_car, pointer tag_cdr)
+{
+  pointer r, t;
+
+  assert(! is_vector(v));
+
+  r = get_consecutive_cells(sc, 2);
+  if (r == sc->sink)
+    return sc->sink;
+
+  memcpy(r, v, sizeof *v);
+  typeflag(r) |= T_TAGGED;
+
+  t = r + 1;
+  typeflag(t) = T_PAIR;
+  car(t) = tag_car;
+  cdr(t) = tag_cdr;
+
+  return r;
+}
+
+static INLINE int
+has_tag(pointer v)
+{
+  return !! (typeflag(v) & T_TAGGED);
+}
+
+static INLINE pointer
+get_tag(scheme *sc, pointer v)
+{
+  if (has_tag(v))
+    return v + 1;
+  return sc->NIL;
+}
+
+#else
+
+#define mk_tagged_value(SC, X, A, B)   (X)
+#define has_tag(V)                     0
+#define get_tag(SC, V)                 (SC)->NIL
+
+#endif
+
+\f
+
+/* Allocate a new cell segment but do not make it available yet.  */
+static int
+_alloc_cellseg(scheme *sc, size_t len, void **alloc, pointer *cells)
+{
+  int adj = ADJ;
+  void *cp;
+
+  if (adj < sizeof(struct cell))
+    adj = sizeof(struct cell);
+
+  cp = sc->malloc(len * sizeof(struct cell) + adj);
+  if (cp == NULL)
+    return 1;
+
+  *alloc = cp;
+
+  /* adjust in TYPE_BITS-bit boundary */
+  if (((uintptr_t) cp) % adj != 0)
+    cp = (void *) (adj * ((uintptr_t) cp / adj + 1));
+
+  *cells = cp;
+  return 0;
+}
+
 /* allocate new cell segment */
 static int alloc_cellseg(scheme *sc, int n) {
      pointer newp;
      pointer last;
      pointer p;
-     void *cp;
      long i;
      int k;
-     int adj=ADJ;
-
-     if(adj<sizeof(struct cell)) {
-       adj=sizeof(struct cell);
-     }
 
      for (k = 0; k < n; k++) {
          if (sc->last_cell_seg >= CELL_NSEGMENT - 1)
               return k;
-         cp = sc->malloc(CELL_SEGSIZE * sizeof(struct cell)+adj);
-         if (cp == 0)
-              return k;
-         i = ++sc->last_cell_seg ;
-         sc->alloc_seg[i] = cp;
-         /* adjust in TYPE_BITS-bit boundary */
-         if(((unsigned long)cp)%adj!=0) {
-           cp=(void *)(adj*((unsigned long)cp/adj+1));
-         }
+        i = ++sc->last_cell_seg;
+        if (_alloc_cellseg(sc, CELL_SEGSIZE, &sc->alloc_seg[i], &newp)) {
+             sc->last_cell_seg--;
+             return k;
+        }
          /* insert new segment in address order */
-         newp=(pointer)cp;
          sc->cell_seg[i] = newp;
          while (i > 0 && sc->cell_seg[i - 1] > sc->cell_seg[i]) {
              p = sc->cell_seg[i];
@@ -1128,16 +1204,64 @@ INTERFACE pointer mk_character(scheme *sc, int c) {
   return (x);
 }
 
+\f
+
+#if USE_SMALL_INTEGERS
+
+/* s_save assumes that all opcodes can be expressed as a small
+ * integer.  */
+#define MAX_SMALL_INTEGER      OP_MAXDEFINED
+
+static int
+initialize_small_integers(scheme *sc)
+{
+  int i;
+  if (_alloc_cellseg(sc, MAX_SMALL_INTEGER, &sc->integer_alloc,
+                    &sc->integer_cells))
+    return 1;
+
+  for (i = 0; i < MAX_SMALL_INTEGER; i++) {
+    pointer x = &sc->integer_cells[i];
+    typeflag(x) = T_NUMBER | T_ATOM | MARK;
+    ivalue_unchecked(x) = i;
+    set_num_integer(x);
+  }
+
+  return 0;
+}
+
+static INLINE pointer
+mk_small_integer(scheme *sc, long n)
+{
+#define mk_small_integer_allocates     0
+  assert(0 <= n && n < MAX_SMALL_INTEGER);
+  return &sc->integer_cells[n];
+}
+#else
+
+#define mk_small_integer_allocates     1
+#define mk_small_integer       mk_integer
+
+#endif
+
 /* get number atom (integer) */
 INTERFACE pointer mk_integer(scheme *sc, long n) {
-  pointer x = get_cell(sc,sc->NIL, sc->NIL);
+  pointer x;
+
+#if USE_SMALL_INTEGERS
+  if (0 <= n && n < MAX_SMALL_INTEGER)
+    return mk_small_integer(sc, n);
+#endif
 
+  x = get_cell(sc,sc->NIL, sc->NIL);
   typeflag(x) = (T_NUMBER | T_ATOM);
   ivalue_unchecked(x)= n;
   set_num_integer(x);
   return (x);
 }
 
+\f
+
 INTERFACE pointer mk_real(scheme *sc, double n) {
   pointer x = get_cell(sc,sc->NIL, sc->NIL);
 
@@ -1285,14 +1409,23 @@ static pointer mk_atom(scheme *sc, char *q) {
      int has_fp_exp = 0;
 
 #if USE_COLON_HOOK
-     if((p=strstr(q,"::"))!=0) {
+     char *next;
+     next = p = q;
+     while ((next = strstr(next, "::")) != 0) {
+         /* Keep looking for the last occurrence.  */
+         p = next;
+         next = next + 2;
+     }
+
+     if (p != q) {
           *p=0;
           return cons(sc, sc->COLON_HOOK,
                           cons(sc,
                               cons(sc,
                                    sc->QUOTE,
-                                   cons(sc, mk_atom(sc,p+2), sc->NIL)),
-                              cons(sc, mk_symbol(sc,strlwr(q)), sc->NIL)));
+                                   cons(sc, mk_symbol(sc, strlwr(p + 2)),
+                                       sc->NIL)),
+                              cons(sc, mk_atom(sc, q), sc->NIL)));
      }
 #endif
 
@@ -1419,6 +1552,9 @@ E2:  setmark(p);
                mark(p+1+i);
           }
      }
+     /* Mark tag if p has one.  */
+     if (has_tag(p))
+       mark(p + 1);
      if (is_atom(p))
           goto E6;
      /* E4: down car */
@@ -1474,6 +1610,7 @@ static void gc(scheme *sc, pointer a, pointer b) {
   mark(sc->args);
   mark(sc->envir);
   mark(sc->code);
+  history_mark(sc);
   dump_stack_mark(sc);
   mark(sc->value);
   mark(sc->inport);
@@ -1508,10 +1645,10 @@ static void gc(scheme *sc, pointer a, pointer b) {
     /* reclaim cell */
         if (typeflag(p) & T_FINALIZE) {
           finalize_cell(sc, p);
-          typeflag(p) = 0;
-          car(p) = sc->NIL;
         }
         ++sc->fcells;
+       typeflag(p) = 0;
+        car(p) = sc->NIL;
         cdr(p) = sc->free_cell;
         sc->free_cell = p;
       }
@@ -2295,9 +2432,9 @@ static pointer list_star(scheme *sc, pointer d) {
 }
 
 /* reverse list -- produce new list */
-static pointer reverse(scheme *sc, pointer a) {
+static pointer reverse(scheme *sc, pointer term, pointer list) {
 /* a must be checked by gc */
-     pointer p = sc->NIL;
+     pointer a = list, p = term;
 
      for ( ; is_pair(a); a = cdr(a)) {
           p = cons(sc, car(a), p);
@@ -2528,6 +2665,7 @@ static INLINE pointer slot_value_in_env(pointer slot)
 
 static pointer _Error_1(scheme *sc, const char *s, pointer a) {
      const char *str = s;
+     pointer history;
 #if USE_ERROR_HOOK
      pointer x;
      pointer hdl=sc->ERROR_HOOK;
@@ -2535,19 +2673,34 @@ static pointer _Error_1(scheme *sc, const char *s, pointer a) {
 
 #if SHOW_ERROR_LINE
      char sbuf[STRBUFFSIZE];
+#endif
+
+     history = history_flatten(sc);
 
+#if SHOW_ERROR_LINE
      /* make sure error is not in REPL */
      if (sc->load_stack[sc->file_i].kind & port_file &&
          sc->load_stack[sc->file_i].rep.stdio.file != stdin) {
-       int ln = sc->load_stack[sc->file_i].rep.stdio.curr_line;
-       const char *fname = sc->load_stack[sc->file_i].rep.stdio.filename;
+       pointer tag;
+       const char *fname;
+       int ln;
+
+       if (history != sc->NIL && has_tag(car(history))
+          && (tag = get_tag(sc, car(history)))
+          && is_string(car(tag)) && is_integer(cdr(tag))) {
+        fname = string_value(car(tag));
+        ln = ivalue_unchecked(cdr(tag));
+       } else {
+        fname = sc->load_stack[sc->file_i].rep.stdio.filename;
+        ln = sc->load_stack[sc->file_i].rep.stdio.curr_line;
+       }
 
        /* should never happen */
        if(!fname) fname = "<unknown>";
 
        /* we started from 0 */
        ln++;
-       snprintf(sbuf, STRBUFFSIZE, "(%s : %i) %s", fname, ln, s);
+       snprintf(sbuf, STRBUFFSIZE, "%s:%i: %s", fname, ln, s);
 
        str = (const char*)sbuf;
      }
@@ -2556,11 +2709,15 @@ static pointer _Error_1(scheme *sc, const char *s, pointer a) {
 #if USE_ERROR_HOOK
      x=find_slot_in_env(sc,sc->envir,hdl,1);
     if (x != sc->NIL) {
+        sc->code = cons(sc, cons(sc, sc->QUOTE,
+                                 cons(sc, history, sc->NIL)),
+                        sc->NIL);
          if(a!=0) {
-               sc->code = cons(sc, cons(sc, sc->QUOTE, cons(sc,(a), sc->NIL)), sc->NIL);
+          sc->code = cons(sc, cons(sc, sc->QUOTE, cons(sc, a, sc->NIL)),
+                          sc->code);
          } else {
-               sc->code = sc->NIL;
-         }
+          sc->code = cons(sc, sc->F, sc->code);
+        }
          sc->code = cons(sc, mk_string(sc, str), sc->code);
          setimmutable(car(sc->code));
          sc->code = cons(sc, slot_value_in_env(x), sc->code);
@@ -2586,6 +2743,40 @@ static pointer _Error_1(scheme *sc, const char *s, pointer a) {
 # define  BEGIN     do {
 # define  END  } while (0)
 
+\f
+
+/* Flags.  The interpreter has a flags field.  When the interpreter
+ * pushes a frame to the dump stack, it is encoded with the opcode.
+ * Therefore, we do not use the least significant byte.  */
+
+/* Masks used to encode and decode opcode and flags.  */
+#define S_OP_MASK      0x000000ff
+#define S_FLAG_MASK    0xffffff00
+
+/* Set if the interpreter evaluates an expression in a tail context
+ * (see R5RS, section 3.5).  If a function, procedure, or continuation
+ * is invoked while this flag is set, the call is recorded as tail
+ * call in the history buffer.  */
+#define S_FLAG_TAIL_CONTEXT    0x00000100
+
+/* Set flag F.  */
+#define s_set_flag(sc, f)                      \
+          BEGIN                                \
+          (sc)->flags |= S_FLAG_ ## f;         \
+          END
+
+/* Clear flag F.  */
+#define s_clear_flag(sc, f)                    \
+          BEGIN                                \
+          (sc)->flags &= ~ S_FLAG_ ## f;       \
+          END
+
+/* Check if flag F is set.  */
+#define s_get_flag(sc, f)                      \
+          !!((sc)->flags & S_FLAG_ ## f)
+
+\f
+
 /* Bounce back to Eval_Cycle and execute A.  */
 #define s_goto(sc,a) BEGIN                                  \
     sc->op = (int)(a);                                      \
@@ -2638,14 +2829,23 @@ static void dump_stack_free(scheme *sc)
 static pointer _s_return(scheme *sc, pointer a, int enable_gc) {
   pointer dump = sc->dump;
   pointer op;
+  unsigned long v;
   sc->value = (a);
   if (enable_gc)
        gc_enable(sc);
   if (dump == sc->NIL)
     return sc->NIL;
   free_cons(sc, dump, &op, &dump);
-  sc->op = ivalue(op);
-  free_cell(sc, op);
+  v = (unsigned long) ivalue_unchecked(op);
+  sc->op = (int) (v & S_OP_MASK);
+  sc->flags = v & S_FLAG_MASK;
+#ifdef USE_SMALL_INTEGERS
+  if (v < MAX_SMALL_INTEGER) {
+    /* This is a small integer, we must not free it.  */
+  } else
+    /* Normal integer.  Recover the cell.  */
+#endif
+    free_cell(sc, op);
   free_cons(sc, dump, &sc->args, &dump);
   free_cons(sc, dump, &sc->envir, &dump);
   free_cons(sc, dump, &sc->code, &sc->dump);
@@ -2655,10 +2855,11 @@ static pointer _s_return(scheme *sc, pointer a, int enable_gc) {
 static void s_save(scheme *sc, enum scheme_opcodes op, pointer args, pointer code) {
 #define s_save_allocates       5
     pointer dump;
+    unsigned long v = sc->flags | ((unsigned long) op);
     gc_disable(sc, gc_reservations (s_save));
     dump = cons(sc, sc->envir, cons(sc, (code), sc->dump));
     dump = cons(sc, (args), dump);
-    sc->dump = cons(sc, mk_integer(sc, (long)(op)), dump);
+    sc->dump = cons(sc, mk_integer(sc, (long) v), dump);
     gc_enable(sc);
 }
 
@@ -2667,10 +2868,236 @@ static INLINE void dump_stack_mark(scheme *sc)
   mark(sc->dump);
 }
 
+\f
+
+#if USE_HISTORY
+
+static void
+history_free(scheme *sc)
+{
+  sc->free(sc->history.m);
+  sc->history.tailstacks = sc->NIL;
+  sc->history.callstack = sc->NIL;
+}
+
+static pointer
+history_init(scheme *sc, size_t N, size_t M)
+{
+  size_t i;
+  struct history *h = &sc->history;
+
+  h->N = N;
+  h->mask_N = N - 1;
+  h->n = N - 1;
+  assert ((N & h->mask_N) == 0);
+
+  h->M = M;
+  h->mask_M = M - 1;
+  assert ((M & h->mask_M) == 0);
+
+  h->callstack = mk_vector(sc, N);
+  if (h->callstack == sc->sink)
+    goto fail;
+
+  h->tailstacks = mk_vector(sc, N);
+  for (i = 0; i < N; i++) {
+    pointer tailstack = mk_vector(sc, M);
+    if (tailstack == sc->sink)
+      goto fail;
+    set_vector_elem(h->tailstacks, i, tailstack);
+  }
+
+  h->m = sc->malloc(N * sizeof *h->m);
+  if (h->m == NULL)
+    goto fail;
+
+  for (i = 0; i < N; i++)
+    h->m[i] = 0;
+
+  return sc->T;
+
+fail:
+  history_free(sc);
+  return sc->F;
+}
+
+static void
+history_mark(scheme *sc)
+{
+  struct history *h = &sc->history;
+  mark(h->callstack);
+  mark(h->tailstacks);
+}
+
+#define add_mod(a, b, mask)    (((a) + (b)) & (mask))
+#define sub_mod(a, b, mask)    add_mod(a, (mask) + 1 - (b), mask)
+
+static INLINE void
+tailstack_clear(scheme *sc, pointer v)
+{
+  assert(is_vector(v));
+  /* XXX optimize */
+  fill_vector(v, sc->NIL);
+}
+
+static pointer
+callstack_pop(scheme *sc)
+{
+  struct history *h = &sc->history;
+  size_t n = h->n;
+  pointer item;
+
+  if (h->callstack == sc->NIL)
+    return sc->NIL;
+
+  item = vector_elem(h->callstack, n);
+  /* Clear our frame so that it can be gc'ed and we don't run into it
+   * when walking the history.  */
+  set_vector_elem(h->callstack, n, sc->NIL);
+  tailstack_clear(sc, vector_elem(h->tailstacks, n));
+
+  /* Exit from the frame.  */
+  h->n = sub_mod(h->n, 1, h->mask_N);
+
+  return item;
+}
+
+static void
+callstack_push(scheme *sc, pointer item)
+{
+  struct history *h = &sc->history;
+  size_t n = h->n;
+
+  if (h->callstack == sc->NIL)
+    return;
+
+  /* Enter a new frame.  */
+  n = h->n = add_mod(n, 1, h->mask_N);
+
+  /* Initialize tail stack.  */
+  tailstack_clear(sc, vector_elem(h->tailstacks, n));
+  h->m[n] = h->mask_M;
+
+  set_vector_elem(h->callstack, n, item);
+}
+
+static void
+tailstack_push(scheme *sc, pointer item)
+{
+  struct history *h = &sc->history;
+  size_t n = h->n;
+  size_t m = h->m[n];
+
+  if (h->callstack == sc->NIL)
+    return;
+
+  /* Enter a new tail frame.  */
+  m = h->m[n] = add_mod(m, 1, h->mask_M);
+  set_vector_elem(vector_elem(h->tailstacks, n), m, item);
+}
+
+static pointer
+tailstack_flatten(scheme *sc, pointer tailstack, size_t i, size_t n,
+                 pointer acc)
+{
+  struct history *h = &sc->history;
+  pointer frame;
+
+  assert(i <= h->M);
+  assert(n < h->M);
+
+  if (acc == sc->sink)
+    return sc->sink;
+
+  if (i == 0) {
+    /* We reached the end, but we did not see a unused frame.  Signal
+       this using '... .  */
+    return cons(sc, mk_symbol(sc, "..."), acc);
+  }
+
+  frame = vector_elem(tailstack, n);
+  if (frame == sc->NIL) {
+    /* A unused frame.  We reached the end of the history.  */
+    return acc;
+  }
+
+  /* Add us.  */
+  acc = cons(sc, frame, acc);
+
+  return tailstack_flatten(sc, tailstack, i - 1, sub_mod(n, 1, h->mask_M),
+                          acc);
+}
+
+static pointer
+callstack_flatten(scheme *sc, size_t i, size_t n, pointer acc)
+{
+  struct history *h = &sc->history;
+  pointer frame;
+
+  assert(i <= h->N);
+  assert(n < h->N);
+
+  if (acc == sc->sink)
+    return sc->sink;
+
+  if (i == 0) {
+    /* We reached the end, but we did not see a unused frame.  Signal
+       this using '... .  */
+    return cons(sc, mk_symbol(sc, "..."), acc);
+  }
+
+  frame = vector_elem(h->callstack, n);
+  if (frame == sc->NIL) {
+    /* A unused frame.  We reached the end of the history.  */
+    return acc;
+  }
+
+  /* First, emit the tail calls.  */
+  acc = tailstack_flatten(sc, vector_elem(h->tailstacks, n), h->M, h->m[n],
+                         acc);
+
+  /* Then us.  */
+  acc = cons(sc, frame, acc);
+
+  return callstack_flatten(sc, i - 1, sub_mod(n, 1, h->mask_N), acc);
+}
+
+static pointer
+history_flatten(scheme *sc)
+{
+  struct history *h = &sc->history;
+  pointer history;
+
+  if (h->callstack == sc->NIL)
+    return sc->NIL;
+
+  history = callstack_flatten(sc, h->N, h->n, sc->NIL);
+  if (history == sc->sink)
+    return sc->sink;
+
+  return reverse_in_place(sc, sc->NIL, history);
+}
+
+#undef add_mod
+#undef sub_mod
+
+#else  /* USE_HISTORY */
+
+#define history_init(SC, A, B) (void) 0
+#define history_free(SC)       (void) 0
+#define callstack_pop(SC)      (void) 0
+#define callstack_push(SC, X)  (void) 0
+#define tailstack_push(SC, X)  (void) 0
+
+#endif /* USE_HISTORY */
+
+\f
+
 #define s_retbool(tf)    s_return(sc,(tf) ? sc->T : sc->F)
 
 static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
      pointer x, y;
+     pointer callsite;
 
      switch (op) {
      CASE(OP_LOAD):       /* load */
@@ -2779,6 +3206,7 @@ static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
                     s_save(sc,OP_E0ARGS, sc->NIL, sc->code);
                     /* If no macros => s_save(sc,OP_E1ARGS, sc->NIL, cdr(sc->code));*/
                     sc->code = car(sc->code);
+                   s_clear_flag(sc, TAIL_CONTEXT);
                     s_thread_to(sc,OP_EVAL);
                }
           } else {
@@ -2792,9 +3220,13 @@ static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
                sc->args = cons(sc,sc->code, sc->NIL);
               gc_enable(sc);
                sc->code = sc->value;
+              s_clear_flag(sc, TAIL_CONTEXT);
                s_thread_to(sc,OP_APPLY);
           } else {
-               sc->code = cdr(sc->code);
+              gc_disable(sc, 1);
+              sc->args = cons(sc, sc->code, sc->NIL);
+              gc_enable(sc);
+              sc->code = cdr(sc->code);
                s_thread_to(sc,OP_E1ARGS);
           }
 
@@ -2806,12 +3238,11 @@ static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
                s_save(sc,OP_E1ARGS, sc->args, cdr(sc->code));
                sc->code = car(sc->code);
                sc->args = sc->NIL;
+              s_clear_flag(sc, TAIL_CONTEXT);
                s_thread_to(sc,OP_EVAL);
           } else {  /* end */
                sc->args = reverse_in_place(sc, sc->NIL, sc->args);
-               sc->code = car(sc->args);
-               sc->args = cdr(sc->args);
-               s_thread_to(sc,OP_APPLY);
+               s_thread_to(sc,OP_APPLY_CODE);
           }
 
 #if USE_TRACING
@@ -2823,6 +3254,20 @@ static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
      }
 #endif
 
+#if USE_HISTORY
+     CASE(OP_CALLSTACK_POP):      /* pop the call stack */
+         callstack_pop(sc);
+         s_return(sc, sc->value);
+#endif
+
+     CASE(OP_APPLY_CODE): /* apply 'cadr(args)' to 'cddr(args)',
+                          * record in the history as invoked from
+                          * 'car(args)' */
+         free_cons(sc, sc->args, &callsite, &sc->args);
+         sc->code = car(sc->args);
+         sc->args = cdr(sc->args);
+         /* Fallthrough.  */
+
      CASE(OP_APPLY):      /* apply 'code' to 'args' */
 #if USE_TRACING
        if(sc->tracing) {
@@ -2835,6 +3280,18 @@ static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
        /* fall through */
      CASE(OP_REAL_APPLY):
 #endif
+#if USE_HISTORY
+          if (op != OP_APPLY_CODE)
+            callsite = sc->code;
+          if (s_get_flag(sc, TAIL_CONTEXT)) {
+            /* We are evaluating a tail call.  */
+            tailstack_push(sc, callsite);
+          } else {
+            callstack_push(sc, callsite);
+            s_save(sc, OP_CALLSTACK_POP, sc->NIL, sc->NIL);
+          }
+#endif
+
           if (is_proc(sc->code)) {
                s_goto(sc,procnum(sc->code));   /* PROCEDURE */
           } else if (is_foreign(sc->code))
@@ -2869,6 +3326,7 @@ static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
                }
                sc->code = cdr(closure_code(sc->code));
                sc->args = sc->NIL;
+              s_set_flag(sc, TAIL_CONTEXT);
                s_thread_to(sc,OP_BEGIN);
           } else if (is_continuation(sc->code)) { /* CONTINUATION */
                sc->dump = cont_dump(sc->code);
@@ -2900,16 +3358,16 @@ static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
                }
           }
 
-     CASE(OP_LAMBDA1):
-         gc_disable(sc, 1);
-          s_return_enable_gc(sc, mk_closure(sc, sc->value, sc->envir));
-
 #else
      CASE(OP_LAMBDA):     /* lambda */
+         sc->value = sc->code;
+         /* Fallthrough. */
+#endif
+
+     CASE(OP_LAMBDA1):
          gc_disable(sc, 1);
-          s_return_enable_gc(sc, mk_closure(sc, sc->code, sc->envir));
+          s_return_enable_gc(sc, mk_closure(sc, sc->value, sc->envir));
 
-#endif
 
      CASE(OP_MKCLOSURE): /* make-closure */
        x=car(sc->args);
@@ -2981,18 +3439,29 @@ static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
 
 
      CASE(OP_BEGIN):      /* begin */
-          if (!is_pair(sc->code)) {
-               s_return(sc,sc->code);
-          }
-          if (cdr(sc->code) != sc->NIL) {
-               s_save(sc,OP_BEGIN, sc->NIL, cdr(sc->code));
-          }
-          sc->code = car(sc->code);
-          s_thread_to(sc,OP_EVAL);
+         {
+           int last;
+
+           if (!is_pair(sc->code)) {
+             s_return(sc,sc->code);
+           }
+
+           last = cdr(sc->code) == sc->NIL;
+           if (!last) {
+             s_save(sc,OP_BEGIN, sc->NIL, cdr(sc->code));
+           }
+           sc->code = car(sc->code);
+           if (! last)
+             /* This is not the end of the list.  This is not a tail
+              * position.  */
+             s_clear_flag(sc, TAIL_CONTEXT);
+           s_thread_to(sc,OP_EVAL);
+         }
 
      CASE(OP_IF0):        /* if */
           s_save(sc,OP_IF1, sc->NIL, cdr(sc->code));
           sc->code = car(sc->code);
+         s_clear_flag(sc, TAIL_CONTEXT);
           s_thread_to(sc,OP_EVAL);
 
      CASE(OP_IF1):        /* if */
@@ -3022,6 +3491,7 @@ static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
               gc_enable(sc);
                sc->code = cadar(sc->code);
                sc->args = sc->NIL;
+              s_clear_flag(sc, TAIL_CONTEXT);
                s_thread_to(sc,OP_EVAL);
           } else {  /* end */
               gc_enable(sc);
@@ -3070,6 +3540,7 @@ static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
           }
           s_save(sc,OP_LET1AST, cdr(sc->code), car(sc->code));
           sc->code = cadaar(sc->code);
+         s_clear_flag(sc, TAIL_CONTEXT);
           s_thread_to(sc,OP_EVAL);
 
      CASE(OP_LET1AST):    /* let* (make new frame) */
@@ -3083,6 +3554,7 @@ static pointer opexe_0(scheme *sc, enum scheme_opcodes op) {
                s_save(sc,OP_LET2AST, sc->args, sc->code);
                sc->code = cadar(sc->code);
                sc->args = sc->NIL;
+              s_clear_flag(sc, TAIL_CONTEXT);
                s_thread_to(sc,OP_EVAL);
           } else {  /* end */
                sc->code = sc->args;
@@ -3119,6 +3591,7 @@ static pointer opexe_1(scheme *sc, enum scheme_opcodes op) {
                s_save(sc,OP_LET1REC, sc->args, cdr(sc->code));
                sc->code = cadar(sc->code);
                sc->args = sc->NIL;
+              s_clear_flag(sc, TAIL_CONTEXT);
                s_goto(sc,OP_EVAL);
           } else {  /* end */
                sc->args = reverse_in_place(sc, sc->NIL, sc->args);
@@ -3141,6 +3614,7 @@ static pointer opexe_1(scheme *sc, enum scheme_opcodes op) {
           }
           s_save(sc,OP_COND1, sc->NIL, sc->code);
           sc->code = caar(sc->code);
+         s_clear_flag(sc, TAIL_CONTEXT);
           s_goto(sc,OP_EVAL);
 
      CASE(OP_COND1):      /* cond */
@@ -3165,6 +3639,7 @@ static pointer opexe_1(scheme *sc, enum scheme_opcodes op) {
                } else {
                     s_save(sc,OP_COND1, sc->NIL, sc->code);
                     sc->code = caar(sc->code);
+                   s_clear_flag(sc, TAIL_CONTEXT);
                     s_goto(sc,OP_EVAL);
                }
           }
@@ -3180,6 +3655,8 @@ static pointer opexe_1(scheme *sc, enum scheme_opcodes op) {
                s_return(sc,sc->T);
           }
           s_save(sc,OP_AND1, sc->NIL, cdr(sc->code));
+         if (cdr(sc->code) != sc->NIL)
+              s_clear_flag(sc, TAIL_CONTEXT);
           sc->code = car(sc->code);
           s_goto(sc,OP_EVAL);
 
@@ -3190,6 +3667,8 @@ static pointer opexe_1(scheme *sc, enum scheme_opcodes op) {
                s_return(sc,sc->value);
           } else {
                s_save(sc,OP_AND1, sc->NIL, cdr(sc->code));
+              if (cdr(sc->code) != sc->NIL)
+                   s_clear_flag(sc, TAIL_CONTEXT);
                sc->code = car(sc->code);
                s_goto(sc,OP_EVAL);
           }
@@ -3199,6 +3678,8 @@ static pointer opexe_1(scheme *sc, enum scheme_opcodes op) {
                s_return(sc,sc->F);
           }
           s_save(sc,OP_OR1, sc->NIL, cdr(sc->code));
+         if (cdr(sc->code) != sc->NIL)
+              s_clear_flag(sc, TAIL_CONTEXT);
           sc->code = car(sc->code);
           s_goto(sc,OP_EVAL);
 
@@ -3209,6 +3690,8 @@ static pointer opexe_1(scheme *sc, enum scheme_opcodes op) {
                s_return(sc,sc->value);
           } else {
                s_save(sc,OP_OR1, sc->NIL, cdr(sc->code));
+              if (cdr(sc->code) != sc->NIL)
+                   s_clear_flag(sc, TAIL_CONTEXT);
                sc->code = car(sc->code);
                s_goto(sc,OP_EVAL);
           }
@@ -3254,6 +3737,7 @@ static pointer opexe_1(scheme *sc, enum scheme_opcodes op) {
      CASE(OP_CASE0):      /* case */
           s_save(sc,OP_CASE1, sc->NIL, cdr(sc->code));
           sc->code = car(sc->code);
+         s_clear_flag(sc, TAIL_CONTEXT);
           s_goto(sc,OP_EVAL);
 
      CASE(OP_CASE1):      /* case */
@@ -3317,6 +3801,52 @@ static pointer opexe_1(scheme *sc, enum scheme_opcodes op) {
      return sc->T;
 }
 
+#if USE_PLIST
+static pointer
+get_property(scheme *sc, pointer obj, pointer key)
+{
+  pointer x;
+
+  assert (is_symbol(obj));
+  assert (is_symbol(key));
+
+  for (x = symprop(obj); x != sc->NIL; x = cdr(x)) {
+    if (caar(x) == key)
+      break;
+  }
+
+  if (x != sc->NIL)
+    return cdar(x);
+
+  return sc->NIL;
+}
+
+static pointer
+set_property(scheme *sc, pointer obj, pointer key, pointer value)
+{
+#define set_property_allocates 2
+  pointer x;
+
+  assert (is_symbol(obj));
+  assert (is_symbol(key));
+
+  for (x = symprop(obj); x != sc->NIL; x = cdr(x)) {
+    if (caar(x) == key)
+      break;
+  }
+
+  if (x != sc->NIL)
+    cdar(x) = value;
+  else {
+    gc_disable(sc, gc_reservations(set_property));
+    symprop(obj) = cons(sc, cons(sc, key, value), symprop(obj));
+    gc_enable(sc);
+  }
+
+  return sc->T;
+}
+#endif
+
 static pointer opexe_2(scheme *sc, enum scheme_opcodes op) {
      pointer x;
      num v;
@@ -4039,7 +4569,7 @@ static pointer opexe_4(scheme *sc, enum scheme_opcodes op) {
           }
 
      CASE(OP_REVERSE):   /* reverse */
-          s_return(sc,reverse(sc, car(sc->args)));
+          s_return(sc,reverse(sc, sc->NIL, car(sc->args)));
 
      CASE(OP_LIST_STAR): /* list* */
           s_return(sc,list_star(sc,sc->args));
@@ -4064,37 +4594,38 @@ static pointer opexe_4(scheme *sc, enum scheme_opcodes op) {
           s_return(sc, reverse_in_place(sc, car(y), x));
 
 #if USE_PLIST
-     CASE(OP_PUT):        /* put */
-          if (!hasprop(car(sc->args)) || !hasprop(cadr(sc->args))) {
-               Error_0(sc,"illegal use of put");
-          }
-          for (x = symprop(car(sc->args)), y = cadr(sc->args); x != sc->NIL; x = cdr(x)) {
-               if (caar(x) == y) {
-                    break;
-               }
-          }
-          if (x != sc->NIL)
-               cdar(x) = caddr(sc->args);
-          else
-               symprop(car(sc->args)) = cons(sc, cons(sc, y, caddr(sc->args)),
-                                symprop(car(sc->args)));
-          s_return(sc,sc->T);
+     CASE(OP_SET_SYMBOL_PROPERTY): /* set-symbol-property! */
+         gc_disable(sc, gc_reservations(set_property));
+          s_return_enable_gc(sc,
+                            set_property(sc, car(sc->args),
+                                         cadr(sc->args), caddr(sc->args)));
 
-     CASE(OP_GET):        /* get */
-          if (!hasprop(car(sc->args)) || !hasprop(cadr(sc->args))) {
-               Error_0(sc,"illegal use of get");
-          }
-          for (x = symprop(car(sc->args)), y = cadr(sc->args); x != sc->NIL; x = cdr(x)) {
-               if (caar(x) == y) {
-                    break;
-               }
-          }
-          if (x != sc->NIL) {
-               s_return(sc,cdar(x));
-          } else {
-               s_return(sc,sc->NIL);
-          }
+     CASE(OP_SYMBOL_PROPERTY):  /* symbol-property */
+         s_return(sc, get_property(sc, car(sc->args), cadr(sc->args)));
 #endif /* USE_PLIST */
+
+#if USE_TAGS
+     CASE(OP_TAG_VALUE): {      /* not exposed */
+         /* This tags sc->value with car(sc->args).  Useful to tag
+          * results of opcode evaluations.  */
+         pointer a, b, c;
+         free_cons(sc, sc->args, &a, &b);
+         free_cons(sc, b, &b, &c);
+         assert(c == sc->NIL);
+          s_return(sc, mk_tagged_value(sc, sc->value, a, b));
+       }
+
+     CASE(OP_MK_TAGGED):        /* make-tagged-value */
+         if (is_vector(car(sc->args)))
+              Error_0(sc, "cannot tag vector");
+          s_return(sc, mk_tagged_value(sc, car(sc->args),
+                                      car(cadr(sc->args)),
+                                      cdr(cadr(sc->args))));
+
+     CASE(OP_GET_TAG):        /* get-tag */
+         s_return(sc, get_tag(sc, car(sc->args)));
+#endif /* USE_TAGS */
+
      CASE(OP_QUIT):       /* quit */
           if(is_pair(sc->args)) {
                sc->retcode=ivalue(car(sc->args));
@@ -4306,6 +4837,19 @@ static pointer opexe_5(scheme *sc, enum scheme_opcodes op) {
                     Error_0(sc,"syntax error: illegal dot expression");
                } else {
                     sc->nesting_stack[sc->file_i]++;
+#if USE_TAGS && SHOW_ERROR_LINE
+                   {
+                     const char *filename =
+                       sc->load_stack[sc->file_i].rep.stdio.filename;
+                     int lineno =
+                       sc->load_stack[sc->file_i].rep.stdio.curr_line;
+
+                     s_save(sc, OP_TAG_VALUE,
+                            cons(sc, mk_string(sc, filename),
+                                 cons(sc, mk_integer(sc, lineno), sc->NIL)),
+                            sc->NIL);
+                   }
+#endif
                     s_save(sc,OP_RDLIST, sc->NIL, sc->NIL);
                     s_thread_to(sc,OP_RDSEXPR);
                }
@@ -4565,6 +5109,8 @@ static pointer opexe_6(scheme *sc, enum scheme_opcodes op) {
           s_retbool(is_closure(car(sc->args)));
      CASE(OP_MACROP):          /* macro? */
           s_retbool(is_macro(car(sc->args)));
+     CASE(OP_VM_HISTORY):          /* *vm-history* */
+          s_return(sc, history_flatten(sc));
      default:
           snprintf(sc->strbuff,STRBUFFSIZE,"%d: illegal operator", sc->op);
           Error_0(sc,sc->strbuff);
@@ -4907,6 +5453,14 @@ int scheme_init_custom_alloc(scheme *sc, func_alloc malloc, func_dealloc free) {
   sc->T = &sc->_HASHT;
   sc->F = &sc->_HASHF;
   sc->EOF_OBJ=&sc->_EOF_OBJ;
+
+#if USE_SMALL_INTEGERS
+  if (initialize_small_integers(sc)) {
+    sc->no_memory=1;
+    return 0;
+  }
+#endif
+
   sc->free_cell = &sc->_NIL;
   sc->fcells = 0;
   sc->inhibit_gc = GC_ENABLED;
@@ -4935,6 +5489,8 @@ int scheme_init_custom_alloc(scheme *sc, func_alloc malloc, func_dealloc free) {
   dump_stack_initialize(sc);
   sc->code = sc->NIL;
   sc->tracing=0;
+  sc->op = -1;
+  sc->flags = 0;
 
   /* init sc->NIL */
   typeflag(sc->NIL) = (T_NIL | T_ATOM | MARK);
@@ -4950,7 +5506,7 @@ int scheme_init_custom_alloc(scheme *sc, func_alloc malloc, func_dealloc free) {
   car(sc->EOF_OBJ) = cdr(sc->EOF_OBJ) = sc->EOF_OBJ;
   /* init sink */
   typeflag(sc->sink) = (T_SINK | T_PAIR | MARK);
-  car(sc->sink) = sc->NIL;
+  car(sc->sink) = cdr(sc->sink) = sc->NIL;
   /* init c_nest */
   sc->c_nest = sc->NIL;
 
@@ -4985,6 +5541,8 @@ int scheme_init_custom_alloc(scheme *sc, func_alloc malloc, func_dealloc free) {
     }
   }
 
+  history_init(sc, 8, 8);
+
   /* initialization of global pointers to special symbols */
   sc->LAMBDA = mk_symbol(sc, "lambda");
   sc->QUOTE = mk_symbol(sc, "quote");
@@ -5034,6 +5592,7 @@ void scheme_deinit(scheme *sc) {
   dump_stack_free(sc);
   sc->envir=sc->NIL;
   sc->code=sc->NIL;
+  history_free(sc);
   sc->args=sc->NIL;
   sc->value=sc->NIL;
   if(is_port(sc->inport)) {
@@ -5052,6 +5611,10 @@ void scheme_deinit(scheme *sc) {
   sc->gc_verbose=0;
   gc(sc,sc->NIL,sc->NIL);
 
+#if USE_SMALL_INTEGERS
+  sc->free(sc->integer_alloc);
+#endif
+
   for(i=0; i<=sc->last_cell_seg; i++) {
     sc->free(sc->alloc_seg[i]);
   }
index 8e93177..8560f7d 100644 (file)
@@ -43,6 +43,9 @@ extern "C" {
 # define USE_COMPILE_HOOK 0
 # define USE_DL 0
 # define USE_PLIST 0
+# define USE_SMALL_INTEGERS 0
+# define USE_TAGS 0
+# define USE_HISTORY 0
 #endif
 
 
@@ -75,6 +78,17 @@ extern "C" {
 # define USE_PLIST 0
 #endif
 
+/* If set, then every object can be tagged.  */
+#ifndef USE_TAGS
+# define USE_TAGS 1
+#endif
+
+/* Keep a history of function calls.  This enables a feature similar
+ * to stack traces.  */
+#ifndef USE_HISTORY
+# define USE_HISTORY 1
+#endif
+
 /* To force system errors through user-defined error handling (see *error-hook*) */
 #ifndef USE_ERROR_HOOK
 # define USE_ERROR_HOOK 1
@@ -95,6 +109,13 @@ extern "C" {
 # define USE_THREADED_CODE 1
 #endif
 
+/* Use a static set of cells to represent small numbers.  This set
+ * notably includes all opcodes, and hence saves a cell reservation
+ * during 's_save'.  */
+#ifndef USE_SMALL_INTEGERS
+# define USE_SMALL_INTEGERS 1
+#endif
+
 #ifndef USE_STRCASECMP   /* stricmp for Unix */
 # define USE_STRCASECMP 0
 #endif
index 72afa99..7b8d489 100644 (file)
@@ -42,7 +42,7 @@
   (if (> (*verbose*) 0)
       (apply info msg)))
 
-(define (error . msg)
+(define (fail . msg)
   (apply info msg)
   (exit 1))
 
        (flush-stdio)))
   (set! *progress-nesting* (- *progress-nesting* 1)))
 
-(define (for-each-p msg proc lst)
-  (for-each-p' msg proc (lambda (x) x) lst))
+(define (for-each-p msg proc lst . lsts)
+  (apply for-each-p' `(,msg ,proc ,(lambda (x . xs) x) ,lst ,@lsts)))
 
-(define (for-each-p' msg proc fmt lst)
+(define (for-each-p' msg proc fmt lst . lsts)
   (call-with-progress
    msg
    (lambda (progress)
-     (for-each (lambda (a)
-                (progress (fmt a))
-                (proc a))
-              lst))))
+     (apply for-each
+           `(,(lambda args
+                (progress (apply fmt args))
+                (apply proc args))
+             ,lst ,@lsts)))))
 
 ;; Process management.
 (define CLOSED_FD -1)
   (let ((result (call-with-io what "")))
     (if (= 0 (:retcode result))
        (:stdout result)
-       (throw (list what "failed:" (:stderr result))))))
+       (throw (string-append (stringify what) " failed")
+              (:stderr result)))))
 
 (define (call-popen command input-string)
   (let ((result (call-with-io command input-string)))
 (assert (string=? (path-join "foo" "bar" "baz") "foo/bar/baz"))
 (assert (string=? (path-join "" "bar" "baz") "bar/baz"))
 
+;; Is PATH an absolute path?
+(define (absolute-path? path)
+  (or (char=? #\/ (string-ref path 0))
+      (and *win32* (char=? #\\ (string-ref path 0)))
+      (and *win32*
+          (char-alphabetic? (string-ref path 0))
+          (char=? #\: (string-ref path 1))
+          (or (char=? #\/ (string-ref path 2))
+              (char=? #\\ (string-ref path 2))))))
+
+;; Make PATH absolute.
 (define (canonical-path path)
-  (if (or (char=? #\/ (string-ref path 0))
-         (and *win32* (char=? #\\ (string-ref path 0)))
-         (and *win32*
-              (char-alphabetic? (string-ref path 0))
-              (char=? #\: (string-ref path 1))
-              (or (char=? #\/ (string-ref path 2))
-                  (char=? #\\ (string-ref path 2)))))
-      path
-      (path-join (getcwd) path)))
+  (if (absolute-path? path) path (path-join (getcwd) path)))
 
 (define (in-srcdir . names)
   (canonical-path (apply path-join (cons (getenv "srcdir") names))))
        (let ((,result-sym
               ,(if (= 1 (length (cadr form)))
                    `(catch (begin (close ,(caaadr form))
-                                  (apply throw *error*))
+                                  (rethrow *error*))
                            ,@(cddr form))
                    `(letfd ,(cdadr form) ,@(cddr form)))))
          (close ,(caaadr form))
     `(let* ((,cwd-sym (getcwd))
            (_ (if ,(cadr form) (chdir ,(cadr form))))
            (,result-sym (catch (begin (chdir ,cwd-sym)
-                                      (apply throw *error*))
+                                      (rethrow *error*))
                                ,@(cddr form))))
        (chdir ,cwd-sym)
        ,result-sym)))
            (_ (chdir ,tmp-sym))
            (,result-sym (catch (begin (chdir ,cwd-sym)
                                       (unlink-recursively ,tmp-sym)
-                                      (apply throw *error*))
+                                      (rethrow *error*))
                                ,@(cdr form))))
        (chdir ,cwd-sym)
        (unlink-recursively ,tmp-sym)
        (let ((,result-sym
               ,(if (= 1 (length (cadr form)))
                    `(catch (begin (remove-temporary-file ,(caadr form))
-                                  (apply throw *error*))
+                                  (rethrow *error*))
                            ,@(cddr form))
                    `(lettmp ,(cdadr form) ,@(cddr form)))))
          (remove-temporary-file ,(caadr form))
   (lettmp (sink)
          (transformer source sink)
          (if (not (file=? source sink))
-             (error "mismatch"))))
+             (fail "mismatch"))))
 
 ;;
 ;; Monadic pipe support.
 (define (tr:spawn input command)
   (lambda (tmpfiles source)
     (if (and (member '**in** command) (not source))
-       (error (string-append (stringify cmd) " needs an input")))
+       (fail (string-append (stringify cmd) " needs an input")))
     (let* ((t (make-temporary-file))
           (cmd (map (lambda (x)
                       (cond
       (catch (list (cons t tmpfiles) t *error*)
             (call-popen cmd input)
             (if (and (member '**out** command) (not (file-exists? t)))
-                (error (string-append (stringify cmd)
+                (fail (string-append (stringify cmd)
                                       " did not produce '" t "'.")))
             (list (cons t tmpfiles) t #f)))))
 
 (define (tr:assert-identity reference)
   (lambda (tmpfiles source)
     (if (not (file=? source reference))
-       (error "mismatch"))
+       (fail "mismatch"))
     (list tmpfiles source #f)))
 
 (define (tr:assert-weak-identity reference)
   (lambda (tmpfiles source)
     (if (not (text-file=? source reference))
-       (error "mismatch"))
+       (fail "mismatch"))
     (list tmpfiles source #f)))
 
 (define (tr:call-with-content function . args)
 
 ;; Spawn an os shell.
 (define (interactive-shell)
-  (call-with-fds `(,(getenv "SHELL")) 0 1 2))
+  (call-with-fds `(,(getenv "SHELL") -i) 0 1 2))
+
+;;
+;; The main test framework.
+;;
+
+;; A pool of tests.
+(define test-pool
+  (package
+   (define (new procs)
+     (package
+      (define (add test)
+       (new (cons test procs)))
+      (define (wait)
+       (let ((unfinished (filter (lambda (t) (not t::retcode)) procs)))
+         (if (null? unfinished)
+             (package)
+             (let* ((names (map (lambda (t) t::name) unfinished))
+                    (pids (map (lambda (t) t::pid) unfinished))
+                    (results
+                     (map (lambda (pid retcode) (list pid retcode))
+                          pids
+                          (wait-processes (map stringify names) pids #t))))
+               (new
+                (map (lambda (t)
+                       (if t::retcode
+                           t
+                           (t::set-retcode (cadr (assoc t::pid results)))))
+                     procs))))))
+      (define (passed)
+       (filter (lambda (p) (= 0 p::retcode)) procs))
+      (define (skipped)
+       (filter (lambda (p) (= 77 p::retcode)) procs))
+      (define (hard-errored)
+       (filter (lambda (p) (= 99 p::retcode)) procs))
+      (define (failed)
+       (filter (lambda (p)
+                 (not (or (= 0 p::retcode) (= 77 p::retcode)
+                          (= 99 p::retcode))))
+               procs))
+      (define (report)
+       (define (print-tests tests message)
+         (unless (null? tests)
+                 (apply echo (cons message
+                                   (map (lambda (t) t::name) tests)))))
+
+       (let ((failed' (failed)) (skipped' (skipped)))
+         (echo (length procs) "tests run,"
+               (length (passed)) "succeeded,"
+               (length failed') "failed,"
+               (length skipped') "skipped.")
+         (print-tests failed' "Failed tests:")
+         (print-tests skipped' "Skipped tests:")
+         (length failed')))))))
+
+(define (verbosity n)
+  (if (= 0 n) '() (cons '--verbose (verbosity (- n 1)))))
+
+(define (locate-test path)
+  (if (absolute-path? path) path (in-srcdir path)))
+
+;; A single test.
+(define test
+  (package
+   (define (scm name path . args)
+     ;; Start the process.
+     (define (spawn-scm args' in out err)
+       (spawn-process-fd `(,*argv0* ,@(verbosity (*verbose*))
+                                   ,(locate-test path)
+                                   ,@args' ,@args) in out err))
+     (new name #f spawn-scm #f #f CLOSED_FD))
+
+   (define (binary name path . args)
+     ;; Start the process.
+     (define (spawn-binary args' in out err)
+       (spawn-process-fd `(,path ,@args' ,@args) in out err))
+     (new name #f spawn-binary #f #f CLOSED_FD))
+
+   (define (new name directory spawn pid retcode logfd)
+     (package
+      (define (set-directory x)
+       (new name x spawn pid retcode logfd))
+      (define (set-retcode x)
+       (new name directory spawn pid x logfd))
+      (define (set-pid x)
+       (new name directory spawn x retcode logfd))
+      (define (set-logfd x)
+       (new name directory spawn pid retcode x))
+      (define (open-log-file)
+       (let ((filename (string-append (basename name) ".log")))
+         (catch '() (unlink filename))
+         (open filename (logior O_RDWR O_BINARY O_CREAT) #o600)))
+      (define (run-sync . args)
+       (letfd ((log (open-log-file)))
+         (with-working-directory directory
+           (let* ((p (inbound-pipe))
+                  (pid (spawn args 0 (:write-end p) (:write-end p))))
+             (close (:write-end p))
+             (splice (:read-end p) STDERR_FILENO log)
+             (close (:read-end p))
+             (let ((t' (set-retcode (wait-process name pid #t))))
+               (t'::report)
+               t')))))
+      (define (run-sync-quiet . args)
+       (with-working-directory directory
+         (set-retcode
+          (wait-process
+           name (spawn args CLOSED_FD CLOSED_FD CLOSED_FD) #t))))
+      (define (run-async . args)
+       (let ((log (open-log-file)))
+         (with-working-directory directory
+           (new name directory spawn
+                (spawn args CLOSED_FD log log)
+                retcode log))))
+      (define (status)
+       (let ((t (assoc retcode '((0 "PASS") (77 "SKIP") (99 "ERROR")))))
+         (if (not t) "FAIL" (cadr t))))
+      (define (report)
+       (unless (= logfd CLOSED_FD)
+               (seek logfd 0 SEEK_SET)
+               (splice logfd STDERR_FILENO)
+               (close logfd))
+       (echo (string-append (status retcode) ":") name))))))
+
+;; Run the setup target to create an environment, then run all given
+;; tests in parallel.
+(define (run-tests-parallel setup tests)
+  (lettmp (gpghome-tar)
+    (setup::run-sync '--create-tarball gpghome-tar)
+    (let loop ((pool (test-pool::new '())) (tests' tests))
+      (if (null? tests')
+         (let ((results (pool::wait)))
+           (for-each (lambda (t)
+                       (catch (echo "Removing" t::directory "failed:" *error*)
+                              (unlink-recursively t::directory))
+                       (t::report)) (reverse results::procs))
+           (exit (results::report)))
+         (let* ((wd (mkdtemp))
+                (test (car tests'))
+                (test' (test::set-directory wd)))
+           (loop (pool::add (test'::run-async '--unpack-tarball gpghome-tar))
+                 (cdr tests')))))))
+
+;; Run the setup target to create an environment, then run all given
+;; tests in sequence.
+(define (run-tests-sequential setup tests)
+  (lettmp (gpghome-tar)
+    (setup::run-sync '--create-tarball gpghome-tar)
+    (let loop ((pool (test-pool::new '())) (tests' tests))
+      (if (null? tests')
+         (let ((results (pool::wait)))
+           (for-each (lambda (t)
+                       (catch (echo "Removing" t::directory "failed:" *error*)
+                              (unlink-recursively t::directory)))
+                     results::procs)
+           (exit (results::report)))
+         (let* ((wd (mkdtemp))
+                (test (car tests'))
+                (test' (test::set-directory wd)))
+           (loop (pool::add (test'::run-sync '--unpack-tarball gpghome-tar))
+                 (cdr tests')))))))
index 2773969..d0cd9ee 100644 (file)
@@ -20,7 +20,8 @@
 
 # Programs required before we can run these tests.
 required_pgms = ../../g10/gpg$(EXEEXT) ../../agent/gpg-agent$(EXEEXT) \
-       ../../tools/gpgtar$(EXEEXT)
+       ../../tools/gpgtar$(EXEEXT) \
+       ../gpgscm/gpgscm$(EXEEXT)
 
 AM_CPPFLAGS = -I$(top_srcdir)/common
 include $(top_srcdir)/am/cmacros.am
@@ -30,11 +31,14 @@ AM_CFLAGS =
 TMP ?= /tmp
 
 TESTS_ENVIRONMENT = GPG_AGENT_INFO= LC_ALL=C \
+       EXEEXT=$(EXEEXT) \
        PATH=../gpgscm:$(PATH) \
        TMP=$(TMP) \
-       GPGSCM_PATH=$(top_srcdir)/tests/gpgscm:$(top_srcdir)/tests/migrations
+       srcdir=$(abs_srcdir) \
+       objdir=$(abs_top_builddir) \
+       GPGSCM_PATH=$(abs_top_srcdir)/tests/gpgscm:$(abs_top_srcdir)/tests/migrations
 
-TESTS = from-classic.scm \
+XTESTS = from-classic.scm \
        extended-pkf.scm \
        issue2276.scm
 
@@ -42,17 +46,22 @@ TEST_FILES = from-classic.tar.asc \
             extended-pkf.tar.asc \
             issue2276.tar.asc
 
-EXTRA_DIST = common.scm $(TESTS) $(TEST_FILES)
+# XXX: Currently, one cannot override automake's 'check' target.  As a
+# workaround, we avoid defining 'TESTS', thus automake will not emit
+# the 'check' target.  For extra robustness, we merely define a
+# dependency on 'xcheck', so this hack should also work even if
+# automake would emit the 'check' target, as adding dependencies to
+# targets is okay.
+check: xcheck
 
-CLEANFILES = prepared.stamp x y yy z out err  $(data_files) \
-            plain-1 plain-2 plain-3 trustdb.gpg *.lock .\#lk* \
-            *.test.log gpg_dearmor gpg.conf gpg-agent.conf S.gpg-agent \
-            pubring.gpg pubring.gpg~ pubring.kbx pubring.kbx~ \
-            secring.gpg pubring.pkr secring.skr \
-            gnupg-test.stop random_seed gpg-agent.log tofu.db
+.PHONY: xcheck
+xcheck:
+       $(TESTS_ENVIRONMENT) $(abs_top_builddir)/tests/gpgscm/gpgscm \
+         run-tests.scm $(TESTFLAGS) $(XTESTS)
 
-clean-local:
-       -rm -rf from-classic.gpghome/*.gpg
+EXTRA_DIST = common.scm run-tests.scm setup.scm $(XTESTS) $(TEST_FILES)
+
+CLEANFILES = *.log
 
 # We need to depend on a couple of programs so that the tests don't
 # start before all programs are built.
index 944d4f6..30ac62b 100644 (file)
 (if (string=? "" (getenv "srcdir"))
     (error "not called from make"))
 
-(setenv "GNUPGHOME" "" #t)
+(let ((verbose (string->number (getenv "verbose"))))
+  (if (number? verbose)
+      (*set-verbose!* verbose)))
 
 (define (qualify executable)
   (string-append executable (getenv "EXEEXT")))
 
 ;; We may not use a relative name for gpg-agent.
-(define GPG-AGENT (qualify (string-append (getcwd) "/../../agent/gpg-agent")))
-(define GPG `(,(qualify (string-append (getcwd) "/../../g10/gpg"))
+(define GPG-AGENT (path-join (getenv "objdir") "agent" (qualify "gpg-agent")))
+(define GPG `(,(path-join (getenv "objdir") "g10" (qualify "gpg"))
              --no-permission-warning --no-greeting
              --no-secmem-warning --batch
              ,(string-append "--agent-program=" GPG-AGENT
@@ -33,7 +35,7 @@
 (define GPG-no-batch
   (filter (lambda (arg) (not (equal? arg '--batch))) GPG))
 
-(define GPGTAR (qualify (string-append (getcwd) "/../../tools/gpgtar")))
+(define GPGTAR (path-join (getenv "objdir") "tools" (qualify "gpgtar")))
 
 (define (untar-armored source-name)
   (pipe:do
diff --git a/tests/migrations/run-tests.scm b/tests/migrations/run-tests.scm
new file mode 100644 (file)
index 0000000..069af5b
--- /dev/null
@@ -0,0 +1,26 @@
+;; Test-suite runner.
+;;
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(let* ((tests (filter (lambda (arg) (not (string-prefix? arg "--"))) *args*))
+       (runner (if (and (member "--parallel" *args*)
+                       (> (length tests) 1))
+                  run-tests-parallel
+                  run-tests-sequential)))
+  (runner (test::scm "setup.scm" "setup.scm")
+         (map (lambda (t) (test::scm t t)) tests)))
diff --git a/tests/migrations/setup.scm b/tests/migrations/setup.scm
new file mode 100644 (file)
index 0000000..76a5840
--- /dev/null
@@ -0,0 +1,20 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+;; Nothing to do for now.
index 9c387af..b827181 100755 (executable)
@@ -25,4 +25,4 @@
 
 (if (= 0 (call `(,@GPG --list-packets ,(in-srcdir "4gb-packet.asc"))))
   (info "Can parse 4GB packets.")
-  (error "Failed to parse 4GB packet."))
+  (fail "Failed to parse 4GB packet."))
index 8f565b3..0670531 100644 (file)
@@ -45,13 +45,17 @@ TESTS_ENVIRONMENT = LC_ALL=C \
 
 XTESTS = \
        version.scm \
+       enarmor.scm \
        mds.scm \
        decrypt.scm \
+       decrypt-multifile.scm \
        decrypt-dsa.scm \
        sigs.scm \
        sigs-dsa.scm \
        encrypt.scm \
+       encrypt-multifile.scm \
        encrypt-dsa.scm \
+       compression.scm \
        seat.scm \
        clearsig.scm \
        encryptp.scm \
@@ -70,9 +74,11 @@ XTESTS = \
        conventional-mdc.scm \
        multisig.scm \
        verify.scm \
+       verify-multifile.scm \
        gpgv-forged-keyring.scm \
        armor.scm \
        import.scm \
+       import-revocation-certificate.scm \
        ecc.scm \
        4gb-packet.scm \
        tofu.scm \
@@ -80,9 +86,11 @@ XTESTS = \
        use-exact-key.scm \
        default-key.scm \
        export.scm \
-       ssh.scm \
+       ssh-import.scm \
+       ssh-export.scm \
        quick-key-manipulation.scm \
        key-selection.scm \
+       delete-keys.scm \
        issue2015.scm \
        issue2346.scm \
        issue2417.scm \
@@ -108,8 +116,27 @@ TEST_FILES = pubring.asc secring.asc plain-1o.asc plain-2o.asc plain-3o.asc \
              gpg.conf.tmpl gpg-agent.conf.tmpl \
             bug537-test.data.asc bug894-test.asc \
             bug1223-good.asc bug1223-bogus.asc 4gb-packet.asc \
-            tofu-keys.asc tofu-keys-secret.asc \
-            tofu-2183839A-1.txt tofu-BC15C85A-1.txt tofu-EE37CF96-1.txt \
+            tofu/conflicting/1C005AF3.gpg \
+            tofu/conflicting/1C005AF3-secret.gpg \
+            tofu/conflicting/1C005AF3-1.txt \
+            tofu/conflicting/1C005AF3-2.txt \
+            tofu/conflicting/1C005AF3-3.txt \
+            tofu/conflicting/1C005AF3-4.txt \
+            tofu/conflicting/1C005AF3-5.txt \
+            tofu/conflicting/B662E42F.gpg \
+            tofu/conflicting/B662E42F-secret.gpg \
+            tofu/conflicting/B662E42F-1.txt \
+            tofu/conflicting/B662E42F-2.txt \
+            tofu/conflicting/B662E42F-3.txt \
+            tofu/conflicting/B662E42F-4.txt \
+            tofu/conflicting/B662E42F-5.txt \
+            tofu/conflicting/BE04EB2B.gpg \
+            tofu/conflicting/BE04EB2B-secret.gpg \
+            tofu/conflicting/BE04EB2B-1.txt \
+            tofu/conflicting/BE04EB2B-2.txt \
+            tofu/conflicting/BE04EB2B-3.txt \
+            tofu/conflicting/BE04EB2B-4.txt \
+            tofu/conflicting/BE04EB2B-5.txt \
             tofu/cross-sigs/EC38277E-secret.gpg \
             tofu/cross-sigs/EC38277E-1.gpg \
             tofu/cross-sigs/EC38277E-1.txt \
@@ -186,9 +213,14 @@ sample_keys = samplekeys/README \
              samplekeys/ssh-ecdsa.key \
              samplekeys/ssh-ed25519.key \
              samplekeys/ssh-rsa.key \
-             samplekeys/issue2346.gpg
-
-sample_msgs = samplemsgs/issue2419.asc
+             samplekeys/issue2346.gpg \
+             samplekeys/authenticate-only.pub.asc \
+             samplekeys/authenticate-only.sec.asc
+
+sample_msgs = samplemsgs/issue2419.asc \
+             samplemsgs/clearsig-1-key-1.asc \
+             samplemsgs/signed-1-key-1.asc \
+             samplemsgs/revoke-2D727CC768697734.asc
 
 EXTRA_DIST = defs.scm $(XTESTS) $(TEST_FILES) \
             mkdemodirs signdemokey $(priv_keys) $(sample_keys)   \
index 7b95561..cdbf603 100755 (executable)
@@ -37,7 +37,7 @@
        ;; Otherwise, we do check that we recover the original file.
        check-identity)
     source
-    (check-signing '(--passphrase-fd "0" --clearsign) usrpass1)))
+    (check-signing '(--passphrase-fd "0" --clear-sign) usrpass1)))
  (append plain-files '("plain-large")))
 
 ;; The test vectors are lists of length three, containing
@@ -102,7 +102,7 @@ there is a blank line after this
        (with-output-to-file tmp (lambda () (display (:string vec))))
        ((if (:check-equality vec) check-identity check-execution)
        tmp
-       (check-signing `(--passphrase-fd "0" --clearsign ,@(:options vec))
+       (check-signing `(--passphrase-fd "0" --clear-sign ,@(:options vec))
                       usrpass1))))
    (lambda (vec) (counter))
    vectors))
diff --git a/tests/openpgp/compression.scm b/tests/openpgp/compression.scm
new file mode 100755 (executable)
index 0000000..f39c132
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "defs.scm"))
+(setup-legacy-environment)
+
+(for-each-p
+ "Checking encryption using a specific compression algorithm"
+ (lambda (compression)
+   (for-each-p
+    ""
+    (lambda (source)
+      (tr:do
+       (tr:open source)
+       (tr:gpg "" `(--yes --encrypt --recipient ,usrname2
+                         --compress-algo ,compression))
+       (tr:gpg "" '(--yes))
+       (tr:assert-identity source)))
+    (append plain-files data-files)))
+ (force all-compression-algos))
index fb92217..5b009ae 100755 (executable)
@@ -37,7 +37,7 @@
        (tr:gpg passphrase `(--yes --passphrase-fd "0" ,s2k))
        (tr:assert-identity source)))
     '("plain-1" "data-80000")))
all-cipher-algos)
(force all-cipher-algos))
 
 (for-each-p
  "Checking sign+symencrypt"
index af889dc..612b992 100755 (executable)
@@ -46,4 +46,4 @@
        (tr:gpg passphrase `(--yes --passphrase-fd "0" ,s2k))
        (tr:assert-identity source)))
     '("plain-1" "data-80000")))
all-cipher-algos)
(force all-cipher-algos))
diff --git a/tests/openpgp/decrypt-multifile.scm b/tests/openpgp/decrypt-multifile.scm
new file mode 100755 (executable)
index 0000000..a7695b1
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "defs.scm"))
+(setup-legacy-environment)
+
+(info "Checking decryption of supplied files using --multifile.")
+
+(define my-wd (getcwd))
+(define encrypted-files (map (lambda (name)
+                              (string-append name ".asc"))
+                            plain-files))
+
+(with-temporary-working-directory
+ ;; First, copy the files so that GnuPG writes the decrypted files here
+ ;; and not into the source directory.
+ (for-each (lambda (name)
+            (file-copy (in-srcdir name) name))
+          encrypted-files)
+
+ ;; Now decrypt all files.
+ (call-check `(,@gpg --decrypt --multifile ,@encrypted-files))
+
+ ;; And verify the result.  Reference files are in our original
+ ;; working directory courtesy of setup-legacy-environment.
+ (for-each-p
+  "Verifying files:"
+  (lambda (name)
+    (unless (file=? (path-join my-wd name) name)
+           (fail "decrypted file differs")))
+  plain-files))
index e91902c..7867080 100644 (file)
 ;; first and then search for the encryption subkey.)
 (define dsa-usrname2 "0xCB879DE9")
 
+(define keys
+  (package
+   (define (new fpr grip uids subkeys)
+     (package))
+   (define (subkey fpr grip)
+     (package))
+   (define alfa (new "A0FF4590BB6122EDEF6E3C542D727CC768697734"
+                    "76F7E2B35832976B50A27A282D9B87E44577EB66"
+                    '("alfa@example.net" "alpha@example.net")
+                    (list
+                     (subkey "3B3FBC948FE59301ED629EFB6AE6D7EE46A871F8"
+                             "A0747D5F9425E6664F4FFBEED20FBCA79FDED2BD"))))
+   (define one (new "289B0EF1D105E124B6F626020EF77096D74C5F22"
+                   "50B2D4FA4122C212611048BC5FC31BD44393626E"
+                   '("one@example.com")
+                   (list
+                    (subkey "EB467DCA4AD7676A6A62B2ABABAB28A247BE2775"
+                            "7E201E28B6FEB2927B321F443205F4724EBE637E"))))
+   (define two (new "C1DEBB34EA8B71009EAFA474973D50E1C40FDECF"
+                   "343D8AF79796EE107D645A2787A9D9252F924E6F"
+                   '("two@example.com")
+                   (list
+                    (subkey "CD3D0F5701CBFCACB2A4907305A37887B27907AA"
+                            "8B5ABF3EF9EB8D96B91A0B8C2C4401C91C834C34"))))))
+
 (define key-file1 "samplekeys/rsa-rsa-sample-1.asc")
 (define key-file2 "samplekeys/ed25519-cv25519-sample-1.asc")
 
 (define data-files '("data-500" "data-9000" "data-32000" "data-80000"))
 (define exp-files '())
 
+(let ((verbose (string->number (getenv "verbose"))))
+  (if (number? verbose)
+      (*set-verbose!* verbose)))
+
 (define (qualify executable)
   (string-append executable (getenv "EXEEXT")))
 
@@ -52,9 +81,7 @@
        value)))
 
 (define tools
-  '((gpg "GPG" "g10/gpg")
-    (gpgv "GPGV" "g10/gpgv")
-    (gpg-agent "GPG_AGENT" "agent/gpg-agent")
+  '((gpgv "GPGV" "g10/gpgv")
     (gpg-connect-agent "GPG_CONNECT_AGENT" "tools/gpg-connect-agent")
     (gpgconf "GPGCONF" "tools/gpgconf")
     (gpg-preset-passphrase "GPG_PRESET_PASSPHRASE"
@@ -63,7 +90,7 @@
     (gpg-zip "GPGZIP" "tools/gpg-zip")
     (pinentry "PINENTRY" "tests/openpgp/fake-pinentry")))
 
-(define (tool which)
+(define (tool-hardcoded which)
   (let ((t (assoc which tools))
        (prefix (getenv "BIN_PREFIX")))
     (getenv' (cadr t)
                          (string-append (getenv "objdir") "/" (caddr t))
                          (string-append prefix "/" (basename (caddr t))))))))
 
+(define (gpg-conf . args)
+  (let ((s (call-popen `(,(tool-hardcoded 'gpgconf) ,@args) "")))
+    (map (lambda (line) (string-split line #\:))
+        (string-split-newlines s))))
+(define :gc:c:name car)
+(define :gc:c:description cadr)
+(define :gc:c:pgmname caddr)
 
-(define have-opt-always-trust
+(setenv "GNUPG_BUILDDIR" (getenv "objdir") #t)
+(define gpg-components (gpg-conf '--build-prefix (getenv "objdir")
+                                '--list-components))
+
+(define (tool which)
+  (case which
+    ((gpg gpg-agent scdaemon gpgsm dirmngr)
+     (:gc:c:pgmname (assoc (symbol->string which) gpg-components)))
+    (else
+     (tool-hardcoded which))))
+
+(define (gpg-has-option? option)
   (string-contains? (call-popen `(,(tool 'gpg) --dump-options) "")
-                       "--always-trust"))
+                   option))
+
+(define have-opt-always-trust
+  (catch #f
+        (call-check `(,(tool 'gpg) --gpgconf-test --always-trust))
+        #t))
 
 (define GPG `(,(tool 'gpg) --no-permission-warning
              ,@(if have-opt-always-trust '(--always-trust) '())))
     (map (lambda (line) (string-split line #\:))
         (string-split-newlines s))))
 
+;; Convenient accessors for the colon output.
+(define (:type x)   (string->symbol (list-ref x 0)))
+(define (:length x) (string->number (list-ref x 2)))
+(define (:alg x) (string->number (list-ref x 3)))
+(define (:expire x) (list-ref x 6))
+(define (:fpr x) (list-ref x 9))
+(define (:cap x) (list-ref x 11))
+
+(define (have-public-key? key)
+  (catch #f
+        (pair? (filter (lambda (l) (and (equal? 'fpr (:type l))
+                                        (equal? key::fpr (:fpr l))))
+                       (gpg-with-colons `(--list-keys ,key::fpr))))))
+
+(define (have-secret-key? key)
+  (catch #f
+        (pair? (filter (lambda (l) (and (equal? 'fpr (:type l))
+                                        (equal? key::fpr (:fpr l))))
+                       (gpg-with-colons `(--list-secret-keys ,key::fpr))))))
+
+(define (have-secret-key-file? key)
+  (file-exists? (path-join (getenv "GNUPGHOME") "private-keys-v1.d"
+                          (string-append key::grip ".key"))))
+
 (define (get-config what)
   (string-split (caddar (gpg-with-colons `(--list-config ,what))) #\;))
 
-(define all-pubkey-algos (get-config "pubkeyname"))
-(define all-hash-algos (get-config "digestname"))
-(define all-cipher-algos (get-config "ciphername"))
+(define all-pubkey-algos (delay (get-config "pubkeyname")))
+(define all-hash-algos (delay (get-config "digestname")))
+(define all-cipher-algos (delay (get-config "ciphername")))
+(define all-compression-algos (delay (get-config "compressname")))
 
 (define (have-pubkey-algo? x)
-  (not (not (member x all-pubkey-algos))))
+  (not (not (member x (force all-pubkey-algos)))))
 (define (have-hash-algo? x)
-  (not (not (member x all-hash-algos))))
+  (not (not (member x (force all-hash-algos)))))
 (define (have-cipher-algo? x)
-  (not (not (member x all-cipher-algos))))
+  (not (not (member x (force all-cipher-algos)))))
 
 (define (gpg-pipe args0 args1 errfd)
   (lambda (source sink)
    (pipe:spawn `(,@GPG --dearmor))
    (pipe:write-to sink-name (logior O_WRONLY O_CREAT O_BINARY) #o600)))
 
-(let ((verbose (string->number (getenv "verbose"))))
-  (if (number? verbose)
-      (*set-verbose!* verbose)))
-
 ;;
 ;; Support for test environment creation and teardown.
 ;;
diff --git a/tests/openpgp/delete-keys.scm b/tests/openpgp/delete-keys.scm
new file mode 100755 (executable)
index 0000000..9a187a2
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "defs.scm"))
+(setup-legacy-environment)
+
+(let* ((key keys::alfa)
+      (subkey (car key::subkeys)))
+  (assert (have-public-key? key))
+  (assert (have-public-key? subkey))
+  (assert (have-secret-key? key))
+  (assert (have-secret-key-file? key))
+  (assert (have-secret-key? subkey))
+  (assert (have-secret-key-file? subkey))
+
+  ;; Firstly, delete the secret key.
+  (call-check `(,@gpg --delete-secret-keys ,key::fpr))
+  (assert (have-public-key? key))
+  (assert (have-public-key? subkey))
+  (assert (not (have-secret-key? key)))
+  (assert (not (have-secret-key-file? key)))
+  (assert (not (have-secret-key? subkey)))
+  (assert (not (have-secret-key-file? subkey)))
+
+  ;; Now, delete the public key.
+  (call-check `(,@gpg --delete-keys ,key::fpr))
+  (assert (not (have-public-key? key)))
+  (assert (not (have-public-key? subkey))))
+
+;; Do the same for key one, but do the subkeys separately.
+(let* ((key keys::one)
+       (subkey (car key::subkeys)))
+  (assert (have-public-key? key))
+  (assert (have-public-key? subkey))
+  (assert (have-secret-key? key))
+  (assert (have-secret-key-file? key))
+  (assert (have-secret-key-file? key))
+  (assert (have-secret-key? subkey))
+  (assert (have-secret-key-file? subkey))
+
+  ;; Firstly, delete the secret subkey.
+  (call-check `(,@gpg --delete-secret-keys ,subkey::fpr))
+  (assert (have-public-key? key))
+  (assert (have-public-key? subkey))
+  ;; JW: Deleting the secret subkey also deletes the secret key.
+  ;; XXX (assert (have-secret-key? key))
+  ;; XXX (assert (have-secret-key-file? key))
+  (assert (not (have-secret-key? subkey)))
+  (assert (not (have-secret-key-file? subkey)))
+
+  ;; Then, delete the secret key.
+  ;; XXX (call-check `(,@gpg --delete-secret-keys ,key::fpr))
+  (assert (have-public-key? key))
+  (assert (have-public-key? subkey))
+  (assert (not (have-secret-key? key)))
+  (assert (not (have-secret-key-file? key)))
+  (assert (not (have-secret-key? subkey)))
+  (assert (not (have-secret-key-file? subkey)))
+
+  ;; Now, delete the public subkey.
+  (call-check `(,@gpg --delete-keys ,subkey::fpr))
+  ;; JW: Deleting the subkey also deletes the key.
+  ;; XXX (assert (have-public-key? key))
+  (assert (not (have-public-key? subkey)))
+
+  ;; Now, delete the public key.
+  ;; XXX (call-check `(,@gpg --delete-keys ,key::fpr))
+  (assert (not (have-public-key? key)))
+  (assert (not (have-public-key? subkey))))
+
+(let* ((key keys::two)
+      (subkey (car key::subkeys)))
+  (assert (have-public-key? key))
+  (assert (have-public-key? subkey))
+  (assert (have-secret-key? key))
+  (assert (have-secret-key-file? key))
+  (assert (have-secret-key? subkey))
+  (assert (have-secret-key-file? subkey))
+
+  ;; Delete everything at once.
+  (call-check `(,@gpg --delete-secret-and-public-key ,key::fpr))
+  (assert (not (have-public-key? key)))
+  (assert (not (have-public-key? subkey)))
+  (assert (not (have-secret-key? key)))
+  (assert (not (have-secret-key-file? key)))
+  (assert (not (have-secret-key? subkey)))
+  (assert (not (have-secret-key-file? subkey))))
index 2190b9b..a40869d 100755 (executable)
@@ -103,7 +103,7 @@ Ic1RdzgeCfosMF+l/zVRchcLKzenEQA=
           x (lambda (p) (display (eval test (current-environment)) p)))
        (call-check `(,(tool 'gpg) --verify ,x))
        (call-check `(,(tool 'gpg) --output ,y ,x))
-       (unless (file=? y z) (error "mismatch"))))
+       (unless (file=? y z) (fail "mismatch"))))
    '(msg_opaque_signed_256 msg_opaque_signed_384 msg_opaque_signed_521)))
 
 ;;
@@ -182,7 +182,7 @@ Rg==
        (call-with-output-file
           x (lambda (p) (display (eval test (current-environment)) p)))
        (call-check `(,@GPG --yes --output ,y ,x))
-       (unless (file=? y z) (error "mismatch"))))
+       (unless (file=? y z) (fail "mismatch"))))
    '(msg_encrypted_256 msg_encrypted_384 msg_encrypted_521)))
 
 ;;
diff --git a/tests/openpgp/enarmor.scm b/tests/openpgp/enarmor.scm
new file mode 100755 (executable)
index 0000000..a301ccd
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "defs.scm"))
+(setup-legacy-environment)
+
+(for-each-p
+ "Checking armor encoding and decoding"
+ (lambda (source)
+   (tr:do
+    (tr:open source)
+    (tr:gpg "" `(--enarmor))
+    (tr:gpg "" '(--dearmor))
+    (tr:assert-identity source)))
+ (append plain-files data-files))
index fccb8c9..7ac1916 100755 (executable)
@@ -43,4 +43,4 @@
        (tr:gpg "" '(--yes))
        (tr:assert-identity source)))
     (append plain-files data-files)))
all-cipher-algos)
(force all-cipher-algos))
diff --git a/tests/openpgp/encrypt-multifile.scm b/tests/openpgp/encrypt-multifile.scm
new file mode 100755 (executable)
index 0000000..4b76ff0
--- /dev/null
@@ -0,0 +1,39 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "defs.scm"))
+(setup-legacy-environment)
+
+(define files (append plain-files data-files))
+
+(info "Checking encryption of supplied files using --multifile.")
+
+;; Now encrypt all files.
+(call-check `(,@gpg --encrypt --recipient ,usrname2
+                   --multifile ,@files))
+
+;; And check if we can decrypt them.
+(for-each-p
+ "Verifying files:"
+ (lambda (source)
+   (tr:do
+    (tr:open (string-append source ".gpg"))
+    (tr:gpg "" '(--yes))
+    (tr:assert-identity source)))
+ files)
index ea97b4d..4247aa8 100755 (executable)
@@ -43,7 +43,7 @@
        (tr:gpg "" '(--yes))
        (tr:assert-identity source)))
     (append plain-files data-files)))
all-cipher-algos)
(force all-cipher-algos))
 
 
 ;; We encrypt to two keys and we have also put the first key into our
index a79411c..c10fc81 100755 (executable)
@@ -22,7 +22,7 @@
 
 (define (check-for predicate lines message)
   (unless (any predicate lines)
-         (error message)))
+         (fail message)))
 
 (define (check-exported-key dump keyid)
   (check-for (lambda (l)
@@ -68,7 +68,7 @@
      (lambda (port)
        (unless
        (eof-object? (peek-char port))
-       (error (string-append
+       (fail (string-append
                "Expected all passphrases to be consumed, but found: "
                (read-all port)))))))
 
index e5008c3..60eba0b 100755 (executable)
@@ -23,7 +23,7 @@
 (define (genkey config)
   (pipe:do
    (pipe:echo config)
-   (pipe:spawn `(,(tool 'gpg) --quiet --batch --gen-key))))
+   (pipe:spawn `(,(tool 'gpg) --quiet --batch --generate-key))))
 
 (info "Checking batch key generation")
 (genkey "Key-Type: DSA
index 69206b4..c88589f 100755 (executable)
@@ -43,7 +43,7 @@
      (tr:call-with-content
       (lambda (c)
        (unless (all (lambda (f) (string-contains? c f)) testfiles)
-               (error "some file(s) are missing from archive")))))
+               (fail "some file(s) are missing from archive")))))
 
     (with-temporary-working-directory
      (call-check `(,(tool 'gpgtar) --gpg ,(tool 'gpg) --gpg-args ,gpgargs
@@ -53,7 +53,7 @@
 
      (for-each
       (lambda (f) (unless (call-with-input-file f (lambda (x) #t))
-                         (error (string-append "missing file: " f))))
+                         (fail (string-append "missing file: " f))))
       testfiles))))
 
 (info "Checking gpgtar without encryption")
@@ -84,7 +84,8 @@
 
     (info "Checking gpgtar with symmetric encryption and chosen cipher")
     (do-test `(,@ppflags --symmetric --gpg-args
-                        ,(string-append "--cipher=" (car all-cipher-algos)))
+                        ,(string-append "--cipher="
+                                        (car (force all-cipher-algos))))
             ppflags (cons '--decrypt ppflags))
 
     (info "Checking gpgtar with both symmetric and asymmetric encryption")
index 65d21c5..6885cd9 100755 (executable)
@@ -64,5 +64,5 @@ N1Glbw1OJfP1q+QFPMPKoCsTYmZpuugq2b5gV/eH0Abvk2pG4Fo/YTDPHhec7Jk=
          (pipe:do
           (pipe:echo (eval armored-file (current-environment)))
           (pipe:spawn `(,@GPGV --keyring ,(in-srcdir "forged-keyring.gpg"))))
-         (error "verification succeeded but should not")))
+         (fail "verification succeeded but should not")))
  '(msg_signed_asc))
diff --git a/tests/openpgp/import-revocation-certificate.scm b/tests/openpgp/import-revocation-certificate.scm
new file mode 100644 (file)
index 0000000..9231afc
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "defs.scm"))
+(setup-legacy-environment)
+
+;; XXX because of --always-trust, the trustdb is not created.
+;; Therefore, we redefine GPG without --always-trust.
+(define gpg `(,(tool 'gpg) --no-permission-warning))
+
+(info "Checking key revocation.")
+(call-check `(,@gpg --import ,(in-srcdir "samplemsgs"
+                                        "revoke-2D727CC768697734.asc")))
+(let loop ((output (gpg-with-colons '(--list-secret-keys "2D727CC768697734"))))
+  (unless (null? output)
+         (let ((line (car output))
+               (rest (cdr output)))
+           (when (member (car line) '("sec" "uid" "ssb"))
+                 (unless (equal? (cadr line) "r")
+                         (fail (car line) "not revoked.")))
+           (loop rest))))
index c354753..3b41746 100755 (executable)
@@ -58,4 +58,4 @@
                        (string-contains? line ":4096:1:DDA252EBB8EBE1AF:")))
                 (string-split-newlines c))))
       (unless (= 2 (length keys))
-             (error "Importing keys with long id collision failed"))))))
+             (fail "Importing keys with long id collision failed"))))))
index 4f151aa..39df333 100755 (executable)
@@ -28,4 +28,4 @@
                            "GET_PASSPHRASE --no-ask some_id X X X")))
   (unless (string=? (string-rtrim char-whitespace? response)
                    "OK 736F6D655F70617373706872617365")
-         (error "Could not retrieve passphrase from cache:" response)))
+         (fail "Could not retrieve passphrase from cache:" response)))
index cbe03f9..9765453 100755 (executable)
@@ -25,4 +25,4 @@
 (info "Checking import statistics (issue2346)...")
 (let ((status (call-popen `(,@GPG --status-fd=1 --import ,key) "")))
   (unless (string-contains? status "IMPORT_RES 1 0 1 0 0 0 0 0 0 1 1 0 0 0 0")
-         (error "Unexpected number of keys imported" status)))
+         (fail "Unexpected number of keys imported" status)))
index 9b67851..e397a88 100755 (executable)
@@ -26,4 +26,4 @@
  (dearmor (in-srcdir "samplemsgs/issue2419.asc") onebyte)
  (catch (assert (string-contains? (car *error*) "invalid packet"))
        (call-popen `(,@GPG --list-packets ,onebyte) "")
-       (error "Expected an error but got none")))
+       (fail "Expected an error but got none")))
index 93bd001..020c9b4 100644 (file)
@@ -76,7 +76,7 @@
             (display (call-popen `(,@gpg --locate-key ,mailbox) ""))
             (echo "This is the key we expected:")
             (display (call-popen `(,@gpg --list-keys ,expected) ""))
-            (error "Expected" expected "but got" fpr)))
+            (fail "Expected" expected "but got" fpr)))
    (delete-keys set))
  (lambda (set)
    (length set))
index bf598eb..fb468e5 100755 (executable)
@@ -49,7 +49,7 @@
 (define (test-hash hash ref)
   (unless (eq? #f ref)
          (if (not (string=? (:value hash) (:value ref)))
-           (error "failed"))))
+           (fail "failed"))))
 
 ;; Test whether the hashes computed over S match the REFERENCE set.
 (define (test-hashes msg s reference)
index 7f1c4c5..c643ac8 100755 (executable)
@@ -164,6 +164,6 @@ cnksIEkgY2FuJ3QgZG8gdGhhdAo=
       (pipe:write-to file (logior O_WRONLY O_CREAT O_BINARY) #o600))
 
      (if (= 0 (call `(,@GPG --verify ,file)))
-        (error "Bad signature verified ok"))))
+        (fail "Bad signature verified ok"))))
  '(sig-1ls1ls-valid sig-ls-valid sig-1lsls-invalid
                    sig-lsls-invalid sig-lss-invalid sig-slsl-invalid))
index 4579081..d43f7b5 100755 (executable)
@@ -29,7 +29,7 @@
 
 (define (count-uids-of-secret-key id)
   (length (filter (lambda (x) (and (string=? "uid" (car x))
-                                  (string=? "u" (cadr x))))
+                                  (not (string=? "r" (cadr x)))))
                  (gpg-with-colons
                   `(--with-fingerprint
                     --list-secret-keys ,(exact id))))))
 (setenv "PINENTRY_USER_DATA" "test" #t)
 
 (info "Checking quick key generation...")
-(call-check `(,@GPG --quick-gen-key ,alpha))
+(call-check `(,@GPG --quick-generate-key ,alpha))
 
-(call-check `(,@GPG --check-trustdb)) ; XXX why?
+(define keyinfo (gpg-with-colons `(-k ,(exact alpha))))
+(define fpr (:fpr (assoc "fpr" keyinfo)))
 
 (assert (= 1 (count-uids-of-secret-key alpha)))
+(assert (not (equal? "" (:expire (assoc "pub" keyinfo)))))
 
 (info "Checking that we can add a user ID...")
 
 ;; Make sure the key capabilities don't change when we add a user id.
 ;; (See bug #2697.)
 (let ((pre (key-data (exact alpha)))
-      (result (call-check `(,@GPG --quick-adduid ,(exact alpha) ,bravo)))
+      (result (call-check `(,@GPG --quick-add-uid ,(exact alpha) ,bravo)))
       (post (key-data (exact alpha))))
   (if (not (equal? pre post))
       (begin
        (newline)
        (exit 1))))
 
-(call-check `(,@GPG --check-trustdb)) ; XXX why?
-
 (assert (= 2 (count-uids-of-secret-key alpha)))
 (assert (= 2 (count-uids-of-secret-key bravo)))
 
 (info "Checking that we can revoke a user ID...")
-(call-check `(,@GPG --quick-revuid ,(exact bravo) ,alpha))
-
-(call-check `(,@GPG --check-trustdb)) ; XXX why?
+(call-check `(,@GPG --quick-revoke-uid ,(exact bravo) ,alpha))
 
 (assert (= 1 (count-uids-of-secret-key bravo)))
+
+(info "Checking that we can change the expiration time.")
+
+(define (expiration-time id)
+  (:expire (assoc "pub" (gpg-with-colons `(-k ,id)))))
+
+;; Remove the expiration date.
+(call-check `(,@gpg --quick-set-expire ,fpr "0"))
+(assert (equal? "" (expiration-time fpr)))
+
+;; Make the key expire in one year.
+(call-check `(,@gpg --quick-set-expire ,fpr "1y"))
+;; XXX It'd be nice to check that the value is right.
+(assert (not (equal? "" (expiration-time fpr))))
+
+
+;;
+;; Check --quick-addkey
+;;
+
+;; Get the subkeys.
+(define (get-subkeys)
+  (filter (lambda (x) (equal? "sub" (car x)))
+         (gpg-with-colons `(-k ,fpr))))
+
+;; This keeps track of the number of subkeys.
+(define count (length (get-subkeys)))
+
+(for-each-p
+ "Checking that we can add subkeys..."
+ (lambda (args check)
+   (set! count (+ 1 count))
+   (call-check `(,@gpg --quick-add-key ,fpr ,@args))
+   (let ((subkeys (get-subkeys)))
+     (assert (= count (length subkeys)))
+     (if check (check (last subkeys)))))
+ ;; A bunch of arguments...
+ '(()
+   (- - -)
+   (default default never)
+   (rsa sign "2d")
+   (rsa1024 sign "2w")
+   (rsa2048 encr "2m")
+   (rsa4096 sign,auth "2y")
+   (future-default))
+ ;; ... with functions to check that the created key matches the
+ ;; expectations (or #f for no tests).
+ (list
+  #f
+  #f
+  (lambda (subkey)
+    (assert (equal? "" (:expire subkey))))
+  (lambda (subkey)
+    (assert (= 1 (:alg subkey)))
+    (assert (string-contains? (:cap subkey) "s"))
+    (assert (not (equal? "" (:expire subkey)))))
+  (lambda (subkey)
+    (assert (= 1 (:alg subkey)))
+    (assert (= 1024 (:length subkey)))
+    (assert (string-contains? (:cap subkey) "s"))
+    (assert (not (equal? "" (:expire subkey)))))
+  (lambda (subkey)
+    (assert (= 1 (:alg subkey)))
+    (assert (= 2048 (:length subkey)))
+    (assert (string-contains? (:cap subkey) "e"))
+    (assert (not (equal? "" (:expire subkey)))))
+  (lambda (subkey)
+    (assert (= 1 (:alg subkey)))
+    (assert (= 4096 (:length subkey)))
+    (assert (string-contains? (:cap subkey) "s"))
+    (assert (string-contains? (:cap subkey) "a"))
+    (assert (not (equal? "" (:expire subkey)))))
+  #f))
index 90879a6..546d7d4 100644 (file)
 ;; Set objdir so that the tests can locate built programs.
 (setenv "objdir" (getcwd) #f)
 
-(define test-pool
-  (package
-   (define (new procs)
-     (package
-      (define (add test)
-       (new (cons test procs)))
-      (define (wait)
-       (let ((unfinished (filter (lambda (t) (not t::retcode)) procs)))
-         (if (null? unfinished)
-             (package)
-             (let* ((commands (map (lambda (t) t::command) unfinished))
-                    (pids (map (lambda (t) t::pid) unfinished))
-                    (results
-                     (map (lambda (pid retcode) (list pid retcode))
-                          pids
-                          (wait-processes (map stringify commands) pids #t))))
-               (new
-                (map (lambda (t)
-                       (if t::retcode
-                           t
-                           (t::set-retcode (cadr (assoc t::pid results)))))
-                     procs))))))
-      (define (passed)
-       (filter (lambda (p) (= 0 p::retcode)) procs))
-      (define (skipped)
-       (filter (lambda (p) (= 77 p::retcode)) procs))
-      (define (hard-errored)
-       (filter (lambda (p) (= 99 p::retcode)) procs))
-      (define (failed)
-       (filter (lambda (p)
-                 (not (or (= 0 p::retcode) (= 77 p::retcode)
-                          (= 99 p::retcode))))
-               procs))
-      (define (report)
-       (echo (length procs) "tests run,"
-             (length (passed)) "succeeded,"
-             (length (failed)) "failed,"
-             (length (skipped)) "skipped.")
-       (length (failed)))))))
-
-(define (verbosity n)
-  (if (= 0 n) '() (cons '--verbose (verbosity (- n 1)))))
-
-(define test
-  (package
-   (define (scm name . args)
-     (new name #f `(,*argv0* ,@(verbosity (*verbose*)) ,@args
-                            ,(in-srcdir name)) #f #f CLOSED_FD))
-   (define (new name directory command pid retcode logfd)
-     (package
-      (define (set-directory x)
-       (new name x command pid retcode logfd))
-      (define (set-retcode x)
-       (new name directory command pid x logfd))
-      (define (set-pid x)
-       (new name directory command x retcode logfd))
-      (define (set-logfd x)
-       (new name directory command pid retcode x))
-      (define (open-log-file)
-       (let ((filename (string-append name ".log")))
-         (catch '() (unlink filename))
-         (open filename (logior O_RDWR O_BINARY O_CREAT) #o600)))
-      (define (run-sync . args)
-       (letfd ((log (open-log-file)))
-         (with-working-directory directory
-           (let* ((p (inbound-pipe))
-                  (pid (spawn-process-fd (append command args) 0
-                                         (:write-end p) (:write-end p))))
-             (close (:write-end p))
-             (splice (:read-end p) STDERR_FILENO log)
-             (close (:read-end p))
-             (let ((t' (set-retcode (wait-process name pid #t))))
-               (t'::report)
-               t')))))
-      (define (run-sync-quiet . args)
-       (with-working-directory directory
-         (set-retcode
-          (wait-process
-           name (spawn-process-fd (append command args)
-                                  CLOSED_FD CLOSED_FD CLOSED_FD) #t))))
-      (define (run-async . args)
-       (let ((log (open-log-file)))
-         (with-working-directory directory
-           (new name directory command
-                (spawn-process-fd (append command args) CLOSED_FD log log)
-                retcode log))))
-      (define (status)
-       (let ((t (assoc retcode '((0 "PASS") (77 "SKIP") (99 "ERROR")))))
-         (if (not t) "FAIL" (cadr t))))
-      (define (report)
-       (unless (= logfd CLOSED_FD)
-               (seek logfd 0 SEEK_SET)
-               (splice logfd STDERR_FILENO)
-               (close logfd))
-       (echo (string-append (status retcode) ":") name))))))
-
-(define (run-tests-parallel setup tests)
-  (lettmp (gpghome-tar)
-    (setup::run-sync '--create-tarball gpghome-tar)
-    (let loop ((pool (test-pool::new '())) (tests' tests))
-      (if (null? tests')
-         (let ((results (pool::wait)))
-           (for-each (lambda (t)
-                       (catch (echo "Removing" t::directory "failed:" *error*)
-                              (unlink-recursively t::directory))
-                       (t::report)) (reverse results::procs))
-           (exit (results::report)))
-         (let* ((wd (mkdtemp))
-                (test (car tests'))
-                (test' (test::set-directory wd)))
-           (loop (pool::add (test'::run-async '--unpack-tarball gpghome-tar))
-                 (cdr tests')))))))
-
-(define (run-tests-sequential setup tests)
-  (lettmp (gpghome-tar)
-    (setup::run-sync '--create-tarball gpghome-tar)
-    (let loop ((pool (test-pool::new '())) (tests' tests))
-      (if (null? tests')
-         (let ((results (pool::wait)))
-           (for-each (lambda (t)
-                       (catch (echo "Removing" t::directory "failed:" *error*)
-                              (unlink-recursively t::directory)))
-                     results::procs)
-           (exit (results::report)))
-         (let* ((wd (mkdtemp))
-                (test (car tests'))
-                (test' (test::set-directory wd)))
-           (loop (pool::add (test'::run-sync '--unpack-tarball gpghome-tar))
-                 (cdr tests')))))))
-
-(let* ((runner (if (member "--parallel" *args*)
+(let* ((tests (filter (lambda (arg) (not (string-prefix? arg "--"))) *args*))
+       (runner (if (and (member "--parallel" *args*)
+                       (> (length tests) 1))
                   run-tests-parallel
-                  run-tests-sequential))
-       (tests (filter (lambda (arg) (not (string-prefix? arg "--"))) *args*)))
-  (runner (test::scm "setup.scm") (map test::scm tests)))
+                  run-tests-sequential)))
+  (runner (test::scm "setup.scm" "setup.scm")
+         (map (lambda (t) (test::scm t t)) tests)))
diff --git a/tests/openpgp/samplekeys/authenticate-only.pub.asc b/tests/openpgp/samplekeys/authenticate-only.pub.asc
new file mode 100644 (file)
index 0000000..a08a003
--- /dev/null
@@ -0,0 +1,31 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBFg8F68BCAC5VS/59Nrg5mw+wEAnk/vi0CGI+K5rGTmyAruy/aiS4kYfUJiE
+Pq/JZYCeNQ9GTbJN5M+Fiv1bhw6xeEfl4FAC/A0Uo39fvUSLJH2V+8lT3GxXt8KR
+Yx8MvotvoKiG1DRvgnwRh1qibX6+htJahPwHDViUuFz0ZeEPWiCwuoMOELmiTsuE
+VZbQsv08BwCOzIc3M8s63TtwMCUe+MBhqfRjEu6/Gt7miuGGVFXB96ao2O9u1HsR
+7cUjgVQp6JwGLgIIijjy167Ms+fKZetqA9tzu1C1SAtNYiqmAUoqGG1YEjS7oApo
+AKmHD+LGCMUPg5d6bhw+NyHjDAFWJEYv3kG3ABEBAAG0NVRlc3QgS2V5IChkbyBu
+b3QgdXNlKSA8YXV0aGVudGljYXRlLW9ubHlAZXhhbXBsZS5vcmc+iQFOBBMBCAA4
+FiEEkn7zd/0aG295XkDAKoeRfY/7pJ8FAlg8F68CGwEFCwkIBwIGFQgJCgsCBBYC
+AwECHgECF4AACgkQKoeRfY/7pJ9+zAf9FVZknDCWCNY42+RIR36fq2l1Gs8KfMsX
+rNXtfYPtLqioP8fz2LE0LoudSsZMLVygonPG0ZAxdIUHXcFdUqPrEzs4fEyR4xae
+OSxui2Q/u6+9bi7eilYYzVNRWkuyx2TrRQZEjMXMnuJcWptGbRWs/ipRUZBHWfLl
+2udLl+0TRIL7hni06xVCHbwE5szcLoyzzvIowGVADXFqeG7nty7uRNZNAD+ZWMH2
+J0vspZlUSVi7z1VygzDI3U42SMJmVqnRmICsB3QLI8Ns4nxWXO2z8fheSFcrP+LA
+cY3W6JgnLCzvyuogxnWmd4fzr1iB3E2Hcy/sr0cgQ5wtuseQRhmTlrkBDQRYPBfZ
+AQgAtKcbBYrIqh3cRTqyZjMxw492RVQoFawYmpr1bQ4HphVGnT4IhJZQ4DAm1JJZ
+QjzwDQiZMc1wD9Om6UC/g8gUBuFCpLCobwBzjH4an/G3Cfa8zGz5ANAROo5i5T1j
+vgoFEFdVue/GpAmMNixz+0ItQBh9jiOC6IYk1yyv/OsTzsj6AHnH0XiDpGoTNkca
+tb6Mu1VcXTMNf620Mxj6c7WE7awxL6MwKIa7XQSgXaO+JnbB+5Szz1wZ1ZhlnDp3
+KOurlfcXIbZirEaqmRByb+V21Dj3icXOJEj1RUbyVNqBS8rhw17kSxcehw/6ZAMW
+ehDmhXO98VVUknS0Mf+OJBi2JwARAQABiQE2BBgBCAAgFiEEkn7zd/0aG295XkDA
+KoeRfY/7pJ8FAlg8F9kCGyAACgkQKoeRfY/7pJ+sawf/eSjxxAglAdolF9lK070u
+VmMgq4GFPqJ3RqJPUFjwEFFSYLXkiALnMGXDSmOfPqCQ4c+PWwumFhKCz4MXVDD8
+x6mi9Z+HlEwIMaCnckrSTuQ5OgwO/6vkhz42OcgMZ3WQnWfNVM8jbNP9vX1vroPe
+HLFaPGy9KJMM0Z/hlCIIeyK/a90zWlT5UMfRoqNQRbY/iiYdmpvf69I9PobGVbo/
+7ahZTumPWwjiGOztNXeuo5UUaAVVxMQBYKp2w3wil2sHzYfTfYUSMyh+oUFx4Xlz
+WF3bLzsafRaeuK1h5+JuvIcimvU5zWZtn0hOpiIXpZOoJvvM9r5D4ZRT5UX2blQ8
+Pw==
+=vt5C
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/tests/openpgp/samplekeys/authenticate-only.sec.asc b/tests/openpgp/samplekeys/authenticate-only.sec.asc
new file mode 100644 (file)
index 0000000..109a7a1
--- /dev/null
@@ -0,0 +1,60 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lQPGBFg8F68BCAC5VS/59Nrg5mw+wEAnk/vi0CGI+K5rGTmyAruy/aiS4kYfUJiE
+Pq/JZYCeNQ9GTbJN5M+Fiv1bhw6xeEfl4FAC/A0Uo39fvUSLJH2V+8lT3GxXt8KR
+Yx8MvotvoKiG1DRvgnwRh1qibX6+htJahPwHDViUuFz0ZeEPWiCwuoMOELmiTsuE
+VZbQsv08BwCOzIc3M8s63TtwMCUe+MBhqfRjEu6/Gt7miuGGVFXB96ao2O9u1HsR
+7cUjgVQp6JwGLgIIijjy167Ms+fKZetqA9tzu1C1SAtNYiqmAUoqGG1YEjS7oApo
+AKmHD+LGCMUPg5d6bhw+NyHjDAFWJEYv3kG3ABEBAAH+BwMCzWzbxiWo+krZWBSs
+jFH7f5mn3T/L0PBesTdqiwKwUOj8Fltri+6OY9G028CJB4UlTrEmvD4zdu7HFlvo
+XgrNhZiE8INjEY6QDrNTKNX474dWcCQDDinz6NIwitYNMkv//Ct19qhrqd9WpGVH
+QtKVe0BOHimiokpqu3YBMvV8tZmM2Am8vN32nn/lHo8G/UhSRVnYewazSvAjCSPG
+OqJAPuD3vldssTgpPA2BhBeh6Q7jDkghGfd97a1LkB+JpuDF6UNXs1OxVthONb7q
+ScEsxUhiYne7LrglzZhfDdqsRWcyQyaTzIg/3tOh69hRI2I+8EoJJ9PeLx4rMGFJ
+Z0DkgSbUwXDYYa8GgewT5yZSkfJyhcx/q8eI3PW39TB0YI48TaN3KufzqS+U8ITz
+bQygpzX2WGBXKcwXV0zDXjpapqd/77m600FhvrrIJuJNGmNNSQ1eWAYmY13rmR1R
+7aXRrocVlF3ZPfWzKLi6DophmW0vfcwlt9XxgQ2VxF0rk2f6OF4WcTYbaOCwh8M3
+Ffo98i3OO+upHsJLKyvQRrM1/LVeecGTxV9cXOnE+YB+aU36AS+AjeLRTqM2ZbzU
+UgNTdkcs6+8B4q3XMWtTojXXCYfHMpFYu2FuKT+cXi1UEKWF7/Rhu2i3aeRahRLF
+zdtSwWhbx0WNqH4BL4hhGZ85BUXKrOwdDiRyzgzKQilwWXu1ZEJuaWrY+vxdqdxi
+PyikwO7/JXlBgcICO7v5hKDVuHGll8jP1CQU3hvfcSKuzny++ComI3ND2H2Wj3Jh
+ocz9/7Z+hAp/RMeE2MbptvnJTzeTEg/Wk2aMt6ArxEKCpcpvXlvm5TENl20/oejo
+H/o6DNJhtU75AI5tQZ315cXfK56ae0Swc90UGPImTDl3LiG+qTgQbcHdNV+I0zL9
+FeqvLZg5ZgpBtDVUZXN0IEtleSAoZG8gbm90IHVzZSkgPGF1dGhlbnRpY2F0ZS1v
+bmx5QGV4YW1wbGUub3JnPokBTgQTAQgAOBYhBJJ+83f9GhtveV5AwCqHkX2P+6Sf
+BQJYPBevAhsBBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJECqHkX2P+6SffswH
+/RVWZJwwlgjWONvkSEd+n6tpdRrPCnzLF6zV7X2D7S6oqD/H89ixNC6LnUrGTC1c
+oKJzxtGQMXSFB13BXVKj6xM7OHxMkeMWnjksbotkP7uvvW4u3opWGM1TUVpLssdk
+60UGRIzFzJ7iXFqbRm0VrP4qUVGQR1ny5drnS5ftE0SC+4Z4tOsVQh28BObM3C6M
+s87yKMBlQA1xanhu57cu7kTWTQA/mVjB9idL7KWZVElYu89VcoMwyN1ONkjCZlap
+0ZiArAd0CyPDbOJ8Vlzts/H4XkhXKz/iwHGN1uiYJyws78rqIMZ1pneH869YgdxN
+h3Mv7K9HIEOcLbrHkEYZk5adA8YEWDwX2QEIALSnGwWKyKod3EU6smYzMcOPdkVU
+KBWsGJqa9W0OB6YVRp0+CISWUOAwJtSSWUI88A0ImTHNcA/TpulAv4PIFAbhQqSw
+qG8Ac4x+Gp/xtwn2vMxs+QDQETqOYuU9Y74KBRBXVbnvxqQJjDYsc/tCLUAYfY4j
+guiGJNcsr/zrE87I+gB5x9F4g6RqEzZHGrW+jLtVXF0zDX+ttDMY+nO1hO2sMS+j
+MCiGu10EoF2jviZ2wfuUs89cGdWYZZw6dyjrq5X3FyG2YqxGqpkQcm/ldtQ494nF
+ziRI9UVG8lTagUvK4cNe5EsXHocP+mQDFnoQ5oVzvfFVVJJ0tDH/jiQYticAEQEA
+Af4HAwLbL5r0DsoKEtmeahTDsYhsRTH/hmOPCGKyjhblVfWoNVQjmrDNsRVrPx+2
+CVj304Npu9XpkKsgp/U20pv6y00xET/l+FfftyN5zUTdGC4qi0gN3AXE5amQkbnJ
+/ItzUTnf8YPYenSKioiQSrk39Q+ikRZ3WBIPVQcMR2bPnzAobNupit5fgS7e/aRA
+0vswD12SeqqNM82JWAuSFJc51F+Gn8xTkWnnK2AdA8Rz351qqUHpmFwg8kaRVhVe
+TwSP7nAN5JPS7lrr91vvp3jRlH84XgaUxFk/8Gm8pSNN4tyxNjo9Cv6YaG0PR/rO
+skg6G/9AfqVtAuRyaRNu1HP/JPDyuGGEqOt7mfMr2vd43a5LTVO3EkO5OfQkXg1/
+t2HXn6dzc0hBolq0pWAUU+sVwZD9h2F8pEWPPyeInv5H4c95J9HU466tro9VJpvm
+p/iw55evDiBRAu+nNkCI8eyFEpB06d3ikjxLAJ5xS3Fb6Vj3PX0TdZMieRNHQXlt
+Gpi8UQOpOLhj+lytW9NYCGG3SYcy2ytYVgs2IoIycx37c4tz5+v1yi8/FvnQPPTf
+8LjLZUrrFL6GRJm0fNtp0I3jm5Ic/oESQ4UDrVmQO1+G6/+9iOalgA7x67+X13rD
+6nz0Et0WBH0HpZrOEFe2b07livdfs5gS3xtyqyMBRLu4lKYDWYJ55R0nbMiwQ1dQ
+EG5U/ur0E1ENJeFDE2MmL06RIXtizVcGmoU23m/tAEShNJBaHGe1qKKDGk8lutmF
+9S4UN5eXE3qzBJmqWQTG4/z2N+QRB/Nj6h1IbKPvAMML2c5vdI1V1LniCa55vytJ
+F6Ngtyw6f7M2UVqyPr6FZmzZyw6SPxjGqFUcp/o30Pqm6k0AYr3HVQLLnCM9vpCY
+Y73QV1EOpBNPNIPTyiWWFSnrXID6xoObHg2JATYEGAEIACAWIQSSfvN3/Robb3le
+QMAqh5F9j/uknwUCWDwX2QIbIAAKCRAqh5F9j/ukn6xrB/95KPHECCUB2iUX2UrT
+vS5WYyCrgYU+ondGok9QWPAQUVJgteSIAucwZcNKY58+oJDhz49bC6YWEoLPgxdU
+MPzHqaL1n4eUTAgxoKdyStJO5Dk6DA7/q+SHPjY5yAxndZCdZ81UzyNs0/29fW+u
+g94csVo8bL0okwzRn+GUIgh7Ir9r3TNaVPlQx9Gio1BFtj+KJh2am9/r0j0+hsZV
+uj/tqFlO6Y9bCOIY7O01d66jlRRoBVXExAFgqnbDfCKXawfNh9N9hRIzKH6hQXHh
+eXNYXdsvOxp9Fp64rWHn4m68hyKa9TnNZm2fSE6mIhelk6gm+8z2vkPhlFPlRfZu
+VDw/
+=vLFX
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/tests/openpgp/samplemsgs/revoke-2D727CC768697734.asc b/tests/openpgp/samplemsgs/revoke-2D727CC768697734.asc
new file mode 100644 (file)
index 0000000..f20029c
--- /dev/null
@@ -0,0 +1,8 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: This is a revocation certificate
+
+iGAEIBECACAWIQSg/0WQu2Ei7e9uPFQtcnzHaGl3NAUCWEaoMAIdAAAKCRAtcnzH
+aGl3NISuAJ9rsxoazHvPs89Ki33o/SgKMjOg/wCggbFG8V5wXU1njwuiviPUKap3
+uqA=
+=79yW
+-----END PGP PUBLIC KEY BLOCK-----
index d2e125e..fc0615f 100755 (executable)
@@ -20,7 +20,7 @@
 (load (with-path "defs.scm"))
 
 (unless (member "--create-tarball" *args*)
-       (error "Usage: setup.scm --create-tarball <file>"))
+       (fail "Usage: setup.scm --create-tarball <file>"))
 
 (with-temporary-working-directory
  (setenv "GNUPGHOME" (getcwd) #t)
index b4c3bd6..35ac89a 100755 (executable)
@@ -37,4 +37,4 @@
              usrpass1)
   (if (not (string=? "4336AE2A528FAE091E73E59E325B588FEE795F9B"
                     (cadar (gpg-hash-string `(--print-md SHA1 ,tmp) ""))))
-      (error "bug537-test.data.asc: mismatch (bug 537)")))
+      (fail "bug537-test.data.asc: mismatch (bug 537)")))
index abdcd8f..5a1efa7 100755 (executable)
@@ -48,4 +48,4 @@
                `(--yes --sign --passphrase-fd "0" --digest-algo ,hash))
        (tr:gpg "" '(--yes))
        (tr:assert-identity (car plain-files)))))
all-hash-algos)
(force all-hash-algos))
diff --git a/tests/openpgp/ssh-export.scm b/tests/openpgp/ssh-export.scm
new file mode 100755 (executable)
index 0000000..322620e
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "defs.scm"))
+(setup-environment)
+
+(define key
+  `(,(in-srcdir "samplekeys" "authenticate-only.sec.asc")
+    "927EF377FD1A1B6F795E40C02A87917D8FFBA49F"
+    "72360FDB6380212D5DAF2FA9E51185A9253C496D"
+    "ssh-rsa"))
+
+(define :file car)
+(define :fpr cadr)
+(define :subkey-fpr caddr)
+(define :kind cadddr)
+
+;; Return true if a-str and b-str share a suffix of length n.
+(define (string-common-suffix? n a-str b-str)
+  (let ((a-len (string-length a-str))
+       (b-len (string-length b-str)))
+  (if (> n (min a-len b-len))
+      #f
+      (string=? (substring a-str (- a-len n) a-len)
+               (substring b-str (- b-len n) b-len)))))
+
+(info "Checking ssh export...")
+(call-check `(,@GPG --yes --import ,(:file key)))
+
+(let* ((result (call-check `(,@GPG --export-ssh-key ,(:fpr key))))
+       ;; XXX: We should split at any whitespace here.
+       (parts (string-split (string-trim char-whitespace? result) #\space)))
+  (assert (string=? (car parts) (:kind key)))
+  ;; XXX: We should not use a short keyid as the comment when
+  ;; exporting an ssh key.
+  (assert (string-common-suffix? 8 (caddr parts) (:subkey-fpr key))))
similarity index 94%
rename from tests/openpgp/ssh.scm
rename to tests/openpgp/ssh-import.scm
index a825409..e8f12d3 100755 (executable)
@@ -22,7 +22,7 @@
 
 (define GNUPGHOME (getenv "GNUPGHOME"))
 (if (string=? "" GNUPGHOME)
-    (error "GNUPGHOME not set"))
+    (fail "GNUPGHOME not set"))
 
 (setenv "SSH_AUTH_SOCK"
         (call-check `(,(tool 'gpgconf) --null --list-dirs agent-ssh-socket))
@@ -51,7 +51,7 @@
       (pipe:open file (logior O_RDONLY O_BINARY))
       (pipe:spawn `(,SSH-ADD -)))
      (unless (string-contains? (call-popen `(,SSH-ADD -l "-E" md5) "") hash)
-            (error "key not added"))))
+            (fail "key not added"))))
  car keys)
 
 (info "Checking for issue2316...")
@@ -64,4 +64,4 @@
 (unless
  (string-contains? (call-popen `(,SSH-ADD -l "-E" md5) "")
                   "c9:85:b5:55:00:84:a9:82:5a:df:d6:62:1b:5a:28:22")
- (error "known private key not (re-)added to sshcontrol"))
+ (fail "known private key not (re-)added to sshcontrol"))
diff --git a/tests/openpgp/tofu-2183839A-1.txt b/tests/openpgp/tofu-2183839A-1.txt
deleted file mode 100644 (file)
index 521b3bb..0000000
Binary files a/tests/openpgp/tofu-2183839A-1.txt and /dev/null differ
diff --git a/tests/openpgp/tofu-BC15C85A-1.txt b/tests/openpgp/tofu-BC15C85A-1.txt
deleted file mode 100644 (file)
index 88cc649..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
------BEGIN PGP MESSAGE-----
-Version: GnuPG v2
-
-owGbwMvMwMF46tzNaXtET0QxnmZPYgj9/c+Sq2MOCwMjBwMbKxOIy8DFKQBTo/SK
-hWFThVuj19r3R/6VzQkpaZuQx7s3r9BQ46v8KXkjb58dSjmXyr7enlCzb7dg1zE7
-aynbc6YTF+wXZI4IlAgPuLJhUeSXo0+WllxbFXUz39407cv15TcXThLj+3tFkSnZ
-YFXwM9+nfAoHpt6I/ZY96SJT3XFZKzO1jeZNJhZsV4Vfrjp0UmnH3E4A
-=X9WM
------END PGP MESSAGE-----
diff --git a/tests/openpgp/tofu-EE37CF96-1.txt b/tests/openpgp/tofu-EE37CF96-1.txt
deleted file mode 100644 (file)
index 33a38db..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
------BEGIN PGP MESSAGE-----
-Version: GnuPG v2
-
-owGbwMvMwMEY0Tqz9J35+WmMp9mTGEJ//xPk6pjDwsDIwcDGygTiMnBxCsDULFZm
-/sk4S36iQ6FuZZPMPdOSe/rZOxNThTmzvJN4l1qe9XGdlLhtpumfzh0uhRnzT2Xc
-jmra+ZdN9+XBhml//i7v6XrfuWu56OuEI/fXH0i3P5HELb+j++6SO85VemLq/tvO
-hNvWtddvuZ7+z2JJaqnP4wiu2t+sEze/MWKZ9zz+u2FV6a3OIyJxjwA=
-=JMtb
------END PGP MESSAGE-----
diff --git a/tests/openpgp/tofu-keys-secret.asc b/tests/openpgp/tofu-keys-secret.asc
deleted file mode 100755 (executable)
index 68e0d20..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
------BEGIN PGP PRIVATE KEY BLOCK-----
-Version: GnuPG v2
-
-lgAAAgYEVfv86AEEAN20yizZgtnQaJPUV++9Z+rRg4XzjWpLvmiWMpTsn8qhjpyS
-kAa4/4P4/MRWVvSXiRC1uJ7T59Sbm/KFs8TdKaqIMuON3QYjztxm2NmDMA/f5FTv
-RuLkgKAEpwGOqI1Zvm3uleH8hkx0n45tHxCI3bLCfW+12lZxJCGNDBnhvj+5ABEB
-AAH+BwMCeYHLsHWjaoTufvOw6/xINpFQV8JcwSc+RaEIfmIwEwO242+vUEZefkia
-yMMJTd20C144zMr/3Tsx/+c8ULAbR/NBtuG49jsGWFJH2uN/5pi40x2S/afJuwru
-0co5xQSnpZtM4v9mvFM517IROhHY1pl6KpK87pZm5JHGB4525DpAYJ7vTTmHE2NW
-e5jr7a7SpXwTU7dKHbLxY+kofH7DLvMX6KjOJ/kDLIqnK3AeCwfhXkkRRP8UI/0J
-pZEPUyImag6FryRdoZJPTPX7TMWM4zrdnT6xOffIe1REpo59LVkvg6TiPtnlnuY8
-Y9NVZ+mWz0RHtxFh1b70G6D5C5Mdi/iGUAAfTwNhjdnmYsN1qKxcO533qlj/rXHn
-6uxauiR4d+7Ioy2RsPpY2FqTkgymhBLn6ZcYvzwEXaAygLUs8HmzPuiVm5Ls5UXn
-VKaRMc+DBQPz3W3CuMWsHAyKsg4ibp/6MSf0klYHUG8WVXI4tLGOkbg5HbQTVGVz
-dGluZyAoaW5zZWN1cmUhKYi9BBMBCAAnBQJV+/zoAhsDBQkB4TOABQsJCAcCBhUI
-CQoLAgQWAgMBAh4BAheAAAoJEFiFmXXuN8+WqPYEAIW+qAoFnc2emFnx/b+vKW9X
-1g3NLmsLyUUBI34GCh+sGa6C0SptdKc68uvKUc6daBiHuoukN4F+1rYUuNG8WNMs
-V/JwGPKVADPIFrgGiotMW770ZnzZsoqGWvwUnyrlaUI6AYHe4Uj9YAmnmi647A/u
-UxcI1H20M3dENSUyiS1zngAAAgUEVfv86AEEAMgaJrwhFOhEmHHgqyzx2KFzG4SD
-F6jyAg1CIVKmiLSBfNXWa43vJwfxLo7vbT1wy0iiJF8+ALD/ghppmZb9NpsiUC+X
-xT4ublOSvRgN+527WdUX8ym0EXxjpuSSW+hVZZwUP0K0fBdIVaVCawJGEp5Lc/mX
-KnjmXvLQxWSQYgB9ABEBAAH+BwMCtE0VqaVadDju5hPxFcvSTjNkKwGVZZgQBWVZ
-sYj/Sd/Pbc90xb3TSf/VQGVQhKei+GBmUPYOPqStOP30pJvK0SBxkJ2BYb876RJC
-lj48lkTGFPZwhw69BZq6QA5nfBm41V+W6iakdyEww6g1Q93AyzuAirBJraR+oQ6Q
-beqo52TtYAhpAQbUBsQ/1VO/1zx8eHOG298kYpU2Jo7Te81d03rWcSaDbJqcEmsI
-jJe1ccvQ8oU+k6ttbY3xTiKYWfJCxEaOcYpO4z1/94CPFYv1D5rJqJ/C0/SPmS4t
-4ZMqenEhsAGhMgPLKXNmQadQA2WBOATsSxmKCcC9LNjw1YudXPiLfHEnBKGQSbRF
-sZ2xZqRm7wRTQ/eXAJGGiQ41owstwSUAcFTGIhHunw9dy41CdgnZIEQCxb7R8tBv
-isRlG0cIpO5159LB3NECR4++xBB02nq6lOjysKDmYuWYuQakD1u9L6R+LQBVTxYL
-/iEK8wyf18n/iKUEGAEIAA8FAlX7/OgCGwwFCQHhM4AACgkQWIWZde43z5ZTvAP9
-EWGZu97aZhjIbD18Y2HjbXQn4L6iyeDMuM++Tsnnn57li+HLUAX8ieRHy1l/VE3t
-HhdcqRqAsrxnkGAWKMlYYZS9WHDzrffxtQlszOwpAOWdNDsWsPdbko95XvLatoqk
-t9KxB19sLao6eCBKwB9muMs10i86P+Cehwh97n/UNGOWAAACBgRV+/07AQQAxCWd
-rsUW2IhexMxOvMi32Z63bOEC5JkEy8tntGYwk54I2XGXRebdutMrXqh0nKO7p23k
-gfWjRp1dpbSp20AzdIkwsRlAjOuqhZ3Q6t+kP6xWtxAQI8YZ6lQ0VeZC0dTBllr3
-UlY4tw0emLcScNsGuDVUPYhQoJBMkk4oNw+wWfUAEQEAAf4HAwJNRwdntiqzHO76
-GxxlNilWuwitCGbGwZfmo8K8m2uAMzSKsxUp16rcLVvfQsEzS6rDhF4VbJQyLvZJ
-LDkXB0/DFbPVrxG8byJ2i6WKUzsqcevM29OXOmFfH1NVuVi5oUWbwCR6ctsNQSL7
-Bje0E6+6pme9YQtKgUIBzc2Dw+nq6WjfLc0aEc+rrXzWsJKEUKkjnaUa/AeAVYyO
-rTOk5fLrw6vy/sKsuScvLNvQUrr7U+g69gpk53Cyw2WILlADxbysg2CDMDsDmXk/
-sK6zikAgDjQTRaOJkX4BzCBoqZRaDbLMfze6kA6cwQqDTsUELy1ziH56FjRXuBqj
-D4IziA0/XE8gyMRtoMYXmF0pKBQh0RLoudorcPQE9PCFvKaXmASA80nMeBoYxlIm
-kPMBkkkwiXU4irc1m8phlcrZjYE12pxzWgSYBEwTbbzNe2EcFKf+H1vp9DXqZSua
-wLdiUx6JrSHGzoPl3XFAQXNFoOEGvlFN9nH+tBNUZXN0aW5nIChpbnNlY3VyZSEp
-iL0EEwEIACcFAlX7/TsCGwMFCQHhM4AFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AA
-CgkQys7ZlrwVyFq0NgP/cazey0+qJrTaQ0Z6eab1p8PMFE8BpcegrokxfJn61zo7
-JECjQW+htoOBBIQH32mtqjO/J/SbiBDp3xNcdabCnkphW4jkcgn+FoUbLA3GFk9f
-xtElNDGXHcQNimvhhxfrEr2Mi1yo2rKShiIO0N2yySXCJJIC9CXpDCAIhNdEYeCe
-AAACBQRV+/07AQQA3BJN5N1RI6uesA03xwTW1ABTV4tbjLROKLlTPbxb+TjWQAfQ
-lztbSavzjTO6wPPmHnGv2sXPiH2guET+thKAw1WchItKx+MiT8nnsBJHl950mqI8
-uTHGljkQBuKARVl1ELS3do6CQvGyG+5qHyl3crpED152Q5C/F53b4EfgNXEAEQEA
-Af4HAwL449o07unvl+6XONg4R9pVE0Qp0xCL5CmjhwlL8lUuGTvjciN+lXD6k7VH
-Xj9Wu86alkKZQKyZxESPtsRR5dGWgrvhmUrvPftRmO4PV7A5AS0yi54CQGaWSnOL
-nqVkENUs85Pq1LLfnM8MRIdGpS9225bwsAoB/eJk7zKNRGOUlzCDGW3f12aemyrR
-2RHGVPOvn6SVb8r8RkqCDMApR0j76cTMDiMyaGByi93y8qhXiu88Y+J/+fK5wQis
-FwPJGZVCqNTiglclgrNG4+z8G4SUvkA6W5yDiZyftN67TXqxJKKBXFS5gzWujPti
-boDzivsY9sP4Mkoc94TAmJeaLtNrqHy4UMo/m9YBmuP4hRJ7TCKmvVN4hZCN2mvJ
-4S1vi4Z9GnyxJAbxq9Gb1UA9glVAVt6bQVYO6ySIp4W29xFnoRUm4i0tCovWBn9x
-MWSkG5SLznbh2tKLN0uJGzh4G8xo2fdfx6tWy2x0gw95T5WDg7S2oe6IpQQYAQgA
-DwUCVfv9OwIbDAUJAeEzgAAKCRDKztmWvBXIWqexA/9nZUXs9BGcwpodhqjGY+H9
-/IUJua95jti9t0BleEu+h0R9O+XDEE/77IK9ET4f0t9WMfMhPO7ZIgUxFutB/Z7U
-MuyVteIvGxF/TTbQAKuCrnLYuPWkGiYjR9e0ZDbgmKrRZ/jwhdaxF0IHrR1PJLUn
-vO97qfZC7097/urCsWDMo5YAAAIGBFX8ElYBBACfcdcAcR6BJ2Ba3/HnQR1S0rG3
-8bWq8Rdtt072hDd16oQCNFpQs5WQNruCCpobmB6yOmjKJv8Cf9mxBdcQDxobcw6M
-lHPWZl04SoQKQOa5h6ptITxr+UFFFqfh7AZ7ZtDYaFfBqQX9fvdOX99C18SIcCcN
-0rHoxXfG7D/AaHEysQARAQAB/gcDAj0P/+idN7Q87sZYs1aBo3OqKKdl+a51tcgd
-80HdoEQWyIwOStl9+XleUHyrU5f9kni1I2NCrl+hLyPGaT8dGJinH103fgsGvY/L
-Z2lg5gsPdfb5U5Kyn8MfgAuAEVh0XiLOAVZf4tVjcn3jGW9VM/cDHQI9uwz0MtN0
-xxj1iw151/ydtFt4Qw+Ljh0cwBauiHSaG8rhfObJGbKpXNBJG6QfaGBlOAErO1my
-fr7UgWbul6xCZe/t7Um2rp5GxTJsN+AwDDLqSbwCzmArXRJiEnL5qaw891HuXTIC
-+lxtGNxP6bqe+4Bg/T+MIjJVWzx9avGR2WweSKBqbsyRkmZQCIkWDmp/g9t17ujo
-RrzNUT60Y0gMhJOQxZcgdXJtlT/X0RvP+tGAiVEAlvpQ+9RTzqvf4sZAPndpE4PY
-dKXJF5Pua9cWU+UceQV/Nr+JAlLzNWOlwSOJUVGsQ+RzeFJyB2D5xoG6tRI9idYU
-V+vcNGRpJzsXO6S0E1Rlc3RpbmcgKGluc2VjdXJlISmIvQQTAQgAJwUCVfwSVgIb
-AwUJAeEzgAULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRA8WpFfIYODmknrA/96
-90yhjN3ELmWSJetKzvt7MlUS0j6UkA5VvDObCmAm+bDrQSGdwDJj6gu88b4biNEx
-Cz/Dmo67R9Z+gLE6LGvzYCPZ+GE/ZQ9VMo/AeUEZO44Aa7vRwnYFU0VmMJUeGQbC
-Je4JnLjF/+0yIgh/CtwFL3J/+9eayf6e6L/9WhUZ5J4AAAIGBFX8ElYBBADXznv8
-7J5i/EN8dMtjzx99LXtJdSJ3iJfp69d5V1FygvsDSlMZVekflWKF2ipHRulxLXea
-8mH0salQviQ32qPAyfCWpELLL2srTVezj6ntKVF9hZruQ2d1KBVV+syq6nSY9Eg8
-0mHizvIV5cR2b2X/X6qybJrwhW10oWh+cuLg6QARAQAB/gcDAkwZfkpx6rGW7qkb
-iuwl3c6d1o2x9HeiZG8fZ8UGU5n0Nx4bp4a60j/d+bJowww8sPRcJ+8mi/dNi9dC
-1Dls2CmmOP8U2DsPT189d+JiqlXUumhRyTo5ptglMrHkrMp489QpyCIUhW6HVopI
-ppdOJGE0kTJ7pRx0fevz3la5553IyglJ9iUqgxz2+9XlvDhSplz8zVhyZd5UPW94
-hi+vHCDf3TSakMFFZEVPCQaMunB7urI1wXx/mOT5BTSOp1PVq4SE5TtC2/GrHBU6
-/5wuqyhlT3oH+jF/GfvZQgattnkaFn/JY77/mfTCzyQb1/2iQMO8uTe8KjWAKd5h
-AoCcgxoX0rqSxe7YS2Obl1v0icWbg4wvI8WUAv5pRL7EMVcuUugrb40rWzOiJzYY
-IwEmO+tp08Ev+arbjEMzk+IXLTr3wDip/2oHHU3P2OSi46iLdueUvVnnNXff0H4e
-mqT2zlJQoPCbYMaKxL0yxvFnZLfCWolLOJaIpQQYAQgADwUCVfwSVgIbDAUJAeEz
-gAAKCRA8WpFfIYODmqzxBACNLC9j2EJvoiKhRMAUJTGCQvDWNWAI/2Ln/61Ftqu5
-+OoOI0N7uL1LjWNHrhS/PMKwcIu9iZn/uQV/OGj9YuKw58WeyKkTIEnD7bU5aUQk
-8jdRITPnr/InyHvs21P9hh18MZvDk9L9rL+uwK+9BkeL0MDL3wlAG57Fay9OXgY1
-CQ==
-=2SlE
------END PGP PRIVATE KEY BLOCK-----
diff --git a/tests/openpgp/tofu-keys.asc b/tests/openpgp/tofu-keys.asc
deleted file mode 100755 (executable)
index 2de1cf7..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: GnuPG v2
-
-mI0EVfv86AEEAN20yizZgtnQaJPUV++9Z+rRg4XzjWpLvmiWMpTsn8qhjpySkAa4
-/4P4/MRWVvSXiRC1uJ7T59Sbm/KFs8TdKaqIMuON3QYjztxm2NmDMA/f5FTvRuLk
-gKAEpwGOqI1Zvm3uleH8hkx0n45tHxCI3bLCfW+12lZxJCGNDBnhvj+5ABEBAAG0
-E1Rlc3RpbmcgKGluc2VjdXJlISmIvQQTAQgAJwUCVfv86AIbAwUJAeEzgAULCQgH
-AgYVCAkKCwIEFgIDAQIeAQIXgAAKCRBYhZl17jfPlqj2BACFvqgKBZ3NnphZ8f2/
-rylvV9YNzS5rC8lFASN+BgofrBmugtEqbXSnOvLrylHOnWgYh7qLpDeBfta2FLjR
-vFjTLFfycBjylQAzyBa4BoqLTFu+9GZ82bKKhlr8FJ8q5WlCOgGB3uFI/WAJp5ou
-uOwP7lMXCNR9tDN3RDUlMoktc7iNBFX7/OgBBADIGia8IRToRJhx4Kss8dihcxuE
-gxeo8gINQiFSpoi0gXzV1muN7ycH8S6O7209cMtIoiRfPgCw/4IaaZmW/TabIlAv
-l8U+Lm5Tkr0YDfudu1nVF/MptBF8Y6bkklvoVWWcFD9CtHwXSFWlQmsCRhKeS3P5
-lyp45l7y0MVkkGIAfQARAQABiKUEGAEIAA8FAlX7/OgCGwwFCQHhM4AACgkQWIWZ
-de43z5ZTvAP9EWGZu97aZhjIbD18Y2HjbXQn4L6iyeDMuM++Tsnnn57li+HLUAX8
-ieRHy1l/VE3tHhdcqRqAsrxnkGAWKMlYYZS9WHDzrffxtQlszOwpAOWdNDsWsPdb
-ko95XvLatoqkt9KxB19sLao6eCBKwB9muMs10i86P+Cehwh97n/UNGOYjQRV+/07
-AQQAxCWdrsUW2IhexMxOvMi32Z63bOEC5JkEy8tntGYwk54I2XGXRebdutMrXqh0
-nKO7p23kgfWjRp1dpbSp20AzdIkwsRlAjOuqhZ3Q6t+kP6xWtxAQI8YZ6lQ0VeZC
-0dTBllr3UlY4tw0emLcScNsGuDVUPYhQoJBMkk4oNw+wWfUAEQEAAbQTVGVzdGlu
-ZyAoaW5zZWN1cmUhKYi9BBMBCAAnBQJV+/07AhsDBQkB4TOABQsJCAcCBhUICQoL
-AgQWAgMBAh4BAheAAAoJEMrO2Za8FchatDYD/3Gs3stPqia02kNGenmm9afDzBRP
-AaXHoK6JMXyZ+tc6OyRAo0FvobaDgQSEB99praozvyf0m4gQ6d8TXHWmwp5KYVuI
-5HIJ/haFGywNxhZPX8bRJTQxlx3EDYpr4YcX6xK9jItcqNqykoYiDtDdssklwiSS
-AvQl6QwgCITXRGHguI0EVfv9OwEEANwSTeTdUSOrnrANN8cE1tQAU1eLW4y0Tii5
-Uz28W/k41kAH0Jc7W0mr840zusDz5h5xr9rFz4h9oLhE/rYSgMNVnISLSsfjIk/J
-57ASR5fedJqiPLkxxpY5EAbigEVZdRC0t3aOgkLxshvuah8pd3K6RA9edkOQvxed
-2+BH4DVxABEBAAGIpQQYAQgADwUCVfv9OwIbDAUJAeEzgAAKCRDKztmWvBXIWqex
-A/9nZUXs9BGcwpodhqjGY+H9/IUJua95jti9t0BleEu+h0R9O+XDEE/77IK9ET4f
-0t9WMfMhPO7ZIgUxFutB/Z7UMuyVteIvGxF/TTbQAKuCrnLYuPWkGiYjR9e0ZDbg
-mKrRZ/jwhdaxF0IHrR1PJLUnvO97qfZC7097/urCsWDMo5iNBFX8ElYBBACfcdcA
-cR6BJ2Ba3/HnQR1S0rG38bWq8Rdtt072hDd16oQCNFpQs5WQNruCCpobmB6yOmjK
-Jv8Cf9mxBdcQDxobcw6MlHPWZl04SoQKQOa5h6ptITxr+UFFFqfh7AZ7ZtDYaFfB
-qQX9fvdOX99C18SIcCcN0rHoxXfG7D/AaHEysQARAQABtBNUZXN0aW5nIChpbnNl
-Y3VyZSEpiL0EEwEIACcFAlX8ElYCGwMFCQHhM4AFCwkIBwIGFQgJCgsCBBYCAwEC
-HgECF4AACgkQPFqRXyGDg5pJ6wP/evdMoYzdxC5lkiXrSs77ezJVEtI+lJAOVbwz
-mwpgJvmw60EhncAyY+oLvPG+G4jRMQs/w5qOu0fWfoCxOixr82Aj2fhhP2UPVTKP
-wHlBGTuOAGu70cJ2BVNFZjCVHhkGwiXuCZy4xf/tMiIIfwrcBS9yf/vXmsn+nui/
-/VoVGeS4jQRV/BJWAQQA1857/OyeYvxDfHTLY88ffS17SXUid4iX6evXeVdRcoL7
-A0pTGVXpH5VihdoqR0bpcS13mvJh9LGpUL4kN9qjwMnwlqRCyy9rK01Xs4+p7SlR
-fYWa7kNndSgVVfrMqup0mPRIPNJh4s7yFeXEdm9l/1+qsmya8IVtdKFofnLi4OkA
-EQEAAYilBBgBCAAPBQJV/BJWAhsMBQkB4TOAAAoJEDxakV8hg4OarPEEAI0sL2PY
-Qm+iIqFEwBQlMYJC8NY1YAj/Yuf/rUW2q7n46g4jQ3u4vUuNY0euFL88wrBwi72J
-mf+5BX84aP1i4rDnxZ7IqRMgScPttTlpRCTyN1EhM+ev8ifIe+zbU/2GHXwxm8OT
-0v2sv67Ar70GR4vQwMvfCUAbnsVrL05eBjUJ
-=Btw1
------END PGP PUBLIC KEY BLOCK-----
index 91c9e78..20c130a 100755 (executable)
 (load (with-path "defs.scm"))
 (setup-environment)
 
- ;; Redefine GPG without --always-trust and a fixed time.
-(define GPG `(,(tool 'gpg) --no-permission-warning
-             --faked-system-time=1466684990))
+(define GPGTIME 1480943782)
+
+;; Generate a --faked-system-time parameter for a particular offset.
+(define (faketime delta)
+  (string-append "--faked-system-time=" (number->string (+ GPGTIME delta))))
+;; A convenience function for the above.
+(define (days->seconds days) (* days 24 60 60))
+
+;; Redefine GPG without --always-trust and a fixed time.
+(define GPG `(,(tool 'gpg) --no-permission-warning ,(faketime GPGTIME)))
 (define GNUPGHOME (getenv "GNUPGHOME"))
 (if (string=? "" GNUPGHOME)
-    (error "GNUPGHOME not set"))
+    (fail "GNUPGHOME not set"))
 
 (catch (skip "Tofu not supported")
        (call-check `(,@GPG --trust-model=tofu --list-config)))
 
-(define KEYS '("2183839A" "BC15C85A" "EE37CF96"))
+(define KEYS '("1C005AF3" "BE04EB2B" "B662E42F"))
 
 ;; Import the test keys.
-(call-check `(,@GPG --import ,(in-srcdir "tofu-keys.asc")))
-
-;; Make sure the keys are imported.
 (for-each (lambda (keyid)
-           (catch (error "Missing key" keyid)
+            (call-check `(,@GPG --import
+                                ,(in-srcdir "tofu/conflicting/"
+                                            (string-append keyid ".gpg"))))
+           (catch (fail "Missing key" keyid)
                   (call-check `(,@GPG --list-keys ,keyid))))
          KEYS)
 
@@ -52,7 +59,7 @@
                                   ,@args
                                   --list-keys ,keyid))) 5)))
     (unless (member policy '("auto" "good" "unknown" "bad" "ask"))
-           (error "Bad policy:" policy))
+           (fail "Bad policy:" policy))
     policy))
 
 ;; Check that KEYID's tofu policy matches EXPECTED-POLICY.  Any
@@ -62,7 +69,7 @@
 (define (checkpolicy keyid expected-policy . args)
   (let ((policy (apply getpolicy `(,keyid ,@args))))
     (unless (string=? policy expected-policy)
-           (error keyid ": Expected policy to be" expected-policy
+           (fail keyid ": Expected policy to be" expected-policy
                   "but got" policy))))
 
 ;; Get the trust level for KEYID.  Any remaining arguments are simply
@@ -77,7 +84,7 @@
                                   --list-keys ,keyid))) 1)))
     (unless (and (= 1 (string-length trust))
                 (member (string-ref trust 0) (string->list "oidreqnmfuws-")))
-           (error "Bad trust value:" trust))
+           (fail "Bad trust value:" trust))
     trust))
 
 ;; Check that KEYID's trust level matches EXPECTED-TRUST.  Any
@@ -87,7 +94,7 @@
 (define (checktrust keyid expected-trust . args)
   (let ((trust (apply gettrust `(,keyid ,@args))))
     (unless (string=? trust expected-trust)
-           (error keyid ": Expected trust to be" expected-trust
+           (fail keyid ": Expected trust to be" expected-trust
                   "but got" trust))))
 
 ;; Set key KEYID's policy to POLICY.  Any remaining arguments are
 ;; Verify a message.  There should be no conflict and the trust
 ;; policy should be set to auto.
 (call-check `(,@GPG --trust-model=tofu
-                   --verify ,(in-srcdir "tofu-2183839A-1.txt")))
+                   --verify ,(in-srcdir "tofu/conflicting/1C005AF3-1.txt")))
 
-(checkpolicy "2183839A" "auto")
+(checkpolicy "1C005AF3" "auto")
 ;; Check default trust.
-(checktrust "2183839A" "m")
+(checktrust "1C005AF3" "m")
 
 ;; Trust should be derived lazily.  Thus, if the policy is set to
 ;; auto and we change --tofu-default-policy, then the trust should
 ;; change as well.  Try it.
-(checktrust "2183839A" "f" '--tofu-default-policy=good)
-(checktrust "2183839A" "-" '--tofu-default-policy=unknown)
-(checktrust "2183839A" "n" '--tofu-default-policy=bad)
+(checktrust "1C005AF3" "f" '--tofu-default-policy=good)
+(checktrust "1C005AF3" "-" '--tofu-default-policy=unknown)
+(checktrust "1C005AF3" "n" '--tofu-default-policy=bad)
 
 ;; Change the policy to something other than auto and make sure the
 ;; policy and the trust are correct.
           ((string=? "good" policy) "f")
           ((string=? "unknown" policy) "-")
           (else "n"))))
-     (setpolicy "2183839A" policy)
+     (setpolicy "1C005AF3" policy)
 
      ;; Since we have a fixed policy, the trust level shouldn't
      ;; change if we change the default policy.
      (for-each-p
       ""
       (lambda (default-policy)
-       (checkpolicy "2183839A" policy
+       (checkpolicy "1C005AF3" policy
                     '--tofu-default-policy default-policy)
-       (checktrust "2183839A" expected-trust
+       (checktrust "1C005AF3" expected-trust
                    '--tofu-default-policy default-policy))
       '("auto" "good" "unknown" "bad" "ask"))))
  '("good" "unknown" "bad"))
 
-;; BC15C85A conflicts with 2183839A.  On conflict, this will set
-;; BC15C85A to ask.  If 2183839A is auto (it's not, it's bad), then
-;; it will be set to ask.
+;; At the end, 1C005AF3's policy should be bad.
+(checkpolicy "1C005AF3" "bad")
+
+;; 1C005AF3 and BE04EB2B conflict.  A policy setting of "auto"
+;; (BE04EB2B's state) will result in an effective policy of ask.  But,
+;; a policy setting of "bad" will result in an effective policy of
+;; bad.
+(setpolicy "BE04EB2B" "auto")
+(checkpolicy "BE04EB2B" "ask")
+(checkpolicy "1C005AF3" "bad")
+
+;; 1C005AF3, B662E42F, and BE04EB2B conflict.  We change BE04EB2B's
+;; policy to auto and leave 1C005AF3's policy at bad.  This conflict
+;; should cause BE04EB2B's effective policy to be ask (since it is
+;; auto), but not affect 1C005AF3's policy.
+(setpolicy "BE04EB2B" "auto")
+(checkpolicy "BE04EB2B" "ask")
+(call-check `(,@GPG --trust-model=tofu
+                   --verify ,(in-srcdir "tofu/conflicting/B662E42F-1.txt")))
+(checkpolicy "BE04EB2B" "ask")
+(checkpolicy "1C005AF3" "bad")
+(checkpolicy "B662E42F" "ask")
+
+;; Check that the stats are emitted correctly.
+
+(display "Checking TOFU stats...\n")
+
+(define (check-counts keyid expected-sigs expected-sig-days
+                      expected-encs expected-enc-days . args)
+  (let*
+      ((tfs (assoc "tfs"
+                   (gpg-with-colons
+                    `(--trust-model=tofu --with-tofu-info
+                                         ,@args --list-keys ,keyid))))
+       (sigs (string->number (list-ref tfs 3)))
+       (sig-days (string->number (list-ref tfs 11)))
+       (encs (string->number (list-ref tfs 4)))
+       (enc-days (string->number (list-ref tfs 12)))
+       )
+    ; (display keyid) (display ": ") (display tfs) (display "\n")
+    (unless (= sigs expected-sigs)
+            (fail keyid ": # signatures (" sigs ") does not match expected"
+                   "# signatures (" expected-sigs ").\n"))
+    (unless (= sig-days expected-sig-days)
+            (fail keyid ": # signature days (" sig-days ")"
+                  "does not match expected"
+                  "# signature days (" expected-sig-days ").\n"))
+    (unless (= encs expected-encs)
+            (fail keyid ": # encryptions (" encs ") does not match expected"
+                   "# encryptions (" expected-encs ").\n"))
+    (unless (= enc-days expected-enc-days)
+            (fail keyid ": # encryption days (" encs ")"
+                  "does not match expected"
+                  "# encryption days (" expected-enc-days ").\n"))
+    ))
+
+;; Carefully remove the TOFU db.
+(catch '() (unlink (string-append GNUPGHOME "/tofu.db")))
+
+(check-counts "1C005AF3" 0 0 0 0)
+(check-counts "BE04EB2B" 0 0 0 0)
+(check-counts "B662E42F" 0 0 0 0)
+
+;; Verify a message.  The signature count should increase by 1.
 (call-check `(,@GPG --trust-model=tofu
-                   --verify ,(in-srcdir "tofu-BC15C85A-1.txt")))
-(checkpolicy "BC15C85A" "ask")
-(checkpolicy "2183839A" "bad")
-
-;; EE37CF96 conflicts with 2183839A and BC15C85A.  We change
-;; BC15C85A's policy to auto and leave 2183839A's policy at bad.
-;; This conflict should cause BC15C85A's policy to be changed to
-;; ask (since it is auto), but not affect 2183839A's policy.
-(setpolicy "BC15C85A" "auto")
-(checkpolicy "BC15C85A" "auto")
+                   --verify ,(in-srcdir "tofu/conflicting/1C005AF3-1.txt")))
+
+(check-counts "1C005AF3" 1 1 0 0)
+
+;; Verify the same message.  The signature count should remain the
+;; same.
 (call-check `(,@GPG --trust-model=tofu
-                   --verify ,(in-srcdir "tofu-EE37CF96-1.txt")))
-(checkpolicy "BC15C85A" "ask")
-(checkpolicy "2183839A" "bad")
-(checkpolicy "EE37CF96" "ask")
+                   --verify ,(in-srcdir "tofu/conflicting/1C005AF3-1.txt")))
+(check-counts "1C005AF3" 1 1 0 0)
 
+;; Verify another message.
+(call-check `(,@GPG --trust-model=tofu
+                   --verify ,(in-srcdir "tofu/conflicting/1C005AF3-2.txt")))
+(check-counts "1C005AF3" 2 1 0 0)
 
+;; Verify another message.
+(call-check `(,@GPG --trust-model=tofu
+                   --verify ,(in-srcdir "tofu/conflicting/1C005AF3-3.txt")))
+(check-counts "1C005AF3" 3 1 0 0)
+
+;; Verify a message from a different sender.  The signature count
+;; should increase by 1 for that key.
+(call-check `(,@GPG --trust-model=tofu
+                   --verify ,(in-srcdir "tofu/conflicting/BE04EB2B-1.txt")))
+(check-counts "1C005AF3" 3 1 0 0)
+(check-counts "BE04EB2B" 1 1 0 0)
+(check-counts "B662E42F" 0 0 0 0)
+
+;; Verify another message on a new day.  (Recall: we are interested in
+;; when the message was first verified, not when the signer claimed
+;; that it was signed.)
+(call-check `(,@GPG --trust-model=tofu ,(faketime (days->seconds 2))
+                   --verify ,(in-srcdir "tofu/conflicting/1C005AF3-4.txt")))
+(check-counts "1C005AF3" 4 2 0 0)
+(check-counts "BE04EB2B" 1 1 0 0)
+(check-counts "B662E42F" 0 0 0 0)
+
+;; And another.
+(call-check `(,@GPG --trust-model=tofu ,(faketime (days->seconds 2))
+                   --verify ,(in-srcdir "tofu/conflicting/1C005AF3-5.txt")))
+(check-counts "1C005AF3" 5 2 0 0)
+(check-counts "BE04EB2B" 1 1 0 0)
+(check-counts "B662E42F" 0 0 0 0)
+
+;; Another, but for a different key.
+(call-check `(,@GPG --trust-model=tofu ,(faketime (days->seconds 2))
+                   --verify ,(in-srcdir "tofu/conflicting/BE04EB2B-2.txt")))
+(check-counts "1C005AF3" 5 2 0 0)
+(check-counts "BE04EB2B" 2 2 0 0)
+(check-counts "B662E42F" 0 0 0 0)
+
+;; And add a third day.
+(call-check `(,@GPG --trust-model=tofu ,(faketime (days->seconds 4))
+                   --verify ,(in-srcdir "tofu/conflicting/BE04EB2B-3.txt")))
+(check-counts "1C005AF3" 5 2 0 0)
+(check-counts "BE04EB2B" 3 3 0 0)
+(check-counts "B662E42F" 0 0 0 0)
+
+(call-check `(,@GPG --trust-model=tofu ,(faketime (days->seconds 4))
+                   --verify ,(in-srcdir "tofu/conflicting/BE04EB2B-4.txt")))
+(check-counts "1C005AF3" 5 2 0 0)
+(check-counts "BE04EB2B" 4 3 0 0)
+(check-counts "B662E42F" 0 0 0 0)
 
 ;; Check that we detect the following attack:
 ;;
 (checkpolicy KEYA "ask")
 (checkpolicy KEYB "ask")
 
-;; Import Alice's signature on the conflicting user id.
+;; Import Alice's signature on the conflicting user id.  Since there
+;; is now a cross signature, we should revert to the default policy.
 (display "    > Adding cross signature on user id. ")
 (call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-4.gpg"))))
 (verify-messages)
 ;; Alice has an ultimately trusted key and she signs Bob's key.  Then
 ;; Bob adds a new user id, "Alice".  TOFU should now detect a
 ;; conflict, because Alice only signed Bob's "Bob" user id.
+;;
+;;
+;; The Alice key:
+;;   pub   rsa2048 2016-10-11 [SC]
+;;         1938C3A0E4674B6C217AC0B987DB2814EC38277E
+;;   uid           [ultimate] Spy Cow <spy@cow.com>
+;;   sub   rsa2048 2016-10-11 [E]
+;;
+;; The Bob key:
+;;
+;;   pub   rsa2048 2016-10-11 [SC]
+;;         DC463A16E42F03240D76E8BA8B48C6BD871C2247
+;;   uid           [  full  ] Spy R. Cow <spy@cow.com>
+;;   uid           [  full  ] Spy R. Cow <spy@cow.de>
+;;   sub   rsa2048 2016-10-11 [E]
 
 (display "Checking UTK sigs...\n")
 (define GPG `(,(tool 'gpg) --no-permission-warning
 (call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-1.gpg"))))
 (display "<\n")
 
+(checkpolicy KEYA "auto")
+(checkpolicy KEYB "auto")
+
 ;; Import the cross sigs.
 (display "    > Adding cross signatures. ")
 (call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDA "-2.gpg"))))
 (call-check `(,@GPG --import ,(in-srcdir DIR (string-append KEYIDB "-2.gpg"))))
 (display "<\n")
 
+(checkpolicy KEYA "auto")
+(checkpolicy KEYB "auto")
+
 ;; Make KEYA ultimately trusted.
 (display (string-append "    > Marking " KEYA " as ultimately trusted. "))
 (pipe:do
diff --git a/tests/openpgp/tofu/conflicting/1C005AF3-1.txt b/tests/openpgp/tofu/conflicting/1C005AF3-1.txt
new file mode 100644 (file)
index 0000000..dba581d
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/1C005AF3-1.txt differ
diff --git a/tests/openpgp/tofu/conflicting/1C005AF3-2.txt b/tests/openpgp/tofu/conflicting/1C005AF3-2.txt
new file mode 100644 (file)
index 0000000..fde9fb8
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/1C005AF3-2.txt differ
diff --git a/tests/openpgp/tofu/conflicting/1C005AF3-3.txt b/tests/openpgp/tofu/conflicting/1C005AF3-3.txt
new file mode 100644 (file)
index 0000000..e6aa4ac
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/1C005AF3-3.txt differ
diff --git a/tests/openpgp/tofu/conflicting/1C005AF3-4.txt b/tests/openpgp/tofu/conflicting/1C005AF3-4.txt
new file mode 100644 (file)
index 0000000..6a14891
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/1C005AF3-4.txt differ
diff --git a/tests/openpgp/tofu/conflicting/1C005AF3-5.txt b/tests/openpgp/tofu/conflicting/1C005AF3-5.txt
new file mode 100644 (file)
index 0000000..12fb5fb
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/1C005AF3-5.txt differ
diff --git a/tests/openpgp/tofu/conflicting/1C005AF3-secret.gpg b/tests/openpgp/tofu/conflicting/1C005AF3-secret.gpg
new file mode 100644 (file)
index 0000000..5f1e78a
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/1C005AF3-secret.gpg differ
diff --git a/tests/openpgp/tofu/conflicting/1C005AF3.gpg b/tests/openpgp/tofu/conflicting/1C005AF3.gpg
new file mode 100644 (file)
index 0000000..7a75011
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/1C005AF3.gpg differ
diff --git a/tests/openpgp/tofu/conflicting/B662E42F-1.txt b/tests/openpgp/tofu/conflicting/B662E42F-1.txt
new file mode 100644 (file)
index 0000000..c39056c
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/B662E42F-1.txt differ
diff --git a/tests/openpgp/tofu/conflicting/B662E42F-2.txt b/tests/openpgp/tofu/conflicting/B662E42F-2.txt
new file mode 100644 (file)
index 0000000..a96ef9f
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/B662E42F-2.txt differ
diff --git a/tests/openpgp/tofu/conflicting/B662E42F-3.txt b/tests/openpgp/tofu/conflicting/B662E42F-3.txt
new file mode 100644 (file)
index 0000000..2e6e81b
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/B662E42F-3.txt differ
diff --git a/tests/openpgp/tofu/conflicting/B662E42F-4.txt b/tests/openpgp/tofu/conflicting/B662E42F-4.txt
new file mode 100644 (file)
index 0000000..470882f
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/B662E42F-4.txt differ
diff --git a/tests/openpgp/tofu/conflicting/B662E42F-5.txt b/tests/openpgp/tofu/conflicting/B662E42F-5.txt
new file mode 100644 (file)
index 0000000..21d54bc
--- /dev/null
@@ -0,0 +1 @@
\ 1\9bÀËÌÀÁ¨%¶¼\7f\13}ÆÓ\1cI\f\11®i\92¦\\9d\8cÆ,\f\8c\1c\f²b\8a,ù&ÖkÏhí³\89ÒÓ\8c\86)ee\ 2©càâ\14\80\89h&q0lÊ;S\93ɵ´`ÅâKË\14¾>\9e¾}iÚ\1fñªwò²ö5?VÙðF+\ 6'      |]ó$í/\9fôj×m\95³ÓJÄ\1fß\96TjY\19\91À¤Á£<ÿø\99i»½6%Æ|ªÿ÷¢aûÿGVkgË4e\9dµ<akï\11¿õ\14ÓB\eî U£o¿ffø\17öZ<Úñ\8d\8dU[ÕÔhþÞJe\81\15Ò\1fÂníóÜýÞë¤\99þ\9c\9aâ\9c\rf~÷[\94dm"v²\95?P\7f3}=×ü\8c\8c}Ó#"ò^µ±¬\9dj-µ\91×ùTÒ\81²Õ\9c+/q­=±&­Ü>̬¸²å\81üëÏÕÕ%îíkºzù\8aNëìýlʳê\14×\9e\9f\9a\9e©:æü\91\9d\8aÏ7\16\9b\9eÎÐ\\eoÞµs\99úJÅs1éÚñû\ 1
\ No newline at end of file
diff --git a/tests/openpgp/tofu/conflicting/B662E42F-secret.gpg b/tests/openpgp/tofu/conflicting/B662E42F-secret.gpg
new file mode 100644 (file)
index 0000000..7362ded
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/B662E42F-secret.gpg differ
diff --git a/tests/openpgp/tofu/conflicting/B662E42F.gpg b/tests/openpgp/tofu/conflicting/B662E42F.gpg
new file mode 100644 (file)
index 0000000..6c07520
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/B662E42F.gpg differ
diff --git a/tests/openpgp/tofu/conflicting/BE04EB2B-1.txt b/tests/openpgp/tofu/conflicting/BE04EB2B-1.txt
new file mode 100644 (file)
index 0000000..1b3de47
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/BE04EB2B-1.txt differ
diff --git a/tests/openpgp/tofu/conflicting/BE04EB2B-2.txt b/tests/openpgp/tofu/conflicting/BE04EB2B-2.txt
new file mode 100644 (file)
index 0000000..f4f5487
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/BE04EB2B-2.txt differ
diff --git a/tests/openpgp/tofu/conflicting/BE04EB2B-3.txt b/tests/openpgp/tofu/conflicting/BE04EB2B-3.txt
new file mode 100644 (file)
index 0000000..7451073
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/BE04EB2B-3.txt differ
diff --git a/tests/openpgp/tofu/conflicting/BE04EB2B-4.txt b/tests/openpgp/tofu/conflicting/BE04EB2B-4.txt
new file mode 100644 (file)
index 0000000..f15496d
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/BE04EB2B-4.txt differ
diff --git a/tests/openpgp/tofu/conflicting/BE04EB2B-5.txt b/tests/openpgp/tofu/conflicting/BE04EB2B-5.txt
new file mode 100644 (file)
index 0000000..39078f1
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/BE04EB2B-5.txt differ
diff --git a/tests/openpgp/tofu/conflicting/BE04EB2B-secret.gpg b/tests/openpgp/tofu/conflicting/BE04EB2B-secret.gpg
new file mode 100644 (file)
index 0000000..5d393aa
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/BE04EB2B-secret.gpg differ
diff --git a/tests/openpgp/tofu/conflicting/BE04EB2B.gpg b/tests/openpgp/tofu/conflicting/BE04EB2B.gpg
new file mode 100644 (file)
index 0000000..787b238
Binary files /dev/null and b/tests/openpgp/tofu/conflicting/BE04EB2B.gpg differ
diff --git a/tests/openpgp/tofu/conflicting/README b/tests/openpgp/tofu/conflicting/README
new file mode 100644 (file)
index 0000000..e2c48f2
--- /dev/null
@@ -0,0 +1,8 @@
+This directory contains three keys (1C005AF3, B662E42F, and BE04EB2B),
+which all have the same user id, namely "Joke Factory
+<joke.factory@example.com>".
+
+The keys are stored in KEYID.gpg.  The secret key material is stored
+in KEYID-secret.gpg (the secret key material is not password
+protected).  The files KEYID-N.txt contain messages, which are signed
+by KEYID.  The message is "N\n".
diff --git a/tests/openpgp/verify-multifile.scm b/tests/openpgp/verify-multifile.scm
new file mode 100755 (executable)
index 0000000..f1cbe99
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env gpgscm
+
+;; Copyright (C) 2016 g10 Code GmbH
+;;
+;; 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 <http://www.gnu.org/licenses/>.
+
+(load (with-path "defs.scm"))
+(setup-legacy-environment)
+
+(define files '("clearsig-1-key-1.asc" "signed-1-key-1.asc"))
+
+(info "Checking verification of supplied files using --multifile.")
+
+(let* ((status
+       (call-popen
+        `(,@gpg --verify --multifile --status-fd=1
+                ,@(map (lambda (name) (in-srcdir "samplemsgs" name)) files))
+        ""))
+       (lines (map (lambda (l)
+                    (assert (string-prefix? l "[GNUPG:] "))
+                    ;; Split, and strip the prefix.
+                    (cdr (string-split l #\space)))
+                  (string-split-newlines status))))
+  (assert
+   (= 2 (length (filter (lambda (l)
+                         (and (equal? (car l) "GOODSIG")
+                              (equal? (caddr l) "steve.biko@example.net")))
+                       lines)))))
index 2c2c14a..a398a14 100755 (executable)
@@ -33,7 +33,7 @@
        (display (make-string 64 (integer->char (string->number char)))
                 port)))
      (if (= 0 (call `(,@GPG --verify ,x data-500)))
-        (error "no error code from verify"))))
+        (fail "no error code from verify"))))
  '("#x2d" "#xca"))
 
 ;; A plain signed message created using
@@ -324,7 +324,7 @@ GisM
           (pipe:defer (lambda (sink)
                         (display armored-file (fdopen sink "w"))))
           (pipe:spawn `(,@GPG --verify)))
-         (error "verification succeeded but should not")))
+         (fail "verification succeeded but should not")))
  '(msg_olsols_asc_multiple msg_clsclss_asc_multiple))
 
 (for-each-p
@@ -334,7 +334,7 @@ GisM
          (pipe:do
           (pipe:echo (eval armored-file (current-environment)))
           (pipe:spawn `(,@GPG --verify)))
-         (error "verification succeeded but should not")))
+         (fail "verification succeeded but should not")))
  '(bad_ls_asc bad_fols_asc bad_olsf_asc bad_ools_asc))
 
 
index 94b53f2..38abd7c 100644 (file)
@@ -155,7 +155,7 @@ gpg_wks_server_SOURCES = \
 
 gpg_wks_server_CFLAGS = $(GPG_ERROR_CFLAGS) $(INCICONV)
 gpg_wks_server_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
-                      $(LIBICONV)
+                      $(LIBINTL) $(LIBICONV)
 
 gpg_wks_client_SOURCES = \
        gpg-wks-client.c \
@@ -171,7 +171,7 @@ gpg_wks_client_SOURCES = \
 gpg_wks_client_CFLAGS = $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV)
 gpg_wks_client_LDADD = $(libcommon) \
                       $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
-                      $(LIBICONV)
+                      $(LIBINTL) $(LIBICONV)
 
 
 # Make sure that all libs are build before we use them.  This is
index c5ee244..51f1fa1 100644 (file)
@@ -258,3 +258,55 @@ wkd_get_policy_flags (const char *addrspec, estream_t *r_buffer)
   assuan_release (ctx);
   return err;
 }
+
+
+/* Ask the dirmngr for the key for ADDRSPEC.  On success a stream with
+ * the key is stored at R_KEY.  */
+gpg_error_t
+wkd_get_key (const char *addrspec, estream_t *r_key)
+{
+  gpg_error_t err;
+  assuan_context_t ctx;
+  struct wkd_get_parm_s parm;
+  char *line = NULL;
+
+  memset (&parm, 0, sizeof parm);
+  *r_key = NULL;
+
+  err = connect_dirmngr (&ctx);
+  if (err)
+    return err;
+
+  line = es_bsprintf ("WKD_GET -- %s", addrspec);
+  if (!line)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
+    {
+      err = gpg_error (GPG_ERR_TOO_LARGE);
+      goto leave;
+    }
+
+  parm.memfp = es_fopenmem (0, "rwb");
+  if (!parm.memfp)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  err = assuan_transact (ctx, line, wkd_get_data_cb, &parm,
+                         NULL, NULL, wkd_get_status_cb, &parm);
+  if (err)
+    goto leave;
+
+  es_rewind (parm.memfp);
+  *r_key = parm.memfp;
+  parm.memfp = NULL;
+
+ leave:
+  es_fclose (parm.memfp);
+  xfree (line);
+  assuan_release (ctx);
+  return err;
+}
index 83ebd2c..32486b1 100644 (file)
@@ -25,5 +25,7 @@ gpg_error_t wkd_get_submission_address (const char *addrspec,
                                         char **r_addrspec);
 gpg_error_t wkd_get_policy_flags (const char *addrspec, estream_t *r_buffer);
 
+gpg_error_t wkd_get_key (const char *addrspec, estream_t *r_key);
+
 
 #endif /*GNUPG_TOOLS_CALL_DIRMNGR_H*/
index 9bf5403..0f90424 100644 (file)
@@ -23,6 +23,7 @@
 #include <string.h>
 
 #include "util.h"
+#include "status.h"
 #include "i18n.h"
 #include "sysutils.h"
 #include "init.h"
@@ -50,6 +51,7 @@ enum cmd_and_opt_values
     oDebug      = 500,
 
     aSupported,
+    aCheck,
     aCreate,
     aReceive,
     aRead,
@@ -57,6 +59,7 @@ enum cmd_and_opt_values
     oGpgProgram,
     oSend,
     oFakeSubmissionAddr,
+    oStatusFD,
 
     oDummy
   };
@@ -68,6 +71,8 @@ static ARGPARSE_OPTS opts[] = {
 
   ARGPARSE_c (aSupported, "supported",
               ("check whether provider supports WKS")),
+  ARGPARSE_c (aCheck, "check",
+              ("check whether a key is available")),
   ARGPARSE_c (aCreate,   "create",
               ("create a publication request")),
   ARGPARSE_c (aReceive,   "receive",
@@ -83,6 +88,7 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
   ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
   ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
+  ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
 
   ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"),
 
@@ -111,6 +117,7 @@ const char *fake_submission_addr;
 
 static void wrong_args (const char *text) GPGRT_ATTR_NORETURN;
 static gpg_error_t command_supported (char *userid);
+static gpg_error_t command_check (char *userid);
 static gpg_error_t command_send (const char *fingerprint, char *userid);
 static gpg_error_t encrypt_response (estream_t *r_output, estream_t input,
                                      const char *addrspec,
@@ -193,11 +200,15 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
         case oFakeSubmissionAddr:
           fake_submission_addr = pargs->r.ret_str;
           break;
+        case oStatusFD:
+          wks_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
+          break;
 
        case aSupported:
        case aCreate:
        case aReceive:
        case aRead:
+        case aCheck:
           cmd = pargs->r_opt;
           break;
 
@@ -290,11 +301,24 @@ main (int argc, char **argv)
         log_error ("processing mail failed: %s\n", gpg_strerror (err));
       break;
 
+    case aCheck:
+      if (argc != 1)
+        wrong_args ("--check USER-ID");
+      err = command_check (argv[0]);
+      break;
+
     default:
       usage (1);
+      err = 0;
       break;
     }
 
+  if (err)
+    wks_write_status (STATUS_FAILURE, "- %u", err);
+  else if (log_get_errorcount (0))
+    wks_write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
+  else
+    wks_write_status (STATUS_SUCCESS, NULL);
   return log_get_errorcount (0)? 1:0;
 }
 
@@ -532,6 +556,96 @@ command_supported (char *userid)
 
 
 \f
+/* Check whether the key for USERID is available in the WKD.  */
+static gpg_error_t
+command_check (char *userid)
+{
+  gpg_error_t err;
+  char *addrspec = NULL;
+  estream_t key = NULL;
+  char *fpr = NULL;
+  strlist_t mboxes = NULL;
+  strlist_t sl;
+  int found = 0;
+
+  addrspec = mailbox_from_userid (userid);
+  if (!addrspec)
+    {
+      log_error (_("\"%s\" is not a proper mail address\n"), userid);
+      err = gpg_error (GPG_ERR_INV_USER_ID);
+      goto leave;
+    }
+
+  /* Get the submission address.  */
+  err = wkd_get_key (addrspec, &key);
+  switch (gpg_err_code (err))
+    {
+    case 0:
+      if (opt.verbose)
+        log_info ("public key for '%s' found via WKD\n", addrspec);
+      /* Fixme: Check that the key contains the user id.  */
+      break;
+
+    case GPG_ERR_NO_DATA: /* No such key.  */
+      if (opt.verbose)
+        log_info ("public key for '%s' NOT found via WKD\n", addrspec);
+      err = gpg_error (GPG_ERR_NO_PUBKEY);
+      log_inc_errorcount ();
+      break;
+
+    case GPG_ERR_UNKNOWN_HOST:
+      if (opt.verbose)
+        log_info ("error looking up '%s' via WKD: %s\n",
+                  addrspec, gpg_strerror (err));
+      err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+      break;
+
+    default:
+      log_error ("error looking up '%s' via WKD: %s\n",
+                 addrspec, gpg_strerror (err));
+      break;
+    }
+
+  if (err)
+    goto leave;
+
+  /* Look closer at the key.  */
+  err = wks_list_key (key, &fpr, &mboxes);
+  if (err || !fpr)
+    {
+      log_error ("error parsing key: %s\n",
+                 err? gpg_strerror (err) : "no fingerprint found");
+      err = gpg_error (GPG_ERR_NO_PUBKEY);
+      goto leave;
+    }
+
+  if (opt.verbose)
+    log_info ("fingerprint: %s\n", fpr);
+
+  for (sl = mboxes; sl; sl = sl->next)
+    {
+      if (!strcmp (sl->d, addrspec))
+        found = 1;
+      if (opt.verbose)
+        log_info ("  addr-spec: %s\n", sl->d);
+    }
+  if (!found)
+    {
+      log_error ("public key for '%s' has no user id with the mail address\n",
+                 addrspec);
+      err = gpg_error (GPG_ERR_CERT_REVOKED);
+    }
+
+ leave:
+  xfree (fpr);
+  free_strlist (mboxes);
+  es_fclose (key);
+  xfree (addrspec);
+  return err;
+}
+
+
+\f
 /* Locate the key by fingerprint and userid and send a publication
  * request.  */
 static gpg_error_t
index fd65b40..1a91858 100644 (file)
@@ -348,168 +348,6 @@ main (int argc, char **argv)
 }
 
 
-\f
-static void
-list_key_status_cb (void *opaque, const char *keyword, char *args)
-{
-  server_ctx_t ctx = opaque;
-  (void)ctx;
-  if (DBG_CRYPTO)
-    log_debug ("gpg status: %s %s\n", keyword, args);
-}
-
-
-static gpg_error_t
-list_key (server_ctx_t ctx, estream_t key)
-{
-  gpg_error_t err;
-  ccparray_t ccp;
-  const char **argv;
-  estream_t listing;
-  char *line = NULL;
-  size_t length_of_line = 0;
-  size_t  maxlen;
-  ssize_t len;
-  char **fields = NULL;
-  int nfields;
-  int lnr;
-  char *mbox = NULL;
-
-  /* We store our results in the context - clear it first.  */
-  xfree (ctx->fpr);
-  ctx->fpr = NULL;
-  free_strlist (ctx->mboxes);
-  ctx->mboxes = NULL;
-
-  /* Open a memory stream.  */
-  listing = es_fopenmem (0, "w+b");
-  if (!listing)
-    {
-      err = gpg_error_from_syserror ();
-      log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
-      return err;
-    }
-
-  ccparray_init (&ccp, 0);
-
-  ccparray_put (&ccp, "--no-options");
-  if (!opt.verbose)
-    ccparray_put (&ccp, "--quiet");
-  else if (opt.verbose > 1)
-    ccparray_put (&ccp, "--verbose");
-  ccparray_put (&ccp, "--batch");
-  ccparray_put (&ccp, "--status-fd=2");
-  ccparray_put (&ccp, "--always-trust");
-  ccparray_put (&ccp, "--with-colons");
-  ccparray_put (&ccp, "--dry-run");
-  ccparray_put (&ccp, "--import-options=import-minimal,import-show");
-  ccparray_put (&ccp, "--import");
-
-  ccparray_put (&ccp, NULL);
-  argv = ccparray_get (&ccp, NULL);
-  if (!argv)
-    {
-      err = gpg_error_from_syserror ();
-      goto leave;
-    }
-  err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
-                                NULL, listing,
-                                list_key_status_cb, ctx);
-  if (err)
-    {
-      log_error ("import failed: %s\n", gpg_strerror (err));
-      goto leave;
-    }
-
-  es_rewind (listing);
-  lnr = 0;
-  maxlen = 2048; /* Set limit.  */
-  while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0)
-    {
-      lnr++;
-      if (!maxlen)
-        {
-          log_error ("received line too long\n");
-          err = gpg_error (GPG_ERR_LINE_TOO_LONG);
-          goto leave;
-        }
-      /* Strip newline and carriage return, if present.  */
-      while (len > 0
-            && (line[len - 1] == '\n' || line[len - 1] == '\r'))
-       line[--len] = '\0';
-      /* log_debug ("line '%s'\n", line); */
-
-      xfree (fields);
-      fields = strtokenize (line, ":");
-      if (!fields)
-        {
-          err = gpg_error_from_syserror ();
-          log_error ("strtokenize failed: %s\n", gpg_strerror (err));
-          goto leave;
-        }
-      for (nfields = 0; fields[nfields]; nfields++)
-        ;
-      if (!nfields)
-        {
-          err = gpg_error (GPG_ERR_INV_ENGINE);
-          goto leave;
-        }
-      if (!strcmp (fields[0], "sec"))
-        {
-          /* gpg may return "sec" as the first record - but we do not
-           * accept secret keys.  */
-          err = gpg_error (GPG_ERR_NO_PUBKEY);
-          goto leave;
-        }
-      if (lnr == 1 && strcmp (fields[0], "pub"))
-        {
-          /* First record is not a public key.  */
-          err = gpg_error (GPG_ERR_INV_ENGINE);
-          goto leave;
-        }
-      if (lnr > 1 && !strcmp (fields[0], "pub"))
-        {
-          /* More than one public key.  */
-          err = gpg_error (GPG_ERR_TOO_MANY);
-          goto leave;
-        }
-      if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb"))
-        break; /* We can stop parsing here.  */
-
-      if (!strcmp (fields[0], "fpr") && nfields > 9 && !ctx->fpr)
-        {
-          ctx->fpr = xtrystrdup (fields[9]);
-          if (!ctx->fpr)
-            {
-              err = gpg_error_from_syserror ();
-              goto leave;
-            }
-        }
-      else if (!strcmp (fields[0], "uid") && nfields > 9)
-        {
-          /* Fixme: Unescape fields[9] */
-          xfree (mbox);
-          mbox = mailbox_from_userid (fields[9]);
-          if (mbox && !append_to_strlist_try (&ctx->mboxes, mbox))
-            {
-              err = gpg_error_from_syserror ();
-              goto leave;
-            }
-        }
-    }
-  if (len < 0 || es_ferror (listing))
-    log_error ("error reading memory stream\n");
-
- leave:
-  xfree (mbox);
-  xfree (fields);
-  es_free (line);
-  xfree (argv);
-  es_fclose (listing);
-  return err;
-}
-
-
 /* Take the key in KEYFILE and write it to OUTFILE in binary encoding.
  * If ADDRSPEC is given only matching user IDs are included in the
  * output.  */
@@ -1216,7 +1054,9 @@ process_new_key (server_ctx_t ctx, estream_t key)
   struct policy_flags_s policybuf;
 
   /* First figure out the user id from the key.  */
-  err = list_key (ctx, key);
+  xfree (ctx->fpr);
+  free_strlist (ctx->mboxes);
+  err = wks_list_key (key, &ctx->fpr, &ctx->mboxes);
   if (err)
     goto leave;
   if (!ctx->fpr)
@@ -1457,7 +1297,9 @@ check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
     }
 
   /* We need to get the fingerprint from the key.  */
-  err = list_key (ctx, key);
+  xfree (ctx->fpr);
+  free_strlist (ctx->mboxes);
+  err = wks_list_key (key, &ctx->fpr, &ctx->mboxes);
   if (err)
     goto leave;
   if (!ctx->fpr)
index f7cccb3..62ceb34 100644 (file)
@@ -65,6 +65,9 @@ typedef struct policy_flags_s *policy_flags_t;
 
 
 /*-- wks-util.c --*/
+void wks_set_status_fd (int fd);
+void wks_write_status (int no, const char *format, ...) GPGRT_ATTR_PRINTF(2,3);
+gpg_error_t wks_list_key (estream_t key, char **r_fpr, strlist_t *r_mboxes);
 gpg_error_t wks_send_mime (mime_maker_t mime);
 gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream,
                               int ignore_unknown);
index cd99c81..925f1cf 100644 (file)
@@ -1,6 +1,6 @@
 /* gpgconf-comp.c - Configuration utility for GnuPG.
- * Copyright (C) 2004, 2007, 2008, 2009, 2010,
- *               2011 Free Software Foundation, Inc.
+ * Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc.
+ * Copyright (C) 2016 Werner Koch
  *
  * This file is part of GnuPG.
  *
@@ -703,9 +703,15 @@ static gc_option_t gc_options_gpg[] =
    { "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED,
      "gnupg", N_("|SPEC|set up email aliases"),
      GC_ARG_TYPE_ALIAS_LIST, GC_BACKEND_GPG },
-   { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
-     "gnupg", "|FILE|read options from FILE",
+   { "options", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
+     NULL, NULL,
      GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
+   { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
+     NULL, NULL,
+     GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
+   { "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
+     NULL, NULL,
+     GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
    { "default_pubkey_algo",
      (GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE,
      NULL, NULL,
@@ -816,6 +822,9 @@ static gc_option_t gc_options_gpgsm[] =
    { "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
      "gnupg", "never consult a CRL",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
+   { "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
+     NULL, NULL,
+     GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
    { "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
      "gnupg", N_("do not check CRLs for root certificates"),
      GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
@@ -880,6 +889,12 @@ static gc_option_t gc_options_dirmngr[] =
    { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
      "dirmngr", "|FILE|read options from FILE",
      GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
+   { "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
+     NULL, NULL,
+     GC_ARG_TYPE_INT32, GC_BACKEND_DIRMNGR },
+   { "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
+     NULL, NULL,
+     GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
 
    { "Debug",
      GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
@@ -2286,9 +2301,6 @@ gc_component_retrieve_options (int component)
   gc_backend_t backend;
   gc_option_t *option;
 
-  if (component == GC_COMPONENT_PINENTRY)
-    return; /* Dummy module for now.  */
-
   for (backend = 0; backend < GC_BACKEND_NR; backend++)
     backend_seen[backend] = 0;
 
@@ -2301,6 +2313,9 @@ gc_component_retrieve_options (int component)
 
   do
     {
+      if (component == GC_COMPONENT_PINENTRY)
+        continue; /* Skip this dummy component.  */
+
       option = gc_component[component].options;
 
       while (option && option->name)
@@ -2333,11 +2348,13 @@ gc_component_retrieve_options (int component)
 
 \f
 /* Perform a simple validity check based on the type.  Return in
-   NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of
-   type GC_ARG_TYPE_NONE.  */
+ * NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of
+ * type GC_ARG_TYPE_NONE.  If VERBATIM is set the profile parsing mode
+ * is used. */
 static void
 option_check_validity (gc_option_t *option, unsigned long flags,
-                      char *new_value, unsigned long *new_value_nr)
+                      char *new_value, unsigned long *new_value_nr,
+                       int verbatim)
 {
   char *arg;
 
@@ -2391,17 +2408,17 @@ option_check_validity (gc_option_t *option, unsigned long flags,
   arg = new_value;
   do
     {
-      if (*arg == '\0' || *arg == ',')
+      if (*arg == '\0' || (*arg == ',' && !verbatim))
        {
          if (!(option->flags & GC_OPT_FLAG_ARG_OPT))
            gc_error (1, 0, "argument required for option %s", option->name);
 
-         if (*arg == ',' && !(option->flags & GC_OPT_FLAG_LIST))
+         if (*arg == ',' && !verbatim && !(option->flags & GC_OPT_FLAG_LIST))
            gc_error (1, 0, "list found for non-list option %s", option->name);
        }
       else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING)
        {
-         if (*arg != '"')
+         if (*arg != '"' && !verbatim)
            gc_error (1, 0, "string argument for option %s must begin "
                      "with a quote (\") character", option->name);
 
@@ -2409,7 +2426,7 @@ option_check_validity (gc_option_t *option, unsigned long flags,
             we do not quote arguments in configuration files, and
             thus no argument is indistinguishable from the empty
             string.  */
-         if (arg[1] == '\0' || arg[1] == ',')
+         if (arg[1] == '\0' || (arg[1] == ',' && !verbatim))
            gc_error (1, 0, "empty string argument for option %s is "
                      "currently not allowed.  Please report this!",
                      option->name);
@@ -2426,7 +2443,7 @@ option_check_validity (gc_option_t *option, unsigned long flags,
            gc_error (1, errno, "invalid argument for option %s",
                      option->name);
 
-         if (*arg != '\0' && *arg != ',')
+         if (*arg != '\0' && (*arg != ',' || verbatim))
            gc_error (1, 0, "garbage after argument for option %s",
                      option->name);
        }
@@ -2442,17 +2459,18 @@ option_check_validity (gc_option_t *option, unsigned long flags,
            gc_error (1, errno, "invalid argument for option %s",
                      option->name);
 
-         if (*arg != '\0' && *arg != ',')
+         if (*arg != '\0' && (*arg != ',' || verbatim))
            gc_error (1, 0, "garbage after argument for option %s",
                      option->name);
        }
-      arg = strchr (arg, ',');
+      arg = verbatim? strchr (arg, ',') : NULL;
       if (arg)
        arg++;
     }
   while (arg && *arg);
 }
 
+
 #ifdef HAVE_W32_SYSTEM
 int
 copy_file (const char *src_name, const char *dst_name)
@@ -2816,11 +2834,13 @@ change_options_file (gc_component_t component, gc_backend_t backend,
 
 
 /* Create and verify the new configuration file for the specified
-   backend and component.  Returns 0 on success and -1 on error.  */
+ * backend and component.  Returns 0 on success and -1 on error.  If
+ * VERBATIM is set the profile mode is used. */
 static int
 change_options_program (gc_component_t component, gc_backend_t backend,
                        char **src_filenamep, char **dest_filenamep,
-                       char **orig_filenamep)
+                       char **orig_filenamep,
+                        int verbatim)
 {
   static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###";
   /* True if we are within the marker in the config file.  */
@@ -3008,15 +3028,20 @@ change_options_program (gc_component_t component, gc_backend_t backend,
                {
                  char *end;
 
-                 assert (*arg == '"');
-                 arg++;
+                  if (!verbatim)
+                    {
+                      log_assert (*arg == '"');
+                      arg++;
 
-                 end = strchr (arg, ',');
-                 if (end)
-                   *end = '\0';
+                      end = strchr (arg, ',');
+                      if (end)
+                        *end = '\0';
+                    }
+                  else
+                    end = NULL;
 
                  fprintf (src_file, "%s %s\n", option->name,
-                          percent_deescape (arg));
+                          verbatim? arg : percent_deescape (arg));
                  if (ferror (src_file))
                    goto change_one_err;
 
@@ -3117,14 +3142,15 @@ change_options_program (gc_component_t component, gc_backend_t backend,
 
 
 /* Common code for gc_component_change_options and
-   gc_process_gpgconf_conf.  */
+ * gc_process_gpgconf_conf.  If VERBATIM is set the profile parsing
+ * mode is used.  */
 static void
 change_one_value (gc_option_t *option, int *runtime,
-                  unsigned long flags, char *new_value)
+                  unsigned long flags, char *new_value, int verbatim)
 {
   unsigned long new_value_nr = 0;
 
-  option_check_validity (option, flags, new_value, &new_value_nr);
+  option_check_validity (option, flags, new_value, &new_value_nr, verbatim);
 
   if (option->flags & GC_OPT_FLAG_RUNTIME)
     runtime[option->backend] = 1;
@@ -3158,9 +3184,10 @@ change_one_value (gc_option_t *option, int *runtime,
 
 /* Read the modifications from IN and apply them.  If IN is NULL the
    modifications are expected to already have been set to the global
-   table. */
+   table.  If VERBATIM is set the profile mode is used.  */
 void
-gc_component_change_options (int component, estream_t in, estream_t out)
+gc_component_change_options (int component, estream_t in, estream_t out,
+                             int verbatim)
 {
   int err = 0;
   int runtime[GC_BACKEND_NR];
@@ -3247,7 +3274,7 @@ gc_component_change_options (int component, estream_t in, estream_t out)
               continue;
             }
 
-          change_one_value (option, runtime, flags, new_value);
+          change_one_value (option, runtime, flags, new_value, 0);
         }
     }
 
@@ -3271,7 +3298,8 @@ gc_component_change_options (int component, estream_t in, estream_t out)
          err = change_options_program (component, option->backend,
                                        &src_filename[option->backend],
                                        &dest_filename[option->backend],
-                                       &orig_filename[option->backend]);
+                                       &orig_filename[option->backend],
+                                        verbatim);
          if (! err)
            {
              /* External verification.  */
@@ -3789,7 +3817,7 @@ gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults,
                   xfree (option_info->new_value);
                   option_info->new_value = NULL;
                 }
-              change_one_value (option_info, runtime, newflags, value);
+              change_one_value (option_info, runtime, newflags, value, 0);
             }
         }
     }
@@ -3814,7 +3842,7 @@ gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults,
 
       for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
         {
-          gc_component_change_options (component_id, NULL, NULL);
+          gc_component_change_options (component_id, NULL, NULL, 0);
         }
       opt.runtime = save_opt_runtime;
 
@@ -3829,3 +3857,210 @@ gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults,
   xfree (fname);
   return result;
 }
+
+
+/*
+ * Apply the profile FNAME to all known configure files.
+ */
+gpg_error_t
+gc_apply_profile (const char *fname)
+{
+  gpg_error_t err;
+  char *fname_buffer = NULL;
+  char *line = NULL;
+  size_t line_len = 0;
+  ssize_t length;
+  estream_t fp;
+  int lineno = 0;
+  int runtime[GC_BACKEND_NR];
+  int backend_id;
+  int component_id = -1;
+  int skip_section = 0;
+  int error_count = 0;
+  int newflags;
+
+  if (!fname)
+    fname = "-";
+
+  for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
+    runtime[backend_id] = 0;
+
+
+  if (!(!strcmp (fname, "-")
+        || strchr (fname, '/')
+#ifdef HAVE_W32_SYSTEM
+        || strchr (fname, '\\')
+#endif
+        || strchr (fname, '.')))
+    {
+      /* FNAME looks like a standard profile name.  Check whether one
+       * is installed and use that instead of the given file name.  */
+      fname_buffer = xstrconcat (gnupg_datadir (), DIRSEP_S,
+                                 fname, ".prf", NULL);
+      if (!access (fname_buffer, F_OK))
+        fname = fname_buffer;
+    }
+
+  fp = !strcmp (fname, "-")? es_stdin : es_fopen (fname, "r");
+  if (!fp)
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
+      return err;
+    }
+
+  if (opt.verbose)
+    log_info ("applying profile '%s'\n", fname);
+
+  err = 0;
+  while ((length = es_read_line (fp, &line, &line_len, NULL)) > 0)
+    {
+      char *name, *flags, *value;
+      gc_option_t *option_info = NULL;
+      char *p;
+
+      lineno++;
+      name = line;
+      while (*name == ' ' || *name == '\t')
+        name++;
+      if (!*name || *name == '#' || *name == '\r' || *name == '\n')
+        continue;
+      trim_trailing_spaces (name);
+
+      /* Check whether this is a new section.  */
+      if (*name == '[')
+        {
+          name++;
+          skip_section = 0;
+          /* New section: Get the name of the component.  */
+          p = strchr (name, ']');
+          if (!p)
+            {
+              error_count++;
+              log_info ("%s:%d:%d: error: syntax error in section tag\n",
+                        fname, lineno, (int)(name - line));
+              skip_section = 1;
+              continue;
+            }
+          *p++ = 0;
+          if (*p)
+            log_info ("%s:%d:%d: warning: garbage after section tag\n",
+                      fname, lineno, (int)(p - line));
+
+          trim_spaces (name);
+          component_id = gc_component_find (name);
+          if (component_id < 0)
+            {
+              log_info ("%s:%d:%d: warning: skipping unknown section '%s'\n",
+                        fname, lineno, (int)(name - line), name );
+              skip_section = 1;
+            }
+          continue;
+        }
+
+      if (skip_section)
+        continue;
+      if (component_id < 0)
+        {
+          error_count++;
+          log_info ("%s:%d:%d: error: not in a valid section\n",
+                    fname, lineno, (int)(name - line));
+          skip_section = 1;
+          continue;
+        }
+
+      /* Parse the option name.  */
+      for (p = name; *p && !spacep (p); p++)
+        ;
+      *p++ = 0;
+      value = p;
+
+      option_info = find_option (component_id, name, GC_BACKEND_ANY);
+      if (!option_info)
+        {
+          error_count++;
+          log_info ("%s:%d:%d: error: unknown option '%s' in section '%s'\n",
+                    fname, lineno, (int)(name - line),
+                    name, gc_component[component_id].name);
+          continue;
+        }
+
+      /* Parse the optional flags. */
+      trim_spaces (value);
+      flags = value;
+      if (*flags == '[')
+        {
+          flags++;
+          p = strchr (flags, ']');
+          if (!p)
+            {
+              log_info ("%s:%d:%d: warning: invalid flag specification\n",
+                        fname, lineno, (int)(p - line));
+              continue;
+            }
+          *p++ = 0;
+          value = p;
+          trim_spaces (value);
+        }
+      else /* No flags given.  */
+        flags = NULL;
+
+      /* Set required defaults.  */
+      if (gc_arg_type[option_info->arg_type].fallback == GC_ARG_TYPE_NONE
+          && !*value)
+        value = "1";
+
+      /* Check and save this option.  */
+      newflags = 0;
+      if (flags && !strcmp (flags, "default"))
+        newflags |= GC_OPT_FLAG_DEFAULT;
+
+      if (newflags)
+        option_info->new_flags = 0;
+      if (*value)
+        {
+          xfree (option_info->new_value);
+          option_info->new_value = NULL;
+        }
+      change_one_value (option_info, runtime, newflags, value, 1);
+    }
+
+  if (length < 0 || es_ferror (fp))
+    {
+      err = gpg_error_from_syserror ();
+      error_count++;
+      log_error (_("%s:%u: read error: %s\n"),
+                 fname, lineno, gpg_strerror (err));
+    }
+  if (es_fclose (fp))
+    log_error (_("error closing '%s'\n"), fname);
+  if (error_count)
+    log_error (_("error parsing '%s'\n"), fname);
+
+  xfree (line);
+
+  /* If it all worked, process the options. */
+  if (!err)
+    {
+      /* We need to switch off the runtime update, so that we can do
+         it later all at once. */
+      int save_opt_runtime = opt.runtime;
+      opt.runtime = 0;
+
+      for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
+        {
+          gc_component_change_options (component_id, NULL, NULL, 1);
+        }
+      opt.runtime = save_opt_runtime;
+
+      if (opt.runtime)
+        {
+          for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
+            if (runtime[backend_id] && gc_backend[backend_id].runtime_change)
+              (*gc_backend[backend_id].runtime_change) (0);
+        }
+    }
+
+  xfree (fname_buffer);
+  return err;
+}
index 67a0dce..af65424 100644 (file)
@@ -44,6 +44,7 @@ enum cmd_and_opt_values
     oNull       = '0',
     oNoVerbose = 500,
     oHomedir,
+    oBuilddir,
 
     aListComponents,
     aCheckPrograms,
@@ -59,6 +60,7 @@ enum cmd_and_opt_values
     aKill,
     aCreateSocketDir,
     aRemoveSocketDir,
+    aApplyProfile,
     aReload
   };
 
@@ -75,6 +77,8 @@ static ARGPARSE_OPTS opts[] =
     { aCheckOptions, "check-options", 256, N_("|COMPONENT|check options") },
     { aApplyDefaults, "apply-defaults", 256,
       N_("apply global default values") },
+    { aApplyProfile, "apply-profile", 256,
+      N_("|FILE|update configuration files using FILE") },
     { aListDirs, "list-dirs", 256,
       N_("get the configuration directories for @GPGCONF@") },
     { aListConfig,   "list-config", 256,
@@ -98,6 +102,7 @@ static ARGPARSE_OPTS opts[] =
     { oRuntime, "runtime",  0, N_("activate changes at runtime, if possible") },
     /* hidden options */
     { oHomedir, "homedir", 2, "@" },
+    { oBuilddir, "build-prefix", 2, "@" },
     { oNull, "null", 0, "@" },
     { oNoVerbose, "no-verbose",  0, "@"},
     {0}
@@ -483,6 +488,7 @@ main (int argc, char **argv)
         case oVerbose:   opt.verbose++; break;
         case oNoVerbose: opt.verbose = 0; break;
         case oHomedir:   gnupg_set_homedir (pargs.r.ret_str); break;
+        case oBuilddir:  gnupg_set_builddir (pargs.r.ret_str); break;
         case oNull:      opt.null = 1; break;
 
        case aListDirs:
@@ -492,6 +498,7 @@ main (int argc, char **argv)
         case aChangeOptions:
         case aCheckOptions:
         case aApplyDefaults:
+        case aApplyProfile:
         case aListConfig:
         case aCheckConfig:
         case aQuerySWDB:
@@ -565,7 +572,8 @@ main (int argc, char **argv)
               if (cmd == aListOptions)
                 gc_component_list_options (idx, get_outfp (&outfp));
               else if (cmd == aChangeOptions)
-                gc_component_change_options (idx, es_stdin, get_outfp (&outfp));
+                gc_component_change_options (idx, es_stdin,
+                                             get_outfp (&outfp), 0);
             }
        }
       break;
@@ -656,6 +664,12 @@ main (int argc, char **argv)
         exit (1);
       break;
 
+    case aApplyProfile:
+      gc_component_retrieve_options (-1);
+      if (gc_apply_profile (fname))
+        exit (1);
+      break;
+
     case aListDirs:
       /* Show the system configuration directories for gpgconf.  */
       get_outfp (&outfp);
@@ -691,6 +705,7 @@ main (int argc, char **argv)
             if (gnupg_mkdir (socketdir, "-rwx"))
               gc_error (1, errno, "error creating '%s'", socketdir);
             /* Try again.  */
+            xfree (socketdir);
             socketdir = _gnupg_socketdir_internal (1, &flags);
           }
 
index e99042f..39d34b6 100644 (file)
@@ -72,7 +72,8 @@ void gc_component_retrieve_options (int component);
 void gc_component_list_options (int component, estream_t out);
 
 /* Read the modifications from IN and apply them.  */
-void gc_component_change_options (int component, estream_t in, estream_t out);
+void gc_component_change_options (int component, estream_t in, estream_t out,
+                                  int verbatim);
 
 /* Check the options of a single component.  Returns 0 if everything
    is OK.  */
@@ -83,5 +84,8 @@ int gc_component_check_options (int component, estream_t out,
 int gc_process_gpgconf_conf (const char *fname, int update, int defaults,
                              estream_t listfp);
 
+/* Apply a profile.  */
+gpg_error_t gc_apply_profile (const char *fname);
+
 
 #endif /*GPGCONF_H*/
index ca05f1d..2e32069 100644 (file)
@@ -251,7 +251,11 @@ ensure_part (mime_maker_t ctx, part_t *r_parent)
     {
       ctx->mail = xtrycalloc (1, sizeof *ctx->mail);
       if (!ctx->mail)
-        return gpg_error_from_syserror ();
+        {
+          if (r_parent)
+            *r_parent = NULL;
+          return gpg_error_from_syserror ();
+        }
       log_assert (!ctx->current_part);
       ctx->current_part = ctx->mail;
       ctx->current_part->headers_tail = &ctx->current_part->headers;
@@ -722,6 +726,7 @@ add_missing_headers (mime_maker_t ctx)
         goto leave;
     }
 
+  err = 0;
 
  leave:
   return err;
index 183bdcd..e6f6b7a 100644 (file)
 #include <string.h>
 
 #include "util.h"
+#include "status.h"
+#include "ccparray.h"
+#include "exectool.h"
+#include "mbox-util.h"
 #include "mime-maker.h"
 #include "send-mail.h"
 #include "gpg-wks.h"
 
+/* The stream to output the status information.  Output is disabled if
+   this is NULL.  */
+static estream_t statusfp;
+
+
+\f
+/* Set the status FD.  */
+void
+wks_set_status_fd (int fd)
+{
+  static int last_fd = -1;
+
+  if (fd != -1 && last_fd == fd)
+    return;
+
+  if (statusfp && statusfp != es_stdout && statusfp != es_stderr)
+    es_fclose (statusfp);
+  statusfp = NULL;
+  if (fd == -1)
+    return;
+
+  if (fd == 1)
+    statusfp = es_stdout;
+  else if (fd == 2)
+    statusfp = es_stderr;
+  else
+    statusfp = es_fdopen (fd, "w");
+  if (!statusfp)
+    {
+      log_fatal ("can't open fd %d for status output: %s\n",
+                 fd, gpg_strerror (gpg_error_from_syserror ()));
+    }
+  last_fd = fd;
+}
+
+
+/* Write a status line with code NO followed by the outout of the
+ * printf style FORMAT.  The caller needs to make sure that LFs and
+ * CRs are not printed.  */
+void
+wks_write_status (int no, const char *format, ...)
+{
+  va_list arg_ptr;
+
+  if (!statusfp)
+    return;  /* Not enabled.  */
+
+  es_fputs ("[GNUPG:] ", statusfp);
+  es_fputs (get_status_string (no), statusfp);
+  if (format)
+    {
+      es_putc (' ', statusfp);
+      va_start (arg_ptr, format);
+      es_vfprintf (statusfp, format, arg_ptr);
+      va_end (arg_ptr);
+    }
+  es_putc ('\n', statusfp);
+}
+
+
+\f
+/* Helper for wks_list_key.  */
+static void
+list_key_status_cb (void *opaque, const char *keyword, char *args)
+{
+  (void)opaque;
+
+  if (DBG_CRYPTO)
+    log_debug ("gpg status: %s %s\n", keyword, args);
+}
+
+
+/* Run gpg on KEY and store the primary fingerprint at R_FPR and the
+ * list of mailboxes at R_MBOXES.  Returns 0 on success; on error NULL
+ * is stored at R_FPR and R_MBOXES and an error code is returned.  */
+gpg_error_t
+wks_list_key (estream_t key, char **r_fpr, strlist_t *r_mboxes)
+{
+  gpg_error_t err;
+  ccparray_t ccp;
+  const char **argv;
+  estream_t listing;
+  char *line = NULL;
+  size_t length_of_line = 0;
+  size_t  maxlen;
+  ssize_t len;
+  char **fields = NULL;
+  int nfields;
+  int lnr;
+  char *mbox = NULL;
+  char *fpr = NULL;
+  strlist_t mboxes = NULL;
+
+  *r_fpr = NULL;
+  *r_mboxes = NULL;
+
+  /* Open a memory stream.  */
+  listing = es_fopenmem (0, "w+b");
+  if (!listing)
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
+      return err;
+    }
+
+  ccparray_init (&ccp, 0);
+
+  ccparray_put (&ccp, "--no-options");
+  if (!opt.verbose)
+    ccparray_put (&ccp, "--quiet");
+  else if (opt.verbose > 1)
+    ccparray_put (&ccp, "--verbose");
+  ccparray_put (&ccp, "--batch");
+  ccparray_put (&ccp, "--status-fd=2");
+  ccparray_put (&ccp, "--always-trust");
+  ccparray_put (&ccp, "--with-colons");
+  ccparray_put (&ccp, "--dry-run");
+  ccparray_put (&ccp, "--import-options=import-minimal,import-show");
+  ccparray_put (&ccp, "--import");
+
+  ccparray_put (&ccp, NULL);
+  argv = ccparray_get (&ccp, NULL);
+  if (!argv)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  err = gnupg_exec_tool_stream (opt.gpg_program, argv, key,
+                                NULL, listing,
+                                list_key_status_cb, NULL);
+  if (err)
+    {
+      log_error ("import failed: %s\n", gpg_strerror (err));
+      goto leave;
+    }
+
+  es_rewind (listing);
+  lnr = 0;
+  maxlen = 2048; /* Set limit.  */
+  while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0)
+    {
+      lnr++;
+      if (!maxlen)
+        {
+          log_error ("received line too long\n");
+          err = gpg_error (GPG_ERR_LINE_TOO_LONG);
+          goto leave;
+        }
+      /* Strip newline and carriage return, if present.  */
+      while (len > 0
+            && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+       line[--len] = '\0';
+      /* log_debug ("line '%s'\n", line); */
+
+      xfree (fields);
+      fields = strtokenize (line, ":");
+      if (!fields)
+        {
+          err = gpg_error_from_syserror ();
+          log_error ("strtokenize failed: %s\n", gpg_strerror (err));
+          goto leave;
+        }
+      for (nfields = 0; fields[nfields]; nfields++)
+        ;
+      if (!nfields)
+        {
+          err = gpg_error (GPG_ERR_INV_ENGINE);
+          goto leave;
+        }
+      if (!strcmp (fields[0], "sec"))
+        {
+          /* gpg may return "sec" as the first record - but we do not
+           * accept secret keys.  */
+          err = gpg_error (GPG_ERR_NO_PUBKEY);
+          goto leave;
+        }
+      if (lnr == 1 && strcmp (fields[0], "pub"))
+        {
+          /* First record is not a public key.  */
+          err = gpg_error (GPG_ERR_INV_ENGINE);
+          goto leave;
+        }
+      if (lnr > 1 && !strcmp (fields[0], "pub"))
+        {
+          /* More than one public key.  */
+          err = gpg_error (GPG_ERR_TOO_MANY);
+          goto leave;
+        }
+      if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb"))
+        break; /* We can stop parsing here.  */
+
+      if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr)
+        {
+          fpr = xtrystrdup (fields[9]);
+          if (!fpr)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+        }
+      else if (!strcmp (fields[0], "uid") && nfields > 9)
+        {
+          /* Fixme: Unescape fields[9] */
+          xfree (mbox);
+          mbox = mailbox_from_userid (fields[9]);
+          if (mbox && !append_to_strlist_try (&mboxes, mbox))
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+        }
+    }
+  if (len < 0 || es_ferror (listing))
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("error reading memory stream\n");
+      goto leave;
+    }
+
+  *r_fpr = fpr;
+  fpr = NULL;
+  *r_mboxes = mboxes;
+  mboxes = NULL;
+
+ leave:
+  xfree (fpr);
+  xfree (mboxes);
+  xfree (mbox);
+  xfree (fields);
+  es_free (line);
+  xfree (argv);
+  es_fclose (listing);
+  return err;
+}
+
 
 /* Helper to write mail to the output(s).  */
 gpg_error_t
@@ -164,7 +403,8 @@ wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
 
   if (!err && !es_feof (stream))
     err = gpg_error_from_syserror ();
-    leave:
+
+ leave:
   if (err)
     log_error ("error reading '%s', line %d: %s\n",
                es_fname_get (stream), lnr, gpg_strerror (err));