/*
 * "$Id: conf.c 10121 2011-11-16 15:28:11Z mike $"
 *
 *   Configuration 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:
 *
 *   cupsdAddAlias()          - Add a host alias.
 *   cupsdCheckPermissions()  - Fix the mode and ownership of a file or
 *                              directory.
 *   cupsdFreeAliases()       - Free all of the alias entries.
 *   cupsdReadConfiguration() - Read the cupsd.conf file.
 *   get_address()            - Get an address + port number from a line.
 *   get_addr_and_mask()      - Get an IP address and netmask.
 *   mime_error_cb()          - Log a MIME error.
 *   parse_aaa()              - Parse authentication, authorization, and access
 *                              control lines.
 *   parse_fatal_errors()     - Parse FatalErrors values in a string.
 *   parse_groups()           - Parse system group names in a string.
 *   parse_protocols()        - Parse browse protocols in a string.
 *   read_configuration()     - Read a configuration file.
 *   read_location()          - Read a <Location path> definition.
 *   read_policy()            - Read a <Policy name> definition.
 *   set_policy_defaults()    - Set default policy values as needed.
 */

/*
 * Include necessary headers...
 */

#include "cupsd.h"
#include <stdarg.h>
#include <grp.h>
#include <sys/utsname.h>
#include <syslog.h>

#ifdef HAVE_LIBPAPER
#  include <paper.h>
#endif /* HAVE_LIBPAPER */


/*
 * Possibly missing network definitions...
 */

#ifndef INADDR_NONE
#  define INADDR_NONE	0xffffffff
#endif /* !INADDR_NONE */


/*
 * Configuration variable structure...
 */

typedef enum
{
  CUPSD_VARTYPE_INTEGER,		/* Integer option */
  CUPSD_VARTYPE_STRING,			/* String option */
  CUPSD_VARTYPE_BOOLEAN,		/* Boolean option */
  CUPSD_VARTYPE_PATHNAME		/* File/directory name option */
} cupsd_vartype_t;

typedef struct
{
  char			*name;		/* Name of variable */
  void			*ptr;		/* Pointer to variable */
  cupsd_vartype_t	type;		/* Type (int, string, address) */
} cupsd_var_t;


/*
 * Local globals...
 */

static const cupsd_var_t	variables[] =
{
  { "AccessLog",		&AccessLog,		CUPSD_VARTYPE_STRING },
  { "AutoPurgeJobs", 		&JobAutoPurge,		CUPSD_VARTYPE_BOOLEAN },
#ifdef HAVE_DNSSD
  { "BrowseDNSSDRegType",	&DNSSDRegType,		CUPSD_VARTYPE_STRING },
#endif /* HAVE_DNSSD */
  { "BrowseInterval",		&BrowseInterval,	CUPSD_VARTYPE_INTEGER },
#ifdef HAVE_LDAP
  { "BrowseLDAPBindDN",		&BrowseLDAPBindDN,	CUPSD_VARTYPE_STRING },
#  ifdef HAVE_LDAP_SSL
  { "BrowseLDAPCACertFile",	&BrowseLDAPCACertFile,	CUPSD_VARTYPE_PATHNAME },
#  endif /* HAVE_LDAP_SSL */
  { "BrowseLDAPDN",		&BrowseLDAPDN,		CUPSD_VARTYPE_STRING },
  { "BrowseLDAPPassword",	&BrowseLDAPPassword,	CUPSD_VARTYPE_STRING },
  { "BrowseLDAPServer",		&BrowseLDAPServer,	CUPSD_VARTYPE_STRING },
#endif /* HAVE_LDAP */
  { "BrowseLocalOptions",	&BrowseLocalOptions,	CUPSD_VARTYPE_STRING },
  { "BrowsePort",		&BrowsePort,		CUPSD_VARTYPE_INTEGER },
  { "BrowseRemoteOptions",	&BrowseRemoteOptions,	CUPSD_VARTYPE_STRING },
  { "BrowseShortNames",		&BrowseShortNames,	CUPSD_VARTYPE_BOOLEAN },
  { "BrowseTimeout",		&BrowseTimeout,		CUPSD_VARTYPE_INTEGER },
  { "BrowseWebIF",		&BrowseWebIF,		CUPSD_VARTYPE_BOOLEAN },
  { "Browsing",			&Browsing,		CUPSD_VARTYPE_BOOLEAN },
  { "CacheDir",			&CacheDir,		CUPSD_VARTYPE_STRING },
  { "Classification",		&Classification,	CUPSD_VARTYPE_STRING },
  { "ClassifyOverride",		&ClassifyOverride,	CUPSD_VARTYPE_BOOLEAN },
  { "ConfigFilePerm",		&ConfigFilePerm,	CUPSD_VARTYPE_INTEGER },
  { "DataDir",			&DataDir,		CUPSD_VARTYPE_STRING },
  { "DefaultLanguage",		&DefaultLanguage,	CUPSD_VARTYPE_STRING },
  { "DefaultLeaseDuration",	&DefaultLeaseDuration,	CUPSD_VARTYPE_INTEGER },
  { "DefaultPaperSize",		&DefaultPaperSize,	CUPSD_VARTYPE_STRING },
  { "DefaultPolicy",		&DefaultPolicy,		CUPSD_VARTYPE_STRING },
  { "DefaultShared",		&DefaultShared,		CUPSD_VARTYPE_BOOLEAN },
  { "DirtyCleanInterval",	&DirtyCleanInterval,	CUPSD_VARTYPE_INTEGER },
  { "DocumentRoot",		&DocumentRoot,		CUPSD_VARTYPE_STRING },
  { "ErrorLog",			&ErrorLog,		CUPSD_VARTYPE_STRING },
  { "ErrorPolicy",		&ErrorPolicy,		CUPSD_VARTYPE_STRING },
  { "FileDevice",		&FileDevice,		CUPSD_VARTYPE_BOOLEAN },
  { "FilterLimit",		&FilterLimit,		CUPSD_VARTYPE_INTEGER },
  { "FilterNice",		&FilterNice,		CUPSD_VARTYPE_INTEGER },
  { "FontPath",			&FontPath,		CUPSD_VARTYPE_STRING },
  { "HideImplicitMembers",	&HideImplicitMembers,	CUPSD_VARTYPE_BOOLEAN },
  { "ImplicitClasses",		&ImplicitClasses,	CUPSD_VARTYPE_BOOLEAN },
  { "ImplicitAnyClasses",	&ImplicitAnyClasses,	CUPSD_VARTYPE_BOOLEAN },
  { "JobKillDelay",		&JobKillDelay,		CUPSD_VARTYPE_INTEGER },
  { "JobRetryLimit",		&JobRetryLimit,		CUPSD_VARTYPE_INTEGER },
  { "JobRetryInterval",		&JobRetryInterval,	CUPSD_VARTYPE_INTEGER },
  { "KeepAliveTimeout",		&KeepAliveTimeout,	CUPSD_VARTYPE_INTEGER },
  { "KeepAlive",		&KeepAlive,		CUPSD_VARTYPE_BOOLEAN },
#ifdef HAVE_LAUNCHD
  { "LaunchdTimeout",		&LaunchdTimeout,	CUPSD_VARTYPE_INTEGER },
#endif /* HAVE_LAUNCHD */
  { "LimitRequestBody",		&MaxRequestSize,	CUPSD_VARTYPE_INTEGER },
  { "ListenBackLog",		&ListenBackLog,		CUPSD_VARTYPE_INTEGER },
  { "LogDebugHistory",		&LogDebugHistory,	CUPSD_VARTYPE_INTEGER },
  { "LogFilePerm",		&LogFilePerm,		CUPSD_VARTYPE_INTEGER },
  { "LPDConfigFile",		&LPDConfigFile,		CUPSD_VARTYPE_STRING },
  { "MaxActiveJobs",		&MaxActiveJobs,		CUPSD_VARTYPE_INTEGER },
  { "MaxClients",		&MaxClients,		CUPSD_VARTYPE_INTEGER },
  { "MaxClientsPerHost",	&MaxClientsPerHost,	CUPSD_VARTYPE_INTEGER },
  { "MaxCopies",		&MaxCopies,		CUPSD_VARTYPE_INTEGER },
  { "MaxEvents",		&MaxEvents,		CUPSD_VARTYPE_INTEGER },
  { "MaxJobs",			&MaxJobs,		CUPSD_VARTYPE_INTEGER },
  { "MaxJobsPerPrinter",	&MaxJobsPerPrinter,	CUPSD_VARTYPE_INTEGER },
  { "MaxJobsPerUser",		&MaxJobsPerUser,	CUPSD_VARTYPE_INTEGER },
  { "MaxLeaseDuration",		&MaxLeaseDuration,	CUPSD_VARTYPE_INTEGER },
  { "MaxLogSize",		&MaxLogSize,		CUPSD_VARTYPE_INTEGER },
  { "MaxRequestSize",		&MaxRequestSize,	CUPSD_VARTYPE_INTEGER },
  { "MaxSubscriptions",		&MaxSubscriptions,	CUPSD_VARTYPE_INTEGER },
  { "MaxSubscriptionsPerJob",	&MaxSubscriptionsPerJob,	CUPSD_VARTYPE_INTEGER },
  { "MaxSubscriptionsPerPrinter",&MaxSubscriptionsPerPrinter,	CUPSD_VARTYPE_INTEGER },
  { "MaxSubscriptionsPerUser",	&MaxSubscriptionsPerUser,	CUPSD_VARTYPE_INTEGER },
  { "MultipleOperationTimeout",	&MultipleOperationTimeout,	CUPSD_VARTYPE_INTEGER },
  { "PageLog",			&PageLog,		CUPSD_VARTYPE_STRING },
  { "PageLogFormat",		&PageLogFormat,		CUPSD_VARTYPE_STRING },
  { "PreserveJobFiles",		&JobFiles,		CUPSD_VARTYPE_BOOLEAN },
  { "PreserveJobHistory",	&JobHistory,		CUPSD_VARTYPE_BOOLEAN },
  { "Printcap",			&Printcap,		CUPSD_VARTYPE_STRING },
  { "PrintcapGUI",		&PrintcapGUI,		CUPSD_VARTYPE_STRING },
  { "ReloadTimeout",		&ReloadTimeout,		CUPSD_VARTYPE_INTEGER },
  { "RemoteRoot",		&RemoteRoot,		CUPSD_VARTYPE_STRING },
  { "RequestRoot",		&RequestRoot,		CUPSD_VARTYPE_STRING },
  { "RIPCache",			&RIPCache,		CUPSD_VARTYPE_STRING },
  { "RootCertDuration",		&RootCertDuration,	CUPSD_VARTYPE_INTEGER },
  { "ServerAdmin",		&ServerAdmin,		CUPSD_VARTYPE_STRING },
  { "ServerBin",		&ServerBin,		CUPSD_VARTYPE_PATHNAME },
#ifdef HAVE_SSL
  { "ServerCertificate",	&ServerCertificate,	CUPSD_VARTYPE_PATHNAME },
#  if defined(HAVE_LIBSSL) || defined(HAVE_GNUTLS)
  { "ServerKey",		&ServerKey,		CUPSD_VARTYPE_PATHNAME },
#  endif /* HAVE_LIBSSL || HAVE_GNUTLS */
#endif /* HAVE_SSL */
  { "ServerName",		&ServerName,		CUPSD_VARTYPE_STRING },
  { "ServerRoot",		&ServerRoot,		CUPSD_VARTYPE_PATHNAME },
  { "SMBConfigFile",		&SMBConfigFile,		CUPSD_VARTYPE_STRING },
  { "StateDir",			&StateDir,		CUPSD_VARTYPE_STRING },
#ifdef HAVE_AUTHORIZATION_H
  { "SystemGroupAuthKey",	&SystemGroupAuthKey,	CUPSD_VARTYPE_STRING },
#endif /* HAVE_AUTHORIZATION_H */
  { "TempDir",			&TempDir,		CUPSD_VARTYPE_PATHNAME },
  { "Timeout",			&Timeout,		CUPSD_VARTYPE_INTEGER },
  { "UseNetworkDefault",	&UseNetworkDefault,	CUPSD_VARTYPE_BOOLEAN },
  { "WebInterface",		&WebInterface,		CUPSD_VARTYPE_BOOLEAN }
};
#define NUM_VARS	(sizeof(variables) / sizeof(variables[0]))


static const unsigned	ones[4] =
			{
			  0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff
			};
static const unsigned	zeros[4] =
			{
			  0x00000000, 0x00000000, 0x00000000, 0x00000000
			};


/*
 * Local functions...
 */

static http_addrlist_t	*get_address(const char *value, int defport);
static int		get_addr_and_mask(const char *value, unsigned *ip,
			                  unsigned *mask);
static void		mime_error_cb(void *ctx, const char *message);
static int		parse_aaa(cupsd_location_t *loc, char *line,
			          char *value, int linenum);
static int		parse_fatal_errors(const char *s);
static int		parse_groups(const char *s);
static int		parse_protocols(const char *s);
static int		read_configuration(cups_file_t *fp);
static int		read_location(cups_file_t *fp, char *name, int linenum);
static int		read_policy(cups_file_t *fp, char *name, int linenum);
static void		set_policy_defaults(cupsd_policy_t *pol);


/*
 * 'cupsdAddAlias()' - Add a host alias.
 */

void
cupsdAddAlias(cups_array_t *aliases,	/* I - Array of aliases */
              const char   *name)	/* I - Name to add */
{
  cupsd_alias_t	*a;			/*  New alias */
  size_t	namelen;		/* Length of name */


  namelen = strlen(name);

  if ((a = (cupsd_alias_t *)malloc(sizeof(cupsd_alias_t) + namelen)) == NULL)
    return;

  a->namelen = namelen;
  strcpy(a->name, name);		/* OK since a->name is allocated */

  cupsArrayAdd(aliases, a);
}


/*
 * 'cupsdCheckPermissions()' - Fix the mode and ownership of a file or directory.
 */

int					/* O - 0 on success, -1 on error, 1 on warning */
cupsdCheckPermissions(
    const char *filename,		/* I - File/directory name */
    const char *suffix,			/* I - Additional file/directory name */
    int        mode,			/* I - Permissions */
    int        user,			/* I - Owner */
    int        group,			/* I - Group */
    int        is_dir,			/* I - 1 = directory, 0 = file */
    int        create_dir)		/* I - 1 = create directory, -1 = create w/o logging, 0 = not */
{
  int		dir_created = 0;	/* Did we create a directory? */
  char		pathname[1024];		/* File name with prefix */
  struct stat	fileinfo;		/* Stat buffer */
  int		is_symlink;		/* Is "filename" a symlink? */


 /*
  * Prepend the given root to the filename before testing it...
  */

  if (suffix)
  {
    snprintf(pathname, sizeof(pathname), "%s/%s", filename, suffix);
    filename = pathname;
  }

 /*
  * See if we can stat the file/directory...
  */

  if (lstat(filename, &fileinfo))
  {
    if (errno == ENOENT && create_dir)
    {
      if (create_dir > 0)
	cupsdLogMessage(CUPSD_LOG_DEBUG, "Creating missing directory \"%s\"",
			filename);

      if (mkdir(filename, mode))
      {
        if (create_dir > 0)
	  cupsdLogMessage(CUPSD_LOG_ERROR,
			  "Unable to create directory \"%s\" - %s", filename,
			  strerror(errno));
        else
	  syslog(LOG_ERR, "Unable to create directory \"%s\" - %s", filename,
		 strerror(errno));

        return (-1);
      }

      dir_created      = 1;
      fileinfo.st_mode = mode | S_IFDIR;
    }
    else
      return (create_dir ? -1 : 1);
  }

  if ((is_symlink = S_ISLNK(fileinfo.st_mode)) != 0)
  {
    if (stat(filename, &fileinfo))
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "\"%s\" is a bad symlink - %s",
                      filename, strerror(errno));
      return (-1);
    }
  }

 /*
  * Make sure it's a regular file or a directory as needed...
  */

  if (!dir_created && !is_dir && !S_ISREG(fileinfo.st_mode))
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "\"%s\" is not a regular file.", filename);
    return (-1);
  }

  if (!dir_created && is_dir && !S_ISDIR(fileinfo.st_mode))
  {
    if (create_dir >= 0)
      cupsdLogMessage(CUPSD_LOG_ERROR, "\"%s\" is not a directory.", filename);
    else
      syslog(LOG_ERR, "\"%s\" is not a directory.", filename);

    return (-1);
  }

 /*
  * If the filename is a symlink, do not change permissions (STR #2937)...
  */

  if (is_symlink)
    return (0);

 /*
  * Fix owner, group, and mode as needed...
  */

  if (dir_created || fileinfo.st_uid != user || fileinfo.st_gid != group)
  {
    if (create_dir >= 0)
      cupsdLogMessage(CUPSD_LOG_DEBUG, "Repairing ownership of \"%s\"",
                      filename);

    if (chown(filename, user, group) && !getuid())
    {
      if (create_dir >= 0)
	cupsdLogMessage(CUPSD_LOG_ERROR,
			"Unable to change ownership of \"%s\" - %s", filename,
			strerror(errno));
      else
	syslog(LOG_ERR, "Unable to change ownership of \"%s\" - %s", filename,
	       strerror(errno));

      return (1);
    }
  }

  if (dir_created || (fileinfo.st_mode & 07777) != mode)
  {
    if (create_dir >= 0)
      cupsdLogMessage(CUPSD_LOG_DEBUG, "Repairing access permissions of \"%s\"",
		      filename);

    if (chmod(filename, mode))
    {
      if (create_dir >= 0)
	cupsdLogMessage(CUPSD_LOG_ERROR,
			"Unable to change permissions of \"%s\" - %s", filename,
			strerror(errno));
      else
	syslog(LOG_ERR, "Unable to change permissions of \"%s\" - %s", filename,
	       strerror(errno));

      return (1);
    }
  }

 /*
  * Everything is OK...
  */

  return (0);
}


