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  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * Door server routines for nfsmapid daemon
30  * Translate NFSv4 users and groups between numeric and string values
31  */
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <alloca.h>
35 #include <signal.h>
36 #include <libintl.h>
37 #include <limits.h>
38 #include <errno.h>
39 #include <sys/types.h>
40 #include <string.h>
41 #include <memory.h>
42 #include <pwd.h>
43 #include <grp.h>
44 #include <door.h>
45 #include <syslog.h>
46 #include <fcntl.h>
47 #include <unistd.h>
48 #include <assert.h>
49 #include <deflt.h>
50 #include <nfs/nfs4.h>
51 #include <nfs/nfssys.h>
52 #include <nfs/nfsid_map.h>
53 #include <nfs/mapid.h>
54 #include <sys/sdt.h>
55 
56 /*
57  * We cannot use the backend nscd as it may make syscalls that may
58  * cause further nfsmapid upcalls introducing deadlock.
59  * Use the internal uncached versions of get*_r.
60  */
61 extern struct group *_uncached_getgrgid_r(gid_t, struct group *, char *, int);
62 extern struct group *_uncached_getgrnam_r(const char *, struct group *,
63     char *, int);
64 extern struct passwd *_uncached_getpwuid_r(uid_t, struct passwd *, char *, int);
65 extern struct passwd *_uncached_getpwnam_r(const char *, struct passwd *,
66     char *, int);
67 
68 #define		UID_MAX_STR_LEN		11	/* Digits in UID_MAX + 1 */
69 #define		DIAG_FILE		"/var/run/nfs4_domain"
70 
71 /*
72  * idmap_kcall() takes a door descriptor as it's argument when we
73  * need to (re)establish the in-kernel door handles. When we only
74  * want to flush the id kernel caches, we don't redo the door setup.
75  */
76 #define		FLUSH_KCACHES_ONLY	(int)-1
77 
78 FILE		*n4_fp;
79 int		 n4_fd;
80 
81 extern size_t	pwd_buflen;
82 extern size_t	grp_buflen;
83 extern thread_t	sig_thread;
84 
85 /*
86  * Prototypes
87  */
88 extern void	 check_domain(int);
89 extern void	 idmap_kcall(int);
90 extern int	 _nfssys(int, void *);
91 extern int	 valid_domain(const char *);
92 extern int	 validate_id_str(const char *);
93 extern int	 extract_domain(char *, char **, char **);
94 extern void	 update_diag_file(char *);
95 extern void	*cb_update_domain(void *);
96 extern int	 cur_domain_null(void);
97 
98 void
99 nfsmapid_str_uid(struct mapid_arg *argp, size_t arg_size)
100 {
101 	struct mapid_res result;
102 	struct passwd	 pwd;
103 	char		*pwd_buf;
104 	char		*user;
105 	char		*domain;
106 
107 	if (argp->u_arg.len <= 0 || arg_size < MAPID_ARG_LEN(argp->u_arg.len)) {
108 		result.status = NFSMAPID_INVALID;
109 		result.u_res.uid = UID_NOBODY;
110 		goto done;
111 	}
112 
113 	if (!extract_domain(argp->str, &user, &domain)) {
114 		long id;
115 
116 		/*
117 		 * Invalid "user@dns_domain" string. Still, the user
118 		 * part might be an encoded uid, so do a final check.
119 		 * Remember, domain part of string was not set since
120 		 * not a valid string.
121 		 */
122 		if (!validate_id_str(user)) {
123 			result.status = NFSMAPID_UNMAPPABLE;
124 			result.u_res.uid = UID_NOBODY;
125 			goto done;
126 		}
127 
128 		/*
129 		 * Since atoi() does not return proper errors for
130 		 * invalid translation, use strtol() instead.
131 		 */
132 		errno = 0;
133 		id = strtol(user, (char **)NULL, 10);
134 
135 		if (errno || id < 0 || id > UID_MAX) {
136 			result.status = NFSMAPID_UNMAPPABLE;
137 			result.u_res.uid = UID_NOBODY;
138 			goto done;
139 		}
140 
141 		result.u_res.uid = (uid_t)id;
142 		result.status = NFSMAPID_NUMSTR;
143 		goto done;
144 	}
145 
146 	/*
147 	 * String properly constructed. Now we check for domain and
148 	 * group validity. Note that we only look at the domain iff
149 	 * the local domain is configured.
150 	 */
151 	if (!cur_domain_null() && !valid_domain(domain)) {
152 		result.status = NFSMAPID_BADDOMAIN;
153 		result.u_res.uid = UID_NOBODY;
154 		goto done;
155 	}
156 
157 	if ((pwd_buf = malloc(pwd_buflen)) == NULL ||
158 	    _uncached_getpwnam_r(user, &pwd, pwd_buf, pwd_buflen) == NULL) {
159 
160 		if (pwd_buf == NULL)
161 			result.status = NFSMAPID_INTERNAL;
162 		else {
163 			/*
164 			 * Not a valid user
165 			 */
166 			result.status = NFSMAPID_NOTFOUND;
167 			free(pwd_buf);
168 		}
169 		result.u_res.uid = UID_NOBODY;
170 		goto done;
171 	}
172 
173 	/*
174 	 * Valid user entry
175 	 */
176 	result.u_res.uid = pwd.pw_uid;
177 	result.status = NFSMAPID_OK;
178 	free(pwd_buf);
179 done:
180 	(void) door_return((char *)&result, sizeof (struct mapid_res), NULL, 0);
181 }
182 
183 /* ARGSUSED1 */
184 void
185 nfsmapid_uid_str(struct mapid_arg *argp, size_t arg_size)
186 {
187 	struct mapid_res	 result;
188 	struct mapid_res	*resp;
189 	struct passwd		 pwd;
190 	int			 pwd_len;
191 	char			*pwd_buf;
192 	uid_t			 uid = argp->u_arg.uid;
193 	size_t			 uid_str_len;
194 	char			*pw_str;
195 	size_t			 pw_str_len;
196 	char			*at_str;
197 	size_t			 at_str_len;
198 	char			 dom_str[DNAMEMAX];
199 	size_t			 dom_str_len;
200 
201 	if (uid < 0 || uid > UID_MAX) {
202 		/*
203 		 * Negative uid or greater than UID_MAX
204 		 */
205 		resp = &result;
206 		resp->status = NFSMAPID_BADID;
207 		resp->u_res.len = 0;
208 		goto done;
209 	}
210 
211 	/*
212 	 * Make local copy of domain for further manipuation
213 	 * NOTE: mapid_get_domain() returns a ptr to TSD.
214 	 */
215 	if (cur_domain_null()) {
216 		dom_str_len = 0;
217 		dom_str[0] = '\0';
218 	} else {
219 		dom_str_len = strlcpy(dom_str, mapid_get_domain(), DNAMEMAX);
220 	}
221 
222 	/*
223 	 * We want to encode the uid into a literal string... :
224 	 *
225 	 *	- upon failure to allocate space from the heap
226 	 *	- if there is no current domain configured
227 	 *	- if there is no such uid in the passwd DB's
228 	 */
229 	if ((pwd_buf = malloc(pwd_buflen)) == NULL || dom_str_len == 0 ||
230 	    _uncached_getpwuid_r(uid, &pwd, pwd_buf, pwd_buflen) == NULL) {
231 
232 		/*
233 		 * If we could not allocate from the heap, try
234 		 * allocating from the stack as a last resort.
235 		 */
236 		if (pwd_buf == NULL && (pwd_buf =
237 		    alloca(MAPID_RES_LEN(UID_MAX_STR_LEN))) == NULL) {
238 			resp = &result;
239 			resp->status = NFSMAPID_INTERNAL;
240 			resp->u_res.len = 0;
241 			goto done;
242 		}
243 
244 		/*
245 		 * Constructing literal string without '@' so that
246 		 * we'll know that it's not a user, but rather a
247 		 * uid encoded string. Can't overflow because we
248 		 * already checked UID_MAX.
249 		 */
250 		pw_str = pwd_buf;
251 		(void) sprintf(pw_str, "%d", (int)uid);
252 		pw_str_len = strlen(pw_str);
253 		at_str_len = dom_str_len = 0;
254 		at_str = "";
255 		dom_str[0] = '\0';
256 	} else {
257 		/*
258 		 * Otherwise, we construct the "user@domain" string
259 		 */
260 		pw_str = pwd.pw_name;
261 		pw_str_len = strlen(pw_str);
262 		at_str = "@";
263 		at_str_len = 1;
264 	}
265 
266 	uid_str_len = pw_str_len + at_str_len + dom_str_len;
267 	if ((resp = alloca(MAPID_RES_LEN(UID_MAX_STR_LEN))) == NULL) {
268 		resp = &result;
269 		resp->status = NFSMAPID_INTERNAL;
270 		resp->u_res.len = 0;
271 		goto done;
272 	}
273 	/* LINTED format argument to sprintf */
274 	(void) sprintf(resp->str, "%s%s%s", pw_str, at_str, dom_str);
275 	resp->u_res.len = uid_str_len;
276 	free(pwd_buf);
277 	resp->status = NFSMAPID_OK;
278 
279 done:
280 	/*
281 	 * There is a chance that the door_return will fail because the
282 	 * resulting string is too large, try to indicate that if possible
283 	 */
284 	if (door_return((char *)resp,
285 	    MAPID_RES_LEN(resp->u_res.len), NULL, 0) == -1) {
286 		resp->status = NFSMAPID_INTERNAL;
287 		resp->u_res.len = 0;
288 		(void) door_return((char *)&result, sizeof (struct mapid_res),
289 							NULL, 0);
290 	}
291 }
292 
293 void
294 nfsmapid_str_gid(struct mapid_arg *argp, size_t arg_size)
295 {
296 	struct mapid_res	result;
297 	struct group		grp;
298 	char			*grp_buf;
299 	char			*group;
300 	char			*domain;
301 
302 	if (argp->u_arg.len <= 0 ||
303 				arg_size < MAPID_ARG_LEN(argp->u_arg.len)) {
304 		result.status = NFSMAPID_INVALID;
305 		result.u_res.gid = GID_NOBODY;
306 		goto done;
307 	}
308 
309 	if (!extract_domain(argp->str, &group, &domain)) {
310 		long id;
311 
312 		/*
313 		 * Invalid "group@dns_domain" string. Still, the
314 		 * group part might be an encoded gid, so do a
315 		 * final check. Remember, domain part of string
316 		 * was not set since not a valid string.
317 		 */
318 		if (!validate_id_str(group)) {
319 			result.status = NFSMAPID_UNMAPPABLE;
320 			result.u_res.gid = GID_NOBODY;
321 			goto done;
322 		}
323 
324 		/*
325 		 * Since atoi() does not return proper errors for
326 		 * invalid translation, use strtol() instead.
327 		 */
328 		errno = 0;
329 		id = strtol(group, (char **)NULL, 10);
330 
331 		if (errno || id < 0 || id > UID_MAX) {
332 			result.status = NFSMAPID_UNMAPPABLE;
333 			result.u_res.gid = GID_NOBODY;
334 			goto done;
335 		}
336 
337 		result.u_res.gid = (gid_t)id;
338 		result.status = NFSMAPID_NUMSTR;
339 		goto done;
340 	}
341 
342 	/*
343 	 * String properly constructed. Now we check for domain and
344 	 * group validity. Note that we only look at the domain iff
345 	 * the local domain is configured.
346 	 */
347 	if (!cur_domain_null() && !valid_domain(domain)) {
348 		result.status = NFSMAPID_BADDOMAIN;
349 		result.u_res.gid = GID_NOBODY;
350 		goto done;
351 	}
352 
353 	if ((grp_buf = malloc(grp_buflen)) == NULL ||
354 	    _uncached_getgrnam_r(group, &grp, grp_buf, grp_buflen) == NULL) {
355 
356 		if (grp_buf == NULL)
357 			result.status = NFSMAPID_INTERNAL;
358 		else {
359 			/*
360 			 * Not a valid group
361 			 */
362 			result.status = NFSMAPID_NOTFOUND;
363 			free(grp_buf);
364 		}
365 		result.u_res.gid = GID_NOBODY;
366 		goto done;
367 	}
368 
369 	/*
370 	 * Valid group entry
371 	 */
372 	result.status = NFSMAPID_OK;
373 	result.u_res.gid = grp.gr_gid;
374 	free(grp_buf);
375 done:
376 	(void) door_return((char *)&result, sizeof (struct mapid_res), NULL, 0);
377 }
378 
379 /* ARGSUSED1 */
380 void
381 nfsmapid_gid_str(struct mapid_arg *argp, size_t arg_size)
382 {
383 	struct mapid_res	 result;
384 	struct mapid_res	*resp;
385 	struct group		 grp;
386 	char			*grp_buf;
387 	gid_t			 gid = argp->u_arg.gid;
388 	size_t			 gid_str_len;
389 	char			*gr_str;
390 	size_t			 gr_str_len;
391 	char			*at_str;
392 	size_t			 at_str_len;
393 	char			 dom_str[DNAMEMAX];
394 	size_t			 dom_str_len;
395 
396 	if (gid < 0 || gid > UID_MAX) {
397 		/*
398 		 * Negative gid or greater than UID_MAX
399 		 */
400 		resp = &result;
401 		resp->status = NFSMAPID_BADID;
402 		resp->u_res.len = 0;
403 		goto done;
404 	}
405 
406 	/*
407 	 * Make local copy of domain for further manipuation
408 	 * NOTE: mapid_get_domain() returns a ptr to TSD.
409 	 */
410 	if (cur_domain_null()) {
411 		dom_str_len = 0;
412 		dom_str[0] = '\0';
413 	} else {
414 		dom_str_len = strlen(mapid_get_domain());
415 		bcopy(mapid_get_domain(), dom_str, dom_str_len);
416 		dom_str[dom_str_len] = '\0';
417 	}
418 
419 	/*
420 	 * We want to encode the gid into a literal string... :
421 	 *
422 	 *	- upon failure to allocate space from the heap
423 	 *	- if there is no current domain configured
424 	 *	- if there is no such gid in the group DB's
425 	 */
426 	if ((grp_buf = malloc(grp_buflen)) == NULL || dom_str_len == 0 ||
427 	    _uncached_getgrgid_r(gid, &grp, grp_buf, grp_buflen) == NULL) {
428 
429 		/*
430 		 * If we could not allocate from the heap, try
431 		 * allocating from the stack as a last resort.
432 		 */
433 		if (grp_buf == NULL && (grp_buf =
434 		    alloca(MAPID_RES_LEN(UID_MAX_STR_LEN))) == NULL) {
435 			resp = &result;
436 			resp->status = NFSMAPID_INTERNAL;
437 			resp->u_res.len = 0;
438 			goto done;
439 		}
440 
441 		/*
442 		 * Constructing literal string without '@' so that
443 		 * we'll know that it's not a group, but rather a
444 		 * gid encoded string. Can't overflow because we
445 		 * already checked UID_MAX.
446 		 */
447 		gr_str = grp_buf;
448 		(void) sprintf(gr_str, "%d", (int)gid);
449 		gr_str_len = strlen(gr_str);
450 		at_str_len = dom_str_len = 0;
451 		at_str = "";
452 		dom_str[0] = '\0';
453 	} else {
454 		/*
455 		 * Otherwise, we construct the "group@domain" string
456 		 */
457 		gr_str = grp.gr_name;
458 		gr_str_len = strlen(gr_str);
459 		at_str = "@";
460 		at_str_len = 1;
461 	}
462 
463 	gid_str_len = gr_str_len + at_str_len + dom_str_len;
464 	if ((resp = alloca(MAPID_RES_LEN(UID_MAX_STR_LEN))) == NULL) {
465 		resp = &result;
466 		resp->status = NFSMAPID_INTERNAL;
467 		resp->u_res.len = 0;
468 		goto done;
469 	}
470 	/* LINTED format argument to sprintf */
471 	(void) sprintf(resp->str, "%s%s%s", gr_str, at_str, dom_str);
472 	resp->u_res.len = gid_str_len;
473 	free(grp_buf);
474 	resp->status = NFSMAPID_OK;
475 
476 done:
477 	/*
478 	 * There is a chance that the door_return will fail because the
479 	 * resulting string is too large, try to indicate that if possible
480 	 */
481 	if (door_return((char *)resp,
482 	    MAPID_RES_LEN(resp->u_res.len), NULL, 0) == -1) {
483 		resp->status = NFSMAPID_INTERNAL;
484 		resp->u_res.len = 0;
485 		(void) door_return((char *)&result, sizeof (struct mapid_res),
486 							NULL, 0);
487 	}
488 }
489 
490 /* ARGSUSED */
491 void
492 nfsmapid_func(void *cookie, char *argp, size_t arg_size,
493 						door_desc_t *dp, uint_t n_desc)
494 {
495 	struct mapid_arg	*mapargp;
496 	struct mapid_res	mapres;
497 
498 	/*
499 	 * Make sure we have a valid argument
500 	 */
501 	if (arg_size < sizeof (struct mapid_arg)) {
502 		mapres.status = NFSMAPID_INVALID;
503 		mapres.u_res.len = 0;
504 		(void) door_return((char *)&mapres, sizeof (struct mapid_res),
505 								NULL, 0);
506 		return;
507 	}
508 
509 	/* LINTED pointer cast */
510 	mapargp = (struct mapid_arg *)argp;
511 	switch (mapargp->cmd) {
512 	case NFSMAPID_STR_UID:
513 		nfsmapid_str_uid(mapargp, arg_size);
514 		return;
515 	case NFSMAPID_UID_STR:
516 		nfsmapid_uid_str(mapargp, arg_size);
517 		return;
518 	case NFSMAPID_STR_GID:
519 		nfsmapid_str_gid(mapargp, arg_size);
520 		return;
521 	case NFSMAPID_GID_STR:
522 		nfsmapid_gid_str(mapargp, arg_size);
523 		return;
524 	default:
525 		break;
526 	}
527 	mapres.status = NFSMAPID_INVALID;
528 	mapres.u_res.len = 0;
529 	(void) door_return((char *)&mapres, sizeof (struct mapid_res), NULL, 0);
530 }
531 
532 /*
533  * mapid_get_domain() always returns a ptr to TSD, so the
534  * check for a NULL domain is not a simple comparison with
535  * NULL but we need to check the contents of the TSD data.
536  */
537 int
538 cur_domain_null(void)
539 {
540 	char	*p;
541 
542 	if ((p = mapid_get_domain()) == NULL)
543 		return (1);
544 
545 	return (p[0] == '\0');
546 }
547 
548 int
549 extract_domain(char *cp, char **upp, char **dpp)
550 {
551 	/*
552 	 * Caller must insure that the string is valid
553 	 */
554 	*upp = cp;
555 
556 	if ((*dpp = strchr(cp, '@')) == NULL)
557 		return (0);
558 	*(*dpp)++ = '\0';
559 	return (1);
560 }
561 
562 int
563 valid_domain(const char *dom)
564 {
565 	const char	*whoami = "valid_domain";
566 
567 	if (!mapid_stdchk_domain(dom)) {
568 		syslog(LOG_ERR, gettext("%s: Invalid inbound domain name %s."),
569 			whoami, dom);
570 		return (0);
571 	}
572 
573 	/*
574 	 * NOTE: mapid_get_domain() returns a ptr to TSD.
575 	 */
576 	return (strcasecmp(dom, mapid_get_domain()) == 0);
577 }
578 
579 int
580 validate_id_str(const char *id)
581 {
582 	while (*id) {
583 		if (!isdigit(*id++))
584 			return (0);
585 	}
586 	return (1);
587 }
588 
589 void
590 idmap_kcall(int door_id)
591 {
592 	struct nfsidmap_args args;
593 
594 	if (door_id >= 0) {
595 		args.state = 1;
596 		args.did = door_id;
597 	} else {
598 		args.state = 0;
599 		args.did = 0;
600 	}
601 	(void) _nfssys(NFS_IDMAP, &args);
602 }
603 
604 /*
605  * Get the current NFS domain.
606  *
607  * If NFSMAPID_DOMAIN is set in /etc/default/nfs, then it is the NFS domain;
608  * otherwise, the DNS domain is used.
609  */
610 void
611 check_domain(int sighup)
612 {
613 	const char	*whoami = "check_domain";
614 	static int	 setup_done = 0;
615 	static cb_t	 cb;
616 
617 	/*
618 	 * Construct the arguments to be passed to libmapid interface
619 	 * If called in response to a SIGHUP, reset any cached DNS TXT
620 	 * RR state.
621 	 */
622 	cb.fcn = cb_update_domain;
623 	cb.signal = sighup;
624 	mapid_reeval_domain(&cb);
625 
626 	/*
627 	 * Restart the signal handler thread if we're still setting up
628 	 */
629 	if (!setup_done) {
630 		setup_done = 1;
631 		if (thr_continue(sig_thread)) {
632 			syslog(LOG_ERR, gettext("%s: Fatal error: signal "
633 			    "handler thread could not be restarted."), whoami);
634 			exit(6);
635 		}
636 	}
637 }
638 
639 /*
640  * Need to be able to open the DIAG_FILE before nfsmapid(1m)
641  * releases it's root priviledges. The DIAG_FILE then remains
642  * open for the duration of this nfsmapid instance via n4_fd.
643  */
644 void
645 open_diag_file()
646 {
647 	static int	msg_done = 0;
648 
649 	if ((n4_fp = fopen(DIAG_FILE, "w+")) != NULL) {
650 		n4_fd = fileno(n4_fp);
651 		return;
652 	}
653 
654 	if (msg_done)
655 		return;
656 
657 	syslog(LOG_ERR, "Failed to create %s. Enable syslog "
658 			"daemon.debug for more info", DIAG_FILE);
659 	msg_done = 1;
660 }
661 
662 /*
663  * When a new domain name is configured, save to DIAG_FILE
664  * and log to syslog, with LOG_DEBUG level (if configured).
665  */
666 void
667 update_diag_file(char *new)
668 {
669 	char	buf[DNAMEMAX];
670 	ssize_t	n;
671 	size_t	len;
672 
673 	(void) lseek(n4_fd, (off_t)0, SEEK_SET);
674 	(void) ftruncate(n4_fd, 0);
675 	(void) snprintf(buf, DNAMEMAX, "%s\n", new);
676 
677 	len = strlen(buf);
678 	n = write(n4_fd, buf, len);
679 	if (n < 0 || n < len)
680 		syslog(LOG_DEBUG, "Could not write %s to diag file", new);
681 	fsync(n4_fd);
682 
683 	syslog(LOG_DEBUG, "nfsmapid domain = %s", new);
684 }
685 
686 /*
687  * Callback function for libmapid. This will be called
688  * by the lib, everytime the nfsmapid(1m) domain changes.
689  */
690 void *
691 cb_update_domain(void *arg)
692 {
693 	char	*new_dname = (char *)arg;
694 
695 	DTRACE_PROBE1(nfsmapid, daemon__domain, new_dname);
696 	update_diag_file(new_dname);
697 	idmap_kcall(FLUSH_KCACHES_ONLY);
698 
699 	return (NULL);
700 }
701