/* lib_krb.c -- This file contains functions that should be in
 * libkrb.a, * or whatever kerberos library is being used, but which
 * needs to be called * internally, since I don't really get the
 * functionality of the kerberos * library that I wan't to have....  
 * Therefore I have created these functions in order to get the 
 * functionality I needed.  A lot of it is taken out of the kerberos 
 * library.
 *
 * Created by: Derek Atkins <warlord@MIT.EDU>
 *
 * $Source: /afs/net.mit.edu/user/warlord/Thesis/src/lib/RCS/lib_krb.c,v $
 * $Author: warlord $
 *
 */

#include <warlord-copyright.h>

#if !defined(lint) && !defined(SABER)
static char rcsid_lib_krb_c[] = "$Id: lib_krb.c,v 1.5 93/12/11 15:15:21 warlord Exp $";
#endif

#include <sys/time.h>
#include <krb.h>
#include <des.h>
#include <charon_prot.h>
#include <charon_err.h>
#include <krb_prot.h>
#include <stdio.h>

extern int krb_debug;

/* Requires:	ticket cache to have been initialized already.
 * Effects: 	takes a ticket an stores it in the ticket cache file.
 *		If init_cache == 1, then initialize the ticket cache.
 *		If ticket == 0, then use charon->tkt2, else use
 *		ticket->rcmd.
 *
 *		This was taken from krb_get_in_tkt.c
 */
int
crn_store_ticket(charon_t *charon, int ticket, int init_cache)
{
  char *ptr;
  KTEXT cip;
  C_Block ses;			/* Session key for tkt */
  char s_name[SNAME_SZ];
  char s_instance[INST_SZ];
  char rlm[REALM_SZ];
  int lifetime;
  int kvno;			/* Kvno for session key */
  KTEXT_ST tkt_st;
  KTEXT tkt = &tkt_st;		/* Current ticket */
  struct timeval t_local;
  int kerror;

  if (!ticket)
    cip = charon->tkt2;
  else
    cip = charon->rcmd;

  ptr = (char *) cip->dat;
  
  /* extract session key */
  COPY(ptr,ses,8);
  ptr += 8;
  
  if ((strlen(ptr) + (ptr - (char *) cip->dat)) > cip->length)
    return(CRN_BADPW);
  
  /* extract server's name */
  (void) strncpy(s_name,ptr, sizeof(s_name)-1);
  s_name[sizeof(s_name)-1] = '\0';
  ptr += strlen(s_name) + 1;
  
  if ((strlen(ptr) + (ptr - (char *) cip->dat)) > cip->length)
    return(CRN_BADPW);
  
  /* extract server's instance */
  (void) strncpy(s_instance,ptr, sizeof(s_instance)-1);
  s_instance[sizeof(s_instance)-1] = '\0';
  ptr += strlen(s_instance) + 1;
  
  if ((strlen(ptr) + (ptr - (char *) cip->dat)) > cip->length)
    return(CRN_BADPW);
  
  /* extract server's realm */
  (void) strncpy(rlm,ptr, sizeof(rlm));
  rlm[sizeof(rlm)-1] = '\0';
  ptr += strlen(rlm) + 1;
  
  /* extract ticket lifetime, server key version, ticket length */
  /* be sure to avoid sign extension on lifetime! */
  lifetime = (unsigned char) ptr[0];
  kvno = (unsigned char) ptr[1];
  tkt->length = (unsigned char) ptr[2];
  ptr += 3;
  
  if ((tkt->length < 0) ||
      ((tkt->length + (ptr - (char *) cip->dat)) > cip->length))
    return(CRN_BADPW);
  
  /* extract ticket itself */
  COPY(ptr,(tkt->dat),tkt->length);
  ptr += tkt->length;
  
  /* Pass KDC time stamp -- I'm using the Cookie to verify it. */
  ptr += 4;
  
  /* initialize ticket cache, if we should */
  if (init_cache) {
    char ticketfile[200];
    /* First, set the ticket file name */

    sprintf(ticketfile, "%s%s.crn", TMP_TKT_FILE, charon->principal);
    krb_set_tkt_string(ticketfile);

    if (in_tkt(charon->principal,charon->instance) != KSUCCESS)
      return(INTK_ERR);

    charon->obtain_ticket = 1;
  }
  
  /* Get local time */
  (void) gettimeofday(&t_local,(struct timezone *) 0);
  
  /* stash ticket, session key, etc. for future use */
  if ((kerror = save_credentials(s_name, s_instance, rlm, ses,
				 lifetime, kvno, tkt, t_local.tv_sec)) 
      != KSUCCESS)
    return(kerror);
  
  return(CRN_OK);
}

