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

/*
 * XXX
 * There should be a concise group membership format (many per file)
 *   name: member, member, ...
 */

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

#include <pwd.h>

#include "dacs_api.h"

static char *log_module_name = "simple_api";

extern Kwv_vartab conf_vartab[];
static char *hostname = NULL;

#ifdef NOTDEF
Groups *
make_simple_groups(char *vfs_uri, char *groupname)
{
  int i;
  char *gstr, *p;
  Dsvec *dsv;
  Groups *groups;
  Group_definition *gd;
  Group_member *gm;

  if ((gstr = load_simple_groups(vfs_uri, groupname)) == NULL)
	return(NULL);
  if ((dsv = strsplit(gstr, "\n", 0)) == NULL)
	return(NULL);
  

}

char *
load_simple_groups(char *vfs_uri)
{
  char *groupinfo;
  Groups *groups;
  Vfs_handle *h;

  if ((h = vfs_open_uri(vfs_uri)) == NULL)
	return(NULL);

  groupinfo = NULL;
  if (vfs_get(h, NULL, (void **) &groupinfo, NULL) == -1) {
	log_msg((LOG_DEBUG_LEVEL,
			 "Can't get simple groupfile \"%s\"", vfs_uri));
  }

  vfs_close(h);

  return(groupinfo);
}
#endif

int
set_federation(Kwv *kwv_conf, Kwv *kwv_dacs, char *name, char *domain)
{

  if (name != NULL) {
	if (!is_valid_federation_name(name))
	  return(-1);

	kwv_replace(kwv_conf, "FEDERATION_NAME", name);
	kwv_replace(kwv_dacs, "FEDERATION", name);
  }

  if (domain != NULL) {
	if (!looks_like_domain_name(domain))
	  return(-1);

	kwv_replace(kwv_conf, "FEDERATION_DOMAIN", domain);
  }

  return(0);
}

int
set_jurisdiction(Kwv *kwv_conf, Kwv *kwv_dacs, char *name)
{

  if (!is_valid_jurisdiction_name(name))
	return(-1);

  kwv_replace(kwv_conf, "JURISDICTION_NAME", name);
  kwv_replace(kwv_dacs, "JURISDICTION", name);
  return(0);
}

static char *
make_name_valid(char *name)
{
  char *errmsg, *n, *new_name;

  n = strtoupper(name);
  if (is_valid_federation_name(n) && is_valid_jurisdiction_name(n))
	return(n);

  if ((new_name = strregexsub(n, "\\.", "-", 0, 1, &errmsg)) == NULL
	  || *new_name == '\0')
	return(n);

  return(new_name);
}

int
get_conf(char **host)
{

  *host = strdup(hostname);

  return(0);
}

int
get_conf_from_host(char *host, char **hname, char **fname, char **jname,
				   char **fdomain)
{
  char *d, *h, *p;

  if (host == NULL) {
	if (hostname == NULL) {
	  hostname = (char *) malloc(MAXHOSTNAMELEN);
	  if (gethostname(hostname, MAXHOSTNAMELEN) == -1)
		return(-1);
	}
	h = strdup(hostname);
  }
  else
	h = strdup(host);

  if ((p = index(h, '.')) != NULL) {
	*p++ = '\0';
	d = p;
  }
  else {
	int i;
	struct hostent *he;

	if (host != NULL) {
	  /* This must be a FQDN. */
	  return(-1);
	}

	/*
	 * Since gethostname(3) didn't return a FQDN, look at its aliases.
	 * We used to try getdomainname(3) but it will return a bogus string
	 * (e.g., "(none)") instead of an indication that no domain name has
	 * be configured.
	 * If nothing is found, use a default string.
	 */
	p = NULL;
	if ((he = gethostbyname(hostname)) != NULL) {
	  if (he->h_name == NULL || (p = index(he->h_name, '.')) == NULL) {
		for (i = 0; he->h_aliases[i] != NULL; i++) {
		  if ((p = index(he->h_aliases[i], '.')) != NULL) {
			hostname = strdup(he->h_aliases[i]);
			break;
		  }
		}
	  }
	  else if (he->h_name != NULL)
		hostname = strdup(he->h_name);
	}

	if (p == NULL) {
	  h = hostname;
	  d = DEFAULT_FEDERATION_DOMAIN;
	}
	else {
	  h = strdup(hostname);
	  p = index(h, '.');
	  *p++ = '\0';
	  d = p;
	}
  }

  /*
   * We have:
   *   hostname: the selected FQDN
   *   h: the left-most component of the selected FQDN, or the hostname
   *   d: the remaining component(s) of the selected FQDN
   */

  if (hname != NULL)
	*hname = strdup(hostname);
  if (fname != NULL)
	*fname = make_name_valid(d);
  if (jname != NULL)
	*jname = make_name_valid(h);
  if (fdomain != NULL)
	*fdomain = strtoupper(d);

  return(0);
}

