#!/usr/bin/perl -w

##############################################################################
#
# Print billing management system - spooler script for lprng.
#
# Version 4.1.2
#
# Copyright (C) 2000, 2001, 2002, 2003 Daniel Franklin
#
# This program is distributed under the terms of the GNU General Public
# License Version 2.
#
# This is a print accounting and billing filter.
#
# To use this, you need the following (or similar) lines in your printcap
# file:
#
#lp|default printer:\
#	 :lp=/dev/lp0
#	 :achk=true
#	 :as=|/usr/sbin/printbill --type [bill|lazybill|accountonly] [-printbill_secondary printer]
#        :if=/etc/magicfilter/psonly600-filter
#	 :sd=/var/spool/lpd/inkjet
#	 :mx#0
#	 :sh
#
# Refer to the man page to see the additional stuff required if you use
# --type bill
#
##############################################################################

use IO::Socket::UNIX;
use Getopt::Long;
use Sys::Syslog qw(:DEFAULT setlogsock);
use Printbill::printbill_pcfg;
use strict;

my $sversion;
my $cversion = "4.1.2";
my $JREMOVE = 3;
my $configdir = "/etc/printbill";
my $config = "$configdir/printbillrc";
my $sockname = "/tmp/printbilld";
my $line = "";
my ($smajor, $sminor, $spointrel, $cmajor, $cminor, $cpointrel);
my ($sock, $msg);
my ($jobname, $job, $remote_host, $secondary, $quoteprinter, $cf, $spooldir, $user, $printer, $type, $response);
my (%params, @filenames);

%params = pcfg ($config);

&abortjob ("$0: problems parsing configuration file\n") if (!defined scalar (%params));

setlogsock 'unix';
openlog $0, 'cons', 'lpr';

# Parse standard command-line options sent to us by lprng

if ($] >= 5.005) {
	Getopt::Long::Configure ("pass_through");
	Getopt::Long::Configure ("bundling");
} else {
	Getopt::Long::config ("pass_through");
	Getopt::Long::config ("bundling");
}

GetOptions ("type=s" => \$type, "J=s" => \$jobname, "j=n" => \$job, "H=s" => \$remote_host, "printbill_secondary=s" => \$secondary, "printbill_printer=s" => \$quoteprinter, "n=s" => \$user, "k=s" => \$cf, "P=s" => \$printer, "d=s" => \$spooldir);

&abortjob ("$0: must specify type via --type option (may be bill, lazybill, account or quote)")
	if (!defined $type || ($type ne "bill" && $type ne "lazybill" && $type ne "account" && $type ne "quote"));
	
&abortjob ("$0: must specify job number via -j option") unless (defined $job);

&abortjob ("$0: must specify remote host via -H option") unless (defined $remote_host);

&abortjob ("$0: must specify username via -n option") unless (defined $user);

&abortjob ("$0: must specify printer name via -P option") unless (defined $printer);

&abortjob ("$0: must specify control file via -k option") unless (defined $cf);

&abortjob ("$0: must specify spool directory via -d option") unless (defined $spooldir);

&abortjob ("$0: must specify secondary print queue name if using --type bill via --printbill_secondary option") if ($type eq "bill" && !defined $secondary);

&abortjob ("$0: must specify target printer name if using --type quote via --printbill_printer option") if ($type eq "quote" && !defined $quoteprinter);

$sock = IO::Socket::UNIX -> new (Peer => $sockname)
	or &abortjob ("$0: Unable to establish socket connection on $sockname: $!");

defined ($line = <$sock>)
	or &abortjob ("$0: Unable to read version string from socket connection on $sockname: $!");

chomp $line;

$sversion = (split (/\s?:\s?/, $line))[1];
($smajor, $sminor, $spointrel) = split (".", $sversion);
($cmajor, $cminor, $cpointrel) = split (".", $cversion);

if ($smajor ne $cmajor) {
	&abortjob ("$0: Incompatible version number (server is $sversion, client is $cversion)");
}

@filenames = copy_files ($cf, $spooldir, $user);

$msg = "type: $type;";
$msg .= "printer: $printer;";
$msg .= "user: $user;";
$msg .= "job: $job;";
$msg .= "jobname: $jobname;";
$msg .= "tempdir: $params{'db_home'}/tmp/.$cf;";
$msg .= "remote_host: $remote_host;";

if ($type eq "bill") {
	$msg .= "secondary_queue: $secondary;";
}

if ($type eq "quote") {
	$msg .= "quote_printer: $quoteprinter;";
}

$msg .= "filenames: ";

foreach (@filenames) {
	$msg .= "$_";
}

$msg .= ";\n";

print $sock $msg
	or &abortjob ("$0: Unable to write to socket connection on $sockname: $!");

defined ($response = <$sock>)
	or &abortjob ("$0: Unable to read final response from socket connection on $sockname: $!");

close $sock;

exit $response;

sub abortjob {
	my ($text) = @_;
	print STDERR "$text\n";
	syslog 'err', $text;
	exit $JREMOVE;
}

# At this point we have all configuration options and the command-line
# arguments which have been passed to us by lprng. We make an efficient copy
# of the job for our own nefarious purposes.

sub copy_files {
	my ($cf, $spooldir, $user) = @_;
	my ($df, $line, @filenames, $nfiles);
	
# If the control file is missing we have serious problems.

	open CONTROL, "$spooldir/$cf" or
		&abortjob ("Error: couldn't open the control file \"spooldir/$cf\": $!");

# OK. Now we need to extract the lines starting with `f' (data filename
# lines), and copy the specified files either to remote machines or to a
# more convenient place on the local machine.

	mkdir "$params{'db_home'}/tmp/.$cf", 0700 or
		&abortjob ("$0: couldn't create temporary directory \"$params{'db_home'}/tmp/.$cf\": $!\n");
	
	while (defined ($line = <CONTROL>)) {
		chomp ($line);

# If the line starts with an f or an l (needed for SuSE apparently)

		if (($line =~ /^f.*/) || ($line =~ /^l.*/)) {
			$df = substr ($line, 1, length ($line) - 1);
			$filenames[$nfiles] = $user . '-' . $nfiles;

# Attempt to hard-link the file, if that fails, copy it. If $params{'db_home'}/tmp
# and $spooldir are on the same filesystem, it will be much faster than
# making a copy. We use a system mv because that can work across filesystems
# (should this be necessary).

			$df =~ /^(.+)$/;
			$df = $1;

			link "$spooldir/$df", "$params{'db_home'}/tmp/.$cf/$filenames[$nfiles]" or do {
				my $answer = `/bin/cp $spooldir/$df $params{'db_home'}/tmp/.$cf/$filenames[$nfiles] 2>&1`;
			
				if ($? >> 8) {
					&die_cleanup ($JREMOVE, "$0: unable to transfer the file $df from $spooldir to $params{'db_home'}/tmp/.$cf/$filenames[$nfiles]: $answer\n");
				}
			};

			$nfiles++;
		}
	}

	close CONTROL or die_cleanup ($JREMOVE, "$0: unable to close control file \"$spooldir/$cf\": $!\n");
	
	return @filenames;
}
