History: we currently provide two TCP sockets, one clear text, no auth, the other TLS with x509 client certificate verification for auth. We also provide a UNIX domain socket relying on file perms to restrict access. I have previously provided a policykit patch for the latter allowing admin defined auth policy for UNIX socket access. The PolicyKit patch was flawed in that it did not provide a way for a client app to determine whether the UNIX socket needed PolicyKit auth ahead of time or not. This required apps to make assumptions prior to connecting which is not really viable. This mail is working towards a more flexible authentication solution, and goes straight for the big picture by integrating SASL authentication. This gives us integration with Kerberos (GSSAPI) and PAM and whatever the hell else SAS supports[1]. The critical decision in all this is the wire protocol & how to adapt it to slot in authentication in a way that is compatible with existing clients. As it stands the decision to use clear data vs TLS on the wire is based on the socket the client connects to - we do a TLS handshake at the very start. We need to be able to support SASL on all sockets, TCP, TLS & UNIX since they can all benefit from stuff like Kerberos. So I decided we need to integrate at the RPC layer for this to work. The general protocol picture ---------------------------- It helps to look at qemud/remote_protocol.x when reading this next ... Every API basically has an RPC call & reply pair. The reply messages have a status field, either 'REMOTE_OK' or 'REMOTE_ERROR'. In the former case the normal API return values follow on the wire. In the latter case a virErrroPtr object is serialized on the wire. For this patch I decided to add a 3rd code, REMOTE_AUTH. If an application tries to make an RPC call on a socket requiring authentication, and has not yet authenticated the server will return REMOTE_AUTH code. It then also returns an int (remote_auth_type) specifying the authentication method to use. I have defined two: enum remote_auth_type { REMOTE_AUTH_NONE = 0, REMOTE_AUTH_SASL = 1 }; With plans to add REMOTE_AUTH_POLKIT in a future patch. A legacy client getting back REMOTE_AUTH code will just quit the connection attempt since they don't support authentication. If the admin so desires they can still provide the TLS socket in a non-authenticated mode and only turn on SASL for the TCP socket. So the decision about whether to enable legacy clients is admin controlled. This is the best we can do. A new client getting back REMOTE_AUTH code will then read the remote_auth_type off the wire. If the requested type is one that the client supports then it can begin the authentication process, otherwise we virRaiseError and stop connecting. The SASL specific picture ------------------------- For the SASL auth the process involves a multi-phase handshake looking something like: client server 1. -> ask for mechanism list -> new ctx 2. new ctx <- list of mechanisms <- 3. start auth -> initial auth data -> start auth 4. step auth <- reply auth data <- 5. -> step auth data -> step auth goto 4. <- reply auth data <- Authentication can complete at step 4 in this process, or steps 4 & 5 can repeat an arbitrary number of times. So, to implement this if it neccessary to define 3 new RPC calls internal to the remote driver/daemon (aka not exposed to public API like the rest of the RPC calls). These are: REMOTE_PROC_AUTH_SASL_INIT = 66, REMOTE_PROC_AUTH_SASL_START = 67, REMOTE_PROC_AUTH_SASL_STEP = 68 These are basically just punting back & forth the data going in & out of the appropriate SASL apis. sasl_{server,client}_{new,start,step} See the man pages for more info. So on the server end, if a socket is configured to require SASL auth, the server will reject all RPC calls *except* for those three above with the REMOTE_AUTH code. Once SASL auth has completed, it will allow all the normal RPC calls. The effect is basically that the client is not able to call virConnectOpen & friends until auth has completed. On the client end, the fun is in the 'call' method of remote_internal.c. This has been split in two. The original method is now 'onecall', and there is a thin wrapper named 'call'. 'call' simply invokves onecall, and if it gets back a REMOTE_AUTH code, will do the SASL handshake & then re-run the original call. So again the effect is basically that the first virConnectOpen will cause the auth handshake to be performed. The SASL implementation details ------------------------------- This is the bare minimum SASL integration. I have not attempted to hook up any callbacks for gathering credentials. This basically means that the only SASL mechanism which works is Kerberos GSSAPI - its credentials are fetched out-of-band & so don't require callbacks. We do need to consider callbacks later so we can do username/password auth, and all the various other methods SASL has. As well as authentication, some SASL mechanisms provide a way to negotiate a data encryption layer for the subsequent session. GSSAPI is the only commonly used mechanism which supports this. I have not implemented this yet though. What we would do though is to enable this capability on the the plain TCP socket only. This would make the TCP socket truely secure, and avoid any extra overhead on the TLS socket or UNIX domain socket. I have set the wire packet size for the SASL negotiation to 8192 bytes at a time. This has been sufficient so far, but I need to validate this before we commit, because this will be wire ABI sensitive. Or I could change the XDR spec to be variable length. Anyway needs re-visiting This only deals with authentication. I have not attempted any authoriztion controls. So anyone who has a valid kerberos principle can connect to the server. We clearly need a local group list as we do for the x509 client certificates. Ultimately we could try LDAP lookups & other intersting suggestions. The SASL stuff is detected in configure & enabled/disabled as appropriate. I need to add extra config file params though to let the sysadmin control what socket uses what auth mechanism. The setup / usage details ------------------------- As I said, I only used GSSAPI so far. To use this all your clients need to be able to kinit & get a ticket. For testing I have setup my own personal Kerberos server using Fedora 7 & FreeIPA (http://freeipa.org/). Each libvirt server needs to be issued with a Kerberos service principle. I am using the word 'libvirt' as the service name. The service principle must match the FQDN of the server host. So on your Kerberos server you can issue a service principle with kadmin.local > addprinc libvirt/cherry.virt.boston.redhat.com@xxxxxxxxxxxxxxxxxxxxxx > ktadd -k /tmp/cherry-libvirt.tab libvirt/cherry.virt.boston.redhat.com@xxxxxxxxxxxxxxxxxxxxxx > quit Then copy the /tmp/cherry-libvirt.tab file to /etc/libvirt/krb5.tab on the server in question. (Change the hostnames & REALM as needed of course). If you want to change the GSSAPI mechanism, the settings are in the file /etc/sasl2/libvirt.conf. Though again only GSSAPI works so far. If you edit the /etc/libvirt/libvirtd.conf file you can enable listen_tcp=1 and run run libvirtd --listen. >From the client machine it should now be possible to do $ kinit berrange@xxxxxxxxxxxxxxxxxxxxxx $ virsh --connect xen+tcp://cherry.virt.boston.redhat.com/ You probably screwed something up though along the way because Kerberos is nasty like that. If you use --verbose with libvirtd it'll show details of any errors during authentication. On the client end you can add in a param on the connect URI of ?debug=stderr and it'll print some info about the auth process to stderr. Check that it shows 'GSSAPI' as a valid mechanism. If it doesn't, then your server is not configured as it should be - check keytab file - check you have cyrus-sasl-gssapi RPM. If that's ok, then check 'klist' on the client - if the first phase of chatter between the client & the KDC worked you should see the principle of the server cached on the client. If you don't, then check the KDC logs in /var/log/krb5kdc.log Kerberos/GSSAPI error reporting sucks really bad. That said, I'm sure I'm missing something, because the errors I get out of SASL are even worse than normal. BTW, you can see various 'XXX' in my patch. Basically all of them need to be addressed before I'd consider this patch suitable to merge. configure.in | 39 +++ include/libvirt/virterror.h | 1 libvirt.spec.in | 3 qemud/Makefile.am | 21 +- qemud/internal.h | 8 qemud/libvirtd.init.in | 3 qemud/libvirtd.sysconf | 3 qemud/qemud.c | 29 ++ qemud/remote.c | 370 ++++++++++++++++++++++++++++++++++-- qemud/remote_dispatch_localvars.h | 5 qemud/remote_dispatch_proc_switch.h | 24 ++ qemud/remote_dispatch_prototypes.h | 3 qemud/remote_protocol.c | 164 +++++++++++++++ qemud/remote_protocol.h | 59 +++++ qemud/remote_protocol.x | 53 ++++- src/Makefile.am | 3 src/remote_internal.c | 307 +++++++++++++++++++++++++++++ src/virsh.c | 4 src/virterror.c | 6 tests/Makefile.am | 2 20 files changed, 1073 insertions(+), 34 deletions(-) Regards, Dan. [1] Not entirely true. Some auth methods require credentials to be fetched from the user, so we can only support methods for which we have callbacks. This patch ignores the callback question, so only support GSSAPI where the credentials are fetched out-of-band. This can be addressed later. -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=|
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 <gnutls/gnutls.h>]) 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,</name>,</name>\n <uuid>$(UUID)</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 <gnutls/gnutls.h> #include <gnutls/x509.h> #include "../src/gnutls_1_0_compat.h" +#if HAVE_SASL +#include <sasl/sasl.h> +#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 <gnutls/gnutls.h> #include <gnutls/x509.h> #include "gnutls_1_0_compat.h" +#if HAVE_SASL +#include <sasl/sasl.h> +#endif #include <libxml/uri.h> #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)
-- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list