We now need a way to store keys
authorWerner Koch <wk@gnupg.org>
Sat, 10 Nov 2001 18:10:11 +0000 (18:10 +0000)
committerWerner Koch <wk@gnupg.org>
Sat, 10 Nov 2001 18:10:11 +0000 (18:10 +0000)
sm/keydb.c [new file with mode: 0644]
sm/keydb.h [new file with mode: 0644]

diff --git a/sm/keydb.c b/sm/keydb.c
new file mode 100644 (file)
index 0000000..b12ba1d
--- /dev/null
@@ -0,0 +1,703 @@
+/* keydb.c - key database dispatcher
+ * Copyright (C) 2001 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "options.h"
+#include "keybox.h"
+#include "keydb.h" 
+#include "i18n.h"
+
+static struct {
+  const char *homedir;
+  int dry_run;
+  int quiet;
+  int verbose;
+  int preserve_permissions;
+} keydbopt;
+
+static int active_handles;
+
+typedef enum {
+    KEYDB_RESOURCE_TYPE_NONE = 0,
+    KEYDB_RESOURCE_TYPE_KEYBOX
+} KeydbResourceType;
+#define MAX_KEYDB_RESOURCES 20
+
+struct resource_item {
+  KeydbResourceType type;
+  union {
+    KEYBOX_HANDLE kr;
+  } u;
+  void *token;
+  int secret;
+};
+
+static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
+static int used_resources;
+
+struct keydb_handle {
+  int locked;
+  int found;
+  int current;
+  int used; /* items in active */
+  struct resource_item active[MAX_KEYDB_RESOURCES];
+};
+
+
+static int lock_all (KEYDB_HANDLE hd);
+static void unlock_all (KEYDB_HANDLE hd);
+
+
+/*
+ * Register a resource (which currently may only be a keybox file).
+ * The first keybox which is added by this function is
+ * created if it does not exist.
+ * Note: this function may be called before secure memory is
+ * available.
+ */
+int
+keydb_add_resource (const char *url, int force, int secret)
+{
+    static int any_secret, any_public;
+    const char *resname = url;
+    IOBUF iobuf = NULL;
+    char *filename = NULL;
+    int rc = 0;
+    KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
+    const char *created_fname = NULL;
+
+    /* Do we have an URL?
+     * gnupg-ring:filename  := this is a plain keybox
+     * filename := See what is is, but create as plain keybox.
+     */
+    if (strlen (resname) > 11) {
+       if (!strncmp( resname, "gnupg-ring:", 11) ) {
+           rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+           resname += 11;
+       }
+      #if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__)
+       else if (strchr (resname, ':')) {
+           log_error ("invalid key resource URL `%s'\n", url );
+           rc = G10ERR_GENERAL;
+           goto leave;
+       }
+      #endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */
+    }
+
+    if (*resname != DIRSEP_C ) { /* do tilde expansion etc */
+       if (strchr(resname, DIRSEP_C) )
+           filename = make_filename (resname, NULL);
+       else
+           filename = make_filename (keydbopt.homedir, resname, NULL);
+    }
+    else
+       filename = m_strdup (resname);
+
+    if (!force)
+       force = secret? !any_secret : !any_public;
+
+    /* see whether we can determine the filetype */
+    if (rt == KEYDB_RESOURCE_TYPE_NONE) {
+       FILE *fp = fopen( filename, "rb" );
+
+       if (fp) {
+           u32 magic;
+
+           if (fread( &magic, 4, 1, fp) == 1 ) {
+               if (magic == 0x13579ace || magic == 0xce9a5713)
+                   ; /* GDBM magic - no more support */
+               else
+                   rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+           }
+           else /* maybe empty: assume ring */
+               rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+           fclose( fp );
+       }
+       else /* no file yet: create ring */
+           rt = KEYDB_RESOURCE_TYPE_KEYBOX;
+    }
+
+    switch (rt) {
+      case KEYDB_RESOURCE_TYPE_NONE:
+       log_error ("unknown type of key resource `%s'\n", url );
+       rc = G10ERR_GENERAL;
+       goto leave;
+
+      case KEYDB_RESOURCE_TYPE_KEYBOX:
+       iobuf = iobuf_open (filename);
+       if (!iobuf && !force) {
+           rc = G10ERR_OPEN_FILE;
+           goto leave;
+       }
+
+       if (!iobuf) {
+           char *last_slash_in_filename;
+
+           last_slash_in_filename = strrchr (filename, DIRSEP_C);
+           *last_slash_in_filename = 0;
+
+           if (access(filename, F_OK)) {
+               /* on the first time we try to create the default homedir and
+                * in this case the process will be terminated, so that on the
+                * next invocation it can read the options file in on startup
+                */
+               try_make_homedir (filename);
+               rc = G10ERR_OPEN_FILE;
+               *last_slash_in_filename = DIRSEP_C;
+               goto leave;
+           }
+
+           *last_slash_in_filename = DIRSEP_C;
+
+           iobuf = iobuf_create (filename);
+           if (!iobuf) {
+               log_error ( _("error creating keybox `%s': %s\n"),
+                            filename, strerror(errno));
+               rc = G10ERR_OPEN_FILE;
+               goto leave;
+           }
+           else {
+             #ifndef HAVE_DOSISH_SYSTEM
+               if (secret && !keydbopt.preserve_permissionws) {
+                   if (chmod (filename, S_IRUSR | S_IWUSR) ) {
+                       log_error (_("changing permission of "
+                                     " `%s' failed: %s\n"),
+                                   filename, strerror(errno) );
+                       rc = G10ERR_WRITE_FILE;
+                       goto leave;
+                   }
+               }
+             #endif
+               if (!keydbopt.quiet)
+                    log_info (_("keybox `%s' created\n"), filename);
+                created_fname = filename;
+           }
+       }
+       iobuf_close (iobuf);
+       iobuf = NULL;
+        if (created_fname) /* must invalidate that ugly cache */
+            iobuf_ioctl (NULL, 2, 0, (char*)created_fname);
+        {
+          void *token = keybox_register_filename (filename, secret);
+          if (!token)
+            ; /* already registered - ignore it */
+          else if (used_resources >= MAX_KEYDB_RESOURCES)
+              rc = G10ERR_RESOURCE_LIMIT;
+          else 
+            {
+              all_resources[used_resources].type = rt;
+              all_resources[used_resources].u.kr = NULL; /* Not used here */
+              all_resources[used_resources].token = token;
+              all_resources[used_resources].secret = secret;
+              used_resources++;
+            }
+        }
+       break;
+
+      default:
+       log_error ("resource type of `%s' not supported\n", url);
+       rc = G10ERR_GENERAL;
+       goto leave;
+    }
+
+    /* fixme: check directory permissions and print a warning */
+
+  leave:
+    if (rc)
+       log_error ("keyblock resource `%s': %s\n", filename, g10_errstr(rc));
+    else if (secret)
+       any_secret = 1;
+    else
+       any_public = 1;
+    m_free (filename);
+    return rc;
+}
+
+
+
+
+KEYDB_HANDLE
+keydb_new (int secret)
+{
+  KEYDB_HANDLE hd;
+  int i, j;
+  
+  hd = m_alloc_clear (sizeof *hd);
+  hd->found = -1;
+  
+  assert (used_resources <= MAX_KEYDB_RESOURCES);
+  for (i=j=0; i < used_resources; i++)
+    {
+      if (!all_resources[i].secret != !secret)
+        continue;
+      switch (all_resources[i].type)
+        {
+        case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
+          break;
+        case KEYDB_RESOURCE_TYPE_KEYBOX:
+          hd->active[j].type   = all_resources[i].type;
+          hd->active[j].token  = all_resources[i].token;
+          hd->active[j].secret = all_resources[i].secret;
+          hd->active[j].u.kr = keybox_new (all_resources[i].token, secret);
+          if (!hd->active[j].u.kr) {
+            m_free (hd);
+            return NULL; /* fixme: release all previously allocated handles*/
+          }
+          j++;
+          break;
+        }
+    }
+  hd->used = j;
+  
+  active_handles++;
+  return hd;
+}
+
+void 
+keydb_release (KEYDB_HANDLE hd)
+{
+    int i;
+
+    if (!hd)
+        return;
+    assert (active_handles > 0);
+    active_handles--;
+
+    unlock_all (hd);
+    for (i=0; i < hd->used; i++) {
+        switch (hd->active[i].type) {
+          case KEYDB_RESOURCE_TYPE_NONE:
+            break;
+          case KEYDB_RESOURCE_TYPE_KEYBOX:
+            keybox_release (hd->active[i].u.kr);
+            break;
+        }
+    }
+
+    m_free (hd);
+}
+
+
+/*
+ * Return the name of the current resource.  This is function first
+ * looks for the last found found, then for the current search
+ * position, and last returns the first available resource.  The
+ * returned string is only valid as long as the handle exists.  This
+ * function does only return NULL if no handle is specified, in all
+ * other error cases an empty string is returned.
+ */
+const char *
+keydb_get_resource_name (KEYDB_HANDLE hd)
+{
+    int idx;
+    const char *s = NULL;
+
+    if (!hd) 
+        return NULL;
+
+    if ( hd->found >= 0 && hd->found < hd->used) 
+        idx = hd->found;
+    else if ( hd->current >= 0 && hd->current < hd->used) 
+        idx = hd->current;
+    else
+        idx = 0;
+
+    switch (hd->active[idx].type) {
+      case KEYDB_RESOURCE_TYPE_NONE:
+        s = NULL; 
+        break;
+      case KEYDB_RESOURCE_TYPE_KEYBOX:
+        s = keybox_get_resource_name (hd->active[idx].u.kr);
+        break;
+    }
+
+    return s? s: "";
+}
+
+
+
+static int 
+lock_all (KEYDB_HANDLE hd)
+{
+    int i, rc = 0;
+
+    for (i=0; !rc && i < hd->used; i++) {
+        switch (hd->active[i].type) {
+          case KEYDB_RESOURCE_TYPE_NONE:
+            break;
+          case KEYDB_RESOURCE_TYPE_KEYBOX:
+            rc = keybox_lock (hd->active[i].u.kr, 1);
+            break;
+        }
+    }
+
+    if (rc) {
+        /* revert the already set locks */
+        for (i--; i >= 0; i--) {
+            switch (hd->active[i].type) {
+              case KEYDB_RESOURCE_TYPE_NONE:
+                break;
+              case KEYDB_RESOURCE_TYPE_KEYBOX:
+                keybox_lock (hd->active[i].u.kr, 0);
+                break;
+            }
+        }
+    }
+    else
+        hd->locked = 1;
+
+    return rc;
+}
+
+static void
+unlock_all (KEYDB_HANDLE hd)
+{
+    int i;
+
+    if (!hd->locked)
+        return;
+
+    for (i=hd->used-1; i >= 0; i--) {
+        switch (hd->active[i].type) {
+          case KEYDB_RESOURCE_TYPE_NONE:
+            break;
+          case KEYDB_RESOURCE_TYPE_KEYBOX:
+            keybox_lock (hd->active[i].u.kr, 0);
+            break;
+        }
+    }
+    hd->locked = 0;
+}
+
+
+/*
+ * Return the last found keybox.  Caller must free it.
+ * The returned keyblock has the kbode flag bit 0 set for the node with
+ * the public key used to locate the keyblock or flag bit 1 set for 
+ * the user ID node.
+ */
+int
+keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
+{
+    int rc = 0;
+
+    if (!hd)
+        return G10ERR_INV_ARG;
+
+    if ( hd->found < 0 || hd->found >= hd->used) 
+        return -1; /* nothing found */
+
+    switch (hd->active[hd->found].type) {
+      case KEYDB_RESOURCE_TYPE_NONE:
+        rc = G10ERR_GENERAL; /* oops */
+        break;
+      case KEYDB_RESOURCE_TYPE_KEYBOX:
+        rc = keybox_get_keyblock (hd->active[hd->found].u.kr, ret_kb);
+        break;
+    }
+
+    return rc;
+}
+
+/* 
+ * update the current keyblock with KB
+ */
+int
+keydb_update_keyblock (KEYDB_HANDLE hd, KBNODE kb)
+{
+    int rc = 0;
+
+    if (!hd)
+        return G10ERR_INV_ARG;
+
+    if ( hd->found < 0 || hd->found >= hd->used) 
+        return -1; /* nothing found */
+
+    if( keydbopt.dry_run )
+       return 0;
+
+    rc = lock_all (hd);
+    if (rc)
+        return rc;
+
+    switch (hd->active[hd->found].type) {
+      case KEYDB_RESOURCE_TYPE_NONE:
+        rc = G10ERR_GENERAL; /* oops */
+        break;
+      case KEYDB_RESOURCE_TYPE_KEYBOX:
+        rc = keybox_update_keyblock (hd->active[hd->found].u.kr, kb);
+        break;
+    }
+
+    unlock_all (hd);
+    return rc;
+}
+
+
+/* 
+ * Insert a new KB into one of the resources. 
+ */
+int
+keydb_insert_keyblock (KEYDB_HANDLE hd, KBNODE kb)
+{
+    int rc = -1;
+    int idx;
+
+    if (!hd) 
+        return G10ERR_INV_ARG;
+
+    if( keydbopt.dry_run )
+       return 0;
+
+    if ( hd->found >= 0 && hd->found < hd->used) 
+        idx = hd->found;
+    else if ( hd->current >= 0 && hd->current < hd->used) 
+        idx = hd->current;
+    else
+        return G10ERR_GENERAL;
+
+    rc = lock_all (hd);
+    if (rc)
+        return rc;
+
+    switch (hd->active[idx].type) {
+      case KEYDB_RESOURCE_TYPE_NONE:
+        rc = G10ERR_GENERAL; /* oops */
+        break;
+      case KEYDB_RESOURCE_TYPE_KEYBOX:
+        rc = keybox_insert_keyblock (hd->active[idx].u.kr, kb);
+        break;
+    }
+
+    unlock_all (hd);
+    return rc;
+}
+
+
+/* 
+ * The current keyblock will be deleted.
+ */
+int
+keydb_delete_keyblock (KEYDB_HANDLE hd)
+{
+    int rc = -1;
+
+    if (!hd)
+        return G10ERR_INV_ARG;
+
+    if ( hd->found < 0 || hd->found >= hd->used) 
+        return -1; /* nothing found */
+
+    if( keydbopt.dry_run )
+       return 0;
+
+    rc = lock_all (hd);
+    if (rc)
+        return rc;
+
+    switch (hd->active[hd->found].type) {
+      case KEYDB_RESOURCE_TYPE_NONE:
+        rc = G10ERR_GENERAL; /* oops */
+        break;
+      case KEYDB_RESOURCE_TYPE_KEYBOX:
+        rc = keybox_delete_keyblock (hd->active[hd->found].u.kr);
+        break;
+    }
+
+    unlock_all (hd);
+    return rc;
+}
+
+\f
+/*
+ * Locate the default writable key resource, so that the next
+ * operation (which is only relevant for inserts) will be done on this
+ * resource.  
+ */
+int
+keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved)
+{
+  int rc;
+  
+  if (!hd)
+    return G10ERR_INV_ARG;
+  
+  rc = keydb_search_reset (hd); /* this does reset hd->current */
+  if (rc)
+    return rc;
+
+  for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++) 
+    {
+      switch (hd->active[hd->current].type) 
+        {
+        case KEYDB_RESOURCE_TYPE_NONE:
+          BUG();
+          break;
+        case KEYDB_RESOURCE_TYPE_KEYBOX:
+          if (keybox_is_writable (hd->active[hd->current].token))
+            return 0; /* found (hd->current is set to it) */
+          break;
+        }
+    }
+  
+  return -1;
+}
+
+/*
+ * Rebuild the caches of all key resources.
+ */
+void
+keydb_rebuild_caches (void)
+{
+  int i, rc;
+  
+  for (i=0; i < used_resources; i++)
+    {
+      if (all_resources[i].secret)
+        continue;
+      switch (all_resources[i].type)
+        {
+        case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
+          break;
+        case KEYDB_RESOURCE_TYPE_KEYBOX:
+          rc = keybox_rebuild_cache (all_resources[i].token);
+          if (rc)
+            log_error (_("failed to rebuild keybox cache: %s\n"),
+                       g10_errstr (rc));
+          break;
+        }
+    }
+}
+
+
+
+/* 
+ * Start the next search on this handle right at the beginning
+ */
+int 
+keydb_search_reset (KEYDB_HANDLE hd)
+{
+    int i, rc = 0;
+
+    if (!hd)
+        return G10ERR_INV_ARG;
+
+    hd->current = 0; 
+    hd->found = -1;
+    /* and reset all resources */
+    for (i=0; !rc && i < hd->used; i++) {
+        switch (hd->active[i].type) {
+          case KEYDB_RESOURCE_TYPE_NONE:
+            break;
+          case KEYDB_RESOURCE_TYPE_KEYBOX:
+            rc = keybox_search_reset (hd->active[i].u.kr);
+            break;
+        }
+    }
+    return rc; 
+}
+
+
+/* 
+ * Search through all keydb resources, starting at the current position,
+ * for a keyblock which contains one of the keys described in the DESC array.
+ */
+int 
+keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc)
+{
+    int rc = -1;
+
+    if (!hd)
+        return G10ERR_INV_ARG;
+
+    while (rc == -1 && hd->current >= 0 && hd->current < hd->used) {
+        switch (hd->active[hd->current].type) {
+          case KEYDB_RESOURCE_TYPE_NONE:
+            BUG(); /* we should never see it here */
+            break;
+          case KEYDB_RESOURCE_TYPE_KEYBOX:
+            rc = keybox_search (hd->active[hd->current].u.kr, desc, ndesc);
+            break;
+        }
+        if (rc == -1) /* EOF -> switch to next resource */
+            hd->current++; 
+        else if (!rc)
+            hd->found = hd->current;
+    }
+
+    return rc; 
+}
+
+
+int
+keydb_search_first (KEYDB_HANDLE hd)
+{
+    KEYDB_SEARCH_DESC desc;
+
+    memset (&desc, 0, sizeof desc);
+    desc.mode = KEYDB_SEARCH_MODE_FIRST;
+    return keydb_search (hd, &desc, 1);
+}
+
+int
+keydb_search_next (KEYDB_HANDLE hd)
+{
+    KEYDB_SEARCH_DESC desc;
+
+    memset (&desc, 0, sizeof desc);
+    desc.mode = KEYDB_SEARCH_MODE_NEXT;
+    return keydb_search (hd, &desc, 1);
+}
+
+int
+keydb_search_kid (KEYDB_HANDLE hd, u32 *kid)
+{
+    KEYDB_SEARCH_DESC desc;
+
+    memset (&desc, 0, sizeof desc);
+    desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
+    desc.u.kid[0] = kid[0];
+    desc.u.kid[1] = kid[1];
+    return keydb_search (hd, &desc, 1);
+}
+
+int
+keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr)
+{
+    KEYDB_SEARCH_DESC desc;
+
+    memset (&desc, 0, sizeof desc);
+    desc.mode = KEYDB_SEARCH_MODE_FPR;
+    memcpy (desc.u.fpr, fpr, MAX_FINGERPRINT_LEN);
+    return keydb_search (hd, &desc, 1);
+}
+
+
+
diff --git a/sm/keydb.h b/sm/keydb.h
new file mode 100644 (file)
index 0000000..f67f736
--- /dev/null
@@ -0,0 +1,77 @@
+/* keydb.h - Key database
+ * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef GNUPG_KEYDB_H
+#define GNUPG_KEYDB_H
+
+typedef struct keydb_handle *KEYDB_HANDLE;
+
+typedef enum {
+    KEYDB_SEARCH_MODE_NONE,
+    KEYDB_SEARCH_MODE_EXACT,
+    KEYDB_SEARCH_MODE_SUBSTR,
+    KEYDB_SEARCH_MODE_MAIL,
+    KEYDB_SEARCH_MODE_MAILSUB,
+    KEYDB_SEARCH_MODE_MAILEND,
+    KEYDB_SEARCH_MODE_WORDS,
+    KEYDB_SEARCH_MODE_SHORT_KID,
+    KEYDB_SEARCH_MODE_LONG_KID,
+    KEYDB_SEARCH_MODE_FPR16,
+    KEYDB_SEARCH_MODE_FPR20,
+    KEYDB_SEARCH_MODE_FPR,
+    KEYDB_SEARCH_MODE_FIRST,
+    KEYDB_SEARCH_MODE_NEXT
+} KeydbSearchMode;
+
+struct keydb_search_desc {
+    KeydbSearchMode mode;
+    int (*skipfnc)(void *,u32*);
+    void *skipfncvalue;
+    union {
+        const char *name;
+        char fpr[MAX_FINGERPRINT_LEN];
+        u32  kid[2];
+    } u;
+};
+
+/*-- keydb.c --*/
+int keydb_add_resource (const char *url, int force, int secret);
+KEYDB_HANDLE keydb_new (int secret);
+void keydb_release (KEYDB_HANDLE hd);
+const char *keydb_get_resource_name (KEYDB_HANDLE hd);
+int keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb);
+int keydb_update_keyblock (KEYDB_HANDLE hd, KBNODE kb);
+int keydb_insert_keyblock (KEYDB_HANDLE hd, KBNODE kb);
+int keydb_delete_keyblock (KEYDB_HANDLE hd);
+int keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved);
+void keydb_rebuild_caches (void);
+int keydb_search_reset (KEYDB_HANDLE hd);
+int keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc);
+int keydb_search_first (KEYDB_HANDLE hd);
+int keydb_search_next (KEYDB_HANDLE hd);
+int keydb_search_kid (KEYDB_HANDLE hd, u32 *kid);
+int keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr);
+
+
+#endif /*GNUPG_KEYDB_H*/
+
+
+
+