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