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