#! /usr/bin/perl

use strict;
use Cwd;
use POSIX qw(tmpnam); 	# ... needed (this may not work on Windows).
use LWP; 		# For the http request and package downloading...
use File::Copy qw(copy);
use File::Basename qw(dirname basename);
use File::Path qw(rmtree mkpath);
use FileHandle; 	# Use saner OO style instead of IO style for print function.
use Getopt::Long 2.01;
use Fcntl;

use FindBin; 
use lib "$FindBin::RealBin/../lib/perl5";
use lib "/tmp/c";

use RDB qw(RDB_parse_array RDB_parse_string RDB_splice_data);
use INF qw(read_inf inf_get_key inf_get_keys inf_has_key);
use inf_nt qw(parse_inf_nt);
use imp_gpg; 

########################################################################
# #### Start of file scoped variables.

# Where to install packages to.
my $base_dir = undef; # look in cd_temp()

# Where to request from.
# my $base_url = "http://code.and.org/cgi-bin/imp_retrieval.cgi";
# my $real_base_url = "http://imprints.sourceforge.net/cgi-bin/imp_retrieval.cgi";
# my $real_base_url = "http://imprints.samba.org/imprints/imp_retrieval.cgi";
my $real_base_url = "http://imprints.samba.org/cgi-bin/imp_retrieval.cgi";
my $base_url = undef;

# Paths to external programs we use.
my $path_self = "$FindBin::RealBin/" . basename($0);
my $path_gzip = "gzip";
my $path_tar = "tar";

## commented out as the rpc_client_wrapper.pl script has been 
## included in this file as a subroutine  --jerry
# my $path_rpc_client_wrapper = "$FindBin::RealBin/../scripts/rpc_client_wrapper.pl";
# my $path_rpc_query_wrapper = "$FindBin::RealBin/../scripts/rpc_query_wrapper.pl";

my $path_rpcclient = "rpcclient";
my $path_smbclient = "smbclient";

# Extra options...
my $option_help = 0; 		# Do we show help.
my $option_version = 0; 	# Do we show the version.
my $option_cleanup = 1; 	# Do we cleanup the temp dir.
my $option_pubkey_install = -1; # -1 = default, 1 = yes, 0 = no
my $option_tmpdir = undef; 	# Change $ENV{'TMPDIR'}, tmpname() doesn't use it.
my $option_query = 0; 		# Just do a query for valid printer names.
my $option_rpcquery = 0; 	# Just do a query for samba stuff.
my $option_verbose = 0; 	# Show verbose messages during progress.
my $option_authfile = undef; 	# File for auth input
my $option_language = undef; 	# Language we want to printer drivers in.
my $option_cache_dir = undef; 	# Place to cache packages
my $option_pkg_name = undef; 	# local cached package
my $option_servers_file = undef; # File with a list of servers/languages.

my $DEBUG = 0;

# Parameters sent to the request server.
my @params = ();
my @download_params = ("action=get-printer-info");
my @query_params =    ("action=list-printers");

my $tmp_dir = "";

# Error code variables... 
my $error_code_default = 1;
my $error_code_request = 2;
my $error_code_query_6 = 3;

# #### End of file scoped variables.
########################################################################


########################################################################
# CD to a known safe directory.
# Params: none
# Returns: directory name.
sub cd_temp ()
{
	my $tmp = undef;

	# Cd to a known tmp dir...
	do {
		# Perl doesn't have mktemp() or tempnam().
		$tmp = tmpnam();
	} until (mkdir($tmp, 0700));

	if (!chdir($tmp)) { 
		fatal_error ("Error: Cannot chdir($tmp): $!", 0); 
	} 

	$base_dir = "$tmp/install";
	return ($tmp)
}


########################################################################
# Params:
#  $base_url = The url from which the request should be made.
#  $params   = An array of parameters to put on the end of the url.
#  $ua       = A UserAgent object from LWP.
sub send_request
{
	my ($base_url, $params, $ua) = @_;

	my $req = HTTP::Request->new('GET' => $base_url . "?" . (join "&", @{$params}));
    
	my $res = $ua->request($req);
	if (!$res->is_success)  { 
		fatal_error ("Error: REQ " . $res->status_line, 1, $error_code_request); 
	}

	return ($res->content);
}


########################################################################
# Params:
#  $tbl      = A table object from RDB.
# Side Effect: Prints table or names and descriptions.
sub output_query_results
{
	my ($tbl) = @_;

	for (my $i=0; $i <= $#{$tbl->{data}->{"printer_name"}}; $i++) {
		my $name = $tbl->{data}->{"printer_name"}[$i];
		print("[printer_name]: $name\n");
	}
}


########################################################################
# Params:
#  $ua        = A UserAgent object from LWP.
#  $file_name = A Filename to download to.
#  $req       = A HTTP::Request object.
# Returns: Return value of $ua->request();
sub request_with_feedback
{
	my ($ua, $file_name, $req) = @_;

	my $expected_length = undef;
	my $bytes_received = 0;

	my $res = undef;

	if (!open(PKG, "> $file_name")) {
		return (undef);
	}

	my $last_timestamp = 0;

	$res = $ua->request($req,
		sub
		{
			my ($chunk, $res) = @_;
			$bytes_received += length($chunk);
			  
			if (!defined ($expected_length)) {
				$expected_length = $res->content_length || 0;
			}

			if ((time - $last_timestamp) > 2) {
				$last_timestamp = time;

				if ($expected_length) {
					my $stats = sprintf ("%d - %d%%\n", $bytes_received,
						100 * $bytes_received / $expected_length);
					STDOUT->print("Download Status: " . $stats);
				}
				else {
					my $stats = sprintf ("%d\n", $bytes_received);
					STDOUT->print("Download Status: " . $stats);
				}
			}

			PKG->print($chunk);
		}
	);
    
	if (!close(PKG)) { 
		return (undef); 
	}

	return ($res);
}

