/*
 * "$Id: dirsvc.c 10243 2012-02-11 02:05:21Z mike $"
 *
 *   Directory services routines for the CUPS scheduler.
 *
 *   Copyright 2007-2011 by Apple Inc.
 *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
 *
 *   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"
 *   which should have been included with this file.  If this file is
 *   file is missing or damaged, see the license at "http://www.cups.org/".
 *
 * Contents:
 *
 *   cupsdDeregisterPrinter()   - Stop sending broadcast information for a local
 *                                printer and remove any pending references to
 *                                remote printers.
 *   cupsdLoadRemoteCache()     - Load the remote printer cache.
 *   cupsdRegisterPrinter()     - Start sending broadcast information for a
 *                                printer or update the broadcast contents.
 *   cupsdRestartPolling()      - Restart polling servers as needed.
 *   cupsdSaveRemoteCache()     - Save the remote printer cache.
 *   cupsdSendBrowseList()      - Send new browsing information as necessary.
 *   ldap_rebind_proc()         - Callback function for LDAP rebind
 *   ldap_connect()             - Start new LDAP connection
 *   ldap_reconnect()           - Reconnect to LDAP Server
 *   ldap_disconnect()          - Disconnect from LDAP Server
 *   cupsdStartBrowsing()       - Start sending and receiving broadcast
 *                                information.
 *   cupsdStartPolling()        - Start polling servers as needed.
 *   cupsdStopBrowsing()        - Stop sending and receiving broadcast
 *                                information.
 *   cupsdStopPolling()         - Stop polling servers as needed.
 *   cupsdUpdateDNSSDName()     - Update the computer name we use for
 *                                browsing...
 *   cupsdUpdateLDAPBrowse()    - Scan for new printers via LDAP...
 *   cupsdUpdateSLPBrowse()     - Get browsing information via SLP.
 *   dequote()                  - Remote quotes from a string.
 *   dnssdAddAlias()            - Add a DNS-SD alias name.
 *   dnssdBuildTxtRecord()      - Build a TXT record from printer info.
 *   dnssdComparePrinters()     - Compare the registered names of two printers.
 *   dnssdDeregisterPrinter()   - Stop sending broadcast information for a
 *                                printer.
 *   dnssdPackTxtRecord()       - Pack an array of key/value pairs into the TXT
 *                                record format.
 *   dnssdRegisterCallback()    - DNSServiceRegister callback.
 *   dnssdRegisterPrinter()     - Start sending broadcast information for a
 *                                printer or update the broadcast contents.
 *   dnssdStop()                - Stop all DNS-SD registrations.
 *   dnssdUpdate()              - Handle DNS-SD queries.
 *   get_auth_info_required()   - Get the auth-info-required value to advertise.
 *   get_hostconfig()           - Get an /etc/hostconfig service setting.
 *   is_local_queue()           - Determine whether the URI points at a local
 *                                queue.
 *   process_browse_data()      - Process new browse data.
 *   process_implicit_classes() - Create/update implicit classes as needed.
 *   send_cups_browse()         - Send new browsing information using the CUPS
 *                                protocol.
 *   ldap_search_rec()          - LDAP Search with reconnect
 *   ldap_freeres()             - Free LDAPMessage
 *   ldap_getval_char()         - Get first LDAP value and convert to string
 *   send_ldap_ou()             - Send LDAP ou registrations.
 *   send_ldap_browse()         - Send LDAP printer registrations.
 *   ldap_dereg_printer()       - Delete printer from directory
 *   ldap_dereg_ou()            - Remove the organizational unit.
 *   send_slp_browse()          - Register the specified printer with SLP.
 *   slp_attr_callback()        - SLP attribute callback
 *   slp_dereg_printer()        - SLPDereg() the specified printer
 *   slp_get_attr()             - Get an attribute from an SLP registration.
 *   slp_reg_callback()         - Empty SLPRegReport.
 *   slp_url_callback()         - SLP service url callback
 *   update_cups_browse()       - Update the browse lists using the CUPS
 *                                protocol.
 *   update_lpd()               - Update the LPD configuration as needed.
 *   update_polling()           - Read status messages from the poll daemons.
 *   update_smb()               - Update the SMB configuration as needed.
 */

/*
 * Include necessary headers...
 */

#include "cupsd.h"
#include <grp.h>

#ifdef HAVE_DNSSD
#  include <dns_sd.h>
#  ifdef __APPLE__
#    include <nameser.h>
#    ifdef HAVE_COREFOUNDATION
#      include <CoreFoundation/CoreFoundation.h>
#    endif /* HAVE_COREFOUNDATION */
#    ifdef HAVE_SYSTEMCONFIGURATION
#      include <SystemConfiguration/SystemConfiguration.h>
#    endif /* HAVE_SYSTEMCONFIGURATION */
#  endif /* __APPLE__ */
#endif /* HAVE_DNSSD */


/*
 * Local functions...
 */

static char	*dequote(char *d, const char *s, int dlen);
static char	*get_auth_info_required(cupsd_printer_t *p, char *buffer,
		                        size_t bufsize);
#ifdef __APPLE__
static int	get_hostconfig(const char *name);
#endif /* __APPLE__ */
static int	is_local_queue(const char *uri, char *host, int hostlen,
		               char *resource, int resourcelen);
static void	process_browse_data(const char *uri, const char *host,
		                    const char *resource, cups_ptype_t type,
				    ipp_pstate_t state, const char *location,
				    const char *info, const char *make_model,
				    int num_attrs, cups_option_t *attrs);
static void	process_implicit_classes(void);
static void	send_cups_browse(cupsd_printer_t *p);
#ifdef HAVE_LDAP
static LDAP	*ldap_connect(void);
static LDAP	*ldap_reconnect(void);
static void	ldap_disconnect(LDAP *ld);
static int	ldap_search_rec(LDAP *ld, char *base, int scope,
                                char *filter, char *attrs[],
                                int attrsonly, LDAPMessage **res);
static int	ldap_getval_firststring(LDAP *ld, LDAPMessage *entry,
                                        char *attr, char *retval,
                                        unsigned long maxsize);
static void	ldap_freeres(LDAPMessage *entry);
static void	send_ldap_ou(char *ou, char *basedn, char *descstring);
static void	send_ldap_browse(cupsd_printer_t *p);
static void	ldap_dereg_printer(cupsd_printer_t *p);
static void	ldap_dereg_ou(char *ou, char *basedn);
#  ifdef HAVE_LDAP_REBIND_PROC
#    if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
static int	ldap_rebind_proc(LDAP *RebindLDAPHandle,
                                 LDAP_CONST char *refsp,
                                 ber_tag_t request,
                                 ber_int_t msgid,
                                 void *params);
#    else
static int	ldap_rebind_proc(LDAP *RebindLDAPHandle,
                                 char **dnp,
                                 char **passwdp,
                                 int *authmethodp,
                                 int freeit,
                                 void *arg);
#    endif /* defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) */
#  endif /* HAVE_LDAP_REBIND_PROC */
#endif /* HAVE_LDAP */
#ifdef HAVE_LIBSLP
static void	send_slp_browse(cupsd_printer_t *p);
#endif /* HAVE_LIBSLP */
static void	update_cups_browse(void);
static void	update_lpd(int onoff);
static void	update_polling(void);
static void	update_smb(int onoff);


#ifdef HAVE_DNSSD
#  ifdef HAVE_COREFOUNDATION
static void	dnssdAddAlias(const void *key, const void *value,
		              void *context);
#  endif /* HAVE_COREFOUNDATION */
static char	*dnssdBuildTxtRecord(int *txt_len, cupsd_printer_t *p,
		                     int for_lpd);
static int	dnssdComparePrinters(cupsd_printer_t *a, cupsd_printer_t *b);
static void	dnssdDeregisterPrinter(cupsd_printer_t *p);
static char	*dnssdPackTxtRecord(int *txt_len, char *keyvalue[][2],
		                    int count);
static void	dnssdRegisterCallback(DNSServiceRef sdRef,
		                      DNSServiceFlags flags,
				      DNSServiceErrorType errorCode,
				      const char *name, const char *regtype,
				      const char *domain, void *context);
static void	dnssdRegisterPrinter(cupsd_printer_t *p);
static void	dnssdStop(void);
static void	dnssdUpdate(void);
#endif /* HAVE_DNSSD */

#ifdef HAVE_LDAP
static const char * const ldap_attrs[] =/* CUPS LDAP attributes */
		{
		  "printerDescription",
		  "printerLocation",
		  "printerMakeAndModel",
		  "printerType",
		  "printerURI",
		  NULL
		};
#endif /* HAVE_LDAP */

#ifdef HAVE_LIBSLP
/*
 * SLP definitions...
 */

/*
 * SLP service name for CUPS...
 */

#  define SLP_CUPS_SRVTYPE	"service:printer"
#  define SLP_CUPS_SRVLEN	15


/*
 * Printer service URL structure
 */

typedef struct _slpsrvurl_s		/**** SLP URL list ****/
{
  struct _slpsrvurl_s	*next;		/* Next URL in list */
  char			url[HTTP_MAX_URI];
					/* URL */
} slpsrvurl_t;


/*
 * Local functions...
 */

static SLPBoolean	slp_attr_callback(SLPHandle hslp, const char *attrlist,
			                  SLPError errcode, void *cookie);
static void		slp_dereg_printer(cupsd_printer_t *p);
static int 		slp_get_attr(const char *attrlist, const char *tag,
			             char **valbuf);
static void		slp_reg_callback(SLPHandle hslp, SLPError errcode,
					 void *cookie);
static SLPBoolean	slp_url_callback(SLPHandle hslp, const char *srvurl,
			                 unsigned short lifetime,
			                 SLPError errcode, void *cookie);
#endif /* HAVE_LIBSLP */


/*
 * 'cupsdDeregisterPrinter()' - Stop sending broadcast information for a
 *				local printer and remove any pending
 *                              references to remote printers.
 */

void
cupsdDeregisterPrinter(
    cupsd_printer_t *p,			/* I - Printer to register */
    int             removeit)		/* I - Printer being permanently removed */
{
 /*
  * Only deregister if browsing is enabled and it's a local printer...
  */

  cupsdLogMessage(CUPSD_LOG_DEBUG,
                  "cupsdDeregisterPrinter(p=%p(%s), removeit=%d)", p, p->name,
		  removeit);

  if (!Browsing || !p->shared ||
      (p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT |
                  CUPS_PRINTER_SCANNER)))
    return;

 /*
  * Announce the deletion...
  */

  if ((BrowseLocalProtocols & BROWSE_CUPS) && BrowseSocket >= 0)
  {
    cups_ptype_t savedtype = p->type;	/* Saved printer type */

    p->type |= CUPS_PRINTER_DELETE;

    send_cups_browse(p);

    p->type = savedtype;
  }

#ifdef HAVE_LIBSLP
  if (BrowseLocalProtocols & BROWSE_SLP)
    slp_dereg_printer(p);
#endif /* HAVE_LIBSLP */

#ifdef HAVE_LDAP
  if (BrowseLocalProtocols & BROWSE_LDAP)
    ldap_dereg_printer(p);
#endif /* HAVE_LDAP */

#ifdef HAVE_DNSSD
  if (removeit && (BrowseLocalProtocols & BROWSE_DNSSD) && DNSSDRef)
    dnssdDeregisterPrinter(p);
#endif /* HAVE_DNSSD */
}


/*
 * 'cupsdLoadRemoteCache()' - Load the remote printer cache.
 */

void
cupsdLoadRemoteCache(void)
{
  int			i;		/* Looping var */
  cups_file_t		*fp;		/* remote.cache file */
  int			linenum;	/* Current line number */
  char			line[4096],	/* Line from file */
			*value,		/* Pointer to value */
			*valueptr,	/* Pointer into value */
			scheme[32],	/* Scheme portion of URI */
			username[64],	/* Username portion of URI */
			host[HTTP_MAX_HOST],
					/* Hostname portion of URI */
			resource[HTTP_MAX_URI];
					/* Resource portion of URI */
  int			port;		/* Port number */
  cupsd_printer_t	*p;		/* Current printer */
  time_t		now;		/* Current time */


 /*
  * Don't load the cache if the remote protocols are disabled...
  */

  if (!Browsing)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG,
                    "cupsdLoadRemoteCache: Not loading remote cache.");
    return;
  }

 /*
  * Open the remote.cache file...
  */

  snprintf(line, sizeof(line), "%s/remote.cache", CacheDir);
  if ((fp = cupsdOpenConfFile(line)) == NULL)
    return;

 /*
  * Read printer configurations until we hit EOF...
  */

  linenum = 0;
  p       = NULL;
  now     = time(NULL);

  while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
  {
   /*
    * Decode the directive...
    */

    if (!_cups_strcasecmp(line, "<Printer") ||
        !_cups_strcasecmp(line, "<DefaultPrinter"))
    {
     /*
      * <Printer name> or <DefaultPrinter name>
      */

      if (p == NULL && value)
      {
       /*
        * Add the printer and a base file type...
	*/

        cupsdLogMessage(CUPSD_LOG_DEBUG,
	                "cupsdLoadRemoteCache: Loading printer %s...", value);

        if ((p = cupsdFindDest(value)) != NULL)
	{
	  if (p->type & CUPS_PRINTER_CLASS)
	  {
	    cupsdLogMessage(CUPSD_LOG_WARN,
	                    "Cached remote printer \"%s\" conflicts with "
			    "existing class!",
	                    value);
	    p = NULL;
	    continue;
	  }
	}
	else
          p = cupsdAddPrinter(value);

	p->accepting     = 1;
	p->state         = IPP_PRINTER_IDLE;
	p->type          |= CUPS_PRINTER_REMOTE | CUPS_PRINTER_DISCOVERED;
	p->browse_time   = now;
	p->browse_expire = now + BrowseTimeout;

       /*
        * Set the default printer as needed...
	*/

        if (!_cups_strcasecmp(line, "<DefaultPrinter"))
	  DefaultPrinter = p;
      }
      else
      {
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
        break;
      }
    }
    else if (!_cups_strcasecmp(line, "<Class") ||
             !_cups_strcasecmp(line, "<DefaultClass"))
    {
     /*
      * <Class name> or <DefaultClass name>
      */

      if (p == NULL && value)
      {
       /*
        * Add the printer and a base file type...
	*/

        cupsdLogMessage(CUPSD_LOG_DEBUG,
	                "cupsdLoadRemoteCache: Loading class %s...", value);

        if ((p = cupsdFindDest(value)) != NULL)
	  p->type = CUPS_PRINTER_CLASS;
	else
          p = cupsdAddClass(value);

	p->accepting     = 1;
	p->state         = IPP_PRINTER_IDLE;
	p->type          |= CUPS_PRINTER_REMOTE | CUPS_PRINTER_DISCOVERED;
	p->browse_time   = now;
	p->browse_expire = now + BrowseTimeout;

       /*
        * Set the default printer as needed...
	*/

        if (!_cups_strcasecmp(line, "<DefaultClass"))
	  DefaultPrinter = p;
      }
      else
      {
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
        break;
      }
    }
    else if (!_cups_strcasecmp(line, "</Printer>") ||
             !_cups_strcasecmp(line, "</Class>"))
    {
      if (p != NULL)
      {
       /*
        * Close out the current printer...
	*/

        cupsdSetPrinterAttrs(p);

        p = NULL;
      }
      else
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
    }
    else if (!p)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Syntax error on line %d of remote.cache.", linenum);
    }
    else if (!_cups_strcasecmp(line, "UUID"))
    {
      if (value && !strncmp(value, "urn:uuid:", 9))
        cupsdSetString(&(p->uuid), value);
      else
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Bad UUID on line %d of remote.cache.", linenum);
    }
    else if (!_cups_strcasecmp(line, "Info"))
    {
      if (value)
	cupsdSetString(&p->info, value);
    }
    else if (!_cups_strcasecmp(line, "MakeModel"))
    {
      if (value)
	cupsdSetString(&p->make_model, value);
    }
    else if (!_cups_strcasecmp(line, "Location"))
    {
      if (value)
	cupsdSetString(&p->location, value);
    }
    else if (!_cups_strcasecmp(line, "DeviceURI"))
    {
      if (value)
      {
	httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme),
	                username, sizeof(username), host, sizeof(host), &port,
			resource, sizeof(resource));

	cupsdSetString(&p->hostname, host);
	cupsdSetString(&p->uri, value);
	cupsdSetDeviceURI(p, value);
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
    }
    else if (!_cups_strcasecmp(line, "Option") && value)
    {
     /*
      * Option name value
      */

      for (valueptr = value; *valueptr && !isspace(*valueptr & 255); valueptr ++);

      if (!*valueptr)
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
      else
      {
        for (; *valueptr && isspace(*valueptr & 255); *valueptr++ = '\0');

        p->num_options = cupsAddOption(value, valueptr, p->num_options,
	                               &(p->options));
      }
    }
    else if (!_cups_strcasecmp(line, "Reason"))
    {
      if (value)
      {
        for (i = 0 ; i < p->num_reasons; i ++)
	  if (!strcmp(value, p->reasons[i]))
	    break;

        if (i >= p->num_reasons &&
	    p->num_reasons < (int)(sizeof(p->reasons) / sizeof(p->reasons[0])))
	{
	  p->reasons[p->num_reasons] = _cupsStrAlloc(value);
	  p->num_reasons ++;
	}
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
    }
    else if (!_cups_strcasecmp(line, "State"))
    {
     /*
      * Set the initial queue state...
      */

      if (value && !_cups_strcasecmp(value, "idle"))
        p->state = IPP_PRINTER_IDLE;
      else if (value && !_cups_strcasecmp(value, "stopped"))
      {
        p->state = IPP_PRINTER_STOPPED;
	cupsdSetPrinterReasons(p, "+paused");
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
    }
    else if (!_cups_strcasecmp(line, "StateMessage"))
    {
     /*
      * Set the initial queue state message...
      */

      if (value)
	strlcpy(p->state_message, value, sizeof(p->state_message));
    }
    else if (!_cups_strcasecmp(line, "Accepting"))
    {
     /*
      * Set the initial accepting state...
      */

      if (value &&
          (!_cups_strcasecmp(value, "yes") ||
           !_cups_strcasecmp(value, "on") ||
           !_cups_strcasecmp(value, "true")))
        p->accepting = 1;
      else if (value &&
               (!_cups_strcasecmp(value, "no") ||
        	!_cups_strcasecmp(value, "off") ||
        	!_cups_strcasecmp(value, "false")))
        p->accepting = 0;
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
    }
    else if (!_cups_strcasecmp(line, "Type"))
    {
      if (value)
        p->type = atoi(value);
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
    }
    else if (!_cups_strcasecmp(line, "BrowseTime"))
    {
      if (value)
      {
        time_t t = atoi(value);

	if (t > p->browse_expire)
          p->browse_expire = t;
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
    }
    else if (!_cups_strcasecmp(line, "JobSheets"))
    {
     /*
      * Set the initial job sheets...
      */

      if (value)
      {
	for (valueptr = value; *valueptr && !isspace(*valueptr & 255); valueptr ++);

	if (*valueptr)
          *valueptr++ = '\0';

	cupsdSetString(&p->job_sheets[0], value);

	while (isspace(*valueptr & 255))
          valueptr ++;

	if (*valueptr)
	{
          for (value = valueptr; *valueptr && !isspace(*valueptr & 255); valueptr ++);

	  if (*valueptr)
            *valueptr = '\0';

	  cupsdSetString(&p->job_sheets[1], value);
	}
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
    }
    else if (!_cups_strcasecmp(line, "AllowUser"))
    {
      if (value)
      {
        p->deny_users = 0;
        cupsdAddString(&(p->users), value);
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
    }
    else if (!_cups_strcasecmp(line, "DenyUser"))
    {
      if (value)
      {
        p->deny_users = 1;
        cupsdAddString(&(p->users), value);
      }
      else
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Syntax error on line %d of remote.cache.", linenum);
    }
    else
    {
     /*
      * Something else we don't understand...
      */

      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Unknown configuration directive %s on line %d of remote.cache.",
	              line, linenum);
    }
  }

  cupsFileClose(fp);

 /*
  * Do auto-classing if needed...
  */

  process_implicit_classes();
}


/*
 * 'cupsdRegisterPrinter()' - Start sending broadcast information for a
 *                            printer or update the broadcast contents.
 */

void
cupsdRegisterPrinter(cupsd_printer_t *p)/* I - Printer */
{
  cupsdLogMessage(CUPSD_LOG_DEBUG, "cupsdRegisterPrinter(p=%p(%s))", p,
                  p->name);

  if (!Browsing || !BrowseLocalProtocols ||
      (p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT |
                  CUPS_PRINTER_SCANNER)))
    return;

#ifdef HAVE_LIBSLP
/*  if (BrowseLocalProtocols & BROWSE_SLP)
    slpRegisterPrinter(p); */
#endif /* HAVE_LIBSLP */

#ifdef HAVE_DNSSD
  if ((BrowseLocalProtocols & BROWSE_DNSSD) && DNSSDRef)
    dnssdRegisterPrinter(p);
#endif /* HAVE_DNSSD */
}


/*
 * 'cupsdRestartPolling()' - Restart polling servers as needed.
 */

void
cupsdRestartPolling(void)
{
  int			i;		/* Looping var */
  cupsd_dirsvc_poll_t	*pollp;		/* Current polling server */


  for (i = 0, pollp = Polled; i < NumPolled; i ++, pollp ++)
    if (pollp->pid)
      kill(pollp->pid, SIGHUP);
}


/*
 * 'cupsdSaveRemoteCache()' - Save the remote printer cache.
 */

void
cupsdSaveRemoteCache(void)
{
  int			i;		/* Looping var */
  cups_file_t		*fp;		/* remote.cache file */
  char			filename[1024],	/* remote.cache filename */
			temp[1024],	/* Temporary string */
			value[2048],	/* Value string */
			*name;		/* Current user name */
  cupsd_printer_t	*printer;	/* Current printer class */
  time_t		curtime;	/* Current time */
  struct tm		*curdate;	/* Current date */
  cups_option_t		*option;	/* Current option */


 /*
  * Create the remote.cache file...
  */

  snprintf(filename, sizeof(filename), "%s/remote.cache", CacheDir);

  if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL)
    return;

  cupsdLogMessage(CUPSD_LOG_DEBUG, "Saving remote.cache...");

 /*
  * Write a small header to the file...
  */

  curtime = time(NULL);
  curdate = localtime(&curtime);
  strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", curdate);

  cupsFilePuts(fp, "# Remote cache file for " CUPS_SVERSION "\n");
  cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp);

 /*
  * Write each local printer known to the system...
  */

  for (printer = (cupsd_printer_t *)cupsArrayFirst(Printers);
       printer;
       printer = (cupsd_printer_t *)cupsArrayNext(Printers))
  {
   /*
    * Skip local destinations...
    */

    if (!(printer->type & CUPS_PRINTER_DISCOVERED))
      continue;

   /*
    * Write printers as needed...
    */

    if (printer == DefaultPrinter)
      cupsFilePuts(fp, "<Default");
    else
      cupsFilePutChar(fp, '<');

    if (printer->type & CUPS_PRINTER_CLASS)
      cupsFilePrintf(fp, "Class %s>\n", printer->name);
    else
      cupsFilePrintf(fp, "Printer %s>\n", printer->name);

    cupsFilePrintf(fp, "BrowseTime %d\n", (int)printer->browse_expire);

    cupsFilePrintf(fp, "UUID %s\n", printer->uuid);

    if (printer->info)
      cupsFilePutConf(fp, "Info", printer->info);

    if (printer->location)
      cupsFilePutConf(fp, "Location", printer->location);

    if (printer->make_model)
      cupsFilePutConf(fp, "MakeModel", printer->make_model);

    cupsFilePutConf(fp, "DeviceURI", printer->device_uri);

    if (printer->state == IPP_PRINTER_STOPPED)
      cupsFilePuts(fp, "State Stopped\n");
    else
      cupsFilePuts(fp, "State Idle\n");

    for (i = 0; i < printer->num_reasons; i ++)
      cupsFilePutConf(fp, "Reason", printer->reasons[i]);

    cupsFilePrintf(fp, "Type %d\n", printer->type);

    if (printer->accepting)
      cupsFilePuts(fp, "Accepting Yes\n");
    else
      cupsFilePuts(fp, "Accepting No\n");

    snprintf(value, sizeof(value), "%s %s", printer->job_sheets[0],
             printer->job_sheets[1]);
    cupsFilePutConf(fp, "JobSheets", value);

    for (name = (char *)cupsArrayFirst(printer->users);
	 name;
	 name = (char *)cupsArrayNext(printer->users))
      cupsFilePutConf(fp, printer->deny_users ? "DenyUser" : "AllowUser", name);

    for (i = printer->num_options, option = printer->options;
         i > 0;
	 i --, option ++)
    {
      snprintf(value, sizeof(value), "%s %s", option->name, option->value);
      cupsFilePutConf(fp, "Option", value);
    }

    if (printer->type & CUPS_PRINTER_CLASS)
      cupsFilePuts(fp, "</Class>\n");
    else
      cupsFilePuts(fp, "</Printer>\n");
  }

  cupsdCloseCreatedConfFile(fp, filename);
}


