/*
 * 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.
 ***************************************************************************/

/*
 * dacsacl - perform sanity checks on one or more ACL files and rebuilds
 *           ACL indexes
 *
 * Usage: dacsacl [dacsoptions] [-f file ...]
 *        dacsacl [dacsoptions] [acl-name ...]
 *
 * Each file argument is assumed to either be the name of a directory to be
 * checked for ACL files or an ACL file.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2013\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: aclcheck.c 2643 2013-02-27 19:59:24Z brachman $";
#endif

#include "dacs.h"
#include "dacs_api.h"

#include <sys/stat.h>

static MAYBE_UNUSED char *log_module_name = "dacsacl";

#ifndef PROG

static int nchecks = 0;

static void
use(void)
{

  fprintf(stderr,
		  "dacsacl %s [-build | -nobuild] [-vfs vfs-uri] [...] [op-spec] [acl-name ...]\n",
		  standard_command_line_usage);
  fprintf(stderr, "where op-spec is:\n");
  fprintf(stderr, "-convert\n");
  fprintf(stderr, "-f file [...]\n");
  fprintf(stderr, "-l | -s\n");
  fprintf(stderr, "-tc | -td # [...] | -tl | -tt\n");

  exit(1);
}

static char *
acl_index_pathname(Vfs_handle *h)
{
  char *p;

  p = ds_xprintf("%s/%s", h->sd->uri_str, ACL_INDEX_FILENAME);

  return(p);
}

/*
 *
 */
static int
check_revocations(void)
{
  char *buf;
  Dsvec *revocations;

  log_msg((LOG_DEBUG_LEVEL, "Checking revocations..."));
  if (vfs_lookup_item_type("revocations") != NULL) {
	if ((buf = get_revocations("revocations")) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot load revocation list"));
	  return(-1);
	}

	if ((revocations = parse_revocations(buf, 1)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Error parsing revocations"));
	  return(-1);
	}
  }

  log_msg((LOG_DEBUG_LEVEL, "Revocation list ok"));

  return(0);
}

/*
 * Check an ACL loaded from FILE.
 */
static int
check_file(char *file)
{
  char *buf;
  Parse_xml_error err;

  if (verbose_level)
	log_msg((LOG_INFO_LEVEL, "Checking: %s ...", file));

  if (load_file(file, &buf, NULL) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not get \"%s\"", file));
	return(-1);
  }

  nchecks++;
  if (check_acl(buf, &err) == -1) {
	log_msg((LOG_ERROR_LEVEL, "%s: %s (Line %d, Character offset %d)",
			file, err.mesg, err.line, err.pos));
	free(buf);
	return(-1);
  }

  free(buf);
  if (verbose_level)
	 log_msg((LOG_INFO_LEVEL, "File is OK"));

  return(0);
}

typedef enum Parse_xml_acs_state_code {
	ACS_PARSE_ACL_INDEX   = 1,
	ACS_PARSE_ACL_MAP     = 2,
	ACS_PARSE_SERVICES    = 3,
	ACS_PARSE_SERVICE     = 4
} Parse_xml_acs_state_code;

typedef struct Parse_xml_acs_state {
  Parse_xml_acs_state_code code;
  union {
	Acl_index *acl_index;
	Acl_map *acl_map;
	Services *services;
	Service *service;
  } 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_INDEX:
	s->object.acl_index = (Acl_index *) object;
	break;

  case ACS_PARSE_ACL_MAP:
	s->object.acl_map = (Acl_map *) object;
	break;

  case ACS_PARSE_SERVICES:
	s->object.services = (Services *) object;
	break;

  case ACS_PARSE_SERVICE:
	s->object.service = (Service *) object;
	break;

  default:
	/* XXX ??? */
	return(NULL);
  }

  return(s);
}

static Parse_attr_tab acl_index_attr_tab[] = {
  { "date_created",      NULL, ATTR_REQUIRED, NULL, 0 },
  { NULL,                NULL, ATTR_END, NULL, 0 }
};

static Parse_attr_tab acl_map_attr_tab[] = {
  { "status",            NULL, ATTR_REQUIRED, NULL, 0 },
  { "priority",          NULL, ATTR_REQUIRED, NULL, 0 },
  { "path",              NULL, ATTR_REQUIRED, NULL, 0 },
  { "expires_expr",      NULL, ATTR_IMPLIED,  NULL, 0 },
  { NULL,                NULL, ATTR_END, NULL, 0 }
};

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

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 }
};

static void
parse_xml_char_data_handler(void *userData, const XML_Char *s, int len)
{

  if (parse_xml_is_error(NULL))
    return;

}

