summaryrefslogtreecommitdiffstats
path: root/utils/statd/monitor.c
blob: 40e8f49ebb17d11c37d5fa84904f4a41a55aae15 (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
/*
 * 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.
 */

#include "config.h"

#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 "misc.h"
#include "statd.h"
#include "notlist.h"
#include "ha-callout.h"

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


/*
 * 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            *path;
	int             fd;
	notify_list	*clnt;
	struct in_addr	my_addr;
#ifdef RESTRICTED_STATD
	struct in_addr	mon_addr, caller;
#else
	struct hostent	*hostinfo = NULL;
#endif

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

	/* Restrict access to statd.
	 * In the light of CERT CA-99.05, we tighten access to
	 * statd.			--okir
	 */
#ifdef RESTRICTED_STATD
	/* 1.	Reject anyone not calling from 127.0.0.1.
	 *	Ignore the my_name specified by the caller, and
	 *	use "127.0.0.1" instead.
	 */
	caller = svc_getcaller(rqstp->rq_xprt)->sin_addr;
	if (caller.s_addr != htonl(INADDR_LOOPBACK)) {
		note(N_WARNING,
			"Call to statd from non-local host %s",
			inet_ntoa(caller));
		goto failure;
	}
	my_addr.s_addr = htonl(INADDR_LOOPBACK);
	my_name = "127.0.0.1";

	/* 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))
	{
		note(N_WARNING,
			"Attempt to register callback to %d/%d",
			id->my_prog, id->my_proc);
		goto failure;
	}

	/* 3.	mon_name must be an address in dotted quad.
	 *	Again, specific to the linux kernel lockd.
	 */
	if (!inet_aton(mon_name, &mon_addr)) {
		note(N_WARNING,
			"Attempt to register host %s (not a dotted quad)",
			mon_name);
		goto failure;
	}
#else
	/*
	 * 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, '/')) {
		note(N_CRIT, "SM_MON request for hostname containing '/': %s",
			mon_name);
		note(N_CRIT, "POSSIBLE SPOOF/ATTACK ATTEMPT!");
		goto failure;
	} else if (gethostbyname(mon_name) == NULL) {
		note(N_WARNING, "gethostbyname error for %s", mon_name);
		goto failure;
	} else if (!(hostinfo = gethostbyname(my_name))) {
		note(N_WARNING, "gethostbyname error for %s", my_name);
		goto failure;
	} else
		my_addr = *(struct in_addr *) hostinfo->h_addr;
#endif

	/*
	 * Hostnames checked OK.
	 * 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.
	 */
	if (rtnl) {
		notify_list    *temp = rtnl;

		while ((temp = nlist_gethost(temp, mon_name, 0))) {
			if (matchhostname(NL_MY_NAME(temp), my_name) &&
				NL_MY_PROC(temp) == id->my_proc &&
				NL_MY_PROG(temp) == id->my_prog &&
				NL_MY_VERS(temp) == id->my_vers) {
				/* Hey!  We already know you guys! */
				dprintf(N_DEBUG,
					"Duplicate SM_MON request for %s "
					"from procedure on %s",
					mon_name, my_name);

				/* But we'll let you pass anyway. */
				result.res_stat = STAT_SUCC;
				result.state = MY_STATE;
				return (&result);
			}
			temp = NL_NEXT(temp);
		}
	}

	/*
	 * 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))) {
		note(N_WARNING, "out of memory");
		goto failure;
	}

	NL_ADDR(clnt) = my_addr;
	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);

	/*
	 * Now, Create file on stable storage for host.
	 */

	path=xmalloc(strlen(SM_DIR)+strlen(mon_name)+2);
	sprintf(path, "%s/%s", SM_DIR, mon_name);
	if ((fd = open(path, O_WRONLY|O_SYNC|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
		/* Didn't fly.  We won't monitor. */
		note(N_ERROR, "creat(%s) failed: %s", path, strerror (errno));
		nlist_free(NULL, clnt);
		free(path);
		goto failure;
	}
	free(path);
	/* PRC: do the HA callout: */
	ha_callout("add-client", mon_name, my_name, -1);
	nlist_insert(&rtnl, clnt);
	close(fd);

	result.res_stat = STAT_SUCC;
	result.state = MY_STATE;
	dprintf(N_DEBUG, "MONITORING %s for %s", mon_name, my_name);
	return (&result);

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


/*
 * 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;
#ifdef RESTRICTED_STATD
	struct in_addr	caller;
#endif

	result.state = MY_STATE;

#ifdef RESTRICTED_STATD
	/* 1.	Reject anyone not calling from 127.0.0.1.
	 *	Ignore the my_name specified by the caller, and
	 *	use "127.0.0.1" instead.
	 */
	caller = svc_getcaller(rqstp->rq_xprt)->sin_addr;
	if (caller.s_addr != htonl(INADDR_LOOPBACK)) {
		note(N_WARNING,
			"Call to statd from non-local host %s",
			inet_ntoa(caller));
		goto failure;
	}
	my_name = "127.0.0.1";
#endif

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

	/*
	 * 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 (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! */
			dprintf(N_DEBUG, "UNMONITORING %s for %s",
					mon_name, my_name);

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

			nlist_free(&rtnl, clnt);
			xunlink(SM_DIR, mon_name, 1);

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

 failure:
	note(N_WARNING, "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;
#ifdef RESTRICTED_STATD
	struct in_addr	caller;

	/* 1.	Reject anyone not calling from 127.0.0.1.
	 *	Ignore the my_name specified by the caller, and
	 *	use "127.0.0.1" instead.
	 */
	caller = svc_getcaller(rqstp->rq_xprt)->sin_addr;
	if (caller.s_addr != htonl(INADDR_LOOPBACK)) {
		note(N_WARNING,
			"Call to statd from non-local host %s",
			inet_ntoa(caller));
		goto failure;
	}
	my_name = "127.0.0.1";
#endif

	result.state = MY_STATE;

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

	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;

			dprintf(N_DEBUG,
				"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);
			nlist_free(&rtnl, clnt);
			xunlink(SM_DIR, mon_name, 1);
			++count;
			clnt = temp;
		} else
			clnt = NL_NEXT(clnt);
	}

	if (!count) {
		dprintf(N_DEBUG, "SM_UNMON_ALL request from %s with no "
			"SM_MON requests from it.", my_name);
	}
 failure:
	return (&result);
}