########################################################################
# Params:
#  $ua       = A UserAgent object from LWP.
#  $tbl      = A table object from RDB.
# Returns: Name of the new package.
sub download_package
{
	my ($ua, $tbl) = @_;
	my $res = undef;
   	my $loc = undef;
	my $here_before = 0;

	if (!scalar(@{$tbl->{data}->{"location_url"}})) { 
		fatal_error ("Error: No download locations.", 1); 
	}

	while (scalar(@{$tbl->{data}->{"location_url"}})) {
		if ($here_before) {
			warn "Warn: PKG " . $loc . ": " . $res->status_line . "\n"; 
		}
		$here_before = 1;

		my $pkg_name = $tbl->{data}->{"package_name"}[0];

		# Take out non good chars.
		$pkg_name =~ s/([^-a-zA-Z0-9 :._])/X/g;
	
		if (defined ($option_cache_dir) &&
			(-r "$option_cache_dir/$pkg_name") &&
			(link("$option_cache_dir/$pkg_name", "$pkg_name") ||
			copy("$option_cache_dir/$pkg_name", "$pkg_name")))
		{ 
			# It's assumed you have to GPG key
			$option_pubkey_install = 0;
			return ($pkg_name);
		}

		$loc = $tbl->{data}->{"location_url"}[0];

		my $req = HTTP::Request->new('GET' => $loc);

		if ($option_verbose) {
			$res = request_with_feedback($ua, $pkg_name, $req);
		}
		else {
			$res = $ua->request($req, $pkg_name);
		}

		if ($res->is_success) {
			if (!$option_pubkey_install) { 
				return ($pkg_name); 
			}

			$loc =~ s!/([^/]+)$!/!;
			$loc .= "public_key";
	    
			$req = HTTP::Request->new('GET' => $loc);
	    
			$res = $ua->request($req, "./public_keys");
	    
			if (($option_pubkey_install != 1) || $res->is_success) { 
				return ($pkg_name); 
			}
  
			unlink("$pkg_name");
		}

		RDB_splice_data($tbl, 0, 1);
	}

	fatal_error ("Error: PKG " . $loc . ": " . $res->status_line, 1);
}

########################################################################
# Params:
#  $file_name = The filename of the package that we want to verify.
#  $version   = The GPG version ID.
#  $sig       = The GPG signature for the package.
#  $crc       = The GPG CRC for the signature.
sub verify_package
{
	my ($file_name, $version, $sig, $crc) = @_;
	my $installed_pubkey = 0;
	my $rc = -1;

	if ($option_pubkey_install == 1) {
		$rc = impgpg_install_pubkey ("./public_keys");
		if ($rc != 0) {
			fatal_error ("Error: Installing public key. gpg ended "
				. "with an exit code of $rc.", 1);
		}
	}
	
	$rc = impgpg_print_sigfile ("signature", $version, $sig, $crc);
	if ($rc != 0)  {
		fatal_error ("Error: Unable to write signature file.", 1);
	}
    
	$rc = impgpg_verify_signature_using_sigfile ($file_name, "signature");
	if ($rc != 0) {
		my $verify_rc = $rc;
	
		if (-f "./signature") {
			if (!$option_pubkey_install || ! -r "./public_keys") {
				fatal_error ("Error: Verifying signature for file '$file_name'. "
					. "gpg ended with an exit code of $rc.", 1);
				return;
			}

			warn "Warn: Trying to install a newer public key.\n";
			$rc = impgpg_install_pubkey ("./public_keys");
			if ($rc == 0) {
				$rc = impgpg_verify_signature_using_sigfile($file_name, "signature");
				if ($rc == 0) { 
					goto make_cached_copy; 
				}
			}
			else {
				$rc = $verify_rc;
			}
		}
	
		fatal_error ("Error: Verifying signature for file '$file_name'. "
			. "gpg ended with an exit code of $rc.", 1);
	}


make_cached_copy:
	if (defined ($option_cache_dir) &&
		(! -r "$option_cache_dir/$file_name"))
	{
		if (!link("$file_name", "$option_cache_dir/$file_name")) {
			copy("$file_name", "$option_cache_dir/$file_name");
		}
	}

}

########################################################################
# Params:
#  $file_name = The filename of the package that we want to explode.
sub explode_package
{
	my ($file_name) = @_;

	if (system("$path_gzip -dc $file_name | $path_tar -xf -")) { 
		fatal_error ("Error: Couldn't explode package.\n", 1); 
	}
}


########################################################################
# Finds a case insensitive match to a filename.
# Params:
#  $file_name = The filename that we want to find.
#  $dir       = The path that we find from.
sub get_real_filename
{
	my ($file_name, $dir) = @_;
	my @files = ();

	if (!$file_name || !opendir(DIR, $dir)) { 
		return (undef); 
	}
    
	@files = readdir(DIR);

	closedir(DIR);

	@files = grep (/^$file_name$/i, @files);

	if (scalar(@files)) {
		return ("$dir/$files[0]");	
	}

	return (undef);
}


