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

/*
 * Command line authentication and credentials management utility,
 * HTTP cookie store, and cookie retrieval API.
 *
 * Format of the cache file:
 * <type> <value>
 * where <type> is one of:
 *   o "cred" if <value> is DACS credentials
 *   o "auth" if <value> is a DACS identity followed by the URI of the service
 *            that was called to authenticate that identity.
 * Any other <type> voids the entire cache file.
 *
 * This could be extended into a DACS service which would offer secure
 * storage of credentials.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: cred.c 2594 2012-10-19 17:28:49Z brachman $";
#endif

#include "dacs.h"

static MAYBE_UNUSED char *log_module_name = "dacscred";

#ifndef PROG

int version_file_cred_c;

#define AUTH_KEYWORD		"auth"
#define COOKIE_KEYWORD		"cookie"
#define CRED_KEYWORD		"cred"

static char *cachefile = NULL;
static Crypt_keys *crypt_keys = NULL;

static Crypt_keys *
get_keys(char *prompt, int must_prompt)
{
  unsigned int needed;
  unsigned char *rbuf;
  char *passwd;
  Ds ds;
  
  if (crypt_keys != NULL && !must_prompt)
	return(crypt_keys);

  ds_init(&ds);
  ds.clear_flag = 1;
  if ((passwd = ds_prompt(&ds, prompt, DS_PROMPT_NOECHO)) == NULL
	  || *passwd == '\0')
	return(NULL);

  needed = AUTH_CRYPT_KEY_LENGTH + CRYPTO_HMAC_KEY_LENGTH;
  crypto_make_randomized_from_passphrase((unsigned char *) passwd,
										 strlen(passwd), needed, &rbuf);
  ds_free(&ds);

  crypt_keys = ALLOC(Crypt_keys);
  crypt_keys->auth_key = rbuf;
  crypt_keys->hmac_key = rbuf + AUTH_CRYPT_KEY_LENGTH;
  crypt_keys->public_key = crypt_keys->private_key = NULL;
  crypt_keys->public_key_str = crypt_keys->private_key_str = NULL;
  crypt_keys->public_key_pem = crypt_keys->fed_id = NULL;

  log_msg((LOG_TRACE_LEVEL, "auth_key=%s",
		   hexdump(crypt_keys->auth_key, AUTH_CRYPT_KEY_LENGTH)));
  log_msg((LOG_TRACE_LEVEL, "hmac_key=%s",
		   hexdump(crypt_keys->hmac_key, CRYPTO_HMAC_KEY_LENGTH)));
  return(crypt_keys);
}

/*
 * Load the cookie cache from PATHNAME, parse and return the cookies.
 * The cache may need to be decrypted.
 */
static Cred_cache *
cookie_cache_load(FILE *fp)
{
  int st;
  char *e, *p, *q, *s;
  unsigned char *plain;
  unsigned int plain_len;
  Cookie *c, *last;
  Cred_auth_url *a;
  Cred_cache *cache; 
  Crypt_keys *keys;
  Ds *ds;

  cache = ALLOC(Cred_cache);
  cache->cookies = NULL;
  cache->auth = dsvec_init(NULL, sizeof(Cred_auth_url));

  if (fseek(fp, 0L, SEEK_SET) == -1)
	return(NULL);

  if ((ds = ds_getf(fp)) == NULL)
	return(NULL);
  ds->clear_flag = 1;

  if ((s = ds_buf(ds)) == NULL)
	return(NULL);

  if ((keys = get_keys("DACS Cache Password: ", 0)) == NULL)
	return(NULL);
  st = crypto_decrypt_string(keys, (unsigned char *) s, ds_len(ds) - 1,
							 &plain, &plain_len);
  if (st == -1)
	return(NULL);

  s = (char *) plain;

  last = NULL;
  while ((e = strchr(s, (int) '\n')) != NULL) {
	*e++ = '\0';
	if ((p = strchr(s, (int) ' ')) == NULL) {
	  cache = NULL;
	  goto done;
	}
	*p++ = '\0';
	if (streq(s, CRED_KEYWORD)) {
	  int is_dacs_cookie;

	  if ((c = cookie_parse(p, &is_dacs_cookie)) == NULL
		  || !is_dacs_cookie) {
		cache = NULL;
		goto done;
	  }
	  c->str = strdup(p);

	  if (cache->cookies == NULL)
		cache->cookies = last = c;
	  else {
		last->next = c;
		last = c;
	  }
	}
	else if (streq(s, AUTH_KEYWORD)) {
	  if ((q = strchr(p, (int) ' ')) == NULL) {
		cache = NULL;
		goto done;
	  }
	  *q++ = '\0';
	  a = ALLOC(Cred_auth_url);
	  a->name = p;
	  a->url = q;
	  dsvec_add_ptr(cache->auth, a);
	}
	else {
	  cache = NULL;
	  goto done;
	}
	s = e;
  }

 done:

  ds_free(ds);

  return(cache);
}

