]> git.decadent.org.uk Git - nfs-utils.git/blob - support/nsm/file.c
Imported upstream 1.2.8
[nfs-utils.git] / support / nsm / file.c
1 /*
2  * Copyright 2009 Oracle.  All rights reserved.
3  *
4  * This file is part of nfs-utils.
5  *
6  * nfs-utils is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * nfs-utils is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with nfs-utils.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /*
21  * NSM for Linux.
22  *
23  * Callback information and NSM state is stored in files, usually
24  * under /var/lib/nfs.  A database of information contained in local
25  * files stores NLM callback data and what remote peers to notify of
26  * reboots.
27  *
28  * For each monitored remote peer, a text file is created under the
29  * directory specified by NSM_MONITOR_DIR.  The name of the file
30  * is a valid DNS hostname.  The hostname string must be a valid
31  * ASCII DNS name, and must not contain slash characters, white space,
32  * or '\0' (ie. anything that might have some special meaning in a
33  * path name).
34  *
35  * The contents of each file include seven blank-separated fields of
36  * text, finished with '\n'.  The first field contains the network
37  * address of the NLM service to call back.  The current implementation
38  * supports using only IPv4 addresses, so the only contents of this
39  * field are a network order IPv4 address expressed in 8 hexadecimal
40  * characters.
41  *
42  * The next four fields are text strings of hexadecimal characters,
43  * representing:
44  *
45  * 2. A 4 byte RPC program number of the NLM service to call back
46  * 3. A 4 byte RPC version number of the NLM service to call back
47  * 4. A 4 byte RPC procedure number of the NLM service to call back
48  * 5. A 16 byte opaque cookie that the NLM service uses to identify
49  *    the monitored host
50  *
51  * The sixth field is the monitored host's mon_name, passed to statd
52  * via an SM_MON request.
53  *
54  * The seventh field is the my_name for this peer, which is the
55  * hostname of the local NLM (currently on Linux, the result of
56  * `uname -n`).  This can be used as the source address/hostname
57  * when sending SM_NOTIFY requests.
58  *
59  * The NSM protocol does not limit the contents of these strings
60  * in any way except that they must fit into 1024 bytes.  Our
61  * implementation requires that these strings not contain
62  * white space or '\0'.
63  */
64
65 #ifdef HAVE_CONFIG_H
66 #include <config.h>
67 #endif
68
69 #include <sys/types.h>
70 #ifdef HAVE_SYS_CAPABILITY_H
71 #include <sys/capability.h>
72 #endif
73 #include <sys/prctl.h>
74 #include <sys/stat.h>
75
76 #include <ctype.h>
77 #include <string.h>
78 #include <stdint.h>
79 #ifndef S_SPLINT_S
80 #include <unistd.h>
81 #endif
82 #include <libgen.h>
83 #include <stdio.h>
84 #include <errno.h>
85 #include <fcntl.h>
86 #include <dirent.h>
87 #include <grp.h>
88
89 #include "xlog.h"
90 #include "nsm.h"
91
92 #define RPCARGSLEN      (4 * (8 + 1))
93 #define LINELEN         (RPCARGSLEN + SM_PRIV_SIZE * 2 + 1)
94
95 #define NSM_KERNEL_STATE_FILE   "/proc/sys/fs/nfs/nsm_local_state"
96
97 static char nsm_base_dirname[PATH_MAX] = NSM_DEFAULT_STATEDIR;
98
99 #define NSM_MONITOR_DIR "sm"
100 #define NSM_NOTIFY_DIR  "sm.bak"
101 #define NSM_STATE_FILE  "state"
102
103
104 static _Bool
105 error_check(const int len, const size_t buflen)
106 {
107         return (len < 0) || ((size_t)len >= buflen);
108 }
109
110 static _Bool
111 exact_error_check(const ssize_t len, const size_t buflen)
112 {
113         return (len < 0) || ((size_t)len != buflen);
114 }
115
116 /*
117  * Returns a dynamically allocated, '\0'-terminated buffer
118  * containing an appropriate pathname, or NULL if an error
119  * occurs.  Caller must free the returned result with free(3).
120  */
121 __attribute__((__malloc__))
122 static char *
123 nsm_make_record_pathname(const char *directory, const char *hostname)
124 {
125         const char *c;
126         size_t size;
127         char *path;
128         int len;
129
130         /*
131          * Block hostnames that contain characters that have
132          * meaning to the file system (like '/'), or that can
133          * be confusing on visual inspection (like ' ').
134          */
135         for (c = hostname; *c != '\0'; c++)
136                 if (*c == '/' || isspace((int)*c) != 0) {
137                         xlog(D_GENERAL, "Hostname contains invalid characters");
138                         return NULL;
139                 }
140
141         size = strlen(nsm_base_dirname) + strlen(directory) + strlen(hostname) + 3;
142         if (size > PATH_MAX) {
143                 xlog(D_GENERAL, "Hostname results in pathname that is too long");
144                 return NULL;
145         }
146
147         path = malloc(size);
148         if (path == NULL) {
149                 xlog(D_GENERAL, "Failed to allocate memory for pathname");
150                 return NULL;
151         }
152
153         len = snprintf(path, size, "%s/%s/%s",
154                         nsm_base_dirname, directory, hostname);
155         if (error_check(len, size)) {
156                 xlog(D_GENERAL, "Pathname did not fit in specified buffer");
157                 free(path);
158                 return NULL;
159         }
160
161         return path;
162 }
163
164 /*
165  * Returns a dynamically allocated, '\0'-terminated buffer
166  * containing an appropriate pathname, or NULL if an error
167  * occurs.  Caller must free the returned result with free(3).
168  */
169 __attribute__((__malloc__))
170 static char *
171 nsm_make_pathname(const char *directory)
172 {
173         size_t size;
174         char *path;
175         int len;
176
177         size = strlen(nsm_base_dirname) + strlen(directory) + 2;
178         if (size > PATH_MAX)
179                 return NULL;
180
181         path = malloc(size);
182         if (path == NULL)
183                 return NULL;
184
185         len = snprintf(path, size, "%s/%s", nsm_base_dirname, directory);
186         if (error_check(len, size)) {
187                 free(path);
188                 return NULL;
189         }
190
191         return path;
192 }
193
194 /*
195  * Returns a dynamically allocated, '\0'-terminated buffer
196  * containing an appropriate pathname, or NULL if an error
197  * occurs.  Caller must free the returned result with free(3).
198  */
199 __attribute__((__malloc__))
200 static char *
201 nsm_make_temp_pathname(const char *pathname)
202 {
203         size_t size;
204         char *path;
205         int len;
206
207         size = strlen(pathname) + sizeof(".new") + 2;
208         if (size > PATH_MAX)
209                 return NULL;
210
211         path = malloc(size);
212         if (path == NULL)
213                 return NULL;
214
215         len = snprintf(path, size, "%s.new", pathname);
216         if (error_check(len, size)) {
217                 free(path);
218                 return NULL;
219         }
220
221         return path;
222 }
223
224 /*
225  * Use "mktemp, write, rename" to update the contents of a file atomically.
226  *
227  * Returns true if completely successful, or false if some error occurred.
228  */
229 static _Bool
230 nsm_atomic_write(const char *path, const void *buf, const size_t buflen)
231 {
232         _Bool result = false;
233         ssize_t len;
234         char *temp;
235         int fd;
236
237         temp = nsm_make_temp_pathname(path);
238         if (temp == NULL) {
239                 xlog(L_ERROR, "Failed to create new path for %s", path);
240                 goto out;
241         }
242
243         fd = open(temp, O_CREAT | O_TRUNC | O_SYNC | O_WRONLY, 0644);
244         if (fd == -1) {
245                 xlog(L_ERROR, "Failed to create %s: %m", temp);
246                 goto out;
247         }
248
249         len = write(fd, buf, buflen);
250         if (exact_error_check(len, buflen)) {
251                 xlog(L_ERROR, "Failed to write %s: %m", temp);
252                 (void)close(fd);
253                 (void)unlink(temp);
254                 goto out;
255         }
256
257         if (close(fd) == -1) {
258                 xlog(L_ERROR, "Failed to close %s: %m", temp);
259                 (void)unlink(temp);
260                 goto out;
261         }
262
263         if (rename(temp, path) == -1) {
264                 xlog(L_ERROR, "Failed to rename %s -> %s: %m",
265                                 temp, path);
266                 (void)unlink(temp);
267                 goto out;
268         }
269
270         /* Ostensibly, a sync(2) is not needed here because
271          * open(O_CREAT), write(O_SYNC), and rename(2) are
272          * already synchronous with persistent storage, for
273          * any file system we care about. */
274
275         result = true;
276
277 out:
278         free(temp);
279         return result;
280 }
281
282 /**
283  * nsm_setup_pathnames - set up pathname
284  * @progname: C string containing name of program, for error messages
285  * @parentdir: C string containing pathname to on-disk state, or NULL
286  *
287  * This runs before logging is set up, so error messages are directed
288  * to stderr.
289  *
290  * Returns true and sets up our pathnames, if @parentdir was valid
291  * and usable; otherwise false is returned.
292  */
293 _Bool
294 nsm_setup_pathnames(const char *progname, const char *parentdir)
295 {
296         static char buf[PATH_MAX];
297         struct stat st;
298         char *path;
299
300         /* First: test length of name and whether it exists */
301         if (lstat(parentdir, &st) == -1) {
302                 (void)fprintf(stderr, "%s: Failed to stat %s: %s",
303                                 progname, parentdir, strerror(errno));
304                 return false;
305         }
306
307         /* Ensure we have a clean directory pathname */
308         strncpy(buf, parentdir, sizeof(buf));
309         path = dirname(buf);
310         if (*path == '.') {
311                 (void)fprintf(stderr, "%s: Unusable directory %s",
312                                 progname, parentdir);
313                 return false;
314         }
315
316         xlog(D_CALL, "Using %s as the state directory", parentdir);
317         strncpy(nsm_base_dirname, parentdir, sizeof(nsm_base_dirname));
318         return true;
319 }
320
321 /**
322  * nsm_is_default_parentdir - check if parent directory is default
323  *
324  * Returns true if the active statd parent directory, set by
325  * nsm_change_pathname(), is the same as the built-in default
326  * parent directory; otherwise false is returned.
327  */
328 _Bool
329 nsm_is_default_parentdir(void)
330 {
331         return strcmp(nsm_base_dirname, NSM_DEFAULT_STATEDIR) == 0;
332 }
333
334 /*
335  * Clear all capabilities but CAP_NET_BIND_SERVICE.  This permits
336  * callers to acquire privileged source ports, but all other root
337  * capabilities are disallowed.
338  *
339  * Returns true if successful, or false if some error occurred.
340  */
341 #ifdef HAVE_SYS_CAPABILITY_H
342 static _Bool
343 nsm_clear_capabilities(void)
344 {
345         cap_t caps;
346
347         caps = cap_from_text("cap_net_bind_service=ep");
348         if (caps == NULL) {
349                 xlog(L_ERROR, "Failed to allocate capability: %m");
350                 return false;
351         }
352
353         if (cap_set_proc(caps) == -1) {
354                 xlog(L_ERROR, "Failed to set capability flags: %m");
355                 (void)cap_free(caps);
356                 return false;
357         }
358
359         (void)cap_free(caps);
360         return true;
361 }
362
363 #define CAP_BOUND_PROCFILE "/proc/sys/kernel/cap-bound"
364 static _Bool
365 prune_bounding_set(void)
366 {
367 #ifdef PR_CAPBSET_DROP
368         int ret;
369         unsigned long i;
370         struct stat st;
371
372         /*
373          * Prior to kernel 2.6.25, the capabilities bounding set was a global
374          * value. Check to see if /proc/sys/kernel/cap-bound exists and don't
375          * bother to clear the bounding set if it does.
376          */
377         ret = stat(CAP_BOUND_PROCFILE, &st);
378         if (!ret) {
379                 xlog(L_WARNING, "%s exists. Not attempting to clear "
380                                 "capabilities bounding set.",
381                                 CAP_BOUND_PROCFILE);
382                 return true;
383         } else if (errno != ENOENT) {
384                 /* Warn, but attempt to clear the bounding set anyway. */
385                 xlog(L_WARNING, "Unable to stat %s: %m", CAP_BOUND_PROCFILE);
386         }
387
388         /* prune the bounding set to nothing */
389         for (i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >=0 ; ++i) {
390                 ret = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
391                 if (ret) {
392                         xlog(L_ERROR, "Unable to prune capability %lu from "
393                                       "bounding set: %m", i);
394                         return false;
395                 }
396         }
397 #endif /* PR_CAPBSET_DROP */
398         return true;
399 }
400 #else /* !HAVE_SYS_CAPABILITY_H */
401 static _Bool
402 nsm_clear_capabilities(void)
403 {
404         return true;
405 }
406
407 static _Bool
408 prune_bounding_set(void)
409 {
410         return true;
411 }
412 #endif /* HAVE_SYS_CAPABILITY_H */
413
414 /**
415  * nsm_drop_privileges - drop root privileges
416  * @pidfd: file descriptor of a pid file
417  *
418  * Returns true if successful, or false if some error occurred.
419  *
420  * Set our effective UID and GID to that of our on-disk database.
421  */
422 _Bool
423 nsm_drop_privileges(const int pidfd)
424 {
425         struct stat st;
426
427         (void)umask(S_IRWXO);
428
429         /*
430          * XXX: If we can't stat dirname, or if dirname is owned by
431          *      root, we should use "statduser" instead, which is set up
432          *      by configure.ac.  Nothing in nfs-utils seems to use
433          *      "statduser," though.
434          */
435         if (lstat(nsm_base_dirname, &st) == -1) {
436                 xlog(L_ERROR, "Failed to stat %s: %m", nsm_base_dirname);
437                 return false;
438         }
439
440         if (chdir(nsm_base_dirname) == -1) {
441                 xlog(L_ERROR, "Failed to change working directory to %s: %m",
442                                 nsm_base_dirname);
443                 return false;
444         }
445
446         if (!prune_bounding_set())
447                 return false;
448
449         if (st.st_uid == 0) {
450                 xlog_warn("Running as root.  "
451                         "chown %s to choose different user", nsm_base_dirname);
452                 return true;
453         }
454
455         /*
456          * If the pidfile happens to reside on NFS, dropping privileges
457          * will probably cause us to lose access, even though we are
458          * holding it open.  Chown it to prevent this.
459          */
460         if (pidfd >= 0)
461                 if (fchown(pidfd, st.st_uid, st.st_gid) == -1)
462                         xlog_warn("Failed to change owner of pidfile: %m");
463
464         /*
465          * Don't clear capabilities when dropping root.
466          */
467         if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) {
468                 xlog(L_ERROR, "prctl(PR_SET_KEEPCAPS) failed: %m");
469                 return false;
470         }
471
472         if (setgroups(0, NULL) == -1) {
473                 xlog(L_ERROR, "Failed to drop supplementary groups: %m");
474                 return false;
475         }
476
477         /*
478          * ORDER
479          *
480          * setgid(2) first, as setuid(2) may remove privileges needed
481          * to set the group id.
482          */
483         if (setgid(st.st_gid) == -1 || setuid(st.st_uid) == -1) {
484                 xlog(L_ERROR, "Failed to drop privileges: %m");
485                 return false;
486         }
487
488         xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid);
489
490         return nsm_clear_capabilities();
491 }
492
493 /**
494  * nsm_get_state - retrieve on-disk NSM state number
495  *
496  * Returns an odd NSM state number read from disk, or an initial
497  * state number.  Zero is returned if some error occurs.
498  */
499 int
500 nsm_get_state(_Bool update)
501 {
502         int fd, state = 0;
503         ssize_t result;
504         char *path = NULL;
505
506         path = nsm_make_pathname(NSM_STATE_FILE);
507         if (path == NULL) {
508                 xlog(L_ERROR, "Failed to allocate path for " NSM_STATE_FILE);
509                 goto out;
510         }
511
512         fd = open(path, O_RDONLY);
513         if (fd == -1) {
514                 if (errno != ENOENT) {
515                         xlog(L_ERROR, "Failed to open %s: %m", path);
516                         goto out;
517                 }
518
519                 xlog(L_NOTICE, "Initializing NSM state");
520                 state = 1;
521                 update = true;
522                 goto update;
523         }
524
525         result = read(fd, &state, sizeof(state));
526         if (exact_error_check(result, sizeof(state))) {
527                 xlog_warn("Failed to read %s: %m", path);
528
529                 xlog(L_NOTICE, "Initializing NSM state");
530                 state = 1;
531                 update = true;
532                 goto update;
533         }
534
535         if ((state & 1) == 0)
536                 state++;
537
538 update:
539         (void)close(fd);
540
541         if (update) {
542                 state += 2;
543                 if (!nsm_atomic_write(path, &state, sizeof(state)))
544                         state = 0;
545         }
546
547 out:
548         free(path);
549         return state;
550 }
551
552 /**
553  * nsm_update_kernel_state - attempt to post new NSM state to kernel
554  * @state: NSM state number
555  *
556  */
557 void
558 nsm_update_kernel_state(const int state)
559 {
560         ssize_t result;
561         char buf[20];
562         int fd, len;
563
564         fd = open(NSM_KERNEL_STATE_FILE, O_WRONLY);
565         if (fd == -1) {
566                 xlog(D_GENERAL, "Failed to open " NSM_KERNEL_STATE_FILE ": %m");
567                 return;
568         }
569
570         len = snprintf(buf, sizeof(buf), "%d", state);
571         if (error_check(len, sizeof(buf))) {
572                 xlog_warn("Failed to form NSM state number string");
573                 return;
574         }
575
576         result = write(fd, buf, strlen(buf));
577         if (exact_error_check(result, strlen(buf)))
578                 xlog_warn("Failed to write NSM state number: %m");
579
580         if (close(fd) == -1)
581                 xlog(L_ERROR, "Failed to close NSM state file "
582                                 NSM_KERNEL_STATE_FILE ": %m");
583 }
584
585 /**
586  * nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/"
587  *
588  * Returns the count of host records that were moved.
589  *
590  * Note that if any error occurs during this process, some monitor
591  * records may be left in the "sm" directory.
592  */
593 unsigned int
594 nsm_retire_monitored_hosts(void)
595 {
596         unsigned int count = 0;
597         struct dirent *de;
598         char *path;
599         DIR *dir;
600
601         path = nsm_make_pathname(NSM_MONITOR_DIR);
602         if (path == NULL) {
603                 xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR);
604                 return count;
605         }
606
607         dir = opendir(path);
608         free(path);
609         if (dir == NULL) {
610                 xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m");
611                 return count;
612         }
613
614         while ((de = readdir(dir)) != NULL) {
615                 char *src, *dst;
616                 struct stat stb;
617
618                 if (de->d_name[0] == '.')
619                         continue;
620
621                 src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name);
622                 if (src == NULL) {
623                         xlog_warn("Bad monitor file name, skipping");
624                         continue;
625                 }
626
627                 /* NB: not all file systems fill in d_type correctly */
628                 if (lstat(src, &stb) == -1) {
629                         xlog_warn("Bad monitor file %s, skipping: %m",
630                                         de->d_name);
631                         free(src);
632                         continue;
633                 }
634                 if (!S_ISREG(stb.st_mode)) {
635                         xlog(D_GENERAL, "Skipping non-regular file %s",
636                                         de->d_name);
637                         free(src);
638                         continue;
639                 }
640
641                 dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name);
642                 if (dst == NULL) {
643                         free(src);
644                         xlog_warn("Bad notify file name, skipping");
645                         continue;
646                 }
647
648                 if (rename(src, dst) == -1)
649                         xlog_warn("Failed to rename %s -> %s: %m",
650                                 src, dst);
651                 else {
652                         xlog(D_GENERAL, "Retired record for mon_name %s",
653                                         de->d_name);
654                         count++;
655                 }
656
657                 free(dst);
658                 free(src);
659         }
660
661         (void)closedir(dir);
662         return count;
663 }
664
665 /*
666  * nsm_priv_to_hex - convert a NSM private cookie to a hex string.
667  *
668  * @priv: buffer holding the binary NSM private cookie
669  * @buf: output buffer for NULL terminated hex string
670  * @buflen: size of output buffer
671  *
672  * Returns the length of the resulting string or 0 on error
673  */
674 size_t
675 nsm_priv_to_hex(const char *priv, char *buf, const size_t buflen)
676 {
677         int i, len;
678         size_t remaining = buflen;
679
680         for (i = 0; i < SM_PRIV_SIZE; i++) {
681                 len = snprintf(buf, remaining, "%02x",
682                                 (unsigned int)(0xff & priv[i]));
683                 if (error_check(len, remaining))
684                         return 0;
685                 buf += len;
686                 remaining -= (size_t)len;
687         }
688
689         return buflen - remaining;
690 }
691
692 /*
693  * Returns the length in bytes of the created record.
694  */
695 __attribute__((__noinline__))
696 static size_t
697 nsm_create_monitor_record(char *buf, const size_t buflen,
698                 const struct sockaddr *sap, const struct mon *m)
699 {
700         const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
701         size_t hexlen, remaining = buflen;
702         int len;
703
704         len = snprintf(buf, remaining, "%08x %08x %08x %08x ",
705                         (unsigned int)sin->sin_addr.s_addr,
706                         (unsigned int)m->mon_id.my_id.my_prog,
707                         (unsigned int)m->mon_id.my_id.my_vers,
708                         (unsigned int)m->mon_id.my_id.my_proc);
709         if (error_check(len, remaining))
710                 return 0;
711         buf += len;
712         remaining -= (size_t)len;
713
714         hexlen = nsm_priv_to_hex(m->priv, buf, remaining);
715         if (hexlen == 0)
716                 return 0;
717         buf += hexlen;
718         remaining -= hexlen;
719
720         len = snprintf(buf, remaining, " %s %s\n",
721                         m->mon_id.mon_name, m->mon_id.my_id.my_name);
722         if (error_check(len, remaining))
723                 return 0;
724         remaining -= (size_t)len;
725
726         return buflen - remaining;
727 }
728
729 static _Bool
730 nsm_append_monitored_host(const char *path, const char *line)
731 {
732         _Bool result = false;
733         char *buf = NULL;
734         struct stat stb;
735         size_t buflen;
736         ssize_t len;
737         int fd;
738
739         if (stat(path, &stb) == -1) {
740                 xlog(L_ERROR, "Failed to insert: "
741                         "could not stat original file %s: %m", path);
742                 goto out;
743         }
744         buflen = (size_t)stb.st_size + strlen(line);
745
746         buf = malloc(buflen + 1);
747         if (buf == NULL) {
748                 xlog(L_ERROR, "Failed to insert: no memory");
749                 goto out;
750         }
751         memset(buf, 0, buflen + 1);
752
753         fd = open(path, O_RDONLY);
754         if (fd == -1) {
755                 xlog(L_ERROR, "Failed to insert: "
756                         "could not open original file %s: %m", path);
757                 goto out;
758         }
759
760         len = read(fd, buf, (size_t)stb.st_size);
761         if (exact_error_check(len, (size_t)stb.st_size)) {
762                 xlog(L_ERROR, "Failed to insert: "
763                         "could not read original file %s: %m", path);
764                 (void)close(fd);
765                 goto out;
766         }
767         (void)close(fd);
768
769         strcat(buf, line);
770
771         if (nsm_atomic_write(path, buf, buflen))
772                 result = true;
773
774 out:
775         free(buf);
776         return result;
777 }
778
779 /**
780  * nsm_insert_monitored_host - write callback data for one host to disk
781  * @hostname: C string containing a hostname
782  * @sap: sockaddr containing NLM callback address
783  * @mon: SM_MON arguments to save
784  *
785  * Returns true if successful, otherwise false if some error occurs.
786  */
787 _Bool
788 nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap,
789                 const struct mon *m)
790 {
791         static char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
792         char *path;
793         _Bool result = false;
794         ssize_t len;
795         size_t size;
796         int fd;
797
798         path = nsm_make_record_pathname(NSM_MONITOR_DIR, hostname);
799         if (path == NULL) {
800                 xlog(L_ERROR, "Failed to insert: bad monitor hostname '%s'",
801                                 hostname);
802                 return false;
803         }
804
805         size = nsm_create_monitor_record(buf, sizeof(buf), sap, m);
806         if (size == 0) {
807                 xlog(L_ERROR, "Failed to insert: record too long");
808                 goto out;
809         }
810
811         /*
812          * If exclusive create fails, we're adding a new line to an
813          * existing file.
814          */
815         fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_SYNC, S_IRUSR | S_IWUSR);
816         if (fd == -1) {
817                 if (errno != EEXIST) {
818                         xlog(L_ERROR, "Failed to insert: creating %s: %m", path);
819                         goto out;
820                 }
821
822                 result = nsm_append_monitored_host(path, buf);
823                 goto out;
824         }
825         result = true;
826
827         len = write(fd, buf, size);
828         if (exact_error_check(len, size)) {
829                 xlog_warn("Failed to insert: writing %s: %m", path);
830                 (void)unlink(path);
831                 result = false;
832         }
833
834         if (close(fd) == -1) {
835                 xlog(L_ERROR, "Failed to insert: closing %s: %m", path);
836                 (void)unlink(path);
837                 result = false;
838         }
839
840 out:
841         free(path);
842         return result;
843 }
844
845 __attribute__((__noinline__))
846 static _Bool
847 nsm_parse_line(char *line, struct sockaddr_in *sin, struct mon *m)
848 {
849         unsigned int i, tmp;
850         int count;
851         char *c;
852
853         c = strchr(line, '\n');
854         if (c != NULL)
855                 *c = '\0';
856
857         count = sscanf(line, "%8x %8x %8x %8x ",
858                         (unsigned int *)&sin->sin_addr.s_addr,
859                         (unsigned int *)&m->mon_id.my_id.my_prog,
860                         (unsigned int *)&m->mon_id.my_id.my_vers,
861                         (unsigned int *)&m->mon_id.my_id.my_proc);
862         if (count != 4)
863                 return false;
864
865         c = line + RPCARGSLEN;
866         for (i = 0; i < SM_PRIV_SIZE; i++) {
867                 if (sscanf(c, "%2x", &tmp) != 1)
868                         return false;
869                 m->priv[i] = (char)tmp;
870                 c += 2;
871         }
872
873         c++;
874         m->mon_id.mon_name = c;
875         while (*c != '\0' && *c != ' ')
876                 c++;
877         if (*c != '\0')
878                 *c++ = '\0';
879         while (*c == ' ')
880                 c++;
881         m->mon_id.my_id.my_name = c;
882
883         return true;
884 }
885
886 /*
887  * Stuff a 'struct mon' with callback data, and call @func.
888  *
889  * Returns the count of in-core records created.
890  */
891 static unsigned int
892 nsm_read_line(const char *hostname, const time_t timestamp, char *line,
893                 nsm_populate_t func)
894 {
895         struct sockaddr_in sin = {
896                 .sin_family             = AF_INET,
897         };
898         struct mon m;
899
900         if (!nsm_parse_line(line, &sin, &m))
901                 return 0;
902
903         return func(hostname, (struct sockaddr *)(char *)&sin, &m, timestamp);
904 }
905
906 /*
907  * Given a filename, reads data from a file under "directory"
908  * and invokes @func so caller can populate their in-core
909  * database with this data.
910  */
911 static unsigned int
912 nsm_load_host(const char *directory, const char *filename, nsm_populate_t func)
913 {
914         char buf[LINELEN + 1 + SM_MAXSTRLEN + 2];
915         unsigned int result = 0;
916         struct stat stb;
917         char *path;
918         FILE *f;
919
920         path = nsm_make_record_pathname(directory, filename);
921         if (path == NULL)
922                 goto out_err;
923
924         if (lstat(path, &stb) == -1) {
925                 xlog(L_ERROR, "Failed to stat %s: %m", path);
926                 goto out_freepath;
927         }
928         if (!S_ISREG(stb.st_mode)) {
929                 xlog(D_GENERAL, "Skipping non-regular file %s",
930                                 path);
931                 goto out_freepath;
932         }
933
934         f = fopen(path, "r");
935         if (f == NULL) {
936                 xlog(L_ERROR, "Failed to open %s: %m", path);
937                 goto out_freepath;
938         }
939
940         while (fgets(buf, (int)sizeof(buf), f) != NULL) {
941                 buf[sizeof(buf) - 1] = '\0';
942                 result += nsm_read_line(filename, stb.st_mtime, buf, func);
943         }
944         if (result == 0)
945                 xlog(L_ERROR, "Failed to read monitor data from %s", path);
946
947         (void)fclose(f);
948
949 out_freepath:
950         free(path);
951 out_err:
952         return result;
953 }
954
955 static unsigned int
956 nsm_load_dir(const char *directory, nsm_populate_t func)
957 {
958         unsigned int count = 0;
959         struct dirent *de;
960         char *path;
961         DIR *dir;
962
963         path = nsm_make_pathname(directory);
964         if (path == NULL) {
965                 xlog(L_ERROR, "Failed to allocate path for directory %s",
966                                 directory);
967                 return 0;
968         }
969
970         dir = opendir(path);
971         free(path);
972         if (dir == NULL) {
973                 xlog(L_ERROR, "Failed to open directory %s: %m",
974                                 directory);
975                 return 0;
976         }
977
978         while ((de = readdir(dir)) != NULL) {
979                 if (de->d_name[0] == '.')
980                         continue;
981
982                 count += nsm_load_host(directory, de->d_name, func);
983         }
984
985         (void)closedir(dir);
986         return count;
987 }
988
989 /**
990  * nsm_load_monitor_list - load list of hosts to monitor
991  * @func: callback function to create entry for one host
992  *
993  * Returns the count of hosts that were found in the directory.
994  */
995 unsigned int
996 nsm_load_monitor_list(nsm_populate_t func)
997 {
998         return nsm_load_dir(NSM_MONITOR_DIR, func);
999 }
1000
1001 /**
1002  * nsm_load_notify_list - load list of hosts to notify
1003  * @func: callback function to create entry for one host
1004  *
1005  * Returns the count of hosts that were found in the directory.
1006  */
1007 unsigned int
1008 nsm_load_notify_list(nsm_populate_t func)
1009 {
1010         return nsm_load_dir(NSM_NOTIFY_DIR, func);
1011 }
1012
1013 static void
1014 nsm_delete_host(const char *directory, const char *hostname,
1015                 const char *mon_name, const char *my_name)
1016 {
1017         char line[LINELEN + 1 + SM_MAXSTRLEN + 2];
1018         char *outbuf = NULL;
1019         struct stat stb;
1020         char *path, *next;
1021         size_t remaining;
1022         FILE *f;
1023
1024         path = nsm_make_record_pathname(directory, hostname);
1025         if (path == NULL) {
1026                 xlog(L_ERROR, "Bad filename, not deleting");
1027                 return;
1028         }
1029
1030         if (stat(path, &stb) == -1) {
1031                 xlog(L_ERROR, "Failed to delete: "
1032                         "could not stat original file %s: %m", path);
1033                 goto out;
1034         }
1035         remaining = (size_t)stb.st_size + 1;
1036
1037         outbuf = malloc(remaining);
1038         if (outbuf == NULL) {
1039                 xlog(L_ERROR, "Failed to delete: no memory");
1040                 goto out;
1041         }
1042
1043         f = fopen(path, "r");
1044         if (f == NULL) {
1045                 xlog(L_ERROR, "Failed to delete: "
1046                         "could not open original file %s: %m", path);
1047                 goto out;
1048         }
1049
1050         /*
1051          * Walk the records in the file, and copy the non-matching
1052          * ones to our output buffer.
1053          */
1054         next = outbuf;
1055         while (fgets(line, (int)sizeof(line), f) != NULL) {
1056                 struct sockaddr_in sin;
1057                 struct mon m;
1058                 size_t len;
1059
1060                 if (!nsm_parse_line(line, &sin, &m)) {
1061                         xlog(L_ERROR, "Failed to delete: "
1062                                 "could not parse original file %s", path);
1063                         (void)fclose(f);
1064                         goto out;
1065                 }
1066
1067                 if (strcmp(mon_name, m.mon_id.mon_name) == 0 &&
1068                          strcmp(my_name, m.mon_id.my_id.my_name) == 0)
1069                         continue;
1070
1071                 /* nsm_parse_line destroys the contents of line[], so
1072                  * reconstruct the copy in our output buffer. */
1073                 len = nsm_create_monitor_record(next, remaining,
1074                                         (struct sockaddr *)(char *)&sin, &m);
1075                 if (len == 0) {
1076                         xlog(L_ERROR, "Failed to delete: "
1077                                 "could not construct output record");
1078                         (void)fclose(f);
1079                         goto out;
1080                 }
1081                 next += len;
1082                 remaining -= len;
1083         }
1084
1085         (void)fclose(f);
1086
1087         /*
1088          * If nothing was copied when we're done, then unlink the file.
1089          * Otherwise, atomically update the contents of the file.
1090          */
1091         if (next != outbuf) {
1092                 if (!nsm_atomic_write(path, outbuf, strlen(outbuf)))
1093                         xlog(L_ERROR, "Failed to delete: "
1094                                 "could not write new file %s: %m", path);
1095         } else {
1096                 if (unlink(path) == -1)
1097                         xlog(L_ERROR, "Failed to delete: "
1098                                 "could not unlink file %s: %m", path);
1099         }
1100
1101 out:
1102         free(outbuf);
1103         free(path);
1104 }
1105
1106 /**
1107  * nsm_delete_monitored_host - delete on-disk record for monitored host
1108  * @hostname: '\0'-terminated C string containing hostname of record to delete
1109  * @mon_name: '\0'-terminated C string containing monname of record to delete
1110  * @my_name: '\0'-terminated C string containing myname of record to delete
1111  *
1112  */
1113 void
1114 nsm_delete_monitored_host(const char *hostname, const char *mon_name,
1115                 const char *my_name)
1116 {
1117         nsm_delete_host(NSM_MONITOR_DIR, hostname, mon_name, my_name);
1118 }
1119
1120 /**
1121  * nsm_delete_notified_host - delete on-disk host record after notification
1122  * @hostname: '\0'-terminated C string containing hostname of record to delete
1123  * @mon_name: '\0'-terminated C string containing monname of record to delete
1124  * @my_name: '\0'-terminated C string containing myname of record to delete
1125  *
1126  */
1127 void
1128 nsm_delete_notified_host(const char *hostname, const char *mon_name,
1129                 const char *my_name)
1130 {
1131         nsm_delete_host(NSM_NOTIFY_DIR, hostname, mon_name, my_name);
1132 }