File indexing completed on 2025-01-05 04:35:37

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0003     SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
0004 */
0005 
0006 #include "usermanager.h"
0007 
0008 #include <KUser>
0009 #include <QFile>
0010 #include <QRegularExpression>
0011 #include <QProcess>
0012 
0013 #include <KAuth/Action>
0014 #include <KAuth/ExecuteJob>
0015 #include <KLocalizedString>
0016 
0017 const auto everyoneUserName = QStringLiteral("Everyone");
0018 
0019 static QStringList getUsersList()
0020 {
0021     uid_t defminuid = 1000;
0022     uid_t defmaxuid = 65000;
0023 
0024     QFile loginDefs(QStringLiteral("/etc/login.defs"));
0025     if (loginDefs.open(QIODevice::ReadOnly | QIODevice::Text)) {
0026         while (!loginDefs.atEnd()) {
0027             const QString line = QString::fromLatin1(loginDefs.readLine());
0028             {
0029                 const QRegularExpression expression(QStringLiteral("^\\s*UID_MIN\\s+(?<UID_MIN>\\d+)"));
0030                 const auto match = expression.match(line);
0031                 if (match.hasMatch()) {
0032                     defminuid = match.captured(u"UID_MIN").toUInt();
0033                 }
0034             }
0035             {
0036                 const QRegularExpression expression(QStringLiteral("^\\s*UID_MAX\\s+(?<UID_MAX>\\d+)"));
0037                 const auto match = expression.match(line);
0038                 if (match.hasMatch()) {
0039                     defmaxuid = match.captured(u"UID_MAX").toUInt();
0040                 }
0041             }
0042         }
0043     }
0044 
0045     QStringList userList;
0046     userList.append(everyoneUserName);
0047     const QStringList userNames = KUser::allUserNames();
0048     for (const QString &username : userNames) {
0049         if (username == QLatin1String("nobody")) {
0050             continue;
0051         }
0052         KUser user(username);
0053         const uid_t nativeId = user.userId().nativeId();
0054         if (nativeId >= defminuid && nativeId <= defmaxuid) {
0055             userList << username;
0056         }
0057     }
0058 
0059     return userList;
0060 }
0061 
0062 User::User(const QString &name, UserManager *parent)
0063     : QObject(parent)
0064     , m_name(name)
0065 {
0066 }
0067 
0068 QString User::name() const
0069 {
0070     return m_name;
0071 }
0072 
0073 bool User::inSamba() const
0074 {
0075     return m_inSamba;
0076 }
0077 
0078 void User::resolve()
0079 {
0080     if (!qobject_cast<UserManager *>(parent())->canManageSamba() || m_name == everyoneUserName) {
0081         // Assume the user is cool. If the auth backend isn't tbdsam it's likely delegated to a domain controller
0082         // which in turn suggests that the local system is delegating all users to the controller. If not we
0083         // can't do anything about it anyway the options are disable everything or pretend it'll work.
0084         m_inSamba = true;
0085         Q_EMIT resolved();
0086         return;
0087     }
0088 
0089     auto action = KAuth::Action(QStringLiteral("org.kde.filesharing.samba.isuserknown"));
0090     action.setHelperId(QStringLiteral("org.kde.filesharing.samba"));
0091     action.addArgument(QStringLiteral("username"), m_name);
0092     // Detail message won't really show up unless the system admin forces authentication for this. Which would
0093     // be very awkward
0094     action.setDetailsV2({{
0095         KAuth::Action::AuthDetail::DetailMessage,
0096         i18nc("@label kauth action description %1 is a username", "Checking if Samba user '%1' exists", m_name) }
0097     });
0098     KAuth::ExecuteJob *job = action.execute();
0099     connect(job, &KAuth::ExecuteJob::result, this, [this, job] {
0100         job->deleteLater();
0101         m_inSamba = job->data().value(QStringLiteral("exists"), false).toBool();
0102         Q_EMIT inSambaChanged();
0103         Q_EMIT resolved();
0104     });
0105     job->start();
0106 }
0107 
0108 void User::addToSamba(const QString &password)
0109 {
0110     Q_ASSERT(qobject_cast<UserManager *>(parent())->canManageSamba());
0111     // We currently only support adding a plasma user for the current user, not others.
0112     Q_ASSERT(qobject_cast<UserManager *>(parent())->currentUser() == this);
0113 
0114     auto action = KAuth::Action(QStringLiteral("org.kde.filesharing.samba.createuser"));
0115     action.setHelperId(QStringLiteral("org.kde.filesharing.samba"));
0116     action.addArgument(QStringLiteral("password"), password);
0117     action.setDetailsV2({{
0118         KAuth::Action::AuthDetail::DetailMessage,
0119         i18nc("@label kauth action description %1 is a username", "Creating new Samba user '%1'", m_name) }
0120     });
0121     KAuth::ExecuteJob *job = action.execute();
0122     connect(job, &KAuth::ExecuteJob::result, this, [this, job] {
0123         job->deleteLater();
0124         m_inSamba = job->data().value(QStringLiteral("created"), false).toBool();
0125         if (!m_inSamba) {
0126             Q_EMIT addToSambaError(job->data().value(QStringLiteral("stderr"), QString()).toString());
0127         }
0128         Q_EMIT inSambaChanged();
0129     });
0130     job->start();
0131 }
0132 
0133 bool UserManager::canManageSamba() const
0134 {
0135     return m_canManageSamba;
0136 }
0137 
0138 void UserManager::load()
0139 {
0140     auto proc = new QProcess(this);
0141     proc->setProgram(QStringLiteral("testparm"));
0142     proc->setArguments({
0143         QStringLiteral("--debuglevel=0"),
0144         QStringLiteral("--suppress-prompt"),
0145         QStringLiteral("--verbose"),
0146         QStringLiteral("--parameter-name"),
0147         QStringLiteral("passdb backend")
0148     });
0149     connect(proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this, proc] {
0150         proc->deleteLater();
0151         const QByteArray output = proc->readAllStandardOutput().simplified();
0152 
0153         if (output == QByteArrayLiteral("tdbsam")) {
0154             m_canManageSamba = true;
0155         }
0156 
0157         const QString currentUserName = KUser().loginName();
0158         const QStringList nameList = getUsersList();
0159         for (const auto &name : nameList) {
0160             ++m_waitingForResolution;
0161             auto user = new User(name, this);
0162             connect(user, &User::resolved, this, [this] {
0163                 if (--m_waitingForResolution <= 0) {
0164                     Q_EMIT loaded();
0165                 }
0166             }, Qt::QueuedConnection /* queue to ensure even shortcut resolution goes through the loop */);
0167             m_users.append(user);
0168             if (user->name() == currentUserName) {
0169                 m_currentUser = user;
0170             }
0171             user->resolve();
0172         }
0173     });
0174     proc->start();
0175 }
0176 
0177 QList<User *> UserManager::users() const
0178 {
0179     return m_users;
0180 }
0181 
0182 Q_INVOKABLE User *UserManager::currentUser() const
0183 {
0184     return m_currentUser;
0185 }