File indexing completed on 2024-04-28 05:35:35

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