/* $TOG: session.c /main/79 1998/02/09 13:56:17 kaleb $ */
/* $Id: session.c,v 1.50.2.2 2001/03/15 17:35:05 ossi Exp $ */
/*

Copyright 1988, 1998  The Open Group

All Rights Reserved.

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of The Open Group shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization
from The Open Group.

*/
/* $XFree86: xc/programs/xdm/session.c,v 3.23 2000/06/17 00:27:34 dawes Exp $ */

/*
 * xdm - display manager daemon
 * Author:  Keith Packard, MIT X Consortium
 *
 * session.c
 */

#include "dm.h"
#include "dm_auth.h"
#include "dm_error.h"
#include "greet.h"

#include <X11/Xlib.h>
#include <signal.h>
#include <X11/Xatom.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <grp.h>	/* for initgroups */
#ifdef AIXV3
# include <usersec.h>
#endif
#ifdef HAVE_CRYPT_H
# include <crypt.h>
#else
# define _XOPEN_SOURCE
# include <unistd.h>
#endif
#ifdef SECURE_RPC
# include <rpc/rpc.h>
# include <rpc/key_prot.h>
#endif
#ifdef K5AUTH
# include <krb5/krb5.h>
#endif
#ifdef USE_PAM
# include <security/pam_appl.h>
#endif

#ifdef KRB4
/* Some systems define an des_encrypt() in their crypt.h (where it
   doesn't belong!), which conflicts with the one from KTH Kerberos.  As we
   are not using it anyway, we temporarily redefine it.  */
# define des_encrypt des_encrypt_faked_XXX
# include <krb.h>
# ifdef AFS
#  include <kafs.h>
# endif
# undef des_encrypt
#endif

#ifndef GREET_USER_STATIC
# include <dlfcn.h>
# ifndef RTLD_NOW
#  define RTLD_NOW 1
# endif
#endif

#ifdef CSRG_BASED
# include <sys/param.h>
# ifdef HAS_SETUSERCONTEXT
#  include <login_cap.h>
#  include <pwd.h>
# endif
#endif

static	int	runAndWait (char **args, char **environ);

#if defined(CSRG_BASED) || defined(__osf__) || defined(__DARWIN__)
# include <sys/types.h>
# include <grp.h>
#else
/* should be in <grp.h> */
extern void    setgrent(void);
extern struct group    *getgrent(void);
extern void    endgrent(void);
#endif

#ifdef USESHADOW
extern	struct spwd	*getspnam(GETSPNAM_ARGS);
extern	void	endspent(void);
#endif
#if defined(CSRG_BASED)
# include <pwd.h>
# include <unistd.h>
#else
extern	struct passwd	*getpwnam(GETPWNAM_ARGS);
# ifdef linux
extern  void	endpwent(void);
# endif
extern	char	*crypt(CRYPT_ARGS);
#endif

#ifdef USE_PAM
pam_handle_t **thepamh(void)
{
	static pam_handle_t *pamh = NULL;
	return &pamh;
}
#endif

static	struct dlfuncs	dlfuncs = {
	PingServer,
	SessionPingFailed,
	Debug,
	RegisterCloseOnFork,
	SecureDisplay,
	UnsecureDisplay,
	ClearCloseOnFork,
	SetupDisplay,
	LogError,
	SessionExit,
	DeleteXloginResources,
	source,
	defaultEnv,
	setEnv,
	putEnv,
	parseArgs,
	printEnv,
	systemEnv,
	LogOutOfMem,
	setgrent,
	getgrent,
	endgrent,
#ifdef USESHADOW
	getspnam,
#ifndef QNX4
	endspent,
#endif /* QNX4 doesn't use endspent */
#endif
	getpwnam,
#ifdef linux
	endpwent,
#endif
	crypt,
#ifdef USE_PAM
	thepamh,
#endif
	};

#ifdef X_NOT_STDC_ENV
extern int errno;
#endif

static Bool StartClient(
    struct verify_info	*verify,
    struct display	*d,
    int			*pidp,
    char		*name,
    char		*passwd);

static int			clientPid;
static struct greet_info	greet;
static struct verify_info	verify;

static Jmp_buf	abortSession;

/* ARGSUSED */
static SIGVAL
catchTerm (int n)
{
    Longjmp (abortSession, 1);
}

