[Search for users] [Overall Top Noters] [List of all Conferences] [Download this site]

Conference mr1pst::sco_unix

Title:sco_unix
Moderator:AVNGRS::BOELKE
Created:Mon Mar 26 1990
Last Modified:Thu May 22 1997
Last Successful Update:Fri Jun 06 1997
Number of topics:2574
Total number of notes:9709

2560.0. "SCO OES 5.0.2 & telnetfilter" by SIOG::BR_MURPHY () Wed Jan 15 1997 12:27

T.RTitleUserPersonal
Name
DateLines
2560.1Try recompiling the sourceCSCMA::EARNESTThu Jan 30 1997 18:332702
    
    Brendan,

	Have you tried recompiling the telnetfilter source on SCO 5.0.2?
	I suspect that something has changed.  This is a copy of the
	telnetfilter.c source:

[DSNACS] Printing to Telnet Ports from non-Ultrix Systems

     Any party granted access to the following copyrighted information
     (protected under Federal Copyright Laws), pursuant to a duly executed
     Digital Service Agreement may, under the terms of such agreement copy
     all or selected portions of this information for internal use and
     distribution only. No other copying or distribution for any other
     purpose is authorized.
Copyright (c) Digital Equipment Corporation 1995.  All rights reserved

PRODUCT:   DECserver 300
           DECserver 700
           DECserver 90TL
           DECserver 90M
           DECserver 900TM
           DECserver Network Access Server
           MUXserver 320, 380

OP/SYS:    OSF/1 V1.0 and greater
           ULTRIX V4.0 and greater
           UNIX as documented in SPD 46.13.03


PROBLEM:

DECservers running Network Access Server software support Telnet
connections to printers.  Such connections may not work as expected
from some non-Ultrix UNIX systems.  A typical behavior is that only
the first line or two of a file is printed after which the Telnet
connection is dropped.

See other articles in the database for information on how to
configure a Telnet printer.


SOLUTION:

Compile the TELNETFILTER.C program included below.  This source code
is extracted from the NAS V1.5 distribution, saveset B. There is also
a TELNETFILTER.S executable for use on SCO[TM] Unix systems.

The program is extensively documented in the initial comments.

Basically what the program does is to provide a "Telnet print filter"
which encapsulates the output of a printer interface program into
Telnet format.  The encapsulated data is sent through a DECserver
Telnet listener port to the specified printer.


SOURCE CODE:

