#endif
#include <sys/types.h>
+#include <sys/capability.h>
+#include <sys/prctl.h>
#include <sys/stat.h>
#include <ctype.h>
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
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
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;
}
xlog(D_CALL, "Effective UID, GID: %u, %u", st.st_uid, st.st_gid);
- return true;
+
+ return nsm_clear_capabilities();
}
/**
int fd, state = 0;
ssize_t result;
char *path = NULL;
- char *newpath = NULL;
path = nsm_make_pathname(NSM_STATE_FILE);
if (path == NULL) {
if (update) {
state += 2;
-
- newpath = nsm_make_pathname(NSM_STATE_FILE ".new");
- if (newpath == NULL) {
- xlog(L_ERROR,
- "Failed to create path for " NSM_STATE_FILE ".new");
+ if (!nsm_atomic_write(path, &state, sizeof(state)))
state = 0;
- goto out;
- }
-
- fd = open(newpath, O_CREAT | O_SYNC | O_WRONLY, 0644);
- if (fd == -1) {
- xlog(L_ERROR, "Failed to create %s: %m", newpath);
- state = 0;
- goto out;
- }
-
- result = write(fd, &state, sizeof(state));
- if (exact_error_check(result, sizeof(state))) {
- xlog(L_ERROR, "Failed to write %s: %m", newpath);
- (void)close(fd);
- (void)unlink(newpath);
- state = 0;
- goto out;
- }
-
- if (close(fd) == -1) {
- xlog(L_ERROR, "Failed to close %s: %m", newpath);
- (void)unlink(newpath);
- state = 0;
- goto out;
- }
-
- if (rename(newpath, path) == -1) {
- xlog(L_ERROR, "Failed to rename %s -> %s: %m",
- newpath, path);
- (void)unlink(newpath);
- state = 0;
- 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. */
}
out:
- free(newpath);
free(path);
return state;
}
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
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;
}
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) {
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);
}