[svn] GnuPG - r4250 - in trunk: . agent common doc doc/examples scd

svn author wk cvs at cvs.gnupg.org
Fri Sep 15 20:53:38 CEST 2006


Author: wk
Date: 2006-09-15 20:53:37 +0200 (Fri, 15 Sep 2006)
New Revision: 4250

Added:
   trunk/common/convert.c
   trunk/common/t-convert.c
   trunk/doc/examples/README
   trunk/doc/examples/trustlist.txt
Modified:
   trunk/NEWS
   trunk/agent/ChangeLog
   trunk/agent/agent.h
   trunk/agent/command.c
   trunk/agent/gpg-agent.c
   trunk/agent/trustlist.c
   trunk/autogen.sh
   trunk/common/ChangeLog
   trunk/common/Makefile.am
   trunk/common/util.h
   trunk/doc/Makefile.am
   trunk/doc/gpg-agent.texi
   trunk/scd/ChangeLog
Log:
Allow for a global trustlist.


Modified: trunk/NEWS
===================================================================
--- trunk/NEWS	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/NEWS	2006-09-15 18:53:37 UTC (rev 4250)
@@ -26,7 +26,9 @@
 
  * gpg-connect-agent has new options to utilize descriptor passing.
 
+ * A global trustlist may now be used.  See doc/examples/trustlist.txt.
 
+
 Noteworthy changes in version 1.9.22 (2006-07-27)
 -------------------------------------------------
 

Modified: trunk/agent/ChangeLog
===================================================================
--- trunk/agent/ChangeLog	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/agent/ChangeLog	2006-09-15 18:53:37 UTC (rev 4250)
@@ -1,3 +1,8 @@
+2006-09-15  Werner Koch  <wk at g10code.com>
+
+	* trustlist.c: Entirely rewritten.
+	(agent_trustlist_housekeeping): Removed and removed all calls.
+
 2006-09-14  Werner Koch  <wk at g10code.com>
 
  	Replaced all call gpg_error_from_errno(errno) by

Modified: trunk/agent/agent.h
===================================================================
--- trunk/agent/agent.h	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/agent/agent.h	2006-09-15 18:53:37 UTC (rev 4250)
@@ -252,11 +252,10 @@
 
 
 /*-- trustlist.c --*/
-int agent_istrusted (const char *fpr);
-int agent_listtrusted (void *assuan_context);
-int agent_marktrusted (ctrl_t ctrl, const char *name,
-                       const char *fpr, int flag);
-void agent_trustlist_housekeeping (void);
+gpg_error_t agent_istrusted (const char *fpr);
+gpg_error_t agent_listtrusted (void *assuan_context);
+gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name,
+                               const char *fpr, int flag);
 void agent_reload_trustlist (void);
 
 

