#!/usr/bin/perl -w

##############################################################################
#
# Print billing management system - admin tools, version 4.2.0
#
# 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 the admin tool for managing the various printbill databases.
#
##############################################################################

use Printbill::PTDB_File;
use Printbill::printbill_pcfg;
use Getopt::Long;
use Locale::gettext;
use POSIX;
use Fcntl qw(:DEFAULT :flock);
use strict;

setlocale (LC_MESSAGES, "");
textdomain ("printbill");

my (%params, $config, %printers, %opt);
my ($INCUSER, $DECUSER, $SETUSER, $INFINITISEUSER, $DEINFINITISEUSER, $AMOUNT, $ADDUSER, $DELUSER, $DISPLAYUSER, $DISPLAYDETAILUSER, $PAGESUSER, $PAIDUSER, $SPENTUSER, $USEDPRINTER, $PPAGESPRINTER, $COLOURSPACEPRINTER, $CLEARBLACKPRINTER, $CLEARCOLOURPRINTER, $CLEARPAGESPRINTER, $WEBADDUSER, $WEBPASSWDUSER, $WEBDELUSER, $WEBNONINTUSER, $WEBNONINTPASSWD, %locks);

sub usage {
	printf gettext ("
pqm - manage printbill database file. Options:

	--inc user --amount N
		increments quota by %sN (N need not be an integer)

	--dec user --amount N
		decrements quota by %sN (N need not be an integer)

	--set user --amount N
		sets user's quota to %sN (N need not be an integer)

	--add user
		adds user to database

	--del user
		removes user from database

	--infinitise user
		gives user an infinite print quota (god mode for admins)

	--deinfinitise user
		returns user to status of ordinary mortal

	--init
		remove all users, reset page counter (DANGER!)

	--display [user]
		displays info for all users [or for 'user']

	--displaydetail [user]
		displays very detailed info for all users [or for 'user']

	--pages [user]
		shows total pages printed [or those by user]

	--spent [user]
		shows total spent [or spent by user]

	--paid [user]
		shows total paid [or paid by user]

	--used [printer]
		shows estimated %% of black [and colour] used

	--ppages [printer]
		shows total pages printed on 'printer'

	--colourspace [printer]
		shows colourspace of 'printer'

	--resetpages [printer]
		 resets printer page count for all or just for 'printer'

	--resetblack [printer]
		you bought a new black ink/toner cartridge for 'printer'

	--resetcolour [printer]
		you bought a new colour ink/toner cartridge for 'printer'

	--updateprinters
		Create printer databases for any new printers in printcap
		(LPRng only!)

	--webadduser user
		add a webpqadmin user

	--webdeluser user
		remove a webpqadmin user

	--webpasswd user
		interactively set password for a webpqadmin user

	--webnoninter user --passwd password
		non-interactively set password for a webpqadmin user

	--web
		(in combination with some of the print commands) print out a
		bare minimum, for the CGI script (does nothing on its own)

	--ask_no_questions
		don't prompt before doing something dangerous

	--sloppy
		don't check that an account is real

	--help
		print out this help

	--version
		print out the version number

"), $params{'currency_symbol'}, $params{'currency_symbol'}, $params{'currency_symbol'};

}

$SIG{TERM} = \&catch_zap;
$SIG{HUP} = \&catch_zap;
$SIG{INT} = \&catch_zap;

$config = '/etc/printbill/printbillrc';

%params = pcfg ($config);

die sprintf (gettext ("%s: Error: problems parsing configuration file.\n"), $0) if (! scalar (%params));

%printers = pcfg_printers ($config);

die sprintf (gettext ("%s: Error: problems parsing printer-specific configuration file(s).\n"), $0) if (! scalar (%printers));

unless (GetOptions (\%opt, # all non-linked options go into %opt
#
#	option			linkage
#
	'inc=s',		\$INCUSER,
	'dec=s',		\$DECUSER,
	'set=s',		\$SETUSER,
	'amount=f',		\$AMOUNT,
	'dollars=f',		\$AMOUNT,
	'add=s',		\$ADDUSER,
	'del=s',		\$DELUSER,
	'infinitise=s',		\$INFINITISEUSER,
	'deinfinitise=s',	\$DEINFINITISEUSER,
	'init!',

	'display:s',		\$DISPLAYUSER,
	'displaydetail:s',	\$DISPLAYDETAILUSER,
	'pages:s',		\$PAGESUSER,
	'paid:s',		\$PAIDUSER,
	'spent:s',		\$SPENTUSER,

	'used:s',		\$USEDPRINTER,
	'ppages:s',		\$PPAGESPRINTER,
	'colourspace:s',	\$COLOURSPACEPRINTER,
	'resetpages:s',		\$CLEARPAGESPRINTER,
	'resetblack:s',		\$CLEARBLACKPRINTER,
	'resetcolour:s',	\$CLEARCOLOURPRINTER,
	'updateprinters!',

	'webadduser=s',		\$WEBADDUSER,
	'webdeluser=s',		\$WEBDELUSER,
	'webpasswd=s',		\$WEBPASSWDUSER,
	'webnonintpasswd=s',	\$WEBNONINTUSER,
	'passwd=s',		\$WEBNONINTPASSWD,

	'web!',
	'ask_no_questions!',
	'sloppy!',
	'help!',
	'version!'
)) {
	&usage;
}

if ($opt{help}) {
	&usage;
} elsif ($opt{version}) {
	print "pqm version 4.2.0\n";
} elsif ($opt{updateprinters}) {
	&updateprinters;
} elsif (defined ($INCUSER) && !defined ($DECUSER) && !defined ($SETUSER) && defined ($AMOUNT)) {
	&incuser ($INCUSER, $AMOUNT);
} elsif (defined ($DECUSER) && !defined ($INCUSER) && !defined ($SETUSER) && defined ($AMOUNT)) {
	&decuser ($DECUSER, $AMOUNT);
} elsif (defined ($SETUSER) && !defined ($INCUSER) && !defined ($DECUSER) && defined ($AMOUNT)) {
	&setuser ($SETUSER, $AMOUNT);
} elsif (defined ($ADDUSER)) {
	&adduser ($ADDUSER);
} elsif (defined ($DELUSER)) {
	&deluser ($DELUSER);
} elsif (defined ($INFINITISEUSER)) {
	&infinitise ($INFINITISEUSER);
} elsif (defined ($DEINFINITISEUSER)) {
	&deinfinitise ($DEINFINITISEUSER);
} elsif (defined ($DISPLAYUSER)) {
	&display ($DISPLAYUSER);
} elsif (defined ($DISPLAYDETAILUSER)) {
	&displaydetail ($DISPLAYDETAILUSER);
} elsif (defined ($PAGESUSER)) {
	&pages ($PAGESUSER);
} elsif (defined ($SPENTUSER)) {
	&spent ($SPENTUSER);
} elsif (defined ($PAIDUSER)) {
	&paid ($PAIDUSER);
} elsif (defined ($USEDPRINTER)) {
	&used ($USEDPRINTER);
} elsif (defined ($CLEARBLACKPRINTER)) {
	&resetblack ($CLEARBLACKPRINTER);
} elsif (defined ($CLEARCOLOURPRINTER)) {
	&resetcolour ($CLEARCOLOURPRINTER);
} elsif (defined ($PPAGESPRINTER)) {
	&ppages ($PPAGESPRINTER);
} elsif (defined ($CLEARPAGESPRINTER)) {
	&resetpages ($CLEARPAGESPRINTER);
} elsif (defined ($COLOURSPACEPRINTER)) {
	&colourspace ($COLOURSPACEPRINTER);
} elsif ($opt{init}) {
	&init;
} elsif (defined $WEBADDUSER) {
	&webadduser ($WEBADDUSER);
} elsif (defined $WEBDELUSER) {
	&webdeluser ($WEBDELUSER);
} elsif (defined $WEBPASSWDUSER) {
	&webpasswd ($WEBPASSWDUSER);
} elsif (defined $WEBNONINTUSER && defined $WEBNONINTPASSWD) {
	&webnonintpasswd ($WEBNONINTUSER, $WEBNONINTPASSWD);
} else {
	&usage;
}

sub updateprinters
{
	my (@printers, @pparams, $pparam, %pparamhash, $printer, $printer_name, @line, $var, $val, %printerhash, $uid, $gid);
	my $printcap = `cat $params{'printcap'}`;
	
	if ($? >> 8) {
		die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, $params{'printcap'}, $printcap));
	}

	$printcap =~ s/#.*//g;
	$printcap =~ s/\n+\s*:\s*/:/g;
	$printcap =~ s/\n+//;
	$printcap =~ s/\n+/\n/g;
	$printcap =~ s/:\\*:/:/g;
	$printcap =~ s/:\n/\n/g;

	@printers = split ("\n", $printcap);
	
	foreach $printer (@printers) {
		@pparams = split (":", $printer);
		$printer_name = $pparams [0];

# Get rid of alternative names
		$printer_name =~ s/\|.*//;
		
		printf gettext ("Checking printer \"%s\"... "), $printer_name;

		%pparamhash = ();

		foreach $pparam (@pparams) {
			@line = split ("=", $pparam);

			if ($#line == 1) {
				$var = $line [0];
				$val = $line [1];
				$pparamhash{$var} = $val;
			}	
		}

# Add any new printers which don't currently have a database entry.

		if (defined ($pparamhash{"achk"}) && ($pparamhash{"achk"} =~ /true/i)
			&& defined ($pparamhash{"as"}) && ($pparamhash{"as"} =~ /--type/) && ($pparamhash{"as"} =~ /bill|lazybill|accountonly/)) {

			if (-e "$params{'db_home'}/printers/$printer_name.db") {
				printf gettext ("Database already exists - ignoring.\n");
			} else {
				printf gettext ("Adding new printer \"%s\".\n"), $printer_name;
			
				if (!defined ($printers{$printer_name})) {
					%{$printers{$printer_name}} = %{$printers{'default'}};
				}

				&lock ("printer_$printer_name");
	
				tie %printerhash, "Printbill::PTDB_File", "$params{'db_home'}/printers/$printer_name.db", "FALSE"
					or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/printers/$printer_name.db", $!));
			
				%printerhash = ();
			
				$printerhash{'total pages'} = 0;
			
				$printerhash{'colourspace'} = $printers{$printer_name}{'colourspace'};
			
				if ($printers{$printer_name}{'colourspace'} ne 'mono') {
					$printerhash{'estimated cyan'} = 0;
					$printerhash{'estimated magenta'} = 0;
					$printerhash{'estimated yellow'} = 0;
				}
				
				if ($printers{$printer_name}{'colourspace'} ne 'cmy') {
					$printerhash{'estimated black'} = 0;
				}

				untie %printerhash;
			
				$uid = (getpwnam ($params{'printbilld_user'}))[2];
		
				if (defined $params{'printbilld_group'}) {
					$gid = getgrnam ($params{'printbilld_group'});
				} else {
					$gid = (getpwnam ($params{'printbilld_user'}))[3];
				}

				chown $uid, $gid, "$params{'db_home'}/printers/$printer_name.db";

				chmod 0664, "$params{'db_home'}/printers/$printer_name.db";

				&unlock ("printer_$printer_name");
			}
		} else {
			print gettext ("Not accounting - ignoring.\n");
		}
	}
}

