/* -*-c-*-
 * support.-c
 * PAM-kerberos5 module, support functions.  
 * Naomaru Itoi <itoi@eecs.umich.edu>, 1997/6/23. 
 * Copyright information is at the end of this file.  
 */

/*
 * here is the string to inform the user that the new passwords they
 * typed were not the same.
 */

#define MISTYPED_PASS "Sorry, kerberos passwords do not match."

/* type definition for the control options */

typedef struct {
     const char *token;
     unsigned int mask;            /* shall assume 32 bits of flags */
     unsigned int flag;
} KRB5_Ctrls;

/*
 * macro to determine if a given flag is on
 */

#define on(x,ctrl)  (krb5_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)&krb5_args[x].mask)|krb5_args[x].flag)
#define unset(x,ctrl) (ctrl &= ~(krb5_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 krb5 module. They
 * are enumerated here and then defined below. Internal arguments are
 * given NULL tokens.
 */

#define KRB5__OLD_PASSWD          0     /* internal */
#define KRB5__VERIFY_PASSWD       1     /* internal */
#define KRB5_USE_AFS_STK          2     

#define KRB5_DEBUG                3
#define KRB5_USE_FIRST_PASS       4
#define KRB5_TRY_FIRST_PASS       5
#define KRB5_NOT_SET_PASS         6     /* don't set the AUTHTOK items */

#define KRB5__PRELIM              7     /* internal */
#define KRB5__UPDATE              8     /* internal */
#define KRB5__NONULL              9     /* internal */
#define KRB5__QUIET              10     /* internal */
#define KRB5_USE_AUTHTOK         11     /* insist on reading PAM_AUTHTOK */

/* -------------- */
#define KRB5_CTRLS_              12     /* number of ctrl arguments defined */

static KRB5_Ctrls krb5_args[KRB5_CTRLS_] = {
/* symbol                 token name          ctrl mask      ctrl       *
 * ------------------     ------------------  -------------- ---------- */

/* KRB5__OLD_PASSWD */    {  NULL,            _ALL_ON_,              01 },
/* KRB5__VERIFY_PASSWD */ {  NULL,            _ALL_ON_,              02 },
/* KRB5_USE_AFS_STK */    { "afskey",         _ALL_ON_,              04 },
/* KRB5_DEBUG */          { "debug",          _ALL_ON_,             010 },
/* KRB5_USE_FIRST_PASS */ { "use_first_pass", _ALL_ON_^(060),       020 },
/* KRB5_TRY_FIRST_PASS */ { "try_first_pass", _ALL_ON_^(060),       040 },
/* KRB5_NOT_SET_PASS */   { "not_set_pass",   _ALL_ON_,            0100 },
/* KRB5__PRELIM */        {  NULL,            _ALL_ON_^(0600),     0200 },
/* KRB5__UPDATE */        {  NULL,            _ALL_ON_^(0600),     0400 },
/* KRB5__NONULL */        {  NULL,            _ALL_ON_,           01000 },
/* KRB5__QUIET */         {  NULL,            _ALL_ON_,           02000 },
/* KRB5_USE_AUTHTOK */    { "use_authtok",    _ALL_ON_,           04000 },
};

#define KRB5_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-Krb5", LOG_CONS|LOG_PID, LOG_AUTH);
    vsyslog(err, format, args);
    va_end(args);
    closelog();
}

/* NI, definitions, from here */
#define KRB5_DEFAULT_LIFE 60*60*10 /* 10 hours */
krb5_data tgtname = {
    0,
    KRB5_TGS_NAME_SIZE,
    KRB5_TGS_NAME
};
char s[100];
/* NI, to here */

/* this is a front-end for module-application conversations */