static int
cache_write(FILE *fp, char *cachefile, char *prompt, Ds *ds)
{
  int st;
  unsigned int e_len;
  unsigned char *encrypted;
  Crypt_keys *keys;
  FILE *nfp;

  if ((nfp = freopen(cachefile, "w+", fp)) == NULL) {
	fclose(fp);
	return(-1);
  }

  /*
	cipher = crypto_cipher_methodbyname("AES-128-CFB");
	OpenSSL_add_all_digests();
	md = EVP_get_digestbyname("SHA1");
	needed = EVP_CIPHER_CTX_key_length(cipher) + EVP_MD_size(md);
	crypto_make_randomized_from_passphrase(passwd, strlen(passwd),
	needed, &rbuf);
	keys.auth_key = rbuf;
	keys.hmac_key = rbuf + EVP_CIPHER_CTX_key_length(cipher);
  */

  fp = NULL;
  st = -1;

  if ((keys = get_keys(prompt, 0)) == NULL)
	goto done;

  e_len = crypto_encrypt_string(keys, (unsigned char *) ds_buf(ds),
								ds_len(ds), &encrypted);

  if (fwrite(encrypted, e_len, 1, nfp) != 1) {
	perror("fwrite");
	goto done;
  }

  st = 0;

 done:
  if (fp != NULL)
	ds_fclose(fp);
  if (nfp != NULL)
	ds_fclose(nfp);

  return(st);
}

FILE *
cookie_cache_open(void)
{
  char *dacsdir;
  FILE *fp;

  dacsdir = get_dacsdir();
  if (!file_exists(dacsdir))
	return(NULL);

  cachefile = ds_xprintf("%s/credentials", dacsdir);
  if ((fp = ds_fopen_secure(cachefile, "a+", 1024)) == NULL)
	return(NULL);

  if (fchmod(fileno(fp), 0600) == -1) {
	ds_fclose(fp);
	return(NULL);
  }

  return(fp);
}

/*
 * Update the user's cookie cache using NEW_CACHE.
 * This may involve: decrypting the cache when its read, adding new cookies
 * and/or replacing existing ones, encrypting and writing the cache file.
 * Return -1 if there's a problem, 0 otherwise.
 */
