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 }