#include #include "global.h" #include "mbuf.h" #include "timer.h" #include "internet.h" #include "icmp.h" #include "netuser.h" #include "tcp.h" #include "telnet.h" #include "session.h" extern int ttyflow; /* DG2KK: flow control */ extern char nospace[]; extern char badhost[]; int refuse_echo = 0; int unix_line_mode = 0; /* if true turn to when in line mode */ #ifdef DEBUG char *t_options[] = { "Transmit Binary", "Echo", "", "Suppress Go Ahead", "", "Status", "Timing Mark" }; #endif /* Execute user telnet command */ int dotelnet(argc,argv) int argc; char *argv[]; { void t_state(),rcv_char(),tn_tx(); char *inet_ntoa(); int32 resolve(); int send_tel(); int unix_send_tel(); struct session *s; struct telnet *tn; struct tcb *tcb; struct socket lsocket,fsocket; lsocket.address = ip_addr; lsocket.port = lport++; if((fsocket.address = resolve(argv[1])) == 0){ printf(badhost,argv[1]); return 1; } if(argc < 3) fsocket.port = TELNET_PORT; else fsocket.port = atoi(argv[2]); /* Allocate a session descriptor */ if((s = newsession()) == NULLSESSION){ printf("Too many sessions\n"); return 1; } if((s->name = malloc((unsigned)strlen(argv[1])+1)) != NULLCHAR) strcpy(s->name,argv[1]); s->type = TELNET; if ((refuse_echo == 0) && (unix_line_mode != 0)) { s->parse = unix_send_tel; } else { s->parse = send_tel; } current = s; /* Create and initialize a Telnet protocol descriptor */ if((tn = (struct telnet *)calloc(1,sizeof(struct telnet))) == NULLTN){ printf(nospace); s->type = FREE; return 1; } tn->session = s; /* Upward pointer */ tn->state = TS_DATA; s->cb.telnet = tn; /* Downward pointer */ tcb = open_tcp(&lsocket,&fsocket,TCP_ACTIVE,0, rcv_char,tn_tx,t_state,0,(char *)tn); tn->tcb = tcb; /* Downward pointer */ go(); return 0; } /* Process typed characters */ int unix_send_tel(buf,n) char *buf; int16 n; { int i; for (i=0; (icb.telnet == NULLTN || current->cb.telnet->tcb == NULLTCB) return; /* If we're doing our own echoing and recording is enabled, record it */ if(!current->cb.telnet->remote[TN_ECHO] && current->record != NULLFILE) fwrite(buf,1,n,current->record); bp = qdata(buf,n); send_tcp(current->cb.telnet->tcb,bp); } /* Process incoming TELNET characters */ int tel_input(tn,bp) register struct telnet *tn; struct mbuf *bp; { char c; int ci; void doopt(),dontopt(),willopt(),wontopt(),answer(); FILE *record; char *memchr(); /* Optimization for very common special case -- no command chars */ if(tn->state == TS_DATA){ while(bp != NULLBUF && memchr(bp->data,IAC,bp->cnt) == NULLCHAR){ fwrite(bp->data,1,bp->cnt,stdout); if((record = tn->session->record) != NULLFILE) fwrite(bp->data,1,bp->cnt,record); bp = free_mbuf(bp); } } while(pullup(&bp,&c,1) == 1){ ci = c & 0xff; switch(tn->state){ case TS_DATA: if(ci == IAC){ tn->state = TS_IAC; } else { if(!tn->remote[TN_TRANSMIT_BINARY]) c &= 0x7f; if(c != '\0') /* Don't screw up ANSI driver */ putchar(c); if((record = tn->session->record) != NULLFILE) putc(c,record); } break; case TS_IAC: switch(ci){ case WILL: tn->state = TS_WILL; break; case WONT: tn->state = TS_WONT; break; case DO: tn->state = TS_DO; break; case DONT: tn->state = TS_DONT; break; case IAC: putchar(c); tn->state = TS_DATA; break; default: tn->state = TS_DATA; break; } break; case TS_WILL: willopt(tn,ci); tn->state = TS_DATA; break; case TS_WONT: wontopt(tn,ci); tn->state = TS_DATA; break; case TS_DO: doopt(tn,ci); tn->state = TS_DATA; break; case TS_DONT: dontopt(tn,ci); tn->state = TS_DATA; break; } } } /* Telnet receiver upcall routine */ void rcv_char(tcb,cnt) register struct tcb *tcb; int16 cnt; { struct mbuf *bp; struct telnet *tn; FILE *record; if((tn = (struct telnet *)tcb->user) == NULLTN){ /* Unknown connection; ignore it */ return; } /* Hold output if we're not the current session */ if(mode != CONV_MODE || current == NULLSESSION || current->type != TELNET || current->cb.telnet != tn) return; /* DG2KK: hold output during keyboard input */ if (ttyflow == 0) return; if(recv_tcp(tcb,&bp,cnt) > 0) tel_input(tn,bp); fflush(stdout); if((record = tn->session->record) != NULLFILE) fflush(record); } /* Handle transmit upcalls. Used only for file uploading */ void tn_tx(tcb,cnt) struct tcb *tcb; int16 cnt; { struct telnet *tn; struct session *s; struct mbuf *bp; int size; if((tn = (struct telnet *)tcb->user) == NULLTN || (s = tn->session) == NULLSESSION || s->upload == NULLFILE) return; if((bp = alloc_mbuf(cnt)) == NULLBUF) return; if((size = fread(bp->data,1,cnt,s->upload)) > 0){ bp->cnt = (int16)size; send_tcp(tcb,bp); } else { free_p(bp); } if(size != cnt){ /* Error or end-of-file */ fclose(s->upload); s->upload = NULLFILE; free(s->ufile); s->ufile = NULLCHAR; } } /* State change upcall routine */ void t_state(tcb,old,new) register struct tcb *tcb; char old,new; { struct telnet *tn; char notify = 0; extern char *tcpstates[]; extern char *reasons[]; extern char *unreach[]; extern char *exceed[]; /* Can't add a check for unknown connection here, it would loop * on a close upcall! We're just careful later on. */ tn = (struct telnet *)tcb->user; if(current != NULLSESSION && current->type == TELNET && current->cb.telnet == tn) notify = 1; switch(new){ case CLOSE_WAIT: if(notify) printf("%s\n",tcpstates[new]); close_tcp(tcb); break; case CLOSED: /* court adjourned */ if(notify){ printf("%s (%s",tcpstates[new],reasons[tcb->reason]); if(tcb->reason == NETWORK){ switch(tcb->type){ case DEST_UNREACH: printf(": %s unreachable",unreach[tcb->code]); break; case TIME_EXCEED: printf(": %s time exceeded",exceed[tcb->code]); break; } } printf(")\n"); cmdmode(); } del_tcp(tcb); if(tn != NULLTN) free_telnet(tn); break; default: if(notify) printf("%s\n",tcpstates[new]); break; } fflush(stdout); } /* Delete telnet structure */ static free_telnet(tn) struct telnet *tn; { if(tn->session != NULLSESSION) freesession(tn->session); if(tn != NULLTN) free((char *)tn); } /* The guts of the actual Telnet protocol: negotiating options */ static void willopt(tn,opt) struct telnet *tn; int opt; { int ack; void answer(); #ifdef DEBUG printf("recv: will "); if(opt <= NOPTIONS) printf("%s\n",t_options[opt]); else printf("%u\n",opt); #endif switch(opt){ case TN_TRANSMIT_BINARY: case TN_ECHO: case TN_SUPPRESS_GA: if(tn->remote[opt] == 1) return; /* Already set, ignore to prevent loop */ if(opt == TN_ECHO){ if(refuse_echo){ /* User doesn't want to accept */ ack = DONT; break; } else raw(); /* Put tty into raw mode */ } tn->remote[opt] = 1; ack = DO; break; default: ack = DONT; /* We don't know what he's offering; refuse */ } answer(tn,ack,opt); } static void wontopt(tn,opt) struct telnet *tn; int opt; { void answer(); #ifdef DEBUG printf("recv: wont "); if(opt <= NOPTIONS) printf("%s\n",t_options[opt]); else printf("%u\n",opt); #endif if(opt <= NOPTIONS){ if(tn->remote[opt] == 0) return; /* Already clear, ignore to prevent loop */ tn->remote[opt] = 0; if(opt == TN_ECHO) cooked(); /* Put tty into cooked mode */ } answer(tn,DONT,opt); /* Must always accept */ } static void doopt(tn,opt) struct telnet *tn; int opt; { void answer(); int ack; #ifdef DEBUG printf("recv: do "); if(opt <= NOPTIONS) printf("%s\n",t_options[opt]); else printf("%u\n",opt); #endif switch(opt){ #ifdef FUTURE /* Use when local options are implemented */ if(tn->local[opt] == 1) return; /* Already set, ignore to prevent loop */ tn->local[opt] = 1; ack = WILL; break; #endif default: ack = WONT; /* Don't know what it is */ } answer(tn,ack,opt); } static void dontopt(tn,opt) struct telnet *tn; int opt; { void answer(); #ifdef DEBUG printf("recv: dont "); if(opt <= NOPTIONS) printf("%s\n",t_options[opt]); else printf("%u\n",opt); #endif if(opt <= NOPTIONS){ if(tn->local[opt] == 0){ /* Already clear, ignore to prevent loop */ return; } tn->local[opt] = 0; } answer(tn,WONT,opt); } static void answer(tn,r1,r2) struct telnet *tn; int r1,r2; { struct mbuf *bp,*qdata(); 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 <= 6) printf("%s\n",t_options[r2]); else printf("%u\n",r2); #endif s[0] = IAC; s[1] = r1; s[2] = r2; bp = qdata(s,(int16)3); send_tcp(tn->tcb,bp); }