#include "config.h"
#include "protos.h"

RCSID("$Header: /afs/nada.kth.se/src/packages/kth-krb/SourceRepository/krb4/lib/kafs/afssys.c,v 1.10 1995/10/24 14:03:21 bg Exp $");

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif

#include <signal.h>
#include <setjmp.h>
#include <errno.h>

#include <krb.h>
#include <kafs.h>

#include "afssysdefs.h"

#define AUTH_SUPERUSER "afs"

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

#define IsAsciiLower(c) ('a' <= (c) && (c) <= 'z')

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

static void
foldup(a, b)
     char *a, *b;
{
  for (; *b; a++, b++)
    if (IsAsciiLower(*b))
      *a = ToAsciiUpper(*b);
    else
      *a = *b;
  *a = '\0';
}

#define _PATH_THISCELL "/usr/vice/etc/ThisCell"

static char *
k_cell()
{
    static char cell[64];

    FILE *f = fopen(_PATH_THISCELL, "r");
    if (f == 0)
	return 0;
    fscanf(f, "%s\n", cell);
    fclose(f);
    if (cell[0] != 0)
	return cell;
    else
	return 0;
}

static int
get_cred(char *princ, char *inst, char *krealm, CREDENTIALS *c, KTEXT_ST *tkt)
{
  int k_errno = krb_get_cred(princ, inst, krealm, c);
  if (k_errno != KSUCCESS)
    {
      k_errno = krb_mk_req(tkt, princ, inst, krealm, 0);
      if (k_errno == KSUCCESS)
	k_errno = krb_get_cred(princ, inst, krealm, c);
    }
  return k_errno;
}

int
k_afsklog(char *cell, char *krealm)
{
  int k_errno;
  CREDENTIALS c;
  KTEXT_ST ticket;
  char realm[REALM_SZ];
  char CELL[64];

  if (!k_hasafs())
    return KSUCCESS;

  if (cell == 0 || cell[0] == 0)
    cell = k_cell();
  if (cell == 0)
    return KSUCCESS;		/* Not running AFS */
  foldup(CELL, cell);

  if (krealm == 0 || krealm[0] == 0)
    {
      k_errno = krb_get_lrealm(realm, 0);
      if (k_errno != KSUCCESS)
	return k_errno;
      krealm = realm;
    }  
  
  /* First we try afs.cell@REALM, if there is no such thing try
   * afs@CELL instead. */
  k_errno = get_cred(AUTH_SUPERUSER, cell, krealm, &c, &ticket);
  if (k_errno != KSUCCESS)
    k_errno = get_cred(AUTH_SUPERUSER, "", CELL, &c, &ticket);
  
  if (k_errno == KSUCCESS)
    {
      struct ViceIoctl parms;
      struct ClearToken ct;
      int32_t sizeof_x;
      char buf[2048], *t;

      /*
       * 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) k_pioctl(0, VIOCSETTOK, &parms, 0);
    }
  return k_errno;
}

#define NO_ENTRY_POINT		0
#define SINGLE_ENTRY_POINT	1
#define MULTIPLE_ENTRY_POINT	2
#define SINGLE_ENTRY_POINT2	3
#define AIX_ENTRY_POINTS	4
#define UNKNOWN_ENTRY_POINT	5
static int afs_entry_point = UNKNOWN_ENTRY_POINT;

int
k_pioctl(char *a_path,
	 int o_opcode,
	 struct ViceIoctl *a_paramsP,
	 int a_followSymlinks)
{
#ifdef AFS_SYSCALL
  if (afs_entry_point == SINGLE_ENTRY_POINT)
    return syscall(AFS_SYSCALL, AFSCALL_PIOCTL,
		   a_path, o_opcode, a_paramsP, a_followSymlinks);
#endif

#ifdef AFS_PIOCTL
    if (afs_entry_point == MULTIPLE_ENTRY_POINT)
      return syscall(AFS_PIOCTL,
		     a_path, o_opcode, a_paramsP, a_followSymlinks);
#endif

#ifdef AFS_SYSCALL2
  if (afs_entry_point == SINGLE_ENTRY_POINT2)
    return syscall(AFS_SYSCALL2, AFSCALL_PIOCTL,
		   a_path, o_opcode, a_paramsP, a_followSymlinks);
#endif

#ifdef _AIX
  if (afs_entry_point == AIX_ENTRY_POINTS)
    return lpioctl(a_path, o_opcode, a_paramsP, a_followSymlinks);
#endif

  errno = ENOSYS;
  kill(getpid(), SIGSYS);	/* You loose! */
  return -1;
}

int
k_unlog(void)
{
  struct ViceIoctl parms;
  bzero((char *)&parms, sizeof(parms));
  return k_pioctl(0, VIOCUNLOG, &parms, 0);
}

int
k_setpag(void)
{
#ifdef AFS_SYSCALL
  if (afs_entry_point == SINGLE_ENTRY_POINT)
    return syscall(AFS_SYSCALL, AFSCALL_SETPAG);
#endif

#ifdef AFS_SETPAG
  if (afs_entry_point == MULTIPLE_ENTRY_POINT)
    return syscall(AFS_SETPAG);
#endif

#ifdef AFS_SYSCALL2
  if (afs_entry_point == SINGLE_ENTRY_POINT2)
    return syscall(AFS_SYSCALL2, AFSCALL_SETPAG);
#endif

#ifdef _AIX
  if (afs_entry_point == AIX_ENTRY_POINTS)
    return lsetpag();
#endif

  errno = ENOSYS;
  kill(getpid(), SIGSYS);	/* You loose! */
  return -1;
}

static jmp_buf catch_SIGSYS;

static void
SIGSYS_handler()
{
  errno = 0;
  signal(SIGSYS, SIGSYS_handler); /* Need to reinstall handler on SYSV */
  longjmp(catch_SIGSYS, 1);
}

int
k_hasafs(void)
{
  int saved_errno;
  RETSIGTYPE (*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);

#ifdef AFS_SYSCALL
  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;
	}
    }
#endif /* AFS_SYSCALL */

#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 /* AFS_PIOCTL */

#ifdef AFS_SYSCALL2
  if (setjmp(catch_SIGSYS) == 0)
    {
      syscall(AFS_SYSCALL2, 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_POINT2;
	  goto done;
	}
    }
#endif /* AFS_SYSCALL */

#ifdef _AIX
  if (setjmp(catch_SIGSYS) == 0)
    {
      lpioctl(0, 0, 0, 0);
      if (errno == EINVAL)
	{
	  afs_entry_point = AIX_ENTRY_POINTS;
	  goto done;
	}
    }
#endif

 done:
  (void) signal(SIGSYS, saved_func);
  errno = saved_errno;
  return afs_entry_point != NO_ENTRY_POINT;
}
