summaryrefslogtreecommitdiffstats
path: root/utils/statd/monitor.c
blob: 286a5e21e173442c6445f109ffe82765ea4bfdaf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
/*
 * Copyright (C) 1995-1999 Jeffrey A. Uphoff
 * Major rewrite by Olaf Kirch, Dec. 1996.
 * Modified by H.J. Lu, 1998.
 * Tighter access control, Olaf Kirch June 1999.
 *
 * NSM for Linux.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <arpa/inet.h>
#include <dirent.h>

#include "sockaddr.h"
#include "rpcmisc.h"
#include "nsm.h"
#include "statd.h"
#include "notlist.h"
#include "ha-callout.h"

notify_list *		rtnl = NULL;	/* Run-time notify list. */

/*
 * Reject requests from non-loopback addresses in order
 * to prevent attack described in CERT CA-99.05.
 *
 * Although the kernel contacts the statd service via only IPv4
 * transports, the statd service can receive other requests, such
 * as SM_NOTIFY, from remote peers via IPv6.
 */
static _Bool
caller_is_localhost(struct svc_req *rqstp)
{
	struct sockaddr *sap = nfs_getrpccaller(rqstp->rq_xprt);
	char buf[INET6_ADDRSTRLEN];

	if (!nfs_is_v4_loopback(sap))
		goto out_nonlocal;
	return true;

out_nonlocal:
	if (!statd_present_address(sap, buf, sizeof(buf)))
		buf[0] = '\0';
	xlog_warn("SM_MON/SM_UNMON call from non-local host %s", buf);
	return false;
}

/*
 * Services SM_MON requests.
 */
