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