1 /*
2 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
3 SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
4 */
5
6 #include "usermanager.h"
7
8 #include <KUser>
9 #include <QFile>
10 #include <QRegularExpression>
11 #include <QProcess>
12
13 #include <KAuthAction>
14 #include <KAuthExecuteJob>
15 #include <KLocalizedString>
16
17 const auto everyoneUserName = QStringLiteral("Everyone");
18
getUsersList()19 static QStringList getUsersList()
20 {
21 uid_t defminuid = 1000;
22 uid_t defmaxuid = 65000;
23
24 QFile loginDefs(QStringLiteral("/etc/login.defs"));
25 if (loginDefs.open(QIODevice::ReadOnly | QIODevice::Text)) {
26 while (!loginDefs.atEnd()) {
27 const QString line = QString::fromLatin1(loginDefs.readLine());
28 {
29 const QRegularExpression expression(QStringLiteral("^\\s*UID_MIN\\s+(?<UID_MIN>\\d+)"));
30 const auto match = expression.match(line);
31 if (match.hasMatch()) {
32 defminuid = match.captured(u"UID_MIN").toUInt();
33 }
34 }
35 {
36 const QRegularExpression expression(QStringLiteral("^\\s*UID_MAX\\s+(?<UID_MAX>\\d+)"));
37 const auto match = expression.match(line);
38 if (match.hasMatch()) {
39 defmaxuid = match.captured(u"UID_MAX").toUInt();
40 }
41 }
42 }
43 }
44
45 QStringList userList;
46 userList.append(everyoneUserName);
47 const QStringList userNames = KUser::allUserNames();
48 for (const QString &username : userNames) {
49 if (username == QLatin1String("nobody")) {
50 continue;
51 }
52 KUser user(username);
53 const uid_t nativeId = user.userId().nativeId();
54 if (nativeId >= defminuid && nativeId <= defmaxuid) {
55 userList << username;
56 }
57 }
58
59 return userList;
60 }
61
User(const QString & name,UserManager * parent)62 User::User(const QString &name, UserManager *parent)
63 : QObject(parent)
64 , m_name(name)
65 {
66 }
67
name() const68 QString User::name() const
69 {
70 return m_name;
71 }
72
inSamba() const73 bool User::inSamba() const
74 {
75 return m_inSamba;
76 }
77
resolve()78 void User::resolve()
79 {
80 if (!qobject_cast<UserManager *>(parent())->canManageSamba() || m_name == everyoneUserName) {
81 // Assume the user is cool. If the auth backend isn't tbdsam it's likely delegated to a domain controller
82 // which in turn suggests that the local system is delegating all users to the controller. If not we
83 // can't do anything about it anyway the options are disable everything or pretend it'll work.
84 m_inSamba = true;
85 Q_EMIT resolved();
86 return;
87 }
88
89 auto action = KAuth::Action(QStringLiteral("org.kde.filesharing.samba.isuserknown"));
90 action.setHelperId(QStringLiteral("org.kde.filesharing.samba"));
91 action.addArgument(QStringLiteral("username"), m_name);
92 // Detail message won't really show up unless the system admin forces authentication for this. Which would
93 // be very awkward
94 action.setDetailsV2({{
95 KAuth::Action::AuthDetail::DetailMessage,
96 i18nc("@label kauth action description %1 is a username", "Checking if Samba user '%1' exists", m_name) }
97 });
98 KAuth::ExecuteJob *job = action.execute();
99 connect(job, &KAuth::ExecuteJob::result, this, [this, job] {
100 job->deleteLater();
101 m_inSamba = job->data().value(QStringLiteral("exists"), false).toBool();
102 Q_EMIT inSambaChanged();
103 Q_EMIT resolved();
104 });
105 job->start();
106 }
107
addToSamba(const QString & password)108 void User::addToSamba(const QString &password)
109 {
110 Q_ASSERT(qobject_cast<UserManager *>(parent())->canManageSamba());
111
112 auto action = KAuth::Action(QStringLiteral("org.kde.filesharing.samba.createuser"));
113 action.setHelperId(QStringLiteral("org.kde.filesharing.samba"));
114 action.addArgument(QStringLiteral("username"), m_name);
115 action.addArgument(QStringLiteral("password"), password);
116 action.setDetailsV2({{
117 KAuth::Action::AuthDetail::DetailMessage,
118 i18nc("@label kauth action description %1 is a username", "Creating new Samba user '%1'", m_name) }
119 });
120 KAuth::ExecuteJob *job = action.execute();
121 connect(job, &KAuth::ExecuteJob::result, this, [this, job] {
122 job->deleteLater();
123 m_inSamba = job->data().value(QStringLiteral("created"), false).toBool();
124 if (!m_inSamba) {
125 Q_EMIT addToSambaError(job->data().value(QStringLiteral("stderr"), QString()).toString());
126 }
127 Q_EMIT inSambaChanged();
128 });
129 job->start();
130 }
131
canManageSamba() const132 bool UserManager::canManageSamba() const
133 {
134 return m_canManageSamba;
135 }
136
load()137 void UserManager::load()
138 {
139 auto proc = new QProcess(this);
140 proc->setProgram(QStringLiteral("testparm"));
141 proc->setArguments({
142 QStringLiteral("--debuglevel=0"),
143 QStringLiteral("--suppress-prompt"),
144 QStringLiteral("--verbose"),
145 QStringLiteral("--parameter-name"),
146 QStringLiteral("passdb backend")
147 });
148 connect(proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this, proc](int exitCode) {
149 proc->deleteLater();
150 const QByteArray output = proc->readAllStandardOutput().simplified();
151
152 if (exitCode == 0 && output == QByteArrayLiteral("tdbsam")) {
153 m_canManageSamba = true;
154 }
155
156 const QString currentUserName = KUser().loginName();
157 const QStringList nameList = getUsersList();
158 for (const auto &name : nameList) {
159 ++m_waitingForResolution;
160 auto user = new User(name, this);
161 connect(user, &User::resolved, this, [this] {
162 if (--m_waitingForResolution <= 0) {
163 Q_EMIT loaded();
164 }
165 }, Qt::QueuedConnection /* queue to ensure even shortcut resolution goes through the loop */);
166 m_users.append(user);
167 if (user->name() == currentUserName) {
168 m_currentUser = user;
169 }
170 user->resolve();
171 }
172 });
173 proc->start();
174 }
175
users() const176 QList<User *> UserManager::users() const
177 {
178 return m_users;
179 }
180
currentUser() const181 Q_INVOKABLE User *UserManager::currentUser() const
182 {
183 return m_currentUser;
184 }
185