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

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2015\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: auth_roles.c 2807 2015-07-22 21:38:35Z brachman $";
#endif

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

static char *log_module_name = "auth_roles";

Acs_expr_result
auth_eval(char *expr, Kwv *kwv, Kwv *kwv_auth, Kwv *kwv_options,
		  char **result_str)
{
  int ns;
  char *p;
  Acs_expr_result st;
  Acs_expr_ns_arg ns_args[10];
  Kwv *kwv_dacs;
  Var_ns *env_ns;

  if (dacs_conf == NULL || dacs_conf->conf_var_ns == NULL
	  || (kwv_dacs = var_ns_lookup_kwv(dacs_conf->conf_var_ns, "DACS"))
	  == NULL)
	kwv_dacs = kwv_init(10);

  /* XXX what else should be in the DACS namespace? */
  if ((p = getenv("REMOTE_USER")) != NULL)
	kwv_replace(kwv_dacs, "REMOTE_USER", p);
  if ((p = getenv("AUTH_TYPE")) != NULL)
	kwv_replace(kwv_dacs, "AUTH_TYPE", p);

  ns = 0;
  ns_args[ns].name = "DACS";
  ns_args[ns++].kwv = kwv_dacs;

  if (kwv != NULL) {
	ns_args[ns].name = "Args";
	ns_args[ns++].kwv = kwv;
  }

  if (dacs_conf != NULL && dacs_conf->conf_var_ns != NULL
	  && dacs_conf->conf_var_ns->ns != NULL
	  && dacs_conf->conf_var_ns->kwv != NULL) {
	ns_args[ns].name = dacs_conf->conf_var_ns->ns;
	ns_args[ns++].kwv = dacs_conf->conf_var_ns->kwv;
  }

  if (kwv_options != NULL) {
	ns_args[ns].name = "Options";
	ns_args[ns++].kwv = kwv_options;
  }

  if (kwv_auth != NULL) {
	ns_args[ns].name = "Auth";
	ns_args[ns++].kwv = kwv_auth;
  }

  if ((env_ns = var_ns_from_env("Env")) != NULL) {
	ns_args[ns].name = "Env";
	ns_args[ns++].kwv = env_ns->kwv;
  }

  ns_args[ns].name = NULL;
  ns_args[ns++].kwv = NULL;

  st = acs_expr_string(expr, ns_args, result_str);

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

  return(st);
}

static Roles_module *
new_roles_module(Kwv_vartab *roles_vartab)
{
  Roles_module *rm;

  rm = ALLOC(Roles_module);
  rm->id = NULL;
  rm->roles_vartab = roles_vartab;
  rm->predicate = NULL;
  rm->url = NULL;
  rm->url_eval = NULL;
  rm->expr = NULL;
  rm->init_eval = NULL;
  rm->exit_eval = NULL;
  rm->options = dsvec_init(NULL, sizeof(char *));
  rm->options_eval = NULL;
  rm->username = NULL;

  rm->kwv_options = kwv_init(10);
  /* Do not allow duplicate OPTIONs */
  kwv_set_mode(rm->kwv_options, "dn");

  rm->kwv_conf = NULL;
  rm->argv_spec = NULL;
  rm->next = NULL;

  return(rm);
}

static int
roles_add_option(Roles_module *rm, char *option_flag)
{
  char *varname, *varvalue;

  if (kwv_parse_qstr(option_flag + 2, &varname, &varvalue, NULL, NULL) == -1) {
	fprintf(stderr, "Invalid module option: %s\n", option_flag);
	return(-1);
  }

  if (rm->kwv_options == NULL) {
	rm->kwv_options = kwv_init(10);
	/* Do not allow duplicate OPTIONs */
	kwv_set_mode(rm->kwv_options, "dn");
  }

  kwv_add_nocopy(rm->kwv_options, varname, varvalue);
  if (rm->options == NULL)
	rm->options = dsvec_init(NULL, sizeof(char *));
  dsvec_add_ptr(rm->options, option_flag);
  dsvec_add_ptr(rm->argv_spec, option_flag);

  return(0);
}

