#!/usr/bin/perl -w
# @(#) BIPclient.pl	Brother-Internet-Print client.
#			Rev'd: 2007-02-12.
#
# Copyright (c) 2007 Graham Jenkins <grahjenk@cpan.org>. All rights reserved.
# This program is free software; you can redistribute it and/or modify it under
# the same terms as Perl itself.

use strict;
use Getopt::Std;
use File::Basename;
use Net::Config;
use Compress::Zlib;
use Net::hostent;
use Net::SMTP;
use MIME::Base64;
use vars qw($VERSION $opt_P $opt_n);
$VERSION = "1.07";				## You can change the next line.
my @Conf=("C:\\Windows\\BIPclien.ini","/usr/local/etc/BIPclient.cf");

getopt('Pn');					## Usage/Configuration checks.
mes ('warning',"Usage.. ". basename($0). " -P printer -n user") 
  unless ( defined($opt_P) && defined($opt_n) );
mes ('warning',"No SMTP Hosts defined")
  if ! (my @Hosts=@{$NetConfig{smtp_hosts}});

my (@printrec, @userrec); 			## Address lookup.
foreach my $F (@Conf) {
  if (open(FILE,$F)) {
    mes ('info',"Seeking records for $opt_P and $opt_n in $F");
    while (<FILE>) {
      chomp; (my @r)=split;
      if  ( ($#r>=3) && ($r[0] eq "P") && ($r[1] eq $opt_P) &&
            ($r[2]=~/^[\d,-]\d+$/) && (abs($r[2])>=16) )         {@printrec=@r}
      elsif(($#r>=3) && ($#r<=4) && ($r[0] eq "U") && ($r[1] eq $opt_n) )
                                                                 {@userrec=@r }
    }
  }
}
if ( (@userrec) && ($#userrec==3) ) {$userrec[3]="Never"; $userrec[4]=""} 
mes ('warning',"Valid printer/user records not found")
  unless ( (@printrec) && (@userrec) );

my @InpBuf; my $TotalParts=0; my $Pr="0"; binmode STDIN;
if($printrec[2]<0){				## Read and split STDIN.
  my ($s,$i); my $o=""; my $w="Compress failed";## Compress if requested ..
  my $x=deflateInit(-Level=>Z_BEST_COMPRESSION) or mes('warning',$w);
  while (read(STDIN,$i,4096)) {
    ($i,$s)=$x->deflate($i);           $s==Z_OK or mes('warning',$w); $o.=$i
  }
  ($i,$s)=$x->flush();                 $s==Z_OK or mes('warning',$w); $o.=$i;
  $i=0; $Pr="Z";
  while (length($s=substr($o,$i,-1024*$printrec[2]))>0) {
    $InpBuf[++$TotalParts]=$s;$i+=length($s)
  }
}
else {						## .. or don't compress.
  while (read(STDIN,$InpBuf[++$TotalParts],1024*$printrec[2]) ) {};--$TotalParts
}

my @l=localtime;				## Generate a sequence-number.
my $seq=sprintf "%04d%02d%02d%02d%02d%02d%01d",
        $l[5]+1900,$l[4]+1,$l[3],$l[2],$l[1],$l[0],$$%10; 

foreach my $PartNo (1..$TotalParts) {		## Encode and send each part.
T:for (my $t=1;$t<=3;$t++) {
  H:foreach my $Host (@Hosts) {
      my $smtp; my $k=2;
      mes ('debug',"Trying SMTP host $Host");
      if ( gethost($Host) && ($smtp=Net::SMTP->new($Host)) &&
                             $smtp->mail($userrec[2])        ) {} else {next H}
      while ( defined $printrec[++$k] ) {
        if ( $smtp->to($printrec[$k]) )                        {} else {next H}
      }
      if ( $smtp->data("MIME-Version: 1.0\n".
             "Subject: Job for Printer $opt_P\n".
             'Content-Type: multipart/mixed; boundary="=_cuthere"'."\n\n\n".
             "--=_cuthere\n".
             'Content-Type: text/plain; charset="US-ASCII"'."\n\n".
             "START-BROBROBRO-START\nBRO-SERVICE=ZYXWVUTSRQ980\n".
             "BRO-NOTIFY=$userrec[3]\n".
             "BRO-REPLY=$userrec[4]\n" .
             "BRO-PARTIAL=$PartNo"."/"."$TotalParts\n".
             "BRO-UID=$userrec[2]".$seq."\n".
             "BRO-LANG=$Pr"."x09\nSTOP-BROBROBRO-STOP\n\n--=_cuthere\n".
             'Content-Type: application/octet-stream; name="PrintJob.PRN"'."\n".
             "Content-Transfer-Encoding: base64\n\n".
             encode_base64($InpBuf[$PartNo])."\n--=_cuthere--\n") &&
           $smtp->quit                                       ) {
        undef $InpBuf[$PartNo];
        mes ('debug',"Part $PartNo/$TotalParts => $Host");
        last T
      }
    }
    sleep 1
  }
  mes ('warning',"Couldn't send part: $PartNo!") if defined $InpBuf[$PartNo]
}

exit 0;						## All done!

sub mes {					## Message/exit function.
  eval "use Sys::Syslog";
  if ($@) {print STDERR $_[1],"\n"}
  else    {syslog ('lpr|'.$_[0],basename($0)." $_[1]")} 
  exit (1) if ($_[0] eq 'warning')
}
__END__

=head1 NAME

BIPclient - client for Brother-Internet-Print protocol

=head1 README

BIPclient uses the Brother-Internet-Print protocol
for emailing jobs to remote printers.

=head1 DESCRIPTION

C<BIPclient> is a simple client program for the 
Brother-Internet-Print protocol. It can be invoked
as a BSD print filter, and uses a simple configuration
file to determine the email address(es) which should
be used for each remote printer. 

The program has been designed to mimic the functionality
of its Windows counterpart (available at <www.brother.com>).
To that end, it uses the above-mentioned configuration
file to associate a reply-address and a reply-condition
with each user.

=head1 USAGE

=over 4

BIPclient -P printer -n user

=back

The SMTP server(s) are determined using Net::Config, and are
tried in turn for each job part. Progress messages are written
using the 'lpr' facility of syslog, at severity levels 'debug',
'info' and 'warning'. If syslog is not available, STDERR is
used for all messages.

=head1 CONFIGURATION

By default, the program tries to open the configuration files
C:\Windows\BIPclien.ini and /usr/local/etc/BIPclient.cf; the
program can be edited to change these if necessary.

A typical configuration file is shown hereunder. It contains
entries for printers ('P' in first column) and users ('U' in
first column). The meanings of the field contents are the same
as those in the program's Windows counterpart. In addition, a
part-size may be negated to request that compression should
be attempted; this should only be done where the corresponding
servers are able to perform decompression.

 # Printers
 #Flag Name PartSz Address-1      [Address-2] ..
 P     lpt1 128    lpt1@acme.com  lpt1@acme.com.au
 P     lpt2 -16    lpt2@acme.com
 # Users
 #Flag Name        Address        Condition   [Reply-To]
 U     foo         foo@acme.co.uk Always      foo@hotmail.com 
 U     bar         bar@acme.co.uk Never

The program can be invoked as an input filter on a machine which
uses BSD-style printing using a 'printcap' entry of the following
form:

 lpt1:\
     :lp=/dev/null:sd=/var/spool/lpd/lpt1:\
     :if=/usr/local/bin/BIPclient.pl:mx#0:sh: 

=head1 SCRIPT CATEGORIES

Networking
UNIX/System_administration

=head1 AUTHOR

Graham Jenkins <grahjenk@cpan.org>

=head1 COPYRIGHT

Copyright (c) 2007 Graham Jenkins. All rights reserved.
This program is free software; you can redistribute it
and/or modify it under the same terms as Perl itself.

=cut