static int converse(pam_handle_t *pamh, int ctrl, int nargs
		    , struct pam_message **message
		    , struct pam_response **response)
{
     int retval;
     struct pam_conv *conv;

     retval = pam_get_item( pamh, PAM_CONV, (const void **) &conv ) ; 
     if ( retval == PAM_SUCCESS ) {

	  retval = conv->conv(nargs, ( const struct pam_message ** ) message
			      , response, conv->appdata_ptr);

	  if (retval != PAM_SUCCESS && on(KRB5_DEBUG,ctrl) ) {
	       _log_err(LOG_DEBUG, "conversation failure [%s]"
			, pam_strerror(retval));
	  }

     } else {
	  _log_err(LOG_ERR, "couldn't obtain coversation function [%s]"
	       , pam_strerror(retval));
     }

     return retval;                  /* propagate error status */
}

static int make_remark(pam_handle_t *pamh, unsigned int ctrl
		       , int type, const char *text)
{
     if ( off(KRB5__QUIET, ctrl) ) {
	  struct pam_message *pmsg[1], msg[1];
	  struct pam_response *resp;
	  
	  pmsg[0] = &msg[0];
	  msg[0].msg = text;
	  msg[0].msg_style = type;
	  resp = NULL;

	  return converse(pamh, ctrl, 1, pmsg, &resp);
     }
     return PAM_SUCCESS;
}

/*
 * set the control flags for the KRB5 module.
 */

