/*

TDE Greeter module for xdm

Copyright (C) 2001-2003 Oswald Buddenhagen <ossi@kde.org>

This file contains code from the old xdm core,
Copyright 1988, 1998  Keith Packard, MIT X Consortium/The Open Group

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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/

#include <config.h>

#include "tdm_greet.h"
#include "tdmconfig.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef _POSIX_PRIORITY_SCHEDULING
# include <sched.h>
#endif

# include <X11/Xlib.h>
#if defined(HAVE_XTEST) || defined(HAVE_XKB)
# include <X11/keysym.h>
#endif

#ifdef HAVE_XTEST
# include <X11/extensions/XTest.h>
#endif

#ifdef HAVE_XKB
# include <X11/XKBlib.h>
#endif

extern void LogOutOfMem( void );

static void *
Realloc( void *ptr, size_t size )
{
	void *ret;

	if (!(ret = realloc( ptr, size )) && size)
		LogOutOfMem();
	return ret;
}

#define PRINT_QUOTES
#define PRINT_ARRAYS
#define LOG_NAME "tdm_greet"
#define LOG_DEBUG_MASK DEBUG_GREET
#define LOG_PANIC_EXIT 1
#define STATIC
#include <printf.c>

static void
GDebug( const char *fmt, ... )
{
	va_list args;

	if (debugLevel & DEBUG_HLPCON) {
		va_start( args, fmt );
		Logger( DM_DEBUG, fmt, args );
		va_end( args );
	}
}


char *dname;

int rfd;
static int wfd, mrfd, mwfd, srfd, swfd;
static const char *who;

void
GSet( int master )
{
	if (master)
		rfd = mrfd, wfd = mwfd, who = "core (master)";
	else
		rfd = srfd, wfd = swfd, who = "core";

}

static int
Reader( void *buf, int count )
{
	int ret, rlen;

	for (rlen = 0; rlen < count; ) {
	  dord:
		ret = read( rfd, (void *)((char *)buf + rlen), count - rlen );
		if (ret < 0) {
			if (errno == EINTR)
				goto dord;
			if (errno == EAGAIN)
				break;
			return -1;
		}
		if (!ret)
			break;
		rlen += ret;
	}
	return rlen;
}

static void
GRead( void *buf, int count )
{
	if (Reader( buf, count ) != count)
		LogPanic( "Can't read from %s\n", who );
}

static void
GWrite( const void *buf, int count )
{
	if (write( wfd, buf, count ) != count)
		LogPanic( "Can't write to %s\n", who );
#ifdef _POSIX_PRIORITY_SCHEDULING
	if ((debugLevel & DEBUG_HLPCON))
		sched_yield();
#endif
}

void
GSendInt( int val )
{
	GDebug( "Sending int %d (%#x) to %s\n", val, val, who );
	GWrite( &val, sizeof(val) );
}

void
GSendStr( const char *buf )
{
	int len = buf ? strlen( buf ) + 1 : 0;
	GDebug( "Sending string %'s to %s\n", buf, who );
	GWrite( &len, sizeof(len) );
	GWrite( buf, len );
}

/*
static void
GSendNStr( const char *buf, int len )
{
	int tlen = len + 1;
	GDebug( "Sending string %'.*s to %s\n", len, buf, who );
	GWrite( &tlen, sizeof(tlen) );
	GWrite( buf, len );
	GWrite( "", 1 );
}
*/

void
GSendArr( int len, const char *buf )
{
	GDebug( "Sending array %02[:*hhx to %s\n", len, buf, who );
	GWrite( &len, sizeof(len) );
	GWrite( buf, len );
}

int
GRecvInt()
{
	int val;

	GDebug( "Receiving int from %s ...\n", who );
	GRead( &val, sizeof(val) );
	GDebug( " -> %d (%#x)\n", val, val );
	return val;
}

static char *
iGRecvArr( int *rlen )
{
	int len;
	char *buf;

	GRead( &len, sizeof(len) );
	*rlen = len;
	GDebug( " -> %d bytes\n", len );
	if (!len)
		return (char *)0;
	if (!(buf = malloc( len )))
		LogPanic( "No memory for read buffer\n" );
	GRead( buf, len );
	return buf;
}