struct sm_stat_res *
sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp)
{
	static sm_stat_res result;
	char		*mon_name = argp->mon_id.mon_name,
			*my_name  = argp->mon_id.my_id.my_name;
	struct my_id	*id = &argp->mon_id.my_id;
	char		*cp;
	notify_list	*clnt;
	struct sockaddr_in my_addr = {
		.sin_family		= AF_INET,
		.sin_addr.s_addr	= htonl(INADDR_LOOPBACK),
	};
	char *dnsname = NULL;

	xlog(D_CALL, "Received SM_MON for %s from %s", mon_name, my_name);

	/* Assume that we'll fail. */
	result.res_stat = STAT_FAIL;
	result.state = -1;	/* State is undefined for STAT_FAIL. */

	/* 1.	Reject any remote callers.
	 *	Ignore the my_name specified by the caller, and
	 *	use "127.0.0.1" instead.
	 */
	if (!caller_is_localhost(rqstp))
		goto failure;

	/* 2.	Reject any registrations for non-lockd services.
	 *
	 *	This is specific to the linux kernel lockd, which
	 *	makes the callback procedure part of the lockd interface.
	 *	It is also prone to break when lockd changes its callback
	 *	procedure number -- which, in fact, has now happened once.
	 *	There must be a better way....   XXX FIXME
	 */
	if (id->my_prog != 100021 ||
	    (id->my_proc != 16 && id->my_proc != 24))
	{
		xlog_warn("Attempt to register callback to %d/%d",
			id->my_prog, id->my_proc);
		goto failure;
	}

	/*
	 * Check hostnames.  If I can't look them up, I won't monitor.  This
	 * might not be legal, but it adds a little bit of safety and sanity.
	 */

	/* must check for /'s in hostname!  See CERT's CA-96.09 for details. */
	if (strchr(mon_name, '/') || mon_name[0] == '.') {
		xlog(L_ERROR, "SM_MON request for hostname containing '/' "
		     "or starting '.': %s", mon_name);
		xlog(L_ERROR, "POSSIBLE SPOOF/ATTACK ATTEMPT!");
		goto failure;
	}

	/* my_name must not have white space */
	for (cp=my_name ; *cp ; cp++)
		if (*cp == ' ' || *cp == '\t' || *cp == '\r' || *cp == '\n')
			*cp = '_';

	/*
	 * Hostnames checked OK.
	 * Now choose a hostname to use for matching.  We cannot
	 * really trust much in the incoming NOTIFY, so to make
	 * sure that multi-homed hosts work nicely, we get an
	 * FQDN now, and use that for matching.
	 */
	dnsname = statd_canonical_name(mon_name);
	if (dnsname == NULL) {
		xlog(L_WARNING, "No canonical hostname found for %s", mon_name);
		goto failure;
	}

	/* Now check to see if this is a duplicate, and warn if so.
	 * I will also return STAT_FAIL. (I *think* this is how I should
	 * handle it.)
	 *
	 * Olaf requests that I allow duplicate SM_MON requests for
	 * hosts due to the way he is coding lockd. No problem,
	 * I'll just do a quickie success return and things should
	 * be happy.
	 */
	clnt = rtnl;

	while ((clnt = nlist_gethost(clnt, mon_name, 0))) {
		if (statd_matchhostname(NL_MY_NAME(clnt), my_name) &&
		    NL_MY_PROC(clnt) == id->my_proc &&
		    NL_MY_PROG(clnt) == id->my_prog &&
		    NL_MY_VERS(clnt) == id->my_vers &&
		    memcmp(NL_PRIV(clnt), argp->priv, SM_PRIV_SIZE) == 0) {
			/* Hey!  We already know you guys! */
			xlog(D_GENERAL,
				"Duplicate SM_MON request for %s "
				"from procedure on %s",
				mon_name, my_name);

			/* But we'll let you pass anyway. */
			free(dnsname);
			goto success;
		}
		clnt = NL_NEXT(clnt);
	}

	/*
	 * We're committed...ignoring errors.  Let's hope that a malloc()
	 * doesn't fail.  (I should probably fix this assumption.)
	 */
	if (!(clnt = nlist_new(my_name, mon_name, 0))) {
		free(dnsname);
		xlog_warn("out of memory");
		goto failure;
	}

	NL_MY_PROG(clnt) = id->my_prog;
	NL_MY_VERS(clnt) = id->my_vers;
	NL_MY_PROC(clnt) = id->my_proc;
	memcpy(NL_PRIV(clnt), argp->priv, SM_PRIV_SIZE);
	clnt->dns_name = dnsname;

	/*
	 * Now, Create file on stable storage for host.
	 */
	if (!nsm_insert_monitored_host(dnsname,
				(struct sockaddr *)(char *)&my_addr, argp)) {
		nlist_free(NULL, clnt);
		goto failure;
	}

	/* PRC: do the HA callout: */
	ha_callout("add-client", mon_name, my_name, -1);
	nlist_insert(&rtnl, clnt);
	xlog(D_GENERAL, "MONITORING %s for %s", mon_name, my_name);
 success:
	result.res_stat = STAT_SUCC;
	/* SUN's sm_inter.x says this should be "state number of local site".
	 * X/Open says '"state" will be contain the state of the remote NSM.'
	 * href=http://www.opengroup.org/onlinepubs/9629799/SM_MON.htm
	 * Linux lockd currently (2.6.21 and prior) ignores whatever is
	 * returned, and given the above contraction, it probably always will..
	 * So we just return what we always returned.  If possible, we
	 * have already told lockd about our state number via a sysctl.
	 * If lockd wants the remote state, it will need to
	 * use SM_STAT (and prayer).
	 */
	result.state = MY_STATE;
	return (&result);

failure:
	xlog_warn("STAT_FAIL to %s for SM_MON of %s", my_name, mon_name);
	return (&result);
}

static unsigned int
load_one_host(const char *hostname,
		__attribute__ ((unused)) const struct sockaddr *sap,
		const struct mon *m,
		__attribute__ ((unused)) const time_t timestamp)
{
	notify_list *clnt;

	clnt = nlist_new(m->mon_id.my_id.my_name,
				m->mon_id.mon_name, 0);
	if (clnt == NULL)
		return 0;

	clnt->dns_name = strdup(hostname);
	if (clnt->dns_name == NULL) {
		nlist_free(NULL, clnt);
		return 0;
	}

	xlog(D_GENERAL, "Adding record for %s to the monitor list...",
			hostname);

	NL_MY_PROG(clnt) = m->mon_id.my_id.my_prog;
	NL_MY_VERS(clnt) = m->mon_id.my_id.my_vers;
	NL_MY_PROC(clnt) = m->mon_id.my_id.my_proc;
	memcpy(NL_PRIV(clnt), m->priv, SM_PRIV_SIZE);

	nlist_insert(&rtnl, clnt);
	return 1;
}

void load_state(void)
{
	unsigned int count;

	count = nsm_load_monitor_list(load_one_host);
	if (count)
		xlog(D_GENERAL, "Loaded %u previously monitored hosts", count);
}

