| 1 | /* vi: ts=8 sts=4 sw=4 |
| 2 | |
| 3 | This file is part of the KDE project, module kdesu. |
| 4 | SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org> |
| 5 | |
| 6 | |
| 7 | kdesud.cpp: KDE su daemon. Offers "keep password" functionality to kde su. |
| 8 | |
| 9 | The socket $KDEHOME/socket-$(HOSTNAME)/kdesud_$(display) is used for communication with |
| 10 | client programs. |
| 11 | |
| 12 | The protocol: Client initiates the connection. All commands and responses |
| 13 | are terminated by a newline. |
| 14 | |
| 15 | Client Server Description |
| 16 | ------ ------ ----------- |
| 17 | |
| 18 | PASS <pass> <timeout> OK Set password for commands in |
| 19 | this session. Password is |
| 20 | valid for <timeout> seconds. |
| 21 | |
| 22 | USER <user> OK Set the target user [required] |
| 23 | |
| 24 | EXEC <command> OK Execute command <command>. If |
| 25 | NO <command> has been executed |
| 26 | before (< timeout) no PASS |
| 27 | command is needed. |
| 28 | |
| 29 | DEL <command> OK Delete password for command |
| 30 | NO <command>. |
| 31 | |
| 32 | PING OK Ping the server (diagnostics). |
| 33 | */ |
| 34 | |
| 35 | #include "config-kdesud.h" |
| 36 | #include <config-kdesu.h> |
| 37 | #include <ksud_debug.h> |
| 38 | |
| 39 | #include <cerrno> |
| 40 | #include <ctype.h> |
| 41 | #include <fcntl.h> |
| 42 | #include <pwd.h> |
| 43 | #include <signal.h> |
| 44 | #include <stdarg.h> |
| 45 | #include <stdio.h> |
| 46 | #include <stdlib.h> |
| 47 | #include <string.h> |
| 48 | #include <unistd.h> |
| 49 | |
| 50 | #include <sys/resource.h> |
| 51 | #include <sys/socket.h> |
| 52 | #include <sys/stat.h> |
| 53 | #include <sys/time.h> |
| 54 | #include <sys/types.h> |
| 55 | #include <sys/un.h> |
| 56 | #include <sys/wait.h> |
| 57 | #ifdef HAVE_SYS_SELECT_H |
| 58 | #include <sys/select.h> // Needed on some systems. |
| 59 | #endif |
| 60 | |
| 61 | #include <dirent.h> |
| 62 | |
| 63 | #if !HAVE_CLOSE_RANGE |
| 64 | #include <sys/syscall.h> // close_range syscall |
| 65 | #endif |
| 66 | |
| 67 | #include <QByteArray> |
| 68 | #include <QCommandLineParser> |
| 69 | #include <QFile> |
| 70 | #include <QList> |
| 71 | #include <QRegularExpression> |
| 72 | #include <QStandardPaths> |
| 73 | |
| 74 | #include <KAboutData> |
| 75 | #include <KLocalizedString> |
| 76 | #include <client.h> |
| 77 | #include <defaults.h> |
| 78 | |
| 79 | #include "handler.h" |
| 80 | #include "repo.h" |
| 81 | |
| 82 | #if HAVE_X11 |
| 83 | #include <X11/X.h> |
| 84 | #include <X11/Xlib.h> |
| 85 | #endif |
| 86 | |
| 87 | #ifdef __FreeBSD__ |
| 88 | #include <sys/procctl.h> |
| 89 | #elif defined(__linux__) |
| 90 | #include <sys/prctl.h> |
| 91 | #endif |
| 92 | |
| 93 | #ifndef SUN_LEN |
| 94 | #define SUN_LEN(ptr) ((socklen_t)(offsetof(struct sockaddr_un, sun_path) + strlen((ptr)->sun_path))) |
| 95 | #endif |
| 96 | |
| 97 | #define ERR strerror(errno) |
| 98 | |
| 99 | using namespace KDESu; |
| 100 | |
| 101 | static int () |
| 102 | { |
| 103 | #if HAVE_CLOSE_RANGE |
| 104 | const int res = close_range(fd: 4, max_fd: ~0U, flags: 0); |
| 105 | if (res == 0) { |
| 106 | return 0; |
| 107 | } |
| 108 | // If ENOSYS, fallback to opendir/readdir/closedir below |
| 109 | if (errno != ENOSYS) { |
| 110 | return -1; |
| 111 | } |
| 112 | #elif defined(SYS_close_range) |
| 113 | const int res = syscall(SYS_close_range, 4, ~0U, 0); |
| 114 | if (res == 0) { |
| 115 | return 0; |
| 116 | } |
| 117 | // If ENOSYS, fallback to opendir/readdir/closedir below |
| 118 | if (errno != ENOSYS) { |
| 119 | return -1; |
| 120 | } |
| 121 | #endif |
| 122 | |
| 123 | #if !defined(__FreeBSD__) && !defined(__OpenBSD__) // /proc, /dev are Linux only |
| 124 | // close_range isn't available, fallback to iterarting over "/dev/fd/" |
| 125 | // and close the fd's manually |
| 126 | qCDebug(KSUD_LOG) << "close_range function/syscall isn't available, falling back to iterarting " |
| 127 | "over '/dev/fd' and closing the file descriptors manually.\n" ; |
| 128 | |
| 129 | std::unique_ptr<DIR, int (*)(DIR *)> dirPtr(opendir(name: "/dev/fd" ), closedir); |
| 130 | if (!dirPtr) { |
| 131 | return -1; |
| 132 | } |
| 133 | |
| 134 | int closeRes = 0; |
| 135 | const int dirFd = dirfd(dirp: dirPtr.get()); |
| 136 | while (struct dirent *dirEnt = readdir(dirp: dirPtr.get())) { |
| 137 | const int currFd = std::atoi(nptr: dirEnt->d_name); |
| 138 | if (currFd > 3 && currFd != dirFd) { |
| 139 | closeRes = close(fd: currFd); |
| 140 | if (closeRes == -1) { |
| 141 | break; |
| 142 | } |
| 143 | } |
| 144 | } |
| 145 | return closeRes; |
| 146 | #else |
| 147 | return -1; |
| 148 | #endif |
| 149 | } |
| 150 | |
| 151 | // Globals |
| 152 | |
| 153 | Repository *repo; |
| 154 | QString Version(QStringLiteral("1.01" )); |
| 155 | QByteArray sock; |
| 156 | #if HAVE_X11 |
| 157 | Display *x11Display; |
| 158 | #endif |
| 159 | int pipeOfDeath[2]; |
| 160 | |
| 161 | void kdesud_cleanup() |
| 162 | { |
| 163 | unlink(name: sock.constData()); |
| 164 | } |
| 165 | |
| 166 | // Borrowed from kdebase/kaudio/kaudioserver.cpp |
| 167 | |
| 168 | #if HAVE_X11 |
| 169 | extern "C" int xio_errhandler(Display *); |
| 170 | |
| 171 | int xio_errhandler(Display *) |
| 172 | { |
| 173 | qCCritical(KSUD_LOG) << "Fatal IO error, exiting...\n" ; |
| 174 | kdesud_cleanup(); |
| 175 | exit(status: 1); |
| 176 | return 1; // silence compilers |
| 177 | } |
| 178 | |
| 179 | int initXconnection() |
| 180 | { |
| 181 | x11Display = XOpenDisplay(nullptr); |
| 182 | if (x11Display != nullptr) { |
| 183 | XSetIOErrorHandler(xio_errhandler); |
| 184 | /* clang-format off */ |
| 185 | XCreateSimpleWindow(x11Display, |
| 186 | DefaultRootWindow(x11Display), |
| 187 | 0, 0, 1, 1, 0, |
| 188 | BlackPixelOfScreen(DefaultScreenOfDisplay(x11Display)), |
| 189 | BlackPixelOfScreen(DefaultScreenOfDisplay(x11Display))); |
| 190 | /* clang-format on*/ |
| 191 | return XConnectionNumber(x11Display); |
| 192 | } else { |
| 193 | qCWarning(KSUD_LOG) << "Can't connect to the X Server.\n" ; |
| 194 | qCWarning(KSUD_LOG) << "Might not terminate at end of session.\n" ; |
| 195 | return -1; |
| 196 | } |
| 197 | } |
| 198 | #endif |
| 199 | |
| 200 | extern "C" { |
| 201 | void signal_exit(int); |
| 202 | void sigchld_handler(int); |
| 203 | } |
| 204 | |
| 205 | void signal_exit(int sig) |
| 206 | { |
| 207 | qCDebug(KSUD_LOG) << "Exiting on signal " << sig << "\n" ; |
| 208 | kdesud_cleanup(); |
| 209 | exit(status: 1); |
| 210 | } |
| 211 | |
| 212 | void sigchld_handler(int) |
| 213 | { |
| 214 | char c = ' '; |
| 215 | write(fd: pipeOfDeath[1], buf: &c, n: 1); |
| 216 | } |
| 217 | |
| 218 | /*! |
| 219 | * Creates an AF_UNIX socket in socket resource, mode 0600. |
| 220 | */ |
| 221 | |
| 222 | int create_socket() |
| 223 | { |
| 224 | int sockfd; |
| 225 | socklen_t addrlen; |
| 226 | struct stat s; |
| 227 | |
| 228 | QString display = QString::fromLocal8Bit(ba: qgetenv(varName: "DISPLAY" )); |
| 229 | if (display.isEmpty()) { |
| 230 | qCWarning(KSUD_LOG) << "$DISPLAY is not set\n" ; |
| 231 | return -1; |
| 232 | } |
| 233 | |
| 234 | // strip the screen number from the display |
| 235 | display.remove(re: QRegularExpression(QStringLiteral("\\.[0-9]+$" ))); |
| 236 | |
| 237 | sock = QFile::encodeName(fileName: QStandardPaths::writableLocation(type: QStandardPaths::RuntimeLocation) + QStringLiteral("/kdesud_%1" ).arg(a: display)); |
| 238 | int stat_err = lstat(file: sock.constData(), buf: &s); |
| 239 | if (!stat_err && S_ISLNK(s.st_mode)) { |
| 240 | qCWarning(KSUD_LOG) << "Someone is running a symlink attack on you\n" ; |
| 241 | if (unlink(name: sock.constData())) { |
| 242 | qCWarning(KSUD_LOG) << "Could not delete symlink\n" ; |
| 243 | return -1; |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | if (!access(name: sock.constData(), R_OK | W_OK)) { |
| 248 | KDESu::Client client; |
| 249 | if (client.ping() == -1) { |
| 250 | qCWarning(KSUD_LOG) << "stale socket exists\n" ; |
| 251 | if (unlink(name: sock.constData())) { |
| 252 | qCWarning(KSUD_LOG) << "Could not delete stale socket\n" ; |
| 253 | return -1; |
| 254 | } |
| 255 | } else { |
| 256 | qCWarning(KSUD_LOG) << "kdesud is already running\n" ; |
| 257 | return -1; |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | sockfd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, protocol: 0); |
| 262 | if (sockfd < 0) { |
| 263 | qCCritical(KSUD_LOG) << "socket(): " << ERR << "\n" ; |
| 264 | return -1; |
| 265 | } |
| 266 | |
| 267 | // Ensure socket closed on error |
| 268 | struct fd_ScopeGuard { |
| 269 | fd_ScopeGuard(int fd) |
| 270 | : _fd(fd) |
| 271 | { |
| 272 | } |
| 273 | ~fd_ScopeGuard() |
| 274 | { |
| 275 | if (_fd >= 0) { |
| 276 | close(fd: _fd); |
| 277 | } |
| 278 | } |
| 279 | fd_ScopeGuard(const fd_ScopeGuard &) = delete; |
| 280 | fd_ScopeGuard &operator=(const fd_ScopeGuard &) = delete; |
| 281 | void reset() |
| 282 | { |
| 283 | _fd = -1; |
| 284 | } |
| 285 | int _fd; |
| 286 | } guard(sockfd); |
| 287 | |
| 288 | struct sockaddr_un addr; |
| 289 | addr.sun_family = AF_UNIX; |
| 290 | strncpy(dest: addr.sun_path, src: sock.constData(), n: sizeof(addr.sun_path) - 1); |
| 291 | addr.sun_path[sizeof(addr.sun_path) - 1] = '\000'; |
| 292 | addrlen = SUN_LEN(&addr); |
| 293 | if (bind(fd: sockfd, addr: (struct sockaddr *)&addr, len: addrlen) < 0) { |
| 294 | qCCritical(KSUD_LOG) << "bind(): " << ERR << "\n" ; |
| 295 | return -1; |
| 296 | } |
| 297 | |
| 298 | struct linger lin; |
| 299 | lin.l_onoff = lin.l_linger = 0; |
| 300 | if (setsockopt(fd: sockfd, SOL_SOCKET, SO_LINGER, optval: (char *)&lin, optlen: sizeof(linger)) < 0) { |
| 301 | qCCritical(KSUD_LOG) << "setsockopt(SO_LINGER): " << ERR << "\n" ; |
| 302 | return -1; |
| 303 | } |
| 304 | |
| 305 | int opt = 1; |
| 306 | if (setsockopt(fd: sockfd, SOL_SOCKET, SO_REUSEADDR, optval: (char *)&opt, optlen: sizeof(opt)) < 0) { |
| 307 | qCCritical(KSUD_LOG) << "setsockopt(SO_REUSEADDR): " << ERR << "\n" ; |
| 308 | return -1; |
| 309 | } |
| 310 | opt = 1; |
| 311 | if (setsockopt(fd: sockfd, SOL_SOCKET, SO_KEEPALIVE, optval: (char *)&opt, optlen: sizeof(opt)) < 0) { |
| 312 | qCCritical(KSUD_LOG) << "setsockopt(SO_KEEPALIVE): " << ERR << "\n" ; |
| 313 | return -1; |
| 314 | } |
| 315 | chmod(file: sock.constData(), mode: 0600); |
| 316 | guard.reset(); |
| 317 | return sockfd; |
| 318 | } |
| 319 | /* The daemon stores passwords, which we don't want any other process to be able to read. */ |
| 320 | static bool prevent_tracing() |
| 321 | { |
| 322 | int r = -1; |
| 323 | #ifdef PR_SET_DUMPABLE |
| 324 | // Linux |
| 325 | r = prctl(PR_SET_DUMPABLE, 0, 0, 0, 0); |
| 326 | #elif defined(PROC_TRACE_CTL) |
| 327 | // FreeBSD |
| 328 | int disable = PROC_TRACE_CTL_DISABLE_EXEC; |
| 329 | r = procctl(P_PID, 0, PROC_TRACE_CTL, &disable); |
| 330 | #else |
| 331 | #warning Missing implementation for disabling traceability on this platform |
| 332 | #endif |
| 333 | |
| 334 | return r == 0; |
| 335 | } |
| 336 | |
| 337 | /*! |
| 338 | * Main program |
| 339 | */ |
| 340 | |
| 341 | int main(int argc, char *argv[]) |
| 342 | { |
| 343 | if (!prevent_tracing()) { |
| 344 | qCWarning(KSUD_LOG) << "failed to make process memory untraceable" << strerror(errno); |
| 345 | } |
| 346 | |
| 347 | QCoreApplication app(argc, argv); |
| 348 | KAboutData aboutData(QStringLiteral("kdesud" ) /* componentName */, |
| 349 | i18n(text: "KDE su daemon" ), |
| 350 | Version, |
| 351 | i18n(text: "Daemon used by kdesu" ), |
| 352 | KAboutLicense::Artistic, |
| 353 | i18n(text: "Copyright (c) 1999,2000 Geert Jansen" )); |
| 354 | aboutData.addAuthor(name: i18n(text: "Geert Jansen" ), task: i18n(text: "Author" ), QStringLiteral("jansen@kde.org" ), QStringLiteral("http://www.stack.nl/~geertj/" )); |
| 355 | |
| 356 | KAboutData::setApplicationData(aboutData); |
| 357 | QCommandLineParser parser; |
| 358 | aboutData.setupCommandLine(&parser); |
| 359 | parser.process(app); |
| 360 | aboutData.processCommandLine(parser: &parser); |
| 361 | |
| 362 | // Set core dump size to 0 |
| 363 | struct rlimit rlim; |
| 364 | rlim.rlim_cur = rlim.rlim_max = 0; |
| 365 | if (setrlimit(RLIMIT_CORE, rlimits: &rlim) < 0) { |
| 366 | qCCritical(KSUD_LOG) << "setrlimit(): " << ERR << "\n" ; |
| 367 | exit(status: 1); |
| 368 | } |
| 369 | |
| 370 | // Create the Unix socket. |
| 371 | int sockfd = create_socket(); |
| 372 | if (sockfd < 0) { |
| 373 | exit(status: 1); |
| 374 | } |
| 375 | if (listen(fd: sockfd, n: 10) < 0) { |
| 376 | qCCritical(KSUD_LOG) << "listen(): " << ERR << "\n" ; |
| 377 | kdesud_cleanup(); |
| 378 | exit(status: 1); |
| 379 | } |
| 380 | |
| 381 | if (sockfd != 3) { |
| 382 | sockfd = dup3(fd: sockfd, fd2: 3, O_CLOEXEC); |
| 383 | } |
| 384 | if (sockfd < 0) { |
| 385 | qCCritical(KSUD_LOG) << "Failed to set sockfd to fd 3" << ERR << "\n" ; |
| 386 | kdesud_cleanup(); |
| 387 | exit(status: 1); |
| 388 | } |
| 389 | |
| 390 | int maxfd = sockfd; |
| 391 | |
| 392 | if (closeExtraFds() < 0) { |
| 393 | qCCritical(KSUD_LOG) << "Failed to close file descriptors higher than 3, with error:" << ERR << "\n" ; |
| 394 | kdesud_cleanup(); |
| 395 | exit(status: 1); |
| 396 | } |
| 397 | |
| 398 | // Ok, we're accepting connections. Fork to the background. |
| 399 | pid_t pid = fork(); |
| 400 | if (pid == -1) { |
| 401 | qCCritical(KSUD_LOG) << "fork():" << ERR << "\n" ; |
| 402 | kdesud_cleanup(); |
| 403 | exit(status: 1); |
| 404 | } |
| 405 | if (pid) { |
| 406 | _exit(status: 0); |
| 407 | } |
| 408 | |
| 409 | #if HAVE_X11 |
| 410 | // Make sure we exit when the display gets closed. |
| 411 | int x11Fd = initXconnection(); |
| 412 | maxfd = qMax(a: maxfd, b: x11Fd); |
| 413 | #endif |
| 414 | |
| 415 | repo = new Repository; |
| 416 | QList<ConnectionHandler *> handler; |
| 417 | |
| 418 | pipe2(pipedes: pipeOfDeath, O_CLOEXEC); |
| 419 | maxfd = qMax(a: maxfd, b: pipeOfDeath[0]); |
| 420 | |
| 421 | // Signal handlers |
| 422 | struct sigaction sa; |
| 423 | sa.sa_handler = signal_exit; |
| 424 | sigemptyset(set: &sa.sa_mask); |
| 425 | sa.sa_flags = 0; |
| 426 | sigaction(SIGHUP, act: &sa, oact: nullptr); |
| 427 | sigaction(SIGINT, act: &sa, oact: nullptr); |
| 428 | sigaction(SIGTERM, act: &sa, oact: nullptr); |
| 429 | sigaction(SIGQUIT, act: &sa, oact: nullptr); |
| 430 | |
| 431 | sa.sa_handler = sigchld_handler; |
| 432 | sa.sa_flags = SA_NOCLDSTOP; |
| 433 | sigaction(SIGCHLD, act: &sa, oact: nullptr); |
| 434 | sa.sa_handler = SIG_IGN; |
| 435 | sigaction(SIGPIPE, act: &sa, oact: nullptr); |
| 436 | |
| 437 | // Main execution loop |
| 438 | |
| 439 | socklen_t addrlen; |
| 440 | struct sockaddr_un clientname; |
| 441 | |
| 442 | fd_set tmp_fds; |
| 443 | fd_set active_fds; |
| 444 | FD_ZERO(&active_fds); |
| 445 | FD_SET(sockfd, &active_fds); |
| 446 | FD_SET(pipeOfDeath[0], &active_fds); |
| 447 | #if HAVE_X11 |
| 448 | if (x11Fd != -1) { |
| 449 | FD_SET(x11Fd, &active_fds); |
| 450 | } |
| 451 | #endif |
| 452 | |
| 453 | while (1) { |
| 454 | tmp_fds = active_fds; |
| 455 | #if HAVE_X11 |
| 456 | if (x11Display) { |
| 457 | XFlush(x11Display); |
| 458 | } |
| 459 | #endif |
| 460 | if (select(nfds: maxfd + 1, readfds: &tmp_fds, writefds: nullptr, exceptfds: nullptr, timeout: nullptr) < 0) { |
| 461 | if (errno == EINTR) { |
| 462 | continue; |
| 463 | } |
| 464 | |
| 465 | qCCritical(KSUD_LOG) << "select(): " << ERR << "\n" ; |
| 466 | exit(status: 1); |
| 467 | } |
| 468 | repo->expire(); |
| 469 | for (int i = 0; i <= maxfd; i++) { |
| 470 | if (!FD_ISSET(i, &tmp_fds)) { |
| 471 | continue; |
| 472 | } |
| 473 | |
| 474 | if (i == pipeOfDeath[0]) { |
| 475 | char buf[101]; |
| 476 | read(fd: pipeOfDeath[0], buf: buf, nbytes: 100); |
| 477 | pid_t result; |
| 478 | do { |
| 479 | int status; |
| 480 | result = waitpid(pid: (pid_t)-1, stat_loc: &status, WNOHANG); |
| 481 | if (result > 0) { |
| 482 | for (int j = handler.size(); j--;) { |
| 483 | if (handler[j] && (handler[j]->m_pid == result)) { |
| 484 | handler[j]->m_exitCode = WEXITSTATUS(status); |
| 485 | handler[j]->m_hasExitCode = true; |
| 486 | handler[j]->sendExitCode(); |
| 487 | handler[j]->m_pid = 0; |
| 488 | break; |
| 489 | } |
| 490 | } |
| 491 | } |
| 492 | } while (result > 0); |
| 493 | } |
| 494 | |
| 495 | #if HAVE_X11 |
| 496 | if (i == x11Fd) { |
| 497 | // Discard X events |
| 498 | XEvent event_return; |
| 499 | if (x11Display) { |
| 500 | while (XPending(x11Display)) { |
| 501 | XNextEvent(x11Display, &event_return); |
| 502 | } |
| 503 | } |
| 504 | continue; |
| 505 | } |
| 506 | #endif |
| 507 | |
| 508 | if (i == sockfd) { |
| 509 | // Accept new connection |
| 510 | int fd; |
| 511 | addrlen = 64; |
| 512 | fd = accept(fd: sockfd, addr: (struct sockaddr *)&clientname, addr_len: &addrlen); |
| 513 | if (fd < 0) { |
| 514 | qCCritical(KSUD_LOG) << "accept():" << ERR << "\n" ; |
| 515 | continue; |
| 516 | } |
| 517 | while (fd + 1 > (int)handler.size()) { |
| 518 | handler.append(t: nullptr); |
| 519 | } |
| 520 | delete handler[fd]; |
| 521 | handler[fd] = new ConnectionHandler(fd); |
| 522 | maxfd = qMax(a: maxfd, b: fd); |
| 523 | FD_SET(fd, &active_fds); |
| 524 | fcntl(fd: fd, F_SETFL, fcntl(fd: fd, F_GETFL) | O_NONBLOCK); |
| 525 | continue; |
| 526 | } |
| 527 | |
| 528 | // handle already established connection |
| 529 | if (handler[i] && handler[i]->handle() < 0) { |
| 530 | delete handler[i]; |
| 531 | handler[i] = nullptr; |
| 532 | FD_CLR(i, &active_fds); |
| 533 | } |
| 534 | } |
| 535 | } |
| 536 | qCWarning(KSUD_LOG) << "???\n" ; |
| 537 | } |
| 538 | |