xref: /freebsd/usr.sbin/rpc.statd/procs.c (revision 422e293c7f933ae77a55a66891f64d8a041049b7)
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.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 
211 static 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 
249 struct 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 
287 struct 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 
324 void *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 
368 void *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