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

/*
 * Rlink - access control rule links
 *
 * Rname+i syntax:
 *  <rname> [ {':' <ident>} | {';' <iptr>} ]
 * Where <rname> is an Rname, <ident> is an encrypted string obtained by
 * conflating the <rname>, a space, and a concise identity, and <iptr>
 * is an indirect identity pointer (an identifier that is mapped by an
 * expression into a concise identity).
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: rlink.c 2542 2012-01-11 19:40:13Z brachman $";
#endif

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

#ifndef PROG

/* How is the rname attached to the URI? */
typedef enum Rlink_mode {
  RLINK_MODE_DACS_ACS = 0,
  RLINK_MODE_QUERY    = 1,
  RLINK_MODE_PATH     = 2
} Rlink_mode;

typedef enum Rrlink_ident_mode {
  RLINK_IDENT_NONE     = 0,
  RLINK_IDENT_DIRECT   = 1,
  RLINK_IDENT_INDIRECT = 2
} Rlink_ident_mode;

typedef struct Rlink_user {
  char *username;
  char *password;
  int count;
  time_t begins_secs;
  time_t ends_secs;
} Rlink_user;

typedef struct CmdFlags {
  char *vfs_uri;
  Dsvec *users;
  char *rname;
  Rlink_ident_mode imode;
  char *ident;
  char *iptr;
  Rlink_mode lmode;
  char *redirect;
  time_t expires_secs;
  int default_count;
  char *default_password;
  time_t default_begins_secs;
  time_t default_ends_secs;
  char *outfile;
  Dsvec *names;
} CmdFlags;

static char *rname_alphabet = RNAME_ALPHABET;
static int rname_length = RNAME_LENGTH;
static char *passwd_algname = NULL;
static char *item_type = ITEM_TYPE_RLINKS;

static MAYBE_UNUSED const char *log_module_name = "rlink";

/*
 * {-a | -allow} username
 * -begin date
 * -c counter
 * -end date
 * -expires date
 * {-i | -ident} ident
 * -imode {direct | indirect | none}
 * -iptr iptr
 * -lmode acs|query|path
 * -out [- | file]
 * -p password
 * -pf password-file
 * -palg digest-algname
 * -r uri
 * -vfs vfs_uri
 * --
 */