char *
GRecvStr()
{
	int len;
	char *buf;

	GDebug( "Receiving string from %s ...\n", who );
	buf = iGRecvArr( &len );
	GDebug( " -> %'.*s\n", len, buf );
	return buf;
}

char **
GRecvStrArr( int *rnum )
{
	int num;
	char **argv, **cargv;

	GDebug( "Receiving string array from %s ...\n", who );
	GRead( &num, sizeof(num) );
	GDebug( " -> %d strings\n", num );
	if (rnum)
		*rnum = num;
	if (!num)
		return (char **)0;
	if (!(argv = malloc( num * sizeof(char *))))
		LogPanic( "No memory for read buffer\n" );
	for (cargv = argv; --num >= 0; cargv++)
		*cargv = GRecvStr();
	return argv;
}

char *
GRecvArr( int *num )
{
	char *arr;

	GDebug( "Receiving array from %s ...\n", who );
	GRead( num, sizeof(*num) );
	GDebug( " -> %d bytes\n", *num );
	if (!*num)
		return (char *)0;
	if (!(arr = malloc( *num )))
		LogPanic( "No memory for read buffer\n" );
	GRead( arr, *num );
	GDebug( " -> %02[*hhx\n", *num, arr );
	return arr;
}

static void
ReqCfg( int id )
{
	GSendInt( G_GetCfg );
	GSendInt( id );
	switch (GRecvInt()) {
	case GE_NoEnt:
		LogPanic( "Config value %#x not available\n", id );
	case GE_BadType:
		LogPanic( "Core does not know type of config value %#x\n", id );
	}
}

int
GetCfgInt( int id )
{
	ReqCfg( id );
	return GRecvInt();
}

char *
GetCfgStr( int id )
{
	ReqCfg( id );
	return GRecvStr();
}

char **
GetCfgStrArr( int id, int *len )
{
	ReqCfg( id );
	return GRecvStrArr( len );
}

static void
disposeSession( dpySpec *sess )
{
	free( sess->display );
	free( sess->from );
	if (sess->user)
		free( sess->user );
	if (sess->session)
		free( sess->session );
}

dpySpec *
fetchSessions( int flags )
{
	dpySpec *sess, *sessions = 0, tsess;

	GSet( 1 );
	GSendInt( G_List );
	GSendInt( flags );
  next:
	while ((tsess.display = GRecvStr())) {
		tsess.from = GRecvStr();
#ifdef HAVE_VTS
		tsess.vt = GRecvInt();
#endif
		tsess.user = GRecvStr();
		tsess.session = GRecvStr();
		tsess.flags = GRecvInt();
		if ((tsess.flags & isTTY) && *tsess.from)
			for (sess = sessions; sess; sess = sess->next)
				if (sess->user && !strcmp( sess->user, tsess.user ) &&
				    !strcmp( sess->from, tsess.from ))
				{
					sess->count++;
					disposeSession( &tsess );
					goto next;
				}
		if (!(sess = malloc( sizeof(*sess) )))
			LogPanic( "Out of memory\n" );
		tsess.count = 1;
		tsess.next = sessions;
		*sess = tsess;
		sessions = sess;
	}
	GSet( 0 );
	return sessions;
}

void
disposeSessions( dpySpec *sess )
{
	while (sess) {
		dpySpec *nsess = sess->next;
		disposeSession( sess );
		free( sess );
		sess = nsess;
	}
}

void
freeStrArr( char **arr )
{
	char **tarr;

	if (arr) {
		for (tarr = arr; *tarr; tarr++)
			free( *tarr );
		free( arr );
	}
}


static int
ignoreErrors( Display *dpy ATTR_UNUSED, XErrorEvent *event ATTR_UNUSED )
{
	Debug( "ignoring X error\n" );
	return 0;
}

/*
 * this is mostly bogus -- but quite useful.  I wish the protocol
 * had some way of enumerating and identifying clients, that way
 * this code wouldn't have to be this kludgy.
 */