Modified: trunk/agent/command.c
===================================================================
--- trunk/agent/command.c	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/agent/command.c	2006-09-15 18:53:37 UTC (rev 4250)
@@ -153,7 +153,7 @@
 static size_t
 percent_plus_unescape (char *string)
 {
-  unsigned char *p = string;
+  unsigned char *p = (unsigned char *)string;
   size_t n = 0;
 
   while (*string)
@@ -240,7 +240,7 @@
   char *p;
   char fpr[41];
 
-  /* parse the fingerprint value */
+  /* Parse the fingerprint value. */
   for (p=line,n=0; hexdigitp (p); p++, n++)
     ;
   if (*p || !(n == 40 || n == 32))

Modified: trunk/agent/gpg-agent.c
===================================================================
--- trunk/agent/gpg-agent.c	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/agent/gpg-agent.c	2006-09-15 18:53:37 UTC (rev 4250)
@@ -1427,11 +1427,6 @@
     log_info (_("handler 0x%lx for fd %d started\n"), 
               (long)pth_self (), fd);
 
-  /* FIXME: Move this housekeeping into a ticker function.  Calling it
-     for each connection should work but won't work anymore if our
-     clients start to keep connections. */
-  agent_trustlist_housekeeping ();
-
   start_command_handler (-1, fd);
   if (opt.verbose)
     log_info (_("handler 0x%lx for fd %d terminated\n"), 
@@ -1451,8 +1446,6 @@
     log_info (_("ssh handler 0x%lx for fd %d started\n"),
               (long)pth_self (), fd);
 
-  agent_trustlist_housekeeping ();
-
   start_command_handler_ssh (fd);
   if (opt.verbose)
     log_info (_("ssh handler 0x%lx for fd %d terminated\n"),

Modified: trunk/agent/trustlist.c
===================================================================
--- trunk/agent/trustlist.c	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/agent/trustlist.c	2006-09-15 18:53:37 UTC (rev 4250)
@@ -1,5 +1,5 @@
 /* trustlist.c - Maintain the list of trusted keys
- *	Copyright (C) 2002, 2004 Free Software Foundation, Inc.
+ *	Copyright (C) 2002, 2004, 2006 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -28,213 +28,347 @@
 #include <assert.h>
 #include <unistd.h>
 #include <sys/stat.h>
+#include <pth.h>
 
 #include "agent.h"
 #include <assuan.h> /* fixme: need a way to avoid assuan calls here */
 #include "i18n.h"
 
-static const char headerblurb[] =
-"# This is the list of trusted keys.  Comment lines, like this one, as\n"
-"# well as empty lines are ignored. The entire file may be integrity\n"
-"# protected by the use of a MAC, so changing the file does not make\n"
-"# sense without the knowledge of the MAC key.  Lines do have a length\n"
-"# limit but this is not serious limitation as the format of the\n"
-"# entries is fixed and checked by gpg-agent: A non-comment line starts\n"
-"# with optional white spaces, followed by the SHA-1 fingerpint in hex,\n"
-"# optionally followed by a flag character which my either be 'P', 'S'\n"
-"# or '*'. Additional data, delimited by white space, is ignored.\n"
-"#\n"
-"# NOTE: You should give the gpg-agent a HUP after editing this file.\n"
-"\n";
 
+/* A structure to store the information from the trust file. */
+struct trustitem_s
+{
+  int keyflag;            /* The keyflag:  '*', 'P' or 'S'. */
+  unsigned char fpr[20];  /* The binary fingerprint. */
+};
+typedef struct trustitem_s trustitem_t;
 
-static FILE *trustfp;
-static int   trustfp_used; /* Counter to track usage of TRUSTFP. */
-static int   reload_trustlist_pending;
+/* Malloced table and its allocated size with all trust items. */
+static trustitem_t *trusttable; 
+static size_t trusttablesize; 
+/* A mutex used to protect the table. */
+static pth_mutex_t trusttable_lock = PTH_MUTEX_INIT;
 
 
-static int
-open_list (int append)
-{
-  char *fname;
 
-  fname = make_filename (opt.homedir, "trustlist.txt", NULL);
-  trustfp = fopen (fname, append? "a+":"r");
-  if (!trustfp && errno == ENOENT)
-    {
-      trustfp = fopen (fname, "wx");
-      if (!trustfp)
-        {
-          gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
-          log_error ("can't create `%s': %s\n", fname, strerror (errno));
-          xfree (fname);
-          return tmperr;
-        }
-      fputs (headerblurb, trustfp);
-      fclose (trustfp);
-      trustfp = fopen (fname, append? "a+":"r");
-    }
+static const char headerblurb[] =
+"# This is the list of trusted keys.  Comment lines, like this one, as\n"
+"# well as empty lines are ignored.  Lines have a length limit but this\n"
+"# is not serious limitation as the format of the entries is fixed and\n"
+"# checked by gpg-agent.  A non-comment line starts with optional white\n"
+"# space, followed by the SHA-1 fingerpint in hex, optionally followed\n"
+"# by a flag character which my either be 'P', 'S' or '*'.  You should\n"
+"# give the gpg-agent a HUP after editing this file.\n"
+"\n\n"
+"# Include the default trust list\n"
+"include-default\n"
+"\n";
 
-  if (!trustfp)
-    {
-      gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
-      log_error ("can't open `%s': %s\n", fname, strerror (errno));
-      xfree (fname);
-      return tmperr;
-    }
 
-  /*FIXME: check the MAC */
 
-  return 0;
+
+static void
+lock_trusttable (void)
+{
+  if (!pth_mutex_acquire (&trusttable_lock, 0, NULL))
+    log_fatal ("failed to acquire mutex in %s\n", __FILE__);
 }
 
+static void
+unlock_trusttable (void)
+{
+  if (!pth_mutex_release (&trusttable_lock))
+    log_fatal ("failed to release mutex in %s\n", __FILE__);
+}
 
 
-/* Read the trustlist and return entry by entry.  KEY must point to a
-   buffer of at least 41 characters. KEYFLAG does return either 'P',
-   'S' or '*'.
 
-   Reading a valid entry returns 0, EOF returns -1 any other error
-   returns the appropriate error code. */
-static int
-read_list (char *key, int *keyflag)
+static gpg_error_t
+read_one_trustfile (const char *fname, int allow_include,
+                    trustitem_t **addr_of_table, 
+                    size_t *addr_of_tablesize,
+                    int *addr_of_tableidx)
 {
-  int rc;
-  int c, i, j;
+  gpg_error_t err = 0;
+  FILE *fp;
+  int n, c;
   char *p, line[256];
-  
-  if (!trustfp)
+  trustitem_t *table, *ti;
+  int tableidx;
+  size_t tablesize;
+  int lnr = 0;
+
+  table = *addr_of_table;
+  tablesize = *addr_of_tablesize;
+  tableidx = *addr_of_tableidx;
+
+  fp = fopen (fname, "r");
+  if (!fp)
     {
-      rc = open_list (0);
-      if (rc)
-        return rc;
+      err = gpg_error_from_syserror ();
+      log_error (_("error opening `%s': %s\n"), fname, gpg_strerror (err));
+      goto leave;
     }
 
-  do
+  while (fgets (line, DIM(line)-1, fp))
     {
-      if (!fgets (line, DIM(line)-1, trustfp) )
-        {
-          if (feof (trustfp))
-            return -1;
-          return gpg_error (gpg_err_code_from_errno (errno));
-        }
+      lnr++;
       
       if (!*line || line[strlen(line)-1] != '\n')
         {
-          /* eat until end of line */
-          while ( (c=getc (trustfp)) != EOF && c != '\n')
+          /* Eat until end of line. */
+          while ( (c=getc (fp)) != EOF && c != '\n')
             ;
-          return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
-                                 : GPG_ERR_INCOMPLETE_LINE);
+          err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
+                           : GPG_ERR_INCOMPLETE_LINE);
+          log_error (_("file `%s', line %d: %s\n"),
+                     fname, lnr, gpg_strerror (err));
+          continue;
         }
+      line[strlen(line)-1] = 0; /* Chop the LF. */
       
       /* Allow for empty lines and spaces */
       for (p=line; spacep (p); p++)
         ;
+      if (!*p || *p == '#')
+        continue;
+  
+      if (!strncmp (p, "include-default", 15)
+          && (!p[15] || spacep (p+15)))
+        {
+          char *etcname;
+          gpg_error_t err2;
+
+          if (!allow_include)
+            {
+              log_error (_("statement \"%s\" ignored in `%s', line %d\n"),
+                         "include-default", fname, lnr);
+              continue;
+            }
+          /* fixme: Should check for trailing garbage.  */
+
+          etcname = make_filename (GNUPG_SYSCONFDIR, "trustlist.txt", NULL);
+          if ( !strcmp (etcname, fname) ) /* Same file. */
+            log_info (_("statement \"%s\" ignored in `%s', line %d\n"),
+                      "include-default", fname, lnr);
+          else if ( access (etcname, F_OK) && errno == ENOENT )
+            {
+              /* A non existent system trustlist is not an error.
+                 Just print a note. */
+              log_info (_("system trustlist `%s' not available\n"), etcname);
+            }
+          else
+            {
+              err2 = read_one_trustfile (etcname, 0,
+                                         &table, &tablesize, &tableidx);
+              if (err2)
+                err = err2;
+            }
+          xfree (etcname);
+          
+          continue;
+        }
+
+      if (tableidx == tablesize)  /* Need more space. */
+        {
+          trustitem_t *tmp;
+          size_t tmplen;
+          
+          tmplen = tablesize + 20;
+          tmp = xtryrealloc (table, tmplen * sizeof *table);
+          if (!tmp)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+          table = tmp;
+          tablesize = tmplen;
+        }
+
+      ti = table + tableidx;
+
+      n = hexcolon2bin (p, ti->fpr, 20);
+      if (n < 0)
+        {
+          log_error (_("bad fingerprint in `%s', line %d\n"), fname, lnr);
+          err = gpg_error (GPG_ERR_BAD_DATA); 
+          continue;
+        }
+      p += n;
+      for (; spacep (p); p++)
+        ;
+      
+      if (!*p)
+        ti->keyflag = '*';
+      else if ( *p == 'P' || *p == 'p')
+        ti->keyflag = 'P';
+      else if ( *p == 'S' || *p == 's')
+        ti->keyflag = 'S';
+      else if ( *p == '*')
+        ti->keyflag = '*';
+      else
+        {
+          log_error (_("invalid keyflag in `%s', line %d\n"), fname, lnr);
+          err = gpg_error (GPG_ERR_BAD_DATA);
+          continue;
+        }
+      p++;
+      if ( *p && !spacep (p) )
+        {
+          log_error (_("invalid keyflag in `%s', line %d\n"), fname, lnr);
+          err = gpg_error (GPG_ERR_BAD_DATA);
+          continue;
+        }
+      /* Fixme: need to check for trailing garbage. */
+      tableidx++;
     }
-  while (!*p || *p == '\n' || *p == '#');
-  
-  for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++)
-    if ( p[i] != ':' )
-      key[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i];
-  key[j] = 0;
-  if (j!=40 || !(spacep (p+i) || p[i] == '\n'))
+  if ( !err && !feof (fp) )
     {
-      log_error ("invalid formatted fingerprint in trustlist\n");
-      return gpg_error (GPG_ERR_BAD_DATA);
+      err = gpg_error_from_syserror ();
+      log_error (_("error reading `%s', line %d: %s\n"),
+                 fname, lnr, gpg_strerror (err));
     }
-  assert (p[i]);
-  if (p[i] == '\n')
-    *keyflag = '*';
-  else 
+
+ leave:
+  if (fp)
+    fclose (fp);
+  *addr_of_table = table;
+  *addr_of_tablesize = tablesize;
+  *addr_of_tableidx = tableidx;
+  return err;
+}
+
+
+/* Read the trust files and update the global table on success. */
+static gpg_error_t
+read_trustfiles (void)
+{
+  gpg_error_t err;
+  trustitem_t *table, *ti;
+  int tableidx;
+  size_t tablesize;
+  char *fname;
+  int allow_include = 1;
+
+  tablesize = 10;
+  table = xtrycalloc (tablesize, sizeof *table);
+  if (!table)
+    return gpg_error_from_syserror ();
+  tableidx = 0;
+
+  fname = make_filename (opt.homedir, "trustlist.txt", NULL);
+  if ( access (fname, F_OK) )
     {
-      i++;
-      if ( p[i] == 'P' || p[i] == 'p')
-        *keyflag = 'P';
-      else if ( p[i] == 'S' || p[i] == 's')
-        *keyflag = 'S';
-      else if ( p[i] == '*')
-        *keyflag = '*';
+      if ( errno == ENOENT )
+        ; /* Silently ignore a non-existing trustfile.  */
       else
         {
-          log_error ("invalid keyflag in trustlist\n");
-          return gpg_error (GPG_ERR_BAD_DATA);
+          err = gpg_error_from_syserror ();
+          log_error (_("error opening `%s': %s\n"), fname, gpg_strerror (err));
         }
-      i++;
-      if ( !(spacep (p+i) || p[i] == '\n'))
-        {
-          log_error ("invalid keyflag in trustlist\n");
-          return gpg_error (GPG_ERR_BAD_DATA);
-        }
+      xfree (fname);
+      fname = make_filename (GNUPG_SYSCONFDIR, "trustlist.txt", NULL);
+      allow_include = 0;
     }
+  err = read_one_trustfile (fname, allow_include,
+                            &table, &tablesize, &tableidx);
+  xfree (fname);
 
+  if (err)
+    {
+      xfree (table);
+      return err;
+    }
+
+  /* Fixme: we should drop duplicates and sort the table. */
+
+  ti = xtryrealloc (table, tableidx * sizeof *table);
+  if (!ti)
+    {
+      xfree (table);
+      return err;
+    }
+
+  lock_trusttable ();
+  xfree (trusttable);
+  trusttable = table;
+  trusttablesize = tableidx;
+  unlock_trusttable ();
   return 0;
 }
 
+
+
 /* Check whether the given fpr is in our trustdb.  We expect FPR to be
    an all uppercase hexstring of 40 characters. */
-int 
+gpg_error_t 
 agent_istrusted (const char *fpr)
 {
-  int rc;
-  static char key[41];
-  int keyflag;
+  gpg_error_t err;
+  trustitem_t *ti;
+  size_t len;
+  unsigned char fprbin[20];
 
-  trustfp_used++;
-  if (trustfp)
-    rewind (trustfp);
-  while (!(rc=read_list (key, &keyflag)))
+  if ( hexcolon2bin (fpr, fprbin, 20) < 0 )
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  if (!trusttable)
     {
-      if (!strcmp (key, fpr))
+      err = read_trustfiles ();
+      if (err)
         {
-          trustfp_used--;
-          return 0;
+          log_error (_("error reading list of trusted root certificates\n"));
+          return err;
         }
     }
-  if (rc != -1)
+
+  if (trusttable)
     {
-      /* Error in the trustdb - close it to give the user a chance for
-         correction */
-      if (trustfp)
-        fclose (trustfp);
-      trustfp = NULL;
+      for (ti=trusttable, len = trusttablesize; len; ti++, len--)
+        if (!memcmp (ti->fpr, fprbin, 20))
+          return 0; /* Trusted. */
     }
-  trustfp_used--;
-  return rc;
+  return gpg_error (GPG_ERR_NOT_TRUSTED);
 }
 
 
 /* Write all trust entries to FP. */
-int 
+gpg_error_t 
 agent_listtrusted (void *assuan_context)
 {
-  int rc;
-  static char key[51];
-  int keyflag;
+  trustitem_t *ti;
+  char key[51];
+  gpg_error_t err;
+  size_t len;
 
-  trustfp_used++;
-  if (trustfp)
-    rewind (trustfp);
-  while (!(rc=read_list (key, &keyflag)))
+  if (!trusttable)
     {
-      key[40] = ' ';
-      key[41] = keyflag;
-      key[42] = '\n';
-      assuan_send_data (assuan_context, key, 43);
-      assuan_send_data (assuan_context, NULL, 0); /* flush */
-    } 
-  if (rc == -1)
-    rc = 0;
-  if (rc)
+      err = read_trustfiles ();
+      if (err)
+        {
+          log_error (_("error reading list of trusted root certificates\n"));
+          return err;
+        }
+    }
+
+  if (trusttable)
     {
-      /* Error in the trustdb - close it to give the user a chance for
-         correction */
-      if (trustfp)
-        fclose (trustfp);
-      trustfp = NULL;
+      /* We need to lock the table because the scheduler may interrupt
+         assuan_send_data and an other thread may then re-read the table. */
+      lock_trusttable ();
+      for (ti=trusttable, len = trusttablesize; len; ti++, len--)
+        {
+          bin2hex (ti->fpr, 20, key);
+          key[40] = ' ';
+          key[41] = ti->keyflag;
+          key[42] = '\n';
+          assuan_send_data (assuan_context, key, 43);
+          assuan_send_data (assuan_context, NULL, 0); /* flush */
+        }
+      unlock_trusttable ();
     }
-  trustfp_used--;
-  return rc;
+
+  return 0;
 }
 
 
