/*
 * Directory services routines for the CUPS scheduler.
 *
 * Copyright © 2007-2018 by Apple Inc.
 * Copyright © 1997-2007 by Easy Software Products, all rights reserved.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 */

/*
 * Include necessary headers...
 */

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

#if defined(HAVE_DNSSD) && defined(__APPLE__)
#  include <nameser.h>
#  include <CoreFoundation/CoreFoundation.h>
#  include <SystemConfiguration/SystemConfiguration.h>
#endif /* HAVE_DNSSD && __APPLE__ */


/*
 * Local globals...
 */

#ifdef HAVE_AVAHI
static int	avahi_running = 0;
#endif /* HAVE_AVAHI */


/*
 * Local functions...
 */

#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
static char		*get_auth_info_required(cupsd_printer_t *p,
			                        char *buffer, size_t bufsize);
#endif /* HAVE_DNSSD || HAVE_AVAHI */
#ifdef __APPLE__
static int		get_hostconfig(const char *name);
#endif /* __APPLE__ */
static void		update_lpd(int onoff);
static void		update_smb(int onoff);


#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
#  ifdef __APPLE__
static void		dnssdAddAlias(const void *key, const void *value,
			              void *context);
#  endif /* __APPLE__ */
static cupsd_txt_t	dnssdBuildTxtRecord(cupsd_printer_t *p, int for_lpd);
#  ifdef HAVE_AVAHI
static void		dnssdClientCallback(AvahiClient *c, AvahiClientState state, void *userdata);
#  endif /* HAVE_AVAHI */
static void		dnssdDeregisterAllPrinters(int from_callback);
static void		dnssdDeregisterInstance(cupsd_srv_t *srv, int from_callback);
static void		dnssdDeregisterPrinter(cupsd_printer_t *p, int clear_name, int from_callback);
static const char	*dnssdErrorString(int error);
static void		dnssdFreeTxtRecord(cupsd_txt_t *txt);
static void		dnssdRegisterAllPrinters(int from_callback);
#  ifdef HAVE_DNSSD
static void		dnssdRegisterCallback(DNSServiceRef sdRef,
					      DNSServiceFlags flags,
					      DNSServiceErrorType errorCode,
					      const char *name,
					      const char *regtype,
					      const char *domain,
					      void *context);
#  else
static void		dnssdRegisterCallback(AvahiEntryGroup *p,
					      AvahiEntryGroupState state,
					      void *context);
#  endif /* HAVE_DNSSD */
static int		dnssdRegisterInstance(cupsd_srv_t *srv, cupsd_printer_t *p, char *name, const char *type, const char *subtypes, int port, cupsd_txt_t *txt, int commit, int from_callback);
static void		dnssdRegisterPrinter(cupsd_printer_t *p, int from_callback);
static void		dnssdStop(void);
#  ifdef HAVE_DNSSD
static void		dnssdUpdate(void);
#  endif /* HAVE_DNSSD */
static void		dnssdUpdateDNSSDName(int from_callback);
#endif /* HAVE_DNSSD || HAVE_AVAHI */


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

 /*
  * Announce the deletion...
  */

#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
  if (removeit && (BrowseLocalProtocols & BROWSE_DNSSD) && DNSSDMaster)
    dnssdDeregisterPrinter(p, 1, 0);
#endif /* HAVE_DNSSD || HAVE_AVAHI */
}


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

#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
  if ((BrowseLocalProtocols & BROWSE_DNSSD) && DNSSDMaster)
    dnssdRegisterPrinter(p, 0);
#endif /* HAVE_DNSSD || HAVE_AVAHI */
}


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

