dnssd.c   [plain text]


/*
 * "$Id: dnssd.c 3301 2011-06-05 17:05:19Z msweet $"
 *
 *   DNS-SD discovery backend for CUPS.
 *
 *   Copyright 2008-2011 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/".
 *
 *   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.
 *   sigterm_handler()       - Handle termination signals...
 *   unquote()               - Unquote a name string.
 */

/*
 * Include necessary headers.
 */

#include "backend-private.h"
#include <cups/array.h>
#include <dns_sd.h>


/*
 * Device structure...
 */

typedef enum
{
  CUPS_DEVICE_PRINTER = 0,		/* lpd://... */
  CUPS_DEVICE_IPP,			/* ipp://... */
  CUPS_DEVICE_IPPS,			/* ipps://... */
  CUPS_DEVICE_FAX_IPP,			/* ipp://... */
  CUPS_DEVICE_PDL_DATASTREAM,		/* socket://... */
  CUPS_DEVICE_RIOUSBPRINT		/* riousbprint://... */
} cups_devtype_t;


typedef struct
{
  DNSServiceRef	ref;			/* Service reference for resolve */
  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 */
  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 */


/*
 * Local functions...
 */

static void		browse_callback(DNSServiceRef sdRef,
			                DNSServiceFlags flags,
				        uint32_t interfaceIndex,
				        DNSServiceErrorType errorCode,
				        const char *serviceName,
				        const char *regtype,
				        const char *replyDomain, void *context);
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);
static int		compare_devices(cups_device_t *a, cups_device_t *b);
static void		exec_backend(char **argv);
static cups_device_t	*get_device(cups_array_t *devices,
			            const char *serviceName,
			            const char *regtype,
				    const char *replyDomain);
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);
static void		sigterm_handler(int sig);
static void		unquote(char *dst, const char *src, size_t dstsize);


/*
 * '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 */
  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 */
#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...
  */

  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(&ipp_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_ipp_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);

 /*
  * Loop until we are killed...
  */

  while (!job_canceled)
  {
    FD_ZERO(&input);
    FD_SET(fd, &input);

    timeout.tv_sec  = 0;
    timeout.tv_usec = 250000;

    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 any devices we've found...
      */

      DNSServiceErrorType status;	/* DNS query status */
      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 < 20)
	  {
	    device->ref = main_ref;

	    fprintf(stderr, "DEBUG: Querying \"%s\"...\n", device->fullName);

	    status = DNSServiceQueryRecord(&(device->ref),
				           kDNSServiceFlagsShareConnection,
				           0, device->fullName,
					   kDNSServiceType_TXT,
				           kDNSServiceClass_IN, query_callback,
				           devices);
            if (status != kDNSServiceErr_NoError)
	    {
	      fputs("ERROR: Unable to query for TXT records!\n", stderr);
	      fprintf(stderr, "DEBUG: DNSServiceQueryRecord returned %d\n",
	              status);
            }
	    else
	      count ++;
          }
	}
	else if (!device->sent)
	{
	 /*
	  * Got the TXT records, now report the device...
	  */

	  DNSServiceRefDeallocate(device->ref);
	  device->ref = 0;

          if (!best)
	    best = device;
	  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),
			    "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));

	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 ++;
      }

      if (sent == cupsArrayCount(devices))
	break;
    }
  }

  return (CUPS_BACKEND_OK);
}


/*
 * '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 ? serviceName : "(null)",
	  regtype ? regtype : "(null)",
	  replyDomain ? replyDomain : "(null)",
	  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 ? serviceName : "(null)",
	  regtype ? regtype : "(null)",
	  replyDomain ? replyDomain : "(null)",
	  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;
}


/*
 * '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);
}


/*
 * '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;

  if (!strcmp(regtype, "_ipp._tcp."))
    key.type = CUPS_DEVICE_IPP;
  else if (!strcmp(regtype, "_ipps._tcp.") ||
	   !strcmp(regtype, "_ipp-tls._tcp."))
    key.type = CUPS_DEVICE_IPPS;
  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;

  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);

	DNSServiceConstructFullName(fullName, device->name, regtype,
	                            replyDomain);
	free(device->fullName);
	device->fullName = strdup(fullName);
      }

      return (device);
    }

 /*
  * Yes, add the device...
  */

  fprintf(stderr, "DEBUG: Found \"%s.%s%s\"...\n", serviceName, regtype,
	  replyDomain);

  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...
  */

  DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain);
  device->fullName = strdup(fullName);

  return (device);
}


