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