1261057Smav/*-
2261057Smav * Copyright (c) 2009, Sun Microsystems, Inc.
3261057Smav * All rights reserved.
4261057Smav *
5261057Smav * Redistribution and use in source and binary forms, with or without
6261057Smav * modification, are permitted provided that the following conditions are met:
7261057Smav * - Redistributions of source code must retain the above copyright notice,
8261057Smav *   this list of conditions and the following disclaimer.
9261057Smav * - Redistributions in binary form must reproduce the above copyright notice,
10261057Smav *   this list of conditions and the following disclaimer in the documentation
11261057Smav *   and/or other materials provided with the distribution.
12261057Smav * - Neither the name of Sun Microsystems, Inc. nor the names of its
13261057Smav *   contributors may be used to endorse or promote products derived
14261057Smav *   from this software without specific prior written permission.
1526219Swpaul *
16261057Smav * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17261057Smav * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18261057Smav * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19261057Smav * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20261057Smav * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21261057Smav * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22261057Smav * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23261057Smav * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24261057Smav * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25261057Smav * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26261057Smav * POSSIBILITY OF SUCH DAMAGE.
2726219Swpaul */
2826219Swpaul/*
2926219Swpaul * Copyright (c) 1986-1991 by Sun Microsystems Inc.
3026219Swpaul */
3126219Swpaul
3226219Swpaul#ident	"@(#)key_call.c	1.25	94/04/24 SMI"
3392990Sobrien#include <sys/cdefs.h>
3492990Sobrien__FBSDID("$FreeBSD$");
3574712Sobrien
3626219Swpaul/*
3726219Swpaul * key_call.c, Interface to keyserver
3826219Swpaul *
3926219Swpaul * setsecretkey(key) - set your secret key
4026219Swpaul * encryptsessionkey(agent, deskey) - encrypt a session key to talk to agent
4126219Swpaul * decryptsessionkey(agent, deskey) - decrypt ditto
4226219Swpaul * gendeskey(deskey) - generate a secure des key
4326219Swpaul */
4426219Swpaul
4575094Siedowse#include "namespace.h"
4674462Salfred#include "reentrant.h"
4726219Swpaul#include <stdio.h>
4826219Swpaul#include <stdlib.h>
4926219Swpaul#include <unistd.h>
5026219Swpaul#include <errno.h>
5126219Swpaul#include <rpc/rpc.h>
5226219Swpaul#include <rpc/auth.h>
5326219Swpaul#include <rpc/auth_unix.h>
5426219Swpaul#include <rpc/key_prot.h>
5526219Swpaul#include <string.h>
5674462Salfred#include <netconfig.h>
5726219Swpaul#include <sys/utsname.h>
5826219Swpaul#include <stdlib.h>
5926219Swpaul#include <signal.h>
6026219Swpaul#include <sys/wait.h>
6126219Swpaul#include <sys/fcntl.h>
6271579Sdeischen#include "un-namespace.h"
63156090Sdeischen#include "mt_misc.h"
6426219Swpaul
6526219Swpaul
6626219Swpaul#define	KEY_TIMEOUT	5	/* per-try timeout in seconds */
6726219Swpaul#define	KEY_NRETRY	12	/* number of retries */
6826219Swpaul
6926219Swpaul#ifdef DEBUG
7026219Swpaul#define	debug(msg)	(void) fprintf(stderr, "%s\n", msg);
7126219Swpaul#else
7226219Swpaul#define	debug(msg)
7326219Swpaul#endif /* DEBUG */
7426219Swpaul
7526219Swpaul/*
7626219Swpaul * Hack to allow the keyserver to use AUTH_DES (for authenticated
7726219Swpaul * NIS+ calls, for example).  The only functions that get called
7826219Swpaul * are key_encryptsession_pk, key_decryptsession_pk, and key_gendes.
7926219Swpaul *
8026219Swpaul * The approach is to have the keyserver fill in pointers to local
8126219Swpaul * implementations of these functions, and to call those in key_call().
8226219Swpaul */
8326219Swpaul
8426219Swpaulcryptkeyres *(*__key_encryptsession_pk_LOCAL)() = 0;
8526219Swpaulcryptkeyres *(*__key_decryptsession_pk_LOCAL)() = 0;
8626219Swpauldes_block *(*__key_gendes_LOCAL)() = 0;
8726219Swpaul
8895658Sdesstatic int key_call( u_long, xdrproc_t, void *, xdrproc_t, void *);
8926219Swpaul
9026219Swpaulint
9126219Swpaulkey_setsecret(secretkey)
9226219Swpaul	const char *secretkey;
9326219Swpaul{
9426219Swpaul	keystatus status;
9526219Swpaul
9699998Salfred	if (!key_call((u_long) KEY_SET, (xdrproc_t)xdr_keybuf,
9799998Salfred			(void *)secretkey,
9895658Sdes			(xdrproc_t)xdr_keystatus, &status)) {
9926219Swpaul		return (-1);
10026219Swpaul	}
10126219Swpaul	if (status != KEY_SUCCESS) {
10226219Swpaul		debug("set status is nonzero");
10326219Swpaul		return (-1);
10426219Swpaul	}
10526219Swpaul	return (0);
10626219Swpaul}
10726219Swpaul
10826219Swpaul
10926219Swpaul/* key_secretkey_is_set() returns 1 if the keyserver has a secret key
11026219Swpaul * stored for the caller's effective uid; it returns 0 otherwise
11126219Swpaul *
11226219Swpaul * N.B.:  The KEY_NET_GET key call is undocumented.  Applications shouldn't
11326219Swpaul * be using it, because it allows them to get the user's secret key.
11426219Swpaul */
11526219Swpaul
11626219Swpaulint
11726219Swpaulkey_secretkey_is_set(void)
11826219Swpaul{
11926219Swpaul	struct key_netstres 	kres;
12026219Swpaul
12126219Swpaul	memset((void*)&kres, 0, sizeof (kres));
12295658Sdes	if (key_call((u_long) KEY_NET_GET, (xdrproc_t)xdr_void, NULL,
12395658Sdes			(xdrproc_t)xdr_key_netstres, &kres) &&
12426219Swpaul	    (kres.status == KEY_SUCCESS) &&
12526219Swpaul	    (kres.key_netstres_u.knet.st_priv_key[0] != 0)) {
12626219Swpaul		/* avoid leaving secret key in memory */
12726219Swpaul		memset(kres.key_netstres_u.knet.st_priv_key, 0, HEXKEYBYTES);
12826219Swpaul		return (1);
12926219Swpaul	}
13026219Swpaul	return (0);
13126219Swpaul}
13226219Swpaul
13326219Swpaulint
13426219Swpaulkey_encryptsession_pk(remotename, remotekey, deskey)
13526219Swpaul	char *remotename;
13626219Swpaul	netobj *remotekey;
13726219Swpaul	des_block *deskey;
13826219Swpaul{
13926219Swpaul	cryptkeyarg2 arg;
14026219Swpaul	cryptkeyres res;
14126219Swpaul
14226219Swpaul	arg.remotename = remotename;
14326219Swpaul	arg.remotekey = *remotekey;
14426219Swpaul	arg.deskey = *deskey;
14595658Sdes	if (!key_call((u_long)KEY_ENCRYPT_PK, (xdrproc_t)xdr_cryptkeyarg2, &arg,
14695658Sdes			(xdrproc_t)xdr_cryptkeyres, &res)) {
14726219Swpaul		return (-1);
14826219Swpaul	}
14926219Swpaul	if (res.status != KEY_SUCCESS) {
15026219Swpaul		debug("encrypt status is nonzero");
15126219Swpaul		return (-1);
15226219Swpaul	}
15326219Swpaul	*deskey = res.cryptkeyres_u.deskey;
15426219Swpaul	return (0);
15526219Swpaul}
15626219Swpaul
15726219Swpaulint
15826219Swpaulkey_decryptsession_pk(remotename, remotekey, deskey)
15926219Swpaul	char *remotename;
16026219Swpaul	netobj *remotekey;
16126219Swpaul	des_block *deskey;
16226219Swpaul{
16326219Swpaul	cryptkeyarg2 arg;
16426219Swpaul	cryptkeyres res;
16526219Swpaul
16626219Swpaul	arg.remotename = remotename;
16726219Swpaul	arg.remotekey = *remotekey;
16826219Swpaul	arg.deskey = *deskey;
16995658Sdes	if (!key_call((u_long)KEY_DECRYPT_PK, (xdrproc_t)xdr_cryptkeyarg2, &arg,
17095658Sdes			(xdrproc_t)xdr_cryptkeyres, &res)) {
17126219Swpaul		return (-1);
17226219Swpaul	}
17326219Swpaul	if (res.status != KEY_SUCCESS) {
17426219Swpaul		debug("decrypt status is nonzero");
17526219Swpaul		return (-1);
17626219Swpaul	}
17726219Swpaul	*deskey = res.cryptkeyres_u.deskey;
17826219Swpaul	return (0);
17926219Swpaul}
18026219Swpaul
18126219Swpaulint
18226219Swpaulkey_encryptsession(remotename, deskey)
18326219Swpaul	const char *remotename;
18426219Swpaul	des_block *deskey;
18526219Swpaul{
18626219Swpaul	cryptkeyarg arg;
18726219Swpaul	cryptkeyres res;
18826219Swpaul
18926219Swpaul	arg.remotename = (char *) remotename;
19026219Swpaul	arg.deskey = *deskey;
19195658Sdes	if (!key_call((u_long)KEY_ENCRYPT, (xdrproc_t)xdr_cryptkeyarg, &arg,
19295658Sdes			(xdrproc_t)xdr_cryptkeyres, &res)) {
19326219Swpaul		return (-1);
19426219Swpaul	}
19526219Swpaul	if (res.status != KEY_SUCCESS) {
19626219Swpaul		debug("encrypt status is nonzero");
19726219Swpaul		return (-1);
19826219Swpaul	}
19926219Swpaul	*deskey = res.cryptkeyres_u.deskey;
20026219Swpaul	return (0);
20126219Swpaul}
20226219Swpaul
20326219Swpaulint
20426219Swpaulkey_decryptsession(remotename, deskey)
20526219Swpaul	const char *remotename;
20626219Swpaul	des_block *deskey;
20726219Swpaul{
20826219Swpaul	cryptkeyarg arg;
20926219Swpaul	cryptkeyres res;
21026219Swpaul
21126219Swpaul	arg.remotename = (char *) remotename;
21226219Swpaul	arg.deskey = *deskey;
21395658Sdes	if (!key_call((u_long)KEY_DECRYPT, (xdrproc_t)xdr_cryptkeyarg, &arg,
21495658Sdes			(xdrproc_t)xdr_cryptkeyres, &res)) {
21526219Swpaul		return (-1);
21626219Swpaul	}
21726219Swpaul	if (res.status != KEY_SUCCESS) {
21826219Swpaul		debug("decrypt status is nonzero");
21926219Swpaul		return (-1);
22026219Swpaul	}
22126219Swpaul	*deskey = res.cryptkeyres_u.deskey;
22226219Swpaul	return (0);
22326219Swpaul}
22426219Swpaul
22526219Swpaulint
22626219Swpaulkey_gendes(key)
22726219Swpaul	des_block *key;
22826219Swpaul{
22995658Sdes	if (!key_call((u_long)KEY_GEN, (xdrproc_t)xdr_void, NULL,
23095658Sdes			(xdrproc_t)xdr_des_block, key)) {
23126219Swpaul		return (-1);
23226219Swpaul	}
23326219Swpaul	return (0);
23426219Swpaul}
23526219Swpaul
23626219Swpaulint
23726219Swpaulkey_setnet(arg)
23874462Salfredstruct key_netstarg *arg;
23926219Swpaul{
24026219Swpaul	keystatus status;
24126219Swpaul
24226219Swpaul
24395658Sdes	if (!key_call((u_long) KEY_NET_PUT, (xdrproc_t)xdr_key_netstarg, arg,
24495658Sdes			(xdrproc_t)xdr_keystatus, &status)){
24526219Swpaul		return (-1);
24626219Swpaul	}
24726219Swpaul
24826219Swpaul	if (status != KEY_SUCCESS) {
24926219Swpaul		debug("key_setnet status is nonzero");
25026219Swpaul		return (-1);
25126219Swpaul	}
25226219Swpaul	return (1);
25326219Swpaul}
25426219Swpaul
25526219Swpaul
25626219Swpaulint
25726219Swpaulkey_get_conv(pkey, deskey)
25826219Swpaul	char *pkey;
25926219Swpaul	des_block *deskey;
26026219Swpaul{
26126219Swpaul	cryptkeyres res;
26226219Swpaul
26395658Sdes	if (!key_call((u_long) KEY_GET_CONV, (xdrproc_t)xdr_keybuf, pkey,
26495658Sdes			(xdrproc_t)xdr_cryptkeyres, &res)) {
26526219Swpaul		return (-1);
26626219Swpaul	}
26726219Swpaul	if (res.status != KEY_SUCCESS) {
26826219Swpaul		debug("get_conv status is nonzero");
26926219Swpaul		return (-1);
27026219Swpaul	}
27126219Swpaul	*deskey = res.cryptkeyres_u.deskey;
27226219Swpaul	return (0);
27326219Swpaul}
27426219Swpaul
27526219Swpaulstruct  key_call_private {
27626219Swpaul	CLIENT	*client;	/* Client handle */
27726219Swpaul	pid_t	pid;		/* process-id at moment of creation */
27826219Swpaul	uid_t	uid;		/* user-id at last authorization */
27926219Swpaul};
28026219Swpaulstatic struct key_call_private *key_call_private_main = NULL;
281204950Sjhbstatic thread_key_t key_call_key;
282204950Sjhbstatic once_t key_call_once = ONCE_INITIALIZER;
283204950Sjhbstatic int key_call_key_error;
28426219Swpaul
28526219Swpaulstatic void
28626219Swpaulkey_call_destroy(void *vp)
28726219Swpaul{
28892889Sobrien	struct key_call_private *kcp = (struct key_call_private *)vp;
28926219Swpaul
29026219Swpaul	if (kcp) {
29126219Swpaul		if (kcp->client)
29226219Swpaul			clnt_destroy(kcp->client);
29326219Swpaul		free(kcp);
29426219Swpaul	}
29526219Swpaul}
29626219Swpaul
297204950Sjhbstatic void
298204950Sjhbkey_call_init(void)
299204950Sjhb{
300204950Sjhb
301204950Sjhb	key_call_key_error = thr_keycreate(&key_call_key, key_call_destroy);
302204950Sjhb}
303204950Sjhb
30426219Swpaul/*
30526219Swpaul * Keep the handle cached.  This call may be made quite often.
30626219Swpaul */
30726219Swpaulstatic CLIENT *
30826219Swpaulgetkeyserv_handle(vers)
30926219Swpaulint	vers;
31026219Swpaul{
31174462Salfred	void *localhandle;
31274462Salfred	struct netconfig *nconf;
31374462Salfred	struct netconfig *tpconf;
314199784Swollman	struct key_call_private *kcp;
31526219Swpaul	struct timeval wait_time;
31674462Salfred	struct utsname u;
31774462Salfred	int main_thread;
31826219Swpaul	int fd;
31926219Swpaul
32026219Swpaul#define	TOTAL_TIMEOUT	30	/* total timeout talking to keyserver */
32126219Swpaul#define	TOTAL_TRIES	5	/* Number of tries */
32226219Swpaul
32374462Salfred	if ((main_thread = thr_main())) {
32474462Salfred		kcp = key_call_private_main;
32574462Salfred	} else {
326204950Sjhb		if (thr_once(&key_call_once, key_call_init) != 0 ||
327204950Sjhb		    key_call_key_error != 0)
328204950Sjhb			return ((CLIENT *) NULL);
32974462Salfred		kcp = (struct key_call_private *)thr_getspecific(key_call_key);
33074462Salfred	}
33126219Swpaul	if (kcp == (struct key_call_private *)NULL) {
33226219Swpaul		kcp = (struct key_call_private *)malloc(sizeof (*kcp));
33326219Swpaul		if (kcp == (struct key_call_private *)NULL) {
33426219Swpaul			return ((CLIENT *) NULL);
33526219Swpaul		}
33674462Salfred                if (main_thread)
33774462Salfred                        key_call_private_main = kcp;
33874462Salfred                else
33974462Salfred                        thr_setspecific(key_call_key, (void *) kcp);
34026219Swpaul		kcp->client = NULL;
34126219Swpaul	}
34226219Swpaul
34326219Swpaul	/* if pid has changed, destroy client and rebuild */
34426219Swpaul	if (kcp->client != NULL && kcp->pid != getpid()) {
34526219Swpaul		clnt_destroy(kcp->client);
34626219Swpaul		kcp->client = NULL;
34726219Swpaul	}
34826219Swpaul
34926219Swpaul	if (kcp->client != NULL) {
35026219Swpaul		/* if uid has changed, build client handle again */
35126219Swpaul		if (kcp->uid != geteuid()) {
35226219Swpaul			kcp->uid = geteuid();
35326219Swpaul			auth_destroy(kcp->client->cl_auth);
35426219Swpaul			kcp->client->cl_auth =
35526219Swpaul				authsys_create("", kcp->uid, 0, 0, NULL);
35626219Swpaul			if (kcp->client->cl_auth == NULL) {
35726219Swpaul				clnt_destroy(kcp->client);
35826219Swpaul				kcp->client = NULL;
35926219Swpaul				return ((CLIENT *) NULL);
36026219Swpaul			}
36126219Swpaul		}
36226219Swpaul		/* Change the version number to the new one */
36326219Swpaul		clnt_control(kcp->client, CLSET_VERS, (void *)&vers);
36426219Swpaul		return (kcp->client);
36526219Swpaul	}
36674462Salfred	if (!(localhandle = setnetconfig())) {
36774462Salfred		return ((CLIENT *) NULL);
36874462Salfred	}
36974462Salfred        tpconf = NULL;
37074712Sobrien#if defined(__FreeBSD__)
37174462Salfred	if (uname(&u) == -1)
37274712Sobrien#else
37374712Sobrien#if defined(i386)
37474712Sobrien	if (_nuname(&u) == -1)
37574712Sobrien#elif defined(sparc)
37674712Sobrien	if (_uname(&u) == -1)
37774712Sobrien#else
37874712Sobrien#error Unknown architecture!
37974712Sobrien#endif
38074712Sobrien#endif
38174462Salfred	{
38274462Salfred		endnetconfig(localhandle);
38374462Salfred		return ((CLIENT *) NULL);
38474462Salfred        }
38590271Salfred	while ((nconf = getnetconfig(localhandle)) != NULL) {
38674462Salfred		if (strcmp(nconf->nc_protofmly, NC_LOOPBACK) == 0) {
38774462Salfred			/*
38874462Salfred			 * We use COTS_ORD here so that the caller can
38974462Salfred			 * find out immediately if the server is dead.
39074462Salfred			 */
39174462Salfred			if (nconf->nc_semantics == NC_TPI_COTS_ORD) {
39274462Salfred				kcp->client = clnt_tp_create(u.nodename,
39374462Salfred					KEY_PROG, vers, nconf);
39474462Salfred				if (kcp->client)
39574462Salfred					break;
39674462Salfred			} else {
39774462Salfred				tpconf = nconf;
39874462Salfred			}
39974462Salfred		}
40074462Salfred	}
40174462Salfred	if ((kcp->client == (CLIENT *) NULL) && (tpconf))
40274462Salfred		/* Now, try the CLTS or COTS loopback transport */
40374462Salfred		kcp->client = clnt_tp_create(u.nodename,
40474462Salfred			KEY_PROG, vers, tpconf);
40574462Salfred	endnetconfig(localhandle);
40626219Swpaul
40726219Swpaul	if (kcp->client == (CLIENT *) NULL) {
40826219Swpaul		return ((CLIENT *) NULL);
40974462Salfred        }
41026219Swpaul	kcp->uid = geteuid();
41126219Swpaul	kcp->pid = getpid();
41226219Swpaul	kcp->client->cl_auth = authsys_create("", kcp->uid, 0, 0, NULL);
41326219Swpaul	if (kcp->client->cl_auth == NULL) {
41426219Swpaul		clnt_destroy(kcp->client);
41526219Swpaul		kcp->client = NULL;
41626219Swpaul		return ((CLIENT *) NULL);
41726219Swpaul	}
41826219Swpaul
41926219Swpaul	wait_time.tv_sec = TOTAL_TIMEOUT/TOTAL_TRIES;
42026219Swpaul	wait_time.tv_usec = 0;
42126219Swpaul	(void) clnt_control(kcp->client, CLSET_RETRY_TIMEOUT,
42226219Swpaul		(char *)&wait_time);
42326219Swpaul	if (clnt_control(kcp->client, CLGET_FD, (char *)&fd))
42456698Sjasone		_fcntl(fd, F_SETFD, 1);	/* make it "close on exec" */
42526219Swpaul
42626219Swpaul	return (kcp->client);
42726219Swpaul}
42826219Swpaul
42926219Swpaul/* returns  0 on failure, 1 on success */
43026219Swpaul
43126219Swpaulstatic int
43226219Swpaulkey_call(proc, xdr_arg, arg, xdr_rslt, rslt)
43326219Swpaul	u_long proc;
43426219Swpaul	xdrproc_t xdr_arg;
43595658Sdes	void *arg;
43626219Swpaul	xdrproc_t xdr_rslt;
43795658Sdes	void *rslt;
43826219Swpaul{
43926219Swpaul	CLIENT *clnt;
44026219Swpaul	struct timeval wait_time;
44126219Swpaul
44226219Swpaul	if (proc == KEY_ENCRYPT_PK && __key_encryptsession_pk_LOCAL) {
44326219Swpaul		cryptkeyres *res;
44426219Swpaul		res = (*__key_encryptsession_pk_LOCAL)(geteuid(), arg);
44526219Swpaul		*(cryptkeyres*)rslt = *res;
44626219Swpaul		return (1);
44726219Swpaul	} else if (proc == KEY_DECRYPT_PK && __key_decryptsession_pk_LOCAL) {
44826219Swpaul		cryptkeyres *res;
44926219Swpaul		res = (*__key_decryptsession_pk_LOCAL)(geteuid(), arg);
45026219Swpaul		*(cryptkeyres*)rslt = *res;
45126219Swpaul		return (1);
45226219Swpaul	} else if (proc == KEY_GEN && __key_gendes_LOCAL) {
45326219Swpaul		des_block *res;
45426219Swpaul		res = (*__key_gendes_LOCAL)(geteuid(), 0);
45526219Swpaul		*(des_block*)rslt = *res;
45626219Swpaul		return (1);
45726219Swpaul	}
45826219Swpaul
45926219Swpaul	if ((proc == KEY_ENCRYPT_PK) || (proc == KEY_DECRYPT_PK) ||
46026219Swpaul	    (proc == KEY_NET_GET) || (proc == KEY_NET_PUT) ||
46126219Swpaul	    (proc == KEY_GET_CONV))
46226219Swpaul		clnt = getkeyserv_handle(2); /* talk to version 2 */
46326219Swpaul	else
46426219Swpaul		clnt = getkeyserv_handle(1); /* talk to version 1 */
46526219Swpaul
46626219Swpaul	if (clnt == NULL) {
46726219Swpaul		return (0);
46826219Swpaul	}
46926219Swpaul
47026219Swpaul	wait_time.tv_sec = TOTAL_TIMEOUT;
47126219Swpaul	wait_time.tv_usec = 0;
47226219Swpaul
47326219Swpaul	if (clnt_call(clnt, proc, xdr_arg, arg, xdr_rslt, rslt,
47426219Swpaul		wait_time) == RPC_SUCCESS) {
47526219Swpaul		return (1);
47626219Swpaul	} else {
47726219Swpaul		return (0);
47826219Swpaul	}
47926219Swpaul}
480