This is the client half of the SASL authentication protocol I've been submitting to QEMU, most recently here: http://lists.gnu.org/archive/html/qemu-devel/2009-02/msg01418.html The original version of the GTK-VNC patch was here http://sourceforge.net/mailarchive/forum.php?thread_name=20090204173459.GK26946%40redhat.com&forum_name=gtk-vnc-devel There have just been some minor bug fixes since the last version I posted here. configure.ac | 47 ++ examples/gvncviewer.c | 3 src/Makefile.am | 4 src/gvnc.c | 935 ++++++++++++++++++++++++++++++++++++++++++-------- src/gvnc.h | 5 src/vncdisplay.c | 14 6 files changed, 864 insertions(+), 144 deletions(-) Daniel diff --git a/configure.ac b/configure.ac index b37a251..ee8a4dd 100644 --- a/configure.ac +++ b/configure.ac @@ -145,6 +145,53 @@ PKG_CHECK_MODULES(GNUTLS, gnutls >= $GNUTLS_REQUIRED) AC_SUBST(GNUTLS_CFLAGS) AC_SUBST(GNUTLS_LIBS) +dnl Cyrus SASL +AC_ARG_WITH([sasl], + [ --with-sasl use cyrus SASL for authentication], + [], + [with_sasl=check]) + +SASL_CFLAGS= +SASL_LIBS= +if test "x$with_sasl" != "xno"; then + if test "x$with_sasl" != "xyes" -a "x$with_sasl" != "xcheck"; then + SASL_CFLAGS="-I$with_sasl" + SASL_LIBS="-L$with_sasl" + fi + fail=0 + old_cflags="$CFLAGS" + old_libs="$LIBS" + CFLAGS="$CFLAGS $SASL_CFLAGS" + LIBS="$LIBS $SASL_LIBS" + AC_CHECK_HEADER([sasl/sasl.h],[],[ + if test "x$with_sasl" != "xcheck" ; then + with_sasl=no + else + fail=1 + fi]) + if test "x$with_sasl" != "xno" ; then + AC_CHECK_LIB([sasl2], [sasl_client_init],[with_sasl=yes],[ + if test "x$with_sasl" = "xcheck" ; then + with_sasl=no + else + fail=1 + fi]) + fi + test $fail = 1 && + AC_MSG_ERROR([You must install the Cyrus SASL development package in order to compile GTK-VNC]) + CFLAGS="$old_cflags" + LIBS="$old_libs" + SASL_LIBS="$SASL_LIBS -lsasl2" + if test "x$with_sasl" = "xyes" ; then + AC_DEFINE_UNQUOTED([HAVE_SASL], 1, + [whether Cyrus SASL is available for authentication]) + fi +fi +AM_CONDITIONAL([HAVE_SASL], [test "x$with_sasl" = "xyes"]) +AC_SUBST([SASL_CFLAGS]) +AC_SUBST([SASL_LIBS]) + + GTHREAD_CFLAGS= GTHREAD_LIBS= diff --git a/examples/gvncviewer.c b/examples/gvncviewer.c index 7ccdb50..00969cb 100644 --- a/examples/gvncviewer.c +++ b/examples/gvncviewer.c @@ -241,7 +241,10 @@ static void vnc_credential(GtkWidget *vncdisplay, GValueArray *credList) case VNC_DISPLAY_CREDENTIAL_PASSWORD: data[i] = gtk_entry_get_text(GTK_ENTRY(entry[row])); break; + default: + continue; } + row++; } } } diff --git a/src/Makefile.am b/src/Makefile.am index df93e21..e2fb931 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,10 +4,10 @@ EXTRA_DIST = libgtk-vnc_sym.version vncmarshal.txt lib_LTLIBRARIES = libgtk-vnc-1.0.la libgtk_vnc_1_0_la_LIBADD = @GTK_LIBS@ @GNUTLS_LIBS@ \ - @GTHREAD_LIBS@ \ + @GTHREAD_LIBS@ @SASL_LIBS@ \ ../gnulib/lib/libgnu.la libgtk_vnc_1_0_la_CFLAGS = @GTK_CFLAGS@ @GNUTLS_CFLAGS@ \ - @GTHREAD_CFLAGS@ @WARNING_CFLAGS@ \ + @GTHREAD_CFLAGS@ @SASL_CFLAGS@ @WARNING_CFLAGS@ \ -DSYSCONFDIR=\""$(sysconfdir)"\" \ -DG_LOG_DOMAIN=\"gtk-vnc\" \ -I$(top_srcdir)/gnulib/lib -I../gnulib/lib diff --git a/src/gvnc.c b/src/gvnc.c index b9ccbfe..2111a55 100644 --- a/src/gvnc.c +++ b/src/gvnc.c @@ -46,6 +46,10 @@ #include <gnutls/gnutls.h> #include <gnutls/x509.h> +#ifdef HAVE_SASL +#include <sasl/sasl.h> +#endif + #include <zlib.h> #include <gdk/gdkkeysyms.h> @@ -133,6 +137,16 @@ struct gvnc char *cred_x509_cacrl; char *cred_x509_cert; char *cred_x509_key; + gboolean want_cred_username; + gboolean want_cred_password; + gboolean want_cred_x509; + +#if HAVE_SASL + sasl_conn_t *saslconn; /* SASL context */ + const char *saslDecoded; + unsigned int saslDecodedLength; + unsigned int saslDecodedOffset; +#endif char read_buffer[4096]; size_t read_offset; @@ -355,6 +369,133 @@ static int gvnc_zread(struct gvnc *gvnc, void *buffer, size_t size) /* IO functions */ + +/* + * Read at least 1 more byte of data straight off the wire + * into the requested buffer. + */ +static int gvnc_read_wire(struct gvnc *gvnc, void *data, size_t len) +{ + int ret; + + reread: + if (gvnc->tls_session) { + ret = gnutls_read(gvnc->tls_session, data, len); + if (ret < 0) { + if (ret == GNUTLS_E_AGAIN) + errno = EAGAIN; + else + errno = EIO; + ret = -1; + } + } else + ret = recv (gvnc->fd, data, len, 0); + + if (ret == -1) { + switch (errno) { + case EWOULDBLOCK: + if (gvnc->wait_interruptable) { + if (!g_io_wait_interruptable(&gvnc->wait, + gvnc->channel, G_IO_IN)) { + GVNC_DEBUG("Read blocking interrupted %d", gvnc->has_error); + return -EAGAIN; + } + } else + g_io_wait(gvnc->channel, G_IO_IN); + case EINTR: + goto reread; + + default: + GVNC_DEBUG("Closing the connection: gvnc_read() - errno=%d\n", errno); + gvnc->has_error = TRUE; + return -errno; + } + } + if (ret == 0) { + GVNC_DEBUG("Closing the connection: gvnc_read() - ret=0\n"); + gvnc->has_error = TRUE; + return -EPIPE; + } + //GVNC_DEBUG("Read wire %p %d -> %d", data, len, ret); + + return ret; +} + + +/* + * Read at least 1 more byte of data out of the SASL decrypted + * data buffer, into the internal read buffer + */ +static int gvnc_read_sasl(struct gvnc *gvnc) +{ + size_t want; + //GVNC_DEBUG("Read SASL %p size %d offset %d", gvnc->saslDecoded, + // gvnc->saslDecodedLength, gvnc->saslDecodedOffset); + if (gvnc->saslDecoded == NULL) { + char encoded[8192]; + int encodedLen = sizeof(encoded); + int err, ret; + + ret = gvnc_read_wire(gvnc, encoded, encodedLen); + if (ret < 0) { + return ret; + } + + err = sasl_decode(gvnc->saslconn, encoded, ret, + &gvnc->saslDecoded, &gvnc->saslDecodedLength); + if (err != SASL_OK) { + GVNC_DEBUG("Failed to decode SASL data %s", + sasl_errstring(err, NULL, NULL)); + gvnc->has_error = TRUE; + return -EINVAL; + } + gvnc->saslDecodedOffset = 0; + } + + want = gvnc->saslDecodedLength - gvnc->saslDecodedOffset; + if (want > sizeof(gvnc->read_buffer)) + want = sizeof(gvnc->read_buffer); + + memcpy(gvnc->read_buffer, + gvnc->saslDecoded + gvnc->saslDecodedOffset, + want); + gvnc->saslDecodedOffset += want; + if (gvnc->saslDecodedOffset == gvnc->saslDecodedLength) { + gvnc->saslDecodedLength = gvnc->saslDecodedOffset = 0; + gvnc->saslDecoded = NULL; + } + //GVNC_DEBUG("Done read write %d - %d", want, gvnc->has_error); + return want; +} + + +/* + * Read at least 1 more byte of data straight off the wire + * into the internal read buffer + */ +static int gvnc_read_plain(struct gvnc *gvnc) +{ + //GVNC_DEBUG("Read plain %d", sizeof(gvnc->read_buffer)); + return gvnc_read_wire(gvnc, gvnc->read_buffer, sizeof(gvnc->read_buffer)); +} + +/* + * Read at least 1 more byte of data into the internal read_buffer + */ +static int gvnc_read_buf(struct gvnc *gvnc) +{ + //GVNC_DEBUG("Start read %d", gvnc->has_error); +#if HAVE_SASL + if (gvnc->saslconn) + return gvnc_read_sasl(gvnc); + else +#endif + return gvnc_read_plain(gvnc); +} + +/* + * Fill the 'data' buffer up with exactly 'len' bytes worth of data + */ static int gvnc_read(struct gvnc *gvnc, void *data, size_t len) { char *ptr = data; @@ -377,43 +518,10 @@ static int gvnc_read(struct gvnc *gvnc, void *data, size_t len) offset += ret; continue; } else if (gvnc->read_offset == gvnc->read_size) { - int ret; - - if (gvnc->tls_session) { - ret = gnutls_read(gvnc->tls_session, gvnc->read_buffer, 4096); - if (ret < 0) { - if (ret == GNUTLS_E_AGAIN) - errno = EAGAIN; - else - errno = EIO; - ret = -1; - } - } else - ret = recv (gvnc->fd, gvnc->read_buffer, 4096, 0); - - if (ret == -1) { - switch (errno) { - case EWOULDBLOCK: - if (gvnc->wait_interruptable) { - if (!g_io_wait_interruptable(&gvnc->wait, - gvnc->channel, G_IO_IN)) - return -EAGAIN; - } else - g_io_wait(gvnc->channel, G_IO_IN); - case EINTR: - continue; - default: - GVNC_DEBUG("Closing the connection: gvnc_read() - errno=%d\n", errno); - gvnc->has_error = TRUE; - return -errno; - } - } - if (ret == 0) { - GVNC_DEBUG("Closing the connection: gvnc_read() - ret=0\n"); - gvnc->has_error = TRUE; - return -EPIPE; - } + int ret = gvnc_read_buf(gvnc); + if (ret < 0) + return ret; gvnc->read_offset = 0; gvnc->read_size = ret; } @@ -429,16 +537,23 @@ static int gvnc_read(struct gvnc *gvnc, void *data, size_t len) return 0; } -static void gvnc_flush(struct gvnc *gvnc) +/* + * Write all 'data' of length 'datalen' bytes out to + * the wire + */ +static void gvnc_flush_wire(struct gvnc *gvnc, + const void *data, + size_t datalen) { size_t offset = 0; - while (offset < gvnc->write_offset) { + //GVNC_DEBUG("Flush write %p %d", data, datalen); + while (offset < datalen) { int ret; if (gvnc->tls_session) { ret = gnutls_write(gvnc->tls_session, - gvnc->write_buffer+offset, - gvnc->write_offset-offset); + data+offset, + datalen-offset); if (ret < 0) { if (ret == GNUTLS_E_AGAIN) errno = EAGAIN; @@ -448,8 +563,8 @@ static void gvnc_flush(struct gvnc *gvnc) } } else ret = send (gvnc->fd, - gvnc->write_buffer+offset, - gvnc->write_offset-offset, 0); + data+offset, + datalen-offset, 0); if (ret == -1) { switch (errno) { case EWOULDBLOCK: @@ -469,6 +584,57 @@ static void gvnc_flush(struct gvnc *gvnc) } offset += ret; } +} + + +/* + * Encode all buffered data, write all encrypted data out + * to the wire + */ +static void gvnc_flush_sasl(struct gvnc *gvnc) +{ + const char *output; + unsigned int outputlen; + int err; + + err = sasl_encode(gvnc->saslconn, + gvnc->write_buffer, + gvnc->write_offset, + &output, &outputlen); + if (err != SASL_OK) { + GVNC_DEBUG("Failed to encode SASL data %s", + sasl_errstring(err, NULL, NULL)); + gvnc->has_error = TRUE; + return; + } + //GVNC_DEBUG("Flush SASL %d: %p %d", gvnc->write_offset, output, outputlen); + gvnc_flush_wire(gvnc, output, outputlen); +} + +/* + * Write all buffered data straight out to the wire + */ +static void gvnc_flush_plain(struct gvnc *gvnc) +{ + //GVNC_DEBUG("Flush plain %d", gvnc->write_offset); + gvnc_flush_wire(gvnc, + gvnc->write_buffer, + gvnc->write_offset); +} + + +/* + * Write all buffered data out to the wire + */ +static void gvnc_flush(struct gvnc *gvnc) +{ + //GVNC_DEBUG("STart write %d", gvnc->has_error); +#if HAVE_SASL + if (gvnc->saslconn) + gvnc_flush_sasl(gvnc); + else +#endif + gvnc_flush_plain(gvnc); gvnc->write_offset = 0; } @@ -602,6 +768,10 @@ static void gvnc_write_s32(struct gvnc *gvnc, int32_t value) #define DH_BITS 1024 static gnutls_dh_params_t dh_params; +static void gvnc_debug_gnutls_log(int level, const char* str) { + GVNC_DEBUG("%d %s", level, str); +} + static gboolean gvnc_tls_initialize(void) { static int tlsinitialized = 0; @@ -617,6 +787,11 @@ static gboolean gvnc_tls_initialize(void) if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0) return FALSE; + if (debug_enabled) { + //gnutls_global_set_log_level(10); + //gnutls_global_set_log_function(gvnc_debug_gnutls_log); + } + tlsinitialized = TRUE; return TRUE; @@ -2120,6 +2295,64 @@ gboolean gvnc_server_message(struct gvnc *gvnc) return !gvnc_has_error(gvnc); } +gboolean gvnc_wants_credential_password(struct gvnc *gvnc) +{ + return gvnc->want_cred_password; +} + +gboolean gvnc_wants_credential_username(struct gvnc *gvnc) +{ + return gvnc->want_cred_username; +} + +gboolean gvnc_wants_credential_x509(struct gvnc *gvnc) +{ + return gvnc->want_cred_x509; +} + +static gboolean gvnc_has_credentials(gpointer data) +{ + struct gvnc *gvnc = (struct gvnc *)data; + + if (gvnc->has_error) + return TRUE; + if (gvnc_wants_credential_username(gvnc) && !gvnc->cred_username) + return FALSE; + if (gvnc_wants_credential_password(gvnc) && !gvnc->cred_password) + return FALSE; + /* + * For x509 we require a minimum of the CA cert. + * Anything else is a bonus - though the server + * may reject auth if it decides it wants a client + * cert. We can't express that based on auth type + * alone though - we'll merely find out when TLS + * negotiation takes place. + */ + if (gvnc_wants_credential_x509(gvnc) && !gvnc->cred_x509_cacert) + return FALSE; + return TRUE; +} + +static gboolean gvnc_gather_credentials(struct gvnc *gvnc) +{ + if (!gvnc_has_credentials(gvnc)) { + GVNC_DEBUG("Requesting missing credentials\n"); + if (gvnc->has_error || !gvnc->ops.auth_cred) { + gvnc->has_error = TRUE; + return FALSE; + } + if (!gvnc->ops.auth_cred(gvnc->ops_data)) + gvnc->has_error = TRUE; + if (gvnc->has_error) + return FALSE; + GVNC_DEBUG("Waiting for missing credentials\n"); + g_condition_wait(gvnc_has_credentials, gvnc); + GVNC_DEBUG("Got all credentials\n"); + } + return !gvnc_has_error(gvnc); +} + + static gboolean gvnc_check_auth_result(struct gvnc *gvnc) { uint32_t result; @@ -2142,7 +2375,7 @@ static gboolean gvnc_check_auth_result(struct gvnc *gvnc) if (!gvnc->has_error && gvnc->ops.auth_failure) gvnc->ops.auth_failure(gvnc->ops_data, reason); } else { - GVNC_DEBUG("Fail\n"); + GVNC_DEBUG("Fail auth no result\n"); if (!gvnc->has_error && gvnc->ops.auth_failure) gvnc->ops.auth_failure(gvnc->ops_data, NULL); } @@ -2155,6 +2388,12 @@ static gboolean gvnc_perform_auth_vnc(struct gvnc *gvnc) uint8_t key[8]; GVNC_DEBUG("Do Challenge\n"); + gvnc->want_cred_password = TRUE; + gvnc->want_cred_username = FALSE; + gvnc->want_cred_x509 = FALSE; + if (!gvnc_gather_credentials(gvnc)) + return FALSE; + if (!gvnc->cred_password) return FALSE; @@ -2173,6 +2412,472 @@ static gboolean gvnc_perform_auth_vnc(struct gvnc *gvnc) } +#if HAVE_SASL +/* + * NB, keep in sync with similar method in qemud/remote.c + */ +static char *gvnc_addr_to_string(struct sockaddr_storage *sa, socklen_t salen) +{ + char host[NI_MAXHOST], port[NI_MAXSERV]; + char *addr; + int err; + + if ((err = getnameinfo((struct sockaddr *)sa, salen, + host, sizeof(host), + port, sizeof(port), + NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { + GVNC_DEBUG("Cannot resolve address %d: %s", + err, gai_strerror(err)); + return NULL; + } + + addr = g_malloc0(strlen(host) + 1 + strlen(port) + 1); + strcpy(addr, host); + strcat(addr, ";"); + strcat(addr, port); + return addr; +} + + + +static gboolean +gvnc_gather_sasl_credentials(struct gvnc *gvnc, + sasl_interact_t *interact) +{ + int ninteract; + + gvnc->want_cred_password = FALSE; + gvnc->want_cred_username = FALSE; + gvnc->want_cred_x509 = FALSE; + + for (ninteract = 0 ; interact[ninteract].id != 0 ; ninteract++) { + switch (interact[ninteract].id) { + case SASL_CB_AUTHNAME: + case SASL_CB_USER: + gvnc->want_cred_username = TRUE; + break; + + case SASL_CB_PASS: + gvnc->want_cred_password = TRUE; + break; + + default: + GVNC_DEBUG("Unsupported credential %lu", + interact[ninteract].id); + /* Unsupported */ + return FALSE; + } + } + + if ((gvnc->want_cred_password || + gvnc->want_cred_username) && + !gvnc_gather_credentials(gvnc)) { + GVNC_DEBUG("%s", "damn "); + return FALSE; + } + + for (ninteract = 0 ; interact[ninteract].id != 0 ; ninteract++) { + switch (interact[ninteract].id) { + case SASL_CB_AUTHNAME: + case SASL_CB_USER: + interact[ninteract].result = gvnc->cred_username; + interact[ninteract].len = strlen(gvnc->cred_username); + GVNC_DEBUG("Gather Username %s", gvnc->cred_username); + break; + + case SASL_CB_PASS: + interact[ninteract].result = gvnc->cred_password; + interact[ninteract].len = strlen(gvnc->cred_password); + GVNC_DEBUG("Gather Password %s", gvnc->cred_password); + break; + } + } + + GVNC_DEBUG("%s", "Filled SASL interact"); + + return TRUE; +} + + + +/* + * + * Init msg from server + * + * u32 mechlist-length + * u8-array mechlist-string + * + * Start msg to server + * + * u32 mechname-length + * u8-array mechname-string + * u32 clientout-length + * u8-array clientout-string + * + * Start msg from server + * + * u32 serverin-length + * u8-array serverin-string + * u8 continue + * + * Step msg to server + * + * u32 clientout-length + * u8-array clientout-string + * + * Step msg from server + * + * u32 serverin-length + * u8-array serverin-string + * u8 continue + */ + +#define SASL_MAX_MECHLIST_LEN 300 +#define SASL_MAX_MECHNAME_LEN 100 +#define SASL_MAX_DATA_LEN (1024 * 1024) + +/* Perform the SASL authentication process + */ +static gboolean gvnc_perform_auth_sasl(struct gvnc *gvnc) +{ + sasl_conn_t *saslconn = NULL; + sasl_security_properties_t secprops; + const char *clientout; + char *serverin = NULL; + unsigned int clientoutlen, serverinlen; + int err, complete; + struct sockaddr_storage sa; + socklen_t salen; + char *localAddr = NULL, *remoteAddr = NULL; + const void *val; + sasl_ssf_t ssf; + sasl_callback_t saslcb[] = { + { .id = SASL_CB_AUTHNAME }, + // { .id = SASL_CB_USER }, + { .id = SASL_CB_PASS }, + { .id = 0 }, + }; + sasl_interact_t *interact = NULL; + guint32 mechlistlen; + char *mechlist; + const char *mechname; + gboolean ret; + + /* Sets up the SASL library as a whole */ + err = sasl_client_init(NULL); + GVNC_DEBUG("Client initialize SASL authentication %d", err); + if (err != SASL_OK) { + GVNC_DEBUG("failed to initialize SASL library: %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + goto error; + } + + /* Get local address in form IPADDR:PORT */ + salen = sizeof(sa); + if (getsockname(gvnc->fd, (struct sockaddr*)&sa, &salen) < 0) { + GVNC_DEBUG("failed to get sock address %d (%s)", + errno, strerror(errno)); + goto error; + } + if ((localAddr = gvnc_addr_to_string(&sa, salen)) == NULL) + goto error; + + /* Get remote address in form IPADDR:PORT */ + salen = sizeof(sa); + if (getpeername(gvnc->fd, (struct sockaddr*)&sa, &salen) < 0) { + GVNC_DEBUG("failed to get peer address %d (%s)", + errno, strerror(errno)); + g_free(localAddr); + goto error; + } + if ((remoteAddr = gvnc_addr_to_string(&sa, salen)) == NULL) { + g_free(localAddr); + goto error; + } + + GVNC_DEBUG("Client SASL new host:'%s' local:'%s' remote:'%s'", gvnc->host, localAddr, remoteAddr); + + /* Setup a handle for being a client */ + err = sasl_client_new("vnc", + gvnc->host, + localAddr, + remoteAddr, + saslcb, + SASL_SUCCESS_DATA, + &saslconn); + g_free(localAddr); + g_free(remoteAddr); + + if (err != SASL_OK) { + GVNC_DEBUG("Failed to create SASL client context: %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + goto error; + } + + /* Initialize some connection props we care about */ + if (gvnc->tls_session) { + gnutls_cipher_algorithm_t cipher; + + cipher = gnutls_cipher_get(gvnc->tls_session); + if (!(ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher))) { + GVNC_DEBUG("%s", "invalid cipher size for TLS session"); + goto error; + } + ssf *= 8; /* key size is bytes, sasl wants bits */ + + GVNC_DEBUG("Setting external SSF %d", ssf); + err = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &ssf); + if (err != SASL_OK) { + GVNC_DEBUG("cannot set external SSF %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + goto error; + } + } + + memset (&secprops, 0, sizeof secprops); + /* If we've got TLS, we don't care about SSF */ + secprops.min_ssf = gvnc->tls_session ? 0 : 56; /* Equiv to DES supported by all Kerberos */ + secprops.max_ssf = gvnc->tls_session ? 0 : 100000; /* Very strong ! AES == 256 */ + secprops.maxbufsize = 100000; + /* If we're not TLS, then forbid any anonymous or trivially crackable auth */ + secprops.security_flags = gvnc->tls_session ? 0 : + SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT; + + err = sasl_setprop(saslconn, SASL_SEC_PROPS, &secprops); + if (err != SASL_OK) { + GVNC_DEBUG("cannot set security props %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + goto error; + } + + /* Get the supported mechanisms from the server */ + mechlistlen = gvnc_read_u32(gvnc); + if (gvnc->has_error) + goto error; + if (mechlistlen > SASL_MAX_MECHLIST_LEN) { + GVNC_DEBUG("mechlistlen %d too long", mechlistlen); + goto error; + } + + mechlist = g_malloc(mechlistlen+1); + gvnc_read(gvnc, mechlist, mechlistlen); + mechlist[mechlistlen] = '\0'; + if (gvnc->has_error) { + g_free(mechlist); + mechlist = NULL; + goto error; + } + +#if 0 + if (wantmech) { + if (strstr(mechlist, wantmech) == NULL) { + GVNC_DEBUG("SASL mechanism %s not supported by server", + wantmech); + VIR_FREE(iret.mechlist); + goto error; + } + mechlist = wantmech; + } +#endif + + restart: + /* Start the auth negotiation on the client end first */ + GVNC_DEBUG("Client start negotiation mechlist '%s'", mechlist); + err = sasl_client_start(saslconn, + mechlist, + &interact, + &clientout, + &clientoutlen, + &mechname); + if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) { + GVNC_DEBUG("Failed to start SASL negotiation: %d (%s)", + err, sasl_errdetail(saslconn)); + g_free(mechlist); + mechlist = NULL; + goto error; + } + + /* Need to gather some credentials from the client */ + if (err == SASL_INTERACT) { + if (!gvnc_gather_sasl_credentials(gvnc, + interact)) { + GVNC_DEBUG("%s", "Failed to collect auth credentials"); + goto error; + } + goto restart; + } + + GVNC_DEBUG("Server start negotiation with mech %s. Data %d bytes %p '%s'", + mechname, clientoutlen, clientout, clientout); + + if (clientoutlen > SASL_MAX_DATA_LEN) { + GVNC_DEBUG("SASL negotiation data too long: %d bytes", + clientoutlen); + goto error; + } + + /* Send back the chosen mechname */ + gvnc_write_u32(gvnc, strlen(mechname)); + gvnc_write(gvnc, mechname, strlen(mechname)); + + /* NB, distinction of NULL vs "" is *critical* in SASL */ + if (clientout) { + gvnc_write_u32(gvnc, clientoutlen + 1); + gvnc_write(gvnc, clientout, clientoutlen + 1); + } else { + gvnc_write_u32(gvnc, 0); + } + gvnc_flush(gvnc); + if (gvnc->has_error) + goto error; + + + GVNC_DEBUG("%s", "Getting sever start negotiation reply"); + /* Read the 'START' message reply from server */ + serverinlen = gvnc_read_u32(gvnc); + if (gvnc->has_error) + goto error; + if (serverinlen > SASL_MAX_DATA_LEN) { + GVNC_DEBUG("SASL negotiation data too long: %d bytes", + clientoutlen); + goto error; + } + + /* NB, distinction of NULL vs "" is *critical* in SASL */ + if (serverinlen) { + serverin = g_malloc(serverinlen); + gvnc_read(gvnc, serverin, serverinlen); + serverin[serverinlen-1] = '\0'; + serverinlen--; + } else { + serverin = NULL; + } + complete = gvnc_read_u8(gvnc); + if (gvnc->has_error) + goto error; + + GVNC_DEBUG("Client start result complete: %d. Data %d bytes %p '%s'", + complete, serverinlen, serverin, 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 lying about something. Mutual auth */ + for (;;) { + restep: + err = sasl_client_step(saslconn, + serverin, + serverinlen, + &interact, + &clientout, + &clientoutlen); + if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) { + GVNC_DEBUG("Failed SASL step: %d (%s)", + err, sasl_errdetail(saslconn)); + goto error; + } + + /* Need to gather some credentials from the client */ + if (err == SASL_INTERACT) { + if (!gvnc_gather_sasl_credentials(gvnc, + interact)) { + GVNC_DEBUG("%s", "Failed to collect auth credentials"); + goto error; + } + goto restep; + } + + if (serverin) { + g_free(serverin); + serverin = NULL; + } + + GVNC_DEBUG("Client step result %d. Data %d bytes %p '%s'", err, clientoutlen, clientout, 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) { + gvnc_write_u32(gvnc, clientoutlen + 1); + gvnc_write(gvnc, clientout, clientoutlen + 1); + } else { + gvnc_write_u32(gvnc, 0); + } + gvnc_flush(gvnc); + if (gvnc->has_error) + goto error; + + GVNC_DEBUG("Server step with %d bytes %p", clientoutlen, clientout); + + serverinlen = gvnc_read_u32(gvnc); + if (gvnc->has_error) + goto error; + if (serverinlen > SASL_MAX_DATA_LEN) { + GVNC_DEBUG("SASL negotiation data too long: %d bytes", + clientoutlen); + goto error; + } + + /* NB, distinction of NULL vs "" is *critical* in SASL */ + if (serverinlen) { + serverin = g_malloc(serverinlen); + gvnc_read(gvnc, serverin, serverinlen); + serverin[serverinlen-1] = '\0'; + serverinlen--; + } else { + serverin = NULL; + } + complete = gvnc_read_u8(gvnc); + if (gvnc->has_error) + goto error; + + GVNC_DEBUG("Client step result complete: %d. Data %d bytes %p '%s'", + complete, serverinlen, serverin, serverin); + + /* This server call shows complete, and earlier client step was OK */ + if (complete && err == SASL_OK) { + g_free(serverin); + serverin = NULL; + break; + } + } + + /* Check for suitable SSF if non-TLS */ + if (!gvnc->tls_session) { + err = sasl_getprop(saslconn, SASL_SSF, &val); + if (err != SASL_OK) { + GVNC_DEBUG("cannot query SASL ssf on connection %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + goto error; + } + ssf = *(const int *)val; + GVNC_DEBUG("SASL SSF value %d", ssf); + if (ssf < 56) { /* 56 == DES level, good for Kerberos */ + GVNC_DEBUG("negotiation SSF %d was not strong enough", ssf); + goto error; + } + } + + GVNC_DEBUG("%s", "SASL authentication complete"); + ret = gvnc_check_auth_result(gvnc); + /* This must come *after* check-auth-result, because the former + * is defined to be sent unencrypted, and setting saslconn turns + * on the SSF layer encryption processing */ + gvnc->saslconn = saslconn; + return ret; + + error: + gvnc->has_error = TRUE; + if (saslconn) + sasl_dispose(&saslconn); + return FALSE; +} +#endif /* HAVE_SASL */ + + static gboolean gvnc_start_tls(struct gvnc *gvnc, int anonTLS) { static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; @@ -2230,6 +2935,12 @@ static gboolean gvnc_start_tls(struct gvnc *gvnc, int anonTLS) return FALSE; } } else { + gvnc->want_cred_password = FALSE; + gvnc->want_cred_username = FALSE; + gvnc->want_cred_x509 = TRUE; + if (!gvnc_gather_credentials(gvnc)) + return FALSE; + gnutls_certificate_credentials_t x509_cred = gvnc_tls_initialize_cert_cred(gvnc); if (!x509_cred) { gnutls_deinit(gvnc->tls_session); @@ -2279,92 +2990,6 @@ static gboolean gvnc_start_tls(struct gvnc *gvnc, int anonTLS) } } -gboolean gvnc_wants_credential_password(struct gvnc *gvnc) -{ - if (gvnc->auth_type == GVNC_AUTH_VNC) - return TRUE; - - if (gvnc->auth_type == GVNC_AUTH_TLS && - gvnc->auth_subtype == GVNC_AUTH_VNC) - return TRUE; - - if (gvnc->auth_type == GVNC_AUTH_VENCRYPT) { - if (gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_PLAIN || - gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_TLSVNC || - gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_TLSPLAIN || - gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509VNC || - gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509PLAIN) - return TRUE; - } - - return FALSE; -} - -gboolean gvnc_wants_credential_username(struct gvnc *gvnc) -{ - if (gvnc->auth_type == GVNC_AUTH_VENCRYPT) { - if (gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_PLAIN || - gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_TLSPLAIN || - gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509PLAIN) - return TRUE; - } - - return FALSE; -} - -gboolean gvnc_wants_credential_x509(struct gvnc *gvnc) -{ - if (gvnc->auth_type == GVNC_AUTH_VENCRYPT) { - if (gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509NONE || - gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509PLAIN || - gvnc->auth_subtype == GVNC_AUTH_VENCRYPT_X509VNC) - return TRUE; - } - - return FALSE; -} - -static gboolean gvnc_has_credentials(gpointer data) -{ - struct gvnc *gvnc = (struct gvnc *)data; - - if (gvnc->has_error) - return TRUE; - if (gvnc_wants_credential_username(gvnc) && !gvnc->cred_username) - return FALSE; - if (gvnc_wants_credential_password(gvnc) && !gvnc->cred_password) - return FALSE; - /* - * For x509 we require a minimum of the CA cert. - * Anything else is a bonus - though the server - * may reject auth if it decides it wants a client - * cert. We can't express that based on auth type - * alone though - we'll merely find out when TLS - * negotiation takes place. - */ - if (gvnc_wants_credential_x509(gvnc) && !gvnc->cred_x509_cacert) - return FALSE; - return TRUE; -} - -static gboolean gvnc_gather_credentials(struct gvnc *gvnc) -{ - if (!gvnc_has_credentials(gvnc)) { - GVNC_DEBUG("Requesting missing credentials\n"); - if (gvnc->has_error || !gvnc->ops.auth_cred) { - gvnc->has_error = TRUE; - return TRUE; - } - if (!gvnc->ops.auth_cred(gvnc->ops_data)) - gvnc->has_error = TRUE; - if (gvnc->has_error) - return TRUE; - GVNC_DEBUG("Waiting for missing credentials\n"); - g_condition_wait(gvnc_has_credentials, gvnc); - GVNC_DEBUG("Got all credentials\n"); - } - return !gvnc_has_error(gvnc); -} static gboolean gvnc_has_auth_subtype(gpointer data) { @@ -2387,14 +3012,18 @@ static gboolean gvnc_perform_auth_tls(struct gvnc *gvnc) GVNC_DEBUG("Could not start TLS\n"); return FALSE; } - GVNC_DEBUG("Completed TLS setup\n"); + GVNC_DEBUG("Completed TLS setup!!!\n"); nauth = gvnc_read_u8(gvnc); + GVNC_DEBUG("Got %d subauths\n", nauth); if (gvnc_has_error(gvnc)) return FALSE; - if (nauth == 0) + GVNC_DEBUG("Got %d subauths\n", nauth); + if (nauth == 0) { + GVNC_DEBUG("No sub-auth types requested\n"); return gvnc_check_auth_result(gvnc); + } if (nauth > sizeof(auth)) { GVNC_DEBUG("Too many (%d) auth types\n", nauth); @@ -2424,9 +3053,6 @@ static gboolean gvnc_perform_auth_tls(struct gvnc *gvnc) GVNC_DEBUG("Choose auth %d\n", gvnc->auth_subtype); - if (!gvnc_gather_credentials(gvnc)) - return FALSE; - gvnc_write_u8(gvnc, gvnc->auth_subtype); gvnc_flush(gvnc); @@ -2437,6 +3063,10 @@ static gboolean gvnc_perform_auth_tls(struct gvnc *gvnc) return TRUE; case GVNC_AUTH_VNC: return gvnc_perform_auth_vnc(gvnc); +#ifdef HAVE_SASL + case GVNC_AUTH_SASL: + return gvnc_perform_auth_sasl(gvnc); +#endif default: return FALSE; } @@ -2519,6 +3149,7 @@ static gboolean gvnc_perform_auth_vencrypt(struct gvnc *gvnc) case GVNC_AUTH_VENCRYPT_TLSNONE: case GVNC_AUTH_VENCRYPT_TLSPLAIN: case GVNC_AUTH_VENCRYPT_TLSVNC: + case GVNC_AUTH_VENCRYPT_TLSSASL: anonTLS = 1; break; default: @@ -2529,7 +3160,7 @@ static gboolean gvnc_perform_auth_vencrypt(struct gvnc *gvnc) GVNC_DEBUG("Could not start TLS\n"); return FALSE; } - GVNC_DEBUG("Completed TLS setup\n"); + GVNC_DEBUG("Completed TLS setup, do subauth %d\n", gvnc->auth_subtype); switch (gvnc->auth_subtype) { /* Plain certificate based auth */ @@ -2544,7 +3175,16 @@ static gboolean gvnc_perform_auth_vencrypt(struct gvnc *gvnc) GVNC_DEBUG("Handing off to VNC auth\n"); return gvnc_perform_auth_vnc(gvnc); +#ifdef HAVE_SASL + /* SASL layered over TLS */ + case GVNC_AUTH_VENCRYPT_TLSSASL: + case GVNC_AUTH_VENCRYPT_X509SASL: + GVNC_DEBUG("Handing off to SASL auth\n"); + return gvnc_perform_auth_sasl(gvnc); +#endif + default: + GVNC_DEBUG("Unknown auth subtype %d\n", gvnc->auth_subtype); return FALSE; } } @@ -2626,6 +3266,11 @@ static gboolean gvnc_perform_auth(struct gvnc *gvnc) case GVNC_AUTH_VENCRYPT: return gvnc_perform_auth_vencrypt(gvnc); +#ifdef HAVE_SASL + case GVNC_AUTH_SASL: + return gvnc_perform_auth_sasl(gvnc); +#endif + default: if (gvnc->ops.auth_unsupported) gvnc->ops.auth_unsupported (gvnc->ops_data, gvnc->auth_type); @@ -2671,6 +3316,11 @@ void gvnc_close(struct gvnc *gvnc) gnutls_bye(gvnc->tls_session, GNUTLS_SHUT_RDWR); gvnc->tls_session = NULL; } +#if HAVE_SASL + if (gvnc->saslconn) + sasl_dispose (&gvnc->saslconn); +#endif + if (gvnc->channel) { g_io_channel_unref(gvnc->channel); gvnc->channel = NULL; @@ -2987,7 +3637,7 @@ gboolean gvnc_open_host(struct gvnc *gvnc, const char *host, const char *port) gboolean gvnc_set_auth_type(struct gvnc *gvnc, unsigned int type) { - GVNC_DEBUG("Requested auth type %u\n", type); + GVNC_DEBUG("Thinking about auth type %u", type); if (gvnc->auth_type != GVNC_AUTH_INVALID) { gvnc->has_error = TRUE; return !gvnc_has_error(gvnc); @@ -2995,13 +3645,16 @@ gboolean gvnc_set_auth_type(struct gvnc *gvnc, unsigned int type) if (type != GVNC_AUTH_NONE && type != GVNC_AUTH_VNC && type != GVNC_AUTH_TLS && - type != GVNC_AUTH_VENCRYPT) { + type != GVNC_AUTH_VENCRYPT && + type != GVNC_AUTH_SASL) { + GVNC_DEBUG("Unsupported auth type %u", type); if (gvnc->ops.auth_unsupported) - gvnc->ops.auth_unsupported (gvnc->ops_data, type); + gvnc->ops.auth_unsupported (gvnc->ops_data, type); gvnc->has_error = TRUE; return !gvnc_has_error(gvnc); } + GVNC_DEBUG("Decided on auth type %u", type); gvnc->auth_type = type; gvnc->auth_subtype = GVNC_AUTH_INVALID; @@ -3027,7 +3680,7 @@ gboolean gvnc_set_auth_subtype(struct gvnc *gvnc, unsigned int type) gboolean gvnc_set_credential_password(struct gvnc *gvnc, const char *password) { - GVNC_DEBUG("Set password credential\n"); + GVNC_DEBUG("Set password credential %s", password); if (gvnc->cred_password) g_free(gvnc->cred_password); if (!(gvnc->cred_password = g_strdup(password))) { @@ -3039,7 +3692,7 @@ gboolean gvnc_set_credential_password(struct gvnc *gvnc, const char *password) gboolean gvnc_set_credential_username(struct gvnc *gvnc, const char *username) { - GVNC_DEBUG("Set username credential %s\n", username); + GVNC_DEBUG("Set username credential %s", username); if (gvnc->cred_username) g_free(gvnc->cred_username); if (!(gvnc->cred_username = g_strdup(username))) { @@ -3051,7 +3704,7 @@ gboolean gvnc_set_credential_username(struct gvnc *gvnc, const char *username) gboolean gvnc_set_credential_x509_cacert(struct gvnc *gvnc, const char *file) { - GVNC_DEBUG("Set x509 cacert %s\n", file); + GVNC_DEBUG("Set x509 cacert %s", file); if (gvnc->cred_x509_cacert) g_free(gvnc->cred_x509_cacert); if (!(gvnc->cred_x509_cacert = g_strdup(file))) { diff --git a/src/gvnc.h b/src/gvnc.h index dc65128..742a408 100644 --- a/src/gvnc.h +++ b/src/gvnc.h @@ -128,7 +128,8 @@ typedef enum { GVNC_AUTH_TIGHT = 16, GVNC_AUTH_ULTRA = 17, GVNC_AUTH_TLS = 18, /* Used by VINO */ - GVNC_AUTH_VENCRYPT = 19 /* Used by VeNCrypt and QEMU */ + GVNC_AUTH_VENCRYPT = 19, /* Used by VeNCrypt and QEMU */ + GVNC_AUTH_SASL = 20, /* SASL type used by VINO and QEMU */ } gvnc_auth; typedef enum { @@ -139,6 +140,8 @@ typedef enum { GVNC_AUTH_VENCRYPT_X509NONE = 260, GVNC_AUTH_VENCRYPT_X509VNC = 261, GVNC_AUTH_VENCRYPT_X509PLAIN = 262, + GVNC_AUTH_VENCRYPT_X509SASL = 263, + GVNC_AUTH_VENCRYPT_TLSSASL = 264, } gvnc_auth_vencrypt; diff --git a/src/vncdisplay.c b/src/vncdisplay.c index f1082da..1759538 100644 --- a/src/vncdisplay.c +++ b/src/vncdisplay.c @@ -1904,9 +1904,23 @@ static void vnc_display_init(VncDisplay *display) priv->shared_flag = FALSE; priv->force_size = TRUE; + /* + * Both these two provide TLS based auth, and can layer + * all the other auth types on top. So these two must + * be the first listed + */ priv->preferable_auths = g_slist_append (priv->preferable_auths, GUINT_TO_POINTER (GVNC_AUTH_VENCRYPT)); priv->preferable_auths = g_slist_append (priv->preferable_auths, GUINT_TO_POINTER (GVNC_AUTH_TLS)); + + /* + * Then stackable auth types in order of preference + */ + priv->preferable_auths = g_slist_append (priv->preferable_auths, GUINT_TO_POINTER (GVNC_AUTH_SASL)); priv->preferable_auths = g_slist_append (priv->preferable_auths, GUINT_TO_POINTER (GVNC_AUTH_VNC)); + + /* + * Or nothing at all + */ priv->preferable_auths = g_slist_append (priv->preferable_auths, GUINT_TO_POINTER (GVNC_AUTH_NONE)); priv->gvnc = gvnc_new(&vnc_display_ops, obj); -- |: http://berrange.com/ -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://freshmeat.net/~danielpb/ -o- http://gtk-vnc.sourceforge.net :|
Attachment:
signature.asc
Description: Digital signature