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