1
0
mirror of https://github.com/fcwu/docker-ubuntu-vnc-desktop synced 2026-08-05 16:12:41 +02:00

Push submodule websockify of noVNC

This commit is contained in:
Doro Wu
2015-05-13 10:05:09 +08:00
parent 1bc6fcb092
commit 89a5132f55
79 changed files with 11786 additions and 6 deletions
+14
View File
@@ -0,0 +1,14 @@
TARGETS=websockify
CFLAGS += -fPIC
all: $(TARGETS)
websockify: websockify.o websocket.o
$(CC) $(LDFLAGS) $^ -lssl -lcrypto -lresolv -o $@
websocket.o: websocket.c websocket.h
websockify.o: websockify.c websocket.h
clean:
rm -f websockify *.o
+51
View File
@@ -0,0 +1,51 @@
This directory contain alternate implementations of
WebSockets-to-TCP-Socket proxies (for noVNC).
## websockify.c (C)
### Description
This is a C version of the original websockify. It is more limited in
functionality than the original.
## websockify.js
### Description
This is a Node.JS (server-side event driven Javascript) implementation
of websockify.
## kumina.c (C)
### Description
The upstream source of the kumina proxy is [here](https://github.com/kumina/wsproxy).
[This article](http://blog.kumina.nl/2011/06/proxying-and-multiplexing-novnc-using-wsproxy/)
describes the kumina proxy.
kumina is an application that is run from inetd, which allows noVNC
to connect to an unmodified VNC server. Furthermore, it makes use of
the recently added support in noVNC for file names. The file name is
used to denote the port number. Say, you connect to:
ws://host:41337/25900
The kumina proxy opens a connection to:
vnc://host:25900/
The address to which kumina connects, is the same as the address to
which the client connected (using getsockname()).
### Configuration
kumina can be enabled by adding the following line to inetd.conf:
41337 stream tcp nowait nobody /usr/sbin/kumina kumina 25900 25909
The two parameters of kumina denote the minimum and the maximum allowed
port numbers. This allows a single kumina instance to multiplex
connections to multiple VNC servers.
@@ -0,0 +1,7 @@
A JavaScript implementation of the websockify WebSocket-to-TCP bridge/proxy.
Copyright (C) 2013 - Joel Martin (github.com/kanaka)
Licensed under LGPL-3.
See http://github.com/kanaka/websockify for more info.
@@ -0,0 +1,24 @@
{
"author": "Joel Martin <github@martintribe.org> (http://github.com/kanaka)",
"name": "websockify",
"description": "websockify is a WebSocket-to-TCP proxy/bridge",
"license": "LGPL-3",
"version": "0.6.0",
"repository": {
"type": "git",
"url": "git://github.com/kanaka/websockify.git"
},
"files": ["../../docs/LICENSE.LGPL-3","websockify.js"],
"bin": {
"websockify": "./websockify.js"
},
"engines": {
"node": ">=0.8.9"
},
"dependencies": {
"ws": ">=0.4.27",
"base64": "latest",
"optimist": "latest",
"policyfile": "latest"
}
}
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env node
// A WebSocket to TCP socket proxy
// Copyright 2012 Joel Martin
// Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
// Known to work with node 0.8.9
// Requires node modules: ws, optimist and policyfile
// npm install ws optimist policyfile
var argv = require('optimist').argv,
net = require('net'),
http = require('http'),
https = require('https'),
url = require('url'),
path = require('path'),
fs = require('fs'),
policyfile = require('policyfile'),
Buffer = require('buffer').Buffer,
WebSocketServer = require('ws').Server,
webServer, wsServer,
source_host, source_port, target_host, target_port,
web_path = null;
// Handle new WebSocket client
new_client = function(client) {
var clientAddr = client._socket.remoteAddress, log;
console.log(client.upgradeReq.url);
log = function (msg) {
console.log(' ' + clientAddr + ': '+ msg);
};
log('WebSocket connection');
log('Version ' + client.protocolVersion + ', subprotocol: ' + client.protocol);
var target = net.createConnection(target_port,target_host, function() {
log('connected to target');
});
target.on('data', function(data) {
//log("sending message: " + data);
try {
if (client.protocol === 'base64') {
client.send(new Buffer(data).toString('base64'));
} else {
client.send(data,{binary: true});
}
} catch(e) {
log("Client closed, cleaning up target");
target.end();
}
});
target.on('end', function() {
log('target disconnected');
client.close();
});
target.on('error', function() {
log('target connection error');
target.end();
client.close();
});
client.on('message', function(msg) {
//log('got message: ' + msg);
if (client.protocol === 'base64') {
target.write(new Buffer(msg, 'base64'));
} else {
target.write(msg,'binary');
}
});
client.on('close', function(code, reason) {
log('WebSocket client disconnected: ' + code + ' [' + reason + ']');
target.end();
});
client.on('error', function(a) {
log('WebSocket client error: ' + a);
target.end();
});
};
// Send an HTTP error response
http_error = function (response, code, msg) {
response.writeHead(code, {"Content-Type": "text/plain"});
response.write(msg + "\n");
response.end();
return;
}
// Process an HTTP static file request
http_request = function (request, response) {
// console.log("pathname: " + url.parse(req.url).pathname);
// res.writeHead(200, {'Content-Type': 'text/plain'});
// res.end('okay');
if (! argv.web) {
return http_error(response, 403, "403 Permission Denied");
}
var uri = url.parse(request.url).pathname
, filename = path.join(argv.web, uri);
fs.exists(filename, function(exists) {
if(!exists) {
return http_error(response, 404, "404 Not Found");
}
if (fs.statSync(filename).isDirectory()) {
filename += '/index.html';
}
fs.readFile(filename, "binary", function(err, file) {
if(err) {
return http_error(response, 500, err);
}
response.writeHead(200);
response.write(file, "binary");
response.end();
});
});
};
// Select 'binary' or 'base64' subprotocol, preferring 'binary'
selectProtocol = function(protocols, callback) {
if (protocols.indexOf('binary') >= 0) {
callback(true, 'binary');
} else if (protocols.indexOf('base64') >= 0) {
callback(true, 'base64');
} else {
console.log("Client must support 'binary' or 'base64' protocol");
callback(false);
}
}
// parse source and target arguments into parts
try {
source_arg = argv._[0].toString();
target_arg = argv._[1].toString();
var idx;
idx = source_arg.indexOf(":");
if (idx >= 0) {
source_host = source_arg.slice(0, idx);
source_port = parseInt(source_arg.slice(idx+1), 10);
} else {
source_host = "";
source_port = parseInt(source_arg, 10);
}
idx = target_arg.indexOf(":");
if (idx < 0) {
throw("target must be host:port");
}
target_host = target_arg.slice(0, idx);
target_port = parseInt(target_arg.slice(idx+1), 10);
if (isNaN(source_port) || isNaN(target_port)) {
throw("illegal port");
}
} catch(e) {
console.error("websockify.js [--web web_dir] [--cert cert.pem [--key key.pem]] [source_addr:]source_port target_addr:target_port");
process.exit(2);
}
console.log("WebSocket settings: ");
console.log(" - proxying from " + source_host + ":" + source_port +
" to " + target_host + ":" + target_port);
if (argv.web) {
console.log(" - Web server active. Serving: " + argv.web);
}
if (argv.cert) {
argv.key = argv.key || argv.cert;
var cert = fs.readFileSync(argv.cert),
key = fs.readFileSync(argv.key);
console.log(" - Running in encrypted HTTPS (wss://) mode using: " + argv.cert + ", " + argv.key);
webServer = https.createServer({cert: cert, key: key}, http_request);
} else {
console.log(" - Running in unencrypted HTTP (ws://) mode");
webServer = http.createServer(http_request);
}
webServer.listen(source_port, function() {
wsServer = new WebSocketServer({server: webServer,
handleProtocols: selectProtocol});
wsServer.on('connection', new_client);
});
// Attach Flash policyfile answer service
policyfile.createServer().listen(-1, webServer);
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env bash
usage() {
if [ "$*" ]; then
echo "$*"
echo
fi
echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT]"
echo
echo "Starts the WebSockets proxy and a mini-webserver and "
echo "provides a cut-and-paste URL to go to."
echo
echo " --listen PORT Port for proxy/webserver to listen on"
echo " Default: 6080"
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
echo " Default: localhost:5900"
echo " --cert CERT Path to combined cert/key file"
echo " Default: self.pem"
echo " --web WEB Path to web files (e.g. vnc.html)"
echo " Default: ./"
exit 2
}
NAME="$(basename $0)"
HERE="$(cd "$(dirname "$0")" && pwd)"
PORT="6080"
VNC_DEST="localhost:5900"
CERT=""
WEB=""
proxy_pid=""
die() {
echo "$*"
exit 1
}
cleanup() {
trap - TERM QUIT INT EXIT
trap "true" CHLD # Ignore cleanup messages
echo
if [ -n "${proxy_pid}" ]; then
echo "Terminating WebSockets proxy (${proxy_pid})"
kill ${proxy_pid}
fi
}
# Process Arguments
# Arguments that only apply to chrooter itself
while [ "$*" ]; do
param=$1; shift; OPTARG=$1
case $param in
--listen) PORT="${OPTARG}"; shift ;;
--vnc) VNC_DEST="${OPTARG}"; shift ;;
--cert) CERT="${OPTARG}"; shift ;;
--web) WEB="${OPTARG}"; shift ;;
-h|--help) usage ;;
-*) usage "Unknown chrooter option: ${param}" ;;
*) break ;;
esac
done
# Sanity checks
which netstat >/dev/null 2>&1 \
|| die "Must have netstat installed"
netstat -ltn | grep -qs "${PORT} .*LISTEN" \
&& die "Port ${PORT} in use. Try --listen PORT"
trap "cleanup" TERM QUIT INT EXIT
# Find vnc.html
if [ -n "${WEB}" ]; then
if [ ! -e "${WEB}/vnc.html" ]; then
die "Could not find ${WEB}/vnc.html"
fi
elif [ -e "$(pwd)/vnc.html" ]; then
WEB=$(pwd)
elif [ -e "${HERE}/../vnc.html" ]; then
WEB=${HERE}/../
elif [ -e "${HERE}/vnc.html" ]; then
WEB=${HERE}
elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
WEB=${HERE}/../share/novnc/
else
die "Could not find vnc.html"
fi
# Find self.pem
if [ -n "${CERT}" ]; then
if [ ! -e "${CERT}" ]; then
die "Could not find ${CERT}"
fi
elif [ -e "$(pwd)/self.pem" ]; then
CERT="$(pwd)/self.pem"
elif [ -e "${HERE}/../self.pem" ]; then
CERT="${HERE}/../self.pem"
elif [ -e "${HERE}/self.pem" ]; then
CERT="${HERE}/self.pem"
else
echo "Warning: could not find self.pem"
fi
echo "Starting webserver and WebSockets proxy on port ${PORT}"
${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
proxy_pid="$!"
sleep 1
if ! ps -p ${proxy_pid} >/dev/null; then
proxy_pid=
echo "Failed to start WebSockets proxy"
exit 1
fi
echo -e "\n\nNavigate to this URL:\n"
echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
echo -e "Press Ctrl-C to exit\n\n"
wait ${proxy_pid}
+13
View File
@@ -0,0 +1,13 @@
(defproject websockify "1.0.0-SNAPSHOT"
:description "Clojure implementation of Websockify"
:url "https://github.com/kanaka/websockify"
:dependencies [[org.clojure/clojure "1.2.1"]
[org.clojure/tools.cli "0.2.1"]
[ring/ring-jetty-adapter "1.0.0-beta2"]
[org.eclipse.jetty/jetty-websocket "7.5.4.v20111024"]
[org.eclipse.jetty/jetty-server "7.5.4.v20111024"]
[org.eclipse.jetty/jetty-servlet "7.5.4.v20111024"]
[org.jboss.netty/netty "3.2.5.Final"]]
;:dev-dependencies [[swank-clojure "1.3.0-SNAPSHOT"]]
:main websockify
)
+802
View File
@@ -0,0 +1,802 @@
/*
* WebSocket lib with support for "wss://" encryption.
* Copyright 2010 Joel Martin
* Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
*
* You can make a cert/key with openssl using:
* openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
* as taken from http://docs.python.org/dev/library/ssl.html#certificates
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h> // daemonizing
#include <fcntl.h> // daemonizing
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <resolv.h> /* base64 encode/decode */
#include <openssl/md5.h> /* md5 hash */
#include <openssl/sha.h> /* sha1 hash */
#include "websocket.h"
/*
* Global state
*
* Warning: not thread safe
*/
int ssl_initialized = 0;
int pipe_error = 0;
settings_t settings;
void traffic(char * token) {
if ((settings.verbose) && (! settings.daemon)) {
fprintf(stdout, "%s", token);
fflush(stdout);
}
}
void error(char *msg)
{
perror(msg);
}
void fatal(char *msg)
{
perror(msg);
exit(1);
}
/* resolve host with also IP address parsing */
int resolve_host(struct in_addr *sin_addr, const char *hostname)
{
if (!inet_aton(hostname, sin_addr)) {
struct addrinfo *ai, *cur;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
if (getaddrinfo(hostname, NULL, &hints, &ai))
return -1;
for (cur = ai; cur; cur = cur->ai_next) {
if (cur->ai_family == AF_INET) {
*sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr;
freeaddrinfo(ai);
return 0;
}
}
freeaddrinfo(ai);
return -1;
}
return 0;
}
/*
* SSL Wrapper Code
*/
ssize_t ws_recv(ws_ctx_t *ctx, void *buf, size_t len) {
if (ctx->ssl) {
//handler_msg("SSL recv\n");
return SSL_read(ctx->ssl, buf, len);
} else {
return recv(ctx->sockfd, buf, len, 0);
}
}
ssize_t ws_send(ws_ctx_t *ctx, const void *buf, size_t len) {
if (ctx->ssl) {
//handler_msg("SSL send\n");
return SSL_write(ctx->ssl, buf, len);
} else {
return send(ctx->sockfd, buf, len, 0);
}
}
ws_ctx_t *alloc_ws_ctx() {
ws_ctx_t *ctx;
if (! (ctx = malloc(sizeof(ws_ctx_t))) )
{ fatal("malloc()"); }
if (! (ctx->cin_buf = malloc(BUFSIZE)) )
{ fatal("malloc of cin_buf"); }
if (! (ctx->cout_buf = malloc(BUFSIZE)) )
{ fatal("malloc of cout_buf"); }
if (! (ctx->tin_buf = malloc(BUFSIZE)) )
{ fatal("malloc of tin_buf"); }
if (! (ctx->tout_buf = malloc(BUFSIZE)) )
{ fatal("malloc of tout_buf"); }
ctx->headers = malloc(sizeof(headers_t));
ctx->ssl = NULL;
ctx->ssl_ctx = NULL;
return ctx;
}
int free_ws_ctx(ws_ctx_t *ctx) {
free(ctx->cin_buf);
free(ctx->cout_buf);
free(ctx->tin_buf);
free(ctx->tout_buf);
free(ctx);
}
ws_ctx_t *ws_socket(ws_ctx_t *ctx, int socket) {
ctx->sockfd = socket;
}
ws_ctx_t *ws_socket_ssl(ws_ctx_t *ctx, int socket, char * certfile, char * keyfile) {
int ret;
char msg[1024];
char * use_keyfile;
ws_socket(ctx, socket);
if (keyfile && (keyfile[0] != '\0')) {
// Separate key file
use_keyfile = keyfile;
} else {
// Combined key and cert file
use_keyfile = certfile;
}
// Initialize the library
if (! ssl_initialized) {
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ssl_initialized = 1;
}
ctx->ssl_ctx = SSL_CTX_new(TLSv1_server_method());
if (ctx->ssl_ctx == NULL) {
ERR_print_errors_fp(stderr);
fatal("Failed to configure SSL context");
}
if (SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, use_keyfile,
SSL_FILETYPE_PEM) <= 0) {
sprintf(msg, "Unable to load private key file %s\n", use_keyfile);
fatal(msg);
}
if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, certfile,
SSL_FILETYPE_PEM) <= 0) {
sprintf(msg, "Unable to load certificate file %s\n", certfile);
fatal(msg);
}
// if (SSL_CTX_set_cipher_list(ctx->ssl_ctx, "DEFAULT") != 1) {
// sprintf(msg, "Unable to set cipher\n");
// fatal(msg);
// }
// Associate socket and ssl object
ctx->ssl = SSL_new(ctx->ssl_ctx);
SSL_set_fd(ctx->ssl, socket);
ret = SSL_accept(ctx->ssl);
if (ret < 0) {
ERR_print_errors_fp(stderr);
return NULL;
}
return ctx;
}
int ws_socket_free(ws_ctx_t *ctx) {
if (ctx->ssl) {
SSL_free(ctx->ssl);
ctx->ssl = NULL;
}
if (ctx->ssl_ctx) {
SSL_CTX_free(ctx->ssl_ctx);
ctx->ssl_ctx = NULL;
}
if (ctx->sockfd) {
shutdown(ctx->sockfd, SHUT_RDWR);
close(ctx->sockfd);
ctx->sockfd = 0;
}
}
/* ------------------------------------------------------- */
int encode_hixie(u_char const *src, size_t srclength,
char *target, size_t targsize) {
int sz = 0, len = 0;
target[sz++] = '\x00';
len = b64_ntop(src, srclength, target+sz, targsize-sz);
if (len < 0) {
return len;
}
sz += len;
target[sz++] = '\xff';
return sz;
}
int decode_hixie(char *src, size_t srclength,
u_char *target, size_t targsize,
unsigned int *opcode, unsigned int *left) {
char *start, *end, cntstr[4];
int i, len, framecount = 0, retlen = 0;
unsigned char chr;
if ((src[0] != '\x00') || (src[srclength-1] != '\xff')) {
handler_emsg("WebSocket framing error\n");
return -1;
}
*left = srclength;
if (srclength == 2 &&
(src[0] == '\xff') &&
(src[1] == '\x00')) {
// client sent orderly close frame
*opcode = 0x8; // Close frame
return 0;
}
*opcode = 0x1; // Text frame
start = src+1; // Skip '\x00' start
do {
/* We may have more than one frame */
end = (char *)memchr(start, '\xff', srclength);
*end = '\x00';
len = b64_pton(start, target+retlen, targsize-retlen);
if (len < 0) {
return len;
}
retlen += len;
start = end + 2; // Skip '\xff' end and '\x00' start
framecount++;
} while (end < (src+srclength-1));
if (framecount > 1) {
snprintf(cntstr, 3, "%d", framecount);
traffic(cntstr);
}
*left = 0;
return retlen;
}
int encode_hybi(u_char const *src, size_t srclength,
char *target, size_t targsize, unsigned int opcode)
{
unsigned long long b64_sz, len_offset = 1, payload_offset = 2, len = 0;
if ((int)srclength <= 0)
{
return 0;
}
b64_sz = ((srclength - 1) / 3) * 4 + 4;
target[0] = (char)(opcode & 0x0F | 0x80);
if (b64_sz <= 125) {
target[1] = (char) b64_sz;
payload_offset = 2;
} else if ((b64_sz > 125) && (b64_sz < 65536)) {
target[1] = (char) 126;
*(u_short*)&(target[2]) = htons(b64_sz);
payload_offset = 4;
} else {
handler_emsg("Sending frames larger than 65535 bytes not supported\n");
return -1;
//target[1] = (char) 127;
//*(u_long*)&(target[2]) = htonl(b64_sz);
//payload_offset = 10;
}
len = b64_ntop(src, srclength, target+payload_offset, targsize-payload_offset);
if (len < 0) {
return len;
}
return len + payload_offset;
}
int decode_hybi(unsigned char *src, size_t srclength,
u_char *target, size_t targsize,
unsigned int *opcode, unsigned int *left)
{
unsigned char *frame, *mask, *payload, save_char, cntstr[4];;
int masked = 0;
int i = 0, len, framecount = 0;
size_t remaining;
unsigned int target_offset = 0, hdr_length = 0, payload_length = 0;
*left = srclength;
frame = src;
//printf("Deocde new frame\n");
while (1) {
// Need at least two bytes of the header
// Find beginning of next frame. First time hdr_length, masked and
// payload_length are zero
frame += hdr_length + 4*masked + payload_length;
//printf("frame[0..3]: 0x%x 0x%x 0x%x 0x%x (tot: %d)\n",
// (unsigned char) frame[0],
// (unsigned char) frame[1],
// (unsigned char) frame[2],
// (unsigned char) frame[3], srclength);
if (frame > src + srclength) {
//printf("Truncated frame from client, need %d more bytes\n", frame - (src + srclength) );
break;
}
remaining = (src + srclength) - frame;
if (remaining < 2) {
//printf("Truncated frame header from client\n");
break;
}
framecount ++;
*opcode = frame[0] & 0x0f;
masked = (frame[1] & 0x80) >> 7;
if (*opcode == 0x8) {
// client sent orderly close frame
break;
}
payload_length = frame[1] & 0x7f;
if (payload_length < 126) {
hdr_length = 2;
//frame += 2 * sizeof(char);
} else if (payload_length == 126) {
payload_length = (frame[2] << 8) + frame[3];
hdr_length = 4;
} else {
handler_emsg("Receiving frames larger than 65535 bytes not supported\n");
return -1;
}
if ((hdr_length + 4*masked + payload_length) > remaining) {
continue;
}
//printf(" payload_length: %u, raw remaining: %u\n", payload_length, remaining);
payload = frame + hdr_length + 4*masked;
if (*opcode != 1 && *opcode != 2) {
handler_msg("Ignoring non-data frame, opcode 0x%x\n", *opcode);
continue;
}
if (payload_length == 0) {
handler_msg("Ignoring empty frame\n");
continue;
}
if ((payload_length > 0) && (!masked)) {
handler_emsg("Received unmasked payload from client\n");
return -1;
}
// Terminate with a null for base64 decode
save_char = payload[payload_length];
payload[payload_length] = '\0';
// unmask the data
mask = payload - 4;
for (i = 0; i < payload_length; i++) {
payload[i] ^= mask[i%4];
}
// base64 decode the data
len = b64_pton((const char*)payload, target+target_offset, targsize);
// Restore the first character of the next frame
payload[payload_length] = save_char;
if (len < 0) {
handler_emsg("Base64 decode error code %d", len);
return len;
}
target_offset += len;
//printf(" len %d, raw %s\n", len, frame);
}
if (framecount > 1) {
snprintf(cntstr, 3, "%d", framecount);
traffic(cntstr);
}
*left = remaining;
return target_offset;
}
int parse_handshake(ws_ctx_t *ws_ctx, char *handshake) {
char *start, *end;
headers_t *headers = ws_ctx->headers;
headers->key1[0] = '\0';
headers->key2[0] = '\0';
headers->key3[0] = '\0';
if ((strlen(handshake) < 92) || (bcmp(handshake, "GET ", 4) != 0)) {
return 0;
}
start = handshake+4;
end = strstr(start, " HTTP/1.1");
if (!end) { return 0; }
strncpy(headers->path, start, end-start);
headers->path[end-start] = '\0';
start = strstr(handshake, "\r\nHost: ");
if (!start) { return 0; }
start += 8;
end = strstr(start, "\r\n");
strncpy(headers->host, start, end-start);
headers->host[end-start] = '\0';
headers->origin[0] = '\0';
start = strstr(handshake, "\r\nOrigin: ");
if (start) {
start += 10;
} else {
start = strstr(handshake, "\r\nSec-WebSocket-Origin: ");
if (!start) { return 0; }
start += 24;
}
end = strstr(start, "\r\n");
strncpy(headers->origin, start, end-start);
headers->origin[end-start] = '\0';
start = strstr(handshake, "\r\nSec-WebSocket-Version: ");
if (start) {
// HyBi/RFC 6455
start += 25;
end = strstr(start, "\r\n");
strncpy(headers->version, start, end-start);
headers->version[end-start] = '\0';
ws_ctx->hixie = 0;
ws_ctx->hybi = strtol(headers->version, NULL, 10);
start = strstr(handshake, "\r\nSec-WebSocket-Key: ");
if (!start) { return 0; }
start += 21;
end = strstr(start, "\r\n");
strncpy(headers->key1, start, end-start);
headers->key1[end-start] = '\0';
start = strstr(handshake, "\r\nConnection: ");
if (!start) { return 0; }
start += 14;
end = strstr(start, "\r\n");
strncpy(headers->connection, start, end-start);
headers->connection[end-start] = '\0';
start = strstr(handshake, "\r\nSec-WebSocket-Protocol: ");
if (!start) { return 0; }
start += 26;
end = strstr(start, "\r\n");
strncpy(headers->protocols, start, end-start);
headers->protocols[end-start] = '\0';
} else {
// Hixie 75 or 76
ws_ctx->hybi = 0;
start = strstr(handshake, "\r\n\r\n");
if (!start) { return 0; }
start += 4;
if (strlen(start) == 8) {
ws_ctx->hixie = 76;
strncpy(headers->key3, start, 8);
headers->key3[8] = '\0';
start = strstr(handshake, "\r\nSec-WebSocket-Key1: ");
if (!start) { return 0; }
start += 22;
end = strstr(start, "\r\n");
strncpy(headers->key1, start, end-start);
headers->key1[end-start] = '\0';
start = strstr(handshake, "\r\nSec-WebSocket-Key2: ");
if (!start) { return 0; }
start += 22;
end = strstr(start, "\r\n");
strncpy(headers->key2, start, end-start);
headers->key2[end-start] = '\0';
} else {
ws_ctx->hixie = 75;
}
}
return 1;
}
int parse_hixie76_key(char * key) {
unsigned long i, spaces = 0, num = 0;
for (i=0; i < strlen(key); i++) {
if (key[i] == ' ') {
spaces += 1;
}
if ((key[i] >= 48) && (key[i] <= 57)) {
num = num * 10 + (key[i] - 48);
}
}
return num / spaces;
}
int gen_md5(headers_t *headers, char *target) {
unsigned long key1 = parse_hixie76_key(headers->key1);
unsigned long key2 = parse_hixie76_key(headers->key2);
char *key3 = headers->key3;
MD5_CTX c;
char in[HIXIE_MD5_DIGEST_LENGTH] = {
key1 >> 24, key1 >> 16, key1 >> 8, key1,
key2 >> 24, key2 >> 16, key2 >> 8, key2,
key3[0], key3[1], key3[2], key3[3],
key3[4], key3[5], key3[6], key3[7]
};
MD5_Init(&c);
MD5_Update(&c, (void *)in, sizeof in);
MD5_Final((void *)target, &c);
target[HIXIE_MD5_DIGEST_LENGTH] = '\0';
return 1;
}
static void gen_sha1(headers_t *headers, char *target) {
SHA_CTX c;
unsigned char hash[SHA_DIGEST_LENGTH];
int r;
SHA1_Init(&c);
SHA1_Update(&c, headers->key1, strlen(headers->key1));
SHA1_Update(&c, HYBI_GUID, 36);
SHA1_Final(hash, &c);
r = b64_ntop(hash, sizeof hash, target, HYBI10_ACCEPTHDRLEN);
//assert(r == HYBI10_ACCEPTHDRLEN - 1);
}
ws_ctx_t *do_handshake(int sock) {
char handshake[4096], response[4096], sha1[29], trailer[17];
char *scheme, *pre;
headers_t *headers;
int len, ret, i, offset;
ws_ctx_t * ws_ctx;
// Peek, but don't read the data
len = recv(sock, handshake, 1024, MSG_PEEK);
handshake[len] = 0;
if (len == 0) {
handler_msg("ignoring empty handshake\n");
return NULL;
} else if (bcmp(handshake, "<policy-file-request/>", 22) == 0) {
len = recv(sock, handshake, 1024, 0);
handshake[len] = 0;
handler_msg("sending flash policy response\n");
send(sock, POLICY_RESPONSE, sizeof(POLICY_RESPONSE), 0);
return NULL;
} else if ((bcmp(handshake, "\x16", 1) == 0) ||
(bcmp(handshake, "\x80", 1) == 0)) {
// SSL
if (!settings.cert) {
handler_msg("SSL connection but no cert specified\n");
return NULL;
} else if (access(settings.cert, R_OK) != 0) {
handler_msg("SSL connection but '%s' not found\n",
settings.cert);
return NULL;
}
ws_ctx = alloc_ws_ctx();
ws_socket_ssl(ws_ctx, sock, settings.cert, settings.key);
if (! ws_ctx) { return NULL; }
scheme = "wss";
handler_msg("using SSL socket\n");
} else if (settings.ssl_only) {
handler_msg("non-SSL connection disallowed\n");
return NULL;
} else {
ws_ctx = alloc_ws_ctx();
ws_socket(ws_ctx, sock);
if (! ws_ctx) { return NULL; }
scheme = "ws";
handler_msg("using plain (not SSL) socket\n");
}
offset = 0;
for (i = 0; i < 10; i++) {
len = ws_recv(ws_ctx, handshake+offset, 4096);
if (len == 0) {
handler_emsg("Client closed during handshake\n");
return NULL;
}
offset += len;
handshake[offset] = 0;
if (strstr(handshake, "\r\n\r\n")) {
break;
}
usleep(10);
}
//handler_msg("handshake: %s\n", handshake);
if (!parse_handshake(ws_ctx, handshake)) {
handler_emsg("Invalid WS request\n");
return NULL;
}
headers = ws_ctx->headers;
if (ws_ctx->hybi > 0) {
handler_msg("using protocol HyBi/IETF 6455 %d\n", ws_ctx->hybi);
gen_sha1(headers, sha1);
sprintf(response, SERVER_HANDSHAKE_HYBI, sha1, "base64");
} else {
if (ws_ctx->hixie == 76) {
handler_msg("using protocol Hixie 76\n");
gen_md5(headers, trailer);
pre = "Sec-";
} else {
handler_msg("using protocol Hixie 75\n");
trailer[0] = '\0';
pre = "";
}
sprintf(response, SERVER_HANDSHAKE_HIXIE, pre, headers->origin, pre, scheme,
headers->host, headers->path, pre, "base64", trailer);
}
//handler_msg("response: %s\n", response);
ws_send(ws_ctx, response, strlen(response));
return ws_ctx;
}
void signal_handler(sig) {
switch (sig) {
case SIGHUP: break; // ignore for now
case SIGPIPE: pipe_error = 1; break; // handle inline
case SIGTERM: exit(0); break;
}
}
void daemonize(int keepfd) {
int pid, i;
umask(0);
chdir("/");
setgid(getgid());
setuid(getuid());
/* Double fork to daemonize */
pid = fork();
if (pid<0) { fatal("fork error"); }
if (pid>0) { exit(0); } // parent exits
setsid(); // Obtain new process group
pid = fork();
if (pid<0) { fatal("fork error"); }
if (pid>0) { exit(0); } // parent exits
/* Signal handling */
signal(SIGHUP, signal_handler); // catch HUP
signal(SIGTERM, signal_handler); // catch kill
/* Close open files */
for (i=getdtablesize(); i>=0; --i) {
if (i != keepfd) {
close(i);
} else if (settings.verbose) {
printf("keeping fd %d\n", keepfd);
}
}
i=open("/dev/null", O_RDWR); // Redirect stdin
dup(i); // Redirect stdout
dup(i); // Redirect stderr
}
void start_server() {
int lsock, csock, pid, clilen, sopt = 1, i;
struct sockaddr_in serv_addr, cli_addr;
ws_ctx_t *ws_ctx;
/* Initialize buffers */
lsock = socket(AF_INET, SOCK_STREAM, 0);
if (lsock < 0) { error("ERROR creating listener socket"); }
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(settings.listen_port);
/* Resolve listen address */
if (settings.listen_host && (settings.listen_host[0] != '\0')) {
if (resolve_host(&serv_addr.sin_addr, settings.listen_host) < -1) {
fatal("Could not resolve listen address");
}
} else {
serv_addr.sin_addr.s_addr = INADDR_ANY;
}
setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, (char *)&sopt, sizeof(sopt));
if (bind(lsock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
fatal("ERROR on binding listener socket");
}
listen(lsock,100);
signal(SIGPIPE, signal_handler); // catch pipe
if (settings.daemon) {
daemonize(lsock);
}
// Reep zombies
signal(SIGCHLD, SIG_IGN);
printf("Waiting for connections on %s:%d\n",
settings.listen_host, settings.listen_port);
while (1) {
clilen = sizeof(cli_addr);
pipe_error = 0;
pid = 0;
csock = accept(lsock,
(struct sockaddr *) &cli_addr,
&clilen);
if (csock < 0) {
error("ERROR on accept");
continue;
}
handler_msg("got client connection from %s\n",
inet_ntoa(cli_addr.sin_addr));
if (!settings.run_once) {
handler_msg("forking handler process\n");
pid = fork();
}
if (pid == 0) { // handler process
ws_ctx = do_handshake(csock);
if (settings.run_once) {
if (ws_ctx == NULL) {
// Not a real WebSocket connection
continue;
} else {
// Successful connection, stop listening for new
// connections
close(lsock);
}
}
if (ws_ctx == NULL) {
handler_msg("No connection after handshake\n");
break; // Child process exits
}
settings.handler(ws_ctx);
if (pipe_error) {
handler_emsg("Closing due to SIGPIPE\n");
}
break; // Child process exits
} else { // parent process
settings.handler_id += 1;
}
}
if (pid == 0) {
if (ws_ctx) {
ws_socket_free(ws_ctx);
free_ws_ctx(ws_ctx);
} else {
shutdown(csock, SHUT_RDWR);
close(csock);
}
handler_msg("handler exit\n");
} else {
handler_msg("websockify exit\n");
}
}
+84
View File
@@ -0,0 +1,84 @@
#include <openssl/ssl.h>
#define BUFSIZE 65536
#define DBUFSIZE (BUFSIZE * 3) / 4 - 20
#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\
Sec-WebSocket-Accept: %s\r\n\
Sec-WebSocket-Protocol: %s\r\n\
\r\n"
#define HYBI_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
#define HYBI10_ACCEPTHDRLEN 29
#define HIXIE_MD5_DIGEST_LENGTH 16
#define POLICY_RESPONSE "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\n"
typedef struct {
char path[1024+1];
char host[1024+1];
char origin[1024+1];
char version[1024+1];
char connection[1024+1];
char protocols[1024+1];
char key1[1024+1];
char key2[1024+1];
char key3[8+1];
} headers_t;
typedef struct {
int sockfd;
SSL_CTX *ssl_ctx;
SSL *ssl;
int hixie;
int hybi;
headers_t *headers;
char *cin_buf;
char *cout_buf;
char *tin_buf;
char *tout_buf;
} ws_ctx_t;
typedef struct {
int verbose;
char listen_host[256];
int listen_port;
void (*handler)(ws_ctx_t*);
int handler_id;
char *cert;
char *key;
int ssl_only;
int daemon;
int run_once;
} settings_t;
ssize_t ws_recv(ws_ctx_t *ctx, void *buf, size_t len);
ssize_t ws_send(ws_ctx_t *ctx, const void *buf, size_t len);
/* base64.c declarations */
//int b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize);
//int b64_pton(char const *src, u_char *target, size_t targsize);
#define gen_handler_msg(stream, ...) \
if (! settings.daemon) { \
fprintf(stream, " %d: ", settings.handler_id); \
fprintf(stream, __VA_ARGS__); \
}
#define handler_msg(...) gen_handler_msg(stdout, __VA_ARGS__);
#define handler_emsg(...) gen_handler_msg(stderr, __VA_ARGS__);
+493
View File
@@ -0,0 +1,493 @@
# Python WebSocket library with support for "wss://" encryption.
# Copyright 2011 Joel Martin
# Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
#
# Supports following protocol versions:
# - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
# - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
# - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
require 'gserver'
require 'openssl'
require 'stringio'
require 'digest/md5'
require 'digest/sha1'
require 'base64'
unless OpenSSL::SSL::SSLSocket.instance_methods.index("read_nonblock")
module OpenSSL
module SSL
class SSLSocket
alias :read_nonblock :readpartial
end
end
end
end
class EClose < Exception
end
class WebSocketServer < GServer
@@Buffer_size = 65536
#
# WebSocket constants
#
@@Server_handshake_hixie = "HTTP/1.1 101 Web Socket Protocol Handshake\r
Upgrade: WebSocket\r
Connection: Upgrade\r
%sWebSocket-Origin: %s\r
%sWebSocket-Location: %s://%s%s\r
"
@@Server_handshake_hybi = "HTTP/1.1 101 Switching Protocols\r
Upgrade: websocket\r
Connection: Upgrade\r
Sec-WebSocket-Accept: %s\r
"
@@GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
def initialize(opts)
vmsg "in WebSocketServer.initialize"
port = opts['listen_port']
host = opts['listen_host'] || GServer::DEFAULT_HOST
super(port, host)
msg opts.inspect
if opts['server_cert']
msg "creating ssl context"
@sslContext = OpenSSL::SSL::SSLContext.new
@sslContext.cert = OpenSSL::X509::Certificate.new(File.open(opts['server_cert']))
@sslContext.key = OpenSSL::PKey::RSA.new(File.open(opts['server_key']))
@sslContext.ca_file = opts['server_cert']
@sslContext.verify_mode = OpenSSL::SSL::VERIFY_NONE
@sslContext.verify_depth = 0
end
@@client_id = 0 # Track client number total on class
@verbose = opts['verbose']
@opts = opts
end
def serve(io)
@@client_id += 1
msg self.inspect
if @sslContext
msg "Enabling SSL context"
ssl = OpenSSL::SSL::SSLSocket.new(io, @sslContext)
#ssl.sync_close = true
#ssl.sync = true
msg "SSL accepting"
ssl.accept
io = ssl # replace the unencrypted handle with the encrypted one
end
msg "initializing thread"
# Initialize per thread state
t = Thread.current
t[:my_client_id] = @@client_id
t[:send_parts] = []
t[:recv_part] = nil
t[:base64] = nil
puts "in serve, client: #{t[:my_client_id].inspect}"
begin
t[:client] = do_handshake(io)
new_websocket_client(t[:client])
rescue EClose => e
msg "Client closed: #{e.message}"
return
rescue Exception => e
msg "Uncaught exception: #{e.message}"
msg "Trace: #{e.backtrace}"
return
end
msg "Client disconnected"
end
#
# WebSocketServer logging/output functions
#
def traffic(token)
if @verbose then print token; STDOUT.flush; end
end
def msg(m)
printf("% 3d: %s\n", Thread.current[:my_client_id] || 0, m)
end
def vmsg(m)
if @verbose then msg(m) end
end
#
# WebSocketServer general support routines
#
def gen_md5(h)
key1 = h['sec-websocket-key1']
key2 = h['sec-websocket-key2']
key3 = h['key3']
spaces1 = key1.count(" ")
spaces2 = key2.count(" ")
num1 = key1.scan(/[0-9]/).join('').to_i / spaces1
num2 = key2.scan(/[0-9]/).join('').to_i / spaces2
return Digest::MD5.digest([num1, num2, key3].pack('NNa8'))
end
def unmask(buf, hlen, length)
pstart = hlen + 4
mask = buf[hlen...hlen+4].each_byte.map{|b|b}
data = buf[pstart...pstart+length]
#data = data.bytes.zip(mask.bytes.cycle(length)).map { |d,m| d^m }
i=-1
data = data.each_byte.map{|b| i+=1; (b ^ mask[i % 4]).chr}.join("")
return data
end
def encode_hybi(buf, opcode, base64=false)
if base64
buf = Base64.encode64(buf).gsub(/\n/, '')
end
b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
payload_len = buf.length
if payload_len <= 125
header = [b1, payload_len].pack('CC')
elsif payload_len > 125 && payload_len < 65536
header = [b1, 126, payload_len].pack('CCn')
elsif payload_len >= 65536
header = [b1, 127, payload_len >> 32,
payload_len & 0xffffffff].pack('CCNN')
end
return [header + buf, header.length, 0]
end
def decode_hybi(buf, base64=false)
f = {'fin' => 0,
'opcode' => 0,
'hlen' => 2,
'length' => 0,
'payload' => nil,
'left' => 0,
'close_code' => nil,
'close_reason' => nil}
blen = buf.length
f['left'] = blen
if blen < f['hlen'] then return f end # incomplete frame
b1, b2 = buf.unpack('CC')
f['opcode'] = b1 & 0x0f
f['fin'] = (b1 & 0x80) >> 7
has_mask = (b2 & 0x80) >> 7
f['length'] = b2 & 0x7f
if f['length'] == 126
f['hlen'] = 4
if blen < f['hlen'] then return f end # incomplete frame
f['length'] = buf.unpack('xxn')[0]
elsif f['length'] == 127
f['hlen'] = 10
if blen < f['hlen'] then return f end # incomplete frame
top, bottom = buf.unpack('xxNN')
f['length'] = (top << 32) & bottom
end
full_len = f['hlen'] + has_mask * 4 + f['length']
if blen < full_len then return f end # incomplete frame
# number of bytes that are part of the next frame(s)
f['left'] = blen - full_len
if has_mask > 0
f['payload'] = unmask(buf, f['hlen'], f['length'])
else
f['payload'] = buf[f['hlen']...full_len]
end
if base64 and [1, 2].include?(f['opcode'])
f['payload'] = Base64.decode64(f['payload'])
end
# close frame
if f['opcode'] == 0x08
if f['length'] >= 2
f['close_code'] = f['payload'].unpack('n')
end
if f['length'] > 3
f['close_reason'] = f['payload'][2...f['payload'].length]
end
end
return f
end
def encode_hixie(buf)
return ["\x00" + Base64.encode64(buf).gsub(/\n/, '') + "\xff", 1, 1]
end
def decode_hixie(buf)
last = buf.index("\377")
return {'payload' => Base64.decode64(buf[1...last]),
'hlen' => 1,
'length' => last - 1,
'left' => buf.length - (last + 1)}
end
def send_frames(bufs)
t = Thread.current
if bufs.length > 0
encbuf = ""
bufs.each do |buf|
if t[:version].start_with?("hybi")
if t[:base64]
encbuf, lenhead, lentail = encode_hybi(
buf, opcode=1, base64=true)
else
encbuf, lenhead, lentail = encode_hybi(
buf, opcode=2, base64=false)
end
else
encbuf, lenhead, lentail = encode_hixie(buf)
end
t[:send_parts] << encbuf
end
end
while t[:send_parts].length > 0
buf = t[:send_parts].shift
sent = t[:client].write(buf)
if sent == buf.length
traffic "<"
else
traffic "<."
t[:send_parts].unshift(buf[sent...buf.length])
end
end
return t[:send_parts].length
end
# Receive and decode Websocket frames
# Returns: [bufs_list, closed_string]
def recv_frames()
t = Thread.current
closed = false
bufs = []
buf = t[:client].read_nonblock(@@Buffer_size)
if buf.length == 0
return bufs, "Client closed abrubtly"
end
if t[:recv_part]
buf = t[:recv_part] + buf
t[:recv_part] = nil
end
while buf.length > 0
if t[:version].start_with?("hybi")
frame = decode_hybi(buf, base64=t[:base64])
if frame['payload'] == nil
traffic "}."
if frame['left'] > 0
t[:recv_part] = buf[-frame['left']...buf.length]
end
break
else
if frame['opcode'] == 0x8
closed = "Client closed, reason: %s - %s" % [
frame['close_code'], frame['close_reason']]
break
end
end
else
if buf[0...2] == "\xff\x00"
closed = "Client sent orderly close frame"
break
elsif buf[0...2] == "\x00\xff"
buf = buf[2...buf.length]
continue # No-op frame
elsif buf.count("\xff") == 0
# Partial frame
traffic "}."
t[:recv_part] = buf
break
end
frame = decode_hixie(buf)
end
#msg "Receive frame: #{frame.inspect}"
traffic "}"
bufs << frame['payload']
if frame['left'] > 0
buf = buf[-frame['left']...buf.length]
else
buf = ''
end
end
return bufs, closed
end
def send_close(code=nil, reason='')
t = Thread.current
if t[:version].start_with?("hybi")
msg = ''
if code
msg = [reason.length, code].pack("na8")
end
buf, lenh, lent = encode_hybi(msg, opcode=0x08, base64=false)
t[:client].write(buf)
elsif t[:version] == "hixie-76"
buf = "\xff\x00"
t[:client].write(buf)
end
end
def do_handshake(sock)
t = Thread.current
stype = ""
if !IO.select([sock], nil, nil, 3)
raise EClose, "ignoring socket not ready"
end
handshake = ""
msg "About to read from sock [#{sock.inspect}]"
handshake = sock.read_nonblock(1024)
msg "Handshake [#{handshake.inspect}]"
if handshake == nil or handshake == ""
raise(EClose, "ignoring empty handshake")
else
stype = "Plain non-SSL (ws://)"
scheme = "ws"
if sock.class == OpenSSL::SSL::SSLSocket
stype = "SSL (wss://)"
scheme = "wss"
end
retsock = sock
end
h = t[:headers] = {}
hlines = handshake.split("\r\n")
req_split = hlines.shift.match(/^(\w+) (\/[^\s]*) HTTP\/1\.1$/)
t[:path] = req_split[2].strip
hlines.each do |hline|
break if hline == ""
hsplit = hline.match(/^([^:]+):\s*(.+)$/)
h[hsplit[1].strip.downcase] = hsplit[2]
end
puts "Headers: #{h.inspect}"
unless h.has_key?('upgrade') &&
h['upgrade'].downcase == 'websocket'
raise EClose, "Non-WebSocket connection"
end
protocols = h.fetch("sec-websocket-protocol", h["websocket-protocol"])
ver = h.fetch('sec-websocket-version', nil)
if ver
# HyBi/IETF vesrion of the protocol
# HyBi 07 reports version 7
# HyBi 08 - 12 report version 8
# HyBi 13 and up report version 13
if ['7', '8', '13'].include?(ver)
t[:version] = "hybi-%02d" % [ver.to_i]
else
raise EClose, "Unsupported protocol version %s" % [ver]
end
# choose binary if client supports it
if protocols.include?('binary')
t[:base64] = false
elsif protocols.include?('base64')
t[:base64] = true
else
raise EClose, "Client must support 'binary' or 'base64' sub-protocol"
end
key = h['sec-websocket-key']
# Generate the hash value for the accpet header
accept = Base64.encode64(
Digest::SHA1.digest(key + @@GUID)).gsub(/\n/, '')
response = @@Server_handshake_hybi % [accept]
if t[:base64]
response += "Sec-WebSocket-Protocol: base64\r\n"
else
response += "Sec-WebSocket-Protocol: binary\r\n"
end
response += "\r\n"
else
# Hixie vesrion of the protocol (75 or 76)
body = handshake.match(/\r\n\r\n(........)/)
if body
h['key3'] = body[1]
trailer = gen_md5(h)
pre = "Sec-"
t[:version] = "hixie-76"
else
trailer = ""
pre = ""
t[:version] = "hixie-75"
end
# base64 required for Hixie since payload is only UTF-8
t[:base64] = true
response = @@Server_handshake_hixie % [pre, h['origin'], pre,
"ws", h['host'], t[:path]]
if protocols && protocols.include?('base64')
response += "%sWebSocket-Protocol: base64\r\n" % [pre]
else
msg "Warning: client does not report 'base64' protocol support"
end
response += "\r\n" + trailer
end
msg "%s WebSocket connection" % [stype]
msg "Version %s, base64: '%s'" % [t[:version], t[:base64]]
if t[:path] then msg "Path: '%s'" % [t[:path]] end
#puts "sending reponse #{response.inspect}"
retsock.write(response)
# Return the WebSocket socket which may be SSL wrapped
return retsock
end
end
# vim: sw=2
+385
View File
@@ -0,0 +1,385 @@
/*
* A WebSocket to TCP socket proxy with support for "wss://" encryption.
* Copyright 2010 Joel Martin
* Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
*
* You can make a cert/key with openssl using:
* openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
* as taken from http://docs.python.org/dev/library/ssl.html#certificates
*/
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <getopt.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/select.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "websocket.h"
char traffic_legend[] = "\n\
Traffic Legend:\n\
} - Client receive\n\
}. - Client receive partial\n\
{ - Target receive\n\
\n\
> - Target send\n\
>. - Target send partial\n\
< - Client send\n\
<. - Client send partial\n\
";
char USAGE[] = "Usage: [options] " \
"[source_addr:]source_port target_addr:target_port\n\n" \
" --verbose|-v verbose messages and per frame traffic\n" \
" --daemon|-D become a daemon (background process)\n" \
" --cert CERT SSL certificate file\n" \
" --key KEY SSL key file (if separate from cert)\n" \
" --ssl-only disallow non-encrypted connections";
#define usage(fmt, args...) \
fprintf(stderr, "%s\n\n", USAGE); \
fprintf(stderr, fmt , ## args); \
exit(1);
char target_host[256];
int target_port;
extern pipe_error;
extern settings_t settings;
void do_proxy(ws_ctx_t *ws_ctx, int target) {
fd_set rlist, wlist, elist;
struct timeval tv;
int i, maxfd, client = ws_ctx->sockfd;
unsigned int opcode, left, ret;
unsigned int tout_start, tout_end, cout_start, cout_end;
unsigned int tin_start, tin_end;
ssize_t len, bytes;
tout_start = tout_end = cout_start = cout_end;
tin_start = tin_end = 0;
maxfd = client > target ? client+1 : target+1;
while (1) {
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&rlist);
FD_ZERO(&wlist);
FD_ZERO(&elist);
FD_SET(client, &elist);
FD_SET(target, &elist);
if (tout_end == tout_start) {
// Nothing queued for target, so read from client
FD_SET(client, &rlist);
} else {
// Data queued for target, so write to it
FD_SET(target, &wlist);
}
if (cout_end == cout_start) {
// Nothing queued for client, so read from target
FD_SET(target, &rlist);
} else {
// Data queued for client, so write to it
FD_SET(client, &wlist);
}
ret = select(maxfd, &rlist, &wlist, &elist, &tv);
if (pipe_error) { break; }
if (FD_ISSET(target, &elist)) {
handler_emsg("target exception\n");
break;
}
if (FD_ISSET(client, &elist)) {
handler_emsg("client exception\n");
break;
}
if (ret == -1) {
handler_emsg("select(): %s\n", strerror(errno));
break;
} else if (ret == 0) {
//handler_emsg("select timeout\n");
continue;
}
if (FD_ISSET(target, &wlist)) {
len = tout_end-tout_start;
bytes = send(target, ws_ctx->tout_buf + tout_start, len, 0);
if (pipe_error) { break; }
if (bytes < 0) {
handler_emsg("target connection error: %s\n",
strerror(errno));
break;
}
tout_start += bytes;
if (tout_start >= tout_end) {
tout_start = tout_end = 0;
traffic(">");
} else {
traffic(">.");
}
}
if (FD_ISSET(client, &wlist)) {
len = cout_end-cout_start;
bytes = ws_send(ws_ctx, ws_ctx->cout_buf + cout_start, len);
if (pipe_error) { break; }
if (len < 3) {
handler_emsg("len: %d, bytes: %d: %d\n",
(int) len, (int) bytes,
(int) *(ws_ctx->cout_buf + cout_start));
}
cout_start += bytes;
if (cout_start >= cout_end) {
cout_start = cout_end = 0;
traffic("<");
} else {
traffic("<.");
}
}
if (FD_ISSET(target, &rlist)) {
bytes = recv(target, ws_ctx->cin_buf, DBUFSIZE , 0);
if (pipe_error) { break; }
if (bytes <= 0) {
handler_emsg("target closed connection\n");
break;
}
cout_start = 0;
if (ws_ctx->hybi) {
cout_end = encode_hybi(ws_ctx->cin_buf, bytes,
ws_ctx->cout_buf, BUFSIZE, 1);
} else {
cout_end = encode_hixie(ws_ctx->cin_buf, bytes,
ws_ctx->cout_buf, BUFSIZE);
}
/*
printf("encoded: ");
for (i=0; i< cout_end; i++) {
printf("%u,", (unsigned char) *(ws_ctx->cout_buf+i));
}
printf("\n");
*/
if (cout_end < 0) {
handler_emsg("encoding error\n");
break;
}
traffic("{");
}
if (FD_ISSET(client, &rlist)) {
bytes = ws_recv(ws_ctx, ws_ctx->tin_buf + tin_end, BUFSIZE-1);
if (pipe_error) { break; }
if (bytes <= 0) {
handler_emsg("client closed connection\n");
break;
}
tin_end += bytes;
/*
printf("before decode: ");
for (i=0; i< bytes; i++) {
printf("%u,", (unsigned char) *(ws_ctx->tin_buf+i));
}
printf("\n");
*/
if (ws_ctx->hybi) {
len = decode_hybi(ws_ctx->tin_buf + tin_start,
tin_end-tin_start,
ws_ctx->tout_buf, BUFSIZE-1,
&opcode, &left);
} else {
len = decode_hixie(ws_ctx->tin_buf + tin_start,
tin_end-tin_start,
ws_ctx->tout_buf, BUFSIZE-1,
&opcode, &left);
}
if (opcode == 8) {
handler_emsg("client sent orderly close frame\n");
break;
}
/*
printf("decoded: ");
for (i=0; i< len; i++) {
printf("%u,", (unsigned char) *(ws_ctx->tout_buf+i));
}
printf("\n");
*/
if (len < 0) {
handler_emsg("decoding error\n");
break;
}
if (left) {
tin_start = tin_end - left;
//printf("partial frame from client");
} else {
tin_start = 0;
tin_end = 0;
}
traffic("}");
tout_start = 0;
tout_end = len;
}
}
}
void proxy_handler(ws_ctx_t *ws_ctx) {
int tsock = 0;
struct sockaddr_in taddr;
handler_msg("connecting to: %s:%d\n", target_host, target_port);
tsock = socket(AF_INET, SOCK_STREAM, 0);
if (tsock < 0) {
handler_emsg("Could not create target socket: %s\n",
strerror(errno));
return;
}
bzero((char *) &taddr, sizeof(taddr));
taddr.sin_family = AF_INET;
taddr.sin_port = htons(target_port);
/* Resolve target address */
if (resolve_host(&taddr.sin_addr, target_host) < -1) {
handler_emsg("Could not resolve target address: %s\n",
strerror(errno));
}
if (connect(tsock, (struct sockaddr *) &taddr, sizeof(taddr)) < 0) {
handler_emsg("Could not connect to target: %s\n",
strerror(errno));
close(tsock);
return;
}
if ((settings.verbose) && (! settings.daemon)) {
printf("%s", traffic_legend);
}
do_proxy(ws_ctx, tsock);
shutdown(tsock, SHUT_RDWR);
close(tsock);
}
int main(int argc, char *argv[])
{
int fd, c, option_index = 0;
static int ssl_only = 0, daemon = 0, run_once = 0, verbose = 0;
char *found;
static struct option long_options[] = {
{"verbose", no_argument, &verbose, 'v'},
{"ssl-only", no_argument, &ssl_only, 1 },
{"daemon", no_argument, &daemon, 'D'},
/* ---- */
{"run-once", no_argument, 0, 'r'},
{"cert", required_argument, 0, 'c'},
{"key", required_argument, 0, 'k'},
{0, 0, 0, 0}
};
settings.cert = realpath("self.pem", NULL);
if (!settings.cert) {
/* Make sure it's always set to something */
settings.cert = "self.pem";
}
settings.key = "";
while (1) {
c = getopt_long (argc, argv, "vDrc:k:",
long_options, &option_index);
/* Detect the end */
if (c == -1) { break; }
switch (c) {
case 0:
break; // ignore
case 1:
break; // ignore
case 'v':
verbose = 1;
break;
case 'D':
daemon = 1;
break;
case 'r':
run_once = 1;
break;
case 'c':
settings.cert = realpath(optarg, NULL);
if (! settings.cert) {
usage("No cert file at %s\n", optarg);
}
break;
case 'k':
settings.key = realpath(optarg, NULL);
if (! settings.key) {
usage("No key file at %s\n", optarg);
}
break;
default:
usage("");
}
}
settings.verbose = verbose;
settings.ssl_only = ssl_only;
settings.daemon = daemon;
settings.run_once = run_once;
if ((argc-optind) != 2) {
usage("Invalid number of arguments\n");
}
found = strstr(argv[optind], ":");
if (found) {
memcpy(settings.listen_host, argv[optind], found-argv[optind]);
settings.listen_port = strtol(found+1, NULL, 10);
} else {
settings.listen_host[0] = '\0';
settings.listen_port = strtol(argv[optind], NULL, 10);
}
optind++;
if (settings.listen_port == 0) {
usage("Could not parse listen_port\n");
}
found = strstr(argv[optind], ":");
if (found) {
memcpy(target_host, argv[optind], found-argv[optind]);
target_port = strtol(found+1, NULL, 10);
} else {
usage("Target argument must be host:port\n");
}
if (target_port == 0) {
usage("Could not parse target port\n");
}
if (ssl_only) {
if (access(settings.cert, R_OK) != 0) {
usage("SSL only and cert file '%s' not found\n", settings.cert);
}
} else if (access(settings.cert, R_OK) != 0) {
fprintf(stderr, "Warning: '%s' not found\n", settings.cert);
}
//printf(" verbose: %d\n", settings.verbose);
//printf(" ssl_only: %d\n", settings.ssl_only);
//printf(" daemon: %d\n", settings.daemon);
//printf(" run_once: %d\n", settings.run_once);
//printf(" cert: %s\n", settings.cert);
//printf(" key: %s\n", settings.key);
settings.handler = proxy_handler;
start_server();
}
+233
View File
@@ -0,0 +1,233 @@
(ns websockify
;(:use ring.adapter.jetty)
(:require [clojure.tools.cli :as cli]
[clojure.string :as string])
(:import
;; Netty TCP Client
[java.util.concurrent Executors]
[java.net InetSocketAddress]
[org.jboss.netty.channel
Channels SimpleChannelHandler ChannelPipelineFactory]
[org.jboss.netty.buffer ChannelBuffers]
[org.jboss.netty.channel.socket.nio NioClientSocketChannelFactory]
[org.jboss.netty.bootstrap ClientBootstrap]
[org.jboss.netty.handler.codec.base64 Base64]
[org.jboss.netty.util CharsetUtil]
;; Jetty WebSocket Server
[org.eclipse.jetty.server Server]
[org.eclipse.jetty.server.nio BlockingChannelConnector]
[org.eclipse.jetty.servlet
ServletContextHandler ServletHolder DefaultServlet]
[org.eclipse.jetty.websocket
WebSocket WebSocket$OnTextMessage
WebSocketClientFactory WebSocketClient WebSocketServlet]))
;; TCP / NIO
;; (defn tcp-channel [host port]
;; (try
;; (let [address (InetSocketAddress. host port)
;; channel (doto (SocketChannel/open)
;; (.connect address))]
;; channel)
;; (catch Exception e
;; (println (str "Failed to connect to'" host ":" port "':" e))
;; nil)))
;; http://docs.jboss.org/netty/3.2/guide/html/start.html#d0e51
;; http://stackoverflow.com/questions/5453602/highly-concurrent-http-with-netty-and-nio
;; https://github.com/datskos/ring-netty-adapter/blob/master/src/ring/adapter/netty.clj
(defn netty-client [host port open close message]
(let [handler (proxy [SimpleChannelHandler] []
(channelConnected [ctx e] (open ctx e))
(channelDisconnected [ctx e] (close ctx e))
(messageReceived [ctx e] (message ctx e))
(exceptionCaught [ctx e]
(println "exceptionCaught:" e)))
pipeline (proxy [ChannelPipelineFactory] []
(getPipeline []
(doto (Channels/pipeline)
(.addLast "handler" handler))))
bootstrap (doto (ClientBootstrap.
(NioClientSocketChannelFactory.
(Executors/newCachedThreadPool)
(Executors/newCachedThreadPool)))
(.setPipelineFactory pipeline)
(.setOption "tcpNoDelay" true)
(.setOption "keepAlive" true))
channel-future (.connect bootstrap (InetSocketAddress. host port))
channel (.. channel-future (awaitUninterruptibly) (getChannel))]
channel))
;; WebSockets
;; http://wiki.eclipse.org/Jetty/Feature/WebSockets
(defn make-websocket-servlet [open close message]
(proxy [WebSocketServlet] []
(doGet [request response]
;;(println "doGet" request)
(.. (proxy-super getServletContext)
(getNamedDispatcher (proxy-super getServletName))
(forward request response)))
(doWebSocketConnect [request response]
(println "doWebSocketConnect")
(reify WebSocket$OnTextMessage
(onOpen [this connection] (open this connection))
(onClose [this code message] (close this code message))
(onMessage [this data] (message this data))))))
(defn websocket-server
[port & {:keys [open close message ws-path web]
:or {open (fn [_ conn]
(println "New websocket client:" conn))
close (fn [_ code reason]
(println "Websocket client closed:" code reason))
message (fn [_ data]
(println "Websocket message:" data))
ws-path "/websocket"}}]
(let [http-servlet (doto (ServletHolder. (DefaultServlet.))
(.setInitParameter "dirAllowed" "true")
(.setInitParameter "resourceBase" web))
ws-servlet (ServletHolder.
(make-websocket-servlet open close message))
context (doto (ServletContextHandler.)
(.setContextPath "/")
(.addServlet ws-servlet ws-path))
connector (doto (BlockingChannelConnector.)
(.setPort port)
(.setMaxIdleTime Integer/MAX_VALUE))
server (doto (Server.)
(.setHandler context)
(.addConnector connector))]
(when web (.addServlet context http-servlet "/"))
server))
;; Websockify
(defonce settings (atom {}))
;; WebSocket client to TCP target mappings
(defonce clients (atom {}))
(defonce targets (atom {}))
(defn target-open [ctx e]
(println "Connected to target")
#_(println "channelConnected:" e))
(defn target-close [ctx e]
#_(println "channelDisconnected:" e)
(println "Target closed")
(when-let [channel (get @targets (.getChannel ctx))]
(.disconnect channel)))
(defn target-message [ctx e]
(let [channel (.getChannel ctx)
client (get @targets channel)
msg (.getMessage e)
len (.readableBytes msg)
b64 (Base64/encode msg false)
blen (.readableBytes b64)]
#_(println "received" len "bytes from target")
#_(println "target receive:" (.toString msg 0 len CharsetUtil/UTF_8))
#_(println "sending to client:" (.toString b64 0 blen CharsetUtil/UTF_8))
(.sendMessage client (.toString b64 0 blen CharsetUtil/UTF_8))))
(defn client-open [this connection]
#_(println "Got WebSocket connection:" connection)
(println "New client")
(let [target (netty-client
(:target-host @settings)
(:target-port @settings)
target-open target-close target-message)]
(swap! clients assoc this {:client connection
:target target})
(swap! targets assoc target connection)))
(defn client-close [this code message]
(println "WebSocket connection closed")
(when-let [target (:target (get @clients this))]
(println "Closing target")
(.close target)
(println "Target closed")
(swap! targets dissoc target))
(swap! clients dissoc this))
(defn client-message [this data]
#_(println "WebSocket onMessage:" data)
(let [target (:target (get @clients this))
cbuf (ChannelBuffers/copiedBuffer data CharsetUtil/UTF_8)
decbuf (Base64/decode cbuf)
rlen (.readableBytes decbuf)]
#_(println "Sending" rlen "bytes to target")
#_(println "Sending to target:" (.toString decbuf 0 rlen CharsetUtil/UTF_8))
(.write target decbuf)))
(defn start-websockify
[& {:keys [listen-port target-host target-port web]
:or {listen-port 6080
target-host "localhost"
target-port 5900
}}]
(reset! clients {})
(reset! targets {})
(reset! settings {:target-host target-host
:target-port target-port})
(let [server (websocket-server listen-port
:web web
:ws-path "/websockify"
:open client-open
:close client-close
:message client-message)]
(.start server)
(if web
(println "Serving web requests from:" web)
(println "Not serving web requests"))
(defn stop-websockify []
(doseq [client (vals @clients)]
(.disconnect (:client client))
(.close (:target client)))
(.stop server)
(reset! clients {})
(reset! targets {})
nil)))
(defn -main [& args]
(let [[options args banner]
(cli/cli
args
["-v" "--[no-]verbose" "Verbose output"]
["--web" "Run webserver with root at given location"]
["-h" "--help" "Show help" :default false :flag true]
)]
(when (or (:help options)
(not= 2 (count args)))
(println banner)
(System/exit 0))
(println options)
(println args)
(let [target (second args)
[target-host target-port] (string/split target #":")]
(start-websockify :listen-port (Integer/parseInt (first args))
:target-host target-host
:target-port (Integer/parseInt target-port)
:web (:web options))))
nil)
+171
View File
@@ -0,0 +1,171 @@
#!/usr/bin/env ruby
# A WebSocket to TCP socket proxy
# Copyright 2011 Joel Martin
# Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
require 'socket'
$: << "other"
$: << "../other"
require 'websocket'
require 'optparse'
# Proxy traffic to and from a WebSockets client to a normal TCP
# socket server target. All traffic to/from the client is base64
# encoded/decoded to allow binary data to be sent/received to/from
# the target.
class WebSocketProxy < WebSocketServer
@@Traffic_legend = "
Traffic Legend:
} - Client receive
}. - Client receive partial
{ - Target receive
> - Target send
>. - Target send partial
< - Client send
<. - Client send partial
"
def initialize(opts)
vmsg "in WebSocketProxy.initialize"
super(opts)
@target_host = opts["target_host"]
@target_port = opts["target_port"]
end
# Echo back whatever is received
def new_websocket_client(client)
msg "connecting to: %s:%s" % [@target_host, @target_port]
tsock = TCPSocket.open(@target_host, @target_port)
if @verbose then puts @@Traffic_legend end
begin
do_proxy(client, tsock)
rescue
tsock.shutdown(Socket::SHUT_RDWR)
tsock.close
raise
end
end
# Proxy client WebSocket to normal target socket.
def do_proxy(client, target)
cqueue = []
c_pend = 0
tqueue = []
rlist = [client, target]
loop do
wlist = []
if tqueue.length > 0
wlist << target
end
if cqueue.length > 0 || c_pend > 0
wlist << client
end
ins, outs, excepts = IO.select(rlist, wlist, nil, 0.001)
if excepts && excepts.length > 0
raise Exception, "Socket exception"
end
# Send queued client data to the target
if outs && outs.include?(target)
dat = tqueue.shift
sent = target.send(dat, 0)
if sent == dat.length
traffic ">"
else
tqueue.unshift(dat[sent...dat.length])
traffic ".>"
end
end
# Receive target data and queue for the client
if ins && ins.include?(target)
buf = target.recv(@@Buffer_size)
if buf.length == 0
raise EClose, "Target closed"
end
cqueue << buf
traffic "{"
end
# Encode and send queued data to the client
if outs && outs.include?(client)
c_pend = send_frames(cqueue)
cqueue = []
end
# Receive client data, decode it, and send it back
if ins && ins.include?(client)
frames, closed = recv_frames
tqueue += frames
if closed
send_close
raise EClose, closed
end
end
end # loop
end
end
# Parse parameters
opts = {}
parser = OptionParser.new do |o|
o.on('--verbose', '-v') { |b| opts['verbose'] = b }
o.parse!
end
if ARGV.length < 2
puts "Too few arguments"
exit 2
end
# Parse host:port and convert ports to numbers
if ARGV[0].count(":") > 0
opts['listen_host'], _, opts['listen_port'] = ARGV[0].rpartition(':')
else
opts['listen_host'], opts['listen_port'] = nil, ARGV[0]
end
begin
opts['listen_port'] = opts['listen_port'].to_i
rescue
puts "Error parsing listen port"
exit 2
end
if ARGV[1].count(":") > 0
opts['target_host'], _, opts['target_port'] = ARGV[1].rpartition(':')
else
puts "Error parsing target"
exit 2
end
begin
opts['target_port'] = opts['target_port'].to_i
rescue
puts "Error parsing target port"
exit 2
end
puts "Starting server on #{opts['listen_host']}:#{opts['listen_port']}"
server = WebSocketProxy.new(opts)
server.start(100)
server.join
puts "Server has been terminated"
# vim: sw=2
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
usage() {
echo "Usage: $(basename $0) PORT CMDLINE"
echo
echo " PORT Port to wrap with WebSockets support"
echo " CMDLINE Command line to wrap"
exit 2
}
# Parameter defaults
mydir=$(readlink -f $(dirname ${0}))
# Process parameters
#while [ "${1}" != "${1#-}" ]; do
# param=$1; shift
#done
export WSWRAP_PORT="${1}"; shift
LD_PRELOAD=${mydir}/wswrapper.so "${@}"