sub incuser
{
	my ($user, $amount) = @_;
	my (%userhash, %mischash, $identity, $gid, $uid, $t, $lt);

	if (!defined ($amount)) {
		die_cleanup (-1, sprintf (gettext ("%s: Error: you need to specify an amount with --amount <nnn>.\n"), $0));
	}
	
	$uid = (getpwnam ($params{'printbilld_user'}))[2];
		
	if (defined $params{'printbilld_group'}) {
		$gid = getgrnam ($params{'printbilld_group'});
	} else {
		$gid = (getpwnam ($params{'printbilld_user'}))[3];
	}

	&lock ("user_$user");

	tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "FALSE"
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
	
	&lock ("misc");	
	
	tie %mischash, "Printbill::PTDB_File", "$params{'db_home'}/misc.db", "FALSE"
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/misc.db", $!));
	
# If the user doesn't exist, create him/her
	
	if (!defined ($userhash{'quota'})) {
		if (!$opt{sloppy}) {
			$identity = (getpwnam ($user)) [0];
		
			if (!defined ($identity) || $identity ne $user) {
				untie %userhash;
# Delete the bogus user's database
				`/bin/rm $params{'db_home'}/users/$user.db`;
# Don't bother testing $? - we'd just have to do the same anyway :)
				untie %mischash;
				die_cleanup (-1, sprintf (gettext ("%s: Error: user %s does not have a valid UNIX account - use --sloppy to override.\n"), $0, $user));
			}
		}

		$userhash{'quota'} = 0;
		$userhash{'spent'} = 0;
		$userhash{'pages'} = 0;
		$userhash{'cyan'} = 0;
		$userhash{'magenta'} = 0;
		$userhash{'yellow'} = 0;

		chown $uid, $gid, "$params{'db_home'}/users/$user.db";
		chmod 0664, "$params{'db_home'}/users/$user.db";
	}

	$userhash{'quota'} += $amount;
	$mischash{'total paid'} += $amount;
	
	printf gettext ("Incrementing %s\'s quota by %s%.2f to %s%.2f.\n"), $user, $params{'currency_symbol'}, $amount, $params{'currency_symbol'}, $userhash{'quota'};

	untie %userhash;
	untie %mischash;
	
	if (defined ($params{'financelog'})) {
		if (! -f $params{'financelog'}) {
			open FINLOG, ">$params{'financelog'}"
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "financial transaction log $params{'financelog'}", $!));
				
			chmod 0660, $params{'financelog'}
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot set mode on file %s to %s: %s.\n"), $0, $params{'financelog'}, "0660", $!));

			chown $uid, $gid, $params{'financelog'}
				or die_cleanup (-1, sprintf (gettext ("%s: could not set ownership on file %s to %s: %s.\n"), $0, $params{'financelog'}, "$uid.$gid", $!));
		} else {
			open FINLOG, ">>$params{'financelog'}"
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "financial transaction log $params{'financelog'}", $!));
		}

		$t = time;
		$lt = localtime (time);

		print FINLOG "$lt	$t	$amount\n";

		close FINLOG;
	}
	
	&unlock ("user_$user");
	&unlock ("misc");
}

sub decuser
{
	my ($user, $amount) = @_;
	my (%userhash, %mischash, $identity, $t, $lt, $uid, $gid);
	
	if (!defined ($amount)) {
		die_cleanup (-1, sprintf (gettext ("%s: Error: you need to specify an amount with --amount <nnn>.\n"), $0));
	}

	$uid = (getpwnam ($params{'printbilld_user'}))[2];
		
	if (defined $params{'printbilld_group'}) {
		$gid = getgrnam ($params{'printbilld_group'});
	} else {
		$gid = (getpwnam ($params{'printbilld_user'}))[3];
	}

	&lock ("user_$user");

	tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "FALSE" 
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
	
	&lock ("misc");	
	
	tie %mischash, "Printbill::PTDB_File", "$params{'db_home'}/misc.db", "FALSE" 
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/misc.db", $!));

# If the user doesn't exist, create him/her

	if (!defined ($userhash{'quota'})) {
		if (!$opt{sloppy}) {
			$identity = (getpwnam ($user)) [0];
		
			if (!defined ($identity) || $identity ne $user) {
				untie %userhash;
# Delete the bogus user's database
				`/bin/rm $params{'db_home'}/users/$user.db`;
# Don't bother testing $? - we'd just have to do the same anyway :)
				untie %mischash;
				die_cleanup (-1, sprintf (gettext ("%s: Error: user %s does not have a valid UNIX account - use --sloppy to override.\n"), $0, $user));
			}
		}

		$userhash{'quota'} = 0;
		$userhash{'spent'} = 0;
		$userhash{'pages'} = 0;
		$userhash{'cyan'} = 0;
		$userhash{'magenta'} = 0;
		$userhash{'yellow'} = 0;
		$userhash{'black'} = 0;
	}

	$userhash{'quota'} -= $amount;
	$mischash{'total paid'} -= $amount;

	printf gettext ("Decrementing %s\'s quota by %s%.2f to %s%.2f.\n"), $user, $params{'currency_symbol'}, $amount, $params{'currency_symbol'}, $userhash{'quota'};

	untie %userhash;
	untie %mischash;

	if (defined ($params{'financelog'})) {
		if (! -f $params{'financelog'}) {
			open FINLOG, ">$params{'financelog'}"
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "financial transaction log $params{'financelog'}", $!));
				
			chmod 0660, $params{'financelog'}
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot set mode on file %s to %s: %s.\n"), $0, $params{'financelog'}, "0660", $!));

			chown $uid, $gid, $params{'financelog'}
				or die_cleanup (-1, sprintf (gettext ("%s: could not set ownership on file %s to %s: %s.\n"), $0, $params{'financelog'}, "$uid.$gid", $!));
		} else {
			open FINLOG, ">>$params{'financelog'}"
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "financial transaction log $params{'financelog'}", $!));
		}

		$t = time;
		$lt = localtime (time);

		print FINLOG "$lt	$t	-$amount\n";

		close FINLOG;
	}
	
	&unlock ("user_$user");
	&unlock ("misc");
}

