#include <sys/types.h>
#include <sys/param.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <stdarg.h>
#include <pwd.h>
#include <krb.h>
#include <unistd.h>
#define PAM_SM_SESSION

#if !defined(HAVE_KTH_KRB) && !defined(HAVE_LIBKRBAFS)
#include <afs/stds.h>
#include <afs/cellconfig.h>
#include <afs/auth.h>
#include <afs/kauth.h> 
#include <afs/kautils.h>
#endif

#include <security/pam_modules.h>

#ifndef LINUX 
#include <security/pam_appl.h>
#endif  /* LINUX */

/* I need to put better debugging support in */
#if 1
#define D(x) 
#else
#endif

/* type definition for the control options */

typedef struct {
     const char *token;
     unsigned int mask;            /* shall assume 32 bits of flags */
     unsigned int flag;
} AFSTOK_Ctrls;

/*
 * macro to determine if a given flag is on
 */

#define on(x,ctrl)  (afstok_args[x].flag & ctrl)

/*
 * macro to determine that a given flag is NOT on
 */

#define off(x,ctrl) (!on(x,ctrl))

/*
 * macro to turn on/off a ctrl flag manually
 */

#define set(x,ctrl)   (ctrl = ((ctrl)&afstok_args[x].mask)|afstok_args[x].flag)
#define unset(x,ctrl) (ctrl &= ~(afstok_args[x].flag))

/* the generic mask */

#define _ALL_ON_  (~0U)

/* end of macro definitions definitions for the control flags */

/* ****************************************************************** *
 * ctrl flags proper..
 */

/*
 * here are the various options recognized by the afstok module. They
 * are enumerated here and then defined below. Internal arguments are
 * given NULL tokens.
 */

#define AFSTOK_ROOT_SETPAG          0     
#define AFSTOK_DEBUG                1
#define AFSTOK__QUIET               2
/* -------------- */
#define AFSTOK_CTRLS_               3     /* number of ctrl arguments defined */


static AFSTOK_Ctrls afstok_args[AFSTOK_CTRLS_] = {
/* symbol                 token name          ctrl mask      ctrl       *
 * ------------------     ------------------  -------------- ---------- */

/* AFSTOK_ROOT_SETPAG */    { "rootpag",        _ALL_ON_,              01 },
/* AFSTOK_DEBUG */          { "debug",          _ALL_ON_,              02 },
/* AFSTOK__QUIET */         { NULL,            _ALL_ON_,              04 },
};

#define AFSTOK_DEFAULTS  0

/* syslogging function for errors and other information */

static void _log_err(int err, const char *format, ...)
{
    va_list args;

    va_start(args, format);
    openlog("PAM-Afstok", LOG_CONS|LOG_PID, LOG_AUTH);
    vsyslog(err, format, args);
    va_end(args);
    closelog();
}

/*
 * set the control flags for the AFSTOK module.
 */

static int set_ctrl(int flags, int argc, const char **argv)
{
     unsigned int ctrl;

     ctrl = AFSTOK_DEFAULTS;         /* the default selection of options */

     /* set some flags manually */

     if ( flags & PAM_SILENT ) {
	  set(AFSTOK__QUIET, ctrl);
     }

     /* now parse the arguments to this module */

     while (argc-- > 0) {
	  int j;

	  D(("pam_afstok arg: %s\n",*argv));

	  for (j=0; j<AFSTOK_CTRLS_; ++j) {
	       if (afstok_args[j].token
		   && ! strcmp(*argv, afstok_args[j].token) ) {
		    break;
	       }
	  }

	  if ( j >= AFSTOK_CTRLS_ ) {
	       _log_err(LOG_ERR, "unrecognized option [%s]",*argv);
	  } else {
	       ctrl &= afstok_args[j].mask;    /* for turning things off */
	       ctrl |= afstok_args[j].flag;    /* for turning things on  */
	  }

	  ++argv;                           /* step to next argument */
     }

     /* return the set of flags */

     return ctrl;
}

/* a convenient helper function to overwrite passwords */

