kde-workspace/kdepasswd/process.cpp
Ivailo Monev de3036ebc0 kdepasswd: format and indent
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2023-07-29 11:05:35 +03:00

531 lines
13 KiB
C++

/* vi: ts=8 sts=4 sw=4
*
* This file is part of the KDE project, module kdesu.
* Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org>
*
* This file contains code from TEShell.C of the KDE konsole.
* Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
*
* This is free software; you can use this library under the GNU Library
* General Public License, version 2. See the file "COPYING.LIB" for the
* exact licensing terms.
*
* process.cpp: Functionality to build a front end to password asking
* terminal programs.
*/
#include "process.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/select.h> // Needed on some systems.
#include <QtCore/qglobal.h>
#include <QtCore/QFile>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <kdebug.h>
#include <kstandarddirs.h>
#include <kde_file.h>
/*
** Wait for @p ms miliseconds
** @param fd file descriptor
** @param ms time to wait in miliseconds
** @return
*/
int PtyProcess::waitMS(int fd,int ms)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 1000*ms;
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd,&fds);
return select(fd+1, &fds, 0L, 0L, &tv);
}
// XXX this function is nonsense:
// - for our child, we could use waitpid().
// - the configurability at this place it *complete* braindamage
/*
** Basic check for the existence of @p pid.
** Returns true iff @p pid is an extant process.
*/
bool PtyProcess::checkPid(pid_t pid)
{
KSharedConfig::Ptr config = KGlobal::config();
KConfigGroup cg(config, "super-user-command");
QString superUserCommand = cg.readEntry("super-user-command", "sudo");
//sudo does not accept signals from user so we except it
if (superUserCommand == "sudo") {
return true;
}
return kill(pid, 0) == 0;
}
/*
** Check process exit status for process @p pid.
** On error (no child, no exit), return Error (-1).
** If child @p pid has exited, return its exit status,
** (which may be zero).
** If child @p has not exited, return NotExited (-2).
*/
int PtyProcess::checkPidExited(pid_t pid)
{
int state, ret;
ret = waitpid(pid, &state, WNOHANG);
if (ret < 0) {
kError(1512) << "waitpid():" << ::strerror(errno);
return Error;
}
if (ret == pid) {
if (WIFEXITED(state)) {
return WEXITSTATUS(state);
}
return Killed;
}
return NotExited;
}
class PtyProcess::PtyProcessPrivate
{
public:
PtyProcessPrivate()
: m_pPTY(0L)
{
}
~PtyProcessPrivate()
{
delete m_pPTY;
}
QList<QByteArray> env;
KPty *m_pPTY;
QByteArray m_Inbuf;
};
PtyProcess::PtyProcess()
: d(new PtyProcessPrivate())
{
m_bTerminal = false;
m_bErase = false;
}
int PtyProcess::init()
{
delete d->m_pPTY;
d->m_pPTY = new KPty();
if (!d->m_pPTY->open()) {
kError(1512) << "Failed to open PTY.";
return -1;
}
d->m_Inbuf.resize(0);
return 0;
}
PtyProcess::~PtyProcess()
{
delete d;
}
/** Set additional environment variables. */
void PtyProcess::setEnvironment(const QList<QByteArray> &env)
{
d->env = env;
}
int PtyProcess::fd() const
{
if (d->m_pPTY) {
return d->m_pPTY->masterFd();
}
return -1;
}
int PtyProcess::pid() const
{
return m_Pid;
}
/** Returns the additional environment variables set by setEnvironment() */
QList<QByteArray> PtyProcess::environment() const
{
return d->env;
}
QByteArray PtyProcess::readAll(bool block)
{
QByteArray ret;
if (!d->m_Inbuf.isEmpty()) {
// if there is still something in the buffer, we need not block.
// we should still try to read any further output, from the fd, though.
block = false;
ret = d->m_Inbuf;
d->m_Inbuf.resize(0);
}
int flags = fcntl(fd(), F_GETFL);
if (flags < 0) {
kError(1512) << "fcntl(F_GETFL):" << ::strerror(errno);
return ret;
}
int oflags = flags;
if (block) {
flags &= ~O_NONBLOCK;
} else {
flags |= O_NONBLOCK;
}
if ((flags != oflags) && (fcntl(fd(), F_SETFL, flags) < 0)) {
// We get an error here when the child process has closed
// the file descriptor already.
return ret;
}
while (1) {
ret.reserve(ret.size() + 0x8000);
int nbytes = read(fd(), ret.data() + ret.size(), 0x8000);
if (nbytes == -1) {
if (errno == EINTR) {
continue;
} else {
break;
}
}
if (nbytes == 0) {
// nothing available / eof
break;
}
ret.resize(ret.size() + nbytes);
break;
}
return ret;
}
QByteArray PtyProcess::readLine(bool block)
{
d->m_Inbuf = readAll(block);
QByteArray ret;
if (!d->m_Inbuf.isEmpty()) {
int pos = d->m_Inbuf.indexOf('\n');
if (pos == -1) {
// NOTE: this means we return something even if there in no full line!
ret = d->m_Inbuf;
d->m_Inbuf.resize(0);
} else {
ret = d->m_Inbuf.left(pos);
d->m_Inbuf.remove(0, pos+1);
}
}
return ret;
}
void PtyProcess::writeLine(const QByteArray &line, bool addnl)
{
if (!line.isEmpty()) {
write(fd(), line, line.length());
}
if (addnl) {
write(fd(), "\n", 1);
}
}
void PtyProcess::unreadLine(const QByteArray &line, bool addnl)
{
QByteArray tmp = line;
if (addnl) {
tmp += '\n';
}
if (!tmp.isEmpty()) {
d->m_Inbuf.prepend(tmp);
}
}
void PtyProcess::setExitString(const QByteArray &exit)
{
m_Exit = exit;
}
/*
* Fork and execute the command. This returns in the parent.
*/
int PtyProcess::exec(const QByteArray &command, const QList<QByteArray> &args)
{
kDebug(1512) << "Running" << command;
if (init() < 0)
return -1;
if ((m_Pid = fork()) == -1) {
kError(1512) << "fork():" << ::strerror(errno);
return -1;
}
// Parent
if (m_Pid) {
d->m_pPTY->closeSlave();
return 0;
}
// Child
if (setupTTY() < 0) {
_exit(1);
}
for (int i = 0; i < d->env.count(); ++i) {
putenv(const_cast<char *>(d->env.at(i).constData()));
}
unsetenv("KDE_FULL_SESSION");
// for : Qt: Session management error
unsetenv("SESSION_MANAGER");
// QMutex::lock , deadlocks without that.
// <thiago> you cannot connect to the user's session bus from another UID
unsetenv("DBUS_SESSION_BUS_ADDRESS");
// set temporarily LC_ALL to C, for su (to be able to parse "Password:")
const QByteArray old_lc_all = qgetenv("LC_ALL");
if (!old_lc_all.isEmpty()) {
qputenv("KDESU_LC_ALL", old_lc_all);
} else {
unsetenv("KDESU_LC_ALL");
}
qputenv("LC_ALL", "C");
// From now on, terminal output goes through the tty.
QByteArray path;
if (command.contains('/')) {
path = command;
} else {
QString file = KStandardDirs::findExe(command);
if (file.isEmpty()) {
kError(1512) << command << "not found.";
_exit(1);
}
path = QFile::encodeName(file);
}
const char **argp = (const char **)malloc((args.count()+2)*sizeof(char *));
int i = 1;
argp[i] = path;
foreach (QByteArray it, args) {
argp[i] = it;
i++;
}
argp[i] = NULL;
execv(path, const_cast<char **>(argp));
free(argp);
kError(1512) << "execv(" << path << "):" << ::strerror(errno);
_exit(1);
return -1; // Shut up compiler. Never reached.
}
/*
* Wait until the terminal is set into no echo mode. At least one su
* (RH6 w/ Linux-PAM patches) sets noecho mode AFTER writing the Password:
* prompt, using TCSAFLUSH. This flushes the terminal I/O queues, possibly
* taking the password with it. So we wait until no echo mode is set
* before writing the password.
* Note that this is done on the slave fd. While Linux allows tcgetattr() on
* the master side, Solaris doesn't.
*/
int PtyProcess::WaitSlave()
{
kDebug(1512) << "Child pid" << m_Pid;
struct termios tio;
while (1) {
if (!checkPid(m_Pid)) {
kError(1512) << "process has exited while waiting for password.";
return -1;
}
if (!d->m_pPTY->tcGetAttr(&tio)) {
kError(1512) << "tcgetattr():" << ::strerror(errno);
return -1;
}
if (tio.c_lflag & ECHO) {
kDebug(1512) << "Echo mode still on.";
usleep(10000);
continue;
}
break;
}
return 0;
}
int PtyProcess::enableLocalEcho(bool enable)
{
return d->m_pPTY->setEcho(enable) ? 0 : -1;
}
void PtyProcess::setTerminal(bool terminal)
{
m_bTerminal = terminal;
}
void PtyProcess::setErase(bool erase)
{
m_bErase = erase;
}
/*
* Copy output to stdout until the child process exits, or a line of output
* matches `m_Exit'.
* We have to use waitpid() to test for exit. Merely waiting for EOF on the
* pty does not work, because the target process may have children still
* attached to the terminal.
*/
int PtyProcess::waitForChild()
{
fd_set fds;
FD_ZERO(&fds);
QByteArray remainder;
while (1) {
FD_SET(fd(), &fds);
// specify timeout to make sure select() does not block, even if the
// process is dead / non-responsive. It does not matter if we abort too
// early. In that case 0 is returned, and we'll try again in the next
// iteration. (As long as we don't consitently time out in each iteration)
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
int ret = select(fd()+1, &fds, 0L, 0L, &timeout);
if (ret == -1) {
if (errno != EINTR) {
kError(1512) << "select():" << ::strerror(errno);
return -1;
}
ret = 0;
}
if (ret) {
forever {
QByteArray output = readAll(false);
if (output.isEmpty()) {
break;
}
if (m_bTerminal) {
fwrite(output.constData(), output.size(), 1, stdout);
fflush(stdout);
}
if (!m_Exit.isEmpty()) {
// match exit string only at line starts
remainder += output;
while (remainder.length() >= m_Exit.length()) {
if (remainder.startsWith(m_Exit)) {
kill(m_Pid, SIGTERM);
remainder.remove(0, m_Exit.length());
}
int off = remainder.indexOf('\n');
if (off < 0) {
break;
}
remainder.remove(0, off + 1);
}
}
}
}
ret = checkPidExited(m_Pid);
if (ret == Error) {
if (errno == ECHILD) {
return 0;
}
return 1;
} else if (ret == Killed) {
return 0;
} else if (ret == NotExited) {
// keep checking
} else {
return ret;
}
}
}
/*
* SetupTTY: Creates a new session. The filedescriptor "fd" should be
* connected to the tty. It is closed after the tty is reopened to make it
* our controlling terminal. This way the tty is always opened at least once
* so we'll never get EIO when reading from it.
*/
int PtyProcess::setupTTY()
{
// Reset signal handlers
for (int sig = 1; sig < NSIG; sig++) {
KDE_signal(sig, SIG_DFL);
}
KDE_signal(SIGHUP, SIG_IGN);
d->m_pPTY->setCTty();
// Connect stdin, stdout and stderr
int slave = d->m_pPTY->slaveFd();
dup2(slave, 0);
dup2(slave, 1);
dup2(slave, 2);
// Close all file handles
// XXX this caused problems in KProcess - not sure why anymore. -- ???
// Because it will close the start notification pipe. -- ossi
struct rlimit rlp;
getrlimit(RLIMIT_NOFILE, &rlp);
for (int i = 3; i < (int)rlp.rlim_cur; i++) {
close(i);
}
// Disable OPOST processing. Otherwise, '\n' are (on Linux at least)
// translated to '\r\n'.
struct ::termios tio;
if (tcgetattr(0, &tio) < 0) {
kError(1512) << "tcgetattr():" << ::strerror(errno);
return -1;
}
tio.c_oflag &= ~OPOST;
if (tcsetattr(0, TCSANOW, &tio) < 0) {
kError(1512) << "tcsetattr():" << ::strerror(errno);
return -1;
}
return 0;
}