
#import "KrbLoginAuthenticator.h"
#include <krb.h>
#include <hesiod.h>
#include <sys/types.h>
#include <sys/dir.h>
#include <netdb.h>

void setenv(char *, char *, int);
extern char *envinit[];

#define ROOT 0
#define LOGIN_TKT_DEFAULT_LIFETIME 96 /* 8 hours */
#define PASSWORD_LEN 14
#define TEMP_DIR_PERM 0710
#define MAXENVIRON 10

/* homedir status */
#define HD_LOCAL 0
#define HD_ATTACHED 1
#define HD_TEMP 2

#ifndef NOLOGIN
#define NOLOGIN "/etc/nologin"
#endif
#ifndef NOCREATE
#define NOCREATE "/etc/nocreate"
#endif
#ifndef NOATTACH
#define NOATTACH "/etc/noattach"
#endif
#define NOWLOCAL "/etc/nowarnlocal"
#define ATTACH "/bin/athena/attach"
#define DETACH "/bin/athena/detach"
#define MOTD "/etc/motd"
#define UTMP "/etc/utmp"
#define WTMP "/usr/adm/wtmp"
#define QUOTA "/usr/ucb/quota"
#define TMPDOTFILES "/usr/athena/lib/prototype_tmpuser/."
#define UNLOG "/usr/afs/bin/unlog"
#define KDESTROY "/usr/athena/bin/kdestroy"
#define DEFAULTPATH "/srvd/patch:/usr/athena:/bin/athena:/usr/bin/X:/usr/new:/usr/new/mh/bin:/usr/ucb:/bin:/usr/bin:/usr/ibm:/usr/andrew/bin:."

#define file_exists(f) (access((f), F_OK) == 0)
extern char *krb_get_phost(); /* should be in <krb.h> */

int attach_pid;
extern int errno, quota_pid;
int homedir_status = HD_LOCAL;
int added_to_passwd = FALSE;
int attach_state;

/* Function Name: IsRemoteDir
 * Description: Stolen form athena's version of /bin/login
 *              returns true of this is an NFS directory.
 * Arguments: dname - name of the directory.
 * Returns: true or false to the question (is remote dir).
 *
 * The following lines rely on the behavior of Sun's NFS (present in
 * 3.0 and 3.2) which causes a read on an NFS directory (actually any
 * non-reg file) to return -1, and AFS which also returns a -1 on
 * read (although with a different errno).  This is a fast, cheap
 * way to discover whether a user's homedir is a remote filesystem.
 * Naturally, if the NFS and/or AFS semantics change, this must also change.
 */

BOOL IsRemoteDir(char *dir)
{
  int f;
  char c;
   
  if ((f = open(dir, O_RDONLY, 0)) < 0)
    return(FALSE);
   
  if (read(f, &c, 1) < 0) {
    close(f);
    return(TRUE);
  }
   
  close(f);
  return(FALSE);
}


/* Function Name: homedirOK
 * Description: checks to see if our homedir is okay, i.e. exists and 
 *      contains at least 1 file
 * Arguments: dir - the directory to check.
 * Returns: TRUE if the homedir is okay.
 */

int homedirOK(char *dir)
{
  DIR *dp;
  struct direct *temp;
  int count;
   
  if ((dp = opendir(dir)) == NULL)
    return(FALSE);
   
  /* Make sure that there is something here besides . and .. */
  for (count = 0; count < 3 ; count++)
    temp = readdir(dp);
   
  closedir(dp);
  return(temp != NULL);
}

cleanup(pwd)
     struct passwd *pwd;
{

#ifdef NOTDEF  
  /* must also detach homedir, clean passwd file */
  dest_tkt();
  if (pwd && homedir_status == HD_ATTACHED) {
    attach_state = -1;
    switch (attach_pid = fork()) {
    case -1:
      NXRunAlertPanel(NULL, "Unable to detach your home directory (could not fork to create attach process).",
		      NULL, NULL, NULL);
      break;
    case 0:
      if (setuid(pwd->pw_uid) != 0) {
	NXRunAlertPanel(NULL,
			"Could not execute detach command as user %s,\n",
			NULL, NULL, NULL, pwd->pw_name);
      }
      execlp("fsid", "fsid", "-unmap", "-filsys", pwd->pw_name, NULL);
      _exit(-1);
    default:
      while (attach_state == -1)
	sigpause(0);
    }
  }
#endif
  
}


