File indexing completed on 2024-04-28 07:45:31

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2002 Waldo Bastian <bastian@kde.org>
0004     SPDX-FileCopyrightText: 2002-2003, 2007-2008 Oswald Buddenhagen <ossi@kde.org>
0005     SPDX-FileCopyrightText: 2010 KDE e.V. <kde-ev-board@kde.org>
0006     SPDX-FileContributor: 2010 Adriaan de Groot <groot@kde.org>
0007     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 
0012 #include "kpty_p.h"
0013 
0014 #include <QProcess>
0015 #include <kpty_debug.h>
0016 
0017 // __USE_XOPEN isn't defined by default in ICC
0018 // (needed for ptsname(), grantpt() and unlockpt())
0019 #ifdef __INTEL_COMPILER
0020 #ifndef __USE_XOPEN
0021 #define __USE_XOPEN
0022 #endif
0023 #endif
0024 
0025 #include <sys/ioctl.h>
0026 #include <sys/param.h>
0027 #include <sys/resource.h>
0028 #include <sys/stat.h>
0029 #include <sys/time.h>
0030 #include <sys/types.h>
0031 
0032 #include <cerrno>
0033 #include <fcntl.h>
0034 #include <grp.h>
0035 #include <stdio.h>
0036 #include <stdlib.h>
0037 #include <string.h>
0038 #include <time.h>
0039 #include <unistd.h>
0040 
0041 #if HAVE_PTY_H
0042 #include <pty.h>
0043 #endif
0044 
0045 #if HAVE_LIBUTIL_H
0046 #include <libutil.h>
0047 #elif HAVE_UTIL_H
0048 #include <util.h>
0049 #endif
0050 
0051 #ifdef UTEMPTER_PATH
0052 // utempter uses 'add' and 'del' whereas ulog-helper uses 'login' and 'logout'
0053 #ifndef UTEMPTER_ULOG
0054 #define UTEMPTER_ADD "add"
0055 #define UTEMPTER_DEL "del"
0056 #else
0057 #define UTEMPTER_ADD "login"
0058 #define UTEMPTER_DEL "logout"
0059 #endif
0060 class UtemptProcess : public QProcess
0061 {
0062 public:
0063     UtemptProcess()
0064     {
0065         setChildProcessModifier([this]() {
0066             // These are the file descriptors the utempter helper wants
0067             dup2(cmdFd, 0);
0068             dup2(cmdFd, 1);
0069             dup2(cmdFd, 3);
0070         });
0071     }
0072 
0073     int cmdFd;
0074 };
0075 #else
0076 #include <utmp.h>
0077 #if HAVE_UTMPX
0078 #include <utmpx.h>
0079 #endif
0080 #if !defined(_PATH_UTMPX) && defined(_UTMPX_FILE)
0081 #define _PATH_UTMPX _UTMPX_FILE
0082 #endif
0083 #if !defined(_PATH_WTMPX) && defined(_WTMPX_FILE)
0084 #define _PATH_WTMPX _WTMPX_FILE
0085 #endif
0086 #endif
0087 
0088 /* for HP-UX (some versions) the extern C is needed, and for other
0089    platforms it doesn't hurt */
0090 extern "C" {
0091 #include <termios.h>
0092 #if HAVE_TERMIO_H
0093 #include <termio.h> // struct winsize on some systems
0094 #endif
0095 }
0096 
0097 #if defined(_HPUX_SOURCE)
0098 #define _TERMIOS_INCLUDED
0099 #include <bsdtty.h>
0100 #endif
0101 
0102 #if HAVE_SYS_STROPTS_H
0103 #include <sys/stropts.h> // Defines I_PUSH
0104 #define _NEW_TTY_CTRL
0105 #endif
0106 
0107 #if HAVE_TCGETATTR
0108 #define _tcgetattr(fd, ttmode) tcgetattr(fd, ttmode)
0109 #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__APPLE__) || defined(__DragonFly__)
0110 #define _tcgetattr(fd, ttmode) ioctl(fd, TIOCGETA, (char *)ttmode)
0111 #else
0112 #define _tcgetattr(fd, ttmode) ioctl(fd, TCGETS, (char *)ttmode)
0113 #endif
0114 
0115 #if HAVE_TCSETATTR
0116 #define _tcsetattr(fd, ttmode) tcsetattr(fd, TCSANOW, ttmode)
0117 #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__APPLE__) || defined(__DragonFly__)
0118 #define _tcsetattr(fd, ttmode) ioctl(fd, TIOCSETA, (char *)ttmode)
0119 #else
0120 #define _tcsetattr(fd, ttmode) ioctl(fd, TCSETS, (char *)ttmode)
0121 #endif
0122 
0123 #include <qplatformdefs.h>
0124 
0125 #define TTY_GROUP "tty"
0126 
0127 #ifndef PATH_MAX
0128 #ifdef MAXPATHLEN
0129 #define PATH_MAX MAXPATHLEN
0130 #else
0131 #define PATH_MAX 1024
0132 #endif
0133 #endif
0134 
0135 ///////////////////////
0136 // private functions //
0137 ///////////////////////
0138 
0139 //////////////////
0140 // private data //
0141 //////////////////
0142 
0143 KPtyPrivate::KPtyPrivate(KPty *parent)
0144     : masterFd(-1)
0145     , slaveFd(-1)
0146     , ownMaster(true)
0147     , q_ptr(parent)
0148 {
0149 #ifdef UTEMPTER_PATH
0150     utempterPath = QStringLiteral(UTEMPTER_PATH);
0151 #endif
0152 }
0153 
0154 KPtyPrivate::~KPtyPrivate()
0155 {
0156 }
0157 
0158 #if !HAVE_OPENPTY
0159 bool KPtyPrivate::chownpty(bool grant)
0160 {
0161     return !QProcess::execute(QFile::decodeName(CMAKE_INSTALL_PREFIX "/" KDE_INSTALL_LIBEXECDIR_KF "/kgrantpty"),
0162                               QStringList() << (grant ? "--grant" : "--revoke") << QString::number(masterFd));
0163 }
0164 #endif
0165 
0166 /////////////////////////////
0167 // public member functions //
0168 /////////////////////////////
0169 
0170 KPty::KPty()
0171     : d_ptr(new KPtyPrivate(this))
0172 {
0173 }
0174 
0175 KPty::KPty(KPtyPrivate *d)
0176     : d_ptr(d)
0177 {
0178     d_ptr->q_ptr = this;
0179 }
0180 
0181 KPty::~KPty()
0182 {
0183     close();
0184 }
0185 
0186 bool KPty::open()
0187 {
0188     Q_D(KPty);
0189 
0190     if (d->masterFd >= 0) {
0191         return true;
0192     }
0193 
0194     d->ownMaster = true;
0195 
0196     QByteArray ptyName;
0197 
0198     // Find a master pty that we can open ////////////////////////////////
0199 
0200     // Because not all the pty animals are created equal, they want to
0201     // be opened by several different methods.
0202 
0203     // We try, as we know them, one by one.
0204 
0205 #if HAVE_OPENPTY
0206 
0207     char ptsn[PATH_MAX];
0208     if (::openpty(&d->masterFd, &d->slaveFd, ptsn, nullptr, nullptr)) {
0209         d->masterFd = -1;
0210         d->slaveFd = -1;
0211         qCWarning(KPTY_LOG) << "Can't open a pseudo teletype";
0212         return false;
0213     }
0214     d->ttyName = ptsn;
0215 
0216 #else
0217 
0218 #if HAVE_PTSNAME || defined(TIOCGPTN)
0219 
0220 #if HAVE_POSIX_OPENPT
0221     d->masterFd = ::posix_openpt(O_RDWR | O_NOCTTY);
0222 #elif HAVE_GETPT
0223     d->masterFd = ::getpt();
0224 #elif defined(PTM_DEVICE)
0225     d->masterFd = QT_OPEN(PTM_DEVICE, QT_OPEN_RDWR | O_NOCTTY);
0226 #else
0227 #error No method to open a PTY master detected.
0228 #endif
0229     if (d->masterFd >= 0) {
0230 #if HAVE_PTSNAME
0231         char *ptsn = ptsname(d->masterFd);
0232         if (ptsn) {
0233             d->ttyName = ptsn;
0234 #else
0235         int ptyno;
0236         if (!ioctl(d->masterFd, TIOCGPTN, &ptyno)) {
0237             char buf[32];
0238             sprintf(buf, "/dev/pts/%d", ptyno);
0239             d->ttyName = buf;
0240 #endif
0241 #if HAVE_GRANTPT
0242             if (!grantpt(d->masterFd)) {
0243                 goto grantedpt;
0244             }
0245 #else
0246             goto gotpty;
0247 #endif
0248         }
0249         ::close(d->masterFd);
0250         d->masterFd = -1;
0251     }
0252 #endif // HAVE_PTSNAME || TIOCGPTN
0253 
0254     // Linux device names, FIXME: Trouble on other systems?
0255     for (const char *s3 = "pqrstuvwxyzabcde"; *s3; s3++) {
0256         for (const char *s4 = "0123456789abcdef"; *s4; s4++) {
0257             ptyName = QString().sprintf("/dev/pty%c%c", *s3, *s4).toLatin1();
0258             d->ttyName = QString().sprintf("/dev/tty%c%c", *s3, *s4).toLatin1();
0259 
0260             d->masterFd = QT_OPEN(ptyName.data(), QT_OPEN_RDWR);
0261             if (d->masterFd >= 0) {
0262                 if (!access(d->ttyName.data(), R_OK | W_OK)) { // checks availability based on permission bits
0263                     if (!geteuid()) {
0264                         struct group *p = getgrnam(TTY_GROUP);
0265                         if (!p) {
0266                             p = getgrnam("wheel");
0267                         }
0268                         gid_t gid = p ? p->gr_gid : getgid();
0269 
0270                         chown(d->ttyName.data(), getuid(), gid);
0271                         chmod(d->ttyName.data(), S_IRUSR | S_IWUSR | S_IWGRP);
0272                     }
0273                     goto gotpty;
0274                 }
0275                 ::close(d->masterFd);
0276                 d->masterFd = -1;
0277             }
0278         }
0279     }
0280 
0281     qCWarning(KPTY_LOG) << "Can't open a pseudo teletype";
0282     return false;
0283 
0284 gotpty:
0285     QFileInfo info(d->ttyName.data());
0286     if (!info.exists()) {
0287         return false; // this just cannot happen ... *cough*  Yeah right, I just
0288     }
0289     // had it happen when pty #349 was allocated.  I guess
0290     // there was some sort of leak?  I only had a few open.
0291     constexpr auto perms = QFile::ReadGroup | QFile::ExeGroup | QFile::ReadOther | QFile::WriteOther | QFile::ExeOther;
0292     if ((info.ownerId() != getuid() || (info.permissions() & perms)) //
0293         && !d->chownpty(true)) {
0294         qCWarning(KPTY_LOG) << "chownpty failed for device " << ptyName << "::" << d->ttyName << "\nThis means the communication can be eavesdropped." << endl;
0295     }
0296 
0297 grantedpt:
0298 
0299 #ifdef HAVE_REVOKE
0300     revoke(d->ttyName.data());
0301 #endif
0302 
0303 #ifdef HAVE_UNLOCKPT
0304     unlockpt(d->masterFd);
0305 #elif defined(TIOCSPTLCK)
0306     int flag = 0;
0307     ioctl(d->masterFd, TIOCSPTLCK, &flag);
0308 #endif
0309 
0310     d->slaveFd = QT_OPEN(d->ttyName.data(), QT_OPEN_RDWR | O_NOCTTY);
0311     if (d->slaveFd < 0) {
0312         qCWarning(KPTY_LOG) << "Can't open slave pseudo teletype";
0313         ::close(d->masterFd);
0314         d->masterFd = -1;
0315         return false;
0316     }
0317 
0318 #endif /* HAVE_OPENPTY */
0319 
0320     fcntl(d->masterFd, F_SETFD, FD_CLOEXEC);
0321     fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC);
0322 
0323     return true;
0324 }
0325 
0326 bool KPty::open(int fd)
0327 {
0328 #if !HAVE_PTSNAME && !defined(TIOCGPTN)
0329     qCWarning(KPTY_LOG) << "Unsupported attempt to open pty with fd" << fd;
0330     return false;
0331 #else
0332     Q_D(KPty);
0333 
0334     if (d->masterFd >= 0) {
0335         qCWarning(KPTY_LOG) << "Attempting to open an already open pty";
0336         return false;
0337     }
0338 
0339     d->ownMaster = false;
0340 
0341 #if HAVE_PTSNAME
0342     char *ptsn = ptsname(fd);
0343     if (ptsn) {
0344         d->ttyName = ptsn;
0345 #else
0346     int ptyno;
0347     if (!ioctl(fd, TIOCGPTN, &ptyno)) {
0348         char buf[32];
0349         sprintf(buf, "/dev/pts/%d", ptyno);
0350         d->ttyName = buf;
0351 #endif
0352     } else {
0353         qCWarning(KPTY_LOG) << "Failed to determine pty slave device for fd" << fd;
0354         return false;
0355     }
0356 
0357     d->masterFd = fd;
0358     if (!openSlave()) {
0359         d->masterFd = -1;
0360         return false;
0361     }
0362 
0363     return true;
0364 #endif
0365 }
0366 
0367 void KPty::closeSlave()
0368 {
0369     Q_D(KPty);
0370 
0371     if (d->slaveFd < 0) {
0372         return;
0373     }
0374     ::close(d->slaveFd);
0375     d->slaveFd = -1;
0376 }
0377 
0378 bool KPty::openSlave()
0379 {
0380     Q_D(KPty);
0381 
0382     if (d->slaveFd >= 0) {
0383         return true;
0384     }
0385     if (d->masterFd < 0) {
0386         qCWarning(KPTY_LOG) << "Attempting to open pty slave while master is closed";
0387         return false;
0388     }
0389     d->slaveFd = QT_OPEN(d->ttyName.data(), QT_OPEN_RDWR | O_NOCTTY);
0390     if (d->slaveFd < 0) {
0391         qCWarning(KPTY_LOG) << "Can't open slave pseudo teletype";
0392         return false;
0393     }
0394     fcntl(d->slaveFd, F_SETFD, FD_CLOEXEC);
0395     return true;
0396 }
0397 
0398 void KPty::close()
0399 {
0400     Q_D(KPty);
0401 
0402     if (d->masterFd < 0) {
0403         return;
0404     }
0405     closeSlave();
0406     if (d->ownMaster) {
0407 #if !HAVE_OPENPTY
0408         // don't bother resetting unix98 pty, it will go away after closing master anyway.
0409         if (memcmp(d->ttyName.data(), "/dev/pts/", 9)) {
0410             if (!geteuid()) {
0411                 struct stat st;
0412                 if (!stat(d->ttyName.data(), &st)) {
0413                     chown(d->ttyName.data(), 0, st.st_gid == getgid() ? 0 : -1);
0414                     chmod(d->ttyName.data(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
0415                 }
0416             } else {
0417                 fcntl(d->masterFd, F_SETFD, 0);
0418                 d->chownpty(false);
0419             }
0420         }
0421 #endif
0422         ::close(d->masterFd);
0423     }
0424     d->masterFd = -1;
0425 }
0426 
0427 void KPty::setCTty()
0428 {
0429     Q_D(KPty);
0430 
0431     if (!d->withCTty) {
0432         return;
0433     }
0434 
0435     // Setup job control //////////////////////////////////
0436 
0437     // Become session leader, process group leader,
0438     // and get rid of the old controlling terminal.
0439     setsid();
0440 
0441     // make our slave pty the new controlling terminal.
0442     ioctl(d->slaveFd, TIOCSCTTY, 0);
0443 
0444     // make our new process group the foreground group on the pty
0445     int pgrp = getpid();
0446     tcsetpgrp(d->slaveFd, pgrp);
0447 }
0448 
0449 void KPty::login(const char *user, const char *remotehost)
0450 {
0451 #ifdef UTEMPTER_PATH
0452     Q_D(KPty);
0453 
0454     Q_UNUSED(user);
0455 
0456     // Emulating libutempter version 1.1.6
0457     if (!d->utempterPath.isEmpty()) {
0458         UtemptProcess utemptProcess;
0459         utemptProcess.cmdFd = d->masterFd;
0460         utemptProcess.setProgram(d->utempterPath);
0461         utemptProcess.setArguments(QStringList() << QStringLiteral(UTEMPTER_ADD) << QString::fromLocal8Bit(remotehost));
0462         utemptProcess.setProcessChannelMode(QProcess::ForwardedChannels);
0463         utemptProcess.start();
0464         utemptProcess.waitForFinished();
0465     }
0466 
0467 #else
0468 #if HAVE_UTMPX
0469     struct utmpx l_struct;
0470 #else
0471     struct utmp l_struct;
0472 #endif
0473     memset(&l_struct, 0, sizeof(l_struct));
0474     // note: strncpy without terminators _is_ correct here. man 4 utmp
0475 
0476     if (user) {
0477         strncpy(l_struct.ut_name, user, sizeof(l_struct.ut_name));
0478     }
0479 
0480     if (remotehost) {
0481         strncpy(l_struct.ut_host, remotehost, sizeof(l_struct.ut_host));
0482 #if HAVE_STRUCT_UTMP_UT_SYSLEN
0483         l_struct.ut_syslen = qMin(strlen(remotehost), sizeof(l_struct.ut_host));
0484 #endif
0485     }
0486 
0487 #ifndef __GLIBC__
0488     Q_D(KPty);
0489     const char *str_ptr = d->ttyName.data();
0490     if (!memcmp(str_ptr, "/dev/", 5)) {
0491         str_ptr += 5;
0492     }
0493     strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line));
0494 #if HAVE_STRUCT_UTMP_UT_ID
0495     strncpy(l_struct.ut_id, str_ptr + strlen(str_ptr) - sizeof(l_struct.ut_id), sizeof(l_struct.ut_id));
0496 #endif
0497 #endif
0498 
0499 #if HAVE_UTMPX
0500     gettimeofday(&l_struct.ut_tv, 0);
0501 #else
0502     l_struct.ut_time = time(0);
0503 #endif
0504 
0505 #if HAVE_LOGIN
0506 #if HAVE_LOGINX
0507     ::loginx(&l_struct);
0508 #else
0509     ::login(&l_struct);
0510 #endif
0511 #else
0512 #if HAVE_STRUCT_UTMP_UT_TYPE
0513     l_struct.ut_type = USER_PROCESS;
0514 #endif
0515 #if HAVE_STRUCT_UTMP_UT_PID
0516     l_struct.ut_pid = getpid();
0517 #if HAVE_STRUCT_UTMP_UT_SESSION
0518     l_struct.ut_session = getsid(0);
0519 #endif
0520 #endif
0521 #if HAVE_UTMPX
0522     utmpxname(_PATH_UTMPX);
0523     setutxent();
0524     pututxline(&l_struct);
0525     endutxent();
0526     updwtmpx(_PATH_WTMPX, &l_struct);
0527 #else
0528     utmpname(_PATH_UTMP);
0529     setutent();
0530     pututline(&l_struct);
0531     endutent();
0532     updwtmp(_PATH_WTMP, &l_struct);
0533 #endif
0534 #endif
0535 #endif
0536 }
0537 
0538 void KPty::logout()
0539 {
0540 #ifdef UTEMPTER_PATH
0541     Q_D(KPty);
0542 
0543     // Emulating libutempter version 1.1.6
0544     if (!d->utempterPath.isEmpty()) {
0545         UtemptProcess utemptProcess;
0546         utemptProcess.cmdFd = d->masterFd;
0547         utemptProcess.setProgram(d->utempterPath);
0548         utemptProcess.setArguments(QStringList(QStringLiteral(UTEMPTER_DEL)));
0549         utemptProcess.setProcessChannelMode(QProcess::ForwardedChannels);
0550         utemptProcess.start();
0551         utemptProcess.waitForFinished();
0552     }
0553 
0554 #else
0555     Q_D(KPty);
0556 
0557     const char *str_ptr = d->ttyName.data();
0558     if (!memcmp(str_ptr, "/dev/", 5)) {
0559         str_ptr += 5;
0560     }
0561 #ifdef __GLIBC__
0562     else {
0563         const char *sl_ptr = strrchr(str_ptr, '/');
0564         if (sl_ptr) {
0565             str_ptr = sl_ptr + 1;
0566         }
0567     }
0568 #endif
0569 #if HAVE_LOGIN
0570 #if HAVE_LOGINX
0571     ::logoutx(str_ptr, 0, DEAD_PROCESS);
0572 #else
0573     ::logout(str_ptr);
0574 #endif
0575 #else
0576 #if HAVE_UTMPX
0577     struct utmpx l_struct, *ut;
0578 #else
0579     struct utmp l_struct, *ut;
0580 #endif
0581     memset(&l_struct, 0, sizeof(l_struct));
0582 
0583     strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line));
0584 
0585 #if HAVE_UTMPX
0586     utmpxname(_PATH_UTMPX);
0587     setutxent();
0588     if ((ut = getutxline(&l_struct))) {
0589 #else
0590     utmpname(_PATH_UTMP);
0591     setutent();
0592     if ((ut = getutline(&l_struct))) {
0593 #endif
0594         memset(ut->ut_name, 0, sizeof(*ut->ut_name));
0595         memset(ut->ut_host, 0, sizeof(*ut->ut_host));
0596 #if HAVE_STRUCT_UTMP_UT_SYSLEN
0597         ut->ut_syslen = 0;
0598 #endif
0599 #if HAVE_STRUCT_UTMP_UT_TYPE
0600         ut->ut_type = DEAD_PROCESS;
0601 #endif
0602 #if HAVE_UTMPX
0603         gettimeofday(&(ut->ut_tv), 0);
0604         pututxline(ut);
0605     }
0606     endutxent();
0607 #else
0608         ut->ut_time = time(0);
0609         pututline(ut);
0610     }
0611     endutent();
0612 #endif
0613 #endif
0614 #endif
0615 }
0616 
0617 bool KPty::tcGetAttr(struct ::termios *ttmode) const
0618 {
0619     Q_D(const KPty);
0620 
0621     return _tcgetattr(d->masterFd, ttmode) == 0;
0622 }
0623 
0624 bool KPty::tcSetAttr(struct ::termios *ttmode)
0625 {
0626     Q_D(KPty);
0627 
0628     return _tcsetattr(d->masterFd, ttmode) == 0;
0629 }
0630 
0631 bool KPty::setWinSize(int lines, int columns, int height, int width)
0632 {
0633     Q_D(KPty);
0634 
0635     struct winsize winSize;
0636     winSize.ws_row = (unsigned short)lines;
0637     winSize.ws_col = (unsigned short)columns;
0638     winSize.ws_ypixel = (unsigned short)height;
0639     winSize.ws_xpixel = (unsigned short)width;
0640     return ioctl(d->masterFd, TIOCSWINSZ, (char *)&winSize) == 0;
0641 }
0642 
0643 bool KPty::setWinSize(int lines, int columns)
0644 {
0645     return setWinSize(lines, columns, 0, 0);
0646 }
0647 
0648 bool KPty::setEcho(bool echo)
0649 {
0650     struct ::termios ttmode;
0651     if (!tcGetAttr(&ttmode)) {
0652         return false;
0653     }
0654     if (!echo) {
0655         ttmode.c_lflag &= ~ECHO;
0656     } else {
0657         ttmode.c_lflag |= ECHO;
0658     }
0659     return tcSetAttr(&ttmode);
0660 }
0661 
0662 const char *KPty::ttyName() const
0663 {
0664     Q_D(const KPty);
0665 
0666     return d->ttyName.data();
0667 }
0668 
0669 int KPty::masterFd() const
0670 {
0671     Q_D(const KPty);
0672 
0673     return d->masterFd;
0674 }
0675 
0676 int KPty::slaveFd() const
0677 {
0678     Q_D(const KPty);
0679 
0680     return d->slaveFd;
0681 }
0682 
0683 void KPty::setCTtyEnabled(bool enable)
0684 {
0685     Q_D(KPty);
0686 
0687     d->withCTty = enable;
0688 }