#!/opt/local/bin/perl -w
# @(#) BIPserver.pl	Acquires Brother-Internet-Print jobs from a POP3 server
#			and passes them to designated printer(s).
#			Rev'd: 2003-04-09.
#
# Copyright (c) 2003 Graham Jenkins <grahjenk@au1.ibm.com>. 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 File::Basename;
use Net::POP3;
use Net::SMTP;
use Sys::Syslog;
use MIME::Base64;
use Compress::Zlib;
use Printer;
use Env qw(MAXMESS MAXJOB EXPIRE SLEEP);	# Maximum message-size (bytes),
use vars qw($VERSION);				# job-size (bytes), expire-time
$VERSION = "1.06";				# (secs) and sleep-time (secs).
my $maxmess=$MAXMESS?$MAXMESS:23000000;
my $maxjob =$MAXJOB ?$MAXJOB :33554432;
my $expire =$EXPIRE ?$EXPIRE :10800;
my $sleep  =$SLEEP  ?$SLEEP  :30;
my %uidtime;
defined($ARGV[0]) || die "Usage: ", basename($0). " printer1 [printer2 ..]\n";
syslog ('local7|info',basename($0)." starting ..");

while (1) {					# Loop forever, processing
  foreach my $user (@ARGV) {			# print-queues in turn.
    my (@field,@part,%slot,%tp); sleep $sleep;
    my $pop=Net::POP3->new() or next;		# Login to POP3 server and get
    my $count=$pop->login($user) or next;	# header plus 1st 15 lines of
  I:for (my $i=1;$i<=$count;$i++) {		# each message. Mark old and
      my $s=$pop->list($i) or next;		# over-size messages for
      my $u=$user."=".$pop->uidl($i) or next;	# deletion.
      $uidtime{$u}=time if ! exists $uidtime{$u};
      if ((time-$uidtime{$u})>$expire) {$uidtime{$u}=0}
      if (($s>$maxmess) or ($uidtime{$u}<1)) {	# Delete marked messages; this
        $pop->delete($i);			# mechanism picks up messages
        syslog('local7|info',"$u expired!");next# which didn't get deleted due
      }						# to server connection failure.
      my $top15=$pop->top($i,15) or next;	
      my $notify="None"; my $reply="";
      for (my $j=0;$j<99;$j++) {
        if (@$top15[$j]) {		
          (@field)=split(/=/,@$top15[$j]);
          if (defined($field[0])) {	
            if ($field[0] eq "BRO-NOTIFY" ) {chomp $field[1]; $notify=$field[1]}
            if ($field[0] eq "BRO-REPLY"  ) {chomp $field[1]; $reply =$field[1]}
            if ($field[0] eq "BRO-PARTIAL") {	# Success is notified if
              (@part)=split("/",$field[1]);	# REPLY address is supplied
              chomp $part[1]			# and NOTIFY is not "None".
            }
            if ($field[0] eq "BRO-UID") {	# Determine print-job and part
              chomp $field[1];			# thereof contained in message.
              $slot{$field[1]."=".$part[0]}=$i;
              $tp{$field[1]}=$part[1] if $part[1] eq $part[0]; 
              next I if ! defined ($tp{$field[1]});
              for (my $k=1;$k<=$tp{$field[1]};$k++) {
                next I if ! defined($slot{$field[1]."=".$k})
              }					# All parts are on server?
              my $dec; my $zip="N";		# If so, get and decode them!
              for (my $k=1;$k<=$tp{$field[1]};$k++) {
                my $buf=$pop->get($slot{$field[1]."=".$k}) or next I;
                my $f=0; my $l=0; my $enc=""; my $sep="";
                while (defined (@$buf[$l])) {
                  chomp @$buf[$l];
                  if   (($f==0)&&(@$buf[$l]=~m/^BRO-LANG=Z/))         {$zip="Y"}
                  elsif(($f==0)&&(@$buf[$l]=~m/^--/))      {$sep=@$buf[$l]."--"}
                  elsif(($f==0)&&(@$buf[$l]=~m/^Content-Transfer-E.+64$/)){$f=1}
                  elsif(($f==1)&&(length(@$buf[$l])<2))                   {$f=2}
                  elsif(($f==2)&&( @$buf[$l] ne $sep ))        {$enc.=@$buf[$l]}
                  elsif( $f==2 ) {
                    $enc=~tr|A-Za-z0-9+=/||cd; next I if length($enc)%4;
                    $dec.=decode_base64($enc);
                    if (length($dec)<=$maxjob)		                  {last}
                    $uidtime{$u}=0; next I
                  }
                  $l++
                }
              }	#$k
              if ($zip eq "Y") {		# Uncompress as appropriate.
                if (defined (my $unc=uncompress($dec))) {$dec=$unc}else{$dec=""}
              }
              if (length($dec)>0) {		# Mark one part for deletion,
                $uidtime{$u}=0;			# then print decoded string.
                my $prn=new Printer($^O=>"$user"); $prn->print($dec);
                syslog('local7|info',"$field[1] $tp{$field[1]} => $user");
                if (($notify ne "None") && ($reply ne "")) {
                  my $smtp=Net::SMTP->new();
                  $smtp->mail($ENV{USER}); $smtp->to($reply);
                  $smtp->data("Subject: Job $field[1] for Printer $user\n",
                    length($dec)." bytes printed from $tp{$field[1]} parts!");
                  $smtp->quit
                }
              }
            }
          }
        }
      } #$j
    } #$i
    if (defined $count) {			# If a server mailbox is empty
      $pop->quit();				# clean out all records relating
      if ($count<1) {				# to it.
        foreach my $r (keys(%uidtime)) {delete $uidtime{$r} if $r=~m#^$user=#}
      }
    }
  } #$user
}

__END__

=head1 NAME

BIPserver - server for Brother-Internet-Print protocol

=head1 README

BIPserver acquires Brother-Internet-Print jobs from
a POP3 server and passes them to designated printers.

=head1 DESCRIPTION

C<BIPserver> is a simple server program for the 
Brother-Internet-Print protocol. It should be started
at boot time, and will run continually
thereafter, acquiring jobs which have been sent to
designated addresses on a POP3 server, and passing
them to corresponding printers.

The program has been designed to handle multi-part jobs,
and to accomodate unreliable connections to its
associated POP3 server. All job parts are left on the
POP3 server until a full set is available; they are
then assembled for printing and marked for deletion. No
local filespace is used. Automatic expiration of orphan
parts is also performed.

This program is able to process compressed jobs fed to
it by the C<BIPclient> program (versions 1.05 or later).

=head1 USAGE

=over 4

BIPserver printer1 [printer2] .. 

=back

Mailboxes having the names of each printer designated on
the command line are accessed in turn, and job-parts
residing therein are extracted and assembled for printing.

Default values are assumed for maximum message-size (bytes),
maximum job-size (bytes), message-expire time (secs) and
sleep-time (secs); these can be adjusted via the environment
variables MAXMESS, MAXJOB, EXPIRE and SLEEP.

The POP3 server is determined using Net::Config, and login
passwords are extracted using Net::Netrc.

An appropriate Windows client program can be downloaded
from <www.brother.com>; a multiplatform implementation is
available from the author.

=head1 SCRIPT CATEGORIES

Networking
UNIX/System_administration

=head1 AUTHOR

Graham Jenkins <grahjenk@au1.ibm.com>

=head1 COPYRIGHT

Copyright (c) 2003 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