static void
killWindows( Display *dpy, Window window )
{
	Window root, parent, *children;
	unsigned child, nchildren = 0;

	while (XQueryTree( dpy, window, &root, &parent, &children, &nchildren )
	       && nchildren > 0)
	{
		for (child = 0; child < nchildren; child++) {
			Debug( "XKillClient 0x%lx\n", (unsigned long)children[child] );
			XKillClient( dpy, children[child] );
		}
		XFree( (char *)children );
	}
}

static jmp_buf resetJmp;

static void
abortReset( int n ATTR_UNUSED )
{
	longjmp (resetJmp, 1);
}

/*
 * this display connection better not have any windows...
 */

static void
pseudoReset( Display *dpy )
{
	int screen;

	if (setjmp( resetJmp )) {
		LogError( "pseudoReset timeout\n" );
	} else {
		(void)signal( SIGALRM, abortReset );
		(void)alarm( 30 );
		XSetErrorHandler( ignoreErrors );
		for (screen = 0; screen < ScreenCount( dpy ); screen++) {
			Debug( "pseudoReset screen %d\n", screen );
			killWindows( dpy, RootWindow( dpy, screen ) );
		}
		Debug( "before XSync\n" );
		XSync( dpy, False );
		(void)alarm( 0 );
	}
	signal( SIGALRM, SIG_DFL );
	XSetErrorHandler( (XErrorHandler)0 );
	Debug( "pseudoReset done\n" );
}


static jmp_buf syncJump;

static void
syncTimeout( int n ATTR_UNUSED )
{
	longjmp( syncJump, 1 );
}

void
SecureDisplay( Display *dpy )
{
	Debug( "SecureDisplay %s\n", dname );
	(void)signal( SIGALRM, syncTimeout );
	if (setjmp( syncJump )) {
		LogError( "Display %s could not be secured\n", dname );
		exit( EX_RESERVER_DPY );
	}
	(void)alarm( (unsigned)_grabTimeout );
	Debug( "Before XGrabServer %s\n", dname );
	XGrabServer( dpy );
	Debug( "XGrabServer succeeded %s\n", dname );
	if (XGrabKeyboard( dpy, DefaultRootWindow( dpy ), True, GrabModeAsync,
	                   GrabModeAsync, CurrentTime ) != GrabSuccess)
	{
		(void)alarm( 0 );
		(void)signal( SIGALRM, SIG_DFL );
		LogError( "Keyboard on display %s could not be secured\n", dname );
		sleep( 10 );
		exit( EX_RESERVER_DPY );
	}
	(void)alarm( 0 );
	(void)signal( SIGALRM, SIG_DFL );
	pseudoReset( dpy );
	if (!_grabServer)
	{
		XUngrabServer( dpy );
		XSync( dpy, 0 );
	}
	Debug( "done secure %s\n", dname );
#ifdef HAVE_XKBSETPERCLIENTCONTROLS
	/*
	 * Activate the correct mapping for modifiers in XKB extension as
	 * grabbed keyboard has its own mapping by default
	 */
	{
		int opcode, evbase, errbase, majret, minret;
		unsigned int value = XkbPCF_GrabsUseXKBStateMask;
		if (XkbQueryExtension( dpy, &opcode, &evbase,
		                       &errbase, &majret, &minret ))
			XkbSetPerClientControls( dpy, value, &value );
	}
#endif
}

void
UnsecureDisplay( Display *dpy )
{
	Debug( "Unsecure display %s\n", dname );
	if (_grabServer) {
		XUngrabServer( dpy );
		XSync( dpy, 0 );
	}
}

static jmp_buf pingTime;

static int
PingLostIOErr( Display *dpy ATTR_UNUSED )
{
	longjmp( pingTime, 1 );
}

static void
PingLostSig( int n ATTR_UNUSED )
{
	longjmp( pingTime, 1 );
}

int
PingServer( Display *dpy )
{
	int (*oldError)( Display * );
	void (*oldSig)( int );
	int oldAlarm;

	oldError = XSetIOErrorHandler( PingLostIOErr );
	oldAlarm = alarm( 0 );
	oldSig = signal( SIGALRM, PingLostSig );
	(void)alarm( _pingTimeout * 60 );
	if (!setjmp( pingTime )) {
		Debug( "Ping server\n" );
		XSync( dpy, 0 );
	} else {
		Debug( "Server dead\n" );
		(void)alarm( 0 );
		(void)signal( SIGALRM, SIG_DFL );
		XSetIOErrorHandler( oldError );
		return 0;
	}
	(void)alarm( 0 );
	(void)signal( SIGALRM, oldSig );
	(void)alarm( oldAlarm );
	Debug( "Server alive\n" );
	XSetIOErrorHandler( oldError );
	return 1;
}

