1 |
|
|
/* $OpenBSD: clnt_udp.c,v 1.32 2015/11/01 03:45:29 guenther Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2010, Oracle America, Inc. |
5 |
|
|
* |
6 |
|
|
* Redistribution and use in source and binary forms, with or without |
7 |
|
|
* modification, are permitted provided that the following conditions are |
8 |
|
|
* met: |
9 |
|
|
* |
10 |
|
|
* * Redistributions of source code must retain the above copyright |
11 |
|
|
* notice, this list of conditions and the following disclaimer. |
12 |
|
|
* * Redistributions in binary form must reproduce the above |
13 |
|
|
* copyright notice, this list of conditions and the following |
14 |
|
|
* disclaimer in the documentation and/or other materials |
15 |
|
|
* provided with the distribution. |
16 |
|
|
* * Neither the name of the "Oracle America, Inc." nor the names of its |
17 |
|
|
* contributors may be used to endorse or promote products derived |
18 |
|
|
* from this software without specific prior written permission. |
19 |
|
|
* |
20 |
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
21 |
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
22 |
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
23 |
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
24 |
|
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
25 |
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
26 |
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
27 |
|
|
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
28 |
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
29 |
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
30 |
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
31 |
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
32 |
|
|
*/ |
33 |
|
|
|
34 |
|
|
/* |
35 |
|
|
* clnt_udp.c, Implements a UDP/IP based, client side RPC. |
36 |
|
|
*/ |
37 |
|
|
|
38 |
|
|
#include <stdio.h> |
39 |
|
|
#include <stdlib.h> |
40 |
|
|
#include <string.h> |
41 |
|
|
#include <unistd.h> |
42 |
|
|
#include <fcntl.h> |
43 |
|
|
#include <rpc/rpc.h> |
44 |
|
|
#include <sys/socket.h> |
45 |
|
|
#include <netdb.h> |
46 |
|
|
#include <errno.h> |
47 |
|
|
#include <rpc/pmap_clnt.h> |
48 |
|
|
|
49 |
|
|
/* |
50 |
|
|
* UDP bases client side rpc operations |
51 |
|
|
*/ |
52 |
|
|
static enum clnt_stat clntudp_call(CLIENT *, u_long, xdrproc_t, caddr_t, |
53 |
|
|
xdrproc_t, caddr_t, struct timeval); |
54 |
|
|
static void clntudp_abort(CLIENT *); |
55 |
|
|
static void clntudp_geterr(CLIENT *, struct rpc_err *); |
56 |
|
|
static bool_t clntudp_freeres(CLIENT *, xdrproc_t, caddr_t); |
57 |
|
|
static bool_t clntudp_control(CLIENT *, u_int, void *); |
58 |
|
|
static void clntudp_destroy(CLIENT *); |
59 |
|
|
|
60 |
|
|
static struct clnt_ops udp_ops = { |
61 |
|
|
clntudp_call, |
62 |
|
|
clntudp_abort, |
63 |
|
|
clntudp_geterr, |
64 |
|
|
clntudp_freeres, |
65 |
|
|
clntudp_destroy, |
66 |
|
|
clntudp_control |
67 |
|
|
}; |
68 |
|
|
|
69 |
|
|
/* |
70 |
|
|
* Private data kept per client handle |
71 |
|
|
*/ |
72 |
|
|
struct cu_data { |
73 |
|
|
int cu_sock; |
74 |
|
|
bool_t cu_closeit; |
75 |
|
|
struct sockaddr_in cu_raddr; |
76 |
|
|
int cu_rlen; |
77 |
|
|
struct timeval cu_wait; |
78 |
|
|
struct timeval cu_total; |
79 |
|
|
struct rpc_err cu_error; |
80 |
|
|
XDR cu_outxdrs; |
81 |
|
|
u_int cu_xdrpos; |
82 |
|
|
u_int cu_sendsz; |
83 |
|
|
char *cu_outbuf; |
84 |
|
|
u_int cu_recvsz; |
85 |
|
|
char cu_inbuf[1]; |
86 |
|
|
}; |
87 |
|
|
|
88 |
|
|
/* |
89 |
|
|
* Create a UDP based client handle. |
90 |
|
|
* If *sockp<0, *sockp is set to a newly created UPD socket. |
91 |
|
|
* If raddr->sin_port is 0 a binder on the remote machine |
92 |
|
|
* is consulted for the correct port number. |
93 |
|
|
* NB: It is the client's responsibility to close *sockp, unless |
94 |
|
|
* clntudp_bufcreate() was called with *sockp = -1 (so it created |
95 |
|
|
* the socket), and CLNT_DESTROY() is used. |
96 |
|
|
* NB: The rpch->cl_auth is initialized to null authentication. |
97 |
|
|
* Caller may wish to set this something more useful. |
98 |
|
|
* |
99 |
|
|
* wait is the amount of time used between retransmitting a call if |
100 |
|
|
* no response has been heard; retransmission occurs until the actual |
101 |
|
|
* rpc call times out. |
102 |
|
|
* |
103 |
|
|
* sendsz and recvsz are the maximum allowable packet sizes that can be |
104 |
|
|
* sent and received. |
105 |
|
|
*/ |
106 |
|
|
CLIENT * |
107 |
|
|
clntudp_bufcreate(struct sockaddr_in *raddr, u_long program, u_long version, |
108 |
|
|
struct timeval wait, int *sockp, u_int sendsz, u_int recvsz) |
109 |
|
|
{ |
110 |
|
|
CLIENT *cl; |
111 |
|
|
struct cu_data *cu = NULL; |
112 |
|
|
struct timeval now; |
113 |
|
|
struct rpc_msg call_msg; |
114 |
|
|
|
115 |
|
|
cl = (CLIENT *)mem_alloc(sizeof(CLIENT)); |
116 |
|
|
if (cl == NULL) { |
117 |
|
|
rpc_createerr.cf_stat = RPC_SYSTEMERROR; |
118 |
|
|
rpc_createerr.cf_error.re_errno = errno; |
119 |
|
|
goto fooy; |
120 |
|
|
} |
121 |
|
|
sendsz = ((sendsz + 3) / 4) * 4; |
122 |
|
|
recvsz = ((recvsz + 3) / 4) * 4; |
123 |
|
|
cu = (struct cu_data *)mem_alloc(sizeof(*cu) + sendsz + recvsz); |
124 |
|
|
if (cu == NULL) { |
125 |
|
|
rpc_createerr.cf_stat = RPC_SYSTEMERROR; |
126 |
|
|
rpc_createerr.cf_error.re_errno = errno; |
127 |
|
|
goto fooy; |
128 |
|
|
} |
129 |
|
|
cu->cu_outbuf = &cu->cu_inbuf[recvsz]; |
130 |
|
|
|
131 |
|
|
(void)gettimeofday(&now, NULL); |
132 |
|
|
if (raddr->sin_port == 0) { |
133 |
|
|
u_short port; |
134 |
|
|
if ((port = |
135 |
|
|
pmap_getport(raddr, program, version, IPPROTO_UDP)) == 0) { |
136 |
|
|
goto fooy; |
137 |
|
|
} |
138 |
|
|
raddr->sin_port = htons(port); |
139 |
|
|
} |
140 |
|
|
cl->cl_ops = &udp_ops; |
141 |
|
|
cl->cl_private = (caddr_t)cu; |
142 |
|
|
cu->cu_raddr = *raddr; |
143 |
|
|
cu->cu_rlen = sizeof (cu->cu_raddr); |
144 |
|
|
cu->cu_wait = wait; |
145 |
|
|
cu->cu_total.tv_sec = -1; |
146 |
|
|
cu->cu_total.tv_usec = -1; |
147 |
|
|
cu->cu_sendsz = sendsz; |
148 |
|
|
cu->cu_recvsz = recvsz; |
149 |
|
|
call_msg.rm_xid = arc4random(); |
150 |
|
|
call_msg.rm_direction = CALL; |
151 |
|
|
call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION; |
152 |
|
|
call_msg.rm_call.cb_prog = program; |
153 |
|
|
call_msg.rm_call.cb_vers = version; |
154 |
|
|
xdrmem_create(&(cu->cu_outxdrs), cu->cu_outbuf, |
155 |
|
|
sendsz, XDR_ENCODE); |
156 |
|
|
if (!xdr_callhdr(&(cu->cu_outxdrs), &call_msg)) { |
157 |
|
|
goto fooy; |
158 |
|
|
} |
159 |
|
|
cu->cu_xdrpos = XDR_GETPOS(&(cu->cu_outxdrs)); |
160 |
|
|
if (*sockp < 0) { |
161 |
|
|
*sockp = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, |
162 |
|
|
IPPROTO_UDP); |
163 |
|
|
if (*sockp < 0) { |
164 |
|
|
rpc_createerr.cf_stat = RPC_SYSTEMERROR; |
165 |
|
|
rpc_createerr.cf_error.re_errno = errno; |
166 |
|
|
goto fooy; |
167 |
|
|
} |
168 |
|
|
/* attempt to bind to priv port */ |
169 |
|
|
(void)bindresvport(*sockp, NULL); |
170 |
|
|
cu->cu_closeit = TRUE; |
171 |
|
|
} else { |
172 |
|
|
cu->cu_closeit = FALSE; |
173 |
|
|
} |
174 |
|
|
cu->cu_sock = *sockp; |
175 |
|
|
cl->cl_auth = authnone_create(); |
176 |
|
|
if (cl->cl_auth == NULL) { |
177 |
|
|
rpc_createerr.cf_stat = RPC_SYSTEMERROR; |
178 |
|
|
rpc_createerr.cf_error.re_errno = errno; |
179 |
|
|
goto fooy; |
180 |
|
|
} |
181 |
|
|
return (cl); |
182 |
|
|
fooy: |
183 |
|
|
if (cu) |
184 |
|
|
mem_free((caddr_t)cu, sizeof(*cu) + sendsz + recvsz); |
185 |
|
|
if (cl) |
186 |
|
|
mem_free((caddr_t)cl, sizeof(CLIENT)); |
187 |
|
|
return (NULL); |
188 |
|
|
} |
189 |
|
|
DEF_WEAK(clntudp_bufcreate); |
190 |
|
|
|
191 |
|
|
CLIENT * |
192 |
|
|
clntudp_create(struct sockaddr_in *raddr, u_long program, u_long version, |
193 |
|
|
struct timeval wait, int *sockp) |
194 |
|
|
{ |
195 |
|
|
|
196 |
|
|
return(clntudp_bufcreate(raddr, program, version, wait, sockp, |
197 |
|
|
UDPMSGSIZE, UDPMSGSIZE)); |
198 |
|
|
} |
199 |
|
|
DEF_WEAK(clntudp_create); |
200 |
|
|
|
201 |
|
|
static enum clnt_stat |
202 |
|
|
clntudp_call(CLIENT *cl, /* client handle */ |
203 |
|
|
u_long proc, /* procedure number */ |
204 |
|
|
xdrproc_t xargs, /* xdr routine for args */ |
205 |
|
|
caddr_t argsp, /* pointer to args */ |
206 |
|
|
xdrproc_t xresults, /* xdr routine for results */ |
207 |
|
|
caddr_t resultsp, /* pointer to results */ |
208 |
|
|
struct timeval utimeout) /* seconds to wait before giving up */ |
209 |
|
|
{ |
210 |
|
|
struct cu_data *cu = (struct cu_data *)cl->cl_private; |
211 |
|
|
XDR *xdrs; |
212 |
|
|
int outlen; |
213 |
|
|
int inlen; |
214 |
|
|
socklen_t fromlen; |
215 |
|
|
struct pollfd pfd[1]; |
216 |
|
|
struct sockaddr_in from; |
217 |
|
|
struct rpc_msg reply_msg; |
218 |
|
|
XDR reply_xdrs; |
219 |
|
|
struct timeval time_waited, start, after, tmp1, tmp2; |
220 |
|
|
bool_t ok; |
221 |
|
|
int nrefreshes = 2; /* number of times to refresh cred */ |
222 |
|
|
struct timeval timeout; |
223 |
|
|
|
224 |
|
|
if (cu->cu_total.tv_usec == -1) |
225 |
|
|
timeout = utimeout; /* use supplied timeout */ |
226 |
|
|
else |
227 |
|
|
timeout = cu->cu_total; /* use default timeout */ |
228 |
|
|
|
229 |
|
|
pfd[0].fd = cu->cu_sock; |
230 |
|
|
pfd[0].events = POLLIN; |
231 |
|
|
timerclear(&time_waited); |
232 |
|
|
call_again: |
233 |
|
|
xdrs = &(cu->cu_outxdrs); |
234 |
|
|
xdrs->x_op = XDR_ENCODE; |
235 |
|
|
XDR_SETPOS(xdrs, cu->cu_xdrpos); |
236 |
|
|
/* |
237 |
|
|
* the transaction is the first thing in the out buffer |
238 |
|
|
*/ |
239 |
|
|
(*(u_short *)(cu->cu_outbuf))++; |
240 |
|
|
if (!XDR_PUTLONG(xdrs, (long *)&proc) || |
241 |
|
|
!AUTH_MARSHALL(cl->cl_auth, xdrs) || |
242 |
|
|
!(*xargs)(xdrs, argsp)) { |
243 |
|
|
return (cu->cu_error.re_status = RPC_CANTENCODEARGS); |
244 |
|
|
} |
245 |
|
|
outlen = (int)XDR_GETPOS(xdrs); |
246 |
|
|
|
247 |
|
|
send_again: |
248 |
|
|
if (sendto(cu->cu_sock, cu->cu_outbuf, outlen, 0, |
249 |
|
|
(struct sockaddr *)&(cu->cu_raddr), cu->cu_rlen) != outlen) { |
250 |
|
|
cu->cu_error.re_errno = errno; |
251 |
|
|
return (cu->cu_error.re_status = RPC_CANTSEND); |
252 |
|
|
} |
253 |
|
|
|
254 |
|
|
/* |
255 |
|
|
* Hack to provide rpc-based message passing |
256 |
|
|
*/ |
257 |
|
|
if (!timerisset(&timeout)) |
258 |
|
|
return (cu->cu_error.re_status = RPC_TIMEDOUT); |
259 |
|
|
|
260 |
|
|
/* |
261 |
|
|
* sub-optimal code appears here because we have |
262 |
|
|
* some clock time to spare while the packets are in flight. |
263 |
|
|
* (We assume that this is actually only executed once.) |
264 |
|
|
*/ |
265 |
|
|
reply_msg.acpted_rply.ar_verf = _null_auth; |
266 |
|
|
reply_msg.acpted_rply.ar_results.where = resultsp; |
267 |
|
|
reply_msg.acpted_rply.ar_results.proc = xresults; |
268 |
|
|
|
269 |
|
|
gettimeofday(&start, NULL); |
270 |
|
|
for (;;) { |
271 |
|
|
switch (poll(pfd, 1, |
272 |
|
|
cu->cu_wait.tv_sec * 1000 + cu->cu_wait.tv_usec / 1000)) { |
273 |
|
|
case 0: |
274 |
|
|
timeradd(&time_waited, &cu->cu_wait, &tmp1); |
275 |
|
|
time_waited = tmp1; |
276 |
|
|
if (timercmp(&time_waited, &timeout, <)) |
277 |
|
|
goto send_again; |
278 |
|
|
return (cu->cu_error.re_status = RPC_TIMEDOUT); |
279 |
|
|
case 1: |
280 |
|
|
if (pfd[0].revents & POLLNVAL) |
281 |
|
|
errno = EBADF; |
282 |
|
|
else if (pfd[0].revents & POLLERR) |
283 |
|
|
errno = EIO; |
284 |
|
|
else |
285 |
|
|
break; |
286 |
|
|
/* FALLTHROUGH */ |
287 |
|
|
case -1: |
288 |
|
|
if (errno == EINTR) { |
289 |
|
|
gettimeofday(&after, NULL); |
290 |
|
|
timersub(&after, &start, &tmp1); |
291 |
|
|
timeradd(&time_waited, &tmp1, &tmp2); |
292 |
|
|
time_waited = tmp2; |
293 |
|
|
if (timercmp(&time_waited, &timeout, <)) |
294 |
|
|
continue; |
295 |
|
|
return (cu->cu_error.re_status = RPC_TIMEDOUT); |
296 |
|
|
} |
297 |
|
|
cu->cu_error.re_errno = errno; |
298 |
|
|
return (cu->cu_error.re_status = RPC_CANTRECV); |
299 |
|
|
} |
300 |
|
|
|
301 |
|
|
do { |
302 |
|
|
fromlen = sizeof(struct sockaddr); |
303 |
|
|
inlen = recvfrom(cu->cu_sock, cu->cu_inbuf, |
304 |
|
|
(int) cu->cu_recvsz, 0, |
305 |
|
|
(struct sockaddr *)&from, &fromlen); |
306 |
|
|
} while (inlen < 0 && errno == EINTR); |
307 |
|
|
if (inlen < 0) { |
308 |
|
|
if (errno == EWOULDBLOCK) |
309 |
|
|
continue; |
310 |
|
|
cu->cu_error.re_errno = errno; |
311 |
|
|
return (cu->cu_error.re_status = RPC_CANTRECV); |
312 |
|
|
} |
313 |
|
|
if (inlen < sizeof(u_int32_t)) |
314 |
|
|
continue; |
315 |
|
|
/* see if reply transaction id matches sent id */ |
316 |
|
|
if (((struct rpc_msg *)(cu->cu_inbuf))->rm_xid != |
317 |
|
|
((struct rpc_msg *)(cu->cu_outbuf))->rm_xid) |
318 |
|
|
continue; |
319 |
|
|
/* we now assume we have the proper reply */ |
320 |
|
|
break; |
321 |
|
|
} |
322 |
|
|
|
323 |
|
|
/* |
324 |
|
|
* now decode and validate the response |
325 |
|
|
*/ |
326 |
|
|
xdrmem_create(&reply_xdrs, cu->cu_inbuf, (u_int)inlen, XDR_DECODE); |
327 |
|
|
ok = xdr_replymsg(&reply_xdrs, &reply_msg); |
328 |
|
|
/* XDR_DESTROY(&reply_xdrs); save a few cycles on noop destroy */ |
329 |
|
|
if (ok) { |
330 |
|
|
#if 0 |
331 |
|
|
/* |
332 |
|
|
* XXX Would like to check these, but call_msg is not |
333 |
|
|
* around. |
334 |
|
|
*/ |
335 |
|
|
if (reply_msg.rm_call.cb_prog != call_msg.rm_call.cb_prog || |
336 |
|
|
reply_msg.rm_call.cb_vers != call_msg.rm_call.cb_vers || |
337 |
|
|
reply_msg.rm_call.cb_proc != call_msg.rm_call.cb_proc) { |
338 |
|
|
goto call_again; /* XXX spin? */ |
339 |
|
|
} |
340 |
|
|
#endif |
341 |
|
|
|
342 |
|
|
_seterr_reply(&reply_msg, &(cu->cu_error)); |
343 |
|
|
if (cu->cu_error.re_status == RPC_SUCCESS) { |
344 |
|
|
if (!AUTH_VALIDATE(cl->cl_auth, |
345 |
|
|
&reply_msg.acpted_rply.ar_verf)) { |
346 |
|
|
cu->cu_error.re_status = RPC_AUTHERROR; |
347 |
|
|
cu->cu_error.re_why = AUTH_INVALIDRESP; |
348 |
|
|
} |
349 |
|
|
if (reply_msg.acpted_rply.ar_verf.oa_base != NULL) { |
350 |
|
|
xdrs->x_op = XDR_FREE; |
351 |
|
|
(void)xdr_opaque_auth(xdrs, |
352 |
|
|
&(reply_msg.acpted_rply.ar_verf)); |
353 |
|
|
} |
354 |
|
|
} else { |
355 |
|
|
/* maybe our credentials need to be refreshed ... */ |
356 |
|
|
if (nrefreshes > 0 && AUTH_REFRESH(cl->cl_auth)) { |
357 |
|
|
nrefreshes--; |
358 |
|
|
goto call_again; |
359 |
|
|
} |
360 |
|
|
} |
361 |
|
|
} else { |
362 |
|
|
/* xdr_replymsg() may have left some things allocated */ |
363 |
|
|
int op = reply_xdrs.x_op; |
364 |
|
|
reply_xdrs.x_op = XDR_FREE; |
365 |
|
|
xdr_replymsg(&reply_xdrs, &reply_msg); |
366 |
|
|
reply_xdrs.x_op = op; |
367 |
|
|
cu->cu_error.re_status = RPC_CANTDECODERES; |
368 |
|
|
} |
369 |
|
|
|
370 |
|
|
return (cu->cu_error.re_status); |
371 |
|
|
} |
372 |
|
|
|
373 |
|
|
static void |
374 |
|
|
clntudp_geterr(CLIENT *cl, struct rpc_err *errp) |
375 |
|
|
{ |
376 |
|
|
struct cu_data *cu = (struct cu_data *)cl->cl_private; |
377 |
|
|
|
378 |
|
|
*errp = cu->cu_error; |
379 |
|
|
} |
380 |
|
|
|
381 |
|
|
|
382 |
|
|
static bool_t |
383 |
|
|
clntudp_freeres(CLIENT *cl, xdrproc_t xdr_res, caddr_t res_ptr) |
384 |
|
|
{ |
385 |
|
|
struct cu_data *cu = (struct cu_data *)cl->cl_private; |
386 |
|
|
XDR *xdrs = &(cu->cu_outxdrs); |
387 |
|
|
|
388 |
|
|
xdrs->x_op = XDR_FREE; |
389 |
|
|
return ((*xdr_res)(xdrs, res_ptr)); |
390 |
|
|
} |
391 |
|
|
|
392 |
|
|
static void |
393 |
|
|
clntudp_abort(CLIENT *clnt) |
394 |
|
|
{ |
395 |
|
|
} |
396 |
|
|
|
397 |
|
|
static bool_t |
398 |
|
|
clntudp_control(CLIENT *cl, u_int request, void *info) |
399 |
|
|
{ |
400 |
|
|
struct cu_data *cu = (struct cu_data *)cl->cl_private; |
401 |
|
|
|
402 |
|
|
switch (request) { |
403 |
|
|
case CLSET_TIMEOUT: |
404 |
|
|
cu->cu_total = *(struct timeval *)info; |
405 |
|
|
break; |
406 |
|
|
case CLGET_TIMEOUT: |
407 |
|
|
*(struct timeval *)info = cu->cu_total; |
408 |
|
|
break; |
409 |
|
|
case CLSET_RETRY_TIMEOUT: |
410 |
|
|
cu->cu_wait = *(struct timeval *)info; |
411 |
|
|
break; |
412 |
|
|
case CLGET_RETRY_TIMEOUT: |
413 |
|
|
*(struct timeval *)info = cu->cu_wait; |
414 |
|
|
break; |
415 |
|
|
case CLGET_SERVER_ADDR: |
416 |
|
|
*(struct sockaddr_in *)info = cu->cu_raddr; |
417 |
|
|
break; |
418 |
|
|
default: |
419 |
|
|
return (FALSE); |
420 |
|
|
} |
421 |
|
|
return (TRUE); |
422 |
|
|
} |
423 |
|
|
|
424 |
|
|
static void |
425 |
|
|
clntudp_destroy(CLIENT *cl) |
426 |
|
|
{ |
427 |
|
|
struct cu_data *cu = (struct cu_data *)cl->cl_private; |
428 |
|
|
|
429 |
|
|
if (cu->cu_closeit && cu->cu_sock != -1) { |
430 |
|
|
(void)close(cu->cu_sock); |
431 |
|
|
} |
432 |
|
|
XDR_DESTROY(&(cu->cu_outxdrs)); |
433 |
|
|
mem_free((caddr_t)cu, (sizeof(*cu) + cu->cu_sendsz + cu->cu_recvsz)); |
434 |
|
|
mem_free((caddr_t)cl, sizeof(CLIENT)); |
435 |
|
|
} |