/*
 * 'cupsdSendBrowseList()' - Send new browsing information as necessary.
 */

void
cupsdSendBrowseList(void)
{
  int			count;		/* Number of dests to update */
  cupsd_printer_t	*p;		/* Current printer */
  time_t		ut,		/* Minimum update time */
			to;		/* Timeout time */


  if (!Browsing || !Printers)
    return;

 /*
  * Compute the update and timeout times...
  */

  to = time(NULL);
  ut = to - BrowseInterval;

 /*
  * Figure out how many printers need an update...
  */

  if (BrowseInterval > 0 && BrowseLocalProtocols)
  {
    int	max_count;			/* Maximum number to update */


   /*
    * Throttle the number of printers we'll be updating this time
    * around based on the number of queues that need updating and
    * the maximum number of queues to update each second...
    */

    max_count = 2 * cupsArrayCount(Printers) / BrowseInterval + 1;

    for (count = 0, p = (cupsd_printer_t *)cupsArrayFirst(Printers);
         count < max_count && p != NULL;
	 p = (cupsd_printer_t *)cupsArrayNext(Printers))
      if (!(p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT |
                       CUPS_PRINTER_SCANNER)) &&
          p->shared && p->browse_time < ut)
        count ++;

   /*
    * Loop through all of the printers and send local updates as needed...
    */

    if (BrowseNext)
      p = (cupsd_printer_t *)cupsArrayFind(Printers, BrowseNext);
    else
      p = (cupsd_printer_t *)cupsArrayFirst(Printers);

    for (;
         count > 0;
	 p = (cupsd_printer_t *)cupsArrayNext(Printers))
    {
     /*
      * Check for wraparound...
      */

      if (!p)
        p = (cupsd_printer_t *)cupsArrayFirst(Printers);

      if (!p)
        break;
      else if ((p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT |
                           CUPS_PRINTER_SCANNER)) ||
               !p->shared)
        continue;
      else if (p->browse_time < ut)
      {
       /*
	* Need to send an update...
	*/

	count --;

	p->browse_time = time(NULL);

	if ((BrowseLocalProtocols & BROWSE_CUPS) && BrowseSocket >= 0)
          send_cups_browse(p);

#ifdef HAVE_LIBSLP
	if (BrowseLocalProtocols & BROWSE_SLP)
          send_slp_browse(p);
#endif /* HAVE_LIBSLP */

#ifdef HAVE_LDAP
	if (BrowseLocalProtocols & BROWSE_LDAP)
          send_ldap_browse(p);
#endif /* HAVE_LDAP */
      }
    }

   /*
    * Save where we left off so that all printers get updated...
    */

    BrowseNext = p;
  }

 /*
  * Loop through all of the printers and timeout old printers as needed...
  */

  for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
       p;
       p = (cupsd_printer_t *)cupsArrayNext(Printers))
  {
   /*
    * If this is a remote queue, see if it needs to be timed out...
    */

    if ((p->type & CUPS_PRINTER_DISCOVERED) &&
        !(p->type & CUPS_PRINTER_IMPLICIT) &&
	p->browse_expire < to)
    {
      cupsdAddEvent(CUPSD_EVENT_PRINTER_DELETED, p, NULL,
		    "%s \'%s\' deleted by directory services (timeout).",
		    (p->type & CUPS_PRINTER_CLASS) ? "Class" : "Printer",
		    p->name);

      cupsdLogMessage(CUPSD_LOG_DEBUG,
		      "Remote destination \"%s\" has timed out; "
		      "deleting it...",
		      p->name);

      cupsArraySave(Printers);
      cupsdDeletePrinter(p, 1);
      cupsArrayRestore(Printers);
      cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP | CUPSD_DIRTY_REMOTE);
    }
  }
}


#ifdef HAVE_LDAP_REBIND_PROC
#  if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
/*
 * 'ldap_rebind_proc()' - Callback function for LDAP rebind
 */

static int				/* O - Result code */
ldap_rebind_proc(
    LDAP            *RebindLDAPHandle,	/* I - LDAP handle */
    LDAP_CONST char *refsp,		/* I - ??? */
    ber_tag_t       request,		/* I - ??? */
    ber_int_t       msgid,		/* I - ??? */
    void            *params)		/* I - ??? */
{
  int		rc;			/* Result code */
#    if LDAP_API_VERSION > 3000
  struct berval	bval;			/* Bind value */
#    endif /* LDAP_API_VERSION > 3000 */


  (void)request;
  (void)msgid;
  (void)params;

 /*
  * Bind to new LDAP server...
  */

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_rebind_proc: Rebind to %s", refsp);

#    if LDAP_API_VERSION > 3000
  bval.bv_val = BrowseLDAPPassword;
  bval.bv_len = (BrowseLDAPPassword == NULL) ? 0 : strlen(BrowseLDAPPassword);

  rc = ldap_sasl_bind_s(RebindLDAPHandle, BrowseLDAPBindDN, LDAP_SASL_SIMPLE,
                        &bval, NULL, NULL, NULL);
#    else
  rc = ldap_bind_s(RebindLDAPHandle, BrowseLDAPBindDN, BrowseLDAPPassword,
                   LDAP_AUTH_SIMPLE);
#    endif /* LDAP_API_VERSION > 3000 */

  return (rc);
}


#  else /* defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) */
/*
 * 'ldap_rebind_proc()' - Callback function for LDAP rebind
 */

static int				/* O - Result code */
ldap_rebind_proc(
    LDAP *RebindLDAPHandle,		/* I - LDAP handle */
    char **dnp,				/* I - ??? */
    char **passwdp,			/* I - ??? */
    int  *authmethodp,			/* I - ??? */
    int  freeit,			/* I - ??? */
    void *arg)				/* I - ??? */
{
  switch (freeit)
  {
    case 1:
       /*
        * Free current values...
        */

        cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_rebind_proc: Free values...");

        if (dnp && *dnp)
          free(*dnp);

        if (passwdp && *passwdp)
          free(*passwdp);
        break;

    case 0:
       /*
        * Return credentials for LDAP referal...
        */

        cupsdLogMessage(CUPSD_LOG_DEBUG2,
                        "ldap_rebind_proc: Return necessary values...");

        *dnp         = strdup(BrowseLDAPBindDN);
        *passwdp     = strdup(BrowseLDAPPassword);
        *authmethodp = LDAP_AUTH_SIMPLE;
        break;

    default:
       /*
        * Should never happen...
        */

        cupsdLogMessage(CUPSD_LOG_ERROR,
                        "LDAP rebind has been called with wrong freeit value!");
        break;
  }

  return (LDAP_SUCCESS);
}
#  endif /* defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) */
#endif /* HAVE_LDAP_REBIND_PROC */


#ifdef HAVE_LDAP
/*
 * 'ldap_connect()' - Start new LDAP connection
 */

static LDAP *				/* O - LDAP handle */
ldap_connect(void)
{
  int		rc;			/* LDAP API status */
  int		version = 3;		/* LDAP version */
  struct berval	bv = {0, ""};		/* SASL bind value */
  LDAP		*TempBrowseLDAPHandle=NULL;
					/* Temporary LDAP Handle */
#  if defined(HAVE_LDAP_SSL) && defined (HAVE_MOZILLA_LDAP)
  int		ldap_ssl = 0;		/* LDAP SSL indicator */
  int		ssl_err = 0;		/* LDAP SSL error value */
#  endif /* defined(HAVE_LDAP_SSL) && defined (HAVE_MOZILLA_LDAP) */


#  ifdef HAVE_OPENLDAP
#    ifdef HAVE_LDAP_SSL
 /*
  * Set the certificate file to use for encrypted LDAP sessions...
  */

  if (BrowseLDAPCACertFile)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG,
	            "ldap_connect: Setting CA certificate file \"%s\"",
                    BrowseLDAPCACertFile);

    if ((rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE,
	                      (void *)BrowseLDAPCACertFile)) != LDAP_SUCCESS)
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Unable to set CA certificate file for LDAP "
                      "connections: %d - %s", rc, ldap_err2string(rc));
  }
#    endif /* HAVE_LDAP_SSL */

 /*
  * Initialize OPENLDAP connection...
  * LDAP stuff currently only supports ldapi EXTERNAL SASL binds...
  */

  if (!BrowseLDAPServer || !_cups_strcasecmp(BrowseLDAPServer, "localhost"))
    rc = ldap_initialize(&TempBrowseLDAPHandle, "ldapi:///");
  else
    rc = ldap_initialize(&TempBrowseLDAPHandle, BrowseLDAPServer);

#  else /* HAVE_OPENLDAP */

  int		ldap_port = 0;			/* LDAP port */
  char		ldap_protocol[11],		/* LDAP protocol */
		ldap_host[255];			/* LDAP host */

 /*
  * Split LDAP URI into its components...
  */

  if (!BrowseLDAPServer)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "BrowseLDAPServer not configured!");
    cupsdLogMessage(CUPSD_LOG_ERROR, "Disabling LDAP browsing!");
    BrowseLocalProtocols  &= ~BROWSE_LDAP;
    BrowseRemoteProtocols &= ~BROWSE_LDAP;
    return (NULL);
  }

  sscanf(BrowseLDAPServer, "%10[^:]://%254[^:/]:%d", ldap_protocol, ldap_host,
         &ldap_port);

  if (!strcmp(ldap_protocol, "ldap"))
    ldap_ssl = 0;
  else if (!strcmp(ldap_protocol, "ldaps"))
    ldap_ssl = 1;
  else
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Unrecognized LDAP protocol (%s)!",
                    ldap_protocol);
    cupsdLogMessage(CUPSD_LOG_ERROR, "Disabling LDAP browsing!");
    BrowseLocalProtocols &= ~BROWSE_LDAP;
    BrowseRemoteProtocols &= ~BROWSE_LDAP;
    return (NULL);
  }

  if (ldap_port == 0)
  {
    if (ldap_ssl)
      ldap_port = LDAPS_PORT;
    else
      ldap_port = LDAP_PORT;
  }

  cupsdLogMessage(CUPSD_LOG_DEBUG, "ldap_connect: PROT:%s HOST:%s PORT:%d",
                  ldap_protocol, ldap_host, ldap_port);

 /*
  * Initialize LDAP connection...
  */

  if (!ldap_ssl)
  {
    if ((TempBrowseLDAPHandle = ldap_init(ldap_host, ldap_port)) == NULL)
      rc = LDAP_OPERATIONS_ERROR;
    else
      rc = LDAP_SUCCESS;

#    ifdef HAVE_LDAP_SSL
  }
  else
  {
   /*
    * Initialize SSL LDAP connection...
    */

    if (BrowseLDAPCACertFile)
    {
      rc = ldapssl_client_init(BrowseLDAPCACertFile, (void *)NULL);
      if (rc != LDAP_SUCCESS)
      {
        cupsdLogMessage(CUPSD_LOG_ERROR,
                        "Failed to initialize LDAP SSL client!");
        rc = LDAP_OPERATIONS_ERROR;
      }
      else
      {
        if ((TempBrowseLDAPHandle = ldapssl_init(ldap_host, ldap_port,
                                                 1)) == NULL)
          rc = LDAP_OPERATIONS_ERROR;
        else
          rc = LDAP_SUCCESS;
      }
    }
    else
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "LDAP SSL certificate file/database not configured!");
      rc = LDAP_OPERATIONS_ERROR;
    }

#    else /* HAVE_LDAP_SSL */

   /*
    * Return error, because client libraries doesn't support SSL
    */

    cupsdLogMessage(CUPSD_LOG_ERROR,
                    "LDAP client libraries do not support SSL");
    rc = LDAP_OPERATIONS_ERROR;

#    endif /* HAVE_LDAP_SSL */
  }
#  endif /* HAVE_OPENLDAP */

 /*
  * Check return code from LDAP initialize...
  */

  if (rc != LDAP_SUCCESS)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to initialize LDAP!");

    if (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR)
      cupsdLogMessage(CUPSD_LOG_ERROR, "Temporarily disabling LDAP browsing...");
    else
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Disabling LDAP browsing!");

      BrowseLocalProtocols  &= ~BROWSE_LDAP;
      BrowseRemoteProtocols &= ~BROWSE_LDAP;
    }

    ldap_disconnect(TempBrowseLDAPHandle);

    return (NULL);
  }

 /*
  * Upgrade LDAP version...
  */

  if (ldap_set_option(TempBrowseLDAPHandle, LDAP_OPT_PROTOCOL_VERSION,
                           (const void *)&version) != LDAP_SUCCESS)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to set LDAP protocol version %d!",
                   version);
    cupsdLogMessage(CUPSD_LOG_ERROR, "Disabling LDAP browsing!");

    BrowseLocalProtocols  &= ~BROWSE_LDAP;
    BrowseRemoteProtocols &= ~BROWSE_LDAP;
    ldap_disconnect(TempBrowseLDAPHandle);

    return (NULL);
  }

 /*
  * Register LDAP rebind procedure...
  */

#  ifdef HAVE_LDAP_REBIND_PROC
#    if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)

  rc = ldap_set_rebind_proc(TempBrowseLDAPHandle, &ldap_rebind_proc,
                            (void *)NULL);
  if (rc != LDAP_SUCCESS)
    cupsdLogMessage(CUPSD_LOG_ERROR,
                    "Setting LDAP rebind function failed with status %d: %s",
                    rc, ldap_err2string(rc));

#    else

  ldap_set_rebind_proc(TempBrowseLDAPHandle, &ldap_rebind_proc, (void *)NULL);

#    endif /* defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000) */
#  endif /* HAVE_LDAP_REBIND_PROC */

 /*
  * Start LDAP bind...
  */

#  if LDAP_API_VERSION > 3000
  struct berval bval;
  bval.bv_val = BrowseLDAPPassword;
  bval.bv_len = (BrowseLDAPPassword == NULL) ? 0 : strlen(BrowseLDAPPassword);

  if (!BrowseLDAPServer || !_cups_strcasecmp(BrowseLDAPServer, "localhost"))
    rc = ldap_sasl_bind_s(TempBrowseLDAPHandle, NULL, "EXTERNAL", &bv, NULL,
                          NULL, NULL);
  else
    rc = ldap_sasl_bind_s(TempBrowseLDAPHandle, BrowseLDAPBindDN, LDAP_SASL_SIMPLE, &bval, NULL, NULL, NULL);

#  else
    rc = ldap_bind_s(TempBrowseLDAPHandle, BrowseLDAPBindDN,
                     BrowseLDAPPassword, LDAP_AUTH_SIMPLE);
#  endif /* LDAP_API_VERSION > 3000 */

  if (rc != LDAP_SUCCESS)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "LDAP bind failed with error %d: %s",
                    rc, ldap_err2string(rc));

