#include "bsd_locl.h"

RCSID("$Header: /afs/nada.kth.se/src/packages/kth-krb/SourceRepository/krb4/appl/bsd/forkpty.c,v 1.10 1995/10/24 14:02:56 bg Exp $");

#include <termios.h>

#ifdef HAVE_STROPTS_H
#include <stropts.h>
#endif

#if (defined(sun) && !defined(__svr4__)) || defined(__hpux)
#define FORKPTY_OK
#endif

#ifndef FORKPTY_OK
int
forkpty(int *amaster, char *name, struct termios *termp, struct winsize *winp)
{
    fatal(0, "Protocol not yet supported, use telnet", 0);
    return -1;
}
#else

#ifdef HAVE_PTSNAME
extern char *ptsname(int fd);
#else
static char *ptsname(int fd) { return NULL; }
#endif

static int
ptym_open(char *pts_name, int *streams_pty)
{
    int	fdm;
    char *ptr1, *ptr2;

    /* Try clone device master ptys */
    *streams_pty = 1;
    fdm = open("/dev/ptmx", O_RDWR);
    if (fdm < 0)
	fdm = open("/dev/ptc", O_RDWR);
    if (fdm < 0)
	fdm = open("/dev/ptm", O_RDWR);
    if (fdm < 0)
	fdm = open("/dev/ptym/clone", O_RDWR);

    if (fdm >= 0) {
	if ( (ptr1 = ptsname(fdm)) != NULL) /* Get slave's name */
	    strcpy(pts_name, ptr1); /* Return name of slave */  
	else {
	    close(fdm);
	    return(-4);
	}
#ifdef HAVE_GRANTPT
	if (grantpt(fdm) < 0) {	/* Grant access to slave */
	    close(fdm);
	    return(-2);
	}
#endif
#ifdef HAVE_UNLOCKPT
	if (unlockpt(fdm) < 0) {	/* Clear slave's lock flag */
	    close(fdm);
	    return(-3);
	}
#endif
	return(fdm);			/* return fd of master */
    }
    
    /* Try BSD style master ptys */
    *streams_pty = 0;
#ifdef __hpux
    strcpy(pts_name, "/dev/ptym/ptyXY");
#define pts_name_X pts_name[13]
#define pts_name_Y pts_name[14]
#else
    strcpy(pts_name, "/dev/ptyXY");
#define pts_name_X pts_name[8]
#define pts_name_Y pts_name[9]
#endif
    /* array index: 0123456789 (for references in following code) */
    for (ptr1 = "pqrstuvwxyzPQRST"; *ptr1 != 0; ptr1++)
	{
	    pts_name_X = *ptr1;
	    for (ptr2 = "0123456789abcdefghijklmnopqrstuv";
		 *ptr2 != 0;
		 ptr2++)
		{
		    pts_name_Y = *ptr2;
		    /* Try to open master */
		    if ( (fdm = open(pts_name, O_RDWR)) < 0)
			continue; /* Try next pty device */
			    
#if defined(sun) && !defined(__svr4__)
		    /* 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
#ifdef __hpux
		    sprintf(pts_name, "/dev/pty/tty%c%c", *ptr1, *ptr2);
#else
		    pts_name[5] = 't'; /* Change "pty" to "tty" */
#endif
		    return fdm;	/* All done! */
		}
	}
    /* We failed to find BSD style pty */
    errno = ENOENT;
    return -1;
}

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);
	}
#ifdef I_PUSH
	if (ioctl(fds, I_PUSH, "ptem") < 0) {
	    close(fdm);
	    close(fds);
	    return(-6);
	}
	if (ioctl(fds, I_PUSH, "ldterm") < 0) {
	    close(fdm);
	    close(fds);
	    return(-7);
	}
	/*
	 * Not all systems have (or need) modules ttcompat and pckt so
	 * don't flag it as a fatal error if they don't exist.
	 */
	if (ioctl(fds, I_PUSH, "ttcompat") < 0 && errno != EINVAL) {
	    close(fdm);
	    close(fds);
	    return(-8);
	}
	if (ioctl(fds, I_PUSH, "pckt") < 0 && errno != EINVAL) {
	    close(fdm);
	    close(fds);
	    return(-9);
	}
#endif /* I_PUSH */
    } 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 */
	chown(pts_name, getuid(), gid);
	chmod(pts_name, S_IRUSR | S_IWUSR | S_IWGRP);

	if ( (fds = open(pts_name, O_RDWR)) < 0) {
	    close(fdm);
	    return(-1);
	}
    }
    return(fds);
}

int
forkpty(int *ptrfdm, char *slave_name, struct termios *slave_termios, struct winsize *slave_winsize)
{
    int		fdm, fds, streams_pty;
    pid_t	pid;
    char	pts_name[20];

    if ( (fdm = ptym_open(pts_name, &streams_pty)) < 0)
	return -1;

    if (slave_name != NULL)
	strcpy(slave_name, pts_name);	/* Return name of slave */

    if ( (pid = fork()) < 0)
	return(-1);
    else if (pid == 0) {		/* Child */
	if (setsid() < 0)
	    return -1;

	/* 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
	/* Set slave's termios and window size */
	if (slave_termios != NULL) {
	    if (tcsetattr(fds, TCSANOW, slave_termios) < 0)
		return -1;
	}
	if (slave_winsize != NULL) {
	    if (ioctl(fds, TIOCSWINSZ, slave_winsize) < 0)
		return -1;
	}
	/* 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 */
    }
}

#endif
