X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=support%2Fnsm%2Ffile.c;h=5dd52c1e2640fb2effd07004c8902700811aa775;hp=10769d9df9e87fdc4ff7302606d7fa59ea818e88;hb=5c3e26650a54af7826a2b4c6759b56681a350c37;hpb=b148d3414a8d574ff7883ad99d3d1dd980a12603 diff --git a/support/nsm/file.c b/support/nsm/file.c index 10769d9..5dd52c1 100644 --- a/support/nsm/file.c +++ b/support/nsm/file.c @@ -67,6 +67,10 @@ #endif #include +#ifdef HAVE_SYS_CAPABILITY_H +#include +#endif +#include #include #include @@ -90,14 +94,6 @@ #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" @@ -122,7 +118,7 @@ exact_error_check(const ssize_t len, const size_t buflen) * containing an appropriate pathname, or NULL if an error * occurs. Caller must free the returned result with free(3). */ -__attribute_malloc__ +__attribute__((__malloc__)) static char * nsm_make_record_pathname(const char *directory, const char *hostname) { @@ -170,7 +166,7 @@ nsm_make_record_pathname(const char *directory, const char *hostname) * containing an appropriate pathname, or NULL if an error * occurs. Caller must free the returned result with free(3). */ -__attribute_malloc__ +__attribute__((__malloc__)) static char * nsm_make_pathname(const char *directory) { @@ -200,7 +196,7 @@ nsm_make_pathname(const char *directory) * containing an appropriate pathname, or NULL if an error * occurs. Caller must free the returned result with free(3). */ -__attribute_malloc__ +__attribute__((__malloc__)) static char * nsm_make_temp_pathname(const char *pathname) { @@ -335,6 +331,36 @@ 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) +{ +#ifdef HAVE_SYS_CAPABILITY_H + 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); +#endif + return true; +} + /** * nsm_drop_privileges - drop root privileges * @pidfd: file descriptor of a pid file @@ -361,18 +387,18 @@ nsm_drop_privileges(const int pidfd) 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 (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 @@ -382,6 +408,14 @@ nsm_drop_privileges(const int pidfd) 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; @@ -399,7 +433,8 @@ nsm_drop_privileges(const int pidfd) } xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid); - return true; + + return nsm_clear_capabilities(); } /** @@ -525,9 +560,8 @@ nsm_retire_monitored_hosts(void) while ((de = readdir(dir)) != NULL) { char *src, *dst; + struct stat stb; - if (de->d_type != (unsigned char)DT_REG) - continue; if (de->d_name[0] == '.') continue; @@ -537,6 +571,20 @@ nsm_retire_monitored_hosts(void) 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); @@ -591,7 +639,7 @@ nsm_priv_to_hex(const char *priv, char *buf, const size_t buflen) /* * Returns the length in bytes of the created record. */ -__attribute_noinline__ +__attribute__((__noinline__)) static size_t nsm_create_monitor_record(char *buf, const size_t buflen, const struct sockaddr *sap, const struct mon *m) @@ -625,6 +673,56 @@ nsm_create_monitor_record(char *buf, const size_t buflen, 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 @@ -657,9 +755,18 @@ nsm_insert_monitored_host(const char *hostname, const struct sockaddr *sap, goto out; } - fd = open(path, O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR); + /* + * 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) { - xlog(L_ERROR, "Failed to insert: creating %s: %m", path); + 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; @@ -682,7 +789,7 @@ out: return result; } -__attribute_noinline__ +__attribute__((__noinline__)) static _Bool nsm_parse_line(char *line, struct sockaddr_in *sin, struct mon *m) { @@ -744,7 +851,7 @@ nsm_read_line(const char *hostname, const time_t timestamp, char *line, } /* - * Given a filename, reads data from a file under NSM_MONITOR_DIR + * Given a filename, reads data from a file under "directory" * and invokes @func so caller can populate their in-core * database with this data. */ @@ -761,10 +868,15 @@ nsm_load_host(const char *directory, const char *filename, nsm_populate_t func) if (path == NULL) goto out_err; - if (stat(path, &stb) == -1) { + 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) { @@ -811,8 +923,6 @@ nsm_load_dir(const char *directory, nsm_populate_t func) } while ((de = readdir(dir)) != NULL) { - if (de->d_type != (unsigned char)DT_REG) - continue; if (de->d_name[0] == '.') continue; @@ -848,9 +958,15 @@ nsm_load_notify_list(nsm_populate_t func) } static void -nsm_delete_host(const char *directory, const char *hostname) +nsm_delete_host(const char *directory, const char *hostname, + const char *mon_name, const char *my_name) { - char *path; + 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) { @@ -858,30 +974,106 @@ nsm_delete_host(const char *directory, const char *hostname) return; } - if (unlink(path) == -1) - xlog(L_ERROR, "Failed to unlink %s: %m", path); + 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) +nsm_delete_monitored_host(const char *hostname, const char *mon_name, + const char *my_name) { - nsm_delete_host(NSM_MONITOR_DIR, hostname); + 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) +nsm_delete_notified_host(const char *hostname, const char *mon_name, + const char *my_name) { - nsm_delete_host(NSM_NOTIFY_DIR, hostname); + nsm_delete_host(NSM_NOTIFY_DIR, hostname, mon_name, my_name); }