view usr/src/cmd/keyserv/setkey.c @ 0:c9caec207d52 b86

Initial porting based on b86
author Koji Uno <koji.uno@sun.com>
date Tue, 02 Jun 2009 18:56:50 +0900
parents
children 1a15d5aaf794
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

#pragma ident	"@(#)setkey.c	1.21	05/09/30 SMI"

/*
 * Do the real work of the keyserver.
 * Store secret keys. Compute common keys,
 * and use them to decrypt and encrypt DES keys.
 * Cache the common keys, so the expensive computation is avoided.
 */
#include <stdio.h>
#include <stdlib.h>
#include <mp.h>
#include <rpc/rpc.h>
#include <rpc/key_prot.h>
#include <rpc/des_crypt.h>
#include <rpcsvc/nis_dhext.h>
#include <sys/errno.h>
#include <string.h>
#include <thread.h>
#include <syslog.h>

#include "debug.h"
#include "keyserv_cache.h"

extern char ROOTKEY[];
extern mechanism_t **mechs;
extern char **cache_options;
extern int *cache_size;
extern int disk_caching;

static MINT *MODULUS;
static int hash_keys();
static keystatus pk_crypt();
static keystatus pk_crypt3();
static int nodefaultkeys = 0;

#define	DES		"des"
#define	DESALIAS	"dh192-0"
#define	DHMECHSTR	"diffie_hellman"
#define	CLASSIC_PK_DH(k, a)	(((k) == 192) && ((a) == 0))

/*
 * Exponential caching management
 */
struct cachekey_list {
	keybuf secret;
	keybuf public;
	des_block deskey;
	struct cachekey_list *next;
};
#define	KEY_HASH_SIZE	256
static struct cachekey_list *g_cachedkeys[KEY_HASH_SIZE];
static rwlock_t g_cachedkeys_lock = DEFAULTRWLOCK;

#ifdef DEBUG
int
test_debug(debug_level level, char *file, int line)
{
	if (level < debugging)
		return (0);
	fprintf(stderr, "file %s,\tline %d :\t", file, line);
	return (1);
}

int
real_debug(char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	(void) vfprintf(stderr, fmt, args);
	va_end(args);
	fprintf(stderr, "\n");
	fflush(stderr);
	return (1);
}
#endif /* DEBUG */

struct cacheuid_list {
	uid_t uid;
	int refcnt;
	keybuf3 *secretkey;
	keybuf3 *publickey;
	netnamestr netname;
	des_block key;
	struct cacheuid_list *next;
};

#define	NUMHASHBUCKETS	256
#define	HASH_UID(x) (x & 0xff)

struct mechdata {
	struct cacheuid_list *bucket[NUMHASHBUCKETS];
};

struct psdata {
	struct cachekey3_list *common[NUMHASHBUCKETS];
};

struct mechentry {
	mutex_t mech_lock;
	struct mechdata *mechdata;
	mutex_t ps_lock;
	struct psdata *psdata;
};

/*
 * we don't need to worry about locking for the keylen + algtype
 * sparse array because it is created once and for all during
 * initialization when there are no threads. The mechentry field
 * and everything underneath it needs protection and this is what
 * the *_lock fields are for.
 */
struct algtypelist {
	algtype_t algtype;
	struct algtypelist *next;
	struct mechentry mech;
};

struct keylenlist {
	keylen_t keylen;
	struct algtypelist *ap;
	struct keylenlist *next;
};

#define	KEYSERV_VERSION	"1.0"

static struct mechtable {
	char *version;
	struct keylenlist *kp;
} mechtable = {KEYSERV_VERSION, NULL};

static struct keylenlist **
getkeylen(keylen_t k)
{
	struct keylenlist **kpp;

	debug(KEYSERV_DEBUG1, ("getkeylen key: %d", k));
	for (kpp = &mechtable.kp;
		*kpp != NULL && (*kpp)->keylen != k;
		kpp = &(*kpp)->next)
		debug(KEYSERV_DEBUG0, ("getkeylen failed %x", kpp));
	debug(KEYSERV_DEBUG0, ("getkeylen return: %x", kpp));
	return (kpp);
}

static void
appendkeylist(struct keylenlist **kpp, keylen_t k)
{
	struct keylenlist *kp;

	if (*kpp == NULL) {
		kp = (struct keylenlist *)malloc(sizeof (*kp));
		if (kp == NULL) {
			debug(KEYSERV_INFO, ("appendkeylist : malloc failed"));
			return;
		}
		debug(KEYSERV_DEBUG, ("appendkeylist : %x %x %d", kpp, kp, k));
		kp->keylen = k;
		kp->ap = NULL;
		kp->next = NULL;
		*kpp = kp;
	} else {
		/*EMPTY*/
		/* do nothing; only happens for multiple algtypes */
		debug(KEYSERV_DEBUG0,
			("appendkeylist called for non tail element"));
	}
}

static struct algtypelist **
getalgtype(struct keylenlist **kpp, algtype_t a)
{
	struct algtypelist **app;

	debug(KEYSERV_DEBUG1, ("getalgtype key: %d", a));
	for (app = &(*kpp)->ap;
		*app != NULL && (*app)->algtype != a;
		app = &(*app)->next)
		debug(KEYSERV_DEBUG0, ("getalgtype key: %x", app));
	debug(KEYSERV_DEBUG0, ("getalgtype return: %x", app));
	return (app);
}

static void
appendalgtype(struct algtypelist **app, algtype_t a)
{
	struct algtypelist *ap;

	if (*app == NULL) {
		ap = (struct algtypelist *)malloc(sizeof (*ap));
		if (ap == NULL) {
			debug(KEYSERV_INFO, ("appendalgtype : malloc failed"));
			return;
		}
		debug(KEYSERV_DEBUG, ("appendalgtype : %x %x %d", app, ap, a));
		ap->algtype = a;
		mutex_init(&ap->mech.mech_lock, USYNC_THREAD, NULL);
		mutex_init(&ap->mech.ps_lock, USYNC_THREAD, NULL);
		ap->mech.mechdata = NULL;
		ap->mech.psdata = NULL;
		ap->next = NULL;
		*app = ap;
	} else {
		/*EMPTY*/
		/* don't mind duplicate (keylen,algtype) paris for now. */
		debug(KEYSERV_DEBUG0,
			("appendalgtype called for non tail element"));
	}
}

static struct mechentry *
getmechtype(keylen_t k, algtype_t a)
{
	struct keylenlist **kpp;
	struct algtypelist **app;

	debug(KEYSERV_DEBUG1, ("getmechtype %d %d", k, a));
	kpp = getkeylen(k);
	if (*kpp == NULL) {
		debug(KEYSERV_DEBUG0, ("getmechtype %d not found in keys", k));
		return (0);
	}
	app = getalgtype(kpp, a);
	if (*app == NULL) {
		debug(KEYSERV_DEBUG0, ("getmechtype %d not found in algs", a));
		return (0);
	}
	debug(KEYSERV_DEBUG0, ("getmechtype found %x", app));
	debug(KEYSERV_DEBUG0, ("getmechtype return %x", &(*app)->mech));
	return (&(*app)->mech);
}

static keybuf3 *
getkeybuf3(int k)
{
	keybuf3 *buf;

	debug(KEYSERV_DEBUG, ("getkeybuf3 malloc %d", k));
	buf = (keybuf3 *) malloc(sizeof (*buf));
	if (buf == NULL) {
		debug(KEYSERV_DEBUG, ("getkeybuf3 malloc failed"));
		syslog(LOG_ERR, "file %s line %d: malloc failed",
			__FILE__, __LINE__);
		return (NULL);
	}
	buf->keybuf3_len = k;
	/* XXX special case k==0 */
	if (k == 0) {
		buf->keybuf3_val = NULL;
	} else {
		buf->keybuf3_val = (char *)malloc(k);
		if (buf->keybuf3_val == NULL) {
			debug(KEYSERV_DEBUG, ("getkeybuf3 malloc failed"));
			free(buf);
			syslog(LOG_ERR, "file %s line %d: malloc failed",
				__FILE__, __LINE__);
			return (NULL);
		}
	}
	debug(KEYSERV_DEBUG1, ("getkeybuf3 ret %x", buf));
	return (buf);
}

