/* 
 * $Id: krb.c,v 1.4 1996/06/01 18:59:49 ghudson Exp $
 * $Source: /afs/sipb.mit.edu/project/sipb-athena/repository/src/rkinit/rkinitd/krb.c,v $
 * $Author: ghudson $
 *
 * This file contains all of the kerberos part of rkinitd.
 */

#if !defined(lint) && !defined(SABER) && !defined(LOCORE) && defined(RCS_HDRS)
static char *rcsid = "$Id: krb.c,v 1.4 1996/06/01 18:59:49 ghudson Exp $";
#endif /* lint || SABER || LOCORE || RCS_HDRS */

#include <stdio.h>
#include <sys/types.h>
#include <errno.h>
#include <syslog.h>
#include <netinet/in.h>
#include <setjmp.h>
#ifdef SYSV
#include <netdb.h>
#endif
#include <pwd.h>
#include <krb.h>
#include <des.h>

#include <rkinit.h>
#include <rkinit_private.h>
#include <rkinit_err.h>

#include "rkinitd.h"

#define FAILURE (!RKINIT_SUCCESS)

extern int errno;
#ifndef __NetBSD__
extern char *sys_errlist[];
#endif

static char errbuf[BUFSIZ];

typedef struct {
    jmp_buf env;
} rkinitd_intkt_info;

#ifdef __hpux
#define seteuid(e) setresuid(-1, e, -1)
#endif

#if (defined(_AIX) && defined(_IBMR2))

#include <sys/id.h>

/*
 * The RIOS has bizzarre ideas about changing uids around.  They are
 * such that the seteuid and setruid calls here fail.  For this reason
 * we are replacing the seteuid and setruid calls.
 * 
 * The bizzarre ideas are as follows:
 *
 * The effective ID may be changed only to the current real or
 * saved IDs.
 *
 * The saved uid may be set only if the real and effective
 * uids are being set to the same value.
 *
 * The real uid may be set only if the effective
 * uid is being set to the same value.
 */

#ifdef __STDC__
static int setruid(uid_t ruid)
#else
static int setruid(ruid)
  uid_t ruid;
#endif /* __STDC__ */
{
    uid_t euid;

    euid = geteuid();

    if (setuidx(ID_REAL | ID_EFFECTIVE, ruid) == -1)
	return (-1);
    
    return (setuidx(ID_EFFECTIVE, euid));
}


#ifdef __STDC__
static int seteuid(uid_t euid)
#else
static int seteuid(euid)
  uid_t euid;
#endif /* __STDC__ */
{
    uid_t ruid;

    ruid = getuid();

    if (setuidx(ID_SAVED | ID_REAL | ID_EFFECTIVE, euid) == -1)
	return (-1);
    
    return (setruid(ruid));
}


#ifdef __STDC__
static int setreuid(uid_t ruid, uid_t euid)
#else
static int setreuid(ruid, euid)
  uid_t ruid;
  uid_t euid;
#endif /* __STDC__ */
{
    if (seteuid(euid) == -1)
	return (-1);

    return (setruid(ruid));
}


#ifdef __STDC__
static int setuid(uid_t uid)
#else
static int setuid(uid)
  uid_t uid;
#endif /* __STDC__ */
{
    return (setreuid(uid, uid));
}

#endif /* RIOS */


#ifdef __STDC__
static void this_phost(char *host, int hostlen)
#else
static void this_phost(host, hostlen)
  char *host;
  int hostlen;
#endif /* __STDC__ */
{
    char this_host[MAXHOSTNAMELEN + 1];

    BCLEAR(this_host);
    
    if (gethostname(this_host, sizeof(this_host)) < 0) {
	sprintf(errbuf, "gethostname: %s", sys_errlist[errno]);
	rkinit_errmsg(errbuf);
	error();
	exit(1);
    }

    strncpy(host, krb_get_phost(this_host), hostlen - 1);
}

#ifdef __STDC__
static int decrypt_tkt(char *user, char *instance, char *realm, char *arg, 
		       int (*key_proc)(), KTEXT *cipp)
