1 /* spooler.c
2  *
3  * Copyright (C) 2008 Till Kamppeter <till.kamppeter@gmail.com>
4  * Copyright (C) 2008 Lars Uebernickel <larsuebernickel@gmx.de>
5  *
6  * This file is part of foomatic-rip.
7  *
8  * Foomatic-rip is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * Foomatic-rip is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23 
24 #include "spooler.h"
25 #include "foomaticrip.h"
26 #include "util.h"
27 #include "options.h"
28 #include <stdlib.h>
29 #include <unistd.h>
30 
spooler_name(int spooler)31 const char *spooler_name(int spooler)
32 {
33     switch (spooler) {
34         case SPOOLER_CUPS: return "cups";
35         case SPOOLER_SOLARIS: return "solaris";
36         case SPOOLER_LPD: return "lpd";
37         case SPOOLER_LPRNG: return "lprng";
38         case SPOOLER_GNULPR: return "gnu lpr";
39         case SPOOLER_PPR: return "ppr";
40         case SPOOLER_PPR_INT: return "ppr interface";
41         case SPOOLER_CPS: return "cps";
42         case SPOOLER_PDQ: return "pdq";
43         case SPOOLER_DIRECT: return "direct";
44     };
45     return "<unknown>";
46 }
47 
48 /*  This piece of PostScript code (initial idea 2001 by Michael
49     Allerhand (michael.allerhand at ed dot ac dot uk, vastly
50     improved by Till Kamppeter in 2002) lets Ghostscript output
51     the page accounting information which CUPS needs on standard
52     error.
53     Redesign by Helge Blischke (2004-11-17):
54     - As the PostScript job itself may define BeginPage and/or EndPage
55     procedures, or the alternate pstops filter may have inserted
56     such procedures, we make sure that the accounting routine
57     will safely coexist with those. To achieve this, we force
58     - the accountint stuff to be inserted at the very end of the
59         PostScript job's setup section,
60     - the accounting stuff just using the return value of the
61         existing EndPage procedure, if any (and providing a default one
62         if not).
63     - As PostScript jobs may contain calls to setpagedevice "between"
64     pages, e.g. to change media type, do in-job stapling, etc.,
65     we cannot rely on the "showpage count since last pagedevice
66     activation" but instead count the physical pages by ourselves
67     (in a global dictionary).
68 */
69 const char *accounting_prolog_code =
70     "[{\n"
71     "%% Code for writing CUPS accounting tags on standard error\n"
72     "\n"
73     "/cupsPSLevel2 % Determine whether we can do PostScript level 2 or newer\n"
74     "    systemdict/languagelevel 2 copy\n"
75     "    known{get exec}{pop pop 1}ifelse 2 ge\n"
76     "def\n"
77     "\n"
78     "cupsPSLevel2\n"
79     "{                    % in case of level 2 or higher\n"
80     "    currentglobal true setglobal    % define a dictioary foomaticDict\n"
81     "    globaldict begin        % in global VM and establish a\n"
82     "    /foomaticDict            % pages count key there\n"
83     "    <<\n"
84     "        /PhysPages 0\n"
85     "    >>def\n"
86     "    end\n"
87     "    setglobal\n"
88     "}if\n"
89     "\n"
90     "/cupsGetNumCopies { % Read the number of Copies requested for the current\n"
91     "            % page\n"
92     "    cupsPSLevel2\n"
93     "    {\n"
94     "    % PS Level 2+: Get number of copies from Page Device dictionary\n"
95     "    currentpagedevice /NumCopies get\n"
96     "    }\n"
97     "    {\n"
98     "    % PS Level 1: Number of copies not in Page Device dictionary\n"
99     "    null\n"
100     "    }\n"
101     "    ifelse\n"
102     "    % Check whether the number is defined, if it is \"null\" use #copies \n"
103     "    % instead\n"
104     "    dup null eq {\n"
105     "    pop #copies\n"
106     "    }\n"
107     "    if\n"
108     "    % Check whether the number is defined now, if it is still \"null\" use 1\n"
109     "    % instead\n"
110     "    dup null eq {\n"
111     "    pop 1\n"
112     "    } if\n"
113     "} bind def\n"
114     "\n"
115     "/cupsWrite { % write a string onto standard error\n"
116     "    (%stderr) (w) file\n"
117     "    exch writestring\n"
118     "} bind def\n"
119     "\n"
120     "/cupsFlush    % flush standard error to make it sort of unbuffered\n"
121     "{\n"
122     "    (%stderr)(w)file flushfile\n"
123     "}bind def\n"
124     "\n"
125     "cupsPSLevel2\n"
126     "{                % In language level 2, we try to do something reasonable\n"
127     "  <<\n"
128     "    /EndPage\n"
129     "    [                    % start the array that becomes the procedure\n"
130     "      currentpagedevice/EndPage 2 copy known\n"
131     "      {get}                    % get the existing EndPage procedure\n"
132     "      {pop pop {exch pop 2 ne}bind}ifelse    % there is none, define the default\n"
133     "      /exec load                % make sure it will be executed, whatever it is\n"
134     "      /dup load                    % duplicate the result value\n"
135     "      {                    % true: a sheet gets printed, do accounting\n"
136     "        currentglobal true setglobal        % switch to global VM ...\n"
137     "        foomaticDict begin            % ... and access our special dictionary\n"
138     "        PhysPages 1 add            % count the sheets printed (including this one)\n"
139     "        dup /PhysPages exch def        % and save the value\n"
140     "        end                    % leave our dict\n"
141     "        exch setglobal                % return to previous VM\n"
142     "        (PAGE: )cupsWrite             % assemble and print the accounting string ...\n"
143     "        16 string cvs cupsWrite            % ... the sheet count ...\n"
144     "        ( )cupsWrite                % ... a space ...\n"
145     "        cupsGetNumCopies             % ... the number of copies ...\n"
146     "        16 string cvs cupsWrite            % ...\n"
147     "        (\\n)cupsWrite                % ... a newline\n"
148     "        cupsFlush\n"
149     "      }/if load\n"
150     "                    % false: current page gets discarded; do nothing    \n"
151     "    ]cvx bind                % make the array executable and apply bind\n"
152     "  >>setpagedevice\n"
153     "}\n"
154     "{\n"
155     "    % In language level 1, we do no accounting currently, as there is no global VM\n"
156     "    % the contents of which are undesturbed by save and restore. \n"
157     "    % If we may be sure that showpage never gets called inside a page related save / restore pair\n"
158     "    % we might implement an hack with showpage similar to the one above.\n"
159     "}ifelse\n"
160     "\n"
161     "} stopped cleartomark\n";
162 
163 
init_ppr(list_t * arglist,jobparams_t * job)164 void init_ppr(list_t *arglist, jobparams_t *job)
165 {
166     size_t arg_count = list_item_count(arglist);
167     char ppr_printer [256];
168     char ppr_address [128];
169     char ppr_options [1024];
170     char ppr_jobbreak [128];
171     char ppr_feedback [128];
172     char ppr_codes [128];
173     char ppr_jobname [128];
174     char ppr_routing [128];
175     char ppr_for [128] = "";
176     char ppr_filetype [128] = "";
177     char ppr_filetoprint [128] = "";
178     FILE *ph;
179     char tmp[256];
180     char *p;
181 
182 
183     /* TODO read interface.sh and signal.sh for exit and signal codes respectively */
184 
185     /* Check whether we run as a PPR interface (if not, we run as a PPR RIP)
186        PPR calls interfaces with many command line parameters,
187        where the forth and the sixth is a small integer
188        number. In addition, we have 8 (PPR <= 1.31), 10
189        (PPR>=1.32), 11 (PPR >= 1.50) command line parameters.
190        We also check whether the current working directory is a
191        PPR directory. */
192     if ((arg_count == 11 || arg_count == 10 || arg_count == 8) &&
193         atoi(arglist_get(arglist, 3)) < 100 && atoi(arglist_get(arglist, 5)) < 100)
194     {
195         /* get all command line parameters */
196         strncpy_omit(ppr_printer, arglist_get(arglist, 0), 256, omit_shellescapes);
197         strlcpy(ppr_address, arglist_get(arglist, 1), 128);
198         strncpy_omit(ppr_options, arglist_get(arglist, 2), 1024, omit_shellescapes);
199         strlcpy(ppr_jobbreak, arglist_get(arglist, 3), 128);
200         strlcpy(ppr_feedback, arglist_get(arglist, 4), 128);
201         strlcpy(ppr_codes, arglist_get(arglist, 5), 128);
202         strncpy_omit(ppr_jobname, arglist_get(arglist, 6), 128, omit_shellescapes);
203         strncpy_omit(ppr_routing, arglist_get(arglist, 7), 128, omit_shellescapes);
204         if (arg_count >= 8) {
205             strlcpy(ppr_for, arglist_get(arglist, 8), 128);
206             strlcpy(ppr_filetype, arglist_get(arglist, 9), 128);
207             if (arg_count >= 10)
208                 strncpy_omit(ppr_filetoprint, arglist_get(arglist, 10), 128, omit_shellescapes);
209         }
210 
211         /* Common job parameters */
212         strcpy(job->printer, ppr_printer);
213         strcpy(job->title, ppr_jobname);
214         if (isempty(job->title) && !isempty(ppr_filetoprint))
215             strcpy(job->title, ppr_filetoprint);
216         dstrcatf(job->optstr, " %s %s", ppr_options, ppr_routing);
217 
218         /* Get the path of the PPD file from the queue configuration */
219         snprintf(tmp, 255, "LANG=en_US; ppad show %s | grep PPDFile", ppr_printer);
220         tmp[255] = '\0';
221         ph = popen(tmp, "r");
222         if (ph) {
223             fgets(tmp, 255, ph);
224             tmp[255] = '\0';
225             pclose(ph);
226 
227             strncpy_omit(job->ppdfile, tmp, 255, omit_shellescapes);
228             if (job->ppdfile[0] == '/') {
229                 strcpy(tmp, job->ppdfile);
230                 strcpy(job->ppdfile, "../../share/ppr/PPDFiles/");
231                 strncat(job->ppdfile, tmp, 200);
232             }
233             if ((p = strrchr(job->ppdfile, '\n')))
234                 *p = '\0';
235         }
236         else {
237             job->ppdfile[0] = '\0';
238         }
239 
240         /* We have PPR and run as an interface */
241         spooler = SPOOLER_PPR_INT;
242     }
243 }
244 
init_cups(list_t * arglist,dstr_t * filelist,jobparams_t * job)245 void init_cups(list_t *arglist, dstr_t *filelist, jobparams_t *job)
246 {
247     char path [1024] = "";
248     char cups_jobid [128];
249     char cups_user [128];
250     char cups_jobtitle [128];
251     char cups_copies [128];
252     int cups_options_len;
253     char *cups_options;
254     char cups_filename [256];
255     char texttopspath[PATH_MAX];
256 
257     if (getenv("CUPS_FONTPATH"))
258         strcpy(path, getenv("CUPS_FONTPATH"));
259     else if (getenv("CUPS_DATADIR")) {
260         strcpy(path, getenv("CUPS_DATADIR"));
261         strcat(path, "/fonts");
262     }
263     if (getenv("GS_LIB")) {
264         strcat(path, ":");
265         strcat(path, getenv("GS_LIB"));
266     }
267     setenv("GS_LIB", path, 1);
268 
269     /* Get all command line parameters */
270     strncpy_omit(cups_jobid, arglist_get(arglist, 0), 128, omit_shellescapes);
271     strncpy_omit(cups_user, arglist_get(arglist, 1), 128, omit_shellescapes);
272     strncpy_omit(cups_jobtitle, arglist_get(arglist, 2), 128, omit_shellescapes);
273     strncpy_omit(cups_copies, arglist_get(arglist, 3), 128, omit_shellescapes);
274 
275     cups_options_len = strlen(arglist_get(arglist, 4));
276     cups_options = malloc(cups_options_len + 1);
277     strncpy_omit(cups_options, arglist_get(arglist, 4), cups_options_len + 1, omit_shellescapes);
278 
279     /* Common job parameters */
280     strcpy(job->id, cups_jobid);
281     strcpy(job->title, cups_jobtitle);
282     strcpy(job->user, cups_user);
283     strcpy(job->copies, cups_copies);
284     dstrcatf(job->optstr, " %s", cups_options);
285 
286     /* Check for and handle inputfile vs stdin */
287     if (list_item_count(arglist) > 4) {
288         strncpy_omit(cups_filename, arglist_get(arglist, 5), 256, omit_shellescapes);
289         if (cups_filename[0] != '-') {
290             /* We get input from a file */
291             dstrcatf(filelist, "%s ", cups_filename);
292             _log("Getting input from file %s\n", cups_filename);
293         }
294     }
295 
296     accounting_prolog = accounting_prolog_code;
297 
298     /* On which queue are we printing?
299        CUPS gives the PPD file the same name as the printer queue,
300        so we can get the queue name from the name of the PPD file. */
301     file_basename(job->printer, job->ppdfile, 256);
302 
303     /* Use cups' texttops if no fileconverter is set
304      * Apply "pstops" when having used a file converter under CUPS, so CUPS
305      * can stuff the default settings into the PostScript output of the file
306      * converter (so all CUPS settings get also applied when one prints the
307      * documentation pages (all other files we get already converted to
308      * PostScript by CUPS). */
309     if (isempty(fileconverter)) {
310         if (find_in_path("texttops", cupsfilterpath, texttopspath)) {
311             snprintf(fileconverter, PATH_MAX, "%s/texttops '%s' '%s' '%s' '%s' "
312                     "page-top=36 page-bottom=36 page-left=36 page-right=36 "
313                     "nolandscape cpi=12 lpi=7 columns=1 wrap %s'"
314                     "| %s/pstops  '%s' '%s' '%s' '%s' '%s'",
315                     texttopspath, cups_jobid, cups_user, cups_jobtitle, cups_copies, cups_options,
316                     texttopspath, cups_jobid, cups_user, cups_jobtitle, cups_copies, cups_options);
317         }
318     }
319 
320     free(cups_options);
321 }
322 
init_solaris(list_t * arglist,dstr_t * filelist,jobparams_t * job)323 void init_solaris(list_t *arglist, dstr_t *filelist, jobparams_t *job)
324 {
325     char *str;
326     int len;
327     listitem_t *i;
328 
329     /* Get all command line parameters */
330     strncpy_omit(job->title, arglist_get(arglist, 2), 128, omit_shellescapes);
331 
332     len = strlen(arglist_get(arglist, 4));
333     str = malloc(len +1);
334     strncpy_omit(str, arglist_get(arglist, 4), len, omit_shellescapes);
335     dstrcatf(job->optstr, " %s", str);
336     free(str);
337 
338     for (i = arglist->first; i; i = i->next)
339         dstrcatf(filelist, "%s ", (char*)i->data);
340 }
341 
342 /* used by init_direct_cps_pdq to find a ppd file */
find_ppdfile(const char * user_default_path,jobparams_t * job)343 int find_ppdfile(const char *user_default_path, jobparams_t *job)
344 {
345     /* Search also common spooler-specific locations, this way a printer
346        configured under a certain spooler can also be used without spooler */
347 
348     strcpy(job->ppdfile, job->printer);
349     if (access(job->ppdfile, R_OK) == 0)
350         return 1;
351 
352     /* CPS can have the PPD in the spool directory */
353     if (spooler == SPOOLER_CPS) {
354         snprintf(job->ppdfile, 256, "/var/spool/lpd/%s/%s.ppd", job->printer, job->printer);
355         if (access(job->ppdfile, R_OK) == 0)
356             return 1;
357         snprintf(job->ppdfile, 256, "/var/local/spool/lpd/%s/%s.ppd", job->printer, job->printer);
358         if (access(job->ppdfile, R_OK) == 0)
359             return 1;
360         snprintf(job->ppdfile, 256, "/var/local/lpd/%s/%s.ppd", job->printer, job->printer);
361         if (access(job->ppdfile, R_OK) == 0)
362             return 1;
363         snprintf(job->ppdfile, 256, "/var/spool/lpd/%s.ppd", job->printer);
364         if (access(job->ppdfile, R_OK) == 0)
365             return 1;
366         snprintf(job->ppdfile, 256, "/var/local/spool/lpd/%s.ppd", job->printer);
367         if (access(job->ppdfile, R_OK) == 0)
368             return 1;
369         snprintf(job->ppdfile, 256, "/var/local/lpd/%s.ppd", job->printer);
370         if (access(job->ppdfile, R_OK) == 0)
371             return 1;
372     }
373     snprintf(job->ppdfile, 256, "%s.ppd", job->printer); /* current dir */
374     if (access(job->ppdfile, R_OK) == 0)
375         return 1;
376     snprintf(job->ppdfile, 256, "%s/%s.ppd", user_default_path, job->printer); /* user dir */
377     if (access(job->ppdfile, R_OK) == 0)
378         return 1;
379     snprintf(job->ppdfile, 256, "%s/direct/%s.ppd", CONFIG_PATH, job->printer); /* system dir */
380     if (access(job->ppdfile, R_OK) == 0)
381         return 1;
382     snprintf(job->ppdfile, 256, "%s/%s.ppd", CONFIG_PATH, job->printer); /* system dir */
383     if (access(job->ppdfile, R_OK) == 0)
384         return 1;
385     snprintf(job->ppdfile, 256, "/etc/cups/ppd/%s.ppd", job->printer); /* CUPS config dir */
386     if (access(job->ppdfile, R_OK) == 0)
387         return 1;
388     snprintf(job->ppdfile, 256, "/usr/local/etc/cups/ppd/%s.ppd", job->printer); /* CUPS config dir */
389     if (access(job->ppdfile, R_OK) == 0)
390         return 1;
391     snprintf(job->ppdfile, 256, "/usr/share/ppr/PPDFiles/%s.ppd", job->printer); /* PPR PPDs */
392     if (access(job->ppdfile, R_OK) == 0)
393         return 1;
394     snprintf(job->ppdfile, 256, "/usr/local/share/ppr/PPDFiles/%s.ppd", job->printer); /* PPR PPDs */
395     if (access(job->ppdfile, R_OK) == 0)
396         return 1;
397 
398     /* nothing found */
399     job->ppdfile[0] = '\0';
400     return 0;
401 }
402 
403 /* search 'configfile' for 'key', copy value into dest, return success */
configfile_find_option(const char * configfile,const char * key,char * dest,size_t destsize)404 int configfile_find_option(const char *configfile, const char *key, char *dest, size_t destsize)
405 {
406     FILE *fh;
407     char line [1024];
408     char *p;
409 
410     dest[0] = '\0';
411 
412     if (!(fh = fopen(configfile, "r")))
413         return 0;
414 
415     while (fgets(line, 1024, fh)) {
416         if (!prefixcmp(line, "default")) {
417             p = strchr(line, ':');
418             if (p) {
419                 strncpy_omit(dest, p + 1, destsize, omit_whitespace_newline);
420                 if (dest[0])
421                     break;
422             }
423         }
424     }
425     fclose(fh);
426     return dest[0] != '\0';
427 }
428 
429 /* tries to find a default printer name in various config files and copies the
430  * result into the global var 'printer'. Returns success */
find_default_printer(const char * user_default_path,jobparams_t * job)431 int find_default_printer(const char *user_default_path, jobparams_t *job)
432 {
433     char configfile [1024];
434     char *key = "default";
435 
436     if (configfile_find_option("./.directconfig", key, job->printer, 256))
437         return 1;
438     if (configfile_find_option("./directconfig", key, job->printer, 256))
439         return 1;
440     if (configfile_find_option("./.config", key, job->printer, 256))
441         return 1;
442     strlcpy(configfile, user_default_path, 1024);
443     strlcat(configfile, "/direct/.config", 1024);
444     if (configfile_find_option(configfile, key, job->printer, 256))
445         return 1;
446     strlcpy(configfile, user_default_path, 1024);
447     strlcat(configfile, "/direct.conf", 1024);
448     if (configfile_find_option(configfile, key, job->printer, 256))
449         return 1;
450     if (configfile_find_option(CONFIG_PATH "/direct/.config", key, job->printer, 256))
451         return 1;
452     if (configfile_find_option(CONFIG_PATH "/direct.conf", key, job->printer, 256))
453         return 1;
454 
455     return 0;
456 }
457 
init_direct_cps_pdq(list_t * arglist,dstr_t * filelist,jobparams_t * job)458 void init_direct_cps_pdq(list_t *arglist, dstr_t *filelist, jobparams_t *job)
459 {
460     char tmp [1024];
461     listitem_t *i;
462     char user_default_path [PATH_MAX];
463 
464     strlcpy(user_default_path, getenv("HOME"), 256);
465     strlcat(user_default_path, "/.foomatic/", 256);
466 
467     /* Which files do we want to print? */
468     for (i = arglist->first; i; i = i->next) {
469         strncpy_omit(tmp, (char*)i->data, 1024, omit_shellescapes);
470         dstrcatf(filelist, "%s ", tmp);
471     }
472 
473     if (job->ppdfile[0] == '\0') {
474         if (job->printer[0] == '\0') {
475             /* No printer definition file selected, check whether we have a
476                default printer defined */
477             find_default_printer(user_default_path, job);
478         }
479 
480         /* Neither in a config file nor on the command line a printer was selected */
481         if (!job->printer[0]) {
482             _log("No printer definition (option \"-P <name>\") specified!\n");
483             exit(EXIT_PRNERR_NORETRY_BAD_SETTINGS);
484         }
485 
486         /* Search for the PPD file */
487         if (!find_ppdfile(user_default_path, job)) {
488             _log("There is no readable PPD file for the printer %s, is it configured?\n", job->printer);
489             exit(EXIT_PRNERR_NORETRY_BAD_SETTINGS);
490         }
491     }
492 }
493 
494