]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/statd/sm-notify.c
Make that last patch compile...
[nfs-utils.git] / utils / statd / sm-notify.c
index 96daa1ed0023c13064f356011643e3a8960c4f29..98c03f9d142656906d36bb7e558851d6b5328c6c 100644 (file)
 #include <stdarg.h>
 #include <netdb.h>
 #include <errno.h>
+#include <grp.h>
 
 #ifndef BASEDIR
-#define BASEDIR                "/var/lib/nfs"
+# ifdef NFS_STATEDIR
+#  define BASEDIR              NFS_STATEDIR
+# else
+#  define BASEDIR              "/var/lib/nfs"
+# endif
 #endif
 
 #define DEFAULT_SM_STATE_PATH  BASEDIR "/state"
@@ -51,6 +56,7 @@ struct nsm_host {
        char *                  name;
        char *                  path;
        nsm_address             addr;
+       struct addrinfo         *ai;
        time_t                  last_used;
        time_t                  send_next;
        unsigned int            timeout;
@@ -76,11 +82,13 @@ static void         backup_hosts(const char *, const char *);
 static void            get_hosts(const char *);
 static void            insert_host(struct nsm_host *);
 struct nsm_host *      find_host(uint32_t);
-static int             addr_parse(int, const char *, nsm_address *);
 static int             addr_get_port(nsm_address *);
 static void            addr_set_port(nsm_address *, int);
-static int             host_lookup(int, const char *, nsm_address *);
+static struct addrinfo *host_lookup(int, const char *);
 void                   nsm_log(int fac, const char *fmt, ...);
+static int             record_pid();
+static void            drop_privs(void);
+static void set_kernel_nsm_state(int state);
 
 static struct nsm_host *       hosts = NULL;
 
@@ -88,9 +96,13 @@ int
 main(int argc, char **argv)
 {
        int     c;
+       int     force = 0;
 
-       while ((c = getopt(argc, argv, "dm:np:v:qP:")) != -1) {
+       while ((c = getopt(argc, argv, "dm:np:v:qP:f")) != -1) {
                switch (c) {
+               case 'f':
+                       force = 1;
+                       break;
                case 'd':
                        opt_debug++;
                        break;
@@ -132,10 +144,18 @@ main(int argc, char **argv)
        }
 
        if (optind < argc) {
-usage:         fprintf(stderr, "sm-notify [-d]\n");
+usage:         fprintf(stderr,
+                       "Usage: sm-notify [-dfq] [-m max-retry-minutes] [-p srcport]\n"
+                       "            [-P /path/to/state/directory] [-v my_host_name]\n");
                return 1;
        }
 
+       if (strcmp(_SM_BASE_PATH, BASEDIR) == 0) {
+               if (record_pid() == 0 && force == 0 && opt_update_state == 1)
+                       /* already run, don't try again */
+                       exit(0);
+       }
+
        if (opt_srcaddr) {
                strncpy(nsm_hostname, opt_srcaddr, sizeof(nsm_hostname)-1);
        } else
@@ -147,6 +167,10 @@ usage:             fprintf(stderr, "sm-notify [-d]\n");
        backup_hosts(_SM_DIR_PATH, _SM_BAK_PATH);
        get_hosts(_SM_BAK_PATH);
 
+       /* Get and update the NSM state. This will call sync() */
+       nsm_state = nsm_get_state(opt_update_state);
+       set_kernel_nsm_state(nsm_state);
+
        if (!opt_debug) {
                if (!opt_quiet)
                        printf("Backgrounding to notify hosts...\n");
@@ -165,9 +189,6 @@ usage:              fprintf(stderr, "sm-notify [-d]\n");
                close(2);
        }
 
-       /* Get and update the NSM state. This will call sync() */
-       nsm_state = nsm_get_state(opt_update_state);
-
        notify();
 
        if (hosts) {
@@ -194,7 +215,9 @@ notify(void)
        nsm_address local_addr;
        time_t  failtime = 0;
        int     sock = -1;
+       int retry_cnt = 0;
 
+ retry:
        sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock < 0) {
                perror("socket");
@@ -207,13 +230,14 @@ notify(void)
 
        /* Bind source IP if provided on command line */
        if (opt_srcaddr) {
-               if (!addr_parse(AF_INET, opt_srcaddr, &local_addr)
-                && !host_lookup(AF_INET, opt_srcaddr, &local_addr)) {
+               struct addrinfo *ai = host_lookup(AF_INET, opt_srcaddr);
+               if (!ai) {
                        nsm_log(LOG_WARNING,
                                "Not a valid hostname or address: \"%s\"\n",
                                opt_srcaddr);
                        exit(1);
                }
+               memcpy(&local_addr, ai->ai_addr, ai->ai_addrlen);
                /* We know it's IPv4 at this point */
        }
 
@@ -226,12 +250,23 @@ notify(void)
                        exit(1);
                }
        } else {
-               (void) bindresvport(sock, (struct sockaddr_in *) &local_addr);
+               struct servent *se;
+               struct sockaddr_in *sin = (struct sockaddr_in *)&local_addr;
+               (void) bindresvport(sock, sin);
+               /* try to avoid known ports */
+               se = getservbyport(sin->sin_port, "udp");
+               if (se && retry_cnt < 100) {
+                       retry_cnt++;
+                       close(sock);
+                       goto retry;
+               }
        }
 
        if (opt_max_retry)
                failtime = time(NULL) + opt_max_retry;
 
+       drop_privs();
+
        while (hosts) {
                struct pollfd   pfd;
                time_t          now = time(NULL);
@@ -303,9 +338,19 @@ notify_host(int sock, struct nsm_host *host)
        *p++ = htonl(2);
 
        /* If we retransmitted 4 times, reset the port to force
-        * a new portmap lookup (in case statd was restarted)
+        * a new portmap lookup (in case statd was restarted).
+        * We also rotate through multiple IP addresses at this
+        * point.
         */
        if (host->retries >= 4) {
+               struct addrinfo *hold = host->ai;
+               struct addrinfo **next = &host->ai;
+               *next = hold->ai_next;
+               while ( *next )
+                       next = & (*next)->ai_next;
+               *next = hold;
+               hold->ai_next = NULL;
+               memcpy(&host->addr, hold->ai_addr, hold->ai_addrlen);
                addr_set_port(&host->addr, 0);
                host->retries = 0;
        }
@@ -418,6 +463,7 @@ recv_reply(int sock)
                        free(hp->name);
                        free(hp->path);
                        free(hp);
+                       freeaddrinfo(hp->ai);
                        return;
                }
        }
@@ -483,9 +529,11 @@ get_hosts(const char *dirname)
                        host = calloc(1, sizeof(*host));
 
                snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
-               if (!addr_parse(AF_INET, de->d_name, &host->addr)
-                && !addr_parse(AF_INET6, de->d_name, &host->addr)
-                && !host_lookup(AF_INET, de->d_name, &host->addr)) {
+               if (stat(path, &stb) < 0)
+                       continue;
+
+               host->ai = host_lookup(AF_UNSPEC, de->d_name);
+               if (! host->ai) {
                        nsm_log(LOG_WARNING,
                                "%s doesn't seem to be a valid address, skipped",
                                de->d_name);
@@ -493,12 +541,11 @@ get_hosts(const char *dirname)
                        continue;
                }
 
-               if (stat(path, &stb) < 0)
-                       continue;
                host->last_used = stb.st_mtime;
                host->timeout = NSM_TIMEOUT;
                host->path = strdup(path);
                host->name = strdup(de->d_name);
+               host->retries = 100; /* force address retry */
 
                insert_host(host);
                host = NULL;
@@ -616,22 +663,6 @@ nsm_get_state(int update)
 /*
  * Address handling utilities
  */
-static int
-addr_parse(int af, const char *name, nsm_address *addr)
-{
-       void    *ptr;
-
-       if (af == AF_INET)
-               ptr = &((struct sockaddr_in *) addr)->sin_addr;
-       else if (af == AF_INET6)
-               ptr = &((struct sockaddr_in6 *) addr)->sin6_addr;
-       else
-               return 0;
-       if (inet_pton(af, name, ptr) <= 0)
-               return 0;
-       ((struct sockaddr *) addr)->sa_family = af;
-       return 1;
-}
 
 int
 addr_get_port(nsm_address *addr)
@@ -657,25 +688,19 @@ addr_set_port(nsm_address *addr, int port)
        }
 }
 
-static int
-host_lookup(int af, const char *name, nsm_address *addr)
+static struct addrinfo *
+host_lookup(int af, const char *name)
 {
        struct addrinfo hints, *ai;
-       int okay = 0;
 
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = af;
+       hints.ai_protocol = IPPROTO_UDP;
 
        if (getaddrinfo(name, NULL, &hints, &ai) != 0)
-               return 0;
+               return NULL;
 
-       if (ai->ai_addrlen < sizeof(*addr)) {
-               memcpy(addr, ai->ai_addr, ai->ai_addrlen);
-               okay = 1;
-       }
-
-       freeaddrinfo(ai);
-       return okay;
+       return ai;
 }
 
 /*
@@ -698,3 +723,64 @@ nsm_log(int fac, const char *fmt, ...)
        }
        va_end(ap);
 }
+
+/*
+ * Record pid in /var/run/sm-notify.pid
+ * This file should remain until a reboot, even if the
+ * program exits.
+ * If file already exists, fail.
+ */
+static int record_pid()
+{
+       char pid[20];
+       int fd;
+
+       snprintf(pid, 20, "%d\n", getpid());
+       fd = open("/var/run/sm-notify.pid", O_CREAT|O_EXCL|O_WRONLY, 0600);
+       if (fd < 0)
+               return 0;
+       write(fd, pid, strlen(pid));
+       close(fd);
+       return 1;
+}
+
+/* Drop privileges to match owner of state-directory
+ * (in case a reply triggers some unknown bug).
+ */
+static void drop_privs(void)
+{
+       struct stat st;
+
+       if (stat(_SM_DIR_PATH, &st) == -1 &&
+           stat(_SM_BASE_PATH, &st) == -1) {
+               st.st_uid = 0;
+               st.st_gid = 0;
+       }
+
+       if (st.st_uid == 0) {
+               nsm_log(LOG_WARNING,
+                       "sm-notify running as root. chown %s to choose different user\n",
+                   _SM_DIR_PATH);
+               return;
+       }
+
+       setgroups(0, NULL);
+       if (setgid(st.st_gid) == -1
+           || setuid(st.st_uid) == -1) {
+               nsm_log(LOG_ERR, "Fail to drop privileges");
+               exit(1);
+       }
+}
+
+static void set_kernel_nsm_state(int state)
+{
+       int fd;
+
+       fd = open("/proc/sys/fs/nfs/nsm_local_state",O_WRONLY);
+       if (fd >= 0) {
+               char buf[20];
+               snprintf(buf, sizeof(buf), "%d", state);
+               write(fd, buf, strlen(buf));
+               close(fd);
+       }
+}