int
cookie_cache_update(Cred_cache *new_cache)
{
  int st;
  unsigned int ui, uj;
  Cred_auth_url *a, *na;
  Cookie *c, *cookies, *nc;
  Cred_cache *cache;
  Ds ds;
  FILE *fp, *nfp;

  if (new_cache == NULL
	  || new_cache->cookies == NULL
	  || new_cache->auth == NULL)
	return(0);

  if ((fp = cookie_cache_open()) == NULL)
	return(-1);

  st = -1;
  nfp = NULL;

  ds_init(&ds);
  ds.clear_flag = 1;
  if ((cache = cookie_cache_load(fp)) != NULL
	  && (cookies = cache->cookies) != NULL) {
	/* Add/merge new cookies. */
	for (c = cookies; c != NULL; c = c->next) {
	  for (nc = new_cache->cookies; nc != NULL; nc = nc->next) {
		if (streq(c->name, nc->name))
		  break;
	  }
	  if (nc == NULL)
		ds_asprintf(&ds, "%s %s\n", CRED_KEYWORD, c->str);
	}
  }

  for (nc = new_cache->cookies; nc != NULL; nc = nc->next)
	ds_asprintf(&ds, "%s %s\n", CRED_KEYWORD, nc->str);
  
  if (cache != NULL && cache->auth != NULL) {
	for (ui = 0; ui < dsvec_len(cache->auth); ui++) {
	  a = (Cred_auth_url *) dsvec_ptr_index(cache->auth, ui);
	  for (uj = 0; uj < dsvec_len(new_cache->auth); uj++) {
		na = (Cred_auth_url *) dsvec_ptr_index(new_cache->auth, uj);
		if (streq(na->name, a->name))
		  break;
	  }
	  if (uj == dsvec_len(new_cache->auth))
		dsvec_add_ptr(new_cache->auth, a);
	}
  }

  for (uj = 0; uj < dsvec_len(new_cache->auth); uj++) {
	na = (Cred_auth_url *) dsvec_ptr_index(new_cache->auth, uj);
	ds_asprintf(&ds, "%s %s %s\n", AUTH_KEYWORD, na->name, na->url);
  }

  if (cache_write(fp, cachefile, "DACS Cache Password: ", &ds) == -1)
	goto done;

  st = 0;

 done:

  ds_free(&ds);

  return(st);
}

/*
 * The USERNAME might not include a federation component.
 */
char *
cache_lookup_url(char *username)
{
  unsigned int ui;
  Cred_auth_url *a;
  Cred_cache *cache;
  DACS_name dacs_name;
  FILE *fp;

  if (parse_dacs_name(username, &dacs_name) != DACS_USER_NAME)
	return(NULL);

  if ((fp = cookie_cache_open()) == NULL)
	return(NULL);
  if ((cache = cookie_cache_load(fp)) == NULL || cache->auth == NULL)
	return(NULL);

  if (dacs_name.federation != NULL) {
	for (ui = 0; ui < dsvec_len(cache->auth); ui++) {
	  a = (Cred_auth_url *) dsvec_ptr_index(cache->auth, ui);
	  if (streq(username, a->name))
		return(a->url);
	}
  }
  else {
	for (ui = 0; ui < dsvec_len(cache->auth); ui++) {
	  DACS_name dn;

	  a = (Cred_auth_url *) dsvec_ptr_index(cache->auth, ui);
	  if (parse_dacs_name(a->name, &dn) != DACS_USER_NAME)
		continue;
	  if (dacs_name.jurisdiction == NULL)
		dacs_name.jurisdiction = conf_val(CONF_JURISDICTION_NAME);
	  if (name_eq(dn.jurisdiction, dacs_name.jurisdiction,
				  DACS_NAME_CMP_CONFIG)
		  && name_eq(dn.username, dacs_name.username, DACS_NAME_CMP_CONFIG))
		return(a->url);
	}
  }

  return(NULL);
}

static int
match(char *regex, regex_t *preg, char *str)
{
  int st;

  if (regex == NULL)
	return(1);

  if ((st = regexec(preg, str, 0, NULL, 0)) == 0)
	return(1);

  if (st == REG_NOMATCH)
	return(0);

  return(-1);
}

#ifdef NOTDEF
char **
dacs_lookup_identities(char *regex)
{
  int st;
  char errbuf[100];
  regex_t preg;
  Cred_cache *cache;
  Dsvec *dsv;
  FILE *fp;

  if ((fp = cookie_cache_open()) == NULL)
	return(NULL);

  cache = cookie_cache_load(fp);
  ds_fclose(fp);
  if (cache == NULL || cache->cookies == NULL)
	return(NULL);

  if ((dsv = match_cookies(cache->cookies, regex)) == NULL)
	return(NULL);

  return((char **) dsvec_base(dsv));
}
#endif

/*
 * Return a vector (NULL-terminated) of cookie strings that should be
 * sent when URL is invoked.
 * If REGEX is non-NULL, only consider those cookies whose name matches.
 */