/*
 * 'cupsdFreeAliases()' - Free all of the alias entries.
 */

void
cupsdFreeAliases(cups_array_t *aliases)	/* I - Array of aliases */
{
  cupsd_alias_t	*a;			/* Current alias */


  for (a = (cupsd_alias_t *)cupsArrayFirst(aliases);
       a;
       a = (cupsd_alias_t *)cupsArrayNext(aliases))
    free(a);

  cupsArrayDelete(aliases);
}


/*
 * 'cupsdReadConfiguration()' - Read the cupsd.conf file.
 */

int					/* O - 1 on success, 0 otherwise */
cupsdReadConfiguration(void)
{
  int		i;			/* Looping var */
  cups_file_t	*fp;			/* Configuration file */
  int		status;			/* Return status */
  char		temp[1024],		/* Temporary buffer */
		mimedir[1024],		/* MIME directory */
		*slash;			/* Directory separator */
  cups_lang_t	*language;		/* Language */
  struct passwd	*user;			/* Default user */
  struct group	*group;			/* Default group */
  char		*old_serverroot,	/* Old ServerRoot */
		*old_requestroot;	/* Old RequestRoot */
  int		old_remote_port;	/* Old RemotePort */
  const char	*tmpdir;		/* TMPDIR environment variable */
  struct stat	tmpinfo;		/* Temporary directory info */
  cupsd_policy_t *p;			/* Policy */


 /*
  * Save the old root paths...
  */

  old_serverroot = NULL;
  cupsdSetString(&old_serverroot, ServerRoot);
  old_requestroot = NULL;
  cupsdSetString(&old_requestroot, RequestRoot);

 /*
  * Reset the server configuration data...
  */

  cupsdDeleteAllLocations();

  if (NumBrowsers > 0)
  {
    free(Browsers);
    Browsers = NULL;

    NumBrowsers = 0;
  }

  if (NumPolled > 0)
  {
    free(Polled);

    NumPolled = 0;
  }

  if (NumRelays > 0)
  {
    for (i = 0; i < NumRelays; i ++)
      cupsArrayDelete(Relays[i].from);

    free(Relays);

    NumRelays = 0;
  }

  cupsdDeleteAllListeners();

  old_remote_port = RemotePort;
  RemotePort      = 0;

 /*
  * String options...
  */

  cupsdFreeAliases(ServerAlias);
  ServerAlias = NULL;

  cupsdClearString(&ServerName);
  cupsdClearString(&ServerAdmin);
  cupsdSetString(&ServerBin, CUPS_SERVERBIN);
  cupsdSetString(&RequestRoot, CUPS_REQUESTS);
  cupsdSetString(&CacheDir, CUPS_CACHEDIR);
  cupsdSetString(&DataDir, CUPS_DATADIR);
  cupsdSetString(&DocumentRoot, CUPS_DOCROOT);
  cupsdSetString(&AccessLog, CUPS_LOGDIR "/access_log");
  cupsdClearString(&ErrorLog);
  cupsdSetString(&PageLog, CUPS_LOGDIR "/page_log");
  cupsdSetString(&PageLogFormat,
                 "%p %u %j %T %P %C %{job-billing} "
		 "%{job-originating-host-name} %{job-name} %{media} %{sides}");
  cupsdSetString(&Printcap, CUPS_DEFAULT_PRINTCAP);
  cupsdSetString(&PrintcapGUI, "/usr/bin/glpoptions");
  cupsdSetString(&FontPath, CUPS_FONTPATH);
  cupsdSetString(&RemoteRoot, "remroot");
  cupsdSetStringf(&ServerHeader, "CUPS/%d.%d", CUPS_VERSION_MAJOR,
                  CUPS_VERSION_MINOR);
  cupsdSetString(&StateDir, CUPS_STATEDIR);

  if (!strcmp(CUPS_DEFAULT_PRINTCAP, "/etc/printers.conf"))
    PrintcapFormat = PRINTCAP_SOLARIS;
  else if (!strcmp(CUPS_DEFAULT_PRINTCAP,
                   "/Library/Preferences/org.cups.printers.plist"))
    PrintcapFormat = PRINTCAP_PLIST;
  else
    PrintcapFormat = PRINTCAP_BSD;

  strlcpy(temp, ConfigurationFile, sizeof(temp));
  if ((slash = strrchr(temp, '/')) != NULL)
    *slash = '\0';

  cupsdSetString(&ServerRoot, temp);

  cupsdClearString(&Classification);
  ClassifyOverride  = 0;

#ifdef HAVE_SSL
#  ifdef HAVE_CDSASSL
  cupsdSetString(&ServerCertificate, "/Library/Keychains/System.keychain");
#  else
  cupsdSetString(&ServerCertificate, "ssl/server.crt");
  cupsdSetString(&ServerKey, "ssl/server.key");
#  endif /* HAVE_CDSASSL */
#endif /* HAVE_SSL */

  language = cupsLangDefault();

  if (!strcmp(language->language, "C") || !strcmp(language->language, "POSIX"))
    cupsdSetString(&DefaultLanguage, "en");
  else
    cupsdSetString(&DefaultLanguage, language->language);

  cupsdClearString(&DefaultPaperSize);

  cupsdSetString(&RIPCache, "128m");

  cupsdSetString(&TempDir, NULL);

 /*
  * Find the default user...
  */

  if ((user = getpwnam(CUPS_DEFAULT_USER)) != NULL)
    User = user->pw_uid;
  else
  {
   /*
    * Use the (historical) NFS nobody user ID (-2 as a 16-bit twos-
    * complement number...)
    */

    User = 65534;
  }

  endpwent();

 /*
  * Find the default group...
  */

  group = getgrnam(CUPS_DEFAULT_GROUP);
  endgrent();

  if (group)
    Group = group->gr_gid;
  else
  {
   /*
    * Fallback to group "nobody"...
    */

    group = getgrnam("nobody");
    endgrent();

    if (group)
      Group = group->gr_gid;
    else
    {
     /*
      * Use the (historical) NFS nobody group ID (-2 as a 16-bit twos-
      * complement number...)
      */

      Group = 65534;
    }
  }

 /*
  * Numeric options...
  */

  AccessLogLevel           = CUPSD_ACCESSLOG_ACTIONS;
  ConfigFilePerm           = CUPS_DEFAULT_CONFIG_FILE_PERM;
  FatalErrors              = parse_fatal_errors(CUPS_DEFAULT_FATAL_ERRORS);
  DefaultAuthType          = CUPSD_AUTH_BASIC;
#ifdef HAVE_SSL
  DefaultEncryption        = HTTP_ENCRYPT_REQUIRED;
  SSLOptions               = CUPSD_SSL_NONE;
#endif /* HAVE_SSL */
  DirtyCleanInterval       = DEFAULT_KEEPALIVE;
  JobKillDelay             = DEFAULT_TIMEOUT;
  JobRetryLimit            = 5;
  JobRetryInterval         = 300;
  FileDevice               = FALSE;
  FilterLevel              = 0;
  FilterLimit              = 0;
  FilterNice               = 0;
  HostNameLookups          = FALSE;
  ImplicitClasses          = CUPS_DEFAULT_IMPLICIT_CLASSES;
  ImplicitAnyClasses       = FALSE;
  HideImplicitMembers      = TRUE;
  KeepAlive                = TRUE;
  KeepAliveTimeout         = DEFAULT_KEEPALIVE;
  ListenBackLog            = SOMAXCONN;
  LogDebugHistory          = 200;
  LogFilePerm              = CUPS_DEFAULT_LOG_FILE_PERM;
  LogLevel                 = CUPSD_LOG_WARN;
  LogTimeFormat            = CUPSD_TIME_STANDARD;
  MaxClients               = 100;
  MaxClientsPerHost        = 0;
  MaxLogSize               = 1024 * 1024;
  MaxRequestSize           = 0;
  MultipleOperationTimeout = DEFAULT_TIMEOUT;
  ReloadTimeout	           = DEFAULT_KEEPALIVE;
  RootCertDuration         = 300;
  Timeout                  = DEFAULT_TIMEOUT;
  NumSystemGroups          = 0;
  WebInterface             = CUPS_DEFAULT_WEBIF;

  BrowseInterval           = DEFAULT_INTERVAL;
  BrowsePort               = ippPort();
  BrowseLocalProtocols     = parse_protocols(CUPS_DEFAULT_BROWSE_LOCAL_PROTOCOLS);
  BrowseRemoteProtocols    = parse_protocols(CUPS_DEFAULT_BROWSE_REMOTE_PROTOCOLS);
  BrowseShortNames         = CUPS_DEFAULT_BROWSE_SHORT_NAMES;
  BrowseTimeout            = DEFAULT_TIMEOUT;
  BrowseWebIF              = FALSE;
  Browsing                 = CUPS_DEFAULT_BROWSING;
  DefaultShared            = CUPS_DEFAULT_DEFAULT_SHARED;

#ifdef HAVE_DNSSD
  cupsdSetString(&DNSSDRegType, "_ipp._tcp,_cups");
#endif /* HAVE_DNSSD */

  cupsdSetString(&LPDConfigFile, CUPS_DEFAULT_LPD_CONFIG_FILE);
  cupsdSetString(&SMBConfigFile, CUPS_DEFAULT_SMB_CONFIG_FILE);

  cupsdClearString(&BrowseLocalOptions);
  cupsdClearString(&BrowseRemoteOptions);

  cupsdSetString(&ErrorPolicy, "stop-printer");

#ifdef HAVE_LDAP
  cupsdClearString(&BrowseLDAPBindDN);
  cupsdClearString(&BrowseLDAPDN);
  cupsdClearString(&BrowseLDAPPassword);
  cupsdClearString(&BrowseLDAPServer);
#  ifdef HAVE_LDAP_SSL
  cupsdClearString(&BrowseLDAPCACertFile);
#  endif /* HAVE_LDAP_SSL */
#endif /* HAVE_LDAP */

  JobHistory          = DEFAULT_HISTORY;
  JobFiles            = DEFAULT_FILES;
  JobAutoPurge        = 0;
  MaxJobs             = 500;
  MaxActiveJobs       = 0;
  MaxJobsPerUser      = 0;
  MaxJobsPerPrinter   = 0;
  MaxCopies           = CUPS_DEFAULT_MAX_COPIES;

  cupsdDeleteAllPolicies();
  cupsdClearString(&DefaultPolicy);

#ifdef HAVE_AUTHORIZATION_H
  cupsdClearString(&SystemGroupAuthKey);
#endif /* HAVE_AUTHORIZATION_H */

  MaxSubscriptions           = 100;
  MaxSubscriptionsPerJob     = 0;
  MaxSubscriptionsPerPrinter = 0;
  MaxSubscriptionsPerUser    = 0;
  DefaultLeaseDuration       = 86400;
  MaxLeaseDuration           = 0;

#ifdef HAVE_LAUNCHD
  LaunchdTimeout = DEFAULT_TIMEOUT + 10;
#endif /* HAVE_LAUNCHD */

 /*
  * Setup environment variables...
  */

  cupsdInitEnv();

 /*
  * Read the configuration file...
  */

  if ((fp = cupsFileOpen(ConfigurationFile, "r")) == NULL)
    return (0);

  status = read_configuration(fp);

  cupsFileClose(fp);

  if (!status)
    return (0);

  if (!ErrorLog)
    cupsdSetString(&ErrorLog, CUPS_LOGDIR "/error_log");

  RunUser = getuid();

  cupsdLogMessage(CUPSD_LOG_INFO, "Remote access is %s.",
                  RemotePort ? "enabled" : "disabled");

  if (!RemotePort)
    BrowseLocalProtocols = 0;		/* Disable sharing - no remote access */

 /*
  * See if the ServerName is an IP address...
  */

  if (ServerName)
  {
    if (!ServerAlias)
      ServerAlias = cupsArrayNew(NULL, NULL);

    cupsdLogMessage(CUPSD_LOG_DEBUG, "Added auto ServerAlias %s", ServerName);
  }
  else
  {
    if (gethostname(temp, sizeof(temp)))
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to get hostname: %s",
                      strerror(errno));
      strlcpy(temp, "localhost", sizeof(temp));
    }

    cupsdSetString(&ServerName, temp);

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

    cupsdAddAlias(ServerAlias, temp);
    cupsdLogMessage(CUPSD_LOG_DEBUG, "Added auto ServerAlias %s", temp);

    if (HostNameLookups || RemotePort)
    {
      struct hostent	*host;		/* Host entry to get FQDN */

      if ((host = gethostbyname(temp)) != NULL)
      {
        if (_cups_strcasecmp(temp, host->h_name))
        {
	  cupsdSetString(&ServerName, host->h_name);
	  cupsdAddAlias(ServerAlias, host->h_name);
          cupsdLogMessage(CUPSD_LOG_DEBUG, "Added auto ServerAlias %s",
	                  host->h_name);
	}

        if (host->h_aliases)
	{
          for (i = 0; host->h_aliases[i]; i ++)
	    if (_cups_strcasecmp(temp, host->h_aliases[i]))
	    {
	      cupsdAddAlias(ServerAlias, host->h_aliases[i]);
	      cupsdLogMessage(CUPSD_LOG_DEBUG, "Added auto ServerAlias %s",
	                      host->h_aliases[i]);
	    }
	}
      }
    }

   /*
    * Make sure we have the base hostname added as an alias, too!
    */

    if ((slash = strchr(temp, '.')) != NULL)
    {
      *slash = '\0';
      cupsdAddAlias(ServerAlias, temp);
      cupsdLogMessage(CUPSD_LOG_DEBUG, "Added auto ServerAlias %s", temp);
    }
  }

  for (slash = ServerName; isdigit(*slash & 255) || *slash == '.'; slash ++);

  ServerNameIsIP = !*slash;

 /*
  * Make sure ServerAdmin is initialized...
  */

  if (!ServerAdmin)
    cupsdSetStringf(&ServerAdmin, "root@%s", ServerName);

 /*
  * Use the default system group if none was supplied in cupsd.conf...
  */

  if (NumSystemGroups == 0)
  {
    if (!parse_groups(CUPS_DEFAULT_SYSTEM_GROUPS))
    {
     /*
      * Find the group associated with GID 0...
      */

      group = getgrgid(0);
      endgrent();

      if (group != NULL)
	cupsdSetString(&SystemGroups[0], group->gr_name);
      else
	cupsdSetString(&SystemGroups[0], "unknown");

      SystemGroupIDs[0] = 0;
      NumSystemGroups   = 1;
    }
  }

 /*
  * Get the access control list for browsing...
  */

  BrowseACL = cupsdFindLocation("CUPS_INTERNAL_BROWSE_ACL");

 /*
  * Open the system log for cupsd if necessary...
  */

#ifdef HAVE_VSYSLOG
  if (!strcmp(AccessLog, "syslog") ||
      !strcmp(ErrorLog, "syslog") ||
      !strcmp(PageLog, "syslog"))
    openlog("cupsd", LOG_PID | LOG_NOWAIT | LOG_NDELAY, LOG_LPR);