######################################################################
# read_authfile
#
## Need to return an array of values
sub read_authfile
{
	my ($rpc_authfile, $printer_model_name) = @_;
	my @lines = ();
	my $rpc_printer_name = "";
	my $rpc_share_samba_name = "";
	my $rpc_port_name = "";
	my $servername = "";
	my @return_params = undef;

	## read in the authorization file data
	if (! open (IN, "< $rpc_authfile")) { 
		fatal_error("Error: Couldn't open Authorization file.\n"); 
	}
	@lines = <IN>;
	if (!close (IN)) { 
		fatal_error("Error: Couldn't close Authorization file.\n"); 
	}

	chomp (@lines);

	## we only need to handle the printer name, share name, port name,
	## and server name.  The username/password lines will be read directly
	## by smbclient and rpcclient
	for (@lines) {
		if (/^\s*folder\s+share\s+name\s*=(.+)$/) {
			$rpc_printer_name = $1;
		}
		if (/^\s*samba\s+share\s+name\s*=(.+)$/) {
			$rpc_share_samba_name = $1;
		}
		if (/^\s*printer\s+port\s+name\s*=(.+)$/) {
			$rpc_port_name = $1;
		}
		if (/^\s*server\s*=(.+)$/) {
			$servername = $1;
		}
	}
    
	if ($rpc_printer_name eq "<auto>") {
		$rpc_printer_name = $printer_model_name;
	}
	
	@return_params = ($rpc_printer_name, $rpc_share_samba_name, $rpc_port_name, $servername);
}


######################################################################
# validate_credentials
#
## return a boolean depending on whether or not we could
## logon to the server
sub validate_credentials
{
	my ($server, $authfile) = @_;
	my $cmd = undef;
	my @cmd_output = undef;
	my $auth_failed = 0;
	my $bad_hostname = 0;
	my $session_request_failed = 0;
	my $valid_credentials = 0;

	$cmd = "$path_smbclient -L $server -A $authfile";
	if ($DEBUG) { print STDERR "$cmd\n"; }
	if (!open ( SMBCLIENT, "$cmd|")) {
		warn "[rpc]: Unable to open smbclient session!\n";
		return 0;
	}
	@cmd_output = <SMBCLIENT>;
	close (SMBCLIENT);
	
	for (@cmd_output) {
		if ($DEBUG) { print STDERR "$_"; }
		
		if ($_ =~ /session request.*failed/) {
			print "[rpc]: $_";
			$session_request_failed = 1;
		}
		
		if ($_ =~ /ERRbadpw/) {
			$auth_failed = 1;
			print "[rpc]: Bad username/password for host $server!.\n";
			last;
		}
		
		if ($_ =~ /Connection to.*failed/) {
			$bad_hostname = 1;
			last;
		}
		
		if ($_ =~ /IPC\$.*IPC Service/) {
			$valid_credentials = 1;
			last;
		}
	}
	
	## reset any failed session requests if we finally got it right
	if ($valid_credentials) {
		$session_request_failed = 0;
	}
	
	if ($session_request_failed) {
		print "\n";
		print "[rpc]: *****************************************************************\n";
		print "[rpc]: A failed session request is usually indicative of\n";
		print "[rpc]:    some type of netbios name resolution problem.  This can\n";
		print "[rpc]:    also be caused by hosts allow/deny lines in the Samba\n";
		print "[rpc]:    server's smb.conf(5) file.\n";
		print "[rpc]: *****************************************************************\n\n";
	}

	## if it was a bad hostname, let's try again, but this time supplying
	## the IP address as well if we can get it via a gethostbyname() call
	if ($bad_hostname) {
		my $name = undef;
		my $aliases = undef;
		my $addrtype = undef;
		my $length = undef;
		my @addrs = undef;
		my ($a, $b, $c, $d);
		
		print "Attempting to resolve $server via gethosybyname()...";
		($name, $aliases, $addrtype, $length, @addrs) = gethostbyname($server);
		if (defined($name)) {
			for (@addrs) {
				$bad_hostname = 0;
				($a, $b, $c, $d) = unpack('C4', $_);
				print "$a.$b.$c.$d...";
				$cmd = "$path_smbclient -L $server -A $authfile -I $a.$b.$c.$d";
				if ($DEBUG) { print STDERR "$cmd\n"; }
				if (!open ( SMBCLIENT, "$cmd|")) {
					warn "[rpc]: Unable to open smbclient session!\n";
					return "";
				}
				@cmd_output = <SMBCLIENT>;
				close (SMBCLIENT);
				
				for (@cmd_output) {
					if ($DEBUG) { print STDERR "$_"; }
					if ($_ =~ /ERRbadpw/) {
						$auth_failed = 1;
						print "[rpc]: Bad username/password for host $server!.\n";
						last;
					}
	
					if ($_ =~ /Connection to.*failed/) {
						$bad_hostname = 1;
						last;
					}
				}

			}
		}
		print "\n";
		
		if ($bad_hostname) {
			print "[rpc]: Invalid Print Server name - $server!\n";
		}

	}

	if ($auth_failed || $bad_hostname || $session_request_failed) {
		return 0;
	}
	
	return 1;
}


######################################################################
# setup_credentials
#
sub setup_credentials
{
	my ($tmpfile) = @_;
	my $servername = undef;
	my $username = undef;
	my $password = undef;
	
	if (!open(TMP, "> $tmpfile")) {
		STDERR->print ("Unable to open $tmpfile for writing!\n");
		return "";
	}
	##
	## get input from user and create the authfile
	##
	print "Server: "; 
	$servername = <STDIN>; 
	chomp ($servername);

	print "Username: ";
	$username = <STDIN>; 
	chomp ($username);

	print "Password: ";
	$password = <STDIN>; 
	chomp ($password);

	## save crendentials  and close authfile since
	## we will need to pass it to the validate_credentials() function
	TMP->print("server=$servername\n");
	TMP->print("username=$username\n");
	TMP->print("password=$password\n");

	close (TMP);

	return $servername;
}