/*
 * Establish a federation and jurisdiction name from HOST (a FQDN, if given),
 * or the hostname or one of its aliases.
 */
int
set_conf_from_host(Kwv *kwv_conf, Kwv *kwv_dacs, char *host)
{
  char *fdomain, *fname, *jname;

  if (get_conf_from_host(host, NULL, &fname, &jname, &fdomain) == -1)
	return(-1);

  if (set_jurisdiction(kwv_conf, kwv_dacs, jname) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Cannot use jurisdiction name \"%s\"", jname));
	return(-1);
  }

  if (set_federation(kwv_conf, kwv_dacs, fname, fdomain) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Cannot use federation name \"%s\"", fname));
	return(-1);
  }

  return(0);
}

char *
ident_username(Credentials *cr)
{
  Ds ds;

  ds_init(&ds);
  if (cr->federation != NULL)
	ds_asprintf(&ds, "%s%c%c", cr->federation,
				JURISDICTION_NAME_SEP_CHAR, JURISDICTION_NAME_SEP_CHAR);
  if (cr->home_jurisdiction != NULL)
	ds_asprintf(&ds, "%s%c",
				cr->home_jurisdiction, JURISDICTION_NAME_SEP_CHAR);
  ds_asprintf(&ds, "%s", cr->username);

  return(ds_buf(&ds));
}

#ifndef IDENT_QUOTE
#define IDENT_QUOTE		"\""
#endif

#ifndef IDENT_LBRACE
#define IDENT_LBRACE	"{"
#endif

#ifndef IDENT_RBRACE
#define IDENT_RBRACE	"}"
#endif

char *
make_ident(char *user, char *groups, char *attrs, time_t expires_secs,
		   char *ip_addr)
{
  Ds ds;

  if (user == NULL)
	return(NULL);

  ds_init(&ds);
  ds_asprintf(&ds, "%s", IDENT_LBRACE);
  ds_asprintf(&ds, "u=%s%s%s",
			  IDENT_QUOTE, user, IDENT_QUOTE);
  if (groups != NULL && *groups != '\0')
	ds_asprintf(&ds, ",g=%s%s%s",
				IDENT_QUOTE, groups, IDENT_QUOTE);
  if (attrs != NULL && *attrs != '\0')
	ds_asprintf(&ds, ",a=%s%s%s",
				IDENT_QUOTE, attrs, IDENT_QUOTE);
  if (ip_addr != NULL && *ip_addr != '\0')
	ds_asprintf(&ds, ",ip=%s%s%s",
				IDENT_QUOTE, ip_addr, IDENT_QUOTE);
  if (expires_secs != 0)
	ds_asprintf(&ds, ",e=%s%lu%s",
				IDENT_QUOTE, expires_secs, IDENT_QUOTE);

  ds_asprintf(&ds, "%s", IDENT_RBRACE);

  return(ds_buf(&ds));
}

char *
make_ident_from_credentials(Credentials *cr)
{
  char *ident;

  if (cr == NULL)
	return(NULL);

  ident = make_ident(ident_username(cr), cr->role_str, NULL, cr->expires_secs,
					 cr->ip_address);

  return(ident);
}

char *
make_idents_from_credentials(Credentials *credentials)
{
  char *ident;
  Credentials *cr;
  Ds ds;

  ds_init(&ds);
  for (cr = credentials; cr != NULL; cr = cr->next) {
	ident = make_ident_from_credentials(cr);
	if (cr == credentials)
	  ds_set(&ds, ident);
	else
	  ds_asprintf(&ds, ", %s", ident);
  }

  return(ds_buf(&ds));
}

/*
 * The syntax of STR is:
 *   ident = '{' kwv-list '}' | user
 *   kwv-list = kwv | kwv ',' kwv-list
 *   kwv = kwv-user| kwv-group | kwv-attr
 *   kwv-user  = 'u=' [q] user [q]
 *   kwv-group = 'g=' [q] groups [q]
 *   kwv-attr  = 'a=' [q] attr [q]
 *
 *   user = username | jname ':' username
 *                   | fname '::' jname ':' username
 *   groups = group [, group]*
 *   group = groupname | role-descriptor
 *   attr = any-alphabetic
 * where user must be a valid DACS username (it cannot contain a '=' or
 * ',' character, for example),
 * q is an optional (matched) quote character,
 * and whitespace may optionally precede most tokens.
 *
 * Examples:
 *     { u="bobo" }
 *     {u="auggie", g="dogs,self"}
 *     {u="harley", g="dogs,bluemerle"}
 */
