File indexing completed on 2024-04-21 11:35:21

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