summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.appveyor.yml29
-rw-r--r--CMakeLists.txt20
-rw-r--r--client_examples/ppmtest.c13
-rw-r--r--libvncclient/rfbproto.c26
-rw-r--r--libvncclient/rfbsasl.c579
-rw-r--r--libvncclient/rfbsasl.h35
-rw-r--r--libvncclient/sockets.c56
-rw-r--r--libvncclient/tls.h5
-rw-r--r--libvncclient/tls_gnutls.c18
-rw-r--r--libvncclient/tls_none.c9
-rw-r--r--libvncclient/tls_openssl.c18
-rw-r--r--libvncclient/vncviewer.c13
-rw-r--r--rfb/rfbclient.h23
-rw-r--r--rfb/rfbconfig.h.cmakein3
-rw-r--r--rfb/rfbproto.h6
15 files changed, 838 insertions, 15 deletions
diff --git a/.appveyor.yml b/.appveyor.yml
index dd07eeb..0393f3a 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -1,8 +1,14 @@
+#environment:
+# APPVEYOR_RDP_PASSWORD: Pa55word
+
os:
- Visual Studio 2013
- Visual Studio 2015
+#init:
+# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
+
install:
- mkdir deps
- cd deps
@@ -22,13 +28,32 @@ install:
- cmake . -DZLIB_INCLUDE_DIR=..\zlib -DZLIB_LIBRARY=..\zlib\debug\zlibstaticd.lib
- cmake --build .
- cd ..
+ # Berkeley DB - required by SASL
+ - curl -fsSL -o db-4.1.25.tar.gz http://download.oracle.com/berkeley-db/db-4.1.25.tar.gz
+ - 7z x db-4.1.25.tar.gz -so | 7z x -si -ttar > nul
+ - move db-4.1.25 db
+ - cd db\build_win32
+ - C:\"Program Files (x86)"\"Microsoft Visual Studio 12.0"\Common7\IDE\devenv.exe db_dll.dsp /upgrade
+ - msbuild /p:Configuration=Release db_dll.vcxproj
+ - cd ..\..
+ # Cyrus SASL
+ - curl -fsSL -o cyrus-sasl-2.1.26.tar.gz ftp://ftp.cyrusimap.org/cyrus-sasl/cyrus-sasl-2.1.26.tar.gz
+ - 7z x cyrus-sasl-2.1.26.tar.gz -so | 7z x -si -ttar > nul
+ - move cyrus-sasl-2.1.26 sasl
+ - cd sasl
+ - '"%vs120comntools%\VsDevCmd.bat"'
+ - nmake /f NTMakefile OPENSSL_INCLUDE=c:\OpenSSL-Win32\include OPENSSL_LIBPATH=c:\OpenSSL-Win32\lib DB_INCLUDE=c:\projects\libvncserver\deps\db\build_win32 DB_LIBPATH=c:\projects\libvncserver\deps\db\build_win32\release DB_LIB=libdb41.lib install
+ - cd ..
# go back to source root
- cd ..
-
build_script:
- mkdir build
- cd build
- - cmake .. -DZLIB_INCLUDE_DIR=..\deps\zlib -DZLIB_LIBRARY=..\deps\zlib\debug\zlibstaticd.lib -DPNG_PNG_INCLUDE_DIR=..\deps\libpng -DPNG_LIBRARY=..\deps\libpng\debug\libpng16_staticd.lib
+ - cmake .. -DZLIB_INCLUDE_DIR=..\deps\zlib -DZLIB_LIBRARY=..\deps\zlib\debug\zlibstaticd.lib -DPNG_PNG_INCLUDE_DIR=..\deps\libpng -DPNG_LIBRARY=..\deps\libpng\debug\libpng16_staticd.lib -D SASL2_INCLUDE_DIR=c:\cmu\include -D LIBSASL2_LIBRARIES=c:\cmu\lib\libsasl.lib ..
- cmake --build .
- ctest -C Debug --output-on-failure
+
+#on_finish:
+# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 16f235e..430e909 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -47,6 +47,7 @@ option(WITH_TIGHTVNC_FILETRANSFER "Enable filetransfer if there is pthreads supp
option(WITH_24BPP "Allow 24 bpp" ON)
option(WITH_IPv6 "Enable IPv6 Support" ON)
option(WITH_WEBSOCKETS "Build with websockets support" ON)
+option(WITH_SASL "Build with SASL support" ON)
@@ -264,6 +265,18 @@ endif(NOT HAVE_LIBVNCSERVER_IN_ADDR_T)
TEST_BIG_ENDIAN(LIBVNCSERVER_WORDS_BIGENDIAN)
+if(WITH_SASL)
+ find_path(SASL2_INCLUDE_DIR sasl/sasl.h)
+ find_library(LIBSASL2_LIBRARIES sasl2 libsasl.lib)
+endif(WITH_SASL)
+
+if(WITH_SASL AND LIBSASL2_LIBRARIES AND SASL2_INCLUDE_DIR)
+ message(STATUS "Building with SASL: ${LIBSASL2_LIBRARIES} and ${SASL2_INCLUDE_DIR}")
+ set(LIBVNCSERVER_HAVE_SASL 1)
+ set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${LIBSASL2_LIBRARIES})
+ include_directories(${SASL2_INCLUDE_DIR})
+endif(WITH_SASL AND LIBSASL2_LIBRARIES AND SASL2_INCLUDE_DIR)
+
# TODO:
# LIBVNCSERVER_ENOENT_WORKAROUND
# inline
@@ -322,6 +335,13 @@ else()
)
endif()
+if(LIBVNCSERVER_HAVE_SASL)
+ set(LIBVNCCLIENT_SOURCES
+ ${LIBVNCCLIENT_SOURCES}
+ ${LIBVNCCLIENT_DIR}/rfbsasl.c
+ )
+endif()
+
if(ZLIB_FOUND)
add_definitions(-DLIBVNCSERVER_HAVE_LIBZ)
include_directories(${ZLIB_INCLUDE_DIR})
diff --git a/client_examples/ppmtest.c b/client_examples/ppmtest.c
index b8602f0..eeafc22 100644
--- a/client_examples/ppmtest.c
+++ b/client_examples/ppmtest.c
@@ -58,12 +58,25 @@ static void SaveFramebufferAsPPM(rfbClient* client, int x, int y, int w, int h)
fclose(f);
}
+char * getuser(rfbClient *client)
+{
+return strdup("testuser@test");
+}
+
+char * getpassword(rfbClient *client)
+{
+return strdup("Password");
+}
+
int
main(int argc, char **argv)
{
rfbClient* client = rfbGetClient(8,3,4);
time_t t=time(NULL);
+ client->GetUser = getuser;
+ client->GetPassword = getpassword;
+
if(argc>1 && !strcmp("-print",argv[1])) {
client->GotFrameBufferUpdate = PrintRect;
argv[1]=argv[0]; argv++; argc--;
diff --git a/libvncclient/rfbproto.c b/libvncclient/rfbproto.c
index e099f1a..27589b8 100644
--- a/libvncclient/rfbproto.c
+++ b/libvncclient/rfbproto.c
@@ -66,6 +66,10 @@
#include <gcrypt.h>
#endif
+#ifdef LIBVNCSERVER_HAVE_SASL
+#include "rfbsasl.h"
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
#include "minilzo.h"
#include "tls.h"
@@ -500,6 +504,9 @@ ReadSupportedSecurityType(rfbClient* client, uint32_t *result, rfbBool subAuth)
#if defined(LIBVNCSERVER_HAVE_GNUTLS) || defined(LIBVNCSERVER_HAVE_LIBSSL)
tAuth[loop]==rfbVeNCrypt ||
#endif
+#ifdef LIBVNCSERVER_HAVE_SASL
+ tAuth[loop]==rfbSASL ||
+#endif /* LIBVNCSERVER_HAVE_SASL */
(tAuth[loop]==rfbARD && client->GetCredential) ||
(!subAuth && (tAuth[loop]==rfbTLS || (tAuth[loop]==rfbVeNCrypt && client->GetCredential))))
{
@@ -1079,6 +1086,12 @@ InitialiseRFBConnection(rfbClient* client)
if (!HandleVncAuth(client)) return FALSE;
break;
+#ifdef LIBVNCSERVER_HAVE_SASL
+ case rfbSASL:
+ if (!HandleSASLAuth(client)) return FALSE;
+ break;
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
case rfbMSLogon:
if (!HandleMSLogonAuth(client)) return FALSE;
break;
@@ -1117,6 +1130,12 @@ InitialiseRFBConnection(rfbClient* client)
if (!HandleVncAuth(client)) return FALSE;
break;
+#ifdef LIBVNCSERVER_HAVE_SASL
+ case rfbSASL:
+ if (!HandleSASLAuth(client)) return FALSE;
+ break;
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
default:
rfbClientLog("Unknown sub authentication scheme from VNC server: %d\n",
(int)subAuthScheme);
@@ -1146,6 +1165,13 @@ InitialiseRFBConnection(rfbClient* client)
if (!HandlePlainAuth(client)) return FALSE;
break;
+#ifdef LIBVNCSERVER_HAVE_SASL
+ case rfbVeNCryptX509SASL:
+ case rfbVeNCryptTLSSASL:
+ if (!HandleSASLAuth(client)) return FALSE;
+ break;
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
default:
rfbClientLog("Unknown sub authentication scheme from VNC server: %d\n",
client->subAuthScheme);
diff --git a/libvncclient/rfbsasl.c b/libvncclient/rfbsasl.c
new file mode 100644
index 0000000..dc7d3bc
--- /dev/null
+++ b/libvncclient/rfbsasl.c
@@ -0,0 +1,579 @@
+/*
+ * The software in this file is derived from the vncconnection.c source file
+ * from the GTK VNC Widget with modifications by S. Waterman <simon.waterman@zynstra.com>
+ * for compatibility with libvncserver. The copyright and license
+ * statements below apply only to this source file and to no other parts of the
+ * libvncserver library.
+ *
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.0 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * rfbsasl.c - functions to deal with client side of the SASL protocol.
+ */
+
+#ifdef __STRICT_ANSI__
+#define _BSD_SOURCE
+#define _POSIX_SOURCE
+#define _XOPEN_SOURCE 600
+#endif
+
+#include <errno.h>
+#include <rfb/rfbclient.h>
+
+#ifdef WIN32
+#undef SOCKET
+#include <winsock2.h>
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#define socklen_t int
+#define close closesocket
+#define read(sock,buf,len) recv(sock,buf,len,0)
+#define write(sock,buf,len) send(sock,buf,len,0)
+#ifdef LIBVNCSERVER_HAVE_WS2TCPIP_H
+#undef socklen_t
+#include <ws2tcpip.h>
+#endif /* LIBVNCSERVER_HAVE_WS2TCPIP_H */
+#else /* WIN32 */
+#include <arpa/inet.h>
+#endif /* WIN32 */
+
+#include "rfbsasl.h"
+
+#include "tls.h"
+
+#ifdef _MSC_VER
+# define snprintf _snprintf /* MSVC went straight to the underscored syntax */
+#endif
+
+/*
+ * NB, keep in sync with similar method in qemud/remote.c
+ */
+static char *vnc_connection_addr_to_string(char *host, int port)
+{
+ char * buf = (char *)malloc(strlen(host) + 7);
+ sprintf(buf, "%s;%hu", host, port);
+ return buf;
+}
+
+static int log_func(void *context,
+ int level,
+ const char *message)
+{
+ rfbClientLog("SASL: %s\n", message);
+
+ return SASL_OK;
+}
+
+static int user_callback_adapt(void *context,
+ int id,
+ const char **result,
+ unsigned *len)
+{
+ rfbClient* client = (rfbClient *)context;
+
+ if (id != SASL_CB_AUTHNAME) {
+ rfbClientLog("Unrecognized SASL callback ID %d\n", id);
+ return SASL_FAIL;
+ }
+
+ if (!client->GetUser) {
+ rfbClientLog("Client user callback not found\n");
+ return SASL_FAIL;
+ }
+
+ *result = client->GetUser(client);
+
+ if (! *result) return SASL_FAIL;
+ /**len = strlen(*result);*/
+ return SASL_OK;
+}
+
+static int password_callback_adapt(sasl_conn_t *conn,
+ void * context,
+ int id,
+ sasl_secret_t **secret)
+{
+ rfbClient* client = (rfbClient *)context;
+ char * password;
+
+ if (id != SASL_CB_PASS) {
+ rfbClientLog("Unrecognized SASL callback ID %d\n", id);
+ return SASL_FAIL;
+ }
+
+ if (client->saslSecret) { /* If we've already got it just return it. */
+ *secret = client->saslSecret;
+ return SASL_OK;
+ }
+
+ if (!client->GetPassword) {
+ rfbClientLog("Client password callback not found\n");
+ return SASL_FAIL;
+ }
+
+ password = client->GetPassword(client);
+
+ if (! password) return SASL_FAIL;
+
+ sasl_secret_t *lsec = (sasl_secret_t *)malloc(sizeof(sasl_secret_t) + strlen(password));
+ if (!lsec) {
+ rfbClientLog("Could not allocate sasl_secret_t\n");
+ return SASL_FAIL;
+ }
+
+ strcpy(lsec->data, password);
+ lsec->len = strlen(password);
+ client->saslSecret = lsec;
+ *secret = lsec;
+
+ /* Clear client password */
+ size_t i;
+ for (i = 0; i < lsec->len; i++) {
+ password[i] = '\0';
+ }
+ free(password);
+
+ return SASL_OK;
+}
+
+#define SASL_MAX_MECHLIST_LEN 300
+#define SASL_MAX_DATA_LEN (1024 * 1024)
+
+/* Perform the SASL authentication process
+ */
+rfbBool
+HandleSASLAuth(rfbClient *client)
+{
+ sasl_conn_t *saslconn = NULL;
+ sasl_security_properties_t secprops;
+ const char *clientout;
+ char *serverin = NULL;
+ unsigned int clientoutlen, serverinlen;
+ int err, complete = 0;
+ char *localAddr = NULL, *remoteAddr = NULL;
+ const void *val;
+ sasl_ssf_t ssf;
+ sasl_callback_t saslcb[] = {
+ {SASL_CB_LOG, (void *)log_func, NULL},
+ {SASL_CB_AUTHNAME, client->GetUser ? (void *)user_callback_adapt : NULL, client},
+ {SASL_CB_PASS, client->GetPassword ? (void *)password_callback_adapt : NULL, client},
+ { .id = 0 },
+ };
+ sasl_interact_t *interact = NULL;
+ uint32_t mechlistlen;
+ char *mechlist;
+ char *wantmech;
+ const char *mechname;
+ rfbBool ret;
+
+ client->saslconn = NULL;
+
+ /* Sets up the SASL library as a whole */
+ err = sasl_client_init(NULL);
+ rfbClientLog("Client initialize SASL authentication %d\n", err);
+ if (err != SASL_OK) {
+ rfbClientLog("failed to initialize SASL library: %d (%s)\n",
+ err, sasl_errstring(err, NULL, NULL));
+ goto error;
+ }
+
+ /* Get local address in form IPADDR:PORT */
+ struct sockaddr_storage localAddress;
+ socklen_t addressLength = sizeof(localAddress);
+ char buf[INET6_ADDRSTRLEN];
+ int port;
+
+ if (getsockname(client->sock, (struct sockaddr*)&localAddress, &addressLength)) {
+ rfbClientLog("failed to get local address\n");
+ goto error;
+ }
+
+ if (localAddress.ss_family == AF_INET) {
+ struct sockaddr_in *sa_in = (struct sockaddr_in*)&localAddress;
+ inet_ntop(AF_INET, &(sa_in->sin_addr), buf, INET_ADDRSTRLEN);
+ port = ntohs(sa_in->sin_port);
+ localAddr = vnc_connection_addr_to_string(buf, port);
+ } else if (localAddress.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sa_in = (struct sockaddr_in6*)&localAddress;
+ inet_ntop(AF_INET6, &(sa_in->sin6_addr), buf, INET6_ADDRSTRLEN);
+ port = ntohs(sa_in->sin6_port);
+ localAddr = vnc_connection_addr_to_string(buf, port);
+ } else {
+ rfbClientLog("failed to get local address\n");
+ goto error;
+ }
+
+ /* Get remote address in form IPADDR:PORT */
+ remoteAddr = vnc_connection_addr_to_string(client->serverHost, client->serverPort);
+
+ rfbClientLog("Client SASL new host:'%s' local:'%s' remote:'%s'\n", client->serverHost, localAddr, remoteAddr);
+
+ /* Setup a handle for being a client */
+ err = sasl_client_new("vnc",
+ client->serverHost,
+ localAddr,
+ remoteAddr,
+ saslcb,
+ SASL_SUCCESS_DATA,
+ &saslconn);
+ free(localAddr);
+ free(remoteAddr);
+
+ if (err != SASL_OK) {
+ rfbClientLog("Failed to create SASL client context: %d (%s)\n",
+ err, sasl_errstring(err, NULL, NULL));
+ goto error;
+ }
+
+ /* Initialize some connection props we care about */
+ if (client->tlsSession) {
+ if (!(ssf = (sasl_ssf_t)GetTLSCipherBits(client))) {
+ rfbClientLog("%s", "invalid cipher size for TLS session\n");
+ goto error;
+ }
+
+ rfbClientLog("Setting external SSF %d\n", ssf);
+ err = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &ssf);
+ if (err != SASL_OK) {
+ rfbClientLog("cannot set external SSF %d (%s)\n",
+ 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 = client->tlsSession ? 0 : 56; /* Equiv to DES supported by all Kerberos */
+ secprops.max_ssf = client->tlsSession ? 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 = client->tlsSession ? 0 :
+ SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
+
+ err = sasl_setprop(saslconn, SASL_SEC_PROPS, &secprops);
+ if (err != SASL_OK) {
+ rfbClientLog("cannot set security props %d (%s)\n",
+ err, sasl_errstring(err, NULL, NULL));
+ goto error;
+ }
+
+ /* Get the supported mechanisms from the server */
+ if (!ReadFromRFBServer(client, (char *)&mechlistlen, 4)) {
+ rfbClientLog("failed to read mechlistlen\n");
+ goto error;
+ }
+ mechlistlen = rfbClientSwap32IfLE(mechlistlen);
+ rfbClientLog("mechlistlen is %d\n", mechlistlen);
+ if (mechlistlen > SASL_MAX_MECHLIST_LEN) {
+ rfbClientLog("mechlistlen %d too long\n", mechlistlen);
+ goto error;
+ }
+
+ mechlist = malloc(mechlistlen+1);
+ if (!ReadFromRFBServer(client, mechlist, mechlistlen)) {
+ free(mechlist);
+ goto error;
+ }
+ mechlist[mechlistlen] = '\0';
+
+ /* Allow the client to influence the mechanism selected. */
+ if (client->GetSASLMechanism) {
+ wantmech = client->GetSASLMechanism(client, mechlist);
+
+ if (wantmech && *wantmech != 0) {
+ if (strstr(mechlist, wantmech) == NULL) {
+ rfbClientLog("Client requested SASL mechanism %s not supported by server\n",
+ wantmech);
+ free(mechlist);
+ free(wantmech);
+ goto error;
+ } else {
+ free(mechlist);
+ mechlist = wantmech;
+ }
+ }
+ }
+
+ rfbClientLog("Client start negotiation mechlist '%s'\n", mechlist);
+
+ restart:
+ /* Start the auth negotiation on the client end first */
+ err = sasl_client_start(saslconn,
+ mechlist,
+ &interact,
+ &clientout,
+ &clientoutlen,
+ &mechname);
+ if (err != SASL_OK && err != SASL_CONTINUE && err != SASL_INTERACT) {
+ rfbClientLog("Failed to start SASL negotiation: %d (%s)\n",
+ err, sasl_errdetail(saslconn));
+ free(mechlist);
+ mechlist = NULL;
+ goto error;
+ }
+
+ /* Need to gather some credentials from the client */
+ if (err == SASL_INTERACT) {
+ rfbClientLog("User interaction required but not currently supported\n");
+ goto error;
+ }
+
+ rfbClientLog("Server start negotiation with mech %s. Data %d bytes %p '%s'\n",
+ mechname, clientoutlen, clientout, clientout);
+
+ if (clientoutlen > SASL_MAX_DATA_LEN) {
+ rfbClientLog("SASL negotiation data too long: %d bytes\n",
+ clientoutlen);
+ goto error;
+ }
+
+ /* Send back the chosen mechname */
+ uint32_t mechnamelen = rfbClientSwap32IfLE(strlen(mechname));
+ if (!WriteToRFBServer(client, (char *)&mechnamelen, 4)) goto error;
+ if (!WriteToRFBServer(client, (char *)mechname, strlen(mechname))) goto error;
+
+ /* NB, distinction of NULL vs "" is *critical* in SASL */
+ if (clientout) {
+ uint32_t colsw = rfbClientSwap32IfLE(clientoutlen + 1);
+ if (!WriteToRFBServer(client, (char *)&colsw, 4)) goto error;
+ if (!WriteToRFBServer(client, (char *)clientout, clientoutlen + 1)) goto error;
+ } else {
+ uint32_t temp = 0;
+ if (!WriteToRFBServer(client, (char *)&temp, 4)) goto error;
+ }
+
+ rfbClientLog("%s", "Getting sever start negotiation reply\n");
+ /* Read the 'START' message reply from server */
+ if (!ReadFromRFBServer(client, (char *)&serverinlen, 4)) goto error;
+ serverinlen = rfbClientSwap32IfLE(serverinlen);
+
+ if (serverinlen > SASL_MAX_DATA_LEN) {
+ rfbClientLog("SASL negotiation data too long: %d bytes\n",
+ serverinlen);
+ goto error;
+ }
+
+ /* NB, distinction of NULL vs "" is *critical* in SASL */
+ if (serverinlen) {
+ serverin = malloc(serverinlen);
+ if (!ReadFromRFBServer(client, serverin, serverinlen)) goto error;
+ serverin[serverinlen-1] = '\0';
+ serverinlen--;
+ } else {
+ serverin = NULL;
+ }
+ if (!ReadFromRFBServer(client, (char *)&complete, 1)) goto error;
+
+ rfbClientLog("Client start result complete: %d. Data %d bytes %p '%s'\n",
+ 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) {
+ rfbClientLog("Failed SASL step: %d (%s)\n",
+ err, sasl_errdetail(saslconn));
+ goto error;
+ }
+
+ /* Need to gather some credentials from the client */
+ if (err == SASL_INTERACT) {
+ rfbClientLog("User interaction required but not currently supported\n");
+ goto error;
+ }
+
+ if (serverin) {
+ free(serverin);
+ serverin = NULL;
+ }
+
+ rfbClientLog("Client step result %d. Data %d bytes %p '%s'\n", 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) {
+ uint32_t colsw = rfbClientSwap32IfLE(clientoutlen + 1);
+ if (!WriteToRFBServer(client, (char *)&colsw, 4)) goto error;
+ if (!WriteToRFBServer(client, (char *)clientout, clientoutlen + 1)) goto error;
+ } else {
+ uint32_t temp = 0;
+ if (!WriteToRFBServer(client, (char *)&temp, 4)) goto error;
+ }
+
+ rfbClientLog("Server step with %d bytes %p\n", clientoutlen, clientout);
+
+ if (!ReadFromRFBServer(client, (char *)&serverinlen, 4)) goto error;
+ serverinlen = rfbClientSwap32IfLE(serverinlen);
+
+ if (serverinlen > SASL_MAX_DATA_LEN) {
+ rfbClientLog("SASL negotiation data too long: %d bytes\n",
+ serverinlen);
+ goto error;
+ }
+
+ /* NB, distinction of NULL vs "" is *critical* in SASL */
+ if (serverinlen) {
+ serverin = malloc(serverinlen);
+ if (!ReadFromRFBServer(client, serverin, serverinlen)) goto error;
+ serverin[serverinlen-1] = '\0';
+ serverinlen--;
+ } else {
+ serverin = NULL;
+ }
+ if (!ReadFromRFBServer(client, (char *)&complete, 1)) goto error;
+
+ rfbClientLog("Client step result complete: %d. Data %d bytes %p '%s'\n",
+ complete, serverinlen, serverin, serverin);
+
+ /* This server call shows complete, and earlier client step was OK */
+ if (complete && err == SASL_OK) {
+ free(serverin);
+ serverin = NULL;
+ break;
+ }
+ }
+
+ /* Check for suitable SSF if non-TLS */
+ if (!client->tlsSession) {
+ err = sasl_getprop(saslconn, SASL_SSF, &val);
+ if (err != SASL_OK) {
+ rfbClientLog("cannot query SASL ssf on connection %d (%s)\n",
+ err, sasl_errstring(err, NULL, NULL));
+ goto error;
+ }
+ ssf = *(const int *)val;
+ rfbClientLog("SASL SSF value %d\n", ssf);
+ if (ssf < 56) { /* 56 == DES level, good for Kerberos */
+ rfbClientLog("negotiation SSF %d was not strong enough\n", ssf);
+ goto error;
+ }
+ }
+
+ rfbClientLog("%s", "SASL authentication complete\n");
+
+ uint32_t result;
+ if (!ReadFromRFBServer(client, (char *)&result, 4)) {
+ rfbClientLog("Failed to read authentication result\n");
+ goto error;
+ }
+ result = rfbClientSwap32IfLE(result);
+
+ if (result != 0) {
+ rfbClientLog("Authentication failure\n");
+ goto error;
+ }
+ rfbClientLog("Authentication successful - switching to SSF\n");
+
+ /* 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 */
+ client->saslconn = saslconn;
+
+ /* Clear SASL secret from memory if set - it'll be free'd on dispose */
+ if (client->saslSecret) {
+ size_t i;
+ for (i = 0; i < client->saslSecret->len; i++)
+ client->saslSecret->data[i] = '\0';
+ client->saslSecret->len = 0;
+ }
+
+ return TRUE;
+
+ error:
+ if (client->saslSecret) {
+ size_t i;
+ for (i = 0; i < client->saslSecret->len; i++)
+ client->saslSecret->data[i] = '\0';
+ client->saslSecret->len = 0;
+ }
+
+ if (saslconn)
+ sasl_dispose(&saslconn);
+ return FALSE;
+}
+
+int
+ReadFromSASL(rfbClient* client, char *out, unsigned int n)
+{
+ size_t want;
+
+ if (client->saslDecoded == NULL) {
+ char *encoded;
+ int encodedLen;
+ int err, ret;
+
+ encodedLen = 8192;
+ encoded = (char *)malloc(encodedLen);
+
+ ret = read(client->sock, encoded, encodedLen);
+ if (ret < 0) {
+ free(encoded);
+ return ret;
+ }
+ if (ret == 0) {
+ free(encoded);
+ errno = EIO;
+ return -EIO;
+ }
+
+ err = sasl_decode(client->saslconn, encoded, ret,
+ &client->saslDecoded, &client->saslDecodedLength);
+ free(encoded);
+ if (err != SASL_OK) {
+ rfbClientLog("Failed to decode SASL data %s\n",
+ sasl_errstring(err, NULL, NULL));
+ return -EINVAL;
+ }
+ client->saslDecodedOffset = 0;
+ }
+
+ want = client->saslDecodedLength - client->saslDecodedOffset;
+ if (want > n)
+ want = n;
+
+ memcpy(out,
+ client->saslDecoded + client->saslDecodedOffset,
+ want);
+ client->saslDecodedOffset += want;
+ if (client->saslDecodedOffset == client->saslDecodedLength) {
+ client->saslDecodedLength = client->saslDecodedOffset = 0;
+ client->saslDecoded = NULL;
+ }
+
+ if (!want) {
+ errno = EAGAIN;
+ return -EAGAIN;
+ }
+
+ return want;
+}
diff --git a/libvncclient/rfbsasl.h b/libvncclient/rfbsasl.h
new file mode 100644
index 0000000..8231096
--- /dev/null
+++ b/libvncclient/rfbsasl.h
@@ -0,0 +1,35 @@
+#ifndef RFBSASL_H
+#define RFBSASL_H
+
+/*
+ * Copyright (C) 2017 S. Waterman. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#include <rfb/rfbclient.h>
+
+/*
+ * Perform the SASL authentication process
+ */
+rfbBool HandleSASLAuth(rfbClient *client);
+
+/*
+ * Read from SASL when the SASL SSF is in use.
+ */
+int ReadFromSASL(rfbClient* client, char *out, unsigned int n);
+
+#endif /* RFBSASL_H */
diff --git a/libvncclient/sockets.c b/libvncclient/sockets.c
index 1019580..8ed51a5 100644
--- a/libvncclient/sockets.c
+++ b/libvncclient/sockets.c
@@ -59,6 +59,10 @@
#endif
#include "tls.h"
+#ifdef LIBVNCSERVER_HAVE_SASL
+#include "rfbsasl.h"
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
#ifdef _MSC_VER
# define snprintf _snprintf
#endif
@@ -154,16 +158,24 @@ ReadFromRFBServer(rfbClient* client, char *out, unsigned int n)
while (client->buffered < n) {
int i;
- if (client->tlsSession) {
+ if (client->tlsSession)
i = ReadFromTLS(client, client->buf + client->buffered, RFB_BUF_SIZE - client->buffered);
- } else {
+ else
+#ifdef LIBVNCSERVER_HAVE_SASL
+ if (client->saslconn)
+ i = ReadFromSASL(client, client->buf + client->buffered, RFB_BUF_SIZE - client->buffered);
+ else {
+#endif /* LIBVNCSERVER_HAVE_SASL */
i = read(client->sock, client->buf + client->buffered, RFB_BUF_SIZE - client->buffered);
+#ifdef WIN32
+ if (i < 0) errno=WSAGetLastError();
+#endif
+#ifdef LIBVNCSERVER_HAVE_SASL
}
+#endif
+
if (i <= 0) {
if (i < 0) {
-#ifdef WIN32
- errno=WSAGetLastError();
-#endif
if (errno == EWOULDBLOCK || errno == EAGAIN) {
/* TODO:
ProcessXtEvents();
@@ -192,11 +204,15 @@ ReadFromRFBServer(rfbClient* client, char *out, unsigned int n)
while (n > 0) {
int i;
- if (client->tlsSession) {
+ if (client->tlsSession)
i = ReadFromTLS(client, out, n);
- } else {
+ else
+#ifdef LIBVNCSERVER_HAVE_SASL
+ if (client->saslconn)
+ i = ReadFromSASL(client, out, n);
+ else
+#endif
i = read(client->sock, out, n);
- }
if (i <= 0) {
if (i < 0) {
@@ -248,6 +264,12 @@ WriteToRFBServer(rfbClient* client, char *buf, int n)
fd_set fds;
int i = 0;
int j;
+ const char *obuf = buf;
+#ifdef LIBVNCSERVER_HAVE_SASL
+ const char *output;
+ unsigned int outputlen;
+ int err;
+#endif /* LIBVNCSERVER_HAVE_SASL */
if (client->serverPort==-1)
return TRUE; /* vncrec playing */
@@ -259,9 +281,23 @@ WriteToRFBServer(rfbClient* client, char *buf, int n)
return TRUE;
}
+#ifdef LIBVNCSERVER_HAVE_SASL
+ if (client->saslconn) {
+ err = sasl_encode(client->saslconn,
+ buf, n,
+ &output, &outputlen);
+ if (err != SASL_OK) {
+ rfbClientLog("Failed to encode SASL data %s",
+ sasl_errstring(err, NULL, NULL));
+ return FALSE;
+ }
+ obuf = output;
+ n = outputlen;
+ }
+#endif /* LIBVNCSERVER_HAVE_SASL */
while (i < n) {
- j = write(client->sock, buf + i, (n - i));
+ j = write(client->sock, obuf + i, (n - i));
if (j <= 0) {
if (j < 0) {
#ifdef WIN32
@@ -294,8 +330,6 @@ WriteToRFBServer(rfbClient* client, char *buf, int n)
return TRUE;
}
-
-
static int initSockets() {
#ifdef WIN32
WSADATA trash;
diff --git a/libvncclient/tls.h b/libvncclient/tls.h
index 48d159b..a5a2ac6 100644
--- a/libvncclient/tls.h
+++ b/libvncclient/tls.h
@@ -48,4 +48,9 @@ int WriteToTLS(rfbClient* client, char *buf, unsigned int n);
/* Free TLS resources */
void FreeTLS(rfbClient* client);
+#ifdef LIBVNCSERVER_HAVE_SASL
+/* Get the number of bits in the current cipher */
+int GetTLSCipherBits(rfbClient* client);
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
#endif /* TLS_H */
diff --git a/libvncclient/tls_gnutls.c b/libvncclient/tls_gnutls.c
index f49fa85..f146d2a 100644
--- a/libvncclient/tls_gnutls.c
+++ b/libvncclient/tls_gnutls.c
@@ -346,6 +346,10 @@ ReadVeNCryptSecurityType(rfbClient* client, uint32_t *result)
if (t==rfbVeNCryptTLSNone ||
t==rfbVeNCryptTLSVNC ||
t==rfbVeNCryptTLSPlain ||
+#ifdef LIBVNCSERVER_HAVE_SASL
+ t==rfbVeNCryptTLSSASL ||
+ t==rfbVeNCryptX509SASL ||
+#endif /*LIBVNCSERVER_HAVE_SASL */
t==rfbVeNCryptX509None ||
t==rfbVeNCryptX509VNC ||
t==rfbVeNCryptX509Plain)
@@ -505,6 +509,9 @@ HandleVeNCryptAuth(rfbClient* client)
case rfbVeNCryptTLSNone:
case rfbVeNCryptTLSVNC:
case rfbVeNCryptTLSPlain:
+#ifdef LIBVNCSERVER_HAVE_SASL
+ case rfbVeNCryptTLSSASL:
+#endif /* LIBVNCSERVER_HAVE_SASL */
anonTLS = TRUE;
break;
default:
@@ -631,3 +638,14 @@ void FreeTLS(rfbClient* client)
client->tlsSession = NULL;
}
}
+
+#ifdef LIBVNCSERVER_HAVE_SASL
+int
+GetTLSCipherBits(rfbClient* client)
+{
+ gnutls_cipher_algorithm_t cipher = gnutls_cipher_get((gnutls_session_t)client->tlsSession);
+
+ return gnutls_cipher_get_key_size(cipher) * 8;
+}
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
diff --git a/libvncclient/tls_none.c b/libvncclient/tls_none.c
index 91a9f93..4dfcb27 100644
--- a/libvncclient/tls_none.c
+++ b/libvncclient/tls_none.c
@@ -56,3 +56,12 @@ void FreeTLS(rfbClient* client)
}
+#ifdef LIBVNCSERVER_HAVE_SASL
+int
+GetTLSCipherBits(rfbClient* client)
+{
+ rfbClientLog("TLS is not supported.\n");
+ return 0;
+}
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
diff --git a/libvncclient/tls_openssl.c b/libvncclient/tls_openssl.c
index 2bdf3eb..fe60147 100644
--- a/libvncclient/tls_openssl.c
+++ b/libvncclient/tls_openssl.c
@@ -475,6 +475,10 @@ ReadVeNCryptSecurityType(rfbClient* client, uint32_t *result)
if (t==rfbVeNCryptTLSNone ||
t==rfbVeNCryptTLSVNC ||
t==rfbVeNCryptTLSPlain ||
+#ifdef LIBVNCSERVER_HAVE_SASL
+ t==rfbVeNCryptTLSSASL ||
+ t==rfbVeNCryptX509SASL ||
+#endif /*LIBVNCSERVER_HAVE_SASL */
t==rfbVeNCryptX509None ||
t==rfbVeNCryptX509VNC ||
t==rfbVeNCryptX509Plain)
@@ -576,6 +580,9 @@ HandleVeNCryptAuth(rfbClient* client)
case rfbVeNCryptTLSNone:
case rfbVeNCryptTLSVNC:
case rfbVeNCryptTLSPlain:
+#ifdef LIBVNCSERVER_HAVE_SASL
+ case rfbVeNCryptTLSSASL:
+#endif /* LIBVNCSERVER_HAVE_SASL */
anonTLS = TRUE;
break;
default:
@@ -679,3 +686,14 @@ void FreeTLS(rfbClient* client)
SSL_free(client->tlsSession);
}
+#ifdef LIBVNCSERVER_HAVE_SASL
+int GetTLSCipherBits(rfbClient* client)
+{
+ SSL *ssl = (SSL *)(client->tlsSession);
+
+ const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
+
+ return SSL_CIPHER_get_bits(cipher, NULL);
+}
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
diff --git a/libvncclient/vncviewer.c b/libvncclient/vncviewer.c
index 780a1cb..407b4a5 100644
--- a/libvncclient/vncviewer.c
+++ b/libvncclient/vncviewer.c
@@ -350,6 +350,13 @@ rfbClient* rfbGetClient(int bitsPerSample,int samplesPerPixel,
client->listen6Sock = -1;
client->listen6Address = NULL;
client->clientAuthSchemes = NULL;
+
+#ifdef LIBVNCSERVER_HAVE_SASL
+ client->GetSASLMechanism = NULL;
+ client->GetUser = NULL;
+ client->saslSecret = NULL;
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
return client;
}
@@ -534,5 +541,11 @@ void rfbClientCleanup(rfbClient* client) {
free(client->destHost);
if (client->clientAuthSchemes)
free(client->clientAuthSchemes);
+
+#ifdef LIBVNCSERVER_HAVE_SASL
+ if (client->saslSecret)
+ free(client->saslSecret);
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
free(client);
}
diff --git a/rfb/rfbclient.h b/rfb/rfbclient.h
index 4ac9cd6..72d672a 100644
--- a/rfb/rfbclient.h
+++ b/rfb/rfbclient.h
@@ -52,6 +52,10 @@
#include <rfb/rfbproto.h>
#include <rfb/keysym.h>
+#ifdef LIBVNCSERVER_HAVE_SASL
+#include <sasl/sasl.h>
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
#define rfbClientSwap16IfLE(s) \
(*(char *)&client->endianTest ? ((((s) & 0xff) << 8) | (((s) >> 8) & 0xff)) : (s))
@@ -205,6 +209,11 @@ typedef rfbBool (*GotJpegProc)(struct _rfbClient* client, const uint8_t* buffer,
typedef rfbBool (*LockWriteToTLSProc)(struct _rfbClient* client);
typedef rfbBool (*UnlockWriteToTLSProc)(struct _rfbClient* client);
+#ifdef LIBVNCSERVER_HAVE_SASL
+typedef char* (*GetUserProc)(struct _rfbClient* client);
+typedef char* (*GetSASLMechanismProc)(struct _rfbClient* client, char* mechlist);
+#endif /* LIBVNCSERVER_HAVE_SASL */
+
typedef struct _rfbClient {
uint8_t* frameBuffer;
int width, height;
@@ -399,6 +408,20 @@ typedef struct _rfbClient {
GotBitmapProc GotBitmap;
/** Hook for custom JPEG decoding and rendering */
GotJpegProc GotJpeg;
+
+#ifdef LIBVNCSERVER_HAVE_SASL
+ sasl_conn_t *saslconn;
+ const char *saslDecoded;
+ unsigned int saslDecodedLength;
+ unsigned int saslDecodedOffset;
+ sasl_secret_t *saslSecret;
+
+ /* Callback to allow the client to choose a preferred mechanism. The string returned will
+ be freed once no longer required. */
+ GetSASLMechanismProc GetSASLMechanism;
+ GetUserProc GetUser;
+
+#endif /* LIBVNCSERVER_HAVE_SASL */
} rfbClient;
/* cursor.c */
diff --git a/rfb/rfbconfig.h.cmakein b/rfb/rfbconfig.h.cmakein
index c4dc5c0..7638921 100644
--- a/rfb/rfbconfig.h.cmakein
+++ b/rfb/rfbconfig.h.cmakein
@@ -148,6 +148,9 @@
/* Define to 1 if OpenSSL is present */
#cmakedefine LIBVNCSERVER_HAVE_LIBSSL 1
+/* Define to 1 if Cyrus SASL is present */
+#cmakedefine LIBVNCSERVER_HAVE_SASL 1
+
/* Define to 1 to build with websockets */
#cmakedefine LIBVNCSERVER_WITH_WEBSOCKETS 1
diff --git a/rfb/rfbproto.h b/rfb/rfbproto.h
index f0d6ea1..c5b2723 100644
--- a/rfb/rfbproto.h
+++ b/rfb/rfbproto.h
@@ -67,9 +67,8 @@
typedef int8_t rfbBool;
#include <sys/timeb.h>
#include <winsock2.h>
-#else
-#include <rfb/rfbconfig.h>
#endif
+#include <rfb/rfbconfig.h>
#ifdef LIBVNCSERVER_HAVE_LIBZ
#include <zlib.h>
@@ -287,6 +286,9 @@ typedef char rfbProtocolVersionMsg[13]; /* allow extra byte for null */
#define rfbUltra 17
#define rfbTLS 18
#define rfbVeNCrypt 19
+#ifdef LIBVNCSERVER_HAVE_SASL
+#define rfbSASL 20
+#endif /* LIBVNCSERVER_HAVE_SASL */
#define rfbARD 30
#define rfbMSLogon 0xfffffffa