Index: qemu-kvm-0.10.4/Makefile =================================================================== --- qemu-kvm-0.10.orig/qemu/Makefile +++ qemu-kvm-0.10.4/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.4/acl.c =================================================================== --- /dev/null +++ qemu-kvm-0.10.4/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 +#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.4/acl.h =================================================================== --- /dev/null +++ qemu-kvm-0.10.4/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.4/configure =================================================================== --- qemu-kvm-0.10.orig/qemu/configure +++ qemu-kvm-0.10.4/configure @@ -913,6 +913,21 @@ EOF fi ########################################## +# fnmatch() probe, used for ACL routines +fnmatch="no" +cat > $TMPC << EOF +#include +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.4/monitor.c =================================================================== --- qemu-kvm-0.10.orig/qemu/monitor.c +++ qemu-kvm-0.10.4/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, " [] []\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.4/qemu-doc.texi =================================================================== --- qemu-kvm-0.10.orig/qemu/qemu-doc.texi +++ qemu-kvm-0.10.4/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 +list all the match rules in the access control list, and the default +policy +@item acl policy @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 [] +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 [] +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 +remove the specified match rule from the access control list. +@item acl reset +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.4/vnc-auth-sasl.c =================================================================== --- qemu-kvm-0.10.orig/qemu/vnc-auth-sasl.c +++ qemu-kvm-0.10.4/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.4/vnc-auth-sasl.h =================================================================== --- qemu-kvm-0.10.orig/qemu/vnc-auth-sasl.h +++ qemu-kvm-0.10.4/vnc-auth-sasl.h @@ -30,6 +30,9 @@ #include 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.4/vnc-tls.c =================================================================== --- qemu-kvm-0.10.orig/qemu/vnc-tls.c +++ qemu-kvm-0.10.4/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.4/vnc-tls.h =================================================================== --- qemu-kvm-0.10.orig/qemu/vnc-tls.h +++ qemu-kvm-0.10.4/vnc-tls.h @@ -31,6 +31,8 @@ #include #include +#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.4/vnc.c =================================================================== --- qemu-kvm-0.10.orig/qemu/vnc.c +++ qemu-kvm-0.10.4/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.4/vnc.h =================================================================== --- qemu-kvm-0.10.orig/qemu/vnc.h +++ qemu-kvm-0.10.4/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