#!/usr/bin/perl -w

##############################################################################
#
# Sophistocated print authentication filter, version 4.1.2
#
# Copyright (C) 2001, 2002, 2003 Daniel Franklin
#
# This program is distributed under the terms of the GNU General Public
# License Version 2.
#
# This is a dumb script which allows users to bill themselves.
#
##############################################################################

use Printbill::PTDB_File;
use Printbill::printbill;
use Printbill::printbill_pcfg;
use Sys::Syslog qw(:DEFAULT setlogsock);
use POSIX 'setsid';
use Fcntl qw(:DEFAULT :flock);
use Getopt::Long;

# $config is where we store the prices.

$configdir = "/etc/printbill";
$config = "$configdir/printbillrc";
$printer = "";
$user = "";
$JSUCC = 0;
$JREMOVE = 3;

setlogsock('unix') or die_cleanup ($JREMOVE, "$0: cannot set log type: $!\n");
openlog ($0, 'cons', 'lpr') or die_cleanup ($JREMOVE, "$0: cannot open lpr syslog: $!\n");

# 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 ("j=n" => \$job, "n=s" => \$user, "P=s" => \$printer, "J=s" => \$file_list);

%params = pcfg ($config, $printer);

# At this point we have all configuration options and the command-line
# arguments which have been passed to us by lprng.

# Check to see if the user has a quota before doing any processing. This is
# read-only so doesn't need to be locked.

tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", TRUE or do {
	&mail_user ($user, "You have no quota. See the quota administrator.\n");
	&die_cleanup ($JREMOVE, "$0: cannot open file $params{'db_home'}/users/$user.db: $!");
};
	
untie %userhash;

@filenames = split (",", $file_list);

# Get a list of our wares... don't want to make this specifiable in the
# config file since this is an IEEE-specific hack
tie %wareshash, "Printbill::PTDB_File", "$params{'db_home'}/wares.db", TRUE or do {
	&mail_user ($user, "Tell the sysadmin that he's a tool.\n");
	&die_cleanup ($JREMOVE, "$0: cannot open file $params{'db_home'}/wares.db: $!");
};

# Check the names - do any of them match things for which we are billing?

foreach $filename (@filenames) {
	$filename =~ s/^.*\///;
# If we bill for this type of thing

	if (defined ($wareshash{$filename})) {
		&lock ("user_$user");
		
		tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", FALSE or do {
			&mail_user ($user, "You have no quota. See the quota administrator.\n");
			&die_cleanup ($JREMOVE, "$0: cannot open file $params{'db_home'}/users/$user.db: $!");
		};

		$userhash{'quota'} -= $wareshash{$filename};
		
# Update the pie count for this user

		$userhash{$filename}++;

		untie %userhash;
		
		&unlock ("user_$user");

# Update global pie count

		&lock ("misc");

		tie %mischash, "Printbill::PTDB_File", "$params{'db_home'}/misc.db", FALSE or
			&die_cleanup ($JREMOVE, "$0: cannot open file \"$params{'db_home'}/misc.db\": $!");

		$mischash{$filename}++;

		untie %mischash;

		&unlock ("misc");
	}
}
	
untie %wareshash;

exit $JSUCC;

# We don't use proper flock() or fcntl() because we would need to keep track
# of file descriptors. This way is simple - we just need to keep track of
# the filename.

