/*
 * Copyright (c) 2003-2013
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*****************************************************************************
 * COPYRIGHT AND PERMISSION NOTICE
 * 
 * Copyright (c) 2001-2003 The Queen in Right of Canada
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, and/or sell
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that both the
 * above copyright notice(s) and this permission notice appear in supporting
 * documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE 
 * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
 * SOFTWARE.
 * 
 * Except as contained in this notice, the name of a copyright holder shall not
 * be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization of the
 * copyright holder.
 ***************************************************************************/

/*
 * ACL parsing and evaluation logic for ACS
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2013\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: acslib.c 2646 2013-02-27 20:10:40Z brachman $";
#endif

#include "auth.h"
#include "group.h"
#include "acs.h"
#include "dacs_api.h"

#ifdef ENABLE_FTS
#include "dacs_fts.h"
#else
#include <fts.h>
#endif

static char *log_module_name = "acslib";

typedef enum Parse_xml_acs_state_code {
	ACS_PARSE_ACL_RULE     = 1,
	ACS_PARSE_IDENTITY     = 2,
	ACS_PARSE_SERVICES     = 3,
	ACS_PARSE_SERVICE      = 4,
	ACS_PARSE_RULE         = 5,
	ACS_PARSE_PRECONDITION = 6,
	ACS_PARSE_USER_LIST    = 7,
	ACS_PARSE_USER         = 8,
	ACS_PARSE_PREDICATE    = 9,
	ACS_PARSE_ALLOW        = 10,
	ACS_PARSE_DENY         = 11
} Parse_xml_acs_state_code;

typedef struct Parse_xml_acs_state {
  Parse_xml_acs_state_code code;
  union {
	Acl_rule *acl_rule;
	Identity *identities;
	Services *services;
	Service *service;
	Rule *rules;
	Precondition *precondition;
	User_list *user_list;
	User *user;
	Predicate *predicate;
	Allow *allows;
	Deny *denies;
  } object;
} Parse_xml_acs_state;

static Parse_xml_acs_state *
parse_xml_make_state(Parse_xml_acs_state_code code, void *object)
{
  Parse_xml_acs_state *s;

  s = ALLOC(Parse_xml_acs_state);
  s->code = code;
  switch (code) {
  case ACS_PARSE_ACL_RULE:
	s->object.acl_rule = (Acl_rule *) object;
	break;
  case ACS_PARSE_IDENTITY:
	s->object.identities = (Identity *) object;
	break;
  case ACS_PARSE_SERVICES:
	s->object.services = (Services *) object;
	break;
  case ACS_PARSE_SERVICE:
	s->object.service = (Service *) object;
	break;
  case ACS_PARSE_RULE:
	s->object.rules = (Rule *) object;
	break;
  case ACS_PARSE_PRECONDITION:
	s->object.precondition = (Precondition *) object;
	break;
  case ACS_PARSE_USER_LIST:
	s->object.user_list = (User_list *) object;
	break;
  case ACS_PARSE_USER:
	s->object.user = (User *) object;
	break;
  case ACS_PARSE_PREDICATE:
	s->object.predicate = (Predicate *) object;
	break;
  case ACS_PARSE_ALLOW:
	s->object.allows = (Allow *) object;
	break;
  case ACS_PARSE_DENY:
	s->object.denies = (Deny *) object;
	break;
  default:
	/* XXX ??? */
	return(NULL);
  }

  return(s);
}

static char *attr_pass_credentials[] = {
  "none", "matched", "all", NULL
};

#define ATTR_PASS_CREDENTIALS (&parse_xml_attr_validate_pass_credentials)
static Parse_attr_validate parse_xml_attr_validate_pass_credentials = {
  parse_xml_attr_validator, attr_pass_credentials, 1, NULL
};

static char *attr_status[] = {
  "enabled", "disabled", NULL
};

#define ATTR_STATUS (&parse_xml_attr_validate_status)
static Parse_attr_validate parse_xml_attr_validate_status = {
  parse_xml_attr_validator, attr_status, 1, NULL
};

/* "acl_rule" element */
static Parse_attr_tab acl_rule_attr_tab[] = {
  { "constraint",        NULL, ATTR_IMPLIED, NULL, 0 },
  { "permit_chaining",   NULL, ATTR_IMPLIED, ATTR_BINARY, 0 },
  { "pass_credentials",  NULL, ATTR_IMPLIED, ATTR_PASS_CREDENTIALS, 0 },
  { "pass_http_cookie",  NULL, ATTR_IMPLIED, ATTR_BINARY, 0 },
  { "permit_caching",    NULL, ATTR_IMPLIED, ATTR_BINARY, 0 },
  { "name",              NULL, ATTR_IMPLIED, NULL, 0 },
  { "expires_expr",      NULL, ATTR_IMPLIED, NULL, 0 },
  { "status",            NULL, ATTR_IMPLIED, ATTR_STATUS, 0 },
  { NULL,                NULL, ATTR_END, NULL, 0 }
};

/* "identity" element */
static Parse_attr_tab acl_rule_identity_attr_tab[] = {
  { "id",            NULL, ATTR_IMPLIED,  NULL, 0 },
  { "iptr",          NULL, ATTR_REQUIRED, NULL, 0 },
  { "ident",         NULL, ATTR_REQUIRED, NULL, 0 },
  { "selector_expr", NULL, ATTR_REQUIRED, NULL, 0 },
  { NULL,            NULL, ATTR_END,      NULL, 0 }
};

/* "services" element */
static Parse_attr_tab acl_rule_services_attr_tab[] = {
  { "shared",      NULL, ATTR_IMPLIED, ATTR_BINARY, 0 },
  { NULL,          NULL, ATTR_END,     NULL, 0 }
};

/* "service" and "delegate" element */
static Parse_attr_tab acl_rule_service_attr_tab[] = {
  { "id",          NULL, ATTR_IMPLIED, NULL, 0 },
  { "url_pattern", NULL, ATTR_IMPLIED, NULL, 0 },
  { "url_expr",    NULL, ATTR_IMPLIED, NULL, 0 },
  { "rule_uri",    NULL, ATTR_IMPLIED, NULL, 0 },
  { NULL,          NULL, ATTR_END,     NULL, 0 }
};

/* "rule" element */
static Parse_attr_tab acl_rule_rule_attr_tab[] = {
  { "id",               NULL, ATTR_IMPLIED,  NULL, 0 },
  { "order",            NULL, ATTR_REQUIRED, NULL, 0 },
  { "constraint",       NULL, ATTR_IMPLIED,  NULL, 0 },
  { "permit_chaining",  NULL, ATTR_IMPLIED,  ATTR_BINARY, 0 },
  { "pass_credentials", NULL, ATTR_IMPLIED,  ATTR_PASS_CREDENTIALS, 0 },
  { "pass_http_cookie", NULL, ATTR_IMPLIED,  ATTR_BINARY, 0 },
  { "permit_caching",   NULL, ATTR_IMPLIED,  ATTR_BINARY, 0 },
  { NULL,   NULL, ATTR_END, NULL, 0 }
};

/* "user" element */
static Parse_attr_tab acl_rule_user_attr_tab[] = {
  { "id",   NULL, ATTR_IMPLIED,  NULL, 0 },
  { "name", NULL, ATTR_REQUIRED, NULL, 0 },
  { NULL,   NULL, ATTR_END, NULL, 0 }
};

/* "allow" element */
static Parse_attr_tab acl_rule_allow_attr_tab[] = {
  { "id",               NULL, ATTR_IMPLIED, NULL, 0 },
  { "constraint",       NULL, ATTR_IMPLIED, NULL, 0 },
  { "permit_chaining",  NULL, ATTR_IMPLIED, ATTR_BINARY, 0 },
  { "pass_credentials", NULL, ATTR_IMPLIED, ATTR_PASS_CREDENTIALS, 0 },
  { "pass_http_cookie", NULL, ATTR_IMPLIED, ATTR_BINARY, 0 },
  { "permit_caching",   NULL, ATTR_IMPLIED, ATTR_BINARY, 0 },
  { NULL,               NULL, ATTR_END, NULL, 0 }
};

/* "deny" element */
static Parse_attr_tab acl_rule_deny_attr_tab[] = {
  { "id",               NULL, ATTR_IMPLIED, NULL, 0 },
  { NULL,               NULL, ATTR_END, NULL, 0 }
};

static int
check_grant_attrs(Grant_attrs *a)
{

  if (a->permit_chaining != NULL
	  && !streq(a->permit_chaining, "yes")
	  && !streq(a->permit_chaining, "no")) {
	parse_xml_set_error("Expecting valid permit_chaining attribute");
	return(-1);
  }

  if (a->pass_credentials
	  && !streq(a->pass_credentials, "none")
	  && !streq(a->pass_credentials, "matched")
	  && !streq(a->pass_credentials, "all")) {
	parse_xml_set_error("Expecting valid pass_credentials attribute");
	return(-1);
  }

  if (a->permit_caching != NULL
	  && !streq(a->permit_caching, "yes")
	  && !streq(a->permit_caching, "no")) {
	parse_xml_set_error("Expecting valid permit_caching attribute");
	return(-1);
  }

  if (a->pass_http_cookie != NULL
	  && !streq(a->pass_http_cookie, "yes")
	  && !streq(a->pass_http_cookie, "no")) {
	parse_xml_set_error("Expecting valid pass_http_cookie attribute");
	return(-1);
  }

  return(0);
}

