1/*
2 * "$Id: dbus.c 11645 2014-02-27 16:35:53Z msweet $"
3 *
4 * D-Bus notifier for CUPS.
5 *
6 * Copyright 2008-2014 by Apple Inc.
7 * Copyright (C) 2011, 2013 Red Hat, Inc.
8 * Copyright (C) 2007 Tim Waugh <twaugh@redhat.com>
9 * Copyright 1997-2005 by Easy Software Products.
10 *
11 * These coded instructions, statements, and computer programs are the
12 * property of Apple Inc. and are protected by Federal copyright
13 * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
14 * which should have been included with this file.  If this file is
15 * file is missing or damaged, see the license at "http://www.cups.org/".
16 */
17
18/*
19 * Include necessary headers...
20 */
21
22#include <cups/cups.h>
23#include <cups/string-private.h>
24#include <fcntl.h>
25#include <signal.h>
26#include <sys/stat.h>
27#include <sys/types.h>
28#include <unistd.h>
29
30#ifdef HAVE_DBUS
31#  include <dbus/dbus.h>
32#  ifdef HAVE_DBUS_MESSAGE_ITER_INIT_APPEND
33#    define dbus_message_append_iter_init dbus_message_iter_init_append
34#    define dbus_message_iter_append_string(i,v) dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, v)
35#    define dbus_message_iter_append_uint32(i,v) dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, v)
36#    define dbus_message_iter_append_boolean(i,v) dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, v)
37#  endif /* HAVE_DBUS_MESSAGE_ITER_INIT_APPEND */
38
39
40/*
41 * D-Bus object: org.cups.cupsd.Notifier
42 * D-Bus object path: /org/cups/cupsd/Notifier
43 *
44 * D-Bus interface name: org.cups.cupsd.Notifier
45 *
46 * Signals:
47 *
48 * ServerRestarted(STRING text)
49 * Server has restarted.
50 *
51 * ServerStarted(STRING text)
52 * Server has started.
53 *
54 * ServerStopped(STRING text)
55 * Server has stopped.
56 *
57 * ServerAudit(STRING text)
58 * Security-related event.
59 *
60 * PrinterRestarted(STRING text,
61 *                  STRING printer-uri,
62 *                  STRING printer-name,
63 *                  UINT32 printer-state,
64 *                  STRING printer-state-reasons,
65 *                  BOOLEAN printer-is-accepting-jobs)
66 * Printer has restarted.
67 *
68 * PrinterShutdown(STRING text,
69 *                 STRING printer-uri,
70 *                 STRING printer-name,
71 *                 UINT32 printer-state,
72 *                 STRING printer-state-reasons,
73 *                 BOOLEAN printer-is-accepting-jobs)
74 * Printer has shutdown.
75 *
76 * PrinterStopped(STRING text,
77 *                STRING printer-uri,
78 *                STRING printer-name,
79 *                UINT32 printer-state,
80 *                STRING printer-state-reasons,
81 *                BOOLEAN printer-is-accepting-jobs)
82 * Printer has stopped.
83 *
84 * PrinterStateChanged(STRING text,
85 *                     STRING printer-uri,
86 *                     STRING printer-name,
87 *                     UINT32 printer-state,
88 *                     STRING printer-state-reasons,
89 *                     BOOLEAN printer-is-accepting-jobs)
90 * Printer state has changed.
91 *
92 * PrinterFinishingsChanged(STRING text,
93 *                          STRING printer-uri,
94 *                          STRING printer-name,
95 *                          UINT32 printer-state,
96 *                          STRING printer-state-reasons,
97 *                          BOOLEAN printer-is-accepting-jobs)
98 * Printer's finishings-supported attribute has changed.
99 *
100 * PrinterMediaChanged(STRING text,
101 *                     STRING printer-uri,
102 *                     STRING printer-name,
103 *                     UINT32 printer-state,
104 *                     STRING printer-state-reasons,
105 *                     BOOLEAN printer-is-accepting-jobs)
106 * Printer's media-supported attribute has changed.
107 *
108 * PrinterAdded(STRING text,
109 *              STRING printer-uri,
110 *              STRING printer-name,
111 *              UINT32 printer-state,
112 *              STRING printer-state-reasons,
113 *              BOOLEAN printer-is-accepting-jobs)
114 * Printer has been added.
115 *
116 * PrinterDeleted(STRING text,
117 *                STRING printer-uri,
118 *                STRING printer-name,
119 *                UINT32 printer-state,
120 *                STRING printer-state-reasons,
121 *                BOOLEAN printer-is-accepting-jobs)
122 * Printer has been deleted.
123 *
124 * PrinterModified(STRING text,
125 *                 STRING printer-uri,
126 *                 STRING printer-name,
127 *                 UINT32 printer-state,
128 *                 STRING printer-state-reasons,
129 *                 BOOLEAN printer-is-accepting-jobs)
130 * Printer has been modified.
131 *
132 * text describes the event.
133 * printer-state-reasons is a comma-separated list.
134 * If printer-uri is "" in a Job* signal, the other printer-* parameters
135 * must be ignored.
136 * If the job name is not know, job-name will be "".
137 */
138
139/*
140 * Constants...
141 */
142
143enum
144{
145  PARAMS_NONE,
146  PARAMS_PRINTER,
147  PARAMS_JOB
148};
149
150
151/*
152 * Global variables...
153 */
154
155static char		lock_filename[1024];	/* Lock filename */
156
157
158/*
159 * Local functions...
160 */
161
162static int	acquire_lock(int *fd, char *lockfile, size_t locksize);
163static void	release_lock(void);
164
165
166/*
167 * 'main()' - Read events and send DBUS notifications.
168 */
169
170int					/* O - Exit status */
171main(int  argc,				/* I - Number of command-line args */
172     char *argv[])			/* I - Command-line arguments */
173{
174  ipp_t			*msg;		/* Event message from scheduler */
175  ipp_state_t		state;		/* IPP event state */
176  struct sigaction	action;		/* POSIX sigaction data */
177  DBusConnection	*con = NULL;	/* Connection to DBUS server */
178  DBusError		error;		/* Error, if any */
179  DBusMessage		*message;	/* Message to send */
180  DBusMessageIter	iter;		/* Iterator for message data */
181  int			lock_fd = -1;	/* Lock file descriptor */
182
183
184 /*
185  * Don't buffer stderr...
186  */
187
188  setbuf(stderr, NULL);
189
190 /*
191  * Ignore SIGPIPE signals...
192  */
193
194  memset(&action, 0, sizeof(action));
195  action.sa_handler = SIG_IGN;
196  sigaction(SIGPIPE, &action, NULL);
197
198 /*
199  * Validate command-line options...
200  */
201
202  if (argc != 3)
203  {
204    fputs("Usage: dbus dbus:/// notify-user-data\n", stderr);
205    return (1);
206  }
207
208  if (strncmp(argv[1], "dbus:", 5))
209  {
210    fprintf(stderr, "ERROR: Bad URI \"%s\"!\n", argv[1]);
211    return (1);
212  }
213
214 /*
215  * Loop forever until we run out of events...
216  */
217
218  for (;;)
219  {
220    ipp_attribute_t	*attr;		/* Current attribute */
221    const char		*event;		/* Event name */
222    const char		*signame = NULL;/* DBUS signal name */
223    char		*printer_reasons = NULL;
224					/* Printer reasons string */
225    char		*job_reasons = NULL;
226					/* Job reasons string */
227    const char		*nul = "";	/* Empty string value */
228    int			no = 0;		/* Boolean "no" value */
229    int			params = PARAMS_NONE;
230					/* What parameters to include? */
231
232
233   /*
234    * Get the next event...
235    */
236
237    msg = ippNew();
238    while ((state = ippReadFile(0, msg)) != IPP_DATA)
239    {
240      if (state <= IPP_IDLE)
241        break;
242    }
243
244    fprintf(stderr, "DEBUG: state=%d\n", state);
245
246    if (state == IPP_ERROR)
247      fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr);
248
249    if (state <= IPP_IDLE)
250    {
251     /*
252      * Out of messages, free memory and then exit...
253      */
254
255      ippDelete(msg);
256      break;
257    }
258
259   /*
260    * Verify connection to DBUS server...
261    */
262
263    if (con && !dbus_connection_get_is_connected(con))
264    {
265      dbus_connection_unref(con);
266      con = NULL;
267    }
268
269    if (!con)
270    {
271      dbus_error_init(&error);
272
273      con = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
274      if (!con)
275	dbus_error_free(&error);
276      else
277	fputs("DEBUG: Connected to D-BUS\n", stderr);
278    }
279
280    if (!con)
281      continue;
282
283    if (lock_fd == -1 &&
284        acquire_lock(&lock_fd, lock_filename, sizeof(lock_filename)))
285      continue;
286
287    attr = ippFindAttribute(msg, "notify-subscribed-event",
288			    IPP_TAG_KEYWORD);
289    if (!attr)
290      continue;
291
292    event = ippGetString(attr, 0, NULL);
293    if (!strncmp(event, "server-", 7))
294    {
295      const char *word2 = event + 7;	/* Second word */
296
297      if (!strcmp(word2, "restarted"))
298	signame = "ServerRestarted";
299      else if (!strcmp(word2, "started"))
300	signame = "ServerStarted";
301      else if (!strcmp(word2, "stopped"))
302	signame = "ServerStopped";
303      else if (!strcmp(word2, "audit"))
304	signame = "ServerAudit";
305      else
306	continue;
307    }
308    else if (!strncmp(event, "printer-", 8))
309    {
310      const char *word2 = event + 8;	/* Second word */
311
312      params = PARAMS_PRINTER;
313      if (!strcmp(word2, "restarted"))
314	signame = "PrinterRestarted";
315      else if (!strcmp(word2, "shutdown"))
316	signame = "PrinterShutdown";
317      else if (!strcmp(word2, "stopped"))
318	signame = "PrinterStopped";
319      else if (!strcmp(word2, "state-changed"))
320	signame = "PrinterStateChanged";
321      else if (!strcmp(word2, "finishings-changed"))
322	signame = "PrinterFinishingsChanged";
323      else if (!strcmp(word2, "media-changed"))
324	signame = "PrinterMediaChanged";
325      else if (!strcmp(word2, "added"))
326	signame = "PrinterAdded";
327      else if (!strcmp(word2, "deleted"))
328	signame = "PrinterDeleted";
329      else if (!strcmp(word2, "modified"))
330	signame = "PrinterModified";
331      else
332	continue;
333    }
334    else if (!strncmp(event, "job-", 4))
335    {
336      const char *word2 = event + 4;	/* Second word */
337
338      params = PARAMS_JOB;
339      if (!strcmp(word2, "state-changed"))
340	signame = "JobState";
341      else if (!strcmp(word2, "created"))
342	signame = "JobCreated";
343      else if (!strcmp(word2, "completed"))
344	signame = "JobCompleted";
345      else if (!strcmp(word2, "stopped"))
346	signame = "JobStopped";
347      else if (!strcmp(word2, "config-changed"))
348	signame = "JobConfigChanged";
349      else if (!strcmp(word2, "progress"))
350	signame = "JobProgress";
351      else
352	continue;
353    }
354    else
355      continue;
356
357    /*
358     * Create and send the new message...
359     */
360
361    fprintf(stderr, "DEBUG: %s\n", signame);
362    message = dbus_message_new_signal("/org/cups/cupsd/Notifier",
363				      "org.cups.cupsd.Notifier",
364				      signame);
365
366    dbus_message_append_iter_init(message, &iter);
367    attr = ippFindAttribute(msg, "notify-text", IPP_TAG_TEXT);
368    if (attr)
369    {
370      const char *val = ippGetString(attr, 0, NULL);
371      if (!dbus_message_iter_append_string(&iter, &val))
372        goto bail;
373    }
374    else
375      goto bail;
376
377    if (params >= PARAMS_PRINTER)
378    {
379      char	*p;			/* Pointer into printer_reasons */
380      size_t	reasons_length;		/* Required size of printer_reasons */
381      int	i;			/* Looping var */
382      int	have_printer_params = 1;/* Do we have printer URI? */
383
384      /* STRING printer-uri or "" */
385      attr = ippFindAttribute(msg, "notify-printer-uri", IPP_TAG_URI);
386      if (attr)
387      {
388        const char *val = ippGetString(attr, 0, NULL);
389        if (!dbus_message_iter_append_string(&iter, &val))
390	  goto bail;
391      }
392      else
393      {
394	have_printer_params = 0;
395	dbus_message_iter_append_string(&iter, &nul);
396      }
397
398      /* STRING printer-name */
399      if (have_printer_params)
400      {
401	attr = ippFindAttribute(msg, "printer-name", IPP_TAG_NAME);
402        if (attr)
403        {
404          const char *val = ippGetString(attr, 0, NULL);
405          if (!dbus_message_iter_append_string(&iter, &val))
406            goto bail;
407        }
408        else
409          goto bail;
410      }
411      else
412	dbus_message_iter_append_string(&iter, &nul);
413
414      /* UINT32 printer-state */
415      if (have_printer_params)
416      {
417	attr = ippFindAttribute(msg, "printer-state", IPP_TAG_ENUM);
418	if (attr)
419	{
420	  dbus_uint32_t val = (dbus_uint32_t)ippGetInteger(attr, 0);
421	  dbus_message_iter_append_uint32(&iter, &val);
422	}
423	else
424	  goto bail;
425      }
426      else
427	dbus_message_iter_append_uint32(&iter, &no);
428
429      /* STRING printer-state-reasons */
430      if (have_printer_params)
431      {
432	attr = ippFindAttribute(msg, "printer-state-reasons",
433				IPP_TAG_KEYWORD);
434	if (attr)
435	{
436	  int num_values = ippGetCount(attr);
437	  for (reasons_length = 0, i = 0; i < num_values; i++)
438	    /* All need commas except the last, which needs a nul byte. */
439	    reasons_length += 1 + strlen(ippGetString(attr, i, NULL));
440	  printer_reasons = malloc(reasons_length);
441	  if (!printer_reasons)
442	    goto bail;
443	  p = printer_reasons;
444	  for (i = 0; i < num_values; i++)
445	  {
446	    if (i)
447	      *p++ = ',';
448
449	    strlcpy(p, ippGetString(attr, i, NULL), reasons_length - (size_t)(p - printer_reasons));
450	    p += strlen(p);
451	  }
452	  if (!dbus_message_iter_append_string(&iter, &printer_reasons))
453	    goto bail;
454	}
455	else
456	  goto bail;
457      }
458      else
459	dbus_message_iter_append_string(&iter, &nul);
460
461      /* BOOL printer-is-accepting-jobs */
462      if (have_printer_params)
463      {
464	attr = ippFindAttribute(msg, "printer-is-accepting-jobs",
465				IPP_TAG_BOOLEAN);
466	if (attr)
467	{
468	  dbus_bool_t val = (dbus_bool_t)ippGetBoolean(attr, 0);
469	  dbus_message_iter_append_boolean(&iter, &val);
470	}
471	else
472	  goto bail;
473      }
474      else
475	dbus_message_iter_append_boolean(&iter, &no);
476    }
477
478    if (params >= PARAMS_JOB)
479    {
480      char	*p;			/* Pointer into job_reasons */
481      size_t	reasons_length;		/* Required size of job_reasons */
482      int	i;			/* Looping var */
483
484      /* UINT32 job-id */
485      attr = ippFindAttribute(msg, "notify-job-id", IPP_TAG_INTEGER);
486      if (attr)
487      {
488        dbus_uint32_t val = (dbus_uint32_t)ippGetInteger(attr, 0);
489        dbus_message_iter_append_uint32(&iter, &val);
490      }
491      else
492	goto bail;
493
494      /* UINT32 job-state */
495      attr = ippFindAttribute(msg, "job-state", IPP_TAG_ENUM);
496      if (attr)
497      {
498        dbus_uint32_t val = (dbus_uint32_t)ippGetInteger(attr, 0);
499        dbus_message_iter_append_uint32(&iter, &val);
500      }
501      else
502	goto bail;
503
504      /* STRING job-state-reasons */
505      attr = ippFindAttribute(msg, "job-state-reasons", IPP_TAG_KEYWORD);
506      if (attr)
507      {
508	int num_values = ippGetCount(attr);
509	for (reasons_length = 0, i = 0; i < num_values; i++)
510	  /* All need commas except the last, which needs a nul byte. */
511	  reasons_length += 1 + strlen(ippGetString(attr, i, NULL));
512	job_reasons = malloc(reasons_length);
513	if (!job_reasons)
514	  goto bail;
515	p = job_reasons;
516	for (i = 0; i < num_values; i++)
517	{
518	  if (i)
519	    *p++ = ',';
520
521	  strlcpy(p, ippGetString(attr, i, NULL), reasons_length - (size_t)(p - job_reasons));
522	  p += strlen(p);
523	}
524	if (!dbus_message_iter_append_string(&iter, &job_reasons))
525	  goto bail;
526      }
527      else
528	goto bail;
529
530      /* STRING job-name or "" */
531      attr = ippFindAttribute(msg, "job-name", IPP_TAG_NAME);
532      if (attr)
533      {
534        const char *val = ippGetString(attr, 0, NULL);
535        if (!dbus_message_iter_append_string(&iter, &val))
536          goto bail;
537      }
538      else
539	dbus_message_iter_append_string(&iter, &nul);
540
541      /* UINT32 job-impressions-completed */
542      attr = ippFindAttribute(msg, "job-impressions-completed",
543			      IPP_TAG_INTEGER);
544      if (attr)
545      {
546        dbus_uint32_t val = (dbus_uint32_t)ippGetInteger(attr, 0);
547        dbus_message_iter_append_uint32(&iter, &val);
548      }
549      else
550	goto bail;
551    }
552
553    dbus_connection_send(con, message, NULL);
554    dbus_connection_flush(con);
555
556   /*
557    * Cleanup...
558    */
559
560    bail:
561
562    dbus_message_unref(message);
563
564    if (printer_reasons)
565      free(printer_reasons);
566
567    if (job_reasons)
568      free(job_reasons);
569
570    ippDelete(msg);
571  }
572
573 /*
574  * Remove lock file...
575  */
576
577  if (lock_fd >= 0)
578  {
579    close(lock_fd);
580    release_lock();
581  }
582
583  return (0);
584}
585
586
587/*
588 * 'release_lock()' - Release the singleton lock.
589 */
590
591static void
592release_lock(void)
593{
594  unlink(lock_filename);
595}
596
597
598/*
599 * 'handle_sigterm()' - Handle SIGTERM signal.
600 */
601static void
602handle_sigterm(int signum)
603{
604  release_lock();
605  _exit(0);
606}
607
608/*
609 * 'acquire_lock()' - Acquire a lock so we only have a single notifier running.
610 */
611
612static int				/* O - 0 on success, -1 on failure */
613acquire_lock(int    *fd,		/* O - Lock file descriptor */
614             char   *lockfile,		/* I - Lock filename buffer */
615	     size_t locksize)		/* I - Size of filename buffer */
616{
617  const char		*tmpdir;	/* Temporary directory */
618  struct sigaction	action;		/* POSIX sigaction data */
619
620
621 /*
622  * Figure out where to put the lock file...
623  */
624
625  if ((tmpdir = getenv("TMPDIR")) == NULL)
626    tmpdir = "/tmp";
627
628  snprintf(lockfile, locksize, "%s/cups-dbus-notifier-lockfile", tmpdir);
629
630 /*
631  * Create the lock file and fail if it already exists...
632  */
633
634  if ((*fd = open(lockfile, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) < 0)
635    return (-1);
636
637 /*
638  * Set a SIGTERM handler to make sure we release the lock if the
639  * scheduler decides to stop us.
640  */
641  memset(&action, 0, sizeof(action));
642  action.sa_handler = handle_sigterm;
643  sigaction(SIGTERM, &action, NULL);
644
645  return (0);
646}
647#else /* !HAVE_DBUS */
648int
649main(void)
650{
651  return (1);
652}
653#endif /* HAVE_DBUS */
654
655
656/*
657 * End of "$Id: dbus.c 11645 2014-02-27 16:35:53Z msweet $".
658 */
659