#!/usr/bin/perl -w
# SEPclient.pl		Secure Email Print client program (GPG-based).
#			Takes incoming stream and generates signed message
#			which is split in accordance with RFC 2046, then
#			emailed to server. Rev'd 2003-03-10.
#
# 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 Getopt::Std;
use File::Basename;
use Net::hostent;
use Net::SMTP;
use Net::Config;
use IO::Handle;
use Socket;
use Env qw(HOME PGPPASS);
use vars qw($VERSION $opt_P $opt_n);
$VERSION = "1.01";
my $Config="/usr/local/conf/SEPclient.cf";	## Adjust as necessary.

getopt('Pn');					## Usage 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}});
mes ('info',"Seeking records for printer $opt_P  and user $opt_n");

my (@printrec,@userrec,$Gpg);			## Address lookup.
if (open(FILE,$Config)) {
  while (<FILE>) {
    chomp; (my @r)=split;
    if  ( ($#r>=3) && ($r[0] eq "P") && ($r[1] eq $opt_P) &&
          ($r[2]=~/^\d+$/) && ($r[2]>=16) )                      {@printrec=@r}
    elsif(($#r>=2) && ($r[0] eq "U") && ($r[1] eq $opt_n) )      {@userrec=@r }
    elsif(($#r==1) && ($r[0] eq "G") )                           {$Gpg=$r[1]  }
  }
}
mes ('warning',"Valid records for printer $opt_P  or user $opt_n ".
     "not found in file $Config") unless ( (@printrec) && (@userrec) );
mes ('warning',"Name of 'gpg' executable not found") unless (defined $Gpg);

if ( open(FILE,"/etc/passwd") ) {		## Environment correction.
  while (<FILE>) { (my @r)=split /:/; if ( $r[2] eq $< ) {$HOME=$r[5]}}
}						## (Required with LPRng.)

my ($Child,$FilDes,$Stamp,$InpBuf,$OutBuf,$Number);
$Number=0; $OutBuf=$InpBuf=""; $Stamp=time;
if (defined $PGPPASS) { 
  eval {socketpair(CHILD,PARENT,AF_UNIX,SOCK_STREAM,PF_UNSPEC)};
  undef $PGPPASS if $@				## If 'socketpair' is supported,
}		 				## accept password from 
if (defined $PGPPASS) {				## environment variable. 
  CHILD->autoflush(1);
  if ($Child = fork) {
    close(PARENT);
    print CHILD $PGPPASS, "\n";
    waitpid($Child,0);
    exit
  }
  else {
    close(CHILD);
    $FilDes=fileno(PARENT);
    open (PIPE,"$Gpg -as --passphrase-fd $FilDes -o - |")
  }
}
else {open (PIPE,"$Gpg -as -o - |")}

while (<PIPE>) {				## Double-buffered read.
  if ( length($InpBuf.$_) > ($printrec[2]*1024) ) {do_output()}
  $InpBuf=$InpBuf.$_
}
foreach my $j (1,2) {do_output()}		## EOF: flush both buffers.
exit 0;

sub do_output {					## Output subroutine.
  my ($Total,$Content);
  if ($OutBuf ne "") {
    $Number++; $Total="";
    $Content="message/partial; id=\"$userrec[2].$Stamp\"; number=$Number";
    if ($InpBuf eq "") {$Total=$Number; $Content="$Content; total=$Total"}
    for (my $t=1;$t<=3;$t++) {			## (3 tries for each Mailhost.)
      foreach my $mailhost (@Hosts) {
        my $smtp; my $k=2; my $Rt="";
        mes ('debug',"Trying SMTP host $mailhost");
        if (gethost($mailhost)   && ($smtp=Net::SMTP->new($mailhost))     &&
            $smtp->mail($userrec[2])                          ) {} else {next}
        while ( defined $printrec[++$k] ) {
          if ( $smtp->to($printrec[$k]) )                       {} else {next}
        }
        if (defined $userrec[4]) { my $Rt=$userrec[4]."\n" }
        if ($smtp->data($Rt."Subject: Secure Email Print Job $Stamp /$Number\n".
              "MIME-Version: 1.0\n"."Content-Type: $Content\n\n".$OutBuf) &&
            $smtp->quit                                                     ) {
          mes ('debug',"Part $Number/$Total => $mailhost");
          goto L
        }
      }
    }
    mes ('warning',"Couldn't send part $Number!")
  }
L:$OutBuf = $InpBuf;				## Move input buffer contents
  $InpBuf = ""					## to output buffer and exit.
}

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

=head1 NAME

SEPclient - Secure Email Print client program (GPG-based).

=head1 README

SEPclient is a Secure Email Print client
which uses GPG to sign a print job before
splitting and emailing it to a server.

=head1 DESCRIPTION

C<SEPclient> is a Secure Email Print client program (GPG-based).
It takes an incoming stream and generates a signed message which 
is split in accordance with RFC 2046, then emailed to a server.
A simple configuration file is used to determine a part-size and
destination address for each remote printer.

=head1 USAGE

=over 4

SEPclient -P printer -n user

=back

Windows users may find it convenient to use a utility like
'redmon' <www.cs.wisc.edu/~ghost/redmon> for feeding print
jobs to this program.

On platforms which support the B<socketpair> function, the
user's GPG passphrase may be passed in the PGPPASS
environment variable.

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 PREREQUISITES

This script requires the 'gpg' executable. The C<Net> module
is also required. The SMTP servers whose names appear in that
module's 'libnet.cfg' file will be tried in turn for sending
each job part.

=head1 CONFIGURATION

A typical configuration file is shown hereunder. It contains
entries for printers ('P' in first column), users ('U' in
first column), and 'gpg' executable ('G' in first column). The
addresses associated with user names are
used to provide sender-addresses which are acceptable the SMTP
host(s), an optional reply-condition (currently ignored) and
an optional reply-to address.

 # Printers
 #Flag Name PartSz(kb) Address1       [Address2] ..
 P     lpt1 16         lpt1@acme.com
 P     lpt2 128        lpt2@acme.com  lpt2@acme.co.uk
 # Users
 #Flag Name Address        [Reply-Condition]  [Reply-To]
 U     foo  foo@acme.co.uk
 U     bar  bar@acme.co.uk Always              bar@hotmail.com
 # GPG
 #Flag Executable
 G     /usr/bin/gpg

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; the GPG key to be used should in this case be contained in
the home directory of the printer-daemon user, and there should
be no passphrase.

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


=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