static CmdFlags *
scan_flags(int argc, char **argv)
{
  int i;
  char *errmsg;
  CmdFlags *flags;
  Rlink_user *user;

  flags = ALLOC(CmdFlags);
  flags->names = NULL;
  flags->users = NULL;
  flags->vfs_uri = NULL;
  flags->default_count = 0;
  flags->imode = RLINK_IDENT_NONE;
  flags->ident = NULL;
  flags->iptr = NULL;
  flags->rname = NULL;
  flags->default_begins_secs = flags->default_ends_secs = 0;
  flags->default_password = NULL;
  flags->outfile = NULL;
  flags->redirect = NULL;
  flags->lmode = RLINK_MODE_DACS_ACS;
  flags->expires_secs = 0;

  user = NULL;

  for (i = 1; i < argc; i++) {
	if (streq(argv[i], "-a") || streq(argv[i], "-allow")) {
	  if (++i == argc)
		return(NULL);
	  if (user != NULL) {
		if (flags->users == NULL)
		  flags->users = dsvec_init(NULL, sizeof(Rlink_user));
		dsvec_add_ptr(flags->users, user);
	  }

	  user = ALLOC(Rlink_user);
	  user->username = argv[i];
	  user->password = NULL;
	  user->count = -1;
	  user->begins_secs = 0;
	  user->ends_secs = 0;
	}
	else if (streq(argv[i], "-begin")) {
	  char *begins;
	  time_t secs;

	  if (++i == argc)
		return(NULL);
      begins = argv[i];
      if (!is_signed_digit_string(begins)
          && utc_date_string_to_secs(&secs, begins) == -1) {
        errmsg = "Usage: Invalid -begin date";
		return(NULL);
      }
	  if (user == NULL) {
		if (flags->default_begins_secs != 0)
		  return(NULL);
		flags->default_begins_secs = secs;
	  }
	  else
		user->begins_secs = secs;
	}
	else if (streq(argv[i], "-c")) {
	  int c;

	  if (++i == argc)
		return(NULL);
	  if (strnum(argv[i], STRNUM_I, &c) == -1 || c <= 0)
		return(NULL);
	  if (user == NULL)
		flags->default_count = c;
	  else
		user->count = c;
	}
	else if (streq(argv[i], "-end")) {
	  char *ends;
	  time_t secs;

	  if (++i == argc)
		return(NULL);
      ends = argv[i];
      if (!is_signed_digit_string(ends)
          && utc_date_string_to_secs(&secs, ends) == -1) {
        errmsg = "Usage: Invalid -end date";
		return(NULL);
      }
	  if (user == NULL) {
		if (flags->default_ends_secs != 0)
		  return(NULL);
		flags->default_ends_secs = secs;
	  }
	  else
		user->ends_secs = secs;
	}
	else if (streq(argv[i], "-expires")) {
	  char *expires;
	  unsigned int es;

	  if (++i == argc)
		return(NULL);

      expires = argv[i];
      if (strnum(expires, STRNUM_UI, &es) != -1) {
		time(&flags->expires_secs);
		flags->expires_secs += es;
	  }
	  else if (utc_date_string_to_secs(&flags->expires_secs, expires) == -1) {
        errmsg = "Usage: Invalid expiry date";
		return(NULL);
      }
	}
	else if (streq(argv[i], "-i") || streq(argv[i], "-ident")) {
	  if (++i == argc)
		return(NULL);
	  if (flags->ident != NULL || flags->iptr != NULL)
		return(NULL);
	  flags->ident = argv[i];
	}
	else if (streq(argv[i], "-imode")) {
	  if (++i == argc)
		return(NULL);
	  if (strcaseeq(argv[i], "direct"))
		flags->imode = RLINK_IDENT_DIRECT;
	  else if (strcaseeq(argv[i], "indirect"))
		flags->imode = RLINK_IDENT_INDIRECT;
	  else if (strcaseeq(argv[i], "none"))
		flags->imode = RLINK_IDENT_NONE;
	  else {
		log_msg((LOG_ERROR_LEVEL, "Unrecognized imode type: \"%s\"", argv[i]));
		return(NULL);
	  }
	}
	else if (streq(argv[i], "-iptr")) {
	  if (++i == argc)
		return(NULL);
	  if (flags->iptr != NULL)
		return(NULL);
	  flags->iptr = argv[i];
	}
	else if (streq(argv[i], "-lmode")) {
	  if (++i == argc)
		return(NULL);
	  if (strcaseeq(argv[i], "dacs_acs") || strcaseeq(argv[i], "acs"))
		flags->lmode = RLINK_MODE_DACS_ACS;
	  else if (strcaseeq(argv[i], "query"))
		flags->lmode = RLINK_MODE_QUERY;
	  else if (strcaseeq(argv[i], "path"))
		flags->lmode = RLINK_MODE_PATH;
	  else {
		log_msg((LOG_ERROR_LEVEL, "Unrecognized lmode type: \"%s\"", argv[i]));
		return(NULL);
	  }
	}
	else if (streq(argv[i], "-out")) {
	  if (++i == argc)
		return(NULL);
	  if (flags->outfile != NULL)
		return(NULL);
	  flags->outfile = argv[i];
	}
	else if (streq(argv[i], "-p")) {
	  if (++i == argc)
		return(NULL);
	  if (user == NULL)
		flags->default_password = argv[i];
	  else
		user->password = argv[i];
	}
	else if (streq(argv[i], "-pf")) {
	  char *passwd;

	  if (argv[++i] == NULL)
		return(NULL);

	  if (streq(argv[i], "-"))
		passwd = get_passwd(GET_PASSWD_STDIN, NULL);
	  else
		passwd = get_passwd(GET_PASSWD_FILE, argv[i]);
	  if (passwd == NULL) {
		log_msg((LOG_ERROR_LEVEL,
				 "Error reading password from \"%s\"", argv[i]));
		return(NULL);
	  }

	  if (user == NULL)
		flags->default_password = passwd;
	  else
		user->password = passwd;
	}
	else if (streq(argv[i], "-palg")) {
	  if (++i == argc)
		return(NULL);
	  passwd_algname = argv[i];
	}
	else if (streq(argv[i], "-r")) {
	  if (++i == argc)
		return(NULL);
	  if (flags->redirect != NULL)
		return(NULL);
	  flags->redirect = argv[i];
	}
	else if (streq(argv[i], "-ralpha")) {
	  if (++i == argc)
		return(NULL);
	  rname_alphabet = argv[i];
	}
	else if (streq(argv[i], "-rlen")) {
	  if (++i == argc)
		return(NULL);
	  if (strnum(argv[i], STRNUM_I, &rname_length) == -1 || rname_length <= 0)
		return(NULL);
	}
	else if (streq(argv[i], "-rname")) {
	  if (++i == argc)
		return(NULL);
	  if (flags->rname != NULL)
		return(NULL);
	  flags->rname = argv[i];
	}
	else if (streq(argv[i], "-vfs")) {
	  if (++i == argc)
		return(NULL);
	  if (flags->vfs_uri != NULL)
		return(NULL);
	  flags->vfs_uri = argv[i];
	}
	else if (streq(argv[i], "--")) {
	  i++;
	  break;
	}
	else if (argv[i][0] == '-') {
	  log_msg((LOG_ERROR_LEVEL, "Unrecognized flag: %s", argv[i]));
	  return(NULL);
	}
	else
	  break;
  }

  if (i < argc) {
	flags->names = dsvec_init(NULL, sizeof(char *));
	while (i < argc)
	  dsvec_add_ptr(flags->names, argv[i++]);
  }

  if (user != NULL) {
	if (flags->users == NULL)
	  flags->users = dsvec_init(NULL, sizeof(Rlink_user));
	dsvec_add_ptr(flags->users, user);
  }

  return(flags);
}