#endif /* HAVE_VSYSLOG */

 /*
  * Make sure each of the log files exists and gets rotated as necessary...
  */

  if (strcmp(AccessLog, "syslog"))
    cupsdCheckLogFile(&AccessFile, AccessLog);

  if (strcmp(ErrorLog, "syslog"))
    cupsdCheckLogFile(&ErrorFile, ErrorLog);

  if (strcmp(PageLog, "syslog"))
    cupsdCheckLogFile(&PageFile, PageLog);

 /*
  * Log the configuration file that was used...
  */

  cupsdLogMessage(CUPSD_LOG_INFO, "Loaded configuration file \"%s\"",
                  ConfigurationFile);

 /*
  * Validate the Group and SystemGroup settings - they cannot be the same,
  * otherwise the CGI programs will be able to authenticate as root without
  * a password!
  */

  if (!RunUser)
  {
    for (i = 0; i < NumSystemGroups; i ++)
      if (Group == SystemGroupIDs[i])
        break;

    if (i < NumSystemGroups)
    {
     /*
      * Log the error and reset the group to a safe value...
      */

      cupsdLogMessage(CUPSD_LOG_NOTICE,
                      "Group and SystemGroup cannot use the same groups.");
      cupsdLogMessage(CUPSD_LOG_INFO, "Resetting Group to \"nobody\"...");

      group = getgrnam("nobody");
      endgrent();

      if (group != NULL)
	Group = group->gr_gid;
      else
      {
       /*
	* Use the (historical) NFS nobody group ID (-2 as a 16-bit twos-
	* complement number...)
	*/

	Group = 65534;
      }
    }
  }

 /*
  * Check that we have at least one listen/port line; if not, report this
  * as an error and exit!
  */

  if (cupsArrayCount(Listeners) == 0)
  {
   /*
    * No listeners!
    */

    cupsdLogMessage(CUPSD_LOG_EMERG,
                    "No valid Listen or Port lines were found in the "
		    "configuration file.");

   /*
    * Commit suicide...
    */

    cupsdEndProcess(getpid(), 0);
  }

 /*
  * Set the default locale using the language and charset...
  */

  cupsdSetStringf(&DefaultLocale, "%s.UTF-8", DefaultLanguage);

 /*
  * Update all relative filenames to include the full path from ServerRoot...
  */

  if (DocumentRoot[0] != '/')
    cupsdSetStringf(&DocumentRoot, "%s/%s", ServerRoot, DocumentRoot);

  if (RequestRoot[0] != '/')
    cupsdSetStringf(&RequestRoot, "%s/%s", ServerRoot, RequestRoot);

  if (ServerBin[0] != '/')
    cupsdSetStringf(&ServerBin, "%s/%s", ServerRoot, ServerBin);

  if (StateDir[0] != '/')
    cupsdSetStringf(&StateDir, "%s/%s", ServerRoot, StateDir);

  if (CacheDir[0] != '/')
    cupsdSetStringf(&CacheDir, "%s/%s", ServerRoot, CacheDir);

#ifdef HAVE_SSL
  if (ServerCertificate[0] != '/')
    cupsdSetStringf(&ServerCertificate, "%s/%s", ServerRoot, ServerCertificate);

  if (!strncmp(ServerRoot, ServerCertificate, strlen(ServerRoot)) &&
      cupsdCheckPermissions(ServerCertificate, NULL, 0600, RunUser, Group,
                            0, 0) < 0 &&
      (FatalErrors & CUPSD_FATAL_PERMISSIONS))
    return (0);

#  if defined(HAVE_LIBSSL) || defined(HAVE_GNUTLS)
  if (ServerKey[0] != '/')
    cupsdSetStringf(&ServerKey, "%s/%s", ServerRoot, ServerKey);

  if (!strncmp(ServerRoot, ServerKey, strlen(ServerRoot)) &&
      cupsdCheckPermissions(ServerKey, NULL, 0600, RunUser, Group, 0, 0) < 0 &&
      (FatalErrors & CUPSD_FATAL_PERMISSIONS))
    return (0);
#  endif /* HAVE_LIBSSL || HAVE_GNUTLS */
#endif /* HAVE_SSL */

 /*
  * Make sure that directories and config files are owned and
  * writable by the user and group in the cupsd.conf file...
  */

  snprintf(temp, sizeof(temp), "%s/rss", CacheDir);

  if ((cupsdCheckPermissions(RequestRoot, NULL, 0710, RunUser,
			     Group, 1, 1) < 0 ||
       cupsdCheckPermissions(CacheDir, NULL, 0775, RunUser,
			     Group, 1, 1) < 0 ||
       cupsdCheckPermissions(temp, NULL, 0775, RunUser,
			     Group, 1, 1) < 0 ||
       cupsdCheckPermissions(StateDir, NULL, 0755, RunUser,
			     Group, 1, 1) < 0 ||
       cupsdCheckPermissions(StateDir, "certs", RunUser ? 0711 : 0511, User,
			     SystemGroupIDs[0], 1, 1) < 0 ||
       cupsdCheckPermissions(ServerRoot, NULL, 0755, RunUser,
			     Group, 1, 0) < 0 ||
       cupsdCheckPermissions(ServerRoot, "ppd", 0755, RunUser,
			     Group, 1, 1) < 0 ||
       cupsdCheckPermissions(ServerRoot, "ssl", 0700, RunUser,
			     Group, 1, 0) < 0 ||
       cupsdCheckPermissions(ServerRoot, "cupsd.conf", ConfigFilePerm, RunUser,
			     Group, 0, 0) < 0 ||
       cupsdCheckPermissions(ServerRoot, "classes.conf", 0600, RunUser,
			     Group, 0, 0) < 0 ||
       cupsdCheckPermissions(ServerRoot, "printers.conf", 0600, RunUser,
			     Group, 0, 0) < 0 ||
       cupsdCheckPermissions(ServerRoot, "passwd.md5", 0600, User,
			     Group, 0, 0) < 0) &&
      (FatalErrors & CUPSD_FATAL_PERMISSIONS))
    return (0);

 /*
  * Update TempDir to the default if it hasn't been set already...
  */

  if (!TempDir)
  {
#ifdef __APPLE__
    if ((tmpdir = getenv("TMPDIR")) != NULL &&
        strncmp(tmpdir, "/private/tmp", 12))
#else
    if ((tmpdir = getenv("TMPDIR")) != NULL)
#endif /* __APPLE__ */
    {
     /*
      * TMPDIR is defined, see if it is OK for us to use...
      */

      if (stat(tmpdir, &tmpinfo))
        cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to access TMPDIR (%s): %s",
	                tmpdir, strerror(errno));
      else if (!S_ISDIR(tmpinfo.st_mode))
        cupsdLogMessage(CUPSD_LOG_ERROR, "TMPDIR (%s) is not a directory.",
	                tmpdir);
      else if ((tmpinfo.st_uid != User || !(tmpinfo.st_mode & S_IWUSR)) &&
               (tmpinfo.st_gid != Group || !(tmpinfo.st_mode & S_IWGRP)) &&
	       !(tmpinfo.st_mode & S_IWOTH))
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "TMPDIR (%s) has the wrong permissions.", tmpdir);
      else
        cupsdSetString(&TempDir, tmpdir);
    }

    if (!TempDir)
    {
      cupsdLogMessage(CUPSD_LOG_INFO, "Using default TempDir of %s/tmp...",
	              RequestRoot);
      cupsdSetStringf(&TempDir, "%s/tmp", RequestRoot);
    }
  }

 /*
  * Make sure the temporary directory has the right permissions...
  */

  if (!strncmp(TempDir, RequestRoot, strlen(RequestRoot)) ||
      access(TempDir, 0))
  {
   /*
    * Update ownership and permissions if the CUPS temp directory
    * is under the spool directory or does not exist...
    */

    if (cupsdCheckPermissions(TempDir, NULL, 01770, RunUser, Group, 1, 1) < 0 &&
	(FatalErrors & CUPSD_FATAL_PERMISSIONS))
      return (0);
  }

 /*
  * Update environment variables...
  */

  cupsdUpdateEnv();

 /*
  * Update default paper size setting as needed...
  */

  if (!DefaultPaperSize)
  {
#ifdef HAVE_LIBPAPER
    char	*paper_result;		/* Paper size name from libpaper */

    if ((paper_result = systempapername()) != NULL)
      cupsdSetString(&DefaultPaperSize, paper_result);
    else
#endif /* HAVE_LIBPAPER */
    if (!DefaultLanguage ||
        !_cups_strcasecmp(DefaultLanguage, "C") ||
        !_cups_strcasecmp(DefaultLanguage, "POSIX") ||
	!_cups_strcasecmp(DefaultLanguage, "en") ||
	!_cups_strncasecmp(DefaultLanguage, "en.", 3) ||
	!_cups_strncasecmp(DefaultLanguage, "en_US", 5) ||
	!_cups_strncasecmp(DefaultLanguage, "en_CA", 5) ||
	!_cups_strncasecmp(DefaultLanguage, "fr_CA", 5))
    {
     /*
      * These are the only locales that will default to "letter" size...
      */

      cupsdSetString(&DefaultPaperSize, "Letter");
    }
    else
      cupsdSetString(&DefaultPaperSize, "A4");
  }

 /*
  * Update classification setting as needed...
  */

  if (Classification && !_cups_strcasecmp(Classification, "none"))
    cupsdClearString(&Classification);

  if (Classification)
    cupsdLogMessage(CUPSD_LOG_INFO, "Security set to \"%s\"", Classification);

 /*
  * Check the MaxClients setting, and then allocate memory for it...
  */

  if (MaxClients > (MaxFDs / 3) || MaxClients <= 0)
  {
    if (MaxClients > 0)
      cupsdLogMessage(CUPSD_LOG_INFO,
                      "MaxClients limited to 1/3 (%d) of the file descriptor "
		      "limit (%d)...",
                      MaxFDs / 3, MaxFDs);

    MaxClients = MaxFDs / 3;
  }

  cupsdLogMessage(CUPSD_LOG_INFO, "Configured for up to %d clients.",
                  MaxClients);

 /*
  * Check the MaxActiveJobs setting; limit to 1/3 the available
  * file descriptors, since we need a pipe for each job...
  */

  if (MaxActiveJobs > (MaxFDs / 3))
    MaxActiveJobs = MaxFDs / 3;

 /*
  * Update the MaxClientsPerHost value, as needed...
  */

  if (MaxClientsPerHost <= 0)
    MaxClientsPerHost = MaxClients;

  if (MaxClientsPerHost > MaxClients)
    MaxClientsPerHost = MaxClients;

  cupsdLogMessage(CUPSD_LOG_INFO,
                  "Allowing up to %d client connections per host.",
                  MaxClientsPerHost);

 /*
  * Make sure that BrowseTimeout is at least twice the interval...
  */

  if (BrowseTimeout < (2 * BrowseInterval) || BrowseTimeout <= 0)
  {
    cupsdLogMessage(CUPSD_LOG_ALERT, "Invalid BrowseTimeout value %d.",
                    BrowseTimeout);

    if (BrowseInterval)
      BrowseTimeout = BrowseInterval * 2;
    else
      BrowseTimeout = DEFAULT_TIMEOUT;

    cupsdLogMessage(CUPSD_LOG_ALERT, "Reset BrowseTimeout to %d.",
                    BrowseTimeout);
  }

 /*
  * Update the default policy, as needed...
  */

  if (DefaultPolicy)
    DefaultPolicyPtr = cupsdFindPolicy(DefaultPolicy);
  else
    DefaultPolicyPtr = NULL;

  if (!DefaultPolicyPtr)
  {
    cupsd_location_t	*po;		/* New policy operation */


    if (DefaultPolicy)
      cupsdLogMessage(CUPSD_LOG_ERROR, "Default policy \"%s\" not found.",
                      DefaultPolicy);

    cupsdSetString(&DefaultPolicy, "default");

    if ((DefaultPolicyPtr = cupsdFindPolicy("default")) != NULL)
      cupsdLogMessage(CUPSD_LOG_INFO,
                      "Using policy \"default\" as the default.");
    else
    {
      cupsdLogMessage(CUPSD_LOG_INFO,
                      "Creating CUPS default administrative policy:");

      DefaultPolicyPtr = p = cupsdAddPolicy("default");

      cupsdLogMessage(CUPSD_LOG_INFO, "<Policy default>");

      cupsdLogMessage(CUPSD_LOG_INFO, "JobPrivateAccess default");
      cupsdAddString(&(p->job_access), "@OWNER");
      cupsdAddString(&(p->job_access), "@SYSTEM");

      cupsdLogMessage(CUPSD_LOG_INFO, "JobPrivateValues default");
      cupsdAddString(&(p->job_attrs), "job-name");
      cupsdAddString(&(p->job_attrs), "job-originating-host-name");
      cupsdAddString(&(p->job_attrs), "job-originating-user-name");

      cupsdLogMessage(CUPSD_LOG_INFO, "SubscriptionPrivateAccess default");
      cupsdAddString(&(p->sub_access), "@OWNER");
      cupsdAddString(&(p->sub_access), "@SYSTEM");

      cupsdLogMessage(CUPSD_LOG_INFO, "SubscriptionPrivateValues default");
      cupsdAddString(&(p->job_attrs), "notify-events");
      cupsdAddString(&(p->job_attrs), "notify-pull-method");
      cupsdAddString(&(p->job_attrs), "notify-recipient-uri");
      cupsdAddString(&(p->job_attrs), "notify-subscriber-user-name");
      cupsdAddString(&(p->job_attrs), "notify-user-data");

      cupsdLogMessage(CUPSD_LOG_INFO,
                      "<Limit Create-Job Print-Job Print-URI Validate-Job>");
      cupsdLogMessage(CUPSD_LOG_INFO, "Order Deny,Allow");

      po = cupsdAddPolicyOp(p, NULL, IPP_CREATE_JOB);
      po->order_type = CUPSD_AUTH_ALLOW;

      cupsdAddPolicyOp(p, po, IPP_PRINT_JOB);
      cupsdAddPolicyOp(p, po, IPP_PRINT_URI);
      cupsdAddPolicyOp(p, po, IPP_VALIDATE_JOB);

      cupsdLogMessage(CUPSD_LOG_INFO, "</Limit>");

      cupsdLogMessage(CUPSD_LOG_INFO,
                      "<Limit Send-Document Send-URI Cancel-Job Hold-Job "
                      "Release-Job Restart-Job Purge-Jobs "
		      "Set-Job-Attributes Create-Job-Subscription "
		      "Renew-Subscription Cancel-Subscription "
		      "Get-Notifications Reprocess-Job Cancel-Current-Job "
		      "Suspend-Current-Job Resume-Job "
		      "Cancel-My-Jobs Close-Job CUPS-Move-Job "
		      "CUPS-Authenticate-Job CUPS-Get-Document>");
      cupsdLogMessage(CUPSD_LOG_INFO, "Order Deny,Allow");

      po = cupsdAddPolicyOp(p, NULL, IPP_SEND_DOCUMENT);
      po->order_type = CUPSD_AUTH_ALLOW;
      po->level      = CUPSD_AUTH_USER;

      cupsdAddName(po, "@OWNER");
      cupsdAddName(po, "@SYSTEM");
      cupsdLogMessage(CUPSD_LOG_INFO, "Require user @OWNER @SYSTEM");

      cupsdAddPolicyOp(p, po, IPP_SEND_URI);
      cupsdAddPolicyOp(p, po, IPP_CANCEL_JOB);
      cupsdAddPolicyOp(p, po, IPP_HOLD_JOB);
      cupsdAddPolicyOp(p, po, IPP_RELEASE_JOB);
      cupsdAddPolicyOp(p, po, IPP_RESTART_JOB);
      cupsdAddPolicyOp(p, po, IPP_PURGE_JOBS);
      cupsdAddPolicyOp(p, po, IPP_SET_JOB_ATTRIBUTES);
      cupsdAddPolicyOp(p, po, IPP_CREATE_JOB_SUBSCRIPTION);
      cupsdAddPolicyOp(p, po, IPP_RENEW_SUBSCRIPTION);
      cupsdAddPolicyOp(p, po, IPP_CANCEL_SUBSCRIPTION);
      cupsdAddPolicyOp(p, po, IPP_GET_NOTIFICATIONS);
      cupsdAddPolicyOp(p, po, IPP_REPROCESS_JOB);
      cupsdAddPolicyOp(p, po, IPP_CANCEL_CURRENT_JOB);
      cupsdAddPolicyOp(p, po, IPP_SUSPEND_CURRENT_JOB);
      cupsdAddPolicyOp(p, po, IPP_RESUME_JOB);
      cupsdAddPolicyOp(p, po, IPP_CANCEL_MY_JOBS);
      cupsdAddPolicyOp(p, po, IPP_CLOSE_JOB);
      cupsdAddPolicyOp(p, po, CUPS_MOVE_JOB);
      cupsdAddPolicyOp(p, po, CUPS_AUTHENTICATE_JOB);
      cupsdAddPolicyOp(p, po, CUPS_GET_DOCUMENT);

      cupsdLogMessage(CUPSD_LOG_INFO, "</Limit>");

      cupsdLogMessage(CUPSD_LOG_INFO,
                      "<Limit Pause-Printer Resume-Printer "
                      "Set-Printer-Attributes Enable-Printer "
		      "Disable-Printer Pause-Printer-After-Current-Job "
		      "Hold-New-Jobs Release-Held-New-Jobs "
		      "Deactivate-Printer Activate-Printer Restart-Printer "
		      "Shutdown-Printer Startup-Printer Promote-Job "
		      "Schedule-Job-After Cancel-Jobs CUPS-Add-Printer "
		      "CUPS-Delete-Printer CUPS-Add-Class CUPS-Delete-Class "
		      "CUPS-Accept-Jobs CUPS-Reject-Jobs CUPS-Set-Default>");
      cupsdLogMessage(CUPSD_LOG_INFO, "Order Deny,Allow");
      cupsdLogMessage(CUPSD_LOG_INFO, "AuthType Default");

      po = cupsdAddPolicyOp(p, NULL, IPP_PAUSE_PRINTER);
      po->order_type = CUPSD_AUTH_ALLOW;
      po->type       = CUPSD_AUTH_DEFAULT;
      po->level      = CUPSD_AUTH_USER;

      cupsdAddName(po, "@SYSTEM");
      cupsdLogMessage(CUPSD_LOG_INFO, "Require user @SYSTEM");

      cupsdAddPolicyOp(p, po, IPP_RESUME_PRINTER);
      cupsdAddPolicyOp(p, po, IPP_SET_PRINTER_ATTRIBUTES);
      cupsdAddPolicyOp(p, po, IPP_ENABLE_PRINTER);
      cupsdAddPolicyOp(p, po, IPP_DISABLE_PRINTER);
      cupsdAddPolicyOp(p, po, IPP_PAUSE_PRINTER_AFTER_CURRENT_JOB);
      cupsdAddPolicyOp(p, po, IPP_HOLD_NEW_JOBS);
      cupsdAddPolicyOp(p, po, IPP_RELEASE_HELD_NEW_JOBS);
      cupsdAddPolicyOp(p, po, IPP_DEACTIVATE_PRINTER);
      cupsdAddPolicyOp(p, po, IPP_ACTIVATE_PRINTER);
      cupsdAddPolicyOp(p, po, IPP_RESTART_PRINTER);
      cupsdAddPolicyOp(p, po, IPP_SHUTDOWN_PRINTER);
      cupsdAddPolicyOp(p, po, IPP_STARTUP_PRINTER);
      cupsdAddPolicyOp(p, po, IPP_PROMOTE_JOB);
      cupsdAddPolicyOp(p, po, IPP_SCHEDULE_JOB_AFTER);
      cupsdAddPolicyOp(p, po, IPP_CANCEL_JOBS);
      cupsdAddPolicyOp(p, po, CUPS_ADD_PRINTER);
      cupsdAddPolicyOp(p, po, CUPS_DELETE_PRINTER);
      cupsdAddPolicyOp(p, po, CUPS_ADD_CLASS);
      cupsdAddPolicyOp(p, po, CUPS_DELETE_CLASS);
      cupsdAddPolicyOp(p, po, CUPS_ACCEPT_JOBS);
      cupsdAddPolicyOp(p, po, CUPS_REJECT_JOBS);
      cupsdAddPolicyOp(p, po, CUPS_SET_DEFAULT);

      cupsdLogMessage(CUPSD_LOG_INFO, "</Limit>");

      cupsdLogMessage(CUPSD_LOG_INFO, "<Limit All>");
      cupsdLogMessage(CUPSD_LOG_INFO, "Order Deny,Allow");

      po = cupsdAddPolicyOp(p, NULL, IPP_ANY_OPERATION);
      po->order_type = CUPSD_AUTH_ALLOW;

      cupsdLogMessage(CUPSD_LOG_INFO, "</Limit>");
      cupsdLogMessage(CUPSD_LOG_INFO, "</Policy>");
    }
  }

  cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdReadConfiguration: NumPolicies=%d",
                  cupsArrayCount(Policies));
  for (i = 0, p = (cupsd_policy_t *)cupsArrayFirst(Policies);
       p;
       i ++, p = (cupsd_policy_t *)cupsArrayNext(Policies))
    cupsdLogMessage(CUPSD_LOG_DEBUG2,
                    "cupsdReadConfiguration: Policies[%d]=\"%s\"", i, p->name);

 /*
  * If we are doing a full reload or the server root has changed, flush
  * the jobs, printers, etc. and start from scratch...
  */

  if (NeedReload == RELOAD_ALL ||
      old_remote_port != RemotePort ||
      !old_serverroot || !ServerRoot || strcmp(old_serverroot, ServerRoot) ||
      !old_requestroot || !RequestRoot || strcmp(old_requestroot, RequestRoot))
  {
    mime_type_t	*type;			/* Current type */
    char	mimetype[MIME_MAX_SUPER + MIME_MAX_TYPE];
					/* MIME type name */


    cupsdLogMessage(CUPSD_LOG_INFO, "Full reload is required.");

   /*
    * Free all memory...
    */

    cupsdDeleteAllSubscriptions();
    cupsdFreeAllJobs();
    cupsdDeleteAllPrinters();

    DefaultPrinter = NULL;

    if (MimeDatabase != NULL)
      mimeDelete(MimeDatabase);

    if (NumMimeTypes)
    {
      for (i = 0; i < NumMimeTypes; i ++)
	_cupsStrFree(MimeTypes[i]);

      free(MimeTypes);
    }

   /*
    * Read the MIME type and conversion database...
    */

    snprintf(temp, sizeof(temp), "%s/filter", ServerBin);
    snprintf(mimedir, sizeof(mimedir), "%s/mime", DataDir);

    MimeDatabase = mimeNew();
    mimeSetErrorCallback(MimeDatabase, mime_error_cb, NULL);

    MimeDatabase = mimeLoadTypes(MimeDatabase, mimedir);
    MimeDatabase = mimeLoadTypes(MimeDatabase, ServerRoot);
    MimeDatabase = mimeLoadFilters(MimeDatabase, mimedir, temp);
    MimeDatabase = mimeLoadFilters(MimeDatabase, ServerRoot, temp);

    if (!MimeDatabase)
    {
      cupsdLogMessage(CUPSD_LOG_EMERG,
                      "Unable to load MIME database from \"%s\" or \"%s\".",
		      mimedir, ServerRoot);
      if (FatalErrors & CUPSD_FATAL_CONFIG)
        return (0);
    }

    cupsdLogMessage(CUPSD_LOG_INFO,
                    "Loaded MIME database from \"%s\" and \"%s\": %d types, "
		    "%d filters...", mimedir, ServerRoot,
		    mimeNumTypes(MimeDatabase), mimeNumFilters(MimeDatabase));

   /*
    * Create a list of MIME types for the document-format-supported
    * attribute...
    */

    NumMimeTypes = mimeNumTypes(MimeDatabase);
    if (!mimeType(MimeDatabase, "application", "octet-stream"))
      NumMimeTypes ++;

    if ((MimeTypes = calloc(NumMimeTypes, sizeof(const char *))) == NULL)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Unable to allocate memory for %d MIME types.",
		      NumMimeTypes);
      NumMimeTypes = 0;
    }
    else
    {
      for (i = 0, type = mimeFirstType(MimeDatabase);
	   type;
	   i ++, type = mimeNextType(MimeDatabase))
      {
	snprintf(mimetype, sizeof(mimetype), "%s/%s", type->super, type->type);

	MimeTypes[i] = _cupsStrAlloc(mimetype);
      }

      if (i < NumMimeTypes)
	MimeTypes[i] = _cupsStrAlloc("application/octet-stream");
    }

    if (LogLevel == CUPSD_LOG_DEBUG2)
    {
      mime_filter_t	*filter;	/* Current filter */


      for (type = mimeFirstType(MimeDatabase);
           type;
	   type = mimeNextType(MimeDatabase))
	cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdReadConfiguration: type %s/%s",
		        type->super, type->type);

      for (filter = mimeFirstFilter(MimeDatabase);
           filter;
	   filter = mimeNextFilter(MimeDatabase))
	cupsdLogMessage(CUPSD_LOG_DEBUG2,
	                "cupsdReadConfiguration: filter %s/%s to %s/%s %d %s",
		        filter->src->super, filter->src->type,
		        filter->dst->super, filter->dst->type,
		        filter->cost, filter->filter);
    }

   /*
    * Load banners...
    */

    snprintf(temp, sizeof(temp), "%s/banners", DataDir);
    cupsdLoadBanners(temp);

   /*
    * Load printers and classes...
    */

    cupsdLoadAllPrinters();
    cupsdLoadAllClasses();
    cupsdLoadRemoteCache();

    cupsdCreateCommonData();

   /*
    * Update the printcap file as needed...
    */

    if (Printcap && *Printcap && access(Printcap, 0))
      cupsdWritePrintcap();

   /*
    * Load queued jobs...
    */

    cupsdLoadAllJobs();

   /*
    * Load subscriptions...
    */

    cupsdLoadAllSubscriptions();

    cupsdLogMessage(CUPSD_LOG_INFO, "Full reload complete.");
  }
  else
  {
   /*
    * Not a full reload, so recreate the common printer attributes...
    */

    cupsdCreateCommonData();

   /*
    * Update all printers as needed...
    */

    cupsdUpdatePrinters();
    cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP);

    cupsdLogMessage(CUPSD_LOG_INFO, "Partial reload complete.");
  }

 /*
  * Reset the reload state...
  */

  NeedReload = RELOAD_NONE;

  cupsdClearString(&old_serverroot);
  cupsdClearString(&old_requestroot);

  return (1);
}