int
parse_ident_string(char *str, Simple_user *suser)
{
  char *a, *e, *g, *ip, *u;
  Kwv *kwv;
  static Kwv_conf conf = {
	"=", "\"'", " \t", KWV_CONF_DEFAULT, " \t,", 10, NULL, NULL
  };

  if (strchr(str, (int) '=') != NULL) {
	char *ident;

	if (*str != '{' || strsuffix(str, strlen(str), "}") == NULL)
	  return(-1);

	ident = strdup(str + 1);
	strtrim(ident, "}", 1);

	if ((kwv = kwv_make_sep(NULL, ident, &conf)) == NULL)
	  return(-1);

	if ((u = kwv_lookup_value(kwv, "u")) == NULL)
	  return(-1);
	g = kwv_lookup_value(kwv, "g");
	a = kwv_lookup_value(kwv, "a");
	e = kwv_lookup_value(kwv, "e");
	ip = kwv_lookup_value(kwv, "ip");
  }
  else {
	u = str;
	g = NULL;
	a = NULL;
	e = NULL;
	ip = NULL;
  }

  if (!is_valid_username(u) && parse_dacs_name(u, NULL) != DACS_USER_NAME) {
	log_msg((LOG_ERROR_LEVEL, "Invalid DACS username: \"%s\"", u));
	return(-1);
  }

  if (ip != NULL && !is_ip_addr(ip, NULL)) {
	log_msg((LOG_ERROR_LEVEL, "Invalid IP address: \"%s\"", ip));
	return(-1);
  }

  if (e != NULL) {
	if (!is_signed_digit_string(e)) {
	  time_t et;

	  /* Allow a date in the past... */
	  if (utc_date_string_to_secs(&et, e) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Invalid expiry date: \"%s\"", e));
		return(-1);
	  }
	}
  }

  if (suser != NULL) {
	suser->user = u;
	suser->groups = g;
	suser->attrs = a;
	suser->expires = e;
	suser->ip_addr = ip;
  }

  return(0);
}

/*
 * Convert a concise identity into credentials.
 * It is possible to return credentials that have expired.
 */
Credentials *
make_credentials_from_ident(char *ident, Auth_style auth_style, Kwv *kwv_conf)
{
  char *fname, *expires, *jname, *ip_addr, *role_str, *uname;
  Credentials *cr;
  DACS_name dn;
  DACS_name_type nt;
  Simple_user suser;

  if (parse_ident_string(ident, &suser) == -1)
	return(NULL);

  fname = kwv_lookup_value(kwv_conf, "FEDERATION_NAME");
  jname = kwv_lookup_value(kwv_conf, "JURISDICTION_NAME");

  if ((nt = parse_dacs_name(suser.user, &dn)) == DACS_USER_NAME) {
	if (dn.federation != NULL)
	  fname = dn.federation;
	if (dn.jurisdiction != NULL)
	  jname = dn.jurisdiction;
	uname = dn.username;
  }
  else if (nt == DACS_UNKNOWN_NAME && is_valid_username(suser.user))
	uname = suser.user;
  else
	return(NULL);

  if (suser.groups != NULL) {
	if (!is_valid_role_str(suser.groups))
	  return(NULL);
	role_str = suser.groups;
  }
  else
	role_str = "";

  if (suser.ip_addr != NULL)
	ip_addr = suser.ip_addr;
  else {
	if ((ip_addr = getenv("REMOTE_ADDR")) == NULL)
	  ip_addr = DEFAULT_IP_ADDR;
  }

  if (suser.expires != NULL && !is_signed_digit_string(suser.expires)) {
	time_t et, now;

	now = time(NULL);
	/* Allow a date in the past... */
	if (utc_date_string_to_secs(&et, suser.expires) == -1)
	  return(NULL);
	expires = ds_xprintf("%lu", et - now);
  }
  else if ((expires = conf_val(CONF_AUTH_CREDENTIALS_DEFAULT_LIFETIME_SECS))
		   == NULL) {
	/* Make 'em last a long time, say a year. */
	expires = "31557600";
  }

  cr = make_credentials(fname, jname, uname, ip_addr, role_str,
						expires, auth_style, AUTH_VALID_FOR_ACS,
						NULL, NULL);

  if (suser.attrs != NULL) {
	if (strchr(suser.attrs, (int) 'a') != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "Ident \"%s\" is an admin", suser.user));
	  kwv_conf->dup_mode = KWV_ALLOW_DUPS;
	  kwv_add(kwv_conf, "ADMIN_IDENTITY",
			  auth_identity(NULL, cr->home_jurisdiction, cr->username, NULL));
	  kwv_conf->dup_mode = KWV_NO_DUPS;
	}
  }

  return(cr);
}

/*
 * Compare two identities in the concise syntax.
 * Return 1 if they're equivalent (same federation/jurisdiction/username),
 * 0 if not, and -1 if either name is invalid.
 * Take the name comparison mode into account.
 */