char *
rlink_get(char *vfs_uri, char *rname)
{
  char *buf;
  Vfs_handle *h;

  if (vfs_uri == NULL)
	vfs_uri = item_type;

  if ((h = vfs_open_any(vfs_uri)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not load Rlink from \"%s\"", vfs_uri));
	return(NULL);
  }

  if (vfs_get(h, rname, (void **) &buf, NULL) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not get Rlink \"%s\" from \"%s\"",
			 rname, vfs_uri));
	vfs_close(h);
	return(NULL);
  }

  if (vfs_close(h) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not close Rlink \"%s\"", vfs_uri));
	return(NULL);
  }

  return(buf);
}

static int
rlink_put(char *vfs_uri, char *rname, char *buf)
{
  Parse_xml_error err;
  Vfs_handle *h;

  /* Do a syntax check. */
  if (check_acl(buf, &err) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: %s (Line %d, Character offset %d)",
			 err.mesg, err.line, err.pos));
	return(-1);
  }

  if (vfs_uri == NULL)
	vfs_uri = item_type;

  if ((h = vfs_open_any(vfs_uri)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not open Rlink from \"%s\"", vfs_uri));
	return(-1);
  }

  if (vfs_put(h, rname, (void *) buf, strlen(buf)) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not put Rlink \"%s\" to \"%s\"",
			 vfs_uri, rname));
	vfs_close(h);
	return(-1);
  }

  if (vfs_close(h) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not close Rlink \"%s\"", vfs_uri));
	return(-1);
  }

  return(0);
}

static int
rlink_delete(char *vfs_uri, char *rname)
{
  Vfs_handle *h;

  if (vfs_uri == NULL)
	vfs_uri = item_type;

  if ((h = vfs_open_any(vfs_uri)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not open Rlink from \"%s\"", vfs_uri));
	return(-1);
  }

  if (vfs_delete(h, rname) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not delete Rlink \"%s\" to \"%s\"",
			 vfs_uri, rname));
	vfs_close(h);
	return(-1);
  }

  if (vfs_close(h) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not close Rlink \"%s\"", vfs_uri));
	return(-1);
  }

  return(0);
}

static int
list_add(char *naming_context, char *name, void ***ptr)
{
  char *p;
  Dsvec *dsv;

  if ((dsv = (Dsvec *) *ptr) == NULL) {
    *ptr = (void **) dsvec_init(NULL, sizeof(char *));
    dsv = (Dsvec *) *ptr;
  }

  if (naming_context != NULL && (p = strprefix(name, naming_context)) != NULL)
    dsvec_add_ptr(dsv, strdup(p + 1));
  else
    dsvec_add_ptr(dsv, strdup(name));

  return(1);
}

static int
rlink_list(char *vfs_uri)
{
  int i, n;
  Dsvec *names;
  Vfs_handle *h;

  if (vfs_uri == NULL)
	vfs_uri = item_type;

  if ((h = vfs_open_any(vfs_uri)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not open Rlink from \"%s\"", vfs_uri));
	return(-1);
  }

  names = NULL;
  if ((n = vfs_list(h, NULL, NULL, list_add, (void ***) &names)) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not list Rlinks from \"%s\"", vfs_uri));
	return(-1);
  }

  for (i = 0; i < n; i++)
	printf("%s\n", (char *) dsvec_ptr_index(names, i));

  if (vfs_close(h) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not close Rlink \"%s\"", vfs_uri));
	return(-1);
  }

  return(0);
}

#ifdef NOTDEF
static char *
make_iptr(char *ident)
{
  unsigned int hlen;
  char *value;
  unsigned char *outp;
  Crypt_keys *ck;
  Hmac_digest *hd;
  Hmac_handle *h;

#define IPTR_DIGEST_NAME	"SHA1"

  if ((hd = hmac_lookup_digest_by_name(IPTR_DIGEST_NAME)) == NULL)
	return(NULL);

  ck = crypt_keys_from_vfs(ITEM_TYPE_JURISDICTION_KEYS);
  h = crypto_hmac_open(hd->name, ck->hmac_key, CRYPTO_HMAC_KEY_LENGTH);
  crypt_keys_free(ck);
  crypto_hmac_hash(h, ident, strlen(ident));
  outp = crypto_hmac_close(h, NULL, &hlen);
  strba64(outp, hlen, &value);

  return(value);
}
#endif

