File indexing completed on 2024-04-28 16:54:31

0001 /*
0002     SPDX-FileCopyrightText: 2004 Oswald Buddenhagen <ossi@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "kdisplaymanager.h"
0008 
0009 #include <kuser.h>
0010 
0011 #include <KLocalizedString>
0012 
0013 #include <QCoreApplication>
0014 #include <QDBusArgument>
0015 #include <QDBusConnectionInterface>
0016 #include <QDBusInterface>
0017 #include <QDBusMetaType>
0018 #include <QDBusObjectPath>
0019 #include <QDBusReply>
0020 
0021 #include "config-X11.h"
0022 #if HAVE_X11
0023 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0024 #include <private/qtx11extras_p.h>
0025 #else
0026 #include <QX11Info>
0027 #endif
0028 
0029 #include <X11/Xauth.h>
0030 #include <X11/Xlib.h>
0031 #endif // HAVE_X11
0032 
0033 #include <errno.h>
0034 #include <fcntl.h>
0035 #include <stdio.h>
0036 #include <stdlib.h>
0037 #include <sys/socket.h>
0038 #include <sys/types.h>
0039 #include <sys/un.h>
0040 #include <unistd.h>
0041 
0042 #define _DBUS_PROPERTIES_IFACE "org.freedesktop.DBus.Properties"
0043 #define _DBUS_PROPERTIES_GET "Get"
0044 
0045 #define DBUS_PROPERTIES_IFACE QLatin1String(_DBUS_PROPERTIES_IFACE)
0046 #define DBUS_PROPERTIES_GET QLatin1String(_DBUS_PROPERTIES_GET)
0047 
0048 #define _SYSTEMD_SERVICE "org.freedesktop.login1"
0049 #define _SYSTEMD_BASE_PATH "/org/freedesktop/login1"
0050 #define _SYSTEMD_MANAGER_IFACE _SYSTEMD_SERVICE ".Manager"
0051 #define _SYSTEMD_SESSION_BASE_PATH _SYSTEMD_BASE_PATH "/session"
0052 #define _SYSTEMD_SEAT_IFACE _SYSTEMD_SERVICE ".Seat"
0053 #define _SYSTEMD_SEAT_BASE_PATH _SYSTEMD_BASE_PATH "/seat"
0054 #define _SYSTEMD_SESSION_IFACE _SYSTEMD_SERVICE ".Session"
0055 #define _SYSTEMD_USER_PROPERTY "User"
0056 #define _SYSTEMD_SEAT_PROPERTY "Seat"
0057 #define _SYSTEMD_SESSIONS_PROPERTY "Sessions"
0058 #define _SYSTEMD_SWITCH_PROPERTY "Activate"
0059 
0060 #define SYSTEMD_SERVICE QLatin1String(_SYSTEMD_SERVICE)
0061 #define SYSTEMD_BASE_PATH QLatin1String(_SYSTEMD_BASE_PATH)
0062 #define SYSTEMD_MANAGER_IFACE QLatin1String(_SYSTEMD_MANAGER_IFACE)
0063 #define SYSTEMD_SESSION_BASE_PATH QLatin1String(_SYSTEMD_SESSION_BASE_PATH)
0064 #define SYSTEMD_SEAT_IFACE QLatin1String(_SYSTEMD_SEAT_IFACE)
0065 #define SYSTEMD_SEAT_BASE_PATH QLatin1String(_SYSTEMD_SEAT_BASE_PATH)
0066 #define SYSTEMD_SESSION_IFACE QLatin1String(_SYSTEMD_SESSION_IFACE)
0067 #define SYSTEMD_USER_PROPERTY QLatin1String(_SYSTEMD_USER_PROPERTY)
0068 #define SYSTEMD_SEAT_PROPERTY QLatin1String(_SYSTEMD_SEAT_PROPERTY)
0069 #define SYSTEMD_SESSIONS_PROPERTY QLatin1String(_SYSTEMD_SESSIONS_PROPERTY)
0070 #define SYSTEMD_SWITCH_CALL QLatin1String(_SYSTEMD_SWITCH_PROPERTY)
0071 
0072 struct NamedDBusObjectPath {
0073     QString name;
0074     QDBusObjectPath path;
0075 };
0076 Q_DECLARE_METATYPE(NamedDBusObjectPath)
0077 Q_DECLARE_METATYPE(QList<NamedDBusObjectPath>)
0078 
0079 // Marshall the NamedDBusObjectPath data into a D-Bus argument
0080 QDBusArgument &operator<<(QDBusArgument &argument, const NamedDBusObjectPath &namedPath)
0081 {
0082     argument.beginStructure();
0083     argument << namedPath.name << namedPath.path;
0084     argument.endStructure();
0085     return argument;
0086 }
0087 
0088 // Retrieve the NamedDBusObjectPath data from the D-Bus argument
0089 const QDBusArgument &operator>>(const QDBusArgument &argument, NamedDBusObjectPath &namedPath)
0090 {
0091     argument.beginStructure();
0092     argument >> namedPath.name >> namedPath.path;
0093     argument.endStructure();
0094     return argument;
0095 }
0096 
0097 struct NumberedDBusObjectPath {
0098     uint num;
0099     QDBusObjectPath path;
0100 };
0101 Q_DECLARE_METATYPE(NumberedDBusObjectPath)
0102 
0103 // Marshall the NumberedDBusObjectPath data into a D-Bus argument
0104 QDBusArgument &operator<<(QDBusArgument &argument, const NumberedDBusObjectPath &numberedPath)
0105 {
0106     argument.beginStructure();
0107     argument << numberedPath.num << numberedPath.path;
0108     argument.endStructure();
0109     return argument;
0110 }
0111 
0112 // Retrieve the NumberedDBusObjectPath data from the D-Bus argument
0113 const QDBusArgument &operator>>(const QDBusArgument &argument, NumberedDBusObjectPath &numberedPath)
0114 {
0115     argument.beginStructure();
0116     argument >> numberedPath.num >> numberedPath.path;
0117     argument.endStructure();
0118     return argument;
0119 }
0120 
0121 class SystemdManager : public QDBusInterface
0122 {
0123 public:
0124     SystemdManager()
0125         : QDBusInterface(SYSTEMD_SERVICE, SYSTEMD_BASE_PATH, SYSTEMD_MANAGER_IFACE, QDBusConnection::systemBus())
0126     {
0127     }
0128 };
0129 
0130 class SystemdSeat : public QDBusInterface
0131 {
0132 public:
0133     SystemdSeat(const QDBusObjectPath &path)
0134         : QDBusInterface(SYSTEMD_SERVICE, path.path(), SYSTEMD_SEAT_IFACE, QDBusConnection::systemBus())
0135     {
0136     }
0137     /* HACK to be able to extract a(so) type from QDBus, property doesn't do the trick */
0138     QList<NamedDBusObjectPath> getSessions()
0139     {
0140         QDBusMessage message = QDBusMessage::createMethodCall(service(), path(), DBUS_PROPERTIES_IFACE, DBUS_PROPERTIES_GET);
0141         message << interface() << SYSTEMD_SESSIONS_PROPERTY;
0142         QDBusMessage reply = QDBusConnection::systemBus().call(message);
0143 
0144         QVariantList args = reply.arguments();
0145         if (!args.isEmpty()) {
0146             QList<NamedDBusObjectPath> namedPathList =
0147                 qdbus_cast<QList<NamedDBusObjectPath>>(args.at(0).value<QDBusVariant>().variant().value<QDBusArgument>());
0148             return namedPathList;
0149         }
0150         return QList<NamedDBusObjectPath>();
0151     }
0152 };
0153 
0154 class SystemdSession : public QDBusInterface
0155 {
0156 public:
0157     SystemdSession(const QDBusObjectPath &path)
0158         : QDBusInterface(SYSTEMD_SERVICE, path.path(), SYSTEMD_SESSION_IFACE, QDBusConnection::systemBus())
0159     {
0160     }
0161     /* HACK to be able to extract (so) type from QDBus, property doesn't do the trick */
0162     NamedDBusObjectPath getSeat()
0163     {
0164         QDBusMessage message = QDBusMessage::createMethodCall(service(), path(), DBUS_PROPERTIES_IFACE, DBUS_PROPERTIES_GET);
0165         message << interface() << SYSTEMD_SEAT_PROPERTY;
0166         QDBusMessage reply = QDBusConnection::systemBus().call(message);
0167 
0168         QVariantList args = reply.arguments();
0169         if (!args.isEmpty()) {
0170             NamedDBusObjectPath namedPath;
0171             args.at(0).value<QDBusVariant>().variant().value<QDBusArgument>() >> namedPath;
0172             return namedPath;
0173         }
0174         return NamedDBusObjectPath();
0175     }
0176     NumberedDBusObjectPath getUser()
0177     {
0178         QDBusMessage message = QDBusMessage::createMethodCall(service(), path(), DBUS_PROPERTIES_IFACE, DBUS_PROPERTIES_GET);
0179         message << interface() << SYSTEMD_USER_PROPERTY;
0180         QDBusMessage reply = QDBusConnection::systemBus().call(message);
0181 
0182         QVariantList args = reply.arguments();
0183         if (!args.isEmpty()) {
0184             NumberedDBusObjectPath numberedPath;
0185             args.at(0).value<QDBusVariant>().variant().value<QDBusArgument>() >> numberedPath;
0186             return numberedPath;
0187         }
0188         return NumberedDBusObjectPath();
0189     }
0190     void getSessionLocation(SessEnt &se)
0191     {
0192         se.tty = (property("Type").toString() != QLatin1String("x11"));
0193         se.display = property(se.tty ? "TTY" : "Display").toString();
0194         se.vt = property("VTNr").toInt();
0195     }
0196 };
0197 
0198 class CKManager : public QDBusInterface
0199 {
0200 public:
0201     CKManager()
0202         : QDBusInterface(QStringLiteral("org.freedesktop.ConsoleKit"),
0203                          QStringLiteral("/org/freedesktop/ConsoleKit/Manager"),
0204                          QStringLiteral("org.freedesktop.ConsoleKit.Manager"),
0205                          QDBusConnection::systemBus())
0206     {
0207     }
0208 };
0209 
0210 class CKSeat : public QDBusInterface
0211 {
0212 public:
0213     CKSeat(const QDBusObjectPath &path)
0214         : QDBusInterface(QStringLiteral("org.freedesktop.ConsoleKit"),
0215                          path.path(),
0216                          QStringLiteral("org.freedesktop.ConsoleKit.Seat"),
0217                          QDBusConnection::systemBus())
0218     {
0219     }
0220 };
0221 
0222 class CKSession : public QDBusInterface
0223 {
0224 public:
0225     CKSession(const QDBusObjectPath &path)
0226         : QDBusInterface(QStringLiteral("org.freedesktop.ConsoleKit"),
0227                          path.path(),
0228                          QStringLiteral("org.freedesktop.ConsoleKit.Session"),
0229                          QDBusConnection::systemBus())
0230     {
0231     }
0232     void getSessionLocation(SessEnt &se)
0233     {
0234         QString tty;
0235         QDBusReply<QString> r = call(QStringLiteral("GetX11Display"));
0236         if (r.isValid() && !r.value().isEmpty()) {
0237             QDBusReply<QString> r2 = call(QStringLiteral("GetX11DisplayDevice"));
0238             tty = r2.value();
0239             se.display = r.value();
0240             se.tty = false;
0241         } else {
0242             QDBusReply<QString> r2 = call(QStringLiteral("GetDisplayDevice"));
0243             tty = r2.value();
0244             se.display = tty;
0245             se.tty = true;
0246         }
0247         se.vt = QStringView(tty).mid(strlen("/dev/tty")).toInt();
0248     }
0249 };
0250 
0251 class GDMFactory : public QDBusInterface
0252 {
0253 public:
0254     GDMFactory()
0255         : QDBusInterface(QStringLiteral("org.gnome.DisplayManager"),
0256                          QStringLiteral("/org/gnome/DisplayManager/LocalDisplayFactory"),
0257                          QStringLiteral("org.gnome.DisplayManager.LocalDisplayFactory"),
0258                          QDBusConnection::systemBus())
0259     {
0260     }
0261 };
0262 
0263 class LightDMDBus : public QDBusInterface
0264 {
0265 public:
0266     LightDMDBus()
0267         : QDBusInterface(QStringLiteral("org.freedesktop.DisplayManager"),
0268                          qgetenv("XDG_SEAT_PATH"),
0269                          QStringLiteral("org.freedesktop.DisplayManager.Seat"),
0270                          QDBusConnection::systemBus())
0271     {
0272     }
0273 };
0274 
0275 static enum {
0276     Dunno,
0277     NoDM,
0278     NewKDM,
0279     OldKDM,
0280     NewGDM,
0281     OldGDM,
0282     LightDM,
0283 } DMType = Dunno;
0284 static const char *ctl, *dpy;
0285 
0286 class KDisplayManager::Private
0287 {
0288 public:
0289     Private()
0290         : fd(-1)
0291     {
0292     }
0293     ~Private()
0294     {
0295         if (fd >= 0)
0296             close(fd);
0297     }
0298 
0299     int fd;
0300 };
0301 
0302 KDisplayManager::KDisplayManager()
0303     : d(new Private)
0304 {
0305     const char *ptr;
0306     struct sockaddr_un sa;
0307 
0308     qDBusRegisterMetaType<NamedDBusObjectPath>();
0309     qDBusRegisterMetaType<QList<NamedDBusObjectPath>>();
0310     qDBusRegisterMetaType<NumberedDBusObjectPath>();
0311 
0312     if (DMType == Dunno) {
0313         dpy = ::getenv("DISPLAY");
0314         if (dpy && (ctl = ::getenv("DM_CONTROL")))
0315             DMType = NewKDM;
0316         else if (dpy && (ctl = ::getenv("XDM_MANAGED")) && ctl[0] == '/')
0317             DMType = OldKDM;
0318         else if (::getenv("XDG_SEAT_PATH") && LightDMDBus().isValid())
0319             DMType = LightDM;
0320         else if (::getenv("GDMSESSION"))
0321             DMType = GDMFactory().isValid() ? NewGDM : OldGDM;
0322         else
0323             DMType = NoDM;
0324     }
0325     switch (DMType) {
0326     default:
0327         return;
0328     case NewKDM:
0329     case OldGDM:
0330         if ((d->fd = ::socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
0331             return;
0332         sa.sun_family = AF_UNIX;
0333         if (DMType == OldGDM) {
0334             strcpy(sa.sun_path, "/var/run/gdm_socket");
0335             if (::connect(d->fd, (struct sockaddr *)&sa, sizeof(sa))) {
0336                 strcpy(sa.sun_path, "/tmp/.gdm_socket");
0337                 if (::connect(d->fd, (struct sockaddr *)&sa, sizeof(sa))) {
0338                     ::close(d->fd);
0339                     d->fd = -1;
0340                     break;
0341                 }
0342             }
0343             GDMAuthenticate();
0344         } else {
0345             if ((ptr = strchr(dpy, ':')))
0346                 ptr = strchr(ptr, '.');
0347             snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/dmctl-%.*s/socket", ctl, ptr ? int(ptr - dpy) : 512, dpy);
0348             if (::connect(d->fd, (struct sockaddr *)&sa, sizeof(sa))) {
0349                 ::close(d->fd);
0350                 d->fd = -1;
0351             }
0352         }
0353         break;
0354     case OldKDM: {
0355         QString tf(ctl);
0356         tf.truncate(tf.indexOf(','));
0357         d->fd = ::open(tf.toLatin1(), O_WRONLY);
0358     } break;
0359     }
0360 }
0361 
0362 KDisplayManager::~KDisplayManager()
0363 {
0364     delete d;
0365 }
0366 
0367 bool KDisplayManager::exec(const char *cmd)
0368 {
0369     QByteArray buf;
0370 
0371     return exec(cmd, buf);
0372 }
0373 
0374 /**
0375  * Execute a KDM/GDM remote control command.
0376  * @param cmd the command to execute. FIXME: undocumented yet.
0377  * @param buf the result buffer.
0378  * @return result:
0379  *  @li If true, the command was successfully executed.
0380  *   @p ret might contain additional results.
0381  *  @li If false and @p ret is empty, a communication error occurred
0382  *   (most probably KDM is not running).
0383  *  @li If false and @p ret is non-empty, it contains the error message
0384  *   from KDM.
0385  */
0386 bool KDisplayManager::exec(const char *cmd, QByteArray &buf)
0387 {
0388     bool ret = false;
0389     int tl;
0390     int len = 0;
0391 
0392     if (d->fd < 0)
0393         goto busted;
0394 
0395     tl = strlen(cmd);
0396     if (::write(d->fd, cmd, tl) != tl) {
0397     bust:
0398         ::close(d->fd);
0399         d->fd = -1;
0400     busted:
0401         buf.resize(0);
0402         return false;
0403     }
0404     if (DMType == OldKDM) {
0405         buf.resize(0);
0406         return true;
0407     }
0408     for (;;) {
0409         if (buf.size() < 128)
0410             buf.resize(128);
0411         else if (buf.size() < len * 2)
0412             buf.resize(len * 2);
0413         if ((tl = ::read(d->fd, buf.data() + len, buf.size() - len)) <= 0) {
0414             if (tl < 0 && errno == EINTR)
0415                 continue;
0416             goto bust;
0417         }
0418         len += tl;
0419         if (buf[len - 1] == '\n') {
0420             buf[len - 1] = 0;
0421             if (len > 2 && (buf[0] == 'o' || buf[0] == 'O') && (buf[1] == 'k' || buf[1] == 'K') && buf[2] <= ' ')
0422                 ret = true;
0423             break;
0424         }
0425     }
0426     return ret;
0427 }
0428 
0429 static bool getCurrentSeat(QDBusObjectPath *currentSession, QDBusObjectPath *currentSeat)
0430 {
0431     SystemdManager man;
0432     if (man.isValid()) {
0433         *currentSeat = QDBusObjectPath(_SYSTEMD_SEAT_BASE_PATH "/auto");
0434         SystemdSeat seat(*currentSeat);
0435         if (seat.property("Id").isValid()) { // query an arbitrary property to confirm the path is valid
0436             return true;
0437         }
0438 
0439         // auto is newer and may not exist on all platforms, fallback to GetSessionByPID if the above failed
0440 
0441         QDBusReply<QDBusObjectPath> r = man.call(QStringLiteral("GetSessionByPID"), (uint)QCoreApplication::applicationPid());
0442         if (r.isValid()) {
0443             SystemdSession sess(r.value());
0444             if (sess.isValid()) {
0445                 NamedDBusObjectPath namedPath = sess.getSeat();
0446                 *currentSeat = namedPath.path;
0447                 return true;
0448             }
0449         }
0450     } else {
0451         CKManager man;
0452         QDBusReply<QDBusObjectPath> r = man.call(QStringLiteral("GetCurrentSession"));
0453         if (r.isValid()) {
0454             CKSession sess(r.value());
0455             if (sess.isValid()) {
0456                 QDBusReply<QDBusObjectPath> r2 = sess.call(QStringLiteral("GetSeatId"));
0457                 if (r2.isValid()) {
0458                     if (currentSession)
0459                         *currentSession = r.value();
0460                     *currentSeat = r2.value();
0461                     return true;
0462                 }
0463             }
0464         }
0465     }
0466     return false;
0467 }
0468 
0469 static QList<QDBusObjectPath> getSessionsForSeat(const QDBusObjectPath &path)
0470 {
0471     if (path.path().startsWith(SYSTEMD_BASE_PATH)) { // systemd path incoming
0472         SystemdSeat seat(path);
0473         if (seat.isValid()) {
0474             const QList<NamedDBusObjectPath> r = seat.getSessions();
0475             QList<QDBusObjectPath> result;
0476             for (const NamedDBusObjectPath &namedPath : r)
0477                 result.append(namedPath.path);
0478             // This pretty much can't contain any other than local sessions as the seat is retrieved from the current session
0479             return result;
0480         }
0481     } else if (path.path().startsWith(QLatin1String("/org/freedesktop/ConsoleKit"))) {
0482         CKSeat seat(path);
0483         if (seat.isValid()) {
0484             QDBusReply<QList<QDBusObjectPath>> r = seat.call(QStringLiteral("GetSessions"));
0485             if (r.isValid()) {
0486                 // This will contain only local sessions:
0487                 // - this is only ever called when isSwitchable() is true => local seat
0488                 // - remote logins into the machine are assigned to other seats
0489                 return r.value();
0490             }
0491         }
0492     }
0493     return QList<QDBusObjectPath>();
0494 }
0495 
0496 #ifndef KDM_NO_SHUTDOWN
0497 bool KDisplayManager::canShutdown()
0498 {
0499     if (DMType == NewGDM || DMType == NoDM || DMType == LightDM) {
0500         QDBusReply<QString> canPowerOff = SystemdManager().call(QStringLiteral("CanPowerOff"));
0501         if (canPowerOff.isValid())
0502             return canPowerOff.value() != QLatin1String("no");
0503         QDBusReply<bool> canStop = CKManager().call(QStringLiteral("CanStop"));
0504         if (canStop.isValid())
0505             return canStop.value();
0506         return false;
0507     }
0508 
0509     if (DMType == OldKDM)
0510         return strstr(ctl, ",maysd") != nullptr;
0511 
0512     QByteArray re;
0513 
0514     if (DMType == OldGDM)
0515         return exec("QUERY_LOGOUT_ACTION\n", re) && re.indexOf("HALT") >= 0;
0516 
0517     return exec("caps\n", re) && re.indexOf("\tshutdown") >= 0;
0518 }
0519 
0520 void KDisplayManager::shutdown(KWorkSpace::ShutdownType shutdownType,
0521                                KWorkSpace::ShutdownMode shutdownMode, /* NOT Default */
0522                                const QString &bootOption)
0523 {
0524     if (shutdownType == KWorkSpace::ShutdownTypeNone || shutdownType == KWorkSpace::ShutdownTypeLogout)
0525         return;
0526 
0527     bool cap_ask;
0528     if (DMType == NewKDM) {
0529         QByteArray re;
0530         cap_ask = exec("caps\n", re) && re.indexOf("\tshutdown ask") >= 0;
0531     } else {
0532         if (!bootOption.isEmpty())
0533             return;
0534 
0535         if (DMType == NewGDM || DMType == NoDM || DMType == LightDM) {
0536             // systemd supports only 2 modes:
0537             // * interactive = true: brings up a PolicyKit prompt if other sessions are active
0538             // * interactive = false: rejects the shutdown if other sessions are active
0539             // There are no schedule or force modes.
0540             // We try to map our 4 shutdown modes in the sanest way.
0541             bool interactive = (shutdownMode == KWorkSpace::ShutdownModeInteractive || shutdownMode == KWorkSpace::ShutdownModeForceNow);
0542             QDBusReply<QString> check =
0543                 SystemdManager().call(QLatin1String(shutdownType == KWorkSpace::ShutdownTypeReboot ? "Reboot" : "PowerOff"), interactive);
0544             if (!check.isValid()) {
0545                 // FIXME: entirely ignoring shutdownMode
0546                 CKManager().call(QLatin1String(shutdownType == KWorkSpace::ShutdownTypeReboot ? "Restart" : "Stop"));
0547                 // if even CKManager call fails, there is nothing more to be done
0548             }
0549             return;
0550         }
0551 
0552         cap_ask = false;
0553     }
0554     if (!cap_ask && shutdownMode == KWorkSpace::ShutdownModeInteractive)
0555         shutdownMode = KWorkSpace::ShutdownModeForceNow;
0556 
0557     QByteArray cmd;
0558     if (DMType == OldGDM) {
0559         cmd.append(shutdownMode == KWorkSpace::ShutdownModeForceNow ? "SET_LOGOUT_ACTION " : "SET_SAFE_LOGOUT_ACTION ");
0560         cmd.append(shutdownType == KWorkSpace::ShutdownTypeReboot ? "REBOOT\n" : "HALT\n");
0561     } else {
0562         cmd.append("shutdown\t");
0563         cmd.append(shutdownType == KWorkSpace::ShutdownTypeReboot ? "reboot\t" : "halt\t");
0564         if (!bootOption.isEmpty())
0565             cmd.append("=").append(bootOption.toLocal8Bit()).append("\t");
0566         cmd.append(shutdownMode == KWorkSpace::ShutdownModeInteractive    ? "ask\n"
0567                        : shutdownMode == KWorkSpace::ShutdownModeForceNow ? "forcenow\n"
0568                        : shutdownMode == KWorkSpace::ShutdownModeTryNow   ? "trynow\n"
0569                                                                           : "schedule\n");
0570     }
0571     exec(cmd.data());
0572 }
0573 
0574 bool KDisplayManager::bootOptions(QStringList &opts, int &defopt, int &current)
0575 {
0576     if (DMType != NewKDM)
0577         return false;
0578 
0579     QByteArray re;
0580     if (!exec("listbootoptions\n", re))
0581         return false;
0582 
0583     opts = QString::fromLocal8Bit(re.data()).split('\t', Qt::SkipEmptyParts);
0584     if (opts.size() < 4)
0585         return false;
0586 
0587     bool ok;
0588     defopt = opts[2].toInt(&ok);
0589     if (!ok)
0590         return false;
0591     current = opts[3].toInt(&ok);
0592     if (!ok)
0593         return false;
0594 
0595     opts = opts[1].split(' ', Qt::SkipEmptyParts);
0596     for (QStringList::Iterator it = opts.begin(); it != opts.end(); ++it)
0597         (*it).replace(QLatin1String("\\s"), QLatin1String(" "));
0598 
0599     return true;
0600 }
0601 #endif // KDM_NO_SHUTDOWN
0602 
0603 bool KDisplayManager::isSwitchable()
0604 {
0605     if (DMType == NewGDM || DMType == LightDM) {
0606         QDBusObjectPath currentSeat;
0607         if (getCurrentSeat(nullptr, &currentSeat)) {
0608             SystemdSeat SDseat(currentSeat);
0609             if (SDseat.isValid()) {
0610                 QVariant prop = SDseat.property("CanMultiSession");
0611                 if (prop.isValid())
0612                     return prop.toBool();
0613                 else {
0614                     // Newer systemd versions (since 246) don't expose "CanMultiSession" anymore.
0615                     // It's hidden and always true.
0616                     // See https://github.com/systemd/systemd/commit/8f8cc84ba4612e74cd1e26898c6816e6e60fc4e9
0617                     // and https://github.com/systemd/systemd/commit/c2b178d3cacad52eadc30ecc349160bc02d32a9c
0618                     // So assume that it's supported if the property is invalid.
0619                     return true;
0620                 }
0621             }
0622             CKSeat CKseat(currentSeat);
0623             if (CKseat.isValid()) {
0624                 QDBusReply<bool> r = CKseat.call(QStringLiteral("CanActivateSessions"));
0625                 if (r.isValid())
0626                     return r.value();
0627             }
0628         }
0629         return false;
0630     }
0631 
0632     if (DMType == OldKDM)
0633         return dpy[0] == ':';
0634 
0635     if (DMType == OldGDM)
0636         return exec("QUERY_VT\n");
0637 
0638     QByteArray re;
0639 
0640     return exec("caps\n", re) && re.indexOf("\tlocal") >= 0;
0641 }
0642 
0643 int KDisplayManager::numReserve()
0644 {
0645     if (DMType == NewGDM || DMType == OldGDM || DMType == LightDM)
0646         return 1; /* Bleh */
0647 
0648     if (DMType == OldKDM)
0649         return strstr(ctl, ",rsvd") ? 1 : -1;
0650 
0651     QByteArray re;
0652     int p;
0653 
0654     if (!(exec("caps\n", re) && (p = re.indexOf("\treserve ")) >= 0))
0655         return -1;
0656     return atoi(re.data() + p + 9);
0657 }
0658 
0659 void KDisplayManager::startReserve()
0660 {
0661     if (DMType == NewGDM)
0662         GDMFactory().call(QStringLiteral("CreateTransientDisplay"));
0663     else if (DMType == OldGDM)
0664         exec("FLEXI_XSERVER\n");
0665     else if (DMType == LightDM) {
0666         LightDMDBus lightDM;
0667         lightDM.call(QStringLiteral("SwitchToGreeter"));
0668     } else
0669         exec("reserve\n");
0670 }
0671 
0672 bool KDisplayManager::localSessions(SessList &list)
0673 {
0674     if (DMType == OldKDM)
0675         return false;
0676 
0677     if (DMType == NewGDM || DMType == LightDM) {
0678         QDBusObjectPath currentSession, currentSeat;
0679         if (getCurrentSeat(&currentSession, &currentSeat)) {
0680             // we'll divide the code in two branches to reduce the overhead of calls to non-existent services
0681             // systemd part // preferred
0682             if (QDBusConnection::systemBus().interface()->isServiceRegistered(SYSTEMD_SERVICE)) {
0683                 const auto sessionsForSeat = getSessionsForSeat(currentSeat);
0684                 for (const QDBusObjectPath &sp : sessionsForSeat) {
0685                     SystemdSession lsess(sp);
0686                     if (lsess.isValid()) {
0687                         SessEnt se;
0688                         lsess.getSessionLocation(se);
0689                         if ((lsess.property("Class").toString() != QLatin1String("greeter"))
0690                             && (lsess.property("State").toString() == QLatin1String("online")
0691                                 || lsess.property("State").toString() == QLatin1String("active"))) {
0692                             NumberedDBusObjectPath numberedPath = lsess.getUser();
0693                             se.display = lsess.property("Display").toString();
0694                             se.vt = lsess.property("VTNr").toInt();
0695                             se.user = KUser(K_UID(numberedPath.num)).loginName();
0696                             /* TODO:
0697                              * regarding the session name in this, it IS possible to find it out - logind tracks the session leader PID
0698                              * the problem is finding out the name of the process, I could come only with reading /proc/PID/comm which
0699                              * doesn't seem exactly... right to me --mbriza
0700                              */
0701                             se.session = QStringLiteral("<unknown>");
0702 
0703                             se.self = lsess.property("Id").toString() == qgetenv("XDG_SESSION_ID");
0704                             se.tty = !lsess.property("TTY").toString().isEmpty();
0705                         }
0706                         list.append(se);
0707                     }
0708                 }
0709             }
0710             // ConsoleKit part
0711             else if (QDBusConnection::systemBus().interface()->isServiceRegistered(QStringLiteral("org.freedesktop.ConsoleKit"))) {
0712                 const auto sessionsForSeat = getSessionsForSeat(currentSeat);
0713                 for (const QDBusObjectPath &sp : sessionsForSeat) {
0714                     CKSession lsess(sp);
0715                     if (lsess.isValid()) {
0716                         SessEnt se;
0717                         lsess.getSessionLocation(se);
0718                         // "Warning: we haven't yet defined the allowed values for this property.
0719                         // It is probably best to avoid this until we do."
0720                         QDBusReply<QString> r = lsess.call(QStringLiteral("GetSessionType"));
0721                         if (r.value() != QLatin1String("LoginWindow")) {
0722                             QDBusReply<unsigned> r2 = lsess.call(QStringLiteral("GetUnixUser"));
0723                             se.user = KUser(K_UID(r2.value())).loginName();
0724                             se.session = QStringLiteral("<unknown>");
0725                         }
0726                         se.self = (sp == currentSession);
0727                         list.append(se);
0728                     }
0729                 }
0730             } else {
0731                 return false;
0732             }
0733             return true;
0734         }
0735         return false;
0736     }
0737 
0738     QByteArray re;
0739 
0740     if (DMType == OldGDM) {
0741         if (!exec("CONSOLE_SERVERS\n", re))
0742             return false;
0743         const QStringList sess = QString(re.data() + 3).split(QChar(';'), Qt::SkipEmptyParts);
0744         for (QStringList::ConstIterator it = sess.constBegin(); it != sess.constEnd(); ++it) {
0745             QStringList ts = (*it).split(QChar(','));
0746             SessEnt se;
0747             se.display = ts[0];
0748             se.user = ts[1];
0749             se.vt = ts[2].toInt();
0750             se.session = QStringLiteral("<unknown>");
0751             se.self = ts[0] == ::getenv("DISPLAY"); /* Bleh */
0752             se.tty = false;
0753             list.append(se);
0754         }
0755     } else {
0756         if (!exec("list\talllocal\n", re))
0757             return false;
0758         const QStringList sess = QString(re.data() + 3).split(QChar('\t'), Qt::SkipEmptyParts);
0759         for (QStringList::ConstIterator it = sess.constBegin(); it != sess.constEnd(); ++it) {
0760             QStringList ts = (*it).split(QChar(','));
0761             SessEnt se;
0762             se.display = ts[0];
0763             se.vt = QStringView(ts[1]).mid(2).toInt();
0764             se.user = ts[2];
0765             se.session = ts[3];
0766             se.self = (ts[4].indexOf('*') >= 0);
0767             se.tty = (ts[4].indexOf('t') >= 0);
0768             list.append(se);
0769         }
0770     }
0771     return true;
0772 }
0773 
0774 void KDisplayManager::sess2Str2(const SessEnt &se, QString &user, QString &loc)
0775 {
0776     if (se.tty) {
0777         user = i18nc("user: …", "%1: TTY login", se.user);
0778         loc = se.vt ? QStringLiteral("vt%1").arg(se.vt) : se.display;
0779     } else {
0780         // clang-format off
0781         user = se.user.isEmpty() ? se.session.isEmpty()
0782                                  ? i18nc("… location (TTY or X display)", "Unused") : se.session == QLatin1String("<remote>")
0783                                  ? i18n("X login on remote host")                     : i18nc("… host", "X login on %1", se.session)
0784                                                                                       : se.session == QLatin1String("<unknown>")
0785                                  ? se.user                                            : i18nc("user: session type", "%1: %2", se.user, se.session);
0786         // clang-format on
0787         loc = se.vt ? QStringLiteral("%1, vt%2").arg(se.display).arg(se.vt) : se.display;
0788     }
0789 }
0790 
0791 QString KDisplayManager::sess2Str(const SessEnt &se)
0792 {
0793     QString user, loc;
0794 
0795     sess2Str2(se, user, loc);
0796     return i18nc("session (location)", "%1 (%2)", user, loc);
0797 }
0798 
0799 bool KDisplayManager::switchVT(int vt)
0800 {
0801     if (DMType == NewGDM || DMType == LightDM) {
0802         QDBusObjectPath currentSeat;
0803         if (getCurrentSeat(nullptr, &currentSeat)) {
0804             // systemd part // preferred
0805             if (QDBusConnection::systemBus().interface()->isServiceRegistered(SYSTEMD_SERVICE)) {
0806                 const auto sessionsForSeat = getSessionsForSeat(currentSeat);
0807                 for (const QDBusObjectPath &sp : sessionsForSeat) {
0808                     SystemdSession lsess(sp);
0809                     if (lsess.isValid()) {
0810                         SessEnt se;
0811                         lsess.getSessionLocation(se);
0812                         if (se.vt == vt) {
0813                             lsess.call(SYSTEMD_SWITCH_CALL);
0814                             return true;
0815                         }
0816                     }
0817                 }
0818             }
0819             // ConsoleKit part
0820             else if (QDBusConnection::systemBus().interface()->isServiceRegistered(QStringLiteral("org.freedesktop.ConsoleKit"))) {
0821                 const auto sessionsForSeat = getSessionsForSeat(currentSeat);
0822                 for (const QDBusObjectPath &sp : sessionsForSeat) {
0823                     CKSession lsess(sp);
0824                     if (lsess.isValid()) {
0825                         SessEnt se;
0826                         lsess.getSessionLocation(se);
0827                         if (se.vt == vt) {
0828                             if (se.tty) // ConsoleKit simply ignores these
0829                                 return false;
0830                             lsess.call(QStringLiteral("Activate"));
0831                             return true;
0832                         }
0833                     }
0834                 }
0835             }
0836         }
0837         return false;
0838     }
0839 
0840     if (DMType == OldGDM)
0841         return exec(QStringLiteral("SET_VT %1\n").arg(vt).toLatin1());
0842 
0843     return exec(QStringLiteral("activate\tvt%1\n").arg(vt).toLatin1());
0844 }
0845 
0846 void KDisplayManager::lockSwitchVT(int vt)
0847 {
0848     // Lock first, otherwise the lock won't be able to kick in until the session is re-activated.
0849     QDBusInterface screensaver(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"));
0850     screensaver.call(QStringLiteral("Lock"));
0851 
0852     switchVT(vt);
0853 }
0854 
0855 void KDisplayManager::GDMAuthenticate()
0856 {
0857 #if HAVE_X11
0858     FILE *fp;
0859     const char *dpy, *dnum, *dne;
0860     int dnl;
0861     Xauth *xau;
0862 
0863     dpy = DisplayString(QX11Info::display());
0864     if (!dpy) {
0865         dpy = ::getenv("DISPLAY");
0866         if (!dpy)
0867             return;
0868     }
0869     dnum = strchr(dpy, ':') + 1;
0870     dne = strchr(dpy, '.');
0871     dnl = dne ? dne - dnum : strlen(dnum);
0872 
0873     /* XXX should do locking */
0874     if (!(fp = fopen(XauFileName(), "r")))
0875         return;
0876 
0877     while ((xau = XauReadAuth(fp))) {
0878         if (xau->family == FamilyLocal && xau->number_length == dnl && !memcmp(xau->number, dnum, dnl) && xau->data_length == 16 && xau->name_length == 18
0879             && !memcmp(xau->name, "MIT-MAGIC-COOKIE-1", 18)) {
0880             QString cmd(QStringLiteral("AUTH_LOCAL "));
0881             for (int i = 0; i < 16; i++)
0882                 cmd += QString::number((uchar)xau->data[i], 16).rightJustified(2, '0');
0883             cmd += '\n';
0884             if (exec(cmd.toLatin1())) {
0885                 XauDisposeAuth(xau);
0886                 break;
0887             }
0888         }
0889         XauDisposeAuth(xau);
0890     }
0891 
0892     fclose(fp);
0893 #endif // HAVE_X11
0894 }