#  if defined(HAVE_LDAP_SSL) && defined (HAVE_MOZILLA_LDAP) && !defined(__sun)
    if (ldap_ssl && (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR))
    {
      ssl_err = PORT_GetError();
      if (ssl_err != 0)
        cupsdLogMessage(CUPSD_LOG_ERROR, "LDAP SSL error %d: %s", ssl_err,
                        ldapssl_err2string(ssl_err));
    }
#  endif /* defined(HAVE_LDAP_SSL) && defined (HAVE_MOZILLA_LDAP) */

    ldap_disconnect(TempBrowseLDAPHandle);

    return (NULL);
  }

  cupsdLogMessage(CUPSD_LOG_INFO, "LDAP connection established");

  return (TempBrowseLDAPHandle);
}


/*
 * 'ldap_reconnect()' - Reconnect to LDAP Server
 */

static LDAP *				/* O - New LDAP handle */
ldap_reconnect(void)
{
  LDAP	*TempBrowseLDAPHandle = NULL;	/* Temp Handle to LDAP server */


 /*
  * Get a new LDAP Handle and replace the global Handle
  * if the new connection was successful.
  */

  cupsdLogMessage(CUPSD_LOG_INFO, "Try LDAP reconnect...");

  TempBrowseLDAPHandle = ldap_connect();

  if (TempBrowseLDAPHandle != NULL)
  {
    if (BrowseLDAPHandle != NULL)
      ldap_disconnect(BrowseLDAPHandle);

    BrowseLDAPHandle = TempBrowseLDAPHandle;
  }

  return (BrowseLDAPHandle);
}


/*
 * 'ldap_disconnect()' - Disconnect from LDAP Server
 */

static void
ldap_disconnect(LDAP *ld)		/* I - LDAP handle */
{
  int	rc;				/* Return code */


 /*
  * Close LDAP handle...
  */

#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
  rc = ldap_unbind_ext_s(ld, NULL, NULL);
#  else
  rc = ldap_unbind_s(ld);
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */

  if (rc != LDAP_SUCCESS)
    cupsdLogMessage(CUPSD_LOG_ERROR,
                    "Unbind from LDAP server failed with status %d: %s",
                    rc, ldap_err2string(rc));
}
#endif /* HAVE_LDAP */


/*
 * 'cupsdStartBrowsing()' - Start sending and receiving broadcast information.
 */

void
cupsdStartBrowsing(void)
{
  int			val;		/* Socket option value */
  struct sockaddr_in	addr;		/* Broadcast address */
  cupsd_printer_t	*p;		/* Current printer */


  BrowseNext = NULL;

  if (!Browsing || !(BrowseLocalProtocols | BrowseRemoteProtocols))
    return;

  if ((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_CUPS)
  {
    if (BrowseSocket < 0)
    {
     /*
      * Create the broadcast socket...
      */

      if ((BrowseSocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
      {
	cupsdLogMessage(CUPSD_LOG_ERROR,
			"Unable to create broadcast socket - %s.",
			strerror(errno));
	BrowseLocalProtocols &= ~BROWSE_CUPS;
	BrowseRemoteProtocols &= ~BROWSE_CUPS;

	if (FatalErrors & CUPSD_FATAL_BROWSE)
	  cupsdEndProcess(getpid(), 0);
      }
    }

    if (BrowseSocket >= 0)
    {
     /*
      * Bind the socket to browse port...
      */

      memset(&addr, 0, sizeof(addr));
      addr.sin_addr.s_addr = htonl(INADDR_ANY);
      addr.sin_family      = AF_INET;
      addr.sin_port        = htons(BrowsePort);

      if (bind(BrowseSocket, (struct sockaddr *)&addr, sizeof(addr)))
      {
	cupsdLogMessage(CUPSD_LOG_ERROR,
			"Unable to bind broadcast socket - %s.",
			strerror(errno));

#ifdef WIN32
	closesocket(BrowseSocket);
#else
	close(BrowseSocket);
#endif /* WIN32 */

	BrowseSocket = -1;
	BrowseLocalProtocols &= ~BROWSE_CUPS;
	BrowseRemoteProtocols &= ~BROWSE_CUPS;

	if (FatalErrors & CUPSD_FATAL_BROWSE)
	  cupsdEndProcess(getpid(), 0);
      }
    }

    if (BrowseSocket >= 0)
    {
     /*
      * Set the "broadcast" flag...
      */

      val = 1;
      if (setsockopt(BrowseSocket, SOL_SOCKET, SO_BROADCAST, &val, sizeof(val)))
      {
	cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to set broadcast mode - %s.",
			strerror(errno));

#ifdef WIN32
	closesocket(BrowseSocket);
#else
	close(BrowseSocket);
#endif /* WIN32 */

	BrowseSocket = -1;
	BrowseLocalProtocols &= ~BROWSE_CUPS;
	BrowseRemoteProtocols &= ~BROWSE_CUPS;

	if (FatalErrors & CUPSD_FATAL_BROWSE)
	  cupsdEndProcess(getpid(), 0);
      }
    }

    if (BrowseSocket >= 0)
    {
     /*
      * Close the socket on exec...
      */

      fcntl(BrowseSocket, F_SETFD, fcntl(BrowseSocket, F_GETFD) | FD_CLOEXEC);

     /*
      * Finally, add the socket to the input selection set as needed...
      */

      if (BrowseRemoteProtocols & BROWSE_CUPS)
      {
       /*
	* We only listen if we want remote printers...
	*/

	cupsdAddSelect(BrowseSocket, (cupsd_selfunc_t)update_cups_browse,
		       NULL, NULL);
      }
    }
  }
  else
    BrowseSocket = -1;

#ifdef HAVE_DNSSD
  if ((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_DNSSD)
  {
    DNSServiceErrorType error;		/* Error from service creation */
    cupsd_listener_t	*lis;		/* Current listening socket */


   /*
    * First create a "master" connection for all registrations...
    */

    if ((error = DNSServiceCreateConnection(&DNSSDRef))
	    != kDNSServiceErr_NoError)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
		      "Unable to create master DNS-SD reference: %d", error);

      if (FatalErrors & CUPSD_FATAL_BROWSE)
	cupsdEndProcess(getpid(), 0);
    }
    else
    {
     /*
      * Add the master connection to the select list...
      */

      int fd = DNSServiceRefSockFD(DNSSDRef);

      fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);

      cupsdAddSelect(fd, (cupsd_selfunc_t)dnssdUpdate, NULL, NULL);

     /*
      * Then get the port we use for registrations.  If we are not listening
      * on any non-local ports, there is no sense sharing local printers via
      * Bonjour...
      */

      DNSSDPort = 0;

      for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners);
	   lis;
	   lis = (cupsd_listener_t *)cupsArrayNext(Listeners))
      {
	if (httpAddrLocalhost(&(lis->address)))
	  continue;

        DNSSDPort = _httpAddrPort(&(lis->address));
	break;
      }

     /*
      * Create an array to track the printers we share...
      */

      if (BrowseRemoteProtocols & BROWSE_DNSSD)
        DNSSDPrinters = cupsArrayNew((cups_array_func_t)dnssdComparePrinters,
	                             NULL);

     /*
      * Set the computer name and register the web interface...
      */

      cupsdUpdateDNSSDName();
    }
  }
#endif /* HAVE_DNSSD */

#ifdef HAVE_LIBSLP
  if ((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_SLP)
  {
   /*
    * Open SLP handle...
    */

    if (SLPOpen("en", SLP_FALSE, &BrowseSLPHandle) != SLP_OK)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Unable to open an SLP handle; disabling SLP browsing!");
      BrowseLocalProtocols &= ~BROWSE_SLP;
      BrowseRemoteProtocols &= ~BROWSE_SLP;
      BrowseSLPHandle = NULL;

      if (FatalErrors & CUPSD_FATAL_BROWSE)
	cupsdEndProcess(getpid(), 0);
    }

    BrowseSLPRefresh = 0;
  }
  else
    BrowseSLPHandle = NULL;
#endif /* HAVE_LIBSLP */

#ifdef HAVE_LDAP
  if ((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_LDAP)
  {
    if (!BrowseLDAPDN)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Need to set BrowseLDAPDN to use LDAP browsing!");
      BrowseLocalProtocols &= ~BROWSE_LDAP;
      BrowseRemoteProtocols &= ~BROWSE_LDAP;

      if (FatalErrors & CUPSD_FATAL_BROWSE)
	cupsdEndProcess(getpid(), 0);
    }
    else
    {
     /*
      * Open LDAP handle...
      */

      if ((BrowseLDAPHandle = ldap_connect()) == NULL &&
          (FatalErrors & CUPSD_FATAL_BROWSE))
	cupsdEndProcess(getpid(), 0);
    }

    BrowseLDAPRefresh = 0;
  }
#endif /* HAVE_LDAP */

 /*
  * Enable LPD and SMB printer sharing as needed through external programs...
  */

  if (BrowseLocalProtocols & BROWSE_LPD)
    update_lpd(1);

  if (BrowseLocalProtocols & BROWSE_SMB)
    update_smb(1);

 /*
  * Register the individual printers
  */

  for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
       p;
       p = (cupsd_printer_t *)cupsArrayNext(Printers))
    if (!(p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT |
                     CUPS_PRINTER_SCANNER)))
      cupsdRegisterPrinter(p);
}


/*
 * 'cupsdStartPolling()' - Start polling servers as needed.
 */

void
cupsdStartPolling(void)
{
  int			i;		/* Looping var */
  cupsd_dirsvc_poll_t	*pollp;		/* Current polling server */
  char			polld[1024];	/* Poll daemon path */
  char			sport[255];	/* Server port */
  char			bport[255];	/* Browser port */
  char			interval[255];	/* Poll interval */
  int			statusfds[2];	/* Status pipe */
  char			*argv[6];	/* Arguments */
  char			*envp[100];	/* Environment */


 /*
  * Don't do anything if we aren't polling...
  */

  if (NumPolled == 0 || BrowseSocket < 0)
  {
    PollPipe         = -1;
    PollStatusBuffer = NULL;
    return;
  }

 /*
  * Setup string arguments for polld, port and interval options.
  */

  snprintf(polld, sizeof(polld), "%s/daemon/cups-polld", ServerBin);

  sprintf(bport, "%d", BrowsePort);

  if (BrowseInterval)
    sprintf(interval, "%d", BrowseInterval);
  else
    strcpy(interval, "30");

  argv[0] = "cups-polld";
  argv[2] = sport;
  argv[3] = interval;
  argv[4] = bport;
  argv[5] = NULL;

  cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0])));

 /*
  * Create a pipe that receives the status messages from each
  * polling daemon...
  */

  if (cupsdOpenPipe(statusfds))
  {
    cupsdLogMessage(CUPSD_LOG_ERROR,
                    "Unable to create polling status pipes - %s.",
	            strerror(errno));
    PollPipe         = -1;
    PollStatusBuffer = NULL;
    return;
  }

  PollPipe         = statusfds[0];
  PollStatusBuffer = cupsdStatBufNew(PollPipe, "[Poll]");

 /*
  * Run each polling daemon, redirecting stderr to the polling pipe...
  */

  for (i = 0, pollp = Polled; i < NumPolled; i ++, pollp ++)
  {
    sprintf(sport, "%d", pollp->port);

    argv[1] = pollp->hostname;

    if (cupsdStartProcess(polld, argv, envp, -1, -1, statusfds[1], -1, -1,
                          0, DefaultProfile, NULL, &(pollp->pid)) < 0)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "cupsdStartPolling: Unable to fork polling daemon - %s",
                      strerror(errno));
      pollp->pid = 0;
      break;
    }
    else
      cupsdLogMessage(CUPSD_LOG_DEBUG,
                      "cupsdStartPolling: Started polling daemon for %s:%d, pid = %d",
                      pollp->hostname, pollp->port, pollp->pid);
  }

  close(statusfds[1]);

 /*
  * Finally, add the pipe to the input selection set...
  */

  cupsdAddSelect(PollPipe, (cupsd_selfunc_t)update_polling, NULL, NULL);
}


/*
 * 'cupsdStopBrowsing()' - Stop sending and receiving broadcast information.
 */

void
cupsdStopBrowsing(void)
{
  cupsd_printer_t	*p;		/* Current printer */


  if (!Browsing || !(BrowseLocalProtocols | BrowseRemoteProtocols))
    return;

 /*
  * De-register the individual printers
  */

  for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
       p;
       p = (cupsd_printer_t *)cupsArrayNext(Printers))
    if (!(p->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT |
                     CUPS_PRINTER_SCANNER)))
      cupsdDeregisterPrinter(p, 1);

 /*
  * Shut down browsing sockets...
  */

  if (((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_CUPS) &&
      BrowseSocket >= 0)
  {
   /*
    * Close the socket and remove it from the input selection set.
    */

#ifdef WIN32
    closesocket(BrowseSocket);
#else
    close(BrowseSocket);
#endif /* WIN32 */

    cupsdRemoveSelect(BrowseSocket);
    BrowseSocket = -1;
  }

#ifdef HAVE_DNSSD
  if ((BrowseLocalProtocols & BROWSE_DNSSD) && DNSSDRef)
    dnssdStop();
#endif /* HAVE_DNSSD */

#ifdef HAVE_LIBSLP
  if (((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_SLP) &&
      BrowseSLPHandle)
  {
   /*
    * Close SLP handle...
    */

    SLPClose(BrowseSLPHandle);
    BrowseSLPHandle = NULL;
  }
#endif /* HAVE_LIBSLP */

#ifdef HAVE_LDAP
  if (((BrowseLocalProtocols | BrowseRemoteProtocols) & BROWSE_LDAP) &&
      BrowseLDAPHandle)
  {
    ldap_dereg_ou(ServerName, BrowseLDAPDN);
    ldap_disconnect(BrowseLDAPHandle);
    BrowseLDAPHandle = NULL;
  }
#endif /* HAVE_OPENLDAP */

 /*
  * Disable LPD and SMB printer sharing as needed through external programs...
  */

  if (BrowseLocalProtocols & BROWSE_LPD)
    update_lpd(0);

  if (BrowseLocalProtocols & BROWSE_SMB)
    update_smb(0);
}


/*
 * 'cupsdStopPolling()' - Stop polling servers as needed.
 */

void
cupsdStopPolling(void)
{
  int			i;		/* Looping var */
  cupsd_dirsvc_poll_t	*pollp;		/* Current polling server */


  if (PollPipe >= 0)
  {
    cupsdStatBufDelete(PollStatusBuffer);
    close(PollPipe);

    cupsdRemoveSelect(PollPipe);

    PollPipe         = -1;
    PollStatusBuffer = NULL;
  }

  for (i = 0, pollp = Polled; i < NumPolled; i ++, pollp ++)
    if (pollp->pid)
      cupsdEndProcess(pollp->pid, 0);
}


#ifdef HAVE_DNSSD
/*
 * 'cupsdUpdateDNSSDName()' - Update the computer name we use for browsing...
 */

void
cupsdUpdateDNSSDName(void)
{
  DNSServiceErrorType error;		/* Error from service creation */
  char		webif[1024];		/* Web interface share name */
#  ifdef HAVE_SYSTEMCONFIGURATION
  SCDynamicStoreRef sc;			/* Context for dynamic store */
  CFDictionaryRef btmm;			/* Back-to-My-Mac domains */
  CFStringEncoding nameEncoding;	/* Encoding of computer name */
  CFStringRef	nameRef;		/* Host name CFString */
  char		nameBuffer[1024];	/* C-string buffer */
#  endif /* HAVE_SYSTEMCONFIGURATION */


 /*
  * Only share the web interface and printers when non-local listening is
  * enabled...
  */


  if (!DNSSDPort)
    return;

 /*
  * Get the computer name as a c-string...
  */

#  ifdef HAVE_SYSTEMCONFIGURATION
  sc = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("cupsd"), NULL, NULL);

  if (sc)
  {
   /*
    * Get the computer name from the dynamic store...
    */

    cupsdClearString(&DNSSDComputerName);

    if ((nameRef = SCDynamicStoreCopyComputerName(sc, &nameEncoding)) != NULL)
    {
      if (CFStringGetCString(nameRef, nameBuffer, sizeof(nameBuffer),
			     kCFStringEncodingUTF8))
      {
        cupsdLogMessage(CUPSD_LOG_DEBUG,
	                "Dynamic store computer name is \"%s\".", nameBuffer);
	cupsdSetString(&DNSSDComputerName, nameBuffer);
      }

      CFRelease(nameRef);
    }

    if (!DNSSDComputerName)
    {
     /*
      * Use the ServerName instead...
      */

      cupsdLogMessage(CUPSD_LOG_DEBUG,
                      "Using ServerName \"%s\" as computer name.", ServerName);
      cupsdSetString(&DNSSDComputerName, ServerName);
    }

   /*
    * Get the local hostname from the dynamic store...
    */

    cupsdClearString(&DNSSDHostName);

    if ((nameRef = SCDynamicStoreCopyLocalHostName(sc)) != NULL)
    {
      if (CFStringGetCString(nameRef, nameBuffer, sizeof(nameBuffer),
			     kCFStringEncodingUTF8))
      {
        cupsdLogMessage(CUPSD_LOG_DEBUG,
	                "Dynamic store host name is \"%s\".", nameBuffer);
	cupsdSetString(&DNSSDHostName, nameBuffer);
      }

      CFRelease(nameRef);
    }

    if (!DNSSDHostName)
    {
     /*
      * Use the ServerName instead...
      */

      cupsdLogMessage(CUPSD_LOG_DEBUG,
                      "Using ServerName \"%s\" as host name.", ServerName);
      cupsdSetString(&DNSSDHostName, ServerName);
    }

   /*
    * Get any Back-to-My-Mac domains and add them as aliases...
    */

    cupsdFreeAliases(DNSSDAlias);
    DNSSDAlias = NULL;

    btmm = SCDynamicStoreCopyValue(sc, CFSTR("Setup:/Network/BackToMyMac"));
    if (btmm && CFGetTypeID(btmm) == CFDictionaryGetTypeID())
    {
      cupsdLogMessage(CUPSD_LOG_DEBUG, "%d Back to My Mac aliases to add.",
		      (int)CFDictionaryGetCount(btmm));
      CFDictionaryApplyFunction(btmm, dnssdAddAlias, NULL);
    }
    else if (btmm)
      cupsdLogMessage(CUPSD_LOG_ERROR,
		      "Bad Back to My Mac data in dynamic store!");
    else
      cupsdLogMessage(CUPSD_LOG_DEBUG, "No Back to My Mac aliases to add.");

    if (btmm)
      CFRelease(btmm);

    CFRelease(sc);
  }
  else
#  endif /* HAVE_SYSTEMCONFIGURATION */
  {
    cupsdSetString(&DNSSDComputerName, ServerName);
    cupsdSetString(&DNSSDHostName, ServerName);
  }

 /*
  * Then (re)register the web interface if enabled...
  */

  if (BrowseWebIF)
  {
    if (DNSSDComputerName)
      snprintf(webif, sizeof(webif), "CUPS @ %s", DNSSDComputerName);
    else
      strlcpy(webif, "CUPS Web Interface", sizeof(webif));

    if (WebIFRef)
      DNSServiceRefDeallocate(WebIFRef);

    WebIFRef = DNSSDRef;
    if ((error = DNSServiceRegister(&WebIFRef,
				    kDNSServiceFlagsShareConnection,
				    0, webif, "_http._tcp", NULL,
				    NULL, htons(DNSSDPort), 7,
				    "\006path=/", dnssdRegisterCallback,
				    NULL)) != kDNSServiceErr_NoError)
      cupsdLogMessage(CUPSD_LOG_ERROR,
		      "DNS-SD web interface registration failed: %d", error);
  }
}
#endif /* HAVE_DNSSD */


#ifdef HAVE_LDAP
/*
 * 'cupsdUpdateLDAPBrowse()' - Scan for new printers via LDAP...
 */

