/*- * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * This software was developed by Pawel Jakub Dawidek under sponsorship from * the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #if defined(HAVE_SYS_ENDIAN_H) && defined(HAVE_BSWAP) #include #else /* !HAVE_SYS_ENDIAN_H || !HAVE_BSWAP */ #ifdef HAVE_MACHINE_ENDIAN_H #include #else /* !HAVE_MACHINE_ENDIAN_H */ #ifdef HAVE_ENDIAN_H #include #else /* !HAVE_ENDIAN_H */ #error "No supported endian.h" #endif /* !HAVE_ENDIAN_H */ #endif /* !HAVE_MACHINE_ENDIAN_H */ #include #endif /* !HAVE_SYS_ENDIAN_H || !HAVE_BSWAP */ #include #include #include #include #include #include #ifdef HAVE_LIBUTIL_H #include #endif #include #include #include #include #include #include #include #include #ifndef HAVE_STRLCPY #include #endif #ifndef HAVE_FSTATAT #include "fstatat.h" #endif #ifndef HAVE_OPENAT #include "openat.h" #endif #ifndef HAVE_RENAMEAT #include "renameat.h" #endif #include "auditdistd.h" #include "pjdlog.h" #include "proto.h" #include "sandbox.h" #include "subr.h" #include "synch.h" #include "trail.h" static struct adist_config *adcfg; static struct adist_host *adhost; static TAILQ_HEAD(, adreq) adist_free_list; static pthread_mutex_t adist_free_list_lock; static pthread_cond_t adist_free_list_cond; static TAILQ_HEAD(, adreq) adist_disk_list; static pthread_mutex_t adist_disk_list_lock; static pthread_cond_t adist_disk_list_cond; static TAILQ_HEAD(, adreq) adist_send_list; static pthread_mutex_t adist_send_list_lock; static pthread_cond_t adist_send_list_cond; static void adreq_clear(struct adreq *adreq) { adreq->adr_error = -1; adreq->adr_byteorder = ADIST_BYTEORDER_UNDEFINED; adreq->adr_cmd = ADIST_CMD_UNDEFINED; adreq->adr_seq = 0; adreq->adr_datasize = 0; } static void init_environment(void) { struct adreq *adreq; unsigned int ii; TAILQ_INIT(&adist_free_list); mtx_init(&adist_free_list_lock); cv_init(&adist_free_list_cond); TAILQ_INIT(&adist_disk_list); mtx_init(&adist_disk_list_lock); cv_init(&adist_disk_list_cond); TAILQ_INIT(&adist_send_list); mtx_init(&adist_send_list_lock); cv_init(&adist_send_list_cond); for (ii = 0; ii < ADIST_QUEUE_SIZE; ii++) { adreq = malloc(sizeof(*adreq) + ADIST_BUF_SIZE); if (adreq == NULL) { pjdlog_exitx(EX_TEMPFAIL, "Unable to allocate %zu bytes of memory for adreq object.", sizeof(*adreq) + ADIST_BUF_SIZE); } adreq_clear(adreq); TAILQ_INSERT_TAIL(&adist_free_list, adreq, adr_next); } } static void adreq_decode_and_validate_header(struct adreq *adreq) { /* Byte-swap only is the sender is using different byte order. */ if (adreq->adr_byteorder != ADIST_BYTEORDER) { adreq->adr_byteorder = ADIST_BYTEORDER; adreq->adr_seq = bswap64(adreq->adr_seq); adreq->adr_datasize = bswap32(adreq->adr_datasize); } /* Validate packet header. */ if (adreq->adr_datasize > ADIST_BUF_SIZE) { pjdlog_exitx(EX_PROTOCOL, "Invalid datasize received (%ju).", (uintmax_t)adreq->adr_datasize); } switch (adreq->adr_cmd) { case ADIST_CMD_OPEN: case ADIST_CMD_APPEND: case ADIST_CMD_CLOSE: if (adreq->adr_datasize == 0) { pjdlog_exitx(EX_PROTOCOL, "Invalid datasize received (%ju).", (uintmax_t)adreq->adr_datasize); } break; case ADIST_CMD_KEEPALIVE: case ADIST_CMD_ERROR: if (adreq->adr_datasize > 0) { pjdlog_exitx(EX_PROTOCOL, "Invalid datasize received (%ju).", (uintmax_t)adreq->adr_datasize); } break; default: pjdlog_exitx(EX_PROTOCOL, "Invalid command received (%hhu).", adreq->adr_cmd); } } static void adreq_validate_data(const struct adreq *adreq) { /* Validate packet data. */ switch (adreq->adr_cmd) { case ADIST_CMD_OPEN: case ADIST_CMD_CLOSE: /* * File name must end up with '\0' and there must be no '\0' * in the middle. */ if (adreq->adr_data[adreq->adr_datasize - 1] != '\0' || strchr(adreq->adr_data, '\0') != (const char *)adreq->adr_data + adreq->adr_datasize - 1) { pjdlog_exitx(EX_PROTOCOL, "Invalid file name received."); } break; } } /* * Thread receives requests from the sender. */ static void * recv_thread(void *arg __unused) { struct adreq *adreq; for (;;) { pjdlog_debug(3, "recv: Taking free request."); QUEUE_TAKE(adreq, &adist_free_list, 0); pjdlog_debug(3, "recv: (%p) Got request.", adreq); if (proto_recv(adhost->adh_remote, &adreq->adr_packet, sizeof(adreq->adr_packet)) == -1) { pjdlog_exit(EX_TEMPFAIL, "Unable to receive request header"); } adreq_decode_and_validate_header(adreq); switch (adreq->adr_cmd) { case ADIST_CMD_KEEPALIVE: adreq->adr_error = 0; adreq_log(LOG_DEBUG, 2, -1, adreq, "recv: (%p) Got request header: ", adreq); pjdlog_debug(3, "recv: (%p) Moving request to the send queue.", adreq); QUEUE_INSERT(adreq, &adist_send_list); continue; case ADIST_CMD_ERROR: pjdlog_error("An error occured on the sender while reading \"%s/%s\".", adhost->adh_directory, adhost->adh_trail_name); adreq_log(LOG_DEBUG, 2, ADIST_ERROR_READ, adreq, "recv: (%p) Got request header: ", adreq); pjdlog_debug(3, "recv: (%p) Moving request to the send queue.", adreq); QUEUE_INSERT(adreq, &adist_disk_list); continue; case ADIST_CMD_OPEN: case ADIST_CMD_APPEND: case ADIST_CMD_CLOSE: if (proto_recv(adhost->adh_remote, adreq->adr_data, adreq->adr_datasize) == -1) { pjdlog_exit(EX_TEMPFAIL, "Unable to receive request data"); } adreq_validate_data(adreq); adreq_log(LOG_DEBUG, 2, -1, adreq, "recv: (%p) Got request header: ", adreq); pjdlog_debug(3, "recv: (%p) Moving request to the disk queue.", adreq); QUEUE_INSERT(adreq, &adist_disk_list); break; default: PJDLOG_ABORT("Invalid condition."); } } /* NOTREACHED */ return (NULL); } /* * Function that opens trail file requested by the sender. * If the file already exist, it has to be the most recent file and it can * only be open for append. * If the file doesn't already exist, it has to be "older" than all existing * files. */ static int receiver_open(const char *filename) { int fd; /* * Previous file should be closed by now. Sending OPEN request without * sending CLOSE for the previous file is a sender bug. */ if (adhost->adh_trail_fd != -1) { pjdlog_error("Sender requested opening file \"%s\" without first closing \"%s\".", filename, adhost->adh_trail_name); return (ADIST_ERROR_WRONG_ORDER); } if (!trail_validate_name(filename, NULL)) { pjdlog_error("Sender wants to open file \"%s\", which has invalid name.", filename); return (ADIST_ERROR_INVALID_NAME); } switch (trail_name_compare(filename, adhost->adh_trail_name)) { case TRAIL_RENAMED: if (!trail_is_not_terminated(adhost->adh_trail_name)) { pjdlog_error("Terminated trail \"%s/%s\" was unterminated on the sender as \"%s/%s\"?", adhost->adh_directory, adhost->adh_trail_name, adhost->adh_directory, filename); return (ADIST_ERROR_INVALID_NAME); } if (renameat(adhost->adh_trail_dirfd, adhost->adh_trail_name, adhost->adh_trail_dirfd, filename) == -1) { pjdlog_errno(LOG_ERR, "Unable to rename file \"%s/%s\" to \"%s/%s\"", adhost->adh_directory, adhost->adh_trail_name, adhost->adh_directory, filename); PJDLOG_ASSERT(errno > 0); return (ADIST_ERROR_RENAME); } pjdlog_debug(1, "Renamed file \"%s/%s\" to \"%s/%s\".", adhost->adh_directory, adhost->adh_trail_name, adhost->adh_directory, filename); /* FALLTHROUGH */ case TRAIL_IDENTICAL: /* Opening existing file. */ fd = openat(adhost->adh_trail_dirfd, filename, O_WRONLY | O_APPEND | O_NOFOLLOW); if (fd == -1) { pjdlog_errno(LOG_ERR, "Unable to open file \"%s/%s\" for append", adhost->adh_directory, filename); PJDLOG_ASSERT(errno > 0); return (ADIST_ERROR_OPEN); } pjdlog_debug(1, "Opened file \"%s/%s\".", adhost->adh_directory, filename); break; case TRAIL_NEWER: /* Opening new file. */ fd = openat(adhost->adh_trail_dirfd, filename, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0600); if (fd == -1) { pjdlog_errno(LOG_ERR, "Unable to create file \"%s/%s\"", adhost->adh_directory, filename); PJDLOG_ASSERT(errno > 0); return (ADIST_ERROR_CREATE); } pjdlog_debug(1, "Created file \"%s/%s\".", adhost->adh_directory, filename); break; case TRAIL_OLDER: /* Trying to open old file. */ pjdlog_error("Sender wants to open an old file \"%s\".", filename); return (ADIST_ERROR_OPEN_OLD); default: PJDLOG_ABORT("Unknown return value from trail_name_compare()."); } PJDLOG_VERIFY(strlcpy(adhost->adh_trail_name, filename, sizeof(adhost->adh_trail_name)) < sizeof(adhost->adh_trail_name)); adhost->adh_trail_fd = fd; return (0); } /* * Function appends data to the trail file that is currently open. */ static int receiver_append(const unsigned char *data, size_t size) { ssize_t done; size_t osize; /* We should have opened trail file. */ if (adhost->adh_trail_fd == -1) { pjdlog_error("Sender requested append without first opening file."); return (ADIST_ERROR_WRONG_ORDER); } osize = size; while (size > 0) { done = write(adhost->adh_trail_fd, data, size); if (done == -1) { if (errno == EINTR) continue; pjdlog_errno(LOG_ERR, "Write to \"%s/%s\" failed", adhost->adh_directory, adhost->adh_trail_name); PJDLOG_ASSERT(errno > 0); return (ADIST_ERROR_WRITE); } pjdlog_debug(3, "Wrote %zd bytes into \"%s/%s\".", done, adhost->adh_directory, adhost->adh_trail_name); size -= done; } pjdlog_debug(2, "Appended %zu bytes to file \"%s/%s\".", osize, adhost->adh_directory, adhost->adh_trail_name); return (0); } static int receiver_close(const char *filename) { /* We should have opened trail file. */ if (adhost->adh_trail_fd == -1) { pjdlog_error("Sender requested closing file without first opening it."); return (ADIST_ERROR_WRONG_ORDER); } /* Validate if we can do the rename. */ if (!trail_validate_name(adhost->adh_trail_name, filename)) { pjdlog_error("Sender wants to close file \"%s\" using name \"%s\".", adhost->adh_trail_name, filename); return (ADIST_ERROR_INVALID_NAME); } PJDLOG_VERIFY(close(adhost->adh_trail_fd) == 0); adhost->adh_trail_fd = -1; pjdlog_debug(1, "Closed file \"%s/%s\".", adhost->adh_directory, adhost->adh_trail_name); if (strcmp(adhost->adh_trail_name, filename) == 0) { /* File name didn't change, we are done here. */ return (0); } if (renameat(adhost->adh_trail_dirfd, adhost->adh_trail_name, adhost->adh_trail_dirfd, filename) == -1) { pjdlog_errno(LOG_ERR, "Unable to rename \"%s\" to \"%s\"", adhost->adh_trail_name, filename); PJDLOG_ASSERT(errno > 0); return (ADIST_ERROR_RENAME); } pjdlog_debug(1, "Renamed file \"%s/%s\" to \"%s/%s\".", adhost->adh_directory, adhost->adh_trail_name, adhost->adh_directory, filename); PJDLOG_VERIFY(strlcpy(adhost->adh_trail_name, filename, sizeof(adhost->adh_trail_name)) < sizeof(adhost->adh_trail_name)); return (0); } static int receiver_error(void) { /* We should have opened trail file. */ if (adhost->adh_trail_fd == -1) { pjdlog_error("Sender send read error, but file is not open."); return (ADIST_ERROR_WRONG_ORDER); } PJDLOG_VERIFY(close(adhost->adh_trail_fd) == 0); adhost->adh_trail_fd = -1; pjdlog_debug(1, "Closed file \"%s/%s\".", adhost->adh_directory, adhost->adh_trail_name); return (0); } static void * disk_thread(void *arg __unused) { struct adreq *adreq; for (;;) { pjdlog_debug(3, "disk: Taking request."); QUEUE_TAKE(adreq, &adist_disk_list, 0); adreq_log(LOG_DEBUG, 3, -1, adreq, "disk: (%p) Got request: ", adreq); /* Handle the actual request. */ switch (adreq->adr_cmd) { case ADIST_CMD_OPEN: adreq->adr_error = receiver_open(adreq->adr_data); break; case ADIST_CMD_APPEND: adreq->adr_error = receiver_append(adreq->adr_data, adreq->adr_datasize); break; case ADIST_CMD_CLOSE: adreq->adr_error = receiver_close(adreq->adr_data); break; case ADIST_CMD_ERROR: adreq->adr_error = receiver_error(); break; default: PJDLOG_ABORT("Unexpected command (cmd=%hhu).", adreq->adr_cmd); } if (adreq->adr_error != 0) { adreq_log(LOG_ERR, 0, adreq->adr_error, adreq, "Request failed: "); } pjdlog_debug(3, "disk: (%p) Moving request to the send queue.", adreq); QUEUE_INSERT(adreq, &adist_send_list); } /* NOTREACHED */ return (NULL); } /* * Thread sends requests back to primary node. */ static void * send_thread(void *arg __unused) { struct adreq *adreq; struct adrep adrep; for (;;) { pjdlog_debug(3, "send: Taking request."); QUEUE_TAKE(adreq, &adist_send_list, 0); adreq_log(LOG_DEBUG, 3, -1, adreq, "send: (%p) Got request: ", adreq); adrep.adrp_byteorder = ADIST_BYTEORDER; adrep.adrp_seq = adreq->adr_seq; adrep.adrp_error = adreq->adr_error; if (proto_send(adhost->adh_remote, &adrep, sizeof(adrep)) == -1) { pjdlog_exit(EX_TEMPFAIL, "Unable to send reply"); } pjdlog_debug(3, "send: (%p) Moving request to the free queue.", adreq); adreq_clear(adreq); QUEUE_INSERT(adreq, &adist_free_list); } /* NOTREACHED */ return (NULL); } static void receiver_directory_create(void) { struct passwd *pw; /* * According to getpwnam(3) we have to clear errno before calling the * function to be able to distinguish between an error and missing * entry (with is not treated as error by getpwnam(3)). */ errno = 0; pw = getpwnam(ADIST_USER); if (pw == NULL) { if (errno != 0) { pjdlog_exit(EX_NOUSER, "Unable to find info about '%s' user", ADIST_USER); } else { pjdlog_exitx(EX_NOUSER, "User '%s' doesn't exist.", ADIST_USER); } } if (mkdir(adhost->adh_directory, 0700) == -1) { pjdlog_exit(EX_OSFILE, "Unable to create directory \"%s\"", adhost->adh_directory); } if (chown(adhost->adh_directory, pw->pw_uid, pw->pw_gid) == -1) { pjdlog_errno(LOG_ERR, "Unable to change owner of the directory \"%s\"", adhost->adh_directory); (void)rmdir(adhost->adh_directory); exit(EX_OSFILE); } } static void receiver_directory_open(void) { #ifdef HAVE_FDOPENDIR adhost->adh_trail_dirfd = open(adhost->adh_directory, O_RDONLY | O_DIRECTORY); if (adhost->adh_trail_dirfd == -1) { if (errno == ENOENT) { receiver_directory_create(); adhost->adh_trail_dirfd = open(adhost->adh_directory, O_RDONLY | O_DIRECTORY); } if (adhost->adh_trail_dirfd == -1) { pjdlog_exit(EX_CONFIG, "Unable to open directory \"%s\"", adhost->adh_directory); } } adhost->adh_trail_dirfp = fdopendir(adhost->adh_trail_dirfd); if (adhost->adh_trail_dirfp == NULL) { pjdlog_exit(EX_CONFIG, "Unable to fdopen directory \"%s\"", adhost->adh_directory); } #else struct stat sb; if (stat(adhost->adh_directory, &sb) == -1) { if (errno == ENOENT) { receiver_directory_create(); } else { pjdlog_exit(EX_CONFIG, "Unable to stat directory \"%s\"", adhost->adh_directory); } } adhost->adh_trail_dirfp = opendir(adhost->adh_directory); if (adhost->adh_trail_dirfp == NULL) { pjdlog_exit(EX_CONFIG, "Unable to open directory \"%s\"", adhost->adh_directory); } adhost->adh_trail_dirfd = dirfd(adhost->adh_trail_dirfp); #endif } static void receiver_connect(void) { uint64_t trail_size; struct stat sb; PJDLOG_ASSERT(adhost->adh_trail_dirfp != NULL); trail_last(adhost->adh_trail_dirfp, adhost->adh_trail_name, sizeof(adhost->adh_trail_name)); if (adhost->adh_trail_name[0] == '\0') { trail_size = 0; } else { if (fstatat(adhost->adh_trail_dirfd, adhost->adh_trail_name, &sb, AT_SYMLINK_NOFOLLOW) == -1) { pjdlog_exit(EX_CONFIG, "Unable to stat \"%s/%s\"", adhost->adh_directory, adhost->adh_trail_name); } if (!S_ISREG(sb.st_mode)) { pjdlog_exitx(EX_CONFIG, "File \"%s/%s\" is not a regular file.", adhost->adh_directory, adhost->adh_trail_name); } trail_size = sb.st_size; } trail_size = htole64(trail_size); if (proto_send(adhost->adh_remote, &trail_size, sizeof(trail_size)) == -1) { pjdlog_exit(EX_TEMPFAIL, "Unable to send size of the most recent trail file"); } if (proto_send(adhost->adh_remote, adhost->adh_trail_name, sizeof(adhost->adh_trail_name)) == -1) { pjdlog_exit(EX_TEMPFAIL, "Unable to send name of the most recent trail file"); } } void adist_receiver(struct adist_config *config, struct adist_host *adh) { sigset_t mask; pthread_t td; pid_t pid; int error, mode, debuglevel; pid = fork(); if (pid == -1) { pjdlog_errno(LOG_ERR, "Unable to fork"); proto_close(adh->adh_remote); adh->adh_remote = NULL; return; } if (pid > 0) { /* This is parent. */ proto_close(adh->adh_remote); adh->adh_remote = NULL; adh->adh_worker_pid = pid; return; } adcfg = config; adhost = adh; mode = pjdlog_mode_get(); debuglevel = pjdlog_debug_get(); descriptors_cleanup(adhost); // descriptors_assert(adhost, mode); pjdlog_init(mode); pjdlog_debug_set(debuglevel); pjdlog_prefix_set("[%s] (%s) ", adhost->adh_name, role2str(adhost->adh_role)); #ifdef HAVE_SETPROCTITLE setproctitle("%s (%s)", adhost->adh_name, role2str(adhost->adh_role)); #endif PJDLOG_VERIFY(sigemptyset(&mask) == 0); PJDLOG_VERIFY(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); /* Error in setting timeout is not critical, but why should it fail? */ if (proto_timeout(adhost->adh_remote, adcfg->adc_timeout) == -1) pjdlog_errno(LOG_WARNING, "Unable to set connection timeout"); init_environment(); adhost->adh_trail_fd = -1; receiver_directory_open(); if (sandbox(ADIST_USER, true, "auditdistd: %s (%s)", role2str(adhost->adh_role), adhost->adh_name) != 0) { exit(EX_CONFIG); } pjdlog_info("Privileges successfully dropped."); receiver_connect(); error = pthread_create(&td, NULL, recv_thread, adhost); PJDLOG_ASSERT(error == 0); error = pthread_create(&td, NULL, disk_thread, adhost); PJDLOG_ASSERT(error == 0); (void)send_thread(adhost); }