/*
 * lib/krb5/os/k5dfspag.c
 *
 * New Kerberos module to issue the DFS PAG syscalls.
 * It also contains the routine to fork and exec the
 * k5dcecon routine to do most of the work.
 *
 * This file is designed to be as independent of DCE
 * and DFS as possible. The only dependencies are on
 * the syscall numbers.  If DFS not running or not installed,
 * the sig handlers will catch and the signal and
 * will  continue.
 *
 * krb5_dfs_newpag and krb5_dfs_getpag should not be real
 * Kerberos routines, since they should be setpag and getpag
 * in the DCE library, but without the DCE baggage.
 * Thus they don't have context, and don't return a krb5 error.
 *
 *
 *
 * krb5_dfs_pag()
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

RCSID("$Id$");

#include <krb5.h>

#ifdef DCE

#include <stdio.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/param.h>

/* Only run this DFS PAG code on systems with POSIX
 * All that we are interested in dor:, AIX 4.x,
 * Solaris 2.5.x, HPUX 10.x  Even SunOS 4.1.4, AIX 3.2.5
 * and SGI 5.3 are OK.  This simplifies
 * the build/configure which I don't want to change now.
 * All of them also have waitpid as well.
 */

#define POSIX_SETJMP
#define POSIX_SIGNALS
#define HAVE_WAITPID

#include <signal.h>
#include <setjmp.h>
#ifndef POSIX_SETJMP
#undef sigjmp_buf
#undef sigsetjmp
#undef siglongjmp
#define sigjmp_buf  jmp_buf
#define sigsetjmp(j,s)  setjmp(j)
#define siglongjmp  longjmp
#endif

#ifdef POSIX_SIGNALS
typedef struct sigaction handler;
#define handler_init(H,F)       (sigemptyset(&(H).sa_mask), \
                     (H).sa_flags=0, \
                     (H).sa_handler=(F))
#define handler_swap(S,NEW,OLD)     sigaction(S, &NEW, &OLD)
#define handler_set(S,OLD)      sigaction(S, &OLD, NULL)
#else
typedef sigtype (*handler)();
#define handler_init(H,F)       ((H) = (F))
#define handler_swap(S,NEW,OLD)     ((OLD) = signal ((S), (NEW)))
#define handler_set(S,OLD)      (signal ((S), (OLD)))
#endif

#define krb5_sigtype void
#define WAIT_USES_INT
typedef krb5_sigtype sigtype;


/*
 * Need some syscall numbers based on different systems.
 * These are based on:
 * HPUX 10.10 /opt/dce/include/dcedfs/syscall.h
 * Solaris 2.5 /opt/dcelocal/share/include/dcedfs/syscall.h
 * AIX 4.2  - needs some funny games with load and kafs_syscall
 * to get the kernel extentions. There should be a better way!
 *
 * DEE 5/27/97
 *
 */


#define AFSCALL_SETPAG 2
#define AFSCALL_GETPAG 11

#if defined(sun)
#define AFS_SYSCALL  72

#elif defined(hpux)
/* assume HPUX 10 +  or is it 50 */
#define AFS_SYSCALL 326

#elif defined(_AIX)
#ifndef DPAGAIX
#define DPAGAIX LIBEXECDIR "/dpagaix"
#endif
int *load();
static int (*dpagaix)(int, int, int, int, int, int) = 0;

#elif defined(sgi) || defined(_sgi)
#define AFS_SYSCALL      206+1000

#else
#define AFS_SYSCALL (Unknown_DFS_AFS_SYSCALL)
#endif


#ifdef  WAIT_USES_INT
                int wait_status;
#else   /* WAIT_USES_INT */
                union wait wait_status;
#endif  /* WAIT_USES_INT */

#ifndef K5DCECON
#define K5DCECON LIBEXECDIR "/k5dcecon"
#endif

/*
 * mysig()
 *
 * signal handler if DFS not running
 *
 */

static sigjmp_buf setpag_buf;

static sigtype mysig()
{
  siglongjmp(setpag_buf, 1);
}

/*
 * krb5_dfs_pag_syscall()
 *
 * wrapper for the syscall with signal handlers
 *
 */

static int  krb5_dfs_pag_syscall(opt1,opt2)
  int opt1;
  int opt2;
{
  handler sa1, osa1;
  handler sa2, osa2;
  int pag = -2;

  handler_init (sa1, mysig);
  handler_init (sa2, mysig);
  handler_swap (SIGSYS, sa1, osa1);
  handler_swap (SIGSEGV, sa2, osa2);

  if (sigsetjmp(setpag_buf, 1) == 0) {

#if defined(_AIX)
    if (!dpagaix)
      dpagaix = load(DPAGAIX, 0, 0);
    if (dpagaix)
      pag = (*dpagaix)(opt1, opt2, 0, 0, 0, 0);
#else
    pag = syscall(AFS_SYSCALL, opt1, opt2, 0, 0, 0, 0);
#endif

    handler_set (SIGSYS, osa1);
    handler_set (SIGSEGV, osa2);
    return(pag);
  }

  /* syscall failed! return 0 */
  handler_set (SIGSYS, osa1);
  handler_set (SIGSEGV, osa2);
  return(-2);
}

