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