/* Internet Telnet client * Copyright 1991 Phil Karn, KA9Q */ #include #ifdef __TURBOC__ #include #include #endif #include "global.h" #include "mbuf.h" #include "socket.h" #include "telnet.h" #include "session.h" #include "proc.h" #include "tty.h" #include "commands.h" #include "internet.h" #include "netuser.h" static int filemode __ARGS((FILE *fp,int mode)); #define CTLZ 26 int Refuse_echo = 0; int Tn_cr_mode = 0; /* if true turn to */ #ifdef DEBUG char *T_options[] = { "Transmit Binary", "Echo", "", "Suppress Go Ahead", "", "Status", "Timing Mark" }; #endif /* Execute user telnet command */ int dotelnet(argc,argv,p) int argc; char *argv[]; void *p; { struct session *sp; struct sockaddr_in fsocket; /* Allocate a session descriptor */ if((sp = newsession(argv[1],TELNET,1)) == NULLSESSION){ tprintf("Too many sessions\n"); return 1; } Current->flowmode = 0; fsocket.sin_family = AF_INET; if(argc < 3) fsocket.sin_port = IPPORT_TELNET; else fsocket.sin_port = atoi(argv[2]); tprintf("Resolving %s... ",sp->name); if((fsocket.sin_addr.s_addr = resolve(sp->name)) == 0){ tprintf(Badhost,sp->name); keywait(NULLCHAR,1); freesession(sp); return 1; } if((sp->s = socket(AF_INET,SOCK_STREAM,0)) == -1){ tprintf("Can't create socket\n"); keywait(NULLCHAR,1); freesession(sp); return 1; } settos(sp->s,LOW_DELAY); return tel_connect(sp,(char *)&fsocket,SOCKSIZE); } /* Generic interactive connect routine, used by Telnet, AX.25, NET/ROM */ int tel_connect(sp,fsocket,len) struct session *sp; char *fsocket; int len; { unsigned int index; struct telnet tn; index = sp - Sessions; memset((char *)&tn,0,sizeof(tn)); tn.eolmode = Tn_cr_mode; tn.session = sp; /* Upward pointer */ sp->cb.telnet = &tn; /* Downward pointer */ sockmode(sp->s,SOCK_ASCII); /* Default to ascii mode */ tprintf("Trying %s...\n",psocket((struct sockaddr *)fsocket)); if(connect(sp->s,fsocket,len) == -1){ tprintf("%s session %u failed: %s errno %d\n", Sestypes[sp->type], index, sockerr(sp->s),errno); keywait(NULLCHAR,1); freesession(sp); return 1; } tprintf("%s session ",Sestypes[sp->type]); tprintf("%u connected to %s\n",index,sp->name); tnrecv(&tn); return 0; } /* Telnet input routine, common to both telnet and ttylink */ void tnrecv(tn) struct telnet *tn; { int c,s,index; struct session *sp; char *cp; sp = tn->session; s = sp->s; index = sp - Sessions; /* Fork off the transmit process */ sp->proc1 = newproc("tel_out",1024,tel_output,0,tn,NULL,0); /* Process input on the connection */ while((c = recvchar(s)) != -1){ if(c != IAC){ /* Ordinary character */ if(!tn->remote[TN_TRANSMIT_BINARY]) c &= 0x7f; tputc((char)c); continue; } /* IAC received, get command sequence */ c = recvchar(s); switch(c){ case WILL: c = recvchar(s); willopt(tn,c); break; case WONT: c = recvchar(s); wontopt(tn,c); break; case DO: c = recvchar(s); doopt(tn,c); break; case DONT: c = recvchar(s); dontopt(tn,c); break; case IAC: /* Escaped IAC */ tputc(IAC); break; } } quit: /* A close was received from the remote host. * Notify the user, kill the output task and wait for a response * from the user before freeing the session. */ sockmode(sp->output,SOCK_ASCII); /* Restore newline translation */ cp = sockerr(s); tprintf("%s session %u", Sestypes[sp->type],index); tprintf(" closed: %s\n", cp != NULLCHAR ? cp : "EOF"); killproc(sp->proc1); sp->proc1 = NULLPROC; close_s(sp->s); sp->s = -1; keywait(NULLCHAR,1); freesession(sp); } /* User telnet output task, started by user telnet command */ void tel_output(unused,tn1,p) int unused; void *tn1; void *p; { struct session *sp; int c; struct telnet *tn; tn = (struct telnet *)tn1; sp = tn->session; /* Send whatever's typed on the terminal */ while((c = recvchar(sp->input)) != EOF){ usputc(sp->s,(char)c); if(!tn->remote[TN_ECHO] && sp->record != NULLFILE) putc(c,sp->record); /* By default, output is transparent in remote echo mode. * If eolmode is set, turn a cr into cr-null. * This can only happen when in remote echo (raw) mode, since * the tty driver normally maps \r to \n in cooked mode. */ if(c == '\r' && tn->eolmode) usputc(sp->s,'\0'); if(tn->remote[TN_ECHO]) usflush(sp->s); } /* Make sure our parent doesn't try to kill us after we exit */ sp->proc1 = NULLPROC; } int doecho(argc,argv,p) int argc; char *argv[]; void *p; { if(argc < 2){ if(Refuse_echo) tprintf("Refuse\n"); else tprintf("Accept\n"); } else { if(argv[1][0] == 'r') Refuse_echo = 1; else if(argv[1][0] == 'a') Refuse_echo = 0; else return -1; } return 0; } /* set for unix end of line for remote echo mode telnet */ int doeol(argc,argv,p) int argc; char *argv[]; void *p; { if(argc < 2){ if(Tn_cr_mode) tprintf("null\n"); else tprintf("standard\n"); } else { if(argv[1][0] == 'n') Tn_cr_mode = 1; else if(argv[1][0] == 's') Tn_cr_mode = 0; else { tprintf("Usage: %s [standard|null]\n",argv[0]); return -1; } } return 0; } /* The guts of the actual Telnet protocol: negotiating options */ void willopt(tn,opt) struct telnet *tn; int opt; { int ack; #ifdef DEBUG printf("recv: will "); if(uchar(opt) <= NOPTIONS) printf("%s\n",T_options[opt]); else printf("%u\n",opt); #endif switch(uchar(opt)){ case TN_TRANSMIT_BINARY: case TN_ECHO: case TN_SUPPRESS_GA: if(tn->remote[uchar(opt)] == 1) return; /* Already set, ignore to prevent loop */ if(uchar(opt) == TN_ECHO){ if(Refuse_echo){ /* User doesn't want to accept */ ack = DONT; break; } else { /* Put tty into raw mode */ tn->session->ttystate.edit = 0; tn->session->ttystate.echo = 0; sockmode(tn->session->s,SOCK_BINARY); sockmode(tn->session->input,SOCK_BINARY); sockmode(tn->session->output,SOCK_BINARY); if(tn->session->record != NULLFILE) filemode(tn->session->record,SOCK_BINARY); } } tn->remote[uchar(opt)] = 1; ack = DO; break; default: ack = DONT; /* We don't know what he's offering; refuse */ } answer(tn,ack,opt); } void wontopt(tn,opt) struct telnet *tn; int opt; { #ifdef DEBUG printf("recv: wont "); if(uchar(opt) <= NOPTIONS) printf("%s\n",T_options[uchar(opt)]); else printf("%u\n",uchar(opt)); #endif if(uchar(opt) <= NOPTIONS){ if(tn->remote[uchar(opt)] == 0) return; /* Already clear, ignore to prevent loop */ tn->remote[uchar(opt)] = 0; if(uchar(opt) == TN_ECHO){ /* Put tty into cooked mode */ tn->session->ttystate.edit = 1; tn->session->ttystate.echo = 1; sockmode(tn->session->s,SOCK_ASCII); sockmode(tn->session->input,SOCK_ASCII); sockmode(tn->session->output,SOCK_ASCII); if(tn->session->record != NULLFILE) filemode(tn->session->record,SOCK_ASCII); } } answer(tn,DONT,opt); /* Must always accept */ } void doopt(tn,opt) struct telnet *tn; int opt; { int ack; #ifdef DEBUG printf("recv: do "); if(uchar(opt) <= NOPTIONS) printf("%s\n",T_options[uchar(opt)]); else printf("%u\n",uchar(opt)); #endif switch(uchar(opt)){ case TN_SUPPRESS_GA: if(tn->local[uchar(opt)] == 1) return; /* Already set, ignore to prevent loop */ tn->local[uchar(opt)] = 1; ack = WILL; break; default: ack = WONT; /* Don't know what it is */ } answer(tn,ack,opt); } void dontopt(tn,opt) struct telnet *tn; int opt; { #ifdef DEBUG printf("recv: dont "); if(uchar(opt) <= NOPTIONS) printf("%s\n",T_options[uchar(opt)]); else printf("%u\n",uchar(opt)); #endif if(uchar(opt) <= NOPTIONS){ if(tn->local[uchar(opt)] == 0){ /* Already clear, ignore to prevent loop */ return; } tn->local[uchar(opt)] = 0; } answer(tn,WONT,opt); } void answer(tn,r1,r2) struct telnet *tn; int r1,r2; { char s[3]; #ifdef DEBUG switch(r1){ case WILL: printf("sent: will "); break; case WONT: printf("sent: wont "); break; case DO: printf("sent: do "); break; case DONT: printf("sent: dont "); break; } if(r2 <= NOPTIONS) printf("%s\n",T_options[r2]); else printf("%u\n",r2); #endif s[0] = IAC; s[1] = r1; s[2] = r2; send(tn->session->s,s,3,0); } #ifdef __TURBOC__ /* Set end-of-line translation mode on file */ static int filemode(fp,mode) FILE *fp; int mode; { int omode; if(fp == NULLFILE) return -1; if(fp->flags & _F_BIN) omode = SOCK_BINARY; else omode = SOCK_ASCII; switch(mode){ case SOCK_BINARY: fp->flags = _F_BIN; setmode(fileno(fp),O_BINARY); break; case SOCK_ASCII: fp->flags &= ~_F_BIN; setmode(fileno(fp),O_TEXT); break; } return omode; } #else static int filemode(fp,mode) FILE *fp; int mode; { return 0; } #endif