KDESu

suprocess.cpp
1 /*
2  This file is part of the KDE project, module kdesu.
3  SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <[email protected]>
4 
5  Sudo support added by Jonathan Riddell <[email protected] 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 
36 namespace KDESu
37 {
38 using namespace KDESuPrivate;
39 
40 class SuProcessPrivate : public StubProcessPrivate
41 {
42 public:
43  bool isPrivilegeEscalation() const;
44  QString superUserCommand;
45 };
46 
47 bool SuProcessPrivate::isPrivilegeEscalation() const
48 {
49  return (superUserCommand == QLatin1String("sudo") || superUserCommand == QLatin1String("doas"));
50 }
51 
52 SuProcess::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, "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 
70 SuProcess::~SuProcess() = default;
71 
73 {
74  Q_D(SuProcess);
75 
76  return d->superUserCommand;
77 }
78 
80 {
81  Q_D(SuProcess);
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 
91 int SuProcess::checkInstall(const char *password)
92 {
93  return exec(password, Install);
94 }
95 
97 {
98  return exec(nullptr, NeedPassword);
99 }
100 
101 /*
102  * Execute a command with su(1).
103  */
104 int 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 
118  QList<QByteArray> args;
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, "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  if (StubProcess::exec(command, args) < 0) {
147  return check ? SuNotFound : -1;
148  }
149 
150  SuErrors ret = (SuErrors)converseSU(password);
151 
152  if (ret == error) {
153  if (!check) {
154  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
155  << "Conversation with" << d->superUserCommand << "failed.";
156  }
157  return ret;
158  }
159  if (check == NeedPassword) {
160  if (ret == killme) {
161  if (d->isPrivilegeEscalation()) {
162  // sudo can not be killed, just return
163  return ret;
164  }
165  if (kill(m_pid, SIGKILL) < 0) {
166  // FIXME SIGKILL doesn't work for sudo,
167  // why is this different from su?
168  // A: because sudo runs as root. Perhaps we could write a Ctrl+C to its stdin, instead?
169  ret = error;
170  } else {
171  int iret = waitForChild();
172  if (iret < 0) {
173  ret = error;
174  }
175  }
176  }
177  return ret;
178  }
179 
180  if (m_erase && password) {
181  memset(const_cast<char *>(password), 0, qstrlen(password));
182  }
183 
184  if (ret != ok) {
185  kill(m_pid, SIGKILL);
186  if (d->isPrivilegeEscalation()) {
187  waitForChild();
188  }
189  return SuIncorrectPassword;
190  }
191 
192  int iret = converseStub(check);
193  if (iret < 0) {
194  if (!check) {
195  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
196  << "Conversation with kdesu_stub failed.";
197  }
198  return iret;
199  } else if (iret == 1) {
200  kill(m_pid, SIGKILL);
201  waitForChild();
202  return SuIncorrectPassword;
203  }
204 
205  if (check == Install) {
206  waitForChild();
207  return 0;
208  }
209 
210  iret = waitForChild();
211  return iret;
212 }
213 
214 /*
215  * Conversation with su: feed the password.
216  * Return values: -1 = error, 0 = ok, 1 = kill me, 2 not authorized
217  */
218 int SuProcess::converseSU(const char *password)
219 {
220  enum {
221  WaitForPrompt,
222  CheckStar,
223  HandleStub,
224  } state = WaitForPrompt;
225  int colon;
226  unsigned i;
227  unsigned j;
228 
229  QByteArray line;
230  while (true) {
231  line = readLine();
232  // return if problem. sudo checks for a second prompt || su gets a blank line
233  if ((line.contains(':') && state != WaitForPrompt) || line.isNull()) {
234  return (state == HandleStub ? notauthorized : error);
235  }
236 
237  if (line == "kdesu_stub") {
238  unreadLine(line);
239  return ok;
240  }
241 
242  switch (state) {
243  case WaitForPrompt: {
244  if (waitMS(fd(), 100) > 0) {
245  // There is more output available, so this line
246  // couldn't have been a password prompt (the definition
247  // of prompt being that there's a line of output followed
248  // by a colon, and then the process waits).
249  continue;
250  }
251 
252  const uint len = line.length();
253  // Match "Password: " with the regex ^[^:]+:[\w]*$.
254  for (i = 0, j = 0, colon = 0; i < len; ++i) {
255  if (line[i] == ':') {
256  j = i;
257  colon++;
258  continue;
259  }
260  if (!isspace(line[i])) {
261  j++;
262  }
263  }
264  if (colon == 1 && line[j] == ':') {
265  if (password == nullptr) {
266  return killme;
267  }
268  if (waitSlave()) {
269  return error;
270  }
271  write(fd(), password, strlen(password));
272  write(fd(), "\n", 1);
273  state = CheckStar;
274  }
275  break;
276  }
277  //////////////////////////////////////////////////////////////////////////
278  case CheckStar: {
279  const QByteArray s = line.trimmed();
280  if (s.isEmpty()) {
281  state = HandleStub;
282  break;
283  }
284  const bool starCond = std::any_of(s.cbegin(), s.cend(), [](const char c) {
285  return c != '*';
286  });
287  if (starCond) {
288  return error;
289  }
290  state = HandleStub;
291  break;
292  }
293  //////////////////////////////////////////////////////////////////////////
294  case HandleStub:
295  break;
296  //////////////////////////////////////////////////////////////////////////
297  } // end switch
298  } // end while (true)
299  return ok;
300 }
301 
302 void SuProcess::virtual_hook(int id, void *data)
303 {
304  StubProcess::virtual_hook(id, data);
305 }
306 
307 } // namespace KDESu
bool isNull() const const
QString fromUtf8(const char *str, int size)
int converseStub(int check)
Exchange all parameters with kdesu_stub.
static int waitMS(int fd, int ms)
Wait ms milliseconds (ie.
Definition: ptyprocess.cpp:54
QByteArray trimmed() const const
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QString findExecutable(const QString &executableName, const QStringList &paths)
int checkNeedPassword()
Checks if a password is needed.
Definition: suprocess.cpp:96
QByteArray readLine(bool block=true)
Reads a line from the program's standard out.
Definition: ptyprocess.cpp:215
int exec(const QByteArray &command, const QList< QByteArray > &args)
Forks off and execute a command.
Definition: ptyprocess.cpp:265
int waitForChild()
Waits for the child to exit.
Definition: ptyprocess.cpp:394
QByteArray::const_iterator cend() const const
int waitSlave()
Waits until the pty has cleared the ECHO flag.
Definition: ptyprocess.cpp:349
int m_pid
PID of child process.
Definition: ptyprocess.h:196
KSharedConfigPtr config()
QString loginName() const
bool contains(char ch) const const
int checkInstall(const char *password)
Checks if the stub is installed and the password is correct.
Definition: suprocess.cpp:91
bool isEmpty() const const
QByteArray::const_iterator cbegin() const const
void setTerminal(bool terminal)
Enables/disables terminal output.
Definition: ptyprocess.cpp:377
int length() const const
QByteArray toLocal8Bit() const const
bool useUsersOwnPassword()
Checks whether or not the user's password is being asked for or another user's password.
Definition: suprocess.cpp:79
int fd() const
Returns the filedescriptor of the process.
Definition: ptyprocess.cpp:148
void virtual_hook(int id, void *data) override
Standard hack to add virtual methods in a BC way.
void virtual_hook(int id, void *data) override
Standard hack to add virtual methods in a BC way.
Definition: suprocess.cpp:302
QString superUserCommand()
Checks what the default super user command is, e.g.
Definition: suprocess.cpp:72
Q_D(Todo)
void unreadLine(const QByteArray &line, bool addNewline=true)
Puts back a line of input.
Definition: ptyprocess.cpp:246
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri May 20 2022 04:13:56 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.