/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "getxby_door.h" #include "server_door.h" #include "nscd.h" /* Includes for filenames of databases */ #include #include #include #include #include #include #include #include #include #include #include #include #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 (¤t_admin.passwd); if (strncmp(caches[1], s, strlen(caches[1])) == 0) return (¤t_admin.group); if (strncmp(caches[2], s, strlen(caches[2])) == 0) return (¤t_admin.host); if (strncmp(caches[3], s, strlen(caches[3])) == 0) return (¤t_admin.node); if (strncmp(caches[4], s, strlen(caches[4])) == 0) return (¤t_admin.exec); if (strncmp(caches[5], s, strlen(caches[5])) == 0) return (¤t_admin.prof); if (strncmp(caches[6], s, strlen(caches[6])) == 0) return (¤t_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(¤t_admin) != 0)) { (void) fprintf(stderr, "%s doesn't appear to be running.\n", argv[0]); exit(1); } client_showstats(¤t_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(¤t_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(¤t_admin); } if (doset) { if (client_setadmin(¤t_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(¤t_admin, current_admin.logfile); } else { if (strlen(current_admin.logfile) == 0) (void) strcpy(current_admin.logfile, "/dev/null"); (void) nscd_set_lf(¤t_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, "