/*	$Id: Getty.c++ 823 2008-04-26 22:34:29Z faxguy $ */
/*
 * Copyright (c) 1990-1996 Sam Leffler
 * Copyright (c) 1991-1996 Silicon Graphics, Inc.
 * HylaFAX is a trademark of Silicon Graphics
 *
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Sam Leffler and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Sam Leffler and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 * 
 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */
#include "Getty.h"
#include "UUCPLock.h"

#include <termios.h>
#include <sys/param.h>

#include "Sys.h"

/*
 * FAX Server Getty Support Base Class.
 */

Getty::Getty(const char* p, const fxStr& l, const fxStr& s)
    : getty(p)
    , line(l)
    , speed(s)
    , tzVar("TZ")
    , langVar("LANG")
{
    pid = 0;
    argv[0] = NULL;
    argv[1] = NULL;
}

Getty::~Getty()
{
}

pid_t Getty::getPID() const		{ return pid; }
void Getty::setPID(pid_t p)		{ pid = p; }
const char* Getty::getLine() const	{ return line; }

static void
sigHUP(int)
{
    Sys::close(STDIN_FILENO);
    Sys::close(STDOUT_FILENO);
    Sys::close(STDERR_FILENO);
    sleep(5);
    _exit(1);
}

/*
 * Parse the getty argument string and
 * substitute runtime parameters:
 *
 *    %l	tty device name
 *    %s	current baud rate
 *
 * The resultant argv array is used to
 * exec getty below.
 */
void
Getty::setupArgv(const char* args, const CallID& callid)
{
    argbuf = args;
    u_int l;
    /*
     * Substitute escape sequences.
     */
    for (l = 0; l < argbuf.length();) {
	l = argbuf.next(l, '%');
	if (l >= argbuf.length()-1)
	    break;
	switch (argbuf[l+1]) {
	case 'l':			// %l = tty device name
	    argbuf.remove(l,2);
	    argbuf.insert(line, l);
	    l += line.length();		// avoid loops
	    break;
	case 's':			// %s = tty speed
	    argbuf.remove(l,2);
	    argbuf.insert(speed, l);
	    l += speed.length();	// avoid loops
	    break;
	case 'a':			// undocumented backwards compatibility, CIDName
	    argbuf.remove(l-1,3);
	    if (callid.size() > 1) {
		argbuf.insert(callid.id(1), l);
		l += callid.length(1);
	    }
	    break;
	case 'u':			// undocumented backwards compatibility, CIDNumber
	    argbuf.remove(l-1,3);
	    if (callid.size()) {
		argbuf.insert(callid.id(0), l);
		l += callid.length(0);
	    }
	    break;            
	case '1': case '2': case '3': case '4': case '5': 
	case '6': case '7': case '8': case '9':
	    {
		u_int id = argbuf[l+1] - 0x31;
		argbuf.remove(l,2);
		if (id < callid.size()) {
		    argbuf.insert(callid.id(id), l);
		    l += callid.length(id);
		}
	    }
	    break;
	case '%':			// %% = %
	    argbuf.remove(l,1);
	    break;
	}
    }
    /*
     * Crack argument string and setup argv.
     */
    argv[0] = &getty[getty.nextR(getty.length(), '/')];
    u_int nargs = 1;
    for (l = 0; l < argbuf.length() && nargs < GETTY_MAXARGS-1;) {
	l = argbuf.skip(l, " \t");
	u_int token = l;
	l = argbuf.next(l, " \t");
	if (l > token) {
	    if (l < argbuf.length())
		argbuf[l++] = '\0';		// null terminate argument
	    argv[nargs++] = &argbuf[token];
	}
    }
    argv[nargs] = NULL;
}

fxStr
Getty::getCmdLine() const
{
    fxStr s(getty);

    for (u_int i = 1; argv[i] != NULL; i++) {
	s.append(' ');
	s.append(argv[i]);
    }
    return (s);
}

void
Getty::addEnvVar(int& envc, char* env[], fxStr& var)
{
    const char* val = getenv(var);
    if (val) {
        var.append(fxStr::format("=%s", val));
        const char* v = var;
        env[envc++] = (char*)v;
    }
}

/*
 * Setup a getty session and if successful exec the
 * getty program.  Note that this is always run in
 * the child.
 */
void
Getty::run(int fd, bool parentIsInit)
{
    if (Sys::chdir(_PATH_DEV) < 0)
	fatal("chdir: %m");
    /*
     * Reset signals known to be handled
     * by the current process (XXX).
     */
    signal(SIGTERM, fxSIGHANDLER(SIG_DFL));
    signal(SIGHUP, fxSIGHANDLER(sigHUP));
    /*
     * After the session is properly setup, the
     * stdio streams should be hooked to the tty
     * and the modem descriptor should be closed.
     */
    setupSession(fd);
    /*
     * If this getty is not being started from init
     * then pass a restricted environment.  Otherwise
     * just pass everything through.
     */
    if (!parentIsInit) {
        char* env[10];
        int envc = 0;
        addEnvVar(envc, env, tzVar);		// timezone
        addEnvVar(envc, env, langVar);		// for locale
        env[envc] = NULL;
        Sys::execve(getty, argv, env);	
    } else
        Sys::execv(getty, argv);
    _exit(127);
}

/*
 * Setup descriptors for stdout, and stderr.
 */
void
Getty::setupSession(int fd)
{
    struct stat sb;
    Sys::fstat(fd, sb);
#if HAS_FCHOWN
    if (fchown(fd, 0, sb.st_gid) < 0)
	fatal("Unable to fchown: %m");
#else
    Sys::chown(getLine(), 0, sb.st_gid);
#endif
#if HAS_FCHMOD
    fchmod(fd, 0622);
#else
    Sys::chmod(getLine(), 0622);
#endif
    if (dup2(fd, STDOUT_FILENO) < 0)
	fatal("Unable to dup stdin to stdout: %m");
    if (dup2(fd, STDERR_FILENO) < 0)
	fatal("Unable to dup stdin to stderr: %m");
}

bool
Getty::wait(int& status, bool block)
{
    return (Sys::waitpid(pid, status, block ? 0 : WNOHANG) == pid);
}

void
Getty::hangup()
{
    // NB: this is executed in the parent
    fxStr device = fxStr::format("%s" | line, _PATH_DEV);
    Sys::chown(device, UUCPLock::getUUCPUid(), UUCPLock::getUUCPGid());
    Sys::chmod(device, 0600);			// reset protection
}

extern void vlogError(const char* fmt, va_list ap);

void
Getty::fatal(const char* fmt ...)
{
    va_list ap;
    va_start(ap, fmt);
    vlogError(fmt, ap);
    hangup();
    va_end(ap);
}
