[comp.protocols.tcp-ip] Printing to a terminal server

jbreeden@netcom.COM (John Breeden) (06/04/91)

Does anyone know of a piece of code that will allow a Unix print queue
to be redirected to a terminal server via telnet (like the peice of
code that xyplex supplies with their terminal servers?). I'm trying to
provide the same type of print services with a non-xyplex terminal
server, hoping that someone has already invented this wheel).

Thanx in advance;

-- 
 John Robert Breeden, 
    jbreeden@netcom.com, apple!netcom!jbreeden, ATTMAIL:!jbreeden
 -------------------------------------------------------------------
 "The nice thing about standards is that you have so many to choose 
  from. If you don't like any of them, you just wait for next year's 
  model."

poorman@convex.com (Peter W. Poorman) (06/04/91)

In <1991Jun3.183905.12329@netcom.COM> jbreeden@netcom.COM (John Breeden) writes:

>Does anyone know of a piece of code that will allow a Unix print queue
>to be redirected to a terminal server via telnet [...]

Check out "tcpf".  Available as C source for UNIX from "ftp.cisco.com".

This generally relies upon the capability of CISCO routers to open a raw
TCP stream to a terminal port.  It ought to work with other brands as well,
assuming they have a similar capability.

--Pete
  poorman@convex.com

c_bstratton@HNS.COM (Bob Stratton) (06/04/91)

   Date: 3 Jun 91 18:39:05 GMT
   From: netcomsv!jbreeden@apple.com  (John Breeden)

   Does anyone know of a piece of code that will allow a Unix print queue
   to be redirected to a terminal server via telnet (like the peice of
   code that xyplex supplies with their terminal servers?). I'm trying to
   provide the same type of print services with a non-xyplex terminal
   server, hoping that someone has already invented this wheel).

Look in cisco's anonymous FTP area...I seem to remember some
non-system specific hacks to do similar things in there...

Bob Stratton           | 
Stratton Systems Design| SMTP: strat@gnu.ai.mit.edu, c_bstratton@hns.com
Alexandria, Virginia   | PSTN: +1 301 409 2703
"Personally, I think the DNS administrative interface was designed by the IRS."
							--Mark Beyer

rlstewart@eng.xyplex.com (Bob Stewart) (06/05/91)

Sorry, list.  I tried to reply directly to John, but the mailers couldn't sort
out his return address.  Besides, this is sort of general...

You may get close, but you won't quite be able to duplicate what we do as we
have a registration and call-back protocol that is unique to us (as far as I
know).  You might be able to set up something that will connect from the Unix
end and send, if the server is available, or you might be able to get it to
work with a permanent type connection.  The dynamic sharing with queued call
back is a bit harder, requiring cooperation from the terminal server.

I probably can't help you more than this, as I've pretty well exhausted my
store of knowledge on the subject, and we can only go so far in supporting
somebody else's terminal server.  :-)

	Bob

rosen@rosen.enet.dec.com ("Eric C. Rosen 13-Jun-1991 0926") (06/13/91)

	This is in response to John Breeden's question about Unix software
that facilitates printing via telnet to a terminal server port.  I've recently
written a print filter that does just that, and I am including its source code
here for anyone that would like to try it out.

	I wrote this with the DECserver 300 in mind, but it should work with
any terminal server that is capable of accepting incoming connections and
mapping them to particular physical ports.  It does not use any proprietary or
vendor-specific protocols.  The filter does assume that the terminal server
will initiate at least one Telnet negotiation after connection setup, but this
would be easy to change if necessary.

	The introductory comments below explain how to set up the printcap 
file so as to invoke this filter properly.  I've tested this on Ultrix, on
MIPS' RISC/os (running with BSD compatibility), and on a 1986 version of
SUN/os.  However, I haven't tested it with other vendors' terminal servers.

	If anyone cares to try this out, please contact me directly with any
comments or problems.

Eric Rosen


