summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--AUTHORS2
-rw-r--r--client_examples/SDLvncviewer.c25
-rw-r--r--configure.ac17
-rw-r--r--libvncclient/Makefile.am16
-rw-r--r--libvncclient/listen.c77
-rw-r--r--libvncclient/rfbproto.c21
-rw-r--r--libvncclient/sockets.c75
-rw-r--r--libvncclient/tls_gnutls.c (renamed from libvncclient/tls.c)52
-rw-r--r--libvncclient/tls_none.c58
-rw-r--r--libvncclient/tls_openssl.c587
-rw-r--r--libvncclient/vncviewer.c7
-rw-r--r--libvncserver/cargs.c34
-rw-r--r--libvncserver/httpd.c91
-rw-r--r--libvncserver/main.c40
-rw-r--r--libvncserver/rfbserver.c17
-rw-r--r--libvncserver/sockets.c214
-rw-r--r--rfb/rfb.h9
-rw-r--r--rfb/rfbclient.h12
-rw-r--r--webclients/index.vnc10
-rw-r--r--webclients/novnc/LICENSE.txt2
-rw-r--r--webclients/novnc/README.md67
-rw-r--r--webclients/novnc/include/base.css18
-rw-r--r--webclients/novnc/include/base64.js2
-rw-r--r--webclients/novnc/include/display.js85
-rw-r--r--webclients/novnc/include/input.js27
-rwxr-xr-xwebclients/novnc/include/jsunzip.js668
-rw-r--r--webclients/novnc/include/rfb.js334
-rw-r--r--webclients/novnc/include/ui.js56
-rw-r--r--webclients/novnc/include/util.js33
-rw-r--r--webclients/novnc/include/vnc.js1
-rw-r--r--webclients/novnc/include/websock.js19
-rw-r--r--webclients/novnc/include/webutil.js8
-rw-r--r--webclients/novnc/vnc.html24
-rw-r--r--webclients/novnc/vnc_auto.html21
35 files changed, 2446 insertions, 284 deletions
diff --git a/.gitignore b/.gitignore
index 1699401..083b816 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,7 @@ libvncserver*.deb
CVS
client_examples/SDLvncviewer
client_examples/backchannel
+client_examples/gtkvncviewer
client_examples/ppmtest
config.guess
config.sub
diff --git a/AUTHORS b/AUTHORS
index dc3e8b3..76b17b5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -35,7 +35,7 @@ Noriaki Yamazaki, Ben Klopfenstein, Vic Lee, Christian Beier,
Alexander Dorokhine, Corentin Chary, Wouter Van Meir, George Kiagiadakis,
Joel Martin, Gernot Tenchio, William Roberts, Cristian Rodríguez,
George Fleury, Kan-Ru Chen, Steve Guo, Luca Stauble, Peter Watkins,
-Kyle J. McKay, Mateus Cesar Groess and D. R. Commander.
+Kyle J. McKay, Mateus Cesar Groess, Philip Van Hoof and D. R. Commander.
Probably I forgot quite a few people sending a patch here and there, which
really made a difference. Without those, some obscure bugs still would
diff --git a/client_examples/SDLvncviewer.c b/client_examples/SDLvncviewer.c
index 5fa8f2c..8fe6f57 100644
--- a/client_examples/SDLvncviewer.c
+++ b/client_examples/SDLvncviewer.c
@@ -78,7 +78,9 @@ static rfbBool resize(rfbClient* client) {
static rfbKeySym SDL_key2rfbKeySym(SDL_KeyboardEvent* e) {
rfbKeySym k = 0;
- switch(e->keysym.sym) {
+ SDLKey sym = e->keysym.sym;
+
+ switch (sym) {
case SDLK_BACKSPACE: k = XK_BackSpace; break;
case SDLK_TAB: k = XK_Tab; break;
case SDLK_CLEAR: k = XK_Clear; break;
@@ -139,10 +141,9 @@ static rfbKeySym SDL_key2rfbKeySym(SDL_KeyboardEvent* e) {
case SDLK_LALT: k = XK_Alt_L; break;
case SDLK_RMETA: k = XK_Meta_R; break;
case SDLK_LMETA: k = XK_Meta_L; break;
+ case SDLK_LSUPER: k = XK_Super_L; break;
+ case SDLK_RSUPER: k = XK_Super_R; break;
#if 0
- /* TODO: find out keysyms */
- case SDLK_LSUPER: k = XK_LSuper; break; /* left "windows" key */
- case SDLK_RSUPER: k = XK_RSuper; break; /* right "windows" key */
case SDLK_COMPOSE: k = XK_Compose; break;
#endif
case SDLK_MODE: k = XK_Mode_switch; break;
@@ -152,16 +153,21 @@ static rfbKeySym SDL_key2rfbKeySym(SDL_KeyboardEvent* e) {
case SDLK_BREAK: k = XK_Break; break;
default: break;
}
- if (k == 0 && e->keysym.sym >= SDLK_a && e->keysym.sym <= SDLK_z) {
- k = XK_a + e->keysym.sym - SDLK_a;
- if (e->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT))
- k &= ~0x20;
+ /* both SDL and X11 keysyms match ASCII in the range 0x01-0x7f */
+ if (k == 0 && sym > 0x0 && sym < 0x100) {
+ k = sym;
+ if (e->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) {
+ if (k >= '1' && k <= '9')
+ k &= ~0x10;
+ else if (k >= 'a' && k <= 'f')
+ k &= ~0x20;
+ }
}
if (k == 0) {
if (e->keysym.unicode < 0x100)
k = e->keysym.unicode;
else
- rfbClientLog("Unknown keysym: %d\n",e->keysym.sym);
+ rfbClientLog("Unknown keysym: %d\n", sym);
}
return k;
@@ -518,6 +524,7 @@ int main(int argc,char** argv) {
cl->HandleTextChat=text_chat;
cl->GotXCutText = got_selection;
cl->listenPort = LISTEN_PORT_OFFSET;
+ cl->listen6Port = LISTEN_PORT_OFFSET;
if(!rfbInitClient(cl,&argc,argv))
{
cl = NULL; /* rfbInitClient has already freed the client struct */
diff --git a/configure.ac b/configure.ac
index ddfe5e0..4e9c889 100644
--- a/configure.ac
+++ b/configure.ac
@@ -822,21 +822,21 @@ if test "x$with_gcrypt" != "xno"; then
fi
# Checks for GnuTLS
-AH_TEMPLATE(WITH_CLIENT_TLS, [Enable support for gnutls in libvncclient])
+AH_TEMPLATE(HAVE_GNUTLS, [GnuTLS library present])
AC_ARG_WITH(gnutls,
-[ --without-gnutls disable support for gnutls],,)
-AC_ARG_WITH(client-tls,
-[ --without-client-tls disable support for gnutls in libvncclient],,)
+[ --without-gnutls disable support for gnutls]
+[ --with-gnutls=DIR use gnutls include/library files in DIR],,)
if test "x$with_gnutls" != "xno"; then
- PKG_CHECK_MODULES(GNUTLS, gnutls >= 2.4.0, , with_client_tls=no)
+ PKG_CHECK_MODULES(GNUTLS, gnutls >= 2.4.0)
CFLAGS="$CFLAGS $GNUTLS_CFLAGS"
LIBS="$LIBS $GNUTLS_LIBS"
- if test "x$with_client_tls" != "xno"; then
- AC_DEFINE(WITH_CLIENT_TLS)
- fi
fi
AM_CONDITIONAL(HAVE_GNUTLS, test ! -z "$GNUTLS_LIBS")
+if test ! -z "$GNUTLS_LIBS" ; then
+ AC_DEFINE(HAVE_GNUTLS)
+fi
+
# warn if neither GnuTLS nor OpenSSL are available
if test -z "$SSL_LIBS" -a -z "$GNUTLS_LIBS"; then
@@ -905,7 +905,6 @@ AH_TEMPLATE(NEED_INADDR_T, [Need a typedef for in_addr_t])
if test $inaddrt = no ; then
AC_DEFINE(NEED_INADDR_T)
fi
-
# Checks for library functions.
AC_FUNC_MEMCMP
AC_FUNC_STAT
diff --git a/libvncclient/Makefile.am b/libvncclient/Makefile.am
index 5dec749..502b73d 100644
--- a/libvncclient/Makefile.am
+++ b/libvncclient/Makefile.am
@@ -1,6 +1,20 @@
INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/common
-libvncclient_la_SOURCES=cursor.c listen.c rfbproto.c sockets.c vncviewer.c ../common/minilzo.c tls.c
+if HAVE_GNUTLS
+TLSSRCS = tls_gnutls.c
+TLSLIBS = @GNUTLS_LIBS@
+else
+if HAVE_LIBSSL
+TLSSRCS = tls_openssl.c
+TLSLIBS = @SSL_LIBS@ @CRYPT_LIBS@
+else
+TLSSRCS = tls_none.c
+endif
+endif
+
+
+libvncclient_la_SOURCES=cursor.c listen.c rfbproto.c sockets.c vncviewer.c ../common/minilzo.c $(TLSSRCS)
+libvncclient_la_LIBADD=$(TLSLIBS)
noinst_HEADERS=../common/lzodefs.h ../common/lzoconf.h ../common/minilzo.h tls.h
diff --git a/libvncclient/listen.c b/libvncclient/listen.c
index 2e9fafb..c91ad6e 100644
--- a/libvncclient/listen.c
+++ b/libvncclient/listen.c
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2011-2012 Christian Beier <dontmind@freeshell.org>
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
@@ -50,7 +51,7 @@ listenForIncomingConnections(rfbClient* client)
rfbClientErr("listenForIncomingConnections on MinGW32 NOT IMPLEMENTED\n");
return;
#else
- int listenSocket;
+ int listenSocket, listen6Socket = -1;
fd_set fds;
client->listenSpecified = TRUE;
@@ -65,8 +66,24 @@ listenForIncomingConnections(rfbClient* client)
rfbClientLog("%s -listen: Command line errors are not reported until "
"a connection comes in.\n", client->programName);
- while (TRUE) {
+#ifdef LIBVNCSERVER_IPv6 /* only try that if we're IPv6-capable, otherwise we may try to bind to the same port which would make all that listening fail */
+ /* only do IPv6 listen of listen6Port is set */
+ if (client->listen6Port > 0)
+ {
+ listen6Socket = ListenAtTcpPortAndAddress(client->listen6Port, client->listen6Address);
+
+ if (listen6Socket < 0)
+ return;
+
+ rfbClientLog("%s -listen: Listening on IPV6 port %d\n",
+ client->programName,client->listenPort);
+ rfbClientLog("%s -listen: Command line errors are not reported until "
+ "a connection comes in.\n", client->programName);
+ }
+#endif
+ while (TRUE) {
+ int r;
/* reap any zombies */
int status, pid;
while ((pid= wait3(&status, WNOHANG, (struct rusage *)0))>0);
@@ -75,12 +92,19 @@ listenForIncomingConnections(rfbClient* client)
FD_ZERO(&fds);
- FD_SET(listenSocket, &fds);
+ if(listenSocket >= 0)
+ FD_SET(listenSocket, &fds);
+ if(listen6Socket >= 0)
+ FD_SET(listen6Socket, &fds);
+
+ r = select(max(listenSocket, listen6Socket)+1, &fds, NULL, NULL, NULL);
- select(listenSocket+1, &fds, NULL, NULL, NULL);
+ if (r > 0) {
+ if (FD_ISSET(listenSocket, &fds))
+ client->sock = AcceptTcpConnection(client->listenSock);
+ else if (FD_ISSET(listen6Socket, &fds))
+ client->sock = AcceptTcpConnection(client->listen6Sock);
- if (FD_ISSET(listenSocket, &fds)) {
- client->sock = AcceptTcpConnection(listenSocket);
if (client->sock < 0)
return;
if (!SetNonBlocking(client->sock))
@@ -97,6 +121,7 @@ listenForIncomingConnections(rfbClient* client)
case 0:
/* child - return to caller */
close(listenSocket);
+ close(listen6Socket);
return;
default:
@@ -144,24 +169,54 @@ listenForIncomingConnectionsNoFork(rfbClient* client, int timeout)
"a connection comes in.\n", client->programName);
}
+#ifdef LIBVNCSERVER_IPv6 /* only try that if we're IPv6-capable, otherwise we may try to bind to the same port which would make all that listening fail */
+ /* only do IPv6 listen of listen6Port is set */
+ if (client->listen6Port > 0 && client->listen6Sock < 0)
+ {
+ client->listen6Sock = ListenAtTcpPortAndAddress(client->listen6Port, client->listen6Address);
+
+ if (client->listen6Sock < 0)
+ return -1;
+
+ rfbClientLog("%s -listennofork: Listening on IPV6 port %d\n",
+ client->programName,client->listenPort);
+ rfbClientLog("%s -listennofork: Command line errors are not reported until "
+ "a connection comes in.\n", client->programName);
+ }
+#endif
+
FD_ZERO(&fds);
- FD_SET(client->listenSock, &fds);
+ if(client->listenSock >= 0)
+ FD_SET(client->listenSock, &fds);
+ if(client->listen6Sock >= 0)
+ FD_SET(client->listen6Sock, &fds);
if (timeout < 0)
- r = select(client->listenSock+1, &fds, NULL, NULL, NULL);
+ r = select(max(client->listenSock, client->listen6Sock) +1, &fds, NULL, NULL, NULL);
else
- r = select(client->listenSock+1, &fds, NULL, NULL, &to);
+ r = select(max(client->listenSock, client->listen6Sock) +1, &fds, NULL, NULL, &to);
if (r > 0)
{
- client->sock = AcceptTcpConnection(client->listenSock);
+ if (FD_ISSET(client->listenSock, &fds))
+ client->sock = AcceptTcpConnection(client->listenSock);
+ else if (FD_ISSET(client->listen6Sock, &fds))
+ client->sock = AcceptTcpConnection(client->listen6Sock);
+
if (client->sock < 0)
return -1;
if (!SetNonBlocking(client->sock))
return -1;
- close(client->listenSock);
+ if(client->listenSock >= 0) {
+ close(client->listenSock);
+ client->listenSock = -1;
+ }
+ if(client->listen6Sock >= 0) {
+ close(client->listen6Sock);
+ client->listen6Sock = -1;
+ }
return r;
}
diff --git a/libvncclient/rfbproto.c b/libvncclient/rfbproto.c
index a7faab1..70fab19 100644
--- a/libvncclient/rfbproto.c
+++ b/libvncclient/rfbproto.c
@@ -1040,9 +1040,7 @@ InitialiseRFBConnection(rfbClient* client)
rfbProtocolVersionMsg pv;
int major,minor;
uint32_t authScheme;
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
uint32_t subAuthScheme;
-#endif
rfbClientInitMsg ci;
/* if the connection is immediately closed, don't report anything, so
@@ -1078,6 +1076,14 @@ InitialiseRFBConnection(rfbClient* client)
DefaultSupportedMessagesUltraVNC(client);
}
+ /* UltraVNC Single Click uses minor codes 14 and 16 for the server */
+ if (major==3 && (minor==14 || minor==16)) {
+ minor = minor - 10;
+ client->minor = minor;
+ rfbClientLog("UltraVNC Single Click server detected, enabling UltraVNC specific messages\n",pv);
+ DefaultSupportedMessagesUltraVNC(client);
+ }
+
/* TightVNC uses minor codes 5 for the server */
if (major==3 && minor==5) {
rfbClientLog("TightVNC server detected, enabling TightVNC specific messages\n",pv);
@@ -1146,10 +1152,6 @@ InitialiseRFBConnection(rfbClient* client)
break;
case rfbTLS:
-#ifndef LIBVNCSERVER_WITH_CLIENT_TLS
- rfbClientLog("TLS support was not compiled in\n");
- return FALSE;
-#else
if (!HandleAnonTLSAuth(client)) return FALSE;
/* After the TLS session is established, sub auth types are expected.
* Note that all following reading/writing are through the TLS session from here.
@@ -1179,15 +1181,10 @@ InitialiseRFBConnection(rfbClient* client)
(int)subAuthScheme);
return FALSE;
}
-#endif
break;
case rfbVeNCrypt:
-#ifndef LIBVNCSERVER_WITH_CLIENT_TLS
- rfbClientLog("TLS support was not compiled in\n");
- return FALSE;
-#else
if (!HandleVeNCryptAuth(client)) return FALSE;
switch (client->subAuthScheme) {
@@ -1213,7 +1210,7 @@ InitialiseRFBConnection(rfbClient* client)
client->subAuthScheme);
return FALSE;
}
-#endif
+
break;
default:
diff --git a/libvncclient/sockets.c b/libvncclient/sockets.c
index be9924a..76441f9 100644
--- a/libvncclient/sockets.c
+++ b/libvncclient/sockets.c
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2011-2012 Christian Beier <dontmind@freeshell.org>
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
@@ -135,15 +136,11 @@ ReadFromRFBServer(rfbClient* client, char *out, unsigned int n)
while (client->buffered < n) {
int i;
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
if (client->tlsSession) {
i = ReadFromTLS(client, client->buf + client->buffered, RFB_BUF_SIZE - client->buffered);
} else {
-#endif
i = read(client->sock, client->buf + client->buffered, RFB_BUF_SIZE - client->buffered);
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
}
-#endif
if (i <= 0) {
if (i < 0) {
#ifdef WIN32
@@ -177,15 +174,12 @@ ReadFromRFBServer(rfbClient* client, char *out, unsigned int n)
while (n > 0) {
int i;
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
if (client->tlsSession) {
i = ReadFromTLS(client, out, n);
} else {
-#endif
i = read(client->sock, out, n);
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
}
-#endif
+
if (i <= 0) {
if (i < 0) {
#ifdef WIN32
@@ -240,7 +234,6 @@ WriteToRFBServer(rfbClient* client, char *buf, int n)
if (client->serverPort==-1)
return TRUE; /* vncrec playing */
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
if (client->tlsSession) {
/* WriteToTLS() will guarantee either everything is written, or error/eof returns */
i = WriteToTLS(client, buf, n);
@@ -248,7 +241,6 @@ WriteToRFBServer(rfbClient* client, char *buf, int n)
return TRUE;
}
-#endif
while (i < n) {
j = write(client->sock, buf + i, (n - i));
@@ -494,8 +486,9 @@ int
ListenAtTcpPortAndAddress(int port, const char *address)
{
int sock;
- struct sockaddr_in addr;
int one = 1;
+#ifndef LIBVNCSERVER_IPv6
+ struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
@@ -527,6 +520,66 @@ ListenAtTcpPortAndAddress(int port, const char *address)
return -1;
}
+#else
+ int rv;
+ struct addrinfo hints, *servinfo, *p;
+ char port_str[8];
+
+ snprintf(port_str, 8, "%d", port);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE; /* fill in wildcard address if address == NULL */
+
+ if (!initSockets())
+ return -1;
+
+ if ((rv = getaddrinfo(address, port_str, &hints, &servinfo)) != 0) {
+ rfbClientErr("ListenAtTcpPortAndAddress: error in getaddrinfo: %s\n", gai_strerror(rv));
+ return -1;
+ }
+
+ /* loop through all the results and bind to the first we can */
+ for(p = servinfo; p != NULL; p = p->ai_next) {
+ if ((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
+ continue;
+ }
+
+#ifdef IPV6_V6ONLY
+ /* we have seperate IPv4 and IPv6 sockets since some OS's do not support dual binding */
+ if (p->ai_family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&one, sizeof(one)) < 0) {
+ rfbClientErr("ListenAtTcpPortAndAddress: error in setsockopt IPV6_V6ONLY: %s\n", strerror(errno));
+ close(sock);
+ freeaddrinfo(servinfo);
+ return -1;
+ }
+#endif
+
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) < 0) {
+ rfbClientErr("ListenAtTcpPortAndAddress: error in setsockopt SO_REUSEADDR: %s\n", strerror(errno));
+ close(sock);
+ freeaddrinfo(servinfo);
+ return -1;
+ }
+
+ if (bind(sock, p->ai_addr, p->ai_addrlen) < 0) {
+ close(sock);
+ continue;
+ }
+
+ break;
+ }
+
+ if (p == NULL) {
+ rfbClientErr("ListenAtTcpPortAndAddress: error in bind: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* all done with this structure now */
+ freeaddrinfo(servinfo);
+#endif
+
if (listen(sock, 5) < 0) {
rfbClientErr("ListenAtTcpPort: listen\n");
close(sock);
diff --git a/libvncclient/tls.c b/libvncclient/tls_gnutls.c
index 5d29362..3daa416 100644
--- a/libvncclient/tls.c
+++ b/libvncclient/tls_gnutls.c
@@ -17,6 +17,7 @@
* USA.
*/
+#include <gnutls/gnutls.h>
#include <rfb/rfbclient.h>
#include <errno.h>
#ifdef WIN32
@@ -29,7 +30,6 @@
#endif
#include "tls.h"
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
static const char *rfbTLSPriority = "NORMAL:+DHE-DSS:+RSA:+DHE-RSA:+SRP";
static const char *rfbAnonTLSPriority= "NORMAL:+ANON-DH";
@@ -135,21 +135,21 @@ InitializeTLSSession(rfbClient* client, rfbBool anonTLS)
if (client->tlsSession) return TRUE;
- if ((ret = gnutls_init(&client->tlsSession, GNUTLS_CLIENT)) < 0)
+ if ((ret = gnutls_init((gnutls_session_t*)&client->tlsSession, GNUTLS_CLIENT)) < 0)
{
rfbClientLog("Failed to initialized TLS session: %s.\n", gnutls_strerror(ret));
return FALSE;
}
- if ((ret = gnutls_priority_set_direct(client->tlsSession,
+ if ((ret = gnutls_priority_set_direct((gnutls_session_t)client->tlsSession,
anonTLS ? rfbAnonTLSPriority : rfbTLSPriority, &p)) < 0)
{
rfbClientLog("Warning: Failed to set TLS priority: %s (%s).\n", gnutls_strerror(ret), p);
}
- gnutls_transport_set_ptr(client->tlsSession, (gnutls_transport_ptr_t)client);
- gnutls_transport_set_push_function(client->tlsSession, PushTLS);
- gnutls_transport_set_pull_function(client->tlsSession, PullTLS);
+ gnutls_transport_set_ptr((gnutls_session_t)client->tlsSession, (gnutls_transport_ptr_t)client);
+ gnutls_transport_set_push_function((gnutls_session_t)client->tlsSession, PushTLS);
+ gnutls_transport_set_pull_function((gnutls_session_t)client->tlsSession, PullTLS);
rfbClientLog("TLS session initialized.\n");
@@ -163,7 +163,7 @@ SetTLSAnonCredential(rfbClient* client)
int ret;
if ((ret = gnutls_anon_allocate_client_credentials(&anonCred)) < 0 ||
- (ret = gnutls_credentials_set(client->tlsSession, GNUTLS_CRD_ANON, anonCred)) < 0)
+ (ret = gnutls_credentials_set((gnutls_session_t)client->tlsSession, GNUTLS_CRD_ANON, anonCred)) < 0)
{
FreeTLS(client);
rfbClientLog("Failed to create anonymous credentials: %s", gnutls_strerror(ret));
@@ -179,7 +179,7 @@ HandshakeTLS(rfbClient* client)
int timeout = 15;
int ret;
- while (timeout > 0 && (ret = gnutls_handshake(client->tlsSession)) < 0)
+ while (timeout > 0 && (ret = gnutls_handshake((gnutls_session_t)client->tlsSession)) < 0)
{
if (!gnutls_error_is_fatal(ret))
{
@@ -335,13 +335,10 @@ CreateX509CertCredential(rfbCredential *cred)
return x509_cred;
}
-#endif
rfbBool
HandleAnonTLSAuth(rfbClient* client)
{
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
-
if (!InitializeTLS() || !InitializeTLSSession(client, TRUE)) return FALSE;
if (!SetTLSAnonCredential(client)) return FALSE;
@@ -349,17 +346,11 @@ HandleAnonTLSAuth(rfbClient* client)
if (!HandshakeTLS(client)) return FALSE;
return TRUE;
-
-#else
- rfbClientLog("TLS is not supported.\n");
- return FALSE;
-#endif
}
rfbBool
HandleVeNCryptAuth(rfbClient* client)
{
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
uint8_t major, minor, status;
uint32_t authScheme;
rfbBool anonTLS;
@@ -447,7 +438,7 @@ HandleVeNCryptAuth(rfbClient* client)
}
else
{
- if ((ret = gnutls_credentials_set(client->tlsSession, GNUTLS_CRD_CERTIFICATE, x509_cred)) < 0)
+ if ((ret = gnutls_credentials_set((gnutls_session_t)client->tlsSession, GNUTLS_CRD_CERTIFICATE, x509_cred)) < 0)
{
rfbClientLog("Cannot set x509 credential: %s.\n", gnutls_strerror(ret));
FreeTLS(client);
@@ -463,20 +454,14 @@ HandleVeNCryptAuth(rfbClient* client)
* to do actual sub authentication.
*/
return TRUE;
-
-#else
- rfbClientLog("TLS is not supported.\n");
- return FALSE;
-#endif
}
int
ReadFromTLS(rfbClient* client, char *out, unsigned int n)
{
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
ssize_t ret;
- ret = gnutls_record_recv(client->tlsSession, out, n);
+ ret = gnutls_record_recv((gnutls_session_t)client->tlsSession, out, n);
if (ret >= 0) return ret;
if (ret == GNUTLS_E_REHANDSHAKE || ret == GNUTLS_E_AGAIN)
{
@@ -487,23 +472,17 @@ ReadFromTLS(rfbClient* client, char *out, unsigned int n)
errno = EINTR;
}
return -1;
-#else
- rfbClientLog("TLS is not supported.\n");
- errno = EINTR;
- return -1;
-#endif
}
int
WriteToTLS(rfbClient* client, char *buf, unsigned int n)
{
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
unsigned int offset = 0;
ssize_t ret;
while (offset < n)
{
- ret = gnutls_record_send(client->tlsSession, buf+offset, (size_t)(n-offset));
+ ret = gnutls_record_send((gnutls_session_t)client->tlsSession, buf+offset, (size_t)(n-offset));
if (ret == 0) continue;
if (ret < 0)
{
@@ -514,20 +493,13 @@ WriteToTLS(rfbClient* client, char *buf, unsigned int n)
offset += (unsigned int)ret;
}
return offset;
-#else
- rfbClientLog("TLS is not supported.\n");
- errno = EINTR;
- return -1;
-#endif
}
void FreeTLS(rfbClient* client)
{
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
if (client->tlsSession)
{
- gnutls_deinit(client->tlsSession);
+ gnutls_deinit((gnutls_session_t)client->tlsSession);
client->tlsSession = NULL;
}
-#endif
}
diff --git a/libvncclient/tls_none.c b/libvncclient/tls_none.c
new file mode 100644
index 0000000..91a9f93
--- /dev/null
+++ b/libvncclient/tls_none.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 Christian Beier.
+ *
+ * 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>
+#include <errno.h>
+#include "tls.h"
+
+rfbBool HandleAnonTLSAuth(rfbClient* client)
+{
+ rfbClientLog("TLS is not supported.\n");
+ return FALSE;
+}
+
+
+rfbBool HandleVeNCryptAuth(rfbClient* client)
+{
+ rfbClientLog("TLS is not supported.\n");
+ return FALSE;
+}
+
+
+int ReadFromTLS(rfbClient* client, char *out, unsigned int n)
+{
+ rfbClientLog("TLS is not supported.\n");
+ errno = EINTR;
+ return -1;
+}
+
+
+int WriteToTLS(rfbClient* client, char *buf, unsigned int n)
+{
+ rfbClientLog("TLS is not supported.\n");
+ errno = EINTR;
+ return -1;
+}
+
+
+void FreeTLS(rfbClient* client)
+{
+
+}
+
diff --git a/libvncclient/tls_openssl.c b/libvncclient/tls_openssl.c
new file mode 100644
index 0000000..b6df946
--- /dev/null
+++ b/libvncclient/tls_openssl.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2012 Philip Van Hoof <philip@codeminded.be>
+ * Copyright (C) 2009 Vic Lee.
+ *
+ * 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>
+#include <errno.h>
+
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+
+#include <pthread.h>
+
+#include "tls.h"
+
+static rfbBool rfbTLSInitialized = FALSE;
+static pthread_mutex_t *mutex_buf = NULL;
+
+struct CRYPTO_dynlock_value {
+ pthread_mutex_t mutex;
+};
+
+static void locking_function(int mode, int n, const char *file, int line)
+{
+ if (mode & CRYPTO_LOCK)
+ pthread_mutex_lock(&mutex_buf[n]);
+ else
+ pthread_mutex_unlock(&mutex_buf[n]);
+}
+
+static unsigned long id_function(void)
+{
+ return ((unsigned long) pthread_self());
+}
+
+static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line)
+{
+ struct CRYPTO_dynlock_value *value;
+
+ value = (struct CRYPTO_dynlock_value *)
+ malloc(sizeof(struct CRYPTO_dynlock_value));
+ if (!value)
+ goto err;
+ pthread_mutex_init(&value->mutex, NULL);
+
+ return value;
+
+err:
+ return (NULL);
+}
+
+static void dyn_lock_function (int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
+{
+ if (mode & CRYPTO_LOCK)
+ pthread_mutex_lock(&l->mutex);
+ else
+ pthread_mutex_unlock(&l->mutex);
+}
+
+
+static void
+dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line)
+{
+ pthread_mutex_destroy(&l->mutex);
+ free(l);
+}
+
+
+static int
+ssl_errno (SSL *ssl, int ret)
+{
+ switch (SSL_get_error (ssl, ret)) {
+ case SSL_ERROR_NONE:
+ return 0;
+ case SSL_ERROR_ZERO_RETURN:
+ /* this one does not map well at all */
+ //d(printf ("ssl_errno: SSL_ERROR_ZERO_RETURN\n"));
+ return EINVAL;
+ case SSL_ERROR_WANT_READ: /* non-fatal; retry */
+ case SSL_ERROR_WANT_WRITE: /* non-fatal; retry */
+ //d(printf ("ssl_errno: SSL_ERROR_WANT_[READ,WRITE]\n"));
+ return EAGAIN;
+ case SSL_ERROR_SYSCALL:
+ //d(printf ("ssl_errno: SSL_ERROR_SYSCALL\n"));
+ return EINTR;
+ case SSL_ERROR_SSL:
+ //d(printf ("ssl_errno: SSL_ERROR_SSL <-- very useful error...riiiiight\n"));
+ return EINTR;
+ default:
+ //d(printf ("ssl_errno: default error\n"));
+ return EINTR;
+ }
+}
+
+static rfbBool
+InitializeTLS(void)
+{
+ int i;
+
+ if (rfbTLSInitialized) return TRUE;
+
+ mutex_buf = malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
+ if (mutex_buf == NULL) {
+ rfbClientLog("Failed to initialized OpenSSL: memory.\n");
+ return (-1);
+ }
+
+ for (i = 0; i < CRYPTO_num_locks(); i++)
+ pthread_mutex_init(&mutex_buf[i], NULL);
+
+ CRYPTO_set_locking_callback(locking_function);
+ CRYPTO_set_id_callback(id_function);
+ CRYPTO_set_dynlock_create_callback(dyn_create_function);
+ CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
+ CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
+ SSL_load_error_strings();
+ SSLeay_add_ssl_algorithms();
+ RAND_load_file("/dev/urandom", 1024);
+
+ rfbClientLog("OpenSSL initialized.\n");
+ rfbTLSInitialized = TRUE;
+ return TRUE;
+}
+
+static int
+ssl_verify (int ok, X509_STORE_CTX *ctx)
+{
+ unsigned char md5sum[16], fingerprint[40], *f;
+ rfbClient *client;
+ char *prompt, *cert_str;
+ int err, i;
+ unsigned int md5len;
+ //char buf[257];
+ X509 *cert;
+ SSL *ssl;
+
+ if (ok)
+ return TRUE;
+
+ ssl = X509_STORE_CTX_get_ex_data (ctx, SSL_get_ex_data_X509_STORE_CTX_idx ());
+
+ client = SSL_CTX_get_app_data (ssl->ctx);
+
+ cert = X509_STORE_CTX_get_current_cert (ctx);
+ err = X509_STORE_CTX_get_error (ctx);
+
+ /* calculate the MD5 hash of the raw certificate */
+ md5len = sizeof (md5sum);
+ X509_digest (cert, EVP_md5 (), md5sum, &md5len);
+ for (i = 0, f = fingerprint; i < 16; i++, f += 3)
+ sprintf ((char *) f, "%.2x%c", md5sum[i], i != 15 ? ':' : '\0');
+
+#define GET_STRING(name) X509_NAME_oneline (name, buf, 256)
+
+ /* TODO: Don't just ignore certificate checks
+
+ fingerprint = key to check in db
+
+ GET_STRING (X509_get_issuer_name (cert));
+ GET_STRING (X509_get_subject_name (cert));
+ cert->valid (bool: GOOD or BAD) */
+
+ ok = TRUE;
+
+ return ok;
+}
+
+static int sock_read_ready(SSL *ssl, uint32_t ms)
+{
+ int r = 0;
+ fd_set fds;
+ struct timeval tv;
+
+ FD_ZERO(&fds);
+
+ FD_SET(SSL_get_fd(ssl), &fds);
+
+ tv.tv_sec = ms / 1000;
+ tv.tv_usec = (ms % 1000) * ms;
+
+ r = select (SSL_get_fd(ssl) + 1, &fds, NULL, NULL, &tv);
+
+ return r;
+}
+
+static int wait_for_data(SSL *ssl, int ret, int timeout)
+{
+ struct timeval tv;
+ fd_set fds;
+ int err;
+ int retval = 1;
+
+ err = SSL_get_error(ssl, ret);
+
+ switch(err)
+ {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ ret = sock_read_ready(ssl, timeout*1000);
+
+ if (ret == -1) {
+ retval = 2;
+ }
+
+ break;
+ default:
+ retval = 3;
+ break;
+ }
+
+ ERR_clear_error();
+
+ return retval;
+}
+
+static SSL *
+open_ssl_connection (rfbClient *client, int sockfd, rfbBool anonTLS)
+{
+ SSL_CTX *ssl_ctx = NULL;
+ SSL *ssl = NULL;
+ int n, finished = 0;
+ BIO *sbio;
+
+ ssl_ctx = SSL_CTX_new (SSLv23_client_method ());
+ SSL_CTX_set_default_verify_paths (ssl_ctx);
+ SSL_CTX_set_verify (ssl_ctx, SSL_VERIFY_NONE, &ssl_verify);
+ ssl = SSL_new (ssl_ctx);
+
+ /* TODO: finetune this list, take into account anonTLS bool */
+ SSL_set_cipher_list(ssl, "ALL");
+
+ SSL_set_fd (ssl, sockfd);
+ SSL_CTX_set_app_data (ssl_ctx, client);
+
+ do
+ {
+ n = SSL_connect(ssl);
+
+ if (n != 1)
+ {
+ if (wait_for_data(ssl, n, 1) != 1)
+ {
+ finished = 1;
+ if (ssl->ctx)
+ SSL_CTX_free (ssl->ctx);
+ SSL_free(ssl);
+ SSL_shutdown (ssl);
+
+ return NULL;
+ }
+ }
+ } while( n != 1 && finished != 1 );
+
+ return ssl;
+}
+
+
+static rfbBool
+InitializeTLSSession(rfbClient* client, rfbBool anonTLS)
+{
+ int ret;
+
+ if (client->tlsSession) return TRUE;
+
+ client->tlsSession = open_ssl_connection (client, client->sock, anonTLS);
+
+ if (!client->tlsSession)
+ return FALSE;
+
+ rfbClientLog("TLS session initialized.\n");
+
+ return TRUE;
+}
+
+static rfbBool
+SetTLSAnonCredential(rfbClient* client)
+{
+ rfbClientLog("TLS anonymous credential created.\n");
+ return TRUE;
+}
+
+static rfbBool
+HandshakeTLS(rfbClient* client)
+{
+ int timeout = 15;
+ int ret;
+
+return TRUE;
+
+ while (timeout > 0 && (ret = SSL_do_handshake(client->tlsSession)) < 0)
+ {
+ if (ret != -1)
+ {
+ rfbClientLog("TLS handshake blocking.\n");
+ sleep(1);
+ timeout--;
+ continue;
+ }
+ rfbClientLog("TLS handshake failed: -.\n");
+ FreeTLS(client);
+ return FALSE;
+ }
+
+ if (timeout <= 0)
+ {
+ rfbClientLog("TLS handshake timeout.\n");
+ FreeTLS(client);
+ return FALSE;
+ }
+
+ rfbClientLog("TLS handshake done.\n");
+ return TRUE;
+}
+
+/* VeNCrypt sub auth. 1 byte auth count, followed by count * 4 byte integers */
+static rfbBool
+ReadVeNCryptSecurityType(rfbClient* client, uint32_t *result)
+{
+ uint8_t count=0;
+ uint8_t loop=0;
+ uint8_t flag=0;
+ uint32_t tAuth[256], t;
+ char buf1[500],buf2[10];
+ uint32_t authScheme;
+
+ if (!ReadFromRFBServer(client, (char *)&count, 1)) return FALSE;
+
+ if (count==0)
+ {
+ rfbClientLog("List of security types is ZERO. Giving up.\n");
+ return FALSE;
+ }
+
+ if (count>sizeof(tAuth))
+ {
+ rfbClientLog("%d security types are too many; maximum is %d\n", count, sizeof(tAuth));
+ return FALSE;
+ }
+
+ rfbClientLog("We have %d security types to read\n", count);
+ authScheme=0;
+ /* now, we have a list of available security types to read ( uint8_t[] ) */
+ for (loop=0;loop<count;loop++)
+ {
+ if (!ReadFromRFBServer(client, (char *)&tAuth[loop], 4)) return FALSE;
+ t=rfbClientSwap32IfLE(tAuth[loop]);
+ rfbClientLog("%d) Received security type %d\n", loop, t);
+ if (flag) continue;
+ if (t==rfbVeNCryptTLSNone ||
+ t==rfbVeNCryptTLSVNC ||
+ t==rfbVeNCryptTLSPlain ||
+ t==rfbVeNCryptX509None ||
+ t==rfbVeNCryptX509VNC ||
+ t==rfbVeNCryptX509Plain)
+ {
+ flag++;
+ authScheme=t;
+ rfbClientLog("Selecting security type %d (%d/%d in the list)\n", authScheme, loop, count);
+ /* send back 4 bytes (in original byte order!) indicating which security type to use */
+ if (!WriteToRFBServer(client, (char *)&tAuth[loop], 4)) return FALSE;
+ }
+ tAuth[loop]=t;
+ }
+ if (authScheme==0)
+ {
+ memset(buf1, 0, sizeof(buf1));
+ for (loop=0;loop<count;loop++)
+ {
+ if (strlen(buf1)>=sizeof(buf1)-1) break;
+ snprintf(buf2, sizeof(buf2), (loop>0 ? ", %d" : "%d"), (int)tAuth[loop]);
+ strncat(buf1, buf2, sizeof(buf1)-strlen(buf1)-1);
+ }
+ rfbClientLog("Unknown VeNCrypt authentication scheme from VNC server: %s\n",
+ buf1);
+ return FALSE;
+ }
+ *result = authScheme;
+ return TRUE;
+}
+
+rfbBool
+HandleAnonTLSAuth(rfbClient* client)
+{
+ if (!InitializeTLS() || !InitializeTLSSession(client, TRUE)) return FALSE;
+
+ if (!SetTLSAnonCredential(client)) return FALSE;
+
+ if (!HandshakeTLS(client)) return FALSE;
+
+ return TRUE;
+}
+
+rfbBool
+HandleVeNCryptAuth(rfbClient* client)
+{
+ uint8_t major, minor, status;
+ uint32_t authScheme;
+ rfbBool anonTLS;
+// gnutls_certificate_credentials_t x509_cred = NULL;
+ int ret;
+
+ if (!InitializeTLS()) return FALSE;
+
+ /* Read VeNCrypt version */
+ if (!ReadFromRFBServer(client, (char *)&major, 1) ||
+ !ReadFromRFBServer(client, (char *)&minor, 1))
+ {
+ return FALSE;
+ }
+ rfbClientLog("Got VeNCrypt version %d.%d from server.\n", (int)major, (int)minor);
+
+ if (major != 0 && minor != 2)
+ {
+ rfbClientLog("Unsupported VeNCrypt version.\n");
+ return FALSE;
+ }
+
+ if (!WriteToRFBServer(client, (char *)&major, 1) ||
+ !WriteToRFBServer(client, (char *)&minor, 1) ||
+ !ReadFromRFBServer(client, (char *)&status, 1))
+ {
+ return FALSE;
+ }
+
+ if (status != 0)
+ {
+ rfbClientLog("Server refused VeNCrypt version %d.%d.\n", (int)major, (int)minor);
+ return FALSE;
+ }
+
+ if (!ReadVeNCryptSecurityType(client, &authScheme)) return FALSE;
+ if (!ReadFromRFBServer(client, (char *)&status, 1) || status != 1)
+ {
+ rfbClientLog("Server refused VeNCrypt authentication %d (%d).\n", authScheme, (int)status);
+ return FALSE;
+ }
+ client->subAuthScheme = authScheme;
+
+ /* Some VeNCrypt security types are anonymous TLS, others are X509 */
+ switch (authScheme)
+ {
+ case rfbVeNCryptTLSNone:
+ case rfbVeNCryptTLSVNC:
+ case rfbVeNCryptTLSPlain:
+ anonTLS = TRUE;
+ break;
+ default:
+ anonTLS = FALSE;
+ break;
+ }
+
+ /* Get X509 Credentials if it's not anonymous */
+ if (!anonTLS)
+ {
+ rfbCredential *cred;
+
+ if (!client->GetCredential)
+ {
+ rfbClientLog("GetCredential callback is not set.\n");
+ return FALSE;
+ }
+ cred = client->GetCredential(client, rfbCredentialTypeX509);
+ if (!cred)
+ {
+ rfbClientLog("Reading credential failed\n");
+ return FALSE;
+ }
+
+ /* TODO: don't just ignore this
+ x509_cred = CreateX509CertCredential(cred);
+ FreeX509Credential(cred);
+ if (!x509_cred) return FALSE; */
+ }
+
+ /* Start up the TLS session */
+ if (!InitializeTLSSession(client, anonTLS)) return FALSE;
+
+ if (anonTLS)
+ {
+ if (!SetTLSAnonCredential(client)) return FALSE;
+ }
+ else
+ {
+/* TODO: don't just ignore this
+ if ((ret = gnutls_credentials_set(client->tlsSession, GNUTLS_CRD_CERTIFICATE, x509_cred)) < 0)
+ {
+ rfbClientLog("Cannot set x509 credential: %s.\n", gnutls_strerror(ret));
+ FreeTLS(client); */
+ return FALSE;
+ // }
+ }
+
+ if (!HandshakeTLS(client)) return FALSE;
+
+ /* TODO: validate certificate */
+
+ /* We are done here. The caller should continue with client->subAuthScheme
+ * to do actual sub authentication.
+ */
+ return TRUE;
+}
+
+int
+ReadFromTLS(rfbClient* client, char *out, unsigned int n)
+{
+ ssize_t ret;
+
+ ret = SSL_read (client->tlsSession, out, n);
+
+ if (ret >= 0)
+ return ret;
+ else {
+ errno = ssl_errno (client->tlsSession, ret);
+
+ if (errno != EAGAIN) {
+ rfbClientLog("Error reading from TLS: -.\n");
+ }
+ }
+
+ return -1;
+}
+
+int
+WriteToTLS(rfbClient* client, char *buf, unsigned int n)
+{
+ unsigned int offset = 0;
+ ssize_t ret;
+
+ while (offset < n)
+ {
+
+ ret = SSL_write (client->tlsSession, buf + offset, (size_t)(n-offset));
+
+ if (ret < 0)
+ errno = ssl_errno (client->tlsSession, ret);
+
+ if (ret == 0) continue;
+ if (ret < 0)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) continue;
+ rfbClientLog("Error writing to TLS: -\n");
+ return -1;
+ }
+ offset += (unsigned int)ret;
+ }
+ return offset;
+}
+
+void FreeTLS(rfbClient* client)
+{
+ int i;
+
+ if (mutex_buf != NULL) {
+ CRYPTO_set_dynlock_create_callback(NULL);
+ CRYPTO_set_dynlock_lock_callback(NULL);
+ CRYPTO_set_dynlock_destroy_callback(NULL);
+
+ CRYPTO_set_locking_callback(NULL);
+ CRYPTO_set_id_callback(NULL);
+
+ for (i = 0; i < CRYPTO_num_locks(); i++)
+ pthread_mutex_destroy(&mutex_buf[i]);
+ free(mutex_buf);
+ mutex_buf = NULL;
+ }
+
+ SSL_free(client->tlsSession);
+}
+
diff --git a/libvncclient/vncviewer.c b/libvncclient/vncviewer.c
index 10b430f..4153c97 100644
--- a/libvncclient/vncviewer.c
+++ b/libvncclient/vncviewer.c
@@ -191,12 +191,12 @@ rfbClient* rfbGetClient(int bitsPerSample,int samplesPerPixel,
client->authScheme = 0;
client->subAuthScheme = 0;
client->GetCredential = NULL;
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
client->tlsSession = NULL;
-#endif
client->sock = -1;
client->listenSock = -1;
client->listenAddress = NULL;
+ client->listen6Sock = -1;
+ client->listen6Address = NULL;
client->clientAuthSchemes = NULL;
return client;
}
@@ -363,9 +363,8 @@ void rfbClientCleanup(rfbClient* client) {
#endif
#endif
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
FreeTLS(client);
-#endif
+
if (client->sock >= 0)
close(client->sock);
if (client->listenSock >= 0)
diff --git a/libvncserver/cargs.c b/libvncserver/cargs.c
index 2e973e8..b9eb02b 100644
--- a/libvncserver/cargs.c
+++ b/libvncserver/cargs.c
@@ -22,6 +22,9 @@ rfbUsage(void)
rfbProtocolExtension* extension;
fprintf(stderr, "-rfbport port TCP port for RFB protocol\n");
+#ifdef LIBVNCSERVER_IPv6
+ fprintf(stderr, "-rfbportv6 port TCP6 port for RFB protocol\n");
+#endif
fprintf(stderr, "-rfbwait time max time in ms to wait for RFB client\n");
fprintf(stderr, "-rfbauth passwd-file use authentication on RFB protocol\n"
" (use 'storepasswd' to create a password file)\n");
@@ -42,10 +45,17 @@ rfbUsage(void)
"instead)\n");
fprintf(stderr, "-httpdir dir-path enable http server using dir-path home\n");
fprintf(stderr, "-httpport portnum use portnum for http connection\n");
+#ifdef LIBVNCSERVER_IPv6
+ fprintf(stderr, "-httpportv6 portnum use portnum for IPv6 http connection\n");
+#endif
fprintf(stderr, "-enablehttpproxy enable http proxy support\n");
fprintf(stderr, "-progressive height enable progressive updating for slow links\n");
fprintf(stderr, "-listen ipaddr listen for connections only on network interface with\n");
fprintf(stderr, " addr ipaddr. '-listen localhost' and hostname work too.\n");
+#ifdef LIBVNCSERVER_IPv6
+ fprintf(stderr, "-listenv6 ipv6addr listen for IPv6 connections only on network interface with\n");
+ fprintf(stderr, " addr ipv6addr. '-listen localhost' and hostname work too.\n");
+#endif
for(extension=rfbGetExtensionIterator();extension;extension=extension->next)
if(extension->usage)
@@ -80,6 +90,14 @@ rfbProcessArguments(rfbScreenInfoPtr rfbScreen,int* argc, char *argv[])
return FALSE;
}
rfbScreen->port = atoi(argv[++i]);
+#ifdef LIBVNCSERVER_IPv6
+ } else if (strcmp(argv[i], "-rfbportv6") == 0) { /* -rfbportv6 port */
+ if (i + 1 >= *argc) {
+ rfbUsage();
+ return FALSE;
+ }
+ rfbScreen->ipv6port = atoi(argv[++i]);
+#endif
} else if (strcmp(argv[i], "-rfbwait") == 0) { /* -rfbwait ms */
if (i + 1 >= *argc) {
rfbUsage();
@@ -147,6 +165,14 @@ rfbProcessArguments(rfbScreenInfoPtr rfbScreen,int* argc, char *argv[])
return FALSE;
}
rfbScreen->httpPort = atoi(argv[++i]);
+#ifdef LIBVNCSERVER_IPv6
+ } else if (strcmp(argv[i], "-httpportv6") == 0) { /* -httpportv6 portnum */
+ if (i + 1 >= *argc) {
+ rfbUsage();
+ return FALSE;
+ }
+ rfbScreen->http6Port = atoi(argv[++i]);
+#endif
} else if (strcmp(argv[i], "-enablehttpproxy") == 0) {
rfbScreen->httpEnableProxyConnect = TRUE;
} else if (strcmp(argv[i], "-progressive") == 0) { /* -httpport portnum */
@@ -163,6 +189,14 @@ rfbProcessArguments(rfbScreenInfoPtr rfbScreen,int* argc, char *argv[])
if (! rfbStringToAddr(argv[++i], &(rfbScreen->listenInterface))) {
return FALSE;
}
+#ifdef LIBVNCSERVER_IPv6
+ } else if (strcmp(argv[i], "-listenv6") == 0) { /* -listenv6 ipv6addr */
+ if (i + 1 >= *argc) {
+ rfbUsage();
+ return FALSE;
+ }
+ rfbScreen->listen6Interface = argv[++i];
+#endif
#ifdef LIBVNCSERVER_WITH_WEBSOCKETS
} else if (strcmp(argv[i], "-sslkeyfile") == 0) { /* -sslkeyfile sslkeyfile */
if (i + 1 >= *argc) {
diff --git a/libvncserver/httpd.c b/libvncserver/httpd.c
index 3025aae..ed91e46 100644
--- a/libvncserver/httpd.c
+++ b/libvncserver/httpd.c
@@ -3,6 +3,7 @@
*/
/*
+ * Copyright (C) 2011-2012 Christian Beier <dontmind@freeshell.org>
* Copyright (C) 2002 RealVNC Ltd.
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
@@ -102,17 +103,27 @@ rfbHttpInitSockets(rfbScreenInfoPtr rfbScreen)
rfbScreen->httpPort = rfbScreen->port-100;
}
- rfbLog("Listening for HTTP connections on TCP port %d\n", rfbScreen->httpPort);
-
- rfbLog(" URL http://%s:%d\n",rfbScreen->thisHost,rfbScreen->httpPort);
-
if ((rfbScreen->httpListenSock =
rfbListenOnTCPPort(rfbScreen->httpPort, rfbScreen->listenInterface)) < 0) {
rfbLogPerror("ListenOnTCPPort");
return;
}
+ rfbLog("Listening for HTTP connections on TCP port %d\n", rfbScreen->httpPort);
+ rfbLog(" URL http://%s:%d\n",rfbScreen->thisHost,rfbScreen->httpPort);
+
+#ifdef LIBVNCSERVER_IPv6
+ if (rfbScreen->http6Port == 0) {
+ rfbScreen->http6Port = rfbScreen->ipv6port-100;
+ }
- /*AddEnabledDevice(httpListenSock);*/
+ if ((rfbScreen->httpListen6Sock
+ = rfbListenOnTCP6Port(rfbScreen->http6Port, rfbScreen->listen6Interface)) < 0) {
+ /* ListenOnTCP6Port has its own detailed error printout */
+ return;
+ }
+ rfbLog("Listening for HTTP connections on TCP6 port %d\n", rfbScreen->http6Port);
+ rfbLog(" URL http://%s:%d\n",rfbScreen->thisHost,rfbScreen->http6Port);
+#endif
}
void rfbHttpShutdownSockets(rfbScreenInfoPtr rfbScreen) {
@@ -121,6 +132,18 @@ void rfbHttpShutdownSockets(rfbScreenInfoPtr rfbScreen) {
FD_CLR(rfbScreen->httpSock,&rfbScreen->allFds);
rfbScreen->httpSock=-1;
}
+
+ if(rfbScreen->httpListenSock>-1) {
+ close(rfbScreen->httpListenSock);
+ FD_CLR(rfbScreen->httpListenSock,&rfbScreen->allFds);
+ rfbScreen->httpListenSock=-1;
+ }
+
+ if(rfbScreen->httpListen6Sock>-1) {
+ close(rfbScreen->httpListen6Sock);
+ FD_CLR(rfbScreen->httpListen6Sock,&rfbScreen->allFds);
+ rfbScreen->httpListen6Sock=-1;
+ }
}
/*
@@ -134,7 +157,11 @@ rfbHttpCheckFds(rfbScreenInfoPtr rfbScreen)
int nfds;
fd_set fds;
struct timeval tv;
+#ifdef LIBVNCSERVER_IPv6
+ struct sockaddr_storage addr;
+#else
struct sockaddr_in addr;
+#endif
socklen_t addrlen = sizeof(addr);
if (!rfbScreen->httpDir)
@@ -145,12 +172,15 @@ rfbHttpCheckFds(rfbScreenInfoPtr rfbScreen)
FD_ZERO(&fds);
FD_SET(rfbScreen->httpListenSock, &fds);
+ if (rfbScreen->httpListen6Sock >= 0) {
+ FD_SET(rfbScreen->httpListen6Sock, &fds);
+ }
if (rfbScreen->httpSock >= 0) {
FD_SET(rfbScreen->httpSock, &fds);
}
tv.tv_sec = 0;
tv.tv_usec = 0;
- nfds = select(max(rfbScreen->httpSock,rfbScreen->httpListenSock) + 1, &fds, NULL, NULL, &tv);
+ nfds = select(max(rfbScreen->httpListen6Sock, max(rfbScreen->httpSock,rfbScreen->httpListenSock)) + 1, &fds, NULL, NULL, &tv);
if (nfds == 0) {
return;
}
@@ -167,19 +197,36 @@ rfbHttpCheckFds(rfbScreenInfoPtr rfbScreen)
httpProcessInput(rfbScreen);
}
- if (FD_ISSET(rfbScreen->httpListenSock, &fds)) {
+ if (FD_ISSET(rfbScreen->httpListenSock, &fds) || FD_ISSET(rfbScreen->httpListen6Sock, &fds)) {
if (rfbScreen->httpSock >= 0) close(rfbScreen->httpSock);
- if ((rfbScreen->httpSock = accept(rfbScreen->httpListenSock,
- (struct sockaddr *)&addr, &addrlen)) < 0) {
- rfbLogPerror("httpCheckFds: accept");
- return;
+ if(FD_ISSET(rfbScreen->httpListenSock, &fds)) {
+ if ((rfbScreen->httpSock = accept(rfbScreen->httpListenSock, (struct sockaddr *)&addr, &addrlen)) < 0) {
+ rfbLogPerror("httpCheckFds: accept");
+ return;
+ }
}
+ else if(FD_ISSET(rfbScreen->httpListen6Sock, &fds)) {
+ if ((rfbScreen->httpSock = accept(rfbScreen->httpListen6Sock, (struct sockaddr *)&addr, &addrlen)) < 0) {
+ rfbLogPerror("httpCheckFds: accept");
+ return;
+ }
+ }
+
#ifdef USE_LIBWRAP
- if(!hosts_ctl("vnc",STRING_UNKNOWN,inet_ntoa(addr.sin_addr),
+ char host[1024];
+#ifdef LIBVNCSERVER_IPv6
+ if(getnameinfo((struct sockaddr*)&addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0) {
+ rfbLogPerror("httpCheckFds: error in getnameinfo");
+ host[0] = '\0';
+ }
+#else
+ memcpy(host, inet_ntoa(addr.sin_addr), sizeof(host));
+#endif
+ if(!hosts_ctl("vnc",STRING_UNKNOWN, host,
STRING_UNKNOWN)) {
rfbLog("Rejected HTTP connection from client %s\n",
- inet_ntoa(addr.sin_addr));
+ host);
close(rfbScreen->httpSock);
rfbScreen->httpSock=-1;
return;
@@ -212,7 +259,11 @@ static rfbClientRec cl;
static void
httpProcessInput(rfbScreenInfoPtr rfbScreen)
{
+#ifdef LIBVNCSERVER_IPv6
+ struct sockaddr_storage addr;
+#else
struct sockaddr_in addr;
+#endif
socklen_t addrlen = sizeof(addr);
char fullFname[512];
char params[1024];
@@ -335,8 +386,16 @@ httpProcessInput(rfbScreenInfoPtr rfbScreen)
getpeername(rfbScreen->httpSock, (struct sockaddr *)&addr, &addrlen);
+#ifdef LIBVNCSERVER_IPv6
+ char host[1024];
+ if(getnameinfo((struct sockaddr*)&addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0) {
+ rfbLogPerror("httpProcessInput: error in getnameinfo");
+ }
+ rfbLog("httpd: get '%s' for %s\n", fname+1, host);
+#else
rfbLog("httpd: get '%s' for %s\n", fname+1,
inet_ntoa(addr.sin_addr));
+#endif
/* Extract parameters from the URL string if necessary */
@@ -562,7 +621,8 @@ parseParams(const char *request, char *result, int max_bytes)
/*
* Check if the string consists only of alphanumeric characters, '+'
- * signs, underscores, and dots. Replace all '+' signs with spaces.
+ * signs, underscores, dots, colons and square brackets.
+ * Replace all '+' signs with spaces.
*/
static rfbBool
@@ -571,7 +631,8 @@ validateString(char *str)
char *ptr;
for (ptr = str; *ptr != '\0'; ptr++) {
- if (!isalnum(*ptr) && *ptr != '_' && *ptr != '.') {
+ if (!isalnum(*ptr) && *ptr != '_' && *ptr != '.'
+ && *ptr != ':' && *ptr != '[' && *ptr != ']' ) {
if (*ptr == '+') {
*ptr = ' ';
} else {
diff --git a/libvncserver/main.c b/libvncserver/main.c
index 0edf994..4cb18ac 100644
--- a/libvncserver/main.c
+++ b/libvncserver/main.c
@@ -569,21 +569,37 @@ listenerRun(void *data)
{
rfbScreenInfoPtr screen=(rfbScreenInfoPtr)data;
int client_fd;
- struct sockaddr_in peer;
- rfbClientPtr cl;
+ struct sockaddr_storage peer;
+ rfbClientPtr cl = NULL;
socklen_t len;
-
- len = sizeof(peer);
+ fd_set listen_fds; /* temp file descriptor list for select() */
/* TODO: this thread wont die by restarting the server */
/* TODO: HTTP is not handled */
- while ((client_fd = accept(screen->listenSock,
- (struct sockaddr*)&peer, &len)) >= 0) {
- cl = rfbNewClient(screen,client_fd);
- len = sizeof(peer);
-
+ while (1) {
+ client_fd = -1;
+ FD_ZERO(&listen_fds);
+ if(screen->listenSock >= 0)
+ FD_SET(screen->listenSock, &listen_fds);
+ if(screen->listen6Sock >= 0)
+ FD_SET(screen->listen6Sock, &listen_fds);
+
+ if (select(screen->maxFd+1, &listen_fds, NULL, NULL, NULL) == -1) {
+ rfbLogPerror("listenerRun: error in select");
+ return NULL;
+ }
+
+ /* there is something on the listening sockets, handle new connections */
+ len = sizeof (peer);
+ if (FD_ISSET(screen->listenSock, &listen_fds))
+ client_fd = accept(screen->listenSock, (struct sockaddr*)&peer, &len);
+ else if (FD_ISSET(screen->listen6Sock, &listen_fds))
+ client_fd = accept(screen->listen6Sock, (struct sockaddr*)&peer, &len);
+
+ if(client_fd >= 0)
+ cl = rfbNewClient(screen,client_fd);
if (cl && !cl->onHold )
- rfbStartOnHoldClient(cl);
+ rfbStartOnHoldClient(cl);
}
return(NULL);
}
@@ -809,6 +825,7 @@ rfbScreenInfoPtr rfbGetScreen(int* argc,char** argv,
screen->clientHead=NULL;
screen->pointerClient=NULL;
screen->port=5900;
+ screen->ipv6port=5900;
screen->socketState=RFB_SOCKET_INIT;
screen->inetdInitDone = FALSE;
@@ -821,12 +838,15 @@ rfbScreenInfoPtr rfbGetScreen(int* argc,char** argv,
screen->maxFd=0;
screen->listenSock=-1;
+ screen->listen6Sock=-1;
screen->httpInitDone=FALSE;
screen->httpEnableProxyConnect=FALSE;
screen->httpPort=0;
+ screen->http6Port=0;
screen->httpDir=NULL;
screen->httpListenSock=-1;
+ screen->httpListen6Sock=-1;
screen->httpSock=-1;
screen->desktopName = "LibVNCServer";
diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c
index 12d1d72..e18f31d 100644
--- a/libvncserver/rfbserver.c
+++ b/libvncserver/rfbserver.c
@@ -51,6 +51,7 @@
#ifdef LIBVNCSERVER_HAVE_NETINET_IN_H
#include <netinet/in.h>
#include <netinet/tcp.h>
+#include <netdb.h>
#include <arpa/inet.h>
#endif
#endif
@@ -286,8 +287,12 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen,
rfbProtocolVersionMsg pv;
rfbClientIteratorPtr iterator;
rfbClientPtr cl,cl_;
+#ifdef LIBVNCSERVER_IPv6
+ struct sockaddr_storage addr;
+#else
struct sockaddr_in addr;
- socklen_t addrlen = sizeof(struct sockaddr_in);
+#endif
+ socklen_t addrlen = sizeof(addr);
rfbProtocolExtension* extension;
cl = (rfbClientPtr)calloc(sizeof(rfbClientRec),1);
@@ -310,7 +315,17 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen,
int one=1;
getpeername(sock, (struct sockaddr *)&addr, &addrlen);
+#ifdef LIBVNCSERVER_IPv6
+ char host[1024];
+ if(getnameinfo((struct sockaddr*)&addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0) {
+ rfbLogPerror("rfbNewClient: error in getnameinfo");
+ cl->host = strdup("");
+ }
+ else
+ cl->host = strdup(host);
+#else
cl->host = strdup(inet_ntoa(addr.sin_addr));
+#endif
rfbLog(" other clients:\n");
iterator = rfbGetClientIterator(rfbScreen);
diff --git a/libvncserver/sockets.c b/libvncserver/sockets.c
index 415f712..84c9c98 100644
--- a/libvncserver/sockets.c
+++ b/libvncserver/sockets.c
@@ -19,6 +19,7 @@
*/
/*
+ * Copyright (C) 2011-2012 Christian Beier <dontmind@freeshell.org>
* Copyright (C) 2005 Rohit Kumar, Johannes E. Schindelin
* OSXvnc Copyright (C) 2001 Dan McGuirk <mcguirk@incompleteness.net>.
* Original Xvnc code Copyright (C) 1999 AT&T Laboratories Cambridge.
@@ -137,6 +138,8 @@ rfbInitSockets(rfbScreenInfoPtr rfbScreen)
if(rfbScreen->autoPort) {
int i;
+ FD_ZERO(&(rfbScreen->allFds));
+
rfbLog("Autoprobing TCP port \n");
for (i = 5900; i < 6000; i++) {
if ((rfbScreen->listenSock = rfbListenOnTCPPort(i, iface)) >= 0) {
@@ -150,22 +153,52 @@ rfbInitSockets(rfbScreenInfoPtr rfbScreen)
return;
}
- rfbLog("Autoprobing selected port %d\n", rfbScreen->port);
- FD_ZERO(&(rfbScreen->allFds));
+ rfbLog("Autoprobing selected TCP port %d\n", rfbScreen->port);
FD_SET(rfbScreen->listenSock, &(rfbScreen->allFds));
rfbScreen->maxFd = rfbScreen->listenSock;
+
+#ifdef LIBVNCSERVER_IPv6
+ rfbLog("Autoprobing TCP6 port \n");
+ for (i = 5900; i < 6000; i++) {
+ if ((rfbScreen->listen6Sock = rfbListenOnTCP6Port(i, rfbScreen->listen6Interface)) >= 0) {
+ rfbScreen->ipv6port = i;
+ break;
+ }
+ }
+
+ if (i >= 6000) {
+ rfbLogPerror("Failure autoprobing");
+ return;
+ }
+
+ rfbLog("Autoprobing selected TCP6 port %d\n", rfbScreen->ipv6port);
+ FD_SET(rfbScreen->listen6Sock, &(rfbScreen->allFds));
+ rfbScreen->maxFd = max((int)rfbScreen->listen6Sock,rfbScreen->maxFd);
+#endif
}
else if(rfbScreen->port>0) {
- rfbLog("Listening for VNC connections on TCP port %d\n", rfbScreen->port);
+ FD_ZERO(&(rfbScreen->allFds));
if ((rfbScreen->listenSock = rfbListenOnTCPPort(rfbScreen->port, iface)) < 0) {
rfbLogPerror("ListenOnTCPPort");
return;
}
-
- FD_ZERO(&(rfbScreen->allFds));
+ rfbLog("Listening for VNC connections on TCP port %d\n", rfbScreen->port);
+
FD_SET(rfbScreen->listenSock, &(rfbScreen->allFds));
rfbScreen->maxFd = rfbScreen->listenSock;
+
+#ifdef LIBVNCSERVER_IPv6
+ if ((rfbScreen->listen6Sock = rfbListenOnTCP6Port(rfbScreen->ipv6port, rfbScreen->listen6Interface)) < 0) {
+ /* ListenOnTCP6Port has its own detailed error printout */
+ return;
+ }
+ rfbLog("Listening for VNC connections on TCP6 port %d\n", rfbScreen->ipv6port);
+
+ FD_SET(rfbScreen->listen6Sock, &(rfbScreen->allFds));
+ rfbScreen->maxFd = max((int)rfbScreen->listen6Sock,rfbScreen->maxFd);
+#endif
+
}
if (rfbScreen->udpPort != 0) {
@@ -175,6 +208,8 @@ rfbInitSockets(rfbScreenInfoPtr rfbScreen)
rfbLogPerror("ListenOnUDPPort");
return;
}
+ rfbLog("Listening for VNC connections on TCP port %d\n", rfbScreen->port);
+
FD_SET(rfbScreen->udpSock, &(rfbScreen->allFds));
rfbScreen->maxFd = max((int)rfbScreen->udpSock,rfbScreen->maxFd);
}
@@ -199,6 +234,12 @@ void rfbShutdownSockets(rfbScreenInfoPtr rfbScreen)
rfbScreen->listenSock=-1;
}
+ if(rfbScreen->listen6Sock>-1) {
+ closesocket(rfbScreen->listen6Sock);
+ FD_CLR(rfbScreen->listen6Sock,&rfbScreen->allFds);
+ rfbScreen->listen6Sock=-1;
+ }
+
if(rfbScreen->udpSock>-1) {
closesocket(rfbScreen->udpSock);
FD_CLR(rfbScreen->udpSock,&rfbScreen->allFds);
@@ -270,6 +311,16 @@ rfbCheckFds(rfbScreenInfoPtr rfbScreen,long usec)
return result;
}
+ if (rfbScreen->listen6Sock != -1 && FD_ISSET(rfbScreen->listen6Sock, &fds)) {
+
+ if (!rfbProcessNewConnection(rfbScreen))
+ return -1;
+
+ FD_CLR(rfbScreen->listen6Sock, &fds);
+ if (--nfds == 0)
+ return result;
+ }
+
if ((rfbScreen->udpSock != -1) && FD_ISSET(rfbScreen->udpSock, &fds)) {
if(!rfbScreen->udpClient)
rfbNewUDPClient(rfbScreen);
@@ -330,10 +381,33 @@ rfbProcessNewConnection(rfbScreenInfoPtr rfbScreen)
{
const int one = 1;
int sock = -1;
+#ifdef LIBVNCSERVER_IPv6
+ struct sockaddr_storage addr;
+#else
struct sockaddr_in addr;
+#endif
socklen_t addrlen = sizeof(addr);
+ fd_set listen_fds;
+ int chosen_listen_sock = -1;
+
+ /* Do another select() call to find out which listen socket
+ has an incoming connection pending. We know that at least
+ one of them has, so this should not block for too long! */
+ FD_ZERO(&listen_fds);
+ if(rfbScreen->listenSock >= 0)
+ FD_SET(rfbScreen->listenSock, &listen_fds);
+ if(rfbScreen->listen6Sock >= 0)
+ FD_SET(rfbScreen->listen6Sock, &listen_fds);
+ if (select(rfbScreen->maxFd+1, &listen_fds, NULL, NULL, NULL) == -1) {
+ rfbLogPerror("rfbProcessNewConnection: error in select");
+ return FALSE;
+ }
+ if (FD_ISSET(rfbScreen->listenSock, &listen_fds))
+ chosen_listen_sock = rfbScreen->listenSock;
+ if (FD_ISSET(rfbScreen->listen6Sock, &listen_fds))
+ chosen_listen_sock = rfbScreen->listen6Sock;
- if ((sock = accept(rfbScreen->listenSock,
+ if ((sock = accept(chosen_listen_sock,
(struct sockaddr *)&addr, &addrlen)) < 0) {
rfbLogPerror("rfbCheckFds: accept");
return FALSE;
@@ -361,7 +435,15 @@ rfbProcessNewConnection(rfbScreenInfoPtr rfbScreen)
}
#endif
+#ifdef LIBVNCSERVER_IPv6
+ char host[1024];
+ if(getnameinfo((struct sockaddr*)&addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0) {
+ rfbLogPerror("rfbProcessNewConnection: error in getnameinfo");
+ }
+ rfbLog("Got connection from client %s\n", host);
+#else
rfbLog("Got connection from client %s\n", inet_ntoa(addr.sin_addr));
+#endif
rfbNewClient(rfbScreen,sock);
@@ -774,12 +856,128 @@ rfbListenOnTCPPort(int port,
return sock;
}
+
+int
+rfbListenOnTCP6Port(int port,
+ const char* iface)
+{
+#ifndef LIBVNCSERVER_IPv6
+ rfbLogPerror("This LibVNCServer does not have IPv6 support");
+ return -1;
+#else
+ int sock;
+ int one = 1;
+ int rv;
+ struct addrinfo hints, *servinfo, *p;
+ char port_str[8];
+
+ snprintf(port_str, 8, "%d", port);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE; /* fill in wildcard address if iface == NULL */
+
+ if ((rv = getaddrinfo(iface, port_str, &hints, &servinfo)) != 0) {
+ rfbErr("rfbListenOnTCP6Port error in getaddrinfo: %s\n", gai_strerror(rv));
+ return -1;
+ }
+
+ /* loop through all the results and bind to the first we can */
+ for(p = servinfo; p != NULL; p = p->ai_next) {
+ if ((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
+ continue;
+ }
+
+#ifdef IPV6_V6ONLY
+ /* we have seperate IPv4 and IPv6 sockets since some OS's do not support dual binding */
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&one, sizeof(one)) < 0) {
+ rfbLogPerror("rfbListenOnTCP6Port error in setsockopt IPV6_V6ONLY");
+ closesocket(sock);
+ freeaddrinfo(servinfo);
+ return -1;
+ }
+#endif
+
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) < 0) {
+ rfbLogPerror("rfbListenOnTCP6Port: error in setsockopt SO_REUSEADDR");
+ closesocket(sock);
+ freeaddrinfo(servinfo);
+ return -1;
+ }
+
+ if (bind(sock, p->ai_addr, p->ai_addrlen) < 0) {
+ closesocket(sock);
+ continue;
+ }
+
+ break;
+ }
+
+ if (p == NULL) {
+ rfbLogPerror("rfbListenOnTCP6Port: error in bind IPv6 socket");
+ freeaddrinfo(servinfo);
+ return -1;
+ }
+
+ /* all done with this structure now */
+ freeaddrinfo(servinfo);
+
+ if (listen(sock, 32) < 0) {
+ rfbLogPerror("rfbListenOnTCP6Port: error in listen on IPv6 socket");
+ closesocket(sock);
+ return -1;
+ }
+
+ return sock;
+#endif
+}
+
+
int
rfbConnectToTcpAddr(char *host,
int port)
{
- struct hostent *hp;
int sock;
+#ifdef LIBVNCSERVER_IPv6
+ struct addrinfo hints, *servinfo, *p;
+ int rv;
+ char port_str[8];
+
+ snprintf(port_str, 8, "%d", port);
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ if ((rv = getaddrinfo(host, port_str, &hints, &servinfo)) != 0) {
+ rfbErr("rfbConnectToTcpAddr: error in getaddrinfo: %s\n", gai_strerror(rv));
+ return -1;
+ }
+
+ /* loop through all the results and connect to the first we can */
+ for(p = servinfo; p != NULL; p = p->ai_next) {
+ if ((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
+ continue;
+
+ if (connect(sock, p->ai_addr, p->ai_addrlen) < 0) {
+ closesocket(sock);
+ continue;
+ }
+
+ break;
+ }
+
+ /* all failed */
+ if (p == NULL) {
+ rfbLogPerror("rfbConnectToTcoAddr: failed to connect\n");
+ sock = -1; /* set return value */
+ }
+
+ /* all done with this structure now */
+ freeaddrinfo(servinfo);
+#else
+ struct hostent *hp;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
@@ -803,7 +1001,7 @@ rfbConnectToTcpAddr(char *host,
closesocket(sock);
return -1;
}
-
+#endif
return sock;
}
diff --git a/rfb/rfb.h b/rfb/rfb.h
index 3f0dbc0..a265746 100644
--- a/rfb/rfb.h
+++ b/rfb/rfb.h
@@ -377,6 +377,14 @@ typedef struct _rfbScreenInfo
char *sslkeyfile;
char *sslcertfile;
#endif
+ int ipv6port; /**< The port to listen on when using IPv6. */
+ char* listen6Interface;
+ /* We have an additional IPv6 listen socket since there are systems that
+ don't support dual binding sockets under *any* circumstances, for
+ instance OpenBSD */
+ SOCKET listen6Sock;
+ int http6Port;
+ SOCKET httpListen6Sock;
} rfbScreenInfo, *rfbScreenInfoPtr;
@@ -743,6 +751,7 @@ extern int rfbCheckFds(rfbScreenInfoPtr rfbScreen,long usec);
extern int rfbConnect(rfbScreenInfoPtr rfbScreen, char* host, int port);
extern int rfbConnectToTcpAddr(char* host, int port);
extern int rfbListenOnTCPPort(int port, in_addr_t iface);
+extern int rfbListenOnTCP6Port(int port, const char* iface);
extern int rfbListenOnUDPPort(int port, in_addr_t iface);
extern int rfbStringToAddr(char* string,in_addr_t* addr);
extern rfbBool rfbSetNonBlocking(int sock);
diff --git a/rfb/rfbclient.h b/rfb/rfbclient.h
index 36ffe13..0ecc5e4 100644
--- a/rfb/rfbclient.h
+++ b/rfb/rfbclient.h
@@ -38,9 +38,6 @@
#include <unistd.h>
#include <rfb/rfbproto.h>
#include <rfb/keysym.h>
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
-#include <gnutls/gnutls.h>
-#endif
#define rfbClientSwap16IfLE(s) \
(*(char *)&client->endianTest ? ((((s) & 0xff) << 8) | (((s) >> 8) & 0xff)) : (s))
@@ -314,10 +311,8 @@ typedef struct _rfbClient {
/** The selected security types */
uint32_t authScheme, subAuthScheme;
-#ifdef LIBVNCSERVER_WITH_CLIENT_TLS
/** The TLS session for Anonymous TLS and VeNCrypt */
- gnutls_session_t tlsSession;
-#endif
+ void* tlsSession;
/** To support security types that requires user input (except VNC password
* authentication), for example VeNCrypt and MSLogon, this callback function
@@ -347,7 +342,10 @@ typedef struct _rfbClient {
FinishedFrameBufferUpdateProc FinishedFrameBufferUpdate;
char *listenAddress;
-
+ /* IPv6 listen socket, address and port*/
+ int listen6Sock;
+ char* listen6Address;
+ int listen6Port;
} rfbClient;
/* cursor.c */
diff --git a/webclients/index.vnc b/webclients/index.vnc
index 8254a70..0a92bd4 100644
--- a/webclients/index.vnc
+++ b/webclients/index.vnc
@@ -22,7 +22,15 @@ If the above Java applet does not work, you can also try the new JavaScript-only
<script language="JavaScript">
<!--
function start_novnc(){
- open("novnc/vnc_auto.html?host=" + document.location.hostname + "&port=$PORT&true_color=1");
+ var host = document.location.hostname;
+ // If there are at least two colons in there, it is likely an IPv6 address. Check for square brackets and add them if missing.
+ if(host.search(/^.*:.*:.*$/) != -1) {
+ if(host.charAt(0) != "[")
+ host = "[" + host;
+ if(host.charAt(host.length-1) != "]")
+ host = host + "]";
+ }
+ open("novnc/vnc_auto.html?host=" + host + "&port=$PORT&true_color=1");
}
-->
</script>
diff --git a/webclients/novnc/LICENSE.txt b/webclients/novnc/LICENSE.txt
index 755ace3..6a1131b 100644
--- a/webclients/novnc/LICENSE.txt
+++ b/webclients/novnc/LICENSE.txt
@@ -13,6 +13,8 @@ version 3 with the following exceptions (all LGPL-3 compatible):
include/des.js : Various BSD style licenses
+ include/jsunzip.js : zlib/libpng license
+
include/web-socket-js/ : New BSD license. Source code at
http://github.com/gimite/web-socket-js
diff --git a/webclients/novnc/README.md b/webclients/novnc/README.md
index 4672969..887c96c 100644
--- a/webclients/novnc/README.md
+++ b/webclients/novnc/README.md
@@ -3,20 +3,28 @@
### Description
-noVNC is a VNC client implemented using HTML5 technologies,
-specifically Canvas and WebSockets (supports 'wss://' encryption).
-noVNC is licensed under the
-[LGPLv3](http://www.gnu.org/licenses/lgpl.html).
+noVNC is a HTML5 VNC client that runs well in any modern browser
+including mobile browsers (iPhone/iPad and Android).
-Special thanks to [Sentry Data Systems](http://www.sentryds.com) for
-sponsoring ongoing development of this project (and for employing me).
+Notable commits, announcements and news are posted to
+@<a href="http://www.twitter.com/noVNC">noVNC</a>
There are many companies/projects that have integrated noVNC into
-their products including: [Sentry Data Systems](http://www.sentryds.com), [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/), [CloudSigma](http://www.cloudsigma.com/), [Zentyal (formerly eBox)](http://www.zentyal.org/), and [SlapOS](http://www.slapos.org). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links.
+their products including: [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/), [CloudSigma](http://www.cloudsigma.com/), [Zentyal (formerly eBox)](http://www.zentyal.org/), [SlapOS](http://www.slapos.org), [Intel MeshCentral](https://meshcentral.com), [Amahi](http://amahi.org), [Brightbox](http://brightbox.com/), [Foreman](http://theforeman.org) and [LibVNCServer](http://libvncserver.sourceforge.net). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links.
-Notable commits, announcements and news are posted to
-@<a href="http://www.twitter.com/noVNC">noVNC</a>
+### Features
+
+* Supports all modern browsers including mobile (iOS, Android)
+* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG
+* WebSocket SSL/TLS encryption (i.e. "wss://") support
+* 24-bit true color and 8 bit colour mapped
+* Supports desktop resize notification/pseudo-encoding
+* Local or remote cursor
+* Clipboard copy/paste
+* Clipping or scolling modes for large remote screens
+* Easy site integration and theming (3 example themes included)
+* Licensed under the [LGPLv3](http://www.gnu.org/licenses/lgpl.html)
### Screenshots
@@ -38,10 +46,8 @@ See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">h
a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in
WebSocket support.
-* Fast Javascript Engine: noVNC avoids using new Javascript
- functionality so it will run on older browsers, but decode and
- rendering happen in Javascript, so a slow Javascript engine will
- mean noVNC is painfully slow.
+* Fast Javascript Engine: this is not strictly a requirement, but
+ without a fast Javascript engine, noVNC might be painfully slow.
* I maintain a more detailed browser compatibility list <a
href="https://github.com/kanaka/noVNC/wiki/Browser-support">here</a>.
@@ -50,22 +56,9 @@ See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">h
### Server Requirements
Unless you are using a VNC server with support for WebSockets
-connections (only my [fork of libvncserver](http://github.com/kanaka/libvncserver)
-currently), you need to use a WebSockets to TCP socket proxy. There is
-a python proxy included ('websockify'). One advantage of using the
-proxy is that it has builtin support for SSL/TLS encryption (i.e.
-"wss://").
-
-There a few reasons why a proxy is required:
-
- 1. WebSockets is not a pure socket protocol. There is an initial HTTP
- like handshake to allow easy hand-off by web servers and allow
- some origin policy exchange. Also, each WebSockets frame begins
- with 0 ('\x00') and ends with 255 ('\xff').
-
- 2. Javascript itself does not have the ability to handle pure byte
- arrays. The python proxy encodes the data as base64 so that the
- Javascript client can decode the data as an integer array.
+connections (such as [x11vnc/libvncserver](http://libvncserver.sourceforge.net/)),
+you need to use a WebSockets to TCP socket proxy. There is
+a python proxy included ('websockify').
### Quick Start
@@ -91,3 +84,19 @@ There a few reasons why a proxy is required:
* [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems.
+### Authors/Contributors
+
+* noVNC : Joel Martin (github.com/kanaka)
+ * New UI and Icons : Chris Gordon
+ * Original Logo : Michael Sersen
+ * tight encoding : Michael Tinglof (Mercuri.ca)
+
+* Included libraries:
+ * web-socket-js : Hiroshi Ichikawa (github.com/gimite/web-socket-js)
+ * as3crypto : Henri Torgemane (code.google.com/p/as3crypto)
+ * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
+ * jsunzip : Erik Moller (github.com/operasoftware/jsunzip),
+ * tinflate : Joergen Ibsen (ibsensoftware.com)
+ * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
+
+
diff --git a/webclients/novnc/include/base.css b/webclients/novnc/include/base.css
index 0a62a1b..105984d 100644
--- a/webclients/novnc/include/base.css
+++ b/webclients/novnc/include/base.css
@@ -153,6 +153,7 @@ html {
}
#noVNC_controls {
+ display:none;
margin-top:77px;
right:12px;
position:fixed;
@@ -161,6 +162,23 @@ html {
right:15px;
}
+#noVNC_description {
+ display:none;
+ position:fixed;
+
+ margin-top:77px;
+ right:20px;
+ left:20px;
+ padding:15px;
+ color:#000;
+ background:#eee; /* default background for browsers without gradient support */
+
+ border:2px solid #E0E0E0;
+ -webkit-border-radius:10px;
+ -moz-border-radius:10px;
+ border-radius:10px;
+}
+
#noVNC_clipboard {
display:none;
margin-top:77px;
diff --git a/webclients/novnc/include/base64.js b/webclients/novnc/include/base64.js
index c68b33a..e9b3c52 100644
--- a/webclients/novnc/include/base64.js
+++ b/webclients/novnc/include/base64.js
@@ -116,7 +116,7 @@ decode: function (data, offset) {
padding = (data.charAt(i) === pad);
// Skip illegal characters and whitespace
if (c === -1) {
- console.error("Illegal character '" + data.charCodeAt(i) + "'");
+ console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
continue;
}
diff --git a/webclients/novnc/include/display.js b/webclients/novnc/include/display.js
index 2cf262d..f2ecdba 100644
--- a/webclients/novnc/include/display.js
+++ b/webclients/novnc/include/display.js
@@ -20,7 +20,7 @@ var that = {}, // Public API methods
c_forceCanvas = false,
// Predefine function variables (jslint)
- imageDataGet, rgbxImageData, cmapImageData,
+ imageDataGet, rgbImageData, bgrxImageData, cmapImageData,
setFillColor, rescale,
// The full frame buffer (logical canvas) size
@@ -183,13 +183,13 @@ rescale = function(factor) {
};
setFillColor = function(color) {
- var rgb, newStyle;
+ var bgr, newStyle;
if (conf.true_color) {
- rgb = color;
+ bgr = color;
} else {
- rgb = conf.colourMap[color[0]];
+ bgr = conf.colourMap[color[0]];
}
- newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
+ newStyle = "rgb(" + bgr[2] + "," + bgr[1] + "," + bgr[0] + ")";
if (newStyle !== c_prevStyle) {
c_ctx.fillStyle = newStyle;
c_prevStyle = newStyle;
@@ -386,10 +386,10 @@ that.getCleanDirtyReset = function() {
// Translate viewport coordinates to absolute coordinates
that.absX = function(x) {
return x + viewport.x;
-}
+};
that.absY = function(y) {
return y + viewport.y;
-}
+};
that.resize = function(width, height) {
@@ -430,7 +430,7 @@ that.copyImage = function(old_x, old_y, new_x, new_y, w, h) {
// Start updating a tile
that.startTile = function(x, y, width, height, color) {
- var data, rgb, red, green, blue, i;
+ var data, bgr, red, green, blue, i;
tile_x = x;
tile_y = y;
if ((width === 16) && (height === 16)) {
@@ -441,13 +441,13 @@ that.startTile = function(x, y, width, height, color) {
data = tile.data;
if (conf.prefer_js) {
if (conf.true_color) {
- rgb = color;
+ bgr = color;
} else {
- rgb = conf.colourMap[color[0]];
+ bgr = conf.colourMap[color[0]];
}
- red = rgb[0];
- green = rgb[1];
- blue = rgb[2];
+ red = bgr[2];
+ green = bgr[1];
+ blue = bgr[0];
for (i = 0; i < (width * height * 4); i+=4) {
data[i ] = red;
data[i + 1] = green;
@@ -461,18 +461,18 @@ that.startTile = function(x, y, width, height, color) {
// Update sub-rectangle of the current tile
that.subTile = function(x, y, w, h, color) {
- var data, p, rgb, red, green, blue, width, j, i, xend, yend;
+ var data, p, bgr, red, green, blue, width, j, i, xend, yend;
if (conf.prefer_js) {
data = tile.data;
width = tile.width;
if (conf.true_color) {
- rgb = color;
+ bgr = color;
} else {
- rgb = conf.colourMap[color[0]];
+ bgr = conf.colourMap[color[0]];
}
- red = rgb[0];
- green = rgb[1];
- blue = rgb[2];
+ red = bgr[2];
+ green = bgr[1];
+ blue = bgr[0];
xend = x + w;
yend = y + h;
for (j = y; j < yend; j += 1) {
@@ -492,12 +492,12 @@ that.subTile = function(x, y, w, h, color) {
// Draw the current tile to the screen
that.finishTile = function() {
if (conf.prefer_js) {
- c_ctx.putImageData(tile, tile_x - viewport.x, tile_y - viewport.y)
+ c_ctx.putImageData(tile, tile_x - viewport.x, tile_y - viewport.y);
}
// else: No-op, if not prefer_js then already done by setSubTile
};
-rgbxImageData = function(x, y, width, height, arr, offset) {
+rgbImageData = function(x, y, width, height, arr, offset) {
var img, i, j, data, v = viewport;
/*
if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
@@ -508,7 +508,7 @@ rgbxImageData = function(x, y, width, height, arr, offset) {
*/
img = c_ctx.createImageData(width, height);
data = img.data;
- for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
+ for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+3) {
data[i ] = arr[j ];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j + 2];
@@ -517,16 +517,36 @@ rgbxImageData = function(x, y, width, height, arr, offset) {
c_ctx.putImageData(img, x - v.x, y - v.y);
};
+bgrxImageData = function(x, y, width, height, arr, offset) {
+ var img, i, j, data, v = viewport;
+ /*
+ if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
+ (x - v.x + width < 0) || (y - v.y + height < 0)) {
+ // Skipping because outside of viewport
+ return;
+ }
+ */
+ img = c_ctx.createImageData(width, height);
+ data = img.data;
+ for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
+ data[i ] = arr[j + 2];
+ data[i + 1] = arr[j + 1];
+ data[i + 2] = arr[j ];
+ data[i + 3] = 255; // Set Alpha
+ }
+ c_ctx.putImageData(img, x - v.x, y - v.y);
+};
+
cmapImageData = function(x, y, width, height, arr, offset) {
- var img, i, j, data, rgb, cmap;
+ var img, i, j, data, bgr, cmap;
img = c_ctx.createImageData(width, height);
data = img.data;
cmap = conf.colourMap;
for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
- rgb = cmap[arr[j]];
- data[i ] = rgb[0];
- data[i + 1] = rgb[1];
- data[i + 2] = rgb[2];
+ bgr = cmap[arr[j]];
+ data[i ] = bgr[2];
+ data[i + 1] = bgr[1];
+ data[i + 2] = bgr[0];
data[i + 3] = 255; // Set Alpha
}
c_ctx.putImageData(img, x - viewport.x, y - viewport.y);
@@ -534,8 +554,17 @@ cmapImageData = function(x, y, width, height, arr, offset) {
that.blitImage = function(x, y, width, height, arr, offset) {
if (conf.true_color) {
- rgbxImageData(x, y, width, height, arr, offset);
+ bgrxImageData(x, y, width, height, arr, offset);
+ } else {
+ cmapImageData(x, y, width, height, arr, offset);
+ }
+};
+
+that.blitRgbImage = function(x, y, width, height, arr, offset) {
+ if (conf.true_color) {
+ rgbImageData(x, y, width, height, arr, offset);
} else {
+ // prolly wrong...
cmapImageData(x, y, width, height, arr, offset);
}
};
diff --git a/webclients/novnc/include/input.js b/webclients/novnc/include/input.js
index 3124d08..1dfe719 100644
--- a/webclients/novnc/include/input.js
+++ b/webclients/novnc/include/input.js
@@ -412,6 +412,26 @@ function onKeyUp(e) {
return false;
}
+function allKeysUp() {
+ Util.Debug(">> Keyboard.allKeysUp");
+ if (keyDownList.length > 0) {
+ Util.Info("Releasing pressed/down keys");
+ }
+ var i, keysym, fevt = null;
+ for (i = keyDownList.length-1; i >= 0; i--) {
+ fevt = keyDownList.splice(i, 1)[0];
+ keysym = fevt.keysym;
+ if (conf.onKeyPress && (keysym > 0)) {
+ Util.Debug("allKeysUp, keysym: " + keysym +
+ " (keyCode: " + fevt.keyCode +
+ ", which: " + fevt.which + ")");
+ conf.onKeyPress(keysym, 0, fevt);
+ }
+ }
+ Util.Debug("<< Keyboard.allKeysUp");
+ return;
+}
+
//
// Public API interface functions
//
@@ -424,6 +444,9 @@ that.grab = function() {
Util.addEvent(c, 'keyup', onKeyUp);
Util.addEvent(c, 'keypress', onKeyPress);
+ // Release (key up) if window loses focus
+ Util.addEvent(window, 'blur', allKeysUp);
+
//Util.Debug("<< Keyboard.grab");
};
@@ -434,6 +457,10 @@ that.ungrab = function() {
Util.removeEvent(c, 'keydown', onKeyDown);
Util.removeEvent(c, 'keyup', onKeyUp);
Util.removeEvent(c, 'keypress', onKeyPress);
+ Util.removeEvent(window, 'blur', allKeysUp);
+
+ // Release (key up) all keys that are in a down state
+ allKeysUp();
//Util.Debug(">> Keyboard.ungrab");
};
diff --git a/webclients/novnc/include/jsunzip.js b/webclients/novnc/include/jsunzip.js
new file mode 100755
index 0000000..f815218
--- /dev/null
+++ b/webclients/novnc/include/jsunzip.js
@@ -0,0 +1,668 @@
+/*
+ * JSUnzip
+ *
+ * Copyright (c) 2011 by Erik Moller
+ * All Rights Reserved
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+
+var tinf;
+
+function JSUnzip() {
+
+ this.getInt = function(offset, size) {
+ switch (size) {
+ case 4:
+ return (this.data.charCodeAt(offset + 3) & 0xff) << 24 |
+ (this.data.charCodeAt(offset + 2) & 0xff) << 16 |
+ (this.data.charCodeAt(offset + 1) & 0xff) << 8 |
+ (this.data.charCodeAt(offset + 0) & 0xff);
+ break;
+ case 2:
+ return (this.data.charCodeAt(offset + 1) & 0xff) << 8 |
+ (this.data.charCodeAt(offset + 0) & 0xff);
+ break;
+ default:
+ return this.data.charCodeAt(offset) & 0xff;
+ break;
+ }
+ };
+
+ this.getDOSDate = function(dosdate, dostime) {
+ var day = dosdate & 0x1f;
+ var month = ((dosdate >> 5) & 0xf) - 1;
+ var year = 1980 + ((dosdate >> 9) & 0x7f)
+ var second = (dostime & 0x1f) * 2;
+ var minute = (dostime >> 5) & 0x3f;
+ hour = (dostime >> 11) & 0x1f;
+ return new Date(year, month, day, hour, minute, second);
+ }
+
+ this.open = function(data) {
+ this.data = data;
+ this.files = [];
+
+ if (this.data.length < 22)
+ return { 'status' : false, 'error' : 'Invalid data' };
+ var endOfCentralDirectory = this.data.length - 22;
+ while (endOfCentralDirectory >= 0 && this.getInt(endOfCentralDirectory, 4) != 0x06054b50)
+ --endOfCentralDirectory;
+ if (endOfCentralDirectory < 0)
+ return { 'status' : false, 'error' : 'Invalid data' };
+ if (this.getInt(endOfCentralDirectory + 4, 2) != 0 || this.getInt(endOfCentralDirectory + 6, 2) != 0)
+ return { 'status' : false, 'error' : 'No multidisk support' };
+
+ var entriesInThisDisk = this.getInt(endOfCentralDirectory + 8, 2);
+ var centralDirectoryOffset = this.getInt(endOfCentralDirectory + 16, 4);
+ var globalCommentLength = this.getInt(endOfCentralDirectory + 20, 2);
+ this.comment = this.data.slice(endOfCentralDirectory + 22, endOfCentralDirectory + 22 + globalCommentLength);
+
+ var fileOffset = centralDirectoryOffset;
+
+ for (var i = 0; i < entriesInThisDisk; ++i) {
+ if (this.getInt(fileOffset + 0, 4) != 0x02014b50)
+ return { 'status' : false, 'error' : 'Invalid data' };
+ if (this.getInt(fileOffset + 6, 2) > 20)
+ return { 'status' : false, 'error' : 'Unsupported version' };
+ if (this.getInt(fileOffset + 8, 2) & 1)
+ return { 'status' : false, 'error' : 'Encryption not implemented' };
+
+ var compressionMethod = this.getInt(fileOffset + 10, 2);
+ if (compressionMethod != 0 && compressionMethod != 8)
+ return { 'status' : false, 'error' : 'Unsupported compression method' };
+
+ var lastModFileTime = this.getInt(fileOffset + 12, 2);
+ var lastModFileDate = this.getInt(fileOffset + 14, 2);
+ var lastModifiedDate = this.getDOSDate(lastModFileDate, lastModFileTime);
+
+ var crc = this.getInt(fileOffset + 16, 4);
+ // TODO: crc
+
+ var compressedSize = this.getInt(fileOffset + 20, 4);
+ var uncompressedSize = this.getInt(fileOffset + 24, 4);
+
+ var fileNameLength = this.getInt(fileOffset + 28, 2);
+ var extraFieldLength = this.getInt(fileOffset + 30, 2);
+ var fileCommentLength = this.getInt(fileOffset + 32, 2);
+
+ var relativeOffsetOfLocalHeader = this.getInt(fileOffset + 42, 4);
+
+ var fileName = this.data.slice(fileOffset + 46, fileOffset + 46 + fileNameLength);
+ var fileComment = this.data.slice(fileOffset + 46 + fileNameLength + extraFieldLength, fileOffset + 46 + fileNameLength + extraFieldLength + fileCommentLength);
+
+ if (this.getInt(relativeOffsetOfLocalHeader + 0, 4) != 0x04034b50)
+ return { 'status' : false, 'error' : 'Invalid data' };
+ var localFileNameLength = this.getInt(relativeOffsetOfLocalHeader + 26, 2);
+ var localExtraFieldLength = this.getInt(relativeOffsetOfLocalHeader + 28, 2);
+ var localFileContent = relativeOffsetOfLocalHeader + 30 + localFileNameLength + localExtraFieldLength;
+
+ this.files[fileName] =
+ {
+ 'fileComment' : fileComment,
+ 'compressionMethod' : compressionMethod,
+ 'compressedSize' : compressedSize,
+ 'uncompressedSize' : uncompressedSize,
+ 'localFileContent' : localFileContent,
+ 'lastModifiedDate' : lastModifiedDate
+ };
+
+ fileOffset += 46 + fileNameLength + extraFieldLength + fileCommentLength;
+ }
+ return { 'status' : true }
+ };
+
+
+ this.read = function(fileName) {
+ var fileInfo = this.files[fileName];
+ if (fileInfo) {
+ if (fileInfo.compressionMethod == 8) {
+ if (!tinf) {
+ tinf = new TINF();
+ tinf.init();
+ }
+ var result = tinf.uncompress(this.data, fileInfo.localFileContent);
+ if (result.status == tinf.OK)
+ return { 'status' : true, 'data' : result.data };
+ else
+ return { 'status' : false, 'error' : result.error };
+ } else {
+ return { 'status' : true, 'data' : this.data.slice(fileInfo.localFileContent, fileInfo.localFileContent + fileInfo.uncompressedSize) };
+ }
+ }
+ return { 'status' : false, 'error' : "File '" + fileName + "' doesn't exist in zip" };
+ };
+
+};
+
+
+
+/*
+ * tinflate - tiny inflate
+ *
+ * Copyright (c) 2003 by Joergen Ibsen / Jibz
+ * All Rights Reserved
+ *
+ * http://www.ibsensoftware.com/
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+
+/*
+ * tinflate javascript port by Erik Moller in May 2011.
+ * emoller@opera.com
+ *
+ * read_bits() patched by mike@imidio.com to allow
+ * reading more then 8 bits (needed in some zlib streams)
+ */
+
+"use strict";
+
+function TINF() {
+
+this.OK = 0;
+this.DATA_ERROR = (-3);
+this.WINDOW_SIZE = 32768;
+
+/* ------------------------------ *
+ * -- internal data structures -- *
+ * ------------------------------ */
+
+this.TREE = function() {
+ this.table = new Array(16); /* table of code length counts */
+ this.trans = new Array(288); /* code -> symbol translation table */
+};
+
+this.DATA = function(that) {
+ this.source = '';
+ this.sourceIndex = 0;
+ this.tag = 0;
+ this.bitcount = 0;
+
+ this.dest = [];
+
+ this.history = [];
+
+ this.ltree = new that.TREE(); /* dynamic length/symbol tree */
+ this.dtree = new that.TREE(); /* dynamic distance tree */
+};
+
+/* --------------------------------------------------- *
+ * -- uninitialized global data (static structures) -- *
+ * --------------------------------------------------- */
+
+this.sltree = new this.TREE(); /* fixed length/symbol tree */
+this.sdtree = new this.TREE(); /* fixed distance tree */
+
+/* extra bits and base tables for length codes */
+this.length_bits = new Array(30);
+this.length_base = new Array(30);
+
+/* extra bits and base tables for distance codes */
+this.dist_bits = new Array(30);
+this.dist_base = new Array(30);
+
+/* special ordering of code length codes */
+this.clcidx = [
+ 16, 17, 18, 0, 8, 7, 9, 6,
+ 10, 5, 11, 4, 12, 3, 13, 2,
+ 14, 1, 15
+];
+
+/* ----------------------- *
+ * -- utility functions -- *
+ * ----------------------- */
+
+/* build extra bits and base tables */
+this.build_bits_base = function(bits, base, delta, first)
+{
+ var i, sum;
+
+ /* build bits table */
+ for (i = 0; i < delta; ++i) bits[i] = 0;
+ for (i = 0; i < 30 - delta; ++i) bits[i + delta] = Math.floor(i / delta);
+
+ /* build base table */
+ for (sum = first, i = 0; i < 30; ++i)
+ {
+ base[i] = sum;
+ sum += 1 << bits[i];
+ }
+}
+
+/* build the fixed huffman trees */
+this.build_fixed_trees = function(lt, dt)
+{
+ var i;
+
+ /* build fixed length tree */
+ for (i = 0; i < 7; ++i) lt.table[i] = 0;
+
+ lt.table[7] = 24;
+ lt.table[8] = 152;
+ lt.table[9] = 112;
+
+ for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i;
+ for (i = 0; i < 144; ++i) lt.trans[24 + i] = i;
+ for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i;
+ for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i;
+
+ /* build fixed distance tree */
+ for (i = 0; i < 5; ++i) dt.table[i] = 0;
+
+ dt.table[5] = 32;
+
+ for (i = 0; i < 32; ++i) dt.trans[i] = i;
+}
+
+/* given an array of code lengths, build a tree */
+this.build_tree = function(t, lengths, loffset, num)
+{
+ var offs = new Array(16);
+ var i, sum;
+
+ /* clear code length count table */
+ for (i = 0; i < 16; ++i) t.table[i] = 0;
+
+ /* scan symbol lengths, and sum code length counts */
+ for (i = 0; i < num; ++i) t.table[lengths[loffset + i]]++;
+
+ t.table[0] = 0;
+
+ /* compute offset table for distribution sort */
+ for (sum = 0, i = 0; i < 16; ++i)
+ {
+ offs[i] = sum;
+ sum += t.table[i];
+ }
+
+ /* create code->symbol translation table (symbols sorted by code) */
+ for (i = 0; i < num; ++i)
+ {
+ if (lengths[loffset + i]) t.trans[offs[lengths[loffset + i]]++] = i;
+ }
+}
+
+/* ---------------------- *
+ * -- decode functions -- *
+ * ---------------------- */
+
+/* get one bit from source stream */
+this.getbit = function(d)
+{
+ var bit;
+
+ /* check if tag is empty */
+ if (!d.bitcount--)
+ {
+ /* load next tag */
+ d.tag = d.source[d.sourceIndex++] & 0xff;
+ d.bitcount = 7;
+ }
+
+ /* shift bit out of tag */
+ bit = d.tag & 0x01;
+ d.tag >>= 1;
+
+ return bit;
+}
+
+/* read a num bit value from a stream and add base */
+this.read_bits = function(d, num, base)
+{
+ if (!num)
+ return base;
+
+ var val = 0;
+ while (d.bitcount < 24) {
+ d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount;
+ d.bitcount += 8;
+ }
+ val = d.tag & (0xffff >> (16 - num));
+ d.tag >>= num;
+ d.bitcount -= num;
+ return val + base;
+}
+
+/* given a data stream and a tree, decode a symbol */
+this.decode_symbol = function(d, t)
+{
+ while (d.bitcount < 16) {
+ d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount;
+ d.bitcount += 8;
+ }
+
+ var sum = 0, cur = 0, len = 0;
+ do {
+ cur = 2 * cur + ((d.tag & (1 << len)) >> len);
+
+ ++len;
+
+ sum += t.table[len];
+ cur -= t.table[len];
+
+ } while (cur >= 0);
+
+ d.tag >>= len;
+ d.bitcount -= len;
+
+ return t.trans[sum + cur];
+}
+
+/* given a data stream, decode dynamic trees from it */
+this.decode_trees = function(d, lt, dt)
+{
+ var code_tree = new this.TREE();
+ var lengths = new Array(288+32);
+ var hlit, hdist, hclen;
+ var i, num, length;
+
+ /* get 5 bits HLIT (257-286) */
+ hlit = this.read_bits(d, 5, 257);
+
+ /* get 5 bits HDIST (1-32) */
+ hdist = this.read_bits(d, 5, 1);
+
+ /* get 4 bits HCLEN (4-19) */
+ hclen = this.read_bits(d, 4, 4);
+
+ for (i = 0; i < 19; ++i) lengths[i] = 0;
+
+ /* read code lengths for code length alphabet */
+ for (i = 0; i < hclen; ++i)
+ {
+ /* get 3 bits code length (0-7) */
+ var clen = this.read_bits(d, 3, 0);
+
+ lengths[this.clcidx[i]] = clen;
+ }
+
+ /* build code length tree */
+ this.build_tree(code_tree, lengths, 0, 19);
+
+ /* decode code lengths for the dynamic trees */
+ for (num = 0; num < hlit + hdist; )
+ {
+ var sym = this.decode_symbol(d, code_tree);
+
+ switch (sym)
+ {
+ case 16:
+ /* copy previous code length 3-6 times (read 2 bits) */
+ {
+ var prev = lengths[num - 1];
+ for (length = this.read_bits(d, 2, 3); length; --length)
+ {
+ lengths[num++] = prev;
+ }
+ }
+ break;
+ case 17:
+ /* repeat code length 0 for 3-10 times (read 3 bits) */
+ for (length = this.read_bits(d, 3, 3); length; --length)
+ {
+ lengths[num++] = 0;
+ }
+ break;
+ case 18:
+ /* repeat code length 0 for 11-138 times (read 7 bits) */
+ for (length = this.read_bits(d, 7, 11); length; --length)
+ {
+ lengths[num++] = 0;
+ }
+ break;
+ default:
+ /* values 0-15 represent the actual code lengths */
+ lengths[num++] = sym;
+ break;
+ }
+ }
+
+ /* build dynamic trees */
+ this.build_tree(lt, lengths, 0, hlit);
+ this.build_tree(dt, lengths, hlit, hdist);
+}
+
+/* ----------------------------- *
+ * -- block inflate functions -- *
+ * ----------------------------- */
+
+/* given a stream and two trees, inflate a block of data */
+this.inflate_block_data = function(d, lt, dt)
+{
+ // js optimization.
+ var ddest = d.dest;
+ var ddestlength = ddest.length;
+
+ while (1)
+ {
+ var sym = this.decode_symbol(d, lt);
+
+ /* check for end of block */
+ if (sym == 256)
+ {
+ return this.OK;
+ }
+
+ if (sym < 256)
+ {
+ ddest[ddestlength++] = sym; // ? String.fromCharCode(sym);
+ d.history.push(sym);
+ } else {
+
+ var length, dist, offs;
+ var i;
+
+ sym -= 257;
+
+ /* possibly get more bits from length code */
+ length = this.read_bits(d, this.length_bits[sym], this.length_base[sym]);
+
+ dist = this.decode_symbol(d, dt);
+
+ /* possibly get more bits from distance code */
+ offs = d.history.length - this.read_bits(d, this.dist_bits[dist], this.dist_base[dist]);
+
+ if (offs < 0)
+ throw ("Invalid zlib offset " + offs);
+
+ /* copy match */
+ for (i = offs; i < offs + length; ++i) {
+ //ddest[ddestlength++] = ddest[i];
+ ddest[ddestlength++] = d.history[i];
+ d.history.push(d.history[i]);
+ }
+ }
+ }
+}
+
+/* inflate an uncompressed block of data */
+this.inflate_uncompressed_block = function(d)
+{
+ var length, invlength;
+ var i;
+
+ if (d.bitcount > 7) {
+ var overflow = Math.floor(d.bitcount / 8);
+ d.sourceIndex -= overflow;
+ d.bitcount = 0;
+ d.tag = 0;
+ }
+
+ /* get length */
+ length = d.source[d.sourceIndex+1];
+ length = 256*length + d.source[d.sourceIndex];
+
+ /* get one's complement of length */
+ invlength = d.source[d.sourceIndex+3];
+ invlength = 256*invlength + d.source[d.sourceIndex+2];
+
+ /* check length */
+ if (length != (~invlength & 0x0000ffff)) return this.DATA_ERROR;
+
+ d.sourceIndex += 4;
+
+ /* copy block */
+ for (i = length; i; --i) {
+ d.history.push(d.source[d.sourceIndex]);
+ d.dest[d.dest.length] = d.source[d.sourceIndex++];
+ }
+
+ /* make sure we start next block on a byte boundary */
+ d.bitcount = 0;
+
+ return this.OK;
+}
+
+/* inflate a block of data compressed with fixed huffman trees */
+this.inflate_fixed_block = function(d)
+{
+ /* decode block using fixed trees */
+ return this.inflate_block_data(d, this.sltree, this.sdtree);
+}
+
+/* inflate a block of data compressed with dynamic huffman trees */
+this.inflate_dynamic_block = function(d)
+{
+ /* decode trees from stream */
+ this.decode_trees(d, d.ltree, d.dtree);
+
+ /* decode block using decoded trees */
+ return this.inflate_block_data(d, d.ltree, d.dtree);
+}
+
+/* ---------------------- *
+ * -- public functions -- *
+ * ---------------------- */
+
+/* initialize global (static) data */
+this.init = function()
+{
+ /* build fixed huffman trees */
+ this.build_fixed_trees(this.sltree, this.sdtree);
+
+ /* build extra bits and base tables */
+ this.build_bits_base(this.length_bits, this.length_base, 4, 3);
+ this.build_bits_base(this.dist_bits, this.dist_base, 2, 1);
+
+ /* fix a special case */
+ this.length_bits[28] = 0;
+ this.length_base[28] = 258;
+
+ this.reset();
+}
+
+this.reset = function()
+{
+ this.d = new this.DATA(this);
+ delete this.header;
+}
+
+/* inflate stream from source to dest */
+this.uncompress = function(source, offset)
+{
+
+ var d = this.d;
+ var bfinal;
+
+ /* initialise data */
+ d.source = source;
+ d.sourceIndex = offset;
+ d.bitcount = 0;
+
+ d.dest = [];
+
+ // Skip zlib header at start of stream
+ if (typeof this.header == 'undefined') {
+ this.header = this.read_bits(d, 16, 0);
+ /* byte 0: 0x78, 7 = 32k window size, 8 = deflate */
+ /* byte 1: check bits for header and other flags */
+ }
+
+ var blocks = 0;
+
+ do {
+
+ var btype;
+ var res;
+
+ /* read final block flag */
+ bfinal = this.getbit(d);
+
+ /* read block type (2 bits) */
+ btype = this.read_bits(d, 2, 0);
+
+ /* decompress block */
+ switch (btype)
+ {
+ case 0:
+ /* decompress uncompressed block */
+ res = this.inflate_uncompressed_block(d);
+ break;
+ case 1:
+ /* decompress block with fixed huffman trees */
+ res = this.inflate_fixed_block(d);
+ break;
+ case 2:
+ /* decompress block with dynamic huffman trees */
+ res = this.inflate_dynamic_block(d);
+ break;
+ default:
+ return { 'status' : this.DATA_ERROR };
+ }
+
+ if (res != this.OK) return { 'status' : this.DATA_ERROR };
+ blocks++;
+
+ } while (!bfinal && d.sourceIndex < d.source.length);
+
+ d.history = d.history.slice(-this.WINDOW_SIZE);
+
+ return { 'status' : this.OK, 'data' : d.dest };
+}
+
+};
diff --git a/webclients/novnc/include/rfb.js b/webclients/novnc/include/rfb.js
index b7aa3f6..75b9797 100644
--- a/webclients/novnc/include/rfb.js
+++ b/webclients/novnc/include/rfb.js
@@ -4,6 +4,9 @@
* Licensed under LGPL-3 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
+ *
+ * TIGHT decoder portion:
+ * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
*/
/*jslint white: false, browser: true, bitwise: false, plusplus: false */
@@ -23,7 +26,7 @@ var that = {}, // Public API methods
pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests,
keyEvent, pointerEvent, clientCutText,
- extract_data_uri, scan_tight_imgQ,
+ getTightCLength, extract_data_uri, scan_tight_imgQ,
keyPress, mouseButton, mouseMove,
checkEvents, // Overridable for testing
@@ -46,6 +49,7 @@ var that = {}, // Public API methods
// In preference order
encodings = [
['COPYRECT', 0x01 ],
+ ['TIGHT', 0x07 ],
['TIGHT_PNG', -260 ],
['HEXTILE', 0x05 ],
['RRE', 0x02 ],
@@ -54,10 +58,12 @@ var that = {}, // Public API methods
['Cursor', -239 ],
// Psuedo-encoding settings
- ['JPEG_quality_lo', -32 ],
+ //['JPEG_quality_lo', -32 ],
+ ['JPEG_quality_med', -26 ],
//['JPEG_quality_hi', -23 ],
- ['compress_lo', -255 ]
- //['compress_hi', -247 ]
+ //['compress_lo', -255 ],
+ ['compress_hi', -247 ],
+ ['last_rect', -224 ]
],
encHandlers = {},
@@ -87,7 +93,8 @@ var that = {}, // Public API methods
encoding : 0,
subencoding : -1,
background : null,
- imgQ : [] // TIGHT_PNG image queue
+ imgQ : [], // TIGHT_PNG image queue
+ zlibs : [] // TIGHT zlib streams
},
fb_Bpp = 4,
@@ -109,7 +116,8 @@ var that = {}, // Public API methods
fbu_rt_start : 0,
fbu_rt_total : 0,
- fbu_rt_cnt : 0
+ fbu_rt_cnt : 0,
+ pixels : 0
},
test_mode = false,
@@ -131,6 +139,7 @@ Util.conf_defaults(conf, that, defaults, [
['true_color', 'rw', 'bool', true, 'Request true color pixel data'],
['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'],
['shared', 'rw', 'bool', true, 'Request shared mode'],
+ ['view_only', 'rw', 'bool', false, 'Disable client mouse/keyboard'],
['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'],
['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
@@ -224,7 +233,10 @@ function constructor() {
fail("Got unexpected WebSockets connection");
}
});
- ws.on('close', function() {
+ ws.on('close', function(e) {
+ if (e.code) {
+ Util.Info("Close code: " + e.code + ", reason: " + e.reason + ", wasClean: " + e.wasClean);
+ }
if (rfb_state === 'disconnect') {
updateState('disconnected', 'VNC disconnected');
} else if (rfb_state === 'ProtocolVersion') {
@@ -266,14 +278,18 @@ function constructor() {
function connect() {
Util.Debug(">> RFB.connect");
-
- var uri = "";
- if (conf.encrypt) {
- uri = "wss://";
+ var uri;
+
+ if (typeof UsingSocketIO !== "undefined") {
+ uri = "http://" + rfb_host + ":" + rfb_port + "/" + rfb_path;
} else {
- uri = "ws://";
+ if (conf.encrypt) {
+ uri = "wss://";
+ } else {
+ uri = "ws://";
+ }
+ uri += rfb_host + ":" + rfb_port + "/" + rfb_path;
}
- uri += rfb_host + ":" + rfb_port + "/" + rfb_path;
Util.Info("connecting to " + uri);
ws.open(uri);
@@ -292,6 +308,7 @@ init_vars = function() {
FBU.lines = 0; // RAW
FBU.tiles = 0; // HEXTILE
FBU.imgQ = []; // TIGHT_PNG image queue
+ FBU.zlibs = []; // TIGHT zlib encoders
mouse_buttonMask = 0;
mouse_arr = [];
@@ -299,6 +316,12 @@ init_vars = function() {
for (i=0; i < encodings.length; i+=1) {
encStats[encodings[i][1]][0] = 0;
}
+
+ for (i=0; i < 4; i++) {
+ //FBU.zlibs[i] = new InflateStream();
+ FBU.zlibs[i] = new TINF();
+ FBU.zlibs[i].init();
+ }
};
// Print statistics
@@ -565,6 +588,9 @@ checkEvents = function() {
keyPress = function(keysym, down) {
var arr;
+
+ if (conf.view_only) { return; } // View only, skip keyboard events
+
arr = keyEvent(keysym, down);
arr = arr.concat(fbUpdateRequests());
ws.send(arr);
@@ -586,9 +612,12 @@ mouseButton = function(x, y, down, bmask) {
return;
} else {
viewportDragging = false;
+ ws.send(fbUpdateRequests()); // Force immediate redraw
}
}
+ if (conf.view_only) { return; } // View only, skip mouse events
+
mouse_arr = mouse_arr.concat(
pointerEvent(display.absX(x), display.absY(y)) );
flushClient();
@@ -611,6 +640,8 @@ mouseMove = function(x, y) {
return;
}
+ if (conf.view_only) { return; } // View only, skip mouse events
+
mouse_arr = mouse_arr.concat(
pointerEvent(display.absX(x), display.absY(y)) );
};
@@ -641,8 +672,10 @@ init_msg = function() {
switch (sversion) {
case "003.003": rfb_version = 3.3; break;
case "003.006": rfb_version = 3.3; break; // UltraVNC
+ case "003.889": rfb_version = 3.3; break; // Apple Remote Desktop
case "003.007": rfb_version = 3.7; break;
case "003.008": rfb_version = 3.8; break;
+ case "004.000": rfb_version = 3.8; break; // Intel AMT KVM
default:
return fail("Invalid server version " + sversion);
}
@@ -806,9 +839,25 @@ init_msg = function() {
", green_shift: " + green_shift +
", blue_shift: " + blue_shift);
+ if (big_endian !== 0) {
+ Util.Warn("Server native endian is not little endian");
+ }
+ if (red_shift !== 16) {
+ Util.Warn("Server native red-shift is not 16");
+ }
+ if (blue_shift !== 0) {
+ Util.Warn("Server native blue-shift is not 0");
+ }
+
/* Connection name/title */
name_length = ws.rQshift32();
fb_name = ws.rQshiftStr(name_length);
+
+ if (conf.true_color && fb_name === "Intel(r) AMT KVM")
+ {
+ Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color");
+ conf.true_color = false;
+ }
display.set_true_color(conf.true_color);
display.resize(fb_width, fb_height);
@@ -865,6 +914,8 @@ normal_msg = function() {
ws.rQshift8(); // Padding
first_colour = ws.rQshift16(); // First colour
num_colours = ws.rQshift16();
+ if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }
+
for (c=0; c < num_colours; c+=1) {
red = ws.rQshift16();
//Util.Debug("red before: " + red);
@@ -872,7 +923,7 @@ normal_msg = function() {
//Util.Debug("red after: " + red);
green = parseInt(ws.rQshift16() / 256, 10);
blue = parseInt(ws.rQshift16() / 256, 10);
- display.set_colourMap([red, green, blue], first_colour + c);
+ display.set_colourMap([blue, green, red], first_colour + c);
}
Util.Debug("colourMap: " + display.get_colourMap());
Util.Info("Registered " + num_colours + " colourMap entries");
@@ -973,9 +1024,10 @@ framebufferUpdate = function() {
if (ret) {
encStats[FBU.encoding][0] += 1;
encStats[FBU.encoding][1] += 1;
+ timing.pixels += FBU.width * FBU.height;
}
- if (FBU.rects === 0) {
+ if (FBU.rects === 0 || (timing.pixels >= (fb_width * fb_height))) {
if (((FBU.width === fb_width) &&
(FBU.height === fb_height)) ||
(timing.fbu_rt_start > 0)) {
@@ -1226,42 +1278,197 @@ encHandlers.HEXTILE = function display_hextile() {
};
-encHandlers.TIGHT_PNG = function display_tight_png() {
- //Util.Debug(">> display_tight_png");
- var ctl, cmode, clength, getCLength, color, img;
- //Util.Debug(" FBU.rects: " + FBU.rects);
- //Util.Debug(" starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
+// Get 'compact length' header and data size
+getTightCLength = function (arr) {
+ var header = 1, data = 0;
+ data += arr[0] & 0x7f;
+ if (arr[0] & 0x80) {
+ header += 1;
+ data += (arr[1] & 0x7f) << 7;
+ if (arr[1] & 0x80) {
+ header += 1;
+ data += arr[2] << 14;
+ }
+ }
+ return [header, data];
+};
+
+function display_tight(isTightPNG) {
+ //Util.Debug(">> display_tight");
+
+ if (fb_depth === 1) {
+ fail("Tight protocol handler only implements true color mode");
+ }
+
+ var ctl, cmode, clength, color, img, data;
+ var filterId = -1, resetStreams = 0, streamId = -1;
+ var rQ = ws.get_rQ(), rQi = ws.get_rQi();
FBU.bytes = 1; // compression-control byte
if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
- // Get 'compact length' header and data size
- getCLength = function (arr) {
- var header = 1, data = 0;
- data += arr[0] & 0x7f;
- if (arr[0] & 0x80) {
- header += 1;
- data += (arr[1] & 0x7f) << 7;
- if (arr[1] & 0x80) {
- header += 1;
- data += arr[2] << 14;
+ var checksum = function(data) {
+ var sum=0, i;
+ for (i=0; i<data.length;i++) {
+ sum += data[i];
+ if (sum > 65536) sum -= 65536;
+ }
+ return sum;
+ }
+
+ var decompress = function(data) {
+ for (var i=0; i<4; i++) {
+ if ((resetStreams >> i) & 1) {
+ FBU.zlibs[i].reset();
+ Util.Info("Reset zlib stream " + i);
+ }
+ }
+ var uncompressed = FBU.zlibs[streamId].uncompress(data, 0);
+ if (uncompressed.status !== 0) {
+ Util.Error("Invalid data in zlib stream");
+ }
+ //Util.Warn("Decompressed " + data.length + " to " +
+ // uncompressed.data.length + " checksums " +
+ // checksum(data) + ":" + checksum(uncompressed.data));
+
+ return uncompressed.data;
+ }
+
+ var handlePalette = function() {
+ var numColors = rQ[rQi + 2] + 1;
+ var paletteSize = numColors * fb_depth;
+ FBU.bytes += paletteSize;
+ if (ws.rQwait("TIGHT palette " + cmode, FBU.bytes)) { return false; }
+
+ var bpp = (numColors <= 2) ? 1 : 8;
+ var rowSize = Math.floor((FBU.width * bpp + 7) / 8);
+ var raw = false;
+ if (rowSize * FBU.height < 12) {
+ raw = true;
+ clength = [0, rowSize * FBU.height];
+ } else {
+ clength = getTightCLength(ws.rQslice(3 + paletteSize,
+ 3 + paletteSize + 3));
+ }
+ FBU.bytes += clength[0] + clength[1];
+ if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
+
+ // Shift ctl, filter id, num colors, palette entries, and clength off
+ ws.rQshiftBytes(3);
+ var palette = ws.rQshiftBytes(paletteSize);
+ ws.rQshiftBytes(clength[0]);
+
+ if (raw) {
+ data = ws.rQshiftBytes(clength[1]);
+ } else {
+ data = decompress(ws.rQshiftBytes(clength[1]));
+ }
+
+ // Convert indexed (palette based) image data to RGB
+ // TODO: reduce number of calculations inside loop
+ var dest = [];
+ var x, y, b, w, w1, dp, sp;
+ if (numColors === 2) {
+ w = Math.floor((FBU.width + 7) / 8);
+ w1 = Math.floor(FBU.width / 8);
+ for (y = 0; y < FBU.height; y++) {
+ for (x = 0; x < w1; x++) {
+ for (b = 7; b >= 0; b--) {
+ dp = (y*FBU.width + x*8 + 7-b) * 3;
+ sp = (data[y*w + x] >> b & 1) * 3;
+ dest[dp ] = palette[sp ];
+ dest[dp+1] = palette[sp+1];
+ dest[dp+2] = palette[sp+2];
+ }
+ }
+ for (b = 7; b >= 8 - FBU.width % 8; b--) {
+ dp = (y*FBU.width + x*8 + 7-b) * 3;
+ sp = (data[y*w + x] >> b & 1) * 3;
+ dest[dp ] = palette[sp ];
+ dest[dp+1] = palette[sp+1];
+ dest[dp+2] = palette[sp+2];
+ }
+ }
+ } else {
+ for (y = 0; y < FBU.height; y++) {
+ for (x = 0; x < FBU.width; x++) {
+ dp = (y*FBU.width + x) * 3;
+ sp = data[y*FBU.width + x] * 3;
+ dest[dp ] = palette[sp ];
+ dest[dp+1] = palette[sp+1];
+ dest[dp+2] = palette[sp+2];
+ }
}
}
- return [header, data];
- };
+
+ FBU.imgQ.push({
+ 'type': 'rgb',
+ 'img': {'complete': true, 'data': dest},
+ 'x': FBU.x,
+ 'y': FBU.y,
+ 'width': FBU.width,
+ 'height': FBU.height});
+ return true;
+ }
+
+ var handleCopy = function() {
+ var raw = false;
+ var uncompressedSize = FBU.width * FBU.height * fb_depth;
+ if (uncompressedSize < 12) {
+ raw = true;
+ clength = [0, uncompressedSize];
+ } else {
+ clength = getTightCLength(ws.rQslice(1, 4));
+ }
+ FBU.bytes = 1 + clength[0] + clength[1];
+ if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
+
+ // Shift ctl, clength off
+ ws.rQshiftBytes(1 + clength[0]);
+
+ if (raw) {
+ data = ws.rQshiftBytes(clength[1]);
+ } else {
+ data = decompress(ws.rQshiftBytes(clength[1]));
+ }
+
+ FBU.imgQ.push({
+ 'type': 'rgb',
+ 'img': {'complete': true, 'data': data},
+ 'x': FBU.x,
+ 'y': FBU.y,
+ 'width': FBU.width,
+ 'height': FBU.height});
+ return true;
+ }
ctl = ws.rQpeek8();
- switch (ctl >> 4) {
- case 0x08: cmode = "fill"; break;
- case 0x09: cmode = "jpeg"; break;
- case 0x0A: cmode = "png"; break;
- default: throw("Illegal basic compression received, ctl: " + ctl);
+
+ // Keep tight reset bits
+ resetStreams = ctl & 0xF;
+
+ // Figure out filter
+ ctl = ctl >> 4;
+ streamId = ctl & 0x3;
+
+ if (ctl === 0x08) cmode = "fill";
+ else if (ctl === 0x09) cmode = "jpeg";
+ else if (ctl === 0x0A) cmode = "png";
+ else if (ctl & 0x04) cmode = "filter";
+ else if (ctl < 0x04) cmode = "copy";
+ else throw("Illegal tight compression received, ctl: " + ctl);
+
+ if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
+ throw("filter/copy received in tightPNG mode");
}
+
switch (cmode) {
// fill uses fb_depth because TPIXELs drop the padding byte
- case "fill": FBU.bytes += fb_depth; break; // TPIXEL
- case "jpeg": FBU.bytes += 3; break; // max clength
- case "png": FBU.bytes += 3; break; // max clength
+ case "fill": FBU.bytes += fb_depth; break; // TPIXEL
+ case "jpeg": FBU.bytes += 3; break; // max clength
+ case "png": FBU.bytes += 3; break; // max clength
+ case "filter": FBU.bytes += 2; break; // filter id + num colors if palette
+ case "copy": break;
}
if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
@@ -1281,16 +1488,17 @@ encHandlers.TIGHT_PNG = function display_tight_png() {
'y': FBU.y,
'width': FBU.width,
'height': FBU.height,
- 'color': color});
+ 'color': [color[2], color[1], color[0]] });
break;
- case "jpeg":
case "png":
- clength = getCLength(ws.rQslice(1, 4));
+ case "jpeg":
+ clength = getTightCLength(ws.rQslice(1, 4));
FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
// We have everything, render it
- //Util.Debug(" png, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
+ //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " +
+ // clength[0] + ", clength[1]: " + clength[1]);
ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
img = new Image();
//img.onload = scan_tight_imgQ;
@@ -1303,13 +1511,27 @@ encHandlers.TIGHT_PNG = function display_tight_png() {
extract_data_uri(ws.rQshiftBytes(clength[1]));
img = null;
break;
+ case "filter":
+ filterId = rQ[rQi + 1];
+ if (filterId === 1) {
+ if (!handlePalette()) { return false; }
+ } else {
+ // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
+ // Filter 2, Gradient is valid but not used if jpeg is enabled
+ throw("Unsupported tight subencoding received, filter: " + filterId);
+ }
+ break;
+ case "copy":
+ if (!handleCopy()) { return false; }
+ break;
}
+
FBU.bytes = 0;
FBU.rects -= 1;
//Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
//Util.Debug("<< display_tight_png");
return true;
-};
+}
extract_data_uri = function(arr) {
//var i, stra = [];
@@ -1327,8 +1549,10 @@ scan_tight_imgQ = function() {
imgQ = FBU.imgQ;
while ((imgQ.length > 0) && (imgQ[0].img.complete)) {
data = imgQ.shift();
- if (data['type'] === 'fill') {
+ if (data.type === 'fill') {
display.fillRect(data.x, data.y, data.width, data.height, data.color);
+ } else if (data.type === 'rgb') {
+ display.blitRgbImage(data.x, data.y, data.width, data.height, data.img.data, 0);
} else {
ctx.drawImage(data.img, data.x, data.y);
}
@@ -1337,6 +1561,16 @@ scan_tight_imgQ = function() {
}
};
+encHandlers.TIGHT = function () { return display_tight(false); };
+encHandlers.TIGHT_PNG = function () { return display_tight(true); };
+
+encHandlers.last_rect = function last_rect() {
+ Util.Debug(">> set_desktopsize");
+ FBU.rects = 0;
+ Util.Debug("<< set_desktopsize");
+ return true;
+};
+
encHandlers.DesktopSize = function set_desktopsize() {
Util.Debug(">> set_desktopsize");
fb_width = FBU.width;
@@ -1408,9 +1642,9 @@ pixelFormat = function() {
arr.push16(255); // red-max
arr.push16(255); // green-max
arr.push16(255); // blue-max
- arr.push8(0); // red-shift
+ arr.push8(16); // red-shift
arr.push8(8); // green-shift
- arr.push8(16); // blue-shift
+ arr.push8(0); // blue-shift
arr.push8(0); // padding
arr.push8(0); // padding
@@ -1556,7 +1790,7 @@ that.sendPassword = function(passwd) {
};
that.sendCtrlAltDel = function() {
- if (rfb_state !== "normal") { return false; }
+ if (rfb_state !== "normal" || conf.view_only) { return false; }
Util.Info("Sending Ctrl-Alt-Del");
var arr = [];
arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
@@ -1572,7 +1806,7 @@ that.sendCtrlAltDel = function() {
// Send a key press. If 'down' is not specified then send a down key
// followed by an up key.
that.sendKey = function(code, down) {
- if (rfb_state !== "normal") { return false; }
+ if (rfb_state !== "normal" || conf.view_only) { return false; }
var arr = [];
if (typeof down !== 'undefined') {
Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
diff --git a/webclients/novnc/include/ui.js b/webclients/novnc/include/ui.js
index 74a0005..eddfa6c 100644
--- a/webclients/novnc/include/ui.js
+++ b/webclients/novnc/include/ui.js
@@ -14,7 +14,7 @@ var UI = {
rfb_state : 'loaded',
settingsOpen : false,
-connSettingsOpen : true,
+connSettingsOpen : false,
clipboardOpen: false,
keyboardVisible: false,
@@ -45,15 +45,16 @@ load: function() {
WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
/* Populate the controls if defaults are provided in the URL */
- UI.initSetting('host', '');
- UI.initSetting('port', '');
+ UI.initSetting('host', window.location.hostname);
+ UI.initSetting('port', window.location.port);
UI.initSetting('password', '');
- UI.initSetting('encrypt', false);
+ UI.initSetting('encrypt', (window.location.protocol === "https:"));
UI.initSetting('true_color', true);
UI.initSetting('cursor', false);
UI.initSetting('shared', true);
+ UI.initSetting('view_only', false);
UI.initSetting('connectTimeout', 2);
- UI.initSetting('path', '');
+ UI.initSetting('path', 'websockify');
UI.rfb = RFB({'target': $D('noVNC_canvas'),
'onUpdateState': UI.updateState,
@@ -101,6 +102,14 @@ load: function() {
}
} );
+ // Show description by default when hosted at for kanaka.github.com
+ if (location.host === "kanaka.github.com") {
+ // Open the description dialog
+ $D('noVNC_description').style.display = "block";
+ } else {
+ // Open the connect panel on first load
+ UI.toggleConnectPanel();
+ }
},
// Read form control compatible setting from cookie
@@ -188,17 +197,19 @@ forceSetting: function(name, val) {
// Show the clipboard panel
toggleClipboardPanel: function() {
+ // Close the description panel
+ $D('noVNC_description').style.display = "none";
//Close settings if open
- if (UI.settingsOpen == true) {
+ if (UI.settingsOpen === true) {
UI.settingsApply();
UI.closeSettingsMenu();
}
//Close connection settings if open
- if (UI.connSettingsOpen == true) {
+ if (UI.connSettingsOpen === true) {
UI.toggleConnectPanel();
}
//Toggle Clipboard Panel
- if (UI.clipboardOpen == true) {
+ if (UI.clipboardOpen === true) {
$D('noVNC_clipboard').style.display = "none";
$D('clipboardButton').className = "noVNC_status_button";
UI.clipboardOpen = false;
@@ -211,18 +222,20 @@ toggleClipboardPanel: function() {
// Show the connection settings panel/menu
toggleConnectPanel: function() {
+ // Close the description panel
+ $D('noVNC_description').style.display = "none";
//Close connection settings if open
- if (UI.settingsOpen == true) {
+ if (UI.settingsOpen === true) {
UI.settingsApply();
UI.closeSettingsMenu();
$D('connectButton').className = "noVNC_status_button";
}
- if (UI.clipboardOpen == true) {
+ if (UI.clipboardOpen === true) {
UI.toggleClipboardPanel();
}
//Toggle Connection Panel
- if (UI.connSettingsOpen == true) {
+ if (UI.connSettingsOpen === true) {
$D('noVNC_controls').style.display = "none";
$D('connectButton').className = "noVNC_status_button";
UI.connSettingsOpen = false;
@@ -238,6 +251,8 @@ toggleConnectPanel: function() {
// On open, settings are refreshed from saved cookies.
// On close, settings are applied
toggleSettingsPanel: function() {
+ // Close the description panel
+ $D('noVNC_description').style.display = "none";
if (UI.settingsOpen) {
UI.settingsApply();
UI.closeSettingsMenu();
@@ -252,6 +267,7 @@ toggleSettingsPanel: function() {
}
UI.updateSetting('clip');
UI.updateSetting('shared');
+ UI.updateSetting('view_only');
UI.updateSetting('connectTimeout');
UI.updateSetting('path');
UI.updateSetting('stylesheet');
@@ -263,11 +279,13 @@ toggleSettingsPanel: function() {
// Open menu
openSettingsMenu: function() {
- if (UI.clipboardOpen == true) {
+ // Close the description panel
+ $D('noVNC_description').style.display = "none";
+ if (UI.clipboardOpen === true) {
UI.toggleClipboardPanel();
}
//Close connection settings if open
- if (UI.connSettingsOpen == true) {
+ if (UI.connSettingsOpen === true) {
UI.toggleConnectPanel();
}
$D('noVNC_settings').style.display = "block";
@@ -292,6 +310,7 @@ settingsApply: function() {
}
UI.saveSetting('clip');
UI.saveSetting('shared');
+ UI.saveSetting('view_only');
UI.saveSetting('connectTimeout');
UI.saveSetting('path');
UI.saveSetting('stylesheet');
@@ -363,6 +382,7 @@ updateState: function(rfb, state, oldstate, msg) {
break;
case 'disconnected':
$D('noVNC_logo').style.display = "block";
+ // Fall through
case 'loaded':
klass = "noVNC_status_normal";
break;
@@ -404,16 +424,19 @@ updateVisualState: function() {
$D('noVNC_cursor').disabled = true;
}
$D('noVNC_shared').disabled = connected;
+ $D('noVNC_view_only').disabled = connected;
$D('noVNC_connectTimeout').disabled = connected;
$D('noVNC_path').disabled = connected;
if (connected) {
UI.setViewClip();
UI.setMouseButton(1);
+ $D('clipboardButton').style.display = "inline";
$D('showKeyboard').style.display = "inline";
$D('sendCtrlAltDelButton').style.display = "inline";
} else {
UI.setMouseButton();
+ $D('clipboardButton').style.display = "none";
$D('showKeyboard').style.display = "none";
$D('sendCtrlAltDelButton').style.display = "none";
}
@@ -464,6 +487,7 @@ connect: function() {
UI.rfb.set_true_color(UI.getSetting('true_color'));
UI.rfb.set_local_cursor(UI.getSetting('cursor'));
UI.rfb.set_shared(UI.getSetting('shared'));
+ UI.rfb.set_view_only(UI.getSetting('view_only'));
UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout'));
UI.rfb.connect(host, port, password, path);
@@ -569,11 +593,11 @@ setViewDrag: function(drag) {
// On touch devices, show the OS keyboard
showKeyboard: function() {
- if(UI.keyboardVisible == false) {
+ if(UI.keyboardVisible === false) {
$D('keyboardinput').focus();
UI.keyboardVisible = true;
$D('showKeyboard').className = "noVNC_status_button_selected";
- } else if(UI.keyboardVisible == true) {
+ } else if(UI.keyboardVisible === true) {
$D('keyboardinput').blur();
$D('showKeyboard').className = "noVNC_status_button";
UI.keyboardVisible = false;
@@ -585,7 +609,7 @@ keyInputBlur: function() {
//Weird bug in iOS if you change keyboardVisible
//here it does not actually occur so next time
//you click keyboard icon it doesnt work.
- setTimeout("UI.setKeyboard()",100)
+ setTimeout(function() { UI.setKeyboard(); },100);
},
setKeyboard: function() {
diff --git a/webclients/novnc/include/util.js b/webclients/novnc/include/util.js
index 0a9e0e0..ddc1914 100644
--- a/webclients/novnc/include/util.js
+++ b/webclients/novnc/include/util.js
@@ -33,6 +33,30 @@ Array.prototype.push32 = function (num) {
(num ) & 0xFF );
};
+// IE does not support map (even in IE9)
+//This prototype is provided by the Mozilla foundation and
+//is distributed under the MIT license.
+//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
+if (!Array.prototype.map)
+{
+ Array.prototype.map = function(fun /*, thisp*/)
+ {
+ var len = this.length;
+ if (typeof fun != "function")
+ throw new TypeError();
+
+ var res = new Array(len);
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++)
+ {
+ if (i in this)
+ res[i] = fun.call(thisp, this[i], i, this);
+ }
+
+ return res;
+ };
+}
+
/*
* ------------------------------------------------------
* Namespaced in Util
@@ -159,7 +183,7 @@ Util.conf_defaults = function(cfg, api, defaults, arr) {
Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
arr[i][2], arr[i][3], arr[i][4]);
}
-}
+};
/*
@@ -240,8 +264,11 @@ Util.stopEvent = function(e) {
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
Util.Engine = {
- 'presto': (function() {
- return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
+ // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
+ //'presto': (function() {
+ // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
+ 'presto': (function() { return (!window.opera) ? false : true; }()),
+
'trident': (function() {
return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
'webkit': (function() {
diff --git a/webclients/novnc/include/vnc.js b/webclients/novnc/include/vnc.js
index f938be7..2b31f45 100644
--- a/webclients/novnc/include/vnc.js
+++ b/webclients/novnc/include/vnc.js
@@ -36,6 +36,7 @@ function get_INCLUDE_URI() {
extra += start + "input.js" + end;
extra += start + "display.js" + end;
extra += start + "rfb.js" + end;
+ extra += start + "jsunzip.js" + end;
document.write(extra);
}());
diff --git a/webclients/novnc/include/websock.js b/webclients/novnc/include/websock.js
index a688f76..33350df 100644
--- a/webclients/novnc/include/websock.js
+++ b/webclients/novnc/include/websock.js
@@ -14,16 +14,23 @@
* read binary data off of the receive queue.
*/
+/*jslint browser: true, bitwise: false, plusplus: false */
+/*global Util, Base64 */
+
// Load Flash WebSocket emulator if needed
-if (window.WebSocket) {
+if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true;
-} else if (window.MozWebSocket) {
+} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true;
window.WebSocket = window.MozWebSocket;
} else {
/* no builtin WebSocket so load web_socket.js */
+
+ // To enable debug:
+ // window.WEB_SOCKET_DEBUG=1;
+
Websock_native = false;
(function () {
function get_INCLUDE_URI() {
@@ -34,11 +41,11 @@ if (window.WebSocket) {
var start = "<script src='" + get_INCLUDE_URI(),
end = "'><\/script>", extra = "";
- WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
+ window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
"web-socket-js/WebSocketMain.swf";
if (Util.Engine.trident) {
Util.Debug("Forcing uncached load of WebSocketMain.swf");
- WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
+ window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
}
extra += start + "web-socket-js/swfobject.js" + end;
extra += start + "web-socket-js/web_socket.js" + end;
@@ -83,7 +90,7 @@ function get_rQi() {
}
function set_rQi(val) {
rQi = val;
-};
+}
function rQlen() {
return rQ.length - rQi;
@@ -115,6 +122,7 @@ function rQshift32() {
(rQ[rQi++] );
}
function rQshiftStr(len) {
+ if (typeof(len) === 'undefined') { len = rQlen(); }
var arr = rQ.slice(rQi, rQi + len);
rQi += len;
return arr.map(function (num) {
@@ -122,6 +130,7 @@ function rQshiftStr(len) {
}
function rQshiftBytes(len) {
+ if (typeof(len) === 'undefined') { len = rQlen(); }
rQi += len;
return rQ.slice(rQi-len, rQi);
}
diff --git a/webclients/novnc/include/webutil.js b/webclients/novnc/include/webutil.js
index 95138f8..7a0a076 100644
--- a/webclients/novnc/include/webutil.js
+++ b/webclients/novnc/include/webutil.js
@@ -8,7 +8,7 @@
"use strict";
/*jslint bitwise: false, white: false */
-/*global window, document */
+/*global Util, window, document */
// Globals defined here
var WebUtil = {}, $D;
@@ -17,7 +17,7 @@ var WebUtil = {}, $D;
* Simple DOM selector by ID
*/
if (!window.$D) {
- $D = function (id) {
+ window.$D = function (id) {
if (document.getElementById) {
return document.getElementById(id);
} else if (document.all) {
@@ -42,8 +42,8 @@ WebUtil.init_logging = function() {
/logging=([A-Za-z0-9\._\-]*)/) ||
['', Util._log_level])[1];
- Util.init_logging()
-}
+ Util.init_logging();
+};
WebUtil.init_logging();
diff --git a/webclients/novnc/vnc.html b/webclients/novnc/vnc.html
index 281b4d3..b6cf85b 100644
--- a/webclients/novnc/vnc.html
+++ b/webclients/novnc/vnc.html
@@ -90,7 +90,7 @@
title="Settings"
onclick="UI.toggleSettingsPanel();" />
<input type="image" src="images/connect.png"
- id="connectButton" class="noVNC_status_button_selected"
+ id="connectButton" class="noVNC_status_button"
title="Connect"
onclick="UI.toggleConnectPanel()" />
<input type="image" src="images/disconnect.png"
@@ -99,6 +99,23 @@
onclick="UI.disconnect()" />
</div>
+ <!-- Description Panel -->
+ <!-- Shown by default when hosted at for kanaka.github.com -->
+ <div id="noVNC_description" style="display:none;" class="">
+ noVNC is a browser based VNC client implemented using HTML5 Canvas
+ and WebSockets. You will either need a VNC server with WebSockets
+ support (such as <a href="http://libvncserver.sourceforge.net/">libvncserver</a>)
+ or you will need to use
+ <a href="https://github.com/kanaka/websockify">websockify</a>
+ to bridge between your browser and VNC server. See the noVNC
+ <a href="https://github.com/kanaka/noVNC">README</a>
+ and <a href="http://kanaka.github.com/noVNC">website</a>
+ for more information.
+ <br />
+ <input type="button" value="Close"
+ onclick="UI.toggleConnectPanel();">
+ </div>
+
<!-- Clipboard Panel -->
<div id="noVNC_clipboard" class="triangle-right top">
<textarea id="noVNC_clipboard_text" rows=5
@@ -118,10 +135,11 @@
<li><input id="noVNC_encrypt" type="checkbox"> Encrypt</li>
<li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
<li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li>
- <li><input id="noVNC_clip" type="checkbox"> Clip to window</li>
+ <li><input id="noVNC_clip" type="checkbox"> Clip to Window</li>
<li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
+ <li><input id="noVNC_view_only" type="checkbox"> View Only</li>
<li><input id="noVNC_connectTimeout" type="input"> Connect Timeout (s)</li>
- <li><input id="noVNC_path" type="input"> Path</li>
+ <li><input id="noVNC_path" type="input" value="websockify"> Path</li>
<hr>
<!-- Stylesheet selection dropdown -->
<li><label><strong>Style: </strong>
diff --git a/webclients/novnc/vnc_auto.html b/webclients/novnc/vnc_auto.html
index a500b79..8d370b5 100644
--- a/webclients/novnc/vnc_auto.html
+++ b/webclients/novnc/vnc_auto.html
@@ -84,16 +84,25 @@
}
window.onload = function () {
- var host, port, password, path;
+ var host, port, password, path, token;
$D('sendCtrlAltDelButton').style.display = "inline";
$D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
- host = WebUtil.getQueryVar('host', null);
- port = WebUtil.getQueryVar('port', null);
+ // By default, use the host and port of server that served this file
+ host = WebUtil.getQueryVar('host', window.location.hostname);
+ port = WebUtil.getQueryVar('port', window.location.port);
+
+ // If a token variable is passed in, set the parameter in a cookie.
+ // This is used by nova-novncproxy.
+ token = WebUtil.getQueryVar('token', null);
+ if (token) {
+ WebUtil.createCookie('token', token, 1)
+ }
+
password = WebUtil.getQueryVar('password', '');
- path = WebUtil.getQueryVar('path', '');
+ path = WebUtil.getQueryVar('path', 'websockify');
if ((!host) || (!port)) {
updateState('failed',
"Must specify host and port in URL");
@@ -101,10 +110,12 @@
}
rfb = new RFB({'target': $D('noVNC_canvas'),
- 'encrypt': WebUtil.getQueryVar('encrypt', false),
+ 'encrypt': WebUtil.getQueryVar('encrypt',
+ (window.location.protocol === "https:")),
'true_color': WebUtil.getQueryVar('true_color', true),
'local_cursor': WebUtil.getQueryVar('cursor', true),
'shared': WebUtil.getQueryVar('shared', true),
+ 'view_only': WebUtil.getQueryVar('view_only', false),
'updateState': updateState,
'onPasswordRequired': passwordRequired});
rfb.connect(host, port, password, path);