static Jmp_buf	pingTime;

/* ARGSUSED */
static SIGVAL
catchAlrm (int n)
{
    Longjmp (pingTime, 1);
}

static Jmp_buf	tenaciousClient;

/* ARGSUSED */
static SIGVAL
waitAbort (int n)
{
    Longjmp (tenaciousClient, 1);
}

#if defined(_POSIX_SOURCE) || defined(SYSV) || defined(SVR4)
#define killpg(pgrp, sig) kill(-(pgrp), sig)
#endif

static void
AbortClient (int pid)
{
    int	sig = SIGTERM;
#ifdef __STDC__
    volatile int	i;
#else
    int	i;
#endif
    int	retId;
    for (i = 0; i < 4; i++) {
	if (killpg (pid, sig) == -1) {
	    switch (errno) {
	    case EPERM:
		LogError ("xdm can't kill client\n");
	    case EINVAL:
	    case ESRCH:
		return;
	    }
	}
	if (!Setjmp (tenaciousClient)) {
	    (void) Signal (SIGALRM, waitAbort);
	    (void) alarm ((unsigned) 10);
	    retId = wait ((waitType *) 0);
	    (void) alarm ((unsigned) 0);
	    (void) Signal (SIGALRM, SIG_DFL);
	    if (retId == pid)
		break;
	} else
	    (void) Signal (SIGALRM, SIG_DFL);
	sig = SIGKILL;
    }
}

/* XXX dissolve this function */
void
SessionPingFailed (struct display *d)
{
    if (clientPid > 1)
    {
    	AbortClient (clientPid);
	source (verify.systemEnviron, d->reset);
    }
    SessionExit (d, RESERVER_AL_DISPLAY, TRUE);
}

/*
 * We need our own error handlers because we can't be sure what exit code Xlib
 * will use, and our Xlib does exit(1) which matches REMANAGE_DISPLAY, which
 * can cause a race condition leaving the display wedged.  We need to use
 * RESERVER_DISPLAY for IO errors, to ensure that the manager waits for the
 * server to terminate.  For other X errors, we should give up.
 */

/*ARGSUSED*/
static int
IOErrorHandler (Display *dpy)
{
    LogError("fatal IO error %d (%s)\n", errno, _SysErrorMsg(errno));
    exit(RESERVER_AL_DISPLAY);
    /*NOTREACHED*/
    return 0;
}

static int
ErrorHandler(Display *dpy, XErrorEvent *event)
{
    LogError("X error\n");
    if (event->error_code == BadImplementation)
	exit(UNMANAGE_DISPLAY);
    return 0;
}

