Index: qemu-kvm-0.10/qemu/Makefile
===================================================================
--- qemu-kvm-0.10.orig/qemu/Makefile
+++ qemu-kvm-0.10/qemu/Makefile
@@ -148,7 +148,7 @@ endif
ifdef CONFIG_CURSES
OBJS+=curses.o
endif
-OBJS+=vnc.o d3des.o
+OBJS+=vnc.o acl.o d3des.o
ifdef CONFIG_VNC_TLS
OBJS+=vnc-tls.o vnc-auth-vencrypt.o
endif
@@ -178,9 +178,11 @@ sdl.o: sdl.c keymaps.h sdl_keysym.h
sdl.o audio/sdlaudio.o: CFLAGS += $(SDL_CFLAGS)
+acl.o: acl.h acl.c
+
vnc.h: vnc-tls.h vnc-auth-vencrypt.h vnc-auth-sasl.h keymaps.h
-vnc.o: vnc.c vnc.h vnc_keysym.h vnchextile.h d3des.c d3des.h
+vnc.o: vnc.c vnc.h vnc_keysym.h vnchextile.h d3des.c d3des.h acl.h
vnc.o: CFLAGS += $(CONFIG_VNC_TLS_CFLAGS)
Index: qemu-kvm-0.10/qemu/acl.c
===================================================================
--- /dev/null
+++ qemu-kvm-0.10/qemu/acl.c
@@ -0,0 +1,185 @@
+/*
+ * QEMU access control list management
+ *
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+#include "qemu-common.h"
+#include "sysemu.h"
+#include "acl.h"
+
+#ifdef HAVE_FNMATCH_H
+#include <fnmatch.h>
+#endif
+
+
+static unsigned int nacls = 0;
+static qemu_acl **acls = NULL;
+
+
+
+qemu_acl *qemu_acl_find(const char *aclname)
+{
+ int i;
+ for (i = 0 ; i < nacls ; i++) {
+ if (strcmp(acls[i]->aclname, aclname) == 0)
+ return acls[i];
+ }
+
+ return NULL;
+}
+
+qemu_acl *qemu_acl_init(const char *aclname)
+{
+ qemu_acl *acl;
+
+ acl = qemu_acl_find(aclname);
+ if (acl)
+ return acl;
+
+ acl = qemu_malloc(sizeof(*acl));
+ acl->aclname = qemu_strdup(aclname);
+ /* Deny by default, so there is no window of "open
+ * access" between QEMU starting, and the user setting
+ * up ACLs in the monitor */
+ acl->defaultDeny = 1;
+
+ acl->nentries = 0;
+ TAILQ_INIT(&acl->entries);
+
+ acls = qemu_realloc(acls, sizeof(*acls) * (nacls +1));
+ acls[nacls] = acl;
+ nacls++;
+
+ return acl;
+}
+
+int qemu_acl_party_is_allowed(qemu_acl *acl,
+ const char *party)
+{
+ qemu_acl_entry *entry;
+
+ TAILQ_FOREACH(entry, &acl->entries, next) {
+#ifdef HAVE_FNMATCH_H
+ if (fnmatch(entry->match, party, 0) == 0)
+ return entry->deny ? 0 : 1;
+#else
+ /* No fnmatch, so fallback to exact string matching
+ * instead of allowing wildcards */
+ if (strcmp(entry->match, party) == 0)
+ return entry->deny ? 0 : 1;
+#endif
+ }
+
+ return acl->defaultDeny ? 0 : 1;
+}
+
+
+void qemu_acl_reset(qemu_acl *acl)
+{
+ qemu_acl_entry *entry;
+
+ /* Put back to deny by default, so there is no window
+ * of "open access" while the user re-initializes the
+ * access control list */
+ acl->defaultDeny = 1;
+ TAILQ_FOREACH(entry, &acl->entries, next) {
+ TAILQ_REMOVE(&acl->entries, entry, next);
+ free(entry->match);
+ free(entry);
+ }
+ acl->nentries = 0;
+}
+
+
+int qemu_acl_append(qemu_acl *acl,
+ int deny,
+ const char *match)
+{
+ qemu_acl_entry *entry;
+
+ entry = qemu_malloc(sizeof(*entry));
+ entry->match = qemu_strdup(match);
+ entry->deny = deny;
+
+ TAILQ_INSERT_TAIL(&acl->entries, entry, next);
+ acl->nentries++;
+
+ return acl->nentries;
+}
+
+
+int qemu_acl_insert(qemu_acl *acl,
+ int deny,
+ const char *match,
+ int index)
+{
+ qemu_acl_entry *entry;
+ qemu_acl_entry *tmp;
+ int i = 0;
+
+ if (index <= 0)
+ return -1;
+ if (index >= acl->nentries)
+ return qemu_acl_append(acl, deny, match);
+
+
+ entry = qemu_malloc(sizeof(*entry));
+ entry->match = qemu_strdup(match);
+ entry->deny = deny;
+
+ TAILQ_FOREACH(tmp, &acl->entries, next) {
+ i++;
+ if (i == index) {
+ TAILQ_INSERT_BEFORE(tmp, entry, next);
+ acl->nentries++;
+ break;
+ }
+ }
+
+ return i;
+}
+
+int qemu_acl_remove(qemu_acl *acl,
+ const char *match)
+{
+ qemu_acl_entry *entry;
+ int i = 0;
+
+ TAILQ_FOREACH(entry, &acl->entries, next) {
+ i++;
+ if (strcmp(entry->match, match) == 0) {
+ TAILQ_REMOVE(&acl->entries, entry, next);
+ return i;
+ }
+ }
+ return -1;
+}
+
+
+/*
+ * Local variables:
+ * c-indent-level: 4
+ * c-basic-offset: 4
+ * tab-width: 8
+ * End:
+ */
Index: qemu-kvm-0.10/qemu/acl.h
===================================================================
--- /dev/null
+++ qemu-kvm-0.10/qemu/acl.h
@@ -0,0 +1,74 @@
+/*
+ * QEMU access control list management
+ *
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef __QEMU_ACL_H__
+#define __QEMU_ACL_H__
+
+#include "sys-queue.h"
+
+typedef struct qemu_acl_entry qemu_acl_entry;
+typedef struct qemu_acl qemu_acl;
+
+struct qemu_acl_entry {
+ char *match;
+ int deny;
+
+ TAILQ_ENTRY(qemu_acl_entry) next;
+};
+
+struct qemu_acl {
+ char *aclname;
+ unsigned int nentries;
+ TAILQ_HEAD(,qemu_acl_entry) entries;
+ int defaultDeny;
+};
+
+qemu_acl *qemu_acl_init(const char *aclname);
+
+qemu_acl *qemu_acl_find(const char *aclname);
+
+int qemu_acl_party_is_allowed(qemu_acl *acl,
+ const char *party);
+
+void qemu_acl_reset(qemu_acl *acl);
+
+int qemu_acl_append(qemu_acl *acl,
+ int deny,
+ const char *match);
+int qemu_acl_insert(qemu_acl *acl,
+ int deny,
+ const char *match,
+ int index);
+int qemu_acl_remove(qemu_acl *acl,
+ const char *match);
+
+#endif /* __QEMU_ACL_H__ */
+
+/*
+ * Local variables:
+ * c-indent-level: 4
+ * c-basic-offset: 4
+ * tab-width: 8
+ * End:
+ */
Index: qemu-kvm-0.10/qemu/configure
===================================================================
--- qemu-kvm-0.10.orig/qemu/configure
+++ qemu-kvm-0.10/qemu/configure
@@ -913,6 +913,21 @@ EOF
fi
##########################################
+# fnmatch() probe, used for ACL routines
+fnmatch="no"
+cat > $TMPC << EOF
+#include <fnmatch.h>
+int main(void)
+{
+ fnmatch("foo", "foo", 0);
+ return 0;
+}
+EOF
+if $cc $ARCH_CFLAGS -o $TMPE $TMPC > /dev/null 2> /dev/null ; then
+ fnmatch="yes"
+fi
+
+##########################################
# vde libraries probe
if test "$vde" = "yes" ; then
cat > $TMPC << EOF
@@ -1501,6 +1516,9 @@ if test "$vnc_sasl" = "yes" ; then
echo "CONFIG_VNC_SASL_LIBS=$vnc_sasl_libs" >> $config_mak
echo "#define CONFIG_VNC_SASL 1" >> $config_h
fi
+if test "$fnmatch" = "yes" ; then
+ echo "#define HAVE_FNMATCH_H 1" >> $config_h
+fi
qemu_version=`head $source_path/VERSION`
echo "VERSION=$qemu_version" >>$config_mak
echo "#define QEMU_VERSION \"$qemu_version\"" >> $config_h
Index: qemu-kvm-0.10/qemu/monitor.c
===================================================================
--- qemu-kvm-0.10.orig/qemu/monitor.c
+++ qemu-kvm-0.10/qemu/monitor.c
@@ -39,6 +39,7 @@
#include "qemu-timer.h"
#include "migration.h"
#include "kvm.h"
+#include "acl.h"
#include "qemu-kvm.h"
@@ -1498,6 +1499,85 @@ static void do_info_balloon(void)
term_printf("balloon: actual=%d\n", (int)(actual >> 20));
}
+static void do_acl(const char *command,
+ const char *aclname,
+ const char *match,
+ int has_index,
+ int index)
+{
+ qemu_acl *acl;
+
+ acl = qemu_acl_find(aclname);
+ if (!acl) {
+ term_printf("acl: unknown list '%s'\n", aclname);
+ return;
+ }
+
+ if (strcmp(command, "show") == 0) {
+ int i = 0;
+ qemu_acl_entry *entry;
+ term_printf("policy: %s\n",
+ acl->defaultDeny ? "deny" : "allow");
+ TAILQ_FOREACH(entry, &acl->entries, next) {
+ i++;
+ term_printf("%d: %s %s\n", i,
+ entry->deny ? "deny" : "allow",
+ entry->match);
+ }
+ } else if (strcmp(command, "reset") == 0) {
+ qemu_acl_reset(acl);
+ term_printf("acl: removed all rules\n");
+ } else if (strcmp(command, "policy") == 0) {
+ if (!match) {
+ term_printf("acl: missing policy parameter\n");
+ return;
+ }
+
+ if (strcmp(match, "allow") == 0) {
+ acl->defaultDeny = 0;
+ term_printf("acl: policy set to 'allow'\n");
+ } else if (strcmp(match, "deny") == 0) {
+ acl->defaultDeny = 1;
+ term_printf("acl: policy set to 'deny'\n");
+ } else {
+ term_printf("acl: unknown policy '%s', expected 'deny' or 'allow'\n", match);
+ }
+ } else if ((strcmp(command, "allow") == 0) ||
+ (strcmp(command, "deny") == 0)) {
+ int deny = strcmp(command, "deny") == 0 ? 1 : 0;
+ int ret;
+
+ if (!match) {
+ term_printf("acl: missing match parameter\n");
+ return;
+ }
+
+ if (has_index)
+ ret = qemu_acl_insert(acl, deny, match, index);
+ else
+ ret = qemu_acl_append(acl, deny, match);
+ if (ret < 0)
+ term_printf("acl: unable to add acl entry\n");
+ else
+ term_printf("acl: added rule at position %d\n", ret);
+ } else if (strcmp(command, "remove") == 0) {
+ int ret;
+
+ if (!match) {
+ term_printf("acl: missing match parameter\n");
+ return;
+ }
+
+ ret = qemu_acl_remove(acl, match);
+ if (ret < 0)
+ term_printf("acl: no matching acl entry\n");
+ else
+ term_printf("acl: removed rule at position %d\n", ret);
+ } else {
+ term_printf("acl: unknown command '%s'\n", command);
+ }
+}
+
/* Please update qemu-doc.texi when adding or changing commands */
static const term_cmd_t term_cmds[] = {
{ "help|?", "s?", do_help,
@@ -1603,6 +1683,12 @@ static const term_cmd_t term_cmds[] = {
{ "set_link", "ss", do_set_link,
"name [up|down]", "change the link status of a network adapter" },
{ "set_link", "ss", do_set_link, "name [up|down]" },
+ { "acl", "sss?i?", do_acl, "<command> <aclname> [<match>] [<index>]\n",
+ "acl show vnc.username\n"
+ "acl policy vnc.username deny\n"
+ "acl allow vnc.username fred\n"
+ "acl deny vnc.username bob\n"
+ "acl reset vnc.username\n" },
{ "cpu_set", "is", do_cpu_set_nr, "cpu [online|offline]", "change cpu state" },
#if defined(TARGET_I386) || defined(TARGET_X86_64)
{ "drive_add", "iss", drive_hot_add, "pcibus pcidevfn [file=file][,if=type][,bus=n]\n"
@@ -1611,6 +1697,7 @@ static const term_cmd_t term_cmds[] = {
"[snapshot=on|off][,cache=on|off]",
"add drive to PCI storage controller" },
#endif
+
{ NULL, NULL, },
};
@@ -2995,3 +3082,12 @@ int monitor_read_bdrv_key(BlockDriverSta
}
return -EPERM;
}
+
+
+/*
+ * Local variables:
+ * c-indent-level: 4
+ * c-basic-offset: 4
+ * tab-width: 8
+ * End:
+ */
Index: qemu-kvm-0.10/qemu/qemu-doc.texi
===================================================================
--- qemu-kvm-0.10.orig/qemu/qemu-doc.texi
+++ qemu-kvm-0.10/qemu/qemu-doc.texi
@@ -639,6 +639,19 @@ ensures a data encryption preventing com
credentials. See the @ref{vnc_security} section for details on using
SASL authentication.
+@item acl
+
+Turn on access control lists for checking of the x509 client certificate
+and SASL party. For x509 certs, the ACL check is made against the
+certificate's distinguished name. This is something that looks like
+@code{C=GB,O=ACME,L=Boston,CN=bob}. For SASL party, the ACL check is
+made against the username, which depending on the SASL plugin, may
+include a realm component, eg @code{bob} or @code{bob\@EXAMPLE.COM}.
+When the @option{acl} flag is set, the initial access list will be
+empty, with a @code{deny} policy. Thus no one will be allowed to
+use the VNC server until the ACLs have been loaded. This can be
+achieved using the @code{acl} monitor command.
+
@end table
@end table
@@ -1400,6 +1413,42 @@ Password: ********
@end table
+@item acl @var{subcommand} @var{aclname} @var{match} @var{index}
+
+Manage access control lists for network services. There are currently
+two named access control lists, @var{vnc.x509dname} and @var{vnc.username}
+matching on the x509 client certificate distinguished name, and SASL
+username respectively.
+
+@table @option
+@item acl show <aclname>
+list all the match rules in the access control list, and the default
+policy
+@item acl policy <aclname> @code{allow|deny}
+set the default access control list policy, used in the event that
+none of the explicit rules match. The default policy at startup is
+always @code{deny}
+@item acl allow <aclname> <match> [<index>]
+add a match to the access control list, allowing access. The match will
+normally be an exact username or x509 distinguished name, but can
+optionally include wildcard globs. eg @code{*\@EXAMPLE.COM} to allow
+all users in the @code{EXAMPLE.COM} kerberos realm. The match will
+normally be appended to the end of the ACL, but can be inserted
+earlier in the list if the optional @code{index} parameter is supplied.
+@item acl deny <aclname> <match> [<index>]
+add a match to the access control list, denying access. The match will
+normally be an exact username or x509 distinguished name, but can
+optionally include wildcard globs. eg @code{*\@EXAMPLE.COM} to allow
+all users in the @code{EXAMPLE.COM} kerberos realm. The match will
+normally be appended to the end of the ACL, but can be inserted
+earlier in the list if the optional @code{index} parameter is supplied.
+@item acl remove <aclname> <match>
+remove the specified match rule from the access control list.
+@item acl reset <aclname>
+remove all matches from the access control list, and set the default
+policy back to @code{deny}.
+@end table
+
@item screendump @var{filename}
Save screen into PPM image @var{filename}.
Index: qemu-kvm-0.10/qemu/vnc-auth-sasl.c
===================================================================
--- qemu-kvm-0.10.orig/qemu/vnc-auth-sasl.c
+++ qemu-kvm-0.10/qemu/vnc-auth-sasl.c
@@ -120,22 +120,32 @@ static int vnc_auth_sasl_check_access(Vn
{
const void *val;
int err;
+ int allow;
err = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
if (err != SASL_OK) {
- VNC_DEBUG("cannot query SASL username on connection %d (%s)\n",
+ VNC_DEBUG("cannot query SASL username on connection %d (%s), denying access\n",
err, sasl_errstring(err, NULL, NULL));
return -1;
}
if (val == NULL) {
- VNC_DEBUG("no client username was found\n");
+ VNC_DEBUG("no client username was found, denying access\n");
return -1;
}
VNC_DEBUG("SASL client username %s\n", (const char *)val);
vs->sasl.username = qemu_strdup((const char*)val);
- return 0;
+ if (vs->vd->sasl.acl == NULL) {
+ VNC_DEBUG("no ACL activated, allowing access\n");
+ return 0;
+ }
+
+ allow = qemu_acl_party_is_allowed(vs->vd->sasl.acl, vs->sasl.username);
+
+ VNC_DEBUG("SASL client %s %s by ACL\n", vs->sasl.username,
+ allow ? "allowed" : "denied");
+ return allow ? 0 : -1;
}
static int vnc_auth_sasl_check_ssf(VncState *vs)
Index: qemu-kvm-0.10/qemu/vnc-auth-sasl.h
===================================================================
--- qemu-kvm-0.10.orig/qemu/vnc-auth-sasl.h
+++ qemu-kvm-0.10/qemu/vnc-auth-sasl.h
@@ -30,6 +30,9 @@
#include <sasl/sasl.h>
typedef struct VncStateSASL VncStateSASL;
+typedef struct VncDisplaySASL VncDisplaySASL;
+
+#include "acl.h"
struct VncStateSASL {
sasl_conn_t *conn;
@@ -56,6 +59,10 @@ struct VncStateSASL {
char *mechlist;
};
+struct VncDisplaySASL {
+ qemu_acl *acl;
+};
+
void vnc_sasl_client_cleanup(VncState *vs);
long vnc_client_read_sasl(VncState *vs);
Index: qemu-kvm-0.10/qemu/vnc-tls.c
===================================================================
--- qemu-kvm-0.10.orig/qemu/vnc-tls.c
+++ qemu-kvm-0.10/qemu/vnc-tls.c
@@ -255,6 +255,25 @@ int vnc_tls_validate_certificate(struct
gnutls_strerror (ret));
return -1;
}
+
+ if (vs->vd->tls.x509verify) {
+ int allow;
+ if (!vs->vd->tls.acl) {
+ VNC_DEBUG("no ACL activated, allowing access");
+ gnutls_x509_crt_deinit (cert);
+ continue;
+ }
+
+ allow = qemu_acl_party_is_allowed(vs->vd->tls.acl,
+ vs->tls.dname);
+
+ VNC_DEBUG("TLS x509 ACL check for %s is %s\n",
+ vs->tls.dname, allow ? "allowed" : "denied");
+ if (!allow) {
+ gnutls_x509_crt_deinit (cert);
+ return -1;
+ }
+ }
}
gnutls_x509_crt_deinit (cert);
Index: qemu-kvm-0.10/qemu/vnc-tls.h
===================================================================
--- qemu-kvm-0.10.orig/qemu/vnc-tls.h
+++ qemu-kvm-0.10/qemu/vnc-tls.h
@@ -31,6 +31,8 @@
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
+#include "acl.h"
+
enum {
VNC_WIREMODE_CLEAR,
VNC_WIREMODE_TLS,
@@ -42,6 +44,7 @@ typedef struct VncStateTLS VncStateTLS;
/* Server state */
struct VncDisplayTLS {
int x509verify; /* Non-zero if server requests & validates client cert */
+ qemu_acl *acl;
/* Paths to x509 certs/keys */
char *x509cacert;
Index: qemu-kvm-0.10/qemu/vnc.c
===================================================================
--- qemu-kvm-0.10.orig/qemu/vnc.c
+++ qemu-kvm-0.10/qemu/vnc.c
@@ -28,6 +28,7 @@
#include "sysemu.h"
#include "qemu_socket.h"
#include "qemu-timer.h"
+#include "acl.h"
#define VNC_REFRESH_INTERVAL (1000 / 30)
@@ -2082,6 +2083,7 @@ int vnc_display_open(DisplayState *ds, c
int sasl = 0;
int saslErr;
#endif
+ int acl = 0;
if (!vnc_display)
return -1;
@@ -2138,9 +2140,28 @@ int vnc_display_open(DisplayState *ds, c
return -1;
}
#endif
+ } else if (strncmp(options, "acl", 3) == 0) {
+ acl = 1;
}
}
+#ifdef CONFIG_VNC_TLS
+ if (acl && x509 && vs->tls.x509verify) {
+ if (!(vs->tls.acl = qemu_acl_init("vnc.x509dname"))) {
+ fprintf(stderr, "Failed to create x509 dname ACL\n");
+ exit(1);
+ }
+ }
+#endif
+#ifdef CONFIG_VNC_SASL
+ if (acl && sasl) {
+ if (!(vs->sasl.acl = qemu_acl_init("vnc.username"))) {
+ fprintf(stderr, "Failed to create username ACL\n");
+ exit(1);
+ }
+ }
+#endif
+
/*
* Combinations we support here:
*
Index: qemu-kvm-0.10/qemu/vnc.h
===================================================================
--- qemu-kvm-0.10.orig/qemu/vnc.h
+++ qemu-kvm-0.10/qemu/vnc.h
@@ -98,6 +98,9 @@ struct VncDisplay
int subauth; /* Used by VeNCrypt */
VncDisplayTLS tls;
#endif
+#ifdef CONFIG_VNC_SASL
+ VncDisplaySASL sasl;
+#endif
};
struct VncState