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