/*
  This program is used to enable the "lpr" command to cause data to be sent
  to a printer that is attached to a Telnet terminal server port.

  The program is offered only as a "proof of concept"; its testing has not
  been extensive, it cannot be claimed to be "production quality software",
  and its author's Unix network programming skills are somewhat rusty.

  The procedures for using this program are somewhat different, depending on
  whether it is to be run on an Ultrix system or on a different kind of Unix
  system.

NON-ULTRIX SYSTEMS

  On non-Ultrix systems, this program is intended to be called both as an
  output filter and an input filter from lpd.  It should  be specified as the
  argument to both the "of" and the "if" parameter of a printcap entry.

  The program requires command line arguments which are not passed by lpd.
  Many Unix systems do not allow command line arguments to be passed explicitly
  in the "if" and "of" lines of a printcap entry.  The work-around is to have
  the "if" and "of" entries refer to shell scripts.  Within the shell scripts,
  any arguments can be passed.

  For example, the following printcap entry has been used successfully:

  telprt:\
        :lp=/dev/null:\
	:sd=/usr/spool/lpd5:\
	:of=/pathname/output_filter_script:\
	:if=/pathname/input_filter_script:

  The shell script "/pathname/output_filter_script" should be something like
  the following:

  #! /bin/sh
  exec /pathname/thisprogram o printer_host printer_port relay_port

  except that:

        "/pathname/thisprogram" would be replaced by the actual pathname
        and file name of the executable of this program,

	"printer_host" would be replaced by the name or IP address of the
	DECserver 300 to which the printer is attached (if a name is used,
	it can be in /etc/hosts or in the Domain Name Service),

	"printer_port" would be replaced by the TCP Port Number which has
	been assigned to the Telnet Listener on the DS300 which is associated
	with the printer (note that this the the TCP Port Number, not the
	physical port number); this can be a decimal number like 2010, or
	a "service name" which appears in /etc/services

	"relay_port" would be replaced by a TCP Port Number which is not
	otherwise in use on this host system.  A GIVEN PRINTCAP ENTRY
	MUST HAVE A UNIQUE RELAY_PORT NUMBER.

  The shell script "/pathname/input_filter_script" should be something like
  the following:

  #! /bin/sh
  exec /pathname/thisprogram i this_host relay_port

  except that:

        "/pathname/thisprogram" would be replaced by the actual pathname
        and file name of the executable of this program,

        "this_host" would be replaced by the name or IP address of the host
	on which this program is to run (i.e., the host from which we are
	printing, NOT the host to which the printer is attached)

	"relay_port" would be replaced with the SAME VALUE that appears for
	relay_port in output_filter_script.


  So an actual output_filter_script might be:

  #! /bin/sh
  exec /usr/users/rosen/bin/betterfilter o tantn1 2010 3000

  and an actual input_filter_script might be:

  #! /bin/sh
  exec /usr/users/rosen/bin/betterfilter i  dsuser 3000

  I've used these scripts to print from the host called "dsuser" to a printer
  on the DS300 called "tantn1", where Telnet Listner TCP Port Number 2010 on
  that DS300 has been assigned to that printer.

  When you install these shell scripts, there are a few things to remember:

        Make them executable (with "chmod ugo+x" )!

	Get the pathnames right, both in the shell scripts and in the
	printcap entry!

	Don't forget the first command line argument ("i" or "o").

	Make sure the same relay_port number is provided in both shell
	scripts, and that it is not otherwise in use on your host (at least
	check that it does not appear in /etc/services).

	Getting the printcap entry exactly right may take some experimenting.
	Some Unix systems like :lp=/dev/null:, some like :lp=:, and some like
	it if you just omit the "lp" entry entirely.
	
  Once this is all setup, the command "lpr -Ptelprt" will send data to the
  printer which is attached to the terminal server whose name is "tantn1",
  and which has been associated with a Telnet Listener at TCP Port 2010 on
  that terminal server.  It will use the directory /usr/spool/lpd5 as the
  spooling directory.

  Of course, you will replace the "sd" entry in the printcap file with one
  that specifies the spooling directory you actually want to use for this
  printer, and you will make sure that that directory really exists before you
  try to print anything.

  This printcap entry can appear on any number of non-Ultrix Unix systems,
  and those systems will  contend for the use of the printer.  While any one
  system is using the printer, the others will not be able to connect to it.
  In this case, they will retry periodically, using exponential backoff.  The
  parameters:

       BASE_RETRY_INTERVAL, MAX_RETRY_INTERVAL, and MAX_RETRY_TIMES_TO_PRINTER

  in the source code govern the retry algorithm.  These default to 10 seconds,
  with infinite retries at a maximum retry interval of one hour.

  As an alternative, you can set up one system to be the main spooler for the
  printer, and have other systems send their printjobs to the main spooler.
  This is entirely a matter of customer preference.  If the main spooler is
  the system whose name is "prowlr", the other systems would have printcap
  entries like:

  telprt:\
        :lp=:\
        :sd=/usr/spool/lpd3:\
	:rm=prowlr:\
	:rp=telprt:


  The very same printer can also be accessed, using LAT, from VMS systems.
  Once any of the Unix systems succeeds in opening a connection to the printer,
  all files   queued on that system will be printed, and then the connection
  will be closed.

ULTRIX SYSTEMS

  Setting the system up to use this program is a bit simpler if you have an
  Ultrix V4 system.  In this case, the program is only invoked as an output
  filter; however, several additional entries in the printcap file are needed.

  It has been used successfully with printcap entries such as the following:

    telprt:\
        :sd=/usr/spool/lpd5:\
	:ct=network:\
	:uv=psv1.0:\
        :of=/pathname/thisprogram x tantn1 2010:

  With this printcap entry, the command "lpr -Ptelprt" will send data to the
  printer which is attached to the terminal server whose name is "tantn1",
  and which has been associated with a Telnet Listener at TCP Port 2010 on
  that terminal server.  It will use the directory /usr/spool/lpd5 as the
  spooling directory.  (We assume in this example that "/pathname/thisprogram"
  is the executable resulting from compiling this program.)

  Of course, you will replace the "sd" entry in the printcap file with one
  that specifies the spooling directory you actually want to use for this
  printer, and you will make sure that that directory really exists before you
  try to print anything.

  Don't forget the "x" as the first argument to the program in the "of" line
  of the printcap entry, and make sure you get the pathname right.

  This printcap entry can appear on any number of Ultrix systems, and those
  systems will  contend for the use of the printer.  While any one system is
  using the printer, the others will not be able to connect to it. In this
  case, they will retry periodically, using exponential backoff.  The
  algorithms and parameters are the same as those described above for
  non-Ultrix systems.

  Any number of systems should be able to contend for the use of the printer,
  whether they are Ultrix systems, other Unix systems, or VMS systems using
  LAT.

PROGRAM OPERATION

  To describe the way in which this program works, it is necessary to first
  describe the principles of operation of the Unix Line Printer Daemon (lpd).
  First, we will describe its operation on non-Ultrix systems.
  
  When lpd sees that there are files on its queue for the printer, it forks an
  output filter.  It creates a pipe to the output filter, and binds it to the
  output filter's standard input.  It then creates a banner page for the first
  file on the queue, and sends the banner page down the pipe to the output
  filter.  It indicates the end of the banner page by sending a two-byte
  sequence consisting of ascii code 25 followed by ascii code 1.  The daemon
  then waits for the output filter to suspend itself.

  When the output filter detects the end of the banner page, it must suspend
  itself by sending itself SIGSTOP (the equivalent of ^Z in the csh).  When
  lpd detects that the output filter has suspended itself, it forks an input
  filter.  It creates a pipe to the input filter, and binds it to the input
  filter's standard input.  It then reads the first file on the queue from the
  spooling directory, and sends it down the pipe to the input filter.  When
  the file has been completely transmitted, lpd closes the pipe.  It then waits
  for the input filter to exit.

  The input filter knows that it has received the complete file when it detects
  that its standard input has been closed.  When the input filter has finished
  processing the file, it must exit.

  When lpd detects that the input filter has exited, it causes the output
  filter to resume, by sending it SIGCONT.  It then creates a banner page
  for the next file on the queue, and sends it to the output filter.  The
  above procedures are repeated until the queue is empty.  When the queue is
  empty, lpd closes the pipe to the output filter.  This causes the output
  filter to see that its standard input has been closed, at which point the
  output filter exits.

  Note that one  output filter process is used for an entire queue's worth of
  files, but a separate input filter process is used for each individual file.

  Each filter indicates a normal exit by supplying an exit status of 0.  This
  indicates to lpd that the data it gave to the filter was printed success-
  fully.  If either filter supplies an exit status of 1, lpd will leave the
  current file at the head of the queue, and try again later to print it.  If
  the filter which exited with a non-zero status was the input filter, lpd
  will send SIGINT to the output filter.
  
  For the purpose of printing via Telnet to a DS300, we have adopted the
  following design.  Only the output filter actually communicates with the
  printer.  That is, only the output filter opens a Telnet connection to
  the DS300.  Since the output filter process exists until the print queue is
  emptied, the Telnet connection is opened once, and remains open until all
  the files are queued.

  The input filter does not communicate directly with the printer.  Instead,
  any data which it receives from lpd is passed via a "back door" to the
  output filter.  The output filter takes this data from the input filter
  and passes it to the printer via the exissting Telnet connection.  The input
  filter uses a TCP connection to pass the data to the output filter.  That is,
  the input filter makes a TCP connection to its own host, at the specified
  "relay_port".

  Actually, at the time that the input filter wants to send the data to the
  output filter, the output filter is supposed to be suspended.  So before the
  output filter suspends itself, it forks a child process, and it is this child
  to which the input filter actually sends the data.  The child and its parent
  share the Telnet connection to the DS300 with no problems.

  When the output filter first opens the Telnet Connection, it waits for the
  DS300 to initiate Telnet Negotiations.  It then completes these negotiations
  successfully.  It then obtains data, one character at a time, from either lpd
  or from the input filter.  It "telnetizes" that character if necessary, and
  then sends it to the printer.  By "Telnetizing" a character, we mean that it
  doubles characters with ascii code 255, and that it replaces linefeeds with
  carriage return, line feed pairs, as required by the Telnet protocol.  Any
  Telnet negotiations which the terminal server may initiate at any time will
  be properly responded to by the output filter.

  Most of the TCP packets generated will be maximum size, for most efficient
  use of the network.

  If the Telnet connection breaks while printing is in progress, the output
  filter will exist with a status of 1, thereby informing lpd that the
  printing was not successful.  The daemon will then retry the same
  file later; files will not be lost when the Telnet connection breaks.

  The output filter tries to keep the Telnet connection open until all the
  data has been printed.  This maximizes the probably of getting the file
  reprinted automatically if the terminal server or printer fails after the
  last of the data has been transferred, but before printing is complete.
  However, with the DECserver 300, a failure while the last 500 bytes of data
  are being printed may not be recoverable.

  We do assume here that the attached printer needs no processing of the
  data.  If it does, that processing would need to be done first, and the
  result piped into this program.  It should be possible to do this with
  a suitable combination of shell scripts and pipelines.

  On Ultrix systems, with the printcap entry set up as described above for
  Ultrix, the situation is quite a bit simpler.  The Ultrix lpd will invoke
  this program only as an output filter, and will invoke it independently for
  each file.  Both the banner page and the data for each file gets sent to
  a single process.

  This program writes error messages to the syslog, using  the "debug"
  priority.  If the symbol DEBUG1 is defined, messages about the inter-process
  communication are logged.  If the symbol DEBUG2 is defined, messages about
  the number of characters read, written, and internally buffered are written.
*/

