blob: d4cba76b853f6bef008def242ad107a71b55927b [file] [log] [blame]
/*
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Simple doors name server cache daemon
*/
#include <stdio.h>
#include <signal.h>
#include <sys/door.h>
#include <sys/types.h>
#include <time.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/zone.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <thread.h>
#include <stdarg.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#include <memory.h>
#include <sys/socket.h>
#include <net/route.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <door.h>
#include "getxby_door.h"
#include "server_door.h"
#include "nscd.h"
/* Includes for filenames of databases */
#include <shadow.h>
#include <userdefs.h>
#include <netdb.h>
#include <nss_dbdefs.h>
#include <exec_attr.h>
#include <prof_attr.h>
#include <user_attr.h>
#include <ucred.h>
#include <priv.h>
#include <libscf.h>
#include <tsol/label.h>
#include <zone.h>
#define TSOL_NAME_SERVICE_DOOR "/var/tsol/doors/name_service_door"
extern int optind;
extern int opterr;
extern int optopt;
extern char *optarg;
static void switcher(void *, char *, size_t, door_desc_t *, uint_t);
static void rts_mon(void);
static void usage(char *);
static int nsc_calllen(nsc_call_t *);
static int client_getadmin(admin_t *);
static void getadmin(nsc_return_t *, int, nsc_call_t *);
static int setadmin(nsc_return_t *, int, nsc_call_t *);
static void client_killserver(void);
static int client_setadmin(admin_t *);
static void client_showstats(admin_t *);
static void detachfromtty(void);
admin_t current_admin;
static int will_become_server;
void
nsc_reaper(char *tbl_name, hash_t *tbl, nsc_stat_t *admin_ptr,
mutex_t *hash_lock)
{
uint_t count;
uint_t interval;
while (1) {
if (current_admin.debug_level >= DBG_ALL) {
logit("reaper_%s: %d entries in cache\n",
tbl_name, admin_ptr->nsc_entries);
}
if (admin_ptr->nsc_entries > 0) {
count = reap_hash(tbl, admin_ptr, hash_lock,
admin_ptr->nsc_pos_ttl);
if (current_admin.debug_level >= DBG_ALL) {
logit("reaper_%s: reaped %d entries\n",
tbl_name, count);
}
} else {
/*
* We set a minimum wait of 60 before checking again;
* we don't want to sleep for no time at all.
* We don't clamp it for the reaping itself, that is
* done in reap_hash, and with a different minimum.
*/
interval = admin_ptr->nsc_pos_ttl;
if (interval < 60) interval = 60;
if (current_admin.debug_level >= DBG_ALL) {
logit(
"reaper_%s: Nothing to reap, sleep %d\n",
tbl_name, interval);
}
sleep(interval);
}
}
}
nsc_stat_t *
getcacheptr(char *s)
{
static const char *caches[7] = {"passwd", "group", "hosts", "ipnodes",
"exec_attr", "prof_attr", "user_attr" };
if (strncmp(caches[0], s, strlen(caches[0])) == 0)
return (&current_admin.passwd);
if (strncmp(caches[1], s, strlen(caches[1])) == 0)
return (&current_admin.group);
if (strncmp(caches[2], s, strlen(caches[2])) == 0)
return (&current_admin.host);
if (strncmp(caches[3], s, strlen(caches[3])) == 0)
return (&current_admin.node);
if (strncmp(caches[4], s, strlen(caches[4])) == 0)
return (&current_admin.exec);
if (strncmp(caches[5], s, strlen(caches[5])) == 0)
return (&current_admin.prof);
if (strncmp(caches[6], s, strlen(caches[6])) == 0)
return (&current_admin.user);
return (NULL);
}
static char *
getcacheopt(char *s)
{
while (*s && *s != ',')
s++;
return ((*s == ',') ? (s + 1) : NULL);
}
/*
* routine to check if server is already running
*/
static int
nsc_ping(void)
{
nsc_data_t data;
nsc_data_t *dptr;
int ndata;
int adata;
data.nsc_call.nsc_callnumber = NULLCALL;
ndata = sizeof (data);
adata = sizeof (data);
dptr = &data;
return (_nsc_trydoorcall(&dptr, &ndata, &adata));
}
static void
dozip(void)
{
/* not much here */
}
static void
keep_open_dns_socket(void)
{
_res.options |= RES_STAYOPEN; /* just keep this udp socket open */
}
/*
* declaring this causes the files backend to use hashing
* this is of course an utter hack, but provides a nice
* quiet back door to enable this feature for only the nscd.
*/
void
__nss_use_files_hash(void)
{
}
/*
*
* The allocation of resources for cache lookups is an interesting
* problem, and one that has caused several bugs in the beta release
* of 2.5. In particular, the introduction of a thottle to prevent
* the creation of excessive numbers of LWPs in the case of a failed
* name service has led to a denial of service problem when the
* name service request rate exceeds the name service's ability
* to respond. As a result, I'm implementing the following
* algorithm:
*
* 1) We cap the number of total threads.
* 2) We save CACHE_THREADS of those for cache lookups only.
* 3) We use a common pool of 2/3 of the remain threads that are used first
* 4) We save the remainder and allocate 1/3 of it for table specific lookups
*
* The intent is to prevent the failure of a single name service from
* causing denial of service, and to always have threads available for
* cached lookups. If a request comes in and the answer isn't in the
* cache and we cannot get a thread, we simply return NOSERVER, forcing
* the client to lookup the
* data itself. This will prevent the types of starvation seen
* at UNC due to a single threaded DNS backend, and allows the cache
* to eventually become filled.
*
*/
/* 7 tables: passwd, group, hosts, ipnodes, exec_attr, prof_attr, user_attr */
#define NSCD_TABLES 7
#define TABLE_THREADS 10
#define COMMON_THREADS 20
#define CACHE_MISS_THREADS (COMMON_THREADS + NSCD_TABLES * TABLE_THREADS)
#define CACHE_HIT_THREADS 20
#define MAX_SERVER_THREADS (CACHE_HIT_THREADS + CACHE_MISS_THREADS)
static sema_t common_sema;
static sema_t passwd_sema;
static sema_t hosts_sema;
static sema_t nodes_sema;
static sema_t group_sema;
static sema_t exec_sema;
static sema_t prof_sema;
static sema_t user_sema;
static thread_key_t lookup_state_key;
static void
initialize_lookup_clearance(void)
{
thr_keycreate(&lookup_state_key, NULL);
(void) sema_init(&common_sema, COMMON_THREADS, USYNC_THREAD, 0);
(void) sema_init(&passwd_sema, TABLE_THREADS, USYNC_THREAD, 0);
(void) sema_init(&hosts_sema, TABLE_THREADS, USYNC_THREAD, 0);
(void) sema_init(&nodes_sema, TABLE_THREADS, USYNC_THREAD, 0);
(void) sema_init(&group_sema, TABLE_THREADS, USYNC_THREAD, 0);
(void) sema_init(&exec_sema, TABLE_THREADS, USYNC_THREAD, 0);
(void) sema_init(&prof_sema, TABLE_THREADS, USYNC_THREAD, 0);
(void) sema_init(&user_sema, TABLE_THREADS, USYNC_THREAD, 0);
}
int
get_clearance(int callnumber)
{
sema_t *table_sema = NULL;
char *tab;
if (sema_trywait(&common_sema) == 0) {
thr_setspecific(lookup_state_key, NULL);
return (0);
}
switch (MASKUPDATEBIT(callnumber)) {
case GETPWUID:
case GETPWNAM:
tab = "passwd";
table_sema = &passwd_sema;
break;
case GETGRNAM:
case GETGRGID:
tab = "group";
table_sema = &group_sema;
break;
case GETHOSTBYNAME:
case GETHOSTBYADDR:
tab = "hosts";
table_sema = &hosts_sema;
break;
case GETIPNODEBYNAME:
case GETIPNODEBYADDR:
tab = "ipnodes";
table_sema = &nodes_sema;
break;
case GETEXECID:
tab = "exec_attr";
table_sema = &exec_sema;
break;
case GETPROFNAM:
tab = "prof_attr";
table_sema = &prof_sema;
break;
case GETUSERNAM:
tab = "user_attr";
table_sema = &user_sema;
break;
}
if (sema_trywait(table_sema) == 0) {
thr_setspecific(lookup_state_key, (void*)1);
return (0);
}
if (current_admin.debug_level >= DBG_CANT_FIND) {
logit("get_clearance: throttling load for %s table\n", tab);
}
return (-1);
}
int
release_clearance(int callnumber)
{
int which;
sema_t *table_sema = NULL;
thr_getspecific(lookup_state_key, (void**)&which);
if (which == 0) /* from common pool */ {
(void) sema_post(&common_sema);
return (0);
}
switch (MASKUPDATEBIT(callnumber)) {
case GETPWUID:
case GETPWNAM:
table_sema = &passwd_sema;
break;
case GETGRNAM:
case GETGRGID:
table_sema = &group_sema;
break;
case GETHOSTBYNAME:
case GETHOSTBYADDR:
table_sema = &hosts_sema;
break;
case GETIPNODEBYNAME:
case GETIPNODEBYADDR:
table_sema = &nodes_sema;
break;
case GETEXECID:
table_sema = &exec_sema;
break;
case GETPROFNAM:
table_sema = &prof_sema;
break;
case GETUSERNAM:
table_sema = &user_sema;
break;
}
(void) sema_post(table_sema);
return (0);
}
static mutex_t create_lock;
static int nscd_max_servers = MAX_SERVER_THREADS;
static int num_servers = 0;
static thread_key_t server_key;
/*
* Bind a TSD value to a server thread. This enables the destructor to
* be called if/when this thread exits. This would be a programming error,
* but better safe than sorry.
*/
/*ARGSUSED*/
static void *
server_tsd_bind(void *arg)
{
static void *value = 0;
/* disable cancellation to avoid hangs if server threads disappear */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
thr_setspecific(server_key, value);
door_return(NULL, 0, NULL, 0);
/* make lint happy */
return (NULL);
}
/*
* Server threads are created here.
*/
/*ARGSUSED*/
static void
server_create(door_info_t *dip)
{
(void) mutex_lock(&create_lock);
if (++num_servers > nscd_max_servers) {
num_servers--;
(void) mutex_unlock(&create_lock);
return;
}
(void) mutex_unlock(&create_lock);
thr_create(NULL, 0, server_tsd_bind, NULL, THR_BOUND|THR_DETACHED,
NULL);
}
/*
* Server thread are destroyed here
*/
/*ARGSUSED*/
static void
server_destroy(void *arg)
{
(void) mutex_lock(&create_lock);
num_servers--;
(void) mutex_unlock(&create_lock);
}
static char **saved_argv;
static char saved_execname[MAXPATHLEN];
static void
save_execname()
{
const char *name = getexecname();
saved_execname[0] = 0;
if (name[0] != '/') { /* started w/ relative path */
(void) getcwd(saved_execname, MAXPATHLEN);
strlcat(saved_execname, "/", MAXPATHLEN);
}
strlcat(saved_execname, name, MAXPATHLEN);
}
int
main(int argc, char ** argv)
{
int did;
int opt;
int errflg = 0;
int showstats = 0;
int doset = 0;
int loaded_config_file = 0;
struct stat buf;
sigset_t myset;
struct sigaction action;
/*
* The admin model for TX is that labeled zones are managed
* in global zone where most trusted configuration database
* resides.
*/
if (is_system_labeled() && (getzoneid() != GLOBAL_ZONEID)) {
(void) fprintf(stderr,
"With Trusted Extensions nscd runs only in " \
"the global zone.\n");
exit(1);
}
/*
* Special case non-root user here - he can just print stats
*/
if (geteuid()) {
if (argc != 2 || strcmp(argv[1], "-g")) {
(void) fprintf(stderr,
"Must be root to use any option other than "\
"-g.\n\n");
usage(argv[0]);
}
if ((nsc_ping() != SUCCESS) ||
(client_getadmin(&current_admin) != 0)) {
(void) fprintf(stderr,
"%s doesn't appear to be running.\n", argv[0]);
exit(1);
}
client_showstats(&current_admin);
exit(0);
}
/*
* Determine if there is already a daemon running
*/
will_become_server = (nsc_ping() != SUCCESS);
/*
* process usual options
*/
/*
* load normal config file
*/
if (will_become_server) {
static const nsc_stat_t defaults = {
0, /* stats */
0, /* stats */
0, /* stats */
0, /* stats */
0, /* stats */
0, /* stats */
0, /* stats */
211, /* suggested size */
1, /* enabled */
0, /* invalidate cmd */
600, /* positive ttl */
10, /* netative ttl */
20, /* keep hot */
0, /* old data not ok */
1 }; /* check files */
current_admin.passwd = defaults;
current_admin.group = defaults;
current_admin.host = defaults;
current_admin.node = defaults;
current_admin.exec = defaults;
current_admin.prof = defaults;
current_admin.user = defaults;
current_admin.logfile[0] = '\0';
if (access("/etc/nscd.conf", R_OK) == 0) {
if (nscd_parse(argv[0], "/etc/nscd.conf") < 0) {
exit(1);
}
loaded_config_file++;
}
}
else {
if (client_getadmin(&current_admin)) {
(void) fprintf(stderr,
"Cannot contact nscd properly(?)\n");
exit(1);
}
current_admin.logfile[0] = '\0';
}
while ((opt = getopt(argc, argv,
"S:Kf:c:ge:p:n:i:l:d:s:h:o:")) != EOF) {
nsc_stat_t *cache;
char *cacheopt;
switch (opt) {
case 'S': /* undocumented feature */
doset++;
cache = getcacheptr(optarg);
cacheopt = getcacheopt(optarg);
if (!cache || !cacheopt) {
errflg++;
break;
}
if (strcmp(cacheopt, "yes") == 0)
cache->nsc_secure_mode = 1;
else if (strcmp(cacheopt, "no") == 0)
cache->nsc_secure_mode = 0;
else
errflg++;
break;
case 'K': /* undocumented feature */
client_killserver();
exit(0);
break;
case 'f':
doset++;
loaded_config_file++;
if (nscd_parse(argv[0], optarg) < 0) {
exit(1);
}
break;
case 'g':
showstats++;
break;
case 'p':
doset++;
cache = getcacheptr(optarg);
cacheopt = getcacheopt(optarg);
if (!cache || !cacheopt) {
errflg++;
break;
}
cache->nsc_pos_ttl = atoi(cacheopt);
break;
case 'n':
doset++;
cache = getcacheptr(optarg);
cacheopt = getcacheopt(optarg);
if (!cache || !cacheopt) {
errflg++;
break;
}
cache->nsc_neg_ttl = atoi(cacheopt);
break;
case 'c':
doset++;
cache = getcacheptr(optarg);
cacheopt = getcacheopt(optarg);
if (!cache || !cacheopt) {
errflg++;
break;
}
if (strcmp(cacheopt, "yes") == 0)
cache->nsc_check_files = 1;
else if (strcmp(cacheopt, "no") == 0)
cache->nsc_check_files = 0;
else
errflg++;
break;
case 'i':
doset++;
cache = getcacheptr(optarg);
if (!cache) {
errflg++;
break;
}
cache->nsc_invalidate = 1;
break;
case 'l':
doset++;
(void) strlcpy(current_admin.logfile, optarg, 128);
break;
case 'd':
doset++;
current_admin.debug_level = atoi(optarg);
break;
case 's':
doset++;
cache = getcacheptr(optarg);
cacheopt = getcacheopt(optarg);
if (!cache || !cacheopt) {
errflg++;
break;
}
cache->nsc_suggestedsize = atoi(cacheopt);
break;
case 'h':
doset++;
cache = getcacheptr(optarg);
cacheopt = getcacheopt(optarg);
if (!cache || !cacheopt) {
errflg++;
break;
}
cache->nsc_keephot = atoi(cacheopt);
break;
case 'o':
doset++;
cache = getcacheptr(optarg);
cacheopt = getcacheopt(optarg);
if (!cache || !cacheopt) {
errflg++;
break;
}
if (strcmp(cacheopt, "yes") == 0)
cache->nsc_old_data_ok = 1;
else if (strcmp(cacheopt, "no") == 0)
cache->nsc_old_data_ok = 0;
else
errflg++;
break;
case 'e':
doset++;
cache = getcacheptr(optarg);
cacheopt = getcacheopt(optarg);
if (!cache || !cacheopt) {
errflg++;
break;
}
if (strcmp(cacheopt, "yes") == 0)
cache->nsc_enabled = 1;
else if (strcmp(cacheopt, "no") == 0)
cache->nsc_enabled = 0;
else
errflg++;
break;
default:
errflg++;
break;
}
}
if (errflg)
usage(argv[0]);
if (!will_become_server) {
if (showstats) {
client_showstats(&current_admin);
}
if (doset) {
if (client_setadmin(&current_admin) < 0) {
(void) fprintf(stderr,
"Error during admin call\n");
exit(1);
}
}
if (!showstats && !doset) {
(void) fprintf(stderr,
"%s already running.... no admin specified\n",
argv[0]);
}
exit(0);
}
/*
* daemon from here ou
*/
if (!loaded_config_file) {
(void) fprintf(stderr,
"No configuration file specifed and /etc/nscd.conf" \
"not present\n");
exit(1);
}
saved_argv = argv;
save_execname();
if (current_admin.debug_level) {
/* we're debugging... */
if (strlen(current_admin.logfile) == 0)
/* no specified log file */
(void) strcpy(current_admin.logfile, "stderr");
else
(void) nscd_set_lf(&current_admin,
current_admin.logfile);
} else {
if (strlen(current_admin.logfile) == 0)
(void) strcpy(current_admin.logfile, "/dev/null");
(void) nscd_set_lf(&current_admin, current_admin.logfile);
detachfromtty();
}
/* perform some initialization */
initialize_lookup_clearance();
keep_open_dns_socket();
getpw_init();
getgr_init();
gethost_init();
getnode_init();
getexec_init();
getprof_init();
getuser_init();
/* Establish our own server thread pool */
door_server_create(server_create);
if (thr_keycreate(&server_key, server_destroy) != 0) {
perror("thr_keycreate");
exit(-1);
}
/* Create a door */
if ((did = door_create(switcher, NAME_SERVICE_DOOR_COOKIE,
DOOR_UNREF | DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) < 0) {
perror("door_create");
exit(-1);
}
/* bind to file system */
if (is_system_labeled()) {
if (stat(TSOL_NAME_SERVICE_DOOR, &buf) < 0) {
int newfd;
if ((newfd = creat(TSOL_NAME_SERVICE_DOOR, 0444)) < 0) {
logit("Cannot create %s:%s\n",
TSOL_NAME_SERVICE_DOOR, strerror(errno));
exit(1);
}
(void) close(newfd);
}
if (symlink(TSOL_NAME_SERVICE_DOOR, NAME_SERVICE_DOOR) != 0) {
if (errno != EEXIST) {
logit("Cannot symlink %s:%s\n",
NAME_SERVICE_DOOR, strerror(errno));
exit(1);
}
}
} else if (stat(NAME_SERVICE_DOOR, &buf) < 0) {
int newfd;
if ((newfd = creat(NAME_SERVICE_DOOR, 0444)) < 0) {
logit("Cannot create %s:%s\n", NAME_SERVICE_DOOR,
strerror(errno));
exit(1);
}
(void) close(newfd);
}
if (fattach(did, NAME_SERVICE_DOOR) < 0) {
if ((errno != EBUSY) ||
(fdetach(NAME_SERVICE_DOOR) < 0) ||
(fattach(did, NAME_SERVICE_DOOR) < 0)) {
perror("door_attach");
exit(2);
}
}
action.sa_handler = dozip;
action.sa_flags = 0;
(void) sigemptyset(&action.sa_mask);
(void) sigemptyset(&myset);
(void) sigaddset(&myset, SIGHUP);
if (sigaction(SIGHUP, &action, NULL) < 0) {
perror("sigaction");
exit(1);
}
if (thr_sigsetmask(SIG_BLOCK, &myset, NULL) < 0) {
perror("thr_sigsetmask");
exit(1);
}
/*
* kick off revalidate threads
*/
if (thr_create(NULL, NULL,
(void *(*)(void *))getpw_revalidate, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void *))gethost_revalidate, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void*))getnode_revalidate, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void*))getgr_revalidate, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void*))getexec_revalidate, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void*))getprof_revalidate, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void*))getuser_revalidate, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
/*
* kick off reaper threads
*/
if (thr_create(NULL, NULL,
(void *(*)(void *))getpw_uid_reaper, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void *))getpw_nam_reaper, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void *))getgr_uid_reaper, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void *))getgr_nam_reaper, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void *))gethost_nam_reaper, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void *))gethost_addr_reaper, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void *))getnode_nam_reaper, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void *))getnode_addr_reaper, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void *))getexec_reaper, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void *))getprof_reaper, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_create(NULL, NULL,
(void *(*)(void *))getuser_reaper, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
/*
* kick off routing socket monitor thread
*/
if (thr_create(NULL, NULL,
(void *(*)(void *))rts_mon, 0, 0, NULL) != 0) {
perror("thr_create");
exit(1);
}
if (thr_sigsetmask(SIG_UNBLOCK, &myset, NULL) < 0) {
perror("thr_sigsetmask");
return (1);
}
for (;;) {
(void) pause();
logit("Reloading /etc/nscd.conf\n");
nscd_parse(argv[0], "/etc/nscd.conf");
}
}
/*ARGSUSED*/
static void
switcher(void *cookie, char *argp, size_t arg_size,
door_desc_t *dp, uint_t n_desc)
{
union {
nsc_data_t data;
char space[8192];
} u;
time_t now;
static time_t last_nsswitch_check;
static time_t last_nsswitch_modified;
static time_t last_resolv_modified;
static mutex_t nsswitch_lock;
nsc_call_t *ptr = (nsc_call_t *)argp;
if (argp == DOOR_UNREF_DATA) {
(void) printf("Door Slam... exiting\n");
exit(0);
}
if (ptr == NULL) { /* empty door call */
(void) door_return(NULL, 0, 0, 0); /* return the favor */
}
now = time(NULL);
/*
* just in case check
*/
(void) mutex_lock(&nsswitch_lock);
if (now - last_nsswitch_check > 10) {
struct stat nss_buf;
struct stat res_buf;
last_nsswitch_check = now;
(void) mutex_unlock(&nsswitch_lock); /* let others continue */
/*
* This code keeps us from statting resolv.conf
* if it doesn't exist, yet prevents us from ignoring
* it if it happens to disappear later on for a bit.
*/
if (last_resolv_modified >= 0) {
if (stat("/etc/resolv.conf", &res_buf) < 0) {
if (last_resolv_modified == 0)
last_resolv_modified = -1;
else
res_buf.st_mtime = last_resolv_modified;
} else if (last_resolv_modified == 0) {
last_resolv_modified = res_buf.st_mtime;
}
}
if (stat("/etc/nsswitch.conf", &nss_buf) < 0) {
/*EMPTY*/;
} else if (last_nsswitch_modified == 0) {
last_nsswitch_modified = nss_buf.st_mtime;
} else if ((last_nsswitch_modified < nss_buf.st_mtime) ||
((last_resolv_modified > 0) &&
(last_resolv_modified < res_buf.st_mtime))) {
static mutex_t exit_lock;
char *fmri;
/*
* time for restart
*/
logit("nscd restart due to /etc/nsswitch.conf or "\
"resolv.conf change\n");
/*
* try to restart under smf
*/
if ((fmri = getenv("SMF_FMRI")) == NULL) {
/* not running under smf - reexec */
execv(saved_execname, saved_argv);
exit(1); /* just in case */
}
mutex_lock(&exit_lock); /* prevent multiple restarts */
if (smf_restart_instance(fmri) == 0)
sleep(10); /* wait a bit */
exit(1); /* give up waiting for resurrection */
}
} else
(void) mutex_unlock(&nsswitch_lock);
switch (ptr->nsc_callnumber) {
case NULLCALL:
u.data.nsc_ret.nsc_return_code = SUCCESS;
u.data.nsc_ret.nsc_bufferbytesused = sizeof (nsc_return_t);
break;
case GETPWNAM:
*(argp + arg_size - 1) = 0; /* FALLTHROUGH */
case GETPWUID:
getpw_lookup(&u.data.nsc_ret, sizeof (u), ptr, now);
break;
case GETGRNAM:
*(argp + arg_size - 1) = 0; /* FALLTHROUGH */
case GETGRGID:
getgr_lookup(&u.data.nsc_ret, sizeof (u), ptr, now);
break;
case GETHOSTBYNAME:
*(argp + arg_size - 1) = 0; /* FALLTHROUGH */
case GETHOSTBYADDR:
gethost_lookup(&u.data.nsc_ret, sizeof (u), ptr, now);
break;
case GETIPNODEBYNAME:
*(argp + arg_size - 1) = 0; /* FALLTHROUGH */
case GETIPNODEBYADDR:
getnode_lookup(&u.data.nsc_ret, sizeof (u), ptr, now);
break;
case GETEXECID:
*(argp + arg_size - 1) = 0;
getexec_lookup(&u.data.nsc_ret, sizeof (u), ptr, now);
break;
case GETPROFNAM:
*(argp + arg_size - 1) = 0;
getprof_lookup(&u.data.nsc_ret, sizeof (u), ptr, now);
break;
case GETUSERNAM:
*(argp + arg_size - 1) = 0;
getuser_lookup(&u.data.nsc_ret, sizeof (u), ptr, now);
break;
case GETADMIN:
getadmin(&u.data.nsc_ret, sizeof (u), ptr);
break;
case SETADMIN:
case KILLSERVER: {
ucred_t *uc = NULL;
const priv_set_t *eset;
zoneid_t zoneid;
if (door_ucred(&uc) != 0) {
perror("door_ucred");
u.data.nsc_ret.nsc_return_code = NOTFOUND;
break;
}
eset = ucred_getprivset(uc, PRIV_EFFECTIVE);
zoneid = ucred_getzoneid(uc);
if ((zoneid != GLOBAL_ZONEID && zoneid != getzoneid()) ||
eset != NULL ? !priv_ismember(eset, PRIV_SYS_ADMIN) :
ucred_geteuid(uc) != 0) {
logit("SETADMIN call failed(cred): caller pid %d, "
"uid %d, euid %d, zoneid %d\n", ucred_getpid(uc),
ucred_getruid(uc), ucred_geteuid(uc), zoneid);
u.data.nsc_ret.nsc_return_code = NOTFOUND;
ucred_free(uc);
break;
}
if (ptr->nsc_callnumber == KILLSERVER) {
logit("Nscd received KILLSERVER cmd from pid %d, "
"uid %d, euid %d, zoneid %d\n", ucred_getpid(uc),
ucred_getruid(uc), ucred_geteuid(uc), zoneid);
exit(0);
} else {
if (setadmin(&u.data.nsc_ret, sizeof (u), ptr) != 0)
logit("SETADMIN call failed\n");
}
ucred_free(uc);
break;
}
default:
logit("Unknown name service door call op %d\n",
ptr->nsc_callnumber);
u.data.nsc_ret.nsc_return_code = -1;
u.data.nsc_ret.nsc_bufferbytesused = sizeof (nsc_return_t);
break;
}
door_return((char *)&u.data, u.data.nsc_ret.nsc_bufferbytesused,
NULL, 0);
}
/*
* Monitor the routing socket. Address lists stored in the ipnodes
* cache are sorted based on destination address selection rules,
* so when things change that could affect that sorting (interfaces
* go up or down, flags change, etc.), we clear that cache so the
* list will be re-ordered the next time the hostname is resolved.
*/
static void
rts_mon(void)
{
int rt_sock, rdlen;
union {
struct {
struct rt_msghdr rtm;
struct sockaddr_storage addrs[RTA_NUMBITS];
} r;
struct if_msghdr ifm;
struct ifa_msghdr ifam;
} mbuf;
struct ifa_msghdr *ifam = &mbuf.ifam;
rt_sock = socket(PF_ROUTE, SOCK_RAW, 0);
if (rt_sock < 0) {
logit("Failed to open routing socket: %s\n", strerror(errno));
thr_exit(0);
}
for (;;) {
rdlen = read(rt_sock, &mbuf, sizeof (mbuf));
if (rdlen <= 0) {
if (rdlen == 0 || (errno != EINTR && errno != EAGAIN)) {
logit("routing socket read: %s\n",
strerror(errno));
thr_exit(0);
}
continue;
}
if (ifam->ifam_version != RTM_VERSION) {
logit("rx unknown version (%d) on routing socket.\n",
ifam->ifam_version);
continue;
}
switch (ifam->ifam_type) {
case RTM_NEWADDR:
case RTM_DELADDR:
getnode_name_invalidate();
break;
case RTM_ADD:
case RTM_DELETE:
case RTM_CHANGE:
case RTM_GET:
case RTM_LOSING:
case RTM_REDIRECT:
case RTM_MISS:
case RTM_LOCK:
case RTM_OLDADD:
case RTM_OLDDEL:
case RTM_RESOLVE:
case RTM_IFINFO:
break;
default:
logit("rx unknown msg type (%d) on routing socket.\n",
ifam->ifam_type);
break;
}
}
}
static void
usage(char *s)
{
(void) fprintf(stderr,
"Usage: %s [-d debug_level] [-l logfilename]\n", s);
(void) fprintf(stderr,
" [-p cachename,positive_time_to_live]\n");
(void) fprintf(stderr,
" [-n cachename,negative_time_to_live]\n");
(void) fprintf(stderr,
" [-i cachename] [-s cachename,suggestedsize]\n");
(void) fprintf(stderr,
" [-h cachename,keep_hot_count] "\
"[-o cachename,\"yes\"|\"no\"]\n");
(void) fprintf(stderr,
" [-e cachename,\"yes\"|\"no\"] [-g] " \
"[-c cachename,\"yes\"|\"no\"]\n");
(void) fprintf(stderr,
" [-f configfilename] \n");
(void) fprintf(stderr,
"\n Supported caches: passwd, group, hosts, ipnodes\n");
(void) fprintf(stderr,
" exec_attr, prof_attr, and user_attr.\n");
exit(1);
}
static int logfd = 2;
int
nscd_set_lf(admin_t *ptr, char *s)
{
int newlogfd;
/*
* we don't really want to try and open the log file
* /dev/null since that will fail w/ our security fixes
*/
if (*s == 0) {
/* ignore empty log file specs */
/*EMPTY*/;
} else if (s == NULL || strcmp(s, "/dev/null") == 0) {
(void) strcpy(current_admin.logfile, "/dev/null");
(void) close(logfd);
logfd = -1;
} else {
/*
* In order to open this file securely, we'll try a few tricks
*/
if ((newlogfd = open(s, O_EXCL|O_WRONLY|O_CREAT, 0644)) < 0) {
/*
* File already exists... now we need to get cute
* since opening a file in a world-writeable directory
* safely is hard = it could be a hard link or a
* symbolic link to a system file.
*/
struct stat before;
if (lstat(s, &before) < 0) {
logit("Cannot open new logfile \"%s\": %sn",
s, strerror(errno));
return (-1);
}
if (S_ISREG(before.st_mode) && /* no symbolic links */
(before.st_nlink == 1) && /* no hard links */
(before.st_uid == 0)) { /* owned by root */
if ((newlogfd =
open(s, O_APPEND|O_WRONLY, 0644)) < 0) {
logit("Cannot open new "\
"logfile \"%s\": %s\n", s,
strerror(errno));
return (-1);
}
} else {
logit("Cannot use specified logfile \"%s\": "\
"file is/has links or isn't owned by "\
"root\n", s);
return (-1);
}
}
(void) strlcpy(ptr->logfile, s, 128);
(void) close(logfd);
logfd = newlogfd;
logit("Start of new logfile %s\n", s);
}
return (0);
}
void
logit(char *format, ...)
{
static mutex_t loglock;
struct timeval tv;
#define LOGBUFLEN 1024
char buffer[LOGBUFLEN];
va_list ap;
va_start(ap, format);
if (logfd >= 0) {
int safechars, offset;
if (gettimeofday(&tv, NULL) != 0 ||
ctime_r(&tv.tv_sec, buffer, LOGBUFLEN) == NULL) {
(void) snprintf(buffer, LOGBUFLEN,
"<time conversion failed>\t");
} else {
/*
* ctime_r() includes some stuff we don't want;
* adjust length to overwrite " YYYY\n".
*/
offset = strlen(buffer) - 6;
safechars = LOGBUFLEN - (offset - 1);
(void) snprintf(buffer + offset, safechars, ".%.4ld\t",
tv.tv_usec/100);
}
offset = strlen(buffer);
safechars = LOGBUFLEN - (offset - 1);
if (vsnprintf(buffer + offset, safechars, format, ap) >
safechars) {
(void) strncat(buffer, "...\n", LOGBUFLEN);
}
(void) mutex_lock(&loglock);
(void) write(logfd, buffer, strlen(buffer));
(void) mutex_unlock(&loglock);
}
va_end(ap);
#undef LOGBUFLEN
}
static void
do_update(nsc_call_t *in)
{
union {
nsc_data_t data;
char space[8192];
} u;
time_t now = time(NULL);
switch (MASKUPDATEBIT(in->nsc_callnumber)) {
case GETPWUID:
case GETPWNAM:
getpw_lookup(&u.data.nsc_ret, sizeof (u), in, now);
break;
case GETGRNAM:
case GETGRGID:
getgr_lookup(&u.data.nsc_ret, sizeof (u), in, now);
break;
case GETHOSTBYNAME:
case GETHOSTBYADDR:
gethost_lookup(&u.data.nsc_ret, sizeof (u), in, now);
break;
case GETIPNODEBYNAME:
case GETIPNODEBYADDR:
getnode_lookup(&u.data.nsc_ret, sizeof (u), in, now);
break;
case GETEXECID:
getexec_lookup(&u.data.nsc_ret, sizeof (u), in, now);
break;
case GETPROFNAM:
getprof_lookup(&u.data.nsc_ret, sizeof (u), in, now);
break;
case GETUSERNAM:
getuser_lookup(&u.data.nsc_ret, sizeof (u), in, now);
break;
default:
assert(0);
break;
}
free(in);
}
int
launch_update(nsc_call_t *in)
{
nsc_call_t *c;
int l = nsc_calllen(in);
in->nsc_callnumber |= UPDATEBIT;
if ((c = malloc(l)) == NULL) {
logit("thread create failed: %s\n", strerror(errno));
exit(1);
}
(void) memcpy(c, in, l);
if (current_admin.debug_level >= DBG_ALL) {
logit("launching update\n");
}
if (thr_create(NULL,
NULL,
(void *(*)(void*))do_update,
c,
0|THR_DETACHED, NULL) != 0) {
logit("thread create failed\n");
exit(1);
}
return (0);
}
static int
nsc_calllen(nsc_call_t *in)
{
switch (MASKUPDATEBIT(in->nsc_callnumber)) {
case GETPWUID:
case GETGRGID:
case NULLCALL:
return (sizeof (*in));
case GETPWNAM:
case GETGRNAM:
case GETHOSTBYNAME:
return (sizeof (*in) + strlen(in->nsc_u.name));
case GETIPNODEBYNAME:
return (sizeof (*in) + strlen(in->nsc_u.ipnode.name));
case GETHOSTBYADDR:
case GETIPNODEBYADDR:
return (sizeof (*in) + in->nsc_u.addr.a_length);
case GETEXECID:
case GETPROFNAM:
case GETUSERNAM:
return (sizeof (*in) + strlen(in->nsc_u.name));
}
return (0);
}
static int
client_getadmin(admin_t *ptr)
{
union {
nsc_data_t data;
char space[8192];
} u;
nsc_data_t *dptr;
int ndata;
int adata;
u.data.nsc_call.nsc_callnumber = GETADMIN;
ndata = sizeof (u);
adata = sizeof (u.data);
dptr = &u.data;
if (_nsc_trydoorcall(&dptr, &ndata, &adata) != SUCCESS) {
return (-1);
}
(void) memcpy(ptr, dptr->nsc_ret.nsc_u.buff, sizeof (*ptr));
return (0);
}
/*ARGSUSED*/
static void
getadmin(nsc_return_t *out, int size, nsc_call_t *ptr)
{
out->nsc_return_code = SUCCESS;
out->nsc_bufferbytesused = sizeof (current_admin);
(void) memcpy(out->nsc_u.buff, &current_admin, sizeof (current_admin));
}
static int
nscd_set_rbac(admin_t *new_admin, int invalidate)
{
int i;
char *dbname = NULL;
nsc_stat_t *cache = NULL;
nsc_stat_t *new = NULL;
void (*invalidate_func)(void);
for (i = 1; i <= 3; i++) {
/*
* Three of the RBAC databases are cached.
*/
switch (i) {
case 1:
dbname = NSS_DBNAM_EXECATTR;
cache = &current_admin.exec;
new = &new_admin->exec;
invalidate_func = getexec_invalidate;
break;
case 2:
dbname = NSS_DBNAM_PROFATTR;
cache = &current_admin.prof;
new = &new_admin->prof;
invalidate_func = getprof_invalidate;
break;
case 3:
dbname = NSS_DBNAM_USERATTR;
cache = &current_admin.user;
new = &new_admin->user;
invalidate_func = getuser_invalidate;
break;
default:
break;
}
if (invalidate) {
if (new->nsc_invalidate) {
logit("Invalidating %s cache\n", dbname);
(*invalidate_func)();
}
} else {
if (nscd_set_ttl_positive(cache, dbname,
new->nsc_pos_ttl) < 0 ||
nscd_set_ttl_negative(cache, dbname,
new->nsc_neg_ttl) < 0 ||
nscd_set_khc(cache, dbname, new->nsc_keephot) < 0 ||
nscd_set_odo(cache, dbname,
new->nsc_old_data_ok) < 0 ||
nscd_set_ec(cache, dbname, new->nsc_enabled) < 0 ||
nscd_set_ss(cache, dbname,
new->nsc_suggestedsize) < 0)
return (-1);
}
}
return (0);
}
/*ARGSUSED*/
static int
setadmin(nsc_return_t *out, int size, nsc_call_t *ptr)
{
admin_t *new;
out->nsc_return_code = SUCCESS;
out->nsc_bufferbytesused = sizeof (nsc_return_t);
new = (admin_t *)ptr->nsc_u.name;
/*
* global admin stuff
*/
if ((nscd_set_lf(&current_admin, new->logfile) < 0) ||
nscd_set_dl(&current_admin, new->debug_level) < 0) {
out->nsc_return_code = NOTFOUND;
return (-1);
}
/*
* per cache items
*/
if (new->passwd.nsc_invalidate) {
logit("Invalidating passwd cache\n");
getpw_invalidate();
}
if (new->group.nsc_invalidate) {
logit("Invalidating group cache\n");
getgr_invalidate();
}
if (new->host.nsc_invalidate) {
logit("Invalidating host cache\n");
gethost_invalidate();
}
if (new->node.nsc_invalidate) {
logit("Invalidating ipnodes cache\n");
getnode_invalidate();
}
(void) nscd_set_rbac(new, 1); /* invalidate rbac cache */
if (nscd_set_ttl_positive(&current_admin.passwd,
"passwd",
new->passwd.nsc_pos_ttl) < 0 ||
nscd_set_ttl_negative(&current_admin.passwd,
"passwd",
new->passwd.nsc_neg_ttl) < 0 ||
nscd_set_khc(&current_admin.passwd,
"passwd",
new->passwd.nsc_keephot) < 0 ||
nscd_set_odo(&current_admin.passwd,
"passwd",
new->passwd.nsc_old_data_ok) < 0 ||
nscd_set_ec(&current_admin.passwd,
"passwd",
new->passwd.nsc_enabled) < 0 ||
nscd_set_ss(&current_admin.passwd,
"passwd",
new->passwd.nsc_suggestedsize) < 0 ||
nscd_set_ttl_positive(&current_admin.group,
"group",
new->group.nsc_pos_ttl) < 0 ||
nscd_set_ttl_negative(&current_admin.group,
"group",
new->group.nsc_neg_ttl) < 0 ||
nscd_set_khc(&current_admin.group,
"group",
new->group.nsc_keephot) < 0 ||
nscd_set_odo(&current_admin.group,
"group",
new->group.nsc_old_data_ok) < 0 ||
nscd_set_ec(&current_admin.group,
"group",
new->group.nsc_enabled) < 0 ||
nscd_set_ss(&current_admin.group,
"group",
new->group.nsc_suggestedsize) < 0 ||
nscd_set_ttl_positive(&current_admin.node,
"ipnodes",
new->node.nsc_pos_ttl) < 0 ||
nscd_set_ttl_negative(&current_admin.node,
"ipnodes",
new->node.nsc_neg_ttl) < 0 ||
nscd_set_khc(&current_admin.node,
"ipnodes",
new->node.nsc_keephot) < 0 ||
nscd_set_odo(&current_admin.node,
"ipnodes",
new->node.nsc_old_data_ok) < 0 ||
nscd_set_ec(&current_admin.node,
"ipnodes",
new->node.nsc_enabled) < 0 ||
nscd_set_ss(&current_admin.node,
"ipnodes",
new->node.nsc_suggestedsize) < 0 ||
nscd_set_ttl_positive(&current_admin.host,
"hosts",
new->host.nsc_pos_ttl) < 0 ||
nscd_set_ttl_negative(&current_admin.host,
"hosts",
new->host.nsc_neg_ttl) < 0 ||
nscd_set_khc(&current_admin.host,
"hosts",
new->host.nsc_keephot) < 0 ||
nscd_set_odo(&current_admin.host,
"hosts",
new->host.nsc_old_data_ok) < 0 ||
nscd_set_ec(&current_admin.host,
"hosts",
new->host.nsc_enabled) < 0 ||
nscd_set_ss(&current_admin.host,
"hosts",
new->host.nsc_suggestedsize) < 0 ||
nscd_set_rbac(new, 0) < 0) {
out->nsc_return_code = NOTFOUND;
return (-1);
}
out->nsc_return_code = SUCCESS;
return (0);
}
void
client_killserver(void)
{
union {
nsc_data_t data;
char space[8192];
} u;
nsc_data_t *dptr;
int ndata;
int adata;
u.data.nsc_call.nsc_callnumber = KILLSERVER;
ndata = sizeof (u);
adata = sizeof (nsc_call_t);
dptr = &u.data;
_nsc_trydoorcall(&dptr, &ndata, &adata);
}
static int
client_setadmin(admin_t *ptr)
{
union {
nsc_data_t data;
char space[8192];
} u;
nsc_data_t *dptr;
int ndata;
int adata;
u.data.nsc_call.nsc_callnumber = SETADMIN;
(void) memcpy(u.data.nsc_call.nsc_u.name, ptr, sizeof (*ptr));
ndata = sizeof (u);
adata = sizeof (*ptr);
dptr = &u.data;
if (_nsc_trydoorcall(&dptr, &ndata, &adata) != SUCCESS) {
return (-1);
}
return (0);
}
static void
dump_stat(nsc_stat_t *ptr)
{
double hitrate;
(void) printf("%10s cache is enabled\n",
(ptr->nsc_enabled?"Yes":"No"));
(void) printf("%10d cache hits on positive entries\n",
ptr->nsc_pos_cache_hits);
(void) printf("%10d cache hits on negative entries\n",
ptr->nsc_neg_cache_hits);
(void) printf("%10d cache misses on positive entries\n",
ptr->nsc_pos_cache_misses);
(void) printf("%10d cache misses on negative entries\n",
ptr->nsc_neg_cache_misses);
hitrate = ptr->nsc_pos_cache_misses + ptr->nsc_neg_cache_misses +
ptr->nsc_pos_cache_hits + ptr->nsc_neg_cache_hits;
if (hitrate > 0.0)
hitrate = (100.0 * ((double)ptr->nsc_pos_cache_hits +
(double)ptr->nsc_neg_cache_hits))/hitrate;
(void) printf("%10.1f%% cache hit rate\n", hitrate);
(void) printf("%10d queries deferred\n", ptr->nsc_throttle_count);
(void) printf("%10d total entries\n", ptr->nsc_entries);
(void) printf("%10d complete cache invalidations\n",
ptr->nsc_invalidate_count);
(void) printf("%10d suggested size\n", ptr->nsc_suggestedsize);
(void) printf("%10d seconds time to live for positive entries\n",
ptr->nsc_pos_ttl);
(void) printf("%10d seconds time to live for negative entries\n",
ptr->nsc_neg_ttl);
(void) printf("%10d most active entries to be kept valid\n",
ptr->nsc_keephot);
(void) printf("%10s check /etc/{passwd, group, hosts, inet/ipnodes} "
"file for changes\n",
(ptr->nsc_check_files?"Yes":"No"));
(void) printf("%10s use possibly stale data rather than waiting for "
"refresh\n",
(ptr->nsc_old_data_ok?"Yes":"No"));
}
static void
client_showstats(admin_t *ptr)
{
(void) printf("nscd configuration:\n\n");
(void) printf("%10d server debug level\n", ptr->debug_level);
(void) printf("\"%s\" is server log file\n", ptr->logfile);
(void) printf("\npasswd cache:\n\n");
dump_stat(&(ptr->passwd));
(void) printf("\ngroup cache:\n\n");
dump_stat(&(ptr->group));
(void) printf("\nhosts cache:\n\n");
dump_stat(&(ptr->host));
(void) printf("\nipnodes cache:\n\n");
dump_stat(&(ptr->node));
(void) printf("\nexec_attr cache:\n\n");
dump_stat(&(ptr->exec));
(void) printf("\nprof_attr cache:\n\n");
dump_stat(&(ptr->prof));
(void) printf("\nuser_attr cache:\n\n");
dump_stat(&(ptr->user));
}
/*
* detach from tty
*/
static void
detachfromtty(void)
{
if (logfd > 0) {
int i;
for (i = 0; i < logfd; i++)
(void) close(i);
closefrom(logfd+1);
} else
closefrom(0);
(void) chdir("/");
switch (fork1()) {
case (pid_t)-1:
exit(1);
break;
case 0:
break;
default:
exit(0);
}
(void) setsid();
(void) open("/dev/null", O_RDWR, 0);
(void) dup(0);
(void) dup(0);
}