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 2010 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/*
28 * Copyright 2018 Nexenta Systems, Inc.
29 */
30
31#include <sys/param.h>
32#include <sys/types.h>
33#include <sys/pathname.h>
34#include <sys/errno.h>
35#include <sys/cmn_err.h>
36#include <sys/debug.h>
37#include <sys/systm.h>
38#include <sys/unistd.h>
39#include <sys/door.h>
40#include <sys/socket.h>
41#include <nfs/export.h>
42#include <nfs/nfs_cmd.h>
43#include <sys/kmem.h>
44#include <sys/sunddi.h>
45
46#define	NFSCMD_DR_TRYCNT	8
47
48#ifdef nextdp
49#undef nextdp
50#endif
51#define	nextdp(dp)	((struct dirent64 *)((char *)(dp) + (dp)->d_reclen))
52
53typedef struct nfscmd_globals {
54	kmutex_t	nfscmd_lock;
55	door_handle_t   nfscmd_dh;
56} nfscmd_globals_t;
57
58static zone_key_t nfscmd_zone_key;
59
60static struct charset_cache *nfscmd_charmap(exportinfo_t *exi,
61    struct sockaddr *sp);
62static void *nfscmd_zone_init(zoneid_t);
63static void nfscmd_zone_fini(zoneid_t, void *);
64
65void
66nfscmd_args(uint_t did)
67{
68	nfscmd_globals_t *ncg = zone_getspecific(nfscmd_zone_key, curzone);
69
70	mutex_enter(&ncg->nfscmd_lock);
71	if (ncg->nfscmd_dh != NULL)
72		door_ki_rele(ncg->nfscmd_dh);
73	ncg->nfscmd_dh = door_ki_lookup(did);
74	mutex_exit(&ncg->nfscmd_lock);
75}
76
77void
78nfscmd_init(void)
79{
80	zone_key_create(&nfscmd_zone_key, nfscmd_zone_init,
81	    NULL, nfscmd_zone_fini);
82}
83
84void
85nfscmd_fini(void)
86{
87	(void) zone_key_delete(nfscmd_zone_key);
88}
89
90/*ARGSUSED*/
91static void *
92nfscmd_zone_init(zoneid_t zoneid)
93{
94	nfscmd_globals_t *ncg;
95
96	ncg = kmem_zalloc(sizeof (*ncg), KM_SLEEP);
97	mutex_init(&ncg->nfscmd_lock, NULL, MUTEX_DEFAULT, NULL);
98
99	return (ncg);
100}
101
102/*ARGSUSED*/
103static void
104nfscmd_zone_fini(zoneid_t zoneid, void *data)
105{
106	nfscmd_globals_t *ncg = data;
107
108	mutex_destroy(&ncg->nfscmd_lock);
109	if (ncg->nfscmd_dh)
110		door_ki_rele(ncg->nfscmd_dh);
111	kmem_free(ncg, sizeof (*ncg));
112}
113
114/*
115 * nfscmd_send(arg, result)
116 *
117 * Send a command to the daemon listening on the door. The result is
118 * returned in the result pointer if the function return value is
119 * NFSCMD_ERR_SUCCESS. Otherwise it is the error value.
120 */
121int
122nfscmd_send(nfscmd_arg_t *arg, nfscmd_res_t *res)
123{
124	door_handle_t	dh;
125	door_arg_t	da;
126	door_info_t	di;
127	int		ntries = 0;
128	int		last = 0;
129	nfscmd_globals_t *ncg = zone_getspecific(nfscmd_zone_key, curzone);
130
131retry:
132	mutex_enter(&ncg->nfscmd_lock);
133	dh = ncg->nfscmd_dh;
134	if (dh != NULL)
135		door_ki_hold(dh);
136	mutex_exit(&ncg->nfscmd_lock);
137
138	if (dh == NULL) {
139		/*
140		 * The rendezvous point has not been established yet !
141		 * This could mean that either mountd(1m) has not yet
142		 * been started or that _this_ routine nuked the door
143		 * handle after receiving an EINTR for a REVOKED door.
144		 *
145		 * Returning NFSAUTH_DROP will cause the NFS client
146		 * to retransmit the request, so let's try to be more
147		 * rescillient and attempt for ntries before we bail.
148		 */
149		if (++ntries % NFSCMD_DR_TRYCNT) {
150			delay(hz);
151			goto retry;
152		}
153		return (NFSCMD_ERR_DROP);
154	}
155
156	da.data_ptr = (char *)arg;
157	da.data_size = sizeof (nfscmd_arg_t);
158	da.desc_ptr = NULL;
159	da.desc_num = 0;
160	da.rbuf = (char *)res;
161	da.rsize = sizeof (nfscmd_res_t);
162
163	switch (door_ki_upcall(dh, &da)) {
164	case 0:
165		/* Success */
166		break;
167	case EAGAIN:
168		/* Need to retry a couple of times */
169		door_ki_rele(dh);
170		delay(hz);
171		goto retry;
172		/* NOTREACHED */
173	case EINTR:
174		if (!door_ki_info(dh, &di)) {
175			if (di.di_attributes & DOOR_REVOKED) {
176				/*
177				 * The server barfed and revoked
178				 * the (existing) door on us; we
179				 * want to wait to give smf(5) a
180				 * chance to restart mountd(1m)
181				 * and establish a new door handle.
182				 */
183				mutex_enter(&ncg->nfscmd_lock);
184				if (dh == ncg->nfscmd_dh)
185					ncg->nfscmd_dh = NULL;
186				mutex_exit(&ncg->nfscmd_lock);
187				door_ki_rele(dh);
188				delay(hz);
189				goto retry;
190			}
191			/*
192			 * If the door was _not_ revoked on us,
193			 * then more than likely we took an INTR,
194			 * so we need to fail the operation.
195			 */
196			door_ki_rele(dh);
197		}
198		/*
199		 * The only failure that can occur from getting
200		 * the door info is EINVAL, so we let the code
201		 * below handle it.
202		 */
203		/* FALLTHROUGH */
204
205	case EBADF:
206	case EINVAL:
207	default:
208		/*
209		 * If we have a stale door handle, give smf a last
210		 * chance to start it by sleeping for a little bit.
211		 * If we're still hosed, we'll fail the call.
212		 *
213		 * Since we're going to reacquire the door handle
214		 * upon the retry, we opt to sleep for a bit and
215		 * _not_ to clear mountd_dh. If mountd restarted
216		 * and was able to set mountd_dh, we should see
217		 * the new instance; if not, we won't get caught
218		 * up in the retry/DELAY loop.
219		 */
220		door_ki_rele(dh);
221		if (!last) {
222			delay(hz);
223			last++;
224			goto retry;
225		}
226		res->error = NFSCMD_ERR_FAIL;
227		break;
228	}
229	return (res->error);
230}
231
232/*
233 * nfscmd_findmap(export, addr)
234 *
235 * Find a characterset map for the specified client address.
236 * First try to find a cached entry. If not successful,
237 * ask mountd daemon running in userland.
238 *
239 * For most of the clients this function is NOOP, since
240 * EX_CHARMAP flag won't be set.
241 */
242struct charset_cache *
243nfscmd_findmap(struct exportinfo *exi, struct sockaddr *sp)
244{
245	struct charset_cache *charset;
246
247	/*
248	 * In debug kernel we want to know about strayed nulls.
249	 * In non-debug kernel we behave gracefully.
250	 */
251	ASSERT(exi != NULL);
252	ASSERT(sp != NULL);
253
254	if (exi == NULL || sp == NULL)
255		return (NULL);
256
257	mutex_enter(&exi->exi_lock);
258
259	if (!(exi->exi_export.ex_flags & EX_CHARMAP)) {
260		mutex_exit(&exi->exi_lock);
261		return (NULL);
262	}
263
264	for (charset = exi->exi_charset;
265	    charset != NULL;
266	    charset = charset->next) {
267		if (bcmp(sp, &charset->client_addr,
268		    sizeof (struct sockaddr)) == 0)
269			break;
270	}
271	mutex_exit(&exi->exi_lock);
272
273	/* the slooow way - ask daemon */
274	if (charset == NULL)
275		charset = nfscmd_charmap(exi, sp);
276
277	return (charset);
278}
279
280/*
281 * nfscmd_insert_charmap(export, addr, name)
282 *
283 * Insert a new character set conversion map into the export structure
284 * for the share. The entry has the IP address of the client and the
285 * character set name.
286 */
287
288static struct charset_cache *
289nfscmd_insert_charmap(struct exportinfo *exi, struct sockaddr *sp, char *name)
290{
291	struct charset_cache *charset;
292
293	charset = (struct charset_cache *)
294	    kmem_zalloc(sizeof (struct charset_cache), KM_SLEEP);
295
296	if (charset == NULL)
297		return (NULL);
298	if (name != NULL) {
299		charset->inbound = kiconv_open("UTF-8", name);
300		charset->outbound = kiconv_open(name, "UTF-8");
301	}
302	charset->client_addr = *sp;
303	mutex_enter(&exi->exi_lock);
304	charset->next = exi->exi_charset;
305	exi->exi_charset = charset;
306	mutex_exit(&exi->exi_lock);
307
308	return (charset);
309}
310
311/*
312 * nfscmd_charmap(response, sp, exi)
313 *
314 * Check to see if this client needs a character set conversion.
315 */
316static struct charset_cache *
317nfscmd_charmap(exportinfo_t *exi, struct sockaddr *sp)
318{
319	nfscmd_arg_t req;
320	int ret;
321	char *path;
322	nfscmd_res_t res;
323	struct charset_cache *charset;
324
325	path = exi->exi_export.ex_path;
326	if (path == NULL)
327		return (NULL);
328
329	/*
330	 * nfscmd_findmap() did not find one in the cache so make
331	 * the request to the daemon. We need to add the entry in
332	 * either case since we want negative as well as
333	 * positive cacheing.
334	 */
335	req.cmd = NFSCMD_CHARMAP_LOOKUP;
336	req.version = NFSCMD_VERSION;
337	req.arg.charmap.addr = *sp;
338	(void) strncpy(req.arg.charmap.path, path, MAXPATHLEN);
339	bzero((caddr_t)&res, sizeof (nfscmd_res_t));
340	ret = nfscmd_send(&req, &res);
341	if (ret == NFSCMD_ERR_SUCCESS)
342		charset = nfscmd_insert_charmap(exi, sp,
343		    res.result.charmap.codeset);
344	else
345		charset = nfscmd_insert_charmap(exi, sp, NULL);
346
347	return (charset);
348}
349
350/*
351 * nfscmd_convname(addr, export, name, inbound, size)
352 *
353 * Convert the given "name" string to the appropriate character set.
354 * If inbound is true, convert from the client character set to UTF-8.
355 * If inbound is false, convert from UTF-8 to the client characters set.
356 *
357 * In case of NFS v4 this is used for ill behaved clients, since
358 * according to the standard all file names should be utf-8 encoded
359 * on client-side.
360 */
361
362char *
363nfscmd_convname(struct sockaddr *ca, struct exportinfo *exi, char *name,
364    int inbound, size_t size)
365{
366	char *newname;
367	char *holdname;
368	int err;
369	int ret;
370	size_t nsize;
371	size_t osize;
372	struct charset_cache *charset = NULL;
373
374	charset = nfscmd_findmap(exi, ca);
375	if (charset == NULL ||
376	    (charset->inbound == NULL && inbound) ||
377	    (charset->outbound == NULL && !inbound))
378		return (name);
379
380	/* make sure we have more than enough space */
381	newname = kmem_zalloc(size, KM_SLEEP);
382	nsize = strlen(name);
383	osize = size;
384	holdname = newname;
385	if (inbound)
386		ret = kiconv(charset->inbound, &name, &nsize,
387		    &holdname, &osize, &err);
388	else
389		ret = kiconv(charset->outbound, &name, &nsize,
390		    &holdname, &osize, &err);
391	if (ret == (size_t)-1) {
392		kmem_free(newname, size);
393		newname = NULL;
394	}
395
396	return (newname);
397}
398
399/*
400 * nfscmd_convdirent()
401 *
402 * There is only one entry in the data.  Convert to new charset, if
403 * required and only return a success if it fits.
404 */
405char *
406nfscmd_convdirent(struct sockaddr *ca, struct exportinfo *exi, char *data,
407    size_t size, enum nfsstat3 *error)
408{
409	char *newdata;
410	size_t ret;
411	size_t nsize;
412	size_t count;
413	int err = 0;
414	char *iname;
415	char *oname;
416	struct charset_cache *charset;
417
418	charset = nfscmd_findmap(exi, ca);
419	if (charset == NULL || charset->outbound == (void *)~0)
420		return (data);
421
422	newdata = kmem_zalloc(size, KM_SLEEP);
423
424	nsize = strlen(((struct dirent64 *)data)->d_name);
425	count = size;
426	bcopy(data, newdata, sizeof (struct dirent64));
427
428	iname = ((struct dirent64 *)data)->d_name;
429	oname = ((struct dirent64 *)newdata)->d_name;
430
431	ret = kiconv(charset->outbound, &iname, &nsize, &oname, &count, &err);
432	if (ret == (size_t)-1) {
433		kmem_free(newdata, size);
434		newdata = NULL;
435		if (err == E2BIG) {
436			if (error != NULL)
437				*error = NFS3ERR_NAMETOOLONG;
438		} else {
439			newdata = data;
440		}
441	} else {
442		ret = strlen(((struct dirent64 *)newdata)->d_name);
443		((struct dirent64 *)newdata)->d_reclen =
444		    DIRENT64_RECLEN(ret + 1);
445	}
446	return (newdata);
447}
448
449/*
450 * nfscmd_convdirplus(addr, export, data, nents, maxsize, ndata)
451 *
452 * Convert the dirents in data into a new list of dirents in ndata.
453 */
454
455size_t
456nfscmd_convdirplus(struct sockaddr *ca, struct exportinfo *exi, char *data,
457    size_t nents, size_t maxsize, char **ndata)
458{
459	char *newdata;
460	size_t nsize;
461	struct dirent64 *dp;
462	struct dirent64 *ndp;
463	size_t i;
464	size_t ret;
465	char *iname;
466	char *oname;
467	size_t ilen;
468	size_t olen;
469	int err;
470	size_t skipped;
471	struct charset_cache *charset;
472	*ndata = data;	/* return the data if no changes to make */
473
474	charset = nfscmd_findmap(exi, ca);
475
476	if (charset == NULL || charset->outbound == (void *)~0)
477		return (0);
478
479	newdata = kmem_zalloc(maxsize, KM_SLEEP);
480	nsize = 0;
481
482	dp = (struct dirent64 *)data;
483	ndp = (struct dirent64 *)newdata;
484
485	for (skipped = 0, i = 0; i < nents; i++) {
486		/*
487		 * Copy the dp information if it fits. Then copy and
488		 * convert the name in the entry.
489		 */
490		if ((maxsize - nsize) < dp->d_reclen)
491			/* doesn't fit */
492			break;
493		*ndp = *dp;
494		iname = dp->d_name;
495		ilen = strlen(iname);
496		oname = ndp->d_name;
497		olen = MIN(MAXNAMELEN, maxsize - nsize);
498		ret = kiconv(charset->outbound, &iname, &ilen, &oname,
499		    &olen, &err);
500
501		if (ret == (size_t)-1) {
502			switch (err) {
503			default:
504			case E2BIG:
505				break;
506			case EILSEQ:
507				skipped++;
508				dp = nextdp(dp);
509				continue;
510			}
511		}
512		ilen = MIN(MAXNAMELEN, maxsize - nsize) - olen;
513		ndp->d_name[ilen] = '\0';
514		/*
515		 * What to do with other errors?
516		 * For now, we return the unconverted string.
517		 */
518		ndp->d_reclen = DIRENT64_RECLEN(strlen(ndp->d_name) + 1);
519		nsize += ndp->d_reclen;
520		dp = nextdp(dp);
521		ndp = nextdp(ndp);
522	}
523
524	*ndata = newdata;
525	return (nents - (i + skipped));
526}
527
528/*
529 * nfscmd_countents(data, len)
530 *
531 * How many dirents are there in the data buffer?
532 */
533
534size_t
535nfscmd_countents(char *data, size_t len)
536{
537	struct dirent64 *dp = (struct dirent64 *)data;
538	size_t curlen;
539	size_t reclen;
540	size_t nents;
541
542	for (nents = 0, curlen = 0; curlen < len; curlen += reclen, nents++) {
543		reclen = dp->d_reclen;
544		dp = nextdp(dp);
545	}
546	return (nents);
547}
548
549/*
550 * nfscmd_dropped_entrysize(dir, drop, nents)
551 *
552 * We need to drop "drop" entries from dir in order to fit in the
553 * buffer.  How much do we reduce the overall size by?
554 */
555
556size_t
557nfscmd_dropped_entrysize(struct dirent64 *dir, size_t drop, size_t nents)
558{
559	size_t size;
560	size_t i;
561
562	for (i = nents - drop; i > 0 && dir != NULL; i--)
563		dir = nextdp(dir);
564
565	if (dir == NULL)
566		return (0);
567
568	for (size = 0, i = 0; i < drop && dir != NULL; i++) {
569		size += dir->d_reclen;
570		dir = nextdp(dir);
571	}
572	return (size);
573}
574