#ifndef AFS
int not_defined_krb_tkt2afs_token;
#else /* AFS */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <setjmp.h>
#include <errno.h>

#include <krb.h>

#if defined(HAS_AFS)

#include <afs/param.h>
#include <afs/afs.h>
#include <netinet/in.h>
/*
 * To get the right definition of _VICEIOCTL one needs to
 * use some trix if using an ANSI C compiler.
 */
#if defined(__STDC__) && !defined(AFS_HPUX_ENV)
#define AFS_HPUX_ENV 1
#include <afs/vice.h>
#undef  AFS_HPUX_ENV
#endif /* __STDC__ */
#include <afs/venus.h>

#else /* !defined(HAS_AFS) */

/*
 * This section is for machines using single entry point AFS syscalls!
 */
#if defined(hpux)
#define AFS_SYSCALL 49
#endif
#if defined(_AIX)
#define AFS_SYSCALL 38
#endif
#if defined(SunOS5)
#define AFS_SYSCALL 105
#endif
#ifndef AFS_SYSCALL
/* This is really a good guess */
#define AFS_SYSCALL 31
#endif

/*
 * This section is for machines using multiple entry point AFS syscalls!
 */
#if defined(sgi)
#define AFS_PIOCTL      64+1000
#define AFS_SETPAG      65+1000
#endif

#define AFSCALL_PIOCTL 20
#define AFSCALL_SETPAG 21

#if (defined(sun) && (defined(__STDC__) || defined(SunOS5))) || defined(ultrix) || defined(NeXT) || defined(sgi)
#define _VICEIOCTL(id)  ((unsigned int ) _IOW('V', id, struct ViceIoctl))
#else
#define _VICEIOCTL(id)  ((unsigned int ) _IOW(V, id, struct ViceIoctl))
#endif

#define VIOCSETTOK _VICEIOCTL(3)
#define VIOCUNLOG  _VICEIOCTL(9)

struct ViceIoctl {
  caddr_t in, out;
  short in_size;
  short out_size;
};

#endif /* !defined(HAS_AFS) */

struct ClearToken {
  long AuthHandle;
  char HandShakeKey[8];
  long ViceId;
  long BeginTimestamp;
  long EndTimestamp;
};

#define AUTH_SUPERUSER "afs"

/*
 * Here only ASCII characters are relevant.
 */

#define IsAsciiUpper(c) ('A' <= (c) && (c) <= 'Z')

#define ToAsciiLower(c) ((c) - 'A' + 'a')

static void
folddown(a, b)
     char *a, *b;
{
  for (; *b; a++, b++)
    if (IsAsciiUpper(*b))
      *a = ToAsciiLower(*b);
    else
      *a = *b;
  *a = '\0';
}

#ifdef NEED_LIFETIME
int
krb_time_to_life(start, end)
     unsigned long start;
     unsigned long end;
{
  return ((end - start) + 5*60 - 1)/(5*60);
}

unsigned long 
krb_life_to_time(start, life)
     unsigned long start;
     int life;
{
  return start + ((unsigned char) life)*5*60;
}
#endif /* NEED_LIFETIME */

int
krb_afs_klog(realm)
     char *realm;
{
  int k_errno;
  CREDENTIALS c;
  KTEXT_ST ticket;
  char username[256];

  if (!krb_has_afs())
    return KSUCCESS;

  k_errno = krb_get_cred(AUTH_SUPERUSER, "", realm, &c);
  if (k_errno != KSUCCESS)
    {
      k_errno = krb_mk_req(&ticket, AUTH_SUPERUSER, "", realm, 0);
      if (k_errno == KSUCCESS)
	k_errno = krb_get_cred(AUTH_SUPERUSER, "", realm, &c);
    }

  if (k_errno == KSUCCESS)
    {
      char cell[256];
      struct ViceIoctl parms;
      struct ClearToken ct;
      long sizeof_x;
      char buf[2048], *t;

      folddown(cell, realm);

      /*
       * Build a struct ClearToken
       */
      ct.AuthHandle = c.kvno;
      bcopy((char *)c.session, ct.HandShakeKey, sizeof(c.session));
      ct.ViceId = getuid();	/* is this always valid? */
      ct.BeginTimestamp = 1 + c.issue_date;
      ct.EndTimestamp = krb_life_to_time(c.issue_date, c.lifetime);

      t = buf;
      /*
       * length of secret token followed by secret token
       */
      sizeof_x = c.ticket_st.length;
      bcopy((char *)&sizeof_x, t, sizeof(sizeof_x));
      t += sizeof(sizeof_x);
      bcopy((char *)c.ticket_st.dat, t, sizeof_x);
      t += sizeof_x;
      /*
       * length of clear token followed by clear token
       */
      sizeof_x = sizeof(ct);
      bcopy((char *)&sizeof_x, t, sizeof(sizeof_x));
      t += sizeof(sizeof_x);
      bcopy((char *)&ct, t, sizeof_x);
      t += sizeof_x;

      /*
       * do *not* mark as primary cell
       */
      sizeof_x = 0;
      bcopy((char *)&sizeof_x, t, sizeof(sizeof_x));
      t += sizeof(sizeof_x);
      /*
       * follow with cell name
       */
      sizeof_x = strlen(cell) + 1;
      bcopy(cell, t, sizeof_x);
      t += sizeof_x;

      /*
       * Build argument block
       */
      parms.in = buf;
      parms.in_size = t - buf;
      parms.out = 0;
      parms.out_size = 0;
      (void) krb_afs_pioctl(0, VIOCSETTOK, &parms, 0);
    }
  return k_errno;
}

