Skip to content
Snippets Groups Projects
Commit fbb44264 authored by echicken's avatar echicken
Browse files

These are no longer needed and will only cause confusion alongside other websocket scripts.

parent 746e9c81
No related branches found
No related tags found
No related merge requests found
load('sha1.js');
var WebSocketProxy = function(client) {
var WEBSOCKET_NEED_PACKET_START = 0;
var WEBSOCKET_NEED_PAYLOAD_LENGTH = 1;
var WEBSOCKET_NEED_MASKING_KEY = 2;
var WEBSOCKET_DATA = 3;
var FFrameMask = [];
var FFrameOpCode = 0;
var FFramePayloadLength = 0;
var FFramePayloadReceived = 0;
var FWebSocketState = WEBSOCKET_NEED_PACKET_START;
var self = this;
this.headers = [];
var ClientDataBuffer = []; // From client
var ServerDataBuffer = []; // From server
function CalculateWebSocketKey(InLine) {
var Digits = '';
var Spaces = 0;
for (var i = 0; i < InLine.length; i++) {
if (InLine.charAt(i) == ' ') {
Spaces++;
} else if (!isNaN(InLine.charAt(i))) {
Digits += InLine.charAt(i);
}
}
return Digits / Spaces;
}
function GetFromWebSocketClient() {
switch (self.headers['Version']) {
case 0: return GetFromWebSocketClientDraft0();
case 7:
case 8:
case 13:
return GetFromWebSocketClientVersion7();
}
}
function GetFromWebSocketClientDraft0() {
var Result = [];
var InByte = 0;
var InByte2 = 0;
var InByte3 = 0;
while (client.socket.data_waiting) {
InByte = client.socket.recvBin(1);
// Check what the client packet state is
switch (FWebSocketState) {
case WEBSOCKET_NEED_PACKET_START:
// Check for 0x00 to indicate the start of a data packet
if (InByte === 0x00) {
FWebSocketState = WEBSOCKET_DATA;
}
break;
case WEBSOCKET_DATA:
// We're in a data packet, so check for 0xFF, which
// indicates the data packet is done
if (InByte == 0xFF) {
FWebSocketState = WEBSOCKET_NEED_PACKET_START;
} else {
// Check if the byte needs to be UTF-8 decoded
if (InByte < 128) {
Result.push(InByte);
} else if ((InByte > 191) && (InByte < 224)) {
// Handle UTF-8 decode
InByte2 = client.socket.recvBin(1);
Result.push(((InByte & 31) << 6) | (InByte2 & 63));
} else {
// Handle UTF-8 decode (should never need this, but
// included anyway)
InByte2 = client.socket.recvBin(1);
InByte3 = client.socket.recvBin(1);
Result.push(
((InByte&15)<<12)|((InByte2&63)<<6)|(InByte3&63)
);
}
}
break;
}
}
return Result;
}
function GetFromWebSocketClientVersion7() {
var Result = [];
var InByte = 0;
var InByte2 = 0;
var InByte3 = 0;
while (client.socket.data_waiting) {
// Check what the client packet state is
switch (FWebSocketState) {
case WEBSOCKET_NEED_PACKET_START:
// Next byte will give us the opcode, and also tell is if
// the message is fragmented
FFrameMask = [];
FFrameOpCode = client.socket.recvBin(1);
FFramePayloadLength = 0;
FFramePayloadReceived = 0;
FWebSocketState = WEBSOCKET_NEED_PAYLOAD_LENGTH;
break;
case WEBSOCKET_NEED_PAYLOAD_LENGTH:
FFramePayloadLength = (client.socket.recvBin(1) & 0x7F);
if (FFramePayloadLength === 126) {
FFramePayloadLength = client.socket.recvBin(2);
} else if (FFramePayloadLength === 127) {
FFramePayloadLength = client.socket.recvBin(8);
}
FWebSocketState = WEBSOCKET_NEED_MASKING_KEY;
break;
case WEBSOCKET_NEED_MASKING_KEY:
InByte = client.socket.recvBin(4);
FFrameMask[0] = (InByte & 0xFF000000) >> 24;
FFrameMask[1] = (InByte & 0x00FF0000) >> 16;
FFrameMask[2] = (InByte & 0x0000FF00) >> 8;
FFrameMask[3] = InByte & 0x000000FF;
FWebSocketState = WEBSOCKET_DATA;
break;
case WEBSOCKET_DATA:
InByte = (
client.socket.recvBin(1)^FFrameMask[
FFramePayloadReceived++ % 4
]
);
// Check if the byte needs to be UTF-8 decoded
if ((InByte & 0x80) === 0) {
Result.push(InByte);
} else if ((InByte & 0xE0) === 0xC0) {
// Handle UTF-8 decode
InByte2 = (
client.socket.recvBin(1)^FFrameMask[
FFramePayloadReceived++ % 4
]
);
Result.push(((InByte & 31) << 6) | (InByte2 & 63));
} else {
log(LOG_NOTICE, 'Byte out of range: ' + InByte);
}
// Check if we've received the full payload
if (FFramePayloadReceived === FFramePayloadLength) {
FWebSocketState = WEBSOCKET_NEED_PACKET_START;
}
break;
}
}
return Result;
}
function SendToWebSocketClient(AData) {
switch (self.headers['Version']) {
case 0:
SendToWebSocketClientDraft0(AData);
break;
case 7:
case 8:
case 13:
SendToWebSocketClientVersion7(AData);
break;
}
}
function SendToWebSocketClientDraft0(AData) {
// Send 0x00 to indicate the start of a data packet
client.socket.sendBin(0x00, 1);
for (var i = 0; i < AData.length; i++) {
// Check if the byte needs to be UTF-8 encoded
if ((AData[i] & 0xFF) <= 127) {
client.socket.sendBin(AData[i], 1);
} else if ((AData[i] & 0xFF) <= 2047) {
// Handle UTF-8 encode
client.socket.sendBin((AData[i] >> 6) | 192, 1);
client.socket.sendBin((AData[i] & 63) | 128, 1);
} else {
log(LOG_NOTICE, 'Byte out of range: ' + AData[i]);
}
}
// Send 0xFF to indicate the end of a data packet
client.socket.sendBin(0xFF, 1);
}
function SendToWebSocketClientVersion7(AData) {
if (AData.length > 0) {
var ToSend = [];
for (var i = 0; i < AData.length; i++) {
// Check if the byte needs to be UTF-8 encoded
if ((AData[i] & 0xFF) <= 127) {
ToSend.push(AData[i]);
} else if (
((AData[i] & 0xFF) >= 128) && ((AData[i] & 0xFF) <= 2047)
) {
// Handle UTF-8 encode
ToSend.push((AData[i] >> 6) | 192);
ToSend.push((AData[i] & 63) | 128);
} else {
log(LOG_NOTICE, 'Byte out of range: ' + AData[i]);
}
}
client.socket.sendBin(0x81, 1);
if (ToSend.length <= 125) {
client.socket.sendBin(ToSend.length, 1);
} else if (ToSend.length <= 65535) {
client.socket.sendBin(126, 1);
client.socket.sendBin(ToSend.length, 2);
} else {
// NOTE: client.socket.sendBin(ToSend.length, 8); didn't work,
// so this modification limits the send to 2^32 bytes (probably
// not an issue). Probably should look into a proper fix at
// some point though
client.socket.sendBin(127, 1);
client.socket.sendBin(0, 4);
client.socket.sendBin(ToSend.length, 4);
}
for (var i = 0; i < ToSend.length; i++) {
client.socket.sendBin(ToSend[i] & 0xFF, 1);
}
}
}
function ShakeHands() {
self.headers['Version'] = 0;
try {
// Keep reading header data until we get all the data we want
while (true) {
// Read another line, abort if we don't get one in 5 seconds
var InLine = client.socket.recvline(1024, 5);
if (InLine === null) {
log(LOG_ERR,
'Timeout exceeded while waiting for complete handshake'
);
return false;
}
log(LOG_DEBUG, 'Handshake Line: ' + InLine);
// Check for blank line (indicates we have most of the header,
// and only the last 8 bytes remain
if (InLine === '') {
switch (self.headers['Version']) {
case 0:
return ShakeHandsDraft0();
case 7:
case 8:
case 13:
return ShakeHandsVersion7();
default:
// TODO If this version does not match a version
// understood by the server, the server MUST abort
// the websocket handshake described in this section
// and instead send an appropriate HTTP error code
// (such as 426 Upgrade Required), and a
// |Sec-WebSocket-Version| header indicating the
// version(s) the server is capable of
// understanding.
break;
}
break;
} else if (InLine.indexOf('Connection:') === 0) {
// Example: "Connection: Upgrade"
self.headers['Connection'] = InLine.replace(
/Connection:\s?/i,
''
);
} else if (InLine.indexOf('GET') === 0) {
// Example: "GET /demo HTTP/1.1"
var GET = InLine.split(' ');
self.headers['Path'] = GET[1];
} else if (InLine.indexOf('Host:') === 0) {
// Example: "Host: example.com"
self.headers['Host'] = InLine.replace(/Host:\s?/i, '');
} else if (InLine.indexOf('Origin:') === 0) {
// Example: "Origin: http://example.com"
self.headers['Origin'] = InLine.replace(/Origin:\s?/i, '');
} else if (InLine.indexOf('Sec-WebSocket-Key:') === 0) {
// Example: "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ=="
self.headers['Key'] = InLine.replace(
/Sec-WebSocket-Key:\s?/i,
''
);
} else if (InLine.indexOf('Sec-WebSocket-Key1:') === 0) {
// Example: "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5"
self.headers['Key1'] = CalculateWebSocketKey(
InLine.replace(/Sec-WebSocket-Key1:\s?/i, '')
);
} else if (InLine.indexOf('Sec-WebSocket-Key2:') === 0) {
// Example: "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00"
self.headers['Key2'] = CalculateWebSocketKey(
InLine.replace(/Sec-WebSocket-Key2:\s?/i, '')
);
} else if (InLine.indexOf('Sec-WebSocket-Origin:') === 0) {
// Example: "Sec-WebSocket-Origin: http://example.com"
self.headers['Origin'] = InLine.replace(
/Sec-WebSocket-Origin:\s?/i,
''
);
} else if (InLine.indexOf('Sec-WebSocket-Protocol:') === 0) {
// Example: "Sec-WebSocket-Protocol: sample"
self.headers['SubProtocol'] = InLine.replace(
/Sec-WebSocket-Protocol:\s?/i,
''
);
} else if (InLine.indexOf('Sec-WebSocket-Draft') === 0) {
// Example: "Sec-WebSocket-Draft: 2"
try {
self.headers['Version'] = parseInt(
InLine.replace(/Sec-WebSocket-Draft:\s?/i, '')
);
} catch (err) {
self.headers['Version'] = 0;
}
} else if (InLine.indexOf('Sec-WebSocket-Version') === 0) {
// Example: "Sec-WebSocket-Version: 8"
try {
self.headers['Version'] = parseInt(
InLine.replace(/Sec-WebSocket-Version:\s?/i, '')
);
} catch (err) {
self.headers['Version'] = 0;
}
} else if (InLine.indexOf('Upgrade:') === 0) {
// Example: "Upgrade: websocket"
self.headers['Upgrade'] = InLine.replace(/Upgrade:\s?/i,'');
} else if (InLine.indexOf('Cookie:') === 0) {
self.headers['Cookie'] = InLine.replace(/Cookie:\s?/i, '');
}
}
} catch (err) {
log(LOG_ERR, 'ShakeHands() error: ' + err.toString());
}
return false;
}
function ShakeHandsDraft0() {
// Ensure we have all the data we need
if (('Key1' in self.headers) &&
('Key2' in self.headers) &&
('Host' in self.headers) &&
('Origin' in self.headers !== '') &&
('Path' in self.headers)
) {
// Combine Key1, Key2, and the last 8 bytes into a string that we
// will later hash
var ToHash = ''
ToHash += String.fromCharCode(
(self.headers['Key1'] & 0xFF000000) >> 24
);
ToHash += String.fromCharCode(
(self.headers['Key1'] & 0x00FF0000) >> 16
);
ToHash += String.fromCharCode(
(self.headers['Key1'] & 0x0000FF00) >> 8
);
ToHash += String.fromCharCode(
(self.headers['Key1'] & 0x000000FF) >> 0
);
ToHash += String.fromCharCode(
(self.headers['Key2'] & 0xFF000000) >> 24
);
ToHash += String.fromCharCode(
(self.headers['Key2'] & 0x00FF0000) >> 16
);
ToHash += String.fromCharCode(
(self.headers['Key2'] & 0x0000FF00) >> 8
);
ToHash += String.fromCharCode(
(self.headers['Key2'] & 0x000000FF) >> 0
);
for (var i = 0; i < 8; i++) {
ToHash += String.fromCharCode(client.socket.recvBin(1));
}
// Hash the string
var Hashed = md5_calc(ToHash, true);
// Setup the handshake response
var Response = 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' +
'Upgrade: WebSocket\r\n' +
'Connection: Upgrade\r\n' +
'Sec-WebSocket-Origin: ' + self.headers['Origin'] +
'\r\n' +
'Sec-WebSocket-Location: ws://' +
self.headers['Host'] +
self.headers['Path'] +
'\r\n';
if ('SubProtocol' in self.headers) {
Response +=
'Sec-WebSocket-Protocol: ' +
self.headers['SubProtocol'] +
'\r\n';
}
Response += '\r\n';
// Loop through the hash string (which is hex encoded) and append
// the individual bytes to the response
for (var i = 0; i < Hashed.length; i += 2) {
Response += String.fromCharCode(
parseInt(Hashed.charAt(i) + Hashed.charAt(i + 1), 16)
);
}
// Send the response and return
client.socket.send(Response);
return true;
} else {
// We're missing some pice of data, log what we do have
log(LOG_ERR,
'Missing some piece of handshake data. Here\'s what we have:'
);
for(var x in self.headers) {
log(LOG_ERR, x + ' => ' + self.headers[x]);
}
return false;
}
}
function ShakeHandsVersion7() {
// Ensure we have all the data we need
if (('Key' in self.headers) &&
('Host' in self.headers) &&
('Origin' in self.headers !== '') &&
('Path' in self.headers)
) {
var AcceptGUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
// Combine Key and GUID
var ToHash = self.headers['Key'] + AcceptGUID;
// Hash the string
var Hashed = Sha1.hash(ToHash, false);
// Encode the hash
var ToEncode = '';
for (var i = 0; i <= 38; i += 2) {
ToEncode += String.fromCharCode(
parseInt(Hashed.substr(i, 2), 16)
);
}
var Encoded = base64_encode(ToEncode);
// Setup the handshake response
var Response = 'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
'Sec-WebSocket-Accept: ' + Encoded + '\r\n';
if ('SubProtocol' in self.headers) {
// Only sub-protocol we support
Response += 'Sec-WebSocket-Protocol: plain\r\n';
}
Response += '\r\n';
// Send the response and return
client.socket.send(Response);
return true;
} else {
log(LOG_ERR,
'Missing some piece of handshake data. Here\'s what we have:'
);
for(var x in self.headers) {
log(LOG_ERR, x + ' => ' + self.headers[x]);
}
return false;
}
}
this.__defineGetter__(
'data_waiting',
function() {
return (ClientDataBuffer.length > 0);
}
);
this.send = function(data) {
if (typeof data === 'string') {
data = data.split('').map(
function (d) {
return ascii(d);
}
);
}
ServerDataBuffer = ServerDataBuffer.concat(data);
}
this.receive = function() {
var data = '';
while (ClientDataBuffer.length > 0) {
data += ascii(ClientDataBuffer.shift());
}
return data;
}
this.receiveArray = function(len) {
return ClientDataBuffer.splice(
0,
(typeof len === 'number' ? len : ClientDataBuffer.length)
);
}
this.cycle = function() {
ClientDataBuffer = ClientDataBuffer.concat(GetFromWebSocketClient());
SendToWebSocketClient(ServerDataBuffer.splice(0, 4096));
}
if(!ShakeHands()) throw 'ShakeHands() failed';
}
\ No newline at end of file
load('sbbsdefs.js');
load('websocket-proxy.js');
function err(msg) {
log(LOG_DEBUG, msg);
client.socket.close();
exit();
}
function getSession(un) {
var fn = format('%suser/%04d.web', system.data_dir, un);
if (!file_exists(fn)) return false;
var f = new File(fn);
if (!f.open('r')) return false;
var session = f.iniGetObject();
f.close();
return session;
}
var RLoginClient = function(options) {
var self = this;
const CAN = 0x18,
CR = 0x0D,
DC1 = 0x11,
DC3 = 0x13,
DOT = 0x2E,
EOM = 0x19,
EOT = 0x04,
LF = 0x0A,
SUB = 0x1A,
DISCARD = 0x02,
RAW = 0x10,
COOKED = 0x20,
WINDOW = 0x80;
var serverBuffer = []; // From server
var clientBuffer = []; // From client
var state = {
connected : false,
cooked : true,
suspendInput : false,
suspendOutput : false,
watchForClientEscape : true,
clientHasEscaped : false
};
var properties = {
rows : 24,
columns : 80,
pixelsX : 640,
pixelsY : 480,
clientEscape : '~'
};
// As suggested by RFC1282
var clientEscapes = {
DOT : self.disconnect,
EOT : self.disconnect,
SUB : function() {
state.suspendInput = (state.suspendInput) ? false : true;
state.suspendOutput = state.suspendInput;
},
EOM : function() {
state.suspendInput = (state.suspendInput) ? false : true;
state.suspendOutput = false;
}
};
this.__defineGetter__('connected', function () { return state.connected; });
this.__defineSetter__(
'connected',
function (value) {
if (typeof value === 'boolean' && !value) self.disconnect();
}
);
this.__defineGetter__('rows', function () { return properties.rows; });
this.__defineSetter__(
'rows',
function(value) {
if (typeof value === 'number' && value > 0) {
properties.rows = value;
} else {
throw 'RLogin: Invalid \'rows\' setting ' + value;
}
}
);
this.__defineGetter__(
'columns',
function () { return properties.columns; }
);
this.__defineSetter__(
'columns',
function (value) {
if (typeof value === 'number' && value > 0) {
properties.columns = value;
} else {
throw 'RLogin: Invalid \'columns\' setting ' + value;
}
}
);
this.__defineGetter__(
'pixelsX',
function () { return properties.pixelsX; }
);
this.__defineSetter__(
'pixelsX',
function (value) {
if (typeof value === 'number' && value > 0) {
properties.pixelsX = value;
} else {
throw 'RLogin: Invalid \'pixelsX\' setting ' + value;
}
}
);
this.__defineGetter__(
'pixelsY',
function () { return properties.pixelsY; }
);
this.__defineSetter__(
'pixelsY',
function (value) {
if (typeof value === 'number' && value > 0) {
properties.pixelsY = value;
} else {
throw 'RLogin: Invalid \'pixelsY\' setting ' + value;
}
}
);
this.__defineGetter__(
'clientEscape',
function() { return properties.clientEscape; }
);
this.__defineSetter__(
'clientEscape',
function (value) {
if (typeof value === 'string' && value.length === 1) {
properties.clientEscape = value;
} else {
throw 'RLogin: Invalid \'clientEscape\' setting ' + value;
}
}
);
var handle = new Socket();
function getServerData() {
if (handle.nread < 1) return;
var data = [];
while (handle.nread > 0) {
data.push(handle.recvBin(1));
}
if (!state.connected) {
if (data[0] === 0) {
state.connected = true;
if (data.length > 1) {
data = data.slice(1);
} else {
return;
}
} else {
self.disconnect();
}
}
// If I could tell if the TCP urgent-data pointer had been set,
// I would uncomment (and complete) this block. We'll settle
// for a partial implementation for the time being.
// We would need something to tell us if urgent data was sent,
// eg. var lookingForControlCode = urgentDataPointerIsSet();
/*
var temp = [];
for (var d = 0; d < data.length; d++) {
if (!lookingForControlCode) {
temp.push(data[d]);
continue;
}
switch (data[d]) {
case DISCARD:
temp = [];
// We found our control code
lookingForControlCode = false;
break;
case RAW:
state.cooked = false;
lookingForControlCode = false;
break;
case COOKED:
state.cooked = true;
lookingForControlCode = false;
break;
case WINDOW:
self.sendWCCS();
lookingForControlCode = false;
break;
default:
temp.push(data[d]);
break;
}
}
if (!state.suspendOutput) self.emit('data', new Buffer(temp));
*/
if (!state.suspendOutput) serverBuffer = serverBuffer.concat(data);
}
// Send a Window Change Control Sequence
// this.sendWCCS = function() {
// var magicCookie = [0xFF, 0xFF, 0x73, 0x73];
// var rcxy = new Buffer(8);
// rcxy.writeUInt16LE(properties.rows, 0);
// rcxy.writeUInt16LE(properties.columns, 2);
// rcxy.writeUInt16LE(properties.pixelsX, 4);
// rcxy.writeUInt16LE(properties.pixelsY, 6);
// if(state.connected)
// handle.write(Buffer.concat([magicCookie, rcxy]));
// }
// Send 'data' (String or Buffer) to the rlogin server
this.send = function (data) {
if (!state.connected) throw 'RLogin.send: not connected.';
if (state.suspendInput) throw 'RLogin.send: input has been suspended.';
if (typeof data === 'string') {
data = data.split('').map(function (d) { return ascii(d); });
}
var temp = [];
for (var d = 0; d < data.length; d++) {
if (state.watchForClientEscape &&
data[d] == properties.clientEscape.charCodeAt(0)
) {
state.watchForClientEscape = false;
state.clientHasEscaped = true;
continue;
}
if (state.clientHasEscaped) {
state.clientHasEscaped = false;
if (typeof clientEscapes[data[d]] !== 'undefined') {
clientEscapes[data[d]]();
}
continue;
}
if (state.cooked && (data[d] === DC1 || data[d] === DC3)) {
state.suspendOutput = (data[d] === DC3);
continue;
}
if ((d > 0 && data[d - 1] === CR && data[d] === LF) ||
data[d] == CAN
) {
state.watchForClientEscape = true;
}
temp.push(data[d]);
}
clientBuffer = clientBuffer.concat(temp);
}
this.receive = function () {
return serverBuffer.splice(0, serverBuffer.length);
}
/* If 'ch' is found in client input immediately after the
'this.clientEscape' character when:
- this is the first input after connection establishment or
- these are the first characters on a new line or
- these are the first characters after a line-cancel character
then the function 'callback' will be called. Use this to allow
client input to trigger a particular action. */
this.addClientEscape = function (ch, callback) {
if( (typeof ch !== 'string' && typeof ch !== 'number') ||
(typeof ch === 'string' && ch.length > 1) ||
typeof callback !== 'function'
) {
throw 'RLogin.addClientEscape: invalid arguments.';
}
clientEscapes[ch.charCodeAt(0)] = callback;
}
this.connect = function () {
if (typeof options.port !== 'number' ||
typeof options.host != 'string'
) {
throw 'RLogin: invalid host or port argument.';
}
if (typeof options.clientUsername !== 'string') {
throw 'RLogin: invalid clientUsername argument.';
}
if (typeof options.serverUsername !== 'string') {
throw 'RLogin: invalid serverUsername argument.';
}
if (typeof options.terminalType !== 'string') {
throw 'RLogin: invalid terminalType argument.';
}
if (typeof options.terminalSpeed !== 'number') {
throw 'RLogin: invalid terminalSpeed argument.';
}
if (handle.connect(options.host, options.port)) {
handle.sendBin(0, 1);
handle.send(options.clientUsername);
handle.sendBin(0, 1);
handle.send(options.serverUsername);
handle.sendBin(0, 1);
handle.send(options.terminalType + '/' + options.terminalSpeed);
handle.sendBin(0, 1);
while (handle.is_connected && handle.nread < 1) {
mswait(5);
}
getServerData();
} else {
throw 'RLogin: Unable to connect to server.';
}
}
this.cycle = function () {
if (!handle.is_connected) {
state.connected = false;
return;
}
getServerData();
if (state.suspendInput || clientBuffer.length < 1) return;
while (clientBuffer.length > 0) {
handle.sendBin(clientBuffer.shift(), 1);
}
}
this.disconnect = function () {
handle.close();
state.connected = false;
}
}
try {
wss = new WebSocketProxy(client);
if (typeof wss.headers['Cookie'] == 'undefined') {
err('No cookie from WebSocket client.');
}
var cookie = null;
wss.headers['Cookie'].split(';').some(
function (e) {
if (e.search(/^\s*synchronet\=/) == 0) {
cookie = e;
return true;
} else {
return false;
}
}
);
if (cookie === null) err('Invalid cookie from WebSocket client.');
cookie = cookie.replace(/^\s*synchronet\=/, '').split(',');
cookie[0] = parseInt(cookie[0]);
if (cookie.length < 2 || isNaN(cookie[0]) || cookie[0] < 1 || cookie[0] > system.lastuser) {
log('cookie ' + JSON.stringify(cookie));
err('Invalid cookie from WebSocket client.');
}
var usr = new User(cookie[0]);
var session = getSession(usr.number);
if (!session) {
err('Unable to read web session file for user #' + usr.number);
}
if (cookie[1] != session.key) {
err('Session key mismatch for user #' + usr.number);
}
if (typeof session.xtrn !== 'string' ||
typeof xtrn_area.prog[session.xtrn] === 'undefined'
) {
err('Invalid external program code.');
}
var f = new File(file_cfgname(system.ctrl_dir, 'sbbs.ini'));
if (!f.open('r')) err('Unable to open sbbs.ini.');
var ini = f.iniGetObject('BBS');
f.close();
if (typeof ini.RLoginInterface === 'undefined') {
var rlogin_addr = '127.0.0.1';
} else {
var rlogin_addr = ini.RLoginInterface.split(/,/)[0];
var ra = parseInt(rlogin_addr.replace(/[^\d]/g, ''));
if (isNaN(ra) || ra == 0) rlogin_addr = '127.0.0.1';
}
rlogin = new RLoginClient(
{ host : rlogin_addr,
port : ini.RLoginPort,
clientUsername : usr.security.password,
serverUsername : usr.alias,
terminalType : 'xtrn=' + session.xtrn,
terminalSpeed : 115200
}
);
rlogin.connect();
log(LOG_DEBUG, usr.alias + ' logged on via RLogin for ' + session.xtrn);
while (client.socket.is_connected && rlogin.connected) {
wss.cycle();
rlogin.cycle();
var send = rlogin.receive();
if (send.length > 0) wss.send(send);
while (wss.data_waiting) {
var data = wss.receiveArray();
rlogin.send(data);
}
mswait(5);
}
} catch (er) {
log(LOG_ERR, er);
} finally {
rlogin.disconnect();
client.socket.close();
}
load('sbbsdefs.js');
load('websocket-proxy.js');
load('modopts.js');
function log_err(msg) {
log(LOG_DEBUG, msg);
client.socket.close();
exit();
}
var TelnetClient = function(host, port) {
var MAX_BUFFER = 32767;
var TELNET_DATA = 0;
var TELNET_IAC = 1;
var TELNET_SUBNEGOTIATE = 2;
var TELNET_SUBNEGOTIATE_IAC = 3;
var TELNET_WILL = 4;
var TELNET_WONT = 5;
var TELNET_DO = 6;
var TELNET_DONT = 7;
var TELNET_CMD_TTYLOC = 28;
var state = TELNET_DATA;
var buffers = {
rx : [], // From server
tx : [] // To server
};
var socket = new Socket();
socket.connect(host, port);
this.__defineGetter__(
'connected',
function () {
return socket.is_connected;
}
);
this.__defineSetter__(
'connected',
function (bool) {
if (!bool && socket.is_connected) {
socket.close();
} else if (bool && !socket.is_connected) {
socket.connect(host, port);
}
}
);
this.__defineGetter__(
'data_waiting',
function () {
return (buffers.rx.length > 0);
}
);
function receive() {
var rx = [];
while (socket.data_waiting && rx.length < MAX_BUFFER) {
var nr = (socket.nread >= 4 ? 4 : (socket.nread >= 2 ? 2 : 1));
var bin = socket.recvBin(nr);
if (nr === 4) {
rx.push((bin&(255<<24))>>>24);
rx.push((bin&(255<<16))>>>16);
}
if (nr >= 2) rx.push((bin&(255<<8))>>>8);
rx.push(bin&255);
}
while (rx.length > 0) {
var b = rx.shift();
switch (state) {
case TELNET_DATA:
if (b == 0xFF) {
state = TELNET_IAC;
} else {
buffers.rx.push(b);
}
break;
case TELNET_IAC:
switch (b) {
case 0xF1: // NOP: No operation.
case 0xF2: // Data Mark: The data stream portion of a Synch. This should always be accompanied by a TCP Urgent notification.
case 0xF3: // Break: NVT character BRK.
case 0xF4: // Interrupt Process: The function IP.
case 0xF5: // Abort output: The function AO.
case 0xF6: // Are You There: The function AYT.
case 0xF7: // Erase character: The function EC.
case 0xF8: // Erase Line: The function EL.
case 0xF9: // Go ahead: The GA signal
// Ignore these single byte commands
state = TELNET_DATA;
break;
case 0xFA: // Subnegotiation
state = TELNET_SUBNEGOTIATE;
break;
case 0xFB: // Will
state = TELNET_WILL;
break;
case 0xFC: // Wont
state = TELNET_WONT;
break;
case 0xFD: // Do
state = TELNET_DO;
break;
case 0xFE: // Dont
state = TELNET_DONT;
break;
case 0xFF:
buffers.rx.push(0xFF);
state = TELNET_DATA;
break;
}
break;
case TELNET_SUBNEGOTIATE:
if (b == 0xFF) state = TELNET_SUBNEGOTIATE_IAC;
break;
case TELNET_SUBNEGOTIATE_IAC:
if (b == 0xFF) {
state = TELNET_SUBNEGOTIATE;
} else if (b == 0xF0) {
state = TELNET_DATA;
} else {
// Unexpected
state = TELNET_DATA;
}
break;
case TELNET_DO:
switch (b) {
// This will bork with IPV6
case TELNET_CMD_TTYLOC:
socket.sendBin(255, 1);
socket.sendBin(250, 1);
socket.sendBin(28, 1);
socket.sendBin(0, 1);
client.ip_address.split('.').forEach(
function (e) {
e = parseInt(e);
socket.sendBin(e, 1);
if (e === 255) socket.sendBin(e, 1);
}
);
client.ip_address.split('.').forEach(
function (e) {
e = parseInt(e);
socket.sendBin(e, 1);
if (e === 255) socket.sendBin(e, 1);
}
);
socket.sendBin(255, 1);
socket.sendBin(240, 1);
break;
default:
break;
}
state = TELNET_DATA;
break;
case TELNET_WILL:
case TELNET_WONT:
case TELNET_DONT:
state = TELNET_DATA;
break;
default:
break;
}
}
}
function transmit() {
if (!socket.is_connected || buffers.tx.length < 1) return;
while (buffers.tx.length >= 4) {
var chunk = (buffers.tx.shift()<<24);
chunk |= (buffers.tx.shift()<<16);
chunk |= (buffers.tx.shift()<<8);
chunk |= buffers.tx.shift();
socket.sendBin(chunk, 4);
}
if (buffers.tx.length >= 2) {
var chunk = (buffers.tx.shift()<<8);
chunk |= buffers.tx.shift();
socket.sendBin(chunk, 2);
}
if (buffers.tx.length > 0) {
socket.sendBin(buffers.tx.shift(), 1);
}
}
this.receive = function () {
return buffers.rx.splice(0, buffers.rx.length);
}
this.send = function (arr) {
if (typeof arr === 'string') {
var arr = arr.map(
function (e) {
return ascii(e);
}
);
}
buffers.tx = buffers.tx.concat(arr);
}
this.cycle = function () {
receive();
transmit();
}
}
try {
var f = new File(file_cfgname(system.ctrl_dir, 'sbbs.ini'));
if (!f.open('r')) log_err('Unable to open sbbs.ini.');
var ini = f.iniGetObject('BBS');
f.close();
if (typeof ini.TelnetInterface === 'undefined') {
var telnet_addr = '127.0.0.1';
} else {
var telnet_addr = ini.TelnetInterface.split(/,/)[0];
var ta = parseInt(telnet_addr.replace(/[^\d]/g, '') == 0);
if (isNaN(ta) || ta == 0) telnet_addr = '127.0.0.1';
}
var wss = new WebSocketProxy(client);
var wsspath = wss.headers.Path.split('/');
if (wsspath.length < 3 || isNaN(parseInt(wsspath[2]))) {
var telnet = new TelnetClient(telnet_addr, ini.TelnetPort);
} else {
var _settings = get_mod_options('web');
if (typeof _settings.allowed_ftelnet_targets !== 'string') {
throw 'Client supplied Path but no allowed_ftelnet_targets supplied in modopts.ini [web] section.';
}
var targets = _settings.allowed_ftelnet_targets.split(',');
if (!targets.some(function (e) { var target = e.split(':'); return target[0] === wsspath[1] && target[1] === wsspath[2]; })) {
throw 'Client supplied Path is not in allowed_ftelnet_targets list.';
}
log('Using client-supplied target ' + wsspath[1] + ':' + wsspath[2]);
var telnet = new TelnetClient(wsspath[1], parseInt(wsspath[2]));
}
while (client.socket.is_connected && telnet.connected) {
wss.cycle();
telnet.cycle();
if (telnet.data_waiting) {
var data = telnet.receive();
wss.send(data);
}
while (wss.data_waiting) {
var data = wss.receiveArray();
telnet.send(data);
}
mswait(5);
}
} catch (err) {
log(LOG_ERR, 'Caught: ' + err);
} finally {
client.socket.close();
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment