File indexing completed on 2024-03-24 03:59:30
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 }