summaryrefslogtreecommitdiffstats
path: root/tdm/backend/ctrl.c
diff options
context:
space:
mode:
Diffstat (limited to 'tdm/backend/ctrl.c')
-rw-r--r--tdm/backend/ctrl.c1015
1 files changed, 1015 insertions, 0 deletions
diff --git a/tdm/backend/ctrl.c b/tdm/backend/ctrl.c
new file mode 100644
index 000000000..622c9370d
--- /dev/null
+++ b/tdm/backend/ctrl.c
@@ -0,0 +1,1015 @@
+/*
+
+Copyright 1988, 1998 The Open Group
+Copyright 2001-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_socket.h"
+#include "dm_error.h"
+
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+
+static void
+acceptSock( CtrlRec *cr )
+{
+ struct cmdsock *cs;
+ int fd;
+
+ if ((fd = accept( cr->fd, 0, 0 )) < 0) {
+ bust:
+ LogError( "Error accepting command connection\n" );
+ return;
+ }
+ if (!(cs = Malloc( sizeof(*cs) ))) {
+ close( fd );
+ goto bust;
+ }
+ cs->sock.fd = fd;
+ cs->sock.buffer = 0;
+ cs->sock.buflen = 0;
+ cs->next = cr->css;
+ cr->css = cs;
+ fcntl( fd, F_SETFL, fcntl( fd, F_GETFL ) | O_NONBLOCK );
+ RegisterCloseOnFork( fd );
+ RegisterInput( fd );
+}
+
+static void
+nukeSock( struct cmdsock *cs )
+{
+ UnregisterInput( cs->sock.fd );
+ CloseNClearCloseOnFork( cs->sock.fd );
+ if (cs->sock.buffer)
+ free( cs->sock.buffer );
+ free( cs );
+}
+
+#ifdef HONORS_SOCKET_PERMS
+static CtrlRec ctrl = { 0, 0, -1, 0, 0, { -1, 0, 0 } };
+#else
+static CtrlRec ctrl = { 0, 0, 0, -1, 0, 0, { -1, 0, 0 } };
+
+static int mkTempDir( char *dir )
+{
+ int i, l = strlen( dir ) - 6;
+
+ for (i = 0; i < 100; i++) {
+ randomStr( dir + l );
+ if (!mkdir( dir, 0700 ))
+ return True;
+ if (errno != EEXIST)
+ break;
+ }
+ return False;
+}
+#endif
+
+void
+openCtrl( struct display *d )
+{
+ CtrlRec *cr;
+ const char *dname;
+ char *sockdir;
+ struct sockaddr_un sa;
+
+ if (!*fifoDir)
+ return;
+ if (d) {
+ cr = &d->ctrl, dname = d->name;
+ if (!memcmp( dname, "localhost:", 10 ))
+ dname += 9;
+ } else
+ cr = &ctrl, dname = 0;
+ if (cr->fifo.fd < 0) {
+ if (mkdir( fifoDir, 0755 )) {
+ if (errno != EEXIST) {
+ LogError( "mkdir %\"s failed; no control FiFos will be available\n",
+ fifoDir );
+ return;
+ }
+ } else
+ chmod( fifoDir, 0755 ); /* override umask */
+ StrApp( &cr->fpath, fifoDir, dname ? "/xdmctl-" : "/xdmctl",
+ dname, (char *)0 );
+ if (cr->fpath) {
+ unlink( cr->fpath );
+ if (mkfifo( cr->fpath, 0 ) < 0)
+ LogError( "Cannot create control FiFo %\"s\n", cr->fpath );
+ else {
+ cr->gid = fifoGroup;
+ if (!d)
+ chown( cr->fpath, -1, fifoGroup );
+ chmod( cr->fpath, 0620 );
+ if ((cr->fifo.fd = open( cr->fpath, O_RDWR | O_NONBLOCK )) >= 0) {
+ RegisterCloseOnFork( cr->fifo.fd );
+ RegisterInput( cr->fifo.fd );
+ goto fifok;
+ }
+ unlink( cr->fpath );
+ LogError( "Cannot open control FiFo %\"s\n", cr->fpath );
+ }
+ free( cr->fpath );
+ cr->fpath = 0;
+ }
+ }
+ fifok:
+ if (cr->fd < 0) {
+ /* fifoDir is created above already */
+ sockdir = 0;
+ StrApp( &sockdir, fifoDir, dname ? "/dmctl-" : "/dmctl",
+ dname, (char *)0 );
+ if (sockdir) {
+ StrApp( &cr->path, sockdir, "/socket", (char *)0 );
+ if (cr->path) {
+ if (strlen( cr->path ) >= sizeof(sa.sun_path))
+ LogError( "path %\"s too long; no control sockets will be available\n",
+ cr->path );
+#ifdef HONORS_SOCKET_PERMS
+ else if (mkdir( sockdir, 0700 ) && errno != EEXIST)
+ LogError( "mkdir %\"s failed; no control sockets will be available\n",
+ sockdir );
+ else if (unlink( cr->path ) && errno != ENOENT)
+ LogError( "unlink %\"s failed: %m; control socket will not be available\n",
+ cr->path );
+ else {
+#else
+ else if (unlink( sockdir ) && errno != ENOENT)
+ LogError( "unlink %\"s failed: %m; control socket will not be available\n",
+ sockdir );
+ else if (!StrApp( &cr->realdir, sockdir, "-XXXXXX", (char *)0))
+ ;
+ else if (!mkTempDir( cr->realdir )) {
+ LogError( "mkdir %\"s failed: %m; control socket will not be available\n",
+ cr->realdir );
+ free( cr->realdir );
+ cr->realdir = 0;
+ } else if (symlink( cr->realdir, sockdir )) {
+ LogError( "symlink %\"s => %\"s failed: %m; control socket will not be available\n",
+ sockdir, cr->realdir );
+ rmdir( cr->realdir );
+ free( cr->realdir );
+ cr->realdir = 0;
+ } else {
+ chown( sockdir, 0, d ? 0 : fifoGroup );
+ chmod( sockdir, 0750 );
+#endif
+ if ((cr->fd = socket( PF_UNIX, SOCK_STREAM, 0 )) < 0)
+ LogError( "Cannot create control socket\n" );
+ else {
+ sa.sun_family = AF_UNIX;
+ strcpy( sa.sun_path, cr->path );
+ if (!bind( cr->fd, (struct sockaddr *)&sa, sizeof(sa) )) {
+ if (!listen( cr->fd, 5 )) {
+#ifdef HONORS_SOCKET_PERMS
+ chmod( cr->path, 0660 );
+ if (!d)
+ chown( cr->path, -1, fifoGroup );
+ chmod( sockdir, 0755 );
+#else
+ chmod( cr->path, 0666 );
+#endif
+ RegisterCloseOnFork( cr->fd );
+ RegisterInput( cr->fd );
+ free( sockdir );
+ return;
+ }
+ unlink( cr->path );
+ LogError( "Cannot listen on control socket %\"s\n",
+ cr->path );
+ } else
+ LogError( "Cannot bind control socket %\"s\n",
+ cr->path );
+ close( cr->fd );
+ cr->fd = -1;
+ }
+#ifdef HONORS_SOCKET_PERMS
+ rmdir( sockdir );
+#else
+ unlink( sockdir );
+ rmdir( cr->realdir );
+ free( cr->realdir );
+ cr->realdir = 0;
+#endif
+ }
+ free( cr->path );
+ cr->path = 0;
+ }
+ free( sockdir );
+ }
+ }
+}
+
+void
+closeCtrl( struct display *d )
+{
+ CtrlRec *cr = d ? &d->ctrl : &ctrl;
+
+ if (cr->fd >= 0) {
+ UnregisterInput( cr->fd );
+ CloseNClearCloseOnFork( cr->fd );
+ cr->fd = -1;
+ unlink( cr->path );
+ *strrchr( cr->path, '/' ) = 0;
+#ifdef HONORS_SOCKET_PERMS
+ rmdir( cr->path );
+#else
+ unlink( cr->path );
+ rmdir( cr->realdir );
+ free( cr->realdir );
+ cr->realdir = 0;
+#endif
+ free( cr->path );
+ cr->path = 0;
+ while (cr->css) {
+ struct cmdsock *cs = cr->css;
+ cr->css = cs->next;
+ nukeSock( cs );
+ }
+ }
+ if (cr->fifo.fd >= 0) {
+ UnregisterInput( cr->fifo.fd );
+ CloseNClearCloseOnFork( cr->fifo.fd );
+ cr->fifo.fd = -1;
+ unlink( cr->fpath );
+ free( cr->fpath );
+ cr->fpath = 0;
+ if (cr->fifo.buffer)
+ free( cr->fifo.buffer );
+ cr->fifo.buffer = 0;
+ cr->fifo.buflen = 0;
+ }
+}
+
+void
+chownCtrl( CtrlRec *cr, int uid )
+{
+ if (cr->fpath)
+ chown( cr->fpath, uid, -1 );
+ if (cr->path)
+#ifdef HONORS_SOCKET_PERMS
+ chown( cr->path, uid, -1 );
+#else
+ chown( cr->realdir, uid, -1 );
+#endif
+}
+
+void
+updateCtrl( void )
+{
+ unsigned ffl, slc;
+
+ ffl = 0;
+ if (ctrl.path)
+ for (ffl = strlen( ctrl.path ), slc = 2; ;)
+ if (ctrl.path[--ffl] == '/')
+ if (!--slc)
+ break;
+ if (ffl != strlen( fifoDir ) || memcmp( fifoDir, ctrl.path, ffl ) ||
+ ctrl.gid != fifoGroup)
+ {
+ closeCtrl( 0 );
+ openCtrl( 0 );
+ }
+}
+
+
+static void
+fLog( struct display *d, int fd, const char *sts, const char *msg, ... )
+{
+ char *fmsg, *otxt;
+ const char *what;
+ int olen;
+ va_list va;
+
+ va_start( va, msg );
+ VASPrintf( &fmsg, msg, va );
+ va_end( va );
+ if (!fmsg)
+ return;
+ if (fd >= 0) {
+ olen = ASPrintf( &otxt, "%s\t%\\s\n", sts, fmsg );
+ if (otxt) {
+ Writer( fd, otxt, olen );
+ free( otxt );
+ }
+ what = "socket";
+ } else
+ what = "FiFo";
+ if (d)
+ Debug( "control %s for %s: %s - %s", what, d->name, sts, fmsg );
+ else
+ Debug( "global control %s: %s - %s", what, sts, fmsg );
+ free( fmsg );
+}
+
+
+static char *
+unQuote( const char *str )
+{
+ char *ret, *adp;
+
+ if (!(ret = Malloc( strlen( str ) + 1 )))
+ return 0;
+ for (adp = ret; *str; str++, adp++)
+ if (*str == '\\')
+ switch (*++str) {
+ case 0: str--; /* fallthrough */
+ case '\\': *adp = '\\'; break;
+ case 'n': *adp = '\n'; break;
+ case 't': *adp = '\t'; break;
+ default: *adp++ = '\\'; *adp = *str; break;
+ }
+ else
+ *adp = *str;
+ *adp = 0;
+ return ret;
+}
+
+static void
+str_cat_l( char **bp, const char *str, int max )
+{
+ int dnl = StrNLen( str, max );
+ memcpy( *bp, str, dnl );
+ *bp += dnl;
+}
+
+static void
+str_cat( char **bp, const char *str )
+{
+ int dnl = strlen( str );
+ memcpy( *bp, str, dnl );
+ *bp += dnl;
+}
+
+static void
+sd_cat( char **bp, SdRec *sdr )
+{
+ if (sdr->how == SHUT_HALT)
+ str_cat( bp, "halt," );
+ else
+ str_cat( bp, "reboot," );
+ if (sdr->start == TO_INF)
+ str_cat( bp, "0," );
+ else
+ *bp += sprintf( *bp, "%d,", sdr->start );
+ if (sdr->timeout == TO_INF)
+ str_cat( bp, "-1," );
+ else
+ *bp += sprintf( *bp, "%d,", sdr->timeout );
+ if (sdr->force == SHUT_ASK)
+ str_cat( bp, "ask" );
+ else if (sdr->force == SHUT_FORCE)
+ str_cat( bp, "force" );
+ else if (sdr->force == SHUT_FORCEMY)
+ str_cat( bp, "forcemy" );
+ else
+ str_cat( bp, "cancel" );
+ *bp += sprintf( *bp, ",%d,%s", sdr->uid, sdr->osname ? sdr->osname : "-" );
+}
+
+static void
+emitXSessC( struct display *di, struct display *d, void *ctx )
+{
+ char *dname, *bp;
+ char cbuf[1024];
+
+ bp = cbuf;
+ *bp++ = '\t';
+ dname = di->name;
+ if (!memcmp( dname, "localhost:", 10 ))
+ dname += 9;
+ str_cat_l( &bp, dname, sizeof(cbuf)/2 );
+ *bp++ = ',';
+#ifdef HAVE_VTS
+ if (di->serverVT)
+ bp += sprintf( bp, "vt%d", di->serverVT );
+#endif
+ *bp++ = ',';
+#ifdef XDMCP
+ if (di->status == remoteLogin) {
+ *bp++ = ',';
+ str_cat_l( &bp, di->remoteHost, sizeof(cbuf)/3 );
+ } else
+#endif
+ {
+ if (di->userName)
+ str_cat_l( &bp, di->userName, sizeof(cbuf)/5 );
+ *bp++ = ',';
+ if (di->sessName)
+ str_cat_l( &bp, di->sessName, sizeof(cbuf)/5 );
+ }
+ *bp++ = ',';
+ if (di == d)
+ *bp++ = '*';
+ if (di->userSess >= 0 &&
+ (d ? (d->userSess != di->userSess &&
+ (d->allowNuke == SHUT_NONE ||
+ (d->allowNuke == SHUT_ROOT && d->userSess))) :
+ !fifoAllowNuke))
+ *bp++ = '!';
+ Writer( (int)ctx, cbuf, bp - cbuf );
+}
+
+static void
+emitTTYSessC( STRUCTUTMP *ut, struct display *d, void *ctx )
+{
+ struct passwd *pw;
+ char *bp;
+ int vt, l;
+ char cbuf[sizeof(ut->ut_line) + sizeof(ut->ut_user) + sizeof(ut->ut_host) + 16];
+ char user[sizeof(ut->ut_user) + 1];
+
+#ifndef BSD_UTMP
+ if (ut->ut_type != USER_PROCESS)
+ l = 0;
+ else
+#endif
+ {
+ l = StrNLen( ut->ut_user, sizeof(ut->ut_user) );
+ memcpy( user, ut->ut_user, l );
+ }
+ user[l] = 0;
+ bp = cbuf;
+ *bp++ = '\t';
+ str_cat_l( &bp, ut->ut_line, sizeof(ut->ut_line) );
+ *bp++ = ',';
+ if (*ut->ut_host) {
+ *bp++ = '@';
+ str_cat_l( &bp, ut->ut_host, sizeof(ut->ut_host) );
+ }
+#ifdef HAVE_VTS
+ else if ((vt = TTYtoVT( ut->ut_line )))
+ bp += sprintf( bp, "vt%d", vt );
+#endif
+ *bp++ = ',';
+ str_cat( &bp, user );
+ *bp++ = ',';
+ /* blank: session type unknown */
+ *bp++ = ',';
+ /* blank: certainly not querying display */
+ *bp++ = 't';
+ if (*user &&
+ (d ? ((d->allowNuke == SHUT_NONE ||
+ (d->allowNuke == SHUT_ROOT && d->userSess)) &&
+ (!(pw = getpwnam( user )) || d->userSess != (int)pw->pw_uid)) :
+ !fifoAllowNuke))
+ *bp++ = '!';
+ Writer( (int)ctx, cbuf, bp - cbuf );
+}
+
+static void
+processCtrl( const char *string, int len, int fd, struct display *d )
+{
+#define Reply(t) Writer (fd, t, strlen (t))
+
+ struct display *di;
+ const char *word;
+ char **ar, **ap, *args, *bp;
+ SdRec sdr;
+ char cbuf[1024];
+
+ if (!(ar = initStrArr( 0 )))
+ return;
+ for (word = string; ; string++, len--)
+ if (!len || *string == '\t') {
+ if (!(ar = addStrArr( ar, word, string - word )))
+ return;
+ if (!len)
+ break;
+ word = string + 1;
+ }
+ word = fd >= 0 ? "socket" : "FiFo";
+ if (d)
+ Debug( "control %s for %s received %'[s\n", word, d->name, ar );
+ else
+ Debug( "global control %s received %'[s\n", word, ar );
+ if (ar[0]) {
+ if (fd >= 0 && !strcmp( ar[0], "caps" )) {
+ if (ar[1])
+ goto exce;
+ Reply( "ok\ttdm\tlist\t" );
+ if (bootManager != BO_NONE)
+ Reply( "bootoptions\t" );
+ if (d) {
+ if ((d->displayType & d_location) == dLocal)
+#ifdef HAVE_VTS
+ Reply( "local\tactivate\t" );
+#else
+ Reply( "local\t" );
+#endif
+ if (d->allowShutdown != SHUT_NONE) {
+ if (d->allowShutdown == SHUT_ROOT && d->userSess)
+ Reply( "shutdown root\t" );
+ else
+ Reply( "shutdown\t" );
+ Reply( "shutdown ask\t" );
+ if (d->allowNuke != SHUT_NONE) {
+ if (d->allowNuke == SHUT_ROOT && d->userSess)
+ Reply( "nuke root\t" );
+ else
+ Reply( "nuke\t" );
+ }
+ }
+ if ((d->displayType & d_location) == dLocal &&
+ AnyReserveDisplays())
+ Writer( fd, cbuf, sprintf( cbuf, "reserve %d\t",
+ idleReserveDisplays() ) );
+ Reply( "lock\tsuicide\n" );
+ } else {
+ if (fifoAllowShutdown) {
+ Reply( "shutdown\t" );
+ if (fifoAllowNuke)
+ Reply( "nuke\t" );
+ }
+ if (AnyReserveDisplays())
+ Writer( fd, cbuf, sprintf( cbuf, "reserve %d\t",
+ idleReserveDisplays() ) );
+#ifdef HAVE_VTS
+ Reply( "login\tactivate\n" );
+#else
+ Reply( "login\n" );
+#endif
+ }
+ goto bust;
+ } else if (fd >= 0 && !strcmp( ar[0], "list" )) {
+ int flags = lstRemote | lstTTY;
+ if (ar[1]) {
+ if (!strcmp( ar[1], "all" ))
+ flags = lstRemote | lstPassive | lstTTY;
+ else if (!strcmp( ar[1], "alllocal" ))
+ flags = lstPassive | lstTTY;
+ else {
+ fLog( d, fd, "bad", "invalid list scope %\"s", ar[1] );
+ goto bust;
+ }
+ if (ar[2])
+ goto exce;
+ }
+ Reply( "ok" );
+ ListSessions( flags, d, (void *)fd, emitXSessC, emitTTYSessC );
+ Reply( "\n" );
+ goto bust;
+ } else if (!strcmp( ar[0], "reserve" )) {
+ int lt = 60; /* XXX make default timeout configurable? */
+ if (ar[1]) {
+ lt = strtol( ar[1], &bp, 10 );
+ if (lt < 15 || *bp) {
+ fLog( d, fd, "bad", "invalid timeout %\"s", ar[1] );
+ goto bust;
+ }
+ if (ar[2])
+ goto exce;
+ }
+ if (d && (d->displayType & d_location) != dLocal) {
+ fLog( d, fd, "perm", "display is not local" );
+ goto bust;
+ }
+ if (!StartReserveDisplay( lt )) {
+ fLog( d, fd, "noent", "no reserve display available" );
+ goto bust;
+ }
+#ifdef HAVE_VTS
+ } else if (!strcmp( ar[0], "activate" )) {
+ int vt;
+ if (!ar[1])
+ goto miss;
+ if (ar[2])
+ goto exce;
+ if (d && (d->displayType & d_location) != dLocal) {
+ fLog( d, fd, "perm", "display is not local" );
+ goto bust;
+ }
+ if (ar[1][0] != 'v' || ar[1][1] != 't' ||
+ (vt = atoi( ar[1] + 2 )) <= 0)
+ {
+ if (!(di = FindDisplayByName( ar[1] ))) {
+ fLog( d, fd, "noent", "display not found" );
+ goto bust;
+ }
+ if ((di->displayType & d_location) != dLocal) {
+ fLog( d, fd, "inval", "target display is not local" );
+ goto bust;
+ }
+ if (!di->serverVT) {
+ fLog( d, fd, "noent", "target display has no VT assigned" );
+ goto bust;
+ }
+ vt = di->serverVT;
+ }
+ if (!activateVT( vt )) {
+ fLog( d, fd, "inval", "VT switch failed" );
+ goto bust;
+ }
+#endif
+ } else if (!strcmp( ar[0], "shutdown" )) {
+ ap = ar;
+ if (!*++ap)
+ goto miss;
+ sdr.force = SHUT_CANCEL;
+ sdr.osname = 0;
+ if (!strcmp( *ap, "status" )) {
+ if (fd < 0)
+ goto bust;
+ if (*++ap)
+ goto exce;
+ bp = cbuf;
+ *bp++ = 'o';
+ *bp++ = 'k';
+ if (sdRec.how) {
+ str_cat( &bp, "\tglobal," );
+ sd_cat( &bp, &sdRec );
+ }
+ if (d && d->hstent->sdRec.how) {
+ str_cat( &bp, "\tlocal," );
+ sd_cat( &bp, &d->hstent->sdRec );
+ }
+ *bp++ = '\n';
+ Writer( fd, cbuf, bp - cbuf );
+ goto bust;
+ } else if (!strcmp( *ap, "cancel" )) {
+ sdr.how = 0;
+ sdr.start = 0;
+ if (ap[1]) {
+ if (!d)
+ goto exce;
+ if (!strcmp( *++ap, "global" ))
+ sdr.start = TO_INF;
+ else if (strcmp( *ap, "local" )) {
+ fLog( d, fd, "bad", "invalid cancel scope %\"s", *ap );
+ goto bust;
+ }
+ }
+ } else {
+ if (!strcmp( *ap, "reboot" ))
+ sdr.how = SHUT_REBOOT;
+ else if (!strcmp( *ap, "halt" ))
+ sdr.how = SHUT_HALT;
+ else {
+ fLog( d, fd, "bad", "invalid type %\"s", *ap );
+ goto bust;
+ }
+ sdr.uid = -1;
+ if (!*++ap)
+ goto miss;
+ if (**ap == '=') {
+ switch (setBootOption( *ap + 1, &sdr )) {
+ case BO_NOMAN:
+ fLog( d, fd, "notsup", "boot options unavailable" );
+ goto bust;
+ case BO_NOENT:
+ fLog( d, fd, "noent", "no such boot option" );
+ goto bust;
+ case BO_IO:
+ fLog( d, fd, "io", "io error" );
+ goto bust;
+ }
+ if (!*++ap)
+ goto miss;
+ }
+ sdr.start = strtol( *ap, &bp, 10 );
+ if (bp != *ap && !*bp) {
+ if (**ap == '+')
+ sdr.start += now;
+ if (!*++ap)
+ goto miss;
+ sdr.timeout = strtol( *ap, &bp, 10 );
+ if (bp == *ap || *bp) {
+ fLog( d, fd, "bad", "invalid timeout %\"s", ar[3] );
+ goto bust;
+ }
+ if (**ap == '+')
+ sdr.timeout += sdr.start ? sdr.start : now;
+ if (sdr.timeout < 0)
+ sdr.timeout = TO_INF;
+ else {
+ if (!*++ap)
+ goto miss;
+ if (!strcmp( *ap, "force" ))
+ sdr.force = SHUT_FORCE;
+ else if (d && !strcmp( *ap, "forcemy" ))
+ sdr.force = SHUT_FORCEMY;
+ else if (strcmp( *ap, "cancel" )) {
+ fLog( d, fd, "bad", "invalid timeout action %\"s",
+ *ap );
+ goto bust;
+ }
+ }
+ } else {
+ sdr.timeout = 0;
+ if (d && !strcmp( *ap, "ask" ))
+ sdr.force = SHUT_ASK;
+ else if (!strcmp( *ap, "forcenow" ))
+ sdr.force = SHUT_FORCE;
+ else if (!strcmp( *ap, "schedule" ))
+ sdr.timeout = TO_INF;
+ else if (strcmp( *ap, "trynow" )) {
+ fLog( d, fd, "bad", "invalid mode %\"s", *ap );
+ goto bust;
+ }
+ }
+ }
+ if (*++ap)
+ goto exce;
+ if (d) {
+ sdr.uid = d->userSess >= 0 ? d->userSess : 0;
+ if (d->allowShutdown == SHUT_NONE ||
+ (d->allowShutdown == SHUT_ROOT && sdr.uid &&
+ sdr.force != SHUT_ASK))
+ {
+ fLog( d, fd, "perm", "shutdown forbidden" );
+ goto bust;
+ }
+ if (!sdr.how && !sdr.start) {
+ if (d->hstent->sdRec.osname)
+ free( d->hstent->sdRec.osname );
+ d->hstent->sdRec = sdr;
+ } else {
+ if (sdRec.how && sdRec.force == SHUT_FORCE &&
+ ((d->allowNuke == SHUT_NONE && sdRec.uid != sdr.uid) ||
+ (d->allowNuke == SHUT_ROOT && sdr.uid)))
+ {
+ fLog( d, fd, "perm", "overriding forced shutdown forbidden" );
+ goto bust;
+ }
+ if (sdr.force == SHUT_FORCE &&
+ (d->allowNuke == SHUT_NONE ||
+ (d->allowNuke == SHUT_ROOT && sdr.uid)))
+ {
+ fLog( d, fd, "perm", "forced shutdown forbidden" );
+ goto bust;
+ }
+ if (!sdr.start) {
+ if (d->hstent->sdRec.osname)
+ free( d->hstent->sdRec.osname );
+ d->hstent->sdRec = sdr;
+ } else {
+ if (!sdr.how)
+ cancelShutdown();
+ else {
+ if (sdRec.osname)
+ free( sdRec.osname );
+ sdRec = sdr;
+ }
+ }
+ }
+ } else {
+ if (!fifoAllowShutdown) {
+ fLog( d, fd, "perm", "shutdown forbidden" );
+ goto bust;
+ }
+ if (sdRec.how && sdRec.force == SHUT_FORCE &&
+ sdRec.uid != -1 && !fifoAllowNuke)
+ {
+ fLog( d, fd, "perm", "overriding forced shutdown forbidden" );
+ goto bust;
+ }
+ if (!sdr.how)
+ cancelShutdown();
+ else {
+ if (sdr.force != SHUT_CANCEL) {
+ if (!fifoAllowNuke) {
+ fLog( d, fd, "perm", "forced shutdown forbidden" );
+ goto bust;
+ }
+ } else {
+ if (!sdr.start && !sdr.timeout && AnyActiveDisplays()) {
+ fLog( d, fd, "busy", "user sessions running" );
+ goto bust;
+ }
+ }
+ sdr.uid = -1;
+ if (sdRec.osname)
+ free( sdRec.osname );
+ sdRec = sdr;
+ }
+ }
+ } else if (fd >= 0 && !strcmp( ar[0], "listbootoptions" )) {
+ char **opts;
+ int def, cur, i, j;
+
+ if (ar[1])
+ goto exce;
+ switch (getBootOptions( &opts, &def, &cur )) {
+ case BO_NOMAN:
+ fLog( d, fd, "notsup", "boot options unavailable" );
+ goto bust;
+ case BO_IO:
+ fLog( d, fd, "io", "io error" );
+ goto bust;
+ }
+ Reply( "ok\t" );
+ for (i = 0; opts[i]; i++) {
+ bp = cbuf;
+ if (i)
+ *bp++ = ' ';
+ for (j = 0; opts[i][j]; j++)
+ if (opts[i][j] == ' ') {
+ *bp++ = '\\';
+ *bp++ = 's';
+ } else
+ *bp++ = opts[i][j];
+ Writer( fd, cbuf, bp - cbuf );
+ }
+ freeStrArr( opts );
+ Writer( fd, cbuf, sprintf( cbuf, "\t%d\t%d\n", def, cur ) );
+ goto bust;
+ } else if (d) {
+ if (!strcmp( ar[0], "lock" )) {
+ if (ar[1])
+ goto exce;
+ d->hstent->lock = 1;
+ } else if (!strcmp( ar[0], "unlock" )) {
+ if (ar[1])
+ goto exce;
+ d->hstent->lock = 0;
+ } else if (!strcmp( ar[0], "suicide" )) {
+ if (ar[1])
+ goto exce;
+ if (d->status == running && d->pid != -1) {
+ TerminateProcess( d->pid, SIGTERM );
+ d->status = raiser;
+ }
+ } else {
+ fLog( d, fd, "nosys", "unknown command" );
+ goto bust;
+ }
+ } else {
+ if (!strcmp( ar[0], "login" )) {
+ int nuke;
+ if (arrLen( ar ) < 5) {
+ miss:
+ fLog( d, fd, "bad", "missing argument(s)" );
+ goto bust;
+ }
+ if (!(di = FindDisplayByName( ar[1] ))) {
+ fLog( d, fd, "noent", "display %s not found", ar[1] );
+ goto bust;
+ }
+ if (ar[5]) {
+ if (!(args = unQuote( ar[5] ))) {
+ fLog( d, fd, "nomem", "out of memory" );
+ goto bust;
+ }
+ if (ar[6]) {
+ free( args );
+ exce:
+ fLog( d, fd, "bad", "excess argument(s)" );
+ goto bust;
+ }
+ setNLogin( di, ar[3], ar[4], args, 2 );
+ free( args );
+ } else
+ setNLogin( di, ar[3], ar[4], 0, 2 );
+ nuke = !strcmp( ar[2], "now" );
+ switch (di->status) {
+ case running:
+ if (di->pid != -1 && (di->userSess < 0 || nuke)) {
+ TerminateProcess( di->pid, SIGTERM );
+ di->status = raiser;
+ }
+ break;
+ case remoteLogin:
+ if (di->serverPid != -1 && nuke)
+ TerminateProcess( di->serverPid, di->termSignal );
+ break;
+ case reserve:
+ di->status = notRunning;
+ break;
+ case textMode:
+#ifndef HAVE_VTS
+ SwitchToX( di );
+#endif
+ break;
+ default:
+ break;
+ }
+ } else {
+ fLog( d, fd, "nosys", "unknown command" );
+ goto bust;
+ }
+ }
+ if (fd >= 0)
+ Reply( "ok\n" );
+ }
+ bust:
+ freeStrArr( ar );
+}
+
+static int
+handleChan( struct display *d, struct bsock *cs, int fd, FD_TYPE *reads )
+{
+ char *bufp, *nbuf, *obuf, *eol;
+ int len, bl, llen;
+ char buf[1024];
+
+ bl = cs->buflen;
+ obuf = cs->buffer;
+ if (bl <= 0 && FD_ISSET( cs->fd, reads )) {
+ FD_CLR( cs->fd, reads );
+ bl = -bl;
+ memcpy( buf, obuf, bl );
+ if ((len = Reader( cs->fd, buf + bl, sizeof(buf) - bl )) <= 0)
+ return -1;
+ bl += len;
+ bufp = buf;
+ } else {
+ len = 0;
+ bufp = obuf;
+ }
+ if (bl > 0) {
+ if ((eol = memchr( bufp, '\n', bl ))) {
+ llen = eol - bufp + 1;
+ bl -= llen;
+ if (bl) {
+ if (!(nbuf = Malloc( bl )))
+ return -1;
+ memcpy( nbuf, bufp + llen, bl );
+ } else
+ nbuf = 0;
+ cs->buffer = nbuf;
+ cs->buflen = bl;
+ processCtrl( bufp, llen - 1, fd, d );
+ if (obuf)
+ free( obuf );
+ return 1;
+ } else if (!len) {
+ if (fd >= 0)
+ cs->buflen = -bl;
+ else
+ fLog( d, -1, "bad", "unterminated command" );
+ }
+ }
+ return 0;
+}
+
+int
+handleCtrl( FD_TYPE *reads, struct display *d )
+{
+ CtrlRec *cr = d ? &d->ctrl : &ctrl;
+ struct cmdsock *cs, **csp;
+
+ if (cr->fifo.fd >= 0) {
+ switch (handleChan( d, &cr->fifo, -1, reads )) {
+ case -1:
+ if (cr->fifo.buffer)
+ free( cr->fifo.buffer );
+ cr->fifo.buflen = 0;
+ break;
+ case 1:
+ return 1;
+ default:
+ break;
+ }
+ }
+ if (cr->fd >= 0 && FD_ISSET( cr->fd, reads ))
+ acceptSock( cr );
+ else {
+ for (csp = &cr->css; (cs = *csp); ) {
+ switch (handleChan( d, &cs->sock, cs->sock.fd, reads )) {
+ case -1:
+ *csp = cs->next;
+ nukeSock( cs );
+ continue;
+ case 1:
+ return 1;
+ default:
+ break;
+ }
+ csp = &cs->next;
+ }
+ }
+ return 0;
+}