sub lock
{
	local ($text) = @_;

	while (-e "$params{'db_home'}/tmp/.printbill_$text.lock") {
		open (LOCKFILE, "<$params{'db_home'}/tmp/.printbill_$text.lock")
			or die_cleanup ($JREMOVE, "$0: cannot open lockfile $params{'db_home'}/tmp/.printbill_$text.lock: $!\n");
		
		flock LOCKFILE, LOCK_EX
			or die_cleanup ($JREMOVE, "$0: cannot lock lockfile $params{'db_home'}/tmp/.printbill_$text.lock: $!\n");

		$lockpid = <LOCKFILE>;
		chomp $lockpid;
		
# Is the locking process still running? If not, we can safely nuke the file
# and lock it ourselves.

		last if (! -d "/proc/$lockpid");

# Otherwise, we have to wait.

		close LOCKFILE
			or die_cleanup ($JREMOVE, "$0: cannot close lockfile: $!\n");

		sleep $params{'retry_interval'};
	}
	
	open (LOCKFILE, ">$params{'db_home'}/tmp/.printbill_$text.lock")
		or die_cleanup ($JREMOVE, "$0: cannot open lockfile $params{'db_home'}/tmp/.printbill_$text.lock: $!\n");
	
	flock LOCKFILE, LOCK_EX
		or die_cleanup ($JREMOVE, "$0: cannot lock lockfile $params{'db_home'}/tmp/.printbill_$text.lock: $!\n");

	print LOCKFILE $$
		or die_cleanup ($JREMOVE, "$0: cannot write to lockfile: $!\n");

	$locks{$text} = 1;

	close LOCKFILE
		or die_cleanup ($JREMOVE, "$0: cannot close lockfile: $!\n");
}

sub unlock
{
	local ($text) = @_;

	if (-e "$params{'db_home'}/tmp/.printbill_$text.lock") {
		unlink "$params{'db_home'}/tmp/.printbill_$text.lock"
			or die_cleanup ($JREMOVE, "$0: cannot remove lockfile $params{'db_home'}/tmp/.printbill_$text.lock: $!\n");

	}
	
	delete $locks{$text};
}

# Wash our hands of the whole affair

sub cleanup {
	if (defined (%locks)) {
		for $key (keys %locks) {
			&unlock ($locks{$key});
		}
	}
}

sub die_cleanup
{
	local ($the_return_val, $msg) = @_;

	&cleanup;
	&mail_admin ("$msg");

	syslog ('err', $msg)
		or die "$0: cannot write \`$msg\' to syslog: $!\n";

	exit $the_return_val;
}

sub mail_user
{
	local ($the_user, $the_errors) = @_;

	my $name = (getpwnam ($the_user)) [6];
	my @assorted_text = split (",", $name);
	my $text = `/bin/cat $configdir/mail_message`;
	
	if ($the_errors eq "") {
		$the_errors = "No errors.";
	}

	$name = $assorted_text [0];
	
	open MAIL, "|$params{'mta'} $the_user"
		or die_cleanup ($JREMOVE, "$0: can't open $params{'mta'}: $!\n");
	
	print MAIL "Reply-to: $params{'admin_mail'}\n";
	print MAIL "From: $params{'admin_mail'}\n";
	print MAIL "To: $name <$the_user>\n";
	print MAIL "Subject: Print job failed\n\n";
	print MAIL $text;
	print MAIL "Job no: $job\n";
	print MAIL "Errors: $the_errors\n";

	if (defined ($userhash{'quota'})) {
		printf MAIL "Total remaining credit: $params{'currency_symbol'}%.2f\n", $userhash{'quota'};
	}
	
	close MAIL
		or die_cleanup ($JREMOVE, "$0: Unable to close $params{'mta'}: $!\n");
}

sub mail_admin
{
	local ($the_errors) = @_;

	open MAIL, "|$params{'mta'} $params{'admin_mail'}"
		or syslog ('err', "$0: Unable to open $params{'mta'}: $!\n")
			or die "$0: cannot write to syslog: $!\n";
	
	print MAIL "Reply-to: $params{'admin_mail'}\n";
	print MAIL "From: $params{'admin_mail'}\n";
	print MAIL "Subject: Print job failed\n\n";
	print MAIL "$the_errors\n";
	
	close MAIL
		or syslog ('err', "$0: Unable to close $params{'mta'}: $!\n")
			or die "$0: cannot write to syslog: $!\n";
}
