diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CMakeLists.txt | 46 | ||||
-rw-r--r-- | common/base64.c | 315 | ||||
-rw-r--r-- | common/base64.h | 10 | ||||
-rw-r--r-- | libvncclient/tls_gnutls.c | 102 | ||||
-rw-r--r-- | libvncclient/tls_openssl.c | 249 | ||||
-rw-r--r-- | libvncserver/font.c | 1 | ||||
-rw-r--r-- | libvncserver/main.c | 2 | ||||
-rw-r--r-- | libvncserver/rfbserver.c | 5 | ||||
-rw-r--r-- | libvncserver/websockets.c | 625 | ||||
-rw-r--r-- | libvncserver/ws_decode.c | 563 | ||||
-rw-r--r-- | libvncserver/ws_decode.h | 145 | ||||
-rw-r--r-- | rfb/rfbclient.h | 8 | ||||
-rwxr-xr-x | test/wsmaketestframe.py | 131 | ||||
-rw-r--r-- | test/wstest.c | 206 | ||||
-rw-r--r-- | test/wstestdata.inc | 146 |
16 files changed, 1869 insertions, 686 deletions
@@ -66,6 +66,7 @@ test/cargstest test/copyrecttest test/cursortest test/encodingstest +test/wstest /test/tjbench /test/tjunittest vncterm/LinuxVNC diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d991bf..430e909 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,28 +175,6 @@ check_function_exists(strdup LIBVNCSERVER_HAVE_STRDUP) check_function_exists(strerror LIBVNCSERVER_HAVE_STRERROR) check_function_exists(strstr LIBVNCSERVER_HAVE_STRSTR) -# On systems such as GNU/Linux with glibc, __b64_ntop is defined in a -# separate library, libresolv. On some others, such as FreeBSD, it is -# part of libc itself. We first check if __b64_ntop is found without -# additional libraries, and then try looking for it with libresolv if -# the first test fails. -check_function_exists(__b64_ntop HAVE_B64_IN_LIBC) -if(NOT HAVE_B64_IN_LIBC) - set(CMAKE_REQUIRED_LIBRARIES resolv) - check_function_exists(__b64_ntop HAVE_B64_IN_LIBRESOLV) - set(CMAKE_REQUIRED_LIBRARIES) - - if(HAVE_B64_IN_LIBRESOLV) - set(RESOLV_LIB "resolv") - endif(HAVE_B64_IN_LIBRESOLV) - - # the function check somehow fails for apple but the function is there - if(APPLE) - set(RESOLV_LIB "resolv") - endif(APPLE) - -endif(NOT HAVE_B64_IN_LIBC) - if(Threads_FOUND) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) endif(Threads_FOUND) @@ -226,16 +204,16 @@ if(WITH_WEBSOCKETS AND LIBVNCSERVER_HAVE_SYS_UIO_H) if(GNUTLS_FOUND) set(LIBVNCSERVER_WITH_CLIENT_TLS 1) message(STATUS "Building websockets with GnuTLS") - set(WEBSOCKET_LIBRARIES ${RESOLV_LIB} ${GNUTLS_LIBRARIES}) + set(WEBSOCKET_LIBRARIES ${GNUTLS_LIBRARIES}) set(WSSRCS ${LIBVNCSERVER_DIR}/rfbssl_gnutls ${LIBVNCSERVER_DIR}/rfbcrypto_gnutls) include_directories(${GNUTLS_INCLUDE_DIR}) elseif(OPENSSL_FOUND) message(STATUS "Building websockets with OpenSSL") - set(WEBSOCKET_LIBRARIES ${RESOLV_LIB} ${OPENSSL_LIBRARIES}) + set(WEBSOCKET_LIBRARIES ${OPENSSL_LIBRARIES}) set(WSSRCS ${LIBVNCSERVER_DIR}/rfbssl_openssl ${LIBVNCSERVER_DIR}/rfbcrypto_openssl) else() message(STATUS "Building websockets without SSL") - set(WEBSOCKET_LIBRARIES ${RESOLV_LIB}) + set(WEBSOCKET_LIBRARIES) set(WSSRCS ${LIBVNCSERVER_DIR}/rfbssl_none.c ${LIBVNCSERVER_DIR}/rfbcrypto_included.c ${COMMON_DIR}/md5.c ${COMMON_DIR}/sha1.c) endif() endif(WITH_WEBSOCKETS AND LIBVNCSERVER_HAVE_SYS_UIO_H) @@ -407,11 +385,12 @@ if(LIBVNCSERVER_WITH_WEBSOCKETS) set(LIBVNCSERVER_SOURCES ${LIBVNCSERVER_SOURCES} ${LIBVNCSERVER_DIR}/websockets.c + ${LIBVNCSERVER_DIR}/ws_decode.c + ${COMMON_DIR}/base64.c ${WSSRCS} ) endif(LIBVNCSERVER_WITH_WEBSOCKETS) - add_library(vncclient ${LIBVNCCLIENT_SOURCES}) add_library(vncserver ${LIBVNCSERVER_SOURCES}) if(WIN32) @@ -576,12 +555,23 @@ if(WITH_JPEG AND FOUND_LIBJPEG_TURBO) endif(WITH_JPEG AND FOUND_LIBJPEG_TURBO) +if(LIBVNCSERVER_WITH_WEBSOCKETS) + add_executable(test_wstest + ${TESTS_DIR}/wstest.c + ${TESTS_DIR}/wstestdata.inc + ) + set_target_properties(test_wstest PROPERTIES OUTPUT_NAME wstest) + set_target_properties(test_wstest PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test) + target_link_libraries(test_wstest vncserver vncclient ${ADDITIONAL_TEST_LIBS}) +endif(LIBVNCSERVER_WITH_WEBSOCKETS) + add_test(NAME cargs COMMAND test_cargstest) if(FOUND_LIBJPEG_TURBO) add_test(NAME turbojpeg COMMAND test_tjunittest) endif(FOUND_LIBJPEG_TURBO) - - +if(LIBVNCSERVER_WITH_WEBSOCKETS) + add_test(NAME wstest COMMAND test_wstest) +endif(LIBVNCSERVER_WITH_WEBSOCKETS) # # this gets the libraries needed by TARGET in "-libx -liby ..." form diff --git a/common/base64.c b/common/base64.c new file mode 100644 index 0000000..4e3685a --- /dev/null +++ b/common/base64.c @@ -0,0 +1,315 @@ +/* $OpenBSD: base64.c,v 1.8 2015/01/16 16:48:51 deraadt Exp $ */ + +/* + * Copyright (c) 1996 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Portions Copyright (c) 1995 by International Business Machines, Inc. + * + * International Business Machines, Inc. (hereinafter called IBM) grants + * permission under its copyrights to use, copy, modify, and distribute this + * Software with or without fee, provided that the above copyright notice and + * all paragraphs of this notice appear in all copies, and that the name of IBM + * not be used in connection with the marketing of any product incorporating + * the Software or modifications thereof, without specific, written prior + * permission. + * + * To the extent it has a right to do so, IBM grants an immunity from suit + * under its patents, if any, for the use, sale or manufacture of products to + * the extent that such products are used for performing Domain Name System + * dynamic updates in TCP/IP networks by means of the Software. No immunity is + * granted for any product per se or for any other function of any product. + * + * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, + * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN + * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> + +#include <ctype.h> +#include <resolv.h> +#include <stdio.h> + +#include <stdlib.h> +#include <string.h> + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt) + The following encoding technique is taken from RFC 1521 by Borenstein + and Freed. It is reproduced here in a slightly edited form for + convenience. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8-bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a quantity. When fewer than 24 input + bits are available in an input group, zero bits are added (on the + right) to form an integral number of 6-bit groups. Padding at the + end of the data is performed using the '=' character. + + Since all base64 input is an integral number of octets, only the + ------------------------------------------------- + following cases can arise: + + (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded + output will be an integral multiple of 4 characters + with no "=" padding, + (2) the final quantum of encoding input is exactly 8 bits; + here, the final unit of encoded output will be two + characters followed by two "=" padding characters, or + (3) the final quantum of encoding input is exactly 16 bits; + here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + +int +__b64_ntop(src, srclength, target, targsize) + u_char const *src; + size_t srclength; + char *target; + size_t targsize; +{ + size_t datalength = 0; + u_char input[3]; + u_char output[4]; + int i; + + while (2 < srclength) { + input[0] = *src++; + input[1] = *src++; + input[2] = *src++; + srclength -= 3; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + output[3] = input[2] & 0x3f; + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + target[datalength++] = Base64[output[2]]; + target[datalength++] = Base64[output[3]]; + } + + /* Now we worry about padding. */ + if (0 != srclength) { + /* Get what's left. */ + input[0] = input[1] = input[2] = '\0'; + for (i = 0; i < srclength; i++) + input[i] = *src++; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + if (srclength == 1) + target[datalength++] = Pad64; + else + target[datalength++] = Base64[output[2]]; + target[datalength++] = Pad64; + } + if (datalength >= targsize) + return (-1); + target[datalength] = '\0'; /* Returned value doesn't count \0. */ + return (datalength); +} + +/* skips all whitespace anywhere. + converts characters, four at a time, starting at (or after) + src from base - 64 numbers into three 8 bit bytes in the target area. + it returns the number of data bytes stored at the target, or -1 on error. + */ + +int +__b64_pton(src, target, targsize) + char const *src; + u_char *target; + size_t targsize; +{ + int tarindex, state, ch; + u_char nextbyte; + char *pos; + + state = 0; + tarindex = 0; + + while ((ch = (unsigned char)*src++) != '\0') { + if (isspace(ch)) /* Skip whitespace anywhere. */ + continue; + + if (ch == Pad64) + break; + + pos = strchr(Base64, ch); + if (pos == 0) /* A non-base64 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] = (pos - Base64) << 2; + } + state = 1; + break; + case 1: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 4; + nextbyte = ((pos - Base64) & 0x0f) << 4; + if (tarindex + 1 < targsize) + target[tarindex+1] = nextbyte; + else if (nextbyte) + return (-1); + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 2; + nextbyte = ((pos - Base64) & 0x03) << 6; + if (tarindex + 1 < targsize) + target[tarindex+1] = nextbyte; + else if (nextbyte) + return (-1); + } + tarindex++; + state = 3; + break; + case 3: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64); + } + tarindex++; + state = 0; + break; + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = (unsigned char)*src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for (; ch != '\0'; ch = (unsigned char)*src++) + if (!isspace(ch)) + break; + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) + return (-1); + ch = (unsigned char)*src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for (; ch != '\0'; ch = (unsigned char)*src++) + if (!isspace(ch)) + return (-1); + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target && tarindex < targsize && + target[tarindex] != 0) + return (-1); + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) + return (-1); + } + + return (tarindex); +} diff --git a/common/base64.h b/common/base64.h new file mode 100644 index 0000000..9b86fc1 --- /dev/null +++ b/common/base64.h @@ -0,0 +1,10 @@ +#ifndef _BASE64_H +#define _BASE64_H + +extern int __b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize); +extern int __b64_pton(char const *src, u_char *target, size_t targsize); + +#define rfbBase64NtoP __b64_ntop +#define rfbBase64PtoN __b64_pton + +#endif /* _BASE64_H */ diff --git a/libvncclient/tls_gnutls.c b/libvncclient/tls_gnutls.c index 75bde35..f146d2a 100644 --- a/libvncclient/tls_gnutls.c +++ b/libvncclient/tls_gnutls.c @@ -18,6 +18,7 @@ */ #include <gnutls/gnutls.h> +#include <gnutls/x509.h> #include <rfb/rfbclient.h> #include <errno.h> #ifdef WIN32 @@ -39,6 +40,98 @@ static gnutls_dh_params_t rfbDHParams; static rfbBool rfbTLSInitialized = FALSE; +static int +verify_certificate_callback (gnutls_session_t session) +{ + unsigned int status; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size; + int ret; + gnutls_x509_crt_t cert; + rfbClient *sptr; + char *hostname; + + sptr = (rfbClient *)gnutls_session_get_ptr(session); + if (!sptr) { + rfbClientLog("Failed to validate certificate - missing client data\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + hostname = sptr->serverHost; + if (!hostname) { + rfbClientLog("No server hostname found for client\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + /* This verification function uses the trusted CAs in the credentials + * structure. So you must have installed one or more CA certificates. + */ + ret = gnutls_certificate_verify_peers2 (session, &status); + if (ret < 0) + { + rfbClientLog ("Certificate validation call failed\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (status & GNUTLS_CERT_INVALID) + rfbClientLog("The certificate is not trusted.\n"); + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + rfbClientLog("The certificate hasn't got a known issuer.\n"); + + if (status & GNUTLS_CERT_REVOKED) + rfbClientLog("The certificate has been revoked.\n"); + + if (status & GNUTLS_CERT_EXPIRED) + rfbClientLog("The certificate has expired\n"); + + if (status & GNUTLS_CERT_NOT_ACTIVATED) + rfbClientLog("The certificate is not yet activated\n"); + + if (status) + return GNUTLS_E_CERTIFICATE_ERROR; + + /* Up to here the process is the same for X.509 certificates and + * OpenPGP keys. From now on X.509 certificates are assumed. This can + * be easily extended to work with openpgp keys as well. + */ + if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) { + rfbClientLog("The certificate was not X509\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (gnutls_x509_crt_init (&cert) < 0) + { + rfbClientLog("Error initialising certificate structure\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + cert_list = gnutls_certificate_get_peers (session, &cert_list_size); + if (cert_list == NULL) + { + rfbClientLog("No certificate was found!\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) + { + rfbClientLog("Error parsing certificate\n"); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (!gnutls_x509_crt_check_hostname (cert, hostname)) + { + rfbClientLog("The certificate's owner does not match hostname '%s'\n", + hostname); + return GNUTLS_E_CERTIFICATE_ERROR; + } + + gnutls_x509_crt_deinit (cert); + + /* notify gnutls to continue handshake normally */ + return 0; +} + static rfbBool InitializeTLS(void) { @@ -52,7 +145,7 @@ InitializeTLS(void) rfbClientLog("Failed to initialized GnuTLS: %s.\n", gnutls_strerror(ret)); return FALSE; } - rfbClientLog("GnuTLS initialized.\n"); + rfbClientLog("GnuTLS version %s initialized.\n", gnutls_check_version(NULL)); rfbTLSInitialized = TRUE; return TRUE; } @@ -200,6 +293,7 @@ HandshakeTLS(rfbClient* client) continue; } rfbClientLog("TLS handshake failed: %s.\n", gnutls_strerror(ret)); + FreeTLS(client); return FALSE; } @@ -456,6 +550,10 @@ HandleVeNCryptAuth(rfbClient* client) } else { + /* Set the certificate verification callback. */ + gnutls_certificate_set_verify_function (x509_cred, verify_certificate_callback); + gnutls_session_set_ptr ((gnutls_session_t)client->tlsSession, (void *)client); + 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)); @@ -466,8 +564,6 @@ HandleVeNCryptAuth(rfbClient* client) if (!HandshakeTLS(client)) return FALSE; - /* TODO: validate certificate */ - /* We are done here. The caller should continue with client->subAuthScheme * to do actual sub authentication. */ diff --git a/libvncclient/tls_openssl.c b/libvncclient/tls_openssl.c index 4f758d0..fe60147 100644 --- a/libvncclient/tls_openssl.c +++ b/libvncclient/tls_openssl.c @@ -168,53 +168,11 @@ InitializeTLS(void) SSLeay_add_ssl_algorithms(); RAND_load_file("/dev/urandom", 1024); - rfbClientLog("OpenSSL initialized.\n"); + rfbClientLog("OpenSSL version %s initialized.\n", SSLeay_version(SSLEAY_VERSION)); rfbTLSInitialized = TRUE; return TRUE; } -static int -ssl_verify (int ok, X509_STORE_CTX *ctx) -{ - unsigned char md5sum[16], fingerprint[40], *f; - rfbClient *client; - 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_get_SSL_CTX(ssl)); - - 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; @@ -251,8 +209,12 @@ static int wait_for_data(SSL *ssl, int ret, int timeout) } break; - default: + default: retval = 3; + long verify_res = SSL_get_verify_result(ssl); + if (verify_res != X509_V_OK) + rfbClientLog("Could not verify server certificate: %s.\n", + X509_verify_cert_error_string(verify_res)); break; } @@ -261,17 +223,131 @@ static int wait_for_data(SSL *ssl, int ret, int timeout) return retval; } +static rfbBool +load_crls_from_file(char *file, SSL_CTX *ssl_ctx) +{ + X509_STORE *st; + X509_CRL *crl; + int i; + int count = 0; + BIO *bio; + STACK_OF(X509_INFO) *xis = NULL; + X509_INFO *xi; + + st = SSL_CTX_get_cert_store(ssl_ctx); + + int rv = 0; + + bio = BIO_new_file(file, "r"); + if (bio == NULL) + return FALSE; + + xis = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL); + BIO_free(bio); + + for (i = 0; i < sk_X509_INFO_num(xis); i++) + { + xi = sk_X509_INFO_value(xis, i); + if (xi->crl) + { + X509_STORE_add_crl(st, xi->crl); + xi->crl = NULL; + count++; + } + } + + sk_X509_INFO_pop_free(xis, X509_INFO_free); + + if (count > 0) + return TRUE; + else + return FALSE; +} + static SSL * -open_ssl_connection (rfbClient *client, int sockfd, rfbBool anonTLS) +open_ssl_connection (rfbClient *client, int sockfd, rfbBool anonTLS, rfbCredential *cred) { SSL_CTX *ssl_ctx = NULL; SSL *ssl = NULL; int n, finished = 0; + X509_VERIFY_PARAM *param; + uint8_t verify_crls = cred->x509Credential.x509CrlVerifyMode; - 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); + if (!(ssl_ctx = SSL_CTX_new(SSLv23_client_method()))) + { + rfbClientLog("Could not create new SSL context.\n"); + return NULL; + } + + param = X509_VERIFY_PARAM_new(); + + /* Setup verification if not anonymous */ + if (!anonTLS) + { + if (cred->x509Credential.x509CACertFile) + { + if (!SSL_CTX_load_verify_locations(ssl_ctx, cred->x509Credential.x509CACertFile, NULL)) + { + rfbClientLog("Failed to load CA certificate from %s.\n", + cred->x509Credential.x509CACertFile); + goto error_free_ctx; + } + } else { + rfbClientLog("Using default paths for certificate verification.\n"); + SSL_CTX_set_default_verify_paths (ssl_ctx); + } + + if (cred->x509Credential.x509CACrlFile) + { + if (!load_crls_from_file(cred->x509Credential.x509CACrlFile, ssl_ctx)) + { + rfbClientLog("CRLs could not be loaded.\n"); + goto error_free_ctx; + } + if (verify_crls == rfbX509CrlVerifyNone) verify_crls = rfbX509CrlVerifyAll; + } + + if (cred->x509Credential.x509ClientCertFile && cred->x509Credential.x509ClientKeyFile) + { + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cred->x509Credential.x509ClientCertFile) != 1) + { + rfbClientLog("Client certificate could not be loaded.\n"); + goto error_free_ctx; + } + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, cred->x509Credential.x509ClientKeyFile, + SSL_FILETYPE_PEM) != 1) + { + rfbClientLog("Client private key could not be loaded.\n"); + goto error_free_ctx; + } + + if (SSL_CTX_check_private_key(ssl_ctx) == 0) { + rfbClientLog("Client certificate and private key do not match.\n"); + goto error_free_ctx; + } + } + + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + + if (verify_crls == rfbX509CrlVerifyClient) + X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK); + else if (verify_crls == rfbX509CrlVerifyAll) + X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); + + if(!X509_VERIFY_PARAM_set1_host(param, client->serverHost, strlen(client->serverHost))) + { + rfbClientLog("Could not set server name for verification.\n"); + goto error_free_ctx; + } + SSL_CTX_set1_param(ssl_ctx, param); + } + + if (!(ssl = SSL_new (ssl_ctx))) + { + rfbClientLog("Could not create a new SSL session.\n"); + goto error_free_ctx; + } /* TODO: finetune this list, take into account anonTLS bool */ SSL_set_cipher_list(ssl, "ALL"); @@ -289,24 +365,32 @@ open_ssl_connection (rfbClient *client, int sockfd, rfbBool anonTLS) { finished = 1; SSL_shutdown(ssl); - SSL_free(ssl); - SSL_CTX_free(ssl_ctx); - return NULL; + goto error_free_ssl; } } } while( n != 1 && finished != 1 ); + X509_VERIFY_PARAM_free(param); return ssl; + +error_free_ssl: + SSL_free(ssl); + +error_free_ctx: + X509_VERIFY_PARAM_free(param); + SSL_CTX_free(ssl_ctx); + + return NULL; } static rfbBool -InitializeTLSSession(rfbClient* client, rfbBool anonTLS) +InitializeTLSSession(rfbClient* client, rfbBool anonTLS, rfbCredential *cred) { if (client->tlsSession) return TRUE; - client->tlsSession = open_ssl_connection (client, client->sock, anonTLS); + client->tlsSession = open_ssl_connection (client, client->sock, anonTLS, cred); if (!client->tlsSession) return FALSE; @@ -317,13 +401,6 @@ InitializeTLSSession(rfbClient* client, rfbBool anonTLS) } static rfbBool -SetTLSAnonCredential(rfbClient* client) -{ - rfbClientLog("TLS anonymous credential created.\n"); - return TRUE; -} - -static rfbBool HandshakeTLS(rfbClient* client) { int timeout = 15; @@ -344,7 +421,8 @@ return TRUE; timeout--; continue; } - rfbClientLog("TLS handshake failed: -.\n"); + rfbClientLog("TLS handshake failed.\n"); + FreeTLS(client); return FALSE; } @@ -433,22 +511,31 @@ ReadVeNCryptSecurityType(rfbClient* client, uint32_t *result) rfbBool HandleAnonTLSAuth(rfbClient* client) { - if (!InitializeTLS() || !InitializeTLSSession(client, TRUE)) return FALSE; - - if (!SetTLSAnonCredential(client)) return FALSE; + if (!InitializeTLS() || !InitializeTLSSession(client, TRUE, NULL)) return FALSE; if (!HandshakeTLS(client)) return FALSE; return TRUE; } +static void +FreeX509Credential(rfbCredential *cred) +{ + if (cred->x509Credential.x509CACertFile) free(cred->x509Credential.x509CACertFile); + if (cred->x509Credential.x509CACrlFile) free(cred->x509Credential.x509CACrlFile); + if (cred->x509Credential.x509ClientCertFile) free(cred->x509Credential.x509ClientCertFile); + if (cred->x509Credential.x509ClientKeyFile) free(cred->x509Credential.x509ClientKeyFile); + free(cred); +} + rfbBool HandleVeNCryptAuth(rfbClient* client) { uint8_t major, minor, status; uint32_t authScheme; rfbBool anonTLS; -// gnutls_certificate_credentials_t x509_cred = NULL; + rfbCredential *cred = NULL; + rfbBool result = TRUE; if (!InitializeTLS()) return FALSE; @@ -506,7 +593,6 @@ HandleVeNCryptAuth(rfbClient* client) /* Get X509 Credentials if it's not anonymous */ if (!anonTLS) { - rfbCredential *cred; if (!client->GetCredential) { @@ -519,39 +605,18 @@ HandleVeNCryptAuth(rfbClient* client) 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 (!InitializeTLSSession(client, anonTLS, cred)) result = 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 */ + if (!HandshakeTLS(client)) result = FALSE; /* We are done here. The caller should continue with client->subAuthScheme * to do actual sub authentication. */ - return TRUE; + if (cred) FreeX509Credential(cred); + return result; } int diff --git a/libvncserver/font.c b/libvncserver/font.c index a9091d2..9935e91 100644 --- a/libvncserver/font.c +++ b/libvncserver/font.c @@ -174,6 +174,7 @@ rfbFontDataPtr rfbLoadConsoleFont(char *filename) if(1!=fread(p->data,4096,1,f)) { free(p->data); free(p); + fclose(f); return NULL; } fclose(f); diff --git a/libvncserver/main.c b/libvncserver/main.c index 95c3da5..05b4b13 100644 --- a/libvncserver/main.c +++ b/libvncserver/main.c @@ -1066,7 +1066,7 @@ void rfbInitServer(rfbScreenInfoPtr screen) int i=WSAStartup(MAKEWORD(2,0),&trash); if(i!=0) { rfbErr("Couldn't init Windows Sockets\n"); - return 0; + return; } WSAinitted=TRUE; } diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index 040238d..116c488 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -1999,11 +1999,6 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) char encBuf[64]; char encBuf2[64]; -#ifdef LIBVNCSERVER_WITH_WEBSOCKETS - if (cl->wsctx && webSocketCheckDisconnect(cl)) - return; -#endif - if ((n = rfbReadExact(cl, (char *)&msg, 1)) <= 0) { if (n != 0) rfbLogPerror("rfbProcessClientNormalMessage: read"); diff --git a/libvncserver/websockets.c b/libvncserver/websockets.c index 72396c2..4ebff72 100644 --- a/libvncserver/websockets.c +++ b/libvncserver/websockets.c @@ -34,10 +34,6 @@ /* errno */ #include <errno.h> -#ifndef _MSC_VER -#include <resolv.h> /* __b64_ntop */ -#endif - #ifdef LIBVNCSERVER_HAVE_ENDIAN_H #include <endian.h> #elif LIBVNCSERVER_HAVE_SYS_ENDIAN_H @@ -55,33 +51,8 @@ #include "rfb/rfbconfig.h" #include "rfbssl.h" #include "rfbcrypto.h" - -#if defined(__APPLE__) - -#include <libkern/OSByteOrder.h> -#define WS_NTOH64(n) OSSwapBigToHostInt64(n) -#define WS_NTOH32(n) OSSwapBigToHostInt32(n) -#define WS_NTOH16(n) OSSwapBigToHostInt16(n) -#define WS_HTON64(n) OSSwapHostToBigInt64(n) -#define WS_HTON16(n) OSSwapHostToBigInt16(n) - -#else - -#define WS_NTOH64(n) htobe64(n) -#define WS_NTOH32(n) htobe32(n) -#define WS_NTOH16(n) htobe16(n) -#define WS_HTON64(n) htobe64(n) -#define WS_HTON16(n) htobe16(n) - -#endif - -#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3) -#define WSHLENMAX 14 /* 2 + sizeof(uint64_t) + sizeof(uint32_t) */ - -enum { - WEBSOCKETS_VERSION_HIXIE, - WEBSOCKETS_VERSION_HYBI -}; +#include "ws_decode.h" +#include "base64.h" #if 0 #include <sys/syscall.h> @@ -90,72 +61,6 @@ static int gettid() { } #endif -typedef int (*wsEncodeFunc)(rfbClientPtr cl, const char *src, int len, char **dst); -typedef int (*wsDecodeFunc)(rfbClientPtr cl, char *dst, int len); - -typedef struct ws_ctx_s { - char codeBufDecode[B64LEN(UPDATE_BUF_SIZE) + WSHLENMAX]; /* base64 + maximum frame header length */ - char codeBufEncode[B64LEN(UPDATE_BUF_SIZE) + WSHLENMAX]; /* base64 + maximum frame header length */ - char readbuf[8192]; - int readbufstart; - int readbuflen; - int dblen; - char carryBuf[3]; /* For base64 carry-over */ - int carrylen; - int version; - int base64; - wsEncodeFunc encode; - wsDecodeFunc decode; -} ws_ctx_t; - -typedef union ws_mask_s { - char c[4]; - uint32_t u; -} ws_mask_t; - -/* XXX: The union and the structs do not need to be named. - * We are working around a bug present in GCC < 4.6 which prevented - * it from recognizing anonymous structs and unions. - * See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=4784 - */ -typedef struct -#if __GNUC__ -__attribute__ ((__packed__)) -#endif -ws_header_s { - unsigned char b0; - unsigned char b1; - union { - struct -#if __GNUC__ - __attribute__ ((__packed__)) -#endif - { - uint16_t l16; - ws_mask_t m16; - } s16; - struct -#if __GNUC__ -__attribute__ ((__packed__)) -#endif - { - uint64_t l64; - ws_mask_t m64; - } s64; - ws_mask_t m; - } u; -} ws_header_t; - -enum -{ - WS_OPCODE_CONTINUATION = 0x0, - WS_OPCODE_TEXT_FRAME, - WS_OPCODE_BINARY_FRAME, - WS_OPCODE_CLOSE = 0x8, - WS_OPCODE_PING, - WS_OPCODE_PONG -}; - #define FLASH_POLICY_RESPONSE "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\n" #define SZ_FLASH_POLICY_RESPONSE 93 @@ -165,14 +70,6 @@ enum */ #define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" -#define SERVER_HANDSHAKE_HIXIE "HTTP/1.1 101 Web Socket Protocol Handshake\r\n\ -Upgrade: WebSocket\r\n\ -Connection: Upgrade\r\n\ -%sWebSocket-Origin: %s\r\n\ -%sWebSocket-Location: %s://%s%s\r\n\ -%sWebSocket-Protocol: %s\r\n\ -\r\n%s" - #define SERVER_HANDSHAKE_HYBI "HTTP/1.1 101 Switching Protocols\r\n\ Upgrade: websocket\r\n\ Connection: Upgrade\r\n\ @@ -199,12 +96,11 @@ struct timeval #endif static rfbBool webSocketsHandshake(rfbClientPtr cl, char *scheme); -void webSocketsGenMd5(char * target, char *key1, char *key2, char *key3); static int webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst); -static int webSocketsEncodeHixie(rfbClientPtr cl, const char *src, int len, char **dst); -static int webSocketsDecodeHybi(rfbClientPtr cl, char *dst, int len); -static int webSocketsDecodeHixie(rfbClientPtr cl, char *dst, int len); + +static int ws_read(void *cl, char *buf, size_t len); + static int min (int a, int b) { @@ -221,8 +117,8 @@ static void webSocketsGenSha1Key(char *target, int size, char *key) iov[1].iov_base = GUID; iov[1].iov_len = sizeof(GUID) - 1; digestsha1(iov, 2, hash); - if (-1 == b64_ntop(hash, sizeof(hash), target, size)) - rfbErr("b64_ntop failed\n"); + if (-1 == rfbBase64NtoP(hash, sizeof(hash), target, size)) + rfbErr("rfbBase64NtoP failed\n"); } /* @@ -245,7 +141,10 @@ webSocketsCheck (rfbClientPtr cl) return FALSE; } - if (strncmp(bbuf, "<", 1) == 0) { + if (strncmp(bbuf, "RFB ", 4) == 0) { + rfbLog("Normal socket connection\n"); + return TRUE; + } else if (strncmp(bbuf, "<", 1) == 0) { rfbLog("Got Flash policy request, sending response\n"); if (rfbWriteExact(cl, FLASH_POLICY_RESPONSE, SZ_FLASH_POLICY_RESPONSE) < 0) { @@ -313,8 +212,8 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) rfbLog("webSocketsHandshake: client gone\n"); else rfbLogPerror("webSocketsHandshake: read"); - free(response); - free(buf); + free(response); + free(buf); return FALSE; } @@ -367,24 +266,33 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) /* rfbLog("Got key2: %s\n", key2); */ /* HyBI */ - } else if ((strncasecmp("sec-websocket-protocol: ", line, min(llen,24))) == 0) { + } else if ((strncasecmp("sec-websocket-protocol: ", line, min(llen,24))) == 0) { protocol = line+24; buf[len-2] = '\0'; rfbLog("Got protocol: %s\n", protocol); } else if ((strncasecmp("sec-websocket-origin: ", line, min(llen,22))) == 0) { - sec_ws_origin = line+22; + sec_ws_origin = line+22; buf[len-2] = '\0'; } else if ((strncasecmp("sec-websocket-key: ", line, min(llen,19))) == 0) { - sec_ws_key = line+19; + sec_ws_key = line+19; buf[len-2] = '\0'; } else if ((strncasecmp("sec-websocket-version: ", line, min(llen,23))) == 0) { - sec_ws_version = strtol(line+23, NULL, 10); + sec_ws_version = strtol(line+23, NULL, 10); buf[len-2] = '\0'; - } + } linestart = len; } } + + /* older hixie handshake, this could be removed if + * a final standard is established -- removed now */ + if (!sec_ws_version) { + rfbErr("Hixie no longer supported\n"); + free(response); + free(buf); + return FALSE; + } if (!(path && host && (origin || sec_ws_origin))) { rfbErr("webSocketsHandshake: incomplete client handshake\n"); @@ -394,12 +302,6 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) } if ((protocol) && (strstr(protocol, "binary"))) { - if (! sec_ws_version) { - rfbErr("webSocketsHandshake: 'binary' protocol not supported with Hixie\n"); - free(response); - free(buf); - return FALSE; - } rfbLog(" - webSocketsHandshake: using binary/raw encoding\n"); base64 = FALSE; protocol = "binary"; @@ -417,32 +319,16 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) * Generate the WebSockets server response based on the the headers sent * by the client. */ + char accept[B64LEN(SHA1_HASH_SIZE) + 1]; + rfbLog(" - WebSockets client version hybi-%02d\n", sec_ws_version); + webSocketsGenSha1Key(accept, sizeof(accept), sec_ws_key); - if (sec_ws_version) { - char accept[B64LEN(SHA1_HASH_SIZE) + 1]; - rfbLog(" - WebSockets client version hybi-%02d\n", sec_ws_version); - webSocketsGenSha1Key(accept, sizeof(accept), sec_ws_key); - if(strlen(protocol) > 0) - len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, - SERVER_HANDSHAKE_HYBI, accept, protocol); - else - len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, - SERVER_HANDSHAKE_HYBI_NO_PROTOCOL, accept); + if(strlen(protocol) > 0) { + len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, + SERVER_HANDSHAKE_HYBI, accept, protocol); } else { - /* older hixie handshake, this could be removed if - * a final standard is established */ - if (!(key1 && key2 && key3)) { - rfbLog(" - WebSockets client version hixie-75\n"); - prefix[0] = '\0'; - trailer[0] = '\0'; - } else { - rfbLog(" - WebSockets client version hixie-76\n"); - snprintf(prefix, 5, "Sec-"); - webSocketsGenMd5(trailer, key1, key2, key3); - } - len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, - SERVER_HANDSHAKE_HIXIE, prefix, origin, prefix, scheme, - host, path, prefix, protocol, trailer); + len = snprintf(response, WEBSOCKETS_MAX_HANDSHAKE_LEN, + SERVER_HANDSHAKE_HYBI_NO_PROTOCOL, accept); } if (rfbWriteExact(cl, response, len) < 0) { @@ -455,357 +341,30 @@ webSocketsHandshake(rfbClientPtr cl, char *scheme) free(response); free(buf); - wsctx = calloc(1, sizeof(ws_ctx_t)); - if (sec_ws_version) { - wsctx->version = WEBSOCKETS_VERSION_HYBI; - wsctx->encode = webSocketsEncodeHybi; - wsctx->decode = webSocketsDecodeHybi; - } else { - wsctx->version = WEBSOCKETS_VERSION_HIXIE; - wsctx->encode = webSocketsEncodeHixie; - wsctx->decode = webSocketsDecodeHixie; - } + wsctx->encode = webSocketsEncodeHybi; + wsctx->decode = webSocketsDecodeHybi; + wsctx->ctxInfo.readFunc = ws_read; wsctx->base64 = base64; + hybiDecodeCleanupComplete(wsctx); cl->wsctx = (wsCtx *)wsctx; return TRUE; } - -void -webSocketsGenMd5(char * target, char *key1, char *key2, char *key3) -{ - unsigned int i, spaces1 = 0, spaces2 = 0; - unsigned long num1 = 0, num2 = 0; - unsigned char buf[17]; - struct iovec iov[1]; - - for (i=0; i < strlen(key1); i++) { - if (key1[i] == ' ') { - spaces1 += 1; - } - if ((key1[i] >= 48) && (key1[i] <= 57)) { - num1 = num1 * 10 + (key1[i] - 48); - } - } - num1 = num1 / spaces1; - - for (i=0; i < strlen(key2); i++) { - if (key2[i] == ' ') { - spaces2 += 1; - } - if ((key2[i] >= 48) && (key2[i] <= 57)) { - num2 = num2 * 10 + (key2[i] - 48); - } - } - num2 = num2 / spaces2; - - /* Pack it big-endian */ - buf[0] = (num1 & 0xff000000) >> 24; - buf[1] = (num1 & 0xff0000) >> 16; - buf[2] = (num1 & 0xff00) >> 8; - buf[3] = num1 & 0xff; - - buf[4] = (num2 & 0xff000000) >> 24; - buf[5] = (num2 & 0xff0000) >> 16; - buf[6] = (num2 & 0xff00) >> 8; - buf[7] = num2 & 0xff; - - strncpy((char *)buf+8, key3, 8); - buf[16] = '\0'; - - iov[0].iov_base = buf; - iov[0].iov_len = 16; - digestmd5(iov, 1, target); - target[16] = '\0'; - - return; -} - -static int -webSocketsEncodeHixie(rfbClientPtr cl, const char *src, int len, char **dst) -{ - int sz = 0; - ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; - - wsctx->codeBufEncode[sz++] = '\x00'; - len = b64_ntop((unsigned char *)src, len, wsctx->codeBufEncode+sz, sizeof(wsctx->codeBufEncode) - (sz + 1)); - if (len < 0) { - return len; - } - sz += len; - - wsctx->codeBufEncode[sz++] = '\xff'; - *dst = wsctx->codeBufEncode; - return sz; -} - -static int -ws_read(rfbClientPtr cl, char *buf, int len) -{ - int n; - if (cl->sslctx) { - n = rfbssl_read(cl, buf, len); - } else { - n = read(cl->sock, buf, len); - } - return n; -} static int -ws_peek(rfbClientPtr cl, char *buf, int len) +ws_read(void *ctxPtr, char *buf, size_t len) { int n; + rfbClientPtr cl = ctxPtr; if (cl->sslctx) { - n = rfbssl_peek(cl, buf, len); + n = rfbssl_read(cl, buf, len); } else { - while (-1 == (n = recv(cl->sock, buf, len, MSG_PEEK))) { - if (errno != EAGAIN) - break; - } + n = read(cl->sock, buf, len); } return n; } static int -webSocketsDecodeHixie(rfbClientPtr cl, char *dst, int len) -{ - int retlen = 0, n, i, avail, modlen, needlen; - char *buf, *end = NULL; - ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; - - buf = wsctx->codeBufDecode; - - n = ws_peek(cl, buf, len*2+2); - - if (n <= 0) { - /* save errno because rfbErr() will tamper it */ - int olderrno = errno; - rfbErr("%s: peek (%d) %m\n", __func__, errno); - errno = olderrno; - return n; - } - - - /* Base64 encoded WebSockets stream */ - - if (buf[0] == '\xff') { - i = ws_read(cl, buf, 1); /* Consume marker */ - buf++; - n--; - } - if (n == 0) { - errno = EAGAIN; - return -1; - } - if (buf[0] == '\x00') { - i = ws_read(cl, buf, 1); /* Consume marker */ - buf++; - n--; - } - if (n == 0) { - errno = EAGAIN; - return -1; - } - - /* end = memchr(buf, '\xff', len*2+2); */ - end = memchr(buf, '\xff', n); - if (!end) { - end = buf + n; - } - avail = end - buf; - - len -= wsctx->carrylen; - - /* Determine how much base64 data we need */ - modlen = len + (len+2)/3; - needlen = modlen; - if (needlen % 4) { - needlen += 4 - (needlen % 4); - } - - if (needlen > avail) { - /* rfbLog("Waiting for more base64 data\n"); */ - errno = EAGAIN; - return -1; - } - - /* Any carryover from previous decode */ - for (i=0; i < wsctx->carrylen; i++) { - /* rfbLog("Adding carryover %d\n", wsctx->carryBuf[i]); */ - dst[i] = wsctx->carryBuf[i]; - retlen += 1; - } - - /* Decode the rest of what we need */ - buf[needlen] = '\x00'; /* Replace end marker with end of string */ - /* rfbLog("buf: %s\n", buf); */ - n = b64_pton(buf, (unsigned char *)dst+retlen, 2+len); - if (n < len) { - rfbErr("Base64 decode error\n"); - errno = EIO; - return -1; - } - retlen += n; - - /* Consume the data from socket */ - i = ws_read(cl, buf, needlen); - - wsctx->carrylen = n - len; - retlen -= wsctx->carrylen; - for (i=0; i < wsctx->carrylen; i++) { - /* rfbLog("Saving carryover %d\n", dst[retlen + i]); */ - wsctx->carryBuf[i] = dst[retlen + i]; - } - - /* rfbLog("<< webSocketsDecode, retlen: %d\n", retlen); */ - return retlen; -} - -static int -webSocketsDecodeHybi(rfbClientPtr cl, char *dst, int len) -{ - char *buf, *payload; - uint32_t *payload32; - int ret = -1, result = -1; - int total = 0; - ws_mask_t mask; - ws_header_t *header; - int i; - unsigned char opcode; - ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; - int flength, fhlen; - /* int fin; */ /* not used atm */ - - /* rfbLog(" <== %s[%d]: %d cl: %p, wsctx: %p-%p (%d)\n", __func__, gettid(), len, cl, wsctx, (char *)wsctx + sizeof(ws_ctx_t), sizeof(ws_ctx_t)); */ - - if (wsctx->readbuflen) { - /* simply return what we have */ - if (wsctx->readbuflen > len) { - memcpy(dst, wsctx->readbuf + wsctx->readbufstart, len); - result = len; - wsctx->readbuflen -= len; - wsctx->readbufstart += len; - } else { - memcpy(dst, wsctx->readbuf + wsctx->readbufstart, wsctx->readbuflen); - result = wsctx->readbuflen; - wsctx->readbuflen = 0; - wsctx->readbufstart = 0; - } - goto spor; - } - - buf = wsctx->codeBufDecode; - header = (ws_header_t *)wsctx->codeBufDecode; - - ret = ws_peek(cl, buf, B64LEN(len) + WSHLENMAX); - - if (ret < 2) { - /* save errno because rfbErr() will tamper it */ - if (-1 == ret) { - int olderrno = errno; - rfbErr("%s: peek; %m\n", __func__); - errno = olderrno; - } else if (0 == ret) { - result = 0; - } else { - errno = EAGAIN; - } - goto spor; - } - - opcode = header->b0 & 0x0f; - /* fin = (header->b0 & 0x80) >> 7; */ /* not used atm */ - flength = header->b1 & 0x7f; - - /* - * 4.3. Client-to-Server Masking - * - * The client MUST mask all frames sent to the server. A server MUST - * close the connection upon receiving a frame with the MASK bit set to 0. - **/ - if (!(header->b1 & 0x80)) { - rfbErr("%s: got frame without mask\n", __func__, ret); - errno = EIO; - goto spor; - } - - if (flength < 126) { - fhlen = 2; - mask = header->u.m; - } else if (flength == 126 && 4 <= ret) { - flength = WS_NTOH16(header->u.s16.l16); - fhlen = 4; - mask = header->u.s16.m16; - } else if (flength == 127 && 10 <= ret) { - flength = WS_NTOH64(header->u.s64.l64); - fhlen = 10; - mask = header->u.s64.m64; - } else { - /* Incomplete frame header */ - rfbErr("%s: incomplete frame header\n", __func__, ret); - errno = EIO; - goto spor; - } - - /* absolute length of frame */ - total = fhlen + flength + 4; - payload = buf + fhlen + 4; /* header length + mask */ - - if (-1 == (ret = ws_read(cl, buf, total))) { - int olderrno = errno; - rfbErr("%s: read; %m", __func__); - errno = olderrno; - return ret; - } else if (ret < total) { - /* GT TODO: hmm? */ - rfbLog("%s: read; got partial data\n", __func__); - } else { - buf[ret] = '\0'; - } - - /* process 1 frame (32 bit op) */ - payload32 = (uint32_t *)payload; - for (i = 0; i < flength / 4; i++) { - payload32[i] ^= mask.u; - } - /* process the remaining bytes (if any) */ - for (i*=4; i < flength; i++) { - payload[i] ^= mask.c[i % 4]; - } - - switch (opcode) { - case WS_OPCODE_CLOSE: - rfbLog("got closure, reason %d\n", WS_NTOH16(((uint16_t *)payload)[0])); - errno = ECONNRESET; - break; - case WS_OPCODE_TEXT_FRAME: - if (-1 == (flength = b64_pton(payload, (unsigned char *)wsctx->codeBufDecode, sizeof(wsctx->codeBufDecode)))) { - rfbErr("%s: Base64 decode error; %m\n", __func__); - break; - } - payload = wsctx->codeBufDecode; - /* fall through */ - case WS_OPCODE_BINARY_FRAME: - if (flength > len) { - memcpy(wsctx->readbuf, payload + len, flength - len); - wsctx->readbufstart = 0; - wsctx->readbuflen = flength - len; - flength = len; - } - memcpy(dst, payload, flength); - result = flength; - break; - default: - rfbErr("%s: unhandled opcode %d, b0: %02x, b1: %02x\n", __func__, (int)opcode, header->b0, header->b1); - } - - /* single point of return, if someone has questions :-) */ -spor: - /* rfbLog("%s: ret: %d/%d\n", __func__, result, len); */ - return result; -} - -static int webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) { int blen, ret = -1, sz = 0; @@ -830,12 +389,12 @@ webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) header = (ws_header_t *)wsctx->codeBufEncode; if (wsctx->base64) { - opcode = WS_OPCODE_TEXT_FRAME; - /* calculate the resulting size */ - blen = B64LEN(len); + opcode = WS_OPCODE_TEXT_FRAME; + /* calculate the resulting size */ + blen = B64LEN(len); } else { - opcode = WS_OPCODE_BINARY_FRAME; - blen = len; + opcode = WS_OPCODE_BINARY_FRAME; + blen = len; } header->b0 = 0x80 | (opcode & 0x0f); @@ -853,16 +412,16 @@ webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) } if (wsctx->base64) { - if (-1 == (ret = b64_ntop((unsigned char *)src, len, wsctx->codeBufEncode + sz, sizeof(wsctx->codeBufEncode) - sz))) { - rfbErr("%s: Base 64 encode failed\n", __func__); - } else { - if (ret != blen) - rfbErr("%s: Base 64 encode; something weird happened\n", __func__); - ret += sz; - } + if (-1 == (ret = rfbBase64NtoP((unsigned char *)src, len, wsctx->codeBufEncode + sz, sizeof(wsctx->codeBufEncode) - sz))) { + rfbErr("%s: Base 64 encode failed\n", __func__); + } else { + if (ret != blen) + rfbErr("%s: Base 64 encode; something weird happened\n", __func__); + ret += sz; + } } else { - memcpy(wsctx->codeBufEncode + sz, src, len); - ret = sz + len; + memcpy(wsctx->codeBufEncode + sz, src, len); + ret = sz + len; } *dst = wsctx->codeBufEncode; @@ -873,76 +432,28 @@ webSocketsEncodeHybi(rfbClientPtr cl, const char *src, int len, char **dst) int webSocketsEncode(rfbClientPtr cl, const char *src, int len, char **dst) { - return ((ws_ctx_t *)cl->wsctx)->encode(cl, src, len, dst); + return webSocketsEncodeHybi(cl, src, len, dst); } int webSocketsDecode(rfbClientPtr cl, char *dst, int len) { - return ((ws_ctx_t *)cl->wsctx)->decode(cl, dst, len); + ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; + wsctx->ctxInfo.ctxPtr = cl; + return webSocketsDecodeHybi(wsctx, dst, len); } - -/* returns TRUE if client sent a close frame or a single 'end of frame' - * marker was received, FALSE otherwise - * - * Note: This is a Hixie-only hack! - **/ +/** + * This is a stub function that was once used for Hixie-encoding. + * We keep it for API compatibility. + */ rfbBool webSocketCheckDisconnect(rfbClientPtr cl) { - ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; - /* With Base64 encoding we need at least 4 bytes */ - char peekbuf[4]; - int n; - - if (wsctx->version == WEBSOCKETS_VERSION_HYBI) - return FALSE; - - if (cl->sslctx) - n = rfbssl_peek(cl, peekbuf, 4); - else - n = recv(cl->sock, peekbuf, 4, MSG_PEEK); - - if (n <= 0) { - if (n != 0) - rfbErr("%s: peek; %m", __func__); - rfbCloseClient(cl); - return TRUE; - } - - if (peekbuf[0] == '\xff') { - int doclose = 0; - /* Make sure we don't miss a client disconnect on an end frame - * marker. Because we use a peek buffer in some cases it is not - * applicable to wait for more data per select(). */ - switch (n) { - case 3: - if (peekbuf[1] == '\xff' && peekbuf[2] == '\x00') - doclose = 1; - break; - case 2: - if (peekbuf[1] == '\x00') - doclose = 1; - break; - default: - return FALSE; - } - - if (cl->sslctx) - n = rfbssl_read(cl, peekbuf, n); - else - n = read(cl->sock, peekbuf, n); - - if (doclose) { - rfbErr("%s: websocket close frame received\n", __func__); - rfbCloseClient(cl); - } - return TRUE; - } return FALSE; } + /* returns TRUE if there is data waiting to be read in our internal buffer * or if is there any pending data in the buffer of the SSL implementation */ @@ -951,8 +462,8 @@ webSocketsHasDataInBuffer(rfbClientPtr cl) { ws_ctx_t *wsctx = (ws_ctx_t *)cl->wsctx; - if (wsctx && wsctx->readbuflen) - return TRUE; + if (wsctx && wsctx->readlen) + return TRUE; return (cl->sslctx && rfbssl_pending(cl) > 0); } diff --git a/libvncserver/ws_decode.c b/libvncserver/ws_decode.c new file mode 100644 index 0000000..441ebc7 --- /dev/null +++ b/libvncserver/ws_decode.c @@ -0,0 +1,563 @@ +#include "ws_decode.h" +#include "base64.h" + +#include <string.h> +#include <errno.h> + +#define WS_HYBI_MASK_LEN 4 +#define WS_HYBI_HEADER_LEN_SHORT 2 + WS_HYBI_MASK_LEN +#define WS_HYBI_HEADER_LEN_EXTENDED 4 + WS_HYBI_MASK_LEN +#define WS_HYBI_HEADER_LEN_LONG 10 + WS_HYBI_MASK_LEN + +#undef WS_DECODE_DEBUG +/* set to 1 to produce very fine debugging output */ +#define WS_DECODE_DEBUG 0 + +#if WS_DECODE_DEBUG == 1 +#define ws_dbg(fmt, ...) rfbLog((fmt), ##__VA_ARGS) +#else +#define ws_dbg(fmt, ...) +#endif + + +static inline int +isControlFrame(ws_ctx_t *wsctx) +{ + return 0 != (wsctx->header.opcode & 0x08); +} + +static uint64_t +hybiRemaining(ws_ctx_t *wsctx) +{ + return wsctx->header.payloadLen - wsctx->nReadPayload; +} + +static void +hybiDecodeCleanupBasics(ws_ctx_t *wsctx) +{ + /* keep opcode, cleanup rest */ + wsctx->header.opcode = WS_OPCODE_INVALID; + wsctx->header.payloadLen = 0; + wsctx->header.mask.u = 0; + wsctx->header.headerLen = 0; + wsctx->header.data = NULL; + wsctx->header.nRead = 0; + wsctx->nReadPayload = 0; + wsctx->carrylen = 0; + wsctx->readPos = (unsigned char *)wsctx->codeBufDecode; + wsctx->readlen = 0; + wsctx->hybiDecodeState = WS_HYBI_STATE_HEADER_PENDING; + wsctx->writePos = NULL; +} + +static void +hybiDecodeCleanupForContinuation(ws_ctx_t *wsctx) +{ + hybiDecodeCleanupBasics(wsctx); + ws_dbg("clean up frame, but expect continuation with opcode %d\n", wsctx->continuation_opcode); +} + +void +hybiDecodeCleanupComplete(ws_ctx_t *wsctx) +{ + hybiDecodeCleanupBasics(wsctx); + wsctx->continuation_opcode = WS_OPCODE_INVALID; + ws_dbg("cleaned up wsctx completely\n"); +} + + +/** + * Return payload data that has been decoded/unmasked from + * a websocket frame. + * + * @param[out] dst destination buffer + * @param[in] len bytes to copy to destination buffer + * @param[in,out] wsctx internal state of decoding procedure + * @param[out] number of bytes actually written to dst buffer + * @return next hybi decoding state + */ +static int +hybiReturnData(char *dst, int len, ws_ctx_t *wsctx, int *nWritten) +{ + int nextState = WS_HYBI_STATE_ERR; + + /* if we have something already decoded copy and return */ + if (wsctx->readlen > 0) { + /* simply return what we have */ + if (wsctx->readlen > len) { + ws_dbg("copy to %d bytes to dst buffer; readPos=%p, readLen=%d\n", len, wsctx->readPos, wsctx->readlen); + memcpy(dst, wsctx->readPos, len); + *nWritten = len; + wsctx->readlen -= len; + wsctx->readPos += len; + nextState = WS_HYBI_STATE_DATA_AVAILABLE; + } else { + ws_dbg("copy to %d bytes to dst buffer; readPos=%p, readLen=%d\n", wsctx->readlen, wsctx->readPos, wsctx->readlen); + memcpy(dst, wsctx->readPos, wsctx->readlen); + *nWritten = wsctx->readlen; + wsctx->readlen = 0; + wsctx->readPos = NULL; + if (hybiRemaining(wsctx) == 0) { + nextState = WS_HYBI_STATE_FRAME_COMPLETE; + } else { + nextState = WS_HYBI_STATE_DATA_NEEDED; + } + } + ws_dbg("after copy: readPos=%p, readLen=%d\n", wsctx->readPos, wsctx->readlen); + } else { + /* it may happen that we read some bytes but could not decode them, + * in that case, set errno to EAGAIN and return -1 */ + nextState = wsctx->hybiDecodeState; + errno = EAGAIN; + *nWritten = -1; + } + return nextState; +} + +/** + * Read an RFC 6455 websocket frame (IETF hybi working group). + * + * Internal state is updated according to bytes received and the + * decoding of header information. + * + * @param[in] cl client ptr with ptr to raw socket and ws_ctx_t ptr + * @param[out] sockRet emulated recv return value + * @param[out] nPayload number of payload bytes already read + * @return next hybi decoding state; WS_HYBI_STATE_HEADER_PENDING indicates + * that the header was not received completely. + */ +static int +hybiReadHeader(ws_ctx_t *wsctx, int *sockRet, int *nPayload) +{ + int ret; + char *headerDst = wsctx->codeBufDecode + wsctx->header.nRead; + int n = ((uint64_t)WSHLENMAX) - wsctx->header.nRead; + + + ws_dbg("header_read to %p with len=%d\n", headerDst, n); + ret = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, headerDst, n); + ws_dbg("read %d bytes from socket\n", ret); + if (ret <= 0) { + if (-1 == ret) { + /* save errno because rfbErr() will tamper it */ + int olderrno = errno; + rfbErr("%s: read; %s\n", __func__, strerror(errno)); + errno = olderrno; + goto err_cleanup_state; + } else { + *sockRet = 0; + goto err_cleanup_state_sock_closed; + } + } + + wsctx->header.nRead += ret; + if (wsctx->header.nRead < 2) { + /* cannot decode header with less than two bytes */ + goto ret_header_pending; + } + + /* first two header bytes received; interpret header data and get rest */ + wsctx->header.data = (ws_header_t *)wsctx->codeBufDecode; + + wsctx->header.opcode = wsctx->header.data->b0 & 0x0f; + wsctx->header.fin = (wsctx->header.data->b0 & 0x80) >> 7; + if (isControlFrame(wsctx)) { + ws_dbg("is control frame\n"); + /* is a control frame, leave remembered continuation opcode unchanged; + * just check if there is a wrong fragmentation */ + if (wsctx->header.fin == 0) { + + /* we only accept text/binary continuation frames; RFC6455: + * Control frames (see Section 5.5) MAY be injected in the middle of + * a fragmented message. Control frames themselves MUST NOT be + * fragmented. */ + rfbErr("control frame with FIN bit cleared received, aborting\n"); + errno = EPROTO; + goto err_cleanup_state; + } + } else { + ws_dbg("not a control frame\n"); + /* not a control frame, check for continuation opcode */ + if (wsctx->header.opcode == WS_OPCODE_CONTINUATION) { + ws_dbg("cont_frame\n"); + /* do we have state (i.e., opcode) for continuation frame? */ + if (wsctx->continuation_opcode == WS_OPCODE_INVALID) { + rfbErr("no continuation state\n"); + errno = EPROTO; + goto err_cleanup_state; + } + + /* otherwise, set opcode = continuation_opcode */ + wsctx->header.opcode = wsctx->continuation_opcode; + ws_dbg("set opcode to continuation_opcode: %d\n", wsctx->header.opcode); + } else { + if (wsctx->header.fin == 0) { + wsctx->continuation_opcode = wsctx->header.opcode; + } else { + wsctx->continuation_opcode = WS_OPCODE_INVALID; + } + ws_dbg("set continuation_opcode to %d\n", wsctx->continuation_opcode); + } + } + + wsctx->header.payloadLen = (uint64_t)(wsctx->header.data->b1 & 0x7f); + ws_dbg("first header bytes received; opcode=%d lenbyte=%d fin=%d\n", wsctx->header.opcode, wsctx->header.payloadLen, wsctx->header.fin); + + /* + * 4.3. Client-to-Server Masking + * + * The client MUST mask all frames sent to the server. A server MUST + * close the connection upon receiving a frame with the MASK bit set to 0. + **/ + if (!(wsctx->header.data->b1 & 0x80)) { + rfbErr("%s: got frame without mask; ret=%d\n", __func__, ret); + errno = EPROTO; + goto err_cleanup_state; + } + + + if (wsctx->header.payloadLen < 126 && wsctx->header.nRead >= 6) { + wsctx->header.headerLen = WS_HYBI_HEADER_LEN_SHORT; + wsctx->header.mask = wsctx->header.data->u.m; + } else if (wsctx->header.payloadLen == 126 && 8 <= wsctx->header.nRead) { + wsctx->header.headerLen = WS_HYBI_HEADER_LEN_EXTENDED; + wsctx->header.payloadLen = WS_NTOH16(wsctx->header.data->u.s16.l16); + wsctx->header.mask = wsctx->header.data->u.s16.m16; + } else if (wsctx->header.payloadLen == 127 && 14 <= wsctx->header.nRead) { + wsctx->header.headerLen = WS_HYBI_HEADER_LEN_LONG; + wsctx->header.payloadLen = WS_NTOH64(wsctx->header.data->u.s64.l64); + wsctx->header.mask = wsctx->header.data->u.s64.m64; + } else { + /* Incomplete frame header, try again */ + rfbErr("%s: incomplete frame header; ret=%d\n", __func__, ret); + goto ret_header_pending; + } + + char *h = wsctx->codeBufDecode; + int i; + ws_dbg("Header:\n"); + for (i=0; i <10; i++) { + ws_dbg("0x%02X\n", (unsigned char)h[i]); + } + ws_dbg("\n"); + + /* while RFC 6455 mandates that lengths MUST be encoded with the minimum + * number of bytes, it does not specify for the server how to react on + * 'wrongly' encoded frames --- this implementation rejects them*/ + if ((wsctx->header.headerLen > WS_HYBI_HEADER_LEN_SHORT + && wsctx->header.payloadLen < (uint64_t)126) + || (wsctx->header.headerLen > WS_HYBI_HEADER_LEN_EXTENDED + && wsctx->header.payloadLen < (uint64_t)65536)) { + rfbErr("%s: invalid length field; headerLen=%d payloadLen=%llu\n", __func__, wsctx->header.headerLen, wsctx->header.payloadLen); + errno = EPROTO; + goto err_cleanup_state; + } + + /* update write position for next bytes */ + wsctx->writePos = wsctx->codeBufDecode + wsctx->header.nRead; + + /* set payload pointer just after header */ + wsctx->readPos = (unsigned char *)(wsctx->codeBufDecode + wsctx->header.headerLen); + + *nPayload = wsctx->header.nRead - wsctx->header.headerLen; + wsctx->nReadPayload = *nPayload; + + ws_dbg("header complete: state=%d headerlen=%d payloadlen=%llu writeTo=%p nPayload=%d\n", wsctx->hybiDecodeState, wsctx->header.headerLen, wsctx->header.payloadLen, wsctx->writePos, *nPayload); + + return WS_HYBI_STATE_DATA_NEEDED; + +ret_header_pending: + errno = EAGAIN; + *sockRet = -1; + return WS_HYBI_STATE_HEADER_PENDING; + +err_cleanup_state: + *sockRet = -1; +err_cleanup_state_sock_closed: + hybiDecodeCleanupComplete(wsctx); + return WS_HYBI_STATE_ERR; +} + +static int +hybiWsFrameComplete(ws_ctx_t *wsctx) +{ + return wsctx != NULL && hybiRemaining(wsctx) == 0; +} + +static char * +hybiPayloadStart(ws_ctx_t *wsctx) +{ + return wsctx->codeBufDecode + wsctx->header.headerLen; +} + + +/** + * Read the remaining payload bytes from associated raw socket. + * + * - try to read remaining bytes from socket + * - unmask all multiples of 4 + * - if frame incomplete but some bytes are left, these are copied to + * the carry buffer + * - if opcode is TEXT: Base64-decode all unmasked received bytes + * - set state for reading decoded data + * - reset write position to begin of buffer (+ header) + * --> before we retrieve more data we let the caller clear all bytes + * from the reception buffer + * - execute return data routine + * + * Sets errno corresponding to what it gets from the underlying + * socket or EPROTO if some invalid data is in the received frame + * or ECONNRESET if a close reason + message is received. EIO is used if + * an internal sanity check fails. + * + * @param[in] cl client ptr with raw socket reference + * @param[out] dst destination buffer + * @param[in] len size of destination buffer + * @param[out] sockRet emulated recv return value + * @param[in] nInBuf number of undecoded bytes before writePos from header read + * @return next hybi decode state + */ +static int +hybiReadAndDecode(ws_ctx_t *wsctx, char *dst, int len, int *sockRet, int nInBuf) +{ + int n; + int i; + int toReturn; /* number of data bytes to return */ + int toDecode; /* number of bytes to decode starting at wsctx->writePos */ + int bufsize; + int nextRead; + unsigned char *data; + uint32_t *data32; + + /* if data was carried over, copy to start of buffer */ + memcpy(wsctx->writePos, wsctx->carryBuf, wsctx->carrylen); + wsctx->writePos += wsctx->carrylen; + + /* -1 accounts for potential '\0' terminator for base64 decoding */ + bufsize = wsctx->codeBufDecode + ARRAYSIZE(wsctx->codeBufDecode) - wsctx->writePos - 1; + ws_dbg("bufsize=%d\n", bufsize); + if (hybiRemaining(wsctx) > bufsize) { + nextRead = bufsize; + } else { + nextRead = hybiRemaining(wsctx); + } + + ws_dbg("calling read with buf=%p and len=%d (decodebuf=%p headerLen=%d)\n", wsctx->writePos, nextRead, wsctx->codeBufDecode, wsctx->header.headerLen); + + if (nextRead > 0) { + /* decode more data */ + if (-1 == (n = wsctx->ctxInfo.readFunc(wsctx->ctxInfo.ctxPtr, wsctx->writePos, nextRead))) { + int olderrno = errno; + rfbErr("%s: read; %s", __func__, strerror(errno)); + errno = olderrno; + *sockRet = -1; + return WS_HYBI_STATE_ERR; + } else if (n == 0) { + *sockRet = 0; + return WS_HYBI_STATE_ERR; + } else { + ws_dbg("read %d bytes from socket; nRead=%d\n", n, wsctx->nReadPayload); + } + } else { + n = 0; + } + + wsctx->nReadPayload += n; + wsctx->writePos += n; + + if (hybiRemaining(wsctx) == 0) { + wsctx->hybiDecodeState = WS_HYBI_STATE_FRAME_COMPLETE; + } + + /* number of not yet unmasked payload bytes: what we read here + what was + * carried over + what was read with the header */ + toDecode = n + wsctx->carrylen + nInBuf; + ws_dbg("toDecode=%d from n=%d carrylen=%d headerLen=%d\n", toDecode, n, wsctx->carrylen, wsctx->header.headerLen); + if (toDecode < 0) { + rfbErr("%s: internal error; negative number of bytes to decode: %d", __func__, toDecode); + errno=EIO; + *sockRet = -1; + return WS_HYBI_STATE_ERR; + } + + /* for a possible base64 decoding, we decode multiples of 4 bytes until + * the whole frame is received and carry over any remaining bytes in the carry buf*/ + data = (unsigned char *)(wsctx->writePos - toDecode); + data32= (uint32_t *)data; + + for (i = 0; i < (toDecode >> 2); i++) { + data32[i] ^= wsctx->header.mask.u; + } + ws_dbg("mask decoding; i=%d toDecode=%d\n", i, toDecode); + + if (wsctx->hybiDecodeState == WS_HYBI_STATE_FRAME_COMPLETE) { + /* process the remaining bytes (if any) */ + for (i*=4; i < toDecode; i++) { + data[i] ^= wsctx->header.mask.c[i % 4]; + } + + /* all data is here, no carrying */ + wsctx->carrylen = 0; + } else { + /* carry over remaining, non-multiple-of-four bytes */ + wsctx->carrylen = toDecode - (i * 4); + if (wsctx->carrylen < 0 || wsctx->carrylen > ARRAYSIZE(wsctx->carryBuf)) { + rfbErr("%s: internal error, invalid carry over size: carrylen=%d, toDecode=%d, i=%d", __func__, wsctx->carrylen, toDecode, i); + *sockRet = -1; + errno = EIO; + return WS_HYBI_STATE_ERR; + } + ws_dbg("carrying over %d bytes from %p to %p\n", wsctx->carrylen, wsctx->writePos + (i * 4), wsctx->carryBuf); + memcpy(wsctx->carryBuf, data + (i * 4), wsctx->carrylen); + wsctx->writePos -= wsctx->carrylen; + } + + toReturn = toDecode - wsctx->carrylen; + + switch (wsctx->header.opcode) { + case WS_OPCODE_CLOSE: + /* this data is not returned as payload data */ + if (hybiWsFrameComplete(wsctx)) { + *(wsctx->writePos) = '\0'; + ws_dbg("got close cmd %d, reason %d: %s\n", (int)(wsctx->writePos - hybiPayloadStart(wsctx)), WS_NTOH16(((uint16_t *)hybiPayloadStart(wsctx))[0]), &hybiPayloadStart(wsctx)[2]); + errno = ECONNRESET; + *sockRet = -1; + return WS_HYBI_STATE_FRAME_COMPLETE; + } else { + ws_dbg("got close cmd; waiting for %d more bytes to arrive\n", hybiRemaining(wsctx)); + *sockRet = -1; + errno = EAGAIN; + return WS_HYBI_STATE_CLOSE_REASON_PENDING; + } + break; + case WS_OPCODE_TEXT_FRAME: + data[toReturn] = '\0'; + ws_dbg("Initiate Base64 decoding in %p with max size %d and '\\0' at %p\n", data, bufsize, data + toReturn); + if (-1 == (wsctx->readlen = rfbBase64PtoN((char *)data, data, bufsize))) { + rfbErr("%s: Base64 decode error; %s\n", __func__, strerror(errno)); + } + wsctx->writePos = hybiPayloadStart(wsctx); + break; + case WS_OPCODE_BINARY_FRAME: + wsctx->readlen = toReturn; + wsctx->writePos = hybiPayloadStart(wsctx); + ws_dbg("set readlen=%d writePos=%p\n", wsctx->readlen, wsctx->writePos); + break; + default: + rfbErr("%s: unhandled opcode %d, b0: %02x, b1: %02x\n", __func__, (int)wsctx->header.opcode, wsctx->header.data->b0, wsctx->header.data->b1); + } + wsctx->readPos = data; + + return hybiReturnData(dst, len, wsctx, sockRet); +} + +/** + * Read function for websocket-socket emulation. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-------+-+-------------+-------------------------------+ + * |F|R|R|R| opcode|M| Payload len | Extended payload length | + * |I|S|S|S| (4) |A| (7) | (16/64) | + * |N|V|V|V| |S| | (if payload len==126/127) | + * | |1|2|3| |K| | | + * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + * | Extended payload length continued, if payload len == 127 | + * + - - - - - - - - - - - - - - - +-------------------------------+ + * | |Masking-key, if MASK set to 1 | + * +-------------------------------+-------------------------------+ + * | Masking-key (continued) | Payload Data | + * +-------------------------------- - - - - - - - - - - - - - - - + + * : Payload Data continued ... : + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * | Payload Data continued ... | + * +---------------------------------------------------------------+ + * + * Using the decode buffer, this function: + * - reads the complete header from the underlying socket + * - reads any remaining data bytes + * - unmasks the payload data using the provided mask + * - decodes Base64 encoded text data + * - copies len bytes of decoded payload data into dst + * + * Emulates a read call on a socket. + */ +int +webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len) +{ + int result = -1; + /* int fin; */ /* not used atm */ + + ws_dbg("%s_enter: len=%d; " + "CTX: readlen=%d readPos=%p " + "writeTo=%p " + "state=%d payloadtoRead=%d payloadRemaining=%llu " + " nReadPayload=%d carrylen=%d carryBuf=%p\n", + __func__, len, + wsctx->readlen, wsctx->readPos, + wsctx->writePos, + wsctx->hybiDecodeState, wsctx->header.payloadLen, hybiRemaining(wsctx), + wsctx->nReadPayload, wsctx->carrylen, wsctx->carryBuf); + + switch (wsctx->hybiDecodeState){ + int nInBuf; + case WS_HYBI_STATE_HEADER_PENDING: + wsctx->hybiDecodeState = hybiReadHeader(wsctx, &result, &nInBuf); + if (wsctx->hybiDecodeState == WS_HYBI_STATE_ERR) { + goto spor; + } + if (wsctx->hybiDecodeState != WS_HYBI_STATE_HEADER_PENDING) { + + /* when header is complete, try to read some more data */ + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result, nInBuf); + } + break; + case WS_HYBI_STATE_DATA_AVAILABLE: + wsctx->hybiDecodeState = hybiReturnData(dst, len, wsctx, &result); + break; + case WS_HYBI_STATE_DATA_NEEDED: + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result, 0); + break; + case WS_HYBI_STATE_CLOSE_REASON_PENDING: + wsctx->hybiDecodeState = hybiReadAndDecode(wsctx, dst, len, &result, 0); + break; + default: + /* invalid state */ + rfbErr("%s: called with invalid state %d\n", wsctx->hybiDecodeState); + result = -1; + errno = EIO; + wsctx->hybiDecodeState = WS_HYBI_STATE_ERR; + } + + /* single point of return, if someone has questions :-) */ +spor: + if (wsctx->hybiDecodeState == WS_HYBI_STATE_FRAME_COMPLETE) { + ws_dbg("frame received successfully, cleaning up: read=%d hlen=%d plen=%d\n", wsctx->header.nRead, wsctx->header.headerLen, wsctx->header.payloadLen); + if (wsctx->header.fin && !isControlFrame(wsctx)) { + /* frame finished, cleanup state */ + hybiDecodeCleanupComplete(wsctx); + } else { + /* always retain continuation opcode for unfinished data frames + * or control frames, which may interleave with data frames */ + hybiDecodeCleanupForContinuation(wsctx); + } + } else if (wsctx->hybiDecodeState == WS_HYBI_STATE_ERR) { + hybiDecodeCleanupComplete(wsctx); + } + + ws_dbg("%s_exit: len=%d; " + "CTX: readlen=%d readPos=%p " + "writePos=%p " + "state=%d payloadtoRead=%d payloadRemaining=%d " + "nRead=%d carrylen=%d carryBuf=%p " + "result=%d " + "errno=%d\n", + __func__, len, + wsctx->readlen, wsctx->readPos, + wsctx->writePos, + wsctx->hybiDecodeState, wsctx->header.payloadLen, hybiRemaining(wsctx), + wsctx->nReadPayload, wsctx->carrylen, wsctx->carryBuf, + result, + errno); + return result; +} diff --git a/libvncserver/ws_decode.h b/libvncserver/ws_decode.h new file mode 100644 index 0000000..709477a --- /dev/null +++ b/libvncserver/ws_decode.h @@ -0,0 +1,145 @@ +#ifndef _WS_DECODE_H_ +#define _WS_DECODE_H_ + +#include <stdint.h> +#include <rfb/rfb.h> + +#if defined(__APPLE__) + +#include <libkern/OSByteOrder.h> +#define WS_NTOH64(n) OSSwapBigToHostInt64(n) +#define WS_NTOH32(n) OSSwapBigToHostInt32(n) +#define WS_NTOH16(n) OSSwapBigToHostInt16(n) +#define WS_HTON64(n) OSSwapHostToBigInt64(n) +#define WS_HTON16(n) OSSwapHostToBigInt16(n) + +#else + +#define WS_NTOH64(n) htobe64(n) +#define WS_NTOH32(n) htobe32(n) +#define WS_NTOH16(n) htobe16(n) +#define WS_HTON64(n) htobe64(n) +#define WS_HTON16(n) htobe16(n) + +#endif + +#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3) +#define WSHLENMAX 14LL /* 2 + sizeof(uint64_t) + sizeof(uint32_t) */ +#define WS_HYBI_MASK_LEN 4 + +#define ARRAYSIZE(a) ((sizeof(a) / sizeof((a[0]))) / (size_t)(!(sizeof(a) % sizeof((a[0]))))) + +struct ws_ctx_s; +typedef struct ws_ctx_s ws_ctx_t; + +typedef int (*wsEncodeFunc)(rfbClientPtr cl, const char *src, int len, char **dst); +typedef int (*wsDecodeFunc)(ws_ctx_t *wsctx, char *dst, int len); + +typedef int (*wsReadFunc)(void *ctx, char *dst, size_t len); + +typedef struct ctxInfo_s{ + void *ctxPtr; + wsReadFunc readFunc; +} ctxInfo_t; + +enum { + /* header not yet received completely */ + WS_HYBI_STATE_HEADER_PENDING, + /* data available */ + WS_HYBI_STATE_DATA_AVAILABLE, + WS_HYBI_STATE_DATA_NEEDED, + /* received a complete frame */ + WS_HYBI_STATE_FRAME_COMPLETE, + /* received part of a 'close' frame */ + WS_HYBI_STATE_CLOSE_REASON_PENDING, + /* */ + WS_HYBI_STATE_ERR +}; + +typedef union ws_mask_s { + char c[4]; + uint32_t u; +} ws_mask_t; + +/* XXX: The union and the structs do not need to be named. + * We are working around a bug present in GCC < 4.6 which prevented + * it from recognizing anonymous structs and unions. + * See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=4784 + */ +typedef struct +#if __GNUC__ +__attribute__ ((__packed__)) +#endif +ws_header_s { + unsigned char b0; + unsigned char b1; + union { + struct +#if __GNUC__ + __attribute__ ((__packed__)) +#endif + { + uint16_t l16; + ws_mask_t m16; + } s16; + struct +#if __GNUC__ +__attribute__ ((__packed__)) +#endif + { + uint64_t l64; + ws_mask_t m64; + } s64; + ws_mask_t m; + } u; +} ws_header_t; + +typedef struct ws_header_data_s { + ws_header_t *data; + /** bytes read */ + int nRead; + /** mask value */ + ws_mask_t mask; + /** length of frame header including payload len, but without mask */ + int headerLen; + /** length of the payload data */ + uint64_t payloadLen; + /** opcode */ + unsigned char opcode; + /** fin bit */ + unsigned char fin; +} ws_header_data_t; + +typedef struct ws_ctx_s { + char codeBufDecode[2048 + WSHLENMAX]; /* base64 + maximum frame header length */ + char codeBufEncode[B64LEN(UPDATE_BUF_SIZE) + WSHLENMAX]; /* base64 + maximum frame header length */ + char *writePos; + unsigned char *readPos; + int readlen; + int hybiDecodeState; + char carryBuf[3]; /* For base64 carry-over */ + int carrylen; + int base64; + ws_header_data_t header; + uint64_t nReadPayload; + unsigned char continuation_opcode; + wsEncodeFunc encode; + wsDecodeFunc decode; + ctxInfo_t ctxInfo; +} ws_ctx_t; + +enum +{ + WS_OPCODE_CONTINUATION = 0x00, + WS_OPCODE_TEXT_FRAME = 0x01, + WS_OPCODE_BINARY_FRAME = 0x02, + WS_OPCODE_CLOSE = 0x08, + WS_OPCODE_PING = 0x09, + WS_OPCODE_PONG = 0x0A, + WS_OPCODE_INVALID = 0xFF +}; + +int webSocketsDecodeHybi(ws_ctx_t *wsctx, char *dst, int len); + +void hybiDecodeCleanupComplete(ws_ctx_t *wsctx); +#endif diff --git a/rfb/rfbclient.h b/rfb/rfbclient.h index 053bd42..72d672a 100644 --- a/rfb/rfbclient.h +++ b/rfb/rfbclient.h @@ -140,6 +140,7 @@ typedef union _rfbCredential char *x509CACrlFile; char *x509ClientCertFile; char *x509ClientKeyFile; + uint8_t x509CrlVerifyMode; /* Only required for OpenSSL - see meanings below */ } x509Credential; /** Plain (VeNCrypt), MSLogon (UltraVNC) */ struct @@ -152,6 +153,13 @@ typedef union _rfbCredential #define rfbCredentialTypeX509 1 #define rfbCredentialTypeUser 2 +/* When using OpenSSL, CRLs can be included in both the x509CACrlFile and appended + to the x509CACertFile as is common with OpenSSL. When rfbX509CrlVerifyAll is + specified the CRL list must include CRLs for all certificates in the chain */ +#define rfbX509CrlVerifyNone 0 /* No CRL checking is performed */ +#define rfbX509CrlVerifyClient 1 /* Only the leaf server certificate is checked */ +#define rfbX509CrlVerifyAll 2 /* All certificates in the server chain are checked */ + struct _rfbClient; /** diff --git a/test/wsmaketestframe.py b/test/wsmaketestframe.py new file mode 100755 index 0000000..fc03e39 --- /dev/null +++ b/test/wsmaketestframe.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# Copyright (C)2017 Andreas Weigel. All Rights Reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import websockets +import base64 + +''' + Create websocket frames for the wstest websocket decoding unit test. + + Generates c ws_frame_test structure definitions + included by wstest.c. +''' + + +def add_field(s, name, value, first=False): + deli = ",\n\t\t" + if first: + deli = "\t\t" + s += "{2}.{0}={1}".format(name, value, deli) + return s + + +class Testframe(): + def __init__(self, frame, descr, modify_bytes={}, experrno=0, mask=True, opcode_overwrite=False): + self.frame = frame + self.descr = descr + self.modify_bytes = modify_bytes + self.experrno = experrno + self.b64 = True if frame.opcode == 1 or opcode_overwrite == 1 else False + self.mask = mask + + def to_carray_initializer(self, buf): + values = [] + for i in range(len(buf)): + values.append("0X{0:02X}".format(buf[i])) + + if self.modify_bytes != {}: + for k in self.modify_bytes: + values[k] = "0X{0:02X}".format(self.modify_bytes[k]) + + return "{{{0}}}".format(",".join(values)) + + + def set_frame_buf(self, buf): + self.frame_carray = self.to_carray_initializer(buf) + self.framelen = len(buf) + + def __str__(self): + print("processing frame: {0}".format(self.descr)) + the_frame = self.frame + if self.b64: + olddata = self.frame.data + newdata = base64.b64encode(self.frame.data) + #print("converting\n{0}\nto{1}\n".format(olddata, newdata)) + the_frame = websockets.framing.Frame(self.frame.fin, self.frame.opcode, base64.b64encode(olddata)) + websockets.framing.write_frame(the_frame, self.set_frame_buf, self.mask) + s = "\t{\n" + s = add_field(s, "frame", "{0}".format(self.frame_carray), True) + s = add_field(s, "expectedDecodeBuf", self.to_carray_initializer(self.frame.data)) + s = add_field(s, "frame_len", self.framelen) + s = add_field(s, "raw_payload_len", len(self.frame.data)) + s = add_field(s, "expected_errno", self.experrno) + s = add_field(s, "descr", "\"{0}\"".format(self.descr)) + s = add_field(s, "i", "0") + s = add_field(s, "simulate_sock_malfunction_at", "0") + s = add_field(s, "errno_val", "0") + s = add_field(s, "close_sock_at", "0") + s += "\n\t}" + return s + +### create test frames +flist = [] +### standard text frames with different lengths +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Testit", encoding="utf-8")), "Short valid text frame")) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Frame2 does contain much more text and even goes beyond the 126 byte len field. Frame2 does contain much more text and even goes beyond the 126 byte len field.", encoding="utf-8")), + "Mid-long valid text frame")) +#flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray([(x % 26) + 65 for x in range(100000)])), "100k text frame (ABC..YZABC..)")) + +### standard binary frames with different lengths +flist.append(Testframe(websockets.framing.Frame(1, 2, bytearray("Testit", encoding="utf-8")), "Short valid binary frame")) +flist.append(Testframe(websockets.framing.Frame(1, 2, bytearray("Frame2 does contain much more text and even goes beyond the 126 byte len field. Frame2 does contain much more text and even goes beyond the 126 byte len field.", encoding="utf-8")), + "Mid-long valid binary frame")) +#flist.append(Testframe(websockets.framing.Frame(1, 2, bytearray([(x % 26) + 65 for x in range(100000)])), "100k binary frame (ABC..YZABC..)")) + +### some conn reset frames, one with no close message, one with close message +flist.append(Testframe(websockets.framing.Frame(1, 8, bytearray(list([0x03, 0xEB]))), "Close frame (Reason 1003)", experrno="ECONNRESET")) +flist.append(Testframe(websockets.framing.Frame(1, 8, bytearray(list([0x03, 0xEB])) + bytearray("I'm a close reason and much more than that!", encoding="utf-8")), "Close frame (Reason 1003) and msg", experrno="ECONNRESET")) + +### invalid header values +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("Testit", encoding="utf-8")), "Invalid frame: Wrong masking", experrno="EPROTO", mask=False)) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("..Lore Ipsum", encoding="utf-8")), "Invalid frame: Length of < 126 with add. 16 bit len field", experrno="EPROTO", modify_bytes={ 1: 0xFE, 2: 0x00, 3: 0x0F})) +flist.append(Testframe(websockets.framing.Frame(1, 1, bytearray("........Lore Ipsum", encoding="utf-8")), "Invalid frame: Length of < 126 with add. 64 bit len field", experrno="EPROTO", modify_bytes={ 1: 0xFF, 2: 0x00, 3: 0x00, 4: 0x00, 5: 0x00, 6: 0x00, 7: 0x00, 8: 0x80, 9: 0x40})) + +frag1 = websockets.framing.Frame(0, 1, bytearray("This is a fragmented websocket...", encoding="utf-8")) +frag2 = websockets.framing.Frame(0, 0, bytearray("... and it goes on...", encoding="utf-8")) +frag3 = websockets.framing.Frame(1, 0, bytearray("and on and stop", encoding="utf-8")) +flist.append(Testframe(frag1, "Continuation test frag1")) +flist.append(Testframe(frag2, "Continuation test frag2", opcode_overwrite=1)) +flist.append(Testframe(frag3, "Continuation test frag3", opcode_overwrite=1)) + +s = "struct ws_frame_test tests[] = {\n" +for i in range(len(flist)): + s += flist[i].__str__() + if (i + 1 < len(flist)): + s += "," + s += "\n" +s += "};\n" + +with open("wstestdata.inc", "w") as cdatafile: + cdatafile.write(s) diff --git a/test/wstest.c b/test/wstest.c new file mode 100644 index 0000000..042b75b --- /dev/null +++ b/test/wstest.c @@ -0,0 +1,206 @@ +/* + * Copyright (C)2017 Andreas Weigel. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _WIN32 + +#include <ws_decode.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <unistd.h> +#include <errno.h> + +/* incoming data frames should not be larger than that */ +#define TEST_BUF_SIZE B64LEN(131072) + WSHLENMAX + +/* seed is fixed deliberately to get reproducible test cases */ +#define RND_SEED 100 + +enum { + OK, + FAIL_DATA, + FAIL_ERRNO, + FAIL_CLOSED, +}; + +const char *result_descr[] = { + "", + "Data buffers do not match", + "Wrong errno", + "Wrongly reported closed socket", + "Internal test error" +}; + +struct ws_frame_test { + char frame[TEST_BUF_SIZE]; + char *pos; + char expectedDecodeBuf[TEST_BUF_SIZE]; + uint64_t n_compare; + uint64_t frame_len; + uint64_t raw_payload_len; + int expected_errno; + const char *descr; + int ret_bytes[16]; + int ret_bytes_len; + int i; + int simulate_sock_malfunction_at; + int errno_val; + int close_sock_at; +}; + +#include "wstestdata.inc" + +char el_log[1000000]; +char *el_pos; + +static void logtest(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + size_t left = el_log + sizeof(el_log) - el_pos; + size_t off = vsnprintf(el_pos, left, fmt, args); + el_pos += off; + va_end(args); +} + +static int emu_read(void *ctx, char *dst, size_t len); + +static int emu_read(void *ctx, char *dst, size_t len) +{ + struct ws_frame_test *ft = (struct ws_frame_test *)ctx; + ssize_t nret; + int r; + ssize_t modu; + + rfbLog("emu_read called with dst=%p and len=%lu\n", dst, len); + if (ft->simulate_sock_malfunction_at > 0 && ft->simulate_sock_malfunction_at == ft->i) { + rfbLog("simulating IO error with errno=%d\n", ft->errno_val); + errno = ft->errno_val; + return -1; + } + + /* return something */ + r = rand(); + modu = (ft->frame + ft->frame_len) - ft->pos; + rfbLog("r=%d modu=%ld frame=%p pos=%p\n", r, modu, ft->frame, ft->pos); + nret = (r % modu) + 1; + nret = nret > len ? len : nret; + + rfbLog("copy and return %ld bytes\n", nret); + memcpy(dst, ft->pos, nret); + ft->pos += nret; + rfbLog("leaving %s; pos=%p framebuf=%p nret=%ld\n", __func__, ft->pos, ft->frame, nret); + return nret; +} + +static uint64_t run_test(struct ws_frame_test *ft, ws_ctx_t *ctx) +{ + uint64_t nleft = ft->raw_payload_len; + char dstbuf[ft->raw_payload_len]; + char *dst = dstbuf; + ssize_t n; + + ft->pos = ft->frame; + + ctx->ctxInfo.ctxPtr = (void *)ft; + + while (nleft > 0) { + rfbLog("calling ws_decode with dst=%p, len=%lu\n", dst, nleft); + n = ctx->decode(ctx, dst, nleft); + rfbLog("read n=%ld\n", n); + if (n == 0) { + if (ft->close_sock_at > 0) { + return OK; + } else { + return FAIL_CLOSED; + } + } else if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + /* ok, just call again */ + } else { + if (ft->expected_errno == errno) { + rfbLog("errno=%d as expected\n", errno); + return OK; + } else { + rfbLog("errno=%d != expected(%d)\n", errno, ft->expected_errno); + return FAIL_ERRNO; + } + } + } else { + nleft -= n; + dst += n; + rfbLog("read n=%ld from decode; dst=%p, nleft=%lu\n", n, dst, nleft); + } + } + + if (memcmp(ft->expectedDecodeBuf, dstbuf, ft->raw_payload_len) != 0) { + ft->expectedDecodeBuf[ft->raw_payload_len] = '\0'; + dstbuf[ft->raw_payload_len] = '\0'; + rfbLog("decoded result not equal:\nexpected:\n%s\ngot\n%s\n\n", ft->expectedDecodeBuf, dstbuf); + return FAIL_DATA; + } + + return OK; +} + + +int main() +{ + ws_ctx_t ctx; + int retall= 0; + int i; + srand(RND_SEED); + + hybiDecodeCleanupComplete(&ctx); + ctx.decode = webSocketsDecodeHybi; + ctx.ctxInfo.readFunc = emu_read; + rfbLog = logtest; + rfbErr = logtest; + + for (i = 0; i < ARRAYSIZE(tests); i++) { + int ret; + + /* reset output log buffer to begin */ + el_pos = el_log; + + ret = run_test(&tests[i], &ctx); + printf("%s: \"%s\"\n", ret == 0 ? "PASS" : "FAIL", tests[i].descr); + if (ret != 0) { + *el_pos = '\0'; + printf("%s", el_log); + retall = -1; + } + } + return retall; +} + +#else + +int main() { + return 0; +} + +#endif diff --git a/test/wstestdata.inc b/test/wstestdata.inc new file mode 100644 index 0000000..595b891 --- /dev/null +++ b/test/wstestdata.inc @@ -0,0 +1,146 @@ +struct ws_frame_test tests[] = { + { + .frame={0X81,0X88,0X2F,0X2A,0X17,0X41,0X79,0X6D,0X41,0X3B,0X4B,0X6D,0X7B,0X71}, + .expectedDecodeBuf={0X54,0X65,0X73,0X74,0X69,0X74}, + .frame_len=14, + .raw_payload_len=6, + .expected_errno=0, + .descr="Short valid text frame", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X81,0XFE,0X00,0XD4,0X66,0X27,0XE5,0X24,0X34,0X49,0XAF,0X4C,0X04,0X70,0XB0,0X5D,0X2F,0X60,0XB7,0X52,0X3C,0X7F,0XA8,0X43,0X3F,0X15,0XDC,0X51,0X02,0X60,0XA3,0X54,0X04,0X4E,0XA7,0X50,0X02,0X70,0XAB,0X4B,0X2F,0X60,0XD4,0X52,0X05,0X4A,0XB0,0X43,0X02,0X60,0XB3,0X10,0X02,0X64,0XA7,0X4C,0X04,0X4A,0XB4,0X43,0X3C,0X7F,0XBF,0X48,0X04,0X4E,0XA7,0X4A,0X04,0X15,0XB3,0X5E,0X2F,0X60,0XAF,0X48,0X03,0X70,0XDC,0X51,0X3C,0X64,0XA7,0X14,0X07,0X60,0XB0,0X43,0X2B,0X73,0XAC,0X16,0X2F,0X60,0XAF,0X11,0X02,0X60,0XB0,0X43,0X04,0X60,0XB3,0X51,0X2F,0X60,0XBF,0X54,0X3C,0X70,0X9D,0X4F,0X2A,0X4E,0XA7,0X63,0X05,0X4A,0XA3,0X50,0X3C,0X73,0XAC,0X43,0X3C,0X60,0XDC,0X48,0X05,0X5E,0XA7,0X4E,0X04,0X15,0XD0,0X14,0X3F,0X70,0X89,0X51,0X2F,0X60,0XD4,0X15,0X3F,0X15,0X82,0X43,0X04,0X70,0XDC,0X5D,0X3C,0X74,0XA7,0X14,0X3C,0X7F,0X8D,0X14,0X2F,0X60,0XA3,0X51,0X3C,0X64,0XA7,0X48,0X02,0X4A,0XB3,0X51,0X2F,0X60,0X81,0X52,0X3C,0X7F,0XA8,0X43,0X3F,0X4A,0XB3,0X11,0X04,0X15,0XD0,0X4F,0X2F,0X6F,0XB7,0X4B,0X3C,0X74,0XA4,0X5C,0X2B,0X4D,0XBC,0X43,0X3F,0X49,0X89,0X14,0X3C,0X74,0XA7,0X57,0X3C,0X70,0XD1,0X43,0X3C,0X4A,0X89,0X48,0X04,0X60,0XB4,0X51}, + .expectedDecodeBuf={0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E,0X20,0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E}, + .frame_len=220, + .raw_payload_len=159, + .expected_errno=0, + .descr="Mid-long valid text frame", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X82,0X86,0XDD,0X9B,0XD8,0X56,0X89,0XFE,0XAB,0X22,0XB4,0XEF}, + .expectedDecodeBuf={0X54,0X65,0X73,0X74,0X69,0X74}, + .frame_len=12, + .raw_payload_len=6, + .expected_errno=0, + .descr="Short valid binary frame", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X82,0XFE,0X00,0X9F,0XB5,0X6E,0X7F,0X4C,0XF3,0X1C,0X1E,0X21,0XD0,0X5C,0X5F,0X28,0XDA,0X0B,0X0C,0X6C,0XD6,0X01,0X11,0X38,0XD4,0X07,0X11,0X6C,0XD8,0X1B,0X1C,0X24,0X95,0X03,0X10,0X3E,0XD0,0X4E,0X0B,0X29,0XCD,0X1A,0X5F,0X2D,0XDB,0X0A,0X5F,0X29,0XC3,0X0B,0X11,0X6C,0XD2,0X01,0X1A,0X3F,0X95,0X0C,0X1A,0X35,0XDA,0X00,0X1B,0X6C,0XC1,0X06,0X1A,0X6C,0X84,0X5C,0X49,0X6C,0XD7,0X17,0X0B,0X29,0X95,0X02,0X1A,0X22,0X95,0X08,0X16,0X29,0XD9,0X0A,0X51,0X6C,0XF3,0X1C,0X1E,0X21,0XD0,0X5C,0X5F,0X28,0XDA,0X0B,0X0C,0X6C,0XD6,0X01,0X11,0X38,0XD4,0X07,0X11,0X6C,0XD8,0X1B,0X1C,0X24,0X95,0X03,0X10,0X3E,0XD0,0X4E,0X0B,0X29,0XCD,0X1A,0X5F,0X2D,0XDB,0X0A,0X5F,0X29,0XC3,0X0B,0X11,0X6C,0XD2,0X01,0X1A,0X3F,0X95,0X0C,0X1A,0X35,0XDA,0X00,0X1B,0X6C,0XC1,0X06,0X1A,0X6C,0X84,0X5C,0X49,0X6C,0XD7,0X17,0X0B,0X29,0X95,0X02,0X1A,0X22,0X95,0X08,0X16,0X29,0XD9,0X0A,0X51}, + .expectedDecodeBuf={0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E,0X20,0X46,0X72,0X61,0X6D,0X65,0X32,0X20,0X64,0X6F,0X65,0X73,0X20,0X63,0X6F,0X6E,0X74,0X61,0X69,0X6E,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X65,0X78,0X74,0X20,0X61,0X6E,0X64,0X20,0X65,0X76,0X65,0X6E,0X20,0X67,0X6F,0X65,0X73,0X20,0X62,0X65,0X79,0X6F,0X6E,0X64,0X20,0X74,0X68,0X65,0X20,0X31,0X32,0X36,0X20,0X62,0X79,0X74,0X65,0X20,0X6C,0X65,0X6E,0X20,0X66,0X69,0X65,0X6C,0X64,0X2E}, + .frame_len=167, + .raw_payload_len=159, + .expected_errno=0, + .descr="Mid-long valid binary frame", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X88,0X82,0X6B,0X33,0X77,0X94,0X68,0XD8}, + .expectedDecodeBuf={0X03,0XEB}, + .frame_len=8, + .raw_payload_len=2, + .expected_errno=ECONNRESET, + .descr="Close frame (Reason 1003)", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X88,0XAD,0X4B,0XA1,0XCE,0XE8,0X48,0X4A,0X87,0XCF,0X26,0X81,0XAF,0XC8,0X28,0XCD,0XA1,0X9B,0X2E,0X81,0XBC,0X8D,0X2A,0XD2,0XA1,0X86,0X6B,0XC0,0XA0,0X8C,0X6B,0XCC,0XBB,0X8B,0X23,0X81,0XA3,0X87,0X39,0XC4,0XEE,0X9C,0X23,0XC0,0XA0,0XC8,0X3F,0XC9,0XAF,0X9C,0X6A}, + .expectedDecodeBuf={0X03,0XEB,0X49,0X27,0X6D,0X20,0X61,0X20,0X63,0X6C,0X6F,0X73,0X65,0X20,0X72,0X65,0X61,0X73,0X6F,0X6E,0X20,0X61,0X6E,0X64,0X20,0X6D,0X75,0X63,0X68,0X20,0X6D,0X6F,0X72,0X65,0X20,0X74,0X68,0X61,0X6E,0X20,0X74,0X68,0X61,0X74,0X21}, + .frame_len=51, + .raw_payload_len=45, + .expected_errno=ECONNRESET, + .descr="Close frame (Reason 1003) and msg", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X81,0X08,0X56,0X47,0X56,0X7A,0X64,0X47,0X6C,0X30}, + .expectedDecodeBuf={0X54,0X65,0X73,0X74,0X69,0X74}, + .frame_len=10, + .raw_payload_len=6, + .expected_errno=EPROTO, + .descr="Invalid frame: Wrong masking", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X81,0XFE,0X00,0X0F,0X71,0XE9,0X29,0X79,0X44,0XA4,0X07,0X23,0X3B,0X85,0X2C,0X55,0X1D,0X9E,0X06,0X23,0X27,0X9D}, + .expectedDecodeBuf={0X2E,0XFE,0X00,0X0F,0X72,0X65,0X20,0X49,0X70,0X73,0X75,0X6D}, + .frame_len=22, + .raw_payload_len=12, + .expected_errno=EPROTO, + .descr="Invalid frame: Length of < 126 with add. 16 bit len field", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X81,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X80,0X40,0X2F,0X40,0XF3,0X5B,0X2F,0X40,0XF2,0X63,0X01,0X1A,0X8D,0X42,0X2A,0X6C,0XAB,0X59,0X00,0X1A,0X91,0X5A}, + .expectedDecodeBuf={0X2E,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X80,0X40,0X72,0X65,0X20,0X49,0X70,0X73,0X75,0X6D}, + .frame_len=30, + .raw_payload_len=18, + .expected_errno=EPROTO, + .descr="Invalid frame: Length of < 126 with add. 64 bit len field", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X01,0XAC,0XC9,0X6E,0XC7,0X6E,0X9F,0X29,0XAF,0X1E,0XAA,0X17,0X85,0X1E,0XAA,0X17,0X85,0X06,0X80,0X29,0X9D,0X17,0X90,0X39,0XA3,0X1A,0X93,0X39,0XF2,0X5E,0X93,0X39,0X96,0X09,0XAD,0X5C,0X91,0X07,0XAA,0X5C,0XFE,0X04,0XA8,0X5C,0X91,0X5E,0X85,0X07,0XF3,0X1B}, + .expectedDecodeBuf={0X54,0X68,0X69,0X73,0X20,0X69,0X73,0X20,0X61,0X20,0X66,0X72,0X61,0X67,0X6D,0X65,0X6E,0X74,0X65,0X64,0X20,0X77,0X65,0X62,0X73,0X6F,0X63,0X6B,0X65,0X74,0X2E,0X2E,0X2E}, + .frame_len=50, + .raw_payload_len=33, + .expected_errno=0, + .descr="Continuation test frag1", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X00,0X9C,0X52,0XBC,0XD5,0X99,0X1E,0XD5,0XE1,0XEC,0X1B,0XFB,0X93,0XEC,0X08,0XFF,0X97,0XE9,0X36,0XFF,0X97,0XF7,0X30,0X8E,0X83,0XE3,0X1B,0XFB,0XEC,0XEC,0X1E,0XD5,0XE1,0XEC}, + .expectedDecodeBuf={0X2E,0X2E,0X2E,0X20,0X61,0X6E,0X64,0X20,0X69,0X74,0X20,0X67,0X6F,0X65,0X73,0X20,0X6F,0X6E,0X2E,0X2E,0X2E}, + .frame_len=34, + .raw_payload_len=21, + .expected_errno=0, + .descr="Continuation test frag2", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + }, + { + .frame={0X80,0X94,0X3B,0X88,0XA1,0XE9,0X62,0XDF,0X94,0X82,0X72,0XCF,0X98,0X9C,0X72,0XCF,0XE7,0X9C,0X61,0XCB,0XE3,0X93,0X5F,0XCF,0X98,0X9E}, + .expectedDecodeBuf={0X61,0X6E,0X64,0X20,0X6F,0X6E,0X20,0X61,0X6E,0X64,0X20,0X73,0X74,0X6F,0X70}, + .frame_len=26, + .raw_payload_len=15, + .expected_errno=0, + .descr="Continuation test frag3", + .i=0, + .simulate_sock_malfunction_at=0, + .errno_val=0, + .close_sock_at=0 + } +}; |