int
compare_idents(char *ident1, char *ident2, Kwv *kwv_conf)
{
  char *fname1, *jname1, *uname1;
  char *fname2, *jname2, *uname2;
  DACS_name dn1, dn2;
  DACS_name_type nt1, nt2;
  DACS_name_cmp cmp_mode;
  Simple_user suser1, suser2;

  if (parse_ident_string(ident1, &suser1) == -1)
	return(-1);

  fname2 = fname1 = kwv_lookup_value(kwv_conf, "FEDERATION_NAME");
  jname2 = jname1 = kwv_lookup_value(kwv_conf, "JURISDICTION_NAME");

  if ((nt1 = parse_dacs_name(suser1.user, &dn1)) == DACS_USER_NAME) {
	if (dn1.federation != NULL)
	  fname1 = dn1.federation;
	if (dn1.jurisdiction != NULL)
	  jname1 = dn1.jurisdiction;
	uname1 = dn1.username;
  }
  else if (nt1 == DACS_UNKNOWN_NAME && is_valid_username(suser1.user))
	uname1 = suser1.user;
  else
	return(-1);

  if (parse_ident_string(ident2, &suser2) == -1)
	return(-1);

  if ((nt2 = parse_dacs_name(suser2.user, &dn2)) == DACS_USER_NAME) {
	if (dn2.federation != NULL)
	  fname2 = dn2.federation;
	if (dn2.jurisdiction != NULL)
	  jname2 = dn2.jurisdiction;
	uname2 = dn2.username;
  }
  else if (nt2 == DACS_UNKNOWN_NAME && is_valid_username(suser2.user))
	uname2 = suser2.user;
  else
	return(-1);

  cmp_mode = get_name_cmp_mode();
  if (name_eq(uname1, uname2, cmp_mode)
	  && name_eq(jname1, jname2, cmp_mode)
	  && name_eq(fname1, fname2, cmp_mode))
	return(1);

  return(0);
}

/*
 * Convert the simple string representation for an identity into the
 * internal form.
 * Syntax is:
 *   ident-seq = '{' ident '}' [[',' | ', '] {' ident '}' ]*
 *
 * Note that an expired identity is deleted.
 */
Dsvec *
make_credentials_from_idents(char *str, Auth_style auth_style, Kwv *kwv_conf)
{
  int braces, sep, st;
  char *p;
  Credentials *head, *cr, *prev;
  Ds ds;
  Dsvec *dsv;

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

  p = str;
  head = prev = NULL;
  dsv = dsvec_init(NULL, sizeof(Credentials *));
  while (*p != '\0') {
	while (*p == ' ' || *p == '\t')
	  p++;

	if (*p == '{') {
	  sep = '}';
	  braces = 1;
	}
	else {
	  sep = ',';
	  braces = 0;
	}

	ds_init(&ds);
	while (*p != '\0' && *p != sep) {
	  if (*p == '\\' && (*(p + 1) == '\\' || *(p + 1) == sep))
		p++;
	  ds_appendc(&ds, (int) *p++);
	}

	if (braces) {
	  if (*p != sep)
		return(NULL);
	  ds_appendc(&ds, (int) *p++);
	}
	else if (*p != '\0' && *p != sep)
	  return(NULL);

	if (*p == ',')
	  p++;

	ds_appendc(&ds, (int) '\0');
	log_msg((LOG_DEBUG_LEVEL, "Converting ident: %s", ds_buf(&ds)));
	if ((cr = make_credentials_from_ident(ds_buf(&ds), auth_style, kwv_conf))
		== NULL)
	  return(NULL);

	/*
	 * XXX This may not be quite right, but it is easiest to weed out
	 * expired credentials now.
	 */
	st = verify_expiration(cr->expires_secs, NULL);
	if (st == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid ident: expiry value"));
	  continue;
	}
	else if (st == 0) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid ident: expired"));
	  continue;
	}

	dsvec_add_ptr(dsv, cr);

	log_msg((LOG_TRACE_LEVEL, "Added credentials \"%s\"",
			 auth_identity_from_credentials(cr)));
	if (cr->role_str != NULL)
	  log_msg((LOG_TRACE_LEVEL, "with roles: \"%s\"", cr->role_str));

	if (head == NULL)
	  head = cr;
	else
	  prev->next = cr;
	prev = cr;
  }

  return(dsv);
}

/*
 * Process command line flags related to defining identities, groups,
 * and related context.
 */