/*
  This program is used to enable the "lpr" command to cause data to be
  sent to a printer that is attached to a Telnet port on a Network
  Access Server. The port must be configured as a Telnet Listener.

  The program is offered only as a "proof of concept"; it has been tested
  and used successfully on a number on Unix systems but it cannot be
  guaranteed to run on every Unix version.  This program is only supported
  on SCO Unix System V/386 Release 3.2 and up.

 It has been tested on the following systems:

        SCO Unix System V/386 Release 3.2
        SunOS Release 4.1.1
        Ultrix V4.3
        OSF/1 Version 2.0.240
        HP-UX Version B.08.00 A 9000/42T
        IBM AIX 3.2.5

  To properly install this program, you must be logged in as superuser.

     If you are installing from a VMS distribution:

        FTP this file, "TELNETFILTER.C", to the target machine and rename
        it "telnetfilter.c" (if you have a SCO Unix system, there is a
        binary named "TELNETFILTER.S" that you need to FTP in binary
        mode to the target machine and rename "telnetfilter".  Make it
        executable with "chmod ugo+x telnetfilter").

     If you are installing from a Unix or Ultrix distribution:

        FTP this file, "telnetfilter.c", the target machine.  If you are
        installing on a SCO Unix system, there is a binary named
        "telnetfilter.s" that you need to FTP in binary mode to target
        machine and rename "telnetfilter".  Make it executable
        with "chmod ugo+x telnetfilter").

  NOTE:

     If you compile and run this program and discover that your printer is
     ejecting a blank page between files, recompile this program with the
     symbol NOFF defined (use -DNOFF on the command line).  This will
     remove the code that adds a form feed between files.

        cc -o telnetfilter -DSYSV -DNOFF telnetfilter.c

  There are two basic types of configurations you can set up for your
  network printers.  You can set up one system as the 'main spooler' for
  the network and have all print requests spool on that system.  Or, you
  can set up each system to handle its own print requests.  All
  configurations shown in this document configure each system to handle
  its own print requests.

  If you would like to set up your network print system with one main
  spooler, configure the printer for that machine as defined in the
  appropriate section of this document.  Configure the printers on all
  other machines in the network as remote printers.  Use the printer
  name on the main spooler machine as the name of the 'remote printer'
  and use the name of the main spooler machine as the 'remote machine'.
  Your operating system documentation will describe how to set up a
  remote (or network) printer.

  This is an example of a printcap entry for an Ultrix system which
  points to the printer called 'lp' on the host called 'prowlr'.  The
  printer on prowlr uses the telnetfilter to send the print request to
  the printer.  The machine on which this entry resides copies the files
  to be printed to the remote machine which actually does the printing.

  lp0|telprt|0|Telnet Printer:\
        :lp=:\
        :sd=/usr/spool/lpd0:\
        :rm=prowlr:\
        :rp=lp:

___________________________________________________________________________

  To configure the Telnet filter for your system, go to the appropriate
  section as listed below:


  I.    BSD 4.3 Systems Other Than Ultrix (Including OSF/1 and SunOS)

  II    Ultrix Systems

  III.  SCO Unix Systems

  IV.   HP-UX Systems

  V.    IBM AIX Systems

__________________________________________________________________________

I.   BSD 4.3 Systems Other Than Ultrix (Including OSF/1 and SunOS)

  This 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
  solution is to have the "if" and "of" entries refer to shell scripts.
  Within the shell scripts, any arguments can be passed.

  This table describes how to construct your printcap entry based upon
  what type of system and printer you have.  Some printers require the
  use of an input and output filter while others use only an output
  filter.  Most PostScript printers require files to be in PostScript
  language to be printed.  This is accomplished by a PostScript filter
  which converts ASCII text into PostScript commands.  Most filters will
  pass documents already in PostScript format through the filter without
  modification.

  System                Type Printer    Telnetfilter Configuration
  ______________________________________________________________________
  SUNOS                 ASCII           input and output filter
  SUNOS                 PostScript      output filter only
  OSF/1                 ASCII           input and output filter
  OSF/1                 PostScript      PostScript filter piped into
                                        Telnet filter (output filter only)
  ______________________________________________________________________

  To configure this program for your system, you must do the following:

1.)  Create a directory with the proper permissions for the line printer
     daemon.  I usually put the telnetfilter source and executable in
     this directory and create another directory for spooling.  I also
     store the input and output filters in the spool directory.  I use
     the same naming convention as the operating system: printer 'lp'
     spools to directory /usr/spool/lpd, printer 'lp0' spools to directory
     /usr/spool/lpd0, etc.

        SunOS systems

                mkdir /usr/spool/telprt
                chown daemon /usr/spool/telprt
                chgrp daemon /usr/spool/telprt
                chmod 770 /usr/spool/telprt

        OSF/1 systems

                mkdir /usr/spool/telprt

2.)  Copy this program to the above directory and compile.

  To compile this program on an OSF/1 system, the symbol OSF1 must be
  defined by including -DOSF1 on the cc command line:

        cc -o telnetfilter -DOSF1 telnetfilter.c

  To compile this program on a SunOS system, the symbol SUN must be
  defined by including -DSUN on the cc command line:

        cc -o telnetfilter -DSUN telnetfilter.c

3.)  Create printcap entry for your printer.

     Printcap characteristics:

        lp - device name to open for output
        sd - spooling directory to use
        of - output filter to use (script that calls this program)
        if - input filter to use (script that calls this program)
        lf - Error logging file name
        sh - suppress printing of banner pages

  a.) Printcap entry for ASCII printer

  This sample printcap entry defines an ASCII printer named 'telprt':

  lp|telprt|Telnet Printer:\
        :lp=/dev/null:\
        :sd=/usr/spool/lpd:\
        :of=/usr/spool/lpd/output_filter:\
        :if=/usr/spool/lpd/input_filter:\
        :lf=/usr/spool/lpd/errorlog:

  b.) Printcap entry for PostScript printer

  This sample printcap entry defines a PostScript printer named 'post':

  lp0|post|PostScript Telnet Printer:\
        :lp=/dev/null:\
        :sh:\
        :sd=/usr/spool/lpd0:\
        :of=/usr/spool/lpd0/output_filter:\
        :lf=/usr/spool/lpd0/errorlog:

4.)  Create an output filter script to call this program.  I create the
     filter scripts in the spooling directory for the printer.  In this
     case, that directory is /usr/spool/lpd0.

  The shell script "/usr/spool/lpd/output_filter" can be created by
  modifying the following sample script:

  SAMPLE OUTPUT FILTER SCRIPT

#! /bin/sh
# this shell script is called when the Telnet printer is used.
# the call to this script can be found in the file /etc/printcap.
# this script calls 'telnetfilter' as an output filter
# 'o' or 'x' tells the telnetfilter that it is being used
#          as an output filter.
# 'access_server' is the name or IP address of the Network Access Server
#          that the printer is attached to.  If a name is used, it MUST
#          be in /etc/hosts or in the Domain Name Service.
# 'printer_port' is the TCP Port Number which has been assigned to the
#          Telnet Listener on the Network Access Server.  Note that this
#          is the TCP Port Number, not the physical port number.  This is
#          usually a decimal number such as 2010, but it can also be a
#          "service name" which appears in /etc/services with a TCP port
#          number associated with it.
# 'relay_port' is a TCP Port Number not otherwise in use on this system.
#          This is only used when you call telnetfilter as both an input
#          AND an output filter (used for synchronization).
#          Check /etc/services to find an unused port number.  A GIVEN
#          PRINTCAP ENTRY MUST HAVE A UNIQUE RELAY_PORT NUMBER.
#
# If you are calling telnetfilter as BOTH and input and output filter
# (printing to an ASCII printer) use the following format:
#
# exec /usr/spool/telprt/telnetfilter o access_server
#            printer_port relay_port
#
# If you are calling telnetfilter as an output filter ONLY (printing to
# a PostScript printer) use the following format:
#
# exec /usr/spool/telprt/telnetfilter x access_server printer_port
#
# If you are calling a PostScript filter first and piping the output into
# telnetfilter (printing to PostScript printer) use the following format:
#
# exec /usr/lbin/ln03rof | /usr/spool/telprt/telnetfilter x access_server\
# printer_port
#
#  modify the line below!
#
exec /usr/spool/telprt/telnetfilter o tantn1 2010 3000
#
# end of output filter script
#----------------------------------------------------------------------------

5.)  If necessary, create an input filter script to call this program.

  The shell script "/usr/spool/lpd/input_filter" can be created by
  modifying the following sample script:

  SAMPLE INPUT FILTER SCRIPT

#!/bin/sh
# this shell script is called when the Telnet printer is used.
# the call to this script can be found in the file /etc/printcap.
# this script calls 'telnetfilter' as an input filter
# 'i' is an argument that tells the telnetfilter that it is being used as
#          an input filter.
# 'this_host' is the name or IP address of the machine you are
#          printing from. If a name is used, it MUST be in /etc/hosts
#          or in the Domain Name Service.
# 'relay_port' is a TCP Port Number not otherwise in use on this system.
#          Check /etc/services to find an unused port number.  This port
#          number must be the same as the port number given for relay_port
#          in 'output_filter'.  This port is used for communication
#          between the input and output filters.
#
# exec /usr/spool/telprt/telnetfilter i this_host relay_port
#
# modify the line below!
exec /usr/spool/telprt/telnetfilter i dsuser 3000
#
# end of input filter script
#-----------------------------------------------------------------------

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

      Make them executable (chmod ugo+x *filter_script)!

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

      If you are using telnetfilter as BOTH an input and output
      filter, make sure the same relay_port number is provided in
      both shell scripts, and ensure that it is not otherwise in
      use on your host (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.

 6.)  Use lpc to start and enable the newly defined printer.

        lpc start printername
        lpc enable printername

  Using the sample configuration, the command "lpr -Ptelprt filename"
  will send data to the printer which is attached to the Network Access
  Server whose name is "tantn1" which has been associated with a Telnet
  Listener at TCP Port 2010 on that Network Access Server.  It will use
  the directory /usr/spool/lpd on localhost "dsuser" as the spooling
  directory.

_______________________________________________________________________

II.  Ultrix Systems

  If you have an Ultrix V4 system, this program is directly invoked as
  an output filter in /etc/printcap.

  To configure this program for Ultrix systems, you must do the following:

1.)  Create a directory with the proper permissions for the line
     printer daemon.  I usually put the telnetfilter source and
     executable in this directory and create another directory for
     spooling.  I also store the input and output filters in the spool
     directory.  I use the same naming convention as the operating
     system: printer 'lp' spools to directory /usr/spool/lpd,
     printer 'lp0' spools to directory /usr/spool/lpd0, etc.

                mkdir /usr/spool/telprt
                chown daemon /usr/spool/telprt
                chgrp daemon /usr/spool/telprt
                chmod 755 /usr/spool/telprt

2.)  Copy this program to the above directory and compile.

  To compile this program for Ultrix systems, use the following command:

        cc -o telnetfilter telnetfilter.c

3.)  Create a printcap entry for your printer.

  a.)  Printcap entry for ASCII printer

  This sample printcap entry defines a printer named 'telprt' with the
  following properties:

        sd - spooling directory to use
        ct - connection type, only valid when uv=psv1.0
                valid choices are: dev, lat, remote, network
        uv - Ultrix version: psv1.0 or 3.0
        of - output filter for Ultrix (this program using x argument)

  SAMPLE PRINTCAP ENTRY FOR ASCII PRINTER

    lp0|telprt|0|Telnet Printer:\
        :sd=/usr/spool/lpd0:\
        :ct=network:\
        :uv=psv1.0:\
        :of=/usr/spool/telprt/telnetfilter x tantn1 2010:

  This printcap entry enables you to print ASCII text to an ASCII printer.

  b.)  Printcap entry for PostScript printer

  This sample printcap entry defines a printer named 'post' with the
  following properties:

        sd - spooling directory to use
        sh - suppress banner pages
        ct - connection type, valid choices are: dev, lat, remote, network
        uv - Ultrix version: psv1.0, 3.0, 4.0
        of - output filter for Ultrix (this program using x argument)
        pw - page width in columns
        pl - page length in lines
        lf - error logging file
        Or - orientation of printed document

  SAMPLE PRINTCAP ENTRY FOR A POSTSCRIPT PRINTER

    lp1|post|1|Telnet PostScript Printer:\
        :sd=/usr/spool/lpd1:\
        :sh:\
        :ct=network:\
        :uv=4.0:\
        :of=ln03rof | /usr/spool/telprt/telnetfilter x tantn1 2006:\
        :pw#80:\
        :pl#66:\
        :lf=/usr/spool/lpd1/lpd-errs:\
        :Or=portrait:

   This printcap entry enables you to print both ASCII text and PostScript
   documents to a PostScript printer.  ASCII documents will be converted
   to PostScript by ln03rof and sent to the printer via telnetfilter.
   PostScript documents will be sent to the printer without conversion.
   Make sure that the spool directory exists before you attempt to print
   any files.

3.)  Add the Network Access Server name and IP address to /etc/hosts

        12.34.56.78 tantn1 tantn1.lkg.dec.com

4.)  Use lpc to start and enable the newly defined printer.

        lpc start printername
        lpc enable printername

  With the first printcap entry, the command "lpr -Ptelprt filename"
  will send data to the printer attached to the port associated with
  Telnet Listener Port 2010 on Network Access Server "tantn1".  It will
  use the directory /usr/spool/lpd0 as the spooling directory.

  With the second printcap entry, the command "lpr -Ppost filename" will
  send data to the printer attached to the port associated with Telnet
  Listener Port 2006 on Network Access Server "tantn1".  It will use the
  directory /usr/spool/lpd1 as the spooling directory.

________________________________________________________________________

III.  SCO Unix Systems

  On SCO Unix systems, this program is intended to be called as a
  "filter". The filter is invoked by an "interface program".  Interface
  programs can be found in directory /usr/spool/lp/model.

  To configure this program for SCO Unix systems, you must do the
  following:

1.)  Create a directory with the proper permissions for the line printer
     daemon.

                mkdir /usr/spool/telprt

2.)  Copy this program to the above directory and compile.

  To compile this program on a SCO Unix system, the symbol SYSV must be
  defined by including -DSYSV on the cc command line (You do not have to
  execute this command if you use the SCO executable included in the
  distribution).

        cc -o telnetfilter -DSYSV telnetfilter.c -lsocket

3.)  Create an interface program

  a.)  ASCII Printers

     Create an interface program for printing to an ASCII printer via
     Telnet.
     Copy /usr/spool/lp/model/network to /usr/spool/lp/model/decserver:

        cd /usr/spool/lp/model
        cp network decserver

     Make sure that this file is owned by bin and group lp:

        chown bin decserver
        chgrp lp decserver

     The very last line of this file is:

                         } | $network -s -ob $lpflags

     Change this line in /usr/spool/lp/model/decserver to:

                         } | $network

  b.)  PostScript Printers

     Create an interface program for printing to a PostScript printer via
     Telnet.  Copy /usr/spool/lp/model/postscript to
     /usr/spool/lp/model/psdecserver:

        cd /usr/spool/lp/model
        cp postscript psdecserver

     Make sure that this file is owned by bin and group lp:

        chown bin psdecserver
        chgrp lp psdecserver

     You will need to edit psdecserver in the following manner:

     First, you need to add a paren "(" before the first executable
     statement in the script.  Next, you need to add the closing paren ")"
     and a pipe to the interface AFTER the last executable statement in the
     script but BEFORE the 'exit 0' statement. The following two lines are
     the last two lines in the program.  Add the indicated line between
     them:

     ${DRAIN}
     ) | /usr/spool/telprt/telnetfilter x hostname tcp_port #<--add this
     exit 0

     NOTE:  There is a statement near the top of the psdecserver program
            something like the following:

                stty ixon ixoff onlcr

        You should comment out this line.  If you do not, you will get
        mail messages from the system stating that your print request
        encountered an "stty: : Not a typewriter" error while printing.

4.)  If it does not already exist, create the file /usr/spool/lp/remote.
     This file must be owned by user lp and group lp, and must be
     publicly readable:

        chown lp remote
        chgrp lp remote
        chmod ugo+r remote

     Add a line like the following to the file:

        telprt: /usr/spool/telprt/telnetfilter x hostname 2010

     OR you can use the IP address of the Network Access Server:

        telprt: /usr/spool/telprt/telnetfilter x 12.34.56.78 2010

  The colon after the printer name "telprt" should be followed by a tab.
  This line assigns the name "telprt" to the printer attached at TCP Port
  2010 of access server "hostname" whose IP address is 12.34.56.78.

5.)  The IP address and hostname must be added to the /etc/hosts file.
     A sample entry is below:

        12.34.56.78     hostname hostname.lkg.dec.com

6.)  Add the printer to the print spooling system.  You could cut and
     paste the following shell script into a file called config_telprt.
     The shell script will do the tedious part of the configuration
     for you.  Be sure to change the file permissions to executable
     (chmod ugo+x config_telprt).
     Execute this script as superuser.

#!/bin/sh
#
# This is a Bourne shell script to configure a printer that you specify.
#
# This script expects two arguments:
#       1)      the name of the printer to configure
#       2)      the name of the interface program to use for this printer.
#               (it must exist in /usr/spool/lp/model)
#
# ex:   config_telprt printername interface
#
#       config_telprt telprt decserver
#
# You can modify the arguments to lpadmin to suit your system.
# When you run this script, lpadmin will create a new printer with the
# given characteristics.  If the printer already exists, lpadmin will
# reconfigure the printer.
#
# The interface program is in /usr/spool/lp/model/
#
#
# lpadmin options used by this script:
#
#       -p      printername
#       -F      what to do if there is a printer fault
#               beginning : restart printing at the file beginning
#               continue  : continue printing file from top of page
#               wait      : wait for you to enter the enable command
#       -h      hardwired
#       -v      device that lp can write to (usually /dev/null)
#       -m      name of the model interface program to use
#       -W      notify system administrator of fault every n minutes
#       -D      description of printer for lpstat
#
# the connection type is 'direct' by default
#
#--------------------------------------------------------------------
#
printername=$1
interface=$2

# reject requests to use this printer
#
/usr/lib/reject $printername
#
# disable queuing on this printer
#
disable $printername
#
# shut down print services
#
/usr/lib/lpshut
#
# configure printer named "printername"
# using interface model "interface" located in /usr/spool/lp/model/
#
/usr/lib/lpadmin -p$printername -Fbeginning -h -v/dev/null -m$interface\
        -D"Telnet Printer" -W1
#
# start print services
#
/usr/lib/lpsched
#
# notify printer to begin accepting print requests
#
/usr/lib/accept $printername
#
# enable printer
#
exec enable $printername
#
# end of script
# -----------------------------------------------------------------------

  If you configured an ASCII printer, use the command "lp -d telprt
  filename" to print ASCII files.  If you configured a PostScript
  printer, to print PostScript files, use the command "lp -d telprt
  -oraw filename". To print ASCII files to a PostScript printer, use the
  command "lp -d telprt -oport filename".  If the printer is busy when a
  print command is given, the print filter will keep trying until the
  port is available (using exponential backoff).

  If the TCP connection breaks while printing is in progress, the print
  spooling system will see this as a "printer fault", and after pausing
  for a few minutes, will try to print the file again.
___________________________________________________________________________

IV.  HP-UX Systems

  To configure this program for your system, you must do the following:

1.)  Create a directory with the proper permissions for the line printer
     daemon.

                mkdir /usr/spool/telprt
                chown lp /usr/spool/telprt
                chgrp bin /usr/spool/telprt
                chmod 775 /usr/spool/telprt

2.)  Copy this program to the above directory and compile.

  To compile this program on an HP-UX system, the symbol SYSV must be
  defined by including -DSYSV on the cc command line:

        cc -o telnetfilter -DSYSV telnetfilter.c

3.)  Create an interface program

  a.)  ASCII Printers

     Create an interface program for printing to an ASCII printer via
     Telnet.
     Copy /usr/spool/lp/model/dumb to /usr/spool/lp/model/decserver:

        cd /usr/spool/lp/model
        cp dumb decserver

     You will need to edit this file in the following manner:

     First, you have to add a paren at the top of the file before the first
     executable statement of the script:

#=======================================================================#
# OPTIONS RECOGNIZED: ( all may be preceded with a "-" )                #
#       nb              do not output banner page (to save paper)       #
#=======================================================================#

banner="yes";

#=======================================================================#
# OPTIONS RECOGNIZED: ( all may be preceded with a "-" )                #
#       nb              do not output banner page (to save paper)       #
#=======================================================================#
(   # <---- ADD THIS PAREN!
banner="yes";

Next, you need to add the closing paren and a pipe to the interface AFTER
the last executable statement in the script but BEFORE the 'exit 0'.
The following two lines are the last two lines in the program.  Add the
indicated line between them:

) | /usr/spool/telprt/telnetfilter x hostname tcp_port

#echo "$x\n$x\n$x\n$x\n$x\n$x\n$x\n$x\n"
exit 0

#echo "$x\n$x\n$x\n$x\n$x\n$x\n$x\n$x\n"
) | /usr/spool/telprt/telnetfilter x hostname tcp_port  # <--ADD THIS!
exit 0

  The "hostname" is the name of the Network Access Server and "tcp_port"
  is the TCP port number that the printer is attached to.  If you want
  to have multiple Telnet Printers, make a copy of this file for each
  printer (give each interface file the same name as the printer it will
  use) and just change "tcp_port" to the TCP port that the printer is
  attached to. If the printer is attached to a different Network Access
  Server, you will need to change that also.  You can then execute the
  script described below with the "printername" and "interface" names as
  arguments.

  b.) PostScript Printers

     Create an interface program for printing ASCII and PostScript
     files to a PostScript printer via Telnet.

     Copy /usr/spool/lp/model/postscript to
          /usr/spool/lp/model/psdecserver:

        cd /usr/spool/lp/model
        cp postscript psdecserver

     You will need to edit this file in the same manner described above
     in section A.

4.)  The IP address and hostname of the Network Access Server must be
     added to the /etc/hosts file. A sample entry is below:

        12.34.56.78     hostname hostname.lkg.dec.com

     The TCP port number associated with the printer should be added
     to the /etc/services file.  A sample entry is below:

        telprt  2010/tcp                # Telnet printer


5.)  Add the printer to the print spooling system.  You could cut and
     paste  the following shell script into a file called config_telprt.
     The shell script will do the tedious part of the configuration for
     you.  Be sure to change the file permissions to executable (chmod
     ugo+x config_telprt). Execute this script as superuser.  You can run
     this script several times in a row to configure several printers.  It
     will only affect the printer that you give as an argument.  Once you
     have completed this configuration, you can print using the command
     "lp -dprintername file1 file2".

#!/bin/sh
#------------------------------------------------------------------------
# This is a Bourne shell script to configure a printer that you specify.
#
# This script expects two arguments:
#       1)      the name of the printer to configure
#       2)      the name of the interface program to use for this printer.
#               (it must exist in /usr/spool/lp/model)
#
#  ex:  config_telprt printername interface
#
#       config_telprt telprt decserver
#
# You can modify the arguments to lpadmin to suit your system.
# When you run this script, lpadmin will create a new printer with the
# given characteristics.  If the printer already exists, lpadmin will
# reconfigure the printer.
#
# The interface program is in /usr/spool/lp/model
#
#
# lpadmin options used by this script:
#
#       -p      printername
#       -h      hardwired
#       -v      device that lp can write to (usually /dev/null)
#       -m      name of the model interface program to use
#
# the connection type is 'direct' by default
#
#--------------------------------------------------------------------

printername=$1
interface=$2

# reject requests to use this printer
#
/usr/lib/reject $printername
#
# disable queuing on this printer
#
disable $printername
#
# shut down print services
#
/usr/lib/lpshut
#
# configure printer named "printername"
# using interface model "interface" located in /usr/spool/lp/model/
#
/usr/lib/lpadmin -p$printername  -h -v/dev/null -m$interface
#
# start print services
#
/usr/lib/lpsched
#
# notify printer to begin accepting print requests
#
/usr/lib/accept $printername
#
# enable printer
#
exec enable $printername
#
# end of script
#--------------------------------------------------------------------------

___________________________________________________________________________

V.  IBM AIX Systems

  On AIX systems, this program is called in a pipeline created by piobe
  (the printer backend).  This program is called as the 'device driver
  interface program' for the virtual printer device which corresponds to
  a printer queue.  The device driver interface program name and its
  arguments are stored as the 'mo' attribute of the virtual printer
  device.

  This configuration introduces an undesirable characteristic:

  A TCP connection will be made for the header page, this connection
  will be closed when the header page completes printing, then another
  TCP connection will be established for the  data. If you are trying to
  share the printer with other hosts, they may  get in between the
  header and the data.  Also, if the TCP connection is lost in the midst
  of printing, the file will not be reprinted.  However, if the printer
  is busy at the time the qdaemon tries to start printing, it will keep
  retrying until it gets through.

  To configure this program for your system, you must do the following:

1.)  Create a directory with the proper permissions for the qdaemon.
     The qdaemon only needs read and execute permissions.  If you
     create this directory as superuser, you shouldn't have any
     trouble with permissions.

                mkdir /usr/spool/telprt

2.)  Copy this program to the above directory and compile.

  To compile this program on an IBM AIX system, the symbol AIX must be
  defined by including -DAIX on the cc command line:

        cc -o telnetfilter -DAIX telnetfilter.c

3.)  Cut and paste the following sample shell script into a file called
     config_telprt.  Make it executable (chmod ugo+x config_telprt).

#!/bin/sh
#--------------------------------------------------------------------------
# This sample Bourne shell script will help you to set up a Telnet printer
# on your IBM RS/6000.
#
#
#queue          name of print queue (lp -dqueue filename) choose any name
#               that you desire (but it must begin with a letter)
#device         name of printer device (lp0, lp1, lp2 ...or np0, np1...)
#pathname       directory path for the telnetfilter program (just the
#               the directory path)
#                       /usr/spool/telprt
#ipaddress      the ip address of the DECserver connected to printer
#                       11.22.33.44
#tcpport        the Telnet Listener port number on the DECserver
#                       2010
#printer        printer from /usr/lib/lpd/pio/predef
#               choose your printer from those listed (no extension)
#                       hplj-3
#data           for each printer, there may be more than one possible
#               datastream. these are all of the possible types.
#
#               this is the extension on the above filename
#
#                       asc     ASCII
#                       ps      PostScript
#                       pcl     Hewlett-Packard PCL
#                       630     Diablo 630
#                       855     Texas Instruments 855
#                       gl      Hewlett-Packard GL
#                       kgi     Kanji
#
queue=$1
device=$2
pathname=$3
ipaddress=$4
tcpport=$5
printer=$6
data=$7
#
# SAMPLE USE OF THIS SCRIPT
#
# config_telprt lp0 telprt0 /usr/spool/telprt 16.20.48.69 2001 printer asc
#
# when a user wants to send jobs to the printer, they just use lp
#
# lp -dlp0 thisismyfile thisisanotherfile andyetanotherfile
#-------------------------------------------------------------------------
# test to see if that printer file does exist
#
if test ! -f /usr/lib/lpd/pio/predef/$printer.$data
   then
   echo '\n\nPrinter' ${printer}.${data} 'does not exist.  Please check:\n
       exit 1
fi
#
# unless you have renamed the telnetfilter, this is the complete path
#
pathname=$pathname/telnetfilter
#
# create file for queue device to write to
touch /usr/spool/$queue:$device
#
# make it readable and writable by everyone
chmod 766 /usr/spool/$queue:$device
#
# make a queue (in /etc/qconfig)
/usr/bin/mkque -q$queue
#
# make a queue device for the above queue (in /etc/qconfig)
/usr/bin/mkquedev -q$queue -d$device -a 'backend = /usr/lib/lpd/piobe'\
        -a 'file = /usr/spool/'$queue':'$device'' \
        -a 'header = never' -a 'trailer = never' -a 'access = both'
# to print a header page before each job, change 'never' to 'always'
# to print a header page before each group of jobs from the same user,
# change 'never' to 'group'.  You can do the same for trailer pages.
#
# create a virtual printer for this queue and device pair
/etc/mkvirprt -d$device -n$device -q$queue -s$data  -t$printer
#
#create a temporary file to hold output of sed
touch /usr/spool/lpd/pio/custom/temp
chmod g+w /usr/spool/lpd/pio/custom/temp
chgrp printq /usr/spool/lpd/pio/custom/temp
#
# replace the default mo string with your custom string
sed 's!:...:mo::.*!:307:mo::'$pathname' x '$ipaddress' '$tcpport'!' \
     /usr/spool/lpd/pio/custom/$queue:$device > \
     /usr/spool/lpd/pio/custom/temp
#
# move the temp file back to the original filename
mv /usr/spool/lpd/pio/custom/temp \
   /usr/spool/lpd/pio/custom/$queue:$device
#
# digest (using piodigest) the custom attribute file (queue:device)
#        into binary form in directory /usr/spool/lpd/pio/ddi/
/etc/chvirprt -d$device -q$queue
#
# done!! now we're ready to start printing
#------------------------------------------------------------------------

4.)  Determine the values of the arguments required by this script:

  a.) If you are going to use lp device names (such as lp0,lp1 ...) you
      should use mkdev to create a printer device.  This is to eliminate
      the possibility of the operating system assigning your device name
      to the next printer configured by the system.  When you use mkdev to
      create a new printer device, the operating system assigns the next
      device name to your printer (lp0, lp1, lp2...).  If you use device
      names such as tp0, tp1, (Telnet printer) np0, np1 (network printer)
      you DO NOT need to create devices using these names because AIX does
      not use these names for devices.


      To create a printer device using mkdev (as root):

        1)  smit mkdev
        2)  choose Printer/Plotters
        3)  choose Printer/Plotter Devices
        4)  choose Add A Printer/Plotter
        5)  choose your model printer from those listed (note the name
            in the first column, this is the system name for your printer).
            if you don't see your printer listed, choose
              'other serial printer'
        6)  choose rs232 for the interface
        7)  choose any value for the parent adapter
        8)  choose any port number
        9)  hit <ENTER>
        10) message "lp# Available" will appear. This is your device name.
        11) exit from smit, your device name is defined.


  b.) call config_telprt using the values you have decided upon:

    sh config_telprt queue device pathname ipaddress tcpport printer data

    sh config_telprt lp1 lp1 /usr/spool/telprt 16.20.104.78 2001 2380 asc

      Now you can use your newly configured printer.

      If you would like to configure more than one printer, create a
      printer device for the printer (if necessary), then run
      config_telprt with the appropriate arguments.  You may choose to
      group several printers into one logical queue.  If you call
      config_telprt more than once using the same queue name, mkque
      and mkvirprt will return FATAL ERRORS stating that the queue
      device already exists in /etc/qconfig. Not to worry, the new
      device will be correctly added to the specified queue in
      /etc/qconfig.

5.) Easy solutions to common problems:

  a.) Nothing prints using lp:

      Make sure that the printer is working correctly, the cables are
      good, the DECserver is working correctly and the telnetfilter
      is working by typing the following command:

      cat somefile | telnetfilter x ipaddress tcpport

      * More Concretely *

      cat /etc/qconfig | /usr/spool/telprt/telnetfilter x 11.22.33.44 2001

      If SOMETHING prints, all of the above components are probably working
      and the problem lies in the IBM print system.  If nothing
      prints, check all of the above components to ensure that they
      function properly.  To make sure that the DECserver is visible
      to your system:

      ping ipaddress

      Check the configuration of the DECserver port (The port must be
      set up as a Telnet Listener).

  b.) Extended ASCII characters print at beginning and end of job:

        /etc/chvirprt -qqueue -ddevice -a _J=! _j=!

      This turns off printer initialization and reset.

  c.) Blank page in between each file and at end of job:

      /etc/chvirprt -qqueue -ddevice -a _Z=!

      This turns off the form feed sent between files and at end of job.

  d.) After a form feed, the next line begins printing at the column it
      ended the previous page on:

      /etc/chvirprt -qqueue -ddevice -a af=\14\15

      This adds a CR to each FF.

  The printer attribute file is located in /usr/spool/lpd/pio/custom/
  The naming convention for the file is queue:device.  If you edit this
  file directly, make sure that you run /etc/chvirprt -qqueue -ddevice to
  digest the changes into the binary used by the system.

  To find out what attributes are defined for your printer and to
  change them:

          1) smit chvirprt
          2) choose the printer you want to work with
          3) enter a * to get a list and description of all attributes
          4) enter new attribute in the form attribute=newvalue
          5) when done, hit enter to terminate and update the database.

___________________________________________________________________________

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 BSD
4.3 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 Network Access Server, 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 Network Access Server.  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 existing 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
Network Access Server with no problems.

When the output filter first opens the Telnet Connection, it waits for
the Network Access Server 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 Network Access 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 probability 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.

SCO Unix systems operate in a manner very similar to Ultrix systems.
The SCO Unix print spooler passes the name of the file to be printed
to the interface program (in our case, to the
/usr/spool/lp/model/decserver shell script discussed above).  The
interface program then runs the filter.  The interface program first
creates the banner page and pipes it into the filter, and then pipes
the file to be printed into the filter.  The sysadmsh program (or
lpadmin) is used to tell the print spooling system which interface
program to use for each printer.  The /usr/spool/lp/remote file is
used to tell the interface program which filter to use for each
printer.

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.

Any number of systems on the network can 'point to' the same printer.
But while any one system is using the printer, the others will not be
able to connect to it. Telnet connect requests DO NOT queue on the
DECserver, LAT connect requests DO.  Both Telnet printers from Unix
and LAT printers from VMS can share the same printer device.  If the
port is busy when the Telnet printer tries to connect, it 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
(3600 seconds). Once it succeeds in opening a connection, all files
queued on that system will be printed, and the connection will be
closed.  LAT requests will queue up on the server while the port is
busy and will begin printing once the Telnet connection closes.
___________________________________________________________________________

*/

