82b1cd0f1b
partway through its attempt to decode the result structure sent by the server. If this happens, it can leave the result partially populated with dynamically allocated memory. In this event, the xdr_replymsg() failure is detected and RPC_CANTDECODERES is returned, but the memory in the partially populated result struct is not free()d. The end result is that memory is leaked when an RPC_CANTDECODERES error occurs. (This condition can occur if a CLIENT * handle is created using clntudp_bufcreate() with a receive buffer size that is too small to handle the result sent by the server.) Fixed by setting reply_xdrs.x_op to XDR_FREE and calling xdr_replymsg() again to free the memory if an RPC_CANTDECODERES error is detected. I suspect that the clnt_tcp.c, clnt_unix.c and clnt_raw.c modules may ha a similar problem, but I haven't duplicated the condition with those yet. Found by: dbmalloc
568 lines
15 KiB
C
568 lines
15 KiB
C
/*
|
|
* Sun RPC is a product of Sun Microsystems, Inc. and is provided for
|
|
* unrestricted use provided that this legend is included on all tape
|
|
* media and as a part of the software program in whole or part. Users
|
|
* may copy or modify Sun RPC without charge, but are not authorized
|
|
* to license or distribute it to anyone else except as part of a product or
|
|
* program developed by the user.
|
|
*
|
|
* SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
|
|
* WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
|
|
*
|
|
* Sun RPC is provided with no support and without any obligation on the
|
|
* part of Sun Microsystems, Inc. to assist in its use, correction,
|
|
* modification or enhancement.
|
|
*
|
|
* SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
|
|
* INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
|
|
* OR ANY PART THEREOF.
|
|
*
|
|
* In no event will Sun Microsystems, Inc. be liable for any lost revenue
|
|
* or profits or other special, indirect and consequential damages, even if
|
|
* Sun has been advised of the possibility of such damages.
|
|
*
|
|
* Sun Microsystems, Inc.
|
|
* 2550 Garcia Avenue
|
|
* Mountain View, California 94043
|
|
*/
|
|
|
|
#if defined(LIBC_SCCS) && !defined(lint)
|
|
/*static char *sccsid = "from: @(#)clnt_udp.c 1.39 87/08/11 Copyr 1984 Sun Micro";*/
|
|
/*static char *sccsid = "from: @(#)clnt_udp.c 2.2 88/08/01 4.0 RPCSRC";*/
|
|
static char *rcsid = "$Id: clnt_udp.c,v 1.8 1996/12/30 14:40:34 peter Exp $";
|
|
#endif
|
|
|
|
/*
|
|
* clnt_udp.c, Implements a UDP/IP based, client side RPC.
|
|
*
|
|
* Copyright (C) 1984, Sun Microsystems, Inc.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <rpc/rpc.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/ioctl.h>
|
|
#include <netdb.h>
|
|
#include <errno.h>
|
|
#include <rpc/pmap_clnt.h>
|
|
|
|
/*
|
|
* UDP bases client side rpc operations
|
|
*/
|
|
static enum clnt_stat clntudp_call();
|
|
static void clntudp_abort();
|
|
static void clntudp_geterr();
|
|
static bool_t clntudp_freeres();
|
|
static bool_t clntudp_control();
|
|
static void clntudp_destroy();
|
|
|
|
static struct clnt_ops udp_ops = {
|
|
clntudp_call,
|
|
clntudp_abort,
|
|
clntudp_geterr,
|
|
clntudp_freeres,
|
|
clntudp_destroy,
|
|
clntudp_control
|
|
};
|
|
|
|
/*
|
|
* Private data kept per client handle
|
|
*/
|
|
struct cu_data {
|
|
int cu_sock;
|
|
bool_t cu_closeit;
|
|
struct sockaddr_in cu_raddr;
|
|
int cu_rlen;
|
|
struct timeval cu_wait;
|
|
struct timeval cu_total;
|
|
struct rpc_err cu_error;
|
|
XDR cu_outxdrs;
|
|
u_int cu_xdrpos;
|
|
u_int cu_sendsz;
|
|
char *cu_outbuf;
|
|
u_int cu_recvsz;
|
|
char cu_inbuf[1];
|
|
};
|
|
|
|
/*
|
|
* Create a UDP based client handle.
|
|
* If *sockp<0, *sockp is set to a newly created UPD socket.
|
|
* If raddr->sin_port is 0 a binder on the remote machine
|
|
* is consulted for the correct port number.
|
|
* NB: It is the clients responsibility to close *sockp.
|
|
* NB: The rpch->cl_auth is initialized to null authentication.
|
|
* Caller may wish to set this something more useful.
|
|
*
|
|
* wait is the amount of time used between retransmitting a call if
|
|
* no response has been heard; retransmition occurs until the actual
|
|
* rpc call times out.
|
|
*
|
|
* sendsz and recvsz are the maximum allowable packet sizes that can be
|
|
* sent and received.
|
|
*/
|
|
CLIENT *
|
|
clntudp_bufcreate(raddr, program, version, wait, sockp, sendsz, recvsz)
|
|
struct sockaddr_in *raddr;
|
|
u_long program;
|
|
u_long version;
|
|
struct timeval wait;
|
|
register int *sockp;
|
|
u_int sendsz;
|
|
u_int recvsz;
|
|
{
|
|
CLIENT *cl;
|
|
register struct cu_data *cu = NULL;
|
|
struct timeval now;
|
|
struct rpc_msg call_msg;
|
|
static u_int32_t disrupt;
|
|
|
|
if (disrupt == 0)
|
|
disrupt = (u_int32_t)(long)raddr;
|
|
|
|
cl = (CLIENT *)mem_alloc(sizeof(CLIENT));
|
|
if (cl == NULL) {
|
|
(void) fprintf(stderr, "clntudp_create: out of memory\n");
|
|
rpc_createerr.cf_stat = RPC_SYSTEMERROR;
|
|
rpc_createerr.cf_error.re_errno = errno;
|
|
goto fooy;
|
|
}
|
|
sendsz = ((sendsz + 3) / 4) * 4;
|
|
recvsz = ((recvsz + 3) / 4) * 4;
|
|
cu = (struct cu_data *)mem_alloc(sizeof(*cu) + sendsz + recvsz);
|
|
if (cu == NULL) {
|
|
(void) fprintf(stderr, "clntudp_create: out of memory\n");
|
|
rpc_createerr.cf_stat = RPC_SYSTEMERROR;
|
|
rpc_createerr.cf_error.re_errno = errno;
|
|
goto fooy;
|
|
}
|
|
cu->cu_outbuf = &cu->cu_inbuf[recvsz];
|
|
|
|
(void)gettimeofday(&now, (struct timezone *)0);
|
|
if (raddr->sin_port == 0) {
|
|
u_short port;
|
|
if ((port =
|
|
pmap_getport(raddr, program, version, IPPROTO_UDP)) == 0) {
|
|
goto fooy;
|
|
}
|
|
raddr->sin_port = htons(port);
|
|
}
|
|
cl->cl_ops = &udp_ops;
|
|
cl->cl_private = (caddr_t)cu;
|
|
cu->cu_raddr = *raddr;
|
|
cu->cu_rlen = sizeof (cu->cu_raddr);
|
|
cu->cu_wait = wait;
|
|
cu->cu_total.tv_sec = -1;
|
|
cu->cu_total.tv_usec = -1;
|
|
cu->cu_sendsz = sendsz;
|
|
cu->cu_recvsz = recvsz;
|
|
call_msg.rm_xid = (++disrupt) ^ getpid() ^ now.tv_sec ^ now.tv_usec;
|
|
call_msg.rm_direction = CALL;
|
|
call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION;
|
|
call_msg.rm_call.cb_prog = program;
|
|
call_msg.rm_call.cb_vers = version;
|
|
xdrmem_create(&(cu->cu_outxdrs), cu->cu_outbuf,
|
|
sendsz, XDR_ENCODE);
|
|
if (! xdr_callhdr(&(cu->cu_outxdrs), &call_msg)) {
|
|
goto fooy;
|
|
}
|
|
cu->cu_xdrpos = XDR_GETPOS(&(cu->cu_outxdrs));
|
|
if (*sockp < 0) {
|
|
int dontblock = 1;
|
|
|
|
*sockp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (*sockp < 0) {
|
|
rpc_createerr.cf_stat = RPC_SYSTEMERROR;
|
|
rpc_createerr.cf_error.re_errno = errno;
|
|
goto fooy;
|
|
}
|
|
/* attempt to bind to priv port */
|
|
(void)bindresvport(*sockp, (struct sockaddr_in *)0);
|
|
/* the sockets rpc controls are non-blocking */
|
|
(void)ioctl(*sockp, FIONBIO, (char *) &dontblock);
|
|
cu->cu_closeit = TRUE;
|
|
} else {
|
|
cu->cu_closeit = FALSE;
|
|
}
|
|
cu->cu_sock = *sockp;
|
|
cl->cl_auth = authnone_create();
|
|
return (cl);
|
|
fooy:
|
|
if (cu)
|
|
mem_free((caddr_t)cu, sizeof(*cu) + sendsz + recvsz);
|
|
if (cl)
|
|
mem_free((caddr_t)cl, sizeof(CLIENT));
|
|
return ((CLIENT *)NULL);
|
|
}
|
|
|
|
CLIENT *
|
|
clntudp_create(raddr, program, version, wait, sockp)
|
|
struct sockaddr_in *raddr;
|
|
u_long program;
|
|
u_long version;
|
|
struct timeval wait;
|
|
register int *sockp;
|
|
{
|
|
|
|
return(clntudp_bufcreate(raddr, program, version, wait, sockp,
|
|
UDPMSGSIZE, UDPMSGSIZE));
|
|
}
|
|
|
|
static enum clnt_stat
|
|
clntudp_call(cl, proc, xargs, argsp, xresults, resultsp, utimeout)
|
|
register CLIENT *cl; /* client handle */
|
|
u_long proc; /* procedure number */
|
|
xdrproc_t xargs; /* xdr routine for args */
|
|
caddr_t argsp; /* pointer to args */
|
|
xdrproc_t xresults; /* xdr routine for results */
|
|
caddr_t resultsp; /* pointer to results */
|
|
struct timeval utimeout; /* seconds to wait before giving up */
|
|
{
|
|
register struct cu_data *cu = (struct cu_data *)cl->cl_private;
|
|
register XDR *xdrs;
|
|
register int outlen;
|
|
register int inlen;
|
|
int fromlen;
|
|
fd_set *fds, readfds;
|
|
struct sockaddr_in from;
|
|
struct rpc_msg reply_msg;
|
|
XDR reply_xdrs;
|
|
struct timeval time_waited, start, after, tmp1, tmp2, tv;
|
|
bool_t ok;
|
|
int nrefreshes = 2; /* number of times to refresh cred */
|
|
struct timeval timeout;
|
|
|
|
if (cu->cu_total.tv_usec == -1)
|
|
timeout = utimeout; /* use supplied timeout */
|
|
else
|
|
timeout = cu->cu_total; /* use default timeout */
|
|
|
|
if (cu->cu_sock + 1 > FD_SETSIZE) {
|
|
int bytes = howmany(cu->cu_sock + 1, NFDBITS) * sizeof(fd_mask);
|
|
fds = (fd_set *)malloc(bytes);
|
|
if (fds == NULL)
|
|
return (cu->cu_error.re_status = RPC_CANTSEND);
|
|
memset(fds, 0, bytes);
|
|
} else {
|
|
fds = &readfds;
|
|
FD_ZERO(fds);
|
|
}
|
|
|
|
timerclear(&time_waited);
|
|
|
|
call_again:
|
|
xdrs = &(cu->cu_outxdrs);
|
|
xdrs->x_op = XDR_ENCODE;
|
|
XDR_SETPOS(xdrs, cu->cu_xdrpos);
|
|
/*
|
|
* the transaction is the first thing in the out buffer
|
|
*/
|
|
(*(u_short *)(cu->cu_outbuf))++;
|
|
if ((! XDR_PUTLONG(xdrs, (long *)&proc)) ||
|
|
(! AUTH_MARSHALL(cl->cl_auth, xdrs)) ||
|
|
(! (*xargs)(xdrs, argsp))) {
|
|
if (fds != &readfds)
|
|
free(fds);
|
|
return (cu->cu_error.re_status = RPC_CANTENCODEARGS);
|
|
}
|
|
outlen = (int)XDR_GETPOS(xdrs);
|
|
|
|
send_again:
|
|
if (sendto(cu->cu_sock, cu->cu_outbuf, outlen, 0,
|
|
(struct sockaddr *)&(cu->cu_raddr), cu->cu_rlen) != outlen) {
|
|
cu->cu_error.re_errno = errno;
|
|
if (fds != &readfds)
|
|
free(fds);
|
|
return (cu->cu_error.re_status = RPC_CANTSEND);
|
|
}
|
|
|
|
/*
|
|
* Hack to provide rpc-based message passing
|
|
*/
|
|
if (!timerisset(&timeout)) {
|
|
if (fds != &readfds)
|
|
free(fds);
|
|
return (cu->cu_error.re_status = RPC_TIMEDOUT);
|
|
}
|
|
/*
|
|
* sub-optimal code appears here because we have
|
|
* some clock time to spare while the packets are in flight.
|
|
* (We assume that this is actually only executed once.)
|
|
*/
|
|
reply_msg.acpted_rply.ar_verf = _null_auth;
|
|
reply_msg.acpted_rply.ar_results.where = resultsp;
|
|
reply_msg.acpted_rply.ar_results.proc = xresults;
|
|
|
|
gettimeofday(&start, NULL);
|
|
for (;;) {
|
|
/* XXX we know the other bits are still clear */
|
|
FD_SET(cu->cu_sock, fds);
|
|
tv = cu->cu_wait;
|
|
switch (select(cu->cu_sock+1, fds, NULL, NULL, &tv)) {
|
|
|
|
case 0:
|
|
timeradd(&time_waited, &cu->cu_wait, &tmp1);
|
|
time_waited = tmp1;
|
|
if (timercmp(&time_waited, &timeout, <))
|
|
goto send_again;
|
|
if (fds != &readfds)
|
|
free(fds);
|
|
return (cu->cu_error.re_status = RPC_TIMEDOUT);
|
|
|
|
case -1:
|
|
if (errno == EINTR) {
|
|
gettimeofday(&after, NULL);
|
|
timersub(&after, &start, &tmp1);
|
|
timeradd(&time_waited, &tmp1, &tmp2);
|
|
time_waited = tmp2;
|
|
if (timercmp(&time_waited, &timeout, <))
|
|
continue;
|
|
if (fds != &readfds)
|
|
free(fds);
|
|
return (cu->cu_error.re_status = RPC_TIMEDOUT);
|
|
}
|
|
cu->cu_error.re_errno = errno;
|
|
if (fds != &readfds)
|
|
free(fds);
|
|
return (cu->cu_error.re_status = RPC_CANTRECV);
|
|
}
|
|
|
|
do {
|
|
fromlen = sizeof(struct sockaddr);
|
|
inlen = recvfrom(cu->cu_sock, cu->cu_inbuf,
|
|
(int) cu->cu_recvsz, 0,
|
|
(struct sockaddr *)&from, &fromlen);
|
|
} while (inlen < 0 && errno == EINTR);
|
|
if (inlen < 0) {
|
|
if (errno == EWOULDBLOCK)
|
|
continue;
|
|
cu->cu_error.re_errno = errno;
|
|
if (fds != &readfds)
|
|
free(fds);
|
|
return (cu->cu_error.re_status = RPC_CANTRECV);
|
|
}
|
|
if (inlen < sizeof(u_int32_t))
|
|
continue;
|
|
/* see if reply transaction id matches sent id */
|
|
if (*((u_int32_t *)(cu->cu_inbuf)) != *((u_int32_t *)(cu->cu_outbuf)))
|
|
continue;
|
|
/* we now assume we have the proper reply */
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* now decode and validate the response
|
|
*/
|
|
xdrmem_create(&reply_xdrs, cu->cu_inbuf, (u_int)inlen, XDR_DECODE);
|
|
ok = xdr_replymsg(&reply_xdrs, &reply_msg);
|
|
/* XDR_DESTROY(&reply_xdrs); save a few cycles on noop destroy */
|
|
if (ok) {
|
|
_seterr_reply(&reply_msg, &(cu->cu_error));
|
|
if (cu->cu_error.re_status == RPC_SUCCESS) {
|
|
if (! AUTH_VALIDATE(cl->cl_auth,
|
|
&reply_msg.acpted_rply.ar_verf)) {
|
|
cu->cu_error.re_status = RPC_AUTHERROR;
|
|
cu->cu_error.re_why = AUTH_INVALIDRESP;
|
|
}
|
|
if (reply_msg.acpted_rply.ar_verf.oa_base != NULL) {
|
|
xdrs->x_op = XDR_FREE;
|
|
(void)xdr_opaque_auth(xdrs,
|
|
&(reply_msg.acpted_rply.ar_verf));
|
|
}
|
|
} /* end successful completion */
|
|
else {
|
|
/* maybe our credentials need to be refreshed ... */
|
|
if (nrefreshes > 0 && AUTH_REFRESH(cl->cl_auth)) {
|
|
nrefreshes--;
|
|
goto call_again;
|
|
}
|
|
} /* end of unsuccessful completion */
|
|
} /* end of valid reply message */
|
|
else {
|
|
/*
|
|
* It's possible for xdr_replymsg() to fail partway
|
|
* through its attempt to decode the result from the
|
|
* server. If this happens, it will leave the reply
|
|
* structure partially populated with dynamically
|
|
* allocated memory. (This can happen if someone uses
|
|
* clntudp_bufcreate() to create a CLIENT handle and
|
|
* specifies a receive buffer size that is too small.)
|
|
* This memory must be free()ed to avoid a leak.
|
|
*/
|
|
int op = reply_xdrs.x_op;
|
|
reply_xdrs.x_op = XDR_FREE;
|
|
xdr_replymsg(&reply_xdrs, &reply_msg);
|
|
reply_xdrs.x_op = op;
|
|
cu->cu_error.re_status = RPC_CANTDECODERES;
|
|
}
|
|
if (fds != &readfds)
|
|
free(fds);
|
|
return (cu->cu_error.re_status);
|
|
}
|
|
|
|
static void
|
|
clntudp_geterr(cl, errp)
|
|
CLIENT *cl;
|
|
struct rpc_err *errp;
|
|
{
|
|
register struct cu_data *cu = (struct cu_data *)cl->cl_private;
|
|
|
|
*errp = cu->cu_error;
|
|
}
|
|
|
|
|
|
static bool_t
|
|
clntudp_freeres(cl, xdr_res, res_ptr)
|
|
CLIENT *cl;
|
|
xdrproc_t xdr_res;
|
|
caddr_t res_ptr;
|
|
{
|
|
register struct cu_data *cu = (struct cu_data *)cl->cl_private;
|
|
register XDR *xdrs = &(cu->cu_outxdrs);
|
|
|
|
xdrs->x_op = XDR_FREE;
|
|
return ((*xdr_res)(xdrs, res_ptr));
|
|
}
|
|
|
|
static void
|
|
clntudp_abort(/*h*/)
|
|
/*CLIENT *h;*/
|
|
{
|
|
}
|
|
|
|
|
|
static bool_t
|
|
clntudp_control(cl, request, info)
|
|
CLIENT *cl;
|
|
int request;
|
|
char *info;
|
|
{
|
|
register struct cu_data *cu = (struct cu_data *)cl->cl_private;
|
|
register struct timeval *tv;
|
|
int len;
|
|
|
|
switch (request) {
|
|
case CLSET_FD_CLOSE:
|
|
cu->cu_closeit = TRUE;
|
|
break;
|
|
case CLSET_FD_NCLOSE:
|
|
cu->cu_closeit = FALSE;
|
|
break;
|
|
case CLSET_TIMEOUT:
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
tv = (struct timeval *)info;
|
|
cu->cu_total.tv_sec = tv->tv_sec;
|
|
cu->cu_total.tv_usec = tv->tv_usec;
|
|
break;
|
|
case CLGET_TIMEOUT:
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
*(struct timeval *)info = cu->cu_total;
|
|
break;
|
|
case CLSET_RETRY_TIMEOUT:
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
tv = (struct timeval *)info;
|
|
cu->cu_wait.tv_sec = tv->tv_sec;
|
|
cu->cu_wait.tv_usec = tv->tv_usec;
|
|
break;
|
|
case CLGET_RETRY_TIMEOUT:
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
*(struct timeval *)info = cu->cu_wait;
|
|
break;
|
|
case CLGET_SERVER_ADDR:
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
*(struct sockaddr_in *)info = cu->cu_raddr;
|
|
break;
|
|
case CLGET_FD:
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
*(int *)info = cu->cu_sock;
|
|
break;
|
|
case CLGET_XID:
|
|
/*
|
|
* use the knowledge that xid is the
|
|
* first element in the call structure *.
|
|
* This will get the xid of the PREVIOUS call
|
|
*/
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
*(u_long *)info = ntohl(*(u_long *)cu->cu_outbuf);
|
|
break;
|
|
case CLSET_XID:
|
|
/* This will set the xid of the NEXT call */
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
*(u_long *)cu->cu_outbuf = htonl(*(u_long *)info - 1);
|
|
/* decrement by 1 as clntudp_call() increments once */
|
|
case CLGET_VERS:
|
|
/*
|
|
* This RELIES on the information that, in the call body,
|
|
* the version number field is the fifth field from the
|
|
* begining of the RPC header. MUST be changed if the
|
|
* call_struct is changed
|
|
*/
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
*(u_long *)info = ntohl(*(u_long *)(cu->cu_outbuf +
|
|
4 * BYTES_PER_XDR_UNIT));
|
|
break;
|
|
case CLSET_VERS:
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
*(u_long *)(cu->cu_outbuf + 4 * BYTES_PER_XDR_UNIT)
|
|
= htonl(*(u_long *)info);
|
|
break;
|
|
case CLGET_PROG:
|
|
/*
|
|
* This RELIES on the information that, in the call body,
|
|
* the program number field is the field from the
|
|
* begining of the RPC header. MUST be changed if the
|
|
* call_struct is changed
|
|
*/
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
*(u_long *)info = ntohl(*(u_long *)(cu->cu_outbuf +
|
|
3 * BYTES_PER_XDR_UNIT));
|
|
break;
|
|
case CLSET_PROG:
|
|
if (info == NULL)
|
|
return(FALSE);
|
|
*(u_long *)(cu->cu_outbuf + 3 * BYTES_PER_XDR_UNIT)
|
|
= htonl(*(u_long *)info);
|
|
break;
|
|
case CLGET_LOCAL_ADDR:
|
|
len = sizeof(struct sockaddr);
|
|
if (getsockname(cu->cu_sock, (struct sockaddr *)info, &len) <0)
|
|
return(FALSE);
|
|
break;
|
|
case CLGET_SVC_ADDR:
|
|
case CLSET_SVC_ADDR:
|
|
case CLSET_PUSH_TIMOD:
|
|
case CLSET_POP_TIMOD:
|
|
default:
|
|
return (FALSE);
|
|
}
|
|
return (TRUE);
|
|
}
|
|
|
|
static void
|
|
clntudp_destroy(cl)
|
|
CLIENT *cl;
|
|
{
|
|
register struct cu_data *cu = (struct cu_data *)cl->cl_private;
|
|
|
|
if (cu->cu_closeit) {
|
|
(void)close(cu->cu_sock);
|
|
}
|
|
XDR_DESTROY(&(cu->cu_outxdrs));
|
|
mem_free((caddr_t)cu, (sizeof(*cu) + cu->cu_sendsz + cu->cu_recvsz));
|
|
mem_free((caddr_t)cl, sizeof(CLIENT));
|
|
}
|