Project

General

Profile

« Previous | Next » 

Revision 479f1136

Added by Hamish Coleman almost 16 years ago

  • ID 479f1136962382016f7ad250979852a0c735f7ea

Make telnet option processing cross packet boundaries. Implement binary mode.
These two changes allow xmodem uploading from client to serial port.

View differences:

wconsd.c
#include <stdio.h>
#include <stdlib.h>
#define VERSION "0.2.5"
#define VERSION "0.2.6"
/* Size of buffers for send and receive */
#define BUFSIZE 1024
......
int debug_mode = 0;
/* these match the official telnet codes */
#define TELNET_OPTION_SB 0xfa
#define TELNET_OPTION_WILL 0xfb
#define TELNET_OPTION_WONT 0xfc
#define TELNET_OPTION_DO 0xfd
#define TELNET_OPTION_DONT 0xfe
#define TELNET_OPTION_IAC 0xff
/* these are my local state-tracking codes */
#define TELNET_OPTION_SBXX 0xfa00 /* received IAC SB xx */
#define MAXCONNECTIONS 8
int next_connection_id = 1; /* lifetime unique connection id */
......
HANDLE serial;
HANDLE serialThread;
int option_runmenu; /* are we at the menu? */
int option_echo; /* will we echo chars recieved? */
int option_binary; /* binary transmission requested */
int option_echo; /* will we echo chars received? */
int option_keepalive; /* will we send IAC NOPs all the time? */
int net_bytes_rx;
int net_bytes_tx;
struct sockaddr *sa;
int telnet_option; /* Set to indicate option processing status */
int telnet_option_param;/* saved parameters from telnet options */
};
struct connection connection[MAXCONNECTIONS];
......
}
/*
* given a buffer starting with 0xff, process the telnet options and return
* the number of bytes to skip
* telnet option receiver state machine.
* Called with the current char, it saves the intermediate state in the
* conn struct.
*
* Returns 0 to indicate no echo of this char, or 0xff to indicate that the
* char should be echoed.
*/
int process_telnet_option(struct connection*conn, unsigned char *buf) {
switch (buf[1]) {
case 0xf0: /* suboption end */
case 241: /* NOP */
case 242: /* Data Mark */
dprintf(2,"wconsd[%i]: option IAC %i\n",conn->id,buf[1]);
return 2;
case 243: /* Break */
if (conn->serialconnected) {
Sleep(1000);
SetCommBreak(conn->serial);
Sleep(1000);
ClearCommBreak(conn->serial);
unsigned char process_telnet_option(struct connection*conn, unsigned char ch) {
dprintf(2,"wconsd[%i]: debug option (0x%02x) 0x%02x\n",conn->id,conn->telnet_option,ch);
switch (conn->telnet_option) {
case 0: /* received nothing */
if (ch==TELNET_OPTION_IAC) {
conn->telnet_option=ch;
return 0; /* dont echo */
}
return 2;
case 244: /* Interrupt */
conn->option_runmenu=1;
return 2;
case 245: /* abort output */
dprintf(1,"wconsd[%i]: option IAC %i\n",conn->id,buf[1]);
return 2;
case 246: /* are you there */
dprintf(1,"wconsd[%i]: option IAC AYT\n",conn->id);
netprintf(conn,"yes\r\n");
return 2;
case 247: /* erase character */
case 248: /* erase line */
case 249: /* go ahead */
dprintf(1,"wconsd[%i]: option IAC %i\n",conn->id,buf[1]);
return 2;
case 0xfa: /* suboption begin */
if (buf[3]==0) {
/*
* I dont expect us to get any IS statements ...
* and if we do, I'm going to need to rewrite the
* way chars are absorbed
*/
dprintf(1,"wconsd[%i]: option IAC SB %i IS\n",conn->id,buf[2]);
return 4;
} else if (buf[3]>1) {
dprintf(1,"wconsd[%i]: option IAC SB %i error\n",conn->id,buf[2]);
return 4;
}
dprintf(1,"wconsd[%i]: option IAC SB %i SEND\n",conn->id,buf[2]);
switch (buf[2]) {
case 5: /* STATUS */
netprintf(conn,"%s%c%s%s%s",
"\xff\xfa\x05",
0,
"\xfb\x05",
conn->option_echo?"\xfb\x01":"",
"\xff\xf0");
return 0xff; /* ECHO */
case TELNET_OPTION_IAC: /* recived IAC */
switch(ch) {
case 0xf0: /* suboption end */
case 0xf1: /* NOP */
case 0xf2: /* Data Mark */
case 0xf5: /* abort output */
case 0xf7: /* erase character */
case 0xf8: /* erase line */
case 0xf9: /* go ahead */
dprintf(1,"wconsd[%i]: option IAC %i\n",conn->id,ch);
conn->telnet_option=0;
return 0; /* dont echo */
case 0xf3: /* Break */
if (conn->serialconnected) {
dprintf(2,"wconsd[%i]: send break\n",conn->id);
Sleep(1000);
SetCommBreak(conn->serial);
Sleep(1000);
ClearCommBreak(conn->serial);
}
conn->telnet_option=0;
return 0; /* dont echo */
case 0xf4: /* Interrupt */
conn->option_runmenu=1;
conn->telnet_option=0;
return 0; /* dont echo */
case 0xf6: /* are you there */
dprintf(1,"wconsd[%i]: option IAC AYT\n",conn->id);
netprintf(conn,"yes\r\n");
conn->telnet_option=0;
return 0; /* dont echo */
case TELNET_OPTION_SB:
case TELNET_OPTION_WILL:
case TELNET_OPTION_WONT:
case TELNET_OPTION_DO:
case TELNET_OPTION_DONT:
conn->telnet_option=ch;
return 0; /* dont echo */
case 0xff: /* send ff */
conn->telnet_option=0;
return ch; /* ECHO */
default:
dprintf(1,"wconsd[%i]: option IAC %i invalid\n",conn->id,ch);
conn->telnet_option=0;
return 0; /* dont echo */
}
return 6;
case 0xfb: /* WILL */
dprintf(2,"wconsd[%i]: option IAC WILL %i\n",conn->id,buf[2]);
return 3;
case 0xfc: /* WONT */
dprintf(1,"wconsd[%i]: option IAC WONT %i\n",conn->id,buf[2]);
return 3;
case 0xfd: /* DO */
switch (buf[2]) {
case TELNET_OPTION_SB: /* received IAC SB 0xfa */
conn->telnet_option=TELNET_OPTION_SBXX;
conn->telnet_option_param=ch;
return 0; /* dont echo */
case TELNET_OPTION_WILL: /* received IAC WILL 0xfb */
dprintf(2,"wconsd[%i]: option IAC WILL %i\n",conn->id,ch);
conn->telnet_option=0;
return 0; /* dont echo */
case TELNET_OPTION_WONT: /* received IAC WONT 0xfc */
dprintf(1,"wconsd[%i]: option IAC WONT %i\n",conn->id,ch);
conn->telnet_option=0;
return 0; /* dont echo */
case TELNET_OPTION_DO: /* received IAC DO 0xfd */
switch (ch) {
case 0x00: /* Binary */
dprintf(2,"wconsd[%i]: DO Binary\n",conn->id);
conn->option_binary=1;
case 0x01: /* ECHO */
dprintf(2,"wconsd[%i]: DO ECHO\n",conn->id);
conn->option_echo=1;
break;
case 0x03: /* suppress go ahead */
dprintf(2,"wconsd[%i]: DO suppress go ahead\n",conn->id);
default:
dprintf(2,"wconsd[%i]: option IAC DO %i\n",conn->id,ch);
break;
}
return 3;
case 0xfe: /* DONT */
switch (buf[2]) {
conn->telnet_option=0;
return 0; /* dont echo */
case TELNET_OPTION_DONT: /* received IAC DONT 0xfe */
switch (ch) {
case 0x00: /* Binary */
dprintf(2,"wconsd[%i]: DONT Binary\n",conn->id);
conn->option_binary=0;
case 0x01: /* ECHO */
dprintf(1,"wconsd[%i]: DONT ECHO\n",conn->id);
conn->option_echo=0;
break;
default:
dprintf(2,"wconsd[%i]: option IAC DONT %i\n",conn->id,buf[2]);
dprintf(2,"wconsd[%i]: option IAC DONT %i\n",conn->id,ch);
break;
}
return 3;
case 0xff: /* send ff */
return 1;
conn->telnet_option=0;
return 0; /* dont echo */
default:
dprintf(1,"wconsd[%i]: option IAC %i %i\n",conn->id,buf[1],buf[2]);
if (conn->telnet_option==TELNET_OPTION_SBXX) {
/* received IAC SB x */
int option = conn->telnet_option_param;
if (ch==0) {
/* IS */
dprintf(1,"wconsd[%i]: option IAC SB %i IS\n",
conn->id,option);
/*
* TODO - stay in telnet option mode while
* absorbing the IS buffer
*/
conn->telnet_option=0;
return 0; /* dont echo */
} else if (ch>1) {
/* error ? */
dprintf(1,"wconsd[%i]: option IAC SB %i %i\n",
conn->id,option,ch);
conn->telnet_option=0;
return 0; /* dont echo */
}
/* SEND */
if (option == 5) {
dprintf(1,"wconsd[%i]: option IAC SB 5 SEND\n",conn->id);
/* FIXME - add option_binary */
netprintf(conn,"%s%c%s%s%s",
"\xff\xfa\x05",
0,
"\xfb\x05",
conn->option_echo?"\xfb\x01":"",
"\xff\xf0");
conn->telnet_option=0;
return 0; /* dont echo */
} else {
/* error ? */
dprintf(1,"wconsd[%i]: option IAC SB %i %i\n",
conn->id,option,ch);
conn->telnet_option=0;
return 0; /* dont echo */
}
}
dprintf(1,"wconsd[%i]: invalid conn->telnet_option==%i \n",conn->id,conn->telnet_option);
}
/* not normally reached */
......
closesocket(conn->net);
conn->net=INVALID_SOCKET;
conn->netconnected=0;
return;
return 0;
default:
dprintf(1,"wconsd[%i]: net_to_com socket error (%i)\n",conn->id,err);
/* General paranoia about blocking sockets */
......
* Scan for telnet options and process then remove them
* This loop is reasonably fast if there are no options,
* but recursively slow if there are options
*
* TODO - if an option is mid packet, this could change
* the semantics of processing at the wrong point
*/
pbuf=buf;
bytes_to_scan=size;
while ((pbuf=memchr(pbuf,0xff,bytes_to_scan))!=NULL) {
int skip = process_telnet_option(conn, pbuf);
bytes_to_scan = size-(pbuf-buf)+skip;
size -= skip;
memmove(pbuf,pbuf+skip,bytes_to_scan);
/*
* hack to skip quoted quotes
*/
if (skip==1) {
pbuf++;
while(bytes_to_scan--) {
/* TODO - use ->telnet_option || 0xff to chose to call process_telnet_option */
if(!process_telnet_option(conn,*pbuf)) {
/* remove this byte from the buffer */
memmove(pbuf,pbuf+1,bytes_to_scan);
size--;
continue;
}
pbuf++;
}
/*
* Scan for CR NUL sequences and uncook them
* it also appears that I need to uncook CR LF sequences
* TODO - implement a "cooked" mode to bypass all this
* mangling
*/
pbuf=buf;
bytes_to_scan=size;
while ((pbuf=memchr(pbuf,0x0d,bytes_to_scan))!=NULL) {
pbuf++;
if (*pbuf!=0x00&&*pbuf!=0x0a) {
continue;
}
if (!conn->option_binary) {
pbuf=buf;
bytes_to_scan=size;
while ((pbuf=memchr(pbuf,0x0d,bytes_to_scan))!=NULL) {
pbuf++;
if (*pbuf!=0x00&&*pbuf!=0x0a) {
continue;
}
bytes_to_scan = size-(pbuf-buf)+1;
size -= 1;
memmove(pbuf,pbuf+1,bytes_to_scan);
bytes_to_scan = size-(pbuf-buf)+1;
size -= 1;
memmove(pbuf,pbuf+1,bytes_to_scan);
}
/* TODO - emulate cisco's ctrl-^,x sequence for exit to menu */
}
/* TODO - emulate ciscos ctrl-^,x sequence for exit to menu */
if (conn->option_runmenu) {
/* TODO - if we are in menu mode, hook into the menu here */
......
"\r\n"
"available commands:\r\n"
"\r\n"
"binary - toggle the binary comms mode\r\n"
"close - Stop serial communications\r\n"
"copyright - Print the copyright notice\r\n"
"data - Set number of data bits\r\n"
"help - This guff\r\n"
"kill_conn - Stop a given connection's serial communications\r\n"
"keepalive - toggle the generation of keepalive packets\r\n"
"open - Connect or resume communications with a serial port\r\n"
"parity - Set the serial parity\r\n"
"port - Set serial port number\r\n"
......
netprintf(conn, " state=closed\r\n\n");
}
netprintf(conn," connectionid=%i hostname=%s\r\n",conn->id,hostname);
netprintf(conn," echo=%i keepalive=%i\r\n",conn->option_echo,conn->option_keepalive);
netprintf(conn," echo=%i binary=%i keepalive=%i\r\n",
conn->option_echo,conn->option_binary,conn->option_keepalive);
netprintf(conn,"\r\n");
}
......
} else if (!strcmp(command, "keepalive")) {
conn->option_keepalive=!conn->option_keepalive;
return;
} else if (!strcmp(command, "binary")) {
conn->option_binary=!conn->option_binary;
return;
} else if (!strcmp(command, "show_conn_table")) {
int i;
netprintf(conn,
"Flags: A - Active Slot, N - Network active, S - Serial active,\r\n"
" M - Run Menu, E - Echo enabled, K - Telnet Keepalives,\r\n"
" * - This connection\r\n"
" M - Run Menu, B - Binary transmission, E - Echo enabled,\r\n"
" K - Telnet Keepalives, * - This connection\r\n"
"\r\n");
netprintf(conn,
"s flags id mThr net netTh serial serialTh netrx nettx peer address\r\n");
"s flags id mThr net netTh serial serialTh netrx nettx peer address\r\n");
netprintf(conn,
"- ------ -- ---- ---- ----- ------ -------- ----- ----- ------------\r\n");
"- ------- -- ---- ---- ----- ------ -------- ----- ----- ------------\r\n");
for (i=0;i<MAXCONNECTIONS;i++) {
netprintf(conn,"%i%c%c%c%c%c%c%c %2i %4i ",
netprintf(conn,"%i%c%c%c%c%c%c%c%c %2i %4i ",
i,
&connection[i]==conn?'*':' ',
connection[i].active?'A':' ',
connection[i].netconnected?'N':' ',
connection[i].serialconnected?'S':' ',
connection[i].option_runmenu?'M':' ',
connection[i].option_binary?'B':' ',
connection[i].option_echo?'E':' ',
connection[i].option_keepalive?'K':' ',
connection[i].id,
......
fd_set set_read;
struct timeval tv;
unsigned char last_ch;
unsigned char ch;
/* IAC WILL ECHO */
/* IAC WILL suppress go ahead */
/* IAC WILL status */
......
conn->net_bytes_rx+=size;
for (i = 0; i < size; i++) {
if (buf[i]==0) {
// strange, I'm not expecting nul bytes !
last_ch=ch;
ch = buf[i];
/* TODO - remove the second call to process_telnet_option */
if (conn->telnet_option) {
/* We are in the middle of handling an option */
process_telnet_option(conn,ch);
/*
* return value ignored, since we dont care to
* print any telnet option values in the menu.
*/
continue;
} else if (buf[i] == 127 || buf[i]==8) {
}
if (ch==0) {
/*
* NULLs could occur as the second char in a
* CR NUL telnet sequence
*/
continue;
} else if (ch==127 || ch==8) {
// backspace
if (linelen > 0) {
netprintf(conn,"\x08 \x08");
......
netprintf(conn,"\x07");
}
continue;
} else if (buf[i] == 0x0d || buf[i]==0x0a) {
} else if (ch==0x0d || ch==0x0a) {
// detected cr or lf
if (i+1<size && (buf[i+1]==0x0a || buf[i+1]==0)) {
i++;
if (last_ch == 0x0d && ch==0x0a) {
/* skip the second char in CR LF */
continue;
}
if (conn->option_echo)
......
show_prompt(conn);
linelen=0;
continue;
} else if (buf[i] <0x20) {
} else if (ch<0x20) {
/* ignore other ctrl chars */
continue;
} else if (buf[i]==0xff) {
// telnet option packet
} else if (ch==TELNET_OPTION_IAC) {
/* start a telnet option packet */
process_telnet_option(conn,ch);
/*
* some magic to ignore quoted quotes
* this will go away when we use net_to_com
* to give us our data buffer
* no possible return values, since IAC is
* just the beginning of a telnet packet
*/
if (buf[i+1]==0xff) {
i++;
continue;
}
/*
* Note that we avoid accessing outside the buffer
* by ensuring that the buffer is larger than
* the largest recv
*
* this doesnt help us if there is a split buffer from
* the client
*/
/*
* our for loop is already performing one increment,
* so subtract one from the returned value
*/
i+=process_telnet_option(conn,&buf[i])-1;
continue;
} else {
// other chars
if (linelen < MAXLEN - 1) {
line[linelen] = buf[i];
line[linelen] = ch;
linelen++;
if (conn->option_echo) {
netprintf(conn,"%c",buf[i]); /* echo */
netprintf(conn,"%c",ch); /* echo */
}
} else {
netprintf(conn,"\x07"); /* linebuf full bell */
......
connection[i].serial=INVALID_HANDLE_VALUE;
connection[i].serialThread=NULL;
connection[i].option_runmenu=1; /* start in the menu */
connection[i].option_binary=0;
connection[i].option_echo=0;
connection[i].option_keepalive=0;
connection[i].net_bytes_rx=0;
connection[i].net_bytes_tx=0;
connection[i].telnet_option=0;
connection[i].telnet_option_param=0;
if (connection[i].sa) {
/* Do lazy de-allocation so that the info is
......
return 0;
} else if (strcmp(argv[1],"-p")==0) {
console_application=1;
default_tcpport = atoi(argv[2]);
if (argc>2) {
default_tcpport = atoi(argv[2]);
}
} else if (strcmp(argv[1],"-d")==0) {
console_application=1;
if (argc>2) {
dprintf_level = atoi(argv[2]);
}
} else {
usage();
return 1;

Also available in: Unified diff