#ifdef SUN
#define SYSLOG3ARGS
#endif

#ifdef SYSV
#define NOUNIONWAIT
#endif

#ifdef AIX
#define NOUNIONWAIT
#endif

#ifdef OSF1
#define NOUNIONWAIT
#define SYSLOG3ARGS
#endif

#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 FOREVER while(1)

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

/* exit status values */
#ifdef SYSV
#define REPRINT 129
#endif

#ifndef SYSV
#define REPRINT 1
#endif

#define OKAY 0
#define ENDOFBANNER 2

/*
  How many seconds we will wait after all data is sent before we close
  the Telnet connection to the printer.  This period should allow all
  outstanding data  to be printed before connection close, unless the
  printer is currently XOFF'ing the Network Access Server.  (This is
  some protection against terminal servers which may discard unprinted
  data if the connection closes; the Network Access Server will not
  discard such data, however.)
*/
#define DISCONNECTTIMER 10
/*
  How many seconds we will sleep after closing the connection, to ensure
  that the printer is ready to take another connection.  In the Access
  Server, a new connection will not be accepted until 5 seconds or so
  have passed.
*/
#define INTERCONNECTGAP 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 FF '\f'       /* added form feed character definition 7/13/94 */
#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 characters 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 Access Server 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
                              makes 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 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_time; /* used as arg to "select" when we want to
                             poll_time 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 */
