freebsd-dev/crypto/kerberosIV/appl/bsd/forkpty.c
2000-01-09 08:31:47 +00:00

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 */