static void
freekeybuf3(keybuf3 *kp)
{
	debug(KEYSERV_DEBUG1, ("freekeybuf3 %x", kp));
	if (kp == NULL)
		return;
	if (kp->keybuf3_val) {
		/* XXX kp->keybuf3_len != 0? */
		free(kp->keybuf3_val);
	}
	free(kp);
}

static keybuf3 *
cpykeybuf3(keybuf3 *src)
{
	keybuf3 *dst;

	if (src == NULL) {
		return (NULL);
	}
	if ((dst = getkeybuf3(src->keybuf3_len)) == NULL) {
		return (NULL);
	}
	memcpy(dst->keybuf3_val, src->keybuf3_val, src->keybuf3_len);
	debug(KEYSERV_DEBUG0, ("cpykeybuf3 ret %x", dst));
	return (dst);
}

static keybuf3 *
setkeybuf3(char *src, int len)
{
	keybuf3 *dst;

	if ((dst = getkeybuf3(++len)) == NULL) {
		return (NULL);
	}
	memcpy(dst->keybuf3_val, src, len);
	return (dst);
}

static int
cmpkeybuf3(keybuf3 *k1, keybuf3 *k2)
{
	if ((k1 == NULL) || (k2 == NULL)) {
		syslog(LOG_ERR, "cmpkeybuf3: invalid parameter: %x, %x",
			k1, k2);
		return (0);
	}
	if (k1->keybuf3_len != k2->keybuf3_len) {
		return (0);
	}
	return (!memcmp(k1->keybuf3_val, k2->keybuf3_val, k1->keybuf3_len));
}

static int
storekeybuf3(keybuf3 *dst, keybuf3 *src)
{
	keybuf3 *tmp;

	if ((tmp = cpykeybuf3(src)) == NULL) {
		return (0);
	}
	*dst = *tmp;
	free(tmp); /* but not the contents */
	debug(KEYSERV_DEBUG0, ("storekeybuf3 ret %d %x",
		dst->keybuf3_len, dst->keybuf3_val));
	return (1);
}

static deskeyarray *
getdeskeyarray(int k)
{
	deskeyarray *buf;

	debug(KEYSERV_DEBUG, ("getdeskeyarray malloc %d", k));
	buf = (deskeyarray *) malloc(sizeof (*buf));
	if (buf == NULL) {
		debug(KEYSERV_DEBUG, ("getdeskeyarray malloc failed"));
		syslog(LOG_ERR, "file %s line %d: malloc failed",
			__FILE__, __LINE__);
		return (NULL);
	}
	buf->deskeyarray_len = k;
	/* XXX special case k==0 */
	if (k == 0) {
		buf->deskeyarray_val = NULL;
	} else {
		buf->deskeyarray_val = (des_block *)
			malloc(k * sizeof (des_block));
		if (buf->deskeyarray_val == NULL) {
			debug(KEYSERV_DEBUG, ("getdeskeyarray malloc failed"));
			free(buf);
			syslog(LOG_ERR, "file %s line %d: malloc failed",
				__FILE__, __LINE__);
			return (NULL);
		}
	}
	debug(KEYSERV_DEBUG1, ("getdeskeyarray ret %x", buf));
	return (buf);
}

static deskeyarray *
cpydeskeyarray(deskeyarray *src)
{
	deskeyarray *dst;

	if (src == NULL) {
		return (NULL);
	}
	if ((dst = getdeskeyarray(src->deskeyarray_len)) == NULL) {
		return (NULL);
	}
	memcpy(dst->deskeyarray_val, src->deskeyarray_val,
		src->deskeyarray_len * sizeof (des_block));
	debug(KEYSERV_DEBUG0, ("cpydeskeyarray ret %x", dst));
	return (dst);
}

static int
storedeskeyarray(deskeyarray *dst, deskeyarray *src)
{
	deskeyarray *tmp;

	if ((tmp = cpydeskeyarray(src)) == NULL) {
		return (0);
	}
	*dst = *tmp;
	free(tmp); /* but not the contents */
	debug(KEYSERV_DEBUG0, ("storedeskeyarray ret %d %x",
		dst->deskeyarray_len, dst->deskeyarray_val));
	return (1);
}

int
setdeskeyarray(deskeyarray *dst, int k)
{
	deskeyarray *tmp;

	if ((tmp = getdeskeyarray(k)) == NULL) {
		return (0);
	}
	*dst = *tmp;
	free(tmp); /* but not the contents */
	debug(KEYSERV_DEBUG0, ("setdeskeyarray ret %d %x",
		dst->deskeyarray_len, dst->deskeyarray_val));
	return (1);
}

static int
cachehit3(keybuf3 *public, keybuf3 *secret, struct cachekey3_list *cp)
{
	return (cmpkeybuf3(public, cp->public) &&
		cmpkeybuf3(secret, cp->secret));
}

static struct cacheuid_list **
mapuid2cache(uid_t uid, struct mechdata *mdp)
{
	struct cacheuid_list **cpp;
	int hash = HASH_UID(uid);

	debug(KEYSERV_DEBUG, ("mapuid2cache %d %d %x", uid, hash, mdp));
	for (cpp = &mdp->bucket[hash];
		*cpp != NULL && (*cpp)->uid != uid;
		cpp = &(*cpp)->next) {
		debug(KEYSERV_DEBUG0, ("mapuid2cache %x", cpp));
	}
	debug(KEYSERV_DEBUG, ("mapuid2cache ret %x", cpp));
	return (cpp);
}

static int
appendsecretkey3(struct mechentry *mp, uid_t uid, setkeyarg3 *skey)
{
	struct mechdata *mdp;
	struct cacheuid_list **cpp, *cp;
	keybuf3 nullkey = {0, NULL};

	debug(KEYSERV_DEBUG, ("appendsecretkey3 %x", mp));
	if ((skey == NULL) || (mp == NULL)) {
		return (0);
	}
	if (skey->key.keybuf3_len == 0) {
		return (0);
	}
	mutex_lock(&mp->mech_lock);
	if ((mdp = mp->mechdata) == NULL) {
		mdp = (struct mechdata *)calloc(1, sizeof (*mdp));
		if (mdp == NULL) {
			mutex_unlock(&mp->mech_lock);
			debug(KEYSERV_INFO,
				("appendsecretkey3 : calloc failed"));
			return (0);
		}
		mp->mechdata = mdp;
	}
	cpp = mapuid2cache(uid, mdp);
	if (*cpp == NULL) {
		cp = (struct cacheuid_list *)malloc(sizeof (*cp));
		if (cp == NULL) {
			mutex_unlock(&mp->mech_lock);
			debug(KEYSERV_INFO,
				("appendsecretkey3 : malloc failed"));
			syslog(LOG_ERR, "file %s line %d: malloc failed",
				__FILE__, __LINE__);
			return (0);
		}
		memset(cp, 0, sizeof (*cp));
		cp->uid = uid;
		*cpp = cp;
	} else {
		cp = *cpp;
	}
	freekeybuf3(cp->secretkey);
	if ((cp->secretkey = cpykeybuf3(&skey->key)) == NULL) {
		mutex_unlock(&mp->mech_lock);
		return (0);
	}
	freekeybuf3(cp->publickey);
	if ((cp->publickey = cpykeybuf3(&nullkey)) == NULL) {
		mutex_unlock(&mp->mech_lock);
		return (0);
	}
	mutex_unlock(&mp->mech_lock);
	return (1);
}

/*
 * Store the vers 3 secretkey for this uid
 */