Roles_module_desc roles_module_desc_tab[] = {
  { ROLES_MODULE_EXPR, 1, "expr",
	{ "expr", NULL }
  },

#ifdef ENABLE_LOCAL_ROLES
  { ROLES_MODULE, 1, "local_roles",
	{ "local_roles", "roles", NULL }
  },
#endif

#ifdef ENABLE_UNIX_ROLES
  { ROLES_MODULE_UNIX, 1, "local_unix_roles",
	{ "local_unix_roles", "unix", NULL }
  },
#endif

  { ROLES_MODULE_UNKNOWN, -1, NULL,
	{ NULL }
  }
};

static Roles_module_desc *
roles_module_lookup_desc_by_name(char *name)
{
  int i;
  Roles_module_desc *desc;

  for (desc = &roles_module_desc_tab[0]; desc->id != ROLES_MODULE_UNKNOWN;
	   desc++) {
	for (i = 0; i < ROLES_MODULE_MAX_ALIASES && desc->aliases[i] != NULL; i++) {
	  if (strcaseeq(desc->aliases[i], name))
		return(desc);
	}
  }

  return(NULL);
}

static Roles_module_id
roles_module_lookup_id_by_name(char *name)
{
  int i;
  Roles_module_desc *desc;

  for (desc = &roles_module_desc_tab[0]; desc->id != ROLES_MODULE_UNKNOWN;
	   desc++) {
	for (i = 0; i < ROLES_MODULE_MAX_ALIASES && desc->aliases[i] != NULL; i++) {
	  if (strcaseeq(desc->aliases[i], name))
		return(desc->id);
	}
  }

  return(ROLES_MODULE_UNKNOWN);
}

/*
 * Parse a command-line argument string roles module specification
 * Module specification syntax:
 *   module [-Of file] [-Oname=value]* [-expr EXPR] [-vfs vfs_uri]*
 * The starting vector index into ARGV is IND.
 * If a valid module spec is found, return it and set IND to the vector index
 * of the next element, otherwise return -1 if this is an invalid module spec.
 */
Roles_module *
roles_module_config(char **argv, int *ind)
{
  int i, is_expr;
  Roles_module *rm;
  Roles_module_desc *desc;

  rm = new_roles_module(NULL);

  i = *ind;
  rm->argv_spec = dsvec_init(NULL, sizeof(char *));

  /*
   * The first component identifies a builtin module or a URL.
   */
  is_expr = 0;
  if ((desc = roles_module_lookup_desc_by_name(argv[i])) != NULL) {
	rm->url = desc->canonical_name;
	if (desc->id == ROLES_MODULE_EXPR)
	  is_expr = 1;
  }
  else {
	Uri *url;

	/* A URL for an external roles module. */
	if ((url = http_parse_uri(argv[i])) == NULL) {
	  fprintf(stderr, "Invalid roles module URL specification\n");
	  return(NULL);
	}
	rm->url = argv[i];
  }

  dsvec_add_ptr(rm->argv_spec, rm->url);

  i++;

  while (argv[i] != NULL) {
	if (streq(argv[i], "-Of")) {
	  if (argv[++i] == NULL) {
		fprintf(stderr, "Role module's filename argument is missing\n");
		return(NULL);
	  }
	  if (auth_get_options_from_file(argv[i], rm->kwv_options, rm->options,
									 rm->argv_spec) == -1) {
		fprintf(stderr, "Error loading role options from \"%s\"\n", argv[i]);
		return(NULL);
	  }
	}
	else if (strprefix(argv[i], "-O") != NULL) {
	  char *varname, *varvalue;

	  /* An OPTION: -Oname=value */
	  if (auth_add_option(&argv[i][2], rm->kwv_options, rm->options,
						  rm->argv_spec) == -1) {
		fprintf(stderr, "Invalid role module option: %s\n", argv[i]);
		return(NULL);
	  }
	}
	else if (streq(argv[i], "-expr")) {
	  if (!is_expr)
		return(NULL);
	  if (rm->expr != NULL) {
		fprintf(stderr, "Only one EXPR can be specified per module\n");
		return(NULL);
	  }
	  if (argv[++i] == NULL) {
		fprintf(stderr, "Module's EXPR argument is missing\n");
		return(NULL);
	  }
	  rm->expr = argv[i];
	  dsvec_add_ptr(rm->argv_spec, "-expr");
	  dsvec_add_ptr(rm->argv_spec, argv[i]);
	}
	else if (streq(argv[i], "-vfs")) {
	  Vfs_directive *vd;

	  if (argv[++i] == NULL) {
		fprintf(stderr, "Module's VFS argument is missing\n");
		return(NULL);
	  }

	  /* A VFS spec */
	  if ((vd = vfs_uri_parse(argv[i], NULL)) == NULL) {
		fprintf(stderr, "Invalid vfs_uri\n");
		return(NULL);
	  }
	  conf_set_directive(rm->kwv_conf, "VFS", argv[i]);
	  dsvec_add_ptr(rm->argv_spec, "-vfs");
	  dsvec_add_ptr(rm->argv_spec, argv[i]);
	}
	else
	  break;

	i++;
  }

  if (is_expr && rm->expr == NULL) {
	fprintf(stderr, "Role module's EXPR argument is missing\n");
	return(NULL);
  }
  if (!is_expr && rm->expr != NULL) {
	fprintf(stderr, "Role module does not accept an EXPR argument\n");
	return(NULL);
  }

  *ind = i;

  return(rm);
}

