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