diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 06:03:02 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 06:03:02 +0000 |
commit | 4897093455a2bf08f3db3a1132cc2f6f5484d77c (patch) | |
tree | 9e6373544263f003139431fb4b08f9766e1ed530 /support/nsm/file.c | |
parent | Initial commit. (diff) | |
download | nfs-utils-4897093455a2bf08f3db3a1132cc2f6f5484d77c.tar.xz nfs-utils-4897093455a2bf08f3db3a1132cc2f6f5484d77c.zip |
Adding upstream version 1:2.6.4.upstream/1%2.6.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'support/nsm/file.c')
-rw-r--r-- | support/nsm/file.c | 1092 |
1 files changed, 1092 insertions, 0 deletions
diff --git a/support/nsm/file.c b/support/nsm/file.c new file mode 100644 index 0000000..f5b4480 --- /dev/null +++ b/support/nsm/file.c @@ -0,0 +1,1092 @@ +/* + * Copyright 2009 Oracle. All rights reserved. + * + * This file is part of nfs-utils. + * + * nfs-utils is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * nfs-utils is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nfs-utils. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * NSM for Linux. + * + * Callback information and NSM state is stored in files, usually + * under /var/lib/nfs. A database of information contained in local + * files stores NLM callback data and what remote peers to notify of + * reboots. + * + * For each monitored remote peer, a text file is created under the + * directory specified by NSM_MONITOR_DIR. The name of the file + * is a valid DNS hostname. The hostname string must be a valid + * ASCII DNS name, and must not contain slash characters, white space, + * or '\0' (ie. anything that might have some special meaning in a + * path name). + * + * The contents of each file include seven blank-separated fields of + * text, finished with '\n'. The first field contains the network + * address of the NLM service to call back. The current implementation + * supports using only IPv4 addresses, so the only contents of this + * field are a network order IPv4 address expressed in 8 hexadecimal + * characters. + * + * The next four fields are text strings of hexadecimal characters, + * representing: + * + * 2. A 4 byte RPC program number of the NLM service to call back + * 3. A 4 byte RPC version number of the NLM service to call back + * 4. A 4 byte RPC procedure number of the NLM service to call back + * 5. A 16 byte opaque cookie that the NLM service uses to identify + * the monitored host + * + * The sixth field is the monitored host's mon_name, passed to statd + * via an SM_MON request. + * + * The seventh field is the my_name for this peer, which is the + * hostname of the local NLM (currently on Linux, the result of + * `uname -n`). This can be used as the source address/hostname + * when sending SM_NOTIFY requests. + * + * The NSM protocol does not limit the contents of these strings + * in any way except that they must fit into 1024 bytes. Our + * implementation requires that these strings not contain + * white space or '\0'. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#ifdef HAVE_SYS_CAPABILITY_H +#include <sys/capability.h> +#endif +#include <sys/prctl.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <string.h> +#include <stdint.h> +#ifndef S_SPLINT_S +#include <unistd.h> +#endif +#include <libgen.h> +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <dirent.h> +#include <grp.h> + +#include "xlog.h" +#include "nsm.h" +#include "misc.h" + +#define RPCARGSLEN (4 * (8 + 1)) +#define LINELEN (RPCARGSLEN + SM_PRIV_SIZE * 2 + 1) + +#define NSM_KERNEL_STATE_FILE "/proc/sys/fs/nfs/nsm_local_state" + +static char nsm_base_dirname[PATH_MAX] = NSM_DEFAULT_STATEDIR; + +#define NSM_MONITOR_DIR "sm" +#define NSM_NOTIFY_DIR "sm.bak" +#define NSM_STATE_FILE "state" + + +static _Bool +error_check(const int len, const size_t buflen) +{ + return (len < 0) || ((size_t)len >= buflen); +} + +static _Bool +exact_error_check(const ssize_t len, const size_t buflen) +{ + return (len < 0) || ((size_t)len != buflen); +} + +/* + * Returns a dynamically allocated, '\0'-terminated buffer + * containing an appropriate pathname, or NULL if an error + * occurs. Caller must free the returned result with free(3). + */ +__attribute__((__malloc__)) +static char * +nsm_make_record_pathname(const char *directory, const char *hostname) +{ + const char *c; + size_t size; + char *path; + int len; + + /* + * Block hostnames that contain characters that have + * meaning to the file system (like '/'), or that can + * be confusing on visual inspection (like ' '). + */ + for (c = hostname; *c != '\0'; c++) + if (*c == '/' || isspace((int)*c) != 0) { + xlog(D_GENERAL, "Hostname contains invalid characters"); + return NULL; + } + + size = strlen(nsm_base_dirname) + strlen(directory) + strlen(hostname) + 3; + if (size > PATH_MAX) { + xlog(D_GENERAL, "Hostname results in pathname that is too long"); + return NULL; + } + + path = malloc(size); + if (path == NULL) { + xlog(D_GENERAL, "Failed to allocate memory for pathname"); + return NULL; + } + + len = snprintf(path, size, "%s/%s/%s", + nsm_base_dirname, directory, hostname); + if (error_check(len, size)) { + xlog(D_GENERAL, "Pathname did not fit in specified buffer"); + free(path); + return NULL; + } + + return path; +} + +/* + * Returns a dynamically allocated, '\0'-terminated buffer + * containing an appropriate pathname, or NULL if an error + * occurs. Caller must free the returned result with free(3). + */ +__attribute__((__malloc__)) +static char * +nsm_make_pathname(const char *directory) +{ + return generic_make_pathname(nsm_base_dirname, directory); +} + +/* + * Returns a dynamically allocated, '\0'-terminated buffer + * containing an appropriate pathname, or NULL if an error + * occurs. Caller must free the returned result with free(3). + */ +__attribute__((__malloc__)) +static char * +nsm_make_temp_pathname(const char *pathname) +{ + size_t size; + char *path; + int len; + + size = strlen(pathname) + sizeof(".new") + 2; + if (size > PATH_MAX) + return NULL; + + path = malloc(size); + if (path == NULL) + return NULL; + + len = snprintf(path, size, "%s.new", pathname); + if (error_check(len, size)) { + free(path); + return NULL; + } + + return path; +} + +/* + * Use "mktemp, write, rename" to update the contents of a file atomically. + * + * Returns true if completely successful, or false if some error occurred. + */ +static _Bool +nsm_atomic_write(const char *path, const void *buf, const size_t buflen) +{ + _Bool result = false; + ssize_t len; + char *temp; + int fd; + + temp = nsm_make_temp_pathname(path); + if (temp == NULL) { + xlog(L_ERROR, "Failed to create new path for %s", path); + goto out; + } + + fd = open(temp, O_CREAT | O_TRUNC | O_SYNC | O_WRONLY, 0644); + if (fd == -1) { + xlog(L_ERROR, "Failed to create %s: %m", temp); + goto out; + } + + len = write(fd, buf, buflen); + if (exact_error_check(len, buflen)) { + xlog(L_ERROR, "Failed to write %s: %m", temp); + (void)close(fd); + (void)unlink(temp); + goto out; + } + + if (close(fd) == -1) { + xlog(L_ERROR, "Failed to close %s: %m", temp); + (void)unlink(temp); + goto out; + } + + if (rename(temp, path) == -1) { + xlog(L_ERROR, "Failed to rename %s -> %s: %m", + temp, path); + (void)unlink(temp); + goto out; + } + + /* Ostensibly, a sync(2) is not needed here because + * open(O_CREAT), write(O_SYNC), and rename(2) are + * already synchronous with persistent storage, for + * any file system we care about. */ + + result = true; + +out: + free(temp); + return result; +} + +/** + * nsm_setup_pathnames - set up pathname + * @progname: C string containing name of program, for error messages + * @parentdir: C string containing pathname to on-disk state, or NULL + * + * This runs before logging is set up, so error messages are directed + * to stderr. + * + * Returns true and sets up our pathnames, if @parentdir was valid + * and usable; otherwise false is returned. + */ +_Bool +nsm_setup_pathnames(const char *progname, const char *parentdir) +{ + return generic_setup_basedir(progname, parentdir, nsm_base_dirname, + PATH_MAX); +} + +/** + * nsm_is_default_parentdir - check if parent directory is default + * + * Returns true if the active statd parent directory, set by + * nsm_change_pathname(), is the same as the built-in default + * parent directory; otherwise false is returned. + */ +_Bool +nsm_is_default_parentdir(void) +{ + return strcmp(nsm_base_dirname, NSM_DEFAULT_STATEDIR) == 0; +} + +/* + * Clear all capabilities but CAP_NET_BIND_SERVICE. This permits + * callers to acquire privileged source ports, but all other root + * capabilities are disallowed. + * + * Returns true if successful, or false if some error occurred. + */ +#ifdef HAVE_SYS_CAPABILITY_H +static _Bool +nsm_clear_capabilities(void) +{ + cap_t caps; + + caps = cap_from_text("cap_net_bind_service=ep"); + if (caps == NULL) { + xlog(L_ERROR, "Failed to allocate capability: %m"); + return false; + } + + if (cap_set_proc(caps) == -1) { + xlog(L_ERROR, "Failed to set capability flags: %m"); + (void)cap_free(caps); + return false; + } + + (void)cap_free(caps); + return true; +} + +#define CAP_BOUND_PROCFILE "/proc/sys/kernel/cap-bound" +static _Bool +prune_bounding_set(void) +{ +#ifdef PR_CAPBSET_DROP + int ret; + unsigned long i; + struct stat st; + + /* + * Prior to kernel 2.6.25, the capabilities bounding set was a global + * value. Check to see if /proc/sys/kernel/cap-bound exists and don't + * bother to clear the bounding set if it does. + */ + ret = stat(CAP_BOUND_PROCFILE, &st); + if (!ret) { + xlog(L_WARNING, "%s exists. Not attempting to clear " + "capabilities bounding set.", + CAP_BOUND_PROCFILE); + return true; + } else if (errno != ENOENT) { + /* Warn, but attempt to clear the bounding set anyway. */ + xlog(L_WARNING, "Unable to stat %s: %m", CAP_BOUND_PROCFILE); + } + + /* prune the bounding set to nothing */ + for (i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >=0 ; ++i) { + ret = prctl(PR_CAPBSET_DROP, i, 0, 0, 0); + if (ret) { + xlog(L_ERROR, "Unable to prune capability %lu from " + "bounding set: %m", i); + return false; + } + } +#endif /* PR_CAPBSET_DROP */ + return true; +} +#else /* !HAVE_SYS_CAPABILITY_H */ +static _Bool +nsm_clear_capabilities(void) +{ + return true; +} + +static _Bool +prune_bounding_set(void) +{ + return true; +} +#endif /* HAVE_SYS_CAPABILITY_H */ + +/** + * nsm_drop_privileges - drop root privileges + * @pidfd: file descriptor of a pid file + * + * Returns true if successful, or false if some error occurred. + * + * Set our effective UID and GID to that of our on-disk database. + */ +_Bool +nsm_drop_privileges(const int pidfd) +{ + struct stat st; + + (void)umask(S_IRWXO); + + if (chdir(nsm_base_dirname) == -1) { + xlog(L_ERROR, "Failed to change working directory to %s: %m", + nsm_base_dirname); + return false; + } + + if (lstat(NSM_MONITOR_DIR, &st) == -1) { + xlog(L_ERROR, "Failed to stat %s/%s: %m", nsm_base_dirname, NSM_MONITOR_DIR); + return false; + } + + if (!prune_bounding_set()) + return false; + + if (st.st_uid == 0) { + xlog_warn("Running as root. " + "chown %s to choose different user", nsm_base_dirname); + return true; + } + + /* + * If the pidfile happens to reside on NFS, dropping privileges + * will probably cause us to lose access, even though we are + * holding it open. Chown it to prevent this. + */ + if (pidfd >= 0) + if (fchown(pidfd, st.st_uid, st.st_gid) == -1) + xlog_warn("Failed to change owner of pidfile: %m"); + + /* + * Don't clear capabilities when dropping root. + */ + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { + xlog(L_ERROR, "prctl(PR_SET_KEEPCAPS) failed: %m"); + return false; + } + + if (setgroups(0, NULL) == -1) { + xlog(L_ERROR, "Failed to drop supplementary groups: %m"); + return false; + } + + /* + * ORDER + * + * setgid(2) first, as setuid(2) may remove privileges needed + * to set the group id. + */ + if (setgid(st.st_gid) == -1 || setuid(st.st_uid) == -1) { + xlog(L_ERROR, "Failed to drop privileges: %m"); + return false; + } + + xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid); + + return nsm_clear_capabilities(); +} + +/** + * nsm_get_state - retrieve on-disk NSM state number + * + * Returns an odd NSM state number read from disk, or an initial + * state number. Zero is returned if some error occurs. + */ +int +nsm_get_state(_Bool update) +{ + int fd, state = 0; + ssize_t result; + char *path = NULL; + + path = nsm_make_pathname(NSM_STATE_FILE); + if (path == NULL) { + xlog(L_ERROR, "Failed to allocate path for " NSM_STATE_FILE); + goto out; + } + + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno != ENOENT) { + xlog(L_ERROR, "Failed to open %s: %m", path); + goto out; + } + + xlog(L_NOTICE, "Initializing NSM state"); + state = 1; + update = true; + goto update; + } + + result = read(fd, &state, sizeof(state)); + if (exact_error_check(result, sizeof(state))) { + xlog_warn("Failed to read %s: %m", path); + + xlog(L_NOTICE, "Initializing NSM state"); + state = 1; + update = true; + goto update; + } + + if ((state & 1) == 0) + state++; + +update: + if(fd >= 0) + (void)close(fd); + + if (update) { + state += 2; + if (!nsm_atomic_write(path, &state, sizeof(state))) + state = 0; + } + +out: + free(path); + return state; +} + +/** + * nsm_update_kernel_state - attempt to post new NSM state to kernel + * @state: NSM state number + * + */ +void +nsm_update_kernel_state(const int state) +{ + ssize_t result; + char buf[20]; + int fd, len; + + fd = open(NSM_KERNEL_STATE_FILE, O_WRONLY); + if (fd == -1) { + xlog(D_GENERAL, "Failed to open " NSM_KERNEL_STATE_FILE ": %m"); + return; + } + + len = snprintf(buf, sizeof(buf), "%d", state); + if (error_check(len, sizeof(buf))) { + xlog_warn("Failed to form NSM state number string"); + close(fd); + return; + } + + result = write(fd, buf, strlen(buf)); + if (exact_error_check(result, strlen(buf))) + xlog_warn("Failed to write NSM state number: %m"); + + if (close(fd) == -1) + xlog(L_ERROR, "Failed to close NSM state file " + NSM_KERNEL_STATE_FILE ": %m"); +} + +/** + * nsm_retire_monitored_hosts - back up all hosts from "sm/" to "sm.bak/" + * + * Returns the count of host records that were moved. + * + * Note that if any error occurs during this process, some monitor + * records may be left in the "sm" directory. + */ +unsigned int +nsm_retire_monitored_hosts(void) +{ + unsigned int count = 0; + struct dirent *de; + char *path; + DIR *dir; + + path = nsm_make_pathname(NSM_MONITOR_DIR); + if (path == NULL) { + xlog(L_ERROR, "Failed to allocate path for " NSM_MONITOR_DIR); + return count; + } + + dir = opendir(path); + free(path); + if (dir == NULL) { + xlog_warn("Failed to open " NSM_MONITOR_DIR ": %m"); + return count; + } + + while ((de = readdir(dir)) != NULL) { + char *src, *dst; + struct stat stb; + + if (de->d_name[0] == '.') + continue; + + src = nsm_make_record_pathname(NSM_MONITOR_DIR, de->d_name); + if (src == NULL) { + xlog_warn("Bad monitor file name, skipping"); + continue; + } + + /* NB: not all file systems fill in d_type correctly */ + if (lstat(src, &stb) == -1) { + xlog_warn("Bad monitor file %s, skipping: %m", + de->d_name); + free(src); + continue; + } + if (!S_ISREG(stb.st_mode)) { + xlog(D_GENERAL, "Skipping non-regular file %s", + de->d_name); + free(src); + continue; + } + + dst = nsm_make_record_pathname(NSM_NOTIFY_DIR, de->d_name); + if (dst == NULL) { + free(src); + xlog_warn("Bad notify file name, skipping"); + continue; + } + + if (rename(src, dst) == -1) + xlog_warn("Failed to rename %s -> %s: %m", + src, dst); + else { + xlog(D_GENERAL, "Retired record for mon_name %s", + de->d_name); + count++; + } + + free(dst); + free(src); + } + + (void)closedir(dir); + return count; +} + +/* + * nsm_priv_to_hex - convert a NSM private cookie to a hex string. + * + * @priv: buffer holding the binary NSM private cookie + * @buf: output buffer for NULL terminated hex string + * @buflen: size of output buffer + * + * Returns the length of the resulting string or 0 on error + */ +size_t +nsm_priv_to_hex(const char *priv, char *buf, const size_t buflen) +{ + int i, len; + size_t remaining = buflen; + + for (i = 0; i < SM_PRIV_SIZE; i++) { + len = snprintf(buf, remaining, "%02x", + (unsigned int)(0xff & priv[i])); + if (error_check(len, remaining)) + return 0; + buf += len; + remaining -= (size_t)len; + } + + return buflen - remaining; +} + +/* + * Returns the length in bytes of the created record. + */ +__attribute__((__noinline__)) +static size_t +nsm_create_monitor_record(char *buf, const size_t buflen, + const struct sockaddr *sap, const struct mon *m) +{ + const struct sockaddr_in *sin = (const struct sockaddr_in *)sap; + size_t hexlen, remaining = buflen; + int len; + + len = snprintf(buf, remaining, "%08x %08x %08x %08x ", + (unsigned int)sin->sin_addr.s_addr, + (unsigned int)m->mon_id.my_id.my_prog, + (unsigned int)m->mon_id.my_id.my_vers, + (unsigned int)m->mon_id.my_id.my_proc); + if (error_check(len, remaining)) + return 0; + buf += len; + remaining -= (size_t)len; + + hexlen = nsm_priv_to_hex(m->priv, buf, remaining); + if (hexlen == 0) + return 0; + buf += hexlen; + remaining -= hexlen; + + len = snprintf(buf, remaining, " %s %s\n", + m->mon_id.mon_name, m->mon_id.my_id.my_name); + if (error_check(len, remaining)) + return 0; + remaining -= (size_t)len; + + return buflen - remaining; +} + +static _Bool +nsm_append_monitored_host(const char *path, const char *line) +{ + _Bool result = false; + char *buf = NULL; + struct stat stb; + size_t buflen; + ssize_t len; + int fd; + + if (stat(path, &stb) == -1) { + xlog(L_ERROR, "Failed to insert: " + "could not stat original file %s: %m", path); + goto out; + } + buflen = (size_t)stb.st_size + strlen(line); + + buf = malloc(buflen + 1); + if (buf == NULL) { + xlog(L_ERROR, "Failed to insert: no memory"); + goto out; + } + memset(buf, 0, buflen + 1); + + fd = open(path, O_RDONLY); + if (fd == -1) { + xlog(L_ERROR, "Failed to insert: " + "could not open original file %s: %m", path); + goto out; + } + + len = read(fd, buf, (size_t)stb.st_size); + if (exact_error_check(len, (size_t)stb.st_size)) { + xlog(L_ERROR, "Failed to insert: " + "could not read original file %s: %m", path); + (void)close(fd); + goto out; + } + (void)close(fd); + + strcat(buf, line); + + if (nsm_atomic_write(path, buf, buflen)) + result = true; + +out: + free(buf); + return result; +} + +/** + * nsm_insert_monitored_host - write callback data for one host to disk + * @hostname: C string containing a hostname + * @sap: sockaddr containing NLM callback address + * @mon: SM_MON arguments to save + * + * Returns true if successful, otherwise false if some error occurs. + */ +_Bool +nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap, + const struct mon *m) +{ + static char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; + char *path; + _Bool result = false; + ssize_t len; + size_t size; + int fd; + + path = nsm_make_record_pathname(NSM_MONITOR_DIR, hostname); + if (path == NULL) { + xlog(L_ERROR, "Failed to insert: bad monitor hostname '%s'", + hostname); + return false; + } + + size = nsm_create_monitor_record(buf, sizeof(buf), sap, m); + if (size == 0) { + xlog(L_ERROR, "Failed to insert: record too long"); + goto out; + } + + /* + * If exclusive create fails, we're adding a new line to an + * existing file. + */ + fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_SYNC, S_IRUSR | S_IWUSR); + if (fd == -1) { + if (errno != EEXIST) { + xlog(L_ERROR, "Failed to insert: creating %s: %m", path); + goto out; + } + + result = nsm_append_monitored_host(path, buf); + goto out; + } + result = true; + + len = write(fd, buf, size); + if (exact_error_check(len, size)) { + xlog_warn("Failed to insert: writing %s: %m", path); + (void)unlink(path); + result = false; + } + + if (close(fd) == -1) { + xlog(L_ERROR, "Failed to insert: closing %s: %m", path); + (void)unlink(path); + result = false; + } + +out: + free(path); + return result; +} + +__attribute__((__noinline__)) +static _Bool +nsm_parse_line(char *line, struct sockaddr_in *sin, struct mon *m) +{ + unsigned int i, tmp; + int count; + char *c; + + c = strchr(line, '\n'); + if (c != NULL) + *c = '\0'; + + count = sscanf(line, "%8x %8x %8x %8x ", + (unsigned int *)&sin->sin_addr.s_addr, + (unsigned int *)&m->mon_id.my_id.my_prog, + (unsigned int *)&m->mon_id.my_id.my_vers, + (unsigned int *)&m->mon_id.my_id.my_proc); + if (count != 4) + return false; + + c = line + RPCARGSLEN; + for (i = 0; i < SM_PRIV_SIZE; i++) { + if (sscanf(c, "%2x", &tmp) != 1) + return false; + m->priv[i] = (char)tmp; + c += 2; + } + + c++; + m->mon_id.mon_name = c; + while (*c != '\0' && *c != ' ') + c++; + if (*c != '\0') + *c++ = '\0'; + while (*c == ' ') + c++; + m->mon_id.my_id.my_name = c; + + return true; +} + +/* + * Stuff a 'struct mon' with callback data, and call @func. + * + * Returns the count of in-core records created. + */ +static unsigned int +nsm_read_line(const char *hostname, const time_t timestamp, char *line, + nsm_populate_t func) +{ + struct sockaddr_in sin = { + .sin_family = AF_INET, + }; + struct mon m; + + if (!nsm_parse_line(line, &sin, &m)) + return 0; + + return func(hostname, (struct sockaddr *)(char *)&sin, &m, timestamp); +} + +/* + * Given a filename, reads data from a file under "directory" + * and invokes @func so caller can populate their in-core + * database with this data. + */ +static unsigned int +nsm_load_host(const char *directory, const char *filename, nsm_populate_t func) +{ + char buf[LINELEN + 1 + SM_MAXSTRLEN + 2]; + unsigned int result = 0; + struct stat stb; + char *path; + FILE *f; + + path = nsm_make_record_pathname(directory, filename); + if (path == NULL) + goto out_err; + + if (lstat(path, &stb) == -1) { + xlog(L_ERROR, "Failed to stat %s: %m", path); + goto out_freepath; + } + if (!S_ISREG(stb.st_mode)) { + xlog(D_GENERAL, "Skipping non-regular file %s", + path); + goto out_freepath; + } + + f = fopen(path, "r"); + if (f == NULL) { + xlog(L_ERROR, "Failed to open %s: %m", path); + goto out_freepath; + } + + while (fgets(buf, (int)sizeof(buf), f) != NULL) { + buf[sizeof(buf) - 1] = '\0'; + result += nsm_read_line(filename, stb.st_mtime, buf, func); + } + if (result == 0) + xlog(L_ERROR, "Failed to read monitor data from %s", path); + + (void)fclose(f); + +out_freepath: + free(path); +out_err: + return result; +} + +static unsigned int +nsm_load_dir(const char *directory, nsm_populate_t func) +{ + unsigned int count = 0; + struct dirent *de; + char *path; + DIR *dir; + + path = nsm_make_pathname(directory); + if (path == NULL) { + xlog(L_ERROR, "Failed to allocate path for directory %s", + directory); + return 0; + } + + dir = opendir(path); + free(path); + if (dir == NULL) { + xlog(L_ERROR, "Failed to open directory %s: %m", + directory); + return 0; + } + + while ((de = readdir(dir)) != NULL) { + if (de->d_name[0] == '.') + continue; + + count += nsm_load_host(directory, de->d_name, func); + } + + (void)closedir(dir); + return count; +} + +/** + * nsm_load_monitor_list - load list of hosts to monitor + * @func: callback function to create entry for one host + * + * Returns the count of hosts that were found in the directory. + */ +unsigned int +nsm_load_monitor_list(nsm_populate_t func) +{ + return nsm_load_dir(NSM_MONITOR_DIR, func); +} + +/** + * nsm_load_notify_list - load list of hosts to notify + * @func: callback function to create entry for one host + * + * Returns the count of hosts that were found in the directory. + */ +unsigned int +nsm_load_notify_list(nsm_populate_t func) +{ + return nsm_load_dir(NSM_NOTIFY_DIR, func); +} + +static void +nsm_delete_host(const char *directory, const char *hostname, + const char *mon_name, const char *my_name, const int chatty) +{ + char line[LINELEN + 1 + SM_MAXSTRLEN + 2]; + char *outbuf = NULL; + struct stat stb; + char *path, *next; + size_t remaining; + FILE *f; + + path = nsm_make_record_pathname(directory, hostname); + if (path == NULL) { + xlog(L_ERROR, "Bad filename, not deleting"); + return; + } + + if (stat(path, &stb) == -1) { + if (chatty) + xlog(L_ERROR, "Failed to delete: " + "could not stat original file %s: %m", path); + goto out; + } + remaining = (size_t)stb.st_size + 1; + + outbuf = malloc(remaining); + if (outbuf == NULL) { + xlog(L_ERROR, "Failed to delete: no memory"); + goto out; + } + + f = fopen(path, "r"); + if (f == NULL) { + xlog(L_ERROR, "Failed to delete: " + "could not open original file %s: %m", path); + goto out; + } + + /* + * Walk the records in the file, and copy the non-matching + * ones to our output buffer. + */ + next = outbuf; + while (fgets(line, (int)sizeof(line), f) != NULL) { + struct sockaddr_in sin; + struct mon m; + size_t len; + + if (!nsm_parse_line(line, &sin, &m)) { + xlog(L_ERROR, "Failed to delete: " + "could not parse original file %s", path); + (void)fclose(f); + goto out; + } + + if (strcmp(mon_name, m.mon_id.mon_name) == 0 && + strcmp(my_name, m.mon_id.my_id.my_name) == 0) + continue; + + /* nsm_parse_line destroys the contents of line[], so + * reconstruct the copy in our output buffer. */ + len = nsm_create_monitor_record(next, remaining, + (struct sockaddr *)(char *)&sin, &m); + if (len == 0) { + xlog(L_ERROR, "Failed to delete: " + "could not construct output record"); + (void)fclose(f); + goto out; + } + next += len; + remaining -= len; + } + + (void)fclose(f); + + /* + * If nothing was copied when we're done, then unlink the file. + * Otherwise, atomically update the contents of the file. + */ + if (next != outbuf) { + if (!nsm_atomic_write(path, outbuf, strlen(outbuf))) + xlog(L_ERROR, "Failed to delete: " + "could not write new file %s: %m", path); + } else { + if (unlink(path) == -1) + xlog(L_ERROR, "Failed to delete: " + "could not unlink file %s: %m", path); + } + +out: + free(outbuf); + free(path); +} + +/** + * nsm_delete_monitored_host - delete on-disk record for monitored host + * @hostname: '\0'-terminated C string containing hostname of record to delete + * @mon_name: '\0'-terminated C string containing monname of record to delete + * @my_name: '\0'-terminated C string containing myname of record to delete + * @chatty: should an error be logged if the monitor file doesn't exist? + * + */ +void +nsm_delete_monitored_host(const char *hostname, const char *mon_name, + const char *my_name, const int chatty) +{ + nsm_delete_host(NSM_MONITOR_DIR, hostname, mon_name, my_name, chatty); +} + +/** + * nsm_delete_notified_host - delete on-disk host record after notification + * @hostname: '\0'-terminated C string containing hostname of record to delete + * @mon_name: '\0'-terminated C string containing monname of record to delete + * @my_name: '\0'-terminated C string containing myname of record to delete + * + */ +void +nsm_delete_notified_host(const char *hostname, const char *mon_name, + const char *my_name) +{ + nsm_delete_host(NSM_NOTIFY_DIR, hostname, mon_name, my_name, 1); +} |