/* * Subscription routines for the CUPS scheduler. * * Copyright © 2007-2019 by Apple Inc. * Copyright © 1997-2007 by Easy Software Products, all rights reserved. * * Licensed under Apache License v2.0. See the file "LICENSE" for more * information. */ /* * Include necessary headers... */ #include "cupsd.h" #ifdef HAVE_DBUS # include # 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)) # endif /* HAVE_DBUS_MESSAGE_ITER_INIT_APPEND */ #endif /* HAVE_DBUS */ /* * Local functions... */ static int cupsd_compare_subscriptions(cupsd_subscription_t *first, cupsd_subscription_t *second, void *unused); static void cupsd_delete_event(cupsd_event_t *event); #ifdef HAVE_DBUS static void cupsd_send_dbus(cupsd_eventmask_t event, cupsd_printer_t *dest, cupsd_job_t *job); #endif /* HAVE_DBUS */ static void cupsd_send_notification(cupsd_subscription_t *sub, cupsd_event_t *event); static void cupsd_start_notifier(cupsd_subscription_t *sub); static void cupsd_update_notifier(void); /* * 'cupsdAddEvent()' - Add an event to the global event cache. */ void cupsdAddEvent( cupsd_eventmask_t event, /* I - Event */ cupsd_printer_t *dest, /* I - Printer associated with event */ cupsd_job_t *job, /* I - Job associated with event */ const char *text, /* I - Notification text */ ...) /* I - Additional arguments as needed */ { va_list ap; /* Pointer to additional arguments */ char ftext[1024]; /* Formatted text buffer */ ipp_attribute_t *attr; /* Printer/job attribute */ cupsd_event_t *temp; /* New event pointer */ cupsd_subscription_t *sub; /* Current subscription */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdAddEvent(event=%s, dest=%p(%s), job=%p(%d), text=\"%s\", ...)", cupsdEventName(event), dest, dest ? dest->name : "", job, job ? job->id : 0, text); /* * Keep track of events with any OS-supplied notification mechanisms... */ LastEvent |= event; #ifdef HAVE_DBUS cupsd_send_dbus(event, dest, job); #endif /* HAVE_DBUS */ /* * Return if we aren't keeping events... */ if (MaxEvents <= 0) { cupsdLogMessage(CUPSD_LOG_WARN, "cupsdAddEvent: Discarding %s event since MaxEvents is %d!", cupsdEventName(event), MaxEvents); return; } /* * Then loop through the subscriptions and add the event to the corresponding * caches... */ for (temp = NULL, sub = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions); sub; sub = (cupsd_subscription_t *)cupsArrayNext(Subscriptions)) { /* * Check if this subscription requires this event... */ if ((sub->mask & event) != 0 && (sub->dest == dest || !sub->dest || sub->job == job)) { /* * Need this event, so create a new event record... */ if ((temp = (cupsd_event_t *)calloc(1, sizeof(cupsd_event_t))) == NULL) { cupsdLogMessage(CUPSD_LOG_CRIT, "Unable to allocate memory for event - %s", strerror(errno)); return; } temp->event = event; temp->time = time(NULL); temp->attrs = ippNew(); temp->job = job; if (dest) temp->dest = dest; else if (job) temp->dest = dest = cupsdFindPrinter(job->dest); /* * Add common event notification attributes... */ ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_CHARSET, "notify-charset", NULL, "utf-8"); ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_LANGUAGE, "notify-natural-language", NULL, "en-US"); ippAddInteger(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_INTEGER, "notify-subscription-id", sub->id); ippAddInteger(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_INTEGER, "notify-sequence-number", sub->next_event_id); ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "notify-subscribed-event", NULL, cupsdEventName(event)); if (sub->user_data_len > 0) ippAddOctetString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, "notify-user-data", sub->user_data, sub->user_data_len); ippAddInteger(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_INTEGER, "printer-up-time", time(NULL)); va_start(ap, text); vsnprintf(ftext, sizeof(ftext), text, ap); va_end(ap); ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_TEXT, "notify-text", NULL, ftext); if (dest) { /* * Add printer attributes... */ ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_URI, "notify-printer-uri", NULL, dest->uri); ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_NAME, "printer-name", NULL, dest->name); ippAddInteger(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_ENUM, "printer-state", (int)dest->state); if (dest->num_reasons == 0) ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "printer-state-reasons", NULL, dest->state == IPP_PRINTER_STOPPED ? "paused" : "none"); else ippAddStrings(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "printer-state-reasons", dest->num_reasons, NULL, (const char * const *)dest->reasons); ippAddBoolean(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, "printer-is-accepting-jobs", (char)dest->accepting); } if (job) { /* * Add job attributes... */ ippAddInteger(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_INTEGER, "notify-job-id", job->id); ippAddInteger(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_ENUM, "job-state", (int)job->state_value); if ((attr = ippFindAttribute(job->attrs, "job-name", IPP_TAG_NAME)) != NULL) ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_NAME, "job-name", NULL, attr->values[0].string.text); switch (job->state_value) { case IPP_JOB_PENDING : if (dest && dest->state == IPP_PRINTER_STOPPED) ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "job-state-reasons", NULL, "printer-stopped"); else ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "job-state-reasons", NULL, "none"); break; case IPP_JOB_HELD : if (ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_KEYWORD) != NULL || ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME) != NULL) ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "job-state-reasons", NULL, "job-hold-until-specified"); else ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "job-state-reasons", NULL, "job-incoming"); break; case IPP_JOB_PROCESSING : ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "job-state-reasons", NULL, "job-printing"); break; case IPP_JOB_STOPPED : ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "job-state-reasons", NULL, "job-stopped"); break; case IPP_JOB_CANCELED : ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "job-state-reasons", NULL, "job-canceled-by-user"); break; case IPP_JOB_ABORTED : ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "job-state-reasons", NULL, "aborted-by-system"); break; case IPP_JOB_COMPLETED : ippAddString(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_KEYWORD, "job-state-reasons", NULL, "job-completed-successfully"); break; } ippAddInteger(temp->attrs, IPP_TAG_EVENT_NOTIFICATION, IPP_TAG_INTEGER, "job-impressions-completed", job->sheets ? job->sheets->values[0].integer : 0); } /* * Send the notification for this subscription... */ cupsd_send_notification(sub, temp); } } if (temp) cupsdMarkDirty(CUPSD_DIRTY_SUBSCRIPTIONS); else cupsdLogMessage(CUPSD_LOG_DEBUG, "Discarding unused %s event...", cupsdEventName(event)); } /* * 'cupsdAddSubscription()' - Add a new subscription object. */ cupsd_subscription_t * /* O - New subscription object */ cupsdAddSubscription( unsigned mask, /* I - Event mask */ cupsd_printer_t *dest, /* I - Printer, if any */ cupsd_job_t *job, /* I - Job, if any */ const char *uri, /* I - notify-recipient-uri, if any */ int sub_id) /* I - notify-subscription-id or 0 */ { cupsd_subscription_t *temp; /* New subscription object */ cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdAddSubscription(mask=%x, dest=%p(%s), job=%p(%d), " "uri=\"%s\")", mask, dest, dest ? dest->name : "", job, job ? job->id : 0, uri ? uri : "(null)"); if (!Subscriptions) Subscriptions = cupsArrayNew((cups_array_func_t)cupsd_compare_subscriptions, NULL); if (!Subscriptions) { cupsdLogMessage(CUPSD_LOG_CRIT, "Unable to allocate memory for subscriptions - %s", strerror(errno)); return (NULL); } /* * Limit the number of subscriptions... */ if (MaxSubscriptions > 0 && cupsArrayCount(Subscriptions) >= MaxSubscriptions) { cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdAddSubscription: Reached MaxSubscriptions %d " "(count=%d)", MaxSubscriptions, cupsArrayCount(Subscriptions)); return (NULL); } if (MaxSubscriptionsPerJob > 0 && job) { int count; /* Number of job subscriptions */ for (temp = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions), count = 0; temp; temp = (cupsd_subscription_t *)cupsArrayNext(Subscriptions)) if (temp->job == job) count ++; if (count >= MaxSubscriptionsPerJob) { cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdAddSubscription: Reached MaxSubscriptionsPerJob %d " "for job #%d (count=%d)", MaxSubscriptionsPerJob, job->id, count); return (NULL); } } if (MaxSubscriptionsPerPrinter > 0 && dest) { int count; /* Number of printer subscriptions */ for (temp = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions), count = 0; temp; temp = (cupsd_subscription_t *)cupsArrayNext(Subscriptions)) if (temp->dest == dest) count ++; if (count >= MaxSubscriptionsPerPrinter) { cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdAddSubscription: Reached " "MaxSubscriptionsPerPrinter %d for %s (count=%d)", MaxSubscriptionsPerPrinter, dest->name, count); return (NULL); } } /* * Allocate memory for this subscription... */ if ((temp = calloc(1, sizeof(cupsd_subscription_t))) == NULL) { cupsdLogMessage(CUPSD_LOG_CRIT, "Unable to allocate memory for subscription object - %s", strerror(errno)); return (NULL); } /* * Fill in common data... */ if (sub_id) { temp->id = sub_id; if (sub_id >= NextSubscriptionId) NextSubscriptionId = sub_id + 1; } else { temp->id = NextSubscriptionId; NextSubscriptionId ++; } temp->mask = mask; temp->dest = dest; temp->job = job; temp->pipe = -1; temp->first_event_id = 1; temp->next_event_id = 1; cupsdSetString(&(temp->recipient), uri); /* * Add the subscription to the array... */ cupsArrayAdd(Subscriptions, temp); /* * For RSS subscriptions, run the notifier immediately... */ if (uri && !strncmp(uri, "rss:", 4)) cupsd_start_notifier(temp); return (temp); } /* * 'cupsdDeleteAllSubscriptions()' - Delete all subscriptions. */ void cupsdDeleteAllSubscriptions(void) { cupsd_subscription_t *sub; /* Subscription */ if (!Subscriptions) return; for (sub = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions); sub; sub = (cupsd_subscription_t *)cupsArrayNext(Subscriptions)) cupsdDeleteSubscription(sub, 0); cupsArrayDelete(Subscriptions); Subscriptions = NULL; } /* * 'cupsdDeleteSubscription()' - Delete a subscription object. */ void cupsdDeleteSubscription( cupsd_subscription_t *sub, /* I - Subscription object */ int update) /* I - 1 = update subscriptions.conf */ { /* * Close the pipe to the notifier as needed... */ if (sub->pipe >= 0) close(sub->pipe); /* * Remove subscription from array... */ cupsArrayRemove(Subscriptions, sub); /* * Free memory... */ cupsdClearString(&(sub->owner)); cupsdClearString(&(sub->recipient)); cupsArrayDelete(sub->events); free(sub); /* * Update the subscriptions as needed... */ if (update) cupsdMarkDirty(CUPSD_DIRTY_SUBSCRIPTIONS); } /* * 'cupsdEventName()' - Return a single event name. */ const char * /* O - Event name */ cupsdEventName( cupsd_eventmask_t event) /* I - Event value */ { switch (event) { default : return (NULL); case CUPSD_EVENT_PRINTER_RESTARTED : return ("printer-restarted"); case CUPSD_EVENT_PRINTER_SHUTDOWN : return ("printer-shutdown"); case CUPSD_EVENT_PRINTER_STOPPED : return ("printer-stopped"); case CUPSD_EVENT_PRINTER_FINISHINGS_CHANGED : return ("printer-finishings-changed"); case CUPSD_EVENT_PRINTER_MEDIA_CHANGED : return ("printer-media-changed"); case CUPSD_EVENT_PRINTER_ADDED : return ("printer-added"); case CUPSD_EVENT_PRINTER_DELETED : return ("printer-deleted"); case CUPSD_EVENT_PRINTER_MODIFIED : return ("printer-modified"); case CUPSD_EVENT_PRINTER_QUEUE_ORDER_CHANGED : return ("printer-queue-order-changed"); case CUPSD_EVENT_PRINTER_STATE : case CUPSD_EVENT_PRINTER_STATE_CHANGED : return ("printer-state-changed"); case CUPSD_EVENT_PRINTER_CONFIG : case CUPSD_EVENT_PRINTER_CONFIG_CHANGED : return ("printer-config-changed"); case CUPSD_EVENT_PRINTER_CHANGED : return ("printer-changed"); case CUPSD_EVENT_JOB_CREATED : return ("job-created"); case CUPSD_EVENT_JOB_COMPLETED : return ("job-completed"); case CUPSD_EVENT_JOB_STOPPED : return ("job-stopped"); case CUPSD_EVENT_JOB_CONFIG_CHANGED : return ("job-config-changed"); case CUPSD_EVENT_JOB_PROGRESS : return ("job-progress"); case CUPSD_EVENT_JOB_STATE : case CUPSD_EVENT_JOB_STATE_CHANGED : return ("job-state-changed"); case CUPSD_EVENT_SERVER_RESTARTED : return ("server-restarted"); case CUPSD_EVENT_SERVER_STARTED : return ("server-started"); case CUPSD_EVENT_SERVER_STOPPED : return ("server-stopped"); case CUPSD_EVENT_SERVER_AUDIT : return ("server-audit"); case CUPSD_EVENT_ALL : return ("all"); } } /* * 'cupsdEventValue()' - Return the event mask value for a name. */ cupsd_eventmask_t /* O - Event mask value */ cupsdEventValue(const char *name) /* I - Name of event */ { if (!strcmp(name, "all")) return (CUPSD_EVENT_ALL); else if (!strcmp(name, "printer-restarted")) return (CUPSD_EVENT_PRINTER_RESTARTED); else if (!strcmp(name, "printer-shutdown")) return (CUPSD_EVENT_PRINTER_SHUTDOWN); else if (!strcmp(name, "printer-stopped")) return (CUPSD_EVENT_PRINTER_STOPPED); else if (!strcmp(name, "printer-finishings-changed")) return (CUPSD_EVENT_PRINTER_FINISHINGS_CHANGED); else if (!strcmp(name, "printer-media-changed")) return (CUPSD_EVENT_PRINTER_MEDIA_CHANGED); else if (!strcmp(name, "printer-added")) return (CUPSD_EVENT_PRINTER_ADDED); else if (!strcmp(name, "printer-deleted")) return (CUPSD_EVENT_PRINTER_DELETED); else if (!strcmp(name, "printer-modified")) return (CUPSD_EVENT_PRINTER_MODIFIED); else if (!strcmp(name, "printer-queue-order-changed")) return (CUPSD_EVENT_PRINTER_QUEUE_ORDER_CHANGED); else if (!strcmp(name, "printer-state-changed")) return (CUPSD_EVENT_PRINTER_STATE_CHANGED); else if (!strcmp(name, "printer-config-changed")) return (CUPSD_EVENT_PRINTER_CONFIG_CHANGED); else if (!strcmp(name, "printer-changed")) return (CUPSD_EVENT_PRINTER_CHANGED); else if (!strcmp(name, "job-created")) return (CUPSD_EVENT_JOB_CREATED); else if (!strcmp(name, "job-completed")) return (CUPSD_EVENT_JOB_COMPLETED); else if (!strcmp(name, "job-stopped")) return (CUPSD_EVENT_JOB_STOPPED); else if (!strcmp(name, "job-config-changed")) return (CUPSD_EVENT_JOB_CONFIG_CHANGED); else if (!strcmp(name, "job-progress")) return (CUPSD_EVENT_JOB_PROGRESS); else if (!strcmp(name, "job-state-changed")) return (CUPSD_EVENT_JOB_STATE_CHANGED); else if (!strcmp(name, "server-restarted")) return (CUPSD_EVENT_SERVER_RESTARTED); else if (!strcmp(name, "server-started")) return (CUPSD_EVENT_SERVER_STARTED); else if (!strcmp(name, "server-stopped")) return (CUPSD_EVENT_SERVER_STOPPED); else if (!strcmp(name, "server-audit")) return (CUPSD_EVENT_SERVER_AUDIT); else return (CUPSD_EVENT_NONE); } /* * 'cupsdExpireSubscriptions()' - Expire old subscription objects. */ void cupsdExpireSubscriptions( cupsd_printer_t *dest, /* I - Printer, if any */ cupsd_job_t *job) /* I - Job, if any */ { cupsd_subscription_t *sub; /* Current subscription */ int update; /* Update subscriptions.conf? */ time_t curtime; /* Current time */ if (cupsArrayCount(Subscriptions) == 0) return; curtime = time(NULL); update = 0; cupsdLogMessage(CUPSD_LOG_INFO, "Expiring subscriptions..."); for (sub = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions); sub; sub = (cupsd_subscription_t *)cupsArrayNext(Subscriptions)) if ((!sub->job && !dest && sub->expire && sub->expire <= curtime) || (dest && sub->dest == dest) || (job && sub->job == job)) { cupsdLogMessage(CUPSD_LOG_INFO, "Subscription %d has expired...", sub->id); cupsdDeleteSubscription(sub, 0); update = 1; } if (update) cupsdMarkDirty(CUPSD_DIRTY_SUBSCRIPTIONS); } /* * 'cupsdFindSubscription()' - Find a subscription by ID. */ cupsd_subscription_t * /* O - Subscription object */ cupsdFindSubscription(int id) /* I - Subscription ID */ { cupsd_subscription_t sub; /* Subscription template */ sub.id = id; return ((cupsd_subscription_t *)cupsArrayFind(Subscriptions, &sub)); } /* * 'cupsdLoadAllSubscriptions()' - Load all subscriptions from the .conf file. */ void cupsdLoadAllSubscriptions(void) { int i; /* Looping var */ cups_file_t *fp; /* subscriptions.conf file */ int linenum; /* Current line number */ char line[1024], /* Line from file */ *value, /* Pointer to value */ *valueptr; /* Pointer into value */ cupsd_subscription_t *sub; /* Current subscription */ int hex; /* Non-zero if reading hex data */ int delete_sub; /* Delete subscription? */ /* * Open the subscriptions.conf file... */ snprintf(line, sizeof(line), "%s/subscriptions.conf", ServerRoot); if ((fp = cupsdOpenConfFile(line)) == NULL) return; /* * Read all of the lines from the file... */ linenum = 0; sub = NULL; delete_sub = 0; while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) { if (!_cups_strcasecmp(line, "NextSubscriptionId") && value) { /* * NextSubscriptionId NNN */ i = atoi(value); if (i >= NextSubscriptionId && i > 0) NextSubscriptionId = i; } else if (!_cups_strcasecmp(line, " */ if (!sub && value && isdigit(value[0] & 255)) { sub = cupsdAddSubscription(CUPSD_EVENT_NONE, NULL, NULL, NULL, atoi(value)); } else { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } } else if (!_cups_strcasecmp(line, "")) { if (!sub) { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } if (delete_sub) cupsdDeleteSubscription(sub, 0); sub = NULL; delete_sub = 0; } else if (!sub) { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); } else if (!_cups_strcasecmp(line, "Events")) { /* * Events name * Events name name name ... */ if (!value) { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } while (*value) { /* * Separate event names... */ for (valueptr = value; !isspace(*valueptr) && *valueptr; valueptr ++); while (isspace(*valueptr & 255)) *valueptr++ = '\0'; /* * See if the name exists... */ if ((sub->mask |= cupsdEventValue(value)) == CUPSD_EVENT_NONE) { cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown event name \'%s\' on line %d of subscriptions.conf.", value, linenum); break; } value = valueptr; } } else if (!_cups_strcasecmp(line, "Owner")) { /* * Owner */ if (value) cupsdSetString(&sub->owner, value); else { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } } else if (!_cups_strcasecmp(line, "Recipient")) { /* * Recipient uri */ if (value) cupsdSetString(&sub->recipient, value); else { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } } else if (!_cups_strcasecmp(line, "JobId")) { /* * JobId # */ if (value && isdigit(*value & 255)) { if ((sub->job = cupsdFindJob(atoi(value))) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "Job %s not found on line %d of subscriptions.conf.", value, linenum); delete_sub = 1; } } else { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } } else if (!_cups_strcasecmp(line, "PrinterName")) { /* * PrinterName name */ if (value) { if ((sub->dest = cupsdFindDest(value)) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, "Printer \'%s\' not found on line %d of subscriptions.conf.", value, linenum); delete_sub = 1; } } else { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } } else if (!_cups_strcasecmp(line, "UserData")) { /* * UserData encoded-string */ if (value) { for (i = 0, valueptr = value, hex = 0; i < 63 && *valueptr; i ++) { if (*valueptr == '<' && !hex) { hex = 1; valueptr ++; } if (hex) { if (isxdigit(valueptr[0]) && isxdigit(valueptr[1])) { if (isdigit(valueptr[0])) sub->user_data[i] = (unsigned char)((valueptr[0] - '0') << 4); else sub->user_data[i] = (unsigned char)((tolower(valueptr[0]) - 'a' + 10) << 4); if (isdigit(valueptr[1])) sub->user_data[i] |= valueptr[1] - '0'; else sub->user_data[i] |= tolower(valueptr[1]) - 'a' + 10; valueptr += 2; if (*valueptr == '>') { hex = 0; valueptr ++; } } else break; } else sub->user_data[i] = (unsigned char)*valueptr++; } if (*valueptr) { cupsdLogMessage(CUPSD_LOG_ERROR, "Bad UserData \'%s\' on line %d of subscriptions.conf.", value, linenum); } else sub->user_data_len = i; } else { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } } else if (!_cups_strcasecmp(line, "LeaseDuration")) { /* * LeaseDuration # */ if (value && isdigit(*value & 255)) { sub->lease = atoi(value); sub->expire = sub->lease ? time(NULL) + sub->lease : 0; } else { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } } else if (!_cups_strcasecmp(line, "Interval")) { /* * Interval # */ if (value && isdigit(*value & 255)) sub->interval = atoi(value); else { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } } else if (!_cups_strcasecmp(line, "ExpirationTime")) { /* * ExpirationTime # */ if (value && isdigit(*value & 255)) sub->expire = atoi(value); else { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } } else if (!_cups_strcasecmp(line, "NextEventId")) { /* * NextEventId # */ if (value && isdigit(*value & 255)) sub->next_event_id = sub->first_event_id = atoi(value); else { cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d of subscriptions.conf.", linenum); break; } } else { /* * Something else we don't understand... */ cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown configuration directive %s on line %d of subscriptions.conf.", line, linenum); } } cupsFileClose(fp); } /* * 'cupsdSaveAllSubscriptions()' - Save all subscriptions to the .conf file. */ void cupsdSaveAllSubscriptions(void) { int i; /* Looping var */ cups_file_t *fp; /* subscriptions.conf file */ char filename[1024]; /* subscriptions.conf filename */ cupsd_subscription_t *sub; /* Current subscription */ unsigned mask; /* Current event mask */ const char *name; /* Current event name */ int hex; /* Non-zero if we are writing hex data */ /* * Create the subscriptions.conf file... */ snprintf(filename, sizeof(filename), "%s/subscriptions.conf", ServerRoot); if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL) return; cupsdLogMessage(CUPSD_LOG_INFO, "Saving subscriptions.conf..."); /* * Write a small header to the file... */ cupsFilePuts(fp, "# Subscription configuration file for " CUPS_SVERSION "\n"); cupsFilePrintf(fp, "# Written by cupsd\n"); cupsFilePrintf(fp, "NextSubscriptionId %d\n", NextSubscriptionId); /* * Write every subscription known to the system... */ for (sub = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions); sub; sub = (cupsd_subscription_t *)cupsArrayNext(Subscriptions)) { cupsFilePrintf(fp, "\n", sub->id); if ((name = cupsdEventName((cupsd_eventmask_t)sub->mask)) != NULL) { /* * Simple event list... */ cupsFilePrintf(fp, "Events %s\n", name); } else { /* * Complex event list... */ cupsFilePuts(fp, "Events"); for (mask = 1; mask < CUPSD_EVENT_ALL; mask <<= 1) if (sub->mask & mask) cupsFilePrintf(fp, " %s", cupsdEventName((cupsd_eventmask_t)mask)); cupsFilePuts(fp, "\n"); } if (sub->owner) cupsFilePrintf(fp, "Owner %s\n", sub->owner); if (sub->recipient) cupsFilePrintf(fp, "Recipient %s\n", sub->recipient); if (sub->job) cupsFilePrintf(fp, "JobId %d\n", sub->job->id); if (sub->dest) cupsFilePrintf(fp, "PrinterName %s\n", sub->dest->name); if (sub->user_data_len > 0) { cupsFilePuts(fp, "UserData "); for (i = 0, hex = 0; i < sub->user_data_len; i ++) { if (sub->user_data[i] < ' ' || sub->user_data[i] > 0x7f || sub->user_data[i] == '<') { if (!hex) { cupsFilePrintf(fp, "<%02X", sub->user_data[i]); hex = 1; } else cupsFilePrintf(fp, "%02X", sub->user_data[i]); } else { if (hex) { cupsFilePrintf(fp, ">%c", sub->user_data[i]); hex = 0; } else cupsFilePutChar(fp, sub->user_data[i]); } } if (hex) cupsFilePuts(fp, ">\n"); else cupsFilePutChar(fp, '\n'); } cupsFilePrintf(fp, "LeaseDuration %d\n", sub->lease); cupsFilePrintf(fp, "Interval %d\n", sub->interval); cupsFilePrintf(fp, "ExpirationTime %ld\n", (long)sub->expire); cupsFilePrintf(fp, "NextEventId %d\n", sub->next_event_id); cupsFilePuts(fp, "\n"); } cupsdCloseCreatedConfFile(fp, filename); } /* * 'cupsdStopAllNotifiers()' - Stop all notifier processes. */ void cupsdStopAllNotifiers(void) { cupsd_subscription_t *sub; /* Current subscription */ /* * See if we have started any notifiers... */ if (!NotifierStatusBuffer) return; /* * Yes, kill any processes that are left... */ for (sub = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions); sub; sub = (cupsd_subscription_t *)cupsArrayNext(Subscriptions)) if (sub->pid) { cupsdEndProcess(sub->pid, 0); close(sub->pipe); sub->pipe = -1; } /* * Close the status pipes... */ if (NotifierPipes[0] >= 0) { cupsdRemoveSelect(NotifierPipes[0]); cupsdStatBufDelete(NotifierStatusBuffer); close(NotifierPipes[0]); close(NotifierPipes[1]); NotifierPipes[0] = -1; NotifierPipes[1] = -1; NotifierStatusBuffer = NULL; } } /* * 'cupsd_compare_subscriptions()' - Compare two subscriptions. */ static int /* O - Result of comparison */ cupsd_compare_subscriptions( cupsd_subscription_t *first, /* I - First subscription object */ cupsd_subscription_t *second, /* I - Second subscription object */ void *unused) /* I - Unused user data pointer */ { (void)unused; return (first->id - second->id); } /* * 'cupsd_delete_event()' - Delete a single event... * * Oldest events must be deleted first, otherwise the subscription cache * flushing code will not work properly. */ static void cupsd_delete_event(cupsd_event_t *event)/* I - Event to delete */ { /* * Free memory... */ ippDelete(event->attrs); free(event); } #ifdef HAVE_DBUS /* * 'cupsd_send_dbus()' - Send a DBUS notification... */ static void cupsd_send_dbus(cupsd_eventmask_t event,/* I - Event to send */ cupsd_printer_t *dest,/* I - Destination, if any */ cupsd_job_t *job) /* I - Job, if any */ { DBusError error; /* Error, if any */ DBusMessage *message; /* Message to send */ DBusMessageIter iter; /* Iterator for message data */ const char *what; /* What to send */ static DBusConnection *con = NULL; /* Connection to DBUS server */ /* * Figure out what to send, if anything... */ if (event & CUPSD_EVENT_PRINTER_ADDED) what = "PrinterAdded"; else if (event & CUPSD_EVENT_PRINTER_DELETED) what = "PrinterRemoved"; else if (event & CUPSD_EVENT_PRINTER_CHANGED) what = "QueueChanged"; else if (event & CUPSD_EVENT_JOB_CREATED) what = "JobQueuedLocal"; else if ((event & CUPSD_EVENT_JOB_STATE) && job && job->state_value == IPP_JOB_PROCESSING) what = "JobStartedLocal"; else return; /* * 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(getuid() ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &error); if (!con) { dbus_error_free(&error); return; } } /* * Create and send the new message... */ message = dbus_message_new_signal("/com/redhat/PrinterSpooler", "com.redhat.PrinterSpooler", what); dbus_message_append_iter_init(message, &iter); if (dest) dbus_message_iter_append_string(&iter, dest->name); if (job) { dbus_message_iter_append_uint32(&iter, job->id); dbus_message_iter_append_string(&iter, job->username); } dbus_connection_send(con, message, NULL); dbus_connection_flush(con); dbus_message_unref(message); } #endif /* HAVE_DBUS */ /* * 'cupsd_send_notification()' - Send a notification for the specified event. */ static void cupsd_send_notification( cupsd_subscription_t *sub, /* I - Subscription object */ cupsd_event_t *event) /* I - Event to send */ { ipp_state_t state; /* IPP event state */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsd_send_notification(sub=%p(%d), event=%p(%s))", sub, sub->id, event, cupsdEventName(event->event)); /* * Allocate the events array as needed... */ if (!sub->events) { sub->events = cupsArrayNew3((cups_array_func_t)NULL, NULL, (cups_ahash_func_t)NULL, 0, (cups_acopy_func_t)NULL, (cups_afree_func_t)cupsd_delete_event); if (!sub->events) { cupsdLogMessage(CUPSD_LOG_CRIT, "Unable to allocate memory for subscription #%d!", sub->id); return; } } /* * Purge an old event as needed... */ if (cupsArrayCount(sub->events) >= MaxEvents) { /* * Purge the oldest event in the cache... */ cupsArrayRemove(sub->events, cupsArrayFirst(sub->events)); sub->first_event_id ++; } /* * Add the event to the subscription. Since the events array is * always MaxEvents in length, and since we will have already * removed an event from the subscription cache if we hit the * event cache limit, we don't need to check for overflow here... */ cupsArrayAdd(sub->events, event); /* * Deliver the event... */ if (sub->recipient) { for (;;) { if (sub->pipe < 0) cupsd_start_notifier(sub); cupsdLogMessage(CUPSD_LOG_DEBUG2, "sub->pipe=%d", sub->pipe); if (sub->pipe < 0) break; event->attrs->state = IPP_IDLE; while ((state = ippWriteFile(sub->pipe, event->attrs)) != IPP_DATA) if (state == IPP_ERROR) break; if (state == IPP_ERROR) { if (errno == EPIPE) { /* * Notifier died, try restarting it... */ cupsdLogMessage(CUPSD_LOG_WARN, "Notifier for subscription %d (%s) went away, " "retrying!", sub->id, sub->recipient); cupsdEndProcess(sub->pid, 0); close(sub->pipe); sub->pipe = -1; continue; } cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to send event for subscription %d (%s)!", sub->id, sub->recipient); } /* * If we get this far, break out of the loop... */ break; } } /* * Bump the event sequence number... */ sub->next_event_id ++; } /* * 'cupsd_start_notifier()' - Start a notifier subprocess... */ static void cupsd_start_notifier( cupsd_subscription_t *sub) /* I - Subscription object */ { int pid; /* Notifier process ID */ int fds[2]; /* Pipe file descriptors */ char *argv[4], /* Command-line arguments */ *envp[MAX_ENV], /* Environment variables */ user_data[128], /* Base-64 encoded user data */ scheme[256], /* notify-recipient-uri scheme */ *ptr, /* Pointer into scheme */ command[1024]; /* Notifier command */ /* * Extract the scheme name from the recipient URI and point to the * notifier program... */ strlcpy(scheme, sub->recipient, sizeof(scheme)); if ((ptr = strchr(scheme, ':')) != NULL) *ptr = '\0'; snprintf(command, sizeof(command), "%s/notifier/%s", ServerBin, scheme); /* * Base-64 encode the user data... */ httpEncode64_2(user_data, sizeof(user_data), (char *)sub->user_data, sub->user_data_len); /* * Setup the argument array... */ argv[0] = command; argv[1] = sub->recipient; argv[2] = user_data; argv[3] = NULL; /* * Setup the environment... */ cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0]))); /* * Create pipes as needed... */ if (!NotifierStatusBuffer) { /* * Create the status pipe... */ if (cupsdOpenPipe(NotifierPipes)) { cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create pipes for notifier status - %s", strerror(errno)); return; } NotifierStatusBuffer = cupsdStatBufNew(NotifierPipes[0], "[Notifier]"); cupsdAddSelect(NotifierPipes[0], (cupsd_selfunc_t)cupsd_update_notifier, NULL, NULL); } if (cupsdOpenPipe(fds)) { cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create pipes for notifier %s - %s", scheme, strerror(errno)); return; } /* * Make sure the delivery pipe is non-blocking... */ fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | O_NONBLOCK); /* * Create the notifier process... */ if (cupsdStartProcess(command, argv, envp, fds[0], -1, NotifierPipes[1], -1, -1, 0, DefaultProfile, NULL, &pid) < 0) { /* * Error - can't fork! */ cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to fork for notifier %s - %s", scheme, strerror(errno)); cupsdClosePipe(fds); } else { /* * Fork successful - return the PID... */ cupsdLogMessage(CUPSD_LOG_DEBUG, "Notifier %s started - PID = %d", scheme, pid); sub->pid = pid; sub->pipe = fds[1]; sub->status = 0; close(fds[0]); } } /* * 'cupsd_update_notifier()' - Read messages from notifiers. */ void cupsd_update_notifier(void) { char message[1024]; /* Pointer to message text */ int loglevel; /* Log level for message */ while (cupsdStatBufUpdate(NotifierStatusBuffer, &loglevel, message, sizeof(message))) { if (loglevel == CUPSD_LOG_INFO) cupsdLogMessage(CUPSD_LOG_INFO, "%s", message); if (!strchr(NotifierStatusBuffer->buffer, '\n')) break; } }