#include "krb_locl.h"

RCSID("$Id: krb_get_in_tkt.c,v 1.13 1996/05/31 21:00:14 assar Exp $");

/*
 * decrypt_tkt(): Given user, instance, realm, passwd, key_proc
 * and the cipher text sent from the KDC, decrypt the cipher text
 * using the key returned by key_proc.
 */

static int
decrypt_tkt(char *user, char *instance, char *realm,
	    void *arg, key_proc_t key_proc, KTEXT *cip)
{
    des_cblock key;		/* Key for decrypting cipher */
    int ret;

    ret = key_proc(user, instance, realm, arg, &key);
    if (ret != 0)
	return ret;
    
    encrypt_ktext(*cip, &key, DES_DECRYPT);

    memset(&key, 0, sizeof(key));
    return 0;
}

/*
 * krb_get_in_tkt() gets a ticket for a given principal to use a given
 * service and stores the returned ticket and session key for future
 * use.
 *
 * The "user", "instance", and "realm" arguments give the identity of
 * the client who will use the ticket.  The "service" and "sinstance"
 * arguments give the identity of the server that the client wishes
 * to use.  (The realm of the server is the same as the Kerberos server
 * to whom the request is sent.)  The "life" argument indicates the
 * desired lifetime of the ticket; the "key_proc" argument is a pointer
 * to the routine used for getting the client's private key to decrypt
 * the reply from Kerberos.  The "decrypt_proc" argument is a pointer
 * to the routine used to decrypt the reply from Kerberos; and "arg"
 * is an argument to be passed on to the "key_proc" routine.
 *
 * If all goes well, krb_get_in_tkt() returns INTK_OK, otherwise it
 * returns an error code:  If an AUTH_MSG_ERR_REPLY packet is returned
 * by Kerberos, then the error code it contains is returned.  Other
 * error codes returned by this routine include INTK_PROT to indicate
 * wrong protocol version, INTK_BADPW to indicate bad password (if
 * decrypted ticket didn't make sense), INTK_ERR if the ticket was for
 * the wrong server or the ticket store couldn't be initialized.
 *
 * The format of the message sent to Kerberos is as follows:
 *
 * Size			Variable		Field
 * ----			--------		-----
 *
 * 1 byte		KRB_PROT_VERSION	protocol version number
 * 1 byte		AUTH_MSG_KDC_REQUEST |	message type
 *			HOST_BYTE_ORDER		local byte order in lsb
 * string		user			client's name
 * string		instance		client's instance
 * string		realm			client's realm
 * 4 bytes		tlocal.tv_sec		timestamp in seconds
 * 1 byte		life			desired lifetime
 * string		service			service's name
 * string		sinstance		service's instance
 */

int
krb_get_in_tkt(char *user, char *instance, char *realm, 
	       char *service, char *sinstance, int life,
	       key_proc_t key_proc, decrypt_proc_t decrypt_proc, void *arg)
{
    KTEXT_ST pkt_st;
    KTEXT pkt = &pkt_st;	/* Packet to KDC */
    KTEXT_ST rpkt_st;
    KTEXT rpkt = &rpkt_st;	/* Returned packet */

    int kerror;
    struct timeval tv;

    /* BUILD REQUEST PACKET */

    unsigned char *p = pkt->dat;
    
    p += put_int(KRB_PROT_VERSION, p, 1);
    p += put_int(AUTH_MSG_KDC_REQUEST, p, 1);

    p += put_nir(user, instance, realm, p);

    gettimeofday(&tv, NULL);
    p += put_int(tv.tv_sec, p, 4);
    p += put_int(life, p, 1);

    p += put_nir(service, sinstance, NULL, p);

    pkt->length = p - pkt->dat;

    rpkt->length = 0;

    /* SEND THE REQUEST AND RECEIVE THE RETURN PACKET */

    if ((kerror = send_to_kdc(pkt, rpkt, realm))) return(kerror);
    
    p = rpkt->dat;
    
    {
	CREDENTIALS cred;
	KTEXT_ST cip;
	KTEXT foo = &cip; /* braindamage */
	
	kerror = kdc_reply_cipher(rpkt, &cip);
	if(kerror != KSUCCESS)
	    return kerror;

	if (decrypt_proc == NULL)
	    decrypt_proc = decrypt_tkt;
	(*decrypt_proc)(user, instance, realm, arg, key_proc, &foo);

	kerror = kdc_reply_cred(&cip, &cred);
	if(kerror != KSUCCESS)
	    return kerror;
	
	if (strcmp(cred.service, service) || 
	    strcmp(cred.instance, sinstance) ||
	    strcmp(cred.realm, realm))	/* not what we asked for */
	    return INTK_ERR;	/* we need a better code here XXX */
	
	if (abs((int)(tv.tv_sec - cred.issue_date)) > CLOCK_SKEW) {
	    return RD_AP_TIME; /* XXX should probably be better code */
	}

	/* initialize ticket cache */

	if (tf_create(TKT_FILE) != KSUCCESS)
	    return INTK_ERR;

	if (tf_put_pname(user) != KSUCCESS ||
	    tf_put_pinst(instance) != KSUCCESS) {
	    tf_close();
	    return INTK_ERR;
	}

	kerror = tf_save_cred(cred.service, cred.instance, cred.realm, 
			      cred.session, cred.lifetime, cred.kvno,
			      &cred.ticket_st, cred.issue_date);
	tf_close();
    
	return kerror;
    }
}
