1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
24 */
25
26/*
27 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
28 * Use is subject to license terms.
29 */
30
31#include <stdio.h>
32#include <stdlib.h>
33#include <ctype.h>
34#include <sys/types.h>
35#include <string.h>
36#include <sys/param.h>
37#include <sys/stat.h>
38#include <sys/file.h>
39#include <sys/time.h>
40#include <errno.h>
41#include <rpcsvc/mount.h>
42#include <sys/pathconf.h>
43#include <sys/systeminfo.h>
44#include <sys/utsname.h>
45#include <signal.h>
46#include <locale.h>
47#include <unistd.h>
48#include <thread.h>
49#include <syslog.h>
50#include <sys/socket.h>
51#include <netinet/in.h>
52#include <arpa/inet.h>
53#include <sharefs/share.h>
54#include "../lib/sharetab.h"
55#include "hashset.h"
56#include "mountd.h"
57
58static char RMTAB[] = "/etc/rmtab";
59static FILE *rmtabf = NULL;
60
61/*
62 * There is nothing magic about the value selected here. Too low,
63 * and mountd might spend too much time rewriting the rmtab file.
64 * Too high, it won't do it frequently enough.
65 */
66static int rmtab_del_thresh = 250;
67
68#define	RMTAB_TOOMANY_DELETED()	\
69	((rmtab_deleted > rmtab_del_thresh) && (rmtab_deleted > rmtab_inuse))
70
71/*
72 * mountd's version of a "struct mountlist". It is the same except
73 * for the added ml_pos field.
74 */
75struct mntentry {
76	char  *m_host;
77	char  *m_path;
78	long   m_pos;
79};
80
81static HASHSET mntlist;
82
83static int mntentry_equal(const void *, const void *);
84static uint32_t mntentry_hash(const void *);
85static int mntlist_contains(char *, char *);
86static void rmtab_delete(long);
87static long rmtab_insert(char *, char *);
88static void rmtab_rewrite(void);
89static void rmtab_parse(char *buf);
90static bool_t xdr_mntlistencode(XDR * xdrs, HASHSET * mntlist);
91
92#define	exstrdup(s) \
93	strcpy(exmalloc(strlen(s)+1), s)
94
95
96static int rmtab_inuse;
97static int rmtab_deleted;
98
99static rwlock_t rmtab_lock;	/* lock to protect rmtab list */
100
101
102/*
103 * Check whether the given client/path combination
104 * already appears in the mount list.
105 */
106
107static int
108mntlist_contains(char *host, char *path)
109{
110	struct mntentry m;
111
112	m.m_host = host;
113	m.m_path = path;
114
115	return (h_get(mntlist, &m) != NULL);
116}
117
118
119/*
120 *  Add an entry to the mount list.
121 *  First check whether it's there already - the client
122 *  may have crashed and be rebooting.
123 */
124
125static void
126mntlist_insert(char *host, char *path)
127{
128	if (!mntlist_contains(host, path)) {
129		struct mntentry *m;
130
131		m = exmalloc(sizeof (struct mntentry));
132
133		m->m_host = exstrdup(host);
134		m->m_path = exstrdup(path);
135		m->m_pos = rmtab_insert(host, path);
136		(void) h_put(mntlist, m);
137	}
138}
139
140void
141mntlist_new(char *host, char *path)
142{
143	(void) rw_wrlock(&rmtab_lock);
144	mntlist_insert(host, path);
145	(void) rw_unlock(&rmtab_lock);
146}
147
148/*
149 * Delete an entry from the mount list.
150 */
151
152void
153mntlist_delete(char *host, char *path)
154{
155	struct mntentry *m, mm;
156
157	mm.m_host = host;
158	mm.m_path = path;
159
160	(void) rw_wrlock(&rmtab_lock);
161
162	if ((m = (struct mntentry *)h_get(mntlist, &mm)) != NULL) {
163		rmtab_delete(m->m_pos);
164
165		(void) h_delete(mntlist, m);
166
167		free(m->m_path);
168		free(m->m_host);
169		free(m);
170
171		if (RMTAB_TOOMANY_DELETED())
172			rmtab_rewrite();
173	}
174	(void) rw_unlock(&rmtab_lock);
175}
176
177/*
178 * Delete all entries for a host from the mount list
179 */
180
181void
182mntlist_delete_all(char *host)
183{
184	HASHSET_ITERATOR iterator;
185	struct mntentry *m;
186
187	(void) rw_wrlock(&rmtab_lock);
188
189	iterator = h_iterator(mntlist);
190
191	while ((m = (struct mntentry *)h_next(iterator)) != NULL) {
192		if (strcasecmp(m->m_host, host))
193			continue;
194
195		rmtab_delete(m->m_pos);
196
197		(void) h_delete(mntlist, m);
198
199		free(m->m_path);
200		free(m->m_host);
201		free(m);
202	}
203
204	if (RMTAB_TOOMANY_DELETED())
205		rmtab_rewrite();
206
207	(void) rw_unlock(&rmtab_lock);
208
209	if (iterator != NULL)
210		free(iterator);
211}
212
213/*
214 * Equivalent to xdr_mountlist from librpcsvc but for HASHSET
215 * rather that for a linked list. It is used only to encode data
216 * from HASHSET before sending it over the wire.
217 */
218
219static bool_t
220xdr_mntlistencode(XDR *xdrs, HASHSET *mntlist)
221{
222	HASHSET_ITERATOR iterator = h_iterator(*mntlist);
223
224	for (;;) {
225		struct mntentry *m = (struct mntentry *)h_next(iterator);
226		bool_t more_data = (m != NULL);
227
228		if (!xdr_bool(xdrs, &more_data)) {
229			if (iterator != NULL)
230				free(iterator);
231			return (FALSE);
232		}
233
234		if (!more_data)
235			break;
236
237		if ((!xdr_name(xdrs, &m->m_host)) ||
238		    (!xdr_dirpath(xdrs, &m->m_path))) {
239			if (iterator != NULL)
240				free(iterator);
241			return (FALSE);
242		}
243
244	}
245
246	if (iterator != NULL)
247		free(iterator);
248
249	return (TRUE);
250}
251
252void
253mntlist_send(SVCXPRT *transp)
254{
255	(void) rw_rdlock(&rmtab_lock);
256
257	errno = 0;
258	if (!svc_sendreply(transp, xdr_mntlistencode, (char *)&mntlist))
259		log_cant_reply(transp);
260
261	(void) rw_unlock(&rmtab_lock);
262}
263
264/*
265 * Compute a 32 bit hash value for an mntlist entry.
266 */
267
268/*
269 * The string hashing algorithm is from the "Dragon Book" --
270 * "Compilers: Principles, Tools & Techniques", by Aho, Sethi, Ullman
271 *
272 * And is modified for this application from usr/src/uts/common/os/modhash.c
273 */
274
275static uint_t
276mntentry_str_hash(char *s, uint_t hash)
277{
278	uint_t	g;
279
280	for (; *s != '\0'; s++) {
281		hash = (hash << 4) + *s;
282		if ((g = (hash & 0xf0000000)) != 0) {
283			hash ^= (g >> 24);
284			hash ^= g;
285		}
286	}
287
288	return (hash);
289}
290
291static uint32_t
292mntentry_hash(const void *p)
293{
294	struct mntentry *m = (struct mntentry *)p;
295	uint_t hash;
296
297	hash = mntentry_str_hash(m->m_host, 0);
298	hash = mntentry_str_hash(m->m_path, hash);
299
300	return (hash);
301}
302
303/*
304 * Compare mntlist entries.
305 * The comparison ignores a value of m_pos.
306 */
307
308static int
309mntentry_equal(const void *p1, const void *p2)
310{
311	struct mntentry *m1 = (struct mntentry *)p1;
312	struct mntentry *m2 = (struct mntentry *)p2;
313
314	return ((strcasecmp(m1->m_host, m2->m_host) ||
315	    strcmp(m1->m_path, m2->m_path)) ? 0 : 1);
316}
317
318/*
319 * Rewrite /etc/rmtab with a current content of mntlist.
320 */
321static void
322rmtab_rewrite()
323{
324	if (rmtabf)
325		(void) fclose(rmtabf);
326
327	/* Rewrite the file. */
328	if ((rmtabf = fopen(RMTAB, "w+")) != NULL) {
329		HASHSET_ITERATOR iterator;
330		struct mntentry *m;
331
332		(void) fchmod(fileno(rmtabf),
333		    (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH));
334		rmtab_inuse = rmtab_deleted = 0;
335
336		iterator = h_iterator(mntlist);
337
338		while ((m = (struct mntentry *)h_next(iterator)) != NULL)
339			m->m_pos = rmtab_insert(m->m_host, m->m_path);
340		if (iterator != NULL)
341			free(iterator);
342	}
343}
344
345/*
346 * Parse the content of /etc/rmtab and insert the entries into mntlist.
347 * The buffer s should be ended with a NUL char.
348 */
349
350static void
351rmtab_parse(char *s)
352{
353	char  *host;
354	char  *path;
355	char  *tmp;
356	struct in6_addr ipv6addr;
357
358host_part:
359	if (*s == '#')
360		goto skip_rest;
361
362	host = s;
363	for (;;) {
364		switch (*s++) {
365		case '\0':
366			return;
367		case '\n':
368			goto host_part;
369		case ':':
370			s[-1] = '\0';
371			goto path_part;
372		case '[':
373			tmp = strchr(s, ']');
374			if (tmp) {
375				*tmp = '\0';
376				if (inet_pton(AF_INET6, s, &ipv6addr) > 0) {
377					host = s;
378					s = ++tmp;
379				} else
380					*tmp = ']';
381			}
382			/* FALLTHROUGH */
383		default:
384			continue;
385		}
386	}
387
388path_part:
389	path = s;
390	for (;;) {
391		switch (*s++) {
392		case '\n':
393			s[-1] = '\0';
394			if (*host && *path)
395				mntlist_insert(host, path);
396			goto host_part;
397		case '\0':
398			if (*host && *path)
399				mntlist_insert(host, path);
400			return;
401		default:
402			continue;
403		}
404	}
405
406skip_rest:
407	for (;;) {
408		switch (*++s) {
409		case '\n':
410			goto host_part;
411		case '\0':
412			return;
413		default:
414			continue;
415		}
416	}
417}
418
419/*
420 * Read in contents of rmtab.
421 * Call rmtab_parse to parse the file and store entries in mntlist.
422 * Rewrites the file to get rid of unused entries.
423 */
424
425#define	RMTAB_LOADLEN	(16*2024)	/* Max bytes to read at a time */
426
427void
428rmtab_load()
429{
430	FILE *fp;
431
432	(void) rwlock_init(&rmtab_lock, USYNC_THREAD, NULL);
433
434	/*
435	 * Don't need to lock the list at this point
436	 * because there's only a single thread running.
437	 */
438	mntlist = h_create(mntentry_hash, mntentry_equal, 101, 0.75);
439
440	if ((fp = fopen(RMTAB, "r")) != NULL) {
441		char buf[RMTAB_LOADLEN+1];
442		size_t len;
443
444		/*
445		 * Read at most RMTAB_LOADLEN bytes from /etc/rmtab.
446		 * - if fread returns RMTAB_LOADLEN we can be in the middle
447		 *   of a line so change the last newline character into NUL
448		 *   and seek back to the next character after newline.
449		 * - otherwise set NUL behind the last character read.
450		 */
451		while ((len = fread(buf, 1, RMTAB_LOADLEN, fp)) > 0) {
452			if (len == RMTAB_LOADLEN) {
453				int i;
454
455				for (i = 1; i < len; i++) {
456					if (buf[len-i] == '\n') {
457						buf[len-i] = '\0';
458						(void) fseek(fp, -i + 1,
459						    SEEK_CUR);
460						goto parse;
461					}
462				}
463			}
464
465			/* Put a NUL character at the end of buffer */
466			buf[len] = '\0';
467	parse:
468			rmtab_parse(buf);
469		}
470		(void) fclose(fp);
471	}
472	rmtab_rewrite();
473}
474
475/*
476 * Write an entry at the current location in rmtab
477 * for the given client and path.
478 *
479 * Returns the starting position of the entry
480 * or -1 if there was an error.
481 */
482
483long
484rmtab_insert(char *host, char *path)
485{
486	long   pos;
487	struct in6_addr ipv6addr;
488
489	if (rmtabf == NULL || fseek(rmtabf, 0L, 2) == -1) {
490		return (-1);
491	}
492	pos = ftell(rmtabf);
493
494	/*
495	 * Check if host is an IPv6 literal
496	 */
497
498	if (inet_pton(AF_INET6, host, &ipv6addr) > 0) {
499		if (fprintf(rmtabf, "[%s]:%s\n", host, path) == EOF) {
500			return (-1);
501		}
502	} else {
503		if (fprintf(rmtabf, "%s:%s\n", host, path) == EOF) {
504			return (-1);
505		}
506	}
507	if (fflush(rmtabf) == EOF) {
508		return (-1);
509	}
510	rmtab_inuse++;
511	return (pos);
512}
513
514/*
515 * Mark as unused the rmtab entry at the given offset in the file.
516 */
517
518void
519rmtab_delete(long pos)
520{
521	if (rmtabf != NULL && pos != -1 && fseek(rmtabf, pos, 0) == 0) {
522		(void) fprintf(rmtabf, "#");
523		(void) fflush(rmtabf);
524
525		rmtab_inuse--;
526		rmtab_deleted++;
527	}
528}
529