/*
 *   stunnel       Universal SSL tunnel
 *   Copyright (c) 1998-2005 Michal Trojnara <Michal.Trojnara@mirt.net>
 *                 All Rights Reserved
 *
 *   Based on a Public Domain code by Tatu Ylonen <ylo@cs.hut.fi>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *   In addition, as a special exception, Michal Trojnara gives
 *   permission to link the code of this program with the OpenSSL
 *   library (or with modified versions of OpenSSL that use the same
 *   license as OpenSSL), and distribute linked combinations including
 *   the two.  You must obey the GNU General Public License in all
 *   respects for all of the code used other than OpenSSL.  If you modify
 *   this file, you may extend this exception to your version of the
 *   file, but you are not obligated to do so.  If you do not wish to
 *   do so, delete this exception statement from your version.
 */

#include "common.h"
#include "prototypes.h"

#ifdef HAVE_UTIL_H
#include <util.h>
#endif /* HAVE_UTIL_H */

#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif /* HAVE_SYS_IOCTL_H */

/* Pty allocated with _getpty gets broken if we do I_PUSH:es to it. */
#if defined(HAVE__GETPTY) || defined(HAVE_OPENPTY)
#undef HAVE_DEV_PTMX
#endif /* HAVE__GETPTY || HAVE_OPENPTY */

#ifdef HAVE_PTY_H
#include <pty.h>
#endif /* HAVE_PTY_H */

#ifndef O_NOCTTY
#define O_NOCTTY 0
#endif /* O_NOCTTY */

/*
 * Allocates and opens a pty.  Returns -1 if no pty could be allocated, or
 * zero if a pty was successfully allocated.  On success, open file
 * descriptors for the pty and tty sides and the name of the tty side are
 * returned (the buffer must be able to hold at least 64 characters).
 */