/*
 * krb5_dfs_newpag()
 *
 * issue a DCE/DFS setpag system call to set the newpag
 * for this process. This takes advantage of a currently
 * undocumented feature of the Transarc port of DFS.
 * Even in DCE 1.2.2 for which the source is available,
 * (but no vendors have released), this feature is not
 * there, but it should be, or could be added.
 * If new_pag is zero, then the syscall will get a new pag
 * and return its value.
 */

int krb5_dfs_newpag(new_pag)
  int new_pag;
{
  return(krb5_dfs_pag_syscall(AFSCALL_SETPAG, new_pag));
}

/*
 * krb5_dfs_getpag()
 *
 * get the current PAG. Used mostly as a test.
 */

int krb5_dfs_getpag()
{
  return(krb5_dfs_pag_syscall(AFSCALL_GETPAG, 0));
}

/*
 * krb5_dfs_pag()
 *
 * Given a principal and local username,
 * fork and exec the k5dcecon module to create
 * refresh or join a new DCE/DFS
 * Process Authentication Group (PAG)
 *
 * This routine should be called after krb5_kuserok has
 * determined that this combination of local user and
 * principal are acceptable for the local host.
 *
 * It should also be called after a forwarded ticket has
 * been received, and the KRB5CCNAME environment variable
 * has been set to point at it. k5dcecon will convert this
 * to a new DCE context and a new pag and replace KRB5CCNAME
 * in the environment.
 *
 * If there is no forwarded ticket, k5dcecon will attempt
 * to join an existing PAG for the same principal and local
 * user.
 *
 * And it should be called before access to the home directory
 * as this may be in DFS, not accessible by root, and require
 * the PAG to have been setup.
 *
 * The krb5_afs_pag can be called after this routine to
 * use the the cache obtained by k5dcecon to get an AFS token.
 * DEE - 7/97
 */

int krb5_dfs_pag(context, flag, principal, luser)
	krb5_context context;
    int flag; /* 1 if a forwarded TGT is to be used */
	krb5_principal principal;
	const char *luser;

{

  struct stat stx;
  int fd[2];
  int i,j;
  int pid;
  int new_pag;
  int pag;
  char newccname[MAXPATHLEN] = "";
  char *princ;
  int err;
  struct sigaction newsig, oldsig;

#ifdef  WAIT_USES_INT
  int wait_status;
#else   /* WAIT_USES_INT */
  union wait wait_status;
#endif  /* WAIT_USES_INT */

  if (krb5_unparse_name(context, principal, &princ))
   return(0);

   /* test if DFS is running or installed */
   if (krb5_dfs_getpag() == -2)
     return(0); /* DFS not running, don't try */

  if (pipe(fd) == -1)
     return(0);

  /* Make sure that telnetd.c's SIGCHLD action don't happen right now... */
  memset((char *)&newsig, 0, sizeof(newsig));
  newsig.sa_handler = SIG_DFL;
  sigaction(SIGCHLD, &newsig, &oldsig);

  pid = fork();
  if (pid <0)
   return(0);

  if (pid == 0) {  /* child process */

    close(1);       /* close stdout */
    dup(fd[1]);     /* point stdout at pipe here */
    close(fd[0]);   /* don't use end of pipe here */
    close(fd[1]);   /* pipe now as stdout */

    execl(K5DCECON, "k5dcecon",
         (flag) ? "-f" : "-s" ,
		 "-l", luser,
		 "-p", princ, (char *)0);

    exit(127);      /* incase execl fails */
  }

  /* parent, wait for child to finish */

  close(fd[1]);  /* don't need this end of pipe */

/* #if defined(sgi) || defined(_sgi) */
  /* wait_status.w_status = 0; */
  /* waitpid((pid_t) pid, &wait_status.w_status, 0); */
/* #else */


  wait_status = 0;
#ifdef  HAVE_WAITPID
  err = waitpid((pid_t) pid, &wait_status, 0);
#else   /* HAVE_WAITPID */
  err = wait4(pid, &wait_status, 0, (struct rusage *) NULL);
#endif  /* HAVE_WAITPID */
/* #endif */

  sigaction(SIGCHLD, &oldsig, 0);
  if (WIFEXITED(wait_status)){
    if (WEXITSTATUS(wait_status) == 0) {
      i = 1;
      j = 0;
      while (i != 0) {
        i = read(fd[0], &newccname[j], sizeof(newccname)-1-j);
        if ( i > 0)
          j += i;
        if (j >=  sizeof(newccname)-1)
          i = 0;
      }
      close(fd[0]);
      if (j > 0) {
        newccname[j] = '\0';
        esetenv("KRB5CCNAME",newccname,1);
        sscanf(&newccname[j-8],"%8x",&new_pag);
        if (new_pag && strncmp("FILE:/opt/dcelocal/var/security/creds/dcecred_", newccname, 46) == 0) {
          if((pag = krb5_dfs_newpag(new_pag)) != -2) {
            return(pag);
          }
        }
      }
    }
  }
  return(0); /* something not right */
}

#else /* DCE */

/*
 * krb5_dfs_pag - dummy version for the lib for systems
 * which don't have DFS, or the needed setpag kernel code.
 */

krb5_boolean
krb5_dfs_pag(context, principal, luser)
	krb5_context context;
	krb5_principal principal;
	const char *luser;
{
	return(0);
}

#endif /* DCE */