@@ -245,52 +379,36 @@
    actually gets inserted, the user is asked by means of the pin-entry
    whether this is actual wants he want to do.
 */
-int 
+gpg_error_t
 agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
 {
-  int rc;
-  static char key[41];
-  int keyflag;
+  gpg_error_t err = 0;
   char *desc;
   char *fname;
+  FILE *fp;
 
   /* Check whether we are at all allowed to modify the trustlist.
      This is useful so that the trustlist may be a symlink to a global
      trustlist with only admin priviliges to modify it.  Of course
      this is not a secure way of denying access, but it avoids the
-     usual clicking on an Okay buttun thing most users are used to. */
+     usual clicking on an Okay button most users are used to. */
   fname = make_filename (opt.homedir, "trustlist.txt", NULL);
-  rc = access (fname, W_OK);
-  if (rc && errno != ENOENT)
+  if ( access (fname, W_OK) && errno != ENOENT)
     {
       xfree (fname);
       return gpg_error (GPG_ERR_EPERM);
     }    
   xfree (fname);
 
-  trustfp_used++;
-  if (trustfp)
-    rewind (trustfp);
-  while (!(rc=read_list (key, &keyflag)))
+  if (!agent_istrusted (fpr))
     {
-      if (!strcmp (key, fpr))
-        return 0;
+      return 0; /* We already got this fingerprint.  Silently return
+                   success. */
     }
-  if (trustfp)
-    fclose (trustfp);
-  trustfp = NULL;
-  if (rc != -1)
-    {
-      trustfp_used--;
-      return rc;   /* Error in the trustlist. */
-    }
 
   /* This feature must explicitly been enabled. */
   if (!opt.allow_mark_trusted)
-    {
-      trustfp_used--;
-      return gpg_error (GPG_ERR_NOT_SUPPORTED);
-    }
+    return gpg_error (GPG_ERR_NOT_SUPPORTED);
 
   /* Insert a new one. */
   if (asprintf (&desc,
@@ -307,21 +425,15 @@
                   "  \"%s\"%%0A"
                   "has the fingerprint:%%0A"
                   "  %s"), name, fpr) < 0 )