static void
parse_xml_acl_element_start(void *data, const char *element, const char **attr)
{
  char *el, *errmsg;
  Acl_rule **arp;

  if (parse_xmlns_name(element, NULL, &el) == -1) {
	parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
	return;
  }

  arp = (Acl_rule **) data;

  if (parse_xml_is_error(NULL))
	return;

  if (streq(el, "acl_rule")) {
	char *status_str;
	Acl_rule *acl_rule;

	acl_rule = ALLOC(Acl_rule);
	parse_xml_push(parse_xml_make_state(ACS_PARSE_ACL_RULE,
										(void *) acl_rule));
	acl_rule->status = ACL_STATUS_ERROR;
	acl_rule->name = NULL;
	acl_rule->expires_expr = NULL;
	acl_rule->identities = NULL;
	acl_rule->services = NULL;
	acl_rule->rules = NULL;
	init_grant_attrs(&acl_rule->default_attrs);

	acl_rule_attr_tab[0].value = &acl_rule->default_attrs.constraint;
	acl_rule_attr_tab[1].value = &acl_rule->default_attrs.permit_chaining;
	acl_rule_attr_tab[2].value = &acl_rule->default_attrs.pass_credentials;
	acl_rule_attr_tab[3].value = &acl_rule->default_attrs.pass_http_cookie;
	acl_rule_attr_tab[4].value = &acl_rule->default_attrs.permit_caching;
	acl_rule_attr_tab[5].value = &acl_rule->name;
	acl_rule_attr_tab[6].value = &acl_rule->expires_expr;
	acl_rule_attr_tab[7].value = &status_str;

	if (parse_xml_attr(acl_rule_attr_tab, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  return;
	}

	if (streq(status_str, "enabled"))
	  acl_rule->status = ACL_STATUS_ENABLED;
	else
	  acl_rule->status = ACL_STATUS_DISABLED;

	if (check_grant_attrs(&acl_rule->default_attrs) == -1)
	  return;

	*arp = acl_rule;
  }
  else if (streq(el, "identity")) {
	Acl_rule *acl_rule;
	Identity *id, **idp;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_ACL_RULE)
	  return;

	acl_rule = state->object.acl_rule;
	if (acl_rule->services == NULL) {
	  parse_xml_set_error("The identity element must follow the services element");
	  return;
	}
	if (acl_rule->rules != NULL) {
	  parse_xml_set_error("The identity element must precede the rule element");
	  return;
	}

	id = ALLOC(Identity);
	id->id = NULL;
	id->iptr = NULL;
	id->ident = NULL;
	id->selector_expr = NULL;
	id->next = NULL;
	parse_xml_push((void *)
				   parse_xml_make_state(ACS_PARSE_IDENTITY, (void *) id));

	acl_rule_identity_attr_tab[0].value = &id->id;
	acl_rule_identity_attr_tab[1].value = &id->iptr;
	acl_rule_identity_attr_tab[2].value = &id->ident;
	acl_rule_identity_attr_tab[3].value = &id->selector_expr;
	if (parse_xml_attr(acl_rule_identity_attr_tab, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  free(id);
	  return;
	}

	for (idp = &acl_rule->identities; *idp != NULL; idp = &(*idp)->next)
	  ;
	*idp = id;

	parse_xml_pop((void **) &state);
	free(state);
  }
  else if (streq(el, "services")) {
	Acl_rule *acl_rule;
	Services *ss;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_ACL_RULE)
	  return;
	acl_rule = state->object.acl_rule;
	ss = ALLOC(Services);

	parse_xml_push((void *)
				   parse_xml_make_state(ACS_PARSE_SERVICES, (void *) ss));

	ss->service = NULL;
	ss->shared = NULL;

	acl_rule_services_attr_tab[0].value = &ss->shared;
	if (parse_xml_attr(acl_rule_services_attr_tab, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  return;
	}

	acl_rule->services = ss;
  }
  else if (streq(el, "service") | streq(el, "delegate")) {
	Services *ss;
	Service *s, **sp;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_SERVICES)
	  return;
	ss = state->object.services;
	s = ALLOC(Service);
	parse_xml_push((void *)
				   parse_xml_make_state(ACS_PARSE_SERVICE, (void *) s));

	s->id = NULL;
	s->url_pattern = NULL;
	s->url_expr = NULL;
	s->url_path = NULL;
	s->rule_uri = NULL;
	s->next = NULL;

	acl_rule_service_attr_tab[0].value = &s->id;
	acl_rule_service_attr_tab[1].value = &s->url_pattern;
	acl_rule_service_attr_tab[2].value = &s->url_expr;
	acl_rule_service_attr_tab[3].value = &s->rule_uri;
	if (parse_xml_attr(acl_rule_service_attr_tab, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  free(s);
	  return;
	}

	if (streq(el, "delegate") && s->rule_uri == NULL) {
	  parse_xml_set_error("The rule_uri attribute is required");
	  return;
	}

	if (s->url_pattern != NULL && s->url_expr != NULL) {
	  parse_xml_set_error("Attributes url_pattern and url_expr are mutually exclusive");
	  return;
	}
	if (s->url_pattern == NULL && s->url_expr == NULL) {
	  parse_xml_set_error("Either the url_pattern or url_expr attribute is required");
	  return;
	}

	for (sp = &ss->service; *sp != NULL; sp = &(*sp)->next)
	  ;
	*sp = s;

	parse_xml_pop((void **) &state);
	free(state);
  }
  else if (streq(el, "rule")) {
	Acl_rule *acl_rule;
	Rule *r, **rp;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_ACL_RULE)
	  return;

	acl_rule = state->object.acl_rule;
	if (acl_rule->services == NULL) {
	  parse_xml_set_error("The rule element must follow the services element");
	  return;
	}

	r = ALLOC(Rule);
	r->id = NULL;
	r->precondition = NULL;
	r->allows = NULL;
	r->denies = NULL;
	r->order = ACS_ORDER_UNKNOWN;
	r->order_str = NULL;
	init_grant_attrs(&r->default_attrs);
	r->next = NULL;

	parse_xml_push((void *)
				   parse_xml_make_state(ACS_PARSE_RULE, (void *) r));

	acl_rule_rule_attr_tab[0].value = &r->id;
	acl_rule_rule_attr_tab[1].value = &r->order_str;
	acl_rule_rule_attr_tab[2].value = &r->default_attrs.constraint;
	acl_rule_rule_attr_tab[3].value = &r->default_attrs.permit_chaining;
	acl_rule_rule_attr_tab[4].value = &r->default_attrs.pass_credentials;
	acl_rule_rule_attr_tab[5].value = &r->default_attrs.pass_http_cookie;
	acl_rule_rule_attr_tab[6].value = &r->default_attrs.permit_caching;

	if (parse_xml_attr(acl_rule_rule_attr_tab, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  return;
	}

	if (check_grant_attrs(&r->default_attrs) == -1) {
	  parse_xml_set_error("Invalid grant attributes");
	  return;
	}

	if (strcaseeq(r->order_str, "allow,deny"))
	  r->order = ACS_ORDER_ALLOW_THEN_DENY;
	else if (strcaseeq(r->order_str, "deny,allow"))
	  r->order = ACS_ORDER_DENY_THEN_ALLOW;
	else {
	  parse_xml_set_error("Invalid order attribute");
	  return;
	}

	for (rp = &acl_rule->rules; *rp != NULL; rp = &(*rp)->next)
	  ;
	*rp = r;
  }
  else if (streq(el, "precondition")) {
	Precondition *pre;
	Rule *r;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_RULE)
	  return;
	r = state->object.rules;
	if (r->precondition != NULL) {
	  parse_xml_set_error("More than one precondition element appears");
	  return;
	}
	pre = ALLOC(Precondition);
	pre->user_list = NULL;
	pre->predicate = NULL;
	parse_xml_push((void *)
				   parse_xml_make_state(ACS_PARSE_PRECONDITION, (void *) pre));

	if (parse_xml_attr(NULL, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  return;
	}

	r->precondition = pre;
  }
  else if (streq(el, "user_list")) {
	Precondition *pre;
	User_list *u;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_PRECONDITION)
	  return;
	pre = state->object.precondition;
	u = ALLOC(User_list);
	pre->user_list = u;
	parse_xml_push((void *)
				   parse_xml_make_state(ACS_PARSE_USER_LIST, (void *) u));

	u->user = NULL;

	if (parse_xml_attr(NULL, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  free(u);
	  return;
	}
  }
  else if (streq(el, "user")) {
	User_list *uu;
	User *u, **up;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_USER_LIST)
	  return;
	uu = state->object.user_list;
	u = ALLOC(User);
	parse_xml_push((void *)
				   parse_xml_make_state(ACS_PARSE_USER, (void *) u));

	u->id = NULL;
	u->name = NULL;
	u->next = NULL;

	acl_rule_user_attr_tab[0].value = &u->id;
	acl_rule_user_attr_tab[1].value = &u->name;
	if (parse_xml_attr(acl_rule_user_attr_tab, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  free(u);
	  return;
	}

	for (up = &uu->user; *up != NULL; up = &(*up)->next)
	  ;
	*up = u;
  }
  else if (streq(el, "predicate")) {
	Precondition *pre;
	Predicate *p;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_PRECONDITION)
	  return;
	pre = state->object.precondition;
	p = ALLOC(Predicate);
	parse_xml_push((void *)
				   parse_xml_make_state(ACS_PARSE_PREDICATE, (void *) p));

	p->expr = NULL;
	pre->predicate = p;

	if (parse_xml_attr(NULL, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  return;
	}
  }
  else if (streq(el, "allow")) {
	Allow *a, **ap;
	Rule *r;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_RULE)
	  return;
	r = state->object.rules;
	a = ALLOC(Allow);
	a->id = NULL;
	a->expr = NULL;
	a->next = NULL;
	init_grant_attrs(&a->attrs);

	parse_xml_push((void *)
				   parse_xml_make_state(ACS_PARSE_ALLOW, (void *) a));

	acl_rule_allow_attr_tab[0].value = &a->id;
	acl_rule_allow_attr_tab[1].value = &a->attrs.constraint;
	acl_rule_allow_attr_tab[2].value = &a->attrs.permit_chaining;
	acl_rule_allow_attr_tab[3].value = &a->attrs.pass_credentials;
	acl_rule_allow_attr_tab[4].value = &a->attrs.pass_http_cookie;
	acl_rule_allow_attr_tab[5].value = &a->attrs.permit_caching;

	if (parse_xml_attr(acl_rule_allow_attr_tab, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  return;
	}

	if (check_grant_attrs(&a->attrs) == -1)
	  return;

	for (ap = &r->allows; *ap != NULL; ap = &(*ap)->next)
	  ;
	*ap = a;
  }
  else if (streq(el, "deny")) {
	Deny *d, **ad;
	Rule *r;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_RULE)
	  return;
	r = state->object.rules;
	d = ALLOC(Deny);
	d->id = NULL;
	d->expr = NULL;
	d->next = NULL;
	parse_xml_push((void *)
				   parse_xml_make_state(ACS_PARSE_DENY, (void *) d));

	acl_rule_deny_attr_tab[0].value = &d->id;

	if (parse_xml_attr(acl_rule_deny_attr_tab, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  return;
	}

	for (ad = &r->denies; *ad != NULL; ad = &(*ad)->next)
	  ;
	*ad = d;
  }
  else {
	parse_xml_set_error(ds_xprintf("Unknown element: %s", el));
	return;
  }
}

static void
parse_xml_acl_element_end(void *data, const char *element)
{
  char *el, *err;
  Acl_rule *ar;
  Parse_xml_acs_state *state;

  if (parse_xmlns_name(element, NULL, &el) == -1) {
	parse_xml_set_error(ds_xprintf("Invalid namespace: %s", element));
	return;
  }

  ar = *(Acl_rule **) data;

  if (parse_xml_is_error(NULL))
	return;

  err = "";
  if (streq(el, "acl_rule")) {
	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
	  goto err;
	if (state->code != ACS_PARSE_ACL_RULE || parse_xml_is_not_empty())
	  goto err;
	ar = state->object.acl_rule;
	if (ar->services == NULL) {
	  err = "One service element must appear";
	  goto err;
	}
	if (ar->rules == NULL) {
	  err = "At least one rule element must appear";
	  goto err;
	}
  }
  else if (streq(el, "identity")) {
	/* Do nothing. */
  }
  else if (streq(el, "services")) {
	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
	  goto err;
	if (state->code != ACS_PARSE_SERVICES)
	  goto err;
	if (state->object.services->service == NULL)
	  goto err;
	free(state);
  }
  else if (streq(el, "delegate")) {
	/* Do nothing. */
  }
  else if (streq(el, "service")) {
	/* Do nothing. */
  }
  else if (streq(el, "rule")) {
	Rule *r;

	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
	  goto err;
	if (state->code != ACS_PARSE_RULE)
	  goto err;
	r = state->object.rules;
	if (r == NULL) {
	  err = "At least one rule element must appear";
	  goto err;
	}
	free(state);
  }
  else if (streq(el, "precondition")) {
	Precondition *pre;

	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
	  goto err;
	if (state->code != ACS_PARSE_PRECONDITION)
	  goto err;
	pre = state->object.precondition;
	if (pre->predicate == NULL && pre->user_list == NULL) {
	  err = "A predicate element or user_list element is required";
	  goto err;
	}
	free(state);
  }
  else if (streq(el, "user_list")) {
	User_list *ul;

	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
	  goto err;
	if (state->code != ACS_PARSE_USER_LIST)
	  goto err;
	ul = state->object.user_list;
	free(state);
  }
  else if (streq(el, "user")) {
	User *u;

	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
	  goto err;
	if (state->code != ACS_PARSE_USER)
	  goto err;
	u = state->object.user;
	free(state);
  }
  else if (streq(el, "predicate")) {
	Predicate *p;
	Ds *new_expr;

	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
	  goto err;
	if (state->code != ACS_PARSE_PREDICATE)
	  goto err;
	p = state->object.predicate;
	if (p->expr != NULL
		&& (new_expr = acs_elide_comments(ds_buf(p->expr))) != NULL)
	  p->expr = new_expr;
	free(state);
  }
  else if (streq(el, "allow")) {
	Allow *a;
	Ds *new_expr;

	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
	  goto err;
	if (state->code != ACS_PARSE_ALLOW)
	  goto err;
	a = state->object.allows;
	if (a->expr != NULL
		&& (new_expr = acs_elide_comments(ds_buf(a->expr))) != NULL)
	  a->expr = new_expr;
	free(state);
  }
  else if (streq(el, "deny")) {
	Deny *d;
	Ds *new_expr;

	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
	  goto err;
	if (state->code != ACS_PARSE_DENY)
	  goto err;
	d = state->object.denies;
	if (d->expr != NULL
		&& (new_expr = acs_elide_comments(ds_buf(d->expr))) != NULL)
	  d->expr = new_expr;
	free(state);
  }
  else {
	err = ds_xprintf("Unknown element: %s", el);
	goto err;
  }

  return;

 err:
  parse_xml_set_error(err);
}

/*
 * Get character data that belongs to an expression.
 * This may consist of several lines, fed to this function in small chunks.
 */
static void
parse_xml_char_data_handler(void *userData, const XML_Char *s, int len)
{
  int nlen;
  char *ptr;
  Parse_xml_acs_state *state;
  Ds **dsp;

  if (parse_xml_is_error(NULL))
    return;

  if (parse_xml_top((void **) &state) == PARSE_XML_ERROR)
    return;

  if (state->code == ACS_PARSE_PREDICATE) {
	Predicate *p;

	p = state->object.predicate;
	dsp = &p->expr;
  }
  else if (state->code == ACS_PARSE_ALLOW) {
	Allow *a;

	a = state->object.allows;
	dsp = &a->expr;
  }
  else if (state->code == ACS_PARSE_DENY) {
	Deny *d;

	d = state->object.denies;
	dsp = &d->expr;
  }
  else {
	/* XXX Shouldn't this detect bogus, non-comment/non-whitespace text? */
	/*	parse_xml_set_error("Character data is not allowed here; "); */
    return;
  }

  if (*dsp == NULL) {
	/* Ignore leading whitespace. */
	nlen = len;
	ptr = (char *) s;
	while (nlen > 0 && (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')) {
	  ptr++;
	  nlen--;
	}

	if (nlen == 0)
	  return;
	*dsp = ds_init(NULL);
  }
  else {
	ptr = (char *) s;
	nlen = len;
  }

  if (nlen > 0)
	ds_concatn(*dsp, ptr, nlen);
}

int
parse_xml_acl(char *str, Acl_rule **acl)
{
  int st;
  Parse_xml_error err;
  XML_Parser p = XML_ParserCreateNS(NULL, XMLNS_SEP_CHAR);

  if (p == NULL)
	return(-1);

  parse_xml_init("Acl_rule", p);

  XML_SetElementHandler(p, parse_xml_acl_element_start,
						parse_xml_acl_element_end);
  XML_SetCommentHandler(p, parse_xml_comment_handler);
  XML_SetCharacterDataHandler(p, parse_xml_char_data_handler);
  XML_SetDefaultHandler(p, parse_xml_default_handler);
  XML_SetUserData(p, (void *) acl);

  st = XML_Parse(p, str, strlen(str), 1);
  if (parse_xml_is_error(&err) || st == 0) {
	if (err.mesg == NULL) {
	  parse_xml_set_error(NULL);
	  parse_xml_is_error(&err);
	}

    log_msg((LOG_ERROR_LEVEL, "parse_xml_acl: line %d, pos %d",
             err.line, err.pos));
    if (err.mesg != NULL)
      log_msg((LOG_ERROR_LEVEL, "parse_xml_acl: %s", err.mesg));
    parse_xml_end();
    return(-1);
  }

  parse_xml_end();

  if (st == 0)
	return(-1);

  if (parse_xml_is_not_empty())
	return(-1);

  return(0);
}

char *
acl_services_xml_text(Ds *ods, Services *ss)
{
  Ds *ds;
  Service *s;

  if (ods == NULL)
	ds = ds_init(NULL);
  else
	ds = ods;

  if (ss->shared != NULL)
	ds_asprintf(ds, "  <services shared=\"%s\">\n", ss->shared);
  else
	ds_asprintf(ds, "  <services>\n");

  for (s = ss->service; s != NULL; s = s->next) {
	if (s->rule_uri != NULL) {
	  ds_asprintf(ds, "      <delegate");
	  if (s->id != NULL)
		ds_asprintf(ds, " id=\"%s\"", s->id);
	  if (s->url_pattern != NULL)
		ds_asprintf(ds, " url_pattern=\"%s\"", strquote(s->url_pattern, "\""));
	  else if (s->url_expr != NULL)
		ds_asprintf(ds, " url_expr='%s'", strquote(s->url_expr, "\'"));
	  else {
		/* Impossible? */
		ds_asprintf(ds, " url_pattern=\"???\"");
	  }
	  ds_asprintf(ds, " rule_uri=\"%s\"/>\n", s->rule_uri);
	}
	else {
	  ds_asprintf(ds, "      <service");
	  if (s->id != NULL)
		ds_asprintf(ds, " id=\"%s\"", s->id);
	  if (s->url_pattern != NULL)
		ds_asprintf(ds, " url_pattern=\"%s\"/>\n",
					strquote(s->url_pattern, "\""));
	  else if (s->url_expr != NULL)
		ds_asprintf(ds, " url_expr='%s'/>\n",
					strquote(s->url_expr, "\'"));
	  else {
		/* Impossible? */
		ds_asprintf(ds, " url_pattern=\"???\"/>\n");
	  }
	}
  }

  ds_asprintf(ds, "  </services>\n");

  return(ds_buf(ds));
}

char *
acl_identities_xml_text(Ds *ods, Identity *identities)
{
  Ds *ds;
  Identity *id;

  if (ods == NULL)
	ds = ds_init(NULL);
  else
	ds = ods;

  for (id = identities; id != NULL; id = id->next) {
	ds_asprintf(ds, "  <identity");
	if (id->id != NULL)
	  ds_asprintf(ds, " id=\"%s\"", id->id);
	ds_asprintf(ds, " iptr=\"%s\"", id->iptr);
	ds_asprintf(ds, " ident=\"%s\"", id->ident);
	ds_asprintf(ds, " selector_expr='%s'/>\n", id->selector_expr);
  }

  if (identities != NULL)
	ds_asprintf(ds, "\n");

  return(ds_buf(ds));
}

char *
acl_rules_xml_text(Ds *ods, Rule *rules)
{
  char *trimmed_expr;
  Allow *a;
  Deny *d;
  Ds *ds;
  Rule *r;
  User *u;

  if (ods == NULL)
	ds = ds_init(NULL);
  else
	ds = ods;

  for (r = rules; r != NULL; r = r->next) {
	ds_asprintf(ds, "  <rule");
	if (r->id != NULL)
	  ds_asprintf(ds, " id=\"%s\"", r->id);
	if (r->order_str != NULL)
	  ds_asprintf(ds, " order=\"%s\"", r->order_str);
	if (r->default_attrs.constraint)
	  ds_asprintf(ds,
				  " constraint=\"%s\"", r->default_attrs.constraint);
	if (r->default_attrs.permit_chaining)
	  ds_asprintf(ds,
				  " permit_chaining=\"%s\"", r->default_attrs.permit_chaining);
	if (r->default_attrs.pass_credentials)
	  ds_asprintf(ds,
				  " pass_credentials=\"%s\"",
				  r->default_attrs.pass_credentials);
	if (r->default_attrs.pass_http_cookie)
	  ds_asprintf(ds,
				  " pass_http_cookie=\"%s\"",
				  r->default_attrs.pass_http_cookie);
	if (r->default_attrs.permit_caching)
	  ds_asprintf(ds,
				  " permit_caching=\"%s\"", r->default_attrs.permit_caching);
	ds_asprintf(ds, ">\n");

	if (r->precondition != NULL) {
	  ds_asprintf(ds, "   <precondition>\n");
	  if (r->precondition->user_list != NULL) {
		ds_asprintf(ds, "     <user_list>\n");
		for (u = r->precondition->user_list->user; u != NULL; u = u->next) {
		  if (u->id != NULL)
			ds_asprintf(ds, "       <user id=\"%s\" name=\"%s\"/>\n",
						u->id, u->name);
		  else
			ds_asprintf(ds, "       <user name=\"%s\"/>\n", u->name);
		}
		ds_asprintf(ds, "     </user_list>\n");
	  }
	  if (r->precondition->predicate != NULL) {
		ds_asprintf(ds, "    <predicate>\n");
		if (r->precondition->predicate->expr != NULL) {
		  trimmed_expr
			= strtrim(ds_buf(r->precondition->predicate->expr), " ", 0);
		  ds_asprintf(ds, "      %s", xml_escape_cdata(trimmed_expr));
		}
		ds_asprintf(ds, "    </predicate>\n");
	  }
	  ds_asprintf(ds, "   </precondition>\n");
	}

	if (r->allows != NULL) {
	  for (a = r->allows; a != NULL; a = a->next) {
		ds_asprintf(ds, "   <allow");
		if (a->id != NULL)
		  ds_asprintf(ds, " id=\"%s\"", a->id);
		if (a->attrs.constraint)
		  ds_asprintf(ds, " constraint=\"%s\"", a->attrs.constraint);
		if (a->attrs.permit_chaining)
		  ds_asprintf(ds, " permit_chaining=\"%s\"", a->attrs.permit_chaining);
		if (a->attrs.pass_credentials)
		  ds_asprintf(ds, " pass_credentials=\"%s\"",
					  a->attrs.pass_credentials);
		if (a->attrs.pass_http_cookie)
		  ds_asprintf(ds, " pass_http_cookie=\"%s\"",
					  a->attrs.pass_http_cookie);
		if (a->attrs.permit_caching)
		  ds_asprintf(ds, " permit_caching=\"%s\"", a->attrs.permit_caching);
		ds_asprintf(ds, ">\n");
		if (a->expr != NULL) {
		  trimmed_expr = strtrim(ds_buf(a->expr), " ", 0);
		  ds_asprintf(ds, "       %s", xml_escape_cdata(trimmed_expr));
		}
		ds_asprintf(ds, "   </allow>\n");
	  }
	}
	if (r->denies != NULL) {
	  for (d = r->denies; d != NULL; d = d->next) {
		if (d->id != NULL)
		  ds_asprintf(ds, "   <deny id=\"%s\">\n", d->id);
		else
		  ds_asprintf(ds, "   <deny>\n");
		if (d->expr != NULL) {
		  trimmed_expr = strtrim(ds_buf(d->expr), " ", 0);
		  ds_asprintf(ds, "       %s", xml_escape_cdata(trimmed_expr));
		}
		ds_asprintf(ds, "   </deny>\n");
	  }
	}

	ds_asprintf(ds, "  </rule>\n");
  }

  return(ds_buf(ds));
}

char *
acl_start_xml_text(Ds *ods, Acl_rule *acl)
{
  Ds *ds;

  if (ods == NULL)
	ds = ds_init(NULL);
  else
	ds = ods;

  ds_asprintf(ds, "<acl_rule");
  ds_asprintf(ds, " status=\"%s\"",
			  (acl->status == ACL_STATUS_ENABLED) ? "enabled" : "disabled");
  if (acl->name != NULL)
	ds_asprintf(ds, " name=\"%s\"", acl->name);
  if (acl->expires_expr != NULL)
	ds_asprintf(ds, " expires_expr='%s'", acl->expires_expr);
  if (acl->default_attrs.constraint)
	ds_asprintf(ds, " constraint=\"%s\"",
				acl->default_attrs.constraint);
  if (acl->default_attrs.permit_chaining)
	ds_asprintf(ds, " permit_chaining=\"%s\"",
				acl->default_attrs.permit_chaining);
  if (acl->default_attrs.pass_credentials)
	ds_asprintf(ds, " pass_credentials=\"%s\"",
				acl->default_attrs.pass_credentials);
  if (acl->default_attrs.pass_http_cookie)
	ds_asprintf(ds, " pass_http_cookie=\"%s\"",
				acl->default_attrs.pass_http_cookie);
  if (acl->default_attrs.permit_caching)
	ds_asprintf(ds, " permit_caching=\"%s\"",
				acl->default_attrs.permit_caching);
  ds_asprintf(ds, ">\n");

  return(ds_buf(ds));
}

char *
acl_end_xml_text(Ds *ods, Acl_rule *acl)
{
  Ds *ds;

  if (ods == NULL)
	ds = ds_init(NULL);
  else
	ds = ods;

  ds_asprintf(ds, "</acl_rule>\n");

  return(ds_buf(ds));
}

char *
acl_xml_text(Acl_rule *acl)
{
  Ds ds;

  ds_init(&ds);
  acl_start_xml_text(&ds, acl);
  acl_services_xml_text(&ds, acl->services);
  acl_identities_xml_text(&ds, acl->identities);
  acl_rules_xml_text(&ds, acl->rules);
  acl_end_xml_text(&ds, acl);

  return(ds_buf(&ds));
}

/****************************************************************************/

/*
 * Given NAME, a "raw" ACL name in CONTEXT used within its virtual store,
 * parse it and determine whether it's enabled, disabled, or invalid.
 * Return its mapped name (ACLNAME) and numeric suffix (SUFFIX).
 */
static Acl_status
parse_acl_name(const char *context, const char *name,
			   char **aclname, int *suffix)
{
  int i, n;
  char *nump, *p, *q;
  const char *base;
  Acl_status status;
  Ds *ds, xname;
  Dsvec *dsv;

  if (context == NULL)
	base = name;
  else if ((p = strprefix(name, context)) != NULL)
	base = p;
  else
	return(ACL_STATUS_ERROR);

  if (*base != '/') {
	dsv = dsvec_init(NULL, sizeof(Ds *));
	ds = ds_set(NULL, (char *) base);
	dsvec_add_ptr(dsv, ds);
	dsvec_add_ptr(dsv, NULL);
  }
  else {
	if ((dsv = uri_path_parse((char *) base)) == NULL)
	  return(ACL_STATUS_ERROR);
  }

  /*
   * An ACL is disabled if any of the path components in its name
   * has the "disabled prefix".
   * Build the ACL name that corresponds to the virtual one.
   */
  status = ACL_STATUS_ENABLED;
  q = NULL;
  ds_init(&xname);
  for (i = 0; (ds = (Ds *) dsvec_ptr_index(dsv, i)) != NULL; i++) {
	p = ds_buf(ds);
	if ((q = strprefix(p, ACL_DISABLED_NAME_PREFIX)) != NULL) {
	  status = ACL_STATUS_DISABLED;
	  p = q;
	}
	if ((q = p = strprefix(p, ACL_NAME_PREFIX)) == NULL)
	  return(ACL_STATUS_ERROR);
	ds_asprintf(&xname, "%s%s", i != 0 ? "/" : "", p);
  }

  if ((p = strrchr(name, '/')) == NULL)
	p = (char *) name;
  else
	p++;

  if ((nump = strrchr(p, '.')) == NULL)
	return(ACL_STATUS_ERROR);

  /*
   * There must be at least once character between the end of the name
   * prefix and the dot; i.e., "acl-.0" is invalid.
   */
  if (nump == q)
	return(ACL_STATUS_ERROR);

  /* At least one character must follow the dot. */
  if (*(nump + 1) == '\0')
	return(ACL_STATUS_ERROR);

  n = 0;
  for (q = nump + 1; *q != '\0'; q++) {
	if (!isdigit((int) *q))
	  return(ACL_STATUS_ERROR);
	n = n * 10 + (*q - '0');
  }

  if (suffix != NULL)
	*suffix = n;

  if (aclname != NULL)
	*aclname = ds_buf(&xname);

  return(status);
}

static int
fts_compar(const void *ap, const void *bp)
{
  int a_n, b_n;
  const FTSENT **a, **b;

  a = (const FTSENT **) ap;
  b = (const FTSENT **) bp;
  if (parse_acl_name(NULL, (*a)->fts_name, NULL, &a_n) == ACL_STATUS_ERROR)
	return(0);
  if (parse_acl_name(NULL, (*b)->fts_name, NULL, &b_n) == ACL_STATUS_ERROR)
	return(0);

  if (verbose_level > 1)
	log_msg((LOG_TRACE_LEVEL, "%s : %s (%d)",
			 (*a)->fts_name, (*b)->fts_name, a_n - b_n));

  if ((a_n - b_n) == 0)
	return(strcmp((*a)->fts_name, (*b)->fts_name));

  return(a_n - b_n);
}

static int
is_valid(char *name)
{

  return(parse_acl_name(NULL, name, NULL, NULL) != ACL_STATUS_ERROR);
}

static int
add(char *naming_context, char *name, void ***xxx)
{
  int n;
  char *aclname;
  Acl_file *a;
  Acl_status status;
  Dsvec *dsv;

  if ((status = parse_acl_name(naming_context, name, &aclname, &n))
	  == ACL_STATUS_ERROR)
    return(0);

  dsv = (Dsvec *) xxx;

  a = ALLOC(Acl_file);
  a->status = status;
  a->aclname = aclname;
  if (naming_context != NULL) {
    char *p;

    a->naming_context = strdup(naming_context);
    p = name + strlen(a->naming_context);
    if (*p == '/')
      p++;
    a->path = strdup(p);
  }
  else {
    a->naming_context = "";
    a->path = strdup(name);
  }
  a->sortkey = n;

  dsvec_add_ptr(dsv, a);

  return(1);
}

/*
 * Given a path component of an ACL name, strip off prefixes and return
 * the bare NAME, non-negative integer SUFFIX, and status of the ACL.
 * E.g., disabled-acl-foo.17 will set NAME to "foo", SUFFIX to 17, and
 * return ACL_STATUS_DISABLED.
 */
static Acl_status
acl_parse_name_el(char *el, char **name, unsigned int *suffix)
{
  char *dotp, *p, *q;
  Acl_status st;

  p = strdup(el);
  if ((q = strprefix(p, ACL_DISABLED_NAME_PREFIX)) != NULL) {
	st = ACL_STATUS_DISABLED;
	p = q;
  }
  else {
	st = ACL_STATUS_ENABLED;
  }

  if ((q = strprefix(p, ACL_NAME_PREFIX)) == NULL)
	return(ACL_STATUS_ERROR);
  p = q;

  if ((dotp = strrchr(p, (int) '.')) == NULL)
	return(ACL_STATUS_ERROR);
  *dotp = '\0';
  q = dotp + 1;

  if (name != NULL)
	*name = p;

  if (suffix != NULL) {
	if (strnum(q, STRNUM_UI, suffix) == -1)
	  return(ACL_STATUS_ERROR);
  }

  return(st);
}

static int
acl_aclname_compar(const void *ap, const void *bp)
{
  int a_len, b_len, i, st;
  unsigned int a_suffix, b_suffix;
  char *a_str, *b_str, *a_name, *b_name;
  Acl_file *a, *b;
  Dsvec *a_parts, *b_parts;

  a = *(Acl_file **) ap;
  b = *(Acl_file **) bp;

  if ((a_parts = strsplit(a->path, "/", 0)) == NULL)
	return(0);
  if ((b_parts = strsplit(b->path, "/", 0)) == NULL)
	return(0);

  a_len = dsvec_len(a_parts);
  b_len = dsvec_len(b_parts);

  for (i = 0; i < a_len; i++) {
	if (i == b_len)
	  return(-1);

	a_str = (char *) dsvec_ptr_index(a_parts, i);
	b_str = (char *) dsvec_ptr_index(b_parts, i);

	/*
	 * Strip off the prefix(es) to get the name to sort on, and the
	 * integer suffix to use as the secondary sort key.
	 */
	if (acl_parse_name_el(a_str, &a_name, &a_suffix) == ACL_STATUS_ERROR)
	  return(0);
	if (acl_parse_name_el(b_str, &b_name, &b_suffix) == ACL_STATUS_ERROR)
	  return(0);

	if ((st = strcmp(a_name, b_name)) != 0)
	  return(st);
	if (a_suffix != b_suffix)
	  return(a_suffix - b_suffix);
  }

  if (b_len > a_len)
	return(-1);

  return(0);
}

Dsvec *
sort_acls(Dsvec *acls)
{

  dsvec_sort(acls, acl_aclname_compar);

  return(acls);
}

Dsvec *
get_acls(Vfs_handle *h)
{
  int n;
  Dsvec *dsv;

  dsv = dsvec_init(NULL, sizeof(Acl_file *));
  if ((n = vfs_list(h, is_valid, fts_compar, add, (void ***) dsv)) == -1)
    return(NULL);

  return(dsv);
}

/****************************************************************************/

int show_ignored_acl_names = 0;

static int
parse_indexed_acl_name(const char *context, const char *name,
					   char **aclname, int *suffix)
{
  int i, n;
  char *nump, *p, *q;
  const char *base;
  Ds *ds, xname;
  Dsvec *dsv;

  if (context == NULL)
	base = name;
  else if ((p = strprefix(name, context)) != NULL)
	base = p;
  else {
  parse_error:
	log_msg((LOG_WARN_LEVEL, "Invalid ACL name: \"%s\"", name));
	return(-1);

  invalid_acl_name:
	return(-1);
  }

  if (*base != '/') {
	dsv = dsvec_init(NULL, sizeof(Ds *));
	ds = ds_set(NULL, (char *) base);
	dsvec_add_ptr(dsv, ds);
	dsvec_add_ptr(dsv, NULL);
  }
  else {
	if ((dsv = uri_path_parse((char *) base)) == NULL)
	  goto parse_error;
  }

  ds_init(&xname);
  for (i = 0; (ds = (Ds *) dsvec_ptr_index(dsv, i)) != NULL; i++) {
	p = ds_buf(ds);
	ds_asprintf(&xname, "%s%s", i != 0 ? "/" : "", p);
  }

  if ((p = strrchr(name, '/')) == NULL)
	p = (char *) name;
  else
	p++;

  if ((nump = strrchr(p, '.')) == NULL)
	goto invalid_acl_name;

  /*
   * There must be at least once character between the end of the name
   * prefix and the dot; i.e., "acl-.0" is invalid.
   */
  if ((q = strprefix(p, ACL_NAME_PREFIX)) == NULL)
	goto invalid_acl_name;
  if (nump == q)
	goto invalid_acl_name;
  if (*(nump + 1) == '\0')
	goto invalid_acl_name;

  n = 0;
  for (q = nump + 1; *q != '\0'; q++) {
	if (!isdigit((int) *q))
	  goto invalid_acl_name;
	n = n * 10 + (*q - '0');
  }

  if (suffix != NULL)
	*suffix = n;

  if (aclname != NULL)
	*aclname = ds_buf(&xname);

  return(0);
}

static int
fts_compar_indexed(const void *ap, const void *bp)
{
  int a_n, b_n;
  const FTSENT **a, **b;

  a = (const FTSENT **) ap;
  b = (const FTSENT **) bp;
  if (parse_indexed_acl_name(NULL, (*a)->fts_name, NULL, &a_n)
	  == ACL_STATUS_ERROR)
	return(0);
  if (parse_indexed_acl_name(NULL, (*b)->fts_name, NULL, &b_n) == -1)
	return(0);

  if (verbose_level > 1)
	log_msg((LOG_TRACE_LEVEL, "%s : %s (%d)",
			 (*a)->fts_name, (*b)->fts_name, a_n - b_n));

  if ((a_n - b_n) == 0)
	return(strcmp((*a)->fts_name, (*b)->fts_name));

  return(a_n - b_n);
}

static int
is_valid_indexed(char *name)
{
  int st;

  st = (parse_indexed_acl_name(NULL, name, NULL, NULL) != -1);

  return(st);
}

static int
add_indexed(char *naming_context, char *name, void ***xxx)
{
  int n;
  char *aclname;
  Acl_file *a;
  Dsvec *dsv;

  if (parse_indexed_acl_name(naming_context, name, &aclname, &n) == -1)
    return(0);

  dsv = (Dsvec *) xxx;

  a = ALLOC(Acl_file);
  a->status = ACL_STATUS_ENABLED;
  a->aclname = aclname;
  if (naming_context != NULL) {
    char *p;

    a->naming_context = strdup(naming_context);
    p = name + strlen(a->naming_context);
    if (*p == '/')
      p++;
    a->path = strdup(p);
  }
  else {
    a->naming_context = "";
    a->path = strdup(name);
  }
  a->sortkey = n;

  dsvec_add_ptr(dsv, a);

  return(1);
}

/*
 * As part of index generation, get a list of ACLs.
 */
Dsvec *
list_indexed_acls(Vfs_handle *h)
{
  int n;
  Dsvec *dsv;

  dsv = dsvec_init(NULL, sizeof(Acl_file *));
  if ((n = vfs_list(h, is_valid_indexed, fts_compar_indexed, add_indexed,
					(void ***) dsv)) == -1)
    return(NULL);

  return(dsv);
}

/****************************************************************************/

int
get_acl_file_list(Vfs_handle *h, Acl_file ***af)
{
  int i, n;
  Dsvec *acls;

  if ((acls = get_acls(h)) == NULL)
	return(-1);
  n = dsvec_len(acls);

  if (af != NULL) {
    if (n > 0)
      *af = (Acl_file **) dsvec_base(acls);
    else
      *af = NULL;
  }

  log_msg((LOG_TRACE_LEVEL, "ACLs found: %d", n));
  for (i = 0; i < n; i++) {
	Acl_file *a;

	a = dsvec_ptr(acls, i, Acl_file *);
	if (i == 0)
	  log_msg((LOG_TRACE_LEVEL, "ACL context: %s", a->naming_context));
	log_msg((LOG_TRACE_LEVEL, "  path=\"%s\" (%s)", a->aclname,
			 (a->status == ACL_STATUS_ENABLED) ? "enabled"
			 : ((a->status == ACL_STATUS_DISABLED) ? "disabled" : "error")));
  }

  return(n);
}

Acs_expr_result
acs_eval(char *expr, Kwv *kwv, Kwv *kwv_args, Credentials *cr,
		 char **result_str)
{
  Acs_environment env;
  Acs_expr_result st;
  Expr_result result;

  acs_new_env(&env);
  if (acs_init_env(kwv, kwv_args, NULL, cr, &env) == -1)
	return(ACS_EXPR_EVAL_ERROR);

  st = acs_expr(expr, &env, &result);

  if (result_str != NULL) {
	if (!acs_expr_error_occurred(st))
	  *result_str = acs_format_result(&result);
	else
	  *result_str = NULL;
  }

  if (st == ACS_EXPR_TRUE && result_str != NULL && *result_str != NULL)
	log_msg((LOG_TRACE_LEVEL, "Eval result: %s", *result_str));

  return(st);
}

int
acs_match_url_segs(char *url_path, Dsvec *dsv_req, int *exact)
{
  int n;
  Ds *req_el, *rule_el;
  Dsvec *dsv_rule;

  if ((dsv_rule = uri_path_parse(url_path)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Rule URL decode failed: %s", url_path));
	return(-1);
  }

  /*
   * Compare each element of the rule's path with the corresponding
   * element of the request's path until they're not identical or one
   * of them ends.
   */
  *exact = 0;
  n = 0;

  while (1) {
	rule_el = (Ds *) dsvec_ptr_index(dsv_rule, n);
	req_el = (Ds *) dsvec_ptr_index(dsv_req, n);
	if (rule_el == NULL || req_el == NULL || !ds_eq(rule_el, req_el))
	  break;
	n++;
  }

  /*
   * Exactly one of the following is true:
   *   o they're identical
   *   o end of rule, ends with a "*" element
   *   o the rule is shorter but doesn't end with a "*" element
   *   o they're not equal (not identical, actually) -> no match
   *   o the request is shorter                      -> no match
   */
  if (rule_el == NULL && req_el == NULL) {
	log_msg((LOG_TRACE_LEVEL, "match (identical)"));
	*exact = 1;
	/*
	 * Special case: if '/' matches '/', count that as one, not zero.
	 */
	if (n == 0)
	  n = 1;
	return(n);
  }

  if (rule_el != NULL && streq(ds_buf(rule_el), "*")) {
	log_msg((LOG_TRACE_LEVEL, "match (wildcard)"));
	return(++n);
  }

  if (rule_el != NULL && req_el != NULL) {
	log_msg((LOG_TRACE_LEVEL, "no match (components)"));
	return(0);
  }

  if (req_el == NULL) {
	log_msg((LOG_TRACE_LEVEL, "no match (shorter req)"));
	return(0);
  }

  if (rule_el == NULL) {
	log_msg((LOG_TRACE_LEVEL, "no match (longer req)"));
	return(0);
  }

  log_msg((LOG_ERROR_LEVEL, "no match... BUG!"));
  return(0);
}

static Uri_pattern *
init_uri_pattern(Uri_pattern *opat)
{
  Uri_pattern *pat;

  if (opat == NULL)
	pat = ALLOC(Uri_pattern);
  else
	pat = opat;

  pat->uri = NULL;
  pat->need_ssl = -1;
  pat->scheme = NULL;
  pat->authority = NULL;
  pat->userinfo = NULL;
  pat->host = NULL;
  pat->host_pat_type = URI_PATTERN_HOST_INVALID;
  pat->port = 0;
  pat->port_given = NULL;
  pat->path = NULL;
  pat->path_parts = NULL;

  return(pat);
}

/*
 * Given a URI-format pattern, parse it for subsequent matching by
 * uri_pattern_match().
 *
 * The syntax is:
 *   <scheme-spec> :// <authority-spec> <path>
 * Where:
 *  <scheme-spec> is:    "http" | "https" | "http*" (for either)
 *  <authority-spec> is: [ userinfo "@" ] <host> [":" portnum]
 *
 * <host> is:
 * - a domain name
 * - a domain name with a subdomain wildcard; the leading hostname component
 *   may be a "*", which matches zero or more subdomains (e.g.,
 *   "*.example.com" matches "example.com", "foo.example.com", and
 *   "foo.bar.example.com") or "+", which matches one or more subdomains
 *   (e.g., "+.example.com" matches "foo.example.com" but not "example.com").
 * - a complete IP address in dotted notation
 * - a partial IP address (e.g., "192.168")
 * - a masked IP address (e.g., "192.168:255.255.0.0")
 * - a CIDR specification (e.g., "192.168.0.0/16")
 * The <host> can optionally be within square brackets, but they are required
 * for a masked IP address or CIDR specification (the brackets are
 * necessary because a '/' or ':' may appear as a separator, which would create
 * ambiguity with the start of the path component or port component).
 *
 * portnum
 * The default port for "http" is 80 and the default port for "https" is 443;
 * if the rule element does not specify a port, then any port in the request
 * URL matches (if the rule element specifies "https" but no port, the
 * request must be "https" but any port will do).
 * Note that if brackets are used and the portnum is present it must appear
 * after the closing bracket (because a ':' may appear within the brackets).
 *
 * <path> is optional, and may include a "*" component at its end to
 * specify wildcard tail matching.
 *
 * Examples:
 *   http://example.com
 *   https://foo.example.com:8443
 *   http*://[*.example.com]/.../...
 *   http://[192.168.0.0/24]:80/.../...
 *   http://192.168/public/.../...
 *   http://[*.edu]
 *
 * <path> is the same as for ACL relative path matching.
 *
 * XXX Is there an RFC for this?  Probably.
 */
Uri_pattern *
uri_parse_pattern(char *str)
{
  char *e, *p, *s;
  char *auth, *auth_end, *userinfo_end;
  Uri xuri;
  Uri_pattern *pat;

  if ((e = strcaseprefix(str, "http://")) != NULL) {
	pat = init_uri_pattern(NULL);
	pat->need_ssl = 0;
	pat->scheme = "http";
  }
  else if ((e = strcaseprefix(str, "https://")) != NULL) {
	pat = init_uri_pattern(NULL);
	pat->need_ssl = 1;
	pat->scheme = "https";
  }
  else if ((e = strcaseprefix(str, "http*://")) != NULL) {
	pat = init_uri_pattern(NULL);
	pat->need_ssl = 2;
	pat->scheme = "http*";
  }
  else
	return(NULL);

  pat->uri = strdup(str);

  /*
   * Next comes the authority component, with the host part optionally
   * within square brackets.
   * authority   = [ userinfo "@" ] host [ ":" port ]
   *          or = [ userinfo "@" ] "[" host "]" [ ":" port ]
   */

  /* Isolate the authority component. */
  s = e;
  if ((p = strpbrk(s, "[/?#")) != NULL && *p == '[') {
	if ((p = strchr(p + 1, (int) ']')) == NULL)
	  return(NULL);
	if ((auth_end = strpbrk(p + 1, "/?#")) != NULL)
	  auth = strndup(s, auth_end - s);
	else {
	  auth = strdup(s);
	  auth_end = "";
	}
  }
  else if (p != NULL) {
	auth_end = p;
	auth = strndup(s, auth_end - s);
  }
  else {
	auth = strdup(s);
	auth_end = "";
  }
  pat->authority = auth;

  if ((userinfo_end = strchr(pat->authority, (int) '@')) != NULL) {
    p = strndup(auth, userinfo_end - auth);
    pat->userinfo = url_decode(p, NULL, NULL);
	s = userinfo_end + 1;
  }
  else
	s = pat->authority;

  if ((p = strchr(s, (int) '[')) != NULL) {
	p++;
	if ((e = strchr(p, (int) ']')) == NULL)
	  return(NULL);
	pat->host = strndup(p, e - p);
	s = e + 1;
  }
  else
	pat->host = s;

  if ((e = strchr(s, (int) ':')) != NULL) {
    /* There's a port. */
    *e++ = '\0';
    pat->port_given = e;
    if (strnum(e, STRNUM_IN_PORT_T, &pat->port) == -1)
      return(NULL);
  }

  /*
   * Figure out the host-matching pattern.
   *   URI_PATTERN_HOST_DN: a normal domain name
   *   URI_PATTERN_HOST_DN_STAR: a domain name with an initial '*' wildcard
   *   URI_PATTERN_HOST_DN_PLUS: a domain name with an initial '+' wildcard
   *   URI_PATTERN_HOST_IP: a normal IP address in dotted notation,
   *       a partially specified dotted IP address, an IP address range using
   *       a mask specifier, or an IP address range in CIDR notation
   *   URI_PATTERN_HOST_ANY: '*' means any host
   *   URI_PATTERN_HOST_INVALID: unrecognized syntax
   */
  s = pat->host;
  if (*s == '*') {
	if (*(s + 1) == '\0')
	  pat->host_pat_type = URI_PATTERN_HOST_ANY;
	else if (*(s + 1) != '.')
	  return(NULL);
	else {
	  if (!looks_like_domain_name(s + 2))
		return(NULL);
	  pat->host_pat_type = URI_PATTERN_HOST_DN_STAR;
	}
  }
  else if (*s == '+') {
	if (*(s + 1) != '.')
	  return(NULL);
	if (!looks_like_domain_name(s + 2))
	  return(NULL);
	pat->host_pat_type = URI_PATTERN_HOST_DN_PLUS;
  }
  else if (looks_like_domain_name(s))
	pat->host_pat_type = URI_PATTERN_HOST_DN;
  else if (parse_ip_expr(s, &pat->host_addr, &pat->host_mask) == 0)
	pat->host_pat_type = URI_PATTERN_HOST_IP;
  else {
	free(pat);
	return(NULL);
  }

  s = auth_end;
  uri_init(&xuri);
  if (uri_parse_hier_part(&xuri, s, 0) == NULL) {
	free(pat);
	return(NULL);
  }

  if (xuri.path == NULL)
	pat->path = "";
  else
	pat->path = xuri.path;
  pat->path_parts = xuri.path_parts;

  return(pat);
}

/*
 * Match PAT, a valid URI pattern parsed by uri_parse_pattern(), against URI.
 * Return 1 if the match is successful, 0 if there is no match, or -1 if
 * an error occurred.
 *
 * A match is successful only if all of the following sub-matches are true:
 * 1) The scheme components are the same, or PAT permits any scheme
 *
 * 2) If PAT specifies a port, the URI's explicit or default port matches it;
 *    otherwise the ports are considered to match
 *
 * 3) One of the following cases is true:
 * 3a) If both URI and PAT are domain names, they match if they are the same
 *     or map to the same IP address
 * 3b) If URI is a domain name and PAT is an IP address, they match if the
 *     domain name maps within PAT's IP address range
 * 3c) If URI is an IP address and PAT is a domain name, they match if the
 *     domain name maps to the IP address
 * 3d) If URI is an IP address and PAT is an IP address, they match if the
 *     URI's IP address maps within PAT's IP address range
 * 3e) If PAT has a wildcard subdomain specifier, they match if PAT is a
 *     matching domain name
 * 3f) If PAT is the "any" ("*") specifier, they match unconditionally
 *
 * The caller is expected to match any path components.
 */
int
uri_pattern_match(Uri *uri, Uri_pattern *pat)
{
  int i, rc;
  struct in_addr uri_addr;
  Dsvec *pat_dsv, *uri_dsv;

  /* 1. Match the schemes. */
  if (streq(uri->scheme, "http") && pat->need_ssl == 1)
	return(0);

  if (streq(uri->scheme, "https") && pat->need_ssl == 0)
	return(0);

  /* 2. Match the ports. */
  if (pat->port_given != NULL) {
	if (uri->port_given != NULL) {
	  if (!streq(pat->port_given, uri->port_given))
		return(0);
	}
	else {
	  if (strcaseeq(uri->scheme, "http") && pat->port != HTTP_DEFAULT_PORT)
		return(0);
	  if (strcaseeq(uri->scheme, "https") && pat->port != HTTP_DEFAULT_SSL_PORT)
		return(0);
	}
  }

  /* 3. Match the hosts. */
  switch (pat->host_pat_type) {
  case URI_PATTERN_HOST_DN:
	/* Simple case is if they are identical strings. */
	if (strcaseeq(uri->host, pat->host))
	  break;

	/* Check if they map to the same IP address. */
	if (is_ip_addr(uri->host, NULL)) {
	  if (match_ip_domain_name(uri->host, pat->host))
		break;
	}
	else if (match_domain_names(uri->host, pat->host) == 1)
	  break;

	return(0);
	/*NOTREACHED*/

  case URI_PATTERN_HOST_ANY:
	/* Matches any host. */
	break;

  case URI_PATTERN_HOST_DN_STAR:
  case URI_PATTERN_HOST_DN_PLUS:
	pat_dsv = strsplit(pat->host, ".", 0);
	uri_dsv = strsplit(uri->host, ".", 0);
	rc = 0;

	for (i = 0; i < dsvec_len(pat_dsv); i++) {
	  char *pat_part, *uri_part;

	  pat_part = (char *) dsvec_ptr_rindex(pat_dsv, i);
	  if ((uri_part = (char *) dsvec_ptr_rindex(uri_dsv, i)) == NULL) {
		if (streq(pat_part, "*"))
		  break;
		return(0);
	  }

	  if (streq(pat_part, "+") || streq(pat_part, "*"))
		break;

	  if (!strcaseeq(pat_part, uri_part))
		return(0);
	}

	dsvec_free(pat_dsv);
	dsvec_free(uri_dsv);
	break;

  case URI_PATTERN_HOST_IP:
	/* Compare an IP address pattern with mask to domain name or IP address. */
	if (is_ip_addr(uri->host, NULL)) {
	  if (match_ip(uri->host, pat->host_addr, pat->host_mask) == 1)
		break;
	}
	else if (match_domain_name_ip(uri->host, pat->host_addr, pat->host_mask)
			 == 1)
	  break;

	return(0);
	/*NOTREACHED*/

  default:
	return(0);
	/*NOTREACHED*/
  }

  return(1);
}

/*
 * Tests for matching an absolute URL against a pattern.
 * This self-test can be run by dacs_acs - see dacs_acs(8).
 */
typedef struct Uri_pattern_test {
  char *request;		/* An absolute URL. */
  char *pattern;		/* A pattern to match the request against. */
  int result;			/* The expected result of the match. */
} Uri_pattern_test;

static Uri_pattern_test pattern_test[] = {
  { "http://example.com",           "http://example.com",                1 },
  { "http://example.com",           "http://example.com:80",             1 },
  { "http://example.com:80",        "http://example.com",                1 },
  { "http://example.com",           "http://example.com:8080",           0 },
  { "http://EXAMPLE.com",           "http://example.COM",                1 },

  { "http://example.com",           "http://*.example.com",              1 },
  { "http://a.example.com",         "http://*.example.com",              1 },
  { "http://a.b.example.com",       "http://*.example.com",              1 },
  { "https://foo.example.com:8443", "https://foo.example.com",           1 },
  { "http://a.b.example.com/a",     "http*://[*.example.com]/a",         1 },
  { "http://example.edu",           "http://[+.edu]",                    1 },

  { "http://example.com",           "http://*",                          1 },
  { "http://example.com/a/b",       "http://*/a/b",                      1 },
  { "http://example.com/a/b",       "http://*:99/a/b",                   0 },
  { "http://example.com:98/a/b",    "http://*:99/a/b",                   0 },
  { "http://example.com:99/a/b",    "http://*:99/a/b",                   1 },

  { "http://192.168.0.12:80",       "http*://[192.168.0.0/24]:80/a/b/*", 1 },
  { "http://192.168.0.12/a",        "http://[192.168.0.0/24]:80/a",      1 },
  { "http://10.0.0.130/public/",    "http://10.0/public/",               1 },

  { "http://bobo@example.com",      "http://example.com",                1 },
  { "http://bobo@bar.example.com",  "http://example.com",                0 },
  { "http://bobo@bar.example.com",  "http://foo.example.com",            0 },
  { "http://bobo@bar.example.com",  "http://*.com",                      1 },

#ifdef DSS_HOSTED
  /*
   * These tests require appropriate DNS entries or they will fail.
   * They assume:
   *   o bsd9.dss.ca is the official hostname and has the IP address 10.0.0.130
   *   o bsd9.dss.bc.ca is an alias for bsd9.dss.ca
   */
  { "http://bsd9.dss.ca",           "http://bsd9.dss.bc.ca",             1 },
  { "http://10.0.0.130",            "http://bsd9.dss.ca",                1 },
  { "http://10.0.0.130",            "http://10.0.0.131",                 0 },
  { "http://bsd9.dss.ca",           "http://10.0.0.130",                 1 },
  { "http://bsd9.dss.ca/public",    "http://[10.0.0.0/24]/public",       1 },
#endif

  { NULL, NULL, -1 }
};

int
uri_pattern_match_selftest(void)
{
  int i, rc;
  Uri *uri;
  Uri_pattern *pat;

  fprintf(stderr, "Testing URI matching:\n");
  for (i = 0; pattern_test[i].request != NULL; i++) {
	if ((uri = uri_parse(pattern_test[i].request)) == NULL) {
	  fprintf(stderr, "Test %d: request is invalid: %s\n",
			  i, pattern_test[i].request);
	  return(-1);
	}

	if ((pat = uri_parse_pattern(pattern_test[i].pattern)) == NULL) {
	  fprintf(stderr, "Test %d: pattern is invalid: %s\n",
			  i, pattern_test[i].pattern);
	  return(-1);
	}

	rc = uri_pattern_match(uri, pat);
	if (rc != pattern_test[i].result) {
	  fprintf(stderr, "\nFailed on pattern %d (returned %d, expected %d)\n",
			  i + 1, rc, pattern_test[i].result);
	  fprintf(stderr, "uri=\"%s\", pattern=\"%s\", rc=%d\n",
			  pattern_test[i].request, pattern_test[i].pattern,
			  pattern_test[i].result);
	  return(-1);
	}
	fprintf(stderr, ".");
  }
  fprintf(stderr, "\n");
  fprintf(stderr, "OK (%d tests)\n", i);

  return(0);
}

#ifdef NOTDEF
/*
 * Checks if URL_PATH is a wildcard url pattern, which currently means that
 * it ends with a '/' followed by a '*'.
 */
static int
is_wildcard_url_path(char *url_path)
{
  int n;
  Dsvec *dsv;

  if ((dsv = uri_path_parse(url_path)) == NULL)
	return(0);

  n = dsvec_len(dsv) - 2;

  if (n >= 0) {
	char *last_el;
	Ds *ds;

	ds = (Ds *) dsvec_ptr_index(dsv, n);
	last_el = ds_buf(ds);
	if (streq(last_el, "*"))
	  return(1);
  }

  return(0);
}
#endif

/*
 * Return 1 if the user identified in ENV appears in user list USERS, 0 if not,
 * and -1 if an error occurs.
 */
static int
acl_lists_user(User *users, Acs_environment *env, Credentials **cp)
{
  int st;
  char *errmsg;
  User *u;

  if (conf_val_eq(CONF_ACS_AUTHENTICATED_ONLY, "yes")) {
	if (env->credentials == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "No credentials provided"));
	  return(0);
	}
  }

  /*
   * Go through the rule's user list, checking for one that matches
   * the credentials of the user making the request.
   */
  for (u = users; u != NULL; u = u->next) {
	st = is_matching_user(u->name, env->credentials, DACS_NAME_CMP_CONFIG,
						  cp, &errmsg);
	if (st == -1) {
	  log_msg((LOG_ERROR_LEVEL, "acl_lists_user: %s: %s", errmsg, u->name));
	  return(-1);
	}
	if (st == 1)
	  return(1);
  }

  return(0);
}

static Acs_allow_result *
acl_allow(Rule *r, Acs_environment *env)
{
  int i;
  Allow *a;
  Acs_allow_result *res;
  Acs_expr_result rc;

  var_ns_delete_temporaries(&env->namespaces);
  if (r->allows == NULL) {
	log_msg((LOG_TRACE_LEVEL, "allow: FALSE"));
	return(NULL);
  }

  res = ALLOC(Acs_allow_result);
  res->allow = NULL;
  res->result = ALLOC(Expr_result);

  i = 1;
  for (a = r->allows; a != NULL; a = a->next) {
	log_msg((LOG_TRACE_LEVEL, "allow clause %d...", i));

	/* The default (no expression) is equivalent to ACS_EXPR_TRUE. */
	if (a->expr == NULL || ds_buf(a->expr) == NULL) {
	  res->allow = a;
	  res->result = NULL;
	  log_msg((LOG_TRACE_LEVEL, "allow: TRUE (no expression)"));
	  return(res);
	}

	if ((rc = acs_expr(ds_buf(a->expr), env, res->result)) == ACS_EXPR_TRUE) {
	  /* The redirect() function is invalid within an Allow element. */
	  if (env->redirect_action != NULL) {
		log_msg((LOG_TRACE_LEVEL, "acl_allow: redirect() has been called"));
		res->allow = NULL;
		log_msg((LOG_TRACE_LEVEL, "allow: FALSE"));
	  }
	  else if (env->notices != NULL) {
		log_msg((LOG_TRACE_LEVEL, "acl_allow: ack() has been called"));
		res->allow = NULL;
		log_msg((LOG_TRACE_LEVEL, "allow: FALSE"));
	  }
	  else {
		res->allow = a;
		log_msg((LOG_TRACE_LEVEL, "allow: TRUE"));
	  }
	  return(res);
	}
	else if (rc != ACS_EXPR_FALSE)
	  return(res);

	log_msg((LOG_TRACE_LEVEL, "allow clause %d: FALSE", i));
	i++;
  }

  return(NULL);
}

static Acs_deny_result *
acl_deny(Rule *r, Acs_environment *env)
{
  int i;
  Deny *d;
  Acs_deny_result *res;
  Acs_expr_result rc;

  var_ns_delete_temporaries(&env->namespaces);
  if (r->denies == NULL) {
	log_msg((LOG_TRACE_LEVEL, "deny: FALSE"));
	return(NULL);
  }

  res = ALLOC(Acs_deny_result);
  res->deny = NULL;
  res->result = ALLOC(Expr_result);

  i = 1;
  for (d = r->denies; d != NULL; d = d->next) {
	log_msg((LOG_TRACE_LEVEL, "deny clause %d...", i));

	/* The default (no expression) is equivalent to ACS_EXPR_TRUE. */
	if (d->expr == NULL) {
	  res->deny = d;
	  res->result = NULL;
	  log_msg((LOG_TRACE_LEVEL, "deny: TRUE"));
	  return(res);
	}

	if ((rc = acs_expr(ds_buf(d->expr), env, res->result)) == ACS_EXPR_TRUE) {
	  res->deny = d;
	  log_msg((LOG_TRACE_LEVEL, "deny: TRUE"));
	  return(res);
	}
	else if (rc != ACS_EXPR_FALSE)
	  return(res);

	log_msg((LOG_TRACE_LEVEL, "deny clause %d: FALSE", i));
	i++;
  }

  return(NULL);
}

static void
override_attrs(Grant_attrs *attrs, Grant_attrs *this_scope)
{

  if (this_scope->constraint != NULL)
	attrs->constraint = this_scope->constraint;
  if (this_scope->permit_chaining != NULL)
	attrs->permit_chaining = this_scope->permit_chaining;
  if (this_scope->pass_credentials != NULL)
	attrs->pass_credentials = this_scope->pass_credentials;
  if (this_scope->pass_http_cookie != NULL)
	attrs->pass_http_cookie = this_scope->pass_http_cookie;
  if (this_scope->permit_caching != NULL)
	attrs->permit_caching = this_scope->permit_caching;
}

/*
 * Locate the first rule clause having a predicate that evaluates to True.
 * Then process that rule clause and return its result.
 * Return 1 if access is granted, 0 if not normally, and -1 if not due to
 * error.
 */
int
acl_grants_user(Acl_rule *acl, Acs_environment *env, Credentials **cp,
				Acs_result *res)
{
  int i, rc;
  Acs_expr_result st;
  Acs_deny_result *d;
  Acs_allow_result *a;
  Expr_result result;
  Precondition *pre;
  Rule *r;
  User *u;

  if (acl->rules == NULL)
	return(-1);

  override_attrs(&res->attrs, &acl->default_attrs);

  log_msg((LOG_TRACE_LEVEL, "Looking for an enabled rule clause"));

  *cp = env->credentials;
  for (i = 0, r = acl->rules; r != NULL; i++, r = r->next) {
	log_msg((LOG_TRACE_LEVEL, "Examining rule clause %d", i));

	pre = r->precondition;
	if (pre == NULL) {
	  log_msg((LOG_TRACE_LEVEL, "No precondition, rule is enabled"));
	  break;
	}
	if (pre->user_list != NULL && (u = pre->user_list->user) != NULL) {
	  if ((rc = acl_lists_user(u, env, cp)) != 1) {
		if (rc == -1) {
		  log_msg((LOG_ERROR_LEVEL,
				   "Error processing precondition's user_list"));
		  return(0);
		}
		log_msg((LOG_TRACE_LEVEL,
				 "Precondition does not list user, rule disabled"));
		continue;
	  }
	  log_msg((LOG_TRACE_LEVEL, "Precondition's user_list is True"));
	}
	else {
	  log_msg((LOG_TRACE_LEVEL, "Precondition has empty user_list, ok"));
	}

	if (pre->predicate == NULL || pre->predicate->expr == NULL) {
	  log_msg((LOG_TRACE_LEVEL, "Empty predicate, rule is enabled"));
	  break;
	}

	log_msg((LOG_DEBUG_LEVEL, "Evaluating predicate..."));
	log_msg((LOG_TRACE_LEVEL, "Predicate:\n%s",
			 ds_buf(pre->predicate->expr)));
	var_ns_delete_temporaries(&env->namespaces);
	st = acs_expr(ds_buf(pre->predicate->expr), env, &result);
	if (st == ACS_EXPR_TRUE) {
	  if (env->redirect_action != NULL) {
		log_msg((LOG_ERROR_LEVEL,
				 "redirect() not allowed within Predicate element"));
		return(-1);
	  }
	  if (env->notices != NULL) {
		log_msg((LOG_ERROR_LEVEL,
				 "ack() not allowed within Predicate element"));
		return(-1);
	  }
	  log_msg((LOG_TRACE_LEVEL, "Predicate is True, precondition succeeds"));
	  break;
	}
	else if (st != ACS_EXPR_FALSE)
	  return(-1);

	log_msg((LOG_TRACE_LEVEL, "Predicate is False, precondition fails"));
  }

  if (r == NULL) {
	log_msg((LOG_TRACE_LEVEL, "No applicable rule clause"));
	return(0);
  }

  /* Found a rule clause to use. */
  log_msg((LOG_DEBUG_LEVEL, "Using rule clause %d", i));
  log_msg((LOG_TRACE_LEVEL, "%s", acl_xml_text(acl)));

  override_attrs(&res->attrs, &r->default_attrs);
  a = NULL;

  if (r->order == ACS_ORDER_ALLOW_THEN_DENY) {
	log_msg((LOG_INFO_LEVEL, "Order is allow,deny, default is to deny"));
	rc = 0;
	if ((a = acl_allow(r, env)) != NULL) {
	  if (a->result != NULL && a->result->err < 0) {
		log_msg((LOG_TRACE_LEVEL, "Allow clause failed due to error"));
		return(-1);
	  }
	  if (a->result != NULL) {
		if (env->redirect_action != NULL) {
		  log_msg((LOG_ERROR_LEVEL,
				   "redirect() not allowed within Allow element"));
		  return(-1);
		}
		if (env->notices != NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Allow clause denies access via ack()"));
		  res->notices = env->notices;		  
		  res->errmsg = "One or more acknowledgements are required";
		  return(0);
		}
	  }
	  log_msg((LOG_TRACE_LEVEL, "Allow clause grants access"));
	  rc = 1;
	}

	if ((d = acl_deny(r, env)) != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "Deny clause overrides any Allow clauses"));
	  if (d->result != NULL && d->result->err < 0) {
		log_msg((LOG_TRACE_LEVEL, "Deny clause failed due to error"));
		return(-1);
	  }
	  if (d->result != NULL) {
		if (env->redirect_action != NULL) {
		  res->redirect_action = env->redirect_action;
		  res->denial_reason = env->redirect_reason;
		  log_msg((LOG_TRACE_LEVEL,
				   "Deny clause redirects to %s", res->redirect_action));
		}
		if (env->notices != NULL) {
		  log_msg((LOG_ERROR_LEVEL, "ack() not allowed within Deny element"));
		  return(-1);
		}
	  }
	  rc = 0;
	}
  }
  else if (r->order == ACS_ORDER_DENY_THEN_ALLOW) {
	log_msg((LOG_INFO_LEVEL, "Order is deny,allow, default is to allow"));
	rc = 1;
	if ((d = acl_deny(r, env)) != NULL) {
	  if (d->result != NULL && d->result->err < 0) {
		log_msg((LOG_TRACE_LEVEL, "Deny clause failed due to error"));
		return(-1);
	  }

	  log_msg((LOG_TRACE_LEVEL, "Deny clause denies access"));
	  if (d->result != NULL) {
		if (env->redirect_action != NULL) {
		  res->redirect_action = env->redirect_action;
		  res->denial_reason = env->redirect_reason;
		  log_msg((LOG_TRACE_LEVEL,
				   "Deny clause redirects to %s", res->redirect_action));
		}
		if (env->notices != NULL) {
		  log_msg((LOG_ERROR_LEVEL, "ack() not allowed within Deny element"));
		  return(-1);
		}
	  }
	  rc = 0;
	}

	if ((a = acl_allow(r, env)) != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "Allow clause overrides any Deny clauses"));
	  if (a->result != NULL && a->result->err < 0) {
		log_msg((LOG_TRACE_LEVEL, "Allow clause failed due to error"));
		return(-1);
	  }
	  if (a->result != NULL) {
		if (env->redirect_action != NULL) {
		  log_msg((LOG_ERROR_LEVEL,
				   "redirect() not allowed within Allow element"));
		  return(-1);
		}
	  }
	  rc = 1;
	}
  }
  else {
	log_msg((LOG_ERROR_LEVEL,
			 "Internal error!  Unrecognized order attribute value"));
	rc = 0;
  }

  if (a != NULL)
	override_attrs(&res->attrs, &a->allow->attrs);

  log_msg((LOG_INFO_LEVEL,
		   "Rule evaluates to %s", rc == 1 ? "Allow" : "Deny"));

  return(rc);
}