#define _BSD
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <syslog.h>
#include <stdio.h>
#include <netdb.h>
#include <ctype.h>
#include <sys/file.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/wait.h>

#define DEBUG1

#define FOREVER while(1)

/* booleans */
#define FALSE 0
#define TRUE 1
#define DONTFLUSH FALSE

/* exit status values */
#define REPRINT 1
#define OKAY 0
#define ENDOFBANNER 2

/*
  How many seconds we will wait before exiting after we close the Telnet
  connection to the printer.  This period should allow all outstanding data
  to be printed, unless the printer is currently XOFF'ing the DS300.
*/  
#define DISCONNECTTIMER 10

/*  Parameters governing retries, if the input filter has trouble connecting
    up with the output filter
*/
#define BASE_RETRY_INTERVAL 10
#define MAX_RETRY_TIMES_TO_PRINTER 0
#define MAX_RETRY_TIMES_TO_OF 10
#define MAX_RETRY_INTERVAL 3600    
  
/* Input and Output Buffer Sizes */
#define INBUFSIZE 1024
#define OUTBUFSIZE (2*INBUFSIZE)    

/* are we running as an input filter or as an output filter */
#define IF 0
#define OF 1
#define UF 2
#define INPUT_FILTER (filtertype == IF)
#define OUTPUT_FILTER (filtertype == OF)
#define ULTRIX_FILTER (filtertype == UF)

/* ascii codes with special significance to Telnet */
#define CR '\r'
#define LF '\n'
#define IAC 255
#define WILL  251
#define WONT 252
#define DO 253
#define DONT 254
#define TIMINGMARK 6
#define BINARY 0

/* state values for responding to Telnet Option Negotiations */
#define DATA 0
#define IACSCAN 1
#define OPTION 2    

/* state values for lpd "end of banner" detection */
#define CTRLY 1    

#define CAN_READ_FROM_LPD (can_read & lpd_mask)
#define CAN_READ_FROM_TCP (can_read & tcp_mask)    

extern int errno;

char *signalnames[] =
    {
     "", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP",
     "SIGIOT", "SIGEMT", "SIGFPE", "SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS",
     "SIGPIPE","SIGALRM", "SIGTERM", "SIGURG", "SIGSTOP", "SIGTSTP", "SIGCONT",
     "SIGCHLD", "SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU", "SIGXFSZ",
     "SIGVTALRM","SIGPROF", "SIGWINCH", "SIGLOST", "SIGUSR1", "SIGUSR2"
    };


int
    tcp_connection = 0,    /* file descriptor representing socket for making
		              outgoing tcp connection; for output filter,
			      connection is to printer, for input filter,
			      connection is to output filter */

    listener,              /* file descriptor representing socket for
			      receiving incoming tcp connection (used only by
			      output filter, while waiting for input filter
			      to make connection */

    lpd_fd = 0,           /* file descriptor for reading characers from lpd;
			     of course, this is the standard input */

    flags,                /* control flags for socket */

    anyaddr = INADDR_ANY, /* for listener, address on which we will receive
			     incoming connections */

    addrlen,              /* for listener, length of anyaddr */

    printer_addr;

int
    lpdstate = DATA,        /* lpd "end of banner detected" state variable */

    telnetstate = DATA,     /* Telnet Option Negotiations state variable */

    binarytransmit = FALSE; /* whether the DS300 has put the Telnet connection
			       into binary transmission mode */
    
int extra_chars;            /* keeps track of extra characters inserted by
			       "Telnetizing" the data */

struct sockaddr_in
    listen_addr,           /* the output filter is listening for connections
			      to this address */
    incoming_addr,         /* the address of the input filter, when it make
			      a TCP connection to the output filter */
    outgoing_addr;         /* in the output filter, this is the address of
			      the printer; in the input filter, this is the
			      address of the output filter */
                   
               
int retrycount,            /* keeps track of number of tries to open TCP
			      connection; used for exponential backoff */

    max_retries,           /* maximum number of attempts to open TCP
			      connection, 0 means no maximum */

    newfile,               /* this is TRUE if we haven't yet sent the printer
			      any characters from the file we are currently
			      trying to print */

    printjobnumber,        /* the number of files that have been printed */

    filtertype;            /* are we running as input filter or as output
			      filter? */

unsigned char answer[3];   /* construct answer to Telnet Option Negotiations */

unsigned char from_net[INBUFSIZE],   /* buffer for holding data received
					from net */
    
              from_lpd[INBUFSIZE],   /* buffer for holding data received from
					lpd */

              to_tcp[OUTBUFSIZE];    /* buffer for holding data to be sent
					to printer */

int to_tcp_start = 0,  /* start of unsent data in to_tcp */
    to_tcp_end = 0,    /* offset for placing NEXT character in to_tcp */
    tcp_data = 0,      /* amount of internally buffered data for TCP */
    tcp_maxseg;        /* TCP Maximum Segment Size; used to ensure that we
			  send maximum size packets when possible */

