/* opieauth.c: Sitecheck program for doing opie authentication.

	Usage: keyauth username [ remote_host [ remote_username ] ]
	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.

	History:

	Created by drk.  A stripped down version of opielogin.c,
		suitable for use as a login SITECHECK program on
		Silicon Graphics machines running IRIX.  A SITECHECK 
		program must be executable, owned by root, and not 
		writable by anyone else.

*/

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

#include "opie_cfg.h"	/* OPIE: defines symbols for filenames & pathnames */
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif /* HAVE_SYS_PARAM_H */
#include <sys/stat.h>
#include <sys/types.h>

#if HAVE_SYS_FILE_H
#include <sys/file.h>
#endif /* HAVE_SYS_FILE_H */
#include <signal.h>
#if HAVE_PWD_H
#include <pwd.h>	/* POSIX Password routines */
#endif /* HAVE_PWD_H */
#include <stdio.h>
#include <errno.h>
#if HAVE_UNISTD_H
#include <unistd.h>	/* Basic POSIX macros and functions */
#endif /* HAVE_UNISTD_H */
#if HAVE_STRING_H
#include <string.h>	/* ANSI C string functions */
#endif /* HAVE_STRING_H */
#include <fcntl.h>	/* File I/O functions */
#include <syslog.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif /* HAVE_STDLIB_H */

#include "opie.h"

#define NMAX	32
#define HMAX	256

static char rusername[NMAX + 1];
static char name[NMAX + 1];
static char host[HMAX + 1];
static struct passwd nouser;
static struct passwd thisuser;

static int need_opieverify = 0;
static struct opie opie;

#if HAVE_SHADOW_H
#include <shadow.h>
#endif /* HAVE_SHADOW_H */

extern int errno;

/*
 * The "timeout" variable bounds the time given to login.
 * We initialize it here for safety and so that it can be
 * patched on machines where the default value is not appropriate.
 */
static int timeout = 120;

#if HAVE_CRYPT_H
#include <crypt.h>
#endif /* HAVE_CRYPT_H */

#undef TRUE
#define TRUE -1


/*------------------ BEGIN REAL CODE --------------------------------*/

/* We allow the malloc()s to potentially leak data out because we can
only call this routine about four times in the lifetime of this process
and the kernel will free all heap memory when we exit or exec. */
static int lookupuser FUNCTION_NOARGS
{
  struct passwd *pwd;
#if HAVE_SHADOW
  struct spwd *spwd;
#endif /* HAVE_SHADOW */

  memcpy(&thisuser, &nouser, sizeof(thisuser));

  if (!(pwd = getpwnam(name)))
    return -1;

  thisuser.pw_uid = pwd->pw_uid;
  thisuser.pw_gid = pwd->pw_gid;

  if (!(thisuser.pw_name = malloc(strlen(pwd->pw_name) + 1)))
    goto lookupuserbad;
  strcpy(thisuser.pw_name, pwd->pw_name);

  if (!(thisuser.pw_dir = malloc(strlen(pwd->pw_dir) + 1)))
    goto lookupuserbad;
  strcpy(thisuser.pw_dir, pwd->pw_dir);

  if (!(thisuser.pw_shell = malloc(strlen(pwd->pw_shell) + 1)))
    goto lookupuserbad;
  strcpy(thisuser.pw_shell, pwd->pw_shell);

#if HAVE_SHADOW
  if (!(spwd = getspnam(name)))
    goto lookupuserbad;

  pwd->pw_passwd = spwd->sp_pwdp;

  endspent();
#endif /* HAVE_SHADOW */

  if (!(thisuser.pw_passwd = malloc(strlen(pwd->pw_passwd) + 1)))
    goto lookupuserbad;
  strcpy(thisuser.pw_passwd, pwd->pw_passwd);

  endpwent();

  return ((thisuser.pw_passwd[0] == '*') || (thisuser.pw_passwd[0] == '#'));

lookupuserbad:
  memcpy(&thisuser, &nouser, sizeof(thisuser));
  return -1;
}

static VOIDRET timedout FUNCTION((i), int i)
{
  /* input variable declared just to keep the compiler quiet */
  printf("Login timed out after %d seconds\n", timeout);
  syslog(LOG_CRIT, "Login timed out after %d seconds!", timeout);
  if (need_opieverify) {
    char buf[256] = "";
    (void) opieverify(&opie, buf);
  }
  exit(SITE_FAIL);
}