/*
 * 'get_address()' - Get an address + port number from a line.
 */

static http_addrlist_t *		/* O - Pointer to list if address good, NULL if bad */
get_address(const char  *value,		/* I - Value string */
	    int         defport)	/* I - Default port */
{
  char			buffer[1024],	/* Hostname + port number buffer */
			defpname[255],	/* Default port name */
			*hostname,	/* Hostname or IP */
			*portname;	/* Port number or name */
  http_addrlist_t	*addrlist;	/* Address list */


 /*
  * Check for an empty value...
  */

  if (!*value)
  {
    cupsdLogMessage(CUPSD_LOG_ERROR, "Bad (empty) address.");
    return (NULL);
  }

 /*
  * Grab a hostname and port number; if there is no colon and the port name
  * is only digits, then we have a port number by itself...
  */

  strlcpy(buffer, value, sizeof(buffer));

  if ((portname = strrchr(buffer, ':')) != NULL && !strchr(portname, ']'))
  {
    *portname++ = '\0';
    hostname = buffer;
  }
  else
  {
    for (portname = buffer; isdigit(*portname & 255); portname ++);

    if (*portname)
    {
     /*
      * Use the default port...
      */

      sprintf(defpname, "%d", defport);
      portname = defpname;
      hostname = buffer;
    }
    else
    {
     /*
      * The buffer contains just a port number...
      */

      portname = buffer;
      hostname = NULL;
    }
  }

  if (hostname && !strcmp(hostname, "*"))
    hostname = NULL;

 /*
  * Now lookup the address using httpAddrGetList()...
  */

  if ((addrlist = httpAddrGetList(hostname, AF_UNSPEC, portname)) == NULL)
    cupsdLogMessage(CUPSD_LOG_ERROR, "Hostname lookup for \"%s\" failed.",
                    hostname ? hostname : "(nil)");

  return (addrlist);
}


/*
 * 'get_addr_and_mask()' - Get an IP address and netmask.
 */

static int				/* O - 1 on success, 0 on failure */
get_addr_and_mask(const char *value,	/* I - String from config file */
                  unsigned   *ip,	/* O - Address value */
		  unsigned   *mask)	/* O - Mask value */
{
  int		i, j,			/* Looping vars */
		family,			/* Address family */
		ipcount;		/* Count of fields in address */
  unsigned	ipval;			/* Value */
  const char	*maskval,		/* Pointer to start of mask value */
		*ptr,			/* Pointer into value */
		*ptr2;			/* ... */


 /*
  * Get the address...
  */

  ip[0]   = ip[1]   = ip[2]   = ip[3]   = 0x00000000;
  mask[0] = mask[1] = mask[2] = mask[3] = 0xffffffff;

  if ((maskval = strchr(value, '/')) != NULL)
    maskval ++;
  else
    maskval = value + strlen(value);

#ifdef AF_INET6
 /*
  * Check for an IPv6 address...
  */

  if (*value == '[')
  {
   /*
    * Parse hexadecimal IPv6/IPv4 address...
    */

    family  = AF_INET6;

    for (i = 0, ptr = value + 1; *ptr && i < 8; i ++)
    {
      if (*ptr == ']')
        break;
      else if (!strncmp(ptr, "::", 2))
      {
        for (ptr2 = strchr(ptr + 2, ':'), j = 0;
	     ptr2;
	     ptr2 = strchr(ptr2 + 1, ':'), j ++);

        i = 6 - j;
	ptr += 2;
      }
      else if (isdigit(*ptr & 255) && strchr(ptr + 1, '.') && i >= 6)
      {
       /*
        * Read IPv4 dotted quad...
        */

	unsigned val[4] = { 0, 0, 0, 0 };
					/* IPv4 address values */

	ipcount = sscanf(ptr, "%u.%u.%u.%u", val + 0, val + 1, val + 2,
	                 val + 3);

       /*
	* Range check the IP numbers...
	*/

	for (i = 0; i < ipcount; i ++)
	  if (val[i] > 255)
	    return (0);

       /*
	* Merge everything into a 32-bit IPv4 address in ip[3]...
	*/

	ip[3] = (((((val[0] << 8) | val[1]) << 8) | val[2]) << 8) | val[3];

	if (ipcount < 4)
	  mask[3] = (0xffffffff << (32 - 8 * ipcount)) & 0xffffffff;

       /*
        * If the leading words are all 0's then this is an IPv4 address...
        */

        if (!val[0] && !val[1] && !val[2])
	  family  = AF_INET;

        while (isdigit(*ptr & 255) || *ptr == '.')
          ptr ++;
	break;
      }
      else if (isxdigit(*ptr & 255))
      {
        ipval = strtoul(ptr, (char **)&ptr, 16);

	if (*ptr == ':' && ptr[1] != ':')
	  ptr ++;

	if (ipval > 0xffff)
	  return (0);

        if (i & 1)
          ip[i / 2] |= ipval;
	else
          ip[i / 2] |= ipval << 16;
      }
      else
        return (0);
    }

    if (*ptr != ']')
      return (0);

    ptr ++;

    if (*ptr && *ptr != '/')
      return (0);
  }
  else
#endif /* AF_INET6 */
  {
   /*
    * Parse dotted-decimal IPv4 address...
    */

    unsigned val[4] = { 0, 0, 0, 0 };	/* IPv4 address values */


    family  = AF_INET;
    ipcount = sscanf(value, "%u.%u.%u.%u", val + 0, val + 1, val + 2, val + 3);

   /*
    * Range check the IP numbers...
    */

    for (i = 0; i < ipcount; i ++)
      if (val[i] > 255)
        return (0);

   /*
    * Merge everything into a 32-bit IPv4 address in ip[3]...
    */

    ip[3] = (((((val[0] << 8) | val[1]) << 8) | val[2]) << 8) | val[3];

    if (ipcount < 4)
      mask[3] = (0xffffffff << (32 - 8 * ipcount)) & 0xffffffff;
  }

  if (*maskval)
  {
   /*
    * Get the netmask value(s)...
    */

    memset(mask, 0, sizeof(unsigned) * 4);

    if (strchr(maskval, '.'))
    {
     /*
      * Get dotted-decimal mask...
      */

      if (family != AF_INET)
        return (0);

      if (sscanf(maskval, "%u.%u.%u.%u", mask + 0, mask + 1, mask + 2,
                 mask + 3) != 4)
        return (0);

      mask[3] |= ((((mask[0] << 8) | mask[1]) << 8) | mask[2]) << 8;
      mask[0] = mask[1] = mask[2] = 0;
    }
    else
    {
     /*
      * Get address/bits format...
      */

      i = atoi(maskval);

#ifdef AF_INET6
      if (family == AF_INET6)
      {
        if (i > 128)
	  return (0);

        i = 128 - i;

	if (i <= 96)
	  mask[0] = 0xffffffff;
	else
	  mask[0] = (0xffffffff << (i - 96)) & 0xffffffff;

	if (i <= 64)
	  mask[1] = 0xffffffff;
	else if (i >= 96)
	  mask[1] = 0;
	else
	  mask[1] = (0xffffffff << (i - 64)) & 0xffffffff;

	if (i <= 32)
	  mask[2] = 0xffffffff;
	else if (i >= 64)
	  mask[2] = 0;
	else
	  mask[2] = (0xffffffff << (i - 32)) & 0xffffffff;

	if (i == 0)
	  mask[3] = 0xffffffff;
	else if (i >= 32)
	  mask[3] = 0;
	else
	  mask[3] = (0xffffffff << i) & 0xffffffff;
      }
      else
#endif /* AF_INET6 */
      {
        if (i > 32)
	  return (0);

        mask[0] = 0xffffffff;
        mask[1] = 0xffffffff;
        mask[2] = 0xffffffff;

	if (i < 32)
          mask[3] = (0xffffffff << (32 - i)) & 0xffffffff;
	else
	  mask[3] = 0xffffffff;
      }
    }
  }

  cupsdLogMessage(CUPSD_LOG_DEBUG2,
                  "get_addr_and_mask(value=\"%s\", "
                  "ip=[%08x:%08x:%08x:%08x], mask=[%08x:%08x:%08x:%08x])",
             value, ip[0], ip[1], ip[2], ip[3], mask[0], mask[1], mask[2],
	     mask[3]);

 /*
  * Check for a valid netmask; no fallback like in CUPS 1.1.x!
  */

  if ((ip[0] & ~mask[0]) != 0 ||
      (ip[1] & ~mask[1]) != 0 ||
      (ip[2] & ~mask[2]) != 0 ||
      (ip[3] & ~mask[3]) != 0)
    return (0);

  return (1);
}


/*
 * 'mime_error_cb()' - Log a MIME error.
 */

static void
mime_error_cb(void       *ctx,		/* I - Context pointer (unused) */
              const char *message)	/* I - Message */
{
  (void)ctx;

  cupsdLogMessage(CUPSD_LOG_ERROR, "%s", message);
}


/*
 * 'parse_aaa()' - Parse authentication, authorization, and access control lines.
 */