static char *
make_rname(Rlink_ident_mode imode, char *rname, char *ident, char *iptr)
{
  char *r, *rn;

  if (rname == NULL)
	rn = crypto_make_random_string_from_spec(rname_alphabet, rname_length, 0);
  else
	rn = rname;

  if (imode == RLINK_IDENT_INDIRECT) {
	if (iptr == NULL)
	  iptr = crypto_make_random_string_from_spec(rname_alphabet,
												 rname_length, 0);
	r = ds_xprintf("%s%c%s", rn, RNAME_IPTR_SEP_CHAR, iptr);
  }
  else if (imode == RLINK_IDENT_DIRECT) {
	unsigned int enc_len;
	unsigned char *enc_str;
	char *istr;
	Crypt_keys *ck;
	Ds ds;

	if (ident == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "An identity must be specified"));
	  return(NULL);
	}

	if (parse_ident_string(ident, NULL) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid ident: \"%s\"", ident));
	  return(NULL);
	}

	if ((ck = crypt_keys_from_vfs(ITEM_TYPE_JURISDICTION_KEYS)) == NULL)
	  return(NULL);
	ds_init(&ds);
	ds_asprintf(&ds, "%s %s", rn, ident);
	enc_len = crypto_encrypt_string(ck, (unsigned char *) ds_buf(&ds),
									ds_len(&ds) + 1, &enc_str);
	crypt_keys_free(ck);

	strba64(enc_str, enc_len, &istr); 
	r = ds_xprintf("%s%c%s", rn, RNAME_IDENT_SEP_CHAR, url_encode(istr, 0));
  }
  else
	r = url_encode(rn, 0);

  return(r);
}

static Acl_rule *
init_acl_rule(Acl_status status)
{
  Acl_rule *acl;
  Rule *rule;
  Services *services;

  acl = ALLOC(Acl_rule);
  acl->status = status;
  acl->name = NULL;
  acl->expires_expr = NULL;
  acl->identities = NULL;
  acl->services = services = ALLOC(Services);
  acl->rules = rule = ALLOC(Rule);
  init_grant_attrs(&acl->default_attrs);

  services->shared = NULL;
  services->service = NULL;
  
  rule->id = NULL;
  rule->precondition = NULL;
  rule->allows = NULL;
  rule->denies = NULL;
  rule->order_str = NULL;
  rule->order = ACS_ORDER_UNKNOWN;
  init_grant_attrs(&rule->default_attrs);
  rule->next = NULL;

  return(acl);
}

static int
op_check(int argc, char **argv)
{
  char *buf;
  CmdFlags *flags;
  Parse_xml_error err;

  if ((flags = scan_flags(argc, argv)) == NULL)
	return(-1);

  if (dsvec_len(flags->names) != 1)
	return(-1);

  buf = rlink_get(flags->vfs_uri, (char *) dsvec_ptr_index(flags->names, 0));
  if (buf == NULL)
	return(-1);

  /* Do a syntax check. */
  if (check_acl(buf, &err) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Parse error: %s (Line %d, Character offset %d)",
			 err.mesg, err.line, err.pos));
	return(-1);
  }

  return(0);
}

static int
op_clone(int argc, char **argv)
{
  char *buf, *rname, *rule_str;
  Acl_rule *acl;
  CmdFlags *flags;

  if ((flags = scan_flags(argc, argv)) == NULL)
	return(-1);

  if (dsvec_len(flags->names) != 1)
	return(-1);

  buf = rlink_get(flags->vfs_uri, (char *) dsvec_ptr_index(flags->names, 0));
  if (buf == NULL)
	return(-1);

  if (parse_xml_acl(buf, &acl) == -1)
	return(-1);

  acl->name = rname = ((flags->rname != NULL)
					   ? flags->rname
					   : make_rname(RLINK_IDENT_NONE, NULL, NULL, NULL));
  if (flags->expires_secs != 0)
	acl->expires_expr = ds_xprintf("time(now) ge %lu",
								   (unsigned long) flags->expires_secs);

  if ((rule_str = acl_xml_text(acl)) == NULL)
	return(-1);

  if (flags->outfile == NULL) {
	if (rlink_put(flags->vfs_uri, rname, rule_str) == -1)
	  return(-1);
	printf("%s\n", rname);
  }
  else if (streq(flags->outfile, "-"))
	printf("%s", rule_str);
  else {
	int fd;

	fd = open(flags->outfile, 0600, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL);
	if (fd == -1)
	  return(-1);
	if (write_buffer(fd, rule_str, strlen(rule_str)) != 0)
	  return(-1);
	close(fd);
  }

  return(0);
}