static void _pam_overwrite(register char *xx)
{
     if (xx != NULL)
          while (*xx)
               *xx++ = '\0';
}

/* use this to free password strings */

static char *_pam_delete(register char *xx)
{
     _pam_overwrite(xx);
     free(xx);
     return NULL;
}

static void _cleanup(pam_handle_t *pamh, void *x, int error_status)
{
     x = _pam_delete( (char *) x );
}

/* safe duplication of character strings */

static char *xstrdup(const char *x)
{
     char *new=NULL;

     if (x != NULL) {
	  if ((new = malloc(strlen(x)+1)) == NULL) {
	       _log_err(LOG_CRIT, "out of memory in xstrdup");
	  } else {
	       strcpy(new,x);
	  }
     }

     return new;           /* return the duplicate or NULL on error */
}

#if !defined(HAVE_KTH_KRB) && !defined(HAVE_LIBKRBAFS) && !defined(HAVE_AFS_33A)
static int my_get_cred(char *name, char *inst, char *realm, CREDENTIALS *c)
{
  int status; 
  
  status = krb_get_cred(name, inst, realm, c);
  if (status != KSUCCESS) {
    status = get_ad_tkt(name, inst, realm, 255);
    if (status == KSUCCESS)
      status = krb_get_cred(name, inst, realm, c);
  }
  D(("get_cred: %d\n", status));

  return (status);
}
#endif

static int _afstok_open_session(pam_handle_t *pamh, unsigned int ctrl)
{
    int rc, dosetpag = 1, retval = PAM_SUCCESS;
    struct ktc_principal aclient;
    const char *user;
    struct passwd *pw;
#if !defined(HAVE_KTH_KRB) && !defined(HAVE_AFS_33A)
    char realm[REALM_SZ];
    CREDENTIALS c;
    struct ktc_principal aserver;
    struct ktc_token atoken;
    struct afsconf_dir *configdir;
    char local_cell[MAXCELLCHARS+1];
#define AFSKEY "afs"
#define AFSINST ""
#endif
    const char *padcred;
    char *tkfile;

    if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL) {
      _log_err(LOG_ERR, "cannot determine the user's name");
      return PAM_USER_UNKNOWN;
    }

    /* */

    tkfile = pam_getenv(pamh, "KRBTKFILE");
    if (tkfile)
      krb_set_tkt_string(tkfile);

    if (on(AFSTOK_ROOT_SETPAG, ctrl)) {
      pw = getpwnam(user);
      if (pw && (pw->pw_uid == 0))
	dosetpag = 0;
    }

#if defined(HAVE_LIBKRBAFS) || defined(HAVE_KTH_KRB)
    if (k_hasafs()) {
      if (dosetpag)
        k_setpag();
      
      k_afsklog(0, 0);
    }
#else
#ifdef HAVE_AFS_33A
    strcpy(aclient.name, user);
    aclient.instance[0]='\0';
    ka_Init(0);

    if (dosetpag)
      setpag();

    ktc_set_tkt_string(tkt_string());
    rc = ka_GetAFSTicket (aclient.name, aclient.instance, 
                        (char *)0, MAXKTCTICKETLIFETIME, 0 );
    rx_Finalize();
    LWP_TerminateProcessSupport();
    if (rc != 0) 
      retval = PAM_AUTH_ERR;
