123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- // A WebSocket to TCP socket proxy
- // Copyright 2010 Joel Martin
- // Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
- var net = require('net'),
- sys = require('sys'),
- crypto = require('crypto'),
- source_arg, source_host, source_port,
- target_arg, target_host, target_port;
- // md5 calculation borrowed from Socket.IO (MIT license)
- function gen_md5(headers, k3) {
- var k1 = headers['sec-websocket-key1'],
- k2 = headers['sec-websocket-key2'],
- md5 = crypto.createHash('md5');
- [k1, k2].forEach(function(k){
- var n = parseInt(k.replace(/[^\d]/g, '')),
- spaces = k.replace(/[^ ]/g, '').length;
- if (spaces === 0 || n % spaces !== 0){
- return false;
- }
- n /= spaces;
- md5.update(String.fromCharCode(
- n >> 24 & 0xFF,
- n >> 16 & 0xFF,
- n >> 8 & 0xFF,
- n & 0xFF));
- });
- md5.update(k3.toString('binary'));
- return md5.digest('binary');
- }
- function encode(buf) {
- return String.fromCharCode(0) +
- buf.toString('base64', 0) +
- String.fromCharCode(255);
- }
- function decode(data) {
- var i, len = 0, strs, retstrs = [],
- buf = new Buffer(data.length),
- str = data.toString('binary', 1, data.length-1);
- if (str.indexOf('\xff') > -1) {
- // We've gotten multiple frames at once
- strs = str.split('\xff\x00')
- for (i = 0; i < strs.length; i++) {
- len = buf.write(strs[i], 0, 'base64');
- retstrs.push(buf.toString('binary', 0, len));
- }
- return retstrs.join("");
- } else {
- len = buf.write(str, 0, 'base64');
- return buf.toString('binary', 0, len);
- }
- }
- var server = net.createServer(function (client) {
- var handshake = "", headers = {}, header,
- version, path, k1, k2, k3, target = null;
- function cleanup() {
- client.end();
- if (target) {
- target.end();
- target = null;
- }
- }
- function do_handshake(data) {
- var i, idx, dlen = data.length, lines, location, rheaders,
- sec_hdr;
- //sys.log("received handshake data: " + data);
- handshake += data.toString('utf8');
- if ((data[dlen-12] != 13) ||
- (data[dlen-11] != 10) ||
- (data[dlen-10] != 13) ||
- (data[dlen-9] != 10)) {
- //sys.log("Got partial handshake");
- return;
- }
- //sys.log("Got whole handshake");
- if (handshake.indexOf('GET ') != 0) {
- sys.error("Got invalid handshake");
- client.end();
- return;
- }
- lines = handshake.split('\r\n');
- path = lines[0].split(' ')[1];
- //sys.log("path: " + path);
- k3 = data.slice(dlen-8, dlen);
- for (i = 1; i < lines.length; i++) {
- //sys.log("lines[i]: " + lines[i]);
- if (lines[i].length == 0) { break; }
- idx = lines[i].indexOf(': ');
- if (idx < 0) {
- sys.error("Got invalid handshake header");
- client.end();
- return;
- }
- header = lines[i].slice(0, idx).toLowerCase();
- headers[header] = lines[i].slice(idx+2);
- }
- //console.dir(headers);
- //sys.log("k3: " + k3 + ", k3.length: " + k3.length);
- if (headers.upgrade !== 'WebSocket') {
- sys.error("Upgrade header is not 'WebSocket'");
- client.end();
- return;
- }
- location = (headers.origin.substr(0, 5) == 'https' ? 'wss' : 'ws')
- + '://' + headers.host + path;
- //sys.log("location: " + location);
- if ('sec-websocket-key1' in headers) {
- version = 76;
- sec_hdr = "Sec-";
- } else {
- version = 75;
- sec_hdr = "";
- }
- sys.log("using protocol version " + version);
- rheaders = [
- 'HTTP/1.1 101 WebSocket Protocol Handshake',
- 'Upgrade: WebSocket',
- 'Connection: Upgrade',
- sec_hdr + 'WebSocket-Origin: ' + headers.origin,
- sec_hdr + 'WebSocket-Location: ' + location
- ];
- if ('sec-websocket-protocol' in headers) {
- rheaders.push('Sec-WebSocket-Protocol: ' + headers['sec-websocket-protocol']);
- }
- rheaders.push('');
- if (version === 76) {
- rheaders.push(gen_md5(headers, k3));
- }
- // Switch listener to normal data path
- client.on('data', client_data);
- //client.setEncoding('utf8');
- client.removeListener('data', do_handshake);
- // Do not delay writes
- client.setNoDelay(true);
- // Send the handshake response
- try {
- //sys.log("response: " + rheaders.join('\r\n'));
- client.write(rheaders.join('\r\n'), 'binary');
- } catch(e) {
- sys.error("Failed to send handshake response");
- client.end();
- return;
- }
- // Create a connection to the target
- target = net.createConnection(target_port, target_host);
- target.on('data', target_data);
- target.on('end', function () {
- sys.log("received target end");
- cleanup();
- });
- target.on('error', function (exc) {
- sys.log("received target error: " + exc);
- cleanup();
- });
- }
- function client_data(data) {
- var ret;
- //sys.log("received client data: " + data);
- //sys.log(" decoded: " + decode(data));
- try {
- ret = target.write(decode(data), 'binary');
- if (! ret) {
- sys.log("target write returned false");
- }
- } catch(e) {
- sys.log("fatal error writing to target");
- cleanup();
- }
- }
- function target_data(data) {
- //sys.log("received target data: " + data);
- //sys.log(" encoded: " + encode(data));
- try {
- client.write(encode(data), 'binary');
- } catch(e) {
- sys.log("fatal error writing to client");
- cleanup();
- }
- }
- client.on('connect', function () {
- sys.log("Got client connection");
- });
- client.on('data', do_handshake);
- client.on('end', function () {
- sys.log("recieved client end");
- cleanup();
- });
- client.on('error', function (exc) {
- sys.log("recieved client error: " + exc);
- cleanup();
- });
- });
- // parse source and target into parts
- source_arg = process.argv[2];
- target_arg = process.argv[3];
- try {
- 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("wsproxy.py [source_addr:]source_port target_addr:target_port");
- process.exit(2);
- }
- sys.log("source: " + source_host + ":" + source_port);
- sys.log("target: " + target_host + ":" + target_port);
- server.listen(source_port, source_host);
|