int tcp_blocking = FALSE;

struct timeval poll; /* used as arg to "select" when we want to poll
			   rather than block */

int lpd_mask,  /* file descriptor mask (for select routine) for data from
	          lpd (standard input) */
    tcp_mask;  /* file description mask (for select routine) for data to/from
		  the tcp connection */

struct servent
    *sp_printer, /* TCP "Service" (Port Number) for outgoing connections (used
		    by output filter to connect to printer, or by input filter
		    to connect to output filter */
    *sp_relay;   /* TCP "Service" (Port Number) for incoming connections (used
		    by output filter to accept connection from input filter */

struct hostent *hp; /* Host Address for outgoing connections */

struct protoent *pp;

/* Signal Handling Routines */
int interruption();
int disconnection();

int child; /* Used by output filter to hold PID of child process which receives
	      data from input filter */

unsigned char iac = IAC,
              cr = CR,
              lf = LF;

main(argc, argv)
    int argc;
    char *argv[];
    {
     int n_read;     /* return value from read call, # of bytes read */
     int n_from_lpd; 

     int i;
     int pid;
     int pr_chr_result, firstchar, lpd_data;

/* Used by output filter while waiting for child process to terminate */
     union wait child_status;
     int *exit_status = (int *)&child_status;

/* masks for the select routine */
     int can_read;

     char *host, *service, *relayservice;


     openlog(argv[0], LOG_PID);
#ifdef DEBUG1
     syslog(LOG_DEBUG, "Telnet Print Filter Starting Up");
#endif
/*
  These signals cause termination with the REPRINT status, so that lpd will
  try again.
*/
     signal(SIGHUP, interruption);
     signal(SIGINT, interruption);
     signal(SIGQUIT, interruption);
     signal(SIGTERM, interruption);

     signal(SIGILL, interruption);
     signal(SIGTRAP, interruption);
     signal(SIGIOT, interruption);
     signal(SIGEMT, interruption);
     signal(SIGFPE, interruption);
     signal(SIGBUS, interruption);
     signal(SIGSEGV, interruption);
     signal(SIGSYS, interruption);
     signal(SIGXCPU, interruption);
/*
  Signals to ignore.
*/
     signal(SIGCONT, SIG_IGN);
     signal(SIGURG, SIG_IGN);
     signal(SIGALRM, SIG_IGN);
     signal(SIGVTALRM, SIG_IGN);
     signal(SIGWINCH, SIG_IGN);
     signal(SIGPROF, SIG_IGN);

/*
  We get this signal if the TCP connection breaks and we then try to write
  to it.  This causes termination with REPRINT status.  We really just ignore
  this signal (except for debugging purposes).
*/
     signal(SIGPIPE, disconnection);

     poll.tv_sec = 0;
     poll.tv_usec = 0;


     if (argc < 2) printhelp();
     switch(argv[1][0])
	 {
	  case 'i':
	      filtertype = IF;
	      break;

	  case 'o':
	      filtertype = OF;
	      break;

	  case 'x':
	      filtertype = UF;
	      break;

	  default:
	      printhelp(); /* terminates program, never returns */
	 }

     switch(filtertype)
	 {
	  case IF:
          case UF:
	      if (argc < 4) printhelp();
	      host = argv[2];
	      service = argv[3];
	      break;

	  case OF:
	      if (argc < 5) printhelp();
	      host = argv[2];
	      service = argv[3];
	      relayservice = argv[4];
	      break;
	 }	  

     closelog();
     openlog(INPUT_FILTER ? "Telnet IF" :
	     (OUTPUT_FILTER ? "Telnet OF" : "Telnet UF"), LOG_PID);
#ifdef DEBUG1
     syslog(LOG_DEBUG, "Started as %s Filter",
	    INPUT_FILTER ? "Input" : (OUTPUT_FILTER ? "Output" : "Ultrix"));
#endif

/*
  Look up host name and service name for outgoing TCP connection, and convert
  them into a destination IP address and a remote TCP Port Number
*/
     init(host, service);
/*
  If running as Output Filter, must set up TCP Port Number on which to listen
  for connection from Input Filter
*/  
     if (OUTPUT_FILTER) init_relay(relayservice);
/*
  Initialize some globals
*/  

     retrycount = 0;
     newfile = TRUE;
     extra_chars = 0;
     printjobnumber = 0;
     max_retries =
	 INPUT_FILTER ? MAX_RETRY_TIMES_TO_OF : MAX_RETRY_TIMES_TO_PRINTER;
     lpd_mask = 1 << lpd_fd;
     firstchar = 0;
     lpd_data = FALSE;

/*
  Create socket for outgoing TCP connection, set it up with destination
  address, and open the TCP connection.
*/  
reconnect:
     make_connection();

/*
  If running as Output Filter or as Ultrix Filter, wait for peer to initiate
  Telnet Option  Negotiations.  We're assuming here that this will happen,
  which is true in the case of the DS300 and most other systems.  This
  prevents us from sending any data in the case where the peer's TCP has
  accepted the connection, but its Telnet is going to refuse it (perhaps
  because the printer is busy).
*/
     if (OUTPUT_FILTER || ULTRIX_FILTER)
	 {
	  can_read = tcp_mask;
	  select(16, &can_read, NULL, NULL, NULL);
	 }
/*
  Now we're ready to enter the main loop.
*/  
     FOREVER
	 {
/*
  Output Filter or Ultrix Filter should hang until there is something to read
  from net; Input Filter shouldn't try to read from net, since it's really
  talking to the output filter.
*/  
	  can_read = lpd_mask;
	  if (OUTPUT_FILTER || ULTRIX_FILTER) can_read |= tcp_mask;
	  select(16, &can_read, NULL, NULL, NULL);

	  if (CAN_READ_FROM_TCP)
	      {
	       n_read = read(tcp_connection, from_net, 1024);
	       if (n_read <= 0)
/*
  Select says that there is data to read, but when we try to read it, we get
  back nothing; this means that the TCP connection has closed unexpectedly.
  If the printing of the file has no yet begun, keep trying to reconnect.  If
  we are in the middle of printing, terminate, and signal lpd that the file
  needs to be reprinted.
*/
		   {
		    syslog(LOG_DEBUG, "TCP Read Error: %m");
		    if (INPUT_FILTER || !newfile) terminate(REPRINT);
		    goto reconnect;
		   }
/*
  We have data from the net.  We will ignore everything except Telnet Option
  negotiations.
*/
	       for (i = 0; i < n_read; ++i) do_options(from_net[i]);
/*
  Continue in this loop until there is no more data to read from the net.
*/
	       continue;
	      }

/*
  If we're here, there is no  data to read from net, but there is data to
  read from lpd.  Before we take it, let's see if we can write it to the
  outgoing TCP connection.  We'll hang in a select until either (a) we can
  write to the outgoing TCP connection, or (b) there's more to read from
  the net.
*/  
	  can_read = wait_on_tcp(OUTPUT_FILTER || ULTRIX_FILTER);

/*
  If there's more to read from the net, let's go handle it immediately.
*/
	  if (can_read) continue;

/*
  If we get here, then we know:

          a) There is nothing to read from the net.
	  b) The lpd daemon has something for us to read.
	  c) It is possible to write to the outgoing TCP connection.

  So let's get the data from lpd and write it to the TCP connection.  We read
  up to 1024 bytes at a time from lpd.  We try to write them out to the TCP
  connection so that maximum size TCP segments are used; this makes the most
  efficient use of network bandwidth.

  We get a character from lpd by reading from standard input, i.e., file
  descriptor 0.  If read returns 0, then lpd has closed the standard input,
  and the print job is complete.

  However, if there is already something in the from_lpd buffer, don't read
  anything new from lpd until we empty out what we already ahve.
*/  
	  if (lpd_data) goto datafromlpd;

	  n_from_lpd = read(lpd_fd, from_lpd, 1024);

	  if (n_from_lpd == 0)
	      {
#ifdef DEBUG1
	       syslog(LOG_DEBUG, "Standard Input Closed");
#endif
#ifdef DEBUG2
	       if (INPUT_FILTER || ULTRIX_FILTER)
		   syslog(LOG_DEBUG,
			  "Telnetizing used %d extra chars", extra_chars);
#endif
	       if (OUTPUT_FILTER || ULTRIX_FILTER) handshake_and_finish();
/*
  Before we let the Input Filter terminate, make sure it's delivered all
  characters to the Output Filter
*/  
	       flush_tcp();
	       terminate(OKAY);
	      }

	  if (n_from_lpd < 0)
	      {
	       syslog(LOG_DEBUG, "LPD Read Error: %m");
	       terminate(REPRINT);
	      }
/*
  We've got characters from lpd.  Go process each character.
*/

#ifdef DEBUG2
	  syslog(LOG_DEBUG, "Read %d chars from lpd", n_from_lpd);
#endif
	  lpd_data = TRUE;
	  newfile = FALSE;
datafromlpd:
	  can_read = NULL;
	  for (pr_chr_result = OKAY;
	       (firstchar < n_from_lpd) && (pr_chr_result == OKAY);
	       ++firstchar)
	      {
	       can_read = wait_on_tcp(OUTPUT_FILTER || ULTRIX_FILTER);
	       if (can_read) break;
	       pr_chr_result = process_character(from_lpd[firstchar]);
	      }

/*
  If we haven't processed all the data from lpd yet, but there's more to read
  from the net; go take care of it.  In this case, firstchar is set to the
  place in from_lpd where we want to resume.

  If (firstchar == n_read), we've emptied the from_lpd buffer, so reset
  firstchar back to 0.
*/  
	  if (firstchar == n_from_lpd)
	      {
	       firstchar = 0;
	       lpd_data = FALSE;
	      }
	  if (can_read) continue;

/*
  If pr_chr_result returns ENDOFBANNER (which should happen only when running
  as an Output Filter),then we have finished printing the banner page.  Now we
  will have to suspend for awhile so that lpd can give the actual file data to
  the Input Filter.  (Of course, the Input Filter will relay it back to us via
  the child process we are about to fork,  but lpd doesn't know that.)

  If pr_chr_result returns anything else, then we just resume getting more
  characters from lpd.
*/  
	  if (pr_chr_result != ENDOFBANNER) continue;
/*
  Before we fork the child process, make sure we've really given TCP all the
  data we've seen so far.
*/  
	  flush_tcp();
#ifdef DEBUG1
	  syslog(LOG_DEBUG, "Suspending Job %d", ++printjobnumber);
#endif
	  
/* Fork a child process to receive the data from the input filter.  The
   communication from the input filter is completely handled in the relay
   subroutine.
	     
   N.B.: The child process NEVER returns from the relay subroutine.
*/
	  child = relay();

/* If we're here, we must be the parent process.  So we suspend ourself, until
   lpd sends us a SIGCONT
*/
	  kill(getpid(), SIGSTOP);

/* Now that we're here, we know that lpd has sent us SIGCONT */
#ifdef DEBUG1
	  syslog(LOG_DEBUG, "Resuming after job %d", printjobnumber);
#endif
/* Don't do anything until we are sure that the child process has completed */
	  pid = wait(&child_status);
	  if (pid != child)
	      {
#ifdef DEBUG1
	       syslog(LOG_DEBUG, "Wrong Child!");
#endif
	       terminate(REPRINT);
	      }
#ifdef DEBUG1
	  syslog(LOG_DEBUG, "Child has Terminated");
#endif

/* If the child's exit status is OKAY, we can go on to the next file to be
   printed.  Otherwise, we exit too, passing the child's exit status up to
   lpd.
*/
	  *exit_status /= 256;
	  *exit_status &= 0xff;
	  if (!WIFEXITED(child_status))
	      {
	       syslog(LOG_DEBUG, "Abnormal Child Termination");
	       terminate(REPRINT);
	      }
	  if (*exit_status != OKAY) terminate(*exit_status);

/* Whew, that file is printed, now we can continue with the next file */
          flush_tcp();
	  newfile = TRUE;
#ifdef DEBUG2
	  syslog(LOG_DEBUG, "Telnetizing generated %d extra chars.",
		 extra_chars);
#endif
	  extra_chars = 0;
	  if (lpd_data) goto datafromlpd;
	 } /* FOREVER */
    }


