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