You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1290 lines
35 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
* DNS-SD discovery backend for CUPS.
*
* Copyright © 2008-2018 by Apple Inc.
*
* Licensed under Apache License v2.0. See the file "LICENSE" for more
* information.
*/
/*
* Include necessary headers.
*/
#include "backend-private.h"
#include <cups/array.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 */
/*
* Device structure...
*/
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://... */
CUPS_DEVICE_RIOUSBPRINT /* riousbprint://... */
} cups_devtype_t;
typedef struct
{
#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 */
*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? */
sent; /* Did we list the device? */
} 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? */
static int browsers = 0; /* Number of running browsers */
#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) _CUPS_NONNULL(1,5,6,7,8);
static void browse_local_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) _CUPS_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) _CUPS_NORETURN;
static cups_device_t *get_device(cups_array_t *devices, const char *serviceName, const char *regtype, const char *replyDomain) _CUPS_NONNULL(1,2,3,4);
#ifdef HAVE_DNSSD
static void query_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullName, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context) _CUPS_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);
#endif /* HAVE_DNSSD */
static void sigterm_handler(int sig);
static void unquote(char *dst, const char *src, size_t dstsize) _CUPS_NONNULL(1,2);
/*
* 'main()' - Browse for printers.
*/
int /* O - Exit status */
main(int argc, /* I - Number of command-line args */
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 */
#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 */
/*
* 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)
{
_cupsLangPrintf(stderr,
_("Usage: %s job-id user title copies options [file]"),
argv[0]);
return (1);
}
/*
* Only do discovery when run as "dnssd"...
*/
if ((name = strrchr(argv[0], '/')) != NULL)
name ++;
else
name = argv[0];
if (strcmp(name, "dnssd"))
return (0);
/*
* Create an array to track devices...
*/
devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL);
/*
* Browse for different kinds of printers...
*/
#ifdef HAVE_DNSSD
if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError)
{
perror("ERROR: Unable to create service connection");
return (1);
}
fd = DNSServiceRefSockFD(main_ref);
fax_ipp_ref = main_ref;
DNSServiceBrowse(&fax_ipp_ref, kDNSServiceFlagsShareConnection, 0,
"_fax-ipp._tcp", NULL, browse_callback, devices);
ipp_ref = main_ref;
DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0,
"_ipp._tcp", NULL, browse_callback, devices);
ipp_tls_ref = main_ref;
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,
"_fax-ipp._tcp", NULL, browse_local_callback, devices);
local_ipp_ref = main_ref;
DNSServiceBrowse(&local_ipp_ref, kDNSServiceFlagsShareConnection,
kDNSServiceInterfaceIndexLocalOnly,
"_ipp._tcp", NULL, browse_local_callback, devices);
local_ipp_tls_ref = main_ref;
DNSServiceBrowse(&local_ipp_tls_ref, kDNSServiceFlagsShareConnection,
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;
DNSServiceBrowse(&pdl_datastream_ref, kDNSServiceFlagsShareConnection, 0,
"_pdl-datastream._tcp", NULL, browse_callback, devices);
printer_ref = main_ref;
DNSServiceBrowse(&printer_ref, kDNSServiceFlagsShareConnection, 0,
"_printer._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 (0);
}
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 (0);
}
browsers = 6;
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...
*/
while (!job_canceled)
{
int announce = 0; /* Announce printers? */
#ifdef HAVE_DNSSD
FD_ZERO(&input);
FD_SET(fd, &input);
timeout.tv_sec = 0;
timeout.tv_usec = 500000;
if (select(fd + 1, &input, NULL, NULL, &timeout) < 0)
continue;
if (FD_ISSET(fd, &input))
{
/*
* Process results of our browsing...
*/
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 */
int sent; /* Number of sent */
for (device = (cups_device_t *)cupsArrayFirst(devices),
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 < 50)
{
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,
device);
if (status != kDNSServiceErr_NoError)
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);
#else
avahi_record_browser_free(device->ref);
#endif /* HAVE_DNSSD */
device->ref = NULL;
if (!best)
best = device;
else if (_cups_strcasecmp(best->name, device->name) ||
_cups_strcasecmp(best->domain, device->domain))
{
unquote(uriName, best->fullName, sizeof(uriName));
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, best->device_id, NULL);
best->sent = 1;
best = device;
sent ++;
}
else if (best->priority > device->priority ||
(best->priority == device->priority &&
best->type < device->type))
{
best->sent = 1;
best = device;
sent ++;
}
else
{
device->sent = 1;
sent ++;
}
}
}
if (best)
{
unquote(uriName, best->fullName, sizeof(uriName));
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, best->device_id, NULL);
best->sent = 1;
sent ++;
}
fprintf(stderr, "DEBUG: sent=%d, count=%d\n", sent, count);
#ifdef HAVE_AVAHI
if (sent == cupsArrayCount(devices) && browsers == 0)
#else
if (sent == cupsArrayCount(devices))
#endif /* HAVE_AVAHI */
break;
}
}
return (CUPS_BACKEND_OK);
}
#ifdef HAVE_DNSSD
/*
* 'browse_callback()' - Browse devices.
*/
static void
browse_callback(
DNSServiceRef sdRef, /* I - Service reference */
DNSServiceFlags flags, /* I - Option flags */
uint32_t interfaceIndex, /* I - Interface number */
DNSServiceErrorType errorCode, /* I - Error, if any */
const char *serviceName, /* I - Name of service/device */
const char *regtype, /* I - Type of service */
const char *replyDomain, /* I - Service domain */
void *context) /* I - Devices array */
{
fprintf(stderr, "DEBUG2: browse_callback(sdRef=%p, flags=%x, "
"interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
"regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
sdRef, flags, interfaceIndex, errorCode,
serviceName, regtype, replyDomain, context);
/*
* Only process "add" data...
*/
if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
return;
/*
* Get the device...
*/
get_device((cups_array_t *)context, serviceName, regtype, replyDomain);
}
/*
* 'browse_local_callback()' - Browse local devices.
*/
static void
browse_local_callback(
DNSServiceRef sdRef, /* I - Service reference */
DNSServiceFlags flags, /* I - Option flags */
uint32_t interfaceIndex, /* I - Interface number */
DNSServiceErrorType errorCode, /* I - Error, if any */
const char *serviceName, /* I - Name of service/device */
const char *regtype, /* I - Type of service */
const char *replyDomain, /* I - Service domain */
void *context) /* I - Devices array */
{
cups_device_t *device; /* Device */
fprintf(stderr, "DEBUG2: browse_local_callback(sdRef=%p, flags=%x, "
"interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
"regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
sdRef, flags, interfaceIndex, errorCode,
serviceName, regtype, replyDomain, context);
/*
* Only process "add" data...
*/
if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
return;
/*
* Get the device...
*/
device = get_device((cups_array_t *)context, serviceName, regtype,
replyDomain);
/*
* Hide locally-registered devices...
*/
fprintf(stderr, "DEBUG: Hiding local printer \"%s\"...\n",
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_CACHE_EXHAUSTED:
break;
case AVAHI_BROWSER_ALL_FOR_NOW:
browsers--;
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()' - Compare two devices.
*/
static int /* O - Result of comparison */
compare_devices(cups_device_t *a, /* I - First device */
cups_device_t *b) /* I - Second device */
{
return (strcmp(a->name, b->name));
}
/*
* 'exec_backend()' - Execute the backend that corresponds to the
* resolved service name.
*/
static void
exec_backend(char **argv) /* I - Command-line arguments */
{
const char *resolved_uri, /* Resolved device URI */
*cups_serverbin; /* Location of programs */
char scheme[1024], /* Scheme from URI */
*ptr, /* Pointer into scheme */
filename[1024]; /* Backend filename */
/*
* Resolve the device URI...
*/
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...
*/
strlcpy(scheme, resolved_uri, sizeof(scheme));
if ((ptr = strchr(scheme, ':')) != NULL)
*ptr = '\0';
/*
* Get the filename of the backend...
*/
if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
cups_serverbin = CUPS_SERVERBIN;
snprintf(filename, sizeof(filename), "%s/backend/%s", cups_serverbin, scheme);
/*
* Overwrite the device URI and run the new backend...
*/
setenv("DEVICE_URI", resolved_uri, 1);
argv[0] = (char *)resolved_uri;
fprintf(stderr, "DEBUG: Executing backend \"%s\"...\n", filename);
execv(filename, argv);
fprintf(stderr, "ERROR: Unable to execute backend \"%s\": %s\n", filename,
strerror(errno));
exit(CUPS_BACKEND_STOP);
}
/*
* '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.
*/
static cups_device_t * /* O - Device */
get_device(cups_array_t *devices, /* I - Device array */
const char *serviceName, /* I - Name of service/device */
const char *regtype, /* I - Type of service */
const char *replyDomain) /* I - Service domain */
{
cups_device_t key, /* Search key */
*device; /* Device */
char fullName[kDNSServiceMaxDomainName];
/* Full name for query */
/*
* See if this is a new device...
*/
key.name = (char *)serviceName;
key.type = device_type(regtype);
for (device = cupsArrayFind(devices, &key);
device;
device = cupsArrayNext(devices))
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...
*/
device = calloc(sizeof(cups_device_t), 1);
device->name = strdup(serviceName);
device->domain = strdup(replyDomain);
device->type = key.type;
device->priority = 50;
cupsArrayAdd(devices, 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.
* (https://github.com/lathiat/avahi/issues/127)
*/
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.
*/
static void
query_callback(
DNSServiceRef sdRef, /* I - Service reference */
DNSServiceFlags flags, /* I - Data flags */
uint32_t interfaceIndex, /* I - Interface */
DNSServiceErrorType errorCode, /* I - Error, if any */
const char *fullName, /* I - Full service name */
uint16_t rrtype, /* I - Record type */
uint16_t rrclass, /* I - Record class */
uint16_t rdlen, /* I - Length of record data */
const void *rdata, /* I - Record data */
uint32_t ttl, /* I - Time-to-live */
void *context) /* I - 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, "
"context=%p)\n",
sdRef, flags, interfaceIndex, errorCode, fullName, rrtype, rrclass, rdlen, rdata, ttl, context);
/*
* Only process "add" data...
*/
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, rrclass, rrtype, rdata, (unsigned)rdlen, flags, context);
/*
* Only process "add" data...
*/
if (event != AVAHI_BROWSER_NEW)
{
if (event == AVAHI_BROWSER_FAILURE)
fprintf(stderr, "ERROR: %s\n",
avahi_strerror(avahi_client_errno(client)));
return;
}
# endif /* HAVE_DNSSD */
/*
* Pull out the priority and make and model from the TXT
* record and save it...
*/
device_id[0] = '\0';
make_and_model[0] = '\0';
pdl[0] = '\0';
strlcpy(model, "Unknown", sizeof(model));
for (data = rdata, dataend = data + rdlen;
data < dataend;
data = datanext)
{
/*
* 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, (size_t)(datanext - data));
value[datanext - data] = '\0';
fprintf(stderr, "DEBUG2: query_callback: \"%s=%s\".\n",
key, value);
}
else
{
fprintf(stderr, "DEBUG2: query_callback: \"%s\" with no value.\n",
key);
continue;
}
if (!_cups_strncasecmp(key, "usb_", 4))
{
/*
* Add USB device ID information...
*/
ptr = device_id + strlen(device_id);
snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%s:%s;", key + 4, value);
}
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] == '(')
{
/*
* Strip parenthesis...
*/
if ((ptr = value + strlen(value) - 1) > value && *ptr == ')')
*ptr = '\0';
strlcpy(model, value + 1, sizeof(model));
}
else
strlcpy(model, value, sizeof(model));
}
else if (!_cups_strcasecmp(key, "ty"))
{
strlcpy(model, value, sizeof(model));
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!
*/
device->cups_shared = 1;
if (device->type == CUPS_DEVICE_PRINTER)
device->sent = 1;
}
else if (!_cups_strcasecmp(key, "UUID"))
device->uuid = strdup(value);
}
if (device->device_id)
free(device->device_id);
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...
*/
memcpy(make_and_model, model, (size_t)(ptr - model));
make_and_model[ptr - model] = '\0';
snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;",
make_and_model, ptr + 1);
}
}
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 */
if (valptr < (value + sizeof(value) - 1))
*valptr++ = ',';
ptr += 6;
while (isalnum(*ptr & 255) || *ptr == '-' || *ptr == '.')
{
if (isalnum(*ptr & 255) && valptr < (value + sizeof(value) - 1))
*valptr++ = (char)toupper(*ptr++ & 255);
else
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_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));
if (!_cups_strncasecmp(make_and_model, "EPSON EPSON ", 12))
_cups_strcpy(make_and_model, make_and_model + 6);
else if (!_cups_strncasecmp(make_and_model, "HP HP ", 6))
_cups_strcpy(make_and_model, make_and_model + 3);
else if (!_cups_strncasecmp(make_and_model, "Lexmark International Lexmark ", 30))
_cups_strcpy(make_and_model, make_and_model + 22);
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;
}
/*
* 'unquote()' - Unquote a name string.
*/
static void
unquote(char *dst, /* I - Destination buffer */
const char *src, /* I - Source string */
size_t dstsize) /* I - Size of destination buffer */
{
char *dstend = dst + dstsize - 1; /* End of destination buffer */
while (*src && dst < dstend)
{
if (*src == '\\')
{
src ++;
if (isdigit(src[0] & 255) && isdigit(src[1] & 255) &&
isdigit(src[2] & 255))
{
*dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0';
src += 3;
}
else
*dst++ = *src++;
}
else
*dst++ = *src ++;
}
*dst = '\0';
}