#ifndef lint static char RCSid[] = "$Header: gaptelnetd.c,v 2.0 85/11/21 07:23:06 jqj Exp $"; #endif /* * server for GAP-style (TransportObject=server,teletype) telnet connections * Note that we support only GAP version 3 */ /* $Log: gaptelnetd.c,v $ * Revision 2.0 85/11/21 07:23:06 jqj * 4.3BSD standard release * * Revision 1.3 85/11/21 06:53:22 jqj * symbolic values for connection type * * Revision 1.2 85/11/08 17:17:13 bill * version on bullwinkle * * Revision 1.3 85/08/05 09:58:00 jqj * fixed for Interlisp -- data from Interlisp appears with dt==0 (wrong!) * also, Interlisp trys to connect to a tty rather than a ttyHost. * increased inactivity timeout to 4 hrs * * Revision 1.2 85/05/23 06:22:18 jqj * *** empty log message *** * * Revision 1.1 85/05/22 09:46:52 jqj * Initial revision */ #include #include #include #include #include #include #include #include #include #include #include #include #include "GAP3.h" #include "gapcontrols.h" #include #include #define BELL '\07' #define BANNER "\r\n\r\n4.3 BSD UNIX (%s)\r\n\r\r\n\r" int pty, net; extern CourierConnection *_serverConnection; char buf[sizeof(struct sphdr)+SPPMAXDATA]; struct sphdr our_sphdr; struct iovec our_iovec[2] = {{((caddr_t)&our_sphdr), sizeof(our_sphdr)}}; /* * I/O data buffers, pointers, and counters. */ char ptyibuf[512], *ptyip = ptyibuf; char ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf; char *netip = buf; char netobuf[512], *nfrontp = netobuf, *nbackp = netobuf; int pcc, ncc; char line[12]; extern char **environ; extern int errno; char *envinit[3]; char wsenv[50]; /* * session parameters */ Cardinal frametimeout; /* 0 or time in seconds to wait */ /* * This modified version of the server is necessary since GAP specifies * that the telnet data-transfer session occurs after the RPC to create * it has returned! */ Server(skipcount,skippedwords) int skipcount; Unspecified skippedwords[]; { Cardinal _procedure; register Unspecified *_buf; LongCardinal programnum; Cardinal versionnum; Cardinal _n; #ifdef DEBUG BUGOUT("Server: %d %d",skipcount,skippedwords); #endif for (;;) { _buf = ReceiveCallMessage(&_procedure, skipcount, skippedwords); DURING switch (_procedure) { case 3: server_GAP3_Delete(_buf); break; case 2: server_GAP3_Create(_buf); net = _serverConnection->fd; gaptelnet(); /* returns on connection close */ break; case 0: server_GAP3_Reset(_buf); break; default: NoSuchProcedureValue("GAP", _procedure); break; } HANDLER { Deallocate(_buf); switch (Exception.Code) { case GAP3_serviceNotFound: case GAP3_userNotAuthorized: case GAP3_userNotAuthenticated: case GAP3_serviceTooBusy: case GAP3_terminalAddressInvalid: case GAP3_terminalAddressInUse: case GAP3_controllerDoesNotExist: case GAP3_controllerAlreadyExists: case GAP3_gapCommunicationError: case GAP3_gapNotExported: case GAP3_bugInGAPCode: case GAP3_tooManyGateStreams: case GAP3_inconsistentParams: case GAP3_transmissionMediumUnavailable: case GAP3_dialingHardwareProblem: case GAP3_noDialingHardware: case GAP3_badAddressFormat: case GAP3_mediumConnectFailed: case GAP3_illegalTransport: case GAP3_noCommunicationHardware: case GAP3_unimplemented: _buf = Allocate(0); SendAbortMessage(Exception.Code-ERROR_OFFSET, 0, _buf); break; default: _buf = Allocate(0); SendRejectMessage(unspecifiedError, 0, _buf); break; } } END_HANDLER; Deallocate(_buf); for (;;) { skipcount = LookAheadCallMsg(&programnum, &versionnum, skippedwords); if (skipcount < 0) return(0); /* timed out */ if (programnum != 3 || versionnum != 3) ExecCourierProgram(programnum, versionnum, skipcount, skippedwords); } /* loop if can't exec that program */ } } void GAP3_Delete(session) GAP3_SessionHandle session; { } void GAP3_Reset() { } GAP3_CreateResults GAP3_Create(conn, BDTproc, sessionparams, transports, createTimeout, credentials, verifier) CourierConnection *conn; int BDTproc; GAP3_SessionParameterObject sessionparams; struct {Cardinal length; GAP3_TransportObject *sequence; } transports; GAP3_WaitTime createTimeout; Authentication1_Credentials credentials; Authentication1_Verifier verifier; { GAP3_CreateResults result; char *c1, *c2, *host; int t, pid; struct sgttyb b; char *xntoa(), *wsname(); struct sockaddr_ns who; int whosize = sizeof(who); LongCardinal servicetype; GAP3_CommParamObject *cp; GAP3_Duplexity duplexity; /* fullDuplex, halfDuplex */ #ifdef DEBUG BUGOUT("CREATE"); #endif switch (sessionparams.designator) { case oldTty: case oldTtyHost: frametimeout = sessionparams.oldTtyHost_case.frameTimeout/1000; /* could set other parameters here */ break; case tty: case ttyHost: frametimeout = sessionparams.ttyHost_case.frameTimeout/1000; /* could set other parameters here */ break; default: raise(GAP3_unimplemented, 0); /*NOTREACHED*/ } if (transports.length != 2) { raise(GAP3_illegalTransport); /*NOTREACHED*/ } switch (transports.sequence[0].designator) { case service: servicetype = transports.sequence[0].service_case.id; switch (servicetype) { case TTYService_any: /* 0 */ servicetype = TTYService_sa; case TTYService_sa: /* 1 */ case TTYService_exec: /* 2 */ case TTYService_its: /* 3 */ break; default: raise(GAP3_serviceNotFound, 0); /*NOTREACHED*/ } duplexity = fullduplex;/* services are allways fulldup */ break; case rs232c: /* maybe some day */ cp = &transports.sequence[0].rs232c_case.commParams; if (cp->accessDetail.designator == directConn) { duplexity = cp->accessDetail.directConn_case.duplex; servicetype = TTYService_sa; /* fake it */ break; } raise(GAP3_noCommunicationHardware, 0); /*NOTREACHED*/ default: raise(GAP3_illegalTransport, 0); /*NOTREACHED*/ } if (transports.sequence[1].designator != teletype) raise(GAP3_illegalTransport, 0); /* ignore createTimeout */ /* ignore credentials and verifier */ for (c1 = "pq"; *c1 != 0; c1++) for (c2 = "0123456789abcdef"; *c2 != 0; c2++) { sprintf(line, "/dev/pty%c%c", *c1, *c2); pty = open(line, 2); if (pty < 0) continue; line[strlen("/dev/")] = 't'; t = open(line, 2); if (t > 0) goto gotpty; close(pty); } raise(GAP3_serviceTooBusy, 0); /*NOTREACHED*/ gotpty: getpeername(_serverConnection->fd, &who, &whosize); host = wsname(who.sns_addr); #ifdef DEBUG BUGOUT("gotpty <%s> %d <%s>",line, pty, host); #endif ioctl(t, TIOCGETP, &b); b.sg_flags = CRMOD|XTABS|ANYP; ioctl(t, TIOCSETP, &b); ioctl(pty, TIOCGETP, &b); if (duplexity == fullduplex) b.sg_flags |= ECHO; else b.sg_flags &= ~ECHO; ioctl(pty, TIOCSETP, &b); /* we do the fork now so we can return failures as REPORTS */ pid = fork(); if (pid < 0) { close(pty); close(t); raise(GAP3_serviceTooBusy, 0); /*NOTREACHED*/ } else if (pid == 0) { /* in the execed fork */ sleep(1); /* let parent get ready for us */ close(_serverConnection->fd); /* close net */ close(pty); dup2(t, 0); dup2(t, 1); dup2(t, 2); if (t > 2) close(t); envinit[0] = "TERM=network"; envinit[1] = sprintf(wsenv, "WORKSTATION=%s", xntoa(who.sns_addr)); envinit[2] = (char*) 0; #ifdef DEBUG BUGOUT("about to exec /bin/login"); #endif execl("/bin/login","login", "-h", host, 0); #ifdef DEBUG BUGOUT("exec of /bin/login failed"); #endif perror("/bin/login"); exit(1); /*NOTREACHED*/ } close(t); #ifdef DEBUG BUGOUT("fork successful"); #endif result.session[0] = pid; return(result); } jmp_buf childdiedbuf; /* * Main loop. Select from pty and network, and * hand data to telnet receiver finite state machine. * Returns 0 on orderly shutdown, 1 on abnormal shutdown. */ gaptelnet() { int on = 1; char hostname[32]; int childdied(); int ibits = 0, obits = 0; register int c; struct sphdr *si = (struct sphdr *)buf; static struct timeval timeout = {600,0}; int keepalives = 0; int i; #ifdef DEBUG BUGOUT("gaptelnet net=%d,pty=%d",net,pty); #endif if (setjmp(childdiedbuf) != 0) return(0); /* child died */ signal(SIGCHLD, childdied); signal(SIGTSTP, SIG_IGN); ioctl(net, FIONBIO, &on); ioctl(pty, FIONBIO, &on); /* * Show banner that getty never gave. */ gethostname(hostname, sizeof (hostname)); sprintf(nfrontp, BANNER, hostname); nfrontp += strlen(nfrontp); /* * Send status message indicating we're ready to go */ changeSPPopts(net, GAPCTLnone, 1); sendoobdata(GAPCTLmediumUp); for (;;) { #ifdef DEBUG BUGOUT("looping in gaptelnet"); #endif ibits = obits = 0; /* * Never look for input if there's still * stuff in the corresponding output buffer */ if (nfrontp - nbackp || pcc > 0) obits |= (1 << net); else ibits |= (1 << pty); if (pfrontp - pbackp || ncc > 0) obits |= (1 << pty); else ibits |= (1 << net); if (ncc < 0 && pcc < 0) break; timeout.tv_sec = 14400; /* 4 hrs. */ timeout.tv_usec = 0; select(16, &ibits, &obits, 0, &timeout); if (ibits == 0 && obits == 0) { /* timeout means no activity for a long time */ #ifdef DEBUG BUGOUT("timeout from select"); #endif if (keepalives++ < 2) { /* first time through send warning */ if (nfrontp == nbackp && pcc == 0) { /* but only if not blocked on output */ #define WARNING "\r\nYou've been idle much too long. Respond or log off.\r\n" strcpy(nfrontp, WARNING); nfrontp += sizeof(WARNING); } sleep(5); continue; } #ifdef DEBUG BUGOUT("keepalive expired -- calling cleanup"); #endif /* keepalive count has expired */ cleanup(); return(1); } /* * Something to read from the network... */ if (ibits & (1 << net)) { ncc = read(net, buf, sizeof(buf)); #ifdef DEBUG BUGOUT("read %d from net, cc=0%o,dt=0%o", ncc-sizeof(struct sphdr), si->sp_cc, si->sp_dt); #endif if (ncc < 0 && errno == EWOULDBLOCK) ncc = 0; else if (ncc < sizeof(struct sphdr)) { #ifdef DEBUG BUGOUT("short read, %d. calling cleanup",ncc); #endif cleanup(); /* will probably fail or block */ return(1); } else if (si->sp_cc & SP_OB) { #ifdef DEBUG BUGOUT("Got OOB control 0%o", (u_char) buf[sizeof(struct sphdr)]); #endif /* a status or OOB control */ switch ((u_char) buf[sizeof(struct sphdr)]) { case GAPCTLinterrupt: /* shove interrupt char in buffer */ interrupt(); break; /* from switch */ case GAPCTLareYouThere: sendoobdata(GAPCTLiAmHere); break; /* from switch */ default: /* Ignore other controls instead of: * sendoobdata( * GAPCTLunexpectedRemoteBehavior); */ break; /* from switch */ } ncc = 0; /* no chars here */ } else if (si->sp_dt==GAPCTLnone || si->sp_dt==0) { /* the normal case */ /* N.B. the standard says dt should be 0300 * i.e. GAPCTLnone, but Interlisp CHAT * generates 0, so we accept that too. */ ncc -= sizeof(struct sphdr); #ifdef DEBUG BUGOUT("Now ncc == %d",ncc); #endif netip = buf + sizeof(struct sphdr); keepalives = 0; } else if(si->sp_dt==GAPCTLcleanup) { #ifdef DEBUG BUGOUT("got CLEANUP packet. Done"); #endif cleanup(); /* normal termination */ return(0); } else if (si->sp_dt==SPPSST_END) { /* got premature termination */ quitquit(net, pty); return(1); } else { /* some other inband ctl */ #ifdef DEBUG BUGOUT("ignoring IB packet, data = 0%o...", (u_char) buf[sizeof(struct sphdr)]); #endif } } /* * Something to read from the pty... */ if (ibits & (1 << pty)) { if (frametimeout > 0) sleep(frametimeout); pcc = read(pty, ptyibuf, sizeof(ptyibuf)); #ifdef DEBUG BUGOUT("read from pty %d",pcc); #endif if (pcc < 0 && errno == EWOULDBLOCK) pcc = 0; else if (pcc <= 0) { #ifdef DEBUG BUGOUT("short read from pty. Calling cleanup"); #endif cleanup(); return(1); /* ?? abnormal termination */ } ptyip = ptyibuf; } while (pcc > 0) { if ((&netobuf[sizeof(netobuf)] - nfrontp) < 2) break; *nfrontp++ = *ptyip++ & 0377; pcc--; } if ((obits & (1 << net)) && (nfrontp - nbackp) > 0) netflush(); while (ncc > 0) { if ((&ptyobuf[sizeof(ptyobuf)] - pfrontp) < 2) break; *pfrontp++ = *netip++ & 0377; ncc--; } if ((obits & (1 << pty)) && (pfrontp - pbackp) > 0) ptyflush(); } /* we should never get to here */ #ifdef DEBUG BUGOUT("broke out of for(;;) somehow. calling cleanup"); #endif cleanup(); return(0); } /* * Send out of band data to other end of network */ sendoobdata(value) u_char value; { struct { struct sphdr hdr; char val; } oob; oob.hdr = our_sphdr; oob.val = value; #ifdef DEBUG BUGOUT("sendoobdata 0%o",value); #endif send(net, &oob, sizeof(oob), MSG_OOB); } /* * Send interrupt to process on other side of pty. * If it is in raw mode, just write NULL; * otherwise, write intr char. */ interrupt() { struct sgttyb b; struct tchars tchars; ptyflush(); /* half-hearted */ ioctl(pty, TIOCGETP, &b); if (b.sg_flags & RAW) { *pfrontp++ = '\0'; return; } *pfrontp++ = ioctl(pty, TIOCGETC, &tchars) < 0 ? '\177' : tchars.t_intrc; } ptyflush() { register int n; if ((n = pfrontp - pbackp) > 0) n = write(pty, pbackp, n); #ifdef DEBUG BUGOUT("ptyflush wrote %d",n); #endif if (n < 0) return; pbackp += n; if (pbackp >= pfrontp) /* actually, > is an error */ pbackp = pfrontp = ptyobuf; } netflush() { register int n; if ((n = nfrontp - nbackp) > 0) { our_iovec[1].iov_len = ((n > SPPMAXDATA) ? SPPMAXDATA : n); our_iovec[1].iov_base = nbackp; n = writev(net, our_iovec, 2) - sizeof(struct sphdr); } #ifdef DEBUG BUGOUT("netflush wrote %d",n); if (our_iovec[0].iov_base != (char*)&our_sphdr) BUGOUT("Oops: our_iovec clobbered"); BUGOUT("header: %d %d, %d %d %d %d %d %d", our_sphdr.sp_cc, our_sphdr.sp_dt, our_sphdr.sp_sid, our_sphdr.sp_did, our_sphdr.sp_seq, our_sphdr.sp_ack, our_sphdr.sp_alo); #endif if (n < 0) { if (errno == EWOULDBLOCK) return; /* should blow this guy away... */ return; } nbackp += n; if (nbackp >= nfrontp) /* actually , > is an error */ nbackp = nfrontp = netobuf; } /* * handle receipt of an SPPSST_END packet * This is currently an error, since client didn't send "cleanup" first */ quitquit() { #ifdef DEBUG BUGOUT("quitquit"); #endif changeSPPopts(net, SPPSST_ENDREPLY, 1); write(net, &our_sphdr, sizeof(our_sphdr)); sleep(3); rmut(); vhangup(); /* XXX */ shutdown(net, 1); close(net); } /* * shut down the data connection for one reason or another */ cleanup() { int fdmask; struct timeval timeout; struct sphdr *si = (struct sphdr *)buf; int off = 0; signal(SIGCHLD, SIG_IGN); sendoobdata(GAPCTLcleanup); changeSPPopts(net, SPPSST_END, 1); if (write(net, &our_sphdr, sizeof(our_sphdr)) < 0) { fdmask = 1< 0 && read(net,buf,sizeof(buf)) >= sizeof(struct sphdr)) { #ifdef DEBUG BUGOUT("cleanup -- got packet"); #endif if ((!(si->sp_cc & SP_OB)) && si->sp_dt == SPPSST_ENDREPLY) { changeSPPopts(net, SPPSST_ENDREPLY, 1); write(net, &our_sphdr, sizeof(our_sphdr)); #ifdef DEBUG BUGOUT("cleanup -- wrote ENDREPLY"); #endif sleep(1); changeSPPopts(net,0,0); ioctl(net, FIONBIO, &off); rmut(); vhangup(); /* XXX */ return; } /* loop: ignore everything except ENDREPLY */ fdmask = 1< struct utmp wtmp; char wtmpf[] = "/usr/adm/wtmp"; char utmp[] = "/etc/utmp"; #define SCPYN(a, b) strncpy(a, b, sizeof (a)) #define SCMPN(a, b) strncmp(a, b, sizeof (a)) rmut() { register f; int found = 0; f = open(utmp, 2); if (f >= 0) { while(read(f, (char *)&wtmp, sizeof (wtmp)) == sizeof (wtmp)) { if (SCMPN(wtmp.ut_line, line+5) || wtmp.ut_name[0]==0) continue; lseek(f, -(long)sizeof (wtmp), 1); SCPYN(wtmp.ut_name, ""); SCPYN(wtmp.ut_host, ""); time(&wtmp.ut_time); write(f, (char *)&wtmp, sizeof (wtmp)); found++; } close(f); } if (found) { f = open(wtmpf, 1); if (f >= 0) { SCPYN(wtmp.ut_line, line+5); SCPYN(wtmp.ut_name, ""); SCPYN(wtmp.ut_host, ""); time(&wtmp.ut_time); lseek(f, (long)0, 2); write(f, (char *)&wtmp, sizeof (wtmp)); close(f); } } chmod(line, 0666); chown(line, 0, 0); line[strlen("/dev/")] = 'p'; chmod(line, 0666); chown(line, 0, 0); } /* * Convert network-format xns address * to ascii * --Replace this with a clearinghouse name lookup someday. */ char * wsname(addr) struct ns_addr addr; { static char b[50]; char temp[10]; int i; /* net */ sprintf(b, "%D.", ntohl(ns_netof(addr))); /* skip leading zeros */ for(i=0; (addr.x_host.c_host[i] == (char) 0); i++) ; /* print the rest */ for(; i < 6; i++) { sprintf(temp,"%x", addr.x_host.c_host[i]); strcat(b, temp); if(i != 5) strcat(b, ":"); } return (b); } /* * generate an xns address that "DE" can parse. * This goes in the environment. Should be the same as above */ char * xntoa(addr) struct ns_addr addr; { static char b[50]; char temp[10]; int i; /* net */ sprintf(b, "%X#", ntohl(ns_netof(addr))); /* print the rest */ for(i=0; i < 6; i++) { sprintf(temp,"%x", addr.x_host.c_host[i]); strcat(b, temp); if(i != 5) strcat(b, "."); } return (b); } #ifdef DEBUG BUGOUT(str,a,b,c,d,e,f,g,h) char *str; { FILE *fd; fd = fopen("/tmp/GAP3d.log","a"); fprintf(fd,str,a,b,c,d,e,f,g,h); putc('\n',fd); fclose(fd); } #endif