#! /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 = ; 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 "") { $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 = ; 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 = ; 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 = ; chomp ($servername); print "Username: "; $username = ; chomp ($username); print "Password: "; $password = ; 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 to default to the printer model name): "; $rpc_printer_name = ; chomp($rpc_printer_name); $rpc_printer_name =~ s/\s+//g; if ( length ($rpc_printer_name) == 0 ) { $rpc_printer_name = ""; } ## 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 = ; 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 = ; 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 () { 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 () { 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 = ) { 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 = ) { 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 = ) { 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 = ) { ## 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] \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= - Cache driver file packages in \n"); STDERR->print(" --local-pkg= - 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 = ; 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);