/*
 * 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: auth_grid.c 2586 2012-03-15 16:21:40Z brachman $";
#endif

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

static MAYBE_UNUSED const char *log_module_name = "dacsgrid";

#ifndef PROG

/*
 * XXX I'd like to implement NIST SP 800-90...
 * Instead of storing the grid, a random seed would be stored from which
 * a grid (and possibly other stuff) would be created (and recreated).
 */

typedef enum {
  EMIT_TEXT     = 0,
  EMIT_HTML     = 1,
  EMIT_HTML_CSS = 2,
  EMIT_FLAT     = 3
} Emit_type;

typedef struct Auth_grid_coord {
  int x;
  int y;
} Auth_grid_coord;

static char *grid_item_type = ITEM_TYPE_AUTH_GRID;
static char *inkeys_item_type = ITEM_TYPE_AUTH_GRID_KEYS;
static char *outkeys_item_type = ITEM_TYPE_AUTH_GRID_KEYS;
static unsigned int pin_length = 0;
static unsigned int auth_grid_lifetime_secs = AUTH_GRID_LIFETIME_SECS;
static Rng_state *rng_state;

static Auth_grid *
auth_grid_new(Auth_grid *grid)
{
  Auth_grid *g;

  if ((g = grid) == NULL)
	g = ALLOC(Auth_grid);

  g->username = NULL;
  g->nrows = 0;
  g->ncols = 0;
  g->serial = NULL;
  g->date_created = 0;
  g->grid = NULL;
  g->pin = NULL;

  return(g);
}

static Auth_grid *
auth_grid_init(Auth_grid *grid, char *username, int nrows, int ncols,
			   char *pin)
{
  Auth_grid *g;

  g = auth_grid_new(grid);

  if (username != NULL)
	g->username = strdup(username);
  g->enabled = 1;
  g->nrows = nrows;
  g->ncols = ncols;
  g->serial = malloc(AUTH_GRID_SERIAL_LENGTH);
  if ((g->serial = rng_generate_string(rng_state, NULL,
									   AUTH_GRID_SERIAL_LENGTH)) == NULL)
	return(NULL);
  time(&g->date_created);
  g->pin = pin;
  g->grid = dsvec_init(NULL, sizeof(char *));

  return(g);
}

static char *
gen_cell(void)
{
  char *cell;
#ifdef NOTDEF
  unsigned long long rval, digit, letter1, letter2;

  if (rng_generate(rng_state, (unsigned char *) &rval, sizeof(rval)) == -1)
	return(NULL);
  letter1 = rval % 26;

  if (rng_generate(rng_state, (unsigned char *) &rval, sizeof(rval)) == -1)
	return(NULL);
  digit = rval % 10;

  if (rng_generate(rng_state, (unsigned char *) &rval, sizeof(rval)) == -1)
	return(NULL);
  letter2 = rval % 26;

  cell = ds_xprintf("%c%c%c",
					(int) ('a' + letter1),
					(int) ('0' + digit),
					(int) ('a' + letter2));
#else
  cell = crypto_make_random_string_from_template("%l%d%l");
#endif

  return(cell);
}

static Auth_grid *
auth_grid_generate(char *username, int nrows, int ncols, int use_pin,
				   char *old_pin)
{
  int i, j;
  char *cell, *pin;
  Ds ds;
  Auth_grid *grid;

  if (ncols > 26)
	return(NULL);

  grid = auth_grid_new(NULL);
  if (old_pin != NULL)
	pin = old_pin;
  else if (use_pin) {
	ds_init(&ds);
	for (i = 0; i < pin_length; i++)
	  ds_asprintf(&ds, "%s", gen_cell());
	pin = ds_buf(&ds);
  }
  else
	pin = NULL;

  auth_grid_init(grid, username, nrows, ncols, pin);

  for (i = 0; i < nrows; i++) {
	ds_init(&ds);
	ds.clear_flag = 0;
	for (j = 0; j < ncols; j++) {
	  if ((cell = gen_cell()) == NULL)
		return(NULL);
	  ds_asprintf(&ds, "%s", cell);
	}
	dsvec_add_ptr(grid->grid, (void *) ds_buf(&ds));
  }

  return(grid);
}

/*
 * Return the contents of GRID at coordinate (X,Y)
 * We assume that each cell contains three symbols.
 * Note that rows and columns are numbered starting at zero.
 */
char *
auth_grid_cell(Auth_grid *grid, int row, int col)
{
  int ncols, nrows;
  char *c, *row_str;

  nrows = dsvec_len(grid->grid);
  if (row > nrows)
	return(NULL);

  row_str = dsvec_ptr(grid->grid, row, char *);

  ncols = strlen(row_str) / 3;
  if (col > ncols)
	return(NULL);

  c = (char *) malloc(4);
  c[0] = *(row_str + (col * 3));
  c[1] = *(row_str + (col * 3 + 1));
  c[2] = *(row_str + (col * 3 + 2));
  c[3] = '\0';

  return(c);
}

/*
 * Validate the syntax of a challenge (a sequence of cell coordinates) and
 * convert it to canonical form.
 */
static char *
canonicalize_challenge(char *challenge)
{
  char *new_challenge, *p, *r;

  if (challenge == NULL)
	return(NULL);

  new_challenge = r = malloc(strlen(challenge) + 1);
  p = challenge;

  while (*p == ' ' || *p == '\t' || *p == ',')
	p++;

  /*
   * Format: {[ ]*<alpha><digit>[digit][ ]*}*
   */
  while (*p != '\0') {
	if (!isalpha((int) *p))
	  return(NULL);
	if (r != new_challenge)
	  *r++ = ',';

	*r++ = toupper((int) *p);
	p++;

	if (!isdigit((int) *p))
	  return(NULL);
	*r++ = *p++;

	if (*p == '\0')
	  break;
	if (isdigit((int) *p))
	  *r++ = *p++;
	while (*p == ' ' || *p == '\t')
	  p++;
	if (*p == ',')
	  p++;
	while (*p == ' ' || *p == '\t')
	  p++;
  }

  if (*p != '\0' || r == new_challenge)
	return(NULL);

  *r = '\0';

  return(new_challenge);
}

/*
 * Validate the syntax of a response (a sequence of cell values) and convert
 * it to canonical form.
 */
