File indexing completed on 2024-04-21 14:59:31

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999-2000 Waldo Bastian <bastian@kde.org>
0004     SPDX-FileCopyrightText: 1999 Mario Weilguni <mweilguni@sime.com>
0005     SPDX-FileCopyrightText: 2001 Lubos Lunak <l.lunak@kde.org>
0006     SPDX-FileCopyrightText: 2006-2011 Ralf Habacker <ralf.habacker@freenet.de>
0007     SPDX-FileCopyrightText: 2009 Patrick Spendrin <ps_ml@gmx.de>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-only
0010 */
0011 
0012 // Get this to compile... TODO: fix properly
0013 #undef QT_NO_CAST_FROM_ASCII
0014 
0015 #include <cerrno>
0016 #include <stdio.h>
0017 #include <stdlib.h>
0018 #include <string.h>
0019 
0020 #include <windows.h>
0021 #ifndef _WIN32_WCE
0022 #include <sddl.h>
0023 #endif
0024 #include <tlhelp32.h>
0025 #include <psapi.h>
0026 
0027 #include <QProcess>
0028 #include <QFileInfo>
0029 #include <QDebug>
0030 
0031 // Under wince interface is defined, so undef it otherwise it breaks it
0032 #undef interface
0033 #include <QDBusConnection>
0034 #include <QDBusInterface>
0035 #include <QDBusConnectionInterface>
0036 
0037 #include <kinit_version.h>
0038 
0039 #if defined (Q_CC_MSVC)
0040 typedef unsigned int pid_t;
0041 #else
0042 #include <sys/types.h>
0043 #endif
0044 
0045 //#define ENABLE_SUICIDE
0046 //#define ENABLE_EXIT
0047 
0048 #define KDED_EXENAME "kded5"
0049 
0050 // print verbose messages
0051 int verbose = 0;
0052 
0053 /// holds process list for suicide mode
0054 QList<QProcess *> startedProcesses;
0055 
0056 /* --------------------------------------------------------------------
0057   sid helper - will be migrated later to a class named Sid, which could
0058   be used as base class for platform independent K_UID and K_GID types
0059   - would this be possible before KDE 5 ?
0060   --------------------------------------------------------------------- */
0061 
0062 /**
0063  copy sid
0064  @param from sif to copy from
0065  @return copied sid, need to be free'd with free
0066  @note null sid's are handled too
0067 */
0068 PSID copySid(PSID from)
0069 {
0070     if (!from) {
0071         return 0;
0072     }
0073     int sidLength = GetLengthSid(from);
0074     PSID to = (PSID) malloc(sidLength);
0075     CopySid(sidLength, to, from);
0076     return to;
0077 }
0078 
0079 /**
0080  copy sid
0081  @param from sif to copy from
0082  @return copied sid, need to be free'd with free
0083  @note null sid's are handled too
0084 */
0085 void freeSid(PSID sid)
0086 {
0087     if (sid) {
0088         free(sid);
0089     }
0090 }
0091 
0092 /**
0093  copy sid
0094  @param from sif to copy from
0095  @return copied sid, need to be free'd with free
0096  @note null sid's are handled too
0097 */
0098 QString toString(PSID sid)
0099 {
0100     LPWSTR s;
0101     if (!ConvertSidToStringSid(sid, &s)) {
0102         return QString();
0103     }
0104 
0105     QString result = QString::fromUtf16(reinterpret_cast<ushort *>(s));
0106     LocalFree(s);
0107     return result;
0108 }
0109 
0110 /* --------------------------------------------------------------------
0111   process helper
0112   --------------------------------------------------------------------- */
0113 
0114 /**
0115  return process handle
0116  @param pid process id
0117  @return process handle
0118  */
0119 static HANDLE getProcessHandle(int processID)
0120 {
0121     return OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION |
0122                        PROCESS_VM_READ | PROCESS_TERMINATE,
0123                        false, processID);
0124 }
0125 
0126 /**
0127  return absolute path of process
0128  @param pid process id
0129  @return process name
0130  */
0131 static QString getProcessName(DWORD pid)
0132 {
0133     HANDLE hModuleSnap = INVALID_HANDLE_VALUE;
0134     MODULEENTRY32 me32;
0135 
0136     hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
0137     if (hModuleSnap == INVALID_HANDLE_VALUE) {
0138         return QString();
0139     }
0140 
0141     me32.dwSize = sizeof(MODULEENTRY32);
0142 
0143     if (!Module32First(hModuleSnap, &me32)) {
0144         CloseHandle(hModuleSnap);             // clean the snapshot object
0145         return QString();
0146     }
0147     QString name = QString::fromWCharArray(me32.szExePath);
0148     CloseHandle(hModuleSnap);
0149     return name;
0150 }
0151 
0152 /**
0153  return sid of specific process
0154  @param hProcess handle to process
0155  @return sid pointer to PSID structure, must be freed with LocalAlloc
0156 */
0157 static PSID getProcessOwner(HANDLE hProcess)
0158 {
0159 #ifndef _WIN32_WCE
0160     HANDLE hToken = NULL;
0161     PSID sid;
0162 
0163     OpenProcessToken(hProcess, TOKEN_READ, &hToken);
0164     if (hToken) {
0165         DWORD size;
0166         PTOKEN_USER userStruct;
0167 
0168         // check how much space is needed
0169         GetTokenInformation(hToken, TokenUser, NULL, 0, &size);
0170         if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) {
0171             userStruct = reinterpret_cast<PTOKEN_USER>(new BYTE[size]);
0172             GetTokenInformation(hToken, TokenUser, userStruct, size, &size);
0173 
0174             sid = copySid(userStruct->User.Sid);
0175             CloseHandle(hToken);
0176             delete [] userStruct;
0177             return sid;
0178         }
0179     }
0180 #endif
0181     return 0;
0182 }
0183 
0184 /**
0185  return sid of current process owner
0186 */
0187 static PSID getCurrentProcessOwner()
0188 {
0189     return getProcessOwner(GetCurrentProcess());
0190 }
0191 
0192 /**
0193  holds single process
0194  */
0195 class ProcessListEntry
0196 {
0197 public:
0198     ProcessListEntry(HANDLE _handle, QString _path, int _pid, PSID _owner = 0)
0199     {
0200         QFileInfo p(_path);
0201         path = p.absolutePath();
0202         name = p.baseName();
0203         handle = _handle;
0204         pid = _pid;
0205         owner = copySid(_owner);
0206     }
0207 
0208     ~ProcessListEntry()
0209     {
0210         freeSid(owner);
0211         CloseHandle(handle);
0212     }
0213 
0214     QString name;
0215     QString path;
0216     int pid;
0217     HANDLE handle;
0218     PSID owner;
0219     friend QDebug operator <<(QDebug out, const ProcessListEntry &c);
0220 };
0221 
0222 QDebug operator <<(QDebug out, const ProcessListEntry &c)
0223 {
0224     out << "(ProcessListEntry"
0225         << "name" << c.name
0226         << "path" << c.path
0227         << "pid" << c.pid
0228         << "handle" << c.handle
0229         << "sid" << toString(c.owner)
0230         << ")";
0231     return out;
0232 }
0233 
0234 /**
0235  holds system process list snapshot
0236 
0237  Could be used as a public platform independent class or namespace in kdecore
0238  for dealing with system processes, named perhaps KSystemProcessSnapshot or similar.
0239  If implemented at Qt level it will be named QSystemProcessSnapshot or similar
0240 */
0241 class ProcessList
0242 {
0243 public:
0244     /**
0245     collect process
0246     @param userSid  sid of user for which processes should be collected or 0 for all processes
0247     */
0248     ProcessList(PSID userSid = 0);
0249 
0250     ~ProcessList();
0251 
0252     /**
0253     find process in list
0254     @param name process name (with or without extension)
0255     @return instance of process entry
0256     */
0257     ProcessListEntry *find(const QString &name);
0258 
0259     /**
0260     killprocess from list
0261     @param name process name (with or without extension)
0262     @return ...
0263     */
0264     bool terminateProcess(const QString &name);
0265 
0266     /**
0267     return all processes
0268     @return list with processes
0269     */
0270     QList<ProcessListEntry *> &list()
0271     {
0272         return m_processes;
0273     }
0274 
0275 private:
0276     void init();
0277     QList<ProcessListEntry *> m_processes;
0278     PSID m_userId;
0279 };
0280 
0281 ProcessList::ProcessList(PSID userSid)
0282 {
0283     m_userId = userSid;
0284     init();
0285 }
0286 
0287 ProcessList::~ProcessList()
0288 {
0289     qDeleteAll(m_processes);
0290 }
0291 
0292 void ProcessList::init()
0293 {
0294     HANDLE h;
0295     PROCESSENTRY32 pe32;
0296 
0297     h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
0298     if (h == INVALID_HANDLE_VALUE) {
0299         return;
0300     }
0301     pe32.dwSize = sizeof(PROCESSENTRY32);
0302     if (!Process32First(h, &pe32)) {
0303         return;
0304     }
0305 
0306     do {
0307         HANDLE hProcess = getProcessHandle(pe32.th32ProcessID);
0308         if (!hProcess) {
0309             continue;
0310         }
0311         QString name = getProcessName(pe32.th32ProcessID);
0312 #ifndef _WIN32_WCE
0313         PSID sid = getProcessOwner(hProcess);
0314         if (!sid || m_userId && !EqualSid(m_userId, sid)) {
0315             freeSid(sid);
0316             continue;
0317         }
0318 #else
0319         PSID sid = 0;
0320 #endif
0321         m_processes << new ProcessListEntry(hProcess, name, pe32.th32ProcessID, sid);
0322     } while (Process32Next(h, &pe32));
0323 #ifndef _WIN32_WCE
0324     CloseHandle(h);
0325 #else
0326     CloseToolhelp32Snapshot(h);
0327 #endif
0328 }
0329 
0330 ProcessListEntry *ProcessList::find(const QString &name)
0331 {
0332     for (ProcessListEntry *ple : std::as_const(m_processes)) {
0333         if (ple->pid < 0) {
0334             qDebug() << "negative pid!";
0335             continue;
0336         }
0337 
0338         if (ple->name != name && ple->name != name + ".exe") {
0339             continue;
0340         }
0341 
0342         if (!ple->path.isEmpty() && !ple->path.toLower().startsWith(QString(QStringLiteral(CMAKE_INSTALL_PREFIX)).toLower())) {
0343             // process is outside of installation directory
0344             qDebug() << "path of the process" << name << "seems to be outside of the installPath:" << ple->path << QStringLiteral(CMAKE_INSTALL_PREFIX);
0345             continue;
0346         }
0347         return ple;
0348     }
0349     return NULL;
0350 }
0351 
0352 bool ProcessList::terminateProcess(const QString &name)
0353 {
0354     qDebug() << "going to terminate process" << name;
0355     ProcessListEntry *p = find(name);
0356     if (!p) {
0357         qDebug() << "could not find ProcessListEntry for process name" << name;
0358         return false;
0359     }
0360 
0361     bool ret = TerminateProcess(p->handle, 0);
0362     if (ret) {
0363         int i = m_processes.indexOf(p);
0364         if (i != -1) {
0365             m_processes.removeAt(i);
0366         }
0367         delete p;
0368         return true;
0369     } else {
0370         return false;
0371     }
0372 }
0373 
0374 // internal launch function
0375 int launch(const QString &cmd)
0376 {
0377     QProcess *proc = new QProcess();
0378     proc->start(cmd, QStringList());
0379     proc->waitForStarted();
0380     startedProcesses << proc;
0381     int pid = proc->processId();
0382     if (verbose) {
0383         fprintf(stderr, "%s", proc->readAllStandardError().constData());
0384         fprintf(stderr, "%s", proc->readAllStandardOutput().constData());
0385     }
0386     if (pid) {
0387         if (verbose) {
0388             fprintf(stderr, "kdeinit5: Launched %s, pid = %ld\n", qPrintable(cmd), (long) pid);
0389         }
0390     } else {
0391         if (verbose) {
0392             fprintf(stderr, "kdeinit5: could not launch %s, exiting\n", qPrintable(cmd));
0393         }
0394     }
0395     return pid;
0396 }
0397 
0398 /// check dbus registration
0399 bool checkIfRegisteredInDBus(const QString &name, int _timeout = 10)
0400 {
0401     int timeout = _timeout * 5;
0402     while (timeout) {
0403         if (QDBusConnection::sessionBus().interface()->isServiceRegistered(name)) {
0404             break;
0405         }
0406         Sleep(200);
0407         timeout--;
0408     }
0409     if (!timeout) {
0410         if (verbose) {
0411             fprintf(stderr, "not registered %s in dbus after %d secs\n", qPrintable(name), _timeout);
0412         }
0413         return false;
0414     }
0415     if (verbose) {
0416         fprintf(stderr, "%s is registered in dbus\n", qPrintable(name));
0417     }
0418     return true;
0419 }
0420 
0421 void listAllRunningKDEProcesses(ProcessList &processList)
0422 {
0423     QString installPrefix = QStringLiteral(CMAKE_INSTALL_PREFIX);
0424 
0425     const auto list = processList.list();
0426     for (const ProcessListEntry *ple : list) {
0427         if (!ple->path.isEmpty() && ple->path.toLower().startsWith(installPrefix.toLower())) {
0428             fprintf(stderr, "path: %s name: %s pid: %u\n", ple->path.toLatin1().data(), ple->name.toLatin1().data(), ple->pid);
0429         }
0430     }
0431 }
0432 
0433 void terminateAllRunningKDEProcesses(ProcessList &processList)
0434 {
0435     QString installPrefix = QStringLiteral(CMAKE_INSTALL_PREFIX);
0436 
0437     const auto list = processList.list();
0438     for (const ProcessListEntry *ple : list) {
0439         if (!ple->path.isEmpty() && ple->path.toLower().startsWith(installPrefix.toLower())) {
0440             if (verbose) {
0441                 fprintf(stderr, "terminating path: %s name: %s pid: %u\n", ple->path.toLatin1().data(), ple->name.toLatin1().data(), ple->pid);
0442             }
0443             processList.terminateProcess(ple->name);
0444         }
0445     }
0446 }
0447 
0448 void listAllNamedAppsInDBus()
0449 {
0450     QDBusConnection connection = QDBusConnection::sessionBus();
0451     QDBusConnectionInterface *bus = connection.interface();
0452     const QStringList services = bus->registeredServiceNames();
0453     for (const QString &service : services) {
0454         if (service.startsWith(QLatin1String("org.freedesktop.DBus")) || service.startsWith(QLatin1Char(':'))) {
0455             continue;
0456         }
0457         fprintf(stderr, "%s \n", service.toLatin1().data());
0458     }
0459 }
0460 
0461 void quitApplicationsOverDBus()
0462 {
0463     QDBusConnection connection = QDBusConnection::sessionBus();
0464     QDBusConnectionInterface *bus = connection.interface();
0465     const QStringList services = bus->registeredServiceNames();
0466     for (const QString &service : services) {
0467         if (service.startsWith(QLatin1String("org.freedesktop.DBus")) || service.startsWith(QLatin1Char(':'))) {
0468             continue;
0469         }
0470         QDBusInterface *iface = new QDBusInterface(service,
0471                 QLatin1String("/MainApplication"),
0472                 QLatin1String("org.kde.KApplication"),
0473                 connection);
0474         if (!iface->isValid()) {
0475             if (verbose) {
0476                 fprintf(stderr, "invalid interface of service %s\n", service.toLatin1().data());
0477             }
0478             continue;
0479         }
0480         iface->call("quit");
0481         if (iface->lastError().isValid()) {
0482             if (verbose) {
0483                 fprintf(stderr, "killing %s with result\n", iface->lastError().message().toLatin1().data());
0484             }
0485         }
0486         delete iface;
0487     }
0488 }
0489 
0490 int main(int argc, char **argv, char **envp)
0491 {
0492     pid_t pid = 0;
0493     bool launch_dbus = true;
0494     bool launch_klauncher = true;
0495     bool launch_kded = true;
0496     bool suicide = false;
0497     bool listProcesses = false;
0498     bool killProcesses = false;
0499     bool listAppsInDBus = false;
0500     bool quitAppsOverDBus = false;
0501     bool shutdown = false;
0502 
0503     /** Save arguments first... **/
0504     char **safe_argv = (char **) malloc(sizeof(char *) * argc);
0505     for (int i = 0; i < argc; i++) {
0506         safe_argv[i] = strcpy((char *)malloc(strlen(argv[i]) + 1), argv[i]);
0507         if (strcmp(safe_argv[i], "--no-dbus") == 0) {
0508             launch_dbus = false;
0509         }
0510         if (strcmp(safe_argv[i], "--no-klauncher") == 0) {
0511             launch_klauncher = false;
0512         }
0513         if (strcmp(safe_argv[i], "--no-kded") == 0) {
0514             launch_kded = false;
0515         }
0516         if (strcmp(safe_argv[i], "--suicide") == 0) {
0517             suicide = true;
0518         }
0519 #ifdef ENABLE_EXIT
0520         if (strcmp(safe_argv[i], "--exit") == 0) {
0521             keep_running = 0;
0522         }
0523 #endif
0524         if (strcmp(safe_argv[i], "--verbose") == 0) {
0525             verbose = 1;
0526         }
0527         if (strcmp(safe_argv[i], "--version") == 0) {
0528             printf("Qt: %s\n", qVersion());
0529             printf("KDE: %s\n", KINIT_VERSION_STRING);
0530             exit(0);
0531         }
0532         if (strcmp(safe_argv[i], "--help") == 0) {
0533             printf("Usage: kdeinit5 [options]\n");
0534 #ifdef ENABLE_EXIT
0535             printf("   --exit                     Terminate when kded has run\n");
0536 #endif
0537             printf("   --help                     this help page\n");
0538             printf("   --list                     list kde processes\n");
0539             printf("   --list-dbus-apps           list all applications registered in dbus\n");
0540             printf("   --quit-over-dbus           quit all application registered in dbus\n");
0541             printf("   --no-dbus                  do not start dbus-daemon\n");
0542             printf("   --no-klauncher             do not start klauncher\n");
0543             printf("   --no-kded                  do not start kded\n");
0544             printf("   --shutdown                 safe shutdown of all running kde processes\n");
0545             printf("                              first over dbus, then using hard kill\n");
0546 #ifdef ENABLE_SUICIDE
0547             printf("    --suicide                 terminate when no KDE applications are left running\n");
0548 #endif
0549             printf("   --terminate                hard kill of *all* running kde processes\n");
0550             printf("   --verbose                  print verbose messages\n");
0551             printf("   --version                  Show version information\n");
0552             exit(0);
0553         }
0554         if (strcmp(safe_argv[i], "--list") == 0) {
0555             listProcesses = true;
0556         }
0557         if (strcmp(safe_argv[i], "--shutdown") == 0) {
0558             shutdown = true;
0559         }
0560         if (strcmp(safe_argv[i], "--terminate") == 0 || strcmp(safe_argv[i], "--kill") == 0) {
0561             killProcesses = true;
0562         }
0563         if (strcmp(safe_argv[i], "--list-dbus-apps") == 0) {
0564             listAppsInDBus = true;
0565         }
0566         if (strcmp(safe_argv[i], "--quit-over-dbus") == 0) {
0567             quitAppsOverDBus = true;
0568         }
0569     }
0570 
0571     PSID currentSid = getCurrentProcessOwner();
0572     if (verbose) {
0573         fprintf(stderr, "current user sid: %s\n", qPrintable(toString(currentSid)));
0574     }
0575     ProcessList processList(currentSid);
0576     freeSid(currentSid);
0577 
0578     if (listProcesses) {
0579         listAllRunningKDEProcesses(processList);
0580         return 0;
0581     } else if (killProcesses) {
0582         terminateAllRunningKDEProcesses(processList);
0583         return 0;
0584     } else if (listAppsInDBus) {
0585         listAllNamedAppsInDBus();
0586         return 0;
0587     } else if (quitAppsOverDBus) {
0588         quitApplicationsOverDBus();
0589         return 0;
0590     } else if (shutdown) {
0591         quitApplicationsOverDBus();
0592         Sleep(2000);
0593         terminateAllRunningKDEProcesses(processList);
0594     }
0595 
0596 #ifdef _DEBUG
0597     // first try to launch dbus-daemond in debug mode
0598     if (launch_dbus && processList.find("dbus-daemond")) {
0599         launch_dbus = false;
0600     }
0601     if (launch_dbus) {
0602         pid = launch("dbus-launchd.exe");
0603         if (!pid) {
0604             pid = launch("dbus-launchd.bat");
0605         }
0606         launch_dbus = (pid == 0);
0607     }
0608 #endif
0609     if (launch_dbus && !processList.find("dbus-daemon")) {
0610         if (!pid) {
0611             pid = launch("dbus-launch.exe");
0612         }
0613         if (!pid) {
0614             pid = launch("dbus-launch.bat");
0615         }
0616         if (!pid) {
0617             exit(1);
0618         }
0619     }
0620 
0621     if (launch_klauncher && !processList.find("klauncher")) {
0622         pid = launch("klauncher");
0623         if (!pid || !checkIfRegisteredInDBus("org.kde.klauncher5", 10)) {
0624             exit(1);
0625         }
0626     }
0627 
0628     if (launch_kded && !processList.find(KDED_EXENAME)) {
0629         pid = launch(KDED_EXENAME);
0630         if (!pid || !checkIfRegisteredInDBus("org.kde.kded5", 10)) {
0631             exit(1);
0632         }
0633     }
0634 
0635     for (int i = 1; i < argc; i++) {
0636         if (safe_argv[i][0] == '+') {
0637             pid = launch(safe_argv[i] + 1);
0638         } else if (safe_argv[i][0] == '-') {
0639             // Ignore
0640         } else {
0641             pid = launch(safe_argv[i]);
0642         }
0643     }
0644 
0645     /** Free arguments **/
0646     for (int i = 0; i < argc; i++) {
0647         free(safe_argv[i]);
0648     }
0649     free(safe_argv);
0650 
0651     /** wait for termination of all (core) processes */
0652 #ifdef ENABLE_SUICIDE
0653     if (suicide) {
0654         QProcess *proc;
0655         int can_exit = 1;
0656         do {
0657             for (proc : std::as_const(startedProcesses)) {
0658                 if (proc->state() != QProcess::NotRunning) {
0659                     can_exit = 0;
0660                 }
0661             }
0662             if (!can_exit) {
0663                 Sleep(2000);
0664             }
0665         } while (!can_exit);
0666         return 0;
0667     }
0668 #endif
0669     return 0;
0670 }