#ifdef SYSV
struct hostent _hp;
#endif

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,
              ff = FF;  /* added form feed character */

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

     unsigned char byte_from_lpd; /* character passed from lpd */


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

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

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

     unsigned char byte_from_net; /* character read from TCP connection */

     char *host, *service, *relayservice;

#ifdef SYSLOG3ARGS
     openlog(argv[0], LOG_PID, LOG_LPR);
#endif
#ifndef SYSLOG3ARGS
     openlog(argv[0], LOG_PID);
#endif
#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);
#ifndef SYSV
     signal(SIGXCPU, interruption);
#endif
/*
  Signals to ignore.
*/
     signal(SIGCONT, SIG_IGN);
#ifndef SYSV
     signal(SIGURG, SIG_IGN);
     signal(SIGALRM, SIG_IGN);
     signal(SIGVTALRM, SIG_IGN);
     signal(SIGWINCH, SIG_IGN);
     signal(SIGPROF, SIG_IGN);
#endif

/*
  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_time.tv_sec = 0;
     poll_time.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();
#ifndef SYSLOG3ARGS
     openlog(INPUT_FILTER ? "Telnet IF" :
            (OUTPUT_FILTER ? "Telnet OF" : "Telnet UF"), LOG_PID);
#endif
#ifdef SYSLOG3ARGS
     openlog(INPUT_FILTER ? "Telnet IF" :
            (OUTPUT_FILTER ? "Telnet OF" : "Telnet UF"), LOG_PID, LOG_LPR);
#endif
#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(service);

/*
  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 have.
*/
          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 = 0;
          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 */