static int				/* O - 1 on success, 0 on failure */
parse_aaa(cupsd_location_t *loc,	/* I - Location */
          char             *line,	/* I - Line from file */
	  char             *value,	/* I - Start of value data */
	  int              linenum)	/* I - Current line number */
{
  char		*valptr;		/* Pointer into value */
  unsigned	ip[4],			/* IP address components */
 		mask[4];		/* IP netmask components */


  if (!_cups_strcasecmp(line, "Encryption"))
  {
   /*
    * "Encryption xxx" - set required encryption level...
    */

    if (!_cups_strcasecmp(value, "never"))
      loc->encryption = HTTP_ENCRYPT_NEVER;
    else if (!_cups_strcasecmp(value, "always"))
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Encryption value \"%s\" on line %d is invalid in this "
		      "context. Using \"required\" instead.", value, linenum);

      loc->encryption = HTTP_ENCRYPT_REQUIRED;
    }
    else if (!_cups_strcasecmp(value, "required"))
      loc->encryption = HTTP_ENCRYPT_REQUIRED;
    else if (!_cups_strcasecmp(value, "ifrequested"))
      loc->encryption = HTTP_ENCRYPT_IF_REQUESTED;
    else
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Unknown Encryption value %s on line %d.", value, linenum);
      return (0);
    }
  }
  else if (!_cups_strcasecmp(line, "Order"))
  {
   /*
    * "Order Deny,Allow" or "Order Allow,Deny"...
    */

    if (!_cups_strncasecmp(value, "deny", 4))
      loc->order_type = CUPSD_AUTH_ALLOW;
    else if (!_cups_strncasecmp(value, "allow", 5))
      loc->order_type = CUPSD_AUTH_DENY;
    else
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown Order value %s on line %d.",
	              value, linenum);
      return (0);
    }
  }
  else if (!_cups_strcasecmp(line, "Allow") || !_cups_strcasecmp(line, "Deny"))
  {
   /*
    * Allow [From] host/ip...
    * Deny [From] host/ip...
    */

    while (*value)
    {
      if (!_cups_strncasecmp(value, "from", 4))
      {
       /*
	* Strip leading "from"...
	*/

	value += 4;

	while (_cups_isspace(*value))
	  value ++;

        if (!*value)
	  break;
      }

     /*
      * Find the end of the value...
      */

      for (valptr = value; *valptr && !_cups_isspace(*valptr); valptr ++);

      while (_cups_isspace(*valptr))
        *valptr++ = '\0';

     /*
      * Figure out what form the allow/deny address takes:
      *
      *    All
      *    None
      *    *.domain.com
      *    .domain.com
      *    host.domain.com
      *    nnn.*
      *    nnn.nnn.*
      *    nnn.nnn.nnn.*
      *    nnn.nnn.nnn.nnn
      *    nnn.nnn.nnn.nnn/mm
      *    nnn.nnn.nnn.nnn/mmm.mmm.mmm.mmm
      */

      if (!_cups_strcasecmp(value, "all"))
      {
       /*
	* All hosts...
	*/

	if (!_cups_strcasecmp(line, "Allow"))
	  cupsdAddIPMask(&(loc->allow), zeros, zeros);
	else
	  cupsdAddIPMask(&(loc->deny), zeros, zeros);
      }
      else if (!_cups_strcasecmp(value, "none"))
      {
       /*
	* No hosts...
	*/

	if (!_cups_strcasecmp(line, "Allow"))
	  cupsdAddIPMask(&(loc->allow), ones, zeros);
	else
	  cupsdAddIPMask(&(loc->deny), ones, zeros);
      }
#ifdef AF_INET6
      else if (value[0] == '*' || value[0] == '.' ||
	       (!isdigit(value[0] & 255) && value[0] != '['))
#else
      else if (value[0] == '*' || value[0] == '.' || !isdigit(value[0] & 255))
#endif /* AF_INET6 */
      {
       /*
	* Host or domain name...
	*/

	if (value[0] == '*')
	  value ++;

	if (!_cups_strcasecmp(line, "Allow"))
	  cupsdAddNameMask(&(loc->allow), value);
	else
	  cupsdAddNameMask(&(loc->deny), value);
      }
      else
      {
       /*
	* One of many IP address forms...
	*/

	if (!get_addr_and_mask(value, ip, mask))
	{
	  cupsdLogMessage(CUPSD_LOG_ERROR, "Bad netmask value %s on line %d.",
			  value, linenum);
	  return (0);
	}

	if (!_cups_strcasecmp(line, "Allow"))
	  cupsdAddIPMask(&(loc->allow), ip, mask);
	else
	  cupsdAddIPMask(&(loc->deny), ip, mask);
      }

     /*
      * Advance to next value...
      */

      value = valptr;
    }
  }
  else if (!_cups_strcasecmp(line, "AuthType"))
  {
   /*
    * AuthType {none,basic,digest,basicdigest,negotiate,default}
    */

    if (!_cups_strcasecmp(value, "none"))
    {
      loc->type  = CUPSD_AUTH_NONE;
      loc->level = CUPSD_AUTH_ANON;
    }
    else if (!_cups_strcasecmp(value, "basic"))
    {
      loc->type = CUPSD_AUTH_BASIC;

      if (loc->level == CUPSD_AUTH_ANON)
	loc->level = CUPSD_AUTH_USER;
    }
    else if (!_cups_strcasecmp(value, "digest"))
    {
      loc->type = CUPSD_AUTH_DIGEST;

      if (loc->level == CUPSD_AUTH_ANON)
	loc->level = CUPSD_AUTH_USER;
    }
    else if (!_cups_strcasecmp(value, "basicdigest"))
    {
      loc->type = CUPSD_AUTH_BASICDIGEST;

      if (loc->level == CUPSD_AUTH_ANON)
	loc->level = CUPSD_AUTH_USER;
    }
    else if (!_cups_strcasecmp(value, "default"))
    {
      loc->type = CUPSD_AUTH_DEFAULT;

      if (loc->level == CUPSD_AUTH_ANON)
	loc->level = CUPSD_AUTH_USER;
    }
#ifdef HAVE_GSSAPI
    else if (!_cups_strcasecmp(value, "negotiate"))
    {
      loc->type = CUPSD_AUTH_NEGOTIATE;

      if (loc->level == CUPSD_AUTH_ANON)
	loc->level = CUPSD_AUTH_USER;
    }
#endif /* HAVE_GSSAPI */
    else
    {
      cupsdLogMessage(CUPSD_LOG_WARN,
                      "Unknown authorization type %s on line %d.",
	              value, linenum);
      return (0);
    }
  }
  else if (!_cups_strcasecmp(line, "AuthClass"))
  {
   /*
    * AuthClass anonymous, user, system, group
    */

    if (!_cups_strcasecmp(value, "anonymous"))
    {
      loc->type  = CUPSD_AUTH_NONE;
      loc->level = CUPSD_AUTH_ANON;

      cupsdLogMessage(CUPSD_LOG_WARN,
                      "\"AuthClass %s\" is deprecated; consider removing "
		      "it from line %d.",
	              value, linenum);
    }
    else if (!_cups_strcasecmp(value, "user"))
    {
      loc->level = CUPSD_AUTH_USER;

      cupsdLogMessage(CUPSD_LOG_WARN,
                      "\"AuthClass %s\" is deprecated; consider using "
		      "\"Require valid-user\" on line %d.",
	              value, linenum);
    }
    else if (!_cups_strcasecmp(value, "group"))
    {
      loc->level = CUPSD_AUTH_GROUP;

      cupsdLogMessage(CUPSD_LOG_WARN,
                      "\"AuthClass %s\" is deprecated; consider using "
		      "\"Require user @groupname\" on line %d.",
	              value, linenum);
    }
    else if (!_cups_strcasecmp(value, "system"))
    {
      loc->level = CUPSD_AUTH_GROUP;

      cupsdAddName(loc, "@SYSTEM");

      cupsdLogMessage(CUPSD_LOG_WARN,
                      "\"AuthClass %s\" is deprecated; consider using "
		      "\"Require user @SYSTEM\" on line %d.",
	              value, linenum);
    }
    else
    {
      cupsdLogMessage(CUPSD_LOG_WARN,
                      "Unknown authorization class %s on line %d.",
	              value, linenum);
      return (0);
    }
  }
  else if (!_cups_strcasecmp(line, "AuthGroupName"))
  {
    cupsdAddName(loc, value);

    cupsdLogMessage(CUPSD_LOG_WARN,
                    "\"AuthGroupName %s\" directive is deprecated; consider "
		    "using \"Require user @%s\" on line %d.",
		    value, value, linenum);
  }
  else if (!_cups_strcasecmp(line, "Require"))
  {
   /*
    * Apache synonym for AuthClass and AuthGroupName...
    *
    * Get initial word:
    *
    *     Require valid-user
    *     Require group names
    *     Require user names
    */

    for (valptr = value; !_cups_isspace(*valptr) && *valptr; valptr ++);

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

    if (!_cups_strcasecmp(value, "valid-user") ||
        !_cups_strcasecmp(value, "user"))
      loc->level = CUPSD_AUTH_USER;
    else if (!_cups_strcasecmp(value, "group"))
      loc->level = CUPSD_AUTH_GROUP;
    else
    {
      cupsdLogMessage(CUPSD_LOG_WARN, "Unknown Require type %s on line %d.",
	              value, linenum);
      return (0);
    }

   /*
    * Get the list of names from the line...
    */

    for (value = valptr; *value;)
    {
      while (_cups_isspace(*value))
	value ++;

#ifdef HAVE_AUTHORIZATION_H
      if (!strncmp(value, "@AUTHKEY(", 9))
      {
       /*
	* Grab "@AUTHKEY(name)" value...
	*/

        for (valptr = value + 9; *valptr != ')' && *valptr; valptr ++);

	if (*valptr)
	  *valptr++ = '\0';
      }
      else
#endif /* HAVE_AUTHORIZATION_H */
      if (*value == '\"' || *value == '\'')
      {
       /*
	* Grab quoted name...
	*/

        for (valptr = value + 1; *valptr != *value && *valptr; valptr ++);

	value ++;
      }
      else
      {
       /*
	* Grab literal name.
	*/

        for (valptr = value; !_cups_isspace(*valptr) && *valptr; valptr ++);
      }

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

      cupsdAddName(loc, value);

      for (value = valptr; _cups_isspace(*value); value ++);
    }
  }
  else if (!_cups_strcasecmp(line, "Satisfy"))
  {
    if (!_cups_strcasecmp(value, "all"))
      loc->satisfy = CUPSD_AUTH_SATISFY_ALL;
    else if (!_cups_strcasecmp(value, "any"))
      loc->satisfy = CUPSD_AUTH_SATISFY_ANY;
    else
    {
      cupsdLogMessage(CUPSD_LOG_WARN, "Unknown Satisfy value %s on line %d.",
                      value, linenum);
      return (0);
    }
  }
  else
    return (0);

  return (1);
}


/*
 * 'parse_fatal_errors()' - Parse FatalErrors values in a string.
 */

static int				/* O - FatalErrors bits */
parse_fatal_errors(const char *s)	/* I - FatalErrors string */
{
  int	fatal;				/* FatalErrors bits */
  char	value[1024],			/* Value string */
	*valstart,			/* Pointer into value */
	*valend;			/* End of value */


 /*
  * Empty FatalErrors line yields NULL pointer...
  */

  if (!s)
    return (CUPSD_FATAL_NONE);

 /*
  * Loop through the value string,...
  */

  strlcpy(value, s, sizeof(value));

  fatal = CUPSD_FATAL_NONE;

  for (valstart = value; *valstart;)
  {
   /*
    * Get the current space/comma-delimited kind name...
    */

    for (valend = valstart; *valend; valend ++)
      if (_cups_isspace(*valend) || *valend == ',')
	break;

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

   /*
    * Add the error to the bitmask...
    */

    if (!_cups_strcasecmp(valstart, "all"))
      fatal = CUPSD_FATAL_ALL;
    else if (!_cups_strcasecmp(valstart, "browse"))
      fatal |= CUPSD_FATAL_BROWSE;
    else if (!_cups_strcasecmp(valstart, "-browse"))
      fatal &= ~CUPSD_FATAL_BROWSE;
    else if (!_cups_strcasecmp(valstart, "config"))
      fatal |= CUPSD_FATAL_CONFIG;
    else if (!_cups_strcasecmp(valstart, "-config"))
      fatal &= ~CUPSD_FATAL_CONFIG;
    else if (!_cups_strcasecmp(valstart, "listen"))
      fatal |= CUPSD_FATAL_LISTEN;
    else if (!_cups_strcasecmp(valstart, "-listen"))
      fatal &= ~CUPSD_FATAL_LISTEN;
    else if (!_cups_strcasecmp(valstart, "log"))
      fatal |= CUPSD_FATAL_LOG;
    else if (!_cups_strcasecmp(valstart, "-log"))
      fatal &= ~CUPSD_FATAL_LOG;
    else if (!_cups_strcasecmp(valstart, "permissions"))
      fatal |= CUPSD_FATAL_PERMISSIONS;
    else if (!_cups_strcasecmp(valstart, "-permissions"))
      fatal &= ~CUPSD_FATAL_PERMISSIONS;
    else if (_cups_strcasecmp(valstart, "none"))
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Unknown FatalErrors kind \"%s\" ignored.", valstart);

    for (valstart = valend; *valstart; valstart ++)
      if (!_cups_isspace(*valstart) || *valstart != ',')
	break;
  }

  return (fatal);
}


/*
 * 'parse_groups()' - Parse system group names in a string.
 */

static int				/* O - 1 on success, 0 on failure */
parse_groups(const char *s)		/* I - Space-delimited groups */
{
  int		status;			/* Return status */
  char		value[1024],		/* Value string */
		*valstart,		/* Pointer into value */
		*valend,		/* End of value */
		quote;			/* Quote character */
  struct group	*group;			/* Group */


 /*
  * Make a copy of the string and parse out the groups...
  */

  strlcpy(value, s, sizeof(value));

  status   = 1;
  valstart = value;

  while (*valstart && NumSystemGroups < MAX_SYSTEM_GROUPS)
  {
    if (*valstart == '\'' || *valstart == '\"')
    {
     /*
      * Scan quoted name...
      */

      quote = *valstart++;

      for (valend = valstart; *valend; valend ++)
	if (*valend == quote)
	  break;
    }
    else
    {
     /*
      * Scan space or comma-delimited name...
      */

      for (valend = valstart; *valend; valend ++)
	if (_cups_isspace(*valend) || *valend == ',')
	  break;
    }

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

    group = getgrnam(valstart);
    if (group)
    {
      cupsdSetString(SystemGroups + NumSystemGroups, valstart);
      SystemGroupIDs[NumSystemGroups] = group->gr_gid;

      NumSystemGroups ++;
    }
    else
      status = 0;

    endgrent();

    valstart = valend;

    while (*valstart == ',' || _cups_isspace(*valstart))
      valstart ++;
  }

  return (status);
}


/*
 * 'parse_protocols()' - Parse browse protocols in a string.
 */

static int				/* O - Browse protocol bits */
parse_protocols(const char *s)		/* I - Space-delimited protocols */
{
  int	protocols;			/* Browse protocol bits */
  char	value[1024],			/* Value string */
	*valstart,			/* Pointer into value */
	*valend;			/* End of value */


 /*
  * Empty protocol line yields NULL pointer...
  */

  if (!s)
    return (0);

 /*
  * Loop through the value string,...
  */

  strlcpy(value, s, sizeof(value));

  protocols = 0;

  for (valstart = value; *valstart;)
  {
   /*
    * Get the current space/comma-delimited protocol name...
    */

    for (valend = valstart; *valend; valend ++)
      if (_cups_isspace(*valend) || *valend == ',')
	break;

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

   /*
    * Add the protocol to the bitmask...
    */

    if (!_cups_strcasecmp(valstart, "cups"))
      protocols |= BROWSE_CUPS;
    else if (!_cups_strcasecmp(valstart, "slp"))
      protocols |= BROWSE_SLP;
    else if (!_cups_strcasecmp(valstart, "ldap"))
      protocols |= BROWSE_LDAP;
    else if (!_cups_strcasecmp(valstart, "dnssd") ||
             !_cups_strcasecmp(valstart, "dns-sd") ||
             !_cups_strcasecmp(valstart, "bonjour"))
      protocols |= BROWSE_DNSSD;
    else if (!_cups_strcasecmp(valstart, "lpd"))
      protocols |= BROWSE_LPD;
    else if (!_cups_strcasecmp(valstart, "smb"))
      protocols |= BROWSE_SMB;
    else if (!_cups_strcasecmp(valstart, "all"))
      protocols |= BROWSE_ALL;
    else if (_cups_strcasecmp(valstart, "none"))
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Unknown browse protocol \"%s\" ignored.", valstart);

    for (valstart = valend; *valstart; valstart ++)
      if (!_cups_isspace(*valstart) || *valstart != ',')
	break;
  }

  return (protocols);
}


/*
 * 'read_configuration()' - Read a configuration file.
 */