void
cupsdUpdateLDAPBrowse(void)
{
  char		uri[HTTP_MAX_URI],	/* Printer URI */
		host[HTTP_MAX_URI],	/* Hostname */
		resource[HTTP_MAX_URI],	/* Resource path */
		location[1024],		/* Printer location */
		info[1024],		/* Printer information */
		make_model[1024],	/* Printer make and model */
		type_num[30];		/* Printer type number */
  int		type;			/* Printer type */
  int		rc;			/* LDAP status */
  int		limit;			/* Size limit */
  LDAPMessage	*res,			/* LDAP search results */
		  *e;			/* Current entry from search */

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "UpdateLDAPBrowse: %s", ServerName);

  BrowseLDAPRefresh = time(NULL) + BrowseInterval;

 /*
  * Reconnect if LDAP Handle is invalid...
  */

  if (! BrowseLDAPHandle)
  {
    ldap_reconnect();
    return;
  }

 /*
  * Search for cups printers in LDAP directory...
  */

  rc = ldap_search_rec(BrowseLDAPHandle, BrowseLDAPDN, LDAP_SCOPE_SUBTREE,
                       "(objectclass=cupsPrinter)", (char **)ldap_attrs, 0, &res);

 /*
  * If ldap search was successfull then exit function
  * and temporary disable LDAP updates...
  */

  if (rc != LDAP_SUCCESS)
  {
    if (BrowseLDAPUpdate && ((rc == LDAP_SERVER_DOWN) || (rc == LDAP_CONNECT_ERROR)))
    {
      BrowseLDAPUpdate = FALSE;
      cupsdLogMessage(CUPSD_LOG_INFO,
                      "LDAP update temporary disabled");
    }
    return;
  }

 /*
  * If LDAP updates were disabled, we will reenable them...
  */

  if (! BrowseLDAPUpdate)
  {
    BrowseLDAPUpdate = TRUE;
    cupsdLogMessage(CUPSD_LOG_INFO,
                    "LDAP update enabled");
  }

 /*
  * Count LDAP entries and return if no entry exist...
  */

  limit = ldap_count_entries(BrowseLDAPHandle, res);
  cupsdLogMessage(CUPSD_LOG_DEBUG2, "LDAP search returned %d entries", limit);
  if (limit < 1)
  {
    ldap_freeres(res);
    return;
  }

 /*
  * Loop through the available printers...
  */

  for (e = ldap_first_entry(BrowseLDAPHandle, res);
       e;
       e = ldap_next_entry(BrowseLDAPHandle, e))
  {
   /*
    * Get the required values from this entry...
    */

    if (ldap_getval_firststring(BrowseLDAPHandle, e,
                                "printerDescription", info, sizeof(info)) == -1)
      continue;

    if (ldap_getval_firststring(BrowseLDAPHandle, e,
                                "printerLocation", location, sizeof(location)) == -1)
      continue;

    if (ldap_getval_firststring(BrowseLDAPHandle, e,
                                "printerMakeAndModel", make_model, sizeof(make_model)) == -1)
      continue;

    if (ldap_getval_firststring(BrowseLDAPHandle, e,
                                "printerType", type_num, sizeof(type_num)) == -1)
      continue;

    type = atoi(type_num);

    if (ldap_getval_firststring(BrowseLDAPHandle, e,
                                "printerURI", uri, sizeof(uri)) == -1)
      continue;

   /*
    * Process the entry as browse data...
    */

    if (!is_local_queue(uri, host, sizeof(host), resource, sizeof(resource)))
      process_browse_data(uri, host, resource, type, IPP_PRINTER_IDLE,
                          location, info, make_model, 0, NULL);

  }

  ldap_freeres(res);
}
#endif /* HAVE_LDAP */


#ifdef HAVE_LIBSLP
/*
 * 'cupsdUpdateSLPBrowse()' - Get browsing information via SLP.
 */

void
cupsdUpdateSLPBrowse(void)
{
  slpsrvurl_t	*s,			/* Temporary list of service URLs */
		*next;			/* Next service in list */
  cupsd_printer_t p;			/* Printer information */
  const char	*uri;			/* Pointer to printer URI */
  char		host[HTTP_MAX_URI],	/* Host portion of URI */
		resource[HTTP_MAX_URI];	/* Resource portion of URI */


 /*
  * Reset the refresh time...
  */

  BrowseSLPRefresh = time(NULL) + BrowseInterval;

 /*
  * Poll for remote printers using SLP...
  */

  s = NULL;

  SLPFindSrvs(BrowseSLPHandle, SLP_CUPS_SRVTYPE, "", "",
	      slp_url_callback, &s);

 /*
  * Loop through the list of available printers...
  */

  for (; s; s = next)
  {
   /*
    * Save the "next" pointer...
    */

    next = s->next;

   /*
    * Load a cupsd_printer_t structure with the SLP service attributes...
    */

    SLPFindAttrs(BrowseSLPHandle, s->url, "", "", slp_attr_callback, &p);

   /*
    * Process this printer entry...
    */

    uri = s->url + SLP_CUPS_SRVLEN + 1;

    if (!strncmp(uri, "http://", 7) || !strncmp(uri, "ipp://", 6))
    {
     /*
      * Pull the URI apart to see if this is a local or remote printer...
      */

      if (!is_local_queue(uri, host, sizeof(host), resource, sizeof(resource)))
        process_browse_data(uri, host, resource, p.type, IPP_PRINTER_IDLE,
	                    p.location,  p.info, p.make_model, 0, NULL);
    }

   /*
    * Free this listing...
    */

    cupsdClearString(&p.info);
    cupsdClearString(&p.location);
    cupsdClearString(&p.make_model);

    free(s);
  }
}
#endif /* HAVE_LIBSLP */


/*
 * 'dequote()' - Remote quotes from a string.
 */

static char *				/* O - Dequoted string */
dequote(char       *d,			/* I - Destination string */
        const char *s,			/* I - Source string */
	int        dlen)		/* I - Destination length */
{
  char	*dptr;				/* Pointer into destination */


  if (s)
  {
    for (dptr = d, dlen --; *s && dlen > 0; s ++)
      if (*s != '\"')
      {
	*dptr++ = *s;
	dlen --;
      }

    *dptr = '\0';
  }
  else
    *d = '\0';

  return (d);
}


#ifdef HAVE_DNSSD
#  ifdef HAVE_COREFOUNDATION
/*
 * 'dnssdAddAlias()' - Add a DNS-SD alias name.
 */

static void
dnssdAddAlias(const void *key,		/* I - Key */
              const void *value,	/* I - Value (domain) */
	      void       *context)	/* I - Unused */
{
  char	valueStr[1024],			/* Domain string */
	hostname[1024],			/* Complete hostname */
	*hostptr;			/* Pointer into hostname */


  (void)key;
  (void)context;

  if (CFGetTypeID((CFStringRef)value) == CFStringGetTypeID() &&
      CFStringGetCString((CFStringRef)value, valueStr, sizeof(valueStr),
                         kCFStringEncodingUTF8))
  {
    snprintf(hostname, sizeof(hostname), "%s.%s", DNSSDHostName, valueStr);
    hostptr = hostname + strlen(hostname) - 1;
    if (*hostptr == '.')
      *hostptr = '\0';			/* Strip trailing dot */

    if (!DNSSDAlias)
      DNSSDAlias = cupsArrayNew(NULL, NULL);

    cupsdAddAlias(DNSSDAlias, hostname);
    cupsdLogMessage(CUPSD_LOG_DEBUG, "Added Back to My Mac ServerAlias %s",
		    hostname);
  }
  else
    cupsdLogMessage(CUPSD_LOG_ERROR,
                    "Bad Back to My Mac domain in dynamic store!");
}
#  endif /* HAVE_COREFOUNDATION */


/*
 * 'dnssdBuildTxtRecord()' - Build a TXT record from printer info.
 */

static char *				/* O - TXT record */
dnssdBuildTxtRecord(
    int             *txt_len,		/* O - TXT record length */
    cupsd_printer_t *p,			/* I - Printer information */
    int             for_lpd)		/* I - 1 = LPD, 0 = IPP */
{
  int		i;			/* Looping var */
  char		admin_hostname[256],	/* .local hostname for admin page */
		adminurl_str[256],	/* URL for the admin page */
		type_str[32],		/* Type to string buffer */
		state_str[32],		/* State to string buffer */
		rp_str[1024],		/* Queue name string buffer */
		air_str[1024],		/* auth-info-required string buffer */
		*keyvalue[32][2];	/* Table of key/value pairs */


 /*
  * Load up the key value pairs...
  */

  i = 0;

  keyvalue[i  ][0] = "txtvers";
  keyvalue[i++][1] = "1";

  keyvalue[i  ][0] = "qtotal";
  keyvalue[i++][1] = "1";

  keyvalue[i  ][0] = "rp";
  keyvalue[i++][1] = rp_str;
  if (for_lpd)
    strlcpy(rp_str, p->name, sizeof(rp_str));
  else
    snprintf(rp_str, sizeof(rp_str), "%s/%s",
	     (p->type & CUPS_PRINTER_CLASS) ? "classes" : "printers", p->name);

  keyvalue[i  ][0] = "ty";
  keyvalue[i++][1] = p->make_model ? p->make_model : "Unknown";

  snprintf(admin_hostname, sizeof(admin_hostname), "%s.local.", DNSSDHostName);
  httpAssembleURIf(HTTP_URI_CODING_ALL, adminurl_str, sizeof(adminurl_str),
                   "http", NULL, admin_hostname, DNSSDPort, "/%s/%s",
		   (p->type & CUPS_PRINTER_CLASS) ? "classes" : "printers",
		   p->name);
  keyvalue[i  ][0] = "adminurl";
  keyvalue[i++][1] = adminurl_str;

  keyvalue[i  ][0] = "note";
  keyvalue[i++][1] = p->location ? p->location : "";

  keyvalue[i  ][0] = "priority";
  keyvalue[i++][1] = for_lpd ? "100" : "0";

  keyvalue[i  ][0] = "product";
  keyvalue[i++][1] = p->pc && p->pc->product ? p->pc->product : "Unknown";

  keyvalue[i  ][0] = "pdl";
  keyvalue[i++][1] = p->pdl ? p->pdl : "application/postscript";

  if (get_auth_info_required(p, air_str, sizeof(air_str)))
  {
    keyvalue[i  ][0] = "air";
    keyvalue[i++][1] = air_str;
  }

  keyvalue[i  ][0] = "UUID";
  keyvalue[i++][1] = p->uuid + 9;

#ifdef HAVE_SSL
  keyvalue[i  ][0] = "TLS";
  keyvalue[i++][1] = "1.2";
#endif /* HAVE_SSL */

  keyvalue[i  ][0] = "Transparent";
  keyvalue[i++][1] = "F";

  keyvalue[i  ][0] = "Binary";
  keyvalue[i++][1] = "F";

  keyvalue[i  ][0] = "Fax";
  keyvalue[i++][1] = (p->type & CUPS_PRINTER_FAX) ? "T" : "F";

  keyvalue[i  ][0] = "Color";
  keyvalue[i++][1] = (p->type & CUPS_PRINTER_COLOR) ? "T" : "F";

  keyvalue[i  ][0] = "Duplex";
  keyvalue[i++][1] = (p->type & CUPS_PRINTER_DUPLEX) ? "T" : "F";

  keyvalue[i  ][0] = "Staple";
  keyvalue[i++][1] = (p->type & CUPS_PRINTER_STAPLE) ? "T" : "F";

  keyvalue[i  ][0] = "Copies";
  keyvalue[i++][1] = (p->type & CUPS_PRINTER_COPIES) ? "T" : "F";

  keyvalue[i  ][0] = "Collate";
  keyvalue[i++][1] = (p->type & CUPS_PRINTER_COLLATE) ? "T" : "F";

  keyvalue[i  ][0] = "Punch";
  keyvalue[i++][1] = (p->type & CUPS_PRINTER_PUNCH) ? "T" : "F";

  keyvalue[i  ][0] = "Bind";
  keyvalue[i++][1] = (p->type & CUPS_PRINTER_BIND) ? "T" : "F";

  keyvalue[i  ][0] = "Sort";
  keyvalue[i++][1] = (p->type & CUPS_PRINTER_SORT) ? "T" : "F";

  keyvalue[i  ][0] = "Scan";
  keyvalue[i++][1] = (p->type & CUPS_PRINTER_MFP) ? "T" : "F";

  snprintf(type_str, sizeof(type_str), "0x%X", p->type | CUPS_PRINTER_REMOTE);
  snprintf(state_str, sizeof(state_str), "%d", p->state);

  keyvalue[i  ][0] = "printer-state";
  keyvalue[i++][1] = state_str;

  keyvalue[i  ][0] = "printer-type";
  keyvalue[i++][1] = type_str;

 /*
  * Then pack them into a proper txt record...
  */

  return (dnssdPackTxtRecord(txt_len, keyvalue, i));
}


/*
 * 'dnssdComparePrinters()' - Compare the registered names of two printers.
 */

static int				/* O - Result of comparison */
dnssdComparePrinters(cupsd_printer_t *a,/* I - First printer */
                     cupsd_printer_t *b)/* I - Second printer */
{
  return (_cups_strcasecmp(a->reg_name, b->reg_name));
}


/*
 * 'dnssdDeregisterPrinter()' - Stop sending broadcast information for a
 *                              printer.
 */

static void
dnssdDeregisterPrinter(
    cupsd_printer_t *p)			/* I - Printer */
{
  cupsdLogMessage(CUPSD_LOG_DEBUG2, "dnssdDeregisterPrinter(%s)", p->name);

 /*
  * Closing the socket deregisters the service
  */

  if (p->ipp_ref)
  {
    DNSServiceRefDeallocate(p->ipp_ref);
    p->ipp_ref = NULL;
  }

  if (p->ipp_txt)
  {
   /*
    * p->ipp_txt is malloc'd, not _cupsStrAlloc'd...
    */

    free(p->ipp_txt);
    p->ipp_txt = NULL;
  }

  if (p->printer_ref)
  {
    DNSServiceRefDeallocate(p->printer_ref);
    p->printer_ref = NULL;
  }

  if (p->printer_txt)
  {
   /*
    * p->printer_txt is malloc'd, not _cupsStrAlloc'd...
    */

    free(p->printer_txt);
    p->printer_txt = NULL;
  }

 /*
  * Remove the printer from the array of DNS-SD printers, then clear the
  * registered name...
  */

  cupsArrayRemove(DNSSDPrinters, p);
  cupsdClearString(&p->reg_name);
}


/*
 * 'dnssdPackTxtRecord()' - Pack an array of key/value pairs into the
 *                          TXT record format.
 */

static char *				/* O - TXT record */
dnssdPackTxtRecord(int  *txt_len,	/* O - TXT record length */
		   char *keyvalue[][2],	/* I - Table of key value pairs */
		   int  count)		/* I - Items in table */
{
  int  i;				/* Looping var */
  int  length;				/* Length of TXT record */
  int  length2;				/* Length of value */
  char *txtRecord;			/* TXT record buffer */
  char *cursor;				/* Looping pointer */


 /*
  * Calculate the buffer size
  */

  if (count <= 0)
    return (NULL);

  for (length = i = 0; i < count; i++)
    length += 1 + strlen(keyvalue[i][0]) +
	      (keyvalue[i][1] ? 1 + strlen(keyvalue[i][1]) : 0);

 /*
  * Allocate and fill it
  */

  txtRecord = malloc(length);
  if (txtRecord)
  {
    *txt_len = length;

    for (cursor = txtRecord, i = 0; i < count; i++)
    {
     /*
      * Drop in the p-string style length byte followed by the data
      */

      length  = strlen(keyvalue[i][0]);
      length2 = keyvalue[i][1] ? 1 + strlen(keyvalue[i][1]) : 0;

      *cursor++ = (unsigned char)(length + length2);

      memcpy(cursor, keyvalue[i][0], length);
      cursor += length;

      if (length2)
      {
        length2 --;
	*cursor++ = '=';
	memcpy(cursor, keyvalue[i][1], length2);
	cursor += length2;
      }
    }
  }

  return (txtRecord);
}


/*
 * 'dnssdRegisterCallback()' - DNSServiceRegister callback.
 */

static void
dnssdRegisterCallback(
    DNSServiceRef	sdRef,		/* I - DNS Service reference */
    DNSServiceFlags	flags,		/* I - Reserved for future use */
    DNSServiceErrorType	errorCode,	/* I - Error code */
    const char		*name,     	/* I - Service name */
    const char		*regtype,  	/* I - Service type */
    const char		*domain,   	/* I - Domain. ".local" for now */
    void		*context)	/* I - User-defined context */
{
  cupsd_printer_t *p = (cupsd_printer_t *)context;
					/* Current printer */


  (void)sdRef;
  (void)flags;
  (void)domain;

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "dnssdRegisterCallback(%s, %s) for %s (%s)",
                  name, regtype, p ? p->name : "Web Interface",
		  p ? (p->reg_name ? p->reg_name : "(null)") : "NA");

  if (errorCode)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR,
		    "DNSServiceRegister failed with error %d", (int)errorCode);
    return;
  }
  else if (p && (!p->reg_name || _cups_strcasecmp(name, p->reg_name)))
  {
    cupsdLogMessage(CUPSD_LOG_INFO, "Using service name \"%s\" for \"%s\"",
                    name, p->name);

    cupsArrayRemove(DNSSDPrinters, p);
    cupsdSetString(&p->reg_name, name);
    cupsArrayAdd(DNSSDPrinters, p);

    LastEvent |= CUPSD_EVENT_PRINTER_MODIFIED;
  }
}


/*
 * 'dnssdRegisterPrinter()' - Start sending broadcast information for a printer
 *		              or update the broadcast contents.
 */

