#ifdef ATHENA
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <hesiod.h>
#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <AL/AL.h>
#include <krb.h>
#include "athena_ftpd.h"

#define LOGIN_TKT_DEFAULT_LIFETIME DEFAULT_TKT_LIFE /* from krb.h */
#define file_exists(f) (access((f), F_OK) == 0)

#ifndef TRUE
#define FALSE 0
#define TRUE (!FALSE)
#endif

#define PASSWORD_LEN 14

#ifndef NOLOGIN
#define NOLOGIN "/etc/nologin"
#endif

#define ATTACH "/bin/athena/attach"
#define DETACH "/bin/athena/detach"
#define FSID "/bin/athena/fsid"
#define UNLOG "/bin/athena/unlog"

extern FILE *ftpd_popen();

int athena_login = LOGIN_NONE;
static ALsessionStruct session;
static int local_passwd;
static char att_errbuf[200];

struct passwd *athena_setuser(user)
    char *user;
{
    static struct passwd save = { NULL };
    long code;

    code = ALsetUser(&session, user, ALisRemoteSession);
    if (ALisError(code))
	return (NULL);
    if (save.pw_name) {
	free(save.pw_name);
	free(save.pw_passwd);
	free(save.pw_gecos);
	free(save.pw_dir);
	free(save.pw_shell);
    }
    save.pw_name = sgetsave(ALpw_name(&session));
    save.pw_passwd = sgetsave(ALpw_passwd(&session));
#if defined(BSD) || defined(ultrix)
    save.pw_quota = 0;
    save.pw_comment = "";
#endif
    save.pw_gecos = sgetsave(ALpw_gecos(&session));
    save.pw_dir = sgetsave(ALpw_dir(&session));
    save.pw_shell = sgetsave(ALpw_shell(&session));
    return (&save);
}

int get_tickets(username, password)
char *username;
char *password;
{
    char inst[INST_SZ], realm[REALM_SZ];
    int error;

    /* 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(username, inst, realm, "krbtgt", realm,
			      LOGIN_TKT_DEFAULT_LIFETIME, password);

    return error;
}

char *athena_authenticate(user, passwd)
     char *user, *passwd;
{
  int local_ok = 0;
  struct passwd *pwd;
  char tkt_file[128];
  long salt;
  char saltc[2], c;
  char encrypt[PASSWORD_LEN+1];
  int child, error, i;
  char *errstring = NULL;
  static char errbuf[1024];
  int status;
  struct sigaction act;
  athena_login = LOGIN_NONE;

  if (ALisTrue(&session, ALdidGetHesiodPasswd))
    {
      /* 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(passwd, saltc));	

      ALpw_passwd(&session) = encrypt;
    }

  if (!ALisTrue(&session, ALdidGetHesiodPasswd) && 
      !strcmp(crypt(passwd, ALpw_passwd(&session), ALpw_passwd(&session))))
    local_ok = 1;

  sprintf(tkt_file, "/tmp/tkt_%s.%d", pwd->pw_name, getpid());
  setenv("KRBTKFILE", tkt_file, 1);
  /* we set the ticket file here because a previous dest_tkt() might
     have cached the wrong ticket file. */
  krb_set_tkt_string(tkt_file);

  if (!(child = fork()))
    {
      /* set real uid/gid for kerberos library */
      setuid(pwd->pw_uid);
      setgid(pwd->pw_gid);
      error = get_tickets(user, passwd);
      memset(passwd, 0, strlen(passwd));
      _exit(error);
    }

  if (child == -1)
    {
      sprintf(errbuf, "Fork to get tickets failed with error %d", errno);
      if (local_ok)
	athena_login = LOGIN_LOCAL;
        memset(passwd, 0, strlen(passwd));
        return errbuf;
    }

#if defined(_IBMR2) || defined(SOLARIS)
  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;
  act.sa_handler= (void (*)()) SIG_DFL;
  sigaction(SIGCHLD, &act, NULL);
  waitpid(child, &status, 0);
  act.sa_handler= (void (*)()) SIG_IGN;
  sigaction(SIGCHLD, &act, NULL);
#else
  waitpid(child, &status, 0);
#endif
  memset(passwd, 0, strlen(passwd));

  if (!WIFEXITED(status))
    errstring = "ticket child failed";
  else
    { /* this is probably screwed up XXX */
      error = WEXITSTATUS(status);      

      switch (error) {
      case KSUCCESS:
#ifdef SETPAG
	setpag();
#endif
	break;
      case INTK_BADPW:
	errstring =  "Incorrect Kerberos password entered.";
	break;
      case KDC_PR_UNKNOWN:
	errstring = "Unknown Kerberos username entered.";
	break;
      default:
	sprintf(errbuf, "Authentication failed: %d: %s.",
		error, krb_err_txt[error]);
	errstring = errbuf;
	break;
      }
    }

  if (errstring != NULL && !local_ok)
    return errstring;

  if (errstring != NULL && local_ok)
    {
      athena_login = LOGIN_LOCAL;
      return errstring;
    }

  ALflagSet(&session, ALhaveAuthentication);

  athena_login = LOGIN_KERBEROS;
  code = ALaddPasswdEntry(&session);
  /* Return warning or error. */
}

