// Synchronet Service for redirecting incoming WebSocket connections to the Telnet server
// Synchronet Service for redirecting incoming WebSocket connections to the Telnet and/or RLogin server
// Intended to be used by SysOps who want to provide web based access to their BBS with an HTML5 client, such as HtmlTerm
// Mainly to be used in conjunction with fTelnet
// HtmlTerm can be found at http://www.ftelnet.ca
// Example configuration (in ctrl/services.ini)
// Example configuration (in ctrl/services.ini)
//
//
// ; WebSocket Service (used with HtmlTerm)
// ; WebSocket to RLogin
// [WebSocket]
// [WebSocket-RLogin]
// Port=11513
// Options=NO_HOST_LOOKUP
// Command=websocketservice.js localhost 513
//
// ; WebSocket to Telnet
// [WebSocket-Telnet]
// Port=1123
// Port=1123
// Options=NO_HOST_LOOKUP
// Options=NO_HOST_LOOKUP
// Command=websocketservice.js
// Command=websocketservice.js localhost 23
// Developer notes:
// - Telnet negotiation commands are filtered by this service, and are not passed on to the websocket client.
// (They are just ignored, and not answered, which seems to be fine with the Synchronet telnet server)
// (When file xfer support is added to HtmlTerm, the negotiation may need to be implemented (to enable binary mode for example))
//include definitions for synchronet
//include definitions for synchronet
load("nodedefs.js");
load("nodedefs.js");
...
@@ -27,11 +27,6 @@ load("ftelnethelper.js");
...
@@ -27,11 +27,6 @@ load("ftelnethelper.js");
load("sha1.js");
load("sha1.js");
// Global constants
// Global constants
constTELNET_DATA=0;
constTELNET_IAC=1;
constTELNET_SUBNEGOTIATE=2;
constTELNET_SUBNEGOTIATE_IAC=3;
constTELNET_WWDD=4;
constWEBSOCKET_NEED_PACKET_START=0;
constWEBSOCKET_NEED_PACKET_START=0;
constWEBSOCKET_NEED_PAYLOAD_LENGTH=1;
constWEBSOCKET_NEED_PAYLOAD_LENGTH=1;
constWEBSOCKET_NEED_MASKING_KEY=2;
constWEBSOCKET_NEED_MASKING_KEY=2;
...
@@ -43,7 +38,6 @@ var FFrameOpCode = 0;
...
@@ -43,7 +38,6 @@ var FFrameOpCode = 0;
varFFramePayloadLength=0;
varFFramePayloadLength=0;
varFFramePayloadReceived=0;
varFFramePayloadReceived=0;
varFServerSocket=null;
varFServerSocket=null;
varFTelnetState=TELNET_DATA;
varFWebSocketHeader=[];
varFWebSocketHeader=[];
varFWebSocketState=WEBSOCKET_NEED_PACKET_START;
varFWebSocketState=WEBSOCKET_NEED_PACKET_START;
...
@@ -51,11 +45,21 @@ var FWebSocketState = WEBSOCKET_NEED_PACKET_START;
...
@@ -51,11 +45,21 @@ var FWebSocketState = WEBSOCKET_NEED_PACKET_START;
try{
try{
// Parse and respond to the WebSocket handshake request
// Parse and respond to the WebSocket handshake request
if (ShakeHands()){
if (ShakeHands()){
SendToWebSocketClient(StringToBytes("Re-directing to telnet server...\r\n"));
SendToWebSocketClient(StringToBytes("Redirecting to server...\r\n"));
// Connect to the local synchronet server
// Determine where to connect
varTargetHostname=system.local_host_name;
varTargetPort=GetTelnetPort();
if (argc===1){
TargetPort=parseInt(argv[0]);
}elseif (argc===2){
TargetHostname=argv[0];
TargetPort=parseInt(argv[1]);
}
// Connect to the server
FServerSocket=newSocket();
FServerSocket=newSocket();
if (FServerSocket.connect(system.local_host_name,GetTelnetPort())){
if (FServerSocket.connect(TargetHostname,TargetPort)){
// Variables we'll use in the loop
// Variables we'll use in the loop
varDoYield=true;
varDoYield=true;
varClientData=[];
varClientData=[];
...
@@ -69,12 +73,12 @@ try {
...
@@ -69,12 +73,12 @@ try {
// Check if the client sent anything
// Check if the client sent anything
ClientData=GetFromWebSocketClient();
ClientData=GetFromWebSocketClient();
if (ClientData.length>0){
if (ClientData.length>0){
SendToTelnetServer(ClientData);
SendToServer(ClientData);
DoYield=false;
DoYield=false;
}
}
// Check if the server sent anything
// Check if the server sent anything
ServerData=GetFromTelnetServer();
ServerData=GetFromServer();
if (ServerData.length>0){
if (ServerData.length>0){
SendToWebSocketClient(ServerData);
SendToWebSocketClient(ServerData);
DoYield=false;
DoYield=false;
...
@@ -90,8 +94,8 @@ try {
...
@@ -90,8 +94,8 @@ try {
if (!FServerSocket.is_connected)log(LOG_DEBUG,'Server socket no longer connected');
if (!FServerSocket.is_connected)log(LOG_DEBUG,'Server socket no longer connected');
}else{
}else{
// FServerSocket.connect() failed
// FServerSocket.connect() failed
log(LOG_ERR,"Unable to connect to telnet server");
log(LOG_ERR,"Unable to connect to server");
SendToWebSocketClient(StringToBytes("ERROR: Unable to connect to telnet server\r\n"));
SendToWebSocketClient(StringToBytes("ERROR: Unable to connect to server\r\n"));
mswait(2500);
mswait(2500);
}
}
}else{
}else{
...
@@ -121,78 +125,13 @@ function CalculateWebSocketKey(InLine) {
...
@@ -121,78 +125,13 @@ function CalculateWebSocketKey(InLine) {
returnDigits/Spaces;
returnDigits/Spaces;
}
}
functionGetFromTelnetServer(){
functionGetFromServer(){
varResult=[];
varResult=[];
varInByte=0;
varInByte=0;
while (FServerSocket.data_waiting&&(Result.length<=4096)){
while (FServerSocket.data_waiting&&(Result.length<=4096)){
InByte=FServerSocket.recvBin(1);
InByte=FServerSocket.recvBin(1);
Result.push(InByte);
switch (FTelnetState){
caseTELNET_DATA:
// We're receiving data, so check for 0xFF, which indicates we may be receiving a telnet negotiation sequence
if (InByte==0xFF){
FTelnetState=TELNET_IAC;
}else{
Result.push(InByte);
}
break;
caseTELNET_IAC:
// We're starting a telnet negotiation sequence, see what type
switch (InByte){
case0xF1:// NOP: No operation.
case0xF2:// Data Mark: The data stream portion of a Synch. This should always be accompanied by a TCP Urgent notification.
case0xF3:// Break: NVT character BRK.
case0xF4:// Interrupt Process: The function IP.
case0xF5:// Abort output: The function AO.
case0xF6:// Are You There: The function AYT.
case0xF7:// Erase character: The function EC.
case0xF8:// Erase Line: The function EL.
case0xF9:// Go ahead: The GA signal
// Ignore these single byte commands
FTelnetState=TELNET_DATA;
break;
case0xFA:// Subnegotiation
FTelnetState=TELNET_SUBNEGOTIATE;
break;
case0xFB:// Will
case0xFC:// Wont
case0xFD:// Do
case0xFE:// Dont
FTelnetState=TELNET_WWDD;
break;
case0xFF:// 0xFF's get doubled, so this is really a data byte
Result.push(0xFF);
FTelnetState=TELNET_DATA;
break;
}
break;
caseTELNET_SUBNEGOTIATE:
// We're receiving subnegotiation data, so check for 0xFF, which indicates we may be done subnegotiating
if (InByte==0xFF){
FTelnetState=TELNET_SUBNEGOTIATE_IAC;
}else{
// Just subnegotiation data that we're going to ignore
}
break;
caseTELNET_SUBNEGOTIATE_IAC:
// We're subnegotiating and expecting to receive the end sequence character here
if (InByte==0xFF){
// 0xFF's get doubled, so this is really a subnegotiation data byte
FTelnetState=TELNET_SUBNEGOTIATE;
}elseif (InByte==0xF0){
// We're finished subnegotiation
FTelnetState=TELNET_DATA;
}else{
// This was unexpected!
FTelnetState=TELNET_DATA;
}
break;
caseTELNET_WWDD:
// Ignore this command
FTelnetState=TELNET_DATA;
break;
}
}
}
returnResult;
returnResult;
...
@@ -283,7 +222,7 @@ function GetFromWebSocketClientVersion7() {
...
@@ -283,7 +222,7 @@ function GetFromWebSocketClientVersion7() {
FFrameMask[1]=(InByte&0x00FF0000)>>16;
FFrameMask[1]=(InByte&0x00FF0000)>>16;
FFrameMask[2]=(InByte&0x0000FF00)>>8;
FFrameMask[2]=(InByte&0x0000FF00)>>8;
FFrameMask[3]=InByte&0x000000FF;
FFrameMask[3]=InByte&0x000000FF;
FWebSocketState=WEBSOCKET_DATA;
FWebSocketState=(FFramePayloadLength>0?WEBSOCKET_DATA:WEBSOCKET_NEED_PACKET_START);// NB: Might not be any data to read, so check for payload length before setting state