@implementation KrbLoginAuthenticator

  static char errbuf[1024];

- (struct passwd *)lookupPasswd
{
  /* Reset state variables */
  local_passwd = FALSE;		/* user is in local passwd file */
  
  /* check to make sure a username was entered */
  if (!strcmp(user, "")) {
    NXRunAlertPanel(NULL, "No username entered.  Please enter a username and password to try again.",
		    NULL, NULL, NULL);
    return (struct passwd *) NULL;
  }

  /* check NetInfo or local password file */
  if ((pwd = getpwnam(user)) != NULL) {
    local_passwd = TRUE;
  } else {
    /* check Hesiod */
    
    pwd = hes_getpwnam(user);
  
    if ((pwd == NULL) || (pwd->pw_dir[0] == 0)) {
      cleanup(NULL);
      if (hes_error() == HES_ER_NOTFOUND) {
	NXRunAlertPanel(NULL, "Unknown user name entered (no hesiod information for \"%s\")",
			NULL, NULL, NULL, user);
	return (struct passwd *) NULL;
      } else {
	NXRunAlertPanel(NULL, "Unable to find account information due to network failure.  Try another workstation or try again later.",
			NULL, NULL, NULL);
	return (struct passwd *) NULL;
      }
    }
    if (strcmp(pwd->pw_name, user)) {
      NXRunAlertPanel(NULL, "Unable to find account information (incorrect hesiod name found).",
		      NULL, NULL, NULL);
      return (struct passwd *) NULL;
    }
    if (getpwuid(pwd->pw_uid)) {
      NXRunAlertPanel(NULL, "This account conflicts with a locally defined account... aborting.",
		      NULL, NULL, NULL);
      return (struct passwd *) NULL;
    }
  } 

  return pwd;
}

- (int)validateUser
{
  int local_ok = FALSE;
  long salt;
  char saltc[2], c;
  char encrypt[PASSWORD_LEN+1];
  int i;
  char tkt_file[128];
  
  /* Sanity check */
  if (!pwd)
    return 0;

  /* Reset state variables */
  nocreate = FALSE;		/* not allowed to modify passwd file */
  nologin = FALSE;		/* logins disabled */

  nocreate = file_exists(NOCREATE);
  nologin = file_exists(NOLOGIN);

  if (local_passwd) {
    if (strcmp(crypt(password, pwd->pw_passwd), pwd->pw_passwd)) {
      if (pwd->pw_uid == ROOT) {
	NXRunAlertPanel(NULL, "Incorrect root password", NULL, NULL, NULL);
	return 0;
      }
    } else
      local_ok = TRUE;
  }
    
  if (!local_ok && nocreate) {
    NXRunAlertPanel(NULL, "You are not allowed to log into this workstation.  Contact the workstation's administrator or a consultant for further information.  (User \"%s\" is not in the password file and No_Create is set.)",
		    NULL, NULL, NULL, user);
    return 0;
  }
  
  if (pwd->pw_uid != ROOT) {
    /* Gross hack to initialize environment */
    environ = envinit;

    sprintf(tkt_file, "/tmp/tkt%d", pwd->pw_uid);
    setenv("KRBTKFILE", tkt_file, 1);
  
    /* set real uid/gid for kerberos library */
    if (setruid(pwd->pw_uid) != 0) {
      NXRunAlertPanel(NULL, "Couldn't set ruid, errno = %d",
		      NULL, NULL, NULL, errno);
    }
    if (getuid() != pwd->pw_uid) {
      NxRunAlertPanel(NULL, "Failed to change uids, uid = %d\n",
		      NULL, NULL, NULL, getuid());
    }
    setrgid(pwd->pw_gid);
    
    if (![self getKerberosTickets]) {
      if (!local_ok) {
	cleanup(NULL);
	return 0;
      } else {
	if (pwd->pw_uid != ROOT) {
	  if (NXRunAlertPanel(NULL, "Unable to get full authentication, you will have local access only during this login session (failed to get kerberos tickets).  Continue anyway?",
			      "Continue", "Abort", NULL) != NX_ALERTDEFAULT) {
	    cleanup(NULL);
	    return 0;
	  }
	}
      }
    }
  
    /* save encrypted password to put in local password file */
    salt = 9 * getpid();
    saltc[0] = salt & 077;
    saltc[1] = (salt>>6) & 077;
    for (i=0;i<2;i++) {
      c = saltc[i] + '.';
      if (c > '9')
	c += 7;
      if (c > 'Z')
	c += 6;
      saltc[i] = c;
    }
    strcpy(encrypt, crypt(password, saltc));	
  }  

  /* don't need the password anymore */
  bzero(password, strlen(password));

  if (!local_passwd)
    pwd->pw_passwd = encrypt;

  /*
   * if NOLOGINs and we're not root, display the contents of the
   * nologin file
   */
  if (nologin && pwd->pw_uid != ROOT) {
    int f, count;
    char *p;
    
    strcpy(errbuf, "Logins are currently disabled on this workstation.  ");
    p = &errbuf[strlen(errbuf)];
    f = open(NOLOGIN, O_RDONLY, 0);
    if (f > 0) {
      count = read(f, p, sizeof(errbuf) - strlen(errbuf) - 1);
      close(f);
      p[count] = 0;
    }
    NXRunAlertPanel(NULL, errbuf, NULL, NULL, NULL);
    cleanup(NULL);
    return 0;
  }

  if (![self attachHomeDirectory]) {
    cleanup(pwd);
    return 0;
  }
    
  /* put in password file if necessary */
  
  /* retset real uid/gid */
  setruid(0);
  setrgid(0);

  return 1;
}