static char *
canonicalize_response(char *response)
{
  char *new_response, *p, *r;

  if (response == NULL)
	return(NULL);

  new_response = r = malloc(strlen(response) + 1);
  p = response;

  while (*p == ' ' || *p == '\t' || *p == ',')
	p++;

  while (*p != '\0') {
	if (!isalpha((int) *p))
	  return(NULL);
	*r++ = tolower((int) *p);
	p++;

	while (*p == ' ' || *p == '\t' || *p == ',')
	  p++;
	if (!isdigit((int) *p))
	  return(NULL);
	*r++ = *p++;

	while (*p == ' ' || *p == '\t' || *p == ',')
	  p++;
	if (!isalpha((int) *p))
	  return(NULL);
	*r++ = tolower((int) *p);
	p++;

	while (*p == ' ' || *p == '\t' || *p == ',')
	  p++;
  }

  *r = '\0';

  return(new_response);
}

static int
grid_has_expired(Auth_grid *grid, unsigned int grid_lifetime)
{
  time_t now;

  time(&now);
  if (now < grid->date_created || (now - grid->date_created) > grid_lifetime)
	return(1);

  return(0);
}

/*
 * Given GRID, check if RESPONSE is correct wrt CHALLENGE.
 * This is where a password is verified.
 *
 * Return 0 if it is, -1 otherwise.
 */
int
auth_grid_verify(Auth_grid *grid, Dsvec *challenge, char *response,
				 unsigned int grid_lifetime)
{
  int i;
  char *cell, *r;
  Auth_grid_coord *c;

  if (grid_has_expired(grid, grid_lifetime)) {
	log_msg((LOG_ERROR_LEVEL, "Grid has expired"));
	return(-1);
  }

  if ((r = canonicalize_response(response)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Grid canonicalization failed"));
	return(-1);
  }

  if (grid->pin != NULL) {
	char *p;

	if ((p = strprefix(r, grid->pin)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "PIN is missing or incorrect"));
	  return(-1);
	}
	r = p;
  }

  for (i = 0; i < dsvec_len(challenge); i++) {
	c = dsvec_ptr(challenge, i, Auth_grid_coord *);
	if ((cell = auth_grid_cell(grid, c->x, c->y)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Challenge includes invalid cell??"));
	  return(-1);
	}
	if (*r != *cell || *(r + 1) != *(cell + 1) || *(r + 2) != *(cell + 2)) {
	  log_msg((LOG_ERROR_LEVEL, "Incorrect user response"));
	  log_msg((LOG_TRACE_LEVEL, "Expected: \"%c%c%c\", Got: \"%c%c%c\"",
			   *cell, *(cell + 1), *(cell + 2),
			   *r, *(r + 1), *(r + 2)));
	  return(-1);
	}
	r += 3;
  }
  if (*r != '\0') {
	log_msg((LOG_ERROR_LEVEL, "Incorrect user response length"));
	return(-1);
  }

  return(0);
}

/*
 * Randomly select N coordinates from a NCOLS x NROWS grid.
 * This is used to generate a challenge to be displayed to a user.
 */
Dsvec *
auth_grid_challenge(int n, int ncols, int nrows)
{
  int i;
  unsigned long long rval;
  Auth_grid_coord *c;
  Dsvec *dsv;

  dsv = dsvec_init(NULL, sizeof(Auth_grid_coord));
  for (i = 0; i < n; i++) {
	c = ALLOC(Auth_grid_coord);
	if (rng_generate(rng_state, (unsigned char *) &rval, sizeof(rval)) == -1)
	  return(NULL);
	c->x = rval % ncols;
	c->y = rval % nrows;
	dsvec_add_ptr(dsv, (void *) c);
  }

  return(dsv);
}

/*
 * Produce an externalized encoded encrypted challenge string.
 * The construction, which consists of several components, serves to simplify
 * the server side of authentication by maintaining state with the client.
 * This approach needs to guard against tampering and replay, hence the
 * encryption.  Basically, we protect against someone making his own challenge
 * or reusing a previous challenge.
 * The challenge should expire after a configurable period - this
 * may assume a degree of clock synchronization.
 *
 * At login time, the user is given a random challenge; the response is
 * returned as the PASSWORD argument.  The user is also given an AUXILIARY
 * value, which is what is produced here, and which must be returned as-is.
 * At authentication time, the AUXILIARY value is decrypted and checked
 * for validity.  If it's ok, then the grid must be retrieved and the
 * response must be validated against the challenge.
 */
char *
auth_grid_encrypt_challenge(char *challenge_str)
{
  unsigned int enc_len;
  char *ext_token;
  unsigned char *enc_token;
  time_t now;
  Crypt_keys *ck;
  Ds ds;

  time(&now);
  ds_init(&ds);
  ds_asprintf(&ds, "%s %lu", challenge_str, (unsigned long) now);
  ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
  enc_len = crypto_encrypt_string(ck, (unsigned char *) ds_buf(&ds),
								  ds_len(&ds) + 1, &enc_token);
  crypt_keys_free(ck);
  mime_encode_base64(enc_token, enc_len, &ext_token);

  return(ext_token);
}

/*
 *
 */
Dsvec *
auth_grid_decrypt_challenge(char *ext_token, unsigned int challenge_lifetime)
{
  char *c_str, *challenge_str;
  unsigned char *enc_token, *token;
  long enc_len;
  time_t creation_date, now;
  Crypt_keys *ck;
  Dsvec *challenge, *dsv;

  if ((enc_len = mime_decode_base64(ext_token, &enc_token)) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Mime decode of challenge failed"));
	return(NULL);
  }

  ck = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS);
  if (crypto_decrypt_string(ck, enc_token, enc_len, &token, NULL) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Challenge decryption failed"));
	crypt_keys_free(ck);
	return(NULL);
  }
  crypt_keys_free(ck);

  dsv = strsplit((char *) token, " ", 0);
  if (dsvec_len(dsv) != 2) {
	log_msg((LOG_ERROR_LEVEL, "Incorrect challenge length"));
	return(NULL);
  }
  challenge_str = dsvec_ptr(dsv, 0, char *);
  if ((challenge = auth_grid_parse_challenge(challenge_str)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Challenge parse failed"));
	return(NULL);
  }

  c_str = dsvec_ptr(dsv, 1, char *);
  if (strnum(c_str, STRNUM_TIME_T, &creation_date) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Challenge date is invalid"));
	return(NULL);
  }
  time(&now);
  if (creation_date > now || (now - creation_date) > challenge_lifetime) {
	log_msg((LOG_ERROR_LEVEL, "Challenge has expired"));
	return(NULL);
  }

  log_msg((LOG_DEBUG_LEVEL, "Challenge token: \"%s\"", token));
  return(challenge);
}

Dsvec *
auth_grid_parse_challenge(char *challenge_str)
{
  int i;
  char *cell, *errmsg;
  Auth_grid_coord *c;
  Dsvec *cells, *challenge;

  challenge = dsvec_init(NULL, sizeof(Auth_grid_coord));
  cells = strsplit_re(challenge_str, " *, *", 0, &errmsg);

  for (i = 0; i < dsvec_len(cells); i++) {
	cell = dsvec_ptr(cells, i, char *);
	c = ALLOC(Auth_grid_coord);
	if (!isupper((int) cell[0]))
	  return(NULL);
	c->x = (int) cell[0] - 'A';
	if (!isdigit((int) cell[1]))
	  return(NULL);
	c->y = (int) cell[1] - '0';
	if (cell[2] != '\0') {
	  if (!isdigit((int) cell[2]) || cell[3] != '\0')
		return(NULL);
	  c->y = (c->y * 10) + (int) cell[2] - '0';
	}
	c->y--;

	dsvec_add_ptr(challenge, c);
  }

  return(challenge);
}

static char *
auth_grid_challenge_to_text(Dsvec *s)
{
  int i;
  Auth_grid_coord *c;
  Ds ds;

  ds_init(&ds);
  for (i = 0; i < dsvec_len(s); i++) {
	c = dsvec_ptr(s, i, Auth_grid_coord *);
	ds_asprintf(&ds, "%c%d%s",
				(int) ('A' + c->x),
				c->y + 1,
				(i + 1) < dsvec_len(s) ? ", " : "");
  }

  return(ds_buf(&ds));
}

/*
 * Flatten (serialize) a grid.
 * The formatted result is:
 *   <state>,<serial-number>,<grid>,<pin>,<date-created>
 * where grid consists of a space-separated sequence of rows.
 */
char *
auth_grid_flatten(Auth_grid *grid, int encrypt)
{
  int i, j;
  char *e_str, *grid_str, *p;
  unsigned int e_len;
  unsigned char *encrypted;
  Ds ds;
  static Crypt_keys *keys = NULL;

  ds_init(&ds);
  ds_asprintf(&ds, "%s,", grid->serial);
  ds_asprintf(&ds, "%s,", grid->enabled ? "1" : "0");

  for (i = 0; i < dsvec_len(grid->grid); i++) {
	p = dsvec_ptr(grid->grid, i, char *);
	if (i != 0)
	  ds_asprintf(&ds, " ");
	for (j = 0; j < grid->ncols; j++) {
	  ds_asprintf(&ds, "%c%c%c", *p, *(p + 1), *(p + 2));
	  p += 3;
	}
  }

  if (grid->pin == NULL)
	ds_asprintf(&ds, ",0");
  else
	ds_asprintf(&ds, ",%s", grid->pin);

  ds_asprintf(&ds, ",%lu", grid->date_created);

  grid_str = ds_buf(&ds);

  if (!encrypt)
	return(grid_str);

  if (keys == NULL
	  && (keys = crypt_keys_from_vfs(outkeys_item_type)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot load keys using item type: %s",
			 outkeys_item_type));
	return(NULL);
  }
  e_len = crypto_encrypt_string(keys, (unsigned char *) grid_str,
								strlen(grid_str) + 1, &encrypted);
  strba64(encrypted, e_len, &e_str);

  return(e_str);
}