static Allow *
create_allow(Rlink_user *user, CmdFlags *flags)
{
  char *pwd;
  Allow *allow;

  allow = ALLOC(Allow);
  allow->id = NULL;
  allow->expr = ds_init(NULL);
  init_grant_attrs(&allow->attrs);
  allow->next = NULL;

  if (user == NULL || (pwd = user->password) == NULL)
	pwd = flags->default_password;

  if (user != NULL)
	ds_asprintf(allow->expr, "user(\"%s\")\n", user->username);

  if (pwd != NULL) {
	char *str;
	Passwd_digest_alg alg;

	if (passwd_algname == NULL) {
	  if (passwd_get_digest_algorithm(&passwd_algname, &alg) == -1)
		return(NULL);
	}
	else {
	  alg = passwd_lookup_digest_algorithm(passwd_algname);
	  if (alg == PASSWD_ALG_NONE)
		return(NULL);
	}
	if ((str = passwd_make_digest(alg, pwd, NULL)) == NULL)
	  return(NULL);

	ds_asprintf(allow->expr,
				"    %s password(check, ${Args::PASSWORD}, \"%s\")\n",
				(user != NULL) ? "and" : "", str);
  }

  return(allow);
}

/*
 * Create a new Rlink.
 */
static int
op_create(int argc, char **argv)
{
  int i;
  char *rname, *rule_str;
  Acl_rule *acl;
  Allow *allow, *allow_prev;
  CmdFlags *flags;
  Service *service, *service_prev;

  if ((flags = scan_flags(argc, argv)) == NULL)
	return(-1);

  if (flags->names == NULL) {
	log_msg((LOG_ERROR_LEVEL, "One or more path arguments are required"));
	return(-1);
  }

  acl = init_acl_rule(ACL_STATUS_ENABLED);

  service_prev = NULL;
  for (i = 0; i < dsvec_len(flags->names); i++) {
	char *p;

	p = (char *) dsvec_ptr_index(flags->names, i);
	service = ALLOC(Service);
	service->id = NULL;
	service->url_pattern = p;
	service->url_expr = NULL;
	service->url_path = NULL;
	service->rule_uri = NULL;
	service->next = NULL;
	if (service_prev == NULL)
	  acl->services->service = service;
	else
	  service_prev->next = service;
	service_prev = service;
  }

  acl->rules->order_str = "allow,deny";
  acl->rules->order = ACS_ORDER_ALLOW_THEN_DENY;

  if (flags->redirect != NULL) {
	Deny deny;

	if (dsvec_len(flags->users)) {
	  log_msg((LOG_ERROR_LEVEL, "No users allowed with -r flag"));
	  return(-1);
	}
	deny.id = NULL;
	deny.expr = ds_init(NULL);
	deny.next = NULL;
	ds_asprintf(deny.expr, "redirect(BY_SIMPLE_REDIRECT, \"%s\")",
				flags->redirect);
	acl->rules->denies = &deny;
  }
  else {
	allow_prev = NULL;
	if (dsvec_len(flags->users) == 0) {
	  allow = create_allow(NULL, flags);
	  acl->rules->allows = allow;
	}
	else {
	  for (i = 0; i < dsvec_len(flags->users); i++) {
		char *pwd;
		Rlink_user *user;

		user = (Rlink_user *) dsvec_ptr_index(flags->users, i);
		if ((allow = create_allow(user, flags)) == NULL)
		  return(-1);
		if (allow_prev == NULL)
		  acl->rules->allows = allow;
		else
		  allow_prev->next = allow;
		allow_prev = allow;
	  }
	}
  }

  acl->name = rname = ((flags->rname != NULL)
					   ? flags->rname
					   : make_rname(RLINK_IDENT_NONE, NULL, NULL, NULL));
  if (flags->expires_secs != 0)
	acl->expires_expr = ds_xprintf("time(now) ge %lu",
								   (unsigned long) flags->expires_secs);

  if (flags->imode == RLINK_IDENT_INDIRECT) {
	char *iptr;
	Identity *id;

	if (flags->ident == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "An identity must be specified"));
	  return(-1);
	}

	if (flags->iptr == NULL)
	  iptr = crypto_make_random_string_from_spec(rname_alphabet,
												 rname_length, 0);
	else
	  iptr = flags->iptr;

	id = ALLOC(Identity);
	id->id = NULL;
	id->iptr = iptr;
	id->ident = flags->ident;
	id->selector_expr = ds_xprintf("${DACS::RIPTR:?} eq \"%s\"", iptr);
	id->next = NULL;
	acl->identities = id;
  }

  if ((rule_str = acl_xml_text(acl)) == NULL)
	return(-1);

  if (flags->outfile == NULL) {
	if (rlink_put(flags->vfs_uri, rname, rule_str) == -1)
	  return(-1);
	printf("%s\n", rname);
  }
  else if (streq(flags->outfile, "-"))
	printf("%s", rule_str);
  else {
	int fd;
	char *wd;

#ifdef NOTDEF
	if (flags->outfile[0] != '/')
	  wd = getcwd(wdbuf, sizeof(wdbuf));
	else
	  wd = NULL;
#else
	wd = NULL;
#endif

	fd = open(flags->outfile, O_CREAT | O_WRONLY | O_TRUNC, 0600);
	if (fd == -1) {
	  if (wd != NULL)
		log_err((LOG_ERROR_LEVEL, "Cannot output to \"%s/%s\"",
				 wd, flags->outfile));
	  else
		log_err((LOG_ERROR_LEVEL, "Cannot output to \"%s\"", flags->outfile));
	  return(-1);
	}

	if (write_buffer(fd, rule_str, strlen(rule_str)) != 0) {
	  log_msg((LOG_ERROR_LEVEL, "Error writing to \"%s\"", flags->outfile));
	  return(-1);
	}
	close(fd);
  }

  return(0);
}