/*
 * If the rule element's URL is absolute (scheme and host, with optional
 * or implicit port), then use the absolute request URL for matching,
 * ignoring any other URL components, such as a query.
 *
 * If the scheme/host/port match, then path matching is the same as for
 * relative URL path matching.
 */
static int
acs_match_abs_url(char *acl_url, Acs_environment *env, Uri_pattern **patp)
{
  int rc;
  Uri *request_uri;
  Uri_pattern *acl_pat;

  if (acl_url == NULL || env->uri == NULL)
	return(0);

  log_msg((LOG_TRACE_LEVEL, "Checking \"%s\" against pat \"%s\"",
		   env->uri->uri, acl_url));
  if ((acl_pat = uri_parse_pattern(acl_url)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "ACL URL parse failed: \"%s\"", acl_url));
	return(-1);
  }

  if ((rc = uri_pattern_match(env->uri, acl_pat)) == -1) {
	log_msg((LOG_ERROR_LEVEL, "ACL pattern match failed: \"%s\"", acl_url));
	return(-1);
  }
  if (rc == 0)
	log_msg((LOG_TRACE_LEVEL, "Pattern does not match"));
  else
	log_msg((LOG_TRACE_LEVEL, "Pattern matches"));

  if (patp != NULL)
	*patp = acl_pat;

  return(rc);
}