#define NO_ENTRY_POINT		0
#define SINGLE_ENTRY_POINT	1
#define MULTIPLE_ENTRY_POINT	2
#define UNKNOWN_ENTRY_POINT	3
static int afs_entry_point = UNKNOWN_ENTRY_POINT;

int
krb_afs_pioctl(a_path, o_opcode, a_paramsP, a_followSymlinks)
     char *a_path;
     int o_opcode;
     struct ViceIoctl *a_paramsP;
     int a_followSymlinks;
{
  if (afs_entry_point == SINGLE_ENTRY_POINT)
    return syscall(AFS_SYSCALL, AFSCALL_PIOCTL,
		   a_path, o_opcode, a_paramsP, a_followSymlinks);
  else
#ifdef AFS_PIOCTL
    return syscall(AFS_PIOCTL,
		   a_path, o_opcode, a_paramsP, a_followSymlinks);
#else
    return -1;
#endif
}

krb_afs_unlog()
{
  struct ViceIoctl parms;
  bzero((char *)&parms, sizeof(parms));
  return krb_afs_pioctl(0, VIOCUNLOG, &parms, 0);
}

krb_afs_setpag()
{
  if (afs_entry_point == SINGLE_ENTRY_POINT)
    return syscall(AFS_SYSCALL, AFSCALL_SETPAG);
  else
#ifdef AFS_SETPAG
    return syscall(AFS_SETPAG);
#else
    return -1;
#endif
}

static jmp_buf catch_SIGSYS;

static void
SIGSYS_handler()
{
  errno = 0;
  longjmp(catch_SIGSYS, 1);
}

int
krb_has_afs()
{
  int saved_errno;
  void (*saved_func)();
  struct ViceIoctl parms;
  
  /*
   * Already checked presence of AFS syscalls?
   */
  if (afs_entry_point != UNKNOWN_ENTRY_POINT)
    return afs_entry_point != NO_ENTRY_POINT;

  /*
   * Probe kernel for AFS specific syscalls,
   * they (currently) come in two flavors.
   * If the syscall is absent we recive a SIGSYS.
   */
  afs_entry_point = NO_ENTRY_POINT;
  bzero(&parms, sizeof(parms));
  
  saved_errno = errno;
  saved_func = signal(SIGSYS, SIGSYS_handler);
  if (setjmp(catch_SIGSYS) == 0)
    {
      syscall(AFS_SYSCALL, AFSCALL_PIOCTL,
	      0, VIOCSETTOK, &parms, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
      if (errno == EINVAL)
	{
	  afs_entry_point = SINGLE_ENTRY_POINT;
	  goto done;
	}
    }
#ifdef AFS_PIOCTL
  if (setjmp(catch_SIGSYS) == 0)
    {
      syscall(AFS_PIOCTL,
	      0, VIOCSETTOK, &parms, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
      if (errno == EINVAL)
	{
	  afs_entry_point = MULTIPLE_ENTRY_POINT;
	  goto done;
	}
    }
#endif

 done:
  (void) signal(SIGSYS, saved_func);
  errno = saved_errno;
#if 0
 printf("afs_entry_point == %d\n", afs_entry_point);
#endif
  return afs_entry_point != NO_ENTRY_POINT;
}

/*
 * For backwards compatibility
 */
int
krb_tkt2afs_token(aname, inst, realm, lifetime)
     char *aname, *inst, *realm;
     int lifetime;
{
  return krb_afs_klog(realm);
}

int
has_afs_syscalls()
{
  return krb_has_afs();
}

#endif /* AFS */
