#!@PERL@ -w use strict; # -*- perl -*- # This is foomatic-configure, a program to establish and configure # print queues, drivers, spoolers, etc using the foomatic database and # companion filters. # It also comprises half of a programmatic API for user tools: you can # learn and control everything about the static properties of print # queues here. With the sister program foomatic-printjob, you can do # everything related to print queue dynamic state: submit jobs, and # query, cancel, reorder, and redirect them. use Foomatic::Defaults; use Foomatic::DB; use Data::Dumper; # Connect syntax: # # This differs a tad from CUPS's, partly because everything is # supposed to be a file, and CUPS doesn't entirely reflect that. # But I'm not really very particular... # # If a certain URI is not supported by all the spoolers, the spoolers # which support it are listed in parantheses, "direct" means direct, # spooler-less printing. # # usb:/path/device # Local USB printer # usb://make/model?serial=xxx # Printer-bound USB connection (CUPS) # parallel:/path/device # Local parallel printer # serial:/path/device # Local serial printer # file:/path/file # includes usb, lp, named pipes, other # hp:/bus/model?serial=xxx # HPLIP print queue (hpinkjet.sf.net) # hpfax:/bus/model?serial=xxx # HPLIP fax queue (hpinkjet.sf.net) # ptal:/provider:bus:name # HPOJ MLC protocol (hpoj.sf.net,obsolete) # mtink:/path/device # Epson inkjet through mtink daemon # # (for ink level monitoring when printing, # # http://xwtools.automatix.de/) # lpd://host/queue # LPD protocol # lpd://host # LPD protocol (default queue, CUPS only) # socket://host:port # TCP aka appsocket # socket://host # TCP aka appsocket (port 9100) # ncp://user:pass@host/queue # Netware (LPD, LPRng, direct) # smb://user:pass@wgrp/host/queue # Windows (CUPS, PPR, LPD, LPRng, direct) # stdout # Standard output (direct) # postpipe:"" # Free-formed backend command line # # (LPD, LPRng, direct) # # Read out the program name with which we were called, but discard the path $0 =~ m!/([^/]+)\s*$!; my $progname = ($1 || $0); my $debug = 0; # We use the library Getopt::Long here, so that we can have more than # one "-o" option on one command line. my($opt_q, $opt_f, $opt_w, $opt_n, $opt_N, $opt_L, $opt_ppd, $opt_d, $opt_p, $opt_s, $opt_C, $opt_R, $opt_D, $opt_Q, $opt_P, $opt_O, $opt_X, $opt_c, @opt_o, $opt_r, $opt_dd, $opt_nodd, $opt_att, $opt_delay, $opt_h); use Getopt::Long; Getopt::Long::Configure("no_ignore_case"); GetOptions("q" => \$opt_q, # Quiet, non-interactive operation "f" => \$opt_f, # Force rebuild of PPD from database "w" => \$opt_w, # Cut GUI strings in the PPD to 39 # characters (for CUPS Windows driver) "n=s" => \$opt_n, # queue Name "N=s" => \$opt_N, # human-readable Name (Model, # Description) "L=s" => \$opt_L, # Location "ppd=s" => \$opt_ppd, # PPD file "d=s" => \$opt_d, # Driver "p=s" => \$opt_p, # Printer "s=s" => \$opt_s, # Spooler "C" => \$opt_C, # Copy queue "R" => \$opt_R, # Remove queue "D" => \$opt_D, # set Default queue "Q" => \$opt_Q, # Query queue info "P" => \$opt_P, # Perl queue/printer/driver info output "O" => \$opt_O, # get printer support Overview "X" => \$opt_X, # query XML printer/driver/combo info "c=s" => \$opt_c, # printer Connection type "o=s" => \@opt_o, # default printing Options "r" => \$opt_r, # list Remote queues "backend-dont-disable=s" => \$opt_dd, # Do not disable CUPS # backends "backend-attempts=s" => \$opt_att, # Try that often when backend # fails "backend-delay=s" => \$opt_delay, # Delay in seconds between # retries of failed backend "h" => \$opt_h, # Help! "help"=> \$opt_h) || help(); help() if $opt_h; my $db = new Foomatic::DB; overview() if $opt_O; get_xml() if $opt_X; my $force = ($opt_f ? 1 : 0); my $shortgui = ($opt_w ? 1 : 0); my $in_config = {'queue' => $opt_n, 'desc' => $opt_N, 'loc' => $opt_L, 'ppdfile' => $opt_ppd, 'driver' => $opt_d, 'printer' => $opt_p, 'spooler' => $opt_s, 'connect' => $opt_c, 'options' => \@opt_o, 'force' => $force, 'shortgui' => $shortgui, 'dd' => $opt_dd, 'att' => $opt_att, 'delay' => $opt_delay, 'foomatic' => 1}; # If description and location contain only whitespace, use an empty string # instead if ((defined($in_config->{'desc'})) && ($in_config->{'desc'} =~ m!^\s*$!)) { $in_config->{'desc'} = ""; } if ((defined($in_config->{'loc'})) && ($in_config->{'loc'} =~ m!^\s*$!)) { $in_config->{'loc'} = ""; } my $action = ($opt_R ? 'delete' : 'configure'); $action = ($opt_D ? 'default' : $action); $action = ($opt_Q ? 'query' : $action); $action = ($opt_P ? 'query' : $action); my $procs = { 'lpd' => { 'delete' => \&delete_lpd, 'configure' => \&setup_lpd, 'default' => \&default_lpd, 'query' => \&query_lpd }, 'lprng'=>{ 'delete' => \&delete_lpd, 'query' => \&query_lpd, 'default' => \&default_lprng, 'configure' => \&setup_lpd }, 'cups' =>{ 'delete' => \&delete_cups, 'query' => \&query_cups, 'default' => \&default_cups, 'configure' => \&setup_cups }, 'pdq' =>{ 'delete' => \&delete_pdq, 'query' => \&query_pdq, 'default' => \&default_pdq, 'configure' => \&setup_pdq }, 'ppr' =>{ 'delete' => \&delete_ppr, 'query' => \&query_ppr, 'default' => \&default_ppr, 'configure' => \&setup_ppr }, 'direct'=>{'delete' => \&delete_direct, 'query' => \&query_direct, 'default' => \&default_direct, 'configure' => \&setup_direct } }; if (!($opt_Q or $opt_P or defined($in_config->{'queue'}))) { # No queue manipulation without knowing the name of the queue print STDERR "You must specify a queue name with -n!\n"; help(); exit 1; } if (!defined($in_config->{'spooler'})) { my $takenfromconfigfile = 0; # Personal default spooler my $s; if (($> != 0) && (-f "$ENV{'HOME'}/.defaultspooler")) { $s = `cat $ENV{'HOME'}/.defaultspooler`; chomp $s; $takenfromconfigfile = 1; } # System default spooler if ((!defined($s)) && (-f "$sysdeps->{'foo-etc'}/defaultspooler")) { $s = `cat $sysdeps->{'foo-etc'}/defaultspooler`; chomp $s; $takenfromconfigfile = 1; } if (!defined($s)) { $s = detect_spooler(); } die "Unable to identify spooler, please specify with -s\n" unless $s; if ((!$opt_q) && (!$takenfromconfigfile)) { print STDERR "You appear to be using $s. Correct? "; my $yn = ; die "\n" if ($yn !~ m!^y!i); } $in_config->{'spooler'} = $s; } if ($in_config->{'printer'}) { # If the user supplies an old numerical printer ID, translate it to # a new clear-text ID $in_config->{'printer'} = Foomatic::DB::translate_printer_id($in_config->{'printer'}); } # Call proper proc &{$procs->{$in_config->{'spooler'}}{$action}}($in_config); exit(0); # Common parts for queue creation/modification functions sub getoldqueuedata { my ($config, $reconf) = @_; my ($sourcespooler, $sourcequeue, $olddatablob, $beh); # Copy a queue if ($opt_C) { if ($#ARGV == 0) { # 1 argument -> queue from same spooler $sourcespooler = $config->{'spooler'}; $sourcequeue = $ARGV[0]; } elsif ($#ARGV == 1) { # 2 arguments -> queue from given spooler $sourcespooler = $ARGV[0]; $sourcequeue = $ARGV[1]; } else { die "Unsufficient options to copy a queue, " . "try \"$progname -h\"!\n"; } # Read data from source queue if (!($olddatablob = load_datablob($sourcespooler, $sourcequeue))) { # It is not possible to copy the given source queue die "The source queue $sourcequeue does not exist " . "or is corrupted!\n"; } # PPD file of the source queue, if it exists, and if the user # does not insist on using another PPD file, we must copy it my $sourceppd = $olddatablob->{'ppdfile'}; if ((-r $sourceppd) && (!$config->{'ppdfile'})) { $config->{'ppdfile'} = $sourceppd; } # Stuff data into the $config structure, all items must be defined, # so that an old queue gets overwritten if ($olddatablob->{'queuedata'}) { my $i; for $i (('desc', 'loc', 'printer', 'driver', 'connect', 'ppdfile', 'dd', 'att', 'delay')) { if (!defined($config->{$i})) { if ($olddatablob->{'queuedata'}{$i}){ $config->{$i} = $olddatablob->{'queuedata'}{$i}; } elsif ($i eq 'dd') { $config->{$i} = 0; } elsif ($i eq 'att') { $config->{$i} = 1; } elsif ($i eq 'delay') { $config->{$i} = 30; } else { $config->{$i} = ""; } } } # Check consistency of the printer/driver settings if ((($config->{'driver'} eq "") || ($config->{'driver'} eq "raw") || # No new driver, printer, ($config->{'printer'} eq "")) && # PPD file ($config->{'ppdfile'} eq "") && ((!defined($olddatablob->{'args'})) || # No existing options ($#{$olddatablob->{'args'}} < 0))) { # -> source queue raw $config->{'driver'} = "raw"; $config->{'printer'} = undef; } # We do not need the queue data block any more delete($olddatablob->{'queuedata'}); } else { # No Foomatic/PPD data $olddatablob = undef; } } else { # Load the datablob of the former configuration if ($reconf) { if ($olddatablob = load_datablob($config->{'spooler'}, $config->{'queue'})) { # If the user has supplied only a printer or only a driver # fill in the second of the two fields in $config if ((!$config->{'ppdfile'}) && ($olddatablob->{'queuedata'}{'foomatic'})) { if ((!$config->{'driver'}) && ($config->{'printer'})) { $config->{'driver'} = $olddatablob->{'driver'}; } if ((!$config->{'printer'}) && ($config->{'driver'})) { $config->{'printer'} = $olddatablob->{'id'}; } } # Extract URI and backend error handling data if ($config->{'spooler'} eq "cups") { $beh->{'uri'} = $olddatablob->{'queuedata'}{'connect'}; $beh->{'dd'} = $olddatablob->{'queuedata'}{'dd'}; $beh->{'att'} = $olddatablob->{'queuedata'}{'att'}; $beh->{'delay'} = $olddatablob->{'queuedata'}{'delay'}; } # We do not need the queue data block here delete($olddatablob->{'queuedata'}); } else { $olddatablob = undef; } } } # If the user does not supply info about his printer and/or driver # and the queue did not exist before we assume that he wants to set up a # raw queue. To make a raw queue out of a formerly filtered one, one # has to use the driver name "raw". $config->{'driver'} = "" if not defined $config->{'driver'}; $config->{'printer'} = "" if not defined $config->{'printer'}; $config->{'ppdfile'} = "" if not defined $config->{'ppdfile'}; my $nodriver = (((!$config->{'driver'}) && (!$config->{'printer'}) && (!$config->{'ppdfile'})) || ($config->{'driver'} eq "raw")); # Set to 1 when we retrieve a data set from the Foomatic database my $newfoomaticdata = 0; if ($nodriver) { if ($olddatablob) { if ($config->{'driver'} ne "raw") { # We couldn't determine a certain driver, probably we had a # native PostScript PPD file $db->{'dat'} = $olddatablob; } else { # For a raw queue overtake at least the $postpipe if (defined($olddatablob->{'postpipe'})) { $db->{'dat'}{'postpipe'} = $olddatablob->{'postpipe'}; } } } } elsif ($config->{'ppdfile'}) { if (! -r $config->{'ppdfile'}) { die "The PPD file \'$config->{'ppdfile'}\' does not exist or is " . "readable.\n"; } # Load the data from the PPD file $db->getdatfromppd($config->{'ppdfile'}); # Overtake the former default settings if ($olddatablob) {overtake_defaults($olddatablob)}; # Overtake the former $postpipe if (defined($olddatablob->{'postpipe'})) { $db->{'dat'}{'postpipe'} = $olddatablob->{'postpipe'}; } } else { if (($olddatablob) && ($olddatablob->{'driver'} eq $config->{'driver'}) && ($olddatablob->{'id'} eq $config->{'printer'}) && (!$config->{'force'})) { # Overtake data from the former configuration $db->{'dat'} = $olddatablob; } else { # Retrieve data from the Foomatic database if (!$config->{'driver'}) { die "You also need to specify a driver with \"-d\"!\n"; } if (!$config->{'printer'}) { die "You also need to specify a printer with \"-p\"!\n"; } # The printer is supported by the chosen driver? If yes, load # its data my $possible = $db->getdat($config->{'driver'}, $config->{'printer'}); die "That printer and driver combination is not possible.\n" if (!$possible); die "There is neither a custom PPD file nor the driver database entry contains sufficient data to build a PPD file.\n" if (!$db->{'dat'}{'cmd'}) && (!$db->{'dat'}{'ppdfile'}); $newfoomaticdata = 1; # Overtake the former default settings if ($olddatablob) {overtake_defaults($olddatablob)}; # Overtake the former $postpipe if (defined($olddatablob->{'postpipe'})) { $db->{'dat'}{'postpipe'} = $olddatablob->{'postpipe'}; } } } # When we have no arguments in the current configuration, we must have # a raw queue my $rawqueue = ((!defined($db->{'dat'}{'args'})) || ($#{$db->{'dat'}{'args'}} < 0)); # Set the default printing options supplied on the command line if (!$rawqueue) { set_default_options($config, $db->{'dat'}); } # Printer model name (for comment field of the queue configuration) my ($make, $model, $makemodel); if (defined($db->{'dat'})) { $make = $db->{'dat'}{'make'}; $model = $db->{'dat'}{'model'}; $makemodel = $db->{'dat'}{'makemodel'}; if (($make) && ($model)) { $makemodel = "$make $model"; } } return ($rawqueue, $newfoomaticdata, $makemodel, ($config->{'spooler'} eq "cups" ? $beh : ())); } #fix to work on Ubuntu, where cupd runs not as root, but as cupsys user #like system ("chown cupsys $ppdfile"), but #changeowner function changes owner only if user exists on system sub changeowner { my ($username, $file) = @_; my ($uid,$gid) = (-1, -1); my $l; $l = getpwnam($username); $uid = $l if defined($l); $l = getgrnam($username); $gid = $l if defined($l); chown $uid, $gid, $file; } sub writeppdfile { my ($config, $ppdfile, $rawqueue, $newfoomaticdata) = @_; # Save old $ppdfile, if any system("cp -f \'$ppdfile\' \'$ppdfile.old\'") if (-f $ppdfile); if ($rawqueue) { # Raw queue with $postpipe, use a "PPD" only containing the # $postpipe (LPRng, LPD, and no spooler only) if (((defined $db->{'dat'}{'postpipe'} && $db->{'dat'}{'postpipe'} ne "") && (($config->{'spooler'} eq 'lprng') || ($config->{'spooler'} eq 'lpd'))) || ($config->{'spooler'} eq 'direct')) { open PPDFILE, "> $ppdfile" or die "Cannot write \'$ppdfile\'!\n"; print PPDFILE "*PPD-Adobe: \"4.3\"\n*%\n"; print PPDFILE "*% This is a raw (driverless/unfiltered) " . "queue, this PPD file only carries\n" . "*% the postpipe.\n*%\n"; close PPDFILE; $db->ppdsetdefaults($ppdfile); chmod 0644, $ppdfile; #fix to work on Ubuntu, where cupd runs not as root, but as cupsys user #system ("chown cupsys $ppdfile"); #changeowner function changes owner only if user exists on system changeowner("cupsys", $ppdfile); } else { if (-f $ppdfile) { unlink "$ppdfile" or die "Cannot delete \'$ppdfile\'!\n"; } } } else { if ($config->{'ppdfile'}) { # Copy in the PPD file specified on the command line if ($config->{'ppdfile'} !~ /\.gz$/i) { # Uncompressed PPD file system("cp -f \'$config->{'ppdfile'}\' \'$ppdfile\'") and die "Cannot copy \'$config->{'ppdfile'}\' to \'$ppdfile\'!\n"; } else { # Compressed PPD file system("$sysdeps->{'gzip'} -dc " . "\'$config->{'ppdfile'}\' > " . "\'$ppdfile\'") and die "Cannot copy \'$config->{'ppdfile'}\' to \'$ppdfile\'!\n"; } # Set default option settings and $postpipe $db->ppdsetdefaults($ppdfile); } elsif ($newfoomaticdata) { # Generate the PPD file from the Foomatic database open PPDFILE, "> $ppdfile" or die "Cannot write \'$ppdfile\'!\n"; print PPDFILE $db->getppd($config->{'shortgui'}); close PPDFILE; } else { # Keep the previous PPD file, only set the options and the # $postpipe $db->ppdsetdefaults($ppdfile); } # Correct the permissions of the PPD file chmod 0644, $ppdfile; #fix to work on Ubuntu, where cupd runs not as root, but as cupsys user #system ("chown cupsys $ppdfile"); #changeowner function changes owner only if user exists on system changeowner("cupsys", $ppdfile); } } ### Queue manipulation functions for both LPD and LPRng sub setup_lpd { my ($config) = $_[0]; # Read the previous /etc/printcap my $pcap = load_lpd_printcap(); my ($ppdfile, $entry, $reconf, $p); for $p (@{$pcap}) { if ($p->{'names'}[0] eq $config->{'queue'}) { $entry = $p; $reconf = 1; print "Reconfigure of ", Dumper($p) if $debug; last; } } # PPD file name $ppdfile = sprintf('%s/lpd/%s.ppd', $sysdeps->{'foo-etc'}, $config->{'queue'}) if !$ppdfile; # Get the data from the former queue if we reconfigure or copy a queue # do also some checking of the user-supplied parameters my ($rawqueue, $newfoomaticdata, $makemodel) = getoldqueuedata($config, $reconf); # Set the printer queue name line in /etc/printcap if (!$reconf) { if (!$rawqueue) { $entry->{'names'}[0] = $config->{'queue'}; $entry->{'names'}[1] = $config->{'desc'}; $entry->{'names'}[2] = "$makemodel"; $entry->{'names'}[3] = $config->{'loc'}; } else { $entry->{'names'}[0] = $config->{'queue'}; $entry->{'names'}[1] = $config->{'desc'}; $entry->{'names'}[2] = "Raw queue"; $entry->{'names'}[3] = $config->{'loc'}; } } else { if (!$rawqueue) { $entry->{'names'}[2] = "$makemodel"; } else { if (($entry->{'names'}[2] eq "Raw queue") || ($config->{'driver'} eq "raw")) { $rawqueue = 1; $entry->{'names'}[2] = "Raw queue"; } } if (defined($config->{'desc'})) { $entry->{'names'}[1] = $config->{'desc'}; } if (defined($config->{'loc'})) { $entry->{'names'}[3] = $config->{'loc'}; } } # These lines are always in /etc/printcap $entry->{'str'}{'sd'} = sprintf('%s/%s', $sysdeps->{'lpd-dir'}, $config->{'queue'}); $entry->{'str'}{'lf'} = $sysdeps->{'lpd-log'}; $entry->{'num'}{'mx'} = '0'; $entry->{'bool'}{'sh'} = 1; # Lines depending on the printer/spooler if (!$rawqueue) { if ($config->{'spooler'} eq "lpd") { $entry->{'str'}{'ppdfile'} = $ppdfile; # For the GPR printing GUI delete $entry->{'str'}{'ppd'}; $entry->{'str'}{'if'} = $sysdeps->{'foomatic-rip'}; $entry->{'str'}{'af'} = $ppdfile; delete $entry->{'bool'}{'force_localhost'}; delete $entry->{'str'}{'filter_options'}; } elsif ($config->{'spooler'} eq "lprng") { $entry->{'str'}{'ppd'} = $ppdfile; # for LPRng PPD support $entry->{'str'}{'if'} = $sysdeps->{'foomatic-rip'}; $entry->{'bool'}{'force_localhost'} = 1; delete $entry->{'str'}{'ppdfile'}; delete $entry->{'str'}{'af'}; delete $entry->{'str'}{'filter_options'}; } else { die "The spooler $config->{'spooler'} is not supported " . "by this function!\n"; } } else { delete $entry->{'str'}{'if'}; delete $entry->{'str'}{'af'}; delete $entry->{'str'}{'filter_options'}; delete $entry->{'str'}{'ppd'}; if ($config->{'spooler'} eq "lpd") { delete $entry->{'bool'}{'force_localhost'}; } elsif ($config->{'spooler'} eq "lprng") { $entry->{'bool'}{'force_localhost'} = 1; } else { die "The spooler $config->{'spooler'} is not supported " . "by this function!\n"; } } # If printing job has to be passed through a special program, put the # command line into $postpipe (for example for Socket, Samba, ...) my $postpipe = ""; if ((!$reconf) or ($config->{'connect'})) { # Set up connection type # Remove "rm" and "rp" tags to avoid problems when overwriting a # raw queue delete $entry->{'str'}{'rm'}; delete $entry->{'str'}{'rp'}; # All URIs ("-c" option) have the same syntax as URIs in CUPS ("-v" # option of "lpadmin"). my $file; if ($config->{'connect'} =~ m!^(file|usb|parallel|serial):(.*)!) { # Local printer or printing to a file $file = $2; if ($config->{'connect'} =~ m!^usb://!) { # Queue with printer-bound USB URI transferred from CUPS, # as LPD/LPRng does not support these URIs, translate it # back to a standard USB device URI $file = cups_usb_printer_uri_to_device_uri($file); } if (! -e $file) { warn "The device or file $file doesn't exist? " . "Working anyway.\n"; } if (($file =~ m!^$sysdeps->{'ptal-pipes'}/(.+)$!) || ($file =~ m!^/dev/ptal-printd/(.+)$!) || ($file =~ m!^/var/run/ptal-printd/(.+)$!)) { # Translate URI for ptal-printd to postpipe using the # "ptal-connect" command my $devname = $1; $devname =~ s/_/:/; $devname =~ s/_/:/; $postpipe = "$sysdeps->{'ptal-connect'} $devname -print"; $entry->{'str'}{'lp'} = "/dev/null"; } else { $entry->{'str'}{'lp'} = $file; } } elsif ($config->{'connect'} =~ m!^ptal://?([^/].*)$!) { # HPOJ MLC protocol my $devname = $1; $postpipe = "$sysdeps->{'ptal-connect'} $devname -print"; $entry->{'str'}{'lp'} = "/dev/null"; } elsif ($config->{'connect'} =~ m!^mtink:/(.+)$!) { # Printing through "mtinkd" $entry->{'str'}{'lp'} = "$sysdeps->{'mtink-pipes'}/$1"; } elsif ($config->{'connect'} =~ m!^lpd://([^/]+)/([^/]+)$!) { # Remote LPD my $remhost = $1; my $remqueue = $2; if (($rawqueue) && ($config->{'spooler'} eq "lpd")) { $entry->{'str'}{'rm'} = $remhost; $entry->{'str'}{'rp'} = $remqueue; delete $entry->{'str'}{'lp'}; } elsif( ($config->{'spooler'} eq "lprng")) { delete $entry->{'str'}{'rm'}; delete $entry->{'str'}{'rp'}; $entry->{'str'}{'lp'} = "$remqueue\@$remhost"; } else { # classic LPD does not support sending jobs to a server with the # "rm" and "rp" tags in /etc/printcap and filtering it # before ("if" tag). So when we do not set up a raw queue, # we do not # # $entry->{'str'}{'rm'} = $remhost; # $entry->{'str'}{'rp'} = $remqueue; # # but use "rlpr" in a $postpipe. Note that "rlpr" prints a # banner page by default, "-h" suppresses it. "rlpr" must # be SUID "root". $postpipe = "$sysdeps->{'rlpr'} -q -h -P " . "$remqueue\@$remhost"; $entry->{'str'}{'lp'} = "/dev/null"; } } elsif ($config->{'connect'} =~ m!^socket://([^/:]+):([0-9]+)/?$!) { # Socket (AppSocket/HP JetDirect) my $remhost = $1; my $remport = $2; if( ($config->{'spooler'} eq "lprng")) { $entry->{'str'}{'lp'} = "$remhost\%$remport"; } else { $postpipe = "$sysdeps->{'nc'} -w 1 $remhost $remport"; $entry->{'str'}{'lp'} = "/dev/null"; } } elsif ($config->{'connect'} =~ m!^smb://(.*)$!) { # SMB (Printer on Windows server) my $parameters = $1; # Get the user's login and password from the URI my $smbuser = ""; my $smbpassword = ""; if ($parameters =~ m!([^@]*)@([^@]+)!) { my $login = $1; $parameters = $2; if ($login =~ m!([^:]*):([^:]*)!) { $smbuser = $1; $smbpassword = $2; } else { $smbuser = $login; $smbpassword = ""; } } else { $smbuser = "GUEST"; $smbpassword = ""; } # Get the workgroup, server, and share name my $workgroup = ""; my $smbserver = ""; my $smbshare = ""; if ($parameters =~ m!([^/]*)/([^/]+)/([^/]+)$!) { $workgroup = $1; $smbserver = $2; $smbshare = $3; } elsif ($parameters =~ m!([^/]+)/([^/]+)$!) { $workgroup = ""; $smbserver = $1; $smbshare = $2; } else { die "The \"smb://\" URI must at least contain the " . "server name and the share name!\n"; } # Set up the command line for printing on the SMB server $postpipe = "$sysdeps->{'smbclient'} '//$smbserver/$smbshare'"; if ($smbpassword ne "") { warn("WARNING: smbclient password is visible in PPD file\n"); $postpipe .= " '$smbpassword'"; } if ($smbuser ne "") {$postpipe .= " -U '$smbuser'";} if ($workgroup ne "") {$postpipe .= " -W '$workgroup'";} $postpipe .= " -N -P -c 'print -' "; $entry->{'str'}{'lp'} = "/dev/null"; } elsif ($config->{'connect'} =~ m!^ncp://(.*)$!) { my $parameters = $1; # Get the user's login and password from the URI my $ncpuser = ""; my $ncppassword = ""; if ($parameters =~ m!([^@]*)@([^@]+)!) { my $login = $1; $parameters = $2; if ($login =~ m!([^:]*):([^:]*)!) { $ncpuser = $1; $ncppassword = $2; } else { $ncpuser = $login; $ncppassword = ""; } } else { $ncpuser = ""; $ncppassword = ""; } # Get the server and share name my $ncpserver = ""; my $ncpqueue = ""; if ($parameters =~ m!([^/]+)/([^/]+)$!) { $ncpserver = $1; $ncpqueue = $2; } else { die "The \"ncp://\" URI must at least contain the " . "server name and the queue name!\n"; } # Set up the command line for printing on the Netware server $postpipe = "$sysdeps->{'nprint'} -S $ncpserver"; if ($ncpuser ne "") { $postpipe .= " -U $ncpuser"; if ($ncppassword ne "") { warn("WARNING: ncp password is visible in PPD file\n"); $postpipe .= " -P $ncppassword"; } else { $postpipe .= " -n"; } } $postpipe .= " -q $ncpqueue -N - 2>/dev/null"; $entry->{'str'}{'lp'} = "/dev/null"; } elsif ($config->{'connect'} =~ m!^postpipe:(.*)$!) { # Pipe output into a command $postpipe = $1; $entry->{'str'}{'lp'} = "/dev/null"; } elsif ($config->{'connect'}) { $entry->{'str'}{'lp'} = '/dev/null'; die ("The URI \"$config->{'connect'}\" is not supported " . "for LPD/LPRng or you have\nmistyped.\n"); } else { print STDERR "You must specify a connection with -c.\n"; help(); exit(1); } # Put $postpipe into the data structure, so that it will be # inserted into the PPD file if ($postpipe ne "") { $postpipe = "| $postpipe"; $db->{'dat'}{'postpipe'} = $postpipe; } else { undef $db->{'dat'}{'postpipe'}; } } else { # Keep previous connection type # Use previous $postpipe if (defined($db->{'dat'}{'postpipe'})) { $postpipe = $db->{'dat'}{'postpipe'}; } } # When we have a $postpipe we never write to a device if ($postpipe ne "") { $entry->{'str'}{'lp'} = '/dev/null'; if ($config->{'spooler'} eq "lpd") { $entry->{'str'}{'if'} = $sysdeps->{'foomatic-rip'}; $entry->{'str'}{'af'} = $ppdfile; } elsif ($config->{'spooler'} eq "lprng") { $entry->{'str'}{'if'} = $sysdeps->{'foomatic-rip'}; $entry->{'str'}{'ppd'} = $ppdfile; $entry->{'bool'}{'force_localhost'} = 1; } else { die "The spooler $config->{'spooler'} is not supported " . "by this function!\n"; } } # Various file setup mkdir $sysdeps->{'foo-etc'}, 0755; mkdir "$sysdeps->{'foo-etc'}/lpd", 0755; mkdir $entry->{'str'}{'sd'}, 0755; # Lead with a blank line for new entries push (@{$entry->{'comments'}}, "\n") if (!$reconf); # Put in a useful comment for both new and old entries push (@{$entry->{'comments'}}, sprintf ("\# Entry edited %s by $progname.", scalar(localtime(time))), "\# Additional configuration atop $ppdfile"); # Add to the printcap if a new entry if (!$reconf) { push(@{$pcap}, $entry); } # Generate/write te PPD file writeppdfile($config, $ppdfile, $rawqueue, $newfoomaticdata); # Make sure that /var/spool/lp-errs exists system "touch $sysdeps->{'lpd-log'}"; chmod 0600, $sysdeps->{'lpd-log'}; my ($lpuid, $lpgid) = (-1, -1); my $l; $l = getpwnam("lp"); $lpuid = $l if defined($l); $l = getgrnam("lp"); $lpgid = $l if defined($l); chown $lpuid, $lpgid, $sysdeps->{'lpd-log'}; # Write back /etc/printcap my $printcap = $sysdeps->{'lpd-pcap'}; rename $printcap, "$printcap.old" or die "Cannot backup $printcap!\n"; open PRINTCAP, "> $printcap" or die "Cannot write $printcap!\n"; print PRINTCAP dump_lpd_printcap($config, $pcap); close PRINTCAP; chmod 0644, $printcap; # In case of LPRng, give SIGHUP to the daemon, LPRng needs this to # recognize a new queue if ($config->{'spooler'} eq "lprng") { # first check configuration system("$sysdeps->{'lprng-checkpc'} -f > /dev/null 2>&1"); # now signal to use it system("$sysdeps->{'lpd-lpc'} reread > /dev/null 2>&1"); } return 1; } sub default_lpd { my ($config) = $_[0]; my $name = $config->{'queue'}; my $pcap = load_lpd_printcap(); # Add the alias "lp" to the /etc/printcap entry to make LPD considering # the chosen printer as default printer # Some stuff for renaming a queue named "lp" my $nppdfile = undef; my $newname = undef; my $rawqueue = 0; my @newcap; for (@{$pcap}) { my $p = $_; if ($p->{'names'}[0] eq $name) { $p->{'names'}[4] = 'lp'; } else { # Rename a printer whose first name is 'lp' if ($p->{'names'}[0] eq 'lp') { # Do we have a raw queue? if ((!defined($p->{'str'}{'if'})) || ($p->{'str'}{'if'} ne $sysdeps->{'foomatic-rip'})) { $rawqueue = 1; } # Search for a free name my $i = 0; my $namefound = 0; while(!$namefound) { my $pp; my $nameinuse = 0; for $pp (@{$pcap}) { if (defined($pp->{'names'})) { my $n; for $n (@{$pp->{'names'}}) { if ($n eq "lp$i") { $nameinuse = 1; last; } } if ($nameinuse) { $i++; last; } } } $namefound = 1 - $nameinuse; } $newname = "lp$i"; # Old PPD file name my $ppdfile = sprintf('%s/lpd/lp.ppd', $sysdeps->{'foo-etc'}); # New PPD file name my $nppdfile = sprintf('%s/lpd/%s.ppd', $sysdeps->{'foo-etc'}, $newname); # Rename the printer $p->{'names'}[0] = $newname; my $oldspooldir = $p->{'str'}{'sd'}; $p->{'str'}{'sd'} = sprintf('%s/%s', $sysdeps->{'lpd-dir'}, $newname); if ($p->{'str'}{'af'} =~ /\.ppd$/) { $p->{'str'}{'af'} = $nppdfile; } # Rename old $ppdfile, if any rename $ppdfile, $nppdfile if (-f $ppdfile); # Rename the spool directory rename $oldspooldir, $p->{'str'}{'sd'} if (-d $oldspooldir); # Put out warning warn("WARNING: Printer \"lp\" renamed to \"$newname\".\n"); } # Remove 'lp' as alias name my $n; for $n (@{$p->{'names'}}) { if ($n eq 'lp') { $n = ''; } } } push (@newcap, $p); } my @newprintcap = dump_lpd_printcap($config, \@newcap); my $printcap = $sysdeps->{'lpd-pcap'}; rename $printcap, "$printcap.old" or die "Cannot backup $printcap!\n"; open PRINTCAP, "> $printcap" or die "Cannot write $printcap!\n"; print PRINTCAP @newprintcap; close PRINTCAP; chmod 0644, $printcap; return 1; } sub default_lprng { my ($config) = $_[0]; my $name = $config->{'queue'}; my $pcap = load_lpd_printcap(); # Move the /etc/printcap entry for the chosen printer to the first place # so that LPRng considers it as the default printer my @newcap; for (@{$pcap}) { push (@newcap, $_) if ($_->{'names'}[0] eq $name); } for (@{$pcap}) { push (@newcap, $_) unless ($_->{'names'}[0] eq $name); } my @newprintcap = dump_lpd_printcap($config, \@newcap); my $printcap = $sysdeps->{'lpd-pcap'}; rename $printcap, "$printcap.old" or die "Cannot backup $printcap!\n"; open PRINTCAP, "> $printcap" or die "Cannot write $printcap!\n"; print PRINTCAP @newprintcap; close PRINTCAP; chmod 0644, $printcap; # In case of LPRng, give SIGHUP to the daemon, LPRng needs this to # recognize the changes if ($config->{'spooler'} eq "lprng") { system("$sysdeps->{'lpd-lpc'} reread > /dev/null 2>&1"); system("$sysdeps->{'lprng-checkpc'} -f > /dev/null 2>&1"); } return 1; } sub delete_lpd { my ($config) = $_[0]; my $name = $config->{'queue'}; my $pcap = load_lpd_printcap(); my @newcap; for (@{$pcap}) { push (@newcap, $_) unless ($_->{'names'}[0] eq $name); } my @newprintcap = dump_lpd_printcap($config, \@newcap); my $printcap = $sysdeps->{'lpd-pcap'}; rename $printcap, "$printcap.old" or die "Cannot backup $printcap!\n"; open PRINTCAP, "> $printcap" or die "Cannot write $printcap!\n"; print PRINTCAP @newprintcap; close PRINTCAP; chmod 0644, $printcap; # PPD file name my $ppdfile = sprintf('%s/lpd/%s.ppd', $sysdeps->{'foo-etc'}, $config->{'queue'}); # Rename old $ppdfile, if any rename $ppdfile, "$ppdfile.old" if (-f $ppdfile); # In case of LPRng, give SIGHUP to the daemon, LPRng needs this to # recognize the changes if ($config->{'spooler'} eq "lprng") { system("$sysdeps->{'lpd-lpc'} reread > /dev/null 2>&1"); system("$sysdeps->{'lprng-checkpc'} -f > /dev/null 2>&1"); } return 1; } sub query_lpd { my ($config) = @_; # User requests data of a printer/driver combo to see the options before # installing a queue if (($opt_P) && ((($config->{'driver'}) && ($config->{'printer'}) && ($config->{'driver'} ne "raw")) || ($config->{'ppdfile'}))) { if ($opt_n) { my $olddatablob = load_lpd_datablob($opt_n); print_perl_combo_data($config, $olddatablob); } else { print_perl_combo_data($config); } return; } my $i = $ARGV[0]; if (!defined($i)) {$i = 0;} my $pcap = load_lpd_printcap(); my $p; if (!$opt_P) { print "\n"; } # Query the default printer my $default; if (!defined($config->{'queue'})) { if ($config->{'spooler'} eq "lpd") { # Under LPD the default printer is the printer which has # "lp" as its name or as an alias name my $def_firstname = undef; for $p (@{$pcap}) { if (defined($p->{'names'})) { my $n; for $n (@{$p->{'names'}}) { if ($n eq 'lp') { $def_firstname = $p->{'names'}[0]; last; } } if (defined($def_firstname)) { last; } } } if (defined($def_firstname)) { $default = $def_firstname; if (!$opt_P) { print "$def_firstname\n"; } } } else { # Under LPRng the default printer is the first entry in # /etc/printcap for $p (@{$pcap}) { if (defined($p->{'names'})) { $default = $p->{'names'}[0]; if (!$opt_P) { print "$p->{'names'}[0]" . "\n"; } last; } } } } for $p (@{$pcap}) { # enpty end entry for trailing comments next if !defined($p->{'names'}); # were we invoked for only one queue? next if (defined($config->{'queue'}) and $config->{'queue'} ne $p->{'names'}[0]); # load the queue data $db->{'dat'} = load_lpd_datablob($p->{'names'}[0]); # extract the queue data block my $c = $db->{'dat'}{'queuedata'}; if ($opt_P) { if ($p->{'names'}[0] eq $default) { $db->{'dat'}{'queuedata'}{'default'} = 1; } else { $db->{'dat'}{'queuedata'}{'default'} = 0; } $db->{'dat'}{'queuedata'}{'remote'} = 0; my $asciidata = $db->getascii(); $asciidata =~ s/\$VAR1/\$QUEUES[$i]/g; print $asciidata; $i ++; } else { # and get it to standard output dump_config($c); } } if (!$opt_P) { print "\n"; } return; } ### Queue manipulation functions for CUPS sub setup_cups { my ($config) = $_[0]; # PPD file name # (/etc/foomatic/cups/ will be a link to /etc/cups/ppd/) my $ppdfile = sprintf('%s/ppd/%s.ppd', $sysdeps->{'cups-etc'}, $config->{'queue'}); # Get the data from the former queue if we reconfigure or copy a queue # do also some checking of the user-supplied parameters my ($rawqueue, $newfoomaticdata, $makemodel, $beh) = getoldqueuedata($config, 1); # Here we set up the command line for the "lpadmin" command my $lpadminline = "$sysdeps->{'cups-admin'} -p \"$config->{'queue'}\" -E"; # Use manufacturer and model as description when no description is # provided if (defined($config->{'desc'})) { $lpadminline .= " -D \"$config->{'desc'}\""; } else { # Before we overwrite the description field with manufacturer # and model, check if there is some old contents my $pconf = load_cups_printersconf(); my $p; my $olddesc; for $p (@{$pconf}) { next if (defined($config->{'queue'}) and $config->{'queue'} ne $p->{'name'}); $olddesc = $p->{'Info'}; } if (!$olddesc) { if (!$rawqueue) { $lpadminline .= " -D \"$makemodel\""; } else { $lpadminline .= " -D \"Raw queue\""; } } } # Fill in the "location" field if something for it is provided. if (defined($config->{'loc'})) { $lpadminline .= " -L \"$config->{'loc'}\""; } # PPD file argument for the printer if (!$rawqueue) { $lpadminline .= " -P \'$ppdfile\'"; } # All URIs ("-c" option) have the same syntax as URIs in CUPS # ("-v" option of "lpadmin"). Here the old "file:/" URIs are # translated to the form which CUPS needs. All other URIs are # simply passed to lpadmin. my $cupsuri = ""; if (defined($config->{'connect'})) { if ($config->{'connect'} =~ m!^(file|usb|parallel|serial):(.*)$!) { # Translate "file:/" into the prefix needed by CUPS, if # necessary $cupsuri = $2; if ((($cupsuri =~ m!$sysdeps->{'ptal-pipes'}/(.+)$!) || ($cupsuri =~ m!/dev/ptal-printd/(.+)$!) || ($cupsuri =~ m!/var/run/ptal-printd/(.+)$!)) && (-x "$sysdeps->{'cups-backends'}/ptal")) { # Translate URI for ptal-printd (does not work with CUPS # 1.1.12 and newer) to URI for the "ptal" CUPS backend # script (if the script is there) my $devname = $1; $devname =~ s/_/:/; $devname =~ s/_/:/; $cupsuri = "ptal:/$devname"; } elsif ((($cupsuri =~ m!^$sysdeps->{'mtink-pipes'}/(.+)$!) || ($cupsuri =~ m!^/var/mtink/(.+)$!)) && (-x "$sysdeps->{'cups-backends'}/mtink")) { # Translate URI for mtinkd (does not work with CUPS # 1.1.12 and newer) to URI for the "mtink" CUPS backend # script (if the script is there) $cupsuri = "mtink:/$1"; } elsif ($config->{'connect'} =~ m!usb!i) { $cupsuri = cups_usb_device_uri_to_printer_uri($cupsuri); $cupsuri = "usb:$cupsuri"; } elsif (($cupsuri =~ m!lp[0-9]!) || ($cupsuri =~ m!LP[0-9]!)|| ($cupsuri =~ m!parallel!)) { $cupsuri = "parallel:$cupsuri"; } elsif (($cupsuri =~ m!tty!) || ($cupsuri =~ m!TTY!) || ($cupsuri =~ m!serial!)) { $cupsuri = "serial:$cupsuri"; } else { $cupsuri = "file:$cupsuri"; } } elsif (($config->{'connect'} =~ m!^ptal://?([^/].*)$!) && (!-x "$sysdeps->{'cups-backends'}/ptal")) { # If there is no "ptal" backend script for CUPS, use an URI # pointing to the pipe set up by ptal-printd. my $devname = $1; $devname =~ tr/:/_/; $cupsuri = "file:$sysdeps->{'ptal-pipes'}/$devname"; } elsif (($config->{'connect'} =~ m!^mtink:/(.*)$!) && (!-x "$sysdeps->{'cups-backends'}/mtink")) { # If there is no "mtink" backend script for CUPS, use an URI # pointing to the pipe set up by mtinkd. $cupsuri = "file:$sysdeps->{'mtink-pipes'}/$1"; } else { $cupsuri=$config->{'connect'}; } # Correct PTAL URIs: "ptal:/..." for HPOJ 0.9, "ptal://..." for newer # HPOJ if ($cupsuri =~ m!^ptal:/!) { $cupsuri = cups_correct_ptal_uri($cupsuri); } } # Are there changes in the error handling of the backend? if (((defined($config->{'dd'})) && (((defined($beh->{'dd'})) && ($config->{'dd'} ne $beh->{'dd'})) || ($config->{'dd'} != 0))) || ((defined($config->{'att'})) && (((defined($beh->{'att'})) && ($config->{'att'} ne $beh->{'att'})) || ($config->{'att'} != 1))) || ((defined($config->{'delay'})) && (((defined($beh->{'delay'})) && ($config->{'delay'} ne $beh->{'delay'})) || ($config->{'delay'} != 30)))) { if (!defined($config->{'dd'})) { $config->{'dd'} = (defined($beh->{'dd'}) ? $beh->{'dd'} : 0); } if (!defined($config->{'att'})) { $config->{'att'} = (defined($beh->{'att'}) ? $beh->{'att'} : 1); } if (!defined($config->{'delay'})) { $config->{'delay'} = (defined($beh->{'delay'}) ? $beh->{'delay'} : 30); } $cupsuri = $beh->{'uri'} if !$cupsuri; # Do only add the "beh" wrapper backend when it is really needed # (More than one retry and/or no disabling) and if the queue is not # using the HPLIP ("hp") backend, as otherwise the "hp-toolbox" # will not list the printer any more. HPLIP does infinite retries # in 30-sec intervals anyway. if (($cupsuri) && ($cupsuri !~ m!^hp(fax|):/!) && (($config->{'dd'} != 0) || ($config->{'att'} != 1))) { $cupsuri = sprintf("beh:/%d/%d/%d/%s", $config->{'dd'}, $config->{'att'}, $config->{'delay'}, $cupsuri); } } if ($cupsuri) { $lpadminline .= " -v \"$cupsuri\""; } # Directory setup, let the Foomatic PPD directory for CUPS be the same # as /etc/cups/ppd/ (where CUPS stores the PPDs of the installed queues) mkdir $sysdeps->{'foo-etc'}, 0755; symlink "$sysdeps->{'cups-etc'}/ppd/", "$sysdeps->{'foo-etc'}/cups"; # In CUPS we never have a $postpipe # (when we get a $postpipe from a source PPD file from another # spooler, we don't need to remove it really, because it will be # ignored by foomatic-rip, uncomment this to remove it) #$db->{'dat'}{'postpipe'} = ""; # Generate/write te PPD file writeppdfile($config, $ppdfile, $rawqueue, $newfoomaticdata); # Execute the lpadmin command to set up the new queue if (system $lpadminline) { # Remove the config files unlink "$ppdfile" if (-f "$ppdfile"); # Revert changed config files rename "$ppdfile.old", "$ppdfile" if (-f "$ppdfile.old"); die "Could not set up/change the queue \"$config->{'queue'}\"!\n"; } return 1; } sub default_cups { my ($config) = $_[0]; if ($< == 0) { # (/etc/cups/printers.conf can only be manipulated by root) # This line sets the default printer in /etc/cups/printers.conf my $command = "$sysdeps->{'cups-admin'} -d " . "\"$config->{'queue'}\" > /dev/null"; # Do it! (Ignore errors silently) system $command; } # This line sets the default printer in /etc/cups/lpoptions # (required for setting a remote queue as default) my $command = "$sysdeps->{'cups-lpoptions'} -d " . "\"$config->{'queue'}\" > /dev/null"; # Do it! system $command and die "Unable to set queue \"$config->{'queue'}\" as default!\n"; } sub delete_cups { my ($config) = $_[0]; # This line deletes the old printer queue my $queuedeleteline = "$sysdeps->{'cups-admin'} -x \"$config->{'queue'}\""; # Do it! system $queuedeleteline and die "Unable to delete queue \"$config->{'queue'}\"!\n"; return 1; } sub query_cups { my ($config) = @_; # User requests data of a printer/driver combo to see the options before # installing a queue if (($opt_P) && ((($config->{'driver'}) && ($config->{'printer'}) && ($config->{'driver'} ne "raw")) || ($config->{'ppdfile'}))) { if ($opt_n) { my $olddatablob = load_cups_datablob($opt_n); print_perl_combo_data($config, $olddatablob); } else { print_perl_combo_data($config); } return; } my $i = $ARGV[0]; if (!defined($i)) {$i = 0;} my $pconf = load_cups_printersconf(); if (defined($opt_r)) {$opt_r = undef;} my $p; if (!$opt_P) { print "\n"; } # Query the default printer my $default = ''; if (!defined($config->{'queue'})) { open DEFAULT, "$sysdeps->{'cups-lpstat'} -d |" or die "Could not run $sysdeps->{'cups-lpstat'}!\n"; my $defaultstr = ; close DEFAULT; if ($defaultstr =~ m!\S+:\s+(\S+)$!) { $default = $1; if (!$opt_P) { print "$default\n"; } } } for $p (@{$pconf}) { # were we invoked for only one queue? next if (defined($config->{'queue'}) and $config->{'queue'} ne $p->{'name'}); # load the queue data $db->{'dat'} = load_cups_datablob($p->{'name'}); # Enter info for remote queue if ($p->{'remote'}) { $db->{'dat'}{'queuedata'}{'foomatic'} = 0; $db->{'dat'}{'queuedata'}{'spooler'} = 'cups'; $db->{'dat'}{'queuedata'}{'queue'} = $p->{'name'}; $db->{'dat'}{'queuedata'}{'connect'} = $p->{'DeviceURI'}; $db->{'dat'}{'queuedata'}{'description'} = $p->{'Info'}; $db->{'dat'}{'queuedata'}{'loc'} = $p->{'Location'}; $db->{'dat'}{'queuedata'}{'remote'} = 1; } else { $db->{'dat'}{'queuedata'}{'remote'} = 0; } # extract the queue data block my $c = $db->{'dat'}{'queuedata'}; if ($opt_P) { if ($p->{'name'} eq $default) { $db->{'dat'}{'queuedata'}{'default'} = 1; } else { $db->{'dat'}{'queuedata'}{'default'} = 0; } my $asciidata = $db->getascii(); $asciidata =~ s/\$VAR1/\$QUEUES[$i]/g; print $asciidata; $i ++; } else { # and get it to standard output dump_config($c); } } if (!$opt_P) { print "\n"; } return; } ### Queue manipulation functions for PDQ sub setup_pdq { my ($config) = $_[0]; # Read the previous /usr/lib/pdq/printrc my $printrc = load_pdq_printrc(); my ($ppdfile, $driverfile, $entry, $reconf, $p); $reconf = 0; for $p (@{$printrc}) { if ((defined($p->{'name'})) && ($p->{'name'} eq $config->{'queue'})) { $entry = $p; $reconf = 1; last; use Data::Dumper; print "Reconfigure of ", Dumper($p); } } # Config file names $ppdfile = sprintf('%s/pdq/%s.ppd', $sysdeps->{'foo-etc'}, $config->{'queue'}); $driverfile = sprintf('%s/pdq/driverdescr/%s.pdq', $sysdeps->{'foo-etc'}, $config->{'queue'}); # Get the data from the former queue if we reconfigure or copy a queue # do also some checking of the user-supplied parameters my ($rawqueue, $newfoomaticdata, $makemodel) = getoldqueuedata($config, $reconf); # Set the initial line of the "printer" block in /usr/lib/pdq/printrc $entry->{'name'} = $config->{'queue'}; # Location field if ((defined($config->{'loc'})) || (!$reconf)) { $entry->{'location'} = "\"$config->{'loc'}\""; } # Model/Description field if (defined($config->{'desc'})) { $entry->{'model'} = "\"$config->{'desc'}\""; } elsif (!$entry->{'model'}) { if (!$rawqueue) { $entry->{'model'} = "\"$makemodel\""; } else { $entry->{'model'} = "\"Raw printer\""; } } # Create directories mkdir $sysdeps->{'foo-etc'}, 0755; mkdir $sysdeps->{'foo-etc'} . '/pdq', 0755; mkdir $sysdeps->{'foo-etc'} . '/pdq/driverdescr', 0755; # Make the printer driver descriptions in /etc/foomatic/pdq visible # for PDQ # symlink $sysdeps->{'foo-etc'} . '/pdq', $sysdeps->{'pdq-foomatic'}; # Save old driver file, use the "~" to make it appear an editor # backup so that PDQ does not parse it. # Save old $driverfile, if any rename $driverfile, "$driverfile.old~" if (-f $driverfile); # Generate/write the PPD file writeppdfile($config, $ppdfile, $rawqueue, $newfoomaticdata); # Create driver description file if ($rawqueue) { system("$sysdeps->{'foomatic-rip'} --genrawpdq $driverfile") and die "Cannot create $driverfile!\n"; } else { system("$sysdeps->{'foomatic-rip'} --ppd \'$ppdfile\' --genpdq " . "$driverfile") and die "Cannot create $driverfile!\n"; } # PDQ configuration file # Driver fields # Extract driver name my $driverdesc = `cat $driverfile`; $driverdesc =~ m!^\s*driver\s*(\"\S*\-\d+\")!m; # Driver-specific entries $entry->{'driver'} = $1; $entry->{'driver_opts'} = "\{ \}"; $entry->{'driver_args'} = "\{ \}"; # Interface fields # All URIs ("-c" option) have the same syntax as URIs in CUPS ("-v" # option of "lpadmin"). if ($config->{'connect'} =~ m!^(file|usb|parallel|serial):(.*)!) { # Local printer or printing to a file my $file = $2; if ($config->{'connect'} =~ m!^usb://!) { # Queue with printer-bound USB URI transferred from CUPS, # as PDQ does not support these URIs, translate it # back to a standard USB device URI $file = cups_usb_printer_uri_to_device_uri($file); } if (! -e $file) { warn "The device or file $file doesn't exist? " . "Working anyway.\n"; } $entry->{'interface'} = "\"local-port\""; $entry->{'interface_opts'} = "\{ \}"; $entry->{'interface_args'} = "\{ \"PORT\" = \"$file\" \}"; } elsif ($config->{'connect'} =~ m!^ptal://?([^/].*)$!) { # HPOJ MLC protocol my $devname = $1; $devname =~ tr/:/_/; $entry->{'interface'} = "\"local-port\""; $entry->{'interface_opts'} = "\{ \}"; $entry->{'interface_args'} = "\{ \"PORT\" = " . "\"$sysdeps->{'ptal-pipes'}/$devname\" \}"; } elsif ($config->{'connect'} =~ m!^mtink:/(.+)$!) { # Printing through "mtinkd" $entry->{'interface'} = "\"local-port\""; $entry->{'interface_opts'} = "\{ \}"; $entry->{'interface_args'} = "\{ \"PORT\" = " . "\"$sysdeps->{'mtink-pipes'}/$1\" \}"; } elsif ($config->{'connect'} =~ m!^lpd://([^/]+)/([^/]+)$!) { # Remote LPD my $remhost = $1; my $remqueue = $2; $entry->{'interface'} = "\"bsd-lpd\""; $entry->{'interface_opts'} = "\{ \}"; $entry->{'interface_args'} = "\{ \"QUEUE\" = \"$remqueue\", \"REMOTE_HOST\" = " . "\"$remhost\" \}"; } elsif ($config->{'connect'} =~ m!^socket://([^/:]+):([0-9]+)/?$!) { # Socket (AppSocket/HP JetDirect) my $remhost = $1; my $remport = $2; $entry->{'interface'} = "\"tcp-port\""; $entry->{'interface_opts'} = "\{ \}"; $entry->{'interface_args'} = "\{ \"REMOTE_PORT\" = \"$remport\", \"REMOTE_HOST\" = " . "\"$remhost\" \}"; } elsif ($config->{'connect'}) { die ("The URI \"$config->{'connect'}\" is not supported " . "for PDQ or you have\nmistyped.\n"); } elsif (!$reconf) { die "You must specify a connection with -c.\n"; } # Add to the printrc if it is a new entry if (!$reconf) { push(@{$printrc}, $entry); } # Write back the modified printrc file my $printrcname = $sysdeps->{'pdq-printrc'}; rename $printrcname, "$printrcname.old" or die "Cannot backup $printrcname!\n"; open PRINTRC, "> $printrcname" or die "Cannot write $printrcname!\n"; print PRINTRC dump_pdq_printrc($printrc); close PRINTRC; chmod 0644, $printrcname; return 1; } sub default_pdq { my ($config) = $_[0]; # Determine the name of the config file to modify my $printrcname = ""; if ($< == 0) { $printrcname = "$sysdeps->{'pdq-printrc'}"; if (!(-f $printrcname)) {die "No file $printrcname!"}; } else { $printrcname = "$ENV{HOME}/.printrc"; if (!(-f $printrcname)) {system "touch $printrcname"}; } # Read the config file open PRINTRC, "$printrcname" or die "Cannot open $printrcname!"; my @printrc = ; close PRINTRC; # Remove all valid "default_printer" lines ($_ =~ /^\s*default_printer/ and $_="") foreach @printrc; # Insert the new "default_printer" line push @printrc, "default_printer $config->{'queue'}\n"; # Write back the modified config file open PRINTRC, "> $printrcname" or die "Cannot open $printrcname!"; print PRINTRC @printrc; close PRINTRC; } sub delete_pdq { my ($config) = $_[0]; my $name = $config->{'queue'}; my $printrc = load_pdq_printrc(); my @newrc; for (@{$printrc}) { push (@newrc, $_) unless (defined($_->{'name'}) && ($_->{'name'} eq $name)); } my @newprintrc = dump_pdq_printrc(\@newrc); my $printrcname = $sysdeps->{'pdq-printrc'}; rename $printrcname, "$printrcname.old" or die "Cannot backup $printrcname!\n"; open PRINTRC, "> $printrcname" or die "Cannot write $printrcname!\n"; print PRINTRC @newprintrc; close PRINTRC; chmod 0644, $printrcname; # Config file names my $ppdfile = sprintf('%s/pdq/%s.ppd', $sysdeps->{'foo-etc'}, $config->{'queue'}); my $driverfile = sprintf('%s/pdq/driverdescr/%s.pdq', $sysdeps->{'foo-etc'}, $config->{'queue'}); # Rename old $ppdfile, if any rename $ppdfile, "$ppdfile.old" if (-f $ppdfile); # Rename old driverfile, if any, use the "~" to make it appear an # editor backup so that PDQ does not parse it. # Rename old $driverfile, if any rename $driverfile, "$driverfile.old~" if (-f $driverfile); return 1; } sub query_pdq { my ($config) = @_; # User requests data of a printer/driver combo to see the options before # installing a queue if (($opt_P) && ((($config->{'driver'}) && ($config->{'printer'}) && ($config->{'driver'} ne "raw")) || ($config->{'ppdfile'}))) { if ($opt_n) { my $olddatablob = load_pdq_datablob($opt_n); print_perl_combo_data($config, $olddatablob); } else { print_perl_combo_data($config); } return; } my $i = $ARGV[0]; if (!defined($i)) {$i = 0;} my $printrc = load_pdq_printrc(); my $p; if (!$opt_P) { print "\n"; } # Query the default printer my $default; if (!defined($config->{'queue'})) { open DEFAULT, "$sysdeps->{'pdq-print'} -h 2>&1 |" or die "Could not run $sysdeps->{'pdq-print'}!\n"; my $defaultstr = join('', ); close DEFAULT; if ($defaultstr =~ m!The\s+default\s+printer\s+is\s+(\S+)$!m) { $default = $1; if (!$opt_P) { print "$default\n"; } } } for $p (@{$printrc}) { # Omit non-printer-block items next if (!(defined($p->{'name'}))); # were we invoked for only one queue? next if (defined($config->{'queue'}) and $config->{'queue'} ne $p->{'name'}); # load the queue data $db->{'dat'} = load_pdq_datablob($p->{'name'}); # extract the queue data block my $c = $db->{'dat'}{'queuedata'}; if ($opt_P) { if ($p->{'name'} eq $default) { $db->{'dat'}{'queuedata'}{'default'} = 1; } else { $db->{'dat'}{'queuedata'}{'default'} = 0; } $db->{'dat'}{'queuedata'}{'remote'} = 0; my $asciidata = $db->getascii(); $asciidata =~ s/\$VAR1/\$QUEUES[$i]/g; print $asciidata; $i ++; } else { # and get it to standard output dump_config($c); } } if (!$opt_P) { print "\n"; } return; } ### Queue manipulation functions for PPR sub setup_ppr { my ($config) = $_[0]; # Read the previous configuration my $printrc = load_ppr_printers_conf(); my ($ppdfile, $entry, $reconf, $p); $reconf = 0; for $p (@{$printrc}) { if ((defined($p->{'name'})) && ($p->{'name'} eq $config->{'queue'})) { $entry = $p; $reconf = 1; last; use Data::Dumper; print "Reconfigure of ", Dumper($p); } } # PPD file name $ppdfile = sprintf('%s/ppr/%s.ppd', $sysdeps->{'foo-etc'}, $config->{'queue'}); # Determine the PPR version in use my $pprversion; if (open VER, "$sysdeps->{'ppr-pprd'} --version |") { my $ver = ; close VER; $ver =~ /^\D*(\d+)\.(\d+)(\.(\d+)|)((a|alpha|b|beta|r|rc)(\d+|)|)/; $pprversion = (1e8 * $1 + 1e6 * $2 + 1e4 * $4 + ($5 ? 100 * (ord(uc($6)) - 64) + $7 : 9999)) / 1e8; } else { # Could not determine version, so we set it to 0 (oldest possible) $pprversion = 0; } # Get the data from the former queue if we reconfigure or copy a queue # do also some checking of the user-supplied parameters my ($rawqueue, $newfoomaticdata, $makemodel) = getoldqueuedata($config, $reconf); # Read out previous interface settings my $interface = ""; my $address = ""; my $options = ""; my $interface_options = ""; if ($reconf) { $interface = $entry->{'Interface'}; $address = $entry->{'Address'}; $interface_options = $entry->{'Options'}; if (($interface eq "foomatic-rip") || ($interface eq "ppromatic")) { if ($interface_options =~ /backend=(\S+)/) { $interface = $1; $interface_options =~ s/backend=(\S+)//; if ($interface_options =~ /^\s*$/) { $interface_options = ""; } } else { $interface = ""; } } } # All URIs ("-c" option) have the same syntax as URIs in CUPS ("-v" # option of "lpadmin"). if (defined($config->{'connect'})) { $interface_options =~ s/smbuser=(\S+)//; $interface_options =~ s/smbpassword=(\S+)//; if ($config->{'connect'} =~ m!^(file|usb|parallel|serial):(.*)!) { # Local printer or printing to a file $address = $2; if ($config->{'connect'} =~ m!^usb://!) { # Queue with printer-bound USB URI transferred from CUPS, # as PPR does not support these URIs, translate it # back to a standard USB device URI $address = cups_usb_printer_uri_to_device_uri($address); } if (! -e $address) { warn "The device or file $address doesn't exist? " . "Working anyway.\n"; } if (($address =~ m!usb!) || ($address =~ m!USB!) || ($address =~ m!$sysdeps->{'ptal-pipes'}!) || ($address =~ m!/dev/ptal-printd!) || ($address =~ m!/var/run/ptal-printd!) || ($address =~ m!$sysdeps->{'mtink-pipes'}!) || ($address =~ m!/var/mtink!)) { $interface = "simple"; } elsif (($address =~ m!lp[0-9]!) || ($address =~ m!LP[0-9]!) || ($address =~ m!parallel!)) { $interface = "parallel"; } elsif (($address =~ m!tty!) || ($address =~ m!TTY!) || ($address =~ m!serial!)) { $interface = "serial"; } else { $interface = "dummy"; } $options = ""; } elsif ($config->{'connect'} =~ m!^ptal://?([^/].*)$!) { # HPOJ MLC protocol my $devname = $1; $devname =~ tr/:/_/; $address = "$sysdeps->{'ptal-pipes'}/$devname"; $interface = "simple"; $options = ""; } elsif ($config->{'connect'} =~ m!^mtink:/(.+)$!) { # Printing through "mtinkd" $address = "$sysdeps->{'mtink-pipes'}/$1"; $interface = "simple"; $options = ""; } elsif ($config->{'connect'} =~ m!^lpd://([^/]+)/([^/]+)$!) { # Remote LPD my $remhost = $1; my $remqueue = $2; $address = "${remqueue}\@${remhost}"; $interface = "lpr"; $options = ""; } elsif ($config->{'connect'} =~ m!^socket://([^/:]+):([0-9]+)/?$!) { # Socket (AppSocket/HP JetDirect) my $remhost = $1; my $remport = $2; $address = "$remhost:$remport"; $interface = "tcpip"; $options = ""; } elsif ($config->{'connect'} =~ m!^smb://(.*)$!) { # SMB (Printer on Windows server) my $parameters = $1; # Get the user's login and password from the URI my $smbuser = ""; my $smbpassword = ""; if ($parameters =~ m!([^@]*)@([^@]+)!) { my $login = $1; $parameters = $2; if ($login =~ m!([^:]*):([^:]*)!) { $smbuser = $1; $smbpassword = $2; } else { $smbuser = $login; $smbpassword = ""; } } else { $smbuser = "GUEST"; $smbpassword = ""; } # When a password is given, a user name should be given, too. if (($smbpassword ne "") && ($smbuser eq "")) { $smbuser = "GUEST"; } # The "smb" interface of PPR uses "ppr" as the SMB user when no # user name is given. Usually one does not have such a user name # under Windows. So use "GUEST" if no user name is given. if ($smbuser eq "") { $smbuser = "GUEST"; } # Set the options for PPR's "smb" interface $options = ""; if ($smbuser ne "") { $options = "smbuser=\"$smbuser\""; if ($smbpassword ne "") { $options .= " smbpassword=\"$smbpassword\""; } } # Get the workgroup, server, and share name my $workgroup = ""; my $smbserver = ""; my $smbshare = ""; if ($parameters =~ m!([^/]*)/([^/]+)/([^/]+)$!) { $workgroup = $1; $smbserver = $2; $smbshare = $3; } elsif ($parameters =~ m!([^/]+)/([^/]+)$!) { $workgroup = ""; $smbserver = $1; $smbshare = $2; } else { die "The \"smb://\" URI must at least contain the " . "server name and the share name!\n"; } $address = "//$smbserver/$smbshare"; $interface = "smb"; } else { die ("The URI \"$config->{'connect'}\" is not supported for " . "PPR or you have\nmistyped.\n"); } } elsif (!$reconf) { die "You must specify a connection with -c.\n"; } # Here we set up the command line for the "ppad interface" and the # "ppad options" commands my $ppad_interface = ""; my $ppad_options = ""; my $ppad_rip = ""; if ($rawqueue) { $ppad_interface = "$sysdeps->{'ppr-ppad'} interface " . "\"$config->{'queue'}\" $interface \"$address\""; $ppad_options = "$sysdeps->{'ppr-ppad'} options " . "\"$config->{'queue'}\" $options $interface_options"; $ppad_rip = "$sysdeps->{'ppr-ppad'} " . "rip \"$config->{'queue'}\""; } else { if ($pprversion >= 1.50000102 ) { #1.50a2 $ppad_interface = "$sysdeps->{'ppr-ppad'} interface " . "\"$config->{'queue'}\" $interface \"$address\""; $ppad_options = "$sysdeps->{'ppr-ppad'} options " . "\"$config->{'queue'}\" $options $interface_options"; if ($db->{'dat'}{'id'}) { $ppad_rip = "$sysdeps->{'ppr-ppad'} " . "rip \"$config->{'queue'}\" foomatic-rip x" . # PPR 1.50a2 has a bug and needs at least one option for # the command line of the PPR RIP, therefore we add the # "0" in this case. The number is very likely not the # name of any boolean option, so it will be ignored by # foomatic-rip (($pprversion < 1.50000103 ) ? " 0" : ""); } else { $ppad_rip = "$sysdeps->{'ppr-ppad'} " . "rip \"$config->{'queue'}\""; } } else { $ppad_interface = "$sysdeps->{'ppr-ppad'} interface " . "\"$config->{'queue'}\" foomatic-rip \"$address\""; $ppad_options = "$sysdeps->{'ppr-ppad'} options " . "\"$config->{'queue'}\" backend=\"$interface\" " . "$options $interface_options"; $ppad_rip = "$sysdeps->{'ppr-ppad'} " . "rip \"$config->{'queue'}\""; } } # Execute the ppad commands to set up the new queue if ((system $ppad_interface) || (system $ppad_options) || (system $ppad_rip)) { die "Could not set up/change the queue \"$config->{'queue'}\"!\n"; } # Use manufacturer and model as description when no description is # provided my($comment, $olddesc); if (defined($config->{'desc'})) { $comment = $config->{'desc'}; } else { # Before we overwrite the description field with manufacturer # and model, check if there is some old contents if (($reconf) && ($entry->{'Comment'})) { $olddesc = $entry->{'Comment'}; } if (!$olddesc) { if (!$rawqueue) { $comment = "$makemodel"; } else { $comment = "Raw queue"; } } } if ($comment) { my $ppad_comment = "$sysdeps->{'ppr-ppad'} comment " . "\"$config->{'queue'}\" \"$comment\""; if (system $ppad_comment) { warn "Could not set description for the queue " . "\"$config->{'queue'}\"!\n"; } } # Fill in the "location" field if something for it is provided. if (defined($config->{'loc'})) { my $ppad_location = "$sysdeps->{'ppr-ppad'} location " . "\"$config->{'queue'}\" \"$config->{'loc'}\""; if (system $ppad_location) { warn "Could not set location for the queue " . "\"$config->{'queue'}\"!\n"; } } # Various file setup mkdir $sysdeps->{'foo-etc'}, 0755; mkdir $sysdeps->{'foo-etc'} . '/ppr', 0755; # Generate/write the PPD file writeppdfile($config, $ppdfile, $rawqueue, $newfoomaticdata); if ($rawqueue) { my $ppad_ppd = "$sysdeps->{'ppr-ppad'} ppd " . "\"$config->{'queue'}\" \"\" 2> /dev/null"; if (!system $ppad_ppd) { # Automatic input tray selection not activated by default, # because the feature requires manual choice of the paper types # in the trays and other spoolers than PPR do not have automatic # paper tray selection. In addition "ppop media " is # broken for printers with a high number of input trays in their # PPD files. #my $ppad_bins = "$sysdeps->{'ppr-ppad'} bins delete " . #"\"$config->{'queue'}\" \"" . #join ('" "', @{$entry->{'Bins'}}) . "\""; #if (system $ppad_bins) { #warn "Could not set paper input trays for the " . #"queue \"$config->{'queue'}\"!\n"; #} my $ppad_deffiltopts = "$sysdeps->{'ppr-ppad'} " . "deffiltopts \"$config->{'queue'}\" 2> /dev/null"; if (system $ppad_deffiltopts) { warn "Could not set \"DefFiltOpts\" entry for " . "the queue \"$config->{'queue'}\"!\n"; } } else { die "Could not set PPD for the queue \"$config->{'queue'}\"!\n"; } } else { my $ppad_ppd = "$sysdeps->{'ppr-ppad'} ppd " . "\"$config->{'queue'}\" \"$ppdfile\" 2> /dev/null"; if (!system $ppad_ppd) { # Automatic input tray selection not activated by default, # because the feature requires manual choice of the paper types # in the trays and other spoolers than PPR do not have automatic # paper tray selection. In addition "ppop media " is # broken for printers with a high number of input trays in their # PPD files. #my $ppad_bins = "$sysdeps->{'ppr-ppad'} bins ppd " . #"\"$config->{'queue'}\""; #if (system $ppad_bins) { #warn "Could not set paper input trays for the " . #"queue \"$config->{'queue'}\"!\n"; #} my $ppad_deffiltopts = "$sysdeps->{'ppr-ppad'} " . "deffiltopts \"$config->{'queue'}\" 2> /dev/null"; if (system $ppad_deffiltopts) { warn "Could not set \"DefFiltOpts\" entry for the " . "queue \"$config->{'queue'}\"!\n"; } } else { die "Could not set PPD for the queue \"$config->{'queue'}\"!\n"; } } if ($rawqueue) { # If we have a raw queue, delete the PPD file if there is still # one from a former queue. unlink "$ppdfile" if (-f "$ppdfile"); } else { # Clean up "Switchset" entry my @switchset = split('|', $entry->{'Switchset'}); my @newswitchset = (); for my $option (@switchset) { if (!(($option =~ /^F\s*\*([^\*\s=:]+)\s+([^\*\s=:]+)\s*$/) || ($option =~ /^F\s*([^\*\s=:]+)\s*=\s*([^\*\s=:]+)\s*$/) || ($option =~ /^F\s*\*([^\*\s=:]+)\s*$/) || ($option =~ /^F\s*([^\*\s=:]+)\s*$/))) { # The option is not a PPD option, keep it. # PPD options are incorporated in the PPD file now and so # they can be dropped in the "Switchset". if ($option =~ /^\s*(\S)(.*)$/) { push (@newswitchset, "-$1 \"$2\""); } } } my $ppad_switchset = "$sysdeps->{'ppr-ppad'} switchset " . "\"$config->{'queue'}\" " . join (' ', @newswitchset); if (system $ppad_switchset) { warn "Could not set switchset for the queue " . "\"$config->{'queue'}\"!\n"; } # Check, if there is a PJL option and set the "Jobbreak" to "none" # because otherwise there is a Ctrl+D between the PJL frame added # by foomatic-rip and the PostScript job. This breaks printing of # certain PS files as the CUPS test page. my $pjloption = 0; for my $arg (@{$db->{'dat'}->{'args'}}) { if ($arg->{'style'} eq "J") { $pjloption = 1; last; } } if ($pjloption) { my $ppad_jobbreak = "$sysdeps->{'ppr-ppad'} jobbreak " . "\"$config->{'queue'}\" none"; if (system $ppad_jobbreak) { warn "Could not set \"Jobbreak\" entry for the " . "queue \"$config->{'queue'}\"!\n"; } } } return 1; } sub default_ppr { my ($config) = $_[0]; # The default printer under PPR is the printer named "default". To be # able to easily switch the default printer we set up a printer group # named "default" containing the chosen default printer as its only # member. If there is already a printer called "default", we rename it. my $name = $config->{'queue'}; my $printrc = load_ppr_printers_conf(); my $printerfound = 0; for my $p (@{$printrc}) { if ($p->{'name'} eq $name) { $printerfound = 1; } # Rename a printer whose name is 'default' if ($p->{'name'} eq 'default') { # Search for a free name my $i = 0; my $namefound = 0; my $newname = ""; while(!$namefound) { my $pp; my $nameinuse = 0; for $pp (@{$printrc}) { if (defined($pp->{'name'})) { if ($pp->{'name'} eq "default$i") { $nameinuse = 1; $i++; last; } } } $namefound = 1 - $nameinuse; } $newname = "default$i"; # If the printer we want to use as default printer has the # name "default", we must use the new name as the member name # in the default group. if ($name eq "default") { $name = $newname; } # Do the renaming # Copy the queue ... if (system("foomatic-configure -s ppr -n $newname -C default")){ die "Could not copy the queue \"default\" into the " . "queue \"$newname\"!\n"; } # ... and remove the original one if (system("foomatic-configure -s ppr -n default -R")) { die "Could not remove the queue \"default\"!\n"; } warn "Renamed the printer\"default\" to \"$newname\"!\n"; } } # The desired default printer exists? Then make it the default if ($printerfound) { # Create a group named "default" with only this printer as member my $ppad_group = "$sysdeps->{'ppr-ppad'} group members " . "default \"$name\""; if (system $ppad_group) { warn "Could not create a group to make the queue \"$name\" " . "the default!\n"; } } } sub delete_ppr { my ($config) = $_[0]; # This line deletes the old printer queue my $queuedeleteline = "$sysdeps->{'ppr-ppad'} delete " . "\"$config->{'queue'}\""; # Do it! system $queuedeleteline and die "Unable to delete queue \"$config->{'queue'}\"!\n"; # Rename the PPD file # PPD file name my $ppdfile = sprintf('%s/ppr/%s.ppd', $sysdeps->{'foo-etc'}, $config->{'queue'}); # Rename old $ppdfile, if any rename "$ppdfile", "$ppdfile.old" if (-f "$ppdfile"); return 1; } sub query_ppr { my ($config) = @_; # User requests data of a printer/driver combo to see the options before # installing a queue if (($opt_P) && ((($config->{'driver'}) && ($config->{'printer'}) && ($config->{'driver'} ne "raw")) || ($config->{'ppdfile'}))) { if ($opt_n) { my $olddatablob = load_ppr_datablob($opt_n); print_perl_combo_data($config, $olddatablob); } else { print_perl_combo_data($config); } return; } my $i = $ARGV[0]; if (!defined($i)) {$i = 0;} my $pconf = load_ppr_printers_conf(); if (defined($opt_r)) {$opt_r = undef;} my $p; if (!$opt_P) { print "\n"; } # Query the default printer my $default; if (!defined($config->{'queue'})) { for $p (@{$pconf}) { if ($p->{'default'}) { $default = $p->{'name'}; if (!$opt_P) { print "$p->{'name'}\n"; } last; } } } for $p (@{$pconf}) { # were we invoked for only one queue? next if (defined($config->{'queue'}) and $config->{'queue'} ne $p->{'name'}); # load the queue data $db->{'dat'} = load_ppr_datablob($p->{'name'}); # extract the queue data block my $c = $db->{'dat'}{'queuedata'}; if ($opt_P) { if ($p->{'name'} eq $default) { $db->{'dat'}{'queuedata'}{'default'} = 1; } else { $db->{'dat'}{'queuedata'}{'default'} = 0; } $db->{'dat'}{'queuedata'}{'remote'} = 0; my $asciidata = $db->getascii(); $asciidata =~ s/\$VAR1/\$QUEUES[$i]/g; print $asciidata; $i ++; } else { # and get it to standard output dump_config($c); } } if (!$opt_P) { print "\n"; } return; } ### Queue manipulation functions for direct, spooler-less printing sub setup_direct { my ($config) = $_[0]; # Read the previous config file my $pconfig = load_direct_config(); my ($entry, $reconf, $p); for $p (@{$pconfig}) { if ($p->{'name'} eq $config->{'queue'}) { $entry = $p; $reconf = 1; last; use Data::Dumper; print "Reconfigure of ", Dumper($p); } } # PPD file name my $ppdfile = sprintf('%s/direct/%s.ppd', $sysdeps->{'foo-etc'}, $config->{'queue'}); # Get the data from the former queue if we reconfigure or copy a queue # do also some checking of the user-supplied parameters my ($rawqueue, $newfoomaticdata, $makemodel) = getoldqueuedata($config, $reconf); # Set the printer queue name $entry->{'name'} = $config->{'queue'}; # Use manufacturer and model as description when no description is # provided if (defined($config->{'desc'})) { $entry->{'desc'} = $config->{'desc'}; } else { # Before we overwrite the description field with manufacturer # and model, check if there is some old contents my( $olddesc ); if (($reconf) && ($entry->{'desc'})) { $olddesc = $entry->{'desc'}; } if (!$olddesc) { $entry->{'desc'} = "$makemodel"; } } # Fill in the "location" field if something for it is provided. if (defined($config->{'loc'})) { $entry->{'loc'} = $config->{'loc'}; } # If the printing jobs should not be passed to standard output, put the # command line into $postpipe (for example for Socket, Samba, parallel # port ...) my $postpipe = ""; if ((!$reconf) or ($config->{'connect'})) { # Set up connection type # All URIs ("-c" option) have the same syntax as URIs in CUPS ("-v" # option of "lpadmin"). if ($config->{'connect'} =~ m!^(file|usb|parallel|serial):(.*)!) { # Local printer or printing to a file my $file = $2; if ($config->{'connect'} =~ m!^usb://!) { # Queue with printer-bound USB URI transferred from CUPS, # as spooler-less printing does not support these URIs, # translate it back to a standard USB device URI $file = cups_usb_printer_uri_to_device_uri($file); } if (! -e $file) { warn "The device or file $file doesn't exist? " . "Working anyway.\n"; } if (($file =~ m!^$sysdeps->{'ptal-pipes'}/(.+)$!) || ($file =~ m!^/dev/ptal-printd/(.+)$!) || ($file =~ m!^/var/run/ptal-printd/(.+)$!)) { # Translate URI for ptal-printd to postpipe using the # "ptal-connect" command my $devname = $1; $devname =~ s/_/:/; $devname =~ s/_/:/; $postpipe = "$sysdeps->{'ptal-connect'} $devname -print"; } else { $postpipe = "$sysdeps->{'cat'} > $file"; } } elsif ($config->{'connect'} =~ m!^ptal://?([^/].*)$!) { # HPOJ MLC protocol my $devname = $1; $postpipe = "$sysdeps->{'ptal-connect'} $devname -print"; } elsif ($config->{'connect'} =~ m!^mtink:/(.+)$!) { # Printing through "mtinkd" $postpipe = "$sysdeps->{'cat'} > $sysdeps->{'mtink-pipes'}/$1"; } elsif ($config->{'connect'} =~ m!^lpd://([^/]+)/([^/]+)$!) { # Remote LPD my $remhost = $1; my $remqueue = $2; $postpipe = "$sysdeps->{'rlpr'} -q -h -P $remqueue\@$remhost"; } elsif ($config->{'connect'} =~ m!^socket://([^/:]+):([0-9]+)/?$!){ # Socket (AppSocket/HP JetDirect) my $remhost = $1; my $remport = $2; $postpipe = "$sysdeps->{'nc'} -w 1 $remhost $remport"; } elsif ($config->{'connect'} =~ m!^smb://(.*)$!) { # SMB (Printer on Windows server) my $parameters = $1; # Get the user's login and password from the URI my $smbuser = ""; my $smbpassword = ""; if ($parameters =~ m!([^@]*)@([^@]+)!) { my $login = $1; $parameters = $2; if ($login =~ m!([^:]*):([^:]*)!) { $smbuser = $1; $smbpassword = $2; } else { $smbuser = $login; $smbpassword = ""; } } else { $smbuser = "GUEST"; $smbpassword = ""; } # Get the workgroup, server, and share name my $workgroup = ""; my $smbserver = ""; my $smbshare = ""; if ($parameters =~ m!([^/]*)/([^/]+)/([^/]+)$!) { $workgroup = $1; $smbserver = $2; $smbshare = $3; } elsif ($parameters =~ m!([^/]+)/([^/]+)$!) { $workgroup = ""; $smbserver = $1; $smbshare = $2; } else { die "The \"smb://\" URI must at least contain the " . "server name and the share name!\n"; } # Set up the command line for printing on the SMB server $postpipe = "$sysdeps->{'smbclient'} \"//$smbserver/$smbshare\""; if ($smbpassword ne "") { warn("WARNING: smbclient password is visible in PPD file\n"); $postpipe .= " $smbpassword"; } if ($smbuser ne "") {$postpipe .= " -U $smbuser";} if ($workgroup ne "") {$postpipe .= " -W $workgroup";} $postpipe .= " -N -P -c 'print -' "; } elsif ($config->{'connect'} =~ m!^ncp://(.*)$!) { my $parameters = $1; # Get the user's login and password from the URI my $ncpuser = ""; my $ncppassword = ""; if ($parameters =~ m!([^@]*)@([^@]+)!) { my $login = $1; $parameters = $2; if ($login =~ m!([^:]*):([^:]*)!) { $ncpuser = $1; $ncppassword = $2; } else { $ncpuser = $login; $ncppassword = ""; } } else { $ncpuser = ""; $ncppassword = ""; } # Get the server and share name my $ncpserver = ""; my $ncpqueue = ""; if ($parameters =~ m!([^/]+)/([^/]+)$!) { $ncpserver = $1; $ncpqueue = $2; } else { die "The \"ncp://\" URI must at least contain the server " . "name and the queue name!\n"; } # Set up the command line for printing on the Netware server $postpipe = "$sysdeps->{'nprint'} -S $ncpserver"; if ($ncpuser ne "") { $postpipe .= " -U $ncpuser"; if ($ncppassword ne "") { warn("WARNING: ncp password is visible in PPD file\n"); $postpipe .= " -P $ncppassword"; } else { $postpipe .= " -n"; } } $postpipe .= " -q $ncpqueue -N - 2>/dev/null"; } elsif ($config->{'connect'} =~ m!^postpipe:(.*)$!) { # Pipe output into a command $postpipe = $1; } elsif ($config->{'connect'} =~ m!^stdout!) { $postpipe = ""; } elsif ($config->{'connect'}) { die ("The URI \"$config->{'connect'}\" is not supported for " . "spooler-less printing or you have\nmistyped.\n"); } else { die "You must specify a connection with -c.\n"; } # Put $postpipe into the data structure, so that it will be # inserted into the PPD file if ($postpipe ne "") { $postpipe = "| $postpipe"; $db->{'dat'}{'postpipe'} = $postpipe; } else { undef $db->{'dat'}{'postpipe'}; } } else { # Keep previous connection type # Use previous $postpipe if (defined($db->{'dat'}{'postpipe'})) { $postpipe = $db->{'dat'}{'postpipe'}; } } # Various file setup mkdir $sysdeps->{'foo-etc'}, 0755; mkdir $sysdeps->{'foo-etc'} . "/direct", 0755; # Add to the config file if a new entry if (!$reconf) { push(@{$pconfig}, $entry); } # Generate/write the PPD file writeppdfile($config, $ppdfile, $rawqueue, $newfoomaticdata); # Write back /etc/foomatic/direct/.config my $pconfigname = $sysdeps->{'direct-config'}; rename $pconfigname, "$pconfigname.old"; open PCONFIG, "> $pconfigname" or die "Cannot write $pconfigname!\n"; print PCONFIG dump_direct_config($pconfig); close PCONFIG; chmod 0644, $pconfigname; return 1; } sub default_direct { my ($config) = $_[0]; my $name = $config->{'queue'}; my $pconfig = load_direct_config(); # Modify the "default" fields of the printers appropriately for (@{$pconfig}) { $_->{'default'} = ($_->{'name'} eq $name); } my @newpconfig = dump_direct_config($pconfig); my $pconfigname = $sysdeps->{'direct-config'}; rename $pconfigname, "$pconfigname.old"; open PCONFIG, "> $pconfigname" or die "Cannot write $pconfigname!\n"; print PCONFIG @newpconfig; close PCONFIG; chmod 0644, $pconfigname; return 1; } sub delete_direct { my ($config) = $_[0]; my $name = $config->{'queue'}; my $pconfig = load_direct_config(); # Overtake all entries except the one of the deleted printer to the # new config file my @newconf; for (@{$pconfig}) { push (@newconf, $_) unless ($_->{'name'} eq $name); } my @newpconfig = dump_direct_config(\@newconf); my $pconfigname = $sysdeps->{'direct-config'}; rename $pconfigname, "$pconfigname.old"; open PCONFIG, "> $pconfigname" or die "Cannot write $pconfigname!\n"; print PCONFIG @newpconfig; close PCONFIG; chmod 0644, $pconfigname; # PPD file name my $ppdfile = sprintf('%s/direct/%s.ppd', $sysdeps->{'foo-etc'}, $config->{'queue'}); # Rename old $ppdfile, if any rename $ppdfile, "$ppdfile.old" if (-f $ppdfile); return 1; } sub query_direct { my ($config) = @_; # User requests data of a printer/driver combo to see the options before # installing a queue if (($opt_P) && ((($config->{'driver'}) && ($config->{'printer'}) && ($config->{'driver'} ne "raw")) || ($config->{'ppdfile'}))) { if ($opt_n) { my $olddatablob = load_direct_datablob($opt_n); print_perl_combo_data($config, $olddatablob); } else { print_perl_combo_data($config); } return; } my $i = $ARGV[0]; if (!defined($i)) {$i = 0;} my $pconf = load_direct_config(); if (defined($opt_r)) {$opt_r = undef;} my $p; if (!$opt_P) { print "\n"; } # Query the default printer my $default; if (!defined($config->{'queue'})) { for $p (@{$pconf}) { if ($p->{'default'}) { $default = $p->{'name'}; if (!$opt_P) { print "$p->{'name'}\n"; } last; } } } for $p (@{$pconf}) { # were we invoked for only one queue? next if (defined($config->{'queue'}) and $config->{'queue'} ne $p->{'name'}); # load the queue data $db->{'dat'} = load_direct_datablob($p->{'name'}); # extract the queue data block my $c = $db->{'dat'}{'queuedata'}; if ($opt_P) { if ($p->{'name'} eq $default) { $db->{'dat'}{'queuedata'}{'default'} = 1; } else { $db->{'dat'}{'queuedata'}{'default'} = 0; } $db->{'dat'}{'queuedata'}{'remote'} = 0; my $asciidata = $db->getascii(); $asciidata =~ s/\$VAR1/\$QUEUES[$i]/g; print $asciidata; $i ++; } else { # and get it to standard output dump_config($c); } } if (!$opt_P) { print "\n"; } return; } ### Functions used by the queue manipulation functions from above sub dump_config { my $c = $_[0]; print sprintf("\n", ($c->{'foomatic'} ? 1 : 0), $c->{'spooler'}), _tag('name',$c->{'queue'}), _tag('printer',$c->{'printer'}), _tag('driver',$c->{'driver'}), _tag('connect',$c->{'connect'}), _tag('location',$c->{'loc'}), _tag('description',$c->{'desc'}), ($c->{'spooler'} eq "cups" ? (_tag('dontdisable',$c->{'dd'}), _tag('attempts',$c->{'att'}), _tag('delay',$c->{'delay'}), (defined($c->{'quotaperiod'}) ? _tag('quotaperiod',$c->{'quotaperiod'}) : ()), (defined($c->{'pagelimit'}) ? _tag('pagelimit',$c->{'pagelimit'}) : ()), (defined($c->{'klimit'}) ? _tag('klimit',$c->{'klimit'}) : ()), (defined($c->{'laststatechange'}) ? _tag('laststatechange',$c->{'laststatechange'}) : ()), (defined($c->{'shared'}) ? _tag('shared',$c->{'shared'}) : ()), (defined($c->{'operationpolicy'}) ? _tag('operationpolicy',$c->{'operationpolicy'}) : ()), (defined($c->{'errorpolicy'}) ? _tag('errorpolicy',$c->{'errorpolicy'}) : ())) : ()), "\n"; return; } sub _tag { my ($t, $v) = @_; return '' if !defined($v); $v =~ s!\&!\&\;!g; $v =~ s!\$v\n"; } sub dump_lpd_printcap { my ($config, $pcap )= @_; my @retval; my $item; my $backslash = "\\"; $backslash = "" if $config->{'spooler'} eq 'lprng'; for $item (@{$pcap}) { for (@{$item->{'comments'}}) { push (@retval, "$_\n"); } if (defined($item->{'names'})) { map { $_ = '' if not defined $_; } @{$item->{'names'}}; push (@retval, (join('|', @{$item->{'names'}}) . ":${backslash}\n")); } for (keys(%{$item->{'str'}})) { # special case of 'tc' items, as there can be more than one if ($_ =~ /^tc\d+$/) { push (@retval, sprintf(" :tc=%s:${backslash}\n", $item->{'str'}{$_})); } else { push (@retval, sprintf(" :$_=%s:${backslash}\n", $item->{'str'}{$_})); } } for (keys(%{$item->{'bool'}})) { if ($item->{'bool'}{$_}) { push (@retval, " :$_:${backslash}\n"); } } for (keys(%{$item->{'num'}})) { push (@retval, sprintf(" :$_#%s:${backslash}\n", $item->{'num'}{$_})); } if( $backslash ){ my $lastline = pop(@retval); $lastline =~ s!:\\!:!; push (@retval, $lastline); } } print "PRINTCAP (spooler '" . $config->{'spooler'} . "') " . Dumper(\@retval) . "\n" if $debug; return @retval; } sub load_lpd_printcap { # list-o-printers, each with comments open PCAP, $sysdeps->{'lpd-pcap'} or die "Cannot read printcap file!\n"; my $pcap = join('', ); close PCAP; print "PC '$pcap'\n" if $debug; # die( "Cannot currently parse lprng style printcaps created by " . # "lprngtool!\n" . # "See the BUGS section in the manpage for details.\n") # if $pcap =~ m/\n\s*(:.*[^\\]\n\s*:)/m; # watch out for comments with \ at end of line - ignore \ $pcap =~ s!^(\s*\#.*\\)$!${1}MEMEMEM!gm; # now we join lines with \ at end $pcap =~ s!\\\n!!gms; # remove \ in comment lines $pcap =~ s!\\MEMEMEM!\\!g; print "AFTER '$pcap'\n" if $debug; my (@comment, @items, @comments_in_pc_entry); my ($pline, $pcentry); $pcentry = ""; for $pline (split('\n',$pcap)) { $pline =~ s/^\s+//; print "LINE '$pline', pcentry '$pcentry'\n" if $debug; next if $pline eq ""; if ($pline =~ m!^\#!) { if( $pcentry ){ push (@comments_in_pc_entry, $pline); } else { push (@comment, $pline); } } elsif ($pline =~ m!^:!) { push( @comment, @comments_in_pc_entry ); @comments_in_pc_entry = (); if( $pcentry ne "" ){ $pcentry .= $pline; } else { die( "bad printcap entry at '$pline'" ); } } elsif( $pcentry ne "" ){ push (@items, { 'itemstr' => $pcentry, 'comments' => [ @comment ] }); @comment = @comments_in_pc_entry; @comments_in_pc_entry = (); $pcentry = $pline; } else { $pcentry = $pline; } } if( $pcentry ){ push( @comment, @comments_in_pc_entry ); @comments_in_pc_entry = (); push (@items, { 'itemstr' => $pcentry, 'comments' => [ @comment ] }); @comment = (); } # Trailing comments get stuck on as empty item later... print "Printcap:\n" . Dumper(\@items ) if $debug; my $p; for $p (@items) { my $item; my $first = 1; my $tci = 0; for $item (split(/:\s*/, $p->{'itemstr'})) { next if $item =~ m!^\s*$!; if ($first) { my $name; for $name (split('\|',$item)) { $name =~ s!\s*(.+)\s*!$1!; push (@{$p->{'names'}}, $name); } $first = 0; } else { if ($item =~ m!^([^=]*)=(.+)!) { # special case of 'tc' items, as there can be more # than one if ($1 eq 'tc') { $p->{'str'}{"tc$tci"} = $2; $tci++; } else { $p->{'str'}{$1} = $2; } } elsif ($item =~ m!^([^\#]*)\#(.+)!) { $p->{'num'}{$1} = $2; } elsif ($item =~ m!^([^\@]*)\@?!) { $p->{'bool'}{$1} = 1; } } } } # Trailing comments from way above... if (scalar(@comment)) { push (@items, {'comments' => [ @comment ]}); } return \@items; } sub load_cups_printersconf { # list-o-printers my @items = (); my $itemshash = {}; if ($< == 0) { # Get info from /etc/cups/printers.conf, works only as "root" and # with locally defined printers my @pconf = (); if (open PCONF, $sysdeps->{'cups-pconf'}) { @pconf = ; close PCONF; } my $line; my $p = {}; my $linecount = 0; for $line (@pconf) { $linecount ++; if (!($line =~ m!^\s*\#!) && (!($line =~ m!^\s*$!))) { if ($line =~ m!^\s*<(.*)Printer\s+([^\s>]+)>\s*$!) { # Beginning of new block $p->{'name'} = $2; $p->{'default'} = ($1 eq "Default"); } elsif ($line =~ m!^\s*\s*$!) { # End of block push (@items, $p); $itemshash->{$p->{name}} = $#items; $p = {}; } elsif (defined($p->{'name'})) { # Inside block if (($line =~ m!^\s*(\S+)\s+(\S.*)$!) and ($1 ne '')) {$p->{$1} = $2}; } else { # Outside block die "Line $linecount in $sysdeps->{'cups-pconf'} " . "invalid!\n"; } } } } if (($< != 0) || (($opt_r) && (($opt_Q) || ($opt_P)))) { # Get info with the "lpstat" command, works for normal users and for # remote printers. open LPSTAT, "$sysdeps->{'cups-lpstat'} -l -d -p -v |" or die "Cannot execute \"lpstat\".\n"; my @lpstat = ; close LPSTAT; my $line; my $linecount = 0; my $defaultprinter = ''; my $currentitem = -1; for $line (@lpstat) { chomp ($line); $linecount ++; if (!($line =~ m!^\s*$!)) { if ($line =~ m!^\s*system\s+default\s+destination:\s+(\S+)\s*$!) { # Default printer $defaultprinter = $1; } elsif ($line =~ m!^printer\s+(\S+)\s+(\S.*)$!) { # Beginning of new printer's entry my $name = $1; my $state = $2; $state =~ s/\s+-$//; if (!defined($itemshash->{$name})) { push(@items, {}); $itemshash->{$name} = $#items; # If we are root and didn't see this entry # in /etc/cups/printers.conf, this printer # is remotely defined if ($< == 0) { $items[$itemshash->{$name}]{'remote'} = 1; } } $currentitem = $itemshash->{$name}; $items[$currentitem]{'name'} ||= $name; $items[$currentitem]{'State'} ||= $state; $items[$currentitem]{'default'} = ($name eq $defaultprinter); } elsif ($line =~ m!^\s+Description:\s+(\S.*)$!) { # Description field if ($currentitem != -1) { $items[$currentitem]{'Info'} ||= $1; } } elsif ($line =~ m!^\s+Location:\s+(\S.*)$!) { # Location field if ($currentitem != -1) { $items[$currentitem]{'Location'} ||= $1; } } elsif ($line =~ m!^\s+Connection:\s+remote!) { # Remote printer, only keep it when the "-r" option is # given if (!$opt_r) { # "delete" does not work on arrays with Perl 5.0.x # Thanks to Olaf Till (i7tiol@t-online.de) who # contributed this fix splice(@items, $currentitem, 1); #delete($items[$currentitem]); $currentitem = -1; } else { if ($currentitem != -1) { $items[$currentitem]{'remote'} = 1; } } } elsif ($line =~ m!^device\s+for\s+(\S+):\s+(\S.*)$!) { # "device for ..." line, extract URI my $name = $1; my $uri = $2; if (defined($itemshash->{$name})) { if ($uri !~ /:/) {$uri = "file:" . $uri}; $currentitem = $itemshash->{$name}; if (($currentitem <= $#items) && ($items[$currentitem]{'name'} eq $name)) { $items[$currentitem]{'DeviceURI'} ||= $uri; } } } } } } return \@items; } sub dump_pdq_printrc { my $printrc = $_[0]; my @retval; my $item; for $item (@{$printrc}) { if (defined($item->{'name'})) { # $item is a "printer" block push (@retval, "printer \"$item->{'name'}\" \{\n"); for my $key (keys(%{$item})) { if (($key ne 'name') && ($key ne 'others')) { push (@retval, "\t$key $item->{$key}\n"); } } push (@retval, "\}\n"); } elsif (defined($item->{'others'})) { # $item is not a "printer" block push (@retval, $item->{'others'}); } } # Check whether there is a already a 'try_include "/etc/foomatic/pdq/*"' # line in the config file if (!(join("", @retval) =~ m!^\s*try_include\s*\"$sysdeps->{'foo-etc'}/pdq/driverdescr/\*\"\s*$!m)) { splice(@retval,0,0,"# Line inserted by $progname\ntry_include " . "\"$sysdeps->{'foo-etc'}/pdq/driverdescr/*\"\n\n"); } # De-activate old line from Foomatic 2.0.x ($_ =~ s!^\s*try_include\s*\"$sysdeps->{'foo-etc'}/pdq/\*\"\s*$!\#$&!m) foreach @retval; return @retval; } sub load_pdq_printrc { # list-o-printers, with storage of non-printer-specific lines open PRINTRC, $sysdeps->{'pdq-printrc'} or die "Cannot read printrc file!\n"; my @printrc = ; close PRINTRC; my @items; my @others; my $line; my $p; my $linecount = 0; my $inprinterblock = 0; my $nonprinterlines = 0; for $line (@printrc) { $linecount ++; if ($line =~ m!^\s*printer\s+\"(.+)\"\s*{\s*$!) { if ($inprinterblock == 1) { die "New printer block started without previous one " . "being closed!\nLine $linecount in " . "$sysdeps->{'pdq-printrc'}.\n"; } # Beginning of new "printer" block # Store all non-printer-block stuff at first if ($nonprinterlines == 1) { push (@items, {'others' => join ("", @others )}); $nonprinterlines = 0; @others = (); } # Read printer block name $inprinterblock = 1; $p->{'name'} = $1; } elsif ($inprinterblock == 1) { # Inside "printer" block if ($line =~ m!^\s*}\s*$!) { # End of "printer" block $inprinterblock = 0; push (@items, $p); $p = {}; } elsif ($line =~ m!^\s*(\S+)\s*(\S+.*)$!) { $p->{$1} = $2; } elsif ((!($line =~ m!^\s*\#!)) && (!($line =~ m!^\s*$!))) { die "Line $linecount in $sysdeps->{'pdq-printrc'} " . "invalid!\n"; } } else { # Outside "printer" block push(@others, $line); $nonprinterlines = 1; } } # Trailing non-printer lines get stuck on as empty item if ($nonprinterlines == 1) { my $lines = join ("", @others); # Make sure that the last line line ends with a newline character if (!($lines =~ m!\n$!s)) {$lines .= "\n";} push (@items, {'others' => $lines}); } return \@items; } sub load_ppr_printers_conf { # Check whether there is a group named "default" to see what is the # default printer. my $defaultfromgroup = " "; if (open SHOWDEFAULTGROUP, "$sysdeps->{'ppr-ppad'} group show default 2>/dev/null |"){ for my $line () { chomp $line; if ($line =~ /\s*Members:\s*([^\s,]+)\s*$/) { $defaultfromgroup = $1; last; } } close SHOWDEFAULTGROUP; } # list-o-printers my @items = (); my $itemshash = {}; if ($< == 0) { # Get info from /etc/ppr/printers/, works only as # "root" opendir PCONFDIR, "$sysdeps->{'ppr-etc'}/printers" or die "Cannot read $sysdeps->{'ppr-etc'}/printers directory!\n"; my $name; while ($name = readdir(PCONFDIR)) { # Do not consider "." and ".." as a printer queue next if ($name =~ /^\./); my $line; my $p = {}; $p->{'name'} = $name; $p->{'default'} = (($name eq "default") || ($name eq $defaultfromgroup)); @{$p->{'Bins'}} = (); my $linecount = 0; open PCONFFILE, "$sysdeps->{'ppr-etc'}/printers/$name" or die "Cannot read $sysdeps->{'ppr-etc'}/printers/$name!\n"; for my $line () { chomp $line; $linecount ++; if (!($line =~ m!^\s*\#!) && (!($line =~ m!^\s*$!))) { if (($line =~ m!^\s*([^\s:]+)\s*:\s*(\S.*)$!) || ($line =~ m!^\s*([^\s:]+)\s*:\s*()$!)) { # : ... my $keyword = $1; my $values = $2; if (($values) && ($values ne "")) { # If the value is enclosed in double quotes, # remove the quotes $values =~ s/^\"(.*)\"$/$1/; if ($keyword eq "Bin") { push (@{$p->{'Bins'}}, $values); } else { $p->{$keyword} = $values; } } } else { warn "Line $linecount in " . "$sysdeps->{'ppr-etc'}/printers/$name " . "corrupted:\n $line\n"; } } } close PCONFFILE; push (@items, $p); $itemshash->{$p->{'name'}} = $#items; } } if ($< != 0) { # Get info with the "ppop"/"ppad" commands, works for normal users, # but needs installed and running PPR printing system open PPOP_DEST, "$sysdeps->{'ppr-ppop'} destination all |" or die "Cannot execute \"ppop\".\n"; my @ppop_dest = ; close PPOP_DEST; my $line; my $linecount = 0; my $currentitem = -1; for $line (@ppop_dest) { chomp ($line); $linecount ++; if (($line !~ m!^\s*-+\s*$!) && ($line !~ m!^\s*Destination\s+Type\s+Status\s+Charge\s*$!)){ if ($line =~ m!^\s*(\S+)\s+printer!) { my $name = $1; open PPAD_SHOW,"$sysdeps->{'ppr-ppad'} show $name |" or die "Cannot execute \"ppad\".\n"; my $lcount = 0; if (!defined($itemshash->{$name})) { push(@items, {}); $itemshash->{$name} = $#items; #print Dumper($itemshash); } $currentitem = $itemshash->{$name}; $items[$currentitem]{'name'} ||= $name; $items[$currentitem]{'default'} = (($name eq "default") || ($name eq $defaultfromgroup)); for my $line () { chomp $line; $lcount ++; if ((!($line =~ m!^\s*\#!)) && (!($line =~ m!^\s*$!))) { if ($line =~ m!^\s*([^\s:][^:]*)\s*:\s*(.*)$!) { # : ... my $keyword = $1; my $values = $2; if (($values) && ($values ne "")) { # If the value is enclosed in double # quotes, remove the quotes $values =~ s/^\"(.*)\"$/$1/; if ($keyword eq "Bins") { @{$items[$currentitem]{'Bins'}} = split(", ", $values); } else { if ($keyword eq "Switchset") { $values =~ s/ -(\S) /\|$1/g; $values =~ s/-(\S) /$1/g; $values =~ s/\'//g; $values =~ s/^|//g; } $items[$currentitem]{$keyword} = $values; } } } else { warn "Line $lcount in \"ppad show " . "$name\" corrupted:\n $line\n"; } } } close PPAD_SHOW; } } } } return \@items; } sub dump_direct_config { my $config = $_[0]; my @retval; my $defaultprinter = undef; my $item; for $item (@{$config}) { if (defined($item->{'name'})) { if (defined($item->{'desc'})) { push (@retval, "$item->{'name'} desc:$item->{'desc'}\n"); } if (defined($item->{'loc'})) { push (@retval, "$item->{'name'} loc:$item->{'loc'}\n"); } if ($item->{'default'}) { $defaultprinter = $item->{'name'}; } } } if (defined($defaultprinter)) { unshift(@retval, "default: $defaultprinter\n"); } return @retval; } sub load_direct_config { # list-o-printers my @items = (); my $itemshash = {}; # Configured printers are represented by PPD files in /etc/foomatic/ opendir PCONFDIR, "$sysdeps->{'foo-etc'}/direct" or die "Cannot read $sysdeps->{'foo-etc'}/direct directory!\n"; my $name; while ($name = readdir(PCONFDIR)) { # Files beginning with a dot or ending with a tilde are never # printers next if (($name =~ /^\./) || ($name =~ /~$/)); # Only ".ppd" files are printer descriptions. next unless ($name =~ /\.ppd$/i); $name =~ s/\.ppd$//i; # Do not make two entries when there is both a ".ppd" AND ".PPD" # file for the same printer name. next if (defined($itemshash->{$name})); my $p = {}; $p->{'name'} = $name; push (@items, $p); $itemshash->{$p->{'name'}} = $#items; } # Get additional info from /etc/foomatic/direct/.config (default # printer, description, location if (open CONFIG, "< $sysdeps->{'direct-config'}") { while (my $line = ) { chomp $line; if ($line =~ /^default\s*:\s*([^:\s]+)\s*$/) { my $currentitem = $itemshash->{$1}; $items[$currentitem]{'default'} = 1; } elsif ($line =~ /^\s*([^:\s]+)\s+([^:\s]+)\s*:(.*)$/) { my $currentitem = $itemshash->{$1}; $items[$currentitem]{$2} = $3; } } close CONFIG; } return \@items; } sub cups_correct_ptal_uri { # HPOJ 0.9 uses "ptal:..." URIs with one slash # ("ptal:/mlc:usb:dj450") and the current CVS of HPOJ uses two # slashes ("ptal://mlc:usb:dj450"). Correct the user-supplied URI # according to what "lpinfo -v" reports. my ($uri) = @_; $uri =~ m!^ptal://?([^/].*)$!; my $device = $1; # PTAL URIs listed by "lpinfo -v" open F, "$sysdeps->{'cups-lpinfo'} -v |" or return (@_); while (my $line = ) { chomp($line); my $d = quotemeta($device); if ($line =~ m!(ptal://?$d)$!) { my $realdevice = $1; close F; return $realdevice; } } close F; # Nothing found, do not correct the input return @_; } sub cups_generate_usb_device_lists { # Generate two lists: One of the actual USB device files in the # file system, another of the USB URIs listed by CUPS' "lpinfo -v" # Actual devices my @usbdevices; for my $pattern ("/dev/usb/lp*", "/dev/usb/usblp*") { open F, "ls -1 $pattern 2>/dev/null |" or next; @usbdevices = sort { Foomatic::DB::normalizename($a) cmp Foomatic::DB::normalizename($b) } grep { chomp } ; close F; last if $#usbdevices >= 0; } return ([], []) if $#usbdevices < 0; # USB URIs listed by "lpinfo -v" open F, "$sysdeps->{'cups-lpinfo'} -v |" or return ([], []); my @usburis = grep { s!^direct usb:!! and chomp } ; close F; return ([], []) if $#usburis < 0; # Results return (\@usbdevices, \@usburis); } sub cups_usb_device_uri_to_printer_uri { # Transfer a device file name into a printer-bound CUPS URI for # the printer currently connected my ($device) = @_; return $device if $device =~ m!^//!; my @devicelists = cups_generate_usb_device_lists(); return $device if (($#{$devicelists[0]} < 0) || ($#{$devicelists[1]} < 0)); for (my $i = 0; $i <= $#{$devicelists[0]}; $i ++) { last if !$devicelists[1][$i]; if ($device eq $devicelists[0][$i]) { return $devicelists[1][$i]; } } return $device; } sub cups_usb_printer_uri_to_device_uri { # Transfer a device file name into a printer-bound CUPS URI for # the printer currently connected my ($device) = @_; return $device if $device =~ m!^/[^/]!; $device =~ s/ /\%20/g; my @devicelists = cups_generate_usb_device_lists(); return $device if (($#{$devicelists[0]} < 0) || ($#{$devicelists[1]} < 0)); for (my $i = 0; $i <= $#{$devicelists[1]}; $i ++) { last if !$devicelists[0][$i]; if ($device eq $devicelists[1][$i]) { return $devicelists[0][$i]; } } return $device; } sub load_datablob { my ($spooler, $queue) = @_; my $spoolersubdir; my $datablob; if (($spooler eq "lpd") || ($spooler eq "lprng")) { $datablob = load_lpd_datablob($queue); $spoolersubdir = 'lpd'; } elsif ($spooler eq "cups") { $datablob = load_cups_datablob($queue); $spoolersubdir = 'cups'; } elsif ($spooler eq "pdq") { $datablob = load_pdq_datablob($queue); $spoolersubdir = 'pdq'; } elsif ($spooler eq "ppr") { $datablob = load_ppr_datablob($queue); $spoolersubdir = 'ppr'; } elsif ($spooler eq "direct") { $datablob = load_direct_datablob($queue); $spoolersubdir = 'direct'; } else { die "Unsupported spooler: $spooler\n"; } # Is the given queue a valid queue? if (!$datablob) { return undef; } return ($datablob); } sub load_lpd_datablob { my ($queue) = $_[0]; # Load the PPD file my $ppdfile = sprintf('%s/lpd/%s.ppd', $sysdeps->{'foo-etc'}, $queue); my $dat = ppdtoperl($ppdfile); if (defined($dat)) { $dat->{'ppdfile'} = $ppdfile; } my $postpipe = (defined($dat) ? $dat->{'postpipe'} : ""); # Get additional info from /etc/printcap my $pcap = load_lpd_printcap(); my $p; for $p (@{$pcap}) { # enpty end entry for trailing comments next if !defined($p->{'names'}); # Search for the correct queue next if ($queue ne $p->{'names'}[0]); # Collect values my $c = {}; my $name = $c->{'queue'} = $p->{'names'}[0]; $c->{'desc'} = $p->{'names'}[1] if $p->{'names'}[1]; $c->{'loc'} = $p->{'names'}[3] if $p->{'names'}[3]; $c->{'foomatic'} = 0; my $if = ($p->{'str'}{'if'} || ""); if ($if =~ m!foomatic-rip$!) { $c->{'foomatic'} = 1; $c->{'printer'} = $dat->{'id'}; $c->{'driver'} = $dat->{'driver'}; } if (!$p->{'bool'}{'force_localhost'}) { # LPD $c->{'spooler'} = 'lpd'; } else { # LPRng $c->{'spooler'} = 'lprng'; } # TODO Raw queue for LPD # if (0 and $p->{'str'}{'if'} eq $file) { # Raw queue with $postpipe # if (open FILE, "$file") { # # The first line is #!/bin/sh # $line = ; # # The second line is a comment # $line = ; # # The remaining line(s) are the $postpipe # $line = join('', ); # chomp $line; # $postpipe = "| $line"; # close FILE; # } # } if (defined($postpipe)) { if ($postpipe =~ m!^\s*\|\s*($sysdeps->{'cat'}|cat)\s+-?\s*>\s*([^\s]+)\s*$!) { my $file = $2; if (($file =~ m!^$sysdeps->{'ptal-pipes'}/(.+)$!) || ($file =~ m!^/dev/ptal-printd/(.+)$!) || ($file =~ m!^/var/run/ptal-printd/(.+)$!)) { # Translate device for ptal-printd to ptal URI my $devname = $1; $devname =~ s/_/:/; $devname =~ s/_/:/; $c->{'connect'} = "ptal:/$devname"; } elsif (($file =~ m!^$sysdeps->{'mtink-pipes'}/(.+)$!) || ($file =~ m!^/var/mtink/(.+)$!)) { # Translate device for mtinkd to mtink URI $c->{'connect'} = "mtink:/$1"; } elsif ($file =~ m!usb!i) { $c->{'connect'} = "usb:$file"; } elsif ($file =~ m!(tty|serial)!i) { $c->{'connect'} = "serial:$file"; } elsif ($file =~ m!(lp[0-9]|parallel)!i) { $c->{'connect'} = "parallel:$file"; } else { $c->{'connect'} = "file:$file"; } } elsif ($postpipe =~ m!^\s*\|\s*($sysdeps->{'ptal-connect'}|ptal-connect|ptal-print)\s+(-print\s+|)([^\s]+)(\s+-print|)\s*$!){ $c->{'connect'} = "ptal:/$3"; } elsif ($postpipe =~ m!^\s*\|\s*($sysdeps->{'nc'}|netcat|nc)\s+(-w\s*1\s+|)([^\s]+)\s+([^\s]+)\s*$!){ $c->{'connect'} = "socket://$3:$4"; } elsif ($postpipe =~ m!^\s*\|\s*$sysdeps->{'rlpr'}\s.*-P\s*([^\s\\\@]+)\@([^\s\\\@]+)\s*$!) { $c->{'connect'} = "lpd://$2/$1"; } elsif ($postpipe =~ m!^.*\|\s*$sysdeps->{'smbclient'}\s+\"//([^/\s]+)/([^/\s]+)\"\s+(\S.*)$!s) { my $servershare = "$1/$2"; my $parameters = $3; my $password = ""; if ($parameters =~ m!^([^-]\S*)\s+(\S.*)$!) { $password = $1; $parameters = $2; } my $username = ""; if ($parameters =~ m!^-U\s+(\S*)\s+(\S.*)$!) { $username = $1; $parameters = $2; } my $workgroup = ""; if ($parameters =~ m!^-W\s+(\S*)\s+(\S.*)$!) { $workgroup = "$1/"; } my $identity = ""; if (($username eq "GUEST") && ($password eq "")) { $identity = ""; } elsif (($username eq "") && ($password eq "")) { $identity = ""; } elsif (($username ne "") && ($password eq "")) { $identity = "$username\@"; } elsif (($username eq "") && ($password ne "")) { $identity = ":$password\@"; } else { $identity = "$username:$password\@"; } $c->{'connect'} = "smb://$identity$workgroup$servershare"; } elsif ($postpipe =~ m!^\s*\|\s*$sysdeps->{'nprint'}\s+(\S.*)$!s) { my $parameters = $1; my $server = ""; if ($parameters =~ m!^-S\s+(\S*)\s+(\S.*)$!) { $server = $1; $parameters = $2; } my $username = ""; if ($parameters =~ m!^-U\s+(\S*)\s+(\S.*)$!) { $username = $1; $parameters = $2; } my $password = ""; if ($parameters =~ m!^-P\s+(\S*)\s+(\S.*)$!) { $password = $1; $parameters = $2; } if ($parameters =~ m!^-n\s+(\S.*)$!) { $parameters = $1; } my $queue = ""; if ($parameters =~ m!^-q\s+(\S*)\s+(\S.*)$!) { $queue = $1; } my $identity = ""; if (($username eq "") && ($password eq "")) { $identity = ""; } elsif (($username ne "") && ($password eq "")) { $identity = "$username\@"; } elsif (($username eq "") && ($password ne "")) { $identity = ":$password\@"; } else { $identity = "$username:$password\@"; } $c->{'connect'} = "ncp://$identity$server/$queue"; } elsif( $postpipe ){ $postpipe =~ m!\s*\|\s*(\S.*)$!; $c->{'connect'} = "postpipe:\"$1\""; } } else { my $lp = $p->{'str'}{'lp'}; if (defined($lp) and $lp and $lp ne '/dev/null') { if (($lp =~ m!^$sysdeps->{'ptal-pipes'}/(.+)$!) || ($lp =~ m!^/dev/ptal-printd/(.+)$!) || ($lp =~ m!^/var/run/ptal-printd/(.+)$!)) { # Translate device for ptal-printd to ptal URI my $devname = $1; $devname =~ s/_/:/; $devname =~ s/_/:/; $c->{'connect'} = "ptal:/$devname"; } elsif (($lp =~ m!^$sysdeps->{'mtink-pipes'}/(.+)$!) || ($lp =~ m!^/var/mtink/(.+)$!)) { # Translate device for mtinkd to mtink URI $c->{'connect'} = "mtink:/$1"; } elsif ($lp =~ m!^\w+:!i) { $c->{'connect'} = $lp; } else { $c->{'connect'} = "file:$lp"; } } my ($rm, $rp) = ($p->{'str'}{'rm'}, $p->{'str'}{'rp'}); if (defined($rm) and defined($rp)) { $c->{'connect'} = "lpd://$rm/$rp"; } } $dat->{'queuedata'} = $c; } if (!defined($dat->{'queuedata'})) {$dat = undef}; return $dat; } sub load_cups_datablob { my ($queue) = $_[0]; # Load the PPD file my $ppdfile = sprintf('%s/ppd/%s.ppd', $sysdeps->{'cups-etc'}, $queue); #my $ppdfile = sprintf('%s/%s.ppd', # $sysdeps->{'foo-etc'}, # $queue); my $dat = ppdtoperl($ppdfile); if (defined($dat)) { $dat->{'ppdfile'} = $ppdfile; } # Get additional info from /etc/cups/printers.conf my $pconf = load_cups_printersconf(); my $p; for $p (@{$pconf}) { # were we invoked for only one queue? next if ($queue ne $p->{'name'}); # Collect values my $c = {}; $c->{'spooler'} = 'cups'; $c->{'queue'} = $p->{'name'}; $c->{'foomatic'} = 0; if (defined($dat->{'id'}) and defined($dat->{'driver'})) { $c->{'foomatic'} = 1; $c->{'printer'} = $dat->{'id'}; $c->{'driver'} = $dat->{'driver'}; } $c->{'desc'} = $p->{'Info'}; $c->{'loc'} = $p->{'Location'}; my $uri = $p->{'DeviceURI'}; # Is the beh (Backend Error Handler) wrapper backend in use? # If yes, read out its parameters and isolate the original URI. if ($uri =~ m!^beh:/(\d+)/(\d+)/(\d+)/(\S+)$!) { $c->{'dd'} = $1; $c->{'att'} = $2; $c->{'delay'} = $3; $uri = $4; } else { $c->{'dd'} = 0; $c->{'att'} = 1; $c->{'delay'} = 30; } if (($uri =~ m!^file:$sysdeps->{'ptal-pipes'}/(.+)$!) || ($uri =~ m!^file:/dev/ptal-printd/(.+)$!) || ($uri =~ m!^file:/var/run/ptal-printd/(.+)$!)) { # Translate URI for ptal-printd to ptal URI my $devname = $1; $devname =~ s/_/:/; $devname =~ s/_/:/; $uri = "ptal:/$devname"; } elsif (($uri =~ m!^file:$sysdeps->{'mtink-pipes'}/(.+)$!) || ($uri =~ m!^file:/var/mtink/(.+)$!)) { # Translate URI for mtinkd to mtink URI $uri = "mtink:/$1"; } $c->{'connect'} = $uri; # CUPS-specific extra info $c->{'quotaperiod'} = $p->{'QuotaPeriod'} if defined($p->{'QuotaPeriod'}); $c->{'pagelimit'} = $p->{'PageLimit'} if defined($p->{'PageLimit'}); $c->{'klimit'} = $p->{'KLimit'} if defined($p->{'KLimit'}); # CUPS 1.2-specific settings $c->{'laststatechange'} = $p->{'StateTime'} if defined($p->{'StateTime'}); $c->{'shared'} = $p->{'Shared'} if defined($p->{'Shared'}); $c->{'operationpolicy'} = $p->{'OpPolicy'} if defined($p->{'OpPolicy'}); $c->{'errorpolicy'} = $p->{'ErrorPolicy'} if defined($p->{'ErrorPolicy'}); $dat->{'queuedata'} = $c; } if (!defined($dat->{'queuedata'})) {$dat = undef}; return $dat; } sub load_pdq_datablob { my ($queue) = $_[0]; # Load the PPD file my $ppdfile = sprintf('%s/pdq/%s.ppd', $sysdeps->{'foo-etc'}, $queue); my $dat = ppdtoperl($ppdfile); if (defined($dat)) { $dat->{'ppdfile'} = $ppdfile; } if (defined($dat)) { my $printrc = load_pdq_printrc(); my $p; my $pdqopts; my $pdqargs; for $p (@{$printrc}) { # Omit non-printer-block items next if (!(defined($p->{'name'}))); # Search the current queue next if ($queue ne $p->{'name'}); $pdqopts = $p->{'driver_opts'}; $pdqargs = $p->{'driver_args'}; } my @printrcdefaults = split(",", $pdqopts); push (@printrcdefaults, split(",", $pdqargs)); my $c; @{$c->{'options'}} = (); for my $option (@printrcdefaults) { if ($option =~ m!^\s*\{?\s*\"(OPT_|)(.+?)\"\s*=\s*\"(.*)\"\s*\}?\s*$!) { push (@{$c->{'options'}}, "$2=$3"); } elsif ($option =~ m!^\s*\{?\s*\"(OPT_|)([^_]+?)_(.+?)\"\s*\}?\s*$!) { push (@{$c->{'options'}}, "$2=$3"); } elsif ($option =~ m!^\s*\{?\s*\"(OPT_|)(.+?)\"\s*\}?\s*$!) { push (@{$c->{'options'}}, "$2"); } } set_default_options($c, $dat); } # Get additional info from printrc my $printrc = load_pdq_printrc(); my $p; for $p (@{$printrc}) { # Omit non-printer-block items next if (!(defined($p->{'name'}))); # Search for the appropriate queue next if ($queue ne $p->{'name'}); my $c = {}; $c->{'spooler'} = 'pdq'; $c->{'queue'} = $p->{'name'}; $c->{'foomatic'} = 0; if (defined($dat->{'id'}) and defined($dat->{'driver'})) { $c->{'foomatic'} = 1; $c->{'printer'} = $dat->{'id'}; $c->{'driver'} = $dat->{'driver'}; } if (defined($p->{'model'})) { my $desc = $p->{'model'}; $desc =~ s!^\"!!; $desc =~ s!\"$!!; if ($desc ne '') {$c->{'desc'} = $desc;} } if (defined($p->{'location'})) { my $loc = $p->{'location'}; $loc =~ s!^\"!!; $loc =~ s!\"$!!; if ($loc ne '') {$c->{'loc'} = $loc;} } if ($p->{'interface'} =~ m!local-port!) { # Local printer $p->{'interface_args'} =~ m!\"?PORT\"?\s*=\s*\"?([^\"\s]+)\"?!; my $file = $1; if (($file =~ m!^$sysdeps->{'ptal-pipes'}/(.+)$!) || ($file =~ m!^/dev/ptal-printd/(.+)$!) || ($file =~ m!^/var/run/ptal-printd/(.+)$!)) { # Translate device for ptal-printd to ptal URI my $devname = $1; $devname =~ s/_/:/; $devname =~ s/_/:/; $c->{'connect'} = "ptal:/$devname"; } elsif (($file =~ m!^$sysdeps->{'mtink-pipes'}/(.+)$!) || ($file =~ m!^/var/mtink/(.+)$!)) { # Translate device for mtinkd to mtink URI $c->{'connect'} = "mtink:/$1"; } elsif ($file =~ m!usb!i) { $c->{'connect'} = "usb:$file"; } elsif ($file =~ m!(tty|serial)!i) { $c->{'connect'} = "serial:$file"; } elsif ($file =~ m!(lp[0-9]|parallel)!i) { $c->{'connect'} = "parallel:$file"; } else { $c->{'connect'} = "file:$file"; } } elsif ($p->{'interface'} =~ m!bsd-lpd!) { # Remote LPD $p->{'interface_args'} =~ m!\"?REMOTE_HOST\"?\s*=\s*\"?([^\"\s]+)\"?!; my $remhost = $1; $p->{'interface_args'} =~ m!\"?QUEUE\"?\s*=\s*\"?([^\"\s]+)\"?!; my $remqueue = $1; $c->{'connect'} = "lpd://$remhost/$remqueue"; } elsif ($p->{'interface'} =~ m!tcp-port!) { # Socket $p->{'interface_args'} =~ m!\"?REMOTE_HOST\"?\s*=\s*\"?([^\"\s]+)\"?!; my $remhost = $1; $p->{'interface_args'} =~ m!\"?REMOTE_PORT\"?\s*=\s*\"?([^\"\s]+)\"?!; my $remport = $1; $c->{'connect'} = "socket://$remhost:$remport"; } $dat->{'queuedata'} = $c; } if (!defined($dat->{'queuedata'})) {$dat = undef}; return $dat; } sub load_ppr_datablob { my ($queue) = $_[0]; # Load the PPD file my $ppdfile = sprintf('%s/ppr/%s.ppd', $sysdeps->{'foo-etc'}, $queue); my $dat = ppdtoperl($ppdfile); if (defined($dat)) { $dat->{'ppdfile'} = $ppdfile; } # Get additional info from /etc/ppr/* my $pconf = load_ppr_printers_conf(); my $p; for $p (@{$pconf}) { # were we invoked for only one queue? next if ($queue ne $p->{'name'}); # Collect values my $c = {}; $c->{'spooler'} = 'ppr'; $c->{'queue'} = $p->{'name'}; $c->{'foomatic'} = 0; if (defined($dat->{'id'}) and defined($dat->{'driver'})) { $c->{'foomatic'} = 1; $c->{'printer'} = $dat->{'id'}; $c->{'driver'} = $dat->{'driver'}; } $c->{'desc'} = $p->{'Comment'}; $c->{'loc'} = $p->{'Location'}; if (defined($dat)) { my @printerdefaults = split('|', $p->{'Switchset'}); my $o; @{$o->{'options'}} = (); for my $option (@printerdefaults) { if (($option =~ /^F\s*\*([^\*\s=:]+)\s+([^\*\s=:]+)\s*$/) || ($option =~ /^F\s*([^\*\s=:]+)\s*=\s*([^\*\s=:]+)\s*$/)) { push (@{$o->{'options'}}, "$1=$2"); } elsif (($option =~ /^F\s*\*([^\*\s=:]+)\s*$/) || ($option =~ /^F\s*([^\*\s=:]+)\s*$/)) { push (@{$o->{'options'}}, "$1"); } } set_default_options($o, $dat); } my $address = $p->{'Address'}; my $interface = $p->{'Interface'}; my $interface_options = $p->{'Options'}; if (($interface eq "foomatic-rip") || ($interface eq "ppromatic")) { if ($interface_options =~ /backend=(\S+)/) { $interface = $1; $interface_options =~ s/backend=(\S+)//; if ($interface_options =~ /^\s*$/) { $interface_options = ""; } } else { $interface = ""; } } my $uri = ""; if (($interface eq "simple") || ($interface eq "parallel") || ($interface eq "serial") || ($interface eq "dummy")) { # local printer if (($address =~ m!^$sysdeps->{'ptal-pipes'}/(.+)$!) || ($address =~ m!^/dev/ptal-printd/(.+)$!) || ($address =~ m!^/var/run/ptal-printd/(.+)$!)) { # Translate device for ptal-printd to ptal URI my $devname = $1; $devname =~ s/_/:/; $devname =~ s/_/:/; $uri = "ptal:/$devname"; } elsif (($address =~ m!^$sysdeps->{'mtink-pipes'}/(.+)$!) || ($address =~ m!^/var/mtink/(.+)$!)) { # Translate device for mtinkd to mtink URI $uri = "mtink:/$1"; } elsif ($address =~ m!^\w+:!i) { $c->{'connect'} = $address; } else { $uri = "file:$address"; } } elsif ($interface eq "lpr") { # Remote LPD if ($address =~ /^([^\@]+)\@([^\@]+)$/) { my $remhost = $2; my $remqueue = $1; $uri = "lpd://$remhost/$remqueue"; } else { die "Remote LPD configuration of the queue $p->{'name'} " . "broken!\n"; } } elsif ($interface eq "tcpip") { # Socket (AppSocket/HP JetDirect) $uri = "socket://$address"; } elsif ($interface eq "smb") { # SMB (Printer on Windows server) if ($address =~ m!^//([^/]+)/([^/]+)$!) { my $smbserver = $1; my $smbshare = $2; my $smbuser = ""; if ($interface_options =~ /smbuser=(\S+)/) { $smbuser = $1; } else { # The PPR interface for SMB uses the user name "ppr" # when no user name is given. $smbuser = "ppr"; } my $smbpassword = ""; if ($interface_options =~ /smbpassword=(\S+)/) { $smbpassword = $1; } if (($smbpassword ne "") && ($smbuser eq "")) { $smbuser = "GUEST"; } $uri = "$smbserver/$smbshare"; if ($smbuser ne "") { if ($smbpassword ne "") { $smbuser .= ":$smbpassword"; } $uri = "$smbuser\@$uri"; } $uri = "smb://$uri"; } else { die "SMB configuration of the queue $p->{'name'} broken!\n"; } } else { # Interface not supported by Foomatic $uri = "$interface:$address"; } $c->{'connect'} = $uri; $dat->{'queuedata'} = $c; } if (!defined($dat->{'queuedata'})) {$dat = undef}; return $dat; } sub load_direct_datablob { my ($queue) = $_[0]; # Load the PPD file my $ppdfile = sprintf('%s/direct/%s.ppd', $sysdeps->{'foo-etc'}, $queue); my $dat = ppdtoperl($ppdfile); if (defined($dat)) { $dat->{'ppdfile'} = $ppdfile; } my $postpipe = (defined($dat) ? $dat->{'postpipe'} : ""); # Get additional info from /etc/foomatic/direct/.config my $config = load_direct_config(); my $p; for $p (@{$config}) { # invalid entry next if !defined($p->{'name'}); # Search for the correct queue next if ($queue ne $p->{'name'}); # Collect values my $c = {}; my $name = $c->{'queue'} = $p->{'name'}; $c->{'desc'} = $p->{'desc'}; $c->{'loc'} = $p->{'loc'}; $c->{'foomatic'} = 0; if (defined($dat->{'id'}) and defined($dat->{'driver'})) { $c->{'foomatic'} = 1; $c->{'printer'} = $dat->{'id'}; $c->{'driver'} = $dat->{'driver'}; } $c->{'spooler'} = 'direct'; if (defined($postpipe)) { if ($postpipe =~ m!^\s*\|\s*($sysdeps->{'cat'}|cat)\s+-?\s*>\s*([^\s]+)\s*$!) { my $file = $2; if (($file =~ m!^$sysdeps->{'ptal-pipes'}/(.+)$!) || ($file =~ m!^/dev/ptal-printd/(.+)$!) || ($file =~ m!^/var/run/ptal-printd/(.+)$!)) { # Translate device for ptal-printd to ptal URI my $devname = $1; $devname =~ s/_/:/; $devname =~ s/_/:/; $c->{'connect'} = "ptal:/$devname"; } elsif (($file =~ m!^$sysdeps->{'mtink-pipes'}/(.+)$!) || ($file =~ m!^/var/mtink/(.+)$!)) { # Translate device for mtinkd to mtink URI $c->{'connect'} = "mtink:/$1"; } elsif ($file =~ m!usb!i) { $c->{'connect'} = "usb:$file"; } elsif ($file =~ m!(tty|serial)!i) { $c->{'connect'} = "serial:$file"; } elsif ($file =~ m!(lp[0-9]|parallel)!i) { $c->{'connect'} = "parallel:$file"; } else { $c->{'connect'} = "file:$file"; } } elsif ($postpipe =~ m!^\s*\|\s*($sysdeps->{'ptal-connect'}|ptal-connect|ptal-print)\s+(-print\s+|)([^\s]+)(\s+-print|)\s*$!){ $c->{'connect'} = "ptal:/$3"; } elsif ($postpipe =~ m!^\s*\|\s*($sysdeps->{'nc'}|netcat|nc)\s+(-w\s*1\s+|)([^\s]+)\s+([^\s]+)\s*$!){ $c->{'connect'} = "socket://$3:$4"; } elsif ($postpipe =~ m!^\s*\|\s*$sysdeps->{'rlpr'}\s.*-P\s*([^\s\\\@]+)\@([^\s\\\@]+)\s*$!) { $c->{'connect'} = "lpd://$2/$1"; } elsif ($postpipe =~ m!^.*\|\s*$sysdeps->{'smbclient'}\s+\"//([^/\s]+)/([^/\s]+)\"\s+(\S.*)$!s) { my $servershare = "$1/$2"; my $parameters = $3; my $password = ""; if ($parameters =~ m!^([^-]\S*)\s+(\S.*)$!) { $password = $1; $parameters = $2; } my $username = ""; if ($parameters =~ m!^-U\s+(\S*)\s+(\S.*)$!) { $username = $1; $parameters = $2; } my $workgroup = ""; if ($parameters =~ m!^-W\s+(\S*)\s+(\S.*)$!) { $workgroup = "$1/"; } my $identity = ""; if (($username eq "GUEST") && ($password eq "")) { $identity = ""; } elsif (($username eq "") && ($password eq "")) { $identity = ""; } elsif (($username ne "") && ($password eq "")) { $identity = "$username\@"; } elsif (($username eq "") && ($password ne "")) { $identity = ":$password\@"; } else { $identity = "$username:$password\@"; } $c->{'connect'} = "smb://$identity$workgroup$servershare"; } elsif ($postpipe =~ m!^\s*\|\s*$sysdeps->{'nprint'}\s+(\S.*)$!s) { my $parameters = $1; my $server = ""; if ($parameters =~ m!^-S\s+(\S*)\s+(\S.*)$!) { $server = $1; $parameters = $2; } my $username = ""; if ($parameters =~ m!^-U\s+(\S*)\s+(\S.*)$!) { $username = $1; $parameters = $2; } my $password = ""; if ($parameters =~ m!^-P\s+(\S*)\s+(\S.*)$!) { $password = $1; $parameters = $2; } if ($parameters =~ m!^-n\s+(\S.*)$!) { $parameters = $1; } my $queue = ""; if ($parameters =~ m!^-q\s+(\S*)\s+(\S.*)$!) { $queue = $1; } my $identity = ""; if (($username eq "") && ($password eq "")) { $identity = ""; } elsif (($username ne "") && ($password eq "")) { $identity = "$username\@"; } elsif (($username eq "") && ($password ne "")) { $identity = ":$password\@"; } else { $identity = "$username:$password\@"; } $c->{'connect'} = "ncp://$identity$server/$queue"; } else { $postpipe =~ m!\s*\|\s*(\S.*)$!; $c->{'connect'} = "postpipe:\"$1\""; } } else { $c->{'connect'} = "stdout"; } $dat->{'queuedata'} = $c; } if (!defined($dat->{'queuedata'})) {$dat = undef}; return $dat; } sub overtake_defaults { # overtake the option default settings from $olddatablob my ($olddatablob) = $_[0]; my $c; @{$c->{'options'}} = (); for my $opt (@{$olddatablob->{'args'}}) { push (@{$c->{'options'}}, "$opt->{'name'}=$opt->{'default'}"); } set_default_options($c, $db->{'dat'}); } sub set_default_options { # Set the default printing options by doing changes on the Perl # structure produced by "getdat", before the spooler-specific # datafile is generated my ($config) = $_[0]; my ($dest) = $_[1]; if ($#{$config->{'options'}} >= 0) { for (@{$config->{'options'}}) { my $option = $_; if ($option =~ m!^\s*([^=]+)=([^=]*)\s*$!) { # evaluated or numerical option, boolean option with # value "True", "False", "Yes", "No", "On", "Off", "1", "0" # given my $optname = $1; my $optvalue = $2; if (defined($dest->{'args_byname'}{$optname})) { if ($dest->{'args_byname'}{$optname}{'type'} eq 'bool') { if ((lc($optvalue) eq 'true') || (lc($optvalue) eq 'on') || (lc($optvalue) eq 'yes')) { $optvalue = '1'; } elsif ((lc($optvalue) eq 'false') || (lc($optvalue) eq 'off') || (lc($optvalue) eq 'no')) { $optvalue = '0'; } if (($optvalue eq '1') || ($optvalue eq '0')) { $dest->{'args_byname'}{$optname}{'default'} = $optvalue; } } elsif (($dest->{'args_byname'}{$optname}{'type'} eq 'int') || ($dest->{'args_byname'}{$optname}{'type'} eq 'float')) { if (($optvalue =~ m!^\s*[\+\-]?\s*[0-9]*\.?[0-9]*\s*$!) && ($optvalue >= $dest->{'args_byname'}{$optname}{'min'}) && ($optvalue <= $dest->{'args_byname'}{$optname}{'max'})) { $dest->{'args_byname'}{$optname}{'default'} = $optvalue; } } elsif (($dest->{'args_byname'}{$optname}{'type'} eq 'string') || ($dest->{'args_byname'}{$optname}{'type'} eq 'password')) { $optvalue = Foomatic::DB::checkoptionvalue ($dest, $optname, $optvalue, 0); $dest->{'args_byname'}{$optname}{'default'} = $optvalue if defined($optvalue); } else { if (defined($dest->{'args_byname'}{$optname}{'vals_byname'}{$optvalue})) { $dest->{'args_byname'}{$optname}{'default'} = $optvalue; } } } } else { if (($option =~ /^no(.+?)$/) && (defined($dest->{'args_byname'}{$1})) && ($dest->{'args_byname'}{$1}{'type'} eq 'bool')) { $dest->{'args_byname'}{$1}{'default'} = '0'; } elsif ((defined($dest->{'args_byname'}{$option})) && ($dest->{'args_byname'}{$option}{'type'} eq 'bool')) { $dest->{'args_byname'}{$option}{'default'} = '1'; } } } } } sub print_perl_combo_data { my ($config, $olddatablob) = @_; # Get the data if ($config->{'ppdfile'}) { # From PPD file my $dat = ppdtoperl($config->{'ppdfile'}); if (!defined($dat)) { die ("Unable to open PPD file \'$config->{'ppdfile'}\'\n"); } $db->{'dat'} = $dat; } else { # From Foomatic XML database my $possible = $db->getdat($config->{'driver'}, $config->{'printer'}); die "That printer and driver combination is not possible.\n" if (!$possible); die "There is neither a custom PPD file nor the driver database entry contains sufficient data to build a PPD file.\n" if (!$db->{'dat'}{'cmd'}) && (!$db->{'dat'}{'ppdfile'}); # Generate the PPD and extract it to Perl again (to get in the # composite options) my $ppd = $db->getppd($config->{'shortgui'}); delete ($db->{'dat'}); $db->{'dat'} = ppdfromvartoperl([split(/\n/, $ppd)]); } # The data can be viewed with the option defaults of an existing # queue set if ($olddatablob) { my $c; @{$c->{'options'}} = (); for my $opt (@{$olddatablob->{'args'}}) { push (@{$c->{'options'}}, "$opt->{'name'}=$opt->{'default'}"); } set_default_options($c, $db->{'dat'}); } # User can view the data of the combo also with options given on the # command line set_default_options($config, $db->{'dat'}); # Put it out my $asciidata = $db->getascii(); $asciidata =~ s/\$VAR1/\$COMBODATA/g; print $asciidata; return; } sub detect_spooler { # If tcp/localhost:631 opens, cups CUPS is the most sophisticated # spooler, if it is running, it is usually the primary printing # system my $page = (getpage('http://localhost:631/', 1) || ""); if ($page =~ m!CUPS!) { return 'cups'; } # PPR is also very sophisticated so check for this spooler if there is # no CUPS running. if (-x $sysdeps->{'ppr-ppr'}) { # There's a /usr/bin/ppr return 'ppr'; } # Else if /etc/printcap, some sort of lpd thing if (-f $sysdeps->{'lpd-pcap'}) { # If -f /etc/lpd.conf, lprng if (-f $sysdeps->{'lprng-conf'}) { return 'lprng'; } elsif (-x $sysdeps->{'lpd-bin'}) { # There's a /usr/sbin/lpd return 'lpd'; } } # pdq executable in our path somewhere? for (split(':', $ENV{'PATH'})) { if (-x "$_/pdq") { return 'pdq'; } } # If there is no known spooler, set up printers for direct, spooler-less # printing. return "direct"; } sub unimp { die "Sorry, $action for your spooler is unimplemented...\n"; } sub overview { print $db->get_overview_xml($opt_f); exit(0); } sub get_xml { my $x = undef; if (($opt_p) and ($opt_d)) { $x = $db->get_combo_data_xml($opt_d,$opt_p); } elsif ($opt_p) { $x = $db->get_printer_xml($opt_p); } elsif ($opt_d) { $x = $db->get_driver_xml($opt_d); } else { die "You must specify a -p printer and/or -d driver.\n"; } if (defined($x)) { print $x; } else { die "Unable to find object.\n"; } exit(0); } sub help { print STDERR <