static void
dnssdRegisterPrinter(cupsd_printer_t *p)/* I - Printer */
{
  DNSServiceErrorType	se;		/* dnssd errors */
  char			*ipp_txt,	/* IPP TXT record buffer */
			*printer_txt,	/* LPD TXT record buffer */
			name[1024],	/* Service name */
			*nameptr;	/* Pointer into name */
  int			ipp_len,	/* IPP TXT record length */
			printer_len,	/* LPD TXT record length */
			printer_port;	/* LPD port number */
  const char		*regtype;	/* Registration type */


  cupsdLogMessage(CUPSD_LOG_DEBUG2, "dnssdRegisterPrinter(%s) %s", p->name,
                  !p->ipp_ref ? "new" : "update");

 /*
  * If per-printer sharing was just disabled make sure we're not
  * registered before returning.
  */

  if (!p->shared)
  {
    dnssdDeregisterPrinter(p);
    return;
  }

 /*
  * The registered name takes the form of "<printer-info> @ <computer name>"...
  */

  if (p->info && strlen(p->info) > 0)
  {
    if (DNSSDComputerName)
      snprintf(name, sizeof(name), "%s @ %s", p->info, DNSSDComputerName);
    else
      strlcpy(name, p->info, sizeof(name));
  }
  else if (DNSSDComputerName)
    snprintf(name, sizeof(name), "%s @ %s", p->name, DNSSDComputerName);
  else
    strlcpy(name, p->name, sizeof(name));

 /*
  * If an existing printer was renamed, unregister it and start over...
  */

  if (p->reg_name && strcmp(p->reg_name, name))
    dnssdDeregisterPrinter(p);

  if (!p->reg_name)
  {
    cupsdSetString(&p->reg_name, name);
    cupsArrayAdd(DNSSDPrinters, p);
  }

 /*
  * Register IPP and (optionally) LPD...
  */

  ipp_len = 0;				/* anti-compiler-warning-code */
  ipp_txt = dnssdBuildTxtRecord(&ipp_len, p, 0);

  if (p->ipp_ref &&
      (ipp_len != p->ipp_len || memcmp(ipp_txt, p->ipp_txt, ipp_len)))
  {
   /*
    * Update the existing registration...
    */

    /* A TTL of 0 means use record's original value (Radar 3176248) */
    if ((se = DNSServiceUpdateRecord(p->ipp_ref, NULL, 0, ipp_len, ipp_txt,
				     0)) == kDNSServiceErr_NoError)
    {
      if (p->ipp_txt)
	free(p->ipp_txt);

      p->ipp_txt = ipp_txt;
      p->ipp_len = ipp_len;
      ipp_txt    = NULL;
    }
    else
    {
     /*
      * Failed to update record, lets close this reference and move on...
      */

      cupsdLogMessage(CUPSD_LOG_ERROR,
		      "Unable to update IPP DNS-SD record for %s - %d", p->name,
		      se);

      DNSServiceRefDeallocate(p->ipp_ref);
      p->ipp_ref = NULL;
    }
  }

  if (!p->ipp_ref)
  {
   /*
    * Initial registration.  Use the _fax-ipp regtype for fax queues...
    */

    regtype = (p->type & CUPS_PRINTER_FAX) ? "_fax-ipp._tcp" : DNSSDRegType;

    cupsdLogMessage(CUPSD_LOG_DEBUG,
		    "Registering DNS-SD printer %s with name \"%s\" and "
		    "type \"%s\"", p->name, name, regtype);

   /*
    * Register the queue, dropping characters as needed until we succeed...
    */

    nameptr = name + strlen(name);

    do
    {
      p->ipp_ref = DNSSDRef;
      if ((se = DNSServiceRegister(&p->ipp_ref, kDNSServiceFlagsShareConnection,
                                   0, name, regtype, NULL, NULL,
				   htons(DNSSDPort), ipp_len, ipp_txt,
				   dnssdRegisterCallback,
				   p)) == kDNSServiceErr_BadParam)
      {
       /*
        * Name is too long, drop trailing characters, taking into account
	* UTF-8 encoding...
	*/

        nameptr --;

        while (nameptr > name && (*nameptr & 0xc0) == 0x80)
	  nameptr --;

        if (nameptr > name)
          *nameptr = '\0';
      }
    }
    while (se == kDNSServiceErr_BadParam && nameptr > name);

    if (se == kDNSServiceErr_NoError)
    {
      p->ipp_txt = ipp_txt;
      p->ipp_len = ipp_len;
      ipp_txt    = NULL;
    }
    else
      cupsdLogMessage(CUPSD_LOG_WARN,
                      "DNS-SD IPP registration of \"%s\" failed: %d",
		      p->name, se);
  }

  if (ipp_txt)
    free(ipp_txt);

  if (BrowseLocalProtocols & BROWSE_LPD)
  {
    printer_len  = 0;			/* anti-compiler-warning-code */
    printer_port = 515;
    printer_txt  = dnssdBuildTxtRecord(&printer_len, p, 1);
  }
  else
  {
    printer_len  = 0;
    printer_port = 0;
    printer_txt  = NULL;
  }

  if (p->printer_ref &&
      (printer_len != p->printer_len ||
       memcmp(printer_txt, p->printer_txt, printer_len)))
  {
   /*
    * Update the existing registration...
    */

    /* A TTL of 0 means use record's original value (Radar 3176248) */
    if ((se = DNSServiceUpdateRecord(p->printer_ref, NULL, 0, printer_len,
				     printer_txt,
				     0)) == kDNSServiceErr_NoError)
    {
      if (p->printer_txt)
	free(p->printer_txt);

      p->printer_txt = printer_txt;
      p->printer_len = printer_len;
      printer_txt    = NULL;
    }
    else
    {
     /*
      * Failed to update record, lets close this reference and move on...
      */

      cupsdLogMessage(CUPSD_LOG_ERROR,
		      "Unable to update LPD DNS-SD record for %s - %d",
		      p->name, se);

      DNSServiceRefDeallocate(p->printer_ref);
      p->printer_ref = NULL;
    }
  }

  if (!p->printer_ref)
  {
   /*
    * Initial registration...
    */

    cupsdLogMessage(CUPSD_LOG_DEBUG,
		    "Registering DNS-SD printer %s with name \"%s\" and "
		    "type \"_printer._tcp\"", p->name, name);

    p->printer_ref = DNSSDRef;
    if ((se = DNSServiceRegister(&p->printer_ref,
				 kDNSServiceFlagsShareConnection,
				 0, name, "_printer._tcp", NULL, NULL,
				 htons(printer_port), printer_len, printer_txt,
				 dnssdRegisterCallback,
				 p)) == kDNSServiceErr_NoError)
    {
      p->printer_txt = printer_txt;
      p->printer_len = printer_len;
      printer_txt    = NULL;
    }
    else
      cupsdLogMessage(CUPSD_LOG_WARN,
		      "DNS-SD LPD registration of \"%s\" failed: %d",
		      p->name, se);
  }

  if (printer_txt)
    free(printer_txt);
}


/*
 * 'dnssdStop()' - Stop all DNS-SD registrations.
 */

static void
dnssdStop(void)
{
  cupsd_printer_t	*p;		/* Current printer */


 /*
  * De-register the individual printers
  */

  for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
       p;
       p = (cupsd_printer_t *)cupsArrayNext(Printers))
    dnssdDeregisterPrinter(p);

 /*
  * Shutdown the rest of the service refs...
  */

  if (WebIFRef)
  {
    DNSServiceRefDeallocate(WebIFRef);
    WebIFRef = NULL;
  }

  if (RemoteRef)
  {
    DNSServiceRefDeallocate(RemoteRef);
    RemoteRef = NULL;
  }

  cupsdRemoveSelect(DNSServiceRefSockFD(DNSSDRef));

  DNSServiceRefDeallocate(DNSSDRef);
  DNSSDRef = NULL;

  cupsArrayDelete(DNSSDPrinters);
  DNSSDPrinters = NULL;

  DNSSDPort = 0;
}


/*
 * 'dnssdUpdate()' - Handle DNS-SD queries.
 */

static void
dnssdUpdate(void)
{
  DNSServiceErrorType	sdErr;		/* Service discovery error */


  if ((sdErr = DNSServiceProcessResult(DNSSDRef)) != kDNSServiceErr_NoError)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR,
                    "DNS Service Discovery registration error %d!",
	            sdErr);
    dnssdStop();
  }
}
#endif /* HAVE_DNSSD */


/*
 * 'get_auth_info_required()' - Get the auth-info-required value to advertise.
 */

static char *				/* O - String or NULL if none */
get_auth_info_required(
    cupsd_printer_t *p,			/* I - Printer */
    char            *buffer,		/* I - Value buffer */
    size_t          bufsize)		/* I - Size of value buffer */
{
  cupsd_location_t *auth;		/* Pointer to authentication element */
  char		resource[1024];		/* Printer/class resource path */


 /*
  * If auth-info-required is set for this printer, return that...
  */

  if (p->num_auth_info_required > 0 && strcmp(p->auth_info_required[0], "none"))
  {
    int		i;			/* Looping var */
    char	*bufptr;		/* Pointer into buffer */

    for (i = 0, bufptr = buffer; i < p->num_auth_info_required; i ++)
    {
      if (bufptr >= (buffer + bufsize - 2))
	break;

      if (i)
	*bufptr++ = ',';

      strlcpy(bufptr, p->auth_info_required[i], bufsize - (bufptr - buffer));
      bufptr += strlen(bufptr);
    }

    return (buffer);
  }

 /*
  * Figure out the authentication data requirements to advertise...
  */

  if (p->type & CUPS_PRINTER_CLASS)
    snprintf(resource, sizeof(resource), "/classes/%s", p->name);
  else
    snprintf(resource, sizeof(resource), "/printers/%s", p->name);

  if ((auth = cupsdFindBest(resource, HTTP_POST)) == NULL ||
      auth->type == CUPSD_AUTH_NONE)
    auth = cupsdFindPolicyOp(p->op_policy_ptr, IPP_PRINT_JOB);

  if (auth)
  {
    int	auth_type;			/* Authentication type */

    if ((auth_type = auth->type) == CUPSD_AUTH_DEFAULT)
      auth_type = DefaultAuthType;

    switch (auth_type)
    {
      case CUPSD_AUTH_NONE :
          return (NULL);

      case CUPSD_AUTH_NEGOTIATE :
	  strlcpy(buffer, "negotiate", bufsize);
	  break;

      default :
	  strlcpy(buffer, "username,password", bufsize);
	  break;
    }

    return (buffer);
  }

  return ("none");
}


#ifdef __APPLE__
/*
 * 'get_hostconfig()' - Get an /etc/hostconfig service setting.
 */

static int				/* O - 1 for YES or AUTOMATIC, 0 for NO */
get_hostconfig(const char *name)	/* I - Name of service */
{
  cups_file_t	*fp;			/* Hostconfig file */
  char		line[1024],		/* Line from file */
		*ptr;			/* Pointer to value */
  int		state = 1;		/* State of service */


 /*
  * Try opening the /etc/hostconfig file; if we can't open it, assume that
  * the service is enabled/auto.
  */

  if ((fp = cupsFileOpen("/etc/hostconfig", "r")) != NULL)
  {
   /*
    * Read lines from the file until we find the service...
    */

    while (cupsFileGets(fp, line, sizeof(line)))
    {
      if (line[0] == '#' || (ptr = strchr(line, '=')) == NULL)
        continue;

      *ptr++ = '\0';

      if (!_cups_strcasecmp(line, name))
      {
       /*
        * Found the service, see if it is set to "-NO-"...
	*/

	if (!_cups_strncasecmp(ptr, "-NO-", 4))
	  state = 0;
        break;
      }
    }

    cupsFileClose(fp);
  }

  return (state);
}
#endif /* __APPLE__ */


/*
 * 'is_local_queue()' - Determine whether the URI points at a local queue.
 */

static int				/* O - 1 = local, 0 = remote, -1 = bad URI */
is_local_queue(const char *uri,		/* I - Printer URI */
               char       *host,	/* O - Host string */
	       int        hostlen,	/* I - Length of host buffer */
               char       *resource,	/* O - Resource string */
	       int        resourcelen)	/* I - Length of resource buffer */
{
  char		scheme[32],		/* Scheme portion of URI */
		username[HTTP_MAX_URI];	/* Username portion of URI */
  int		port;			/* Port portion of URI */
  cupsd_netif_t	*iface;			/* Network interface */


 /*
  * Pull the URI apart to see if this is a local or remote printer...
  */

  if (httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme),
                      username, sizeof(username), host, hostlen, &port,
		      resource, resourcelen) < HTTP_URI_OK)
    return (-1);

  DEBUG_printf(("host=\"%s\", ServerName=\"%s\"\n", host, ServerName));

 /*
  * Check for local server addresses...
  */

  if (!_cups_strcasecmp(host, ServerName) && port == LocalPort)
    return (1);

  cupsdNetIFUpdate();

  for (iface = (cupsd_netif_t *)cupsArrayFirst(NetIFList);
       iface;
       iface = (cupsd_netif_t *)cupsArrayNext(NetIFList))
    if (!_cups_strcasecmp(host, iface->hostname) && port == iface->port)
      return (1);

 /*
  * If we get here, the printer is remote...
  */

  return (0);
}


/*
 * 'process_browse_data()' - Process new browse data.
 */

static void
process_browse_data(
    const char    *uri,			/* I - URI of printer/class */
    const char    *host,		/* I - Hostname */
    const char    *resource,		/* I - Resource path */
    cups_ptype_t  type,			/* I - Printer type */
    ipp_pstate_t  state,		/* I - Printer state */
    const char    *location,		/* I - Printer location */
    const char    *info,		/* I - Printer information */
    const char    *make_model,		/* I - Printer make and model */
    int		  num_attrs,		/* I - Number of attributes */
    cups_option_t *attrs)		/* I - Attributes */
{
  int		i;			/* Looping var */
  int		update;			/* Update printer attributes? */
  char		finaluri[HTTP_MAX_URI],	/* Final URI for printer */
		name[IPP_MAX_NAME],	/* Name of printer */
		newname[IPP_MAX_NAME],	/* New name of printer */
		*hptr,			/* Pointer into hostname */
		*sptr;			/* Pointer into ServerName */
  const char	*shortname;		/* Short queue name (queue) */
  char		local_make_model[IPP_MAX_NAME];
					/* Local make and model */
  cupsd_printer_t *p;			/* Printer information */
  const char	*ipp_options,		/* ipp-options value */
		*lease_duration,	/* lease-duration value */
		*uuid;			/* uuid value */
  int		is_class;		/* Is this queue a class? */


  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "process_browse_data(uri=\"%s\", host=\"%s\", "
		  "resource=\"%s\", type=%x, state=%d, location=\"%s\", "
		  "info=\"%s\", make_model=\"%s\", num_attrs=%d, attrs=%p)",
		  uri, host, resource, type, state,
		  location ? location : "(nil)", info ? info : "(nil)",
		  make_model ? make_model : "(nil)", num_attrs, attrs);

 /*
  * Determine if the URI contains any illegal characters in it...
  */

  if (strncmp(uri, "ipp://", 6) || !host[0] ||
      (strncmp(resource, "/printers/", 10) &&
       strncmp(resource, "/classes/", 9)))
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Bad printer URI in browse data: %s", uri);
    return;
  }

  if (strchr(resource, '?') ||
      (!strncmp(resource, "/printers/", 10) && strchr(resource + 10, '/')) ||
      (!strncmp(resource, "/classes/", 9) && strchr(resource + 9, '/')))
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Bad resource in browse data: %s",
                    resource);
    return;
  }

 /*
  * OK, this isn't a local printer; add any remote options...
  */

  ipp_options = cupsGetOption("ipp-options", num_attrs, attrs);

  if (BrowseRemoteOptions)
  {
    if (BrowseRemoteOptions[0] == '?')
    {
     /*
      * Override server-supplied options...
      */

      snprintf(finaluri, sizeof(finaluri), "%s%s", uri, BrowseRemoteOptions);
    }
    else if (ipp_options)
    {
     /*
      * Combine the server and local options...
      */

      snprintf(finaluri, sizeof(finaluri), "%s?%s+%s", uri, ipp_options,
               BrowseRemoteOptions);
    }
    else
    {
     /*
      * Just use the local options...
      */

      snprintf(finaluri, sizeof(finaluri), "%s?%s", uri, BrowseRemoteOptions);
    }

    uri = finaluri;
  }
  else if (ipp_options)
  {
   /*
    * Just use the server-supplied options...
    */

    snprintf(finaluri, sizeof(finaluri), "%s?%s", uri, ipp_options);
    uri = finaluri;
  }

 /*
  * See if we already have it listed in the Printers list, and add it if not...
  */

  type     |= CUPS_PRINTER_REMOTE | CUPS_PRINTER_DISCOVERED;
  type     &= ~CUPS_PRINTER_IMPLICIT;
  update   = 0;
  hptr     = strchr(host, '.');
  sptr     = strchr(ServerName, '.');
  is_class = type & CUPS_PRINTER_CLASS;
  uuid     = cupsGetOption("uuid", num_attrs, attrs);

  if (!ServerNameIsIP && sptr != NULL && hptr != NULL)
  {
   /*
    * Strip the common domain name components...
    */

    while (hptr != NULL)
    {
      if (!_cups_strcasecmp(hptr, sptr))
      {
        *hptr = '\0';
	break;
      }
      else
        hptr = strchr(hptr + 1, '.');
    }
  }

  if (is_class)
  {
   /*
    * Remote destination is a class...
    */

    if (!strncmp(resource, "/classes/", 9))
      snprintf(name, sizeof(name), "%s@%s", resource + 9, host);
    else
      return;

    shortname = resource + 9;
  }
  else
  {
   /*
    * Remote destination is a printer...
    */

    if (!strncmp(resource, "/printers/", 10))
      snprintf(name, sizeof(name), "%s@%s", resource + 10, host);
    else
      return;

    shortname = resource + 10;
  }

  if (hptr && !*hptr)
    *hptr = '.';			/* Resource FQDN */

  if ((p = cupsdFindDest(name)) == NULL && BrowseShortNames)
  {
   /*
    * Long name doesn't exist, try short name...
    */

    cupsdLogMessage(CUPSD_LOG_DEBUG, "process_browse_data: %s not found...",
                    name);

    if ((p = cupsdFindDest(shortname)) == NULL)
    {
     /*
      * Short name doesn't exist, use it for this shared queue.
      */

      cupsdLogMessage(CUPSD_LOG_DEBUG2, "process_browse_data: %s not found...",
		      shortname);
      strlcpy(name, shortname, sizeof(name));
    }
    else
    {
     /*
      * Short name exists...
      */

      cupsdLogMessage(CUPSD_LOG_DEBUG2,
                      "process_browse_data: %s found, type=%x, hostname=%s...",
		      shortname, p->type, p->hostname ? p->hostname : "(nil)");

      if (p->type & CUPS_PRINTER_IMPLICIT)
        p = NULL;			/* Don't replace implicit classes */
      else if (p->hostname && _cups_strcasecmp(p->hostname, host))
      {
       /*
	* Short name exists but is for a different host.  If this is a remote
	* queue, rename it and use the long name...
	*/

	if (p->type & CUPS_PRINTER_REMOTE)
	{
	  cupsdLogMessage(CUPSD_LOG_DEBUG,
			  "Renamed remote %s \"%s\" to \"%s@%s\"...",
			  is_class ? "class" : "printer", p->name, p->name,
			  p->hostname);
	  cupsdAddEvent(CUPSD_EVENT_PRINTER_DELETED, p, NULL,
			"%s \'%s\' deleted by directory services.",
			is_class ? "Class" : "Printer", p->name);

	  snprintf(newname, sizeof(newname), "%s@%s", p->name, p->hostname);
	  cupsdRenamePrinter(p, newname);

	  cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED, p, NULL,
			"%s \'%s\' added by directory services.",
			is_class ? "Class" : "Printer", p->name);
	}

       /*
        * Force creation with long name...
	*/

	p = NULL;
      }
    }
  }
  else if (p)
    cupsdLogMessage(CUPSD_LOG_DEBUG2,
		    "process_browse_data: %s found, type=%x, hostname=%s...",
		    name, p->type, p->hostname ? p->hostname : "(nil)");

  if (!p)
  {
   /*
    * Queue doesn't exist; add it...
    */

    if (is_class)
      p = cupsdAddClass(name);
    else
      p = cupsdAddPrinter(name);

    if (!p)
      return;

    cupsdClearString(&(p->hostname));

    cupsdLogMessage(CUPSD_LOG_DEBUG, "Added remote %s \"%s\"...",
                    is_class ? "class" : "printer", name);

    cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED, p, NULL,
		  "%s \'%s\' added by directory services.",
		  is_class ? "Class" : "Printer", name);

   /*
    * Force the URI to point to the real server...
    */

    p->type      = type & ~CUPS_PRINTER_REJECTING;
    p->accepting = 1;

    cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP);
  }

  if (!p->hostname)
  {
   /*
    * Hostname not set, so this must be a cached remote printer
    * that was created for a pending print job...
    */

    cupsdSetString(&p->hostname, host);
    cupsdSetString(&p->uri, uri);
    cupsdSetString(&p->device_uri, uri);
    update = 1;

    cupsdMarkDirty(CUPSD_DIRTY_REMOTE);
  }

 /*
  * Update the state...
  */

  p->state       = state;
  p->browse_time = time(NULL);

  if ((lease_duration = cupsGetOption("lease-duration", num_attrs,
                                      attrs)) != NULL)
  {
   /*
    * Grab the lease-duration for the browse data; anything less then 1
    * second or more than 1 week gets the default BrowseTimeout...
    */

    i = atoi(lease_duration);
    if (i < 1 || i > 604800)
      i = BrowseTimeout;

    p->browse_expire = p->browse_time + i;
  }
  else
    p->browse_expire = p->browse_time + BrowseTimeout;

  if (type & CUPS_PRINTER_REJECTING)
  {
    type &= ~CUPS_PRINTER_REJECTING;

    if (p->accepting)
    {
      update       = 1;
      p->accepting = 0;
    }
  }
  else if (!p->accepting)
  {
    update       = 1;
    p->accepting = 1;
  }

  if (p->type != type)
  {
    p->type = type;
    update  = 1;
  }

  if (uuid && strcmp(p->uuid, uuid))
  {
    cupsdSetString(&p->uuid, uuid);
    update = 1;
  }

  if (location && (!p->location || strcmp(p->location, location)))
  {
    cupsdSetString(&p->location, location);
    update = 1;
  }

  if (info && (!p->info || strcmp(p->info, info)))
  {
    cupsdSetString(&p->info, info);
    update = 1;

    cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP | CUPSD_DIRTY_REMOTE);
  }

  if (!make_model || !make_model[0])
  {
    if (is_class)
      snprintf(local_make_model, sizeof(local_make_model),
               "Remote Class on %s", host);
    else
      snprintf(local_make_model, sizeof(local_make_model),
               "Remote Printer on %s", host);
  }
  else
    snprintf(local_make_model, sizeof(local_make_model),
             "%s on %s", make_model, host);

  if (!p->make_model || strcmp(p->make_model, local_make_model))
  {
    cupsdSetString(&p->make_model, local_make_model);
    update = 1;
  }

  if (p->num_options)
  {
    if (!update && !(type & CUPS_PRINTER_DELETE))
    {
     /*
      * See if we need to update the attributes...
      */

      if (p->num_options != num_attrs)
	update = 1;
      else
      {
	for (i = 0; i < num_attrs; i ++)
          if (strcmp(attrs[i].name, p->options[i].name) ||
	      (!attrs[i].value != !p->options[i].value) ||
	      (attrs[i].value && strcmp(attrs[i].value, p->options[i].value)))
          {
	    update = 1;
	    break;
          }
      }
    }

   /*
    * Free the old options...
    */

    cupsFreeOptions(p->num_options, p->options);
  }

  p->num_options = num_attrs;
  p->options     = attrs;

  if (type & CUPS_PRINTER_DELETE)
  {
    cupsdAddEvent(CUPSD_EVENT_PRINTER_DELETED, p, NULL,
                  "%s \'%s\' deleted by directory services.",
		  is_class ? "Class" : "Printer", p->name);

    cupsdExpireSubscriptions(p, NULL);

    cupsdDeletePrinter(p, 1);
    cupsdUpdateImplicitClasses();
    cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP | CUPSD_DIRTY_REMOTE);
  }
  else if (update)
  {
    cupsdSetPrinterAttrs(p);
    cupsdUpdateImplicitClasses();
  }

 /*
  * See if we have a default printer...  If not, make the first network
  * default printer the default.
  */

  if (DefaultPrinter == NULL && Printers != NULL && UseNetworkDefault)
  {
   /*
    * Find the first network default printer and use it...
    */

    for (p = (cupsd_printer_t *)cupsArrayFirst(Printers);
         p;
	 p = (cupsd_printer_t *)cupsArrayNext(Printers))
      if (p->type & CUPS_PRINTER_DEFAULT)
      {
        DefaultPrinter = p;
        cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP | CUPSD_DIRTY_REMOTE);
	break;
      }
  }

 /*
  * Do auto-classing if needed...
  */

  process_implicit_classes();
}


