#ifndef lint
static const char rcsid[] = "$FreeBSD$";
#endif
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <oncrpc/rpc.h>
#include <oncrpc/rpcb.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/param.h>
#include <libutil.h>
#include <spawn.h>
#include <launch.h>
#include "statd.h"
int bindresvport_sa(int sd, struct sockaddr * sa);
int statd_server = 0;
int notify_only = 0;
int list_only = 0;
int udpport, tcpport;
int udp6port, tcp6port;
int statudpsock, stattcpsock;
int statudp6sock, stattcp6sock;
struct pidfh *pfh = NULL;
const struct nfs_conf_statd config_defaults =
{
0,
0,
0,
1,
1,
0,
};
struct nfs_conf_statd config;
int log_to_stderr = 0;
extern void sm_prog_1(struct svc_req * rqstp, SVCXPRT * transp);
static void handle_sigchld(int sig);
static void cleanup(int sig);
static void usage(void);
static int config_read(struct nfs_conf_statd * conf);
int
main(int argc, char **argv)
{
SVCXPRT *transp;
struct sigaction sa;
int c, on = 1;
pid_t pid;
struct sockaddr_storage saddr;
struct sockaddr_in *sin = (struct sockaddr_in*)&saddr;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&saddr;
socklen_t socklen;
char *unnotify_host = NULL;
int rv, need_notify;
config = config_defaults;
config_read(&config);
while ((c = getopt(argc, argv, "dnlLN:")) != EOF)
switch (c) {
case 'd':
config.verbose = INT_MAX;
break;
case 'n':
if (list_only || unnotify_host)
usage();
notify_only = 1;
break;
case 'l':
if (notify_only || unnotify_host || (list_only == LIST_MODE_WATCH))
usage();
list_only = LIST_MODE_ONCE;
break;
case 'L':
if (notify_only || unnotify_host || (list_only == LIST_MODE_ONCE))
usage();
list_only = LIST_MODE_WATCH;
break;
case 'N':
if (notify_only || unnotify_host || list_only)
usage();
unnotify_host = optarg;
break;
default:
usage();
}
if (list_only || unnotify_host)
log_to_stderr = 1;
if (list_only)
exit(list_hosts(list_only));
if (getuid()) {
log(LOG_ERR, "Sorry, rpc.statd must be run as root");
exit(0);
}
if (unnotify_host)
exit(do_unnotify_host(unnotify_host));
signal(SIGINT, cleanup);
signal(SIGTERM, cleanup);
signal(SIGHUP, cleanup);
signal(SIGQUIT, cleanup);
openlog("rpc.statd", LOG_PID | LOG_CONS, LOG_DAEMON);
setlogmask(LOG_UPTO(LOG_LEVEL));
if (notify_only) {
rv = notify_hosts();
if (rv)
log(LOG_NOTICE, "statd.notify exiting %d", rv);
exit(rv);
}
statd_server = 1;
log(LOG_INFO, "statd starting");
pfh = pidfile_open(_PATH_STATD_PID, 0644, &pid);
if (pfh == NULL) {
log(LOG_ERR, "can't open statd pidfile: %s (%d)", strerror(errno), errno);
if (errno == EEXIST) {
log(LOG_ERR, "statd already running, pid: %d", pid);
exit(0);
}
exit(2);
}
if (pidfile_write(pfh) == -1)
log(LOG_WARNING, "can't write to statd pidfile: %s (%d)", strerror(errno), errno);
need_notify = init_file(_PATH_STATD_DATABASE);
if (need_notify && !get_statd_notify_pid()) {
log(LOG_INFO, "need to start statd notify");
if (statd_notify_is_loaded())
statd_notify_start();
else
statd_notify_load();
}
statudpsock = stattcpsock = -1;
statudp6sock = stattcp6sock = -1;
rpcb_unset(NULL, SM_PROG, SM_VERS);
if (config.udp) {
if ((statudpsock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
log(LOG_ERR, "can't create UDP IPv4 socket: %s (%d)", strerror(errno), errno);
if (statudpsock >= 0) {
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
sin->sin_port = htons(config.port);
sin->sin_len = sizeof(*sin);
if (bindresvport_sa(statudpsock, (struct sockaddr *)sin) < 0) {
sleep(6);
if (bindresvport_sa(statudpsock, (struct sockaddr *)sin) < 0) {
log(LOG_ERR, "can't bind UDP IPv4 addr: %s (%d)", strerror(errno), errno);
close(statudpsock);
statudpsock = -1;
}
}
}
if (statudpsock >= 0) {
socklen = sizeof(*sin);
if (getsockname(statudpsock, (struct sockaddr *)sin, &socklen)) {
log(LOG_ERR, "can't getsockname on UDP IPv4 socket: %s (%d)", strerror(errno), errno);
close(statudpsock);
statudpsock = -1;
} else {
udpport = ntohs(sin->sin_port);
}
}
if ((statudpsock >= 0) && ((transp = svcudp_create(statudpsock)) == NULL)) {
log(LOG_WARNING, "cannot create UDP IPv4 service");
close(statudpsock);
statudpsock = -1;
}
if ((statudpsock >= 0) && !svc_register(transp, SM_PROG, SM_VERS, sm_prog_1, IPPROTO_UDP)) {
log(LOG_WARNING, "unable to register IPv4 (SM_PROG, SM_VERS, UDP)");
svc_destroy(transp);
close(statudpsock);
statudpsock = -1;
}
if ((statudp6sock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
log(LOG_ERR, "can't create UDP IPv6 socket: %s (%d)", strerror(errno), errno);
if (statudp6sock >= 0) {
setsockopt(statudp6sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = in6addr_any;
sin6->sin6_port = htons(config.port);
sin6->sin6_len = sizeof(*sin6);
if (bindresvport_sa(statudp6sock, (struct sockaddr *)sin6) < 0) {
sleep(6);
if (bindresvport_sa(statudp6sock, (struct sockaddr *)sin6) < 0) {
log(LOG_ERR, "can't bind UDP IPv6 addr: %s (%d)", strerror(errno), errno);
close(statudp6sock);
statudp6sock = -1;
}
}
}
if (statudp6sock >= 0) {
socklen = sizeof(*sin6);
if (getsockname(statudp6sock, (struct sockaddr *)sin6, &socklen)) {
log(LOG_ERR, "can't getsockname on UDP IPv6 socket: %s (%d)", strerror(errno), errno);
close(statudp6sock);
statudp6sock = -1;
} else {
udp6port = ntohs(sin6->sin6_port);
}
}
if ((statudp6sock >= 0) && ((transp = svcudp_create(statudp6sock)) == NULL)) {
log(LOG_WARNING, "cannot create UDP IPv6 service");
close(statudp6sock);
statudp6sock = -1;
}
if ((statudp6sock >= 0) && !svc_register(transp, SM_PROG, SM_VERS, sm_prog_1, IPPROTO_UDP)) {
log(LOG_WARNING, "unable to register IPv6 (SM_PROG, SM_VERS, UDP)");
svc_destroy(transp);
close(statudp6sock);
statudp6sock = -1;
}
}
if (config.tcp) {
if ((stattcpsock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
log(LOG_ERR, "can't create TCP IPv4 socket: %s (%d)", strerror(errno), errno);
if (stattcpsock >= 0) {
if (setsockopt(stattcpsock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
log(LOG_WARNING, "setsockopt TCP IPv4 SO_REUSEADDR: %s (%d)", strerror(errno), errno);
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
sin->sin_port = htons(config.port);
sin->sin_len = sizeof(*sin);
if (bindresvport_sa(stattcpsock, (struct sockaddr *)sin) < 0) {
log(LOG_ERR, "can't bind TCP IPv4 addr: %s (%d)", strerror(errno), errno);
close(stattcpsock);
stattcpsock = -1;
}
}
if (stattcpsock >= 0) {
socklen = sizeof(*sin);
if (getsockname(stattcpsock, (struct sockaddr *)sin, &socklen)) {
log(LOG_ERR, "can't getsockname on TCP IPv4 socket: %s (%d)", strerror(errno), errno);
close(stattcpsock);
stattcpsock = -1;
} else {
tcpport = ntohs(sin->sin_port);
}
}
if ((stattcpsock >= 0) && ((transp = svctcp_create(stattcpsock, 0, 0)) == NULL)) {
log(LOG_WARNING, "cannot create TCP IPv4 service");
close(stattcpsock);
stattcpsock = -1;
}
if ((stattcpsock >= 0) && !svc_register(transp, SM_PROG, SM_VERS, sm_prog_1, IPPROTO_TCP)) {
log(LOG_WARNING, "unable to register IPv4 (SM_PROG, SM_VERS, TCP)");
svc_destroy(transp);
close(stattcpsock);
stattcpsock = -1;
}
if ((stattcp6sock = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
log(LOG_ERR, "can't create TCP IPv6 socket: %s (%d)", strerror(errno), errno);
if (stattcp6sock >= 0) {
if (setsockopt(stattcp6sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
log(LOG_WARNING, "setsockopt TCP IPv6 SO_REUSEADDR: %s (%d)", strerror(errno), errno);
setsockopt(stattcp6sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = in6addr_any;
sin6->sin6_port = htons(config.port);
sin6->sin6_len = sizeof(*sin6);
if (bindresvport_sa(stattcp6sock, (struct sockaddr *)sin6) < 0) {
log(LOG_ERR, "can't bind TCP IPv6 addr: %s (%d)", strerror(errno), errno);
close(stattcp6sock);
stattcp6sock = -1;
}
}
if (stattcp6sock >= 0) {
socklen = sizeof(*sin6);
if (getsockname(stattcp6sock, (struct sockaddr *)sin6, &socklen)) {
log(LOG_ERR, "can't getsockname on TCP IPv6 socket: %s (%d)", strerror(errno), errno);
close(stattcp6sock);
stattcp6sock = -1;
} else {
tcp6port = ntohs(sin6->sin6_port);
}
}
if ((stattcp6sock >= 0) && ((transp = svctcp_create(stattcp6sock, 0, 0)) == NULL)) {
log(LOG_WARNING, "cannot create TCP IPv6 service");
close(stattcp6sock);
stattcp6sock = -1;
}
if ((stattcp6sock >= 0) && !svc_register(transp, SM_PROG, SM_VERS, sm_prog_1, IPPROTO_TCP)) {
log(LOG_WARNING, "unable to register IPv6 (SM_PROG, SM_VERS, TCP)");
svc_destroy(transp);
close(stattcp6sock);
stattcp6sock = -1;
}
}
if ((statudp6sock < 0) && (stattcp6sock < 0))
log(LOG_WARNING, "Can't create NSM IPv6 sockets");
if ((statudpsock < 0) && (stattcpsock < 0))
log(LOG_WARNING, "Can't create NSM IPv4 sockets");
if ((statudp6sock < 0) && (stattcp6sock < 0) &&
(statudpsock < 0) && (stattcpsock < 0)) {
log(LOG_ERR, "Can't create any NSM sockets!");
exit(1);
}
sa.sa_handler = handle_sigchld;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGCHLD);
sa.sa_flags = SA_RESTART;
sigaction(SIGCHLD, &sa, NULL);
svc_run();
exit(1);
}
static void
usage(void)
{
fprintf(stderr, "usage: rpc.statd [-d] [ -n | -l | -L | -N hostname ]\n");
exit(1);
}
pid_t
get_statd_pid(void)
{
char pidbuf[128], *pidend;
int fd, len, rv;
pid_t pid;
struct flock lock;
if ((fd = open(_PATH_STATD_PID, O_RDONLY)) < 0) {
DEBUG(9, "%s: %s (%d)", _PATH_STATD_PID, strerror(errno), errno);
return (0);
}
len = sizeof(pidbuf) - 1;
if ((len = read(fd, pidbuf, len)) < 0) {
DEBUG(9, "%s: %s (%d)", _PATH_STATD_PID, strerror(errno), errno);
return (0);
}
pidbuf[len] = '\0';
pid = strtol(pidbuf, &pidend, 10);
if (!len || (pid < 1)) {
DEBUG(1, "%s: bogus pid: %s", _PATH_STATD_PID, pidbuf);
return (0);
}
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
rv = fcntl(fd, F_GETLK, &lock);
close(fd);
if (rv != 0) {
DEBUG(1, "%s: fcntl: %s (%d)", _PATH_STATD_PID, strerror(errno), errno);
return (0);
} else if (lock.l_type == F_UNLCK) {
DEBUG(8, "%s: not locked\n", _PATH_STATD_PID);
return (0);
}
return (pid);
}
pid_t
get_statd_notify_pid(void)
{
char pidbuf[128], *pidend;
int fd, len, rv;
pid_t pid;
struct flock lock;
if ((fd = open(_PATH_STATD_NOTIFY_PID, O_RDONLY)) < 0) {
DEBUG(9, "%s: %s (%d)", _PATH_STATD_NOTIFY_PID, strerror(errno), errno);
return (0);
}
len = sizeof(pidbuf) - 1;
if ((len = read(fd, pidbuf, len)) < 0) {
DEBUG(9, "%s: %s (%d)", _PATH_STATD_NOTIFY_PID, strerror(errno), errno);
return (0);
}
pidbuf[len] = '\0';
pid = strtol(pidbuf, &pidend, 10);
if (!len || (pid < 1)) {
DEBUG(1, "%s: bogus pid: %s", _PATH_STATD_NOTIFY_PID, pidbuf);
return (0);
}
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
rv = fcntl(fd, F_GETLK, &lock);
close(fd);
if (rv != 0) {
DEBUG(1, "%s: fcntl: %s (%d)", _PATH_STATD_NOTIFY_PID, strerror(errno), errno);
return (0);
} else if (lock.l_type == F_UNLCK) {
DEBUG(8, "%s: not locked\n", _PATH_STATD_NOTIFY_PID);
return (0);
}
return (pid);
}
static void
handle_sigchld(int sig __unused)
{
int pid, status;
pid = wait4(-1, &status, WNOHANG, (struct rusage *) 0);
if (!pid)
log(LOG_ERR, "Phantom SIGCHLD??");
else if (status == 0)
DEBUG(2, "Child %d exited OK", pid);
else
log(LOG_ERR, "Child %d failed with status %d", pid, WEXITSTATUS(status));
}
static void
cleanup(int sig)
{
if (statd_server) {
status_info->fh_state = htonl(ntohl(status_info->fh_state) + 1);
sync_file();
alarm(1);
rpcb_unset(NULL, SM_PROG, SM_VERS);
}
pidfile_remove(pfh);
exit((sig == SIGTERM) ? 0 : 1);
}
static int
config_read(struct nfs_conf_statd * conf)
{
FILE *f;
size_t len, linenum = 0;
char *line, *p, *key, *value;
long val;
if (!(f = fopen(_PATH_NFS_CONF, "r"))) {
if (errno != ENOENT)
log(LOG_WARNING, "%s", _PATH_NFS_CONF);
return (1);
}
for (; (line = fparseln(f, &len, &linenum, NULL, 0)); free(line)) {
if (len <= 0)
continue;
p = line + len - 1;
while ((p > line) && isspace(*p))
*p-- = '\0';
key = line;
while (isspace(*key))
key++;
value = p = strchr(line, '=');
if (p)
do { *p-- = '\0'; } while ((p > line) && isspace(*p));
if (value)
do { value++; } while (isspace(*value));
if (strncmp(key, "nfs.statd.", 10)) {
DEBUG(4, "%4ld %s=%s\n", linenum, key, value ? value : "");
continue;
}
val = !value ? 1 : strtol(value, NULL, 0);
DEBUG(1, "%4ld %s=%s (%d)\n", linenum, key, value ? value : "", val);
if (!strcmp(key, "nfs.statd.port")) {
if (value && val)
conf->port = val;
} else if (!strcmp(key, "nfs.statd.send_using_tcp")) {
conf->send_using_tcp = val;
} else if (!strcmp(key, "nfs.statd.simu_crash_allowed")) {
conf->simu_crash_allowed = val;
} else if (!strcmp(key, "nfs.statd.tcp")) {
conf->tcp = val;
} else if (!strcmp(key, "nfs.statd.udp")) {
conf->udp = val;
} else if (!strcmp(key, "nfs.statd.verbose")) {
conf->verbose = val;
} else {
DEBUG(2, "ignoring unknown config value: %4ld %s=%s\n", linenum, key, value ? value : "");
}
}
fclose(f);
return (0);
}
static int
safe_exec(char *const argv[], int silent)
{
posix_spawn_file_actions_t psfileact, *psfileactp = NULL;
pid_t pid;
int status;
extern char **environ;
if (silent) {
psfileactp = &psfileact;
if ((status = posix_spawn_file_actions_init(psfileactp))) {
log(LOG_ERR, "spawn init of %s failed: %s (%d)", argv[0], strerror(status), status);
return (1);
}
posix_spawn_file_actions_addopen(psfileactp, STDIN_FILENO, "/dev/null", O_RDONLY, 0);
posix_spawn_file_actions_addopen(psfileactp, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
posix_spawn_file_actions_addopen(psfileactp, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
}
status = posix_spawn(&pid, argv[0], psfileactp, NULL, argv, environ);
if (psfileactp)
posix_spawn_file_actions_destroy(psfileactp);
if (status) {
log(LOG_ERR, "spawn of %s failed: %s (%d)", argv[0], strerror(status), status);
return (1);
}
while ((waitpid(pid, &status, 0) == -1) && (errno == EINTR))
usleep(1000);
if (WIFSIGNALED(status)) {
log(LOG_ERR, "%s aborted by signal %d", argv[0], WTERMSIG(status));
return (1);
} else if (WIFSTOPPED(status)) {
log(LOG_ERR, "%s stopped by signal %d ?", argv[0], WSTOPSIG(status));
return (1);
} else if (WEXITSTATUS(status) && !silent) {
log(LOG_ERR, "%s exited with status %d", argv[0], WEXITSTATUS(status));
}
return (WEXITSTATUS(status));
}
int
statd_notify_load(void)
{
const char *const args[] = {_PATH_LAUNCHCTL, "load", _PATH_STATD_NOTIFY_PLIST, NULL};
return safe_exec((char *const *) args, 1);
}
int
statd_notify_is_loaded(void)
{
launch_data_t msg, resp;
int rv = 0;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
if (!msg)
return (0);
launch_data_dict_insert(msg, launch_data_new_string(_STATD_NOTIFY_SERVICE_LABEL), LAUNCH_KEY_GETJOB);
resp = launch_msg(msg);
if (resp) {
if (launch_data_get_type(resp) == LAUNCH_DATA_DICTIONARY)
rv = 1;
launch_data_free(resp);
} else {
log(LOG_ERR, "launch_msg(): %m");
}
launch_data_free(msg);
return (rv);
}
int
statd_notify_start(void)
{
launch_data_t msg, resp;
int rv = 1;
msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
if (!msg)
return (1);
launch_data_dict_insert(msg, launch_data_new_string(_STATD_NOTIFY_SERVICE_LABEL), LAUNCH_KEY_STARTJOB);
resp = launch_msg(msg);
if (!resp) {
rv = errno;
} else {
if (launch_data_get_type(resp) == LAUNCH_DATA_ERRNO)
rv = launch_data_get_errno(resp);
launch_data_free(resp);
}
launch_data_free(msg);
return (rv);
}
void
SYSLOG(int pri, const char *fmt,...)
{
va_list ap;
if (pri > LOG_LEVEL)
return;
va_start(ap, fmt);
if (log_to_stderr) {
vfprintf(stderr, fmt, ap);
fputc('\n', stderr);
fflush(stderr);
} else {
vsyslog(pri, fmt, ap);
}
va_end(ap);
}
static int
addrinfo_cmp(struct addrinfo *a1, struct addrinfo *a2)
{
if (a1->ai_family != a2->ai_family)
return (a1->ai_family - a2->ai_family);
if (a1->ai_addrlen != a2->ai_addrlen)
return (a1->ai_addrlen - a2->ai_addrlen);
return bcmp(a1->ai_addr, a2->ai_addr, a1->ai_addrlen);
}
int
getaddresslist(const char *name, struct addrinfo **ailistp)
{
struct addrinfo aihints, *ailist = NULL, *ai, *aiprev, *ainext, *aidiscard;
char namebuf[NI_MAXHOST];
const char *hostname;
hostname = name;
if ((hostname[0] == '[') && (hostname[strlen(hostname)-1] == ']')) {
strlcpy(namebuf, hostname+1, sizeof(namebuf));
namebuf[strlen(namebuf)-1] = '\0';
hostname = namebuf;
}
bzero(&aihints, sizeof(aihints));
aihints.ai_flags = AI_ADDRCONFIG;
if (getaddrinfo(hostname, NULL, &aihints, &ailist))
return (ENOENT);
aidiscard = NULL;
aiprev = NULL;
for (ai = ailist; ai; ai = ainext) {
ainext = ai->ai_next;
if ((ai->ai_family != AF_INET) && (ai->ai_family != AF_INET6))
goto discard;
if (aiprev && (ai->ai_socktype != aiprev->ai_socktype) &&
!addrinfo_cmp(aiprev, ai))
goto discard;
aiprev = ai;
continue;
discard:
if (aiprev)
aiprev->ai_next = ai->ai_next;
else
ailist = ai->ai_next;
ai->ai_next = aidiscard;
aidiscard = ai;
}
if (aidiscard)
freeaddrinfo(aidiscard);
*ailistp = ailist;
return (0);
}