/* opiepasswd.c: Add/change an OTP password in the key database.

Portions of this software are Copyright 1995 by Randall Atkinson and Dan
McDonald, All Rights Reserved. All Rights under this copyright are assigned
to the U.S. Naval Research Laboratory (NRL). The NRL Copyright Notice and
License Agreement applies to this software.

	History:

	Modified at NRL for OPIE 2.1. Minor autoconf changes.
        Modified heavily at NRL for OPIE 2.0.
	Written at Bellcore for the S/Key Version 1 software distribution
		(skeyinit.c).
*/
/* The implications of this program needing to run setuid are not entirely
   clear. We believe it to be safe, but more proactive measures need to be
   taken to reduce the risks of being setuid (such as discarding priveleges
   as quickly as possible. More thought needs to be given to this at some
   future date. */

#include "opie_cfg.h"

#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <stdio.h>
#if TM_IN_SYS_TIME
#include <sys/time.h>
#else /* TM_IN_SYS_TIME */
#include <time.h>
#endif /* TM_IN_SYS_TIME */
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>

#include "opie.h"

extern int optind;
extern char *optarg;

/* We really shouldn't be messing with this variable, but we have to in order
   to get the proper locking behavior. Programs other than opiepasswd should
   not mess with this variable. 
*/
extern char *opielockfilename;

void usage(myname)
char *myname;
{
  fprintf(stderr, "usage: %s [-v] [-h] [-c] [-n initial_sequence_number]\n                            [-s seed] [username]\n", myname);
  exit(1);
}

