summaryrefslogtreecommitdiff
path: root/vnc.c
diff options
context:
space:
mode:
authoraliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162>2009-03-06 20:27:28 +0000
committeraliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162>2009-03-06 20:27:28 +0000
commit2f9606b3736c3be4dbd606c46525c7b770ced119 (patch)
treec68a38d8b691e5235f1f2afc833aae9fea961d8b /vnc.c
parent5fb6c7a8b26eab1a22207d24b4784bd2b39ab54b (diff)
downloadqemu-2f9606b3736c3be4dbd606c46525c7b770ced119.tar.gz
Add SASL authentication support ("Daniel P. Berrange")
This patch adds the new SASL authentication protocol to the VNC server. It is enabled by setting the 'sasl' flag when launching VNC. SASL can optionally provide encryption via its SSF layer, if a suitable mechanism is configured (eg, GSSAPI/Kerberos, or Digest-MD5). If an SSF layer is not available, then it should be combined with the x509 VNC authentication protocol which provides encryption. eg, if using GSSAPI qemu -vnc localhost:1,sasl eg if using TLS/x509 for encryption qemu -vnc localhost:1,sasl,tls,x509 By default the Cyrus SASL library will look for its configuration in the file /etc/sasl2/qemu.conf. For non-root users, this can be overridden by setting the SASL_CONF_PATH environment variable, eg to make it look in $HOME/.sasl2. NB unprivileged users may not have access to the full range of SASL mechanisms, since some of them require some administrative privileges to configure. The patch includes an example SASL configuration file which illustrates config for GSSAPI and Digest-MD5, though it should be noted that the latter is not really considered secure any more. Most of the SASL authentication code is located in a separate source file, vnc-auth-sasl.c. The main vnc.c file only contains minimal integration glue, specifically parsing of command line flags / setup, and calls to start the SASL auth process, to do encoding/decoding for data. There are several possible stacks for reading & writing of data, depending on the combo of VNC authentication methods in use - Clear. read/write straight to socket - TLS. read/write via GNUTLS helpers - SASL. encode/decode via SASL SSF layer, then read/write to socket - SASL+TLS. encode/decode via SASL SSF layer, then read/write via GNUTLS Hence, the vnc_client_read & vnc_client_write methods have been refactored a little. vnc_client_read: main entry point for reading, calls either - vnc_client_read_plain reading, with no intermediate decoding - vnc_client_read_sasl reading, with SASL SSF decoding These two methods, then call vnc_client_read_buf(). This decides whether to write to the socket directly or write via GNUTLS. The situation is the same for writing data. More extensive comments have been added in the code / patch. The vnc_client_read_sasl and vnc_client_write_sasl method implementations live in the separate vnc-auth-sasl.c file. The state required for the SASL auth mechanism is kept in a separate VncStateSASL struct, defined in vnc-auth-sasl.h and included in the main VncState. The configure script probes for SASL and automatically enables it if found, unless --disable-vnc-sasl was given to override it. Makefile | 7 Makefile.target | 5 b/qemu.sasl | 34 ++ b/vnc-auth-sasl.c | 626 ++++++++++++++++++++++++++++++++++++++++++++++++++++ b/vnc-auth-sasl.h | 67 +++++ configure | 34 ++ qemu-doc.texi | 97 ++++++++ vnc-auth-vencrypt.c | 12 vnc.c | 249 ++++++++++++++++++-- vnc.h | 31 ++ 10 files changed, 1129 insertions(+), 33 deletions(-) Signed-off-by: Daniel P. Berrange <berrange@redhat.com> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com> git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@6724 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'vnc.c')
-rw-r--r--vnc.c249
1 files changed, 222 insertions, 27 deletions
diff --git a/vnc.c b/vnc.c
index f4d8fe60ee..074238790b 100644
--- a/vnc.c
+++ b/vnc.c
@@ -68,7 +68,8 @@ static char *addr_to_string(const char *format,
return addr;
}
-static char *vnc_socket_local_addr(const char *format, int fd) {
+
+char *vnc_socket_local_addr(const char *format, int fd) {
struct sockaddr_storage sa;
socklen_t salen;
@@ -79,7 +80,8 @@ static char *vnc_socket_local_addr(const char *format, int fd) {
return addr_to_string(format, &sa, salen);
}
-static char *vnc_socket_remote_addr(const char *format, int fd) {
+
+char *vnc_socket_remote_addr(const char *format, int fd) {
struct sockaddr_storage sa;
socklen_t salen;
@@ -125,12 +127,18 @@ static const char *vnc_auth_name(VncDisplay *vd) {
return "vencrypt+x509+vnc";
case VNC_AUTH_VENCRYPT_X509PLAIN:
return "vencrypt+x509+plain";
+ case VNC_AUTH_VENCRYPT_TLSSASL:
+ return "vencrypt+tls+sasl";
+ case VNC_AUTH_VENCRYPT_X509SASL:
+ return "vencrypt+x509+sasl";
default:
return "vencrypt";
}
#else
return "vencrypt";
#endif
+ case VNC_AUTH_SASL:
+ return "sasl";
}
return "unknown";
}
@@ -278,7 +286,7 @@ static void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
vnc_write_s32(vs, encoding);
}
-static void buffer_reserve(Buffer *buffer, size_t len)
+void buffer_reserve(Buffer *buffer, size_t len)
{
if ((buffer->capacity - buffer->offset) < len) {
buffer->capacity += (len + 1024);
@@ -290,22 +298,22 @@ static void buffer_reserve(Buffer *buffer, size_t len)
}
}
-static int buffer_empty(Buffer *buffer)
+int buffer_empty(Buffer *buffer)
{
return buffer->offset == 0;
}
-static uint8_t *buffer_end(Buffer *buffer)
+uint8_t *buffer_end(Buffer *buffer)
{
return buffer->buffer + buffer->offset;
}
-static void buffer_reset(Buffer *buffer)
+void buffer_reset(Buffer *buffer)
{
buffer->offset = 0;
}
-static void buffer_append(Buffer *buffer, const void *data, size_t len)
+void buffer_append(Buffer *buffer, const void *data, size_t len)
{
memcpy(buffer->buffer + buffer->offset, data, len);
buffer->offset += len;
@@ -822,7 +830,8 @@ static void audio_del(VncState *vs)
}
}
-static int vnc_client_io_error(VncState *vs, int ret, int last_errno)
+
+int vnc_client_io_error(VncState *vs, int ret, int last_errno)
{
if (ret == 0 || ret == -1) {
if (ret == -1) {
@@ -848,6 +857,9 @@ static int vnc_client_io_error(VncState *vs, int ret, int last_errno)
#ifdef CONFIG_VNC_TLS
vnc_tls_client_cleanup(vs);
#endif /* CONFIG_VNC_TLS */
+#ifdef CONFIG_VNC_SASL
+ vnc_sasl_client_cleanup(vs);
+#endif /* CONFIG_VNC_SASL */
audio_del(vs);
VncState *p, *parent = NULL;
@@ -878,14 +890,28 @@ void vnc_client_error(VncState *vs)
vnc_client_io_error(vs, -1, EINVAL);
}
-void vnc_client_write(void *opaque)
+
+/*
+ * Called to write a chunk of data to the client socket. The data may
+ * be the raw data, or may have already been encoded by SASL.
+ * The data will be written either straight onto the socket, or
+ * written via the GNUTLS wrappers, if TLS/SSL encryption is enabled
+ *
+ * NB, it is theoretically possible to have 2 layers of encryption,
+ * both SASL, and this TLS layer. It is highly unlikely in practice
+ * though, since SASL encryption will typically be a no-op if TLS
+ * is active
+ *
+ * Returns the number of bytes written, which may be less than
+ * the requested 'datalen' if the socket would block. Returns
+ * -1 on error, and disconnects the client socket.
+ */
+long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
{
long ret;
- VncState *vs = opaque;
-
#ifdef CONFIG_VNC_TLS
if (vs->tls.session) {
- ret = gnutls_write(vs->tls.session, vs->output.buffer, vs->output.offset);
+ ret = gnutls_write(vs->tls.session, data, datalen);
if (ret < 0) {
if (ret == GNUTLS_E_AGAIN)
errno = EAGAIN;
@@ -895,10 +921,42 @@ void vnc_client_write(void *opaque)
}
} else
#endif /* CONFIG_VNC_TLS */
- ret = send(vs->csock, vs->output.buffer, vs->output.offset, 0);
- ret = vnc_client_io_error(vs, ret, socket_error());
+ ret = send(vs->csock, data, datalen, 0);
+ VNC_DEBUG("Wrote wire %p %d -> %ld\n", data, datalen, ret);
+ return vnc_client_io_error(vs, ret, socket_error());
+}
+
+
+/*
+ * Called to write buffered data to the client socket, when not
+ * using any SASL SSF encryption layers. Will write as much data
+ * as possible without blocking. If all buffered data is written,
+ * will switch the FD poll() handler back to read monitoring.
+ *
+ * Returns the number of bytes written, which may be less than
+ * the buffered output data if the socket would block. Returns
+ * -1 on error, and disconnects the client socket.
+ */
+static long vnc_client_write_plain(VncState *vs)
+{
+ long ret;
+
+#ifdef CONFIG_VNC_SASL
+ VNC_DEBUG("Write Plain: Pending output %p size %d offset %d. Wait SSF %d\n",
+ vs->output.buffer, vs->output.capacity, vs->output.offset,
+ vs->sasl.waitWriteSSF);
+
+ if (vs->sasl.conn &&
+ vs->sasl.runSSF &&
+ vs->sasl.waitWriteSSF) {
+ ret = vnc_client_write_buf(vs, vs->output.buffer, vs->sasl.waitWriteSSF);
+ if (ret)
+ vs->sasl.waitWriteSSF -= ret;
+ } else
+#endif /* CONFIG_VNC_SASL */
+ ret = vnc_client_write_buf(vs, vs->output.buffer, vs->output.offset);
if (!ret)
- return;
+ return 0;
memmove(vs->output.buffer, vs->output.buffer + ret, (vs->output.offset - ret));
vs->output.offset -= ret;
@@ -906,6 +964,29 @@ void vnc_client_write(void *opaque)
if (vs->output.offset == 0) {
qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
}
+
+ return ret;
+}
+
+
+/*
+ * First function called whenever there is data to be written to
+ * the client socket. Will delegate actual work according to whether
+ * SASL SSF layers are enabled (thus requiring encryption calls)
+ */
+void vnc_client_write(void *opaque)
+{
+ long ret;
+ VncState *vs = opaque;
+
+#ifdef CONFIG_VNC_SASL
+ if (vs->sasl.conn &&
+ vs->sasl.runSSF &&
+ !vs->sasl.waitWriteSSF)
+ ret = vnc_client_write_sasl(vs);
+ else
+#endif /* CONFIG_VNC_SASL */
+ ret = vnc_client_write_plain(vs);
}
void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
@@ -914,16 +995,28 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
vs->read_handler_expect = expecting;
}
-void vnc_client_read(void *opaque)
+
+/*
+ * Called to read a chunk of data from the client socket. The data may
+ * be the raw data, or may need to be further decoded by SASL.
+ * The data will be read either straight from to the socket, or
+ * read via the GNUTLS wrappers, if TLS/SSL encryption is enabled
+ *
+ * NB, it is theoretically possible to have 2 layers of encryption,
+ * both SASL, and this TLS layer. It is highly unlikely in practice
+ * though, since SASL encryption will typically be a no-op if TLS
+ * is active
+ *
+ * Returns the number of bytes read, which may be less than
+ * the requested 'datalen' if the socket would block. Returns
+ * -1 on error, and disconnects the client socket.
+ */
+long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
{
- VncState *vs = opaque;
long ret;
-
- buffer_reserve(&vs->input, 4096);
-
#ifdef CONFIG_VNC_TLS
if (vs->tls.session) {
- ret = gnutls_read(vs->tls.session, buffer_end(&vs->input), 4096);
+ ret = gnutls_read(vs->tls.session, data, datalen);
if (ret < 0) {
if (ret == GNUTLS_E_AGAIN)
errno = EAGAIN;
@@ -933,12 +1026,52 @@ void vnc_client_read(void *opaque)
}
} else
#endif /* CONFIG_VNC_TLS */
- ret = recv(vs->csock, buffer_end(&vs->input), 4096, 0);
- ret = vnc_client_io_error(vs, ret, socket_error());
- if (!ret)
- return;
+ ret = recv(vs->csock, data, datalen, 0);
+ VNC_DEBUG("Read wire %p %d -> %ld\n", data, datalen, ret);
+ return vnc_client_io_error(vs, ret, socket_error());
+}
+
+/*
+ * Called to read data from the client socket to the input buffer,
+ * when not using any SASL SSF encryption layers. Will read as much
+ * data as possible without blocking.
+ *
+ * Returns the number of bytes read. Returns -1 on error, and
+ * disconnects the client socket.
+ */
+static long vnc_client_read_plain(VncState *vs)
+{
+ int ret;
+ VNC_DEBUG("Read plain %p size %d offset %d\n",
+ vs->input.buffer, vs->input.capacity, vs->input.offset);
+ buffer_reserve(&vs->input, 4096);
+ ret = vnc_client_read_buf(vs, buffer_end(&vs->input), 4096);
+ if (!ret)
+ return 0;
vs->input.offset += ret;
+ return ret;
+}
+
+
+/*
+ * First function called whenever there is more data to be read from
+ * the client socket. Will delegate actual work according to whether
+ * SASL SSF layers are enabled (thus requiring decryption calls)
+ */
+void vnc_client_read(void *opaque)
+{
+ VncState *vs = opaque;
+ long ret;
+
+#ifdef CONFIG_VNC_SASL
+ if (vs->sasl.conn && vs->sasl.runSSF)
+ ret = vnc_client_read_sasl(vs);
+ else
+#endif /* CONFIG_VNC_SASL */
+ ret = vnc_client_read_plain(vs);
+ if (!ret)
+ return;
while (vs->read_handler && vs->input.offset >= vs->read_handler_expect) {
size_t len = vs->read_handler_expect;
@@ -1723,6 +1856,13 @@ static int protocol_client_auth(VncState *vs, uint8_t *data, size_t len)
break;
#endif /* CONFIG_VNC_TLS */
+#ifdef CONFIG_VNC_SASL
+ case VNC_AUTH_SASL:
+ VNC_DEBUG("Accept SASL auth\n");
+ start_auth_sasl(vs);
+ break;
+#endif /* CONFIG_VNC_SASL */
+
default: /* Should not be possible, but just in case */
VNC_DEBUG("Reject auth %d\n", vs->vd->auth);
vnc_write_u8(vs, 1);
@@ -1924,6 +2064,10 @@ int vnc_display_open(DisplayState *ds, const char *display)
#ifdef CONFIG_VNC_TLS
int tls = 0, x509 = 0;
#endif
+#ifdef CONFIG_VNC_SASL
+ int sasl = 0;
+ int saslErr;
+#endif
if (!vnc_display)
return -1;
@@ -1943,6 +2087,10 @@ int vnc_display_open(DisplayState *ds, const char *display)
reverse = 1;
} else if (strncmp(options, "to=", 3) == 0) {
to_port = atoi(options+3) + 5900;
+#ifdef CONFIG_VNC_SASL
+ } else if (strncmp(options, "sasl", 4) == 0) {
+ sasl = 1; /* Require SASL auth */
+#endif
#ifdef CONFIG_VNC_TLS
} else if (strncmp(options, "tls", 3) == 0) {
tls = 1; /* Require TLS */
@@ -1979,6 +2127,22 @@ int vnc_display_open(DisplayState *ds, const char *display)
}
}
+ /*
+ * Combinations we support here:
+ *
+ * - no-auth (clear text, no auth)
+ * - password (clear text, weak auth)
+ * - sasl (encrypt, good auth *IF* using Kerberos via GSSAPI)
+ * - tls (encrypt, weak anonymous creds, no auth)
+ * - tls + password (encrypt, weak anonymous creds, weak auth)
+ * - tls + sasl (encrypt, weak anonymous creds, good auth)
+ * - tls + x509 (encrypt, good x509 creds, no auth)
+ * - tls + x509 + password (encrypt, good x509 creds, weak auth)
+ * - tls + x509 + sasl (encrypt, good x509 creds, good auth)
+ *
+ * NB1. TLS is a stackable auth scheme.
+ * NB2. the x509 schemes have option to validate a client cert dname
+ */
if (password) {
#ifdef CONFIG_VNC_TLS
if (tls) {
@@ -1991,13 +2155,34 @@ int vnc_display_open(DisplayState *ds, const char *display)
vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC;
}
} else {
-#endif
+#endif /* CONFIG_VNC_TLS */
VNC_DEBUG("Initializing VNC server with password auth\n");
vs->auth = VNC_AUTH_VNC;
#ifdef CONFIG_VNC_TLS
vs->subauth = VNC_AUTH_INVALID;
}
-#endif
+#endif /* CONFIG_VNC_TLS */
+#ifdef CONFIG_VNC_SASL
+ } else if (sasl) {
+#ifdef CONFIG_VNC_TLS
+ if (tls) {
+ vs->auth = VNC_AUTH_VENCRYPT;
+ if (x509) {
+ VNC_DEBUG("Initializing VNC server with x509 SASL auth\n");
+ vs->subauth = VNC_AUTH_VENCRYPT_X509SASL;
+ } else {
+ VNC_DEBUG("Initializing VNC server with TLS SASL auth\n");
+ vs->subauth = VNC_AUTH_VENCRYPT_TLSSASL;
+ }
+ } else {
+#endif /* CONFIG_VNC_TLS */
+ VNC_DEBUG("Initializing VNC server with SASL auth\n");
+ vs->auth = VNC_AUTH_SASL;
+#ifdef CONFIG_VNC_TLS
+ vs->subauth = VNC_AUTH_INVALID;
+ }
+#endif /* CONFIG_VNC_TLS */
+#endif /* CONFIG_VNC_SASL */
} else {
#ifdef CONFIG_VNC_TLS
if (tls) {
@@ -2019,6 +2204,16 @@ int vnc_display_open(DisplayState *ds, const char *display)
#endif
}
+#ifdef CONFIG_VNC_SASL
+ if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) {
+ fprintf(stderr, "Failed to initialize SASL auth %s",
+ sasl_errstring(saslErr, NULL, NULL));
+ free(vs->display);
+ vs->display = NULL;
+ return -1;
+ }
+#endif
+
if (reverse) {
/* connect to viewer */
if (strncmp(display, "unix:", 5) == 0)