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

/*
 *
 */

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

#include <pwd.h>

#include "dacs_api.h"

static MAYBE_UNUSED char *log_module_name = "dacscheck";

#ifndef PROG

static int
add_context(FILE *fp, Kwv *kwv)
{
  char *name, *p, *value;
  Ds *ds;

  ds = ds_init(NULL);
  ds->delnl_flag = 1;
  while ((p = ds_gets(ds, fp)) != NULL) {
	if (env_parse(p, &name, &value) == NULL)
	  return(-1);
	if (!var_ns_is_valid_varname(name, NULL))
	  return(-1);
	kwv_replace(kwv, name, value);
  }
	
  ds_free(ds);

  if (!feof(fp) || ferror(fp))
	return(-1);

  return(0);
}

static void
dacs_usage(void)
{

  fprintf(stderr, "Usage: dacscheck [flags] object\n");
  fprintf(stderr, "flags are composed from:\n");
  fprintf(stderr, "-admin:           following idents are each DACS admins\n");
  fprintf(stderr, "-context ctx      read DACS context from file ctx\n");
  fprintf(stderr, "-Dname=value:     same as -var name=value\n");
  fprintf(stderr, "-dump:            initialize, dump variables, and exit\n");
  fprintf(stderr, "-F sep:           use sep as the field separator for roles\n");
  fprintf(stderr, "-fd domain:       the federation domain\n");
  fprintf(stderr, "-fh hostname:     the hostname\n");
  fprintf(stderr, "-fj jurname:      the jurisdiction name\n");
  fprintf(stderr, "-fn fedname:      the federation name\n");
  fprintf(stderr, "-groups grps_vfs: grps_vfs is location of DACS groups\n");
  fprintf(stderr, "-h:               show this help blurb\n");
  fprintf(stderr, "-i ident:         use ident\n");
  fprintf(stderr, "-icgi:            use REMOTE_USER for username\n");
  fprintf(stderr, "-icgig:           like -icgi but also add roles\n");
  fprintf(stderr, "-ieuid:           add the euid to set of identities\n");
  fprintf(stderr, "-ieuidg:          like -ieuid but also add its groups\n");
  fprintf(stderr, "-il ident:        use ident, a local name\n");
  fprintf(stderr, "-ilg ident:       use local ident with its Unix groups\n");
  fprintf(stderr, "-iuid:            add the uid to set of identities\n");
  fprintf(stderr, "-iuidg:           like -iuid but also add its groups\n");
  fprintf(stderr, "-lg:              add Unix groups to roles of locals\n");
  fprintf(stderr, "-ll level:        set logging to level\n");
  fprintf(stderr, "-name_compare method: set NAME_COMPARE to method\n");
  fprintf(stderr, "-q:               be quiet except for error messages\n");
  fprintf(stderr, "-redirect:        if redirected, emit URL to stdout\n");
  fprintf(stderr, "-roles roles_vfs: where to look for roles\n");
  fprintf(stderr, "-rules rules_vfs: add rules_vfs to DACS rulesets\n");
  fprintf(stderr, "-v:               increase verbosity level\n");
  fprintf(stderr, "-var name=value:  set name to value in DACS context\n");
  fprintf(stderr, "-vfs vfs_uri:     add vfs_uri to the VFS configuration\n");
  fprintf(stderr, "--:               end of flags\n");

  fprintf(stderr, "\n");
  fprintf(stderr, "o The default ruleset is the directory %s/dacscheck/acls\n",
		  DACS_HOME);
  fprintf(stderr,
		  "o The default group directory root is %s/dacscheck/groups\n",
		  DACS_HOME);

  fprintf(stderr, "\n");
  fprintf(stderr, "Exit status:\n");
  fprintf(stderr, "  0 means access is granted\n");
  fprintf(stderr, "  1 means access is denied\n");
  fprintf(stderr, "  anything else means an error occurred\n");

  exit(1);
}

