/* Time-stamp: "Sat 30 Jan 1999, 13:01:51 EST by drk@sgi.com (David Kaelbling)"
 * 
 * A stripped-down version of skeylogin, intended for use as an IRIX
 * login SITECHECK program.  A SITECHECK program must be executable,
 * owned by root, and not writable by others.
 *
 * Usage: keyauth name [ remhost [ rusername ] ]
 * Prompt for an s/key password and authenticate it.  Return status:
 * 	0:     Success; user was authenticated, log in.
 *	1:     Failure; exit login.
 *	2:     Failure; try again (don't exit login).
 *	other: Use normal UNIX authentication.
 */

#ifdef _BSD_SIGNALS
#undef _BSD_SIGNALS
#endif
#ifdef _BSD_COMPAT
#undef _BSD_COMPAT
#endif

#include <sys/param.h>
#include <sys/types.h>

#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <termio.h>

#include "skey.h"

#define SCPYN(a, b)	strncpy(a, b, sizeof(a)), a[sizeof(a)-1]='\0'

#define NMAX	32		/* Should be >= sizeof(utmpx.ut_user) */

#define SITE_OK		0
#define SITE_FAIL	1
#define SITE_AGAIN	2
#define SITE_CONTINUE	3

/*
 * This bounds the time given to login.  We initialize it here
 * so it can be patched on machines where it's too small.
 */
int timeout = 300;

char *readskeypass(char *buf, int n);
static void timedout(void);
static int authfile(char *host);
static int isaddr(register char *s);
static long aton(register char *s);
static int rdnets(long host);

void
main(int argc, char *argv[])
{
  struct passwd *pwd;
  char lusername[NMAX+1];
  char host[MAXHOSTNAMELEN];
  char rusername[NMAX+1];
  char skeyprompt[80];
  struct skey skey;
  int skey_status;
  char buf[LINE_MAX];

  *host = *rusername = *skeyprompt = '\0';

  /* Save the username */
  if (argc > 1) {
    SCPYN(lusername, argv[1]);
    if (*lusername == '-') {
      puts("user names may not start with '-'.");
      exit(SITE_FAIL);
    }
    argc--;
    argv++;
  } else {
    puts("missing user name argument.");
    exit(SITE_FAIL);
  }

  /* Save the remotehost */
  if (argc > 1) {
    SCPYN(host, argv[1]);
    if (*host == '-') {
      puts("remote host name may not start with '-'.");
      exit(SITE_FAIL);
    }
    argc--;
    argv++;
  }

  /* Save the remote username */
  if (argc > 1) {
    SCPYN(rusername, argv[1]);
    if (*rusername == '-') {
      puts("remote user names may not start with '-'.");
      exit(SITE_FAIL);
    }
    argc--;
    argv++;
  }

  /* Check for extra args. */
  if (argc > 1) {
    puts("too many arguments.");
    exit(SITE_FAIL);
  }


  /* If this is a local login use normal password authentication. */
  if (authfile(host))
    exit(SITE_CONTINUE);

  /* Punt back to login if this user does not have a password. */
  pwd = getpwnam(lusername);
  if (pwd && *pwd->pw_passwd == '\0')
    exit(SITE_CONTINUE);


  /* Issue an s/key challenge */
  if (feof(stdin)) exit(SITE_FAIL);
  skey_status = skeychallenge(&skey, lusername, skeyprompt);
  printf("%s\n", skeyprompt);
  printf("(s/key required)\nPassword:");
  fflush(stdout);

  /* Read password */
  signal(SIGALRM, timedout);
  alarm(timeout);
  readskeypass(buf, sizeof(buf));
  alarm(0);

  /* Did S/Key authentication succeed? */
  if (skey_status == 0 && skeyverify(&skey, buf) == 0 && 
      pwd && *pwd->pw_passwd != '*' && *pwd->pw_passwd != '#') {
    if (skey.n < 5)
      puts("Warning! Change password soon");
    exit(SITE_OK);
  }

  puts("Login incorrect");
  exit(SITE_AGAIN);
}

static void
timedout(void)
{
  printf("\nLogin timed out after %d seconds\n", timeout);
  exit(SITE_FAIL);
}

/*
 * Turn host into an IP address and then look it up in the authorization
 * database to determine if ordinary password logins are OK.
 */
static int
authfile(char *host)
{
  long n;
  struct hostent *hp;
  char **lp;

  if (*host == '\0') {
    /* Local login, okay */
    return 1;
  }

  if (isaddr(host)) {
    n = aton(host);
    return rdnets(n);
  } else {
    hp = gethostbyname(host);
    if (hp == NULL) {
      printf("Unknown host %s\n", host);
      return 0;
    }
    for (lp = hp->h_addr_list; *lp != NULL; lp++) {
      memcpy((char *)&n, *lp, sizeof(n));
      n = ntohl(n);
      if (rdnets(n))
	return 1;
    }
    return 0;
  }
}

/*
 * Return non-zero if "host" is permitted to use normal password login.
 */
static int
rdnets(long host)
{
  FILE *fp;
  char buf[LINE_MAX], *cp;
  long pattern, mask;
  int permit_it;

  fp = fopen("/etc/skey.access", "r");
  if (fp == NULL)
    return 0;

  while(fgets(buf, sizeof(buf), fp), !feof(fp)) {
    if (*buf == '#')
      continue;			/* Comment */

    cp = strtok(buf, " \t");
    if (cp == NULL) 
      continue;

    /* two choices: permit or deny */
    if (strncasecmp(cp, "permit", 4) == 0) {
      permit_it = 1;
    } else if (strncasecmp(cp, "deny" , 4) == 0) {
      permit_it = 0;
    } else {
      continue; /* ignore this it is not permit/deny */
    }

    cp = strtok(NULL, " \t");
    if (cp == NULL)
      continue;			/* Invalid line */
    pattern = aton(cp);

    cp = strtok(NULL, " \t");
    if (cp == NULL)
      continue;			/* Invalid line */
    mask = aton(cp);

    if ((host & mask) == pattern) {
      fclose(fp);
      return permit_it;
    }
  }

  fclose(fp);
  return 0;
}

/*
 * Return non-zero if string appears to be an IP address in dotted decimal;
 * return 0 otherwise (i.e., if string is a domain name)
 */
static int
isaddr(register char *s)
{
  char c;

  if (s == NULL)
    return 1;			/* Can't happen */

  while((c = *s++) != '\0') {
    if (c != '[' && c != ']' && !isdigit(c) && c != '.')
      return 0;
  }
  return 1;
}

/*
 * Convert Internet address in ascii dotted-decimal format (44.0.0.1) to
 * binary IP address
 */
static long
aton(register char *s)
{
  long n = 0;
  register int i;

  if (s == NULL)
    return 0;

  for (i = 24; i >= 0; i -= 8) {
    /* Skip any leading stuff (e.g., spaces, '[') */
    while(*s != '\0' && !isdigit(*s))
      s++;
    if (*s == '\0')
      break;

    n |= atol(s) << i;
    if ((s = strchr(s, '.')) == NULL)
      break;
    s++;
  }

  return n;
}
