#include "cupsd.h"
#ifdef HAVE_DBUS
# 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))
# endif
#endif
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
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);
void
cupsdAddEvent(
cupsd_eventmask_t event,
cupsd_printer_t *dest,
cupsd_job_t *job,
const char *text,
...)
{
va_list ap;
char ftext[1024];
ipp_attribute_t *attr;
cupsd_event_t *temp;
cupsd_subscription_t *sub;
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);
LastEvent |= event;
#ifdef HAVE_DBUS
cupsd_send_dbus(event, dest, job);
#endif
if (MaxEvents <= 0)
{
cupsdLogMessage(CUPSD_LOG_WARN,
"cupsdAddEvent: Discarding %s event since MaxEvents is %d!",
cupsdEventName(event), MaxEvents);
return;
}
for (temp = NULL, sub = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions);
sub;
sub = (cupsd_subscription_t *)cupsArrayNext(Subscriptions))
{
if ((sub->mask & event) != 0 &&
(sub->dest == dest || !sub->dest) &&
(sub->job == job || !sub->job))
{
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;
temp->dest = dest;
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-langugage", 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)
{
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", 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", dest->accepting);
}
if (job)
{
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", 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);
}
cupsd_send_notification(sub, temp);
}
}
if (temp)
cupsdMarkDirty(CUPSD_DIRTY_SUBSCRIPTIONS);
else
cupsdLogMessage(CUPSD_LOG_DEBUG, "Discarding unused %s event...",
cupsdEventName(event));
}
cupsd_subscription_t *
cupsdAddSubscription(
unsigned mask,
cupsd_printer_t *dest,
cupsd_job_t *job,
const char *uri,
int sub_id)
{
cupsd_subscription_t *temp;
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);
}
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;
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;
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);
}
}
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);
}
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);
cupsArrayAdd(Subscriptions, temp);
if (uri && !strncmp(uri, "rss:", 4))
cupsd_start_notifier(temp);
return (temp);
}
void
cupsdDeleteAllSubscriptions(void)
{
cupsd_subscription_t *sub;
if (!Subscriptions)
return;
for (sub = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions);
sub;
sub = (cupsd_subscription_t *)cupsArrayNext(Subscriptions))
cupsdDeleteSubscription(sub, 0);
cupsArrayDelete(Subscriptions);
Subscriptions = NULL;
}
void
cupsdDeleteSubscription(
cupsd_subscription_t *sub,
int update)
{
int i;
if (sub->pipe >= 0)
close(sub->pipe);
cupsArrayRemove(Subscriptions, sub);
cupsdClearString(&(sub->owner));
cupsdClearString(&(sub->recipient));
if (sub->events)
{
for (i = 0; i < sub->num_events; i ++)
cupsd_delete_event(sub->events[i]);
free(sub->events);
}
free(sub);
if (update)
cupsdMarkDirty(CUPSD_DIRTY_SUBSCRIPTIONS);
}
const char *
cupsdEventName(
cupsd_eventmask_t event)
{
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");
}
}
cupsd_eventmask_t
cupsdEventValue(const char *name)
{
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);
}
void
cupsdExpireSubscriptions(
cupsd_printer_t *dest,
cupsd_job_t *job)
{
cupsd_subscription_t *sub;
int update;
time_t curtime;
curtime = time(NULL);
update = 0;
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);
}
cupsd_subscription_t *
cupsdFindSubscription(int id)
{
cupsd_subscription_t sub;
sub.id = id;
return ((cupsd_subscription_t *)cupsArrayFind(Subscriptions, &sub));
}
void
cupsdLoadAllSubscriptions(void)
{
int i;
cups_file_t *fp;
int linenum;
char line[1024],
*value,
*valueptr;
cupsd_subscription_t *sub;
int hex;
int delete_sub;
snprintf(line, sizeof(line), "%s/subscriptions.conf", ServerRoot);
if ((fp = cupsFileOpen(line, "r")) == NULL)
{
if (errno != ENOENT)
cupsdLogMessage(CUPSD_LOG_ERROR,
"LoadAllSubscriptions: Unable to open %s - %s", line,
strerror(errno));
return;
}
linenum = 0;
sub = NULL;
delete_sub = 0;
while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
{
if (!strcasecmp(line, "NextSubscriptionId") && value)
{
i = atoi(value);
if (i >= NextSubscriptionId && i > 0)
NextSubscriptionId = i;
}
else if (!strcasecmp(line, "<Subscription"))
{
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 (!strcasecmp(line, "</Subscription>"))
{
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 (!strcasecmp(line, "Events"))
{
if (!value)
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of subscriptions.conf.",
linenum);
break;
}
while (*value)
{
for (valueptr = value; !isspace(*valueptr) && *valueptr; valueptr ++);
while (isspace(*valueptr & 255))
*valueptr++ = '\0';
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 (!strcasecmp(line, "Owner"))
{
if (value)
cupsdSetString(&sub->owner, value);
else
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of subscriptions.conf.",
linenum);
break;
}
}
else if (!strcasecmp(line, "Recipient"))
{
if (value)
cupsdSetString(&sub->recipient, value);
else
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Syntax error on line %d of subscriptions.conf.",
linenum);
break;
}
}
else if (!strcasecmp(line, "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 (!strcasecmp(line, "PrinterName"))
{
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 (!strcasecmp(line, "UserData"))
{
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] = (valueptr[0] - '0') << 4;
else
sub->user_data[i] = (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] = *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 (!strcasecmp(line, "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 (!strcasecmp(line, "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 (!strcasecmp(line, "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 (!strcasecmp(line, "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
{
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unknown configuration directive %s on line %d of subscriptions.conf.",
line, linenum);
}
}
cupsFileClose(fp);
}
void
cupsdSaveAllSubscriptions(void)
{
int i;
cups_file_t *fp;
char temp[1024];
char backup[1024];
cupsd_subscription_t *sub;
time_t curtime;
struct tm *curdate;
unsigned mask;
const char *name;
int hex;
snprintf(temp, sizeof(temp), "%s/subscriptions.conf", ServerRoot);
snprintf(backup, sizeof(backup), "%s/subscriptions.conf.O", ServerRoot);
if (rename(temp, backup))
{
if (errno != ENOENT)
cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to backup subscriptions.conf - %s",
strerror(errno));
}
if ((fp = cupsFileOpen(temp, "w")) == NULL)
{
cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to save subscriptions.conf - %s",
strerror(errno));
if (rename(backup, temp))
cupsdLogMessage(CUPSD_LOG_ERROR,
"Unable to restore subscriptions.conf - %s",
strerror(errno));
return;
}
else
cupsdLogMessage(CUPSD_LOG_INFO, "Saving subscriptions.conf...");
fchown(cupsFileNumber(fp), getuid(), Group);
fchmod(cupsFileNumber(fp), ConfigFilePerm);
curtime = time(NULL);
curdate = localtime(&curtime);
strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", curdate);
cupsFilePuts(fp, "# Subscription configuration file for " CUPS_SVERSION "\n");
cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp);
cupsFilePrintf(fp, "NextSubscriptionId %d\n", NextSubscriptionId);
for (sub = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions);
sub;
sub = (cupsd_subscription_t *)cupsArrayNext(Subscriptions))
{
cupsFilePrintf(fp, "<Subscription %d>\n", sub->id);
if ((name = cupsdEventName((cupsd_eventmask_t)sub->mask)) != NULL)
{
cupsFilePrintf(fp, "Events %s\n", name);
}
else
{
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, "</Subscription>\n");
}
cupsFileClose(fp);
}
void
cupsdStopAllNotifiers(void)
{
cupsd_subscription_t *sub;
if (!NotifierStatusBuffer)
return;
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;
}
if (NotifierPipes[0] >= 0)
{
cupsdRemoveSelect(NotifierPipes[0]);
cupsdStatBufDelete(NotifierStatusBuffer);
close(NotifierPipes[0]);
close(NotifierPipes[1]);
NotifierPipes[0] = -1;
NotifierPipes[1] = -1;
NotifierStatusBuffer = NULL;
}
}
static int
cupsd_compare_subscriptions(
cupsd_subscription_t *first,
cupsd_subscription_t *second,
void *unused)
{
(void)unused;
return (first->id - second->id);
}
static void
cupsd_delete_event(cupsd_event_t *event)
{
ippDelete(event->attrs);
free(event);
}
#ifdef HAVE_DBUS
static void
cupsd_send_dbus(cupsd_eventmask_t event,
cupsd_printer_t *dest,
cupsd_job_t *job)
{
DBusError error;
DBusMessage *message;
DBusMessageIter iter;
const char *what;
static DBusConnection *con = NULL;
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;
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;
}
}
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
static void
cupsd_send_notification(
cupsd_subscription_t *sub,
cupsd_event_t *event)
{
ipp_state_t state;
cupsdLogMessage(CUPSD_LOG_DEBUG2,
"cupsd_send_notification(sub=%p(%d), event=%p(%s))",
sub, sub->id, event, cupsdEventName(event->event));
if (!sub->events)
{
sub->events = calloc(MaxEvents, sizeof(cupsd_event_t *));
if (!sub->events)
{
cupsdLogMessage(CUPSD_LOG_CRIT,
"Unable to allocate memory for subscription #%d!",
sub->id);
return;
}
}
if (sub->num_events >= MaxEvents)
{
cupsd_delete_event(sub->events[0]);
sub->num_events --;
sub->first_event_id ++;
memmove(sub->events, sub->events + 1,
sub->num_events * sizeof(cupsd_event_t *));
}
sub->events[sub->num_events] = event;
sub->num_events ++;
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)
{
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);
}
break;
}
}
sub->next_event_id ++;
}
static void
cupsd_start_notifier(
cupsd_subscription_t *sub)
{
int pid;
int fds[2];
char *argv[4],
*envp[MAX_ENV],
user_data[128],
scheme[256],
*ptr,
command[1024];
strlcpy(scheme, sub->recipient, sizeof(scheme));
if ((ptr = strchr(scheme, ':')) != NULL)
*ptr = '\0';
snprintf(command, sizeof(command), "%s/notifier/%s", ServerBin, scheme);
httpEncode64_2(user_data, sizeof(user_data), (char *)sub->user_data,
sub->user_data_len);
argv[0] = command;
argv[1] = sub->recipient;
argv[2] = user_data;
argv[3] = NULL;
cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0])));
if (!NotifierStatusBuffer)
{
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;
}
fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | O_NONBLOCK);
if (cupsdStartProcess(command, argv, envp, fds[0], -1, NotifierPipes[1],
-1, -1, 0, DefaultProfile, NULL, &pid) < 0)
{
cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to fork for notifier %s - %s",
scheme, strerror(errno));
cupsdClosePipe(fds);
}
else
{
cupsdLogMessage(CUPSD_LOG_DEBUG, "Notifier %s started - PID = %d",
scheme, pid);
sub->pid = pid;
sub->pipe = fds[1];
sub->status = 0;
close(fds[0]);
}
}
void
cupsd_update_notifier(void)
{
char message[1024];
int loglevel;
while (cupsdStatBufUpdate(NotifierStatusBuffer, &loglevel,
message, sizeof(message)))
{
if (loglevel == CUPSD_LOG_INFO)
cupsdLogMessage(CUPSD_LOG_INFO, "%s", message);
if (!strchr(NotifierStatusBuffer->buffer, '\n'))
break;
}
}