static int dacs_debug_sleep = 15;

/*
 * Examine the services for ACL to find the closest match to ENV->URL.
 * Any trailing '/' has been stripped off all URLs.
 * Returns 1 if something matches better than previously is found,
 * 0 if not, and -1 if an error occurs.
 */
static int
select_acl_url(Services *services, Acs_environment *env, Acl_url_match *m)
{
  int better, exact, n, rc;
  char *acl_url, *effective_url, *p, *url;
  Dsvec *dsv_req;
  Service *s;

  /*
   * ACLs are ordinarily shared by URI path differentiated jurisdictions,
   * unless disabled.  This feature allows a jurisdiction configured using
   * uri="dss.ca/dog" and another configured using uri="dss.ca/cat",
   * for example, to both use the ACL for "/cgi-bin/dacs/dacs_authenticate"
   * instead of having to have a url_pattern for
   * "/dog/cgi-bin/dacs/dacs_authenticate" *and* one for
   * "/cat/cgi-bin/dacs/dacs_authenticate".
   * That is, the "/dog" and "/cat" prefixes of the path are elided.
   * Note that when the feature is enabled, an ACL specifically for the
   * url_pattern "/dog/cgi-bin/dacs/dacs_authenticate" will not be matched
   * by "/dog/cgi-bin/dacs/dacs_authenticate"; the feature must be "off".
   */
  url = env->url;
  if ((services->shared == NULL || strcaseeq(services->shared, "yes"))
	  && dacs_conf->parsed_uri->path != NULL) {
	if (streq(dacs_conf->parsed_uri->path, url))
	  effective_url = "/";
	else {
	  p = strsuffix(dacs_conf->parsed_uri->path,
					strlen(dacs_conf->parsed_uri->path), "/");
	  if (p == NULL)
		effective_url = url + strlen(dacs_conf->parsed_uri->path) + 1;
	  else
		effective_url = url + (p - dacs_conf->parsed_uri->path);
	}
	log_msg((LOG_TRACE_LEVEL, "Using effective URI path: \"%s\"",
			 effective_url));
  }
  else
	effective_url = url;

  if ((dsv_req = uri_path_parse(effective_url)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Request URL decode failed: \"%s\"",
			 effective_url));
	return(-1);
  }

  better = 0;
  for (s = services->service; s != NULL; s = s->next) {
	if (s->url_expr != NULL) {
	  char *str;
	  Acs_expr_result st;
	  Expr_result result;

	  str = s->url_expr;
	  if ((st = acs_expr(str, env, &result)) == ACS_EXPR_TRUE
		  && (acl_url = acs_format_result(&result)) != NULL) {
		log_msg((LOG_TRACE_LEVEL, "ACL URL expression: \"%s\"", s->url_expr));
		log_msg((LOG_TRACE_LEVEL, "ACL URL value: \"%s\"", acl_url));
	  }
	  else {
		log_msg((LOG_ERROR_LEVEL, "URL expression evaluation error: \"%s\"",
				 s->url_expr));
		return(-1);
	  }
	}
	else
	  acl_url = s->url_pattern;

	if (dacs_enable_rule_patterns && acl_url[0] != '/') {
	  Uri_pattern *acl_pat;

	  /*
	   * If the rule element's URL is absolute (scheme and host, with optional
	   * or implicit port), then use the absolute request URL for matching,
	   * ignoring any other URL components, such as a query.
	   *
	   * If the scheme/host/port match, then fall through to do path matching
	   * in the same way as for relative URL path matching.
	   */

	  if ((rc = acs_match_abs_url(acl_url, env, &acl_pat)) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Absolute URL match failed: %s", acl_url));
		return(-1);
	  }
	  if (rc == 0)
		continue;
	  acl_url = acl_pat->path;
	}

	s->url_path = acl_url;

	/*
	 * As a special case, the effective URL pattern "*" matches anything
	 * exactly.
	 */
	if (streq(acl_url, "*")) {
	  log_msg((LOG_TRACE_LEVEL, "match (forced)"));
	  n = dsvec_len(dsv_req);
	  exact = 1;
	}
	else
	  n = acs_match_url_segs(acl_url, dsv_req, &exact);

	if (exact || (n > 0 && n > m->nsegs_matched)) {
	  m->nsegs_matched = n;
	  /* XXX ??? */
	  m->acl = NULL;
	  m->s = s;
	  m->exact = exact;
	  if (m->exact)
		return(1);
	  better = 1;
	}
  }

  if (m->subordinate == NULL && m->s != NULL && m->s->url_path != NULL) {
	for (s = services->service; s != NULL; s = s->next) {
	  log_msg((LOG_TRACE_LEVEL, "SUB %s %s", m->s->url_path, s->url_path));
	  if ((dsv_req = uri_path_parse(s->url_path)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "URL decode failed: %s", s->url_path));
		return(-1);
	  }

	  n = acs_match_url_segs(m->s->url_path, dsv_req, &exact);

	  if (n > 0 && m->nsegs < strchrcount(s->url_path, '/')) {
		m->subordinate = strdup(s->url_path);
		log_msg((LOG_TRACE_LEVEL, "Subordinate: %s", m->subordinate));
		break;
	  }
	}
  }

  return(better);
}

/*
 * This table determines which variables passed to ACS by mod_auth_dacs
 * (dacs_name) are made available to ACS expressions and functions and how
 * those variables will be renamed (var_name).
 * They are put in the "DACS" variable namespace.
 * For example, the variable passed in by mod_auth_dacs as "SERVICE_URI"
 * will be available to access control rules as ${DACS::URI}.
 */
static struct {
  char *dacs_name;
  char *var_name;
} env_tab[] = {
  { "SERVICE_URI",                 "URI" },
  { "SERVICE_URI",                 "URL" },
  { "SERVICE_QUERY",               "QUERY" },
  { "SERVICE_METHOD",              "METHOD" },
  { "SERVICE_FILENAME",            "FILENAME" },
  { "SERVICE_PATH_INFO",           "PATH_INFO" },
  { "SERVICE_REMOTE_ADDR",         "REMOTE_ADDR" },
  { "SERVICE_REMOTE_HOST",         "REMOTE_HOST" },
  { "SERVICE_ARGS_TRUNCATED",      "ARGS_TRUNCATED" },
  { "SERVICE_ARGS",                "ARGS" },
  { "SERVICE_CONTENT_TYPE",        "CONTENT_TYPE" },
  { "SERVICE_CONTENT_LENGTH",      "CONTENT_LENGTH" },
  { "SERVICE_CONTENT_ENCODING",    "CONTENT_ENCODING" },
  { "SERVICE_POSTDATA",            "POSTDATA"},
  { "SERVICE_AUTHORIZATION",       "AUTHORIZATION"},
  { "SERVICE_PROXY_AUTHORIZATION", "PROXY_AUTHORIZATION"},
  { "SERVICE_USER_AGENT",          "USER_AGENT"},
  { "SERVICE_PROXYREQ",            "PROXYREQ"},
  { "DACS_ACS",                    "ACS"},
  { "RLINK",                       "RLINK"},
  { NULL,                          NULL }
};

Acs_environment *
acs_new_env(Acs_environment *env)
{
  Acs_environment *new_env;

  if (env == NULL)
	new_env = ALLOC(Acs_environment);
  else
	new_env = env;

  new_env->uri = NULL;
  new_env->url = NULL;
  new_env->do_eval = 1;
  new_env->acl_rule = NULL;
  new_env->notices = NULL;
  new_env->redirect_action = NULL;
  new_env->redirect_reason = ACS_DENIAL_REASON_UNKNOWN;
  new_env->namespaces = NULL;
  new_env->trace_level = trace_level;
  new_env->credentials = NULL;
  new_env->got_jmp_env = 0;

  new_env->is_dacsexpr = (dacs_app_type == DACS_STANDALONE);

  return(new_env);
}

