xref: /freebsd/usr.sbin/rpc.statd/procs.c (revision adfa0adec0b5d7c19c220a85ef6ca729235ed172)
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
35 static const char rcsid[] =
36   "$FreeBSD$";
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 
66 int 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 
101 struct 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 
142 struct 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 
212 static 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 
250 struct 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 
288 struct 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 
325 void *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 
369 void *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