KDESu

suprocess.cpp
1/*
2 This file is part of the KDE project, module kdesu.
3 SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org>
4
5 Sudo support added by Jonathan Riddell <jriddell@ ubuntu.com>
6 SPDX-FileCopyrightText: 2005 Canonical Ltd // krazy:exclude=copyright (no email)
7
8 SPDX-License-Identifier: GPL-2.0-only
9
10 su.cpp: Execute a program as another user with "class SuProcess".
11*/
12
13#include "suprocess.h"
14
15#include "kcookie_p.h"
16#include "stubprocess_p.h"
17#include <ksu_debug.h>
18
19#include <QFile>
20#include <QStandardPaths>
21#include <qplatformdefs.h>
22
23#include <KConfig>
24#include <KConfigGroup>
25#include <KSharedConfig>
26#include <kuser.h>
27
28#if defined(KDESU_USE_SUDO_DEFAULT)
29#define DEFAULT_SUPER_USER_COMMAND QStringLiteral("sudo")
30#elif defined(KDESU_USE_DOAS_DEFAULT)
31#define DEFAULT_SUPER_USER_COMMAND QStringLiteral("doas")
32#else
33#define DEFAULT_SUPER_USER_COMMAND QStringLiteral("su")
34#endif
35
36namespace KDESu
37{
38using namespace KDESuPrivate;
39
40class SuProcessPrivate : public StubProcessPrivate
41{
42public:
43 bool isPrivilegeEscalation() const;
44 QString superUserCommand;
45};
46
47bool SuProcessPrivate::isPrivilegeEscalation() const
48{
49 return (superUserCommand == QLatin1String("sudo") || superUserCommand == QLatin1String("doas"));
50}
51
52SuProcess::SuProcess(const QByteArray &user, const QByteArray &command)
53 : StubProcess(*new SuProcessPrivate)
54{
55 Q_D(SuProcess);
56
57 m_user = user;
58 m_command = command;
59
60 KSharedConfig::Ptr config = KSharedConfig::openConfig();
61 KConfigGroup group(config, QStringLiteral("super-user-command"));
62 d->superUserCommand = group.readEntry("super-user-command", DEFAULT_SUPER_USER_COMMAND);
63
64 if (!d->isPrivilegeEscalation() && d->superUserCommand != QLatin1String("su")) {
65 qCWarning(KSU_LOG) << "unknown super user command.";
66 d->superUserCommand = DEFAULT_SUPER_USER_COMMAND;
67 }
68}
69
70SuProcess::~SuProcess() = default;
71
72QString SuProcess::superUserCommand()
73{
75
76 return d->superUserCommand;
77}
78
79bool SuProcess::useUsersOwnPassword()
80{
82
83 if (d->isPrivilegeEscalation() && m_user == "root") {
84 return true;
85 }
86
87 KUser user;
88 return user.loginName() == QString::fromUtf8(m_user);
89}
90
91int SuProcess::checkInstall(const char *password)
92{
93 return exec(password, Install);
94}
95
96int SuProcess::checkNeedPassword()
97{
98 return exec(nullptr, NeedPassword);
99}
100
101/*
102 * Execute a command with su(1).
103 */
104int SuProcess::exec(const char *password, int check)
105{
106 Q_D(SuProcess);
107
108 if (check) {
109 setTerminal(true);
110 }
111
112 // since user may change after constructor (due to setUser())
113 // we need to override sudo with su for non-root here
114 if (m_user != QByteArray("root")) {
115 d->superUserCommand = QStringLiteral("su");
116 }
117
119 if (d->isPrivilegeEscalation()) {
120 args += "-u";
121 }
122
123 if (m_scheduler != SchedNormal || m_priority > 50) {
124 args += "root";
125 } else {
126 args += m_user;
127 }
128
129 if (d->superUserCommand == QLatin1String("su")) {
130 args += "-c";
131 }
132 // Get the kdesu_stub and su command from a config file if set, used in test
133 KSharedConfig::Ptr config = KSharedConfig::openConfig();
134 KConfigGroup group(config, QStringLiteral("super-user-command"));
135 const QString defaultPath = QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF) + QStringLiteral("/kdesu_stub");
136 const QString kdesuStubPath = group.readEntry("kdesu_stub_path", defaultPath);
137 args += kdesuStubPath.toLocal8Bit();
138 args += "-"; // krazy:exclude=doublequote_chars (QList, not QString)
139
140 const QString commandString = group.readEntry("command", QStandardPaths::findExecutable(d->superUserCommand));
141 const QByteArray command = commandString.toLocal8Bit();
142 if (command.isEmpty()) {
143 return check ? SuNotFound : -1;
144 }
145
146 // Turn echo off for conversion with kdesu_stub. Needs to be done before
147 // it's started so that sudo copies this option to its internal PTY.
148 enableLocalEcho(false);
149
150 if (StubProcess::exec(command, args) < 0) {
151 return check ? SuNotFound : -1;
152 }
153
154 SuErrors ret = (SuErrors)converseSU(password);
155
156 if (ret == error) {
157 if (!check) {
158 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
159 << "Conversation with" << d->superUserCommand << "failed.";
160 }
161 return ret;
162 }
163 if (check == NeedPassword) {
164 if (ret == killme) {
165 if (d->isPrivilegeEscalation()) {
166 // sudo can not be killed, just return
167 return ret;
168 }
169 if (kill(m_pid, SIGKILL) < 0) {
170 // FIXME SIGKILL doesn't work for sudo,
171 // why is this different from su?
172 // A: because sudo runs as root. Perhaps we could write a Ctrl+C to its stdin, instead?
173 ret = error;
174 } else {
175 int iret = waitForChild();
176 if (iret < 0) {
177 ret = error;
178 }
179 }
180 }
181 return ret;
182 }
183
184 if (m_erase && password) {
185 memset(const_cast<char *>(password), 0, qstrlen(password));
186 }
187
188 if (ret != ok) {
189 kill(m_pid, SIGKILL);
190 if (d->isPrivilegeEscalation()) {
191 waitForChild();
192 }
193 return SuIncorrectPassword;
194 }
195
196 int iret = converseStub(check);
197 if (iret < 0) {
198 if (!check) {
199 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
200 << "Conversation with kdesu_stub failed.";
201 }
202 return iret;
203 } else if (iret == 1) {
204 kill(m_pid, SIGKILL);
205 waitForChild();
206 return SuIncorrectPassword;
207 }
208
209 if (check == Install) {
210 waitForChild();
211 return 0;
212 }
213
214 iret = waitForChild();
215 return iret;
216}
217
218/*
219 * Conversation with su: feed the password.
220 * Return values: -1 = error, 0 = ok, 1 = kill me, 2 not authorized
221 */
222int SuProcess::converseSU(const char *password)
223{
224 enum {
225 WaitForPrompt,
226 CheckStar,
227 HandleStub,
228 } state = WaitForPrompt;
229 int colon;
230 unsigned i;
231 unsigned j;
232
233 QByteArray line;
234 while (true) {
235 line = readLine();
236 // return if problem. sudo checks for a second prompt || su gets a blank line
237 if ((line.contains(':') && state != WaitForPrompt) || line.isNull()) {
238 return (state == HandleStub ? notauthorized : error);
239 }
240
241 if (line == "kdesu_stub") {
242 unreadLine(line);
243 return ok;
244 }
245
246 switch (state) {
247 case WaitForPrompt: {
248 if (waitMS(fd(), 100) > 0) {
249 // There is more output available, so this line
250 // couldn't have been a password prompt (the definition
251 // of prompt being that there's a line of output followed
252 // by a colon, and then the process waits).
253 continue;
254 }
255
256 const uint len = line.length();
257 // Match "Password: " with the regex ^[^:]+:[\w]*$.
258 for (i = 0, j = 0, colon = 0; i < len; ++i) {
259 if (line[i] == ':') {
260 j = i;
261 colon++;
262 continue;
263 }
264 if (!isspace(line[i])) {
265 j++;
266 }
267 }
268 if (colon == 1 && line[j] == ':') {
269 if (password == nullptr) {
270 return killme;
271 }
272 if (waitSlave()) {
273 return error;
274 }
275 write(fd(), password, strlen(password));
276 write(fd(), "\n", 1);
277 state = CheckStar;
278 }
279 break;
280 }
281 //////////////////////////////////////////////////////////////////////////
282 case CheckStar: {
283 const QByteArray s = line.trimmed();
284 if (s.isEmpty()) {
285 state = HandleStub;
286 break;
287 }
288 const bool starCond = std::any_of(s.cbegin(), s.cend(), [](const char c) {
289 return c != '*';
290 });
291 if (starCond) {
292 return error;
293 }
294 state = HandleStub;
295 break;
296 }
297 //////////////////////////////////////////////////////////////////////////
298 case HandleStub:
299 break;
300 //////////////////////////////////////////////////////////////////////////
301 } // end switch
302 } // end while (true)
303 return ok;
304}
305
306void SuProcess::virtual_hook(int id, void *data)
307{
308 StubProcess::virtual_hook(id, data);
309}
310
311} // namespace KDESu
Executes a command under elevated privileges, using su.
Definition suprocess.h:24
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QString loginName() const
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
const_iterator cbegin() const const
const_iterator cend() const const
bool contains(QByteArrayView bv) const const
bool isEmpty() const const
bool isNull() const const
qsizetype length() const const
QByteArray trimmed() const const
QString findExecutable(const QString &executableName, const QStringList &paths)
QString fromUtf8(QByteArrayView str)
QByteArray toLocal8Bit() const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:55 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.