int
set_ident_config(int *argcp, char **argv, Kwv *kwv_dacs, Kwv *kwv_conf,
				 char **identsp, char **errmsg)
{
  int i, n;
  char *idents, *p, *r;
  Vfs_directive *vd;
  static int add_unix_groups = 0;
  static char *field_sep = NULL;
  static char *ident_flags = NULL;
  static char *roles_vfs = NULL;;

  idents = *identsp;

  for (i = *argcp; argv[i] != NULL; i++) {
	if (streq(argv[i], "-admin")) {
	  /*
	   * Each identity that follows is an ADMIN_IDENTITY
	   * (it must be added to kwv_conf with duplicates enabled).
	   */
	  ident_flags = "a";
	}
	else if (streq(argv[i], "-F")) {
	  if (argv[++i] == NULL) {
		*errmsg = "Field separator character expected after -F";
		return(-1);
	  }
	  field_sep = argv[i];
	  if (strlen(field_sep) != 1) {
		*errmsg = "Invalid field separator character";
		return(-1);
	  }
	}
	else if (streq(argv[i], "-fd")) {
	  if (argv[++i] == NULL) {
		*errmsg = "Federation domain expected after -fd";
		return(-1);
	  }
	  if (set_federation(kwv_conf, kwv_dacs, NULL, argv[i]) == -1) {
		*errmsg = "Invalid federation domain name";
		return(-1);
	  }
	}
	else if (streq(argv[i], "-fh")) {
	  if (argv[++i] == NULL) {
		*errmsg = "Hostname expected after -fh";
		return(-1);
	  }
	  if (set_conf_from_host(kwv_conf, kwv_dacs, argv[i]) == -1) {
		*errmsg = "Could not use hostname";
		return(-1);
	  }
	}
	else if (streq(argv[i], "-fj")) {
	  if (argv[++i] == NULL) {
		*errmsg = "Jurisdiction name expected after -fj";
		return(-1);
	  }
	  if (set_jurisdiction(kwv_conf, kwv_dacs, argv[i]) == -1) {
		*errmsg = "Invalid jurisdiction name";
		return(-1);
	  }
	}
	else if (streq(argv[i], "-fn")) {
	  if (argv[++i] == NULL) {
		*errmsg = "Federation name expected after -fn";
		return(-1);
	  }
	  if (set_federation(kwv_conf, kwv_dacs, argv[i], NULL) == -1) {
		*errmsg = "Invalid federation name";
		return(-1);
	  }
	}
	else if (streq(argv[i], "-i")) {
	  char *ident, *new_groups, *roles;
	  Simple_user suser;

	  /*
	   * Either a simple identity is expected or a simple name.
	   * It is not expected to be locally known.
	   */
	  if (argv[++i] == NULL) {
		*errmsg = "Identity expected after -i";
		return(-1);
	  }

	  ident = argv[i];
	  if (*ident == '\0')
		continue;

	  if (parse_ident_string(ident, &suser) == -1) {
		*errmsg = "Invalid identity for -i flag";
		return(-1);
	  }

	  new_groups = suser.groups;
	  roles = get_role_string(roles_vfs, ITEM_TYPE_ROLES, suser.user,
							  field_sep);
	  if (roles != NULL) {
		if (suser.groups == NULL)
		  new_groups = roles;
		else
		  new_groups = ds_xprintf("%s,%s", suser.groups, roles);
	  }

	  if (new_groups != NULL)
		ident = make_ident(suser.user, new_groups, ident_flags, 0,
						   suser.ip_addr);

	  if (idents == NULL)
		idents = ident;
	  else
		idents = ds_xprintf("%s %s", idents, ident);
	}
	else if (streq(argv[i], "-icgi") || streq(argv[i], "-icgig")) {
	  char *ident, *remote_user, *roles, *username;
	  DACS_name_type nt;
	  DACS_name dn;

	  /* If REMOTE_USER is set, use it, otherwise do nothing. */
	  if ((remote_user = getenv("REMOTE_USER")) == NULL)
		continue;

	  if (*remote_user == '\0')
		continue;

	  nt = parse_dacs_name(remote_user, &dn);
	  if (nt == DACS_UNKNOWN_NAME && is_valid_username(remote_user))
		username = remote_user;
	  else if (nt == DACS_USER_NAME)
		username = dn.username;
	  else {
		log_msg((LOG_INFO_LEVEL,
				 "REMOTE_USER is invalid: \"%s\"\n", remote_user));
		continue;
	  }

	  roles = NULL;
	  if (streq(argv[i], "-icgig"))
		roles = get_role_string(roles_vfs, ITEM_TYPE_ROLES, username,
								field_sep);

	  ident = make_ident(remote_user, roles, ident_flags, 0, NULL);

	  if (idents == NULL)
		idents = ident;
	  else
		idents = ds_xprintf("%s %s", idents, ident);
	}
	else if (streq(argv[i], "-il") || streq(argv[i], "-ilg")) {
	  int add_groups;
	  char *ident, *new_groups, *new_ident, *roles, *user;
	  char *unix_roles;
	  struct passwd *pw;
	  DACS_name_type nt;
	  DACS_name dn;
	  Simple_user suser;

	  if (add_unix_groups || streq(argv[i], "-ilg"))
		add_groups = 1;
	  else
		add_groups = 0;

	  if (argv[++i] == NULL) {
		*errmsg = "Identity expected after -i";
		return(-1);
	  }

	  /*
	   * A locally known username is expected, either in the simple
	   * identity syntax ({u="blah"}), as a DACS name (FOO:baz), or
	   * a simple name ("bobo").
	   * Verify that the username is known.
	   * Add its group membership to the list of roles if so requested.
	   */
	  ident = argv[i];
	  if (parse_ident_string(ident, &suser) == -1) {
		*errmsg = "Invalid identity";
		return(-1);
	  }

	  nt = parse_dacs_name(ident, &dn);
	  if (nt == DACS_USER_NAME)
		user = dn.username;
	  else
		user = suser.user;

	  if ((pw = getpwnam(user)) == NULL) {
		*errmsg = ds_xprintf("getpwnam failed: %s", strerror(errno));
		return(-1);
	  }

	  unix_roles = NULL;
	  if (add_groups) {
		get_unix_roles(user, &unix_roles);
		if (suser.groups == NULL)
		  new_groups = unix_roles;
		else
		  new_groups = ds_xprintf("%s,%s", suser.groups, unix_roles);
	  }
	  else
		new_groups = suser.groups;

	  roles = get_role_string(roles_vfs, ITEM_TYPE_ROLES, user, field_sep);
	  if (roles != NULL) {
		if (new_groups == NULL)
		  new_groups = roles;
		else
		  new_groups = ds_xprintf("%s,%s", new_groups, roles);
	  }

	  new_ident = make_ident(user, new_groups, ident_flags, 0, NULL);

	  if (idents == NULL)
		idents = new_ident;
	  else
		idents = ds_xprintf("%s, %s", idents, new_ident);
	}
	else if (streq(argv[i], "-ieuid") || streq(argv[i], "-ieuidg")
			 || streq(argv[i], "-iuid") || streq(argv[i], "-iuidg")) {
	  char *roles, *user, *unix_roles;
	  struct passwd *pw;
	  uid_t uid;

	  if (streq(argv[i], "-ieuid") || streq(argv[i], "-ieuidg"))
		uid = geteuid();
	  else
		uid = getuid();

	  if ((pw = getpwuid(uid)) == NULL) {
		*errmsg = "getpwuid failed";
		return(-1);
	  }
  
	  user = pw->pw_name;
	  unix_roles = NULL;
	  if (strsuffix(argv[i], strlen(argv[i]), "g") != NULL)
		get_unix_roles(user, &unix_roles);

	  roles = unix_roles;
	  r = get_role_string(roles_vfs, ITEM_TYPE_ROLES, user, field_sep);
	  if (r != NULL) {
		if (unix_roles == NULL)
		  roles = r;
		else
		  roles = ds_xprintf("%s,%s", unix_roles, r);
	  }

	  if (idents == NULL)
		idents = make_ident(user, roles, ident_flags, 0, NULL);
	  else
		idents = ds_xprintf("%s, %s", idents,
							make_ident(user, roles, ident_flags, 0, NULL));
	}
	else if (streq(argv[i], "-lg"))
	  add_unix_groups = 1;
	else if (streq(argv[i], "-roles")) {
	  char *h;

	  if (argv[++i] == NULL) {
		*errmsg = "roles_vfs expected after -roles";
		return(-1);
	  }
	  if (roles_vfs != NULL) {
		*errmsg = "Only one -roles flag is allowed";
		return(-1);
	  }

	  if ((vd = vfs_uri_parse(argv[i], NULL)) == NULL) {
		if (argv[i][0] != '/') {
		  *errmsg = "Invalid roles_vfs";
		  return(-1);
		}

		get_conf(&h);
		p = directory_name_interpolate(argv[i], h, DEFAULT_PORT_NUMBER);
		if (p == NULL)
		  p = argv[i];
		roles_vfs = ds_xprintf("[roles]dacs-kwv-fs:%s", p);
	  }
	  else
		roles_vfs = argv[i];
	}
	else
	  break;
  }

  n = i - *argcp;
  *argcp = i;
  *identsp = idents;

  return(n);
}

