/* 
 * $Id: krb.c,v 1.9 90/10/05 15:02:24 qjb Exp $
 * $Source: /afs/athena.mit.edu/user/q/qjb/source/qrpc/src/RCS/krb.c,v $
 * $Author: qjb $
 *
 * This file contains code to deal with kerberos.
 */

#if !defined(lint) && !defined(SABER) && !defined(RCS_HDRS)
static char *rcsid = "$Id: krb.c,v 1.9 90/10/05 15:02:24 qjb Exp $";
#endif /* lint || SABER || RCS_HDRS */

#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <krb.h>
#include <des.h>

#include "qrpc.h"
#include "qrpc_private.h"

#ifdef __STDC__
qrpc_error_t qrpc_sendauth(qrpc_t qrpc, KTEXT auth)
#else
qrpc_error_t qrpc_sendauth(qrpc, auth)
  qrpc_t qrpc;
  KTEXT auth;
#endif /* __STDC__ */
{
    return(qrpci_send_packet(qrpc, QRPC_OP_AUTH, 
			     (qrpc_data_t) auth->dat,
			     (qrpc_length_t) auth->length));
}

#ifdef __STDC__
qrpc_error_t qrpc_recvauth(qrpc_t qrpc, KTEXT auth)
#else
qrpc_error_t qrpc_recvauth(qrpc, auth)
  qrpc_t qrpc;
  KTEXT auth;
#endif /* __STDC__ */
{
    auth->length = MAX_KTXT_LEN;
    return(qrpci_recv_packet(qrpc, QRPC_OP_AUTH,
			     (qrpc_data_t) auth->dat,
			     (qrpc_length_t *) &(auth->length)));
}

#ifdef __STDC__
qrpc_error_t qrpc_auth_to_server(qrpc_t qrpc, char *aname, char *krealm,
			       des_cblock sess_key, des_key_schedule sched)
  /*
   * Requires:
   *   sess_key and sched point to valid des_cblock and des_key_schedule
   *   objects.  Caller is client.
   * Modifies:
   *   sess_key, sched
   * Effects:
   *   Creates an authenticator whose name is aname, whose instance
   *   is krb_get_phost(<server host>), and whose realm is the krealm
   *   if krealm is non-null and non-empty, or the realm of the remote
   *   host as determined by krb_realmofhost otherwise.  Only the
   *   first ANAME_SZ and REALM_SZ (as defined in krb.h) characters of
   *   aname and krealm are used.  The authenticator created is then
   *   send to the server.  If the server decrypts it succesfully, the
   *   session key and key_schedule are saved and returned to the
   *   user.  The server should call qrpc_auth_from_client to receive
   *   this call.
   */
#else
qrpc_error_t qrpc_auth_to_server(qrpc, aname, krealm, sess_key, sched)
  qrpc_t qrpc;
  char *aname;
  char *krealm;
  des_cblock sess_key;
  des_key_schedule sched;
#endif /* __STDC__ */
{
    if (qrpc->client_or_server == QRPC_IS_SERVER) {
	sprintf(qrpc->errmsg, 
		"qrpc_auth_to_server may be called from the client only");
	return(QRPC_ERR_APP);
    }
    else if (qrpc->client_or_server != QRPC_IS_CLIENT) {
	sprintf(qrpc->errmsg, "%s (%d)", 
		"qrpc_auth_to_server detects illegal client_or_server value",
		qrpc->client_or_server);
	return(QRPC_ERR_INTERNAL);
    }
    else {
	KTEXT_ST auth;
	CREDENTIALS cred;
	char name[ANAME_SZ + 1];
	char inst[INST_SZ + 1];
	char realm[REALM_SZ + 1];
	
	qrpc_error_t status = QRPC_SUCCESS;
	qrpc_length_t length;
	int kstatus = KSUCCESS;

	SBCLEAR(auth);
	SBCLEAR(cred);
	BCLEAR(name);
	BCLEAR(inst);
	BCLEAR(realm);

	/* Take care of name */
	(void) strncpy(name, aname, ANAME_SZ);

	/* Take care of inst */
	sprintf(qrpc->errmsg, "host: %s", qrpc->setup.client.host);
	qrpci_dprint(qrpc, qrpc->errmsg);

	(void) strncpy(inst, krb_get_phost(qrpc->setup.client.host), INST_SZ);

	sprintf(qrpc->errmsg, "inst: %s", inst);
	qrpci_dprint(qrpc, qrpc->errmsg);

	/* Take care of realm */
	if ((krealm != NULL) && (krealm[0] != 0))
	    strncpy(realm, krealm, REALM_SZ);
	else
	    strncpy(realm, krb_realmofhost(qrpc->setup.client.fullhost), 
		    REALM_SZ);
	
	if (kstatus = krb_mk_req(&auth, name, inst, realm, 0)) {
	    sprintf(qrpc->errmsg, "krb_mk_req (%s%s%s@%s): %s",
		    name, (inst[0]) ? "." : "", inst, realm,
		    krb_err_txt[kstatus]);
	    return(QRPC_ERR_KERBEROS);
	}

	if (status = qrpc_sendauth(qrpc, &auth)) 
	    return (status);

	length = sizeof(qrpc->errmsg);
	if (status = qrpci_recv_status(qrpc, (qrpc_data_t) qrpc->errmsg,
				       &length))
	    return (status);

	if (length != 0)
	    return(QRPC_ERR_REMOTE);

	/* 
	 * If we've made it this far, initialize and return the session key. 
	 */

	/* Get credentials from ticket file */
	if (kstatus = krb_get_cred(name, inst, realm, &cred)) {
	    sprintf(qrpc->errmsg, "krb_get_cred: %s", krb_err_txt[kstatus]);
	    return(QRPC_ERR_KERBEROS);
	}

	/* Extract the session key and make the schedule */
	bcopy(cred.session, sess_key, sizeof(des_cblock));
	if (des_key_sched(sess_key, sched) != KSUCCESS) {
	    sprintf(qrpc->errmsg, "des_key_sched failed");
	    return(QRPC_ERR_DES);
	}

	return(QRPC_SUCCESS);
    }
}