static int
storesecretkey3(uid_t uid, setkeyarg3 *skey)
{
	struct mechentry *mp;

	if (skey == NULL) {
		return (0);
	}
	if ((mp = getmechtype(skey->keylen, skey->algtype)) == NULL) {
		return (0);
	}
	return (appendsecretkey3(mp, uid, skey));
}

/*
 * Set the vers 3 secretkey key for this uid
 */
keystatus
pk_setkey3(uid_t uid, setkeyarg3 *skey)
{
	if (!storesecretkey3(uid, skey)) {
		return (KEY_SYSTEMERR);
	}
	return (KEY_SUCCESS);
}

/*
 * Set the secretkey key for this uid
 */
keystatus
pk_setkey(uid, skey)
	uid_t uid;
	keybuf skey;
{
	int storesecretkey(uid_t, keybuf);

	if (!storesecretkey(uid, skey)) {
		return (KEY_SYSTEMERR);
	}
	return (KEY_SUCCESS);
}

int
storeotherrootkeys(FILE *fp, char *netname, char *passwd, char *osecret)
{
	des_block master;
	struct keylenlist *kp;
	struct algtypelist *ap;
	keybuf3 *secret;
	setkeyarg3 skey;

	debug(KEYSERV_DEBUG, ("storeotherrootkeys %s %s",
		netname, passwd));
	passwd2des_g(passwd, netname, strlen(netname), &master, FALSE);
	for (kp = mechtable.kp; kp != NULL; kp = kp->next) {
		debug(KEYSERV_DEBUG0,
			("storeotherrootkeys key %d", kp->keylen));
		for (ap = kp->ap; ap != NULL; ap = ap->next) {
			debug(KEYSERV_DEBUG,
				("storeotherrootkeys alg: %d", ap->algtype));
			if ((secret = getkeybuf3(kp->keylen/4+1)) == NULL) {
				return (0);
			}
			debug(KEYSERV_DEBUG,
				("storeotherrootkeys calling getsecretkey_g"));
			if (!getsecretkey_g(netname,
				kp->keylen, ap->algtype,
				secret->keybuf3_val, secret->keybuf3_len,
				passwd)) {
				debug(KEYSERV_INFO,
				("Can't find %s's secret key", netname));
				return (0);
			}
			if (*secret->keybuf3_val == 0) { /* XXX */
				debug(KEYSERV_INFO,
				("Password does not decrypt secret key for %s",
					netname));
				return (0);
			}
			skey.key = *secret;
			free(secret); /* but not the buffer it points to */
			skey.userkey = master;
			skey.keylen = kp->keylen;
			skey.algtype = ap->algtype;
			if (CLASSIC_PK_DH(kp->keylen, ap->algtype)) {
				pk_setkey((uid_t)0, osecret);
				fprintf(fp, "%s\n", osecret);
			}
			if (pk_setkey3(0, &skey) != KEY_SUCCESS) {
				return (0);
			}
			if (!CLASSIC_PK_DH(kp->keylen, ap->algtype)) {
				fprintf(fp, "%s %d\n", skey.key.keybuf3_val,
					ap->algtype);
			}
		}
	}
	return (1);
}

/*
 * prohibit the nobody key on this machine k (the -d flag)
 */
int
pk_nodefaultkeys()
{
	nodefaultkeys = 1;
	return (0);
}

static void
freedisklist(struct cacheuid_list *cp)
{
	if (cp == NULL) {
		return;
	}
	free(cp->netname); /* ok even if this is NULL */
	freekeybuf3(cp->secretkey);
	freekeybuf3(cp->publickey);
}

keystatus
pk_clear3(uid_t uid)
{
	struct keylenlist *kp;
	struct algtypelist *ap;
	struct mechdata *mdp;
	struct cacheuid_list **cpp, *cp;

	debug(KEYSERV_DEBUG, ("pk_clear3 %d", uid));
	for (kp = mechtable.kp; kp != NULL; kp = kp->next) {
		debug(KEYSERV_DEBUG0, ("pk_clear3 key %d", kp->keylen));
		for (ap = kp->ap; ap != NULL; ap = ap->next) {
			debug(KEYSERV_DEBUG0,
				("pk_clear3 alg: %d", ap->algtype));
			mutex_lock(&ap->mech.mech_lock);
			if ((mdp = ap->mech.mechdata) == NULL) {
				mutex_unlock(&ap->mech.mech_lock);
				continue;
			}
			cpp = mapuid2cache(uid, mdp);
			if (*cpp == NULL) {
				mutex_unlock(&ap->mech.mech_lock);
				continue;
			}
			cp = (*cpp)->next;
			freedisklist(*cpp);
			*cpp = cp;
			mutex_unlock(&ap->mech.mech_lock);
		}
	}
	/* XXX clear stuff out of the common key cache as well? */
	/* XXX return success only if something was removed? */
	return (KEY_SUCCESS);
}

/*
 * Set the modulus for all our Diffie-Hellman operations
 */
int
setmodulus(modx)
	char *modx;
{
	MODULUS = mp_xtom(modx);
	return (0);
}

/*
 * Encrypt the key using the public key associated with remote_name and the
 * secret key associated with uid.
 */
keystatus
pk_encrypt(uid, remote_name, remote_key, key)
	uid_t uid;
	char *remote_name;
	netobj	*remote_key;
	des_block *key;
{
	return (pk_crypt(uid, remote_name, remote_key, key, DES_ENCRYPT));
}

/*
 * Encrypt the key using the public key associated with remote_name and the
 * secret key associated with uid using vers 3
 */
keystatus
pk_encrypt3(
	uid_t uid,
	cryptkeyarg3 *arg,
	deskeyarray *key
)
{
	return (pk_crypt3(uid, arg, key, DES_ENCRYPT));
}

/*
 * Decrypt the key using the public key associated with remote_name and the
 * secret key associated with uid.
 */
keystatus
pk_decrypt(uid, remote_name, remote_key, key)
	uid_t uid;
	char *remote_name;
	netobj *remote_key;
	des_block *key;
{
	return (pk_crypt(uid, remote_name, remote_key, key, DES_DECRYPT));
}

/*
 * Decrypt the key using the public key associated with remote_name and the
 * secret key associated with uid using vers 3
 */
keystatus
pk_decrypt3(
	uid_t uid,
	cryptkeyarg3 *arg,
	deskeyarray *key
)
{
	return (pk_crypt3(uid, arg, key, DES_DECRYPT));
}

/*
 * Key storage management
 */

#define	KEY_ONLY 0
#define	KEY_NAME 1
struct secretkey_netname_list {
	uid_t uid;
	key_netstarg keynetdata;
	uchar_t sc_flag;
	struct secretkey_netname_list *next;
};

#define	HASH_UID(x)	(x & 0xff)
static struct secretkey_netname_list *g_secretkey_netname[KEY_HASH_SIZE];
static rwlock_t g_secretkey_netname_lock = DEFAULTRWLOCK;

/*
 * Store the keys and netname for this uid
 */
static int
store_netname(uid, netstore)
	uid_t uid;
	key_netstarg *netstore;
{
	struct secretkey_netname_list *new;
	struct secretkey_netname_list **l;
	int hash = HASH_UID(uid);

	(void) rw_wrlock(&g_secretkey_netname_lock);
	for (l = &g_secretkey_netname[hash]; *l != NULL && (*l)->uid != uid;
			l = &(*l)->next) {
	}
	if (*l == NULL) {
/* LINTED pointer alignment */
		new = (struct secretkey_netname_list *)malloc(sizeof (*new));
		if (new == NULL) {
			(void) rw_unlock(&g_secretkey_netname_lock);
			return (0);
		}
		new->uid = uid;
		new->next = NULL;
		*l = new;
	} else {
		new = *l;
		if (new->keynetdata.st_netname)
			(void) free(new->keynetdata.st_netname);
	}
	memcpy(new->keynetdata.st_priv_key, netstore->st_priv_key,
		HEXKEYBYTES);
	memcpy(new->keynetdata.st_pub_key, netstore->st_pub_key, HEXKEYBYTES);

	if (netstore->st_netname)
		new->keynetdata.st_netname = strdup(netstore->st_netname);
	else
		new->keynetdata.st_netname = (char *)NULL;
	new->sc_flag = KEY_NAME;
	(void) rw_unlock(&g_secretkey_netname_lock);
	return (1);

}