######################################################################
# build_authfile
#
## Need to return a filename
sub build_authfile
{
	my $tmp_file_name = undef;
	my $rpc_printer_name = undef;
	my $rpc_share_samba_name = undef;
	my $rpc_port_name = undef;
	my $servername = undef;
	my $username = undef;
	my $password = undef;
	my $cmd = undef;
	my @rpc_output = ();
	my ($tmp_str, $string) = undef;
	my @port_list = ();
	my $port_ok = undef;

	## generate a temp file name which can be used as the authfile
	## and write out the data
	do {
		$tmp_file_name = tmpnam();
	} while (!sysopen(TMP, $tmp_file_name, O_RDWR|O_CREAT|O_EXCL, 0600));

	## get the username, password and server tuple from user
	$servername = setup_credentials($tmp_file_name);
	if ("$servername" eq "") {
		return "";
	}
	
	## since we have the servername, username and password, 
	print "Testing logon credentials...\n";
	if (!validate_credentials($servername, $tmp_file_name)) {
		## never leave a password lying around (even a bad one)
		unlink ($tmp_file_name);
		return "";
	}

	## get the remaining data since the servername/username/password
	## tuple has been validated.  
		
	print "Printer name (press <ENTER> to default to the printer model name): ";
	$rpc_printer_name = <STDIN>; 
	chomp($rpc_printer_name);
	$rpc_printer_name =~ s/\s+//g;
	if ( length ($rpc_printer_name) == 0 ) {
		$rpc_printer_name = "<auto>";
	}

	## We can query the remote server also to get the valid port 
	## names and printer share names
	print "Retrieving remote server information...\n";
	
	@rpc_output = rpc_query_wrapper($tmp_file_name, $servername);
	print "\tValid Printer Share Names:\n";
	for (@rpc_output) {
		if ($_ =~ /\[samba_share\]/) {
			($tmp_str, $string) = split(/:/, $_);
			$string =~ s/^\s+//;
			print "\t\t$string\n";
		}
	}
	print "\n";

	print "\tValid Port Names:\n";
	for (@rpc_output) {
		if ($_ =~ /\[printer_port\]/) {
			($tmp_str, $string) = split(/:/, $_);
			$string =~ s/^\s+//;
			chomp ($string);
			print "\t\t$string\n";
			push (@port_list, $string);
		}
	}
	print "\n";
	
	print "Samba share name: ";
	$rpc_share_samba_name = <STDIN>; 
	chomp($rpc_share_samba_name);

	## loop to verify the correctness of the portname
	## against the list returned by the server
	do {
		$port_ok = 0;
		print "Printer port: ";
		$rpc_port_name = <STDIN>;
		chomp($rpc_port_name);

		for (@port_list) {
			if ("$_" eq "$rpc_port_name") {
				$port_ok = 1;
				last;
			}
		}
		
		if (!$port_ok) {
			print "Invalid port name!\n";
		}
	} while (!$port_ok);

	
	if (!open(TMP, ">> $tmp_file_name")) {
		print "Unable to reopen the authentication file\n";
		return "failed";
	}
	
	TMP->print("folder share name=$rpc_printer_name\n");
	TMP->print("samba share name=$rpc_share_samba_name\n");
	TMP->print("printer port name=$rpc_port_name\n");

	close(TMP);

	## return the filename
	return $tmp_file_name;

}


######################################################################
# rpc_query_wrapper
#
sub rpc_query_wrapper
{

	my ($rpc_authfile, $server) = @_;
	my $cmd = undef;
	my @info = ();	

	# get the shared printers
	$cmd = "$path_smbclient -L $server -A $rpc_authfile";
	if (!open (RPC_IN, "$cmd  |")) {
		fatal_error("Error: Couldn't run $cmd (get enum ports).\n");
	}

	my $state = 0;
	while (<RPC_IN>) {
		if (!$state && /^\s*Sharename\s+Type\s+Comment\s*$/) {
			$state = 1;
		}

		last if ($state && /^\s*Server\s+Comment\s*$/);
			

		if ($state && /^\s*(\S+)\s+Printer\s+/) {
			push( @info, "[samba_share]: $1");
		}
	}
	close (RPC_IN);
	
	## get the ports
	$cmd = "$path_rpcclient $server -d 1 -A $rpc_authfile -c \"enumports 1\"";
	if (!open (RPC_IN, "$cmd |")) {
	    fatal_error("Error: Couldn't run $cmd (get enum ports).\n");
	}

	while (<RPC_IN>) {
		if (/^\s*Port Name:\s*\[([^\]]+)\]\s*$/) {
			push (@info, "[printer_port]: $1");
		}
	}
	close (RPC_IN);
	
	@info;
}