/* Output a form feed character to eject last page from printer
   Added: wait_on_tcp(FALSE); and write_tcp(ff, DONTFLUSH); on 7/13/94
   to ensure that last page of each file gets ejected before printing
   of next file begins.  On some printers, this may cause an extra page
   to be ejected.  To stop this from happening, recompile this program
   with the symbol NOFF defined:
        cc -o telnetfilter -DNOFF telnetfilter.c
*/

#ifndef NOFF
          wait_on_tcp(FALSE);
          write_tcp(ff, DONTFLUSH);
#endif
          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 n_written, can_write;

     while(tcp_data)
         {
#ifdef DEBUG2
          can_write = tcp_mask;
          select(16, NULL, &can_write, NULL, &poll_time);
          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
          n_written = 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, wait_count, n, can_read;

/*   if ULTRIX_FILTER, write a form feed to clear printer.
        the following if statement was added 7/13/94.
        The purpose of the form feed is to clear the print buffer.
        The last page of each file was not ejecting from the printer
        and the next file printed would overwrite this page.  On some
        printers, this may cause an extra page to be ejected.  To stop
        this from happening, recompile this program with the symbol
        NOFF defined:
                cc -o telnetfilter -DNOFF telnetfilter.c

*/
#ifndef NOFF

     if (ULTRIX_FILTER)
        {
        wait_on_tcp(FALSE);
        write_tcp(ff,DONTFLUSH);
        }
#endif

     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
         {
          can_read = tcp_mask;
          select(16, &can_read, NULL, NULL, NULL);
          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;
          can_read = tcp_mask;
          select(16, &can_read, NULL, NULL, NULL);
          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;
          can_read = tcp_mask;
          select(16, &can_read, NULL, NULL, NULL);
          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
     */
     sleep(DISCONNECTTIMER);
     close(tcp_connection);
     /*
       After a connection to the Network Access Server is closed, no
       further connections can be made to the same port for about 5
       seconds.  So we'll sleep a little more before we really finish
     */
     sleep(INTERCONNECTGAP);
    }


/*
  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;
#ifdef SYSV
     hp = &_hp;
#endif
#ifndef SYSV
     sethostent(NULL);
     hp = gethostent();
#endif
     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(service)
    char *service;
    {
     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;
     set_port(service, &sp_printer);
     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(service)
    char *service;
    {
     int optlen = sizeof(tcp_maxseg);
     int maxseg_opt;

     init_outgoing(service);
     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(service);
         }
     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, can_write_to_tcp;

     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(OKAY);
                  }
              break;

          case '\1':
              /* see if lpd wants us to suspend */
              if (lpdstate == CTRLY)
                  {
                   lpdstate = DATA;
                   return(ENDOFBANNER);
                  }
              break;
         }


