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