/*
 * Services SM_UNMON requests.
 *
 * There is no statement in the X/Open spec's about returning an error
 * for requests to unmonitor a host that we're *not* monitoring.  I just
 * return the state of the NSM when I get such foolish requests for lack
 * of any better ideas.  (I also log the "offense.")
 */
struct sm_stat *
sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp)
{
	static sm_stat  result;
	notify_list	*clnt;
	char		*mon_name = argp->mon_name,
			*my_name  = argp->my_id.my_name;
	struct my_id	*id = &argp->my_id;
	char		*cp;

	xlog(D_CALL, "Received SM_UNMON for %s from %s", mon_name, my_name);

	result.state = MY_STATE;

	if (!caller_is_localhost(rqstp))
		goto failure;

	/* my_name must not have white space */
	for (cp=my_name ; *cp ; cp++)
		if (*cp == ' ' || *cp == '\t' || *cp == '\r' || *cp == '\n')
			*cp = '_';


	/* Check if we're monitoring anyone. */
	if (rtnl == NULL) {
		xlog_warn("Received SM_UNMON request from %s for %s while not "
			"monitoring any hosts", my_name, argp->mon_name);
		return (&result);
	}
	clnt = rtnl;

	/*
	 * OK, we are.  Now look for appropriate entry in run-time list.
	 * There should only be *one* match on this, since I block "duplicate"
	 * SM_MON calls.  (Actually, duplicate calls are allowed, but only one
	 * entry winds up in the list the way I'm currently handling them.)
	 */
	while ((clnt = nlist_gethost(clnt, mon_name, 0))) {
		if (statd_matchhostname(NL_MY_NAME(clnt), my_name) &&
			NL_MY_PROC(clnt) == id->my_proc &&
			NL_MY_PROG(clnt) == id->my_prog &&
			NL_MY_VERS(clnt) == id->my_vers) {
			/* Match! */
			xlog(D_GENERAL, "UNMONITORING %s for %s",
					mon_name, my_name);

			/* PRC: do the HA callout: */
			ha_callout("del-client", mon_name, my_name, -1);

			nsm_delete_monitored_host(clnt->dns_name,
							mon_name, my_name);
			nlist_free(&rtnl, clnt);

			return (&result);
		} else
			clnt = NL_NEXT(clnt);
	}

 failure:
	xlog_warn("Received erroneous SM_UNMON request from %s for %s",
		my_name, mon_name);
	return (&result);
}


struct sm_stat *
sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp)
{
	short int       count = 0;
	static sm_stat  result;
	notify_list	*clnt;
	char		*my_name = argp->my_name;

	xlog(D_CALL, "Received SM_UNMON_ALL for %s", my_name);

	if (!caller_is_localhost(rqstp))
		goto failure;

	result.state = MY_STATE;

	if (rtnl == NULL) {
		xlog_warn("Received SM_UNMON_ALL request from %s "
			"while not monitoring any hosts", my_name);
		return (&result);
	}
	clnt = rtnl;

	while ((clnt = nlist_gethost(clnt, my_name, 1))) {
		if (NL_MY_PROC(clnt) == argp->my_proc &&
			NL_MY_PROG(clnt) == argp->my_prog &&
			NL_MY_VERS(clnt) == argp->my_vers) {
			/* Watch stack! */
			char            mon_name[SM_MAXSTRLEN + 1];
			notify_list	*temp;

			xlog(D_GENERAL,
				"UNMONITORING (SM_UNMON_ALL) %s for %s",
				NL_MON_NAME(clnt), NL_MY_NAME(clnt));
			strncpy(mon_name, NL_MON_NAME(clnt),
				sizeof (mon_name) - 1);
			mon_name[sizeof (mon_name) - 1] = '\0';
			temp = NL_NEXT(clnt);
			/* PRC: do the HA callout: */
			ha_callout("del-client", mon_name, my_name, -1);
			nsm_delete_monitored_host(clnt->dns_name,
							mon_name, my_name);
			nlist_free(&rtnl, clnt);
			++count;
			clnt = temp;
		} else
			clnt = NL_NEXT(clnt);
	}

	if (!count) {
		xlog(D_GENERAL, "SM_UNMON_ALL request from %s with no "
			"SM_MON requests from it", my_name);
	}

 failure:
	return (&result);
}