sub setuser
{
	my ($user, $amount) = @_;
	my (%userhash, %mischash, $identity, $delta, $t, $lt);
	
	if (!defined ($amount)) {
		die_cleanup (-1, sprintf (gettext ("%s: Error: you need to specify an amount with --amount <nnn>.\n"), $0));
	}

	&lock ("user_$user");

	tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "FALSE" 
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
	
	&lock ("misc");	
	
	tie %mischash, "Printbill::PTDB_File", "$params{'db_home'}/misc.db", "FALSE" 
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/misc.db", $!));

# If the user doesn't exist, create him/her

	if (!defined ($userhash{'quota'})) {
		if (!$opt{sloppy}) {
			$identity = (getpwnam ($user)) [0];
		
			if (!defined ($identity) || $identity ne $user) {
				untie %userhash;
# Delete the bogus user's database
				`/bin/rm $params{'db_home'}/users/$user.db`;
# Don't bother testing $? - we'd just have to do the same anyway :)
				untie %mischash;
				die_cleanup (-1, sprintf (gettext ("%s: Error: user %s does not have a valid UNIX account - use --sloppy to override.\n"), $0, $user));
			}
		}

		$userhash{'quota'} = 0;
		$userhash{'spent'} = 0;
		$userhash{'pages'} = 0;
		$userhash{'cyan'} = 0;
		$userhash{'magenta'} = 0;
		$userhash{'yellow'} = 0;
		$userhash{'black'} = 0;
	}

	$delta = $amount - $userhash{'quota'};
	$mischash{'total paid'} += $delta;

	if (defined ($params{'financelog'})) {
		open FINLOG, ">>$params{'financelog'}"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "financial transaction log $params{'financelog'}", $!));

		$t = time;
		$lt = localtime (time);

		print FINLOG "$lt	$t	$delta\n";

		close FINLOG;
	}
	
	$userhash{'quota'} = $amount;

	printf gettext ("Setting %s\'s quota to %s%.2f.\n"), $user, $params{'currency_symbol'}, $userhash{'quota'};

	untie %userhash;
	untie %mischash;

	&unlock ("user_$user");
	&unlock ("misc");
}

sub adduser
{
	my ($user) = @_;
	my (%userhash, $identity, $uid, $gid);
	
	if (! $opt{sloppy}) {
		$identity = (getpwnam ($user)) [0];
	
		if (!defined ($identity) || $identity ne $user) {
			die_cleanup (-1, sprintf (gettext ("%s: Error: user %s does not have a valid UNIX account - use --sloppy to override.\n"), $0, $user));
		}
	}
	
	&lock ("user_$user");

	tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "FALSE"
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));

	if (defined ($userhash{'quota'})) {
		untie %userhash;
		die_cleanup (-2, sprintf (gettext ("%s: Error: user %s already exists in the database.\n"), $0, $user));
	}

	$userhash{'quota'} = 0;
	$userhash{'spent'} = 0;
	$userhash{'pages'} = 0;
	$userhash{'cyan'} = 0;
	$userhash{'magenta'} = 0;
	$userhash{'yellow'} = 0;
	$userhash{'black'} = 0;
	
	untie %userhash;

	printf gettext ("User %s added to database, initial credit = %s0.00.\n"), $user, $params{'currency_symbol'};
	
# Are we running as the web user or as root?
	
	if ($< == (getpwnam ($params{'web_user'}))[2]) {
		$uid = $<;
	} else {
		$uid = (getpwnam ($params{'printbilld_user'}))[2];
	}
	
	if (defined $params{'printbilld_group'}) {
		$gid = getgrnam ($params{'printbilld_group'});
	} else {
		$gid = (getpwnam ($params{'printbilld_user'}))[3];
	}
	
	chown $uid, $gid, "$params{'db_home'}/users/$user.db"
		or die_cleanup (-1, sprintf (gettext ("%s: Error: running as %s, but not a member of group %s.\n"), $0, $<, $gid));
		
	chmod 0664, "$params{'db_home'}/users/$user.db";

	&unlock ("user_$user");
}

sub deluser
{
	my ($user) = @_;
	my (%userhash);
	
	&lock ("user_$user");

# Open read-only to see if the user actually exists...

	tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "TRUE"
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));

	untie %userhash;

	unlink "$params{'db_home'}/users/$user.db" 
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot delete file %s: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
		
	printf gettext ("Deleted %s from the database.\n"), $user;

	&unlock ("user_$user");
}

sub infinitise
{
	my ($user) = @_;
	my (%userhash);
	
	&lock ("user_$user");

	tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "FALSE"
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
		
	$userhash{'infinitism'} = "YES";

	untie %userhash;

	&unlock ("user_$user");

	printf gettext ("Unlimited printing privilidges granted to %s.\n"), $user;
}

sub deinfinitise
{
	my ($user) = @_;
	my (%userhash);
	
	&lock ("user_$user");

	tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "FALSE" 
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
		
	$userhash{'infinitism'} = "NO";

	untie %userhash;

	&unlock ("user_$user");

	printf gettext ("Unlimited printing privilidges withdrawn from %s.\n"), $user;
}

sub display
{
	my ($user) = @_;
	my (@users, %userhash, %mischash);
	
	if ($user eq "") {
		opendir USERS_DIR, "$params{'db_home'}/users"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open directory %s for reading: %s.\n"), $0, "$params{'db_home'}/users", $!));

		@users = readdir USERS_DIR
			or @users = ();

		@users = sort grep { !/^\./ } @users; 

		closedir USERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot close directory %s: %s.\n"), $0, "$params{'db_home'}/users", $!));
		
		if ($opt{web}) {
			foreach $user (@users) {
				$user =~ s/\.db//;

				tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "TRUE"
					or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
				
				if (!defined ($userhash{'quota'})) {
					$userhash{'quota'} = 0;
				}
				
				if (!defined ($userhash{'spent'})) {
					$userhash{'spent'} = 0;
				}
				
				if (!defined ($userhash{'pages'})) {
					$userhash{'pages'} = 0;
				}

				if (!defined ($userhash{'cyan'})) {
					$userhash{'cyan'} = 0;
				}

				if (!defined ($userhash{'magenta'})) {
					$userhash{'magenta'} = 0;
				}

				if (!defined ($userhash{'yellow'})) {
					$userhash{'yellow'} = 0;
				}
				
				if (!defined ($userhash{'black'})) {
					$userhash{'black'} = 0;
				}

				if (!defined ($userhash{'infinitism'})) {
					$userhash{'infinitism'} = "NO";
				}
				
				printf ("%s&$params{'currency_symbol'}%.2f%s&$params{'currency_symbol'}%.2f&%i&%f&%f&%f&%f\n", $user, $userhash{'quota'}, ($userhash{'infinitism'} eq 'YES') ? " (infinite)" : "", $userhash{'spent'}, $userhash{'pages'}, $userhash{'cyan'}, $userhash{'magenta'}, $userhash{'yellow'}, $userhash{'black'});
				
				untie %userhash;
			}
		} else {
			print gettext ("Current database:\n");

			foreach $user (@users) {
				$user =~ s/\.db//;
				
				tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "TRUE"
					or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
				
				if (!defined ($userhash{'quota'})) {
					$userhash{'quota'} = 0;
				}
				
				if (!defined ($userhash{'spent'})) {
					$userhash{'spent'} = 0;
				}
				
				if (!defined ($userhash{'pages'})) {
					$userhash{'pages'} = 0;
				}

				if (!defined ($userhash{'cyan'})) {
					$userhash{'cyan'} = 0;
				}

				if (!defined ($userhash{'magenta'})) {
					$userhash{'magenta'} = 0;
				}

				if (!defined ($userhash{'yellow'})) {
					$userhash{'yellow'} = 0;
				}
				
				if (!defined ($userhash{'black'})) {
					$userhash{'black'} = 0;
				}
		
				if (!defined ($userhash{'infinitism'})) {
					$userhash{'infinitism'} = "NO";
				}
				
				printf (gettext ("%s: Credit: %s%.2f, Spent: %s%.2f, Pages: %i, CMYK: %.2f, %.2f, %.2f, %.2f.\n"), ($userhash{'infinitism'} eq 'YES') ? (sprintf ("$user (%s)", gettext ("unlimited"))) : $user , $params{'currency_symbol'}, $userhash{'quota'}, $params{'currency_symbol'}, $userhash{'spent'}, $userhash{'pages'}, $userhash{'cyan'}, $userhash{'magenta'}, $userhash{'yellow'}, $userhash{'black'});
				
				untie %userhash;
			}
	
			tie %mischash, "Printbill::PTDB_File", "$params{'db_home'}/misc.db", "TRUE"
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/misc.db", $!));
	
			printf gettext ("A total of %i pages has been printed.\n"), $mischash{'total pages'};
			printf gettext ("A total of %s%.2f has been paid for print quota.\n"), $params{'currency_symbol'}, $mischash{'total paid'};
			printf gettext ("A total of %s%.2f has been spent on printing.\n"), $params{'currency_symbol'}, $mischash{'total spent'};

			untie %mischash;
		}
	} else {
		tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "TRUE"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
			
		if (!defined ($userhash{'quota'})) {
			$userhash{'quota'} = 0;
		}
		
		if (!defined ($userhash{'spent'})) {
			$userhash{'spent'} = 0;
		}
		
		if (!defined ($userhash{'pages'})) {
			$userhash{'pages'} = 0;
		}

		if (!defined ($userhash{'cyan'})) {
			$userhash{'cyan'} = 0;
		}

		if (!defined ($userhash{'magenta'})) {
			$userhash{'magenta'} = 0;
		}

		if (!defined ($userhash{'yellow'})) {
			$userhash{'yellow'} = 0;
		}
				
		if (!defined ($userhash{'black'})) {
			$userhash{'black'} = 0;
		}
						
		if (!defined ($userhash{'infinitism'})) {
			$userhash{'infinitism'} = "NO";
		}
		
		if ($opt{web}) {
			printf ("$params{'currency_symbol'}%.2f%s\n", $userhash{'quota'}, ($userhash{'infinitism'} eq 'YES') ? " (infinite)" : "");
		} else {
			printf (gettext ("%s: Credit: %s%.2f, Spent: %s%.2f, Pages: %i, CMYK: %.2f, %.2f, %.2f, %.2f.\n"), ($userhash{'infinitism'} eq 'YES') ? (sprintf ("$user (%s)", gettext ("unlimited"))) : $user , $params{'currency_symbol'}, $userhash{'quota'}, $params{'currency_symbol'}, $userhash{'spent'}, $userhash{'pages'}, $userhash{'cyan'}, $userhash{'magenta'}, $userhash{'yellow'}, $userhash{'black'});
		}
	
		untie %userhash;
	}
}