int
acs_init_env(Kwv *kwv, Kwv *args, char *url, Credentials *cr,
			 Acs_environment *env)
{
  int i;
  char *p;
  Kwv *dacs;
  Kwv_pair *v;
  Var_ns *args_ns, *dacs_ns, *env_ns;

  if (url != NULL) {
	env->url = strdup(url);
	/* Delete trailing '/' characters, but allow "/". */
	p = env->url + strlen(env->url) - 1;
	while (p > env->url) {
	  if (*p != '/')
		break;
	  *p-- = '\0';
	}
  }
  else
	env->url = NULL;
  env->credentials = cr;

  if ((dacs_ns = var_ns_new(&env->namespaces, "DACS", NULL)) == NULL) {
	if ((dacs_ns = var_ns_lookup(env->namespaces, "DACS")) == NULL)
	  return(-1);
  }
  dacs = dacs_ns->kwv;

  if ((args_ns = var_ns_new(&env->namespaces, "Args", args)) == NULL) {
	if ((args_ns = var_ns_lookup(env->namespaces, "Args")) == NULL)
	  return(-1);
  }
  if (args_ns != NULL && kwv_count(args_ns->kwv, NULL) != 0) {
	log_msg(((Log_level) (LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG),
			 "Contents of Args namespace:\n%s",
			 kwv_buf(args_ns->kwv, '=', '"')));
  }

  kwv_replace(dacs, "ARG_COUNT", ds_xprintf("%d", kwv_count(args, NULL)));

  if (dacs_conf->conf_var_ns != NULL)
	var_ns_replace(&env->namespaces, "Conf", dacs_conf->conf_var_ns->kwv);

  if ((env_ns = var_ns_from_env("Env")) != NULL) {
	var_ns_replace(&env->namespaces, "Env", env_ns->kwv);
	log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
			 "Contents of Env namespace:\n%s",
			 kwv_buf(env_ns->kwv, '=', '"')));
  }

  for (i = 0; env_tab[i].dacs_name != NULL; i++) {
	if ((p = getenv(env_tab[i].dacs_name)) != NULL) {
	  if (kwv_replace(dacs, env_tab[i].var_name, p) == NULL)
		return(-1);
	}
	else if ((v = kwv_lookup(kwv, env_tab[i].dacs_name)) != NULL) {
	  if (kwv_replace(dacs, env_tab[i].var_name, v->val) == NULL)
		return(-1);
	}
  }

  if (non_auth_cookies != NULL) {
	Cookie *c;
	Kwv *cookie_kwv;

	log_msg((LOG_TRACE_LEVEL, "Initializing Cookies namespace..."));
	cookie_kwv = kwv_init(4);
	for (c = non_auth_cookies; c != NULL; c = c->next) {
	  log_msg((LOG_TRACE_LEVEL, "  name=\"%s\"", c->name));
	  log_msg((LOG_TRACE_LEVEL, "  value=\"%s\"", c->value));
	  kwv_replace(cookie_kwv, c->name, c->value);
	}
	var_ns_replace(&env->namespaces, "Cookies", cookie_kwv);
	var_ns_set_flags(env->namespaces, "Cookies", VAR_NS_READONLY);
	log_msg((LOG_TRACE_LEVEL, "Added Cookies namespace: \"%s\"",
			 non_auth_cookie_header));
  }

  if (kwv_lookup_value(dacs, "REMOTE_ADDR") == NULL
	  && (p = getenv("REMOTE_ADDR")) != NULL)
	kwv_replace(dacs, "REMOTE_ADDR", p);
  if (kwv_lookup_value(dacs, "REMOTE_HOST") == NULL
	  && (p = getenv("REMOTE_HOST")) != NULL)
	kwv_replace(dacs, "REMOTE_HOST", p);

  if ((p = current_uri(NULL)) != NULL)
	kwv_replace(dacs, "CURRENT_URI", p);
  if ((p = current_uri_no_query(NULL)) != NULL)
	kwv_replace(dacs, "CURRENT_URI_NO_QUERY", p);

  if (cr != NULL) {
	if (kwv_replace(dacs, "FEDERATION", cr->federation) == NULL)
	  return(-1);
	if (kwv_replace(dacs, "USERNAME", cr->username) == NULL)
	  return(-1);
	if (kwv_replace(dacs, "IDENTITY", auth_identity_from_credentials(cr))
		== NULL)
	  return(-1);
	if (kwv_replace(dacs, "JURISDICTION", cr->home_jurisdiction) == NULL)
	  return(-1);
	if (kwv_replace(dacs, "IP", cr->ip_address) == NULL)
	  return(-1);
	if (kwv_replace(dacs, "ROLES", cr->role_str) == NULL)
	  return(-1);
  }

  log_msg((LOG_TRACE_LEVEL, "Environment initialized"));

  return(0);
}

static int
check_acl_expr(char *expr, Parse_xml_error *err)
{
  Acs_environment env;
  Acs_expr_result st;
  Expr_result result;

  acs_new_env(&env);
  acs_init_env(NULL, NULL, "", NULL, &env);
  env.do_eval = 0;

  st = acs_expr(expr, &env, &result);
  if (st == ACS_EXPR_SYNTAX_ERROR) {
	err->mesg = "Syntax error in predicate";
	err->line = -1;
	err->pos = -1;
	return(-1);
  }

  if (st == ACS_EXPR_EVAL_ERROR) {
	err->mesg = "Evaluation error in predicate";
	err->line = -1;
	err->pos = -1;
	return(-1);
  }

  if (st == ACS_EXPR_LEXICAL_ERROR) {
	err->mesg = "Lexical error in predicate";
	err->line = -1;
	err->pos = -1;
	return(-1);
  }

  return(0);
}

/*
 * Return 1 if ACL has expired, based on its expires_expr attribute evaluating
 * to True, otherwise return 0 it has not expired or -1 if an error occurs.
 */
int
check_acl_expired(char *expires_expr, Acs_environment *env)
{
  Acs_expr_result st;
  Expr_result result;

  if (expires_expr == NULL)
	return(0);

  log_msg((LOG_TRACE_LEVEL, "Checking if rule has expired"));

  st = acs_expr(expires_expr, env, &result);
  if (st == ACS_EXPR_TRUE) {
	log_msg((LOG_DEBUG_LEVEL, "ACL has expired"));
	return(1);
  }
  else if (st == ACS_EXPR_FALSE) {
	log_msg((LOG_TRACE_LEVEL, "ACL has not expired"));
	return(0);
  }

  log_msg((LOG_ERROR_LEVEL, "Error evaluating expires_expr"));
  return(-1);
}

int
is_valid_id_attr(Dsvec *dsv, char *id)
{
  int i;
  char *p, *s;

  if (id == NULL || *id == '\0')
	return(0);

  for (p = id; *p != '\0'; p++) {
	if (!isalnum((int) *p) && *p != '_')
	  return(0);
  }

  for (i = 0; i < dsvec_len(dsv); i++) {
	s = (char *) dsvec_ptr_index(dsv, i);
	if (streq(s, id))
	  return(0);
  }

  dsvec_add_ptr(dsv, id);

  return(1);
}

/*
 * Parse the given ACL rule (BUF) to check for a syntax error, including
 * many syntax errors in expressions.  This won't detect run-time errors,
 * but ought to help weed out many dumb mistakes.
 * Return 0 if ok, -1 otherwise.
 */
int
check_acl(char *buf, Parse_xml_error *err)
{
  int i;
  char *mesg;
  Acl_rule *acl;
  Acs_environment env;
  Acs_allow_result *a;
  Acs_deny_result *d;
  Dsvec *dsv;
  Identity *ident;
  Precondition *pre;
  Rule *r;
  Service *s;

  mesg = NULL;

  if (parse_xml_acl(buf, &acl) == -1) {
    if (err != NULL) {
      if (parse_xml_is_error(err)) {
        if (err->mesg == NULL)
          err->mesg = "Parse failed";
      }
      else {
        err->mesg = "Parse failed";
        err->line = -1;
        err->pos = -1;
      }
    }
    return(-1);
  }

  if (acl->expires_expr != NULL) {
	if (trace_level > 1)
	  fprintf(stderr, "Checking expires_expr:\n%s\n", acl->expires_expr);
	else if (trace_level)
	  fprintf(stderr, "Checking expires_expr...\n");

	if (check_acl_expr(acl->expires_expr, err) == -1)
	  return(-1);
  }

  dsv = dsvec_init(NULL, sizeof(char *));
  for (s = acl->services->service; s != NULL; s = s->next) {
	if (s->id != NULL && !is_valid_id_attr(dsv, s->id)) {
	  mesg = "Invalid id attribute in service element";
	  goto fail;
	}

	if (s->url_expr != NULL) {
	  if (trace_level > 1)
		fprintf(stderr, "Checking url_expr:\n%s\n", s->url_expr);
	  else if (trace_level)
		fprintf(stderr, "Checking url_expr...\n");

	  if (check_acl_expr(s->url_expr, err) == -1)
		return(-1);
	}
  }

  for (ident = acl->identities; ident != NULL; ident = ident->next) {
	if (ident->id != NULL && !is_valid_id_attr(dsv, ident->id)) {
	  mesg = "Invalid id attribute in identity element";
	  goto fail;
	}
  }

  for (i = 0, r = acl->rules; r != NULL; i++, r = r->next) {
	Allow *allow;
	Deny *deny;

	if (trace_level)
	  fprintf(stderr, "Examining rule clause %d\n", i);

	if (r->id != NULL && !is_valid_id_attr(dsv, r->id)) {
	  mesg = "Invalid id attribute in rule element";
	  goto fail;
	}

	pre = r->precondition;
	if (pre != NULL && pre->predicate != NULL && pre->predicate->expr != NULL) {
	  if (trace_level > 1)
		fprintf(stderr, "Checking predicate:\n%s\n",
				ds_buf(pre->predicate->expr));
	  else if (trace_level)
		fprintf(stderr, "Checking predicate...\n");

	  if (check_acl_expr(ds_buf(pre->predicate->expr), err) == -1)
		return(-1);

	  if (pre->user_list != NULL) {
		User *u;

		for (u = pre->user_list->user; u != NULL; u = u->next) {
		  if (u->id != NULL && !is_valid_id_attr(dsv, u->id)) {
			mesg = "Invalid id attribute in user element";
			goto fail;
		  }
		}
	  }
	}

	for (allow = r->allows; allow != NULL; allow = allow->next) {
	  if (allow->id != NULL && !is_valid_id_attr(dsv, allow->id)) {
		mesg = "Invalid id attribute in allow element";
		goto fail;
	  }
	}

	for (deny = r->denies; deny != NULL; deny = deny->next) {
	  if (deny->id != NULL && !is_valid_id_attr(dsv, deny->id)) {
		mesg = "Invalid id attribute in deny element";
		goto fail;
	  }
	}

	acs_new_env(&env);
	acs_init_env(NULL, NULL, "", NULL, &env);
	env.do_eval = 0;
	if (r->order == ACS_ORDER_ALLOW_THEN_DENY) {
	  if ((a = acl_allow(r, &env)) != NULL) {
		if (a->result != NULL && a->result->err < 0) {
		  mesg = a->result->errmsg;
		  goto fail;
		}
	  }
	  if ((d = acl_deny(r, &env)) != NULL) {
		if (d->result != NULL && d->result->err < 0) {
		  mesg = d->result->errmsg;
		  goto fail;
		}
	  }
	}
	else if (r->order == ACS_ORDER_DENY_THEN_ALLOW) {
	  if ((d = acl_deny(r, &env)) != NULL) {
		if (d->result != NULL && d->result->err < 0) {
		  mesg = d->result->errmsg;
		  goto fail;
		}
	  }
	  if ((a = acl_allow(r, &env)) != NULL) {
		if (a->result != NULL && a->result->err < 0) {
		  mesg = a->result->errmsg;
		  goto fail;
		}
	  }
	}
	else {
	  mesg = "Internal error!  Unrecognized order attribute value";
	  goto fail;
	}
  }

  return(0);

 fail:
  if (err != NULL) {
	err->mesg = mesg;
	err->line = -1;
	err->pos = -1;
  }

  return(-1);
}

/*
 * Get an ordered list of ACLs from the given source (H) and retrieve,
 * parse, and examine them until a match is found or the entire list has
 * been scanned.
 * Return 0 if a match is found and identify the matching rule, otherwise
 * return -1.
 */
static int
scan_rules(Vfs_handle *h, Acs_environment *env,
		   Acs_result *result, Acl_file **best)
{
  int acl_count, best_match, i, st;
  char *acl_file, *buf;
  Acl_file **acls;
  Acl_rule *acl;

  log_msg((LOG_TRACE_LEVEL, "Scanning rules to match request for \"%s\"",
		   env->url));

  /*
   * Since each rule is stored in a separate item, prepare to iterate
   * through all ACL items to find the one that applies, if any.
   */
  if ((acl_count = get_acl_file_list(h, &acls)) == -1) {
	result->errmsg = "Can't obtain list of ACLs - check configuration file";
	return(-1);
  }

  log_msg((LOG_TRACE_LEVEL, "ACL items found: %d", acl_count));
  if (acl_count == 0)
	log_msg((LOG_WARN_LEVEL, "No ACLs of this type - check configuration"));

  best_match = -1;
  for (i = 0; i < acl_count; i++) {
	acl_file = acls[i]->path;
	if (acls[i]->status != ACL_STATUS_ENABLED) {
	  log_msg((LOG_TRACE_LEVEL, "Not examining ACL: \"%s/%s\"",
			   acls[i]->naming_context, acl_file));
	  continue;
	}

	if (vfs_get(h, acl_file, (void **) &buf, NULL) == -1) {
	  result->errmsg = ds_xprintf("Can't load ACL: '%s'", acl_file);
	  return(-1);
	}

	log_msg((LOG_TRACE_LEVEL, "Examining ACL: \"%s/%s\"",
			 acls[i]->naming_context, acl_file));

	if (parse_xml_acl(buf, &acl) == -1) {
	  result->errmsg = ds_xprintf("parse_xml_acl failed for \"%s\"", acl_file);
	  return(-1);
	}

	free(buf);

	if ((st = check_acl_expired(acl->expires_expr, env)) == 1) {
	  /* If this rule has expired, do not use it. */
	  log_msg((LOG_TRACE_LEVEL, "Ignoring expired rule"));
	  return(0);
	}
	else if (st == -1) {
	  log_msg((LOG_TRACE_LEVEL, "Rule has invalid expires_expr attribute"));
	  return(-1);
	}

	if ((st = select_acl_url(acl->services, env, &result->m)) == 1) {
	  log_msg((LOG_DEBUG_LEVEL,
			   "Matched: %s in '%s/%s'",
			   result->m.s->url_path, acls[i]->naming_context, acl_file));
	  best_match = i;
	  if (result->m.exact) {
		log_msg((LOG_DEBUG_LEVEL, "Exact URL match... ACL search terminated"));
		break;
	  }
	}
	else if (st == -1)
	  return(-1);

	log_msg((LOG_DEBUG_LEVEL, "Finished with ACL: '%s/%s'",
			 acls[i]->naming_context, acl_file));
  }

  if (best_match != -1)
	*best = acls[best_match];

  return(0);
}

static int
scan_indexed_rules(Vfs_handle *h, Acs_environment *env,
				   Acs_result *result, Acl_file **best)
{
  int acl_count, best_match, i, st;
  Acl_index *ad;
  Acl_map *am;

  log_msg((LOG_TRACE_LEVEL, "Scanning indexed rules to match \"%s\"",
		   env->url));

  if ((ad = read_acl_index(h)) == NULL) {
	result->errmsg = "Can't obtain list of ACLs - check configuration file";
	return(-1);
  }
  acl_count = dsvec_len(ad->acl_map);

  log_msg((LOG_TRACE_LEVEL, "ACL items found: %d", acl_count));
  if (acl_count == 0)
	log_msg((LOG_WARN_LEVEL, "No ACLs of this type - check configuration"));

  best_match = -1;
  for (i = 0; i < acl_count; i++) {
	am = (Acl_map *) dsvec_ptr_index(ad->acl_map, i);
	if (am->status != ACL_STATUS_ENABLED) {
	  log_msg((LOG_TRACE_LEVEL, "Ignoring rule - disabled: \"%s\"", am->path));
	  continue;
	}

	log_msg((LOG_TRACE_LEVEL, "Considering ACL: \"%s\"", am->path));

	if ((st = check_acl_expired(am->expires_expr, env)) == 1) {
	  /* If this rule has expired, do not use it. */
	  log_msg((LOG_TRACE_LEVEL, "Ignoring rule - expired: \"%s\"", am->path));
	  continue;
	}
	else if (st == -1) {
	  log_msg((LOG_TRACE_LEVEL,
			   "Ignoring rule - invalid expires_expr attribute: \"%s\"",
			   am->path));
	  continue;
	}

	if ((st = select_acl_url(am->services, env, &result->m)) == 1) {
	  log_msg((LOG_DEBUG_LEVEL,
			   "Matched: %s in \"%s\"", result->m.s->url_path, am->path));
	  best_match = i;
	  if (result->m.exact) {
		log_msg((LOG_DEBUG_LEVEL, "Exact URL match... ACL search terminated"));
		break;
	  }
	}
	else if (st == -1)
	  return(-1);

	log_msg((LOG_DEBUG_LEVEL, "Finished with ACL: \"%s\"", am->path));
  }

  if (best_match != -1) {
	char *buf;

	am = (Acl_map *) dsvec_ptr_index(ad->acl_map, best_match);
	*best = ALLOC(Acl_file);
	(*best)->status = ACL_STATUS_ENABLED;
	(*best)->aclname = "";
	(*best)->naming_context = "";
	(*best)->path = am->path;
	(*best)->sortkey = am->priority;

	if (vfs_get(h, am->path, (void **) &buf, NULL) == -1) {
	  result->errmsg = ds_xprintf("Cannot load rule: '%s'", am->path);
	  return(-1);
	}

	if (parse_xml_acl(buf, &result->m.acl) == -1) {
	  result->errmsg = ds_xprintf("parse_xml_acl failed for \"%s\"", am->path);
	  return(-1);
	}

	free(buf);
  }

  return(0);
}

Acs_result *
acs_init_result(Acs_result *result)
{

  result->denial_reason = ACS_DENIAL_REASON_UNKNOWN;
  result->chaining = NULL;
  result->pass = NULL;
  result->pass_http_cookie = NULL;
  result->m.nsegs = result->m.nsegs_matched = result->m.exact = 0;
  result->m.delegated = 0;
  result->m.subordinate = NULL;
  result->m.acl = NULL;
  result->m.s = NULL;
  result->caching = NULL;
  result->cr = NULL;
  result->redirect_action = NULL;
  result->notices = NULL;
  result->errmsg = NULL;
  init_grant_attrs(&result->attrs);
  result->auth_kwv = NULL;

  return(result);
}

static void
reinit_result(Acs_result *result)
{

  result->m.nsegs = result->m.nsegs_matched = result->m.exact = 0;
  result->m.subordinate = NULL;
  result->m.acl = NULL;
  result->m.s = NULL;
  result->caching = NULL;
  init_grant_attrs(&result->attrs);
}

/*
 * Scan the rules found in ITEM_TYPE, in order, until either an exact
 * match is found or all rules have been examined.  The matching algorithm
 * is parameterized by ENV->URL and a rule's "service" elements, which are
 * examined in the order in which they appear.
 * ENV provides context in case any service element needs to be evaluated.
 * Return -1 on error, 0 otherwise with RESULT and BEST telling the caller
 * about any exact or less-specific match.
 */
int
acs_find_applicable_acl(char *item_type, Acs_environment *env,
						Acl_file **best, int go, Acs_result *result)
{
  Vfs_directive *vd;
  Vfs_handle *h;

  if (go <= 0) {
	log_msg((LOG_ERROR_LEVEL,
			 "Maximum delegation level of %d has been reached",
			 ACL_DELEGATE_MAX));
	return(-1);
  }

  if (go != ACL_DELEGATE_MAX) {
	/* We're following an instruction to delegate, so forget what we know. */
	reinit_result(result);
  }

  result->m.nsegs = strchrcount(env->url, (int) '/');

  if (result->m.delegated)
	log_msg((LOG_TRACE_LEVEL, "For delegation level %d, rule source is \"%s\"",
			 ACL_DELEGATE_MAX - go, item_type));

  if ((vd = vfs_uri_parse(item_type, NULL)) != NULL) {
	if (vd->item_type == NULL)
	  vd->item_type = "uri";
	if (streq(vd->store_name, "vfs") || streq(vd->store_name, "kwv-vfs")) {
	  log_msg((LOG_ERROR_LEVEL, "VFS \"%s\" is not supported in this mode",
			   vd->store_name));
	  return(-1);
	}
  }
  else if ((vd = vfs_lookup_item_type(item_type)) == NULL)
	return(-1);

  if (vfs_open(vd, &h) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Can't open item_type \"%s\"", item_type));
	if (h->error_msg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "Reason: %s", h->error_msg));
	return(-1);
  }

  log_msg((LOG_DEBUG_LEVEL, "Scanning \"%s\" for a match", item_type));
#ifndef NOTDEF
  if (scan_indexed_rules(h, env, result, best) == -1) {
#else
  if (scan_rules(h, env, result, best) == -1) {
#endif
	if (result->errmsg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "%s", result->errmsg));
	vfs_close(h);
	return(-1);
  }

  vfs_close(h);

  if (result->m.s != NULL && result->m.s->rule_uri != NULL) {
	Grant_attrs *g;

	/*
	 * This service request is being delegated to ACLs in a different
	 * virtual filestore.
	 * We need to obtain ACLs from that filestore, parsing and evaluating them
	 * as usual.
	 * Detect and/or limit recursion.
	 * When a delegated ACL is matched, ACS will never
	 * resume searching for another ACL; that is, delegation is merely a
	 * "goto" operation and turns off ACL searching at the top level.
	 */
	log_msg((LOG_INFO_LEVEL, "This service is delegated to: \"%s\"",
			 result->m.s->rule_uri));
	result->m.delegated++;
	g = &result->m.acl->default_attrs;
	/* XXX */
	if (g->constraint != NULL || g->permit_chaining != NULL
		|| g->pass_credentials != NULL || g->pass_http_cookie != NULL
		|| g->permit_caching != NULL)
	  log_msg((LOG_INFO_LEVEL, "Default attributes will be ignored"));
	return(acs_find_applicable_acl(result->m.s->rule_uri, env, best,
								   go - 1, result));
  }

  return(0);
}

