gpg: Update list of card vendors from master
[gnupg.git] / kbx / keybox-update.c
index 47e5396..580330f 100644 (file)
@@ -1,11 +1,11 @@
 /* keybox-update.c - keybox update operations
- *     Copyright (C) 2001, 2003 Free Software Foundation, Inc.
+ * Copyright (C) 2001, 2003, 2004, 2012 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
  * GnuPG is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 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,8 +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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
 #include <stdio.h>
 #include <string.h>
 #include <errno.h>
+#include <time.h>
 #include <unistd.h>
+#include <assert.h>
 
 #include "keybox-defs.h"
+#include "../common/sysutils.h"
+#include "../common/host2net.h"
+#include "../common/utilproto.h"
 
 #define EXTSEP_S "."
 
+#define FILECOPY_INSERT 1
+#define FILECOPY_DELETE 2
+#define FILECOPY_UPDATE 3
+
+
+#if !defined(HAVE_FSEEKO) && !defined(fseeko)
+
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+#ifndef LONG_MAX
+# define LONG_MAX ((long) ((unsigned long) -1 >> 1))
+#endif
+#ifndef LONG_MIN
+# define LONG_MIN (-1 - LONG_MAX)
+#endif
+
+/****************
+ * A substitute for fseeko, for hosts that don't have it.
+ */
+static int
+fseeko (FILE * stream, off_t newpos, int whence)
+{
+  while (newpos != (long) newpos)
+    {
+      long pos = newpos < 0 ? LONG_MIN : LONG_MAX;
+      if (fseek (stream, pos, whence) != 0)
+       return -1;
+      newpos -= pos;
+      whence = SEEK_CUR;
+    }
+  return fseek (stream, (long) newpos, whence);
+}
+#endif /* !defined(HAVE_FSEEKO) && !defined(fseeko) */
+
 
 static int
 create_tmp_file (const char *template,
                  char **r_bakfname, char **r_tmpfname, FILE **r_fp)
