#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)logout.c	4.02 97/04/01 xlockmore";

#endif

/*-
 * logout.c: handle compile-time optional logout
 *
 * See xlock.c for copying information.
 *
 * xclosedown code
 * Copyright 1990 by Janet Carson
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  The author makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * Revision History:
 * 27-Jul-95: put back in logout.c (oops).
 *            Window shutdown program by Janet L. Carson,
 *            Baylor College of Medicine.
 *            Main procedure modified for use w/ xlock by
 *            Anthony Thyssen <anthony@cit.gu.edu.au>.
 * 24-Feb-95: fullLock rewritten to handle non-default group names from
 *            Dale A. Harris <rodmur@ecst.csuchico.edu>
 * 13-Feb-95: Heath A. Kehoe <hakehoe AT icaen.uiowa.edu>.
 *            Mostly taken from bomb.c
 * 1994:      bomb.c written.  Copyright (c) 1994 Dave Shield
 *            Liverpool Computer Science
 */

#include "xlock.h"
#include "iostuff.h"

#if defined( USE_AUTO_LOGOUT ) || defined( USE_BUTTON_LOGOUT ) || defined( USE_BOMB )

#if ( HAVE_SYSLOG_H && defined( USE_SYSLOG ))
#include <syslog.h>
#endif
#if defined(__CYGWIN__) || defined(SOLARIS2) || defined(__FreeBSD__)
#include <signal.h>
#endif
#include <sys/signal.h>

extern Bool inroot, inwindow, nolock, debug;
extern char *logoutCmd;

/*-
 * This file contains a function called logoutUser() that, when called,
 * will (try) to log out the user.
 *
 * A portable way to do this is to simply kill all of the user's processes,
 * but this is a really ugly way to do it (it kills background jobs that
 * users may want to remain running after they log out).
 *
 * If your system provides for a cleaner/easier way to log out a user,
 * you may implement it here.
 *
 * For example, on some systems, one may define for the users an environment
 * variable named XSESSION that contains the pid of the X session leader
 * process.  So, to log the user out, we just need to kill that process,
 * and the X session will end.  Of course, a user can defeat that by
 * changing the value of XSESSION; so we can fall back on the ugly_logout()
 * method.
 *
 * If you can't log the user out (and you don't want to use the brute
 * force method) simply return from logoutUser(), and xlock will continue
 * on it's merry way (only applies if USE_AUTO_LOGOUT or USE_BUTTON_LOGOUT
 * is defined.)
 */

#define NAP_TIME        5	/* Sleep between shutdown attempts */


#ifdef CLOSEDOWN_LOGOUT

/* Logout the user by contacting the display and closing all windows */


/*-
 *  Window shutdown program by Janet L. Carson, Baylor College of Medicine.
 *  Version 1.0, placed in /contrib on 2/12/90.
 *
 *  Please send comments or fixes to jcarson@bcm.tmc.edu
 */

/*-
 *  I'm probably going to get some BadWindow errors as I kill clients
 *  which have multiple windows open and then try to kill them again
 *  on another of their windows.  I'm just going to plow right through!
 *  The flag is set back to false in recurse_tree and kill_tree...
 */

static int  err_occurred = False;

static int
err_handler(Display * display, XErrorEvent * err)
{
	err_occurred = True;
	return 0;
}

/*-
 *  Looking for properties...
 */

static int
has_property(Display * display, Window window, Atom prop)
{
	int         nprops, j, retval = 0;
	Atom       *list = XListProperties(display, window, &nprops);

	if (err_occurred)
		return 0;

	for (j = 0; j < nprops; j++) {
		if (list[j] == prop) {
			retval = 1;
			break;
		}
	}

	if (nprops)
		XFree((caddr_t) list);

	return retval;
}

/*-
 *  Send a WM_PROTOCOLS WM_DELETE_WINDOW message to a window
 */

