#!/usr/sbin/perl
#
==============================================================================
# LPRng/filters/phaserif                Fri Jan  8 09:47:35 1999       
has
#
==============================================================================
#
use Getopt::Std;
use Socket;
use Sys::Hostname;

# -- Exit codes --
$JSUCC = 0;
$JFAIL = 32;
$JABORT = 33;
$JREMOVE = 34;
$JHOLD = 37;


# -- Network info for printer --
$printhost = 'NAME_OF_PRINTER_HOSTNAME_HERE';
$printip = inet_aton($printhost);
$udpport = '9101';
$tcpport = '9100';

$Medium='Paper';
$PSJob='';

#
#==============================================================================
#==============================================================================
#

pop @ARGV;

# Get all the filter options LPRng knows

getopts('a:b:c:d:e:f:h:i:j:k:l:m:n:p:r:s:t:w:x:y:F:P:S:C:H:A:J:L:Q:');

@foo = split(/\//, $0);
$prog = pop @foo;

# -- Set default error messages --
$udpsockerr = "$prog(ERROR): UDP socket: $!\n";
$udpbinderr = "$prog(ERROR): UDP bind: $!\n";
$udpsenderr = "$prog(ERROR): UDP send status request: !$\n";
$udprecverr = "$prog(ERROR): UDP receive status: !$\n";
$tcpsockerr = "$prog(ERROR): TCP socket: $!\n";
$tcpcloseerr = "$prog(ERROR): TCP socket close: $!\n";
$udptimeout = "$prog(ERROR): UDP timeout -- printer off line\n";
$tcptimeout = "$prog(ERROR): TCP timeout -- printer off line\n";

# -- Get current time/date --
@MONTHS = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
           "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$month = $MONTHS[$mon];
if (length($sec)  == 1)  { $sec = "0$sec" }
if (length($min)  == 1)  { $min = "0$min" }
if (length($hour) == 1)  { $hour= "0$hour" }
if (length($mday) == 1)  { $mday= " $mday" }

$datestamp = "$month $mday $hour:$min:$sec";

# -- Write 'job begin' line to printer log file --
print STDERR "$prog(START): job $opt_j = $opt_f for $opt_L on $opt_P at
$datestamp\n";

#
#------------------------------------------------------------------------------
#  Prepare the print job
#------------------------------------------------------------------------------
#
# Get ready to scan the print file
open( DFILE, "$opt_e" ) or die "$prog(ERROR): can't open spool file
$opt_e: $!\n";

$DocType = '';
$line=<DFILE>;                             # Get first line of file

# Skip first line  if ctrl-D
# ??? What if there is no line separator before %!PS ???
if ( $line =~ /^\004/ ) { $line=<DFILE>; };
# Check for raw PS doc
if ( $line =~ /^%!PS/ ) { $DocType='PS'; };

#  if ( $opt_L =~ /netatalk/ ) {
#  
#DVP #  # -- Authenticate a job from netatalk --
#DVP #  #    Scan for client name, job name, and medium type
#DVP #  
#DVP #  $opt_J = '';
#DVP #  NTLoop:  while ($line=<DFILE>) {
    #DVP #  $nlines++;
    #DVP #  if ( $line =~ /%%For:\s\((.*)\)/ ) { $Client = $1; }
    #DVP #  if ( $line =~ /%%Title:\s\((.*)\)/ ) { $opt_J = $1; }
    #DVP #  if ( $line =~ /\/MediaType \(.*\)/ ) { $Medium = $1; }
    #DVP #  if ( $Client && $Medium && $opt_J ) { last NTLoop; }
  #DVP #  }
  #DVP #  if ( !$Client ) { 
    #DVP #  print STDERR"$prog(REJECTED): No Client ID in netatalk print
file\n";
    #DVP #  exit $JREMOVE;
  #DVP #  }
  #DVP #  print STDERR "$prog(INFO): job $opt_j = netatalk job
$Client\n";
  #DVP #  $opt_L = "$opt_L($Client)";
#DVP #  
#DVP #  # -- Authenticate other jobs --
#DVP #  
#DVP #  } else {
#DVP #  OJLoop:  while (<DFILE>) {
    #DVP #  $nlines++;
    #DVP #  if (/\/MediaType \(.*\)/) {
      #DVP #  split /[\(,\)]/;
      #DVP #  $Medium = $_[1];
      #DVP #  last OJLoop;
    #DVP #  }
  #DVP #  }
#DVP #  }
#DVP #  
#DVP #  #  Append print medium type to printer name if given
#DVP #  if ( $Medium ) { $opt_P = "$opt_P($Medium)"; }
 #DVP #   
#------------------------------------------------------------------------------
#       Check if the printer is on line and free
#------------------------------------------------------------------------------

# -- Get UDP socket info for status from printers --
$myip = gethostbyname(hostname());
$udpproto = getprotobyname('udp');
$myudppaddr = sockaddr_in(0, $myip);
$printudppaddr = sockaddr_in($udpport, $printip);     

socket(UDPSOCK, PF_INET, SOCK_DGRAM, $udpproto) or die $udpsockerr;
bind(UDPSOCK, $myudppaddr) or die $udpbinderr;

# -- Send <cr><lf> over UDP to provoke 'status: xxx' --
defined(send(UDPSOCK, "\r\n", 0, $printudppaddr)) or die $udpsenderr;
$udpans = "";
($printudppaddr = recv(UDPSOCK, $udpans, 100, 0)) or die $udprecverr;

# -- Check for printer on line and idle. Wait if on line and busy --
Uloop: while (1) {
# Try for UDP socket connection, timeout after 10 seconds
  eval {
    local $SIG{ALRM} = sub { die $udptimeout };
    alarm 20;
    defined(send(UDPSOCK, "\r\n", 0, $printudppaddr)) or die $udpsenderr;
    $udpans = "";
    ($printudppaddr = recv(UDPSOCK, $udpans, 100, 0)) or die $udprecverr;
    alarm 0;
  };
  if ($@) { die $@; }
  @foo = split(/; /,$udpans);
  foreach my $str (@foo,'*down*') {
    last Uloop if ($str eq 'status: idle');
    last if ( $str =~ 'status: ' );
    if ($str eq '*down*') {
      die "$prog(ERROR): unexpected response $udpans\n";
    }
  }
  sleep 5;
}

#------------------------------------------------------------------------------
#       Open a TCP socket to the printer
#------------------------------------------------------------------------------
#  1) to get initial page count
#  2) to send configuration files
#  3) to pump out the print job
#
$tcpproto = getprotobyname('tcp');
$mytcppaddr = sockaddr_in(0, $myip);
$printtcppaddr = sockaddr_in($tcpport, $printip);

# -- Try to connect to TCP port, wait if connection refused  --
T1Loop: for ($i=0; $i<30; $i++) {
  eval {
    local $SIG{ALRM} = sub { die $tcptimeout };
    alarm 20;
    socket(TCPSOCK, PF_INET, SOCK_STREAM, $tcpproto) or die $tcpsockerr;
    setsockopt(TCPSOCK, SOL_SOCKET, SO_KEEPALIVE, 0);
    setsockopt(TCPSOCK, SOL_SOCKET, SO_LINGER, 0);     
    connect(TCPSOCK, $printtcppaddr) or die $!;
#   The error message from connect is available after the eval{} if
needed.
    alarm 0;
  };
  last T1Loop if (!$@);
# Fatal error messages start with the special leader
  if ($@ and $@ =~ /$prog(ERROR)/) { die $@; }
  close TCPSOCK or die $tcpcloseerr;
  sleep 10;
}
if ($i == 30) { die "$prog(ERROR): TCP connect timeout (5 min.)\n"; }

# -- Get the initial page count --
select TCPSOCK;
$| = 1;
select STDOUT;
print TCPSOCK "%!\n";
print TCPSOCK "(%%\[ pagecount: )print statusdict /pagecount get exec ";
print TCPSOCK "(                )cvs print ";
print TCPSOCK "( \]%%) = flush\n";
$tcpout = <TCPSOCK>;
if ($tcpout =~ /%%\[ pagecount:.*/) {
  @tcparray = split /\s/, $tcpout;
  $pagecount1 = $tcparray[2];
}

# -- Get current time/date for the accounting file --
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$month = $MONTHS[$mon];
if (length($sec)  == 1)  { $sec = "0$sec" }
if (length($min)  == 1)  { $min = "0$min" }
if (length($hour) == 1)  { $hour= "0$hour" }
if (length($mday) == 1)  { $mday= " $mday" }

$datestamp = "$month $mday $hour:$min:$sec";

print "start($prog) -p'$pagecount1' -P'$opt_P' -j'$opt_j' -J'$opt_J'
-f'$opt_f' -b'$opt_b' -L'$opt_L' -h'$opt_h'
-P'$opt_P' -t'$datestamp'\n";

# -- open LPRng accounting file --
if( defined( $opt_a ) && $opt_a && open ACCT, ">>$opt_a" ){
print ACCT "start($prog) -p'$pagecount1' -P'$opt_P' -j'$opt_j'
-J'$opt_J' -f'$opt_f' -b'$opt_b' -L'$opt_L' -h'$opt_h'
-P'$opt_P' -t'$datestamp'\n";  
close ACCT;
}

# -- Output print job to printer --
while ($line = <STDIN>) {
  print TCPSOCK $line;
}

#  -- Close connection when printing is complete --
close TCPSOCK or die $tcpcloseerr;

#------------------------------------------------------------------------------
#       Get final page count (same as initial TCP connect above)
#------------------------------------------------------------------------------
#
#     * printer refuses connection until print job is done *
T2Loop: for ($i=0; $i<30; $i++) {
  eval {
    local $SIG{ALRM} = sub { die $tcptimeout };
    alarm 20;
    socket(TCPSOCK, PF_INET, SOCK_STREAM, $tcpproto) or die $tcpsockerr;
    setsockopt(TCPSOCK, SOL_SOCKET, SO_KEEPALIVE, 0);
    setsockopt(TCPSOCK, SOL_SOCKET, SO_LINGER, 0);     
    connect(TCPSOCK, $printtcppaddr) or die $!;
    alarm 0;
  };
  last T2Loop if (!$@);
  if ($@ and $@ =~ /$prog(ERROR)/) { die $@; }
  close TCPSOCK or die $tcpcloseerr;
  sleep 10;
}
if ($i == 30) { die "$prog(ERROR): TCP connect timeout (5 min.)\n"; }

print TCPSOCK "%!\n";
print TCPSOCK "(%%\[ pagecount: )print statusdict /pagecount get exec ";
print TCPSOCK "(                )cvs print ";
print TCPSOCK "( \]%%) = flush\n";
$tcpout = <TCPSOCK>;
if ($tcpout =~ /%%\[ pagecount:.*/) {
  @tcparray = split /\s/, $tcpout;
  $pagecount2 = $tcparray[2];
}
close TCPSOCK or die $tcpcloseerr;

#------------------------------------------------------------------------------
#       Finish up accounting and log output
#------------------------------------------------------------------------------

# -- Get date/time again --
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
$month = $MONTHS[$mon];
if (length($sec)  == 1)  { $sec = "0$sec" }
if (length($min)  == 1)  { $min = "0$min" }
if (length($hour) == 1)  { $hour= "0$hour" }
if (length($mday) == 1)  { $mday= " $mday" }
$datestamp = "$month $mday $hour:$min:$sec";

# -- Update accounting file upon close --
if( defined( $opt_a ) && $opt_a && open ACCT, ">>$opt_a" ){
print ACCT "end($prog) -p'$pagecount2' -P'$opt_P' -J'$opt_J' -f'$opt_f'
-L'$opt_L' -h'$opt_h' -t'$datestamp'\n";
close ACCT;
}

# -- Write a 'Job End' line to the printer log --
print STDERR "$prog(END): job $opt_j OK at $datestamp\n\n";

exit $JSUCC;