Auth_grid *
auth_grid_unflatten(char *e_grid_str, int decrypt)
{
  char *dstr, *gstr, *grid_str, *pstr, *sstr;
  unsigned int e_len;
  unsigned char *e_str;
  Auth_grid *grid;
  Dsvec *dsv1;
  static Crypt_keys *keys = NULL;

  if (e_grid_str == NULL)
	return(NULL);

  if (decrypt) {
	if (stra64b(e_grid_str, &e_str, &e_len) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Base-64 decoding failed"));
	  return(NULL);
	}
	if (keys == NULL
		&& (keys = crypt_keys_from_vfs(inkeys_item_type)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot load keys from item type %s",
			   inkeys_item_type));
	  return(NULL);
	}
	if (crypto_decrypt_string(keys, e_str, e_len,
							  (unsigned char **) &grid_str, NULL) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Decryption failed"));
	  return(NULL);
	}
  }
  else
	grid_str = e_grid_str;

  grid = auth_grid_new(NULL);
  if ((dsv1 = strsplit(grid_str, ",", 0)) == NULL)
	return(NULL);
  if ((grid->serial = dsvec_ptr(dsv1, 0, char *)) == NULL)
	return(NULL);
  if ((sstr = dsvec_ptr(dsv1, 1, char *)) == NULL)
	return(NULL);
  if (streq(sstr, "1"))
	grid->enabled = 1;
  else
	grid->enabled = 0;

  if ((gstr = dsvec_ptr(dsv1, 2, char *)) == NULL)
	return(NULL);
  if ((grid->grid = strsplit(gstr, " ", 0)) == NULL)
	return(NULL);
  grid->nrows = dsvec_len(grid->grid);
  if (grid->nrows < AUTH_GRID_MIN_ROWS || grid->nrows > AUTH_GRID_MAX_ROWS)
	return(NULL);
  grid->ncols = strlen(dsvec_ptr(grid->grid, 0, char *)) / 3;
  if (grid->ncols < AUTH_GRID_MIN_COLS || grid->ncols > AUTH_GRID_MAX_COLS)
	return(NULL);

  if ((pstr = dsvec_ptr(dsv1, 3, char *)) == NULL)
	return(NULL);
  if (streq(pstr, "0"))
	grid->pin = NULL;
  else
	grid->pin = pstr;

  if ((dstr = dsvec_ptr(dsv1, 4, char *)) == NULL)
	return(NULL);
  if (strnum(dstr, STRNUM_TIME_T, &grid->date_created) == -1)
	return(NULL);

  return(grid);
}