static void
send_delete_message(Display * display, Window window,
		    Atom protocols_atom, Atom delete_window_atom)
{
	XClientMessageEvent xclient;

	xclient.type = ClientMessage;
	xclient.send_event = True;
	xclient.display = display;
	xclient.window = window;
	xclient.message_type = protocols_atom;
	xclient.format = 32;
	xclient.data.l[0] = delete_window_atom;

	XSendEvent(display, window, False, 0, (XEvent *) & xclient);
}

/*-
 *  To shutdown a top level window:  if the window participates
 *  in WM_DELETE_WINDOW, let the client shut itself off.  Otherwise,
 *  do an XKillClient on it.
 */

static void
handle_top_level(Display * display, Window window,
		 Atom protocols_atom, Atom delete_window_atom)
{
	Atom       *prots;
	int         nprots, j;

	if (has_property(display, window, protocols_atom)) {
		XGetWMProtocols(display, window, &prots, &nprots);

		if (err_occurred)
			return;

		for (j = 0; j < nprots; j++)
			if (prots[j] == delete_window_atom) {
				send_delete_message(display, window,
					 protocols_atom, delete_window_atom);
				break;
			}
		if (j == nprots)	/* delete window not found */
			XKillClient(display, window);

		XFree((caddr_t) prots);
	} else
		XKillClient(display, window);
}

/*-
 *  recurse_tree: look for top level windows to kill all the way down
 *  the window tree.  This pass is "nice"--I'll use delete_window protocol
 *  if the window supports it.  If I get an error in the middle, I'll start
 *  over again at the same level, because reparenting window managers throw
 *  windows back up to the root...
 */

static void
recurse_tree(Display * display, Window window,
	     Atom state_atom, Atom protocols_atom, Atom delete_window_atom)
{
	Window      root, parent, *kids;
	unsigned int nkids;
	int         j;
	int         swm_state;

	for (;;) {
		XQueryTree(display, window, &root, &parent, &kids, &nkids);
		if (err_occurred) {
			err_occurred = False;
			return;
		}
		for (j = 0; j < nkids; j++) {
			swm_state = has_property(display, kids[j], state_atom);

			if (err_occurred)
				break;

			if (swm_state) {
				handle_top_level(display, kids[j], protocols_atom, delete_window_atom);
				if (err_occurred)
					break;
			} else
				recurse_tree(display, kids[j],
					     state_atom, protocols_atom, delete_window_atom);
		}

		XFree((caddr_t) kids);

		/* when I get all the way through a level without an error, I'm done  */

		if (err_occurred)
			err_occurred = False;
		else
			return;

	}
}

/*-
 *  This is the second pass--anything left gets an XKillClient!
 */

static void
kill_tree(Display * display, Window window)
{
	Window      root, parent, *kids;
	unsigned int nkids;
	int         j;

	for (;;) {
		XQueryTree(display, window, &root, &parent, &kids, &nkids);
		if (err_occurred) {
			err_occurred = False;
			return;
		}
		for (j = 0; j < nkids; j++) {
			XKillClient(display, kids[j]);
			if (err_occurred)
				break;
		}

		XFree((caddr_t) kids);

		/* when I get all the way through a level without an error, I'm done  */

		if (err_occurred)
			err_occurred = False;
		else
			return;
	}
}

/*-
 *  Main program
 */

