• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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