/*
 * The logic is as follows:
 * 1) Locate the most specific ACL file by comparing URI against
 *    the service elements in one or more files.  The ACL file may
 *    optionally establish default attributes if access is granted.
 *    If default rules have been configured, try those files if no exact match
 *    has been found; the first, closest match identifies the applicable file.
 * 2) Examine the file's rules, from top to bottom, stopping when a rule
 *    has no precondition or a precondition that evaluates to True.
 *    If no such rule is found, access is denied.  If one is found, it may
 *    optionally establish default attributes if access is granted, overriding
 *    the top-level defaults.
 * 3a)If the evaluation order specified is "allow,deny", then evaluate
 *    the expression in each allow clause within the rule, from top to bottom,
 *    stopping if one is True.  If no expression appears within a clause, it
 *    is treated as True.  Then, the expression in each deny clause is
 *    evaluated, from top to bottom, stopping if one is True.  If no
 *    expression appears within a clause it is treated as True.  Access is
 *    granted iff an allow clause is True but no deny clause is true.
 * 3b)If the evaluation order is "deny,allow", then evaluate the expression
 *    in each deny clause within the rule, from top to bottom, stopping if
 *    one is True.  If no expression appears within a clause, it is treated
 *    as True.  Then, the expression in each allow clause is evaluated,
 *    from top to bottom, stopping if one is True.  If no expression appears
 *    within a clause it is treated as True.  Access is granted iff no
 *    deny clause is True or an allow clause is true.
 * 3c)In either ordering, if an allow clause evaluated to True and it had
 *    attributes associated with it, those attributed override any in
 *    an outer scope.
 */
int
dacs_acs(char *jurisdiction, char *uri, Kwv *kwv, Kwv *args,
		 Credentials *credentials, Credentials *selected,
		 Scredentials *scredentials, Cookie *cookies,
		 Kwv *proxy_kwv, Rlink *rlink, Acs_result *result)
{
  int by_token, i, ncred, pass_env_flag, st;
  int proxy_exec_flag, proxy_static_flag, proxy_pass_authorization_flag;
  char *chaining, *cookie_str, *ident, *method, *pass, *proxied_uri;
  char *remote, *remote_addr, *remote_host;
  Acl_file *best;
  Acl_url_match m;
  Acs_environment env;
  Cookie *c;
  Credentials *cr;
  Kwv *args_kwv, *auth_kwv, *dacs_kwv;
  Kwv_pair *v;
  Var_ns *dacs_ns;
  Vfs_directive *vd;

  by_token = 0;
  acs_init_result(result);

  if (credentials == NULL && (v = conf_var(CONF_ACS_PRE_AUTH)) != NULL) {
	char *expr, *username;
	Acs_expr_result st;
	Kwv *kwv_auth;

	while (v != NULL) {
	  if ((expr = v->val) == NULL || *expr == '\0') {
		v = v->next;
		continue;
	  }

	  log_msg((LOG_TRACE_LEVEL, "Evaluating ACS_PRE_AUTH: \"%s\"", expr));
	  kwv_auth = kwv_init(4);

	  st = auth_eval(expr, args, kwv_auth, NULL, &username);

	  if (st == ACS_EXPR_TRUE && is_valid_username(username)) {
		char *remote_addr, *roles_str, *ua_str;
		Auth_style auth_style;

		/* Create temporary credentials */
		remote_addr = kwv_lookup_value(kwv, "SERVICE_REMOTE_ADDR");
		ua_str = kwv_lookup_value(kwv, "SERVICE_USER_AGENT");
		auth_style = AUTH_STYLE_EXPR | AUTH_STYLE_ACS;
		if ((roles_str = kwv_lookup_value(kwv_auth, "ROLES")) == NULL
			|| !is_valid_role_str(roles_str))
		  roles_str = "";
		credentials = make_credentials(conf_val(CONF_FEDERATION_NAME),
									   conf_val(CONF_JURISDICTION_NAME),
									   username, remote_addr,
									   roles_str, NULL, auth_style,
									   AUTH_VALID_FOR_ACS, NULL, ua_str);
		selected = credentials;
		log_msg((LOG_TRACE_LEVEL,
				 "ACS_PRE_AUTH succeeded, creating credentials for \"%s\"",
				 username));
		break;
	  }
	  else if (st != ACS_EXPR_FALSE)
		log_msg((LOG_ERROR_LEVEL,
				 "Ignoring invalid ACS_PRE_AUTH directive: \"%s\"", expr));

	  v = v->next;
	}
  }

  if (rlink == NULL && credentials == NULL) {
	int rc, send401;
	char *authorization, *errmsg, *service_authorization, *service_proxy_authorization;
	char *p, *uri_path;
	Auth_module *am;
	Dsvec *args;
	Http_auth *auth;
	Http_auth_authorization *aa;
	Kwv *akwv;
	Roles_module *rm;

	/*
	 * If no credentials are available, authentication can be performed
	 * at this point, if this feature is enabled.
	 *
	 * Credentials are *not* returned to the user (unless an invoked
	 * program is passed credentials and does something with them) and no
	 * client redirection occurs (as by AUTH_SUCCESS_HANDLER or
	 * AUTH_FAILURE_HANDLER).  Because the client does not receive DACS
	 * credentials, this will tend to break DACS's idea of a stateful session
	 * (achieved using HTTP cookies).  This mechanism can be useful for clients
	 * that cannot or will not handle cookies and/or redirection, making the
	 * usual mechanism that involves dacs_authenticate unsuitable.
	 * Users authenticated in this way never see a DACS identity - one is
	 * assigned and credentials temporarily generated for a request and then
	 * destroyed - so some DACS web services may not work as they would when
	 * "real" (persistent) credentials are used.
	 *
	 * The client could potentially provide authentication material in any
	 * number of ways: using an HTTP request header (such as Authorization or a
	 * custom header), using an argument (query or in the body), or encoded
	 * by the URI.
	 *
	 * The canonical application of this particular method is by a web client
	 * that obtains a username/password itself (i.e., not via a browser popup
	 * initiated by Basic Auth), perhaps non-interactively from a user's
	 * configuration file, and simply sends the Authorization header with each
	 * request rather than waiting for the server to ask for it to be sent
	 * via the WWW-Authenticate header.  RFC 2617 permits this behaviour.
	 *
	 * The implementation of this mechanism requires a build-time option
	 * to enable the feature, and at least one new directive that allows
	 * the authentication method and role collecting method(s) to be
	 * configured.  This might involve adding an API for authenticating
	 * and role collecting functions, or perhaps by naming the Auth clause
	 * and Roles clauses to use.  Note that the fine-control provided by
	 * dacs_authenticate will not be available.
	 * Notes:
	 * o Revocation processing should be performed.
	 * o The new credentials should have a special style.
	 * o There should be a way to either semi-customize the temporary
	 *   credentials or obtain the credentials from a given location
	 *   (allowing a session identifier to be mapped to previously produced
	 *   and stored credentials).
	 *
	 * HTTP_AUTH "basic \"Foo Realm\" /foo.php -m unix suff"
	 *
	 * XXX This is a prototype/kludge to experiment with using RFC 2617 auth
	 * to be imported as a DACS identity by ACS without involving
	 * dacs_authenticate.
	 * All that is required is to validate the Authorization string according
	 * to the configured authentication method.
	 */
	akwv = var_ns_lookup_kwv(dacs_conf->conf_var_ns, "Conf");
	current_uri_pqf(&uri_path, NULL, NULL);

	if (forward_proxy_jurisdiction) {
	  service_proxy_authorization
		= kwv_lookup_value(kwv, "SERVICE_PROXY_AUTHORIZATION");
	  authorization = service_proxy_authorization;
	}
	else {
	  service_authorization = kwv_lookup_value(kwv, "SERVICE_AUTHORIZATION");
	  authorization = service_authorization;
	}
	send401 = ((p = kwv_lookup_value(akwv, "http_auth_401")) != NULL
			   && strcaseeq(p, "yes"));

	if ((conf_val_eq(CONF_HTTP_AUTH_ENABLE, "pre_acs_only")
		 || conf_val_eq(CONF_HTTP_AUTH_ENABLE, "yes")
		 || conf_val_eq(CONF_HTTP_AUTH_ENABLE, "both"))
		&& (auth = http_auth_match_directive(uri_path)) != NULL
		&& auth->pre_flag
		&& (auth->scheme == HTTP_AUTH_BASIC || auth->scheme == HTTP_AUTH_DIGEST)
		&& (authorization != NULL || send401)) {
	  log_msg((LOG_TRACE_LEVEL, "Authentication by ACS is enabled"));
	  log_msg((LOG_TRACE_LEVEL, "Authorization request header is %s",
			   authorization ? "present" : "absent"));
	  log_msg((LOG_TRACE_LEVEL, "%s-Authenticate response is %s",
			   forward_proxy_jurisdiction ? "Proxy" : "WWW",
			   send401 ? "enabled" : "disabled"));

	  /*
	   * If service_authorization is NULL and the configuration says we're
	   * allowed to send a WWW-Authenticate response, then return one with a
	   * 401 status code.
	   */
	  if (authorization == NULL) {
		char *auth_message, *www_authenticate;

	  prompt_client:
		if (auth->scheme == HTTP_AUTH_BASIC)
		  www_authenticate = http_auth_basic_auth(auth);
		else
		  www_authenticate = http_auth_digest_auth(auth, uri_path);

		auth_message = kwv_lookup_value(akwv, "http_auth_message");
		if (auth_message == NULL)
		  auth_message = ds_xprintf("%d Authentication by DACS is required",
									ACS_DENIAL_REASON_NO_AUTH);
		if (forward_proxy_jurisdiction) {
		  acs_emit_set_header("Proxy-Authenticate: %s", www_authenticate);
		  acs_emit_set_header("Status: 407");
		  log_msg((LOG_DEBUG_LEVEL, "Set-Header Proxy-Authenticate: \"%s\"",
				   www_authenticate));
		}
		else {
		  acs_emit_set_header("WWW-Authenticate: %s", www_authenticate);
		  acs_emit_set_header("Status: 401");
		  log_msg((LOG_DEBUG_LEVEL, "Set-Header WWW-Authenticate: \"%s\"",
				   www_authenticate));
		}

		if (auth_message != NULL && *auth_message != '\0')
		  printf("%s\n", auth_message);
		fflush(stdout);

		exit(1);
	  }

	  aa = http_auth_authorization_parse(authorization, &errmsg);
	  if (aa != NULL) {
		Auth_out dacsauth_out;

		if (auth->auth_modules == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "No auth modules are specified"));
		  return(-1);
		}

		args = dsvec_init(NULL, sizeof(char *));
		dsvec_add_ptr(args, "dacsauth");
		for (am = auth->auth_modules; am != NULL; am = am->next) {
		  dsvec_add_ptr(args, "-m");
		  dsvec_append(args, am->argv_spec);
		}
		for (rm = auth->roles_modules; rm != NULL; rm = rm->next) {
		  dsvec_add_ptr(args, "-r");
		  dsvec_append(args, rm->argv_spec);
		}
		dsvec_add_ptr(args, "-p");
		dsvec_add_ptr(args, non_null(aa->password));
		dsvec_add_ptr(args, "-u");
		dsvec_add_ptr(args, non_null(aa->username));
		dsvec_add_ptr(args, NULL);

		if (aa->username != NULL && aa->password != NULL)
		  log_msg((LOG_DEBUG_LEVEL, "Preparing to call dacsauth_main()"));

		if (aa->username != NULL && aa->password != NULL
			&& (rc = dacsauth_main(dsvec_len(args),
								   (char **) dsvec_base(args), 0,
								   &dacsauth_out)) == 0) {
		  char *mapped_username, *remote_addr, *ua_str;
		  Auth_style auth_style;

		  if (forward_proxy_jurisdiction) {
			log_msg((LOG_NOTICE_LEVEL,
					 "Proxy-Authorization header used directly"));
			log_msg((LOG_NOTICE_LEVEL | LOG_SENSITIVE_FLAG,
					 "Proxy-Authorization: \"%s\"",
					 service_proxy_authorization));
		  }
		  else {
			log_msg((LOG_NOTICE_LEVEL, "Authorization header used directly"));
			log_msg((LOG_NOTICE_LEVEL | LOG_SENSITIVE_FLAG,
					 "Authorization: \"%s\"", service_authorization));
		  }
		  kwv_delete(kwv, "SERVICE_AUTHORIZATION");
		  kwv_delete(kwv, "SERVICE_PROXY_AUTHORIZATION");

		  log_msg((LOG_TRACE_LEVEL,
				   "dacsauth: result=%d, identity=\"%s\", username=\"%s\", roles=\"%s\"",
				   dacsauth_out.result, dacsauth_out.identity,
				   dacsauth_out.username, dacsauth_out.role_string));

		  /* Create temporary credentials for aa->username. */
		  remote_addr = kwv_lookup_value(kwv, "SERVICE_REMOTE_ADDR");
		  ua_str = kwv_lookup_value(kwv, "SERVICE_USER_AGENT");
		  auth_style = (aa->scheme == HTTP_AUTH_BASIC)
			? AUTH_STYLE_PASSWORD : AUTH_STYLE_DIGEST;
		  auth_style |= AUTH_STYLE_ACS;

		  if (*dacsauth_out.username == '\0')
			mapped_username = aa->username;
		  else
			mapped_username = dacsauth_out.username;

		  credentials = make_credentials(conf_val(CONF_FEDERATION_NAME),
										 conf_val(CONF_JURISDICTION_NAME),
										 mapped_username, remote_addr,
										 dacsauth_out.role_string,
										 NULL, auth_style,
										 AUTH_VALID_FOR_ACS, NULL, ua_str);
		  log_msg((LOG_DEBUG_LEVEL, "Temporary credentials: \"%s\"",
				   make_ident_from_credentials(credentials)));
		  selected = credentials;
		}
		else {
		  /* Something is wrong. */
		  if (aa->username == NULL)
			log_msg((LOG_ERROR_LEVEL, "No username provided"));
		  else if (aa->password == NULL)
			log_msg((LOG_ERROR_LEVEL, "No password provided"));
		  else
			log_msg((LOG_ERROR_LEVEL, "Authentication failed, rc=%d", rc));

		  result->errmsg = "Authorization is invalid";
		  result->denial_reason = ACS_DENIAL_REASON_NO_AUTH;
		  log_msg((LOG_ERROR_LEVEL,
				   "Authentication failed for username \"%s\"",
				   (aa->username == NULL) ? "" : aa->username));
		  /*
		   * If send401 is allowed, then we should prompt again...
		   */
		  if (send401) {
			log_msg((LOG_TRACE_LEVEL, "Re-prompting client..."));
			goto prompt_client;
		  }

		  return(-1);
		}
	  }
	}
  }

  if (rlink != NULL && rlink->credentials != NULL) {
	/* Use the specified identity */
	credentials = rlink->credentials;
	selected = credentials;
  }

  /*
   * If any identities have been revoked we pull the plug.
   * If any credentials have been revoked, we don't accept them and ask the
   * client to invalidate its cookie.
   * Deselected credentials are also considered.
   * XXX Note that we can't force the client to delete credentials that
   * it's holding.
   */
  if ((st = check_revocation(credentials, kwv, "revocations", 1)) >= 1) {
	result->errmsg = "Access has been revoked";
	result->denial_reason = ACS_DENIAL_REASON_REVOKED;
	return(-1);
  }
  else if (st == -1) {
	log_msg((LOG_CRITICAL_LEVEL, "Couldn't process revocation list"));
	result->errmsg = "Revocation testing error";
	result->denial_reason = ACS_DENIAL_REASON_REVOKED;
	return(-1);
  }

  for (cr = credentials; cr != NULL; cr = cr->next) {
	if (streq(cr->valid_for, AUTH_VALID_FOR_NOTHING)) {
	  make_set_void_cookie_header(cr->cookie_name, 1, &cookie_str);
	  printf("%s", cookie_str);
	  log_msg((LOG_INFO_LEVEL, "Delete cookie: %s", cr->cookie_name));
	}
  }

  log_msg((LOG_INFO_LEVEL, "No identities have been revoked"));

  st = acs_token_grants(selected, uri, cookies, result);

  /*
   * Delete any of the client's invalid access token cookies discovered by
   * the call to acs_token_grants().
   */
  for (c = cookies; c != NULL; c = c->next) {
	if (c->set_void) {
	  make_set_void_cookie_header(c->name, 1, &cookie_str);
	  printf("%s", cookie_str);
	  log_msg((LOG_INFO_LEVEL, "Delete access token cookie: %s", c->name));
	}
  }

  if (st == 1) {
	by_token = 1;
	log_msg((LOG_INFO_LEVEL, "Token grants access"));
	goto token_ok;
  }

  acs_new_env(&env);
  if (acs_init_env(kwv, args, uri, selected, &env) == -1) {
	result->errmsg = "acs_init_env failed";
	return(-1);
  }

  if ((args_kwv = var_ns_lookup_kwv(env.namespaces, "Args")) != NULL) {
	Kwv_iter *iter;
	Kwv_pair *v;

	iter = kwv_iter_begin(args_kwv, NULL);
	i = 1;
	for (v = kwv_iter_first(iter); v != NULL ; v = kwv_iter_next(iter)) {
	  log_msg(((Log_level) (LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG),
			   "Service arg %d: %s -> \"%s\"", i++, v->name, v->val));
	}
	kwv_iter_end(iter);
  }

  if ((dacs_kwv = var_ns_lookup_kwv(env.namespaces, "DACS")) != NULL) {
	Kwv_iter *iter;
	Kwv_pair *v;

	iter = kwv_iter_begin(dacs_kwv, NULL);
	i = 1;
	for (v = kwv_iter_first(iter); v != NULL ; v = kwv_iter_next(iter)) {
	  log_msg(((Log_level) (LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG),
			   "DACS arg %d: %s -> \"%s\"", i++, v->name, v->val));
	}
	kwv_iter_end(iter);
  }

  ncred = count_valid_credentials(selected);

  if (rlink != NULL) {
	if ((st = acs_rlink_check(rlink, &env, selected, result)) == -1) {
	  if (result->errmsg == NULL)
		result->errmsg = "Rlink processing error";
	  result->denial_reason = ACS_DENIAL_REASON_UNKNOWN;
	  return(-1);
	}
	else if (st == 0) {
	  result->errmsg = "No ACL grants access";
	  result->denial_reason = ACS_DENIAL_REASON_NO_RULE;
	  return(-1);
	}

	if ((dacs_ns = var_ns_new(&env.namespaces, "DACS", NULL)) == NULL) {
	  if ((dacs_ns = var_ns_lookup(env.namespaces, "DACS")) == NULL) {
		result->denial_reason = ACS_DENIAL_REASON_UNKNOWN;
		return(-1);
	  }
	}
	dacs_kwv = dacs_ns->kwv;
	kwv_replace(dacs_kwv, "RNAME", rlink->rname);
	if (rlink->ident != NULL)
	  kwv_replace(dacs_kwv, "RIDENT", rlink->ident);
	if (rlink->iptr != NULL)
	  kwv_replace(dacs_kwv, "RIPTR", rlink->iptr);

	goto test_access;
  }
  else {
	if (dacs_kwv != NULL) {
	  kwv_delete(dacs_kwv, "RNAME");
	  kwv_delete(dacs_kwv, "RIDENT");
	  kwv_delete(dacs_kwv, "RIPTR");
	}
  }

  best = NULL;
  if (acs_find_applicable_acl("acls", &env, &best, ACL_DELEGATE_MAX, result)
	  == -1)
	return(-1);

  if (!result->m.delegated && !result->m.exact
	  && (vd = vfs_lookup_item_type("dacs_acls")) != NULL) {
	if (best == NULL)
	  log_msg((LOG_DEBUG_LEVEL, "Scanning \"dacs_acls\" for a match"));
	else
	  log_msg((LOG_DEBUG_LEVEL, "Scanning \"dacs_acls\" for a closer match"));
	st = acs_find_applicable_acl("dacs_acls", &env, &best,
								 ACL_DELEGATE_MAX, result);
	if (st == -1)
	  return(-1);
  }
  else
	log_msg((LOG_DEBUG_LEVEL, "Not scanning \"dacs_acls\""));

  if (result->m.nsegs_matched == 0) {
	result->errmsg = "No matching rule";
	if (ncred == 0)
	  result->denial_reason = ACS_DENIAL_REASON_NO_AUTH;
	else
	  result->denial_reason = ACS_DENIAL_REASON_NO_RULE;
	return(-1);
  }

  log_msg((LOG_INFO_LEVEL, "Using %sACL: '%s/%s'",
		   result->m.delegated ? "delegated " : "",
		   best->naming_context, best->path));

 test_access:

  if (result->m.acl->identities != NULL) {
	int rc;
	Acs_expr_result st;
	Credentials *ident_cr;
	Expr_result res;
	Identity *id;
	Kwv *kwv_conf;

	ident_cr = NULL;
	for (id = result->m.acl->identities; id != NULL; id = id->next) {
	  log_msg((LOG_TRACE_LEVEL, "Testing selector_expr: \"%s\"",
			   id->selector_expr));

	  kwv_conf = var_ns_lookup_kwv(dacs_conf->conf_var_ns, "Conf");
	  if ((st = acs_expr(id->selector_expr, &env, &res)) == ACS_EXPR_TRUE) {
		ident_cr = make_credentials_from_ident(id->ident, AUTH_STYLE_GENERATED,
											   kwv_conf);
		if (ident_cr == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Invalid identity: \"%s\"", id->ident));
		  result->errmsg = "Error evaluating an selector_expr";
		  result->denial_reason = ACS_DENIAL_REASON_UNKNOWN;
		  return(-1);
		}

		rc = verify_expiration(ident_cr->expires_secs, NULL);
		if (rc == -1) {
		  log_msg((LOG_ERROR_LEVEL,
				   "Ignoring identity with invalid expiry: \"%s\"",
				   id->ident));
		  ident_cr = NULL;
		  continue;
		}
		if (rc == 0) {
		  log_msg((LOG_ERROR_LEVEL, "Ignoring expired identity: \"%s\"",
				   id->ident));
		  ident_cr = NULL;
		  continue;
		}
		log_msg((LOG_TRACE_LEVEL, "Assigning identity: \"%s\"", id->ident));

		if ((rc = check_revocation(ident_cr, kwv, "revocations", 1)) >= 1) {
		  result->errmsg = "Access has been revoked";
		  result->denial_reason = ACS_DENIAL_REASON_REVOKED;
		  return(-1);
		}
		else if (rc == -1) {
		  log_msg((LOG_CRITICAL_LEVEL, "Couldn't process revocation list"));
		  result->errmsg = "Revocation testing error";
		  result->denial_reason = ACS_DENIAL_REASON_REVOKED;
		  return(-1);
		}

		break;
	  }
	  else if (st != ACS_EXPR_FALSE) {
		log_msg((LOG_ERROR_LEVEL, "selector_expr: evaluation error"));
		result->errmsg = "Error evaluating an selector_expr";
		result->denial_reason = ACS_DENIAL_REASON_UNKNOWN;
		return(-1);
	  }
	  else
		log_msg((LOG_TRACE_LEVEL, "Ignoring identity: \"%s\"", id->ident));
	}

	if (ident_cr != NULL) {
	  if (acs_init_env(kwv, args, uri, ident_cr, &env) == -1) {
		result->errmsg = "acs_init_env failed with assigned identity";
		return(-1);
	  }
	  ncred = 1;
	  selected = ident_cr;
	}
  }

  env.acl_rule = result->m.acl;
  if ((st = acl_grants_user(result->m.acl, &env, &result->cr, result)) != 1) {
	result->errmsg = "No ACL grants access";
	if (st == -1)
	  result->denial_reason = ACS_DENIAL_REASON_UNKNOWN;
	else if (result->redirect_action != NULL)
	  /* denial_reason was already set */
	  ;
	else if (result->notices != NULL)
	  result->denial_reason = ACS_DENIAL_REASON_ACK_NEEDED;
	else if (ncred == 0)
	  result->denial_reason = ACS_DENIAL_REASON_NO_AUTH;
	else
	  result->denial_reason = ACS_DENIAL_REASON_BY_RULE;

#ifdef USE_SIMPLE_REMOTE_USER
    /* XXX does not set REMOTE_USER to "unauth" if not selected... bug? */
	if (selected != NULL)
      acs_emit_header("User", "%s", selected->username);
#else
	acs_emit_header("User", "%s", auth_identity_from_credentials(selected));
#endif

	return(-1);
  }

  result->chaining = (result->attrs.permit_chaining != NULL)
	? result->attrs.permit_chaining
	: result->m.acl->default_attrs.permit_chaining;
  result->pass = (result->attrs.pass_credentials != NULL)
	? result->attrs.pass_credentials
	: result->m.acl->default_attrs.pass_credentials;
  result->pass_http_cookie = (result->attrs.pass_http_cookie != NULL)
	? result->attrs.pass_http_cookie
	: result->m.acl->default_attrs.pass_http_cookie;
  result->caching = (result->attrs.permit_caching != NULL)
	? result->attrs.permit_caching
	: result->m.acl->default_attrs.permit_caching;
  log_msg((LOG_DEBUG_LEVEL,
		   "chaining=\"%s\",pass=\"%s\",pass_cookie=\"%s\",caching=\"%s\"",
		   (result->chaining != NULL) ? result->chaining : "no",
		   (result->pass != NULL) ? result->pass : "no",
		   (result->pass_http_cookie) ? result->pass_http_cookie : "no",
		   (result->caching != NULL) ? result->caching : "no"));

  if (result->caching != NULL && strcaseeq(result->caching, "yes")) {
	char *set_cookie;

	if (acs_token_create(selected, result, &set_cookie) == 1) {
	  printf("%s", set_cookie);
	  log_msg((LOG_INFO_LEVEL,
			   "Access token issued for url=\"%s\"", result->m.s->url_path));
	}
  }

  /*********/

  token_ok:

  /* Do not set REMOTE_USER (even to "unauth") if unauthenticated */
  if (result->cr != NULL)
