478 lines
11 KiB
C
478 lines
11 KiB
C
/*
|
|
* Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Högskolan
|
|
* (Royal Institute of Technology, Stockholm, Sweden).
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the Institute nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "bsd_locl.h"
|
|
|
|
#ifndef HAVE_FORKPTY
|
|
|
|
RCSID("$Id: forkpty.c,v 1.57 1999/12/02 16:58:28 joda Exp $");
|
|
|
|
/* Only CRAY is known to have problems with forkpty(). */
|
|
#if defined(CRAY)
|
|
static int forkpty_ok = 0;
|
|
#else
|
|
static int forkpty_ok = 1;
|
|
#endif
|
|
|
|
#ifndef HAVE_PTSNAME
|
|
static char *ptsname(int fd)
|
|
{
|
|
#ifdef HAVE_TTYNAME
|
|
return ttyname(fd);
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#ifndef HAVE_GRANTPT
|
|
#define grantpt(fdm) (0)
|
|
#endif
|
|
|
|
#ifndef HAVE_UNLOCKPT
|
|
#define unlockpt(fdm) (0)
|
|
#endif
|
|
|
|
#ifndef HAVE_VHANGUP
|
|
#define vhangup() (0)
|
|
#endif
|
|
|
|
#ifndef HAVE_REVOKE
|
|
static
|
|
void
|
|
revoke(char *line)
|
|
{
|
|
int slave;
|
|
RETSIGTYPE (*ofun)();
|
|
|
|
if ( (slave = open(line, O_RDWR)) < 0)
|
|
return;
|
|
|
|
ofun = signal(SIGHUP, SIG_IGN);
|
|
vhangup();
|
|
signal(SIGHUP, ofun);
|
|
/*
|
|
* Some systems (atleast SunOS4) want to have the slave end open
|
|
* at all times to prevent a race in the child. Login will close
|
|
* it so it should really not be a problem. However for the
|
|
* paranoid we use the close on exec flag so it will only be open
|
|
* in the parent. Additionally since this will be the controlling
|
|
* tty of rlogind the final vhangup() in rlogind should hangup all
|
|
* processes. A working revoke would of course have been prefered
|
|
* though (sigh).
|
|
*/
|
|
fcntl(slave, F_SETFD, 1);
|
|
/* close(slave); */
|
|
}
|
|
#endif
|
|
|
|
|
|
static int pty_major, pty_minor;
|
|
|
|
static void
|
|
pty_scan_start(void)
|
|
{
|
|
pty_major = -1;
|
|
pty_minor = 0;
|
|
}
|
|
|
|
static char *bsd_1 = "0123456789abcdefghijklmnopqrstuv";
|
|
/* there are many more */
|
|
static char *bsd_2 = "pqrstuvwxyzabcdefghijklmnoABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
|
static int
|
|
pty_scan_next(char *buf, size_t sz)
|
|
{
|
|
#ifdef CRAY
|
|
if(++pty_major >= sysconf(_SC_CRAY_NPTY))
|
|
return -1;
|
|
snprintf(buf, sz, "/dev/pty/%03d", pty_major);
|
|
#else
|
|
if(++pty_major == strlen(bsd_1)){
|
|
pty_major = 0;
|
|
if(++pty_minor == strlen(bsd_2))
|
|
return -1;
|
|
}
|
|
#ifdef __hpux
|
|
snprintf(buf, sz, "/dev/ptym/pty%c%c", bsd_2[pty_major], bsd_1[pty_minor]);
|
|
#else
|
|
snprintf(buf, sz, "/dev/pty%c%c", bsd_2[pty_major], bsd_1[pty_minor]);
|
|
#endif /* __hpux */
|
|
#endif /* CRAY */
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
pty_scan_tty(char *buf, size_t sz)
|
|
{
|
|
#ifdef CRAY
|
|
snprintf(buf, sz, "/dev/ttyp%03d", pty_major);
|
|
#elif defined(__hpux)
|
|
snprintf(buf, sz, "/dev/pty/tty%c%c", bsd_2[pty_major], bsd_1[pty_minor]);
|
|
#else
|
|
snprintf(buf, sz, "/dev/tty%c%c", bsd_2[pty_major], bsd_1[pty_minor]);
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
ptym_open_streams_flavor(char *pts_name,
|
|
size_t pts_name_sz,
|
|
int *streams_pty)
|
|
{
|
|
/* Try clone device master ptys */
|
|
const char *const clone[] = { "/dev/ptc", "/dev/ptmx",
|
|
"/dev/ptm", "/dev/ptym/clone", 0 };
|
|
int fdm;
|
|
const char *const *q;
|
|
|
|
for (q = clone; *q; q++) {
|
|
fdm = open(*q, O_RDWR);
|
|
if (fdm >= 0)
|
|
break;
|
|
}
|
|
if (fdm >= 0) {
|
|
char *ptr1;
|
|
if ((ptr1 = ptsname(fdm)) != NULL) /* Get slave's name */
|
|
/* Return name of slave */
|
|
strlcpy(pts_name, ptr1, pts_name_sz);
|
|
else {
|
|
close(fdm);
|
|
return(-4);
|
|
}
|
|
if (grantpt(fdm) < 0) { /* Grant access to slave */
|
|
close(fdm);
|
|
return(-2);
|
|
}
|
|
if (unlockpt(fdm) < 0) { /* Clear slave's lock flag */
|
|
close(fdm);
|
|
return(-3);
|
|
}
|
|
return(fdm); /* return fd of master */
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
ptym_open_bsd_flavor(char *pts_name, size_t pts_name_sz, int *streams_pty)
|
|
{
|
|
int fdm;
|
|
char ptm[MaxPathLen];
|
|
|
|
pty_scan_start();
|
|
|
|
while (pty_scan_next(ptm, sizeof(ptm)) != -1) {
|
|
fdm = open(ptm, O_RDWR);
|
|
if (fdm < 0)
|
|
continue;
|
|
#if SunOS == 40
|
|
/* Avoid a bug in SunOS4 ttydriver */
|
|
if (fdm > 0) {
|
|
int pgrp;
|
|
if ((ioctl(fdm, TIOCGPGRP, &pgrp) == -1)
|
|
&& (errno == EIO))
|
|
/* All fine */;
|
|
else {
|
|
close(fdm);
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
pty_scan_tty(pts_name, sizeof(ptm));
|
|
#if CRAY
|
|
/* this is some magic from the telnet code */
|
|
{
|
|
struct stat sb;
|
|
if(stat(pts_name, &sb) < 0) {
|
|
close(fdm);
|
|
continue;
|
|
}
|
|
if(sb.st_uid || sb.st_gid || sb.st_mode != 0600) {
|
|
chown(pts_name, 0, 0);
|
|
chmod(pts_name, 0600);
|
|
close(fdm);
|
|
fdm = open(ptm, 2);
|
|
if (fdm < 0)
|
|
continue;
|
|
}
|
|
}
|
|
/*
|
|
* Now it should be safe...check for accessability.
|
|
*/
|
|
if (access(pts_name, 6) != 0){
|
|
/* no tty side to pty so skip it */
|
|
close(fdm);
|
|
continue;
|
|
}
|
|
#endif
|
|
return fdm; /* All done! */
|
|
}
|
|
|
|
/* We failed to find BSD style pty */
|
|
errno = ENOENT;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
*
|
|
* Open a master pty either using the STREAM flavor or the BSD flavor.
|
|
* Depending on if there are any free ptys in the different classes we
|
|
* need to try both. Normally try STREAMS first and then BSD.
|
|
*
|
|
* Kludge alert: Under HP-UX 10 and perhaps other systems STREAM ptys
|
|
* doesn't get initialized properly so we try them in different order
|
|
* until the problem has been resolved.
|
|
*
|
|
*/
|
|
static int
|
|
ptym_open(char *pts_name, size_t pts_name_sz, int *streams_pty)
|
|
{
|
|
int fdm;
|
|
|
|
#ifdef HAVE__GETPTY
|
|
{
|
|
char *p = _getpty(&fdm, O_RDWR, 0600, 1);
|
|
if (p) {
|
|
*streams_pty = 1;
|
|
strlcpy (pts_name, p, pts_name_sz);
|
|
return fdm;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef STREAMSPTY
|
|
fdm = ptym_open_streams_flavor(pts_name, pts_name_sz, streams_pty);
|
|
if (fdm >= 0)
|
|
{
|
|
*streams_pty = 1;
|
|
return fdm;
|
|
}
|
|
#endif
|
|
|
|
fdm = ptym_open_bsd_flavor(pts_name, pts_name_sz, streams_pty);
|
|
if (fdm >= 0)
|
|
{
|
|
*streams_pty = 0;
|
|
return fdm;
|
|
}
|
|
|
|
#ifndef STREAMSPTY
|
|
fdm = ptym_open_streams_flavor(pts_name, pts_name_sz, streams_pty);
|
|
if (fdm >= 0)
|
|
{
|
|
*streams_pty = 1;
|
|
return fdm;
|
|
}
|
|
#endif
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
maybe_push_modules(int fd, char **modules)
|
|
{
|
|
#ifdef I_PUSH
|
|
char **p;
|
|
int err;
|
|
|
|
for(p=modules; *p; p++){
|
|
err=ioctl(fd, I_FIND, *p);
|
|
if(err == 1)
|
|
break;
|
|
if(err < 0 && errno != EINVAL)
|
|
return -17;
|
|
/* module not pushed or does not exist */
|
|
}
|
|
/* p points to null or to an already pushed module, now push all
|
|
modules before this one */
|
|
|
|
for(p--; p >= modules; p--){
|
|
err = ioctl(fd, I_PUSH, *p);
|
|
if(err < 0 && errno != EINVAL)
|
|
return -17;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ptys_open(int fdm, char *pts_name, int streams_pty)
|
|
{
|
|
int fds;
|
|
|
|
if (streams_pty) {
|
|
/* Streams style slave ptys */
|
|
if ( (fds = open(pts_name, O_RDWR)) < 0) {
|
|
close(fdm);
|
|
return(-5);
|
|
}
|
|
|
|
{
|
|
char *ttymodules[] = { "ttcompat", "ldterm", "ptem", NULL };
|
|
char *ptymodules[] = { "pckt", NULL };
|
|
|
|
if(maybe_push_modules(fds, ttymodules)<0){
|
|
close(fdm);
|
|
close(fds);
|
|
return -6;
|
|
}
|
|
if(maybe_push_modules(fdm, ptymodules)<0){
|
|
close(fdm);
|
|
close(fds);
|
|
return -7;
|
|
}
|
|
}
|
|
} else {
|
|
/* BSD style slave ptys */
|
|
struct group *grptr;
|
|
int gid;
|
|
if ( (grptr = getgrnam("tty")) != NULL)
|
|
gid = grptr->gr_gid;
|
|
else
|
|
gid = -1; /* group tty is not in the group file */
|
|
|
|
/* Grant access to slave */
|
|
if (chown(pts_name, getuid(), gid) < 0)
|
|
fatal(0, "chown slave tty failed", 1);
|
|
if (chmod(pts_name, S_IRUSR | S_IWUSR | S_IWGRP) < 0)
|
|
fatal(0, "chmod slave tty failed", 1);
|
|
|
|
if ( (fds = open(pts_name, O_RDWR)) < 0) {
|
|
close(fdm);
|
|
return(-1);
|
|
}
|
|
}
|
|
return(fds);
|
|
}
|
|
|
|
int
|
|
forkpty_truncate(int *ptrfdm,
|
|
char *slave_name,
|
|
size_t slave_name_sz,
|
|
struct termios *slave_termios,
|
|
struct winsize *slave_winsize)
|
|
{
|
|
int fdm, fds, streams_pty;
|
|
pid_t pid;
|
|
char pts_name[20];
|
|
|
|
if (!forkpty_ok)
|
|
fatal(0, "Protocol not yet supported, use telnet", 0);
|
|
|
|
if ( (fdm = ptym_open(pts_name, sizeof(pts_name), &streams_pty)) < 0)
|
|
return -1;
|
|
|
|
if (slave_name != NULL)
|
|
/* Return name of slave */
|
|
strlcpy(slave_name, pts_name, slave_name_sz);
|
|
|
|
pid = fork();
|
|
if (pid < 0)
|
|
return(-1);
|
|
else if (pid == 0) { /* Child */
|
|
if (setsid() < 0)
|
|
fatal(0, "setsid() failure", errno);
|
|
|
|
revoke(slave_name);
|
|
|
|
#if defined(NeXT) || defined(ultrix)
|
|
/* The NeXT is severely broken, this makes things slightly
|
|
* better but we still doesn't get a working pty. If there
|
|
* where a TIOCSCTTY we could perhaps fix things but... The
|
|
* same problem also exists in xterm! */
|
|
if (setpgrp(0, 0) < 0)
|
|
fatal(0, "NeXT kludge failed setpgrp", errno);
|
|
#endif
|
|
|
|
/* SVR4 acquires controlling terminal on open() */
|
|
if ( (fds = ptys_open(fdm, pts_name, streams_pty)) < 0)
|
|
return -1;
|
|
close(fdm); /* All done with master in child */
|
|
|
|
#if defined(TIOCSCTTY) && !defined(CIBAUD) && !defined(__hpux)
|
|
/* 44BSD way to acquire controlling terminal */
|
|
/* !CIBAUD to avoid doing this under SunOS */
|
|
if (ioctl(fds, TIOCSCTTY, (char *) 0) < 0)
|
|
return -1;
|
|
#endif
|
|
#if defined(NeXT)
|
|
{
|
|
int t = open("/dev/tty", O_RDWR);
|
|
if (t < 0)
|
|
fatal(0, "Failed to open /dev/tty", errno);
|
|
close(fds);
|
|
fds = t;
|
|
}
|
|
#endif
|
|
/* Set slave's termios and window size */
|
|
if (slave_termios != NULL) {
|
|
if (tcsetattr(fds, TCSANOW, slave_termios) < 0)
|
|
return -1;
|
|
}
|
|
#ifdef TIOCSWINSZ
|
|
if (slave_winsize != NULL) {
|
|
if (ioctl(fds, TIOCSWINSZ, slave_winsize) < 0)
|
|
return -1;
|
|
}
|
|
#endif
|
|
/* slave becomes stdin/stdout/stderr of child */
|
|
if (dup2(fds, STDIN_FILENO) != STDIN_FILENO)
|
|
return -1;
|
|
if (dup2(fds, STDOUT_FILENO) != STDOUT_FILENO)
|
|
return -1;
|
|
if (dup2(fds, STDERR_FILENO) != STDERR_FILENO)
|
|
return -1;
|
|
if (fds > STDERR_FILENO)
|
|
close(fds);
|
|
return(0); /* child returns 0 just like fork() */
|
|
}
|
|
else { /* Parent */
|
|
*ptrfdm = fdm; /* Return fd of master */
|
|
return(pid); /* Parent returns pid of child */
|
|
}
|
|
}
|
|
|
|
int
|
|
forkpty(int *ptrfdm,
|
|
char *slave_name,
|
|
struct termios *slave_termios,
|
|
struct winsize *slave_winsize)
|
|
{
|
|
return forkpty_truncate (ptrfdm,
|
|
slave_name,
|
|
MaxPathLen,
|
|
slave_termios,
|
|
slave_winsize);
|
|
}
|
|
|
|
#endif /* HAVE_FORKPTY */
|