#! /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);