######################################################################
# rpc_client_wrapper
#
sub rpc_client_wrapper
{
	my @args = @_;

	my $cmd_file_name = undef; 	# We _will_ have to create this

	my $cmd = undef; # Throw into 
	my $cmd_string = undef;
	my $cmd_rpc = undef;
	my $rpc_output = undef;
	my $drv_file = undef;
	my $authfile = undef;
	my $ret = 1;
	
	## save the current working directory
	my $save_cwd = &cwd;

	# Stuff that is always passed in as args...
	my $printer_model_name = undef;
	my $driver_file_name = undef;
	my $data_file_name = undef;
	my $config_file_name = undef;
	my $help_file_name = undef;
	my $language_monitor = undef; # unused

	my $rpc_printer_name = undef;
	my $rpc_share_samba_name = undef;
	my $rpc_port_name = undef;
	my $servername = undef;
	my ($error, $error_string) = undef;

	# Values got from rpcclient/smbclient
	my $printer_upload_dir = undef; # Dir for first arg to smbclient
	my $printer_upload_last = undef; # Dir for in last arg to smbclient

	my %arch_map = ("W32X86"   => "Windows NT x86",
			"WIN40"    => "Windows 4.0",
			"W32MIPS"  => "Windows NT R4000",
			"W32ALPHA" => "Windows NT Alpha_AXP",
			"W32PPC"   => "Windows NT PowerPC");
	my $arch = undef; 	# Right side of above mapping...

	## first thing is to grab the authfile name
	$authfile = shift (@args);

	## make sure this is a valid architecture for printer drivers
	if (exists($arch_map{$args[0]})) {
		$arch = $arch_map{$args[0]};
		shift(@args);
	}
	else {
		fatal_error("Error: Not a supported arch for rpcclient.\n");
	}

	# grab the remaining fields for a DRIVER_INFO_3 struct
	$printer_model_name = shift (@args);
	$driver_file_name = shift(@args);
	$data_file_name = shift(@args);
	$config_file_name = shift(@args);
	$help_file_name = shift(@args);
	$language_monitor = shift(@args); 	# unused

	## previous generate authfile code was here
	( $rpc_printer_name, 
	  $rpc_share_samba_name, 
	  $rpc_port_name, 
	  $servername) 	= read_authfile($authfile, $printer_model_name);

	if (!chdir (dirname($args[0]))) { 
		fatal_error("ERROR!  Couldn't chdir()\n"); 
	}

	for (@args) {
		$_ = basename($_);
	}

	print "[rpc]: Installing $arch drivers for $printer_model_name...\n";

	##
	## Step #1 : get the upload directory for the driver files
	##
	## see the rpcclient(1) man page for more information
	## on this MS-RPC
	##
	## e.g. getdriverdir "Windows NT x86"

	## run the command
	$cmd_string = "getdriverdir \\\"$arch\\\"";
	$cmd = "$path_rpcclient $servername -d 1 -A $authfile -c \"$cmd_string\"";
	if ($DEBUG) { print STDERR "$cmd\n"; }
	if (!open (RPC_IN, "$cmd|")) {
		fatal_error("ERROR!  Couldn't run $cmd (get driver dir).\n");
	}

	while ($rpc_output = <RPC_IN>) {
		if ($DEBUG) { print STDERR "$rpc_output"; }
		if ($rpc_output =~ /^\s*Directory Name/) {
			$printer_upload_dir = $rpc_output;
			last;
		}
	}

	$_ = $printer_upload_dir;

	if (!defined ($_) || !s/\s*Directory Name:\[([^\]]+)]\s*/$1/) {
		fatal_error("ERROR!  Unable to locate printer upload dir.\n"); 
	}

	s/\\([^\\]+)$//;
	$printer_upload_last = $1;
	$printer_upload_dir = $_;
	$printer_upload_dir =~ s/\\/\//g;

	print "[rpc]: Printer Driver Upload Directory = $_\\$printer_upload_last\n";

	##
	## Step #2 : upload the driver files to $printer_upload_dir
	##
	## e.g. prompt; cd "W32X86"; put hp4000_6.ppd; put pscrptui.dll; 
	##      put pscript.hlp; put pscript.dll

	## run the command
	$cmd_string = "prompt; cd $printer_upload_last";
	foreach $drv_file (@args) {
		$cmd_string .= "; put $drv_file";
	}
	$cmd = "$path_smbclient $printer_upload_dir -A $authfile -d 1 -c \"$cmd_string\"";
	if ($DEBUG) { print STDERR "$cmd\n"; }
	if (!open (RPC_IN, "$cmd|")) {
		fatal_error("Error: Couldn't run $cmd (upload driver files).\n");
	}

	$error = 0;
	$error_string = "";
	while ($rpc_output = <RPC_IN>) {
		if ($DEBUG) { print STDERR "$rpc_output"; }
		if ($rpc_output =~ /Connection to.* failed/ ) {
			$error = 1;
			$error_string = "ERROR! $rpc_output";
			last;
		}

		if ($rpc_output =~ /tree connect failed/) {
			$error = 1;
			$error_string =  "ERROR! smbclient failed to connect to [$printer_upload_dir]";
			$error_string .= "  Check access settings on share.\n";
			last;
		}

		if ($rpc_output =~ /ERRnosuchshare/ ) {
			$error = 1;
			$error_string = "ERROR! No access to [$printer_upload_dir]";
			last;
		}
		
		if ($rpc_output =~ /ERRnoaccess/ ) {
			$error = 1;
			$error_string = "ERROR! No access to upload files!";
			last;
		}
		
		if ($rpc_output =~ /^putting file/) {
			print "[rpc]: $rpc_output";
		}
	}
	close (RPC_IN);
	
	if ($error) {
		print "[rpc]: $error_string\n";
		chdir ($save_cwd);
		return 0;
	}

	##
	## Step #3 : need an AddPrinterDriver() RPC
	##
	## see the rpcclient(1) man page for more information on the 
	## format of this MS-RPC
	##
	## e.g. adddriver "Windows NT x86" \
	##      "HP LaserJet 4000 Series PS:PSCRIPT.DLL:HP4000_6.PPD:PSCRPTUI.DLL:\
	##       PSCRIPT.HLP:NULL:RAW:hp4000_6.ppd,pscrptui.dll,pscript.hlp,\
	##       pscript.dll"

	## run the client program
	$cmd_string = "adddriver \\\"$arch\\\" " .
		   "\\\"$printer_model_name:$driver_file_name:$data_file_name:" . 
		   "$config_file_name:$help_file_name:NULL:RAW:" . 
		   (join (',', @args)) . "\\\"";
	$cmd = "$path_rpcclient $servername -d 1 -A  $authfile -c \"$cmd_string\"";
	if ($DEBUG) { print STDERR "$cmd\n"; }
	if (!open (RPC_IN, "$cmd|")) {
		fatal_error("Error: Couldn't run $cmd (add printer driver).\n");
	}

	## parse the output
	$error = 0;
	$error_string = "";
	while ($rpc_output = <RPC_IN>) {

		if ($DEBUG) { print STDERR "$rpc_output"; }
		
		## catch errors heres
		
		## successful output
		if ($rpc_output =~ /successfully installed/) {
			print "[rpc]: $rpc_output";
			last;
		}
	}
	
	if ($error) {
		print "[rpc]: $error_string\n";
		chdir ($save_cwd);
		return 0;
	}

	##
	## Step #4 : invoke an AddPrinter() RPC
	##
	## e.g. addprinter "HP LaserJet 4000 Series PS" "" \
	##      "HP LaserJet 4000 Series PS" "Samba Printer Port"
	##
	## Possible errors include
	##   - invalid port name
	##   - bad share name (and unable to create new on on Samba server)

	## run the command
	$cmd_string = "addprinter " . 
		   "\\\"$rpc_printer_name\\\"" . " " .
		   "\\\"$rpc_share_samba_name\\\"" . " " .
		   "\\\"$printer_model_name\\\"" . " " .
		   "\\\"$rpc_port_name\\\"";
	$cmd = "$path_rpcclient $servername -d 1 -A  $authfile -c \"$cmd_string\"";
	if ($DEBUG) { print STDERR "$cmd\n"; }
	if (!open (RPC_IN, "$cmd|")) {
		fatal_error("Error: Couldn't run $cmd (add printer).\n");
	}

	## grab any output to print a message
	$error = 0;
	$error_string = "";
	while ($rpc_output = <RPC_IN>) {

		## enable the following line for debugging
		if ($DEBUG) { print STDERR "$rpc_output"; }
		if ($rpc_output =~ /Invalid port/) {
			$error = 1;
			$error_string = "ERROR! Invalid port ($rpc_port_name) specified in addprinter command";
			last;
		}
		if ($rpc_output =~ /NT_STATUS/) {
			chomp ($rpc_output);
			$error = 1;
			$error_string = "ERROR! Windows NT error code : [$rpc_output]";
		}
		if ($rpc_output =~ /successfully installed/) {
			print "[rpc]: $rpc_output";
			print "[rpc]: Installed arch: $arch\n";
			last;
		}
	}
	close (RPC_IN);

	if ($error) {
		print "[rpc]: $error_string\n";
		chdir ($save_cwd);
		return 0;
	}
	
	## return to the previous working directory
	chdir ($save_cwd);

	return 1;
}