-    {
-      trustfp_used--;
-      return out_of_core ();
-    }
+    return out_of_core ();
 
   /* TRANSLATORS: "Correct" is the label of a button and intended to
      be hit if the fingerprint matches the one of the CA.  The other
      button is "the default "Cancel" of the Pinentry. */
-  rc = agent_get_confirmation (ctrl, desc, _("Correct"), NULL);
+  err = agent_get_confirmation (ctrl, desc, _("Correct"), NULL);
   free (desc);
-  if (rc)
-    {
-      trustfp_used--;
-      return rc;
-    }
+  if (err)
+    return err;
 
   if (asprintf (&desc,
                 /* TRANSLATORS: This prompt is shown by the Pinentry
@@ -336,83 +448,78 @@
                   "  \"%s\"%%0A"
                   "to correctly certify user certificates?"),
                 name) < 0 )
-    {
-      trustfp_used--;
-      return out_of_core ();
-    }
-  rc = agent_get_confirmation (ctrl, desc, _("Yes"), _("No"));
+    return out_of_core ();
+
+  err = agent_get_confirmation (ctrl, desc, _("Yes"), _("No"));
   free (desc);
-  if (rc)
+  if (err)
+    return err;
+
+  /* Now check again to avoid duplicates.  We take the lock to make
+     sure that nobody else plays with our file.  Frankly we don't work
+     with the trusttable but using this lock is just fine for our
+     purpose.  */
+  lock_trusttable ();
+  if (!agent_istrusted (fpr))
     {
-      trustfp_used--;
-      return rc;
+      unlock_trusttable ();
+      return 0; 
     }
 
-  /* Now check again to avoid duplicates.  Also open in append mode now. */
-  rc = open_list (1);
-  if (rc)
+
+  fname = make_filename (opt.homedir, "trustlist.txt", NULL);
+  if ( access (fname, F_OK) && errno == ENOENT)
     {
-      trustfp_used--;
-      return rc;
-    }
-  rewind (trustfp);
-  while (!(rc=read_list (key, &keyflag)))
-    {
-      if (!strcmp (key, fpr))
+      fp = fopen (fname, "wx"); /* Warning: "x" is a GNU extension. */
+      if (!fp)
         {
-          trustfp_used--;
-          return 0;
+          err = gpg_error_from_syserror ();
+          log_error ("can't create `%s': %s\n", fname, gpg_strerror (err));
+          xfree (fname);
+          unlock_trusttable ();
+          return err;
         }
+      fputs (headerblurb, fp);
+      fclose (fp);
     }
-  if (rc != -1)
+  fp = fopen (fname, "a+");
+  if (!fp)
     {
-      if (trustfp)
-        fclose (trustfp);
-      trustfp = NULL;
-      trustfp_used--;
-      return rc;   /* Error in the trustlist. */
+      err = gpg_error_from_syserror ();
+      log_error ("can't open `%s': %s\n", fname, gpg_strerror (err));
+      xfree (fname);
+      unlock_trusttable ();
+      return err;
     }
-  rc = 0;
 
   /* Append the key. */
-  fflush (trustfp);
-  fputs ("\n# ", trustfp);
-  print_sanitized_string (trustfp, name, 0);
-  fprintf (trustfp, "\n%s %c\n", fpr, flag);
-  if (ferror (trustfp))
-    rc = gpg_error (gpg_err_code_from_errno (errno));
+  fputs ("\n# ", fp);
+  print_sanitized_string (fp, name, 0);
+  fprintf (fp, "\n%s %c\n", fpr, flag);
+  if (ferror (fp))
+    err = gpg_error_from_syserror ();
   
-  /* close because we are in append mode */
-  if (fclose (trustfp))
-    rc = gpg_error (gpg_err_code_from_errno (errno));
-  trustfp = NULL;
-  trustfp_used--;
-  return rc;
-}
+  if (fclose (fp))
+    err = gpg_error_from_syserror ();
 
-
-void
-agent_trustlist_housekeeping (void)
-{
-  if (reload_trustlist_pending && !trustfp_used)
-    {
-      if (trustfp)
-        {
-          fclose (trustfp);
-          trustfp = NULL;
-        }
-      reload_trustlist_pending = 0;
-    }
+  if (!err)
+    agent_reload_trustlist ();
+  xfree (fname);
+  unlock_trusttable ();
+  return err;
 }
 
 
-/* Not all editors are editing files in place, thus a changes
-   trustlist.txt won't be recognozed if we keep the file descriptor
-   open. This function may be used to explicitly close that file
-   descriptor, which will force a reopen in turn. */
+/* This function may be called to force reloading of the
+   trustlist.  */
 void
 agent_reload_trustlist (void)
 {
-  reload_trustlist_pending = 1;
-  agent_trustlist_housekeeping ();
+  /* All we need to do is to delete the trusttable.  At the next
+     access it will get re-read. */
+  lock_trusttable ();
+  xfree (trusttable);
+  trusttable = NULL;
+  trusttablesize = 0;
+  unlock_trusttable ();
 }

Modified: trunk/autogen.sh
===================================================================
--- trunk/autogen.sh	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/autogen.sh	2006-09-15 18:53:37 UTC (rev 4250)
@@ -153,4 +153,4 @@
 echo "Running autoconf..."
 $AUTOCONF
 
-echo "You may now run \"./configure --enable-maintainer-mode && make\"."
+echo "You may now run \"./configure --sysconfdir=/etc --enable-maintainer-mode && make\"."

Modified: trunk/common/ChangeLog
===================================================================
--- trunk/common/ChangeLog	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/common/ChangeLog	2006-09-15 18:53:37 UTC (rev 4250)
@@ -1,3 +1,10 @@
+2006-09-15  Werner Koch  <wk at g10code.com>
+
+	* convert.c: New.
+	(hexcolon2bin): New.
+	(bin2hex, bin2hexcolon, do_binhex): New.
+	* t-convert.c: New
+
 2006-09-14  Werner Koch  <wk at g10code.com>
 
 	* util.h (out_of_core): Use new gpg_error_from_syserror function.

Modified: trunk/common/Makefile.am
===================================================================
--- trunk/common/Makefile.am	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/common/Makefile.am	2006-09-15 18:53:37 UTC (rev 4250)
@@ -21,6 +21,8 @@
 ## Process this file with automake to produce Makefile.in
 
 noinst_LIBRARIES = libcommon.a libsimple-pwquery.a
+noinst_PROGRAMS = $(module_tests)
+TESTS = $(module_tests)
 
 AM_CPPFLAGS = -I$(top_srcdir)/gl
 
@@ -39,6 +41,7 @@
 	gettime.c \
 	yesno.c \
 	b64enc.c \
+	convert.c \
 	miscellaneous.c \
 	xasprintf.c \
 	xreadline.c \
@@ -60,3 +63,15 @@
 libsimple_pwquery_a_SOURCES = \
 	simple-pwquery.c simple-pwquery.h asshelp.c asshelp.h
 
+
+#
+# Module tests
+#
+module_tests = t-convert
+
+t_common_ldadd = ../jnlib/libjnlib.a ../common/libcommon.a ../gl/libgnu.a \
+                 $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS)
+
+t_convert_DEPENDENCIES = convert.c
+t_convert_LDADD = $(t_common_ldadd)
+