/*
 * Modifier changing code based on tdebase/kxkb/kcmmisc.cpp
 *
 * XTest part: Copyright (C) 2000-2001 Lubos Lunak <l.lunak@kde.org>
 * XKB part:   Copyright (C) 2001-2002 Oswald Buddenhagen <ossi@kde.org>
 *
 */

#ifdef HAVE_XKB
static int
xkb_init( Display *dpy )
{
	int xkb_opcode, xkb_event, xkb_error;
	int xkb_lmaj = XkbMajorVersion;
	int xkb_lmin = XkbMinorVersion;
	return XkbLibraryVersion( &xkb_lmaj, &xkb_lmin ) &&
	       XkbQueryExtension( dpy, &xkb_opcode, &xkb_event,
	                          &xkb_error, &xkb_lmaj, &xkb_lmin );
}

static unsigned int
xkb_modifier_mask_work( XkbDescPtr xkb, const char *name )
{
	int i;

	if (!xkb->names)
		return 0;
	for (i = 0; i < XkbNumVirtualMods; i++) {
		char *modStr = XGetAtomName( xkb->dpy, xkb->names->vmods[i] );
		if( modStr == NULL ) {
			continue;
		}
		if( strcmp( name, modStr ) == 0 ) {
			unsigned int mask;
			XkbVirtualModsToReal( xkb, 1 << i, &mask );
			XFree(modStr);
			return mask;
		}
		XFree(modStr);
	}
	return 0;
}

static unsigned int
xkb_modifier_mask( Display *dpy, const char *name )
{
	XkbDescPtr xkb;

	if ((xkb = XkbGetKeyboard( dpy, XkbAllComponentsMask, XkbUseCoreKbd ))) {
		unsigned int mask = xkb_modifier_mask_work( xkb, name );
		XkbFreeKeyboard( xkb, 0, True );
		return mask;
	}
	return 0;
}

static int
xkb_get_modifier_state( Display *dpy, const char *name )
{
	unsigned int mask;
	XkbStateRec state;

	if (!(mask = xkb_modifier_mask( dpy, name )))
		return 0;
	XkbGetState( dpy, XkbUseCoreKbd, &state );
	return (mask & state.locked_mods) != 0;
}

static int
xkb_set_modifier( Display *dpy, const char *name, int sts )
{
	unsigned int mask;

	if (!(mask = xkb_modifier_mask( dpy, name )))
		return 0;
	XkbLockModifiers( dpy, XkbUseCoreKbd, mask, sts ? mask : 0 );
	return 1;
}
#endif /* HAVE_XKB */

#ifdef HAVE_XTEST
static int
xtest_get_modifier_state( Display *dpy, int key )
{
	XModifierKeymap *map;
	KeyCode modifier_keycode;
	unsigned int i, mask;
	Window dummy1, dummy2;
	int dummy3, dummy4, dummy5, dummy6;

	if ((modifier_keycode = XKeysymToKeycode( dpy, key )) == NoSymbol)
		return 0;
	map = XGetModifierMapping( dpy );
	for (i = 0; i < 8; ++i)
		if (map->modifiermap[map->max_keypermod * i] == modifier_keycode) {
			XFreeModifiermap( map );
			XQueryPointer( dpy, DefaultRootWindow( dpy ),
			               &dummy1, &dummy2, &dummy3, &dummy4, &dummy5, &dummy6,
			               &mask );
			return (mask & (1 << i)) != 0;
		}
	XFreeModifiermap( map );
	return 0;
}

static void
xtest_fake_keypress( Display *dpy, int key )
{
	XTestFakeKeyEvent( dpy, XKeysymToKeycode( dpy, key ), True, CurrentTime );
	XTestFakeKeyEvent( dpy, XKeysymToKeycode( dpy, key ), False, CurrentTime );
}
#endif /* HAVE_XTEST */