void
cupsdStartBrowsing(void)
{
  if (!Browsing || !BrowseLocalProtocols)
    return;

#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
  if (BrowseLocalProtocols & BROWSE_DNSSD)
  {
#  ifdef HAVE_DNSSD
    DNSServiceErrorType error;		/* Error from service creation */

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

    if ((error = DNSServiceCreateConnection(&DNSSDMaster))
	    != 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(DNSSDMaster);

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

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

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

    DNSSDPort = 0;
    cupsdUpdateDNSSDName();

#  else /* HAVE_AVAHI */
    if ((DNSSDMaster = avahi_threaded_poll_new()) == NULL)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to create DNS-SD thread.");

      if (FatalErrors & CUPSD_FATAL_BROWSE)
	cupsdEndProcess(getpid(), 0);
    }
    else
    {
      int error;			/* Error code, if any */

      DNSSDClient = avahi_client_new(avahi_threaded_poll_get(DNSSDMaster), AVAHI_CLIENT_NO_FAIL, dnssdClientCallback, NULL, &error);

      if (DNSSDClient == NULL)
      {
        cupsdLogMessage(CUPSD_LOG_ERROR,
                        "Unable to communicate with avahi-daemon: %s",
                        dnssdErrorString(error));

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

        avahi_threaded_poll_free(DNSSDMaster);
        DNSSDMaster = NULL;
      }
      else
	avahi_threaded_poll_start(DNSSDMaster);
    }
#  endif /* HAVE_DNSSD */
  }
#endif /* HAVE_DNSSD || HAVE_AVAHI */

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

#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
 /*
  * Register the individual printers
  */

  dnssdRegisterAllPrinters(0);
#endif /* HAVE_DNSSD || HAVE_AVAHI */
}


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

