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
99using namespace KDESu;
100
101static int closeExtraFds()
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
153Repository *repo;
154QString Version(QStringLiteral("1.01"));
155QByteArray sock;
156#if HAVE_X11
157Display *x11Display;
158#endif
159int pipeOfDeath[2];
160
161void kdesud_cleanup()
162{
163 unlink(name: sock.constData());
164}
165
166// Borrowed from kdebase/kaudio/kaudioserver.cpp
167
168#if HAVE_X11
169extern "C" int xio_errhandler(Display *);
170
171int 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
179int 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
200extern "C" {
201void signal_exit(int);
202void sigchld_handler(int);
203}
204
205void signal_exit(int sig)
206{
207 qCDebug(KSUD_LOG) << "Exiting on signal " << sig << "\n";
208 kdesud_cleanup();
209 exit(status: 1);
210}
211
212void 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
222int 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. */
320static 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
341int 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

source code of kdesu/src/kdesud/kdesud.cpp