char *athena_attach(pw, dir, auth)
     struct passwd *pw;
     char *dir;
     int auth;
{
  int attach_error, child;
  int v, out, err;
  int status;
  struct sigaction act;

  if (!(child = fork()))
    {
      setuid(pw->pw_uid);
      setgid(pw->pw_gid);

      out = fileno(stdout);
      err = fileno(stderr);

      v = open("/dev/null", O_WRONLY);
      dup2(v, out);
      dup2(v, err);
      close(v);

      execl(ATTACH, "attach", "-nozephyr", auth ? "-y" : "-n", dir, 0);
      _exit(255); /* Attach isn't supposed to return 255 */
    }

  if (child == -1)
    {
      sprintf(att_errbuf, "Fork for attach failed with error %d", errno);
      return att_errbuf;
    }

#ifdef SOLARIS
  sigemptyset(&act.sa_mask);
  act.sa_flags = 0;
  act.sa_handler= (void (*)()) SIG_DFL;
  sigaction(SIGCHLD, &act, NULL);
  waitpid(child, &status, 0);
  act.sa_handler= (void (*)()) SIG_IGN;
  sigaction(SIGCHLD, &act, NULL);
#else
  waitpid(child, &status, 0);
#endif

  if (!WIFEXITED(status))
    {
      sprintf(att_errbuf, "Bad exit on attach child");
      return att_errbuf;
    }
  else
    { /* this is probably screwed up XXX */
      attach_error = WEXITSTATUS(status);      

      if (attach_error)
	{
	  switch(attach_error)
	    {
	    case 255:
	      sprintf(att_errbuf, "Couldn't exec attach");
	      break;
	    case 11:
	      sprintf(att_errbuf, "Server not responding");
	      break;
	    case 12:
	      sprintf(att_errbuf, "Authentication failure");
	      break;
	    case 20:
	      sprintf(att_errbuf, "Filesystem does not exist");
	      break;
	    case 24:
	      sprintf(att_errbuf, "You are not allowed to attach this filesystem.");
	      break;
	    case 25:
	      sprintf(att_errbuf, "You are not allowed to attach a filesystem here.");
	      break;
	    default:
	      sprintf(att_errbuf, "Could not attach directory: attach returned error %d", attach_error);
	      break;
	    }
	  return att_errbuf;
	}
    }
  return NULL;
}

char *athena_attachhomedir(pw, auth)
     struct passwd *pw;
     int auth;
{
  /* Delete empty directory if it exists.  We just try to rmdir the 
   * directory, and if it's not empty that will fail.
   */
  rmdir(pw->pw_dir);

  /* If a good local homedir exists, use it */
  if (file_exists(pw->pw_dir) && !IsRemoteDir(pw->pw_dir) &&
      homedirOK(pw->pw_dir))
    return(NULL);

  /* Using homedir already there that may or may not be good. */
  if (file_exists(NOATTACH) && file_exists(pw->pw_dir) &&
      homedirOK(pw->pw_dir)) {
    return(NULL);
  }

  if (file_exists(NOATTACH))
    {
      sprintf(att_errbuf, "This workstation is configured not to create local home directories.");
      return att_errbuf;
    }

  return athena_attach(pw, pw->pw_name, auth);
}

/*
 * Should be called when root
 */
athena_logout(pw)
     struct passwd *pw;
{
  FILE *att, *unlog;
  int detach_error, unlog_error, pid;
  char str_uid[10];
  if (athena_login != LOGIN_NONE)
    {
      /* destroy kerberos tickets */
      if (athena_login == LOGIN_KERBEROS)
	{
	  dest_tkt();
#ifdef SETPAG
	  ktc_ForgetAllTokens();
#endif
	  if ((pid = fork()) == 0)
	    {
	      execl(FSID, FSID, "-q", "-u", "-f", pw->pw_name, 0);
	      exit(1);
	    }
	}
      athena_login = LOGIN_NONE;
#ifdef SOLARIS
      /* lock before */
      if (lckpwdf()) return;

      remove_from_passwd(pw);
      remove_from_shadow(pw);
#endif
#ifdef oldcode
      if (!user_logged_in(pw->pw_name))
	{
	  chdir("/");
	  sprintf(str_uid, "#%d", pw->pw_uid);

	  if ((pid = fork()) == 0) /* we just don't care about failure... */
	    {
	      execl(DETACH, DETACH, "-quiet", "-user", str_uid, "-a", 0);
	      exit(1);
	    }

	  if (pid != -1)
	    if ((pid = fork()) == 0)
	      {
		execl(UNLOG, UNLOG, 0);
		exit(1);
	      }
	}
#endif
    }
}

