summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/util/pty/ChangeLog18
-rw-r--r--src/util/pty/getpty.c14
-rw-r--r--src/util/pty/pty-int.h1
-rw-r--r--src/util/pty/pty_paranoia.c382
4 files changed, 379 insertions, 36 deletions
diff --git a/src/util/pty/ChangeLog b/src/util/pty/ChangeLog
index eda91e588..7b21d6d03 100644
--- a/src/util/pty/ChangeLog
+++ b/src/util/pty/ChangeLog
@@ -1,3 +1,21 @@
+2001-05-15 Tom Yu <tlyu@mit.edu>
+
+ * getpty.c: Make pty_getpty() into ptyint_getpty_ext(), which has
+ an extra argument that determines whether to call grantpt() and
+ unlockpt() on systems that support it. The new pty_getpty() will
+ simply call the extended version. This is to support some
+ wackiness needed by pty_paranoia.c tests.
+
+ * pty-int.h: Add prototype for ptyint_getpty_ext().
+
+ * pty_paranoia.c: Add rant about ptys and quirks therein. Needs
+ to be updated somewhat. Add some more paranoia for the case where
+ we actually succeed in opening the slave of a closed master and
+ then succeed in opening the same master. This program will get
+ rewritten at some point to actually see what things result in EOFs
+ and under what conditions data will actually get passed between
+ master and slave.
+
2001-05-10 Tom Yu <tlyu@mit.edu>
* pty_paranoia.c: New file; do many paranoid checks about ctty
diff --git a/src/util/pty/getpty.c b/src/util/pty/getpty.c
index f02b87ee8..0e86514b6 100644
--- a/src/util/pty/getpty.c
+++ b/src/util/pty/getpty.c
@@ -24,9 +24,8 @@
#include "libpty.h"
#include "pty-int.h"
-long pty_getpty (fd, slave, slavelength)
- int slavelength;
- int *fd; char *slave;
+long
+ptyint_getpty_ext(int *fd, char *slave, int slavelength, int do_grantpt)
{
#if !defined(HAVE__GETPTY) && !defined(HAVE_OPENPTY)
char *cp;
@@ -79,7 +78,8 @@ long pty_getpty (fd, slave, slavelength)
if (*fd >= 0) {
#if defined(HAVE_GRANTPT)&&defined(HAVE_STREAMS)
- if (grantpt(*fd) || unlockpt(*fd)) return PTY_GETPTY_STREAMS;
+ if (do_grantpt)
+ if (grantpt(*fd) || unlockpt(*fd)) return PTY_GETPTY_STREAMS;
#endif
#ifdef HAVE_PTSNAME
@@ -142,3 +142,9 @@ long pty_getpty (fd, slave, slavelength)
#endif /*HAVE__GETPTY*/
#endif /* HAVE_OPENPTY */
}
+
+long
+pty_getpty(int *fd, char *slave, int slavelength)
+{
+ return ptyint_getpty_ext(fd, slave, slavelength, 1);
+}
diff --git a/src/util/pty/pty-int.h b/src/util/pty/pty-int.h
index 3f9d2cea3..b3c6146a6 100644
--- a/src/util/pty/pty-int.h
+++ b/src/util/pty/pty-int.h
@@ -102,6 +102,7 @@ extern void getutmpx (const struct utmp *, struct utmpx *);
/* Internal functions */
long ptyint_void_association(void);
long ptyint_open_ctty (char *slave, int *fd);
+long ptyint_getpty_ext(int *, char *, int, int);
#ifdef HAVE_SETUTXENT
long ptyint_update_wtmpx(struct utmpx *utx);
#endif
diff --git a/src/util/pty/pty_paranoia.c b/src/util/pty/pty_paranoia.c
index e07e0ff33..7311e0834 100644
--- a/src/util/pty/pty_paranoia.c
+++ b/src/util/pty/pty_paranoia.c
@@ -17,7 +17,125 @@
*/
/*
- * This bears some explanation.
+ * A rant on the nature of pseudo-terminals:
+ * -----------------------------------------
+ *
+ * Controlling terminals and job control:
+ *
+ * First, some explanation of job control and controlling terminals is
+ * necessary for background. This discussion applies to hardwired
+ * terminals as well as ptys. On most modern systems, all processes
+ * belong to a process group. A process whose process group id (pgid)
+ * is the sames as its pid is the process group leader of its process
+ * group. Process groups belong to sessions. On a modern system, a
+ * process that is not currently a process group leader may create a
+ * new session by calling setsid(), which makes it a session leader as
+ * well as a process group leader, and also removes any existing
+ * controlling terminal (ctty) association. Only a session leader may
+ * acquire a ctty. It's not clear how systems that don't have
+ * setsid() handle ctty acquisition, though probably any process group
+ * leader that doesn't have a ctty may acquire one that way.
+ *
+ * A terminal that is a ctty has an associated foreground process
+ * group, which is a member of the terminal's associated session.
+ * This process group gets read/write access to the terminal and will
+ * receive terminal-generated signals (e.g. SIGINT, SIGTSTP). Process
+ * groups belonging to the session but not in the foreground may get
+ * signals that suspend them if they try to read/write from the ctty,
+ * depending on various terminal settings.
+ *
+ * On many systems, the controlling process (the session leader
+ * associated with a ctty) exiting will cause the session to lose its
+ * ctty, even though some processes may continue to have open file
+ * descriptors on the former ctty. It is possible for a process to
+ * have no file descriptors open on its controlling tty, but to
+ * reacquire such by opening /dev/tty, as long as its session still
+ * has a ctty.
+ *
+ * On ptys in general:
+ *
+ * Ptys have a slave side and a master side. The slave side looks
+ * like a hardwired serial line to the application that opens it;
+ * usually, telnetd or rlogind, etc. opens the slave and hands it to
+ * the login program as stdin/stdout/stderr. The master side usually
+ * gets the actual network traffic written to/from it. Roughly, the
+ * master and slave are two ends of a bidirectional pair of FIFOs,
+ * though this can get complicated by other things.
+ *
+ * The master side of a pty is theoretically a single-open device.
+ * This MUST be true on systems that have BSD-style ptys, since there
+ * is usually no way to allocate an unused pty except by attempting to
+ * open all the master pty nodes in the system.
+ *
+ * Often, but not always, the last close of a slave device will cause
+ * the master to get an EOF. Closing the master device will sometimes
+ * cause the foreground process group of the slave to get a SIGHUP,
+ * but that may depend on terminal settings.
+ *
+ * BSD ptys:
+ *
+ * On a BSD-derived system, the master nodes are named like
+ * /dev/ptyp0, and the slave nodes are named like /dev/ttyp0. The
+ * last two characters are the variable ones, and a shell-glob type
+ * pattern for a slave device is usually of the form
+ * /dev/tty[p-z][0-9a-f], though variants are known to exist.
+ *
+ * System V cloning ptys:
+ *
+ * There is a cloning master device (usually /dev/ptmx, but the name
+ * can vary) that gets opened. Each open of the cloning master
+ * results in an open file descriptor of a unique master device. The
+ * application calls ptsname() to find the pathname to the slave node.
+ *
+ * In theory, the slave side of the pty is locked out until the
+ * process opening the master calls grantpt() to adjust permissions
+ * and unlockpt() to unlock the slave. It turns out that Unix98
+ * doesn't require that the slave actually get locked out, or that
+ * unlockpt() actually do anything on such systems. At least AIX
+ * allows the slave to be opened prior to calling unlockpt(), but most
+ * other SysV-ish systems seem to actually lock out the slave.
+ *
+ * Pty security:
+ *
+ * It's not guaranteed on a BSD-ish system that a slave can't be
+ * opened when the master isn't open. It's even possible to acquire
+ * the slave as a ctty (!) if the open is done as non-blocking. It's
+ * possible to open the master corresponding to an open slave, which
+ * creates some security issues: once this master is open, data
+ * written to the slave will actually pass to the master.
+ *
+ * On a SysV-ish system, the close of the master will invalidate any
+ * open file descriptors on the slave.
+ *
+ * In general, there are two functions that can be used to "clean" a
+ * pty slave, revoke() and vhangup(). revoke() will invalidate all
+ * file descriptors open on a particular pathname (often this only
+ * works on terminal devices), usually by invalidating the underlying
+ * vnode. vhangup() will send a SIGHUP to the foreground process
+ * group of the control terminal. On many systems, it also has
+ * revoke() semantics.
+ *
+ * If a process acquires a controlling terminal in order to perform a
+ * vhangup(), the reopen of the controlling terminal after the
+ * vhangup() call should be done prior to the close of the file
+ * descriptor used to initially acquire the controlling terminal,
+ * since that will likely prevent the process on the master side from
+ * reading a spurious EOF due to all file descriptors to the slave
+ * being closed.
+ *
+ * Known quirks of various OSes:
+ *
+ * AIX 4.3.3:
+ *
+ * If the environment variable XPG_SUS_ENV is not equal to "ON", then
+ * it's possible to open the slave prior to calling unlockpt().
+ */
+
+/*
+ * NOTE: this program will get reworked at some point to actually test
+ * passing of data between master and slave, and to do general cleanup.
+ *
+ * This is rather complex, so it bears some explanation.
*
* There are multiple child processes and a parent process. These
* communicate via pipes (which we assume here to be unidirectional).
@@ -59,6 +177,11 @@
* pp1. It will then check if it has a ctty and then attempt to
* reopen the slave. This should fail. It will then write to the
* parent via syncpipe p1p and exit.
+ *
+ * If this doesn't fail, child3 will attempt to write to the open
+ * slave fd. This should fail unless a prior call to revoke(),
+ * etc. failed due to lack of permissions, e.g. NetBSD when running as
+ * non-root.
*/
#include <com_err.h>
@@ -78,6 +201,8 @@ void handler(int);
void rdsync(int, int *, const char *);
void wrsync(int, int, const char *);
void testctty(const char *);
+void testex(int, const char *);
+void testwr(int, const char *);
void child1(void);
void child2(void);
void child3(void);
@@ -96,9 +221,13 @@ rdsync(int fd, int *status, const char *caller)
int n;
char c;
+#if 0
+ printf("rdsync: %s: starting\n", caller);
+ fflush(stdout);
+#endif
while ((n = read(fd, &c, 1)) < 0) {
if (errno != EINTR) {
- fprintf(stderr, "wrsync: %s", caller);
+ fprintf(stderr, "rdsync: %s", caller);
perror("");
exit(1);
} else {
@@ -144,7 +273,7 @@ testctty(const char *caller)
{
int fd;
- fd = open("/dev/tty", O_RDWR);
+ fd = open("/dev/tty", O_RDWR|O_NONBLOCK);
if (fd < 0) {
printf("%s: no ctty\n", caller);
} else {
@@ -153,44 +282,147 @@ testctty(const char *caller)
}
void
+testex(int fd, const char *caller)
+{
+ fd_set rfds, xfds;
+ struct timeval timeout;
+ int n;
+ char c;
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ FD_ZERO(&rfds);
+ FD_ZERO(&xfds);
+ FD_SET(fd, &rfds);
+ FD_SET(fd, &xfds);
+
+ n = select(fd + 1, &rfds, NULL, &xfds, &timeout);
+ if (n < 0) {
+ fprintf(stderr, "testex: %s: ", caller);
+ perror("select");
+ }
+ if (n) {
+ if (FD_ISSET(fd, &rfds) || FD_ISSET(fd, &xfds)) {
+ n = read(fd, &c, 1);
+ if (!n) {
+ printf("testex: %s: got EOF\n", caller);
+ fflush(stdout);
+ return;
+ } else if (n == -1) {
+ printf("testex: %s: got errno=%ld (%s)\n",
+ caller, (long)errno, strerror(errno));
+ } else {
+ printf("testex: %s: read 1 byte!?\n", caller);
+ }
+ }
+ } else {
+ printf("testex: %s: no exceptions or readable fds\n", caller);
+ }
+}
+
+void
+testwr(int fd, const char *caller)
+{
+ fd_set wfds;
+ struct timeval timeout;
+ int n;
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ FD_ZERO(&wfds);
+ FD_SET(fd, &wfds);
+
+ n = select(fd + 1, NULL, &wfds, NULL, &timeout);
+ if (n < 0) {
+ fprintf(stderr, "testwr: %s: ", caller);
+ perror("select");
+ }
+ if (n) {
+ if (FD_ISSET(fd, &wfds)) {
+ printf("testwr: %s: is writable\n", caller);
+ fflush(stdout);
+ }
+ }
+}
+
+
+void
child3(void)
{
+ int n;
ptyint_void_association();
- slavefd = open(slave, O_RDWR);
+ slavefd = open(slave, O_RDWR|O_NONBLOCK);
if (slavefd < 0) {
+ wrsync(p1p[1], 1, "[02] child3->parent");
printf("child3: failed reopen of slave\n");
fflush(stdout);
- exit(0);
+ exit(1);
}
-#ifdef TIOCSTTY
- ioctl(slavefd, TIOCSTTY, 0);
+#ifdef TIOCSCTTY
+ ioctl(slavefd, TIOCSCTTY, 0);
#endif
printf("child3: reopened slave\n");
testctty("child3: after reopen of slave");
+ testwr(slavefd, "child3: after reopen of slave");
+ testex(slavefd, "child3: after reopen of slave");
close(slavefd);
testctty("child3: after close of slave");
/*
* Sync for parent to close master.
*/
- wrsync(p1p[1], 0, "child3->parent");
- rdsync(pp1[0], NULL, "parent->child3");
+ wrsync(p1p[1], 0, "[02] child3->parent");
+ rdsync(pp1[0], NULL, "[03] parent->child3");
testctty("child3: after close of master");
- slavefd = open(slave, O_RDWR);
+ printf("child3: attempting reopen of slave\n");
+ fflush(stdout);
+ slavefd = open(slave, O_RDWR|O_NONBLOCK);
if (slavefd < 0) {
- printf("child3: failed reopen of slave after master close "
+ printf("child3: failed reopen of slave after master close: "
"errno=%ld (%s)\n", (long)errno, strerror(errno));
- wrsync(p1p[1], 0, "child3->parent");
+ wrsync(p1p[1], 0, "[04] child3->parent");
fflush(stdout);
exit(0);
}
+ if (fcntl(slavefd, F_SETFL, 0) == -1) {
+ perror("child3: fcntl");
+ wrsync(p1p[1], 2, "[04] child3->parent");
+ exit(1);
+ }
+#ifdef TIOCSCTTY
+ ioctl(slavefd, TIOCSCTTY, 0);
+#endif
printf("child3: reopened slave after master close\n");
- testctty("child3: after reopen of slave after master close\n");
- wrsync(p1p[1], 1, "child3->parent");
+ testctty("child3: after reopen of slave after master close");
+ testwr(slavefd, "child3: after reopen of slave after master close");
+ testex(slavefd, "child3: after reopen of slave after master close");
+ n = write(slavefd, "foo", 4);
+ if (n < 0) {
+ printf("child3: writing to slave of closed master: errno=%ld (%s)\n",
+ (long)errno, strerror(errno));
+ wrsync(p1p[1], 1, "[04] child3->parent");
+ } else {
+ printf("child3: wrote %d byes to slave of closed master\n", n);
+ fflush(stdout);
+ wrsync(p1p[1], 2, "[04] child3->parent");
+ }
+ rdsync(pp1[0], NULL, "[05] parent->child3");
+ testex(slavefd, "child3: after parent reopen of master");
+ testwr(slavefd, "child3: after parent reopen of master");
fflush(stdout);
+ n = write(slavefd, "bar", 4);
+ if (n < 0) {
+ perror("child3: writing to slave");
+ } else {
+ printf("child3: wrote %d bytes to slave\n", n);
+ fflush(stdout);
+ }
+ wrsync(p1p[1], 0, "[06] child3->parent");
+ rdsync(pp1[0], NULL, "[07] parent->child3");
+ wrsync(p1p[1], 0, "[08] child3->parent");
exit(0);
}
@@ -205,23 +437,32 @@ child2(void)
sigemptyset(&sa.sa_mask);
sa.sa_handler = handler;
if (sigaction(SIGHUP, &sa, NULL) < 0) {
+ wrsync(p21[1], 1, "[00] child2->child1");
perror("child2: sigaction");
fflush(stdout);
exit(1);
}
printf("child2: set up signal handler\n");
testctty("child2: after start");
- wrsync(p21[1], 0, "child2->child1");
- rdsync(pp1[0], NULL, "parent->child2");
+ testwr(slavefd, "child2: after start");
+ wrsync(p21[1], 0, "[00] child2->child1");
+ rdsync(pp1[0], NULL, "[01] parent->child2");
+
testctty("child2: after child1 exit");
+ testex(slavefd, "child2: after child1 exit");
+ testwr(slavefd, "child2: after child1 exit");
close(slavefd);
testctty("child2: after close of slavefd");
- slavefd = open(slave, O_RDWR);
+ slavefd = open(slave, O_RDWR|O_NONBLOCK);
if (slavefd < 0) {
+ wrsync(p1p[1], 1, "[02] child2->parent");
printf("child2: failed reopen of slave\n");
fflush(stdout);
- exit(0);
+ exit(1);
}
+#ifdef TIOCSCTTY
+ ioctl(slavefd, TIOCSCTTY, 0);
+#endif
printf("child2: reopened slave\n");
testctty("child2: after reopen of slave");
fflush(stdout);
@@ -230,30 +471,34 @@ child2(void)
if (!pid3) {
child3();
} else if (pid3 == -1) {
+ wrsync(p1p[1], 1, "[02] child2->parent");
perror("child2: fork of child3");
exit(1);
}
printf("child2: forked child3=%ld\n", (long)pid3);
fflush(stdout);
exit(0);
-
}
void
child1(void)
{
+ int status;
+#if 0
+ setuid(1);
+#endif
close(pp1[1]);
close(p1p[0]);
close(masterfd);
ptyint_void_association();
- slavefd = open(slave, O_RDWR);
+ slavefd = open(slave, O_RDWR|O_NONBLOCK);
if (slavefd < 0) {
perror("child1: open slave");
exit(1);
}
-#ifdef TIOCSTTY
- ioctl(slavefd, TIOCSTTY, 0);
+#ifdef TIOCSCTTY
+ ioctl(slavefd, TIOCSCTTY, 0);
#endif
printf("child1: opened slave\n");
@@ -273,8 +518,8 @@ child1(void)
close(p21[1]);
printf("child1: forked child2=%ld\n", (long)pid2);
fflush(stdout);
- rdsync(p21[0], NULL, "child2->child1");
- exit(0);
+ rdsync(p21[0], &status, "[00] child2->child1");
+ exit(status);
}
int
@@ -282,18 +527,49 @@ main(int argc, char *argv[])
{
long retval;
int status;
+ char buf[4];
+ int n;
prog = argv[0];
- retval = pty_getpty(&masterfd, slave, sizeof(slave));
+ printf("parent: pid=%ld\n", (long)getpid());
+
+ retval = ptyint_getpty_ext(&masterfd, slave, sizeof(slave), 0);
if (retval) {
com_err(prog, retval, "open master");
exit(1);
}
+#if 0
+ chown(slave, 1, -1);
+#endif
printf("parent: master opened; slave=%s\n", slave);
fflush(stdout);
+#if defined(HAVE_GRANTPT) && defined(HAVE_STREAMS)
+#ifdef O_NOCTTY
+ printf("parent: attempting to open slave before unlockpt\n");
+ fflush(stdout);
+ slavefd = open(slave, O_RDWR|O_NONBLOCK|O_NOCTTY);
+ if (slavefd < 0) {
+ printf("parent: failed slave open before unlockpt errno=%ld (%s)\n",
+ (long)errno, strerror(errno));
+ } else {
+ printf("parent: WARNING: "
+ "succeeded in opening slave before unlockpt\n");
+ }
+ close(slavefd);
+#endif
+ if (grantpt(masterfd) < 0) {
+ perror("parent: grantpt");
+ exit(1);
+ }
+ if (unlockpt(masterfd) < 0) {
+ perror("parent: unlockpt");
+ exit(1);
+ }
+#endif /* HAVE_GRANTPT && HAVE_STREAMS */
+
if (pipe(pp1) < 0) {
perror("pipe parent->child1");
exit(1);
@@ -317,16 +593,58 @@ main(int argc, char *argv[])
exit(1);
}
printf("parent: child1 exited, status=%d\n", status1);
- wrsync(pp1[1], 0, "parent->child2");
- rdsync(p1p[0], NULL, "child3->parent");
+ if (status1)
+ exit(status1);
+
+ wrsync(pp1[1], 0, "[01] parent->child2");
+ rdsync(p1p[0], &status, "[02] child3->parent");
+ if (status) {
+ fprintf(stderr, "child2 or child3 got an error\n");
+ exit(1);
+ }
+
printf("parent: closing master\n");
fflush(stdout);
close(masterfd);
+ chmod(slave, 0666);
printf("parent: closed master\n");
- wrsync(pp1[1], 0, "parent->child3");
- rdsync(p1p[0], &status, "child3->parent");
- if (status) {
- fprintf(stderr, "got status %d\n", status);
+ wrsync(pp1[1], 0, "[03] parent->child3");
+
+ rdsync(p1p[0], &status, "[04] child3->parent");
+ switch (status) {
+ case 1:
+ break;
+ case 0:
+ exit(0);
+ default:
+ fprintf(stderr, "child3 got an error\n");
+ fflush(stdout);
+ exit(1);
}
- exit(status);
+
+ retval = pty_getpty(&masterfd, slave2, sizeof(slave2));
+ printf("parent: new master opened; slave=%s\n", slave2);
+#if 0
+#ifdef HAVE_REVOKE
+ printf("parent: revoking\n");
+ revoke(slave2);
+#endif
+#endif
+ fflush(stdout);
+ wrsync(pp1[1], 0, "[05] parent->child3");
+ rdsync(p1p[0], NULL, "[06] child3->parent");
+
+ n = read(masterfd, buf, 4);
+ if (n < 0) {
+ perror("parent: reading from master");
+ } else {
+ printf("parent: read %d bytes (%.*s) from master\n", n, n, buf);
+ fflush(stdout);
+ }
+ chmod(slave2, 0666);
+ close(masterfd);
+ wrsync(pp1[1], 0, "[07] parent->child3");
+ rdsync(p1p[0], NULL, "[08] child3->parent");
+ fflush(stdout);
+ exit(0);
}