getxby_door.c revision 2830:5228d1267a01
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 2006 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#pragma ident	"%Z%%M%	%I%	%E% SMI"
28
29#include "synonyms.h"
30#include <mtlib.h>
31#include <sys/types.h>
32#include <errno.h>
33#include <pwd.h>
34#include <nss_dbdefs.h>
35#include <stdio.h>
36#include <string.h>
37#include <synch.h>
38#include <sys/param.h>
39#include <fcntl.h>
40#include <unistd.h>
41#include <stdlib.h>
42#include <getxby_door.h>
43#include <sys/door.h>
44#include <procfs.h>
45#include <door.h>
46#include "libc.h"
47#include "tsd.h"
48#include "base_conversion.h"
49
50/* nss<->door hints */
51static mutex_t	hints_lock = DEFAULTMUTEX;
52static size_t	door_bsize = 0;
53static size_t	door_nbsize = 0;
54static int	proc_is_cache = -1;
55
56/* library<->nscd door interaction apis */
57
58/*
59 *
60 * Routine that actually performs the door call.
61 * Note that we cache a file descriptor.  We do
62 * the following to prevent disasters:
63 *
64 * 1) Never use 0,1 or 2; if we get this from the open
65 *    we dup it upwards.
66 *
67 * 2) Set the close on exec flags so descriptor remains available
68 *    to child processes.
69 *
70 * 3) Verify that the door is still the same one we had before
71 *    by using door_info on the client side.
72 *
73 *	Note that we never close the file descriptor if it isn't one
74 *	we allocated; we check this with door info.  The rather tricky
75 *	logic is designed to be fast in the normal case (fd is already
76 *	allocated and is ok) while handling the case where the application
77 *	closed it underneath us or where the nscd dies or re-execs itself
78 *	and we're a multi-threaded application.  Note that we cannot protect
79 *	the application if it closes the fd and it is multi-threaded.
80 *
81 *  int _nsc_trydoorcall(void *dptr, size_t *bufsize, size_t *actualsize);
82 *
83 *      *dptr           IN: points to arg buffer OUT: points to results buffer
84 *      *bufsize        IN: overall size of buffer OUT: overall size of buffer
85 *      *actualsize     IN: size of call data OUT: size of return data
86 *
87 *  Note that *dptr may change if provided space as defined by *bufsize is
88 *  inadequate.  In this case the door call mmaps more space and places
89 *  the answer there and sets dptr to contain a pointer to the space, which
90 *  should be freed with munmap.
91 *
92 *  Returns 0 if the door call reached the server, -1 if contact was not made.
93 *
94 */
95
96/*
97 * Max size for list of db names supported by the private nscd
98 * No implied max here, any size will do, fixed size chosen to
99 * reduce yet another malloc
100 */
101
102#define	BD_BUFSIZE	1024
103#define	BD_SEP		','
104
105typedef struct _nsc_door_t {
106	int 		doorfd;
107	mutex_t		door_lock;
108	door_info_t 	doori;
109} nsc_door_t;
110
111static nsc_door_t	nsc_door[2] = {
112	{ -1, DEFAULTMUTEX, { 0 } },		/* front (fattached) door */
113	{ -1, DEFAULTMUTEX, { 0 } },		/* back (private) door */
114};
115
116/* assumed to be locked by using nsc_door[1] mutex */
117static char	*nsc_db_buf = NULL;
118static char	**nsc_db_list = NULL;
119
120/*
121 * Check for a valid and matching db in the list.
122 * assume list is in the locked state.
123 */
124
125static int
126_nsc_use_backdoor(char *db)
127{
128	char 	**ndb;
129
130	if (db && nsc_db_buf != NULL && nsc_db_list != NULL) {
131		for (ndb = nsc_db_list; *ndb; ndb++) {
132			if (strcmp(db, *ndb) == 0)
133				return (1);
134		}
135	}
136	return (0);
137}
138
139/*
140 * flush private db lists
141 */
142static void
143_nsc_flush_private_db()
144{
145	if (nsc_db_buf != NULL) {
146		libc_free((void *)nsc_db_buf);
147		nsc_db_buf = NULL;
148	}
149	if (nsc_db_list != NULL) {
150		libc_free((void *)nsc_db_list);
151		nsc_db_list = NULL;
152	}
153}
154
155/*
156 * init/update nsc_db_buf given buff containing list of
157 * db's to be processed by a private nscd.
158 * This function assumes it has a well formed string from nscd.
159 */
160
161static int
162_nsc_init_private_db(char *dblist)
163{
164	char	*cp, **lp;
165	int	buflen = 0;
166	int	arrlen = 0;
167
168	if (dblist == NULL)
169		return (0);
170
171	/* reset db list */
172	_nsc_flush_private_db();
173
174	/* rebuild fresh list */
175	buflen = strlen(dblist) + 1;
176	for (cp = dblist; *cp; cp++)
177		if (*cp == BD_SEP)
178			arrlen++;
179	if (cp == dblist)
180		return (0);
181	arrlen += 2;
182	nsc_db_buf = (char *)libc_malloc(buflen);
183	if (nsc_db_buf == (char *)NULL)
184		return (0);
185	nsc_db_list = (char **)libc_malloc(arrlen * sizeof (char *));
186	if (nsc_db_list == (char **)NULL) {
187		libc_free((void *)nsc_db_buf);
188		nsc_db_buf = NULL;
189		return (0);
190	}
191	(void) memcpy(nsc_db_buf, dblist, buflen);
192	lp = nsc_db_list;
193	*lp++ = nsc_db_buf;
194	for (cp = nsc_db_buf; *cp; ) {
195		if (*cp == BD_SEP) {
196			*cp++ = '\0';
197			*lp++ = cp;
198		} else
199			cp++;
200	}
201	*lp = NULL;
202	return (1);
203}
204
205/*
206 * _nsc_initdoor_fp attempts to validate the given door and
207 * confirm that it is still available for use.  The options are:
208 *	Front door:
209 *		If it's not open, attempt to open or error
210 *		If it's open attempt to validate.
211 *		If it's not validatable, reset fd and try again.
212 *		Other wise it open and validated, return success
213 *	Per user (back) door:
214 *		This door is passed to the client through th front door
215 *		attempt to validate it.  If it can't be validated, it
216 *		must be reset. Then send a NSS_ALTRESET error, so nscd can
217 *		forward another fd if desired.
218 */
219
220static nss_status_t
221_nsc_initdoor_fp(nsc_door_t *dp)
222{
223
224	door_info_t 		my_door;
225
226	if (dp == NULL) {
227		errno = ENOTCONN;
228		return (NSS_ERROR);
229	}
230
231	/*
232	 * the first time in we try and open and validate the front door.
233	 * A front door request may return an alternate private back door
234	 * that the client should use instead.
235	 *
236	 * To validate a door the door must have been created with
237	 * the name service door cookie. The front door is file
238	 * attached, owned by root and readonly by user, group and
239	 * other.  If any of these validations fail we refuse to use
240	 * the door.  A back door is delivered from the front door
241	 * via a door_desc_t, and have the same cooke notification.
242	 */
243
244	lmutex_lock(&dp->door_lock);
245
246try_again:
247
248	if (dp->doorfd == -1 && dp == &nsc_door[0]) {	/* open front door */
249		int		tbc[3];
250		int		i;
251
252		dp->doorfd = open64(NAME_SERVICE_DOOR, O_RDONLY, 0);
253		if (dp->doorfd == -1) {
254			lmutex_unlock(&dp->door_lock);
255			return (NSS_ERROR);
256		}
257
258		/*
259		 * dup up the file descriptor if we have 0 - 2
260		 * to avoid problems with shells stdin/out/err
261		 */
262		i = 0;
263
264		while (dp->doorfd < 3) { /* we have a reserved fd */
265			tbc[i++] = dp->doorfd;
266			if ((dp->doorfd = dup(dp->doorfd)) < 0) {
267				while (i--)
268				    (void) close(tbc[i]);
269				dp->doorfd = -1;
270				lmutex_unlock(&dp->door_lock);
271				return (NSS_ERROR);
272			}
273		}
274
275		while (i--)
276		    (void) close(tbc[i]);
277
278		/*
279		 * mark this door descriptor as close on exec
280		 */
281		(void) fcntl(dp->doorfd, F_SETFD, FD_CLOEXEC);
282		if (__door_info(dp->doorfd, &dp->doori) < 0 ||
283		    (dp->doori.di_attributes & DOOR_REVOKED) ||
284		    dp->doori.di_data != (uintptr_t)NAME_SERVICE_DOOR_COOKIE) {
285			/*
286			 * we should close doorfd because we just opened it
287			 */
288			(void) close(dp->doorfd);
289			dp->doorfd = -1;
290			(void) memset((void *)&dp->doori,
291					'\0', sizeof (door_info_t));
292			lmutex_unlock(&dp->door_lock);
293			errno = ECONNREFUSED;
294			return (NSS_ERROR);
295		}
296	} else {
297		if (__door_info(dp->doorfd, &my_door) < 0 ||
298		    my_door.di_data != (uintptr_t)NAME_SERVICE_DOOR_COOKIE ||
299		    my_door.di_uniquifier != dp->doori.di_uniquifier) {
300			/*
301			 * don't close it -
302			 * someone else has clobbered fd
303			 */
304			dp->doorfd = -1;
305			(void) memset((void *)&dp->doori,
306					'\0', sizeof (door_info_t));
307			if (dp == &nsc_door[1]) {	/* reset back door */
308				/* flush invalid db list */
309				_nsc_flush_private_db();
310				lmutex_unlock(&dp->door_lock);
311				return (NSS_ALTRESET);
312			}
313			goto try_again;
314		}
315
316		if (my_door.di_attributes & DOOR_REVOKED) {
317			(void) close(dp->doorfd);	/* nscd exited .... */
318			dp->doorfd = -1;	/* try and restart connection */
319			(void) memset((void *)&dp->doori,
320					'\0', sizeof (door_info_t));
321			if (dp == &nsc_door[1]) {	/* back door reset */
322				/* flush invalid db list */
323				_nsc_flush_private_db();
324				lmutex_unlock(&dp->door_lock);
325				return (NSS_ALTRESET);
326			}
327			goto try_again;
328		}
329	}
330
331	lmutex_unlock(&dp->door_lock);
332	return (NSS_SUCCESS);
333}
334
335/*
336 * Try the door request once only, to the specified connection.
337 * return the results or error.
338 */
339
340static nss_status_t
341_nsc_try1door(nsc_door_t *dp, void **dptr, size_t *ndata,
342			size_t *adata, int *pdesc)
343{
344	door_arg_t		param;
345	int			ret;
346	nss_pheader_t		*rp;
347
348	ret = _nsc_initdoor_fp(dp);
349	if (ret != NSS_SUCCESS)
350		return (ret);
351
352	param.rbuf = (char *)*dptr;
353	param.rsize = *ndata;
354	param.data_ptr = (char *)*dptr;
355	param.data_size = *adata;
356	param.desc_ptr = NULL;
357	param.desc_num = 0;
358	ret = __door_call(dp->doorfd, &param);
359	if (ret < 0) {
360		return (NSS_ERROR);
361	}
362	*adata = param.data_size;
363	*ndata = param.rsize;
364	*dptr = (void *)param.data_ptr;
365	rp = (nss_pheader_t *)((void *)param.rbuf);
366	if (pdesc != NULL && rp && rp->p_status == NSS_ALTRETRY &&
367	    param.desc_ptr != NULL && param.desc_num > 0) {
368		if ((param.desc_ptr->d_attributes & DOOR_DESCRIPTOR) &&
369		    param.desc_ptr->d_data.d_desc.d_descriptor >= 0 &&
370		    param.desc_ptr->d_data.d_desc.d_id != 0) {
371			/* have an alt descriptor */
372			*pdesc = param.desc_ptr->d_data.d_desc.d_descriptor;
373			/* got a NSS_ALTRETRY command */
374			return (NSS_ALTRETRY);
375		}
376		errno = EINVAL;
377		return (NSS_ERROR);		/* other error? */
378	}
379	if (*adata == 0 || *dptr == NULL) {
380		errno = ENOTCONN;
381		return (NSS_ERROR);
382	}
383
384	if (rp->p_status == NSS_ALTRESET ||
385		rp->p_status == NSS_ALTRETRY ||
386		rp->p_status == NSS_TRYLOCAL)
387		return (rp->p_status);
388
389	return (NSS_SUCCESS);
390}
391
392/*
393 * Backwards compatible API
394 */
395
396nss_status_t
397_nsc_trydoorcall(void **dptr, size_t *ndata, size_t *adata)
398{
399	return (_nsc_try1door(&nsc_door[0], dptr, ndata, adata, NULL));
400}
401
402/*
403 * Send the request to the designated door, based on the supplied db
404 * Retry on the alternate door fd if possible.
405 */
406
407nss_status_t
408_nsc_trydoorcall_ext(void **dptr, size_t *ndata, size_t *adata)
409{
410	int		ret = NSS_ALTRETRY;
411	nsc_door_t	*frontd = &nsc_door[0];
412	nsc_door_t	*backd = &nsc_door[1];
413	int		fd;
414
415	nss_pheader_t	*ph, ph_save;
416	char		*dbl;
417	char		*db = NULL;
418	nss_dbd_t	*dbd;
419	int		fb2frontd = 0;
420	int		reset_frontd = 0;
421
422	ph = (nss_pheader_t *)*dptr;
423	dbd = (nss_dbd_t *)((void *)((char *)ph + ph->dbd_off));
424	if (dbd->o_name != 0)
425		db = (char *)dbd + dbd->o_name;
426	ph_save = *ph;
427
428	while (ret == NSS_ALTRETRY || ret == NSS_ALTRESET) {
429		/* try private (back) door first if it exists and applies */
430		if (db != NULL && backd->doorfd > 0 && fb2frontd == 0 &&
431				_nsc_use_backdoor(db)) {
432			ret = _nsc_try1door(backd, dptr, ndata, adata, NULL);
433			if (ret == NSS_ALTRESET) {
434				/*
435				 * received NSS_ALTRESET command,
436				 * retry on front door
437				 */
438				lmutex_lock(&backd->door_lock);
439				backd->doorfd = -1;
440				(void) memset((void *)&backd->doori,
441					'\0', sizeof (door_info_t));
442				/* flush now invalid db list */
443				_nsc_flush_private_db();
444				lmutex_unlock(&backd->door_lock);
445				continue;
446			} else if (ret == NSS_ALTRETRY) {
447				/*
448				 * received NSS_ALTRETRY command,
449				 * fall back and retry on front door
450				 */
451				fb2frontd = 1;
452				*ph = ph_save;
453				/*
454				 * tell the front door server, this is
455				 * a fallback call
456				 */
457				ph->p_status = NSS_ALTRETRY;
458				continue;
459			}
460
461			/* return the result or error */
462			break;
463		}
464
465		/* try the front door */
466		fd = -1;
467		ret = _nsc_try1door(frontd, dptr, ndata, adata, &fd);
468
469		if (ret != NSS_ALTRETRY) {
470			/*
471			 * got a success or failure result.
472			 * but front door should never send NSS_ALTRESET
473			 */
474			if (ret == NSS_ALTRESET)
475				/* reset the front door */
476				reset_frontd = 1;
477			else
478				/*
479				 * not NSS_ALTRETRY and not NSS_ALTRESET
480				 * return the result or error
481				 */
482				break;
483		} else if (fb2frontd == 1) {
484			/*
485			 * front door should never send NSS_ALTRETRY
486			 * in a fallback call. Reset the front door.
487			 */
488			reset_frontd = 1;
489		}
490
491		if (reset_frontd == 1) {
492			lmutex_lock(&frontd->door_lock);
493			frontd->doorfd = -1;
494			(void) memset((void *)&frontd->doori,
495				'\0', sizeof (door_info_t));
496			lmutex_unlock(&frontd->door_lock);
497			/* error out */
498			ret = NSS_ERROR;
499			break;
500		}
501
502		/* process NSS_ALTRETRY request from front door */
503		if (fd < 0)
504			continue;	/* no new door given, try again */
505
506		/* update and try alternate door */
507		lmutex_lock(&backd->door_lock);
508		if (backd->doorfd >= 0) {
509			/* unexpected open alt door - clean up, continue */
510			_nsc_flush_private_db();
511			(void) close(backd->doorfd);
512		}
513
514		/* set up back door fd */
515		backd->doorfd = fd;
516
517		/* set up back door db list */
518		ph =  (nss_pheader_t *)*dptr;
519		dbl = ((char *)ph) + ph->data_off;
520
521		if (_nsc_init_private_db(dbl) == 0) {
522			/* could not init db list, try again */
523			(void) close(backd->doorfd);
524			backd->doorfd = -1;
525			lmutex_unlock(&backd->door_lock);
526			continue;
527		}
528		if (door_info(backd->doorfd, &backd->doori) < 0 ||
529				(backd->doori.di_attributes & DOOR_REVOKED) ||
530				backd->doori.di_data !=
531				(uintptr_t)NAME_SERVICE_DOOR_COOKIE) {
532			/* doorfd bad, or must not really be open */
533			(void) close(backd->doorfd);
534			backd->doorfd = -1;
535			(void) memset((void *)&backd->doori,
536				'\0', sizeof (door_info_t));
537		}
538		(void) fcntl(backd->doorfd, F_SETFD, FD_CLOEXEC);
539		lmutex_unlock(&backd->door_lock);
540		/* NSS_ALTRETRY new back door */
541		*ph = ph_save;
542	}
543	return (ret);
544}
545
546/*
547 * Get the current (but growable) buffer size for a NSS2 packet.
548 * Heuristic algorithm used:
549 *	1) Make sure it's at least NSS_BUFLEN_DOOR in length (16k default)
550 *	2) if an incoming user buffer is > larger than the current size
551 *	   Make the buffer at least NSS_BUFLEN_DOOR/2+user buffer size
552 *	   This should account for any reasonable nss_pheader, keys
553 *	   extended area etc.
554 *	3) keep the prototype/debugging (private)NSS_BUFLEN option
555 *	   to change any preconfigured value if needed(?)
556 */
557
558static size_t
559_nsc_getdoorbsize(size_t min_size)
560{
561	if (!door_bsize) {
562		lmutex_lock(&hints_lock);
563		if (!door_bsize) {
564			/* future work - get nscd hint & use hint size */
565			door_bsize = ROUND_UP(door_bsize, NSS_BUFSIZ);
566			if (door_bsize < NSS_BUFLEN_DOOR) {
567				door_bsize = NSS_BUFLEN_DOOR;
568			}
569		}
570		lmutex_unlock(&hints_lock);
571	}
572	if (min_size && door_bsize < (min_size + NSS_BUFLEN_DOOR/2)) {
573		lmutex_lock(&hints_lock);
574		if (door_bsize < (min_size + NSS_BUFLEN_DOOR/2)) {
575			min_size += NSS_BUFLEN_DOOR;
576			door_bsize = ROUND_UP(min_size, NSS_BUFSIZ);
577		}
578		lmutex_unlock(&hints_lock);
579	}
580	return (door_bsize);
581}
582
583static void
584_nsc_freedbuf(void *arg)
585{
586	nss_XbyY_buf_t *tsdbuf = arg;
587
588	if (tsdbuf != NULL && tsdbuf->buffer != NULL) {
589		lfree(tsdbuf->buffer, (size_t)tsdbuf->buflen);
590		tsdbuf->result = NULL;
591		tsdbuf->buffer = NULL;
592		tsdbuf->buflen = 0;
593	}
594}
595
596/*
597 * _nsc_getdoorbuf - return the client side per thread door buffer
598 * Elsewhere, it is assumed that the header is 0'd upon return from here.
599 */
600
601int
602_nsc_getdoorbuf(void **doorptr, size_t *bufsize)
603{
604	nss_XbyY_buf_t *tsdbuf;
605	char *bp;
606	size_t dsize;
607
608	if (doorptr == NULL || bufsize == NULL)
609		return (-1);
610
611	/* Get thread specific pointer to door buffer */
612	tsdbuf = tsdalloc(_T_DOORBUF, sizeof (nss_XbyY_buf_t), _nsc_freedbuf);
613	if (tsdbuf == NULL)
614		return (-1);
615
616	/* if door buffer does not exist create it */
617	if (tsdbuf->buffer == NULL) {
618		dsize = _nsc_getdoorbsize(*bufsize);
619
620		/* setup a door buffer with a total length of dsize */
621		bp = lmalloc(dsize);
622		if (bp == NULL)
623			return (-1);
624		tsdbuf->buffer = bp;
625		tsdbuf->buflen = dsize;
626	} else {
627		/* check old buffer size and resize if needed */
628		if (*bufsize) {
629			dsize = _nsc_getdoorbsize(*bufsize);
630			if (tsdbuf->buflen < dsize) {
631				lfree(tsdbuf->buffer, (size_t)tsdbuf->buflen);
632				bp = lmalloc(dsize);
633				if (bp == NULL)
634					return (-1);
635				tsdbuf->buffer = bp;
636				tsdbuf->buflen = dsize;
637			}
638		}
639		/* freshly malloc'd door bufs are 0'd */
640		/* 0 header for now.  Zero entire buf(?) TDB */
641		(void) memset((void *)tsdbuf->buffer, 0,
642				(size_t)sizeof (nss_pheader_t));
643
644	}
645	*doorptr = (void *)tsdbuf->buffer;
646	*bufsize = tsdbuf->buflen;
647	return (0);
648}
649
650void
651_nsc_resizedoorbuf(size_t bsize)
652{
653	/* signal to update if new door size is desired */
654	lmutex_lock(&hints_lock);
655	if (bsize > door_bsize && door_nbsize < bsize)
656		door_nbsize = bsize;
657	lmutex_unlock(&hints_lock);
658}
659
660/*
661 * Check uid and /proc/PID/psinfo to see if this process is nscd
662 * If it is set the appropriate flags and allow policy reconfiguration.
663 */
664int
665_nsc_proc_is_cache()
666{
667	psinfo_t	pinfo;
668	char		fname[128];
669	int		ret;
670	int		fd;
671
672	if (proc_is_cache >= 0)
673		return (proc_is_cache);
674	lmutex_lock(&hints_lock);
675	if (proc_is_cache >= 0) {
676		lmutex_unlock(&hints_lock);
677		return (proc_is_cache);
678	}
679	proc_is_cache = 0;
680	/* It can't be nscd if it's not running as root... */
681	if (getuid() != 0) {
682		lmutex_unlock(&hints_lock);
683		return (0);
684	}
685	ret = snprintf(fname, 128, "/proc/%d/psinfo", getpid());
686	if (ret > 0 && ret < 128) {
687		if ((fd = open(fname,  O_RDONLY)) > 0) {
688			ret = read(fd, &pinfo, sizeof (psinfo_t));
689			(void) close(fd);
690			if (ret == sizeof (psinfo_t) &&
691			    (strcmp(pinfo.pr_fname, "nscd") == 0)) {
692				/* process runs as root and is named nscd */
693				/* that's good enough for now */
694				proc_is_cache = 1;
695			}
696		}
697	}
698	lmutex_unlock(&hints_lock);
699	return (proc_is_cache);
700}
701