File indexing completed on 2025-03-09 06:45:40
0001 /* 0002 This file is part of the KDE project, module kdesu. 0003 SPDX-FileCopyrightText: 1999, 2000 Geert Jansen <jansen@kde.org> 0004 0005 This file contains code from TEShell.C of the KDE konsole. 0006 SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de> 0007 0008 SPDX-License-Identifier: GPL-2.0-only 0009 0010 process.cpp: Functionality to build a front end to password asking terminal programs. 0011 */ 0012 0013 #include "ptyprocess.h" 0014 #include "kcookie_p.h" 0015 #include "ptyprocess_p.h" 0016 0017 #include <config-kdesu.h> 0018 #include <ksu_debug.h> 0019 0020 #include <cerrno> 0021 #include <fcntl.h> 0022 #include <signal.h> 0023 #include <stdlib.h> 0024 #include <termios.h> 0025 #include <unistd.h> 0026 0027 #include <sys/resource.h> 0028 #include <sys/stat.h> 0029 #include <sys/time.h> 0030 #include <sys/wait.h> 0031 0032 #if HAVE_SYS_SELECT_H 0033 #include <sys/select.h> // Needed on some systems. 0034 #endif 0035 0036 #include <QFile> 0037 #include <QStandardPaths> 0038 0039 #include <KConfigGroup> 0040 #include <KSharedConfig> 0041 0042 extern int kdesuDebugArea(); 0043 0044 namespace KDESu 0045 { 0046 using namespace KDESuPrivate; 0047 0048 /* 0049 ** Wait for @p ms milliseconds 0050 ** @param fd file descriptor 0051 ** @param ms time to wait in milliseconds 0052 ** @return 0053 */ 0054 int PtyProcess::waitMS(int fd, int ms) 0055 { 0056 struct timeval tv; 0057 tv.tv_sec = 0; 0058 tv.tv_usec = 1000 * ms; 0059 0060 fd_set fds; 0061 FD_ZERO(&fds); 0062 FD_SET(fd, &fds); 0063 return select(fd + 1, &fds, nullptr, nullptr, &tv); 0064 } 0065 0066 // XXX this function is nonsense: 0067 // - for our child, we could use waitpid(). 0068 // - the configurability at this place it *complete* braindamage 0069 /* 0070 ** Basic check for the existence of @p pid. 0071 ** Returns true iff @p pid is an extant process. 0072 */ 0073 bool PtyProcess::checkPid(pid_t pid) 0074 { 0075 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0076 KConfigGroup cg(config, QStringLiteral("super-user-command")); 0077 QString superUserCommand = cg.readEntry("super-user-command", "sudo"); 0078 // sudo does not accept signals from user so we except it 0079 if (superUserCommand == QLatin1String("sudo")) { 0080 return true; 0081 } else { 0082 return kill(pid, 0) == 0; 0083 } 0084 } 0085 0086 /* 0087 ** Check process exit status for process @p pid. 0088 ** On error (no child, no exit), return Error (-1). 0089 ** If child @p pid has exited, return its exit status, 0090 ** (which may be zero). 0091 ** If child @p has not exited, return NotExited (-2). 0092 */ 0093 int PtyProcess::checkPidExited(pid_t pid) 0094 { 0095 int state; 0096 int ret; 0097 ret = waitpid(pid, &state, WNOHANG); 0098 0099 if (ret < 0) { 0100 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " 0101 << "waitpid():" << strerror(errno); 0102 return Error; 0103 } 0104 if (ret == pid) { 0105 if (WIFEXITED(state)) { 0106 return WEXITSTATUS(state); 0107 } 0108 0109 return Killed; 0110 } 0111 0112 return NotExited; 0113 } 0114 0115 PtyProcess::PtyProcess() 0116 : PtyProcess(*new PtyProcessPrivate) 0117 { 0118 } 0119 0120 PtyProcess::PtyProcess(PtyProcessPrivate &dd) 0121 : d_ptr(&dd) 0122 { 0123 m_terminal = false; 0124 m_erase = false; 0125 } 0126 0127 PtyProcess::~PtyProcess() = default; 0128 0129 int PtyProcess::init() 0130 { 0131 Q_D(PtyProcess); 0132 0133 delete d->pty; 0134 d->pty = new KPty(); 0135 if (!d->pty->open()) { 0136 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " 0137 << "Failed to open PTY."; 0138 return -1; 0139 } 0140 if (!d->wantLocalEcho) { 0141 enableLocalEcho(false); 0142 } 0143 d->inputBuffer.resize(0); 0144 return 0; 0145 } 0146 0147 /** Set additional environment variables. */ 0148 void PtyProcess::setEnvironment(const QList<QByteArray> &env) 0149 { 0150 Q_D(PtyProcess); 0151 0152 d->env = env; 0153 } 0154 0155 int PtyProcess::fd() const 0156 { 0157 Q_D(const PtyProcess); 0158 0159 return d->pty ? d->pty->masterFd() : -1; 0160 } 0161 0162 int PtyProcess::pid() const 0163 { 0164 return m_pid; 0165 } 0166 0167 /** Returns the additional environment variables set by setEnvironment() */ 0168 QList<QByteArray> PtyProcess::environment() const 0169 { 0170 Q_D(const PtyProcess); 0171 0172 return d->env; 0173 } 0174 0175 QByteArray PtyProcess::readAll(bool block) 0176 { 0177 Q_D(PtyProcess); 0178 0179 QByteArray ret; 0180 if (!d->inputBuffer.isEmpty()) { 0181 // if there is still something in the buffer, we need not block. 0182 // we should still try to read any further output, from the fd, though. 0183 block = false; 0184 ret = d->inputBuffer; 0185 d->inputBuffer.resize(0); 0186 } 0187 0188 int flags = fcntl(fd(), F_GETFL); 0189 if (flags < 0) { 0190 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " 0191 << "fcntl(F_GETFL):" << strerror(errno); 0192 return ret; 0193 } 0194 int oflags = flags; 0195 if (block) { 0196 flags &= ~O_NONBLOCK; 0197 } else { 0198 flags |= O_NONBLOCK; 0199 } 0200 0201 if ((flags != oflags) && (fcntl(fd(), F_SETFL, flags) < 0)) { 0202 // We get an error here when the child process has closed 0203 // the file descriptor already. 0204 return ret; 0205 } 0206 0207 while (1) { 0208 ret.reserve(ret.size() + 0x8000); 0209 int nbytes = read(fd(), ret.data() + ret.size(), 0x8000); 0210 if (nbytes == -1) { 0211 if (errno == EINTR) { 0212 continue; 0213 } else { 0214 break; 0215 } 0216 } 0217 if (nbytes == 0) { 0218 break; // nothing available / eof 0219 } 0220 0221 ret.resize(ret.size() + nbytes); 0222 break; 0223 } 0224 0225 return ret; 0226 } 0227 0228 QByteArray PtyProcess::readLine(bool block) 0229 { 0230 Q_D(PtyProcess); 0231 0232 d->inputBuffer = readAll(block); 0233 0234 int pos; 0235 QByteArray ret; 0236 if (!d->inputBuffer.isEmpty()) { 0237 pos = d->inputBuffer.indexOf('\n'); 0238 if (pos == -1) { 0239 // NOTE: this means we return something even if there in no full line! 0240 ret = d->inputBuffer; 0241 d->inputBuffer.resize(0); 0242 } else { 0243 ret = d->inputBuffer.left(pos); 0244 d->inputBuffer.remove(0, pos + 1); 0245 } 0246 } 0247 0248 return ret; 0249 } 0250 0251 void PtyProcess::writeLine(const QByteArray &line, bool addnl) 0252 { 0253 if (!line.isEmpty()) { 0254 write(fd(), line.constData(), line.length()); 0255 } 0256 if (addnl) { 0257 write(fd(), "\n", 1); 0258 } 0259 } 0260 0261 void PtyProcess::unreadLine(const QByteArray &line, bool addnl) 0262 { 0263 Q_D(PtyProcess); 0264 0265 QByteArray tmp = line; 0266 if (addnl) { 0267 tmp += '\n'; 0268 } 0269 if (!tmp.isEmpty()) { 0270 d->inputBuffer.prepend(tmp); 0271 } 0272 } 0273 0274 void PtyProcess::setExitString(const QByteArray &exit) 0275 { 0276 m_exitString = exit; 0277 } 0278 0279 /* 0280 * Fork and execute the command. This returns in the parent. 0281 */ 0282 int PtyProcess::exec(const QByteArray &command, const QList<QByteArray> &args) 0283 { 0284 Q_D(PtyProcess); 0285 0286 int i; 0287 0288 if (init() < 0) { 0289 return -1; 0290 } 0291 0292 if ((m_pid = fork()) == -1) { 0293 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " 0294 << "fork():" << strerror(errno); 0295 return -1; 0296 } 0297 0298 // Parent 0299 if (m_pid) { 0300 d->pty->closeSlave(); 0301 return 0; 0302 } 0303 0304 // Child 0305 if (setupTTY() < 0) { 0306 _exit(1); 0307 } 0308 0309 for (const QByteArray &var : std::as_const(d->env)) { 0310 putenv(const_cast<char *>(var.constData())); 0311 } 0312 unsetenv("KDE_FULL_SESSION"); 0313 // for : Qt: Session management error 0314 unsetenv("SESSION_MANAGER"); 0315 // QMutex::lock , deadlocks without that. 0316 // <thiago> you cannot connect to the user's session bus from another UID 0317 unsetenv("DBUS_SESSION_BUS_ADDRESS"); 0318 0319 // set temporarily LC_ALL to C, for su (to be able to parse "Password:") 0320 const QByteArray old_lc_all = qgetenv("LC_ALL"); 0321 if (!old_lc_all.isEmpty()) { 0322 qputenv("KDESU_LC_ALL", old_lc_all); 0323 } else { 0324 unsetenv("KDESU_LC_ALL"); 0325 } 0326 qputenv("LC_ALL", "C"); 0327 0328 // From now on, terminal output goes through the tty. 0329 0330 QByteArray path; 0331 if (command.contains('/')) { 0332 path = command; 0333 } else { 0334 QString file = QStandardPaths::findExecutable(QFile::decodeName(command)); 0335 if (file.isEmpty()) { 0336 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " << command << "not found."; 0337 _exit(1); 0338 } 0339 path = QFile::encodeName(file); 0340 } 0341 0342 const char **argp = (const char **)malloc((args.count() + 2) * sizeof(char *)); 0343 0344 i = 0; 0345 argp[i++] = path.constData(); 0346 for (const QByteArray &arg : args) { 0347 argp[i++] = arg.constData(); 0348 } 0349 0350 argp[i] = nullptr; 0351 0352 execv(path.constData(), const_cast<char **>(argp)); 0353 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " 0354 << "execv(" << path << "):" << strerror(errno); 0355 _exit(1); 0356 return -1; // Shut up compiler. Never reached. 0357 } 0358 0359 /* 0360 * Wait until the terminal is set into no echo mode. At least one su 0361 * (RH6 w/ Linux-PAM patches) sets noecho mode AFTER writing the Password: 0362 * prompt, using TCSAFLUSH. This flushes the terminal I/O queues, possibly 0363 * taking the password with it. So we wait until no echo mode is set 0364 * before writing the password. 0365 * Note that this is done on the slave fd. While Linux allows tcgetattr() on 0366 * the master side, Solaris doesn't. 0367 */ 0368 int PtyProcess::waitSlave() 0369 { 0370 Q_D(PtyProcess); 0371 0372 struct termios tio; 0373 while (1) { 0374 if (!checkPid(m_pid)) { 0375 qCCritical(KSU_LOG) << "process has exited while waiting for password."; 0376 return -1; 0377 } 0378 if (!d->pty->tcGetAttr(&tio)) { 0379 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " 0380 << "tcgetattr():" << strerror(errno); 0381 return -1; 0382 } 0383 if (tio.c_lflag & ECHO) { 0384 // qDebug() << "[" << __FILE__ << ":" << __LINE__ << "] " << "Echo mode still on."; 0385 usleep(10000); 0386 continue; 0387 } 0388 break; 0389 } 0390 return 0; 0391 } 0392 0393 int PtyProcess::enableLocalEcho(bool enable) 0394 { 0395 Q_D(PtyProcess); 0396 0397 d->wantLocalEcho = enable; 0398 if (!d->pty) { 0399 // Apply it on init 0400 return 0; 0401 } 0402 0403 return d->pty->setEcho(enable) ? 0 : -1; 0404 } 0405 0406 void PtyProcess::setTerminal(bool terminal) 0407 { 0408 m_terminal = terminal; 0409 } 0410 0411 void PtyProcess::setErase(bool erase) 0412 { 0413 m_erase = erase; 0414 } 0415 0416 /* 0417 * Copy output to stdout until the child process exits, or a line of output 0418 * matches `m_exitString'. 0419 * We have to use waitpid() to test for exit. Merely waiting for EOF on the 0420 * pty does not work, because the target process may have children still 0421 * attached to the terminal. 0422 */ 0423 int PtyProcess::waitForChild() 0424 { 0425 fd_set fds; 0426 FD_ZERO(&fds); 0427 QByteArray remainder; 0428 0429 while (1) { 0430 FD_SET(fd(), &fds); 0431 0432 // specify timeout to make sure select() does not block, even if the 0433 // process is dead / non-responsive. It does not matter if we abort too 0434 // early. In that case 0 is returned, and we'll try again in the next 0435 // iteration. (As long as we don't consistently time out in each iteration) 0436 timeval timeout; 0437 timeout.tv_sec = 0; 0438 timeout.tv_usec = 100000; 0439 int ret = select(fd() + 1, &fds, nullptr, nullptr, &timeout); 0440 if (ret == -1) { 0441 if (errno != EINTR) { 0442 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " 0443 << "select():" << strerror(errno); 0444 return -1; 0445 } 0446 ret = 0; 0447 } 0448 0449 if (ret) { 0450 for (;;) { 0451 QByteArray output = readAll(false); 0452 if (output.isEmpty()) { 0453 break; 0454 } 0455 if (m_terminal) { 0456 fwrite(output.constData(), output.size(), 1, stdout); 0457 fflush(stdout); 0458 } 0459 if (!m_exitString.isEmpty()) { 0460 // match exit string only at line starts 0461 remainder += output; 0462 while (remainder.length() >= m_exitString.length()) { 0463 if (remainder.startsWith(m_exitString)) { 0464 kill(m_pid, SIGTERM); 0465 remainder.remove(0, m_exitString.length()); 0466 } 0467 int off = remainder.indexOf('\n'); 0468 if (off < 0) { 0469 break; 0470 } 0471 remainder.remove(0, off + 1); 0472 } 0473 } 0474 } 0475 } 0476 0477 ret = checkPidExited(m_pid); 0478 if (ret == Error) { 0479 if (errno == ECHILD) { 0480 return 0; 0481 } else { 0482 return 1; 0483 } 0484 } else if (ret == Killed) { 0485 return 0; 0486 } else if (ret == NotExited) { 0487 continue; // keep checking 0488 } else { 0489 return ret; 0490 } 0491 } 0492 } 0493 0494 /* 0495 * SetupTTY: Creates a new session. The filedescriptor "fd" should be 0496 * connected to the tty. It is closed after the tty is reopened to make it 0497 * our controlling terminal. This way the tty is always opened at least once 0498 * so we'll never get EIO when reading from it. 0499 */ 0500 int PtyProcess::setupTTY() 0501 { 0502 Q_D(PtyProcess); 0503 0504 // Reset signal handlers 0505 for (int sig = 1; sig < NSIG; sig++) { 0506 signal(sig, SIG_DFL); 0507 } 0508 signal(SIGHUP, SIG_IGN); 0509 0510 d->pty->setCTty(); 0511 0512 // Connect stdin, stdout and stderr 0513 int slave = d->pty->slaveFd(); 0514 dup2(slave, 0); 0515 dup2(slave, 1); 0516 dup2(slave, 2); 0517 0518 // Close all file handles 0519 // XXX this caused problems in KProcess - not sure why anymore. -- ??? 0520 // Because it will close the start notification pipe. -- ossi 0521 struct rlimit rlp; 0522 getrlimit(RLIMIT_NOFILE, &rlp); 0523 for (int i = 3; i < (int)rlp.rlim_cur; i++) { 0524 close(i); 0525 } 0526 0527 // Disable OPOST processing. Otherwise, '\n' are (on Linux at least) 0528 // translated to '\r\n'. 0529 struct ::termios tio; 0530 if (tcgetattr(0, &tio) < 0) { 0531 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " 0532 << "tcgetattr():" << strerror(errno); 0533 return -1; 0534 } 0535 tio.c_oflag &= ~OPOST; 0536 if (tcsetattr(0, TCSANOW, &tio) < 0) { 0537 qCCritical(KSU_LOG) << "[" << __FILE__ << ":" << __LINE__ << "] " 0538 << "tcsetattr():" << strerror(errno); 0539 return -1; 0540 } 0541 0542 return 0; 0543 } 0544 0545 void PtyProcess::virtual_hook(int id, void *data) 0546 { 0547 Q_UNUSED(id); 0548 Q_UNUSED(data); 0549 /*BASE::virtual_hook( id, data );*/ 0550 } 0551 0552 } // namespace KDESu