sub displaydetail
{
	my ($user) = @_;
	my (@users, %userhash, %mischash, @printers, $printer);
	
	opendir PRINTERS_DIR, "$params{'db_home'}/printers"
	or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

	@printers = readdir PRINTERS_DIR
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot read from directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

	@printers = sort grep { !/^\./ } @printers; 

	closedir PRINTERS_DIR
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot close directory %s: %s.\n"), $0, "$params{'db_home'}/printers", $!));
		
	if ($user eq "") {
		opendir USERS_DIR, "$params{'db_home'}/users"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open directory %s for reading: %s.\n"), $0, "$params{'db_home'}/users", $!));

		@users = readdir USERS_DIR
			or @users = ();

		@users = sort grep { !/^\./ } @users; 

		closedir USERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot close directory %s: %s.\n"), $0, "$params{'db_home'}/users", $!));
		
		print gettext ("Current database:\n");

		foreach $user (@users) {
			$user =~ s/\.db//;
				
			tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "TRUE"
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
				
			if (!defined ($userhash{'quota'})) {
				$userhash{'quota'} = 0;
			}
				
			if (!defined ($userhash{'spent'})) {
				$userhash{'spent'} = 0;
			}
				
			if (!defined ($userhash{'pages'})) {
				$userhash{'pages'} = 0;
			}

			if (!defined ($userhash{'cyan'})) {
				$userhash{'cyan'} = 0;
			}

			if (!defined ($userhash{'magenta'})) {
				$userhash{'magenta'} = 0;
			}

			if (!defined ($userhash{'yellow'})) {
				$userhash{'yellow'} = 0;
			}
				
			if (!defined ($userhash{'black'})) {
				$userhash{'black'} = 0;
			}
		
			if (!defined ($userhash{'infinitism'})) {
				$userhash{'infinitism'} = "NO";
			}
			
			printf (gettext ("Username") . ": %s\n", $user);
			printf ("  " . gettext ("Credit") . ": %s%.2f\n", $params{'currency_symbol'}, $userhash{'quota'});
			printf ("  " . gettext ("Spent") . ": %s%.2f\n", $params{'currency_symbol'}, $userhash{'spent'});
			printf ("  " . gettext ("Pages") . ": %i\n", $userhash{'pages'});
			printf ("  " . gettext ("Cyan") . ": %.2f\n", $userhash{'cyan'});
			printf ("  " . gettext ("Magenta") . ": %.2f\n", $userhash{'magenta'});
			printf ("  " . gettext ("Yellow") . ": %.2f\n", $userhash{'yellow'});
			printf ("  " . gettext ("Black") . ": %.2f\n", $userhash{'black'});
			printf ("  " . gettext ("Unlimited Printing") . ": %s\n", $userhash{'infinitism'} eq 'YES' ? gettext ("Yes") : gettext ("No"));

			foreach $printer (@printers) {
				$printer =~ s/\.db//;

				if (defined ($userhash{$printer . ".pages"}) || defined ($userhash{$printer . ".cyan"}) || defined ($userhash{$printer . ".magenta"}) || defined ($userhash{$printer . ".yellow"}) || defined ($userhash{$printer . ".black"})) {
					print "  " . gettext ("Printer") . ": $printer\n";
				}
				
				if (defined ($userhash{$printer . ".pages"})) {
					printf ("    " . gettext ("Pages") . ": %i\n", $userhash{$printer . ".pages"});
				}

				if (defined ($userhash{$printer . ".cyan"})) {
					printf ("    " . gettext ("Cyan") . ": %.2f\n", $userhash{$printer . ".cyan"});
				}
				
				if (defined ($userhash{$printer . ".magenta"})) {
					printf ("    " . gettext ("Magenta") . ": %.2f\n", $userhash{$printer . ".magenta"});
				}
				
				if (defined ($userhash{$printer . ".yellow"})) {
					printf ("    " . gettext ("Yellow") . ": %.2f\n", $userhash{$printer . ".yellow"});
				}
				
				if (defined ($userhash{$printer . ".black"})) {
					printf ("    " . gettext ("Black") . ": %.2f\n", $userhash{$printer . ".black"});
				}
			}
			
			untie %userhash;
		}
	
		tie %mischash, "Printbill::PTDB_File", "$params{'db_home'}/misc.db", "TRUE"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/misc.db", $!));
	
		printf gettext ("A total of %i pages has been printed.\n"), $mischash{'total pages'};
		printf gettext ("A total of %s%.2f has been paid for print quota.\n"), $params{'currency_symbol'}, $mischash{'total paid'};
		printf gettext ("A total of %s%.2f has been spent on printing.\n"), $params{'currency_symbol'}, $mischash{'total spent'};

		untie %mischash;
	} else {
		tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "TRUE"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
			
		if (!defined ($userhash{'quota'})) {
			$userhash{'quota'} = 0;
		}
		
		if (!defined ($userhash{'spent'})) {
			$userhash{'spent'} = 0;
		}
		
		if (!defined ($userhash{'pages'})) {
			$userhash{'pages'} = 0;
		}

		if (!defined ($userhash{'cyan'})) {
			$userhash{'cyan'} = 0;
		}

		if (!defined ($userhash{'magenta'})) {
			$userhash{'magenta'} = 0;
		}

		if (!defined ($userhash{'yellow'})) {
			$userhash{'yellow'} = 0;
		}
				
		if (!defined ($userhash{'black'})) {
			$userhash{'black'} = 0;
		}
						
		if (!defined ($userhash{'infinitism'})) {
			$userhash{'infinitism'} = "NO";
		}
		
		foreach $printer (@printers) {
			$printer =~ s/\.db//;
		}
		
		printf (gettext ("Username") . ": %s\n", $user);
		printf ("  " . gettext ("Credit") . ": %s%.2f\n", $params{'currency_symbol'}, $userhash{'quota'});
		printf ("  " . gettext ("Spent") . ": %s%.2f\n", $params{'currency_symbol'}, $userhash{'spent'});
		printf ("  " . gettext ("Pages") . ": %i\n", $userhash{'pages'});
		printf ("  " . gettext ("Cyan") . ": %.2f\n", $userhash{'cyan'});
		printf ("  " . gettext ("Magenta") . ": %.2f\n", $userhash{'magenta'});
		printf ("  " . gettext ("Yellow") . ": %.2f\n", $userhash{'yellow'});
		printf ("  " . gettext ("Black") . ": %.2f\n", $userhash{'black'});
		printf ("  " . gettext ("Unlimited Printing") . ": %s\n", $userhash{'infinitism'} eq 'YES' ? gettext ("Yes") : gettext ("No"));

		foreach $printer (@printers) {
			$printer =~ s/\.db//;

			if (defined ($userhash{$printer . ".pages"}) || defined ($userhash{$printer . ".cyan"}) || defined ($userhash{$printer . ".magenta"}) || defined ($userhash{$printer . ".yellow"}) || defined ($userhash{$printer . ".black"})) {
				print "  " . gettext ("Printer") . ": $printer\n";
			}
				
			if (defined ($userhash{$printer . ".pages"})) {
				printf ("    " . gettext ("Pages") . ": %i\n", $userhash{$printer . ".pages"});
			}

			if (defined ($userhash{$printer . ".cyan"})) {
				printf ("    " . gettext ("Cyan") . ": %.2f\n", $userhash{$printer . ".cyan"});
			}
				
			if (defined ($userhash{$printer . ".magenta"})) {
				printf ("    " . gettext ("Magenta") . ": %.2f\n", $userhash{$printer . ".magenta"});
			}
				
			if (defined ($userhash{$printer . ".yellow"})) {
				printf ("    " . gettext ("Yellow") . ": %.2f\n", $userhash{$printer . ".yellow"});
			}
				
			if (defined ($userhash{$printer . ".black"})) {
				printf ("    " . gettext ("Black") . ": %.2f\n", $userhash{$printer . ".black"});
			}
		}
	
		untie %userhash;
	}
}

