#!@PERL@ # The above Perl path may vary on your system; fix it!!! -*- perl -*- use strict; use POSIX; use Cwd; my $ripversion='$Revision$'; #'# Fix emacs syntax highlighting # foomatic-rip is a spooler-independent filter script which takes # PostScript as standard input and generates the printer's page # description language (PDL)/raster format as standard output. This # kind of filter is usually called Raster Image Processor (RIP), # therefore the name "foomatic-rip". # Save it in one of the directories of your $PATH, so that it gets # found when called from the command line (for spooler-less printing), # link it to spooler-specific directories when you use CUPS or PPR: # ln -s /usr/bin/foomatic-rip /usr/lib/cups/filter/ # ln -s /usr/bin/foomatic-rip /usr/lib/ppr/lib/ # ln -s /usr/bin/foomatic-rip /usr/lib/ppr/interfaces/ # Mark this filter world-readable and world-executable (note that most # spoolers run the print filters as a special user, as "lp", not as # "root" or as the user who sent the job). # See http://www.linuxprinting.org/cups-doc.html # http://www.linuxprinting.org/lpd-doc.html # http://www.linuxprinting.org/ppr-doc.html # http://www.linuxprinting.org/pdq-doc.html # http://www.linuxprinting.org/direct-doc.html # http://www.linuxprinting.org/ppd-doc.html # ========================================================================== # # User-configurable settings, edit them if needed # # ========================================================================== # What path to use for filter programs and such. Your printer driver # must be in the path, as must be the renderer, $enscriptcommand, and # possibly other stuff. The default path is often fine on Linux, but # may not be on other systems. # my $execpath = "@EXECPATH@"; # CUPS raster drivers are searched here my $cupsfilterpath = "@prefix@/lib/cups/filter:/usr/local/lib/cups/filter:/usr/local/libexec/cups/filter:/opt/cups/filter:/usr/lib/cups/filter"; # Location of the configuration file "filter.conf", this file can be # used to change the settings of foomatic-rip without editing # foomatic-rip. itself. This variable must contain the full pathname # of the directory which contains the configuration file, usually # "/etc/foomatic". # Some versions of configure do not fully expand $sysconfdir my $prefix = "@prefix@"; my $configpath = "@sysconfdir@/foomatic"; # For the stuff below, the settings in the configuration file have priority. # Set to 1 to insert postscript code for page accounting (CUPS only). my $ps_accounting = 1; my $accounting_prolog = ""; # Enter here your personal command for converting non-postscript files # (especially text) to PostScript. If you leave it blank, at first the # line "textfilter: ..." from /etc/foomatic/filter.conf is read and # then the commands given on the list below are tried, beginning with # the first one. # You can set this to "a2ps", "enscript" or "mpage" to select one of the # default command strings. my $fileconverter = '@FILECONVERTER@'; my($kid0,$kid1,$kid2,$kid3,$kid4); my($kidfailed,$kid3finished,$kid4finished); my($convkidfailed,$dockidfailed,$kid0finished,$kid1finished,$kid2finished); my($fileconverterpid,$rendererpid,$fileconverterhandle,$rendererhandle); my($jobhasjcl); # What 'echo' program to use. It needs -e and -n. Linux's builtin # and regular echo work fine; non-GNU platforms may need to install # gnu echo and put gecho here or something. # my $myecho = '@ECHO@'; # Which shell to use for executing shell commands. Some of the PPD files # specify a FoomaticRIPCommandLine that makes use of constructs not available # from a vanilla Bourne shell. On systems where /bin/sh is a vanilla Bourne # we need to use a more "modern" shell to execute the command. This will # be set via a 'preferred_shell: (shell)' setting in the foomatic.conf file # or automatically detected at runtime later on in this program. # my $modern_shell = ''; # Set debug to 1 to enable the debug logfile for this filter; it will # appear as defined by $logfile. It will contain status from this # filter, plus the renderer's stderr output. You can also add a line # "debug: 1" to your /etc/foomatic/filter.conf to get all your # Foomatic filters into debug mode. # # WARNING: This logfile is a security hole; do not use in production. my $debug = 0; # This is the location of the debug logfile (and also the copy of the # processed PostScript data) in case you have enabled debugging above. # The logfile will get the extension ".log", the PostScript data ".ps". my $logfile = "/tmp/foomatic-rip"; # End interesting enduser options # ========================================================================== # # foomatic-rip spooler-independent PS->Printer filter (RIP) of Foomatic # # Copyright 2002 - 2004 Grant Taylor # & Till Kamppeter # & Helge Blischke # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, # USA. # my $added_lf = "\n"; # Flush everything immediately. $|=1; ## Constants used by this filter # Error codes, as some spooles behave different depending on the reason why # the RIP failed, we return an error code. As I have only found a table of # error codes for the PPR spooler. If our spooler is really PPR, these # definitions get overwritten by the ones of the PPR version currently in # use. my $EXIT_PRINTED = 0; # file was printed normally my $EXIT_PRNERR = 1; # printer error occured my $EXIT_PRNERR_NORETRY = 2; # printer error with no hope of retry my $EXIT_JOBERR = 3; # job is defective my $EXIT_SIGNAL = 4; # terminated after catching signal my $EXIT_ENGAGED = 5; # printer is otherwise engaged (connection # refused) my $EXIT_STARVED = 6; # starved for system resources my $EXIT_PRNERR_NORETRY_ACCESS_DENIED = 7; # bad password? bad port # permissions? my $EXIT_PRNERR_NOT_RESPONDING = 8; # just doesn't answer at all # (turned off?) my $EXIT_PRNERR_NORETRY_BAD_SETTINGS = 9; # interface settings are invalid my $EXIT_PRNERR_NO_SUCH_ADDRESS = 10; # address lookup failed, may be # transient my $EXIT_PRNERR_NORETRY_NO_SUCH_ADDRESS = 11; # address lookup failed, not # transient my $EXIT_INCAPABLE = 50; # printer wants (lacks) features # or resources # Standard Unix signal names #my SIGHUP = 1; #my SIGINT = 2; #my SIGQUIT = 3; #my SIGKILL = 9; #my SIGTERM = 15; #my SIGUSR1 = 10; #my SIGUSR2 = 12; #my SIGTTIN = 21; #my SIGTTOU = 22; my $ESPIPE = 29; # the errno value when seeking a pipe or socket ## Some important variables # We don't know yet, which spooler will be used. If we don't detect # one. we assume that we do spooler-less printing. Supported spoolers # are currently: # cups - CUPS - Common Unix Printing System # solaris - Solaris LP (possibly some other SysV LP services as well) # lpd - LPD - Line Printer Daemon # lprng - LPRng - LPR - New Generation # gnulpr - GNUlpr, an enhanced LPD (development stopped) # ppr - PPR (foomatic-rip runs as a PPR RIP) # ppr_int - PPR (foomatic-rip runs as an interface) # cps - CPS - Coherent Printing System # pdq - PDQ - Print, Don't Queue (development stopped) # direct - Direct, spooler-less printing my $spooler = 'direct'; # PPD file name my $ppdfile = ""; # Printer model my $model = ""; # Printer queue name my $printer = ""; # Printing options my $optstr = ""; # Job ID my $jobid = ""; # User who sent job my $jobuser = ((getpwuid($<))[0] || `whoami` || ""); chomp $jobuser; # Host from which job was sent my $jobhost = `hostname`; chomp $jobhost; # Job title my $jobtitle = "$jobuser\@$jobhost"; # Number of copies my $copies = "1"; # Post pipe (command into which the output of this filter should be piped) my $postpipe = ""; # job meta-data file path (for Solaris LP) my $attrpath = ''; # Files to be printed my @filelist = (); # Where to send debugging log output. Initialized to STDERR until the command # line arguments are parsed. my $logh = *STDERR; # JCL prefix to put before the JCL options (Can be modified by a # "*JCLBegin:" keyword in the PPD file): my $jclbegin = "\033%-12345X\@PJL\n"; # JCL command to switch the printer to the PostScript interpreter (Can # be modified by a "*JCLToPSInterpreter:" keyword in the PPD file): my $jcltointerpreter = ""; # JCL command to close a print job (Can be modified by a "*JCLEnd:" # keyword in the PPD file): my $jclend = "\033%-12345X\@PJL RESET\n"; # Prefix for starting every JCL command (Can be modified by # "*FoomaticJCLPrefix:" keyword in the PPD file): my $jclprefix = "\@PJL "; # Under which name were we called and in which directory do we reside $0 =~ m!^(.*/)([^/]+)$!; my $programdir = $1; my $programname = $2; # Filters to convert non-PostScript files my @fileconverters = (# a2ps (converts also other files than text) 'a2ps -1 @@--medium=@@PAGESIZE@@ @@--center-title=@@JOBTITLE@@ -o -', # enscript 'enscript -G @@-M @@PAGESIZE@@ @@-b "Page $%|@@JOBTITLE@@ ' . '--margins=36:36:36:36 --mark-wrapped-lines=arrow --word-wrap -p-', # mpage 'mpage -o -1 @@-b @@PAGESIZE@@ @@-H -h @@JOBTITLE@@ -m36l36b36t36r ' . '-f -P- -'); # spooler-specific file converters, default for the specific spooler when # none of the converters above is chosen. Remove weird characters from the # command line arguments to enhance security my @fixed_args = (defined($ARGV[0])?removespecialchars($ARGV[0]):"", defined($ARGV[1])?removespecialchars($ARGV[1]):"", defined($ARGV[2])?removespecialchars($ARGV[2]):"", defined($ARGV[3])?removespecialchars($ARGV[3]):"", defined($ARGV[4])?removespecialchars($ARGV[4]):""); my $spoolerfileconverters = { 'cups' => "${programdir}texttops '$fixed_args[0]' '$fixed_args[1]' '$fixed_args[2]' " . "'$fixed_args[3]' '$fixed_args[4] page-top=36 page-bottom=36 " . "page-left=36 page-right=36 nolandscape cpi=12 lpi=7 " . "columns=1 wrap'" }; ## Config file # Read config file if present my %conf = readConfFile("$configpath/filter.conf"); # Get execution path from config file $execpath = $conf{execpath} if defined $conf{execpath}; $ENV{'PATH'} = $execpath; # Get CUPS filter path from config file $cupsfilterpath = $conf{cupsfilterpath} if defined $conf{cupsfilterpath}; # Set debug mode $debug = $conf{debug} if defined $conf{debug}; # Determine which filter to use for non-PostScript files to be converted # to PostScript if (defined $conf{textfilter}) { $fileconverter = $conf{textfilter}; $fileconverter eq 'a2ps' and $fileconverter = $fileconverters[0]; $fileconverter eq 'enscript' and $fileconverter = $fileconverters[1]; $fileconverter eq 'mpage' and $fileconverter = $fileconverters[2]; } # Set the preferred shell for "system()" execution (defined $conf{preferred_shell}) && ($modern_shell = $conf{preferred_shell}); # if none was preferred, look for a shell that will work foreach my $shell ('/bin/sh', '/bin/bash', '/bin/ksh', '/bin/zsh') { if (($modern_shell eq '') && (-x $shell)) { open(FD, "| ".$shell." -c \"((0<1))\" 2>/dev/null"); (close(FD) == 1) && ($modern_shell = $shell); } } ## Environment variables; # "PPD": PPD file name for CUPS, Solaris, or PPR (if we run as PPR RIP) if (defined($ENV{'PPD'})) { # Clean the file name from weird characters which could cause # unexpected behaviour $ppdfile = removespecialchars($ENV{'PPD'}); # CUPS, Solaris LP, and PPR (RIP filter) use the "PPD" environment variable # to make the PPD file name available (we set CUPS here preliminarily, # in the next step we check for Solaris LP and the PPR) $spooler = 'cups'; } # "SPOOLER_KEY": Solaris LP print service if (defined($ENV{'SPOOLER_KEY'})) { $spooler = 'solaris'; $ppdfile = $ENV{'PPD'}; # set the printer name from the PPD file name ($ppdfile =~ m!^.*/([^/]+)\.ppd$!) && ($printer = $1); # Solaris LP may augment the "options" string argument from the command # line with an attributes file ($ATTRPATH) (defined($attrpath = $ENV{'ATTRPATH'})) && ($optstr = read_attribute_file($attrpath)); } # "PPR_VERSION": PPR if (defined($ENV{'PPR_VERSION'})) { # We have PPR $spooler = 'ppr'; } # "PPR_RIPOPTS": PPR if (defined($ENV{'PPR_RIPOPTS'})) { # PPR 1.5 allows the user to specify options for the PPR RIP with the # "--ripopts" option on the "ppr" command line. They are provided to # the RIP via the "PPR_RIPOPTS" environment variable. # Clean the option string from weird characters which could cause # unexpected behaviour $optstr .= removespecialchars("$ENV{'PPR_RIPOPTS'} "); # We have PPR $spooler = 'ppr'; } # "LPOPTS": Option settings for some LPD implementations (ex: GNUlpr) if (defined($ENV{'LPOPTS'})) { my @lpopts = split(/,/, removespecialchars($ENV{'LPOPTS'})); foreach my $opt (@lpopts) { $opt =~ s/^\s+//; $opt =~ s/\s+$//; if ($opt =~ /\s+/) { $opt = "\"$opt\""; } $optstr .= "$opt "; } # We have an LPD which accepts "-o" for options $spooler = 'gnulpr'; } ## Named command line options # We do not use Getopt::Long because it does not work when between the # option and the argument is no space ("-w80" instead of "-w 80"). This # happens in the command line of LPRng, but also users could type in # options this way when printing without spooler. # Make one option string with a non-printable character as separator, # So we can parse it more easily. # To avoid the separator to be in the options itselves, it is filters # out of the options. This does not break anything as having non # printable characters in the command line options does not make sense # nor is this needed. This way misinterpretation and even abuse is # prevented. my $argstr = "\x01" . join("\x01", map { removeunprintables($_) } @ARGV) . "\x01"; # Debug mode activated via command line if ($argstr =~ s/\x01--debug\x01/\x01/) { $debug = 1; } # Command line options for verbosity my $verbose = ($argstr =~ s/\x01-v\x01/\x01/); my $quiet = ($argstr =~ s/\x01-q\x01/\x01/); my $show_docs = ($argstr =~ s/\x01-d\x01/\x01/); my $do_docs; my $cupscolorprofile; if ($debug) { # Grotesquely unsecure; use for debugging only open LOG, "> ${logfile}.log"; $logh = *LOG; use IO::Handle; $logh->autoflush(1); } elsif (($quiet) && (!$verbose)) { # Quiet mode, do not log open LOG, "> /dev/null"; $logh = *LOG; use IO::Handle; $logh->autoflush(1); } else { # Default: log to STDERR $logh=*STDERR; } ## Start debug logging if ($debug) { # If we are not in debug mode, we do this later, as we must find out at # first which spooler is used. When printing without spooler we # suppress logging because foomatic-rip is called directly on the # command line and so we avoid logging onto the console. print $logh "foomatic-rip version $ripversion running...\n"; # Print the command line only in debug mode, Mac OS X adds very many # options so that CUPS cannot handle the output of the command line # in its log files. If CUPS encounters a line with more than 1024 # characters sent into its log files, it aborts the job with an error. if (($debug) || ($spooler ne 'cups')) { print $logh "called with arguments: '", join("', '",@ARGV), "'\n"; } } ## Continue with named options # Check for LPRng first so we do not pick up bogus ppd files by the -p option if ($argstr =~ s/\x01--lprng\x01/\x01/) { # We have LPRng $spooler = 'lprng'; } # 'PRINTCAP_ENTRY' environment variable is : LPRng # the :ppd=/path/to/ppdfile printcap entry should be used if (defined($ENV{'PRINTCAP_ENTRY'})){ $spooler = 'lprng'; my( @pc); @pc = split( /\s*:\s*/, $ENV{'PRINTCAP_ENTRY'} ); shift @pc; foreach (@pc) { if( /^ppd=(.*)$/ or /^ppdfile=(.*)$/ ){ $ppdfile = removespecialchars($1) if $1; } } } elsif ($argstr =~ s/\x01--lprng\x01/\x01/g) { # We have LPRng $spooler = 'lprng'; } # PPD file name given via the command line # allow duplicates, and use the last specified one while ( ($spooler ne 'lprng') and ($argstr =~ s/\x01-p(\x01|)([^\x01]+)\x01/\x01/)) { $ppdfile = removeshellescapes($2); } while ($argstr =~ s/\x01--ppd(\x01|=|)([^\x01]+)\x01/\x01/) { $ppdfile = removeshellescapes($2); } # Check for LPD/GNUlpr by typical options which the spooler puts onto # the filter's command line (options "-w": text width, "-l": text # length, "-i": indent, "-x", "-y": graphics size, "-c": raw printing, # "-n": user name, "-h": host name) if ($argstr =~ s/\x01-h(\x01|)([^\x01]+)\x01/\x01/) { # We have LPD or GNUlpr if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) { $spooler = 'lpd'; } $jobhost = $2; } if ($argstr =~ s/\x01-n(\x01|)([^\x01]+)\x01/\x01/) { # We have LPD or GNUlpr if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) { $spooler = 'lpd'; } $jobuser = $2; } if (($argstr =~ s/\x01-w(\x01|)\d+\x01/\x01/) || ($argstr =~ s/\x01-l(\x01|)\d+\x01/\x01/) || ($argstr =~ s/\x01-x(\x01|)\d+\x01/\x01/) || ($argstr =~ s/\x01-y(\x01|)\d+\x01/\x01/) || ($argstr =~ s/\x01-i(\x01|)\d+\x01/\x01/) || ($argstr =~ s/\x01-c\x01/\x01/)) { # We have LPD or GNUlpr if (($spooler ne 'lpd') && ($spooler ne 'gnulpr') && ($spooler ne 'lprng')) { $spooler = 'lpd'; } } # LPRng delivers the option settings via the "-Z" argument if ($argstr =~ s/\x01-Z(\x01|)([^\x01]+)\x01/\x01/) { my @lpopts = split(/,/, $2); foreach my $opt (@lpopts) { $opt =~ s/^\s+//; $opt =~ s/\s+$//; $opt = removeshellescapes($opt); if ($opt =~ /\s+/) { $opt = "\"$opt\""; } $optstr .= "$opt "; } # We have LPRng $spooler = 'lprng'; } # Job title and options for stock LPD if ($argstr =~ s/\x01-[jJ](\x01|)([^\x01]+)\x01/\x01/) { # An LPD $jobtitle = removeshellescapes($2); # Classic LPD hack if ($spooler eq "lpd") { $optstr .= "$jobtitle "; } } # Check for CPS if ($argstr =~ s/\x01--cps\x01/\x01/) { # We have cps $spooler = 'cps'; } # Options for spooler-less printing, CPS, or PDQ while ($argstr =~ s/\x01-o(\x01|)([^\x01]+)\x01/\x01/) { my $opt = $2; $opt =~ s/^\s+//; $opt =~ s/\s+$//; $opt = removeshellescapes($opt); if ($opt =~ /\s+/) { $opt = "\"$opt\""; } $optstr .= "$opt "; # If we don't print as a PPR RIP or as a CPS filter, we print without # spooler (we check for PDQ later) if (($spooler ne 'ppr') && ($spooler ne 'cps')) { $spooler = 'direct'; } } # Printer for spooler-less printing or PDQ if ($argstr =~ s/\x01-d(\x01|)([^\x01]+)\x01/\x01/) { $printer = removeshellescapes($2); } # Printer for spooler-less printing, PDQ, or LPRng if ($argstr =~ s/\x01-P(\x01|)([^\x01]+)\x01/\x01/) { $printer = removeshellescapes($2); } # Were we called from a PDQ wrapper? if ($argstr =~ s/\x01--pdq\x01/\x01/) { # We have PDQ $spooler = 'pdq'; } # Were we called to build the PDQ driver declaration file? # "--appendpdq=" appends the data to the , # "--genpdq=" creates/overwrites for the data, and # "--genpdq" writes to standard output my $genpdqfile = ""; if (($argstr =~ s/\x01--(gen)(raw|)pdq(\x01|=|)([^\x01]*)\x01/\x01/) || ($argstr =~ s/\x01--(append)(raw|)pdq(\x01|=|)([^\x01]+)\x01/\x01/)) { # Determine output file name if (!$4) { $genpdqfile = ">&STDOUT"; } else { if ($1 eq 'gen') { $genpdqfile = "> " . removeshellescapes($4); } else { $genpdqfile = ">> " . removeshellescapes($4); } } # Do we want to have a PDQ driver declaration for a raw printer? if ($2 eq 'raw') { my $time = time(); my @pdqfile = "driver \"Raw-Printer-$time\" { # This PDQ driver declaration file was generated automatically by # foomatic-rip to allow raw (filter-less) printing. language_driver all { # We accept all file types and pass them through without any changes filetype_regx \"\" convert_exec { ln -s \$INPUT \$OUTPUT } } filter_exec { ln -s \$INPUT \$OUTPUT } }"; open PDQFILE, $genpdqfile or rip_die("Cannot write PDQ driver declaration file", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); print PDQFILE join('', @pdqfile); close PDQFILE; exit $EXIT_PRINTED; } # We have PDQ $spooler = 'pdq'; } # remove extra spacing if running as LPRng filter $added_lf = "" if $spooler eq 'lprng'; ## Command line arguments without name # Remaining arguments my @rargs = split(/\x01/, $argstr); shift @rargs; # Load definitions for PPR error messages, check whether we run as # PPR interface or as PPR RIP my( $ppr_printer, $ppr_address, $ppr_options, $ppr_jobbreak, $ppr_feedback, $ppr_codes, $ppr_jobname, $ppr_routing, $ppr_for, $ppr_filetype, $ppr_filetoprint ); if ($spooler eq 'ppr') { # Read interface.sh so we will know the correct exit codes and # also signal.sh for the signal codes my $deffound = 0; # Did we find one of the definition files my @definitions; for my $file (("lib/interface.sh", "lib/signal.sh")) { open FILE, "< $file" || do { print $logh "error opening $file.\n"; next; }; $deffound = 1; while(my $line = ) { # Translate the shell script to Perl if (($line !~ m/^\s*$/) && ($line !~ m/^\s*\#/)) { $line =~ s/^\s*([^\#\s]*)/\$$1;/; push (@definitions, $line); } } close FILE; } if ($deffound) { # Apply the definitions loaded from PPR eval join('',@definitions) || do { print $logh "unable to evaluate definitions\n"; rip_die ("Error in definitions evaluation", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); }; } # Check whether we run as a PPR interface (if not, we run as a PPR RIP) if (($rargs[3] =~ /^\s*\d\d?\s*$/) && ($rargs[5] =~ /^\s*\d\d?\s*$/) && (($#rargs == 10) || ($#rargs == 9) || ($#rargs == 7))) { # PPR calls interfaces with many command line parameters, # where the forth and the sixth is a small integer # number. In addition, we have 8 (PPR <= 1.31), 10 # (PPR>=1.32), 11 (PPR >= 1.50) command line parameters. # We also check whether the current working directory is a # PPR directory. # Get all command line parameters $ppr_printer = removeshellescapes($rargs[0]); $ppr_address = $rargs[1]; $ppr_options = removeshellescapes($rargs[2]); $ppr_jobbreak = $rargs[3]; $ppr_feedback = $rargs[4]; $ppr_codes = $rargs[5]; $ppr_jobname = removeshellescapes($rargs[6]); $ppr_routing = removeshellescapes($rargs[7]); $ppr_for = $rargs[8]; $ppr_filetype = $rargs[9]; $ppr_filetoprint = removeshellescapes($rargs[10]); # Common job parameters $printer = $ppr_printer; $jobtitle = $ppr_jobname; if ((!$jobtitle) && ($ppr_filetoprint)) { $jobtitle = $ppr_filetoprint; } $optstr .= "$ppr_options $ppr_routing"; # Get the path of the PPD file from the queue configuration $ppdfile = `LANG=en_US; ppad show $ppr_printer | grep PPDFile`; $ppdfile = removeshellescapes($ppdfile); $ppdfile =~ s/PPDFile:\s+//; if ($ppdfile !~ m!^/!) { $ppdfile = "../../share/ppr/PPDFiles/$ppdfile"; } chomp($ppdfile); # We have PPR and run as an interface $spooler = 'ppr_int'; } } # CUPS my( $cups_jobid, $cups_user, $cups_jobtitle, $cups_copies, $cups_options, $cups_filename ); if ($spooler eq 'cups') { # Use CUPS font path ("FontPath" in /etc/cups/cupsd.conf) if ($ENV{'CUPS_FONTPATH'}) { $ENV{'GS_LIB'} = $ENV{'CUPS_FONTPATH'} . ($ENV{'GS_LIB'} ? ":$ENV{'GS_LIB'}" : ""); } else { if ($ENV{'CUPS_DATADIR'}) { $ENV{'GS_LIB'} = "$ENV{'CUPS_DATADIR'}/fonts" . ($ENV{'GS_LIB'} ? ":$ENV{'GS_LIB'}" : ""); } } # Get all command line parameters $cups_jobid = removeshellescapes($rargs[0]); $cups_user = removeshellescapes($rargs[1]); $cups_jobtitle = removeshellescapes($rargs[2]); $cups_copies = removeshellescapes($rargs[3]); $cups_options = removeshellescapes($rargs[4]); $cups_filename = removeshellescapes($rargs[5]); # Common job parameters #$printer = $cups_printer; $jobid = $cups_jobid; $jobtitle = $cups_jobtitle; $jobuser = $cups_user; $copies = $cups_copies; $optstr .= $cups_options; # Check for and handle inputfile vs stdin if ((defined($cups_filename)) && ($cups_filename) && ($cups_filename ne '-')) { # We get the input from a file @filelist = ($cups_filename); print $logh "Getting input from file $cups_filename\n"; } } # Solaris LP spooler if ($spooler eq 'solaris') { # Get all command line parameters # $printer = # argv[0] # ($rargs[0] =~ m!^.*/([^/]+)$!); # $request_id = removeshellescapes($rargs[0]); # argv[1] # $user_name = removeshellescapes($rargs[1]); # argv[2] $jobtitle = removeshellescapes($rargs[2]); # argv[3] # $copies = removeshellescapes($rargs[3]); # argv[4] # handled by the # interface script $optstr .= removeshellescapes($rargs[4]); # argv[5] ($#rargs > 4) && # argv[6...] (@filelist = @rargs[5, $#rargs]); } # LPD/LPRng/GNUlpr if (($spooler eq 'lpd') || ($spooler eq 'lprng' and !$ppdfile) || ($spooler eq 'gnulpr')) { # Get PPD file name as the last command line argument $ppdfile = removeshellescapes($rargs[$#rargs]); } # No spooler, CPS, or PDQ if (($spooler eq 'direct') || ($spooler eq 'cps') || ($spooler eq 'pdq')) { # Which files do we want to print? @filelist = map { removeshellescapes($_) } @rargs; } ## Additional spooler-specific preparations # CUPS if ($spooler eq 'cups') { # This piece of PostScript code (initial idea 2001 by Michael # Allerhand (michael.allerhand at ed dot ac dot uk, vastly # improved by Till Kamppeter in 2002) lets GhostScript output # the page accounting information which CUPS needs on standard # error. # Redesign by Helge Blischke (2004-11-17): # - As the PostScript job itself may define BeginPage and/or EndPage # procedures, or the alternate pstops filter may have inserted # such procedures, we make sure that the accounting routine # will safely coexist with those. To achieve this, we force # - the accountint stuff to be inserted at the very end of the # PostScript job's setup section, # - the accounting stuff just using the return value of the # existing EndPage procedure, if any (and providing a default one # if not). # - As PostScript jobs may contain calls to setpagedevice "between" # pages, e.g. to change media type, do in-job stapling, etc., # we cannot rely on the "showpage count since last pagedevice # activation" but instead count the physical pages by ourselves # (in a global dictionary). if (defined $conf{ps_accounting}) { $ps_accounting = $conf{ps_accounting}; } $accounting_prolog = $ps_accounting ? "[{ %% Code for writing CUPS accounting tags on standard error /cupsPSLevel2 % Determine whether we can do PostScript level 2 or newer systemdict/languagelevel 2 copy known{get exec}{pop pop 1}ifelse 2 ge def cupsPSLevel2 { % in case of level 2 or higher currentglobal true setglobal % define a dictioary foomaticDict globaldict begin % in global VM and establish a /foomaticDict % pages count key there << /PhysPages 0 >>def end setglobal }if /cupsGetNumCopies { % Read the number of Copies requested for the current % page cupsPSLevel2 { % PS Level 2+: Get number of copies from Page Device dictionary currentpagedevice /NumCopies get } { % PS Level 1: Number of copies not in Page Device dictionary null } ifelse % Check whether the number is defined, if it is \"null\" use #copies % instead dup null eq { pop #copies } if % Check whether the number is defined now, if it is still \"null\" use 1 % instead dup null eq { pop 1 } if } bind def /cupsWrite { % write a string onto standard error (%stderr) (w) file exch writestring } bind def /cupsFlush % flush standard error to make it sort of unbuffered { (%stderr)(w)file flushfile }bind def cupsPSLevel2 { % In language level 2, we try to do something reasonable << /EndPage [ % start the array that becomes the procedure currentpagedevice/EndPage 2 copy known {get} % get the existing EndPage procedure {pop pop {exch pop 2 ne}bind}ifelse % there is none, define the default /exec load % make sure it will be executed, whatever it is /dup load % duplicate the result value { % true: a sheet gets printed, do accounting currentglobal true setglobal % switch to global VM ... foomaticDict begin % ... and access our special dictionary PhysPages 1 add % count the sheets printed (including this one) dup /PhysPages exch def % and save the value end % leave our dict exch setglobal % return to previous VM (PAGE: )cupsWrite % assemble and print the accounting string ... 16 string cvs cupsWrite % ... the sheet count ... ( )cupsWrite % ... a space ... cupsGetNumCopies % ... the number of copies ... 16 string cvs cupsWrite % ... (\\n)cupsWrite % ... a newline cupsFlush }/if load % false: current page gets discarded; do nothing ]cvx bind % make the array executable and apply bind >>setpagedevice } { % In language level 1, we do no accounting currently, as there is no global VM % the contents of which are undesturbed by save and restore. % If we may be sure that showpage never gets called inside a page related save / restore pair % we might implement an hack with showpage similar to the one above. }ifelse } stopped cleartomark " : ""; # On which queue are we printing? # CUPS gives the PPD file the same name as the printer queue, # so we can get the queue name from the name of the PPD file. $ppdfile =~ m!^(.*/)([^/]+)\.ppd$!; $printer = $2; } # No spooler, CPS, or PDQ if (($spooler eq 'direct') || ($spooler eq 'cps') || ($spooler eq 'pdq')) { # Path for personal Foomatic configuration my $user_default_path = "$ENV{'HOME'}/.foomatic"; if (!$ppdfile) { if (!$printer) { # No printer definition file selected, check whether we have a # default printer defined. for my $conf_file (("./.directconfig", "./directconfig", "./.config", "$user_default_path/direct/.config", "$user_default_path/direct.conf", "$configpath/direct/.config", "$configpath/direct.conf")) { if (open CONFIG, "< $conf_file") { while (my $line = ) { chomp $line; if ($line =~ /^default\s*:\s*([^:\s]+)\s*$/) { $printer = $1; last; } } close CONFIG; } if ($printer) { last; } } } # Neither in a config file nor on the command line a printer was # selected. if (!$printer) { rip_die("No printer definition (option \"-P \") " . "specified!", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } # Search for the PPD file # Search also common spooler-specific locations, this way a printer # configured under a certain spooler can also be used without # spooler if (-r $printer) { $ppdfile = $printer; # CPS can have the PPD in the spool directory } elsif (($spooler eq 'cps') && (-r "/var/spool/lpd/${printer}/${printer}.ppd")) { $ppdfile = "/var/spool/lpd/${printer}/${printer}.ppd"; } elsif (($spooler eq 'cps') && (-r "/var/local/spool/lpd/${printer}/${printer}.ppd")) { $ppdfile = "/var/local/spool/lpd/${printer}/${printer}.ppd"; } elsif (($spooler eq 'cps') && (-r "/var/local/lpd/${printer}/${printer}.ppd")) { $ppdfile = "/var/local/lpd/${printer}/${printer}.ppd"; } elsif (($spooler eq 'cps') && (-r "/var/spool/lpd/${printer}.ppd")) { $ppdfile = "/var/spool/lpd/${printer}.ppd"; } elsif (($spooler eq 'cps') && (-r "/var/local/spool/lpd/${printer}.ppd")) { $ppdfile = "/var/local/spool/lpd/${printer}.ppd"; } elsif (($spooler eq 'cps') && (-r "/var/local/lpd/${printer}.ppd")) { $ppdfile = "/var/local/lpd/${printer}.ppd"; } elsif (-r "${printer}.ppd") { # current dir $ppdfile = "${printer}.ppd"; } elsif (-r "$user_default_path/${printer}.ppd") { # user dir $ppdfile = "$user_default_path/${printer}.ppd"; } elsif (-r "$configpath/direct/${printer}.ppd") { # system dir $ppdfile = "$configpath/direct/${printer}.ppd"; } elsif (-r "$configpath/${printer}.ppd") { # system dir $ppdfile = "$configpath/${printer}.ppd"; } elsif (-r "/etc/cups/ppd/${printer}.ppd") { # CUPS config dir $ppdfile = "/etc/cups/ppd/${printer}.ppd"; } elsif (-r "/usr/local/etc/cups/ppd/${printer}.ppd") { $ppdfile = "/usr/local/etc/cups/ppd/${printer}.ppd"; } elsif (-r "/usr/share/ppr/PPDFiles/${printer}.ppd") { # PPR PPDs $ppdfile = "/usr/share/ppr/PPDFiles/${printer}.ppd"; } elsif (-r "/usr/local/share/ppr/PPDFiles/${printer}.ppd") { $ppdfile = "/usr/local/share/ppr/PPDFiles/${printer}.ppd"; } else { rip_die ("There is no readable PPD file for the printer " . "$printer, is it configured?", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } } } ## Files to be printed (can be more than one for spooler-less printing) # Empty file list -> print STDIN if ($#filelist < 0) { @filelist = (""); } # Check file list my $file; my $filecnt = 0; for $file (@filelist) { if ($file ne "") { if ($file =~ /^-/) { rip_die ("Invalid argument: $file", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); } elsif (! -r $file) { print $logh "File $file does not exist/is not readable\n"; splice(@filelist, $filecnt, 1); $filecnt --; } } $filecnt ++; } ## When we print without spooler or with CPS do not log onto STDERR unless ## the "-v" ('Verbose') is set or the debug mode is used if ((($spooler eq 'direct') || ($spooler eq 'cps') || ($genpdqfile)) && (!$verbose) && (!$debug)) { close $logh; open LOG, "> /dev/null"; $logh = *LOG; use IO::Handle; $logh->autoflush(1); } ## Start logging if (!$debug) { # If we are in debug mode, we do this earlier. print $logh "foomatic-rip version $ripversion running...\n"; # Print the command line only in debug mode, Mac OS X adds very many # options so that CUPS cannot handle the output of the command line # in its log files. If CUPS encounters a line with more than 1024 # characters sent into its log files, it aborts the job with an error. if (($debug) || ($spooler ne 'cups')) { print $logh "called with arguments: '", join("', '",@ARGV), "'\n"; } } ## PPD file # Load the PPD file and build a data structure for the renderer's # command line and the options open PPD, "< $ppdfile" || do { print $logh "error opening $ppdfile.\n"; rip_die ("Unable to open PPD file $ppdfile", $EXIT_PRNERR_NORETRY_BAD_SETTINGS); }; print $logh "Parsing PPD file ...\n"; my $dat = {}; # data structure for the options my $currentargument = ""; # We are currently reading this argument # If we have an old Foomatic 2.0.x PPD file, read its built-in Perl # data structure into @datablob and the default values in %ppddefaults # Then delete the $dat structure, replace it by the one "eval"ed from # @datablob, and correct the default settings according to the ones of # the main PPD structure my @datablob; my $jclprefixset = 0; # Parse the PPD file sub undossify( $ ); while() { # foomatic-rip should also work with PPD file downloaded under Windows. $_ = undossify($_); # Parse keywords if (m!^\*NickName:\s*\"(.*)$!) { # "*NickName: " my $line = $1; # Store the value # Code string can have multiple lines, read all of them my $cmd = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $cmd .= substr($line, 0, -2); } else { # line ends here $cmd .= "$line\n"; } # Read next line $line = ; chomp $line; } $line =~ m!^([^\"]*)\"!; $cmd .= $1; $model = unhtmlify($cmd); } elsif (m!^\*FoomaticIDs:\s*(\S+)\s+(\S+)\s*$!) { # "*FoomaticIDs: " my $id = $1; my $driver = $2; # Store the values $dat->{'id'} = $id; $dat->{'driver'} = $driver; } elsif (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) { # "*FoomaticRIPPostPipe: " my $line = $1; # Store the value # Code string can have multiple lines, read all of them my $cmd = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $cmd .= substr($line, 0, -2); } else { # line ends here $cmd .= "$line\n"; } # Read next line $line = ; chomp $line; } $line =~ m!^([^\"]*)\"!; $cmd .= $1; $postpipe = unhtmlify($cmd); } elsif (m!^\*FoomaticRIPCommandLine:\s*\"(.*)$!) { # "*FoomaticRIPCommandLine: " my $line = $1; # Store the value # Code string can have multiple lines, read all of them my $cmd = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $cmd .= substr($line, 0, -2); } else { # line ends here $cmd .= "$line\n"; } # Read next line $line = ; chomp $line; } $line =~ m!^([^\"]*)\"!; $cmd .= $1; $dat->{'cmd'} = unhtmlify($cmd); } elsif (m!^\*cupsFilter:\s*\"(.*)$!) { # "*cupsFilter: " my $line = $1; # Store the value # Code string can have multiple lines, read all of them my $cmd = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $cmd .= substr($line, 0, -2); } else { # line ends here $cmd .= "$line\n"; } # Read next line $line = ; chomp $line; } $line =~ m!^([^\"]*)\"!; $cmd .= $1; my $cupsfilterline = unhtmlify($cmd); if ($cupsfilterline =~ /^\s*(\S+)\s+\d+\s+(\S+)\s*$/) { print $logh "*cupsFilter: \"$cupsfilterline\"\n"; # Make a hash by mime type for all CUPS filters set in this PPD $dat->{'cupsfilter'}{$1} = $2; } } elsif (m!^\*CustomPageSize\s+True:\s*\"(.*)$!) { # "*CustomPageSize True: " my $setting = "Custom"; my $translation = "Custom Size"; my $line = $1; # Make sure that the argument is in the data structure checkarg ($dat, "PageSize"); checkarg ($dat, "PageRegion"); # Make sure that the setting is in the data structure checksetting ($dat, "PageSize", $setting); checksetting ($dat, "PageRegion", $setting); $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'comment'} = $translation; $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'comment'} = $translation; # Store the value # Code string can have multiple lines, read all of them my $code = ""; while ($line !~ m!\"!) { if ($line =~ m!&&$!) { # line continues in next line $code .= substr($line, 0, -2); } else { # line ends here $code .= "$line\n"; } # Read next line $line = ; chomp $line; } $line =~ m!^([^\"]*)\"!; $code .= $1; if ($code !~ m!^%% FoomaticRIPOptionSetting!m) { $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'driverval'} = $code; $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'driverval'} = $code; } } elsif (m!^\*(JCL|)OpenUI\s+\*([^:]+):\s*(\S+)\s*$!) { # "*[JCL]OpenUI *