delay(interval, max_interval, retries, max_retries)
    int interval, /* base retry interval */
	max_interval; /* don't let the interval get longer than this */
    int *retries,  /* the number of attempts we've already made */
	max_retries; /* maximum number of attempts to make, or 0 for no
			limit */
    {
     int later;

     if ((*retries)++ == 0) return; /* first attempt, no delay */
     if (max_retries && (*retries > max_retries))
	 {
	  syslog(LOG_DEBUG, "Exceeded max retries (%d), exiting", max_retries);
	  exit(REPRINT);
	 }
     later = interval << (*retries - 2);
     if (later > max_interval) later = max_interval;
     syslog(LOG_DEBUG, "Will try again in %d seconds", later);
     sleep(later);
    }

/*
  This subroutine is called when we read something from the net.  It ignores
  everything except Telnet Option Negotiations.
*/  
do_options(byte_from_net)
    unsigned char byte_from_net;
    {
     int i, can_write_to_net, n_written;

     switch(telnetstate)
	 {
	  case DATA:
	      /* If IAC found, enter IACSCAN state */
	      if (byte_from_net == IAC) telnetstate = IACSCAN;
	      break;
	  case IACSCAN:
	      /* If WILL, WONT, DO, DONT found, prepare to send back an
		 agreeable answer: enter OPTION state.  Otherwise, we will
		 ignore the Telnet Command, and reenter DATA state. */
	      switch(byte_from_net)
		  {
		   case WILL:
		       answer[1] = DO;
		       break;
		   case DO:
		       answer[1] = WILL;
		       break;
		   case WONT:
		       answer[1] = DONT;
		       break;
		   case DONT:
		       answer[1] = WONT;
		       break;
		   default:
		       telnetstate = DATA;
		       break;
		  }
	      if (telnetstate == IACSCAN)
		  {
		   answer[0] = IAC;
		   telnetstate = OPTION;
		  }
	      break;
	 case OPTION:
	      /* Answer is complete, send it back. */
	      telnetstate = DATA;
	      answer[2] = byte_from_net;
	      if (answer[2] == BINARY)
		  {
		   switch(answer[1])
		       {
		        case WILL:
			    binarytransmit = TRUE;
			    break;
			case WONT:
			    binarytransmit = FALSE;
			    break;
			default:
			    break;
		       }
		  }
	      for (i = 0; i < 3; ++i)
		  {
		   wait_on_tcp(FALSE);
		   /* We block until we can write the reply */
		   n_written = write_tcp(answer[i], DONTFLUSH);
		   /* terminate if connection closes */
		   if (n_written != 1)
		       {
			syslog(LOG_DEBUG, "TCP Write Error 3: %m");
			terminate(REPRINT);
		       }
		  }
	      break;
	}
    }

