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 | |