/* vi: ts=8 sts=4 sw=4 This file is part of the KDE project, module kdesu. SPDX-FileCopyrightText: 1999, 2000 Geert Jansen kdesud.cpp: KDE su daemon. Offers "keep password" functionality to kde su. The socket $KDEHOME/socket-$(HOSTNAME)/kdesud_$(display) is used for communication with client programs. The protocol: Client initiates the connection. All commands and responses are terminated by a newline. Client Server Description ------ ------ ----------- PASS OK Set password for commands in this session. Password is valid for seconds. USER OK Set the target user [required] EXEC OK Execute command . If NO has been executed before (< timeout) no PASS command is needed. DEL OK Delete password for command NO . PING OK Ping the server (diagnostics). */ #include "config-kdesud.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include // Needed on some systems. #endif #include #if !HAVE_CLOSE_RANGE #include // close_range syscall #endif #include #include #include #include #include #include #include #include #include #include #include "handler.h" #include "repo.h" #if HAVE_X11 #include #include #endif #ifdef __FreeBSD__ #include #elif defined(__linux__) #include #endif #ifndef SUN_LEN #define SUN_LEN(ptr) ((socklen_t)(offsetof(struct sockaddr_un, sun_path) + strlen((ptr)->sun_path))) #endif #define ERR strerror(errno) using namespace KDESu; static int closeExtraFds() { #if HAVE_CLOSE_RANGE const int res = close_range(4, ~0U, 0); if (res == 0) { return 0; } // If ENOSYS, fallback to opendir/readdir/closedir below if (errno != ENOSYS) { return -1; } #elif defined(SYS_close_range) const int res = syscall(SYS_close_range, 4, ~0U, 0); if (res == 0) { return 0; } // If ENOSYS, fallback to opendir/readdir/closedir below if (errno != ENOSYS) { return -1; } #endif #if !defined(__FreeBSD__) && !defined(__OpenBSD__) // /proc, /dev are Linux only // close_range isn't available, fallback to iterarting over "/dev/fd/" // and close the fd's manually qCDebug(KSUD_LOG) << "close_range function/syscall isn't available, falling back to iterarting " "over '/dev/fd' and closing the file descriptors manually.\n"; std::unique_ptr dirPtr(opendir("/dev/fd"), closedir); if (!dirPtr) { return -1; } int closeRes = 0; const int dirFd = dirfd(dirPtr.get()); while (struct dirent *dirEnt = readdir(dirPtr.get())) { const int currFd = std::atoi(dirEnt->d_name); if (currFd > 3 && currFd != dirFd) { closeRes = close(currFd); if (closeRes == -1) { break; } } } return closeRes; #else return -1; #endif } // Globals Repository *repo; QString Version(QStringLiteral("1.01")); QByteArray sock; #if HAVE_X11 Display *x11Display; #endif int pipeOfDeath[2]; void kdesud_cleanup() { unlink(sock.constData()); } // Borrowed from kdebase/kaudio/kaudioserver.cpp #if HAVE_X11 extern "C" int xio_errhandler(Display *); int xio_errhandler(Display *) { qCCritical(KSUD_LOG) << "Fatal IO error, exiting...\n"; kdesud_cleanup(); exit(1); return 1; // silence compilers } int initXconnection() { x11Display = XOpenDisplay(nullptr); if (x11Display != nullptr) { XSetIOErrorHandler(xio_errhandler); /* clang-format off */ XCreateSimpleWindow(x11Display, DefaultRootWindow(x11Display), 0, 0, 1, 1, 0, BlackPixelOfScreen(DefaultScreenOfDisplay(x11Display)), BlackPixelOfScreen(DefaultScreenOfDisplay(x11Display))); /* clang-format on*/ return XConnectionNumber(x11Display); } else { qCWarning(KSUD_LOG) << "Can't connect to the X Server.\n"; qCWarning(KSUD_LOG) << "Might not terminate at end of session.\n"; return -1; } } #endif extern "C" { void signal_exit(int); void sigchld_handler(int); } void signal_exit(int sig) { qCDebug(KSUD_LOG) << "Exiting on signal " << sig << "\n"; kdesud_cleanup(); exit(1); } void sigchld_handler(int) { char c = ' '; write(pipeOfDeath[1], &c, 1); } /** * Creates an AF_UNIX socket in socket resource, mode 0600. */ int create_socket() { int sockfd; socklen_t addrlen; struct stat s; QString display = QString::fromLocal8Bit(qgetenv("DISPLAY")); if (display.isEmpty()) { qCWarning(KSUD_LOG) << "$DISPLAY is not set\n"; return -1; } // strip the screen number from the display display.remove(QRegularExpression(QStringLiteral("\\.[0-9]+$"))); sock = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QStringLiteral("/kdesud_%1").arg(display)); int stat_err = lstat(sock.constData(), &s); if (!stat_err && S_ISLNK(s.st_mode)) { qCWarning(KSUD_LOG) << "Someone is running a symlink attack on you\n"; if (unlink(sock.constData())) { qCWarning(KSUD_LOG) << "Could not delete symlink\n"; return -1; } } if (!access(sock.constData(), R_OK | W_OK)) { KDESu::Client client; if (client.ping() == -1) { qCWarning(KSUD_LOG) << "stale socket exists\n"; if (unlink(sock.constData())) { qCWarning(KSUD_LOG) << "Could not delete stale socket\n"; return -1; } } else { qCWarning(KSUD_LOG) << "kdesud is already running\n"; return -1; } } sockfd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); if (sockfd < 0) { qCCritical(KSUD_LOG) << "socket(): " << ERR << "\n"; return -1; } // Ensure socket closed on error struct fd_ScopeGuard { fd_ScopeGuard(int fd) : _fd(fd) { } ~fd_ScopeGuard() { if (_fd >= 0) { close(_fd); } } fd_ScopeGuard(const fd_ScopeGuard &) = delete; fd_ScopeGuard &operator=(const fd_ScopeGuard &) = delete; void reset() { _fd = -1; } int _fd; } guard(sockfd); struct sockaddr_un addr; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sock.constData(), sizeof(addr.sun_path) - 1); addr.sun_path[sizeof(addr.sun_path) - 1] = '\000'; addrlen = SUN_LEN(&addr); if (bind(sockfd, (struct sockaddr *)&addr, addrlen) < 0) { qCCritical(KSUD_LOG) << "bind(): " << ERR << "\n"; return -1; } struct linger lin; lin.l_onoff = lin.l_linger = 0; if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (char *)&lin, sizeof(linger)) < 0) { qCCritical(KSUD_LOG) << "setsockopt(SO_LINGER): " << ERR << "\n"; return -1; } int opt = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0) { qCCritical(KSUD_LOG) << "setsockopt(SO_REUSEADDR): " << ERR << "\n"; return -1; } opt = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *)&opt, sizeof(opt)) < 0) { qCCritical(KSUD_LOG) << "setsockopt(SO_KEEPALIVE): " << ERR << "\n"; return -1; } chmod(sock.constData(), 0600); guard.reset(); return sockfd; } /* The daemon stores passwords, which we don't want any other process to be able to read. */ static bool prevent_tracing() { int r = -1; #ifdef PR_SET_DUMPABLE // Linux r = prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); #elif defined(PROC_TRACE_CTL) // FreeBSD int disable = PROC_TRACE_CTL_DISABLE_EXEC; r = procctl(P_PID, 0, PROC_TRACE_CTL, &disable); #else #warning Missing implementation for disabling traceability on this platform #endif return r == 0; } /** * Main program */ int main(int argc, char *argv[]) { if (!prevent_tracing()) { qCWarning(KSUD_LOG) << "failed to make process memory untraceable" << strerror(errno); } QCoreApplication app(argc, argv); KAboutData aboutData(QStringLiteral("kdesud") /* componentName */, i18n("KDE su daemon"), Version, i18n("Daemon used by kdesu"), KAboutLicense::Artistic, i18n("Copyright (c) 1999,2000 Geert Jansen")); aboutData.addAuthor(i18n("Geert Jansen"), i18n("Author"), QStringLiteral("jansen@kde.org"), QStringLiteral("http://www.stack.nl/~geertj/")); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); // Set core dump size to 0 struct rlimit rlim; rlim.rlim_cur = rlim.rlim_max = 0; if (setrlimit(RLIMIT_CORE, &rlim) < 0) { qCCritical(KSUD_LOG) << "setrlimit(): " << ERR << "\n"; exit(1); } // Create the Unix socket. int sockfd = create_socket(); if (sockfd < 0) { exit(1); } if (listen(sockfd, 10) < 0) { qCCritical(KSUD_LOG) << "listen(): " << ERR << "\n"; kdesud_cleanup(); exit(1); } if (sockfd != 3) { sockfd = dup3(sockfd, 3, O_CLOEXEC); } if (sockfd < 0) { qCCritical(KSUD_LOG) << "Failed to set sockfd to fd 3" << ERR << "\n"; kdesud_cleanup(); exit(1); } int maxfd = sockfd; if (closeExtraFds() < 0) { qCCritical(KSUD_LOG) << "Failed to close file descriptors higher than 3, with error:" << ERR << "\n"; kdesud_cleanup(); exit(1); } // Ok, we're accepting connections. Fork to the background. pid_t pid = fork(); if (pid == -1) { qCCritical(KSUD_LOG) << "fork():" << ERR << "\n"; kdesud_cleanup(); exit(1); } if (pid) { _exit(0); } #if HAVE_X11 // Make sure we exit when the display gets closed. int x11Fd = initXconnection(); maxfd = qMax(maxfd, x11Fd); #endif repo = new Repository; QList handler; pipe2(pipeOfDeath, O_CLOEXEC); maxfd = qMax(maxfd, pipeOfDeath[0]); // Signal handlers struct sigaction sa; sa.sa_handler = signal_exit; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGHUP, &sa, nullptr); sigaction(SIGINT, &sa, nullptr); sigaction(SIGTERM, &sa, nullptr); sigaction(SIGQUIT, &sa, nullptr); sa.sa_handler = sigchld_handler; sa.sa_flags = SA_NOCLDSTOP; sigaction(SIGCHLD, &sa, nullptr); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, nullptr); // Main execution loop socklen_t addrlen; struct sockaddr_un clientname; fd_set tmp_fds; fd_set active_fds; FD_ZERO(&active_fds); FD_SET(sockfd, &active_fds); FD_SET(pipeOfDeath[0], &active_fds); #if HAVE_X11 if (x11Fd != -1) { FD_SET(x11Fd, &active_fds); } #endif while (1) { tmp_fds = active_fds; #if HAVE_X11 if (x11Display) { XFlush(x11Display); } #endif if (select(maxfd + 1, &tmp_fds, nullptr, nullptr, nullptr) < 0) { if (errno == EINTR) { continue; } qCCritical(KSUD_LOG) << "select(): " << ERR << "\n"; exit(1); } repo->expire(); for (int i = 0; i <= maxfd; i++) { if (!FD_ISSET(i, &tmp_fds)) { continue; } if (i == pipeOfDeath[0]) { char buf[101]; read(pipeOfDeath[0], buf, 100); pid_t result; do { int status; result = waitpid((pid_t)-1, &status, WNOHANG); if (result > 0) { for (int j = handler.size(); j--;) { if (handler[j] && (handler[j]->m_pid == result)) { handler[j]->m_exitCode = WEXITSTATUS(status); handler[j]->m_hasExitCode = true; handler[j]->sendExitCode(); handler[j]->m_pid = 0; break; } } } } while (result > 0); } #if HAVE_X11 if (i == x11Fd) { // Discard X events XEvent event_return; if (x11Display) { while (XPending(x11Display)) { XNextEvent(x11Display, &event_return); } } continue; } #endif if (i == sockfd) { // Accept new connection int fd; addrlen = 64; fd = accept(sockfd, (struct sockaddr *)&clientname, &addrlen); if (fd < 0) { qCCritical(KSUD_LOG) << "accept():" << ERR << "\n"; continue; } while (fd + 1 > (int)handler.size()) { handler.append(nullptr); } delete handler[fd]; handler[fd] = new ConnectionHandler(fd); maxfd = qMax(maxfd, fd); FD_SET(fd, &active_fds); fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); continue; } // handle already established connection if (handler[i] && handler[i]->handle() < 0) { delete handler[i]; handler[i] = nullptr; FD_CLR(i, &active_fds); } } } qCWarning(KSUD_LOG) << "???\n"; }