home *** CD-ROM | disk | FTP | other *** search
- /*
- ** S E R V E R C O M M
- **
- ** Title: servercomm (formerly annexf)
- ** Original Author: John Sloan (internet: jsloan@SPOTS.Wright.Edu)
- ** System: SunOS
- ** Language: C
- ** Date: April 1988
- ** Abstract
- **
- ** Servercomm is a filter for the Berkeley spooler daemon that
- ** establishes a bidirectional TCP/IP/TELNET connection to
- ** a serial port on an terminal server made by popular makers
- ** so that printouts can be spooled to a serial printer connected
- ** to the server. It is intended to be used with an Apple
- ** Laserwriter and the Adobe Transcript package, but could
- ** be used for other things as well. It can also be configured
- ** at run time to talk to a /dev device instead of the terminal server
- ++
- ++ Modified by Jim Warner to work with the service listener
- ++ port on Bridge Communications terminal servers. The dialog
- ++ in handshake() is unnecessary. The "port" number was the physical
- ++ connector on the back of the annex. For Bridge (and Cisco, I bet)
- ++ this is the tcp-port number to use instead of telnet=23. For
- ++ Bridge, we're going to need an IP address per printer.
- ++ Jan 2 1989
- ==
- == Modified by Dinah Anderson <dinah@bcm.tmc.edu> to use the
- == sys/termios.h data structure instead of the sys/ioctl.h.
- == Also set the ptys characteristics explicitly since most postscript
- == files would not print. We tested this using a Bridge server.
- == 8/23/89.
- **
- ** Disclaimer
- **
- ** This software is supplied as is, and no warranty is expressed or
- ** implied as to its correctness, functionality, or suitability
- ** for any application, anywhere, at anytime, by anyone.
- **
- ** Acknowledgements
- **
- ** Much of this code was inspired by telnet.c from UCB and aprint.c
- ** from Encore Computer. No code borrowed, but a lot of questions
- ** were answered by perusing these examples. Also, many thanks to
- ** Ken Yap <ken@cs.rochester.edu>, who suggested using a pty/tty
- ** pair to communicate with pscomm. I was working on that when I
- ** received his email, and it was encouraging to hear someone else
- ** that thought it should work.
- **
- ** Compile Time Flags
- **
- ** DEBUG Compiles in debugging code to make
- ** useful remarks on stderr. Use the
- ** -v run time flag to activate.
- **
- ** DUMP Compiles in code to dump data to stderr
- ** as it's being processed. Very verbose,
- ** but useful in debugging. Use the -x run
- ** time flag to activate.
- **
- ** BRIDGE Compiles in code to support CISCO or BRIDGE
- ** communications servers.
- **
- */
- /*
- ** To do:
- ** The pseudottys should not have to be specified. The program
- ** should find the first available ones and use them.
- **
- */
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/file.h>
- #include <sys/termios.h>
- #include <ctype.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <setjmp.h>
- #include <signal.h>
- #include <netdb.h>
- #include <netinet/in.h>
- #define TELOPTS
- #include <arpa/telnet.h>
- #include <arpa/inet.h>
- #include <sys/socket.h>
- #include <sys/wait.h>
- #include <strings.h>
-
- #define LPD_OK 0 /* lpd filter exit codes */
- #define LPD_RETRY 1
- #define LPD_ABORT 2
- #define BUFFER 512 /* spooler buffer size in bytes */
- #define CR '\015'
- #define PROMPT ':'
-
- #ifdef DEBUG
- #define DPRINTF(s) if (debugon) fprintf s
- #else
- #define DPRINTF(s)
- #endif
-
- /*
- ** Identification
- */
- static char id[]="$Header: /home/crick/vpit/sob/src/servercomm/RCS/servercomm.c,v 1.1 89/08/06 00:10:36 sob Exp Locker: sob $";
-
- /*
- ** Must be static (used by network code).
- */
- struct servent *srvc;
- struct hostent *host;
- struct sockaddr_in sock;
- struct sockaddr dev;
- struct termios ttycb;
- jmp_buf context;
-
- /*
- ** Must be global (used by interrupt service routines).
- */
- #ifndef errno
- extern int errno; /* warner: not needed for Sun OS */
- #endif
- char *pgmname;
- int filterpid,prodpid,conspid;
- int netfd,ptyfd,ttyfd;
- int debugon,dumpon;
-
- void setup(),cleanup(),handshake(),trigger(),rendezvous();
- void spool(),transmit(),usage(),bailout(),fatal();
- void lost(),interrupted(),finished();
- #ifdef DEBUG
- void pargs();
- #endif
-
- main(argc,argv)
- int argc;
- char *argv[];
- {
- int nargc,rc;
- char *host,*port,*device,*filter,*pty,*tty,**nargv;
-
- pgmname=rindex(argv[0],'/');
- pgmname=pgmname?pgmname+1:argv[0];
- setup(argc,argv,&debugon,&dumpon,&host,&port,&device,&pty,&tty,&filter,&nargc,&nargv);
- filterpid=0;
- prodpid=0;
- conspid=0;
- if (pty!=NULL)
- {
- ptyfd=acquire(pty);
- ttyfd=acquire(tty);
- if ((rc=ioctl(ttyfd,TCGETS,&ttycb))<0)
- fatal(rc,"main ioctl getp failed",LPD_ABORT);
- ttycb.c_cflag = (B9600 | CS7 | CREAD );
- ttycb.c_oflag = (OPOST | OCRNL );
- ttycb.c_iflag = (ISTRIP );
- ttycb.c_lflag = (0 );
- if ((rc=ioctl(ttyfd,TCSETS,&ttycb))<0)
- fatal(rc,"main ioctl setp failed",LPD_ABORT);
- }
- else
- ptyfd=0;
- if (host!=NULL)
- netfd=establish(host,port);
- else if (device!=NULL)
- netfd=acquire(device);
- else
- usage(0);
- if (rc=setjmp(context))
- bailout(rc);
- if (port!=NULL)
- handshake(netfd,port);
- if (filter!=NULL)
- {
- /*
- ** Fork off producer and consumer processes
- ** ahead of the filter so that they are ready
- ** to handle the filter's I/O immediately. This
- ** scheme is process intensive (makes several
- ** processes) but is simpler than using non-blocking
- ** I/O, which was the original implementation.
- */
- spool(netfd,ptyfd,&prodpid,&conspid);
- filterpid=attach(filter,nargc,nargv,ttyfd);
- /*
- ** Don't let children inherit our interrupt
- ** service routines. This naively assumes that
- ** nothing interesting will happen prior to now.
- */
- signal(SIGPIPE,lost);
- signal(SIGINT,interrupted);
- /*
- ** Wait for something interesting to happen.
- */
- rendezvous(filterpid,prodpid,conspid);
- DPRINTF((stderr,"%s: main complete\n",pgmname));
- exit(LPD_OK);
- }
- }
-
- /*
- ** Setup: parses argument list.
- */
- void
- setup(argc,argv,dbgon,dmpon,host,port,device,pty,tty,filter,nargc,nargv)
- int argc,*dbgon,*dmpon,*nargc;
- char *argv[],**host,**port,**device,**pty,**tty,**filter,***nargv;
- {
-
- *dbgon=0;
- *dmpon=0;
- *host=NULL;
- *port=NULL;
- *device=NULL;
- *pty=NULL;
- *tty=NULL;
- *filter=NULL;
- *nargc=0;
- *nargv=NULL;
- while (argc>0)
- {
- if (argv[0][0]=='-')
- {
- switch (argv[0][1])
- {
- case 'v':
- *dbgon=1;
- DPRINTF((stderr,"%s: debug on\n",pgmname));
- break;
- case 'x':
- *dmpon=1;
- DPRINTF((stderr,"%s: dump on\n",pgmname));
- break;
- case 'h':
- usage(--argc);
- *host=(*++argv);
- DPRINTF((stderr,"%s: setup host=%s\n",pgmname,*host));
- break;
- case 'p':
- usage(--argc);
- *port=(*++argv);
- DPRINTF((stderr,"%s: setup port=%s\n",pgmname,*port));
- break;
- case 'd':
- usage(--argc);
- *device=(*++argv);
- DPRINTF((stderr,"%s: setup device=%s\n",pgmname,*device));
- break;
- case 't':
- usage(--argc);
- *pty=(*++argv);
- usage(--argc);
- *tty=(*++argv);
- DPRINTF((stderr,"%s: setup pty=%s, tty=%s\n",pgmname,*pty,*tty));
- break;
- case 'f':
- usage(--argc);
- *filter=(*++argv);
- *nargv=argv;
- *nargc=argc;
- argc=0;
- DPRINTF((stderr,"%s: setup filter=%s, argc=%d\n",pgmname,*filter,*nargc));
- break;
- default:
- usage(0);
- }
- }
- argv++;
- argc--;
- }
- }
-
- /*
- ** Cleanup: Shutdown child processes, close files, do anything else
- ** necessary to gracefully complete in the event of an error.
- */
- void
- cleanup()
- {
- /*
- ** Filter may not have been started up yet.
- */
- if (filterpid)
- {
- kill(filterpid,SIGTERM);
- DPRINTF((stderr,"%s: cleanup killed filter %d\n",pgmname,filterpid));
- }
- /*
- ** Producer may not have been started up yet.
- */
- if (prodpid)
- {
- kill(prodpid,SIGTERM);
- DPRINTF((stderr,"%s: cleanup killed spooler %d\n",pgmname,prodpid));
- }
- /*
- ** Consumer may not have been started up yet.
- */
- if (conspid)
- {
- kill(conspid,SIGTERM);
- DPRINTF((stderr,"%s: cleanup killed spooler %d\n",pgmname,conspid));
- }
- /*
- ** These may be already closed, in which case close returns an
- ** error we can safely ignore.
- */
- close(ptyfd);
- close(ttyfd);
- close(netfd);
- }
-
- /*
- ** Establish: Given a host name or an IP address, establish a real
- ** time TCP/IP/TELNET connection to the specified Annex.
- */
- int
- establish(hostname,port)
- char *hostname, *port;
- {
- char *name;
- int rc,netfd;
-
- if ((srvc=getservbyname("telnet","tcp"))==0)
- fatal(srvc,"establish getservbyname failed",LPD_ABORT);
- if (host=gethostbyname(hostname))
- {
- /*
- ** hostname is an alphanumeric name in /etc/hosts
- */
- DPRINTF((stderr,"%s: got hostname\n",pgmname));
- sock.sin_family=host->h_addrtype;
- bcopy(host->h_addr,(caddr_t)&sock.sin_addr,host->h_length);
- name=host->h_name;
- }
- else if ((sock.sin_addr.s_addr=inet_addr(hostname))!=-1)
- {
- /*
- ** hostname is a numeric IP address
- */
- DPRINTF((stderr,"%s: got IP address\n",pgmname));
- sock.sin_family=AF_INET;
- name=hostname;
- }
- else
- /*
- ** hostname is not anything useful
- */
- fatal(sock.sin_addr.s_addr,"establish hostname failed",LPD_ABORT);
- #ifndef BRIDGE
- sock.sin_port=srvc->s_port;
- #else
- sock.sin_port= htons ( atoi (port));
- DPRINTF ((stderr,"%s: tcp port %d\n",pgmname, atoi (port) ));
- #endif
- if ((netfd=socket(AF_INET,SOCK_STREAM,0,0))<0)
- fatal(netfd,"establish socket failed",LPD_ABORT);
- if ((rc=connect(netfd,(caddr_t)&sock,sizeof(sock),0))<0)
- fatal(rc,"establish connect failed",LPD_ABORT);
- DPRINTF((stderr,"%s: establish host=%s, socket=%d\n",pgmname,name,netfd));
- return(netfd);
- }
-
- /*
- ** Handshake: Given a TCP/IP/TELNET connection to an Annex,
- ** carry out handshaking necessary to connect to the specified
- ** serial port. This is clearly kludgy, and should be replaced
- ** with Encore's Annex R4.0.
- **
- */
- void
- handshake(netfd,port)
- int netfd;
- char *port;
- {
- int rc;
-
- DPRINTF((stderr,"%s: handshake fd=%d, port=%s\n",pgmname,netfd,port));
- #ifndef BRIDGE
- transmit(netfd,"\r",1);
- trigger(netfd,':');
- trigger(netfd,':');
- transmit(netfd,port,strlen(port));
- transmit(netfd,"\r",1);
- trigger(netfd,'\n');
- trigger(netfd,'\n');
- #endif
- }
-
- /*
- ** Trigger: Scan the data from a specified file descriptor until
- ** the given trigger character is encountered. Hey, is this kludgey
- ** or what? But it keeps handshake and the comm server synchronized
- ** (providing no characters are lost), and keeps pscomm from seeing
- ** any of the prompts from the annex. Both trigger and handshake
- ** will be obsolesced by Encore's Annex R4.0, which has a better
- ** reverse telnet mechanism.
- */
- void
- trigger(fd,ch)
- int fd;
- char ch;
- {
- int count;
- char buf;
-
- do
- {
- if ((count=receive(fd,&buf,1))!=1)
- fatal(count,"trigger receive would block",LPD_ABORT);
- #ifdef DEBUG
- if (debugon) putc(buf,stderr);
- #endif
- }
- while (buf!=ch);
- }
-
- /*
- ** Attach: Given a connection to a particular serial port on an
- ** terminal server, invoke an appropriate printer filter to handle the
- ** input/output.
- */
- int
- attach(filter,argc,argv,prtfd)
- int argc,prtfd;
- char *filter,**argv;
- {
- int rc,pid;
-
- if ((pid=fork())<0)
- fatal(pid,"attach fork failed",LPD_ABORT);
- else
- if (pid)
- {
- /*
- ** Child owns the prtfd now.
- */
- close(prtfd);
- DPRINTF((stderr,"%s: attach filter=%s, prtfd=%d, pid=%d\n",pgmname,filter,prtfd,pid));
- #ifdef DEBUG
- if (debugon) pargs(argc,argv);
- #endif
- return(pid);
- }
- else
- {
- /*
- ** stdout gets connected to the tty end of pty
- ** so pscomm sees a tty devices as its output.
- ** stdin remains connected to lpd output.
- */
- close(1);
- if ((rc=dup2(prtfd,1))==-1)
- fatal(rc,"attach dup2 failed",LPD_ABORT);
- if ((rc=execvp(filter,argv))==-1)
- fatal(rc,"attach execvp failed",LPD_ABORT);
- /*
- ** Never reached.
- */
- }
- }
-
- /*
- ** Rendezvous: waits for the child process (the true printer
- ** filter) to complete.
- */
- void
- rendezvous(filterpid,prodpid,conspid)
- int filterpid,prodpid,conspid;
- {
- int wpid,rc;
- union wait status;
-
- do
- wpid=wait(&status);
- while ((wpid!=filterpid)&&(wpid!=-1));
- if (wpid==-1)
- fatal(wpid,"rendezvous wait failed",LPD_ABORT);
- rc=status.w_retcode;
- if (rc!=LPD_OK)
- fatal(rc,"rendezvous filter failed",rc);
- DPRINTF((stderr,"%s: rendezvous wpid=%d, retcode=%d\n",pgmname,wpid,rc));
- /*
- ** These may have already returned, in which case the kill
- ** returns an error we can safely ignore.
- */
- kill(prodpid,SIGTERM);
- kill(conspid,SIGTERM);
- }
-
- /*
- ** Acquire: open a device and return the file descriptor.
- */
- int
- acquire(dev)
- char *dev;
- {
- int fd;
-
- if ((fd=open(dev,O_RDWR))==-1)
- fatal(fd,"acquire open failed",LPD_ABORT);
- DPRINTF((stderr,"%s: acquire dev=%s, fd=%d\n",pgmname,dev,fd));
- return(fd);
- }
-
- /*
- ** Spool: set up the net and pty connections and fork off
- ** children to do the actual work.
- */
- void
- spool(netfd,ptyfd,prodpid,conspid)
- int netfd,ptyfd,*prodpid,*conspid;
- {
- int rc;
-
- if ((*conspid=fork())<0)
- fatal(*conspid,"spool consumer fork failed",LPD_ABORT);
- if (!*conspid)
- {
- rc=process(netfd,ptyfd);
- /*
- ** Release fds now that consumer is complete.
- */
- close(netfd);
- close(ptyfd);
- exit(rc);
- }
- DPRINTF((stderr,"%s: spool %d->%d=%d\n",pgmname,netfd,ptyfd,*conspid));
- if ((*prodpid=fork())<0)
- fatal(*prodpid,"spool producer fork failed",LPD_ABORT);
- if (!*prodpid)
- {
- rc=process(ptyfd,netfd);
- /*
- ** Release fds now that producer is complete.
- */
- close(ptyfd);
- close(netfd);
- exit(rc);
- }
- /*
- ** Producer and consumer children own the fds now.
- */
- close(ptyfd);
- close(netfd);
- DPRINTF((stderr,"%s: spool %d->%d=%d\n",pgmname,ptyfd,netfd,*prodpid));
- }
-
- /*
- ** Process: copy from input fd to output fd until end of file.
- */
- int
- process(infd,outfd)
- {
- int len;
- char buf[BUFFER+1];
-
- do
- if ((len=receive(infd,buf,sizeof(buf)))>0)
- {
- transmit(outfd,buf,len);
- #ifdef DUMP
- if (dumpon)
- {
- buf[len]='\0';
- fputs(buf,stderr);
- }
- #endif
- }
- while (len>0);
- DPRINTF((stderr,"%s: process %d->%d=%d\n",pgmname,infd,outfd,len));
- return(len);
- }
-
- /*
- ** Transmit: send a buffer of a given length to a
- ** specified file descriptor.
- */
- void
- transmit(fd,buf,len)
- int fd,len;
- char *buf;
- {
- int count;
-
- while (len>0)
- if ((count=write(fd,buf,len))<0)
- {
- if (errno!=EWOULDBLOCK)
- fatal(count,"transmit write failed",LPD_ABORT);
- }
- else
- {
- buf+=count;
- len-=count;
- }
- }
-
- /*
- ** Receive: Receive a specified amount of data from a given
- ** file descriptor and place it in the specied buffer.
- */
- int
- receive(fd,buf,len)
- int fd,len;
- char *buf;
- {
- int count;
-
- if (len<=0)
- return(len);
- if ((count=read(fd,buf,len))<0)
- {
- if (errno!=EWOULDBLOCK)
- fatal(count,"receive read failed",LPD_ABORT);
- }
- else
- if (count>len)
- fatal(count,"receive read overflow",LPD_ABORT);
- return(count);
- }
-
- /*
- ** Bailout: handle interrupts while attempting to exit
- ** as gracefully and politely as possible.
- */
- void
- bailout(rc)
- int rc;
- {
- switch(rc)
- {
- case 1:
- signal(SIGPIPE,SIG_DFL);
- fatal(rc,"broken pipe",LPD_ABORT);
- break;
- case 2:
- signal(SIGINT,SIG_DFL);
- fatal(rc,"aborted",LPD_ABORT);
- break;
- default:
- fatal(rc,"unknown signal",LPD_ABORT);
- }
- }
-
- /*
- ** Fatal: Prints the system error message, prints a user
- ** error message, and exits with the specified code.
- */
- void
- fatal(rc,msg,xit)
- int rc,xit;
- char *msg;
- {
- /*
- ** Naively assume that if rc<=0, it's a system call
- ** return, and perror is valid; otherwise, perror is
- ** misleading.
- */
- if (rc<=0)
- perror("annexf");
- fprintf(stderr,"%s: %s (%d)\n",pgmname,msg,rc);
- cleanup();
- exit(xit);
- }
-
- /*
- ** Usage: check argc, and report usage error.
- */
- void
- usage(argc)
- int argc;
- {
- if (argc <= 0)
- {
- fprintf(stderr,"usage: %s [-v] [-x] [[-h host [-p port]]|[-d device]] [-t pty tty] [-f filter [args]]\n",pgmname);
- cleanup();
- exit(LPD_ABORT);
- }
- }
-
- /*
- ** Lost: Interrupt service routine which is invoked when
- ** a pipe breaks and a connection is lost.
- */
- void
- lost()
- {
- longjmp(context,1);
- }
-
- /*
- ** Interrupted: Interrupt service routine which is invoked
- ** when a interrupt signal is received from another process
- ** (typically the line printer spooler daemon).
- */
- void
- interrupted()
- {
- longjmp(context,2);
- }
-
- /*
- ** Finished: Interrupt service routine which is invoked
- ** when the parent receives end of file and signals the
- ** child process to terminate.
- */
- void
- finished()
- {
- longjmp(context,3);
- }
-
- #ifdef DEBUG
- /*
- ** Pargs: prints argc and argv argument lists for debugging.
- */
- void
- pargs(argc,argv)
- int argc;
- char **argv;
- {
- int nargc;
-
- fprintf(stderr,"%s:",pgmname);
- for (nargc=0;argc>0;argc--,nargc++,argv++)
- {
- putc(' ',stderr);
- fputs(*argv,stderr);
- }
- putc('\n',stderr);
- }
- #endif
-