static void
parse_xml_acl_index_element_start(void *data, const char *element,
								  const char **attr)
{
  char *el, *errmsg;
  Acl_index **adp;

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

  adp = (Acl_index **) data;

  if (parse_xml_is_error(NULL))
	return;

  if (streq(el, "acl_index")) {
	char *date_created_str;
	Acl_index *acl_index;

	acl_index = ALLOC(Acl_index);
	parse_xml_push(parse_xml_make_state(ACS_PARSE_ACL_INDEX,
										(void *) acl_index));
	acl_index->date_created = 0;
	acl_index->acl_map = NULL;

	acl_index_attr_tab[0].value = &date_created_str;

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

	if (strnum(date_created_str, STRNUM_TIME_T, &acl_index->date_created)
		== -1)
	  return;

	*adp = acl_index;
  }
  else if (streq(el, "acl_map")) {
	char *status_str, *priority_str;
	Acl_index *ad;
	Acl_map *am;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_ACL_INDEX)
	  return;
	ad = state->object.acl_index;
	am = ALLOC(Acl_map);
	parse_xml_push((void *)
				   parse_xml_make_state(ACS_PARSE_ACL_MAP, (void *) am));

	am->status = ACL_STATUS_ERROR;
	am->priority = 0;
	am->path = NULL;
	am->expires_expr = NULL;
	am->services = NULL;

	acl_map_attr_tab[0].value = &status_str;
	acl_map_attr_tab[1].value = &priority_str;
	acl_map_attr_tab[2].value = &am->path;
	acl_map_attr_tab[3].value = &am->expires_expr;
	if (parse_xml_attr(acl_map_attr_tab, attr, &errmsg) == -1) {
	  parse_xml_set_error(errmsg);
	  free(am);
	  return;
	}

	if (strcaseeq(status_str, "ENABLED"))
	  am->status = ACL_STATUS_ENABLED;
	else if (strcaseeq(status_str, "DISABLED"))
	  am->status = ACL_STATUS_DISABLED;
	else
	  return;

	if (strnum(priority_str, STRNUM_UI, &am->priority) == -1)
	  return;

	if (ad->acl_map == NULL)
	  ad->acl_map = dsvec_init(NULL, sizeof(Acl_map *));
	dsvec_add_ptr(ad->acl_map, am);
  }
  else if (streq(el, "services")) {
	Acl_map *acl_map;
	Services *ss;
	Parse_xml_acs_state *state;

	if (parse_xml_top((void **) &state) != PARSE_XML_OK
		|| state->code != ACS_PARSE_ACL_MAP)
	  return;
	acl_map = state->object.acl_map;
	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_map->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 {
	parse_xml_set_error(ds_xprintf("Unknown element: %s", el));
	return;
  }
}

static void
parse_xml_acl_index_element_end(void *data, const char *element)
{
  char *el, *err;
  Acl_index *ad;
  Parse_xml_acs_state *state;

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

  ad = *(Acl_index **) data;

  if (parse_xml_is_error(NULL))
	return;

  err = "";
  if (streq(el, "acl_index")) {
	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
	  goto err;
	if (state->code != ACS_PARSE_ACL_INDEX || parse_xml_is_not_empty())
	  goto err;
	free(state);
  }
  else if (streq(el, "acl_map")) {
	if (parse_xml_pop((void **) &state) == PARSE_XML_ERROR)
	  goto err;
	if (state->code != ACS_PARSE_ACL_MAP)
	  goto err;
	if (state->object.services == NULL)
	  goto err;
	free(state);
  }
  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 {
	err = ds_xprintf("Unknown element: %s", el);
	goto err;
  }

  return;

 err:
  parse_xml_set_error(err);
}