static int				/* O - 1 on success, 0 on failure */
read_configuration(cups_file_t *fp)	/* I - File to read from */
{
  int			i;		/* Looping var */
  int			linenum;	/* Current line number */
  char			line[HTTP_MAX_BUFFER],
					/* Line from file */
			temp[HTTP_MAX_BUFFER],
					/* Temporary buffer for value */
			*ptr,		/* Pointer into line/temp */
			*value,		/* Pointer to value */
			*valueptr;	/* Pointer into value */
  int			valuelen;	/* Length of value */
  cupsd_var_t const	*var;		/* Current variable */
  http_addrlist_t	*addrlist,	/* Address list */
			*addr;		/* Current address */
  unsigned		ip[4],		/* Address value */
			mask[4];	/* Netmask value */
  cupsd_dirsvc_relay_t	*relay;		/* Relay data */
  cupsd_dirsvc_poll_t	*pollp;		/* Polling data */
  cupsd_location_t	*location;	/* Browse location */
  cups_file_t		*incfile;	/* Include file */
  char			incname[1024];	/* Include filename */
  struct group		*group;		/* Group */


 /*
  * Loop through each line in the file...
  */

  linenum = 0;

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

    if (!_cups_strcasecmp(line, "Include") && value)
    {
     /*
      * Include filename
      */

      if (value[0] == '/')
        strlcpy(incname, value, sizeof(incname));
      else
        snprintf(incname, sizeof(incname), "%s/%s", ServerRoot, value);

      if ((incfile = cupsFileOpen(incname, "rb")) == NULL)
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Unable to include config file \"%s\" - %s",
	                incname, strerror(errno));
      else
      {
        read_configuration(incfile);
	cupsFileClose(incfile);
      }
    }
    else if (!_cups_strcasecmp(line, "<Location") && value)
    {
     /*
      * <Location path>
      */

      linenum = read_location(fp, value, linenum);
      if (linenum == 0)
	return (0);
    }
    else if (!_cups_strcasecmp(line, "<Policy") && value)
    {
     /*
      * <Policy name>
      */

      linenum = read_policy(fp, value, linenum);
      if (linenum == 0)
	return (0);
    }
    else if (!_cups_strcasecmp(line, "FatalErrors"))
      FatalErrors = parse_fatal_errors(value);
    else if (!_cups_strcasecmp(line, "FaxRetryInterval") && value)
    {
      JobRetryInterval = atoi(value);
      cupsdLogMessage(CUPSD_LOG_WARN,
		      "FaxRetryInterval is deprecated; use "
		      "JobRetryInterval on line %d.", linenum);
    }
    else if (!_cups_strcasecmp(line, "FaxRetryLimit") && value)
    {
      JobRetryLimit = atoi(value);
      cupsdLogMessage(CUPSD_LOG_WARN,
		      "FaxRetryLimit is deprecated; use "
		      "JobRetryLimit on line %d.", linenum);
    }
    else if ((!_cups_strcasecmp(line, "Port") || !_cups_strcasecmp(line, "Listen")
#ifdef HAVE_SSL
             || !_cups_strcasecmp(line, "SSLPort") || !_cups_strcasecmp(line, "SSLListen")
#endif /* HAVE_SSL */
	     ) && value)
    {
     /*
      * Add listening address(es) to the list...
      */

      cupsd_listener_t	*lis;		/* New listeners array */


     /*
      * Get the address list...
      */

      addrlist = get_address(value, IPP_PORT);

      if (!addrlist)
      {
        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad %s address %s at line %d.", line,
	                value, linenum);
        continue;
      }

     /*
      * Add each address...
      */

      for (addr = addrlist; addr; addr = addr->next)
      {
       /*
        * See if this address is already present...
	*/

        for (lis = (cupsd_listener_t *)cupsArrayFirst(Listeners);
	     lis;
	     lis = (cupsd_listener_t *)cupsArrayNext(Listeners))
          if (httpAddrEqual(&(addr->addr), &(lis->address)) &&
	      _httpAddrPort(&(addr->addr)) == _httpAddrPort(&(lis->address)))
	    break;

        if (lis)
	{
	  httpAddrString(&lis->address, temp, sizeof(temp));
	  cupsdLogMessage(CUPSD_LOG_WARN,
	                  "Duplicate listen address \"%s\" ignored.", temp);
          continue;
	}

       /*
        * Allocate another listener...
	*/

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

	if (!Listeners)
	{
          cupsdLogMessage(CUPSD_LOG_ERROR,
	                  "Unable to allocate %s at line %d - %s.",
	                  line, linenum, strerror(errno));
          break;
	}

        if ((lis = calloc(1, sizeof(cupsd_listener_t))) == NULL)
	{
          cupsdLogMessage(CUPSD_LOG_ERROR,
	                  "Unable to allocate %s at line %d - %s.",
	                  line, linenum, strerror(errno));
          break;
	}

        cupsArrayAdd(Listeners, lis);

       /*
        * Copy the current address and log it...
	*/

	memcpy(&(lis->address), &(addr->addr), sizeof(lis->address));
	lis->fd = -1;

#ifdef HAVE_SSL
        if (!_cups_strcasecmp(line, "SSLPort") || !_cups_strcasecmp(line, "SSLListen"))
          lis->encryption = HTTP_ENCRYPT_ALWAYS;
#endif /* HAVE_SSL */

	httpAddrString(&lis->address, temp, sizeof(temp));

#ifdef AF_LOCAL
        if (lis->address.addr.sa_family == AF_LOCAL)
          cupsdLogMessage(CUPSD_LOG_INFO, "Listening to %s (Domain)", temp);
	else
#endif /* AF_LOCAL */
	cupsdLogMessage(CUPSD_LOG_INFO, "Listening to %s:%d (IPv%d)", temp,
                        _httpAddrPort(&(lis->address)),
			_httpAddrFamily(&(lis->address)) == AF_INET ? 4 : 6);

        if (!httpAddrLocalhost(&(lis->address)))
	  RemotePort = _httpAddrPort(&(lis->address));
      }

     /*
      * Free the list...
      */

      httpAddrFreeList(addrlist);
    }
    else if (!_cups_strcasecmp(line, "BrowseAddress") && value)
    {
     /*
      * Add a browse address to the list...
      */

      cupsd_dirsvc_addr_t	*dira;	/* New browse address array */


      if (NumBrowsers == 0)
        dira = malloc(sizeof(cupsd_dirsvc_addr_t));
      else
        dira = realloc(Browsers, (NumBrowsers + 1) * sizeof(cupsd_dirsvc_addr_t));

      if (!dira)
      {
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Unable to allocate BrowseAddress at line %d - %s.",
	                linenum, strerror(errno));
        continue;
      }

      Browsers = dira;
      dira     += NumBrowsers;

      memset(dira, 0, sizeof(cupsd_dirsvc_addr_t));

      if (!_cups_strcasecmp(value, "@LOCAL"))
      {
       /*
	* Send browse data to all local interfaces...
	*/

	strcpy(dira->iface, "*");
	NumBrowsers ++;
      }
      else if (!_cups_strncasecmp(value, "@IF(", 4))
      {
       /*
	* Send browse data to the named interface...
	*/

	strlcpy(dira->iface, value + 4, sizeof(Browsers[0].iface));

        ptr = dira->iface + strlen(dira->iface) - 1;
        if (*ptr == ')')
	  *ptr = '\0';

	NumBrowsers ++;
      }
      else if ((addrlist = get_address(value, BrowsePort)) != NULL)
      {
       /*
        * Only IPv4 addresses are supported...
        */

	for (addr = addrlist; addr; addr = addr->next)
	  if (_httpAddrFamily(&(addr->addr)) == AF_INET)
	    break;

	if (addr)
	{
	  memcpy(&(dira->to), &(addrlist->addr), sizeof(dira->to));
	  httpAddrString(&(dira->to), temp, sizeof(temp));

	  cupsdLogMessage(CUPSD_LOG_INFO,
	                  "Sending browsing info to %s:%d (IPv4)",
			  temp, _httpAddrPort(&(dira->to)));

	  NumBrowsers ++;
	}
	else
	  cupsdLogMessage(CUPSD_LOG_ERROR, "Bad BrowseAddress %s at line %d.",
			  value, linenum);

	httpAddrFreeList(addrlist);
      }
      else
        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad BrowseAddress %s at line %d.",
	                value, linenum);
    }
    else if (!_cups_strcasecmp(line, "BrowseOrder") && value)
    {
     /*
      * "BrowseOrder Deny,Allow" or "BrowseOrder Allow,Deny"...
      */

      if ((location = cupsdFindLocation("CUPS_INTERNAL_BROWSE_ACL")) == NULL)
        if ((location = cupsdNewLocation("CUPS_INTERNAL_BROWSE_ACL")) != NULL)
	  cupsdAddLocation(location);

      if (location == NULL)
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Unable to initialize browse access control list.");
      else if (!_cups_strncasecmp(value, "deny", 4))
        location->order_type = CUPSD_AUTH_ALLOW;
      else if (!_cups_strncasecmp(value, "allow", 5))
        location->order_type = CUPSD_AUTH_DENY;
      else
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Unknown BrowseOrder value %s on line %d.",
	                value, linenum);
    }
    else if (!_cups_strcasecmp(line, "BrowseProtocols") ||
             !_cups_strcasecmp(line, "BrowseLocalProtocols") ||
             !_cups_strcasecmp(line, "BrowseRemoteProtocols"))
    {
     /*
      * "BrowseProtocols name [... name]"
      * "BrowseLocalProtocols name [... name]"
      * "BrowseRemoteProtocols name [... name]"
      */

      int protocols = parse_protocols(value);

      if (protocols < 0)
      {
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Unknown browse protocol \"%s\" on line %d.",
	                value, linenum);
        break;
      }

      if (_cups_strcasecmp(line, "BrowseLocalProtocols"))
        BrowseRemoteProtocols = protocols;
      if (_cups_strcasecmp(line, "BrowseRemoteProtocols"))
        BrowseLocalProtocols = protocols;
    }
    else if ((!_cups_strcasecmp(line, "BrowseAllow") ||
              !_cups_strcasecmp(line, "BrowseDeny")) && value)
    {
     /*
      * BrowseAllow [From] host/ip...
      * BrowseDeny [From] host/ip...
      */

      if ((location = cupsdFindLocation("CUPS_INTERNAL_BROWSE_ACL")) == NULL)
        if ((location = cupsdNewLocation("CUPS_INTERNAL_BROWSE_ACL")) != NULL)
	  cupsdAddLocation(location);


      if (location == NULL)
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Unable to initialize browse access control list.");
      else
      {
	if (!_cups_strncasecmp(value, "from", 4))
	{
	 /*
	  * Skip leading "from"...
	  */

	  value += 4;
	}

	while (*value)
	{
	 /*
	  * Skip leading whitespace...
	  */

	  while (_cups_isspace(*value))
	    value ++;

	  if (!*value)
	    break;

	 /*
	  * Find the end of the value...
	  */

	  for (valueptr = value;
	       *valueptr && !_cups_isspace(*valueptr);
	       valueptr ++);

	  while (_cups_isspace(*valueptr))
	    *valueptr++ = '\0';

	 /*
	  * Figure out what form the allow/deny address takes:
	  *
	  *    All
	  *    None
	  *    *.domain.com
	  *    .domain.com
	  *    host.domain.com
	  *    nnn.*
	  *    nnn.nnn.*
	  *    nnn.nnn.nnn.*
	  *    nnn.nnn.nnn.nnn
	  *    nnn.nnn.nnn.nnn/mm
	  *    nnn.nnn.nnn.nnn/mmm.mmm.mmm.mmm
	  */

	  if (!_cups_strcasecmp(value, "all"))
	  {
	   /*
	    * All hosts...
	    */

	    if (!_cups_strcasecmp(line, "BrowseAllow"))
	      cupsdAddIPMask(&(location->allow), zeros, zeros);
	    else
	      cupsdAddIPMask(&(location->deny), zeros, zeros);
	  }
	  else if (!_cups_strcasecmp(value, "none"))
	  {
	   /*
	    * No hosts...
	    */

	    if (!_cups_strcasecmp(line, "BrowseAllow"))
	      cupsdAddIPMask(&(location->allow), ones, zeros);
	    else
	      cupsdAddIPMask(&(location->deny), ones, zeros);
	  }
#ifdef AF_INET6
	  else if (value[0] == '*' || value[0] == '.' ||
		   (!isdigit(value[0] & 255) && value[0] != '['))
#else
	  else if (value[0] == '*' || value[0] == '.' ||
	           !isdigit(value[0] & 255))
#endif /* AF_INET6 */
	  {
	   /*
	    * Host or domain name...
	    */

	    if (!_cups_strcasecmp(line, "BrowseAllow"))
	      cupsdAddNameMask(&(location->allow), value);
	    else
	      cupsdAddNameMask(&(location->deny), value);
	  }
	  else
	  {
	   /*
	    * One of many IP address forms...
	    */

	    if (!get_addr_and_mask(value, ip, mask))
	    {
	      cupsdLogMessage(CUPSD_LOG_ERROR, "Bad netmask value %s on line %d.",
			      value, linenum);
	      break;
	    }

	    if (!_cups_strcasecmp(line, "BrowseAllow"))
	      cupsdAddIPMask(&(location->allow), ip, mask);
	    else
	      cupsdAddIPMask(&(location->deny), ip, mask);
	  }

	 /*
	  * Advance to next value...
	  */

	  value = valueptr;
	}
      }
    }
    else if (!_cups_strcasecmp(line, "BrowseRelay") && value)
    {
     /*
      * BrowseRelay [from] source [to] destination
      */

      if (NumRelays == 0)
        relay = malloc(sizeof(cupsd_dirsvc_relay_t));
      else
        relay = realloc(Relays, (NumRelays + 1) * sizeof(cupsd_dirsvc_relay_t));

      if (!relay)
      {
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Unable to allocate BrowseRelay at line %d - %s.",
	                linenum, strerror(errno));
        continue;
      }

      Relays = relay;
      relay  += NumRelays;

      memset(relay, 0, sizeof(cupsd_dirsvc_relay_t));

      if (!_cups_strncasecmp(value, "from ", 5))
      {
       /*
	* Skip leading "from"...
	*/

	value += 5;

       /*
        * Skip leading whitespace...
	*/

	while (_cups_isspace(*value))
	  value ++;
      }

     /*
      * Find the end of the from value...
      */

      for (valueptr = value;
	   *valueptr && !_cups_isspace(*valueptr);
	   valueptr ++);

      while (_cups_isspace(*valueptr))
	*valueptr++ = '\0';

     /*
      * Figure out what form the from address takes:
      *
      *    *.domain.com
      *    .domain.com
      *    host.domain.com
      *    nnn.*
      *    nnn.nnn.*
      *    nnn.nnn.nnn.*
      *    nnn.nnn.nnn.nnn
      *    nnn.nnn.nnn.nnn/mm
      *    nnn.nnn.nnn.nnn/mmm.mmm.mmm.mmm
      */

#ifdef AF_INET6
      if (value[0] == '*' || value[0] == '.' ||
          (!isdigit(value[0] & 255) && value[0] != '['))
#else
      if (value[0] == '*' || value[0] == '.' || !isdigit(value[0] & 255))
