/*
* "$Id$"
*
- * DNS-SD discovery backend for the Common UNIX Printing System (CUPS).
+ * DNS-SD discovery backend for CUPS.
*
- * Copyright 2008-2009 by Apple Inc.
+ * Copyright 2008-2014 by Apple Inc.
*
- * 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"
- * "LICENSE" which should have been included with this file. If this
- * file is missing or damaged, see the license at "http://www.cups.org/".
+ * 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"
+ * "LICENSE" which should have been included with this file. If this
+ * file is missing or damaged, see the license at "http://www.cups.org/".
*
- * This file is subject to the Apple OS-Developed Software exception.
- *
- * Contents:
- *
- * main() - Browse for printers.
- * browse_callback() - Browse devices.
- * browse_local_callback() - Browse local devices.
- * compare_devices() - Compare two devices.
- * exec_backend() - Execute the backend that corresponds to the
- * resolved service name.
- * get_device() - Create or update a device.
- * query_callback() - Process query data.
- * unquote() - Unquote a name string.
+ * This file is subject to the Apple OS-Developed Software exception.
*/
/*
#include "backend-private.h"
#include <cups/array.h>
-#include <dns_sd.h>
+#ifdef HAVE_DNSSD
+# include <dns_sd.h>
+#endif /* HAVE_DNSSD */
+#ifdef HAVE_AVAHI
+# include <avahi-client/client.h>
+# include <avahi-client/lookup.h>
+# include <avahi-common/simple-watch.h>
+# include <avahi-common/domain.h>
+# include <avahi-common/error.h>
+# include <avahi-common/malloc.h>
+#define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
+#endif /* HAVE_AVAHI */
/*
typedef enum
{
CUPS_DEVICE_PRINTER = 0, /* lpd://... */
+ CUPS_DEVICE_IPPS, /* ipps://... */
CUPS_DEVICE_IPP, /* ipp://... */
CUPS_DEVICE_FAX_IPP, /* ipp://... */
CUPS_DEVICE_PDL_DATASTREAM, /* socket://... */
typedef struct
{
- DNSServiceRef ref; /* Service reference for resolve */
+#ifdef HAVE_DNSSD
+ DNSServiceRef ref; /* Service reference for query */
+#endif /* HAVE_DNSSD */
+#ifdef HAVE_AVAHI
+ AvahiRecordBrowser *ref; /* Browser for query */
+#endif /* HAVE_AVAHI */
char *name, /* Service name */
*domain, /* Domain name */
*fullName, /* Full name */
- *make_and_model; /* Make and model from TXT record */
+ *make_and_model, /* Make and model from TXT record */
+ *device_id, /* 1284 device ID from TXT record */
+ *uuid; /* UUID from TXT record */
cups_devtype_t type; /* Device registration type */
int priority, /* Priority associated with type */
cups_shared, /* CUPS shared printer? */
} cups_device_t;
+/*
+ * Local globals...
+ */
+
+static int job_canceled = 0;
+ /* Set to 1 on SIGTERM */
+#ifdef HAVE_AVAHI
+static AvahiSimplePoll *simple_poll = NULL;
+ /* Poll information */
+static int got_data = 0; /* Got data from poll? */
+#endif /* HAVE_AVAHI */
+
+
/*
* Local functions...
*/
+#ifdef HAVE_DNSSD
static void browse_callback(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *serviceName,
const char *regtype,
- const char *replyDomain, void *context);
+ const char *replyDomain, void *context)
+ __attribute__((nonnull(1,5,6,7,8)));
static void browse_local_callback(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
const char *serviceName,
const char *regtype,
const char *replyDomain,
- void *context);
+ void *context)
+ __attribute__((nonnull(1,5,6,7,8)));
+#endif /* HAVE_DNSSD */
+#ifdef HAVE_AVAHI
+static void browse_callback(AvahiServiceBrowser *browser,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiBrowserEvent event,
+ const char *serviceName,
+ const char *regtype,
+ const char *replyDomain,
+ AvahiLookupResultFlags flags,
+ void *context);
+static void client_callback(AvahiClient *client,
+ AvahiClientState state,
+ void *context);
+#endif /* HAVE_AVAHI */
+
static int compare_devices(cups_device_t *a, cups_device_t *b);
-static void exec_backend(char **argv);
+static void exec_backend(char **argv) __attribute__((noreturn));
static cups_device_t *get_device(cups_array_t *devices,
const char *serviceName,
const char *regtype,
- const char *replyDomain);
+ const char *replyDomain)
+ __attribute__((nonnull(1,2,3,4)));
+#ifdef HAVE_DNSSD
static void query_callback(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
const char *fullName, uint16_t rrtype,
uint16_t rrclass, uint16_t rdlen,
const void *rdata, uint32_t ttl,
+ void *context)
+ __attribute__((nonnull(1,5,9,11)));
+#elif defined(HAVE_AVAHI)
+static int poll_callback(struct pollfd *pollfds,
+ unsigned int num_pollfds, int timeout,
+ void *context);
+static void query_callback(AvahiRecordBrowser *browser,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiBrowserEvent event,
+ const char *name, uint16_t rrclass,
+ uint16_t rrtype, const void *rdata,
+ size_t rdlen,
+ AvahiLookupResultFlags flags,
void *context);
-static void unquote(char *dst, const char *src, size_t dstsize);
+#endif /* HAVE_DNSSD */
+static void sigterm_handler(int sig);
+static void unquote(char *dst, const char *src, size_t dstsize)
+ __attribute__((nonnull(1,2)));
/*
char *argv[]) /* I - Command-line arguments */
{
const char *name; /* Backend name */
+ cups_array_t *devices; /* Device array */
+ cups_device_t *device; /* Current device */
+ char uriName[1024]; /* Unquoted fullName for URI */
+#ifdef HAVE_DNSSD
+ int fd; /* Main file descriptor */
+ fd_set input; /* Input set for select() */
+ struct timeval timeout; /* Timeout for select() */
DNSServiceRef main_ref, /* Main service reference */
fax_ipp_ref, /* IPP fax service reference */
ipp_ref, /* IPP service reference */
ipp_tls_ref, /* IPP w/TLS service reference */
+ ipps_ref, /* IPP service reference */
local_fax_ipp_ref, /* Local IPP fax service reference */
local_ipp_ref, /* Local IPP service reference */
local_ipp_tls_ref, /* Local IPP w/TLS service reference */
+ local_ipps_ref, /* Local IPP service reference */
local_printer_ref, /* Local LPD service reference */
pdl_datastream_ref, /* AppSocket service reference */
printer_ref, /* LPD service reference */
riousbprint_ref; /* Remote IO service reference */
- int fd; /* Main file descriptor */
- fd_set input; /* Input set for select() */
- struct timeval timeout; /* Timeout for select() */
- cups_array_t *devices; /* Device array */
- cups_device_t *device; /* Current device */
- char uriName[1024]; /* Unquoted fullName for URI */
+#endif /* HAVE_DNSSD */
+#ifdef HAVE_AVAHI
+ AvahiClient *client; /* Client information */
+ int error; /* Error code, if any */
+#endif /* HAVE_AVAHI */
+#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
+ struct sigaction action; /* Actions for POSIX signals */
+#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
/*
- * Check command-line...
+ * Don't buffer stderr, and catch SIGTERM...
*/
setbuf(stderr, NULL);
+#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
+ sigset(SIGTERM, sigterm_handler);
+#elif defined(HAVE_SIGACTION)
+ memset(&action, 0, sizeof(action));
+
+ sigemptyset(&action.sa_mask);
+ action.sa_handler = sigterm_handler;
+ sigaction(SIGTERM, &action, NULL);
+#else
+ signal(SIGTERM, sigterm_handler);
+#endif /* HAVE_SIGSET */
+
+ /*
+ * Check command-line...
+ */
+
if (argc >= 6)
exec_backend(argv);
else if (argc != 1)
{
- fprintf(stderr, "Usage: %s job user title copies options [filename(s)]\n",
- argv[0]);
+ _cupsLangPrintf(stderr,
+ _("Usage: %s job-id user title copies options [file]"),
+ argv[0]);
return (1);
}
* Browse for different kinds of printers...
*/
+#ifdef HAVE_DNSSD
if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError)
{
perror("ERROR: Unable to create service connection");
DNSServiceBrowse(&ipp_tls_ref, kDNSServiceFlagsShareConnection, 0,
"_ipp-tls._tcp", NULL, browse_callback, devices);
+ ipps_ref = main_ref;
+ DNSServiceBrowse(&ipps_ref, kDNSServiceFlagsShareConnection, 0,
+ "_ipps._tcp", NULL, browse_callback, devices);
+
local_fax_ipp_ref = main_ref;
DNSServiceBrowse(&local_fax_ipp_ref, kDNSServiceFlagsShareConnection,
kDNSServiceInterfaceIndexLocalOnly,
kDNSServiceInterfaceIndexLocalOnly,
"_ipp-tls._tcp", NULL, browse_local_callback, devices);
+ local_ipps_ref = main_ref;
+ DNSServiceBrowse(&local_ipps_ref, kDNSServiceFlagsShareConnection,
+ kDNSServiceInterfaceIndexLocalOnly,
+ "_ipps._tcp", NULL, browse_local_callback, devices);
+
local_printer_ref = main_ref;
DNSServiceBrowse(&local_printer_ref, kDNSServiceFlagsShareConnection,
kDNSServiceInterfaceIndexLocalOnly,
"_printer._tcp", NULL, browse_local_callback, devices);
- pdl_datastream_ref = main_ref;
+ pdl_datastream_ref = main_ref;
DNSServiceBrowse(&pdl_datastream_ref, kDNSServiceFlagsShareConnection, 0,
"_pdl-datastream._tcp", NULL, browse_callback, devices);
riousbprint_ref = main_ref;
DNSServiceBrowse(&riousbprint_ref, kDNSServiceFlagsShareConnection, 0,
"_riousbprint._tcp", NULL, browse_callback, devices);
+#endif /* HAVE_DNSSD */
+
+#ifdef HAVE_AVAHI
+ if ((simple_poll = avahi_simple_poll_new()) == NULL)
+ {
+ fputs("DEBUG: Unable to create Avahi simple poll object.\n", stderr);
+ return (1);
+ }
+
+ avahi_simple_poll_set_func(simple_poll, poll_callback, NULL);
+
+ client = avahi_client_new(avahi_simple_poll_get(simple_poll),
+ 0, client_callback, simple_poll, &error);
+ if (!client)
+ {
+ fputs("DEBUG: Unable to create Avahi client.\n", stderr);
+ return (1);
+ }
+
+ avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ "_fax-ipp._tcp", NULL, 0,
+ browse_callback, devices);
+ avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ "_ipp._tcp", NULL, 0,
+ browse_callback, devices);
+ avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ "_ipp-tls._tcp", NULL, 0,
+ browse_callback, devices);
+ avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ "_ipps._tcp", NULL, 0,
+ browse_callback, devices);
+ avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ "_pdl-datastream._tcp",
+ NULL, 0,
+ browse_callback,
+ devices);
+ avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ "_printer._tcp", NULL, 0,
+ browse_callback, devices);
+#endif /* HAVE_AVAHI */
/*
* Loop until we are killed...
*/
- for (;;)
+ while (!job_canceled)
{
+ int announce = 0; /* Announce printers? */
+
+#ifdef HAVE_DNSSD
FD_ZERO(&input);
FD_SET(fd, &input);
- timeout.tv_sec = 1;
- timeout.tv_usec = 0;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 500000;
if (select(fd + 1, &input, NULL, NULL, &timeout) < 0)
continue;
DNSServiceProcessResult(main_ref);
}
else
+ announce = 1;
+
+#elif defined(HAVE_AVAHI)
+ got_data = 0;
+
+ if ((error = avahi_simple_poll_iterate(simple_poll, 500)) > 0)
+ {
+ /*
+ * We've been told to exit the loop. Perhaps the connection to
+ * Avahi failed.
+ */
+
+ break;
+ }
+
+ if (!got_data)
+ announce = 1;
+#endif /* HAVE_DNSSD */
+
+/* fprintf(stderr, "DEBUG: announce=%d\n", announce);*/
+
+ if (announce)
{
/*
* Announce any devices we've found...
*/
+#ifdef HAVE_DNSSD
DNSServiceErrorType status; /* DNS query status */
+#endif /* HAVE_DNSSD */
cups_device_t *best; /* Best matching device */
char device_uri[1024]; /* Device URI */
int count; /* Number of queries */
- static const char * const schemes[] =
- { "lpd", "ipp", "ipp", "socket", "riousbprint" };
- /* URI schemes for devices */
-
+ int sent; /* Number of sent */
for (device = (cups_device_t *)cupsArrayFirst(devices),
- best = NULL, count = 0;
+ best = NULL, count = 0, sent = 0;
device;
device = (cups_device_t *)cupsArrayNext(devices))
+ {
+ if (device->sent)
+ sent ++;
+
+ if (device->ref)
+ count ++;
+
if (!device->ref && !device->sent)
{
/*
* Found the device, now get the TXT record(s) for it...
*/
- if (count < 10)
+ if (count < 50)
{
- device->ref = main_ref;
-
fprintf(stderr, "DEBUG: Querying \"%s\"...\n", device->fullName);
+#ifdef HAVE_DNSSD
+ device->ref = main_ref;
+
status = DNSServiceQueryRecord(&(device->ref),
kDNSServiceFlagsShareConnection,
0, device->fullName,
kDNSServiceType_TXT,
kDNSServiceClass_IN, query_callback,
- devices);
+ device);
if (status != kDNSServiceErr_NoError)
- {
- fputs("ERROR: Unable to query for TXT records!\n", stderr);
- fprintf(stderr, "DEBUG: DNSServiceQueryRecord returned %d\n",
- status);
- }
+ fprintf(stderr,
+ "ERROR: Unable to query \"%s\" for TXT records: %d\n",
+ device->fullName, status);
+ /* Users never see this */
else
count ++;
+
+#else
+ if ((device->ref = avahi_record_browser_new(client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ device->fullName,
+ AVAHI_DNS_CLASS_IN,
+ AVAHI_DNS_TYPE_TXT,
+ 0,
+ query_callback,
+ device)) == NULL)
+ fprintf(stderr,
+ "ERROR: Unable to query \"%s\" for TXT records: %s\n",
+ device->fullName,
+ avahi_strerror(avahi_client_errno(client)));
+ /* Users never see this */
+ else
+ count ++;
+#endif /* HAVE_AVAHI */
}
}
else if (!device->sent)
{
+#ifdef HAVE_DNSSD
/*
* Got the TXT records, now report the device...
*/
DNSServiceRefDeallocate(device->ref);
- device->ref = 0;
+#else
+ avahi_record_browser_free(device->ref);
+#endif /* HAVE_DNSSD */
+
+ device->ref = NULL;
if (!best)
best = device;
- else if (strcasecmp(best->name, device->name) ||
- strcasecmp(best->domain, device->domain))
+ else if (_cups_strcasecmp(best->name, device->name) ||
+ _cups_strcasecmp(best->domain, device->domain))
{
unquote(uriName, best->fullName, sizeof(uriName));
- httpAssembleURI(HTTP_URI_CODING_ALL, device_uri, sizeof(device_uri),
- schemes[best->type], NULL, uriName, 0,
- best->cups_shared ? "/cups" : "/");
+ if (best->uuid)
+ httpAssembleURIf(HTTP_URI_CODING_ALL, device_uri,
+ sizeof(device_uri), "dnssd", NULL, uriName, 0,
+ best->cups_shared ? "/cups?uuid=%s" : "/?uuid=%s",
+ best->uuid);
+ else
+ httpAssembleURI(HTTP_URI_CODING_ALL, device_uri,
+ sizeof(device_uri), "dnssd", NULL, uriName, 0,
+ best->cups_shared ? "/cups" : "/");
cupsBackendReport("network", device_uri, best->make_and_model,
- best->name, NULL, NULL);
+ best->name, best->device_id, NULL);
best->sent = 1;
best = device;
+
+ sent ++;
}
else if (best->priority > device->priority ||
(best->priority == device->priority &&
{
best->sent = 1;
best = device;
+
+ sent ++;
}
else
+ {
device->sent = 1;
+
+ sent ++;
+ }
}
+ }
if (best)
{
unquote(uriName, best->fullName, sizeof(uriName));
- httpAssembleURI(HTTP_URI_CODING_ALL, device_uri, sizeof(device_uri),
- schemes[best->type], NULL, uriName, 0,
- best->cups_shared ? "/cups" : "/");
+ if (best->uuid)
+ httpAssembleURIf(HTTP_URI_CODING_ALL, device_uri,
+ sizeof(device_uri), "dnssd", NULL, uriName, 0,
+ best->cups_shared ? "/cups?uuid=%s" : "/?uuid=%s",
+ best->uuid);
+ else
+ httpAssembleURI(HTTP_URI_CODING_ALL, device_uri,
+ sizeof(device_uri), "dnssd", NULL, uriName, 0,
+ best->cups_shared ? "/cups" : "/");
cupsBackendReport("network", device_uri, best->make_and_model,
- best->name, NULL, NULL);
+ best->name, best->device_id, NULL);
best->sent = 1;
+ sent ++;
}
+
+ fprintf(stderr, "DEBUG: sent=%d, count=%d\n", sent, count);
+
+ if (sent == cupsArrayCount(devices))
+ break;
}
}
+
+ return (CUPS_BACKEND_OK);
}
+#ifdef HAVE_DNSSD
/*
* 'browse_callback()' - Browse devices.
*/
"interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
"regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
sdRef, flags, interfaceIndex, errorCode,
- serviceName ? serviceName : "(null)",
- regtype ? regtype : "(null)",
- replyDomain ? replyDomain : "(null)",
- context);
+ serviceName, regtype, replyDomain, context);
/*
* Only process "add" data...
"interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
"regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
sdRef, flags, interfaceIndex, errorCode,
- serviceName ? serviceName : "(null)",
- regtype ? regtype : "(null)",
- replyDomain ? replyDomain : "(null)",
- context);
+ serviceName, regtype, replyDomain, context);
/*
* Only process "add" data...
device->fullName);
device->sent = 1;
}
+#endif /* HAVE_DNSSD */
+
+
+#ifdef HAVE_AVAHI
+/*
+ * 'browse_callback()' - Browse devices.
+ */
+
+static void
+browse_callback(
+ AvahiServiceBrowser *browser, /* I - Browser */
+ AvahiIfIndex interface, /* I - Interface index (unused) */
+ AvahiProtocol protocol, /* I - Network protocol (unused) */
+ AvahiBrowserEvent event, /* I - What happened */
+ const char *name, /* I - Service name */
+ const char *type, /* I - Registration type */
+ const char *domain, /* I - Domain */
+ AvahiLookupResultFlags flags, /* I - Flags */
+ void *context) /* I - Devices array */
+{
+ AvahiClient *client = avahi_service_browser_get_client(browser);
+ /* Client information */
+
+
+ (void)interface;
+ (void)protocol;
+ (void)context;
+
+ switch (event)
+ {
+ case AVAHI_BROWSER_FAILURE:
+ fprintf(stderr, "DEBUG: browse_callback: %s\n",
+ avahi_strerror(avahi_client_errno(client)));
+ avahi_simple_poll_quit(simple_poll);
+ break;
+
+ case AVAHI_BROWSER_NEW:
+ /*
+ * This object is new on the network.
+ */
+
+ if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
+ {
+ /*
+ * This comes from the local machine so ignore it.
+ */
+
+ fprintf(stderr, "DEBUG: Ignoring local service %s.\n", name);
+ }
+ else
+ {
+ /*
+ * Create a device entry for it if it doesn't yet exist.
+ */
+
+ get_device((cups_array_t *)context, name, type, domain);
+ }
+ break;
+
+ case AVAHI_BROWSER_REMOVE:
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ break;
+ }
+}
+
+
+/*
+ * 'client_callback()' - Avahi client callback function.
+ */
+
+static void
+client_callback(
+ AvahiClient *client, /* I - Client information (unused) */
+ AvahiClientState state, /* I - Current state */
+ void *context) /* I - User data (unused) */
+{
+ (void)client;
+ (void)context;
+
+ /*
+ * If the connection drops, quit.
+ */
+
+ if (state == AVAHI_CLIENT_FAILURE)
+ {
+ fputs("DEBUG: Avahi connection failed.\n", stderr);
+ avahi_simple_poll_quit(simple_poll);
+ }
+}
+#endif /* HAVE_AVAHI */
/*
compare_devices(cups_device_t *a, /* I - First device */
cups_device_t *b) /* I - Second device */
{
- int result = strcmp(a->name, b->name);
-
- if (result)
- return (result);
- else
- return (strcmp(a->domain, b->domain));
+ return (strcmp(a->name, b->name));
}
* Resolve the device URI...
*/
- if ((resolved_uri = cupsBackendDeviceURI(argv)) == NULL)
- exit(CUPS_BACKEND_FAILED);
+ job_canceled = -1;
+
+ while ((resolved_uri = cupsBackendDeviceURI(argv)) == NULL)
+ {
+ _cupsLangPrintFilter(stderr, "INFO", _("Unable to locate printer."));
+ sleep(10);
+
+ if (getenv("CLASS") != NULL)
+ exit(CUPS_BACKEND_FAILED);
+ }
/*
* Extract the scheme from the URI...
snprintf(filename, sizeof(filename), "%s/backend/%s", cups_serverbin, scheme);
/*
- * Overwrite the device URIs and run the new backend...
+ * Overwrite the device URI and run the new backend...
*/
setenv("DEVICE_URI", resolved_uri, 1);
}
+/*
+ * 'device_type()' - Get DNS-SD type enumeration from string.
+ */
+
+static cups_devtype_t /* O - Device type */
+device_type(const char *regtype) /* I - Service registration type */
+{
+#ifdef HAVE_AVAHI
+ if (!strcmp(regtype, "_ipp._tcp"))
+ return (CUPS_DEVICE_IPP);
+ else if (!strcmp(regtype, "_ipps._tcp") ||
+ !strcmp(regtype, "_ipp-tls._tcp"))
+ return (CUPS_DEVICE_IPPS);
+ else if (!strcmp(regtype, "_fax-ipp._tcp"))
+ return (CUPS_DEVICE_FAX_IPP);
+ else if (!strcmp(regtype, "_printer._tcp"))
+ return (CUPS_DEVICE_PDL_DATASTREAM);
+#else
+ if (!strcmp(regtype, "_ipp._tcp."))
+ return (CUPS_DEVICE_IPP);
+ else if (!strcmp(regtype, "_ipps._tcp.") ||
+ !strcmp(regtype, "_ipp-tls._tcp."))
+ return (CUPS_DEVICE_IPPS);
+ else if (!strcmp(regtype, "_fax-ipp._tcp."))
+ return (CUPS_DEVICE_FAX_IPP);
+ else if (!strcmp(regtype, "_printer._tcp."))
+ return (CUPS_DEVICE_PRINTER);
+ else if (!strcmp(regtype, "_pdl-datastream._tcp."))
+ return (CUPS_DEVICE_PDL_DATASTREAM);
+#endif /* HAVE_AVAHI */
+
+ return (CUPS_DEVICE_RIOUSBPRINT);
+}
+
+
/*
* 'get_device()' - Create or update a device.
*/
* See if this is a new device...
*/
- key.name = (char *)serviceName;
- key.domain = (char *)replyDomain;
-
- if (!strcmp(regtype, "_ipp._tcp.") ||
- !strcmp(regtype, "_ipp-tls._tcp."))
- key.type = CUPS_DEVICE_IPP;
- else if (!strcmp(regtype, "_fax-ipp._tcp."))
- key.type = CUPS_DEVICE_FAX_IPP;
- else if (!strcmp(regtype, "_printer._tcp."))
- key.type = CUPS_DEVICE_PRINTER;
- else if (!strcmp(regtype, "_pdl-datastream._tcp."))
- key.type = CUPS_DEVICE_PDL_DATASTREAM;
- else
- key.type = CUPS_DEVICE_RIOUSBPRINT;
+ key.name = (char *)serviceName;
+ key.type = device_type(regtype);
for (device = cupsArrayFind(devices, &key);
device;
device = cupsArrayNext(devices))
- if (strcasecmp(device->name, key.name) ||
- strcasecmp(device->domain, key.domain))
+ if (_cups_strcasecmp(device->name, key.name))
break;
else if (device->type == key.type)
+ {
+ if (!_cups_strcasecmp(device->domain, "local.") &&
+ _cups_strcasecmp(device->domain, replyDomain))
+ {
+ /*
+ * Update the .local listing to use the "global" domain name instead.
+ * The backend will try local lookups first, then the global domain name.
+ */
+
+ free(device->domain);
+ device->domain = strdup(replyDomain);
+
+#ifdef HAVE_DNSSD
+ DNSServiceConstructFullName(fullName, device->name, regtype,
+ replyDomain);
+#else /* HAVE_AVAHI */
+ avahi_service_name_join(fullName, kDNSServiceMaxDomainName,
+ serviceName, regtype, replyDomain);
+#endif /* HAVE_DNSSD */
+
+ free(device->fullName);
+ device->fullName = strdup(fullName);
+ }
+
return (device);
+ }
/*
* Yes, add the device...
* Set the "full name" of this service, which is used for queries...
*/
+#ifdef HAVE_DNSSD
DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain);
+#else /* HAVE_AVAHI */
+ avahi_service_name_join(fullName, kDNSServiceMaxDomainName,
+ serviceName, regtype, replyDomain);
+#endif /* HAVE_DNSSD */
+
device->fullName = strdup(fullName);
return (device);
}
+#ifdef HAVE_AVAHI
+/*
+ * 'poll_callback()' - Wait for input on the specified file descriptors.
+ *
+ * Note: This function is needed because avahi_simple_poll_iterate is broken
+ * and always uses a timeout of 0 (!) milliseconds.
+ * (Avahi Ticket #364)
+ */
+
+static int /* O - Number of file descriptors matching */
+poll_callback(
+ struct pollfd *pollfds, /* I - File descriptors */
+ unsigned int num_pollfds, /* I - Number of file descriptors */
+ int timeout, /* I - Timeout in milliseconds (unused) */
+ void *context) /* I - User data (unused) */
+{
+ int val; /* Return value */
+
+
+ (void)timeout;
+ (void)context;
+
+ val = poll(pollfds, num_pollfds, 500);
+
+ if (val < 0)
+ fprintf(stderr, "DEBUG: poll_callback: %s\n", strerror(errno));
+ else if (val > 0)
+ got_data = 1;
+
+ return (val);
+}
+#endif /* HAVE_AVAHI */
+
+
+#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
+# ifdef HAVE_DNSSD
/*
* 'query_callback()' - Process query data.
*/
uint16_t rdlen, /* I - Length of record data */
const void *rdata, /* I - Record data */
uint32_t ttl, /* I - Time-to-live */
- void *context) /* I - Devices array */
+ void *context) /* I - Device */
{
- cups_array_t *devices; /* Device array */
- char name[1024], /* Service name */
- *ptr; /* Pointer into name */
- cups_device_t key, /* Search key */
- *device; /* Device */
+# else
+/*
+ * 'query_callback()' - Process query data.
+ */
+static void
+query_callback(
+ AvahiRecordBrowser *browser, /* I - Record browser */
+ AvahiIfIndex interfaceIndex,
+ /* I - Interface index (unused) */
+ AvahiProtocol protocol, /* I - Network protocol (unused) */
+ AvahiBrowserEvent event, /* I - What happened? */
+ const char *fullName, /* I - Service name */
+ uint16_t rrclass, /* I - Record class */
+ uint16_t rrtype, /* I - Record type */
+ const void *rdata, /* I - TXT record */
+ size_t rdlen, /* I - Length of TXT record */
+ AvahiLookupResultFlags flags, /* I - Flags */
+ void *context) /* I - Device */
+{
+ AvahiClient *client = avahi_record_browser_get_client(browser);
+ /* Client information */
+# endif /* HAVE_DNSSD */
+ char *ptr; /* Pointer into string */
+ cups_device_t *device = (cups_device_t *)context;
+ /* Device */
+ const uint8_t *data, /* Pointer into data */
+ *datanext, /* Next key/value pair */
+ *dataend; /* End of entire TXT record */
+ uint8_t datalen; /* Length of current key/value pair */
+ char key[256], /* Key string */
+ value[256], /* Value string */
+ make_and_model[512], /* Manufacturer and model */
+ model[256], /* Model */
+ pdl[256], /* PDL */
+ device_id[2048]; /* 1284 device ID */
+
+# ifdef HAVE_DNSSD
fprintf(stderr, "DEBUG2: query_callback(sdRef=%p, flags=%x, "
"interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
"rrtype=%u, rrclass=%u, rdlen=%u, rdata=%p, ttl=%u, "
if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
return;
+# else
+ fprintf(stderr, "DEBUG2: query_callback(browser=%p, interfaceIndex=%d, "
+ "protocol=%d, event=%d, fullName=\"%s\", rrclass=%u, "
+ "rrtype=%u, rdata=%p, rdlen=%u, flags=%x, context=%p)\n",
+ browser, interfaceIndex, protocol, event,
+ fullName ? fullName : "(null)", rrclass, rrtype, rdata,
+ (unsigned)rdlen, flags, context);
+
/*
- * Lookup the service in the devices array.
+ * Only process "add" data...
*/
- devices = (cups_array_t *)context;
- key.name = name;
+ if (event != AVAHI_BROWSER_NEW)
+ {
+ if (event == AVAHI_BROWSER_FAILURE)
+ fprintf(stderr, "ERROR: %s\n",
+ avahi_strerror(avahi_client_errno(client)));
- unquote(name, fullName, sizeof(name));
+ return;
+ }
+# endif /* HAVE_DNSSD */
- if ((key.domain = strstr(name, "._tcp.")) != NULL)
- key.domain += 6;
- else
- key.domain = (char *)"local.";
+ /*
+ * Pull out the priority and make and model from the TXT
+ * record and save it...
+ */
- if ((ptr = strstr(name, "._")) != NULL)
- *ptr = '\0';
+ device_id[0] = '\0';
+ make_and_model[0] = '\0';
+ pdl[0] = '\0';
- if (strstr(fullName, "_ipp._tcp.") ||
- strstr(fullName, "_ipp-tls._tcp."))
- key.type = CUPS_DEVICE_IPP;
- else if (strstr(fullName, "_fax-ipp._tcp."))
- key.type = CUPS_DEVICE_FAX_IPP;
- else if (strstr(fullName, "_printer._tcp."))
- key.type = CUPS_DEVICE_PRINTER;
- else if (strstr(fullName, "_pdl-datastream._tcp."))
- key.type = CUPS_DEVICE_PDL_DATASTREAM;
- else
- key.type = CUPS_DEVICE_RIOUSBPRINT;
+ strlcpy(model, "Unknown", sizeof(model));
- for (device = cupsArrayFind(devices, &key);
- device;
- device = cupsArrayNext(devices))
+ for (data = rdata, dataend = data + rdlen;
+ data < dataend;
+ data = datanext)
{
- if (strcasecmp(device->name, key.name) ||
- strcasecmp(device->domain, key.domain))
- {
- device = NULL;
+ /*
+ * Read a key/value pair starting with an 8-bit length. Since the
+ * length is 8 bits and the size of the key/value buffers is 256, we
+ * don't need to check for overflow...
+ */
+
+ datalen = *data++;
+
+ if (!datalen || (data + datalen) > dataend)
break;
+
+ datanext = data + datalen;
+
+ for (ptr = key; data < datanext && *data != '='; data ++)
+ *ptr++ = (char)*data;
+ *ptr = '\0';
+
+ if (data < datanext && *data == '=')
+ {
+ data ++;
+
+ if (data < datanext)
+ memcpy(value, data, datanext - data);
+ value[datanext - data] = '\0';
+
+ fprintf(stderr, "DEBUG2: query_callback: \"%s=%s\".\n",
+ key, value);
}
- else if (device->type == key.type)
+ else
+ {
+ fprintf(stderr, "DEBUG2: query_callback: \"%s\" with no value.\n",
+ key);
+ continue;
+ }
+
+ if (!_cups_strncasecmp(key, "usb_", 4))
{
/*
- * Found it, pull out the priority and make and model from the TXT
- * record and save it...
+ * Add USB device ID information...
*/
- const void *value; /* Pointer to value */
- uint8_t valueLen; /* Length of value (max 255) */
- char make_and_model[512], /* Manufacturer and model */
- model[256], /* Model */
- priority[256]; /* Priority */
-
-
- value = TXTRecordGetValuePtr(rdlen, rdata, "priority", &valueLen);
+ ptr = device_id + strlen(device_id);
+ snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%s:%s;", key + 4, value);
+ }
- if (value && valueLen)
+ if (!_cups_strcasecmp(key, "usb_MFG") || !_cups_strcasecmp(key, "usb_MANU") ||
+ !_cups_strcasecmp(key, "usb_MANUFACTURER"))
+ strlcpy(make_and_model, value, sizeof(make_and_model));
+ else if (!_cups_strcasecmp(key, "usb_MDL") || !_cups_strcasecmp(key, "usb_MODEL"))
+ strlcpy(model, value, sizeof(model));
+ else if (!_cups_strcasecmp(key, "product") && !strstr(value, "Ghostscript"))
+ {
+ if (value[0] == '(')
{
- memcpy(priority, value, valueLen);
- priority[valueLen] = '\0';
- device->priority = atoi(priority);
- }
+ /*
+ * Strip parenthesis...
+ */
- if ((value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MFG",
- &valueLen)) == NULL)
- value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MANUFACTURER",
- &valueLen);
+ if ((ptr = value + strlen(value) - 1) > value && *ptr == ')')
+ *ptr = '\0';
- if (value && valueLen)
- {
- memcpy(make_and_model, value, valueLen);
- make_and_model[valueLen] = '\0';
+ strlcpy(model, value + 1, sizeof(model));
}
else
- make_and_model[0] = '\0';
+ strlcpy(model, value, sizeof(model));
+ }
+ else if (!_cups_strcasecmp(key, "ty"))
+ {
+ strlcpy(model, value, sizeof(model));
- if ((value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MDL",
- &valueLen)) == NULL)
- value = TXTRecordGetValuePtr(rdlen, rdata, "usb_MODEL", &valueLen);
+ if ((ptr = strchr(model, ',')) != NULL)
+ *ptr = '\0';
+ }
+ else if (!_cups_strcasecmp(key, "pdl"))
+ strlcpy(pdl, value, sizeof(pdl));
+ else if (!_cups_strcasecmp(key, "priority"))
+ device->priority = atoi(value);
+ else if ((device->type == CUPS_DEVICE_IPP ||
+ device->type == CUPS_DEVICE_IPPS ||
+ device->type == CUPS_DEVICE_PRINTER) &&
+ !_cups_strcasecmp(key, "printer-type"))
+ {
+ /*
+ * This is a CUPS printer!
+ */
- if (value && valueLen)
- {
- memcpy(model, value, valueLen);
- model[valueLen] = '\0';
- }
- else if ((value = TXTRecordGetValuePtr(rdlen, rdata, "product",
- &valueLen)) != NULL && valueLen > 2)
- {
- if (((char *)value)[0] == '(')
- {
- /*
- * Strip parenthesis...
- */
+ device->cups_shared = 1;
- memcpy(model, value + 1, valueLen - 2);
- model[valueLen - 2] = '\0';
- }
- else
- {
- memcpy(model, value, valueLen);
- model[valueLen] = '\0';
- }
+ if (device->type == CUPS_DEVICE_PRINTER)
+ device->sent = 1;
+ }
+ else if (!_cups_strcasecmp(key, "UUID"))
+ device->uuid = strdup(value);
+ }
- if (!strcasecmp(model, "GPL Ghostscript") ||
- !strcasecmp(model, "GNU Ghostscript") ||
- !strcasecmp(model, "ESP Ghostscript"))
- {
- if ((value = TXTRecordGetValuePtr(rdlen, rdata, "ty",
- &valueLen)) != NULL)
- {
- memcpy(model, value, valueLen);
- model[valueLen] = '\0';
+ if (device->device_id)
+ free(device->device_id);
- if ((ptr = strchr(model, ',')) != NULL)
- *ptr = '\0';
- }
- else
- strcpy(model, "Unknown");
- }
- }
- else
- strcpy(model, "Unknown");
+ if (!device_id[0] && strcmp(model, "Unknown"))
+ {
+ if (make_and_model[0])
+ snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;",
+ make_and_model, model);
+ else if (!_cups_strncasecmp(model, "designjet ", 10))
+ snprintf(device_id, sizeof(device_id), "MFG:HP;MDL:%s", model + 10);
+ else if (!_cups_strncasecmp(model, "stylus ", 7))
+ snprintf(device_id, sizeof(device_id), "MFG:EPSON;MDL:%s", model + 7);
+ else if ((ptr = strchr(model, ' ')) != NULL)
+ {
+ /*
+ * Assume the first word is the make...
+ */
- if (device->make_and_model)
- free(device->make_and_model);
+ memcpy(make_and_model, model, ptr - model);
+ make_and_model[ptr - model] = '\0';
- if (make_and_model[0])
- {
- strlcat(make_and_model, " ", sizeof(make_and_model));
- strlcat(make_and_model, model, sizeof(make_and_model));
- device->make_and_model = strdup(make_and_model);
- }
- else
- device->make_and_model = strdup(model);
+ snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s",
+ make_and_model, ptr + 1);
+ }
+ }
- if ((device->type == CUPS_DEVICE_IPP ||
- device->type == CUPS_DEVICE_PRINTER) &&
- TXTRecordGetValuePtr(rdlen, rdata, "printer-type", &valueLen))
- {
- /*
- * This is a CUPS printer!
- */
+ if (device_id[0] &&
+ !strstr(device_id, "CMD:") &&
+ !strstr(device_id, "COMMAND SET:") &&
+ (strstr(pdl, "application/pdf") ||
+ strstr(pdl, "application/postscript") ||
+ strstr(pdl, "application/vnd.hp-PCL") ||
+ strstr(pdl, "image/")))
+ {
+ value[0] = '\0';
+ if (strstr(pdl, "application/pdf"))
+ strlcat(value, ",PDF", sizeof(value));
+ if (strstr(pdl, "application/postscript"))
+ strlcat(value, ",PS", sizeof(value));
+ if (strstr(pdl, "application/vnd.hp-PCL"))
+ strlcat(value, ",PCL", sizeof(value));
+ for (ptr = strstr(pdl, "image/"); ptr; ptr = strstr(ptr, "image/"))
+ {
+ char *valptr = value + strlen(value);
+ /* Pointer into value */
- device->cups_shared = 1;
+ if (valptr < (value + sizeof(value) - 1))
+ *valptr++ = ',';
- if (device->type == CUPS_DEVICE_PRINTER)
- device->sent = 1;
+ ptr += 6;
+ while (isalnum(*ptr & 255) || *ptr == '-' || *ptr == '.')
+ {
+ if (isalnum(*ptr & 255) && valptr < (value + sizeof(value) - 1))
+ *valptr++ = (char)toupper(*ptr++ & 255);
+ else
+ break;
}
- break;
+ *valptr = '\0';
}
+
+ ptr = device_id + strlen(device_id);
+ snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "CMD:%s;", value + 1);
}
- if (!device)
- fprintf(stderr, "DEBUG: Ignoring TXT record for \"%s\"...\n", fullName);
+ if (device_id[0])
+ device->device_id = strdup(device_id);
+ else
+ device->device_id = NULL;
+
+ if (device->make_and_model)
+ free(device->make_and_model);
+
+ if (make_and_model[0])
+ {
+ strlcat(make_and_model, " ", sizeof(make_and_model));
+ strlcat(make_and_model, model, sizeof(make_and_model));
+
+ device->make_and_model = strdup(make_and_model);
+ }
+ else
+ device->make_and_model = strdup(model);
+}
+#endif /* HAVE_DNSSD || HAVE_AVAHI */
+
+
+/*
+ * 'sigterm_handler()' - Handle termination signals.
+ */
+
+static void
+sigterm_handler(int sig) /* I - Signal number (unused) */
+{
+ (void)sig;
+
+ if (job_canceled)
+ exit(CUPS_BACKEND_OK);
+ else
+ job_canceled = 1;
}