#else
    if (krb_get_lrealm(realm, 1) != 0) {
      return PAM_AUTH_ERR;
    }

    if (!(configdir = afsconf_Open(AFSCONF_CLIENTNAME))) {
      return PAM_SYSTEM_ERR;
    }

    if (afsconf_GetLocalCell(configdir, local_cell, MAXCELLCHARS)) {
      return PAM_SYSTEM_ERR;
    }

    (void) afsconf_Close(configdir);

    rc = my_get_cred(AFSKEY, local_cell, realm, &c);

    if ((rc == KDC_PR_UNKNOWN)||(rc == KDC_NAME_EXP)) {
      rc = my_get_cred(AFSKEY, AFSINST, realm, &c);
    }

    if (rc) {
      retval = PAM_AUTH_ERR;
    } else {
      strncpy(aserver.name, AFSKEY, MAXKTCNAMELEN - 1);
      strncpy(aserver.instance, AFSINST, MAXKTCNAMELEN - 1);
      strncpy(aserver.cell, local_cell, MAXKTCREALMLEN - 1);
      
      strcpy(aclient.name, user);
      aclient.instance[0]='\0';

      atoken.kvno = c.kvno;
      atoken.startTime = c.issue_date;
#if defined(HAVE_KRB_LIFE_TO_TIME)
      /* use AFS ticket lifetime lookup table */
      atoken.endTime = krb_life_to_time(c.issue_date, c.lifetime);
#else /* defined(HAVE_KRB_LIFE_TO_TIME) */
      /* ticket lifetime is in five-minutes blocks. */
      atoken.endTime = c.issue_date + ((unsigned char)c.lifetime * 5 * 60);
#endif /* defined(HAVE_KRB_LIFE_TO_TIME) */
      memcpy(&atoken.sessionKey, c.session, sizeof(atoken.sessionKey));
      atoken.ticketLen = c.ticket_st.length;
      memcpy(atoken.ticket, c.ticket_st.dat, atoken.ticketLen);

      if (dosetpag)
        setpag();

      if (ktc_SetToken(&aserver, &atoken, &aclient, 0) != 0) {
        D(("SetToken\n"));
        retval = PAM_AUTH_ERR;
      }
    }
    D(("ok\n"));
#endif /* HAVE_AFS_33A */
#endif /* HAVE_KTH_KRB || HAVE_LIBKRBAFS */

    if (dosetpag) {
      void *got_a_pag = malloc(sizeof(int));
      int one = 1;
      memcpy((int *)got_a_pag, &one, sizeof(int));
      pam_set_data(pamh, "gotpag", got_a_pag, _cleanup);
    }

    return retval;
}

static int _afstok_close_session(pam_handle_t *pamh, unsigned int ctrl)
{
  int retval, nukepag;

  retval = pam_get_data( pamh, "gotpag", (void*) &nukepag );
  if (  retval != PAM_SUCCESS ) {
    return PAM_SESSION_ERR;
  }

  if (nukepag == 1) {
#if defined(HAVE_KTH_KRB) || defined(HAVE_LIBKRBAFS)
    if (k_hasafs()) {
      (void) k_unlog();
    }
#else
    ktc_ForgetAllTokens();
#endif
  }
  return PAM_SUCCESS;
}

PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
                                   int argc, const char **argv)
{
     unsigned int ctrl;

     D(("called.\n"));
     ctrl = set_ctrl(flags, argc, argv);
     return _afstok_open_session(pamh, ctrl);
}

PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,
                                    int argc, const char **argv)
{
     unsigned int ctrl;

     D(("called.\n"));
     ctrl = set_ctrl(flags, argc, argv);
     return _afstok_close_session(pamh, ctrl);
}

/* static module data */

#ifdef PAM_STATIC
struct pam_module _pam_afstok_modstruct = {
     "pam_afstok",
     NULL,
     NULL,
     NULL,
     pam_sm_open_session,
     pam_sm_close_session,
     NULL
};

#endif

/*
 * Copyright (c) Alexander O. Yuriev, 1996.  All rights reserved.
 * Copyright (c) Andrew G. Morgan, 1996, <morgan@physics.ucla.edu>
 * Copyright (c) Chaskiel Grundman, 1996
 * Copyright (c) Derrick J. Brashear, 1996, <shadow@dementia.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, and the entire permission notice in its entirety,
 *    including the disclaimer of warranties.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 * 
 * ALTERNATIVELY, this product may be distributed under the terms of
 * the GNU Public License, in which case the provisions of the GPL are
 * required INSTEAD OF the above restrictions.  (This clause is
 * necessary due to a potential bad interaction between the GPL and
 * the restrictions contained in a BSD-style copyright.)
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