int
auth_grid_set(Auth_grid *grid, char *item_type, char *username)
{
  char *grid_str;
  Vfs_handle *h;
  
  if ((h = vfs_open_item_type(item_type)) == NULL) {
	return(-1);
  }

  grid_str = auth_grid_flatten(grid, 1);

  if (vfs_put(h, username, (void *) grid_str, strlen(grid_str)) == -1) {
	vfs_close(h);
	return(-1);
  }

  if (vfs_close(h) == -1)
	log_msg((LOG_ERROR_LEVEL, "vfs_close() failed"));

  return(0);
}

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

  dsv = (Dsvec *) names;
  dsvec_add_ptr(dsv, strdup(name));

  return(1);
}

/*
 * Return a vector of grid owners.
 */
Dsvec *
auth_grid_list(char *item_type)
{
  int n;
  Dsvec *dsv;
  Vfs_handle *h;
  
  if ((h = vfs_open_item_type(item_type)) == NULL) {
	return(NULL);
  }

  dsv = dsvec_init(NULL, sizeof(char *));
  if ((n = vfs_list(h, NULL, NULL, list_add, (void ***) dsv)) == -1)
    return(NULL);

  vfs_close(h);

  return(dsv);
}

char *
auth_grid_get_flattened(char *item_type, char *username)
{
  char *grid_str;
  Vfs_handle *h;
  
  if ((h = vfs_open_item_type(item_type)) == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Item type \"%s\" not found", item_type));
	return(NULL);
  }

  if (vfs_get(h, username, (void **) &grid_str, NULL) == -1) {
	log_msg((LOG_DEBUG_LEVEL, "Cannot get key \"%s\" for item type \"%s\"",
			 username, item_type));
	vfs_close(h);
	return(NULL);
  }

  if (vfs_close(h) == -1)
	log_msg((LOG_ERROR_LEVEL, "vfs_close() failed"));

  return(grid_str);
}

Auth_grid *
auth_grid_get(char *item_type, char *username)
{
  char *grid_str;
  Auth_grid *grid;
  
  if ((grid_str = auth_grid_get_flattened(item_type, username)) == NULL)
	return(NULL);

  if ((grid = auth_grid_unflatten(grid_str, 1)) == NULL)
	return(NULL);

  grid->username = strdup(username);

  return(grid);
}

int
auth_grid_delete(char *item_type, char *username)
{
  Vfs_handle *h;

  if ((h = vfs_open_item_type(item_type)) == NULL) {
	return(-1);
  }

  if (vfs_delete(h, username) == -1) {
	vfs_close(h);
	return(-1);
  }

  if (vfs_close(h) == -1)
	log_msg((LOG_ERROR_LEVEL, "vfs_close() failed"));

  return(0);
}

Dsvec *
auth_grid_get_all(char *item_type)
{
  int i;
  Auth_grid *grid;
  Dsvec *dsv, *grids;

  if ((dsv = auth_grid_list(item_type)) == NULL)
	return(NULL);

  grids = dsvec_init(NULL, sizeof(Auth_grid));
  for (i = 0; i < dsvec_len(dsv); i++) {
	char *u;

	u = dsvec_ptr(dsv, i, char *);
	if ((grid = auth_grid_get(item_type, u)) == NULL)
	  return(NULL);
	dsvec_add_ptr(grids, grid);
  }

  return(grids);
}

/*
 * Copy SRC_ITEM_TYPE to DST_ITEM_TYPE, deleting any existing content
 * in DST_ITEM_TYPE.
 * XXX Should be a "refresh" version.
 */
int
auth_grid_copy(char *src_item_type, char *dst_item_type)
{
  int i;
  Auth_grid *grid;
  Dsvec *src_grids;
  Vfs_conf *conf;
  Vfs_handle *h_dst;

  /* Must delete it if it exists. */
  conf = vfs_conf(NULL);
  conf->delete_flag = 1;
  vfs_conf(conf);

  if ((h_dst = vfs_open_item_type(dst_item_type)) == NULL) {
	fprintf(stderr, "Cannot open destination item type \"%s\"\n",
			dst_item_type);
	return(-1);
  }

  if ((src_grids = auth_grid_get_all(src_item_type)) == NULL) {
	vfs_close(h_dst);
	return(0);
  }

  for (i = 0; i < dsvec_len(src_grids); i++) {
	char *grid_str;

	grid = dsvec_ptr(src_grids, i, Auth_grid *);
	grid_str = auth_grid_flatten(grid, 1);
	if (vfs_put(h_dst, grid->username,
				(void *) grid_str, strlen(grid_str)) == -1) {
	  vfs_close(h_dst);
	  return(-1);
	}
  }

  vfs_close(h_dst);

  return(i);
}

char *
auth_grid_text(Auth_grid *grid)
{
  int i, j;
  char *p;
  Ds ds;

  ds_init(&ds);
  ds_asprintf(&ds, "    ");
  for (i = 0; i < grid->ncols; i++)
	ds_asprintf(&ds, " %c  ", (int) ('A' + i));
  ds_asprintf(&ds, "\n");

  for (i = 0; i < dsvec_len(grid->grid); i++) {
	p = dsvec_ptr(grid->grid, i, char *);
	ds_asprintf(&ds, "%2d  ", i + 1);
	for (j = 0; j < grid->ncols; j++) {
	  ds_asprintf(&ds, "%c%c%c%s", *p, *(p + 1), *(p + 2),
				  j != (grid->ncols - 1) ? " " : "");
	  p += 3;
	}
	ds_asprintf(&ds, "\n");
  }

  ds_asprintf(&ds, "\n");
  ds_asprintf(&ds, "Serial: %s\n", grid->serial);
  ds_asprintf(&ds, "Created: %s\n",
			  make_local_date_string(localtime(&grid->date_created), 1));
  return(ds_buf(&ds));
}