/*
 * Initialize the evaluation environment.
 * Establish the DACS namespace for variables.
 * We expect acs_init_env() to be called to further instantiate the
 * evaluation context, including the Args, Conf, and Env namespaces.
 * The Conf namespace context will come from dacs_conf->conf_var_ns.
 */
Acs_environment *
init_env(char *object, Uri *uri, Kwv *kwv_conf, Kwv *kwv_dacs)
{
  char *url;
  Acs_environment *env;
  Var_ns *conf_var_ns;

  env = acs_new_env(NULL);
  env->uri = uri;
  if (*object == '/')
	env->url = ds_xprintf("file://%s", object);
  else
	env->url = strdup(object);

  conf_var_ns = var_ns_create("Conf", kwv_conf);

  if (kwv_dacs == NULL)
	kwv_dacs = kwv_init(10);
  kwv_merge(kwv_dacs, kwv_dacsoptions, KWV_REPLACE_DUPS);

  var_ns_new(&env->namespaces, "DACS", kwv_dacs);

  dacs_conf = new_dacs_conf(NULL, NULL, NULL, conf_vartab, conf_var_ns);

  return(env);
}

int
add_vfs_uri(Kwv *kwv_conf, char *vfs_uri)
{
  int old_prepend_flag;

  kwv_conf->dup_mode = KWV_ALLOW_DUPS;
  old_prepend_flag = kwv_conf->prepend_flag;
  kwv_conf->prepend_flag = 1;
  kwv_add(kwv_conf, "VFS", vfs_uri);
  kwv_vartab_init(kwv_conf, conf_vartab, NULL, NULL);
  kwv_conf->dup_mode = KWV_NO_DUPS;
  kwv_conf->prepend_flag = old_prepend_flag;

  return(0);
}