/*
  This subroutine is invoked when we try to write to a dead TCP connection
*/  
disconnection()
    {
#ifdef DEBUG1
     syslog(LOG_DEBUG, "SIGPIPE received");
#endif
    }

/*
  This routine is called in order to force all internally buffered data out to
  TCP.  It blocks until successful.
*/  
flush_tcp()
    {
     int can_write;

     while(tcp_data)
	 {
#ifdef DEBUG2
	  can_write = tcp_mask;
	  select(16, NULL, &can_write, NULL, &poll);
	  if (!can_write)
	      syslog(LOG_DEBUG, "Waiting to flush TCP (%d)",tcp_data);
#endif
	  can_write = tcp_mask;
	  select(16, NULL, &can_write, NULL, NULL);
#ifdef DEBUG2
	  syslog(LOG_DEBUG, "Attempting to flush TCP");
#endif
	  push_tcp();
	 }
    }

/*
  This routine is called when lpd has finished sending us data (i.e, standard
  input is closed).  We make an attempt to keep the connection open until all
  the data has actually been printed.

  To do this, we first handshake with the terminal server by sending a "DO
  TIMING-MARK" command, and we wait to receive its "WONT TIMING-MARK" command
  back in response.  There can still be about 500 characters left in the
  terminal server which haven't yet printed out, so we wait 10 more seconds,
  which will usually be enough.
*/
handshake_and_finish()
    {
     unsigned char byte_from_net, cmd;
     int i, n;
     
     flags &= ~FNDELAY;
     fcntl(tcp_connection, F_SETFL, flags);  /* make socket blocking */
     answer[0] = IAC;
     answer[1] = DO;
     answer[2] = TIMINGMARK;
     for (i = 0; i < 3; ++i)
        {
	 wait_on_tcp(FALSE);
	 write_tcp(answer[i], i == 2);
	}
#ifdef DEBUG1
     syslog(LOG_DEBUG, "Starting Handshake");
#endif

     FOREVER
	 {
	  n = read(tcp_connection, &byte_from_net, 1);
	  if (n < 1)
	      {
	       syslog(LOG_DEBUG, "TCP Read Error 1: %m");
	       terminate(REPRINT);
	      }
	  if (byte_from_net != IAC) continue;
	  n = read(tcp_connection, &cmd, 1);
	  if (n < 1) 
	      {
	       syslog(LOG_DEBUG, "TCP Read Error 2: %m");
	       terminate(REPRINT);
	      }
	  if ((cmd != DO) && (cmd != DONT) && (cmd != WILL) && (cmd != WONT))
	      continue;
	  n = read(tcp_connection, &byte_from_net, 1);
	  if (n < 1)
	      {
	       syslog(LOG_DEBUG, "TCP Read Error 3: %m");
	       terminate(REPRINT);
	      }
	  if (byte_from_net != TIMINGMARK) continue;
	  if ((cmd != WONT) && (cmd != WILL)) continue;
#ifdef DEBUG1
	  syslog(LOG_DEBUG, "Handshake Complete");
#endif
	  break;
        }
     /*
       Now, in a DS300, there can be at most 512 characters backed up at the
       printer.  Let's wait 10 seconds and hope that the printer is not
       XOFFing us
     */
     close(tcp_connection);
     sleep(DISCONNECTTIMER);
    }
 
/*
  This subroutine sets up the host address and port number data structures
*/  
init(host, service)
    char *host, *service;
    {
     pp = getprotobyname("tcp");

     set_port(service, &sp_printer);
/*
  If a host name was given, this will find it either in /etc/hosts or via
  the Domain Name Service.  If an IP address was given, it is used.
*/
     if (isalpha(host[0]))
	 {
	  hp = gethostbyname(host);
	  if (!hp)
	      {
	       syslog(LOG_DEBUG, "Can't Find Host %s: %m", host);
	       exit(REPRINT);
	      }
	  return;
	 }
     printer_addr = inet_addr(host);
     hp = gethostbyaddr(&printer_addr, sizeof(printer_addr), AF_INET);
     if (hp) return;
     sethostent(NULL);
     hp = gethostent();
     hp -> h_addrtype = AF_INET;
     hp -> h_length = 4;
     hp -> h_addr = (char *) &printer_addr;
    }

/*
  This subroutine initializes the socket structure used for outgoing TCP
  connections.
*/
init_outgoing()
    {
     bzero((char *)&outgoing_addr, sizeof(outgoing_addr));
     bcopy(hp->h_addr, (char *)&outgoing_addr.sin_addr, hp->h_length);
     outgoing_addr.sin_family = hp->h_addrtype;
     outgoing_addr.sin_port = sp_printer -> s_port;

     if (tcp_connection) close(tcp_connection);
     tcp_connection = socket(AF_INET, SOCK_STREAM, 0);
     if (tcp_connection < 0)
	 {
	  syslog(LOG_DEBUG, "Can't Create Outgoing TCP Socket: %m");
	  exit(REPRINT);
	 }
     tcp_mask = 1 << tcp_connection;

     flags = fcntl(tcp_connection, F_GETFL);
     flags &= ~FNDELAY; /* make socket blocking, for connect */
     fcntl(tcp_connection, F_SETFL, flags);
    }