#else
static int decrypt_tkt(user, instance, realm, arg, key_proc, cipp)
  char *user;
  char *instance;
  char *realm;
  char *arg;
  int (*key_proc)();
  KTEXT *cipp;
#endif /* __STDC__ */
{
    MSG_DAT msg_data;		/* Message data containing decrypted data */
    KTEXT_ST auth;		/* Authenticator */
    AUTH_DAT auth_dat;		/* Authentication data */
    KTEXT cip = *cipp;
    MSG_DAT scip;
    int status = 0;
    des_cblock key;
    des_key_schedule sched;
    struct sockaddr_in caddr;	/* client internet address */
    struct sockaddr_in saddr;	/* server internet address */

    rkinitd_intkt_info *rii = (rkinitd_intkt_info *)arg;

    u_char enc_data[MAX_KTXT_LEN];

    SBCLEAR(auth);
    SBCLEAR(auth_dat);
    SBCLEAR(scip);
    BCLEAR(enc_data);

    scip.app_data = enc_data;

    /* 
     * Exchange with the client our response from the KDC (ticket encrypted
     * in user's private key) for the same ticket encrypted in our
     * (not yet known) session key.
     */

    rpc_exchange_tkt(cip, &scip);

    /* 
     * Get the authenticator 
     */

    SBCLEAR(auth);

    rpc_getauth(&auth, &caddr, &saddr);

    /* 
     * Decode authenticator and extract session key.  The first zero
     * means we don't care what host this comes from.  We fetched the key
     * earlier before dropping privileges.
     */

    /* 
     * This function has to use longjmp to return to the caller
     * because the kerberos library routine that calls it doesn't
     * pay attention to the return value it gives.  That means that
     * if any of these routines failed, the error returned to the client
     * would be "password incorrect".
     */

    if (status = krb_rd_req(&auth, (char *)0, (char *)0,
			    caddr.sin_addr.s_addr, &auth_dat, (char *)0)) {
	sprintf(errbuf, "krb_rd_req: %s", krb_err_txt[status]);
	rkinit_errmsg(errbuf);
	longjmp(rii->env, status);
    }

    bcopy(auth_dat.session, key, sizeof(key));
    if (des_key_sched(key, sched)) {
	sprintf(errbuf, "Error in des_key_sched");
	rkinit_errmsg(errbuf);
	longjmp(rii->env, RKINIT_DES);
    }

    /* Decrypt the data. */
    if ((status = 
	 krb_rd_priv((u_char *)scip.app_data, scip.app_length, 
		     sched, key, &caddr, &saddr, &msg_data)) == KSUCCESS) {
	cip->length = msg_data.app_length;
	bcopy(msg_data.app_data, cip->dat, msg_data.app_length);
	cip->dat[cip->length] = 0;
    } 
    else {
	sprintf(errbuf, "krb_rd_priv: %s", krb_err_txt[status]);
	rkinit_errmsg(errbuf);
	longjmp(rii->env, status);
    }
    
    return(status);
}

#ifdef __STDC__
static int validate_user(char *aname, char *inst, char *realm, 
			 char *username, char *errmsg)
#else
static int validate_user(aname, inst, realm, username, errmsg)
  char *aname;
  char *inst;
  char *realm;
  char *username;
  char *errmsg;
