| /* |
| * 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> |
| #include <sys/capability.h> |
| #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" |
| |
| #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" |
| |
| /* |
| * Some distributions place statd's files in a subdirectory |
| */ |
| #define NSM_PATH_EXTENSION |
| /* #define NSM_PATH_EXTENSION "/statd" */ |
| |
| #define NSM_DEFAULT_STATEDIR NFS_STATEDIR NSM_PATH_EXTENSION |
| |
| 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) |
| { |
| size_t size; |
| char *path; |
| int len; |
| |
| size = strlen(nsm_base_dirname) + strlen(directory) + 2; |
| if (size > PATH_MAX) |
| return NULL; |
| |
| path = malloc(size); |
| if (path == NULL) |
| return NULL; |
| |
| len = snprintf(path, size, "%s/%s", nsm_base_dirname, directory); |
| if (error_check(len, size)) { |
| 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_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) |
| { |
| static char buf[PATH_MAX]; |
| struct stat st; |
| char *path; |
| |
| /* First: test length of name and whether it exists */ |
| if (lstat(parentdir, &st) == -1) { |
| (void)fprintf(stderr, "%s: Failed to stat %s: %s", |
| progname, parentdir, strerror(errno)); |
| return false; |
| } |
| |
| /* Ensure we have a clean directory pathname */ |
| strncpy(buf, parentdir, sizeof(buf)); |
| path = dirname(buf); |
| if (*path == '.') { |
| (void)fprintf(stderr, "%s: Unusable directory %s", |
| progname, parentdir); |
| return false; |
| } |
| |
| xlog(D_CALL, "Using %s as the state directory", parentdir); |
| strncpy(nsm_base_dirname, parentdir, sizeof(nsm_base_dirname)); |
| return true; |
| } |
| |
| /** |
| * 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. |
| */ |
| 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; |
| } |
| |
| /** |
| * 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); |
| |
| /* |
| * XXX: If we can't stat dirname, or if dirname is owned by |
| * root, we should use "statduser" instead, which is set up |
| * by configure.ac. Nothing in nfs-utils seems to use |
| * "statduser," though. |
| */ |
| if (lstat(nsm_base_dirname, &st) == -1) { |
| xlog(L_ERROR, "Failed to stat %s: %m", nsm_base_dirname); |
| return false; |
| } |
| |
| if (st.st_uid == 0) { |
| xlog_warn("Running as root. " |
| "chown %s to choose different user", nsm_base_dirname); |
| return true; |
| } |
| |
| if (chdir(nsm_base_dirname) == -1) { |
| xlog(L_ERROR, "Failed to change working directory to %s: %m", |
| nsm_base_dirname); |
| return false; |
| } |
| |
| /* |
| * 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 0; |
| } |
| |
| 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: |
| (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"); |
| 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; |
| |
| if (de->d_type != (unsigned char)DT_REG) |
| continue; |
| 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; |
| } |
| |
| 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 NSM_MONITOR_DIR |
| * 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 (stat(path, &stb) == -1) { |
| xlog(L_ERROR, "Failed to stat %s: %m", 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_type != (unsigned char)DT_REG) |
| continue; |
| 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) |
| { |
| 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) { |
| 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 |
| * |
| */ |
| void |
| nsm_delete_monitored_host(const char *hostname, const char *mon_name, |
| const char *my_name) |
| { |
| nsm_delete_host(NSM_MONITOR_DIR, hostname, mon_name, my_name); |
| } |
| |
| /** |
| * 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); |
| } |