static void
closedownLogout(Display * display, int screens)
{
	Atom        __SWM_DELETE_WINDOW = None;
	Atom        __SWM_PROTOCOLS = None;
	Atom        __SWM_STATE = None;
	int         j;

#if 0
	/* synchronize -- so I'm aware of errors immediately */
	XSynchronize(display, True);

	/* use my error handler from here on out */
	(void) XSetErrorHandler(err_handler);
#endif

	/* init atoms */
	__SWM_STATE = XInternAtom(display, "__SWM_STATE", False);
	__SWM_PROTOCOLS = XInternAtom(display, "__SWM_PROTOCOLS", False);
	__SWM_DELETE_WINDOW = XInternAtom(display, "__SWM_DELETE_WINDOW", False);

	/* start looking for windows to kill -- be nice on pass 1 */
	for (j = 0; j < screens; j++)
		recurse_tree(display, RootWindow(display, j),
			  __SWM_STATE, __SWM_PROTOCOLS, __SWM_DELETE_WINDOW);

	/* wait for things to clean themselves up */
	(void) sleep(NAP_TIME);

	/* this will forcibly kill anything that's still around --
	   this second pass may or may not be needed... */
	for (j = 0; j < screens; j++)
		kill_tree(display, RootWindow(display, j));
	(void) sleep(NAP_TIME);
}

#endif /* CLOSEDOWN_LOGOUT */

#ifdef SunCplusplus
/* #include <signal.h> */
extern void (*signal(int, void (*)(int))) (int);
extern int kill(pid_t, int);
#else
#if 0
extern int signal(int, void *);
extern int kill(int, int);
#endif
#endif

#ifdef SESSION_LOGOUT
static void
sessionLogout(void)
{
	char       *pidstr;

	pidstr = getenv("XSESSION");
	if (pidstr) {
		(void) kill(atoi(pidstr), SIGTERM);
		(void) sleep(NAP_TIME);
	}
}

#endif /* SESSION_LOGOUT */

static void
uglyLogout(void)
{
#ifndef VMS
#ifndef KILL_ALL_OTHERS
#define KILL_ALL_OTHERS -1
	(void) signal(SIGHUP, SIG_IGN);
	(void) signal(SIGTERM, SIG_IGN);
#endif

	(void) kill(KILL_ALL_OTHERS, SIGHUP);
	(void) sleep(NAP_TIME);
	(void) kill(KILL_ALL_OTHERS, SIGTERM);
	(void) sleep(NAP_TIME);

#if ( HAVE_SYSLOG_H && defined( USE_SYSLOG ))
	syslog(SYSLOG_NOTICE, "%s: failed to exit - sending kill (uid %d)\n",
	       ProgramName, getuid());
#endif

	(void) kill(KILL_ALL_OTHERS, SIGKILL);
	(void) sleep(NAP_TIME);

#if ( HAVE_SYSLOG_H && defined( USE_SYSLOG ))
	syslog(SYSLOG_WARNING, "%s: still won't exit - suicide (uid %d)\n",
	       ProgramName, getuid());
#endif

	(void) kill((int) getpid(), SIGKILL);
#endif /* !VMS */
	exit(-1);
}

#if ( HAVE_SYSLOG_H && defined( USE_SYSLOG ))
extern void syslogStop(char *);
#endif

void
logoutUser(Display * display
#ifdef CLOSEDOWN_LOGOUT
  , int screens
#endif
)
{

#if ( HAVE_SYSLOG_H && defined( USE_SYSLOG ))
	syslog(SYSLOG_INFO, "%s: expired. closing down (uid %d) on %s\n",
	       ProgramName, getuid(), getenv("DISPLAY"));
	syslogStop(XDisplayString(display));
#endif
	if (logoutCmd && *logoutCmd) {
		int         cmd_pid;

		if ((cmd_pid = (int) FORK()) == -1) {
			(void) fprintf(stderr, "Failed to launch \"%s\"\n", logoutCmd);
			perror(ProgramName);
			cmd_pid = 0;
		} else if (!cmd_pid) {
			(void) system(logoutCmd);
			exit(0);
		}
	}
#ifdef CLOSEDOWN_LOGOUT
	(void) finish(display, False);
#else
	(void) finish(display, True);
#endif
#ifdef VMS
	(void) system("mcr decw$endsession -noprompt");
#else
#ifdef __sgi
	(void) system("/usr/bin/X11/tellwm end_session >/dev/null 2>&1");
	(void) sleep(10);	/* Give the above a chance to run */
#endif
#endif

#ifdef CLOSEDOWN_LOGOUT
	/* Do not want to kill other user's processes e.g. telnet session */
	closedownLogout(display, screens);
	return;
#endif
#ifdef SESSION_LOGOUT
	sessionLogout();
#endif
	uglyLogout();
	exit(-1);
}