char **
dacs_lookup_cookies(char *url, char *regex)
{
  int st;
  char errbuf[100], *req_path;
  regex_t preg;
  Cookie *c, *cookies;
  Cred_cache *cache;
  Dsvec *dsv;
  FILE *fp;
  Uri *req_uri;

  req_path = NULL;
  req_uri = NULL;

  if (url != NULL) {
	if ((req_uri = http_parse_uri(url)) == NULL) {
	  fprintf(stderr, "Usage: invalid URL argument\n");
	  return(NULL);
	}
	if ((req_path = req_uri->path) == NULL)
	  req_path = "/";
  }

  if (regex != NULL) {
	if ((st = regcomp(&preg, regex, REG_EXTENDED)) != 0) {
	  regerror(st, &preg, errbuf, sizeof(errbuf));
	  fprintf(stderr, "%s\n", errbuf);
	  return(NULL);
	}
  }

  if ((fp = cookie_cache_open()) == NULL)
	return(NULL);

  cache = cookie_cache_load(fp);
  ds_fclose(fp);
  if (cache == NULL || (cookies = cache->cookies) == NULL)
	return(NULL);

  dsv = dsvec_init(NULL, sizeof(char *));
  for (c = cookies; c != NULL; c = c->next) {
	if (url == NULL && match(regex, &preg, c->name))
	  dsvec_add_ptr(dsv, c->str);
	else if (cookie_tail_match(req_uri->host, c->domain)
			 && cookie_path_match(req_path, c->path)
			 && match(regex, &preg, c->name))
	  dsvec_add_ptr(dsv, ds_xprintf("%s=%s", c->name, c->value));
  }

  dsvec_add_ptr(dsv, NULL);

  return((char **) dsvec_base(dsv));
}

static char *conf_ssl_prog = DACS_HOME/**/"/bin/sslclient";
static char *conf_ssl_prog_client_crt = NULL;
static char *conf_ssl_prog_ca_crt = NULL;

/*
 * Authenticate; if successful, add credentials to the cache.
 */
