• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * PostScript command filter for CUPS.
3  *
4  * Copyright © 2020-2024 by OpenPrinting.
5  * Copyright 2008-2014 by Apple Inc.
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/ppd.h>
16 #include <cups/sidechannel.h>
17 
18 
19 /*
20  * Local functions...
21  */
22 
23 static int	auto_configure(ppd_file_t *ppd, const char *user);
24 static void	begin_ps(ppd_file_t *ppd, const char *user);
25 static void	end_ps(ppd_file_t *ppd);
26 static void	print_self_test_page(ppd_file_t *ppd, const char *user);
27 static void	report_levels(ppd_file_t *ppd, const char *user);
28 
29 
30 /*
31  * 'main()' - Process a CUPS command file.
32  */
33 
34 int					/* O - Exit status */
main(int argc,char * argv[])35 main(int  argc,				/* I - Number of command-line arguments */
36      char *argv[])			/* I - Command-line arguments */
37 {
38   int		status = 0;		/* Exit status */
39   cups_file_t	*fp;			/* Command file */
40   char		line[1024],		/* Line from file */
41 		*value;			/* Value on line */
42   int		linenum;		/* Line number in file */
43   ppd_file_t	*ppd;			/* PPD file */
44 
45 
46  /*
47   * Check for valid arguments...
48   */
49 
50   if (argc != 6 && argc != 7)
51   {
52    /*
53     * We don't have the correct number of arguments; write an error message
54     * and return.
55     */
56 
57     _cupsLangPrintf(stderr,
58                     _("Usage: %s job-id user title copies options [file]"),
59                     argv[0]);
60     return (1);
61   }
62 
63  /*
64   * Open the PPD file...
65   */
66 
67   if ((ppd = ppdOpenFile(getenv("PPD"))) == NULL)
68   {
69     fputs("ERROR: Unable to open PPD file!\n", stderr);
70     return (1);
71   }
72 
73  /*
74   * Open the command file as needed...
75   */
76 
77   if (argc == 7)
78   {
79     if ((fp = cupsFileOpen(argv[6], "r")) == NULL)
80     {
81       perror("ERROR: Unable to open command file - ");
82       return (1);
83     }
84   }
85   else
86     fp = cupsFileStdin();
87 
88  /*
89   * Read the commands from the file and send the appropriate commands...
90   */
91 
92   linenum = 0;
93 
94   while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
95   {
96    /*
97     * Parse the command...
98     */
99 
100     if (!_cups_strcasecmp(line, "AutoConfigure"))
101       status |= auto_configure(ppd, argv[2]);
102     else if (!_cups_strcasecmp(line, "PrintSelfTestPage"))
103       print_self_test_page(ppd, argv[2]);
104     else if (!_cups_strcasecmp(line, "ReportLevels"))
105       report_levels(ppd, argv[2]);
106     else
107     {
108       _cupsLangPrintFilter(stderr, "ERROR",
109                            _("Invalid printer command \"%s\"."), line);
110       status = 1;
111     }
112   }
113 
114   return (status);
115 }
116 
117 
118 /*
119  * 'auto_configure()' - Automatically configure the printer using PostScript
120  *                      query commands and/or SNMP lookups.
121  */
122 
123 static int				/* O - Exit status */
auto_configure(ppd_file_t * ppd,const char * user)124 auto_configure(ppd_file_t *ppd,		/* I - PPD file */
125                const char *user)	/* I - Printing user */
126 {
127   int		status = 0;		/* Exit status */
128   ppd_option_t	*option;		/* Current option in PPD */
129   ppd_attr_t	*attr;			/* Query command attribute */
130   const char	*valptr;		/* Pointer into attribute value */
131   char		buffer[1024],		/* String buffer */
132 		*bufptr;		/* Pointer into buffer */
133   ssize_t	bytes;			/* Number of bytes read */
134   int		datalen;		/* Side-channel data length */
135 
136 
137  /*
138   * See if the backend supports bidirectional I/O...
139   */
140 
141   datalen = 1;
142   if (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_BIDI, buffer, &datalen,
143                                30.0) != CUPS_SC_STATUS_OK ||
144       buffer[0] != CUPS_SC_BIDI_SUPPORTED)
145   {
146     fputs("DEBUG: Unable to auto-configure PostScript Printer - no "
147           "bidirectional I/O available!\n", stderr);
148     return (1);
149   }
150 
151  /*
152   * Put the printer in PostScript mode...
153   */
154 
155   begin_ps(ppd, user);
156 
157  /*
158   * (STR #4028)
159   *
160   * As a lot of PPDs contain bad PostScript query code, we need to prevent one
161   * bad query sequence from affecting all auto-configuration.  The following
162   * error handler allows us to log PostScript errors to cupsd.
163   */
164 
165   puts("/cups_handleerror {\n"
166        "  $error /newerror false put\n"
167        "  (:PostScript error in \") print cups_query_keyword print (\": ) "
168        "print\n"
169        "  $error /errorname get 128 string cvs print\n"
170        "  (; offending command:) print $error /command get 128 string cvs "
171        "print (\n) print flush\n"
172        "} bind def\n"
173        "errordict /timeout {} put\n"
174        "/cups_query_keyword (?Unknown) def\n");
175   fflush(stdout);
176 
177  /*
178   * Wait for the printer to become connected...
179   */
180 
181   do
182   {
183     sleep(1);
184     datalen = 1;
185   }
186   while (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_CONNECTED, buffer, &datalen,
187                                   5.0) == CUPS_SC_STATUS_OK && !buffer[0]);
188 
189  /*
190   * Then loop through every option in the PPD file and ask for the current
191   * value...
192   */
193 
194   fputs("DEBUG: Auto-configuring PostScript printer...\n", stderr);
195 
196   for (option = ppdFirstOption(ppd); option; option = ppdNextOption(ppd))
197   {
198    /*
199     * See if we have a query command for this option...
200     */
201 
202     snprintf(buffer, sizeof(buffer), "?%s", option->keyword);
203 
204     if ((attr = ppdFindAttr(ppd, buffer, NULL)) == NULL || !attr->value)
205     {
206       fprintf(stderr, "DEBUG: Skipping %s option...\n", option->keyword);
207       continue;
208     }
209 
210    /*
211     * Send the query code to the printer...
212     */
213 
214     fprintf(stderr, "DEBUG: Querying %s...\n", option->keyword);
215 
216     for (bufptr = buffer, valptr = attr->value; *valptr; valptr ++)
217     {
218      /*
219       * Log the query code, breaking at newlines...
220       */
221 
222       if (*valptr == '\n')
223       {
224         *bufptr = '\0';
225         fprintf(stderr, "DEBUG: %s\\n\n", buffer);
226         bufptr = buffer;
227       }
228       else if (*valptr < ' ')
229       {
230         if (bufptr >= (buffer + sizeof(buffer) - 4))
231         {
232 	  *bufptr = '\0';
233 	  fprintf(stderr, "DEBUG: %s\n", buffer);
234 	  bufptr = buffer;
235         }
236 
237         if (*valptr == '\r')
238         {
239           *bufptr++ = '\\';
240           *bufptr++ = 'r';
241         }
242         else if (*valptr == '\t')
243         {
244           *bufptr++ = '\\';
245           *bufptr++ = 't';
246         }
247         else
248         {
249           *bufptr++ = '\\';
250           *bufptr++ = '0' + ((*valptr / 64) & 7);
251           *bufptr++ = '0' + ((*valptr / 8) & 7);
252           *bufptr++ = '0' + (*valptr & 7);
253         }
254       }
255       else
256       {
257         if (bufptr >= (buffer + sizeof(buffer) - 1))
258         {
259 	  *bufptr = '\0';
260 	  fprintf(stderr, "DEBUG: %s\n", buffer);
261 	  bufptr = buffer;
262         }
263 
264 	*bufptr++ = *valptr;
265       }
266     }
267 
268     if (bufptr > buffer)
269     {
270       *bufptr = '\0';
271       fprintf(stderr, "DEBUG: %s\n", buffer);
272     }
273 
274     printf("/cups_query_keyword (?%s) def\n", option->keyword);
275 					/* Set keyword for error reporting */
276     fputs("{ (", stdout);
277     for (valptr = attr->value; *valptr; valptr ++)
278     {
279       if (*valptr == '(' || *valptr == ')' || *valptr == '\\')
280         putchar('\\');
281       putchar(*valptr);
282     }
283     fputs(") cvx exec } stopped { cups_handleerror } if clear\n", stdout);
284     					/* Send query code */
285     fflush(stdout);
286 
287     datalen = 0;
288     cupsSideChannelDoRequest(CUPS_SC_CMD_DRAIN_OUTPUT, buffer, &datalen, 5.0);
289 
290    /*
291     * Read the response data...
292     */
293 
294     bufptr    = buffer;
295     buffer[0] = '\0';
296     while ((bytes = cupsBackChannelRead(bufptr, sizeof(buffer) - (size_t)(bufptr - buffer) - 1, 10.0)) > 0)
297     {
298      /*
299       * No newline at the end? Go on reading ...
300       */
301 
302       bufptr += bytes;
303       *bufptr = '\0';
304 
305       if (bytes == 0 ||
306           (bufptr > buffer && bufptr[-1] != '\r' && bufptr[-1] != '\n'))
307 	continue;
308 
309      /*
310       * Trim whitespace and control characters from both ends...
311       */
312 
313       bytes = bufptr - buffer;
314 
315       for (bufptr --; bufptr >= buffer; bufptr --)
316         if (isspace(*bufptr & 255) || iscntrl(*bufptr & 255))
317 	  *bufptr = '\0';
318 	else
319 	  break;
320 
321       for (bufptr = buffer; isspace(*bufptr & 255) || iscntrl(*bufptr & 255);
322 	   bufptr ++);
323 
324       if (bufptr > buffer)
325       {
326         _cups_strcpy(buffer, bufptr);
327 	bufptr = buffer;
328       }
329 
330       fprintf(stderr, "DEBUG: Got %d bytes.\n", (int)bytes);
331 
332      /*
333       * Skip blank lines...
334       */
335 
336       if (!buffer[0])
337         continue;
338 
339      /*
340       * Check the response...
341       */
342 
343       if ((bufptr = strchr(buffer, ':')) != NULL)
344       {
345        /*
346         * PostScript code for this option in the PPD is broken; show the
347         * interpreter's error message that came back...
348         */
349 
350 	fprintf(stderr, "DEBUG%s\n", bufptr);
351 	break;
352       }
353 
354      /*
355       * Verify the result is a valid option choice...
356       */
357 
358       if (!ppdFindChoice(option, buffer))
359       {
360 	if (!strcasecmp(buffer, "Unknown"))
361 	  break;
362 
363 	bufptr    = buffer;
364 	buffer[0] = '\0';
365         continue;
366       }
367 
368      /*
369       * Write out the result and move on to the next option...
370       */
371 
372       fprintf(stderr, "PPD: Default%s=%s\n", option->keyword, buffer);
373       break;
374     }
375 
376    /*
377     * Printer did not answer this option's query
378     */
379 
380     if (bytes <= 0)
381     {
382       fprintf(stderr,
383 	      "DEBUG: No answer to query for option %s within 10 seconds.\n",
384 	      option->keyword);
385       status = 1;
386     }
387   }
388 
389  /*
390   * Finish the job...
391   */
392 
393   fflush(stdout);
394   end_ps(ppd);
395 
396  /*
397   * Return...
398   */
399 
400   if (status)
401     _cupsLangPrintFilter(stderr, "WARNING",
402                          _("Unable to configure printer options."));
403 
404   return (0);
405 }
406 
407 
408 /*
409  * 'begin_ps()' - Send the standard PostScript prolog.
410  */
411 
412 static void
begin_ps(ppd_file_t * ppd,const char * user)413 begin_ps(ppd_file_t *ppd,		/* I - PPD file */
414          const char *user)		/* I - Username */
415 {
416   (void)user;
417 
418   if (ppd->jcl_begin)
419   {
420     fputs(ppd->jcl_begin, stdout);
421     fputs(ppd->jcl_ps, stdout);
422   }
423 
424   puts("%!");
425   puts("userdict dup(\\004)cvn{}put (\\004\\004)cvn{}put\n");
426 
427   fflush(stdout);
428 }
429 
430 
431 /*
432  * 'end_ps()' - Send the standard PostScript trailer.
433  */
434 
435 static void
end_ps(ppd_file_t * ppd)436 end_ps(ppd_file_t *ppd)			/* I - PPD file */
437 {
438   if (ppd->jcl_end)
439     fputs(ppd->jcl_end, stdout);
440   else
441     putchar(0x04);
442 
443   fflush(stdout);
444 }
445 
446 
447 /*
448  * 'print_self_test_page()' - Print a self-test page.
449  */
450 
451 static void
print_self_test_page(ppd_file_t * ppd,const char * user)452 print_self_test_page(ppd_file_t *ppd,	/* I - PPD file */
453                      const char *user)	/* I - Printing user */
454 {
455  /*
456   * Put the printer in PostScript mode...
457   */
458 
459   begin_ps(ppd, user);
460 
461  /*
462   * Send a simple file the draws a box around the imageable area and shows
463   * the product/interpreter information...
464   */
465 
466   puts("\r%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
467        "%%%%%%%%%%%%%\n"
468        "\r%%%% If you can read this, you are using the wrong driver for your "
469        "printer. %%%%\n"
470        "\r%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
471        "%%%%%%%%%%%%%\n"
472        "0 setgray\n"
473        "2 setlinewidth\n"
474        "initclip newpath clippath gsave stroke grestore pathbbox\n"
475        "exch pop exch pop exch 9 add exch 9 sub moveto\n"
476        "/Courier findfont 12 scalefont setfont\n"
477        "0 -12 rmoveto gsave product show grestore\n"
478        "0 -12 rmoveto gsave version show ( ) show revision 20 string cvs show "
479        "grestore\n"
480        "0 -12 rmoveto gsave serialnumber 20 string cvs show grestore\n"
481        "showpage");
482 
483  /*
484   * Finish the job...
485   */
486 
487   end_ps(ppd);
488 }
489 
490 
491 /*
492  * 'report_levels()' - Report supply levels.
493  */
494 
495 static void
report_levels(ppd_file_t * ppd,const char * user)496 report_levels(ppd_file_t *ppd,		/* I - PPD file */
497               const char *user)		/* I - Printing user */
498 {
499  /*
500   * Put the printer in PostScript mode...
501   */
502 
503   begin_ps(ppd, user);
504 
505  /*
506   * Don't bother sending any additional PostScript commands, since we just
507   * want the backend to have enough time to collect the supply info.
508   */
509 
510  /*
511   * Finish the job...
512   */
513 
514   end_ps(ppd);
515 }
516