static int
appendnetname3(struct mechentry *mp, uid_t uid, key_netstarg3 *net)
{
	struct mechdata *mdp;
	struct cacheuid_list **cpp, *cp;

	debug(KEYSERV_DEBUG, ("appendnetname3 %x", mp));
	if ((mp == NULL) || (net == NULL)) {
		return (0);
	}
	mutex_lock(&mp->mech_lock);
	if ((mdp = mp->mechdata) == NULL) {
		mdp = (struct mechdata *)calloc(1, sizeof (*mdp));
		if (mdp == NULL) {
			mutex_unlock(&mp->mech_lock);
			debug(KEYSERV_INFO, ("appendnetname3 : calloc failed"));
			return (0);
		}
		mp->mechdata = mdp;
	}
	cpp = mapuid2cache(uid, mdp);
	if (*cpp == NULL) {
		cp = (struct cacheuid_list *)malloc(sizeof (*cp));
		if (cp == NULL) {
			mutex_unlock(&mp->mech_lock);
			debug(KEYSERV_INFO, ("appendnetname3 : malloc failed"));
			syslog(LOG_ERR, "file %s line %d: malloc failed",
				__FILE__, __LINE__);
			return (0);
		}
		memset(cp, 0, sizeof (*cp));
		cp->uid = uid;
		*cpp = cp;
	} else {
		cp = *cpp;
	}
	freekeybuf3(cp->secretkey);
	if ((cp->secretkey = cpykeybuf3(&net->st_priv_key)) == NULL) {
		mutex_unlock(&mp->mech_lock);
		return (0);
	}
	freekeybuf3(cp->publickey);
	if ((cp->publickey = cpykeybuf3(&net->st_pub_key)) == NULL) {
		mutex_unlock(&mp->mech_lock);
		return (0);
	}
	free(cp->netname);
	if (net->st_netname) {
		cp->netname = strdup(net->st_netname);
	} else {
		cp->netname = (char *)NULL;
	}
	mutex_unlock(&mp->mech_lock);
	return (1);
}

keystatus
pk_netput(uid, netstore)
	uid_t uid;
	key_netstarg *netstore;
{

	if (!store_netname(uid, netstore)) {
		return (KEY_SYSTEMERR);
	}
	return (KEY_SUCCESS);
}

/*
 * Store the keys and netname for this uid vers 3
 */
static int
store_netname3(uid_t uid, key_netstarg3 *net)
{
	struct mechentry *mp;
	key_netstarg netstore;

	if (net == NULL) {
		return (0);
	}
	if ((mp = getmechtype(net->keylen, net->algtype)) == NULL) {
		return (0);
	}
	if (uid == 0 && CLASSIC_PK_DH(net->keylen, net->algtype)) {
		memcpy(netstore.st_priv_key, net->st_priv_key.keybuf3_val,
			HEXKEYBYTES);
		memset(netstore.st_pub_key, 0, HEXKEYBYTES);
		netstore.st_netname = net->st_netname;
		if (pk_netput(uid, &netstore) != KEY_SUCCESS) {
			(void) fprintf(stderr,
			"keyserv: could not set root's key and netname.\n");
			return (0);
		}
	}
	return (appendnetname3(mp, uid, net));
}

keystatus
pk_netput3(uid_t uid, key_netstarg3 *netstore)
{

	if (!store_netname3(uid, netstore)) {
		return (KEY_SYSTEMERR);
	}
	return (KEY_SUCCESS);
}

int
addmasterkey(char *master, char *netname, algtype_t algtype)
{
	keybuf3 *secret, *public;
	int bytelen = strlen(master);
	keylen_t keylen = bytelen*4;
	key_netstarg3 tmp;

	if ((secret = setkeybuf3(master, bytelen)) == NULL) {
		return (0);
	}
	if ((public = getkeybuf3(bytelen+1)) == NULL) {
		/* the +1 is mandated by getpublickey_g() */
		return (0);
	}
	/*
	 * getpublickey_g(netname, keylen, algtype,
	 *  public->keybuf3_val, public->keybuf3_len);
	 * cannot be called since rpc.nisd is not up yet
	 * so we continue to return a zero filled public key
	 * as in the earlier version
	 */
	memset(public->keybuf3_val, 0, bytelen+1);
	tmp.st_priv_key = *secret;
	free(secret);
	tmp.st_pub_key = *public;
	free(public);
	tmp.st_netname = strdup(netname);
	tmp.keylen = keylen;
	tmp.algtype = algtype;
	return (store_netname3(0, &tmp));
}

/*
 * Fetch the keys and netname for this uid
 */
static int
fetch_netname(uid, key_netst)
	uid_t uid;
	struct key_netstarg *key_netst;
{
	struct secretkey_netname_list *l;
	int hash = HASH_UID(uid);

	(void) rw_rdlock(&g_secretkey_netname_lock);
	for (l = g_secretkey_netname[hash]; l != NULL; l = l->next) {
		if ((l->uid == uid) && (l->sc_flag == KEY_NAME)) {

			memcpy(key_netst->st_priv_key,
				l->keynetdata.st_priv_key, HEXKEYBYTES);

			memcpy(key_netst->st_pub_key,
				l->keynetdata.st_pub_key, HEXKEYBYTES);

			if (l->keynetdata.st_netname)
				strcpy(key_netst->st_netname,
						l->keynetdata.st_netname);
			else
				key_netst->st_netname = NULL;
			(void) rw_unlock(&g_secretkey_netname_lock);
			return (1);
		}
	}
	(void) rw_unlock(&g_secretkey_netname_lock);
	return (0);
}

static void
remove_ref(struct cacheuid_list *cp)
{
	debug(KEYSERV_DEBUG0, ("remove_ref %x", cp));
	/*
	 * XXX
	 * if we are going to do this along the lines of vn_rele,
	 * more stuff needs to be done here and the access to refcnt
	 * needs to be mutex locked. Keep it simple for now.
	 */
	cp->refcnt--;
}

static void
add_ref(struct cacheuid_list **cpp)
{
	struct cacheuid_list *cp;

	if (cpp == NULL) {
		return;
	}
	/*LINTED assignment operator "=" found where "==" was expected*/
	if (cp = *cpp) {
		debug(KEYSERV_DEBUG0, ("add_ref %x", cp));
		cp->refcnt++;
	}
}

static struct cacheuid_list *
getcachekey3(uid_t uid, struct mechentry *mp)
{
	struct cacheuid_list **cpp, *cp;
	struct mechdata *mdp;

	debug(KEYSERV_DEBUG1, ("getcachekey3 %d %x", uid, mp));
	if (mp == NULL) {
		return (0);
	}
	mutex_lock(&mp->mech_lock);
	if ((mdp = mp->mechdata) == NULL) {
		mutex_unlock(&mp->mech_lock);
		debug(KEYSERV_DEBUG0, ("getcachekey3 ret 0"));
		return (0);
	}
	cpp = mapuid2cache(uid, mdp);
	cp = *cpp;
	add_ref(cpp);
	mutex_unlock(&mp->mech_lock);
	debug(KEYSERV_DEBUG0, ("getcachekey3 ret %x", *cpp));
	return (cp);
}

/*
 * Fetch any available cache for this uid (vers 3)
 */
