/* opiesubr.c: OTP support subroutines.

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.01: opiereadpass() -- Before messing with
                 terminal attributes, make sure that we're actually on a
                 terminal in order to be more pipe-friendly.
        
	Modified at NRL for OPIE 2.0. Changes include making the code more
		generic by changing it to support both MD4 and MD5 with the
		default set at compile time using the MDX symbol and cleaned
		includes and code to be POSIX compliant and hence portable to
		more systems.
	Modified at NRL to support MD5 as an alternative to MD4 for OPIE 1.0.
	Written at Bellcore for the S/Key Version 1 software distribution
		(skeysubr.c).
*/
#include "opie_cfg.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>	/* ANSI C standard library */

#ifdef	__MSDOS__
#include <dos.h>
#else	/* must be UNIX */
#include <fcntl.h>	/* POSIX file control function headers */
#include <termios.h>	/* POSIX Terminal I/O functions */
#include <unistd.h>	/* POSIX standard definitions */
#endif

#include <utmp.h>
#if DOUTMPX
#include <utmpx.h>
#endif	/* DOUTMPX */

void getutmpentry __ARGS((char *, struct UTMPX *));

#include "global.h"
#include "md5.h"
#include "md4.h"
#include "opie.h"

/* Crunch a key:
 * concatenate the seed and the password, run through MD4 or MD5 and
 * collapse to 64 bits. This is defined as the user's starting key.
 */
int opiekeycrunch(algorithm, result, seed, passwd)
unsigned algorithm;	/* which algorithm to use: 4==MD4, 5==MD5 */
char *result;	/* 8-byte result */
char *seed;	/* Seed, any length */
char *passwd;	/* Password, any length */
{
  char *buf;
  struct MD5_CTX md5;
  struct MD4_CTX md4;
  unsigned long results[4];
  unsigned int buflen;

/* NOTE - we need to bound seed and password before this point.
   "any length" can bite us... */
  buflen = strlen(seed) + strlen(passwd);
  if ((buf = malloc(buflen + 1)) == NULL)
    return -1;
  strcpy(buf, seed);
  strcat(buf, passwd);

  /* Crunch the key through MD[45] */
  opiesevenbit(buf);
  if (4 == algorithm) {
    MD4Init(&md4);
    MD4Update(&md4, (unsigned char *) buf, buflen);
    MD4Final((unsigned char *) results, &md4);
  } else {
    MD5Init(&md5);
    MD5Update(&md5, (unsigned char *) buf, buflen);
    MD5Final((unsigned char *) results, &md5);
  }
  free(buf);

  results[0] ^= results[2];
  results[1] ^= results[3];

  memcpy(result, (char *) results, 8);
  return 0;
}

/*
 * The one-way function f(x).
 * Takes 8 bytes and returns 8 bytes in place.
 * The value of "algorithm" determines whether MD4 or MD5 is used,
 * where (algorithm==5) implies MD5 and (algorithm==4) implies MD4.
 */
void opiehash(x, algorithm)
char *x;
unsigned algorithm;
{
  struct MD5_CTX md5;
  struct MD4_CTX md4;
  unsigned long results[4];

  if (4 == algorithm) {
    MD4Init(&md4);
    MD4Update(&md4, (unsigned char *) x, 8);
    MD4Final((unsigned char *) results, &md4);
  } else {
    /* default algorithm is MD5 */
    MD5Init(&md5);
    MD5Update(&md5, (unsigned char *) x, 8);
    MD5Final((unsigned char *) results, &md5);
  }
  /* Fold 128 to 64 bits */
  results[0] ^= results[2];
  results[1] ^= results[3];

  /* Only works on byte-addressed little-endian machines!! */
  memcpy(x, (char *) results, 8);
}


/* Strip trailing cr/lf from a line of text */
void opiestrip_crlf(buf)
char *buf;
{
  char *cp;

  if ((cp = strchr(buf, '\r')) != NULL)
    *cp = '\0';

  if ((cp = strchr(buf, '\n')) != NULL)
    *cp = '\0';
}

/************************/
#ifdef	__MSDOS__
char *opiereadpass(buf, n)
char *buf;
int n;
{
  int i;
  char *cp;

  for (cp = buf, i = 0; i < n; i++)
    if ((*cp++ = bdos(7, 0, 0)) == '\r')
      break;
  *cp = '\0';
  printf("\n");
  opiestrip_crlf(buf);
  return buf;
}