static int set_ctrl(int flags, int argc, const char **argv)
{
     unsigned int ctrl;

     ctrl = KRB5_DEFAULTS;         /* the default selection of options */

     /* set some flags manually */

     if ( flags & PAM_UPDATE_AUTHTOK ) {
	  set(KRB5__UPDATE, ctrl);
     }
     if ( flags & PAM_PRELIM_CHECK ) {
	  set(KRB5__PRELIM, ctrl);
     }
     if ( flags & PAM_DISALLOW_NULL_AUTHTOK ) {
	  set(KRB5__NONULL, ctrl);
     }
     if ( flags & PAM_SILENT ) {
	  set(KRB5__QUIET, ctrl);
     }
     /*     if ( flags & PAM_CRED_ESTABLISH ) {
       set(KRB5__CRED_ESTABLISH, ctrl);
     }
     if ( flags & PAM_CRED_DELETE ) {
       set(KRB5__CRED_DELETE, ctrl);
     }*/

     /* now parse the arguments to this module */

     while (argc-- > 0) {
	  int j;

	  for (j=0; j<KRB5_CTRLS_; ++j) {
	       if (krb5_args[j].token
		   && ! strcmp(*argv, krb5_args[j].token) ) {
		    break;
	       }
	  }

	  if ( j >= KRB5_CTRLS_ ) {
	       _log_err(LOG_ERR, "unrecognized option [%s]",*argv);
	  } else {
	       ctrl &= krb5_args[j].mask;    /* for turning things off */
	       ctrl |= krb5_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 */
}

static int _krb5_set_cred(pam_handle_t *pamh, unsigned int ctrl)
{
     int retval = 0;
     int code = 0;

     /* We need to deal with: 
      * PAM_CRED_ESTABLISH (get new cred)
      * PAM_CRED_DELETE (nuke creds)
      * PAM_CRED_REINITIALIZE (not sure that we need to deal with this)
      * PAM_CRED_REFRESH (ha ha)
      */
     
     switch (ctrl) {
     case PAM_CRED_REINITIALIZE:
     case PAM_CRED_REFRESH:
       retval = PAM_CRED_UNAVAIL;
       break;
       
     case PAM_CRED_DELETE:
       {
	 krb5_context context;
	 krb5_ccache ccache = NULL;

	 /* initialize krb5 context & error tables */
	 if (retval = krb5_init_context(&context)) {
	   com_err("NI", retval, "initializing krb5 context");
	   exit(retval);
	 }
	 krb5_init_ets(context);

	 /* get credential cache */
	 if ((code = krb5_cc_default(context, &ccache))) {
	   com_err("_krb5_set_cred", code, "while getting default ccache");
	 return PAM_AUTH_ERR;
	 }
	 /* destroy credential cache */
	 krb5_cc_destroy (context, ccache);
	 return PAM_SUCCESS;
	 break;
       }
     case PAM_CRED_ESTABLISH:
     default:
       {
	 krb5_creds tgtcred;
	 char *padcred;
	 krb5_context context;
	 krb5_ccache ccache = NULL;
	 const char *user_name;
	 krb5_principal me, tmp;
	 int i;

	 /* load tgt credential from PAM storage */
	 if (pam_get_data(pamh, "krbcred", (const void **)&padcred) != PAM_SUCCESS)
	   {
	     return PAM_CRED_UNAVAIL;
	   }
	 
	 /* get user name */
	 if ((pam_get_item( pamh, PAM_USER, (void*) &user_name ) 
	      != PAM_SUCCESS) || ( user_name == NULL))
	   return PAM_SESSION_ERR;
	 
	 /* initialize krb5 context & error table */
	 if (retval = krb5_init_context(&context)) {
	   com_err("_krb5_set_cred", retval, "initializing krb5 context");
	   exit(retval);
	 }
	 krb5_init_ets(context);

	 /* get default credential cache */
	 code = krb5_cc_default(context, &ccache);
	 if (code) {
	   com_err("_krb5_set_cred", code, "while getting default ccache");
	   return PAM_AUTH_ERR;
	 }
	 code = krb5_cc_get_principal(context, ccache, &tmp);

	 if (code) {
	   /* initialize credential cache */
	   /* parse user_name to genarate principal me*/
	   code = krb5_parse_name (context, user_name, &me);
	   if (code) {
	     com_err ("_krb5_set_cred", code, "when parsing name %s",user_name);
	     return PAM_AUTH_ERR;
	   }
	   /* initialize it */
	   code = krb5_cc_initialize (context, ccache, me);
	   if (code) {
	     com_err ("_krb5_set_cred", code, 
		      "when initializing credential cache");
	     return PAM_AUTH_ERR;
	   }
	 }

	 /* copy tgtcred from tgt to PAM storage (padcred) */
	 memcpy((char *)&tgtcred, padcred, sizeof(tgtcred));

	 if (code = krb5_cc_store_cred(context, ccache, &tgtcred)) {
	   com_err ("_krb5_set_cred", code, 
		    "when storing credential cache");
	   return PAM_AUTH_ERR;
	 }
	 
	 krb5_cc_close(context, ccache);
	 
	 break;
       }
     }     
     return code;
}

/*
 * verify the password of a user
 */

static int _krb5_verify_password(pam_handle_t *pamh
				 , const char *name, const char *p
				 , unsigned int ctrl)
{
     int retval;
     krb5_error_code code;
     krb5_context context;
     krb5_ccache ccache = NULL;
     krb5_principal server, me, *me_p;
     krb5_creds my_creds;
     krb5_timestamp now;
     krb5_deltat lifetime = KRB5_DEFAULT_LIFE;
     krb5_creds tgt_creds;
     int i;
     char *padcred;
	  
     /* We don't allow null Kerberos passwords */
     if ( !p )
       return PAM_AUTH_ERR;

     /* initialize krb5 context & error tables*/
     if (retval = krb5_init_context(&context)) {
       com_err("NI", retval, "initializing krb5 context");
       exit(retval);
     }
     krb5_init_ets(context);

     /* get credential cache */
     if ((code = krb5_cc_default(context, &ccache))) {
       com_err("_krb5_verify_password", code, "while getting default ccache");
       return PAM_AUTH_ERR;
     }
	  
     /* setup credentials */
     memset((char *)&my_creds, 0, sizeof(my_creds));
     
     /* parse from name */
     code = krb5_parse_name (context, name, &me);
     if (code) {
       com_err ("_krb5_verify_password", code, "when parsing name %s",name);
       return PAM_AUTH_ERR;
     }
     my_creds.client = me;

     /* initialize cc */
     code = krb5_cc_initialize (context, ccache, me);
     if (code) {
       com_err ("verify", code, 
		"when initializing cache");
       return PAM_AUTH_ERR;
     }
     
     /* build server name */
     code = krb5_build_principal_ext(context, &server,
				     krb5_princ_realm(context, me)->length,
				     krb5_princ_realm(context, me)->data,
				     tgtname.length, tgtname.data,
				     krb5_princ_realm(context, me)->length,
				     krb5_princ_realm(context, me)->data,
				     0);
     if (code) {
       com_err("_krb5_verify_password", code, "while building server name");
       {
	 krb5_cc_destroy (context, ccache);
	 return PAM_AUTH_ERR;
       }
     }
    
     my_creds.server = server;
     
     code = krb5_timeofday(context, &now);
     if (code) {
       com_err("_krb5_verify_password", code, "while getting time of day");
       krb5_cc_destroy (context, ccache);
       return PAM_AUTH_ERR;
     }
     my_creds.times.starttime = 0; /* start timer when 
				      request gets to KDC */
     my_creds.times.endtime = now + lifetime;
     my_creds.times.renew_till = 0;
     
     /* okay, now get tgt */
     code = krb5_get_in_tkt_with_password(context, 0,
					  0, NULL, 0,
					  p,
					  ccache,
					  &my_creds, 0);
      
     if (code) {
       if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY)
	 fprintf (stderr,
		  "%s: %s\n", name, MISTYPED_PASS);
       else
	 com_err ("_krb5_verify_password", code,
		  "while getting initial credentials");
       krb5_cc_destroy (context, ccache);
       return PAM_AUTH_ERR;
    }

     /* verify the tgt */
     code = verify_krb_v5_tgt(context);
    if (code==-1){
      com_err ("_krb5_verify_password", code, "while verifying tgt");
      krb5_cc_destroy (context, ccache);
      return PAM_AUTH_ERR;
    }

    /* setup tgt credential */
    memset((char *)&tgt_creds, 0, sizeof(tgt_creds));
    
    /* retrieve ID of tgt */
    my_creds.ticket_flags =0;
    if (retval = krb5_cc_retrieve_cred(context, ccache,
				       0,
				       &my_creds, &tgt_creds)) {
      com_err ("_krb5_verify_password", code,
		 "while retrieving initial ticket for copy credentials");
    }

    /* checking whether this is correct tgt */
    /* copy tgt_creds to padcred, and save it to PAM storage */

    padcred = (char *)malloc(sizeof(tgt_creds));
    memcpy(padcred, &tgt_creds, sizeof(tgt_creds));
    pam_set_data(pamh,"krbcred", padcred, _cleanup);

    /*destroy credential cache*/
    krb5_cc_destroy(context, ccache);
    return PAM_SUCCESS;
}

/*
 * this function obtains the name of the current user and ensures
 * that the PAM_USER item is set to this value
 */

static int _krb5_get_user(pam_handle_t *pamh, unsigned int ctrl
			  , const char *prompt, const char **user)
{
     int retval;
     char *luser;

     retval = pam_get_user(pamh, (const char **)&luser, prompt);
     *user = luser;

     if (retval == PAM_SUCCESS) {
	  if (on(KRB5_DEBUG,ctrl)) {
	       _log_err(LOG_DEBUG, "username [%s] obtained", *user);
	  }
	  retval = pam_set_item(pamh, PAM_USER, *user);
     }

     if (retval != PAM_SUCCESS) {
	  _log_err(LOG_ERR, "unable to set username [%s]"
		   , pam_strerror(retval));
     }
     return retval;
}


/*
 * obtain a password from the user
 */

static int _krb5_read_password( pam_handle_t *pamh
				, unsigned int ctrl
				, const char *comment
				, const char *prompt1
				, const char *prompt2
				, const char *data_name
				, const char **pass )
{
     int authtok_flag;
     int retval;
     const char *item;
     char *token;

     /*
      * make sure nothing inappropriate gets returned
      */

     *pass = token = NULL;

     /*
      * which authentication token are we getting?
      */

     authtok_flag = on(KRB5__OLD_PASSWD,ctrl) ? PAM_OLDAUTHTOK:PAM_AUTHTOK ;

     /*
      * should we obtain the password from a PAM item ?
      */

     if ( on(KRB5_TRY_FIRST_PASS,ctrl) || on(KRB5_USE_FIRST_PASS,ctrl) ) {
	  retval = pam_get_item(pamh, authtok_flag, (const void **) &item);
	  if (retval != PAM_SUCCESS ) {
	       /* very strange. */
	       _log_err(LOG_ALERT
			, "pam_get_item returned error to krb5-read-password"
		    );
	       return retval;
	  } else if (item != NULL) {    /* we have a password! */
	       *pass = item;
	       item = NULL;
	       return PAM_SUCCESS;
	  } else if (on(KRB5_USE_FIRST_PASS,ctrl)) {
	       return PAM_AUTHTOK_RECOVER_ERR;       /* didn't work */
	  } else if (on(KRB5_USE_AUTHTOK, ctrl)
		     && off(KRB5__OLD_PASSWD, ctrl)) {
	       return PAM_AUTHTOK_RECOVER_ERR;
	  }
     }

     /*
      * getting here implies we will have to get the password from the
      * user directly.
      */

     {
	  struct pam_message msg[3],*pmsg[3];
	  struct pam_response *resp;
	  int i,expect;

	  /* prepare to converse */

	  if ( comment != NULL && off(KRB5__QUIET, ctrl) ) {
	       pmsg[0] = &msg[0];
	       msg[0].msg_style = PAM_TEXT_INFO;
	       msg[0].msg = comment;
	       i = 1;
	  } else {
	       i = 0;
	  }

	  pmsg[i] = &msg[i];
	  msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
	  msg[i++].msg = prompt1;

	  if ( prompt2 != NULL ) {
	       pmsg[i] = &msg[i];
	       msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
	       msg[i++].msg = prompt2;
	       expect = 2;
	  } else
	       expect = 1;

	  resp = NULL;

	  /* so call the conversation expecting 'expect' responses */

	  retval = converse(pamh, ctrl, i, pmsg, &resp);

	  if (resp != NULL) {

	       /* interpret the response */

	       if (retval == PAM_SUCCESS) {     /* a good conversation */

		    token = xstrdup(resp[0].resp);
		    if (token != NULL) {
			 if (expect == 2) {

			      /* verify that password entered correctly */
			      if (!resp[1].resp
				  || strcmp(token,resp[1].resp)) {
				   token = _pam_delete(token); /* mistyped */
				   retval = PAM_AUTHTOK_RECOVER_ERR;
				   make_remark(pamh, ctrl
					       , PAM_ERROR_MSG, MISTYPED_PASS);
			      }
			 }

		    } else {
			 _log_err(LOG_NOTICE
				  , "could not recover authentication token");
		    }

	       }

	       /*
		* tidy up the conversation (resp_retcode) is ignored
		* -- what is it for anyway? AGM
		*/

	       for (i=0; i<expect; ++i) {    /* tidy up */
		 resp[i].resp = _pam_delete(resp[i].resp);
	       }
	       free(resp);

	  } else {
	       retval = (retval == PAM_SUCCESS)
		    ? PAM_AUTHTOK_RECOVER_ERR:retval ;
	  }
     }

     if (retval != PAM_SUCCESS) {
	  if ( on(KRB5_DEBUG,ctrl) )
	       _log_err(LOG_DEBUG,"unable to obtain a password");
	  return retval;
     }

     /* 'token' is the entered password */

     if ( off(KRB5_NOT_SET_PASS, ctrl) ) {

	  /* we store this password as an item */

	  retval = pam_set_item(pamh, authtok_flag, token);
	  token = _pam_delete(token);   /* clean it up */
	  if ( retval != PAM_SUCCESS
	      || (retval = pam_get_item(pamh, authtok_flag
					, (const void **)&item))
	       != PAM_SUCCESS ) {

	       _log_err(LOG_CRIT, "error manipulating password");
	       return retval;

	  }

     } else {
	  /*
	   * then store it as data specific to this module. pam_end()
	   * will arrange to clean it up.
	   */

	  retval = pam_set_data(pamh, data_name, (void *) token, _cleanup);
	  if (retval != PAM_SUCCESS) {
	       _log_err(LOG_CRIT, "error manipulating password data [%s]"
		    , pam_strerror(retval) );
	       token = _pam_delete(token);
	       return retval;
	  }
	  item = token;
	  token = NULL;           /* break link to password */

     }

     *pass = item;
     item = NULL;                 /* break link to password */

     return PAM_SUCCESS;
}

#ifdef DEBUG

/* This is for debugging purposes ONLY DO NOT use on life systems !!!
 * You have been warned :-) - CG
 *
 * to get automated debugging to the log file, it must be created manually.
 * LOGFILE must exist, mode 666
 */
#define LOGFILE "/tmp/pam-debug.log"

static void _debug_pam(const char *format, ...)
{
     va_list args;
     FILE *logfile;
    
     va_start(args, format);

     if (!(logfile = fopen(LOGFILE,"a"))) {
          vfprintf(stderr, format, args);
     } else {
          vfprintf(logfile, format, args);
          fflush(logfile);
          fclose(logfile);
     }

     va_end(args);
}

#undef LOGFILE
#endif /* DEBUG */


/* NI verify_krb_v5_tgt from kerberos5/login.c */
/* call already conditionalized on login_krb5_get_tickets */
/*
 * Verify the Kerberos ticket-granting ticket just retrieved for the
 * user.  If the Kerberos server doesn't respond, assume the user is
 * trying to fake us out (since we DID just get a TGT from what is
 * supposedly our KDC).  If the host/<host> service is unknown (i.e.,
 * the local keytab doesn't have it), let her in.
 *
 * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
 */
int verify_krb_v5_tgt (c)
    krb5_context c;
{
    char phost[BUFSIZ];
    krb5_ccache ccdef;
    int retval, have_keys;
    krb5_principal princ;
    krb5_keyblock *kb = 0;
    krb5_error_code krbval;
    krb5_data packet;
    krb5_auth_context auth_context = NULL;
    krb5_ticket *ticket = NULL;

    /* XXX This is to work around a library bug.  I'm not sure if it's
       been fixed for beta-7, so leave this in for now.  Remove it (and
       fix the bug if necessary) after beta-7 ships.

       Whoever wrote that comment didn't mention what the bug is!  Ted
       says it is something about the starttime of the ticket and
       "now" being equal.  He thinks it is fixed, but isn't sure.
       */

    /* get the server principal for the local host */
    /* (use defaults of "host" and canonicalized local name) */
    krbval = krb5_sname_to_principal(c, 0, 0, KRB5_NT_SRV_HST, &princ);
    if (krbval) {
	    com_err ("login", krbval, "constructing local service name");
	    return -1;
    }

    /* since krb5_sname_to_principal has done the work for us, just
       extract the name directly */
    strncpy(phost, krb5_princ_component(c, princ, 1)->data, BUFSIZ);

    /* Do we have host/<host> keys? */
    /* (use default keytab, kvno IGNORE_VNO to get the first match,
       and enctype is currently ignored anyhow.) */
    krbval = krb5_kt_read_service_key (c, NULL, princ, 0, ENCTYPE_DES_CBC_CRC, &kb);
    if (kb)
	krb5_free_keyblock (c, kb);
    /* any failure means we don't have keys at all. */
    have_keys = krbval ? 0 : 1;

    /* set up credential cache -- obeying KRB5_ENV_CCNAME set earlier */
    /* (KRB5_ENV_CCNAME == "KRB5CCNAME" via osconf.h) */
    if (krbval = krb5_cc_default(c, &ccdef)) {
	com_err("login", krbval, "while getting default ccache");
	return -1;
    }
    /* talk to the kdc and construct the ticket */
    krbval = krb5_mk_req(c, &auth_context, 0, "host", phost,
			 0, ccdef, &packet);
    /* wipe the auth context for mk_req */
    if (auth_context) {
	krb5_auth_con_free(c, auth_context);
	auth_context = NULL;
    }
    if (krbval == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) {
	    /* we have a service key, so something should be 
	       in the database, therefore this error packet could
	       have come from an attacker. */
	    if (have_keys) { retval = -1; goto EGRESS; }
	    /* but if it is unknown and we've got no key, we don't
	       have any security anyhow, so it is ok. */
	    else { retval = 0; goto EGRESS; }
    }
    else if (krbval) {
	    com_err("login", krbval, 
		    "Unable to verify Kerberos V5 TGT: %s", phost);
#ifndef SYSLOG42
	    syslog (LOG_NOTICE|LOG_AUTH, "Kerberos V5 TGT bad: %s", 
		    error_message(krbval));
#endif
	    retval = -1;
	    goto EGRESS;
    }
    /* got ticket, try to use it */
    krbval = krb5_rd_req(c, &auth_context, &packet, 
			 princ, NULL, NULL, &ticket);
    if (krbval) {
	if (!have_keys)
	    /* The krb5 errors aren't specified well, but I think
	       these values cover the cases we expect.  */
	    switch (krbval) {
		/* no keytab */
	    case ENOENT:
		/* keytab found, missing entry */
#if 0 /* Don't depend on the nameserver for security.  Assume that if
	 we have a keytab, it must contain the right host/FQDN key.  */
	    case KRB5_KT_NOTFOUND:
#endif
		retval = 0;
		break;
	    default:
		/* unexpected error: fail */
		retval = -1;
		break;
	    }
	else
	    /* Any error here is bad.  */
	    retval = -1;
	com_err("login", krbval, "Unable to verify host ticket");
#ifndef SYSLOG42
	syslog (LOG_NOTICE|LOG_AUTH, "can't verify v5 ticket: %s; %s\n",
		error_message(krbval),
		retval
		? "keytab found, assuming failure"
		: "no keytab found, assuming success");
#endif
	goto EGRESS;
    }
    /*
     * The host/<host> ticket has been received _and_ verified.
     */
    retval = 1;
    /* do cleanup and return */
EGRESS:
    if (auth_context) krb5_auth_con_free(c, auth_context);
    krb5_free_principal(c, princ);
    /* possibly ticket and packet need freeing here as well */
    /* memset (&ticket, 0, sizeof (ticket)); */
    return retval;
}


/*--------------------------------------------------------------------------
COPYRIGHT (c)  1997
THE REGENTS OF THE UNIVERSITY OF MICHIGAN
ALL RIGHTS RESERVED

PERMISSION IS GRANTED TO USE, COPY, CREATE DERIVATIVE WORKS AND REDISTRIBUTE
THIS SOFTWARE AND SUCH DERIVATIVE WORKS FOR ANY PURPOSE, SO LONG AS THE NAME
OF THE UNIVERSITY OF MICHIGAN IS NOT USED IN ANY ADVERTISING OR PUBLICITY
PERTAINING TO THE USE OR DISTRIBUTION OF THIS SOFTWARE WITHOUT SPECIFIC,
WRITTEN PRIOR AUTHORIZATION.  IF THE ABOVE COPYRIGHT NOTICE OR ANY OTHER
IDENTIFICATION OF THE UNIVERSITY OF MICHIGAN IS INCLUDED IN ANY COPY OF ANY
PORTION OF THIS SOFTWARE, THEN THE DISCLAIMER BELOW MUST ALSO BE INCLUDED.

THE SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION FROM THE UNIVERSITY OF
MICHIGAN AS TO ITS FITNESS FOR ANY PURPOSE, AND WITHOUT WARRANTY BY THE
UNIVERSITY OF MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABITILY AND FITNESS FOR A
PARTICULAR PURPOSE.  THE REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE
LIABLE FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING OUT OF OR IN
CONNECTION WITH THE USE OF THE SOFTWARE, EVEN IF IT HAS BEEN OR IS HEREAFTER
ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
--------------------------------------------------------------------------*/