/* This is in the kerberos library */
extern int swap_bytes;

/* Effects:	This will get an rcmd ticket from the kerberos server
 *	by going across the rpc connection and asking for it.  It will
 *	then store that ticket in a file.
 *	returns CRN_OK on success, or an error.
 */
int
crn_get_rcmd_tkt(charon_t *charon)
{
  KTEXT_ST pkt_st;
  KTEXT pkt = & pkt_st;	/* Packet to KDC */
  KTEXT_ST rpkt_st;
  KTEXT rpkt = &rpkt_st;	/* Returned packet */
  KTEXT cip = charon->rcmd;	/* Returned Ciphertext */
  CREDENTIALS cr;
  Key_schedule key_s;
  long time_ws = 0;
  int msg_byte_order;
  int kerror;
  unsigned long rep_err_code;
  static struct timeval tt_local = { 0, 0 };
  
  BCLEAR(pkt);
  BCLEAR(rpkt);

  /* Reset this variable */
  swap_bytes = 0;  

  /* Create skeleton of packet to be sent */
  (void) gettimeofday(&tt_local,(struct timezone *) 0);
  
  pkt->length = 0;
  
  /*
   * Look for the session key (and other stuff we don't need)
   * in the ticket file for krbtgt.realm@lrealm where "realm" 
   * is the service's realm (passed in "realm" argument) and 
   * lrealm is the realm of our initial ticket.  If we don't 
   * have this, we will try to get it.
   */
  
  if ((kerror = krb_get_cred(KRBTGT,charon->hostrealm,charon->realm,&cr)) 
      != KSUCCESS) {
	return(kerror);
      }
  
  /*
   * Make up a request packet to the "krbtgt.realm@lrealm".
   * Start by calling krb_mk_req() which puts ticket+authenticator
   * into "pkt".  Then tack other stuff on the end.
   */
  
  kerror = krb_mk_req(pkt,KRBTGT,charon->hostrealm,charon->realm,0L);
  
  if (kerror)
    return(AD_NOTGT);
  
  /* timestamp */
  COPY(&time_ws,(pkt->dat+pkt->length),4);
  pkt->length += 4;
  *(pkt->dat+(pkt->length)++) = (char) 1;
  (void) strcpy((char *) (pkt->dat+pkt->length),RCMD);
  pkt->length += 1 + strlen(RCMD);
  (void) strcpy((char *)(pkt->dat+pkt->length),charon->hostinst);
  pkt->length += 1 + strlen(charon->hostinst);
  
  rpkt->length = 0;
  
  /* Send the request to the local ticket-granting server 
   * THIS IS SENT VIA THE RPC PORT 
   *
   * Also, I *know* that this is only going to be a client!
   */
  crn_rpc_put_ktext(pkt, CLIENT_MODE);
  if ((kerror = crn_get_status()) != CRN_OK)
    return(kerror);
  crn_rpc_get_ktext(rpkt);

  /* check packet version of the returned packet */
  if (pkt_version(rpkt) != KRB_PROT_VERSION )
    return(CRN_KRB_PROT);
  
  /* Check byte order */
  msg_byte_order = pkt_msg_type(rpkt) & 1;
  swap_bytes = 0;
  if (msg_byte_order != HOST_BYTE_ORDER)
    swap_bytes++;
  
  switch (pkt_msg_type(rpkt) & ~1) {
  case AUTH_MSG_KDC_REPLY:
    break;
  case AUTH_MSG_ERR_REPLY:
    COPY(pkt_err_code(rpkt), &rep_err_code, 4);
    if (swap_bytes)
      swap_u_long(rep_err_code);
    return(rep_err_code);
    
  default:
    return(CRN_KRB_PROT);
  }
  
  /* Extract the ciphertext */
  cip->length = pkt_clen(rpkt);       /* let clen do the swap */
  
  COPY(pkt_cipher(rpkt),(cip->dat),cip->length);
  
#ifndef NOENCRYPTION
  /* Attempt to decrypt it */
  
  key_sched(cr.session,key_s);
  if (krb_debug)  printf("About to do decryption ...");
  pcbc_encrypt((C_Block *)cip->dat,(C_Block *)cip->dat,
	       (long) cip->length,key_s,cr.session,0);
#endif /* !NOENCRYPTION */
  /* Get rid of all traces of key */
  BCLEAR(cr.session);
  BCLEAR(key_s);
  
  /* Continue to parse this ticekt and then store it */
  return(crn_store_ticket(charon, 1, 0));
}