/*
 * '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 - Devices array */
{
  cups_array_t	*devices;		/* Device array */
  char		name[1024],		/* Service name */
		*ptr;			/* Pointer into string */
  cups_device_t	dkey,			/* Search key */
		*device;		/* Device */


  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 ? fullName : "(null)", rrtype, rrclass, rdlen, rdata, ttl,
	  context);

 /*
  * Only process "add" data...
  */

  if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
    return;

 /*
  * Lookup the service in the devices array.
  */

  devices   = (cups_array_t *)context;
  dkey.name = name;

  unquote(name, fullName, sizeof(name));

  if ((dkey.domain = strstr(name, "._tcp.")) != NULL)
    dkey.domain += 6;
  else
    dkey.domain = (char *)"local.";

  if ((ptr = strstr(name, "._")) != NULL)
    *ptr = '\0';

  if (strstr(fullName, "_ipp._tcp."))
    dkey.type = CUPS_DEVICE_IPP;
  else if (strstr(fullName, "_ipps._tcp.") ||
           strstr(fullName, "_ipp-tls._tcp."))
    dkey.type = CUPS_DEVICE_IPPS;
  else if (strstr(fullName, "_fax-ipp._tcp."))
    dkey.type = CUPS_DEVICE_FAX_IPP;
  else if (strstr(fullName, "_printer._tcp."))
    dkey.type = CUPS_DEVICE_PRINTER;
  else if (strstr(fullName, "_pdl-datastream._tcp."))
    dkey.type = CUPS_DEVICE_PDL_DATASTREAM;
  else
    dkey.type = CUPS_DEVICE_RIOUSBPRINT;

  for (device = cupsArrayFind(devices, &dkey);
       device;
       device = cupsArrayNext(devices))
  {
    if (_cups_strcasecmp(device->name, dkey.name) ||
        _cups_strcasecmp(device->domain, dkey.domain))
    {
      device = NULL;
      break;
    }
    else if (device->type == dkey.type)
    {
     /*
      * Found it, pull out the priority and make and model from the TXT
      * record and save it...
      */

      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 */
			device_id[2048];/* 1284 device ID */


      device_id[0]      = '\0';
      make_and_model[0] = '\0';

      strcpy(model, "Unknown");

      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++ = *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
	{
	  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) - (ptr - device_id), "%s:%s;",
	           key + 4, value);
        }

        if (!_cups_strcasecmp(key, "usb_MFG") || !_cups_strcasecmp(key, "usb_MANU") ||
	    !_cups_strcasecmp(key, "usb_MANUFACTURER"))
	  strcpy(make_and_model, value);
        else if (!_cups_strcasecmp(key, "usb_MDL") || !_cups_strcasecmp(key, "usb_MODEL"))
	  strcpy(model, value);
	else if (!_cups_strcasecmp(key, "product") && !strstr(value, "Ghostscript"))
	{
	  if (value[0] == '(')
	  {
	   /*
	    * Strip parenthesis...
	    */

            if ((ptr = value + strlen(value) - 1) > value && *ptr == ')')
	      *ptr = '\0';

	    strcpy(model, value + 1);
	  }
	  else
	    strcpy(model, value);
        }
	else if (!_cups_strcasecmp(key, "ty"))
	{
          strcpy(model, value);

	  if ((ptr = strchr(model, ',')) != NULL)
	    *ptr = '\0';
	}
	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;
	}
      }

      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, 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])
        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);
      break;
    }
  }

  if (!device)
    fprintf(stderr, "DEBUG: Ignoring TXT record for \"%s\"...\n", fullName);
}


/*
 * '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';
}


/*
 * End of "$Id: dnssd.c 3301 2011-06-05 17:05:19Z msweet $".
 */