void
ManageSession (struct display *d)
{
    static int		exitCode = OBEYSESS_DISPLAY;
    static int		pid = 0;
    Display		*dpy;
    greet_user_rtn	greet_stat;
    static GreetUserProc greet_user_proc = NULL;
#ifndef GREET_USER_STATIC
    void		*greet_lib_handle;
#endif

    Debug ("ManageSession %s\n", d->name);
    (void)XSetIOErrorHandler(IOErrorHandler);
    (void)XSetErrorHandler(ErrorHandler);
    SetTitle(d->name, (char *) 0);
    /*
     * Load system default Resources
     */
    LoadXloginResources (d);

#ifdef GREET_USER_STATIC
    greet_user_proc = GreetUser;
#else
    Debug("ManageSession: loading greeter library %s\n", greeterLib);
    greet_lib_handle = dlopen(greeterLib, RTLD_NOW);
    if (greet_lib_handle != NULL)
	greet_user_proc = (GreetUserProc)dlsym(greet_lib_handle, "GreetUser");
    if (greet_user_proc == NULL)
    {
	LogError("%s while loading %s\n", dlerror(), greeterLib);
	exit(UNMANAGE_DISPLAY);
    }
#endif

    /* tell the possibly dynamically loaded greeter function
     * what data structure formats to expect.
     * These version numbers are registered with The Open Group. */
    verify.version = 1;
    greet.version = 1;
    greet_stat = (*greet_user_proc)(d, &dpy, &verify, &greet, &dlfuncs);

#ifndef GREET_USER_STATIC
/*    Debug("ManageSession: unloading greeter library ...\n");
    dlclose (greet_lib_handle);
*/
#endif

#ifdef XDMCP
/*
    if (greet_stat == Greet_RunChooser)
	RunChooser (d);
*/
#endif

    if (greet_stat == Greet_Success)
    {
	clientPid = 0;
	if (!Setjmp (abortSession)) {
	    (void) Signal (SIGTERM, catchTerm);
	    /*
	     * Start the clients, changing uid/groups
	     *	   setting up environment and running the session
	     */
	    if (StartClient (&verify, d, &clientPid, greet.name, greet.password)) {
		Debug ("Client Started\n");
		/*
		 * Wait for session to end,
		 */
		for (;;) {
		    if (d->pingInterval)
		    {
			if (!Setjmp (pingTime))
			{
			    (void) Signal (SIGALRM, catchAlrm);
			    (void) alarm (d->pingInterval * 60);
			    pid = wait ((waitType *) 0);
			    (void) alarm (0);
			}
			else
			{
			    (void) alarm (0);
			    if (!PingServer (d, (Display *) NULL))
				SessionPingFailed (d);
			}
		    }
		    else
		    {
			pid = wait ((waitType *) 0);
		    }
		    if (pid == clientPid)
			break;
		}
		/* 
    		 * Sometimes the Xsession somehow manages to exit before a 
		 * server crash is noticed - so we sleep a bit and wait for
		 * being killed.
		 */
		if (!PingServer (d, (Display *) NULL)) {
		    Debug("X-Server dead upon session exit.\n");
		    if (d->displayType.origin == FromXDMCP)
			SessionPingFailed (d);
		    else
			sleep (10);
		}
	    } else {
		LogError ("session start failed\n");
	    }
	} else {
	    /*
	     * when terminating the session, nuke
	     * the child and then run the reset script
	     */
	    AbortClient (clientPid);
	    exitCode = RESERVER_AL_DISPLAY;
	}
    }
    /*
     * run system-wide reset file
     */
    Debug ("Source reset program %s\n", d->reset);
    source (verify.systemEnviron, d->reset);

    SessionExit (d, exitCode, TRUE);
}

void
LoadXloginResources (struct display *d)
{
    char	**args;
    char	**env = 0;

    if (d->resources[0] && access (d->resources, 4) == 0) {
	env = systemEnv (d, (char *) 0, (char *) 0);
	args = parseArgs ((char **) 0, d->xrdb);
	args = parseArgs (args, d->resources);
	Debug ("Loading resource file: %s\n", d->resources);
	(void) runAndWait (args, env);
	freeArgs (args);
	freeEnv (env);
    }
}

void
SetupDisplay (struct display *d)
{
    char	**env = 0;

    if (d->setup && d->setup[0])
    {
    	env = systemEnv (d, (char *) 0, (char *) 0);
    	(void) source (env, d->setup);
    	freeEnv (env);
    }
}

/*ARGSUSED*/
void
DeleteXloginResources (struct display *d, Display *dpy)
{
    int i;
    Atom prop = XInternAtom(dpy, "SCREEN_RESOURCES", True);

    XDeleteProperty(dpy, RootWindow (dpy, 0), XA_RESOURCE_MANAGER);
    if (prop) {
	for (i = ScreenCount(dpy); --i >= 0; )
	    XDeleteProperty(dpy, RootWindow (dpy, i), prop);
    }
}

static Jmp_buf syncJump;

/* ARGSUSED */
static SIGVAL
syncTimeout (int n)
{
    Longjmp (syncJump, 1);
}

void
SecureDisplay (struct display *d, Display *dpy)
{
    Debug ("SecureDisplay %s\n", d->name);
    (void) Signal (SIGALRM, syncTimeout);
    if (Setjmp (syncJump)) {
	LogError ("WARNING: display %s could not be secured\n",
		   d->name);
	SessionExit (d, RESERVER_DISPLAY, FALSE);
    }
    (void) alarm ((unsigned) d->grabTimeout);
    Debug ("Before XGrabServer %s\n", d->name);
    XGrabServer (dpy);
    if (XGrabKeyboard (dpy, DefaultRootWindow (dpy), True, GrabModeAsync,
		       GrabModeAsync, CurrentTime) != GrabSuccess)
    {
	(void) alarm (0);
	(void) Signal (SIGALRM, SIG_DFL);
	LogError ("WARNING: keyboard on display %s could not be secured\n",
		  d->name);
	SessionExit (d, RESERVER_DISPLAY, FALSE);
    }
    Debug ("XGrabKeyboard succeeded %s\n", d->name);
    (void) alarm (0);
    (void) Signal (SIGALRM, SIG_DFL);
    pseudoReset (dpy);
    if (!d->grabServer)
    {
	XUngrabServer (dpy);
	XSync (dpy, 0);
    }
    Debug ("done secure %s\n", d->name);
}