#ifdef HAVE_XKB
static int havexkb;
#endif
static int nummodified, oldnumstate, newnumstate;
static Display *dpy;

void
setup_modifiers( Display *mdpy, int numlock )
{
	if (numlock == 2)
		return;
	newnumstate = numlock;
	nummodified = 1;
	dpy = mdpy;
#ifdef HAVE_XKB
	if (xkb_init( mdpy )) {
		havexkb = 1;
		oldnumstate = xkb_get_modifier_state( mdpy, "NumLock" );
		xkb_set_modifier( mdpy, "NumLock", numlock );
		return;
	}
#endif
#ifdef HAVE_XTEST
	oldnumstate = xtest_get_modifier_state( mdpy, XK_Num_Lock );
	if (oldnumstate != numlock)
		xtest_fake_keypress( mdpy, XK_Num_Lock );
#endif
}

void
restore_modifiers( void )
{
#ifdef HAVE_XTEST
	int numstat;
#endif

	if (!nummodified)
		return;
#ifdef HAVE_XKB
	if (havexkb) {
		if (xkb_get_modifier_state( dpy, "NumLock" ) == newnumstate)
			xkb_set_modifier( dpy, "NumLock", oldnumstate );
		return;
	}
#endif
#ifdef HAVE_XTEST
	numstat = xtest_get_modifier_state( dpy, XK_Num_Lock );
	if (numstat == newnumstate && newnumstate != oldnumstate)
		xtest_fake_keypress( dpy, XK_Num_Lock );
#endif
}

void
setCursor( Display *mdpy, int window, int shape )
{
	Cursor xcursor;

	if ((xcursor = XCreateFontCursor( mdpy, shape ))) {
		XDefineCursor( mdpy, window, xcursor );
		XFreeCursor( mdpy, xcursor );
		XFlush( mdpy );
	}
}

static void
sigterm( int n ATTR_UNUSED )
{
	exit( EX_NORMAL );
}

static char *savhome;

static void
cleanup( void )
{
	char buf[128];

	if (strcmp( savhome, getenv( "HOME" ) ) || memcmp( savhome, "/tmp/", 5 ))
		LogError( "Internal error: memory corruption detected\n" ); /* no panic: recursion */
	else {
		sprintf( buf, "rm -rf %s", savhome );
		system( buf );
	}
}

extern void kg_main( const char *argv0 );

int
main( int argc ATTR_UNUSED, char **argv )
{
	char *ci;
	int i;
	char qtrc[40];

	if (!(ci = getenv( "CONINFO" ))) {
		fprintf( stderr, "This program is part of tdm and should not be run manually.\n" );
		return 1;
	}
	if (sscanf( ci, "%d %d %d %d", &srfd, &swfd, &mrfd, &mwfd ) != 4)
		return 1;
	fcntl( srfd, F_SETFD, FD_CLOEXEC );
	fcntl( swfd, F_SETFD, FD_CLOEXEC );
	fcntl( mrfd, F_SETFD, FD_CLOEXEC );
	fcntl( mwfd, F_SETFD, FD_CLOEXEC );
	GSet( 0 );

	InitLog();

	if ((debugLevel = GRecvInt()) & DEBUG_WGREET)
		sleep( 100 );

	signal( SIGTERM, sigterm );

	dname = getenv( "DISPLAY" );

	init_config();

	/* for TQSettings */
	srand( time( 0 ) );
	for (i = 0; i < 10000; i++) {
		sprintf( qtrc, "/tmp/%010d", rand() );
		if (!mkdir( qtrc, 0700 ))
			goto okay;
	}
	LogPanic( "Cannot create $HOME\n" );
  okay:
	if (setenv( "HOME", qtrc, 1 ))
		LogPanic( "Cannot set $HOME\n" );
	if (!(savhome = strdup( qtrc )))
		LogPanic( "Cannot save $HOME\n" );
	atexit( cleanup );

	if ( getenv( "LANG" ) == NULL ) {
		setenv( "LC_ALL", _language, 1 );
	}
	else {
		setenv( "LC_ALL", getenv( "LANG" ), 1 );
	}

	kg_main( argv[0] );

	return EX_NORMAL;
}