void
cupsdStopBrowsing(void)
{
  if (!Browsing || !BrowseLocalProtocols)
    return;

#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
 /*
  * De-register the individual printers
  */

  dnssdDeregisterAllPrinters(0);

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

  if ((BrowseLocalProtocols & BROWSE_DNSSD) && DNSSDMaster)
    dnssdStop();
#endif /* HAVE_DNSSD || HAVE_AVAHI */

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


#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
/*
 * 'cupsdUpdateDNSSDName()' - Update the computer name we use for browsing...
 */

void
cupsdUpdateDNSSDName(void)
{
  dnssdUpdateDNSSDName(0);
}


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


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

static cupsd_txt_t			/* O - TXT record */
dnssdBuildTxtRecord(
    cupsd_printer_t *p,			/* I - Printer information */
    int             for_lpd)		/* I - 1 = LPD, 0 = IPP */
{
  int		i,			/* Looping var */
		count;			/* Count of key/value pairs */
  char		admin_hostname[256],	/* 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 */
                *ptr;                   /* Pointer in string */
  cupsd_txt_t	txt;			/* TXT record */
  cupsd_listener_t *lis;                /* Current listener */
  const char    *admin_scheme = "http"; /* Admin page URL scheme */


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

  count = 0;

  if (!for_lpd || (BrowseLocalProtocols & BROWSE_LPD))
  {
    keyvalue[count  ][0] = "txtvers";
    keyvalue[count++][1] = "1";

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

    keyvalue[count  ][0] = "rp";
    keyvalue[count++][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[count  ][0] = "ty";
    keyvalue[count++][1] = p->make_model ? p->make_model : "Unknown";

   /*
    * Get the hostname for the admin page...
    */

    if (strchr(DNSSDHostName, '.'))
    {
     /*
      * Use the provided hostname, but make sure it ends with a period...
      */

      if ((ptr = DNSSDHostName + strlen(DNSSDHostName) - 1) >= DNSSDHostName && *ptr == '.')
        strlcpy(admin_hostname, DNSSDHostName, sizeof(admin_hostname));
      else
        snprintf(admin_hostname, sizeof(admin_hostname), "%s.", DNSSDHostName);
    }
    else
    {
     /*
      * Unqualified hostname gets ".local." added to it...
      */

      snprintf(admin_hostname, sizeof(admin_hostname), "%s.local.", DNSSDHostName);
    }

   /*
    * Get the URL scheme for the admin page...
    */

#  ifdef HAVE_SSL
    for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners); lis; lis = (cupsd_listener_t *)cupsArrayNext(Listeners))
    {
      if (lis->encryption != HTTP_ENCRYPTION_NEVER)
      {
        admin_scheme = "https";
        break;
      }
    }
#  endif /* HAVE_SSL */

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

    if (p->location)
    {
      keyvalue[count  ][0] = "note";
      keyvalue[count++][1] = p->location;
    }

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

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

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

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

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

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

    if (p->type & CUPS_PRINTER_FAX)
    {
      keyvalue[count  ][0] = "Fax";
      keyvalue[count++][1] = "T";
      keyvalue[count  ][0] = "rfo";
      keyvalue[count++][1] = rp_str;
    }

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

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

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

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

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

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

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

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

    if (p->type & CUPS_PRINTER_MFP)
    {
      keyvalue[count  ][0] = "Scan";
      keyvalue[count++][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[count  ][0] = "printer-state";
    keyvalue[count++][1] = state_str;

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

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

#  ifdef HAVE_DNSSD
  TXTRecordCreate(&txt, 0, NULL);

  for (i = 0; i < count; i ++)
  {
    size_t len = strlen(keyvalue[i][1]);

    if (len < 256)
      TXTRecordSetValue(&txt, keyvalue[i][0], (uint8_t)len, keyvalue[i][1]);
  }

#  else
  for (i = 0, txt = NULL; i < count; i ++)
    txt = avahi_string_list_add_printf(txt, "%s=%s", keyvalue[i][0],
                                       keyvalue[i][1]);
#  endif /* HAVE_DNSSD */

  return (txt);
}


#  ifdef HAVE_AVAHI
/*
 * 'dnssdClientCallback()' - Client callback for Avahi.
 *
 * Called whenever the client or server state changes...
 */

static void
dnssdClientCallback(
    AvahiClient      *c,		/* I - Client */
    AvahiClientState state,		/* I - Current state */
    void             *userdata)		/* I - User data (unused) */
{
  int	error;				/* Error code, if any */


  (void)userdata;

  if (!c)
    return;

 /*
  * Make sure DNSSDClient is already set also if this callback function is
  * already running before avahi_client_new() in dnssdStartBrowsing()
  * finishes.
  */

  if (!DNSSDClient)
    DNSSDClient = c;

  switch (state)
  {
    case AVAHI_CLIENT_S_REGISTERING:
    case AVAHI_CLIENT_S_RUNNING:
    case AVAHI_CLIENT_S_COLLISION:
	cupsdLogMessage(CUPSD_LOG_DEBUG, "Avahi server connection now available, registering printers for Bonjour broadcasting.");

       /*
	* Mark that Avahi server is running...
	*/

	avahi_running = 1;

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

	DNSSDPort = 0;
	dnssdUpdateDNSSDName(1);

       /*
	* Register the individual printers
	*/

	dnssdRegisterAllPrinters(1);
	break;

    case AVAHI_CLIENT_FAILURE:
	if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED)
	{
	  cupsdLogMessage(CUPSD_LOG_DEBUG, "Avahi server disappeared, unregistering printers for Bonjour broadcasting.");

	 /*
	  * Unregister everything and close the client...
	  */

	  dnssdDeregisterAllPrinters(1);
	  dnssdDeregisterInstance(&WebIFSrv, 1);
	  avahi_client_free(DNSSDClient);
	  DNSSDClient = NULL;

	 /*
	  * Mark that Avahi server is not running...
	  */

	  avahi_running = 0;

	 /*
	  * Renew Avahi client...
	  */

	  DNSSDClient = avahi_client_new(avahi_threaded_poll_get(DNSSDMaster), AVAHI_CLIENT_NO_FAIL, dnssdClientCallback, NULL, &error);

	  if (!DNSSDClient)
	  {
	    cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to communicate with avahi-daemon: %s", dnssdErrorString(error));
	    if (FatalErrors & CUPSD_FATAL_BROWSE)
	      cupsdEndProcess(getpid(), 0);
	  }
	}
	else
	{
	  cupsdLogMessage(CUPSD_LOG_ERROR, "Communication with avahi-daemon has failed: %s", avahi_strerror(avahi_client_errno(c)));
	  if (FatalErrors & CUPSD_FATAL_BROWSE)
	    cupsdEndProcess(getpid(), 0);
	}
	break;

    default:
        break;
  }
}
#  endif /* HAVE_AVAHI */


/*
 * 'dnssdDeregisterAllPrinters()' - Deregister all printers.
 */

static void
dnssdDeregisterAllPrinters(
    int             from_callback)	/* I - Deregistering because of callback? */
{
  cupsd_printer_t	*p;		/* Current printer */


  if (!DNSSDMaster)
    return;

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


/*
 * 'dnssdDeregisterInstance()' - Deregister a DNS-SD service instance.
 */

static void
dnssdDeregisterInstance(
    cupsd_srv_t     *srv,		/* I - Service */
    int             from_callback)	/* I - Called from callback? */
{
  if (!srv || !*srv)
    return;

#  ifdef HAVE_DNSSD
  (void)from_callback;

  DNSServiceRefDeallocate(*srv);

  *srv = NULL;

#  else /* HAVE_AVAHI */
  if (!from_callback)
    avahi_threaded_poll_lock(DNSSDMaster);

  if (*srv)
  {
    avahi_entry_group_free(*srv);
    *srv = NULL;
  }

  if (!from_callback)
    avahi_threaded_poll_unlock(DNSSDMaster);
#  endif /* HAVE_DNSSD */
}


/*
 * 'dnssdDeregisterPrinter()' - Deregister all services for a printer.
 */

static void
dnssdDeregisterPrinter(
    cupsd_printer_t *p,			/* I - Printer */
    int             clear_name,		/* I - Clear the name? */
    int             from_callback)	/* I - Called from callback? */

{
  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "dnssdDeregisterPrinter(p=%p(%s), clear_name=%d)", p, p->name,
                  clear_name);

  if (p->ipp_srv)
  {
    dnssdDeregisterInstance(&p->ipp_srv, from_callback);

#  ifdef HAVE_DNSSD
#    ifdef HAVE_SSL
    dnssdDeregisterInstance(&p->ipps_srv, from_callback);
#    endif /* HAVE_SSL */
    dnssdDeregisterInstance(&p->printer_srv, from_callback);
#  endif /* HAVE_DNSSD */
  }

 /*
  * Remove the printer from the array of DNS-SD printers but keep the
  * registered name...
  */

  cupsArrayRemove(DNSSDPrinters, p);

 /*
  * Optionally clear the service name...
  */

  if (clear_name)
    cupsdClearString(&p->reg_name);
}


/*
 * 'dnssdErrorString()' - Return an error string for an error code.
 */

static const char *			/* O - Error message */
dnssdErrorString(int error)		/* I - Error number */
{
#  ifdef HAVE_DNSSD
  switch (error)
  {
    case kDNSServiceErr_NoError :
        return ("OK.");

    default :
    case kDNSServiceErr_Unknown :
        return ("Unknown error.");

    case kDNSServiceErr_NoSuchName :
        return ("Service not found.");

    case kDNSServiceErr_NoMemory :
        return ("Out of memory.");

    case kDNSServiceErr_BadParam :
        return ("Bad parameter.");

    case kDNSServiceErr_BadReference :
        return ("Bad service reference.");

    case kDNSServiceErr_BadState :
        return ("Bad state.");

    case kDNSServiceErr_BadFlags :
        return ("Bad flags.");

    case kDNSServiceErr_Unsupported :
        return ("Unsupported.");

    case kDNSServiceErr_NotInitialized :
        return ("Not initialized.");

    case kDNSServiceErr_AlreadyRegistered :
        return ("Already registered.");

    case kDNSServiceErr_NameConflict :
        return ("Name conflict.");

    case kDNSServiceErr_Invalid :
        return ("Invalid name.");

    case kDNSServiceErr_Firewall :
        return ("Firewall prevents registration.");

    case kDNSServiceErr_Incompatible :
        return ("Client library incompatible.");

    case kDNSServiceErr_BadInterfaceIndex :
        return ("Bad interface index.");

    case kDNSServiceErr_Refused :
        return ("Server prevents registration.");

    case kDNSServiceErr_NoSuchRecord :
        return ("Record not found.");

    case kDNSServiceErr_NoAuth :
        return ("Authentication required.");

    case kDNSServiceErr_NoSuchKey :
        return ("Encryption key not found.");

    case kDNSServiceErr_NATTraversal :
        return ("Unable to traverse NAT boundary.");

    case kDNSServiceErr_DoubleNAT :
        return ("Unable to traverse double-NAT boundary.");

    case kDNSServiceErr_BadTime :
        return ("Bad system time.");

    case kDNSServiceErr_BadSig :
        return ("Bad signature.");

    case kDNSServiceErr_BadKey :
        return ("Bad encryption key.");

    case kDNSServiceErr_Transient :
        return ("Transient error occurred - please try again.");

    case kDNSServiceErr_ServiceNotRunning :
        return ("Server not running.");

    case kDNSServiceErr_NATPortMappingUnsupported :
        return ("NAT doesn't support NAT-PMP or UPnP.");

    case kDNSServiceErr_NATPortMappingDisabled :
        return ("NAT supports NAT-PNP or UPnP but it is disabled.");

    case kDNSServiceErr_NoRouter :
        return ("No Internet/default router configured.");

    case kDNSServiceErr_PollingMode :
        return ("Service polling mode error.");

    case kDNSServiceErr_Timeout :
        return ("Service timeout.");
  }

#  else /* HAVE_AVAHI */
  return (avahi_strerror(error));
#  endif /* HAVE_DNSSD */
}


/*
 * 'dnssdRegisterCallback()' - Free a TXT record.
 */

static void
dnssdFreeTxtRecord(cupsd_txt_t *txt)	/* I - TXT record */
{
#  ifdef HAVE_DNSSD
  TXTRecordDeallocate(txt);

#  else /* HAVE_AVAHI */
  avahi_string_list_free(*txt);
  *txt = NULL;
#  endif /* HAVE_DNSSD */
}


/*
 * 'dnssdRegisterAllPrinters()' - Register all printers.
 */

static void
dnssdRegisterAllPrinters(int from_callback)	/* I - Called from callback? */
{
  cupsd_printer_t	*p;			/* Current printer */


  if (!DNSSDMaster)
    return;

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


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

#  ifdef HAVE_DNSSD
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 - Printer */
{
  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;
  }
}

#  else /* HAVE_AVAHI */
static void
dnssdRegisterCallback(
    AvahiEntryGroup      *srv,		/* I - Service */
    AvahiEntryGroupState state,		/* I - Registration state */
    void                 *context)	/* I - Printer */
{
  cupsd_printer_t *p = (cupsd_printer_t *)context;
					/* Current printer */

  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "dnssdRegisterCallback(srv=%p, state=%d, context=%p) "
                  "for %s (%s)", srv, state, context,
                  p ? p->name : "Web Interface",
		  p ? (p->reg_name ? p->reg_name : "(null)") : "NA");

  /* TODO: Handle collisions with avahi_alternate_service_name(p->reg_name)? */
}
#  endif /* HAVE_DNSSD */


/*
 * 'dnssdRegisterInstance()' - Register an instance of a printer service.
 */

static int				/* O - 1 on success, 0 on failure */
dnssdRegisterInstance(
    cupsd_srv_t     *srv,		/* O - Service */
    cupsd_printer_t *p,			/* I - Printer */
    char            *name,		/* I - DNS-SD service name */
    const char      *type,		/* I - DNS-SD service type */
    const char      *subtypes,		/* I - Subtypes to register or NULL */
    int             port,		/* I - Port number or 0 */
    cupsd_txt_t     *txt,		/* I - TXT record */
    int             commit,		/* I - Commit registration? */
    int             from_callback)	/* I - Called from callback? */
{
  char	temp[256],			/* Temporary string */
	*ptr;				/* Pointer into string */
  int	error;				/* Any error */


#  ifdef HAVE_DNSSD
  (void)from_callback;
#  endif /* HAVE_DNSSD */

  cupsdLogMessage(CUPSD_LOG_DEBUG, "Registering \"%s\" with DNS-SD type \"%s\".", name, type);

  if (p && !srv)
  {
   /*
    * Assign the correct pointer for "srv"...
    */

#  ifdef HAVE_DNSSD
    if (!strcmp(type, "_printer._tcp"))
      srv = &p->printer_srv;		/* Target LPD service */
#    ifdef HAVE_SSL
    else if (!strcmp(type, "_ipps._tcp"))
      srv = &p->ipps_srv;		/* Target IPPS service */
#    endif /* HAVE_SSL */
    else
      srv = &p->ipp_srv;		/* Target IPP service */

#  else /* HAVE_AVAHI */
    srv = &p->ipp_srv;			/* Target service group */
#  endif /* HAVE_DNSSD */
  }

#  ifdef HAVE_DNSSD
  (void)commit;

#  else /* HAVE_AVAHI */
  if (!from_callback)
    avahi_threaded_poll_lock(DNSSDMaster);

  if (!*srv)
    *srv = avahi_entry_group_new(DNSSDClient, dnssdRegisterCallback, NULL);
  if (!*srv)
  {
    if (!from_callback)
      avahi_threaded_poll_unlock(DNSSDMaster);

    cupsdLogMessage(CUPSD_LOG_WARN, "DNS-SD registration of \"%s\" failed: %s",
                    name, dnssdErrorString(avahi_client_errno(DNSSDClient)));
    return (0);
  }
#  endif /* HAVE_DNSSD */

 /*
  * Make sure the name is <= 63 octets, and when we truncate be sure to
  * properly truncate any UTF-8 characters...
  */

  ptr = name + strlen(name);
  while ((ptr - name) > 63)
  {
    do
    {
      ptr --;
    }
    while (ptr > name && (*ptr & 0xc0) == 0x80);

    if (ptr > name)
      *ptr = '\0';
  }

 /*
  * Register the service...
  */

#  ifdef HAVE_DNSSD
  if (subtypes)
    snprintf(temp, sizeof(temp), "%s,%s", type, subtypes);
  else
    strlcpy(temp, type, sizeof(temp));

  *srv  = DNSSDMaster;
  error = DNSServiceRegister(srv, kDNSServiceFlagsShareConnection,
			     0, name, temp, NULL, DNSSDHostName, htons(port),
			     txt ? TXTRecordGetLength(txt) : 0,
			     txt ? TXTRecordGetBytesPtr(txt) : NULL,
			     dnssdRegisterCallback, p);

#  else /* HAVE_AVAHI */
  if (txt)
  {
    AvahiStringList *temptxt;
    for (temptxt = *txt; temptxt; temptxt = temptxt->next)
      cupsdLogMessage(CUPSD_LOG_DEBUG, "DNS_SD \"%s\" %s", name, temptxt->text);
  }

  error = avahi_entry_group_add_service_strlst(*srv, AVAHI_IF_UNSPEC,
                                               AVAHI_PROTO_UNSPEC, 0, name,
                                               type, NULL, DNSSDHostName, port,
                                               txt ? *txt : NULL);
  if (error)
    cupsdLogMessage(CUPSD_LOG_DEBUG, "DNS-SD service add for \"%s\" failed.",
                    name);

  if (!error && subtypes)
  {
   /*
    * Register all of the subtypes...
    */

    char	*start,			/* Start of subtype */
		subtype[256];		/* Subtype string */

    strlcpy(temp, subtypes, sizeof(temp));

    for (start = temp; *start; start = ptr)
    {
     /*
      * Skip leading whitespace...
      */

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

     /*
      * Grab everything up to the next comma or the end of the string...
      */

      for (ptr = start; *ptr && *ptr != ','; ptr ++);

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

      if (!*start)
        break;

     /*
      * Register the subtype...
      */

      snprintf(subtype, sizeof(subtype), "%s._sub.%s", start, type);

      error = avahi_entry_group_add_service_subtype(*srv, AVAHI_IF_UNSPEC,
                                                    AVAHI_PROTO_UNSPEC, 0,
                                                    name, type, NULL, subtype);
      if (error)
      {
        cupsdLogMessage(CUPSD_LOG_DEBUG,
                        "DNS-SD subtype %s registration for \"%s\" failed." ,
                        subtype, name);
        break;
      }
    }
  }

  if (!error && commit)
  {
    if ((error = avahi_entry_group_commit(*srv)) != 0)
      cupsdLogMessage(CUPSD_LOG_DEBUG, "DNS-SD commit of \"%s\" failed.",
                      name);
  }

  if (!from_callback)
    avahi_threaded_poll_unlock(DNSSDMaster);
#  endif /* HAVE_DNSSD */

  if (error)
  {
    cupsdLogMessage(CUPSD_LOG_WARN, "DNS-SD registration of \"%s\" failed: %s",
                    name, dnssdErrorString(error));
    cupsdLogMessage(CUPSD_LOG_DEBUG, "DNS-SD type: %s", type);
    if (subtypes)
      cupsdLogMessage(CUPSD_LOG_DEBUG, "DNS-SD sub-types: %s", subtypes);
  }

  return (!error);
}


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

static void
dnssdRegisterPrinter(
    cupsd_printer_t *p,			/* I - Printer */
    int             from_callback)	/* I - Called from callback? */
{
  char		name[256];		/* Service name */
  int		printer_port;		/* LPD port number */
  int		status;			/* Registration status */
  cupsd_txt_t	ipp_txt,		/* IPP(S) TXT record */
 		printer_txt;		/* LPD TXT record */


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

#  ifdef HAVE_AVAHI
  if (!avahi_running)
    return;
#  endif /* HAVE_AVAHI */

 /*
  * Remove the current registrations if we have them and then return if
  * per-printer sharing was just disabled...
  */

  dnssdDeregisterPrinter(p, 0, from_callback);

  if (!p->shared)
    return;

 /*
  * Set the registered name as needed; the registered name takes the form of
  * "<printer-info> @ <computer name>"...
  */

  if (!p->reg_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));
  }
  else
    strlcpy(name, p->reg_name, sizeof(name));

 /*
  * Register IPP and LPD...
  *
  * We always must register the "_printer" service type in order to reserve
  * our name, but use port number 0 if we haven't actually configured cups-lpd
  * to share via LPD...
  */

  ipp_txt     = dnssdBuildTxtRecord(p, 0);
  printer_txt = dnssdBuildTxtRecord(p, 1);

  if (BrowseLocalProtocols & BROWSE_LPD)
    printer_port = 515;
  else
    printer_port = 0;

  status = dnssdRegisterInstance(NULL, p, name, "_printer._tcp", NULL, printer_port, &printer_txt, 0, from_callback);