/*
 * 'process_implicit_classes()' - Create/update implicit classes as needed.
 */

static void
process_implicit_classes(void)
{
  int		i;			/* Looping var */
  int		update;			/* Update printer attributes? */
  char		name[IPP_MAX_NAME],	/* Name of printer */
		*hptr;			/* Pointer into hostname */
  cupsd_printer_t *p,			/* Printer information */
		*pclass,		/* Printer class */
		*first;			/* First printer in class */
  int		offset,			/* Offset of name */
		len;			/* Length of name */


  if (!ImplicitClasses || !Printers)
    return;

 /*
  * Loop through all available printers and create classes as needed...
  */

  for (p = (cupsd_printer_t *)cupsArrayFirst(Printers), len = 0, offset = 0,
           update = 0, pclass = NULL, first = NULL;
       p != NULL;
       p = (cupsd_printer_t *)cupsArrayNext(Printers))
  {
   /*
    * Skip implicit classes...
    */

    if (p->type & CUPS_PRINTER_IMPLICIT)
    {
      len = 0;
      continue;
    }

   /*
    * If len == 0, get the length of this printer name up to the "@"
    * sign (if any).
    */

    cupsArraySave(Printers);

    if (len > 0 &&
	!_cups_strncasecmp(p->name, name + offset, len) &&
	(p->name[len] == '\0' || p->name[len] == '@'))
    {
     /*
      * We have more than one printer with the same name; see if
      * we have a class, and if this printer is a member...
      */

      if (pclass && _cups_strcasecmp(pclass->name, name))
      {
	if (update)
	  cupsdSetPrinterAttrs(pclass);

	update = 0;
	pclass = NULL;
      }

      if (!pclass && (pclass = cupsdFindDest(name)) == NULL)
      {
       /*
	* Need to add the class...
	*/

	pclass = cupsdAddPrinter(name);
	cupsArrayAdd(ImplicitPrinters, pclass);

	pclass->type      |= CUPS_PRINTER_IMPLICIT;
	pclass->accepting = 1;
	pclass->state     = IPP_PRINTER_IDLE;

        cupsdSetString(&pclass->location, p->location);
        cupsdSetString(&pclass->info, p->info);

        cupsdSetString(&pclass->job_sheets[0], p->job_sheets[0]);
        cupsdSetString(&pclass->job_sheets[1], p->job_sheets[1]);

        update = 1;

	cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP | CUPSD_DIRTY_REMOTE);

        cupsdLogMessage(CUPSD_LOG_DEBUG, "Added implicit class \"%s\"...",
	                name);
	cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED, p, NULL,
                      "Implicit class \'%s\' added by directory services.",
		      name);
      }

      if (first != NULL)
      {
        for (i = 0; i < pclass->num_printers; i ++)
	  if (pclass->printers[i] == first)
	    break;

        if (i >= pclass->num_printers)
	{
	  first->in_implicit_class = 1;
	  cupsdAddPrinterToClass(pclass, first);
        }

	first = NULL;
      }

      for (i = 0; i < pclass->num_printers; i ++)
	if (pclass->printers[i] == p)
	  break;

      if (i >= pclass->num_printers)
      {
	p->in_implicit_class = 1;
	cupsdAddPrinterToClass(pclass, p);
	update = 1;
      }
    }
    else
    {
     /*
      * First time around; just get name length and mark it as first
      * in the list...
      */

      if ((hptr = strchr(p->name, '@')) != NULL)
	len = hptr - p->name;
      else
	len = strlen(p->name);

      if (len >= sizeof(name))
      {
       /*
	* If the printer name length somehow is greater than we normally allow,
	* skip this printer...
	*/

	len = 0;
	cupsArrayRestore(Printers);
	continue;
      }

      strncpy(name, p->name, len);
      name[len] = '\0';
      offset    = 0;

      if ((first = (hptr ? cupsdFindDest(name) : p)) != NULL &&
	  !(first->type & CUPS_PRINTER_IMPLICIT))
      {
       /*
	* Can't use same name as a local printer; add "Any" to the
	* front of the name, unless we have explicitly disabled
	* the "ImplicitAnyClasses"...
	*/

        if (ImplicitAnyClasses && len < (sizeof(name) - 4))
	{
	 /*
	  * Add "Any" to the class name...
	  */

          strcpy(name, "Any");
          strncpy(name + 3, p->name, len);
	  name[len + 3] = '\0';
	  offset        = 3;
	}
	else
	{
	 /*
	  * Don't create an implicit class if we have a local printer
	  * with the same name...
	  */

	  len = 0;
          cupsArrayRestore(Printers);
	  continue;
	}
      }

      first = p;
    }

    cupsArrayRestore(Printers);
  }

 /*
  * Update the last printer class as needed...
  */

  if (pclass && update)
    cupsdSetPrinterAttrs(pclass);
}


/*
 * 'send_cups_browse()' - Send new browsing information using the CUPS
 *                        protocol.
 */

static void
send_cups_browse(cupsd_printer_t *p)	/* I - Printer to send */
{
  int			i;		/* Looping var */
  cups_ptype_t		type;		/* Printer type */
  cupsd_dirsvc_addr_t	*b;		/* Browse address */
  int			bytes;		/* Length of packet */
  char			packet[1453],	/* Browse data packet */
			uri[1024],	/* Printer URI */
			location[1024],	/* printer-location */
			info[1024],	/* printer-info */
			make_model[1024],
					/* printer-make-and-model */
			air[1024];	/* auth-info-required */
  cupsd_netif_t		*iface;		/* Network interface */


 /*
  * Figure out the printer type value...
  */

  type = p->type | CUPS_PRINTER_REMOTE;

  if (!p->accepting)
    type |= CUPS_PRINTER_REJECTING;

  if (p == DefaultPrinter)
    type |= CUPS_PRINTER_DEFAULT;

 /*
  * Remove quotes from printer-info, printer-location, and
  * printer-make-and-model attributes...
  */

  dequote(location, p->location, sizeof(location));
  dequote(info, p->info, sizeof(info));

  if (p->make_model)
    dequote(make_model, p->make_model, sizeof(make_model));
  else if (p->type & CUPS_PRINTER_CLASS)
  {
    if (p->num_printers > 0 && p->printers[0]->make_model)
      strlcpy(make_model, p->printers[0]->make_model, sizeof(make_model));
    else
      strlcpy(make_model, "Local Printer Class", sizeof(make_model));
  }
  else if (p->raw)
    strlcpy(make_model, "Local Raw Printer", sizeof(make_model));
  else
    strlcpy(make_model, "Local System V Printer", sizeof(make_model));

  if (get_auth_info_required(p, packet, sizeof(packet)))
    snprintf(air, sizeof(air), " auth-info-required=%s", packet);
  else
    air[0] = '\0';

 /*
  * Send a packet to each browse address...
  */

  for (i = NumBrowsers, b = Browsers; i > 0; i --, b ++)
    if (b->iface[0])
    {
     /*
      * Send the browse packet to one or more interfaces...
      */

      if (!strcmp(b->iface, "*"))
      {
       /*
        * Send to all local interfaces...
	*/

        cupsdNetIFUpdate();

	for (iface = (cupsd_netif_t *)cupsArrayFirst(NetIFList);
	     iface;
	     iface = (cupsd_netif_t *)cupsArrayNext(NetIFList))
	{
	 /*
	  * Only send to local, IPv4 interfaces...
	  */

	  if (!iface->is_local || !iface->port ||
	      iface->address.addr.sa_family != AF_INET)
	    continue;

	  httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
	                   iface->hostname, iface->port,
			   (p->type & CUPS_PRINTER_CLASS) ? "/classes/%s" :
			                                    "/printers/%s",
			   p->name);
	  snprintf(packet, sizeof(packet),
	           "%x %x %s \"%s\" \"%s\" \"%s\" %s%s uuid=%s\n",
        	   type, p->state, uri, location, info, make_model,
		   p->browse_attrs ? p->browse_attrs : "", air, p->uuid);

	  bytes = strlen(packet);

	  cupsdLogMessage(CUPSD_LOG_DEBUG2,
	                  "cupsdSendBrowseList: (%d bytes to \"%s\") %s", bytes,
        	          iface->name, packet);

          iface->broadcast.ipv4.sin_port = htons(BrowsePort);

	  sendto(BrowseSocket, packet, bytes, 0,
		 (struct sockaddr *)&(iface->broadcast),
		 httpAddrLength(&(iface->broadcast)));
        }
      }
      else if ((iface = cupsdNetIFFind(b->iface)) != NULL)
      {
       /*
        * Send to the named interface using the IPv4 address...
	*/

        while (iface)
	  if (strcmp(b->iface, iface->name))
	  {
	    iface = NULL;
	    break;
	  }
	  else if (iface->address.addr.sa_family == AF_INET && iface->port)
	    break;
	  else
            iface = (cupsd_netif_t *)cupsArrayNext(NetIFList);

        if (iface)
	{
	  httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
	                   iface->hostname, iface->port,
			   (p->type & CUPS_PRINTER_CLASS) ? "/classes/%s" :
			                                    "/printers/%s",
			   p->name);
	  snprintf(packet, sizeof(packet),
	           "%x %x %s \"%s\" \"%s\" \"%s\" %s%s uuid=%s\n",
        	   type, p->state, uri, location, info, make_model,
		   p->browse_attrs ? p->browse_attrs : "", air, p->uuid);

	  bytes = strlen(packet);

	  cupsdLogMessage(CUPSD_LOG_DEBUG2,
	                  "cupsdSendBrowseList: (%d bytes to \"%s\") %s", bytes,
        	          iface->name, packet);

          iface->broadcast.ipv4.sin_port = htons(BrowsePort);

	  sendto(BrowseSocket, packet, bytes, 0,
		 (struct sockaddr *)&(iface->broadcast),
		 httpAddrLength(&(iface->broadcast)));
        }
      }
    }
    else
    {
     /*
      * Send the browse packet to the indicated address using
      * the default server name...
      */

      snprintf(packet, sizeof(packet),
               "%x %x %s \"%s\" \"%s\" \"%s\" %s%s uuid=%s\n",
       	       type, p->state, p->uri, location, info, make_model,
	       p->browse_attrs ? p->browse_attrs : "", air, p->uuid);

      bytes = strlen(packet);
      cupsdLogMessage(CUPSD_LOG_DEBUG2,
                      "cupsdSendBrowseList: (%d bytes) %s", bytes, packet);

      if (sendto(BrowseSocket, packet, bytes, 0,
		 (struct sockaddr *)&(b->to),
		 httpAddrLength(&(b->to))) <= 0)
      {
       /*
        * Unable to send browse packet, so remove this address from the
	* list...
	*/

	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "cupsdSendBrowseList: sendto failed for browser "
			"%d - %s.",
	                (int)(b - Browsers + 1), strerror(errno));

        if (i > 1)
	  memmove(b, b + 1, (i - 1) * sizeof(cupsd_dirsvc_addr_t));

	b --;
	NumBrowsers --;
      }
    }
}


#ifdef HAVE_LDAP
/*
 * 'ldap_search_rec()' - LDAP Search with reconnect
 */

static int				/* O - Return code */
ldap_search_rec(LDAP        *ld,	/* I - LDAP handler */
                char        *base,	/* I - Base dn */
                int         scope,	/* I - LDAP search scope */
                char        *filter,	/* I - Filter string */
                char        *attrs[],	/* I - Requested attributes */
                int         attrsonly,	/* I - Return only attributes? */
                LDAPMessage **res)	/* I - LDAP handler */
{
  int	rc;				/* Return code */
  LDAP  *ldr;				/* LDAP handler after reconnect */


#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
  rc = ldap_search_ext_s(ld, base, scope, filter, attrs, attrsonly, NULL, NULL,
                         NULL, LDAP_NO_LIMIT, res);
#  else
  rc = ldap_search_s(ld, base, scope, filter, attrs, attrsonly, res);
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */

 /*
  * If we have a connection problem try again...
  */

  if (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR,
                    "LDAP search failed with status %d: %s",
                     rc, ldap_err2string(rc));
    cupsdLogMessage(CUPSD_LOG_INFO,
                    "We try the LDAP search once again after reconnecting to "
		    "the server");
    ldap_freeres(*res);
    ldr = ldap_reconnect();

#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
    rc = ldap_search_ext_s(ldr, base, scope, filter, attrs, attrsonly, NULL,
                           NULL, NULL, LDAP_NO_LIMIT, res);
#  else
    rc = ldap_search_s(ldr, base, scope, filter, attrs, attrsonly, res);
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */
  }

  if (rc == LDAP_NO_SUCH_OBJECT)
    cupsdLogMessage(CUPSD_LOG_DEBUG,
                    "ldap_search_rec: LDAP entry/object not found");
  else if (rc != LDAP_SUCCESS)
    cupsdLogMessage(CUPSD_LOG_ERROR,
                    "ldap_search_rec: LDAP search failed with status %d: %s",
                     rc, ldap_err2string(rc));

  if (rc != LDAP_SUCCESS)
    ldap_freeres(*res);

  return (rc);
}


/*
 * 'ldap_freeres()' - Free LDAPMessage
 */

static void
ldap_freeres(LDAPMessage *entry)	/* I - LDAP handler */
{
  int	rc;				/* Return value */


  rc = ldap_msgfree(entry);
  if (rc == -1)
    cupsdLogMessage(CUPSD_LOG_WARN, "Can't free LDAPMessage!");
  else if (rc == 0)
    cupsdLogMessage(CUPSD_LOG_DEBUG2, "Freeing LDAPMessage was unnecessary");
}


/*
 * 'ldap_getval_char()' - Get first LDAP value and convert to string
 */

static int				/* O - Return code */
ldap_getval_firststring(
    LDAP          *ld,			/* I - LDAP handler */
    LDAPMessage   *entry,		/* I - LDAP message or search result */
    char          *attr,		/* I - the wanted attribute  */
    char          *retval,		/* O - String to return */
    unsigned long maxsize)		/* I - Max string size */
{
  char			*dn;		/* LDAP DN */
  int			rc = 0;		/* Return code */
#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
  struct berval		**bval;		/* LDAP value array */
  unsigned long		size;		/* String size */


 /*
  * Get value from LDAPMessage...
  */

  if ((bval = ldap_get_values_len(ld, entry, attr)) == NULL)
  {
    rc = -1;
    dn = ldap_get_dn(ld, entry);
    cupsdLogMessage(CUPSD_LOG_WARN,
                    "Failed to get LDAP value %s for %s!",
                    attr, dn);
    ldap_memfree(dn);
  }
  else
  {
   /*
    * Check size and copy value into our string...
    */

    size = maxsize;
    if (size < (bval[0]->bv_len + 1))
    {
      rc = -1;
      dn = ldap_get_dn(ld, entry);
      cupsdLogMessage(CUPSD_LOG_WARN,
                      "Attribute %s is too big! (dn: %s)",
                      attr, dn);
      ldap_memfree(dn);
    }
    else
      size = bval[0]->bv_len + 1;

    strlcpy(retval, bval[0]->bv_val, size);
    ldap_value_free_len(bval);
  }
#  else
  char	**value;			/* LDAP value */

 /*
  * Get value from LDAPMessage...
  */

  if ((value = (char **)ldap_get_values(ld, entry, attr)) == NULL)
  {
    rc = -1;
    dn = ldap_get_dn(ld, entry);
    cupsdLogMessage(CUPSD_LOG_WARN, "Failed to get LDAP value %s for %s!",
                    attr, dn);
    ldap_memfree(dn);
  }
  else
  {
    strlcpy(retval, *value, maxsize);
    ldap_value_free(value);
  }
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */

  return (rc);
}


/*
 * 'send_ldap_ou()' - Send LDAP ou registrations.
 */

