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