summaryrefslogtreecommitdiff
path: root/notifier
diff options
context:
space:
mode:
authorDidier Raboud <odyx@debian.org>2012-10-25 20:44:09 +0200
committerDidier Raboud <odyx@debian.org>2012-10-25 20:44:09 +0200
commitb0950df0920600c7b76f0aada5fb2d7dfe01eb7b (patch)
tree572af97f88a95670b867d6986953623579edc26e /notifier
parent3621a2b09269d8c9c136b4317193c31e96471ec6 (diff)
downloadcups-b0950df0920600c7b76f0aada5fb2d7dfe01eb7b.tar.gz
Imported Upstream version 1.4.4upstream/1.4.4
Diffstat (limited to 'notifier')
-rw-r--r--notifier/Dependencies15
-rw-r--r--notifier/Makefile162
-rw-r--r--notifier/dbus.c552
-rw-r--r--notifier/mailto.c790
-rw-r--r--notifier/rss.c732
-rw-r--r--notifier/testnotify.c285
6 files changed, 2536 insertions, 0 deletions
diff --git a/notifier/Dependencies b/notifier/Dependencies
new file mode 100644
index 00000000..8f5cfed1
--- /dev/null
+++ b/notifier/Dependencies
@@ -0,0 +1,15 @@
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+
+dbus.o: ../cups/cups.h ../cups/ipp.h ../cups/http.h ../cups/versioning.h
+dbus.o: ../cups/ppd.h ../cups/array.h ../cups/file.h ../cups/language.h
+dbus.o: ../cups/string.h ../config.h
+mailto.o: ../cups/cups.h ../cups/ipp.h ../cups/http.h ../cups/versioning.h
+mailto.o: ../cups/ppd.h ../cups/array.h ../cups/file.h ../cups/language.h
+mailto.o: ../cups/i18n.h ../cups/transcode.h ../cups/string.h ../config.h
+rss.o: ../cups/cups.h ../cups/ipp.h ../cups/http.h ../cups/versioning.h
+rss.o: ../cups/ppd.h ../cups/array.h ../cups/file.h ../cups/language.h
+rss.o: ../cups/language.h ../cups/string.h ../config.h ../cups/array.h
+testnotify.o: ../cups/cups.h ../cups/ipp.h ../cups/http.h
+testnotify.o: ../cups/versioning.h ../cups/ppd.h ../cups/array.h
+testnotify.o: ../cups/file.h ../cups/language.h ../cups/language.h
+testnotify.o: ../cups/string.h ../config.h
diff --git a/notifier/Makefile b/notifier/Makefile
new file mode 100644
index 00000000..53d0fc08
--- /dev/null
+++ b/notifier/Makefile
@@ -0,0 +1,162 @@
+#
+# "$Id: Makefile 8497 2009-04-12 23:04:51Z mike $"
+#
+# Notifier makefile for the Common UNIX Printing System (CUPS).
+#
+# Copyright 2007-2008 by Apple Inc.
+# Copyright 1997-2007 by Easy Software Products, all rights reserved.
+#
+# These coded instructions, statements, and computer programs are the
+# property of Apple Inc. and are protected by Federal copyright
+# law. Distribution and use rights are outlined in the file "LICENSE.txt"
+# which should have been included with this file. If this file is
+# file is missing or damaged, see the license at "http://www.cups.org/".
+#
+
+include ../Makedefs
+
+
+NOTIFIERS = $(DBUS_NOTIFIER) mailto rss
+TARGETS = $(NOTIFIERS) testnotify
+OBJS = dbus.o mailto.o rss.o testnotify.o
+
+
+#
+# Make all targets...
+#
+
+all: $(TARGETS)
+
+
+#
+# Make library targets...
+#
+
+libs:
+
+
+#
+# Make unit tests...
+#
+
+unittests:
+
+
+#
+# Clean all object files...
+#
+
+clean:
+ $(RM) $(OBJS) $(TARGETS) dbus.h
+
+
+#
+# Install all targets...
+#
+
+install: all install-data install-headers install-libs install-exec
+
+
+#
+# Install data files...
+#
+
+install-data:
+ $(INSTALL_DIR) -m 775 -g $(CUPS_GROUP) $(CACHEDIR)/rss
+
+
+#
+# Install programs...
+#
+
+install-exec:
+ echo Installing notifiers in $(SERVERBIN)/notifier...
+ $(INSTALL_DIR) -m 755 $(SERVERBIN)/notifier
+ for file in $(NOTIFIERS); do \
+ $(INSTALL_BIN) $$file $(SERVERBIN)/notifier; \
+ done
+ if test "x$(SYMROOT)" != "x"; then \
+ $(INSTALL_DIR) $(SYMROOT); \
+ cp $(NOTIFIERS) $(SYMROOT); \
+ fi
+
+
+#
+# Install headers...
+#
+
+install-headers:
+
+
+#
+# Install libraries...
+#
+
+install-libs:
+
+
+#
+# Uninstall all targets...
+#
+
+uninstall:
+ for file in $(NOTIFIERS); do \
+ $(RM) $(SERVERBIN)/notifier/$$file; \
+ done
+ -$(RMDIR) $(SERVERBIN)/notifier
+ -$(RMDIR) $(SERVERBIN)
+ -$(RMDIR) $(CACHEDIR)/rss
+
+
+#
+# Update dependencies (without system header dependencies...)
+#
+
+depend:
+ makedepend -Y -I.. -fDependencies $(OBJS:.o=.c) >/dev/null 2>&1
+
+
+#
+# dbus
+#
+
+dbus: dbus.o ../cups/$(LIBCUPS)
+ echo Linking $@...
+ $(CC) $(LDFLAGS) -o dbus dbus.o $(DBUS_NOTIFIERLIBS) $(LIBS)
+
+
+#
+# mailto
+#
+
+mailto: mailto.o ../cups/$(LIBCUPS)
+ echo Linking $@...
+ $(CC) $(LDFLAGS) -o mailto mailto.o $(LIBS)
+
+
+#
+# rss
+#
+
+rss: rss.o ../cups/$(LIBCUPS)
+ echo Linking $@...
+ $(CC) $(LDFLAGS) -o rss rss.o $(LIBS)
+
+
+#
+# testnotify
+#
+
+testnotify: testnotify.o ../cups/$(LIBCUPS)
+ echo Linking $@...
+ $(CC) $(LDFLAGS) -o testnotify testnotify.o $(LIBS)
+
+
+$(OBJS): ../Makedefs
+
+include Dependencies
+
+
+#
+# End of "$Id: Makefile 8497 2009-04-12 23:04:51Z mike $".
+#
diff --git a/notifier/dbus.c b/notifier/dbus.c
new file mode 100644
index 00000000..a06fcd70
--- /dev/null
+++ b/notifier/dbus.c
@@ -0,0 +1,552 @@
+/*
+ * "$Id: dbus.c 7916 2008-09-07 22:27:01Z mike $"
+ *
+ * D-Bus notifier for the Common UNIX Printing System (CUPS).
+ *
+ * Copyright 2008 by Apple Inc.
+ * Copyright (C) 2007 Red Hat, Inc.
+ * Copyright (C) 2007 Tim Waugh <twaugh@redhat.com>
+ * Copyright 1997-2005 by Easy Software Products.
+ *
+ * These coded instructions, statements, and computer programs are the
+ * property of Apple Inc. and are protected by Federal copyright
+ * law. Distribution and use rights are outlined in the file "LICENSE.txt"
+ * which should have been included with this file. If this file is
+ * file is missing or damaged, see the license at "http://www.cups.org/".
+ *
+ * Contents:
+ *
+ * main() - Read events and send DBUS notifications.
+ * acquire_lock() - Acquire a lock so we only have a single notifier running.
+ */
+
+/*
+ * Include necessary headers...
+ */
+
+#include <cups/cups.h>
+#include <cups/string.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <dbus/dbus.h>
+#ifdef HAVE_DBUS_MESSAGE_ITER_INIT_APPEND
+# define dbus_message_append_iter_init dbus_message_iter_init_append
+# define dbus_message_iter_append_string(i,v) dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, v)
+# define dbus_message_iter_append_uint32(i,v) dbus_message_iter_append_basic(i, DBUS_TYPE_UINT32, v)
+# define dbus_message_iter_append_boolean(i,v) dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, v)
+#endif /* HAVE_DBUS_MESSAGE_ITER_INIT_APPEND */
+
+
+/*
+ * D-Bus object: org.cups.cupsd.Notifier
+ * D-Bus object path: /org/cups/cupsd/Notifier
+ *
+ * D-Bus interface name: org.cups.cupsd.Notifier
+ *
+ * Signals:
+ *
+ * ServerRestarted(STRING text)
+ * Server has restarted.
+ *
+ * ServerStarted(STRING text)
+ * Server has started.
+ *
+ * ServerStopped(STRING text)
+ * Server has stopped.
+ *
+ * ServerAudit(STRING text)
+ * Security-related event.
+ *
+ * PrinterRestarted(STRING text,
+ * STRING printer-uri,
+ * STRING printer-name,
+ * UINT32 printer-state,
+ * STRING printer-state-reasons,
+ * BOOLEAN printer-is-accepting-jobs)
+ * Printer has restarted.
+ *
+ * PrinterShutdown(STRING text,
+ * STRING printer-uri,
+ * STRING printer-name,
+ * UINT32 printer-state,
+ * STRING printer-state-reasons,
+ * BOOLEAN printer-is-accepting-jobs)
+ * Printer has shutdown.
+ *
+ * PrinterStopped(STRING text,
+ * STRING printer-uri,
+ * STRING printer-name,
+ * UINT32 printer-state,
+ * STRING printer-state-reasons,
+ * BOOLEAN printer-is-accepting-jobs)
+ * Printer has stopped.
+ *
+ * PrinterStateChanged(STRING text,
+ * STRING printer-uri,
+ * STRING printer-name,
+ * UINT32 printer-state,
+ * STRING printer-state-reasons,
+ * BOOLEAN printer-is-accepting-jobs)
+ * Printer state has changed.
+ *
+ * PrinterFinishingsChanged(STRING text,
+ * STRING printer-uri,
+ * STRING printer-name,
+ * UINT32 printer-state,
+ * STRING printer-state-reasons,
+ * BOOLEAN printer-is-accepting-jobs)
+ * Printer's finishings-supported attribute has changed.
+ *
+ * PrinterMediaChanged(STRING text,
+ * STRING printer-uri,
+ * STRING printer-name,
+ * UINT32 printer-state,
+ * STRING printer-state-reasons,
+ * BOOLEAN printer-is-accepting-jobs)
+ * Printer's media-supported attribute has changed.
+ *
+ * PrinterAdded(STRING text,
+ * STRING printer-uri,
+ * STRING printer-name,
+ * UINT32 printer-state,
+ * STRING printer-state-reasons,
+ * BOOLEAN printer-is-accepting-jobs)
+ * Printer has been added.
+ *
+ * PrinterDeleted(STRING text,
+ * STRING printer-uri,
+ * STRING printer-name,
+ * UINT32 printer-state,
+ * STRING printer-state-reasons,
+ * BOOLEAN printer-is-accepting-jobs)
+ * Printer has been deleted.
+ *
+ * PrinterModified(STRING text,
+ * STRING printer-uri,
+ * STRING printer-name,
+ * UINT32 printer-state,
+ * STRING printer-state-reasons,
+ * BOOLEAN printer-is-accepting-jobs)
+ * Printer has been modified.
+ *
+ * text describes the event.
+ * printer-state-reasons is a comma-separated list.
+ * If printer-uri is "" in a Job* signal, the other printer-* parameters
+ * must be ignored.
+ * If the job name is not know, job-name will be "".
+ */
+
+/*
+ * Constants...
+ */
+
+enum
+{
+ PARAMS_NONE,
+ PARAMS_PRINTER,
+ PARAMS_JOB
+};
+
+
+/*
+ * Local functions...
+ */
+
+static int acquire_lock(int *fd, char *lockfile, size_t locksize);
+
+
+/*
+ * 'main()' - Read events and send DBUS notifications.
+ */
+
+int /* O - Exit status */
+main(int argc, /* I - Number of command-line args */
+ char *argv[]) /* I - Command-line arguments */
+{
+ ipp_t *msg; /* Event message from scheduler */
+ ipp_state_t state; /* IPP event state */
+ struct sigaction action; /* POSIX sigaction data */
+ DBusConnection *con = NULL; /* Connection to DBUS server */
+ DBusError error; /* Error, if any */
+ DBusMessage *message; /* Message to send */
+ DBusMessageIter iter; /* Iterator for message data */
+ int lock_fd = -1; /* Lock file descriptor */
+ char lock_filename[1024];
+ /* Lock filename */
+
+
+ /*
+ * Don't buffer stderr...
+ */
+
+ setbuf(stderr, NULL);
+
+ /*
+ * Ignore SIGPIPE signals...
+ */
+
+ memset(&action, 0, sizeof(action));
+ action.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &action, NULL);
+
+ /*
+ * Validate command-line options...
+ */
+
+ if (argc != 3)
+ {
+ fputs("Usage: dbus dbus:/// notify-user-data\n", stderr);
+ return (1);
+ }
+
+ if (strncmp(argv[1], "dbus:", 5))
+ {
+ fprintf(stderr, "ERROR: Bad URI \"%s\"!\n", argv[1]);
+ return (1);
+ }
+
+ /*
+ * Loop forever until we run out of events...
+ */
+
+ for (;;)
+ {
+ ipp_attribute_t *attr; /* Current attribute */
+ const char *event; /* Event name */
+ const char *signame = NULL;/* DBUS signal name */
+ char *printer_reasons = NULL;
+ /* Printer reasons string */
+ const char *nul = ""; /* Empty string value */
+ int no = 0; /* Boolean "no" value */
+ int params = PARAMS_NONE;
+ /* What parameters to include? */
+
+
+ /*
+ * Get the next event...
+ */
+
+ msg = ippNew();
+ while ((state = ippReadFile(0, msg)) != IPP_DATA)
+ {
+ if (state <= IPP_IDLE)
+ break;
+ }
+
+ fprintf(stderr, "DEBUG: state=%d\n", state);
+
+ if (state == IPP_ERROR)
+ fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr);
+
+ if (state <= IPP_IDLE)
+ {
+ /*
+ * Out of messages, free memory and then exit...
+ */
+
+ ippDelete(msg);
+ break;
+ }
+
+ /*
+ * Verify connection to DBUS server...
+ */
+
+ if (con && !dbus_connection_get_is_connected(con))
+ {
+ dbus_connection_unref(con);
+ con = NULL;
+ }
+
+ if (!con)
+ {
+ dbus_error_init(&error);
+
+ con = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
+ if (!con)
+ dbus_error_free(&error);
+ else
+ fputs("DEBUG: Connected to D-BUS\n", stderr);
+ }
+
+ if (!con)
+ continue;
+
+ if (lock_fd == -1 &&
+ acquire_lock(&lock_fd, lock_filename, sizeof(lock_filename)))
+ continue;
+
+ attr = ippFindAttribute(msg, "notify-subscribed-event",
+ IPP_TAG_KEYWORD);
+ if (!attr)
+ continue;
+
+ event = attr->values[0].string.text;
+ if (!strncmp(event, "server-", 7))
+ {
+ const char *word2 = event + 7; /* Second word */
+
+ if (!strcmp(word2, "restarted"))
+ signame = "ServerRestarted";
+ else if (!strcmp(word2, "started"))
+ signame = "ServerStarted";
+ else if (!strcmp(word2, "stopped"))
+ signame = "ServerStopped";
+ else if (!strcmp(word2, "audit"))
+ signame = "ServerAudit";
+ else
+ continue;
+ }
+ else if (!strncmp(event, "printer-", 8))
+ {
+ const char *word2 = event + 8; /* Second word */
+
+ params = PARAMS_PRINTER;
+ if (!strcmp(word2, "restarted"))
+ signame = "PrinterRestarted";
+ else if (!strcmp(word2, "shutdown"))
+ signame = "PrinterShutdown";
+ else if (!strcmp(word2, "stopped"))
+ signame = "PrinterStopped";
+ else if (!strcmp(word2, "state-changed"))
+ signame = "PrinterStateChanged";
+ else if (!strcmp(word2, "finishings-changed"))
+ signame = "PrinterFinishingsChanged";
+ else if (!strcmp(word2, "media-changed"))
+ signame = "PrinterMediaChanged";
+ else if (!strcmp(word2, "added"))
+ signame = "PrinterAdded";
+ else if (!strcmp(word2, "deleted"))
+ signame = "PrinterDeleted";
+ else if (!strcmp(word2, "modified"))
+ signame = "PrinterModified";
+ else
+ continue;
+ }
+ else if (!strncmp(event, "job-", 4))
+ {
+ const char *word2 = event + 4; /* Second word */
+
+ params = PARAMS_JOB;
+ if (!strcmp(word2, "state"))
+ signame = "JobState";
+ else if (!strcmp(word2, "created"))
+ signame = "JobCreated";
+ else if (!strcmp(word2, "completed"))
+ signame = "JobCompleted";
+ else if (!strcmp(word2, "stopped"))
+ signame = "JobStopped";
+ else if (!strcmp(word2, "config-changed"))
+ signame = "JobConfigChanged";
+ else if (!strcmp(word2, "progress"))
+ signame = "JobProgress";
+ else
+ continue;
+ }
+ else
+ continue;
+
+ /*
+ * Create and send the new message...
+ */
+
+ fprintf(stderr, "DEBUG: %s\n", signame);
+ message = dbus_message_new_signal("/org/cups/cupsd/Notifier",
+ "org.cups.cupsd.Notifier",
+ signame);
+
+ dbus_message_append_iter_init(message, &iter);
+ attr = ippFindAttribute(msg, "notify-text", IPP_TAG_TEXT);
+ if (!attr)
+ goto bail;
+ dbus_message_iter_append_string(&iter, &(attr->values[0].string.text));
+
+ if (params >= PARAMS_PRINTER)
+ {
+ char *p; /* Pointer into printer_reasons */
+ size_t reasons_length; /* Required size of printer_reasons */
+ int i; /* Looping var */
+ int have_printer_params = 1;/* Do we have printer URI? */
+
+ /* STRING printer-uri or "" */
+ attr = ippFindAttribute(msg, "notify-printer-uri", IPP_TAG_URI);
+ if (attr)
+ dbus_message_iter_append_string(&iter, &(attr->values[0].string.text));
+ else
+ {
+ have_printer_params = 0;
+ dbus_message_iter_append_string(&iter, &nul);
+ }
+
+ /* STRING printer-name */
+ if (have_printer_params)
+ {
+ attr = ippFindAttribute(msg, "printer-name", IPP_TAG_NAME);
+ if (attr)
+ dbus_message_iter_append_string(&iter,
+ &(attr->values[0].string.text));
+ else
+ goto bail;
+ }
+ else
+ dbus_message_iter_append_string(&iter, &nul);
+
+ /* UINT32 printer-state */
+ if (have_printer_params)
+ {
+ attr = ippFindAttribute(msg, "printer-state", IPP_TAG_ENUM);
+ if (attr)
+ dbus_message_iter_append_uint32(&iter, &(attr->values[0].integer));
+ else
+ goto bail;
+ }
+ else
+ dbus_message_iter_append_uint32(&iter, &no);
+
+ /* STRING printer-state-reasons */
+ if (have_printer_params)
+ {
+ attr = ippFindAttribute(msg, "printer-state-reasons",
+ IPP_TAG_KEYWORD);
+ if (attr)
+ {
+ for (reasons_length = 0, i = 0; i < attr->num_values; i++)
+ /* All need commas except the last, which needs a nul byte. */
+ reasons_length += 1 + strlen(attr->values[i].string.text);
+ printer_reasons = malloc(reasons_length);
+ if (!printer_reasons)
+ goto bail;
+ p = printer_reasons;
+ for (i = 0; i < attr->num_values; i++)
+ {
+ strcpy(p, attr->values[i].string.text);
+ p += strlen(p);
+ if (i)
+ *p++ = ',';
+ }
+ dbus_message_iter_append_string(&iter, &printer_reasons);
+ }
+ else
+ goto bail;
+ }
+ else
+ dbus_message_iter_append_string(&iter, &nul);
+
+ /* BOOL printer-is-accepting-jobs */
+ if (have_printer_params)
+ {
+ attr = ippFindAttribute(msg, "printer-is-accepting-jobs",
+ IPP_TAG_BOOLEAN);
+ if (attr)
+ dbus_message_iter_append_boolean(&iter, &(attr->values[0].boolean));
+ else
+ goto bail;
+ }
+ else
+ dbus_message_iter_append_boolean(&iter, &no);
+ }
+
+ if (params >= PARAMS_JOB)
+ {
+ /* UINT32 job-id */
+ attr = ippFindAttribute(msg, "notify-job-id", IPP_TAG_INTEGER);
+ if (!attr)
+ goto bail;
+ dbus_message_iter_append_uint32(&iter, &(attr->values[0].integer));
+
+ /* UINT32 job-state */
+ attr = ippFindAttribute(msg, "job-state", IPP_TAG_ENUM);
+ if (!attr)
+ goto bail;
+ dbus_message_iter_append_uint32(&iter, &(attr->values[0].integer));
+
+ /* STRING job-state-reasons */
+ attr = ippFindAttribute(msg, "job-state-reasons", IPP_TAG_KEYWORD);
+ if (!attr)
+ goto bail;
+ dbus_message_iter_append_string(&iter, &(attr->values[0].string.text));
+
+ /* STRING job-name or "" */
+ attr = ippFindAttribute(msg, "job-name", IPP_TAG_NAME);
+ if (attr)
+ dbus_message_iter_append_string(&iter, &(attr->values[0].string.text));
+ else
+ dbus_message_iter_append_string(&iter, &nul);
+
+ /* UINT32 job-impressions-completed */
+ attr = ippFindAttribute(msg, "job-impressions-completed",
+ IPP_TAG_INTEGER);
+ if (!attr)
+ goto bail;
+ dbus_message_iter_append_uint32(&iter, &(attr->values[0].integer));
+ }
+
+ dbus_connection_send(con, message, NULL);
+ dbus_connection_flush(con);
+
+ /*
+ * Cleanup...
+ */
+
+ bail:
+
+ if (printer_reasons)
+ free(printer_reasons);
+
+ dbus_message_unref(message);
+ ippDelete(msg);
+ }
+
+ /*
+ * Remove lock file...
+ */
+
+ if (lock_fd >= 0)
+ {
+ close(lock_fd);
+ unlink(lock_filename);
+ }
+
+ return (0);
+}
+
+
+/*
+ * 'acquire_lock()' - Acquire a lock so we only have a single notifier running.
+ */
+
+static int /* O - 0 on success, -1 on failure */
+acquire_lock(int *fd, /* O - Lock file descriptor */
+ char *lockfile, /* I - Lock filename buffer */
+ size_t locksize) /* I - Size of filename buffer */
+{
+ const char *tmpdir; /* Temporary directory */
+
+
+ /*
+ * Figure out where to put the lock file...
+ */
+
+ if ((tmpdir = getenv("TMPDIR")) == NULL)
+ tmpdir = "/tmp";
+
+ snprintf(lockfile, locksize, "%s/cups-dbus-notifier-lockfile", tmpdir);
+
+ /*
+ * Create the lock file and fail if it already exists...
+ */
+
+ if ((*fd = open(lockfile, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) < 0)
+ return (-1);
+ else
+ return (0);
+}
+
+
+/*
+ * End of "$Id: dbus.c 7916 2008-09-07 22:27:01Z mike $".
+ */
diff --git a/notifier/mailto.c b/notifier/mailto.c
new file mode 100644
index 00000000..ca6c85f0
--- /dev/null
+++ b/notifier/mailto.c
@@ -0,0 +1,790 @@
+/*
+ * "$Id: mailto.c 7460 2008-04-16 02:19:54Z mike $"
+ *
+ * "mailto" notifier for the Common UNIX Printing System (CUPS).
+ *
+ * Copyright 2007-2008 by Apple Inc.
+ * Copyright 1997-2005 by Easy Software Products.
+ *
+ * These coded instructions, statements, and computer programs are the
+ * property of Apple Inc. and are protected by Federal copyright
+ * law. Distribution and use rights are outlined in the file "LICENSE.txt"
+ * which should have been included with this file. If this file is
+ * file is missing or damaged, see the license at "http://www.cups.org/".
+ *
+ * Contents:
+ *
+ * main() - Main entry for the mailto notifier.
+ * email_message() - Email a notification message.
+ * load_configuration() - Load the mailto.conf file.
+ * pipe_sendmail() - Open a pipe to sendmail...
+ * print_attributes() - Print the attributes in a request...
+ */
+
+/*
+ * Include necessary headers...
+ */
+
+#include <cups/cups.h>
+#include <cups/i18n.h>
+#include <cups/string.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+
+/*
+ * Globals...
+ */
+
+char mailtoCc[1024]; /* Cc email address */
+char mailtoFrom[1024]; /* From email address */
+char mailtoReplyTo[1024]; /* Reply-To email address */
+char mailtoSubject[1024]; /* Subject prefix */
+char mailtoSMTPServer[1024]; /* SMTP server to use */
+char mailtoSendmail[1024]; /* Sendmail program to use */
+
+
+/*
+ * Local functions...
+ */
+
+void email_message(const char *to, const char *subject,
+ const char *text);
+int load_configuration(void);
+cups_file_t *pipe_sendmail(const char *to);
+void print_attributes(ipp_t *ipp, int indent);
+
+
+/*
+ * 'main()' - Main entry for the mailto notifier.
+ */
+
+int /* O - Exit status */
+main(int argc, /* I - Number of command-line arguments */
+ char *argv[]) /* I - Command-line arguments */
+{
+ int i; /* Looping var */
+ ipp_t *msg; /* Event message from scheduler */
+ ipp_state_t state; /* IPP event state */
+ char *subject, /* Subject for notification message */
+ *text; /* Text for notification message */
+ cups_lang_t *lang; /* Language info */
+ char temp[1024]; /* Temporary string */
+ int templen; /* Length of temporary string */
+#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
+ struct sigaction action; /* POSIX sigaction data */
+#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
+
+
+ /*
+ * Don't buffer stderr...
+ */
+
+ setbuf(stderr, NULL);
+
+ /*
+ * Ignore SIGPIPE signals...
+ */
+
+#ifdef HAVE_SIGSET
+ sigset(SIGPIPE, SIG_IGN);
+#elif defined(HAVE_SIGACTION)
+ memset(&action, 0, sizeof(action));
+ action.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &action, NULL);
+#else
+ signal(SIGPIPE, SIG_IGN);
+#endif /* HAVE_SIGSET */
+
+ /*
+ * Validate command-line options...
+ */
+
+ if (argc != 3)
+ {
+ fputs("Usage: mailto mailto:user@domain.com notify-user-data\n", stderr);
+ return (1);
+ }
+
+ if (strncmp(argv[1], "mailto:", 7))
+ {
+ fprintf(stderr, "ERROR: Bad recipient \"%s\"!\n", argv[1]);
+ return (1);
+ }
+
+ fprintf(stderr, "DEBUG: argc=%d\n", argc);
+ for (i = 0; i < argc; i ++)
+ fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]);
+
+ /*
+ * Load configuration data...
+ */
+
+ if ((lang = cupsLangDefault()) == NULL)
+ return (1);
+
+ if (!load_configuration())
+ return (1);
+
+ /*
+ * Get the reply-to address...
+ */
+
+ templen = sizeof(temp);
+ httpDecode64_2(temp, &templen, argv[2]);
+
+ if (!strncmp(temp, "mailto:", 7))
+ strlcpy(mailtoReplyTo, temp + 7, sizeof(mailtoReplyTo));
+ else if (temp[0])
+ fprintf(stderr, "WARNING: Bad notify-user-data value (%d bytes) ignored!\n",
+ templen);
+
+ /*
+ * Loop forever until we run out of events...
+ */
+
+ for (;;)
+ {
+ /*
+ * Get the next event...
+ */
+
+ msg = ippNew();
+ while ((state = ippReadFile(0, msg)) != IPP_DATA)
+ {
+ if (state <= IPP_IDLE)
+ break;
+ }
+
+ fprintf(stderr, "DEBUG: state=%d\n", state);
+
+ if (state == IPP_ERROR)
+ fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr);
+
+ if (state <= IPP_IDLE)
+ {
+ /*
+ * Out of messages, free memory and then exit...
+ */
+
+ ippDelete(msg);
+ return (0);
+ }
+
+ /*
+ * Get the subject and text for the message, then email it...
+ */
+
+ subject = cupsNotifySubject(lang, msg);
+ text = cupsNotifyText(lang, msg);
+
+ fprintf(stderr, "DEBUG: subject=\"%s\"\n", subject);
+ fprintf(stderr, "DEBUG: text=\"%s\"\n", text);
+
+ if (subject && text)
+ email_message(argv[1] + 7, subject, text);
+ else
+ {
+ fputs("ERROR: Missing attributes in event notification!\n", stderr);
+ print_attributes(msg, 4);
+ }
+
+ /*
+ * Free the memory used for this event...
+ */
+
+ if (subject)
+ free(subject);
+
+ if (text)
+ free(text);
+
+ ippDelete(msg);
+ }
+}
+
+
+/*
+ * 'email_message()' - Email a notification message.
+ */
+
+void
+email_message(const char *to, /* I - Recipient of message */
+ const char *subject, /* I - Subject of message */
+ const char *text) /* I - Text of message */
+{
+ cups_file_t *fp; /* Pipe/socket to mail server */
+ const char *nl; /* Newline to use */
+ char response[1024]; /* SMTP response buffer */
+
+
+ /*
+ * Connect to the mail server...
+ */
+
+ if (mailtoSendmail[0])
+ {
+ /*
+ * Use the sendmail command...
+ */
+
+ fp = pipe_sendmail(to);
+
+ if (!fp)
+ return;
+
+ nl = "\n";
+ }
+ else
+ {
+ /*
+ * Use an SMTP server...
+ */
+
+ char hostbuf[1024]; /* Local hostname */
+
+
+ if (strchr(mailtoSMTPServer, ':'))
+ fp = cupsFileOpen(mailtoSMTPServer, "s");
+ else
+ {
+ char spec[1024]; /* Host:service spec */
+
+
+ snprintf(spec, sizeof(spec), "%s:smtp", mailtoSMTPServer);
+ fp = cupsFileOpen(spec, "s");
+ }
+
+ if (!fp)
+ {
+ fprintf(stderr, "ERROR: Unable to connect to SMTP server \"%s\"!\n",
+ mailtoSMTPServer);
+ return;
+ }
+
+ fprintf(stderr, "DEBUG: Connected to \"%s\"...\n", mailtoSMTPServer);
+
+ cupsFilePrintf(fp, "HELO %s\r\n",
+ httpGetHostname(NULL, hostbuf, sizeof(hostbuf)));
+ fprintf(stderr, "DEBUG: >>> HELO %s\n", hostbuf);
+
+ if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
+ goto smtp_error;
+ fprintf(stderr, "DEBUG: <<< %s\n", response);
+
+ cupsFilePrintf(fp, "MAIL FROM:%s\r\n", mailtoFrom);
+ fprintf(stderr, "DEBUG: >>> MAIL FROM:%s\n", mailtoFrom);
+
+ if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
+ goto smtp_error;
+ fprintf(stderr, "DEBUG: <<< %s\n", response);
+
+ cupsFilePrintf(fp, "RCPT TO:%s\r\n", to);
+ fprintf(stderr, "DEBUG: >>> RCPT TO:%s\n", to);
+
+ if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
+ goto smtp_error;
+ fprintf(stderr, "DEBUG: <<< %s\n", response);
+
+ cupsFilePuts(fp, "DATA\r\n");
+ fputs("DEBUG: DATA\n", stderr);
+
+ if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
+ goto smtp_error;
+ fprintf(stderr, "DEBUG: <<< %s\n", response);
+
+ nl = "\r\n";
+ }
+
+ /*
+ * Send the message...
+ */
+
+ cupsFilePrintf(fp, "Date: %s%s", httpGetDateString(time(NULL)), nl);
+ cupsFilePrintf(fp, "From: %s%s", mailtoFrom, nl);
+ cupsFilePrintf(fp, "Subject: %s %s%s", mailtoSubject, subject, nl);
+ if (mailtoReplyTo[0])
+ {
+ cupsFilePrintf(fp, "Sender: %s%s", mailtoReplyTo, nl);
+ cupsFilePrintf(fp, "Reply-To: %s%s", mailtoReplyTo, nl);
+ }
+ cupsFilePrintf(fp, "To: %s%s", to, nl);
+ if (mailtoCc[0])
+ cupsFilePrintf(fp, "Cc: %s%s", mailtoCc, nl);
+ cupsFilePrintf(fp, "Content-Type: text/plain%s", nl);
+ cupsFilePuts(fp, nl);
+ cupsFilePrintf(fp, "%s%s", text, nl);
+ cupsFilePrintf(fp, ".%s", nl);
+
+ /*
+ * Close the connection to the mail server...
+ */
+
+ if (mailtoSendmail[0])
+ {
+ /*
+ * Close the pipe and wait for the sendmail command to finish...
+ */
+
+ int status; /* Exit status */
+
+
+ cupsFileClose(fp);
+
+ if (wait(&status))
+ status = errno << 8;
+
+ /*
+ * Report any non-zero status...
+ */
+
+ if (status)
+ {
+ if (WIFEXITED(status))
+ fprintf(stderr, "ERROR: Sendmail command returned status %d!\n",
+ WEXITSTATUS(status));
+ else
+ fprintf(stderr, "ERROR: Sendmail command crashed on signal %d!\n",
+ WTERMSIG(status));
+ }
+ }
+ else
+ {
+ /*
+ * Finish up the SMTP submission and close the connection...
+ */
+
+ if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
+ goto smtp_error;
+ fprintf(stderr, "DEBUG: <<< %s\n", response);
+
+ /*
+ * Process SMTP errors here...
+ */
+
+ smtp_error:
+
+ cupsFilePuts(fp, "QUIT\r\n");
+ fputs("DEBUG: QUIT\n", stderr);
+
+ if (!cupsFileGets(fp, response, sizeof(response)) || atoi(response) >= 500)
+ goto smtp_error;
+ fprintf(stderr, "DEBUG: <<< %s\n", response);
+
+ cupsFileClose(fp);
+
+ fprintf(stderr, "DEBUG: Closed connection to \"%s\"...\n",
+ mailtoSMTPServer);
+ }
+}
+
+
+/*
+ * 'load_configuration()' - Load the mailto.conf file.
+ */
+
+int /* I - 1 on success, 0 on failure */
+load_configuration(void)
+{
+ cups_file_t *fp; /* mailto.conf file */
+ const char *server_root, /* CUPS_SERVERROOT environment variable */
+ *server_admin; /* SERVER_ADMIN environment variable */
+ char line[1024], /* Line from file */
+ *value; /* Value for directive */
+ int linenum; /* Line number in file */
+
+
+ /*
+ * Initialize defaults...
+ */
+
+ mailtoCc[0] = '\0';
+
+ if ((server_admin = getenv("SERVER_ADMIN")) != NULL)
+ strlcpy(mailtoFrom, server_admin, sizeof(mailtoFrom));
+ else
+ snprintf(mailtoFrom, sizeof(mailtoFrom), "root@%s",
+ httpGetHostname(NULL, line, sizeof(line)));
+
+ strlcpy(mailtoSendmail, "/usr/sbin/sendmail", sizeof(mailtoSendmail));
+
+ mailtoSMTPServer[0] = '\0';
+
+ mailtoSubject[0] = '\0';
+
+ /*
+ * Try loading the config file...
+ */
+
+ if ((server_root = getenv("CUPS_SERVERROOT")) == NULL)
+ server_root = CUPS_SERVERROOT;
+
+ snprintf(line, sizeof(line), "%s/mailto.conf", server_root);
+
+ if ((fp = cupsFileOpen(line, "r")) == NULL)
+ {
+ fprintf(stderr, "ERROR: Unable to open \"%s\" - %s\n", line,
+ strerror(errno));
+ return (1);
+ }
+
+ linenum = 0;
+
+ while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
+ {
+ if (!value)
+ {
+ fprintf(stderr, "ERROR: No value found for %s directive on line %d!\n",
+ line, linenum);
+ cupsFileClose(fp);
+ return (0);
+ }
+
+ if (!strcasecmp(line, "Cc"))
+ strlcpy(mailtoCc, value, sizeof(mailtoCc));
+ else if (!strcasecmp(line, "From"))
+ strlcpy(mailtoFrom, value, sizeof(mailtoFrom));
+ else if (!strcasecmp(line, "Sendmail"))
+ {
+ strlcpy(mailtoSendmail, value, sizeof(mailtoSendmail));
+ mailtoSMTPServer[0] = '\0';
+ }
+ else if (!strcasecmp(line, "SMTPServer"))
+ {
+ mailtoSendmail[0] = '\0';
+ strlcpy(mailtoSMTPServer, value, sizeof(mailtoSMTPServer));
+ }
+ else if (!strcasecmp(line, "Subject"))
+ strlcpy(mailtoSubject, value, sizeof(mailtoSubject));
+ else
+ {
+ fprintf(stderr,
+ "ERROR: Unknown configuration directive \"%s\" on line %d!\n",
+ line, linenum);
+ }
+ }
+
+ /*
+ * Close file and return...
+ */
+
+ cupsFileClose(fp);
+
+ return (1);
+}
+
+
+/*
+ * 'pipe_sendmail()' - Open a pipe to sendmail...
+ */
+
+cups_file_t * /* O - CUPS file */
+pipe_sendmail(const char *to) /* I - To: address */
+{
+ cups_file_t *fp; /* CUPS file */
+ int pid; /* Process ID */
+ int pipefds[2]; /* Pipe file descriptors */
+ int argc; /* Number of arguments */
+ char *argv[100], /* Argument array */
+ line[1024], /* Sendmail command + args */
+ *lineptr; /* Pointer into line */
+
+
+ /*
+ * First break the mailtoSendmail string into arguments...
+ */
+
+ strlcpy(line, mailtoSendmail, sizeof(line));
+ argv[0] = line;
+ argc = 1;
+
+ for (lineptr = strchr(line, ' '); lineptr; lineptr = strchr(lineptr, ' '))
+ {
+ while (*lineptr == ' ')
+ *lineptr++ = '\0';
+
+ if (*lineptr)
+ {
+ /*
+ * Point to the next argument...
+ */
+
+ argv[argc ++] = lineptr;
+
+ /*
+ * Stop if we have too many...
+ */
+
+ if (argc >= (int)(sizeof(argv) / sizeof(argv[0]) - 2))
+ break;
+ }
+ }
+
+ argv[argc ++] = (char *)to;
+ argv[argc] = NULL;
+
+ /*
+ * Create the pipe...
+ */
+
+ if (pipe(pipefds))
+ {
+ perror("ERROR: Unable to create pipe");
+ return (NULL);
+ }
+
+ /*
+ * Then run the command...
+ */
+
+ if ((pid = fork()) == 0)
+ {
+ /*
+ * Child goes here - redirect stdin to the input side of the pipe,
+ * redirect stdout to stderr, and exec...
+ */
+
+ close(0);
+ dup(pipefds[0]);
+
+ close(1);
+ dup(2);
+
+ close(pipefds[0]);
+ close(pipefds[1]);
+
+ execvp(argv[0], argv);
+ exit(errno);
+ }
+ else if (pid < 0)
+ {
+ /*
+ * Unable to fork - error out...
+ */
+
+ perror("ERROR: Unable to fork command");
+
+ close(pipefds[0]);
+ close(pipefds[1]);
+
+ return (NULL);
+ }
+
+ /*
+ * Create a CUPS file using the output side of the pipe and close the
+ * input side...
+ */
+
+ close(pipefds[0]);
+
+ if ((fp = cupsFileOpenFd(pipefds[1], "w")) == NULL)
+ {
+ int status; /* Status of command */
+
+
+ close(pipefds[1]);
+ wait(&status);
+ }
+
+ return (fp);
+}
+
+
+/*
+ * 'print_attributes()' - Print the attributes in a request...
+ */
+
+void
+print_attributes(ipp_t *ipp, /* I - IPP request */
+ int indent) /* I - Indentation */
+{
+ int i; /* Looping var */
+ ipp_tag_t group; /* Current group */
+ ipp_attribute_t *attr; /* Current attribute */
+ ipp_value_t *val; /* Current value */
+ static const char * const tags[] = /* Value/group tag strings */
+ {
+ "reserved-00",
+ "operation-attributes-tag",
+ "job-attributes-tag",
+ "end-of-attributes-tag",
+ "printer-attributes-tag",
+ "unsupported-attributes-tag",
+ "subscription-attributes-tag",
+ "event-attributes-tag",
+ "reserved-08",
+ "reserved-09",
+ "reserved-0A",
+ "reserved-0B",
+ "reserved-0C",
+ "reserved-0D",
+ "reserved-0E",
+ "reserved-0F",
+ "unsupported",
+ "default",
+ "unknown",
+ "no-value",
+ "reserved-14",
+ "not-settable",
+ "delete-attr",
+ "admin-define",
+ "reserved-18",
+ "reserved-19",
+ "reserved-1A",
+ "reserved-1B",
+ "reserved-1C",
+ "reserved-1D",
+ "reserved-1E",
+ "reserved-1F",
+ "reserved-20",
+ "integer",
+ "boolean",
+ "enum",
+ "reserved-24",
+ "reserved-25",
+ "reserved-26",
+ "reserved-27",
+ "reserved-28",
+ "reserved-29",
+ "reserved-2a",
+ "reserved-2b",
+ "reserved-2c",
+ "reserved-2d",
+ "reserved-2e",
+ "reserved-2f",
+ "octetString",
+ "dateTime",
+ "resolution",
+ "rangeOfInteger",
+ "begCollection",
+ "textWithLanguage",
+ "nameWithLanguage",
+ "endCollection",
+ "reserved-38",
+ "reserved-39",
+ "reserved-3a",
+ "reserved-3b",
+ "reserved-3c",
+ "reserved-3d",
+ "reserved-3e",
+ "reserved-3f",
+ "reserved-40",
+ "textWithoutLanguage",
+ "nameWithoutLanguage",
+ "reserved-43",
+ "keyword",
+ "uri",
+ "uriScheme",
+ "charset",
+ "naturalLanguage",
+ "mimeMediaType",
+ "memberName"
+ };
+
+
+ for (group = IPP_TAG_ZERO, attr = ipp->attrs; attr; attr = attr->next)
+ {
+ if ((attr->group_tag == IPP_TAG_ZERO && indent <= 8) || !attr->name)
+ {
+ group = IPP_TAG_ZERO;
+ fputc('\n', stderr);
+ continue;
+ }
+
+ if (group != attr->group_tag)
+ {
+ group = attr->group_tag;
+
+ fprintf(stderr, "DEBUG: %*s%s:\n\n", indent - 4, "", tags[group]);
+ }
+
+ fprintf(stderr, "DEBUG: %*s%s (", indent, "", attr->name);
+ if (attr->num_values > 1)
+ fputs("1setOf ", stderr);
+ fprintf(stderr, "%s):", tags[attr->value_tag]);
+
+ switch (attr->value_tag)
+ {
+ case IPP_TAG_ENUM :
+ case IPP_TAG_INTEGER :
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ fprintf(stderr, " %d", val->integer);
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_BOOLEAN :
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ fprintf(stderr, " %s", val->boolean ? "true" : "false");
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_RANGE :
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ fprintf(stderr, " %d-%d", val->range.lower, val->range.upper);
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_DATE :
+ {
+ time_t vtime; /* Date/Time value */
+ struct tm *vdate; /* Date info */
+ char vstring[256]; /* Formatted time */
+
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ {
+ vtime = ippDateToTime(val->date);
+ vdate = localtime(&vtime);
+ strftime(vstring, sizeof(vstring), "%c", vdate);
+ fprintf(stderr, " (%s)", vstring);
+ }
+ }
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_RESOLUTION :
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ fprintf(stderr, " %dx%d%s", val->resolution.xres,
+ val->resolution.yres,
+ val->resolution.units == IPP_RES_PER_INCH ? "dpi" : "dpc");
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_STRING :
+ case IPP_TAG_TEXTLANG :
+ case IPP_TAG_NAMELANG :
+ case IPP_TAG_TEXT :
+ case IPP_TAG_NAME :
+ case IPP_TAG_KEYWORD :
+ case IPP_TAG_URI :
+ case IPP_TAG_URISCHEME :
+ case IPP_TAG_CHARSET :
+ case IPP_TAG_LANGUAGE :
+ case IPP_TAG_MIMETYPE :
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ fprintf(stderr, " \"%s\"", val->string.text);
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_BEGIN_COLLECTION :
+ fputc('\n', stderr);
+
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ {
+ if (i)
+ fputc('\n', stderr);
+ print_attributes(val->collection, indent + 4);
+ }
+ break;
+
+ default :
+ fprintf(stderr, "UNKNOWN (%d values)\n", attr->num_values);
+ break;
+ }
+ }
+}
+
+
+/*
+ * End of "$Id: mailto.c 7460 2008-04-16 02:19:54Z mike $".
+ */
diff --git a/notifier/rss.c b/notifier/rss.c
new file mode 100644
index 00000000..94e4af62
--- /dev/null
+++ b/notifier/rss.c
@@ -0,0 +1,732 @@
+/*
+ * "$Id: rss.c 8359 2009-02-16 23:57:08Z mike $"
+ *
+ * RSS notifier for the Common UNIX Printing System (CUPS).
+ *
+ * Copyright 2007-2009 by Apple Inc.
+ * Copyright 2007 by Easy Software Products.
+ *
+ * These coded instructions, statements, and computer programs are the
+ * property of Apple Inc. and are protected by Federal copyright
+ * law. Distribution and use rights are outlined in the file "LICENSE.txt"
+ * which should have been included with this file. If this file is
+ * file is missing or damaged, see the license at "http://www.cups.org/".
+ *
+ * Contents:
+ *
+ * main() - Main entry for the test notifier.
+ * compare_rss() - Compare two messages.
+ * delete_message() - Free all memory used by a message.
+ * load_rss() - Load an existing RSS feed file.
+ * new_message() - Create a new RSS message.
+ * password_cb() - Return the cached password.
+ * save_rss() - Save messages to a RSS file.
+ * xml_escape() - Copy a string, escaping &, <, and > as needed.
+ */
+
+/*
+ * Include necessary headers...
+ */
+
+#include <cups/cups.h>
+#include <cups/language.h>
+#include <cups/string.h>
+#include <cups/array.h>
+#include <errno.h>
+#include <sys/select.h>
+
+
+/*
+ * Structures...
+ */
+
+typedef struct _cups_rss_s /**** RSS message data ****/
+{
+ int sequence_number; /* notify-sequence-number */
+ char *subject, /* Message subject/summary */
+ *text, /* Message text */
+ *link_url; /* Link to printer */
+ time_t event_time; /* When the event occurred */
+} _cups_rss_t;
+
+
+/*
+ * Local globals...
+ */
+
+static char *rss_password; /* Password for remote RSS */
+
+
+/*
+ * Local functions...
+ */
+
+static int compare_rss(_cups_rss_t *a, _cups_rss_t *b);
+static void delete_message(_cups_rss_t *rss);
+static void load_rss(cups_array_t *rss, const char *filename);
+static _cups_rss_t *new_message(int sequence_number, char *subject,
+ char *text, char *link_url,
+ time_t event_time);
+static const char *password_cb(const char *prompt);
+static int save_rss(cups_array_t *rss, const char *filename,
+ const char *baseurl);
+static char *xml_escape(const char *s);
+
+
+/*
+ * 'main()' - Main entry for the test notifier.
+ */
+
+int /* O - Exit status */
+main(int argc, /* I - Number of command-line arguments */
+ char *argv[]) /* I - Command-line arguments */
+{
+ int i; /* Looping var */
+ ipp_t *event; /* Event from scheduler */
+ ipp_state_t state; /* IPP event state */
+ char scheme[32], /* URI scheme ("rss") */
+ username[256], /* Username for remote RSS */
+ host[1024], /* Hostname for remote RSS */
+ resource[1024], /* RSS file */
+ *options; /* Options */
+ int port, /* Port number for remote RSS */
+ max_events; /* Maximum number of events */
+ http_t *http; /* Connection to remote server */
+ http_status_t status; /* HTTP GET/PUT status code */
+ char filename[1024], /* Local filename */
+ newname[1024]; /* filename.N */
+ cups_lang_t *language; /* Language information */
+ ipp_attribute_t *printer_up_time, /* Timestamp on event */
+ *notify_sequence_number,/* Sequence number */
+ *notify_printer_uri; /* Printer URI */
+ char *subject, /* Subject for notification message */
+ *text, /* Text for notification message */
+ link_url[1024], /* Link to printer */
+ link_scheme[32], /* Scheme for link */
+ link_username[256], /* Username for link */
+ link_host[1024], /* Host for link */
+ link_resource[1024]; /* Resource for link */
+ int link_port; /* Link port */
+ cups_array_t *rss; /* RSS message array */
+ _cups_rss_t *msg; /* RSS message */
+ char baseurl[1024]; /* Base URL */
+ fd_set input; /* Input set for select() */
+ struct timeval timeout; /* Timeout for select() */
+ int changed; /* Has the RSS data changed? */
+ int exit_status; /* Exit status */
+
+
+ fprintf(stderr, "DEBUG: argc=%d\n", argc);
+ for (i = 0; i < argc; i ++)
+ fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]);
+
+ /*
+ * See whether we are publishing this RSS feed locally or remotely...
+ */
+
+ if (httpSeparateURI(HTTP_URI_CODING_ALL, argv[1], scheme, sizeof(scheme),
+ username, sizeof(username), host, sizeof(host), &port,
+ resource, sizeof(resource)) < HTTP_URI_OK)
+ {
+ fprintf(stderr, "ERROR: Bad RSS URI \"%s\"!\n", argv[1]);
+ return (1);
+ }
+
+ max_events = 20;
+
+ if ((options = strchr(resource, '?')) != NULL)
+ {
+ *options++ = '\0';
+
+ if (!strncmp(options, "max_events=", 11))
+ {
+ max_events = atoi(options + 11);
+
+ if (max_events <= 0)
+ max_events = 20;
+ }
+ }
+
+ rss = cupsArrayNew((cups_array_func_t)compare_rss, NULL);
+
+ if (host[0])
+ {
+ /*
+ * Remote feed, see if we can get the current file...
+ */
+
+ int fd; /* Temporary file */
+
+
+ if ((rss_password = strchr(username, ':')) != NULL)
+ *rss_password++ = '\0';
+
+ cupsSetPasswordCB(password_cb);
+ cupsSetUser(username);
+
+ if ((fd = cupsTempFd(filename, sizeof(filename))) < 0)
+ {
+ fprintf(stderr, "ERROR: Unable to create temporary file: %s\n",
+ strerror(errno));
+
+ return (1);
+ }
+
+ if ((http = httpConnect(host, port)) == NULL)
+ {
+ fprintf(stderr, "ERROR: Unable to connect to %s on port %d: %s\n",
+ host, port, strerror(errno));
+
+ close(fd);
+ unlink(filename);
+
+ return (1);
+ }
+
+ status = cupsGetFd(http, resource, fd);
+
+ close(fd);
+
+ if (status != HTTP_OK && status != HTTP_NOT_FOUND)
+ {
+ fprintf(stderr, "ERROR: Unable to GET %s from %s on port %d: %d %s\n",
+ resource, host, port, status, httpStatus(status));
+
+ httpClose(http);
+ unlink(filename);
+
+ return (1);
+ }
+
+ strlcpy(newname, filename, sizeof(newname));
+
+ httpAssembleURI(HTTP_URI_CODING_ALL, baseurl, sizeof(baseurl), "http",
+ NULL, host, port, resource);
+ }
+ else
+ {
+ const char *cachedir, /* CUPS_CACHEDIR */
+ *server_name, /* SERVER_NAME */
+ *server_port; /* SERVER_PORT */
+
+
+ http = NULL;
+
+ if ((cachedir = getenv("CUPS_CACHEDIR")) == NULL)
+ cachedir = CUPS_CACHEDIR;
+
+ if ((server_name = getenv("SERVER_NAME")) == NULL)
+ server_name = "localhost";
+
+ if ((server_port = getenv("SERVER_PORT")) == NULL)
+ server_port = "631";
+
+ snprintf(filename, sizeof(filename), "%s/rss%s", cachedir, resource);
+ snprintf(newname, sizeof(newname), "%s.N", filename);
+
+ httpAssembleURIf(HTTP_URI_CODING_ALL, baseurl, sizeof(baseurl), "http",
+ NULL, server_name, atoi(server_port), "/rss%s", resource);
+ }
+
+ /*
+ * Load the previous RSS file, if any...
+ */
+
+ load_rss(rss, filename);
+
+ changed = cupsArrayCount(rss) == 0;
+
+ /*
+ * Localize for the user's chosen language...
+ */
+
+ language = cupsLangDefault();
+
+ /*
+ * Read events and update the RSS file until we are out of events.
+ */
+
+ for (exit_status = 0, event = NULL;;)
+ {
+ if (changed)
+ {
+ /*
+ * Save the messages to the file again, uploading as needed...
+ */
+
+ if (save_rss(rss, newname, baseurl))
+ {
+ if (http)
+ {
+ /*
+ * Upload the RSS file...
+ */
+
+ if ((status = cupsPutFile(http, resource, filename)) != HTTP_CREATED)
+ fprintf(stderr, "ERROR: Unable to PUT %s from %s on port %d: %d %s\n",
+ resource, host, port, status, httpStatus(status));
+ }
+ else
+ {
+ /*
+ * Move the new RSS file over top the old one...
+ */
+
+ if (rename(newname, filename))
+ fprintf(stderr, "ERROR: Unable to rename %s to %s: %s\n",
+ newname, filename, strerror(errno));
+ }
+
+ changed = 0;
+ }
+ }
+
+ /*
+ * Wait up to 30 seconds for an event...
+ */
+
+ timeout.tv_sec = 30;
+ timeout.tv_usec = 0;
+
+ FD_ZERO(&input);
+ FD_SET(0, &input);
+
+ if (select(1, &input, NULL, NULL, &timeout) < 0)
+ continue;
+ else if (!FD_ISSET(0, &input))
+ {
+ fprintf(stderr, "DEBUG: %s is bored, exiting...\n", argv[1]);
+ break;
+ }
+
+ /*
+ * Read the next event...
+ */
+
+ event = ippNew();
+ while ((state = ippReadFile(0, event)) != IPP_DATA)
+ {
+ if (state <= IPP_IDLE)
+ break;
+ }
+
+ if (state == IPP_ERROR)
+ fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr);
+
+ if (state <= IPP_IDLE)
+ break;
+
+ /*
+ * Collect the info from the event...
+ */
+
+ printer_up_time = ippFindAttribute(event, "printer-up-time",
+ IPP_TAG_INTEGER);
+ notify_sequence_number = ippFindAttribute(event, "notify-sequence-number",
+ IPP_TAG_INTEGER);
+ notify_printer_uri = ippFindAttribute(event, "notify-printer-uri",
+ IPP_TAG_URI);
+ subject = cupsNotifySubject(language, event);
+ text = cupsNotifyText(language, event);
+
+ if (printer_up_time && notify_sequence_number && subject && text)
+ {
+ /*
+ * Create a new RSS message...
+ */
+
+ if (notify_printer_uri)
+ {
+ httpSeparateURI(HTTP_URI_CODING_ALL,
+ notify_printer_uri->values[0].string.text,
+ link_scheme, sizeof(link_scheme),
+ link_username, sizeof(link_username),
+ link_host, sizeof(link_host), &link_port,
+ link_resource, sizeof(link_resource));
+ httpAssembleURI(HTTP_URI_CODING_ALL, link_url, sizeof(link_url),
+ "http", link_username, link_host, link_port,
+ link_resource);
+ }
+
+ msg = new_message(notify_sequence_number->values[0].integer,
+ xml_escape(subject), xml_escape(text),
+ notify_printer_uri ? xml_escape(link_url) : NULL,
+ printer_up_time->values[0].integer);
+
+ if (!msg)
+ {
+ fprintf(stderr, "ERROR: Unable to create message: %s\n",
+ strerror(errno));
+ exit_status = 1;
+ break;
+ }
+
+ /*
+ * Add it to the array...
+ */
+
+ cupsArrayAdd(rss, msg);
+
+ changed = 1;
+
+ /*
+ * Trim the array as needed...
+ */
+
+ while (cupsArrayCount(rss) > max_events)
+ {
+ msg = cupsArrayFirst(rss);
+
+ cupsArrayRemove(rss, msg);
+
+ delete_message(msg);
+ }
+ }
+
+ if (subject)
+ free(subject);
+
+ if (text)
+ free(text);
+
+ ippDelete(event);
+ event = NULL;
+ }
+
+ /*
+ * We only get here when idle or error...
+ */
+
+ ippDelete(event);
+
+ if (http)
+ {
+ unlink(filename);
+ httpClose(http);
+ }
+
+ return (exit_status);
+}
+
+
+/*
+ * 'compare_rss()' - Compare two messages.
+ */
+
+static int /* O - Result of comparison */
+compare_rss(_cups_rss_t *a, /* I - First message */
+ _cups_rss_t *b) /* I - Second message */
+{
+ return (a->sequence_number - b->sequence_number);
+}
+
+
+/*
+ * 'delete_message()' - Free all memory used by a message.
+ */
+
+static void
+delete_message(_cups_rss_t *msg) /* I - RSS message */
+{
+ if (msg->subject)
+ free(msg->subject);
+
+ if (msg->text)
+ free(msg->text);
+
+ if (msg->link_url)
+ free(msg->link_url);
+
+ free(msg);
+}
+
+
+/*
+ * 'load_rss()' - Load an existing RSS feed file.
+ */
+
+static void
+load_rss(cups_array_t *rss, /* I - RSS messages */
+ const char *filename) /* I - File to load */
+{
+ FILE *fp; /* File pointer */
+ char line[4096], /* Line from file */
+ *subject, /* Subject */
+ *text, /* Text */
+ *link_url, /* Link URL */
+ *start, /* Start of element */
+ *end; /* End of element */
+ time_t event_time; /* Event time */
+ int sequence_number; /* Sequence number */
+ int in_item; /* In an item */
+ _cups_rss_t *msg; /* New message */
+
+
+ if ((fp = fopen(filename, "r")) == NULL)
+ {
+ if (errno != ENOENT)
+ fprintf(stderr, "ERROR: Unable to open %s: %s\n", filename,
+ strerror(errno));
+
+ return;
+ }
+
+ subject = NULL;
+ text = NULL;
+ link_url = NULL;
+ event_time = 0;
+ sequence_number = 0;
+ in_item = 0;
+
+ while (fgets(line, sizeof(line), fp))
+ {
+ if (strstr(line, "<item>"))
+ in_item = 1;
+ else if (strstr(line, "</item>") && in_item)
+ {
+ if (subject && text)
+ {
+ msg = new_message(sequence_number, subject, text, link_url,
+ event_time);
+
+ if (msg)
+ cupsArrayAdd(rss, msg);
+
+ }
+ else
+ {
+ if (subject)
+ free(subject);
+
+ if (text)
+ free(text);
+
+ if (link_url)
+ free(link_url);
+ }
+
+ subject = NULL;
+ text = NULL;
+ link_url = NULL;
+ event_time = 0;
+ sequence_number = 0;
+ in_item = 0;
+ }
+ else if (!in_item)
+ continue;
+ else if ((start = strstr(line, "<title>")) != NULL)
+ {
+ start += 7;
+ if ((end = strstr(start, "</title>")) != NULL)
+ {
+ *end = '\0';
+ subject = strdup(start);
+ }
+ }
+ else if ((start = strstr(line, "<description>")) != NULL)
+ {
+ start += 13;
+ if ((end = strstr(start, "</description>")) != NULL)
+ {
+ *end = '\0';
+ text = strdup(start);
+ }
+ }
+ else if ((start = strstr(line, "<link>")) != NULL)
+ {
+ start += 6;
+ if ((end = strstr(start, "</link>")) != NULL)
+ {
+ *end = '\0';
+ link_url = strdup(start);
+ }
+ }
+ else if ((start = strstr(line, "<pubDate>")) != NULL)
+ {
+ start += 9;
+ if ((end = strstr(start, "</pubDate>")) != NULL)
+ {
+ *end = '\0';
+ event_time = httpGetDateTime(start);
+ }
+ }
+ else if ((start = strstr(line, "<guid>")) != NULL)
+ sequence_number = atoi(start + 6);
+ }
+
+ fclose(fp);
+}
+
+
+/*
+ * 'new_message()' - Create a new RSS message.
+ */
+
+static _cups_rss_t * /* O - New message */
+new_message(int sequence_number, /* I - notify-sequence-number */
+ char *subject, /* I - Subject/summary */
+ char *text, /* I - Text */
+ char *link_url, /* I - Link to printer */
+ time_t event_time) /* I - Date/time of event */
+{
+ _cups_rss_t *msg; /* New message */
+
+
+ if ((msg = calloc(1, sizeof(_cups_rss_t))) == NULL)
+ return (NULL);
+
+ msg->sequence_number = sequence_number;
+ msg->subject = subject;
+ msg->text = text;
+ msg->link_url = link_url;
+ msg->event_time = event_time;
+
+ return (msg);
+}
+
+
+/*
+ * 'password_cb()' - Return the cached password.
+ */
+
+static const char * /* O - Cached password */
+password_cb(const char *prompt) /* I - Prompt string, unused */
+{
+ (void)prompt;
+
+ return (rss_password);
+}
+
+
+/*
+ * 'save_rss()' - Save messages to a RSS file.
+ */
+
+static int /* O - 1 on success, 0 on failure */
+save_rss(cups_array_t *rss, /* I - RSS messages */
+ const char *filename, /* I - File to save to */
+ const char *baseurl) /* I - Base URL */
+{
+ FILE *fp; /* File pointer */
+ _cups_rss_t *msg; /* Current message */
+ char date[1024]; /* Current date */
+ char *href; /* Escaped base URL */
+
+
+ if ((fp = fopen(filename, "w")) == NULL)
+ {
+ fprintf(stderr, "ERROR: Unable to create %s: %s\n", filename,
+ strerror(errno));
+ return (0);
+ }
+
+ fputs("<?xml version=\"1.0\"?>\n", fp);
+ fputs("<rss version=\"2.0\">\n", fp);
+ fputs(" <channel>\n", fp);
+ fputs(" <title>CUPS RSS Feed</title>\n", fp);
+
+ href = xml_escape(baseurl);
+ fprintf(fp, " <link>%s</link>\n", href);
+ free(href);
+
+ fputs(" <description>CUPS RSS Feed</description>\n", fp);
+ fputs(" <generator>" CUPS_SVERSION "</generator>\n", fp);
+ fputs(" <ttl>1</ttl>\n", fp);
+
+ fprintf(fp, " <pubDate>%s</pubDate>\n",
+ httpGetDateString2(time(NULL), date, sizeof(date)));
+
+ for (msg = (_cups_rss_t *)cupsArrayLast(rss);
+ msg;
+ msg = (_cups_rss_t *)cupsArrayPrev(rss))
+ {
+ fputs(" <item>\n", fp);
+ fprintf(fp, " <title>%s</title>\n", msg->subject);
+ fprintf(fp, " <description>%s</description>\n", msg->text);
+ if (msg->link_url)
+ fprintf(fp, " <link>%s</link>\n", msg->link_url);
+ fprintf(fp, " <pubDate>%s</pubDate>\n",
+ httpGetDateString2(msg->event_time, date, sizeof(date)));
+ fprintf(fp, " <guid>%d</guid>\n", msg->sequence_number);
+ fputs(" </item>\n", fp);
+ }
+
+ fputs(" </channel>\n", fp);
+ fputs("</rss>\n", fp);
+
+ return (!fclose(fp));
+}
+
+
+/*
+ * 'xml_escape()' - Copy a string, escaping &, <, and > as needed.
+ */
+
+static char * /* O - Escaped string */
+xml_escape(const char *s) /* I - String to escape */
+{
+ char *e, /* Escaped string */
+ *eptr; /* Pointer into escaped string */
+ const char *sptr; /* Pointer into string */
+ size_t bytes; /* Bytes needed for string */
+
+
+ /*
+ * First figure out how many extra bytes we need...
+ */
+
+ for (bytes = 0, sptr = s; *sptr; sptr ++)
+ if (*sptr == '&')
+ bytes += 4; /* &amp; */
+ else if (*sptr == '<' || *sptr == '>')
+ bytes += 3; /* &lt; and &gt; */
+
+ /*
+ * If there is nothing to escape, just strdup() it...
+ */
+
+ if (bytes == 0)
+ return (strdup(s));
+
+ /*
+ * Otherwise allocate memory and copy...
+ */
+
+ if ((e = malloc(bytes + 1 + strlen(s))) == NULL)
+ return (NULL);
+
+ for (eptr = e, sptr = s; *sptr; sptr ++)
+ if (*sptr == '&')
+ {
+ *eptr++ = '&';
+ *eptr++ = 'a';
+ *eptr++ = 'm';
+ *eptr++ = 'p';
+ *eptr++ = ';';
+ }
+ else if (*sptr == '<')
+ {
+ *eptr++ = '&';
+ *eptr++ = 'l';
+ *eptr++ = 't';
+ *eptr++ = ';';
+ }
+ else if (*sptr == '>')
+ {
+ *eptr++ = '&';
+ *eptr++ = 'g';
+ *eptr++ = 't';
+ *eptr++ = ';';
+ }
+ else
+ *eptr++ = *sptr;
+
+ *eptr = '\0';
+
+ return (e);
+}
+
+
+/*
+ * End of "$Id: rss.c 8359 2009-02-16 23:57:08Z mike $".
+ */
diff --git a/notifier/testnotify.c b/notifier/testnotify.c
new file mode 100644
index 00000000..fd96cca2
--- /dev/null
+++ b/notifier/testnotify.c
@@ -0,0 +1,285 @@
+/*
+ * "$Id: testnotify.c 7997 2008-10-01 21:20:33Z mike $"
+ *
+ * Test notifier for the Common UNIX Printing System (CUPS).
+ *
+ * Copyright 2007 by Apple Inc.
+ * Copyright 1997-2005 by Easy Software Products.
+ *
+ * These coded instructions, statements, and computer programs are the
+ * property of Apple Inc. and are protected by Federal copyright
+ * law. Distribution and use rights are outlined in the file "LICENSE.txt"
+ * which should have been included with this file. If this file is
+ * file is missing or damaged, see the license at "http://www.cups.org/".
+ *
+ * Contents:
+ *
+ * main() - Main entry for the test notifier.
+ * print_attributes() - Print the attributes in a request...
+ */
+
+/*
+ * Include necessary headers...
+ */
+
+#include <cups/cups.h>
+#include <cups/language.h>
+#include <cups/string.h>
+
+
+/*
+ * Local functions...
+ */
+
+void print_attributes(ipp_t *ipp, int indent);
+
+
+/*
+ * 'main()' - Main entry for the test notifier.
+ */
+
+int /* O - Exit status */
+main(int argc, /* I - Number of command-line arguments */
+ char *argv[]) /* I - Command-line arguments */
+{
+ int i; /* Looping var */
+ ipp_t *event; /* Event from scheduler */
+ ipp_state_t state; /* IPP event state */
+
+
+ setbuf(stderr, NULL);
+
+ fprintf(stderr, "DEBUG: argc=%d\n", argc);
+ for (i = 0; i < argc; i ++)
+ fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]);
+ fprintf(stderr, "DEBUG: TMPDIR=\"%s\"\n", getenv("TMPDIR"));
+
+ for (;;)
+ {
+ event = ippNew();
+ while ((state = ippReadFile(0, event)) != IPP_DATA)
+ {
+ if (state <= IPP_IDLE)
+ break;
+ }
+
+ if (state == IPP_ERROR)
+ fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr);
+
+ if (state <= IPP_IDLE)
+ {
+ ippDelete(event);
+ return (0);
+ }
+
+ print_attributes(event, 4);
+ ippDelete(event);
+
+ /*
+ * If the recipient URI is "testnotify://nowait", then we exit after each
+ * event...
+ */
+
+ if (!strcmp(argv[1], "testnotify://nowait"))
+ return (0);
+ }
+}
+
+
+/*
+ * 'print_attributes()' - Print the attributes in a request...
+ */
+
+void
+print_attributes(ipp_t *ipp, /* I - IPP request */
+ int indent) /* I - Indentation */
+{
+ int i; /* Looping var */
+ ipp_tag_t group; /* Current group */
+ ipp_attribute_t *attr; /* Current attribute */
+ ipp_value_t *val; /* Current value */
+ static const char * const tags[] = /* Value/group tag strings */
+ {
+ "reserved-00",
+ "operation-attributes-tag",
+ "job-attributes-tag",
+ "end-of-attributes-tag",
+ "printer-attributes-tag",
+ "unsupported-attributes-tag",
+ "subscription-attributes-tag",
+ "event-attributes-tag",
+ "reserved-08",
+ "reserved-09",
+ "reserved-0A",
+ "reserved-0B",
+ "reserved-0C",
+ "reserved-0D",
+ "reserved-0E",
+ "reserved-0F",
+ "unsupported",
+ "default",
+ "unknown",
+ "no-value",
+ "reserved-14",
+ "not-settable",
+ "delete-attr",
+ "admin-define",
+ "reserved-18",
+ "reserved-19",
+ "reserved-1A",
+ "reserved-1B",
+ "reserved-1C",
+ "reserved-1D",
+ "reserved-1E",
+ "reserved-1F",
+ "reserved-20",
+ "integer",
+ "boolean",
+ "enum",
+ "reserved-24",
+ "reserved-25",
+ "reserved-26",
+ "reserved-27",
+ "reserved-28",
+ "reserved-29",
+ "reserved-2a",
+ "reserved-2b",
+ "reserved-2c",
+ "reserved-2d",
+ "reserved-2e",
+ "reserved-2f",
+ "octetString",
+ "dateTime",
+ "resolution",
+ "rangeOfInteger",
+ "begCollection",
+ "textWithLanguage",
+ "nameWithLanguage",
+ "endCollection",
+ "reserved-38",
+ "reserved-39",
+ "reserved-3a",
+ "reserved-3b",
+ "reserved-3c",
+ "reserved-3d",
+ "reserved-3e",
+ "reserved-3f",
+ "reserved-40",
+ "textWithoutLanguage",
+ "nameWithoutLanguage",
+ "reserved-43",
+ "keyword",
+ "uri",
+ "uriScheme",
+ "charset",
+ "naturalLanguage",
+ "mimeMediaType",
+ "memberName"
+ };
+
+
+ for (group = IPP_TAG_ZERO, attr = ipp->attrs; attr; attr = attr->next)
+ {
+ if ((attr->group_tag == IPP_TAG_ZERO && indent <= 8) || !attr->name)
+ {
+ group = IPP_TAG_ZERO;
+ fputc('\n', stderr);
+ continue;
+ }
+
+ if (group != attr->group_tag)
+ {
+ group = attr->group_tag;
+
+ fprintf(stderr, "DEBUG: %*s%s:\n\n", indent - 4, "", tags[group]);
+ }
+
+ fprintf(stderr, "DEBUG: %*s%s (", indent, "", attr->name);
+ if (attr->num_values > 1)
+ fputs("1setOf ", stderr);
+ fprintf(stderr, "%s):", tags[attr->value_tag]);
+
+ switch (attr->value_tag)
+ {
+ case IPP_TAG_ENUM :
+ case IPP_TAG_INTEGER :
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ fprintf(stderr, " %d", val->integer);
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_BOOLEAN :
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ fprintf(stderr, " %s", val->boolean ? "true" : "false");
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_RANGE :
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ fprintf(stderr, " %d-%d", val->range.lower, val->range.upper);
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_DATE :
+ {
+ time_t vtime; /* Date/Time value */
+ struct tm *vdate; /* Date info */
+ char vstring[256]; /* Formatted time */
+
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ {
+ vtime = ippDateToTime(val->date);
+ vdate = localtime(&vtime);
+ strftime(vstring, sizeof(vstring), "%c", vdate);
+ fprintf(stderr, " (%s)", vstring);
+ }
+ }
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_RESOLUTION :
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ fprintf(stderr, " %dx%d%s", val->resolution.xres,
+ val->resolution.yres,
+ val->resolution.units == IPP_RES_PER_INCH ? "dpi" : "dpc");
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_STRING :
+ case IPP_TAG_TEXTLANG :
+ case IPP_TAG_NAMELANG :
+ case IPP_TAG_TEXT :
+ case IPP_TAG_NAME :
+ case IPP_TAG_KEYWORD :
+ case IPP_TAG_URI :
+ case IPP_TAG_URISCHEME :
+ case IPP_TAG_CHARSET :
+ case IPP_TAG_LANGUAGE :
+ case IPP_TAG_MIMETYPE :
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ fprintf(stderr, " \"%s\"", val->string.text);
+ fputc('\n', stderr);
+ break;
+
+ case IPP_TAG_BEGIN_COLLECTION :
+ fputc('\n', stderr);
+
+ for (i = 0, val = attr->values; i < attr->num_values; i ++, val ++)
+ {
+ if (i)
+ fputc('\n', stderr);
+ print_attributes(val->collection, indent + 4);
+ }
+ break;
+
+ default :
+ fprintf(stderr, "UNKNOWN (%d values)\n", attr->num_values);
+ break;
+ }
+ }
+}
+
+
+/*
+ * End of "$Id: testnotify.c 7997 2008-10-01 21:20:33Z mike $".
+ */