sub pages
{
	my ($user) = @_;
	my (%mischash, %userhash);

	if ($user eq "") {
		tie %mischash, "Printbill::PTDB_File", "$params{'db_home'}/misc.db", "TRUE"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/misc.db", $!));
	
		if ($opt{web}) {
			print "$mischash{'total pages'}\n";
		} else {
			printf gettext ("A total of %i pages has been printed.\n"), $mischash{'total pages'};
		}
		
		untie %mischash;
	} else {
		tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "TRUE"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
	
		if ($opt{web}) {
			print "$userhash{'pages'}\n";
		} else {
			printf gettext ("A total of %i pages has been printed by user %s.\n"), $userhash{'spent'} + $userhash{'quota'}, $user;
		}
		
		untie %userhash;
	}
}

sub spent
{
	my ($user) = @_;
	my (%mischash, %userhash);
	
	if ($user eq "") {
		tie %mischash, "Printbill::PTDB_File", "$params{'db_home'}/misc.db", "TRUE"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/misc.db", $!));
	
		if ($opt{web}) {
			printf ("$params{'currency_symbol'}%.2f\n", $mischash{'total spent'});
		} else {
			printf gettext ("A total of %s%.2f has been spent on printing.\n"), $params{'currency_symbol'}, $mischash{'total spent'};
		}

		untie %mischash;
	} else {
		tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "TRUE"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));
	
		if ($opt{web}) {
			printf ("$params{'currency_symbol'}%.2f\n", $userhash{'spent'});
		} else {
			printf gettext ("A total of %s%.2f has been spent on printing by user %s.\n"), $params{'currency_symbol'}, $userhash{'spent'} + $userhash{'quota'}, $user;
		}
		
		untie %userhash;
	}
}

sub paid
{
	my ($user) = @_;
	my (%mischash, %userhash);
	
	if ($user eq "") {
		tie %mischash, "Printbill::PTDB_File", "$params{'db_home'}/misc.db", "TRUE"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/misc.db", $!));
	
		if ($opt{web}) {
			printf ("$params{'currency_symbol'}%.2f\n", $mischash{'total paid'});
		} else {
			printf gettext ("A total of %s%.2f has been paid for print quota.\n"), $params{'currency_symbol'}, $mischash{'total paid'};
		}
		
		untie %mischash;
	} else {
		tie %userhash, "Printbill::PTDB_File", "$params{'db_home'}/users/$user.db", "TRUE"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/users/$user.db", $!));

		if ($opt{web}) {
			printf ("$params{'currency_symbol'}%.2f\n", $userhash{'spent'} + $userhash{'quota'});
		} else {
			printf gettext ("A total of %s%.2f has been paid for print quota by user %s.\n"), $params{'currency_symbol'}, $userhash{'spent'} + $userhash{'quota'}, $user;
		}	
		
		untie %userhash;
	}
}

sub used
{
	my ($printer) = @_;
	my (@printers, %printerhash, $etpb, $etpc, $cyanleft, $magentaleft, $yellowleft, $blackleft);
	
	if ($printer eq "") {
		opendir PRINTERS_DIR, "$params{'db_home'}/printers"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		@printers = readdir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot read from directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		@printers = sort grep { !/^\./ } @printers; 

		closedir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot close directory %s: %s.\n"), $0, "$params{'db_home'}/printers", $!));
		
		if ($#printers == -1) {
			printf gettext ("%s: Warning: no printers listed in %s - run %s --updateprinters.\n"), $0, "$params{'db_home'}/printers", $0;
		} else {
			foreach $printer (@printers) {
				$printer =~ s/\.db//;

# If the parameters are taken from global defaults, create an entry in the
# printers hash for this printer which is just a copy of the defaults.

				if (!defined ($printers{$printer})) {
					%{$printers{$printer}} = %{$printers{'default'}};
				}
				
				next if ($printers{$printer}{'colourspace'} eq 'pagecount');

				tie %printerhash, "Printbill::PTDB_File", "$params{'db_home'}/printers/$printer.db", "TRUE"
					or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/printers/$printer.db", $!));

				if ($printers{$printer}{'colourspace'} ne 'cmyk') {
					$etpb = $printers{$printer}{'estimated_total_percent_black'};
				}

				if ($printers{$printer}{'colourspace'} ne 'mono') {
					$etpc = $printers{$printer}{'estimated_total_percent_colour'};
				}

				if ($opt{'web'}) {
					if ($printers{$printer}{'colourspace'} ne 'mono') {
						$cyanleft = 100 * $printerhash{'estimated cyan'} / $etpc;
						$magentaleft = 100 * $printerhash{'estimated magenta'} / $etpc;
						$yellowleft = 100 * $printerhash{'estimated yellow'} / $etpc;
					} else {
						$cyanleft = 0;
						$magentaleft = 0;
						$yellowleft = 0;
					}
					
					if ($printers{$printer}{'colourspace'} ne 'cmy') {
						$blackleft = 100 * $printerhash{'estimated black'} / $etpb;
					} else {
						$blackleft = 0;
					}
					
					printf "$printer&%.2f:%.2f:%.2f:%.2f\n", $cyanleft, $magentaleft, $yellowleft, $blackleft;
				} else {
					printf gettext ("Printer \"$printer\":\n\n");

					if ($printers{$printer}{'colourspace'} ne 'mono') {
						$cyanleft = 100 * $printerhash{'estimated cyan'} / $etpc;
						printf gettext ("cyan: %.2f%% used.\n"), $cyanleft;

						$magentaleft = 100 * $printerhash{'estimated magenta'} / $etpc;
						printf gettext ("magenta: %.2f%% used.\n"), $magentaleft;

						$yellowleft = 100 * $printerhash{'estimated yellow'} / $etpc;
						printf gettext ("yellow: %.2f%% used.\n"), $yellowleft;
					}

					if ($printers{$printer}{'colourspace'} ne 'cmy') {
						$blackleft = 100 * $printerhash{'estimated black'} / $etpb;
						printf gettext ("black: %.2f%% used.\n"), $blackleft;
					}
					
					print "\n";
				}
			
				untie %printerhash;
			}
		}
	} else {
		if (-r "$params{'db_home'}/printers/$printer.db") {
			if (!defined ($printers{$printer})) {
				%{$printers{$printer}} = %{$printers{'default'}};
			}
	
			tie %printerhash, "Printbill::PTDB_File", "$params{'db_home'}/printers/$printer.db", "TRUE"
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/printers/$printer.db", $!));

			if ($printers{$printer}{'colourspace'} eq 'pagecount') {
				die_cleanup (-1, sprintf (gettext ("%s: Error: billing based only on pagecount - no information on ink/toner levels available.\n"), $0));
			}
			
			if ($printers{$printer}{'colourspace'} ne 'cmy') {
				$etpb = $printers{$printer}{'estimated_total_percent_black'};
			}

			if ($printers{$printer}{'colourspace'} ne 'mono') {
				$etpc = $printers{$printer}{'estimated_total_percent_colour'};
			}

			if ($opt{'web'}) {
				if ($printers{$printer}{'colourspace'} ne 'mono') {
					$cyanleft = 100 * $printerhash{'estimated cyan'} / $etpc;
					$magentaleft = 100 * $printerhash{'estimated magenta'} / $etpc;
					$yellowleft = 100 * $printerhash{'estimated yellow'} / $etpc;
				} else {
					$cyanleft = 0;
					$magentaleft = 0;
					$yellowleft = 0;
				}
				
				if ($printers{$printer}{'colourspace'} ne 'cmy') {
					$blackleft = 100 * $printerhash{'estimated black'} / $etpb;
				} else {
					$blackleft = 0;
				}
				
				printf "$printer&%.2f:%.2f:%.2f:%.2f\n", $cyanleft, $magentaleft, $yellowleft, $blackleft;
			} else {
				printf gettext ("Printer \"$printer\":\n\n");

				if ($printers{$printer}{'colourspace'} ne 'mono') {
					$cyanleft = 100 * $printerhash{'estimated cyan'} / $etpc;
					printf gettext ("cyan: %.2f%% used\n"), $cyanleft;

					$magentaleft = 100 * $printerhash{'estimated magenta'} / $etpc;
					printf gettext ("magenta: %.2f%% used\n"), $magentaleft;

					$yellowleft = 100 * $printerhash{'estimated yellow'} / $etpc;
					printf gettext ("yellow: %.2f%% used\n"), $yellowleft;
				}

				if ($printers{$printer}{'colourspace'} ne 'cmy') {
					$blackleft = 100 * $printerhash{'estimated black'} / $etpb;
					printf gettext ("black: %.2f%% used\n"), $blackleft;
				}
				
				print "\n";
			}
			
			untie %printerhash;
		} else {
			die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/printers/$printer.db", "Permission denied"));
		}
	}
}