int
dacscheck_main(int argc, char **argv, int do_init, void *main_out)
{
  int dump_flag, i, st, add_unix_groups, quiet_flag, redirect_flag;
  char *app, **constraints, *ident_flags, *r;
  char *errmsg, *field_sep, *groups_vfs, *objects, *roles_vfs, *rules_vfs;
  char *idents, *p, *unix_roles;
  Acs_result result;
  Kwv *kwv_conf, *kwv_dacs;
  Log_desc *ld;
  Simple_user suser;
  Vfs_directive *vd;

  errmsg = "Internal error";

  if (dacs_init(DACS_STANDALONE_NOARGS, &argc, &argv, NULL, &errmsg) == -1) {
  fail:
	fprintf(stderr, "%s\n", errmsg);
	dacs_usage();
	/*NOTREACHED*/
  }

  ld = log_init(NULL, 0, NULL, "dacscheck", LOG_NONE_LEVEL, NULL);
  log_set_level(ld, LOG_WARN_LEVEL);
  log_set_desc(ld, LOG_ENABLED);

  idents = NULL;
  objects = NULL;
  rules_vfs = NULL;
  groups_vfs = NULL;
  roles_vfs = NULL;
  app = NULL;
  add_unix_groups = 0;
  quiet_flag = 0;
  verbose_level = 0;
  dump_flag = 0;
  ident_flags = NULL;
  redirect_flag = 0;
  field_sep = NULL;
  kwv_conf = kwv_init(10);
  kwv_conf->dup_mode = KWV_REPLACE_DUPS;
  kwv_dacs = kwv_init(10);
  kwv_dacs->dup_mode = KWV_REPLACE_DUPS;
  init_env("", NULL, kwv_conf, kwv_dacs);

  if (set_conf_from_host(kwv_conf, kwv_dacs, NULL) == -1) {
	fprintf(stderr, "Cannot set default federation/jurisdiction name\n");
	goto fail;
  }

  for (i = 1; i < argc; 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], "-app")) {
	  if (++i == argc) {
		errmsg = "Directory path expected after -app";
		goto fail;
	  }
	  app = argv[i];
	}
	else if (streq(argv[i], "-context")) {
	  FILE *fp;

	  if (++i == argc) {
		errmsg = "Context expected after -context";
		goto fail;
	  }
	  if (streq(argv[i], "-"))
		fp = stdin;
	  else {
		if ((fp = fopen(argv[i], "r")) == NULL) {
		  fprintf(stderr, "Can't open context file \"%s\"\n", argv[i]);
		  return(-1);
		}
	  }
	  if (add_context(fp, kwv_dacs) == -1) {
		  fprintf(stderr, "Can't read context from \"%s\"\n", argv[i]);
		  fclose(fp);
		  return(-1);
	  }
	  fclose(fp);
	}
	else if (argv[i][0] == '-' && argv[i][1] == 'D') {
	  char *varname, *varvalue;

	  if (kwv_parse_str(&argv[i][2], &varname, &varvalue) == -1
		  || !var_ns_is_valid_varname(varname, NULL)) {
		errmsg = "Usage: -Dname=value";
		goto fail;
	  }

	  if (kwv_add_nocopy(kwv_dacs, varname, varvalue) == NULL) {
		errmsg = ds_xprintf("Can't initialize: %s", argv[i]);
		goto fail;
	  }
	}
	else if (streq(argv[i], "-dump"))
	  dump_flag = 1;
	else if (streq(argv[i], "-F")) {
	  if (++i == argc) {
		errmsg = "Field separator character expected after -F";
		goto fail;
	  }
	  field_sep = argv[i];
	  if (strlen(field_sep) != 1) {
		errmsg = "Invalid field separator character";
		goto fail;
	  }
	}
	else if (streq(argv[i], "-fd")) {
	  if (++i == argc) {
		errmsg = "Federation domain expected after -fd";
		goto fail;
	  }
	  if (set_federation(kwv_conf, kwv_dacs, NULL, argv[i]) == -1) {
		errmsg = "Invalid federation domain name";
		goto fail;
	  }
	}
	else if (streq(argv[i], "-fh")) {
	  if (++i == argc) {
		errmsg = "Hostname expected after -fh";
		goto fail;
	  }
	  if (set_conf_from_host(kwv_conf, kwv_dacs, argv[i]) == -1) {
		errmsg = "Could not use hostname";
		goto fail;
	  }
	}
	else if (streq(argv[i], "-fj")) {
	  if (++i == argc) {
		errmsg = "Jurisdiction name expected after -fj";
		goto fail;
	  }
	  if (set_jurisdiction(kwv_conf, kwv_dacs, argv[i]) == -1) {
		errmsg = "Invalid jurisdiction name";
		goto fail;
	  }
	}
	else if (streq(argv[i], "-fn")) {
	  if (++i == argc) {
		errmsg = "Federation name expected after -fn";
		goto fail;
	  }
	  if (set_federation(kwv_conf, kwv_dacs, argv[i], NULL) == -1) {
		errmsg = "Invalid federation name";
		goto fail;
	  }
	}
	else if (streq(argv[i], "-groups")) {
	  char *h;

	  if (++i == argc) {
		errmsg = "groups_vfs expected after -groups";
		goto fail;
	  }
	  if (groups_vfs != NULL) {
		errmsg = "Only one -groups flag is allowed";
		goto fail;
	  }

	  if ((vd = vfs_uri_parse(argv[i], NULL)) == NULL) {
		if (argv[i][0] != '/') {
		  errmsg = "Invalid groups_vfs";
		  goto fail;
		}

		get_conf(&h);
		p = directory_name_interpolate(argv[i], h, DEFAULT_PORT_NUMBER);
		if (p == NULL)
		  p = argv[i];
		groups_vfs = ds_xprintf("[groups]dacs-fs:%s", p);
	  }
	  else
		groups_vfs = argv[i];
	}
	else if (streq(argv[i], "-i")) {
	  char *ident, *new_groups, *roles;

	  /*
	   * Either a simple identity is expected or a simple name.
	   * It is not expected to be locally known.
	   */
	  if (++i == argc) {
		errmsg = "Identity expected after -i";
		goto fail;
	  }

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

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

	  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;
	  struct passwd *pw;
	  DACS_name_type nt;
	  DACS_name dn;

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

	  if (++i == argc) {
		errmsg = "Identity expected after -i";
		goto fail;
	  }

	  /*
	   * 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";
		goto fail;
	  }

	  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));
		goto fail;
	  }

	  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;
	  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";
		goto fail;
	  }
  
	  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], "-h")) {
	  dacs_usage();
	  /*NOTREACHED*/
	}
	else if (streq(argv[i], "-ll")) {
	  Log_level ll;

	  i++;
	  if ((ll = log_lookup_level(argv[i])) == LOG_INVALID_LEVEL
		  || log_set_level(ld, ll) == LOG_INVALID_LEVEL) {
		fprintf(stderr, "Invalid log_level: %s", argv[i]);
		dacs_usage();
	  }
	}
	else if (streq(argv[i], "-name_compare")) {
	  DACS_name_cmp cmp_mode;

	  if (++i == argc) {
		errmsg = "comparison method expected after -name_compare";
		goto fail;
	  }
	  if ((cmp_mode = lookup_name_cmp(argv[i])) == DACS_NAME_CMP_UNKNOWN) {
		errmsg = "unrecognized comparison method after -name_compare";
		goto fail;
	  }
	  set_name_cmp_mode(cmp_mode);
	  kwv_add(kwv_conf, "NAME_COMPARE", argv[i]);
	}
	else if (streq(argv[i], "-q"))
	  quiet_flag = 1;
	else if (streq(argv[i], "-roles")) {
	  char *h;

	  if (++i == argc) {
		errmsg = "roles_vfs expected after -roles";
		goto fail;
	  }
	  if (roles_vfs != NULL) {
		errmsg = "Only one -roles flag is allowed";
		goto fail;
	  }

	  if ((vd = vfs_uri_parse(argv[i], NULL)) == NULL) {
		if (argv[i][0] != '/') {
		  errmsg = "Invalid roles_vfs";
		  goto fail;
		}

		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 if (streq(argv[i], "-redirect"))
	  redirect_flag = quiet_flag = 1;
	else if (streq(argv[i], "-rules")) {
	  char *h, *r;
	  static int num = 1;

	  if (++i == argc) {
		errmsg = "rule_vfs expected after -rules";
		goto fail;
	  }
	  if ((vd = vfs_uri_parse(argv[i], NULL)) == NULL) {
		if (argv[i][0] != '/') {
		  errmsg = "Invalid rule_vfs";
		  goto fail;
		}

		get_conf(&h);
		p = directory_name_interpolate(argv[i], h, DEFAULT_PORT_NUMBER);
		if (p == NULL)
		  p = argv[i];
		r = ds_xprintf("[acl%d]dacs-fs:%s", num++, p);
	  }
	  else
		r = argv[i];

	  if (rules_vfs == NULL)
		rules_vfs = r;
	  else
		rules_vfs = ds_xprintf("%s %s", rules_vfs, r);
	}
	else if (streq(argv[i], "-v")) {
	  if (++verbose_level == 1)
	    log_set_level(ld, LOG_DEBUG_LEVEL);
	  else if (verbose_level > 1)
	    log_set_level(ld, LOG_TRACE_LEVEL);
	}
	else if (streq(argv[i], "-var")) {
	  char *def, *varname, *varvalue;

	  if (++i == argc) {
		errmsg = "Variable name=value required after -var";
		goto fail;
	  }
	  def = argv[i];

	  if (kwv_parse_str(def, &varname, &varvalue) == -1
		  || !var_ns_is_valid_varname(varname, NULL)) {
		errmsg = "Usage: -var name=value";
		goto fail;
	  }

	  if (kwv_add_nocopy(kwv_dacs, varname, varvalue) == NULL) {
		errmsg = ds_xprintf("Can't initialize: %s", def);
		goto fail;
	  }
	}
	else if (streq(argv[i], "-vfs")) {
	  if (++i == argc) {
		errmsg = "vfs_uri expected after -vfs";
		goto fail;
	  }
	  if ((vd = vfs_uri_parse(argv[i], NULL)) == NULL) {
		errmsg = "Invalid vfs_uri";
		goto fail;
	  }
	  add_vfs_uri(kwv_conf, argv[i]);
	}
	else if (streq(argv[i], "--")) {
	  i++;
	  break;
	}
	else
	  break;
  }

  while (i < argc) {
	if (argv[i][0] != '/') {
	  if (uri_parse(argv[i]) == NULL) {
		errmsg = ds_xprintf("Object must be a URI or absolute path: \"%s\"",
							argv[i]);
		goto fail;
	  }
	}

	if (objects == NULL)
	  objects = argv[i];
	else {
#ifdef NOTDEF
	  objects = ds_xprintf("%s %s", objects, argv[i]);
#else
	  /* Restrict to one object, for now. */
	  errmsg = "Only one object can be specified";
	  goto fail;
	  /*NOTREACHED*/
#endif
	}
	i++;
  }

  if (objects == NULL) {
	errmsg = "At least one object must be specified";
	goto fail;
  }

  kwv_add(kwv_conf, "DACS_HOME", DACS_HOME);
  kwv_add(kwv_conf, "DACS_CGIBINDIR", CGIBINDIR);
  kwv_add(kwv_conf, "APACHE_HOME", APACHE_HOME);
  kwv_add(kwv_conf, "FEDERATIONS_ROOT", FEDERATIONS_ROOT);

  if (kwv_lookup(kwv_conf, "SSL_PROG") == NULL)
	kwv_add(kwv_conf, "SSL_PROG", DACS_BINDIR/**/"/sslclient");

  log_msg((LOG_DEBUG_LEVEL, "Using federation name: \"%s\"",
		   kwv_lookup_value(kwv_conf, "FEDERATION_NAME")));
  log_msg((LOG_DEBUG_LEVEL, "Using jurisdiction name: \"%s\"",
		   kwv_lookup_value(kwv_conf, "JURISDICTION_NAME")));

  if (rules_vfs == NULL) {
	if (app != NULL)
	  rules_vfs = ds_xprintf("[default-acls]dacs-fs:%s/dacscheck/%s/acls",
							 DACS_HOME, app);
	else
	  rules_vfs = ds_xprintf("[default-acls]dacs-fs:%s/dacscheck/acls",
							 DACS_HOME);
  }

  if (groups_vfs == NULL) {
	if (app != NULL)
	  groups_vfs = ds_xprintf("[default-groups]dacs-fs:%s/dacscheck/%s/groups",
							  DACS_HOME, app);
	else
	  groups_vfs = ds_xprintf("[default-acls]dacs-fs:%s/dacscheck/groups",
							  DACS_HOME);
  }

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

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

	return(0);
  }

  constraints = NULL;
  st = check_access(rules_vfs, groups_vfs, idents, objects, kwv_conf, kwv_dacs,
					&result, &constraints);
  if (st == 0) {
	if (redirect_flag
		&& result.denial_reason == ACS_DENIAL_REASON_BY_SIMPLE_REDIRECT
		&& result.redirect_action != NULL)
	  printf("%s\n", result.redirect_action);
	else if (!quiet_flag)
	  printf("Access is denied\n");

	return(-1);
  }

  if (st == 1) {
	if (!quiet_flag) {
	  if (constraints != NULL && constraints[0] != NULL) {
		printf("Access is granted");
		for (i = 0; constraints[i] != NULL; i++) {
		  if (i == 0)
			printf(": ");
		  else
			printf(", ");
		  printf("\"%s\"", constraints[i]);
		}
		printf("\n");
	  }
	  else
		printf("Access is granted\n");
	}

	return(0);
  }

  if (!quiet_flag)
	printf("Access testing failed\n");

  return(1);
}

#else

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

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

  exit(1);
}
#endif