/*
 * RULES is a space-separated list of rulesets to examine.
 * IDENTS is a (possibly NULL) space-separated list of identities.
 * A NULL IDENTS indicates an unauthenticated user.
 *
 * Return 1 if access to OBJECT should be granted to
 * IDENTS according to RULES and a context (KWV_CONF and KWV_DACS),
 * 0 if it should not, and -1 if an error occurred.
 * If any constraints are associated with the rule that grants access they
 * are returned through CONSTRAINT.
 * If access was denied with a redirection BY_SIMPLE_REDIRECT, set
 * REDIRECT.
 */
int
check_object_access(char *rules, char *groups, char *idents,
					char *object, Kwv *kwv_conf,
					Kwv *kwv_dacs, Acs_result *result, char **constraint)
{
  int st;
  char *errmsg, *host, *object_url, *p, *port, *r, *str;
  struct hostent *he;
  struct in_addr addr;
  Acl_file *best;
  Acs_environment *env;
  Credentials *cr, *credentials;
  Dsvec *dsv;
  Kwv *kwv_args;
  Uri *uri;

  if (idents != NULL) {
	if ((dsv = make_credentials_from_idents(idents, AUTH_STYLE_GENERATED,
											kwv_conf)) == NULL)
	  return(-1);
	/* Although we get a vector back, the list will be linked as required. */
	credentials = (Credentials *) dsvec_ptr_index(dsv, 0);
  }
  else {
	credentials = NULL;
	dsv = NULL;
	log_msg((LOG_DEBUG_LEVEL, "User is unauthenticated"));
  }

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

  if (*object == '/')
	object_url = ds_xprintf("file://%s", object);
  else
	object_url = object;

  if ((uri = uri_parse(object_url)) == NULL || uri->scheme == NULL)
	return(-1);

  kwv_conf->dup_mode = KWV_REPLACE_DUPS;
  if (strcaseeq(uri->scheme, "https")) {
	str = ds_xprintf("HTTPS=on");
	kwv_add_str(kwv_dacs, str);
	putenv(str);
  }

  if (uri->host != NULL && uri->host[0] != '\0')
	host = uri->host;
  else {
	if (hostname != NULL)
	  host = hostname;
	else
	  host = "localhost";
  }

  str = ds_xprintf("SERVER_NAME=%s", host);
  kwv_add_str(kwv_dacs, str);
  putenv(str);

  if ((he = gethostbyname(host)) != NULL
	  && he->h_addr_list != NULL && he->h_addr_list[0] != NULL) {
	addr = *(struct in_addr *) he->h_addr_list[0];
	str = ds_xprintf("SERVER_ADDR=%s", inet_ntoa(addr));
	kwv_add_str(kwv_dacs, str);
	putenv(str);
  }
  else
	log_msg((LOG_WARN_LEVEL, "Can't determine SERVER_ADDR for \"%s\"", host));

  if (uri->port_given != NULL)
	port = ds_xprintf("%d", uri->port);
  else {
	if (uri->scheme != NULL && strcaseeq(uri->scheme, "https"))
	  port = DEFAULT_HTTPS_PORT_NUMBER;
	else
	  port = DEFAULT_PORT_NUMBER;
  }
  str = ds_xprintf("HTTP_HOST=%s:%s", host, port);
  putenv(str);
  kwv_add_str(kwv_dacs, str);

  str = ds_xprintf("SERVER_PORT=%s", port);
  kwv_add_str(kwv_dacs, str);
  putenv(str);

  if (uri->path != NULL) {
	if (uri->query_string != NULL)
	  str = ds_xprintf("REQUEST_URI=%s?%s", uri->path, uri->query_string);
	else
	  str = ds_xprintf("REQUEST_URI=%s", uri->path);
	kwv_add_str(kwv_dacs, str);
	putenv(str);

#ifdef NOTDEF
	str = ds_xprintf("SCRIPT_NAME=%s", uri->path);
	kwv_add_str(kwv_dacs, str);
	putenv(str);

	str = ds_xprintf("SCRIPT_FILENAME=");
	kwv_add_str(kwv_dacs, str);
	putenv(str);
#endif
  }

  str = ds_xprintf("DOCUMENT_ROOT=/");
  kwv_add_str(kwv_dacs, str);
  putenv(str);

  str = ds_xprintf("REQUEST_METHOD=GET");
  kwv_add_str(kwv_dacs, str);
  putenv(str);

  str = ds_xprintf("SERVER_SOFTWARE=dacscheck-%s", DACS_VERSION_RELEASE);
  kwv_add_str(kwv_dacs, str);
  putenv(str);

  if ((cr = credentials) != NULL) {
	str = ds_xprintf("REMOTE_USER=%s",
					 auth_identity(cr->federation, cr->home_jurisdiction,
								   cr->username, NULL));
	kwv_add_str(kwv_dacs, str);
	putenv(str);
  }

  kwv_conf->dup_mode = KWV_NO_DUPS;

  if (uri->query_args != NULL)
	kwv_args = query_args_to_kwv(uri->query_args);
  else
	kwv_args = NULL;

  env = NULL;
  r = strdup(rules);
  while (r != NULL && *r != '\0') {
	for (p = r; *p != '\0' && *p != ' ' && *p != '\t'; p++)
	  ;
	if (*p != '\0')
	  *p++ = '\0';

	kwv_conf->dup_mode = KWV_ALLOW_DUPS;
	if (groups != NULL)
	  kwv_add(kwv_conf, "VFS", groups);
	kwv_add(kwv_conf, "VFS", r);
	kwv_vartab_init(kwv_conf, conf_vartab, NULL, NULL);
	kwv_conf->dup_mode = KWV_NO_DUPS;

	if (uri->query_string != NULL) {
	  str = ds_xprintf("QUERY_STRING=%s", uri->query_string);
	  kwv_add_str(kwv_dacs, str);
	  putenv(str);
	}

	env = init_env(object, uri, kwv_conf, kwv_dacs);
	if (acs_init_env(NULL, kwv_args, uri->path, credentials, env) == -1)
	  return(-1);

	if (verbose_level) {
	  printf("Conf namespace:\n");
	  kwv_text(stdout, kwv_conf);

	  printf("\n");
	  printf("DACS namespace:\n");
	  kwv_text(stdout, kwv_dacs);

	  printf("\n");
	  printf("Env namespace:\n");
	  printf("%s\n", var_ns_buf(env->namespaces, "Env"));
	}

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

	if (result->m.delegated || result->m.exact)
	  break;

	r = p;
  }

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

  env->acl_rule = result->m.acl;
  if ((st = acl_grants_user(result->m.acl, env, &result->cr, result)) != 1) {
	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 (dsvec_len(dsv) == 0)
	  result->denial_reason = ACS_DENIAL_REASON_NO_AUTH;
	else
	  result->denial_reason = ACS_DENIAL_REASON_BY_RULE;

	return(st);
  }

  if (result->attrs.constraint != NULL && constraint != NULL)
	*constraint = strdup(result->attrs.constraint);

  return(1);
}