void
UnsecureDisplay (struct display *d, Display *dpy)
{
    Debug ("Unsecure display %s\n", d->name);
    if (d->grabServer)
    {
	XUngrabServer (dpy);
	XSync (dpy, 0);
    }
}

static int removeAuth = 0;

void
SessionExit (struct display *d, int status, int dummy /* we know better */)
{
#ifdef USE_PAM
    pam_handle_t **pamh = thepamh();
#endif
    /* make sure the server gets reset after the session is over */
    if (d->serverPid >= 2 && d->resetSignal)
	kill (d->serverPid, d->resetSignal);
    else
	ResetServer (d);
    if (removeAuth)
    {
#ifdef USE_PAM
	if (*pamh) {
	    /* shutdown PAM session */
	    pam_setcred(*pamh, PAM_DELETE_CRED);
	    pam_close_session(*pamh, 0);
	    pam_end(*pamh, PAM_SUCCESS);
	    *pamh = NULL;
	}
#endif
	setgid (verify.gid);
	setuid (verify.uid);
	RemoveUserAuthorization (d, &verify);
#ifdef K5AUTH
	/* do like "kdestroy" program */
        {
	    krb5_error_code code;
	    krb5_ccache ccache;

	    code = Krb5DisplayCCache(d->name, &ccache);
	    if (code)
		LogError("%s while getting Krb5 ccache to destroy\n",
			 error_message(code));
	    else {
		code = krb5_cc_destroy(ccache);
		if (code) {
		    if (code == KRB5_FCC_NOFILE) {
			Debug ("No Kerberos ccache file found to destroy\n");
		    } else
			LogError("%s while destroying Krb5 credentials cache\n",
				 error_message(code));
		} else
		    Debug ("Kerberos ccache destroyed\n");
		krb5_cc_close(ccache);
	    }
	}
#endif /* K5AUTH */
#ifndef USE_PAM
# ifdef KRB4
	if (krbtkfile[0]) {
	    (void) dest_tkt();
#  ifdef AFS
	    if (k_hasafs())
		(void) k_unlog();
#  endif
	}
# endif
#else /* !USE_PAM */
    } else {
	if (*pamh) {
	    pam_end(*pamh, PAM_SUCCESS);
	    *pamh = NULL;
	}
#endif
    }
    Debug ("Display %s exiting with status %d\n", d->name, status);
    exit (status);
}