static struct cacheuid_list *
getanycache3(uid_t uid)
{
	struct keylenlist *kp;
	struct algtypelist *ap;
	struct mechdata *mdp;
	struct cacheuid_list **cpp, *cp;

	debug(KEYSERV_DEBUG, ("getanycache3 %d", uid));
	for (kp = mechtable.kp; kp != NULL; kp = kp->next) {
		debug(KEYSERV_DEBUG0, ("getanycache3 key %d", kp->keylen));
		for (ap = kp->ap; ap != NULL; ap = ap->next) {
			debug(KEYSERV_DEBUG0,
				("getanycache3 alg: %d", ap->algtype));
			mutex_lock(&ap->mech.mech_lock);
			if ((mdp = ap->mech.mechdata) == NULL) {
				mutex_unlock(&ap->mech.mech_lock);
				continue;
			}
			cpp = mapuid2cache(uid, mdp);
			if (*cpp == NULL) {
				mutex_unlock(&ap->mech.mech_lock);
				continue;
			}
			cp = *cpp;
			cp->refcnt++;
			mutex_unlock(&ap->mech.mech_lock);
			return (cp);
		}
	}
	return (NULL);
}

static struct cacheuid_list *
fetchcache3(uid_t uid, keylen_t k, algtype_t a)
{
	struct mechentry *mp;
	struct cacheuid_list *cp;

	debug(KEYSERV_DEBUG, ("fetchcache3 %d %d %d", uid, k, a));
	if ((mp = getmechtype(k, a)) == NULL) {
		return (NULL);
	}
	if ((cp = getcachekey3(uid, mp)) == NULL) {
		return (NULL);
	}
	debug(KEYSERV_DEBUG, ("fetchcache3 ret %x", cp));
	return (cp);
}

/*
 * Fetch the keys and netname for this uid vers 3
 */
static int
fetch_netname3(uid_t uid, mechtype *net, key_netstarg3 *ret)
{
	struct cacheuid_list *cp;

	if ((net == NULL) || (ret == NULL)) {
		return (0);
	}
	debug(KEYSERV_DEBUG, ("fetch_netname3 %d %d %d",
		uid, net->keylen, net->algtype));
	if (net->keylen == 0) {
		cp = getanycache3(uid);
	} else {
		cp = fetchcache3(uid, net->keylen, net->algtype);
	}
	debug(KEYSERV_DEBUG, ("fetch_netname3 cp %x", cp));
	if (cp == NULL) {
		return (0);
	}
	debug(KEYSERV_DEBUG, ("fetch_netname3 sec %x", cp->secretkey));
	if (!storekeybuf3(&ret->st_priv_key, cp->secretkey)) {
		return (0);
	}
	debug(KEYSERV_DEBUG, ("fetch_netname3 pub %x", cp->publickey));
	if (!storekeybuf3(&ret->st_pub_key, cp->publickey)) {
		return (0);
	}
	if (cp->netname) {
		debug(KEYSERV_DEBUG, ("fetch_netname3 net %s", cp->netname));
		ret->st_netname = strdup(cp->netname);
	} else {
		ret->st_netname = NULL;
	}
	remove_ref(cp);
	return (1);
}

keystatus
pk_netget(uid, netstore)
	uid_t uid;
	key_netstarg *netstore;
{
	if (!fetch_netname(uid, netstore)) {
		return (KEY_SYSTEMERR);
	}
	return (KEY_SUCCESS);
}

keystatus
pk_netget3(uid_t uid, mechtype *net, key_netstarg3 *ret)
{
	if (!fetch_netname3(uid, net, ret)) {
		return (KEY_SYSTEMERR);
	}
	return (KEY_SUCCESS);
}

#define	cachehit(pub, sec, list)	\
		(memcmp(pub, (list)->public, sizeof (keybuf)) == 0 && \
		memcmp(sec, (list)->secret, sizeof (keybuf)) == 0)

/*
 * Try to find the common key in the cache
 */
static int
readcache(pub, sec, deskey, hash)
	char *pub;
	char *sec;
	des_block *deskey;
	int hash;
{
	register struct cachekey_list **l;

	for (l = &g_cachedkeys[hash]; (*l) != NULL && !cachehit(pub, sec, *l);
		l = &(*l)->next)
		;
	if ((*l) == NULL)
		return (0);
	*deskey = (*l)->deskey;
	return (1);
}

/*
 * cache result of expensive multiple precision exponential operation
 */
static int
writecache(pub, sec, deskey, hash)
	char *pub;
	char *sec;
	des_block *deskey;
	int hash;
{
	struct cachekey_list *new;

	new = (struct cachekey_list *)malloc(sizeof (struct cachekey_list));
	if (new == NULL) {
		return (0);
	}
	memcpy(new->public, pub, sizeof (keybuf));
	memcpy(new->secret, sec, sizeof (keybuf));
	new->deskey = *deskey;

	new->next = g_cachedkeys[hash];
	g_cachedkeys[hash] = new;
	return (1);
}

/*
 * Choose middle 64 bits of the common key to use as our des key, possibly
 * overwriting the lower order bits by setting parity.
 */
static int
extractdeskey(ck, deskey)
	MINT *ck;
	des_block *deskey;
{
	void _mp_move(MINT *, MINT *);
	MINT *a;
	short r;
	int i;
	short base = (1 << 8);
	char *k;

	a = mp_itom(0);
	_mp_move(ck, a);
	for (i = 0; i < ((KEYSIZE - 64) / 2) / 8; i++) {
		mp_sdiv(a, base, a, &r);
	}
	k = deskey->c;
	for (i = 0; i < 8; i++) {
		mp_sdiv(a, base, a, &r);
		*k++ = r;
	}
	mp_mfree(a);
	des_setparity((char *)deskey);
	return (0);
}

static bool_t
fetchsecretkey(uid, buf)
	uid_t uid;
	char *buf;
{
	struct secretkey_netname_list *l;
	int hash = HASH_UID(uid);

	(void) rw_rdlock(&g_secretkey_netname_lock);
	for (l = g_secretkey_netname[hash]; l != NULL; l = l->next) {
		if (l->uid == uid) {
			memcpy(buf, l->keynetdata.st_priv_key,
				sizeof (keybuf));
			(void) rw_unlock(&g_secretkey_netname_lock);
			return (TRUE);
		}
	}
	(void) rw_unlock(&g_secretkey_netname_lock);
	return (FALSE);
}

static keybuf3 *
fetchsecretkey3(uid_t uid, keylen_t k, algtype_t a)
{
	struct cacheuid_list *cp;

	debug(KEYSERV_DEBUG, ("fetchsecretkey3 %d %d %d", uid, k, a));
	if ((cp = fetchcache3(uid, k, a)) == NULL) {
		return (NULL);
	}
	debug(KEYSERV_DEBUG, ("fetchsecretkey3 ret %x", cp->secretkey));
	return (cp->secretkey);
}

/*
 * Do the work of pk_encrypt && pk_decrypt
 */