/*
 * Also see conf.c:conf_roles_vartab_by_id()
 */
Roles_module *
roles_module_lookup(Roles_module_info *rmi, char *id)
{
  Roles_module *rm;

  for (rm = rmi->modules; rm != NULL; rm = rm->next) {
	if (rm->id != NULL && streq(rm->id, id))
	  return(rm);
  }

  return(NULL);
}

Roles_module_info *
auth_parse_roles_clause_init(Roles_module_info *o_rmi)
{
  Roles_module_info *rmi;

  if (o_rmi == NULL)
	rmi = ALLOC(Roles_module_info);
  else
	rmi = o_rmi;

  rmi->modules = NULL;
  rmi->mcount = 0;

  return(rmi);
}

/*
 * Parse all of the Roles clauses.
 */
Roles_module_info *
auth_parse_roles_clauses(Roles_module_info *o_rmi)
{
  int vtnum;
  char *p;
  Roles_module *rm;
  Roles_module_info *rmi;
  Kwv_pair *v;
  Kwv_vartab *roles_vartab;

  rmi = auth_parse_roles_clause_init(o_rmi);

  vtnum = 0;
  while ((roles_vartab = conf_roles_vartab(vtnum++)) != NULL) {
	int c;
	Roles_module **mp;

	rm = new_roles_module(roles_vartab);

	if ((p = conf_vartab_val(roles_vartab, CONF_ROLES_ROLES_ID)) != NULL)
	  rm->id = p;
	else {
	  log_msg((LOG_ERROR_LEVEL, "Missing ROLES_ID?"));
	  return(NULL);
	}

	rm->init_eval = conf_vartab_val(roles_vartab, CONF_ROLES_INIT_EVAL);
	rm->exit_eval = conf_vartab_val(roles_vartab, CONF_ROLES_EXIT_EVAL);

	rm->expr = conf_vartab_val(roles_vartab, CONF_ROLES_EXPR);
	rm->url_eval = conf_vartab_val(roles_vartab, CONF_ROLES_URL_EVAL);
	rm->url = conf_vartab_val(roles_vartab, CONF_ROLES_URL);
	c = (rm->expr != NULL) + (rm->url_eval != NULL) + (rm->url != NULL);
	if (c != 1) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Exactly one of URL, URL*, and EXPR must be configured"));
	  return(NULL);
	}

	rm->predicate = conf_vartab_val(roles_vartab, CONF_ROLES_PREDICATE);

	/*
	 * Collect each OPTION, checking for errors and keeping the original
	 * OPTION string.
	 * XXX Check for proper encoding, etc.
	 */
	for (v = conf_vartab_var(roles_vartab, CONF_ROLES_OPTION); v != NULL;
		 v = v->next) {
	  char *o_name, *o_val;

	  log_msg((LOG_NOTICE_LEVEL, "OPTION %s", v->val));
	  if (rm->kwv_options == NULL) {
		rm->kwv_options = kwv_init(10);
		/* Do not allow duplicate OPTIONs */
		kwv_set_mode(rm->kwv_options, "dn");
	  }

	  if (kwv_parse_qstr(v->val, &o_name, &o_val, NULL, NULL) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Invalid OPTION value: \"%s\"", v->val));
		return(NULL);
	  }

	  log_msg((LOG_TRACE_LEVEL, "name=\"%s\", value=\"%s\"", o_name, o_val));
	  rm->kwv_options = kwv_add_nocopy(rm->kwv_options, o_name, o_val);
	  if (rm->kwv_options == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Error processing option: %s", v->val));
		return(NULL);
	  }
	  if (rm->options == NULL)
		rm->options = dsvec_init(NULL, sizeof(char *));
	  dsvec_add_ptr(rm->options, v->val);
	}

	/*
	 * Get each OPTION* directive for processing later if/when the
	 * module is run.
	 */
	for (v = conf_vartab_var(roles_vartab, CONF_ROLES_OPTION_EVAL);
		 v != NULL; v = v->next) {
	  log_msg((LOG_NOTICE_LEVEL, "OPTION* %s", v->val));
	  if (rm->options_eval == NULL)
		rm->options_eval = dsvec_init(NULL, sizeof(char *));
	  dsvec_add_ptr(rm->options_eval, v->val);
	}

	rm->next = NULL;

	for (mp = &rmi->modules; *mp != NULL; mp = &(*mp)->next)
	  ;
	*mp = rm;
	rmi->mcount++;
  }

  return(rmi);
}