static Bool
StartClient (
    struct verify_info	*verify,
    struct display	*d,
    int			*pidp,
    char		*name,
    char		*passwd)
{
    char	**f, *home;
    char	*failsafeArgv[2];
    int		pid;
#ifndef AIXV3
# ifdef USE_PAM
    pam_handle_t **pamh = thepamh();
# else
#  ifdef HAS_SETUSERCONTEXT
    struct passwd	*pwd;
    char	**e, **envinit;
    extern char	**environ;
#  endif
# endif
#endif

    Debug ("StartSession:");
    if (verify->argv)
	for (f = verify->argv; *f; f++)
	    Debug (" %s", *f);
    Debug (" ;");
    if (verify->userEnviron)
	for (f = verify->userEnviron; *f; f++)
	    Debug (" %s", *f);
    Debug ("\n");
#ifdef USE_PAM
    if (*pamh)
	pam_open_session(*pamh, 0);
#endif    
    removeAuth = 1;
    switch (pid = fork ()) {
    case 0:
	CleanUpChild ();
#ifdef XDMCP
	/* The chooser socket is not closed by CleanUpChild() */
	DestroyWellKnownSockets();
#endif

	/* Do system-dependent login setup here */

#ifndef AIXV3
# if !defined(HAS_SETUSERCONTEXT) || defined(USE_PAM)
	if (setgid(verify->gid) < 0)
	{
	    LogError("setgid %d (user \"%s\") failed, errno=%d\n",
		     verify->gid, name, errno);
	    return (0);
	}
#  ifndef QNX4
	if (initgroups(name, verify->gid) < 0)
	{
	    LogError("initgroups for \"%s\" failed, errno=%d\n", name, errno);
	    return (0);
	}
#  endif   /* QNX4 doesn't support multi-groups, no initgroups() */
#  ifdef USE_PAM
	if (*pamh) {
	    int i;
	    char **pam_env;

	    pam_setcred(*pamh, 0);

	    /* pass in environment variables set by libpam and modules it called */
	    pam_env = pam_getenvlist(*pamh);
	    for(i = 0; pam_env && pam_env[i]; i++)
		verify->userEnviron = putEnv(pam_env[i], verify->userEnviron);
	}
#  endif
#  if defined(BSD) && (BSD >= 199103)
	if (setlogin(name) < 0)
	{
	    LogError("setlogin for \"%s\" failed, errno=%d\n", name, errno);
	    return(0);
	}
#  endif
	if (setuid(verify->uid) < 0)
	{
	    LogError("setuid %d (user \"%s\") failed, errno=%d\n",
		     verify->uid, name, errno);
	    return (0);
	}
# else /* HAS_SETUSERCONTEXT && !USE_PAM */
	/*
	 * Destroy environment unless user has requested its preservation.
	 * We need to do this before setusercontext() because that may
	 * set or reset some environment variables.
	 */
	envinit = malloc(sizeof(char*));
	if (NULL == envinit) {
		LogError("malloc() of initial environment failed, errno=%d\n",
			 errno);
		return (0);
	}
	envinit[0] = NULL;
	environ = envinit;

	/*
	 * Set the user's credentials: uid, gid, groups,
	 * environment variables, resource limits, and umask.
	 */
	if (!(pwd = getpwnam(name)))
	{
	    LogError("getpwnam for \"%s\" failed, errno=%d\n", name, errno);
	    return (0);
	}

	if (setusercontext(NULL, pwd, pwd->pw_uid, LOGIN_SETALL) < 0)
	{
	    LogError("setusercontext for \"%s\" failed, errno=%d\n", name,
		     errno);
	    return (0);
	}
	endpwent();

	e = environ;
	while (*e)
	    verify->userEnviron = putEnv(*e++, verify->userEnviron);

# endif /* HAS_SETUSERCONTEXT */
#else /* AIXV3 */
	/*
	 * Set the user's credentials: uid, gid, groups,
	 * audit classes, user limits, and umask.
	 */
	if (setpcred(name, NULL) == -1)
	{
	    LogError("setpcred for \"%s\" failed, errno=%d\n", name, errno);
	    return (0);
	}

	{ 
	    extern char **newenv; /* from libs.a, this is set up by setpenv */
	    char **theenv;
	    int i;

	    /*
	     * Make a copy of the environment, because setpenv will trash it.
	     */
	    for (i = 0; verify->userEnviron[i++]; );
	    if (!(theenv = (char **)malloc(i * sizeof(char *)))) {
		Debug("Out of memory\n");
		return(0);
	    }
	    memcpy(theenv, verify->userEnviron, i * sizeof(char *));

	    /*
	     * Set the users process environment. Store protected variables and
	     * obtain updated user environment list. This call will initialize
	     * global 'newenv'. 
	     */
	    if (setpenv(name, PENV_INIT | PENV_ARGV | PENV_NOEXEC,
			theenv, NULL) != 0) {
		Debug("Can't set process environment (user=%s)\n", name);
		free(theenv);
		return(0);
	    }

	    /*
	     * Free old userEnviron and replace with newenv from setpenv().
	     */
	    free(theenv);
	    freeEnv(verify->userEnviron);
	    verify->userEnviron = newenv;
	}
#endif /* AIXV3 */

	/*
	 * for user-based authorization schemes,
	 * use the password to get the user's credentials.
	 */
#ifndef USE_PAM
# ifdef SECURE_RPC
	/* do like "keylogin" program */
	if (!passwd)
	    LogError("Warning: no password for NIS provided.\n");
	else
	{
	    char    netname[MAXNETNAMELEN+1], secretkey[HEXKEYBYTES+1];
	    int	    nameret, keyret;
	    int	    len;
	    int     key_set_ok = 0;

	    nameret = getnetname (netname);
	    Debug ("User netname: %s\n", netname);
	    len = strlen (passwd);
	    if (len > 8)
		bzero (passwd + 8, len - 8);
	    keyret = getsecretkey(netname, secretkey, passwd);
	    Debug ("getsecretkey returns %d, key length %d\n",
		    keyret, strlen (secretkey));
	    /* is there a key, and do we have the right password? */
	    if (keyret == 1)
	    {
		if (*secretkey)
		{
		    keyret = key_setsecret(secretkey);
		    Debug ("key_setsecret returns %d\n", keyret);
		    if (keyret == -1)
			LogError ("failed to set NIS secret key\n");
		    else
			key_set_ok = 1;
		}
		else
		{
		    /* found a key, but couldn't interpret it */
		    LogError ("password incorrect for NIS principal \"%s\"\n",
			      nameret ? netname : name);
		}
	    }
	    if (!key_set_ok)
	    {
		/* remove SUN-DES-1 from authorizations list */
		int i, j;
		for (i = 0; i < d->authNum; i++)
		{
		    if (d->authorizations[i]->name_length == 9 &&
			memcmp(d->authorizations[i]->name, "SUN-DES-1", 9) == 0)
		    {
			for (j = i+1; j < d->authNum; j++)
			    d->authorizations[j-1] = d->authorizations[j];
			d->authNum--;
			break;
		    }
		}
	    }
	    bzero(secretkey, strlen(secretkey));
	}
# endif
# ifdef K5AUTH
	/* do like "kinit" program */
	if (!passwd)
	    LogError("Warning: no password for Kerberos5 provided.\n");
	else
	{
	    int i, j;
	    int result;
	    extern char *Krb5CCacheName();

	    result = Krb5Init(name, passwd, d);
	    if (result == 0) {
		/* point session clients at the Kerberos credentials cache */
		verify->userEnviron =
		    setEnv(verify->userEnviron,
			   "KRB5CCNAME", Krb5CCacheName(d->name));
	    } else {
		for (i = 0; i < d->authNum; i++)
		{
		    if (d->authorizations[i]->name_length == 14 &&
			memcmp(d->authorizations[i]->name, "MIT-KERBEROS-5", 14) == 0)
		    {
			/* remove Kerberos from authorizations list */
			for (j = i+1; j < d->authNum; j++)
			    d->authorizations[j-1] = d->authorizations[j];
			d->authNum--;
			break;
		    }
		}
	    }
	}
# endif /* K5AUTH */
#endif /* USE_PAM */
	if (passwd)
	    bzero(passwd, strlen(passwd));
	SetUserAuthorization (d, verify);
	home = getEnv (verify->userEnviron, "HOME");
	if (home)
	    if (chdir (home) == -1) {
		LogError ("user \"%s\": cannot chdir to home \"%s\" (err %d), using \"/\"\n",
			  getEnv (verify->userEnviron, "USER"), home, errno);
		chdir ("/");
		verify->userEnviron = setEnv(verify->userEnviron, "HOME", "/");
	    }
	if (verify->argv) {
		Debug ("executing session %s\n", verify->argv[0]);
		execute (verify->argv, verify->userEnviron);
		LogError ("Session \"%s\" execution failed (err %d)\n", verify->argv[0], errno);
	} else {
		LogError ("Session has no command/arguments\n");
	}
	failsafeArgv[0] = d->failsafeClient;
	failsafeArgv[1] = 0;
	execute (failsafeArgv, verify->userEnviron);
	exit (1);
    case -1:
	if (passwd)
	    bzero(passwd, strlen(passwd));
	Debug ("StartSession, fork failed\n");
	LogError ("can't start session on \"%s\", fork failed, errno=%d\n",
		  d->name, errno);
	return 0;
    default:
	if (passwd)
	    bzero(passwd, strlen(passwd));
	Debug ("StartSession, fork succeeded %d\n", pid);
	*pidp = pid;
	return 1;
    }
}

