1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2002 Waldo Bastian <bastian@kde.org>
4 SPDX-FileCopyrightText: 2002-2003, 2007-2008 Oswald Buddenhagen <ossi@kde.org>
5 SPDX-FileCopyrightText: 2010 KDE e.V. <kde-ev-board@kde.org>
6 SPDX-FileContributor: 2010 Adriaan de Groot <groot@kde.org>
7 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
8
9 SPDX-License-Identifier: LGPL-2.0-or-later
10*/
11
12#include "kpty_p.h"
13
14#include <QProcess>
15#include <kpty_debug.h>
16
17// __USE_XOPEN isn't defined by default in ICC
18// (needed for ptsname(), grantpt() and unlockpt())
19#ifdef __INTEL_COMPILER
20#ifndef __USE_XOPEN
21#define __USE_XOPEN
22#endif
23#endif
24
25#include <sys/ioctl.h>
26#include <sys/param.h>
27#include <sys/resource.h>
28#include <sys/stat.h>
29#include <sys/time.h>
30#include <sys/types.h>
31
32#include <cerrno>
33#include <fcntl.h>
34#include <grp.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <time.h>
39#include <unistd.h>
40
41#if HAVE_PTY_H
42#include <pty.h>
43#endif
44
45#if HAVE_LIBUTIL_H
46#include <libutil.h>
47#elif HAVE_UTIL_H
48#include <util.h>
49#endif
50
51#ifdef UTEMPTER_PATH
52// utempter uses 'add' and 'del' whereas ulog-helper uses 'login' and 'logout'
53#ifndef UTEMPTER_ULOG
54#define UTEMPTER_ADD "add"
55#define UTEMPTER_DEL "del"
56#else
57#define UTEMPTER_ADD "login"
58#define UTEMPTER_DEL "logout"
59#endif
60class UtemptProcess : public QProcess
61{
62public:
63 UtemptProcess()
64 {
65 setChildProcessModifier([this]() {
66 // These are the file descriptors the utempter helper wants
67 dup2(cmdFd, 0);
68 dup2(cmdFd, 1);
69 dup2(cmdFd, 3);
70 });
71 }
72
73 int cmdFd;
74};
75#else
76#include <utmp.h>
77#if HAVE_UTMPX
78#include <utmpx.h>
79#endif
80#if !defined(_PATH_UTMPX) && defined(_UTMPX_FILE)
81#define _PATH_UTMPX _UTMPX_FILE
82#endif
83#if !defined(_PATH_WTMPX) && defined(_WTMPX_FILE)
84#define _PATH_WTMPX _WTMPX_FILE
85#endif
86#endif
87
88/* for HP-UX (some versions) the extern C is needed, and for other
89 platforms it doesn't hurt */
90extern "C" {
91#include <termios.h>
92#if HAVE_TERMIO_H
93#include <termio.h> // struct winsize on some systems
94#endif
95}
96
97#if defined(_HPUX_SOURCE)
98#define _TERMIOS_INCLUDED
99#include <bsdtty.h>
100#endif
101
102#if HAVE_SYS_STROPTS_H
103#include <sys/stropts.h> // Defines I_PUSH
104#define _NEW_TTY_CTRL
105#endif
106
107#if HAVE_TCGETATTR
108#define _tcgetattr(fd, ttmode) tcgetattr(fd, ttmode)
109#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__APPLE__) || defined(__DragonFly__)
110#define _tcgetattr(fd, ttmode) ioctl(fd, TIOCGETA, (char *)ttmode)
111#else
112#define _tcgetattr(fd, ttmode) ioctl(fd, TCGETS, (char *)ttmode)
113#endif
114
115#if HAVE_TCSETATTR
116#define _tcsetattr(fd, ttmode) tcsetattr(fd, TCSANOW, ttmode)
117#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__APPLE__) || defined(__DragonFly__)
118#define _tcsetattr(fd, ttmode) ioctl(fd, TIOCSETA, (char *)ttmode)
119#else
120#define _tcsetattr(fd, ttmode) ioctl(fd, TCSETS, (char *)ttmode)
121#endif
122
123#include <qplatformdefs.h>
124
125#define TTY_GROUP "tty"
126
127#ifndef PATH_MAX
128#ifdef MAXPATHLEN
129#define PATH_MAX MAXPATHLEN
130#else
131#define PATH_MAX 1024
132#endif
133#endif
134
135///////////////////////
136// private functions //
137///////////////////////
138
139//////////////////
140// private data //
141//////////////////
142
143KPtyPrivate::KPtyPrivate(KPty *parent)
144 : masterFd(-1)
145 , slaveFd(-1)
146 , ownMaster(true)
147 , q_ptr(parent)
148{
149#ifdef UTEMPTER_PATH
150 utempterPath = QStringLiteral(UTEMPTER_PATH);
151#endif
152}
153
154KPtyPrivate::~KPtyPrivate()
155{
156}
157
158#if !HAVE_OPENPTY
159bool KPtyPrivate::chownpty(bool grant)
160{
161 return !QProcess::execute(QFile::decodeName(CMAKE_INSTALL_PREFIX "/" KDE_INSTALL_LIBEXECDIR_KF "/kgrantpty"),
162 QStringList() << (grant ? "--grant" : "--revoke") << QString::number(masterFd));
163}
164#endif
165
166/////////////////////////////
167// public member functions //
168/////////////////////////////
169
170KPty::KPty()
171 : d_ptr(new KPtyPrivate(this))
172{
173}
174
175KPty::KPty(KPtyPrivate *d)
176 : d_ptr(d)
177{
178 d_ptr->q_ptr = this;
179}
180
181KPty::~KPty()
182{
183 close();
184}
185
186bool KPty::open()
187{
188 Q_D(KPty);
189
190 if (d->masterFd >= 0) {
191 return true;
192 }
193
194 d->ownMaster = true;
195
196 QByteArray ptyName;
197
198 // Find a master pty that we can open ////////////////////////////////
199
200 // Because not all the pty animals are created equal, they want to
201 // be opened by several different methods.
202
203 // We try, as we know them, one by one.
204
205#if HAVE_OPENPTY
206
207 char ptsn[PATH_MAX];
208 if (::openpty(amaster: &d->masterFd, aslave: &d->slaveFd, name: ptsn, termp: nullptr, winp: nullptr)) {
209 d->masterFd = -1;
210 d->slaveFd = -1;
211 qCWarning(KPTY_LOG) << "Can't open a pseudo teletype";
212 return false;
213 }
214 d->ttyName = ptsn;
215
216#else
217
218#if HAVE_PTSNAME || defined(TIOCGPTN)
219
220#if HAVE_POSIX_OPENPT
221 d->masterFd = ::posix_openpt(O_RDWR | O_NOCTTY);
222#elif HAVE_GETPT
223 d->masterFd = ::getpt();
224#elif defined(PTM_DEVICE)
225 d->masterFd = QT_OPEN(PTM_DEVICE, QT_OPEN_RDWR | O_NOCTTY);
226#else
227#error No method to open a PTY master detected.
228#endif
229 if (d->masterFd >= 0) {
230#if HAVE_PTSNAME
231 char *ptsn = ptsname(d->masterFd);
232 if (ptsn) {
233 d->ttyName = ptsn;
234#else
235 int ptyno;
236 if (!ioctl(d->masterFd, TIOCGPTN, &ptyno)) {
237 char buf[32];
238 sprintf(buf, "/dev/pts/%d", ptyno);
239 d->ttyName = buf;
240#endif
241#if HAVE_GRANTPT
242 if (!grantpt(d->masterFd)) {
243 goto grantedpt;
244 }
245#else
246 goto gotpty;
247#endif
248 }
249 ::close(d->masterFd);
250 d->masterFd = -1;
251 }
252#endif // HAVE_PTSNAME || TIOCGPTN
253
254 // Linux device names, FIXME: Trouble on other systems?
255 for (const char *s3 = "pqrstuvwxyzabcde"; *s3; s3++) {
256 for (const char *s4 = "0123456789abcdef"; *s4; s4++) {
257 ptyName = QString().sprintf("/dev/pty%c%c", *s3, *s4).toLatin1();
258 d->ttyName = QString().sprintf("/dev/tty%c%c", *s3, *s4).toLatin1();
259
260 d->masterFd = QT_OPEN(ptyName.data(), QT_OPEN_RDWR);
261 if (d->masterFd >= 0) {
262 if (!access(d->ttyName.data(), R_OK | W_OK)) { // checks availability based on permission bits
263 if (!geteuid()) {
264 struct group *p = getgrnam(TTY_GROUP);
265 if (!p) {
266 p = getgrnam("wheel");
267 }
268 gid_t gid = p ? p->gr_gid : getgid();
269
270 chown(d->ttyName.data(), getuid(), gid);
271 chmod(d->ttyName.data(), S_IRUSR | S_IWUSR | S_IWGRP);
272 }
273 goto gotpty;
274 }
275 ::close(d->masterFd);
276 d->masterFd = -1;
277 }
278 }
279 }
280
281 qCWarning(KPTY_LOG) << "Can't open a pseudo teletype";
282 return false;
283
284gotpty:
285 QFileInfo info(d->ttyName.data());
286 if (!info.exists()) {
287 return false; // this just cannot happen ... *cough* Yeah right, I just
288 }
289 // had it happen when pty #349 was allocated. I guess
290 // there was some sort of leak? I only had a few open.
291 constexpr auto perms = QFile::ReadGroup | QFile::ExeGroup | QFile::ReadOther | QFile::WriteOther | QFile::ExeOther;
292 if ((info.ownerId() != getuid() || (info.permissions() & perms)) //
293 && !d->chownpty(true)) {
294 qCWarning(KPTY_LOG) << "chownpty failed for device " << ptyName << "::" << d->ttyName << "\nThis means the communication can be eavesdropped." << endl;
295 }
296
297grantedpt:
298
299#ifdef HAVE_REVOKE
300 revoke(d->ttyName.data());
301#endif
302
303#ifdef HAVE_UNLOCKPT
304 unlockpt(d->masterFd);
305#elif defined(TIOCSPTLCK)
306 int flag = 0;
307 ioctl(d->masterFd, TIOCSPTLCK, &flag);
308#endif
309
310 d->slaveFd = QT_OPEN(d->ttyName.data(), QT_OPEN_RDWR | O_NOCTTY);
311 if (d->slaveFd < 0) {
312 qCWarning(KPTY_LOG) << "Can't open slave pseudo teletype";
313 ::close(d->masterFd);
314 d->masterFd = -1;
315 return false;
316 }
317
318#endif /* HAVE_OPENPTY */
319
320 fcntl(fd: d->masterFd, F_SETFD, FD_CLOEXEC);
321 fcntl(fd: d->slaveFd, F_SETFD, FD_CLOEXEC);
322
323 return true;
324}
325
326bool KPty::open(int fd)
327{
328#if !HAVE_PTSNAME && !defined(TIOCGPTN)
329 qCWarning(KPTY_LOG) << "Unsupported attempt to open pty with fd" << fd;
330 return false;
331#else
332 Q_D(KPty);
333
334 if (d->masterFd >= 0) {
335 qCWarning(KPTY_LOG) << "Attempting to open an already open pty";
336 return false;
337 }
338
339 d->ownMaster = false;
340
341#if HAVE_PTSNAME
342 char *ptsn = ptsname(fd: fd);
343 if (ptsn) {
344 d->ttyName = ptsn;
345#else
346 int ptyno;
347 if (!ioctl(fd, TIOCGPTN, &ptyno)) {
348 char buf[32];
349 sprintf(buf, "/dev/pts/%d", ptyno);
350 d->ttyName = buf;
351#endif
352 } else {
353 qCWarning(KPTY_LOG) << "Failed to determine pty slave device for fd" << fd;
354 return false;
355 }
356
357 d->masterFd = fd;
358 if (!openSlave()) {
359 d->masterFd = -1;
360 return false;
361 }
362
363 return true;
364#endif
365}
366
367void KPty::closeSlave()
368{
369 Q_D(KPty);
370
371 if (d->slaveFd < 0) {
372 return;
373 }
374 ::close(fd: d->slaveFd);
375 d->slaveFd = -1;
376}
377
378bool KPty::openSlave()
379{
380 Q_D(KPty);
381
382 if (d->slaveFd >= 0) {
383 return true;
384 }
385 if (d->masterFd < 0) {
386 qCWarning(KPTY_LOG) << "Attempting to open pty slave while master is closed";
387 return false;
388 }
389 d->slaveFd = QT_OPEN(file: d->ttyName.data(), QT_OPEN_RDWR | O_NOCTTY);
390 if (d->slaveFd < 0) {
391 qCWarning(KPTY_LOG) << "Can't open slave pseudo teletype";
392 return false;
393 }
394 fcntl(fd: d->slaveFd, F_SETFD, FD_CLOEXEC);
395 return true;
396}
397
398void KPty::close()
399{
400 Q_D(KPty);
401
402 if (d->masterFd < 0) {
403 return;
404 }
405 closeSlave();
406 if (d->ownMaster) {
407#if !HAVE_OPENPTY
408 // don't bother resetting unix98 pty, it will go away after closing master anyway.
409 if (memcmp(d->ttyName.data(), "/dev/pts/", 9)) {
410 if (!geteuid()) {
411 struct stat st;
412 if (!stat(d->ttyName.data(), &st)) {
413 chown(d->ttyName.data(), 0, st.st_gid == getgid() ? 0 : -1);
414 chmod(d->ttyName.data(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
415 }
416 } else {
417 fcntl(d->masterFd, F_SETFD, 0);
418 d->chownpty(false);
419 }
420 }
421#endif
422 ::close(fd: d->masterFd);
423 }
424 d->masterFd = -1;
425}
426
427void KPty::setCTty()
428{
429 Q_D(KPty);
430
431 if (!d->withCTty) {
432 return;
433 }
434
435 // Setup job control //////////////////////////////////
436
437 // Become session leader, process group leader,
438 // and get rid of the old controlling terminal.
439 setsid();
440
441 // make our slave pty the new controlling terminal.
442 ioctl(fd: d->slaveFd, TIOCSCTTY, 0);
443
444 // make our new process group the foreground group on the pty
445 int pgrp = getpid();
446 tcsetpgrp(fd: d->slaveFd, pgrp_id: pgrp);
447}
448
449void KPty::login(const char *user, const char *remotehost)
450{
451#ifdef UTEMPTER_PATH
452 Q_D(KPty);
453
454 Q_UNUSED(user);
455
456 // Emulating libutempter version 1.1.6
457 if (!d->utempterPath.isEmpty()) {
458 UtemptProcess utemptProcess;
459 utemptProcess.cmdFd = d->masterFd;
460 utemptProcess.setProgram(d->utempterPath);
461 utemptProcess.setArguments(QStringList() << QStringLiteral(UTEMPTER_ADD) << QString::fromLocal8Bit(remotehost));
462 utemptProcess.setProcessChannelMode(QProcess::ForwardedChannels);
463 utemptProcess.start();
464 utemptProcess.waitForFinished();
465 }
466
467#else
468#if HAVE_UTMPX
469 struct utmpx l_struct;
470#else
471 struct utmp l_struct;
472#endif
473 memset(s: &l_struct, c: 0, n: sizeof(l_struct));
474 // note: strncpy without terminators _is_ correct here. man 4 utmp
475
476 if (user) {
477 strncpy(dest: l_struct.ut_name, src: user, n: sizeof(l_struct.ut_name));
478 }
479
480 if (remotehost) {
481 strncpy(dest: l_struct.ut_host, src: remotehost, n: sizeof(l_struct.ut_host));
482#if HAVE_STRUCT_UTMP_UT_SYSLEN
483 l_struct.ut_syslen = qMin(strlen(remotehost), sizeof(l_struct.ut_host));
484#endif
485 }
486
487#ifndef __GLIBC__
488 Q_D(KPty);
489 const char *str_ptr = d->ttyName.data();
490 if (!memcmp(str_ptr, "/dev/", 5)) {
491 str_ptr += 5;
492 }
493 strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line));
494#if HAVE_STRUCT_UTMP_UT_ID
495 strncpy(l_struct.ut_id, str_ptr + strlen(str_ptr) - sizeof(l_struct.ut_id), sizeof(l_struct.ut_id));
496#endif
497#endif
498
499#if HAVE_UTMPX
500 gettimeofday(&l_struct.ut_tv, 0);
501#else
502 l_struct.ut_time = time(timer: 0);
503#endif
504
505#if HAVE_LOGIN
506#if HAVE_LOGINX
507 ::loginx(&l_struct);
508#else
509 ::login(entry: &l_struct);
510#endif
511#else
512#if HAVE_STRUCT_UTMP_UT_TYPE
513 l_struct.ut_type = USER_PROCESS;
514#endif
515#if HAVE_STRUCT_UTMP_UT_PID
516 l_struct.ut_pid = getpid();
517#if HAVE_STRUCT_UTMP_UT_SESSION
518 l_struct.ut_session = getsid(0);
519#endif
520#endif
521#if HAVE_UTMPX
522 utmpxname(_PATH_UTMPX);
523 setutxent();
524 pututxline(&l_struct);
525 endutxent();
526 updwtmpx(_PATH_WTMPX, &l_struct);
527#else
528 utmpname(_PATH_UTMP);
529 setutent();
530 pututline(&l_struct);
531 endutent();
532 updwtmp(_PATH_WTMP, &l_struct);
533#endif
534#endif
535#endif
536}
537
538void KPty::logout()
539{
540#ifdef UTEMPTER_PATH
541 Q_D(KPty);
542
543 // Emulating libutempter version 1.1.6
544 if (!d->utempterPath.isEmpty()) {
545 UtemptProcess utemptProcess;
546 utemptProcess.cmdFd = d->masterFd;
547 utemptProcess.setProgram(d->utempterPath);
548 utemptProcess.setArguments(QStringList(QStringLiteral(UTEMPTER_DEL)));
549 utemptProcess.setProcessChannelMode(QProcess::ForwardedChannels);
550 utemptProcess.start();
551 utemptProcess.waitForFinished();
552 }
553
554#else
555 Q_D(KPty);
556
557 const char *str_ptr = d->ttyName.data();
558 if (!memcmp(s1: str_ptr, s2: "/dev/", n: 5)) {
559 str_ptr += 5;
560 }
561#ifdef __GLIBC__
562 else {
563 const char *sl_ptr = strrchr(s: str_ptr, c: '/');
564 if (sl_ptr) {
565 str_ptr = sl_ptr + 1;
566 }
567 }
568#endif
569#if HAVE_LOGIN
570#if HAVE_LOGINX
571 ::logoutx(str_ptr, 0, DEAD_PROCESS);
572#else
573 ::logout(ut_line: str_ptr);
574#endif
575#else
576#if HAVE_UTMPX
577 struct utmpx l_struct, *ut;
578#else
579 struct utmp l_struct, *ut;
580#endif
581 memset(&l_struct, 0, sizeof(l_struct));
582
583 strncpy(l_struct.ut_line, str_ptr, sizeof(l_struct.ut_line));
584
585#if HAVE_UTMPX
586 utmpxname(_PATH_UTMPX);
587 setutxent();
588 if ((ut = getutxline(&l_struct))) {
589#else
590 utmpname(_PATH_UTMP);
591 setutent();
592 if ((ut = getutline(&l_struct))) {
593#endif
594 memset(ut->ut_name, 0, sizeof(*ut->ut_name));
595 memset(ut->ut_host, 0, sizeof(*ut->ut_host));
596#if HAVE_STRUCT_UTMP_UT_SYSLEN
597 ut->ut_syslen = 0;
598#endif
599#if HAVE_STRUCT_UTMP_UT_TYPE
600 ut->ut_type = DEAD_PROCESS;
601#endif
602#if HAVE_UTMPX
603 gettimeofday(&(ut->ut_tv), 0);
604 pututxline(ut);
605 }
606 endutxent();
607#else
608 ut->ut_time = time(0);
609 pututline(ut);
610 }
611 endutent();
612#endif
613#endif
614#endif
615}
616
617bool KPty::tcGetAttr(struct ::termios *ttmode) const
618{
619 Q_D(const KPty);
620
621 return _tcgetattr(d->masterFd, ttmode) == 0;
622}
623
624bool KPty::tcSetAttr(struct ::termios *ttmode)
625{
626 Q_D(KPty);
627
628 return _tcsetattr(d->masterFd, ttmode) == 0;
629}
630
631bool KPty::setWinSize(int lines, int columns, int height, int width)
632{
633 Q_D(KPty);
634
635 struct winsize winSize;
636 winSize.ws_row = (unsigned short)lines;
637 winSize.ws_col = (unsigned short)columns;
638 winSize.ws_ypixel = (unsigned short)height;
639 winSize.ws_xpixel = (unsigned short)width;
640 return ioctl(fd: d->masterFd, TIOCSWINSZ, (char *)&winSize) == 0;
641}
642
643bool KPty::setWinSize(int lines, int columns)
644{
645 return setWinSize(lines, columns, height: 0, width: 0);
646}
647
648bool KPty::setEcho(bool echo)
649{
650 struct ::termios ttmode;
651 if (!tcGetAttr(ttmode: &ttmode)) {
652 return false;
653 }
654 if (!echo) {
655 ttmode.c_lflag &= ~ECHO;
656 } else {
657 ttmode.c_lflag |= ECHO;
658 }
659 return tcSetAttr(ttmode: &ttmode);
660}
661
662const char *KPty::ttyName() const
663{
664 Q_D(const KPty);
665
666 return d->ttyName.data();
667}
668
669int KPty::masterFd() const
670{
671 Q_D(const KPty);
672
673 return d->masterFd;
674}
675
676int KPty::slaveFd() const
677{
678 Q_D(const KPty);
679
680 return d->slaveFd;
681}
682
683void KPty::setCTtyEnabled(bool enable)
684{
685 Q_D(KPty);
686
687 d->withCTty = enable;
688}
689

source code of kpty/src/kpty.cpp