diff options
Diffstat (limited to 'tdesu/process.cpp')
-rw-r--r-- | tdesu/process.cpp | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/tdesu/process.cpp b/tdesu/process.cpp new file mode 100644 index 000000000..d52308f63 --- /dev/null +++ b/tdesu/process.cpp @@ -0,0 +1,626 @@ +/* vi: ts=8 sts=4 sw=4 + * + * $Id$ + * + * This file is part of the KDE project, module tdesu. + * 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 <config.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 <signal.h> + +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/ioctl.h> + +#if defined(__SVR4) && defined(sun) +#include <stropts.h> +#include <sys/stream.h> +#endif + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> // Needed on some systems. +#endif + +#include <tqglobal.h> +#include <tqcstring.h> +#include <tqfile.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <kstandarddirs.h> + +#include "process.h" +#include "tdesu_pty.h" +#include "kcookie.h" + +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); +} + +/* +** Basic check for the existence of @p pid. +** Returns true iff @p pid is an extant process. +*/ +bool PtyProcess::checkPid(pid_t pid) +{ + KConfig* config = KGlobal::config(); + config->setGroup("super-user-command"); + TQString superUserCommand = config->readEntry("super-user-command", DEFAULT_SUPER_USER_COMMAND); + //sudo does not accept signals from user so we except it + if (superUserCommand == "sudo") { + return true; + } else { + 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) + { + kdError(900) << k_lineinfo << "waitpid(): " << perror << "\n"; + return Error; + } + if (ret == pid) + { + if (WIFEXITED(state)) + return WEXITSTATUS(state); + return Killed; + } + + return NotExited; +} + + +class PtyProcess::PtyProcessPrivate +{ +public: + QCStringList env; +}; + + +PtyProcess::PtyProcess() +{ + m_bTerminal = false; + m_bErase = false; + m_pPTY = 0L; + d = new PtyProcessPrivate; +} + + +int PtyProcess::init() +{ + delete m_pPTY; + m_pPTY = new PTY(); + m_Fd = m_pPTY->getpt(); + if (m_Fd < 0) + return -1; + if ((m_pPTY->grantpt() < 0) || (m_pPTY->unlockpt() < 0)) + { + kdError(900) << k_lineinfo << "Master setup failed.\n"; + m_Fd = -1; + return -1; + } + m_TTY = m_pPTY->ptsname(); + m_Inbuf.resize(0); + return 0; +} + + +PtyProcess::~PtyProcess() +{ + delete m_pPTY; + delete d; +} + +/** Set additinal environment variables. */ +void PtyProcess::setEnvironment( const QCStringList &env ) +{ + d->env = env; +} + +const QCStringList& PtyProcess::environment() const +{ + return d->env; +} + +/* + * Read one line of input. The terminal is in canonical mode, so you always + * read a line at at time, but it's possible to receive multiple lines in + * one time. + */ + +TQCString PtyProcess::readLine(bool block) +{ + int pos; + TQCString ret; + + if (!m_Inbuf.isEmpty()) + { + pos = m_Inbuf.find('\n'); + if (pos == -1) + { + ret = m_Inbuf; + m_Inbuf.resize(0); + } else + { + ret = m_Inbuf.left(pos); + m_Inbuf = m_Inbuf.mid(pos+1); + } + return ret; + } + + int flags = fcntl(m_Fd, F_GETFL); + if (flags < 0) + { + kdError(900) << k_lineinfo << "fcntl(F_GETFL): " << perror << "\n"; + return ret; + } + int oflags = flags; + if (block) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if ((flags != oflags) && (fcntl(m_Fd, F_SETFL, flags) < 0)) + { + // We get an error here when the child process has closed + // the file descriptor already. + return ret; + } + + int nbytes; + char buf[256]; + while (1) + { + nbytes = read(m_Fd, buf, 255); + if (nbytes == -1) + { + if (errno == EINTR) + continue; + else break; + } + if (nbytes == 0) + break; // eof + + buf[nbytes] = '\000'; + m_Inbuf += buf; + + pos = m_Inbuf.find('\n'); + if (pos == -1) + { + ret = m_Inbuf; + m_Inbuf.resize(0); + } else + { + ret = m_Inbuf.left(pos); + m_Inbuf = m_Inbuf.mid(pos+1); + } + break; + } + + return ret; +} + +TQCString PtyProcess::readAll(bool block) +{ + TQCString ret; + + if (!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 = m_Inbuf; + m_Inbuf.resize(0); + } + + int flags = fcntl(m_Fd, F_GETFL); + if (flags < 0) + { + kdError(900) << k_lineinfo << "fcntl(F_GETFL): " << perror << "\n"; + return ret; + } + int oflags = flags; + if (block) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if ((flags != oflags) && (fcntl(m_Fd, F_SETFL, flags) < 0)) + { + // We get an error here when the child process has closed + // the file descriptor already. + return ret; + } + + int nbytes; + char buf[256]; + while (1) + { + nbytes = read(m_Fd, buf, 255); + if (nbytes == -1) + { + if (errno == EINTR) + continue; + else break; + } + if (nbytes == 0) + break; // eof + + buf[nbytes] = '\000'; + ret += buf; + break; + } + + return ret; +} + + +void PtyProcess::writeLine(const TQCString &line, bool addnl) +{ + if (!line.isEmpty()) + write(m_Fd, line, line.length()); + if (addnl) + write(m_Fd, "\n", 1); +} + + +void PtyProcess::unreadLine(const TQCString &line, bool addnl) +{ + TQCString tmp = line; + if (addnl) + tmp += '\n'; + if (!tmp.isEmpty()) + m_Inbuf.prepend(tmp); +} + +/* + * Fork and execute the command. This returns in the parent. + */ + +int PtyProcess::exec(const TQCString &command, const QCStringList &args) +{ + kdDebug(900) << k_lineinfo << "Running `" << command << "'\n"; + + if (init() < 0) + return -1; + + // Open the pty slave before forking. See SetupTTY() + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(900) << k_lineinfo << "Could not open slave pty.\n"; + return -1; + } + + if ((m_Pid = fork()) == -1) + { + kdError(900) << k_lineinfo << "fork(): " << perror << "\n"; + return -1; + } + + // Parent + if (m_Pid) + { + close(slave); + return 0; + } + + // Child + if (SetupTTY(slave) < 0) + _exit(1); + + for(QCStringList::ConstIterator it = d->env.begin(); + it != d->env.end(); it++) + { + putenv(const_cast<TQCString&>(*it).data()); + } + unsetenv("TDE_FULL_SESSION"); + + // set temporarily LC_ALL to C, for su (to be able to parse "Password:") + const char* old_lc_all = getenv( "LC_ALL" ); + if( old_lc_all != NULL ) + setenv( "KDESU_LC_ALL", old_lc_all, 1 ); + else + unsetenv( "KDESU_LC_ALL" ); + setenv("LC_ALL", "C", 1); + + // From now on, terminal output goes through the tty. + + TQCString path; + if (command.contains('/')) + path = command; + else + { + TQString file = KStandardDirs::findExe(command); + if (file.isEmpty()) + { + kdError(900) << k_lineinfo << command << " not found\n"; + _exit(1); + } + path = TQFile::encodeName(file); + } + + const char **argp = (const char **)malloc((args.count()+2)*sizeof(char *)); + int i = 0; + argp[i++] = path; + for (QCStringList::ConstIterator it=args.begin(); it!=args.end(); ++it) + argp[i++] = *it; + + argp[i] = 0L; + + execv(path, (char * const *)argp); + kdError(900) << k_lineinfo << "execv(\"" << path << "\"): " << perror << "\n"; + _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() +{ + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(900) << k_lineinfo << "Could not open slave tty.\n"; + return -1; + } + + kdDebug(900) << k_lineinfo << "Child pid " << m_Pid << endl; + + struct termios tio; + while (1) + { + if (!checkPid(m_Pid)) + { + close(slave); + return -1; + } + if (tcgetattr(slave, &tio) < 0) + { + kdError(900) << k_lineinfo << "tcgetattr(): " << perror << "\n"; + close(slave); + return -1; + } + if (tio.c_lflag & ECHO) + { + kdDebug(900) << k_lineinfo << "Echo mode still on.\n"; + waitMS(slave,100); + continue; + } + break; + } + close(slave); + return 0; +} + + +int PtyProcess::enableLocalEcho(bool enable) +{ + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(900) << k_lineinfo << "Could not open slave tty.\n"; + return -1; + } + struct termios tio; + if (tcgetattr(slave, &tio) < 0) + { + kdError(900) << k_lineinfo << "tcgetattr(): " << perror << "\n"; + close(slave); return -1; + } + if (enable) + tio.c_lflag |= ECHO; + else + tio.c_lflag &= ~ECHO; + if (tcsetattr(slave, TCSANOW, &tio) < 0) + { + kdError(900) << k_lineinfo << "tcsetattr(): " << perror << "\n"; + close(slave); return -1; + } + close(slave); + return 0; +} + + +/* + * Copy output to stdout until the child process exists, 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() +{ + int retval = 1; + + fd_set fds; + FD_ZERO(&fds); + + while (1) + { + FD_SET(m_Fd, &fds); + int ret = select(m_Fd+1, &fds, 0L, 0L, 0L); + if (ret == -1) + { + if (errno != EINTR) + { + kdError(900) << k_lineinfo << "select(): " << perror << "\n"; + return -1; + } + ret = 0; + } + + if (ret) + { + TQCString output = readAll(false); + bool lineStart = true; + while (!output.isNull()) + { + if (!m_Exit.isEmpty()) + { + // match exit string only at line starts + int pos = output.find(m_Exit.data()); + if ((pos >= 0) && ((pos == 0 && lineStart) || (output.at (pos - 1) == '\n'))) + { + kill(m_Pid, SIGTERM); + } + } + if (m_bTerminal) + { + fputs(output, stdout); + fflush(stdout); + } + lineStart = output.tqat( output.length() - 1 ) == '\n'; + output = readAll(false); + } + } + + ret = checkPidExited(m_Pid); + if (ret == Error) + { + if (errno == ECHILD) retval = 0; + else retval = 1; + break; + } + else if (ret == Killed) + { + retval = 0; + break; + } + else if (ret == NotExited) + { + // keep checking + } + else + { + retval = ret; + break; + } + } + return retval; +} + +/* + * 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(int fd) +{ + // Reset signal handlers + for (int sig = 1; sig < NSIG; sig++) + signal(sig, SIG_DFL); + signal(SIGHUP, SIG_IGN); + + // Close all file handles + struct rlimit rlp; + getrlimit(RLIMIT_NOFILE, &rlp); + for (int i = 0; i < (int)rlp.rlim_cur; i++) + if (i != fd) close(i); + + // Create a new session. + setsid(); + + // Open slave. This will make it our controlling terminal + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(900) << k_lineinfo << "Could not open slave side: " << perror << "\n"; + return -1; + } + close(fd); + +#if defined(__SVR4) && defined(sun) + + // Solaris STREAMS environment. + // Push these modules to make the stream look like a terminal. + ioctl(slave, I_PUSH, "ptem"); + ioctl(slave, I_PUSH, "ldterm"); + +#endif + +#ifdef TIOCSCTTY + ioctl(slave, TIOCSCTTY, NULL); +#endif + + // Connect stdin, stdout and stderr + dup2(slave, 0); dup2(slave, 1); dup2(slave, 2); + if (slave > 2) + close(slave); + + // Disable OPOST processing. Otherwise, '\n' are (on Linux at least) + // translated to '\r\n'. + struct termios tio; + if (tcgetattr(0, &tio) < 0) + { + kdError(900) << k_lineinfo << "tcgetattr(): " << perror << "\n"; + return -1; + } + tio.c_oflag &= ~OPOST; + if (tcsetattr(0, TCSANOW, &tio) < 0) + { + kdError(900) << k_lineinfo << "tcsetattr(): " << perror << "\n"; + return -1; + } + + return 0; +} + +void PtyProcess::virtual_hook( int, void* ) +{ /*BASE::virtual_hook( id, data );*/ } |