]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - backend/dnssd.c
Remove all of the Subversion keywords from various source files.
[thirdparty/cups.git] / backend / dnssd.c
index be08ddd7a1feb80cc1fb662a6a69417adcccda6c..20beaae29297b1d4fe547d9991b1101df7e306a9 100644 (file)
@@ -1,30 +1,15 @@
 /*
- * "$Id$"
+ * DNS-SD discovery backend for CUPS.
  *
- *   DNS-SD discovery backend for the Common UNIX Printing System (CUPS).
+ * Copyright 2008-2015 by Apple Inc.
  *
- *   Copyright 2008-2009 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.
- *   sigterm_handler()       - Handle termination signals...
- *   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 */
 
 
 /*
@@ -43,6 +39,7 @@
 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://... */
@@ -52,12 +49,18 @@ typedef enum
 
 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 */
-               *device_id;             /* 1284 device ID 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? */
@@ -71,19 +74,27 @@ typedef struct
 
 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);
+                                       const char *replyDomain, void *context)
+                                       __attribute__((nonnull(1,5,6,7,8)));
 static void            browse_local_callback(DNSServiceRef sdRef,
                                              DNSServiceFlags flags,
                                              uint32_t interfaceIndex,
@@ -91,13 +102,32 @@ static void                browse_local_callback(DNSServiceRef sdRef,
                                              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,
@@ -105,9 +135,25 @@ static void                query_callback(DNSServiceRef sdRef,
                                       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);
+#endif /* HAVE_DNSSD */
 static void            sigterm_handler(int sig);
-static void            unquote(char *dst, const char *src, size_t dstsize);
+static void            unquote(char *dst, const char *src, size_t dstsize)
+                           __attribute__((nonnull(1,2)));
 
 
 /*
@@ -119,23 +165,31 @@ 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 */
-  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 */
@@ -153,8 +207,8 @@ main(int  argc,                             /* I - Number of command-line args */
   memset(&action, 0, sizeof(action));
 
   sigemptyset(&action.sa_mask);
-  action.sa_handler = SIG_IGN;
-  sigaction(SIGTERM, &action, sigterm_handler);
+  action.sa_handler = sigterm_handler;
+  sigaction(SIGTERM, &action, NULL);
 #else
   signal(SIGTERM, sigterm_handler);
 #endif /* HAVE_SIGSET */
@@ -167,8 +221,9 @@ main(int  argc,                             /* I - Number of command-line args */
     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);
   }
 
@@ -194,6 +249,7 @@ main(int  argc,                             /* I - Number of command-line args */
   * Browse for different kinds of printers...
   */
 
+#ifdef HAVE_DNSSD
   if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError)
   {
     perror("ERROR: Unable to create service connection");
@@ -214,6 +270,10 @@ main(int  argc,                            /* I - Number of command-line args */
   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,
@@ -229,12 +289,17 @@ main(int  argc,                           /* I - Number of command-line args */
                    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);
 
@@ -245,6 +310,53 @@ main(int  argc,                            /* I - Number of command-line args */
   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...
@@ -252,11 +364,14 @@ main(int  argc,                           /* I - Number of command-line args */
 
   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;
@@ -270,73 +385,135 @@ main(int  argc,                          /* I - Number of command-line args */
       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;
+               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),
-                           "dnssd", 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, best->device_id, NULL);
            best->sent = 1;
            best       = device;
+
+           sent ++;
          }
          else if (best->priority > device->priority ||
                   (best->priority == device->priority &&
@@ -344,23 +521,46 @@ main(int  argc,                           /* I - Number of command-line args */
           {
            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" : "/");
+       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;
     }
   }
 
@@ -368,6 +568,7 @@ main(int  argc,                             /* I - Number of command-line args */
 }
 
 
+#ifdef HAVE_DNSSD
 /*
  * 'browse_callback()' - Browse devices.
  */
@@ -387,10 +588,7 @@ browse_callback(
                   "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...
@@ -429,10 +627,7 @@ browse_local_callback(
                   "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...
@@ -456,6 +651,100 @@ browse_local_callback(
          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 */
 
 
 /*
@@ -489,11 +778,14 @@ exec_backend(char **argv)         /* I - Command-line arguments */
   * Resolve the device URI...
   */
 
-  if ((resolved_uri = cupsBackendDeviceURI(argv)) == NULL)
+  job_canceled = -1;
+
+  while ((resolved_uri = cupsBackendDeviceURI(argv)) == NULL)
   {
-    if (job_canceled)
-      exit(CUPS_BACKEND_OK);
-    else
+    _cupsLangPrintFilter(stderr, "INFO", _("Unable to locate printer."));
+    sleep(10);
+
+    if (getenv("CLASS") != NULL)
       exit(CUPS_BACKEND_FAILED);
   }
 
@@ -532,6 +824,41 @@ exec_backend(char **argv)          /* I - Command-line arguments */
 }
 
 
+/*
+ * '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.
  */
@@ -553,28 +880,17 @@ get_device(cups_array_t *devices, /* I - Device array */
   */
 
   key.name = (char *)serviceName;
-
-  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.type = device_type(regtype);
 
   for (device = cupsArrayFind(devices, &key);
        device;
        device = cupsArrayNext(devices))
-    if (strcasecmp(device->name, key.name))
+    if (_cups_strcasecmp(device->name, key.name))
       break;
     else if (device->type == key.type)
     {
-      if (!strcasecmp(device->domain, "local.") &&
-          strcasecmp(device->domain, replyDomain))
+      if (!_cups_strcasecmp(device->domain, "local.") &&
+          _cups_strcasecmp(device->domain, replyDomain))
       {
        /*
         * Update the .local listing to use the "global" domain name instead.
@@ -584,8 +900,14 @@ get_device(cups_array_t *devices,  /* I - Device array */
         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);
       }
@@ -597,9 +919,6 @@ get_device(cups_array_t *devices,   /* I - Device array */
   * 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);
@@ -612,13 +931,54 @@ get_device(cups_array_t *devices, /* I - Device array */
   * 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.
  */
@@ -635,22 +995,52 @@ query_callback(
     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 string */
-  cups_device_t        dkey,                   /* 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, "
                  "context=%p)\n",
-          sdRef, flags, interfaceIndex, errorCode,
-         fullName ? fullName : "(null)", rrtype, rrclass, rdlen, rdata, ttl,
-         context);
+          sdRef, flags, interfaceIndex, errorCode, fullName, rrtype, rrclass, rdlen, rdata, ttl, context);
 
  /*
   * Only process "add" data...
@@ -659,217 +1049,235 @@ query_callback(
   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 */
+
  /*
-  * Lookup the service in the devices array.
+  * Pull out the priority and make and model from the TXT
+  * record and save it...
   */
 
-  devices   = (cups_array_t *)context;
-  dkey.name = name;
+  device_id[0]      = '\0';
+  make_and_model[0] = '\0';
+  pdl[0]            = '\0';
 
-  unquote(name, fullName, sizeof(name));
+  strlcpy(model, "Unknown", sizeof(model));
 
-  if ((dkey.domain = strstr(name, "._tcp.")) != NULL)
-    dkey.domain += 6;
-  else
-    dkey.domain = (char *)"local.";
+  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;
 
-  if ((ptr = strstr(name, "._")) != NULL)
+    for (ptr = key; data < datanext && *data != '='; data ++)
+      *ptr++ = (char)*data;
     *ptr = '\0';
 
-  if (strstr(fullName, "_ipp._tcp.") ||
-      strstr(fullName, "_ipp-tls._tcp."))
-    dkey.type = CUPS_DEVICE_IPP;
-  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;
+    if (data < datanext && *data == '=')
+    {
+      data ++;
 
-  for (device = cupsArrayFind(devices, &dkey);
-       device;
-       device = cupsArrayNext(devices))
-  {
-    if (strcasecmp(device->name, dkey.name) ||
-        strcasecmp(device->domain, dkey.domain))
+      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
     {
-      device = NULL;
-      break;
+      fprintf(stderr, "DEBUG2: query_callback: \"%s\" with no value.\n",
+             key);
+      continue;
     }
-    else if (device->type == dkey.type)
+
+    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 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");
+      ptr = device_id + strlen(device_id);
+      snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%s:%s;", key + 4, value);
+    }
 
-      for (data = rdata, dataend = data + rdlen;
-           data < dataend;
-           data = datanext)
+    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] == '(')
       {
        /*
-        * 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...
+       * Strip parenthesis...
        */
 
-        datalen = *data++;
+       if ((ptr = value + strlen(value) - 1) > value && *ptr == ')')
+         *ptr = '\0';
 
-        if (!datalen || (data + datalen) >= dataend)
-         break;
-
-        datanext = data + datalen;
+       strlcpy(model, value + 1, sizeof(model));
+      }
+      else
+       strlcpy(model, value, sizeof(model));
+    }
+    else if (!_cups_strcasecmp(key, "ty"))
+    {
+      strlcpy(model, value, sizeof(model));
 
-        for (ptr = key; data < datanext && *data != '='; data ++)
-         *ptr++ = *data;
+      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 (data < datanext && *data == '=')
-       {
-         data ++;
-
-         if (data < datanext)
-           memcpy(value, data, datanext - data);
-         value[datanext - data] = '\0';
-       }
-       else
-         continue;
-
-        if (!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);
-        }
+      device->cups_shared = 1;
 
-        if (!strcasecmp(key, "usb_MFG") || !strcasecmp(key, "usb_MANU") ||
-           !strcasecmp(key, "usb_MANUFACTURER"))
-         strcpy(make_and_model, value);
-        else if (!strcasecmp(key, "usb_MDL") || !strcasecmp(key, "usb_MODEL"))
-         strcpy(model, value);
-       else if (!strcasecmp(key, "product") && !strstr(value, "Ghostscript"))
-       {
-         if (value[0] == '(')
-         {
-          /*
-           * Strip parenthesis...
-           */
+      if (device->type == CUPS_DEVICE_PRINTER)
+       device->sent = 1;
+    }
+    else if (!_cups_strcasecmp(key, "UUID"))
+      device->uuid = strdup(value);
+  }
 
-            if ((ptr = value + strlen(value) - 1) > value && *ptr == ')')
-             *ptr = '\0';
+  if (device->device_id)
+    free(device->device_id);
 
-           strcpy(model, value + 1);
-         }
-         else
-           strcpy(model, value);
-        }
-       else if (!strcasecmp(key, "ty"))
-       {
-          strcpy(model, value);
+  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 ((ptr = strchr(model, ',')) != NULL)
-           *ptr = '\0';
-       }
-       else if (!strcasecmp(key, "priority"))
-         device->priority = atoi(value);
-       else if ((device->type == CUPS_DEVICE_IPP ||
-                 device->type == CUPS_DEVICE_PRINTER) &&
-                !strcasecmp(key, "printer-type"))
-       {
-        /*
-         * This is a CUPS printer!
-         */
+      memcpy(make_and_model, model, (size_t)(ptr - model));
+      make_and_model[ptr - model] = '\0';
 
-         device->cups_shared = 1;
+      snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s",
+              make_and_model, ptr + 1);
+    }
+  }
 
-         if (device->type == CUPS_DEVICE_PRINTER)
-           device->sent = 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 (device->device_id)
-        free(device->device_id);
+      if (valptr < (value + sizeof(value) - 1))
+        *valptr++ = ',';
 
-      if (!device_id[0] && strcmp(model, "Unknown"))
+      ptr += 6;
+      while (isalnum(*ptr & 255) || *ptr == '-' || *ptr == '.')
       {
-        if (make_and_model[0])
-         snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;",
-                  make_and_model, model);
-        else if (!strncasecmp(model, "designjet ", 10))
-         snprintf(device_id, sizeof(device_id), "MFG:HP;MDL:%s", model + 10);
-        else if (!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 (isalnum(*ptr & 255) && valptr < (value + sizeof(value) - 1))
+          *valptr++ = (char)toupper(*ptr++ & 255);
+        else
+          break;
+      }
 
-          memcpy(make_and_model, model, ptr - model);
-         make_and_model[ptr - model] = '\0';
+      *valptr = '\0';
+    }
 
-         snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s",
-                  make_and_model, ptr + 1);
-        }
-      }
+    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_id[0])
+    device->device_id = strdup(device_id);
+  else
+    device->device_id = NULL;
 
-      if (device->make_and_model)
-       free(device->make_and_model);
+  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 (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;
-    }
+    device->make_and_model = strdup(make_and_model);
   }
-
-  if (!device)
-    fprintf(stderr, "DEBUG: Ignoring TXT record for \"%s\"...\n", fullName);
+  else
+    device->make_and_model = strdup(model);
 }
+#endif /* HAVE_DNSSD || HAVE_AVAHI */
 
 
 /*
- * 'sigterm_handler()' - Handle termination signals...
+ * 'sigterm_handler()' - Handle termination signals.
  */
 
 static void
 sigterm_handler(int sig)               /* I - Signal number (unused) */
 {
-  job_canceled = 1;
+  (void)sig;
+
+  if (job_canceled)
+    _exit(CUPS_BACKEND_OK);
+  else
+    job_canceled = 1;
 }
 
 
@@ -905,8 +1313,3 @@ unquote(char       *dst,           /* I - Destination buffer */
 
   *dst = '\0';
 }
-
-
-/*
- * End of "$Id$".
- */