- (int)getKerberosTickets
{
  char inst[INST_SZ], realm[REALM_SZ];
  char hostname[MAXHOSTNAMELEN], phost[INST_SZ];
  char key[8], *rcmd;
  char *errstr;
  int error;
  struct hostent *hp;
  KTEXT_ST ticket;
  AUTH_DAT authdata;
  unsigned long addr;
    
  rcmd = "rcmd";
    
  /* inst has to be a buffer instead of the constant "" because
   * krb_get_pw_in_tkt() will write a zero at inst[INST_SZ] to
   * truncate it.
   */
  inst[0] = 0;
  dest_tkt();
    
  if (krb_get_lrealm(realm, 1) != KSUCCESS)
    strcpy(realm, KRB_REALM);
    
  error = krb_get_pw_in_tkt(user, inst, realm, "krbtgt", realm,
			    LOGIN_TKT_DEFAULT_LIFETIME, password);
  errstr = NULL;
  switch (error) {
  case KSUCCESS:
    break;
  case INTK_BADPW:
    errstr = "(Kerberos)Incorrect password entered.";
    break;
  case KDC_PR_UNKNOWN:
    errstr = "(Kerberos)Unknown username entered.";
    break;
  default:
    sprintf(errbuf, "(Kerberos)Unable to authenticate you, kerberos failure %d: %s.  Try again here or on another workstation.", error, krb_err_txt[error]);
    errstr = errbuf;
    break;
  }

  if (errstr && (pwd->pw_uid != ROOT)) {
    NXRunAlertPanel(NULL, errstr, NULL, NULL, NULL);
    return 0;
  }
  
  if (gethostname(hostname, sizeof(hostname)) == -1) {
    NXRunAlertPanel(NULL, "(Kerberos)cannot retrieve local hostname",
		    NULL, NULL, NULL);
    return 0;
  }
  strncpy (phost, krb_get_phost (hostname), sizeof (phost));
  phost[sizeof(phost)-1] = 0;
    
  /* without srvtab, cannot verify tickets */
  if (read_service_key(rcmd, phost, realm, 0, KEYFILE, key) != KFAILURE) {
    hp = gethostbyname (hostname);
    if (!hp) {
      NXRunAlertPanel(NULL, "(Kerberos)cannot get address for host %s\n",
		      NULL, NULL, NULL, hostname);
      return 0;
    }
    bcopy ((char *)hp->h_addr, (char *) &addr, sizeof (addr));
      
    error = krb_mk_req(&ticket, rcmd, phost, realm, 0);
    if (error == KDC_PR_UNKNOWN)
      return 0;
    if (error != KSUCCESS) {
      NXRunAlertPanel(NULL, "(kerberos)Unable to authenticate you, kerberos failure %d: %s",
		      NULL, NULL, NULL, error, krb_err_txt[error]);
      return 0;
    }
    error = krb_rd_req(&ticket, rcmd, phost, addr, &authdata, "");
    if (error != KSUCCESS) {
      bzero(&ticket, sizeof(ticket));
      NXRunAlertPanel(NULL, "(Kerberos)Unable to authenticate you, kerberos failure %d: %s",
		      NULL, NULL, NULL, error, krb_err_txt[error]);
      return 0;
    }
    bzero(&ticket, sizeof(ticket));
    bzero(&authdata, sizeof(authdata));
    return 1;
  }
}

