]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/statd/sm-notify.c
statd - keep persistent state in sm/* files.
[nfs-utils.git] / utils / statd / sm-notify.c
1 /*
2  * Send NSM notify calls to all hosts listed in /var/lib/sm
3  *
4  * Copyright (C) 2004-2006 Olaf Kirch <okir@suse.de>
5  */
6
7 #include <sys/types.h>
8 #include <sys/socket.h>
9 #include <sys/stat.h>
10 #include <sys/poll.h>
11 #include <sys/param.h>
12 #include <sys/syslog.h>
13 #include <arpa/inet.h>
14 #include <dirent.h>
15 #include <time.h>
16 #include <stdio.h>
17 #include <getopt.h>
18 #include <stdlib.h>
19 #include <fcntl.h>
20 #include <unistd.h>
21 #include <string.h>
22 #include <stdarg.h>
23 #include <netdb.h>
24 #include <errno.h>
25 #include <grp.h>
26
27 #ifndef BASEDIR
28 # ifdef NFS_STATEDIR
29 #  define BASEDIR               NFS_STATEDIR
30 # else
31 #  define BASEDIR               "/var/lib/nfs"
32 # endif
33 #endif
34
35 #define DEFAULT_SM_STATE_PATH   BASEDIR "/state"
36 #define DEFAULT_SM_DIR_PATH     BASEDIR "/sm"
37 #define DEFAULT_SM_BAK_PATH     DEFAULT_SM_DIR_PATH ".bak"
38
39 char *_SM_BASE_PATH = BASEDIR;
40 char *_SM_STATE_PATH = DEFAULT_SM_STATE_PATH;
41 char *_SM_DIR_PATH = DEFAULT_SM_DIR_PATH;
42 char *_SM_BAK_PATH = DEFAULT_SM_BAK_PATH;
43
44 #define NSM_PROG        100024
45 #define NSM_PROGRAM     100024
46 #define NSM_VERSION     1
47 #define NSM_TIMEOUT     2
48 #define NSM_NOTIFY      6
49 #define NSM_MAX_TIMEOUT 120     /* don't make this too big */
50 #define MAXMSGSIZE      256
51
52 typedef struct sockaddr_storage nsm_address;
53
54 struct nsm_host {
55         struct nsm_host *       next;
56         char *                  name;
57         char *                  path;
58         nsm_address             addr;
59         time_t                  last_used;
60         time_t                  send_next;
61         unsigned int            timeout;
62         unsigned int            retries;
63         unsigned int            xid;
64 };
65
66 static char             nsm_hostname[256];
67 static uint32_t         nsm_state;
68 static int              opt_debug = 0;
69 static int              opt_quiet = 0;
70 static int              opt_update_state = 1;
71 static unsigned int     opt_max_retry = 15 * 60;
72 static char *           opt_srcaddr = 0;
73 static uint16_t         opt_srcport = 0;
74 static int              log_syslog = 0;
75
76 static unsigned int     nsm_get_state(int);
77 static void             notify(void);
78 static void             notify_host(int, struct nsm_host *);
79 static void             recv_reply(int);
80 static void             backup_hosts(const char *, const char *);
81 static void             get_hosts(const char *);
82 static void             insert_host(struct nsm_host *);
83 struct nsm_host *       find_host(uint32_t);
84 static int              addr_get_port(nsm_address *);
85 static void             addr_set_port(nsm_address *, int);
86 static int              host_lookup(int, const char *, nsm_address *);
87 void                    nsm_log(int fac, const char *fmt, ...);
88 static int              record_pid();
89 static void             drop_privs(void);
90
91 static struct nsm_host *        hosts = NULL;
92
93 int
94 main(int argc, char **argv)
95 {
96         int     c;
97         int     force = 0;
98
99         while ((c = getopt(argc, argv, "dm:np:v:qP:f")) != -1) {
100                 switch (c) {
101                 case 'f':
102                         force = 1;
103                         break;
104                 case 'd':
105                         opt_debug++;
106                         break;
107                 case 'm':
108                         opt_max_retry = atoi(optarg) * 60;
109                         break;
110                 case 'n':
111                         opt_update_state = 0;
112                         break;
113                 case 'p':
114                         opt_srcport = atoi(optarg);
115                         break;
116                 case 'v':
117                         opt_srcaddr = optarg;
118                         break;
119                 case 'q':
120                         opt_quiet = 1;
121                         break;
122                 case 'P':
123                         _SM_BASE_PATH = strdup(optarg);
124                         _SM_STATE_PATH = malloc(strlen(optarg)+1+sizeof("state"));
125                         _SM_DIR_PATH = malloc(strlen(optarg)+1+sizeof("sm"));
126                         _SM_BAK_PATH = malloc(strlen(optarg)+1+sizeof("sm.bak"));
127                         if (_SM_BASE_PATH == NULL ||
128                             _SM_STATE_PATH == NULL ||
129                             _SM_DIR_PATH == NULL ||
130                             _SM_BAK_PATH == NULL) {
131                                 nsm_log(LOG_WARNING, "unable to allocate memory");
132                                 exit(1);
133                         }
134                         strcat(strcpy(_SM_STATE_PATH, _SM_BASE_PATH), "/state");
135                         strcat(strcpy(_SM_DIR_PATH, _SM_BASE_PATH), "/sm");
136                         strcat(strcpy(_SM_BAK_PATH, _SM_BASE_PATH), "/sm.bak");
137                         break;
138
139                 default:
140                         goto usage;
141                 }
142         }
143
144         if (optind < argc) {
145 usage:          fprintf(stderr,
146                         "Usage: sm-notify [-dfq] [-m max-retry-minutes] [-p srcport]\n"
147                         "            [-P /path/to/state/directory] [-v my_host_name]\n");
148                 return 1;
149         }
150
151         if (strcmp(_SM_BASE_PATH, BASEDIR) == 0) {
152                 if (record_pid() == 0 && force == 0 && opt_update_state == 0)
153                         /* already run, don't try again */
154                         exit(0);
155         }
156
157         if (opt_srcaddr) {
158                 strncpy(nsm_hostname, opt_srcaddr, sizeof(nsm_hostname)-1);
159         } else
160         if (gethostname(nsm_hostname, sizeof(nsm_hostname)) < 0) {
161                 perror("gethostname");
162                 return 1;
163         }
164
165         backup_hosts(_SM_DIR_PATH, _SM_BAK_PATH);
166         get_hosts(_SM_BAK_PATH);
167
168         if (!opt_debug) {
169                 if (!opt_quiet)
170                         printf("Backgrounding to notify hosts...\n");
171
172                 openlog("sm-notify", LOG_PID, LOG_DAEMON);
173                 log_syslog = 1;
174
175                 if (daemon(0, 0) < 0) {
176                         nsm_log(LOG_WARNING, "unable to background: %s",
177                                         strerror(errno));
178                         return 1;
179                 }
180
181                 close(0);
182                 close(1);
183                 close(2);
184         }
185
186         /* Get and update the NSM state. This will call sync() */
187         nsm_state = nsm_get_state(opt_update_state);
188
189         notify();
190
191         if (hosts) {
192                 struct nsm_host *hp;
193
194                 while ((hp = hosts) != 0) {
195                         hosts = hp->next;
196                         nsm_log(LOG_NOTICE,
197                                 "Unable to notify %s, giving up",
198                                 hp->name);
199                 }
200                 return 1;
201         }
202
203         return 0;
204 }
205
206 /*
207  * Notify hosts
208  */
209 void
210 notify(void)
211 {
212         nsm_address local_addr;
213         time_t  failtime = 0;
214         int     sock = -1;
215
216         sock = socket(AF_INET, SOCK_DGRAM, 0);
217         if (sock < 0) {
218                 perror("socket");
219                 exit(1);
220         }
221         fcntl(sock, F_SETFL, O_NONBLOCK);
222
223         memset(&local_addr, 0, sizeof(local_addr));
224         local_addr.ss_family = AF_INET; /* Default to IPv4 */
225
226         /* Bind source IP if provided on command line */
227         if (opt_srcaddr) {
228                 if (!host_lookup(AF_INET, opt_srcaddr, &local_addr)) {
229                         nsm_log(LOG_WARNING,
230                                 "Not a valid hostname or address: \"%s\"\n",
231                                 opt_srcaddr);
232                         exit(1);
233                 }
234                 /* We know it's IPv4 at this point */
235         }
236
237         /* Use source port if provided on the command line,
238          * otherwise use bindresvport */
239         if (opt_srcport) {
240                 addr_set_port(&local_addr, opt_srcport);
241                 if (bind(sock, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0) {
242                         perror("bind");
243                         exit(1);
244                 }
245         } else {
246                 (void) bindresvport(sock, (struct sockaddr_in *) &local_addr);
247         }
248
249         if (opt_max_retry)
250                 failtime = time(NULL) + opt_max_retry;
251
252         drop_privs();
253
254         while (hosts) {
255                 struct pollfd   pfd;
256                 time_t          now = time(NULL);
257                 unsigned int    sent = 0;
258                 struct nsm_host *hp;
259                 long            wait;
260
261                 if (failtime && now >= failtime)
262                         break;
263
264                 while ((wait = hosts->send_next - now) <= 0) {
265                         /* Never send more than 10 packets at once */
266                         if (sent++ >= 10)
267                                 break;
268
269                         /* Remove queue head */
270                         hp = hosts;
271                         hosts = hp->next;
272
273                         notify_host(sock, hp);
274
275                         /* Set the timeout for this call, using an
276                            exponential timeout strategy */
277                         wait = hp->timeout;
278                         if ((hp->timeout <<= 1) > NSM_MAX_TIMEOUT)
279                                 hp->timeout = NSM_MAX_TIMEOUT;
280                         hp->send_next = now + wait;
281                         hp->retries++;
282
283                         insert_host(hp);
284                 }
285
286                 nsm_log(LOG_DEBUG, "Host %s due in %ld seconds",
287                                 hosts->name, wait);
288
289                 pfd.fd = sock;
290                 pfd.events = POLLIN;
291
292                 wait *= 1000;
293                 if (wait < 100)
294                         wait = 100;
295                 if (poll(&pfd, 1, wait) != 1)
296                         continue;
297
298                 recv_reply(sock);
299         }
300 }
301
302 /*
303  * Send notification to a single host
304  */
305 void
306 notify_host(int sock, struct nsm_host *host)
307 {
308         static unsigned int     xid = 0;
309         nsm_address             dest;
310         uint32_t                msgbuf[MAXMSGSIZE], *p;
311         unsigned int            len;
312
313         if (!xid)
314                 xid = getpid() + time(NULL);
315         if (!host->xid)
316                 host->xid = xid++;
317
318         memset(msgbuf, 0, sizeof(msgbuf));
319         p = msgbuf;
320         *p++ = htonl(host->xid);
321         *p++ = 0;
322         *p++ = htonl(2);
323
324         /* If we retransmitted 4 times, reset the port to force
325          * a new portmap lookup (in case statd was restarted)
326          */
327         if (host->retries >= 4) {
328                 addr_set_port(&host->addr, 0);
329                 host->retries = 0;
330         }
331
332         dest = host->addr;
333         if (addr_get_port(&dest) == 0) {
334                 /* Build a PMAP packet */
335                 nsm_log(LOG_DEBUG, "Sending portmap query to %s", host->name);
336
337                 addr_set_port(&dest, 111);
338                 *p++ = htonl(100000);
339                 *p++ = htonl(2);
340                 *p++ = htonl(3);
341
342                 /* Auth and verf */
343                 *p++ = 0; *p++ = 0;
344                 *p++ = 0; *p++ = 0;
345
346                 *p++ = htonl(NSM_PROGRAM);
347                 *p++ = htonl(NSM_VERSION);
348                 *p++ = htonl(IPPROTO_UDP);
349                 *p++ = 0;
350         } else {
351                 /* Build an SM_NOTIFY packet */
352                 nsm_log(LOG_DEBUG, "Sending SM_NOTIFY to %s", host->name);
353
354                 *p++ = htonl(NSM_PROGRAM);
355                 *p++ = htonl(NSM_VERSION);
356                 *p++ = htonl(NSM_NOTIFY);
357
358                 /* Auth and verf */
359                 *p++ = 0; *p++ = 0;
360                 *p++ = 0; *p++ = 0;
361
362                 /* state change */
363                 len = strlen(nsm_hostname);
364                 *p++ = htonl(len);
365                 memcpy(p, nsm_hostname, len);
366                 p += (len + 3) >> 2;
367                 *p++ = htonl(nsm_state);
368         }
369         len = (p - msgbuf) << 2;
370
371         sendto(sock, msgbuf, len, 0, (struct sockaddr *) &dest, sizeof(dest));
372 }
373
374 /*
375  * Receive reply from remote host
376  */
377 void
378 recv_reply(int sock)
379 {
380         struct nsm_host *hp;
381         uint32_t        msgbuf[MAXMSGSIZE], *p, *end;
382         uint32_t        xid;
383         int             res;
384
385         res = recv(sock, msgbuf, sizeof(msgbuf), 0);
386         if (res < 0)
387                 return;
388
389         nsm_log(LOG_DEBUG, "Received packet...");
390
391         p = msgbuf;
392         end = p + (res >> 2);
393
394         xid = ntohl(*p++);
395         if (*p++ != htonl(1)    /* must be REPLY */
396          || *p++ != htonl(0)    /* must be ACCEPTED */
397          || *p++ != htonl(0)    /* must be NULL verifier */
398          || *p++ != htonl(0)
399          || *p++ != htonl(0))   /* must be SUCCESS */
400                 return;
401
402         /* Before we look at the data, find the host struct for
403            this reply */
404         if ((hp = find_host(xid)) == NULL)
405                 return;
406
407         if (addr_get_port(&hp->addr) == 0) {
408                 /* This was a portmap request */
409                 unsigned int    port;
410
411                 port = ntohl(*p++);
412                 if (p > end)
413                         goto fail;
414
415                 hp->send_next = time(NULL);
416                 if (port == 0) {
417                         /* No binding for statd. Delay the next
418                          * portmap query for max timeout */
419                         nsm_log(LOG_DEBUG, "No statd on %s", hp->name);
420                         hp->timeout = NSM_MAX_TIMEOUT;
421                         hp->send_next += NSM_MAX_TIMEOUT;
422                 } else {
423                         addr_set_port(&hp->addr, port);
424                         if (hp->timeout >= NSM_MAX_TIMEOUT / 4)
425                                 hp->timeout = NSM_MAX_TIMEOUT / 4;
426                 }
427                 hp->xid = 0;
428         } else {
429                 /* Successful NOTIFY call. Server returns void,
430                  * so nothing we need to do here (except
431                  * check that we didn't read past the end of the
432                  * packet)
433                  */
434                 if (p <= end) {
435                         nsm_log(LOG_DEBUG, "Host %s notified successfully", hp->name);
436                         unlink(hp->path);
437                         free(hp->name);
438                         free(hp->path);
439                         free(hp);
440                         return;
441                 }
442         }
443
444 fail:   /* Re-insert the host */
445         insert_host(hp);
446 }
447
448 /*
449  * Back up all hosts from the sm directory to sm.bak
450  */
451 static void
452 backup_hosts(const char *dirname, const char *bakname)
453 {
454         struct dirent   *de;
455         DIR             *dir;
456
457         if (!(dir = opendir(dirname))) {
458                 perror(dirname);
459                 return;
460         }
461
462         while ((de = readdir(dir)) != NULL) {
463                 char    src[1024], dst[1024];
464
465                 if (de->d_name[0] == '.')
466                         continue;
467
468                 snprintf(src, sizeof(src), "%s/%s", dirname, de->d_name);
469                 snprintf(dst, sizeof(dst), "%s/%s", bakname, de->d_name);
470                 if (rename(src, dst) < 0) {
471                         nsm_log(LOG_WARNING,
472                                 "Failed to rename %s -> %s: %m",
473                                 src, dst);
474                 }
475         }
476         closedir(dir);
477 }
478
479 /*
480  * Get all entries from sm.bak and convert them to host names
481  */
482 static void
483 get_hosts(const char *dirname)
484 {
485         struct nsm_host *host;
486         struct dirent   *de;
487         DIR             *dir;
488
489         if (!(dir = opendir(dirname))) {
490                 perror(dirname);
491                 return;
492         }
493
494         host = NULL;
495         while ((de = readdir(dir)) != NULL) {
496                 struct stat     stb;
497                 char            path[1024];
498
499                 if (de->d_name[0] == '.')
500                         continue;
501                 if (host == NULL)
502                         host = calloc(1, sizeof(*host));
503
504                 snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
505                 if (!host_lookup(AF_UNSPEC, de->d_name, &host->addr)) {
506                         nsm_log(LOG_WARNING,
507                                 "%s doesn't seem to be a valid address, skipped",
508                                 de->d_name);
509                         unlink(path);
510                         continue;
511                 }
512
513                 if (stat(path, &stb) < 0)
514                         continue;
515                 host->last_used = stb.st_mtime;
516                 host->timeout = NSM_TIMEOUT;
517                 host->path = strdup(path);
518                 host->name = strdup(de->d_name);
519
520                 insert_host(host);
521                 host = NULL;
522         }
523         closedir(dir);
524
525         if (host)
526                 free(host);
527 }
528
529 /*
530  * Insert host into sorted list
531  */
532 void
533 insert_host(struct nsm_host *host)
534 {
535         struct nsm_host **where, *p;
536
537         where = &hosts;
538         while ((p = *where) != 0) {
539                 /* Sort in ascending order of timeout */
540                 if (host->send_next < p->send_next)
541                         break;
542                 /* If we have the same timeout, put the
543                  * most recently used host first.
544                  * This makes sure that "recent" hosts
545                  * get notified first.
546                  */
547                 if (host->send_next == p->send_next
548                  && host->last_used > p->last_used)
549                         break;
550                 where = &p->next;
551         }
552
553         host->next = *where;
554         *where = host;
555 }
556
557 /*
558  * Find host given the XID
559  */
560 struct nsm_host *
561 find_host(uint32_t xid)
562 {
563         struct nsm_host **where, *p;
564
565         where = &hosts;
566         while ((p = *where) != 0) {
567                 if (p->xid == xid) {
568                         *where = p->next;
569                         return p;
570                 }
571                 where = &p->next;
572         }
573         return NULL;
574 }
575
576
577 /*
578  * Retrieve the current NSM state
579  */
580 unsigned int
581 nsm_get_state(int update)
582 {
583         char            newfile[PATH_MAX];
584         int             fd, state;
585
586         if ((fd = open(_SM_STATE_PATH, O_RDONLY)) < 0) {
587                 if (!opt_quiet) {
588                         nsm_log(LOG_WARNING, "%s: %m", _SM_STATE_PATH);
589                         nsm_log(LOG_WARNING, "Creating %s, set initial state 1",
590                                 _SM_STATE_PATH);
591                 }
592                 state = 1;
593                 update = 1;
594         } else {
595                 if (read(fd, &state, sizeof(state)) != sizeof(state)) {
596                         nsm_log(LOG_WARNING,
597                                 "%s: bad file size, setting state = 1",
598                                 _SM_STATE_PATH);
599                         state = 1;
600                         update = 1;
601                 } else {
602                         if (!(state & 1))
603                                 state += 1;
604                 }
605                 close(fd);
606         }
607
608         if (update) {
609                 state += 2;
610                 snprintf(newfile, sizeof(newfile),
611                                 "%s.new", _SM_STATE_PATH);
612                 if ((fd = open(newfile, O_CREAT|O_WRONLY, 0644)) < 0) {
613                         nsm_log(LOG_WARNING, "Cannot create %s: %m", newfile);
614                         exit(1);
615                 }
616                 if (write(fd, &state, sizeof(state)) != sizeof(state)) {
617                         nsm_log(LOG_WARNING,
618                                 "Failed to write state to %s", newfile);
619                         exit(1);
620                 }
621                 close(fd);
622                 if (rename(newfile, _SM_STATE_PATH) < 0) {
623                         nsm_log(LOG_WARNING,
624                                 "Cannot create %s: %m", _SM_STATE_PATH);
625                         exit(1);
626                 }
627                 sync();
628         }
629
630         return state;
631 }
632
633 /*
634  * Address handling utilities
635  */
636
637 int
638 addr_get_port(nsm_address *addr)
639 {
640         switch (((struct sockaddr *) addr)->sa_family) {
641         case AF_INET:
642                 return ntohs(((struct sockaddr_in *) addr)->sin_port);
643         case AF_INET6:
644                 return ntohs(((struct sockaddr_in6 *) addr)->sin6_port);
645         }
646         return 0;
647 }
648
649 static void
650 addr_set_port(nsm_address *addr, int port)
651 {
652         switch (((struct sockaddr *) addr)->sa_family) {
653         case AF_INET:
654                 ((struct sockaddr_in *) addr)->sin_port = htons(port);
655                 break;
656         case AF_INET6:
657                 ((struct sockaddr_in6 *) addr)->sin6_port = htons(port);
658         }
659 }
660
661 static int
662 host_lookup(int af, const char *name, nsm_address *addr)
663 {
664         struct addrinfo hints, *ai;
665         int okay = 0;
666
667         memset(&hints, 0, sizeof(hints));
668         hints.ai_family = af;
669
670         if (getaddrinfo(name, NULL, &hints, &ai) != 0)
671                 return 0;
672
673         if (ai->ai_addrlen < sizeof(*addr)) {
674                 memcpy(addr, ai->ai_addr, ai->ai_addrlen);
675                 okay = 1;
676         }
677
678         freeaddrinfo(ai);
679         return okay;
680 }
681
682 /*
683  * Log a message
684  */
685 void
686 nsm_log(int fac, const char *fmt, ...)
687 {
688         va_list ap;
689
690         if (fac == LOG_DEBUG && !opt_debug)
691                 return;
692
693         va_start(ap, fmt);
694         if (log_syslog)
695                 vsyslog(fac, fmt, ap);
696         else {
697                 vfprintf(stderr, fmt, ap);
698                 fputs("\n", stderr);
699         }
700         va_end(ap);
701 }
702
703 /*
704  * Record pid in /var/run/sm-notify.pid
705  * This file should remain until a reboot, even if the
706  * program exits.
707  * If file already exists, fail.
708  */
709 static int record_pid()
710 {
711         char pid[20];
712         int fd;
713
714         snprintf(pid, 20, "%d\n", getpid());
715         fd = open("/var/run/sm-notify.pid", O_CREAT|O_EXCL|O_WRONLY, 0600);
716         if (!fd)
717                 return 0;
718         write(fd, pid, strlen(pid));
719         close(fd);
720         return 1;
721 }
722
723 /* Drop privileges to match owner of state-directory
724  * (in case a reply triggers some unknown bug).
725  */
726 static void drop_privs(void)
727 {
728         struct stat st;
729
730         if (stat(_SM_DIR_PATH, &st) == -1 &&
731             stat(_SM_BASE_PATH, &st) == -1) {
732                 st.st_uid = 0;
733                 st.st_gid = 0;
734         }
735
736         if (st.st_uid == 0) {
737                 nsm_log(LOG_WARNING,
738                         "sm-notify running as root. chown %s to choose different user\n",
739                     _SM_DIR_PATH);
740                 return;
741         }
742
743         setgroups(0, NULL);
744         if (setgid(st.st_gid) == -1
745             || setuid(st.st_uid) == -1) {
746                 nsm_log(LOG_ERR, "Fail to drop privileges");
747                 exit(1);
748         }
749 }