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