#endif /* AF_INET6 */
      {
       /*
        * Host or domain name...
	*/

        if (!cupsdAddNameMask(&(relay->from), value))
	{
	  cupsdLogMessage(CUPSD_LOG_ERROR,
			  "Unable to allocate BrowseRelay name at line %d - %s.",
			  linenum, strerror(errno));
	  continue;
	}
      }
      else
      {
       /*
        * One of many IP address forms...
	*/

        if (!get_addr_and_mask(value, ip, mask))
	{
          cupsdLogMessage(CUPSD_LOG_ERROR, "Bad netmask value %s on line %d.",
	                  value, linenum);
	  break;
	}

        if (!cupsdAddIPMask(&(relay->from), ip, mask))
	{
	  cupsdLogMessage(CUPSD_LOG_ERROR,
			  "Unable to allocate BrowseRelay IP at line %d - %s.",
			  linenum, strerror(errno));
	  continue;
	}
      }

     /*
      * Get "to" address and port...
      */

      if (!_cups_strncasecmp(valueptr, "to ", 3))
      {
       /*
        * Strip leading "to"...
	*/

	valueptr += 3;

	while (_cups_isspace(*valueptr))
	  valueptr ++;
      }

      if ((addrlist = get_address(valueptr, BrowsePort)) != NULL)
      {
       /*
        * Only IPv4 addresses are supported...
        */

	for (addr = addrlist; addr; addr = addr->next)
	  if (addr->addr.addr.sa_family == AF_INET)
	    break;

	if (addr)
	{
	  memcpy(&(relay->to), &(addrlist->addr), sizeof(relay->to));

	  httpAddrString(&(relay->to), temp, sizeof(temp));

	  cupsdLogMessage(CUPSD_LOG_INFO, "Relaying from %s to %s:%d (IPv4)",
			  value, temp, _httpAddrPort(&(relay->to)));

	  NumRelays ++;
	}
	else
	{
	  cupsArrayDelete(relay->from);
	  relay->from = NULL;

	  cupsdLogMessage(CUPSD_LOG_ERROR, "Bad relay address %s at line %d.",
	                  valueptr, linenum);
	}

	httpAddrFreeList(addrlist);
      }
      else
      {
	cupsArrayDelete(relay->from);
	relay->from = NULL;

        cupsdLogMessage(CUPSD_LOG_ERROR, "Bad relay address %s at line %d.",
	                valueptr, linenum);
      }
    }
    else if (!_cups_strcasecmp(line, "BrowsePoll") && value)
    {
     /*
      * BrowsePoll address[:port]
      */

      char		*portname;	/* Port name */
      int		portnum;	/* Port number */
      struct servent	*service;	/* Service */


     /*
      * Extract the port name from the address...
      */

      if ((portname = strrchr(value, ':')) != NULL && !strchr(portname, ']'))
      {
        *portname++ = '\0';

        if (isdigit(*portname & 255))
	  portnum = atoi(portname);
	else if ((service = getservbyname(portname, NULL)) != NULL)
	  portnum = ntohs(service->s_port);
	else
	{
	  cupsdLogMessage(CUPSD_LOG_ERROR, "Lookup of service \"%s\" failed.",
	                  portname);
          continue;
	}
      }
      else
        portnum = ippPort();

     /*
      * Add the poll entry...
      */

      if (NumPolled == 0)
        pollp = malloc(sizeof(cupsd_dirsvc_poll_t));
      else
        pollp = realloc(Polled, (NumPolled + 1) * sizeof(cupsd_dirsvc_poll_t));

      if (!pollp)
      {
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Unable to allocate BrowsePoll at line %d - %s.",
	                linenum, strerror(errno));
        continue;
      }

      Polled = pollp;
      pollp   += NumPolled;

      NumPolled ++;
      memset(pollp, 0, sizeof(cupsd_dirsvc_poll_t));

      strlcpy(pollp->hostname, value, sizeof(pollp->hostname));
      pollp->port = portnum;

      cupsdLogMessage(CUPSD_LOG_INFO, "Polling %s:%d", pollp->hostname,
	              pollp->port);
    }
    else if (!_cups_strcasecmp(line, "DefaultAuthType") && value)
    {
     /*
      * DefaultAuthType {basic,digest,basicdigest,negotiate}
      */

      if (!_cups_strcasecmp(value, "none"))
	DefaultAuthType = CUPSD_AUTH_NONE;
      else if (!_cups_strcasecmp(value, "basic"))
	DefaultAuthType = CUPSD_AUTH_BASIC;
      else if (!_cups_strcasecmp(value, "digest"))
	DefaultAuthType = CUPSD_AUTH_DIGEST;
      else if (!_cups_strcasecmp(value, "basicdigest"))
	DefaultAuthType = CUPSD_AUTH_BASICDIGEST;
#ifdef HAVE_GSSAPI
      else if (!_cups_strcasecmp(value, "negotiate"))
        DefaultAuthType = CUPSD_AUTH_NEGOTIATE;
#endif /* HAVE_GSSAPI */
      else
      {
	cupsdLogMessage(CUPSD_LOG_WARN,
	                "Unknown default authorization type %s on line %d.",
	                value, linenum);
	if (FatalErrors & CUPSD_FATAL_CONFIG)
	  return (0);
      }
    }
#ifdef HAVE_SSL
    else if (!_cups_strcasecmp(line, "DefaultEncryption"))
    {
     /*
      * DefaultEncryption {Never,IfRequested,Required}
      */

      if (!value || !_cups_strcasecmp(value, "never"))
	DefaultEncryption = HTTP_ENCRYPT_NEVER;
      else if (!_cups_strcasecmp(value, "required"))
	DefaultEncryption = HTTP_ENCRYPT_REQUIRED;
      else if (!_cups_strcasecmp(value, "ifrequested"))
	DefaultEncryption = HTTP_ENCRYPT_IF_REQUESTED;
      else
      {
	cupsdLogMessage(CUPSD_LOG_WARN,
	                "Unknown default encryption %s on line %d.",
	                value, linenum);
	if (FatalErrors & CUPSD_FATAL_CONFIG)
	  return (0);
      }
    }
#endif /* HAVE_SSL */
    else if (!_cups_strcasecmp(line, "User") && value)
    {
     /*
      * User ID to run as...
      */

      if (isdigit(value[0] & 255))
      {
        int uid = atoi(value);

	if (!uid)
	  cupsdLogMessage(CUPSD_LOG_ERROR,
	                  "Will not use User 0 as specified on line %d "
			  "for security reasons.  You must use a non-"
			  "privileged account instead.",
	                  linenum);
        else
	  User = atoi(value);
      }
      else
      {
        struct passwd *p;	/* Password information */

        endpwent();
	p = getpwnam(value);

	if (p)
	{
	  if (!p->pw_uid)
	    cupsdLogMessage(CUPSD_LOG_ERROR,
	                    "Will not use User %s (UID=0) as specified on line "
			    "%d for security reasons.  You must use a non-"
			    "privileged account instead.",
	                    value, linenum);
	  else
	    User = p->pw_uid;
	}
	else
	  cupsdLogMessage(CUPSD_LOG_ERROR,
	                  "Unknown User \"%s\" on line %d, ignoring.",
	                  value, linenum);
      }
    }
    else if (!_cups_strcasecmp(line, "Group") && value)
    {
     /*
      * Group ID to run as...
      */

      if (isdigit(value[0]))
        Group = atoi(value);
      else
      {
        endgrent();
	group = getgrnam(value);

	if (group != NULL)
	  Group = group->gr_gid;
	else
	  cupsdLogMessage(CUPSD_LOG_ERROR,
	                  "Unknown Group \"%s\" on line %d, ignoring.",
	                  value, linenum);
      }
    }
    else if (!_cups_strcasecmp(line, "SystemGroup") && value)
    {
     /*
      * SystemGroup (admin) group(s)...
      */

      if (!parse_groups(value))
	cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Unknown SystemGroup \"%s\" on line %d, ignoring.",
	                value, linenum);
    }
    else if (!_cups_strcasecmp(line, "HostNameLookups") && value)
    {
     /*
      * Do hostname lookups?
      */

      if (!_cups_strcasecmp(value, "off") || !_cups_strcasecmp(value, "no") ||
          !_cups_strcasecmp(value, "false"))
        HostNameLookups = 0;
      else if (!_cups_strcasecmp(value, "on") || !_cups_strcasecmp(value, "yes") ||
          !_cups_strcasecmp(value, "true"))
        HostNameLookups = 1;
      else if (!_cups_strcasecmp(value, "double"))
        HostNameLookups = 2;
      else
	cupsdLogMessage(CUPSD_LOG_WARN, "Unknown HostNameLookups %s on line %d.",
	                value, linenum);
    }
    else if (!_cups_strcasecmp(line, "AccessLogLevel") && value)
    {
     /*
      * Amount of logging to do to access log...
      */

      if (!_cups_strcasecmp(value, "all"))
        AccessLogLevel = CUPSD_ACCESSLOG_ALL;
      else if (!_cups_strcasecmp(value, "actions"))
        AccessLogLevel = CUPSD_ACCESSLOG_ACTIONS;
      else if (!_cups_strcasecmp(value, "config"))
        AccessLogLevel = CUPSD_ACCESSLOG_CONFIG;
      else
        cupsdLogMessage(CUPSD_LOG_WARN, "Unknown AccessLogLevel %s on line %d.",
	                value, linenum);
    }
    else if (!_cups_strcasecmp(line, "LogLevel") && value)
    {
     /*
      * Amount of logging to do to error log...
      */

      if (!_cups_strcasecmp(value, "debug2"))
        LogLevel = CUPSD_LOG_DEBUG2;
      else if (!_cups_strcasecmp(value, "debug"))
        LogLevel = CUPSD_LOG_DEBUG;
      else if (!_cups_strcasecmp(value, "info"))
        LogLevel = CUPSD_LOG_INFO;
      else if (!_cups_strcasecmp(value, "notice"))
        LogLevel = CUPSD_LOG_NOTICE;
      else if (!_cups_strcasecmp(value, "warn"))
        LogLevel = CUPSD_LOG_WARN;
      else if (!_cups_strcasecmp(value, "error"))
        LogLevel = CUPSD_LOG_ERROR;
      else if (!_cups_strcasecmp(value, "crit"))
        LogLevel = CUPSD_LOG_CRIT;
      else if (!_cups_strcasecmp(value, "alert"))
        LogLevel = CUPSD_LOG_ALERT;
      else if (!_cups_strcasecmp(value, "emerg"))
        LogLevel = CUPSD_LOG_EMERG;
      else if (!_cups_strcasecmp(value, "none"))
        LogLevel = CUPSD_LOG_NONE;
      else
        cupsdLogMessage(CUPSD_LOG_WARN, "Unknown LogLevel %s on line %d.",
	                value, linenum);
    }
    else if (!_cups_strcasecmp(line, "LogTimeFormat") && value)
    {
     /*
      * Amount of logging to do to error log...
      */

      if (!_cups_strcasecmp(value, "standard"))
        LogTimeFormat = CUPSD_TIME_STANDARD;
      else if (!_cups_strcasecmp(value, "usecs"))
        LogTimeFormat = CUPSD_TIME_USECS;
      else
        cupsdLogMessage(CUPSD_LOG_WARN, "Unknown LogTimeFormat %s on line %d.",
	                value, linenum);
    }
    else if (!_cups_strcasecmp(line, "PrintcapFormat") && value)
    {
     /*
      * Format of printcap file?
      */

      if (!_cups_strcasecmp(value, "bsd"))
        PrintcapFormat = PRINTCAP_BSD;
      else if (!_cups_strcasecmp(value, "plist"))
        PrintcapFormat = PRINTCAP_PLIST;
      else if (!_cups_strcasecmp(value, "solaris"))
        PrintcapFormat = PRINTCAP_SOLARIS;
      else
	cupsdLogMessage(CUPSD_LOG_WARN, "Unknown PrintcapFormat %s on line %d.",
	                value, linenum);
    }
    else if (!_cups_strcasecmp(line, "ServerTokens") && value)
    {
     /*
      * Set the string used for the Server header...
      */

      struct utsname plat;		/* Platform info */


      uname(&plat);

      if (!_cups_strcasecmp(value, "ProductOnly"))
	cupsdSetString(&ServerHeader, "CUPS");
      else if (!_cups_strcasecmp(value, "Major"))
	cupsdSetStringf(&ServerHeader, "CUPS/%d", CUPS_VERSION_MAJOR);
      else if (!_cups_strcasecmp(value, "Minor"))
	cupsdSetStringf(&ServerHeader, "CUPS/%d.%d", CUPS_VERSION_MAJOR,
	                CUPS_VERSION_MINOR);
      else if (!_cups_strcasecmp(value, "Minimal"))
	cupsdSetString(&ServerHeader, CUPS_MINIMAL);
      else if (!_cups_strcasecmp(value, "OS"))
	cupsdSetStringf(&ServerHeader, CUPS_MINIMAL " (%s)", plat.sysname);
      else if (!_cups_strcasecmp(value, "Full"))
	cupsdSetStringf(&ServerHeader, CUPS_MINIMAL " (%s) IPP/2.1",
	                plat.sysname);
      else if (!_cups_strcasecmp(value, "None"))
	cupsdClearString(&ServerHeader);
      else
	cupsdLogMessage(CUPSD_LOG_WARN, "Unknown ServerTokens %s on line %d.",
                        value, linenum);
    }
    else if (!_cups_strcasecmp(line, "PassEnv") && value)
    {
     /*
      * PassEnv variable [... variable]
      */

      for (; *value;)
      {
        for (valuelen = 0; value[valuelen]; valuelen ++)
	  if (_cups_isspace(value[valuelen]) || value[valuelen] == ',')
	    break;

        if (value[valuelen])
        {
	  value[valuelen] = '\0';
	  valuelen ++;
	}

        cupsdSetEnv(value, NULL);

        for (value += valuelen; *value; value ++)
	  if (!_cups_isspace(*value) || *value != ',')
	    break;
      }
    }
    else if (!_cups_strcasecmp(line, "ServerAlias") && value)
    {
     /*
      * ServerAlias name [... name]
      */

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

      for (; *value;)
      {
        for (valuelen = 0; value[valuelen]; valuelen ++)
	  if (_cups_isspace(value[valuelen]) || value[valuelen] == ',')
	    break;

        if (value[valuelen])
        {
	  value[valuelen] = '\0';
	  valuelen ++;
	}

	cupsdAddAlias(ServerAlias, value);

        for (value += valuelen; *value; value ++)
	  if (!_cups_isspace(*value) || *value != ',')
	    break;
      }
    }
    else if (!_cups_strcasecmp(line, "SetEnv") && value)
    {
     /*
      * SetEnv variable value
      */

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

      if (*valueptr)
      {
       /*
        * Found a value...
	*/

        while (isspace(*valueptr & 255))
	  *valueptr++ = '\0';

        cupsdSetEnv(value, valueptr);
      }
      else
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Missing value for SetEnv directive on line %d.",
	                linenum);
    }
#ifdef HAVE_SSL
    else if (!_cups_strcasecmp(line, "SSLOptions"))
    {
     /*
      * SSLOptions options
      */

      if (!value || !_cups_strcasecmp(value, "none"))
        SSLOptions = CUPSD_SSL_NONE;
      else if (!_cups_strcasecmp(value, "noemptyfragments"))
        SSLOptions = CUPSD_SSL_NOEMPTY;
      else
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "Unknown value \"%s\" for SSLOptions directive on "
			"line %d.", value, linenum);
    }
#endif /* HAVE_SSL */
    else
    {
     /*
      * Find a simple variable in the list...
      */

      for (i = NUM_VARS, var = variables; i > 0; i --, var ++)
        if (!_cups_strcasecmp(line, var->name))
	  break;

      if (i == 0)
      {
       /*
        * Unknown directive!  Output an error message and continue...
	*/

        if (!value)
	  cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value for %s on line %d.",
	                  line, linenum);
	else
	  cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown directive %s on line %d.",
	                  line, linenum);
        continue;
      }

      switch (var->type)
      {
        case CUPSD_VARTYPE_INTEGER :
	    if (!value)
	      cupsdLogMessage(CUPSD_LOG_ERROR,
	                      "Missing integer value for %s on line %d.",
			      line, linenum);
	    else
	    {
	      int	n;		/* Number */
	      char	*units;		/* Units */


              n = strtol(value, &units, 0);

	      if (units && *units)
	      {
        	if (tolower(units[0] & 255) == 'g')
		  n *= 1024 * 1024 * 1024;
        	else if (tolower(units[0] & 255) == 'm')
		  n *= 1024 * 1024;
		else if (tolower(units[0] & 255) == 'k')
		  n *= 1024;
		else if (tolower(units[0] & 255) == 't')
		  n *= 262144;
	      }

              if (n < 0)
		cupsdLogMessage(CUPSD_LOG_ERROR,
	                	"Bad negative integer value for %s on line %d.",
				line, linenum);
	      else
		*((int *)var->ptr) = n;
	    }
	    break;

	case CUPSD_VARTYPE_BOOLEAN :
	    if (!value)
	      cupsdLogMessage(CUPSD_LOG_ERROR,
	                      "Missing boolean value for %s on line %d.",
			      line, linenum);
            else if (!_cups_strcasecmp(value, "true") ||
	             !_cups_strcasecmp(value, "on") ||
		     !_cups_strcasecmp(value, "enabled") ||
		     !_cups_strcasecmp(value, "yes") ||
		     atoi(value) != 0)
              *((int *)var->ptr) = TRUE;
	    else if (!_cups_strcasecmp(value, "false") ||
	             !_cups_strcasecmp(value, "off") ||
		     !_cups_strcasecmp(value, "disabled") ||
		     !_cups_strcasecmp(value, "no") ||
		     !_cups_strcasecmp(value, "0"))
              *((int *)var->ptr) = FALSE;
	    else
              cupsdLogMessage(CUPSD_LOG_ERROR,
	                      "Unknown boolean value %s on line %d.",
	                      value, linenum);
	    break;

	case CUPSD_VARTYPE_PATHNAME :
            if (!value)
	    {
	      cupsdLogMessage(CUPSD_LOG_ERROR,
	                      "Missing pathname value for %s on line %d.",
			      line, linenum);
              break;
	    }

	    if (value[0] == '/')
	      strlcpy(temp, value, sizeof(temp));
	    else
	      snprintf(temp, sizeof(temp), "%s/%s", ServerRoot, value);

            if (access(temp, 0))
	    {
	      cupsdLogMessage(CUPSD_LOG_ERROR,
	                      "File or directory for \"%s %s\" on line %d "
			      "does not exist.", line, value, linenum);
              break;
	    }

	case CUPSD_VARTYPE_STRING :
	    cupsdSetString((char **)var->ptr, value);
	    break;
      }
    }
  }

  return (1);
}


/*
 * 'read_location()' - Read a <Location path> definition.
 */