char *
auth_grid_html(Auth_grid *grid)
{
  int i, j;
  char *p;
  Ds ds;
  Dsvec *dsv;
  Html_table *tab;

  ds_init(&ds);
  ds_asprintf(&ds, "<div class=\"auth_grid\">\n");

  tab = html_table(&ds, NULL);
  tab->row_class = "auth_grid_tr";
  tab->auto_row_nclasses = 2;
  tab->auto_column_class = NULL;
  dsv = dsvec_init(NULL, sizeof(char *));
  dsvec_add_ptr(dsv, "class=\"grid_table\"");
  dsvec_add_ptr(dsv, "border");
  dsvec_add_ptr(dsv, "width=\"100%\"");
  html_table_begin(tab, dsv, grid->ncols + 1);

  tab->in_header = 1;
  tab->repeat_column_class = 1;
  html_cell(tab, "\\");
  for (i = 0; i < grid->ncols; i++)
	html_cellf(tab, "%c", (int) ('A' + i));
  html_row_end(tab);
  tab->in_header = 0;

  for (i = 0; i < dsvec_len(grid->grid); i++) {
	p = dsvec_ptr(grid->grid, i, char *);

	html_row_begin(tab);
	tab->in_header = 1;
	html_cellf(tab, "%2d", i + 1);
	tab->in_header = 0;

	for (j = 0; j < grid->ncols; j++) {
	  html_cellf(tab, "%c%c%c", *p, *(p + 1), *(p + 2));
	  p += 3;
	}
	html_row_end(tab);
  }

  html_table_end(tab);

  ds_asprintf(&ds,
			  "<b>Serial</b>: <span class=\"auth_grid_serial\">%s</span><br/>\n", grid->serial);
  ds_asprintf(&ds,
			  "<b>Created</b>: <span class=\"auth_grid_date\">%s</span><br/>\n",
			  make_local_date_string(localtime(&grid->date_created), 1));
  ds_asprintf(&ds, "</div>\n");

  return(ds_buf(&ds));
}

static char *
add_vfs(char *vfs_uri)
{
  char *uri;
  Kwv *kwv_conf;
  Vfs_directive *vd;

  if ((vd = vfs_uri_parse(vfs_uri, NULL)) == NULL) {
	fprintf(stderr, "Invalid vfs_uri: \"%s\"\n", vfs_uri);
	return(NULL);
  }

  if (vd->item_type == NULL) {
	time_t now;

	/* This is a bit dodgey... */
	time(&now);
	uri = ds_xprintf("[auth_grid_%lu]%s", (unsigned long) now, vfs_uri);
	if ((vd = vfs_uri_parse(uri, NULL)) == NULL) {
	  fprintf(stderr, "Invalid vfs_uri: \"%s\"\n", vfs_uri);
	  return(NULL);
	}
  }
  else
	uri = strdup(vfs_uri);

  if (dacs_conf == NULL) {
	fprintf(stderr, "Initialization error?!\n");
	return(NULL);
  }

  kwv_conf = var_ns_lookup_kwv(dacs_conf->conf_var_ns, "Conf");
  if (kwv_conf == NULL) {
	kwv_conf = kwv_init(4);
	dacs_conf->conf_var_ns = var_ns_create("Conf", kwv_conf);
  }

  add_vfs_uri(kwv_conf, uri);

  return(strdup(vd->item_type));
}

static char *
list_grid(Auth_grid *grid)
{
  Ds ds;

  ds_init(&ds);
  ds_asprintf(&ds, "%s: ", grid->username);
  ds_asprintf(&ds, "%s", grid->enabled ? "enabled" : "disabled");
  ds_asprintf(&ds, ", %dx%d", grid->nrows, grid->ncols);
  ds_asprintf(&ds, ", %s", grid->serial);
  ds_asprintf(&ds, ", %s", grid->pin ? "PIN" : "no PIN");
  ds_asprintf(&ds, ", %s",
			  make_short_local_date_string(localtime(&grid->date_created)));

  return(ds_buf(&ds));
}

static void
dacs_usage()
{

  fprintf(stderr, "Usage: dacsgrid [dacsoptions] [flag ...] [username]\n");
  fprintf(stderr, "dacsoptions: %s\n", standard_command_line_usage);
  fprintf(stderr, "-challenge: emit a random challenge\n");
  fprintf(stderr, "-clen #:    challenge length (default: %d)\n",
		  AUTH_GRID_CHALLENGE_LENGTH);
  fprintf(stderr, "-copy vfs_uri: copy input grids to vfs_uri\n");
  fprintf(stderr, "-dec token: decode a challenge\n");
  fprintf(stderr, "-delete:    delete the grid associated with username\n");
  fprintf(stderr, "-disable:   disable an account\n");
  fprintf(stderr, "-enable:    enable an account\n");
  fprintf(stderr, "-enc challenge: encode a challenge\n");
  fprintf(stderr, "-expired:   list only expired grids\n");
  fprintf(stderr, "-flat:      emit a flattened grid\n");
  fprintf(stderr, "-get:       retrieve the grid associated with username\n");
  fprintf(stderr, "-grid str:  use str as the grid (flattened)\n");
  fprintf(stderr, "-h | -help: display usage message\n");
  fprintf(stderr, "-html:      emit a grid as HTML\n");
  fprintf(stderr, "-htmlcss:   emit a grid as HTML with default CSS\n");
  fprintf(stderr, "-inkeys item_type: decryption keys (default: %s)\n",
		  inkeys_item_type);
  fprintf(stderr, "-lifetime ndays: set grid lifetime (default: %d)\n",
		  AUTH_GRID_LIFETIME_SECS / (60 * 60 * 24));
  fprintf(stderr, "-list:      list one or all users\n");
  fprintf(stderr, "-long:      detailed list of one or all users\n");
  fprintf(stderr, "-ncols #:   set number of columns (default: %d)\n",
		  AUTH_GRID_NCOLS);
  fprintf(stderr, "-nrows #:   set number of rows (default: %d)\n",
		  AUTH_GRID_NROWS);
  fprintf(stderr, "-outkeys item_type: encryption keys (default: %s)\n",
		  outkeys_item_type);
  fprintf(stderr, "-pin[#]:    set PIN generation parameter\n");
  fprintf(stderr, "-refresh:   generate new grids for all users\n");
  fprintf(stderr, "-rng:       use pseudo random numbers\n");
  fprintf(stderr, "-seed str:  use str as the initial seed\n");
  fprintf(stderr, "-serial:    display a grid's serial number\n");
  fprintf(stderr, "-set:       associate the grid with username\n");
  fprintf(stderr, "-size:      list grid dimensions\n");
  fprintf(stderr, "-test:      emit a grid and challenge, and verify\n");
  fprintf(stderr, "-text:      emit a pretty-printed grid\n");
  fprintf(stderr,
		  "-validate challenge response: test response against challenge\n");
  fprintf(stderr, "-vfs vfs_uri: use vfs_uri instead of item type %s\n",
		  grid_item_type);

  exit(1);
}

