agent/
[gnupg.git] / agent / trustlist.c
index 0034525..a0b23b5 100644 (file)
@@ -1,11 +1,11 @@
 /* trustlist.c - Maintain the list of trusted keys
- *     Copyright (C) 2002, 2004, 2006 Free Software Foundation, Inc.
+ * Copyright (C) 2002, 2004, 2006, 2007, 2009 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
+ * 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,
@@ -14,9 +14,7 @@
  * 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.
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
@@ -33,6 +31,7 @@
 #include "agent.h"
 #include <assuan.h> /* fixme: need a way to avoid assuan calls here */
 #include "i18n.h"
+#include "estream.h"
 
 
 /* A structure to store the information from the trust file. */
@@ -40,10 +39,12 @@ struct trustitem_s
 {
   struct
   {
+    int disabled:1;       /* This entry is disabled.  */
     int for_pgp:1;        /* Set by '*' or 'P' as first flag. */
     int for_smime:1;      /* Set by '*' or 'S' as first flag. */
     int relax:1;          /* Relax checking of root certificate
                              constraints. */
+    int cm:1;             /* Use chain model for validation. */
   } flags;
   unsigned char fpr[20];  /* The binary fingerprint. */
 };
@@ -53,24 +54,44 @@ typedef struct trustitem_s trustitem_t;
 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 pth_mutex_t trusttable_lock;
 
 
 
 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"
+"# 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"
+"# space, followed by the SHA-1 fingerpint in hex, followed by a flag\n"
+"# which may be one of 'P', 'S' or '*' and optionally followed by a list of\n"
+"# other flags.  The fingerprint may be prefixed with a '!' to mark the\n"
+"# key as not trusted.  You should give the gpg-agent a HUP or run the\n"
+"# command \"gpgconf --reload gpg-agent\" after changing this file.\n"
 "\n\n"
 "# Include the default trust list\n"
 "include-default\n"
 "\n";
 
 
+/* This function must be called once to initialize this module.  This
+   has to be done before a second thread is spawned.  We can't do the
+   static initialization because Pth emulation code might not be able
+   to do a static init; in particular, it is not possible for W32. */
+void
+initialize_module_trustlist (void)
+{
+  static int initialized;
+
+  if (!initialized)
+    {
+      if (!pth_mutex_init (&trusttable_lock))
+        log_fatal ("error initializing mutex: %s\n", strerror (errno));
+      initialized = 1;
+    }
+}
+
+
 
 \f
 static void
