]> git.ipfire.org Git - thirdparty/cups.git/blob - scheduler/cupsfilter.c
Update all references to OS X to macOS.
[thirdparty/cups.git] / scheduler / cupsfilter.c
1 /*
2 * Filtering program for CUPS.
3 *
4 * Copyright 2007-2014 by Apple Inc.
5 * Copyright 1997-2006 by Easy Software Products, all rights reserved.
6 *
7 * These coded instructions, statements, and computer programs are the
8 * property of Apple Inc. and are protected by Federal copyright
9 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
10 * which should have been included with this file. If this file is
11 * file is missing or damaged, see the license at "http://www.cups.org/".
12 */
13
14 /*
15 * Include necessary headers...
16 */
17
18 #include <cups/cups-private.h>
19 #include <cups/file-private.h>
20 #include <cups/ppd-private.h>
21 #include "mime.h"
22 #include <limits.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <signal.h>
26 #include <sys/wait.h>
27 #if defined(__APPLE__)
28 # include <libgen.h>
29 #endif /* __APPLE__ */
30
31
32 /*
33 * Local globals...
34 */
35
36 static char *DataDir = NULL;/* CUPS_DATADIR environment variable */
37 static char *FontPath = NULL;
38 /* CUPS_FONTPATH environment variable */
39 static mime_filter_t GZIPFilter = /* gziptoany filter */
40 {
41 NULL, /* Source type */
42 NULL, /* Destination type */
43 0, /* Cost */
44 "gziptoany" /* Filter program to run */
45 };
46 static char *Path = NULL; /* PATH environment variable */
47 static char *ServerBin = NULL;
48 /* CUPS_SERVERBIN environment variable */
49 static char *ServerRoot = NULL;
50 /* CUPS_SERVERROOT environment variable */
51 static char *RIPCache = NULL;
52 /* RIP_MAX_CACHE environment variable */
53 static char TempFile[1024] = "";
54 /* Temporary file */
55
56
57 /*
58 * Local functions...
59 */
60
61 static void add_printer_filter(const char *command, mime_t *mime,
62 mime_type_t *printer_type,
63 const char *filter);
64 static mime_type_t *add_printer_filters(const char *command,
65 mime_t *mime, const char *printer,
66 const char *ppdfile,
67 mime_type_t **prefilter_type);
68 static void check_cb(void *context, _cups_fc_result_t result,
69 const char *message);
70 static int compare_pids(mime_filter_t *a, mime_filter_t *b);
71 static char *escape_options(int num_options, cups_option_t *options);
72 static int exec_filter(const char *filter, char **argv,
73 char **envp, int infd, int outfd);
74 static int exec_filters(mime_type_t *srctype,
75 cups_array_t *filters, const char *infile,
76 const char *outfile, const char *ppdfile,
77 const char *printer, const char *user,
78 const char *title, int num_options,
79 cups_option_t *options);
80 static void get_job_file(const char *job);
81 static int open_pipe(int *fds);
82 static int read_cups_files_conf(const char *filename);
83 static void set_string(char **s, const char *val);
84 static void sighandler(int sig);
85 static void usage(const char *opt) __attribute__((noreturn));
86
87
88 /*
89 * 'main()' - Main entry for the test program.
90 */
91
92 int /* O - Exit status */
93 main(int argc, /* I - Number of command-line args */
94 char *argv[]) /* I - Command-line arguments */
95 {
96 int i, /* Looping vars */
97 list_filters = 0; /* Just list the filters? */
98 const char *command, /* Command name */
99 *opt, /* Current option */
100 *printer; /* Printer name */
101 mime_type_t *printer_type, /* Printer MIME type */
102 *prefilter_type; /* Printer prefilter MIME type */
103 char *srctype, /* Source type */
104 *dsttype, /* Destination type */
105 super[MIME_MAX_SUPER], /* Super-type name */
106 type[MIME_MAX_TYPE]; /* Type name */
107 int compression; /* Compression of file */
108 int cost; /* Cost of filters */
109 mime_t *mime; /* MIME database */
110 char mimedir[1024]; /* MIME directory */
111 char *infile, /* File to filter */
112 *outfile; /* File to create */
113 char cupsfilesconf[1024]; /* cups-files.conf file */
114 const char *server_root; /* CUPS_SERVERROOT environment variable */
115 mime_type_t *src, /* Source type */
116 *dst; /* Destination type */
117 cups_array_t *filters; /* Filters for the file */
118 int num_options; /* Number of options */
119 cups_option_t *options; /* Options */
120 const char *ppdfile; /* PPD file */
121 const char *title, /* Title string */
122 *user; /* Username */
123 int all_filters, /* Use all filters */
124 removeppd, /* Remove PPD file */
125 removeinfile; /* Remove input file */
126 int status; /* Execution status */
127
128
129 /*
130 * Setup defaults...
131 */
132
133 if ((command = strrchr(argv[0], '/')) != NULL)
134 command ++;
135 else
136 command = argv[0];
137
138 printer = !strcmp(command, "convert") ? "tofile" : "cupsfilter";
139 mime = NULL;
140 srctype = NULL;
141 compression = 0;
142 dsttype = "application/pdf";
143 infile = NULL;
144 outfile = NULL;
145 num_options = 0;
146 options = NULL;
147 ppdfile = NULL;
148 title = NULL;
149 user = cupsUser();
150 all_filters = 0;
151 removeppd = 0;
152 removeinfile = 0;
153
154 if ((server_root = getenv("CUPS_SERVERROOT")) == NULL)
155 server_root = CUPS_SERVERROOT;
156
157 snprintf(cupsfilesconf, sizeof(cupsfilesconf), "%s/cups-files.conf", server_root);
158
159 /*
160 * Process command-line arguments...
161 */
162
163 _cupsSetLocale(argv);
164
165 for (i = 1; i < argc; i ++)
166 {
167 if (argv[i][0] == '-')
168 {
169 if (!strcmp(argv[i], "--list-filters"))
170 {
171 list_filters = 1;
172 }
173 else if (!strcmp(argv[i], "--"))
174 {
175 i ++;
176 if (i < argc && !infile)
177 infile = argv[i];
178 else
179 usage(NULL);
180 }
181 else
182 {
183 for (opt = argv[i] + 1; *opt; opt ++)
184 {
185 switch (*opt)
186 {
187 case 'a' : /* Specify option... */
188 i ++;
189 if (i < argc)
190 num_options = cupsParseOptions(argv[i], num_options, &options);
191 else
192 usage(opt);
193 break;
194
195 case 'c' : /* Specify cups-files.conf file location... */
196 i ++;
197 if (i < argc)
198 {
199 if (!strcmp(command, "convert"))
200 num_options = cupsAddOption("copies", argv[i], num_options, &options);
201 else
202 strlcpy(cupsfilesconf, argv[i], sizeof(cupsfilesconf));
203 }
204 else
205 usage(opt);
206 break;
207
208 case 'd' : /* Specify the real printer name */
209 i ++;
210 if (i < argc)
211 printer = argv[i];
212 else
213 usage(opt);
214 break;
215
216 case 'D' : /* Delete input file after conversion */
217 removeinfile = 1;
218 break;
219
220 case 'e' : /* Use every filter from the PPD file */
221 all_filters = 1;
222 break;
223
224 case 'f' : /* Specify input file... */
225 i ++;
226 if (i < argc && !infile)
227 infile = argv[i];
228 else
229 usage(opt);
230 break;
231
232 case 'i' : /* Specify source MIME type... */
233 i ++;
234 if (i < argc)
235 {
236 if (sscanf(argv[i], "%15[^/]/%255s", super, type) != 2)
237 usage(opt);
238
239 srctype = argv[i];
240 }
241 else
242 usage(opt);
243 break;
244
245 case 'j' : /* Get job file or specify destination MIME type... */
246 if (strcmp(command, "convert"))
247 {
248 i ++;
249 if (i < argc)
250 {
251 get_job_file(argv[i]);
252 infile = TempFile;
253 }
254 else
255 usage(opt);
256
257 break;
258 }
259
260 case 'm' : /* Specify destination MIME type... */
261 i ++;
262 if (i < argc)
263 {
264 if (sscanf(argv[i], "%15[^/]/%255s", super, type) != 2)
265 usage(opt);
266
267 dsttype = argv[i];
268 }
269 else
270 usage(opt);
271 break;
272
273 case 'n' : /* Specify number of copies... */
274 i ++;
275 if (i < argc)
276 num_options = cupsAddOption("copies", argv[i], num_options, &options);
277 else
278 usage(opt);
279 break;
280
281 case 'o' : /* Specify option(s) or output filename */
282 i ++;
283 if (i < argc)
284 {
285 if (!strcmp(command, "convert"))
286 {
287 if (outfile)
288 usage(NULL);
289 else
290 outfile = argv[i];
291 }
292 else
293 num_options = cupsParseOptions(argv[i], num_options, &options);
294 }
295 else
296 usage(opt);
297 break;
298
299 case 'p' : /* Specify PPD file... */
300 case 'P' : /* Specify PPD file... */
301 i ++;
302 if (i < argc)
303 ppdfile = argv[i];
304 else
305 usage(opt);
306 break;
307
308 case 't' : /* Specify title... */
309 case 'J' : /* Specify title... */
310 i ++;
311 if (i < argc)
312 title = argv[i];
313 else
314 usage(opt);
315 break;
316
317 case 'u' : /* Delete PPD file after conversion */
318 removeppd = 1;
319 break;
320
321 case 'U' : /* Specify username... */
322 i ++;
323 if (i < argc)
324 user = argv[i];
325 else
326 usage(opt);
327 break;
328
329 default : /* Something we don't understand... */
330 usage(opt);
331 break;
332 }
333 }
334 }
335 }
336 else if (!infile)
337 {
338 if (strcmp(command, "convert"))
339 infile = argv[i];
340 else
341 usage(NULL);
342 }
343 else
344 {
345 _cupsLangPuts(stderr,
346 _("cupsfilter: Only one filename can be specified."));
347 usage(NULL);
348 }
349 }
350
351 if (!infile && !srctype)
352 usage(NULL);
353
354 if (!title)
355 {
356 if (!infile)
357 title = "(stdin)";
358 else if ((title = strrchr(infile, '/')) != NULL)
359 title ++;
360 else
361 title = infile;
362 }
363
364 /*
365 * Load the cups-files.conf file and create the MIME database...
366 */
367
368 if (read_cups_files_conf(cupsfilesconf))
369 return (1);
370
371 snprintf(mimedir, sizeof(mimedir), "%s/mime", DataDir);
372
373 mime = mimeLoadTypes(NULL, mimedir);
374 mime = mimeLoadTypes(mime, ServerRoot);
375 mime = mimeLoadFilters(mime, mimedir, Path);
376 mime = mimeLoadFilters(mime, ServerRoot, Path);
377
378 if (!mime)
379 {
380 _cupsLangPrintf(stderr,
381 _("%s: Unable to read MIME database from \"%s\" or "
382 "\"%s\"."),
383 command, mimedir, ServerRoot);
384 return (1);
385 }
386
387 prefilter_type = NULL;
388
389 if (all_filters)
390 printer_type = add_printer_filters(command, mime, printer, ppdfile,
391 &prefilter_type);
392 else
393 printer_type = mimeType(mime, "application", "vnd.cups-postscript");
394
395 /*
396 * Get the source and destination types...
397 */
398
399 if (srctype)
400 {
401 /* sscanf return value already checked above */
402 sscanf(srctype, "%15[^/]/%255s", super, type);
403 if ((src = mimeType(mime, super, type)) == NULL)
404 {
405 _cupsLangPrintf(stderr,
406 _("%s: Unknown source MIME type %s/%s."),
407 command, super, type);
408 return (1);
409 }
410 }
411 else if ((src = mimeFileType(mime, infile, infile, &compression)) == NULL)
412 {
413 _cupsLangPrintf(stderr,
414 _("%s: Unable to determine MIME type of \"%s\"."),
415 command, infile);
416 return (1);
417 }
418
419 /* sscanf return value already checked above */
420 sscanf(dsttype, "%15[^/]/%255s", super, type);
421 if (!_cups_strcasecmp(super, "printer"))
422 dst = printer_type;
423 else if ((dst = mimeType(mime, super, type)) == NULL)
424 {
425 _cupsLangPrintf(stderr,
426 _("%s: Unknown destination MIME type %s/%s."),
427 command, super, type);
428 return (1);
429 }
430
431 /*
432 * Figure out how to filter the file...
433 */
434
435 if (src == dst)
436 {
437 /*
438 * Special case - no filtering needed...
439 */
440
441 filters = cupsArrayNew(NULL, NULL);
442 cupsArrayAdd(filters, &GZIPFilter);
443 GZIPFilter.src = src;
444 GZIPFilter.dst = dst;
445 }
446 else if ((filters = mimeFilter(mime, src, dst, &cost)) == NULL)
447 {
448 _cupsLangPrintf(stderr,
449 _("%s: No filter to convert from %s/%s to %s/%s."),
450 command, src->super, src->type, dst->super, dst->type);
451 return (1);
452 }
453 else if (compression)
454 cupsArrayInsert(filters, &GZIPFilter);
455
456 if (prefilter_type)
457 {
458 /*
459 * Add pre-filters...
460 */
461
462 mime_filter_t *filter, /* Current filter */
463 *prefilter; /* Current pre-filter */
464 cups_array_t *prefilters = cupsArrayNew(NULL, NULL);
465 /* New filters array */
466
467
468 for (filter = (mime_filter_t *)cupsArrayFirst(filters);
469 filter;
470 filter = (mime_filter_t *)cupsArrayNext(filters))
471 {
472 if ((prefilter = mimeFilterLookup(mime, filter->src,
473 prefilter_type)) != NULL)
474 cupsArrayAdd(prefilters, prefilter);
475
476 cupsArrayAdd(prefilters, filter);
477 }
478
479 cupsArrayDelete(filters);
480 filters = prefilters;
481 }
482
483 if (list_filters)
484 {
485 /*
486 * List filters...
487 */
488
489 mime_filter_t *filter; /* Current filter */
490
491 for (filter = (mime_filter_t *)cupsArrayFirst(filters);
492 filter;
493 filter = (mime_filter_t *)cupsArrayNext(filters))
494 if (strcmp(filter->filter, "-"))
495 _cupsLangPuts(stdout, filter->filter);
496
497 status = 0;
498 }
499 else
500 {
501 /*
502 * Run filters...
503 */
504
505 status = exec_filters(src, filters, infile, outfile, ppdfile, printer, user,
506 title, num_options, options);
507 }
508
509 /*
510 * Remove files as needed, then exit...
511 */
512
513 if (TempFile[0])
514 unlink(TempFile);
515
516 if (removeppd && ppdfile)
517 unlink(ppdfile);
518
519 if (removeinfile && infile)
520 unlink(infile);
521
522 return (status);
523 }
524
525
526 /*
527 * 'add_printer_filter()' - Add a single filters from a PPD file.
528 */
529
530 static void
531 add_printer_filter(
532 const char *command, /* I - Command name */
533 mime_t *mime, /* I - MIME database */
534 mime_type_t *filtertype, /* I - Printer or prefilter MIME type */
535 const char *filter) /* I - Filter to add */
536 {
537 char super[MIME_MAX_SUPER], /* Super-type for filter */
538 type[MIME_MAX_TYPE], /* Type for filter */
539 dsuper[MIME_MAX_SUPER], /* Destination super-type for filter */
540 dtype[MIME_MAX_TYPE], /* Destination type for filter */
541 dest[MIME_MAX_SUPER + MIME_MAX_TYPE + 2],
542 /* Destination super/type */
543 program[1024]; /* Program/filter name */
544 int cost; /* Cost of filter */
545 size_t maxsize = 0; /* Maximum supported file size */
546 mime_type_t *temptype, /* MIME type looping var */
547 *desttype; /* Destination MIME type */
548 mime_filter_t *filterptr; /* MIME filter */
549
550
551 /*
552 * Parse the filter string; it should be in one of the following formats:
553 *
554 * source/type cost program
555 * source/type cost maxsize(nnnn) program
556 * source/type dest/type cost program
557 * source/type dest/type cost maxsize(nnnn) program
558 */
559
560 if (sscanf(filter, "%15[^/]/%255s%*[ \t]%15[^/]/%255s%d%*[ \t]%1023[^\n]",
561 super, type, dsuper, dtype, &cost, program) == 6)
562 {
563 snprintf(dest, sizeof(dest), "%s/%s/%s", filtertype->type, dsuper, dtype);
564
565 if ((desttype = mimeType(mime, "printer", dest)) == NULL)
566 desttype = mimeAddType(mime, "printer", dest);
567 }
568 else
569 {
570 if (sscanf(filter, "%15[^/]/%255s%d%*[ \t]%1023[^\n]", super, type, &cost,
571 program) == 4)
572 {
573 desttype = filtertype;
574 }
575 else
576 {
577 _cupsLangPrintf(stderr, _("%s: Invalid filter string \"%s\"."), command,
578 filter);
579 return;
580 }
581 }
582
583 if (!strncmp(program, "maxsize(", 8))
584 {
585 char *ptr; /* Pointer into maxsize(nnnn) program */
586
587 maxsize = (size_t)strtoll(program + 8, &ptr, 10);
588
589 if (*ptr != ')')
590 {
591 printf("testmime: Invalid filter string \"%s\".\n", filter);
592 return;
593 }
594
595 ptr ++;
596 while (_cups_isspace(*ptr))
597 ptr ++;
598
599 _cups_strcpy(program, ptr);
600 }
601
602 /*
603 * See if the filter program exists; if not, stop the printer and flag
604 * the error!
605 */
606
607 if (strcmp(program, "-"))
608 {
609 char filename[1024]; /* Full path to program */
610
611 if (program[0] == '/')
612 strlcpy(filename, program, sizeof(filename));
613 else
614 snprintf(filename, sizeof(filename), "%s/filter/%s", ServerBin, program);
615
616 if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_PROGRAM, !geteuid(), check_cb,
617 (void *)command))
618 return;
619 }
620
621 /*
622 * Add the filter to the MIME database, supporting wildcards as needed...
623 */
624
625 for (temptype = mimeFirstType(mime);
626 temptype;
627 temptype = mimeNextType(mime))
628 if (((super[0] == '*' && _cups_strcasecmp(temptype->super, "printer")) ||
629 !_cups_strcasecmp(temptype->super, super)) &&
630 (type[0] == '*' || !_cups_strcasecmp(temptype->type, type)))
631 {
632 if (desttype != filtertype)
633 {
634 filterptr = mimeAddFilter(mime, temptype, desttype, cost, program);
635
636 if (!mimeFilterLookup(mime, desttype, filtertype))
637 mimeAddFilter(mime, desttype, filtertype, 0, "-");
638 }
639 else
640 filterptr = mimeAddFilter(mime, temptype, filtertype, cost, program);
641
642 if (filterptr)
643 filterptr->maxsize = maxsize;
644 }
645 }
646
647
648 /*
649 * 'add_printer_filters()' - Add filters from a PPD file.
650 */
651
652 static mime_type_t * /* O - Printer type or NULL on error */
653 add_printer_filters(
654 const char *command, /* I - Command name */
655 mime_t *mime, /* I - MIME database */
656 const char *printer, /* I - Printer name */
657 const char *ppdfile, /* I - PPD file */
658 mime_type_t **prefilter_type) /* O - Prefilter type */
659 {
660 ppd_file_t *ppd; /* PPD file data */
661 _ppd_cache_t *pc; /* Cache data for PPD */
662 const char *value; /* Filter definition value */
663 mime_type_t *printer_type; /* Printer filter type */
664
665
666 if ((ppd = _ppdOpenFile(ppdfile, _PPD_LOCALIZATION_NONE)) == NULL)
667 {
668 ppd_status_t status; /* PPD load status */
669 int linenum; /* Line number */
670
671 status = ppdLastError(&linenum);
672 _cupsLangPrintf(stderr, _("%s: Unable to open PPD file: %s on line %d."),
673 command, ppdErrorString(status), linenum);
674 return (NULL);
675 }
676
677 pc = _ppdCacheCreateWithPPD(ppd);
678 if (!pc)
679 return (NULL);
680
681 printer_type = mimeAddType(mime, "printer", printer);
682 *prefilter_type = NULL;
683
684 if (pc->filters)
685 {
686 for (value = (const char *)cupsArrayFirst(pc->filters);
687 value;
688 value = (const char *)cupsArrayNext(pc->filters))
689 add_printer_filter(command, mime, printer_type, value);
690 }
691 else
692 {
693 add_printer_filter(command, mime, printer_type,
694 "application/vnd.cups-raw 0 -");
695 add_printer_filter(command, mime, printer_type,
696 "application/vnd.cups-postscript 0 -");
697 }
698
699 if (pc->prefilters)
700 {
701 *prefilter_type = mimeAddType(mime, "prefilter", printer);
702
703 for (value = (const char *)cupsArrayFirst(pc->prefilters);
704 value;
705 value = (const char *)cupsArrayNext(pc->prefilters))
706 add_printer_filter(command, mime, *prefilter_type, value);
707 }
708
709 return (printer_type);
710 }
711
712
713 /*
714 * 'check_cb()' - Callback function for _cupsFileCheck.
715 */
716
717 static void
718 check_cb(void *context, /* I - Context (command name) */
719 _cups_fc_result_t result, /* I - Result of check */
720 const char *message) /* I - Localized message */
721 {
722 (void)result;
723
724 _cupsLangPrintf(stderr, _("%s: %s"), (char *)context, message);
725 }
726
727
728 /*
729 * 'compare_pids()' - Compare two filter PIDs...
730 */
731
732 static int /* O - Result of comparison */
733 compare_pids(mime_filter_t *a, /* I - First filter */
734 mime_filter_t *b) /* I - Second filter */
735 {
736 /*
737 * Because we're particularly lazy, we store the process ID in the "cost"
738 * variable...
739 */
740
741 return (a->cost - b->cost);
742 }
743
744
745 /*
746 * 'escape_options()' - Convert an options array to a string.
747 */
748
749 static char * /* O - Option string */
750 escape_options(
751 int num_options, /* I - Number of options */
752 cups_option_t *options) /* I - Options */
753 {
754 int i; /* Looping var */
755 cups_option_t *option; /* Current option */
756 size_t bytes; /* Number of bytes needed */
757 char *s, /* Option string */
758 *sptr, /* Pointer into string */
759 *vptr; /* Pointer into value */
760
761
762 /*
763 * Figure out the worst-case number of bytes we need for the option string.
764 */
765
766 for (i = num_options, option = options, bytes = 1; i > 0; i --, option ++)
767 bytes += 2 * (strlen(option->name) + strlen(option->value)) + 2;
768
769 if ((s = malloc(bytes)) == NULL)
770 return (NULL);
771
772 /*
773 * Copy the options to the string...
774 */
775
776 for (i = num_options, option = options, sptr = s; i > 0; i --, option ++)
777 {
778 if (!strcmp(option->name, "copies"))
779 continue;
780
781 if (sptr > s)
782 *sptr++ = ' ';
783
784 strlcpy(sptr, option->name, bytes - (size_t)(sptr - s));
785 sptr += strlen(sptr);
786 *sptr++ = '=';
787
788 for (vptr = option->value; *vptr;)
789 {
790 if (strchr("\\ \t\n", *vptr))
791 *sptr++ = '\\';
792
793 *sptr++ = *vptr++;
794 }
795 }
796
797 *sptr = '\0';
798
799 return (s);
800 }
801
802
803 /*
804 * 'exec_filter()' - Execute a single filter.
805 */
806
807 static int /* O - Process ID or -1 on error */
808 exec_filter(const char *filter, /* I - Filter to execute */
809 char **argv, /* I - Argument list */
810 char **envp, /* I - Environment list */
811 int infd, /* I - Stdin file descriptor */
812 int outfd) /* I - Stdout file descriptor */
813 {
814 int pid, /* Process ID */
815 fd; /* Temporary file descriptor */
816 #if defined(__APPLE__)
817 char processPath[1024], /* CFProcessPath environment variable */
818 linkpath[1024]; /* Link path for symlinks... */
819 int linkbytes; /* Bytes for link path */
820
821
822 /*
823 * Add special voodoo magic for MacmacOS - this allows MacmacOS
824 * programs to access their bundle resources properly...
825 */
826
827 if ((linkbytes = readlink(filter, linkpath, sizeof(linkpath) - 1)) > 0)
828 {
829 /*
830 * Yes, this is a symlink to the actual program, nul-terminate and
831 * use it...
832 */
833
834 linkpath[linkbytes] = '\0';
835
836 if (linkpath[0] == '/')
837 snprintf(processPath, sizeof(processPath), "CFProcessPath=%s",
838 linkpath);
839 else
840 snprintf(processPath, sizeof(processPath), "CFProcessPath=%s/%s",
841 dirname((char *)filter), linkpath);
842 }
843 else
844 snprintf(processPath, sizeof(processPath), "CFProcessPath=%s", filter);
845
846 envp[0] = processPath; /* Replace <CFProcessPath> string */
847 #endif /* __APPLE__ */
848
849 if ((pid = fork()) == 0)
850 {
851 /*
852 * Child process goes here...
853 *
854 * Update stdin/stdout/stderr as needed...
855 */
856
857 if (infd != 0)
858 {
859 if (infd < 0)
860 infd = open("/dev/null", O_RDONLY);
861
862 if (infd > 0)
863 {
864 dup2(infd, 0);
865 close(infd);
866 }
867 }
868
869 if (outfd != 1)
870 {
871 if (outfd < 0)
872 outfd = open("/dev/null", O_WRONLY);
873
874 if (outfd > 1)
875 {
876 dup2(outfd, 1);
877 close(outfd);
878 }
879 }
880
881 if ((fd = open("/dev/null", O_RDWR)) > 3)
882 {
883 dup2(fd, 3);
884 close(fd);
885 }
886 fcntl(3, F_SETFL, O_NDELAY);
887
888 if ((fd = open("/dev/null", O_RDWR)) > 4)
889 {
890 dup2(fd, 4);
891 close(fd);
892 }
893 fcntl(4, F_SETFL, O_NDELAY);
894
895 /*
896 * Execute command...
897 */
898
899 execve(filter, argv, envp);
900
901 perror(filter);
902
903 exit(errno);
904 }
905
906 return (pid);
907 }
908
909
910 /*
911 * 'exec_filters()' - Execute filters for the given file and options.
912 */
913
914 static int /* O - 0 on success, 1 on error */
915 exec_filters(mime_type_t *srctype, /* I - Source type */
916 cups_array_t *filters, /* I - Array of filters to run */
917 const char *infile, /* I - File to filter */
918 const char *outfile, /* I - File to create */
919 const char *ppdfile, /* I - PPD file, if any */
920 const char *printer, /* I - Printer name */
921 const char *user, /* I - Username */
922 const char *title, /* I - Job title */
923 int num_options, /* I - Number of filter options */
924 cups_option_t *options) /* I - Filter options */
925 {
926 int i; /* Looping var */
927 const char *argv[8], /* Command-line arguments */
928 *envp[17], /* Environment variables */
929 *temp; /* Temporary string */
930 char *optstr, /* Filter options */
931 content_type[1024], /* CONTENT_TYPE */
932 cups_datadir[1024], /* CUPS_DATADIR */
933 cups_fontpath[1024], /* CUPS_FONTPATH */
934 cups_serverbin[1024], /* CUPS_SERVERBIN */
935 cups_serverroot[1024], /* CUPS_SERVERROOT */
936 final_content_type[1024] = "",
937 /* FINAL_CONTENT_TYPE */
938 lang[1024], /* LANG */
939 path[1024], /* PATH */
940 ppd[1024], /* PPD */
941 printer_info[255], /* PRINTER_INFO env variable */
942 printer_location[255], /* PRINTER_LOCATION env variable */
943 printer_name[255], /* PRINTER env variable */
944 rip_max_cache[1024], /* RIP_MAX_CACHE */
945 userenv[1024], /* USER */
946 program[1024]; /* Program to run */
947 mime_filter_t *filter, /* Current filter */
948 *next; /* Next filter */
949 int current, /* Current filter */
950 filterfds[2][2], /* Pipes for filters */
951 pid, /* Process ID of filter */
952 status, /* Exit status */
953 retval; /* Return value */
954 cups_array_t *pids; /* Executed filters array */
955 mime_filter_t key; /* Search key for filters */
956 cups_lang_t *language; /* Current language */
957 cups_dest_t *dest; /* Destination information */
958
959
960 /*
961 * Figure out the final content type...
962 */
963
964 for (filter = (mime_filter_t *)cupsArrayLast(filters);
965 filter && filter->dst;
966 filter = (mime_filter_t *)cupsArrayPrev(filters))
967 if (strcmp(filter->dst->super, "printer"))
968 break;
969
970 if (filter && filter->dst)
971 {
972 const char *ptr; /* Pointer in type name */
973
974 if ((ptr = strchr(filter->dst->type, '/')) != NULL)
975 snprintf(final_content_type, sizeof(final_content_type),
976 "FINAL_CONTENT_TYPE=%s", ptr + 1);
977 else
978 snprintf(final_content_type, sizeof(final_content_type),
979 "FINAL_CONTENT_TYPE=%s/%s", filter->dst->super,
980 filter->dst->type);
981 }
982
983 /*
984 * Remove NULL ("-") filters...
985 */
986
987 for (filter = (mime_filter_t *)cupsArrayFirst(filters);
988 filter;
989 filter = (mime_filter_t *)cupsArrayNext(filters))
990 if (!strcmp(filter->filter, "-"))
991 cupsArrayRemove(filters, filter);
992
993 /*
994 * Setup the filter environment and command-line...
995 */
996
997 optstr = escape_options(num_options, options);
998
999 snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s",
1000 srctype->super, srctype->type);
1001 snprintf(cups_datadir, sizeof(cups_datadir), "CUPS_DATADIR=%s", DataDir);
1002 snprintf(cups_fontpath, sizeof(cups_fontpath), "CUPS_FONTPATH=%s", FontPath);
1003 snprintf(cups_serverbin, sizeof(cups_serverbin), "CUPS_SERVERBIN=%s",
1004 ServerBin);
1005 snprintf(cups_serverroot, sizeof(cups_serverroot), "CUPS_SERVERROOT=%s",
1006 ServerRoot);
1007 language = cupsLangDefault();
1008 snprintf(lang, sizeof(lang), "LANG=%s.UTF8", language->language);
1009 snprintf(path, sizeof(path), "PATH=%s", Path);
1010 if (ppdfile)
1011 snprintf(ppd, sizeof(ppd), "PPD=%s", ppdfile);
1012 else if ((temp = getenv("PPD")) != NULL)
1013 snprintf(ppd, sizeof(ppd), "PPD=%s", temp);
1014 else
1015 #ifdef __APPLE__
1016 if (!access("/System/Library/Frameworks/ApplicationServices.framework/"
1017 "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
1018 "Resources/English.lproj/Generic.ppd", 0))
1019 strlcpy(ppd, "PPD=/System/Library/Frameworks/ApplicationServices.framework/"
1020 "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
1021 "Resources/English.lproj/Generic.ppd", sizeof(ppd));
1022 else
1023 strlcpy(ppd, "PPD=/System/Library/Frameworks/ApplicationServices.framework/"
1024 "Versions/A/Frameworks/PrintCore.framework/Versions/A/"
1025 "Resources/Generic.ppd", sizeof(ppd));
1026 #else
1027 snprintf(ppd, sizeof(ppd), "PPD=%s/model/laserjet.ppd", DataDir);
1028 #endif /* __APPLE__ */
1029 snprintf(rip_max_cache, sizeof(rip_max_cache), "RIP_MAX_CACHE=%s", RIPCache);
1030 snprintf(userenv, sizeof(userenv), "USER=%s", user);
1031
1032 if (printer &&
1033 (dest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, printer, NULL)) != NULL)
1034 {
1035 if ((temp = cupsGetOption("printer-info", dest->num_options,
1036 dest->options)) != NULL)
1037 snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s", temp);
1038 else
1039 snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s", printer);
1040
1041 if ((temp = cupsGetOption("printer-location", dest->num_options,
1042 dest->options)) != NULL)
1043 snprintf(printer_location, sizeof(printer_location),
1044 "PRINTER_LOCATION=%s", temp);
1045 else
1046 strlcpy(printer_location, "PRINTER_LOCATION=Unknown",
1047 sizeof(printer_location));
1048 }
1049 else
1050 {
1051 snprintf(printer_info, sizeof(printer_info), "PRINTER_INFO=%s",
1052 printer ? printer : "Unknown");
1053 strlcpy(printer_location, "PRINTER_LOCATION=Unknown",
1054 sizeof(printer_location));
1055 }
1056
1057 snprintf(printer_name, sizeof(printer_name), "PRINTER=%s",
1058 printer ? printer : "Unknown");
1059
1060 argv[0] = (char *)printer;
1061 argv[1] = "1";
1062 argv[2] = user;
1063 argv[3] = title;
1064 argv[4] = cupsGetOption("copies", num_options, options);
1065 argv[5] = optstr;
1066 argv[6] = infile;
1067 argv[7] = NULL;
1068
1069 if (!argv[4])
1070 argv[4] = "1";
1071
1072 envp[0] = "<CFProcessPath>";
1073 envp[1] = content_type;
1074 envp[2] = cups_datadir;
1075 envp[3] = cups_fontpath;
1076 envp[4] = cups_serverbin;
1077 envp[5] = cups_serverroot;
1078 envp[6] = lang;
1079 envp[7] = path;
1080 envp[8] = ppd;
1081 envp[9] = printer_info;
1082 envp[10] = printer_location;
1083 envp[11] = printer_name;
1084 envp[12] = rip_max_cache;
1085 envp[13] = userenv;
1086 envp[14] = "CHARSET=utf-8";
1087 if (final_content_type[0])
1088 {
1089 envp[15] = final_content_type;
1090 envp[16] = NULL;
1091 }
1092 else
1093 envp[15] = NULL;
1094
1095 for (i = 0; argv[i]; i ++)
1096 fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]);
1097
1098 for (i = 0; envp[i]; i ++)
1099 fprintf(stderr, "DEBUG: envp[%d]=\"%s\"\n", i, envp[i]);
1100
1101 /*
1102 * Execute all of the filters...
1103 */
1104
1105 pids = cupsArrayNew((cups_array_func_t)compare_pids, NULL);
1106 current = 0;
1107 filterfds[0][0] = -1;
1108 filterfds[0][1] = -1;
1109 filterfds[1][0] = -1;
1110 filterfds[1][1] = -1;
1111
1112 if (!infile)
1113 filterfds[0][0] = 0;
1114
1115 for (filter = (mime_filter_t *)cupsArrayFirst(filters);
1116 filter;
1117 filter = next, current = 1 - current)
1118 {
1119 next = (mime_filter_t *)cupsArrayNext(filters);
1120
1121 if (filter->filter[0] == '/')
1122 strlcpy(program, filter->filter, sizeof(program));
1123 else
1124 snprintf(program, sizeof(program), "%s/filter/%s", ServerBin,
1125 filter->filter);
1126
1127 if (filterfds[!current][1] > 1)
1128 {
1129 close(filterfds[1 - current][0]);
1130 close(filterfds[1 - current][1]);
1131
1132 filterfds[1 - current][0] = -1;
1133 filterfds[1 - current][0] = -1;
1134 }
1135
1136 if (next)
1137 open_pipe(filterfds[1 - current]);
1138 else if (outfile)
1139 {
1140 filterfds[1 - current][1] = open(outfile, O_CREAT | O_TRUNC | O_WRONLY,
1141 0666);
1142
1143 if (filterfds[1 - current][1] < 0)
1144 fprintf(stderr, "ERROR: Unable to create \"%s\" - %s\n", outfile,
1145 strerror(errno));
1146 }
1147 else
1148 filterfds[1 - current][1] = 1;
1149
1150 pid = exec_filter(program, (char **)argv, (char **)envp,
1151 filterfds[current][0], filterfds[1 - current][1]);
1152
1153 if (pid > 0)
1154 {
1155 fprintf(stderr, "INFO: %s (PID %d) started.\n", filter->filter, pid);
1156
1157 filter->cost = pid;
1158 cupsArrayAdd(pids, filter);
1159 }
1160 else
1161 break;
1162
1163 argv[6] = NULL;
1164 }
1165
1166 /*
1167 * Close remaining pipes...
1168 */
1169
1170 if (filterfds[0][1] > 1)
1171 {
1172 close(filterfds[0][0]);
1173 close(filterfds[0][1]);
1174 }
1175
1176 if (filterfds[1][1] > 1)
1177 {
1178 close(filterfds[1][0]);
1179 close(filterfds[1][1]);
1180 }
1181
1182 /*
1183 * Wait for the children to exit...
1184 */
1185
1186 retval = 0;
1187
1188 while (cupsArrayCount(pids) > 0)
1189 {
1190 if ((pid = wait(&status)) < 0)
1191 continue;
1192
1193 key.cost = pid;
1194 if ((filter = (mime_filter_t *)cupsArrayFind(pids, &key)) != NULL)
1195 {
1196 cupsArrayRemove(pids, filter);
1197
1198 if (status)
1199 {
1200 if (WIFEXITED(status))
1201 fprintf(stderr, "ERROR: %s (PID %d) stopped with status %d\n",
1202 filter->filter, pid, WEXITSTATUS(status));
1203 else
1204 fprintf(stderr, "ERROR: %s (PID %d) crashed on signal %d\n",
1205 filter->filter, pid, WTERMSIG(status));
1206
1207 retval = 1;
1208 }
1209 else
1210 fprintf(stderr, "INFO: %s (PID %d) exited with no errors.\n",
1211 filter->filter, pid);
1212 }
1213 }
1214
1215 cupsArrayDelete(pids);
1216
1217 return (retval);
1218 }
1219
1220
1221 /*
1222 * 'get_job_file()' - Get the specified job file.
1223 */
1224
1225 static void
1226 get_job_file(const char *job) /* I - Job ID */
1227 {
1228 long jobid, /* Job ID */
1229 docnum; /* Document number */
1230 const char *jobptr; /* Pointer into job ID string */
1231 char uri[1024]; /* job-uri */
1232 http_t *http; /* Connection to server */
1233 ipp_t *request; /* Request data */
1234 int tempfd; /* Temporary file */
1235
1236
1237 /*
1238 * Get the job ID and document number, if any...
1239 */
1240
1241 if ((jobptr = strrchr(job, '-')) != NULL)
1242 jobptr ++;
1243 else
1244 jobptr = job;
1245
1246 jobid = strtol(jobptr, (char **)&jobptr, 10);
1247
1248 if (*jobptr == ',')
1249 docnum = strtol(jobptr + 1, NULL, 10);
1250 else
1251 docnum = 1;
1252
1253 if (jobid < 1 || jobid > INT_MAX)
1254 {
1255 _cupsLangPrintf(stderr, _("cupsfilter: Invalid job ID %d."), (int)jobid);
1256 exit(1);
1257 }
1258
1259 if (docnum < 1 || docnum > INT_MAX)
1260 {
1261 _cupsLangPrintf(stderr, _("cupsfilter: Invalid document number %d."),
1262 (int)docnum);
1263 exit(1);
1264 }
1265
1266 /*
1267 * Ask the server for the document file...
1268 */
1269
1270 if ((http = httpConnectEncrypt(cupsServer(), ippPort(),
1271 cupsEncryption())) == NULL)
1272 {
1273 _cupsLangPrintf(stderr, _("%s: Unable to connect to server."),
1274 "cupsfilter");
1275 exit(1);
1276 }
1277
1278 request = ippNewRequest(CUPS_GET_DOCUMENT);
1279
1280 snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", (int)jobid);
1281
1282 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
1283 ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "document-number",
1284 (int)docnum);
1285
1286 if ((tempfd = cupsTempFd(TempFile, sizeof(TempFile))) == -1)
1287 {
1288 _cupsLangPrintError("ERROR", _("Unable to create temporary file"));
1289 httpClose(http);
1290 exit(1);
1291 }
1292
1293 signal(SIGTERM, sighandler);
1294
1295 ippDelete(cupsDoIORequest(http, request, "/", -1, tempfd));
1296
1297 close(tempfd);
1298
1299 httpClose(http);
1300
1301 if (cupsLastError() != IPP_OK)
1302 {
1303 _cupsLangPrintf(stderr, _("cupsfilter: Unable to get job file - %s"),
1304 cupsLastErrorString());
1305 unlink(TempFile);
1306 exit(1);
1307 }
1308 }
1309
1310
1311 /*
1312 * 'open_pipe()' - Create a pipe which is closed on exec.
1313 */
1314
1315 static int /* O - 0 on success, -1 on error */
1316 open_pipe(int *fds) /* O - Pipe file descriptors (2) */
1317 {
1318 /*
1319 * Create the pipe...
1320 */
1321
1322 if (pipe(fds))
1323 {
1324 fds[0] = -1;
1325 fds[1] = -1;
1326
1327 return (-1);
1328 }
1329
1330 /*
1331 * Set the "close on exec" flag on each end of the pipe...
1332 */
1333
1334 if (fcntl(fds[0], F_SETFD, fcntl(fds[0], F_GETFD) | FD_CLOEXEC))
1335 {
1336 close(fds[0]);
1337 close(fds[1]);
1338
1339 fds[0] = -1;
1340 fds[1] = -1;
1341
1342 return (-1);
1343 }
1344
1345 if (fcntl(fds[1], F_SETFD, fcntl(fds[1], F_GETFD) | FD_CLOEXEC))
1346 {
1347 close(fds[0]);
1348 close(fds[1]);
1349
1350 fds[0] = -1;
1351 fds[1] = -1;
1352
1353 return (-1);
1354 }
1355
1356 /*
1357 * Return 0 indicating success...
1358 */
1359
1360 return (0);
1361 }
1362
1363
1364 /*
1365 * 'read_cups_files_conf()' - Read the cups-files.conf file to get the filter settings.
1366 */
1367
1368 static int /* O - 0 on success, 1 on error */
1369 read_cups_files_conf(
1370 const char *filename) /* I - File to read */
1371 {
1372 cups_file_t *fp; /* cups-files.conf file */
1373 const char *temp; /* Temporary string */
1374 char line[1024], /* Line from file */
1375 *ptr; /* Pointer into line */
1376 int linenum; /* Current line number */
1377
1378
1379 if ((temp = getenv("CUPS_DATADIR")) != NULL)
1380 set_string(&DataDir, temp);
1381 else
1382 set_string(&DataDir, CUPS_DATADIR);
1383
1384 if ((temp = getenv("CUPS_FONTPATH")) != NULL)
1385 set_string(&FontPath, temp);
1386 else
1387 set_string(&FontPath, CUPS_FONTPATH);
1388
1389 set_string(&RIPCache, "128m");
1390
1391 if ((temp = getenv("CUPS_SERVERBIN")) != NULL)
1392 set_string(&ServerBin, temp);
1393 else
1394 set_string(&ServerBin, CUPS_SERVERBIN);
1395
1396 strlcpy(line, filename, sizeof(line));
1397 if ((ptr = strrchr(line, '/')) != NULL)
1398 *ptr = '\0';
1399 else
1400 getcwd(line, sizeof(line));
1401
1402 set_string(&ServerRoot, line);
1403
1404 if ((fp = cupsFileOpen(filename, "r")) != NULL)
1405 {
1406 linenum = 0;
1407
1408 while (cupsFileGetConf(fp, line, sizeof(line), &ptr, &linenum))
1409 {
1410 if (!_cups_strcasecmp(line, "DataDir"))
1411 set_string(&DataDir, ptr);
1412 else if (!_cups_strcasecmp(line, "FontPath"))
1413 set_string(&FontPath, ptr);
1414 else if (!_cups_strcasecmp(line, "RIPCache"))
1415 set_string(&RIPCache, ptr);
1416 else if (!_cups_strcasecmp(line, "ServerBin"))
1417 set_string(&ServerBin, ptr);
1418 else if (!_cups_strcasecmp(line, "ServerRoot"))
1419 set_string(&ServerRoot, ptr);
1420 }
1421
1422 cupsFileClose(fp);
1423 }
1424
1425 snprintf(line, sizeof(line), "%s/filter:" CUPS_BINDIR ":" CUPS_SBINDIR ":/bin:/usr/bin", ServerBin);
1426 set_string(&Path, line);
1427
1428 return (0);
1429 }
1430
1431
1432 /*
1433 * 'set_string()' - Copy and set a string.
1434 */
1435
1436 static void
1437 set_string(char **s, /* O - Copy of string */
1438 const char *val) /* I - String to copy */
1439 {
1440 if (*s)
1441 free(*s);
1442
1443 *s = strdup(val);
1444 }
1445
1446
1447 /*
1448 * 'sighandler()' - Signal catcher for when we print from stdin...
1449 */
1450
1451 static void
1452 sighandler(int s) /* I - Signal number */
1453 {
1454 /*
1455 * Remove the temporary file we're using to print a job file...
1456 */
1457
1458 if (TempFile[0])
1459 unlink(TempFile);
1460
1461 /*
1462 * Exit...
1463 */
1464
1465 exit(s);
1466 }
1467
1468
1469 /*
1470 * 'usage()' - Show program usage...
1471 */
1472
1473 static void
1474 usage(const char *opt) /* I - Incorrect option, if any */
1475 {
1476 if (opt)
1477 _cupsLangPrintf(stderr, _("%s: Unknown option \"%c\"."), "cupsfilter", *opt);
1478
1479 _cupsLangPuts(stdout, _("Usage: cupsfilter [ options ] [ -- ] filename"));
1480 _cupsLangPuts(stdout, _("Options:"));
1481 _cupsLangPuts(stdout, _(" --list-filters List filters that will be used."));
1482 _cupsLangPuts(stdout, _(" -D Remove the input file when finished."));
1483 _cupsLangPuts(stdout, _(" -P filename.ppd Set PPD file."));
1484 _cupsLangPuts(stdout, _(" -U username Specify username."));
1485 _cupsLangPuts(stdout, _(" -c cups-files.conf Set cups-files.conf file to use."));
1486 _cupsLangPuts(stdout, _(" -d printer Use the named printer."));
1487 _cupsLangPuts(stdout, _(" -e Use every filter from the PPD file."));
1488 _cupsLangPuts(stdout, _(" -i mime/type Set input MIME type (otherwise auto-typed)."));
1489 _cupsLangPuts(stdout, _(" -j job-id[,N] Filter file N from the specified job (default is file 1)."));
1490 _cupsLangPuts(stdout, _(" -m mime/type Set output MIME type (otherwise application/pdf)."));
1491 _cupsLangPuts(stdout, _(" -n copies Set number of copies."));
1492 _cupsLangPuts(stdout, _(" -o name=value Set option(s)."));
1493 _cupsLangPuts(stdout, _(" -p filename.ppd Set PPD file."));
1494 _cupsLangPuts(stdout, _(" -t title Set title."));
1495 _cupsLangPuts(stdout, _(" -u Remove the PPD file when finished."));
1496
1497 exit(1);
1498 }