int
dacsgrid_main(int argc, char **argv, int do_init, void *main_out)
{
  int i, n, ncols, nrows, st, use_rng;
  int do_challenge, do_enc, do_test, do_validate, do_delete, do_get, do_set;
  int do_copy, do_dec, do_expired, do_list, do_refresh, do_serial, do_show;
  int do_disable, do_enable, do_long, do_size;
  int challenge_length, use_pin;
  char *challenge_str, *dec_str, *grid_str, *grid_str_in, *response_str, *seed;
  char *errmsg, *grid_item_type_out, *p, *username, *vfs_uri_alt, *vfs_uri_dst;
  Auth_grid *grid;
  Dsvec *challenge;
  Emit_type emit;
  Proc_lock *lock;

  ncols = AUTH_GRID_NCOLS;
  nrows = AUTH_GRID_NROWS;
  challenge_length = AUTH_GRID_CHALLENGE_LENGTH;
  pin_length = AUTH_GRID_PIN_LENGTH;
  emit = EMIT_TEXT;
  do_challenge = do_dec = do_enc = do_refresh = do_test = do_validate = 0;
  do_copy = do_disable = do_enable = do_expired = do_serial = do_show = 0;
  do_set = do_get = do_delete = do_list = do_long = do_size = 0;
  challenge_str = NULL;
  dec_str = NULL;
  grid = NULL;
  grid_str = grid_str_in = NULL;
  response_str = NULL;
  use_rng = 0;
  use_pin = 0;
  seed = NULL;
  username = NULL;
  vfs_uri_alt = vfs_uri_dst = NULL;

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

  fail:
    dacs_usage();
	/*NOTREACHED*/
    return(-1);
  }
  
  if (!dacs_saw_command_line_log_level)
	log_set_level(NULL, LOG_WARN_LEVEL);

  /* XXX This course-grained lock prevents concurrent updates. */
  if ((lock = proc_lock_create(PROC_LOCK_GRID)) == NULL) {
    log_msg((LOG_ERROR_LEVEL, "Can't set lock"));
    errmsg = "Can't set lock";
    goto fail;
  }

  for (i = 1; i < argc; i++) {
	if (argv[i][0] != '-')
	  break;

	if (streq(argv[i], "-challenge"))
	  do_challenge = 1;
	else if (streq(argv[i], "-clen")) {
	  if (++i == argc)
		goto fail;
	  if (strnum(argv[i], STRNUM_I, &challenge_length) == -1)
		goto fail;
	}
	else if (streq(argv[i], "-copy")) {
	  if (++i == argc || vfs_uri_dst != NULL)
		goto fail;
	  do_copy = 1;
	  vfs_uri_dst = argv[i];
	}
	else if (streq(argv[i], "-dec")) {
	  if (++i == argc)
		goto fail;
	  do_dec = 1;
	  dec_str = argv[i];
	}
	else if (streq(argv[i], "-delete"))
	  do_delete = 1;
	else if (streq(argv[i], "-disable")) {
	  do_disable = 1;
	  do_set = 1;
	}
	else if (streq(argv[i], "-enable")) {
	  do_enable = 1;
	  do_set = 1;
	}
	else if (streq(argv[i], "-enc")) {
	  if (++i == argc)
		goto fail;
	  do_enc = 1;
	  if ((challenge_str = canonicalize_challenge(argv[i])) == NULL) {
		fprintf(stderr, "Invalid challenge string: \"%s\"\n", argv[i]);
		goto fail;
	  }
	}
	else if (streq(argv[i], "-expired")) {
	  do_expired = 1;
	  do_list = 1;
	}
	else if (streq(argv[i], "-flat")) {
	  emit = EMIT_FLAT;
	  do_show = 1;
	}
	else if (streq(argv[i], "-get"))
	  do_get = 1;
	else if (streq(argv[i], "-grid")) {
	  if (++i == argc)
		goto fail;
	  grid_str_in = argv[i];
	}
	else if (streq(argv[i], "-h") || streq(argv[i], "-help"))
	  goto fail;
	else if (streq(argv[i], "-html")) {
	  emit = EMIT_HTML;
	  do_show = 1;
	}
	else if (streq(argv[i], "-htmlcss")) {
	  emit = EMIT_HTML_CSS;
	  do_show = 1;
	}
	else if (streq(argv[i], "-inkeys")) {
	  if (++i == argc)
		goto fail;
	  inkeys_item_type = argv[i];
	}
	else if (streq(argv[i], "-lifetime")) {
	  unsigned int ndays;

	  if (++i == argc)
		goto fail;
	  if (strnum(argv[i], STRNUM_UI, &ndays) == -1 || ndays == 0)
		goto fail;

	  auth_grid_lifetime_secs = ndays * 60 * 60 * 24;
	}
	else if (streq(argv[i], "-list"))
	  do_list = 1;
	else if (streq(argv[i], "-long")) {
	  do_list = 1;
	  do_long = 1;
	}
	else if (streq(argv[i], "-nrows")) {
	  if (++i == argc)
		goto fail;
	  if (strnum(argv[i], STRNUM_I, &nrows) == -1)
		goto fail;
	}
	else if (streq(argv[i], "-ncols")) {
	  if (++i == argc)
		goto fail;
	  if (strnum(argv[i], STRNUM_I, &ncols) == -1)
		goto fail;
	}
	else if (streq(argv[i], "-outkeys")) {
	  if (++i == argc)
		goto fail;
	  outkeys_item_type = argv[i];
	}
	else if (strprefix(argv[i], "-pin") != NULL) {
	  use_pin = 1;
	  if (!streq(argv[i], "-pin")
		  && strnum(&argv[i][4], STRNUM_UI, &pin_length) == -1)
		goto fail;
	}
	else if (streq(argv[i], "-refresh"))
	  do_refresh = 1;
	else if (streq(argv[i], "-rng"))
	  use_rng = 1;
	else if (streq(argv[i], "-seed")) {
	  if (++i == argc)
		goto fail;
	  seed = argv[i];
	}
	else if (streq(argv[i], "-serial"))
	  do_serial = 1;
	else if (streq(argv[i], "-set"))
	  do_set = 1;
	else if (streq(argv[i], "-size")) {
	  do_list = 1;
	  do_size = 1;
	}
	else if (streq(argv[i], "-test"))
	  do_test = 1;
	else if (streq(argv[i], "-text"))
	  emit = EMIT_TEXT;
	else if (streq(argv[i], "-validate")) {
	  if (++i == argc)
		goto fail;
	  if ((challenge_str = canonicalize_challenge(argv[i])) == NULL) {
		fprintf(stderr, "Invalid challenge string: \"%s\"\n", argv[i]);
		goto fail;
	  }
	  if (++i == argc)
		goto fail;
	  response_str = argv[i];
	  do_validate = 1;
	}
	else if (streq(argv[i], "-vfs")) {
	  if (++i == argc || vfs_uri_alt != NULL)
		goto fail;
	  vfs_uri_alt = argv[i];
	}
	else {
	  fprintf(stderr, "Unrecognized argument\n");
	  goto fail;
	}
  }

  if (argv[i] != NULL) {
	username = argv[i++];
	if (i != argc)
	  goto fail;
  }

  n = do_challenge + do_copy + do_dec + do_delete + do_enc + do_list
	+ do_get + do_refresh + do_serial + do_set + do_show + do_validate;
  if (n > 1)
	goto fail;
  if (do_get && username != NULL)
	do_show = 1;
  if (n == 0) {
	if ((do_enable || do_disable) && username != NULL)
	  do_set = 1;
	else {
	  if (username != NULL)
		do_get = 1;
	  do_show = 1;
	}
  }

  if (do_long && do_size) {
	fprintf(stderr, "Incompatible flags\n");
	goto fail;
  }

  if (vfs_uri_alt != NULL) {
	if ((grid_item_type = add_vfs(vfs_uri_alt)) == NULL)
	  goto fail;
  }

  if (nrows < AUTH_GRID_MIN_ROWS) {
	fprintf(stderr, "The minimum number of rows is %d\n", AUTH_GRID_MIN_ROWS);
	goto fail;
  }
  if (nrows > AUTH_GRID_MAX_ROWS) {
	fprintf(stderr, "The maximum number of rows is %d\n", AUTH_GRID_MAX_ROWS);
	goto fail;
  }
  if (ncols < AUTH_GRID_MIN_COLS) {
	fprintf(stderr, "The minimum number of cols is %d\n", AUTH_GRID_MIN_COLS);
	goto fail;
  }
  if (ncols > AUTH_GRID_MAX_COLS) {
	fprintf(stderr, "The maximum number of cols is %d\n", AUTH_GRID_MAX_COLS);
	goto fail;
  }
  if (challenge_length < AUTH_GRID_MIN_CHALLENGE) {
	fprintf(stderr, "The minimum challenge lenght is %d\n"
			, AUTH_GRID_MIN_CHALLENGE);
	goto fail;
  }

  if (use_rng) {
	if (seed == NULL)
	  goto fail;
	rng_state = rng_init(seed);
  }

  if (do_copy) {
	if (vfs_uri_dst != NULL) {
	  if ((grid_item_type_out = add_vfs(vfs_uri_dst)) == NULL)
		goto fail;
	}
	else
	  goto fail;

	if (streq(grid_item_type, grid_item_type_out)) {
	  fprintf(stderr, "Input and output grids cannot be the same\n");
	  goto fail;
	}

	if (auth_grid_copy(grid_item_type, grid_item_type_out) == -1)
	  return(-1);

	return(0);
  }

  if (do_enc) {
	char *str, *token;

	if (auth_grid_parse_challenge(challenge_str) == NULL) {
	  fprintf(stderr, "Invalid challenge string: \"%s\"\n", challenge_str);
	  goto fail;
	}

	token = auth_grid_encrypt_challenge(challenge_str);
	printf("%s\n", token);

	if (do_test) {
	  challenge = auth_grid_decrypt_challenge(token, AUTH_GRID_CHALLENGE_SECS);
	  if (challenge == NULL) {
		fprintf(stderr, "Failed to decrypt!\n");
		return(-1);
	  }
	  str = canonicalize_challenge(auth_grid_challenge_to_text(challenge));
	  if (!streq(str, challenge_str))
		printf("Failed!\n");
	  else
		printf("Ok\n");
	}

	return(0);
  }

  if (do_dec) {
	challenge = auth_grid_decrypt_challenge(dec_str, AUTH_GRID_CHALLENGE_SECS);
	if (challenge == NULL) {
	  fprintf(stderr, "Failed to decrypt!\n");
	  return(-1);
	}

	printf("%s\n", auth_grid_challenge_to_text(challenge));

	return(0);
  }

  if (do_list) {
	Dsvec *dsv;

	if (username != NULL) {
	  if ((grid = auth_grid_get(grid_item_type, username)) == NULL) {
		fprintf(stderr, "User \"%s\" not found\n", username);
		return(-1);
	  }

	  if (!do_expired || grid_has_expired(grid, auth_grid_lifetime_secs)) {
		if (do_size)
		  printf("-nrows %d -ncols %d\n", grid->nrows, grid->ncols);
		else
		  printf("%s\n", do_long ? list_grid(grid) : username);
		return(0);
	  }

	  return(-1);
	}

	dsv = auth_grid_list(grid_item_type);
	for (i = 0; i < dsvec_len(dsv); i++) {
	  char *u;

	  u = dsvec_ptr(dsv, i, char *);
	  if ((grid = auth_grid_get(grid_item_type, u)) == NULL) {
		fprintf(stderr, "Cannot get grid for \"%s\"\n", u);
		return(-1);
	  }

	  if (do_expired) {
		if (grid_has_expired(grid, auth_grid_lifetime_secs)) {
		  if (do_size)
			printf("%s: -nrows %d -ncols %d\n", u, grid->nrows, grid->ncols);
		  else
			printf("%s\n", do_long ? list_grid(grid) : u);
		}
	  }
	  else {
		if (do_size)
		  printf("%s: -nrows %d -ncols %d\n", u, grid->nrows, grid->ncols);
		else
		  printf("%s\n", do_long ? list_grid(grid) : u);
	  }
	}

	return(0);
  }

  if (do_refresh) {
	char *pin, *u;
	Auth_grid *new_grid;
	Dsvec *dsv;

	/*
	 * When refreshing, there are three possibilities:
	 * keep existing PIN or lack thereof (the default), delete any existing
	 * PIN, or generate a new PIN of a given length.
	 */
	if (username != NULL) {
	  if ((grid = auth_grid_get(grid_item_type, username)) == NULL) {
		fprintf(stderr, "User \"%s\" not found\n", username);
		return(-1);
	  }
	  if (pin_length == 0) {
		use_pin = 0;
		pin = NULL;
	  }
	  else if (use_pin)
		pin = NULL;
	  else
		pin = grid->pin;

	  new_grid = auth_grid_generate(username, nrows, ncols, use_pin, pin);
	  new_grid->enabled = grid->enabled;
	  if (auth_grid_set(new_grid, grid_item_type, username) == -1)
		return(-1);
	}
	else {
	  dsv = auth_grid_list(grid_item_type);
	  for (i = 0; i < dsvec_len(dsv); i++) {	  
		u = dsvec_ptr(dsv, i, char *);
		if ((grid = auth_grid_get(grid_item_type, u)) == NULL) {
		  fprintf(stderr, "User \"%s\" not found\n", u);
		  return(-1);
		}
		if (pin_length == 0) {
		  use_pin = 0;
		  pin = NULL;
		}
		else if (use_pin)
		  pin = NULL;
		else
		  pin = grid->pin;

		new_grid = auth_grid_generate(u, nrows, ncols, use_pin, pin);
		new_grid->enabled = grid->enabled;

		if (auth_grid_set(new_grid, grid_item_type, u) == -1)
		  return(-1);
	  }
	}

	return(0);
  }

  if (do_serial) {
	if (username == NULL)
	  goto fail;
	grid = auth_grid_get(grid_item_type, username);
	if (grid != NULL) {
	  printf("%s\n", grid->serial);
	  return(0);
	}

	fprintf(stderr, "User \"%s\" not found\n", username);
	return(-1);
  }

  if (do_delete) {
	if (username == NULL)
	  goto fail;
	st = auth_grid_delete(grid_item_type, username);
	return(st);
  }

  if (do_challenge) {
	if (username != NULL) {
	  grid = auth_grid_get(grid_item_type, username);
	  if (grid == NULL) {
		fprintf(stderr, "User \"%s\" not found\n", username);
		return(-1);
	  }
	  ncols = grid->ncols;
	  nrows = grid->nrows;
	}
	challenge = auth_grid_challenge(challenge_length, ncols, nrows);
	printf("%s\n", auth_grid_challenge_to_text(challenge));

	return(0);
  }

  if (do_get) {
	if (username == NULL)
	  goto fail;
	grid_str = auth_grid_get_flattened(grid_item_type, username);
	if (grid_str == NULL) {
	  fprintf(stderr, "No grid found for username \"%s\"\n", username);
	  return(-1);
	} 

	if (use_pin) {
	  grid = auth_grid_unflatten(grid_str, 1);
	  if (grid != NULL && grid->pin != NULL) {
		printf("%s\n", grid->pin);
		return(0);
	  }
	  return(-1);
	}
  }

  if (do_validate) {
	if (grid_str_in == NULL)
	  goto fail;
	grid = auth_grid_unflatten(grid_str_in, 0);
	challenge = auth_grid_parse_challenge(challenge_str);
	st = auth_grid_verify(grid, challenge, response_str,
						  auth_grid_lifetime_secs);
	if (st == 0)
	  printf("Ok\n");
	else
	  printf("No\n");

	return(st);
  }

  /*
   * If given a grid or a grid was retrieved, use it, otherwise
   * generate a new one.
   */
  if (grid_str != NULL && grid_str_in != NULL)
	goto fail;
  if (grid_str_in != NULL)
	grid = auth_grid_unflatten(grid_str, 0);
  else if (grid_str != NULL)
	grid = auth_grid_unflatten(grid_str, 1);
  else
	grid = auth_grid_generate(username, nrows, ncols, use_pin, NULL);

  if (grid == NULL) {
	fprintf(stderr, "Unable to create a grid\n");
	goto fail;
  }

  if (do_set) {
	if (username == NULL)
	  goto fail;

	if (do_disable)
	  grid->enabled = 0;
	else if (do_enable)
	  grid->enabled = 1;

	st = auth_grid_set(grid, grid_item_type, username);

	return(st);
  }

  /* Display the grid. */
  if (!do_show)
	goto fail;

  if (emit == EMIT_TEXT)
	printf("%s", auth_grid_text(grid));
  else if (emit == EMIT_HTML || emit == EMIT_HTML_CSS) {
	if (emit == EMIT_HTML_CSS) {
	  printf("<style type=\"text/css\">\n");
	  printf(".auth_grid {\n");
	  printf("font-family: \"courier new\",courier,fixed;\n");
	  printf("font-size: 9pt;\n");
	  printf("}\n");
	  printf(".auth_grid th {\n");
	  printf("font-size: 10pt;\n");
	  printf("background: #ffffcc;\n");
	  printf("}\n");
	  printf(".auth_grid_tr_1 {\n");
	  printf("background-color: #e4e4e4;\n");
	  printf("}\n");
	  printf(".auth_grid_tr_2 {\n");
	  printf("background-color: #d2d2d2;\n");
	  printf("}\n");
	  printf(".auth_grid td {\n");
	  printf("font-weight: bold;\n");
	  printf("text-align: center;\n");
	  printf("}\n");
	  printf("</style>\n");
	}
	printf("%s", auth_grid_html(grid));
  }
  else if (emit == EMIT_FLAT)
	printf("%s\n", auth_grid_flatten(grid, 0));

  /*
   *
   */
  st = 0;
  if (do_test) {
	char buf[128];

	challenge = auth_grid_challenge(challenge_length, ncols, nrows);

	printf("%s\n", auth_grid_challenge_to_text(challenge));

	printf("Response? ");
	fflush(stdout);
	if (fgets(buf, sizeof(buf), stdin) == NULL)
	  return(-1);
	if ((p = strchr(buf, (int) '\n')) == NULL)
	  return(-1);
	*p = '\0';

	st = auth_grid_verify(grid, challenge, buf, auth_grid_lifetime_secs);
	if (st == -1)
	  printf("Sorry\n");
	else
	  printf("OK\n");
  }

  return(st);
}

#else

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

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

  exit(1);
}
#endif