/*
  Now we write the character to the net.  (If we converted a character
  to a two-character sequence, we are now writing the second character.
  Therefore we must block until we can send it.)
*/
     if (n_written < 0)
         {
          syslog(LOG_DEBUG, "Internal Write Error 1");
          terminate(REPRINT);
         }
     lpdstate = DATA;
     wait_on_tcp(FALSE);
     n_written = write_tcp(byte_from_lpd, DONTFLUSH);
     if (n_written < 0)
         {
          syslog(LOG_DEBUG, "Internal Write Error 2");
          terminate(REPRINT);
         }
     return(OKAY);
    }



/*
  This subroutine is called in order to write bytes from the internal
  buffer (where they are put via the call to write_tcp) to TCP.  An
  attempt is made to write all the stored bytes, but since TCP is flow
  controlled, it may not be able to take them all.

  However, if the buffer is wrapped around, we write only to the end of
  the buffer.  The next call to push_tcp will start writing from the
  beginning.
*/

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_time);
     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, can_write_to_net;
     int if_mask;
     int i, n, n_written;
     int pid;
     int accept_retries = 1, later;
     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();
#ifndef OSF1
     openlog("Telnet OF Child", LOG_PID);
#endif
#ifdef OSF1
     openlog("Telnet OF Child", LOG_PID, LOG_LPR);
#endif
#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);

     *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_time);
          if (can_read_from_tcp) return(can_read_from_tcp);
         }
     if (tcp_data < OUTBUFSIZE) return(0);
     while (tcp_data >= OUTBUFSIZE)
         {
          can_read_from_tcp = need_to_read ? tcp_mask : 0;
          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(0);
    }



/*
  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);
    }