int
parse_xml_acl_index(char *str, Acl_index **acl_index)
{
  int st;
  Parse_xml_error err;
  XML_Parser p = XML_ParserCreateNS(NULL, XMLNS_SEP_CHAR);

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

  parse_xml_init("Acl_map", p);

  XML_SetElementHandler(p, parse_xml_acl_index_element_start,
						parse_xml_acl_index_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_index);

  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_index: line %d, pos %d",
             err.line, err.pos));
    if (err.mesg != NULL)
      log_msg((LOG_ERROR_LEVEL, "parse_xml_acl_index: %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);
}

static int
acl_map_compar(const void *ap, const void *bp)
{
  Acl_map *a, *b;

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

  if (a->priority != b->priority)
	return(a->priority - b->priority);

  return(strcmp(a->path, b->path));
}

/*
 * Return 1 if the index file exists, 0 if it does not, or -1 if the test failed.
 */
int
exists_acl_index(Vfs_handle *h)
{
  int st;

  st = vfs_exists(h, ACL_INDEX_FILENAME);
  if (st == -1) {
	log_msg((LOG_ERROR_LEVEL, "Error testing ACL index"));
	return(-1);
  }

  return(st);
}

/*
 * Read and parse the index file, and sort it by priority.
 */
Acl_index *
read_acl_index(Vfs_handle *h)
{
  char *buf;
  Acl_index *ad;

  ad = NULL;
  log_msg((LOG_DEBUG_LEVEL, "Reading index file \"%s\"", acl_index_pathname(h)));
  if (vfs_get(h, ACL_INDEX_FILENAME, (void *) &buf, NULL) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Error reading ACL index"));
	return(NULL);
  }

  if (parse_xml_acl_index(buf, &ad) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Error parsing ACL map"));
	return(NULL);
  }

  /* Put entries into evaluation order. */
  dsvec_sort(ad->acl_map, acl_map_compar);

  if (h != NULL) {
	if (ad == NULL) {
	  if (h->error_msg != NULL)
		log_msg((LOG_ERROR_LEVEL,
				 ds_xprintf("Could not read ACL map: %s", h->error_msg)));
	  else
		log_msg((LOG_ERROR_LEVEL, "Could not read ACL map"));
	}
  }

  return(ad);
}

/*
 * Get the ACL index for ITEM_TYPE.
 */
Acl_index *
get_acl_index(char *item_type)
{
  Acl_index *ad;
  Vfs_directive *vd;
  Vfs_handle *h;

  if ((vd = vfs_lookup_item_type(item_type)) == NULL) {
	fprintf(stderr, "Item type \"%s\" not found\n", item_type);
	return(NULL);
  }

  ad = NULL;
  h = NULL;
  if (vfs_open(vd, &h) != -1)
	ad = read_acl_index(h);

  if (h != NULL) {
	if (ad == NULL) {
	  if (h->error_msg != NULL)
		fprintf(stderr, "Could not read ACL map: %s\n", h->error_msg);
	  else
		fprintf(stderr, "Could not read ACL map\n");
	}
	vfs_close(h);
  }

  return(ad);
}

int
acl_index_xml_text(Ds *ds, Acl_index *ad)
{
  int i;
  Acl_map *am;

  ds_asprintf(ds, "<acl_index date_created=\"%lu\">\n", ad->date_created);

  for (i = 0; i < dsvec_len(ad->acl_map); i++) {
	am = (Acl_map *) dsvec_ptr_index(ad->acl_map, i);
	ds_asprintf(ds, "<acl_map ");
	ds_asprintf(ds, "status=\"%s\" priority=\"%d\" path=\"%s\"",
				am->status == ACL_STATUS_ENABLED ? "enabled" : "disabled",
				am->priority, am->path);
	if (am->expires_expr != NULL)
	  ds_asprintf(ds, " expires_expr=\"%s\">\n", am->expires_expr);
	else
	  ds_asprintf(ds, ">\n");

	acl_services_xml_text(ds, am->services);

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

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

  return(0);
}

typedef struct Service_ref {
  char *aclname;
  char *ref;
} Service_ref;

static Service_ref *
check_dup(Dsvec *dsv, char *ref)
{
  int i;
  Service_ref *sr;

  for (i = 0; i < dsvec_len(dsv); i++) {
	sr = (Service_ref *) dsvec_ptr_index(dsv, i);
	if (streq(sr->ref, ref))
	  return(sr);
  }

  return(NULL);
}

static int
make_index_from_af(Vfs_handle *h, int n, Acl_file **af, Ds *dir, int is_new)
{
  int i;
  char *buf;
  Acl_rule *acl;
  Service *s;
  Service_ref *sr;
  Dsvec *exprs, *patterns;

  exprs = dsvec_init(NULL, sizeof(char *));
  patterns = dsvec_init(NULL, sizeof(char *));

  ds_asprintf(dir, "<acl_index date_created=\"%lu\">\n", time(NULL));
  for (i = 0; i < n; i++) {
	if (vfs_get(h, af[i]->path, (void *) &buf, NULL) == -1) {
	  fprintf(stderr, "Error reading ACL \"%s\"\n", af[i]->path);
	  return(-1);
	}

	if (parse_xml_acl(buf, &acl) == -1) {
	  fprintf(stderr, "Error parsing ACL \"%s\"\n", af[i]->path);
	  return(-1);
	}

	/*
	 * If the rule includes a valid status, use it, otherwise guess
	 * based on the file name.
	 * A status attribute is required for the new format but is optional
	 * for the old format, so that if the status is missing we will assume
	 * that we are converting an old format rule file.
	 */
	if (is_new && acl->status == ACL_STATUS_ERROR) {
	  fprintf(stderr, "Invalid or missing status attribute in ACL \"%s\"",
			  af[i]->path);
	  return(-1);
	}
	if (acl->status == ACL_STATUS_ERROR)
	  acl->status = af[i]->status;

	ds_asprintf(dir, "<acl_map ");
	ds_asprintf(dir, "status=\"%s\" priority=\"%d\" path=\"%s\"",
				acl->status == ACL_STATUS_ENABLED ? "enabled" : "disabled",
				af[i]->sortkey, af[i]->path);
	if (acl->expires_expr != NULL)
	  ds_asprintf(dir, " expires_expr=\"%s\">\n", acl->expires_expr);
	else
	  ds_asprintf(dir, ">\n");

	acl_services_xml_text(dir, acl->services);

	for (s = acl->services->service; s != NULL; s = s->next) {
	  if (s->url_pattern != NULL) {
		if ((sr = check_dup(patterns, s->url_pattern)) != NULL) {
		  fprintf(stderr,
				  "Warning: duplicate url_pattern: \"%s\" in %s and %s\n",
				  sr->ref, sr->aclname, af[i]->aclname);
		}
		else {
		  sr = ALLOC(Service_ref);
		  sr->ref = s->url_pattern;
		  sr->aclname = af[i]->aclname;
		  dsvec_add_ptr(patterns, sr);
		}
	  }
	  else if (s->url_expr != NULL) {
		if ((sr = check_dup(exprs, s->url_expr)) != NULL) {
		  fprintf(stderr,
				  "Warning: duplicate url_expr: \"%s\" in %s and %s\n",
				  sr->ref, sr->aclname, af[i]->aclname);
		}
		else {
		  sr = ALLOC(Service_ref);
		  sr->ref = s->url_expr;
		  sr->aclname = af[i]->aclname;
		  dsvec_add_ptr(exprs, sr);
		}
	  }
	}

	ds_asprintf(dir, "</acl_map>\n\n");

	buf = acl_xml_text(acl);
	if (vfs_put(h, af[i]->path, (void *) buf, strlen(buf)) == -1) {
	  fprintf(stderr, "Warning: could not write \"%s/%s\"\n",
			  h->sd->uri_str, af[i]->path);
	  fprintf(stderr, "Rule was not updated\n");
	}
	else if (verbose_level)
	  fprintf(stderr, "Updated rule: %s/%s\n", h->sd->uri_str, af[i]->path);
  }
  ds_asprintf(dir, "</acl_index>\n");

  return(n);
}

/*
 * Obtain a rule's priority from its filename or pathname.
 */
static int
get_priority(char *path, unsigned int *priority)
{
  char *p;

  if ((p = strrchr(path, (int) '.')) == NULL)
	return(-1);
  if (strnum(p + 1, STRNUM_UI, priority) == -1)
	return(-1);

  return(0);
}

static int
convert(Vfs_handle *h, Ds *dir)
{
  int n;
  Acl_file **af;
  Dsvec *acls, *sorted_acls;

  if ((acls = get_acls(h)) == NULL) {
	fprintf(stderr, "Could not get ACL list\n");
	if (h->error_msg != NULL)
	  fprintf(stderr, "%s: %s", h->sd->uri_str, h->error_msg);
	return(-1);
  }

  sorted_acls = sort_acls(acls);
  n = dsvec_len(sorted_acls);
  af = (Acl_file **) dsvec_base(sorted_acls);

  if (make_index_from_af(h, n, af, dir, 0) == -1)
	return(-1);

  return(n);
}

static int
convert_acl_index(char *item_type)
{
  int n;
  Ds dir;
  Vfs_directive *vd;
  Vfs_handle *h;

  if ((vd = vfs_lookup_item_type(item_type)) == NULL) {
	fprintf(stderr, "Item type \"%s\" not found\n", item_type);
	return(-1);
  }

  h = NULL;
  if (vfs_open(vd, &h) == -1) {
	n = -1;
	goto done;
  }

  ds_init(&dir);
  if ((n = convert(h, &dir)) == -1)
	goto done;

  if (vfs_put(h, ACL_INDEX_FILENAME, ds_buf(&dir), ds_len(&dir) - 1) == -1) {
	fprintf(stderr, "Could not write %s\n", acl_index_pathname(h));
	n = -1;
	goto done;
  }

 done:
 
  if (h != NULL) {
	if (n == -1) {
	  fprintf(stderr, "Could not write ACL index: %s\n", acl_index_pathname(h));
	  if (h->error_msg != NULL)
		fprintf(stderr, "%s\n", h->error_msg);
	}
	vfs_close(h);
  }

  return(n);
}

Acl_rule *
read_indexed_acl(char *item_type, Acl_map *am, char **xml)
{
  char *buf;
  Acl_rule *acl;
  Vfs_directive *vd;
  Vfs_handle *h;

  if ((vd = vfs_lookup_item_type(item_type)) == NULL) {
	fprintf(stderr, "Item type \"%s\" not found\n", item_type);
	return(NULL);
  }

  h = NULL;
  if (vfs_open(vd, &h) == -1)
	return(NULL);

  if (vfs_get(h, am->path, (void *) &buf, NULL) == -1) {
	fprintf(stderr, "Error reading ACL \"%s\"\n", am->path);
	return(NULL);
  }

  if (parse_xml_acl(buf, &acl) == -1) {
	fprintf(stderr, "Error parsing ACL \"%s\"\n", am->path);
	return(NULL);
  }

  if (xml != NULL)
	*xml = buf;
  else
	free(buf);

  return(acl);
}

/*
 * Look up PATH in the rule index associated with ITEM_TYPE.
 * If ACL is non-NULL, also read and parse the rule file.
 * Return NULL if an error occurs.
 */
Acl_map *
lookup_indexed_acl(char *item_type, char *path, Acl_rule **acl)
{
  int i, n;
  Acl_index *ad;
  Acl_map *am;

  if ((ad = get_acl_index(item_type)) == NULL) {
	fprintf(stderr, "Could not get ACL index\n");
	return(NULL);
  }

  n = dsvec_len(ad->acl_map);
  for (i = 0; i < n; i++) {
	am = (Acl_map *) dsvec_ptr_index(ad->acl_map, i);
	if (streq(am->path, path))
	  return(am);
  }

  return(NULL);
}

/*
 * Add (or replace) ACL to the set of ACLs defined by ITEM_TYPE.
 */
int
add_indexed_acl(char *item_type, char *path, Acl_rule *acl)
{
  Acl_map *am;

  if ((am = lookup_indexed_acl(item_type, path, NULL)) == NULL)
	am = ALLOC(Acl_map);

  am->status = acl->status;
  if (get_priority(path, &am->priority) == -1)
	return(-1);
  am->path = path;
  am->expires_expr = acl->expires_expr;
  am->services = acl->services;

  /* Update the index and write the ACL. */


  return(-1);
}

/*
 * Remove an ACL file and de-index it.
 */
int
delete_indexed_acl(char *item_type, char *name)
{

  return(-1);
}

/*
 * Rename an existing ACL, updating its index entry.
 */
int
rename_indexed_acl(char *item_type, char *old_name, char *new_name)
{

  return(-1);
}

static int
build(Vfs_handle *h, Ds *dir)
{
  int n;
  Acl_file **af;
  Dsvec *acls, *sorted_acls;

  if ((acls = list_indexed_acls(h)) == NULL) {
	fprintf(stderr, "Could not get ACL list\n");
	if (h->error_msg != NULL)
	  fprintf(stderr, "%s: %s", h->sd->uri_str, h->error_msg);
	return(-1);
  }

  sorted_acls = sort_acls(acls);
  n = dsvec_len(sorted_acls);
  af = (Acl_file **) dsvec_base(sorted_acls);

  if (make_index_from_af(h, n, af, dir, 1) == -1)
	return(-1);
 
  return(n);
}

/*
 * Build a new index for a set of ACLs, creating or replacing any existing
 * index.
 */
int
build_acl_index(char *item_type)
{
  int n;
  Ds dir;
  Vfs_directive *vd;
  Vfs_handle *h;

  if ((vd = vfs_lookup_item_type(item_type)) == NULL) {
	fprintf(stderr, "Item type \"%s\" not found\n", item_type);
	return(-1);
  }

  h = NULL;
  if (vfs_open(vd, &h) == -1) {
	n = -1;
	goto done;
  }

  ds_init(&dir);
  if ((n = build(h, &dir)) == -1)
	goto done;

  if (vfs_put(h, ACL_INDEX_FILENAME, ds_buf(&dir), ds_len(&dir) - 1) == -1) {
	fprintf(stderr, "Could not write %s\n", acl_index_pathname(h));
	n = -1;
	goto done;
  }

 done:
 
  if (h != NULL) {
	if (n == -1) {
	  fprintf(stderr, "Could not write ACL index: %s\n", acl_index_pathname(h));
	  if (h->error_msg != NULL)
		fprintf(stderr, "%s\n", h->error_msg);
	}
	vfs_close(h);
  }

  return(n);
}

/*
 * Check ACL NAME, obtained from store H.
 * Return 0 if successful, -1 if the check failed.
 */
static int
check(Vfs_handle *h, char *name)
{
  char *buf;
  Parse_xml_error err;

  if (verbose_level)
	log_msg((LOG_INFO_LEVEL, "Checking: %s/%s ...", h->sd->naming_context, name));

  if (vfs_get(h, name, (void **) &buf, NULL) == -1) {
	if (verbose_level)
	  log_msg((LOG_ERROR_LEVEL, " %s", h->error_msg));

	return(-1);
  }

  nchecks++;
  if (check_acl(buf, &err) == -1) {
	if (err.line != -1)
	  log_msg((LOG_ERROR_LEVEL, "%s: %s (Line %d, Character offset %d)",
			   name, err.mesg, err.line, err.pos));
	else
	  log_msg((LOG_ERROR_LEVEL, "%s: %s", name, err.mesg));
	free(buf);

	return(-1);
  }

  free(buf);
  if (verbose_level)
	log_msg((LOG_INFO_LEVEL, "File is OK"));

  return(0);
}

/*
 * Check all ACLs in the store that are currently indexed.
 * Return the number of ACLs if successful, or -1 if any check failed.
 */
static int
check_indexed_acls(Vfs_handle *h)
{
  int i, n;
  Acl_index *ad;
  Acl_map **amp;

  if ((ad = read_acl_index(h)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not get ACL list"));
	if (h->error_msg != NULL)
	  log_msg((LOG_ERROR_LEVEL, "%s: %s", acl_index_pathname(h), h->error_msg));
	return(-1);
  }

  if ((n = dsvec_len(ad->acl_map)) == 0)
	return(0);

  amp = (Acl_map **) dsvec_base(ad->acl_map);
  for (i = 0; i < n; i++) {
	if (i > 0 && amp[i - 1]->priority == amp[i]->priority && verbose_level)
	  log_msg((LOG_INFO_LEVEL,
			   "(Note: duplicate priority level for \"%s\" and \"%s\")",
			   amp[i - 1]->path, amp[i]->path));
	if (amp[i]->status != ACL_STATUS_ENABLED)
	  log_msg((LOG_DEBUG_LEVEL,
			   "File not enabled, ignoring: \"%s\"", amp[i]->path));

	if (check(h, amp[i]->path) == -1)
	  return(-1);
  }

  return(n);
}

static int
show_acl_item_type(FILE *fp, char *item_type, int long_form)
{
  int i;
  Acl_index *ad;
  Acl_map *am;
  Vfs_directive *vd;
  Vfs_handle *h;

  log_msg((LOG_DEBUG_LEVEL, "Looking for item_type \"%s\"", item_type));
  if ((vd = vfs_lookup_item_type(item_type)) == NULL)
	return(1);

  log_msg((LOG_DEBUG_LEVEL, "Got URI: \"%s\"", vd->uri_str));
  if (vfs_open(vd, &h) == -1)
	return(-1);

  if ((ad = read_acl_index(h)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not get ACL list"));
	return(-1);
  }

  for (i = 0; i < dsvec_len(ad->acl_map); i++) {
	am = (Acl_map *) dsvec_ptr_index(ad->acl_map, i);

	if (long_form)
	  fprintf(fp, "%s/%s\n", h->sd->uri_str, am->path);
	else
	  fprintf(fp, "%s\n", am->path);
  }

  vfs_close(h);
  return(0);
}

/*
 * Check all ACLs (if FILES is NULL) associated with ITEM_TYPE, or only those in FILES
 * (a vector terminated by a NULL element).
 * Return 0 if successful, or -1 if an error occurred.
 * XXX FILES should probably be a Dsvec
 */
static int
check_acl_item_type(char *item_type, char **files, int require_index)
{
  int i, rc, have_index;
  Vfs_directive *vd;
  Vfs_handle *h;

  if ((vd = vfs_lookup_item_type(item_type)) == NULL)
	return(-1);

  if (vfs_open(vd, &h) == -1)
	return(-1);

  have_index = exists_acl_index(h);
  if (require_index) {
	if (have_index == 0) {
	  log_msg((LOG_ERROR_LEVEL, "Index does not exist: %s", acl_index_pathname(h)));
	  return(-1);
	}
	if (have_index == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot access index: %s", acl_index_pathname(h)));
	  return(-1);
	}
  }

  rc = 0;
  if (files == NULL || files[0] == NULL) {
	if (have_index) {
	  if (check_indexed_acls(h) == -1)
		rc = -1;
	}
  }
  else {
	for  (i = 0; files[i] != NULL; i++) {
	  if (check(h, files[i]) == -1)
		rc = -1;
	}
  }

  vfs_close(h);
  return(rc);
}

int
dacsacl_main(int argc, char **argv, int do_init, void *main_out)
{
  int do_build, i, long_form, n, st;
  int saw_build_flag, saw_nobuild_flag;
  int saw_acls_vfs, saw_dacs_acls_vfs;
  char *errmsg;

  if (dacs_init(DACS_UTILITY_OPT, &argc, &argv, NULL, &errmsg) == -1) {
	fprintf(stderr, "dacsacl: %s\n", errmsg);
	use();
	return(-1);
  }

  nchecks = 0;
  i = 1;

  /* It's probably wise to always do this unless it's suppressed. */
  do_build = 1;
  saw_acls_vfs = saw_dacs_acls_vfs = 0;
  saw_build_flag = saw_nobuild_flag = 0;

  while (i < argc) {
	if (streq(argv[i], "--"))
	  break;

	if (streq(argv[i], "-convert")) {
	  /* Convert from the old (non-indexed) format. */
	  if ((n = convert_acl_index(ITEM_TYPE_ACLS)) == -1) {
		fprintf(stderr, "Error converting \"%s\" ACLs\n", ITEM_TYPE_ACLS);
		return(-1);
	  }
	  if (!quiet_flag)
		fprintf(stderr, "Converted \"%s\": %d rules\n", ITEM_TYPE_ACLS, n);

#ifdef NOTDEF
	  /*
	   * Since dacs_acls should only consist of the standard set of ACLs and
	   * releases that use the new format ship with their standard set already
	   * converted, conversion of the standard set should be unnecessary.
	   */
	  if ((n = convert_acl_index(ITEM_TYPE_DACS_ACLS)) == -1) {
		fprintf(stderr, "Error converting \"%s\" ACLs\n", ITEM_TYPE_DACS_ACLS);
		return(-1);
	  }
	  if (!quiet_flag)
		fprintf(stderr, "Converted \"%s\": %d rules\n", ITEM_TYPE_DACS_ACLS, n);
#endif

	  return(0);
	}  

	if (streq(argv[i], "-tc")) {
	  /* List access tokens. */

	  fprintf(stderr, "Cleaning up access tokens...\n");
	  if (++i != argc) {
		use();
		/*NOTREACHED*/
	  }

	  if (acs_token_cleanup() == -1) {
		log_msg((LOG_ERROR_LEVEL, "Access token cleanup failed"));
		return(-1);
	  }

	  return(0);
	}

	if (streq(argv[i], "-td")) {
	  unsigned int num;
	  char *key;
	  Dsvec *dsv;
	  Kwv *kwv;

	  /* Delete access tokens. */
	  if ((dsv = access_token_list()) == NULL)
		return(-1);

	  while (++i < argc) {
		if (strnum(argv[i], STRNUM_UI, &num) == -1) {
		  log_msg((LOG_ERROR_LEVEL,
				   ds_xprintf("Invalid token number: \"%s\"", argv[i])));
		  continue;
		}
		num--;

		if ((kwv = (Kwv *) dsvec_ptr_index(dsv, num)) == NULL
			|| (key = kwv_lookup_value(kwv, "UNIQUE")) == NULL) {
		  log_msg((LOG_ERROR_LEVEL,
				   ds_xprintf("Invalid token number: \"%s\"", argv[i])));
		  continue;
		}

		log_msg((LOG_DEBUG_LEVEL, ds_xprintf("Delete key=\"%s\"", key)));
		if (access_token_delete(key) == -1)
		  log_msg((LOG_ERROR_LEVEL,
				   ds_xprintf("Deletion failed, key=\"%s\"", key)));
	  }

	  return(0);
	}

	if (streq(argv[i], "-tl")) {
	  int j;
	  Dsvec *dsv;
	  Kwv_iter *iter;
	  Kwv_pair *pair;

	  /* List access tokens. */

	  if (++i != argc) {
		use();
		/*NOTREACHED*/
	  }

	  if ((dsv = access_token_list()) == NULL)
		return(-1);

	  for (j = 0; j < dsvec_len(dsv); j++) {
		char *key;
		Kwv *kwv;

		kwv = (Kwv *) dsvec_ptr_index(dsv, j);
		key = kwv_lookup_value(kwv, "UNIQUE");
		printf("%4d. KEY=\"%s\"\n", j + 1, key);
		iter = kwv_iter_begin(kwv, NULL);
		while ((pair = kwv_iter_next(iter)) != NULL) {
		  if (streq(pair->name, "UNIQUE"))
			continue;
		  printf("      %s=\"%s\"\n", pair->name, pair->val);
		}

		kwv_iter_end(iter);
	  }

	  return(0);
	}

	if (streq(argv[i], "-tt")) {
	  char *container;
	  Vfs_handle *h;

	  /* Delete all access tokens. */
	  if (++i != argc) {
		use();
		/*NOTREACHED*/
	  }

	  log_msg((LOG_ERROR_LEVEL,
			   "Sorry, this operation is not implemented yet"));
	  return(-1);

	  if ((h = vfs_open_item_type("tokens")) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Item type \"tokens\" is not configured"));
		return(-1);
	  }

	  if (vfs_control(h, VFS_GET_CONTAINER, &container) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Could not determine container"));
		return(-1);
	  }
	  printf("Store container is: %s\n", container);
	  vfs_close(h);

	  return(0);
	}

	if ((long_form = streq(argv[i], "-l")) || streq(argv[i], "-s")) {
	  if (++i != argc) {
		fprintf(stderr, "Invalid argument\n");
		return(-1);
	  }

	  show_acl_item_type(stdout, ITEM_TYPE_ACLS, long_form);
	  show_acl_item_type(stdout, ITEM_TYPE_DACS_ACLS, long_form);

	  return(0);
	}

	if (streq(argv[i], "-build")) {
	  saw_build_flag = 1;
	  i++;
	}
	else if (streq(argv[i], "-nobuild")) {
	  saw_nobuild_flag = 1;
	  i++;
	}
	else if (streq(argv[i], "-vfs")) {
	  Vfs_directive *vd;

	  if (argv[++i] == NULL) {
		log_msg((LOG_ERROR_LEVEL, "VFS argument is missing"));
		return(-1);
	  }

	  /* A VFS spec... */
	  if ((vd = vfs_uri_parse(argv[i], NULL)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Invalid vfs_uri"));
		return(-1);
	  }
	  conf_set_directive(var_ns_lookup_kwv(dacs_conf->conf_var_ns, "Conf"),
						 "VFS", argv[i]);

	  if (streq(vd->item_type, ITEM_TYPE_ACLS))
		saw_acls_vfs = 1;
	  else if (streq(vd->item_type, ITEM_TYPE_DACS_ACLS))
		saw_dacs_acls_vfs = 1;

	  i++;
	}
	else if (streq(argv[i], "-f")) {
	  /* Check one or more files. */
	  if (++i == argc) {
		fprintf(stderr, "No file arguments were given\n");
		return(-1);
	  }

	  while (i < argc)
		check_file(argv[i++]);

	  break;
	}
	else if (streq(argv[i], "-warn")) {
	  extern int show_ignored_acl_names;

	  /* XXX not yet implemented... */
	  show_ignored_acl_names = 1;
	  i++;
	}
	else if (argv[i][0] == '-') {
	  use();
	  /*NOTREACHED*/
	}
	else
	  break;
  }

  if (saw_build_flag && saw_nobuild_flag) {
	use();
	/*NOTREACHED*/
  }

  if (saw_nobuild_flag)
	do_build = 0;
  else if (saw_build_flag)
	do_build = 1;

  if (do_build) {
	int n;

	if (saw_acls_vfs || !saw_dacs_acls_vfs) {
	  /* Build or rebuild indexes. */
	  if ((n = build_acl_index(ITEM_TYPE_ACLS)) == -1) {
		fprintf(stderr, "Error building \"%s\" ACLs\n", ITEM_TYPE_ACLS);
		return(-1);
	  }

	  if (!quiet_flag)
		fprintf(stderr, "Built index for \"%s\": %d rule%s\n",
				ITEM_TYPE_ACLS, n, (n == 1) ? "" : "s");
	}

	if (saw_dacs_acls_vfs || !saw_acls_vfs) {
	  if ((n = build_acl_index(ITEM_TYPE_DACS_ACLS)) == -1) {
		fprintf(stderr, "Error building \"%s\" ACLs\n", ITEM_TYPE_DACS_ACLS);
		return(-1);
	  }

	  if (!quiet_flag)
		fprintf(stderr, "Built index for \"%s\": %d rule%s\n",
				ITEM_TYPE_DACS_ACLS, n, (n == 1) ? "" : "s");
	}
  }

  /*
   * Check ACLs
   * If there are any non-flag arguments, they are the names of ACL files to
   * validate; otherwise, check all ACL files.
   */
  if (i < argc) {
	/* Check one or more ACL files in both of the virtual filestores. */
	if (!dacs_no_conf_flag || saw_acls_vfs) {
	  if ((st = check_acl_item_type(ITEM_TYPE_ACLS, &argv[i], 0)) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Error processing item_type \"%s\"",
				 ITEM_TYPE_ACLS));
		return(-1);
	  }
	  if (st == 1)
		log_msg((LOG_WARN_LEVEL, "No config for item_type \"%s\"",
				 ITEM_TYPE_ACLS));
	}
	else if (!quiet_flag)
	  log_msg((LOG_WARN_LEVEL, "Ignoring undefined item_type \"%s\"",
			   ITEM_TYPE_ACLS));

	if (!dacs_no_conf_flag || saw_dacs_acls_vfs) {
	  if ((st = check_acl_item_type(ITEM_TYPE_DACS_ACLS, &argv[i], 0)) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Error processing item_type \"%s\"",
				 ITEM_TYPE_DACS_ACLS));
		return(-1);
	  }
	  if (st == 1)
		log_msg((LOG_WARN_LEVEL, "No config for item_type \"%s\"",
				 ITEM_TYPE_DACS_ACLS));
	}
	else if (!quiet_flag)
	  log_msg((LOG_WARN_LEVEL, "Ignoring undefined item_type \"%s\"",
			   ITEM_TYPE_DACS_ACLS));
  }
  else {
	/*
	 * Check all ACLs that have been indexed.
	 */
	if (!dacs_no_conf_flag || saw_acls_vfs) {
	  if ((st = check_acl_item_type(ITEM_TYPE_ACLS, NULL, !saw_build_flag))
		  == -1) {
		log_msg((LOG_ERROR_LEVEL, "Error processing item_type \"%s\"",
				 ITEM_TYPE_ACLS));
		return(-1);
	  }
	  if (st == 1)
		log_msg((LOG_WARN_LEVEL, "No config for item_type \"%s\"",
				 ITEM_TYPE_ACLS));
	}
	else if (!quiet_flag)
	  log_msg((LOG_WARN_LEVEL, "Ignoring undefined item_type \"%s\"",
			   ITEM_TYPE_ACLS));

	if (!dacs_no_conf_flag || saw_dacs_acls_vfs) {
	  if ((st = check_acl_item_type(ITEM_TYPE_DACS_ACLS, NULL,
									!saw_build_flag)) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Error processing item_type \"%s\"",
				 ITEM_TYPE_DACS_ACLS));
		return(-1);
	  }
	  if (st == 1)
		log_msg((LOG_WARN_LEVEL, "No config for item_type \"%s\"",
				 ITEM_TYPE_DACS_ACLS));
	}
	else if (!quiet_flag)
	  log_msg((LOG_WARN_LEVEL, "Ignoring undefined item_type \"%s\"",
			   ITEM_TYPE_DACS_ACLS));
  }

  if (check_revocations() == -1)
	return(-1);

  if (nchecks == 0)
	log_msg((LOG_INFO_LEVEL, "No ACL files were checked"));
  else if (verbose_level)
	log_msg((LOG_INFO_LEVEL, "%d ACL file%s checked (OK)",
			nchecks, (nchecks == 1) ? " was" : "s were"));

  return(0);
}

#else

int
main(int argc, char **argv)
{
  int rc;

  if ((rc = dacsacl_main(argc, argv, 1, NULL)) == 0)
	exit(0);

  exit(1);
}
#endif
