/* * DNS-SD discovery backend for CUPS. * * Copyright © 2021-2022 by OpenPrinting. * 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 #ifdef HAVE_MDNSRESPONDER # include #endif /* HAVE_MDNSRESPONDER */ #ifdef HAVE_AVAHI # include # include # include # include # include # include #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_MDNSRESPONDER DNSServiceRef ref; /* Service reference for query */ #endif /* HAVE_MDNSRESPONDER */ #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_MDNSRESPONDER 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_MDNSRESPONDER */ #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_MDNSRESPONDER 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_MDNSRESPONDER */ 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_MDNSRESPONDER 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_MDNSRESPONDER */ #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_MDNSRESPONDER 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_MDNSRESPONDER */ #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_MDNSRESPONDER 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_MDNSRESPONDER */ /* fprintf(stderr, "DEBUG: announce=%d\n", announce);*/ if (announce) { /* * Announce any devices we've found... */ #ifdef HAVE_MDNSRESPONDER DNSServiceErrorType status; /* DNS query status */ #endif /* HAVE_MDNSRESPONDER */ 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_MDNSRESPONDER 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_MDNSRESPONDER /* * Got the TXT records, now report the device... */ DNSServiceRefDeallocate(device->ref); #else avahi_record_browser_free(device->ref); #endif /* HAVE_MDNSRESPONDER */ 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_MDNSRESPONDER /* * '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_MDNSRESPONDER */ #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_MDNSRESPONDER DNSServiceConstructFullName(fullName, device->name, regtype, replyDomain); #else /* HAVE_AVAHI */ avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName, regtype, replyDomain); #endif /* HAVE_MDNSRESPONDER */ free(device->fullName); device->fullName = strdup(fullName); } return (device); } /* * Yes, add the device... */ if ((device = calloc(sizeof(cups_device_t), 1)) == NULL) { perror("DEBUG: Out of memory adding a device"); return (NULL); } 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_MDNSRESPONDER DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain); #else /* HAVE_AVAHI */ avahi_service_name_join(fullName, kDNSServiceMaxDomainName, serviceName, regtype, replyDomain); #endif /* HAVE_MDNSRESPONDER */ 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 */ #ifdef HAVE_DNSSD # ifdef HAVE_MDNSRESPONDER /* * '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_MDNSRESPONDER */ 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_MDNSRESPONDER 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_MDNSRESPONDER */ /* * 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 */ /* * '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'; }