Added: trunk/common/convert.c
===================================================================
--- trunk/common/convert.c	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/common/convert.c	2006-09-15 18:53:37 UTC (rev 4250)
@@ -0,0 +1,136 @@
+/* convert.c - Hex conversion functions.
+ *	Copyright (C) 2006 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "util.h"
+
+
+#define tohex(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'A'))
+
+
+/* Convert STRING consisting of hex characters into its binary representation
+   and store that at BUFFER.  BUFFER needs to be of LENGTH bytes.  The
+   function check that the STRING will convert exactly to LENGTH
+   bytes. Colons inbetween the hex digits are allowed, if one colon
+   has been given a colon is expected very 2 characters. The string
+   is delimited by either end of string or a white space character.
+   The function returns -1 on error or the length of the parsed
+   string.  */
+int
+hexcolon2bin (const char *string, void *buffer, size_t length)
+{
+  int i;
+  const char *s = string;
+  int need_colon = 0;
+
+  for (i=0; i < length; )
+    {
+      if (i==1 && *s == ':')  /* Skip colons between hex digits.  */
+        {
+          need_colon = 1;
+          s++;
+        }
+      else if (need_colon && *s == ':')
+        s++;
+      else if (need_colon)
+        return -1;           /* Colon expected. */
+      if (!hexdigitp (s) || !hexdigitp (s+1))
+        return -1;           /* Invalid hex digits. */
+      ((unsigned char*)buffer)[i++] = xtoi_2 (s);
+      s += 2;
+    }
+  if (*s == ':')
+    return -1;             /* Trailing colons are not allowed.  */
+  if (*s && (!isascii (*s) || !isspace (*s)) )
+    return -1;             /* Not followed by Nul or white space.  */
+  if (i != length)
+    return -1;             /* Not of expected length.  */
+  if (*s)
+    s++; /* Skip the delimiter. */
+  return s - string;
+}
+
+
+
+static char *
+do_bin2hex (const void *buffer, size_t length, char *stringbuf, int with_colon)
+{
+  const unsigned char *s;
+  char *p;
+  
+  if (!stringbuf)
+    {
+      /* Not really correct for with_colon but we don't care about the
+         one wasted byte. */
+      size_t n = with_colon? 3:2; 
+      size_t nbytes = n * length + 1; 
+      if (length &&  (nbytes-1) / n != length) 
+        {
+          errno = ENOMEM;
+          return NULL;
+        }
+      stringbuf = xtrymalloc (nbytes);
+      if (!stringbuf)
+        return NULL;
+    }
+  
+  for (s = buffer, p = stringbuf; length; length--, s++)
+    {
+      if (with_colon && s != buffer)
+        *p++ = ':';
+      *p++ = tohex ((*s>>4)&15);
+      *p++ = tohex (*s&15);
+    }
+  *p = 0;
+
+  return stringbuf;
+}
+
+
+/* Convert LENGTH bytes of data in BUFFER into hex encoding and store
+   that at the provided STRINGBUF.  STRINGBUF must be allocated of at
+   least (2*LENGTH+1) bytes or be NULL so that the function mallocs an
+   appropriate buffer.  Returns STRINGBUF or NULL on error (which may
+   only occur if STRINGBUF has been NULL and the internal malloc
+   failed). */
+char *
+bin2hex (const void *buffer, size_t length, char *stringbuf)
+{
+  return do_bin2hex (buffer, length, stringbuf, 0);
+}
+
+/* Convert LENGTH bytes of data in BUFFER into hex encoding and store
+   that at the provided STRINGBUF.  STRINGBUF must be allocated of at
+   least (3*LENGTH+1) bytes or be NULL so that the function mallocs an
+   appropriate buffer.  Returns STRINGBUF or NULL on error (which may
+   only occur if STRINGBUF has been NULL and the internal malloc
+   failed). */
+char *
+bin2hexcolon (const void *buffer, size_t length, char *stringbuf)
+{
+  return do_bin2hex (buffer, length, stringbuf, 1);
+}
+
+