static int
op_edit(int argc, char **argv)
{
  char *buf, *rname, *tmpfile;
  size_t buflen;
  CmdFlags *flags;
  FILE *fp;

  if ((flags = scan_flags(argc, argv)) == NULL)
	return(-1);

  if (dsvec_len(flags->names) != 1) {
	log_msg((LOG_ERROR_LEVEL, "An Rname argument is required"));
	return(-1);
  }

  rname = (char *) dsvec_ptr_index(flags->names, 0);
  if ((buf = rlink_get(flags->vfs_uri, rname)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not get Rlink \"%s\"", rname));
	return(-1);
  }

  /*
   * Use create_temp_filename() and create_temp_file() instead?
   */
  tmpfile = create_temp_filename(NULL);
  if ((fp = create_temp_file(tmpfile)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not create temp file \"%s\"", tmpfile));
	return(-1);
  }

  buflen = strlen(buf);
  if (write_buffer(fileno(fp), buf, buflen) != 0) {
	log_msg((LOG_ERROR_LEVEL, "Could not write to temp file \"%s\"", tmpfile));
	unlink(tmpfile);
	fclose(fp);
	return(-1);
  }

 retry:

  if (edit_file(tmpfile) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Edit of temp file \"%s\" failed", tmpfile));
	unlink(tmpfile);
	fclose(fp);
	return(-1);
  }

  if (load_file(tmpfile, &buf, &buflen) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not read temp file \"%s\"", tmpfile));
	goto retry;
  }

  if (rlink_put(flags->vfs_uri, rname, buf) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not copy temp file \"%s\"", tmpfile));
	goto retry;
  }

  unlink(tmpfile);
  fclose(fp);

  return(0);
}

static int
op_delete(int argc, char **argv)
{
  int st;
  CmdFlags *flags;

  if ((flags = scan_flags(argc, argv)) == NULL)
	return(-1);

  if (dsvec_len(flags->names) != 1)
	return(-1);

  st = rlink_delete(flags->vfs_uri, (char *) dsvec_ptr_index(flags->names, 0));

  return(st);
}

static int
op_list(int argc, char **argv)
{
  int st;
  CmdFlags *flags;

  if ((flags = scan_flags(argc, argv)) == NULL)
	return(-1);

  if (dsvec_len(flags->names) != 0)
	return(-1);

  st = rlink_list(flags->vfs_uri);

  return(st);
}

/*
 * Emit a URI containing a given rname.
 */
