xref: /freebsd/usr.sbin/rpc.statd/procs.c (revision 5861f9665471e98e544f6fa3ce73c4912229ff82)
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 
53 static const char *
54 from_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 
76 int 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 
111 struct 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 
152 struct 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 
222 static 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 
260 struct 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 
298 struct 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 
335 void *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 
380 void *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