#ifdef USE_SIMPLE_REMOTE_USER
	if (result->cr != NULL)
      acs_emit_header("User", "%s", result->cr->username);
    else {
      /* XXX do we need to ask mod_auth_dacs to unset r->user? */
    }
#else
	acs_emit_header("User", "%s", auth_identity_from_credentials(result->cr));
#endif

  chaining = result->chaining;
  pass = result->pass;
  m = result->m;
  proxied_uri = kwv_lookup_value(proxy_kwv, "DACS_PROXIED_URI");

  remote_addr = kwv_lookup_value(kwv, "SERVICE_REMOTE_ADDR");
  remote_host = kwv_lookup_value(kwv, "SERVICE_REMOTE_HOST");
  if (remote_host != NULL)
	remote = ds_xprintf("from %s (%s)", remote_host, remote_addr);
  else
	remote = ds_xprintf("from %s", remote_addr);

  if (result->cr != NULL) {
	log_msg(((Log_level) (LOG_NOTICE_LEVEL | LOG_AUDIT_FLAG),
			 "*** Access granted to %s with roles=\"%s\" %s for %s%s",
			 auth_identity_from_credentials_track(result->cr),
			 result->cr->role_str, remote, uri,
			 by_token ? " by token" : "")); 
#ifdef ENABLE_USER_INFO
	user_info_access(result->cr, uri, remote_addr, remote_host);
#endif
  }
  else {
	log_msg(((Log_level) (LOG_NOTICE_LEVEL | LOG_AUDIT_FLAG),
			 "*** Access granted to %s %s for %s%s",
			 auth_identity_from_credentials_track(NULL), remote, uri,
			 by_token ? " by token" : "")); 
  }

  /*
   * The standard output is read by mod_auth_dacs, which passes it on
   * depending on the particular situation.
   *
   * For directly invoked CGI programs, it is converted into
   * environment variables (doing it this way isn't secure, because a
   * process's environment may be readable, but how else to do this?  But this
   * information should not be sensitive.  It could be done by having these
   * programs explicitly query a server for the information.)
   * For proxied service requests, the information is passed via an
   * HTTP header.
   */
  auth_kwv = result->auth_kwv = kwv_init(20);
  kwv_add(auth_kwv, "DACS_VERSION", DACS_VERSION_NUMBER);
  if (dacs_conf != NULL) {
	if (dacs_conf->dacs_conf != NULL)
	  kwv_add(auth_kwv, "DACS_CONF", dacs_conf->dacs_conf);
	if (dacs_conf->site_conf != NULL)
	  kwv_add(auth_kwv, "DACS_SITE_CONF", dacs_conf->site_conf);
  }

  if (result->cr != NULL) {
	ident = auth_identity_from_credentials(result->cr);
	kwv_add(auth_kwv, "DACS_IDENTITY", ident);
	kwv_add(auth_kwv, "DACS_CONCISE_IDENTITY",
			make_ident_from_credentials(result->cr));
	kwv_add(auth_kwv, "DACS_FEDERATION", result->cr->federation);
	kwv_add(auth_kwv, "DACS_JURISDICTION",
			result->cr->home_jurisdiction);
	kwv_add(auth_kwv, "DACS_USERNAME", result->cr->username);
	kwv_add(auth_kwv, "DACS_ROLES", result->cr->role_str);
  }
  else
	ident = NULL;

  kwv_add(auth_kwv, "DACS_ACS_JURISDICTION", jurisdiction);

  if (result->attrs.constraint != NULL)
	kwv_add(auth_kwv, "DACS_CONSTRAINT", result->attrs.constraint);
  else
	kwv_add(auth_kwv, "DACS_CONSTRAINT", "");

  if (m.acl != NULL && m.acl->default_attrs.constraint != NULL)
	kwv_add(auth_kwv, "DACS_DEFAULT_CONSTRAINT",
			m.acl->default_attrs.constraint);
  else
	kwv_add(auth_kwv, "DACS_DEFAULT_CONSTRAINT", "");

  if ((method = kwv_lookup_value(kwv, "SERVICE_METHOD")) == NULL)
	method = "GET";		/* XXX ??? */

  if (conf_val_eq(CONF_ACS_EMIT_APPROVAL, "yes")) {
	char *approval, *dn;
	Crypt_keys *ck;

	dn = "SHA1";
	if ((ck = crypt_keys_from_vfs(ITEM_TYPE_JURISDICTION_KEYS)) != NULL) {
	  /* The stamp is intended to help guard against replay. */
	  if ((dn = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
								 "dacs_approval_digest_name")) == NULL)
		dn = "SHA1";

	  if ((approval = dacs_approval_create(current_uri(NULL), method, ident,
										   dn, ck->private_key)) != NULL) {
		kwv_add(auth_kwv, "DACS_APPROVAL", approval);
		log_msg((LOG_TRACE_LEVEL, "Emitting DACS_APPROVAL: %s", approval));
	  }
	}

	if (ck == NULL || approval == NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "ACS_EMIT_APPROVAL is enabled but cannot sign approval"));
	  if (ck == NULL)
		log_msg((LOG_ERROR_LEVEL, "Cannot get jurisdiction keys"));
	  else {
		log_msg((LOG_ERROR_LEVEL, "Signing operation failed"));
		if (ck != NULL)
		  crypt_keys_free(ck);
	  }
	}
  }

  if (pass != NULL && !streq(pass, "none")) {
	if (result->cr != NULL && streq(pass, "matched")) {
	  if (chaining == NULL || streq(chaining, "yes")) {
		result->cr->valid_for = AUTH_VALID_FOR_CHAINING;
		log_msg((LOG_WARN_LEVEL, "Credentials are valid for chaining"));
	  }
	  else
		result->cr->valid_for = AUTH_VALID_FOR_IDENT;
	  result->cr->next = NULL;

	  if (credentials_to_auth_cookies(result->cr, &cookie_str) == -1) {
		result->errmsg = "Couldn't reconstruct a cookie";
		result->denial_reason = ACS_DENIAL_REASON_UNKNOWN;
		return(-1);
	  }
	}
	else if (streq(pass, "all")) {
	  char *auth_cookies, *s;

	  for (cr = credentials; cr != NULL; cr = cr->next) {
		if (!streq(cr->valid_for, AUTH_VALID_FOR_NOTHING)) {
		  if (chaining == NULL || streq(chaining, "yes")) {
			cr->valid_for = AUTH_VALID_FOR_CHAINING;
			log_msg((LOG_WARN_LEVEL, "Credentials are valid for chaining"));
		  }
		  else
			cr->valid_for = AUTH_VALID_FOR_IDENT;
		}
	  }
	  result->cr = credentials;

	  if (credentials_to_auth_cookies(result->cr, &auth_cookies) == -1) {
		result->errmsg = "Couldn't reconstruct a cookie";
		result->denial_reason = ACS_DENIAL_REASON_UNKNOWN;
		return(-1);
	  }

	  if (scredentials != NULL) {
		if (scredentials->unauth != NULL)
		  make_scredentials_cookie(NULL, &s);
		else
		  make_scredentials_cookie(credentials, &s);
		if (auth_cookies[0] != '\0')
		  cookie_str = ds_xprintf("%s; %s", auth_cookies, s);
		else
		  cookie_str = s;
	  }
	  else
		cookie_str = auth_cookies;
	}
	else {
	  result->errmsg = "Invalid default_pass_credentials?";
	  result->denial_reason = ACS_DENIAL_REASON_UNKNOWN;
	  return(-1);
	}

	log_msg((LOG_INFO_LEVEL, "Passing DACS_COOKIE"));
	log_msg((LOG_TRACE_LEVEL, "DACS_COOKIE:\n%s", cookie_str));

	kwv_add(auth_kwv, "DACS_COOKIE", cookie_str);
  }

  proxy_static_flag = (kwv_lookup(proxy_kwv, "PROXY_STATIC_FLAG") != NULL);
  proxy_exec_flag = (kwv_lookup(proxy_kwv, "PROXY_EXEC_FLAG") != NULL);
  proxy_pass_authorization_flag
	= (kwv_lookup(proxy_kwv, "PROXY_PASS_AUTHORIZATION_FLAG") != NULL);
  pass_env_flag
	= (kwv_lookup(proxy_kwv, "PASS_ENV_FLAG") != NULL);

  if (proxy_static_flag || proxy_exec_flag) {
	if (proxy_pass_authorization_flag || proxy_exec_flag) {
	  kwv_add(auth_kwv, "DACS_REMOTE_ADDR",
			  kwv_lookup_value(proxy_kwv, "REMOTE_ADDR"));
	  if (result->cr != NULL) {
		kwv_add(auth_kwv, "DACS_EXPIRES", ul_str(result->cr->expires_secs));
		kwv_add(auth_kwv, "DACS_UNIQUE", result->cr->unique);
	  }
	  if (streq(uri, kwv_lookup_value(proxy_kwv, "PROXY_EXEC_PROG_URI"))) {
		kwv_add(auth_kwv, "DACS_PROG_URI", uri);
		if (proxied_uri != NULL)
		  kwv_add(auth_kwv, "DACS_PROXIED_URI",
				  kwv_lookup_value(proxy_kwv, "DACS_PROXIED_URI"));
	  }
	  else
		kwv_add(auth_kwv, "DACS_PROXIED_URI", uri);
	  if (proxy_exec_flag) {
		kwv_add(auth_kwv, "DACS_PASS_AUTH",
				ul_str((unsigned long) proxy_pass_authorization_flag));
	  }
	}

	kwv_add(auth_kwv, "DACS_PASS_CREDENTIALS",
			(char *) ((pass == NULL) ? "none" : pass));
	kwv_add(auth_kwv, "DACS_ALLOW_CHAINING",
			(char *) ((chaining == NULL) ? "no" : chaining));
	kwv_add(auth_kwv, "DACS_PASS_ENV",
			ul_str((unsigned long) pass_env_flag));
  }

  return(0);
}

Acs_error_handler *
acs_init_error_handler(char *code, char *url_pattern, char *action,
					   Acs_error_handler_type type)
{
  Acs_error_handler *h;

  h = ALLOC(Acs_error_handler);
  h->type = ACS_HANDLER_DEFAULT;
  h->url_pattern = (url_pattern != NULL) ? strdup(url_pattern) : NULL;
  h->code_string = code;
  h->handler_action = action;

  if (action == NULL)
	return(h);

  if (type != ACS_HANDLER_NONE)
	h->type = type;
  else if (strneq(action, "http", 4))
	h->type = ACS_HANDLER_URL;
  else if (action[0] == '/')
	h->type = ACS_HANDLER_LOCAL_URL;
  else if (streq(action, "reason"))
	h->type = ACS_HANDLER_REASON;
  else if (streq(action, "default"))
	h->type = ACS_HANDLER_DEFAULT;
  else if (action[0] == '"')
	h->type = ACS_HANDLER_MESSAGE;
  else
	return(NULL);

  return(h);
}

static struct denial_reason_map {
  char *name;
  Acs_denial_reason reason;
} denial_reason_map[] = {
  { "NO_RULE",            ACS_DENIAL_REASON_NO_RULE },
  { "BY_RULE",            ACS_DENIAL_REASON_BY_RULE },
  { "NO_AUTH",            ACS_DENIAL_REASON_NO_AUTH },
  { "REVOKED",            ACS_DENIAL_REASON_REVOKED },
  { "BY_REDIRECT",        ACS_DENIAL_REASON_BY_REDIRECT },
  { "ACK_NEEDED",         ACS_DENIAL_REASON_ACK_NEEDED },
  { "LOW_AUTH",           ACS_DENIAL_REASON_LOW_AUTH },
  { "BY_SIMPLE_REDIRECT", ACS_DENIAL_REASON_BY_SIMPLE_REDIRECT },
  { "CREDENTIALS_LIMIT",  ACS_DENIAL_REASON_CREDENTIALS_LIMIT },
  { "INACTIVITY",         ACS_DENIAL_REASON_INACTIVITY },
  { "UNKNOWN",            ACS_DENIAL_REASON_UNKNOWN },
  { "DEFAULT",            ACS_DENIAL_REASON_DEFAULT },
  { "*",                  ACS_DENIAL_REASON_DEFAULT },
  { NULL,                 ACS_DENIAL_REASON_LAST_RULE }
};

Acs_denial_reason
acs_lookup_error_code(char *code_str)
{
  int i;
  unsigned long ec;
  char *ptr;

  ec = strtoul(code_str, &ptr, 10);
  if (ptr == code_str) {
	/* No digits, so a keyword is expected. */
	for (i = 0; denial_reason_map[i].name != NULL; i++) {
	  if (strcaseeq(denial_reason_map[i].name, code_str))
		return(denial_reason_map[i].reason);
	}
  }
  else {
	if (errno == ERANGE || *ptr != '\0'
		|| ec < ACS_DENIAL_REASON_FIRST_RULE
		|| ec > ACS_DENIAL_REASON_LAST_RULE)
	  return(ACS_DENIAL_REASON_UNKNOWN);

	for (i = 0; denial_reason_map[i].name != NULL; i++) {
	  if (denial_reason_map[i].reason == ec)
		return(denial_reason_map[i].reason);
	}
  }

  log_msg((LOG_ERROR_LEVEL, "Invalid error code: \"%s\"", code_str));

  return(ACS_DENIAL_REASON_UNKNOWN);
}

/*
 * Look through the ACS_ERROR_HANDLER directives to find the best match.
 * Syntax:
 *  [<url_pattern>] <error-code> <error-action>
 * where <url_pattern> is an ACS url_pattern (which must begin with a slash),
 * <error-code> is an error code number or keyword equivalent, and
 * <error-action> is a reserved word, absolute URL, relative URL,
 * or string message.
 */
Acs_error_handler *
acs_lookup_error_handler(Acs_denial_reason reason, char *request_uri)
{
  int best, exact, n;
  char *urlpattern;
  char *best_urlpattern, *best_error_code, *best_error_action;
  char *default_error_code, *default_error_action, *default_urlpattern;
  unsigned long ec;
  Acs_denial_reason r;
  Acs_error_handler *h;
  Acs_error_handler_type best_type, default_type;
  Dsvec *dsv_path;
  Kwv_pair *best_v, *default_v, *v;
  Strfields *best_fields, *default_fields, *fields;

  if ((v = conf_var(CONF_ACS_ERROR_HANDLER)) == NULL)
	return(NULL);

  if (request_uri != NULL)
	dsv_path = uri_path_parse(request_uri);
  else
	dsv_path = NULL;

  best = 0;
  best_fields = NULL;
  best_urlpattern = NULL;
  best_v = NULL;
  best_error_code = NULL;
  best_error_action = NULL;
  best_type = ACS_HANDLER_NONE;

  default_fields = NULL;
  default_urlpattern = NULL;
  default_v = NULL;
  default_error_code = NULL;
  default_error_action = NULL;
  default_type = ACS_HANDLER_NONE;

  for (; v != NULL; v = v->next) {
	int c;
	char *error_code, *error_action;
	Acs_error_handler_type type;
	Strfields *f;
	Mkargv mkargv = { 1, 0, " \t", NULL, NULL };

	/* Keep the quotes to detect "message" error-action */
	f = ALLOC(Strfields);
	f->conf = &mkargv;
	if ((fields = strpfields(v->val, f)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid directive: %s \"%s\"", v->name, v->val));
	  return(NULL);
	}

	if (fields->argc < 2 || fields->argc > 4) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid directive: %s \"%s\"", v->name, v->val));
	  return(NULL);
	}

	c = 0;
	urlpattern = "/*";
	if (fields->argv[0][0] == '/') {
	  urlpattern = fields->argv[0];
	  c = 1;
	}

	error_code = fields->argv[c++];

	if (fields->argv[c] == NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid directive: %s \"%s\"", v->name, v->val));
	  return(NULL);
	}

	/* Look for an optional type keyword. */
	type = ACS_HANDLER_NONE;
	if (streq(fields->argv[c], "expr")
		&& fields->argv[c + 1] != NULL) {
	  type = ACS_HANDLER_EXPR;
	  c++;
	}
	else if (streq(fields->argv[c], "reason")
			 && fields->argv[c + 1] == NULL) {
	  type = ACS_HANDLER_REASON;
	  c++;
	}
	else if (streq(fields->argv[c], "default")
			 && fields->argv[c + 1] == NULL) {
	  type = ACS_HANDLER_DEFAULT;
	  c++;
	}
	else if (streq(fields->argv[c], "url")
			 && fields->argv[c + 1] != NULL) {
	  type = ACS_HANDLER_URL;
	  c++;
	}
	else if (streq(fields->argv[c], "localurl")
			 && fields->argv[c + 1] != NULL) {
	  type = ACS_HANDLER_LOCAL_URL;
	  c++;
	}
	else if (streq(fields->argv[c], "message")
			 && fields->argv[c + 1] != NULL) {
	  type = ACS_HANDLER_MESSAGE;
	  c++;
	}

	error_action = fields->argv[c++];

	if (fields->argv[c] != NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid directive: %s \"%s\"", v->name, v->val));
	  return(NULL);
	}

	log_msg((LOG_TRACE_LEVEL, "ACS_ERROR_HANDLER: \"%s %s %s\"",
			 urlpattern, error_code, error_action));

	/*
	 * If this directive does not apply to the request or is not the closet
	 * URI match found so far, skip to the next one.
	 */
	if (dsv_path != NULL) {
	  exact = 0;
	  n = acs_match_url_segs(urlpattern, dsv_path, &exact);

	  if (n <= 0 || n < best) {
		log_msg((LOG_TRACE_LEVEL, "Ignoring inferior URI match"));
		continue;
	  }
	}
	else
	  n = 0;

	/* Now check if the <error-code> matches the REASON argument. */
	r = acs_lookup_error_code(error_code);
	if (r == ACS_DENIAL_REASON_UNKNOWN) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Invalid error code in ACS_ERROR_HANDLER directive"));
	  return(NULL);
	}
	ec = (unsigned long) r;

	if (r == ACS_DENIAL_REASON_DEFAULT) {
	  /*
	   * This is a default handler specification.
	   * It may turn out to be the best match if no exact code match
	   * is found or if no better URI match is found.
	   */
	  if (default_fields == NULL) {
		default_fields = fields;
		default_v = v;
		default_urlpattern = urlpattern;
		default_error_code = error_code;
		default_error_action = error_action;
		default_type = type;
	  }
	  else
		log_msg((LOG_DEBUG_LEVEL,
				 "Multiple default handlers: ignoring earlier ones"));
	  ec = 0;		/* An invalid reason code */
	}

	if (ec == (unsigned long) reason) {
	  best = n;
	  best_fields = fields;
	  best_v = v;
	  best_urlpattern = urlpattern;
	  best_error_code = error_code;
	  best_error_action = error_action;
	  best_type = type;

	  if (exact)
		break;
	}
  }

  if (best_fields == NULL) {
	if (default_fields == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "No applicable ACS_ERROR_HANDLER found"));
	  return(NULL);
	}
	best_fields = default_fields;
	best_v = default_v;
	best_urlpattern = default_urlpattern;
	best_error_code = default_error_code;
	best_error_action = default_error_action;
	best_type = default_type;
  }

  if (best_type == ACS_HANDLER_EXPR)
	best_error_action = strdequote(best_error_action);

  h = acs_init_error_handler(best_error_code, best_urlpattern,
							 best_error_action, best_type);
  if (h == NULL) {
	log_msg((LOG_ERROR_LEVEL,
			 "Error parsing ACS_ERROR_HANDLER directive: %s", best_v->val));
	return(NULL);
  }

  log_msg((LOG_DEBUG_LEVEL, "Using error handler: \"%s %s%s %s\"",
		   h->url_pattern, h->code_string,
		   (h->type == ACS_HANDLER_EXPR) ? " (expr) " : "",
		   h->handler_action));
  return(h);
}