static int
do_auth(int argc, char **argv)
{
  int i, prompt, rc, no_save;
  char *auth_password, *full_username, *p, *url, *username;
  char *aux, *pfile, *remote;
  Cookie *cookies;
  Cred_cache cache;
  DACS_name dacs_name;
  Ds ds, response;
  Dsvec *v;
  Http *h;
  Http_params *params;
  Uri *uri;

  auth_password = NULL;
  prompt = 0;
  pfile = NULL;
  no_save = 0;
  aux = NULL;

  for (i = 1; i < argc; i++) {
	if (streq(argv[i], "-p"))
	  prompt = 1;
	else if (streq(argv[i], "-pf")) {
	  if (argv[++i] == NULL)
		return(-1);
	  pfile = argv[i];
	  prompt = 1;
	}
	else if (streq(argv[i], "-caf")) {
	  if (argv[++i] == NULL)
		return(-1);
	  conf_ssl_prog_ca_crt = argv[i];
	}
	else if (streq(argv[i], "-ccf")) {
	  if (argv[++i] == NULL)
		return(-1);
	  conf_ssl_prog_client_crt = argv[i];
	}
	else if (streq(argv[i], "-aux")) {
	  if (argv[++i] == NULL)
		return(-1);
	  aux = argv[i];
	}
	else if (streq(argv[i], "-s"))
	  no_save = 1;
	else
	  break;
  }

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

  if (parse_dacs_name(username, &dacs_name) != DACS_USER_NAME) {
	fprintf(stderr, "Usage: invalid DACS username\n");
	return(-1);
  }

  url = NULL;

  if (argv[i] == NULL) {
	if ((url = cache_lookup_url(username)) == NULL) {
	  fprintf(stderr, "Can't find DACS identity \"%s\"\n", username);
	  return(-1);
	}
  }
  else {
	if (strneq(argv[i], "http://", 7) || strneq(argv[i], "https://", 8))
	  url = argv[i++];
  }

  if (url == NULL || (uri = http_parse_uri(url)) == NULL) {
	fprintf(stderr, "Usage: invalid URL\n");
	return(-1);
  }

  if (argv[i] != NULL) {
	fprintf(stderr, "Usage: unrecognized argument\n");
	return(-1);
  }

  if (uri->port_given != NULL)
	remote = ds_xprintf("%s:%u", uri->host, uri->port);
  else
	remote = uri->host;

  v = dsvec_init(NULL, sizeof(Http_params));
  h = http_init(NULL);

  h->method = HTTP_POST_METHOD;

  if (uri->scheme != NULL && strcaseeq(uri->scheme, "https")) {
	Dsvec *ssl_args;

	ssl_args = dsvec_init(NULL, sizeof(char *));
	dsvec_add_ptr(ssl_args, conf_ssl_prog);
#ifdef USE_STUNNEL
	dsvec_add_ptr(ssl_args, "-r");
	dsvec_add_ptr(ssl_args, remote);
	/*
	 * XXX The following method is a crock but I haven't found a better
	 * method than using stunnel 3.26 so this kludge will remain for
	 * the time being.
	 */
	dsvec_add_ptr(ssl_args, "-r");
	dsvec_add_ptr(ssl_args, remote);
	if ((p = conf_ssl_prog_client_crt) != NULL) {
	  dsvec_add_ptr(ssl_args, "-p");
	  dsvec_add_ptr(ssl_args, p);
	}
	dsvec_add_ptr(ssl_args, "-c");
	if (conf_ssl_prog_ca_crt != NULL) {
	  dsvec_add_ptr(ssl_args, "-v");
	  dsvec_add_ptr(ssl_args, "1");
	  dsvec_add_ptr(ssl_args, "-A");
	  dsvec_add_ptr(ssl_args, conf_ssl_prog_ca_crt);
	}
	dsvec_add_ptr(ssl_args, NULL);
#else
	if ((p = conf_ssl_prog_client_crt) != NULL) {
	  dsvec_add_ptr(ssl_args, "-ccf");
	  dsvec_add_ptr(ssl_args, p);
	}
	if (conf_ssl_prog_ca_crt != NULL) {
	  dsvec_add_ptr(ssl_args, "-vt");
	  dsvec_add_ptr(ssl_args, "peer");
	  dsvec_add_ptr(ssl_args, "-caf");
	  dsvec_add_ptr(ssl_args, conf_ssl_prog_ca_crt);
	}
	dsvec_add_ptr(ssl_args, remote);
	dsvec_add_ptr(ssl_args, NULL);

	http_set_ssl_params(h, ssl_args);
#endif
  }

  params = http_param(v, "DACS_JURISDICTION", NULL, NULL, 0);
  if (dacs_name.jurisdiction == NULL)
	params->value = conf_val(CONF_JURISDICTION_NAME);
  else
	params->value = dacs_name.jurisdiction;

  params = http_param(v, "ENABLE_AUTH_HANDLERS", "0", NULL, 0);

  params = http_param(v, "FORMAT", "PLAIN", NULL, 0);

  params = http_param(v, "USERNAME", dacs_name.username, NULL, 0);

  if (aux != NULL)
	params = http_param(v, "AUXILIARY", aux, NULL, 0);

  if (prompt) {
	if (pfile != NULL) {
	  if (streq(pfile, "-")) {
		/* Read the password from stdin. */
		if ((auth_password = get_passwd(GET_PASSWD_STDIN, NULL)) == NULL) {
		  fprintf(stderr, "Error reading password from stdin\n");
		  return(-1);
		}
	  }
	  else {
		if ((auth_password = get_passwd(GET_PASSWD_FILE, pfile)) == NULL) {
		  fprintf(stderr, "Error reading password from \"%s\"\n", pfile);
		  return(-1);
		}
	  }
	}
	else {
	  auth_password = get_passwd(GET_PASSWD_PROMPT, "Auth password: ");
	  if (auth_password == NULL)
		return(-1);
	}
  }

  if (auth_password != NULL)
	params = http_param(v, "PASSWORD", auth_password, NULL, 0);

  params = http_param(v, "DACS_BROWSER", "1", NULL, 0);

  http_set_post_params(h, dsvec_len(v), (Http_params *) dsvec_base(v));

  h->response_headers = dsvec_init(NULL, sizeof(char *));
  rc = http_request(h, url);

  if (rc == -1) {
	fprintf(stderr, "Can't get \"%s\"\n", url);
	http_close(h);
	if (prompt)
	  ds_free(&ds);
	return(-1);
  }

  if (prompt)
	ds_free(&ds);

  cookies = NULL;
  full_username = NULL;
  if (h->response_headers != NULL) {
    char *p;
	Cookie *c, *cookie, *lastc;
  
	lastc = NULL;
    for (i = 0; (p = dsvec_ptr(h->response_headers, i, char *)) != NULL; i++) {
	  log_msg((LOG_TRACE_LEVEL, "%s", p));
	  if (strprefix(p, "Set-Cookie: ") != NULL) {
		int is_dacs_cookie;

		log_msg((LOG_TRACE_LEVEL, "Found a cookie"));
		if ((cookie = cookie_parse(p + 12, &is_dacs_cookie)) != NULL
			&& is_dacs_cookie) {
		  cookie->str = strdup(p + 12);
		  if (cookie->parsed_name->special == NULL && full_username == NULL) {
			full_username = auth_identity_from_cookie(cookie);
			log_msg((LOG_DEBUG_LEVEL, "Authenticated identity: \"%s\"",
					 full_username));
		  }

		  if (cookies == NULL)
			cookies = lastc = cookie;
		  else {
			/*
			 * If this cookie has the same name as a previous one, the
			 * most recent one replaces the older one.
			 */
			for (c = cookies; c != NULL; c = c->next) {
			  if (streq(c->name, cookie->name)) {
				c->name = NULL;		/* Mark it as deleted */
				break;
			  }
			}
			lastc->next = cookie;
			lastc = cookie;
		  }
		}
		else
		  log_msg((LOG_DEBUG_LEVEL, "Ignoring cookie"));
	  }
	}
  }

  /* Read the response. */
  if (!no_save)
	printf("\n");
  ds_init(&response);
  while ((rc = http_read_response_body(h, &response)) > 0) {
	if (!no_save)
	  printf("%s", ds_buf(&response));
	ds_reset(&response);
  }

  if (rc == -1) {
	fprintf(stderr, "Error reading response\n");
	return(-1);
  }

  http_close(h);

  /* If there are cookies we need to stash them away for later use. */
  cache.cookies = cookies;
  if (no_save)
	cache.auth = NULL;
  else {
	Cred_auth_url auth;

	if (full_username == NULL)
	  full_username = username;
	auth.name = full_username;
	auth.url = url;
	cache.auth = dsvec_init(NULL, sizeof(Cred_auth_url));
	dsvec_add_ptr(cache.auth, &auth);
  }

  if (cookie_cache_update(&cache) == -1)
	return(-1);

  return(0);
}