/*
 * OBJECTS is a space-separated list of objects to be checked.
 * Return 1 if access to each element of OBJECTS should be granted to
 * IDENTS according to RULES and a context (KWV_CONF and KWV_DACS),
 */
int
check_access(char *rules, char *groups, char *idents, char *objects,
			 Kwv *kwv_conf, Kwv *kwv_dacs, Acs_result *result,
			 char ***constraints)
{
  int st;
  char *constraint, *object, *p;
  Acs_result *res;
  Dsvec *dsv;

  if (result == NULL)
	res = ALLOC(Acs_result);
  else
	res = result;

  object = strdup(objects);
  dsv = dsvec_init(NULL, sizeof(char *));

  while (object != NULL && *object != '\0') {
	for (p = object; *p != '\0' && *p != ' ' && *p != '\t'; p++)
	  ;
	if (*p != '\0')
	  *p++ = '\0';

	constraint = NULL;
	if ((st = check_object_access(rules, groups, idents, object, kwv_conf,
								  kwv_dacs, res, &constraint)) != 1)
	  return(st);
	if (constraint != NULL)
	  dsvec_add_ptr(dsv, constraint);

	object = p;
  }

  if (dsvec_len(dsv) > 0 && constraints != NULL) {
	dsvec_add_ptr(dsv, NULL);
	*constraints = (char **) dsvec_base(dsv);
  }

  return(1);
}