/*
 * Invoke the DACS roles service at URL.
 * Return 1 if roles were obtained, 0 if no roles are available, and
 * -1 if an error occurs.
 */
static int
call_roles_module(char *url, char *username, char *jurisdiction, Kwv *options,
				  char **role_str, Common_status *status)
{
  int i, reply_len, st;
  char *cookies[2], *reply, *roles;
  Credentials *admin_cr;
  Dsvec *dsv, *response_headers;
  Http_params *params;
  Roles_reply *roles_reply;

  st = -2;
  if (0)
	;
#ifdef ENABLE_UNIX_ROLES
  else if (strcaseeq(url, "local_unix_roles") || strcaseeq(url, "unix")) {
	log_msg((LOG_INFO_LEVEL, "Using built-in local_unix_roles"));
	st = local_unix_roles(username, &roles);
  }
#endif
#ifdef ENABLE_LOCAL_ROLES
  else if (strcaseeq(url, "local_roles") || strcaseeq(url, "roles")) {
	log_msg((LOG_INFO_LEVEL, "Using built-in local_roles"));
	st = local_roles(username, &roles);
  }
#endif

  if (st == 0) {
	roles_reply = ALLOC(Roles_reply);
	roles_reply->ok = ALLOC(Roles_reply_ok);
	roles_reply->ok->roles = roles;
	roles_reply->failed = NULL;
	roles_reply->status = NULL;
	goto got_roles;
  }
  else if (st == -1) {
	roles_reply = ALLOC(Roles_reply);
	roles_reply->ok = NULL;
	roles_reply->failed = ALLOC(Roles_reply_failed);
	roles_reply->failed->reason = NULL;
	roles_reply->status = NULL;
	goto got_roles;
  }

  dsv = dsvec_init_size(NULL, sizeof(Http_params), 5);

  if (role_str != NULL)
	*role_str = "";

  for (i = 0; i < kwv_count(options, NULL); i++) {
	params = http_param(dsv, options->pairs[i]->name,
						options->pairs[i]->val, NULL, 0);
  }

  admin_cr = make_admin_credentials();
  credentials_to_auth_cookies(admin_cr, &cookies[0]);
  cookies[1] = NULL;

  reply_len = -1;
  response_headers = dsvec_init(NULL, sizeof(char *));
  if (http_invoke(url, HTTP_POST_METHOD,
				  ssl_verify ? HTTP_SSL_ON_VERIFY
				  : (use_ssl ? HTTP_SSL_ON : HTTP_SSL_URL_SCHEME),
				  dsvec_len(dsv), (Http_params *) dsvec_base(dsv), NULL,
				  cookies, &reply, &reply_len, NULL, response_headers) == -1) {
	init_common_status(status, NULL, NULL, reply);
	return(-1);
  }

  if (reply_len == 0) {
	init_common_status(status, NULL, NULL,
					   "roles module returned 0 bytes: no XML document");
	return(-1);
  }

  log_msg((LOG_DEBUG_LEVEL, "roles module returned: %s", reply));

  if (parse_xml_roles_reply(reply, &roles_reply) == -1) {
	init_common_status(status, NULL, NULL, "parse_xml_roles_reply error");
	return(-1);
  }

 got_roles:

  if (roles_reply->ok != NULL) {
	/* Don't completely trust the local auth service... */
	if (role_str != NULL) {
	  if (*roles_reply->ok->roles == '\0'
		  || is_valid_role_str(roles_reply->ok->roles))
		*role_str = roles_reply->ok->roles;
	  else
		log_msg((LOG_NOTICE_LEVEL,
				 "Returned roles are invalid and will be ignored: \"%s\"",
				 (roles_reply->ok->roles) == NULL
				 ? "" : roles_reply->ok->roles));
	}
	return(1);
  }

  if (roles_reply->failed != NULL) {
	if (roles_reply->failed->reason != NULL)
	  init_common_status(status, NULL, NULL, roles_reply->failed->reason);
	else
	  init_common_status(status, NULL, NULL, "No role string is available");
	return(0);
  }

  if (roles_reply->status != NULL) {
	*status = *roles_reply->status;
	return(-1);
  }

  init_common_status(status, NULL, NULL, "Invalid roles_reply result");
  return(-1);
}

