1 /* $OpenBSD: clnt_udp.c,v 1.42 2024/04/02 08:51:06 jsg 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 "clnt_udp.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 const 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 int
clntudp_bufcreate1(struct clntudp_bufcreate_args * args)70 clntudp_bufcreate1(struct clntudp_bufcreate_args *args)
71 {
72 args->cl = (CLIENT *)mem_alloc(sizeof(CLIENT));
73 if (args->cl == NULL) {
74 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
75 rpc_createerr.cf_error.re_errno = errno;
76 return -1;
77 }
78 args->sendsz = ((args->sendsz + 3) / 4) * 4;
79 args->recvsz = ((args->recvsz + 3) / 4) * 4;
80 args->cu = (struct cu_data *)mem_alloc(sizeof(*args->cu) +
81 args->sendsz + args->recvsz);
82 if (args->cu == NULL) {
83 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
84 rpc_createerr.cf_error.re_errno = errno;
85 return -1;
86 }
87 args->cu->cu_outbuf = &args->cu->cu_inbuf[args->recvsz];
88 args->cl->cl_ops = &udp_ops;
89 args->cl->cl_private = (caddr_t)args->cu;
90 args->cu->cu_connected = 0;
91 args->cu->cu_rlen = sizeof (args->cu->cu_raddr);
92 args->cu->cu_wait = args->wait;
93 args->cu->cu_total.tv_sec = -1;
94 args->cu->cu_total.tv_usec = -1;
95 args->cu->cu_sendsz = args->sendsz;
96 args->cu->cu_recvsz = args->recvsz;
97 args->cu->cu_closeit = FALSE;
98 args->call_msg.rm_xid = arc4random();
99 args->call_msg.rm_direction = CALL;
100 args->call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION;
101 args->call_msg.rm_call.cb_prog = args->program;
102 args->call_msg.rm_call.cb_vers = args->version;
103 return 0;
104 }
105
106 int
clntudp_bufcreate2(struct clntudp_bufcreate_args * args)107 clntudp_bufcreate2(struct clntudp_bufcreate_args *args)
108 {
109 xdrmem_create(&(args->cu->cu_outxdrs), args->cu->cu_outbuf,
110 args->sendsz, XDR_ENCODE);
111 if (!xdr_callhdr(&(args->cu->cu_outxdrs), &args->call_msg))
112 return -1;
113 args->cu->cu_xdrpos = XDR_GETPOS(&(args->cu->cu_outxdrs));
114 args->cl->cl_auth = authnone_create();
115 if (args->cl->cl_auth == NULL) {
116 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
117 rpc_createerr.cf_error.re_errno = errno;
118 return -1;
119 }
120 return 0;
121 }
122
123 /*
124 * Create a UDP based client handle.
125 * If *sockp<0, *sockp is set to a newly created UPD socket. (***)
126 * If raddr->sin_port is 0 a binder on the remote machine
127 * is consulted for the correct port number. (***)
128 * NB: It is the client's responsibility to close *sockp, unless
129 * clntudp_bufcreate() was called with *sockp = -1 (so it created
130 * the socket), and CLNT_DESTROY() is used.
131 * NB: The rpch->cl_auth is initialized to null authentication.
132 * Caller may wish to set this something more useful.
133 *
134 * wait is the amount of time used between retransmitting a call if
135 * no response has been heard; retransmission occurs until the actual
136 * rpc call times out.
137 *
138 * sendsz and recvsz are the maximum allowable packet sizes that can be
139 * sent and received.
140 *
141 * This is a reduced-functionality version of clntudp_bufcreate() that
142 * does not allocate socket or binding (***, above).
143 * The official function clntudp_bufcreate(), which does perform those
144 * two steps, is in clnt_udp_bufcreate.c. This split avoids pulling
145 * socket / portmap related code into programs only using getpwent / YP code.
146 */
147
148 CLIENT *
clntudp_bufcreate_simple(struct sockaddr_in * raddr,u_long program,u_long version,struct timeval wait,int * sockp,u_int sendsz,u_int recvsz)149 clntudp_bufcreate_simple(struct sockaddr_in *raddr, u_long program, u_long version,
150 struct timeval wait, int *sockp, u_int sendsz, u_int recvsz)
151 {
152 struct clntudp_bufcreate_args args;
153
154 args.raddr = raddr;
155 args.program = program;
156 args.version = version;
157 args.wait = wait;
158 args.sockp = sockp;
159 args.sendsz = sendsz;
160 args.recvsz = recvsz;
161 args.cl = NULL;
162 args.cu = NULL;
163
164 if (clntudp_bufcreate1(&args) == -1)
165 goto fooy;
166 args.cu->cu_raddr = *raddr;
167 args.cu->cu_sock = *sockp;
168 if (clntudp_bufcreate2(&args) == -1)
169 goto fooy;
170 return (args.cl);
171 fooy:
172 if (args.cu)
173 mem_free((caddr_t)args.cu,
174 sizeof(*args.cu) + args.sendsz + args.recvsz);
175 if (args.cl)
176 mem_free((caddr_t)args.cl, sizeof(CLIENT));
177 return (NULL);
178 }
179
180 static enum clnt_stat
clntudp_call(CLIENT * cl,u_long proc,xdrproc_t xargs,caddr_t argsp,xdrproc_t xresults,caddr_t resultsp,struct timeval utimeout)181 clntudp_call(CLIENT *cl, /* client handle */
182 u_long proc, /* procedure number */
183 xdrproc_t xargs, /* xdr routine for args */
184 caddr_t argsp, /* pointer to args */
185 xdrproc_t xresults, /* xdr routine for results */
186 caddr_t resultsp, /* pointer to results */
187 struct timeval utimeout) /* seconds to wait before giving up */
188 {
189 struct cu_data *cu = (struct cu_data *)cl->cl_private;
190 XDR *xdrs;
191 int outlen;
192 int inlen;
193 int ret;
194 socklen_t fromlen;
195 struct pollfd pfd[1];
196 struct sockaddr_in from;
197 struct rpc_msg reply_msg;
198 XDR reply_xdrs;
199 struct timespec time_waited, start, after, duration, wait;
200 bool_t ok;
201 int nrefreshes = 2; /* number of times to refresh cred */
202 struct timespec timeout;
203
204 if (cu->cu_total.tv_usec == -1)
205 TIMEVAL_TO_TIMESPEC(&utimeout, &timeout); /* use supplied timeout */
206 else
207 TIMEVAL_TO_TIMESPEC(&cu->cu_total, &timeout); /* use default timeout */
208
209 pfd[0].fd = cu->cu_sock;
210 pfd[0].events = POLLIN;
211 timespecclear(&time_waited);
212 TIMEVAL_TO_TIMESPEC(&cu->cu_wait, &wait);
213 call_again:
214 xdrs = &(cu->cu_outxdrs);
215 xdrs->x_op = XDR_ENCODE;
216 XDR_SETPOS(xdrs, cu->cu_xdrpos);
217 /*
218 * the transaction is the first thing in the out buffer
219 */
220 (*(u_short *)(cu->cu_outbuf))++;
221 if (!XDR_PUTLONG(xdrs, (long *)&proc) ||
222 !AUTH_MARSHALL(cl->cl_auth, xdrs) ||
223 !(*xargs)(xdrs, argsp)) {
224 return (cu->cu_error.re_status = RPC_CANTENCODEARGS);
225 }
226 outlen = (int)XDR_GETPOS(xdrs);
227
228 send_again:
229 if (cu->cu_connected)
230 ret = send(cu->cu_sock, cu->cu_outbuf, outlen, 0);
231 else
232 ret = sendto(cu->cu_sock, cu->cu_outbuf, outlen, 0,
233 (struct sockaddr *)&(cu->cu_raddr), cu->cu_rlen);
234 if (ret != outlen) {
235 cu->cu_error.re_errno = errno;
236 return (cu->cu_error.re_status = RPC_CANTSEND);
237 }
238
239 /*
240 * Hack to provide rpc-based message passing
241 */
242 if (!timespecisset(&timeout))
243 return (cu->cu_error.re_status = RPC_TIMEDOUT);
244
245 /*
246 * sub-optimal code appears here because we have
247 * some clock time to spare while the packets are in flight.
248 * (We assume that this is actually only executed once.)
249 */
250 reply_msg.acpted_rply.ar_verf = _null_auth;
251 reply_msg.acpted_rply.ar_results.where = resultsp;
252 reply_msg.acpted_rply.ar_results.proc = xresults;
253
254 WRAP(clock_gettime)(CLOCK_MONOTONIC, &start);
255 for (;;) {
256 switch (ppoll(pfd, 1, &wait, NULL)) {
257 case 0:
258 timespecadd(&time_waited, &wait, &time_waited);
259 if (timespeccmp(&time_waited, &timeout, <))
260 goto send_again;
261 return (cu->cu_error.re_status = RPC_TIMEDOUT);
262 case 1:
263 if (pfd[0].revents & POLLNVAL)
264 errno = EBADF;
265 else if (pfd[0].revents & POLLERR)
266 errno = EIO;
267 else
268 break;
269 /* FALLTHROUGH */
270 case -1:
271 if (errno == EINTR) {
272 WRAP(clock_gettime)(CLOCK_MONOTONIC, &after);
273 timespecsub(&after, &start, &duration);
274 timespecadd(&time_waited, &duration, &time_waited);
275 if (timespeccmp(&time_waited, &timeout, <))
276 continue;
277 return (cu->cu_error.re_status = RPC_TIMEDOUT);
278 }
279 cu->cu_error.re_errno = errno;
280 return (cu->cu_error.re_status = RPC_CANTRECV);
281 }
282
283 do {
284 fromlen = sizeof(struct sockaddr);
285 if (cu->cu_connected) {
286 inlen = recv(cu->cu_sock, cu->cu_inbuf,
287 (int) cu->cu_recvsz, 0);
288 } else {
289
290 inlen = recvfrom(cu->cu_sock, cu->cu_inbuf,
291 (int) cu->cu_recvsz, 0,
292 (struct sockaddr *)&from, &fromlen);
293 }
294 } while (inlen == -1 && errno == EINTR);
295 if (inlen == -1) {
296 if (errno == EWOULDBLOCK)
297 continue;
298 cu->cu_error.re_errno = errno;
299 return (cu->cu_error.re_status = RPC_CANTRECV);
300 }
301 if (inlen < sizeof(u_int32_t))
302 continue;
303 /* see if reply transaction id matches sent id */
304 if (((struct rpc_msg *)(cu->cu_inbuf))->rm_xid !=
305 ((struct rpc_msg *)(cu->cu_outbuf))->rm_xid)
306 continue;
307 /* we now assume we have the proper reply */
308 break;
309 }
310
311 /*
312 * now decode and validate the response
313 */
314 xdrmem_create(&reply_xdrs, cu->cu_inbuf, (u_int)inlen, XDR_DECODE);
315 ok = xdr_replymsg(&reply_xdrs, &reply_msg);
316 /* XDR_DESTROY(&reply_xdrs); save a few cycles on noop destroy */
317 if (ok) {
318 #if 0
319 /*
320 * XXX Would like to check these, but call_msg is not
321 * around.
322 */
323 if (reply_msg.rm_call.cb_prog != call_msg.rm_call.cb_prog ||
324 reply_msg.rm_call.cb_vers != call_msg.rm_call.cb_vers ||
325 reply_msg.rm_call.cb_proc != call_msg.rm_call.cb_proc) {
326 goto call_again; /* XXX spin? */
327 }
328 #endif
329
330 _seterr_reply(&reply_msg, &(cu->cu_error));
331 if (cu->cu_error.re_status == RPC_SUCCESS) {
332 if (!AUTH_VALIDATE(cl->cl_auth,
333 &reply_msg.acpted_rply.ar_verf)) {
334 cu->cu_error.re_status = RPC_AUTHERROR;
335 cu->cu_error.re_why = AUTH_INVALIDRESP;
336 }
337 if (reply_msg.acpted_rply.ar_verf.oa_base != NULL) {
338 xdrs->x_op = XDR_FREE;
339 (void)xdr_opaque_auth(xdrs,
340 &(reply_msg.acpted_rply.ar_verf));
341 }
342 } else {
343 /* maybe our credentials need to be refreshed ... */
344 if (nrefreshes > 0 && AUTH_REFRESH(cl->cl_auth)) {
345 nrefreshes--;
346 goto call_again;
347 }
348 }
349 } else {
350 /* xdr_replymsg() may have left some things allocated */
351 int op = reply_xdrs.x_op;
352 reply_xdrs.x_op = XDR_FREE;
353 xdr_replymsg(&reply_xdrs, &reply_msg);
354 reply_xdrs.x_op = op;
355 cu->cu_error.re_status = RPC_CANTDECODERES;
356 }
357
358 return (cu->cu_error.re_status);
359 }
360
361 static void
clntudp_geterr(CLIENT * cl,struct rpc_err * errp)362 clntudp_geterr(CLIENT *cl, struct rpc_err *errp)
363 {
364 struct cu_data *cu = (struct cu_data *)cl->cl_private;
365
366 *errp = cu->cu_error;
367 }
368
369
370 static bool_t
clntudp_freeres(CLIENT * cl,xdrproc_t xdr_res,caddr_t res_ptr)371 clntudp_freeres(CLIENT *cl, xdrproc_t xdr_res, caddr_t res_ptr)
372 {
373 struct cu_data *cu = (struct cu_data *)cl->cl_private;
374 XDR *xdrs = &(cu->cu_outxdrs);
375
376 xdrs->x_op = XDR_FREE;
377 return ((*xdr_res)(xdrs, res_ptr));
378 }
379
380 static void
clntudp_abort(CLIENT * clnt)381 clntudp_abort(CLIENT *clnt)
382 {
383 }
384
385 static bool_t
clntudp_control(CLIENT * cl,u_int request,void * info)386 clntudp_control(CLIENT *cl, u_int request, void *info)
387 {
388 struct cu_data *cu = (struct cu_data *)cl->cl_private;
389
390 switch (request) {
391 case CLSET_TIMEOUT:
392 cu->cu_total = *(struct timeval *)info;
393 break;
394 case CLGET_TIMEOUT:
395 *(struct timeval *)info = cu->cu_total;
396 break;
397 case CLSET_RETRY_TIMEOUT:
398 cu->cu_wait = *(struct timeval *)info;
399 break;
400 case CLGET_RETRY_TIMEOUT:
401 *(struct timeval *)info = cu->cu_wait;
402 break;
403 case CLGET_SERVER_ADDR:
404 *(struct sockaddr_in *)info = cu->cu_raddr;
405 break;
406 case CLSET_CONNECTED:
407 cu->cu_connected = *(int *)info;
408 break;
409 default:
410 return (FALSE);
411 }
412 return (TRUE);
413 }
414
415 static void
clntudp_destroy(CLIENT * cl)416 clntudp_destroy(CLIENT *cl)
417 {
418 struct cu_data *cu = (struct cu_data *)cl->cl_private;
419
420 if (cu->cu_closeit && cu->cu_sock != -1) {
421 (void)close(cu->cu_sock);
422 }
423 XDR_DESTROY(&(cu->cu_outxdrs));
424 mem_free((caddr_t)cu, (sizeof(*cu) + cu->cu_sendsz + cu->cu_recvsz));
425 mem_free((caddr_t)cl, sizeof(CLIENT));
426 }
427