########################################################################
# locate_client_programs
#
sub locate_client_programs
{
	## let's verify that smbclient and rpcclient are actually 
	## in the search path and can be found
	my @path = ();
	my @dirs = ();
	my $found = 0;
	
	## look for rpcclient
	push (@path, dirname($path_rpcclient));
	@dirs = split(/:/, $ENV{'PATH'});
	push (@path, @dirs);
	for (@path) {
		if (-f "$_/$path_rpcclient") {
			$found = 1;
			last;
		}
	}
	if (! $found ) {
		print "ERROR! Unable to locate rpcclient binary!\n";
		print "Please make sure the following is a valid path\n";
		print "(or is in your search path):\n";
		print "\t$path_rpcclient\n";
		return 0;
	}

	## look for smbclient
	$found = 0;
	@path = ();
	push (@path, dirname($path_smbclient));
	push (@path, @dirs);
	for (@path) {
		if (-f "$_/$path_smbclient") {
			$found = 1;
			last;
		}
	}	
	if (! $found ) {
		print "ERROR! Unable to locate smbclient binary!\n";
		print "Please make sure the following is a valid path\n";
		print "(or is in your search path):\n";
		print "\t$path_smbclient\n";
		return 0;
	}

	return 1;
}

########################################################################
# Params: 
#  $inf = Return value from read_inf() call, on the control file for a package.
sub install_package
{
	my ($inf) = @_;
	my $tmp_file_name = undef;
    	my $ret = 1;
	
	if (!locate_client_programs()) {
		return 0;
    	}
	
    	## now onto more important stuff....
	if (!inf_has_key($inf, "W32X86")) { 
		fatal_error ("ERROR!  Package doesn't include a \"Windows NT x86\" driver set.\n", 1); 
	}
    
	while ((!defined($option_authfile)) || (length($option_authfile)==0)) { 
		## generate a temp file name which can be used as the authfile
		## and write out the data
		do {
			$tmp_file_name = tmpnam();
		} while (!sysopen(TMP, $tmp_file_name, O_RDWR|O_CREAT|O_EXCL, 0600));

		$tmp_file_name = build_authfile();
		$option_authfile = $tmp_file_name;
	}

	for my $arch ("W32X86", "WIN40", "W32mips", "W32alpha", "W32ppc") {
		if (!inf_has_key($inf, $arch)) { 
			next; 
		}
	
		my $inf_arch = inf_get_key($inf, $arch);
		my $arch_file_name = inf_get_key($inf_arch, "inf_fname");

		my $inf_hack_arch = inf_get_key($inf, "W32X86");

		if ($arch_file_name) {
			my $tmp_inf = read_inf("$arch/$arch_file_name");
	    
			if (!defined($tmp_inf)) {
				warn "Warn: Bad arch INF file: $arch.\n";
				next;
			}
	
			my $model_name = inf_get_key($inf_arch, "model");

			if ($arch eq "WIN40") { 
				# '9x uses NT arch model name.
				$model_name = inf_get_key($inf_hack_arch, "model");
			}
			my %info = parse_inf_nt($tmp_inf, 
					inf_get_key($inf_arch, "manufacturer"), 
					inf_get_key($inf_arch, "model"),
					$arch);
	    
			if (!%info) {
				warn "Warn: Invalid arch INF file: $arch.\n";
				next;
			}
	    
			my @rpc_args = ();
			my $arg_string = undef;
			my $printer_model_name = inf_get_key($inf_arch, "model");

			push (@rpc_args, $option_authfile);

			push (@rpc_args, $arch);
			push (@rpc_args, $printer_model_name);

			print "[rpc]: Printer Driver Information : \n";
			print "[rpc]:     Printer Model   = $rpc_args[$#rpc_args]\n";
			print "[rpc]:     Environment     = $arch\n";

			push (@rpc_args, $info{"DriverFile"});
			push (@rpc_args, $info{"DataFile"});
			push (@rpc_args, $info{"ConfigFile"});
			push (@rpc_args, $info{"HelpFile"});
			push (@rpc_args, $info{"LanguageMonitor"});

			print "[rpc]:     Driver Filename = " . $info{'DriverFile'} . "\n";
			print "[rpc]:     Data Filename   = " . $info{'DataFile'} . "\n";
			print "[rpc]:     Config Filename = " . $info{'ConfigFile'} . "\n";
			print "[rpc]:     Help Filename   = " . $info{'HelpFile'} . "\n";

			# Install the files...
			for my $filehash (@{$info{CopyFiles}}) { 
				# $src is not to be used.
				my $dst = $filehash->{DstFilename};
				my $src = get_real_filename($dst, $arch);

				if (!defined($src)) {
					warn "Warn: File not found: '$dst' in $arch.\n";
					next;
				}
		
				$dst = lc($dst); # Because they are usually upper case.
						 # and $src contains the arch again.

				my $loc = "$base_dir/$arch/$dst";

				if (! -d dirname($loc) && !mkpath(dirname($loc), 0, 0755)) { 
					warn "Warn: mkpath: Couldn't create directories\n"; 
					next;
				} 

				if (!copy($src, $loc)) {
					warn "Warn: copy: $!\n"; 
					next;
				} 
		
				push(@rpc_args, $loc);
			}

			if (!rpc_client_wrapper(@rpc_args)) {
				## return an error
				return 0;
			}
    		}
	}
	
	if (defined($tmp_file_name)) {
		unlink ($tmp_file_name);
	}

	return 1;
}