-{  
-  char *bakfname, *tmpfname;
-  
-  *r_bakfname = NULL;
-  *r_tmpfname = NULL;
-  
-# ifdef USE_ONLY_8DOT3
-  /* Here is another Windoze bug?:
-   * you cant rename("pubring.kbx.tmp", "pubring.kbx");
-   * but       rename("pubring.kbx.tmp", "pubring.aaa");
-   * works.  So we replace .kbx by .bak or .tmp
-   */
-  if (strlen (template) > 4
-      && !strcmp (template+strlen(template)-4, EXTSEP_S "kbx") )
+{
+  gpg_error_t err;
+
+  err = keybox_tmp_names (template, 0, r_bakfname, r_tmpfname);
+  if (!err)
     {
-      bakfname = xtrymalloc (strlen (template) + 1);
-      if (!bakfname)
-        return gpg_error (gpg_err_code_from_errno (errno));
-      strcpy (bakfname, template);
-      strcpy (bakfname+strlen(template)-4, EXTSEP_S "bak");
-      
-      tmpfname = xtrymalloc (strlen (template) + 1);
-      if (!tmpfname)
+      *r_fp = fopen (*r_tmpfname, "wb");
+      if (!*r_fp)
         {
-          gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
-          xfree (bakfname);
-          return tmperr;
+          err = gpg_error_from_syserror ();
+          xfree (*r_tmpfname);
+          *r_tmpfname = NULL;
+          xfree (*r_bakfname);
+          *r_bakfname = NULL;
         }
-      strcpy (tmpfname,template);
-      strcpy (tmpfname + strlen (template)-4, EXTSEP_S "tmp");
     }
-  else 
-    { /* file does not end with kbx; hmmm */
-      bakfname = xtrymalloc ( strlen (template) + 5);
-      if (!bakfname)
-        return gpg_error (gpg_err_code_from_errno (errno));
-      strcpy (stpcpy (bakfname, template), EXTSEP_S "bak");
-      
-      tmpfname = xtrymalloc ( strlen (template) + 5);
-      if (!tmpfname)
-        {
-          gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
-          xfree (bakfname);
-          return tmperr;
-        }
-      strcpy (stpcpy (tmpfname, template), EXTSEP_S "tmp");
-    }
-# else /* Posix file names */
-  bakfname = xtrymalloc (strlen (template) + 2);
-  if (!bakfname)
-    return gpg_error (gpg_err_code_from_errno (errno));
-  strcpy (stpcpy (bakfname,template),"~");
-  
-  tmpfname = xtrymalloc ( strlen (template) + 5);
-  if (!tmpfname)
-    {
-      gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
-      xfree (bakfname);
-      return tmperr;
-    }
-  strcpy (stpcpy (tmpfname,template), EXTSEP_S "tmp");
-# endif /* Posix filename */
 
-  *r_fp = fopen (tmpfname, "wb");
-  if (!*r_fp)
-    {
-      gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
-      xfree (tmpfname);
-      xfree (bakfname);
-      return tmperr;
-    }
-  
-  *r_bakfname = bakfname;
-  *r_tmpfname = tmpfname;
-  return 0;
+  return err;
 }
 
 
@@ -116,6 +98,7 @@ rename_tmp_file (const char *bakfname, const char *tmpfname,
                  const char *fname, int secret )
 {
   int rc=0;
+  int block = 0;
 
   /* restrict the permissions for secret keyboxs */
 #ifndef HAVE_DOSISH_SYSTEM
@@ -123,7 +106,7 @@ rename_tmp_file (const char *bakfname, const char *tmpfname,
 /*      { */
 /*        if (chmod (tmpfname, S_IRUSR | S_IWUSR) )  */
 /*          { */
-/*            log_debug ("chmod of `%s' failed: %s\n", */
+/*            log_debug ("chmod of '%s' failed: %s\n", */
 /*                       tmpfname, strerror(errno) ); */
 /*            return KEYBOX_Write_File; */
 /*     } */
@@ -131,196 +114,233 @@ rename_tmp_file (const char *bakfname, const char *tmpfname,
 #endif
 
   /* fixme: invalidate close caches (not used with stdio)*/
-/*    iobuf_ioctl (NULL, 2, 0, (char*)tmpfname ); */
-/*    iobuf_ioctl (NULL, 2, 0, (char*)bakfname ); */
-/*    iobuf_ioctl (NULL, 2, 0, (char*)fname ); */
+/*    iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)tmpfname ); */
+/*    iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)bakfname ); */
+/*    iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname ); */
 
-  /* first make a backup file except for secret keyboxs */
+  /* First make a backup file except for secret keyboxes. */
   if (!secret)
-    { 
-#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__)
-      remove (bakfname);
-#endif
-      if (rename (fname, bakfname) )
-        {
-          return gpg_error (gpg_err_code_from_errno (errno));
-       }
+    {
+      block = 1;
+      rc = gnupg_rename_file (fname, bakfname, &block);
+      if (rc)
+        goto leave;
     }
-  
-  /* then rename the file */
-#if defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__)
-  remove (fname);
-#endif
-  if (rename (tmpfname, fname) )
+
+  /* Then rename the file. */
+  rc = gnupg_rename_file (tmpfname, fname, NULL);
+  if (block)
     {
-      rc = gpg_error (gpg_err_code_from_errno (errno));
-      if (secret)
-        {
-/*            log_info ("WARNING: 2 files with confidential" */
-/*                       " information exists.\n"); */
-/*            log_info ("%s is the unchanged one\n", fname ); */
-/*            log_info ("%s is the new one\n", tmpfname ); */
-/*            log_info ("Please fix this possible security flaw\n"); */
-       }
-      return rc;
+      gnupg_unblock_all_signals ();
+      block = 0;
     }
-  
-  return 0;
+  /* if (rc) */
+  /*   { */
+  /*     if (secret) */
+  /*       { */
+  /*         log_info ("WARNING: 2 files with confidential" */
+  /*                   " information exists.\n"); */
+  /*         log_info ("%s is the unchanged one\n", fname ); */
+  /*         log_info ("%s is the new one\n", tmpfname ); */
+  /*         log_info ("Please fix this possible security flaw\n"); */
+  /*       } */
+  /*   } */
+
+ leave:
+  if (block)
+    gnupg_unblock_all_signals ();
+  return rc;
 }
 
 
 
-/* Perform insert/delete/update operation.
-    mode 1 = insert
-        2 = delete
-        3 = update
-*/
+/* Perform insert/delete/update operation.  MODE is one of
+   FILECOPY_INSERT, FILECOPY_DELETE, FILECOPY_UPDATE.  FOR_OPENPGP
+   indicates that this is called due to an OpenPGP keyblock change.  */
 static int
-blob_filecopy (int mode, const char *fname, KEYBOXBLOB blob, 
-               int secret, off_t start_offset, unsigned int n_packets )
+blob_filecopy (int mode, const char *fname, KEYBOXBLOB blob,
+               int secret, int for_openpgp, off_t start_offset)
 {
   FILE *fp, *newfp;
   int rc=0;
   char *bakfname = NULL;
   char *tmpfname = NULL;
-  char buffer[4096];
+  char buffer[4096];  /* (Must be at least 32 bytes) */
   int nread, nbytes;
 
-  /* Open the source file. Because we do a rename, we have to check the 
+  /* Open the source file. Because we do a rename, we have to check the
      permissions of the file */
   if (access (fname, W_OK))
-    return gpg_error (gpg_err_code_from_errno (errno));
+    return gpg_error_from_syserror ();
 
   fp = fopen (fname, "rb");
-  if (mode == 1 && !fp && errno == ENOENT)
-    { /* insert mode but file does not exist: create a new keybox file */
+  if (mode == FILECOPY_INSERT && !fp && errno == ENOENT)
+    {
+      /* Insert mode but file does not exist:
+         Create a new keybox file. */
       newfp = fopen (fname, "wb");
       if (!newfp )
+        return gpg_error_from_syserror ();
+
+      rc = _keybox_write_header_blob (newfp, for_openpgp);
+      if (rc)
         {
-          return gpg_error (gpg_err_code_from_errno (errno));
-       }
+          fclose (newfp);
+          return rc;
+        }
 
       rc = _keybox_write_blob (blob, newfp);
       if (rc)
         {
+          fclose (newfp);
           return rc;
         }
+
       if ( fclose (newfp) )
-        {
-          return gpg_error (gpg_err_code_from_errno (errno));
-       }
+        return gpg_error_from_syserror ();
 
 /*        if (chmod( fname, S_IRUSR | S_IWUSR )) */
 /*          { */
 /*            log_debug ("%s: chmod failed: %s\n", fname, strerror(errno) ); */
 /*            return KEYBOX_File_Error; */
 /*          } */
-      return 0; /* ready */
+      return 0; /* Ready. */
     }
 
   if (!fp)
     {
-      rc = gpg_error (gpg_err_code_from_errno (errno));
+      rc = gpg_error_from_syserror ();
       goto leave;
     }
 
-  /* create the new file */
+  /* Create the new file.  On success NEWFP is initialized.  */
   rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp);
   if (rc)
     {
-      fclose(fp);
+      fclose (fp);
       goto leave;
     }
-  
+
   /* prepare for insert */
-  if (mode == 1)
-    { 
-      /* copy everything to the new file */
+  if (mode == FILECOPY_INSERT)
+    {
+      int first_record = 1;
+
+      /* Copy everything to the new file.  If this is for OpenPGP, we
+         make sure that the openpgp flag is set in the header.  (We
+         failsafe the blob type.) */
       while ( (nread = fread (buffer, 1, DIM(buffer), fp)) > 0 )
         {
+          if (first_record && for_openpgp
+              && buffer[4] == KEYBOX_BLOBTYPE_HEADER)
+            {
+              first_record = 0;
+              buffer[7] |= 0x02; /* OpenPGP data may be available.  */
+            }
+
           if (fwrite (buffer, nread, 1, newfp) != 1)
             {
-              rc = gpg_error (gpg_err_code_from_errno (errno));
+              rc = gpg_error_from_syserror ();
+              fclose (fp);
+              fclose (newfp);
               goto leave;
             }
         }
       if (ferror (fp))
         {
-          rc = gpg_error (gpg_err_code_from_errno (errno));
+          rc = gpg_error_from_syserror ();
+          fclose (fp);
+          fclose (newfp);
           goto leave;
         }
     }
-  
-  /* prepare for delete or update */
-  if ( mode == 2 || mode == 3 ) 
-    { 
+
+  /* Prepare for delete or update. */
+  if ( mode == FILECOPY_DELETE || mode == FILECOPY_UPDATE )
+    {
       off_t current = 0;
-      
-      /* copy first part to the new file */
+
+      /* Copy first part to the new file. */
       while ( current < start_offset )
         {
           nbytes = DIM(buffer);
           if (current + nbytes > start_offset)
               nbytes = start_offset - current;
           nread = fread (buffer, 1, nbytes, fp);
-          if (!fread)
+          if (!nread)
             break;
           current += nread;
-          
+
           if (fwrite (buffer, nread, 1, newfp) != 1)
             {
-              rc = gpg_error (gpg_err_code_from_errno (errno));
+              rc = gpg_error_from_syserror ();
+              fclose (fp);
+              fclose (newfp);
               goto leave;
             }
         }
       if (ferror (fp))
         {
-          rc = gpg_error (gpg_err_code_from_errno (errno));
+          rc = gpg_error_from_syserror ();
+          fclose (fp);
+          fclose (newfp);
           goto leave;
         }
-      
-      /* skip this blob */
-      rc = _keybox_read_blob (NULL, fp);
+
+      /* Skip this blob. */
+      rc = _keybox_read_blob (NULL, fp, NULL);
       if (rc)
-        return rc;
+        {
+          fclose (fp);
+          fclose (newfp);
+          return rc;
+        }
     }
-  
-  /* Do an insert or update */
-  if ( mode == 1 || mode == 3 )
-    { 
+
+  /* Do an insert or update. */
+  if ( mode == FILECOPY_INSERT || mode == FILECOPY_UPDATE )
+    {
       rc = _keybox_write_blob (blob, newfp);
       if (rc)
+        {
+          fclose (fp);
+          fclose (newfp);
           return rc;
+        }
     }
-  
-  /* copy the rest of the packet for an delete or update */
-  if (mode == 2 || mode == 3)
-    { 
+
+  /* Copy the rest of the packet for an delete or update. */
+  if (mode == FILECOPY_DELETE || mode == FILECOPY_UPDATE)
+    {
       while ( (nread = fread (buffer, 1, DIM(buffer), fp)) > 0 )
         {
           if (fwrite (buffer, nread, 1, newfp) != 1)
             {
-              rc = gpg_error (gpg_err_code_from_errno (errno));
+              rc = gpg_error_from_syserror ();
+              fclose (fp);
+              fclose (newfp);
               goto leave;
             }
         }
       if (ferror (fp))
         {
-          rc = gpg_error (gpg_err_code_from_errno (errno));
+          rc = gpg_error_from_syserror ();
+          fclose (fp);
+          fclose (newfp);
           goto leave;
         }
     }
-    
-  /* close both files */
+
+  /* Close both files. */
   if (fclose(fp))
     {
-      rc = gpg_error (gpg_err_code_from_errno (errno));
+      rc = gpg_error_from_syserror ();
       fclose (newfp);
       goto leave;
     }
   if (fclose(newfp))
     {
-      rc = gpg_error (gpg_err_code_from_errno (errno));
+      rc = gpg_error_from_syserror ();
       goto leave;
     }
 
@@ -333,9 +353,101 @@ blob_filecopy (int mode, const char *fname, KEYBOXBLOB blob,
 }
 
 
+/* Insert the OpenPGP keyblock {IMAGE,IMAGELEN} into HD. */
+gpg_error_t
+keybox_insert_keyblock (KEYBOX_HANDLE hd, const void *image, size_t imagelen)
+{
+  gpg_error_t err;
+  const char *fname;
+  KEYBOXBLOB blob;
+  size_t nparsed;
+  struct _keybox_openpgp_info info;
+
+  if (!hd)
+    return gpg_error (GPG_ERR_INV_HANDLE);
+  if (!hd->kb)
+    return gpg_error (GPG_ERR_INV_HANDLE);
+  fname = hd->kb->fname;
+  if (!fname)
+    return gpg_error (GPG_ERR_INV_HANDLE);
+
+
+  /* Close this one otherwise we will mess up the position for a next
+     search.  Fixme: it would be better to adjust the position after
+     the write operation.  */
+  _keybox_close_file (hd);
+
+  err = _keybox_parse_openpgp (image, imagelen, &nparsed, &info);
+  if (err)
+    return err;
+  assert (nparsed <= imagelen);
+  err = _keybox_create_openpgp_blob (&blob, &info, image, imagelen,
+                                      hd->ephemeral);
+  _keybox_destroy_openpgp_info (&info);
+  if (!err)
+    {
+      err = blob_filecopy (FILECOPY_INSERT, fname, blob, hd->secret, 1, 0);
+      _keybox_release_blob (blob);
+      /*    if (!rc && !hd->secret && kb_offtbl) */
+      /*      { */
+      /*        update_offset_hash_table_from_kb (kb_offtbl, kb, 0); */
+      /*      } */
+    }
+  return err;
+}
+
+
+/* Update the current key at HD with the given OpenPGP keyblock in
+   {IMAGE,IMAGELEN}.  */
+gpg_error_t
+keybox_update_keyblock (KEYBOX_HANDLE hd, const void *image, size_t imagelen)
+{
+  gpg_error_t err;
+  const char *fname;
+  off_t off;
+  KEYBOXBLOB blob;
+  size_t nparsed;
+  struct _keybox_openpgp_info info;
+
+  if (!hd || !image || !imagelen)
+    return gpg_error (GPG_ERR_INV_VALUE);
+  if (!hd->found.blob)
+    return gpg_error (GPG_ERR_NOTHING_FOUND);
+  if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_PGP)
+    return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
+  fname = hd->kb->fname;
+  if (!fname)
+    return gpg_error (GPG_ERR_INV_HANDLE);
+
+  off = _keybox_get_blob_fileoffset (hd->found.blob);
+  if (off == (off_t)-1)
+    return gpg_error (GPG_ERR_GENERAL);
+
+  /* Close this the file so that we do no mess up the position for a
+     next search.  */
+  _keybox_close_file (hd);
+
+  /* Build a new blob.  */
+  err = _keybox_parse_openpgp (image, imagelen, &nparsed, &info);
+  if (err)
+    return err;
+  assert (nparsed <= imagelen);
+  err = _keybox_create_openpgp_blob (&blob, &info, image, imagelen,
+                                     hd->ephemeral);
+  _keybox_destroy_openpgp_info (&info);
+
+  /* Update the keyblock.  */
+  if (!err)
+    {
+      err = blob_filecopy (FILECOPY_UPDATE, fname, blob, hd->secret, 1, off);
+      _keybox_release_blob (blob);
+    }
+  return err;
+}
+
 
 
-#ifdef KEYBOX_WITH_X509 
+#ifdef KEYBOX_WITH_X509
 int
 keybox_insert_cert (KEYBOX_HANDLE hd, ksba_cert_t cert,
                     unsigned char *sha1_digest)
@@ -345,26 +457,22 @@ keybox_insert_cert (KEYBOX_HANDLE hd, ksba_cert_t cert,
   KEYBOXBLOB blob;
 
   if (!hd)
-    return gpg_error (GPG_ERR_INV_HANDLE); 
+    return gpg_error (GPG_ERR_INV_HANDLE);
   if (!hd->kb)
-    return gpg_error (GPG_ERR_INV_HANDLE); 
+    return gpg_error (GPG_ERR_INV_HANDLE);
   fname = hd->kb->fname;
   if (!fname)
-    return gpg_error (GPG_ERR_INV_HANDLE); 
+    return gpg_error (GPG_ERR_INV_HANDLE);
 
-  /* close this one otherwise we will mess up the position for a next
+  /* Close this one otherwise we will mess up the position for a next
      search.  Fixme: it would be better to adjust the position after
-     the write opertions.  */
-  if (hd->fp)
-    {
-      fclose (hd->fp);
-      hd->fp = NULL;
-    }
+     the write operation.  */
+  _keybox_close_file (hd);
 
   rc = _keybox_create_x509_blob (&blob, cert, sha1_digest, hd->ephemeral);
   if (!rc)
     {
-      rc = blob_filecopy (1, fname, blob, hd->secret, 0, 0 );
+      rc = blob_filecopy (FILECOPY_INSERT, fname, blob, hd->secret, 0, 0);
       _keybox_release_blob (blob);
       /*    if (!rc && !hd->secret && kb_offtbl) */
       /*      { */
@@ -378,6 +486,9 @@ int
 keybox_update_cert (KEYBOX_HANDLE hd, ksba_cert_t cert,
                     unsigned char *sha1_digest)
 {
+  (void)hd;
+  (void)cert;
+  (void)sha1_digest;
   return -1;
 }
 
@@ -398,17 +509,19 @@ keybox_set_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int value)
   const unsigned char *buffer;
   size_t length;
 
+  (void)idx;  /* Not yet used.  */
+
   if (!hd)
     return gpg_error (GPG_ERR_INV_VALUE);
   if (!hd->found.blob)
     return gpg_error (GPG_ERR_NOTHING_FOUND);
   if (!hd->kb)
-    return gpg_error (GPG_ERR_INV_HANDLE); 
+    return gpg_error (GPG_ERR_INV_HANDLE);
   if (!hd->found.blob)
     return gpg_error (GPG_ERR_NOTHING_FOUND);
   fname = hd->kb->fname;
   if (!fname)
-    return gpg_error (GPG_ERR_INV_HANDLE); 
+    return gpg_error (GPG_ERR_INV_HANDLE);
 
   off = _keybox_get_blob_fileoffset (hd->found.blob);
   if (off == (off_t)-1)
@@ -421,18 +534,14 @@ keybox_set_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int value)
 
   off += flag_pos;
 
-  if (hd->fp)
-    {
-      fclose (hd->fp);
-      hd->fp = NULL;
-    }
+  _keybox_close_file (hd);
   fp = fopen (hd->kb->fname, "r+b");
   if (!fp)
-    return gpg_error (gpg_err_code_from_errno (errno));
+    return gpg_error_from_syserror ();
 
   ec = 0;
   if (fseeko (fp, off, SEEK_SET))
-    ec = gpg_error (gpg_err_code_from_errno (errno));
+    ec = gpg_err_code_from_syserror ();
   else
     {
       unsigned char tmp[4];
@@ -444,11 +553,11 @@ keybox_set_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int value)
 
       switch (flag_size)
         {
-        case 1: 
+        case 1:
         case 2:
         case 4:
           if (fwrite (tmp+4-flag_size, flag_size, 1, fp) != 1)
-            ec = gpg_err_code_from_errno (errno);
+            ec = gpg_err_code_from_syserror ();
           break;
         default:
           ec = GPG_ERR_BUG;
@@ -459,7 +568,7 @@ keybox_set_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int value)
   if (fclose (fp))
     {
       if (!ec)
-        ec = gpg_err_code_from_errno (errno);
+        ec = gpg_err_code_from_syserror ();
     }
 
   return gpg_error (ec);
@@ -480,40 +589,209 @@ keybox_delete (KEYBOX_HANDLE hd)
   if (!hd->found.blob)
     return gpg_error (GPG_ERR_NOTHING_FOUND);
   if (!hd->kb)
-    return gpg_error (GPG_ERR_INV_HANDLE); 
+    return gpg_error (GPG_ERR_INV_HANDLE);
   fname = hd->kb->fname;
   if (!fname)
-    return gpg_error (GPG_ERR_INV_HANDLE); 
+    return gpg_error (GPG_ERR_INV_HANDLE);
 
   off = _keybox_get_blob_fileoffset (hd->found.blob);
   if (off == (off_t)-1)
     return gpg_error (GPG_ERR_GENERAL);
   off += 4;
 
-  if (hd->fp)
-    {
-      fclose (hd->fp);
-      hd->fp = NULL;
-    }
-  
+  _keybox_close_file (hd);
   fp = fopen (hd->kb->fname, "r+b");
   if (!fp)
-    return gpg_error (gpg_err_code_from_errno (errno));
+    return gpg_error_from_syserror ();
 
   if (fseeko (fp, off, SEEK_SET))
-    rc = gpg_error (gpg_err_code_from_errno (errno));
+    rc = gpg_error_from_syserror ();
   else if (putc (0, fp) == EOF)
-    rc = gpg_error (gpg_err_code_from_errno (errno));
+    rc = gpg_error_from_syserror ();
   else
     rc = 0;
 
   if (fclose (fp))
     {
       if (!rc)
-        rc = gpg_error (gpg_err_code_from_errno (errno));
+        rc = gpg_error_from_syserror ();
     }
 
   return rc;
 }
 
 
+/* Compress the keybox file.  This should be run with the file
+   locked. */
+int
+keybox_compress (KEYBOX_HANDLE hd)
+{
+  int read_rc, rc;
+  const char *fname;
+  FILE *fp, *newfp;
+  char *bakfname = NULL;
+  char *tmpfname = NULL;
+  int first_blob;
+  KEYBOXBLOB blob = NULL;
+  u32 cut_time;
+  int any_changes = 0;
+  int skipped_deleted;
+
+  if (!hd)
+    return gpg_error (GPG_ERR_INV_HANDLE);
+  if (!hd->kb)
+    return gpg_error (GPG_ERR_INV_HANDLE);
+  if (hd->secret)
+    return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+  fname = hd->kb->fname;
+  if (!fname)
+    return gpg_error (GPG_ERR_INV_HANDLE);
+
+  _keybox_close_file (hd);
+
+  /* Open the source file. Because we do a rename, we have to check the
+     permissions of the file */
+  if (access (fname, W_OK))
+    return gpg_error_from_syserror ();
+
+  fp = fopen (fname, "rb");
+  if (!fp && errno == ENOENT)
+    return 0; /* Ready. File has been deleted right after the access above. */
+  if (!fp)
+    {
+      rc = gpg_error_from_syserror ();
+      return rc;
+    }
+
+  /* A quick test to see if we need to compress the file at all.  We
+     schedule a compress run after 3 hours. */
+  if ( !_keybox_read_blob (&blob, fp, NULL) )
+    {
+      const unsigned char *buffer;
+      size_t length;
+
+      buffer = _keybox_get_blob_image (blob, &length);
+      if (length > 4 && buffer[4] == KEYBOX_BLOBTYPE_HEADER)
+        {
+          u32 last_maint = buf32_to_u32 (buffer+20);
+
+          if ( (last_maint + 3*3600) > time (NULL) )
+            {
+              fclose (fp);
+              _keybox_release_blob (blob);
+              return 0; /* Compress run not yet needed. */
+            }
+        }
+      _keybox_release_blob (blob);
+      fseek (fp, 0, SEEK_SET);
+      clearerr (fp);
+    }
+
+  /* Create the new file. */
+  rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp);
+  if (rc)
+    {
+      fclose (fp);
+      return rc;;
+    }
+
+
+  /* Processing loop.  By reading using _keybox_read_blob we
+     automagically skip any blobs flagged as deleted.  Thus what we
+     only have to do is to check all ephemeral flagged blocks whether
+     their time has come and write out all other blobs. */
+  cut_time = time(NULL) - 86400;
+  first_blob = 1;
+  skipped_deleted = 0;
+  for (rc=0; !(read_rc = _keybox_read_blob (&blob, fp, &skipped_deleted));
+       _keybox_release_blob (blob), blob = NULL )
+    {
+      unsigned int blobflags;
+      const unsigned char *buffer;
+      size_t length, pos, size;
+      u32 created_at;
+
+      if (skipped_deleted)
+        any_changes = 1;
+      buffer = _keybox_get_blob_image (blob, &length);
+      if (first_blob)
+        {
+          first_blob = 0;
+          if (length > 4 && buffer[4] == KEYBOX_BLOBTYPE_HEADER)
+            {
+              /* Write out the blob with an updated maintenance time
+                 stamp and if needed (ie. used by gpg) set the openpgp
+                 flag.  */
+              _keybox_update_header_blob (blob, hd->for_openpgp);
+              rc = _keybox_write_blob (blob, newfp);
+              if (rc)
+                break;
+              continue;
+            }
+
+          /* The header blob is missing.  Insert it.  */
+          rc = _keybox_write_header_blob (newfp, hd->for_openpgp);
+          if (rc)
+            break;
+          any_changes = 1;
+        }
+      else if (length > 4 && buffer[4] == KEYBOX_BLOBTYPE_HEADER)
+        {
+          /* Oops: There is another header record - remove it. */
+          any_changes = 1;
+          continue;
+        }
+
+      if (_keybox_get_flag_location (buffer, length,
+                                     KEYBOX_FLAG_BLOB, &pos, &size)
+          || size != 2)
+        {
+          rc = gpg_error (GPG_ERR_BUG);
+          break;
+        }
+      blobflags = buf16_to_uint (buffer+pos);
+      if ((blobflags & KEYBOX_FLAG_BLOB_EPHEMERAL))
+        {
+          /* This is an ephemeral blob. */
+          if (_keybox_get_flag_location (buffer, length,
+                                         KEYBOX_FLAG_CREATED_AT, &pos, &size)
+              || size != 4)
+            created_at = 0; /* oops. */
+          else
+            created_at = buf32_to_u32 (buffer+pos);
+
+          if (created_at && created_at < cut_time)
+            {
+              any_changes = 1;
+              continue; /* Skip this blob. */
+            }
+        }
+
+      rc = _keybox_write_blob (blob, newfp);
+      if (rc)
+        break;
+    }
+  if (skipped_deleted)
+    any_changes = 1;
+  _keybox_release_blob (blob); blob = NULL;
+  if (!rc && read_rc == -1)
+    rc = 0;
+  else if (!rc)
+    rc = read_rc;
+
+  /* Close both files. */
+  if (fclose(fp) && !rc)
+    rc = gpg_error_from_syserror ();
+  if (fclose(newfp) && !rc)
+    rc = gpg_error_from_syserror ();
+
+  /* Rename or remove the temporary file. */
+  if (rc || !any_changes)
+    gnupg_remove (tmpfname);
+  else
+    rc = rename_tmp_file (bakfname, tmpfname, fname, hd->secret);
+
+  xfree(bakfname);
+  xfree(tmpfname);
+  return rc;
+}