sub resetblack
{
	my ($printer) = @_;
	my (@printers, %printerhash);

	if ($printer eq "") {
		opendir PRINTERS_DIR, "$params{'db_home'}/printers"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		@printers = readdir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot read from directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		@printers = sort grep { !/^\./ } @printers; 

		closedir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot close directory %s: %s.\n"), $0, "$params{'db_home'}/printers", $!));
		
		if ($#printers == -1) {
			printf gettext ("%s: Warning: no printers listed in %s - run %s --updateprinters.\n"), $0, "$params{'db_home'}/printers", $0;
		} else {
			foreach $printer (@printers) {
				$printer =~ s/\.db//;
				
				if (!defined ($printers{$printer})) {
					%{$printers{$printer}} = %{$printers{'default'}};
				}

				print "$printer\n";

				if ($printers{$printer}{'colourspace'} eq 'mono' || $printers{$printer}{'colourspace'} eq 'cmyk') {
					&lock ("printer_$printer");
			
					tie %printerhash, "Printbill::PTDB_File", "$params{'db_home'}/printers/$printer.db", "FALSE"
						or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/printers/$printer.db", $!));

					$printerhash{'estimated black'} = 0;

					untie %printerhash;
			
					&unlock ("printer_$printer");

					printf gettext ("Black ink levels for printer \"%s\" reset.\n"), $printer;
				} else {
					printf gettext ("$0: Warning: printer \"%s\" does not use black ink/toner, ignoring.\n"), $printer;
				}
			}
		}
	} else {
		if (!defined ($printers{$printer})) {
			%{$printers{$printer}} = %{$printers{'default'}};
		}

		if ($printers{$printer}{'colourspace'} ne 'cmy' || $printers{$printer}{'colourspace'} eq 'cmyk') {
			&lock ("printer_$printer");

			tie %printerhash, "Printbill::PTDB_File", "$params{'db_home'}/printers/$printer.db", "FALSE"
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/printers/$printer.db", $!));

			$printerhash{'estimated black'} = 0;
		
			untie %printerhash;

			&unlock ("printer_$printer");
		
			printf gettext ("Black ink levels for printer \"%s\" reset.\n"), $printer;
		} else {
			die_cleanup (sprintf (gettext ("%s: Error: printer \"%s\" does not use black ink/toner - can't reset black levels.\n"), $0, $printer));
		}
	}
}

sub resetcolour
{
	my ($printer) = @_;
	my (@printers, %printerhash);
	
	if ($printer eq "") {
		opendir PRINTERS_DIR, "$params{'db_home'}/printers"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		@printers = readdir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot read from directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		@printers = sort grep { !/^\./ } @printers; 

		closedir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot close directory %s: %s.\n"), $0, "$params{'db_home'}/printers", $!));
		
		if ($#printers == -1) {
			printf gettext ("%s: Warning: no printers listed in %s - run %s --updateprinters.\n"), $0, "$params{'db_home'}/printers", $0;
		} else {
			foreach $printer (@printers) {
				$printer =~ s/\.db//;
			
				if (!defined ($printers{$printer})) {
					%{$printers{$printer}} = %{$printers{'default'}};
				}

				if ($printers{$printer}{'colourspace'} ne 'mono' && $printers{$printer}{'colourspace'} ne 'pagecount') {
					&lock ("printer_$printer");
				
					tie %printerhash, "Printbill::PTDB_File", "$params{'db_home'}/printers/$printer.db", "FALSE"
						or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/printers/$printer.db", $!));
					
					$printerhash{'estimated cyan'} = 0;
					$printerhash{'estimated magenta'} = 0;
					$printerhash{'estimated yellow'} = 0;
					
					untie %printerhash;
	
					&unlock ("printer_$printer");
					
					printf gettext ("Colour ink levels for printer \"%s\" reset.\n"), $printer;
				} else {
					printf gettext ("%s: Warning: %s is mono, ignoring.\n"), $0, $printer;
				}
			}
		}
	} else {
		if (!defined ($printers{$printer})) {
			%{$printers{$printer}} = %{$printers{'default'}};
		}

		if ($printers{$printer}{'colourspace'} ne 'mono' && $printers{$printer}{'colourspace'} ne 'pagecount') {
			&lock ("printer_$printer");
			
			tie %printerhash, "Printbill::PTDB_File", "$params{'db_home'}/printers/$printer.db", "FALSE"
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/printers/$printer.db", $!));

			$printerhash{'estimated cyan'} = 0;
			$printerhash{'estimated magenta'} = 0;
			$printerhash{'estimated yellow'} = 0;
				
			untie %printerhash;
		
			&unlock ("printer_$printer");

			printf gettext ("Colour ink levels for printer \"%s\" reset.\n"), $printer;
		} else {
			die_cleanup (-1, sprintf (gettext ("%s: Error: printer %s is mono - can't reset colour levels.\n"), $0, $printer));
		}
	}
}

sub ppages
{
	my ($printer) = @_;
	my (@printers, %printerhash);

	if ($printer eq "") {
		opendir PRINTERS_DIR, "$params{'db_home'}/printers"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		@printers = readdir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot read from directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		@printers = sort grep { !/^\./ } @printers; 

		closedir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot close directory %s: %s.\n"), $0, "$params{'db_home'}/printers", $!));
		
		if ($#printers == -1) {
			printf gettext ("%s: Warning: no printers listed in %s - run %s --updateprinters.\n"), $0, "$params{'db_home'}/printers", $0;
		} else {
			foreach $printer (@printers) {
				$printer =~ s/\.db//;
			
				tie %printerhash, "Printbill::PTDB_File", "$params{'db_home'}/printers/$printer.db", "TRUE"
					or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/printers/$printer.db", $!));
				
				if (!defined ($printerhash{'total pages'})) {
					$printerhash{'total pages'} = 0;
				}

				if ($opt{'web'}) {
					print $printerhash{'total pages'} . "\n";
				} else {
					printf gettext ("A total of %s pages has been printed on printer \"%s\".\n"), $printerhash{'total pages'}, $printer;
				}

				untie %printerhash;
			}
		}
	} else {
		tie %printerhash, "Printbill::PTDB_File", "$params{'db_home'}/printers/$printer.db", "TRUE"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/printers/$printer.db", $!));

		if (!defined ($printerhash{'total pages'})) {
			$printerhash{'total pages'} = 0;
		}

		if ($opt{'web'}) {
			print $printerhash{'total pages'} . "\n";
		} else {
			printf gettext ("A total of %s pages has been printed on printer \"%s\".\n"), $printerhash{'total pages'}, $printer;
		}
		
		untie %printerhash;
	}
}

