#include "lint.h"
#include <mtlib.h>
#include <sys/types.h>
#include <errno.h>
#include <pwd.h>
#include <nss_dbdefs.h>
#include <stdio.h>
#include <string.h>
#include <synch.h>
#include <sys/param.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <getxby_door.h>
#include <sys/door.h>
#include <procfs.h>
#include <door.h>
#include <sys/mman.h>
#include "libc.h"
#include "tsd.h"
#include "base_conversion.h"
static mutex_t hints_lock = DEFAULTMUTEX;
static size_t door_bsize = 0;
static size_t door_nbsize = 0;
static int proc_is_cache = -1;
#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 } },
{ -1, DEFAULTMUTEX, { 0 } },
};
static char *nsc_db_buf = NULL;
static char **nsc_db_list = NULL;
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);
}
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;
}
}
static int
_nsc_init_private_db(char *dblist)
{
char *cp, **lp;
int buflen = 0;
int arrlen = 0;
if (dblist == NULL)
return (0);
_nsc_flush_private_db();
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);
}
static nss_status_t
_nsc_initdoor_fp(nsc_door_t *dp)
{
door_info_t my_door;
if (dp == NULL) {
errno = ENOTCONN;
return (NSS_ERROR);
}
lmutex_lock(&dp->door_lock);
try_again:
if (dp->doorfd == -1 && dp == &nsc_door[0]) {
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);
}
i = 0;
while (dp->doorfd < 3) {
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]);
(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) {
(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) {
dp->doorfd = -1;
(void) memset((void *)&dp->doori,
'\0', sizeof (door_info_t));
if (dp == &nsc_door[1]) {
_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);
dp->doorfd = -1;
(void) memset((void *)&dp->doori,
'\0', sizeof (door_info_t));
if (dp == &nsc_door[1]) {
_nsc_flush_private_db();
lmutex_unlock(&dp->door_lock);
return (NSS_ALTRESET);
}
goto try_again;
}
}
lmutex_unlock(&dp->door_lock);
return (NSS_SUCCESS);
}
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) {
*pdesc = param.desc_ptr->d_data.d_desc.d_descriptor;
return (NSS_ALTRETRY);
}
errno = EINVAL;
return (NSS_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);
}
nss_status_t
_nsc_trydoorcall(void **dptr, size_t *ndata, size_t *adata)
{
return (_nsc_try1door(&nsc_door[0], dptr, ndata, adata, NULL));
}
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;
ph_save = *ph;
while (ret == NSS_ALTRETRY || ret == NSS_ALTRESET) {
if (db != NULL && backd->doorfd > 0 && fb2frontd == 0 &&
_nsc_use_backdoor(db)) {
ret = _nsc_try1door(backd, dptr, ndata, adata, NULL);
if (ret == NSS_ALTRESET) {
lmutex_lock(&backd->door_lock);
backd->doorfd = -1;
(void) memset((void *)&backd->doori,
'\0', sizeof (door_info_t));
_nsc_flush_private_db();
lmutex_unlock(&backd->door_lock);
continue;
} else if (ret == NSS_ALTRETRY) {
fb2frontd = 1;
if (*dptr != dptr_save)
(void) munmap((void *)*dptr, *ndata);
*ndata = ndata_save;
*adata = adata_save;
*dptr = dptr_save;
ph = (nss_pheader_t *)*dptr;
*ph = ph_save;
ph->p_status = NSS_ALTRETRY;
continue;
}
break;
}
fd = -1;
ret = _nsc_try1door(frontd, dptr, ndata, adata, &fd);
if (ret != NSS_ALTRETRY) {
if (ret == NSS_ALTRESET)
reset_frontd = 1;
else
break;
} else if (fb2frontd == 1) {
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);
ret = NSS_ERROR;
break;
}
if (fd < 0)
continue;
lmutex_lock(&backd->door_lock);
if (backd->doorfd >= 0) {
_nsc_flush_private_db();
(void) close(backd->doorfd);
}
backd->doorfd = fd;
ph = (nss_pheader_t *)*dptr;
dbl = ((char *)ph) + ph->data_off;
if (_nsc_init_private_db(dbl) == 0) {
(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) {
(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);
if (*dptr != dptr_save)
(void) munmap((void *)*dptr, *ndata);
*ndata = ndata_save;
*adata = adata_save;
*dptr = dptr_save;
ph = (nss_pheader_t *)*dptr;
*ph = ph_save;
}
return (ret);
}
static size_t
_nsc_getdoorbsize(size_t min_size)
{
if (!door_bsize) {
lmutex_lock(&hints_lock);
if (!door_bsize) {
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;
}
}
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);
tsdbuf = tsdalloc(_T_DOORBUF, sizeof (nss_XbyY_buf_t), _nsc_freedbuf);
if (tsdbuf == NULL)
return (-1);
if (tsdbuf->buffer == NULL) {
dsize = _nsc_getdoorbsize(*bufsize);
bp = lmalloc(dsize);
if (bp == NULL)
return (-1);
tsdbuf->buffer = bp;
tsdbuf->buflen = dsize;
} else {
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;
}
}
(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)
{
lmutex_lock(&hints_lock);
if (bsize > door_bsize && door_nbsize < bsize)
door_nbsize = bsize;
lmutex_unlock(&hints_lock);
}
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;
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)) {
proc_is_cache = 1;
}
}
}
lmutex_unlock(&hints_lock);
return (proc_is_cache);
}