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