/* Copyright (c) 2006 Trusted Computer Solutions, Inc. */ #include <errno.h> #include <poll.h> #include <signal.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <syslog.h> #include <unistd.h> #include <selinux/selinux.h> #include <sys/capability.h> #include <sys/resource.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/uio.h> #include <sys/un.h> #include "mcstrans.h" #ifdef UNUSED #elif defined(__GNUC__) # define UNUSED(x) UNUSED_ ## x __attribute__((unused)) #elif defined(__LCLINT__) # define UNUSED(x) /*@unused@*/ x #else # define UNUSED(x) x #endif #define SETRANS_UNIX_SOCKET "/var/run/setrans/.setrans-unix" #define SETRANS_INIT 1 #define RAW_TO_TRANS_CONTEXT 2 #define TRANS_TO_RAW_CONTEXT 3 #define RAW_CONTEXT_TO_COLOR 4 #define MAX_DATA_BUF 4096 #define MAX_DESCRIPTORS 8192 #ifdef DEBUG //#define log_debug(fmt, ...) syslog(LOG_DEBUG, fmt, __VA_ARGS__) #define log_debug(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) #else #define log_debug(fmt, ...) ; #endif extern int init_translations(void); extern void finish_context_translations(void); extern int trans_context(const security_context_t, security_context_t *); extern int untrans_context(const security_context_t, security_context_t *); extern int init_colors(void); extern void finish_context_colors(void); extern int raw_color(const security_context_t, char **); #define SETRANSD_PATHNAME "/sbin/mcstransd" /* name of program (for error messages) */ #define SETRANSD_PROGNAME "mcstransd" static int sockfd = -1; /* socket we are listening on */ static volatile int restart_daemon = 0; static void cleanup_exit(int ret) __attribute__ ((noreturn)); static void cleanup_exit(int ret) { finish_context_colors(); finish_context_translations(); if (sockfd >=0) (void)unlink(SETRANS_UNIX_SOCKET); log_debug("%s\n", "cleanup_exit"); exit(ret); } static void clean_exit(void); static __attribute__((noreturn)) void clean_exit(void) { log_debug("%s\n", "clean_exit"); cleanup_exit(0); } static int send_response(int fd, uint32_t function, char *data, int32_t ret_val) { struct iovec resp_hdr[3]; uint32_t data_size; struct iovec resp_data; ssize_t count; if (!data) data = (char *)""; data_size = strlen(data) + 1; resp_hdr[0].iov_base = &function; resp_hdr[0].iov_len = sizeof(function); resp_hdr[1].iov_base = &data_size; resp_hdr[1].iov_len = sizeof(data_size); resp_hdr[2].iov_base = &ret_val; resp_hdr[2].iov_len = sizeof(ret_val); while (((count = writev(fd, resp_hdr, 3)) < 0) && (errno == EINTR)); if (count != (sizeof(function) + sizeof(data_size) + sizeof(ret_val))) { syslog(LOG_ERR, "Failed to write response header"); return -1; } resp_data.iov_base = data; resp_data.iov_len = data_size; while (((count = writev(fd, &resp_data, 1)) < 0) && (errno == EINTR)); if (count < 0 || (size_t)count != data_size) { syslog(LOG_ERR, "Failed to write response data"); return -1; } return ret_val; } static int get_peer_pid(int fd, pid_t *pid) { int ret; socklen_t size = sizeof(struct ucred); struct ucred peercred; /* get the context of the requesting process */ ret = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &peercred, &size); if (ret < 0) { syslog(LOG_ERR, "Failed to get PID of client process"); return -1; } *pid = peercred.pid; return ret; } static int process_request(int fd, uint32_t function, char *data1, char *UNUSED(data2)) { int32_t result; char *out = NULL; int ret; switch (function) { case SETRANS_INIT: result = 0; ret = send_response(fd, function, NULL, result); break; case RAW_TO_TRANS_CONTEXT: result = trans_context(data1, &out); ret = send_response(fd, function, out, result); break; case TRANS_TO_RAW_CONTEXT: result = untrans_context(data1, &out); ret = send_response(fd, function, out, result); break; case RAW_CONTEXT_TO_COLOR: result = raw_color(data1, &out); ret = send_response(fd, function, out, result); break; default: result = -1; ret = -1; break; } if (result) { pid_t pid = 0; get_peer_pid(fd, &pid); syslog(LOG_ERR, "Invalid request func=%d from=%u", function, pid); } free(out); return ret; } static int service_request(int fd) { struct iovec req_hdr[3]; uint32_t function; uint32_t data1_size; uint32_t data2_size; struct iovec req_data[2]; char *data1; char *data2; int ret; ssize_t count; req_hdr[0].iov_base = &function; req_hdr[0].iov_len = sizeof(function); req_hdr[1].iov_base = &data1_size; req_hdr[1].iov_len = sizeof(data1_size); req_hdr[2].iov_base = &data2_size; req_hdr[2].iov_len = sizeof(data2_size); while (((count = readv(fd, req_hdr, 3)) < 0) && (errno == EINTR)); if (count <= 0) { return 1; } if (count != (sizeof(function) + sizeof(data1_size) + sizeof(data2_size) )) { log_debug("Failed to read request header %d != %u\n",(int)count, (unsigned)(sizeof(function) + sizeof(data1_size) + sizeof(data2_size) )); return -1; } if (!data1_size || !data2_size || data1_size > MAX_DATA_BUF || data2_size > MAX_DATA_BUF ) { log_debug("Header invalid data1_size=%u data2_size=%u\n", data1_size, data2_size); return -1; } data1 = malloc(data1_size); if (!data1) { log_debug("Could not allocate %d bytes\n", data1_size); return -1; } data2 = malloc(data2_size); if (!data2) { free(data1); log_debug("Could not allocate %d bytes\n", data2_size); return -1; } req_data[0].iov_base = data1; req_data[0].iov_len = data1_size; req_data[1].iov_base = data2; req_data[1].iov_len = data2_size; while (((count = readv(fd, req_data, 2)) < 0) && (errno == EINTR)); if (count <= 0 || (size_t)count != (data1_size + data2_size) || data1[data1_size - 1] != '\0' || data2[data2_size - 1] != '\0') { free(data1); free(data2); log_debug("Failed to read request data (%d)\n", (int)count); return -1; } ret = process_request(fd, function, data1, data2); free(data1); free(data2); return ret; } static int add_pollfd(struct pollfd **ufds, int *nfds, int connfd) { int ii = 0; /* First see if we can find an already invalidated ufd */ for (ii = 0; ii < *nfds; ii++) { if ((*ufds)[ii].fd == -1) break; } if (ii == *nfds) { struct pollfd *tmp = (struct pollfd *)realloc(*ufds, (*nfds+1)*sizeof(struct pollfd)); if (!tmp) { syslog(LOG_ERR, "realloc failed for %d fds", *nfds+1); return -1; } *ufds = tmp; (*nfds)++; } (*ufds)[ii].fd = connfd; (*ufds)[ii].events = POLLIN|POLLPRI; (*ufds)[ii].revents = 0; return 0; } static void adj_pollfds(struct pollfd **ufds, int *nfds) { int ii, jj; jj = 0; for (ii = 0; ii < *nfds; ii++) { if ((*ufds)[ii].fd != -1) { if (jj < ii) (*ufds)[jj] = (*ufds)[ii]; jj++; } } *nfds = jj; } static int process_events(struct pollfd **ufds, int *nfds) { int ii = 0; int ret = 0; for (ii = 0; ii < *nfds; ii++) { short revents = (*ufds)[ii].revents; int connfd = (*ufds)[ii].fd; if (revents & (POLLIN | POLLPRI)) { if (connfd == sockfd) { /* Probably received a connection */ if ((connfd = accept(sockfd, NULL, NULL)) < 0) { syslog(LOG_ERR, "accept() failed: %m"); return -1; } if (add_pollfd(ufds, nfds, connfd)) { syslog(LOG_ERR, "Failed to add fd (%d) to poll list\n", connfd); return -1; } } else { ret = service_request(connfd); if (ret) { if (ret < 0) { syslog(LOG_ERR, "Servicing of request " "failed for fd (%d)\n", connfd); } /* Setup pollfd for deletion later. */ (*ufds)[ii].fd = -1; close(connfd); /* So we don't get bothered later */ revents = revents & ~(POLLHUP); } } revents = revents & ~(POLLIN | POLLPRI); } if (revents & POLLHUP) { log_debug("The connection with fd (%d) hung up\n", connfd); /* Set the pollfd up for deletion later. */ (*ufds)[ii].fd = -1; close(connfd); revents = revents & ~(POLLHUP); } if (revents) { syslog(LOG_ERR, "Unknown/error events (%x) encountered" " for fd (%d)\n", revents, connfd); /* Set the pollfd up for deletion later. */ (*ufds)[ii].fd = -1; close(connfd); } (*ufds)[ii].revents = 0; } /* Delete any invalidated ufds */ adj_pollfds(ufds, nfds); return 0; } static void process_connections(void) __attribute__ ((noreturn)); static void process_connections(void) { int ret = 0; int nfds = 1; struct pollfd *ufds = (struct pollfd *)malloc(sizeof(struct pollfd)); if (!ufds) { syslog(LOG_ERR, "Failed to allocate a pollfd"); cleanup_exit(1); } ufds[0].fd = sockfd; ufds[0].events = POLLIN|POLLPRI; ufds[0].revents = 0; while (1) { if (restart_daemon) { syslog(LOG_NOTICE, "Reload Translations"); finish_context_colors(); finish_context_translations(); if (init_translations()) { syslog(LOG_ERR, "Failed to initialize label translations"); cleanup_exit(1); } if (init_colors()) { syslog(LOG_ERR, "Failed to initialize color translations"); syslog(LOG_ERR, "No color information will be available"); } restart_daemon = 0; } ret = poll(ufds, nfds, -1); if (ret < 0) { if (errno == EINTR) { continue; } syslog(LOG_ERR, "poll() failed: %m"); cleanup_exit(1); } ret = process_events(&ufds, &nfds); if (ret) { syslog(LOG_ERR, "Error processing events"); cleanup_exit(1); } } } static void sigterm_handler(int sig) __attribute__ ((noreturn)); static void sigterm_handler(int UNUSED(sig)) { cleanup_exit(0); } static void sighup_handler(int UNUSED(sig)) { restart_daemon = 1; } static void initialize(void) { struct sigaction act; struct sockaddr_un addr; struct rlimit rl ; if (init_translations()) { syslog(LOG_ERR, "Failed to initialize label translations"); cleanup_exit(1); } if (init_colors()) { syslog(LOG_ERR, "Failed to initialize color translations"); syslog(LOG_ERR, "No color information will be available"); } /* the socket will be unlinked when the daemon terminates */ act.sa_handler = sigterm_handler; sigemptyset(&act.sa_mask); sigaddset(&act.sa_mask, SIGINT); sigaddset(&act.sa_mask, SIGQUIT); sigaddset(&act.sa_mask, SIGTERM); sigaddset(&act.sa_mask, SIGHUP); act.sa_flags = 0; sigaction(SIGINT, &act, NULL); sigaction(SIGQUIT, &act, NULL); sigaction(SIGTERM, &act, NULL); /* restart the daemon on SIGHUP */ act.sa_handler = sighup_handler; sigemptyset(&act.sa_mask); sigaddset(&act.sa_mask, SIGINT); sigaddset(&act.sa_mask, SIGQUIT); sigaddset(&act.sa_mask, SIGTERM); act.sa_flags = 0; sigaction(SIGHUP, &act, NULL); /* ignore SIGPIPE (in case a client terminates after sending request) */ act.sa_handler = SIG_IGN; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGPIPE, &act, NULL); atexit(clean_exit); sockfd = socket(PF_UNIX, SOCK_STREAM, 0); if (sockfd < 0) { syslog(LOG_ERR, "socket() failed: %m"); cleanup_exit(1); } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SETRANS_UNIX_SOCKET, sizeof(addr.sun_path) - 1); (void)unlink(SETRANS_UNIX_SOCKET); if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { syslog(LOG_ERR, "bind() failed: %m"); cleanup_exit(1); } if (listen(sockfd, SOMAXCONN) < 0) { syslog(LOG_ERR, "listen() failed: %m"); cleanup_exit(1); } if (chmod(SETRANS_UNIX_SOCKET, S_IRWXU | S_IRWXG | S_IRWXO)) { syslog(LOG_ERR, "chmod() failed: %m"); cleanup_exit(1); } /* Raise the rlimit for file descriptors... */ rl.rlim_max = MAX_DESCRIPTORS; rl.rlim_cur = MAX_DESCRIPTORS; setrlimit(RLIMIT_NOFILE, &rl); } void dropprivs(void) { cap_t new_caps; new_caps = cap_init(); if (cap_set_proc(new_caps)) { syslog(LOG_ERR, "Error dropping capabilities, aborting: %s\n", strerror(errno)); cleanup_exit(-1); } cap_free(new_caps); } static void usage(char *program) { printf("%s [-f] [-h] \n", program); } int main(int argc, char *argv[]) { int opt; int do_fork = 1; while ((opt = getopt(argc, argv, "hf")) > 0) { switch (opt) { case 'f': do_fork = 0; break; case 'h': usage(argv[0]); exit(0); break; case '?': usage(argv[0]); exit(-1); } } #ifndef DEBUG /* Make sure we are root */ if (getuid() != 0) { syslog(LOG_ERR, "You must be root to run this program.\n"); return 4; } #endif openlog(SETRANSD_PROGNAME, 0, LOG_DAEMON); syslog(LOG_NOTICE, "%s starting", argv[0]); initialize(); #ifndef DEBUG dropprivs(); /* run in the background as a daemon */ if (do_fork && daemon(0, 0)) { syslog(LOG_ERR, "daemon() failed: %m"); cleanup_exit(1); } #endif syslog(LOG_NOTICE, "%s initialized", argv[0]); process_connections(); /* we should never get here */ return 1; }