static Dsvec *
match_cookies(Cookie *cookies, char *regex)
{
  int st;
  char errbuf[100], *ident;
  regex_t preg;
  Cookie *c;
  Dsvec *dsv;

  if (regex != NULL) {
	if ((st = regcomp(&preg, regex, REG_EXTENDED)) != 0) {
	  regerror(st, &preg, errbuf, sizeof(errbuf));
	  fprintf(stderr, "%s\n", errbuf);
	  return(NULL);
	}
  }

  dsv = dsvec_init(NULL, sizeof(Cookie *));
  for (c = cookies; c != NULL; c = c->next) {
	ident = auth_identity_from_cookie(c);
	if ((st = match(regex, &preg, ident)) == -1) {
	  regerror(st, &preg, errbuf, sizeof(errbuf));
	  fprintf(stderr, "%s\n", errbuf);
	  regfree(&preg);
	  return(NULL);
	}
	if (st == 0)
	  continue;
	dsvec_add_ptr(dsv, c);
  }

  if (regex != NULL)
	regfree(&preg);

  return(dsv);
}

static int
do_delete(int argc, char **argv)
{
  int i, rc;
  Cookie *c, *cookies;
  Cred_cache *cache;
  Ds ds;
  FILE *fp, *nfp;

  if (argv[1] == NULL)
	return(0);

  if ((fp = cookie_cache_open()) == NULL)
	return(-1);

  if ((cache = cookie_cache_load(fp)) == NULL
	  || (cookies = cache->cookies) == NULL) {
	ds_fclose(fp);
	return(0);
  }

  rc = -1;
  nfp = NULL;

  ds_init(&ds);
  ds.clear_flag = 1;
  for (i = 1; i < argc; i++) {
	int j;
	Dsvec *dsv;

	dsv = match_cookies(cookies, argv[i]);
	for (j = 0; j < dsvec_len(dsv); j++) {
	  c = dsvec_ptr_index(dsv, j);
	  ds_asprintf(&ds, "%s %s\n", CRED_KEYWORD, c->str);
	}
  }

  if (cache_write(fp, cachefile, "DACS Cache Password: ", &ds) == -1)
	goto done;

  rc = 0;

 done:

  ds_free(&ds);

  return(rc);
}