Added: trunk/common/t-convert.c
===================================================================
--- trunk/common/t-convert.c	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/common/t-convert.c	2006-09-15 18:53:37 UTC (rev 4250)
@@ -0,0 +1,204 @@
+/* t-convert.c - Module test for convert.c
+ *	Copyright (C) 2006 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "util.h"
+
+#define pass()  do { ; } while(0)
+#define fail(a)  do { fprintf (stderr, "%s:%d: test %d failed\n",\
+                               __FILE__,__LINE__, (a));          \
+                     exit (1);                                   \
+                   } while(0)
+
+
+static void
+test_hexcolon2bin (void)
+{
+  static const char *valid[] = {
+    "00112233445566778899aabbccddeeff11223344",
+    "00112233445566778899AABBCCDDEEFF11223344",
+    "00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44",
+    "00112233445566778899AABBCCDDEEFF11223344 blah",
+    "00112233445566778899AABBCCDDEEFF11223344\tblah",
+    "00112233445566778899AABBCCDDEEFF11223344\nblah",
+    NULL
+  };
+  static const char *invalid[] = {
+    "00112233445566778899aabbccddeeff1122334",
+    "00112233445566778899AABBCCDDEEFF1122334",
+    "00112233445566778899AABBCCDDEEFG11223344",
+    ":00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44",
+    "00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44:",
+    "00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:3344",
+    "00:1122:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44",
+    "0011:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44",
+    "00 11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44",
+    "00:11 22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44",
+    "00112233445566778899aabbccddeeff112233445",
+    "00112233445566778899aabbccddeeff1122334455",
+    "00112233445566778899aabbccddeeff11223344blah",
+    NULL
+  };
+  static const char *valid2[] = {
+    "00",
+    "00 x",
+    NULL
+  };
+  static const char *invalid2[] = {
+    "",
+    "0",
+    "00:",
+    ":00",
+    "0:0",
+    "00x",
+    " 00",
+    NULL
+  };
+  unsigned char buffer[20];
+  int len;
+  int i;
+  
+  
+  for (i=0; valid[i]; i++)
+    {
+      len = hexcolon2bin (valid[i], buffer, sizeof buffer);
+      if (len < 0)
+        fail (i);
+      if (memcmp (buffer, ("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa"
+                           "\xbb\xcc\xdd\xee\xff\x11\x22\x33\x44"), 20))
+          fail (i);
+    }
+  if (hexcolon2bin (valid[0], buffer, sizeof buffer) != 40)
+    fail (0);
+  if (hexcolon2bin (valid[3], buffer, sizeof buffer) != 41)
+    fail (0);
+  
+  for (i=0; invalid[i]; i++)
+    {
+      len = hexcolon2bin (invalid[i], buffer, sizeof buffer);
+      if (!(len < 0))
+        fail (i);
+    }
+
+  for (i=0; valid2[i]; i++)
+    {
+      len = hexcolon2bin (valid2[i], buffer, 1);
+      if (len < 0)
+        fail (i);
+      if (memcmp (buffer, "\x00", 1))
+        fail (i);
+    }
+  if (hexcolon2bin (valid2[0], buffer, 1) != 2)
+    fail (0);
+  if (hexcolon2bin (valid2[1], buffer, 1) != 3)
+    fail (0);
+  
+  for (i=0; invalid2[i]; i++)
+    {
+      len = hexcolon2bin (invalid2[i], buffer, 1);
+      if (!(len < 0))
+        fail (i);
+    }
+
+
+}
+
+
+
+static void
+test_bin2hex (void)
+{
+  char stuff[20+1] = ("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa"
+                      "\xbb\xcc\xdd\xee\xff\x01\x10\x02\xa3");
+  char hexstuff[] = "00112233445566778899AABBCCDDEEFF011002A3";
+  char buffer[2*20+1];
+  char *p;
+
+  p = bin2hex (stuff, 20, buffer);
+  if (!p)
+    fail (0);
+  if (p != buffer)
+    fail (0);
+  if (strcmp (buffer, hexstuff))
+    fail (0);
+
+  p = bin2hex (stuff, 20, NULL);
+  if (!p)
+    fail (0);
+  if (strcmp (p, hexstuff))
+    fail (0);
+  
+  p = bin2hex (stuff, (size_t)(-1), NULL);
+  if (p)
+    fail (0); 
+  if (errno != ENOMEM)
+    fail (1);
+}
+
+
+static void
+test_bin2hexcolon (void)
+{
+  char stuff[20+1] = ("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa"
+                      "\xbb\xcc\xdd\xee\xff\x01\x10\x02\xa3");
+  char hexstuff[] = ("00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF"
+                     ":01:10:02:A3");
+  char buffer[3*20+1];
+  char *p;
+
+  p = bin2hexcolon (stuff, 20, buffer);
+  if (!p)
+    fail (0);
+  if (p != buffer)
+    fail (0);
+  if (strcmp (buffer, hexstuff))
+    fail (0);
+
+  p = bin2hexcolon (stuff, 20, NULL);
+  if (!p)
+    fail (0); 
+  if (strcmp (p, hexstuff))
+    fail (0);
+  
+  p = bin2hexcolon (stuff, (size_t)(-1), NULL);
+  if (p)
+    fail (0); 
+  if (errno != ENOMEM)
+    fail (1);
+}
+
+
+
+
+int
+main (int argc, char **argv)
+{
+  
+  test_hexcolon2bin ();
+  test_bin2hex ();
+  test_bin2hexcolon ();
+
+  return 0;
+}
+

Modified: trunk/common/util.h
===================================================================
--- trunk/common/util.h	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/common/util.h	2006-09-15 18:53:37 UTC (rev 4250)
@@ -144,6 +144,12 @@
 unsigned char *make_simple_sexp_from_hexstr (const char *line,
                                              size_t *nscanned);
 
+/*-- convert.c --*/
+int hexcolon2bin (const char *string, void *buffer, size_t length);
+char *bin2hex (const void *buffer, size_t length, char *stringbuf);
+char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf);
+
+
 /*-- homedir.c --*/
 const char *default_homedir (void);
 

