1 |
|
|
/* $OpenBSD: svc_tcp.c,v 1.37 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 |
|
|
* svc_tcp.c, Server side for TCP/IP based RPC. |
36 |
|
|
* |
37 |
|
|
* Actually implements two flavors of transporter - |
38 |
|
|
* a tcp rendezvouser (a listner and connection establisher) |
39 |
|
|
* and a record/tcp stream. |
40 |
|
|
*/ |
41 |
|
|
|
42 |
|
|
#include <stdio.h> |
43 |
|
|
#include <stdlib.h> |
44 |
|
|
#include <string.h> |
45 |
|
|
#include <unistd.h> |
46 |
|
|
#include <rpc/rpc.h> |
47 |
|
|
#include <sys/socket.h> |
48 |
|
|
#include <errno.h> |
49 |
|
|
|
50 |
|
|
#include <netinet/in.h> |
51 |
|
|
#include <netinet/ip.h> |
52 |
|
|
#include <netinet/ip_var.h> |
53 |
|
|
|
54 |
|
|
/* |
55 |
|
|
* Ops vector for TCP/IP based rpc service handle |
56 |
|
|
*/ |
57 |
|
|
static bool_t svctcp_recv(SVCXPRT *xprt, struct rpc_msg *msg); |
58 |
|
|
static enum xprt_stat svctcp_stat(SVCXPRT *xprt); |
59 |
|
|
static bool_t svctcp_getargs(SVCXPRT *xprt, xdrproc_t xdr_args, |
60 |
|
|
caddr_t args_ptr); |
61 |
|
|
static bool_t svctcp_reply(SVCXPRT *xprt, struct rpc_msg *msg); |
62 |
|
|
static bool_t svctcp_freeargs(SVCXPRT *xprt, xdrproc_t xdr_args, |
63 |
|
|
caddr_t args_ptr); |
64 |
|
|
static void svctcp_destroy(SVCXPRT *xprt); |
65 |
|
|
|
66 |
|
|
static struct xp_ops svctcp_op = { |
67 |
|
|
svctcp_recv, |
68 |
|
|
svctcp_stat, |
69 |
|
|
svctcp_getargs, |
70 |
|
|
svctcp_reply, |
71 |
|
|
svctcp_freeargs, |
72 |
|
|
svctcp_destroy |
73 |
|
|
}; |
74 |
|
|
|
75 |
|
|
/* |
76 |
|
|
* Ops vector for TCP/IP rendezvous handler |
77 |
|
|
*/ |
78 |
|
|
static bool_t rendezvous_request(SVCXPRT *xprt, struct rpc_msg *); |
79 |
|
|
static enum xprt_stat rendezvous_stat(SVCXPRT *xprt); |
80 |
|
|
|
81 |
|
|
static struct xp_ops svctcp_rendezvous_op = { |
82 |
|
|
rendezvous_request, |
83 |
|
|
rendezvous_stat, |
84 |
|
|
/* XXX abort illegal in library */ |
85 |
|
|
(bool_t (*)(struct __rpc_svcxprt *, xdrproc_t, caddr_t))abort, |
86 |
|
|
(bool_t (*)(struct __rpc_svcxprt *, struct rpc_msg *))abort, |
87 |
|
|
(bool_t (*)(struct __rpc_svcxprt *, xdrproc_t, caddr_t))abort, |
88 |
|
|
svctcp_destroy |
89 |
|
|
}; |
90 |
|
|
|
91 |
|
|
static int readtcp(SVCXPRT *xprt, caddr_t buf, int len), |
92 |
|
|
writetcp(SVCXPRT *xprt, caddr_t buf, int len); |
93 |
|
|
static SVCXPRT *makefd_xprt(int fd, u_int sendsize, u_int recvsize); |
94 |
|
|
|
95 |
|
|
struct tcp_rendezvous { /* kept in xprt->xp_p1 */ |
96 |
|
|
u_int sendsize; |
97 |
|
|
u_int recvsize; |
98 |
|
|
}; |
99 |
|
|
|
100 |
|
|
struct tcp_conn { /* kept in xprt->xp_p1 */ |
101 |
|
|
enum xprt_stat strm_stat; |
102 |
|
|
u_long x_id; |
103 |
|
|
XDR xdrs; |
104 |
|
|
char verf_body[MAX_AUTH_BYTES]; |
105 |
|
|
}; |
106 |
|
|
|
107 |
|
|
/* |
108 |
|
|
* Usage: |
109 |
|
|
* xprt = svctcp_create(sock, send_buf_size, recv_buf_size); |
110 |
|
|
* |
111 |
|
|
* Creates, registers, and returns a (rpc) tcp based transporter. |
112 |
|
|
* Once *xprt is initialized, it is registered as a transporter |
113 |
|
|
* see (svc.h, xprt_register). This routine returns |
114 |
|
|
* a NULL if a problem occurred. |
115 |
|
|
* |
116 |
|
|
* If sock<0 then a socket is created, else sock is used. |
117 |
|
|
* If the socket, sock is not bound to a port then svctcp_create |
118 |
|
|
* binds it to an arbitrary port. The routine then starts a tcp |
119 |
|
|
* listener on the socket's associated port. In any (successful) case, |
120 |
|
|
* xprt->xp_sock is the registered socket number and xprt->xp_port is the |
121 |
|
|
* associated port number. |
122 |
|
|
* |
123 |
|
|
* Since tcp streams do buffered io similar to stdio, the caller can specify |
124 |
|
|
* how big the send and receive buffers are via the second and third parms; |
125 |
|
|
* 0 => use the system default. |
126 |
|
|
*/ |
127 |
|
|
SVCXPRT * |
128 |
|
|
svctcp_create(int sock, u_int sendsize, u_int recvsize) |
129 |
|
|
{ |
130 |
|
|
bool_t madesock = FALSE; |
131 |
|
|
SVCXPRT *xprt; |
132 |
|
|
struct tcp_rendezvous *r; |
133 |
|
|
struct sockaddr_in addr; |
134 |
|
|
socklen_t len = sizeof(struct sockaddr_in); |
135 |
|
|
|
136 |
|
|
if (sock == RPC_ANYSOCK) { |
137 |
|
|
if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) |
138 |
|
|
return (NULL); |
139 |
|
|
madesock = TRUE; |
140 |
|
|
} |
141 |
|
|
memset(&addr, 0, sizeof (addr)); |
142 |
|
|
addr.sin_len = sizeof(struct sockaddr_in); |
143 |
|
|
addr.sin_family = AF_INET; |
144 |
|
|
if (bindresvport(sock, &addr)) { |
145 |
|
|
addr.sin_port = 0; |
146 |
|
|
(void)bind(sock, (struct sockaddr *)&addr, len); |
147 |
|
|
} |
148 |
|
|
if ((getsockname(sock, (struct sockaddr *)&addr, &len) != 0) || |
149 |
|
|
(listen(sock, 2) != 0)) { |
150 |
|
|
if (madesock) |
151 |
|
|
(void)close(sock); |
152 |
|
|
return (NULL); |
153 |
|
|
} |
154 |
|
|
r = (struct tcp_rendezvous *)mem_alloc(sizeof(*r)); |
155 |
|
|
if (r == NULL) { |
156 |
|
|
if (madesock) |
157 |
|
|
(void)close(sock); |
158 |
|
|
return (NULL); |
159 |
|
|
} |
160 |
|
|
r->sendsize = sendsize; |
161 |
|
|
r->recvsize = recvsize; |
162 |
|
|
xprt = (SVCXPRT *)mem_alloc(sizeof(SVCXPRT)); |
163 |
|
|
if (xprt == NULL) { |
164 |
|
|
if (madesock) |
165 |
|
|
(void)close(sock); |
166 |
|
|
free(r); |
167 |
|
|
return (NULL); |
168 |
|
|
} |
169 |
|
|
xprt->xp_p2 = NULL; |
170 |
|
|
xprt->xp_p1 = (caddr_t)r; |
171 |
|
|
xprt->xp_verf = _null_auth; |
172 |
|
|
xprt->xp_ops = &svctcp_rendezvous_op; |
173 |
|
|
xprt->xp_port = ntohs(addr.sin_port); |
174 |
|
|
xprt->xp_sock = sock; |
175 |
|
|
if (__xprt_register(xprt) == 0) { |
176 |
|
|
if (madesock) |
177 |
|
|
(void)close(sock); |
178 |
|
|
free(r); |
179 |
|
|
free(xprt); |
180 |
|
|
return (NULL); |
181 |
|
|
} |
182 |
|
|
return (xprt); |
183 |
|
|
} |
184 |
|
|
DEF_WEAK(svctcp_create); |
185 |
|
|
|
186 |
|
|
/* |
187 |
|
|
* Like svtcp_create(), except the routine takes any *open* UNIX file |
188 |
|
|
* descriptor as its first input. |
189 |
|
|
*/ |
190 |
|
|
SVCXPRT * |
191 |
|
|
svcfd_create(int fd, u_int sendsize, u_int recvsize) |
192 |
|
|
{ |
193 |
|
|
|
194 |
|
|
return (makefd_xprt(fd, sendsize, recvsize)); |
195 |
|
|
} |
196 |
|
|
|
197 |
|
|
static SVCXPRT * |
198 |
|
|
makefd_xprt(int fd, u_int sendsize, u_int recvsize) |
199 |
|
|
{ |
200 |
|
|
SVCXPRT *xprt; |
201 |
|
|
struct tcp_conn *cd; |
202 |
|
|
|
203 |
|
|
xprt = (SVCXPRT *)mem_alloc(sizeof(SVCXPRT)); |
204 |
|
|
if (xprt == NULL) |
205 |
|
|
goto done; |
206 |
|
|
cd = (struct tcp_conn *)mem_alloc(sizeof(struct tcp_conn)); |
207 |
|
|
if (cd == NULL) { |
208 |
|
|
mem_free((char *) xprt, sizeof(SVCXPRT)); |
209 |
|
|
xprt = NULL; |
210 |
|
|
goto done; |
211 |
|
|
} |
212 |
|
|
cd->strm_stat = XPRT_IDLE; |
213 |
|
|
xdrrec_create(&(cd->xdrs), sendsize, recvsize, |
214 |
|
|
(caddr_t)xprt, (int(*)(caddr_t, caddr_t, int))readtcp, |
215 |
|
|
(int(*)(caddr_t, caddr_t, int))writetcp); |
216 |
|
|
xprt->xp_p2 = NULL; |
217 |
|
|
xprt->xp_p1 = (caddr_t)cd; |
218 |
|
|
xprt->xp_verf.oa_base = cd->verf_body; |
219 |
|
|
xprt->xp_addrlen = 0; |
220 |
|
|
xprt->xp_ops = &svctcp_op; /* truely deals with calls */ |
221 |
|
|
xprt->xp_port = 0; /* this is a connection, not a rendezvouser */ |
222 |
|
|
xprt->xp_sock = fd; |
223 |
|
|
if (__xprt_register(xprt) == 0) { |
224 |
|
|
free(xprt); |
225 |
|
|
free(cd); |
226 |
|
|
return (NULL); |
227 |
|
|
} |
228 |
|
|
done: |
229 |
|
|
return (xprt); |
230 |
|
|
} |
231 |
|
|
|
232 |
|
|
static bool_t |
233 |
|
|
rendezvous_request(SVCXPRT *xprt, struct rpc_msg *ignored) |
234 |
|
|
{ |
235 |
|
|
int sock; |
236 |
|
|
struct tcp_rendezvous *r; |
237 |
|
|
struct sockaddr_in addr; |
238 |
|
|
socklen_t len; |
239 |
|
|
|
240 |
|
|
r = (struct tcp_rendezvous *)xprt->xp_p1; |
241 |
|
|
again: |
242 |
|
|
len = sizeof(struct sockaddr_in); |
243 |
|
|
if ((sock = accept(xprt->xp_sock, (struct sockaddr *)&addr, |
244 |
|
|
&len)) < 0) { |
245 |
|
|
if (errno == EINTR || errno == EWOULDBLOCK || |
246 |
|
|
errno == ECONNABORTED) |
247 |
|
|
goto again; |
248 |
|
|
return (FALSE); |
249 |
|
|
} |
250 |
|
|
|
251 |
|
|
#ifdef IP_OPTIONS |
252 |
|
|
{ |
253 |
|
|
struct ipoption opts; |
254 |
|
|
socklen_t optsize = sizeof(opts); |
255 |
|
|
int i; |
256 |
|
|
|
257 |
|
|
if (!getsockopt(sock, IPPROTO_IP, IP_OPTIONS, (char *)&opts, |
258 |
|
|
&optsize) && optsize != 0) { |
259 |
|
|
for (i = 0; (char *)&opts.ipopt_list[i] - (char *)&opts < |
260 |
|
|
optsize; ) { |
261 |
|
|
u_char c = (u_char)opts.ipopt_list[i]; |
262 |
|
|
if (c == IPOPT_LSRR || c == IPOPT_SSRR) { |
263 |
|
|
close(sock); |
264 |
|
|
return (FALSE); |
265 |
|
|
} |
266 |
|
|
if (c == IPOPT_EOL) |
267 |
|
|
break; |
268 |
|
|
i += (c == IPOPT_NOP) ? 1 : |
269 |
|
|
(u_char)opts.ipopt_list[i+1]; |
270 |
|
|
} |
271 |
|
|
} |
272 |
|
|
} |
273 |
|
|
#endif |
274 |
|
|
|
275 |
|
|
/* |
276 |
|
|
* XXX careful for ftp bounce attacks. If discovered, close the |
277 |
|
|
* socket and look for another connection. |
278 |
|
|
*/ |
279 |
|
|
if (addr.sin_port == htons(20)) { |
280 |
|
|
close(sock); |
281 |
|
|
return (FALSE); |
282 |
|
|
} |
283 |
|
|
|
284 |
|
|
/* |
285 |
|
|
* make a new transporter (re-uses xprt) |
286 |
|
|
*/ |
287 |
|
|
xprt = makefd_xprt(sock, r->sendsize, r->recvsize); |
288 |
|
|
xprt->xp_raddr = addr; |
289 |
|
|
xprt->xp_addrlen = len; |
290 |
|
|
return (FALSE); /* there is never an rpc msg to be processed */ |
291 |
|
|
} |
292 |
|
|
|
293 |
|
|
static enum xprt_stat |
294 |
|
|
rendezvous_stat(SVCXPRT *xprt) |
295 |
|
|
{ |
296 |
|
|
|
297 |
|
|
return (XPRT_IDLE); |
298 |
|
|
} |
299 |
|
|
|
300 |
|
|
static void |
301 |
|
|
svctcp_destroy(SVCXPRT *xprt) |
302 |
|
|
{ |
303 |
|
|
struct tcp_conn *cd = (struct tcp_conn *)xprt->xp_p1; |
304 |
|
|
|
305 |
|
|
xprt_unregister(xprt); |
306 |
|
|
if (xprt->xp_sock != -1) |
307 |
|
|
(void)close(xprt->xp_sock); |
308 |
|
|
xprt->xp_sock = -1; |
309 |
|
|
if (xprt->xp_port != 0) { |
310 |
|
|
/* a rendezvouser socket */ |
311 |
|
|
xprt->xp_port = 0; |
312 |
|
|
} else { |
313 |
|
|
/* an actual connection socket */ |
314 |
|
|
XDR_DESTROY(&(cd->xdrs)); |
315 |
|
|
} |
316 |
|
|
mem_free((caddr_t)cd, sizeof(struct tcp_conn)); |
317 |
|
|
mem_free((caddr_t)xprt, sizeof(SVCXPRT)); |
318 |
|
|
} |
319 |
|
|
|
320 |
|
|
/* |
321 |
|
|
* All read operations timeout after 35 seconds. |
322 |
|
|
* A timeout is fatal for the connection. |
323 |
|
|
*/ |
324 |
|
|
static struct timeval wait_per_try = { 35, 0 }; |
325 |
|
|
|
326 |
|
|
/* |
327 |
|
|
* reads data from the tcp conection. |
328 |
|
|
* any error is fatal and the connection is closed. |
329 |
|
|
* (And a read of zero bytes is a half closed stream => error.) |
330 |
|
|
*/ |
331 |
|
|
static int |
332 |
|
|
readtcp(SVCXPRT *xprt, caddr_t buf, int len) |
333 |
|
|
{ |
334 |
|
|
int sock = xprt->xp_sock; |
335 |
|
|
int delta, nready; |
336 |
|
|
struct timeval start; |
337 |
|
|
struct timeval tmp1, tmp2; |
338 |
|
|
struct pollfd pfd[1]; |
339 |
|
|
|
340 |
|
|
/* |
341 |
|
|
* All read operations timeout after 35 seconds. |
342 |
|
|
* A timeout is fatal for the connection. |
343 |
|
|
*/ |
344 |
|
|
delta = wait_per_try.tv_sec * 1000; |
345 |
|
|
gettimeofday(&start, NULL); |
346 |
|
|
pfd[0].fd = sock; |
347 |
|
|
pfd[0].events = POLLIN; |
348 |
|
|
do { |
349 |
|
|
nready = poll(pfd, 1, delta); |
350 |
|
|
switch (nready) { |
351 |
|
|
case -1: |
352 |
|
|
if (errno != EINTR) |
353 |
|
|
goto fatal_err; |
354 |
|
|
gettimeofday(&tmp1, NULL); |
355 |
|
|
timersub(&tmp1, &start, &tmp2); |
356 |
|
|
timersub(&wait_per_try, &tmp2, &tmp1); |
357 |
|
|
if (tmp1.tv_sec < 0 || !timerisset(&tmp1)) |
358 |
|
|
goto fatal_err; |
359 |
|
|
delta = tmp1.tv_sec * 1000 + tmp1.tv_usec / 1000; |
360 |
|
|
continue; |
361 |
|
|
case 0: |
362 |
|
|
goto fatal_err; |
363 |
|
|
} |
364 |
|
|
} while (pfd[0].revents == 0); |
365 |
|
|
if ((len = read(sock, buf, len)) > 0) |
366 |
|
|
return (len); |
367 |
|
|
fatal_err: |
368 |
|
|
((struct tcp_conn *)(xprt->xp_p1))->strm_stat = XPRT_DIED; |
369 |
|
|
return (-1); |
370 |
|
|
} |
371 |
|
|
|
372 |
|
|
/* |
373 |
|
|
* writes data to the tcp connection. |
374 |
|
|
* Any error is fatal and the connection is closed. |
375 |
|
|
*/ |
376 |
|
|
static int |
377 |
|
|
writetcp(SVCXPRT *xprt, caddr_t buf, int len) |
378 |
|
|
{ |
379 |
|
|
int i, cnt; |
380 |
|
|
|
381 |
|
|
for (cnt = len; cnt > 0; cnt -= i, buf += i) { |
382 |
|
|
if ((i = write(xprt->xp_sock, buf, cnt)) < 0) { |
383 |
|
|
((struct tcp_conn *)(xprt->xp_p1))->strm_stat = |
384 |
|
|
XPRT_DIED; |
385 |
|
|
return (-1); |
386 |
|
|
} |
387 |
|
|
} |
388 |
|
|
return (len); |
389 |
|
|
} |
390 |
|
|
|
391 |
|
|
static enum xprt_stat |
392 |
|
|
svctcp_stat(SVCXPRT *xprt) |
393 |
|
|
{ |
394 |
|
|
struct tcp_conn *cd = |
395 |
|
|
(struct tcp_conn *)(xprt->xp_p1); |
396 |
|
|
|
397 |
|
|
if (cd->strm_stat == XPRT_DIED) |
398 |
|
|
return (XPRT_DIED); |
399 |
|
|
if (! xdrrec_eof(&(cd->xdrs))) |
400 |
|
|
return (XPRT_MOREREQS); |
401 |
|
|
return (XPRT_IDLE); |
402 |
|
|
} |
403 |
|
|
|
404 |
|
|
static bool_t |
405 |
|
|
svctcp_recv(SVCXPRT *xprt, struct rpc_msg *msg) |
406 |
|
|
{ |
407 |
|
|
struct tcp_conn *cd = |
408 |
|
|
(struct tcp_conn *)(xprt->xp_p1); |
409 |
|
|
XDR *xdrs = &(cd->xdrs); |
410 |
|
|
|
411 |
|
|
xdrs->x_op = XDR_DECODE; |
412 |
|
|
(void)xdrrec_skiprecord(xdrs); |
413 |
|
|
if (xdr_callmsg(xdrs, msg)) { |
414 |
|
|
cd->x_id = msg->rm_xid; |
415 |
|
|
return (TRUE); |
416 |
|
|
} |
417 |
|
|
cd->strm_stat = XPRT_DIED; /* XXX */ |
418 |
|
|
return (FALSE); |
419 |
|
|
} |
420 |
|
|
|
421 |
|
|
static bool_t |
422 |
|
|
svctcp_getargs(SVCXPRT *xprt, xdrproc_t xdr_args, caddr_t args_ptr) |
423 |
|
|
{ |
424 |
|
|
|
425 |
|
|
return ((*xdr_args)(&(((struct tcp_conn *)(xprt->xp_p1))->xdrs), args_ptr)); |
426 |
|
|
} |
427 |
|
|
|
428 |
|
|
static bool_t |
429 |
|
|
svctcp_freeargs(SVCXPRT *xprt, xdrproc_t xdr_args, caddr_t args_ptr) |
430 |
|
|
{ |
431 |
|
|
XDR *xdrs = |
432 |
|
|
&(((struct tcp_conn *)(xprt->xp_p1))->xdrs); |
433 |
|
|
|
434 |
|
|
xdrs->x_op = XDR_FREE; |
435 |
|
|
return ((*xdr_args)(xdrs, args_ptr)); |
436 |
|
|
} |
437 |
|
|
|
438 |
|
|
static bool_t |
439 |
|
|
svctcp_reply(SVCXPRT *xprt, struct rpc_msg *msg) |
440 |
|
|
{ |
441 |
|
|
struct tcp_conn *cd = |
442 |
|
|
(struct tcp_conn *)(xprt->xp_p1); |
443 |
|
|
XDR *xdrs = &(cd->xdrs); |
444 |
|
|
bool_t stat; |
445 |
|
|
|
446 |
|
|
xdrs->x_op = XDR_ENCODE; |
447 |
|
|
msg->rm_xid = cd->x_id; |
448 |
|
|
stat = xdr_replymsg(xdrs, msg); |
449 |
|
|
(void)xdrrec_endofrecord(xdrs, TRUE); |
450 |
|
|
return (stat); |
451 |
|
|
} |