sub resetpages
{
	my ($printer) = @_;
	my (@printers, %printerhash);

	if ($printer eq "") {
		opendir PRINTERS_DIR, "$params{'db_home'}/printers"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		@printers = readdir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot read from directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		@printers = sort grep { !/^\./ } @printers; 

		closedir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot close directory %s: %s.\n"), $0, "$params{'db_home'}/printers", $!));
		
		if ($#printers == -1) {
			printf gettext ("%s: Warning: no printers listed in %s - run %s --updateprinters.\n"), $0, "$params{'db_home'}/printers", $0;
		} else {
			foreach $printer (@printers) {
				$printer =~ s/\.db//;
			
				&lock ("printer_$printer");
			
				tie %printerhash, "Printbill::PTDB_File", "$params{'db_home'}/printers/$printer.db", "FALSE"
					or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/printers/$printer.db", $!));

				$printerhash{'total pages'} = 0;

				untie %printerhash;

				&unlock ("printer_$printer");
			
				printf gettext ("Page count for printer \"%s\" reset.\n"), $printer;
			}
		}
	} else {
		&lock ("printer_$printer");

		tie %printerhash, "Printbill::PTDB_File", "$params{'db_home'}/printers/$printer.db", "FALSE"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/printers/$printer.db", $!));

		$printerhash{'total pages'} = 0;
		
		untie %printerhash;
		
		&unlock ("printer_$printer");
		
		printf gettext ("Page count for printer \"%s\" reset.\n"), $printer;
	}
}

sub colourspace
{
	my ($printer) = @_;
	my (@printers);

	if ($printer eq "") {
		opendir PRINTERS_DIR, "$params{'db_home'}/printers"
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));
		
		@printers = readdir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot read from directory %s for reading: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		@printers = sort grep { !/^\./ } @printers; 
		
		closedir PRINTERS_DIR
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot close directory %s: %s.\n"), $0, "$params{'db_home'}/printers", $!));

		if ($#printers == -1) {
			printf gettext ("%s: Warning: no printers listed in %s - run %s --updateprinters.\n"), $0, "$params{'db_home'}/printers", $0;
		} else {
			foreach $printer (@printers) {
				$printer =~ s/\.db//;
			
				if (!defined ($printers{$printer})) {
					%{$printers{$printer}} = %{$printers{'default'}};
				}

				if ($opt{'web'}) {
					print $printers{$printer}{'colourspace'}, "\n";
				} else {
					printf gettext ("Printer \"%s\" is %s.\n"), $printer, $printers{$printer}{'colourspace'};
				}
			}
		}
	} else {
		if (!defined ($printers{$printer})) {
			%{$printers{$printer}} = %{$printers{'default'}};
		}
			
		if ($opt{'web'}) {
			print $printers{$printer}{'colourspace'} . "\n";
		} else {
			printf gettext ("Printer \"%s\" is %s.\n"), $printer, $printers{$printer}{'colourspace'};
		}
	}
}

