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