/*
  This subroutine is used by the Output Filter to listen for a connection from
  the Input Filter
*/
init_relay(service)
    char *service;
    {
     char *any = (char *)&anyaddr;
     int bind_retries = 0;

     listener = socket(AF_INET, SOCK_STREAM, 0);
     if (listener < 0)
	 {
	  syslog(LOG_DEBUG, "Can't create socket for Listener: %m");
	  exit(REPRINT);
	 }

     bzero((char *)&listen_addr, sizeof(listen_addr));
     bcopy(any, (char *)&listen_addr.sin_addr, 4);
     listen_addr.sin_family = AF_INET;
     set_port(service, &sp_relay);
     listen_addr.sin_port = sp_relay -> s_port;

     while (bind(listener, &listen_addr, sizeof(listen_addr)) < 0)
	 {
	  syslog(LOG_DEBUG, "Can't bind address to listener: %m");
	  delay(BASE_RETRY_INTERVAL, MAX_RETRY_INTERVAL, &bind_retries, 0);
	 }
     if (listen(listener, 1) < 0)
	 {
	  syslog(LOG_DEBUG, "Can't set up listener: %m");
	  exit(REPRINT);
	 }
    }


/*
  This subroutine handles those signals which terminate the process
  prematurely.
*/  
interruption(signo)
    int signo;
    {
     syslog(LOG_DEBUG, "interrupted by signal %d (%s)",
	    signo, signalnames[signo]);
     terminate(REPRINT);
    }


/*
  This subroutine makes outgoing TCP connections.
*/  
make_connection()
    {
     int optlen = sizeof(tcp_maxseg);
     int maxseg_opt;

     init_outgoing();
     delay(BASE_RETRY_INTERVAL, MAX_RETRY_INTERVAL,
	   &retrycount, max_retries);

     while (connect(tcp_connection,
		    (char *)&outgoing_addr,
		    sizeof(outgoing_addr))
	    < 0)
	 {
	  syslog(LOG_DEBUG, "Can't Open Outgoing TCP Connection: %m");
	  delay(BASE_RETRY_INTERVAL, MAX_RETRY_INTERVAL,
		&retrycount, max_retries);
	  init_outgoing();
	 }
     syslog(LOG_DEBUG, "Outgoing TCP Connection Made");
     flags |= FNDELAY;
     fcntl(tcp_connection, F_SETFL, flags); /* make socket non-blocking */

     maxseg_opt = 0;
#ifdef TCP_MAXSEG
     maxseg_opt = TCP_MAXSEG;
#endif
#ifdef TCPOPT_MAXSEG
     maxseg_opt = TCPOPT_MAXSEG;
#endif

     if (!maxseg_opt ||
	 (getsockopt(tcp_connection, pp -> p_proto, maxseg_opt,
		    (char *) &tcp_maxseg, &optlen) < 0))
	 {
	  tcp_maxseg = 512;
#ifdef DEBUG1
	  syslog(LOG_DEBUG, "TCP MSS = %d (default)", tcp_maxseg);
#endif
	 }
#ifdef DEBUG1
        else syslog(LOG_DEBUG,"TCP MSS = %d (dynamic)", tcp_maxseg);
#endif
    }

printhelp()
    {
     syslog(LOG_DEBUG, "Invalid Command Line Arguments");
     syslog(LOG_DEBUG, "\ti thishost relayport");
     syslog(LOG_DEBUG, "\to printerhost printerport relayport");
     syslog(LOG_DEBUG, "\tx printerhost printerport");
     exit(REPRINT);
    }