static void
send_ldap_ou(char *ou,			/* I - Servername/ou to register */
             char *basedn,		/* I - Our base dn */
             char *descstring)		/* I - Description for ou */
{
  int           i;                      /* Looping var... */
  LDAPMod       mods[3];                /* The 3 attributes we will be adding */
  LDAPMod       *pmods[4];              /* Pointers to the 3 attributes + NULL */
  LDAPMessage   *res,                   /* Search result token */
		*e;			/* Current entry from search */
  int           rc;                     /* LDAP status */
  int           rcmod;                  /* LDAP status for modifications */
  char          dn[1024],               /* DN of the organizational unit we are adding */
                *desc[2],               /* Change records */
                *ou_value[2];
  char		old_desc[1024];		/* Old description */
  static const char * const objectClass_values[] =
		{			/* The 2 objectClass's we use in */
		  "top",		/* our LDAP entries              */
		  "organizationalUnit",
		  NULL
		};
  static const char * const ou_attrs[] =/* CUPS LDAP attributes */
		{
		  "description",
		  NULL
		};


  cupsdLogMessage(CUPSD_LOG_DEBUG2, "send_ldap_ou: %s", ou);

 /*
  * Reconnect if LDAP Handle is invalid...
  */

  if (!BrowseLDAPHandle)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG2,
                    "send_ldap_ou: LDAP Handle is invalid. Try reconnecting...");
    ldap_reconnect();
    return;
  }

 /*
  * Prepare ldap search...
  */

  snprintf(dn, sizeof(dn), "ou=%s, %s", ou, basedn);
  cupsdLogMessage(CUPSD_LOG_DEBUG2, "send_ldap_ou: dn=\"%s\"", dn);

  ou_value[0] = ou;
  ou_value[1] = NULL;
  desc[0]     = descstring;
  desc[1]     = NULL;

  mods[0].mod_type   = "ou";
  mods[0].mod_values = ou_value;
  mods[1].mod_type   = "description";
  mods[1].mod_values = desc;
  mods[2].mod_type   = "objectClass";
  mods[2].mod_values = (char **)objectClass_values;

  rc = ldap_search_rec(BrowseLDAPHandle, dn, LDAP_SCOPE_BASE, NULL,
                       (char **)ou_attrs, 0, &res);

 /*
  * If ldap search was not successfull then exit function...
  */

  if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_OBJECT)
    return;

 /*
  * Check if we need to insert or update the LDAP entry...
  */

  if (ldap_count_entries(BrowseLDAPHandle, res) > 0 &&
      rc != LDAP_NO_SUCH_OBJECT)
  {
   /*
    * Printserver has already been registered, check if
    * modification is required...
    */

    e = ldap_first_entry(BrowseLDAPHandle, res);

   /*
    * Get the required values from this entry...
    */

    if (ldap_getval_firststring(BrowseLDAPHandle, e, "description", old_desc,
                                sizeof(old_desc)) == -1)
      old_desc[0] = '\0';

   /*
    * Check if modification is required...
    */

    if ( strcmp(desc[0], old_desc) == 0 )
    {
     /*
      * LDAP entry for the printer exists.
      * Printer has already been registered,
      * no modifications required...
      */
      cupsdLogMessage(CUPSD_LOG_DEBUG2,
                      "send_ldap_ou: No updates required for %s", ou);
    }
    else
    {

      cupsdLogMessage(CUPSD_LOG_DEBUG2,
                      "send_ldap_ou: Replace entry for %s", ou);

      for (i = 0; i < 3; i ++)
      {
        pmods[i]         = mods + i;
        pmods[i]->mod_op = LDAP_MOD_REPLACE;
      }
      pmods[i] = NULL;

#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
      if ((rcmod = ldap_modify_ext_s(BrowseLDAPHandle, dn, pmods, NULL,
                                     NULL)) != LDAP_SUCCESS)
#  else
      if ((rcmod = ldap_modify_s(BrowseLDAPHandle, dn, pmods)) != LDAP_SUCCESS)
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */
      {
        cupsdLogMessage(CUPSD_LOG_ERROR,
                        "LDAP modify for %s failed with status %d: %s",
                        ou, rcmod, ldap_err2string(rcmod));
        if (rcmod == LDAP_SERVER_DOWN)
          ldap_reconnect();
      }
    }
  }
  else
  {
   /*
    * Printserver has never been registered,
    * add registration...
    */

    cupsdLogMessage(CUPSD_LOG_DEBUG2,
                    "send_ldap_ou: Add entry for %s", ou);

    for (i = 0; i < 3; i ++)
    {
      pmods[i]         = mods + i;
      pmods[i]->mod_op = LDAP_MOD_ADD;
    }
    pmods[i] = NULL;

#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
    if ((rcmod = ldap_add_ext_s(BrowseLDAPHandle, dn, pmods, NULL,
                                NULL)) != LDAP_SUCCESS)
#  else
    if ((rcmod = ldap_add_s(BrowseLDAPHandle, dn, pmods)) != LDAP_SUCCESS)
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "LDAP add for %s failed with status %d: %s",
                      ou, rcmod, ldap_err2string(rcmod));
      if (rcmod == LDAP_SERVER_DOWN)
        ldap_reconnect();
    }
  }

  if (rc == LDAP_SUCCESS)
    ldap_freeres(res);
}


/*
 * 'send_ldap_browse()' - Send LDAP printer registrations.
 */

static void
send_ldap_browse(cupsd_printer_t *p)	/* I - Printer to register */
{
  int		i;			/* Looping var... */
  LDAPMod	mods[7];		/* The 7 attributes we will be adding */
  LDAPMod	*pmods[8];		/* Pointers to the 7 attributes + NULL */
  LDAPMessage	*res,			/* Search result token */
		*e;			/* Current entry from search */
  char		*cn_value[2],		/* Change records */
		*uri[2],
		*info[2],
		*location[2],
		*make_model[2],
		*type[2],
		typestring[255],	/* String to hold printer-type */
		dn[1024];		/* DN of the printer we are adding */
  int		rc;			/* LDAP status */
  int		rcmod;			/* LDAP status for modifications */
  char		old_uri[HTTP_MAX_URI],	/* Printer URI */
		old_location[1024],	/* Printer location */
		old_info[1024],		/* Printer information */
		old_make_model[1024],	/* Printer make and model */
		old_type_string[30];	/* Temporary type number */
  int		old_type;		/* Printer type */
  static const char * const objectClass_values[] =
		{			/* The 3 objectClass's we use in */
		  "top",		/* our LDAP entries              */
		  "device",
		  "cupsPrinter",
		  NULL
		};


  cupsdLogMessage(CUPSD_LOG_DEBUG2, "send_ldap_browse: %s", p->name);

 /*
  * Exit function if LDAP updates has been disabled...
  */

  if (!BrowseLDAPUpdate)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG2,
                    "send_ldap_browse: Updates temporary disabled; "
		    "skipping...");
    return;
  }

 /*
  * Reconnect if LDAP Handle is invalid...
  */

  if (!BrowseLDAPHandle)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG2,
                    "send_ldap_browse: LDAP Handle is invalid. Try "
		    "reconnecting...");
    ldap_reconnect();
    return;
  }

 /*
  * Everything in ldap is ** so we fudge around it...
  */

  sprintf(typestring, "%u", p->type);

  cn_value[0]   = p->name;
  cn_value[1]   = NULL;
  info[0]       = p->info ? p->info : "Unknown";
  info[1]       = NULL;
  location[0]   = p->location ? p->location : "Unknown";
  location[1]   = NULL;
  make_model[0] = p->make_model ? p->make_model : "Unknown";
  make_model[1] = NULL;
  type[0]       = typestring;
  type[1]       = NULL;
  uri[0]        = p->uri;
  uri[1]        = NULL;

 /*
  * Get ldap entry for printer ...
  */

  snprintf(dn, sizeof(dn), "cn=%s, ou=%s, %s", p->name, ServerName,
           BrowseLDAPDN);
  cupsdLogMessage(CUPSD_LOG_DEBUG2, "send_ldap_browse: dn=\"%s\"", dn);

  rc = ldap_search_rec(BrowseLDAPHandle, dn, LDAP_SCOPE_BASE, NULL,
                       (char **)ldap_attrs, 0, &res);

 /*
  * If ldap search was not successfull then exit function
  * and temporary disable LDAP updates...
  */

  if (rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_OBJECT)
  {
    if (BrowseLDAPUpdate &&
        (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR))
    {
      BrowseLDAPUpdate = FALSE;
      cupsdLogMessage(CUPSD_LOG_INFO,
                      "LDAP update temporary disabled");
    }

    return;
  }

 /*
  * Fill modification array...
  */

  mods[0].mod_type   = "cn";
  mods[0].mod_values = cn_value;
  mods[1].mod_type   = "printerDescription";
  mods[1].mod_values = info;
  mods[2].mod_type   = "printerURI";
  mods[2].mod_values = uri;
  mods[3].mod_type   = "printerLocation";
  mods[3].mod_values = location;
  mods[4].mod_type   = "printerMakeAndModel";
  mods[4].mod_values = make_model;
  mods[5].mod_type   = "printerType";
  mods[5].mod_values = type;
  mods[6].mod_type   = "objectClass";
  mods[6].mod_values = (char **)objectClass_values;

 /*
  * Check if we need to insert or update the LDAP entry...
  */

  if (ldap_count_entries(BrowseLDAPHandle, res) > 0 &&
      rc != LDAP_NO_SUCH_OBJECT)
  {
   /*
    * Printer has already been registered, check if
    * modification is required...
    */

    e = ldap_first_entry(BrowseLDAPHandle, res);

   /*
    * Get the required values from this entry...
    */

    if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerDescription",
                                old_info, sizeof(old_info)) == -1)
      old_info[0] = '\0';

    if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerLocation",
                                old_location, sizeof(old_location)) == -1)
      old_info[0] = '\0';

    if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerMakeAndModel",
                                old_make_model, sizeof(old_make_model)) == -1)
      old_info[0] = '\0';

    if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerType",
                                old_type_string, sizeof(old_type_string)) == -1)
      old_info[0] = '\0';

    old_type = atoi(old_type_string);

    if (ldap_getval_firststring(BrowseLDAPHandle, e, "printerURI", old_uri,
                                sizeof(old_uri)) == -1)
      old_info[0] = '\0';

   /*
    * Check if modification is required...
    */

    if (!strcmp(info[0], old_info) && !strcmp(uri[0], old_uri) &&
        !strcmp(location[0], old_location) &&
	!strcmp(make_model[0], old_make_model) && p->type == old_type)
    {
     /*
      * LDAP entry for the printer exists. Printer has already been registered,
      * no modifications required...
      */

      cupsdLogMessage(CUPSD_LOG_DEBUG2,
                       "send_ldap_browse: No updates required for %s", p->name);
    }
    else
    {
     /*
      * LDAP entry for the printer exists.  Printer has already been registered,
      * modify the current registration...
      */

      cupsdLogMessage(CUPSD_LOG_DEBUG2,
                      "send_ldap_browse: Replace entry for %s", p->name);

      for (i = 0; i < 7; i ++)
      {
        pmods[i]         = mods + i;
        pmods[i]->mod_op = LDAP_MOD_REPLACE;
      }
      pmods[i] = NULL;

#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
      if ((rcmod = ldap_modify_ext_s(BrowseLDAPHandle, dn, pmods, NULL,
                                     NULL)) != LDAP_SUCCESS)
#  else
      if ((rcmod = ldap_modify_s(BrowseLDAPHandle, dn, pmods)) != LDAP_SUCCESS)
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */
      {
        cupsdLogMessage(CUPSD_LOG_ERROR,
                        "LDAP modify for %s failed with status %d: %s",
                        p->name, rcmod, ldap_err2string(rcmod));
        if (rcmod == LDAP_SERVER_DOWN)
          ldap_reconnect();
      }
    }
  }
  else
  {
   /*
    * No LDAP entry exists for the printer.  Printer has never been registered,
    * add the current registration...
    */

    send_ldap_ou(ServerName, BrowseLDAPDN, "CUPS Server");

    cupsdLogMessage(CUPSD_LOG_DEBUG2,
                    "send_ldap_browse: Add entry for %s", p->name);

    for (i = 0; i < 7; i ++)
    {
      pmods[i]         = mods + i;
      pmods[i]->mod_op = LDAP_MOD_ADD;
    }
    pmods[i] = NULL;

#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
    if ((rcmod = ldap_add_ext_s(BrowseLDAPHandle, dn, pmods, NULL,
                                NULL)) != LDAP_SUCCESS)
#  else
    if ((rcmod = ldap_add_s(BrowseLDAPHandle, dn, pmods)) != LDAP_SUCCESS)
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "LDAP add for %s failed with status %d: %s",
                      p->name, rcmod, ldap_err2string(rcmod));
      if (rcmod == LDAP_SERVER_DOWN)
        ldap_reconnect();
    }
  }

  if (rc == LDAP_SUCCESS)
    ldap_freeres(res);
}


/*
 * 'ldap_dereg_printer()' - Delete printer from directory
 */

static void
ldap_dereg_printer(cupsd_printer_t *p)	/* I - Printer to deregister */
{
  char		dn[1024];		/* DN of the printer */
  int		rc;			/* LDAP status */


  cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_dereg_printer: Remove entry for %s",
                  p->name);

 /*
  * Reconnect if LDAP Handle is invalid...
  */

  if (!BrowseLDAPHandle)
  {
    ldap_reconnect();
    return;
  }

 /*
  * Get dn for printer and delete LDAP entry...
  */

  snprintf(dn, sizeof(dn), "cn=%s, ou=%s, %s", p->name, ServerName,
           BrowseLDAPDN);
  cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_dereg_printer: dn=\"%s\"", dn);

#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
  if ((rc = ldap_delete_ext_s(BrowseLDAPHandle, dn, NULL,
                              NULL)) != LDAP_SUCCESS)
#  else
  if ((rc = ldap_delete_s(BrowseLDAPHandle, dn)) != LDAP_SUCCESS)
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */
  {
    cupsdLogMessage(CUPSD_LOG_WARN,
                    "LDAP delete for %s failed with status %d: %s",
                    p->name, rc, ldap_err2string(rc));

   /*
    * If we had a connection problem (connection timed out, etc.)
    * we should reconnect and try again to delete the entry...
    */

    if (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR)
    {
      cupsdLogMessage(CUPSD_LOG_INFO,
                      "Retry deleting LDAP entry for %s after a reconnect...", p->name);
      ldap_reconnect();

#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
      if ((rc = ldap_delete_ext_s(BrowseLDAPHandle, dn, NULL,
                                  NULL)) != LDAP_SUCCESS)
#  else
      if ((rc = ldap_delete_s(BrowseLDAPHandle, dn)) != LDAP_SUCCESS)
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */
        cupsdLogMessage(CUPSD_LOG_WARN,
                        "LDAP delete for %s failed with status %d: %s",
                        p->name, rc, ldap_err2string(rc));
    }
  }
}


/*
 * 'ldap_dereg_ou()' - Remove the organizational unit.
 */

static void
ldap_dereg_ou(char *ou,			/* I - Organizational unit (servername) */
              char *basedn)		/* I - Dase dn */
{
  char		dn[1024];		/* DN of the printer */
  int		rc;			/* LDAP status */


  cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_dereg_ou: Remove entry for %s", ou);

 /*
  * Reconnect if LDAP Handle is invalid...
  */

  if (!BrowseLDAPHandle)
  {
    ldap_reconnect();
    return;
  }

 /*
  * Get dn for printer and delete LDAP entry...
  */

  snprintf(dn, sizeof(dn), "ou=%s, %s", ou, basedn);
  cupsdLogMessage(CUPSD_LOG_DEBUG2, "ldap_dereg_ou: dn=\"%s\"", dn);

#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
  if ((rc = ldap_delete_ext_s(BrowseLDAPHandle, dn, NULL,
                              NULL)) != LDAP_SUCCESS)
#  else
  if ((rc = ldap_delete_s(BrowseLDAPHandle, dn)) != LDAP_SUCCESS)
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */
  {
    cupsdLogMessage(CUPSD_LOG_WARN,
                    "LDAP delete for %s failed with status %d: %s",
                    ou, rc, ldap_err2string(rc));

   /*
    * If we had a connection problem (connection timed out, etc.)
    * we should reconnect and try again to delete the entry...
    */

    if (rc == LDAP_SERVER_DOWN || rc == LDAP_CONNECT_ERROR)
    {
      cupsdLogMessage(CUPSD_LOG_INFO,
                      "Retry deleting LDAP entry for %s after a reconnect...", ou);
      ldap_reconnect();
#  if defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000
      if ((rc = ldap_delete_ext_s(BrowseLDAPHandle, dn, NULL,
                                  NULL)) != LDAP_SUCCESS)
#  else
      if ((rc = ldap_delete_s(BrowseLDAPHandle, dn)) != LDAP_SUCCESS)
#  endif /* defined(HAVE_OPENLDAP) && LDAP_API_VERSION > 3000 */
        cupsdLogMessage(CUPSD_LOG_WARN,
                        "LDAP delete for %s failed with status %d: %s",
                        ou, rc, ldap_err2string(rc));
    }
  }
}
#endif /* HAVE_LDAP */


#ifdef HAVE_LIBSLP
/*
 * 'send_slp_browse()' - Register the specified printer with SLP.
 */

static void
send_slp_browse(cupsd_printer_t *p)	/* I - Printer to register */
{
  char		srvurl[HTTP_MAX_URI],	/* Printer service URI */
		attrs[8192],		/* Printer attributes */
		finishings[1024],	/* Finishings to support */
		make_model[IPP_MAX_NAME * 2],
					/* Make and model, quoted */
		location[IPP_MAX_NAME * 2],
					/* Location, quoted */
		info[IPP_MAX_NAME * 2],	/* Info, quoted */
		*src,			/* Pointer to original string */
		*dst;			/* Pointer to destination string */
  ipp_attribute_t *authentication;	/* uri-authentication-supported value */
  SLPError	error;			/* SLP error, if any */


  cupsdLogMessage(CUPSD_LOG_DEBUG, "send_slp_browse(%p = \"%s\")", p,
                  p->name);

 /*
  * Make the SLP service URL that conforms to the IANA
  * 'printer:' template.
  */

  snprintf(srvurl, sizeof(srvurl), SLP_CUPS_SRVTYPE ":%s", p->uri);

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "Service URL = \"%s\"", srvurl);

 /*
  * Figure out the finishings string...
  */

  if (p->type & CUPS_PRINTER_STAPLE)
    strcpy(finishings, "staple");
  else
    finishings[0] = '\0';

  if (p->type & CUPS_PRINTER_BIND)
  {
    if (finishings[0])
      strlcat(finishings, ",bind", sizeof(finishings));
    else
      strcpy(finishings, "bind");
  }

  if (p->type & CUPS_PRINTER_PUNCH)
  {
    if (finishings[0])
      strlcat(finishings, ",punch", sizeof(finishings));
    else
      strcpy(finishings, "punch");
  }

  if (p->type & CUPS_PRINTER_COVER)
  {
    if (finishings[0])
      strlcat(finishings, ",cover", sizeof(finishings));
    else
      strcpy(finishings, "cover");
  }

  if (p->type & CUPS_PRINTER_SORT)
  {
    if (finishings[0])
      strlcat(finishings, ",sort", sizeof(finishings));
    else
      strcpy(finishings, "sort");
  }

  if (!finishings[0])
    strcpy(finishings, "none");

 /*
  * Quote any commas in the make and model, location, and info strings...
  */

  for (src = p->make_model, dst = make_model;
       src && *src && dst < (make_model + sizeof(make_model) - 2);)
  {
    if (*src == ',' || *src == '\\' || *src == ')')
      *dst++ = '\\';

    *dst++ = *src++;
  }

  *dst = '\0';

  if (!make_model[0])
    strcpy(make_model, "Unknown");

  for (src = p->location, dst = location;
       src && *src && dst < (location + sizeof(location) - 2);)
  {
    if (*src == ',' || *src == '\\' || *src == ')')
      *dst++ = '\\';

    *dst++ = *src++;
  }

  *dst = '\0';

  if (!location[0])
    strcpy(location, "Unknown");

  for (src = p->info, dst = info;
       src && *src && dst < (info + sizeof(info) - 2);)
  {
    if (*src == ',' || *src == '\\' || *src == ')')
      *dst++ = '\\';

    *dst++ = *src++;
  }

  *dst = '\0';

  if (!info[0])
    strcpy(info, "Unknown");

 /*
  * Get the authentication value...
  */

  authentication = ippFindAttribute(p->attrs, "uri-authentication-supported",
                                    IPP_TAG_KEYWORD);

 /*
  * Make the SLP attribute string list that conforms to
  * the IANA 'printer:' template.
  */

  snprintf(attrs, sizeof(attrs),
           "(printer-uri-supported=%s),"
           "(uri-authentication-supported=%s>),"
#ifdef HAVE_SSL
           "(uri-security-supported=tls>),"
#else
           "(uri-security-supported=none>),"
#endif /* HAVE_SSL */
           "(printer-name=%s),"
           "(printer-location=%s),"
           "(printer-info=%s),"
           "(printer-more-info=%s),"
           "(printer-make-and-model=%s),"
	   "(printer-type=%d),"
	   "(charset-supported=utf-8),"
	   "(natural-language-configured=%s),"
	   "(natural-language-supported=de,en,es,fr,it),"
           "(color-supported=%s),"
           "(finishings-supported=%s),"
           "(sides-supported=one-sided%s),"
	   "(multiple-document-jobs-supported=true)"
	   "(ipp-versions-supported=1.0,1.1)",
	   p->uri, authentication->values[0].string.text, p->name, location,
	   info, p->uri, make_model, p->type, DefaultLanguage,
           p->type & CUPS_PRINTER_COLOR ? "true" : "false",
           finishings,
           p->type & CUPS_PRINTER_DUPLEX ?
	       ",two-sided-long-edge,two-sided-short-edge" : "");

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "Attributes = \"%s\"", attrs);

 /*
  * Register the printer with the SLP server...
  */

  error = SLPReg(BrowseSLPHandle, srvurl, BrowseTimeout,
	         SLP_CUPS_SRVTYPE, attrs, SLP_TRUE, slp_reg_callback, 0);

  if (error != SLP_OK)
    cupsdLogMessage(CUPSD_LOG_ERROR, "SLPReg of \"%s\" failed with status %d!", p->name,
                    error);
}


