/* * 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) 1989, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2016 by Delphix. All rights reserved. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ #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 #include #include #include #include "smfcfg.h" #include "sm_statd.h" #define home0 "/var/statmon" #define current0 "/var/statmon/sm" #define backup0 "/var/statmon/sm.bak" #define state0 "/var/statmon/state" #define home1 "statmon" #define current1 "statmon/sm/" #define backup1 "statmon/sm.bak/" #define state1 "statmon/state" extern int daemonize_init(void); extern void daemonize_fini(int fd); /* * User and group IDs to run as. These are hardwired, rather than looked * up at runtime, because they are very unlikely to change and because they * provide some protection against bogus changes to the passwd and group * files. */ uid_t daemon_uid = DAEMON_UID; gid_t daemon_gid = DAEMON_GID; char STATE[MAXPATHLEN], CURRENT[MAXPATHLEN], BACKUP[MAXPATHLEN]; static char statd_home[MAXPATHLEN]; int debug; int regfiles_only = 0; /* 1 => use symlinks in statmon, 0 => don't */ int statd_port = 0; char hostname[MAXHOSTNAMELEN]; /* * These variables will be used to store all the * alias names for the host, as well as the -a * command line hostnames. */ int host_name_count; char **host_name; /* store -a opts */ int addrix; /* # of -a entries */ /* * The following 2 variables are meaningful * only under a HA configuration. * The path_name array is dynamically allocated in main() during * command line argument processing for the -p options. */ char **path_name = NULL; /* store -p opts */ int pathix = 0; /* # of -p entries */ /* Global variables. Refer to sm_statd.h for description */ mutex_t crash_lock; int die; int in_crash; mutex_t sm_trylock; rwlock_t thr_rwlock; cond_t retrywait; mutex_t name_addrlock; mutex_t merges_lock; cond_t merges_cond; boolean_t in_merges; /* forward references */ static void set_statmon_owner(void); static void copy_client_names(void); static void one_statmon_owner(const char *); static int nftw_owner(const char *, const struct stat *, int, struct FTW *); /* * statd protocol * commands: * SM_STAT * returns stat_fail to caller * SM_MON * adds an entry to the monitor_q and the record_q. * This message is sent by the server lockd to the server * statd, to indicate that a new client is to be monitored. * It is also sent by the server lockd to the client statd * to indicate that a new server is to be monitored. * SM_UNMON * removes an entry from the monitor_q and the record_q * SM_UNMON_ALL * removes all entries from a particular host from the * monitor_q and the record_q. Our statd has this * disabled. * SM_SIMU_CRASH * simulate a crash. Removes everything from the * record_q and the recovery_q, then calls statd_init() * to restart things. This message is sent by the server * lockd to the server statd to have all clients notified * that they should reclaim locks. * SM_NOTIFY * Sent by statd on server to statd on client during * crash recovery. The client statd passes the info * to its lockd so it can attempt to reclaim the locks * held on the server. * * There are three main hash tables used to keep track of things. * mon_table * table that keeps track hosts statd must watch. If one of * these hosts crashes, then any locks held by that host must * be released. * record_table * used to keep track of all the hostname files stored in * the directory /var/statmon/sm. These are client hosts who * are holding or have held a lock at some point. Needed * to determine if a file needs to be created for host in * /var/statmon/sm. * recov_q * used to keep track hostnames during a recovery * * The entries are hashed based upon the name. * * There is a directory /var/statmon/sm which holds a file named * for each host that is holding (or has held) a lock. This is * used during initialization on startup, or after a simulated * crash. */ static void sm_prog_1(struct svc_req *rqstp, SVCXPRT *transp) { union { struct sm_name sm_stat_1_arg; struct mon sm_mon_1_arg; struct mon_id sm_unmon_1_arg; struct my_id sm_unmon_all_1_arg; struct stat_chge ntf_arg; struct reg1args reg1_arg; } argument; union { sm_stat_res stat_resp; sm_stat mon_resp; struct reg1res reg1_resp; } result; bool_t (*xdr_argument)(), (*xdr_result)(); void (*local)(void *, void *); /* * Dispatch according to which protocol is being used: * NSM_ADDR_PROGRAM is the private lockd address * registration protocol. * SM_PROG is the normal statd (NSM) protocol. */ if (rqstp->rq_prog == NSM_ADDR_PROGRAM) { switch (rqstp->rq_proc) { case NULLPROC: svc_sendreply(transp, xdr_void, (caddr_t)NULL); return; case NSMADDRPROC1_REG: xdr_argument = xdr_reg1args; xdr_result = xdr_reg1res; local = nsmaddrproc1_reg; break; case NSMADDRPROC1_UNREG: /* Not impl. */ default: svcerr_noproc(transp); return; } } else { /* Must be SM_PROG */ switch (rqstp->rq_proc) { case NULLPROC: svc_sendreply(transp, xdr_void, (caddr_t)NULL); return; case SM_STAT: xdr_argument = xdr_sm_name; xdr_result = xdr_sm_stat_res; local = sm_stat_svc; break; case SM_MON: xdr_argument = xdr_mon; xdr_result = xdr_sm_stat_res; local = sm_mon_svc; break; case SM_UNMON: xdr_argument = xdr_mon_id; xdr_result = xdr_sm_stat; local = sm_unmon_svc; break; case SM_UNMON_ALL: xdr_argument = xdr_my_id; xdr_result = xdr_sm_stat; local = sm_unmon_all_svc; break; case SM_SIMU_CRASH: xdr_argument = xdr_void; xdr_result = xdr_void; local = sm_simu_crash_svc; break; case SM_NOTIFY: xdr_argument = xdr_stat_chge; xdr_result = xdr_void; local = sm_notify_svc; break; default: svcerr_noproc(transp); return; } } (void) memset(&argument, 0, sizeof (argument)); if (!svc_getargs(transp, xdr_argument, (caddr_t)&argument)) { svcerr_decode(transp); return; } (void) memset(&result, 0, sizeof (result)); (*local)(&argument, &result); if (!svc_sendreply(transp, xdr_result, (caddr_t)&result)) { svcerr_systemerr(transp); } if (!svc_freeargs(transp, xdr_argument, (caddr_t)&argument)) { syslog(LOG_ERR, "statd: unable to free arguments\n"); } } /* * Remove all files under directory path_dir. */ static int remove_dir(char *path_dir) { DIR *dp; struct dirent *dirp; char tmp_path[MAXPATHLEN]; if ((dp = opendir(path_dir)) == NULL) { if (debug) syslog(LOG_ERR, "warning: open directory %s failed: %m\n", path_dir); return (1); } while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, ".") != 0 && strcmp(dirp->d_name, "..") != 0) { if (strlen(path_dir) + strlen(dirp->d_name) +2 > MAXPATHLEN) { syslog(LOG_ERR, "statd: remove dir %s/%s " "failed. Pathname too long.\n", path_dir, dirp->d_name); continue; } (void) strcpy(tmp_path, path_dir); (void) strcat(tmp_path, "/"); (void) strcat(tmp_path, dirp->d_name); delete_file(tmp_path); } } (void) closedir(dp); return (0); } /* * Copy all files from directory `from_dir' to directory `to_dir'. * Symlinks, if any, are preserved. */ void copydir_from_to(char *from_dir, char *to_dir) { int n; DIR *dp; struct dirent *dirp; char rname[MAXNAMELEN + 1]; char path[MAXPATHLEN+MAXNAMELEN+2]; if ((dp = opendir(from_dir)) == NULL) { if (debug) syslog(LOG_ERR, "warning: open directory %s failed: %m\n", from_dir); return; } while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) { continue; } (void) strcpy(path, from_dir); (void) strcat(path, "/"); (void) strcat(path, dirp->d_name); if (is_symlink(path)) { /* * Follow the link to get the referenced file name * and make a new link for that file in to_dir. */ n = readlink(path, rname, MAXNAMELEN); if (n <= 0) { if (debug >= 2) { (void) printf("copydir_from_to: can't " "read link %s\n", path); } continue; } rname[n] = '\0'; (void) create_symlink(to_dir, rname, dirp->d_name); } else { /* * Simply copy regular files to to_dir. */ (void) strcpy(path, to_dir); (void) strcat(path, "/"); (void) strcat(path, dirp->d_name); (void) create_file(path); } } (void) closedir(dp); } static int init_hostname(void) { struct lifnum lifn; int sock; if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { syslog(LOG_ERR, "statd:init_hostname, socket: %m"); return (-1); } lifn.lifn_family = AF_UNSPEC; lifn.lifn_flags = 0; if (ioctl(sock, SIOCGLIFNUM, (char *)&lifn) < 0) { syslog(LOG_ERR, "statd:init_hostname, get number of interfaces, error: %m"); close(sock); return (-1); } host_name_count = lifn.lifn_count; host_name = malloc(host_name_count * sizeof (char *)); if (host_name == NULL) { perror("statd -a can't get ip configuration\n"); close(sock); return (-1); } close(sock); return (0); } static void thr_statd_merges(void) { /* * Get other aliases from each interface. */ merge_hosts(); /* * Get all of the configured IP addresses. */ merge_ips(); /* * Notify the waiters. */ (void) mutex_lock(&merges_lock); in_merges = B_FALSE; (void) cond_broadcast(&merges_cond); (void) mutex_unlock(&merges_lock); } /* * This function is called for each configured network type to * bind and register our RPC service programs. * * On TCP or UDP, we may want to bind SM_PROG on a specific port * (when statd_port is specified) in which case we'll use the * variant of svc_tp_create() that lets us pass a bind address. */ static void sm_svc_tp_create(struct netconfig *nconf) { char port_str[8]; struct nd_hostserv hs; struct nd_addrlist *al = NULL; SVCXPRT *xprt = NULL; /* * If statd_port is set and this is an inet transport, * bind this service on the specified port. The TLI way * to create such a bind address is netdir_getbyname() * with the special "host" HOST_SELF_BIND. This builds * an all-zeros IP address with the specified port. */ if (statd_port != 0 && (strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf->nc_protofmly, NC_INET6) == 0)) { int err; snprintf(port_str, sizeof (port_str), "%u", (unsigned short)statd_port); hs.h_host = HOST_SELF_BIND; hs.h_serv = port_str; err = netdir_getbyname((struct netconfig *)nconf, &hs, &al); if (err == 0 && al != NULL) { xprt = svc_tp_create_addr(sm_prog_1, SM_PROG, SM_VERS, nconf, al->n_addrs); netdir_free(al, ND_ADDRLIST); } if (xprt == NULL) { syslog(LOG_ERR, "statd: unable to create " "(SM_PROG, SM_VERS) on transport %s (port %d)", nconf->nc_netid, statd_port); } /* fall-back to default bind */ } if (xprt == NULL) { /* * Had statd_port=0, or non-inet transport, * or the bind to a specific port failed. * Do a default bind. */ xprt = svc_tp_create(sm_prog_1, SM_PROG, SM_VERS, nconf); } if (xprt == NULL) { syslog(LOG_ERR, "statd: unable to create " "(SM_PROG, SM_VERS) for transport %s", nconf->nc_netid); return; } /* * Also register the NSM_ADDR program on this * transport handle (same dispatch function). */ if (!svc_reg(xprt, NSM_ADDR_PROGRAM, NSM_ADDR_V1, sm_prog_1, nconf)) { syslog(LOG_ERR, "statd: failed to register " "(NSM_ADDR_PROGRAM, NSM_ADDR_V1) for " "netconfig %s", nconf->nc_netid); } } int main(int argc, char *argv[]) { int c; int ppid; extern char *optarg; int choice = 0; struct rlimit rl; int mode; int sz; int pipe_fd = -1; int ret; int connmaxrec = RPC_MAXDATASIZE; struct netconfig *nconf; NCONF_HANDLE *nc; addrix = 0; pathix = 0; (void) gethostname(hostname, MAXHOSTNAMELEN); if (init_hostname() < 0) exit(1); ret = nfs_smf_get_iprop("statd_port", &statd_port, DEFAULT_INSTANCE, SCF_TYPE_INTEGER, STATD); if (ret != SA_OK) { syslog(LOG_ERR, "Reading of statd_port from SMF " "failed, using default value"); } while ((c = getopt(argc, argv, "Dd:a:G:p:P:rU:")) != EOF) switch (c) { case 'd': (void) sscanf(optarg, "%d", &debug); break; case 'D': choice = 1; break; case 'a': if (addrix < host_name_count) { if (strcmp(hostname, optarg) != 0) { sz = strlen(optarg); if (sz < MAXHOSTNAMELEN) { host_name[addrix] = (char *)xmalloc(sz+1); if (host_name[addrix] != NULL) { (void) sscanf(optarg, "%s", host_name[addrix]); addrix++; } } else (void) fprintf(stderr, "statd: -a name of host is too long.\n"); } } else (void) fprintf(stderr, "statd: -a exceeding maximum hostnames\n"); break; case 'U': (void) sscanf(optarg, "%d", &daemon_uid); break; case 'G': (void) sscanf(optarg, "%d", &daemon_gid); break; case 'p': if (strlen(optarg) < MAXPATHLEN) { /* If the path_name array has not yet */ /* been malloc'ed, do that. The array */ /* should be big enough to hold all of the */ /* -p options we might have. An upper */ /* bound on the number of -p options is */ /* argc/2, because each -p option consumes */ /* two arguments. Here the upper bound */ /* is supposing that all the command line */ /* arguments are -p options, which would */ /* actually never be the case. */ if (path_name == NULL) { size_t sz = (argc/2) * sizeof (char *); path_name = (char **)malloc(sz); if (path_name == NULL) { (void) fprintf(stderr, "statd: malloc failed\n"); exit(1); } (void) memset(path_name, 0, sz); } path_name[pathix] = optarg; pathix++; } else { (void) fprintf(stderr, "statd: -p pathname is too long.\n"); } break; case 'P': (void) sscanf(optarg, "%d", &statd_port); if (statd_port < 1 || statd_port > UINT16_MAX) { (void) fprintf(stderr, "statd: -P port invalid.\n"); statd_port = 0; } break; case 'r': regfiles_only = 1; break; default: (void) fprintf(stderr, "statd [-d level] [-D]\n"); return (1); } if (choice == 0) { (void) strcpy(statd_home, home0); (void) strcpy(CURRENT, current0); (void) strcpy(BACKUP, backup0); (void) strcpy(STATE, state0); } else { (void) strcpy(statd_home, home1); (void) strcpy(CURRENT, current1); (void) strcpy(BACKUP, backup1); (void) strcpy(STATE, state1); } if (debug) (void) printf("debug is on, create entry: %s, %s, %s\n", CURRENT, BACKUP, STATE); if (getrlimit(RLIMIT_NOFILE, &rl)) (void) printf("statd: getrlimit failed. \n"); /* Set maxfdlimit current soft limit */ rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_NOFILE, &rl) != 0) syslog(LOG_ERR, "statd: unable to set RLIMIT_NOFILE to %d\n", rl.rlim_cur); (void) enable_extended_FILE_stdio(-1, -1); if (!debug) { pipe_fd = daemonize_init(); openlog("statd", LOG_PID, LOG_DAEMON); } (void) _create_daemon_lock(STATD, daemon_uid, daemon_gid); /* * establish our lock on the lock file and write our pid to it. * exit if some other process holds the lock, or if there's any * error in writing/locking the file. */ ppid = _enter_daemon_lock(STATD); switch (ppid) { case 0: break; case -1: syslog(LOG_ERR, "error locking for %s: %s", STATD, strerror(errno)); exit(2); default: /* daemon was already running */ exit(0); } mutex_init(&merges_lock, USYNC_THREAD, NULL); cond_init(&merges_cond, USYNC_THREAD, NULL); in_merges = B_TRUE; /* * Create thr_statd_merges() thread to populate the host_name list * asynchronously. */ if (thr_create(NULL, 0, (void *(*)(void *))thr_statd_merges, NULL, THR_DETACHED, NULL) != 0) { syslog(LOG_ERR, "statd: unable to create thread for " "thr_statd_merges()."); exit(1); } /* * Set to automatic mode such that threads are automatically * created */ mode = RPC_SVC_MT_AUTO; if (!rpc_control(RPC_SVC_MTMODE_SET, &mode)) { syslog(LOG_ERR, "statd:unable to set automatic MT mode."); exit(1); } /* * Set non-blocking mode and maximum record size for * connection oriented RPC transports. */ if (!rpc_control(RPC_SVC_CONNMAXREC_SET, &connmaxrec)) { syslog(LOG_INFO, "unable to set maximum RPC record size"); } /* * Enumerate network transports and create service listeners * as appropriate for each. */ if ((nc = setnetconfig()) == NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } while ((nconf = getnetconfig(nc)) != NULL) { /* * Skip things like tpi_raw, invisible... */ if ((nconf->nc_flag & NC_VISIBLE) == 0) continue; if (nconf->nc_semantics != NC_TPI_CLTS && nconf->nc_semantics != NC_TPI_COTS && nconf->nc_semantics != NC_TPI_COTS_ORD) continue; sm_svc_tp_create(nconf); } (void) endnetconfig(nc); /* * Make sure /var/statmon and any alternate (-p) statmon * directories exist and are owned by daemon. Then change our uid * to daemon. The uid change is to prevent attacks against local * daemons that trust any call from a local root process. */ set_statmon_owner(); /* * * statd now runs as a daemon rather than root and can not * dump core under / because of the permission. It is * important that current working directory of statd be * changed to writable directory /var/statmon so that it * can dump the core upon the receipt of the signal. * One still need to set allow_setid_core to non-zero in * /etc/system to get the core dump. * */ if (chdir(statd_home) < 0) { syslog(LOG_ERR, "can't chdir %s: %m", statd_home); exit(1); } copy_client_names(); rwlock_init(&thr_rwlock, USYNC_THREAD, NULL); mutex_init(&crash_lock, USYNC_THREAD, NULL); mutex_init(&name_addrlock, USYNC_THREAD, NULL); cond_init(&retrywait, USYNC_THREAD, NULL); sm_inithash(); die = 0; /* * This variable is set to ensure that an sm_crash * request will not be done at the same time * when a statd_init is being done, since sm_crash * can reset some variables that statd_init will be using. */ in_crash = 1; statd_init(); /* * statd is up and running as far as we are concerned. */ daemonize_fini(pipe_fd); if (debug) (void) printf("Starting svc_run\n"); svc_run(); syslog(LOG_ERR, "statd: svc_run returned\n"); /* NOTREACHED */ thr_exit((void *)1); return (0); } /* * Make sure the ownership of the statmon directories is correct, then * change our uid to match. If the top-level directories (/var/statmon, -p * arguments) don't exist, they are created first. The sm and sm.bak * directories are not created here, but if they already exist, they are * chowned to the correct uid, along with anything else in the * directories. */ static void set_statmon_owner(void) { int i; boolean_t can_do_mlp; /* * Recursively chown/chgrp /var/statmon and the alternate paths, * creating them if necessary. */ one_statmon_owner(statd_home); for (i = 0; i < pathix; i++) { char alt_path[MAXPATHLEN]; snprintf(alt_path, MAXPATHLEN, "%s/statmon", path_name[i]); one_statmon_owner(alt_path); } can_do_mlp = priv_ineffect(PRIV_NET_BINDMLP); if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, daemon_uid, daemon_gid, can_do_mlp ? PRIV_NET_BINDMLP : NULL, NULL) == -1) { syslog(LOG_ERR, "can't run unprivileged: %m"); exit(1); } __fini_daemon_priv(PRIV_PROC_EXEC, PRIV_PROC_SESSION, PRIV_FILE_LINK_ANY, PRIV_PROC_INFO, NULL); } /* * Copy client names from the alternate statmon directories into * /var/statmon. The top-level (statmon) directories should already * exist, though the sm and sm.bak directories might not. */ static void copy_client_names(void) { int i; char buf[MAXPATHLEN+SM_MAXPATHLEN]; /* * Copy all clients from alternate paths to /var/statmon/sm * Remove the files in alternate directory when copying is done. */ for (i = 0; i < pathix; i++) { /* * If the alternate directories do not exist, create it. * If they do exist, just do the copy. */ snprintf(buf, sizeof (buf), "%s/statmon/sm", path_name[i]); if ((mkdir(buf, SM_DIRECTORY_MODE)) == -1) { if (errno != EEXIST) { syslog(LOG_ERR, "can't mkdir %s: %m\n", buf); continue; } copydir_from_to(buf, CURRENT); (void) remove_dir(buf); } (void) snprintf(buf, sizeof (buf), "%s/statmon/sm.bak", path_name[i]); if ((mkdir(buf, SM_DIRECTORY_MODE)) == -1) { if (errno != EEXIST) { syslog(LOG_ERR, "can't mkdir %s: %m\n", buf); continue; } copydir_from_to(buf, BACKUP); (void) remove_dir(buf); } } } /* * Create the given directory if it doesn't already exist. Set the user * and group to daemon for the directory and anything under it. */ static void one_statmon_owner(const char *dir) { if ((mkdir(dir, SM_DIRECTORY_MODE)) == -1) { if (errno != EEXIST) { syslog(LOG_ERR, "can't mkdir %s: %m", dir); return; } } if (debug) printf("Setting owner for %s\n", dir); if (nftw(dir, nftw_owner, MAX_FDS, FTW_PHYS) != 0) { syslog(LOG_WARNING, "error setting owner for %s: %m", dir); } } /* * Set the user and group to daemon for the given file or directory. If * it's a directory, also makes sure that it is mode 755. * Generates a syslog message but does not return an error if there were * problems. */ /*ARGSUSED3*/ static int nftw_owner(const char *path, const struct stat *statp, int info, struct FTW *ftw) { if (!(info == FTW_F || info == FTW_D)) return (0); /* * Some older systems might have mode 777 directories. Fix that. */ if (info == FTW_D && (statp->st_mode & (S_IWGRP | S_IWOTH)) != 0) { mode_t newmode = (statp->st_mode & ~(S_IWGRP | S_IWOTH)) & S_IAMB; if (debug) printf("chmod %03o %s\n", newmode, path); if (chmod(path, newmode) < 0) { int error = errno; syslog(LOG_WARNING, "can't chmod %s to %03o: %m", path, newmode); if (debug) printf(" FAILED: %s\n", strerror(error)); } } /* If already owned by daemon, don't bother changing. */ if (statp->st_uid == daemon_uid && statp->st_gid == daemon_gid) return (0); if (debug) printf("lchown %s daemon:daemon\n", path); if (lchown(path, daemon_uid, daemon_gid) < 0) { int error = errno; syslog(LOG_WARNING, "can't chown %s to daemon: %m", path); if (debug) printf(" FAILED: %s\n", strerror(error)); } return (0); }