home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Columbia Kermit
/
kermit.zip
/
archives
/
mskermit.tar.gz
/
mskermit.tar
/
msntnd.c
< prev
next >
Wrap
C/C++ Source or Header
|
1998-05-28
|
35KB
|
1,243 lines
/* File MSNTND.C
* Telnet driver
*
* Copyright (C) 1982, 1997, Trustees of Columbia University in the
* City of New York. The MS-DOS Kermit software may not be, in whole
* or in part, licensed or sold for profit as a software product itself,
* nor may it be included in or distributed with commercial products
* or otherwise distributed by commercial concerns to their clients
* or customers without written permission of the Office of Kermit
* Development and Distribution, Columbia University. This copyright
* notice must not be removed, altered, or obscured.
*
* Written for MS-DOS Kermit by Joe R. Doupnik,
* Utah State University, jrd@cc.usu.edu, jrd@usu.Bitnet,
* and by Frank da Cruz, Columbia Univ., fdc@watsun.cc.columbia.edu.
* With earlier contributions by Erick Engelke of the University of
* Waterloo, Waterloo, Ontario, Canada.
*
* Last edit
* 12 Jan 1995 v3.14
*/
#include "msntcp.h"
#include "msnlib.h"
#define MAXSESSIONS 6
#define MSGBUFLEN 512
/* TCP/IP Telnet negotiation support code */
#define IAC 255
#define DONT 254
#define DO 253
#define WONT 252
#define WILL 251
#define SB 250
#define AYT 246
#define BREAK 243
#define SE 240
#define EOR 239
#define TELOPT_BINARY 0
#define TELOPT_ECHO 1
#define TELOPT_SGA 3
#define TELOPT_STATUS 5
#define TELOPT_TTYPE 24
#define TELOPT_EOR 25
#define TELOPT_NAWS 31
#define BIN_REJECT 0 /* Binary mode, decline option */
#define BIN_REQUEST 1 /* Binary mode, option has been requested */
#define BIN_RESPONSE 2 /* Binary mode, option has been answered */
#define BAPICON 0xa0 /* 3Com BAPI, connect to port */
#define BAPIDISC 0xa1 /* 3Com BAPI, disconnect */
#define BAPIWRITE 0xa4 /* 3Com BAPI, write block */
#define BAPIREAD 0xa5 /* 3Com BAPI, read block */
#define BAPIBRK 0xa6 /* 3Com BAPI, send short break */
#define BAPISTAT 0xa7 /* 3Com BAPI, read status (# chars avail) */
#define BAPIHERE 0xaf /* 3Com BAPI, presence check */
#define BAPIEECM 0xb0 /* 3Com BAPI, enable/disable ECM char */
#define BAPIECM 0xb1 /* 3Com BAPI, trap Enter Command Mode char */
#define BAPIPING 0xb2 /* Send Ping, Kermit extension of BAPI */
#define BAPITO_3270 0xb3 /* Write to 3270, Kermit extension of BAPI */
#define BAPINAWS 0xb4 /* Send NAWS update, Kermit extension */
#define BAPISTAT_SUC 0 /* function successful */
#define BAPISTAT_NCW 1 /* no character written */
#define BAPISTAT_NCR 2 /* no character read */
#define BAPISTAT_NSS 3 /* no such session */
#define BAPISTAT_NOS 7 /* session aborted */
#define BAPISTAT_NSF 9 /* no such function */
#define SUCCESS 0 /* tcp_status for main body of Kermit */
#define NO_DRIVER 1
#define NO_LOCAL_ADDRESS 2
#define BOOTP_FAILED 3
#define RARP_FAILED 4
#define BAD_SUBNET_MASK 5
#define SESSIONS_EXCEEDED 6
#define HOST_UNKNOWN 7
#define HOST_UNREACHABLE 8
#define CONNECTION_REJECTED 9
#define TSBUFSIZ 41
static byte sb[TSBUFSIZ]; /* Buffer for subnegotiations */
static byte *termtype; /* Telnet, used in negotiations */
/*static int sgaflg = 0; /* Telnet SGA flag */
int sgaflg = 0; /* Telnet SGA flag */
static int dosga = 0; /* Telnet 1 if I sent DO SGA from tn_ini() */
static int wttflg = 0; /* Telnet Will Termtype flag */
static int doEOR = 0; /* Telnet EOR done if not zero */
byte echo = 1; /* Telnet echo, default on, but hate it */
byte bootmethod = BOOT_FIXED; /* Boot method (fixed, bootp, rarp) */
struct {
word ident;
char *name;
} termarray[]= /* terminal type names, from mssdef.h */
{
{0,"UNKNOWN"}, {1,"H-19"}, {2,"VT52"}, {4,"VT100"},
{8,"VT102"}, {0x10,"VT220"}, {0x20,"VT320"}, {0x40,"TEK4014"},
{0x80,"VIP7809"}, {0x100, "PT200"}, {0x200, "D463"},
{0x400, "D470"}, {0x800, "wyse50"},
{0x2000, "ANSI"}, {0xff,"UNKNOWN"}
};
extern byte FAR * bapiadr; /* Far address of local Telnet client's buf */
extern int bapireq, bapiret; /* count of chars in/out */
extern longword my_ip_addr, sin_mask; /* binary of our IP, netmask */
extern longword ipbcast; /* binary IP of broadcast */
extern byte * def_domain; /* default domain string */
extern byte * hostname; /* our name from BootP server */
static longword host; /* binary IP of host */
byte tempip[18] = {0}; /* for IP string from NET.CFG, count, string*/
byte dobinary = 0; /* local NVT-ASCII (0) or Binary (1) mode */
byte inbinary = 0; /* incoming Binary, Option status */
byte outbinary = 0; /* outgoing Binary, Option status */
byte do_greeting; /* non-zero to show Telnet server herald */
int doslevel = 1; /* operating at DOS level if != 0 */
static tcp_Socket *s; /* ptr to active socket */
int msgcnt = 0; /* count of chars in message buffer */
byte msgbuf[MSGBUFLEN+1] = {0}; /* message buffer for client */
extern int hookvect(void);
extern int unhookvect(void);
extern void kecho(byte);
extern void kmode(byte);
extern void readback(void);
extern void get_kscreen(void);
int session_close(int);
int session_rotate(int);
void session_cleanout(void);
int tn_ini(void);
int ttinc(void);
int tn_doop(word);
int send_iac(byte, int);
int tn_sttyp(void);
int tn_snaws(void);
int subnegotiate(void);
void optdebug(byte, int);
void server_hello(tcp_Socket *);
extern byte kmyip[]; /* our IP number */
extern byte knetmask[]; /* our netmask */
extern byte kdomain[]; /* our domain */
extern byte kgateway[]; /* our gateway */
extern byte kns1[]; /* our nameserver #1 */
extern byte kns2[]; /* our nameserver #2 */
extern byte kbcast[]; /* broadcast address pattern */
extern byte khost[]; /* remote host name/IP # */
extern word kport; /* remote host TCP port */
extern word kserver; /* if Kermit is in server mode */
extern byte ktttype[]; /* user term type override string */
extern byte kterm; /* terminal type index, from mssdef.h*/
extern byte kterm_lines; /* terminal screen height */
extern word kterm_cols; /* terminal screen width */
extern byte kbtpserver[]; /* IP of Bootp host answering query */
extern byte kdebug; /* non-zero if debug mode is active */
extern byte ktnmode; /* 0=NVT-ASCII, 1=BINARY */
extern word tcp_status; /* report errors and > 0 */
static struct sessioninfo
{
tcp_Socket socketdata; /* current socket storage area */
int sessionid; /* identifying session */
int tn_inited; /* Telnet Options inited */
byte echo; /* local echo state (0=remote) */
byte dobinary; /* mode NVT-ASCII (0) or Binary (1) */
byte server_mode; /* if Telnet server */
byte nawsflg; /* NAWS flag (1=offered, 2=wanted) */
} session[MAXSESSIONS];
static int num_sessions = 0; /* qty of active sessions */
static int active = -1; /* ident of active session */
/* this runs at Kermit task level */
int
tnmain(void) /* start TCP from Kermit */
{
int status = 0;
register byte *p;
register int i;
doslevel = 1; /* at DOS level i/o */
tcp_status = SUCCESS; /* assume all this works */
if (num_sessions == 0) /* then initialize the system */
{
my_ip_addr = 0L;
def_domain = kdomain; /* define our domain */
host = 0L; /* BELONGS IN SESSION, but is global */
msgcnt = 0;
for (i = 0; i < MAXSESSIONS; i++) /* init sessioninfo array */
{
session[i].sessionid = -1;
session[i].socketdata.sisopen = SOCKET_CLOSED;
}
if (hookvect() == 0)
{
outs("\r\n Hooking vectors failed");
tcp_status = NO_DRIVER;
goto anyerr;
}
s = NULL; /* no TCP socket yet */
if (tcp_init() == 0) /* init TCP code */
{
outs("\r\n Unable to initialize TCP/IP system, quitting");
tcp_status = NO_DRIVER;
goto anyerr;
}
if (tempip[1] != '\0' && stricmp(kmyip, "Telebit-PPP") == 0)
strcpy(kmyip, &tempip[1]); /* our IP from NET.CFG */
if (kdebug) /* can debug to file */
doslevel = 0; /* say not at DOS level, to buffer msgs */
/* set Ethernet broadcast to all 1's or all 0's */
ipbcast = resolve(kbcast); /* IP broadcast address */
bootphost = ipbcast; /* set Bootp to this IP too */
if ((p = strchr(kmyip, ' ')) != NULL) /* have a space */
*p = '\0'; /* terminate on the space */
if (kmyip == NULL)
{
outs("\r\n No TCP/IP address specified for this station.");
tcp_status = NO_LOCAL_ADDRESS;
goto anyerr;
}
if (stricmp(kmyip, "bootp") == 0) bootmethod = BOOT_BOOTP;
if (stricmp(kmyip, "DHCP") == 0) bootmethod = BOOT_DHCP;
if (bootmethod == BOOT_BOOTP || bootmethod == BOOT_DHCP)
{
if (do_bootp() != 0)
{
outs("\r\n Bootp/DHCP query failed. Quitting.");
tcp_status = BOOTP_FAILED;
goto anyerr;
}
ntoa(kmyip, my_ip_addr);
if (sin_mask != 0L)
ntoa(knetmask, sin_mask);
if (arp_rpt_gateway(0) != 0L)
ntoa(kgateway, arp_rpt_gateway(0));
if (last_nameserver > 0)
ntoa(kns1, def_nameservers[0]);
if (last_nameserver > 1)
ntoa(kns2, def_nameservers[1]);
if (kdomain[0] == '\0' && hostname[0] != '\0')
/* construct domain */
if (p = strchr(hostname, '.')) /* find dot */
strcpy(kdomain, &p[1]);
}
if (stricmp(kmyip, "rarp") == 0 || bootmethod == BOOT_RARP)
{
if (pkt_rarp_init() == 0)
{
tcp_status = NO_DRIVER;
goto anyerr; /* no RARP handle */
}
if (do_rarp() == 0) /* use RARP */
{
outs("\r\n RARP query failed.");
tcp_status = RARP_FAILED;
goto anyerr;
}
ntoa(kmyip, my_ip_addr);
bootmethod = BOOT_RARP;
}
if ((my_ip_addr = resolve(kmyip)) == 0L)
{ /* something drastically wrong */
outs("\r\n Cannot understand my IP address, terminating");
tcp_status = NO_LOCAL_ADDRESS;
goto anyerr;
}
ntoa(kmyip, my_ip_addr); /* binary to dotted decimal */
readback();
if ((sin_mask = resolve(knetmask)) == 0L)
{ /* something drastically wrong */
outs("\r\n Bad network submask, terminating");
tcp_status = BAD_SUBNET_MASK;
goto anyerr;
}
if (stricmp(kns1, "unknown"))
add_server(&last_nameserver, MAX_NAMESERVERS, def_nameservers,
resolve(kns1));
if (stricmp(kns2, "unknown"))
add_server(&last_nameserver, MAX_NAMESERVERS, def_nameservers,
resolve(kns2));
if (stricmp(kgateway, "unknown"))
arp_add_gateway(kgateway, 0L);
} /* end of initial system setup */
/* starting a new session */
session_cleanout(); /* clean out deceased sessions */
msgcnt = 0;
if (num_sessions >= MAXSESSIONS)
{
tcp_status = SESSIONS_EXCEEDED;
outs("\nAll sessions are in use. Sorry\n");
return (-1); /* say can't do another, fail */
}
status = -1;
for (i = 0; i < MAXSESSIONS; i++) /* find free ident */
if (session[i].sessionid < 0 &&
session[i].socketdata.sisopen == SOCKET_CLOSED)
{
s = &session[i].socketdata; /* active socket */
s->sisopen = SOCKET_CLOSED;
session[i].sessionid = i; /* ident */
status = i;
active = i; /* identify active session */
session[i].echo = echo = 1;/* Telnet echo from mainpgm */
/* mode, assume NVT-ASCII */
session[i].dobinary = dobinary = 0;
session[i].tn_inited = 0; /* do Telnet Options init */
session[i].server_mode = 0; /* assume not server */
num_sessions++; /* say have another active session */
break;
}
if (status == -1)
{
tcp_status = SESSIONS_EXCEEDED;
goto sock_err; /* report bad news and exit */
}
status = 0;
if (ktttype[0] != '\0') /* if user override given */
termtype = ktttype;
else /* get termtype from real use */
{
for (i = 0; termarray[i].ident != 0xff; i++)
if (termarray[i].ident == kterm) /* match */
break;
termtype = termarray[i].name;
}
/* kserver is set by main body SERVER command. If khost[0] is '*'
then also behave as a server/listener for any main body mode. */
if ((khost[0] == '*') || (kserver != 0))
{
if (khost[0] == '*')
host = 0L; /* force no host IP */
else
{
outs("\r\n Resolving address of host ");
outs(khost); outs(" ...");
if ((host = resolve(khost)) == 0)
{
outs( "\r\n Cannot resolve address of host ");
outs(khost);
tcp_status = HOST_UNKNOWN;
goto anyerr;
}
}
session[active].server_mode = 1; /* say being a server */
tcp_listen(s, kport, host, 0, 0); /* post a listen */
}
else /* normal client mode */
{
session[active].server_mode = 0; /* say being a client */
outs("\r\n Resolving address of host ");
outs(khost); outs(" ...");
if ((host = resolve(khost)) == 0)
{
outs( "\r\n Cannot resolve address of host ");
outs(khost);
tcp_status = HOST_UNKNOWN;
goto anyerr;
}
outs("\r\n"); /* get clean screen line */
}
if ((host == my_ip_addr) || ((host & 0x7f000000L) == 0x7f000000L))
{
outs("\r\n Cannot talk to myself, sorry.");
tcp_status = HOST_UNREACHABLE;
goto anyerr;
}
do_greeting = FALSE; /* Telnet server greeting msg */
if (session[active].server_mode != 0) /* if server */
{
doslevel = 0; /* end of DOS level i/o */
if (kserver == 0)
outs("\r\n Operating as a Telnet server. Waiting...");
do_greeting = TRUE;
}
else /* we are a client */
{
if (tcp_open(s, 0, host, kport) == 0)
{
outs("\r\n Unable to contact the host.");
outs("\r\n The host may be down or");
outs(" a gateway may be needed.");
tcp_status = HOST_UNREACHABLE;
goto anyerr;
}
sock_wait_established(s, 30, NULL, &status);
}
doslevel = 0; /* end of DOS level i/o */
return (active); /* return handle of this session */
sock_err:
switch (status)
{
case 1 : outs("\r\n Session is closed");
break;
case -1:/* outs("\r\n Cannot start a connection");*/
break;
}
anyerr: session_close(active);
if (num_sessions <= 0)
{
tcp_shutdown();
eth_release(); /* do absolutely */
unhookvect();
msgcnt = 0; /* clear message buffer */
doslevel = 1; /* at DOS level i/o */
}
return (-1); /* say have stopped */
}
/* this runs at Kermit task level */
int
tnexit(int value) /* stop TCP from Kermit */
{
register int i;
for (i = 0; i < MAXSESSIONS; i++)
session_close(i); /* close session, free buffers */
num_sessions = 0;
tcp_shutdown(); /* force the issue if necessary */
eth_release(); /* do absolutely */
unhookvect();
msgcnt = 0; /* clear message buffer */
return (-1);
}
/* return the int of the next available session after session "ident", or
return -1 if none. Do not actually change sessions.
*/
int
session_rotate(int ident)
{
register int i, j;
session_cleanout(); /* clean out deceased sessions */
if (ident == -1) ident = 0;
for (i = 1; i <= MAXSESSIONS; i++)
{
j = (i + ident) % MAXSESSIONS; /* modulo maxsessions */
if (session[j].sessionid != -1) /* if active */
return (j);
}
return (-1);
}
/* Change to session ident "ident" and active to "ident", return "active"
if that session is valid, else return -1.
*/
int
session_change(int ident)
{ /* change active session to ident */
if (ident == -1 || ident >= MAXSESSIONS)
return (-1);
if (session[ident].sessionid == -1)
return (-1); /* inactive session */
s = &session[ident].socketdata; /* watch mid-stream stuff */
kecho(echo = session[ident].echo); /* update Kermit main body */
kmode(dobinary = session[ident].dobinary); /* mode */
return (active = ident); /* ident of active session */
}
/* Close session "ident". Return ident of next available session, if any,
without actually changing sessions. Returns -1 if no more sessions or
if ident is out of legal range or if the session is already closed.
*/
int
session_close(int ident) /* close a particular, ident, session */
{
register tcp_Socket *s;
if (ident != -1 && ident < MAXSESSIONS &&
session[ident].sessionid != -1) /* graceful close */
{
s = &session[ident].socketdata;
s->rdatalen = 0; /* flush read buffer */
sock_close(s);
}
session_cleanout(); /* clean out deceased sessions */
/* activate, rtn next available session */
return (session_change(session_rotate(ident)));
}
/* Adjust num_sessions to reflect deceased sessions.
Memory freeing is done here to ensure we are not within DOS.
*/
void
session_cleanout(void)
{
register int i;
register tcp_Socket *s;
for (i = 0; i < MAXSESSIONS; i++) /* clean out old sessions */
if ((session[i].sessionid != -1) &&
(session[i].socketdata.sisopen == SOCKET_CLOSED))
{
num_sessions--; /* qty active sessions */
session[i].sessionid = -1; /* user level closed */
s = &session[i].socketdata;
if (s->sdata != NULL)
{
free(s->sdata); /* free send buffer */
s->sdata = NULL; /* clear pointer */
s->sdatalen = 0;
}
if (s->rdata != NULL)
{
free(s->rdata); /* free recv buffer */
s->rdata = NULL; /* clear pointer */
s->rdatalen = 0;
}
}
}
/* This is called by the main body of Kermit to transfer data. It returns
the transfer status as a BAPI valued int. */
int
serial_handler(word cmd)
{
int cmdstatus;
register int i, ch;
extern int session_change(int);
if (session[active].tn_inited == 0) /* if not initialized yet */
if (tn_ini() == -1) /* init Telnet negotiations */
return (BAPISTAT_NOS); /* fatal error, quit */
tcp_tick(s); /* catch up on packet reading */
cmdstatus = BAPISTAT_SUC; /* success so far */
if (do_greeting == TRUE && session[active].server_mode &&
s->state == tcp_StateESTAB)
{
do_greeting = FALSE;
server_hello(s); /* send greeting message */
}
switch (cmd) /* cmd is function code */
{
case BAPIWRITE: /* write a block, bapireq chars */
if (session[active].server_mode != 0
&& s->state != tcp_StateESTAB)
{
bapiret = bapireq; /* discard output and */
break; /* send nothing until client appears*/
}
if (s->state == tcp_StateESTAB)
{
bapiret = sock_write(s, bapiadr, bapireq);
if (bapiret == -1) /* no session */
cmdstatus = BAPISTAT_NOS;
}
else
{
cmdstatus = BAPISTAT_NOS; /* no session */
bapiret = bapireq; /* discard data */
break;
}
/* if terminal serving with no local echoing do echo here */
if (session[active].echo == 0 &&
session[active].server_mode != 0 &&
kserver == 0)
{ /* echo to us */
i = bapiret > MSGBUFLEN-msgcnt?
MSGBUFLEN-msgcnt: bapiret;
bcopyff(bapiadr, msgbuf, i);
outsn(msgbuf, i);
}
break;
case BAPIREAD: /* read block, count of bapireq */
bapiret = 0;
/* i = byte count in buffer preceeding an IAC */
/* first, shorten search if bapireq < data in buf */
if (s->rdatalen == 0)
{
cmdstatus = BAPISTAT_NCR;/* nothing present */
break;
}
i = (bapireq > s->rdatalen)? s->rdatalen: bapireq;
i = fstchr(s->rdata, i, IAC);
if (i < 0) /* negative -> nothing present */
{
cmdstatus = BAPISTAT_NCR;/* nothing present */
break;
}
if (i > 0) /* read up to IAC */
{
if (i > bapireq) /* safety check */
i = bapireq;
bapiret = sock_fastread(s, bapiadr, i);
/* if terminal serving with local echoing then echo to host */
if (session[active].echo != 0 &&
session[active].server_mode != 0 &&
kserver == 0)
sock_write(s, bapiadr, bapiret);
break;
}
/* i = 0. IAC is at start of buffer, get escaped byte(s) */
/* Delay until two bytes, or three for Telnet Options */
if ((s->rdatalen < 2) || (s->rdatalen == 2 &&
s->rdata[1] >= SB && s->rdata[1] != IAC))
{
cmdstatus = BAPISTAT_NCR;/* nothing present */
break;
}
if ((ch = ttinc()) == -1) /* read the IAC */
break; /* no char */
ch &= 0xff;
if (ch != IAC) /* not an IAC */
{
*bapiadr = (byte) ch;
bapiret++;
break;
}
/* get escaped byte */
if ((ch = ttinc()) == -1)
break; /* none present */
switch (ch &= 0xff) /* dispatch on escaped byte */
{
case AYT: /* Are You There */
sock_write(s, "Yes\r\n", 5);
break;
case IAC: /* IAC IAC yields just IAC */
*bapiadr = (byte) ch;
bapiret++;
break;
default:
if (ch < SB) /* ignore if not */
break; /* an Option */
tn_doop(ch); /* do Options */
session[active].echo = echo;
session[active].dobinary = dobinary;
break;
}
break;
case BAPIBRK: /* send BREAK */
{
byte cmd[2];
cmd[0] = IAC; cmd[1] = BREAK;
if (sock_write(s, cmd, 2) == -1)
cmdstatus = BAPISTAT_NOS; /* no session */
break;
}
case BAPIPING: /* Ping current host */
do_ping(khost, host);
break;
case BAPINAWS: /* if want to send screen size update */
if (session[active].nawsflg == 2) /* if wanted */
tn_snaws(); /* send NAWS Option */
break;
case BAPISTAT: /* check read status (chars avail) */
bapiret = sock_dataready(s); /* # chars available */
break;
case BAPIDISC: /* close this connection */
session_close(active);
/* fall through to do session rotate */
case BAPIECM:
i = session_rotate(active); /* get new ident */
if (i != -1) /* if exists */
i = session_change(i); /* make active */
bapiret = 0; /* no chars processed */
return (i); /* New session or -1, special */
default: cmdstatus = BAPISTAT_NSF;/* unsupported function */
break;
}
if ((s->sisopen == SOCKET_CLOSED) && (msgcnt == 0) &&
(sock_dataready(s) == 0)) /* no data and no session */
{
session_close(active); /* close deceased */
return (BAPISTAT_NOS); /* means exit session */
}
else
return (cmdstatus); /* stuff is yet unread */
}
/* ttinc - destructively read char from socket buffer, return -1 if fail */
int
ttinc(void)
{
byte ch;
tcp_tick(s); /* read another packet */
if (sock_fastread(s, &ch, 1) != 0) /* qty chars returned */
return (0xff & ch);
return (-1);
}
/* Initialize a telnet connection */
/* Returns -1 on error, 0 is ok */
int
tn_ini(void)
{
sgaflg = 0; /* SGA flag starts out this way */
wttflg = 0; /* Did not send WILL TERM TYPE yet. */
session[active].nawsflg = 0; /* Did not send NAWS info yet */
dosga = 0; /* Did not send DO SGA yet. */
dobinary = 0; /* presume NVT-ASCII result */
inbinary = outbinary = BIN_REJECT; /* NVT-ASCII mode (reject binary)*/
doEOR = 0;
session[active].tn_inited = 1; /* say we are doing this proc */
kecho(echo = 1); /* start with local echoing */
/* if not server or not Telnet port */
if (session[active].server_mode != 0 || kport != 23)
return (0); /* don't go first */
if (send_iac(WILL, TELOPT_TTYPE)) return( -1 );
wttflg = 1; /* remember we offered TTYPE */
if (send_iac(WILL, TELOPT_NAWS)) return(-1);
session[active].nawsflg = 1; /* remember we offered NAWS */
if (send_iac(DO, TELOPT_SGA)) return( -1 );
dosga = 1; /* remember we sent DO SGA */
if (ktnmode != 0) /* if we want Binary mode */
{
outbinary = inbinary = BIN_REQUEST;
if (send_iac(DO, TELOPT_BINARY)) return (-1);
if (send_iac(WILL, TELOPT_BINARY)) return (-1);
} /* remember said WILL and DO BINARY */
kmode(dobinary); /* tell main body current NVT state */
return(0);
}
/*
* send_iac - send interupt character and pertanent stuff
* - return 0 on success
*/
int
send_iac(byte cmd, int opt)
{
byte io_data[3];
io_data[0] = IAC;
io_data[1] = cmd;
io_data[2] = (byte)(opt & 0xff);
if (sock_write(s, io_data, 3) != 3 )
return (1); /* failed to write */
if (kdebug != 0)
{
outs("Opt send ");
optdebug(cmd, opt);
outs("\r\n");
}
return (0);
}
/*
* Process in-band Telnet negotiation characters from the remote host.
* Call with the telnet IAC character and the current duplex setting
* (0 = remote echo, 1 = local echo).
* Returns:
* -1 on success or char 0x255 (IAC) when IAC is the first char read here.
*/
int
tn_doop(word ch)
{ /* enter after reading IAC char */
register int c, x;
if (ch < SB) return(0); /* ch is not in range of Options */
if ((x = ttinc()) == -1) /* read Option character */
return (-1); /* nothing there */
x &= 0xff;
c = ch; /* use register'd character */
if (kdebug)
{
outs("Opt recv ");
optdebug((byte)c, x);
if ((byte)c != SB) outs("\r\n");
}
switch (x) {
case TELOPT_ECHO: /* ECHO negotiation */
if (c == WILL) /* Host says it will echo */
{
if (echo != 0) /* reply only if change required */
{
send_iac(DO,x); /* Please do */
kecho(echo = 0); /* echo is from the other side */
}
break;
}
if (c == WONT) /* Host says it won't echo */
{
if (echo == 0) /* If we not echoing now */
{
send_iac(DONT,x); /* agree to no host echo */
kecho(echo = 1); /* do local echoing */
}
break;
}
if (c == DO)
{ /* Host wants me to echo to it */
send_iac(WONT,x); /* I say I won't */
break;
}
break; /* do not respond to DONT */
case TELOPT_SGA: /* Suppress Go-Ahead */
if (c == WONT) /* Host says it won't sup go-aheads */
{
if (sgaflg == 0)
send_iac(DONT, x); /* acknowledge */
sgaflg = 1; /* no suppress, remember */
if (echo == 0) /* if we're not echoing, */
kecho(echo = 1); /* switch to local echo */
break;
}
if (c == WILL) /* Host says it will sup go aheads */
{
if (sgaflg || !dosga) /* ACK only if necessary */
{
sgaflg = 0; /* supp go-aheads, remember */
dosga++; /* remember said DO */
send_iac(DO,x); /* this is a change, so ACK */
}
break;
}
/*
Note: The following is proper behavior, and required for talking to the
Apertus interface to the NOTIS library system, e.g. at Iowa State U:
scholar.iastate.edu. Without this reply, the server hangs forever. This
code should not be loop-inducing, since Kermit never sends WILL SGA as
an initial bid, so if DO SGA comes, it is never an ACK.
*/
if (c == DO || c == DONT) /* Server wants me to SGA, or not */
{
if (send_iac(WILL,x) < 0) /* I have to say WILL SGA, */
return(-1); /* even tho I'm not changing state */
break; /* or else server might hang. */
}
break;
case TELOPT_TTYPE: /* Terminal Type */
switch (c) {
case DO: /* DO terminal type */
if (wttflg == 0) { /* If I haven't said so before, */
send_iac(WILL, x); /* say I'll send it if asked */
wttflg++;
}
break;
case SB: /* enter subnegotiations */
if (wttflg == 0)
break; /* we have not been introduced yet */
if (subnegotiate() != 0) /* successful negotiation */
tn_sttyp(); /* report terminal type */
break;
default: /* ignore other TTYPE Options */
break;
} /* end of inner switch (c) */
break;
case TELOPT_NAWS: /* terminal width and height */
switch (c) {
case DO: /* DO terminal type */
if (session[active].nawsflg == 0) /* If haven't said so before, */
send_iac(WILL, x); /* say we will send it if asked */
tn_snaws(); /* report screen size */
session[active].nawsflg = 2; /* say NAWS is wanted */
break;
default: /* ignore other NAWS Options */
break;
} /* end of inner switch (c) */
break;
case TELOPT_BINARY:
switch (c) {
case DO: /* what they want to receive from us*/
if (outbinary & BIN_REQUEST != BIN_REQUEST)
{ /* if have not sent WILL */
send_iac(WILL, x); /* we can send bin */
outbinary |= BIN_REQUEST;
}
/* ensure the other direction works the same way */
if (inbinary & BIN_REQUEST != BIN_REQUEST)
{ /* if have not sent DO */
send_iac(DO, x); /* want you to bin */
inbinary |= BIN_REQUEST;
}
outbinary |= BIN_RESPONSE; /* they said DO */
/* if we said WILL then BIN_REQUEST will be on */
break;
case WILL: /* what they want to send to us */
if (inbinary & BIN_REQUEST != BIN_REQUEST)
{ /* if we have not said DO */
send_iac(DO, x); /* want you to bin */
inbinary |= BIN_REQUEST;
}
/* ensure the other direction works the same way */
if (outbinary & BIN_REQUEST != BIN_REQUEST)
{ /* if we have not said WILL */
send_iac(WILL, x); /* we can send bin */
outbinary |= BIN_REQUEST;
}
inbinary |= BIN_RESPONSE; /* they said WILL */
/* if we said DO then BIN_REQUEST will be on */
break;
case DONT: /* they will not receive binary */
if (inbinary & BIN_REQUEST == BIN_REQUEST) {
send_iac(WONT, x); /* we won't receive binary */
inbinary = BIN_REJECT | BIN_RESPONSE;
}
break;
case WONT: /* they will not send binary */
if (outbinary & BIN_REQUEST == BIN_REQUEST) {
send_iac(WONT, x); /* we will not send binary */
outbinary = BIN_REJECT | BIN_RESPONSE;
}
break;
} /* end of Binary switch (c) */
/* if have both answers */
if ((inbinary & outbinary & BIN_RESPONSE) == BIN_RESPONSE)
{
dobinary = ((inbinary & outbinary & BIN_REQUEST)
== BIN_REQUEST) ? 1: 0;
kmode( dobinary ); /* update main body, 1 = binary */
}
break;
case TELOPT_EOR:
switch (c) {
case DO:
if (ktnmode != 0 && doEOR == 0) {
send_iac(WILL, x); /* say I will */
doEOR++;
break;
}
if (ktnmode == 0) {
send_iac(WONT, x); /* I won't do EOR to you */
doEOR = 0;
}
break;
case WILL:
if (ktnmode != 0 && doEOR == 0) {
send_iac(DO, x); /* tell host to do EORs */
doEOR++;
break;
}
if (ktnmode == 0) {
send_iac(DONT, x); /* don't do EORs */
doEOR = 0;
}
break;
case DONT:
if (doEOR != 0) {
send_iac(WONT, x); /* say we won't */
doEOR = 0;
}
break;
case WONT:
if (doEOR != 0) {
send_iac(DONT, x); /* say don't do it */
doEOR = 0;
}
break;
} /* send of EOR switch (c) */
break;
default: /* all other Options: refuse nicely */
switch(c) {
case WILL: /* You will? */
send_iac(DONT,x); /* Please don't */
break;
case DO: /* You want me to? */
send_iac(WONT,x); /* I won't */
break;
case DONT:
send_iac(WONT,x); /* I won't */
break;
case WONT: /* You won't? */
break; /* Good */
default:
break; /* unknown character, discard */
} /* end of default switch (c) */
break;
} /* end switch (x) */
return (-1); /* say done with Telnet Options */
}
/* Perform Telnet Option subnegotiation. SB byte has been read. Consume
through IAC SE. Return 1 if successful, else 0.
*/
int
subnegotiate(void)
{
register word flag, y;
word n;
n = flag = 0; /* flag for when done reading SB */
while (n < TSBUFSIZ)
{ /* loop looking for IAC SE */
if ((y = ttinc()) == -1)
continue; /* nothing there */
y &= 0xff; /* make sure it's just 8 bits */
sb[n++] = (byte) y; /* save what we got in buffer */
if (kdebug)
{
if (y == SE) outs(" se\r\n");
else
if (y != IAC)
{
if (n == 1 && y == 1)
outs(" send");
else
{
outs(" \\x");
outhex((byte)y);
}
}
}
if (y == IAC) /* If this is an IAC */
{
if (flag) /* If previous char was IAC */
{
n--; /* it's quoted, keep one IAC */
flag = 0; /* and turn off the flag. */
}
else flag = 1; /* Otherwise set the flag. */
}
else if (flag) /* Something else following IAC */
{
if (y != SE) /* If not SE, it's a protocol error */
flag = 0;
break;
} /* end of if (y == IAC) */
} /* end while */
if (flag == 0 || y == -1) /* no option IAC SE */
return (0); /* flag == 0 is invalid SB */
if ( *sb == 1 ) /* wants us to report option */
return (1); /* say can do report */
else
return (0);
}
/* Telnet send terminal type */
/* Returns -1 on error, 0 on success */
int
tn_sttyp(void)
{ /* Send telnet terminal type. */
register byte *ttn;
register int ttnl; /* Name & length of terminal type. */
ttn = termtype; /* we already have this from environment */
if ((*ttn == 0) || ((ttnl = strlen(ttn)) >= TSBUFSIZ))
{
ttn = "UNKNOWN";
ttnl = 7;
}
sb[0] = (byte)IAC;
sb[1] = (byte)SB;
sb[2] = (byte)TELOPT_TTYPE;
sb[3] = 0; /* 'is'... */
ttn = strcpy(&sb[4], ttn); /* Copy to subnegotiation buffer */
ttn = &sb[ttnl + 4]; /* go to end of buffer */
*ttn++ = (byte)IAC;
*ttn = (byte)SE;
sock_write(s, sb, ttnl + 6);
if (kdebug)
{
int i;
outs("Opt send ");
optdebug(SB, TELOPT_TTYPE);
outs(" is ");
for (i = 0; i < ttnl; i++) outch(sb[i+4]);
outs(" se\r\n");
}
return (0);
}
/* Send terminal width and height (characters). RFC 1073 */
/* IAC SB NAWS <16-bit value> <16-bit value> IAC SE */
int
tn_snaws(void)
{
static byte sbuf[9] = {(byte)IAC, (byte)SB, (byte)TELOPT_NAWS,
0,0, 0,0, (byte)IAC, (byte)SE};
get_kscreen(); /* get current screen */
sbuf[4] = kterm_cols;
sbuf[6] = kterm_lines;
sock_write(s, sbuf, sizeof(sbuf));
if (kdebug)
{
outs("Opt send ");
optdebug(SB, TELOPT_NAWS);
outs(" \\x");
outhex(sbuf[4]);
outs(" \\x");
outhex(sbuf[6]);
outs(" se\r\n");
}
return (0);
}
/* assist displaying of Telnet Options negotiation material */
void
optdebug(byte cmd, int option)
{
switch (cmd)
{
case WILL: outs("will ");
break;
case WONT: outs("wont ");
break;
case DO: outs("do ");
break;
case DONT: outs("dont ");
break;
case SB: outs("sb ");
break;
default: outs("\\x "); outhex(cmd);
break;
} /* end of switch c */
switch (option)
{
case TELOPT_BINARY: outs("binary");
break;
case TELOPT_ECHO: outs("echo");
break;
case TELOPT_SGA: outs("sga");
break;
case TELOPT_TTYPE: outs("ttype");
break;
case TELOPT_EOR: outs("eor");
break;
case TELOPT_NAWS: outs("naws");
break;
default: outs("\\x"); outhex((byte)option);
break;
}
}
/* Compose a nice greeting message for incoming Telnet connections. Called
by tcp_handler() in the tcp_StateLISTEN section. It also notifies the
local terminal emulator of the client's presence and address. */
void
server_hello(tcp_Socket *s)
{
char hellomsg[MSGBUFLEN]; /* work buffer, keep short */
strcpy(hellomsg,
"\r\n Welcome to the MS-DOS Kermit Telnet server at [");
ntoa(&hellomsg[strlen(hellomsg)], my_ip_addr); /* our IP */
strcat(hellomsg, "].\r\n"); /* as [dotted decimal] */
if (kserver != 0) /* if file serving */
{
strcat(hellomsg," Escape back to your Kermit prompt and");
strcat(hellomsg," issue Kermit file server commands.\r\n\n");
}
else /* if terminal emulating */
{
strcat(hellomsg,
" You are talking to the terminal emulator,\r\n");
strcat(hellomsg, " adjust local echoing accordingly.\r\n");
}
sock_write(s, hellomsg, strlen(hellomsg)); /* tell remote */
/* tell main body the news */
strcpy(hellomsg, "\r\n Connection starting from [");
ntoa(&hellomsg[strlen(hellomsg)], s->hisaddr); /* their IP */
strcat(hellomsg, "].\r\n");
outs(hellomsg); /* send connection info to main body */
}