procs.c revision 162263
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: head/usr.sbin/rpc.statd/procs.c 162263 2006-09-13 05:01:25Z charnier $");
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
53/* sm_check_hostname -------------------------------------------------------- */
54/*
55 * Purpose: Check `mon_name' member of sm_name struct to ensure that the array
56 * consists only of printable characters.
57 *
58 * Returns: TRUE if hostname is good. FALSE if hostname contains binary or
59 * otherwise non-printable characters.
60 *
61 * Notes: Will syslog(3) to warn of corrupt hostname.
62 */
63
64int sm_check_hostname(struct svc_req *req, char *arg)
65{
66  int len, dstlen, ret;
67  struct sockaddr_in *claddr;
68  char *dst;
69
70  len = strlen(arg);
71  dstlen = (4 * len) + 1;
72  dst = malloc(dstlen);
73  claddr = svc_getcaller(req->rq_xprt);
74  ret = 1;
75
76  if (claddr == NULL || dst == NULL)
77  {
78    ret = 0;
79  }
80  else if (strvis(dst, arg, VIS_WHITE) != len)
81  {
82    syslog(LOG_ERR,
83	"sm_stat: client %s hostname %s contained invalid characters.",
84	inet_ntoa(claddr->sin_addr),
85	dst);
86    ret = 0;
87  }
88  free(dst);
89  return (ret);
90}
91
92/*  sm_stat_1 --------------------------------------------------------------- */
93/*
94   Purpose:	RPC call to enquire if a host can be monitored
95   Returns:	TRUE for any hostname that can be looked up to give
96		an address.
97*/
98
99struct sm_stat_res *sm_stat_1_svc(sm_name *arg, struct svc_req *req)
100{
101  static sm_stat_res res;
102  struct addrinfo *ai;
103  struct sockaddr_in *claddr;
104  static int err;
105
106  err = 1;
107  if ((err = sm_check_hostname(req, arg->mon_name)) == 0)
108  {
109    res.res_stat = stat_fail;
110  }
111  if (err != 0)
112  {
113    if (debug)
114	    syslog(LOG_DEBUG, "stat called for host %s", arg->mon_name);
115    if (getaddrinfo(arg->mon_name, NULL, NULL, &ai) == 0) {
116	    res.res_stat = stat_succ;
117	    freeaddrinfo(ai);
118    }
119    else
120    {
121      claddr = svc_getcaller(req->rq_xprt);
122      syslog(LOG_ERR, "invalid hostname to sm_stat from %s: %s",
123	  inet_ntoa(claddr->sin_addr), arg->mon_name);
124      res.res_stat = stat_fail;
125    }
126  }
127  res.state = status_info->ourState;
128  return (&res);
129}
130
131/* sm_mon_1 ---------------------------------------------------------------- */
132/*
133   Purpose:	RPC procedure to establish a monitor request
134   Returns:	Success, unless lack of resources prevents
135		the necessary structures from being set up
136		to record the request, or if the hostname is not
137		valid (as judged by getaddrinfo())
138*/
139
140struct sm_stat_res *sm_mon_1_svc(mon *arg, struct svc_req *req)
141{
142  static sm_stat_res res;
143  HostInfo *hp;
144  static int err;
145  MonList *lp;
146  struct addrinfo *ai;
147
148  if ((err = sm_check_hostname(req, arg->mon_id.mon_name)) == 0)
149  {
150    res.res_stat = stat_fail;
151  }
152
153  if (err != 0)
154  {
155    if (debug)
156    {
157      syslog(LOG_DEBUG, "monitor request for host %s", arg->mon_id.mon_name);
158      syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d",
159      arg->mon_id.my_id.my_name,
160      arg->mon_id.my_id.my_prog,
161      arg->mon_id.my_id.my_vers,
162      arg->mon_id.my_id.my_proc);
163    }
164    res.res_stat = stat_fail;  /* Assume fail until set otherwise      */
165    res.state = status_info->ourState;
166
167    /* Find existing host entry, or create one if not found            */
168    /* If find_host() fails, it will have logged the error already.    */
169    if (getaddrinfo(arg->mon_id.mon_name, NULL, NULL, &ai) != 0)
170    {
171      syslog(LOG_ERR, "Invalid hostname to sm_mon: %s", arg->mon_id.mon_name);
172      return (&res);
173    }
174    freeaddrinfo(ai);
175    if ((hp = find_host(arg->mon_id.mon_name, TRUE)))
176    {
177      lp = (MonList *)malloc(sizeof(MonList));
178      if (!lp)
179      {
180        syslog(LOG_ERR, "Out of memory");
181      }
182      else
183      {
184        strncpy(lp->notifyHost, arg->mon_id.my_id.my_name, SM_MAXSTRLEN);
185        lp->notifyProg = arg->mon_id.my_id.my_prog;
186        lp->notifyVers = arg->mon_id.my_id.my_vers;
187        lp->notifyProc = arg->mon_id.my_id.my_proc;
188        memcpy(lp->notifyData, arg->priv, sizeof(lp->notifyData));
189
190        lp->next = hp->monList;
191        hp->monList = lp;
192        sync_file();
193
194        res.res_stat = stat_succ;      /* Report success                       */
195      }
196    }
197  }
198  return (&res);
199}
200
201/* do_unmon ---------------------------------------------------------------- */
202/*
203   Purpose:	Remove a monitor request from a host
204   Returns:	TRUE if found, FALSE if not found.
205   Notes:	Common code from sm_unmon_1_svc and sm_unmon_all_1_svc
206		In the unlikely event of more than one identical monitor
207		request, all are removed.
208*/
209
210static int do_unmon(HostInfo *hp, my_id *idp)
211{
212  MonList *lp, *next;
213  MonList *last = NULL;
214  int result = FALSE;
215
216  lp = hp->monList;
217  while (lp)
218  {
219    if (!strncasecmp(idp->my_name, lp->notifyHost, SM_MAXSTRLEN)
220      && (idp->my_prog == lp->notifyProg) && (idp->my_proc == lp->notifyProc)
221      && (idp->my_vers == lp->notifyVers))
222    {
223      /* found one.  Unhook from chain and free.		*/
224      next = lp->next;
225      if (last) last->next = next;
226      else hp->monList = next;
227      free(lp);
228      lp = next;
229      result = TRUE;
230    }
231    else
232    {
233      last = lp;
234      lp = lp->next;
235    }
236  }
237  return (result);
238}
239
240/* sm_unmon_1 -------------------------------------------------------------- */
241/*
242   Purpose:	RPC procedure to release a monitor request.
243   Returns:	Local machine's status number
244   Notes:	The supplied mon_id should match the value passed in an
245		earlier call to sm_mon_1
246*/
247
248struct sm_stat *sm_unmon_1_svc(mon_id *arg, struct svc_req *req __unused)
249{
250  static sm_stat res;
251  HostInfo *hp;
252
253  if (debug)
254  {
255    syslog(LOG_DEBUG, "un-monitor request for host %s", arg->mon_name);
256    syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d",
257      arg->mon_name,
258      arg->my_id.my_prog, arg->my_id.my_vers, arg->my_id.my_proc);
259  }
260
261  if ((hp = find_host(arg->mon_name, FALSE)))
262  {
263    if (do_unmon(hp, &arg->my_id)) sync_file();
264    else
265    {
266      syslog(LOG_ERR, "unmon request from %s, no matching monitor",
267	arg->my_id.my_name);
268    }
269  }
270  else syslog(LOG_ERR, "unmon request from %s for unknown host %s",
271    arg->my_id.my_name, arg->mon_name);
272
273  res.state = status_info->ourState;
274
275  return (&res);
276}
277
278/* sm_unmon_all_1 ---------------------------------------------------------- */
279/*
280   Purpose:	RPC procedure to release monitor requests.
281   Returns:	Local machine's status number
282   Notes:	Releases all monitor requests (if any) from the specified
283		host and program number.
284*/
285
286struct sm_stat *sm_unmon_all_1_svc(my_id *arg, struct svc_req *req __unused)
287{
288  static sm_stat res;
289  HostInfo *hp;
290  int i;
291
292  if (debug)
293  {
294    syslog(LOG_DEBUG, "unmon_all for host: %s prog: %d ver: %d proc: %d",
295      arg->my_name, arg->my_prog, arg->my_vers, arg->my_proc);
296  }
297
298  for (i = status_info->noOfHosts, hp = status_info->hosts; i; i--, hp++)
299  {
300    do_unmon(hp, arg);
301  }
302  sync_file();
303
304  res.state = status_info->ourState;
305
306  return (&res);
307}
308
309/* sm_simu_crash_1 --------------------------------------------------------- */
310/*
311   Purpose:	RPC procedure to simulate a crash
312   Returns:	Nothing
313   Notes:	Standardised mechanism for debug purposes
314		The specification says that we should drop all of our
315		status information (apart from the list of monitored hosts
316		on disc).  However, this would confuse the rpc.lockd
317		which would be unaware that all of its monitor requests
318		had been silently junked.  Hence we in fact retain all
319		current requests and simply increment the status counter
320		and inform all hosts on the monitor list.
321*/
322
323void *sm_simu_crash_1_svc(void *v __unused, struct svc_req *req __unused)
324{
325  static char dummy;
326  int work_to_do;
327  HostInfo *hp;
328  int i;
329
330  work_to_do = FALSE;
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, (xdrproc_t)xdr_sm_status, &tx_arg,
413	  (xdrproc_t)xdr_void, &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