/*
 * 'slp_attr_callback()' - SLP attribute callback
 */

static SLPBoolean			/* O - SLP_TRUE for success */
slp_attr_callback(
    SLPHandle  hslp,			/* I - SLP handle */
    const char *attrlist,		/* I - Attribute list */
    SLPError   errcode,			/* I - Parsing status for this attr */
    void       *cookie)			/* I - Current printer */
{
  char			*tmp = 0;	/* Temporary string */
  cupsd_printer_t	*p = (cupsd_printer_t*)cookie;
					/* Current printer */


  (void)hslp;				/* anti-compiler-warning-code */

 /*
  * Bail if there was an error
  */

  if (errcode != SLP_OK)
    return (SLP_TRUE);

 /*
  * Parse the attrlist to obtain things needed to build CUPS browse packet
  */

  memset(p, 0, sizeof(cupsd_printer_t));

  if (slp_get_attr(attrlist, "(printer-location=", &(p->location)))
    return (SLP_FALSE);
  if (slp_get_attr(attrlist, "(printer-info=", &(p->info)))
    return (SLP_FALSE);
  if (slp_get_attr(attrlist, "(printer-make-and-model=", &(p->make_model)))
    return (SLP_FALSE);
  if (!slp_get_attr(attrlist, "(printer-type=", &tmp))
    p->type = atoi(tmp);
  else
    p->type = CUPS_PRINTER_REMOTE;

  cupsdClearString(&tmp);

  return (SLP_TRUE);
}


/*
 * 'slp_dereg_printer()' - SLPDereg() the specified printer
 */

static void
slp_dereg_printer(cupsd_printer_t *p)	/* I - Printer */
{
  char	srvurl[HTTP_MAX_URI];		/* Printer service URI */


  cupsdLogMessage(CUPSD_LOG_DEBUG, "slp_dereg_printer: printer=\"%s\"", p->name);

  if (!(p->type & CUPS_PRINTER_REMOTE))
  {
   /*
    * Make the SLP service URL that conforms to the IANA
    * 'printer:' template.
    */

    snprintf(srvurl, sizeof(srvurl), SLP_CUPS_SRVTYPE ":%s", p->uri);

   /*
    * Deregister the printer...
    */

    SLPDereg(BrowseSLPHandle, srvurl, slp_reg_callback, 0);
  }
}


/*
 * 'slp_get_attr()' - Get an attribute from an SLP registration.
 */

static int 				/* O - 0 on success */
slp_get_attr(const char *attrlist,	/* I - Attribute list string */
             const char *tag,		/* I - Name of attribute */
             char       **valbuf)	/* O - Value */
{
  char	*ptr1,				/* Pointer into string */
	*ptr2;				/* ... */


  cupsdClearString(valbuf);

  if ((ptr1 = strstr(attrlist, tag)) != NULL)
  {
    ptr1 += strlen(tag);

    if ((ptr2 = strchr(ptr1,')')) != NULL)
    {
     /*
      * Copy the value...
      */

      *valbuf = calloc(ptr2 - ptr1 + 1, 1);
      strncpy(*valbuf, ptr1, ptr2 - ptr1);

     /*
      * Dequote the value...
      */

      for (ptr1 = *valbuf; *ptr1; ptr1 ++)
	if (*ptr1 == '\\' && ptr1[1])
	  _cups_strcpy(ptr1, ptr1 + 1);

      return (0);
    }
  }

  return (-1);
}


/*
 * 'slp_reg_callback()' - Empty SLPRegReport.
 */

static void
slp_reg_callback(SLPHandle hslp,	/* I - SLP handle */
                 SLPError  errcode,	/* I - Error code, if any */
		 void      *cookie)	/* I - App data */
{
  (void)hslp;
  (void)errcode;
  (void)cookie;

  return;
}


/*
 * 'slp_url_callback()' - SLP service url callback
 */

static SLPBoolean			/* O - TRUE = OK, FALSE = error */
slp_url_callback(
    SLPHandle      hslp,	 	/* I - SLP handle */
    const char     *srvurl, 		/* I - URL of service */
    unsigned short lifetime,		/* I - Life of service */
    SLPError       errcode, 		/* I - Existing error code */
    void           *cookie)		/* I - Pointer to service list */
{
  slpsrvurl_t	*s,			/* New service entry */
		**head;			/* Pointer to head of entry */


 /*
  * Let the compiler know we won't be using these vars...
  */

  (void)hslp;
  (void)lifetime;

 /*
  * Bail if there was an error
  */

  if (errcode != SLP_OK)
    return (SLP_TRUE);

 /*
  * Grab the head of the list...
  */

  head = (slpsrvurl_t**)cookie;

 /*
  * Allocate a *temporary* slpsrvurl_t to hold this entry.
  */

  if ((s = (slpsrvurl_t *)calloc(1, sizeof(slpsrvurl_t))) == NULL)
    return (SLP_FALSE);

 /*
  * Copy the SLP service URL...
  */

  strlcpy(s->url, srvurl, sizeof(s->url));

 /*
  * Link the SLP service URL into the head of the list
  */

  if (*head)
    s->next = *head;

  *head = s;

  return (SLP_TRUE);
}
#endif /* HAVE_LIBSLP */


/*
 * 'update_cups_browse()' - Update the browse lists using the CUPS protocol.
 */

static void
update_cups_browse(void)
{
  int		i;			/* Looping var */
  int		auth;			/* Authorization status */
  int		len;			/* Length of name string */
  int		bytes;			/* Number of bytes left */
  char		packet[1541],		/* Broadcast packet */
		*pptr;			/* Pointer into packet */
  socklen_t	srclen;			/* Length of source address */
  http_addr_t	srcaddr;		/* Source address */
  char		srcname[1024];		/* Source hostname */
  unsigned	address[4];		/* Source address */
  unsigned	type;			/* Printer type */
  unsigned	state;			/* Printer state */
  char		uri[HTTP_MAX_URI],	/* Printer URI */
		host[HTTP_MAX_URI],	/* Host portion of URI */
		resource[HTTP_MAX_URI],	/* Resource portion of URI */
		info[IPP_MAX_NAME],	/* Information string */
		location[IPP_MAX_NAME],	/* Location string */
		make_model[IPP_MAX_NAME];/* Make and model string */
  int		num_attrs;		/* Number of attributes */
  cups_option_t	*attrs;			/* Attributes */


 /*
  * Read a packet from the browse socket...
  */

  srclen = sizeof(srcaddr);
  if ((bytes = recvfrom(BrowseSocket, packet, sizeof(packet) - 1, 0,
                        (struct sockaddr *)&srcaddr, &srclen)) < 0)
  {
   /*
    * "Connection refused" is returned under Linux if the destination port
    * or address is unreachable from a previous sendto(); check for the
    * error here and ignore it for now...
    */

    if (errno != ECONNREFUSED && errno != EAGAIN)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Browse recv failed - %s.",
                      strerror(errno));
      cupsdLogMessage(CUPSD_LOG_ERROR, "CUPS browsing turned off.");

#ifdef WIN32
      closesocket(BrowseSocket);
#else
      close(BrowseSocket);
#endif /* WIN32 */

      cupsdRemoveSelect(BrowseSocket);
      BrowseSocket = -1;

      BrowseLocalProtocols  &= ~BROWSE_CUPS;
      BrowseRemoteProtocols &= ~BROWSE_CUPS;
    }

    return;
  }

  packet[bytes] = '\0';

 /*
  * If we're about to sleep, ignore incoming browse packets.
  */

  if (Sleeping)
    return;

 /*
  * Figure out where it came from...
  */

#ifdef AF_INET6
  if (srcaddr.addr.sa_family == AF_INET6)
  {
    address[0] = ntohl(srcaddr.ipv6.sin6_addr.s6_addr32[0]);
    address[1] = ntohl(srcaddr.ipv6.sin6_addr.s6_addr32[1]);
    address[2] = ntohl(srcaddr.ipv6.sin6_addr.s6_addr32[2]);
    address[3] = ntohl(srcaddr.ipv6.sin6_addr.s6_addr32[3]);
  }
  else
#endif /* AF_INET6 */
  {
    address[0] = 0;
    address[1] = 0;
    address[2] = 0;
    address[3] = ntohl(srcaddr.ipv4.sin_addr.s_addr);
  }

  if (HostNameLookups)
    httpAddrLookup(&srcaddr, srcname, sizeof(srcname));
  else
    httpAddrString(&srcaddr, srcname, sizeof(srcname));

  len = strlen(srcname);

 /*
  * Do ACL stuff...
  */

  if (BrowseACL)
  {
    if (httpAddrLocalhost(&srcaddr) || !_cups_strcasecmp(srcname, "localhost"))
    {
     /*
      * Access from localhost (127.0.0.1) is always allowed...
      */

      auth = CUPSD_AUTH_ALLOW;
    }
    else
    {
     /*
      * Do authorization checks on the domain/address...
      */

      switch (BrowseACL->order_type)
      {
        default :
	    auth = CUPSD_AUTH_DENY;	/* anti-compiler-warning-code */
	    break;

	case CUPSD_AUTH_ALLOW : /* Order Deny,Allow */
            auth = CUPSD_AUTH_ALLOW;

            if (cupsdCheckAuth(address, srcname, len, BrowseACL->deny))
	      auth = CUPSD_AUTH_DENY;

            if (cupsdCheckAuth(address, srcname, len, BrowseACL->allow))
	      auth = CUPSD_AUTH_ALLOW;
	    break;

	case CUPSD_AUTH_DENY : /* Order Allow,Deny */
            auth = CUPSD_AUTH_DENY;

            if (cupsdCheckAuth(address, srcname, len, BrowseACL->allow))
	      auth = CUPSD_AUTH_ALLOW;

            if (cupsdCheckAuth(address, srcname, len, BrowseACL->deny))
	      auth = CUPSD_AUTH_DENY;
	    break;
      }
    }
  }
  else
    auth = CUPSD_AUTH_ALLOW;

  if (auth == CUPSD_AUTH_DENY)
  {
    cupsdLogMessage(CUPSD_LOG_DEBUG,
                    "update_cups_browse: Refused %d bytes from %s", bytes,
                    srcname);
    return;
  }

  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "update_cups_browse: (%d bytes from %s) %s", bytes,
		  srcname, packet);

 /*
  * Parse packet...
  */

  if (sscanf(packet, "%x%x%1023s", &type, &state, uri) < 3)
  {
    cupsdLogMessage(CUPSD_LOG_WARN,
                    "update_cups_browse: Garbled browse packet - %s", packet);
    return;
  }

  strcpy(location, "Location Unknown");
  strcpy(info, "No Information Available");
  make_model[0] = '\0';
  num_attrs     = 0;
  attrs         = NULL;

  if ((pptr = strchr(packet, '\"')) != NULL)
  {
   /*
    * Have extended information; can't use sscanf for it because not all
    * sscanf's allow empty strings with %[^\"]...
    */

    for (i = 0, pptr ++;
         i < (sizeof(location) - 1) && *pptr && *pptr != '\"';
         i ++, pptr ++)
      location[i] = *pptr;

    if (i)
      location[i] = '\0';

    if (*pptr == '\"')
      pptr ++;

    while (*pptr && isspace(*pptr & 255))
      pptr ++;

    if (*pptr == '\"')
    {
      for (i = 0, pptr ++;
           i < (sizeof(info) - 1) && *pptr && *pptr != '\"';
           i ++, pptr ++)
	info[i] = *pptr;

      info[i] = '\0';

      if (*pptr == '\"')
	pptr ++;

      while (*pptr && isspace(*pptr & 255))
	pptr ++;

      if (*pptr == '\"')
      {
	for (i = 0, pptr ++;
             i < (sizeof(make_model) - 1) && *pptr && *pptr != '\"';
             i ++, pptr ++)
	  make_model[i] = *pptr;

	if (*pptr == '\"')
	  pptr ++;

	make_model[i] = '\0';

        if (*pptr)
	  num_attrs = cupsParseOptions(pptr, num_attrs, &attrs);
      }
    }
  }

  DEBUG_puts(packet);
  DEBUG_printf(("type=%x, state=%x, uri=\"%s\"\n"
                "location=\"%s\", info=\"%s\", make_model=\"%s\"\n",
	        type, state, uri, location, info, make_model));

 /*
  * Pull the URI apart to see if this is a local or remote printer...
  */

  if (is_local_queue(uri, host, sizeof(host), resource, sizeof(resource)))
  {
    cupsFreeOptions(num_attrs, attrs);
    return;
  }

 /*
  * Do relaying...
  */

  for (i = 0; i < NumRelays; i ++)
    if (cupsdCheckAuth(address, srcname, len, Relays[i].from))
      if (sendto(BrowseSocket, packet, bytes, 0,
                 (struct sockaddr *)&(Relays[i].to),
		 httpAddrLength(&(Relays[i].to))) <= 0)
      {
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "update_cups_browse: sendto failed for relay %d - %s.",
	                i + 1, strerror(errno));
	cupsFreeOptions(num_attrs, attrs);
	return;
      }

 /*
  * Process the browse data...
  */

  process_browse_data(uri, host, resource, (cups_ptype_t)type,
                      (ipp_pstate_t)state, location, info, make_model,
		      num_attrs, attrs);
}


/*
 * 'update_lpd()' - Update the LPD configuration as needed.
 */

static void
update_lpd(int onoff)			/* - 1 = turn on, 0 = turn off */
{
  if (!LPDConfigFile)
    return;

#ifdef __APPLE__
 /*
  * Allow /etc/hostconfig CUPS_LPD service setting to override cupsd.conf
  * setting for backwards-compatibility.
  */

  if (onoff && !get_hostconfig("CUPS_LPD"))
    onoff = 0;
#endif /* __APPLE__ */

  if (!strncmp(LPDConfigFile, "xinetd:///", 10))
  {
   /*
    * Enable/disable LPD via the xinetd.d config file for cups-lpd...
    */

    char	newfile[1024];		/* New cups-lpd.N file */
    cups_file_t	*ofp,			/* Original file pointer */
		*nfp;			/* New file pointer */
    char	line[1024];		/* Line from file */


    snprintf(newfile, sizeof(newfile), "%s.N", LPDConfigFile + 9);

    if ((ofp = cupsFileOpen(LPDConfigFile + 9, "r")) == NULL)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open \"%s\" - %s",
                      LPDConfigFile + 9, strerror(errno));
      return;
    }

    if ((nfp = cupsFileOpen(newfile, "w")) == NULL)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create \"%s\" - %s",
                      newfile, strerror(errno));
      cupsFileClose(ofp);
      return;
    }

   /*
    * Copy all of the lines from the cups-lpd file...
    */

    while (cupsFileGets(ofp, line, sizeof(line)))
    {
      if (line[0] == '{')
      {
        cupsFilePrintf(nfp, "%s\n", line);
        snprintf(line, sizeof(line), "\tdisable = %s",
	         onoff ? "no" : "yes");
      }
      else if (!strstr(line, "disable ="))
        cupsFilePrintf(nfp, "%s\n", line);
    }

    cupsFileClose(nfp);
    cupsFileClose(ofp);
    rename(newfile, LPDConfigFile + 9);
  }
#ifdef __APPLE__
  else if (!strncmp(LPDConfigFile, "launchd:///", 11))
  {
   /*
    * Enable/disable LPD via the launchctl command...
    */

    char	*argv[5],		/* Arguments for command */
		*envp[MAX_ENV];		/* Environment for command */
    int		pid;			/* Process ID */


    cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0])));
    argv[0] = (char *)"launchctl";
    argv[1] = (char *)(onoff ? "load" : "unload");
    argv[2] = (char *)"-w";
    argv[3] = LPDConfigFile + 10;
    argv[4] = NULL;

    cupsdStartProcess("/bin/launchctl", argv, envp, -1, -1, -1, -1, -1, 1,
                      NULL, NULL, &pid);
  }
#endif /* __APPLE__ */
  else
    cupsdLogMessage(CUPSD_LOG_INFO, "Unknown LPDConfigFile scheme!");
}


/*
 * 'update_polling()' - Read status messages from the poll daemons.
 */

static void
update_polling(void)
{
  char		*ptr,			/* Pointer to end of line in buffer */
		message[1024];		/* Pointer to message text */
  int		loglevel;		/* Log level for message */


  while ((ptr = cupsdStatBufUpdate(PollStatusBuffer, &loglevel,
                                   message, sizeof(message))) != NULL)
  {
    if (loglevel == CUPSD_LOG_INFO)
      cupsdLogMessage(CUPSD_LOG_INFO, "%s", message);

    if (!strchr(PollStatusBuffer->buffer, '\n'))
      break;
  }

  if (ptr == NULL && !PollStatusBuffer->bufused)
  {
   /*
    * All polling processes have died; stop polling...
    */

    cupsdLogMessage(CUPSD_LOG_ERROR,
                    "update_polling: all polling processes have exited!");
    cupsdStopPolling();
  }
}


/*
 * 'update_smb()' - Update the SMB configuration as needed.
 */

static void
update_smb(int onoff)			/* I - 1 = turn on, 0 = turn off */
{
  if (!SMBConfigFile)
    return;

  if (!strncmp(SMBConfigFile, "samba:///", 9))
  {
   /*
    * Enable/disable SMB via the specified smb.conf config file...
    */

    char	newfile[1024];		/* New smb.conf.N file */
    cups_file_t	*ofp,			/* Original file pointer */
		*nfp;			/* New file pointer */
    char	line[1024];		/* Line from file */
    int		in_printers;		/* In [printers] section? */


    snprintf(newfile, sizeof(newfile), "%s.N", SMBConfigFile + 8);

    if ((ofp = cupsFileOpen(SMBConfigFile + 8, "r")) == NULL)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to open \"%s\" - %s",
                      SMBConfigFile + 8, strerror(errno));
      return;
    }

    if ((nfp = cupsFileOpen(newfile, "w")) == NULL)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create \"%s\" - %s",
                      newfile, strerror(errno));
      cupsFileClose(ofp);
      return;
    }

   /*
    * Copy all of the lines from the smb.conf file...
    */

    in_printers = 0;

    while (cupsFileGets(ofp, line, sizeof(line)))
    {
      if (in_printers && strstr(line, "printable ="))
        snprintf(line, sizeof(line), "    printable = %s",
	         onoff ? "yes" : "no");

      cupsFilePrintf(nfp, "%s\n", line);

      if (line[0] == '[')
        in_printers = !strcmp(line, "[printers]");
    }

    cupsFileClose(nfp);
    cupsFileClose(ofp);
    rename(newfile, SMBConfigFile + 8);
  }
  else
    cupsdLogMessage(CUPSD_LOG_INFO, "Unknown SMBConfigFile scheme!");
}


/*
 * End of "$Id: dirsvc.c 10243 2012-02-11 02:05:21Z mike $".
 */
