Subject: [PATCH] Secret manipulation step 7: Local driver

From: Miloslav Trmač <[email-masked]>
Date: Fri, 14 Aug 2009 21:48:55 +0200

This implementation stores the secrets in an unencrypted text file,
for simplicity in implementation and debugging.

(Symmetric encryption, e.g. using gpgme, will not be difficult to add.
Because the TLS private key used by libvirtd is stored unencrypted,
encrypting the secrets file does not currently provide much additional
security.)

Changes since the fifth submission:
- Drop unused SECRET_MAX_XML_FILE.

* include/libvirt/virterror.h, src/virterror.c (VIR_ERR_NO_SECRET): New
  error number.
* po/POTFILES.in, src/Makefile.am: Add secret_driver.
* bootstrap: Use gnulib's base64 module.
* src/secret_driver.c, src.secret_driver.h, src/libvirt_private.syms:
  Add local secret driver.
* qemud/qemud.c (qemudInitialize): Use the local secret driver.
---
 bootstrap                   |    1 +
 include/libvirt/virterror.h |    1 +
 po/POTFILES.in              |    1 +
 qemud/qemud.c               |    3 +
 src/Makefile.am             |   14 +
 src/libvirt_private.syms    |    3 +
 src/secret_driver.c         | 1058 +++++++++++++++++++++++++++++++++++++++++++
 src/secret_driver.h         |   28 ++
 src/virterror.c             |    5 +
 9 files changed, 1114 insertions(+), 0 deletions(-)
 create mode 100644 src/secret_driver.c
 create mode 100644 src/secret_driver.h

diff --git a/bootstrap b/bootstrap
index 8b81e0e..885b299 100755
--- a/bootstrap
+++ b/bootstrap
@@ -65,6 +65,7 @@ gnulib_tool=$GNULIB_SRCDIR/gnulib-tool
 <$gnulib_tool || exit
 
 modules='
+base64
 c-ctype
 close
 connect
diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h
index 62cad88..fa5cac4 100644
--- a/include/libvirt/virterror.h
+++ b/include/libvirt/virterror.h
@@ -169,6 +169,7 @@ typedef enum {
     VIR_ERR_MULTIPLE_INTERFACES, /* more than one matching interface found */
     VIR_WAR_NO_SECRET, /* failed to start secret storage */
     VIR_ERR_INVALID_SECRET, /* invalid secret */
+    VIR_ERR_NO_SECRET, /* secret not found */
 } virErrorNumber;
 
 /**
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 30cb08a..5a23893 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -31,6 +31,7 @@ src/qemu_conf.c
 src/qemu_driver.c
 src/remote_internal.c
 src/secret_conf.c
+src/secret_driver.c
 src/security.c
 src/security_selinux.c
 src/storage_backend.c
diff --git a/qemud/qemud.c b/qemud/qemud.c
index df275e6..00b9859 100644
--- a/qemud/qemud.c
+++ b/qemud/qemud.c
@@ -92,6 +92,7 @@
 #ifdef WITH_NODE_DEVICES
 #include "node_device.h"
 #endif
+#include "secret_driver.h"
 #endif

@@ -814,6 +815,7 @@ static struct qemud_server *qemudInitialize(int sigread) {
     virDriverLoadModule("network");
     virDriverLoadModule("storage");
     virDriverLoadModule("nodedev");
+    virDriverLoadModule("secret");
     virDriverLoadModule("qemu");
     virDriverLoadModule("lxc");
     virDriverLoadModule("uml");
@@ -832,6 +834,7 @@ static struct qemud_server *qemudInitialize(int sigread) {
     (defined(HAVE_HAL) || defined(HAVE_DEVKIT))
     nodedevRegister();
 #endif
+    secretRegister();
 #ifdef WITH_QEMU
     qemuRegister();
 #endif
diff --git a/src/Makefile.am b/src/Makefile.am
index d6d9a6b..ceaae35 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -181,6 +181,9 @@ NETWORK_DRIVER_SOURCES =					\
 INTERFACE_DRIVER_SOURCES =					\
 		interface_driver.h interface_driver.c
 
+SECRET_DRIVER_SOURCES =						\
+		secret_driver.h secret_driver.c
+
 # Storage backend specific impls
 STORAGE_DRIVER_SOURCES =					\
 		storage_driver.h storage_driver.c		\
@@ -454,6 +457,17 @@ endif
 libvirt_driver_interface_la_SOURCES = $(INTERFACE_DRIVER_SOURCES)
 endif
 
+if WITH_DRIVER_MODULES
+mod_LTLIBRARIES += libvirt_driver_secret.la
+else
+noinst_LTLIBRARIES += libvirt_driver_secret.la
+libvirt_la_LIBADD += libvirt_driver_secret.la
+endif
+if WITH_DRIVER_MODULES
+libvirt_driver_secret_la_LDFLAGS = -module -avoid-version
+endif
+libvirt_driver_secret_la_SOURCES = $(SECRET_DRIVER_SOURCES)
+
 # Needed to keep automake quiet about conditionals
 libvirt_driver_storage_la_SOURCES =
 if WITH_STORAGE_DIR
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index dbae930..68a94c2 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -306,6 +306,9 @@ virSecretDefParseString;
 virSecretDefParseFile;
 virSecretDefFormat;
 
+# secret_driver.h
+secretRegister;
+
 # security.h
 virSecurityDriverVerify;
 virSecurityDriverStartup;
diff --git a/src/secret_driver.c b/src/secret_driver.c
new file mode 100644
index 0000000..f1698e5
--- /dev/null
+++ b/src/secret_driver.c
@@ -0,0 +1,1058 @@
+/*
+ * secret_driver.c: local driver for secret manipulation API
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Red Hat Author: Miloslav Trmač <[email-masked]>
+ */
+
+#include <config.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "internal.h"
+#include "base64.h"
+#include "datatypes.h"
+#include "driver.h"
+#include "logging.h"
+#include "memory.h"
+#include "secret_conf.h"
+#include "secret_driver.h"
+#include "threads.h"
+#include "util.h"
+#include "uuid.h"
+#include "virterror_internal.h"
+
+#define VIR_FROM_THIS VIR_FROM_SECRET
+
+ /* Internal driver state */
+
+typedef struct _virSecretEntry virSecretEntry;
+typedef virSecretEntry *virSecretEntryPtr;
+struct _virSecretEntry {
+    virSecretEntryPtr next;
+    virSecretDefPtr def;
+    unsigned char *value;       /* May be NULL */
+    size_t value_size;
+};
+
+typedef struct _virSecretDriverState virSecretDriverState;
+typedef virSecretDriverState *virSecretDriverStatePtr;
+struct _virSecretDriverState {
+    virMutex lock;
+    virSecretEntry *secrets;
+    char *directory;
+};
+
+static virSecretDriverStatePtr driverState;
+
+static void
+secretDriverLock(virSecretDriverStatePtr driver)
+{
+    virMutexLock(&driver->lock);
+}
+
+static void
+secretDriverUnlock(virSecretDriverStatePtr driver)
+{
+    virMutexUnlock(&driver->lock);
+}
+
+static virSecretEntryPtr
+listUnlink(virSecretEntryPtr *pptr)
+{
+    virSecretEntryPtr secret;
+
+    secret = *pptr;
+    *pptr = secret->next;
+    return secret;
+}
+
+static void
+listInsert(virSecretEntryPtr *pptr, virSecretEntryPtr secret)
+{
+    secret->next = *pptr;
+    *pptr = secret;
+}
+
+static void
+secretFree(virSecretEntryPtr secret)
+{
+    if (secret == NULL)
+        return;
+
+    virSecretDefFree(secret->def);
+    if (secret->value != NULL) {
+        memset(secret->value, 0, secret->value_size);
+        VIR_FREE(secret->value);
+    }
+    VIR_FREE(secret);
+}
+
+static virSecretEntryPtr *
+secretFind(virSecretDriverStatePtr driver, const char *uuid)
+{
+    virSecretEntryPtr *pptr, s;
+
+    for (pptr = &driver->secrets; *pptr != NULL; pptr = &s->next) {
+        s = *pptr;
+        if (STREQ(s->def->id, uuid))
+            return pptr;
+    }
+    return NULL;
+}
+
+static virSecretEntryPtr
+secretCreate(virConnectPtr conn, virSecretDriverStatePtr driver,
+             const char *uuid)
+{
+    virSecretEntryPtr secret = NULL;
+
+    if (VIR_ALLOC(secret) < 0 || VIR_ALLOC(secret->def))
+        goto no_memory;
+    secret->def->id = strdup(uuid);
+    if (secret->def->id == NULL)
+        goto no_memory;
+    listInsert(&driver->secrets, secret);
+    return secret;
+
+ no_memory:
+    virReportOOMError(conn);
+    secretFree(secret);
+    return NULL;
+}
+
+static virSecretEntryPtr
+secretFindOrCreate(virConnectPtr conn, virSecretDriverStatePtr driver,
+                   const char *uuid, bool *created_new)
+{
+    virSecretEntryPtr *pptr, secret;
+
+    pptr = secretFind(driver, uuid);
+    if (pptr != NULL) {
+        if (created_new != NULL)
+            *created_new = false;
+        return *pptr;
+    }
+
+    secret = secretCreate(conn, driver, uuid);
+    if (secret != NULL && created_new != NULL)
+        *created_new = true;
+    return secret;
+}
+
+ /* Permament secret storage */
+
+/* Secrets are stored in virSecretDriverStatePtr->directory.  Each secret
+   has virSecretDef stored as XML in "$basename.xml".  If a value of the
+   secret is defined, it is stored as base64 (with no formatting) in
+   "$basename.base64".  "$basename" is in both cases the base64-encoded UUID. */
+
+static int
+replaceFile(virConnectPtr conn, const char *filename, void *data, size_t size)
+{
+    char *tmp_path = NULL;
+    int fd = -1, ret = -1;
+
+    if (virAsprintf(&tmp_path, "%sXXXXXX", filename) < 0) {
+        virReportOOMError(conn);
+        goto cleanup;
+    }
+    fd = mkstemp (tmp_path);
+    if (fd == -1) {
+        virReportSystemError(conn, errno, _("mkstemp('%s') failed"), tmp_path);
+        goto cleanup;
+    }
+    if (fchmod(fd, S_IRUSR | S_IWUSR) != 0) {
+        virReportSystemError(conn, errno, _("fchmod('%s') failed"), tmp_path);
+        goto cleanup;
+    }
+
+    ret = safewrite(fd, data, size);
+    if (ret < 0) {
+        virReportSystemError(conn, errno, _("error writing to '%s'"),
+                              tmp_path);
+        goto cleanup;
+    }
+    if (close(fd) < 0) {
+        virReportSystemError(conn, errno, _("error closing '%s'"), tmp_path);
+        goto cleanup;
+    }
+    fd = -1;
+
+    if (rename(tmp_path, filename) < 0) {
+        virReportSystemError(conn, errno, _("rename(%s, %s) failed"), tmp_path,
+                             filename);
+        goto cleanup;
+    }
+    VIR_FREE(tmp_path);
+    ret = 0;
+
+cleanup:
+    if (fd != -1)
+        close(fd);
+    if (tmp_path != NULL) {
+        unlink(tmp_path);
+        VIR_FREE(tmp_path);
+    }
+    return ret;
+}
+
+static char *
+secretComputePath(virConnectPtr conn, virSecretDriverStatePtr driver,
+                  const virSecretEntry *secret, const char *suffix)
+{
+    char *ret, *base64_id;
+
+    base64_encode_alloc(secret->def->id, strlen(secret->def->id), &base64_id);
+    if (base64_id == NULL) {
+        virReportOOMError(conn);
+        return NULL;
+    }
+
+    if (virAsprintf(&ret, "%s/%s%s", driver->directory, base64_id, suffix) < 0)
+        /* ret is NULL */
+        virReportOOMError(conn);
+    VIR_FREE(base64_id);
+    return ret;
+}
+
+static char *
+secretXMLPath(virConnectPtr conn, virSecretDriverStatePtr driver,
+              const virSecretEntry *secret)
+{
+    return secretComputePath(conn, driver, secret, ".xml");
+}
+
+static char *
+secretBase64Path(virConnectPtr conn, virSecretDriverStatePtr driver,
+                 const virSecretEntry *secret)
+{
+    return secretComputePath(conn, driver, secret, ".base64");
+}
+
+static int
+secretEnsureDirectory(virConnectPtr conn, virSecretDriverStatePtr driver)
+{
+    if (mkdir(driver->directory, S_IRWXU) < 0 && errno != EEXIST) {
+        virReportSystemError(conn, errno, _("cannot create '%s'"),
+                             driver->directory);
+        return -1;
+    }
+    return 0;
+}
+
+static int
+secretSaveDef(virConnectPtr conn, virSecretDriverStatePtr driver,
+              const virSecretEntry *secret)
+{
+    char *filename = NULL, *xml = NULL;
+    int ret = -1;
+
+    if (secretEnsureDirectory(conn, driver) < 0)
+        goto cleanup;
+
+    filename = secretXMLPath(conn, driver, secret);
+    if (filename == NULL)
+        goto cleanup;
+    xml = virSecretDefFormat(conn, secret->def);
+    if (xml == NULL)
+        goto cleanup;
+
+    if (replaceFile(conn, filename, xml, strlen(xml)) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(xml);
+    VIR_FREE(filename);
+    return ret;
+}
+
+static int
+secretSaveValue(virConnectPtr conn, virSecretDriverStatePtr driver,
+                const virSecretEntry *secret)
+{
+    char *filename = NULL, *base64 = NULL;
+    int ret = -1;
+
+    if (secret->value == NULL)
+        return 0;
+
+    if (secretEnsureDirectory(conn, driver) < 0)
+        goto cleanup;
+
+    filename = secretBase64Path(conn, driver, secret);
+    if (filename == NULL)
+        goto cleanup;
+    base64_encode_alloc((const char *)secret->value, secret->value_size,
+                        &base64);
+    if (base64 == NULL) {
+        virReportOOMError(conn);
+        goto cleanup;
+    }
+
+    if (replaceFile(conn, filename, base64, strlen(base64)) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(base64);
+    VIR_FREE(filename);
+    return ret;
+}
+
+static int
+secretDeleteSaved(virConnectPtr conn, virSecretDriverStatePtr driver,
+                  const virSecretEntry *secret)
+{
+    char *xml_filename = NULL, *value_filename = NULL;
+    int ret = -1;
+
+    xml_filename = secretXMLPath(conn, driver, secret);
+    if (xml_filename == NULL)
+        goto cleanup;
+    value_filename = secretBase64Path(conn, driver, secret);
+    if (value_filename == NULL)
+        goto cleanup;
+
+    if (unlink(xml_filename) < 0 && errno != ENOENT)
+        goto cleanup;
+    /* When the XML is missing, the rest may waste disk space, but the secret
+       won't be loaded again, so we have succeeded already. */
+    ret = 0;
+
+    (void)unlink(value_filename);
+
+cleanup:
+    VIR_FREE(value_filename);
+    VIR_FREE(xml_filename);
+    return ret;
+}
+
+static int
+secretLoadValidateUUID(virConnectPtr conn, virSecretDefPtr def,
+                       const char *xml_basename)
+{
+    char *base64_id;
+
+    if (def->id == NULL) {
+        virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                             _("<uuid> missing in secret description in '%s'"),
+                             xml_basename);
+        return -1;
+    }
+
+    base64_encode_alloc(def->id, strlen(def->id), &base64_id);
+    if (base64_id == NULL) {
+        virReportOOMError(conn);
+        return -1;
+    }
+    if (!virFileMatchesNameSuffix(xml_basename, base64_id, ".xml")) {
+        virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                             _("<uuid> does not match secret file name '%s'"),
+                             xml_basename);
+        VIR_FREE(base64_id);
+        return -1;
+    }
+    VIR_FREE(base64_id);
+
+    return 0;
+}
+
+static int
+secretLoadValue(virConnectPtr conn, virSecretDriverStatePtr driver,
+                virSecretEntryPtr secret)
+{
+    int ret = -1, fd = -1;
+    struct stat st;
+    char *filename = NULL, *contents = NULL, *value = NULL;
+    size_t value_size;
+
+    filename = secretBase64Path(conn, driver, secret);
+    if (filename == NULL)
+        goto cleanup;
+
+    fd = open(filename, O_RDONLY);
+    if (fd == -1) {
+        if (errno == ENOENT) {
+            ret = 0;
+            goto cleanup;
+        }
+        virReportSystemError (conn, errno, _("cannot open '%s'"), filename);
+        goto cleanup;
+    }
+    if (fstat(fd, &st) < 0) {
+        virReportSystemError (conn, errno, _("cannot stat '%s'"), filename);
+        goto cleanup;
+    }
+    if ((size_t)st.st_size != st.st_size) {
+        virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                             _("'%s' file does not fit in memory"), filename);
+        goto cleanup;
+    }
+
+    if (VIR_ALLOC_N(contents, st.st_size) < 0) {
+        virReportOOMError(conn);
+        goto cleanup;
+    }
+    if (saferead(fd, contents, st.st_size) != st.st_size) {
+        virReportSystemError (conn, errno, _("cannot read '%s'"), filename);
+        goto cleanup;
+    }
+    close(fd);
+    fd = -1;
+
+    if (!base64_decode_alloc(contents, st.st_size, &value, &value_size)) {
+        virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR,
+                             _("invalid base64 in '%s'"), filename);
+        goto cleanup;
+    }
+    if (value == NULL) {
+        virReportOOMError(conn);
+        goto cleanup;
+    }
+
+    secret->value = (unsigned char *)value;
+    value = NULL;
+    secret->value_size = value_size;
+
+    ret = 0;
+
+cleanup:
+    if (value != NULL) {
+        memset(value, 0, value_size);
+        VIR_FREE(value);
+    }
+    if (contents != NULL) {
+        memset(contents, 0, st.st_size);
+        VIR_FREE(contents);
+    }
+    if (fd != -1)
+        close(fd);
+    VIR_FREE(filename);
+    return ret;
+}
+
+static virSecretEntryPtr
+secretLoad(virConnectPtr conn, virSecretDriverStatePtr driver,
+           const char *xml_basename)
+{
+    virSecretDefPtr def;
+    virSecretEntryPtr secret = NULL, ret = NULL;
+    char *xml_filename;
+
+    if (virAsprintf(&xml_filename, "%s/%s", driver->directory,
+                    xml_basename) < 0) {
+        virReportOOMError(conn);
+        goto cleanup;
+    }
+    def = virSecretDefParseFile(conn, xml_filename);
+    if (def == NULL)
+        goto cleanup;
+    VIR_FREE(xml_filename);
+
+    if (secretLoadValidateUUID(conn, def, xml_basename) < 0)
+        goto cleanup;
+
+    if (VIR_ALLOC(secret) < 0)
+        goto cleanup;
+    secret->def = def;
+    def = NULL;
+
+    if (secretLoadValue(conn, driver, secret) < 0)
+        goto cleanup;
+
+    ret = secret;
+    secret = NULL;
+
+cleanup:
+    secretFree(secret);
+    virSecretDefFree(def);
+    VIR_FREE(xml_filename);
+    return ret;
+}
+
+static int
+loadSecrets(virConnectPtr conn, virSecretDriverStatePtr driver,
+            virSecretEntryPtr *dest)
+{
+    int ret = -1;
+    DIR *dir = NULL;
+    struct dirent *de;
+    virSecretEntryPtr list = NULL;
+
+    dir = opendir(driver->directory);
+    if (dir == NULL) {
+        if (errno == ENOENT)
+            return 0;
+        virReportSystemError(conn, errno, _("cannot open '%s'"),
+                             driver->directory);
+        goto cleanup;
+    }
+    while ((de = readdir(dir)) != NULL) {
+        virSecretEntryPtr secret;
+
+        if (STREQ(de->d_name, ".") || STREQ(de->d_name, ".."))
+            continue;
+        if (!virFileHasSuffix(de->d_name, ".xml"))
+            continue;
+
+        secret = secretLoad(conn, driver, de->d_name);
+        if (secret == NULL) {
+            virErrorPtr err = virGetLastError();
+
+            VIR_ERROR(_("Error reading secret: %s\n"),
+                      err != NULL ? err->message: "");
+            virResetError(err);
+            continue;
+        }
+        listInsert(&list, secret);
+    }
+    /* Ignore error reported by readdir(), if any.  It's better to keep the
+       secrets we managed to find. */
+
+    while (list != NULL) {
+        virSecretEntryPtr s;
+
+        s = listUnlink(&list);
+        listInsert(dest, s);
+    }
+
+    ret = 0;
+
+cleanup:
+    while (list != NULL) {
+        virSecretEntryPtr s;
+
+        s = listUnlink(&list);
+        secretFree(s);
+    }
+    if (dir != NULL)
+        closedir(dir);
+    return ret;
+}
+
+ /* Driver functions */
+
+static virDrvOpenStatus
+secretOpen(virConnectPtr conn, virConnectAuthPtr auth ATTRIBUTE_UNUSED,
+           int flags ATTRIBUTE_UNUSED) {
+    if (driverState == NULL)
+        return VIR_DRV_OPEN_DECLINED;
+
+    conn->secretPrivateData = driverState;
+    return VIR_DRV_OPEN_SUCCESS;
+}
+
+static int
+secretClose(virConnectPtr conn) {
+    conn->secretPrivateData = NULL;
+    return 0;
+}
+
+static int
+secretNumOfSecrets(virConnectPtr conn)
+{
+    virSecretDriverStatePtr driver = conn->secretPrivateData;
+    int i;
+    virSecretEntryPtr secret;
+
+    secretDriverLock(driver);
+
+    i = 0;
+    for (secret = driver->secrets; secret != NULL; secret = secret->next)
+        i++;
+
+    secretDriverUnlock(driver);
+    return i;
+}
+
+static int
+secretListSecrets(virConnectPtr conn, char **uuids, int maxuuids)
+{
+    virSecretDriverStatePtr driver = conn->secretPrivateData;
+    int i;
+    virSecretEntryPtr secret;
+
+    memset(uuids, 0, maxuuids * sizeof(*uuids));
+
+    secretDriverLock(driver);
+
+    i = 0;
+    for (secret = driver->secrets; secret != NULL; secret = secret->next) {
+        if (i == maxuuids)
+            break;
+        uuids[i] = strdup(secret->def->id);
+        if (uuids[i] == NULL)
+            goto cleanup;
+        i++;
+    }
+
+    secretDriverUnlock(driver);
+    return i;
+
+cleanup:
+    secretDriverUnlock(driver);
+
+    for (i = 0; i < maxuuids; i++)
+        VIR_FREE(uuids[i]);
+
+    return -1;
+}
+
+static virSecretPtr
+secretLookupByUUIDString(virConnectPtr conn, const char *uuid)
+{
+    virSecretDriverStatePtr driver = conn->secretPrivateData;
+    virSecretPtr ret = NULL;
+    virSecretEntryPtr *pptr;
+
+    secretDriverLock(driver);
+
+    pptr = secretFind(driver, uuid);
+    if (pptr == NULL) {
+        virSecretReportError(conn, VIR_ERR_NO_SECRET,
+                             _("no secret with matching id '%s'"), uuid);
+        goto cleanup;
+    }
+
+    ret = virGetSecret(conn, (*pptr)->def->id);
+
+cleanup:
+    secretDriverUnlock(driver);
+    return ret;
+}
+
+static char *
+secretGenerateUUID(virConnectPtr conn, virSecretDriverStatePtr driver)
+{
+    char *uuid = NULL;
+    unsigned attempt;
+
+    if (VIR_ALLOC_N(uuid, VIR_UUID_STRING_BUFLEN) < 0) {
+        virReportOOMError(conn);
+        goto error;
+    }
+
+    for (attempt = 0; attempt < 65536; attempt++) {
+        unsigned char uuid_data[VIR_UUID_BUFLEN];
+
+        if (virUUIDGenerate(uuid_data) < 0) {
+            virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s",
+                                 _("unable to generate uuid"));
+            goto error;
+        }
+        virUUIDFormat(uuid_data, uuid);
+        if (secretFind(driver, uuid) == NULL)
+            return uuid;
+    }
+    virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s",
+                         _("too many conflicts when generating an uuid"));
+    goto error;
+
+error:
+    VIR_FREE(uuid);
+    return NULL;
+}
+
+static virSecretPtr
+secretDefineXML(virConnectPtr conn, const char *xml,
+                unsigned int flags ATTRIBUTE_UNUSED)
+{
+    virSecretDriverStatePtr driver = conn->secretPrivateData;
+    virSecretPtr ret = NULL;
+    virSecretEntryPtr secret;
+    virSecretDefPtr backup, new_attrs;
+    bool secret_is_new;
+
+    new_attrs = virSecretDefParseString(conn, xml);
+    if (new_attrs == NULL)
+        return NULL;
+
+    secretDriverLock(driver);
+
+    if (new_attrs->id != NULL)
+        secret = secretFindOrCreate(conn, driver, new_attrs->id,
+                                    &secret_is_new);
+    else {
+        new_attrs->id = secretGenerateUUID(conn, driver);
+        if (new_attrs->id == NULL)
+            goto cleanup;
+        secret = secretCreate(conn, driver, new_attrs->id);
+        secret_is_new = true;
+    }
+    if (secret == NULL)
+        goto cleanup;
+
+    /* Save old values of the attributes */
+    backup = secret->def;
+
+    if (backup->private && !new_attrs->private) {
+        virSecretReportError(conn, VIR_ERR_OPERATION_DENIED, "%s",
+                             virErrorMsg(VIR_ERR_OPERATION_DENIED, NULL));
+        goto cleanup;
+    }
+
+    secret->def = new_attrs;
+    if (!new_attrs->ephemeral) {
+        if (backup->ephemeral) {
+            if (secretSaveValue(conn, driver, secret) < 0)
+                goto restore_backup;
+        }
+        if (secretSaveDef(conn, driver, secret) < 0) {
+            if (backup->ephemeral) {
+                char *filename;
+
+                /* Undo the secretSaveValue() above; ignore errors */
+                filename = secretBase64Path(conn, driver, secret);
+                if (filename != NULL)
+                    (void)unlink(filename);
+                VIR_FREE(filename);
+            }
+            goto restore_backup;
+        }
+    } else if (!backup->ephemeral) {
+        if (secretDeleteSaved(conn, driver, secret) < 0)
+            goto restore_backup;
+    }
+    /* Saved succesfully - drop old values */
+    new_attrs = NULL;
+    virSecretDefFree(backup);
+
+    ret = virGetSecret(conn, secret->def->id);
+    goto cleanup;
+
+restore_backup:
+    /* Error - restore previous state and free new attributes */
+    secret->def = backup;
+    if (secret_is_new) {
+        /* "secret" was added to the head of the list above */
+        if (listUnlink(&driverState->secrets) != secret)
+            virSecretReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s",
+                                 _("list of secrets is inconsistent"));
+        else
+            secretFree(secret);
+    }
+
+cleanup:
+    virSecretDefFree(new_attrs);
+    secretDriverUnlock(driver);
+
+    return ret;
+}
+
+static char *
+secretGetXMLDesc(virSecretPtr obj, unsigned int flags ATTRIBUTE_UNUSED)
+{
+    virSecretDriverStatePtr driver = obj->conn->secretPrivateData;
+    char *ret = NULL;
+    virSecretEntryPtr *pptr;
+
+    secretDriverLock(driver);
+
+    pptr = secretFind(driver, obj->uuid);
+    if (pptr == NULL) {
+        virSecretReportError(obj->conn, VIR_ERR_NO_SECRET,
+                             _("no secret with matching id '%s'"), obj->uuid);
+        goto cleanup;
+    }
+
+    ret = virSecretDefFormat(obj->conn, (*pptr)->def);
+
+cleanup:
+    secretDriverUnlock(driver);
+
+    return ret;
+}
+
+static int
+secretSetValue(virSecretPtr obj, const unsigned char *value,
+               size_t value_size, unsigned int flags ATTRIBUTE_UNUSED)
+{
+    virSecretDriverStatePtr driver = obj->conn->secretPrivateData;
+    int ret = -1;
+    unsigned char *old_value, *new_value;
+    size_t old_value_size;
+    virSecretEntryPtr secret, *pptr;
+
+    if (VIR_ALLOC_N(new_value, value_size) < 0) {
+        virReportOOMError(obj->conn);
+        return -1;
+    }
+
+    secretDriverLock(driver);
+
+    pptr = secretFind(driver, obj->uuid);
+    if (pptr == NULL) {
+        virSecretReportError(obj->conn, VIR_ERR_NO_SECRET,
+                             _("no secret with matching id '%s'"), obj->uuid);
+        goto cleanup;
+    }
+    secret = *pptr;
+
+    old_value = secret->value;
+    old_value_size = secret->value_size;
+
+    memcpy(new_value, value, value_size);
+    secret->value = new_value;
+    secret->value_size = value_size;
+    if (!secret->def->ephemeral) {
+        if (secretSaveValue(obj->conn, driver, secret) < 0)
+            goto restore_backup;
+    }
+    /* Saved succesfully - drop old value */
+    if (old_value != NULL) {
+        memset(old_value, 0, old_value_size);
+        VIR_FREE(old_value);
+    }
+    new_value = NULL;
+
+    ret = 0;
+    goto cleanup;
+
+restore_backup:
+    /* Error - restore previous state and free new value */
+    secret->value = old_value;
+    secret->value_size = old_value_size;
+    memset(new_value, 0, value_size);
+
+cleanup:
+    secretDriverUnlock(driver);
+
+    VIR_FREE(new_value);
+
+    return ret;
+}
+
+static unsigned char *
+secretGetValue(virSecretPtr obj, size_t *value_size, unsigned int flags)
+{
+    virSecretDriverStatePtr driver = obj->conn->secretPrivateData;
+    unsigned char *ret = NULL;
+    virSecretEntryPtr *pptr, secret;
+
+    secretDriverLock(driver);
+
+    pptr = secretFind(driver, obj->uuid);
+    if (pptr == NULL) {
+        virSecretReportError(obj->conn, VIR_ERR_NO_SECRET,
+                             _("no secret with matching id '%s'"), obj->uuid);
+        goto cleanup;
+    }
+    secret = *pptr;
+    if (secret->value == NULL) {
+        virSecretReportError(obj->conn, VIR_ERR_NO_SECRET,
+                             _("secret '%s' does not have a value"), obj->uuid);
+        goto cleanup;
+    }
+
+    if ((flags & VIR_SECRET_GET_VALUE_INTERNAL_CALL) == 0 &&
+        secret->def->private) {
+        virSecretReportError(obj->conn, VIR_ERR_OPERATION_DENIED, "%s",
+                             _("secret is private"));
+        goto cleanup;
+    }
+
+    if (VIR_ALLOC_N(ret, secret->value_size) < 0) {
+        virReportOOMError(obj->conn);
+        goto cleanup;
+    }
+    memcpy(ret, secret->value, secret->value_size);
+    *value_size = secret->value_size;
+
+cleanup:
+    secretDriverUnlock(driver);
+
+    return ret;
+}
+
+static int
+secretUndefine(virSecretPtr obj)
+{
+    virSecretDriverStatePtr driver = obj->conn->secretPrivateData;
+    int ret = -1;
+    virSecretEntryPtr *pptr, secret;
+
+    secretDriverLock(driver);
+
+    pptr = secretFind(driver, obj->uuid);
+    if (pptr == NULL) {
+        virSecretReportError(obj->conn, VIR_ERR_NO_SECRET,
+                             _("no secret with matching id '%s'"), obj->uuid);
+        goto cleanup;
+    }
+
+    secret = listUnlink(pptr);
+    if (!secret->def->ephemeral) {
+        if (secretDeleteSaved(obj->conn, driver, secret) < 0)
+            goto restore_backup;
+    }
+    secretFree(secret);
+
+    ret = 0;
+    goto cleanup;
+
+restore_backup:
+    /* This may change the order of secrets in the list.  We don't care. */
+    listInsert(&driver->secrets, secret);
+
+cleanup:
+    secretDriverUnlock(driver);
+
+    return ret;
+}
+
+static int
+secretDriverCleanup(void)
+{
+    if (driverState == NULL)
+        return -1;
+
+    secretDriverLock(driverState);
+
+    while (driverState->secrets != NULL) {
+        virSecretEntryPtr s;
+
+        s = listUnlink(&driverState->secrets);
+        secretFree(s);
+    }
+    VIR_FREE(driverState->directory);
+
+    secretDriverUnlock(driverState);
+    virMutexDestroy(&driverState->lock);
+    VIR_FREE(driverState);
+
+    return 0;
+}
+
+static int
+secretDriverStartup(int privileged)
+{
+    char *base = NULL;
+
+    if (VIR_ALLOC(driverState) < 0)
+        return -1;
+
+    if (virMutexInit(&driverState->lock) < 0) {
+        VIR_FREE(driverState);
+        return -1;
+    }
+    secretDriverLock(driverState);
+
+    if (privileged) {
+        base = strdup(SYSCONF_DIR "/libvirt");
+        if (base == NULL)
+            goto out_of_memory;
+    } else {
+        uid_t uid = geteuid();
+        char *userdir = virGetUserDirectory(NULL, uid);
+
+        if (!userdir)
+            goto error;
+
+        if (virAsprintf(&base, "%s/.libvirt", userdir) == -1) {
+            VIR_FREE(userdir);
+            goto out_of_memory;
+        }
+        VIR_FREE(userdir);
+    }
+    if (virAsprintf(&driverState->directory, "%s/secrets", base) == -1)
+        goto out_of_memory;
+    VIR_FREE(base);
+
+    if (loadSecrets(NULL, driverState, &driverState->secrets) < 0)
+        goto error;
+
+    secretDriverUnlock(driverState);
+    return 0;
+
+ out_of_memory:
+    VIR_ERROR0(_("Out of memory initializing secrets"));
+ error:
+    VIR_FREE(base);
+    secretDriverUnlock(driverState);
+    secretDriverCleanup();
+    return -1;
+}
+
+static int
+secretDriverReload(void)
+{
+    virSecretEntryPtr new_secrets = NULL;
+
+    if (!driverState)
+        return -1;
+
+    secretDriverLock(driverState);
+
+    if (loadSecrets(NULL, driverState, &new_secrets) < 0)
+        goto end;
+
+    /* Keep ephemeral secrets from current state.  Discard non-ephemeral secrets
+       that were removed by the secrets directory.  */
+    while (driverState->secrets != NULL) {
+        virSecretEntryPtr s;
+
+        s = listUnlink(&driverState->secrets);
+        if (s->def->ephemeral)
+            listInsert(&new_secrets, s);
+        else
+            secretFree(s);
+    }
+    driverState->secrets = new_secrets;
+
+ end:
+    secretDriverUnlock(driverState);
+    return 0;
+}
+
+static virSecretDriver secretDriver = {
+    .name = "secret",
+    .open = secretOpen,
+    .close = secretClose,
+    .numOfSecrets = secretNumOfSecrets,
+    .listSecrets = secretListSecrets,
+    .lookupByUUIDString = secretLookupByUUIDString,
+    .defineXML = secretDefineXML,
+    .getXMLDesc = secretGetXMLDesc,
+    .setValue = secretSetValue,
+    .getValue = secretGetValue,
+    .undefine = secretUndefine
+};
+
+static virStateDriver stateDriver = {
+    .initialize = secretDriverStartup,
+    .cleanup = secretDriverCleanup,
+    .reload = secretDriverReload,
+    .active = NULL      /* All persistent state is immediately saved to disk */
+};
+
+int
+secretRegister(void)
+{
+    virRegisterSecretDriver(&secretDriver);
+    virRegisterStateDriver(&stateDriver);
+    return 0;
+}
diff --git a/src/secret_driver.h b/src/secret_driver.h
new file mode 100644
index 0000000..0d0b80a
--- /dev/null
+++ b/src/secret_driver.h
@@ -0,0 +1,28 @@
+/*
+ * secret_driver.h: local driver for secret manipulation API
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Red Hat Author: Miloslav Trmač <[email-masked]>
+ */
+
+#ifndef __VIR_SECRET_DRIVER_H__
+#define __VIR_SECRET_DRIVER_H__
+
+int secretRegister(void);
+
+#endif /* __VIR_SECRET_DRIVER_H__ */
diff --git a/src/virterror.c b/src/virterror.c
index 2a3cdaf..77b295c 100644
--- a/src/virterror.c
+++ b/src/virterror.c
@@ -1082,6 +1082,11 @@ virErrorMsg(virErrorNumber error, const char *info)
                 errmsg = _("Invalid secret");
             else
                 errmsg = _("Invalid secret: %s");
+        case VIR_ERR_NO_SECRET:
+            if (info == NULL)
+                errmsg = _("Secret not found");
+            else
+                errmsg = _("Secret not found: %s");
             break;
     }
     return (errmsg);
-- 
1.6.2.5