Internet Printing – Another Way LG #65

The Problem

You are doing some work on your home PC, connected to your favorite ISP – and you decide you want to print a Word document on the high-speed color printer at your office. That printer is connected to the corporate LAN, but you can’t talk to it using LPR or IPP because it is hidden behind the corporate firewall.

You could perform a print-to-file operation, then email the file to somebody at your office, and get them to send it to the printer. But there are a few steps here – and it gets more complicated if there is a restriction on the length of email messages which can be passed through one of the servers along the way. You will then have to perform some sort of file-split operation and send the individual parts.

Client Software

The people who make Brother printers thought of all this, and developed a set of Windows printer drivers. These enable users to print directly to a designated email address. The print-job is automatically split into parts if necessary, and each part is base64-encoded prior to transmission. Users can also nominate an address for email confirmation.

These Windows printer drivers (for Windows 95/98, and for Windows NT-4.0/2000) can be downloaded from the Brother website.

Printer Capabilities

What the Brother people expect users to do their printing on is, of course, a Brother printer – specifically, in this instance, one equipped with a network card able to accept, decode and re-assemble mail messages directed to it.

But what if you wish to print on a printer from another manufacturer?

READ  General Graphics Interface Project (GGI) LG #54

Doing it in Software

My first stab at this was a Korn-shell program to which appropriate incoming mail items were piped via a sendmail alias. The program used ‘awk’ to extract information such as job and part number, then decoded each such item into an appropriately named file in a designated directory.

After receiving a part, the program marked it as « complete », then set an anti-simultaneity lock and went through a procedure to determine if all necessary parts had been received in full. If they had, it concatenated them in sequence, piped the result to the nominated printer, and deleted them.

It was then that I started thinking: « What if there isn’t enough room to store all the parts for all the jobs which may currently be arriving? » And: « How do the Brother people do it on a network card? »

Doing it Without Local Storage

The answer to my second question is: « They use a POP3 server! ». The components of each job stay on that server until the network card determines that all necessary parts are available, at which stage it sucks them down and decodes them in sequence, sending the output to the printer mechanism, and requests their deletion from the server.

So here’s how it can be done on a Linux machine. The program has been written in Perl so that the NET::POP3 module can be used for easy access to a POP3 server. It has been tested on both NetBSD and Solaris machines, so it should work almost anywhere; all you’ll have to change are the location of the Perl interpreter, the name used for ‘awk’, and the format of the ‘lpr’ command. [Text version of this listing.]

#!/usr/bin/perl -w
# @(#) BIPprint.pl      Acquires Brother-Internet-Print files from POP3 server
#                       and passes them to designated printer(s). Small-memory
#                       version.  Intended for invocation via inittab entry.
#                       Graham Jenkins, IBM GSA, Feb. 2001. Rev'd: 17 Mar. 2001.

use strict;
use File::Basename;
use Net::POP3;
use Date::Manip;
use IO::File;
my $host="bronzeback.in.telstra.com.au";        # Same host and password for
my $pass="MySecret";                            # each printer.
my $limit=30*1024*1024;                         # Maximum bytes per print job.
my ($printer,$awkprog);
defined($ARGV[0]) || die "Usage: ", basename($0). " printer1 [ printer2 ..]\n";
open(LOG,"|/usr/bin/logger -p local7.info -t ".basename($0)); autoflush LOG 1;
$awkprog="";                            
while () {$awkprog = $awkprog . $_};      # Build awk program for later,
while (1) {                                     # then loop forever, processing 
  sleep 30;                                     # all printers in each pass, and
  foreach $printer (@ARGV) {process($printer);} # sleeping for 30 seconds
}                                               # between each pass.

sub process {
  my ($flag,$i,$j,$k,$l,$m,$allparts,$user,$pop,@field,@part,$count,$top15,
      $msgdate,$parsdate,$notify,$reply,%slot,$fh);
  $user = $_[0];
  $pop = Net::POP3->new($host);                 # Login to POP3 server and get
  $count = $pop->login($user,$pass) ;           # header plus 1st 15 lines
  $count = -1 if ! defined ($count) ;           # of each message. Use apop
  for ($i = 1; $i top($i,15) ;                   # supports it.
    if ($top15) {                       
      $msgdate = ""; $notify="None"; $reply="";
      for ($j = 0; $j < 99; $j++ ) {
        if (@$top15[$j]) {                      # Use arrival-date on POP3
          if($msgdate eq "") {                  # server to ascertain age of
            (@field) = split(/;/,@$top15[$j]);  # message; if it is stale,
            if ( defined($field[1])) {          # delete it and loop for next.
              $parsdate=&ParseDate($field[1]);  # (Search for semi-colon
              if( $parsdate ) {                 # followed by valid date.)
                $msgdate="Y";
                if(&Date_Cmp($parsdate, &DateCalc("today","-3 days") ) lt 0 ) {
                  print LOG "Stale msg: $user $parsdate\n";
                  $pop->delete($i);
                  goto I;                       # If POP3 server does
                }                               # automatic message expiration
              }                                 # this entire section can be
            }                                   # omitted.
          }
          (@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" ) { # Comment above line to
              ( @part )=split("/", $field[1]);  # prevent mail notification.
              chomp $part[1];           
            }
            if ( $field[0] eq "BRO-UID" ) {     # Determine print-job and part
              chomp $field[1];                  # thereof contained in message.
              $slot{$field[1]."=".$part[0]} = $i ;
              $allparts = "Y";                  # As we see each message, check
              for ($k=1;$kdelete($slot{$field[1]."=".$k}) ;
                }                               # If there is enough filespace,
                $fh->close;                     # pipe awk output thru gzip to
              }                                 # a temporary file, then print
            }                                   # it and delete all parts; this
          }                                     # caters for connection failure.
        }                       
      }                                         # The awk program here-under
    }                                           # is used to extract parts from
I:}                                             # a file containing multiple
  $pop->quit() if ($count >= 0);                # parts and feed each of them
}                                               # through a decoder to stdout.
__DATA__
if( Flag == 2 ) {
    Size=Size+length
    if(length == 0) { Flag=0; close("mmencode -u 2>/dev/null") }
    else if(Size