/*
 * Invoke a roles module when it is configured from arguments rather than
 * the DACS config file.
 */
int
roles_module_invoke(Roles_module *rm, char *username, char *password,
					char *jurisdiction, Kwv *kwv, Kwv *kwv_auth,
					char **role_str, Common_status *common_status,
					char **errmsg)
{
  int i, rc, st;
  char *p, *roles_url, *url;

  rc = 0;
  *role_str = NULL;
  roles_url = NULL;

  if (rm->init_eval != NULL) {
	st = auth_eval(rm->init_eval, kwv, kwv_auth, NULL, NULL);
	if (acs_expr_error_occurred(st)) {
	  *errmsg = ds_xprintf("Invalid INIT* directive: %s", rm->init_eval);
	  return(-1);
	}
  }

  log_msg((LOG_DEBUG_LEVEL, "Using Roles clause \"%s\"", rm->id));

  /* These are mutually exclusive. */
  if ((url = rm->url_eval) != NULL) {
	st = auth_eval(url, kwv, kwv_auth, NULL, &roles_url);
	if (st != ACS_EXPR_TRUE || roles_url == NULL) {
	  *errmsg = ds_xprintf("Roles \"%s\": URL* directive \"%s\" is invalid",
						   rm->id, url);
	  return(-1);
	}
  }
  else if (rm->expr != NULL) {
	/* There is no module to invoke, only an expression to evaluate. */
	st = auth_eval(rm->expr, kwv, kwv_auth, NULL, role_str);
	if (st == ACS_EXPR_TRUE && *role_str != NULL)
	  rc = 1;
	else {
	  rc = -1;
	  *role_str = "";
	}
  }
  else if ((roles_url = rm->url) == NULL) {
	*errmsg
	  = ds_xprintf("Roles '%s': either URL or URL* directive is required",
				   rm->id);
	return(-1);
  }

  if (password != NULL)
	kwv_add_nocopy(rm->kwv_options, "PASSWORD", password);

  kwv_add_nocopy(rm->kwv_options, "DACS_USERNAME", username);
  kwv_add_nocopy(rm->kwv_options, "DACS_JURISDICTION", jurisdiction);
  kwv_add_nocopy(rm->kwv_options, "DACS_VERSION", DACS_VERSION_NUMBER);

  rc = call_roles_module(roles_url, username, jurisdiction, rm->kwv_options,
						 role_str, common_status);

  if (rc <= 0) {
	log_msg((LOG_ERROR_LEVEL,
			 "Roles \"%s\" failed for username \"%s\"", rm->id, username));
	log_msg((LOG_ERROR_LEVEL, "%s", common_status->message));
	*role_str = "";
  }
  else {
	/* Success! */
	log_msg((LOG_TRACE_LEVEL, "Received role string: \"%s\"", *role_str));
  }

  kwv_replace(kwv_auth, "LAST_ROLES", *role_str);

  if (rm->exit_eval != NULL) {
	st = auth_eval(rm->exit_eval, kwv, kwv_auth, NULL, NULL);
	if (acs_expr_error_occurred(st)) {
	  *errmsg = ds_xprintf("Invalid EXIT* directive: %s", rm->exit_eval);
	  return(-1);
	}
  }

  if ((p = kwv_lookup_value(kwv_auth, "LAST_ROLES")) != NULL)
	*role_str = p;

  return(rc);
}

/*
 * Evaluate one Roles clause.
 */
