#include <stdio.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <ftw.h>
#include <signal.h>
#include <string.h>
#include <syslog.h>
#include <netconfig.h>
#include <netdir.h>
#include <unistd.h>
#include <netdb.h>
#include <rpc/rpc.h>
#include <rpc/svc.h>
#include <netinet/in.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sockio.h>
#include <dirent.h>
#include <errno.h>
#include <rpcsvc/sm_inter.h>
#include <rpcsvc/nsm_addr.h>
#include <thread.h>
#include <synch.h>
#include <net/if.h>
#include <limits.h>
#include <rpcsvc/daemon_utils.h>
#include <priv_utils.h>
#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);
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;
int statd_port = 0;
char hostname[MAXHOSTNAMELEN];
int host_name_count;
char **host_name;
int addrix;
char **path_name = NULL;
int pathix = 0;
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;
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 *);
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 *);
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:
default:
svcerr_noproc(transp);
return;
}
} else {
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");
}
}
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);
}
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)) {
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 {
(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)
{
merge_hosts();
merge_ips();
(void) mutex_lock(&merges_lock);
in_merges = B_FALSE;
(void) cond_broadcast(&merges_cond);
(void) mutex_unlock(&merges_lock);
}
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 != 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);
}
}
if (xprt == NULL) {
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;
}
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 (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");
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);
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:
exit(0);
}
mutex_init(&merges_lock, USYNC_THREAD, NULL);
cond_init(&merges_cond, USYNC_THREAD, NULL);
in_merges = B_TRUE;
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);
}
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);
}
if (!rpc_control(RPC_SVC_CONNMAXREC_SET, &connmaxrec)) {
syslog(LOG_INFO, "unable to set maximum RPC record size");
}
if ((nc = setnetconfig()) == NULL) {
syslog(LOG_ERR, "setnetconfig failed: %m");
return (-1);
}
while ((nconf = getnetconfig(nc)) != NULL) {
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);
set_statmon_owner();
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;
in_crash = 1;
statd_init();
daemonize_fini(pipe_fd);
if (debug)
(void) printf("Starting svc_run\n");
svc_run();
syslog(LOG_ERR, "statd: svc_run returned\n");
thr_exit((void *)1);
return (0);
}
static void
set_statmon_owner(void)
{
int i;
boolean_t can_do_mlp;
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);
}
static void
copy_client_names(void)
{
int i;
char buf[MAXPATHLEN+SM_MAXPATHLEN];
for (i = 0; i < pathix; i++) {
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);
}
}
}
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);
}
}
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);
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 (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);
}