static int
do_get(int argc, char **argv)
{
  int i;
  char **cookies, *url;

  if ((url = argv[1]) != NULL && argv[2] != NULL)
	return(-1);

  if ((cookies = dacs_lookup_cookies(url, NULL)) == NULL)
	return(0);
  
  for (i = 0; cookies[i] != NULL; i++) {
	printf("%s\n", cookies[i]);
  }

  return(0);
}

/*
 * List all credentials, or credentials that match the regex argument.
 */
static int
do_list(int argc, char **argv)
{
  int i, list_auth, list_cred, st;
  char *regex;
  char errbuf[100];
  regex_t preg;
  Cred_cache *cache;
  Dsvec *dsv;
  FILE *fp;

  list_cred = 1;
  list_auth = 0;

  i = 1;
  if (argv[i] != NULL) {
	if (streq(argv[i], "auth")) {
	  list_auth = 1;
	  list_cred = 0;
	  i++;
	}
	else if (streq(argv[i], "cred")) {
	  list_cred = 1;
	  list_auth = 0;
	  i++;
	}
  }

  if ((regex = argv[i]) != NULL) {
	if (list_auth && (st = regcomp(&preg, regex, REG_EXTENDED)) != 0) {
	  regerror(st, &preg, errbuf, sizeof(errbuf));
	  fprintf(stderr, "%s\n", errbuf);
	  return(-1);
	}
	i++;
  }

  if (argv[i] != NULL) {
	fprintf(stderr, "Invalid argument\n");
	return(-1);
  }

  if ((fp = cookie_cache_open()) == NULL) {
	if (regex != NULL)
	  regfree(&preg);
	return(-1);
  }

  cache = cookie_cache_load(fp);
  ds_fclose(fp);
  if (cache == NULL || cache->cookies == NULL) {
	if (regex != NULL)
	  regfree(&preg);
	return(0);
  }

  if (list_cred) {
	int j;
	Cookie *c;

	dsv = match_cookies(cache->cookies, regex);

	for (j = 0; j < dsvec_len(dsv); j++) {
	  c = dsvec_ptr_index(dsv, j);
	  printf("%s\n", auth_identity_from_cookie(c));
	}
  }

  if (list_auth) {
	for (i = 0; i < dsvec_len(cache->auth); i++) {
	  char *str;
	  Cred_auth_url *a;

	  a = (Cred_auth_url *) dsvec_ptr_index(cache->auth, i);
	  str = ds_xprintf("%s %s", a->name, a->url);
	  if ((st = match(regex, &preg, str)) == -1) {
		regerror(st, &preg, errbuf, sizeof(errbuf));
		fprintf(stderr, "%s\n", errbuf);
		regfree(&preg);
		return(-1);
	  }
	  if (st == 0)
		continue;
	  printf("%s\n", str);
	}
	if (regex != NULL)
	  regfree(&preg);
  }

  return(0);
}

static int
do_passwd(int argc, char **argv)
{
  int rc;
  Cookie *c, *cookies;
  Cred_cache *cache;
  Ds ds;
  FILE *fp, *nfp;

  if ((fp = cookie_cache_open()) == NULL)
	return(-1);

  if ((cache = cookie_cache_load(fp)) == NULL
	  || (cookies = cache->cookies) == NULL) {
	ds_fclose(fp);
	return(0);
  }

  rc = -1;
  nfp = NULL;

  ds_init(&ds);
  ds.clear_flag = 1;
  for (c = cookies; c != NULL; c = c->next) {
	ds_asprintf(&ds, "%s\n", c->str);
  }

  if (cache_write(fp, cachefile, "New DACS Cache Password: ", &ds) == -1)
	goto done;

  rc = 0;

 done:

  ds_free(&ds);

  return(rc);
}

