1 /*-
2 * SPDX-License-Identifier: BSD-4-Clause
3 *
4 * Copyright (c) 1995
5 * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed for the FreeBSD project
18 * 4. Neither the name of the author nor the names of any co-contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 *
34 */
35
36 #include <sys/cdefs.h>
37 #include <errno.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <rpc/rpc.h>
43 #include <syslog.h>
44 #include <vis.h>
45 #include <netdb.h> /* for getaddrinfo() */
46 #include <sys/types.h>
47 #include <sys/socket.h>
48 #include <netinet/in.h>
49 #include <arpa/inet.h>
50
51 #include "statd.h"
52
53 static const char *from_addr(struct sockaddr *);
54
55 static const char *
from_addr(struct sockaddr * saddr)56 from_addr(struct sockaddr *saddr)
57 {
58 static char inet_buf[INET6_ADDRSTRLEN];
59
60 if (getnameinfo(saddr, saddr->sa_len, inet_buf, sizeof(inet_buf),
61 NULL, 0, NI_NUMERICHOST) == 0)
62 return inet_buf;
63 return "???";
64 }
65
66 /* sm_check_hostname -------------------------------------------------------- */
67 /*
68 * Purpose: Check `mon_name' member of sm_name struct to ensure that the array
69 * consists only of printable characters.
70 *
71 * Returns: TRUE if hostname is good. FALSE if hostname contains binary or
72 * otherwise non-printable characters.
73 *
74 * Notes: Will syslog(3) to warn of corrupt hostname.
75 */
76
sm_check_hostname(struct svc_req * req,char * arg)77 int sm_check_hostname(struct svc_req *req, char *arg)
78 {
79 int len, dstlen, ret;
80 struct sockaddr *claddr;
81 char *dst;
82
83 len = strlen(arg);
84 dstlen = (4 * len) + 1;
85 dst = malloc(dstlen);
86 claddr = (struct sockaddr *) (svc_getrpccaller(req->rq_xprt)->buf) ;
87 ret = 1;
88
89 if (claddr == NULL || dst == NULL)
90 {
91 ret = 0;
92 }
93 else if (strvis(dst, arg, VIS_WHITE) != len)
94 {
95 syslog(LOG_ERR,
96 "sm_stat: client %s hostname %s contained invalid characters.",
97 from_addr(claddr),
98 dst);
99 ret = 0;
100 }
101 free(dst);
102 return (ret);
103 }
104
105 /* sm_stat_1 --------------------------------------------------------------- */
106 /*
107 Purpose: RPC call to enquire if a host can be monitored
108 Returns: TRUE for any hostname that can be looked up to give
109 an address.
110 */
111
sm_stat_1_svc(sm_name * arg,struct svc_req * req)112 struct sm_stat_res *sm_stat_1_svc(sm_name *arg, struct svc_req *req)
113 {
114 static sm_stat_res res;
115 struct addrinfo *ai;
116 struct sockaddr *claddr;
117 static int err;
118
119 err = 1;
120 if ((err = sm_check_hostname(req, arg->mon_name)) == 0)
121 {
122 res.res_stat = stat_fail;
123 }
124 if (err != 0)
125 {
126 if (debug)
127 syslog(LOG_DEBUG, "stat called for host %s", arg->mon_name);
128 if (getaddrinfo(arg->mon_name, NULL, NULL, &ai) == 0) {
129 res.res_stat = stat_succ;
130 freeaddrinfo(ai);
131 }
132 else
133 {
134 claddr = (struct sockaddr *) (svc_getrpccaller(req->rq_xprt)->buf) ;
135 syslog(LOG_ERR, "invalid hostname to sm_stat from %s: %s",
136 from_addr(claddr), arg->mon_name);
137 res.res_stat = stat_fail;
138 }
139 }
140 res.state = status_info->ourState;
141 return (&res);
142 }
143
144 /* sm_mon_1 ---------------------------------------------------------------- */
145 /*
146 Purpose: RPC procedure to establish a monitor request
147 Returns: Success, unless lack of resources prevents
148 the necessary structures from being set up
149 to record the request, or if the hostname is not
150 valid (as judged by getaddrinfo())
151 */
152
sm_mon_1_svc(mon * arg,struct svc_req * req)153 struct sm_stat_res *sm_mon_1_svc(mon *arg, struct svc_req *req)
154 {
155 static sm_stat_res res;
156 HostInfo *hp;
157 static int err;
158 MonList *lp;
159 struct addrinfo *ai;
160
161 if ((err = sm_check_hostname(req, arg->mon_id.mon_name)) == 0)
162 {
163 res.res_stat = stat_fail;
164 }
165
166 if (err != 0)
167 {
168 if (debug)
169 {
170 syslog(LOG_DEBUG, "monitor request for host %s", arg->mon_id.mon_name);
171 syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d",
172 arg->mon_id.my_id.my_name,
173 arg->mon_id.my_id.my_prog,
174 arg->mon_id.my_id.my_vers,
175 arg->mon_id.my_id.my_proc);
176 }
177 res.res_stat = stat_fail; /* Assume fail until set otherwise */
178 res.state = status_info->ourState;
179
180 /* Find existing host entry, or create one if not found */
181 /* If find_host() fails, it will have logged the error already. */
182 if (getaddrinfo(arg->mon_id.mon_name, NULL, NULL, &ai) != 0)
183 {
184 syslog(LOG_ERR, "Invalid hostname to sm_mon: %s", arg->mon_id.mon_name);
185 return (&res);
186 }
187 freeaddrinfo(ai);
188 if ((hp = find_host(arg->mon_id.mon_name, TRUE)))
189 {
190 lp = (MonList *)malloc(sizeof(MonList));
191 if (!lp)
192 {
193 syslog(LOG_ERR, "Out of memory");
194 }
195 else
196 {
197 strncpy(lp->notifyHost, arg->mon_id.my_id.my_name, SM_MAXSTRLEN);
198 lp->notifyProg = arg->mon_id.my_id.my_prog;
199 lp->notifyVers = arg->mon_id.my_id.my_vers;
200 lp->notifyProc = arg->mon_id.my_id.my_proc;
201 memcpy(lp->notifyData, arg->priv, sizeof(lp->notifyData));
202
203 lp->next = hp->monList;
204 hp->monList = lp;
205 sync_file();
206
207 res.res_stat = stat_succ; /* Report success */
208 }
209 }
210 }
211 return (&res);
212 }
213
214 /* do_unmon ---------------------------------------------------------------- */
215 /*
216 Purpose: Remove a monitor request from a host
217 Returns: TRUE if found, FALSE if not found.
218 Notes: Common code from sm_unmon_1_svc and sm_unmon_all_1_svc
219 In the unlikely event of more than one identical monitor
220 request, all are removed.
221 */
222
do_unmon(HostInfo * hp,my_id * idp)223 static int do_unmon(HostInfo *hp, my_id *idp)
224 {
225 MonList *lp, *next;
226 MonList *last = NULL;
227 int result = FALSE;
228
229 lp = hp->monList;
230 while (lp)
231 {
232 if (!strncasecmp(idp->my_name, lp->notifyHost, SM_MAXSTRLEN)
233 && (idp->my_prog == lp->notifyProg) && (idp->my_proc == lp->notifyProc)
234 && (idp->my_vers == lp->notifyVers))
235 {
236 /* found one. Unhook from chain and free. */
237 next = lp->next;
238 if (last) last->next = next;
239 else hp->monList = next;
240 free(lp);
241 lp = next;
242 result = TRUE;
243 }
244 else
245 {
246 last = lp;
247 lp = lp->next;
248 }
249 }
250 return (result);
251 }
252
253 /* sm_unmon_1 -------------------------------------------------------------- */
254 /*
255 Purpose: RPC procedure to release a monitor request.
256 Returns: Local machine's status number
257 Notes: The supplied mon_id should match the value passed in an
258 earlier call to sm_mon_1
259 */
260
sm_unmon_1_svc(mon_id * arg,struct svc_req * req __unused)261 struct sm_stat *sm_unmon_1_svc(mon_id *arg, struct svc_req *req __unused)
262 {
263 static sm_stat res;
264 HostInfo *hp;
265
266 if (debug)
267 {
268 syslog(LOG_DEBUG, "un-monitor request for host %s", arg->mon_name);
269 syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d",
270 arg->mon_name,
271 arg->my_id.my_prog, arg->my_id.my_vers, arg->my_id.my_proc);
272 }
273
274 if ((hp = find_host(arg->mon_name, FALSE)))
275 {
276 if (do_unmon(hp, &arg->my_id)) sync_file();
277 else
278 {
279 syslog(LOG_ERR, "unmon request from %s, no matching monitor",
280 arg->my_id.my_name);
281 }
282 }
283 else syslog(LOG_ERR, "unmon request from %s for unknown host %s",
284 arg->my_id.my_name, arg->mon_name);
285
286 res.state = status_info->ourState;
287
288 return (&res);
289 }
290
291 /* sm_unmon_all_1 ---------------------------------------------------------- */
292 /*
293 Purpose: RPC procedure to release monitor requests.
294 Returns: Local machine's status number
295 Notes: Releases all monitor requests (if any) from the specified
296 host and program number.
297 */
298
sm_unmon_all_1_svc(my_id * arg,struct svc_req * req __unused)299 struct sm_stat *sm_unmon_all_1_svc(my_id *arg, struct svc_req *req __unused)
300 {
301 static sm_stat res;
302 HostInfo *hp;
303 int i;
304
305 if (debug)
306 {
307 syslog(LOG_DEBUG, "unmon_all for host: %s prog: %d ver: %d proc: %d",
308 arg->my_name, arg->my_prog, arg->my_vers, arg->my_proc);
309 }
310
311 for (i = status_info->noOfHosts, hp = status_info->hosts; i; i--, hp++)
312 {
313 do_unmon(hp, arg);
314 }
315 sync_file();
316
317 res.state = status_info->ourState;
318
319 return (&res);
320 }
321
322 /* sm_simu_crash_1 --------------------------------------------------------- */
323 /*
324 Purpose: RPC procedure to simulate a crash
325 Returns: Nothing
326 Notes: Standardised mechanism for debug purposes
327 The specification says that we should drop all of our
328 status information (apart from the list of monitored hosts
329 on disc). However, this would confuse the rpc.lockd
330 which would be unaware that all of its monitor requests
331 had been silently junked. Hence we in fact retain all
332 current requests and simply increment the status counter
333 and inform all hosts on the monitor list.
334 */
335
sm_simu_crash_1_svc(void * v __unused,struct svc_req * req __unused)336 void *sm_simu_crash_1_svc(void *v __unused, struct svc_req *req __unused)
337 {
338 static char dummy;
339 int work_to_do;
340 HostInfo *hp;
341 int i;
342
343 work_to_do = FALSE;
344 if (debug) syslog(LOG_DEBUG, "simu_crash called!!");
345
346 /* Simulate crash by setting notify-required flag on all monitored */
347 /* hosts, and incrementing our status number. notify_hosts() is */
348 /* then called to fork a process to do the notifications. */
349
350 for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++)
351 {
352 if (hp->monList)
353 {
354 work_to_do = TRUE;
355 hp->notifyReqd = TRUE;
356 }
357 }
358 status_info->ourState += 2; /* always even numbers if not crashed */
359
360 if (work_to_do) notify_hosts();
361
362 return (&dummy);
363 }
364
365 /* sm_notify_1 ------------------------------------------------------------- */
366 /*
367 Purpose: RPC procedure notifying local statd of the crash of another
368 Returns: Nothing
369 Notes: There is danger of deadlock, since it is quite likely that
370 the client procedure that we call will in turn call us
371 to remove or adjust the monitor request.
372 We therefore fork() a process to do the notifications.
373 Note that the main HostInfo structure is in a mmap()
374 region and so will be shared with the child, but the
375 monList pointed to by the HostInfo is in normal memory.
376 Hence if we read the monList before forking, we are
377 protected from the parent servicing other requests
378 that modify the list.
379 */
380
sm_notify_1_svc(stat_chge * arg,struct svc_req * req __unused)381 void *sm_notify_1_svc(stat_chge *arg, struct svc_req *req __unused)
382 {
383 struct timeval timeout = { 20, 0 }; /* 20 secs timeout */
384 CLIENT *cli;
385 static char dummy;
386 sm_status tx_arg; /* arg sent to callback procedure */
387 MonList *lp;
388 HostInfo *hp;
389 pid_t pid;
390
391 if (debug) syslog(LOG_DEBUG, "notify from host %s, new state %d",
392 arg->mon_name, arg->state);
393
394 hp = find_host(arg->mon_name, FALSE);
395 if (!hp)
396 {
397 /* Never heard of this host - why is it notifying us? */
398 syslog(LOG_ERR, "Unsolicited notification from host %s", arg->mon_name);
399 return (&dummy);
400 }
401 lp = hp->monList;
402 if (!lp) return (&dummy); /* We know this host, but have no */
403 /* outstanding requests. */
404 pid = fork();
405 if (pid == -1)
406 {
407 syslog(LOG_ERR, "Unable to fork notify process - %s", strerror(errno));
408 return (NULL); /* no answer, the client will retry */
409 }
410 if (pid) return (&dummy); /* Parent returns */
411
412 while (lp)
413 {
414 tx_arg.mon_name = arg->mon_name;
415 tx_arg.state = arg->state;
416 memcpy(tx_arg.priv, lp->notifyData, sizeof(tx_arg.priv));
417 cli = clnt_create(lp->notifyHost, lp->notifyProg, lp->notifyVers, "udp");
418 if (!cli)
419 {
420 syslog(LOG_ERR, "Failed to contact host %s%s", lp->notifyHost,
421 clnt_spcreateerror(""));
422 }
423 else
424 {
425 if (clnt_call(cli, lp->notifyProc, (xdrproc_t)xdr_sm_status, &tx_arg,
426 (xdrproc_t)xdr_void, &dummy, timeout) != RPC_SUCCESS)
427 {
428 syslog(LOG_ERR, "Failed to call rpc.statd client at host %s",
429 lp->notifyHost);
430 }
431 clnt_destroy(cli);
432 }
433 lp = lp->next;
434 }
435
436 exit (0); /* Child quits */
437 }
438