| /* |
| Copyright (C) 2002-2010 Karl J. Runge <[email protected]> |
| All rights reserved. |
| |
| This file is part of x11vnc. |
| |
| x11vnc is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or (at |
| your option) any later version. |
| |
| x11vnc is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with x11vnc; if not, write to the Free Software |
| Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA |
| or see <http://www.gnu.org/licenses/>. |
| |
| In addition, as a special exception, Karl J. Runge |
| gives permission to link the code of its release of x11vnc with the |
| OpenSSL project's "OpenSSL" library (or with modified versions of it |
| that use the same license as the "OpenSSL" library), and distribute |
| the linked executables. You must obey the GNU General Public License |
| in all respects for all of the code used other than "OpenSSL". If you |
| modify this file, you may extend this exception to your version of the |
| file, but you are not obligated to do so. If you do not wish to do |
| so, delete this exception statement from your version. |
| */ |
| |
| #ifndef _X11VNC_ENC_H |
| #define _X11VNC_ENC_H |
| |
| /* -- enc.h -- */ |
| |
| #if 0 |
| :r /home/runge/uvnc/ultraSC/rc4/ultravnc_dsm_helper.c |
| #endif |
| |
| /* |
| * ultravnc_dsm_helper.c unix/openssl UltraVNC encryption encoder/decoder. |
| * (also a generic symmetric encryption tunnel) |
| * (also a generic TCP relay and supports IPv6) |
| * |
| * compile via: |
| |
| cc -O -o ultravnc_dsm_helper ultravnc_dsm_helper.c -lssl -lcrypto |
| cc -DDBG -O -o ultravnc_dsm_helper ultravnc_dsm_helper.c -lssl -lcrypto |
| |
| * |
| * See usage below for how to run it. |
| * |
| * Note: since the UltraVNC DSM plugin implementation changes the RFB |
| * (aka VNC) protocol (extra data is sent), you will *ALSO* need to modify |
| * your VNC viewer or server to discard (or insert) this extra data. |
| * |
| * This tool knows nothing about the RFB protocol: it simply |
| * encrypts/decrypts a stream using a symmetric cipher, arc4 and aesv2, |
| * (others have been added, see usage). It could be used as a general |
| * encrypted tunnel: |
| * |
| * any-client <=> ultravnc_dsm_helper <--network--> ultravnc_dsm_helper(reverse mode) <=> any-server |
| * |
| * e.g. to connect a non-ultra-dsm-vnc viewer to a non-ultra-dsm-vnc server |
| * without using SSH or SSL. |
| * |
| * It can also be used as a general TCP relay (no encryption.) |
| * |
| * It supports IPv6 and so can also be used as a IPv6 gateway. |
| * |
| * ----------------------------------------------------------------------- |
| * Copyright (C) 2008-2010 Karl J. Runge <[email protected]> |
| * All rights reserved. |
| * |
| * This is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; version 2 of the License, or (at |
| * your option) any later version. |
| * |
| * This software is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this software; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
| * USA or see <http://www.gnu.org/licenses/>. |
| * |
| * In addition, as a special exception, Karl J. Runge gives permission |
| * to link the code of its release of ultravnc_dsm_helper with the |
| * OpenSSL project's "OpenSSL" library (or with modified versions of it |
| * that use the same license as the "OpenSSL" library), and distribute |
| * the linked executables. You must obey the GNU General Public License |
| * in all respects for all of the code used other than "OpenSSL". If you |
| * modify this file, you may extend this exception to your version of the |
| * file, but you are not obligated to do so. If you do not wish to do |
| * so, delete this exception statement from your version. |
| * ----------------------------------------------------------------------- |
| */ |
| |
| static char *usage = |
| "\n" |
| "ultravnc_dsm_helper: a symmetric encryption tunnel. version 0.2\n" |
| "\n" |
| " Created to enable encrypted VNC connections to UltraVNC, it can act as\n" |
| " a general encrypted tunnel between any two applications. It can also\n" |
| " be used as a general TCP relay (i.e. no encryption) or an IPv6 gateway.\n" |
| "\n" |
| "Usage: ultravnc_dsm_helper cipher keyfile listenport remotehost:port\n" |
| " ultravnc_dsm_helper relay listenport remotehost:port\n" |
| " ultravnc_dsm_helper showcert remotehost:port\n" |
| "\n" |
| "e.g.: ultravnc_dsm_helper arc4 ./arc4.key 5901 snoopy.net:5900\n" |
| "\n" |
| " IPv6 is supported: both IPv4 and IPv6 are attempted to listen on (port\n" |
| " 'listenport'.) For connections to remotehost, if IPv4 fails\n" |
| " then IPv6 is tried. Set the env. var ULTRAVNC_DSM_HELPER_NOIPV6\n" |
| " to completely disable the use of IPv6.\n" |
| "\n" |
| "\n" |
| " cipher: specify 'msrc4', 'msrc4_sc', 'arc4', 'aesv2', 'aes-cfb',\n" |
| " 'aes256', 'blowfish', '3des', 'securevnc'.\n" |
| "\n" |
| " Also 'none', 'relay', or 'showcert'. See below for details.\n" |
| "\n" |
| " 'msrc4_sc' enables a workaround for UVNC SC -plugin use.\n" |
| " (it might not be required in SC circa 2009 and later; try 'msrc4'.)\n" |
| "\n" |
| " use 'securevnc' for SecureVNCPlugin (RSA key exchange). 'keyfile' is\n" |
| " used as a server RSA keystore in this mode. If 'keyfile' does not\n" |
| " exist the user is prompted whether to save the key or not (a MD5\n" |
| " hash of it is shown) If 'keyfile' already exists the server key\n" |
| " must match its contents or the connection is dropped.\n" |
| "\n" |
| " HOWEVER, if 'keyfile' ends in the string 'ClientAuth.pkey', then the\n" |
| " normal SecureVNCPlugin client key authentication is performed.\n" |
| " If you want to do both have 'keyfile' end with 'ClientAuth.pkey.rsa'\n" |
| " that file will be used for the RSA keystore, and the '.rsa' will be\n" |
| " trimmed off and the remaining name used as the Client Auth file.\n" |
| "\n" |
| " use '.' to have it try to guess the cipher from the keyfile name,\n" |
| " e.g. 'arc4.key' implies arc4, 'rc4.key' implies msrc4, etc.\n" |
| "\n" |
| " use 'rev:arc4', etc. to reverse the roles of encrypter and decrypter.\n" |
| " (i.e. if you want to use it for a vnc server, not vnc viewer)\n" |
| "\n" |
| " use 'noultra:...' to skip steps involving salt and IV to try to be\n" |
| " compatible with UltraVNC DSM, i.e. assume a normal symmetric cipher\n" |
| " at the other end.\n" |
| "\n" |
| " use 'noultra:rev:...' if both are to be supplied.\n" |
| "\n" |
| "\n" |
| " keyfile: file holding the key (16 bytes for arc4 and aesv2, 87 for msrc4)\n" |
| " E.g. dd if=/dev/random of=./my.key bs=16 count=1\n" |
| " keyfile can also be pw=<string> to use \"string\" for the key.\n" |
| " Or for 'securevnc' the RSA keystore and/or ClientAuth file.\n" |
| "\n" |
| "\n" |
| " listenport: port to listen for incoming connection on. (use 0 to connect\n" |
| " to stdio, use a negative value to force localhost listening)\n" |
| "\n" |
| "\n" |
| " remotehost:port: host and port to connect to. (e.g. ultravnc server)\n" |
| "\n" |
| "\n" |
| " Also: cipher may be cipher@n,m where n is the salt size and m is the\n" |
| " initialization vector size. E.g. aesv2@8,16 Use n=-1 to disable salt\n" |
| " and the MD5 hash (i.e. insert the keydata directly into the cipher.)\n" |
| "\n" |
| " Use cipher@md+n,m to change the message digest. E.g. arc4@sha+8,16\n" |
| " Supported: 'md5', 'sha', 'sha1', 'ripemd160'.\n" |
| "\n" |
| "\n" |
| " TCP Relay mode: to connect without any encryption use a cipher type of\n" |
| " either 'relay' or 'none' (both are the equivalent):\n" |
| "\n" |
| " ultravnc_dsm_helper relay listenport remotehost:port\n" |
| " ultravnc_dsm_helper none listenport remotehost:port\n" |
| "\n" |
| " where 'relay' or 'none' is a literal string.\n" |
| " Note that for this mode no keyfile is suppled.\n" |
| " Note that this mode can act as an IPv4 to IPv6 gateway.\n" |
| "\n" |
| " ultravnc_dsm_helper relay 8080 ipv6.beijing2008.cn:80\n" |
| "\n" |
| "\n" |
| " SSL Show Certificate mode: Set the cipher to 'showcert' to fetch\n" |
| " the SSL certificate from remotehost:port and print it to the stdout.\n" |
| " No certificate authentication or verification is performed. E.g.\n" |
| "\n" |
| " ultravnc_dsm_helper showcert www.verisign.com:443\n" |
| "\n" |
| " (the output resembles that of 'openssl s_client ...') Set the env var\n" |
| " ULTRAVNC_DSM_HELPER_SHOWCERT_ADH=1 for Anonymous Diffie Hellman mode.\n" |
| "\n" |
| "\n" |
| " Looping Mode: Set the env. var. ULTRAVNC_DSM_HELPER_LOOP=1 to have it\n" |
| " restart itself after every disconnection in an endless loop. It pauses\n" |
| " 500 msec before restarting. Use ULTRAVNC_DSM_HELPER_LOOP=N to set the\n" |
| " pause to N msec.\n" |
| "\n" |
| " You can also set the env. var. ULTRAVNC_DSM_HELPER_BG to have the\n" |
| " program fork into the background for each connection, thereby acting\n" |
| " as a simple daemon.\n" |
| ; |
| |
| /* |
| * We can also run as a module included into x11vnc (-enc option) |
| * The includer must set ENC_MODULE and ENC_HAVE_OPENSSL. |
| * |
| * Note that when running as a module we still assume we have been |
| * forked off of the parent process and are communicating back to it |
| * via a socket. So we *still* exit(3) at the end or on error. And |
| * the global settings won't work. |
| */ |
| #ifdef ENC_MODULE |
| # define main __enc_main |
| static char *prog = "enc_helper"; |
| #else |
| # define ENC_HAVE_OPENSSL 1 |
| static char *prog = "ultravnc_dsm_helper"; |
| #endif |
| |
| /* unix includes */ |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <fcntl.h> |
| |
| #include <string.h> |
| #include <errno.h> |
| #include <signal.h> |
| |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <netinet/tcp.h> |
| #include <arpa/inet.h> |
| #include <netdb.h> |
| |
| |
| /* Solaris (sysv?) needs INADDR_NONE */ |
| #ifndef INADDR_NONE |
| #define INADDR_NONE ((in_addr_t) 0xffffffff) |
| #endif |
| |
| /* openssl includes */ |
| #if ENC_HAVE_OPENSSL |
| #include <openssl/evp.h> |
| #include <openssl/rand.h> |
| #include <openssl/rsa.h> |
| #include <openssl/err.h> |
| #include <openssl/ssl.h> |
| #include <openssl/rsa.h> |
| static const EVP_CIPHER *Cipher; |
| static const EVP_MD *Digest; |
| #endif |
| |
| static char *cipher = NULL; /* name of cipher, e.g. "aesv2" */ |
| static int reverse = 0; /* listening connection */ |
| static int msrc4_sc = 0; /* enables workaround for SC I/II */ |
| static int noultra = 0; /* manage salt/iv differently from ultradsm */ |
| static int nomd = 0; /* use the keydata directly, no md5 or salt */ |
| static int pw_in = 0; /* pw=.... read in */ |
| |
| |
| /* The data that was read in from key file (or pw=password) */ |
| static char keydata[1024]; |
| static int keydata_len; |
| |
| /* Size of salt and IV; based on UltraVNC DSM */ |
| #define SALT 16 |
| #define MSRC4_SALT 11 |
| #define IVEC 16 |
| |
| /* Set default values of salt and IV */ |
| static int salt_size = SALT; |
| static int ivec_size = IVEC; |
| |
| /* To track parent and child pids */ |
| static pid_t parent, child; |
| |
| /* transfer buffer size */ |
| #define BSIZE 8192 |
| |
| /* Some very verbose debugging stuff I enable for testing */ |
| #ifdef DBG |
| # include "dbg.h" |
| #else |
| # define DEC_CT_DBG(p, n) |
| # define DEC_PT_DBG(p, n) |
| # define ENC_CT_DBG(p, n) |
| # define ENC_PT_DBG(p, n) |
| # define PRINT_IVEC |
| # define PRINT_KEYDATA |
| # define PRINT_KEYSTR_AND_FRIENDS |
| # define PRINT_LOOP_DBG1 |
| # define PRINT_LOOP_DBG2 |
| # define PRINT_LOOP_DBG3 |
| #endif |
| |
| /* SecureVNCPlugin from: http://adamwalling.com/SecureVNC/ */ |
| #define SECUREVNC_RSA_PUBKEY_SIZE 270 |
| #define SECUREVNC_ENCRYPTED_KEY_SIZE 256 |
| #define SECUREVNC_SIGNATURE_SIZE 256 |
| #define SECUREVNC_KEY_SIZE 16 |
| #define SECUREVNC_RESERVED_SIZE 4 |
| #define SECUREVNC_RC4_DROP_BYTES 3072 |
| #define SECUREVNC_RAND_KEY_SOURCE 1024 |
| static int securevnc = 0; |
| static int securevnc_arc4 = 0; |
| static char *securevnc_file = NULL; |
| |
| static void enc_connections(int, char*, int); |
| |
| #if !ENC_HAVE_OPENSSL |
| |
| /* In case we are a module and there is no OpenSSL buildtime support */ |
| |
| extern void enc_do(char *ciph, char *keyfile, char *lport, char *rhp) { |
| fprintf(stderr, "%s: not compiled with OpenSSL\n", prog); |
| exit(1); |
| } |
| |
| #else |
| |
| #if defined(NO_EVP_aes_256_cfb) || (defined (__SVR4) && defined (__sun) && !defined(EVP_aes_256_cfb) && !defined(ASSUME_EVP_aes_256_cfb)) |
| /* |
| * For Solaris 10 missing 192 & 256 bit crypto. |
| * Note that EVP_aes_256_cfb is a macro. |
| */ |
| #undef EVP_aes_256_cfb |
| #define EVP_aes_256_cfb() EVP_aes_128_cfb(); {fprintf(stderr, "Not compiled with EVP_aes_256_cfb() 'aes256' support.\n"); exit(1);} |
| #endif |
| |
| /* If we are a module, enc_do() is the only interface we export. */ |
| |
| |
| /* This works out key type & etc., reads key, calls enc_connections */ |
| |
| extern void enc_do(char *ciph, char *keyfile, char *lport, char *rhp) { |
| |
| struct stat sb; |
| char *q, *p, *connect_host; |
| char tmp[16]; |
| int fd, len = 0, listen_port = 0, connect_port, mbits; |
| |
| q = ciph; |
| |
| /* check for noultra mode: */ |
| if (strstr(q, "noultra:") == q) { |
| noultra = 1; |
| q += strlen("noultra:"); |
| } |
| |
| /* check for reverse mode: */ |
| if (strstr(q, "rev:") == q) { |
| reverse = 1; |
| q += strlen("rev:"); |
| } |
| |
| /* work out which cipher and set Cipher to the selected one. */ |
| if (!strcasecmp(q, "msrc4")) { |
| Cipher = EVP_rc4(); cipher = "msrc4"; |
| |
| } else if (!strcasecmp(q, "msrc4_sc")) { |
| Cipher = EVP_rc4(); cipher = "msrc4"; |
| msrc4_sc = 1; /* no salt/iv workaround */ |
| |
| } else if (strstr(q, "arc4") == q) { |
| Cipher = EVP_rc4(); cipher = "arc4"; |
| |
| } else if (strstr(q, "aesv2") == q || strstr(q, "aes-ofb") == q) { |
| Cipher = EVP_aes_128_ofb(); cipher = "aesv2"; |
| |
| } else if (strstr(q, "aes-cfb") == q) { |
| Cipher = EVP_aes_128_cfb(); cipher = "aes-cfb"; |
| |
| } else if (strstr(q, "aes256") == q) { |
| Cipher = EVP_aes_256_cfb(); cipher = "aes256"; |
| |
| } else if (strstr(q, "blowfish") == q) { |
| Cipher = EVP_bf_cfb(); cipher = "blowfish"; |
| |
| } else if (strstr(q, "3des") == q) { |
| Cipher = EVP_des_ede3_cfb(); cipher = "3des"; |
| |
| } else if (strstr(q, "securevnc") == q) { |
| Cipher = EVP_aes_128_ofb(); cipher = "securevnc"; |
| securevnc = 1; |
| |
| } else if (strstr(q, "none") == q || strstr(q, "relay") == q) { |
| cipher = "none"; |
| |
| } else if (strstr(q, "showcert") == q) { |
| cipher = "showcert"; |
| |
| } else if (strstr(q, ".") == q) { |
| /* otherwise, try to guess cipher from key filename: */ |
| if (strstr(keyfile, "arc4.key")) { |
| Cipher = EVP_rc4(); cipher = "arc4"; |
| |
| } else if (strstr(keyfile, "rc4.key")) { |
| Cipher = EVP_rc4(); cipher = "msrc4"; |
| |
| } else if (strstr(keyfile, "aesv2.key")) { |
| Cipher = EVP_aes_128_ofb(); cipher = "aesv2"; |
| |
| } else if (strstr(keyfile, "aes-cfb.key")) { |
| Cipher = EVP_aes_128_cfb(); cipher = "aes-cfb"; |
| |
| } else if (strstr(keyfile, "aes256.key")) { |
| Cipher = EVP_aes_256_cfb(); cipher = "aes256"; |
| |
| } else if (strstr(keyfile, "blowfish.key")) { |
| Cipher = EVP_bf_cfb(); cipher = "blowfish"; |
| |
| } else if (strstr(keyfile, "3des.key")) { |
| Cipher = EVP_des_ede3_cfb(); cipher = "3des"; |
| |
| } else if (strstr(keyfile, "securevnc.")) { |
| Cipher = EVP_aes_128_ofb(); cipher = "securevnc"; |
| securevnc = 1; |
| |
| } else { |
| fprintf(stderr, "cannot figure out cipher, supply 'msrc4', 'arc4', or 'aesv2' ...\n"); |
| exit(1); |
| } |
| } else { |
| fprintf(stderr, "cannot figure out cipher, supply 'msrc4', 'arc4', or 'aesv2' ...\n"); |
| exit(1); |
| } |
| |
| /* set the default message digest (md5) */ |
| if (!securevnc) { |
| Digest = EVP_md5(); |
| } else { |
| Digest = EVP_sha1(); |
| } |
| |
| /* |
| * Look for user specified salt and IV sizes at the end |
| * ( ciph@salt,iv and ciph@[md+]salt,iv ): |
| */ |
| p = strchr(q, '@'); |
| if (p) { |
| int s, v; |
| p++; |
| if (strstr(p, "md5+") == p) { |
| Digest = EVP_md5(); p += strlen("md5+"); |
| } else if (strstr(p, "sha+") == p) { |
| Digest = EVP_sha(); p += strlen("sha+"); |
| } else if (strstr(p, "sha1+") == p) { |
| Digest = EVP_sha1(); p += strlen("sha1+"); |
| } else if (strstr(p, "ripe+") == p) { |
| Digest = EVP_ripemd160(); p += strlen("ripe+"); |
| } else if (strstr(p, "ripemd160+") == p) { |
| Digest = EVP_ripemd160(); p += strlen("ripemd160+"); |
| } |
| if (sscanf(p, "%d,%d", &s, &v) == 2) { |
| /* cipher@n,m */ |
| if (-1 <= s && s <= SALT) { |
| salt_size = s; |
| } else { |
| fprintf(stderr, "%s: invalid salt size: %d\n", |
| prog, s); |
| exit(1); |
| } |
| if (0 <= v && v <= EVP_MAX_IV_LENGTH) { |
| ivec_size = v; |
| } else { |
| fprintf(stderr, "%s: invalid IV size: %d\n", |
| prog, v); |
| exit(1); |
| } |
| } else if (sscanf(p, "%d", &s) == 1) { |
| /* cipher@n */ |
| if (-1 <= s && s <= SALT) { |
| salt_size = s; |
| } else { |
| fprintf(stderr, "%s: invalid salt size: %d\n", |
| prog, s); |
| exit(1); |
| } |
| } |
| if (salt_size == -1) { |
| /* let salt = -1 mean skip both MD5 and salt */ |
| nomd = 1; |
| salt_size = 0; |
| } |
| } |
| |
| /* port to listen on (0 => stdio, negative => localhost) */ |
| if (lport != NULL) { |
| listen_port = atoi(lport); |
| } |
| |
| /* extract remote hostname and port */ |
| q = strrchr(rhp, ':'); |
| if (q) { |
| connect_port = atoi(q+1); |
| *q = '\0'; |
| } else { |
| /* otherwise guess VNC display 0 ... */ |
| connect_port = 5900; |
| } |
| connect_host = strdup(rhp); |
| |
| /* check for and read in the key file */ |
| memset(keydata, 0, sizeof(keydata)); |
| |
| if (!strcmp(cipher, "none")) { |
| goto readed_in; |
| } |
| if (!strcmp(cipher, "showcert")) { |
| goto readed_in; |
| } |
| |
| if (securevnc) { |
| /* note the keyfile for rsa verification later */ |
| if (keyfile != NULL && strcasecmp(keyfile, "none")) { |
| securevnc_file = keyfile; |
| } |
| goto readed_in; |
| } |
| |
| if (stat(keyfile, &sb) != 0) { |
| if (strstr(keyfile, "pw=") == keyfile) { |
| /* user specified key/password on cmdline */ |
| int i; |
| len = 0; |
| pw_in = 1; |
| for (i=0; i < (int) strlen(keyfile); i++) { |
| /* load the string to keydata: */ |
| int n = i + strlen("pw="); |
| keydata[i] = keyfile[n]; |
| if (keyfile[n] == '\0') break; |
| len++; |
| if (i > 100) break; |
| } |
| goto readed_in; |
| } |
| /* otherwise invalid file */ |
| perror("stat"); |
| exit(1); |
| } |
| if (sb.st_size > 1024) { |
| fprintf(stderr, "%s: key file too big.\n", prog); |
| exit(1); |
| } |
| fd = open(keyfile, O_RDONLY); |
| if (fd < 0) { |
| perror("open"); |
| exit(1); |
| } |
| |
| /* read it all in */ |
| len = (int) read(fd, keydata, (size_t) sb.st_size); |
| if (len != sb.st_size) { |
| perror("read"); |
| fprintf(stderr, "%s, could not read key file.\n", prog); |
| exit(1); |
| } |
| close(fd); |
| |
| readed_in: |
| |
| |
| /* check for ultravnc msrc4 format 'rc4.key' */ |
| mbits = 0; |
| if (strstr(keydata, "128 bit") == keydata) { |
| mbits = 128; |
| } else if (strstr(keydata, " 56 bit") == keydata) { |
| mbits = 56; |
| } else if (strstr(keydata, " 40 bit") == keydata) { |
| mbits = 40; |
| } |
| if (mbits > 0) { |
| /* 4 is for int key length, 12 is for BLOBHEADER. */ |
| int i, offset = strlen("xxx bit") + 4 + 12; |
| |
| /* the key is stored in reverse order! */ |
| len = mbits/8; |
| for (i=0; i < len; i++) { |
| tmp[i] = keydata[offset + len - i - 1]; |
| } |
| |
| /* clear keydata and then copy the reversed bytes there: */ |
| memset(keydata, 0, sizeof(keydata)); |
| memcpy(keydata, tmp, len); |
| } |
| |
| keydata_len = len; |
| |
| /* initialize random */ |
| RAND_poll(); |
| |
| /* |
| * Setup connections, then transfer data when they are all |
| * hooked up. |
| */ |
| enc_connections(listen_port, connect_host, connect_port); |
| } |
| #endif |
| |
| static void enc_raw_xfer(int sock_fr, int sock_to) { |
| |
| unsigned char buf[BSIZE]; |
| unsigned char *psrc = NULL; |
| int len, m, n = 0; |
| |
| /* zero the buffers */ |
| memset(buf, 0, BSIZE); |
| |
| /* now loop forever processing the data stream */ |
| while (1) { |
| errno = 0; |
| |
| /* general case of loop, read some in: */ |
| n = read(sock_fr, buf, BSIZE); |
| |
| if (n == 0 || (n < 0 && errno != EINTR)) { |
| /* failure to read any data, it is EOF or fatal error */ |
| int err = errno; |
| |
| /* debug output: */ |
| fprintf(stderr, "%s: input stream finished: n=%d, err=%d", prog, n, err); |
| |
| /* EOF or fatal error */ |
| break; |
| |
| } else if (n > 0) { |
| |
| /* write data to the other end: */ |
| len = n; |
| psrc = buf; |
| while (len > 0) { |
| errno = 0; |
| m = write(sock_to, psrc, len); |
| |
| if (m > 0) { |
| /* scoot them by how much was written: */ |
| psrc += m; |
| len -= m; |
| } |
| if (m < 0 && (errno == EINTR || errno == EAGAIN)) { |
| /* interrupted or blocked */ |
| continue; |
| } |
| /* EOF or fatal error */ |
| break; |
| } |
| } else { |
| /* this is EINTR */ |
| } |
| } |
| |
| /* transfer done (viewer exited or some error) */ |
| |
| fprintf(stderr, "\n%s: close sock_to\n", prog); |
| close(sock_to); |
| |
| fprintf(stderr, "%s: close sock_fr\n", prog); |
| close(sock_fr); |
| |
| /* kill our partner after 1 secs. */ |
| sleep(1); |
| if (child) { |
| if (kill(child, SIGTERM) == 0) { |
| fprintf(stderr, "%s[%d]: killed my partner: %d\n", |
| prog, (int) getpid(), (int) child); |
| } |
| } else { |
| if (kill(parent, SIGTERM) == 0) { |
| fprintf(stderr, "%s[%d]: killed my partner: %d\n", |
| prog, (int) getpid(), (int) parent); |
| } |
| } |
| } |
| |
| #if ENC_HAVE_OPENSSL |
| /* |
| * Initialize cipher context and then loop till EOF doing transfer & |
| * encrypt or decrypt. |
| */ |
| static void enc_xfer(int sock_fr, int sock_to, int encrypt) { |
| /* |
| * We keep both E and D aspects in case we revert back to a |
| * single process calling select(2) on all fds... |
| */ |
| unsigned char E_keystr[EVP_MAX_KEY_LENGTH]; |
| unsigned char D_keystr[EVP_MAX_KEY_LENGTH]; |
| EVP_CIPHER_CTX E_ctx, D_ctx; |
| EVP_CIPHER_CTX *ctx = NULL; |
| |
| unsigned char buf[BSIZE], out[BSIZE]; |
| unsigned char *psrc = NULL, *keystr; |
| unsigned char salt[SALT+1]; |
| unsigned char ivec_real[EVP_MAX_IV_LENGTH]; |
| unsigned char *ivec = ivec_real; |
| |
| int i, cnt, len, m, n = 0, vb = 0, first = 1; |
| int whoops = 1; /* for the msrc4 problem */ |
| char *encstr, *encsym; |
| |
| /* zero the buffers */ |
| memset(buf, 0, BSIZE); |
| memset(out, 0, BSIZE); |
| memset(salt, 0, sizeof(salt)); |
| memset(ivec_real, 0, sizeof(ivec_real)); |
| memset(E_keystr, 0, sizeof(E_keystr)); |
| memset(D_keystr, 0, sizeof(D_keystr)); |
| |
| if (!strcmp(cipher, "msrc4")) { |
| salt_size = MSRC4_SALT; /* 11 vs. 16 */ |
| } |
| |
| if (msrc4_sc) { |
| whoops = 1; /* force workaround in SC mode */ |
| } |
| |
| if (getenv("ENCRYPT_VERBOSE")) { |
| vb = 1; /* let user turn on some debugging via env. var. */ |
| } |
| |
| /* |
| * reverse mode, e.g. we help a vnc server instead of a viewer. |
| */ |
| if (reverse) { |
| encrypt = (!encrypt); |
| } |
| encstr = encrypt ? "encrypt" : "decrypt"; /* string for messages */ |
| encsym = encrypt ? "+" : "-"; |
| |
| /* use the encryption/decryption context variables below */ |
| if (encrypt) { |
| ctx = &E_ctx; |
| keystr = E_keystr; |
| } else { |
| ctx = &D_ctx; |
| keystr = D_keystr; |
| } |
| |
| if (securevnc) { |
| first = 0; /* no need for salt+iv on first time */ |
| salt_size = 0; /* we want no salt */ |
| n = 0; /* nothing read */ |
| ivec_size = 0; /* we want no IV. */ |
| ivec = NULL; |
| } else if (encrypt) { |
| /* encrypter initializes the salt and initialization vector */ |
| |
| /* |
| * Our salt is 16 bytes but I believe only the first 8 |
| * bytes are used by EVP_BytesToKey(3). Since we send it |
| * to the other "plugin" we need to keep it 16. Also, |
| * the IV size can depend on the cipher type. Again, 16. |
| */ |
| RAND_bytes(salt, salt_size); |
| RAND_bytes(ivec, ivec_size); |
| |
| /* place them in the send buffer: */ |
| memcpy(buf, salt, salt_size); |
| memcpy(buf+salt_size, ivec, ivec_size); |
| |
| n = salt_size + ivec_size; |
| |
| ENC_PT_DBG(buf, n); |
| |
| } else { |
| /* decrypter needs to read salt + iv from the wire: */ |
| |
| /* sleep 100 ms (TODO: select on fd) */ |
| struct timeval tv; |
| tv.tv_sec = 0; |
| tv.tv_usec = 100 * 1000; |
| select(1, NULL, NULL, NULL, &tv); |
| |
| if (salt_size+ivec_size == 0) { |
| n = 0; /* no salt or iv, skip reading. */ |
| } else { |
| n = read(sock_fr, buf, salt_size+ivec_size+96); |
| } |
| if (n == 0 && salt_size+ivec_size > 0) { |
| fprintf(stderr, "%s: decrypt finished.\n", prog); |
| goto finished; |
| } |
| if (n < salt_size+ivec_size) { |
| if (msrc4_sc && n == 12) { |
| fprintf(stderr, "%s: only %d bytes read. Assuming " |
| "UVNC Single Click server.\n", prog, n); |
| } else { |
| if (n < 0) perror("read"); |
| fprintf(stderr, "%s: could not read enough for salt " |
| "and ivec: n=%d\n", prog, n); |
| goto finished; |
| } |
| } |
| |
| DEC_CT_DBG(buf, n); |
| |
| if (msrc4_sc && n == 12) { |
| ; /* send it as is */ |
| } else { |
| /* extract them to their buffers: */ |
| memcpy(salt, buf, salt_size); |
| memcpy(ivec, buf+salt_size, ivec_size); |
| |
| /* the rest is some encrypted data: */ |
| n = n - salt_size - ivec_size; |
| psrc = buf + salt_size + ivec_size; |
| |
| if (n > 0) { |
| /* |
| * copy it down to the start of buf for |
| * sending below: |
| */ |
| for (i=0; i < n; i++) { |
| buf[i] = psrc[i]; |
| } |
| } |
| } |
| } |
| |
| /* debug output */ |
| PRINT_KEYDATA; |
| PRINT_IVEC; |
| |
| if (!strcmp(cipher, "msrc4")) { |
| /* special cases for MSRC4: */ |
| |
| if (whoops) { |
| fprintf(stderr, "%s: %s - WARNING: MSRC4 mode and IGNORING random salt\n", prog, encstr); |
| fprintf(stderr, "%s: %s - WARNING: and initialization vector!!\n", prog, encstr); |
| EVP_CIPHER_CTX_init(ctx); |
| if (pw_in) { |
| /* for pw=xxxx a md5 hash is used */ |
| EVP_BytesToKey(Cipher, Digest, NULL, (unsigned char *) keydata, |
| keydata_len, 1, keystr, NULL); |
| EVP_CipherInit_ex(ctx, Cipher, NULL, keystr, NULL, |
| encrypt); |
| } else { |
| /* otherwise keydata as is */ |
| EVP_CipherInit_ex(ctx, Cipher, NULL, |
| (unsigned char *) keydata, NULL, encrypt); |
| } |
| } else { |
| /* XXX might not be correct, just exit. */ |
| fprintf(stderr, "%s: %s - Not sure about msrc4 && !whoops case, exiting.\n", prog, encstr); |
| exit(1); |
| |
| EVP_BytesToKey(Cipher, Digest, NULL, (unsigned char *) keydata, |
| keydata_len, 1, keystr, ivec); |
| EVP_CIPHER_CTX_init(ctx); |
| EVP_CipherInit_ex(ctx, Cipher, NULL, keystr, ivec, |
| encrypt); |
| } |
| |
| } else { |
| unsigned char *in_salt = NULL; |
| |
| /* check salt and IV source and size. */ |
| if (securevnc) { |
| in_salt = NULL; |
| } else if (salt_size <= 0) { |
| /* let salt_size = 0 mean keep it out of the MD5 */ |
| fprintf(stderr, "%s: %s - WARNING: no salt\n", |
| prog, encstr); |
| in_salt = NULL; |
| } else { |
| in_salt = salt; |
| } |
| |
| if (ivec_size < Cipher->iv_len && !securevnc) { |
| fprintf(stderr, "%s: %s - WARNING: short IV %d < %d\n", |
| prog, encstr, ivec_size, Cipher->iv_len); |
| } |
| |
| /* make the hashed value and place in keystr */ |
| |
| /* |
| * XXX N.B.: DSM plugin had count=0, and overwrote ivec |
| * by not passing NULL iv. |
| */ |
| |
| if (nomd) { |
| /* special mode: no salt or md5, use keydata directly */ |
| |
| int sz = keydata_len < EVP_MAX_KEY_LENGTH ? |
| keydata_len : EVP_MAX_KEY_LENGTH; |
| |
| fprintf(stderr, "%s: %s - WARNING: no-md5 specified: ignoring salt & hash\n", prog, encstr); |
| memcpy(keystr, keydata, sz); |
| |
| } else if (noultra && ivec_size > 0) { |
| /* "normal" mode, don't overwrite ivec. */ |
| |
| EVP_BytesToKey(Cipher, Digest, in_salt, (unsigned char *) keydata, |
| keydata_len, 1, keystr, NULL); |
| |
| } else { |
| /* |
| * Ultra DSM compatibility mode. Note that this |
| * clobbers the ivec we set up above! Under |
| * noultra we overwrite ivec only if ivec_size=0. |
| * |
| * SecureVNC also goes through here. in_salt and ivec are NULL. |
| * And ivec is NULL below in the EVP_CipherInit_ex() call. |
| */ |
| EVP_BytesToKey(Cipher, Digest, in_salt, (unsigned char *) keydata, |
| keydata_len, 1, keystr, ivec); |
| } |
| |
| |
| /* initialize the context */ |
| EVP_CIPHER_CTX_init(ctx); |
| |
| |
| /* set the cipher & initialize */ |
| |
| /* |
| * XXX N.B.: DSM plugin implementation had encrypt=1 |
| * for both (i.e. perfectly symmetric) |
| */ |
| |
| EVP_CipherInit_ex(ctx, Cipher, NULL, keystr, ivec, encrypt); |
| } |
| |
| if (securevnc && securevnc_arc4) { |
| /* need to discard initial 3072 bytes */ |
| unsigned char buf1[SECUREVNC_RC4_DROP_BYTES]; |
| unsigned char buf2[SECUREVNC_RC4_DROP_BYTES]; |
| int cnt = 0; |
| EVP_CipherUpdate(ctx, buf1, &cnt, buf2, SECUREVNC_RC4_DROP_BYTES); |
| } |
| |
| /* debug output */ |
| PRINT_KEYSTR_AND_FRIENDS; |
| |
| /* now loop forever processing the data stream */ |
| |
| while (1) { |
| errno = 0; |
| if (first && n > 0) { |
| if (encrypt && msrc4_sc) { |
| /* skip sending salt+iv */ |
| first = 0; |
| continue; |
| } else { |
| /* use that first block of data placed in buf */ |
| } |
| } else if (first && n == 0 && salt_size + ivec_size == 0) { |
| first = 0; |
| continue; |
| } else { |
| /* general case of loop, read some in: */ |
| n = read(sock_fr, buf, BSIZE); |
| } |
| |
| /* debug output: */ |
| if (vb) fprintf(stderr, "%s%d/%d ", encsym, n, errno); |
| PRINT_LOOP_DBG1; |
| |
| if (n == 0 || (n < 0 && errno != EINTR)) { |
| /* failure to read any data, it is EOF or fatal error */ |
| int err = errno; |
| |
| /* debug output: */ |
| PRINT_LOOP_DBG2; |
| fprintf(stderr, "%s: %s - input stream finished: n=%d, err=%d", prog, encstr, n, err); |
| |
| /* EOF or fatal error */ |
| break; |
| |
| } else if (n > 0) { |
| /* we read in some data, now transform it: */ |
| |
| if (first && encrypt) { |
| /* first time, copy the salt and ivec to out[] for sending */ |
| memcpy(out, buf, n); |
| cnt = n; |
| |
| } else if (!EVP_CipherUpdate(ctx, out, &cnt, buf, n)) { |
| /* otherwise, we transform the data */ |
| fprintf(stderr, "%s: enc_xfer EVP_CipherUpdate failed.\n", prog); |
| break; |
| } |
| |
| /* debug output: */ |
| if (vb) fprintf(stderr, "%sc%d/%d ", encsym, cnt, n); |
| PRINT_LOOP_DBG3; |
| |
| /* write transformed data to the other end: */ |
| len = cnt; |
| psrc = out; |
| while (len > 0) { |
| errno = 0; |
| m = write(sock_to, psrc, len); |
| |
| /* debug output: */ |
| if (vb) fprintf(stderr, "m%s%d/%d ", encsym, m, errno); |
| |
| if (m > 0) { |
| /* scoot them by how much was written: */ |
| psrc += m; |
| len -= m; |
| } |
| if (m < 0 && (errno == EINTR || errno == EAGAIN)) { |
| /* interrupted or blocked */ |
| continue; |
| } |
| /* EOF or fatal error */ |
| break; |
| } |
| } else { |
| /* this is EINTR */ |
| } |
| first = 0; |
| } |
| |
| /* transfer done (viewer exited or some error) */ |
| finished: |
| |
| fprintf(stderr, "\n%s: %s - close sock_to\n", prog, encstr); |
| close(sock_to); |
| |
| fprintf(stderr, "%s: %s - close sock_fr\n", prog, encstr); |
| close(sock_fr); |
| |
| /* kill our partner after 2 secs. */ |
| sleep(2); |
| if (child) { |
| if (kill(child, SIGTERM) == 0) { |
| fprintf(stderr, "%s[%d]: %s - killed my partner: %d\n", |
| prog, (int) getpid(), encstr, (int) child); |
| } |
| } else { |
| if (kill(parent, SIGTERM) == 0) { |
| fprintf(stderr, "%s[%d]: %s - killed my partner: %d\n", |
| prog, (int) getpid(), encstr, (int) parent); |
| } |
| } |
| } |
| |
| static int securevnc_server_rsa_save_dialog(char *file, char *md5str, unsigned char* rsabuf) { |
| /* since we are likely running in the background, use this kludge by running tk */ |
| FILE *p; |
| char str[2], *q = file, *cmd = getenv("WISH") ? getenv("WISH") : "wish"; |
| int rc; |
| |
| memset(str, 0, sizeof(str)); |
| |
| p = popen(cmd, "w"); |
| if (p == NULL) { |
| fprintf(stderr, "checkserver_rsa: could not run: %s\n", cmd); |
| return 0; |
| } |
| |
| /* start piping tk/tcl code to it: */ |
| fprintf(p, "wm withdraw .\n"); |
| fprintf(p, "set x [expr [winfo screenwidth .]/2]\n"); |
| fprintf(p, "set y [expr [winfo screenheight .]/2]\n"); |
| fprintf(p, "wm geometry . +$x+$y; update\n"); |
| fprintf(p, "catch {option add *Dialog.msg.font {helvetica -14 bold}}\n"); |
| fprintf(p, "catch {option add *Dialog.msg.wrapLength 6i}\n"); |
| fprintf(p, "set ans [tk_messageBox -title \"Save and Trust UltraVNC RSA Key?\" -icon question "); |
| fprintf(p, "-type yesno -message \"Save and Trust UltraVNC SecureVNCPlugin RSA Key\\n\\n"); |
| fprintf(p, "With MD5 sum: %s\\n\\n", md5str); |
| fprintf(p, "In file: "); |
| while (*q != '\0') { |
| /* sanitize user supplied string: */ |
| str[0] = *q; |
| if (strpbrk(str, "[](){}`'\"$&*|<>") == NULL) { |
| fprintf(p, "%s", str); |
| } |
| q++; |
| } |
| fprintf(p, " ?\"]\n"); |
| fprintf(p, "if { $ans == \"yes\" } {destroy .; exit 0} else {destroy .; exit 1}\n"); |
| rc = pclose(p); |
| if (rc == 0) { |
| fprintf(stderr, "checkserver_rsa: query returned: %d. saving it.\n", rc); |
| p = fopen(file, "w"); |
| if (p == NULL) { |
| fprintf(stderr, "checkserver_rsa: could not open %s\n", file); |
| return 0; |
| } |
| write(fileno(p), rsabuf, SECUREVNC_RSA_PUBKEY_SIZE); |
| fclose(p); |
| return 2; |
| } else { |
| fprintf(stderr, "checkserver_rsa: query returned: %d. NOT saving it.\n", rc); |
| return -1; |
| } |
| } |
| |
| static char *rsa_md5_sum(unsigned char* rsabuf) { |
| EVP_MD_CTX md; |
| char digest[EVP_MAX_MD_SIZE], tmp[16]; |
| char md5str[EVP_MAX_MD_SIZE * 8]; |
| unsigned int i, size = 0; |
| |
| EVP_DigestInit(&md, EVP_md5()); |
| EVP_DigestUpdate(&md, rsabuf, SECUREVNC_RSA_PUBKEY_SIZE); |
| EVP_DigestFinal(&md, (unsigned char *)digest, &size); |
| |
| memset(md5str, 0, sizeof(md5str)); |
| for (i=0; i < size; i++) { |
| unsigned char uc = (unsigned char) digest[i]; |
| sprintf(tmp, "%02x", (int) uc); |
| strcat(md5str, tmp); |
| } |
| return strdup(md5str); |
| } |
| |
| static int securevnc_check_server_rsa(char *file, unsigned char *rsabuf) { |
| struct stat sb; |
| unsigned char filebuf[SECUREVNC_RSA_PUBKEY_SIZE]; |
| char *md5str = rsa_md5_sum(rsabuf); |
| |
| if (!file) { |
| return 0; |
| } |
| |
| memset(filebuf, 0, sizeof(filebuf)); |
| if (stat(file, &sb) == 0) { |
| int n, fd, i, ok = 1; |
| |
| if (sb.st_size != SECUREVNC_RSA_PUBKEY_SIZE) { |
| fprintf(stderr, "checkserver_rsa: file is wrong size: %d != %d '%s'\n", |
| (int) sb.st_size, SECUREVNC_RSA_PUBKEY_SIZE, file); |
| return 0; |
| } |
| |
| fd = open(file, O_RDONLY); |
| if (fd < 0) { |
| fprintf(stderr, "checkserver_rsa: could not open: '%s'\n", file); |
| return 0; |
| } |
| |
| n = (int) read(fd, filebuf, SECUREVNC_RSA_PUBKEY_SIZE); |
| close(fd); |
| if (n != SECUREVNC_RSA_PUBKEY_SIZE) { |
| fprintf(stderr, "checkserver_rsa: could not read all of file: %d != %d '%s'\n", |
| n, SECUREVNC_RSA_PUBKEY_SIZE, file); |
| return 0; |
| } |
| |
| for (i=0; i < SECUREVNC_RSA_PUBKEY_SIZE; i++) { |
| if (filebuf[i] != rsabuf[i]) { |
| ok = 0; |
| } |
| } |
| if (!ok) { |
| char *str1 = rsa_md5_sum(rsabuf); |
| char *str2 = rsa_md5_sum(filebuf); |
| fprintf(stderr, "checkserver_rsa: rsa keystore contents differ for '%s'\n", file); |
| fprintf(stderr, "checkserver_rsa: MD5 sum of server key: %s\n", str1); |
| fprintf(stderr, "checkserver_rsa: MD5 sum of keystore: %s\n", str2); |
| } |
| return ok; |
| } else { |
| |
| fprintf(stderr, "checkserver_rsa: rsa keystore file does not exist: '%s'\n", file); |
| fprintf(stderr, "checkserver_rsa: asking user if we should store rsa key in it.\n\n"); |
| fprintf(stderr, "checkserver_rsa: RSA key has MD5 sum: %s\n\n", md5str); |
| |
| return securevnc_server_rsa_save_dialog(file, md5str, rsabuf); |
| } |
| } |
| |
| static RSA *load_client_auth(char *file) { |
| struct stat sb; |
| int fd, n; |
| char *contents; |
| RSA *rsa; |
| |
| if (!file) { |
| return NULL; |
| } |
| if (stat(file, &sb) != 0) { |
| return NULL; |
| } |
| |
| fd = open(file, O_RDONLY); |
| if (fd < 0) { |
| fprintf(stderr, "load_client_auth: could not open: '%s'\n", file); |
| return NULL; |
| } |
| |
| contents = (char *) malloc(sb.st_size); |
| n = (int) read(fd, contents, sb.st_size); |
| close(fd); |
| |
| if (n != sb.st_size) { |
| fprintf(stderr, "load_client_auth: could not read all of: '%s'\n", file); |
| free(contents); |
| return NULL; |
| } |
| |
| rsa = d2i_RSAPrivateKey(NULL, (const unsigned char **) ((void *) &contents), sb.st_size); |
| if (!rsa) { |
| fprintf(stderr, "load_client_auth: d2i_RSAPrivateKey failed for: '%s'\n", file); |
| return NULL; |
| } |
| |
| if (RSA_check_key(rsa) != 1) { |
| fprintf(stderr, "load_client_auth: rsa key invalid: '%s'\n", file); |
| return NULL; |
| } |
| |
| return rsa; |
| } |
| |
| static void sslexit(char *msg) { |
| fprintf(stderr, "%s: %s\n", msg, ERR_error_string(ERR_get_error(), NULL)); |
| exit(1); |
| } |
| |
| static void securevnc_setup(int conn1, int conn2) { |
| RSA *rsa = NULL; |
| EVP_CIPHER_CTX init_ctx; |
| unsigned char keystr[EVP_MAX_KEY_LENGTH]; |
| unsigned char *rsabuf, *rsasav; |
| unsigned char *encrypted_keybuf; |
| unsigned char *initkey; |
| unsigned int server_flags = 0; |
| unsigned char one = 1, zero = 0, sig = 16; |
| unsigned char b1, b2, b3, b4; |
| unsigned char buf[BSIZE], to_viewer[BSIZE]; |
| int to_viewer_len = 0; |
| int n = 0, len, rc; |
| int server = reverse ? conn1 : conn2; |
| int viewer = reverse ? conn2 : conn1; |
| char *client_auth = NULL; |
| int client_auth_req = 0; |
| int keystore_verified = 0; |
| |
| ERR_load_crypto_strings(); |
| |
| /* alloc and read from server the 270 comprising the rsa public key: */ |
| rsabuf = (unsigned char *) calloc(SECUREVNC_RSA_PUBKEY_SIZE, 1); |
| rsasav = (unsigned char *) calloc(SECUREVNC_RSA_PUBKEY_SIZE, 1); |
| len = 0; |
| while (len < SECUREVNC_RSA_PUBKEY_SIZE) { |
| n = read(server, rsabuf + len, SECUREVNC_RSA_PUBKEY_SIZE - len); |
| if (n == 0 || (n < 0 && errno != EINTR)) { |
| fprintf(stderr, "securevnc_setup: fail read rsabuf: n=%d len=%d\n", n, len); |
| exit(1); |
| } |
| len += n; |
| } |
| if (len != SECUREVNC_RSA_PUBKEY_SIZE) { |
| fprintf(stderr, "securevnc_setup: fail final read rsabuf: n=%d len=%d\n", n, len); |
| exit(1); |
| } |
| fprintf(stderr, "securevnc_setup: rsa data read len: %d\n", len); |
| memcpy(rsasav, rsabuf, SECUREVNC_RSA_PUBKEY_SIZE); |
| |
| fprintf(stderr, "securevnc_setup: RSA key has MD5 sum: %s\n", rsa_md5_sum(rsabuf)); |
| fprintf(stderr, "securevnc_setup:\n"); |
| fprintf(stderr, "securevnc_setup: One way to print out the SecureVNC Server key MD5 sum is:\n\n"); |
| fprintf(stderr, "openssl rsa -inform DER -outform DER -pubout -in ./Server_SecureVNC.pkey | dd bs=1 skip=24 | md5sum\n\n"); |
| if (securevnc_file == NULL) { |
| fprintf(stderr, "securevnc_setup:\n"); |
| fprintf(stderr, "securevnc_setup: ** WARNING: ULTRAVNC SERVER RSA KEY NOT VERIFIED. **\n"); |
| fprintf(stderr, "securevnc_setup: ** WARNING: A MAN-IN-THE-MIDDLE ATTACK IS POSSIBLE. **\n"); |
| fprintf(stderr, "securevnc_setup:\n"); |
| } else { |
| char *q = strrchr(securevnc_file, 'C'); |
| int skip = 0; |
| if (q) { |
| if (!strcmp(q, "ClientAuth.pkey")) { |
| client_auth = strdup(securevnc_file); |
| skip = 1; |
| } else if (!strcmp(q, "ClientAuth.pkey.rsa")) { |
| client_auth = strdup(securevnc_file); |
| q = strrchr(client_auth, '.'); |
| *q = '\0'; |
| } |
| } |
| if (!skip) { |
| rc = securevnc_check_server_rsa(securevnc_file, rsabuf); |
| } |
| if (skip) { |
| ; |
| } else if (rc == 0) { |
| fprintf(stderr, "securevnc_setup:\n"); |
| fprintf(stderr, "securevnc_setup: VERIFY_ERROR: SERVER RSA KEY DID NOT MATCH:\n"); |
| fprintf(stderr, "securevnc_setup: %s\n", securevnc_file); |
| fprintf(stderr, "securevnc_setup:\n"); |
| exit(1); |
| } else if (rc == -1) { |
| fprintf(stderr, "securevnc_setup: User cancelled the save and hence the connection.\n"); |
| fprintf(stderr, "securevnc_setup: %s\n", securevnc_file); |
| exit(1); |
| } else if (rc == 1) { |
| fprintf(stderr, "securevnc_setup: VERIFY SUCCESS: server rsa key matches the contents of:\n"); |
| fprintf(stderr, "securevnc_setup: %s\n", securevnc_file); |
| keystore_verified = 1; |
| } else if (rc == 2) { |
| fprintf(stderr, "securevnc_setup: Server rsa key stored in:\n"); |
| fprintf(stderr, "securevnc_setup: %s\n", securevnc_file); |
| keystore_verified = 2; |
| } |
| } |
| |
| /* |
| * read in the server flags. Note that SecureVNCPlugin sends these |
| * in little endian and not network order!! |
| */ |
| read(server, (char *) &b1, 1); |
| read(server, (char *) &b2, 1); |
| read(server, (char *) &b3, 1); |
| read(server, (char *) &b4, 1); |
| |
| server_flags = 0; |
| server_flags |= ((unsigned int) b4) << 24; |
| server_flags |= ((unsigned int) b3) << 16; |
| server_flags |= ((unsigned int) b2) << 8; |
| server_flags |= ((unsigned int) b1) << 0; |
| fprintf(stderr, "securevnc_setup: server_flags: 0x%08x\n", server_flags); |
| |
| /* check for arc4 usage: */ |
| if (server_flags & 0x1) { |
| fprintf(stderr, "securevnc_setup: server uses AES cipher.\n"); |
| } else { |
| fprintf(stderr, "securevnc_setup: server uses ARC4 cipher.\n"); |
| securevnc_arc4 = 1; |
| Cipher = EVP_rc4(); |
| } |
| |
| /* check for client auth signature requirement: */ |
| if (server_flags & (sig << 24)) { |
| fprintf(stderr, "securevnc_setup: server requires Client Auth signature.\n"); |
| client_auth_req = 1; |
| if (!client_auth) { |
| fprintf(stderr, "securevnc_setup: However, NO *ClientAuth.pkey keyfile was supplied on our\n"); |
| fprintf(stderr, "securevnc_setup: command line. Exiting.\n"); |
| exit(1); |
| } |
| } |
| |
| /* |
| * The first packet 'RFB 003.006' is obscured with key |
| * that is a sha1 hash of public key. So make this tmp key now: |
| * |
| */ |
| initkey = (unsigned char *) calloc(SECUREVNC_KEY_SIZE, 1); |
| EVP_BytesToKey(EVP_rc4(), EVP_sha1(), NULL, rsabuf, SECUREVNC_RSA_PUBKEY_SIZE, 1, initkey, NULL); |
| |
| /* expand the transported rsabuf into an rsa object */ |
| rsa = d2i_RSAPublicKey(NULL, (const unsigned char **) &rsabuf, SECUREVNC_RSA_PUBKEY_SIZE); |
| if (rsa == NULL) { |
| sslexit("securevnc_setup: failed to create rsa"); |
| } |
| |
| /* |
| * Back to the work involving the tmp obscuring key: |
| */ |
| EVP_CIPHER_CTX_init(&init_ctx); |
| rc = EVP_CipherInit_ex(&init_ctx, EVP_rc4(), NULL, initkey, NULL, 1); |
| if (rc == 0) { |
| sslexit("securevnc_setup: EVP_CipherInit_ex(init_ctx) failed"); |
| } |
| |
| /* for the first obscured packet, read what we can... */ |
| n = read(server, (char *) buf, BSIZE); |
| fprintf(stderr, "securevnc_setup: data read: %d\n", n); |
| if (n < 0) { |
| exit(1); |
| } |
| fprintf(stderr, "securevnc_setup: initial data[%d]: ", n); |
| |
| /* decode with the tmp key */ |
| if (n > 0) { |
| memset(to_viewer, 0, sizeof(to_viewer)); |
| if (EVP_CipherUpdate(&init_ctx, to_viewer, &len, buf, n) == 0) { |
| sslexit("securevnc_setup: EVP_CipherUpdate(init_ctx) failed"); |
| exit(1); |
| } |
| to_viewer_len = len; |
| } |
| EVP_CIPHER_CTX_cleanup(&init_ctx); |
| free(initkey); |
| |
| /* print what we would send to the viewer (sent below): */ |
| write(2, to_viewer, 12); /* and first 12 bytes 'RFB ...' as message */ |
| |
| /* now create the random session key: */ |
| encrypted_keybuf = (unsigned char*) calloc(RSA_size(rsa), 1); |
| |
| fprintf(stderr, "securevnc_setup: creating random session key: %d/%d\n", |
| SECUREVNC_KEY_SIZE, SECUREVNC_RAND_KEY_SOURCE); |
| keydata_len = SECUREVNC_RAND_KEY_SOURCE; |
| |
| rc = RAND_bytes((unsigned char *)keydata, SECUREVNC_RAND_KEY_SOURCE); |
| if (rc <= 0) { |
| fprintf(stderr, "securevnc_setup: RAND_bytes() failed: %s\n", ERR_error_string(ERR_get_error(), NULL)); |
| rc = RAND_pseudo_bytes((unsigned char *)keydata, SECUREVNC_RAND_KEY_SOURCE); |
| fprintf(stderr, "securevnc_setup: RAND_pseudo_bytes() rc=%d\n", rc); |
| if (getenv("RANDSTR")) { |
| char *s = getenv("RANDSTR"); |
| fprintf(stderr, "securevnc_setup: seeding with RANDSTR len=%d\n", strlen(s)); |
| RAND_add(s, strlen(s), strlen(s)); |
| } |
| } |
| |
| /* N.B. this will be repeated in enc_xfer() setup. */ |
| EVP_BytesToKey(Cipher, Digest, NULL, (unsigned char *) keydata, keydata_len, 1, keystr, NULL); |
| |
| /* encrypt the session key with the server's public rsa key: */ |
| n = RSA_public_encrypt(SECUREVNC_KEY_SIZE, keystr, encrypted_keybuf, rsa, RSA_PKCS1_PADDING); |
| if (n == -1) { |
| sslexit("securevnc_setup: RSA_public_encrypt() failed"); |
| exit(1); |
| } |
| fprintf(stderr, "securevnc_setup: encrypted session key size: %d. sending to server.\n", n); |
| |
| /* send it to the server: */ |
| write(server, encrypted_keybuf, n); |
| free(encrypted_keybuf); |
| |
| /* |
| * Reply back with flags indicating cipher (same as one sent to |
| * us) and we do not want client-side auth. |
| * |
| * We send it out on the wire in little endian order: |
| */ |
| if (securevnc_arc4) { |
| write(server, (char *)&zero, 1); |
| } else { |
| write(server, (char *)&one, 1); |
| } |
| write(server, (char *)&zero, 1); |
| write(server, (char *)&zero, 1); |
| if (client_auth_req) { |
| write(server, (char *)&sig, 1); |
| } else { |
| write(server, (char *)&zero, 1); |
| } |
| |
| if (client_auth_req && client_auth) { |
| RSA *client_rsa = load_client_auth(client_auth); |
| EVP_MD_CTX dctx; |
| unsigned char digest[EVP_MAX_MD_SIZE], *signature; |
| unsigned int ndig = 0, nsig = 0; |
| |
| if (0) { |
| /* for testing only, use the wrong RSA key: */ |
| client_rsa = RSA_generate_key(2048, 0x10001, NULL, NULL); |
| } |
| |
| if (client_rsa == NULL) { |
| fprintf(stderr, "securevnc_setup: problem reading rsa key from '%s'\n", client_auth); |
| exit(1); |
| } |
| |
| EVP_DigestInit(&dctx, EVP_sha1()); |
| EVP_DigestUpdate(&dctx, keystr, SECUREVNC_KEY_SIZE); |
| /* |
| * Without something like the following MITM is still possible. |
| * This is because the MITM knows keystr and can use it with |
| * the server connection as well, and then he just forwards our |
| * signed digest. The additional information below would be the |
| * MITM's rsa public key, and so the real VNC server will notice |
| * the difference. And MITM can't sign keystr+server_rsa.pub since |
| * he doesn't have Viewer_ClientAuth.pkey. |
| */ |
| if (0) { |
| EVP_DigestUpdate(&dctx, rsasav, SECUREVNC_RSA_PUBKEY_SIZE); |
| if (!keystore_verified) { |
| fprintf(stderr, "securevnc_setup:\n"); |
| fprintf(stderr, "securevnc_setup: Warning: even *WITH* Client Authentication in SecureVNC,\n"); |
| fprintf(stderr, "securevnc_setup: an attacker may be able to trick you into connecting to his\n"); |
| fprintf(stderr, "securevnc_setup: fake VNC server and supplying VNC or Windows passwords, etc.\n"); |
| fprintf(stderr, "securevnc_setup: To increase security manually verify the Server RSA key's MD5\n"); |
| fprintf(stderr, "securevnc_setup: checksum and then have SSVNC save the key in its keystore to\n"); |
| fprintf(stderr, "securevnc_setup: be used to verify the server in subsequent connections.\n"); |
| fprintf(stderr, "securevnc_setup:\n"); |
| } |
| } else { |
| if (!keystore_verified) { |
| fprintf(stderr, "securevnc_setup:\n"); |
| fprintf(stderr, "securevnc_setup: WARNING: THE FIRST VERSION OF THE SECUREVNC PROTOCOL IS\n"); |
| fprintf(stderr, "securevnc_setup: WARNING: BEING USED. *EVEN* WITH CLIENT AUTHENTICATION IT\n"); |
| fprintf(stderr, "securevnc_setup: WARNING: IS SUSCEPTIBLE TO A MAN-IN-THE-MIDDLE ATTACK.\n"); |
| fprintf(stderr, "securevnc_setup: To increase security manually verify the Server RSA key's MD5\n"); |
| fprintf(stderr, "securevnc_setup: checksum and then have SSVNC save the key in its keystore to\n"); |
| fprintf(stderr, "securevnc_setup: be used to verify the server in subsequent connections.\n"); |
| fprintf(stderr, "securevnc_setup:\n"); |
| } |
| } |
| EVP_DigestFinal(&dctx, (unsigned char *)digest, &ndig); |
| |
| signature = (unsigned char *) calloc(RSA_size(client_rsa), 1); |
| RSA_sign(NID_sha1, digest, ndig, signature, &nsig, client_rsa); |
| |
| fprintf(stderr, "securevnc_setup: sending ClientAuth.pkey signed data: %d\n", nsig); |
| write(server, signature, nsig); |
| free(signature); |
| |
| RSA_free(client_rsa); |
| } |
| |
| fprintf(stderr, "securevnc_setup: done.\n"); |
| |
| /* now send the 'RFB ...' to the viewer */ |
| if (to_viewer_len > 0) { |
| write(viewer, to_viewer, to_viewer_len); |
| } |
| } |
| |
| #ifndef ENC_DISABLE_SHOW_CERT |
| static void enc_sslerrexit(void) { |
| unsigned long err = ERR_get_error(); |
| |
| if (err) { |
| char str[256]; |
| ERR_error_string(err, str); |
| fprintf(stdout, "ssl error: %s\n", str); |
| } |
| exit(1); |
| } |
| #endif |
| |
| static void show_cert(int sock) { |
| #ifndef ENC_DISABLE_SHOW_CERT |
| SSL_CTX *ctx; |
| SSL *ssl = NULL; |
| STACK_OF(X509) *sk = NULL; |
| X509 *peer = NULL; |
| SSL_CIPHER *c; |
| BIO *bio; |
| unsigned char *sid = (unsigned char *) "ultravnc_dsm_helper SID"; |
| long mode; |
| int i; |
| |
| fprintf(stdout, "CONNECTED(%08X)\n",sock); |
| |
| SSL_library_init(); |
| SSL_load_error_strings(); |
| |
| if (!RAND_status()) { |
| RAND_poll(); |
| } |
| /* this is not for a secured connection. */ |
| for (i=0; i < 100; i++) { |
| if (!RAND_status()) { |
| char tmp[32]; |
| sprintf(tmp, "%d", getpid() * (17 + i)); |
| RAND_add(tmp, strlen(tmp), 5); |
| } else { |
| break; |
| } |
| } |
| |
| ctx = SSL_CTX_new( SSLv23_client_method() ); |
| if (ctx == NULL) { |
| fprintf(stdout, "show_cert: SSL_CTX_new failed.\n"); |
| close(sock); |
| enc_sslerrexit(); |
| } |
| |
| mode = 0; |
| mode |= SSL_MODE_ENABLE_PARTIAL_WRITE; |
| mode |= SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; |
| SSL_CTX_set_mode(ctx, mode); |
| |
| if (getenv("ULTRAVNC_DSM_HELPER_SHOWCERT_ADH")) { |
| SSL_CTX_set_cipher_list(ctx, "ADH:@STRENGTH"); |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); |
| } |
| |
| ssl = SSL_new(ctx); |
| |
| if (ssl == NULL) { |
| fprintf(stdout, "show_cert: SSL_new failed.\n"); |
| close(sock); |
| enc_sslerrexit(); |
| } |
| |
| SSL_set_session_id_context(ssl, sid, strlen((char *)sid)); |
| |
| if (! SSL_set_fd(ssl, sock)) { |
| fprintf(stdout, "show_cert: SSL_set_fd failed.\n"); |
| close(sock); |
| enc_sslerrexit(); |
| } |
| |
| SSL_set_connect_state(ssl); |
| |
| if (SSL_connect(ssl) <= 0) { |
| unsigned long err = ERR_get_error(); |
| fprintf(stdout, "show_cert: SSL_connect failed.\n"); |
| if (err) { |
| char str[256]; |
| ERR_error_string(err, str); |
| fprintf(stdout, "ssl error: %s\n", str); |
| } |
| } |
| |
| SSL_get_verify_result(ssl); |
| |
| sk = SSL_get_peer_cert_chain(ssl); |
| if (sk != NULL) { |
| fprintf(stdout, "---\nCertificate chain\n"); |
| for (i=0; i < sk_X509_num(sk); i++) { |
| char buf[2048]; |
| X509_NAME_oneline(X509_get_subject_name(sk_X509_value(sk,i)), buf, sizeof buf); |
| fprintf(stdout, "%2d s:%s\n", i, buf); |
| X509_NAME_oneline(X509_get_issuer_name(sk_X509_value(sk,i)), buf, sizeof buf); |
| fprintf(stdout, " i:%s\n", buf); |
| } |
| } else { |
| fprintf(stdout, "show_cert: SSL_get_peer_cert_chain failed.\n"); |
| } |
| fprintf(stdout, "---\n"); |
| peer = SSL_get_peer_certificate(ssl); |
| bio = BIO_new_fp(stdout, BIO_NOCLOSE); |
| if (peer != NULL) { |
| char buf[2048]; |
| BIO_printf(bio,"Server certificate\n"); |
| PEM_write_bio_X509(bio, peer); |
| |
| X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof buf); |
| BIO_printf(bio,"subject=%s\n",buf); |
| X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof buf); |
| BIO_printf(bio,"issuer=%s\n",buf); |
| } else { |
| fprintf(stdout, "show_cert: SSL_get_peer_certificate failed.\n"); |
| } |
| |
| c = SSL_get_current_cipher(ssl); |
| BIO_printf(bio,"---\nNew, %s, Cipher is %s\n", SSL_CIPHER_get_version(c), SSL_CIPHER_get_name(c)); |
| |
| if (peer != NULL) { |
| EVP_PKEY *pktmp; |
| pktmp = X509_get_pubkey(peer); |
| BIO_printf(bio,"Server public key is %d bit\n", EVP_PKEY_bits(pktmp)); |
| EVP_PKEY_free(pktmp); |
| } |
| BIO_printf(bio,"---\nDONE\n---\n"); |
| |
| fflush(stdout); |
| |
| #endif |
| close(sock); |
| exit(0); |
| } |
| |
| #ifndef SOL_IPV6 |
| #ifdef IPPROTO_IPV6 |
| #define SOL_IPV6 IPPROTO_IPV6 |
| #endif |
| #endif |
| |
| /* |
| * Listens on incoming port for a client, then connects to remote server. |
| * Then forks into two processes one is the encrypter the other the |
| * decrypter. |
| */ |
| static void enc_connections(int listen_port, char *connect_host, int connect_port) { |
| int listen_fd = -1, listen_fd6 = -1, conn1 = -1, conn2 = -1, ret, one = 1; |
| socklen_t clen; |
| struct hostent *hp; |
| struct sockaddr_in client, server; |
| fd_set fds; |
| int maxfd = -1; |
| |
| /* zero means use stdio (preferably from socketpair()) */ |
| if (listen_port == 0) { |
| conn1 = fileno(stdin); |
| goto use_stdio; |
| } |
| |
| if (!strcmp(cipher, "showcert")) { |
| goto use_stdio; |
| } |
| |
| /* fd=n,m means use the supplied already established sockets */ |
| if (sscanf(connect_host, "fd=%d,%d", &conn1, &conn2) == 2) { |
| goto use_input_fds; |
| } |
| |
| /* create the listening socket: */ |
| memset(&client, 0, sizeof(client)); |
| client.sin_family = AF_INET; |
| if (listen_port < 0) { |
| /* negative port means use loopback */ |
| client.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| client.sin_port = htons(-listen_port); |
| } else { |
| client.sin_addr.s_addr = htonl(INADDR_ANY); |
| client.sin_port = htons(listen_port); |
| } |
| |
| listen_fd = socket(AF_INET, SOCK_STREAM, 0); |
| if (listen_fd < 0) { |
| perror("socket"); |
| goto try6; |
| } |
| |
| ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, |
| (char *)&one, sizeof(one)); |
| if (ret < 0) { |
| perror("setsockopt"); |
| close(listen_fd); |
| listen_fd = -1; |
| goto try6; |
| } |
| |
| ret = bind(listen_fd, (struct sockaddr *) &client, sizeof(client)); |
| if (ret < 0) { |
| perror("bind"); |
| close(listen_fd); |
| listen_fd = -1; |
| goto try6; |
| } |
| |
| ret = listen(listen_fd, 2); |
| if (ret < 0) { |
| perror("listen"); |
| close(listen_fd); |
| listen_fd = -1; |
| goto try6; |
| } |
| |
| try6: |
| #ifdef AF_INET6 |
| if (!getenv("ULTRAVNC_DSM_HELPER_NOIPV6")) { |
| struct sockaddr_in6 sin; |
| int one = 1, sock = -1; |
| |
| sock = socket(AF_INET6, SOCK_STREAM, 0); |
| if (sock < 0) { |
| perror("socket6"); |
| goto fail; |
| } |
| |
| if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) < 0) { |
| perror("setsockopt6 SO_REUSEADDR"); |
| close(sock); |
| sock = -1; |
| goto fail; |
| } |
| |
| #if defined(SOL_IPV6) && defined(IPV6_V6ONLY) |
| if (setsockopt(sock, SOL_IPV6, IPV6_V6ONLY, (char *)&one, sizeof(one)) < 0) { |
| perror("setsockopt6 IPV6_V6ONLY"); |
| close(sock); |
| sock = -1; |
| goto fail; |
| } |
| #endif |
| |
| memset((char *)&sin, 0, sizeof(sin)); |
| sin.sin6_family = AF_INET6; |
| |
| if (listen_port < 0) { |
| sin.sin6_addr = in6addr_loopback; |
| sin.sin6_port = htons(-listen_port); |
| } else { |
| sin.sin6_addr = in6addr_any; |
| sin.sin6_port = htons(listen_port); |
| } |
| |
| if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) { |
| perror("bind6"); |
| close(sock); |
| sock = -1; |
| goto fail; |
| } |
| |
| if (listen(sock, 2) < 0) { |
| perror("listen6"); |
| close(sock); |
| sock = -1; |
| goto fail; |
| } |
| |
| fail: |
| listen_fd6 = sock; |
| } |
| #endif |
| |
| if (listen_fd < 0 && listen_fd6 < 0) { |
| fprintf(stderr, "%s: could not listen on port: %d\n", |
| prog, listen_port); |
| exit(1); |
| } |
| |
| fprintf(stderr, "%s: waiting for connection on port: %d\n", |
| prog, listen_port); |
| |
| /* wait for a connection: */ |
| FD_ZERO(&fds); |
| if (listen_fd >= 0) { |
| FD_SET(listen_fd, &fds); |
| if (listen_fd > maxfd) { |
| maxfd = listen_fd; |
| } |
| } |
| if (listen_fd6 >= 0) { |
| FD_SET(listen_fd6, &fds); |
| if (listen_fd6 > maxfd) { |
| maxfd = listen_fd6; |
| } |
| } |
| if (select(maxfd+1, &fds, NULL, NULL, NULL) <= 0) { |
| perror("select"); |
| exit(1); |
| } |
| |
| if (FD_ISSET(listen_fd, &fds)) { |
| clen = sizeof(client); |
| conn1 = accept(listen_fd, (struct sockaddr *) &client, &clen); |
| if (conn1 < 0) { |
| perror("accept"); |
| exit(1); |
| } |
| } else if (FD_ISSET(listen_fd6, &fds)) { |
| #ifdef AF_INET6 |
| struct sockaddr_in6 addr; |
| socklen_t addrlen = sizeof(addr); |
| |
| conn1 = accept(listen_fd6, (struct sockaddr *) &addr, &addrlen); |
| if (conn1 < 0) { |
| perror("accept6"); |
| exit(1); |
| } |
| #else |
| fprintf(stderr, "No IPv6 / AF_INET6 support.\n"); |
| exit(1); |
| #endif |
| } |
| |
| if (setsockopt(conn1, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one)) < 0) { |
| perror("setsockopt TCP_NODELAY"); |
| exit(1); |
| } |
| |
| /* done with the listening socket(s): */ |
| if (listen_fd >= 0) { |
| close(listen_fd); |
| } |
| if (listen_fd6 >= 0) { |
| close(listen_fd6); |
| } |
| |
| if (getenv("ULTRAVNC_DSM_HELPER_BG")) { |
| int p, n; |
| if ((p = fork()) > 0) { |
| fprintf(stderr, "%s: putting child %d in background.\n", |
| prog, p); |
| exit(0); |
| } else if (p == -1) { |
| fprintf(stderr, "%s: could not fork\n", prog); |
| perror("fork"); |
| exit(1); |
| } |
| if (setsid() == -1) { |
| fprintf(stderr, "%s: setsid failed\n", prog); |
| perror("setsid"); |
| exit(1); |
| } |
| /* adjust our stdio */ |
| n = open("/dev/null", O_RDONLY); |
| dup2(n, 0); |
| dup2(n, 1); |
| dup2(n, 2); |
| if (n > 2) { |
| close(n); |
| } |
| } |
| |
| use_stdio: |
| |
| fprintf(stderr, "%s: got connection: %d\n", prog, conn1); |
| |
| /* now connect to remote server: */ |
| memset(&server, 0, sizeof(server)); |
| server.sin_family = AF_INET; |
| server.sin_port = htons(connect_port); |
| |
| if ((server.sin_addr.s_addr = inet_addr(connect_host)) == htonl(INADDR_NONE)) { |
| if (!(hp = gethostbyname(connect_host))) { |
| perror("gethostbyname"); |
| goto tryconn6; |
| } |
| server.sin_addr.s_addr = *(unsigned long *)hp->h_addr; |
| } |
| |
| conn2 = socket(AF_INET, SOCK_STREAM, 0); |
| if (conn2 < 0) { |
| perror("socket"); |
| goto tryconn6; |
| } |
| |
| if (connect(conn2, (struct sockaddr *)&server, (sizeof(server))) < 0) { |
| perror("connect"); |
| goto tryconn6; |
| } |
| |
| tryconn6: |
| #ifdef AF_INET6 |
| if (conn2 < 0 && !getenv("ULTRAVNC_DSM_HELPER_NOIPV6")) { |
| int err; |
| struct addrinfo *ai; |
| struct addrinfo hints; |
| char service[32]; |
| |
| fprintf(stderr, "connect[ipv6]: trying to connect via IPv6 to %s\n", connect_host); |
| conn2 = -1; |
| |
| memset(&hints, 0, sizeof(hints)); |
| sprintf(service, "%d", connect_port); |
| |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_socktype = SOCK_STREAM; |
| #ifdef AI_ADDRCONFIG |
| hints.ai_flags |= AI_ADDRCONFIG; |
| #endif |
| #ifdef AI_NUMERICSERV |
| hints.ai_flags |= AI_NUMERICSERV; |
| #endif |
| |
| err = getaddrinfo(connect_host, service, &hints, &ai); |
| if (err != 0) { |
| fprintf(stderr, "getaddrinfo[%d]: %s\n", err, gai_strerror(err)); |
| } else { |
| struct addrinfo *ap = ai; |
| while (ap != NULL) { |
| int fd = -1; |
| fd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol); |
| if (fd == -1) { |
| perror("socket6"); |
| } else { |
| int dmsg = 0; |
| int res = connect(fd, ap->ai_addr, ap->ai_addrlen); |
| #if defined(SOL_IPV6) && defined(IPV6_V6ONLY) |
| if (res != 0) { |
| int zero = 0; |
| perror("connect6"); |
| dmsg = 1; |
| if (setsockopt(fd, SOL_IPV6, IPV6_V6ONLY, (char *)&zero, sizeof(zero)) == 0) { |
| fprintf(stderr, "connect[ipv6]: trying again with IPV6_V6ONLY=0\n"); |
| res = connect(fd, ap->ai_addr, ap->ai_addrlen); |
| dmsg = 0; |
| } |
| } |
| #endif |
| if (res == 0) { |
| conn2 = fd; |
| break; |
| } else { |
| if (!dmsg) perror("connect6"); |
| close(fd); |
| } |
| } |
| ap = ap->ai_next; |
| } |
| freeaddrinfo(ai); |
| } |
| } |
| #endif |
| if (conn2 < 0) { |
| fprintf(stderr, "could not connect to %s\n", connect_host); |
| exit(1); |
| } |
| if (conn2 >= 0 && setsockopt(conn2, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one)) < 0) { |
| perror("setsockopt TCP_NODELAY"); |
| } |
| |
| use_input_fds: |
| |
| if (!strcmp(cipher, "showcert")) { |
| show_cert(conn2); |
| close(conn2); |
| exit(0); |
| } |
| |
| if (securevnc) { |
| securevnc_setup(conn1, conn2); |
| } |
| |
| /* fork into two processes; one for each direction: */ |
| parent = getpid(); |
| |
| child = fork(); |
| |
| if (child == (pid_t) -1) { |
| /* couldn't fork... */ |
| perror("fork"); |
| close(conn1); |
| close(conn2); |
| exit(1); |
| } |
| |
| /* Do transfer/encode/decode loop: */ |
| |
| if (child == 0) { |
| /* encrypter: local-viewer -> remote-server */ |
| if (!strcmp(cipher, "none") || !strcmp(cipher, "relay")) { |
| enc_raw_xfer(conn1, conn2); |
| } else { |
| enc_xfer(conn1, conn2, 1); |
| } |
| } else { |
| /* decrypter: remote-server -> local-viewer */ |
| if (!strcmp(cipher, "none") || !strcmp(cipher, "relay")) { |
| enc_raw_xfer(conn2, conn1); |
| } else { |
| enc_xfer(conn2, conn1, 0); |
| } |
| } |
| } |
| #endif /* ENC_HAVE_OPENSSL */ |
| |
| static void doloop (int argc, char *argv[]) { |
| int ms = atoi(getenv("ULTRAVNC_DSM_HELPER_LOOP")); |
| if (ms > 0) { |
| char *cmd; |
| int i, len = 0; |
| for (i = 0; i < argc; i++) { |
| len += strlen(argv[i]) + 2; |
| } |
| cmd = (char *)malloc(len); |
| cmd[0] = '\0'; |
| for (i = 0; i < argc; i++) { |
| strcat(cmd, argv[i]); |
| if (i < argc - 1) { |
| strcat(cmd, " "); |
| } |
| } |
| |
| putenv("ULTRAVNC_DSM_HELPER_LOOP_SET=1"); |
| if (ms == 1) { |
| ms = 500; |
| } |
| i = 0; |
| while (1) { |
| fprintf(stderr, "loop running[%d]: %s\n", ++i, cmd); |
| system(cmd); |
| usleep(1000 * ms); |
| } |
| } |
| } |
| |
| extern int main (int argc, char *argv[]) { |
| char *kf, *q; |
| |
| if (getenv("ULTRAVNC_DSM_HELPER_LOOP")) { |
| if (!getenv("ULTRAVNC_DSM_HELPER_LOOP_SET")) { |
| doloop(argc, argv); |
| } |
| } |
| |
| if (argc == 3) { |
| if (!strcmp(argv[1], "showcert")) { |
| enc_do(argv[1], NULL, NULL, argv[2]); |
| return 0; |
| } |
| } |
| if (argc == 4) { |
| if (!strcmp(argv[1], "none") || !strcmp(argv[1], "relay")) { |
| enc_do(argv[1], NULL, argv[2], argv[3]); |
| return 0; |
| } |
| } |
| if (argc < 5) { |
| fprintf(stdout, "%s\n", usage); |
| exit(1); |
| } |
| |
| /* guard against pw= on cmdline (e.g. linux) */ |
| kf = strdup(argv[2]); |
| q = strstr(argv[2], "pw="); |
| if (q) { |
| while (*q != '\0') { |
| *q = '\0'; /* now ps(1) won't show it */ |
| q++; |
| } |
| } |
| |
| enc_do(argv[1], kf, argv[3], argv[4]); |
| |
| return 0; |
| } |
| |
| /* |
| * a crude utility to have this work "keyless" i.e. the vnc password |
| * is used instead of a pre-shared key file. |
| */ |
| |
| /* |
| |
| #!/usr/bin/perl |
| # |
| # md5_to_rc4key.pl |
| # |
| # This program requires md5sum(1) installed on your machine. |
| # |
| # It translates a VNC password to a ultravnc dsm plugin |
| # compatible key file. |
| # |
| # Supply VNC password on cmdline, capture in key file: |
| # |
| # md5_to_rc4key.pl swordfish > rc4.key |
| # md5_to_rc4key.pl -a swordfish > arc4.key |
| # |
| # Use rc4.key with ultravnc_dsm_helper in msrc4 mode, |
| # or arc4.key in either arc4 or aesv4 mode. |
| # |
| # |
| $rfmt = 1; |
| if ($ARGV[0] eq '-a') { |
| $rfmt = 0; |
| shift; |
| } |
| |
| # n.b. this is not super secure against bad locals... |
| |
| $pw = shift; |
| $tmp = "/tmp/md5out.$$"; |
| |
| open(MD5, "| md5sum > $tmp"); |
| print MD5 $pw; |
| close MD5; |
| |
| $md5 = `cat $tmp`; |
| unlink $tmp; |
| |
| ($md5, $junk) = split(/\s/, $md5); |
| |
| print "128 bit" if $rfmt; |
| print 'a' x 4 if $rfmt; |
| print 'b' x 12 if $rfmt; |
| |
| $str = ''; |
| foreach $d (split(//, $md5)) { |
| $str .= $d; |
| if (length($str) == 2) { |
| push @key, $str; |
| $str = ''; |
| } |
| } |
| |
| @key = (reverse @key) if $rfmt; |
| |
| foreach $h (@key) { |
| $c = pack('c', hex("0x$h")); |
| print $c; |
| } |
| |
| print 'c' x 48 if $rfmt; |
| |
| */ |
| #endif /* _X11VNC_ENC_H */ |