Modified: trunk/doc/Makefile.am
===================================================================
--- trunk/doc/Makefile.am	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/doc/Makefile.am	2006-09-15 18:53:37 UTC (rev 4250)
@@ -19,7 +19,7 @@
 
 ## Process this file with automake to produce Makefile.in
 
-examples=examples/scd-event
+examples = examples/README examples/scd-event examples/trustlist.txt
 
 EXTRA_DIST = DETAILS HACKING TRANSLATE OpenPGP KEYSERVER samplekeys.asc \
 	     gnupg-badge-openpgp.eps gnupg-badge-openpgp.jpg \

Added: trunk/doc/examples/README
===================================================================
--- trunk/doc/examples/README	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/doc/examples/README	2006-09-15 18:53:37 UTC (rev 4250)
@@ -0,0 +1,9 @@
+Files in this directory:
+
+
+scd-event       A handler script used with scdaemon
+
+trustlist.txt   A list of trustworthy root certificates 
+                (Please check yourself whether you actually trust them)
+
+

Added: trunk/doc/examples/trustlist.txt
===================================================================
--- trunk/doc/examples/trustlist.txt	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/doc/examples/trustlist.txt	2006-09-15 18:53:37 UTC (rev 4250)
@@ -0,0 +1,46 @@
+# This is the global list of trusted keys.  Comment lines, like this
+# one, as well as empty lines are ignored.  Lines have a length limit
+# but this is not serious limitation as the format of the entries is
+# fixed and checked by gpg-agent.  A non-comment line starts with
+# optional white space, followed by the SHA-1 fingerpint in hex,
+# optionally followed by a flag character which my either be 'P', 'S'
+# or '*'.  This file will be read by gpg-agent if no local trustlist
+# is available or if the statement "include-default" is used in the
+# local list. You should give the gpg-agent(s) a HUP after editing
+# this file.
+
+
+#Serial number: 32D18D
+#       Issuer: /CN=6R-Ca 1:PN/NameDistinguisher=1/O=RegulierungsbehÈorde
+#               fÈur Telekommunikation und Post/C=DE
+EA:8D:99:DD:36:AA:2D:07:1A:3C:7B:69:00:9E:51:B9:4A:2E:E7:60  S
+
+#Serial number: 00C48C8D
+#       Issuer: /CN=7R-CA 1:PN/NameDistinguisher=1/O=RegulierungsbehÈorde
+#               fÈur Telekommunikation und Post/C=DE
+DB:45:3D:1B:B0:1A:F3:23:10:6B:DE:D0:09:61:57:AA:F4:25:E0:5B  S
+
+#Serial number: 01
+#       Issuer: /CN=8R-CA 1:PN/O=Regulierungsbehörde für
+#               Telekommunikation und Post/C=DE
+42:6A:F6:78:30:E9:CE:24:5B:EF:41:A2:C1:A8:51:DA:C5:0A:6D:F5  S
+
+#Serial number: 02
+#       Issuer: /CN=9R-CA 1:PN/O=Regulierungsbehörde für
+#               Telekommunikation und Post/C=DE
+75:9A:4A:CE:7C:DA:7E:89:1B:B2:72:4B:E3:76:EA:47:3A:96:97:24  S
+
+#Serial number: 2A
+#       Issuer: /CN=10R-CA 1:PN/O=Bundesnetzagentur/C=DE
+31:C9:D2:E6:31:4D:0B:CC:2C:1A:45:00:A6:6B:97:98:27:18:8E:CD  S
+
+#Serial number: 2D
+#       Issuer: /CN=11R-CA 1:PN/O=Bundesnetzagentur/C=DE
+A0:8B:DF:3B:AA:EE:3F:9D:64:6C:47:81:23:21:D4:A6:18:81:67:1D  S
+
+#Serial number: 00
+#       Issuer: /CN=CA Cert Signing Authority/OU=http:\x2f\x2fwww.
+#               cacert.org/O=Root CA/EMail=support at cacert.org
+13:5C:EC:36:F4:9C:B8:E9:3B:1A:B2:70:CD:80:88:46:76:CE:8F:33  S
+
+

