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 }