You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/kdm/kfrontend/kdm_greet.c

788 lines
16 KiB

/*
KDE 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 "kdm_greet.h"
#include "kdmconfig.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 "kdm_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 kdebase/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 && strcmp( name, modStr ) == 0) {
unsigned int mask;
XkbVirtualModsToReal( xkb, 1 << i, &mask );
return mask;
}
}
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 tqshape )
{
Cursor xcursor;
if ((xcursor = XCreateFontCursor( mdpy, tqshape ))) {
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 kdm 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 );
setenv( "LC_ALL", _language, 1 );
kg_main( argv[0] );
return EX_NORMAL;
}