########################################################################
# Clean up the temporary directory and its files.
#-----------------------------------------------------------------
sub cleanup 
{
    
	if ($option_cleanup == 1) {
		if (! chdir("/")) { 
			warn "Warn: Couldn't chdir to / $!\n"; 
			return;
		}

		status_msg ("Cleaning up temporary files...");
		rmtree($tmp_dir);

		## very important to remove this file if it exists!!
		#if (defined($option_authfile)) {
		#	unlink ($option_authfile);
		#}

	}
}


########################################################################
# handle_package
#
## 
sub handle_package {

	my ($pkg_name) = @_;
	my $res = undef;
	my $control_inf = undef;
	
	status_msg ("Exploding package...");

	explode_package($pkg_name);

	$control_inf = read_inf("./control");

	if (!defined($control_inf)) { 
		fatal_error ("Error: Bad control INF file.", 1); 
	}

	status_msg ("Installing package...");

	$res = install_package($control_inf);

	return ($res);
}
########################################################################
# In case of an unrecoverable error, print a message to STDERR,
# optionally perform cleanup, and exit.
#-----------------------------------------------------------------
sub fatal_error 
{
	my ($msg, $do_cleanup, $error_code) = @_;

	if (!defined ($error_code) || !$error_code) { 
		$error_code = $error_code_default; 
	}

	# FIXME: output twice (GUI would like to see the real error _first_ atm.
	STDERR->print ("$msg\n");

	if ($do_cleanup != 0) {
		cleanup ();
	}

	STDERR->print ("\n\n$msg\n");    

	exit ($error_code);
}

########################################################################
# If verbose mode is on, then print the status message to STDOUT.
#-----------------------------------------------------------------
sub status_msg 
{
	my ($msg) = @_;

	if ($option_verbose == 1) {
		print "$msg\n";
	}
}




########################################################################
#                       #### Main ####                                 #
########################################################################
my @saved_argv = @ARGV;

local($|) = 1;

# Don't let anyone mess with the files.
$tmp_dir = cd_temp();

# Getopt stuff...

# Getopt::Long::Configure ("bundling");

my $res = GetOptions('help|h' => \$option_help,
		     'base-url|b=s' => \$base_url,
		     'host=s' => 
		     sub 
		     {
		       if ($_[1] ne '')
			 {
			   $base_url = $real_base_url;
			   $base_url =~ 
			     s!^http://([^/]+)/(.+)$!http://$_[1]/$2!;
			 }
		       else
			 {
			   $base_url = '';
			 }
		     },
		     'servers=s' => \$option_servers_file,
		     'query!' => \$option_query,
		     'rpcquery!' => \$option_rpcquery,
		     'cleanup!' => \$option_cleanup,
		     'verbose' => \$option_verbose,
		     'local-pkg=s' => \$option_pkg_name,
		     'authfile=s' => \$option_authfile,
		     'cache-dir=s' => \$option_cache_dir,
		     'language=s' => \$option_language,
		     'pubkey-install!' => \$option_pubkey_install,
		     'version|v' => \$option_version,
		     'debug|d' => \$DEBUG);

# Allow the query option to take no parameter,
# this will match all printers.
if (($option_query || $option_rpcquery) && (scalar (@ARGV) != 1)) {
	$ARGV[0] = "";
}

if (!$option_help && (scalar (@ARGV) != 1)) { 
	$res = 0; 
}

if (!$res || $option_help) {
	STDERR->print(" Format: $0 [options] <name>\n");
	STDERR->print(" --help -h             - Print this message.\n");
	STDERR->print(" --debug -d            - log debug information to stderr.\n");
	STDERR->print(" --version -v          - Print the version.\n");
	STDERR->print(" --base-url            - Change the url to query requests from.\n");
	STDERR->print(" --cache-dir=<dir>     - Cache driver file packages in <dir>\n");
	STDERR->print(" --local-pkg=<file>    - Install a locally archived driver pkg\n");
	STDERR->print(" --host                - Change the host in the request url.\n");
	STDERR->print(" --query               - Do a query for fully qualified printer names.\n");
	STDERR->print(" --rpcquery            - Do an rpc query for samba details.\n");
	STDERR->print(" --cleanup             - Should we cleanup the tmp directory.\n");
	STDERR->print(" --authfile            - File which contains authorization information.\n");
	STDERR->print(" --pubkey-install      - Always install new public keys for the package.\n");
	STDERR->print(" --verbose             - Print progress messages during each program step.\n");
	STDERR->print("\n");

	cleanup ();
	exit (!$res);
}

