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