@@ -103,7 +124,7 @@ read_one_trustfile (const char *fname, int allow_include,
   int tableidx;
   size_t tablesize;
   int lnr = 0;
-
+  
   table = *addr_of_table;
   tablesize = *addr_of_tablesize;
   tableidx = *addr_of_tableidx;
@@ -153,7 +174,7 @@ read_one_trustfile (const char *fname, int allow_include,
             }
           /* fixme: Should check for trailing garbage.  */
 
-          etcname = make_filename (GNUPG_SYSCONFDIR, "trustlist.txt", NULL);
+          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);
@@ -193,6 +214,15 @@ read_one_trustfile (const char *fname, int allow_include,
 
       ti = table + tableidx;
 
+      memset (&ti->flags, 0, sizeof ti->flags);
+      if (*p == '!')
+        {
+          ti->flags.disabled = 1;
+          p++;
+          while (spacep (p))
+            p++;
+        }
+
       n = hexcolon2bin (p, ti->fpr, 20);
       if (n < 0)
         {
@@ -204,7 +234,6 @@ read_one_trustfile (const char *fname, int allow_include,
       for (; spacep (p); p++)
         ;
       
-      memset (&ti->flags, 0, sizeof ti->flags);
       /* Process the first flag which needs to be the first for
          backward compatibility. */
       if (!*p || *p == '*' )
@@ -251,6 +280,8 @@ read_one_trustfile (const char *fname, int allow_include,
             }
           else if (n == 5 && !memcmp (p, "relax", 5))
             ti->flags.relax = 1;
+          else if (n == 2 && !memcmp (p, "cm", 2))
+            ti->flags.cm = 1;
           else
             log_error ("flag `%.*s' in `%s', line %d ignored\n",
                        n, p, fname, lnr);
@@ -275,7 +306,7 @@ read_one_trustfile (const char *fname, int allow_include,
 }
 
 
-/* Read the trust files and update the global table on success. */
+/* Read the trust files and update the global table on success.  */
 static gpg_error_t
 read_trustfiles (void)
 {
@@ -303,7 +334,7 @@ read_trustfiles (void)
           log_error (_("error opening `%s': %s\n"), fname, gpg_strerror (err));
         }
       xfree (fname);
-      fname = make_filename (GNUPG_SYSCONFDIR, "trustlist.txt", NULL);
+      fname = make_filename (gnupg_sysconfdir (), "trustlist.txt", NULL);
       allow_include = 0;
     }
   err = read_one_trustfile (fname, allow_include,
@@ -313,6 +344,16 @@ read_trustfiles (void)
   if (err)
     {
       xfree (table);
+      if (gpg_err_code (err) == GPG_ERR_ENOENT)
+        {
+          /* Take a missing trustlist as an empty one.  */
+          lock_trusttable ();
+          xfree (trusttable);
+          trusttable = NULL;
+          trusttablesize = 0;
+          unlock_trusttable ();
+          err = 0;
+        }
       return err;
     }
 
@@ -337,13 +378,16 @@ read_trustfiles (void)
 /* Check whether the given fpr is in our trustdb.  We expect FPR to be
    an all uppercase hexstring of 40 characters. */
 gpg_error_t 
-agent_istrusted (ctrl_t ctrl, const char *fpr)
+agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled)
 {
   gpg_error_t err;
   trustitem_t *ti;
   size_t len;
   unsigned char fprbin[20];
 
+  if (r_disabled)
+    *r_disabled = 0;
+
   if ( hexcolon2bin (fpr, fprbin, 20) < 0 )
     return gpg_error (GPG_ERR_INV_VALUE);
 
@@ -362,6 +406,9 @@ agent_istrusted (ctrl_t ctrl, const char *fpr)
       for (ti=trusttable, len = trusttablesize; len; ti++, len--)
         if (!memcmp (ti->fpr, fprbin, 20))
           {
+            if (ti->flags.disabled && r_disabled)
+              *r_disabled = 1;
+
             if (ti->flags.relax)
               {
                 err = agent_write_status (ctrl,
@@ -370,7 +417,15 @@ agent_istrusted (ctrl_t ctrl, const char *fpr)
                 if (err)
                   return err;
               }
-            return 0; /* Trusted. */
+            else if (ti->flags.cm)
+              {
+                err = agent_write_status (ctrl,
+                                          "TRUSTLISTFLAG", "cm", 
+                                          NULL);
+                if (err)
+                  return err;
+              }
+            return ti->flags.disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0;
           }
     }
   return gpg_error (GPG_ERR_NOT_TRUSTED);
@@ -403,6 +458,8 @@ agent_listtrusted (void *assuan_context)
       lock_trusttable ();
       for (ti=trusttable, len = trusttablesize; len; ti++, len--)
         {
+          if (ti->flags.disabled)
+            continue;
           bin2hex (ti->fpr, 20, key);
           key[40] = ' ';
           key[41] = ((ti->flags.for_smime && ti->flags.for_pgp)? '*'
@@ -418,20 +475,89 @@ agent_listtrusted (void *assuan_context)
 }
 
 
+/* Create a copy of string with colons inserted after each two bytes.
+   Caller needs to release the string.  In case of a memory failure,
+   NULL is returned.  */
+static char *
+insert_colons (const char *string)
+{
+  char *buffer, *p;
+  size_t n = strlen (string);
+  size_t nnew = n + (n+1)/2;
+
+  p = buffer = xtrymalloc ( nnew + 1 );
+  if (!buffer)
+    return NULL;
+  while (*string)
+    {
+      *p++ = *string++;
+      if (*string)
+        {
+          *p++ = *string++;
+          if (*string)
+            *p++ = ':';
+        }
+    }
+  *p = 0;
+  assert (strlen (buffer) <= nnew);
+
+  return buffer;
+}
+
+
+/* To pretty print DNs in the Pinentry, we replace slashes by
+   REPLSTRING.  The caller needs to free the returned string.  NULL is
+   returned on error with ERRNO set.  */
+static char *
+reformat_name (const char *name, const char *replstring)
+{
+  const char *s;
+  char *newname;
+  char *d;
+  size_t count;
+  size_t replstringlen = strlen (replstring);
+
+  /* If the name does not start with a slash it is not a preformatted
+     DN and thus we don't bother to reformat it.  */
+  if (*name != '/')
+    return xtrystrdup (name);
+
+  /* Count the names.  Note that a slash contained in a DN part is
+     expected to be C style escaped and thus the slashes we see here
+     are the actual part delimiters.  */
+  for (s=name+1, count=0; *s; s++)
+    if (*s == '/')
+      count++;
+  newname = xtrymalloc (strlen (name) + count*replstringlen + 1);
+  if (!newname)
+    return NULL; 
+  for (s=name+1, d=newname; *s; s++)
+    if (*s == '/')
+      d = stpcpy (d, replstring);
+    else
+      *d++ = *s;
+  *d = 0;
+  return newname;
+}
+
+
 /* Insert the given fpr into our trustdb.  We expect FPR to be an all
    uppercase hexstring of 40 characters. FLAG is either 'P' or 'C'.
-   This function does first check whether that key has alreay been put
+   This function does first check whether that key has already been put
    into the trustdb and returns success in this case.  Before a FPR
-   actually gets inserted, the user is asked by means of the pin-entry
-   whether this is actual wants he want to do.
-*/
+   actually gets inserted, the user is asked by means of the Pinentry
+   whether this is actual want he wants to do.  */
 gpg_error_t
 agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
 {
   gpg_error_t err = 0;
   char *desc;
   char *fname;
-  FILE *fp;
+  estream_t fp;
+  char *fprformatted;
+  char *nameformatted;
+  int is_disabled;
+  int yes_i_trust;
 
   /* 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
@@ -446,118 +572,176 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
     }    
   xfree (fname);
 
-  if (!agent_istrusted (ctrl, fpr))
+  if (!agent_istrusted (ctrl, fpr, &is_disabled))
     {
       return 0; /* We already got this fingerprint.  Silently return
                    success. */
     }
-
+  
   /* This feature must explicitly been enabled. */
   if (!opt.allow_mark_trusted)
     return gpg_error (GPG_ERR_NOT_SUPPORTED);
 
-  /* Insert a new one. */
-  if (asprintf (&desc,
-                /* TRANSLATORS: This prompt is shown by the Pinentry
-                   and has one special property: A "%%0A" is used by
-                   Pinentry to insert a line break.  The double
-                   percent sign is actually needed because it is also
-                   a printf format string.  If you need to insert a
-                   plain % sign, you need to encode it as "%%25".  The
-                   second "%s" gets replaced by a hexdecimal
-                   fingerprint string whereas the first one receives
-                   the name as store in the certificate. */
-                _("Please verify that the certificate identified as:%%0A"
-                  "  \"%s\"%%0A"
-                  "has the fingerprint:%%0A"
-                  "  %s"), name, fpr) < 0 )
-    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. */
-  err = agent_get_confirmation (ctrl, desc, _("Correct"), NULL);
-  free (desc);
-  /* If the user did not confirmed this, we return cancel here so that
-     gpgsm may stop asking further questions.  We won't do this for
-     the second question of course. */
-  if (err)
-    return (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED ? 
-            gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED) : err);
+  if (is_disabled)
+    {
+      /* There is an disabled entry in the trustlist.  Return an error
+         so that the user won't be asked again for that one.  Changing
+         this flag with the integrated marktrusted feature is and will
+         not be made possible.  */
+      return gpg_error (GPG_ERR_NOT_TRUSTED);
+    }
 
 
+  /* Insert a new one. */
+  nameformatted = reformat_name (name, "%0A   ");
+  if (!nameformatted)
+    return gpg_error_from_syserror ();
 
-  if (asprintf (&desc,
+  /* First a general question whether this is trusted.  */
+  desc = xtryasprintf (
                 /* TRANSLATORS: This prompt is shown by the Pinentry
                    and has one special property: A "%%0A" is used by
                    Pinentry to insert a line break.  The double
                    percent sign is actually needed because it is also
                    a printf format string.  If you need to insert a
                    plain % sign, you need to encode it as "%%25".  The
-                   "%s" gets replaced by the name as store in the
+                   "%s" gets replaced by the name as stored in the
                    certificate. */
                 _("Do you ultimately trust%%0A"
                   "  \"%s\"%%0A"
                   "to correctly certify user certificates?"),
-                name) < 0 )
-    return out_of_core ();
+                nameformatted);
+  if (!desc)
+    {
+      xfree (nameformatted);
+      return out_of_core ();
+    }
+  err = agent_get_confirmation (ctrl, desc, _("Yes"), _("No"), 1);
+  xfree (desc);
+  if (!err)
+    yes_i_trust = 1;
+  else if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED)
+    yes_i_trust = 0;
+  else
+    {
+      xfree (nameformatted);
+      return err;
+    }
+    
+
+  fprformatted = insert_colons (fpr);
+  if (!fprformatted)
+    {
+      xfree (nameformatted);
+      return out_of_core ();
+    }
+
+  /* If the user trusts this certificate he has to verify the
+     fingerprint of course.  */
+  if (yes_i_trust)
+    {
+      desc = xtryasprintf 
+        (
+         /* TRANSLATORS: This prompt is shown by the Pinentry and has
+            one special property: A "%%0A" is used by Pinentry to
+            insert a line break.  The double percent sign is actually
+            needed because it is also a printf format string.  If you
+            need to insert a plain % sign, you need to encode it as
+            "%%25".  The second "%s" gets replaced by a hexdecimal
+            fingerprint string whereas the first one receives the name
+            as stored in the certificate. */
+         _("Please verify that the certificate identified as:%%0A"
+           "  \"%s\"%%0A"
+           "has the fingerprint:%%0A"
+           "  %s"), nameformatted, fprformatted);
+      if (!desc)
+        {
+          xfree (fprformatted);
+          xfree (nameformatted);
+          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. */
+      err = agent_get_confirmation (ctrl, desc, _("Correct"), _("Wrong"), 1);
+      xfree (desc);
+      if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED)
+        yes_i_trust = 0;
+      else if (err)
+        {
+          xfree (fprformatted);
+          xfree (nameformatted);
+          return err;
+        }
+    }
 
-  err = agent_get_confirmation (ctrl, desc, _("Yes"), _("No"));
-  free (desc);
-  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.  */
+     sure that nobody else plays with our file and force a reread.  */
   lock_trusttable ();
-  if (!agent_istrusted (ctrl, fpr))
+  agent_reload_trustlist ();
+  if (!agent_istrusted (ctrl, fpr, &is_disabled) || is_disabled)
     {
       unlock_trusttable ();
-      return 0; 
+      xfree (fprformatted);
+      xfree (nameformatted);
+      return is_disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0; 
     }
 
-
   fname = make_filename (opt.homedir, "trustlist.txt", NULL);
   if ( access (fname, F_OK) && errno == ENOENT)
     {
-      fp = fopen (fname, "wx"); /* Warning: "x" is a GNU extension. */
+      fp = es_fopen (fname, "wx");
       if (!fp)
         {
           err = gpg_error_from_syserror ();
           log_error ("can't create `%s': %s\n", fname, gpg_strerror (err));
           xfree (fname);
           unlock_trusttable ();
+          xfree (fprformatted);
+          xfree (nameformatted);
           return err;
         }
-      fputs (headerblurb, fp);
-      fclose (fp);
+      es_fputs (headerblurb, fp);
+      es_fclose (fp);
     }
-  fp = fopen (fname, "a+");
+  fp = es_fopen (fname, "a+");
   if (!fp)
     {
       err = gpg_error_from_syserror ();
       log_error ("can't open `%s': %s\n", fname, gpg_strerror (err));
       xfree (fname);
       unlock_trusttable ();
+      xfree (fprformatted);
+      xfree (nameformatted);
       return err;
     }
 
   /* Append the key. */
-  fputs ("\n# ", fp);
-  print_sanitized_string (fp, name, 0);
-  fprintf (fp, "\n%s %c\n", fpr, flag);
-  if (ferror (fp))
+  es_fputs ("\n# ", fp);
+  xfree (nameformatted);
+  nameformatted = reformat_name (name, "\n# ");
+  if (!nameformatted || strchr (name, '\n'))
+    {
+      /* Note that there should never be a LF in NAME but we better
+         play safe and print a sanitized version in this case.  */
+      es_write_sanitized (fp, name, strlen (name), NULL, NULL);
+    }
+  else
+    es_fputs (nameformatted, fp);
+  es_fprintf (fp, "\n%s%s %c\n", yes_i_trust?"":"!", fprformatted, flag);
+  if (es_ferror (fp))
     err = gpg_error_from_syserror ();
   
-  if (fclose (fp))
+  if (es_fclose (fp))
     err = gpg_error_from_syserror ();
 
-  if (!err)
-    agent_reload_trustlist ();
+  agent_reload_trustlist ();
   xfree (fname);
   unlock_trusttable ();
+  xfree (fprformatted);
+  xfree (nameformatted);
   return err;
 }
 
@@ -574,4 +758,5 @@ agent_reload_trustlist (void)
   trusttable = NULL;
   trusttablesize = 0;
   unlock_trusttable ();
+  bump_key_eventcounter ();
 }