sub init
{
	my ($confirm, $uid, $gid, $web_uid, %mischash, %adminhash, $answer);
	
	if (! $opt{ask_no_questions}) {
		printf gettext ("About to WIPE everything in ALL databases!

*** WARNING ***

You only get one more chance, and this is it. If you press enter, any
existing databases under %s will be destroyed.

Press enter to confirm or CTRL-C to abort."), $params{db_home};

		<STDIN>;
	}

	$answer = `/bin/rm -rf $params{'db_home'} &> /dev/null`;
	
	if ($? >> 8) {
		die_cleanup (-1, sprintf (gettext ("%s: Error: cannot recursively remove directory %s: %s.\n"), $0, $params{'db_home'}, $answer));
	}

	umask 0;

	$uid = (getpwnam ($params{'printbilld_user'}))[2];

	if (defined $params{'printbilld_group'}) {
		$gid = getgrnam ($params{'printbilld_group'});
	} else {
		$gid = (getpwnam ($params{'printbilld_user'}))[3];
	}

# No main directory? Start by creating one!

	mkdir "$params{'db_home'}", 0755
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot create directory %s: %s.\n"), $0, $params{'db_home'}, $!));
		
	chown $uid, $gid, $params{'db_home'}
		or die_cleanup (-1, sprintf (gettext ("%s: could not set ownership on directory %s to %s: %s.\n"), $0, "$params{'db_home'}", "$uid.$gid", $!));

	mkdir "$params{'db_home'}/users", 0775
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot create directory %s: %s.\n"), $0, "$params{'db_home'}/users/", $!));

	chown $uid, $gid, "$params{'db_home'}/users"
		or die_cleanup (-1, sprintf (gettext ("%s: could not set ownership on directory %s to %s: %s.\n"), $0, "$params{'db_home'}/users/", "$uid.$gid", $!));
	
# Set directory permissions and ownership to appropriate values

	mkdir "$params{'db_home'}/stats", 0755
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot create directory %s: %s.\n"), $0, "$params{'db_home'}/stats/", $!));

	chown $uid, $gid, "$params{'db_home'}/stats"
		or die_cleanup (-1, sprintf (gettext ("%s: could not set ownership on directory %s to %s: %s.\n"), $0, "$params{'db_home'}/stats/", "$uid.$gid", $!));

	mkdir "$params{'db_home'}/printers", 0755
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot create directory %s: %s.\n"), $0, "$params{'db_home'}/printers/", $!));

	chown $uid, $gid, "$params{'db_home'}/printers"
		or die_cleanup (-1, sprintf (gettext ("%s: could not set ownership on directory %s to %s: %s.\n"), $0, "$params{'db_home'}/printers/", "$uid.$gid", $!));

	mkdir "$params{'db_home'}/tmp", 0770
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot create directory %s: %s.\n"), $0, "$params{'db_home'}/tmp/", $!));

	chown $uid, $gid, "$params{'db_home'}/tmp"
		or die_cleanup (-1, sprintf (gettext ("%s: could not set ownership on directory %s to %s: %s.\n."), $0, "$params{'db_home'}/tmp/", "$uid.$gid", $!));
		
	if (defined $params{'stats_path'} && defined $params{'graph_png_output_dir'}) {
# Be slightly more forgiving with this directory - if it exists, just
# chown/chmod it. And if we can't, don't worry - it's not critical.
		if (-d $params{'graph_png_output_dir'}) {
			chmod 0755, $params{'graph_png_output_dir'};
		} else {
			mkdir $params{'graph_png_output_dir'}, 0755
				or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot create directory %s: %s.\n"), $0, $params{'graph_png_output_dir'}, $!));
		}
			
		if (defined $params{'web_user'}) {
			$web_uid = getpwnam ($params{'web_user'});
		} else {
			$web_uid = $uid;
		}
		
		chown $web_uid, $gid, $params{'graph_png_output_dir'};
	}

	&lock ("misc");

	tie %mischash, "Printbill::PTDB_File", "$params{'db_home'}/misc.db", "FALSE" or
		die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/misc.db", $!));

	$mischash{'total pages'} = 0;
	$mischash{'total paid'} = 0;
	$mischash{'total spent'} = 0;

	untie %mischash;

	chmod 0664, "$params{'db_home'}/misc.db"
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot set mode on file %s to %s: %s.\n"), $0, "$params{'db_home'}/misc.db", "0664", $!));

	chown $uid, $gid, "$params{'db_home'}/misc.db"
		or die_cleanup (-1, sprintf (gettext ("%s: could not set ownership on file %s to %s: %s.\n"), $0, "$params{'db_home'}/misc.db", "$uid.$gid", $!));

	&unlock ("misc");

	if (defined $params{'web_user'}) {
		$web_uid = getpwnam ($params{'web_user'});

		&lock ("admin");

		tie %adminhash, "Printbill::PTDB_File", "$params{'db_home'}/web_admin.db", "FALSE" or
			die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/web_admin.db", $!));

		%adminhash = ();

		untie %adminhash;

		chmod 0600, "$params{'db_home'}/web_admin.db"
			or die_cleanup (-1, sprintf (gettext ("%s: could not set mode on file %s to %s: %s.\n"), $0, "$params{'db_home'}/web_admin.db", "0600", $!));

		chown $web_uid, $gid, "$params{'db_home'}/web_admin.db"
			or die_cleanup (-1, sprintf (gettext ("%s: could not set ownership on file %s to %s: %s.\n"), $0, "$params{'db_home'}/web_admin.db", "$web_uid.$gid", $!));

		&unlock ("admin");

		mkdir "$params{'db_home'}/cookies", 0700
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot create directory %s: %s.\n"), $0, "$params{'db_home'}/cookies", $!));

		chown $web_uid, $gid, "$params{'db_home'}/cookies"
			or die_cleanup (-1, sprintf (gettext ("%s: could not set ownership on directory %s to %s: %s.\n"), $0, "$params{'db_home'}/cookies", "$web_uid.$gid", $!));
	}
	
	printf gettext ("Re-constructed %s and cleared the databases.\n"), $params{'db_home'};
}

sub webadduser
{
	my ($user) = @_;
	my (%adminhash, $password1, $password2, $salt, $web_uid, $gid, $dne);

	$web_uid = getpwnam ($params{'web_user'});

	&lock ("admin");
	
	if (! -f "$params{'db_home'}/web_admin.db") {
		$dne = 1;
	} else {
		$dne = 0;
		
		tie %adminhash, "Printbill::PTDB_File", "$params{'db_home'}/web_admin.db", "TRUE" or
			die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/web_admin.db", $!));
	}

	if ($user eq "") {
		printf gettext ("%s: Error: (null) is not a valid username.\n"), $0;
		if (! $dne) {
			untie %adminhash;
		}
	} elsif ((! $dne) && defined ($adminhash{$user})) {
		printf gettext ("%s: Error: %s already has an admin account.\n"), $0, $user;
		untie %adminhash;
	} else {
		printf gettext ("Adding new web print administration user %s.\n"), $user;
		system "stty -echo";
		printf gettext ("Password for %s: "), $user;
		chomp ($password1 = <STDIN>);
		print "\n";
		printf gettext ("Re-enter password for %s: "), $user;
		chomp ($password2 = <STDIN>);
		print "\n";
		system "stty echo";

		$salt = &random_salt;

		if ($password1 eq $password2) {
			if (! $dne) {
				untie %adminhash;
			}

			tie %adminhash, "Printbill::PTDB_File", "$params{'db_home'}/web_admin.db", "FALSE" or
				die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/web_admin.db", $!));
			
			$adminhash{$user} = crypt ($password1, $salt);
			
			untie %adminhash;
			
			if ($dne) {
				if (defined $params{'printbilld_group'}) {
					$gid = getgrnam ($params{'printbilld_group'});
				} else {
					$gid = (getpwnam ($params{'printbilld_user'}))[3];
				}

				chmod 0600, "$params{'db_home'}/web_admin.db"
					or die_cleanup (-1, sprintf (gettext ("%s: could not set mode on file %s to %s: %s.\n"), $0, "$params{'db_home'}/web_admin.db", "0600", $!));

				chown $web_uid, $gid, "$params{'db_home'}/web_admin.db"
					or die_cleanup (-1, sprintf (gettext ("%s: could not set ownership on file %s to %s: %s.\n"), $0, "$params{'db_home'}/web_admin.db", "$web_uid.$gid", $!));
			}
			
			printf gettext ("Print administrator %s now exists.\n"), $user;
		} else {
			printf gettext ("%s: Error: passwords don't match.\n"), $0;	
		}
	}

	&unlock ("admin");
}

sub webdeluser
{
	my ($user) = @_;
	my (%adminhash);

	&lock ("admin");

	tie %adminhash, "Printbill::PTDB_File", "$params{'db_home'}/web_admin.db", "FALSE" or
		die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/web_admin.db", $!));

	if (defined ($adminhash{$user})) {
		delete $adminhash{$user};
		printf gettext ("Print administrator %s no longer exists.\n"), $user;
	} else {
		printf gettext ("%s: Error: %s does not appear to have an admin account.\n"), $0, $user;
	}
	
	untie %adminhash;

	&unlock ("admin");
}

sub webpasswd
{
	my ($user) = @_;
	my (%adminhash, $password1, $password2, $salt);
	
	printf gettext ("Changing administrative password for %s.\n"), $user;
	printf gettext ("New password for %s: "), $user;
	system "stty -echo";
	chomp ($password1 = <STDIN>);
	print "\n";
	printf gettext ("Re-enter password for %s: "), $user;
	chomp ($password2 = <STDIN>);
	print "\n";
	system "stty echo";

	if ($password1 eq $password2) {
		$salt = &random_salt;

		&lock ("admin");
	
		tie %adminhash, "Printbill::PTDB_File", "$params{'db_home'}/web_admin.db", "FALSE" or
			die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/web_admin.db", $!));

		if (defined ($adminhash{$user})) {
			$adminhash{$user} = crypt ($password1, $salt);
			untie %adminhash;

			printf gettext ("Password for %s changed.\n"), $user;
		} else {
			untie %adminhash;

			printf gettext ("Print administrator %s does not exist.\n"), $user;
			print gettext ("Please use --webadduser first.\n");
		}

		&unlock ("admin");
	} else {
		printf gettext ("%s: Error: passwords don't match.\n"), $0;
	}
	
}

sub webnonintpasswd
{
	my ($user, $pass) = @_;
	my (%adminhash, $salt);
	
	&lock ("admin");
	
	tie %adminhash, "Printbill::PTDB_File", "$params{'db_home'}/web_admin.db", "FALSE" or
		die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/web_admin.db", $!));

	if (defined ($adminhash{$user})) {
		$salt = &random_salt;
		$adminhash{$user} = crypt ($pass, $salt);
		printf gettext ("Password for %s changed.\n"), $user;
	} else {
		printf gettext ("Print administrator %s does not exist.\n"), $user;
		print gettext ("Please use --webadduser first.\n");
	}
	
	untie %adminhash;

	&unlock ("admin");
}

# Generate a random salt

sub random_salt
{
	my ($C1, $C2);
	$C1 = &salt_char;
	$C2 = &salt_char;
	
	return $C1 . $C2;
}

sub salt_char
{
	my $R = floor (rand() * 64);
	my ($C);
	
	if ($R < 26) {
		$C = chr ($R + ord ("A")); 
	} elsif ($R < 52) {
		$C = chr ($R - 26 + ord ("a"));
	} elsif ($R < 62) {
		$C = chr ($R - 52 + ord ("0"));
	} elsif ($R == 62) {
		$C = '.';
	} else {
		$C = '/';
	}

	return $C;
}

# 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
{
	my ($text) = @_;
	my $lockpid;

	while (-e "$params{'db_home'}/tmp/.printbill_$text.lock") {
		open (LOCKFILE, "<$params{'db_home'}/tmp/.printbill_$text.lock")
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for reading: %s.\n"), $0, "$params{'db_home'}/tmp/.printbill_$text.lock", $!));
		
		flock LOCKFILE, LOCK_EX
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot lock %s: %s.\n"), $0, "$params{'db_home'}/tmp/.printbill_$text.lock", $!));

		$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");

		if (!$opt{web}) {
			&die_cleanup (-1, "$0: $params{'db_home'}/tmp/.printbill_$text.lock is held by another active process.\n");
		}

# Otherwise, we have to wait.

		close LOCKFILE
			or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot close lockfile %s: %s.\n"), $0, "$params{'db_home'}/tmp/.printbill_$text.lock", $!));

		sleep $params{'retry_interval'};
	}
	
	open (LOCKFILE, ">$params{'db_home'}/tmp/.printbill_$text.lock")
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot open %s for writing: %s.\n"), $0, "$params{'db_home'}/tmp/.printbill_$text.lock", $!));
	
	flock LOCKFILE, LOCK_EX
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot lock %s: %s.\n"), $0, "$params{'db_home'}/tmp/.printbill_$text.lock", $!));

	print LOCKFILE $$
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot write to %s: %s.\n"), $0, "$params{'db_home'}/tmp/.printbill_$text.lock", $!));

	$locks{$text} = 1;

	close LOCKFILE
		or die_cleanup (-1, sprintf (gettext ("%s: Error: cannot close lockfile %s: %s.\n"), $0, "$params{'db_home'}/tmp/.printbill_$text.lock", $!));
}

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

	if (-e "$params{'db_home'}/tmp/.printbill_$text.lock") {
		unlink "$params{'db_home'}/tmp/.printbill_$text.lock"
			or printf STDERR gettext ("%s: Error: cannot remove lockfile %s: %s.\n"), $0, "$params{'db_home'}/tmp/.printbill_$text.lock", $!;
		delete $locks{$text};
	} else {
		printf STDERR gettext ("%s: %s doesn't exist...\n"), $0, "$params{'db_home'}/tmp/.printbill_$text.lock";
	}
	
}

# Wash our hands of the whole affair

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

# Cleanup and die gracefully...

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

	&cleanup;

	print STDERR $msg;
	exit $the_return_val;
}

# Deal with signals. Die, but clean up our mess first.

sub catch_zap {
	&cleanup;
	exit 0;
}