static keystatus
pk_crypt(uid, remote_name, remote_key, key, mode)
	uid_t uid;
	char *remote_name;
	netobj *remote_key;
	des_block *key;
	int mode;
{
	char xsecret[1024];
	char xpublic[1024];
	des_block deskey;
	int err;
	MINT *public;
	MINT *secret;
	MINT *common;
	char zero[8];
	int hash;

	if (!fetchsecretkey(uid, xsecret) || xsecret[0] == 0) {
		memset(zero, 0, sizeof (zero));
		if (nodefaultkeys)
			return (KEY_NOSECRET);

		if (!getsecretkey("nobody", xsecret, zero) || xsecret[0] == 0) {
			return (KEY_NOSECRET);
		}
	}
	if (remote_key) {
		memcpy(xpublic, remote_key->n_bytes, remote_key->n_len);
	} else {
		if (!getpublickey(remote_name, xpublic)) {
			if (nodefaultkeys || !getpublickey("nobody", xpublic))
				return (KEY_UNKNOWN);
		}
	}

	xsecret[HEXKEYBYTES] = '\0';
	xpublic[HEXKEYBYTES] = '\0';

	hash = hash_keys(xpublic, xsecret);
	(void) rw_rdlock(&g_cachedkeys_lock);
	if (!readcache(xpublic, xsecret, &deskey, hash)) {
		(void) rw_unlock(&g_cachedkeys_lock);
		(void) rw_wrlock(&g_cachedkeys_lock);
		if (!readcache(xpublic, xsecret, &deskey, hash)) {
			public = mp_xtom(xpublic);
			secret = mp_xtom(xsecret);
			/* Sanity Check on public and private keys */
			if (public == NULL || secret == NULL) {
				(void) rw_unlock(&g_cachedkeys_lock);
				return (KEY_SYSTEMERR);
			}
			common = mp_itom(0);
			mp_pow(public, secret, MODULUS, common);
			extractdeskey(common, &deskey);
			writecache(xpublic, xsecret, &deskey, hash);
			mp_mfree(secret);
			mp_mfree(public);
			mp_mfree(common);
		}
	}
	(void) rw_unlock(&g_cachedkeys_lock);

	err = ecb_crypt((char *)&deskey, (char *)key, sizeof (des_block),
		DES_HW | mode);
	if (DES_FAILED(err)) {
		return (KEY_SYSTEMERR);
	}
	return (KEY_SUCCESS);
}

static int
hash_keys3(keybuf3 *p, keybuf3 *s)
{
	int i;
	int hash = 0;
	char *pub = p->keybuf3_val;
	char *sec = s->keybuf3_val;

	debug(KEYSERV_DEBUG, ("hash_keys3 public %d %s",
		p->keybuf3_len, pub));
	debug(KEYSERV_DEBUG, ("hash_keys3 secret %d %s",
		s->keybuf3_len, sec));
	for (i = 0; i < s->keybuf3_len; i += 6, pub += 6, sec += 6) {
		hash ^= *pub;
		hash ^= *sec;
	}
	debug(KEYSERV_DEBUG, ("hash_keys3 ret %d", hash & 0xff));
	return (hash & 0xff);
}

static struct cachekey3_list **
map_ps2cache(keybuf3 *public, keybuf3 *secret, struct psdata *pdp)
{
	struct cachekey3_list **cpp;
	int hash = hash_keys3(public, secret);

	debug(KEYSERV_DEBUG, ("map_ps2cache %x %d", pdp, hash));
	for (cpp = &pdp->common[hash];
		*cpp != NULL && !(cachehit3(public, secret, *cpp));
		cpp = &(*cpp)->next) {
		debug(KEYSERV_DEBUG0, ("map_ps2cache %x", cpp));
	}
	debug(KEYSERV_DEBUG, ("map_ps2cache ret %x", cpp));
	return (cpp);
}

static struct cachekey3_list *
getdeskey3(
	keylen_t keylen,
	algtype_t algtype,
	int desarylen,
	keybuf3 *public,
	keybuf3 *secret,
	uid_t uid
)
{
	struct mechentry *mp;
	struct psdata *pdp;
	struct cachekey3_list **cpp, *cp, *cachep;
	struct cacheuid_list *cu;
	int i;
	int cached = 0;

	debug(KEYSERV_DEBUG, ("getdeskey3 %d %d %d %x %x",
		keylen, algtype, desarylen, public, secret));
	if ((mp = getmechtype(keylen, algtype)) == NULL) {
		return (0);
	}
	(void) mutex_lock(&mp->ps_lock);
	if ((pdp = mp->psdata) == NULL) {
		if ((pdp = (struct psdata *)calloc(1, sizeof (*pdp))) ==
			NULL) {
			mutex_unlock(&mp->ps_lock);
			debug(KEYSERV_INFO, ("getdeskey3 : calloc failed"));
			return (0);
		}
		mp->psdata = pdp;
	}
	debug(KEYSERV_DEBUG, ("getdeskey3 %x", pdp));
	cpp = map_ps2cache(public, secret, pdp);
	if (*cpp == NULL) {
		debug(KEYSERV_DEBUG, ("getdeskey3 calling fetchcache3"));
		if (disk_caching &&
			(cu = fetchcache3(uid, keylen, algtype)) != NULL) {
			debug(KEYSERV_DEBUG,
				("getdeskey3 calling cache_retrieve"));
			if ((cachep = cache_retrieve(keylen, algtype, uid,
				public, cu->key)) != NULL) {
				if (cmpkeybuf3(cachep->secret, cu->secretkey)) {
					cached = 1;
				} else {
					debug(KEYSERV_DEBUG,
					("getdeskey3 calling cache_remove"));
					cache_remove(keylen, algtype,
						uid, NULL);
				}
			}
		}
		if (cached) {
			cp = cachep;
		} else {
			if ((cp = (struct cachekey3_list *)
				malloc(sizeof (*cp))) == NULL) {
				mutex_unlock(&mp->ps_lock);
				debug(KEYSERV_INFO,
					("getdeskey3 : malloc failed"));
				syslog(LOG_ERR,
					"file %s line %d: malloc failed",
					__FILE__, __LINE__);
				return (0);
			}
			cp->refcnt = 0;
			cp->next = NULL;
			if ((cp->public = cpykeybuf3(public)) == NULL) {
				mutex_unlock(&mp->ps_lock);
				return (0);
			}
			if ((cp->secret = cpykeybuf3(secret)) == NULL) {
				mutex_unlock(&mp->ps_lock);
				return (0);
			}
			if (!setdeskeyarray(&cp->deskey, desarylen)) {
				mutex_unlock(&mp->ps_lock);
				return (0);
			}
			debug(KEYSERV_DEBUG, ("getdeskey3 %x %x %x",
				cp->public, cp->secret,
				cp->deskey.deskeyarray_val));
			debug(KEYSERV_DEBUG,
				("getdeskey3 calling __gen_common_dhkeys_g"));
			if (!__gen_common_dhkeys_g(public->keybuf3_val,
				secret->keybuf3_val,
				keylen, algtype,
				cp->deskey.deskeyarray_val, desarylen)) {
				mutex_unlock(&mp->ps_lock);
				return (0);
			}
			for (i = 0; i < desarylen; i++) {
				debug(KEYSERV_DEBUG0,
					("getdeskey3 gendh key : (%x,%x)",
					cp->deskey.deskeyarray_val[i].key.high,
					cp->deskey.deskeyarray_val[i].key.low));
			}
			if (disk_caching && cu != NULL) {
				debug(KEYSERV_DEBUG,
					("getdeskey3 calling cache_insert"));
				cache_insert(keylen, algtype, uid, cp->deskey,
					cu->key, public, secret);
			}
		}
		*cpp = cp;
	} else {
		cp = *cpp;
	}
	cp->refcnt++;
	mutex_unlock(&mp->ps_lock);
	debug(KEYSERV_DEBUG, ("getdeskey3 ret %x", cp));
	return (cp);
}

keystatus
pk_get_conv_key3(uid_t uid, deskeyarg3 *arg, cryptkeyres3 *res)
{
	keybuf3 *xsecret, *xpublic;
	char zero[8];
	struct cachekey3_list *cp;

	debug(KEYSERV_DEBUG, ("pk_get_conv_key3 %d %x %x",
		uid, arg, res));
	if ((xsecret = fetchsecretkey3(uid,
		arg->keylen, arg->algtype)) == NULL) {
		if (nodefaultkeys)
			return (KEY_NOSECRET);
		memset(zero, 0, sizeof (zero));
		if ((xsecret = getkeybuf3(arg->keylen/4+1)) == NULL) {
			return (KEY_SYSTEMERR);
		}
		debug(KEYSERV_DEBUG,
			("pk_get_conv_key3 calling getsecretkey_g"));
		if (!getsecretkey_g("nobody",
			arg->keylen, arg->algtype,
			xsecret->keybuf3_val, xsecret->keybuf3_len,
			zero) || *xsecret->keybuf3_val == 0) { /* XXX */
			debug(KEYSERV_DEBUG,
			("pk_get_conv_key3 calling getsecretkey_g failed"));
			return (KEY_NOSECRET);
		}
		debug(KEYSERV_DEBUG,
			("pk_get_conv_key3 calling getsecretkey_g succeeded"));
	}
	xpublic = &arg->pub_key;
	if ((cp = getdeskey3(arg->keylen, arg->algtype, arg->nkeys,
		xpublic, xsecret, uid)) == NULL) {
		return (KEY_SYSTEMERR);
	}
	storedeskeyarray(&res->cryptkeyres3_u.deskey, &cp->deskey);
	return (KEY_SUCCESS);
}