- (int)attachHomeDirectory
{
  struct stat stb;
  int i;
  
  /* Delete empty directory if it exists.  We just try to rmdir the 
   * directory, and if it's not empty that will fail.
   */
  rmdir(pwd->pw_dir);
    
  /* If a good local homedir exists, use it */
  if (file_exists(pwd->pw_dir) && !IsRemoteDir(pwd->pw_dir) && 
      homedirOK(pwd->pw_dir))
    return 1;
    
  /* Using homedir already there that may or may not be good. */
  if (file_exists(NOATTACH) && file_exists(pwd->pw_dir) &&
      homedirOK(pwd->pw_dir)) {
    return 1;
  }
    
  if (file_exists(NOATTACH)) {
    NXRunAlertPanel(NULL, "This workstation is configured not to create local home directories.  Please contact the system administrator for this machine or a consultant for further information.",
		    NULL, NULL, NULL);
    return 0;
  }
    
  /* attempt attach now */
  attach_state = -1;
  switch (attach_pid = fork()) {
  case -1:
    NXRunAlertPanel(NULL, "(Kerberos)Unable to attach your home directory (could not fork to create attach process).  Try another workstation.",
		    NULL, NULL, NULL);
    return 0;
  case 0:
    if (setuid(pwd->pw_uid) != 0) {
      NXRunAlertPanel(NULL, "(Kerberos)Could not execute attach command as user %s, filesystem mappings may be incorrect.",
		      NULL, NULL, NULL, pwd->pw_name);
    }
    /* don't do zephyr here since user doesn't have zwgc started anyway */
    execl(ATTACH, ATTACH, "-quiet", "-nozephyr", pwd->pw_name, NULL);
    _exit(-1);
  default:
    break;
  }
  while (attach_state == -1) {
    sigpause(0);
  }
  if (attach_state != 0 || !file_exists(pwd->pw_dir)) {
    NXRunAlertPanel(NULL, "(Kerberos)error attaching homedir",
		    NULL, NULL, NULL);
    return 0;
  } else
    homedir_status = HD_ATTACHED;
  return 1;
}

- userDidLogout
{
  [super userDidLogout];
  attach_state = -1;
  switch (attach_pid = fork()) {
  case -1:
    /* error */
    return;
    break;
  case 0:
    setruid(pwd->pw_uid);
    setrgid(pwd->pw_gid);
    execl(DETACH, DETACH, "-quiet", "-zephyr","-a", user, NULL);
    break;
  default:
    break;
  }
  switch (attach_pid = fork()) {
  case -1:
    /* error */
    return;
    break;
  case 0:
    setruid(pwd->pw_uid);
    setrgid(pwd->pw_gid);
    execl(UNLOG,UNLOG,NULL);
    break;
  default:
    break;
  }
  switch (attach_pid = fork()) {
  case -1:
    /* error */
    return;
    break;
  case 0:
    setruid(pwd->pw_uid);
    setrgid(pwd->pw_gid);
    execl(KDESTROY,KDESTROY,NULL);
    break;
  default:
    break;
  }
  krb_set_tkt_string("");
}

@end