static int
op_rlink(int argc, char **argv)
{
  char *base_prefix, *ident, *iptr, *path, *rname, *xpath;
  CmdFlags *flags;
  Uri *uri;

  if ((flags = scan_flags(argc, argv)) == NULL)
	return(-1);

  if (dsvec_len(flags->names) != 2) {
	log_msg((LOG_ERROR_LEVEL,
			 "An rname argument followed by a path is required"));
	return(-1);
  }

  rname = (char *) dsvec_ptr_index(flags->names, 0);
  path = (char *) dsvec_ptr_index(flags->names, 1);

  if (flags->imode != RLINK_IDENT_INDIRECT && flags->iptr != NULL) {
	log_msg((LOG_ERROR_LEVEL, "No iptr can be given"));
	return(-1);
  }

  iptr = flags->iptr;
  ident = flags->ident;

  if (flags->imode == RLINK_IDENT_INDIRECT && iptr == NULL) {
	char *buf;
	Acl_rule *acl;
	Identity *id;
	Kwv *kwv_conf;

	if (ident == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "An ident or iptr must be specified"));
	  return(-1);
	}

	if ((buf = rlink_get(flags->vfs_uri, rname)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot load rule '%s'", rname));
	  return(-1);
	}

	if (parse_xml_acl(buf, &acl) == -1)
	  return(-1);

	/* Lookup iptr using the identity */
	if (parse_ident_string(ident, NULL) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid ident: \"%s\"", ident));
	  return(-1);
	}

	kwv_conf = var_ns_lookup_kwv(dacs_conf->conf_var_ns, "Conf");
	for (id = acl->identities; id != NULL; id = id->next) {
	  if (parse_ident_string(id->ident, NULL) == -1) {
		log_msg((LOG_ERROR_LEVEL, "Invalid ident: \"%s\"", id->ident));
		return(-1);
	  }
	  if (compare_idents(ident, id->ident, kwv_conf) == 1)
		break;
	}
	if (id == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "No matching ident attribute found"));
	  return(-1);
	}
	iptr = id->iptr;
  }
  else if (flags->imode == RLINK_IDENT_DIRECT && ident == NULL) {
	char *buf;
	Acl_rule *acl;
	Identity *id;

	if (iptr == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "An ident or iptr must be specified"));
	  return(-1);
	}

	if ((buf = rlink_get(flags->vfs_uri, rname)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot load rule '%s'", rname));
	  return(-1);
	}

	if (parse_xml_acl(buf, &acl) == -1)
	  return(-1);

	/* Lookup the identity using the iptr */
	for (id = acl->identities; id != NULL; id = id->next) {
	  if (streq(id->iptr, iptr))
		break;
	}
	if (id == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "No matching iptr attribute found"));
	  return(-1);
	}
	ident = id->ident;
  }

  if (*path == '/') {
	if ((base_prefix = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
										"rlink_base_prefix")) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "rlink_base_prefix must be set"));
	  return(-1);
	}
	xpath = ds_xprintf("%s%s", base_prefix, path);
  }
  else
	xpath = path;

  if ((uri = uri_parse(xpath)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Invalid path: \"%s\"", xpath));
	return(-1);
  }

  if (flags->lmode == RLINK_MODE_DACS_ACS) {
	/* XXX if DACS_ACS exists, the Rlink should be added to its arg list */
	printf("%s%s?DACS_ACS=-rname+%s%s\n", uri_scheme_authority(uri),
		   path, make_rname(flags->imode, rname, ident, iptr),
		   non_null(uri->query_string));
  }
  else if (flags->lmode == RLINK_MODE_QUERY) {
	/* XXX check for existing RNAME arg */
	printf("%s%s?RNAME=%s%s\n", uri_scheme_authority(uri),
		   path, make_rname(flags->imode, rname, ident, iptr),
		   non_null(uri->query_string));
  }
  else if (flags->lmode == RLINK_MODE_PATH) {
	/* XXX Interpolate? */
	;
  }
  else
	return(-1);

  return(0);
}

/*
 * Generate and emit one Rname in the requested format.
 */
static int
op_rname(int argc, char **argv)
{
  char *rname;
  CmdFlags *flags;

  if ((flags = scan_flags(argc, argv)) == NULL)
	return(-1);

  rname = make_rname(flags->imode, flags->rname, flags->ident, flags->iptr);
  if (rname == NULL)
	return(-1);

  printf("%s\n", rname);

  return(0);
}

static int
op_ident(int argc, char **argv)
{
  int i;
  char *rname;
  CmdFlags *flags;
  Rlink *rlink;

  if ((flags = scan_flags(argc, argv)) == NULL)
	return(-1);

  rlink = ALLOC(Rlink);
  for (i = 0; i < dsvec_len(flags->names); i++) {
	rlink->rname = NULL;
	rlink->ident = NULL;
	rlink->iptr = NULL;
	rlink->expr = NULL;
	rlink->rule_vfs = NULL;
	rlink->credentials = NULL;

	rname = (char *) dsvec_ptr_index(flags->names, i);
	if (acs_rname_parse(rlink, rname) == -1)
	  return(-1);

	printf("%s:", rlink->rname);
	if (rlink->ident != NULL)
	  printf(" ident='%s'", rlink->ident);
	if (rlink->iptr != NULL)
	  printf(" iptr='%s'", rlink->iptr);
	if (rlink->expr != NULL)
	  printf(" expr='%s'", rlink->expr);
	if (rlink->rule_vfs != NULL)
	  printf(" rule_vfs='%s'", rlink->rule_vfs);
	printf("\n");
  }

  return(0);
}

