/*
* 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 (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2012 Milan Jurik. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <synch.h>
#include <thread.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#include <door.h>
#include <libscf.h>
#include <ucred.h>
#include <sys/varargs.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/proc.h>
#include <procfs.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libscf.h>
#include "nscd_door.h"
#include "nscd_config.h"
#include "nscd_log.h"
#include "nscd_frontend.h"
#include "nscd_selfcred.h"
#include "nscd_admin.h"
#include "nscd_common.h"
#include "ns_sldap.h"
extern int _logfd;
static char *execpath;
static char **execargv;
static char *selfcred_dbs = NULL;
static void *get_smf_prop(const char *var, char type, void *def_val);
/* current self-cred configuration data being used */
static nscd_cfg_global_selfcred_t nscd_selfcred_cfg_g;
#define _NSCD_PUN_BLOCK 1024
static uint8_t pu_nscd_enabled;
static int max_pu_nscd = _NSCD_PUN_BLOCK;
static int pu_nscd_ttl;
static nscd_rc_t setup_ldap_backend();
static nscd_rc_t init_user_proc_monitor();
/*
* clild state
*/
typedef enum {
CHILD_STATE_NONE = 0,
CHILD_STATE_UIDKNOWN,
CHILD_STATE_FORKSENT,
CHILD_STATE_PIDKNOWN
} child_state_t;
typedef struct _child {
int child_slot;
int child_door;
pid_t child_pid;
uid_t child_uid;
gid_t child_gid;
child_state_t child_state;
int next_open;
mutex_t *mutex;
cond_t *cond;
} child_t;
static child_t **child = NULL;
static mutex_t child_lock = DEFAULTMUTEX;
static int open_head;
static int open_tail;
static int used_slot;
/* nscd door id */
extern int _doorfd;
static pid_t main_uid = 0;
/* nscd id: main, forker, or child */
extern int _whoami;
/* forker nscd pid */
static pid_t forker_pid = 0;
static pid_t forker_uid = 0;
long activity = 0;
mutex_t activity_lock = DEFAULTMUTEX;
static int forking_door = -1;
static mutex_t forking_lock = DEFAULTMUTEX;
static void
free_slot(int s)
{
if (child[s] == NULL)
return;
free(child[s]->mutex);
free(child[s]->cond);
free(child[s]);
child[s] = NULL;
}
void
_nscd_free_cslots()
{
int i;
(void) mutex_lock(&child_lock);
for (i = 0; i < max_pu_nscd; i++)
free_slot(i);
open_head = -1;
open_tail = -1;
used_slot = -1;
(void) mutex_unlock(&child_lock);
}
static int
init_slot(int s)
{
child_t *ch;
char *me = "init_slot";
if (child[s] == NULL) {
child[s] = (child_t *)calloc(1, sizeof (child_t));
if (child[s] == NULL)
return (-1);
ch = child[s];
if ((ch->mutex = (mutex_t *)calloc(1,
sizeof (mutex_t))) == NULL) {
free(ch);
return (-1);
}
(void) mutex_init(ch->mutex, USYNC_THREAD, NULL);
if ((ch->cond = (cond_t *)calloc(1,
sizeof (cond_t))) == NULL) {
free(ch->mutex);
free(ch);
return (-1);
}
(void) cond_init(ch->cond, USYNC_THREAD, NULL);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "slot %d allocated\n", s);
} else
ch = child[s];
ch->child_slot = s;
ch->child_door = 0;
ch->child_state = CHILD_STATE_NONE;
ch->child_pid = 0;
ch->child_uid = 0;
ch->child_gid = 0;
ch->next_open = -1;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "slot %d initialized\n", s);
return (0);
}
static int
_nscd_init_cslots()
{
(void) mutex_lock(&child_lock);
child = (child_t **)calloc(max_pu_nscd, sizeof (child_t *));
if (child == NULL)
return (-1);
open_head = -1;
open_tail = -1;
used_slot = -1;
(void) mutex_unlock(&child_lock);
return (0);
}
static child_t *
get_cslot(
uid_t uid,
int no_alloc)
{
int i;
child_t *ch, *ret = NULL;
char *me = "get_cslot";
(void) mutex_lock(&child_lock);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "looking for uid %d (slot used = %d)\n", uid, used_slot);
/* first find the slot with a matching uid */
for (i = 0; i <= used_slot; i++) {
ch = child[i];
if (ch->child_state >= CHILD_STATE_UIDKNOWN &&
ch->child_uid == uid) {
ret = ch;
(void) mutex_unlock(&child_lock);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "slot %d found with uid %d\n",
ret->child_slot, ret->child_uid);
return (ret);
}
}
/* if no need to allocate a new slot, return NULL */
if (no_alloc == 1) {
(void) mutex_unlock(&child_lock);
return (ret);
}
/* no open slot ? get a new one */
if (open_head == -1) {
/* if no slot available, allocate more */
if (used_slot >= max_pu_nscd - 1) {
child_t **tmp;
int newmax = max_pu_nscd + _NSCD_PUN_BLOCK;
tmp = (child_t **)calloc(newmax, sizeof (child_t *));
if (tmp == NULL) {
(void) mutex_unlock(&child_lock);
return (ret);
}
(void) memcpy(tmp, child, sizeof (child_t) *
max_pu_nscd);
free(child);
child = tmp;
max_pu_nscd = newmax;
}
used_slot++;
if (init_slot(used_slot) == -1) {
used_slot--;
(void) mutex_unlock(&child_lock);
return (ret);
}
ch = child[used_slot];
} else {
ch = child[open_head];
open_head = ch->next_open;
/* got last one ? reset tail */
if (open_head == -1)
open_tail = -1;
ch->next_open = -1;
}
ch->child_uid = uid;
ch->child_state = CHILD_STATE_UIDKNOWN;
ret = ch;
(void) mutex_unlock(&child_lock);
return (ret);
}
static void
return_cslot_nolock(child_t *ch)
{
int slot = ch->child_slot;
/* have open slot ? add to and reset tail */
if (open_tail != -1) {
child[open_tail]->next_open = slot;
open_tail = slot;
} else {
/* no open slot ? make one */
open_head = open_tail = slot;
}
(void) init_slot(ch->child_slot);
}
static void
return_cslot(child_t *ch)
{
char *me = "return_cslot";
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "returning slot %d\n", ch->child_slot);
/* return if the slot has been returned by another thread */
if (ch->child_state == CHILD_STATE_NONE)
return;
(void) mutex_lock(&child_lock);
/* check one more time */
if (ch->child_state == CHILD_STATE_NONE) {
(void) mutex_unlock(&child_lock);
return;
}
return_cslot_nolock(ch);
(void) mutex_unlock(&child_lock);
}
static int
selfcred_kill(
int fd)
{
int ret;
char *me = "selfcred_kill";
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "sending kill to door %d\n", fd);
if (fd != -1)
ret = _nscd_doorcall_fd(fd, NSCD_KILL, NULL, 0,
NULL, 0, NULL);
else
ret = _nscd_doorcall(NSCD_KILL);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "kill request sent to door %d (rc = %d)\n", fd, ret);
return (ret);
}
void
_nscd_kill_forker()
{
(void) mutex_lock(&forking_lock);
if (forking_door != -1)
(void) selfcred_kill(forking_door);
forking_door = -1;
(void) mutex_unlock(&forking_lock);
}
void
_nscd_kill_all_children()
{
int i;
int ret;
char *me = "_nscd_kill_all_children";
(void) mutex_lock(&child_lock);
for (i = 0; i <= used_slot; i++) {
if (child[i] == NULL)
continue;
if (child[i]->child_state >= CHILD_STATE_PIDKNOWN) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "killing child process %d (doorfd %d)\n",
child[i]->child_pid, child[i]->child_door);
ret = selfcred_kill(child[i]->child_door);
if (ret != -1)
(void) kill(child[i]->child_pid, SIGTERM);
}
if (child[i]->child_state != CHILD_STATE_NONE)
(void) return_cslot_nolock(child[i]);
}
(void) mutex_unlock(&child_lock);
}
static int
selfcred_pulse(
int fd)
{
int ret;
char *me = "selfcred_pulse";
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "start monitoring door %d\n", fd);
ret = _nscd_doorcall_fd(fd, NSCD_PULSE |(_whoami & NSCD_WHOAMI),
NULL, 0, NULL, 0, NULL);
/* Close door because the other side exited. */
(void) close(fd);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "door (%d) monitor exited (rc = %d)\n", fd, ret);
return (ret);
}
/*ARGSUSED*/
static void *
forker_monitor(
void *arg)
{
pid_t fpid;
char *fmri;
char *me = "forker_monitor";
/* wait until forker exits */
fpid = forker_pid;
(void) selfcred_pulse(forking_door);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "forker (pid = %d) exited or crashed, "
"killing all child processes\n", fpid);
(void) mutex_lock(&forking_lock);
forking_door = -1;
forker_pid = -1;
(void) mutex_unlock(&forking_lock);
/* forker exited/crashed, kill all the child processes */
_nscd_kill_all_children();
/* restart forker */
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "restarting the forker ...\n");
switch (fpid = fork1()) {
case (pid_t)-1:
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "unable to fork and start the forker ...\n");
/* enter the maintenance mode */
if ((fmri = getenv("SMF_FMRI")) != NULL) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "entering maintenance mode ...\n");
(void) smf_maintain_instance(fmri, SMF_TEMPORARY);
}
return ((void *)1);
case 0:
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "execv path = %s\n", execpath);
(void) execv(execpath, execargv);
exit(0);
default:
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "new forker's pid is %d\n", fpid);
forker_pid = fpid;
break;
}
return (NULL);
}
static void *
child_monitor(
void *arg)
{
child_t *ch = (child_t *)arg;
pid_t cpid;
char *me = "child_monitor";
/* wait until child exits */
cpid = ch->child_pid;
(void) selfcred_pulse(ch->child_door);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "child (pid = %d) exited or crashed ...\n", cpid);
/* return the slot used by the child */
return_cslot(ch);
return (NULL);
}
void
_nscd_proc_iamhere(
void *buf,
door_desc_t *dp,
uint_t n_desc,
int iam)
{
int cslot;
child_t *ch;
int errnum;
ucred_t *uc = NULL;
uid_t uid;
nscd_imhere_t *ih;
nss_pheader_t *phdr = (nss_pheader_t *)buf;
char *me = "_nscd_proc_iamhere";
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "%d receives iamhere from %d\n", _whoami, iam);
if (door_ucred(&uc) != 0) {
errnum = errno;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "door_ucred failed: %s\n", strerror(errnum));
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, errnum,
NSCD_DOOR_UCRED_ERROR);
return;
}
uid = ucred_geteuid(uc);
switch (iam) {
case NSCD_MAIN:
if (_whoami == NSCD_MAIN || uid != main_uid) {
/*
* I'm main, or uid from door is not correct,
* this must be an imposter
*/
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "MAIN IMPOSTER CAUGHT!\n");
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_MAIN_IMPOSTER);
}
break;
case NSCD_FORKER:
if (_whoami == NSCD_FORKER || uid != forker_uid) {
/*
* I'm forker, or uid from door is not correct,
* this must be an imposter
*/
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "FORKER IMPOSTER CAUGHT!\n");
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_FORKER_IMPOSTER);
break;
}
/* only main needs to know the forker */
if (_whoami != NSCD_MAIN) {
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_WRONG_NSCD);
break;
}
if (ucred_getpid(uc) != forker_pid) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "FORKER IMPOSTER CAUGHT: pid = %d should be %d\n",
ucred_getpid(uc), forker_pid);
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_FORKER_IMPOSTER);
break;
}
if (n_desc < 1) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "BAD FORKER, NO DOOR!\n");
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_NO_DOOR);
break;
}
if ((dp->d_attributes & DOOR_DESCRIPTOR) &&
dp->d_data.d_desc.d_descriptor > 0 &&
dp->d_data.d_desc.d_id != 0) {
(void) mutex_lock(&forking_lock);
if (forking_door != -1)
(void) close(forking_door);
forking_door = dp->d_data.d_desc.d_descriptor;
(void) mutex_unlock(&forking_lock);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "forking door is %d\n", forking_door);
NSCD_SET_STATUS_SUCCESS(phdr);
} else {
NSCD_SET_STATUS(phdr, NSS_ALTRETRY, 0);
break;
}
/* monitor the forker nscd */
(void) thr_create(NULL, 0, forker_monitor, NULL,
THR_DETACHED, NULL);
break;
case NSCD_CHILD:
if (_whoami != NSCD_MAIN) {
/* child nscd can only talk to the main nscd */
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "CHILD IMPOSTER CAUGHT!\n");
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_CHILD_IMPOSTER);
break;
}
/* get the main nscd assigned slot number */
ih = NSCD_N2N_DOOR_DATA(nscd_imhere_t, buf);
cslot = ih->slot;
(void) mutex_lock(&child_lock);
if (cslot < 0 || cslot >= max_pu_nscd)
ch = NULL;
else
ch = child[cslot];
(void) mutex_unlock(&child_lock);
if (ch == NULL) {
/* Bad slot number */
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "bad slot number %d\n", cslot);
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_INVALID_SLOT_NUMBER);
break;
}
if (uid != ch->child_uid) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "CHILD IMPOSTER CAUGHT: uid = %d should be %d\n",
uid, ch->child_uid);
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_CHILD_IMPOSTER);
break;
}
if (ch->child_state != CHILD_STATE_UIDKNOWN &&
ch->child_state != CHILD_STATE_FORKSENT) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "invalid slot/child state (%d) for uid %d\n",
ch->child_state, uid);
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_INVALID_SLOT_STATE);
break;
}
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "d_descriptor = %d, d_id = %lld\n",
dp->d_data.d_desc.d_descriptor, dp->d_data.d_desc.d_id);
if ((dp->d_attributes & DOOR_DESCRIPTOR) &&
dp->d_data.d_desc.d_descriptor > 0 &&
dp->d_data.d_desc.d_id != 0) {
(void) mutex_lock(ch->mutex);
if (ch->child_door != -1)
(void) close(ch->child_door);
ch->child_door = dp->d_data.d_desc.d_descriptor;
ch->child_pid = ucred_getpid(uc);
ch->child_state = CHILD_STATE_PIDKNOWN;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "child in slot %d has door %d\n",
cslot, ch->child_door);
/*
* let waiters know that the child is ready to
* serve
*/
(void) cond_broadcast(ch->cond);
(void) mutex_unlock(ch->mutex);
/* monitor the child nscd */
(void) thr_create(NULL, 0, child_monitor,
ch, THR_DETACHED, NULL);
NSCD_SET_STATUS_SUCCESS(phdr);
break;
} else {
NSCD_SET_STATUS(phdr, NSS_ALTRETRY, 0);
}
break;
}
ucred_free(uc);
uc = NULL;
}
void
_nscd_proc_pulse(
void *buf,
int iam)
{
long last_active;
int done = 0;
nss_pheader_t *phdr = (nss_pheader_t *)buf;
char *me = "_nscd_proc_pulse";
/* only main nscd sends pulse */
if (iam != NSCD_MAIN) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "MAIN IMPOSTER CAUGHT! i am %d not NSCD_MAIN\n", iam);
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_MAIN_IMPOSTER);
return;
}
/* forker doesn't return stats, it just pauses */
if (_whoami == NSCD_FORKER) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "forker ready to pause ...\n");
for (;;)
(void) pause();
}
/* remember the current activity sequence number */
(void) mutex_lock(&activity_lock);
last_active = activity;
(void) mutex_unlock(&activity_lock);
while (!done) {
/* allow per_user_nscd_ttl seconds of inactivity */
(void) sleep(pu_nscd_ttl);
(void) mutex_lock(&activity_lock);
if (last_active == activity)
done = 1;
else {
last_active = activity;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "active, sleep again for %d seconds\n",
pu_nscd_ttl);
}
(void) mutex_unlock(&activity_lock);
}
/* no activity in the specified seconds, exit and disconnect */
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "no activity in the last %d seconds, exit\n", pu_nscd_ttl);
exit(0);
}
void
_nscd_proc_fork(
void *buf,
int iam)
{
int slot;
int ret;
char *fmri;
pid_t cid;
uid_t set2uid;
gid_t set2gid;
nss_pheader_t *phdr = (nss_pheader_t *)buf;
char *me = "_nscd_proc_fork";
nscd_fork_t *f;
nscd_imhere_t ih;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "%d receives fork request from %d\n", _whoami, iam);
/* only main nscd sends fork requests */
if (iam != NSCD_MAIN) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "MAIN IMPOSTER CAUGHT! i am %d not NSCD_MAIN\n", iam);
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_MAIN_IMPOSTER);
return;
}
/* only forker handles fork requests */
if (_whoami != NSCD_FORKER) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "MAIN IMPOSTER CAUGHT! I AM NOT FORKER!\n");
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_WRONG_NSCD);
return;
}
/* fork a child for the slot assigned by the main nscd */
f = NSCD_N2N_DOOR_DATA(nscd_fork_t, buf);
slot = f->slot;
/* set the uid/gid as assigned by the main nscd */
set2uid = f->uid;
set2gid = f->gid;
/* ignore bad slot number */
if (slot < 0 || slot >= max_pu_nscd) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "bas slot number\n");
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_INVALID_SLOT_NUMBER);
return;
}
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "before fork1() ...\n");
if ((cid = fork1()) == 0) {
_whoami = NSCD_CHILD;
/*
* remember when this child nscd starts
* (replace the forker start time)
*/
_nscd_set_start_time(1);
/* close all except the log file */
if (_logfd > 0) {
int i;
for (i = 0; i < _logfd; i++)
(void) close(i);
closefrom(_logfd + 1);
} else
closefrom(0);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "child %d\n", getpid());
(void) setgid(set2gid);
(void) setuid(set2uid);
/* set up the door and server thread pool */
if ((_doorfd = _nscd_setup_child_server(_doorfd)) == -1)
exit(-1);
/* tell libsldap to do self cred only */
(void) setup_ldap_backend();
/* notify main that child is active */
ih.slot = slot;
for (ret = NSS_ALTRETRY; ret == NSS_ALTRETRY; )
ret = _nscd_doorcall_sendfd(_doorfd,
NSCD_IMHERE | (NSCD_CHILD & NSCD_WHOAMI),
&ih, sizeof (ih), NULL);
NSCD_SET_STATUS_SUCCESS(phdr);
return;
} if (cid == (pid_t)-1) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "forker unable to fork ...\n");
/* enter the maintenance mode */
if ((fmri = getenv("SMF_FMRI")) != NULL) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "entering maintenance mode ...\n");
(void) smf_maintain_instance(fmri, SMF_TEMPORARY);
}
exit(0);
} else {
/*
* start the monitor so as to exit as early as
* possible if no other processes are running
* with the same PUN uid (i.e., this PUN is
* not needed any more)
*/
(void) init_user_proc_monitor();
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "child forked: parent pid = %d, child pid = %d\n",
getpid(), cid);
NSCD_SET_STATUS_SUCCESS(phdr);
}
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "after fork\n");
}
static void
selfcred_fork(
void *buf,
int doorfd,
int cslot,
uid_t uid,
gid_t gid)
{
int ret;
nscd_fork_t f;
nss_pheader_t *phdr = (nss_pheader_t *)buf;
char *me = "selfcred_fork";
/* if no door fd, do nothing */
if (doorfd == -1) {
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_NO_DOOR);
}
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "sending fork request to door %d for slot %d "
"(uid = %d, gid = %d)\n", doorfd, cslot, uid, gid);
f.slot = cslot;
f.uid = uid;
f.gid = gid;
ret = _nscd_doorcall_fd(doorfd, NSCD_FORK|(_whoami&NSCD_WHOAMI),
&f, sizeof (f), NULL, 0, phdr);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "fork request sent to door %d for slot %d (rc = %d)\n",
doorfd, cslot, ret);
if (NSCD_STATUS_IS_NOT_OK(phdr)) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "fork request sent to door %d for slot %d failed: "
"status = %d, errno = %s, nscd status = %d\n", doorfd,
cslot, NSCD_GET_STATUS(phdr),
strerror(NSCD_GET_ERRNO(phdr)),
NSCD_GET_NSCD_STATUS(phdr));
}
}
void
_nscd_proc_alt_get(
void *buf,
int *door)
{
int errnum;
uid_t set2uid;
gid_t set2gid;
nss_pheader_t *phdr = (nss_pheader_t *)buf;
char *me = "_nscd_proc_alt_get";
ucred_t *uc = NULL;
child_t *ch;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "getting an alternate door ...\n");
/* make sure there is a door to talk to the forker */
if (forking_door == -1) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_ERROR)
(me, "no door to talk to the forker\n");
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_NO_FORKER);
return;
}
/* get door client's credential information */
if (door_ucred(&uc) != 0) {
errnum = errno;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "door_ucred failed: %s\n", strerror(errnum));
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, errnum,
NSCD_DOOR_UCRED_ERROR);
return;
}
/* get door client's effective uid and effective gid */
set2uid = ucred_geteuid(uc);
set2gid = ucred_getegid(uc);
ucred_free(uc);
uc = NULL;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "child uid = %d, gid = %d\n", set2uid, set2gid);
/* is a slot available ? if not, no one to serve */
if (child == NULL || (ch = get_cslot(set2uid, 0)) == NULL) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "no child slot available (child array = %p, slot = %d)\n",
child, ch->child_slot);
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_NO_CHILD_SLOT);
return;
}
/* create the per user nscd if necessary */
if (ch->child_state != CHILD_STATE_PIDKNOWN) {
nss_pheader_t phdr1;
NSCD_CLEAR_STATUS(&phdr1);
(void) mutex_lock(ch->mutex);
if (ch->child_state == CHILD_STATE_UIDKNOWN) {
/* ask forker to fork a new child */
selfcred_fork(&phdr1, forking_door, ch->child_slot,
set2uid, set2gid);
if (NSCD_STATUS_IS_NOT_OK(&phdr1)) {
(void) mutex_unlock(ch->mutex);
NSCD_COPY_STATUS(phdr, &phdr1);
return;
}
ch->child_state = CHILD_STATE_FORKSENT;
}
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "waiting for door (slot = %d, uid = %d, gid = %d)\n",
ch->child_slot, set2uid, set2gid);
/* wait for the per user nscd to become available */
while (ch->child_state == CHILD_STATE_FORKSENT) {
timestruc_t to;
int err;
int ttl = 5;
to.tv_sec = ttl;
to.tv_nsec = 0;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "cond_reltimedwait %d seconds\n", ttl);
err = cond_reltimedwait(ch->cond, ch->mutex, &to);
if (err == ETIME) {
ch->child_state = CHILD_STATE_UIDKNOWN;
_NSCD_LOG(NSCD_LOG_SELF_CRED,
NSCD_LOG_LEVEL_DEBUG)
(me, "door wait timedout (slot = %d)\n",
ch->child_slot);
break;
}
}
(void) mutex_unlock(ch->mutex);
}
if (ch->child_state != CHILD_STATE_PIDKNOWN) {
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_INVALID_SLOT_STATE);
return;
}
*door = ch->child_door;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "returning door %d for slot %d, uid %d, gid = %d\n",
*door, ch->child_slot, set2uid, set2gid);
NSCD_SET_STATUS(phdr, NSS_ALTRETRY, 0);
}
static char **
cpargv(
int argc,
char **inargv)
{
char **newargv;
int c = 4;
int i = 0, j, k = 0, n = 0;
newargv = (char **)calloc(c + 1, sizeof (char *));
if (newargv == NULL)
return (NULL);
newargv[n] = strdup(inargv[0]);
if (newargv[n++] == NULL) {
free(newargv);
return (NULL);
}
newargv[n] = strdup("-F");
if (newargv[n++] == NULL) {
free(newargv[0]);
free(newargv);
return (NULL);
}
for (i = 1; i < argc; i++) {
if (strcmp(inargv[i], "-f") == 0)
k = 2;
if (k == 0)
continue;
newargv[n] = strdup(inargv[i]);
if (newargv[n] == NULL) {
for (j = 0; j < n; j++)
free(newargv[j]);
free(newargv);
return (NULL);
}
k--;
n++;
}
return (newargv);
}
void
_nscd_start_forker(
char *path,
int argc,
char **argv)
{
pid_t cid;
/* if self cred is not configured, do nothing */
if (!_nscd_is_self_cred_on(1, NULL))
return;
/* save pathname and generate the new argv for the forker */
execpath = strdup(path);
execargv = cpargv(argc, argv);
if (execpath == NULL || execargv == NULL)
exit(1);
switch (cid = fork1()) {
case (pid_t)-1:
exit(1);
break;
case 0:
/* start the forker nscd */
(void) execv(path, execargv);
exit(0);
break;
default:
/* main nscd */
/* remember process id of the forker */
forker_pid = cid;
/* enable child nscd management */
(void) _nscd_init_cslots();
break;
}
}
static nscd_rc_t
get_ldap_funcs(
char *name,
void **func_p)
{
char *me = "get_ldap_funcs";
static void *handle = NULL;
void *sym;
if (name == NULL && handle != NULL) {
(void) dlclose(handle);
return (NSCD_SUCCESS);
}
/* no handle to close, it's OK */
if (name == NULL)
return (NSCD_SUCCESS);
if (handle == NULL) {
handle = dlopen("libsldap.so.1", RTLD_LAZY);
if (handle == NULL) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_ERROR)
(me, "unable to dlopen libsldap.so.1");
return (NSCD_CFG_DLOPEN_ERROR);
}
}
if ((sym = dlsym(handle, name)) == NULL) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_ERROR)
(me, "unable to find symbol %s", name);
return (NSCD_CFG_DLSYM_ERROR);
} else
(void) memcpy(func_p, &sym, sizeof (void *));
return (NSCD_SUCCESS);
}
int
_nscd_is_self_cred_on(int recheck, char **dblist)
{
static int checked = 0;
static int is_on = 0;
static int (*ldap_func)();
char *srcs = "ldap"; /* only ldap support self cred */
int ldap_on = 0;
char *ldap_sc_func = "__ns_ldap_self_gssapi_config";
ns_ldap_self_gssapi_config_t ldap_config;
if (checked && !recheck) {
if (is_on && dblist != NULL)
*dblist = selfcred_dbs;
return (is_on);
}
if (selfcred_dbs != NULL)
free(selfcred_dbs);
selfcred_dbs = _nscd_srcs_in_db_nsw_policy(1, &srcs);
if (selfcred_dbs == NULL) {
is_on = 0;
checked = 1;
return (0);
}
/*
* also check the ldap backend to see if
* the configuration there is good for
* doing self credentialing
*/
if (ldap_func == NULL)
(void) get_ldap_funcs(ldap_sc_func, (void **)&ldap_func);
if (ldap_func != NULL) {
if (ldap_func(&ldap_config) == NS_LDAP_SUCCESS &&
ldap_config != NS_LDAP_SELF_GSSAPI_CONFIG_NONE)
ldap_on = 1;
}
is_on = (pu_nscd_enabled == nscd_true) && ldap_on;
checked = 1;
if (is_on && dblist != NULL)
*dblist = selfcred_dbs;
return (is_on);
}
static nscd_rc_t
setup_ldap_backend()
{
nscd_rc_t rc;
static void (*ldap_func)();
char *ldap_sc_func = "__ns_ldap_self_gssapi_only_set";
if (ldap_func == NULL)
rc = get_ldap_funcs(ldap_sc_func, (void **)&ldap_func);
if (ldap_func != NULL) {
ldap_func(1);
return (NSCD_SUCCESS);
}
return (rc);
}
/*ARGSUSED*/
void
_nscd_peruser_getadmin(
void *buf,
int buf_size)
{
void *result_mn = NSCD_N2N_DOOR_DATA(void, buf);
int errnum = 0;
int ret;
uid_t uid;
nss_pheader_t *phdr = (nss_pheader_t *)buf;
char *me = "_nscd_peruser_getadmin";
ucred_t *uc = NULL;
child_t *ch;
/* get door client's credential information */
if (door_ucred(&uc) != 0) {
errnum = errno;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "door_ucred failed: %s\n", strerror(errnum));
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, errnum,
NSCD_DOOR_UCRED_ERROR);
return;
}
/* get door client's effective uid */
uid = ucred_geteuid(uc);
ucred_free(uc);
uc = NULL;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "per user get admin ... (uid = %d)\n", uid);
/* is the per-user nscd running ? if not, no one to serve */
ch = get_cslot(uid, 1);
if (ch == NULL) {
NSCD_SET_N2N_STATUS(phdr, NSS_NSCD_PRIV, 0,
NSCD_SELF_CRED_NO_CHILD_SLOT);
return;
}
ret = _nscd_doorcall_fd(ch->child_door, NSCD_GETADMIN,
NULL, sizeof (nscd_admin_t), result_mn,
sizeof (nscd_admin_t), phdr);
if (ret == NSS_SUCCESS) {
phdr->data_len = sizeof (nscd_admin_t);
return;
}
}
static void
set_selfcred_cfg(
char param,
void *data)
{
int64_t prop_int;
uint8_t prop_boolean;
char *me = "set_selfcred_cfg";
if (param == 'e') {
prop_boolean = *(uint8_t *)data;
pu_nscd_enabled = *(uint8_t *)get_smf_prop(
"enable_per_user_lookup", 'b', &prop_boolean);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "self cred config: enabled = %d\n", pu_nscd_enabled);
}
if (param == 't') {
prop_int = *(int *)data;
pu_nscd_ttl = *(int64_t *)get_smf_prop(
"per_user_nscd_time_to_live", 'i', &prop_int);
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "self cred config: PUN TTL = %d\n", pu_nscd_ttl);
}
}
/* ARGSUSED */
nscd_rc_t
_nscd_cfg_selfcred_notify(
void *data,
struct nscd_cfg_param_desc *pdesc,
nscd_cfg_id_t *nswdb,
nscd_cfg_flag_t dflag,
nscd_cfg_error_t **errorp,
void *cookie)
{
nscd_cfg_global_selfcred_t *sc_cfg = &nscd_selfcred_cfg_g;
int off;
/*
* At init time, the whole group of config params are received.
* At update time, group or individual parameter value could
* be received.
*/
if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_GROUP)) {
*sc_cfg = *(nscd_cfg_global_selfcred_t *)data;
off = offsetof(nscd_cfg_global_selfcred_t,
enable_selfcred);
set_selfcred_cfg('e', (char *)data + off);
off = offsetof(nscd_cfg_global_selfcred_t,
per_user_nscd_ttl);
set_selfcred_cfg('t', (char *)data + off);
return (NSCD_SUCCESS);
}
/*
* individual config parameter
*/
off = offsetof(nscd_cfg_global_selfcred_t, enable_selfcred);
if (pdesc->p_offset == off) {
sc_cfg->enable_selfcred = *(nscd_bool_t *)data;
set_selfcred_cfg('e', data);
return (NSCD_SUCCESS);
}
off = offsetof(nscd_cfg_global_selfcred_t, per_user_nscd_ttl);
if (pdesc->p_offset == off) {
sc_cfg->per_user_nscd_ttl = *(int *)data;
set_selfcred_cfg('t', data);
return (NSCD_SUCCESS);
}
return (NSCD_SUCCESS);
}
/* ARGSUSED */
nscd_rc_t
_nscd_cfg_selfcred_verify(
void *data,
struct nscd_cfg_param_desc *pdesc,
nscd_cfg_id_t *nswdb,
nscd_cfg_flag_t dflag,
nscd_cfg_error_t **errorp,
void **cookie)
{
return (NSCD_SUCCESS);
}
/* ARGSUSED */
nscd_rc_t
_nscd_cfg_selfcred_get_stat(
void **stat,
struct nscd_cfg_stat_desc *sdesc,
nscd_cfg_id_t *nswdb,
nscd_cfg_flag_t *dflag,
void (**free_stat)(void *stat),
nscd_cfg_error_t **errorp)
{
return (NSCD_SUCCESS);
}
static int
check_uid(char *pid_name)
{
char pname[PATH_MAX];
static pid_t pid = 0;
static uid_t uid = 0;
static uid_t euid = 0;
int pfd; /* file descriptor for /proc/<pid>/psinfo */
psinfo_t info; /* process information from /proc */
if (uid == 0) {
pid = getpid();
uid = getuid();
euid = geteuid();
}
(void) snprintf(pname, sizeof (pname), "/proc/%s/psinfo", pid_name);
retry:
if ((pfd = open(pname, O_RDONLY)) == -1) {
/* Process may have exited */
return (1);
}
/*
* Get the info structure for the process and close quickly.
*/
if (read(pfd, (char *)&info, sizeof (info)) < 0) {
int saverr = errno;
(void) close(pfd);
if (saverr == EAGAIN)
goto retry;
if (saverr != ENOENT)
return (1);
}
(void) close(pfd);
if (info.pr_pid != pid &&
info.pr_uid == uid && info.pr_euid == euid)
return (0);
else
return (1);
}
/*
* FUNCTION: check_user_process
*/
/*ARGSUSED*/
static void *
check_user_process(void *arg)
{
DIR *dp;
struct dirent *ep;
int found;
char *me = "check_user_process";
for (;;) {
(void) sleep(60);
found = 0;
/*
* search the /proc directory and look at each process
*/
if ((dp = opendir("/proc")) == NULL) {
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_ERROR)
(me, "unable to open the /proc directory\n");
continue;
}
/* for each active process */
while (ep = readdir(dp)) {
if (ep->d_name[0] == '.') /* skip . and .. */
continue;
if (check_uid(ep->d_name) == 0) {
found = 1;
break;
}
}
/*
* if no process running as the PUN uid found, exit
* to kill this PUN
*/
if (found == 0) {
(void) closedir(dp);
exit(1);
}
(void) closedir(dp);
}
/*LINTED E_FUNC_HAS_NO_RETURN_STMT*/
}
static nscd_rc_t
init_user_proc_monitor() {
int errnum;
char *me = "init_user_proc_monitor";
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_DEBUG)
(me, "initializing the user process monitor\n");
/*
* start a thread to make sure there is at least a process
* running as the PUN user. If not, terminate this PUN.
*/
if (thr_create(NULL, NULL, check_user_process,
NULL, THR_DETACHED, NULL) != 0) {
errnum = errno;
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_ERROR)
(me, "thr_create: %s\n", strerror(errnum));
return (NSCD_THREAD_CREATE_ERROR);
}
return (NSCD_SUCCESS);
}
static void *
get_smf_prop(const char *var, char type, void *def_val)
{
scf_simple_prop_t *prop;
void *val;
char *me = "get_smf_prop";
prop = scf_simple_prop_get(NULL, NULL, "config", var);
if (prop) {
switch (type) {
case 'b':
val = scf_simple_prop_next_boolean(prop);
if (val != NULL)
(void) memcpy(def_val, val, sizeof (uint8_t));
break;
case 'i':
val = scf_simple_prop_next_integer(prop);
if (val != NULL)
(void) memcpy(def_val, val, sizeof (int64_t));
break;
}
scf_simple_prop_free(prop);
}
if (prop == NULL || val == NULL) {
char vs[64];
switch (type) {
case 'b':
if (*(uint8_t *)def_val)
(void) strcpy(vs, "yes");
else
(void) strcpy(vs, "no");
break;
case 'i':
(void) sprintf(vs, "%lld", *(int64_t *)def_val);
break;
}
_NSCD_LOG(NSCD_LOG_SELF_CRED, NSCD_LOG_LEVEL_ALERT)
(me, "no value for config/%s (%s). "
"Using default \"%s\"\n", var,
scf_strerror(scf_error()), vs);
}
return (def_val);
}