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