int main(argc, argv)
int argc;
char *argv[];
{
  struct opie opie;
  int rval, n = 499, nn, i, consolemode = 0;
  char seed[18];
  char tmp[OPIE_RESPONSE_MAX + 2];	/* extra space for \n */
  char key[8], key2[8];
  struct passwd *ppuser, *pp;
  char passwd[OPIE_PASS_MAX + 1], passwd2[OPIE_PASS_MAX + 1];
  char defaultseed[17];

  time_t now;
  struct tm *tm;
  char tbuf[30], buf[MAXHOSTNAMELEN];
  char lastc;
  int l;

  int recstart = 0;
  FILE *keyfile;
  char *savelockfilename;

  memset(seed, 0, sizeof(seed));
  memset(tmp, 0, sizeof(tmp));
  memset(key, 0, sizeof(key));
  memset(key2, 0, sizeof(key2));

  time(&now);
  srand(now);
  now = rand();
  if (gethostname(buf, sizeof(buf)) < 0) {
    perror("gethostname");
    buf[0] = 'k';
    buf[1] = 'e';
  }
  buf[NAMELEN] = 0;
  sprintf(defaultseed, "%s%04d", buf, (now % 9998) + 1);

  if (!(pp = ppuser = getpwuid(getuid()))) {
    fprintf(stderr, "Who are you?");
    return 1;
  }

  while ((i = getopt(argc, argv, "hvcn:s:")) != EOF) {
    switch (i) {
    case 'v':
      opieversion();
    case 'c':
      consolemode = 1;
      break;
    case 'n':
      nn = atoi(optarg);
      if (!(nn > 0 && nn < 10000)) {
	printf("Sequence numbers must be > 0 and < 10000\n");
	exit(1);
      }
      n = nn;
      break;
    case 's':
      nn = strlen(optarg);
      if ((nn > OPIE_SEED_MAX) || (nn < OPIE_SEED_MIN)) {
	printf("Seeds must be between %d and %d characters long.\n",
	       OPIE_SEED_MIN, OPIE_SEED_MAX);
	exit(1);
      }
      strncpy(seed, optarg, sizeof(seed));
      seed[sizeof(seed) - 1] = 0;
      break;
    default:
      usage(argv[0]);
    }
  }

  if (argc - optind >= 1) {
    if (strcmp(argv[optind], pp->pw_name)) {
      if (getuid()) {
	printf("Only root can change others' passwords.\n");
	return (1);
      }
      if ((pp = getpwnam(argv[optind])) == NULL) {
	printf("%s: user unknown.\n", argv[optind]);
	return 1;
      }
    }
  }
  rval = opiechallenge(&opie, pp->pw_name, tmp);
  switch (rval) {
  case 0:
/* This code is bad. It messes with the internal state block. */
    printf("Updating %s:\n", pp->pw_name);
    /* If they have a seed that ends in 0-8 just add one */
    l = strlen(opie.seed);
    if (l > 0) {
      lastc = opie.seed[l - 1];
      if (isdigit(lastc) && lastc != '9') {
	strcpy(defaultseed, opie.seed);
	defaultseed[l - 1] = lastc + 1;
      }
      if (isdigit(lastc) && lastc == '9' && l < 16) {
	strcpy(defaultseed, opie.seed);
	defaultseed[l - 1] = '0';
	defaultseed[l] = '0';
	defaultseed[l + 1] = '\0';
      }
    }
    recstart = opie.recstart;
    keyfile = fopen(KEY_FILE, "r+");
    if (keyfile) {
      if (fseek(keyfile, recstart, SEEK_SET)) {
        fclose(keyfile);
        keyfile = NULL;
      }
    }
    break;
  case 1:
    printf("Adding %s:\n", pp->pw_name);
    keyfile = fopen(KEY_FILE, "a");
    break;
  case -1:
    perror("Error opening key database");
    return 1;
  }

  if (!keyfile) {
    fprintf(stderr, "Error updating key database.\n");
    return 1;
  };

  if (!consolemode) {
    printf("Reminder: You need the response from your OPIE calculator.\n");
    if (!seed[0])
      strcpy(seed, defaultseed);
    if (!rval && getuid()) {
      printf("Old secret pass phrase:\n\t%s\n\tResponse: ", tmp);
      fgets(tmp, sizeof(tmp), stdin);
      opiestrip_crlf(tmp);
      opiebackspace(tmp);

      /* We don't want opieverify() removing our lock -- we want the atexit()
         handler to do it for us instead. */
      savelockfilename = opielockfilename;
      opielockfilename = NULL;
      nn = opieverify(&opie, tmp);
      opielockfilename = savelockfilename;
      if (nn) {
	fprintf(stderr, "Sorry.\n", i);
	exit(1);
      }
    }
    printf("New secret pass phrase:");
    for (i = 0;; i++) {
      if (i >= 2)
	exit(1);
      printf("\n\totp-md%d %d %s\n\tResponse: ", MDX, n, seed);
      fgets(tmp, sizeof(tmp), stdin);
      opiestrip_crlf(tmp);
      opiebackspace(tmp);
      if (tmp[0] == '?') {
	printf("Enter the response from your OTP calculator: \n");
	continue;
      }
      if (tmp[0] == '\0') {
        fprintf(stderr, "Secret pass phrase unchanged.\n");
	exit(1);
      }
      if ((opieetob(key, tmp) == 1) || opieatob8(key, tmp))
	break;	/* Valid format */
      printf("Invalid format, try again with 6 English words.\n");
    }
  } else {
    /* Get user's secret password */
    fprintf(stderr, "Reminder - Only use this method from the console; NEVER from remote. If you\n");
    fprintf(stderr, "are using telnet, xterm, or a dial-in, type ^C now or exit with no password.\n");
    fprintf(stderr, "Then run opiepasswd without the -c parameter.\n");
    if (opieinsecure()) {
      fprintf(stderr, "Sorry, but you don't seem to be on the console or a secure terminal.\n");
      exit(1);
    };
    printf("Using MD%d to compute responses.\n", MDX);
    if (!rval && getuid()) {
      printf("Enter old secret pass phrase: ");
      opiereadpass(passwd, sizeof(passwd));
      if (passwd[0] == '\0') {
        fprintf(stderr, "Secret pass phrase unchanged.\n");
	exit(1);
      }
      if (opiekeycrunch(MDX, key, opie.seed, passwd) != 0) {
	fprintf(stderr, "%s: key crunch failed -- secret pass phrase unchanged\n", argv[0]);
	exit(1);
      }
      nn = opie.n - 1;
      while (nn-- != 0)
	opiehash(key, MDX);
      opiebtoe(buf, key);
      savelockfilename = opielockfilename;
      opielockfilename = NULL;
      nn = opieverify(&opie, buf);
      opielockfilename = savelockfilename;
      if (nn) {
	fprintf(stderr, "Sorry.\n", i);
	exit(1);
      }
    }
    for (i = 0;; i++) {
      if (i >= 2)
	exit(1);
      printf("Enter new secret pass phrase: ");
      opiereadpass(passwd, sizeof(passwd));
      if (!passwd[0] || feof(stdin)) {
	fprintf(stderr, "Secret pass phrase unchanged.\n");
	exit(1);
      }
      printf("Again new secret pass phrase: ");
      opiereadpass(passwd2, sizeof(passwd2));
      if (feof(stdin)) {
	fprintf(stderr, "Secret pass phrase unchanged.\n");
	exit(1);
      }
      if (!passwd[0] || !strcmp(passwd, passwd2))
        break;
      fprintf(stderr, "Sorry, no match.\n");
    }
    if (opiepasscheck(passwd)) { 
      fprintf(stderr, "Secret pass phrases must be between %d and %d characters long.\n", OPIE_PASS_MIN, OPIE_PASS_MAX);
      exit(1);
    };
    if (!seed[0])
      strcpy(seed, defaultseed);

    /* Crunch seed and password into starting key */
    if (opiekeycrunch(MDX, key, seed, passwd) != 0) {
      fprintf(stderr, "%s: key crunch failed\n", argv[0]);
      return (1);
    }
    nn = n;
    while (nn-- != 0)
      opiehash(key, MDX);
  }

  time(&now);
  tm = localtime(&now);
  strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm);

  opiebtoa8(tmp, key);
  fprintf(keyfile, "%s %04d %-16s %s %-21s\n", pp->pw_name, n,
	  seed, tmp, tbuf);
  fclose(keyfile);
  printf("\nID %s OTP key is %d %s\n", pp->pw_name, n, seed);
  printf("%s\n", opiebtoe(buf, key));
  return 0;
}
