KDESu

ptyprocess.cpp
1 /*
2  This file is part of the KDE project, module kdesu.
3  SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <[email protected]>
4 
5  This file contains code from TEShell.C of the KDE konsole.
6  SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <[email protected]>
7 
8  SPDX-License-Identifier: GPL-2.0-only
9 
10  process.cpp: Functionality to build a front end to password asking terminal programs.
11 */
12 
13 #include "ptyprocess.h"
14 #include "kcookie_p.h"
15 #include "ptyprocess_p.h"
16 
17 #include <config-kdesu.h>
18 #include <ksu_debug.h>
19 
20 #include <cerrno>
21 #include <fcntl.h>
22 #include <signal.h>
23 #include <stdlib.h>
24 #include <termios.h>
25 #include <unistd.h>
26 
27 #include <sys/resource.h>
28 #include <sys/stat.h>
29 #include <sys/time.h>
30 #include <sys/wait.h>
31 
32 #if HAVE_SYS_SELECT_H
33 #include <sys/select.h> // Needed on some systems.
34 #endif
35 
36 #include <QFile>
37 #include <QStandardPaths>
38 
39 #include <KConfigGroup>
40 #include <KSharedConfig>
41 
42 extern int kdesuDebugArea();
43 
44 namespace KDESu
45 {
46 using namespace KDESuPrivate;
47 
48 /*
49 ** Wait for @p ms milliseconds
50 ** @param fd file descriptor
51 ** @param ms time to wait in milliseconds
52 ** @return
53 */
54 int PtyProcess::waitMS(int fd, int ms)
55 {
56  struct timeval tv;
57  tv.tv_sec = 0;
58  tv.tv_usec = 1000 * ms;
59 
60  fd_set fds;
61  FD_ZERO(&fds);
62  FD_SET(fd, &fds);
63  return select(fd + 1, &fds, nullptr, nullptr, &tv);
64 }
65 
66 // XXX this function is nonsense:
67 // - for our child, we could use waitpid().
68 // - the configurability at this place it *complete* braindamage
69 /*
70 ** Basic check for the existence of @p pid.
71 ** Returns true iff @p pid is an extant process.
72 */
73 bool PtyProcess::checkPid(pid_t pid)
74 {
75  KSharedConfig::Ptr config = KSharedConfig::openConfig();
76  KConfigGroup cg(config, "super-user-command");
77  QString superUserCommand = cg.readEntry("super-user-command", "sudo");
78  // sudo does not accept signals from user so we except it
79  if (superUserCommand == QLatin1String("sudo")) {
80  return true;
81  } else {
82  return kill(pid, 0) == 0;
83  }
84 }
85 
86 /*
87 ** Check process exit status for process @p pid.
88 ** On error (no child, no exit), return Error (-1).
89 ** If child @p pid has exited, return its exit status,
90 ** (which may be zero).
91 ** If child @p has not exited, return NotExited (-2).
92 */
94 {
95  int state;
96  int ret;
97  ret = waitpid(pid, &state, WNOHANG);
98 
99  if (ret < 0) {
100  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
101  << "waitpid():" << strerror(errno);
102  return Error;
103  }
104  if (ret == pid) {
105  if (WIFEXITED(state)) {
106  return WEXITSTATUS(state);
107  }
108 
109  return Killed;
110  }
111 
112  return NotExited;
113 }
114 
115 PtyProcess::PtyProcess()
116  : PtyProcess(*new PtyProcessPrivate)
117 {
118 }
119 
120 PtyProcess::PtyProcess(PtyProcessPrivate &dd)
121  : d(&dd)
122 {
123  m_terminal = false;
124  m_erase = false;
125 }
126 
127 PtyProcess::~PtyProcess() = default;
128 
129 int PtyProcess::init()
130 {
131  delete d->pty;
132  d->pty = new KPty();
133  if (!d->pty->open()) {
134  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
135  << "Failed to open PTY.";
136  return -1;
137  }
138  d->inputBuffer.resize(0);
139  return 0;
140 }
141 
142 /** Set additional environment variables. */
144 {
145  d->env = env;
146 }
147 
148 int PtyProcess::fd() const
149 {
150  return d->pty ? d->pty->masterFd() : -1;
151 }
152 
153 int PtyProcess::pid() const
154 {
155  return m_pid;
156 }
157 
158 /** Returns the additional environment variables set by setEnvironment() */
160 {
161  return d->env;
162 }
163 
165 {
166  QByteArray ret;
167  if (!d->inputBuffer.isEmpty()) {
168  // if there is still something in the buffer, we need not block.
169  // we should still try to read any further output, from the fd, though.
170  block = false;
171  ret = d->inputBuffer;
172  d->inputBuffer.resize(0);
173  }
174 
175  int flags = fcntl(fd(), F_GETFL);
176  if (flags < 0) {
177  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
178  << "fcntl(F_GETFL):" << strerror(errno);
179  return ret;
180  }
181  int oflags = flags;
182  if (block) {
183  flags &= ~O_NONBLOCK;
184  } else {
185  flags |= O_NONBLOCK;
186  }
187 
188  if ((flags != oflags) && (fcntl(fd(), F_SETFL, flags) < 0)) {
189  // We get an error here when the child process has closed
190  // the file descriptor already.
191  return ret;
192  }
193 
194  while (1) {
195  ret.reserve(ret.size() + 0x8000);
196  int nbytes = read(fd(), ret.data() + ret.size(), 0x8000);
197  if (nbytes == -1) {
198  if (errno == EINTR) {
199  continue;
200  } else {
201  break;
202  }
203  }
204  if (nbytes == 0) {
205  break; // nothing available / eof
206  }
207 
208  ret.resize(ret.size() + nbytes);
209  break;
210  }
211 
212  return ret;
213 }
214 
216 {
217  d->inputBuffer = readAll(block);
218 
219  int pos;
220  QByteArray ret;
221  if (!d->inputBuffer.isEmpty()) {
222  pos = d->inputBuffer.indexOf('\n');
223  if (pos == -1) {
224  // NOTE: this means we return something even if there in no full line!
225  ret = d->inputBuffer;
226  d->inputBuffer.resize(0);
227  } else {
228  ret = d->inputBuffer.left(pos);
229  d->inputBuffer.remove(0, pos + 1);
230  }
231  }
232 
233  return ret;
234 }
235 
236 void PtyProcess::writeLine(const QByteArray &line, bool addnl)
237 {
238  if (!line.isEmpty()) {
239  write(fd(), line.constData(), line.length());
240  }
241  if (addnl) {
242  write(fd(), "\n", 1);
243  }
244 }
245 
246 void PtyProcess::unreadLine(const QByteArray &line, bool addnl)
247 {
248  QByteArray tmp = line;
249  if (addnl) {
250  tmp += '\n';
251  }
252  if (!tmp.isEmpty()) {
253  d->inputBuffer.prepend(tmp);
254  }
255 }
256 
258 {
259  m_exitString = exit;
260 }
261 
262 /*
263  * Fork and execute the command. This returns in the parent.
264  */
265 int PtyProcess::exec(const QByteArray &command, const QList<QByteArray> &args)
266 {
267  int i;
268 
269  if (init() < 0) {
270  return -1;
271  }
272 
273  if ((m_pid = fork()) == -1) {
274  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
275  << "fork():" << strerror(errno);
276  return -1;
277  }
278 
279  // Parent
280  if (m_pid) {
281  d->pty->closeSlave();
282  return 0;
283  }
284 
285  // Child
286  if (setupTTY() < 0) {
287  _exit(1);
288  }
289 
290  for (const QByteArray &var : std::as_const(d->env)) {
291  putenv(const_cast<char *>(var.constData()));
292  }
293  unsetenv("KDE_FULL_SESSION");
294  // for : Qt: Session management error
295  unsetenv("SESSION_MANAGER");
296  // QMutex::lock , deadlocks without that.
297  // <thiago> you cannot connect to the user's session bus from another UID
298  unsetenv("DBUS_SESSION_BUS_ADDRESS");
299 
300  // set temporarily LC_ALL to C, for su (to be able to parse "Password:")
301  const QByteArray old_lc_all = qgetenv("LC_ALL");
302  if (!old_lc_all.isEmpty()) {
303  qputenv("KDESU_LC_ALL", old_lc_all);
304  } else {
305  unsetenv("KDESU_LC_ALL");
306  }
307  qputenv("LC_ALL", "C");
308 
309  // From now on, terminal output goes through the tty.
310 
311  QByteArray path;
312  if (command.contains('/')) {
313  path = command;
314  } else {
316  if (file.isEmpty()) {
317  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " << command << "not found.";
318  _exit(1);
319  }
320  path = QFile::encodeName(file);
321  }
322 
323  const char **argp = (const char **)malloc((args.count() + 2) * sizeof(char *));
324 
325  i = 0;
326  argp[i++] = path.constData();
327  for (const QByteArray &arg : args) {
328  argp[i++] = arg.constData();
329  }
330 
331  argp[i] = nullptr;
332 
333  execv(path.constData(), const_cast<char **>(argp));
334  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
335  << "execv(" << path << "):" << strerror(errno);
336  _exit(1);
337  return -1; // Shut up compiler. Never reached.
338 }
339 
340 /*
341  * Wait until the terminal is set into no echo mode. At least one su
342  * (RH6 w/ Linux-PAM patches) sets noecho mode AFTER writing the Password:
343  * prompt, using TCSAFLUSH. This flushes the terminal I/O queues, possibly
344  * taking the password with it. So we wait until no echo mode is set
345  * before writing the password.
346  * Note that this is done on the slave fd. While Linux allows tcgetattr() on
347  * the master side, Solaris doesn't.
348  */
350 {
351  struct termios tio;
352  while (1) {
353  if (!checkPid(m_pid)) {
354  qCCritical(KSU_LOG) << "process has exited while waiting for password.";
355  return -1;
356  }
357  if (!d->pty->tcGetAttr(&tio)) {
358  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
359  << "tcgetattr():" << strerror(errno);
360  return -1;
361  }
362  if (tio.c_lflag & ECHO) {
363  // qDebug() << "[" << __FILE__ << ":" << __LINE__ << "] " << "Echo mode still on.";
364  usleep(10000);
365  continue;
366  }
367  break;
368  }
369  return 0;
370 }
371 
373 {
374  return d->pty->setEcho(enable) ? 0 : -1;
375 }
376 
377 void PtyProcess::setTerminal(bool terminal)
378 {
379  m_terminal = terminal;
380 }
381 
382 void PtyProcess::setErase(bool erase)
383 {
384  m_erase = erase;
385 }
386 
387 /*
388  * Copy output to stdout until the child process exits, or a line of output
389  * matches `m_exitString'.
390  * We have to use waitpid() to test for exit. Merely waiting for EOF on the
391  * pty does not work, because the target process may have children still
392  * attached to the terminal.
393  */
395 {
396  fd_set fds;
397  FD_ZERO(&fds);
398  QByteArray remainder;
399 
400  while (1) {
401  FD_SET(fd(), &fds);
402 
403  // specify timeout to make sure select() does not block, even if the
404  // process is dead / non-responsive. It does not matter if we abort too
405  // early. In that case 0 is returned, and we'll try again in the next
406  // iteration. (As long as we don't consistently time out in each iteration)
407  timeval timeout;
408  timeout.tv_sec = 0;
409  timeout.tv_usec = 100000;
410  int ret = select(fd() + 1, &fds, nullptr, nullptr, &timeout);
411  if (ret == -1) {
412  if (errno != EINTR) {
413  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
414  << "select():" << strerror(errno);
415  return -1;
416  }
417  ret = 0;
418  }
419 
420  if (ret) {
421  for (;;) {
422  QByteArray output = readAll(false);
423  if (output.isEmpty()) {
424  break;
425  }
426  if (m_terminal) {
427  fwrite(output.constData(), output.size(), 1, stdout);
428  fflush(stdout);
429  }
430  if (!m_exitString.isEmpty()) {
431  // match exit string only at line starts
432  remainder += output;
433  while (remainder.length() >= m_exitString.length()) {
434  if (remainder.startsWith(m_exitString)) {
435  kill(m_pid, SIGTERM);
436  remainder.remove(0, m_exitString.length());
437  }
438  int off = remainder.indexOf('\n');
439  if (off < 0) {
440  break;
441  }
442  remainder.remove(0, off + 1);
443  }
444  }
445  }
446  }
447 
448  ret = checkPidExited(m_pid);
449  if (ret == Error) {
450  if (errno == ECHILD) {
451  return 0;
452  } else {
453  return 1;
454  }
455  } else if (ret == Killed) {
456  return 0;
457  } else if (ret == NotExited) {
458  continue; // keep checking
459  } else {
460  return ret;
461  }
462  }
463 }
464 
465 /*
466  * SetupTTY: Creates a new session. The filedescriptor "fd" should be
467  * connected to the tty. It is closed after the tty is reopened to make it
468  * our controlling terminal. This way the tty is always opened at least once
469  * so we'll never get EIO when reading from it.
470  */
471 int PtyProcess::setupTTY()
472 {
473  // Reset signal handlers
474  for (int sig = 1; sig < NSIG; sig++) {
475  signal(sig, SIG_DFL);
476  }
477  signal(SIGHUP, SIG_IGN);
478 
479  d->pty->setCTty();
480 
481  // Connect stdin, stdout and stderr
482  int slave = d->pty->slaveFd();
483  dup2(slave, 0);
484  dup2(slave, 1);
485  dup2(slave, 2);
486 
487  // Close all file handles
488  // XXX this caused problems in KProcess - not sure why anymore. -- ???
489  // Because it will close the start notification pipe. -- ossi
490  struct rlimit rlp;
491  getrlimit(RLIMIT_NOFILE, &rlp);
492  for (int i = 3; i < (int)rlp.rlim_cur; i++) {
493  close(i);
494  }
495 
496  // Disable OPOST processing. Otherwise, '\n' are (on Linux at least)
497  // translated to '\r\n'.
498  struct ::termios tio;
499  if (tcgetattr(0, &tio) < 0) {
500  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
501  << "tcgetattr():" << strerror(errno);
502  return -1;
503  }
504  tio.c_oflag &= ~OPOST;
505  if (tcsetattr(0, TCSANOW, &tio) < 0) {
506  qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] "
507  << "tcsetattr():" << strerror(errno);
508  return -1;
509  }
510 
511  return 0;
512 }
513 
514 void PtyProcess::virtual_hook(int id, void *data)
515 {
516  Q_UNUSED(id);
517  Q_UNUSED(data);
518  /*BASE::virtual_hook( id, data );*/
519 }
520 
521 } // namespace KDESu
QString readEntry(const char *key, const char *aDefault=nullptr) const
void setErase(bool erase)
Overwrites the password as soon as it is used.
Definition: ptyprocess.cpp:382
QByteArray readAll(bool block=true)
Read all available output from the program's standard out.
Definition: ptyprocess.cpp:164
void writeLine(const QByteArray &line, bool addNewline=true)
Writes a line of text to the program's standard in.
Definition: ptyprocess.cpp:236
int indexOf(char ch, int from) const const
void setEnvironment(const QList< QByteArray > &env)
Set additinal environment variables.
Definition: ptyprocess.cpp:143
static bool checkPid(pid_t pid)
Basic check for the existence of pid.
Definition: ptyprocess.cpp:73
int enableLocalEcho(bool enable=true)
Enables/disables local echo on the pseudo tty.
Definition: ptyprocess.cpp:372
int count(const T &value) const const
QByteArray encodeName(const QString &fileName)
static int waitMS(int fd, int ms)
Wait ms milliseconds (ie.
Definition: ptyprocess.cpp:54
@ NotExited
Child hasn't exited.
Definition: ptyprocess.h:41
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
@ Killed
Child terminated by signal.
Definition: ptyprocess.h:42
QString findExecutable(const QString &executableName, const QStringList &paths)
QByteArray & remove(int pos, int len)
QByteArray readLine(bool block=true)
Reads a line from the program's standard out.
Definition: ptyprocess.cpp:215
virtual void virtual_hook(int id, void *data)
Standard hack to add virtual methods in a BC way.
Definition: ptyprocess.cpp:514
int exec(const QByteArray &command, const QList< QByteArray > &args)
Forks off and execute a command.
Definition: ptyprocess.cpp:265
static int checkPidExited(pid_t pid)
Check process exit status for process pid.
Definition: ptyprocess.cpp:93
int waitForChild()
Waits for the child to exit.
Definition: ptyprocess.cpp:394
QList< QByteArray > environment() const
Returns the additional environment variables set by setEnvironment()
Definition: ptyprocess.cpp:159
bool isEmpty() 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
bool startsWith(const QByteArray &ba) const const
QByteArray m_exitString
String to scan for in output that indicates child has exited.
Definition: ptyprocess.h:198
bool m_terminal
Indicates running in a terminal, causes additional newlines to be printed after output.
Definition: ptyprocess.h:193
bool contains(char ch) const const
bool isEmpty() const const
void resize(int size)
const char * constData() const const
void reserve(int size)
void setTerminal(bool terminal)
Enables/disables terminal output.
Definition: ptyprocess.cpp:377
int size() const const
int length() const const
@ Error
No child.
Definition: ptyprocess.h:40
int pid() const
Returns the pid of the process.
Definition: ptyprocess.cpp:153
int fd() const
Returns the filedescriptor of the process.
Definition: ptyprocess.cpp:148
void setExitString(const QByteArray &exit)
Sets the exit string.
Definition: ptyprocess.cpp:257
char * data()
void unreadLine(const QByteArray &line, bool addNewline=true)
Puts back a line of input.
Definition: ptyprocess.cpp:246
QString decodeName(const QByteArray &localFileName)
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.