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