Modified: trunk/doc/gpg-agent.texi
===================================================================
--- trunk/doc/gpg-agent.texi	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/doc/gpg-agent.texi	2006-09-15 18:53:37 UTC (rev 4250)
@@ -467,17 +467,22 @@
   DC:BD:69:25:48:BD:BB:7E:31:6E:BB:80:D3:00:80:35:D4:F8:A6:CD S 
   @end example
   
-  Before entering a key into this file, you need to ensure its
-  authenticity.  How to do this depends on your organisation; your
-  administrator might have already entered those keys which are deemed
-  trustworthy enough into this file.  Places where to look for the
-  fingerprint of a root certificate are letters received from the CA or
-  the website of the CA (after making 100% sure that this is indeed the
-  website of that CA).  You may want to consider allowing interactive
-  updates of this file by using the @xref{option --allow-mark-trusted}.
-  This is however not as secure as maintaining this file manually.  It is
-  even advisable to change the permissions to read-only so that this file
-  can't be changed inadvertently.
+Before entering a key into this file, you need to ensure its
+authenticity.  How to do this depends on your organisation; your
+administrator might have already entered those keys which are deemed
+trustworthy enough into this file.  Places where to look for the
+fingerprint of a root certificate are letters received from the CA or
+the website of the CA (after making 100% sure that this is indeed the
+website of that CA).  You may want to consider allowing interactive
+updates of this file by using the @xref{option --allow-mark-trusted}.
+This is however not as secure as maintaining this file manually.  It is
+even advisable to change the permissions to read-only so that this file
+can't be changed inadvertently.
+
+As a special feature a line @code{include-default} will include a global
+list of trusted certificates (e.g. @file{/etc/gnupg/trustlist.txt}).
+This global list is also used if the local list ios not available.
+
   
 @item sshcontrol
 

Modified: trunk/scd/ChangeLog
===================================================================
--- trunk/scd/ChangeLog	2006-09-14 16:50:33 UTC (rev 4249)
+++ trunk/scd/ChangeLog	2006-09-15 18:53:37 UTC (rev 4250)
@@ -52,7 +52,7 @@
 
 	* pcsc-wrapper.c (handle_open, handle_close): Reset card and
 	protocol on error/close.
-	(handle_status): Don't set the stae if the state is unknown.
+	(handle_status): Don't set the state if the state is unknown.
 	(handle_reset): Ignore an error if already disconnected.  May
 	happen due to system wake-up after hibernation.  Suggested by Bob
 	Dunlop.




More information about the Gnupg-commits mailing list