#  ifdef HAVE_SSL
  if (status)
    dnssdRegisterInstance(NULL, p, name, "_ipps._tcp", DNSSDSubTypes, DNSSDPort, &ipp_txt, 0, from_callback);
#  endif /* HAVE_SSL */

  if (status)
  {
   /*
    * Use the "_fax-ipp" service type for fax queues, otherwise use "_ipp"...
    */

    if (p->type & CUPS_PRINTER_FAX)
      status = dnssdRegisterInstance(NULL, p, name, "_fax-ipp._tcp", DNSSDSubTypes, DNSSDPort, &ipp_txt, 1, from_callback);
    else
      status = dnssdRegisterInstance(NULL, p, name, "_ipp._tcp", DNSSDSubTypes, DNSSDPort, &ipp_txt, 1, from_callback);
  }

  dnssdFreeTxtRecord(&ipp_txt);
  dnssdFreeTxtRecord(&printer_txt);

  if (status)
  {
   /*
    * Save the registered name and add the printer to the array of DNS-SD
    * printers...
    */

    cupsdSetString(&p->reg_name, name);
    cupsArrayAdd(DNSSDPrinters, p);
  }
  else
  {
   /*
    * Registration failed for this printer...
    */

    dnssdDeregisterInstance(&p->ipp_srv, from_callback);

#  ifdef HAVE_DNSSD
#    ifdef HAVE_SSL
    dnssdDeregisterInstance(&p->ipps_srv, from_callback);
#    endif /* HAVE_SSL */
    dnssdDeregisterInstance(&p->printer_srv, from_callback);
#  endif /* HAVE_DNSSD */
  }
}