#endif /* __STDC__ */
{
    struct passwd *pwnam;	/* For access_check and uid */
    AUTH_DAT auth_dat;
    int kstatus = KSUCCESS;
    char phost[MAXHOSTNAMELEN + 1];
    char lrealm[REALM_SZ];	/* Realm of issuing kerberos */
    unsigned char skey[KKEY_SZ];

    SBCLEAR(auth_dat);

    if ((pwnam = getpwnam(username)) == NULL) {
	sprintf(errmsg, "%s does not exist on the remote host.", username);
	return(FAILURE);
    }

    strcpy(auth_dat.pname, aname);
    strcpy(auth_dat.pinst, inst);
    strcpy(auth_dat.prealm, realm);

    if (seteuid(pwnam->pw_uid) < 0) {
	sprintf(errmsg, "Failure setting euid to %d: %s\n", pwnam->pw_uid, 
		sys_errlist[errno]);
	return(FAILURE);
    }
    kstatus = kuserok(&auth_dat, username);
    if (seteuid(0) < 0) {
	sprintf(errmsg, "Failure setting euid to 0: %s\n", 
		sys_errlist[errno]);
	return(FAILURE);
    }
    
    if (kstatus != KSUCCESS) {
	sprintf(errmsg, "%s has not allowed you to log in with", username);
	if (strlen(auth_dat.pinst))
	    sprintf(errmsg, "%s %s.%s", errmsg, auth_dat.pname, 
		    auth_dat.pinst);
	else
	    sprintf(errmsg, "%s %s", errmsg, auth_dat.pname);
	sprintf(errmsg, "%s@%s tickets.", errmsg, auth_dat.prealm);
	return(FAILURE);
    }
    
    /*
     * Read the service key from the key file before we drop privs.
     */

    BCLEAR(phost);
    this_phost(phost, sizeof(phost));

    if ((kstatus = krb_get_lrealm(lrealm, 1))) {
	sprintf(errmsg, "Failure to fetch realm: %s\n",
		krb_err_txt[kstatus]);
	return(FAILURE);
    }
    if (kstatus = read_service_key(KEY,phost,lrealm,0,KEYFILE,skey)) {
	sprintf(errmsg, "Failure to fetch service key: %s\n",
		krb_err_txt[kstatus]);
	return(FAILURE);
    }
    kstatus = krb_set_key(skey, 0);
    BCLEAR(skey);
    if (kstatus != KSUCCESS) {
	sprintf(errmsg, "Failure to set service key: %s\n",
		krb_err_txt[kstatus]);
	return(FAILURE);
    }

    /* 
     * Set our uid to the user.  We don't need to be root for anything else.
     */
    if (setuid(pwnam->pw_uid) < 0) {
	sprintf(errmsg,	"Failure setting uuid to %d: %s\n", pwnam->pw_uid,
		sys_errlist[errno]);
	return(FAILURE);
    }
    return(RKINIT_SUCCESS);
}

#ifdef __STDC__
int get_tickets(int version)
#else
int get_tickets(version)
  int version;
#endif /* __STDC__ */
{
    rkinit_info info;
    AUTH_DAT auth_dat;

    int status;
    char errmsg[BUFSIZ];	/* error message for client */

    rkinitd_intkt_info rii;

    SBCLEAR(info);
    SBCLEAR(auth_dat);
    BCLEAR(errmsg);
    SBCLEAR(rii);

    rpc_get_rkinit_info(&info);

    /* 
     * The validate_user routine makes sure that the principal in question
     * is allowed to log in as username, and if so, does a setuid(localuid).
     * If there is an access violation or an error in setting the uid,
     * an error is returned and the string errmsg is initialized with 
     * an error message that will be sent back to the client.
     */
    if ((status = validate_user(info.aname, info.inst, info.realm, 
				info.username, errmsg)) != RKINIT_SUCCESS) {
	rpc_send_error(errmsg);
	exit(0);
    }
    else
	rpc_send_success();

    /* 
     * If the name of a ticket file was specified, set it; otherwise, 
     * just use the default. 
     */
    if (strlen(info.tktfilename))
	krb_set_tkt_string(info.tktfilename);
    
    /* 
     * Call internal kerberos library routine so that we can supply
     * our own ticket decryption routine.
     */

    /* 
     * We need a setjmp here because krb_get_in_tkt ignores the
     * return value of decrypt_tkt.  Thus if we want any of its
     * return values to reach the client, we have to jump out of 
     * the routine.
     */

    if (setjmp(rii.env) == 0) {
	if (status = krb_get_in_tkt(info.aname, info.inst, info.realm, 
				    info.sname, info.sinst, info.lifetime,
				    NULL, decrypt_tkt, (char *)&rii)) {
	    strcpy(errmsg, krb_err_txt[status]);
	    rpc_send_error(errmsg);
	}
	else
	    rpc_send_success();
    }
    else
	rpc_send_error(errbuf);
    
    return(RKINIT_SUCCESS);
}