/*
 * Do the work of pk_encrypt3 && pk_decrypt3
 */
static keystatus
pk_crypt3(
	uid_t uid,
	cryptkeyarg3 *arg,
	deskeyarray *key,
	int mode
)
{
	keybuf3 *xsecret = NULL, *xpublic = NULL;
	char zero[8];
	struct cachekey3_list *cp;
	int err;
	int xsecret_alloc = 0;
	char ivec[8];

	memset(ivec, 0, 8);
	debug(KEYSERV_DEBUG1, ("pk_crypt3 %d %x %x %d",
		uid, arg, key, mode));
	if ((xsecret = fetchsecretkey3(uid,
		arg->keylen, arg->algtype)) == NULL) {
		if (nodefaultkeys)
			return (KEY_NOSECRET);
		memset(zero, 0, sizeof (zero));
		if ((xsecret = getkeybuf3(arg->keylen/4+1)) == NULL) {
			return (KEY_SYSTEMERR);
		}
		xsecret_alloc = 1;
		debug(KEYSERV_DEBUG1, ("pk_crypt3 calling getsecretkey_g"));
		if (!getsecretkey_g("nobody",
			arg->keylen, arg->algtype,
			xsecret->keybuf3_val, xsecret->keybuf3_len,
			zero) || *xsecret->keybuf3_val == 0) { /* XXX */
			debug(KEYSERV_DEBUG,
				("pk_crypt3 calling getsecretkey_g failed"));
			freekeybuf3(xsecret);
			return (KEY_NOSECRET);
		}
		/* XXX optimize to cache nobody's secret key? */
		debug(KEYSERV_DEBUG0,
			("pk_crypt3 calling getsecretkey_g succeeded"));
	}
	if (arg->remotekey.keybuf3_len) {
		if ((xpublic = cpykeybuf3(&arg->remotekey)) == NULL) {
			if (xsecret_alloc) freekeybuf3(xsecret);
			return (KEY_SYSTEMERR);
		}
	} else {
		if ((xpublic = getkeybuf3(arg->keylen/4+1)) == NULL) {
			if (xsecret_alloc) freekeybuf3(xsecret);
			return (KEY_SYSTEMERR);
		}
		debug(KEYSERV_DEBUG1, ("pk_crypt3 calling getpublickey_g"));
		if (!getpublickey_g(arg->remotename,
			arg->keylen, arg->algtype,
			xpublic->keybuf3_val, xpublic->keybuf3_len)) {
			debug(KEYSERV_DEBUG0,
				("pk_crypt3 calling getpublickey_g nobody"));
			if (nodefaultkeys || !getpublickey_g("nobody",
				arg->keylen, arg->algtype,
				xpublic->keybuf3_val, xpublic->keybuf3_len)) {
				debug(KEYSERV_DEBUG,
			("pk_crypt3 calling getpublickey_g nobody failed"));
				if (xsecret_alloc) freekeybuf3(xsecret);
				freekeybuf3(xpublic);
				return (KEY_UNKNOWN);
			}
		}
		debug(KEYSERV_DEBUG0,
			("pk_crypt3 calling getpublickey_g succeeded"));
	}

	if ((cp = getdeskey3(arg->keylen, arg->algtype,
		arg->deskey.deskeyarray_len, xpublic, xsecret, uid)) == NULL) {
		if (xsecret_alloc) freekeybuf3(xsecret);
		freekeybuf3(xpublic);
		return (KEY_SYSTEMERR);
	}
	storedeskeyarray(key, &arg->deskey);
	if (CLASSIC_PK_DH(arg->keylen, arg->algtype)) {
		/*EMPTY*/
		debug(KEYSERV_DEBUG1,
			("pk_crypt3 WARNING received 192-bit key"));
	} else {
		debug(KEYSERV_DEBUG,
			("pk_crypt3 calling __cbc_triple_crypt"));
		err = __cbc_triple_crypt(cp->deskey.deskeyarray_val,
			(char *)key->deskeyarray_val,
			cp->deskey.deskeyarray_len*sizeof (des_block),
			DES_HW | mode, ivec);
		if (DES_FAILED(err)) {
			debug(KEYSERV_DEBUG,
		("pk_crypt3 calling ecb_crypt/__cbc_triple_crypt failed"));
			if (xsecret_alloc) freekeybuf3(xsecret);
			freekeybuf3(xpublic);
			return (KEY_SYSTEMERR);
		}
		debug(KEYSERV_DEBUG,
			("pk_crypt3 calling __cbc_triple_crypt succeeded"));
	}
	if (xsecret_alloc) freekeybuf3(xsecret);
	freekeybuf3(xpublic);
	return (KEY_SUCCESS);
}

keystatus
pk_get_conv_key(uid, pubkey, result)
	uid_t uid;
	keybuf pubkey;
	cryptkeyres *result;
{
	char xsecret[1024];
	char xpublic[1024];
	MINT *public;
	MINT *secret;
	MINT *common;
	char zero[8];
	int hash;

	if (!fetchsecretkey(uid, xsecret) || xsecret[0] == 0) {
		memset(zero, 0, sizeof (zero));
		if (nodefaultkeys)
			return (KEY_NOSECRET);

		if (!getsecretkey("nobody", xsecret, zero) ||
			xsecret[0] == 0)
			return (KEY_NOSECRET);
	}

	memcpy(xpublic, pubkey, sizeof (keybuf));
	xsecret[HEXKEYBYTES] = '\0';
	xpublic[HEXKEYBYTES] = '\0';

	hash = hash_keys(xpublic, xsecret);
	(void) rw_rdlock(&g_cachedkeys_lock);
	if (!readcache(xpublic, xsecret, &result->cryptkeyres_u.deskey, hash)) {
		(void) rw_unlock(&g_cachedkeys_lock);
		(void) rw_wrlock(&g_cachedkeys_lock);
		if (!readcache(xpublic, xsecret, &result->cryptkeyres_u.deskey,
									hash)) {
			public = mp_xtom(xpublic);
			secret = mp_xtom(xsecret);
			/* Sanity Check on public and private keys */
			if (public == NULL || secret == NULL) {
				(void) rw_unlock(&g_cachedkeys_lock);
				return (KEY_SYSTEMERR);
			}
			common = mp_itom(0);
			mp_pow(public, secret, MODULUS, common);
			extractdeskey(common, &result->cryptkeyres_u.deskey);
			writecache(xpublic, xsecret,
					&result->cryptkeyres_u.deskey, hash);
			mp_mfree(secret);
			mp_mfree(public);
			mp_mfree(common);
		}
	}
	(void) rw_unlock(&g_cachedkeys_lock);

	return (KEY_SUCCESS);
}

#define	findsec(sec, list)	\
		(memcmp(sec, (list)->secret, sizeof (keybuf)) == 0)

/*
 * Remove common keys from the cache.
 */
static int
removecache(sec)
	char *sec;
{
	struct cachekey_list *found;
	register struct cachekey_list **l;
	int i;

	(void) rw_wrlock(&g_cachedkeys_lock);
	for (i = 0; i < KEY_HASH_SIZE; i++) {
		for (l = &g_cachedkeys[i]; (*l) != NULL; ) {
			if (findsec(sec, *l)) {
				found = *l;
				*l = (*l)->next;
				memset((char *)found, 0,
					sizeof (struct cachekey_list));
				free(found);
			} else {
				l = &(*l)->next;
			}
		}
	}
	(void) rw_unlock(&g_cachedkeys_lock);
	return (1);
}

