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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
Executable
+118
@@ -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}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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__);
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
Executable
+171
@@ -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
|
||||
Executable
+22
@@ -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 "${@}"
|
||||
|
||||
Reference in New Issue
Block a user