1 /*
2 * implicitclass backend for implementing an implicit-class-like behavior
3 * of redundant print servers managed by cups-browsed.
4 *
5 * Copyright 2015-2019 by Till Kamppeter
6 * Copyright 2018-2019 by Deepak Patankar
7 *
8 * This is based on dnssd.c of CUPS
9 * dnssd.c copyright notice is follows:
10 *
11 * Copyright 2008-2015 by Apple Inc.
12 *
13 * These coded instructions, statements, and computer programs are the
14 * property of Apple Inc. and are protected by Federal copyright
15 * law. Distribution and use rights are outlined in the file "COPYING"
16 * which should have been included with this file.
17 */
18
19 /*
20 * Include necessary headers.
21 */
22
23 #include "backend-private.h"
24 #include <cups/array.h>
25 #include <ctype.h>
26 #include <cups/array.h>
27 #include <ctype.h>
28 #include <cups/cups.h>
29 #include <sys/types.h>
30 #include <sys/wait.h>
31 #include <cupsfilters/pdftoippprinter.h>
32
33 /*
34 * Local globals...
35 */
36
37 /* IPP Attribute which cups-browsed uses to tell us the destination queue for
38 the current job */
39 #define CUPS_BROWSED_DEST_PRINTER "cups-browsed-dest-printer"
40
41 static int job_canceled = 0; /* Set to 1 on SIGTERM */
42
43 /*
44 * Local functions... */
45
46 static void sigterm_handler(int sig);
47
48 #if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
49 #define HAVE_CUPS_1_6 1
50 #endif
51
52 #ifndef HAVE_CUPS_1_6
53 int
ippGetInteger(ipp_attribute_t * attr,int element)54 ippGetInteger(ipp_attribute_t *attr,
55 int element)
56 {
57 return (attr->values[element].integer);
58 }
59 #endif
60
61
62 int /* O - Next delay value */
next_delay(int current,int * previous)63 next_delay(int current, /* I - Current delay value or 0 */
64 int *previous) /* IO - Previous delay value */
65 {
66 int next; /* Next delay value */
67 if (current > 0) {
68 next = (current + *previous) % 12;
69 *previous = next < current ? 0 : current;
70 } else {
71 next = 1;
72 *previous = 0;
73 }
74 return (next);
75 }
76
77 /*
78 * 'main()' - Browse for printers.
79 */
80
81 int /* O - Exit status */
main(int argc,char * argv[])82 main(int argc, /* I - Number of command-line args */
83 char *argv[]) /* I - Command-line arguments */
84 {
85 const char *device_uri; /* URI with which we were called */
86 char scheme[64], username[32], queue_name[1024], resource[32],
87 printer_uri[1024],document_format[256],resolution[16];
88 int port, status;
89 const char *ptr1 = NULL;
90 char *ptr2,*ptr3,*ptr4;
91 const char *job_id;
92 char *filename, /* PDF file to convert */
93 tempfile[1024],
94 tempfile_filter[1024]; /* Temporary file */
95 int i;
96 char dest_host[1024]; /* Destination host */
97 ipp_t *request, *response;
98 ipp_attribute_t *attr;
99 int bytes; /* Bytes copied */
100 char uri[HTTP_MAX_URI];
101 char *argv_nt[8];
102 int outbuflen, filefd, savestdout, exit_status, dup_status;
103 char buf[1024];
104 const char *serverbin;
105 static const char *pattrs[] =
106 {
107 "printer-defaults"
108 };
109 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
110 struct sigaction action; /* Actions for POSIX signals */
111 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
112
113 /*
114 * Don't buffer stderr, and catch SIGTERM...
115 */
116
117 setbuf(stderr, NULL);
118
119 #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
120 sigset(SIGTERM, sigterm_handler);
121 #elif defined(HAVE_SIGACTION)
122 memset(&action, 0, sizeof(action));
123
124 sigemptyset(&action.sa_mask);
125 action.sa_handler = sigterm_handler;
126 sigaction(SIGTERM, &action, NULL);
127 #else
128 signal(SIGTERM, sigterm_handler);
129 #endif /* HAVE_SIGSET */
130
131 /*
132 * Check command-line...
133 */
134
135 if (argc >= 6) {
136 if ((device_uri = getenv("DEVICE_URI")) == NULL) {
137 if (!argv || !argv[0] || !strchr(argv[0], ':'))
138 return (-1);
139
140 device_uri = argv[0];
141 }
142 status = httpSeparateURI(HTTP_URI_CODING_ALL, device_uri,
143 scheme, sizeof(scheme),
144 username, sizeof(username),
145 queue_name, sizeof(queue_name),
146 &port,
147 resource, sizeof(resource));
148 if (status != HTTP_URI_STATUS_OK &&
149 status != HTTP_URI_STATUS_UNKNOWN_SCHEME) {
150 fprintf(stderr, "ERROR: Incorrect device URI syntax: %s\n",
151 device_uri);
152 return (CUPS_BACKEND_STOP);
153 }
154 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
155 "localhost", ippPort(), "/printers/%s", queue_name);
156 job_id = argv[1];
157 for (i = 0; i < 120; i++) {
158 /* Wait up to 60 sec for cups-browsed to supply the destination host */
159 /* Try reading the option in which cups-browsed has deposited the
160 destination host */
161 request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
162 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
163 uri);
164 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
165 "requested-attributes",
166 sizeof(pattrs) / sizeof(pattrs[0]),
167 NULL, pattrs);
168 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
169 "requesting-user-name",
170 NULL, cupsUser());
171 if ((response = cupsDoRequest(CUPS_HTTP_DEFAULT, request, "/")) ==
172 NULL)
173 goto failed;
174 for (attr = ippFirstAttribute(response); attr != NULL;
175 attr = ippNextAttribute(response)) {
176 while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER)
177 attr = ippNextAttribute(response);
178 if (attr == NULL)
179 break;
180 ptr1 = NULL;
181 while (attr != NULL && ippGetGroupTag(attr) ==
182 IPP_TAG_PRINTER) {
183 if (!strcmp(ippGetName(attr),
184 CUPS_BROWSED_DEST_PRINTER "-default"))
185 ptr1 = ippGetString(attr, 0, NULL);
186 if (ptr1 != NULL)
187 break;
188 attr = ippNextAttribute(response);
189 }
190 if (ptr1 != NULL)
191 break;
192 }
193 fprintf(stderr, "DEBUG: Read " CUPS_BROWSED_DEST_PRINTER " option: %s\n",
194 (ptr1 ? ptr1 : "Option not found"));
195 if (ptr1 == NULL)
196 goto failed;
197 /* Destination host is between double quotes, as double quotes are
198 illegal in host names one easily recognizes whether the option is
199 complete and avoids accepting a partially written host name */
200 if (*ptr1 != '"')
201 goto failed;
202 ptr1 ++;
203 /* Check whether option was set for this job, if not, keep waiting */
204 if (strncmp(ptr1, job_id, strlen(job_id)) != 0)
205 goto failed;
206 ptr1 += strlen(job_id);
207 if (*ptr1 != ' ')
208 goto failed;
209 ptr1 ++;
210 /* Read destination host name (or message) and check whether it is
211 complete (second double quote) */
212 if ((ptr2 = strchr(ptr1, '"')) != NULL) {
213 *ptr2 = '\0';
214 break;
215 }
216 failed:
217 /* Pause half a second before next attempt */
218 usleep(500000);
219 }
220
221 if (i >= 120) {
222 /* Timeout, no useful data from cups-browsed received */
223 fprintf(stderr, "ERROR: No destination host name supplied by cups-browsed for printer \"%s\", is cups-browsed running?\n",
224 queue_name);
225 return (CUPS_BACKEND_STOP);
226 }
227 strncpy(dest_host,ptr1,sizeof(dest_host) - 1);
228 if (!strcmp(dest_host, "NO_DEST_FOUND")) {
229 /* All remote queues are either disabled or not accepting jobs, let
230 CUPS retry after the usual interval */
231 fprintf(stderr, "ERROR: No suitable destination host found by cups-browsed.\n");
232 return (CUPS_BACKEND_RETRY);
233 } else if (!strcmp(dest_host, "ALL_DESTS_BUSY")) {
234 /* We queue on the client and all remote queues are busy, so we wait
235 5 sec and check again then */
236 fprintf(stderr, "DEBUG: No free destination host found by cups-browsed, retrying in 5 sec.\n");
237 sleep(5);
238 return (CUPS_BACKEND_RETRY_CURRENT);
239 } else {
240 /* We have the destination host name now, do the job */
241 const char *title;
242 int num_options = 0;
243 cups_option_t *options = NULL;
244 int fd;
245 char buffer[8192];
246
247 fprintf(stderr, "DEBUG: Received destination host name from cups-browsed: printer-uri %s\n",
248 ptr1);
249
250 /* Parse the command line options and prepare them for the new print
251 job */
252 cupsSetUser(argv[2]);
253 title = argv[3];
254 if (title == NULL) {
255 if (argc == 7) {
256 if ((title = strrchr(argv[6], '/')) != NULL)
257 title ++;
258 else
259 title = argv[6];
260 } else
261 title = "(stdin)";
262 }
263 num_options = cupsAddOption("copies", argv[4], num_options, &options);
264 num_options = cupsParseOptions(argv[5], num_options, &options);
265 if (argc == 7)
266 fd = open(argv[6], O_RDONLY);
267 else
268 fd = 0; /* stdin */
269
270 /* Finding the document format in which the pdftoippprinter will
271 convert the pdf file */
272 if ((ptr3 = strchr(ptr1, ' ')) != NULL) {
273 *ptr3='\0';
274 ptr3++;
275 }
276
277 /* Finding the resolution requested for the job*/
278 if ((ptr4 = strchr(ptr3, ' ')) != NULL) {
279 *ptr4='\0';
280 ptr4++;
281 }
282
283 strncpy(printer_uri, ptr1, sizeof(printer_uri) - 1);
284 strncpy(document_format, ptr3, sizeof(document_format) - 1);
285 strncpy(resolution, ptr4, sizeof(resolution) - 1);
286
287 fprintf(stderr,"DEBUG: Received job for the printer with the destination uri - %s, Final-document format for the printer - %s and requested resolution - %s\n",
288 printer_uri, document_format, resolution);
289
290 /* We need to send modified arguments to the IPP backend */
291 if (argc == 6) {
292 /* Copy stdin to a temp file...*/
293 if ((fd = cupsTempFd(tempfile, sizeof(tempfile))) < 0){
294 fprintf(stderr,"Debug: Can't Read PDF file.\n");
295 return CUPS_BACKEND_FAILED;
296 }
297 fprintf(stderr, "Debug: implicitclass - copying to temp print file \"%s\"\n",
298 tempfile);
299 while ((bytes = fread(buffer, 1, sizeof(buffer), stdin)) > 0)
300 bytes = write(fd, buffer, bytes);
301 close(fd);
302 filename = tempfile;
303 } else {
304 /* Use the filename on the command-line... */
305 filename = argv[6];
306 tempfile[0] = '\0';
307 }
308
309 /* Copying the argument to a new char** which will be sent to the filter
310 and the ipp backend */
311 argv_nt[0] = calloc(strlen(printer_uri) + 8, sizeof(char));
312 strcpy(argv_nt[0], printer_uri);
313 for (i = 1; i < 5; i++)
314 argv_nt[i] = argv[i];
315
316 /* Few new options will be added to the argv[5]*/
317 outbuflen = strlen(argv[5]) + 256;
318 argv_nt[5] = calloc(outbuflen, sizeof(char));
319 strcpy(argv_nt[5], (const char*)argv[5]);
320
321 /* Filter pdftoippprinter.c will read the input from this file*/
322 argv_nt[6] = filename;
323 argv_nt[7] = NULL;
324 set_option_in_str(argv_nt[5], outbuflen, "output-format",
325 document_format);
326 set_option_in_str(argv_nt[5], outbuflen, "Resolution",resolution);
327 set_option_in_str(argv_nt[5], outbuflen, "cups-browsed-dest-printer",NULL);
328 set_option_in_str(argv_nt[5], outbuflen, "cups-browsed",NULL);
329 setenv("DEVICE_URI",printer_uri, 1);
330 fprintf(stderr, "Setting the device uri to %s\n",printer_uri);
331 fprintf(stderr, "Changed the argv[5] to %s\n",argv_nt[5]);
332
333 filefd = cupsTempFd(tempfile_filter, sizeof(tempfile_filter));
334
335 /* The output of the last filter in pdftoippprinter will be
336 written to this file. We could have sent the output directly
337 to the backend, but having this temperory file will help us
338 find whether the filter worked correctly and what was the
339 document-format of the filtered output.*/
340 savestdout = dup(1);
341 dup_status = dup2(filefd, 1);
342 if(dup_status < 0) {
343 fprintf(stderr, "Could not write the output of pdftoippprinter printer to tmp file\n");
344 return CUPS_BACKEND_FAILED;
345 }
346 close(filefd);
347
348 /* Calling pdftoippprinter.c filter*/
349 apply_filters(7,argv_nt);
350
351 /* Reset stdout to standard */
352 dup2(savestdout, 1);
353 close(savestdout);
354
355 /* We will send the filtered output of the pdftoippprinter.c to
356 the IPP Backend*/
357 argv_nt[6] = tempfile_filter;
358 fprintf(stderr, "DEBUG: The filtered output of pdftoippprinter is written to file %s\n",
359 tempfile_filter);
360
361 /* Setting the final content type to the best pdl supported by
362 the printer.*/
363 if(!strcmp(document_format,"pdf"))
364 setenv("FINAL_CONTENT_TYPE", "application/pdf", 1);
365 else if(!strcmp(document_format,"raster"))
366 setenv("FINAL_CONTENT_TYPE", "image/pwg-raster", 1);
367 else if(!strcmp(document_format,"apple-raster"))
368 setenv("FINAL_CONTENT_TYPE", "image/urf", 1);
369 else if(!strcmp(document_format,"pclm"))
370 setenv("FINAL_CONTENT_TYPE", "application/PCLm", 1);
371 else if(!strcmp(document_format,"pclxl"))
372 setenv("FINAL_CONTENT_TYPE", "application/vnd.hp-pclxl", 1);
373 else if(!strcmp(document_format,"postscript"))
374 setenv("FINAL_CONTENT_TYPE", "application/postscript", 1);
375 else if(!strcmp(document_format,"pcl"))
376 setenv("FINAL_CONTENT_TYPE", "application/pcl", 1);
377
378 ippDelete(response);
379 fprintf(stderr, "Passing the following arguments to the ipp backend\n");
380 /* Arguments sent to the ipp backend */
381 for (i = 0; i < 7; i ++) {
382 fprintf(stderr, "argv[%d]: %s\n", i, argv_nt[i]);
383 }
384
385 /* The implicitclass backend will send the job directly to the
386 ipp backend*/
387
388 pid_t pid = fork();
389 if (pid == 0) {
390 serverbin = getenv("CUPS_SERVERBIN");
391 if (serverbin == NULL)
392 serverbin = CUPS_SERVERBIN;
393 snprintf(buf, sizeof(buf) - 1, "%s/backend/ipp", serverbin);
394 fprintf(stderr, "DEBUG: Started IPP Backend (%s) with pid: %d\n",
395 buf, getpid());
396 execv(buf, argv_nt);
397 fprintf(stderr, "ERROR: Could not start IPP Backend (%s): %d %s\n",
398 buf, errno, strerror(errno));
399 return CUPS_BACKEND_FAILED;
400 } else {
401 int status;
402 waitpid(pid, &status, 0);
403 if (WIFEXITED(status)) {
404 exit_status = WEXITSTATUS(status);
405 fprintf(stderr, "DEBUG: The IPP Backend exited with the status %d\n",
406 exit_status);
407 }
408 return exit_status;
409 }
410 }
411 } else if (argc != 1) {
412 fprintf(stderr,
413 "Usage: %s job-id user title copies options [file]\n",
414 argv[0]);
415 return (CUPS_BACKEND_FAILED);
416 }
417
418 /*
419 * No discovery mode at all for this backend
420 */
421
422 return (CUPS_BACKEND_OK);
423 }
424
425
426 /*
427 * 'sigterm_handler()' - Handle termination signals.
428 */
429
430 static void
sigterm_handler(int sig)431 sigterm_handler(int sig) /* I - Signal number (unused) */
432 {
433 (void)sig;
434
435 if (job_canceled)
436 _exit(CUPS_BACKEND_OK);
437 else
438 job_canceled = 1;
439 }
440