/*
 * '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, 1, 0);

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

  dnssdDeregisterInstance(&WebIFSrv, 0);

#  ifdef HAVE_DNSSD
  cupsdRemoveSelect(DNSServiceRefSockFD(DNSSDMaster));

  DNSServiceRefDeallocate(DNSSDMaster);
  DNSSDMaster = NULL;

#  else /* HAVE_AVAHI */
  if (DNSSDMaster)
    avahi_threaded_poll_stop(DNSSDMaster);

  if (DNSSDClient)
  {
    avahi_client_free(DNSSDClient);
    DNSSDClient = NULL;
  }

  if (DNSSDMaster)
  {
    avahi_threaded_poll_free(DNSSDMaster);
    DNSSDMaster = NULL;
  }
#  endif /* HAVE_DNSSD */

  cupsArrayDelete(DNSSDPrinters);
  DNSSDPrinters = NULL;

  DNSSDPort = 0;
}


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

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


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


/*
 * 'dnssdUpdateDNSSDName()' - Update the listen port, computer name, and web interface registration.
 */

static void
dnssdUpdateDNSSDName(int from_callback)	/* I - Called from callback? */
{
  char		webif[1024];		/* Web interface share name */
#  ifdef __APPLE__
  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 /* __APPLE__ */


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

  if (!DNSSDPort)
  {
   /*
    * 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...
    */

    cupsd_listener_t	*lis;		/* Current listening socket */

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

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

  if (!DNSSDPort)
    return;

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

#  ifdef __APPLE__
  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);
    }

    if (!DNSSDHostName)
    {
     /*
      * Get the local hostname from the dynamic store...
      */

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

	  if (strchr(nameBuffer, '.'))
	    cupsdSetString(&DNSSDHostName, nameBuffer);
	  else
	    cupsdSetStringf(&DNSSDHostName, "%s.local", nameBuffer);

	  cupsdLogMessage(CUPSD_LOG_INFO, "Defaulting to \"DNSSDHostName %s\".", DNSSDHostName);
	}

	CFRelease(nameRef);
      }
    }

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

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

      cupsdLogMessage(CUPSD_LOG_INFO, "Defaulting to \"DNSSDHostName %s\".", DNSSDHostName);
    }

   /*
    * 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 /* __APPLE__ */