static int				/* O - New line number or 0 on error */
read_location(cups_file_t *fp,		/* I - Configuration file */
              char        *location,	/* I - Location name/path */
	      int         linenum)	/* I - Current line number */
{
  cupsd_location_t	*loc,		/* New location */
			*parent;	/* Parent location */
  char			line[HTTP_MAX_BUFFER],
					/* Line buffer */
			*value,		/* Value for directive */
			*valptr;	/* Pointer into value */


  if ((parent = cupsdFindLocation(location)) != NULL)
    cupsdLogMessage(CUPSD_LOG_WARN, "Duplicate <Location %s> on line %d.",
                    location, linenum);
  else if ((parent = cupsdNewLocation(location)) == NULL)
    return (0);
  else
  {
    cupsdAddLocation(parent);

    parent->limit = CUPSD_AUTH_LIMIT_ALL;
  }

  loc = parent;

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

    if (!_cups_strcasecmp(line, "</Location>"))
      return (linenum);
    else if (!_cups_strcasecmp(line, "<Limit") ||
             !_cups_strcasecmp(line, "<LimitExcept"))
    {
      if (!value)
      {
        cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d.", linenum);
        if (FatalErrors & CUPSD_FATAL_CONFIG)
	  return (0);
        else
	  continue;
      }

      if ((loc = cupsdCopyLocation(parent)) == NULL)
        return (0);

      cupsdAddLocation(loc);

      loc->limit = 0;
      while (*value)
      {
        for (valptr = value; !isspace(*valptr & 255) && *valptr; valptr ++);

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

        if (!strcmp(value, "ALL"))
	  loc->limit = CUPSD_AUTH_LIMIT_ALL;
	else if (!strcmp(value, "GET"))
	  loc->limit |= CUPSD_AUTH_LIMIT_GET;
	else if (!strcmp(value, "HEAD"))
	  loc->limit |= CUPSD_AUTH_LIMIT_HEAD;
	else if (!strcmp(value, "OPTIONS"))
	  loc->limit |= CUPSD_AUTH_LIMIT_OPTIONS;
	else if (!strcmp(value, "POST"))
	  loc->limit |= CUPSD_AUTH_LIMIT_POST;
	else if (!strcmp(value, "PUT"))
	  loc->limit |= CUPSD_AUTH_LIMIT_PUT;
	else if (!strcmp(value, "TRACE"))
	  loc->limit |= CUPSD_AUTH_LIMIT_TRACE;
	else
	  cupsdLogMessage(CUPSD_LOG_WARN, "Unknown request type %s on line %d.",
	                  value, linenum);

        for (value = valptr; isspace(*value & 255); value ++);
      }

      if (!_cups_strcasecmp(line, "<LimitExcept"))
        loc->limit = CUPSD_AUTH_LIMIT_ALL ^ loc->limit;

      parent->limit &= ~loc->limit;
    }
    else if (!_cups_strcasecmp(line, "</Limit>") ||
             !_cups_strcasecmp(line, "</LimitExcept>"))
      loc = parent;
    else if (!value)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d.", linenum);
      if (FatalErrors & CUPSD_FATAL_CONFIG)
	return (0);
    }
    else if (!parse_aaa(loc, line, value, linenum))
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Unknown Location directive %s on line %d.",
	              line, linenum);
      if (FatalErrors & CUPSD_FATAL_CONFIG)
	return (0);
    }
  }

  cupsdLogMessage(CUPSD_LOG_ERROR,
                  "Unexpected end-of-file at line %d while reading location.",
                  linenum);

  return ((FatalErrors & CUPSD_FATAL_CONFIG) ? 0 : linenum);
}


/*
 * 'read_policy()' - Read a <Policy name> definition.
 */

static int				/* O - New line number or 0 on error */
read_policy(cups_file_t *fp,		/* I - Configuration file */
            char        *policy,	/* I - Location name/path */
	    int         linenum)	/* I - Current line number */
{
  int			i;		/* Looping var */
  cupsd_policy_t	*pol;		/* Policy */
  cupsd_location_t	*op;		/* Policy operation */
  int			num_ops;	/* Number of IPP operations */
  ipp_op_t		ops[100];	/* Operations */
  char			line[HTTP_MAX_BUFFER],
					/* Line buffer */
			*value,		/* Value for directive */
			*valptr;	/* Pointer into value */


 /*
  * Create the policy...
  */

  if ((pol = cupsdFindPolicy(policy)) != NULL)
    cupsdLogMessage(CUPSD_LOG_WARN, "Duplicate <Policy %s> on line %d.",
                    policy, linenum);
  else if ((pol = cupsdAddPolicy(policy)) == NULL)
    return (0);

 /*
  * Read from the file...
  */

  op      = NULL;
  num_ops = 0;

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

    if (!_cups_strcasecmp(line, "</Policy>"))
    {
      if (op)
        cupsdLogMessage(CUPSD_LOG_WARN,
	                "Missing </Limit> before </Policy> on line %d.",
	                linenum);

      set_policy_defaults(pol);

      return (linenum);
    }
    else if (!_cups_strcasecmp(line, "<Limit") && !op)
    {
      if (!value)
      {
        cupsdLogMessage(CUPSD_LOG_ERROR, "Syntax error on line %d.", linenum);
        if (FatalErrors & CUPSD_FATAL_CONFIG)
	  return (0);
        else
	  continue;
      }

     /*
      * Scan for IPP operation names...
      */

      num_ops = 0;

      while (*value)
      {
        for (valptr = value; !isspace(*valptr & 255) && *valptr; valptr ++);

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

        if (num_ops < (int)(sizeof(ops) / sizeof(ops[0])))
	{
	  if (!_cups_strcasecmp(value, "All"))
	    ops[num_ops] = IPP_ANY_OPERATION;
	  else if ((ops[num_ops] = ippOpValue(value)) == IPP_BAD_OPERATION)
	    cupsdLogMessage(CUPSD_LOG_ERROR,
	                    "Bad IPP operation name \"%s\" on line %d.",
	                    value, linenum);
          else
	    num_ops ++;
	}
	else
	  cupsdLogMessage(CUPSD_LOG_ERROR,
	                  "Too many operations listed on line %d.",
	                  linenum);

        for (value = valptr; isspace(*value & 255); value ++);
      }

     /*
      * If none are specified, apply the policy to all operations...
      */

      if (num_ops == 0)
      {
        ops[0]  = IPP_ANY_OPERATION;
	num_ops = 1;
      }

     /*
      * Add a new policy for the first operation...
      */

      op = cupsdAddPolicyOp(pol, NULL, ops[0]);
    }
    else if (!_cups_strcasecmp(line, "</Limit>") && op)
    {
     /*
      * Finish the current operation limit...
      */

      if (num_ops > 1)
      {
       /*
        * Copy the policy to the other operations...
	*/

        for (i = 1; i < num_ops; i ++)
	  cupsdAddPolicyOp(pol, op, ops[i]);
      }

      op = NULL;
    }
    else if (!value)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d.", linenum);
      if (FatalErrors & CUPSD_FATAL_CONFIG)
	return (0);
    }
    else if (!_cups_strcasecmp(line, "JobPrivateAccess") ||
	     !_cups_strcasecmp(line, "JobPrivateValues") ||
	     !_cups_strcasecmp(line, "SubscriptionPrivateAccess") ||
	     !_cups_strcasecmp(line, "SubscriptionPrivateValues"))
    {
      if (op)
      {
        cupsdLogMessage(CUPSD_LOG_ERROR,
	                "%s directive must appear outside <Limit>...</Limit> "
			"on line %d.", line, linenum);
	if (FatalErrors & CUPSD_FATAL_CONFIG)
	  return (0);
      }
      else
      {
       /*
        * Pull out whitespace-delimited values...
	*/

	while (*value)
	{
	 /*
	  * Find the end of the current value...
	  */

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

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

         /*
	  * Save it appropriately...
	  */

	  if (!_cups_strcasecmp(line, "JobPrivateAccess"))
	  {
	   /*
	    * JobPrivateAccess {all|default|user/group list|@@ACL}
	    */

            if (!_cups_strcasecmp(value, "default"))
	    {
	      cupsdAddString(&(pol->job_access), "@OWNER");
	      cupsdAddString(&(pol->job_access), "@SYSTEM");
	    }
	    else
	      cupsdAddString(&(pol->job_access), value);
	  }
	  else if (!_cups_strcasecmp(line, "JobPrivateValues"))
	  {
	   /*
	    * JobPrivateValues {all|none|default|attribute list}
	    */

	    if (!_cups_strcasecmp(value, "default"))
	    {
	      cupsdAddString(&(pol->job_attrs), "job-name");
	      cupsdAddString(&(pol->job_attrs), "job-originating-host-name");
	      cupsdAddString(&(pol->job_attrs), "job-originating-user-name");
	    }
	    else
	      cupsdAddString(&(pol->job_attrs), value);
	  }
	  else if (!_cups_strcasecmp(line, "SubscriptionPrivateAccess"))
	  {
	   /*
	    * SubscriptionPrivateAccess {all|default|user/group list|@@ACL}
	    */

            if (!_cups_strcasecmp(value, "default"))
	    {
	      cupsdAddString(&(pol->sub_access), "@OWNER");
	      cupsdAddString(&(pol->sub_access), "@SYSTEM");
	    }
	    else
	      cupsdAddString(&(pol->sub_access), value);
	  }
	  else /* if (!_cups_strcasecmp(line, "SubscriptionPrivateValues")) */
	  {
	   /*
	    * SubscriptionPrivateValues {all|none|default|attribute list}
	    */

	    if (!_cups_strcasecmp(value, "default"))
	    {
	      cupsdAddString(&(pol->sub_attrs), "notify-events");
	      cupsdAddString(&(pol->sub_attrs), "notify-pull-method");
	      cupsdAddString(&(pol->sub_attrs), "notify-recipient-uri");
	      cupsdAddString(&(pol->sub_attrs), "notify-subscriber-user-name");
	      cupsdAddString(&(pol->sub_attrs), "notify-user-data");
	    }
	    else
	      cupsdAddString(&(pol->sub_attrs), value);
	  }

	 /*
	  * Find the next string on the line...
	  */

	  for (value = valptr; isspace(*value & 255); value ++);
	}
      }
    }
    else if (!op)
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
                      "Missing <Limit ops> directive before %s on line %d.",
                      line, linenum);
      if (FatalErrors & CUPSD_FATAL_CONFIG)
	return (0);
    }
    else if (!parse_aaa(op, line, value, linenum))
    {
      cupsdLogMessage(CUPSD_LOG_ERROR,
		      "Unknown Policy Limit directive %s on line %d.",
		      line, linenum);

      if (FatalErrors & CUPSD_FATAL_CONFIG)
	return (0);
    }
  }

  cupsdLogMessage(CUPSD_LOG_ERROR,
                  "Unexpected end-of-file at line %d while reading policy "
                  "\"%s\".", linenum, policy);

  return ((FatalErrors & CUPSD_FATAL_CONFIG) ? 0 : linenum);
}


/*
 * 'set_policy_defaults()' - Set default policy values as needed.
 */

static void
set_policy_defaults(cupsd_policy_t *pol)/* I - Policy */
{
  cupsd_location_t	*op;		/* Policy operation */


 /*
  * Verify that we have an explicit policy for Validate-Job, Cancel-Jobs,
  * Cancel-My-Jobs, Close-Job, and CUPS-Get-Document, which ensures that
  * upgrades do not introduce new security issues...
  */

  if ((op = cupsdFindPolicyOp(pol, IPP_VALIDATE_JOB)) == NULL ||
      op->op == IPP_ANY_OPERATION)
  {
    if ((op = cupsdFindPolicyOp(pol, IPP_PRINT_JOB)) != NULL &&
	op->op != IPP_ANY_OPERATION)
    {
     /*
      * Add a new limit for Validate-Job using the Print-Job limit as a
      * template...
      */

      cupsdLogMessage(CUPSD_LOG_WARN,
		      "No limit for Validate-Job defined in policy %s "
		      "- using Print-Job's policy.", pol->name);

      cupsdAddPolicyOp(pol, op, IPP_VALIDATE_JOB);
    }
    else
      cupsdLogMessage(CUPSD_LOG_WARN,
		      "No limit for Validate-Job defined in policy %s "
		      "and no suitable template found.", pol->name);
  }

  if ((op = cupsdFindPolicyOp(pol, IPP_CANCEL_JOBS)) == NULL ||
      op->op == IPP_ANY_OPERATION)
  {
    if ((op = cupsdFindPolicyOp(pol, IPP_PAUSE_PRINTER)) != NULL &&
	op->op != IPP_ANY_OPERATION)
    {
     /*
      * Add a new limit for Cancel-Jobs using the Pause-Printer limit as a
      * template...
      */

      cupsdLogMessage(CUPSD_LOG_WARN,
		      "No limit for Cancel-Jobs defined in policy %s "
		      "- using Pause-Printer's policy.", pol->name);

      cupsdAddPolicyOp(pol, op, IPP_CANCEL_JOBS);
    }
    else
      cupsdLogMessage(CUPSD_LOG_WARN,
		      "No limit for Cancel-Jobs defined in policy %s "
		      "and no suitable template found.", pol->name);
  }

  if ((op = cupsdFindPolicyOp(pol, IPP_CANCEL_MY_JOBS)) == NULL ||
      op->op == IPP_ANY_OPERATION)
  {
    if ((op = cupsdFindPolicyOp(pol, IPP_SEND_DOCUMENT)) != NULL &&
	op->op != IPP_ANY_OPERATION)
    {
     /*
      * Add a new limit for Cancel-My-Jobs using the Send-Document limit as
      * a template...
      */

      cupsdLogMessage(CUPSD_LOG_WARN,
		      "No limit for Cancel-My-Jobs defined in policy %s "
		      "- using Send-Document's policy.", pol->name);

      cupsdAddPolicyOp(pol, op, IPP_CANCEL_MY_JOBS);
    }
    else
      cupsdLogMessage(CUPSD_LOG_WARN,
		      "No limit for Cancel-My-Jobs defined in policy %s "
		      "and no suitable template found.", pol->name);
  }

  if ((op = cupsdFindPolicyOp(pol, IPP_CLOSE_JOB)) == NULL ||
      op->op == IPP_ANY_OPERATION)
  {
    if ((op = cupsdFindPolicyOp(pol, IPP_SEND_DOCUMENT)) != NULL &&
	op->op != IPP_ANY_OPERATION)
    {
     /*
      * Add a new limit for Close-Job using the Send-Document limit as a
      * template...
      */

      cupsdLogMessage(CUPSD_LOG_WARN,
		      "No limit for Close-Job defined in policy %s "
		      "- using Send-Document's policy.", pol->name);

      cupsdAddPolicyOp(pol, op, IPP_CLOSE_JOB);
    }
    else
      cupsdLogMessage(CUPSD_LOG_WARN,
		      "No limit for Close-Job defined in policy %s "
		      "and no suitable template found.", pol->name);
  }

  if ((op = cupsdFindPolicyOp(pol, CUPS_GET_DOCUMENT)) == NULL ||
      op->op == IPP_ANY_OPERATION)
  {
    if ((op = cupsdFindPolicyOp(pol, IPP_SEND_DOCUMENT)) != NULL &&
	op->op != IPP_ANY_OPERATION)
    {
     /*
      * Add a new limit for CUPS-Get-Document using the Send-Document
      * limit as a template...
      */

      cupsdLogMessage(CUPSD_LOG_WARN,
		      "No limit for CUPS-Get-Document defined in policy %s "
		      "- using Send-Document's policy.", pol->name);

      cupsdAddPolicyOp(pol, op, CUPS_GET_DOCUMENT);
    }
    else
      cupsdLogMessage(CUPSD_LOG_WARN,
		      "No limit for CUPS-Get-Document defined in policy %s "
		      "and no suitable template found.", pol->name);
  }

 /*
  * Verify we have JobPrivateAccess, JobPrivateValues,
  * SubscriptionPrivateAccess, and SubscriptionPrivateValues in the policy.
  */

  if (!pol->job_access)
  {
    cupsdLogMessage(CUPSD_LOG_WARN,
		    "No JobPrivateAccess defined in policy %s "
		    "- using defaults.", pol->name);
    cupsdAddString(&(pol->job_access), "@OWNER");
    cupsdAddString(&(pol->job_access), "@SYSTEM");
  }

  if (!pol->job_attrs)
  {
    cupsdLogMessage(CUPSD_LOG_WARN,
		    "No JobPrivateValues defined in policy %s "
		    "- using defaults.", pol->name);
    cupsdAddString(&(pol->job_attrs), "job-name");
    cupsdAddString(&(pol->job_attrs), "job-originating-host-name");
    cupsdAddString(&(pol->job_attrs), "job-originating-user-name");
  }

  if (!pol->sub_access)
  {
    cupsdLogMessage(CUPSD_LOG_WARN,
		    "No SubscriptionPrivateAccess defined in policy %s "
		    "- using defaults.", pol->name);
    cupsdAddString(&(pol->sub_access), "@OWNER");
    cupsdAddString(&(pol->sub_access), "@SYSTEM");
  }

  if (!pol->sub_attrs)
  {
    cupsdLogMessage(CUPSD_LOG_WARN,
		    "No SubscriptionPrivateValues defined in policy %s "
		    "- using defaults.", pol->name);
    cupsdAddString(&(pol->sub_attrs), "notify-events");
    cupsdAddString(&(pol->sub_attrs), "notify-pull-method");
    cupsdAddString(&(pol->sub_attrs), "notify-recipient-uri");
    cupsdAddString(&(pol->sub_attrs), "notify-subscriber-user-name");
    cupsdAddString(&(pol->sub_attrs), "notify-user-data");
  }
}


/*
 * End of "$Id: conf.c 10121 2011-11-16 15:28:11Z mike $".
 */
