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 }