static void
free_keys(void)
{

  if (crypt_keys != NULL) {
	memzap(crypt_keys->auth_key, AUTH_CRYPT_KEY_LENGTH);
	memzap(crypt_keys->hmac_key, CRYPTO_HMAC_KEY_LENGTH);
	crypt_keys = NULL;
  }
}

static char *
logging_callback(Log_desc *logd, Log_level level, void *arg)
{
  char *prefix;

  prefix = log_format(logd, level, "[%l]");

  return(prefix);
}

static void
dacs_usage(void)
{

  fprintf(stderr, "Usage: dacscred [args] op [opargs]\n");
  fprintf(stderr, " args:\n");
  fprintf(stderr, "   -dd dir\n");
  fprintf(stderr, "   -ll log_level\n");
  fprintf(stderr, "   -v\n");
  fprintf(stderr, " op:\n");
  fprintf(stderr,
		  "   auth [-p | -pf file] [-aux aux] [-ccf file] [-caf file] [-s] JURISDICTION:username [auth-URL]\n");
  fprintf(stderr, "   delete regex [...]\n");
  fprintf(stderr, "   get [url]\n");
  fprintf(stderr, "   list [auth | cred] [regex ...]\n");
  fprintf(stderr, "   passwd\n");
  exit(1);
}

int
dacscred_main(int argc, char **argv, int do_init, void *main_out)
{
  int i, rc, vflag;
  char *dd, *errmsg;
  Log_desc *ld;

  errmsg = "Internal error";

  /*
   * Don't leave a core dump behind that might reveal passwords or cookies.
   */
  dacs_disable_dump();

  ld = log_init(NULL, 0, NULL, "dacscred", LOG_NONE_LEVEL, NULL);
  log_set_level(ld, LOG_WARN_LEVEL);
  log_set_user_callback(ld, logging_callback, (void *) NULL);
  log_set_desc(ld, LOG_ENABLED);
  vflag = 0;

  if (argc == 1) {
	errmsg = "Usage: missing arguments";
	goto fail;
  }

  dd = NULL;
  for (i = 1; i < argc; i++) {
	if (streq(argv[i], "-dd")) {
	  if (++i == argc) {
		errmsg = "Usage: missing directory argument";
		goto fail;
	  }
	  dd = argv[i];
	}
	else if (streq(argv[i], "-ll")) {
	  Log_level ll;

	  if (++i == argc) {
		errmsg = "Usage: missing log_level argument";
		goto fail;
	  }
	  if ((ll = log_lookup_level(argv[i])) == LOG_INVALID_LEVEL
		  || log_set_level(ld, ll) == LOG_INVALID_LEVEL) {
		errmsg = "Invalid log_level";
		goto fail;
	  }
	}
	else if (streq(argv[i], "-v"))
	  vflag++;
	else
	  break;
  }

  if (vflag == 1)
	log_set_level(ld, LOG_DEBUG_LEVEL);
  else if (vflag > 1)
	log_set_level(ld, LOG_TRACE_LEVEL);

  if (i == argc) {
	errmsg = "Usage: missing arguments";
	goto fail;
  }

  if (set_dacsdir(dd) == -1) {
	errmsg = "Missing or invalid DACS directory";
	goto fail;
  }

  if (strcaseeq(argv[i], "auth"))
	rc = do_auth(argc - i, argv + i);
  else if (strcaseeq(argv[i], "delete"))
	rc = do_delete(argc - i, argv + i);
  else if (strcaseeq(argv[i], "get"))
	rc = do_get(argc - i, argv + i);
  else if (strcaseeq(argv[i], "list"))
	rc = do_list(argc - i, argv + i);
  else if (strcaseeq(argv[i], "passwd"))
	rc = do_passwd(argc - i, argv + i);
  else {
	errmsg = "Usage: invalid operation";
	goto fail;
  }

  free_keys();
  return(rc);

 fail:
  free_keys();
  fprintf(stderr, "%s\n", errmsg);
  dacs_usage();

  return(-1);
}

#else

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

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

  exit(1);
}
#endif