#else	/* assume POSIX compliant OS */
char *opiereadpass(buf, n)
char *buf;
int n;
{
  struct termios attr, orig_attr;
  int bytes_read = 0;

  /* This section was heavily rewritten by rja following the model of code
     samples circa page 151 of the POSIX Programmer's Guide by Donald Lewine,
     ISBN 0-937175-73-0. That book is Copyright 1991 by O'Reilly &
     Associates, Inc. All Rights Reserved. I recommend the book to anyone
     trying to write portable software. rja */

  /* Flush any pending output */
  fflush(stdout);

  /* Get original terminal attributes */
  if (isatty(STDIN_FILENO)) {
    if (tcgetattr(STDIN_FILENO, &orig_attr) != 0)
      return ((char *) -1);

    /* copy terminal settings into attr */
    memcpy(&attr, &orig_attr, sizeof(struct termios));

    /* Turn off echoing */
    attr.c_lflag &= ~(ECHO);	/* should also disable raw and cbreak */
    attr.c_lflag |= ISTRIP;	/* strip input to 7 bits */
    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &attr) != 0)
      return ((char *) -1);
  }

  /* get input, avoiding use of fgets() for security reasons */
  bytes_read = read(STDIN_FILENO, buf, n);
  buf[n - 1] = 0;
  opiestrip_crlf(buf);

  /* Restore previous tty modes */
  if (isatty(STDIN_FILENO))
    if (tcsetattr(STDIN_FILENO, TCSANOW, &orig_attr) != 0)
      return ((char *) -1);

  /* After the secret key is taken from the keyboard, the line feed is
     written to standard error instead of standard output.  That means that
     anyone using the program from a terminal won't notice, but capturing
     standard output will get the key words without a newline in front of
     them. */
  fprintf(stderr, "\n");
  fflush(stderr);

  return buf;	/* probably should return buffer length as well */
}

#endif

/* remove backspaced-over charaters from the string */
void opiebackspace(buf)
char *buf;
{
  char bs = '\b';
  char del = 127;
  char *cp = buf;
  char *out = buf;

  while (*cp) {
    if ((*cp == bs) || (*cp == del)) {
      if (out == buf) {
	cp++;
	continue;
      } else {
	cp++;
	out--;
      }
    } else {
      *out++ = *cp++;
    }
  }
  *out = '\0';
}

void opiesevenbit(s)
char *s;
{
  /* make sure there are only 7 bit code in the line */
  while (*s) {
    *s = 0x7f & (*s);
    s++;
  }
}

void opieversion()
{
  printf("\nNRL OPIE %s (%s)\n\nCompile-time options:\n%s\n\n", VERSION,
    DATE, OPTION_STRING);
  exit(0);
}

int opieinsecure()
{
#ifndef NO_INSECURE_CHECK
  char *display_name;
  char *s;
  char *term_name;
  char hostname[MAXHOSTNAMELEN];
  int  insecure = 0;
  struct UTMPX utmp;
  

  display_name = (char *) getenv("DISPLAY");
  term_name = (char *) getenv("TERM");

  if (display_name != NULL) {
    insecure = 1;
    if (s = strchr(display_name, ':'))
      if (s == display_name)
	insecure = 0;
      else {
	*s = 0;
	if (!strcmp("unix", display_name))
	  insecure = 0;
	else {
	  if (!gethostname(hostname, sizeof(hostname))) {
	    if (!strcmp(hostname, display_name))
	      insecure = 0;
	    else {
	      if (s = strchr(display_name, '.')) {
		*s = 0;
		if (!strcmp(hostname, display_name))
		  insecure = 0;
	      } /* endif display_name is '.' */
	    } /* endif hostname != display_name */
	  } /* endif was able to get hostname */
	} /* endif display_name == UNIX */
      } /* endif display_name == ":" */ 
    if (insecure)
      return (1);
  } 


  /* If no DISPLAY variable exists and TERM=xterm, 
     then we probably have an xterm executing on a remote system 
     with an rlogin or telnet to our system.  If it were a local
     xterm, then the DISPLAY environment variable would
     have to exist. rja */
  if ((display_name == NULL) && (term_name != NULL)    && 
      (0 == strcmp("xterm", term_name))) {
      return (1);
  } 


  memset(&utmp, 0, sizeof(utmp));
  getutmpentry(ttyname(0), &utmp);
  if (utmp.ut_host[0]) {
    insecure = 1;
    if (s = strchr(utmp.ut_host, ':')) {
      if (s = &(utmp.ut_host[0]))
	insecure = 0;
      else {
	*s = 0;
	if (display_name)
	  if (!strcmp(utmp.ut_host, display_name))
	    insecure = 0;
      }
    }
  }
  if (insecure)
    return (1);


#endif	/* NO_INSECURE_CHECK */
  return 0;
}

/* 
   Applies "good password" rules to the secret pass phrase.

   We currently implement the following:

   Passwords must be at least OPIE_PASS_MIN (10) characters long.
   Passwords must be at most OPIE_PASS_MAX (127) characters long.

   N.B.: Passing NULL pointers to this function is a bad idea.
*/
int opiepasscheck(passphrase)
char *passphrase;
{
	int len = strlen(passphrase);

	if (len < OPIE_PASS_MIN)
		return 1;

	if (len > OPIE_PASS_MAX)
		return 1;

	return 0;
}