/*
 * Store the secretkey for this uid
 */
int
storesecretkey(uid, key)
	uid_t uid;
	keybuf key;
{
	struct secretkey_netname_list *new;
	struct secretkey_netname_list **l;
	int hash = HASH_UID(uid);

	(void) rw_wrlock(&g_secretkey_netname_lock);
	for (l = &g_secretkey_netname[hash]; *l != NULL && (*l)->uid != uid;
			l = &(*l)->next) {
	}
	if (*l == NULL) {
		if (key[0] == '\0') {
			(void) rw_unlock(&g_secretkey_netname_lock);
			return (0);
		}
		new = (struct secretkey_netname_list *)malloc(sizeof (*new));
		if (new == NULL) {
			(void) rw_unlock(&g_secretkey_netname_lock);
			return (0);
		}
		new->uid = uid;
		new->sc_flag = KEY_ONLY;
		memset(new->keynetdata.st_pub_key, 0, HEXKEYBYTES);
		new->keynetdata.st_netname = NULL;
		new->next = NULL;
		*l = new;
	} else {
		new = *l;
		if (key[0] == '\0')
			removecache(new->keynetdata.st_priv_key);
	}

	memcpy(new->keynetdata.st_priv_key, key,
		HEXKEYBYTES);
	(void) rw_unlock(&g_secretkey_netname_lock);
	return (1);
}

static int
hexdigit(val)
	int val;
{
	return ("0123456789abcdef"[val]);
}

int
bin2hex(bin, hex, size)
	unsigned char *bin;
	unsigned char *hex;
	int size;
{
	int i;

	for (i = 0; i < size; i++) {
		*hex++ = hexdigit(*bin >> 4);
		*hex++ = hexdigit(*bin++ & 0xf);
	}
	return (0);
}

static int
hexval(dig)
	char dig;
{
	if ('0' <= dig && dig <= '9') {
		return (dig - '0');
	} else if ('a' <= dig && dig <= 'f') {
		return (dig - 'a' + 10);
	} else if ('A' <= dig && dig <= 'F') {
		return (dig - 'A' + 10);
	} else {
		return (-1);
	}
}

int
hex2bin(hex, bin, size)
	unsigned char *hex;
	unsigned char *bin;
	int size;
{
	int i;

	for (i = 0; i < size; i++) {
		*bin = hexval(*hex++) << 4;
		*bin++ |= hexval(*hex++);
	}
	return (0);
}

static int
hash_keys(pub, sec)
	char *pub;
	char *sec;
{
	int i;
	int hash = 0;

	for (i = 0; i < HEXKEYBYTES; i += 6, pub += 6, sec += 6) {
		hash ^= *pub;
		hash ^= *sec;
	}
	return (hash & 0xff);
}

/*
 * problem:  keyserv loads keys from /etc/.rootkey based on nisauthconf(1M)
 *           which is too nis+-centric (see secure_rpc(3N)).
 *
 * So we want to make sure there is always a AUTH_DES compat entry
 * in the "list" of nis+ mechs so that the 192bit key always gets loaded so
 * non-nis+ services that use AUTH_DES (e.g. nfs) won't get hosed.  The real
 * hacky part of it is we muck with the array returned from
 * __nis_get_mechanisms which we really don't have any business
 * doing cause we should not know/care how that is implemented.  A better
 * way would be to change the __nis_get_mechanisms interface or add another
 * one similiar to it that forces the "des" compat entry into the list.
 *
 * Return ptr to mechs array on success, else NULL on memory errs.
 */
mechanism_t **
getmechwrap()
{
	mechanism_t	**mechs = __nis_get_mechanisms(FALSE);
	mechanism_t	**mechsbak = NULL;
	mechanism_t	*desmech = NULL;
	int		i = 0;

	if (mechs) {
		/* got some valid mechs and possibly the AUTH_DES compat one */
		for (i = 0; mechs[i]; i++) {
			if (AUTH_DES_COMPAT_CHK(mechs[i]))
				return (mechs);
		}
		/* i == number of ptrs not counting terminating NULL */
	}

	/* AUTH_DES compat entry not found, let's add it */
	if ((desmech = malloc(sizeof (mechanism_t))) == NULL) {
		if (mechs)
			__nis_release_mechanisms(mechs);
		return (NULL);
	}
	desmech->mechname = NULL;
	desmech->alias = NIS_SEC_CF_DES_ALIAS;
	desmech->keylen = AUTH_DES_KEYLEN;
	desmech->algtype = AUTH_DES_ALGTYPE;
	desmech->qop = NULL;
	desmech->secserv = rpc_gss_svc_default;

	mechsbak = mechs;
	/* mechs == NULL and i == 0 is valid "no mechs configed" case */
	if ((mechs = (mechanism_t **)realloc(mechs,
			sizeof (mechanism_t *) * (i + 2))) == NULL) {
		if (mechsbak)
			__nis_release_mechanisms(mechsbak);
		free(desmech);
		return (NULL);
	}
	mechs[i] = desmech;
	mechs[i+1] = NULL;

	return (mechs);
}

int
init_mechs()
{
	int nmechs, oldmechseen;
	mechanism_t **mechpp;
	char **cpp;

	if (!(mechs = getmechwrap()))
		return (-1);

	/*
	 * find how many mechanisms were specified and also
	 * setup the mechanism table for unique keylen/algtype pair
	 */
	nmechs = 0;
	for (mechpp = mechs; *mechpp != NULL; mechpp++) {
		struct keylenlist **kpp;
		struct algtypelist **app;

		nmechs++;
		if (((*mechpp)->keylen < 0) || ((*mechpp)->algtype < 0)) {
			continue;
		}
		kpp = getkeylen((*mechpp)->keylen);
		appendkeylist(kpp, (*mechpp)->keylen);
		app = getalgtype(kpp, (*mechpp)->algtype);
		appendalgtype(app, (*mechpp)->algtype);
	}

	/*
	 * set of mechs for getsubopt()
	 */
	cache_options = (char **)calloc((size_t)nmechs + 1,
	    sizeof (*cache_options));
	if (cache_options == NULL) {
		(void) fprintf(stderr, "unable to allocate option array");
		return (-1);
	}
	/*
	 * cache sizes
	 */
	cache_size = (int *)calloc((size_t)nmechs, sizeof (int));
	if (cache_size == NULL) {
		(void) fprintf(stderr, "unable to allocate cache array");
		return (-1);
	}

	oldmechseen = 0;
	cpp = cache_options;
	for (mechpp = mechs; *mechpp != NULL; mechpp++) {
		/*
		 * usual case: a DH-style mechanism type, with an alias
		 */
		if ((*mechpp)->mechname != NULL &&
		    strncmp((*mechpp)->mechname, DHMECHSTR,
		    strlen(DHMECHSTR)) == 0 &&
		    (*mechpp)->alias != NULL) {
			/*
			 * Is this trad 192-DH? already added?
			 */
			if (strcmp((*mechpp)->alias, DESALIAS) == 0) {
				if (oldmechseen) {
					continue;
				}
				oldmechseen++;
			}

			*cpp++ = (*mechpp)->alias;
			continue;
		}

		/*
		 * HACK: we recognise a special alias for traditional
		 * 192-bit DH, unless the latter has already been mentioned
		 * in it's full form
		 */
		if ((*mechpp)->mechname == NULL && (*mechpp)->alias != NULL &&
		    strcmp((*mechpp)->alias, DES) == 0 && !oldmechseen) {
			*cpp++ = DESALIAS;
			oldmechseen++;
			continue;
		}

		/*
		 * Ignore anything else
		 */
	}

	/* Terminate the options list */
	*cpp = NULL;

	return (0);
}