/*
  This subroutine is called to process each character received from lpd.  If
  its ascii value is 255, Telnet protocol requires that it be doubled.  If
  it is a linefeed, Telnet protocol requires that it be converted into a
  carriage return followed by a linefeed (unless the binary option has been
  enabled by the peer).  This subroutine also checks for the "end of banner"
  sequence ^Y^A from lpd.
*/  
/*
  We've got a character from lpd.  
*/
process_character(byte_from_lpd)
    int byte_from_lpd;
    {
     int n_written;
     
     switch(byte_from_lpd)  /* Process the Character */
	 {
	  case IAC:
	      /* Double the IACs. */
	      n_written = write_tcp(iac, DONTFLUSH);
	      ++extra_chars;
	      break;
		   
	  case LF:
	      /* LF --> CR LF, unless binary transmission enabled */
	      if (!binarytransmit)
		  {
		   n_written = write_tcp(cr, DONTFLUSH);
		   ++extra_chars;
		  }
	      break;

	  case CR:
	      /* CR --> CR NUL, unless binary transmission enabled */
	      if (!binarytransmit)
		  {
		   n_written = write_tcp(cr, DONTFLUSH);
		   byte_from_lpd = '\0';
		   ++extra_chars;
		  }
	      break;

	  case '\031':
	      if (OUTPUT_FILTER)
		  {
		   lpdstate = CTRLY;
		   return(OKA

push_tcp()
    {
     int n_written, n_to_write, last_to_write, can_write;
     
     if (!tcp_data) return(0);

     last_to_write = to_tcp_start + tcp_data - 1;
     if (last_to_write >= OUTBUFSIZE) last_to_write = OUTBUFSIZE - 1;
     n_to_write = last_to_write - to_tcp_start + 1;
     can_write = tcp_mask;
     select(16, NULL, &can_write, NULL, &poll);
     if (!can_write)
	 {
	  n_written = 0;
	  if (!tcp_blocking)
	      {
	       tcp_blocking = TRUE;
#ifdef DEBUG2
	       syslog(LOG_DEBUG, "TCP Blocked");
#endif
	      }
	 }
       else {
	     n_written = write(tcp_connection, &to_tcp[to_tcp_start],
			       n_to_write);
	     if (tcp_blocking)
		 {
		  tcp_blocking = FALSE;
#ifdef DEBUG2
		  if (n_written) syslog(LOG_DEBUG, "TCP Unblocked");
		    else syslog(LOG_DEBUG, "Blocking Confusion!");
#endif
		 }
	    }
     if (n_written < 0)
	 {
	  if (errno != EWOULDBLOCK)
	      {
	       syslog(LOG_DEBUG, "TCP Write Error: %m");
	       terminate(REPRINT);
	      }
	    else n_written = 0;
	 }

     if (n_written == 0) return(0);
     
#ifdef DEBUG2
     if (n_written > 0)
	  syslog(LOG_DEBUG, "Wrote %d bytes (%d-%d) of %d to TCP",
		 n_written, to_tcp_start, to_tcp_start+n_written-1, tcp_data);
#endif

     tcp_data -= n_written;

#ifdef DEBUG2
     syslog(LOG_DEBUG, "%d bytes still buffered for TCP", tcp_data);
#endif

/*
  Adjust to_tcp_start for wraparound, so next push works properly
*/  
     to_tcp_start += n_written;
     if (to_tcp_start >= OUTBUFSIZE) to_tcp_start -= OUTBUFSIZE;
/*
  If we have flushed all the data in the internal buffer, we will start
  accumulating data at the beginning again.
*/  

     if (!tcp_data) to_tcp_start = to_tcp_end = 0;

     return(n_written);
   }

/*
  This subroutine is called by the Output Filter after each Banner Page.  It
  forks a child process which gets the file to be printed from the Input
  Filter, and sends it out the TCP Connection to the printer.

  N.B.: Data received from the Input Filter has already been Telnetized.
*/  

int relay()
    {

     int from_if;
     int can_read_from_if;
     int if_mask;
     int i, n, n_written;
     int pid;
     int accept_retries = 1;
     unsigned char bytes_from_if[1024];

#ifdef DEBUG1
     syslog(LOG_DEBUG, "Forking Child Process");
#endif
     pid = fork();
/*
  Parent process returns immediately.
*/  
     if (pid != 0) return(pid);
     
/*
  Child Process continues here, and NEVER returns from this routine.
*/
     closelog();
     openlog("Telnet OF Child", LOG_PID);
#ifdef DEBUG1
     syslog(LOG_DEBUG, "Child running");
#endif
     addrlen = sizeof(incoming_addr);
/*
  Listen for the connection from the Input Filter.
*/  
     while ((from_if = accept(listener, &incoming_addr, &addrlen)) < 0)
	 {
	  syslog(LOG_DEBUG, "Accept Failed: %m");
	  delay(BASE_RETRY_INTERVAL, MAX_RETRY_INTERVAL,
		&accept_retries, MAX_RETRY_TIMES_TO_OF);
	 }
/*
  Now read from the input filter, and write out to the printer
*/
#ifdef DEBUG1
     syslog(LOG_DEBUG, "Connection from IF received");
#endif
     if_mask = 1 << from_if;
     FOREVER
	 {
	  can_read_from_if = if_mask;
	  select(16, &can_read_from_if, NULL, NULL, NULL);
	  n = read(from_if, bytes_from_if, 1024);
#ifdef DEBUG2
	  syslog(LOG_DEBUG, "Read %d from IF", n);
#endif
	  if (n == 0)
	      {
#ifdef DEBUG1
	       syslog(LOG_DEBUG, "OF sees IF terminate");
#endif
	       flush_tcp();
	       terminate(OKAY);
	      }
	  if (n < 0)
	      {
	       syslog(LOG_DEBUG, "Read from IF failure");
	       terminate(REPRINT);
	      }
	  for (i = 0; i < n; ++i)
	      {
	       wait_on_tcp(FALSE);
	       n_written = write_tcp(bytes_from_if[i], DONTFLUSH);
	       if (n_written < 0)
		   {
		    syslog(LOG_DEBUG, "Internal Write Error 2");
		    terminate(REPRINT);
		   }
	      }
	 }
    }
    
/*
  This subroutine sets up port number data structures
*/  

set_port(service, sp)
    char *service;
    struct servent **sp;
    {
     int port;
     short *port_high = (short *)&port;
/*
  If a service name was given, look it up in /etc/services.  If it's not
  alphabetic, it must be a TCP port number.  Look up the port number in
  /etc/services, but if it's not there, use it anyway.
*/
     if (isalpha(service[0]))
	 {
	  *sp = getservbyname(service, "tcp");
	  if (!*sp)
	      {
	       syslog(LOG_DEBUG, "Can't Find Service %s: %m", service);
	       exit(REPRINT);
	      }
	  return;
	 }
     port = 0;
     sscanf(service, "%d", port_high);
     *port_high = htons(*port_high);

     /* Don't quite know what I'm doing here, but it seems to work on 
	big-endian machines and on those horrid little-endian machines too.
     */

     *sp = getservbyport(port, "tcp");
     if (!*sp)
	 {
	  *sp = (struct servent *) malloc(sizeof (struct servent));
	  (*sp) -> s_port = port;
	  (*sp) -> s_proto = "tcp";
	 }
    }

terminate(exit_status)
    int exit_status;
    {
#ifdef DEBUG1
     syslog(LOG_DEBUG, "Terminating with Status %d", exit_status);
#endif
     if (child) kill(child, SIGINT);
     close(tcp_connection);
     exit(exit_status);
    }

/*
  Since TCP is flow controlled, we can't always write to it whenever we want.
  This routine is called when we need to block until it is possible to write.
  Logically, we consider that it is "possible to write" as long as the
  internal buffer is not full.
  
  If the argument is TRUE, however, the routine will return if there is data
  to read from the network as well.
*/  

wait_on_tcp(need_to_read)
    int need_to_read;
    {
     int can_write_to_tcp;
     int can_read_from_tcp;

     if (need_to_read)
	 {
	  can_read_from_tcp = tcp_mask;
	  select(16, &can_read_from_tcp, NULL, NULL, &poll);
	  if (can_read_from_tcp) return(can_read_from_tcp);
	 }
     if (tcp_data < OUTBUFSIZE) return(NULL);
     while (tcp_data >= OUTBUFSIZE)
	 {
	  if (need_to_read) can_read_from_tcp = tcp_mask;
	    else can_read_from_tcp = NULL;
	  can_write_to_tcp = tcp_mask;
#ifdef DEBUG2
	  syslog(LOG_DEBUG, "Waiting to write to TCP");
#endif
	  select(16, &can_read_from_tcp, &can_write_to_tcp, NULL, NULL);
	  if (can_write_to_tcp)
	      {
#ifdef DEBUG2
	       syslog(LOG_DEBUG, "TCP now writable");
#endif
	       push_tcp();
	      }
	  if (can_read_from_tcp) return(can_read_from_tcp);
	 }
     return(NULL);
    } 

/*
  This subroutine is called to "logically" output a byte to the printer.
  If its second argument is FALSE, it ordinarily just stores the byte in its
  own buffer.  Once it has accumulated enough bytes to fill a maximum size TCP
  segment, it automatically delivers those bytes to TCP.
  
  If the second argument is TRUE, all bytes in the buffer will be given to TCP.
*/

write_tcp(byte, flush)
    unsigned char byte;
    int flush;
    {
     if (tcp_data >= OUTBUFSIZE)
        {
	 syslog(LOG_DEBUG, "Multiple writes to full buffer!");
	 errno = EWOULDBLOCK;
	 return(-1);
	}
     to_tcp[to_tcp_end++] = byte;
     ++tcp_data;
#ifdef DEBUG2
     if (tcp_data == OUTBUFSIZE)
	 syslog(LOG_DEBUG, "Internal buffer full, start = %d",
		to_tcp_start);
#endif
     if (to_tcp_end == OUTBUFSIZE)
	 {
#ifdef DEBUG2
	  syslog(LOG_DEBUG, "Internal Buffer Wrapping, start = %d",
		 to_tcp_start);
#endif
	  to_tcp_end = 0;
	 }
     if (flush) flush_tcp();
         else if (tcp_data >= tcp_maxseg) push_tcp();
     return(1);
    }