/* 
 * Filename:  skeysh.c
 *
 * An S/Key authentication shell
 *
 * The original authors:
 *
 *          Neil M. Haller <nmh@thumper.bellcore.com>
 *          Philip R. Karn <karn@chicago.qualcomm.com>
 *          John S. Walden <jsw@thumper.bellcore.com>
 *          Scott Chasin   <chasin@crimelab.com>
 *
 * Changes and additions by Michael Holst <mholst@math.ucsd.edu>
 *
 *          9-1-98:  Ported keysh from v1.1b to v2.2 s/key libraries
 *          10-3-98: Username support (to simulate logdaemon) added
 *          10-7-98: Correct shell support added
 */

#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <../libskey/skey.h>

char userbuf[16] = "USER=";
char homebuf[128] = "HOME=";
char shellbuf[128] = "SHELL=";
char pathbuf[128] = "PATH=:/usr/ucb:/bin:/usr/bin";
char *cleanenv[] = { userbuf, homebuf, shellbuf, pathbuf, 0, 0 };
char shell[128] = "/bin/bash";

char *my_getenv(char *ename);
void my_setenv (char *ename, char *eval, char *buf);
int skey_authenticate (char *username);
int skey_haskey (char *username);

extern char **environ;
struct passwd *pwd;

main (argc, argv)
  int argc;
  char *argv[];
{
  int stat, pid, len, i;
  char user[8];
 
  /* get username as securely as we can -- watch buffer overflows! */
  fprintf(stderr, "s/key Login: ");  /* login prompt */
  fgets(user,9,stdin);               /* get at most 9 chars, including CR */
  len=strlen(user);                  /* determine the length of string */
  if (len > 8) {                     /* sanity check on fgets/strlen... */
      fprintf(stderr, "skeysh: internal error.\n");
      exit(1);
  }

  /* okay, we will try authentication; isolate username (remove CR) */
  for (i=0; i<8; i++)
      if (user[i] == '\n')
          user[i]='\0';

  /* try to find the user in the password file */
  if ((pwd = getpwnam(user)) == NULL) {
      fprintf(stderr, "Unknown login: <%s>\n", user);
      exit(1);
  }
 
  /* try to find user in the skey database */
  stat = skey_haskey (user);
 
  /* user is not an skey user */
  if (stat == 1) {
     fprintf(stderr,"skeysh: no entry for user %s.\n", user);
     exit (-1);
  }

  /* had trouble opening the skey database */
  if (stat == -1) {
     fprintf(stderr, "skeysh: could not open key file.\n");
     exit (-1);
  }
 
  /* can the user be authenticated */
  if (skey_authenticate (user) == -1) {
      printf ("Invalid response.\n");
      exit (-1);
  } 

  /* reset the group id */
  if (setgid(pwd->pw_gid) < 0) {
      perror("skeysh: setgid");
      exit(3);
  }

  /* init groups */
  if (initgroups(user, pwd->pw_gid)) {
      fprintf(stderr, "skeysh: initgroups failed\n");
      exit(4);
  }

  /* set the userid */
  if (setuid(pwd->pw_uid) < 0) {
      perror("skeysh: setuid");
      exit(5);
  }

  /* determine the correct shell to use (do some safety checking...) */
  strncpy(shell, pwd->pw_shell, 128);
  if (  (strncmp("/bin/bash",shell,128))
     && (strncmp("/bin/csh",shell,128))
     && (strncmp("/bin/sh",shell,128)) ) {
      fprintf(stderr, "skeysh: bad shell listed in passwd file\n");
      exit(1);
  }
  fprintf(stderr, "skeysh: starting <%s> for you..", shell);
  fprintf(stderr, ".happy hacking. -- mholst@math.ucsd.edu\n");

  /* get the correct environment for the correct user */
  cleanenv[4] = my_getenv("TERM");
  environ = cleanenv;

  /* set some base shell variables */
  my_setenv("USER", pwd->pw_name, userbuf);
  my_setenv("SHELL", shell, shellbuf);
  my_setenv("HOME", pwd->pw_dir, homebuf);

  /* change to the home directory */
  if (chdir(pwd->pw_dir) < 0) {
      fprintf(stderr, "No directory\n");
      exit(6);
  }

  /* exec the shell */
  execv (shell, argv);

  /* should never get to this point... */
  fprintf(stderr, "No shell\n");
  exit(7);
}
 
void my_setenv (ename, eval, buf)
char *ename, *eval, *buf;
{
  register char *cp, *dp;
  register char **ep = environ;
 
  /*
   * this assumes an environment variable "ename" already exists
   */
   while (dp = *ep++) {
       for (cp = ename; *cp == *dp && *cp; cp++, dp++)
            continue;
       if (*cp == 0 && (*dp == '=' || *dp == 0)) {
            strcat(buf, eval);
            *--ep = buf;
            return;
       }
   }
}

char *my_getenv(ename)
char *ename;
{
  register char *cp, *dp;
  register char **ep = environ;
 
  while (dp = *ep++) {
         for (cp = ename; *cp == *dp && *cp; cp++, dp++)
              continue;
         if (*cp == 0 && (*dp == '=' || *dp == 0))
              return (*--ep);
  }
  return ((char *)0);
}

/*
 * skey_haskey ()
 *
 * Returns: 1 user doesnt exist, -1 fle error, 0 user exists.
 *
 */

skey_haskey (username)
  char *username;
{
  int i;
  struct skey skey;

  return (skeylookup (&skey, username));
}

/*
 * skey_authenticate ()
 *
 * Used when calling program will allow input of the user's
 * response to the challenge.
 *
 * Returns: 0 success, -1 failure
 *
 */
skey_authenticate (username)
  char *username;
{
  int i;
  char pbuf [256], skeyprompt [50];
  struct skey skey;

  /* Attempt a S/Key challenge */
  i = skeychallenge (&skey, username, skeyprompt);

  if (i == -2)
    return 0;

  printf ("[%s]\n", skeyprompt);
  fflush (stdout);

  printf ("Response: ");
  readpass (pbuf, sizeof (pbuf));
  rip (pbuf);

  /* Is it a valid response? */
  if (i == 0 && skeyverify (&skey, pbuf) == 0)
  {
    if (skey.n < 5)
    {
      printf ("\nWarning! Key initialization needed soon.  ");
      printf ("(%d logins left)\n", skey.n);
    }
    return 0;
  }
  return -1;
}


