diff options
Diffstat (limited to 'tdm/backend/dm.c')
-rw-r--r-- | tdm/backend/dm.c | 1669 |
1 files changed, 1669 insertions, 0 deletions
diff --git a/tdm/backend/dm.c b/tdm/backend/dm.c new file mode 100644 index 000000000..d372dd472 --- /dev/null +++ b/tdm/backend/dm.c @@ -0,0 +1,1669 @@ +/* + +Copyright 1988, 1998 The Open Group +Copyright 2000-2005 Oswald Buddenhagen <[email protected]> + +Permission to use, copy, modify, distribute, and sell 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 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 COPYRIGHT HOLDERS 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 a copyright holder 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 copyright holder. + +*/ + +/* + * xdm - display manager daemon + * Author: Keith Packard, MIT X Consortium + * + * display manager + */ + +#include "dm.h" +#include "dm_auth.h" +#include "dm_error.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdarg.h> +#include <signal.h> +#include <sys/stat.h> + +#ifdef HAVE_VTS +# include <sys/ioctl.h> +# include <sys/vt.h> +#endif + +static void SigHandler( int n ); +static int ScanConfigs( int force ); +static void StartDisplays( void ); +#define XS_KEEP 0 +#define XS_RESTART 1 +#define XS_RETRY 2 +static void ExitDisplay( struct display *d, int endState, int serverCmd, int goodExit ); +static void rStopDisplay( struct display *d, int endState ); +static void MainLoop( void ); + +static int signalFds[2]; + +#if !defined(HAVE_SETPROCTITLE) && !defined(NOXDMTITLE) +static char *Title; +static int TitleLen; +#endif + +static int StorePid( void ); + +static int Stopping; +SdRec sdRec = { 0, 0, 0, TO_INF, TO_INF, 0, 0, 0 }; + +time_t now; + +char *prog, *progpath; + +int +main( int argc, char **argv ) +{ + int oldpid, oldumask, fd, noDaemonMode; + char *pt, *errorLogFile, **opts; + + /* make sure at least world write access is disabled */ + if (((oldumask = umask( 022 )) & 002) == 002) + (void)umask( oldumask ); + + /* give /dev/null as stdin */ + if ((fd = open( "/dev/null", O_RDONLY )) > 0) { + dup2( fd, 0 ); + close( fd ); + } + if (fcntl( 1, F_GETFD ) < 0) + dup2( 0, 1 ); + if (fcntl( 2, F_GETFD ) < 0) + dup2( 0, 2 ); + + if (argv[0][0] == '/') { + if (!StrDup( &progpath, argv[0] )) + Panic( "Out of memory" ); + } else +#ifdef __linux__ + { + /* note that this will resolve symlinks ... */ + int len; + char fullpath[PATH_MAX]; + if ((len = readlink( "/proc/self/exe", fullpath, sizeof(fullpath) )) < 0) + Panic( "Invoke with full path specification or mount /proc" ); + if (!StrNDup( &progpath, fullpath, len )) + Panic( "Out of memory" ); + } +#else +# if 0 + Panic( "Must be invoked with full path specification" ); +# else + { + char directory[PATH_MAX+1]; + if (!getcwd( directory, sizeof(directory) )) + Panic( "Can't find myself (getcwd failed)" ); + if (strchr( argv[0], '/' )) + StrApp( &progpath, directory, "/", argv[0], (char *)0 ); + else { + int len; + char *path, *name, *thenam, nambuf[PATH_MAX+1]; + char *pathe; + + if (!(path = getenv( "PATH" ))) + Panic( "Can't find myself (no PATH)" ); + len = strlen( argv[0] ); + name = nambuf + PATH_MAX - len; + memcpy( name, argv[0], len + 1 ); + *--name = '/'; + do { + if (!(pathe = strchr( path, ':' ))) + pathe = path + strlen( path ); + len = pathe - path; + if (!len || (len == 1 && *path == '.')) { + len = strlen( directory ); + path = directory; + } + thenam = name - len; + if (thenam >= nambuf) { + memcpy( thenam, path, len ); + if (!access( thenam, X_OK )) + goto found; + } + path = pathe; + } while (*path++ != '\0'); + Panic( "Can't find myself (not in PATH)" ); + found: + if (!StrDup( &progpath, thenam )) + Panic( "Out of memory" ); + } + } +# endif +#endif + prog = strrchr( progpath, '/' ) + 1; + +#if !defined(HAVE_SETPROCTITLE) && !defined(NOXDMTITLE) + Title = argv[0]; + TitleLen = (argv[argc - 1] + strlen( argv[argc - 1] )) - Title; +#endif + + /* + * Parse command line options + */ + noDaemonMode = getppid(); + errorLogFile = 0; + if (!(opts = Malloc( 2 * sizeof(char *) ))) + return 1; + opts[0] = (char *)""; + opts[1] = 0; + while (*++argv) { + if (**argv != '-') + break; + pt = *argv + 1; + if (*pt == '-') + pt++; + if (!strcmp( pt, "help" ) || !strcmp( pt, "h" )) { + printf( "Usage: %s [options] [tty]\n" +" -daemon\t - Daemonize even when started by init\n" +" -nodaemon\t - Don't daemonize even when started from command line\n" +" -config <file> - Use alternative master configuration file\n" +" -xrm <res>\t - Override frontend-specific resource\n" +" -error <file>\t - Use alternative log file\n" +" -debug <num>\t - Debug option bitfield:\n" +"\t\t\t0x1 - core log\n" +"\t\t\t0x2 - config reader log\n" +"\t\t\t0x4 - greeter log\n" +"\t\t\t0x8 - IPC log\n" +"\t\t\t0x10 - session sub-daemon post-fork delay\n" +"\t\t\t0x20 - config reader post-start delay\n" +"\t\t\t0x40 - greeter post-start delay\n" +"\t\t\t0x80 - don't use syslog\n" +"\t\t\t0x100 - core Xauth log\n" +"\t\t\t0x400 - valgrind config reader and greeter\n" +"\t\t\t0x800 - strace config reader and greeter\n" + , prog ); + exit( 0 ); + } else if (!strcmp( pt, "daemon" )) + noDaemonMode = 0; + else if (!strcmp( pt, "nodaemon" )) + noDaemonMode = 1; + else if (argv[1] && !strcmp( pt, "config" )) + StrDup( opts, *++argv ); + else if (argv[1] && !strcmp( pt, "xrm" )) + opts = addStrArr( opts, *++argv, -1 ); + else if (argv[1] && !strcmp( pt, "debug" )) + sscanf( *++argv, "%i", &debugLevel ); + else if (argv[1] && (!strcmp( pt, "error" ) || !strcmp( pt, "logfile" ))) + errorLogFile = *++argv; + else { + fprintf( stderr, "\"%s\" is an unknown option or is missing a parameter\n", *argv ); + exit( 1 ); + } + } + + /* + * Only allow root to run in non-debug mode to avoid problems + */ + if (!debugLevel && getuid()) { + fprintf( stderr, "Only root wants to run %s\n", prog ); + exit( 1 ); + } + + InitErrorLog( errorLogFile ); + + if (noDaemonMode != 1) + BecomeDaemon(); + + /* + * Step 1 - load configuration parameters + */ + if (!InitResources( opts ) || ScanConfigs( FALSE ) < 0) + LogPanic( "Config reader failed. Aborting ...\n" ); + + /* SUPPRESS 560 */ + if ((oldpid = StorePid())) { + if (oldpid == -1) + LogError( "Can't create/lock pid file %s\n", pidFile ); + else + LogError( "Can't lock pid file %s, another xdm is running (pid %d)\n", + pidFile, oldpid ); + exit( 1 ); + } + +#ifdef NEED_ENTROPY + AddOtherEntropy(); +#endif + + /* + * We used to clean up old authorization files here. As authDir is + * supposed to be /var/run/xauth or /tmp, we needn't to care for it. + */ + +#ifdef XDMCP + init_session_id(); +#else + Debug( "not compiled for XDMCP\n" ); +#endif + if (pipe( signalFds )) + LogPanic( "Unable to create signal notification pipe.\n" ); + RegisterInput( signalFds[0] ); + RegisterCloseOnFork( signalFds[0] ); + RegisterCloseOnFork( signalFds[1] ); + (void)Signal( SIGTERM, SigHandler ); + (void)Signal( SIGINT, SigHandler ); + (void)Signal( SIGHUP, SigHandler ); + (void)Signal( SIGCHLD, SigHandler ); + (void)Signal( SIGUSR1, SigHandler ); + + /* + * Step 2 - run a sub-daemon for each entry + */ +#ifdef XDMCP + UpdateListenSockets(); +#endif + openCtrl( 0 ); + MainLoop(); + closeCtrl( 0 ); + if (sdRec.how) { + commitBootOption(); + if (Fork() <= 0) { + char *cmd = sdRec.how == SHUT_HALT ? cmdHalt : cmdReboot; + execute( parseArgs( (char **)0, cmd ), (char **)0 ); + LogError( "Failed to execute shutdown command %\"s\n", cmd ); + exit( 1 ); + } else { + sigset_t mask; + sigemptyset( &mask ); + sigaddset( &mask, SIGCHLD ); + sigaddset( &mask, SIGHUP ); + sigsuspend( &mask ); + } + } + Debug( "nothing left to do, exiting\n" ); + return 0; +} + + +#ifdef HAVE_VTS +int +TTYtoVT( const char *tty ) +{ + return memcmp( tty, "tty", 3 ) ? 0 : atoi( tty + 3 ); +} + +int +activateVT( int vt ) +{ + int ret = 0; + int con = open( "/dev/console", O_RDONLY ); + if (con >= 0) { + if (!ioctl( con, VT_ACTIVATE, vt )) + ret = 1; + close( con ); + } + return ret; +} + + +static void +WakeDisplay( struct display *d ) +{ + if (d->status == textMode) + d->status = (d->displayType & d_lifetime) == dReserve ? reserve : notRunning; +} +#endif + +enum utState { UtDead, UtWait, UtActive }; + +struct utmps { + struct utmps *next; +#ifndef HAVE_VTS + struct display *d; +#endif + time_t time; + enum utState state; + int hadSess; +}; + +#define TIME_LOG 40 +#define TIME_RELOG 10 + +static struct utmps *utmpList; +static time_t utmpTimeout = TO_INF; + +static void +bombUtmp( void ) +{ + struct utmps *utp; + + while ((utp = utmpList)) { +#ifdef HAVE_VTS + ForEachDisplay( WakeDisplay ); +#else + utp->d->status = notRunning; +#endif + utmpList = utp->next; + free( utp ); + } +} + +static void +CheckUtmp( void ) +{ + static time_t modtim; + time_t nck; + time_t ends; + struct utmps *utp, **utpp; + struct stat st; +#ifdef BSD_UTMP + int fd; + struct utmp ut[1]; +#else + STRUCTUTMP *ut; +#endif + + if (!utmpList) + return; + if (stat( UTMP_FILE, &st )) { + LogError( UTMP_FILE " not found - cannot use console mode\n" ); + bombUtmp(); + return; + } + if (modtim != st.st_mtime) { + Debug( "rescanning " UTMP_FILE "\n" ); + for (utp = utmpList; utp; utp = utp->next) + utp->state = UtDead; +#ifdef BSD_UTMP + if ((fd = open( UTMP_FILE, O_RDONLY )) < 0) { + LogError( "Cannot open " UTMP_FILE " - cannot use console mode\n" ); + bombUtmp(); + return; + } + while (Reader( fd, ut, sizeof(ut[0]) ) == sizeof(ut[0])) +#else + SETUTENT(); + while ((ut = GETUTENT())) +#endif + { + for (utp = utmpList; utp; utp = utp->next) { +#ifdef HAVE_VTS + char **line; + for (line = consoleTTYs; *line; line++) + if (!strncmp( *line, ut->ut_line, sizeof(ut->ut_line) )) + goto hitlin; + continue; + hitlin: +#else + if (strncmp( utp->d->console, ut->ut_line, sizeof(ut->ut_line) )) + continue; +#endif +#ifdef BSD_UTMP + if (!*ut->ut_user) { +#else + if (ut->ut_type != USER_PROCESS) { +#endif +#ifdef HAVE_VTS + if (utp->state == UtActive) + break; +#endif + utp->state = UtWait; + } else { + utp->hadSess = 1; + utp->state = UtActive; + } + if (utp->time < ut->ut_time) /* theoretically superfluous */ + utp->time = ut->ut_time; + break; + } + } +#ifdef BSD_UTMP + close( fd ); +#else + ENDUTENT(); +#endif + modtim = st.st_mtime; + } + for (utpp = &utmpList; (utp = *utpp); ) { + if (utp->state != UtActive) { + if (utp->state == UtDead) /* shouldn't happen ... */ + utp->time = 0; + ends = utp->time + (utp->hadSess ? TIME_RELOG : TIME_LOG); + if (ends <= now) { +#ifdef HAVE_VTS + ForEachDisplay( WakeDisplay ); + Debug( "console login timed out\n" ); +#else + utp->d->status = notRunning; + Debug( "console login for %s at %s timed out\n", + utp->d->name, utp->d->console ); +#endif + *utpp = utp->next; + free( utp ); + continue; + } else + nck = ends; + } else + nck = TIME_RELOG + now; + if (nck < utmpTimeout) + utmpTimeout = nck; + utpp = &(*utpp)->next; + } +} + +static void +#ifdef HAVE_VTS +SwitchToTty( void ) +#else +SwitchToTty( struct display *d ) +#endif +{ + struct utmps *utp; +#ifdef HAVE_VTS + int vt; +#endif + + if (!(utp = Malloc( sizeof(*utp) ))) { +#ifdef HAVE_VTS + ForEachDisplay( WakeDisplay ); +#else + d->status = notRunning; +#endif + return; + } +#ifndef HAVE_VTS + d->status = textMode; + utp->d = d; +#endif + utp->time = now; + utp->hadSess = 0; + utp->next = utmpList; + utmpList = utp; + CheckUtmp(); + +#ifdef HAVE_VTS + if ((vt = TTYtoVT( *consoleTTYs ))) + activateVT( vt ); +#endif + + /* XXX output something useful here */ +} + +#ifdef HAVE_VTS +static void +StopToTTY( struct display *d ) +{ + if ((d->displayType & d_location) == dLocal) + switch (d->status) { + default: + rStopDisplay( d, DS_TEXTMODE | 0x100 ); + case reserve: + case textMode: + break; + } +} + +static void +CheckTTYMode( void ) +{ + struct display *d; + + for (d = displays; d; d = d->next) + if (d->status == zombie) + return; + + SwitchToTty(); +} + +#else + +void +SwitchToX( struct display *d ) +{ + struct utmps *utp, **utpp; + + for (utpp = &utmpList; (utp = *utpp); utpp = &(*utpp)->next) + if (utp->d == d) { + *utpp = utp->next; + free( utp ); + d->status = notRunning; + return; + } +} +#endif + +#ifdef XDMCP +static void +StartRemoteLogin( struct display *d ) +{ + char **argv; + int pid; + + Debug( "StartRemoteLogin for %s\n", d->name ); + /* HACK: omitting LoadDisplayResources( d ) here! */ + switch (pid = Fork()) { + case 0: + argv = PrepServerArgv( d, d->serverArgsRemote ); + if (!(argv = addStrArr( argv, "-once", 5 )) || + !(argv = addStrArr( argv, "-query", 6 )) || + !(argv = addStrArr( argv, d->remoteHost, -1 ))) + exit( 1 ); + Debug( "exec %\"[s\n", argv ); + (void)execv( argv[0], argv ); + LogError( "X server %\"s cannot be executed\n", argv[0] ); + + /* Let's try again with some standard paths */ + argv[0] = (char *)realloc(argv[0], strlen("/usr/X11R6/bin/X") + 1); + if (argv[0] != NULL) { + argv[0] = "/usr/X11R6/bin/X"; + Debug( "exec %\"[s\n", argv ); + (void)execv( argv[0], argv ); + LogError( "X server %\"s cannot be executed\n", argv[0] ); + + argv[0] = "/usr/bin/X"; /* Shorter than the previous file name */ + Debug( "exec %\"[s\n", argv ); + (void)execv( argv[0], argv ); + LogError( "X server %\"s cannot be executed\n", argv[0] ); + } + + exit( 1 ); + case -1: + LogError( "Forking X server for remote login failed: %m" ); + d->status = notRunning; + return; + default: + break; + } + Debug( "X server forked, pid %d\n", pid ); + d->serverPid = pid; + + d->status = remoteLogin; +} +#endif + + +static void +StopInactiveDisplay( struct display *d ) +{ + if (d->status != remoteLogin && d->userSess < 0) + StopDisplay( d ); +} + +static void +stoppen( int force ) +{ +#ifdef XDMCP + request_port = 0; + UpdateListenSockets(); +#endif + if (force) + ForEachDisplay( StopDisplay ); + else + ForEachDisplay( StopInactiveDisplay ); + Stopping = 1; +} + + +void +setNLogin( struct display *d, + const char *nuser, const char *npass, char *nargs, int rl ) +{ + struct disphist *he = d->hstent; + he->rLogin = + (ReStr( &he->nuser, nuser ) && + ReStr( &he->npass, npass ) && + ReStr( &he->nargs, nargs )) ? rl : 0; + Debug( "set next login for %s, level %d\n", nuser, rl ); +} + +static void +processDPipe( struct display *d ) +{ + char *user, *pass, *args; + int cmd; + GTalk dpytalk; +#ifdef XDMCP + int ct, len; + ARRAY8 ca, ha; +#endif + + dpytalk.pipe = &d->pipe; + if (Setjmp( dpytalk.errjmp )) { + StopDisplay( d ); + return; + } + GSet( &dpytalk ); + if (!GRecvCmd( &cmd )) { + /* process already exited */ + UnregisterInput( d->pipe.rfd ); + return; + } + switch (cmd) { + case D_User: + d->userSess = GRecvInt(); + d->userName = GRecvStr(); + d->sessName = GRecvStr(); + break; + case D_ReLogin: + user = GRecvStr(); + pass = GRecvStr(); + args = GRecvStr(); + setNLogin( d, user, pass, args, 1 ); + free( args ); + free( pass ); + free( user ); + break; +#ifdef XDMCP + case D_ChooseHost: + ca.data = (unsigned char *)GRecvArr( &len ); + ca.length = (CARD16)len; + ct = GRecvInt(); + ha.data = (unsigned char *)GRecvArr( &len ); + ha.length = (CARD16)len; + RegisterIndirectChoice( &ca, ct, &ha ); + XdmcpDisposeARRAY8( &ha ); + XdmcpDisposeARRAY8( &ca ); + break; + case D_RemoteHost: + if (d->remoteHost) + free( d->remoteHost ); + d->remoteHost = GRecvStr(); + break; +#endif + case D_XConnOk: + startingServer = 0; + break; + default: + LogError( "Internal error: unknown D_* command %d\n", cmd ); + StopDisplay( d ); + break; + } +} + +static void +emitXSessG( struct display *di, struct display *d, void *ctx ATTR_UNUSED ) +{ + GSendStr( di->name ); + GSendStr( "" ); +#ifdef HAVE_VTS + GSendInt( di->serverVT ); +#endif +#ifdef XDMCP + if (di->status == remoteLogin) { + GSendStr( "" ); + GSendStr( di->remoteHost ); + } else +#endif + { + GSendStr( di->userName ); + GSendStr( di->sessName ); + } + GSendInt( di == d ? isSelf : 0 ); +} + +static void +emitTTYSessG( STRUCTUTMP *ut, struct display *d ATTR_UNUSED, void *ctx ATTR_UNUSED ) +{ + GSendStrN( ut->ut_line, sizeof(ut->ut_line) ); + GSendStrN( ut->ut_host, sizeof(ut->ut_host) ); +#ifdef HAVE_VTS + GSendInt( TTYtoVT( ut->ut_line ) ); +#endif +#ifdef BSD_UTMP + GSendStrN( *ut->ut_user ? ut->ut_user : 0, sizeof(ut->ut_user) ); +#else + GSendStrN( ut->ut_type == USER_PROCESS ? ut->ut_user : 0, sizeof(ut->ut_user) ); +#endif + GSendStr( 0 ); /* session type unknown */ + GSendInt( isTTY ); +} + +static void +processGPipe( struct display *d ) +{ + char **opts, *option; + int cmd, ret, dflt, curr; + GTalk dpytalk; + + dpytalk.pipe = &d->gpipe; + if (Setjmp( dpytalk.errjmp )) { + StopDisplay( d ); + return; + } + GSet( &dpytalk ); + if (!GRecvCmd( &cmd )) { + /* process already exited */ + UnregisterInput( d->gpipe.rfd ); + return; + } + switch (cmd) { + case G_ListBootOpts: + ret = getBootOptions( &opts, &dflt, &curr ); + GSendInt( ret ); + if (ret == BO_OK) { + GSendArgv( opts ); + freeStrArr( opts ); + GSendInt( dflt ); + GSendInt( curr ); + } + break; + case G_Shutdown: + sdRec.how = GRecvInt(); + sdRec.start = GRecvInt(); + sdRec.timeout = GRecvInt(); + sdRec.force = GRecvInt(); + sdRec.uid = GRecvInt(); + option = GRecvStr(); + setBootOption( option, &sdRec ); + if (option) + free( option ); + break; + case G_QueryShutdown: + GSendInt( sdRec.how ); + GSendInt( sdRec.start ); + GSendInt( sdRec.timeout ); + GSendInt( sdRec.force ); + GSendInt( sdRec.uid ); + GSendStr( sdRec.osname ); + break; + case G_List: + ListSessions( GRecvInt(), d, 0, emitXSessG, emitTTYSessG ); + GSendInt( 0 ); + break; +#ifdef HAVE_VTS + case G_Activate: + activateVT( GRecvInt() ); + break; +#endif + case G_Console: +#ifdef HAVE_VTS + if (*consoleTTYs) { /* sanity check against greeter */ + ForEachDisplay( StopToTTY ); + CheckTTYMode(); + } +#else + if (*d->console) /* sanity check against greeter */ + rStopDisplay( d, DS_TEXTMODE ); +#endif + break; + default: + LogError( "Internal error: unknown G_* command %d\n", cmd ); + StopDisplay( d ); + break; + } +} + + +static int +ScanConfigs( int force ) +{ + int ret; + + if ((ret = LoadDMResources( force )) <= 0) + return ret; + ScanServers(); +#ifdef XDMCP + ScanAccessDatabase( force ); +#endif + return 1; +} + +static void +MarkDisplay( struct display *d ) +{ + d->stillThere = 0; +} + +static void +RescanConfigs( int force ) +{ + if (ScanConfigs( force ) > 0) { +#ifdef XDMCP + UpdateListenSockets(); +#endif + updateCtrl(); + } +} + +void +cancelShutdown( void ) +{ + sdRec.how = 0; + if (sdRec.osname) { + free( sdRec.osname ); + sdRec.osname = 0; + } + Stopping = 0; + RescanConfigs( TRUE ); +} + + +static void +ReapChildren( void ) +{ + int pid; + struct display *d; + waitType status; + + while ((pid = waitpid( -1, &status, WNOHANG )) > 0) + { + Debug( "manager wait returns pid %d sig %d core %d code %d\n", + pid, waitSig( status ), waitCore( status ), waitCode( status ) ); + /* SUPPRESS 560 */ + if ((d = FindDisplayByPid( pid ))) { + d->pid = -1; + UnregisterInput( d->pipe.rfd ); + GClosen (&d->pipe); + UnregisterInput( d->gpipe.rfd ); + GClosen (&d->gpipe); + closeCtrl( d ); + switch (waitVal( status )) { +#ifdef XDMCP + case EX_REMOTE: + Debug( "display exited with EX_REMOTE\n" ); + ExitDisplay( d, DS_REMOTE, 0, 0 ); + break; +#endif + case EX_NORMAL: + /* (any type of) session ended */ + Debug( "display exited with EX_NORMAL\n" ); + if ((d->displayType & d_lifetime) == dReserve) + ExitDisplay( d, DS_RESERVE, 0, 0 ); + else + ExitDisplay( d, DS_RESTART, XS_KEEP, TRUE ); + break; +#if 0 + case EX_REMANAGE_DPY: + /* user session ended */ + Debug( "display exited with EX_REMANAGE_DPY\n" ); + ExitDisplay( d, DS_RESTART, XS_KEEP, TRUE ); + break; +#endif + case EX_OPENFAILED_DPY: + /* WaitForServer() failed */ + LogError( "Display %s cannot be opened\n", d->name ); +#ifdef XDMCP + /* + * no display connection was ever made, tell the + * terminal that the open attempt failed + */ + if ((d->displayType & d_origin) == dFromXDMCP) + SendFailed( d, "cannot open display" ); +#endif + ExitDisplay( d, DS_RESTART, XS_RETRY, FALSE ); + break; + case waitCompose( SIGTERM,0,0 ): + /* killed before/during WaitForServer() + - local Xserver died + - display stopped (is zombie) + - "login now" and "suicide" pipe commands (is raiser) + */ + Debug( "display exited on SIGTERM\n" ); + ExitDisplay( d, DS_RESTART, XS_RETRY, FALSE ); + break; + case EX_AL_RESERVER_DPY: + /* - killed after WaitForServer() + - Xserver dead after remote session exit + */ + Debug( "display exited with EX_AL_RESERVER_DPY\n" ); + ExitDisplay( d, DS_RESTART, XS_RESTART, FALSE ); + break; + case EX_RESERVER_DPY: + /* induced by greeter: + - could not secure display + - requested by user + */ + Debug( "display exited with EX_RESERVER_DPY\n" ); + ExitDisplay( d, DS_RESTART, XS_RESTART, TRUE ); + break; + case EX_UNMANAGE_DPY: + /* some fatal error */ + Debug( "display exited with EX_UNMANAGE_DPY\n" ); + ExitDisplay( d, DS_REMOVE, 0, 0 ); + break; + default: + /* prolly crash */ + LogError( "Unknown session exit code %d (sig %d) from manager process\n", + waitCode( status ), waitSig( status ) ); + ExitDisplay( d, DS_REMOVE, 0, 0 ); + break; + } + } else if ((d = FindDisplayByServerPid( pid ))) { + d->serverPid = -1; + switch (d->status) { + case zombie: + Debug( "zombie X server for display %s reaped\n", d->name ); +#ifdef HAVE_VTS + if (d->serverVT && d->zstatus != DS_REMOTE) { + if (d->follower) { + d->follower->serverVT = d->serverVT; + d->follower = 0; + } else { + int con = open( "/dev/console", O_RDONLY ); + if (con >= 0) { + struct vt_stat vtstat; + ioctl( con, VT_GETSTATE, &vtstat ); + if (vtstat.v_active == d->serverVT) { + int vt = 1; + struct display *di; + for (di = displays; di; di = di->next) + if (di != d && di->serverVT) + vt = di->serverVT; + for (di = displays; di; di = di->next) + if (di != d && di->serverVT && + (di->userSess >= 0 || + di->status == remoteLogin)) + vt = di->serverVT; + ioctl( con, VT_ACTIVATE, vt ); + } + ioctl( con, VT_DISALLOCATE, d->serverVT ); + close( con ); + } + } + d->serverVT = 0; + } +#endif + rStopDisplay( d, d->zstatus ); + break; + case phoenix: + Debug( "phoenix X server arises, restarting display %s\n", + d->name ); + d->status = notRunning; + break; + case remoteLogin: + Debug( "remote login X server for display %s exited\n", + d->name ); + d->status = ((d->displayType & d_lifetime) == dReserve) ? + reserve : notRunning; + break; + case raiser: + LogError( "X server for display %s terminated unexpectedly\n", + d->name ); + /* don't kill again */ + break; + case running: + if (startingServer == d && d->serverStatus != ignore) { + if (d->serverStatus == starting && waitCode( status ) != 47) + LogError( "X server died during startup\n" ); + StartServerFailed(); + break; + } + LogError( "X server for display %s terminated unexpectedly\n", + d->name ); + if (d->pid != -1) { + Debug( "terminating session pid %d\n", d->pid ); + TerminateProcess( d->pid, SIGTERM ); + } + break; + case notRunning: + case textMode: + case reserve: + /* this cannot happen */ + Debug( "X server exited for passive (%d) session on display %s\n", + (int)d->status, d->name ); + break; + } + } else + Debug( "unknown child termination\n" ); + } +#ifdef NEED_ENTROPY + AddOtherEntropy(); +#endif +} + +static int +wouldShutdown( void ) +{ + struct display *d; + + if (sdRec.force != SHUT_CANCEL) { + if (sdRec.force == SHUT_FORCEMY) + for (d = displays; d; d = d->next) + if (d->status == remoteLogin || + (d->userSess >= 0 && d->userSess != sdRec.uid)) + return 0; + return 1; + } + return !AnyActiveDisplays(); +} + +FD_TYPE WellKnownSocketsMask; +int WellKnownSocketsMax; +int WellKnownSocketsCount; + +void +RegisterInput( int fd ) +{ + /* can be omited, as it is always called right after opening a socket + if (!FD_ISSET (fd, &WellKnownSocketsMask)) + */ + { + FD_SET( fd, &WellKnownSocketsMask ); + if (fd > WellKnownSocketsMax) + WellKnownSocketsMax = fd; + WellKnownSocketsCount++; + } +} + +void +UnregisterInput( int fd ) +{ + /* the check _is_ necessary, as some handles are unregistered before + the regular close sequence. + */ + if (FD_ISSET( fd, &WellKnownSocketsMask )) { + FD_CLR( fd, &WellKnownSocketsMask ); + WellKnownSocketsCount--; + } +} + +static void +SigHandler( int n ) +{ + int olderrno = errno; + char buf = (char)n; + /* Debug( "caught signal %d\n", n ); this hangs in syslog() */ + write( signalFds[1], &buf, 1 ); +#ifdef __EMX__ + (void)Signal( n, SigHandler ); +#endif + errno = olderrno; +} + +static void +MainLoop( void ) +{ + struct display *d; + struct timeval *tvp, tv; + time_t to; + int nready; + char buf; + FD_TYPE reads; + + Debug( "MainLoop\n" ); + time( &now ); + while ( +#ifdef XDMCP + AnyListenSockets() || +#endif + (Stopping ? AnyRunningDisplays() : AnyDisplaysLeft())) + { + if (!Stopping) + StartDisplays(); + to = TO_INF; + if (sdRec.how) { + if (sdRec.start != TO_INF && now < sdRec.start) { + /*if (sdRec.start < to)*/ + to = sdRec.start; + } else { + sdRec.start = TO_INF; + if (now >= sdRec.timeout) { + sdRec.timeout = TO_INF; + if (wouldShutdown()) + stoppen( TRUE ); + else + cancelShutdown(); + } else { + stoppen( FALSE ); + /*if (sdRec.timeout < to)*/ + to = sdRec.timeout; + } + } + } + if (serverTimeout < to) + to = serverTimeout; + if (utmpTimeout < to) + to = utmpTimeout; + if (to == TO_INF) + tvp = 0; + else { + to -= now; + if (to < 0) + to = 0; + tv.tv_sec = to; + tv.tv_usec = 0; + tvp = &tv; + } + reads = WellKnownSocketsMask; + nready = select( WellKnownSocketsMax + 1, &reads, 0, 0, tvp ); + Debug( "select returns %d\n", nready ); + time( &now ); +#ifdef NEED_ENTROPY + AddTimerEntropy(); +#endif + if (now >= serverTimeout) { + serverTimeout = TO_INF; + StartServerTimeout(); + } + if (now >= utmpTimeout) { + utmpTimeout = TO_INF; + CheckUtmp(); + } + if (nready > 0) { + /* + * we restart after the first handled fd, as + * a) it makes things simpler + * b) the probability that multiple fds trigger at once is + * ridiculously small. we handle it in the next iteration. + */ + /* XXX a cleaner solution would be a callback mechanism */ + if (FD_ISSET( signalFds[0], &reads )) { + if (read( signalFds[0], &buf, 1 ) != 1) + LogPanic( "Signal notification pipe broken.\n" ); + switch (buf) { + case SIGTERM: + case SIGINT: + Debug( "shutting down entire manager\n" ); + stoppen( TRUE ); + break; + case SIGHUP: + LogInfo( "Rescanning all config files\n" ); + ForEachDisplay( MarkDisplay ); + RescanConfigs( TRUE ); + break; + case SIGCHLD: + ReapChildren(); + if (!Stopping && autoRescan) + RescanConfigs( FALSE ); + break; + case SIGUSR1: + if (startingServer && + startingServer->serverStatus == starting) + StartServerSuccess(); + break; + } + continue; + } +#ifdef XDMCP + if (ProcessListenSockets( &reads )) + continue; +#endif /* XDMCP */ + if (handleCtrl( &reads, 0 )) + continue; + /* Must be last (because of the breaks)! */ + again: + for (d = displays; d; d = d->next) { + if (handleCtrl( &reads, d )) + goto again; + if (d->pipe.rfd >= 0 && FD_ISSET( d->pipe.rfd, &reads )) { + processDPipe( d ); + break; + } + if (d->gpipe.rfd >= 0 && FD_ISSET( d->gpipe.rfd, &reads )) { + processGPipe( d ); + break; + } + } + } + } +} + +static void +CheckDisplayStatus( struct display *d ) +{ + if ((d->displayType & d_origin) == dFromFile && !d->stillThere) + StopDisplay( d ); + else if ((d->displayType & d_lifetime) == dReserve && + d->status == running && d->userSess < 0 && !d->idleTimeout) + rStopDisplay( d, DS_RESERVE ); + else if (d->status == notRunning) + if (LoadDisplayResources( d ) < 0) { + LogError( "Unable to read configuration for display %s; " + "stopping it.\n", d->name ); + StopDisplay( d ); + return; + } +} + +static void +KickDisplay( struct display *d ) +{ + if (d->status == notRunning) + StartDisplay( d ); + if (d->serverStatus == awaiting && !startingServer) + StartServer( d ); +} + +#ifdef HAVE_VTS +static int active_vts; + +static int +GetBusyVTs( void ) +{ + struct vt_stat vtstat; + int con; + + if (active_vts == -1) { + vtstat.v_state = 0; + if ((con = open( "/dev/console", O_RDONLY )) >= 0) { + ioctl( con, VT_GETSTATE, &vtstat ); + close( con ); + } + active_vts = vtstat.v_state; + } + return active_vts; +} + +static void +AllocateVT( struct display *d ) +{ + struct display *cd; + int i, tvt, volun; + + if ((d->displayType & d_location) == dLocal && + d->status == notRunning && !d->serverVT && d->reqSrvVT >= 0) + { + if (d->reqSrvVT && d->reqSrvVT < 16) + d->serverVT = d->reqSrvVT; + else { + for (i = tvt = 0;;) { + if (serverVTs[i]) { + tvt = atoi( serverVTs[i++] ); + volun = 0; + if (tvt < 0) { + tvt = -tvt; + volun = 1; + } + if (!tvt || tvt >= 16) + continue; + } else { + if (++tvt >= 16) + break; + volun = 1; + } + for (cd = displays; cd; cd = cd->next) { + if (cd->reqSrvVT == tvt && /* protect from lusers */ + (cd->status != zombie || cd->zstatus != DS_REMOVE)) + goto next; + if (cd->serverVT == tvt) { + if (cd->status != zombie || cd->zstatus == DS_REMOTE) + goto next; + if (!cd->follower) { + d->serverVT = -1; + cd->follower = d; + return; + } + } + } + if (!volun || !((1 << tvt) & GetBusyVTs())) { + d->serverVT = tvt; + return; + } + next: ; + } + } + } +} +#endif + +static void +StartDisplays( void ) +{ + ForEachDisplay( CheckDisplayStatus ); + CloseGetter(); +#ifdef HAVE_VTS + active_vts = -1; + ForEachDisplayRev( AllocateVT ); +#endif + ForEachDisplay( KickDisplay ); +} + +void +StartDisplay( struct display *d ) +{ + if (Stopping) { + Debug( "stopping display %s because shutdown is scheduled\n", d->name ); + StopDisplay( d ); + return; + } + +#ifdef HAVE_VTS + if (d->serverVT < 0) + return; +#endif + + d->status = running; + if ((d->displayType & d_location) == dLocal) { + Debug( "StartDisplay %s\n", d->name ); + /* don't bother pinging local displays; we'll + * certainly notice when they exit + */ + d->pingInterval = 0; + if (d->authorize) { + SetLocalAuthorization( d ); + /* + * reset the server after writing the authorization information + * to make it read the file (for compatibility with old + * servers which read auth file only on reset instead of + * at first connection) + */ + if (d->serverPid != -1 && d->resetForAuth && d->resetSignal) + kill( d->serverPid, d->resetSignal ); + } + if (d->serverPid == -1) { + d->serverStatus = awaiting; + return; + } + } else { + Debug( "StartDisplay %s, try %d\n", d->name, d->startTries + 1 ); + /* this will only happen when using XDMCP */ + if (d->authorizations) + SaveServerAuthorizations( d, d->authorizations, d->authNum ); + } + StartDisplayP2( d ); +} + +void +StartDisplayP2( struct display *d ) +{ + char *cname, *cgname; + int pid; + + openCtrl( d ); + Debug( "forking session\n" ); + ASPrintf( &cname, "sub-daemon for display %s", d->name ); + ASPrintf( &cgname, "greeter for display %s", d->name ); + pid = GFork( &d->pipe, "master daemon", cname, + &d->gpipe, cgname ); + switch (pid) { + case 0: + SetTitle( d->name ); + if (debugLevel & DEBUG_WSESS) + sleep( 100 ); + mstrtalk.pipe = &d->pipe; + (void)Signal( SIGPIPE, SIG_IGN ); + SetAuthorization( d ); + WaitForServer( d ); + if ((d->displayType & d_location) == dLocal) { + GSet( &mstrtalk ); + GSendInt( D_XConnOk ); + } + ManageSession( d ); + /* NOTREACHED */ + case -1: + closeCtrl( d ); + d->status = notRunning; + break; + default: + Debug( "forked session, pid %d\n", pid ); + + /* (void) fcntl (d->pipe.rfd, F_SETFL, O_NONBLOCK); */ + /* (void) fcntl (d->gpipe.rfd, F_SETFL, O_NONBLOCK); */ + RegisterInput( d->pipe.rfd ); + RegisterInput( d->gpipe.rfd ); + + d->pid = pid; + d->hstent->lock = d->hstent->rLogin = d->hstent->goodExit = + d->hstent->sdRec.how = 0; + d->lastStart = now; + break; + } +} + +/* + * transition from running to zombie, textmode, reserve or deleted + */ + +static void +rStopDisplay( struct display *d, int endState ) +{ + Debug( "stopping display %s to state %d\n", d->name, endState ); + AbortStartServer( d ); + d->idleTimeout = 0; + if (d->serverPid != -1 || d->pid != -1) { + if (d->pid != -1) + TerminateProcess( d->pid, SIGTERM ); + if (d->serverPid != -1) + TerminateProcess( d->serverPid, d->termSignal ); + d->status = zombie; + d->zstatus = endState & 0xff; + Debug( " zombiefied\n" ); + } else if (endState == DS_TEXTMODE) { +#ifdef HAVE_VTS + d->status = textMode; + CheckTTYMode(); + } else if (endState == (DS_TEXTMODE | 0x100)) { + d->status = textMode; +#else + SwitchToTty( d ); +#endif + } else if (endState == DS_RESERVE) + d->status = reserve; +#ifdef XDMCP + else if (endState == DS_REMOTE) + StartRemoteLogin( d ); +#endif + else { +#ifndef HAVE_VTS + SwitchToX( d ); +#endif + RemoveDisplay( d ); + } +} + +void +StopDisplay( struct display *d ) +{ + rStopDisplay( d, DS_REMOVE ); +} + +static void +ExitDisplay( + struct display *d, + int endState, + int serverCmd, + int goodExit ) +{ + struct disphist *he; + + if (d->status == raiser) { + serverCmd = XS_KEEP; + goodExit = TRUE; + } + + Debug( "ExitDisplay %s, " + "endState = %d, serverCmd = %d, GoodExit = %d\n", + d->name, endState, serverCmd, goodExit ); + + d->userSess = -1; + if (d->userName) + free( d->userName ); + d->userName = 0; + if (d->sessName) + free( d->sessName ); + d->sessName = 0; + he = d->hstent; + he->lastExit = now; + he->goodExit = goodExit; + if (he->sdRec.how) { + if (he->sdRec.force == SHUT_ASK && + (AnyActiveDisplays() || d->allowShutdown == SHUT_ROOT)) + { + endState = DS_RESTART; + } else { + if (!sdRec.how || sdRec.force != SHUT_FORCE || + !((d->allowNuke == SHUT_NONE && sdRec.uid != he->sdRec.uid) || + (d->allowNuke == SHUT_ROOT && he->sdRec.uid))) + { + if (sdRec.osname) + free( sdRec.osname ); + sdRec = he->sdRec; + if (now < sdRec.timeout || wouldShutdown()) + endState = DS_REMOVE; + } else if (he->sdRec.osname) + free( he->sdRec.osname ); + he->sdRec.how = 0; + he->sdRec.osname = 0; + } + } + if (d->status == zombie) + rStopDisplay( d, d->zstatus ); + else { + if (Stopping) { + StopDisplay( d ); + return; + } + if (endState != DS_RESTART || + (d->displayType & d_origin) != dFromFile) + { + rStopDisplay( d, endState ); + } else { + if (serverCmd == XS_RETRY) { + if ((d->displayType & d_location) == dLocal) { + if (he->lastExit - d->lastStart < 120) { + LogError( "Unable to fire up local display %s;" + " disabling.\n", d->name ); + StopDisplay( d ); + return; + } + } else { + if (++d->startTries > d->startAttempts) { + LogError( "Disabling foreign display %s" + " (too many attempts)\n", d->name ); + StopDisplay( d ); + return; + } + } + } else + d->startTries = 0; + if (d->serverPid != -1 && + (serverCmd != XS_KEEP || d->terminateServer)) + { + Debug( "killing X server for %s\n", d->name ); + TerminateProcess( d->serverPid, d->termSignal ); + d->status = phoenix; + } else + d->status = notRunning; + } + } +} + + +static int pidFd; +static FILE *pidFilePtr; + +static int +StorePid( void ) +{ + int oldpid; + + if (pidFile[0] != '\0') { + pidFd = open( pidFile, O_RDWR ); + if (pidFd == -1 && errno == ENOENT) + pidFd = open( pidFile, O_RDWR|O_CREAT, 0666 ); + if (pidFd == -1 || !(pidFilePtr = fdopen( pidFd, "r+" ))) { + LogError( "process-id file %s cannot be opened\n", + pidFile ); + return -1; + } + if (fscanf( pidFilePtr, "%d\n", &oldpid ) != 1) + oldpid = -1; + fseek( pidFilePtr, 0l, 0 ); + if (lockPidFile) { +#ifdef F_SETLK +# ifndef SEEK_SET +# define SEEK_SET 0 +# endif + struct flock lock_data; + lock_data.l_type = F_WRLCK; + lock_data.l_whence = SEEK_SET; + lock_data.l_start = lock_data.l_len = 0; + if (fcntl( pidFd, F_SETLK, &lock_data ) == -1) { + if (errno == EAGAIN) + return oldpid; + else + return -1; + } +#else +# ifdef LOCK_EX + if (flock( pidFd, LOCK_EX|LOCK_NB ) == -1) { + if (errno == EWOULDBLOCK) + return oldpid; + else + return -1; + } +# else + if (lockf( pidFd, F_TLOCK, 0 ) == -1) { + if (errno == EACCES) + return oldpid; + else + return -1; + } +# endif +#endif + } + fprintf( pidFilePtr, "%ld\n", (long)getpid() ); + (void)fflush( pidFilePtr ); + RegisterCloseOnFork( pidFd ); + } + return 0; +} + +#if 0 +void +UnlockPidFile( void ) +{ + if (lockPidFile) +# ifdef F_SETLK + { + struct flock lock_data; + lock_data.l_type = F_UNLCK; + lock_data.l_whence = SEEK_SET; + lock_data.l_start = lock_data.l_len = 0; + (void)fcntl( pidFd, F_SETLK, &lock_data ); + } +# else +# ifdef F_ULOCK + lockf( pidFd, F_ULOCK, 0 ); +# else + flock( pidFd, LOCK_UN ); +# endif +# endif + close( pidFd ); + fclose( pidFilePtr ); +} +#endif + +void +SetTitle( const char *name ) +{ +#if !defined(HAVE_SETPROCTITLE) && !defined(NOXDMTITLE) + char *p; + int left; +#endif + + ASPrintf( &prog, "%s: %s", prog, name ); + ReInitErrorLog(); +#ifdef HAVE_SETPROCTITLE + setproctitle( "%s", name ); +#elif !defined(NOXDMTITLE) + p = Title; + left = TitleLen; + + *p++ = '-'; + --left; + while (*name && left > 0) { + *p++ = *name++; + --left; + } + while (left > 0) { + *p++ = '\0'; + --left; + } +#endif +} |