diff -r 2ebd10b676a9 configure.in --- a/configure.in Wed Oct 17 15:03:04 2007 -0400 +++ b/configure.in Wed Oct 17 15:03:07 2007 -0400 @@ -329,6 +329,40 @@ AC_CHECK_TYPE(gnutls_session, [#include ]) CFLAGS="$old_cflags" LDFLAGS="$old_ldflags" + + +dnl Cyrus SASL +AC_ARG_WITH(sasl, + [ --with-sasl use cyrus SASL for authentication], + [], + [with_sasl=yes]) + +SASL_CFLAGS= +SASL_LIBS= +if test "$with_sasl" != "no"; then + if test "$with_sasl" != "yes"; then + SASL_CFLAGS="-I$with_sasl" + SASL_LIBS="-L$with_sasl" + fi + old_cflags="$CFLAGS" + old_libs="$LIBS" + CFLAGS="$CFLAGS $SASL_CFLAGS" + LIBS="$LIBS $SASL_LIBS" + AC_CHECK_HEADER([sasl/sasl.h], + [], + AC_MSG_ERROR([You must install the Cyrus SASL development package in order to compile libvirt])) + AC_CHECK_LIB(sasl2, sasl_client_init, + [], + [AC_MSG_ERROR([You must install the Cyrus SASL library in order to compile and run libvirt])]) + CFLAGS="$old_cflags" + LIBS="$old_libs" + SASL_LIBS="$SASL_LIBS -lsasl2" + AC_DEFINE_UNQUOTED(HAVE_SASL, 1, [whether Cyrus SASL is available for authentication]) +fi +AM_CONDITIONAL(HAVE_SASL, [test "$with_sasl" != "no"]) +AC_SUBST(SASL_CFLAGS) +AC_SUBST(SASL_LIBS) + dnl Avahi library @@ -537,6 +571,11 @@ AC_MSG_NOTICE([]) AC_MSG_NOTICE([]) AC_MSG_NOTICE([ libxml: $LIBXML_CFLAGS $LIBXML_LIBS]) AC_MSG_NOTICE([ gnutls: $GNUTLS_CFLAGS $GNUTLS_LIBS]) +if test "$with_sasl" != "no" ; then +AC_MSG_NOTICE([ sasl: $SASL_CFLAGS $SASL_LIBS]) +else +AC_MSG_NOTICE([ sasl: no]) +fi if test "$with_avahi" = "yes" ; then AC_MSG_NOTICE([ avahi: $AVAHI_CFLAGS $AVAHI_LIBS]) else diff -r 2ebd10b676a9 include/libvirt/virterror.h --- a/include/libvirt/virterror.h Wed Oct 17 15:03:04 2007 -0400 +++ b/include/libvirt/virterror.h Wed Oct 17 15:03:07 2007 -0400 @@ -129,6 +129,7 @@ typedef enum { VIR_ERR_NO_DOMAIN, /* domain not found or unexpectedly disappeared */ VIR_ERR_NO_NETWORK, /* network not found */ VIR_ERR_INVALID_MAC, /* invalid MAC adress */ + VIR_ERR_AUTH_FAILED, /* authentication failed */ } virErrorNumber; /** diff -r 2ebd10b676a9 libvirt.spec.in --- a/libvirt.spec.in Wed Oct 17 15:03:04 2007 -0400 +++ b/libvirt.spec.in Wed Oct 17 15:15:04 2007 -0400 @@ -16,6 +16,7 @@ Requires: dnsmasq Requires: dnsmasq Requires: bridge-utils Requires: iptables +Requires: cyrus-sasl BuildRequires: xen-devel BuildRequires: libxml2-devel BuildRequires: readline-devel @@ -25,6 +26,7 @@ BuildRequires: avahi-devel BuildRequires: avahi-devel BuildRequires: dnsmasq BuildRequires: bridge-utils +BuildRequires: cyrus-sasl-devel Obsoletes: libvir ExclusiveArch: i386 x86_64 ia64 @@ -131,6 +133,7 @@ fi %config(noreplace) %{_sysconfdir}/sysconfig/libvirtd %config(noreplace) %{_sysconfdir}/libvirt/libvirtd.conf %config(noreplace) %{_sysconfdir}/libvirt/qemu.conf +%config(noreplace) %{_sysconfdir}/sasl2/libvirt.conf %dir %{_datadir}/libvirt/ %dir %{_datadir}/libvirt/networks/ %{_datadir}/libvirt/networks/default.xml diff -r 2ebd10b676a9 qemud/Makefile.am --- a/qemud/Makefile.am Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/Makefile.am Wed Oct 17 15:21:35 2007 -0400 @@ -17,6 +17,7 @@ EXTRA_DIST = libvirtd.init.in libvirtd.s remote_dispatch_localvars.h \ remote_dispatch_proc_switch.h \ mdns.c mdns.h \ + libvirtd.sasl \ $(conf_DATA) libvirtd_SOURCES = \ @@ -28,14 +29,14 @@ libvirtd_SOURCES = \ #-D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED=1 -D_POSIX_C_SOURCE=199506L libvirtd_CFLAGS = \ -I$(top_srcdir)/include -I$(top_builddir)/include \ - $(LIBXML_CFLAGS) $(GNUTLS_CFLAGS) \ + $(LIBXML_CFLAGS) $(GNUTLS_CFLAGS) $(SASL_CFLAGS) \ $(WARN_CFLAGS) -DLOCAL_STATE_DIR="\"$(localstatedir)\"" \ -DSYSCONF_DIR="\"$(sysconfdir)\"" \ -DQEMUD_PID_FILE="\"$(QEMUD_PID_FILE)\"" \ -DREMOTE_PID_FILE="\"$(REMOTE_PID_FILE)\"" \ -DGETTEXT_PACKAGE=\"$(PACKAGE)\" -libvirtd_LDFLAGS = $(WARN_CFLAGS) $(LIBXML_LIBS) $(GNUTLS_LIBS) +libvirtd_LDFLAGS = $(WARN_CFLAGS) $(LIBXML_LIBS) $(GNUTLS_LIBS) $(SASL_LIBS) libvirtd_DEPENDENCIES = ../src/libvirt.la libvirtd_LDADD = ../src/libvirt.la @@ -45,7 +46,7 @@ libvirtd_LDADD += $(AVAHI_LIBS) libvirtd_LDADD += $(AVAHI_LIBS) endif -install-data-local: install-init +install-data-local:: install-init install-data-sasl mkdir -p $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/autostart $(INSTALL_DATA) $(srcdir)/default-network.xml $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml sed -i -e "s,,\n $(UUID)," $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml @@ -55,7 +56,7 @@ install-data-local: install-init mkdir -p $(DESTDIR)$(localstatedir)/run/libvirt mkdir -p $(DESTDIR)$(localstatedir)/lib/libvirt -uninstall-local: uninstall-init +uninstall-local:: uninstall-init uninstall-data-sasl rm -f $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/autostart/default.xml rm -f $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/default.xml rmdir $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/autostart || : @@ -63,6 +64,18 @@ uninstall-local: uninstall-init rmdir $(DESTDIR)$(localstatedir)/run/libvirt || : rmdir $(DESTDIR)$(localstatedir)/lib/libvirt || : +if HAVE_SASL +install-data-sasl:: install-init + mkdir -p $(DESTDIR)$(sysconfdir)/sasl2/ + $(INSTALL_DATA) $(srcdir)/libvirtd.sasl $(DESTDIR)$(sysconfdir)/sasl2/libvirt.conf + +uninstall-data-sasl:: install-init + rm -f $(DESTDIR)$(sysconfdir)/sasl2/libvirt.conf + rmdir $(DESTDIR)$(sysconfdir)/sasl2/ +else +install-data-sasl: +uninstall-data-sasl: +endif .x.c: rm -f $@ diff -r 2ebd10b676a9 qemud/internal.h --- a/qemud/internal.h Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/internal.h Wed Oct 17 15:03:07 2007 -0400 @@ -28,6 +28,9 @@ #include #include #include "../src/gnutls_1_0_compat.h" +#if HAVE_SASL +#include +#endif #include "remote_protocol.h" #include "../config.h" @@ -85,6 +88,10 @@ struct qemud_client { int tls; gnutls_session_t session; enum qemud_tls_direction direction; + int auth; +#if HAVE_SASL + sasl_conn_t *saslconn; +#endif unsigned int incomingSerial; unsigned int outgoingSerial; @@ -110,6 +117,7 @@ struct qemud_socket { int readonly; /* If set, TLS is required on this socket. */ int tls; + int auth; int port; struct qemud_socket *next; }; diff -r 2ebd10b676a9 qemud/libvirtd.init.in --- a/qemud/libvirtd.init.in Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/libvirtd.init.in Wed Oct 17 15:40:49 2007 -0400 @@ -37,6 +37,7 @@ PROCESS=libvirtd LIBVIRTD_CONFIG= LIBVIRTD_ARGS= +KRB5_KTNAME=/etc/libvirt/krb5.tab test -f @sysconfdir@/sysconfig/libvirtd && . @sysconfdir@/sysconfig/libvirtd @@ -50,7 +51,7 @@ RETVAL=0 start() { echo -n $"Starting $SERVICE daemon: " - daemon --check $SERVICE $PROCESS --daemon $LIBVIRTD_CONFIG_ARGS $LIBVIRTD_ARGS + KRB5_KTNAME=$KRB5_KTNAME daemon --check $SERVICE $PROCESS --daemon $LIBVIRTD_CONFIG_ARGS $LIBVIRTD_ARGS RETVAL=$? echo [ $RETVAL -eq 0 ] && touch @localstatedir@/lock/subsys/$SERVICE diff -r 2ebd10b676a9 qemud/libvirtd.sysconf --- a/qemud/libvirtd.sysconf Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/libvirtd.sysconf Wed Oct 17 15:03:07 2007 -0400 @@ -4,3 +4,6 @@ # Listen for TCP/IP connections # NB. must setup TLS/SSL keys prior to using this #LIBVIRTD_ARGS="--listen" + +# Override Kerberos service keytab for SASL/GSSAPI +#KRB5_KTNAME=/etc/libvirt/krb5.tab diff -r 2ebd10b676a9 qemud/qemud.c --- a/qemud/qemud.c Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/qemud.c Wed Oct 17 15:03:07 2007 -0400 @@ -577,7 +577,8 @@ static int static int remoteListenTCP (struct qemud_server *server, const char *port, - int tls) + int tls, + int auth) { int fds[2]; int nfds = 0; @@ -606,6 +607,7 @@ remoteListenTCP (struct qemud_server *se sock->fd = fds[i]; sock->tls = tls; + sock->auth = auth; if (getsockname(sock->fd, (struct sockaddr *)(&sa), &salen) < 0) return -1; @@ -699,6 +701,7 @@ static struct qemud_server *qemudInitial struct qemud_socket *sock; char sockname[PATH_MAX]; char roSockname[PATH_MAX]; + int err; if (!(server = calloc(1, sizeof(struct qemud_server)))) { qemudLog(QEMUD_ERR, "Failed to allocate struct qemud_server"); @@ -713,6 +716,7 @@ static struct qemud_server *qemudInitial if (qemudInitPaths(server, sockname, roSockname, PATH_MAX) < 0) goto cleanup; + /* XXX configurable auth */ if (qemudListenUnix(server, sockname, 0) < 0) goto cleanup; @@ -728,15 +732,30 @@ static struct qemud_server *qemudInitial virStateInitialize(); +#if HAVE_SASL + if ((err = sasl_server_init(NULL, "libvirt")) != SASL_OK) { + qemudLog(QEMUD_ERR, "Failed to initialize SASL authentication %s", + sasl_errstring(err, NULL, NULL)); + goto cleanup; + } +#endif + if (ipsock) { - if (listen_tcp && remoteListenTCP (server, tcp_port, 0) < 0) +#if HAVE_SASL + /* XXX configurable auth */ + if (listen_tcp && remoteListenTCP (server, tcp_port, 0, REMOTE_AUTH_SASL) < 0) goto cleanup; +#else + if (listen_tcp && remoteListenTCP (server, tcp_port, 0, REMOTE_AUTH_NONE) < 0) + goto cleanup; +#endif if (listen_tls) { if (remoteInitializeGnuTLS () < 0) goto cleanup; - if (remoteListenTCP (server, tls_port, 1) < 0) + /* XXX configurable auth */ + if (remoteListenTCP (server, tls_port, 1, REMOTE_AUTH_NONE) < 0) goto cleanup; } } @@ -1046,6 +1065,7 @@ static int qemudDispatchServer(struct qe client->fd = fd; client->readonly = sock->readonly; client->tls = sock->tls; + client->auth = sock->auth; memcpy (&client->addr, &addr, sizeof addr); client->addrlen = addrlen; @@ -1126,6 +1146,9 @@ static void qemudDispatchClientFailure(s if (client->conn) virConnectClose(client->conn); +#if HAVE_SASL + if (client->saslconn) sasl_dispose(&client->saslconn); +#endif if (client->tls && client->session) gnutls_deinit (client->session); close(client->fd); free(client); diff -r 2ebd10b676a9 qemud/remote.c --- a/qemud/remote.c Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/remote.c Wed Oct 17 15:03:07 2007 -0400 @@ -55,6 +55,11 @@ static void remoteDispatchError (struct static void remoteDispatchError (struct qemud_client *client, remote_message_header *req, const char *fmt, ...); +/* Send a reply back to client indicating they need + * to authentication before continuing + */ +static void remoteDispatchNeedAuth (struct qemud_client *client, + remote_message_header *req); static virDomainPtr get_nonnull_domain (virConnectPtr conn, remote_nonnull_domain domain); static virNetworkPtr get_nonnull_network (virConnectPtr conn, remote_nonnull_network network); static void make_nonnull_domain (remote_nonnull_domain *dom_dst, virDomainPtr dom_src); @@ -113,6 +118,31 @@ remoteDispatchClientRequest (struct qemu if (req.status != REMOTE_OK) { remoteDispatchError (client, &req, "status (%d) != REMOTE_OK", (int) req.status); + xdr_destroy (&xdr); + return; + } + + switch (client->auth) { +#if HAVE_SASL + case REMOTE_AUTH_SASL: + /* Only allow SASL call while in SASL auth method */ + if (req.proc != REMOTE_PROC_AUTH_SASL_INIT && + req.proc != REMOTE_PROC_AUTH_SASL_START && + req.proc != REMOTE_PROC_AUTH_SASL_STEP) { + remoteDispatchNeedAuth (client, &req); + xdr_destroy (&xdr); + return; + } + break; +#endif + + case REMOTE_AUTH_NONE: + /* No auth, allow all calls */ + break; + + default: + /* Unexpected auth - deny all calls */ + remoteDispatchError (client, &req, "unexpected authentication method"); xdr_destroy (&xdr); return; } @@ -274,23 +304,14 @@ remoteDispatchClientRequest (struct qemu * reply. */ static void -remoteDispatchError (struct qemud_client *client, - remote_message_header *req, - const char *fmt, ...) +remoteDispatchSendError (struct qemud_client *client, + remote_message_header *req, + int code, const char *msg) { remote_message_header rep; remote_error error; - va_list args; - char msgbuf[1024]; - char *msg = msgbuf; XDR xdr; int len; - - va_start (args, fmt); - vsnprintf (msgbuf, sizeof msgbuf, fmt, args); - va_end (args); - - qemudDebug ("%s", msgbuf); /* Future versions of the protocol may use different vers or prog. Try * our hardest to send back a message that such clients could see. @@ -312,12 +333,12 @@ remoteDispatchError (struct qemud_client } /* Construct the error. */ - error.code = VIR_ERR_RPC; + error.code = code; error.domain = VIR_FROM_REMOTE; - error.message = &msg; + error.message = (char**)&msg; error.level = VIR_ERR_ERROR; error.dom = NULL; - error.str1 = &msg; + error.str1 = (char**)&msg; error.str2 = NULL; error.str3 = NULL; error.int1 = 0; @@ -339,6 +360,106 @@ remoteDispatchError (struct qemud_client } if (!xdr_remote_error (&xdr, &error)) { + xdr_destroy (&xdr); + return; + } + + len = xdr_getpos (&xdr); + if (xdr_setpos (&xdr, 0) == 0) { + xdr_destroy (&xdr); + return; + } + + if (!xdr_int (&xdr, &len)) { + xdr_destroy (&xdr); + return; + } + + xdr_destroy (&xdr); + + /* Send it. */ + client->mode = QEMUD_MODE_TX_PACKET; + client->bufferLength = len; + client->bufferOffset = 0; + if (client->tls) client->direction = QEMUD_TLS_DIRECTION_WRITE; +} + +static void +remoteDispatchFailAuth (struct qemud_client *client, + remote_message_header *req) +{ + remoteDispatchSendError (client, req, VIR_ERR_AUTH_FAILED, ""); +} + +static void +remoteDispatchError (struct qemud_client *client, + remote_message_header *req, + const char *fmt, ...) +{ + va_list args; + char msgbuf[1024]; + char *msg = msgbuf; + + va_start (args, fmt); + vsnprintf (msgbuf, sizeof msgbuf, fmt, args); + va_end (args); + + qemudDebug ("%s", msgbuf); + + remoteDispatchSendError (client, req, VIR_ERR_RPC, msg); +} + + +/* Authentication is required before calling this func (ie. not + * an error from the function being called). We return auth type + * reply. + */ +static void +remoteDispatchNeedAuth (struct qemud_client *client, + remote_message_header *req) +{ + remote_message_header rep; + remote_auth_type type; + XDR xdr; + int len; + + /* Future versions of the protocol may use different vers or prog. Try + * our hardest to send back a message that such clients could see. + */ + if (req) { + rep.prog = req->prog; + rep.vers = req->vers; + rep.proc = req->proc; + rep.direction = REMOTE_REPLY; + rep.serial = req->serial; + rep.status = REMOTE_AUTH; + } else { + rep.prog = REMOTE_PROGRAM; + rep.vers = REMOTE_PROTOCOL_VERSION; + rep.proc = REMOTE_PROC_OPEN; + rep.direction = REMOTE_REPLY; + rep.serial = 1; + rep.status = REMOTE_AUTH; + } + + /* Construct the error. */ + type = client->auth; + + /* Serialise the return header and error. */ + xdrmem_create (&xdr, client->buffer, sizeof client->buffer, XDR_ENCODE); + + len = 0; /* We'll come back and write this later. */ + if (!xdr_int (&xdr, &len)) { + xdr_destroy (&xdr); + return; + } + + if (!xdr_remote_message_header (&xdr, &rep)) { + xdr_destroy (&xdr); + return; + } + + if (!xdr_remote_auth_type (&xdr, &type)) { xdr_destroy (&xdr); return; } @@ -1945,6 +2066,225 @@ remoteDispatchNumOfNetworks (struct qemu return 0; } + +/* + * Initializes the SASL session in prepare for authentication + * and gives the client a list of allowed mechansims to choose + * + * XXX request wire encryption for non-TLS links + * XXX callbacks for stuff like password verification ? + * XXX fill in real hostname (from config file?) + * XXX fill in user realm (from config file ?) + * XXX fill in local & remote IP + */ +static int +remoteDispatchAuthSaslInit (struct qemud_client *client, + remote_message_header *req, + void *args ATTRIBUTE_UNUSED, + remote_auth_sasl_init_ret *ret) +{ +#if HAVE_SASL + const char *mechlist = NULL; + int err; + + qemudDebug("Initialize SASL auth\n"); + if (client->auth != REMOTE_AUTH_SASL || + client->saslconn != NULL) { + qemudLog(QEMUD_ERR, "client tried illegal SASL init request"); + remoteDispatchFailAuth(client, req); + return -2; + } + + err = sasl_server_new("libvirt", + NULL, /* XXX Server FQDN */ + NULL, /* XXX User realm */ + NULL, /* XXX IP local */ + NULL, /* XXX IP remote */ + NULL, /* XXX Callbacks */ + SASL_SUCCESS_DATA, + &client->saslconn); + if (err != SASL_OK) { + qemudLog(QEMUD_ERR, "sasl context setup failed %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + remoteDispatchFailAuth(client, req); + client->saslconn = NULL; + return -2; + } + + err = sasl_listmech(client->saslconn, + NULL, /* XXX username */ + "", /* Prefix */ + ",", /* Separator */ + "", /* Suffix */ + &mechlist, + NULL, + NULL); + if (err != SASL_OK) { + qemudLog(QEMUD_ERR, "cannot list SASL mechanisms %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + remoteDispatchFailAuth(client, req); + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + return -2; + } + qemudDebug("Available mechanisms for client: '%s'", mechlist); + ret->mechlist = strdup(mechlist); + if (!ret->mechlist) { + remoteDispatchFailAuth(client, req); + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + return -2; + } + + return 0; +#else + qemudLog(QEMUD_ERR, "client tried unsupported SASL init request"); + remoteDispatchFailAuth(client, req); + return -2; +#endif +} + +/* + * This starts the SASL authentication negotiation. + */ +static int +remoteDispatchAuthSaslStart (struct qemud_client *client, + remote_message_header *req ATTRIBUTE_UNUSED, + remote_auth_sasl_start_args *args, + remote_auth_sasl_start_ret *ret) +{ +#if HAVE_SASL + const char *serverout; + unsigned int serveroutlen; + int err; + + qemudDebug("Start SASL auth"); + if (client->auth != REMOTE_AUTH_SASL || + client->saslconn == NULL) { + qemudLog(QEMUD_ERR, "client tried illegal SASL start request"); + remoteDispatchFailAuth(client, req); + return -2; + } + + qemudDebug("Using SASL mechanism %s. Data %d bytes, nil: %d", + args->mech, args->datalen, args->nil); + err = sasl_server_start(client->saslconn, + args->mech, + /* NB, distinction of NULL vs "" is *critical* in SASL */ + args->nil ? NULL : args->data, + args->datalen, + &serverout, + &serveroutlen); + if (err != SASL_OK && + err != SASL_CONTINUE) { + qemudLog(QEMUD_ERR, "sasl start failed %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + remoteDispatchFailAuth(client, req); + return -2; + } + if (serveroutlen > REMOTE_AUTH_SASL_DATA_MAX) { + qemudLog(QEMUD_ERR, "sasl start reply data too long %d", serveroutlen); + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + remoteDispatchFailAuth(client, req); + return -2; + } + /* NB, distinction of NULL vs "" is *critical* in SASL */ + if (serverout) + memcpy(ret->data, serverout, serveroutlen); + else + ret->nil = 1; + ret->datalen = serveroutlen; + + qemudDebug("SASL return data %d bytes, nil; %d", ret->datalen, ret->nil); + if (err == SASL_CONTINUE) { + ret->complete = 0; + } else { + qemudDebug("Authentication successful"); + ret->complete = 1; + client->auth = REMOTE_AUTH_NONE; + } + + return 0; +#else + qemudLog(QEMUD_ERR, "client tried unsupported SASL start request"); + remoteDispatchFailAuth(client, req); + return -2; +#endif +} + + +static int +remoteDispatchAuthSaslStep (struct qemud_client *client, + remote_message_header *req, + remote_auth_sasl_step_args *args, + remote_auth_sasl_step_ret *ret) +{ +#if HAVE_SASL + const char *serverout; + unsigned int serveroutlen; + int err; + + qemudDebug("Step SASL auth"); + if (client->auth != REMOTE_AUTH_SASL || + client->saslconn == NULL) { + qemudLog(QEMUD_ERR, "client tried illegal SASL start request"); + remoteDispatchFailAuth(client, req); + return -2; + } + + qemudDebug("Using SASL Data %d bytes, nil: %d", + args->datalen, args->nil); + err = sasl_server_step(client->saslconn, + /* NB, distinction of NULL vs "" is *critical* in SASL */ + args->nil ? NULL : args->data, + args->datalen, + &serverout, + &serveroutlen); + if (err != SASL_OK && + err != SASL_CONTINUE) { + qemudLog(QEMUD_ERR, "sasl step failed %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + remoteDispatchFailAuth(client, req); + return -2; + } + + if (serveroutlen > REMOTE_AUTH_SASL_DATA_MAX) { + qemudLog(QEMUD_ERR, "sasl step reply data too long %d", serveroutlen); + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + remoteDispatchFailAuth(client, req); + return -2; + } + + /* NB, distinction of NULL vs "" is *critical* in SASL */ + if (serverout) + memcpy(ret->data, serverout, serveroutlen); + else + ret->nil = 1; + ret->datalen = serveroutlen; + + qemudDebug("SASL return data %d bytes, nil; %d", ret->datalen, ret->nil); + if (err == SASL_CONTINUE) { + ret->complete = 0; + } else { + qemudDebug("Authentication successful"); + ret->complete = 1; + client->auth = REMOTE_AUTH_NONE; + } + + return 0; +#else + qemudLog(QEMUD_ERR, "client tried unsupported SASL step request"); + remoteDispatchFailAuth(client, req); + return -1; +#endif +} + /*----- Helpers. -----*/ /* get_nonnull_domain and get_nonnull_network turn an on-wire diff -r 2ebd10b676a9 qemud/remote_dispatch_localvars.h --- a/qemud/remote_dispatch_localvars.h Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/remote_dispatch_localvars.h Wed Oct 17 15:07:55 2007 -0400 @@ -9,6 +9,7 @@ remote_list_defined_domains_ret lv_remot remote_list_defined_domains_ret lv_remote_list_defined_domains_ret; remote_get_capabilities_ret lv_remote_get_capabilities_ret; remote_domain_set_max_memory_args lv_remote_domain_set_max_memory_args; +remote_auth_sasl_init_ret lv_remote_auth_sasl_init_ret; remote_domain_get_os_type_args lv_remote_domain_get_os_type_args; remote_domain_get_os_type_ret lv_remote_domain_get_os_type_ret; remote_domain_get_autostart_args lv_remote_domain_get_autostart_args; @@ -36,6 +37,8 @@ remote_domain_create_linux_args lv_remot remote_domain_create_linux_args lv_remote_domain_create_linux_args; remote_domain_create_linux_ret lv_remote_domain_create_linux_ret; remote_domain_set_scheduler_parameters_args lv_remote_domain_set_scheduler_parameters_args; +remote_auth_sasl_start_args lv_remote_auth_sasl_start_args; +remote_auth_sasl_start_ret lv_remote_auth_sasl_start_ret; remote_domain_interface_stats_args lv_remote_domain_interface_stats_args; remote_domain_interface_stats_ret lv_remote_domain_interface_stats_ret; remote_domain_get_max_vcpus_args lv_remote_domain_get_max_vcpus_args; @@ -50,6 +53,8 @@ remote_network_get_bridge_name_args lv_r remote_network_get_bridge_name_args lv_remote_network_get_bridge_name_args; remote_network_get_bridge_name_ret lv_remote_network_get_bridge_name_ret; remote_domain_destroy_args lv_remote_domain_destroy_args; +remote_auth_sasl_step_args lv_remote_auth_sasl_step_args; +remote_auth_sasl_step_ret lv_remote_auth_sasl_step_ret; remote_domain_migrate_finish_args lv_remote_domain_migrate_finish_args; remote_domain_migrate_finish_ret lv_remote_domain_migrate_finish_ret; remote_domain_get_vcpus_args lv_remote_domain_get_vcpus_args; diff -r 2ebd10b676a9 qemud/remote_dispatch_proc_switch.h --- a/qemud/remote_dispatch_proc_switch.h Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/remote_dispatch_proc_switch.h Wed Oct 17 15:07:55 2007 -0400 @@ -2,6 +2,30 @@ * Do not edit this file. Any changes you make will be lost. */ +case REMOTE_PROC_AUTH_SASL_INIT: + fn = (dispatch_fn) remoteDispatchAuthSaslInit; + ret_filter = (xdrproc_t) xdr_remote_auth_sasl_init_ret; + ret = (char *) &lv_remote_auth_sasl_init_ret; + memset (&lv_remote_auth_sasl_init_ret, 0, sizeof lv_remote_auth_sasl_init_ret); + break; +case REMOTE_PROC_AUTH_SASL_START: + fn = (dispatch_fn) remoteDispatchAuthSaslStart; + args_filter = (xdrproc_t) xdr_remote_auth_sasl_start_args; + args = (char *) &lv_remote_auth_sasl_start_args; + memset (&lv_remote_auth_sasl_start_args, 0, sizeof lv_remote_auth_sasl_start_args); + ret_filter = (xdrproc_t) xdr_remote_auth_sasl_start_ret; + ret = (char *) &lv_remote_auth_sasl_start_ret; + memset (&lv_remote_auth_sasl_start_ret, 0, sizeof lv_remote_auth_sasl_start_ret); + break; +case REMOTE_PROC_AUTH_SASL_STEP: + fn = (dispatch_fn) remoteDispatchAuthSaslStep; + args_filter = (xdrproc_t) xdr_remote_auth_sasl_step_args; + args = (char *) &lv_remote_auth_sasl_step_args; + memset (&lv_remote_auth_sasl_step_args, 0, sizeof lv_remote_auth_sasl_step_args); + ret_filter = (xdrproc_t) xdr_remote_auth_sasl_step_ret; + ret = (char *) &lv_remote_auth_sasl_step_ret; + memset (&lv_remote_auth_sasl_step_ret, 0, sizeof lv_remote_auth_sasl_step_ret); + break; case REMOTE_PROC_CLOSE: fn = (dispatch_fn) remoteDispatchClose; break; diff -r 2ebd10b676a9 qemud/remote_dispatch_prototypes.h --- a/qemud/remote_dispatch_prototypes.h Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/remote_dispatch_prototypes.h Wed Oct 17 15:07:55 2007 -0400 @@ -2,6 +2,9 @@ * Do not edit this file. Any changes you make will be lost. */ +static int remoteDispatchAuthSaslInit (struct qemud_client *client, remote_message_header *req, void *args, remote_auth_sasl_init_ret *ret); +static int remoteDispatchAuthSaslStart (struct qemud_client *client, remote_message_header *req, remote_auth_sasl_start_args *args, remote_auth_sasl_start_ret *ret); +static int remoteDispatchAuthSaslStep (struct qemud_client *client, remote_message_header *req, remote_auth_sasl_step_args *args, remote_auth_sasl_step_ret *ret); static int remoteDispatchClose (struct qemud_client *client, remote_message_header *req, void *args, void *ret); static int remoteDispatchDomainAttachDevice (struct qemud_client *client, remote_message_header *req, remote_domain_attach_device_args *args, void *ret); static int remoteDispatchDomainBlockStats (struct qemud_client *client, remote_message_header *req, remote_domain_block_stats_args *args, remote_domain_block_stats_ret *ret); diff -r 2ebd10b676a9 qemud/remote_protocol.c --- a/qemud/remote_protocol.c Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/remote_protocol.c Wed Oct 17 15:07:54 2007 -0400 @@ -100,6 +100,15 @@ xdr_remote_error (XDR *xdrs, remote_erro if (!xdr_int (xdrs, &objp->int2)) return FALSE; if (!xdr_remote_network (xdrs, &objp->net)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_auth_type (XDR *xdrs, remote_auth_type *objp) +{ + + if (!xdr_enum (xdrs, (enum_t *) objp)) return FALSE; return TRUE; } @@ -1222,6 +1231,161 @@ xdr_remote_network_set_autostart_args (X } bool_t +xdr_remote_auth_sasl_init_ret (XDR *xdrs, remote_auth_sasl_init_ret *objp) +{ + + if (!xdr_remote_nonnull_string (xdrs, &objp->mechlist)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_auth_sasl_start_args (XDR *xdrs, remote_auth_sasl_start_args *objp) +{ + + if (!xdr_remote_nonnull_string (xdrs, &objp->mech)) + return FALSE; + if (!xdr_int (xdrs, &objp->nil)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->datalen)) + return FALSE; + if (!xdr_vector (xdrs, (char *)objp->data, REMOTE_AUTH_SASL_DATA_MAX, + sizeof (char), (xdrproc_t) xdr_char)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_auth_sasl_start_ret (XDR *xdrs, remote_auth_sasl_start_ret *objp) +{ + register int32_t *buf; + + + if (xdrs->x_op == XDR_ENCODE) { + buf = XDR_INLINE (xdrs, 3 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_int (xdrs, &objp->complete)) + return FALSE; + if (!xdr_int (xdrs, &objp->nil)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->datalen)) + return FALSE; + + } else { + (void)IXDR_PUT_INT32(buf, objp->complete); + (void)IXDR_PUT_INT32(buf, objp->nil); + (void)IXDR_PUT_U_INT32(buf, objp->datalen); + } + if (!xdr_vector (xdrs, (char *)objp->data, REMOTE_AUTH_SASL_DATA_MAX, + sizeof (char), (xdrproc_t) xdr_char)) + return FALSE; + return TRUE; + } else if (xdrs->x_op == XDR_DECODE) { + buf = XDR_INLINE (xdrs, 3 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_int (xdrs, &objp->complete)) + return FALSE; + if (!xdr_int (xdrs, &objp->nil)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->datalen)) + return FALSE; + + } else { + objp->complete = IXDR_GET_LONG(buf); + objp->nil = IXDR_GET_LONG(buf); + objp->datalen = IXDR_GET_U_LONG(buf); + } + if (!xdr_vector (xdrs, (char *)objp->data, REMOTE_AUTH_SASL_DATA_MAX, + sizeof (char), (xdrproc_t) xdr_char)) + return FALSE; + return TRUE; + } + + if (!xdr_int (xdrs, &objp->complete)) + return FALSE; + if (!xdr_int (xdrs, &objp->nil)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->datalen)) + return FALSE; + if (!xdr_vector (xdrs, (char *)objp->data, REMOTE_AUTH_SASL_DATA_MAX, + sizeof (char), (xdrproc_t) xdr_char)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_auth_sasl_step_args (XDR *xdrs, remote_auth_sasl_step_args *objp) +{ + + if (!xdr_int (xdrs, &objp->nil)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->datalen)) + return FALSE; + if (!xdr_vector (xdrs, (char *)objp->data, REMOTE_AUTH_SASL_DATA_MAX, + sizeof (char), (xdrproc_t) xdr_char)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_auth_sasl_step_ret (XDR *xdrs, remote_auth_sasl_step_ret *objp) +{ + register int32_t *buf; + + + if (xdrs->x_op == XDR_ENCODE) { + buf = XDR_INLINE (xdrs, 3 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_int (xdrs, &objp->complete)) + return FALSE; + if (!xdr_int (xdrs, &objp->nil)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->datalen)) + return FALSE; + + } else { + (void)IXDR_PUT_INT32(buf, objp->complete); + (void)IXDR_PUT_INT32(buf, objp->nil); + (void)IXDR_PUT_U_INT32(buf, objp->datalen); + } + if (!xdr_vector (xdrs, (char *)objp->data, REMOTE_AUTH_SASL_DATA_MAX, + sizeof (char), (xdrproc_t) xdr_char)) + return FALSE; + return TRUE; + } else if (xdrs->x_op == XDR_DECODE) { + buf = XDR_INLINE (xdrs, 3 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_int (xdrs, &objp->complete)) + return FALSE; + if (!xdr_int (xdrs, &objp->nil)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->datalen)) + return FALSE; + + } else { + objp->complete = IXDR_GET_LONG(buf); + objp->nil = IXDR_GET_LONG(buf); + objp->datalen = IXDR_GET_U_LONG(buf); + } + if (!xdr_vector (xdrs, (char *)objp->data, REMOTE_AUTH_SASL_DATA_MAX, + sizeof (char), (xdrproc_t) xdr_char)) + return FALSE; + return TRUE; + } + + if (!xdr_int (xdrs, &objp->complete)) + return FALSE; + if (!xdr_int (xdrs, &objp->nil)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->datalen)) + return FALSE; + if (!xdr_vector (xdrs, (char *)objp->data, REMOTE_AUTH_SASL_DATA_MAX, + sizeof (char), (xdrproc_t) xdr_char)) + return FALSE; + return TRUE; +} + +bool_t xdr_remote_procedure (XDR *xdrs, remote_procedure *objp) { diff -r 2ebd10b676a9 qemud/remote_protocol.h --- a/qemud/remote_protocol.h Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/remote_protocol.h Wed Oct 17 15:07:53 2007 -0400 @@ -28,6 +28,7 @@ typedef remote_nonnull_string *remote_st #define REMOTE_MIGRATE_COOKIE_MAX 256 #define REMOTE_NETWORK_NAME_LIST_MAX 256 #define REMOTE_DOMAIN_SCHEDULER_PARAMETERS_MAX 16 +#define REMOTE_AUTH_SASL_DATA_MAX 8192 typedef char remote_uuid[VIR_UUID_BUFLEN]; @@ -63,6 +64,12 @@ struct remote_error { }; typedef struct remote_error remote_error; +enum remote_auth_type { + REMOTE_AUTH_NONE = 0, + REMOTE_AUTH_SASL = 1, +}; +typedef enum remote_auth_type remote_auth_type; + struct remote_vcpu_info { u_int number; int state; @@ -659,6 +666,42 @@ struct remote_network_set_autostart_args int autostart; }; typedef struct remote_network_set_autostart_args remote_network_set_autostart_args; + +struct remote_auth_sasl_init_ret { + remote_nonnull_string mechlist; +}; +typedef struct remote_auth_sasl_init_ret remote_auth_sasl_init_ret; + +struct remote_auth_sasl_start_args { + remote_nonnull_string mech; + int nil; + u_int datalen; + char data[REMOTE_AUTH_SASL_DATA_MAX]; +}; +typedef struct remote_auth_sasl_start_args remote_auth_sasl_start_args; + +struct remote_auth_sasl_start_ret { + int complete; + int nil; + u_int datalen; + char data[REMOTE_AUTH_SASL_DATA_MAX]; +}; +typedef struct remote_auth_sasl_start_ret remote_auth_sasl_start_ret; + +struct remote_auth_sasl_step_args { + int nil; + u_int datalen; + char data[REMOTE_AUTH_SASL_DATA_MAX]; +}; +typedef struct remote_auth_sasl_step_args remote_auth_sasl_step_args; + +struct remote_auth_sasl_step_ret { + int complete; + int nil; + u_int datalen; + char data[REMOTE_AUTH_SASL_DATA_MAX]; +}; +typedef struct remote_auth_sasl_step_ret remote_auth_sasl_step_ret; #define REMOTE_PROGRAM 0x20008086 #define REMOTE_PROTOCOL_VERSION 1 @@ -728,6 +771,9 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_MIGRATE_FINISH = 63, REMOTE_PROC_DOMAIN_BLOCK_STATS = 64, REMOTE_PROC_DOMAIN_INTERFACE_STATS = 65, + REMOTE_PROC_AUTH_SASL_INIT = 66, + REMOTE_PROC_AUTH_SASL_START = 67, + REMOTE_PROC_AUTH_SASL_STEP = 68, }; typedef enum remote_procedure remote_procedure; @@ -741,6 +787,7 @@ enum remote_message_status { enum remote_message_status { REMOTE_OK = 0, REMOTE_ERROR = 1, + REMOTE_AUTH = 2, }; typedef enum remote_message_status remote_message_status; #define REMOTE_MESSAGE_HEADER_XDR_LEN 4 @@ -766,6 +813,7 @@ extern bool_t xdr_remote_domain (XDR *, extern bool_t xdr_remote_domain (XDR *, remote_domain*); extern bool_t xdr_remote_network (XDR *, remote_network*); extern bool_t xdr_remote_error (XDR *, remote_error*); +extern bool_t xdr_remote_auth_type (XDR *, remote_auth_type*); extern bool_t xdr_remote_vcpu_info (XDR *, remote_vcpu_info*); extern bool_t xdr_remote_sched_param_value (XDR *, remote_sched_param_value*); extern bool_t xdr_remote_sched_param (XDR *, remote_sched_param*); @@ -864,6 +912,11 @@ extern bool_t xdr_remote_network_get_au extern bool_t xdr_remote_network_get_autostart_args (XDR *, remote_network_get_autostart_args*); extern bool_t xdr_remote_network_get_autostart_ret (XDR *, remote_network_get_autostart_ret*); extern bool_t xdr_remote_network_set_autostart_args (XDR *, remote_network_set_autostart_args*); +extern bool_t xdr_remote_auth_sasl_init_ret (XDR *, remote_auth_sasl_init_ret*); +extern bool_t xdr_remote_auth_sasl_start_args (XDR *, remote_auth_sasl_start_args*); +extern bool_t xdr_remote_auth_sasl_start_ret (XDR *, remote_auth_sasl_start_ret*); +extern bool_t xdr_remote_auth_sasl_step_args (XDR *, remote_auth_sasl_step_args*); +extern bool_t xdr_remote_auth_sasl_step_ret (XDR *, remote_auth_sasl_step_ret*); extern bool_t xdr_remote_procedure (XDR *, remote_procedure*); extern bool_t xdr_remote_message_direction (XDR *, remote_message_direction*); extern bool_t xdr_remote_message_status (XDR *, remote_message_status*); @@ -878,6 +931,7 @@ extern bool_t xdr_remote_domain (); extern bool_t xdr_remote_domain (); extern bool_t xdr_remote_network (); extern bool_t xdr_remote_error (); +extern bool_t xdr_remote_auth_type (); extern bool_t xdr_remote_vcpu_info (); extern bool_t xdr_remote_sched_param_value (); extern bool_t xdr_remote_sched_param (); @@ -976,6 +1030,11 @@ extern bool_t xdr_remote_network_get_aut extern bool_t xdr_remote_network_get_autostart_args (); extern bool_t xdr_remote_network_get_autostart_ret (); extern bool_t xdr_remote_network_set_autostart_args (); +extern bool_t xdr_remote_auth_sasl_init_ret (); +extern bool_t xdr_remote_auth_sasl_start_args (); +extern bool_t xdr_remote_auth_sasl_start_ret (); +extern bool_t xdr_remote_auth_sasl_step_args (); +extern bool_t xdr_remote_auth_sasl_step_ret (); extern bool_t xdr_remote_procedure (); extern bool_t xdr_remote_message_direction (); extern bool_t xdr_remote_message_status (); diff -r 2ebd10b676a9 qemud/remote_protocol.x --- a/qemud/remote_protocol.x Wed Oct 17 15:03:04 2007 -0400 +++ b/qemud/remote_protocol.x Wed Oct 17 15:05:13 2007 -0400 @@ -80,6 +80,10 @@ const REMOTE_NETWORK_NAME_LIST_MAX = 256 /* Upper limit on list of scheduler parameters. */ const REMOTE_DOMAIN_SCHEDULER_PARAMETERS_MAX = 16; + +/* Upper limit on SASL auth negotiation packet */ +/* XXX is this large enough for all SASL mechs ? */ +const REMOTE_AUTH_SASL_DATA_MAX = 8192; /* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */ typedef opaque remote_uuid[VIR_UUID_BUFLEN]; @@ -121,6 +125,12 @@ struct remote_error { int int1; int int2; remote_network net; +}; + +/* Sent back with REMOTE_NEED_AUTH */ +enum remote_auth_type { + REMOTE_AUTH_NONE = 0, + REMOTE_AUTH_SASL = 1 }; /* Wire encoding of virVcpuInfo. */ @@ -610,6 +620,37 @@ struct remote_network_set_autostart_args struct remote_network_set_autostart_args { remote_nonnull_network net; int autostart; +}; + +struct remote_auth_sasl_init_ret { + remote_nonnull_string mechlist; +}; + +struct remote_auth_sasl_start_args { + remote_nonnull_string mech; + int nil; + unsigned datalen; + char data[REMOTE_AUTH_SASL_DATA_MAX]; +}; + +struct remote_auth_sasl_start_ret { + int complete; + int nil; + unsigned datalen; + char data[REMOTE_AUTH_SASL_DATA_MAX]; +}; + +struct remote_auth_sasl_step_args { + int nil; + unsigned datalen; + char data[REMOTE_AUTH_SASL_DATA_MAX]; +}; + +struct remote_auth_sasl_step_ret { + int complete; + int nil; + unsigned datalen; + char data[REMOTE_AUTH_SASL_DATA_MAX]; }; /*----- Protocol. -----*/ @@ -683,7 +724,10 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_MIGRATE_PERFORM = 62, REMOTE_PROC_DOMAIN_MIGRATE_FINISH = 63, REMOTE_PROC_DOMAIN_BLOCK_STATS = 64, - REMOTE_PROC_DOMAIN_INTERFACE_STATS = 65 + REMOTE_PROC_DOMAIN_INTERFACE_STATS = 65, + REMOTE_PROC_AUTH_SASL_INIT = 66, + REMOTE_PROC_AUTH_SASL_START = 67, + REMOTE_PROC_AUTH_SASL_STEP = 68 }; /* Custom RPC structure. */ @@ -714,7 +758,12 @@ enum remote_message_status { /* For replies, indicates that an error happened, and a struct * remote_error follows. */ - REMOTE_ERROR = 1 + REMOTE_ERROR = 1, + + /* Require authentication before this call is allowed + * remote_auth_type follows. + */ + REMOTE_AUTH = 2 }; /* 4 byte length word per header */ diff -r 2ebd10b676a9 src/Makefile.am --- a/src/Makefile.am Wed Oct 17 15:03:04 2007 -0400 +++ b/src/Makefile.am Wed Oct 17 15:03:07 2007 -0400 @@ -5,6 +5,7 @@ INCLUDES = -I$(top_builddir)/include \ -I@top_srcdir@/qemud \ $(LIBXML_CFLAGS) \ $(GNUTLS_CFLAGS) \ + $(SASL_CFLAGS) \ -DBINDIR=\""$(libexecdir)"\" \ -DSBINDIR=\""$(sbindir)"\" \ -DSYSCONF_DIR="\"$(sysconfdir)\"" \ @@ -24,7 +25,7 @@ EXTRA_DIST = libvirt_sym.version $(conf_ EXTRA_DIST = libvirt_sym.version $(conf_DATA) lib_LTLIBRARIES = libvirt.la -libvirt_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) +libvirt_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) $(SASL_LIBS) libvirt_la_LDFLAGS = -Wl,--version-script=$(srcdir)/libvirt_sym.version \ -version-info @LIBVIRT_VERSION_INFO@ \ $(COVERAGE_CFLAGS:-f%=-Wc,-f%) diff -r 2ebd10b676a9 src/remote_internal.c --- a/src/remote_internal.c Wed Oct 17 15:03:04 2007 -0400 +++ b/src/remote_internal.c Wed Oct 17 16:15:14 2007 -0400 @@ -46,6 +46,9 @@ #include #include #include "gnutls_1_0_compat.h" +#if HAVE_SASL +#include +#endif #include #include "internal.h" @@ -71,6 +74,8 @@ struct private_data { int counter; /* Generates serial numbers for RPC. */ char *uri; /* Original (remote) URI. */ int networkOnly; /* Only used for network API */ + char *hostname; /* Original hostname */ + FILE *debugLog; /* Debug remote protocol */ }; #define GET_PRIVATE(conn,retcode) \ @@ -89,7 +94,14 @@ struct private_data { return (retcode); \ } -static int call (virConnectPtr conn, struct private_data *priv, int in_open, int proc_nr, xdrproc_t args_filter, char *args, xdrproc_t ret_filter, char *ret); +static int call (virConnectPtr conn, struct private_data *priv, + int in_open, int proc_nr, + xdrproc_t args_filter, char *args, + xdrproc_t ret_filter, char *ret); +static int onecall (virConnectPtr conn, struct private_data *priv, + int in_open, int in_auth, int proc_nr, + xdrproc_t args_filter, char *args, + xdrproc_t ret_filter, char *ret); static void error (virConnectPtr conn, virErrorNumber code, const char *info); static void server_error (virConnectPtr conn, remote_error *err); static virDomainPtr get_nonnull_domain (virConnectPtr conn, remote_nonnull_domain domain); @@ -131,6 +143,20 @@ remoteStartup(void) inside_daemon = 1; return 0; } + +static void +remoteDebug(struct private_data *priv, const char *msg,...) +{ + va_list args; + if (priv->debugLog == NULL) + return; + + va_start(args, msg); + vfprintf(priv->debugLog, msg, args); + va_end(args); + fprintf(priv->debugLog, "\n"); +} + /** * remoteFindServerPath: @@ -372,6 +398,12 @@ doRemoteOpen (virConnectPtr conn, struct } else if (strcasecmp (var->name, "no_tty") == 0) { no_tty = atoi (var->value); var->ignore = 1; + } else if (strcasecmp (var->name, "debug") == 0) { + if (var->value && + strcasecmp(var->value, "stdout") == 0) + priv->debugLog = stdout; + else + priv->debugLog = stderr; } #if DEBUG else @@ -648,6 +680,13 @@ doRemoteOpen (virConnectPtr conn, struct } } /* switch (transport) */ + + priv->hostname = strdup (server); + if (!priv->hostname) { + error (NULL, VIR_ERR_NO_MEMORY, "allocating priv->hostname"); + goto failed; + } + /* Finally we can call the remote side's open function. */ remote_open_args args = { &name, flags }; @@ -659,6 +698,8 @@ doRemoteOpen (virConnectPtr conn, struct /* Duplicate and save the uri_str. */ priv->uri = strdup (uri_str); if (!priv->uri) { + free(priv->hostname); + priv->hostname = NULL; error (NULL, VIR_ERR_NO_MEMORY, "allocating priv->uri"); goto failed; } @@ -1225,6 +1266,9 @@ doRemoteClose (virConnectPtr conn, struc /* Free URI copy. */ if (priv->uri) free (priv->uri); + /* Free hostname copy */ + if (priv->hostname) free (priv->hostname); + /* Free private data. */ priv->magic = DEAD; @@ -2759,12 +2803,189 @@ remoteNetworkSetAutostart (virNetworkPtr /*----------------------------------------------------------------------*/ +#if HAVE_SASL +/* Perform the SASL authentication process + * + * XXX negotiate a session encryption layer for non-TLS sockets + * XXX fetch credentials from a libvirt client app callback + * XXX fill in local/remote IP details + * XXX use the real hostname from the connect URI + * XXX max packet size spec + * XXX better mechanism negotiation ? Ask client app ? + */ +static int +remoteAuthSasl (virConnectPtr conn, struct private_data *priv, + int in_open /* if we are in virConnectOpen */) { + sasl_conn_t *saslconn; + remote_auth_sasl_init_ret iret; + remote_auth_sasl_start_args sargs; + remote_auth_sasl_start_ret sret; + remote_auth_sasl_step_args pargs; + remote_auth_sasl_step_ret pret; + const char *clientout, *serverin; + unsigned int clientoutlen, serverinlen; + const char *mech; + int err, complete; + + remoteDebug(priv, "Client initialize SASL authentication"); + err = sasl_client_init(NULL); + if (err != SASL_OK) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "failed to initialize SASL library: %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + return -1; + } + + err = sasl_client_new("libvirt", + priv->hostname, + NULL, /* XXX local addr */ + NULL, /* XXX remote addr */ + NULL, /* XXX callbacks */ + SASL_SUCCESS_DATA, + &saslconn); + if (err != SASL_OK) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "Failed to create SASL client context: %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + return -1; + } + + /* First call is to inquire about supported mechanisms in the server */ + + memset (&iret, 0, sizeof iret); + if (onecall (conn, priv, in_open, 1, REMOTE_PROC_AUTH_SASL_INIT, + (xdrproc_t) xdr_void, (char *)NULL, + (xdrproc_t) xdr_remote_auth_sasl_init_ret, (char *) &iret) != 0) { + sasl_dispose(&saslconn); + return -1; /* virError already set by onecall */ + } + + + /* Start the auth negotiation on the client end first */ + remoteDebug(priv, "Client start negotiation mechlist '%s'", iret.mechlist); + err = sasl_client_start(saslconn, + iret.mechlist, + NULL, /* XXX interactions */ + &clientout, + &clientoutlen, + &mech); + if (err != SASL_OK && err != SASL_CONTINUE) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "Failed to start SASL negotiation: %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + free(iret.mechlist); + sasl_dispose(&saslconn); + return -1; + } + free(iret.mechlist); + + if (clientoutlen > REMOTE_AUTH_SASL_DATA_MAX) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "SASL negotiation data too long: %d bytes", clientoutlen); + sasl_dispose(&saslconn); + return -1; + } + /* NB, distinction of NULL vs "" is *critical* in SASL */ + if (clientout) + memcpy(sargs.data, clientout, clientoutlen); + else + sargs.nil = 1; + sargs.datalen = clientoutlen; + sargs.mech = (char*)mech; + remoteDebug(priv, "Server start negotiation with mech %s. Data %d bytes %p", mech, clientoutlen, clientout); + + /* Now send the initial auth data to the server */ + memset (&sret, 0, sizeof sret); + if (onecall (conn, priv, in_open, 1, REMOTE_PROC_AUTH_SASL_START, + (xdrproc_t) xdr_remote_auth_sasl_start_args, (char *) &sargs, + (xdrproc_t) xdr_remote_auth_sasl_start_ret, (char *) &sret) != 0) { + sasl_dispose(&saslconn); + return -1; /* virError already set by onecall */ + } + + complete = sret.complete; + /* NB, distinction of NULL vs "" is *critical* in SASL */ + serverin = sret.nil ? NULL : sret.data; + serverinlen = sret.datalen; + remoteDebug(priv, "Client step result complete: %d. Data %d bytes %p", + complete, serverinlen, serverin); + + /* Loop-the-loop... + * Even if the server has completed, the client must *always* do at least one step + * in this loop to verify the server isn't lieing about something. Mutual auth */ + for (;;) { + err = sasl_client_step(saslconn, + serverin, + serverinlen, + NULL, /* XXX interactions */ + &clientout, + &clientoutlen); + if (err != SASL_OK && err != SASL_CONTINUE) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "Failed step %d %s", + err, sasl_errstring(err, NULL, NULL)); + sasl_dispose(&saslconn); + return -1; + } + remoteDebug(priv, "Client step result %d. Data %d bytes %p", err, clientoutlen, clientout); + + /* Previous server call showed completion & we're now locally complete too */ + if (complete && err == SASL_OK) + break; + + /* Not done, prepare to talk with the server for another iteration */ + /* NB, distinction of NULL vs "" is *critical* in SASL */ + if (clientout) { + memcpy(pargs.data, clientout, clientoutlen); + pargs.nil = 0; + } else { + pargs.nil = 1; + } + pargs.datalen = clientoutlen; + remoteDebug(priv, "Server step with %d bytes %p", clientoutlen, clientout); + + memset (&pret, 0, sizeof pret); + if (onecall (conn, priv, in_open, 1, REMOTE_PROC_AUTH_SASL_STEP, + (xdrproc_t) xdr_remote_auth_sasl_step_args, (char *) &pargs, + (xdrproc_t) xdr_remote_auth_sasl_step_ret, (char *) &pret) != 0) { + sasl_dispose(&saslconn); + return -1; /* virError already set by onecall */ + } + + complete = pret.complete; + /* NB, distinction of NULL vs "" is *critical* in SASL */ + serverin = pret.nil ? NULL : pret.data; + serverinlen = pret.datalen; + + remoteDebug(priv, "Client step result complete: %d. Data %d bytes %p", + complete, serverinlen, serverin); + + /* This server call shows complete, and earlier client step was OK */ + if (complete && err == SASL_OK) + break; + } + + remoteDebug(priv, "SASL authentication complete"); + /* XXX keep this around for wire encoding */ + sasl_dispose(&saslconn); + return 0; +} +#endif + +/*----------------------------------------------------------------------*/ + static int really_write (virConnectPtr conn, struct private_data *priv, int in_open, char *bytes, int len); static int really_read (virConnectPtr conn, struct private_data *priv, int in_open, char *bytes, int len); -/* This function performs a remote procedure call to procedure PROC_NR. +/* This function performs a single remote procedure call to procedure PROC_NR. + * It will not retry a call if it fails due to lack of authentication * * NB. This does not free the args structure (not desirable, since you * often want this allocated on the stack or else it contains strings @@ -2773,13 +2994,19 @@ static int really_read (virConnectPtr co * * NB(2). Make sure to memset (&ret, 0, sizeof ret) before calling, * else Bad Things will happen in the XDR code. + * + * If in_auth is set: + * Return 0 on success, -1 on failure or auth request + * else + * Return 0 on success, -1 on failure, > 0 for auth type */ static int -call (virConnectPtr conn, struct private_data *priv, - int in_open /* if we are in virConnectOpen */, - int proc_nr, - xdrproc_t args_filter, char *args, - xdrproc_t ret_filter, char *ret) +onecall (virConnectPtr conn, struct private_data *priv, + int in_open /* if we are in virConnectOpen */, + int in_auth, /* if we are doing an auth call */ + int proc_nr, + xdrproc_t args_filter, char *args, + xdrproc_t ret_filter, char *ret) { char buffer[REMOTE_MESSAGE_MAX]; char buffer2[4]; @@ -2787,6 +3014,7 @@ call (virConnectPtr conn, struct private XDR xdr; int len; struct remote_error rerror; + enum remote_auth_type rauth; /* Get a unique serial number for this message. */ int serial = priv->counter++; @@ -2933,6 +3161,20 @@ call (virConnectPtr conn, struct private xdr_free ((xdrproc_t) xdr_remote_error, (char *) &rerror); return -1; + case REMOTE_AUTH: + if (in_auth) { + error (in_open ? NULL : conn, + VIR_ERR_RPC, "recursive authentication requests"); + return -1; + } + if (!xdr_remote_auth_type (&xdr, &rauth)) { + error (in_open ? NULL : conn, + VIR_ERR_RPC, "unmarshalling remote_auth"); + return -1; + } + xdr_destroy(&xdr); + return rauth; + default: __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, VIR_ERR_RPC, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, @@ -2942,6 +3184,57 @@ call (virConnectPtr conn, struct private return -1; } } + +/* This function performs a remote procedure call to procedure PROC_NR. + * If a call fails due to requiring authentication, it may do an auth + * handshake consisting of several RPCs, and then re-try the original call. + * + * NB. This does not free the args structure (not desirable, since you + * often want this allocated on the stack or else it contains strings + * which come from the user). It does however free any intermediate + * results, eg. the error structure if there is one. + * + * NB(2). Make sure to memset (&ret, 0, sizeof ret) before calling, + * else Bad Things will happen in the XDR code. + * + * Return 0 on success, -1 on failure + */ +static int +call (virConnectPtr conn, struct private_data *priv, + int in_open /* if we are in virConnectOpen */, + int proc_nr, + xdrproc_t args_filter, char *args, + xdrproc_t ret_filter, char *ret) +{ + int err; + +#if HAVE_SASL + redocall: +#endif + err = onecall(conn, priv, in_open, 0, proc_nr, + args_filter, args, + ret_filter, ret); + + if (err > 0) { + switch (err) { +#if HAVE_SASL + case REMOTE_AUTH_SASL: + if (remoteAuthSasl(conn, priv, in_open) < 0) + return -1; + goto redocall; +#endif + + default: + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "unsupported auth type %d", err); + return -1; + } + } else { + return err; + } +} + static int really_write (virConnectPtr conn, struct private_data *priv, diff -r 2ebd10b676a9 src/virsh.c --- a/src/virsh.c Wed Oct 17 15:03:04 2007 -0400 +++ b/src/virsh.c Wed Oct 17 15:03:07 2007 -0400 @@ -4512,8 +4512,10 @@ vshInit(vshControl * ctl) * vshConnectionUsability, except ones which don't need a connection * such as "help". */ - if (!ctl->conn) + if (!ctl->conn) { vshError(ctl, FALSE, _("failed to connect to the hypervisor")); + return FALSE; + } return TRUE; } diff -r 2ebd10b676a9 src/virterror.c --- a/src/virterror.c Wed Oct 17 15:03:04 2007 -0400 +++ b/src/virterror.c Wed Oct 17 15:03:07 2007 -0400 @@ -652,6 +652,12 @@ __virErrorMsg(virErrorNumber error, cons else errmsg = _("invalid MAC adress: %s"); break; + case VIR_ERR_AUTH_FAILED: + if (info == NULL) + errmsg = _("authentication failed"); + else + errmsg = _("authentication failed: %s"); + break; } return (errmsg); } diff -r 2ebd10b676a9 tests/Makefile.am --- a/tests/Makefile.am Wed Oct 17 15:03:04 2007 -0400 +++ b/tests/Makefile.am Wed Oct 17 15:03:07 2007 -0400 @@ -17,6 +17,7 @@ INCLUDES = \ -I$(top_srcdir)/src \ $(LIBXML_CFLAGS) \ $(GNUTLS_CFLAGS) \ + $(SASL_CFLAGS) \ -D_XOPEN_SOURCE=600 -D_POSIX_C_SOURCE=199506L \ -DGETTEXT_PACKAGE=\"$(PACKAGE)\" \ $(COVERAGE_CFLAGS) \ @@ -27,6 +28,7 @@ LDADDS = \ @STATIC_BINARIES@ \ $(LIBXML_LIBS) \ $(GNUTLS_LIBS) \ + $(SASL_LIBS) \ $(WARN_CFLAGS) \ $(LIBVIRT) \ $(COVERAGE_LDFLAGS)