int main FUNCTION((argc, argv), int argc AND char *argv[])
{
  int invalid;
  char *tty;
  int i;
  char opieprompt[OPIE_CHALLENGE_MAX + 1];
  int af_pwok;
  int authsok = 0;
  char *pp;
  char buf[256];
  int opiepassed;

#ifndef DEBUG
  if (geteuid()) {
    fprintf(stderr, "This program requires super-user privileges.\n");
    exit(SITE_FAIL);
  }
#endif /* DEBUG */

  for (i = sysconf(_SC_OPEN_MAX); i > 2; i--)
    close(i);

  openlog("opieauth", LOG_ODELAY, LOG_AUTH);

  /* initialisation */
  host[0] = '\0';
  rusername[0] = '\0';
  opieprompt[0] = '\0';

  memset(&nouser, 0, sizeof(nouser));
  nouser.pw_uid = -1;
  nouser.pw_gid = -1;
  nouser.pw_passwd = "#nope";
  nouser.pw_name = nouser.pw_gecos = nouser.pw_dir = nouser.pw_shell = "";

  signal(SIGALRM, timedout);
  signal(SIGQUIT, SIG_IGN);
  signal(SIGINT, SIG_IGN);

#ifdef DEBUG
  syslog(LOG_DEBUG, "my args are: (argc=%d)", i = argc);
  while (--i)
    syslog(LOG_DEBUG, "%d: %s", i, argv[i]);
#endif /* DEBUG */

  /* Save the username */
  if (argc > 1) {
    strncpy(name, argv[1], sizeof(name));
    name[sizeof(name) - 1] = '\0';
    if (name[0] == '-') {
      fprintf(stderr, "User names can't start with '-'.\n");
      syslog(LOG_AUTH, "Attempt to use invalid username: %s.", name);
      exit(SITE_AGAIN);
    }
    argc--;
    argv++;
  } else {
    fprintf(stderr, "Missing username argument.\n");
    syslog(LOG_AUTH, "Missing username.");
    exit(SITE_FAIL);
  }

  /* Save the remotehost */
  if (argc > 1) {
    strncpy(host, argv[1], sizeof(host));
    host[sizeof(host) - 1] = '\0';
    if (host[0] == '-') {
      fprintf(stderr, "Host names can't start with '-'.\n");
      syslog(LOG_AUTH, "Attempt to use invalid host name: %s.", host);
      exit(SITE_FAIL);
    }
    argc--;
    argv++;
  }

  /* Save the remote username */
  if (argc > 1) {
    strncpy(rusername, argv[1], sizeof(rusername));
    rusername[sizeof(rusername) - 1] = '\0';
    if (rusername[0] == '-') {
      fprintf(stderr, "Remote user names can't start with '-'.\n");
      syslog(LOG_AUTH, "Attempt to use invalid remote username: %s.", rusername);
      exit(SITE_AGAIN);
    }
    argc--;
    argv++;
  }

  /* Check for extra args. */
  if (argc > 1) {
    fprintf(stderr, "Too many arguments.\n");
    syslog(LOG_AUTH, "Too many arguments.");
    exit(SITE_FAIL);
  }

#ifdef TIOCNXCL
  /* BSDism:  not sure how to rewrite for POSIX.  rja */
  ioctl(0, TIOCNXCL, 0);	/* set non-exclusive use of tty */
#endif

  tty = ttyname(0);

  if (tty == (char *) 0 || *tty == '\0')
    tty = "UNKNOWN";	/* was: "/dev/tty??" */

#if HAVE_SETVBUF && defined(_IONBF)
#if SETVBUF_REVERSED
  setvbuf(stdout, _IONBF, NULL, 0);
  setvbuf(stderr, _IONBF, NULL, 0);
#else /* SETVBUF_REVERSED */
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);
#endif /* SETVBUF_REVERSED */
#endif /* HAVE_SETVBUF && defined(_IONBF) */

#ifdef DEBUG
  syslog(LOG_DEBUG, "tty = %s", tty);
#endif /* DEBUG */

  invalid = TRUE;
  af_pwok = opieaccessfile(host);

  invalid = lookupuser();

#ifdef DEBUG
  syslog(LOG_DEBUG, "login name is +%s+, of length %d, [0] = %d", name, strlen(name), name[0]);
#endif /* DEBUG */

  if (feof(stdin))
    exit(SITE_FAIL);

  /* If this user does not have a password punt back to login. */
  if (! *thisuser.pw_passwd)
    exit(SITE_CONTINUE);

