File indexing completed on 2024-05-05 03:54:29

0001 /* vi: ts=8 sts=4 sw=4
0002 
0003     This file is part of the KDE project, module kdesu.
0004     SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org>
0005 
0006 
0007     kdesud.cpp: KDE su daemon. Offers "keep password" functionality to kde su.
0008 
0009     The socket $KDEHOME/socket-$(HOSTNAME)/kdesud_$(display) is used for communication with
0010     client programs.
0011 
0012     The protocol: Client initiates the connection. All commands and responses
0013     are terminated by a newline.
0014 
0015     Client                     Server     Description
0016     ------                     ------     -----------
0017 
0018     PASS <pass> <timeout>      OK         Set password for commands in
0019                                           this session. Password is
0020                                           valid for <timeout> seconds.
0021 
0022     USER <user>                OK         Set the target user [required]
0023 
0024     EXEC <command>             OK         Execute command <command>. If
0025                                NO         <command> has been executed
0026                                           before (< timeout) no PASS
0027                                           command is needed.
0028 
0029     DEL <command>              OK         Delete password for command
0030                                NO         <command>.
0031 
0032     PING                       OK         Ping the server (diagnostics).
0033 */
0034 
0035 #include "config-kdesud.h"
0036 #include <config-kdesu.h>
0037 #include <ksud_debug.h>
0038 
0039 #include <cerrno>
0040 #include <ctype.h>
0041 #include <fcntl.h>
0042 #include <pwd.h>
0043 #include <signal.h>
0044 #include <stdarg.h>
0045 #include <stdio.h>
0046 #include <stdlib.h>
0047 #include <string.h>
0048 #include <unistd.h>
0049 
0050 #include <sys/resource.h>
0051 #include <sys/socket.h>
0052 #include <sys/stat.h>
0053 #include <sys/time.h>
0054 #include <sys/types.h>
0055 #include <sys/un.h>
0056 #include <sys/wait.h>
0057 #ifdef HAVE_SYS_SELECT_H
0058 #include <sys/select.h> // Needed on some systems.
0059 #endif
0060 
0061 #include <dirent.h>
0062 
0063 #if !HAVE_CLOSE_RANGE
0064 #include <sys/syscall.h> // close_range syscall
0065 #endif
0066 
0067 #include <QByteArray>
0068 #include <QCommandLineParser>
0069 #include <QFile>
0070 #include <QList>
0071 #include <QRegularExpression>
0072 #include <QStandardPaths>
0073 
0074 #include <KAboutData>
0075 #include <KLocalizedString>
0076 #include <client.h>
0077 #include <defaults.h>
0078 
0079 #include "handler.h"
0080 #include "repo.h"
0081 
0082 #if HAVE_X11
0083 #include <X11/X.h>
0084 #include <X11/Xlib.h>
0085 #endif
0086 
0087 #ifdef __FreeBSD__
0088 #include <sys/procctl.h>
0089 #elif defined(__linux__)
0090 #include <sys/prctl.h>
0091 #endif
0092 
0093 #ifndef SUN_LEN
0094 #define SUN_LEN(ptr) ((socklen_t)(offsetof(struct sockaddr_un, sun_path) + strlen((ptr)->sun_path)))
0095 #endif
0096 
0097 #define ERR strerror(errno)
0098 
0099 using namespace KDESu;
0100 
0101 static int closeExtraFds()
0102 {
0103 #if HAVE_CLOSE_RANGE
0104     const int res = close_range(4, ~0U, 0);
0105     if (res == 0) {
0106         return 0;
0107     }
0108     // If ENOSYS, fallback to opendir/readdir/closedir below
0109     if (errno != ENOSYS) {
0110         return -1;
0111     }
0112 #elif defined(SYS_close_range)
0113     const int res = syscall(SYS_close_range, 4, ~0U, 0);
0114     if (res == 0) {
0115         return 0;
0116     }
0117     // If ENOSYS, fallback to opendir/readdir/closedir below
0118     if (errno != ENOSYS) {
0119         return -1;
0120     }
0121 #endif
0122 
0123 #if !defined(__FreeBSD__) && !defined(__OpenBSD__) // /proc, /dev are Linux only
0124     // close_range isn't available, fallback to iterarting over "/dev/fd/"
0125     // and close the fd's manually
0126     qCDebug(KSUD_LOG) << "close_range function/syscall isn't available, falling back to iterarting "
0127                          "over '/dev/fd' and closing the file descriptors manually.\n";
0128 
0129     std::unique_ptr<DIR, int (*)(DIR *)> dirPtr(opendir("/dev/fd"), closedir);
0130     if (!dirPtr) {
0131         return -1;
0132     }
0133 
0134     int closeRes = 0;
0135     const int dirFd = dirfd(dirPtr.get());
0136     while (struct dirent *dirEnt = readdir(dirPtr.get())) {
0137         const int currFd = std::atoi(dirEnt->d_name);
0138         if (currFd > 3 && currFd != dirFd) {
0139             closeRes = close(currFd);
0140             if (closeRes == -1) {
0141                 break;
0142             }
0143         }
0144     }
0145     return closeRes;
0146 #else
0147     return -1;
0148 #endif
0149 }
0150 
0151 // Globals
0152 
0153 Repository *repo;
0154 QString Version(QStringLiteral("1.01"));
0155 QByteArray sock;
0156 #if HAVE_X11
0157 Display *x11Display;
0158 #endif
0159 int pipeOfDeath[2];
0160 
0161 void kdesud_cleanup()
0162 {
0163     unlink(sock.constData());
0164 }
0165 
0166 // Borrowed from kdebase/kaudio/kaudioserver.cpp
0167 
0168 #if HAVE_X11
0169 extern "C" int xio_errhandler(Display *);
0170 
0171 int xio_errhandler(Display *)
0172 {
0173     qCCritical(KSUD_LOG) << "Fatal IO error, exiting...\n";
0174     kdesud_cleanup();
0175     exit(1);
0176     return 1; // silence compilers
0177 }
0178 
0179 int initXconnection()
0180 {
0181     x11Display = XOpenDisplay(nullptr);
0182     if (x11Display != nullptr) {
0183         XSetIOErrorHandler(xio_errhandler);
0184         /* clang-format off */
0185         XCreateSimpleWindow(x11Display,
0186                             DefaultRootWindow(x11Display),
0187                             0, 0, 1, 1, 0,
0188                             BlackPixelOfScreen(DefaultScreenOfDisplay(x11Display)),
0189                             BlackPixelOfScreen(DefaultScreenOfDisplay(x11Display)));
0190         /* clang-format on*/
0191         return XConnectionNumber(x11Display);
0192     } else {
0193         qCWarning(KSUD_LOG) << "Can't connect to the X Server.\n";
0194         qCWarning(KSUD_LOG) << "Might not terminate at end of session.\n";
0195         return -1;
0196     }
0197 }
0198 #endif
0199 
0200 extern "C" {
0201 void signal_exit(int);
0202 void sigchld_handler(int);
0203 }
0204 
0205 void signal_exit(int sig)
0206 {
0207     qCDebug(KSUD_LOG) << "Exiting on signal " << sig << "\n";
0208     kdesud_cleanup();
0209     exit(1);
0210 }
0211 
0212 void sigchld_handler(int)
0213 {
0214     char c = ' ';
0215     write(pipeOfDeath[1], &c, 1);
0216 }
0217 
0218 /**
0219  * Creates an AF_UNIX socket in socket resource, mode 0600.
0220  */
0221 
0222 int create_socket()
0223 {
0224     int sockfd;
0225     socklen_t addrlen;
0226     struct stat s;
0227 
0228     QString display = QString::fromLocal8Bit(qgetenv("DISPLAY"));
0229     if (display.isEmpty()) {
0230         qCWarning(KSUD_LOG) << "$DISPLAY is not set\n";
0231         return -1;
0232     }
0233 
0234     // strip the screen number from the display
0235     display.remove(QRegularExpression(QStringLiteral("\\.[0-9]+$")));
0236 
0237     sock = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QStringLiteral("/kdesud_%1").arg(display));
0238     int stat_err = lstat(sock.constData(), &s);
0239     if (!stat_err && S_ISLNK(s.st_mode)) {
0240         qCWarning(KSUD_LOG) << "Someone is running a symlink attack on you\n";
0241         if (unlink(sock.constData())) {
0242             qCWarning(KSUD_LOG) << "Could not delete symlink\n";
0243             return -1;
0244         }
0245     }
0246 
0247     if (!access(sock.constData(), R_OK | W_OK)) {
0248         KDESu::Client client;
0249         if (client.ping() == -1) {
0250             qCWarning(KSUD_LOG) << "stale socket exists\n";
0251             if (unlink(sock.constData())) {
0252                 qCWarning(KSUD_LOG) << "Could not delete stale socket\n";
0253                 return -1;
0254             }
0255         } else {
0256             qCWarning(KSUD_LOG) << "kdesud is already running\n";
0257             return -1;
0258         }
0259     }
0260 
0261     sockfd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
0262     if (sockfd < 0) {
0263         qCCritical(KSUD_LOG) << "socket(): " << ERR << "\n";
0264         return -1;
0265     }
0266 
0267     // Ensure socket closed on error
0268     struct fd_ScopeGuard {
0269         fd_ScopeGuard(int fd)
0270             : _fd(fd)
0271         {
0272         }
0273         ~fd_ScopeGuard()
0274         {
0275             if (_fd >= 0) {
0276                 close(_fd);
0277             }
0278         }
0279         fd_ScopeGuard(const fd_ScopeGuard &) = delete;
0280         fd_ScopeGuard &operator=(const fd_ScopeGuard &) = delete;
0281         void reset()
0282         {
0283             _fd = -1;
0284         }
0285         int _fd;
0286     } guard(sockfd);
0287 
0288     struct sockaddr_un addr;
0289     addr.sun_family = AF_UNIX;
0290     strncpy(addr.sun_path, sock.constData(), sizeof(addr.sun_path) - 1);
0291     addr.sun_path[sizeof(addr.sun_path) - 1] = '\000';
0292     addrlen = SUN_LEN(&addr);
0293     if (bind(sockfd, (struct sockaddr *)&addr, addrlen) < 0) {
0294         qCCritical(KSUD_LOG) << "bind(): " << ERR << "\n";
0295         return -1;
0296     }
0297 
0298     struct linger lin;
0299     lin.l_onoff = lin.l_linger = 0;
0300     if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (char *)&lin, sizeof(linger)) < 0) {
0301         qCCritical(KSUD_LOG) << "setsockopt(SO_LINGER): " << ERR << "\n";
0302         return -1;
0303     }
0304 
0305     int opt = 1;
0306     if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0) {
0307         qCCritical(KSUD_LOG) << "setsockopt(SO_REUSEADDR): " << ERR << "\n";
0308         return -1;
0309     }
0310     opt = 1;
0311     if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *)&opt, sizeof(opt)) < 0) {
0312         qCCritical(KSUD_LOG) << "setsockopt(SO_KEEPALIVE): " << ERR << "\n";
0313         return -1;
0314     }
0315     chmod(sock.constData(), 0600);
0316     guard.reset();
0317     return sockfd;
0318 }
0319 /* The daemon stores passwords, which we don't want any other process to be able to read. */
0320 static bool prevent_tracing()
0321 {
0322     int r = -1;
0323 #ifdef PR_SET_DUMPABLE
0324     // Linux
0325     r = prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
0326 #elif defined(PROC_TRACE_CTL)
0327     // FreeBSD
0328     int disable = PROC_TRACE_CTL_DISABLE_EXEC;
0329     r = procctl(P_PID, 0, PROC_TRACE_CTL, &disable);
0330 #else
0331 #warning Missing implementation for disabling traceability on this platform
0332 #endif
0333 
0334     return r == 0;
0335 }
0336 
0337 /**
0338  * Main program
0339  */
0340 
0341 int main(int argc, char *argv[])
0342 {
0343     if (!prevent_tracing()) {
0344         qCWarning(KSUD_LOG) << "failed to make process memory untraceable" << strerror(errno);
0345     }
0346 
0347     QCoreApplication app(argc, argv);
0348     KAboutData aboutData(QStringLiteral("kdesud") /* componentName */,
0349                          i18n("KDE su daemon"),
0350                          Version,
0351                          i18n("Daemon used by kdesu"),
0352                          KAboutLicense::Artistic,
0353                          i18n("Copyright (c) 1999,2000 Geert Jansen"));
0354     aboutData.addAuthor(i18n("Geert Jansen"), i18n("Author"), QStringLiteral("jansen@kde.org"), QStringLiteral("http://www.stack.nl/~geertj/"));
0355 
0356     KAboutData::setApplicationData(aboutData);
0357     QCommandLineParser parser;
0358     aboutData.setupCommandLine(&parser);
0359     parser.process(app);
0360     aboutData.processCommandLine(&parser);
0361 
0362     // Set core dump size to 0
0363     struct rlimit rlim;
0364     rlim.rlim_cur = rlim.rlim_max = 0;
0365     if (setrlimit(RLIMIT_CORE, &rlim) < 0) {
0366         qCCritical(KSUD_LOG) << "setrlimit(): " << ERR << "\n";
0367         exit(1);
0368     }
0369 
0370     // Create the Unix socket.
0371     int sockfd = create_socket();
0372     if (sockfd < 0) {
0373         exit(1);
0374     }
0375     if (listen(sockfd, 10) < 0) {
0376         qCCritical(KSUD_LOG) << "listen(): " << ERR << "\n";
0377         kdesud_cleanup();
0378         exit(1);
0379     }
0380 
0381     if (sockfd != 3) {
0382         sockfd = dup3(sockfd, 3, O_CLOEXEC);
0383     }
0384     if (sockfd < 0) {
0385         qCCritical(KSUD_LOG) << "Failed to set sockfd to fd 3" << ERR << "\n";
0386         kdesud_cleanup();
0387         exit(1);
0388     }
0389 
0390     int maxfd = sockfd;
0391 
0392     if (closeExtraFds() < 0) {
0393         qCCritical(KSUD_LOG) << "Failed to close file descriptors higher than 3, with error:" << ERR << "\n";
0394         kdesud_cleanup();
0395         exit(1);
0396     }
0397 
0398     // Ok, we're accepting connections. Fork to the background.
0399     pid_t pid = fork();
0400     if (pid == -1) {
0401         qCCritical(KSUD_LOG) << "fork():" << ERR << "\n";
0402         kdesud_cleanup();
0403         exit(1);
0404     }
0405     if (pid) {
0406         _exit(0);
0407     }
0408 
0409 #if HAVE_X11
0410     // Make sure we exit when the display gets closed.
0411     int x11Fd = initXconnection();
0412     maxfd = qMax(maxfd, x11Fd);
0413 #endif
0414 
0415     repo = new Repository;
0416     QList<ConnectionHandler *> handler;
0417 
0418     pipe2(pipeOfDeath, O_CLOEXEC);
0419     maxfd = qMax(maxfd, pipeOfDeath[0]);
0420 
0421     // Signal handlers
0422     struct sigaction sa;
0423     sa.sa_handler = signal_exit;
0424     sigemptyset(&sa.sa_mask);
0425     sa.sa_flags = 0;
0426     sigaction(SIGHUP, &sa, nullptr);
0427     sigaction(SIGINT, &sa, nullptr);
0428     sigaction(SIGTERM, &sa, nullptr);
0429     sigaction(SIGQUIT, &sa, nullptr);
0430 
0431     sa.sa_handler = sigchld_handler;
0432     sa.sa_flags = SA_NOCLDSTOP;
0433     sigaction(SIGCHLD, &sa, nullptr);
0434     sa.sa_handler = SIG_IGN;
0435     sigaction(SIGPIPE, &sa, nullptr);
0436 
0437     // Main execution loop
0438 
0439     socklen_t addrlen;
0440     struct sockaddr_un clientname;
0441 
0442     fd_set tmp_fds;
0443     fd_set active_fds;
0444     FD_ZERO(&active_fds);
0445     FD_SET(sockfd, &active_fds);
0446     FD_SET(pipeOfDeath[0], &active_fds);
0447 #if HAVE_X11
0448     if (x11Fd != -1) {
0449         FD_SET(x11Fd, &active_fds);
0450     }
0451 #endif
0452 
0453     while (1) {
0454         tmp_fds = active_fds;
0455 #if HAVE_X11
0456         if (x11Display) {
0457             XFlush(x11Display);
0458         }
0459 #endif
0460         if (select(maxfd + 1, &tmp_fds, nullptr, nullptr, nullptr) < 0) {
0461             if (errno == EINTR) {
0462                 continue;
0463             }
0464 
0465             qCCritical(KSUD_LOG) << "select(): " << ERR << "\n";
0466             exit(1);
0467         }
0468         repo->expire();
0469         for (int i = 0; i <= maxfd; i++) {
0470             if (!FD_ISSET(i, &tmp_fds)) {
0471                 continue;
0472             }
0473 
0474             if (i == pipeOfDeath[0]) {
0475                 char buf[101];
0476                 read(pipeOfDeath[0], buf, 100);
0477                 pid_t result;
0478                 do {
0479                     int status;
0480                     result = waitpid((pid_t)-1, &status, WNOHANG);
0481                     if (result > 0) {
0482                         for (int j = handler.size(); j--;) {
0483                             if (handler[j] && (handler[j]->m_pid == result)) {
0484                                 handler[j]->m_exitCode = WEXITSTATUS(status);
0485                                 handler[j]->m_hasExitCode = true;
0486                                 handler[j]->sendExitCode();
0487                                 handler[j]->m_pid = 0;
0488                                 break;
0489                             }
0490                         }
0491                     }
0492                 } while (result > 0);
0493             }
0494 
0495 #if HAVE_X11
0496             if (i == x11Fd) {
0497                 // Discard X events
0498                 XEvent event_return;
0499                 if (x11Display) {
0500                     while (XPending(x11Display)) {
0501                         XNextEvent(x11Display, &event_return);
0502                     }
0503                 }
0504                 continue;
0505             }
0506 #endif
0507 
0508             if (i == sockfd) {
0509                 // Accept new connection
0510                 int fd;
0511                 addrlen = 64;
0512                 fd = accept(sockfd, (struct sockaddr *)&clientname, &addrlen);
0513                 if (fd < 0) {
0514                     qCCritical(KSUD_LOG) << "accept():" << ERR << "\n";
0515                     continue;
0516                 }
0517                 while (fd + 1 > (int)handler.size()) {
0518                     handler.append(nullptr);
0519                 }
0520                 delete handler[fd];
0521                 handler[fd] = new ConnectionHandler(fd);
0522                 maxfd = qMax(maxfd, fd);
0523                 FD_SET(fd, &active_fds);
0524                 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
0525                 continue;
0526             }
0527 
0528             // handle already established connection
0529             if (handler[i] && handler[i]->handle() < 0) {
0530                 delete handler[i];
0531                 handler[i] = nullptr;
0532                 FD_CLR(i, &active_fds);
0533             }
0534         }
0535     }
0536     qCWarning(KSUD_LOG) << "???\n";
0537 }