File indexing completed on 2024-04-28 05:35:31
0001 /* 0002 ksmserver - the KDE session management server 0003 0004 SPDX-FileCopyrightText: 2000 Matthias Ettrich <ettrich@kde.org> 0005 SPDX-FileCopyrightText: 2005 Lubos Lunak <l.lunak@kde.org> 0006 SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org> 0007 0008 SPDX-FileContributor: Oswald Buddenhagen <ob6@inf.tu-dresden.de> 0009 0010 some code taken from the dcopserver (part of the KDE libraries), which is 0011 SPDX-FileCopyrightText: 1999 Matthias Ettrich <ettrich@kde.org> 0012 SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org> 0013 0014 SPDX-License-Identifier: MIT 0015 */ 0016 0017 #include "server.h" 0018 #include "client.h" 0019 #include "global.h" 0020 #include "klocalizedstring.h" 0021 #include "ksmserver_debug.h" 0022 #include "ksmserverinterfaceadaptor.h" 0023 0024 #include <config-ksmserver.h> 0025 #include <config-workspace.h> 0026 #include <pwd.h> 0027 #include <sys/param.h> 0028 #include <sys/resource.h> 0029 #include <sys/stat.h> 0030 #include <sys/types.h> 0031 #ifdef HAVE_SYS_TIME_H 0032 #include <sys/time.h> 0033 #endif 0034 #include <sys/socket.h> 0035 #include <sys/un.h> 0036 0037 #include <cassert> 0038 #include <cerrno> 0039 #include <climits> 0040 #include <csignal> 0041 #include <cstdlib> 0042 #include <cstring> 0043 #include <ctime> 0044 #include <fcntl.h> 0045 #include <unistd.h> 0046 0047 #include <QAction> 0048 #include <QApplication> 0049 #include <QDBusConnection> 0050 #include <QDebug> 0051 #include <QFile> 0052 #include <QPushButton> 0053 #include <QRegularExpression> 0054 #include <QSocketNotifier> 0055 #include <QStandardPaths> 0056 0057 #include <KNotification> 0058 #include <KSharedConfig> 0059 #include <QTemporaryFile> 0060 #include <kactioncollection.h> 0061 #include <kauthorized.h> 0062 #include <kconfig.h> 0063 #include <kconfiggroup.h> 0064 #include <kdesktopfile.h> 0065 #include <kprocess.h> 0066 #include <kshell.h> 0067 0068 #include <KApplicationTrader> 0069 #include <KIO/CommandLauncherJob> 0070 #include <KIO/DesktopExecParser> 0071 #include <KService> 0072 0073 #include <KScreenLocker/KsldApp> 0074 0075 #include <private/qtx11extras_p.h> 0076 0077 #include <krandom.h> 0078 #include <qstandardpaths.h> 0079 #include <startup_interface.h> 0080 0081 #include "kscreenlocker_interface.h" 0082 #include "kwinsession_interface.h" 0083 0084 #include <KUpdateLaunchEnvironmentJob> 0085 0086 extern "C" { 0087 #include <X11/ICE/ICEmsg.h> 0088 #include <X11/ICE/ICEproto.h> 0089 #include <X11/ICE/ICEutil.h> 0090 } 0091 0092 // ICEmsg.h has a static_assert macro, which conflicts with C++'s static_assert. 0093 #ifdef static_assert 0094 #undef static_assert 0095 #endif 0096 0097 namespace 0098 { 0099 size_t fileNumberLimit() 0100 { 0101 static auto limit = []() -> size_t { 0102 struct rlimit limit { 0103 }; 0104 if (getrlimit(RLIMIT_NOFILE, &limit) != 0) { 0105 const auto err = errno; 0106 qCWarning(KSMSERVER) << "Failed to getrlimit:" << strerror(err); 0107 return std::numeric_limits<size_t>::max(); 0108 } 0109 return limit.rlim_cur; 0110 }(); 0111 return limit; 0112 } 0113 0114 size_t perAppStartLimit() 0115 { 0116 static auto limit = fileNumberLimit() / 4; 0117 return limit; 0118 } 0119 } // namespace 0120 0121 KSMServer *the_server = nullptr; 0122 0123 KSMServer *KSMServer::self() 0124 { 0125 return the_server; 0126 } 0127 0128 /*! Utility function to execute a command on the local machine. Used 0129 * to restart applications. 0130 */ 0131 void KSMServer::startApplication(const QStringList &cmd, const QString &clientMachine, const QString &userId) 0132 { 0133 QStringList command = cmd; 0134 if (command.isEmpty()) { 0135 return; 0136 } 0137 if (!userId.isEmpty()) { 0138 struct passwd *pw = getpwuid(getuid()); 0139 if (pw != nullptr && userId != QString::fromLocal8Bit(pw->pw_name)) { 0140 command.prepend(QStringLiteral("--")); 0141 command.prepend(userId); 0142 command.prepend(QStringLiteral("-u")); 0143 command.prepend(QStandardPaths::findExecutable(QStringLiteral("kdesu"))); 0144 } 0145 } 0146 if (!clientMachine.isEmpty() && clientMachine != QLatin1String("localhost")) { 0147 command.prepend(clientMachine); 0148 command.prepend(xonCommand); // "xon" by default 0149 } 0150 0151 const QString app = command.takeFirst(); 0152 const QStringList argList = command; 0153 auto *job = new KIO::CommandLauncherJob(app, argList); 0154 auto apps = KApplicationTrader::query([&app](const KService::Ptr &service) { 0155 const QString binary = KIO::DesktopExecParser::executablePath(service->exec()); 0156 return !service->noDisplay() && !binary.isEmpty() && app.endsWith(binary); 0157 }); 0158 if (!apps.empty()) { 0159 job->setDesktopName(apps[0]->desktopEntryName()); 0160 } 0161 job->start(); 0162 } 0163 0164 /*! Utility function to execute a command on the local machine. Used 0165 * to discard session data 0166 */ 0167 void KSMServer::executeCommand(const QStringList &command) 0168 { 0169 if (command.isEmpty()) { 0170 return; 0171 } 0172 0173 KProcess::execute(command); 0174 } 0175 0176 IceAuthDataEntry *authDataEntries = nullptr; 0177 0178 static QTemporaryFile *remTempFile = nullptr; 0179 0180 static IceListenObj *listenObjs = nullptr; 0181 int numTransports = 0; 0182 static bool only_local = false; 0183 0184 static Bool HostBasedAuthProc(char * /*hostname*/) 0185 { 0186 return only_local; 0187 } 0188 0189 Status KSMRegisterClientProc(SmsConn /* smsConn */, SmPointer managerData, char *previousId) 0190 { 0191 KSMClient *client = (KSMClient *)managerData; 0192 client->registerClient(previousId); 0193 return 1; 0194 } 0195 0196 void KSMInteractRequestProc(SmsConn /* smsConn */, SmPointer managerData, int dialogType) 0197 { 0198 the_server->interactRequest((KSMClient *)managerData, dialogType); 0199 } 0200 0201 void KSMInteractDoneProc(SmsConn /* smsConn */, SmPointer managerData, Bool cancelShutdown) 0202 { 0203 the_server->interactDone((KSMClient *)managerData, cancelShutdown); 0204 } 0205 0206 void KSMSaveYourselfRequestProc(SmsConn smsConn, SmPointer /* managerData */, int saveType, Bool shutdown, int interactStyle, Bool fast, Bool global) 0207 { 0208 if (shutdown) { 0209 qCWarning(KSMSERVER) << "Shutdown via ICE was called but not supported"; 0210 } 0211 if (!global) { 0212 SmsSaveYourself(smsConn, saveType, false, interactStyle, fast); 0213 SmsSaveComplete(smsConn); 0214 } 0215 // else checkpoint only, ksmserver does not yet support this 0216 // mode. Will come for KDE 3.1 0217 } 0218 0219 void KSMSaveYourselfPhase2RequestProc(SmsConn /* smsConn */, SmPointer managerData) 0220 { 0221 the_server->phase2Request((KSMClient *)managerData); 0222 } 0223 0224 void KSMSaveYourselfDoneProc(SmsConn /* smsConn */, SmPointer managerData, Bool success) 0225 { 0226 the_server->saveYourselfDone((KSMClient *)managerData, success); 0227 } 0228 0229 void KSMCloseConnectionProc(SmsConn smsConn, SmPointer managerData, int count, char **reasonMsgs) 0230 { 0231 the_server->deleteClient((KSMClient *)managerData); 0232 if (count) { 0233 SmFreeReasons(count, reasonMsgs); 0234 } 0235 IceConn iceConn = SmsGetIceConnection(smsConn); 0236 SmsCleanUp(smsConn); 0237 IceSetShutdownNegotiation(iceConn, false); 0238 IceCloseConnection(iceConn); 0239 } 0240 0241 void KSMSetPropertiesProc(SmsConn /* smsConn */, SmPointer managerData, int numProps, SmProp **props) 0242 { 0243 auto client = (KSMClient *)managerData; 0244 for (int i = 0; i < numProps; i++) { 0245 SmProp *p = client->property(props[i]->name); 0246 if (p) { 0247 client->properties.removeAll(p); 0248 SmFreeProperty(p); 0249 } 0250 client->properties.append(props[i]); 0251 } 0252 0253 if (numProps) { 0254 free(props); 0255 } 0256 } 0257 0258 void KSMDeletePropertiesProc(SmsConn /* smsConn */, SmPointer managerData, int numProps, char **propNames) 0259 { 0260 auto client = (KSMClient *)managerData; 0261 for (int i = 0; i < numProps; i++) { 0262 SmProp *p = client->property(propNames[i]); 0263 if (p) { 0264 client->properties.removeAll(p); 0265 SmFreeProperty(p); 0266 } 0267 } 0268 } 0269 0270 void KSMGetPropertiesProc(SmsConn smsConn, SmPointer managerData) 0271 { 0272 auto client = (KSMClient *)managerData; 0273 auto props = new SmProp *[client->properties.count()]; 0274 int i = 0; 0275 foreach (SmProp *prop, client->properties) 0276 props[i++] = prop; 0277 0278 SmsReturnProperties(smsConn, i, props); 0279 delete[] props; 0280 } 0281 0282 class KSMListener : public QSocketNotifier 0283 { 0284 public: 0285 explicit KSMListener(IceListenObj obj) 0286 : QSocketNotifier(IceGetListenConnectionNumber(obj), QSocketNotifier::Read) 0287 , listenObj(obj) 0288 { 0289 } 0290 0291 IceListenObj listenObj; 0292 }; 0293 0294 class KSMConnection : public QSocketNotifier 0295 { 0296 public: 0297 explicit KSMConnection(IceConn conn) 0298 : QSocketNotifier(IceConnectionNumber(conn), QSocketNotifier::Read) 0299 , iceConn(conn) 0300 { 0301 count++; 0302 } 0303 0304 ~KSMConnection() override 0305 { 0306 count--; 0307 } 0308 0309 Q_DISABLE_COPY_MOVE(KSMConnection) 0310 0311 IceConn iceConn; 0312 inline static size_t count = 0; 0313 }; 0314 0315 /* for printing hex digits */ 0316 static void fprintfhex(FILE *fp, unsigned int len, char *cp) 0317 { 0318 static const char hexchars[] = "0123456789abcdef"; 0319 0320 for (; len > 0; len--, cp++) { 0321 unsigned char s = *cp; 0322 putc(hexchars[s >> 4], fp); 0323 putc(hexchars[s & 0x0f], fp); 0324 } 0325 } 0326 0327 /* 0328 * We use temporary files which contain commands to add/remove entries from 0329 * the .ICEauthority file. 0330 */ 0331 static void write_iceauth(FILE *addfp, FILE *removefp, IceAuthDataEntry *entry) 0332 { 0333 fprintf(addfp, "add %s \"\" %s %s ", entry->protocol_name, entry->network_id, entry->auth_name); 0334 fprintfhex(addfp, entry->auth_data_length, entry->auth_data); 0335 fprintf(addfp, "\n"); 0336 0337 fprintf(removefp, "remove protoname=%s protodata=\"\" netid=%s authname=%s\n", entry->protocol_name, entry->network_id, entry->auth_name); 0338 } 0339 0340 #define MAGIC_COOKIE_LEN 16 0341 0342 Status SetAuthentication_local(int count, IceListenObj *listenObjs) 0343 { 0344 for (int i = 0; i < count; i++) { 0345 char *prot = IceGetListenConnectionString(listenObjs[i]); 0346 if (!prot) { 0347 continue; 0348 } 0349 char *host = strchr(prot, '/'); 0350 char *sock = nullptr; 0351 if (host) { 0352 *host = 0; 0353 host++; 0354 sock = strchr(host, ':'); 0355 if (sock) { 0356 *sock = 0; 0357 sock++; 0358 } 0359 } 0360 qCDebug(KSMSERVER) << "KSMServer: SetAProc_loc: conn " << (unsigned)i << ", prot=" << prot << ", file=" << sock; 0361 if (sock && !strcmp(prot, "local")) { 0362 chmod(sock, 0700); 0363 } 0364 IceSetHostBasedAuthProc(listenObjs[i], HostBasedAuthProc); 0365 free(prot); 0366 } 0367 return 1; 0368 } 0369 0370 Status SetAuthentication(int count, IceListenObj *listenObjs, IceAuthDataEntry **authDataEntries) 0371 { 0372 QTemporaryFile addTempFile; 0373 remTempFile = new QTemporaryFile; 0374 0375 if (!addTempFile.open() || !remTempFile->open()) { 0376 return 0; 0377 } 0378 0379 if ((*authDataEntries = (IceAuthDataEntry *)malloc(count * 2 * sizeof(IceAuthDataEntry))) == nullptr) { 0380 return 0; 0381 } 0382 0383 FILE *addAuthFile = fopen(QFile::encodeName(addTempFile.fileName()).constData(), "r+"); 0384 FILE *remAuthFile = fopen(QFile::encodeName(remTempFile->fileName()).constData(), "r+"); 0385 0386 for (int i = 0; i < numTransports * 2; i += 2) { 0387 (*authDataEntries)[i].network_id = IceGetListenConnectionString(listenObjs[i / 2]); 0388 (*authDataEntries)[i].protocol_name = (char *)"ICE"; 0389 (*authDataEntries)[i].auth_name = (char *)"MIT-MAGIC-COOKIE-1"; 0390 0391 (*authDataEntries)[i].auth_data = IceGenerateMagicCookie(MAGIC_COOKIE_LEN); 0392 (*authDataEntries)[i].auth_data_length = MAGIC_COOKIE_LEN; 0393 0394 (*authDataEntries)[i + 1].network_id = IceGetListenConnectionString(listenObjs[i / 2]); 0395 (*authDataEntries)[i + 1].protocol_name = (char *)"XSMP"; 0396 (*authDataEntries)[i + 1].auth_name = (char *)"MIT-MAGIC-COOKIE-1"; 0397 0398 (*authDataEntries)[i + 1].auth_data = IceGenerateMagicCookie(MAGIC_COOKIE_LEN); 0399 (*authDataEntries)[i + 1].auth_data_length = MAGIC_COOKIE_LEN; 0400 0401 write_iceauth(addAuthFile, remAuthFile, &(*authDataEntries)[i]); 0402 write_iceauth(addAuthFile, remAuthFile, &(*authDataEntries)[i + 1]); 0403 0404 IceSetPaAuthData(2, &(*authDataEntries)[i]); 0405 0406 IceSetHostBasedAuthProc(listenObjs[i / 2], HostBasedAuthProc); 0407 } 0408 fclose(addAuthFile); 0409 fclose(remAuthFile); 0410 0411 QString iceAuth = QStandardPaths::findExecutable(QStringLiteral("iceauth")); 0412 if (iceAuth.isEmpty()) { 0413 qCWarning(KSMSERVER, "KSMServer: could not find iceauth"); 0414 return 0; 0415 } 0416 0417 KProcess p; 0418 p << iceAuth << QStringLiteral("source") << addTempFile.fileName(); 0419 p.execute(); 0420 0421 return (1); 0422 } 0423 0424 /* 0425 * Free up authentication data. 0426 */ 0427 void FreeAuthenticationData(int count, IceAuthDataEntry *authDataEntries) 0428 { 0429 /* Each transport has entries for ICE and XSMP */ 0430 if (only_local) { 0431 return; 0432 } 0433 0434 for (int i = 0; i < count * 2; i++) { 0435 free(authDataEntries[i].network_id); 0436 free(authDataEntries[i].auth_data); 0437 } 0438 0439 free(authDataEntries); 0440 0441 QString iceAuth = QStandardPaths::findExecutable(QStringLiteral("iceauth")); 0442 if (iceAuth.isEmpty()) { 0443 qCWarning(KSMSERVER, "KSMServer: could not find iceauth"); 0444 return; 0445 } 0446 0447 if (remTempFile) { 0448 KProcess p; 0449 p << iceAuth << QStringLiteral("source") << remTempFile->fileName(); 0450 p.execute(); 0451 } 0452 0453 delete remTempFile; 0454 remTempFile = nullptr; 0455 } 0456 0457 static int Xio_ErrorHandler(Display *) 0458 { 0459 qCWarning(KSMSERVER, "ksmserver: Fatal IO error: client killed"); 0460 0461 // Don't do anything that might require the X connection 0462 if (the_server) { 0463 KSMServer *server = the_server; 0464 the_server = nullptr; 0465 server->cleanUp(); 0466 // Don't delete server!! 0467 } 0468 0469 exit(0); // Don't report error, it's not our fault. 0470 return 0; // Bogus return value, notreached 0471 } 0472 0473 void KSMServer::setupXIOErrorHandler() 0474 { 0475 XSetIOErrorHandler(Xio_ErrorHandler); 0476 } 0477 0478 static int wake_up_socket = -1; 0479 static void sighandler(int sig) 0480 { 0481 if (sig == SIGHUP) { 0482 signal(SIGHUP, sighandler); 0483 return; 0484 } 0485 0486 char ch = 0; 0487 std::ignore = ::write(wake_up_socket, &ch, 1); 0488 } 0489 0490 void KSMWatchProc(IceConn iceConn, IcePointer client_data, Bool opening, IcePointer *watch_data) 0491 { 0492 auto ds = (KSMServer *)client_data; 0493 0494 if (opening) { 0495 *watch_data = (IcePointer)ds->watchConnection(iceConn); 0496 } else { 0497 ds->removeConnection((KSMConnection *)*watch_data); 0498 } 0499 } 0500 0501 static Status KSMNewClientProc(SmsConn conn, SmPointer manager_data, unsigned long *mask_ret, SmsCallbacks *cb, char **failure_reason_ret) 0502 { 0503 *failure_reason_ret = nullptr; 0504 0505 void *client = ((KSMServer *)manager_data)->newClient(conn); 0506 if (!client) { 0507 const char *errstr = "Connection rejected: ksmserver is shutting down"; 0508 qCWarning(KSMSERVER, "%s", errstr); 0509 0510 if ((*failure_reason_ret = (char *)malloc(strlen(errstr) + 1)) != nullptr) { 0511 strcpy(*failure_reason_ret, errstr); 0512 } 0513 return 0; 0514 } 0515 0516 cb->register_client.callback = KSMRegisterClientProc; 0517 cb->register_client.manager_data = client; 0518 cb->interact_request.callback = KSMInteractRequestProc; 0519 cb->interact_request.manager_data = client; 0520 cb->interact_done.callback = KSMInteractDoneProc; 0521 cb->interact_done.manager_data = client; 0522 cb->save_yourself_request.callback = KSMSaveYourselfRequestProc; 0523 cb->save_yourself_request.manager_data = client; 0524 cb->save_yourself_phase2_request.callback = KSMSaveYourselfPhase2RequestProc; 0525 cb->save_yourself_phase2_request.manager_data = client; 0526 cb->save_yourself_done.callback = KSMSaveYourselfDoneProc; 0527 cb->save_yourself_done.manager_data = client; 0528 cb->close_connection.callback = KSMCloseConnectionProc; 0529 cb->close_connection.manager_data = client; 0530 cb->set_properties.callback = KSMSetPropertiesProc; 0531 cb->set_properties.manager_data = client; 0532 cb->delete_properties.callback = KSMDeletePropertiesProc; 0533 cb->delete_properties.manager_data = client; 0534 cb->get_properties.callback = KSMGetPropertiesProc; 0535 cb->get_properties.manager_data = client; 0536 0537 *mask_ret = SmsRegisterClientProcMask | SmsInteractRequestProcMask | SmsInteractDoneProcMask | SmsSaveYourselfRequestProcMask 0538 | SmsSaveYourselfP2RequestProcMask | SmsSaveYourselfDoneProcMask | SmsCloseConnectionProcMask | SmsSetPropertiesProcMask | SmsDeletePropertiesProcMask 0539 | SmsGetPropertiesProcMask; 0540 return 1; 0541 } 0542 0543 #ifdef HAVE__ICETRANSNOLISTEN 0544 extern "C" int _IceTransNoListen(const char *protocol); 0545 #endif 0546 0547 KSMServer::KSMServer(InitFlags flags) 0548 : sessionGroup(QLatin1String("")) 0549 , m_kwinInterface(new OrgKdeKWinSessionInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Session"), QDBusConnection::sessionBus(), this)) 0550 , sockets{-1, -1} 0551 { 0552 if (!flags.testFlag(InitFlag::NoLockScreen)) { 0553 ScreenLocker::KSldApp::self()->initialize(); 0554 if (flags.testFlag(InitFlag::ImmediateLockScreen)) { 0555 ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); 0556 } 0557 } 0558 0559 if (::socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sockets) != 0) { 0560 qFatal("Could not create socket pair, error %d (%s)", errno, strerror(errno)); 0561 } 0562 wake_up_socket = sockets[0]; 0563 auto notifier = new QSocketNotifier(sockets[1], QSocketNotifier::Read, this); 0564 qApp->connect(notifier, &QSocketNotifier::activated, &QApplication::quit); 0565 0566 new KSMServerInterfaceAdaptor(this); 0567 QDBusConnection::sessionBus().registerObject(QStringLiteral("/KSMServer"), this); 0568 the_server = this; 0569 clean = false; 0570 0571 state = Idle; 0572 saveSession = false; 0573 KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("General")); 0574 clientInteracting = nullptr; 0575 xonCommand = config.readEntry("xonCommand", "xon"); 0576 0577 only_local = flags.testFlag(InitFlag::OnlyLocal); 0578 #ifdef HAVE__ICETRANSNOLISTEN 0579 if (only_local) { 0580 _IceTransNoListen("tcp"); 0581 } 0582 #else 0583 only_local = false; 0584 #endif 0585 0586 char errormsg[256]; 0587 if (!SmsInitialize((char *)KSMVendorString, (char *)KSMReleaseString, KSMNewClientProc, (SmPointer)this, HostBasedAuthProc, 256, errormsg)) { 0588 qCWarning(KSMSERVER, "KSMServer: could not register XSM protocol"); 0589 } 0590 0591 if (!IceListenForConnections(&numTransports, &listenObjs, 256, errormsg)) { 0592 qCWarning(KSMSERVER, "KSMServer: Error listening for connections: %s", errormsg); 0593 qCWarning(KSMSERVER, "KSMServer: Aborting."); 0594 exit(1); 0595 } 0596 0597 { 0598 // publish available transports. 0599 QByteArray fName = 0600 QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QDir::separator() + QStringLiteral("KSMserver")); 0601 qCDebug(KSMSERVER) << fName; 0602 QString display = QString::fromLocal8Bit(::getenv("DISPLAY")); 0603 // strip the screen number from the display 0604 display.remove(QRegularExpression(QStringLiteral("\\.[0-9]+$"))); 0605 int i = 0; 0606 while ((i = display.indexOf(QLatin1Char(':'))) >= 0) { 0607 display[i] = QLatin1Char('_'); 0608 } 0609 while ((i = display.indexOf(QLatin1Char('/'))) >= 0) { 0610 display[i] = QLatin1Char('_'); 0611 } 0612 0613 fName += '_' + display.toLocal8Bit(); 0614 FILE *f = ::fopen(fName.data(), "w+"); 0615 if (!f) { 0616 qCWarning(KSMSERVER, "KSMServer: cannot open %s: %s", fName.data(), strerror(errno)); 0617 qCWarning(KSMSERVER, "KSMServer: Aborting."); 0618 exit(1); 0619 } 0620 char *session_manager = IceComposeNetworkIdList(numTransports, listenObjs); 0621 fprintf(f, "%s\n%i\n", session_manager, getpid()); 0622 fclose(f); 0623 setenv("SESSION_MANAGER", session_manager, true); 0624 0625 QProcessEnvironment newEnv; 0626 newEnv.insert(QStringLiteral("SESSION_MANAGER"), QString::fromLatin1(session_manager)); 0627 auto updateEnvJob = new KUpdateLaunchEnvironmentJob(newEnv); 0628 QEventLoop loop; 0629 QObject::connect(updateEnvJob, &KUpdateLaunchEnvironmentJob::finished, &loop, &QEventLoop::quit); 0630 loop.exec(); 0631 0632 free(session_manager); 0633 } 0634 0635 if (only_local) { 0636 if (!SetAuthentication_local(numTransports, listenObjs)) { 0637 qFatal("KSMSERVER: authentication setup failed."); 0638 } 0639 } else { 0640 if (!SetAuthentication(numTransports, listenObjs, &authDataEntries)) { 0641 qFatal("KSMSERVER: authentication setup failed."); 0642 } 0643 } 0644 0645 IceAddConnectionWatch(KSMWatchProc, (IcePointer)this); 0646 0647 KSMListener *con = nullptr; 0648 for (int i = 0; i < numTransports; i++) { 0649 fcntl(IceGetListenConnectionNumber(listenObjs[i]), F_SETFD, FD_CLOEXEC); 0650 con = new KSMListener(listenObjs[i]); 0651 listener.append(con); 0652 connect(con, &KSMListener::activated, this, &KSMServer::newConnection); 0653 } 0654 0655 signal(SIGHUP, sighandler); 0656 signal(SIGTERM, sighandler); 0657 signal(SIGINT, sighandler); 0658 signal(SIGPIPE, SIG_IGN); 0659 0660 connect(&protectionTimer, &QTimer::timeout, this, &KSMServer::protectionTimeout); 0661 connect(this, &KSMServer::sessionRestored, this, [this]() { 0662 auto reply = m_restoreSessionCall.createReply(); 0663 QDBusConnection::sessionBus().send(reply); 0664 m_restoreSessionCall = QDBusMessage(); 0665 }); 0666 connect(qApp, &QApplication::aboutToQuit, this, &KSMServer::cleanUp); 0667 0668 setupXIOErrorHandler(); 0669 0670 QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), 0671 QStringLiteral("/KSplash"), 0672 QStringLiteral("org.kde.KSplash"), 0673 QStringLiteral("setStage")); 0674 ksplashProgressMessage.setArguments({QStringLiteral("ksmserver")}); 0675 QDBusConnection::sessionBus().call(ksplashProgressMessage, QDBus::NoBlock); 0676 } 0677 0678 KSMServer::~KSMServer() 0679 { 0680 qDeleteAll(listener); 0681 the_server = nullptr; 0682 cleanUp(); 0683 } 0684 0685 void KSMServer::cleanUp() 0686 { 0687 if (clean) { 0688 return; 0689 } 0690 clean = true; 0691 IceFreeListenObjs(numTransports, listenObjs); 0692 0693 wake_up_socket = -1; 0694 ::close(sockets[1]); 0695 ::close(sockets[0]); 0696 sockets[0] = -1; 0697 sockets[1] = -1; 0698 0699 QByteArray fName = QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + QLatin1Char('/') + QStringLiteral("KSMserver")); 0700 QString display = QString::fromLocal8Bit(::getenv("DISPLAY")); 0701 // strip the screen number from the display 0702 display.remove(QRegularExpression(QStringLiteral("\\.[0-9]+$"))); 0703 int i = 0; 0704 while ((i = display.indexOf(QLatin1Char(':'))) >= 0) { 0705 display[i] = QLatin1Char('_'); 0706 } 0707 while ((i = display.indexOf(QLatin1Char('/'))) >= 0) { 0708 display[i] = QLatin1Char('_'); 0709 } 0710 0711 fName += '_' + display.toLocal8Bit(); 0712 ::unlink(fName.data()); 0713 0714 FreeAuthenticationData(numTransports, authDataEntries); 0715 signal(SIGTERM, SIG_DFL); 0716 signal(SIGINT, SIG_DFL); 0717 } 0718 0719 void *KSMServer::watchConnection(IceConn iceConn) 0720 { 0721 auto conn = new KSMConnection(iceConn); 0722 connect(conn, &KSMConnection::activated, this, &KSMServer::processData); 0723 return (void *)conn; 0724 } 0725 0726 void KSMServer::removeConnection(KSMConnection *conn) 0727 { 0728 delete conn; 0729 } 0730 0731 /*! 0732 Called from our IceIoErrorHandler 0733 */ 0734 void KSMServer::ioError(IceConn /*iceConn*/) 0735 { 0736 } 0737 0738 void KSMServer::processData(int /*socket*/) 0739 { 0740 auto iceConn = ((KSMConnection *)sender())->iceConn; 0741 IceProcessMessagesStatus status = IceProcessMessages(iceConn, nullptr, nullptr); 0742 if (status == IceProcessMessagesIOError) { 0743 IceSetShutdownNegotiation(iceConn, false); 0744 QList<KSMClient *>::iterator it = clients.begin(); 0745 QList<KSMClient *>::iterator const itEnd = clients.end(); 0746 while ((it != itEnd) && *it && (SmsGetIceConnection((*it)->connection()) != iceConn)) { 0747 ++it; 0748 } 0749 if ((it != itEnd) && *it) { 0750 SmsConn smsConn = (*it)->connection(); 0751 deleteClient(*it); 0752 SmsCleanUp(smsConn); 0753 } 0754 (void)IceCloseConnection(iceConn); 0755 } 0756 } 0757 0758 KSMClient *KSMServer::newClient(SmsConn conn) 0759 { 0760 KSMClient *client = nullptr; 0761 if (state != Killing) { 0762 client = new KSMClient(conn); 0763 clients.append(client); 0764 } 0765 return client; 0766 } 0767 0768 void KSMServer::deleteClient(KSMClient *client) 0769 { 0770 if (!clients.contains(client)) { // paranoia 0771 return; 0772 } 0773 clients.removeAll(client); 0774 clientsToKill.removeAll(client); 0775 clientsToSave.removeAll(client); 0776 if (client == clientInteracting) { 0777 clientInteracting = nullptr; 0778 handlePendingInteractions(); 0779 } 0780 delete client; 0781 if (state == Shutdown || state == Checkpoint || state == ClosingSubSession) { 0782 completeShutdownOrCheckpoint(); 0783 } 0784 if (state == Killing) { 0785 completeKilling(); 0786 } else if (state == KillingSubSession) { 0787 completeKillingSubSession(); 0788 } 0789 } 0790 0791 void KSMServer::newConnection(int /*socket*/) 0792 { 0793 IceAcceptStatus status = IceAcceptFailure; 0794 auto iceConn = IceAcceptConnection(((KSMListener *)sender())->listenObj, &status); 0795 if (!iceConn) { 0796 return; 0797 } 0798 0799 // This should be sufficient to hold all open file descriptors that are not connection watches. The rest we will 0800 // freely use to watch ICE connections. 0801 static const size_t backupFileCount = 128; 0802 qCDebug(KSMSERVER) << "KSMConnection::count" << KSMConnection::count; 0803 if (KSMConnection::count > (fileNumberLimit() - backupFileCount)) { 0804 // https://bugs.kde.org/show_bug.cgi?id=475506 0805 qCWarning(KSMSERVER) << "Too many open connections. Refusing to track any more to prevent exhaustion of open file limits."; 0806 QHash<QString, size_t> consumers; 0807 for (const auto &client : clients) { 0808 QString program = client->program(); 0809 if (program.isEmpty() && !client->restartCommand().isEmpty()) { 0810 program = client->restartCommand().first(); 0811 } 0812 if (program.isEmpty()) { 0813 program = i18nc("@label an unknown executable is using resources", "[unknown]"); 0814 } 0815 0816 if (!consumers.contains(program)) { 0817 consumers[program] = 0; 0818 } 0819 consumers[program]++; 0820 } 0821 std::multimap<size_t, QString> ranking; // using STL container because QMultiMap has no reverse iterators 0822 for (auto it = consumers.cbegin(); it != consumers.cend(); it++) { 0823 ranking.emplace(it.value(), it.key()); 0824 } 0825 0826 QString consumerInfo; 0827 int i = 0; 0828 for (auto it = ranking.rbegin(); i != 3 && it != ranking.rend(); it++, i++) { 0829 if (!consumerInfo.isEmpty()) { 0830 consumerInfo += QLatin1String(", "); 0831 } 0832 consumerInfo += QStringLiteral("%1:%2").arg(it->second, QString::number(it->first)); 0833 } 0834 KNotification::event(KNotification::Error, 0835 xi18nc("@label notification; %1 is a list of executables", 0836 "Unable to manage some apps because the system's session management resources are exhausted. Here are the top three " 0837 "consumers of session resources:\n%1", 0838 consumerInfo), 0839 QPixmap(), 0840 KNotification::Persistent); 0841 0842 std::ignore = IceCloseConnection(iceConn); 0843 return; 0844 } 0845 0846 IceSetShutdownNegotiation(iceConn, false); 0847 IceConnectStatus cstatus = IceConnectPending; 0848 while ((cstatus = IceConnectionStatus(iceConn)) == IceConnectPending) { 0849 (void)IceProcessMessages(iceConn, nullptr, nullptr); 0850 } 0851 0852 if (cstatus != IceConnectAccepted) { 0853 if (cstatus == IceConnectIOError) { 0854 qCDebug(KSMSERVER) << "IO error opening ICE Connection!"; 0855 } else { 0856 qCDebug(KSMSERVER) << "ICE Connection rejected!"; 0857 } 0858 std::ignore = IceCloseConnection(iceConn); 0859 return; 0860 } 0861 0862 // don't leak the fd 0863 fcntl(IceConnectionNumber(iceConn), F_SETFD, FD_CLOEXEC); 0864 } 0865 0866 QString KSMServer::currentSession() 0867 { 0868 if (sessionGroup.startsWith(SESSION_PREFIX)) { 0869 return sessionGroup.mid(SESSION_PREFIX.size()); 0870 } 0871 return QLatin1String(""); // empty, not null, since used for KConfig::setGroup // TODO does this comment make any sense? 0872 } 0873 0874 void KSMServer::discardSession() 0875 { 0876 KConfigGroup config(KSharedConfig::openConfig(), sessionGroup); 0877 int count = config.readEntry("count", 0); 0878 foreach (KSMClient *c, clients) { 0879 QStringList discardCommand = c->discardCommand(); 0880 if (discardCommand.isEmpty()) { 0881 continue; 0882 } 0883 // check that non of the old clients used the exactly same 0884 // discardCommand before we execute it. This used to be the 0885 // case up to KDE and Qt < 3.1 0886 int i = 1; 0887 while (i <= count && config.readPathEntry(QStringLiteral("discardCommand") + QString::number(i), QStringList()) != discardCommand) { 0888 i++; 0889 } 0890 if (i <= count) { 0891 executeCommand(discardCommand); 0892 } 0893 } 0894 } 0895 0896 void KSMServer::storeSession() 0897 { 0898 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0899 config->reparseConfiguration(); // config may have changed in the KControl module 0900 KConfigGroup generalGroup(config, QStringLiteral("General")); 0901 excludeApps = generalGroup.readEntry("excludeApps").toLower().split(QRegularExpression(QStringLiteral("[,:]")), Qt::SkipEmptyParts); 0902 KConfigGroup configSessionGroup(config, sessionGroup); 0903 int count = configSessionGroup.readEntry("count", 0); 0904 for (int i = 1; i <= count; i++) { 0905 const auto discardCommand = configSessionGroup.readPathEntry(QLatin1String("discardCommand") + QString::number(i), QStringList()); 0906 if (discardCommand.isEmpty()) { 0907 continue; 0908 } 0909 // check that non of the new clients uses the exactly same 0910 // discardCommand before we execute it. This used to be the 0911 // case up to KDE and Qt < 3.1 0912 QList<KSMClient *>::iterator it = clients.begin(); 0913 QList<KSMClient *>::iterator const itEnd = clients.end(); 0914 while ((it != itEnd) && *it && (discardCommand != (*it)->discardCommand())) { 0915 ++it; 0916 } 0917 if ((it != itEnd) && *it) { 0918 continue; 0919 } 0920 executeCommand(discardCommand); 0921 } 0922 config->deleteGroup(sessionGroup); // ### does not work with global config object... 0923 KConfigGroup cg(config, sessionGroup); 0924 count = 0; 0925 0926 // Tell kwin to save its state 0927 auto reply = m_kwinInterface->finishSaveSession(currentSession()); 0928 reply.waitForFinished(); // boo! 0929 0930 foreach (KSMClient *c, clients) { 0931 const int restartHint = c->restartStyleHint(); 0932 if (restartHint == SmRestartNever) { 0933 continue; 0934 } 0935 const QString program = c->program(); 0936 const QStringList restartCommand = c->restartCommand(); 0937 if (program.isEmpty() && restartCommand.isEmpty()) { 0938 continue; 0939 } 0940 if (state == ClosingSubSession && !clientsToSave.contains(c)) { 0941 continue; 0942 } 0943 0944 // 'program' might be (mostly) fullpath, or (sometimes) just the name. 0945 // 'name' is just the name. 0946 const QFileInfo info(program); 0947 const QString &name = info.fileName(); 0948 0949 if (excludeApps.contains(program.toLower()) || excludeApps.contains(name.toLower())) { 0950 continue; 0951 } 0952 0953 count++; 0954 const QString n = QString::number(count); 0955 cg.writeEntry(QStringLiteral("program") + n, program); 0956 cg.writeEntry(QStringLiteral("clientId") + n, c->clientId()); 0957 cg.writeEntry(QStringLiteral("restartCommand") + n, restartCommand); 0958 cg.writePathEntry(QStringLiteral("discardCommand") + n, c->discardCommand()); 0959 cg.writeEntry(QStringLiteral("restartStyleHint") + n, restartHint); 0960 cg.writeEntry(QStringLiteral("userId") + n, c->userId()); 0961 } 0962 cg.writeEntry("count", count); 0963 0964 KConfigGroup cg2(config, QStringLiteral("General")); 0965 0966 storeLegacySession(config.data()); 0967 config->sync(); 0968 } 0969 0970 QStringList KSMServer::sessionList() 0971 { 0972 QStringList sessions(QStringLiteral("default")); 0973 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0974 const QStringList groups = config->groupList(); 0975 for (auto it = groups.constBegin(); it != groups.constEnd(); ++it) { 0976 if ((*it).startsWith(SESSION_PREFIX)) { 0977 sessions << (*it).mid(SESSION_PREFIX.size()); 0978 } 0979 } 0980 return sessions; 0981 } 0982 0983 bool KSMServer::defaultSession() const 0984 { 0985 return sessionGroup.isEmpty(); 0986 } 0987 0988 void KSMServer::setRestoreSession(const QString &sessionName) 0989 { 0990 if (state != Idle) { 0991 return; 0992 } 0993 qCDebug(KSMSERVER) << "KSMServer::restoreSession " << sessionName; 0994 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0995 0996 sessionGroup = SESSION_PREFIX + sessionName; 0997 KConfigGroup configSessionGroup(config, sessionGroup); 0998 0999 int count = configSessionGroup.readEntry("count", 0); 1000 appsToStart = count; 1001 } 1002 1003 /*! 1004 Starts the default session. 1005 */ 1006 void KSMServer::startDefaultSession() 1007 { 1008 if (state != Idle) { 1009 return; 1010 } 1011 sessionGroup = QString(); 1012 } 1013 1014 void KSMServer::restoreSession() 1015 { 1016 Q_ASSERT(calledFromDBus()); 1017 if (defaultSession()) { 1018 state = KSMServer::Idle; 1019 return; 1020 } 1021 1022 setDelayedReply(true); 1023 m_restoreSessionCall = message(); 1024 1025 state = KSMServer::Restoring; 1026 1027 auto reply = m_kwinInterface->loadSession(currentSession()); 1028 auto watcher = new QDBusPendingCallWatcher(reply, this); 1029 connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, reply](QDBusPendingCallWatcher *watcher) { 1030 watcher->deleteLater(); 1031 if (reply.isError()) { 1032 qWarning() << "Failed to notify kwin of current session " << reply.error().message(); 1033 } 1034 restoreLegacySession(KSharedConfig::openConfig().data()); 1035 tryRestore(); 1036 }); 1037 } 1038 1039 void KSMServer::restoreSubSession(const QString &name) 1040 { 1041 sessionGroup = SUBSESSION_PREFIX + name; 1042 1043 KConfigGroup configSessionGroup(KSharedConfig::openConfig(), sessionGroup); 1044 int count = configSessionGroup.readEntry("count", 0); 1045 appsToStart = count; 1046 1047 state = RestoringSubSession; 1048 tryRestore(); 1049 } 1050 1051 void KSMServer::clientRegistered(const char * /* previousId */) 1052 { 1053 } 1054 1055 void KSMServer::tryRestore() 1056 { 1057 if (state != Restoring && state != RestoringSubSession) { 1058 return; 1059 } 1060 KConfigGroup config(KSharedConfig::openConfig(), sessionGroup); 1061 1062 struct DontStartEntry { 1063 QString clientId; 1064 QString appName; 1065 }; 1066 QList<DontStartEntry> dontStartEntries; 1067 QHash<QString, size_t> startCounter; 1068 1069 struct RestartEntry { 1070 QString clientId; 1071 QStringList restartCommand; 1072 int restartStyleHint; 1073 QString clientMachine; 1074 QString userId; 1075 }; 1076 QList<RestartEntry> entries; 1077 entries.reserve(appsToStart); 1078 for (int i = 0; i < appsToStart; i++) { 1079 const auto n = QString::number(i); 1080 const auto entry = entries.emplace_back(RestartEntry{ 1081 .clientId = config.readEntry(QLatin1String("clientId") + n, QString()), 1082 .restartCommand = config.readEntry(QLatin1String("restartCommand") + n, QStringList()), 1083 .restartStyleHint = config.readEntry(QLatin1String("restartStyleHint") + n, 0), 1084 .clientMachine = config.readEntry(QLatin1String("clientMachine") + n, QString()), 1085 .userId = config.readEntry(QLatin1String("userId") + n, QString()), 1086 }); 1087 1088 // Count how many times this command is going to be started. If it is too many we will create a DontStartEntry 1089 // and consequently turn *all* restorations no-op in the actual start loop. 1090 if (entry.restartCommand.isEmpty()) { 1091 continue; 1092 } 1093 const auto appName = entry.restartCommand.first(); 1094 if (!startCounter.contains(appName)) { 1095 startCounter[appName] = 0; 1096 } 1097 startCounter[appName]++; 1098 if (startCounter[appName] == perAppStartLimit()) { // only insert this entry once! when the limit is hit 1099 dontStartEntries.push_back(DontStartEntry{.clientId = entry.clientId, .appName = appName}); 1100 } 1101 } 1102 1103 for (const auto &entry : dontStartEntries) { 1104 qCWarning(KSMSERVER) << "Too many application starts issued for" << entry.appName << ". Not starting any more." 1105 << "Something may be broken with the application's session management."; 1106 KNotification::event(KNotification::Error, 1107 xi18nc("@label notification; %1 is an executable name", 1108 "Unable to restore <application>%1</application> because it is broken and has exhausted the system's session restoration " 1109 "resources. Please report this to the app's developers.", 1110 entry.appName)); 1111 } 1112 1113 for (const auto &entry : entries) { 1114 // We only discard the entries here because a violating app will get all entries disabled, not just the ones in 1115 // excess. So we need to loop all entries twice: once to establish the in-excess apps, and again to actually start 1116 // (or not). 1117 const bool dontStart = std::any_of(dontStartEntries.cbegin(), dontStartEntries.cend(), [&entry](const auto &dontStartEntry) { 1118 return dontStartEntry.clientId == entry.clientId; 1119 }); 1120 if (dontStart) { 1121 continue; 1122 } 1123 1124 const bool alreadyStarted = std::any_of(clients.cbegin(), clients.cend(), [&entry](const auto &client) { 1125 return QString::fromLocal8Bit(client->clientId()) == entry.clientId; 1126 }); 1127 if (alreadyStarted) { 1128 continue; 1129 } 1130 1131 if (entry.restartCommand.isEmpty() || entry.restartStyleHint == SmRestartNever) { 1132 continue; 1133 } 1134 startApplication(entry.restartCommand, entry.clientMachine, entry.userId); 1135 } 1136 1137 // all done 1138 if (state == Restoring) { 1139 Q_EMIT sessionRestored(); 1140 } else { // subsession 1141 Q_EMIT subSessionOpened(); 1142 } 1143 state = Idle; 1144 } 1145 1146 void KSMServer::startupDone() 1147 { 1148 state = Idle; 1149 } 1150 1151 void KSMServer::openSwitchUserDialog() 1152 { 1153 // this method exists only for compatibility. Users should ideally call this directly 1154 OrgKdeScreensaverInterface iface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus()); 1155 iface.SwitchUser(); 1156 }