int pty_allocate(int *ptyfd, int *ttyfd, char *namebuf, int namebuflen) {
#if defined(HAVE_OPENPTY) || defined(BSD4_4) && !defined(__INNOTEK_LIBC__)
    /* openpty(3) exists in OSF/1 and some other os'es */
    char buf[64];
    int i;

    i = openpty(ptyfd, ttyfd, buf, NULL, NULL);
    if (i < 0) {
        ioerror("openpty");
        return -1;
    }
    safecopy(namebuf, buf); /* possible truncation */
    return 0;
#else /* HAVE_OPENPTY */
#ifdef HAVE__GETPTY
    /*
     * _getpty(3) exists in SGI Irix 4.x, 5.x & 6.x -- it generates more
     * pty's automagically when needed
     */
    char *slave;

    slave = _getpty(ptyfd, O_RDWR, 0622, 0);
    if (slave == NULL) {
        ioerror("_getpty");
        return -1;
    }
    safecopy(namebuf, slave);
    /* Open the slave side. */
    *ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
    if (*ttyfd < 0) {
        ioerror(namebuf);
        close(*ptyfd);
        return -1;
    }
    return 0;
#else /* HAVE__GETPTY */
#if defined(HAVE_DEV_PTMX)
    /*
     * This code is used e.g. on Solaris 2.x.  (Note that Solaris 2.3
     * also has bsd-style ptys, but they simply do not work.)
     */
    int ptm;
    char *pts;

    ptm = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    if (ptm < 0) {
        ioerror("/dev/ptmx");
        return -1;
    }
    if (grantpt(ptm) < 0) {
        ioerror("grantpt");
        /* return -1; */
        /* Can you tell me why it doesn't work? */
    }
    if (unlockpt(ptm) < 0) {
        ioerror("unlockpt");
        return -1;
    }
    pts = ptsname(ptm);
    if (pts == NULL)
        s_log(LOG_ERR, "Slave pty side name could not be obtained");
    safecopy(namebuf, pts);
    *ptyfd = ptm;

    /* Open the slave side. */
    *ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
    if (*ttyfd < 0) {
        ioerror(namebuf);
        close(*ptyfd);
        return -1;
    }
    /* Push the appropriate streams modules, as described in Solaris pts(7). */
    if (ioctl(*ttyfd, I_PUSH, "ptem") < 0)
        ioerror("ioctl I_PUSH ptem");
    if (ioctl(*ttyfd, I_PUSH, "ldterm") < 0)
        ioerror("ioctl I_PUSH ldterm");
    if (ioctl(*ttyfd, I_PUSH, "ttcompat") < 0)
        ioerror("ioctl I_PUSH ttcompat");
    return 0;
#else /* HAVE_DEV_PTMX */
#ifdef HAVE_DEV_PTS_AND_PTC
    /* AIX-style pty code. */
    const char *name;

    *ptyfd = open("/dev/ptc", O_RDWR | O_NOCTTY);
    if (*ptyfd < 0) {
        ioerror("open(/dev/ptc)");
        return -1;
    }
    name = ttyname(*ptyfd);
    if (!name) {
        s_log(LOG_ERR, "Open of /dev/ptc returns device for which ttyname fails");
        return -1;
    }
    safecopy(namebuf, name);
    *ttyfd = open(name, O_RDWR | O_NOCTTY);
    if (*ttyfd < 0) {
        ioerror(name);
        close(*ptyfd);
        return -1;
    }
    return 0;
#else /* HAVE_DEV_PTS_AND_PTC */
    /* BSD-style pty code. */
    char buf[64];
    int i;
    const char *ptymajors = "pqrstuvwxyzabcdefghijklmnoABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const char *ptyminors = "0123456789abcdef";
    int num_minors = strlen(ptyminors);
    int num_ptys = strlen(ptymajors) * num_minors;

    for (i = 0; i < num_ptys; i++) {
#ifdef HAVE_SNPRINTF
        snprintf(buf, sizeof buf,
#else
        sprintf(buf,
#endif
             "/dev/pty%c%c", ptymajors[i / num_minors],
             ptyminors[i % num_minors]);
        *ptyfd = open(buf, O_RDWR | O_NOCTTY);
        if (*ptyfd < 0)
            continue;
#ifdef HAVE_SNPRINTF
        snprintf(namebuf, namebuflen,
#else
        sprintf(namebuf,
#endif
            "/dev/tty%c%c",
            ptymajors[i / num_minors], ptyminors[i % num_minors]);

        /* Open the slave side. */
        *ttyfd = open(namebuf, O_RDWR | O_NOCTTY);
        if (*ttyfd < 0) {
            ioerror(namebuf);
            close(*ptyfd);
            return -1;
        }
        return 0;
    }
    return -1;
#endif /* HAVE_DEV_PTS_AND_PTC */
#endif /* HAVE_DEV_PTMX */
#endif /* HAVE__GETPTY */
#endif /* HAVE_OPENPTY */
}

/* The code below is currently not used */
#if 0

/* Releases the tty.  Its ownership is returned to root, and permissions to 0666. */

void pty_release(char *tty_name) {
    if(chown(tty_name, (uid_t)0, (gid_t)0)<0)
        s_log(LOG_DEBUG, "chown %.100s 0 0 failed: %.100s", tty_name,
            strerror(get_last_socket_error()));
    if(chmod(tty_name, (mode_t)0666)<0)
        s_log(LOG_DEBUG, "chmod %.100s 0666 failed: %.100s", tty_name,
            strerror(get_last_socket_error()));
}

/* Makes the tty the processes controlling tty and sets it to sane modes. */

void pty_make_controlling_tty(int *ttyfd, char *tty_name) {
    int fd;

    /* First disconnect from the old controlling tty. */
#ifdef TIOCNOTTY
    fd = open("/dev/tty", O_RDWR | O_NOCTTY);
    if (fd >= 0) {
        (void) ioctl(fd, TIOCNOTTY, NULL);
        close(fd);
    }
#endif /* TIOCNOTTY */
    if (setsid() < 0)
        ioerror("setsid");

    /*
     * Verify that we are successfully disconnected from the controlling
     * tty.
     */
    fd = open("/dev/tty", O_RDWR | O_NOCTTY);
    if (fd >= 0) {
        s_log(LOG_ERR, "Failed to disconnect from controlling tty");
        close(fd);
    }
    /* Make it our controlling tty. */
#ifdef TIOCSCTTY
    s_log(LOG_DEBUG, "Setting controlling tty using TIOCSCTTY");
    /*
     * We ignore errors from this, because HPSUX defines TIOCSCTTY, but
     * returns EINVAL with these arguments, and there is absolutely no
     * documentation.
     */
    ioctl(*ttyfd, TIOCSCTTY, NULL);
#endif /* TIOCSCTTY */
    fd = open(tty_name, O_RDWR);
    if (fd < 0)
        ioerror(tty_name);
    else
        close(fd);

    /* Verify that we now have a controlling tty. */
    fd = open("/dev/tty", O_WRONLY);
    if (fd < 0)
        ioerror("open /dev/tty failed - could not set controlling tty");
    else {
        close(fd);
    }
}

#endif

/* End of pty.c */
