• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Device scanning mini-daemon for CUPS.
3  *
4  * Copyright © 2020-2024 by OpenPrinting.
5  * Copyright © 2007-2018 by Apple Inc.
6  * Copyright © 1997-2006 by Easy Software Products.
7  *
8  * Licensed under Apache License v2.0.  See the file "LICENSE" for more
9  * information.
10  */
11 
12 /*
13  * Include necessary headers...
14  */
15 
16 #include "util.h"
17 #include <cups/array.h>
18 #include <cups/dir.h>
19 #include <fcntl.h>
20 #include <sys/wait.h>
21 #include <poll.h>
22 
23 
24 /*
25  * Constants...
26  */
27 
28 #define MAX_BACKENDS	200		/* Maximum number of backends we'll run */
29 
30 
31 /*
32  * Backend information...
33  */
34 
35 typedef struct
36 {
37   char		*name;			/* Name of backend */
38   int		pid,			/* Process ID */
39 		status;			/* Exit status */
40   cups_file_t	*pipe;			/* Pipe from backend stdout */
41   int		count;			/* Number of devices found */
42 } cupsd_backend_t;
43 
44 
45 /*
46  * Device information structure...
47  */
48 
49 typedef struct
50 {
51   char	device_class[128],		/* Device class */
52 	device_info[128],		/* Device info/description */
53 	device_uri[1024];		/* Device URI */
54 } cupsd_device_t;
55 
56 
57 /*
58  * Local globals...
59  */
60 
61 static int		num_backends = 0,
62 					/* Total backends */
63 			active_backends = 0;
64 					/* Active backends */
65 static cupsd_backend_t	backends[MAX_BACKENDS];
66 					/* Array of backends */
67 static struct pollfd	backend_fds[MAX_BACKENDS];
68 					/* Array for poll() */
69 static cups_array_t	*devices;	/* Array of devices */
70 static uid_t		normal_user;	/* Normal user ID */
71 static int		device_limit;	/* Maximum number of devices */
72 static int		send_class,	/* Send device-class attribute? */
73 			send_info,	/* Send device-info attribute? */
74 			send_make_and_model,
75 					/* Send device-make-and-model attribute? */
76 			send_uri,	/* Send device-uri attribute? */
77 			send_id,	/* Send device-id attribute? */
78 			send_location;	/* Send device-location attribute? */
79 static int		dead_children = 0;
80 					/* Dead children? */
81 
82 
83 /*
84  * Local functions...
85  */
86 
87 static int		add_device(const char *device_class,
88 				   const char *device_make_and_model,
89 				   const char *device_info,
90 				   const char *device_uri,
91 				   const char *device_id,
92 				   const char *device_location);
93 static int		compare_devices(cupsd_device_t *p0,
94 			                cupsd_device_t *p1);
95 static double		get_current_time(void);
96 static int		get_device(cupsd_backend_t *backend);
97 static void		process_children(void);
98 static void		sigchld_handler(int sig);
99 static int		start_backend(const char *backend, int root);
100 
101 
102 /*
103  * 'main()' - Scan for devices and return an IPP response.
104  *
105  * Usage:
106  *
107  *    cups-deviced request_id limit options
108  */
109 
110 int					/* O - Exit code */
main(int argc,char * argv[])111 main(int  argc,				/* I - Number of command-line args */
112      char *argv[])			/* I - Command-line arguments */
113 {
114   int		i;			/* Looping var */
115   int		request_id;		/* Request ID */
116   int		timeout;		/* Timeout in seconds */
117   const char	*server_bin;		/* CUPS_SERVERBIN environment variable */
118   char		filename[1024];		/* Backend directory filename */
119   cups_dir_t	*dir;			/* Directory pointer */
120   cups_dentry_t *dent;			/* Directory entry */
121   double	current_time,		/* Current time */
122 		end_time;		/* Ending time */
123   int		num_options;		/* Number of options */
124   cups_option_t	*options;		/* Options */
125   cups_array_t	*requested,		/* requested-attributes values */
126 		*exclude,		/* exclude-schemes values */
127 		*include;		/* include-schemes values */
128 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
129   struct sigaction action;		/* Actions for POSIX signals */
130 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
131 
132 
133   setbuf(stderr, NULL);
134 
135  /*
136   * Check the command-line...
137   */
138 
139   if (argc != 6)
140   {
141     fputs("Usage: cups-deviced request-id limit timeout user-id options\n", stderr);
142 
143     return (1);
144   }
145 
146   request_id = atoi(argv[1]);
147   if (request_id < 1)
148   {
149     fprintf(stderr, "ERROR: [cups-deviced] Bad request ID %d!\n", request_id);
150 
151     return (1);
152   }
153 
154   device_limit = atoi(argv[2]);
155   if (device_limit < 0)
156   {
157     fprintf(stderr, "ERROR: [cups-deviced] Bad limit %d!\n", device_limit);
158 
159     return (1);
160   }
161 
162   timeout = atoi(argv[3]);
163   if (timeout < 1)
164   {
165     fprintf(stderr, "ERROR: [cups-deviced] Bad timeout %d!\n", timeout);
166 
167     return (1);
168   }
169 
170   normal_user = (uid_t)strtoul(argv[4], NULL, 10);
171   if (normal_user == 0)
172   {
173     fprintf(stderr, "ERROR: [cups-deviced] Bad user %u!\n", (unsigned)normal_user);
174 
175     return (1);
176   }
177 
178   num_options = cupsParseOptions(argv[5], 0, &options);
179   requested   = cupsdCreateStringsArray(cupsGetOption("requested-attributes",
180                                                       num_options, options));
181   exclude     = cupsdCreateStringsArray(cupsGetOption("exclude-schemes",
182                                                       num_options, options));
183   include     = cupsdCreateStringsArray(cupsGetOption("include-schemes",
184                                                       num_options, options));
185 
186   if (!requested || cupsArrayFind(requested, "all") != NULL)
187   {
188     send_class = send_info = send_make_and_model = send_uri = send_id =
189         send_location = 1;
190   }
191   else
192   {
193     send_class          = cupsArrayFind(requested, "device-class") != NULL;
194     send_info           = cupsArrayFind(requested, "device-info") != NULL;
195     send_make_and_model = cupsArrayFind(requested, "device-make-and-model") != NULL;
196     send_uri            = cupsArrayFind(requested, "device-uri") != NULL;
197     send_id             = cupsArrayFind(requested, "device-id") != NULL;
198     send_location       = cupsArrayFind(requested, "device-location") != NULL;
199   }
200 
201  /*
202   * Listen to child signals...
203   */
204 
205 #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
206   sigset(SIGCHLD, sigchld_handler);
207 #elif defined(HAVE_SIGACTION)
208   memset(&action, 0, sizeof(action));
209 
210   sigemptyset(&action.sa_mask);
211   sigaddset(&action.sa_mask, SIGCHLD);
212   action.sa_handler = sigchld_handler;
213   sigaction(SIGCHLD, &action, NULL);
214 #else
215   signal(SIGCLD, sigchld_handler);	/* No, SIGCLD isn't a typo... */
216 #endif /* HAVE_SIGSET */
217 
218  /*
219   * Try opening the backend directory...
220   */
221 
222   if ((server_bin = getenv("CUPS_SERVERBIN")) == NULL)
223     server_bin = CUPS_SERVERBIN;
224 
225   snprintf(filename, sizeof(filename), "%s/backend", server_bin);
226 
227   if ((dir = cupsDirOpen(filename)) == NULL)
228   {
229     fprintf(stderr, "ERROR: [cups-deviced] Unable to open backend directory "
230                     "\"%s\": %s", filename, strerror(errno));
231 
232     return (1);
233   }
234 
235  /*
236   * Setup the devices array...
237   */
238 
239   devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL);
240 
241  /*
242   * Loop through all of the device backends...
243   */
244 
245   while ((dent = cupsDirRead(dir)) != NULL)
246   {
247    /*
248     * Skip entries that are not executable files...
249     */
250 
251     if (!S_ISREG(dent->fileinfo.st_mode) ||
252         !isalnum(dent->filename[0] & 255) ||
253         (dent->fileinfo.st_mode & (S_IRUSR | S_IXUSR)) != (S_IRUSR | S_IXUSR))
254       continue;
255 
256    /*
257     * Skip excluded or not included backends...
258     */
259 
260     if (cupsArrayFind(exclude, dent->filename) ||
261         (include && !cupsArrayFind(include, dent->filename)))
262       continue;
263 
264    /*
265     * Backends without permissions for normal users run as root,
266     * all others run as the unprivileged user...
267     */
268 
269     start_backend(dent->filename, !(dent->fileinfo.st_mode & (S_IWGRP | S_IWOTH | S_IXOTH)));
270   }
271 
272   cupsDirClose(dir);
273 
274  /*
275   * Collect devices...
276   */
277 
278   if (getenv("SOFTWARE"))
279     puts("Content-Type: application/ipp\n");
280 
281   cupsdSendIPPHeader(IPP_OK, request_id);
282   cupsdSendIPPGroup(IPP_TAG_OPERATION);
283   cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
284   cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US");
285 
286   end_time = get_current_time() + timeout;
287 
288   while (active_backends > 0 && (current_time = get_current_time()) < end_time)
289   {
290    /*
291     * Collect the output from the backends...
292     */
293 
294     timeout = (int)(1000 * (end_time - current_time));
295 
296     if (poll(backend_fds, (nfds_t)num_backends, timeout) > 0)
297     {
298       for (i = 0; i < num_backends; i ++)
299         if (backend_fds[i].revents && backends[i].pipe)
300 	{
301 	  cups_file_t *bpipe = backends[i].pipe;
302 					/* Copy of pipe for backend... */
303 
304 	  do
305 	  {
306 	    if (get_device(backends + i))
307 	    {
308 	      backend_fds[i].fd     = 0;
309 	      backend_fds[i].events = 0;
310 	      break;
311 	    }
312 	  }
313 	  while (_cupsFilePeekAhead(bpipe, '\n'));
314         }
315     }
316 
317    /*
318     * Get exit status from children...
319     */
320 
321     if (dead_children)
322       process_children();
323   }
324 
325   cupsdSendIPPTrailer();
326 
327  /*
328   * Terminate any remaining backends and exit...
329   */
330 
331   if (active_backends > 0)
332   {
333     for (i = 0; i < num_backends; i ++)
334       if (backends[i].pid)
335 	kill(backends[i].pid, SIGTERM);
336   }
337 
338   return (0);
339 }
340 
341 
342 /*
343  * 'add_device()' - Add a new device to the list.
344  */
345 
346 static int				/* O - 0 on success, -1 on error */
add_device(const char * device_class,const char * device_make_and_model,const char * device_info,const char * device_uri,const char * device_id,const char * device_location)347 add_device(
348     const char *device_class,		/* I - Device class */
349     const char *device_make_and_model,	/* I - Device make and model */
350     const char *device_info,		/* I - Device information */
351     const char *device_uri,		/* I - Device URI */
352     const char *device_id,		/* I - 1284 device ID */
353     const char *device_location)	/* I - Physical location */
354 {
355   cupsd_device_t	*device;	/* New device */
356 
357 
358  /*
359   * Allocate memory for the device record...
360   */
361 
362   if ((device = calloc(1, sizeof(cupsd_device_t))) == NULL)
363   {
364     fputs("ERROR: [cups-deviced] Ran out of memory allocating a device!\n",
365           stderr);
366     return (-1);
367   }
368 
369  /*
370   * Copy the strings over...
371   */
372 
373   strlcpy(device->device_class, device_class, sizeof(device->device_class));
374   strlcpy(device->device_info, device_info, sizeof(device->device_info));
375   strlcpy(device->device_uri, device_uri, sizeof(device->device_uri));
376 
377  /*
378   * Add the device to the array and return...
379   */
380 
381   if (cupsArrayFind(devices, device))
382   {
383    /*
384     * Avoid duplicates!
385     */
386 
387     free(device);
388   }
389   else
390   {
391     cupsArrayAdd(devices, device);
392 
393     if (device_limit <= 0 || cupsArrayCount(devices) < device_limit)
394     {
395      /*
396       * Send device info...
397       */
398 
399       cupsdSendIPPGroup(IPP_TAG_PRINTER);
400       if (send_class)
401 	cupsdSendIPPString(IPP_TAG_KEYWORD, "device-class",
402 	                   device_class);
403       if (send_info)
404 	cupsdSendIPPString(IPP_TAG_TEXT, "device-info", device_info);
405       if (send_make_and_model)
406 	cupsdSendIPPString(IPP_TAG_TEXT, "device-make-and-model",
407 			   device_make_and_model);
408       if (send_uri)
409 	cupsdSendIPPString(IPP_TAG_URI, "device-uri", device_uri);
410       if (send_id)
411 	cupsdSendIPPString(IPP_TAG_TEXT, "device-id",
412 	                   device_id ? device_id : "");
413       if (send_location)
414 	cupsdSendIPPString(IPP_TAG_TEXT, "device-location",
415 	                   device_location ? device_location : "");
416 
417       fflush(stdout);
418       fputs("DEBUG: Flushed attributes...\n", stderr);
419     }
420   }
421 
422   return (0);
423 }
424 
425 
426 /*
427  * 'compare_devices()' - Compare device names to eliminate duplicates.
428  */
429 
430 static int				/* O - Result of comparison */
compare_devices(cupsd_device_t * d0,cupsd_device_t * d1)431 compare_devices(cupsd_device_t *d0,	/* I - First device */
432                 cupsd_device_t *d1)	/* I - Second device */
433 {
434   int		diff;			/* Difference between strings */
435 
436 
437  /*
438   * Sort devices by device-info, device-class, and device-uri...
439   */
440 
441   if ((diff = cupsdCompareNames(d0->device_info, d1->device_info)) != 0)
442     return (diff);
443   else if ((diff = _cups_strcasecmp(d0->device_class, d1->device_class)) != 0)
444     return (diff);
445   else
446     return (_cups_strcasecmp(d0->device_uri, d1->device_uri));
447 }
448 
449 
450 /*
451  * 'get_current_time()' - Get the current time as a double value in seconds.
452  */
453 
454 static double				/* O - Time in seconds */
get_current_time(void)455 get_current_time(void)
456 {
457   struct timeval	curtime;	/* Current time */
458 
459 
460   gettimeofday(&curtime, NULL);
461 
462   return (curtime.tv_sec + 0.000001 * curtime.tv_usec);
463 }
464 
465 
466 /*
467  * 'get_device()' - Get a device from a backend.
468  */
469 
470 static int				/* O - 0 on success, -1 on error */
get_device(cupsd_backend_t * backend)471 get_device(cupsd_backend_t *backend)	/* I - Backend to read from */
472 {
473   char	line[2048],			/* Line from backend */
474 	temp[2048],			/* Copy of line */
475 	*ptr,				/* Pointer into line */
476 	*dclass,			/* Device class */
477 	*uri,				/* Device URI */
478 	*make_model,			/* Make and model */
479 	*info,				/* Device info */
480 	*device_id,			/* 1284 device ID */
481 	*location;			/* Physical location */
482 
483 
484   if (cupsFileGets(backend->pipe, line, sizeof(line)))
485   {
486    /*
487     * Each line is of the form:
488     *
489     *   class URI "make model" "name" ["1284 device ID"] ["location"]
490     */
491 
492     strlcpy(temp, line, sizeof(temp));
493 
494    /*
495     * device-class
496     */
497 
498     dclass = temp;
499 
500     for (ptr = temp; *ptr; ptr ++)
501       if (isspace(*ptr & 255))
502         break;
503 
504     while (isspace(*ptr & 255))
505       *ptr++ = '\0';
506 
507    /*
508     * device-uri
509     */
510 
511     if (!*ptr)
512       goto error;
513 
514     for (uri = ptr; *ptr; ptr ++)
515       if (isspace(*ptr & 255))
516         break;
517 
518     while (isspace(*ptr & 255))
519       *ptr++ = '\0';
520 
521    /*
522     * device-make-and-model
523     */
524 
525     if (*ptr != '\"')
526       goto error;
527 
528     for (ptr ++, make_model = ptr; *ptr && *ptr != '\"'; ptr ++)
529     {
530       if (*ptr == '\\' && ptr[1])
531         _cups_strcpy(ptr, ptr + 1);
532     }
533 
534     if (*ptr != '\"')
535       goto error;
536 
537     for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
538 
539    /*
540     * device-info
541     */
542 
543     if (*ptr != '\"')
544       goto error;
545 
546     for (ptr ++, info = ptr; *ptr && *ptr != '\"'; ptr ++)
547     {
548       if (*ptr == '\\' && ptr[1])
549         _cups_strcpy(ptr, ptr + 1);
550     }
551 
552     if (*ptr != '\"')
553       goto error;
554 
555     for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
556 
557    /*
558     * device-id
559     */
560 
561     if (*ptr == '\"')
562     {
563       for (ptr ++, device_id = ptr; *ptr && *ptr != '\"'; ptr ++)
564       {
565 	if (*ptr == '\\' && ptr[1])
566 	  _cups_strcpy(ptr, ptr + 1);
567       }
568 
569       if (*ptr != '\"')
570 	goto error;
571 
572       for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
573 
574      /*
575       * device-location
576       */
577 
578       if (*ptr == '\"')
579       {
580 	for (ptr ++, location = ptr; *ptr && *ptr != '\"'; ptr ++)
581 	{
582 	  if (*ptr == '\\' && ptr[1])
583 	    _cups_strcpy(ptr, ptr + 1);
584 	}
585 
586 	if (*ptr != '\"')
587 	  goto error;
588 
589 	*ptr = '\0';
590       }
591       else
592         location = NULL;
593     }
594     else
595     {
596       device_id = NULL;
597       location  = NULL;
598     }
599 
600    /*
601     * Add the device to the array of available devices...
602     */
603 
604     if (!add_device(dclass, make_model, info, uri, device_id, location))
605       fprintf(stderr, "DEBUG: [cups-deviced] Found device \"%s\"...\n", uri);
606 
607     return (0);
608   }
609 
610  /*
611   * End of file...
612   */
613 
614   cupsFileClose(backend->pipe);
615   backend->pipe = NULL;
616 
617   return (-1);
618 
619  /*
620   * Bad format; strip trailing newline and write an error message.
621   */
622 
623   error:
624 
625   if (line[strlen(line) - 1] == '\n')
626     line[strlen(line) - 1] = '\0';
627 
628   fprintf(stderr, "ERROR: [cups-deviced] Bad line from \"%s\": %s\n",
629 	  backend->name, line);
630   return (0);
631 }
632 
633 
634 /*
635  * 'process_children()' - Process all dead children...
636  */
637 
638 static void
process_children(void)639 process_children(void)
640 {
641   int			i;		/* Looping var */
642   int			status;		/* Exit status of child */
643   int			pid;		/* Process ID of child */
644   cupsd_backend_t	*backend;	/* Current backend */
645   const char		*name;		/* Name of process */
646 
647 
648  /*
649   * Reset the dead_children flag...
650   */
651 
652   dead_children = 0;
653 
654  /*
655   * Collect the exit status of some children...
656   */
657 
658 #ifdef HAVE_WAITPID
659   while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
660 #elif defined(HAVE_WAIT3)
661   while ((pid = wait3(&status, WNOHANG, NULL)) > 0)
662 #else
663   if ((pid = wait(&status)) > 0)
664 #endif /* HAVE_WAITPID */
665   {
666     if (status == SIGTERM)
667       status = 0;
668 
669     for (i = num_backends, backend = backends; i > 0; i --, backend ++)
670       if (backend->pid == pid)
671         break;
672 
673     if (i > 0)
674     {
675       name            = backend->name;
676       backend->pid    = 0;
677       backend->status = status;
678 
679       active_backends --;
680     }
681     else
682       name = "Unknown";
683 
684     if (status)
685     {
686       if (WIFEXITED(status))
687 	fprintf(stderr,
688 	        "ERROR: [cups-deviced] PID %d (%s) stopped with status %d!\n",
689 		pid, name, WEXITSTATUS(status));
690       else
691 	fprintf(stderr,
692 	        "ERROR: [cups-deviced] PID %d (%s) crashed on signal %d!\n",
693 		pid, name, WTERMSIG(status));
694     }
695     else
696       fprintf(stderr,
697               "DEBUG: [cups-deviced] PID %d (%s) exited with no errors.\n",
698 	      pid, name);
699   }
700 }
701 
702 
703 /*
704  * 'sigchld_handler()' - Handle 'child' signals from old processes.
705  */
706 
707 static void
sigchld_handler(int sig)708 sigchld_handler(int sig)		/* I - Signal number */
709 {
710   (void)sig;
711 
712  /*
713   * Flag that we have dead children...
714   */
715 
716   dead_children = 1;
717 
718  /*
719   * Reset the signal handler as needed...
720   */
721 
722 #if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION)
723   signal(SIGCLD, sigchld_handler);
724 #endif /* !HAVE_SIGSET && !HAVE_SIGACTION */
725 }
726 
727 
728 /*
729  * 'start_backend()' - Run a backend to gather the available devices.
730  */
731 
732 static int				/* O - 0 on success, -1 on error */
start_backend(const char * name,int root)733 start_backend(const char *name,		/* I - Backend to run */
734               int        root)		/* I - Run as root? */
735 {
736   const char		*server_bin;	/* CUPS_SERVERBIN environment variable */
737   char			program[1024];	/* Full path to backend */
738   cupsd_backend_t	*backend;	/* Current backend */
739   char			*argv[2];	/* Command-line arguments */
740 
741 
742   if (num_backends >= MAX_BACKENDS)
743   {
744     fprintf(stderr, "ERROR: Too many backends (%d)!\n", num_backends);
745     return (-1);
746   }
747 
748   if ((server_bin = getenv("CUPS_SERVERBIN")) == NULL)
749     server_bin = CUPS_SERVERBIN;
750 
751   snprintf(program, sizeof(program), "%s/backend/%s", server_bin, name);
752 
753   if (_cupsFileCheck(program, _CUPS_FILE_CHECK_PROGRAM, !geteuid(),
754                      _cupsFileCheckFilter, NULL))
755     return (-1);
756 
757   backend = backends + num_backends;
758 
759   argv[0] = (char *)name;
760   argv[1] = NULL;
761 
762   if ((backend->pipe = cupsdPipeCommand(&(backend->pid), program, argv,
763                                         root ? 0 : normal_user)) == NULL)
764   {
765     fprintf(stderr, "ERROR: [cups-deviced] Unable to execute \"%s\" - %s\n",
766             program, strerror(errno));
767     return (-1);
768   }
769 
770  /*
771   * Fill in the rest of the backend information...
772   */
773 
774   fprintf(stderr, "DEBUG: [cups-deviced] Started backend %s (PID %d)\n",
775           program, backend->pid);
776 
777   backend_fds[num_backends].fd     = cupsFileNumber(backend->pipe);
778   backend_fds[num_backends].events = POLLIN;
779 
780   backend->name   = strdup(name);
781   backend->status = 0;
782   backend->count  = 0;
783 
784   active_backends ++;
785   num_backends ++;
786 
787   return (0);
788 }
789