#ifdef DEBUG
  syslog(LOG_DEBUG, "login name is +%s+, of length %d, [0] = %d\n", name, strlen(name), name[0]);
#endif /* DEBUG */

  /* Attempt a one-time password challenge */
  i = opiechallenge(&opie, name, opieprompt);
  need_opieverify = TRUE;

  if ((i < 0) || (i > 1)) {
    syslog(LOG_ERR, "error: opiechallenge() returned %d, errno=%d!\n", i, errno);
    exit(SITE_FAIL);
  }
  printf("%s\n", opieprompt);
  authsok |= 1;

  if (!memcmp(&thisuser, &nouser, sizeof(thisuser)))
    if (host[0] && rusername[0])
      syslog(LOG_WARNING, "Invalid login attempt for %s on %s from %s@%s.",
	     name, tty, rusername, host);
    else if (host[0])
      syslog(LOG_WARNING, "Invalid login attempt for %s on %s from %s.",
	     name, tty, host);
    else
      syslog(LOG_WARNING, "Invalid login attempt for %s on %s.",
	     name, tty);

  if (af_pwok && opiealways(thisuser.pw_dir))
    authsok |= 2;

#if DEBUG
  syslog(LOG_DEBUG, "af_pwok = %d, authsok = %d", af_pwok, authsok);
#endif /* DEBUG */

  if (!authsok)
    syslog(LOG_ERR, "no authentication methods are available for %s!", name);

  /* Only timeout based on user input delay, not NIS holdups. */
  alarm(timeout);
#if NEW_PROMPTS
  if ((authsok & 1) || !authsok)
    printf("Response");
  if (((authsok & 3) == 3) || !authsok)
    printf(" or ");
  if ((authsok & 2) || !authsok)
    printf("Password");
  printf(": ");
  fflush(stdout);
  if (!opiereadpass(buf, sizeof(buf), !(authsok & 2)))
    invalid = TRUE;
#else /* NEW_PROMPTS */
  if (!(authsok & 2) && authsok)
    printf("(OTP response required)\n");
  printf("Password:");
  fflush(stdout);
  if (!opiereadpass(buf, sizeof(buf), 0))
    invalid = TRUE;
#endif /* NEW_PROMPTS */

  if (!buf[0] && (authsok & 1)) {
    authsok &= ~2;
    /* Null line entered, so display appropriate prompt & flush current
       data. */
#if NEW_PROMPTS
    printf("Response: ");
#else /* NEW_PROMPTS */
    printf(" (echo on)\nPassword:");
#endif /* NEW_PROMPTS */
    if (!opiereadpass(buf, sizeof(buf), 1))
      invalid = TRUE;
  }
  alarm(0);

  if (authsok & 1) {
    i = opiegetsequence(&opie);
    opiepassed = !opieverify(&opie, buf);
    need_opieverify = 0;
#ifdef DEBUG
    syslog(LOG_DEBUG, "opiepassed = %d", opiepassed);
#endif /* DEBUG */
  }

  if (!invalid) {
    if ((authsok & 1) && opiepassed) {
      if (i < 10) {
	printf("Warning: Re-initialize your OTP information");
	if (i < 5)
	  printf(" NOW!");
	printf("\n");
      }
    } else if (authsok & 2) {
      pp = crypt(buf, thisuser.pw_passwd);
      invalid = strcmp(pp, thisuser.pw_passwd);
    } else {
      invalid = TRUE;
    }
  }

  /* If invalid, then log failure attempt data to appropriate system
     logfiles and close the connection. */
  if (invalid) {
    printf("Login incorrect\n");
    if (rusername[0] && host[0])
      syslog(LOG_ERR, "LOGIN FAILURE ON %s FROM %.*s@%.*s, %.*s",
	     tty, HMAX, host, sizeof(rusername), rusername, sizeof(name), name);
    else if (host[0])
      syslog(LOG_ERR, "LOGIN FAILURE ON %s FROM %.*s, %.*s",
	     tty, HMAX, host, sizeof(name), name);
    else
      syslog(LOG_ERR, "LOGIN FAILURE ON %s, %.*s", 
	     tty, sizeof(name), name);
    exit(SITE_AGAIN);
  }

  signal(SIGALRM, SIG_DFL);
  signal(SIGQUIT, SIG_DFL);
  signal(SIGINT, SIG_DFL);
  signal(SIGTSTP, SIG_IGN);

  exit(SITE_OK);
}
