This provides two modules for handling SASL
* virNetSASLContext provides the process-wide state, currently
just a whitelist of usernames on the server and a one time
library init call
* virNetTLSSession provides the per-connection state, ie the
SASL session itself. This also include APIs for providing
data encryption/decryption once the session is established
* src/Makefile.am: Add to libvirt-net-rpc.la
* src/rpc/virnetsaslcontext.c, src/rpc/virnetsaslcontext.h: Generic
SASL handling code
---
po/POTFILES.in | 1 +
src/Makefile.am | 3 +
src/rpc/virnetsaslcontext.c | 525 +++++++++++++++++++++++++++++++++++++++++++
src/rpc/virnetsaslcontext.h | 125 ++++++++++
4 files changed, 654 insertions(+), 0 deletions(-)
create mode 100644 src/rpc/virnetsaslcontext.c
create mode 100644 src/rpc/virnetsaslcontext.h
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 39cecdd..bb8c0b2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -59,6 +59,7 @@ src/qemu/qemu_monitor_text.c
src/qemu/qemu_security_dac.c
src/remote/remote_driver.c
src/rpc/virnetmessage.c
+src/rpc/virnetsaslcontext.c
src/rpc/virnetsocket.c
src/rpc/virnettlscontext.c
src/secret/secret_driver.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 10fce7d..def7e0a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1124,13 +1124,16 @@ libvirt_net_rpc_la_SOURCES = \
../daemon/event.c \
rpc/virnetmessage.h rpc/virnetmessage.c \
rpc/virnetprotocol.h rpc/virnetprotocol.c \
+ rpc/virnetsaslcontext.h rpc/virnetsaslcontext.c \
rpc/virnetsocket.h rpc/virnetsocket.c \
rpc/virnettlscontext.h rpc/virnettlscontext.c
libvirt_net_rpc_la_CFLAGS = \
$(GNUTLS_CFLAGS) \
+ $(SASL_CFLAGS) \
$(AM_CFLAGS)
libvirt_net_rpc_la_LDFLAGS = \
$(GNUTLS_LIBS) \
+ $(SASL_LIBS) \
$(AM_LDFLAGS) \
$(CYGWIN_EXTRA_LDFLAGS) \
$(MINGW_EXTRA_LDFLAGS)
diff --git a/src/rpc/virnetsaslcontext.c b/src/rpc/virnetsaslcontext.c
new file mode 100644
index 0000000..8e72d2c
--- /dev/null
+++ b/src/rpc/virnetsaslcontext.c
@@ -0,0 +1,525 @@
+/*
+ * virnetsaslcontext.c: SASL encryption/auth handling
+ *
+ * Copyright (C) 2010 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
+ */
+
+#include <config.h>
+
+#include <fnmatch.h>
+
+#include "virnetsaslcontext.h"
+
+#include "virterror_internal.h"
+#include "memory.h"
+#include "logging.h"
+
+#define VIR_FROM_THIS VIR_FROM_RPC
+
+#define virNetError(code, ...) \
+ virReportErrorHelper(NULL, VIR_FROM_RPC, code, __FILE__, \
+ __FUNCTION__, __LINE__, __VA_ARGS__)
+
+
+struct _virNetSASLContext {
+ const char *const*usernameWhitelist;
+ int refs;
+};
+
+struct _virNetSASLSession {
+ sasl_conn_t *conn;
+ int refs;
+};
+
+
+virNetSASLContextPtr virNetSASLContextNewClient(void)
+{
+ virNetSASLContextPtr ctxt;
+ int err;
+
+ err = sasl_client_init(NULL);
+ if (err != SASL_OK) {
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("failed to initialize SASL library: %d (%s)"),
+ err, sasl_errstring(err, NULL, NULL));
+ return NULL;
+ }
+
+ if (VIR_ALLOC(ctxt) < 0) {
+ virReportOOMError();
+ return NULL;
+ }
+
+ ctxt->refs = 1;
+
+ return ctxt;
+}
+
+virNetSASLContextPtr virNetSASLContextNewServer(const char *const*usernameWhitelist)
+{
+ virNetSASLContextPtr ctxt;
+ int err;
+
+ err = sasl_server_init(NULL, "libvirt");
+ if (err != SASL_OK) {
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("failed to initialize SASL library: %d (%s)"),
+ err, sasl_errstring(err, NULL, NULL));
+ return NULL;
+ }
+
+ if (VIR_ALLOC(ctxt) < 0) {
+ virReportOOMError();
+ return NULL;
+ }
+
+ ctxt->usernameWhitelist = usernameWhitelist;
+ ctxt->refs = 1;
+
+ return ctxt;
+}
+
+int virNetSASLContextCheckIdentity(virNetSASLContextPtr ctxt,
+ const char *identity)
+{
+ const char *const*wildcards;
+
+ /* If the list is not set, allow any DN. */
+ wildcards = ctxt->usernameWhitelist;
+ if (!wildcards)
+ return 1; /* No ACL, allow all */
+
+ while (*wildcards) {
+ if (fnmatch (*wildcards, identity, 0) == 0)
+ return 1; /* Allowed */
+ wildcards++;
+ }
+
+ /* Denied */
+ VIR_ERROR(_("SASL client %s not allowed in whitelist"), identity);
+ return 0;
+}
+
+
+void virNetSASLContextRef(virNetSASLContextPtr ctxt)
+{
+ ctxt->refs++;
+}
+
+void virNetSASLContextFree(virNetSASLContextPtr ctxt)
+{
+ if (!ctxt)
+ return;
+
+ ctxt->refs--;
+ if (ctxt->refs > 0)
+ return;
+
+ VIR_FREE(ctxt);
+}
+
+virNetSASLSessionPtr virNetSASLSessionNewClient(virNetSASLContextPtr ctxt
ATTRIBUTE_UNUSED,
+ const char *service,
+ const char *hostname,
+ const char *localAddr,
+ const char *remoteAddr,
+ const sasl_callback_t *cbs)
+{
+ virNetSASLSessionPtr sasl = NULL;
+ int err;
+
+ if (VIR_ALLOC(sasl) < 0) {
+ virReportOOMError();
+ goto cleanup;
+ }
+
+ sasl->refs = 1;
+
+ err = sasl_client_new(service,
+ hostname,
+ localAddr,
+ remoteAddr,
+ cbs,
+ SASL_SUCCESS_DATA,
+ &sasl->conn);
+ if (err != SASL_OK) {
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("Failed to create SASL client context: %d (%s)"),
+ err, sasl_errstring(err, NULL, NULL));
+ goto cleanup;
+ }
+
+ return sasl;
+
+cleanup:
+ virNetSASLSessionFree(sasl);
+ return NULL;
+}
+
+virNetSASLSessionPtr virNetSASLSessionNewServer(virNetSASLContextPtr ctxt
ATTRIBUTE_UNUSED,
+ const char *service,
+ const char *localAddr,
+ const char *remoteAddr)
+{
+ virNetSASLSessionPtr sasl = NULL;
+ int err;
+
+ if (VIR_ALLOC(sasl) < 0) {
+ virReportOOMError();
+ goto cleanup;
+ }
+
+ sasl->refs = 1;
+
+ err = sasl_server_new(service,
+ NULL,
+ NULL,
+ localAddr,
+ remoteAddr,
+ NULL,
+ SASL_SUCCESS_DATA,
+ &sasl->conn);
+ if (err != SASL_OK) {
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("Failed to create SASL client context: %d (%s)"),
+ err, sasl_errstring(err, NULL, NULL));
+ goto cleanup;
+ }
+
+ return sasl;
+
+cleanup:
+ virNetSASLSessionFree(sasl);
+ return NULL;
+}
+
+void virNetSASLSessionRef(virNetSASLSessionPtr sasl)
+{
+ sasl->refs++;
+}
+
+int virNetSASLSessionExtKeySize(virNetSASLSessionPtr sasl,
+ int ssf)
+{
+ int err;
+
+ err = sasl_setprop(sasl->conn, SASL_SSF_EXTERNAL, &ssf);
+ if (err != SASL_OK) {
+ virNetError(VIR_ERR_INTERNAL_ERROR,
+ _("cannot set external SSF %d (%s)"),
+ err, sasl_errstring(err, NULL, NULL));
+ return -1;
+ }
+ return 0;
+}
+
+const char *virNetSASLSessionGetIdentity(virNetSASLSessionPtr sasl)
+{
+ const void *val;
+ int err;
+
+ err = sasl_getprop(sasl->conn, SASL_USERNAME, &val);
+ if (err != SASL_OK) {
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("cannot query SASL username on connection %d (%s)"),
+ err, sasl_errstring(err, NULL, NULL));
+ return NULL;
+ }
+ if (val == NULL) {
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("no client username was found"));
+ return NULL;
+ }
+ VIR_DEBUG("SASL client username %s", (const char *)val);
+
+ return (const char*)val;
+}
+
+
+int virNetSASLSessionGetKeySize(virNetSASLSessionPtr sasl)
+{
+ int err;
+ int ssf;
+ const void *val;
+ err = sasl_getprop(sasl->conn, SASL_SSF, &val);
+ if (err != SASL_OK) {
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("cannot query SASL ssf on connection %d (%s)"),
+ err, sasl_errstring(err, NULL, NULL));
+ return -1;
+ }
+ ssf = *(const int *)val;
+ return ssf;
+}
+
+int virNetSASLSessionSecProps(virNetSASLSessionPtr sasl,
+ int minSSF,
+ int maxSSF,
+ bool allowAnonymous)
+{
+ sasl_security_properties_t secprops;
+ int err;
+
+ memset (&secprops, 0, sizeof secprops);
+
+ secprops.min_ssf = minSSF;
+ secprops.max_ssf = maxSSF;
+ secprops.maxbufsize = 100000;
+ secprops.security_flags = allowAnonymous ? 0 :
+ SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
+
+ err = sasl_setprop(sasl->conn, SASL_SEC_PROPS, &secprops);
+ if (err != SASL_OK) {
+ virNetError(VIR_ERR_INTERNAL_ERROR,
+ _("cannot set security props %d (%s)"),
+ err, sasl_errstring(err, NULL, NULL));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+char *virNetSASLSessionListMechanisms(virNetSASLSessionPtr sasl)
+{
+ const char *mechlist;
+ char *ret;
+ int err;
+
+ err = sasl_listmech(sasl->conn,
+ NULL, /* Don't need to set user */
+ "", /* Prefix */
+ ",", /* Separator */
+ "", /* Suffix */
+ &mechlist,
+ NULL,
+ NULL);
+ if (err != SASL_OK) {
+ virNetError(VIR_ERR_INTERNAL_ERROR,
+ _("cannot list SASL mechanisms %d (%s)"),
+ err, sasl_errdetail(sasl->conn));
+ return NULL;
+ }
+ if (!(ret = strdup(mechlist))) {
+ virReportOOMError();
+ return NULL;
+ }
+ return ret;
+}
+
+
+int virNetSASLSessionClientStart(virNetSASLSessionPtr sasl,
+ const char *mechlist,
+ sasl_interact_t **prompt_need,
+ const char **clientout,
+ size_t *clientoutlen,
+ const char **mech)
+{
+ unsigned outlen = 0;
+
+ int err = sasl_client_start(sasl->conn,
+ mechlist,
+ prompt_need,
+ clientout,
+ &outlen,
+ mech);
+
+ *clientoutlen = outlen;
+
+ switch (err) {
+ case SASL_OK:
+ return VIR_NET_SASL_COMPLETE;
+ case SASL_CONTINUE:
+ return VIR_NET_SASL_CONTINUE;
+ case SASL_INTERACT:
+ return VIR_NET_SASL_INTERACT;
+
+ default:
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("Failed to start SASL negotiation: %d (%s)"),
+ err, sasl_errdetail(sasl->conn));
+ return -1;
+ }
+}
+
+
+int virNetSASLSessionClientStep(virNetSASLSessionPtr sasl,
+ const char *serverin,
+ size_t serverinlen,
+ sasl_interact_t **prompt_need,
+ const char **clientout,
+ size_t *clientoutlen)
+{
+ unsigned inlen = serverinlen;
+ unsigned outlen = 0;
+
+ int err = sasl_client_step(sasl->conn,
+ serverin,
+ inlen,
+ prompt_need,
+ clientout,
+ &outlen);
+ *clientoutlen = outlen;
+
+ switch (err) {
+ case SASL_OK:
+ return VIR_NET_SASL_COMPLETE;
+ case SASL_CONTINUE:
+ return VIR_NET_SASL_CONTINUE;
+ case SASL_INTERACT:
+ return VIR_NET_SASL_INTERACT;
+
+ default:
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("Failed to start SASL negotiation: %d (%s)"),
+ err, sasl_errdetail(sasl->conn));
+ return -1;
+ }
+}
+
+int virNetSASLSessionServerStart(virNetSASLSessionPtr sasl,
+ const char *mechname,
+ const char *clientin,
+ size_t clientinlen,
+ const char **serverout,
+ size_t *serveroutlen)
+{
+ unsigned inlen = clientinlen;
+ unsigned outlen = 0;
+ int err = sasl_server_start(sasl->conn,
+ mechname,
+ clientin,
+ inlen,
+ serverout,
+ &outlen);
+
+ *serveroutlen = outlen;
+
+ switch (err) {
+ case SASL_OK:
+ return VIR_NET_SASL_COMPLETE;
+ case SASL_CONTINUE:
+ return VIR_NET_SASL_CONTINUE;
+ case SASL_INTERACT:
+ return VIR_NET_SASL_INTERACT;
+
+ default:
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("Failed to start SASL negotiation: %d (%s)"),
+ err, sasl_errdetail(sasl->conn));
+ return -1;
+ }
+}
+
+
+int virNetSASLSessionServerStep(virNetSASLSessionPtr sasl,
+ const char *clientin,
+ size_t clientinlen,
+ const char **serverout,
+ size_t *serveroutlen)
+{
+ unsigned inlen = clientinlen;
+ unsigned outlen = 0;
+
+ int err = sasl_server_step(sasl->conn,
+ clientin,
+ inlen,
+ serverout,
+ &outlen);
+
+ *serveroutlen = outlen;
+
+ switch (err) {
+ case SASL_OK:
+ return VIR_NET_SASL_COMPLETE;
+ case SASL_CONTINUE:
+ return VIR_NET_SASL_CONTINUE;
+ case SASL_INTERACT:
+ return VIR_NET_SASL_INTERACT;
+
+ default:
+ VIR_DEBUG("Foo %s", sasl_errdetail(sasl->conn));
+ virNetError(VIR_ERR_AUTH_FAILED,
+ _("Failed to start SASL negotiation: %d (%s)"),
+ err, sasl_errdetail(sasl->conn));
+ return -1;
+ }
+}
+
+ssize_t virNetSASLSessionEncode(virNetSASLSessionPtr sasl,
+ const char *input,
+ size_t inputLen,
+ const char **output,
+ size_t *outputlen)
+{
+ unsigned inlen = inputLen;
+ unsigned outlen = 0;
+ int err;
+ err = sasl_encode(sasl->conn,
+ input,
+ inlen,
+ output,
+ &outlen);
+ *outputlen = outlen;
+
+ if (err != SASL_OK) {
+ virNetError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to encode SASL data: %d (%s)"),
+ err, sasl_errstring(err, NULL, NULL));
+ return -1;
+ }
+ return 0;
+}
+
+ssize_t virNetSASLSessionDecode(virNetSASLSessionPtr sasl,
+ const char *input,
+ size_t inputLen,
+ const char **output,
+ size_t *outputlen)
+{
+ unsigned inlen = inputLen;
+ unsigned outlen = 0;
+ int err;
+ err = sasl_decode(sasl->conn,
+ input,
+ inlen,
+ output,
+ &outlen);
+ *outputlen = outlen;
+ if (err != SASL_OK) {
+ virNetError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to decode SASL data: %d (%s)"),
+ err, sasl_errstring(err, NULL, NULL));
+ return -1;
+ }
+ return 0;
+}
+
+void virNetSASLSessionFree(virNetSASLSessionPtr sasl)
+{
+ if (!sasl)
+ return;
+
+ sasl->refs--;
+ if (sasl->refs > 0)
+ return;
+
+ if (sasl->conn)
+ sasl_dispose(&sasl->conn);
+
+ VIR_FREE(sasl);
+}
diff --git a/src/rpc/virnetsaslcontext.h b/src/rpc/virnetsaslcontext.h
new file mode 100644
index 0000000..9efa2cc
--- /dev/null
+++ b/src/rpc/virnetsaslcontext.h
@@ -0,0 +1,125 @@
+/*
+ * virnetsaslcontext.h: SASL encryption/auth handling
+ *
+ * Copyright (C) 2010 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
+ */
+
+#ifndef __VIR_NET_CLIENT_SASL_CONTEXT_H__
+# define __VIR_NET_CLIENT_SASL_CONTEXT_H__
+
+# ifdef HAVE_SASL
+/* Only needed for sasl_callback_t :-( */
+# include <sasl/sasl.h>
+# endif
+
+# include <stdbool.h>
+# include <sys/types.h>
+
+# ifndef HAVE_SASL
+typedef sasl_callback_t void *;
+# endif
+
+typedef struct _virNetSASLContext virNetSASLContext;
+typedef virNetSASLContext *virNetSASLContextPtr;
+
+typedef struct _virNetSASLSession virNetSASLSession;
+typedef virNetSASLSession *virNetSASLSessionPtr;
+
+enum {
+ VIR_NET_SASL_COMPLETE,
+ VIR_NET_SASL_CONTINUE,
+ VIR_NET_SASL_INTERACT,
+};
+
+virNetSASLContextPtr virNetSASLContextNewClient(void);
+virNetSASLContextPtr virNetSASLContextNewServer(const char *const*usernameWhitelist);
+
+int virNetSASLContextCheckIdentity(virNetSASLContextPtr ctxt,
+ const char *identity);
+
+void virNetSASLContextRef(virNetSASLContextPtr sasl);
+void virNetSASLContextFree(virNetSASLContextPtr sasl);
+
+virNetSASLSessionPtr virNetSASLSessionNewClient(virNetSASLContextPtr ctxt,
+ const char *service,
+ const char *hostname,
+ const char *localAddr,
+ const char *remoteAddr,
+ const sasl_callback_t *cbs);
+virNetSASLSessionPtr virNetSASLSessionNewServer(virNetSASLContextPtr ctxt,
+ const char *service,
+ const char *localAddr,
+ const char *remoteAddr);
+
+char *virNetSASLSessionListMechanisms(virNetSASLSessionPtr sasl);
+
+void virNetSASLSessionRef(virNetSASLSessionPtr sasl);
+
+int virNetSASLSessionExtKeySize(virNetSASLSessionPtr sasl,
+ int ssf);
+
+int virNetSASLSessionGetKeySize(virNetSASLSessionPtr sasl);
+
+const char *virNetSASLSessionGetIdentity(virNetSASLSessionPtr sasl);
+
+int virNetSASLSessionSecProps(virNetSASLSessionPtr sasl,
+ int minSSF,
+ int maxSSF,
+ bool allowAnonymous);
+
+int virNetSASLSessionClientStart(virNetSASLSessionPtr sasl,
+ const char *mechlist,
+ sasl_interact_t **prompt_need,
+ const char **clientout,
+ size_t *clientoutlen,
+ const char **mech);
+
+int virNetSASLSessionClientStep(virNetSASLSessionPtr sasl,
+ const char *serverin,
+ size_t serverinlen,
+ sasl_interact_t **prompt_need,
+ const char **clientout,
+ size_t *clientoutlen);
+
+int virNetSASLSessionServerStart(virNetSASLSessionPtr sasl,
+ const char *mechname,
+ const char *clientin,
+ size_t clientinlen,
+ const char **serverout,
+ size_t *serveroutlen);
+
+int virNetSASLSessionServerStep(virNetSASLSessionPtr sasl,
+ const char *clientin,
+ size_t clientinlen,
+ const char **serverout,
+ size_t *serveroutlen);
+
+ssize_t virNetSASLSessionEncode(virNetSASLSessionPtr sasl,
+ const char *input,
+ size_t inputLen,
+ const char **output,
+ size_t *outputlen);
+
+ssize_t virNetSASLSessionDecode(virNetSASLSessionPtr sasl,
+ const char *input,
+ size_t inputLen,
+ const char **output,
+ size_t *outputlen);
+
+void virNetSASLSessionFree(virNetSASLSessionPtr sasl);
+
+#endif /* __VIR_NET_CLIENT_SASL_CONTEXT_H__ */
--
1.7.2.3