int
auth_process_roles_clause(Roles_module *rm, char *username, char *jurisdiction,
						  Kwv *kwv, Kwv *kwv_auth, char **role_str,
						  Common_status *common_status, char **errmsg)
{
  int i, rc, st;
  char *p, *roles_url, *url;
  Kwv *kwv_options;

  rc = 0;
  *role_str = NULL;

  if (rm->predicate != NULL && *(rm->predicate) != '\0') {
	Acs_expr_result st;

	st = auth_eval(rm->predicate, kwv, kwv_auth, NULL, NULL);

	if (st == ACS_EXPR_SYNTAX_ERROR) {
	  *errmsg = ds_xprintf("Syntax error in Roles '%s' predicate", rm->id);
	  return(-1);
	}
	else if (st == ACS_EXPR_EVAL_ERROR) {
	  *errmsg = ds_xprintf("Evaluation error in Roles '%s' predicate", rm->id);
	  return(-1);
	}
	else if (st == ACS_EXPR_LEXICAL_ERROR) {
	  *errmsg = ds_xprintf("Lexical error in Roles '%s' predicate", rm->id);
	  return(-1);
	}
	else if (st == ACS_EXPR_FALSE) {
	  log_msg((LOG_DEBUG_LEVEL, "Roles '%s' predicate is false...", rm->id));
	  return(0);
	}
  }

  if (rm->init_eval != NULL) {
	st = auth_eval(rm->init_eval, kwv, kwv_auth, NULL, NULL);
	if (acs_expr_error_occurred(st)) {
	  *errmsg = ds_xprintf("Invalid INIT* directive: %s", rm->init_eval);
	  return(-1);
	}
  }

  log_msg((LOG_DEBUG_LEVEL, "Using Roles clause \"%s\"", rm->id));

  /* These are mutually exclusive. */
  if ((url = rm->url_eval) != NULL) {
	st = auth_eval(url, kwv, kwv_auth, NULL, &roles_url);
	if (st != ACS_EXPR_TRUE || roles_url == NULL) {
	  *errmsg = ds_xprintf("Roles \"%s\": URL* directive \"%s\" is invalid",
						   rm->id, url);
	  return(-1);
	}
  }
  else if (rm->expr != NULL) {
	/* There is no module to invoke, only an expression to evaluate. */
	st = auth_eval(rm->expr, kwv, kwv_auth, NULL, role_str);
	if (st == ACS_EXPR_TRUE && *role_str != NULL)
	  rc = 1;
	else {
	  rc = -1;
	  *role_str = "";
	}
  }
  else if ((roles_url = rm->url) == NULL) {
	*errmsg
	  = ds_xprintf("Roles '%s': either URL or URL* directive is required",
				   rm->id);
	return(-1);
  }

  kwv_options = kwv_init(4);
  kwv_add_nocopy(kwv_options, "DACS_USERNAME", username);
  kwv_add_nocopy(kwv_options, "DACS_JURISDICTION", jurisdiction);
  kwv_add_nocopy(kwv_options, "DACS_VERSION", DACS_VERSION_NUMBER);

  if (rm->expr == NULL) {
	if (rm->options != NULL) {
	  for (i = 0; i < dsvec_len(rm->options); i++) {
		char *p, *varname, *varvalue;

		p = dsvec_ptr(rm->options, i, char *);
		log_msg((LOG_NOTICE_LEVEL, "OPTION %s", p));

		if (kwv_parse_qstr(p, &varname, &varvalue, NULL, NULL) == -1) {
		  *errmsg = ds_xprintf("Invalid OPTION directive: %s", p);
		  return(-1);
		}
		log_msg((LOG_TRACE_LEVEL, "OPTION sets \"%s\" to \"%s\"",
				 varname, varvalue));

		if (kwv_replace(kwv_options, varname, varvalue) == NULL) {
		  *errmsg = ds_xprintf("Error processing OPTION: %s", p);
		  return(-1);
		}
	  }
	}

	if (rm->options_eval != NULL) {
	  for (i = 0; i < dsvec_len(rm->options_eval); i++) {
		char *option, *p, *varname, *varvalue;

		p = dsvec_ptr(rm->options_eval, i, char *);
		log_msg((LOG_NOTICE_LEVEL, "OPTION* %s", p));

		st = auth_eval(p, kwv, kwv_auth,kwv_options, &option);
		if (st != ACS_EXPR_TRUE || option == NULL) {
		  *errmsg = ds_xprintf("Invalid OPTION* directive: %s", p);
		  return(-1);
		}

		if (kwv_parse_qstr(option, &varname, &varvalue, NULL, NULL) == -1) {
		  *errmsg = ds_xprintf("Invalid OPTION* directive: %s", p);
		  return(-1);
		}
		log_msg((LOG_TRACE_LEVEL, "OPTION* sets \"%s\" to \"%s\"",
				 varname, varvalue));

		/* OPTION* directives override everything else. */
		if (kwv_replace(kwv_options, varname, varvalue) == NULL) {
		  *errmsg = ds_xprintf("Error processing OPTION*: %s", p);
		  return(-1);
		}
	  }
	}

	rc = call_roles_module(roles_url, username, jurisdiction, kwv_options,
						   role_str, common_status);
  }

  if (rc <= 0) {
	log_msg((LOG_ERROR_LEVEL,
			 "Roles \"%s\" failed for username \"%s\"", rm->id, username));
	log_msg((LOG_ERROR_LEVEL, "%s", common_status->message));
	*role_str = "";
  }
  else {
	/* Success! */
	log_msg((LOG_TRACE_LEVEL, "Received role string: \"%s\"", *role_str));
  }

  kwv_replace(kwv_auth, "LAST_ROLES", *role_str);

  if (rm->exit_eval != NULL) {
	st = auth_eval(rm->exit_eval, kwv, kwv_auth, NULL, NULL);
	if (acs_expr_error_occurred(st)) {
	  *errmsg = ds_xprintf("Invalid EXIT* directive: %s", rm->exit_eval);
	  return(-1);
	}
  }

  if ((p = kwv_lookup_value(kwv_auth, "LAST_ROLES")) != NULL)
	*role_str = p;

  return(rc);
}