#if defined( USE_AUTO_LOGOUT ) || defined( USE_BUTTON_LOGOUT )
	/*
	 *  Determine whether to "fully" lock the terminal, or
	 *    whether the time-limited version should be imposed.
	 *
	 *  Policy:
	 *      Members of staff can fully lock
	 *        (hard-wired user/group names + file read at run time)
	 *      Students (i.e. everyone else)
	 *          are forced to use the time-limit version.
	 *
	 *  An alternative policy could be based on display location
	 */
#define FULL_LOCK       1
#define TEMP_LOCK       0

/* assuming only staff file is needed */
#ifndef STAFF_FILE
#define STAFF_FILE      "/usr/remote/etc/xlock.staff"
#endif

#undef passwd
#undef pw_name
#undef getpwnam
#undef getpwuid
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#ifndef NGROUPS
#if HAVE_LIMITS_H
#include <limits.h>
#endif
#ifdef NGROUPS_MAX
#define NGROUPS NGROUPS_MAX
#else
#define NGROUPS NGROUPS_MAX_DEFAULT
#endif
#endif

#if defined( USE_BUTTON_LOGOUT ) && !defined( NO_NEGATIVE_LOGOUT )
extern int  logoutButton;
#endif

int
fullLock(void)
{
	uid_t       uid;
	int         ngroups = NGROUPS;

#ifdef SUNOS4
	gid_t       mygidset[NGROUPS * sizeof (gid_t)];

#else
	gid_t       mygidset[NGROUPS];

#endif
	int         ngrps, i;
	struct passwd *pwp;
	struct group *gp;
	FILE       *fp;
	char        buf[BUFSIZ];
#if defined( USE_BUTTON_LOGOUT ) && !defined( NO_NEGATIVE_LOGOUT )
	if (logoutButton < 0)
		return (FULL_LOCK);
#endif
	/* The debug portion may depend on what you are debugging.  :) */
	if (inwindow || inroot || nolock /*|| debug */ )
		return (FULL_LOCK);	/* (mostly) harmless user */
	uid = getuid();
	/* Do not try to logout root! */
	if (!uid)
		return (FULL_LOCK);	/* root */

	pwp = getpwuid(uid);
	if ((ngrps = getgroups(ngroups, mygidset)) == -1)
		perror(ProgramName);

#ifdef STAFF_NETGROUP
	if (innetgr(STAFF_NETGROUP, NULL, pwp->pw_name, NULL))
		return (FULL_LOCK);
#endif

	if ((fp = my_fopen(STAFF_FILE, "r")) == NULL)
		return (TEMP_LOCK);

	while ((fgets(buf, BUFSIZ, fp)) != NULL) {
		char       *cp;

		if ((cp = (char *) strchr(buf, '\n')) != NULL)
			*cp = '\0';
		if (!strcmp(buf, pwp->pw_name))
			return (FULL_LOCK);
		if ((gp = getgrnam(buf)) != NULL) {
			/* check all of user's groups */
#ifdef SUNOS4
			for (i = 1; i < ngrps * sizeof (gid_t); i += 2)
#else
			for (i = 0; i < ngrps; ++i)
#endif
				if (gp->gr_gid == mygidset[i])
					return (FULL_LOCK);
		}
	}
	(void) fclose(fp);

	return (TEMP_LOCK);
}
#endif /* USE_AUTO_LOGOUT || USE_BUTTON_LOGOUT */

#endif /* USE_AUTO_LOGOUT || USE_BUTTON_LOGOUT || USE_BOMB */
