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