char *
acs_make_notice_presentation_args(Acs_result *result, char **notice_uris,
								  char **resource_uris, char **time_str,
								  char **hmac)
{
  char *p;
  unsigned char *outp;
  time_t now;
  Crypt_keys *ck;
  Ds ds;
  Hmac_handle *h;

  ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
  h = crypto_hmac_open(CRYPTO_HMAC_DIGEST_ALG_NAME, ck->hmac_key,
					   CRYPTO_HMAC_KEY_LENGTH);
  crypt_keys_free(ck);

  ds_init(&ds);

  p = strjoin(result->notices, " ");
  crypto_hmac_hash(h, (unsigned char *) p, strlen(p));
  ds_asprintf(&ds, "&NOTICE_URIS=%s", p);
  if (notice_uris != NULL)
	*notice_uris = p;

  p = current_uri_no_query(NULL);
  crypto_hmac_hash(h, (unsigned char *) p, strlen(p));
  ds_asprintf(&ds, "&RESOURCE_URIS=%s", p);
  if (resource_uris != NULL)
	*resource_uris = p;

  time(&now);
  p = ds_xprintf("%lu", (unsigned long) now);
  crypto_hmac_hash(h, (unsigned char *) p, strlen(p));
  ds_asprintf(&ds, "&TIME=%s", p);
  if (time_str != NULL)
	*time_str = p;

  outp = crypto_hmac_close(h, NULL, NULL);
  strba64(outp, CRYPTO_HMAC_BYTE_LENGTH, &p);
  ds_asprintf(&ds, "&HMAC=%s", p);
  if (hmac != NULL)
	*hmac = p;

  return(ds_buf(&ds));
}

static char *
make_dacs_acs_attr_string(void)
{
  Crypt_keys *ck;
  Ds ds;
  Jurisdiction *j;

  ds_init(&ds);
  ds_asprintf(&ds, "fed_name=\"%s\"", conf_val(CONF_FEDERATION_NAME));
  ds_asprintf(&ds, " fed_domain=\"%s\"", conf_val(CONF_FEDERATION_DOMAIN));
  if ((ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS)) != NULL) {
	if (ck->fed_id != NULL)
	  ds_asprintf(&ds, " fed_id=\"%s\"", ck->fed_id);
	if (ck->public_key_pem != NULL) {
	  char *public_key;

	  mime_encode_base64((unsigned char *) ck->public_key_pem,
						 strlen(ck->public_key_pem), &public_key);
	  ds_asprintf(&ds, " fed_public_key=\"%s\"", public_key);
	}
	crypt_keys_free(ck);
  }
  ds_asprintf(&ds, " jur_name=\"%s\"", conf_val(CONF_JURISDICTION_NAME));
  if (get_jurisdiction_meta(conf_val(CONF_JURISDICTION_NAME), &j) == 0)
	  ds_asprintf(&ds, " jur_dacs_url=\"%s\"", j->dacs_url);

  return(ds_buf(&ds));
}

int
acs_emit_vheader(char *name, char *fmt, va_list ap)
{
  char *str;

  str = ds_xprintf("=%s=%s\n", name, ds_vxprintf(fmt, ap));
  fprintf(stdout, "%s", str);

  return(0);
}

int
acs_emit_header(char *name, char *fmt, ...)
{
  char *str;
  va_list ap;

  va_start(ap, fmt);
  str = ds_xprintf("=%s=%s\n", name, ds_vxprintf(fmt, ap));
  fprintf(stdout, "%s", str);
  va_end(ap);

  return(0);
}

int
acs_emit_set_header(char *fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);
  acs_emit_vheader("Set-Header", fmt, ap);
  va_end(ap);

  return(0);
}

int
acs_emit_add_header(char *fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);
  acs_emit_vheader("Add-Header", "%s", ap);
  va_end(ap);

  return(0);
}

int
acs_emit_access_granted(FILE *fp)
{

  if (!test_emit_xml_format()) {
	fprintf(fp, "798 Access granted\n");
	return(0);
  }

  emit_content_type_header = 0;
  emit_xml_header(fp, "dacs_acs");
  fprintf(fp, "<%s %s>\n",
		  make_xml_root_element("dacs_acs"), make_dacs_acs_attr_string());
  fprintf(fp, "<access_granted/>\n");
  fprintf(fp, "</dacs_acs>\n");

  return(0);
}

int
acs_emit_access_error(FILE *fp, int code, char *mesg)
{
  char *code_str;
  Common_status status;

  if (!test_emit_xml_format()) {
	if (mesg != NULL)
	  fprintf(fp, "799 Access error -- %s\n", mesg);
	else
	  fprintf(fp, "799 Access error\n");
	return(0);
  }

  emit_content_type_header = 0;
  emit_xml_header(fp, "dacs_acs");

  fprintf(fp, "<%s %s>\n",
		  make_xml_root_element("dacs_acs"), make_dacs_acs_attr_string());
  code_str = ds_xprintf("%d", code);
  init_common_status(&status, NULL, code_str, mesg);
  fprintf(fp, "%s", make_xml_common_status(&status));
  fprintf(fp, "</dacs_acs>\n");

  return(0);
}

int
acs_emit_access_denial(FILE *fp, Acs_denial_reason denial_reason,
					   char *request_uri, char *denial_msg, Acs_result *result)
{
  int i;
  char *action, *p;
  Acs_error_handler *handler;

  if (!test_emit_xml_format()) {
	fprintf(fp, "797 Access denied\n");
	return(0);
  }

  emit_content_type_header = 0;
  emit_xml_header(fp, "dacs_acs");

  action = NULL;
  if ((handler = acs_lookup_error_handler(denial_reason, request_uri))
	  != NULL) {
	if (handler->type == ACS_HANDLER_LOCAL_URL)
	  action = ds_xprintf("%s%s",
						  current_uri_sa(NULL), handler->handler_action);
	else if (handler->type == ACS_HANDLER_URL)
	  action = handler->handler_action;
	else
	  action = NULL;
  }

  if (denial_reason == ACS_DENIAL_REASON_ACK_NEEDED) {
	char *hmac, *notice_uris, *resource_uris, *time_str;

	acs_make_notice_presentation_args(result, &notice_uris, &resource_uris,
									  &time_str, &hmac);

	fprintf(fp, "<%s %s>\n",
			make_xml_root_element("dacs_acs"), make_dacs_acs_attr_string());
	fprintf(fp, "<access_denied>\n");
	fprintf(fp, "<event%d", denial_reason);

	if (action != NULL)
	  fprintf(fp, " presentation_handler=\"%s\"", action);
	/*
	 * XXX Whether the following argument is provided should be configurable.
	 * If not provided, the client should be forced to call the presentation
	 * handler.
	 */
	if ((p = conf_val(CONF_NOTICES_ACK_HANDLER)) != NULL)
	  fprintf(fp, " ack_handler=\"%s\"", p);
	else if (action != NULL)
	  fprintf(fp, " ack_handler=\"%s\"", action);
	fprintf(fp, " notice_uris=\"%s\"", notice_uris);
	fprintf(fp, " resource_uris=\"%s\"", resource_uris);
	fprintf(fp, " time=\"%s\"", time_str);
	fprintf(fp, " hmac=\"%s\"", hmac);
	fprintf(fp, ">\n");
	fprintf(fp, "<notices>\n");
	for (i = 0; i < dsvec_len(result->notices); i++)
	  fprintf(fp, "<notice_uri uri=\"%s\"/>\n",
			  (char *) dsvec_ptr_index(result->notices, i));
	fprintf(fp, "</notices>\n");
	fprintf(fp, "</event%d>\n", denial_reason);
	fprintf(fp, "</access_denied>\n");
	fprintf(fp, "</dacs_acs>\n");
  }
  else {
	fprintf(fp, "<%s %s>\n",
			make_xml_root_element("dacs_acs"), make_dacs_acs_attr_string());
	fprintf(fp, "<access_denied>\n");
	fprintf(fp, "<event%d message=\"%s\"", denial_reason, denial_msg);
	if (action != NULL)
	  fprintf(fp, " handler=\"%s\"/>\n", action);
	else
	  fprintf(fp, "/>\n");
	fprintf(fp, "</access_denied>\n");
	fprintf(fp, "</dacs_acs>\n");
  }

  return(0);
}

/*
 * Map an externally visible name RPARTS, as separate path components,
 * into the corresponding relative filesystem name, reflecting the state of
 * the ACL as STATUS.
 */
char *
acl_build_name(Dsvec *rparts, Acl_status status)
{
  int i;
  char *name, *p;
  Ds path;

  ds_init(&path);
  for (i = 0; i < dsvec_len(rparts); i++) {
	p = (char *) dsvec_ptr_index(rparts, i);
	if (i == dsvec_len(rparts) - 1 && status == ACL_STATUS_DISABLED)
	  ds_asprintf(&path, "%s%s%s%s",
				  (i != 0) ? "/" : "",
				  ACL_DISABLED_NAME_PREFIX,
				  ACL_NAME_PREFIX,
				  p);
	else
	  ds_asprintf(&path, "%s%s%s",
				  (i != 0) ? "/" : "", ACL_NAME_PREFIX, p);
  }

  name = ds_buf(&path);

  return(name);
}

static int
acl_change_status(Vfs_handle *h, char *path, Acl_status new_status)
{
  int st_disabled, st_enabled;
  char *disabled_name, *enabled_name;
  Dsvec *rparts;

  if ((rparts = strsplit(path, "/", 0)) == NULL)
	return(-1);

  enabled_name = acl_build_name(rparts, ACL_STATUS_ENABLED);
  disabled_name = acl_build_name(rparts, ACL_STATUS_DISABLED);

  if ((st_enabled = vfs_exists(h, enabled_name)) == -1)
	return(-1);

  if ((st_disabled = vfs_exists(h, disabled_name)) == -1)
	return(-1);

  if (st_enabled == 1 && st_disabled == 1)
	return(-1);

  if (st_enabled == 0 && st_disabled == 0)
	return(-1);

  if (new_status == ACL_STATUS_ENABLED) {
	if (st_enabled == 1)
	  return(0);

	if (vfs_rename(h, disabled_name, enabled_name) == -1)
	  return(-1);
  }
  else {
	if (st_disabled == 1)
	  return(0);

	if (vfs_rename(h, enabled_name, disabled_name) == -1)
	  return(-1);
  }

  return(0);

}

int
acl_enable(Vfs_handle *h, char *path)
{

  return(acl_change_status(h, path, ACL_STATUS_ENABLED));
}

int
acl_disable(Vfs_handle *h, char *path)
{

  return(acl_change_status(h, path, ACL_STATUS_DISABLED));
}

#ifdef NOTDEF
/*
 * This is intended to return a list of all services belonging to the
 * ACL applicable to the request.
 * But it's buggy - not all service path strings may have
 * been evaluated because select_acl_url() stops if an exact match is
 * found.
 */
char *
acl_services_str(Acl_rule *acl_rule)
{
  Service *s;
  Ds ds;

  if (acl_rule == NULL || acl_rule->services == NULL
	  || acl_rule->services->service == NULL)
	return("");

  ds_init(&ds);
  for (s = acl_rule->services->service; s != NULL; s = s->next) {
	ds_asprintf(&ds, "%s%s%s",
				current_uri_no_query(NULL),
				s->url_path, s->next != NULL ? "," : "");
  }

  return(ds_buf(&ds));
}
#endif

static Dsvec *acs_success_list = NULL;

int
acs_add_success_expr(char *expr)
{

  if (acs_success_list == NULL)
	acs_success_list = dsvec_init(NULL, sizeof(char *));

  dsvec_add_ptr(acs_success_list, strdup(expr));

  return(0);
}

int
acs_get_success_exprs(char ***list)
{

  *list = (char **) dsvec_base(acs_success_list);

  return(dsvec_len(acs_success_list));
}

int
acs_success(Kwv *kwv, Kwv *kwv_args, Acs_result *result)
{
  int i, st;
  char *p;
  Kwv_pair *v;

  for (v = conf_var(CONF_ACS_SUCCESS); v != NULL; v = v->next) {
	p = v->val;
	log_msg((LOG_TRACE_LEVEL, "Evaluating ACS_SUCCESS expr: \"%s\"", p));
	st = acs_eval(p, kwv, kwv_args, result->cr, NULL);
	if (acs_expr_error_occurred(st))
	  log_msg((LOG_ERROR_LEVEL, "Invalid ACS_SUCCESS directive: \"%s\"", p));
  }

  for (i = 0; i < dsvec_len(acs_success_list); i++) {
	p = (char *) dsvec_ptr_index(acs_success_list, i);
	log_msg((LOG_TRACE_LEVEL, "Evaluating acs_success_list expr: \"%s\"", p));
	st = acs_eval(p, kwv, kwv_args, result->cr, NULL);
	if (acs_expr_error_occurred(st))
	  log_msg((LOG_ERROR_LEVEL, "Invalid ACS_SUCCESS directive: \"%s\"", p));
  }

  return(0);
}

/*
 * Parse an RLINK directive.
 * Syntax: RLINK <expression> [<vfs_uri>]
 * If the expression evaluates to the name of an Rlink that is found
 * in <vfs_uri>, then that Rlink applies to this request.
 * If the RLINK should be ignored, the expression should evaluate to the
 * empty string or zero.
 * The <vfs_uri> can be anything acceptable to vfs_open_any(), including
 * an item type.
 */
Rlink *
acs_rlink_parse(char *rlink_str)
{
  Rlink *rlink;
  Strfields *fields;

  if (rlink_str == NULL || *rlink_str == '\0')
	return(NULL);

  if ((fields = strpfields(rlink_str, NULL)) == NULL
	  || fields->argc < 1 || fields->argc > 2) {
	log_msg((LOG_TRACE_LEVEL, "RLINK parse error: \"%s\"", rlink_str));
	return(NULL);
  }

  rlink = ALLOC(Rlink);
  rlink->rname = NULL;
  rlink->ident = NULL;
  rlink->iptr = NULL;
  rlink->expr = fields->argv[0];
  if (fields->argc == 2)
	rlink->rule_vfs = fields->argv[1];
  else
	rlink->rule_vfs = NULL;
  rlink->credentials = NULL;

  return(rlink);
}

/*
 * Evaluate the expression in an RLINK directive.
 * If it returns a string, then that directive is enabled and the result
 * is the rlink to use - return the string.
 * Return NULL if the directive should be ignored.
 */
char *
acs_rlink_is_enabled(Rlink *rlink, Kwv *kwv, Kwv *kwv_args, Credentials *cr)
{
  char *result_str;
  Acs_expr_result st;
  
  if ((st = acs_eval(rlink->expr, kwv, kwv_args, cr, &result_str))
	  == ACS_EXPR_TRUE) {
	log_msg((LOG_TRACE_LEVEL, "rlink evaluates to: \"%s\"", result_str));
	return(result_str);
  }

  log_msg((LOG_TRACE_LEVEL, "rlink disabled (%s)",
		   (st == ACS_EXPR_FALSE) ? "False" : "Error"));
  return(NULL);
}

int
acs_rname_parse(Rlink *rlink, char *rname)
{
  int st;
  char *p, *r;
  Kwv *kwv_conf;

  r = strdup(rname);
  if ((p = strchr(r, (int) RNAME_IDENT_SEP_CHAR)) != NULL) {
	char *str;
	unsigned char *enc_str;
	unsigned int enc_len;
	Crypt_keys *ck;
	Dsvec *dsv;

	rlink->rname = r;
	*p++ = '\0';

	if ((ck = crypt_keys_from_vfs(ITEM_TYPE_JURISDICTION_KEYS)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Rname: can't get jurisdiction_keys"));
	  return(-1);
	}

	if (stra64b(p, &enc_str, &enc_len) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Rname: base-64 decoding failed"));
	  return(-1);
	}
	if (crypto_decrypt_string(ck, enc_str, enc_len, (unsigned char **) &str,
							  NULL) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Rname: decryption failed"));
	  crypt_keys_free(ck);
	  return(-1);
	}
	crypt_keys_free(ck);
	dsv = strsplit(str, " ", 0);
	if (dsvec_len(dsv) != 2) {
	  log_msg((LOG_ERROR_LEVEL, "Rname: field parse failed"));
	  return(-1);
	}

	if (!streq(dsvec_ptr(dsv, 0, char *), rlink->rname)) {
	  log_msg((LOG_ERROR_LEVEL, "Rname: decrypted Rname does not match"));
	  return(-1);
	}

	rlink->ident = dsvec_ptr(dsv, 1, char *);
	kwv_conf = var_ns_lookup_kwv(dacs_conf->conf_var_ns, "Conf");
	rlink->credentials = make_credentials_from_ident(rlink->ident,
													 AUTH_STYLE_RLINK,
													 kwv_conf);

	if (rlink->credentials == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Rlink has invalid ident"));
	  return(-1);
	}
	st = verify_expiration(rlink->credentials->expires_secs, NULL);
	if (st == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Rlink has invalid ident: expiry value"));
	  return(-1);
	}
	else if (st == 0) {
	  log_msg((LOG_ERROR_LEVEL, "Rlink has invalid ident: expired"));
	  return(-1);
	}
	log_msg((LOG_TRACE_LEVEL, "Using Rlink ident: \"%s\"", rlink->ident));
  }
  else if ((p = strchr(r, (int) RNAME_IPTR_SEP_CHAR)) != NULL) {
	rlink->rname = r;
	*p++ = '\0';
	rlink->iptr = p;
  }
  else {
	rlink->rname = r;
	rlink->ident = NULL;
  }

  return(0);
}

/*
 * Scan through the RLINK directives, stop at the first one that
 * is enabled, and return its parsed and evaluated contents.
 */
Rlink *
acs_rlink_lookup(Kwv *kwv, Kwv *kwv_args, Credentials *cr)
{
  char *rname;
  Kwv_pair *v;
  Rlink *rlink;

  for ((v = conf_var(CONF_RLINK)) != NULL; v != NULL; v = v->next) {
	log_msg((LOG_TRACE_LEVEL, "Examining RLINK: \"%s\"", v->val));
	if ((rlink = acs_rlink_parse(v->val)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Ignoring invalid RLINK directive: \"%s\"",
			   v->val));
	  continue;
	}

	if ((rname = acs_rlink_is_enabled(rlink, kwv, kwv_args, cr)) != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "Enabled RLINK directive: \"%s\"", v->val));
	  if (acs_rname_parse(rlink, rname) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Found unacceptable Rname: \"%s\"", rname));
		return(NULL);
	  }
	  return(rlink);
	}
	log_msg((LOG_TRACE_LEVEL, "Ignoring disabled RLINK directive: \"%s\"",
			 v->val));
  }

  log_msg((LOG_DEBUG_LEVEL, "No RLINKs were enabled"));
  return(NULL);
}

/*
 * Get the rule that is associated with an Rlink (a request bearing an
 * Rname, which is a string, intended to be kept secret, that names the
 * rule that should be used for the request) and do an authorization check.
 * Return 1 if access is granted, 0 if access is denied, and -1 otherwise.
 */
int
acs_rlink_check(Rlink *rlink, Acs_environment *env, Credentials *selected,
				Acs_result *result)
{
  int st;
  char *buf;
  Acl_rule *acl;

  log_msg((LOG_TRACE_LEVEL, "Checking Rlink Rname=\"%s\"", rlink->rname));

  if ((buf = rlink_get(rlink->rule_vfs, rlink->rname)) == NULL) {
	result->errmsg = ds_xprintf("Could not load Rlink Rname=\"%s\"",
								rlink->rname);
	return(-1);
  }

  if (parse_xml_acl(buf, &acl) == -1) {
	result->errmsg = ds_xprintf("parse_xml_acl failed for \"%s\"",
								rlink->rule_vfs);
	return(-1);
  }

  /* Check if name attr in ACL matches the rname */
  if (acl->name != NULL && !streq(acl->name, rlink->rname)) {
	result->errmsg
	  = ds_xprintf("Rname mismatch in ACL, expected \"%s\" got \"%s\"",
				   rlink->rname, acl->name);
	return(-1);
  }

  free(buf);

  if ((st = check_acl_expired(acl->expires_expr, env)) == 1) {
	/* If this rule has expired, do not use it. */
	log_msg((LOG_TRACE_LEVEL, "Ignoring expired rlink"));
	return(0);
  }
  else if (st == -1) {
	log_msg((LOG_TRACE_LEVEL, "Rlink has invalid expires_expr attribute"));
	return(-1);
  }

  /*
   * Verify that the Rlink applies to this request, otherwise an Rlink
   * could be taken from one context and used in another.
   * All that's required is that one service element be applicable.
   */
  result->cr = selected;
  if ((st = select_acl_url(acl->services, env, &result->m)) == 1) {
	log_msg((LOG_DEBUG_LEVEL,
			 "rlink matched: %s", result->m.s->url_path));
	if (result->m.exact)
	  log_msg((LOG_DEBUG_LEVEL, "Exact rlink URL match"));
	result->m.acl = acl;
  }
  else if (st == 0)
	log_msg((LOG_WARN_LEVEL, "rlink did not match"));

  return(st);
}

int
acs_rlink_update(Rlink *rlink, char *uri, Credentials *selected)
{

  return(-1);
}

int
acs_rlink_delete(Rlink *rlink, char *uri, Credentials *selected)
{

  return(-1);
}