int
collect_roles(char *username, char *jurisdiction, Kwv *kwv, Kwv *kwv_auth,
			  Ds **ds_roles, Common_status *common_status,
			  Auth_failure_reason *failure_reason)
{
  int rc;
  char *errmsg, *role_str;
  Roles_module *rm;
  Roles_module_info *rmi;

  if ((rmi = auth_parse_roles_clauses(NULL)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not parse Roles clause"));
	init_common_status(common_status, NULL, NULL,
					   "Syntactical error in Roles clause");
	/* XXX The reason doesn't matter here, but this one is incorrect */
	*failure_reason = AUTH_FAILURE_REASON_INTERNAL_ERROR;
	return(-1);
  }

  kwv_replace(kwv_auth, "DACS_USERNAME", username);
  kwv_replace(kwv_auth, "DACS_IDENTITY",
			  auth_identity(conf_val(CONF_FEDERATION_NAME),
							jurisdiction, username, NULL));
  kwv_replace(kwv_auth, "DACS_JURISDICTION", jurisdiction);
  kwv_replace(kwv_auth, "DACS_VERSION", DACS_VERSION_NUMBER);

  rc = 0;
  errmsg = NULL;
  role_str = NULL;

  for (rm = rmi->modules; rm != NULL; rm = rm->next) {
	rc = auth_process_roles_clause(rm, username, jurisdiction, kwv, kwv_auth,
								   &role_str, common_status, &errmsg);
	if (rc == -1) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Roles \"%s\" failed for username \"%s\"", rm->id, username));
	  log_msg((LOG_ERROR_LEVEL, "%s", errmsg));
	  init_common_status(common_status, NULL, NULL, errmsg);
	  *failure_reason = AUTH_FAILURE_REASON_INTERNAL_ERROR;
	  return(-1);
	}

	if (rc == 0)
	  continue;

	if (*ds_roles == NULL)
	  *ds_roles = ds_init(NULL);

	/* XXX This assumes the syntax of a role string... */
	if (ds_len(*ds_roles)) {
	  if (*role_str != '\0') {
		ds_asprintf(*ds_roles, ",%s", role_str);
		log_msg((LOG_TRACE_LEVEL, "Appended to role string"));
	  }
	}
	else
	  ds_asprintf(*ds_roles, "%s", role_str);

	kwv_replace(kwv_auth, "CURRENT_ROLES", ds_buf(*ds_roles));
	log_msg((LOG_DEBUG_LEVEL, "CURRENT_ROLES=%s", ds_buf(*ds_roles)));
  }

  return(0);
}