#  ifdef HAVE_AVAHI
  if (DNSSDClient)
  {
    const char	*host_name = avahi_client_get_host_name(DNSSDClient);

    cupsdSetString(&DNSSDComputerName, host_name ? host_name : ServerName);

    if (!DNSSDHostName)
    {
      const char *host_fqdn = avahi_client_get_host_name_fqdn(DNSSDClient);

      if (host_fqdn)
	cupsdSetString(&DNSSDHostName, host_fqdn);
      else if (strchr(ServerName, '.'))
	cupsdSetString(&DNSSDHostName, ServerName);
      else
	cupsdSetStringf(&DNSSDHostName, "%s.local", ServerName);

      cupsdLogMessage(CUPSD_LOG_INFO, "Defaulting to \"DNSSDHostName %s\".", DNSSDHostName);
    }
  }
  else
#  endif /* HAVE_AVAHI */
  {
    cupsdSetString(&DNSSDComputerName, ServerName);

    if (!DNSSDHostName)
    {
      if (strchr(ServerName, '.'))
	cupsdSetString(&DNSSDHostName, ServerName);
      else
	cupsdSetStringf(&DNSSDHostName, "%s.local", ServerName);

      cupsdLogMessage(CUPSD_LOG_INFO, "Defaulting to \"DNSSDHostName %s\".", DNSSDHostName);
    }
  }

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

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

    dnssdDeregisterInstance(&WebIFSrv, from_callback);
    dnssdRegisterInstance(&WebIFSrv, NULL, webif, "_http._tcp", "_printer", DNSSDPort, NULL, 1, from_callback);
  }
}


/*
 * '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 - (size_t)(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 = cupsdDefaultAuthType();

    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");
}
#endif /* HAVE_DNSSD || HAVE_AVAHI */


#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__ */


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