if ($option_version) {
	print "$0: Version 1.0.0\n"; 
	cleanup ();
	exit (0);
}

if (!defined ($ENV{'TMPDIR'})) { 
	$ENV{'TMPDIR'} = "/tmp/"; 
}

if ($option_pkg_name) {
	$res = handle_package("$option_cache_dir/$option_pkg_name");
	cleanup();
	exit ($res);
}

# #### MAIN server list code ####
if (!defined ($base_url) && defined($option_servers_file)) { 

	# So it works recursivly.
	if (!open (SERV_IN, "< $option_servers_file")) {
		fatal_error(" Couldn't open server list: $!\n", 1);
	}

	my @lines = <SERV_IN>;
	chomp(@lines);

	my $tbl = RDB_parse_array(@lines);

	close(SERV_IN);

	my $first = 1;
	while (scalar(@{$tbl->{data}->{"host"}})) {
		if (!$first) { 
			status_msg ("Auto restarting with the next server..."); 
		}

		$first = 0;

		my @xtra_args = ();

		if ($tbl->{data}->{"language"}[0] ne '') { 
			push(@xtra_args, "--language");
			push(@xtra_args, $tbl->{data}->{"language"}[0]);
		}
		
		# Might still be undef.
		elsif (defined($option_language)) {
			push(@xtra_args, "--language");
			if (!defined($option_language)) { 
				$option_language = ''; 
			}
			push(@xtra_args, $option_language);
		}
	
		if ($tbl->{data}->{"url"}[0] ne '') {
			push(@xtra_args, "--base-url");
			push(@xtra_args, $tbl->{data}->{"url"}[0]);
		}
		else {
			push(@xtra_args, "--host");
			push(@xtra_args, $tbl->{data}->{"host"}[0]);
		}
 
		$res = system($path_self, @saved_argv, @xtra_args);
		$res /= 256;
	
		if (($res != $error_code_query_6) && ($res != $error_code_request)) { 
			last; 
		}

		RDB_splice_data($tbl, 0, 1);
	}
    

	cleanup();
	exit ($res);
}

if (!defined ($base_url) || ($base_url eq '')) { 
	$base_url = $real_base_url; 
}

# #### MAIN rpcquery code ####
if ($option_rpcquery) {
	my $tmp_file_name = undef;
	my $server = undef;
	my @args = ();
	my @rpc_output = ();

	if (!locate_client_programs()) {
		exit 0;
    	}

	if (!defined ($option_authfile)) {
		## generate a temp file name which can be used as the authfile
		## and write out the data
		do {
			$tmp_file_name = tmpnam();
		} while (!sysopen(TMP, $tmp_file_name, O_RDWR|O_CREAT|O_EXCL, 0600));
		
		$server = setup_credentials($tmp_file_name);
		$option_authfile = $tmp_file_name;
	}
	else {
		@args = read_authfile($option_authfile);
		$server = $args[3];
	}

	if (validate_credentials($server, $option_authfile)) {
		@rpc_output = rpc_query_wrapper($option_authfile, $server);
		for (@rpc_output) {
			print "$_\n";
		}
		
	}
	else {
		print STDERR "Invalid credentials!\n";
	}
	
	if (defined($tmp_file_name)) {
		unlink($tmp_file_name);
	}
	cleanup();
	exit 0;
}

# #### MAIN query and install code ####

# http-ify the parameter.
$ARGV[0] =~ s/([^a-zA-Z0-9])/"%" . sprintf("%02x", ord($1))/eg;

if ($option_query) {
	@params = @query_params;
	if ($#ARGV >= 0) {
		push (@params, "printer-name=" . $ARGV[0]);
	}
}
else {
	@params = @download_params;
	push (@params, "printer-name=" . $ARGV[0]);
}

if (defined ($option_language) && ($option_language ne '')) {
	# http-ify the parameter.
	$option_language =~ s/([^a-zA-Z0-9])/"%" . sprintf("%x", ord($1))/eg;
	push (@params, "lang=" . $option_language);
}

# Do downloads...

my $ua = LWP::UserAgent->new();

$ua->agent("Imprints-Client/1.0 " . $ua->agent);
$ua->env_proxy();

status_msg ("Sending request to server $base_url...");

my $request_data = send_request($base_url, \@params, $ua);

status_msg ("Parsing RDB table...");

my $tbl = RDB_parse_string($request_data);

if (!defined ($tbl)) { 
	fatal_error ("Error: Failed to parse information from retrieval server.", 1); 
}

# Check for error_code in returned table.
if (exists ($tbl->{data}->{"error_code"})) {
	my $error_code = $tbl->{data}->{"error_code"}[0];
	my $error_desc = $tbl->{data}->{"error_desc"}[0];
	my $err = $error_code_default;

	# Not found error code...	
	if ($error_code == 6) { 
		$err = $error_code_query_6; 
	}

	fatal_error ("Error: Query failed with return code=$error_code. $error_desc.", 1, $err);
}

if ($option_query) { 
	output_query_results($tbl);
	cleanup ();
	exit (0);    
}

status_msg ("Downloading package...");
my $pkg_name = download_package($ua, $tbl);


# Deal with the package...
status_msg ("Verifying package using gpg...");

verify_package($pkg_name, 
	       $tbl->{data}->{"gpg_version"}[0], 
	       $tbl->{data}->{"gpg_sig"}[0],
	       $tbl->{data}->{"gpg_crc"}[0]);

$res = handle_package ($pkg_name);

# Cleanup temporary storage...
cleanup ();

if ($res) { 
	print("Installation completed successfully.\n");  
}
else { 
	print("Installation experienced problems.\n");  
}

exit ($res);

