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 }