procs.c revision 100126
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 100126 2002-07-15 19:02:45Z alfred $";
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.mon_name,
162      arg->mon_id.my_id.my_prog, arg->mon_id.my_id.my_vers,
163      arg->mon_id.my_id.my_proc);
164    }
165    res.res_stat = stat_fail;  /* Assume fail until set otherwise      */
166    res.state = status_info->ourState;
167
168    /* Find existing host entry, or create one if not found            */
169    /* If find_host() fails, it will have logged the error already.    */
170    if (getaddrinfo(arg->mon_id.mon_name, NULL, NULL, &ai) != 0)
171    {
172      syslog(LOG_ERR, "Invalid hostname to sm_mon: %s", arg->mon_id.mon_name);
173      return (&res);
174    }
175    freeaddrinfo(ai);
176    if ((hp = find_host(arg->mon_id.mon_name, TRUE)))
177    {
178      lp = (MonList *)malloc(sizeof(MonList));
179      if (!lp)
180      {
181        syslog(LOG_ERR, "Out of memory");
182      }
183      else
184      {
185        strncpy(lp->notifyHost, arg->mon_id.my_id.my_name, SM_MAXSTRLEN);
186        lp->notifyProg = arg->mon_id.my_id.my_prog;
187        lp->notifyVers = arg->mon_id.my_id.my_vers;
188        lp->notifyProc = arg->mon_id.my_id.my_proc;
189        memcpy(lp->notifyData, arg->priv, sizeof(lp->notifyData));
190
191        lp->next = hp->monList;
192        hp->monList = lp;
193        sync_file();
194
195        res.res_stat = stat_succ;      /* Report success                       */
196      }
197    }
198  }
199  return (&res);
200}
201
202/* do_unmon ---------------------------------------------------------------- */
203/*
204   Purpose:	Remove a monitor request from a host
205   Returns:	TRUE if found, FALSE if not found.
206   Notes:	Common code from sm_unmon_1_svc and sm_unmon_all_1_svc
207		In the unlikely event of more than one identical monitor
208		request, all are removed.
209*/
210
211static int do_unmon(HostInfo *hp, my_id *idp)
212{
213  MonList *lp, *next;
214  MonList *last = NULL;
215  int result = FALSE;
216
217  lp = hp->monList;
218  while (lp)
219  {
220    if (!strncasecmp(idp->my_name, lp->notifyHost, SM_MAXSTRLEN)
221      && (idp->my_prog == lp->notifyProg) && (idp->my_proc == lp->notifyProc)
222      && (idp->my_vers == lp->notifyVers))
223    {
224      /* found one.  Unhook from chain and free.		*/
225      next = lp->next;
226      if (last) last->next = next;
227      else hp->monList = next;
228      free(lp);
229      lp = next;
230      result = TRUE;
231    }
232    else
233    {
234      last = lp;
235      lp = lp->next;
236    }
237  }
238  return (result);
239}
240
241/* sm_unmon_1 -------------------------------------------------------------- */
242/*
243   Purpose:	RPC procedure to release a monitor request.
244   Returns:	Local machine's status number
245   Notes:	The supplied mon_id should match the value passed in an
246		earlier call to sm_mon_1
247*/
248
249struct sm_stat *sm_unmon_1_svc(mon_id *arg, struct svc_req *req __unused)
250{
251  static sm_stat res;
252  HostInfo *hp;
253
254  if (debug)
255  {
256    syslog(LOG_DEBUG, "un-monitor request for host %s", arg->mon_name);
257    syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d",
258      arg->mon_name,
259      arg->my_id.my_prog, arg->my_id.my_vers, arg->my_id.my_proc);
260  }
261
262  if ((hp = find_host(arg->mon_name, FALSE)))
263  {
264    if (do_unmon(hp, &arg->my_id)) sync_file();
265    else
266    {
267      syslog(LOG_ERR, "unmon request from %s, no matching monitor",
268	arg->my_id.my_name);
269    }
270  }
271  else syslog(LOG_ERR, "unmon request from %s for unknown host %s",
272    arg->my_id.my_name, arg->mon_name);
273
274  res.state = status_info->ourState;
275
276  return (&res);
277}
278
279/* sm_unmon_all_1 ---------------------------------------------------------- */
280/*
281   Purpose:	RPC procedure to release monitor requests.
282   Returns:	Local machine's status number
283   Notes:	Releases all monitor requests (if any) from the specified
284		host and program number.
285*/
286
287struct sm_stat *sm_unmon_all_1_svc(my_id *arg, struct svc_req *req __unused)
288{
289  static sm_stat res;
290  HostInfo *hp;
291  int i;
292
293  if (debug)
294  {
295    syslog(LOG_DEBUG, "unmon_all for host: %s prog: %d ver: %d proc: %d",
296      arg->my_name, arg->my_prog, arg->my_vers, arg->my_proc);
297  }
298
299  for (i = status_info->noOfHosts, hp = status_info->hosts; i; i--, hp++)
300  {
301    do_unmon(hp, arg);
302  }
303  sync_file();
304
305  res.state = status_info->ourState;
306
307  return (&res);
308}
309
310/* sm_simu_crash_1 --------------------------------------------------------- */
311/*
312   Purpose:	RPC procedure to simulate a crash
313   Returns:	Nothing
314   Notes:	Standardised mechanism for debug purposes
315		The specification says that we should drop all of our
316		status information (apart from the list of monitored hosts
317		on disc).  However, this would confuse the rpc.lockd
318		which would be unaware that all of its monitor requests
319		had been silently junked.  Hence we in fact retain all
320		current requests and simply increment the status counter
321		and inform all hosts on the monitor list.
322*/
323
324void *sm_simu_crash_1_svc(void *v __unused, struct svc_req *req __unused)
325{
326  static char dummy;
327  int work_to_do;
328  HostInfo *hp;
329  int i;
330
331  if (debug) syslog(LOG_DEBUG, "simu_crash called!!");
332
333  /* Simulate crash by setting notify-required flag on all monitored	*/
334  /* hosts, and incrementing our status number.  notify_hosts() is	*/
335  /* then called to fork a process to do the notifications.		*/
336
337  for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++)
338  {
339    if (hp->monList)
340    {
341      work_to_do = TRUE;
342      hp->notifyReqd = TRUE;
343    }
344  }
345  status_info->ourState += 2;	/* always even numbers if not crashed	*/
346
347  if (work_to_do) notify_hosts();
348
349  return (&dummy);
350}
351
352/* sm_notify_1 ------------------------------------------------------------- */
353/*
354   Purpose:	RPC procedure notifying local statd of the crash of another
355   Returns:	Nothing
356   Notes:	There is danger of deadlock, since it is quite likely that
357		the client procedure that we call will in turn call us
358		to remove or adjust the monitor request.
359		We therefore fork() a process to do the notifications.
360		Note that the main HostInfo structure is in a mmap()
361		region and so will be shared with the child, but the
362		monList pointed to by the HostInfo is in normal memory.
363		Hence if we read the monList before forking, we are
364		protected from the parent servicing other requests
365		that modify the list.
366*/
367
368void *sm_notify_1_svc(stat_chge *arg, struct svc_req *req __unused)
369{
370  struct timeval timeout = { 20, 0 };	/* 20 secs timeout		*/
371  CLIENT *cli;
372  static char dummy;
373  sm_status tx_arg;		/* arg sent to callback procedure	*/
374  MonList *lp;
375  HostInfo *hp;
376  pid_t pid;
377
378  if (debug) syslog(LOG_DEBUG, "notify from host %s, new state %d",
379    arg->mon_name, arg->state);
380
381  hp = find_host(arg->mon_name, FALSE);
382  if (!hp)
383  {
384    /* Never heard of this host - why is it notifying us?		*/
385    syslog(LOG_ERR, "Unsolicited notification from host %s", arg->mon_name);
386    return (&dummy);
387  }
388  lp = hp->monList;
389  if (!lp) return (&dummy);	/* We know this host, but have no	*/
390				/* outstanding requests.		*/
391  pid = fork();
392  if (pid == -1)
393  {
394    syslog(LOG_ERR, "Unable to fork notify process - %s", strerror(errno));
395    return (NULL);		/* no answer, the client will retry */
396  }
397  if (pid) return (&dummy);	/* Parent returns			*/
398
399  while (lp)
400  {
401    tx_arg.mon_name = arg->mon_name;
402    tx_arg.state = arg->state;
403    memcpy(tx_arg.priv, lp->notifyData, sizeof(tx_arg.priv));
404    cli = clnt_create(lp->notifyHost, lp->notifyProg, lp->notifyVers, "udp");
405    if (!cli)
406    {
407      syslog(LOG_ERR, "Failed to contact host %s%s", lp->notifyHost,
408        clnt_spcreateerror(""));
409    }
410    else
411    {
412      if (clnt_call(cli, lp->notifyProc, xdr_sm_status, &tx_arg, xdr_void,
413	      &dummy, timeout) != RPC_SUCCESS)
414      {
415        syslog(LOG_ERR, "Failed to call rpc.statd client at host %s",
416	  lp->notifyHost);
417      }
418      clnt_destroy(cli);
419    }
420    lp = lp->next;
421  }
422
423  exit (0);	/* Child quits	*/
424}
425