#ifdef __STDC__
qrpc_error_t qrpc_auth_from_client(qrpc_t qrpc, char *aname, char *keyfile,
				   AUTH_DAT *auth_dat, 
				   des_cblock sess_key, des_key_schedule sched)
  /* 
   * Requires:
   *   aname and keyfile are initialized.  auth_dat points to a valid
   *   AUTH_DAT object.  sess_key and sched are valid objects.  Caller
   *   is server.
   * Modifies:
   *   auth_dat
   * Effects:
   *   Receives an authenticator from the client and attempts to
   *   decode it using aname.this_host@determined_realm as the
   *   principal (where "determined_realm" is deteremined by the
   *   kerberos library) and keyfile as the keyfile.  If keyfile is
   *   null or the empty string, KEYFILE as defined in krb.h is used.
   *   Sends a status message to the client.  If the authenticator is
   *   succesfully decoded, the authentication data, session key, and
   *   key schedule are saved are returned to the caller.
   */
#else
qrpc_error_t qrpc_auth_from_client(qrpc, aname, keyfile, 
				   auth_dat, sess_key, sched)
  qrpc_t qrpc;
  char *aname;
  char *keyfile;
  AUTH_DAT *auth_dat;
  des_cblock sess_key;
  des_key_schedule sched;
#endif /* __STDC__ */
{
    if (qrpc->client_or_server == QRPC_IS_CLIENT) {
	sprintf(qrpc->errmsg, 
		"qrpc_auth_from_client may be called from the server only");
	return(QRPC_ERR_APP);
    }
    else if (qrpc->client_or_server != QRPC_IS_SERVER) {
	sprintf(qrpc->errmsg, "%s %s (%d)",
		"qrpc_auth_from_client detects", 
		"illegal client_or_server value", qrpc->client_or_server);
	return(QRPC_ERR_INTERNAL);
    }
    else {
	KTEXT_ST auth;
	CREDENTIALS cred;
	char name[ANAME_SZ + 1];
	char inst[INST_SZ + 1];

	char this_host[MAXHOSTNAMELEN + 1];

	qrpc_error_t status = QRPC_SUCCESS;
	int kstatus = KSUCCESS;
	int dstatus = KSUCCESS;
  
	SBCLEAR(auth);
	SBCLEAR(cred);
	BCLEAR(name);
	BCLEAR(inst);
	BCLEAR(this_host);

	if (status = qrpc_recvauth(qrpc, &auth))
	    return (status);
    
	qrpci_dprint(qrpc, "Got authenticator");

	/* Get keyfile */
	if ((keyfile == NULL) || (keyfile[0] == 0)) 
	    keyfile = KEYFILE;

	/* Get name */
	strncpy(name, aname, ANAME_SZ);
	
	/* Get instance */
	if (gethostname(this_host, sizeof(this_host) - 1) < 0) {
	    sprintf(qrpc->errmsg, "Error while getting local host name: %s",
		    sys_errlist[errno]);
	    return (QRPC_ERR_GETHOSTNAME);
	}
	else
	    strncpy(inst, krb_get_phost(this_host), INST_SZ);
	    
	/* Decode authenticator */
	if (kstatus = krb_rd_req(&auth, name, inst, 
				 qrpc->caddr.sin_addr.s_addr,
				 auth_dat, keyfile)) 
	    sprintf(qrpc->errmsg, "krb_rd_req (%s%s%s): %s", 
		    name, (inst[0]) ? "." : "", inst,
		    krb_err_txt[kstatus]);
	
	/* Extract session key and create schedule */
	
	qrpci_dprint(qrpc, "Getting session key");
	
	if (kstatus == KSUCCESS) {
	    /* Extract the session key and make the schedule */
	    bcopy(auth_dat->session, sess_key, sizeof(des_cblock));
	    if (dstatus = des_key_sched(sess_key, sched))
		sprintf(qrpc->errmsg, "des_key_sched failed");
	}

	qrpci_dprint(qrpc, "Sending response");

	if (dstatus != KSUCCESS) 
	    qrpci_send_error(qrpc, qrpc->errmsg);
	else if (kstatus == KSUCCESS) {
	    qrpci_dprint(qrpc, "Sending success");
	    status = qrpci_send_success(qrpc);
	}
	else {
	    qrpci_dprint(qrpc, "Sending failure");
	    status = qrpci_send_error(qrpc, qrpc->errmsg);
	}

	qrpci_dprint(qrpc, "Exiting auth_from_client");

	if (status)
	    return (status);

	if (dstatus)
	    return (QRPC_ERR_DES);

	if (kstatus)

	    return (QRPC_ERR_KERBEROS);

	return (QRPC_SUCCESS);
    }
}