int
source (char **environ, char *file)
{
    char	**args, *args_safe[2];
    int		ret;

    if (file && file[0]) {
	Debug ("source %s\n", file);
	args = parseArgs ((char **) 0, file);
	if (!args)
	{
	    args = args_safe;
	    args[0] = file;
	    args[1] = NULL;
	}
	ret = runAndWait (args, environ);
	freeArgs (args);
	return ret;
    }
    return 0;
}

static int
runAndWait (char **args, char **environ)
{
    int		pid, r;
    waitType	result;

    switch (pid = fork ()) {
    case 0:
	CleanUpChild ();
	execute (args, environ);
	LogError ("can't execute \"%s\" (err %d)\n", args[0], errno);
	exit (1);
    case -1:
	Debug ("fork failed\n");
	LogError ("can't fork to execute \"%s\" (err %d)\n", args[0], errno);
	return 1;
    default:
	while ((r = wait (&result)) != pid)
	    if (r < 0)
		break;
    }
    return waitVal (result);
}

void
execute (char **argv, char **environ)
{
    /* give /dev/null as stdin */
    (void) close (0);
    open ("/dev/null", O_RDONLY);
    /* make stdout follow stderr to the log file 
     * XXX this does not work! with syslog inherently, */
    dup2 (2, 1);
    execve (argv[0], argv, environ);
    /*
     * In case this is a shell script which hasn't been
     * made executable (or this is a SYSV box), do
     * a reasonable thing
     */
    if (errno != ENOENT) {
	char	program[1024], *e, *p, *optarg;
	FILE	*f;
	char	**newargv, **av;
	int	argc;

	/*
	 * emulate BSD kernel behaviour -- read
	 * the first line; check if it starts
	 * with "#!", in which case it uses
	 * the rest of the line as the name of
	 * program to run.  Else use "/bin/sh".
	 */
	f = fopen (argv[0], "r");
	if (!f)
	    return;
	if (fgets (program, sizeof (program) - 1, f) == NULL)
 	{
	    fclose (f);
	    return;
	}
	fclose (f);
	e = program + strlen (program) - 1;
	if (*e == '\n')
	    *e = '\0';
	if (!strncmp (program, "#!", 2)) {
	    p = program + 2;
	    while (*p && isspace (*p))
		++p;
	    optarg = p;
	    while (*optarg && !isspace (*optarg))
		++optarg;
	    if (*optarg) {
		*optarg = '\0';
		do
		    ++optarg;
		while (*optarg && isspace (*optarg));
	    } else
		optarg = 0;
	} else {
	    p = "/bin/sh";
	    optarg = 0;
	}
	Debug ("Shell script execution: %s (optarg %s)\n",
		p, optarg ? optarg : "(null)");
	for (av = argv, argc = 0; *av; av++, argc++)
	    /* SUPPRESS 530 */
	    ;
	newargv = (char **) malloc ((argc + (optarg ? 3 : 2)) * sizeof (char *));
	if (!newargv)
	    return;
	av = newargv;
	*av++ = p;
	if (optarg)
	    *av++ = optarg;
	/* SUPPRESS 560 */
	while ((*av++ = *argv++))
	    /* SUPPRESS 530 */
	    ;
	execve (newargv[0], newargv, environ);
    }
}

char **
defaultEnv (void)
{
    char    **env, **exp, *value;

    env = 0;
    for (exp = exportList; exp && *exp; ++exp)
    {
	value = getenv (*exp);
	if (value)
	    env = setEnv (env, *exp, value);
    }
    return env;
}

char **
systemEnv (struct display *d, char *user, char *home)
{
    char	**env;
    
    env = defaultEnv ();
    env = setEnv (env, "DISPLAY", d->name);
    if (!home)
	home = "/root";
    env = setEnv (env, "HOME", home);
    if (!user)
	user = "root";
    env = setEnv (env, "USER", user);
    env = setEnv (env, "LOGNAME", user);
    env = setEnv (env, "PATH", d->systemPath);
    env = setEnv (env, "SHELL", d->systemShell);
    if (d->authFile)
	    env = setEnv (env, "XAUTHORITY", d->authFile);
    return env;
}

/* #if (defined(Lynx) && !defined(HAS_CRYPT)) || defined(SCO) && !defined(SCO_USA) && !defined(_SCO_DS) */
#ifndef HAVE_CRYPT
char *crypt(char *s1, char *s2)
{
	return(s2);
}
#endif