static int
op_show(int argc, char **argv)
{
  char *buf;
  CmdFlags *flags;

  if ((flags = scan_flags(argc, argv)) == NULL)
	return(-1);

  if (dsvec_len(flags->names) != 1) {
	log_msg((LOG_ERROR_LEVEL, "An Rname is required"));
	return(-1);
  }

  buf = rlink_get(flags->vfs_uri, (char *) dsvec_ptr_index(flags->names, 0));
  if (buf == NULL)
	return(-1);

  printf("%s", buf);

  return(0);
}

static void
show_usage(void)
{

  fprintf(stderr, "Usage:\n");
  fprintf(stderr, "  rlink [dacsoptions] clone [options] rname\n");
  fprintf(stderr, "  rlink [dacsoptions] check [options] rname\n");
  fprintf(stderr, "  rlink [dacsoptions] create [options] path [...]\n");
  fprintf(stderr, "  rlink [dacsoptions] delete [options] rname\n");
  fprintf(stderr, "  rlink [dacsoptions] edit [options] rname\n");
  fprintf(stderr, "  rlink [dacsoptions] list [options]\n");
  fprintf(stderr, "  rlink [dacsoptions] rlink [options] rname path\n");
  fprintf(stderr, "  rlink [dacsoptions] rname [options]\n");
  fprintf(stderr, "  rlink [dacsoptions] show [options] rname\n");
  fprintf(stderr, "dacsoptions: %s\n", standard_command_line_usage);
  fprintf(stderr, "options:\n");
  fprintf(stderr, "-a | -allow username: allow username, set current user\n");
  fprintf(stderr, "-begin date: not implemented yet\n");
  fprintf(stderr, "-c count: not implemented yet\n");
  fprintf(stderr, "-end date: not implemented yet\n");
  fprintf(stderr, "-expires date: value for the rule's expires_expr\n");
  fprintf(stderr, "-i | -ident ident: a concise user identity\n");
  fprintf(stderr, "-imode {direct | indirect | none}: identity attachment mode\n");
  fprintf(stderr, "-iptr iptr: use iptr as the indirect identity label\n");
  fprintf(stderr, "-lmode {acs | query | path}: link attachment mode\n");
  fprintf(stderr, "-out {filename | -}: rule output\n");
  fprintf(stderr, "-p password: current user must provide this password\n");
  fprintf(stderr, "-palg digest-algname: password hashing algorithm\n");
  fprintf(stderr, "-r uri: rule will redirect users to this uri\n");
  fprintf(stderr, "-ralpha str: alphabet for Rname and iptr generation\n");
  fprintf(stderr, "-rlen #: length of a generated Rname or iptr\n");
  fprintf(stderr, "-rname str: use str as the Rname\n");
  fprintf(stderr, "-vfs vfs_uri: use rlinks in vfs_uri [default: rlinks]\n");
  fprintf(stderr, "--:  end option flags\n");

  exit(1);
}

int
rlink_main(int argc, char **argv, int do_init, void *main_out)
{
  int st;
  char *errmsg;

  errmsg = "Internal error";
  if (dacs_init(DACS_UTILITY, &argc, &argv, NULL, &errmsg) == -1) {
	fprintf(stderr, "Could not initialize: %s\n", errmsg);

  usage:
	show_usage();
	return(-1);
  }

  if (argv[1] == NULL)
	goto usage;

  if (!dacs_saw_command_line_log_level) {
    /* Override the config file default */
    log_set_level(NULL, LOG_WARN_LEVEL);
  }

  if (strcaseeq(argv[1], "create"))
	st = op_create(argc - 1, argv + 1);
  else if (strcaseeq(argv[1], "delete"))
	st = op_delete(argc - 1, argv + 1);
  else if (strcaseeq(argv[1], "check"))
	st = op_check(argc - 1, argv + 1);
  else if (strcaseeq(argv[1], "clone"))
	st = op_clone(argc - 1, argv + 1);
  else if (strcaseeq(argv[1], "edit"))
	st = op_edit(argc - 1, argv + 1);
  else if (strcaseeq(argv[1], "ident"))
	st = op_ident(argc - 1, argv + 1);
  else if (strcaseeq(argv[1], "list"))
	st = op_list(argc - 1, argv + 1);
  else if (strcaseeq(argv[1], "rlink"))
	st = op_rlink(argc - 1, argv + 1);
  else if (strcaseeq(argv[1], "rname"))
	st = op_rname(argc - 1, argv + 1);
  else if (strcaseeq(argv[1], "show"))
	st = op_show(argc - 1, argv + 1);
  else {
	fprintf(stderr, "Unrecognized operation: \"%s\"\n", argv[1]);
	goto usage;
  }

  exit(st ? 1 : 0);
}

#else

static MAYBE_UNUSED const char *log_module_name = "dacsrlink";

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

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

  exit(1);
}
#endif
