/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "lint.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libc.h" #include "tsd.h" #include "base_conversion.h" /* nss<->door hints */ static mutex_t hints_lock = DEFAULTMUTEX; static size_t door_bsize = 0; static size_t door_nbsize = 0; static int proc_is_cache = -1; /* library<->nscd door interaction apis */ /* * * Routine that actually performs the door call. * Note that we cache a file descriptor. We do * the following to prevent disasters: * * 1) Never use 0,1 or 2; if we get this from the open * we dup it upwards. * * 2) Set the close on exec flags so descriptor remains available * to child processes. * * 3) Verify that the door is still the same one we had before * by using door_info on the client side. * * Note that we never close the file descriptor if it isn't one * we allocated; we check this with door info. The rather tricky * logic is designed to be fast in the normal case (fd is already * allocated and is ok) while handling the case where the application * closed it underneath us or where the nscd dies or re-execs itself * and we're a multi-threaded application. Note that we cannot protect * the application if it closes the fd and it is multi-threaded. * * int _nsc_trydoorcall(void *dptr, size_t *bufsize, size_t *actualsize); * * *dptr IN: points to arg buffer OUT: points to results buffer * *bufsize IN: overall size of buffer OUT: overall size of buffer * *actualsize IN: size of call data OUT: size of return data * * Note that *dptr may change if provided space as defined by *bufsize is * inadequate. In this case the door call mmaps more space and places * the answer there and sets dptr to contain a pointer to the space, which * should be freed with munmap. * * Returns 0 if the door call reached the server, -1 if contact was not made. * */ /* * Max size for list of db names supported by the private nscd * No implied max here, any size will do, fixed size chosen to * reduce yet another malloc */ #define BD_BUFSIZE 1024 #define BD_SEP ',' typedef struct _nsc_door_t { int doorfd; mutex_t door_lock; door_info_t doori; } nsc_door_t; static nsc_door_t nsc_door[2] = { { -1, DEFAULTMUTEX, { 0 } }, /* front (fattached) door */ { -1, DEFAULTMUTEX, { 0 } }, /* back (private) door */ }; /* assumed to be locked by using nsc_door[1] mutex */ static char *nsc_db_buf = NULL; static char **nsc_db_list = NULL; /* * Check for a valid and matching db in the list. * assume list is in the locked state. */ static int _nsc_use_backdoor(char *db) { char **ndb; if (db && nsc_db_buf != NULL && nsc_db_list != NULL) { for (ndb = nsc_db_list; *ndb; ndb++) { if (strcmp(db, *ndb) == 0) return (1); } } return (0); } /* * flush private db lists */ static void _nsc_flush_private_db() { if (nsc_db_buf != NULL) { libc_free((void *)nsc_db_buf); nsc_db_buf = NULL; } if (nsc_db_list != NULL) { libc_free((void *)nsc_db_list); nsc_db_list = NULL; } } /* * init/update nsc_db_buf given buff containing list of * db's to be processed by a private nscd. * This function assumes it has a well formed string from nscd. */ static int _nsc_init_private_db(char *dblist) { char *cp, **lp; int buflen = 0; int arrlen = 0; if (dblist == NULL) return (0); /* reset db list */ _nsc_flush_private_db(); /* rebuild fresh list */ buflen = strlen(dblist) + 1; for (cp = dblist; *cp; cp++) if (*cp == BD_SEP) arrlen++; if (cp == dblist) return (0); arrlen += 2; nsc_db_buf = (char *)libc_malloc(buflen); if (nsc_db_buf == (char *)NULL) return (0); nsc_db_list = (char **)libc_malloc(arrlen * sizeof (char *)); if (nsc_db_list == (char **)NULL) { libc_free((void *)nsc_db_buf); nsc_db_buf = NULL; return (0); } (void) memcpy(nsc_db_buf, dblist, buflen); lp = nsc_db_list; *lp++ = nsc_db_buf; for (cp = nsc_db_buf; *cp; ) { if (*cp == BD_SEP) { *cp++ = '\0'; *lp++ = cp; } else cp++; } *lp = NULL; return (1); } /* * _nsc_initdoor_fp attempts to validate the given door and * confirm that it is still available for use. The options are: * Front door: * If it's not open, attempt to open or error * If it's open attempt to validate. * If it's not validatable, reset fd and try again. * Other wise it open and validated, return success * Per user (back) door: * This door is passed to the client through th front door * attempt to validate it. If it can't be validated, it * must be reset. Then send a NSS_ALTRESET error, so nscd can * forward another fd if desired. */ static nss_status_t _nsc_initdoor_fp(nsc_door_t *dp) { door_info_t my_door; if (dp == NULL) { errno = ENOTCONN; return (NSS_ERROR); } /* * the first time in we try and open and validate the front door. * A front door request may return an alternate private back door * that the client should use instead. * * To validate a door the door must have been created with * the name service door cookie. The front door is file * attached, owned by root and readonly by user, group and * other. If any of these validations fail we refuse to use * the door. A back door is delivered from the front door * via a door_desc_t, and have the same cooke notification. */ lmutex_lock(&dp->door_lock); try_again: if (dp->doorfd == -1 && dp == &nsc_door[0]) { /* open front door */ int tbc[3]; int i; dp->doorfd = open64(NAME_SERVICE_DOOR, O_RDONLY, 0); if (dp->doorfd == -1) { lmutex_unlock(&dp->door_lock); return (NSS_ERROR); } /* * dup up the file descriptor if we have 0 - 2 * to avoid problems with shells stdin/out/err */ i = 0; while (dp->doorfd < 3) { /* we have a reserved fd */ tbc[i++] = dp->doorfd; if ((dp->doorfd = dup(dp->doorfd)) < 0) { while (i--) (void) close(tbc[i]); dp->doorfd = -1; lmutex_unlock(&dp->door_lock); return (NSS_ERROR); } } while (i--) (void) close(tbc[i]); /* * mark this door descriptor as close on exec */ (void) fcntl(dp->doorfd, F_SETFD, FD_CLOEXEC); if (__door_info(dp->doorfd, &dp->doori) < 0 || (dp->doori.di_attributes & DOOR_REVOKED) || dp->doori.di_data != (uintptr_t)NAME_SERVICE_DOOR_COOKIE) { /* * we should close doorfd because we just opened it */ (void) close(dp->doorfd); dp->doorfd = -1; (void) memset((void *)&dp->doori, '\0', sizeof (door_info_t)); lmutex_unlock(&dp->door_lock); errno = ECONNREFUSED; return (NSS_ERROR); } } else { if (__door_info(dp->doorfd, &my_door) < 0 || my_door.di_data != (uintptr_t)NAME_SERVICE_DOOR_COOKIE || my_door.di_uniquifier != dp->doori.di_uniquifier) { /* * don't close it - * someone else has clobbered fd */ dp->doorfd = -1; (void) memset((void *)&dp->doori, '\0', sizeof (door_info_t)); if (dp == &nsc_door[1]) { /* reset back door */ /* flush invalid db list */ _nsc_flush_private_db(); lmutex_unlock(&dp->door_lock); return (NSS_ALTRESET); } goto try_again; } if (my_door.di_attributes & DOOR_REVOKED) { (void) close(dp->doorfd); /* nscd exited .... */ dp->doorfd = -1; /* try and restart connection */ (void) memset((void *)&dp->doori, '\0', sizeof (door_info_t)); if (dp == &nsc_door[1]) { /* back door reset */ /* flush invalid db list */ _nsc_flush_private_db(); lmutex_unlock(&dp->door_lock); return (NSS_ALTRESET); } goto try_again; } } lmutex_unlock(&dp->door_lock); return (NSS_SUCCESS); } /* * Try the door request once only, to the specified connection. * return the results or error. */ static nss_status_t _nsc_try1door(nsc_door_t *dp, void **dptr, size_t *ndata, size_t *adata, int *pdesc) { door_arg_t param; int ret; nss_pheader_t *rp; ret = _nsc_initdoor_fp(dp); if (ret != NSS_SUCCESS) return (ret); param.rbuf = (char *)*dptr; param.rsize = *ndata; param.data_ptr = (char *)*dptr; param.data_size = *adata; param.desc_ptr = NULL; param.desc_num = 0; ret = __door_call(dp->doorfd, ¶m); if (ret < 0) { return (NSS_ERROR); } *adata = param.data_size; *ndata = param.rsize; *dptr = (void *)param.data_ptr; rp = (nss_pheader_t *)((void *)param.rbuf); if (pdesc != NULL && rp && rp->p_status == NSS_ALTRETRY && param.desc_ptr != NULL && param.desc_num > 0) { if ((param.desc_ptr->d_attributes & DOOR_DESCRIPTOR) && param.desc_ptr->d_data.d_desc.d_descriptor >= 0 && param.desc_ptr->d_data.d_desc.d_id != 0) { /* have an alt descriptor */ *pdesc = param.desc_ptr->d_data.d_desc.d_descriptor; /* got a NSS_ALTRETRY command */ return (NSS_ALTRETRY); } errno = EINVAL; return (NSS_ERROR); /* other error? */ } if (*adata == 0 || *dptr == NULL) { errno = ENOTCONN; return (NSS_ERROR); } if (rp->p_status == NSS_ALTRESET || rp->p_status == NSS_ALTRETRY || rp->p_status == NSS_TRYLOCAL) return (rp->p_status); return (NSS_SUCCESS); } /* * Backwards compatible API */ nss_status_t _nsc_trydoorcall(void **dptr, size_t *ndata, size_t *adata) { return (_nsc_try1door(&nsc_door[0], dptr, ndata, adata, NULL)); } /* * Send the request to the designated door, based on the supplied db * Retry on the alternate door fd if possible. */ nss_status_t _nsc_trydoorcall_ext(void **dptr, size_t *ndata, size_t *adata) { int ret = NSS_ALTRETRY; nsc_door_t *frontd = &nsc_door[0]; nsc_door_t *backd = &nsc_door[1]; int fd; nss_pheader_t *ph, ph_save; char *dbl; char *db = NULL; nss_dbd_t *dbd; int fb2frontd = 0; int reset_frontd = 0; size_t ndata_save = *ndata, adata_save = *adata; void *dptr_save = *dptr; ph = (nss_pheader_t *)*dptr; dbd = (nss_dbd_t *)((void *)((char *)ph + ph->dbd_off)); if (dbd->o_name != 0) db = (char *)dbd + dbd->o_name; /* * save away a copy of the header, in case the request needs * to be sent to nscd more than once. In that case, this * original header can be copied back to the door buffer * to replace the possibly changed header */ ph_save = *ph; while (ret == NSS_ALTRETRY || ret == NSS_ALTRESET) { /* try private (back) door first if it exists and applies */ if (db != NULL && backd->doorfd > 0 && fb2frontd == 0 && _nsc_use_backdoor(db)) { ret = _nsc_try1door(backd, dptr, ndata, adata, NULL); if (ret == NSS_ALTRESET) { /* * received NSS_ALTRESET command, * retry on front door */ lmutex_lock(&backd->door_lock); backd->doorfd = -1; (void) memset((void *)&backd->doori, '\0', sizeof (door_info_t)); /* flush now invalid db list */ _nsc_flush_private_db(); lmutex_unlock(&backd->door_lock); continue; } else if (ret == NSS_ALTRETRY) { /* * received NSS_ALTRETRY command, * fall back and retry on front door */ fb2frontd = 1; if (*dptr != dptr_save) (void) munmap((void *)*dptr, *ndata); /* * restore the buffer size and header * data so that the front door will * see the original request */ *ndata = ndata_save; *adata = adata_save; *dptr = dptr_save; ph = (nss_pheader_t *)*dptr; *ph = ph_save; /* * tell the front door server, this is * a fallback call */ ph->p_status = NSS_ALTRETRY; continue; } /* return the result or error */ break; } /* try the front door */ fd = -1; ret = _nsc_try1door(frontd, dptr, ndata, adata, &fd); if (ret != NSS_ALTRETRY) { /* * got a success or failure result. * but front door should never send NSS_ALTRESET */ if (ret == NSS_ALTRESET) /* reset the front door */ reset_frontd = 1; else /* * not NSS_ALTRETRY and not NSS_ALTRESET * return the result or error */ break; } else if (fb2frontd == 1) { /* * front door should never send NSS_ALTRETRY * in a fallback call. Reset the front door. */ reset_frontd = 1; } if (reset_frontd == 1) { lmutex_lock(&frontd->door_lock); frontd->doorfd = -1; (void) memset((void *)&frontd->doori, '\0', sizeof (door_info_t)); lmutex_unlock(&frontd->door_lock); /* error out */ ret = NSS_ERROR; break; } /* process NSS_ALTRETRY request from front door */ if (fd < 0) continue; /* no new door given, try again */ /* update and try alternate door */ lmutex_lock(&backd->door_lock); if (backd->doorfd >= 0) { /* unexpected open alt door - clean up, continue */ _nsc_flush_private_db(); (void) close(backd->doorfd); } /* set up back door fd */ backd->doorfd = fd; /* set up back door db list */ ph = (nss_pheader_t *)*dptr; dbl = ((char *)ph) + ph->data_off; if (_nsc_init_private_db(dbl) == 0) { /* could not init db list, try again */ (void) close(backd->doorfd); backd->doorfd = -1; lmutex_unlock(&backd->door_lock); continue; } if (door_info(backd->doorfd, &backd->doori) < 0 || (backd->doori.di_attributes & DOOR_REVOKED) || backd->doori.di_data != (uintptr_t)NAME_SERVICE_DOOR_COOKIE) { /* doorfd bad, or must not really be open */ (void) close(backd->doorfd); backd->doorfd = -1; (void) memset((void *)&backd->doori, '\0', sizeof (door_info_t)); } (void) fcntl(backd->doorfd, F_SETFD, FD_CLOEXEC); lmutex_unlock(&backd->door_lock); /* NSS_ALTRETRY new back door */ if (*dptr != dptr_save) (void) munmap((void *)*dptr, *ndata); /* * restore the buffer size and header * data so that the back door will * see the original request */ *ndata = ndata_save; *adata = adata_save; *dptr = dptr_save; ph = (nss_pheader_t *)*dptr; *ph = ph_save; } return (ret); } /* * Get the current (but growable) buffer size for a NSS2 packet. * Heuristic algorithm used: * 1) Make sure it's at least NSS_BUFLEN_DOOR in length (16k default) * 2) if an incoming user buffer is > larger than the current size * Make the buffer at least NSS_BUFLEN_DOOR/2+user buffer size * This should account for any reasonable nss_pheader, keys * extended area etc. * 3) keep the prototype/debugging (private)NSS_BUFLEN option * to change any preconfigured value if needed(?) */ static size_t _nsc_getdoorbsize(size_t min_size) { if (!door_bsize) { lmutex_lock(&hints_lock); if (!door_bsize) { /* future work - get nscd hint & use hint size */ door_bsize = ROUND_UP(door_bsize, NSS_BUFSIZ); if (door_bsize < NSS_BUFLEN_DOOR) { door_bsize = NSS_BUFLEN_DOOR; } } lmutex_unlock(&hints_lock); } if (min_size && door_bsize < (min_size + NSS_BUFLEN_DOOR/2)) { lmutex_lock(&hints_lock); if (door_bsize < (min_size + NSS_BUFLEN_DOOR/2)) { min_size += NSS_BUFLEN_DOOR; door_bsize = ROUND_UP(min_size, NSS_BUFSIZ); } lmutex_unlock(&hints_lock); } return (door_bsize); } static void _nsc_freedbuf(void *arg) { nss_XbyY_buf_t *tsdbuf = arg; if (tsdbuf != NULL && tsdbuf->buffer != NULL) { lfree(tsdbuf->buffer, (size_t)tsdbuf->buflen); tsdbuf->result = NULL; tsdbuf->buffer = NULL; tsdbuf->buflen = 0; } } /* * _nsc_getdoorbuf - return the client side per thread door buffer * Elsewhere, it is assumed that the header is 0'd upon return from here. */ int _nsc_getdoorbuf(void **doorptr, size_t *bufsize) { nss_XbyY_buf_t *tsdbuf; char *bp; size_t dsize; if (doorptr == NULL || bufsize == NULL) return (-1); /* Get thread specific pointer to door buffer */ tsdbuf = tsdalloc(_T_DOORBUF, sizeof (nss_XbyY_buf_t), _nsc_freedbuf); if (tsdbuf == NULL) return (-1); /* if door buffer does not exist create it */ if (tsdbuf->buffer == NULL) { dsize = _nsc_getdoorbsize(*bufsize); /* setup a door buffer with a total length of dsize */ bp = lmalloc(dsize); if (bp == NULL) return (-1); tsdbuf->buffer = bp; tsdbuf->buflen = dsize; } else { /* check old buffer size and resize if needed */ if (*bufsize) { dsize = _nsc_getdoorbsize(*bufsize); if (tsdbuf->buflen < dsize) { lfree(tsdbuf->buffer, (size_t)tsdbuf->buflen); bp = lmalloc(dsize); if (bp == NULL) return (-1); tsdbuf->buffer = bp; tsdbuf->buflen = dsize; } } /* freshly malloc'd door bufs are 0'd */ /* 0 header for now. Zero entire buf(?) TDB */ (void) memset((void *)tsdbuf->buffer, 0, (size_t)sizeof (nss_pheader_t)); } *doorptr = (void *)tsdbuf->buffer; *bufsize = tsdbuf->buflen; return (0); } void _nsc_resizedoorbuf(size_t bsize) { /* signal to update if new door size is desired */ lmutex_lock(&hints_lock); if (bsize > door_bsize && door_nbsize < bsize) door_nbsize = bsize; lmutex_unlock(&hints_lock); } /* * Check uid and /proc/PID/psinfo to see if this process is nscd * If it is set the appropriate flags and allow policy reconfiguration. */ int _nsc_proc_is_cache() { psinfo_t pinfo; char fname[128]; int ret; int fd; if (proc_is_cache >= 0) return (proc_is_cache); lmutex_lock(&hints_lock); if (proc_is_cache >= 0) { lmutex_unlock(&hints_lock); return (proc_is_cache); } proc_is_cache = 0; /* It can't be nscd if it's not running as root... */ if (getuid() != 0) { lmutex_unlock(&hints_lock); return (0); } ret = snprintf(fname, 128, "/proc/%d/psinfo", getpid()); if (ret > 0 && ret < 128) { if ((fd = open(fname, O_RDONLY)) >= 0) { ret = read(fd, &pinfo, sizeof (psinfo_t)); (void) close(fd); if (ret == sizeof (psinfo_t) && (strcmp(pinfo.pr_fname, "nscd") == 0)) { /* process runs as root and is named nscd */ /* that's good enough for now */ proc_is_cache = 1; } } } lmutex_unlock(&hints_lock); return (proc_is_cache); }