-- Transport mechanism: Viticulturists' interface

-- [Indigo]<Grapevine>MS>Enquiry.mesa

-- Andrew Birrell  27-Oct-82 16:07:51
-- Michael Schroeder, 25-Jan-83 13:03:03

DIRECTORY
Ascii		USING[ CR, DEL, SP ],
BodyDefs	USING[ maxRNameLength, RName ],
EnquiryDefs,
GlassDefs	USING[ Handle, Listen, TimeOut ],
FrameDefs	USING[ IsBound ],
ImageDefs	USING[ StopMesa ],
LogDefs		USING[ WriteLogEntry ],
LogWatchDefs	USING[ GetLogPosition, LogPosition, PutLogChars ],
MaintainDefs	USING[ DoIt ],
NameInfoDefs	USING[ Authenticate, IsMemberClosure, Membership ],
PolicyDefs	USING[ Activate, Operation, PeriodicProcess,
		       ReadOperationControl, ReadOperationCurrent,
		       SetOperationAllowed, SetTelnetAllowed, Wait ],
Process		USING[ Detach, SecondsToTicks ],
ProtocolDefs	USING[ AppendTimestamp ],
RestartDefs	USING[],
String		USING[ AppendChar, AppendString, EquivalentString,
		       InvalidNumber, StringToDecimal ],
Time		USING[ Append, Current, Packed, Unpack ],
VMDefs		USING[ CantOpen, CloseFile, Error, FileHandle,
		       GetFileLength, MarkStartWait, OpenFile, Page, pageSize, Position,
		       ReadPage, Release, SetFileLength, UsePage ];

Enquiry: MONITOR
IMPORTS EnquiryDefs, FrameDefs, GlassDefs, ImageDefs, LogDefs,
        LogWatchDefs, MaintainDefs, NameInfoDefs, PolicyDefs, Process,
        ProtocolDefs, String, Time, VMDefs
EXPORTS RestartDefs--PROGRAM-- =

BEGIN

Command: TYPE = { addRegistry, histograms, inboxes, servers, displayPolicy,
                  queues, statistics, time, enable, archive, background,
                  msMail, purge, rsMail, login, maintain, observeLog, quit,
                  restart, archiveDays, setPolicy, wait };

Del: ERROR = CODE;

LoginState: TYPE = { none, ok, enabled };

Login: PROC[str: GlassDefs.Handle, user, pwd: STRING]
    RETURNS[ok: BOOLEAN] =
   BEGIN
   OPEN str;
   default: STRING = ".pa"L;
   WriteChar[Ascii.CR];
   IF ReadString["Your name please: "L, user, word] = Ascii.DEL
   THEN ERROR Del[];
   IF String.EquivalentString[user, "ogin"L] -- ugh! --
   THEN BEGIN
        WriteString["← "L]; user.length ← 0;
        IF ReadString[""L, user, word] = Ascii.DEL THEN ERROR Del[];
        END;
   FOR i: CARDINAL DECREASING IN [0..user.length)
   DO IF user[i] = '. THEN EXIT;
   REPEAT
   FINISHED =>
      IF user.length + default.length <= user.maxlength
      THEN { String.AppendString[user, default]; WriteString[default] };
   ENDLOOP;
   WriteChar[Ascii.CR];
   SELECT ReadString["Your password: "L, pwd, pwd] FROM 
     Ascii.DEL => ERROR Del[];
     Ascii.SP =>
       BEGIN
       acc: STRING = [8];
       [] ← ReadString[" (account): "L, acc, word];
       END;
   ENDCASE => NULL;
   WriteString[" ... "L]; SendNow[]; ok ← FALSE;
   SELECT NameInfoDefs.Authenticate[user, pwd] FROM
     individual => { WriteString["ok"L]; ok ← TRUE };
     group =>      WriteString["Can't login as a group"L];
     notFound =>   WriteString["Not a valid user name"L];
     badPwd =>     WriteString["Incorrect password"L];
     allDown =>    WriteString["Can't contact authentication server"L];
   ENDCASE => ERROR;
   IF ok THEN LogAction[user, "Vc login"L];
   END;

Enable: PROC[str: GlassDefs.Handle, user: STRING]
       RETURNS[ privileged: BOOLEAN ] =
   BEGIN
   OPEN str;
   privileged ← FALSE;
   SELECT (IF String.EquivalentString[user, "Wizard.GV"L--side door!--]
           THEN NameInfoDefs.Membership[yes]
           ELSE NameInfoDefs.IsMemberClosure["Transport↑.ms"L, user]) FROM
     yes => { WriteString["ok"L]; privileged ← TRUE };
     allDown => WriteString["can't contact access control server"L];
     no, notGroup => WriteString["not privileged"L];
   ENDCASE;
   IF privileged THEN LogAction[user, "Vc enabled"L];
   END;

Confirm: PROC[str: GlassDefs.Handle] RETURNS[ ok: BOOLEAN ] =
   BEGIN
   OPEN str;
   WriteString[" [Confirm] "L];
   DO SELECT ReadChar[] FROM
        'y, 'Y, Ascii.CR => { ok ← TRUE; EXIT };
        'n, 'N => { ok ← FALSE; EXIT };
        Ascii.DEL => ERROR Del[];
      ENDCASE => WriteChar['?];
   ENDLOOP;
   WriteString[IF ok THEN "yes ... "L ELSE "no"L]; SendNow[];
   END;

DoAddRegistry: PROC[str: GlassDefs.Handle] =
   BEGIN
   OPEN str;
   name: BodyDefs.RName = [BodyDefs.maxRNameLength];
   ok: BOOLEAN ← TRUE;
   c, m, b: BOOLEAN;
   IF ReadString[" registry R-Name: "L, name, word] = Ascii.DEL
   THEN ERROR Del[];
   IF NOT Confirm[str] THEN RETURN;
   WriteString["turning off policy controls ... "L];
   c ← PolicyDefs.ReadOperationControl[connection].allowed;
   m ← PolicyDefs.ReadOperationControl[mainLine].allowed;
   b ← PolicyDefs.ReadOperationControl[background].allowed;
   PolicyDefs.SetOperationAllowed[connection, FALSE];
   PolicyDefs.SetOperationAllowed[mainLine, FALSE];
   PolicyDefs.SetOperationAllowed[background, FALSE];
   WriteString["wait until idle ... "L];
   WaitUntilIdle[str ! Del => { WriteString["(not idle)  "L]; CONTINUE }];
   WriteString["adding entry ... "L];
   ok ← EnquiryDefs.AddSelfToRegistry[name];
   IF ok
   THEN BEGIN
        WriteString[
"now use Maintain (not on this machine) to add to registry."L];
        DO WriteString["Confirm when update has propagated "L];
           IF Confirm[str] THEN EXIT;
        ENDLOOP;
        WriteString["fetching registry ... "L];
        ok ← EnquiryDefs.AddRegistry[name];
        END;
   IF ok
   THEN BEGIN
        WriteString["ok, restoring policy controls ... "L];
        PolicyDefs.SetOperationAllowed[connection, c];
        PolicyDefs.SetOperationAllowed[mainLine, m];
        PolicyDefs.SetOperationAllowed[background, b];
        END
   ELSE WriteString["failed (controls still off)"L];
   END;

ControlOperation: PROC[str: GlassDefs.Handle] =
   BEGIN
   OPEN str;
   opNames: ARRAY PolicyDefs.Operation OF STRING = [
      work:		"work"L,
       connection:	"connection"L,
        clientInput:	"clientInput"L,
        serverInput:	"serverInput"L,
        readMail:	"readMail"L,
        regExpand:	"regExpand"L,
        lily:		"lily"L,
        MTP:		"MTP"L,
        FTP:		"FTP"L,
       telnet:		"telnet"L,
       mainLine:	"mainLine"L,
        readExpress:	"readExpress",
        readInput:	"readInput"L,
        readPending:	"readPending"L,
        readForward:	"readForward"L,
        readMailbox:	"readMailbox"L,
        remailing:	"remailing"L,
       background:	"background"L,
        RSReadMail:	"RSReadMail"L,
        MSReadMail:	"MSReadMail"L,
        archiver:	"archiver"L,
        regPurger:	"regPurger"L ];
   opName: STRING = [20];
   IF ReadString[" operation: "L, opName, word] = Ascii.DEL
   THEN ERROR Del[];
   IF String.EquivalentString[opName, "?"L]
   THEN BEGIN
        WriteString[" one of:"L];
        FOR op: PolicyDefs.Operation IN PolicyDefs.Operation
        DO WriteString["  "L]; WriteString[opNames[op]]; ENDLOOP;
        END
   ELSE FOR op: PolicyDefs.Operation IN PolicyDefs.Operation
        DO IF String.EquivalentString[opName, opNames[op]]
           THEN BEGIN
                allowed: BOOLEAN =
                   NOT PolicyDefs.ReadOperationControl[op].allowed;
                WriteString[IF allowed
                            THEN " ← allowed"L
                            ELSE " ← not allowed"L];
                IF Confirm[str]
                THEN BEGIN
                     PolicyDefs.SetOperationAllowed[op, allowed];
                     WriteString["ok"L];
                     END;
                EXIT
                END;
        REPEAT
        FINISHED =>
           WriteString[" ... unknown operation (type ? for help)"L];
        ENDLOOP;
   END;

ForceActivation: PROC[str: GlassDefs.Handle] =
   BEGIN
   OPEN str;
   processes: ARRAY PolicyDefs.PeriodicProcess OF STRING = [
      "ReadPending"L,
      "ProdServers"L,
      "Archiver"L,
      "RegPurger"L ];
   name: STRING = [20];
   IF ReadString[" process: "L, name, word] = Ascii.DEL
   THEN ERROR Del[];
   IF String.EquivalentString[name, "?"L]
   THEN BEGIN
        WriteString[" one of:"L];
        FOR pr: PolicyDefs.PeriodicProcess IN PolicyDefs.PeriodicProcess
        DO WriteString["  "L]; WriteString[processes[pr]]; ENDLOOP;
        END
   ELSE FOR pr: PolicyDefs.PeriodicProcess IN PolicyDefs.PeriodicProcess
        DO IF String.EquivalentString[name, processes[pr]]
           THEN BEGIN
                IF Confirm[str]
                THEN BEGIN
                     PolicyDefs.Activate[pr];
                     WriteString["ok"L];
                     END;
                EXIT
                END;
        REPEAT
        FINISHED =>
           WriteString[" ... unknown process (type ? for help)"L];
        ENDLOOP;
   END;

ImmediatePurge: PROC[str: GlassDefs.Handle] =
   BEGIN
   OPEN str;
   name: BodyDefs.RName = [BodyDefs.maxRNameLength];
   IF ReadString[" of R-Name: "L, name, word] = Ascii.DEL
   THEN ERROR Del[];
   IF Confirm[str]
   THEN BEGIN
        done: BOOLEAN = EnquiryDefs.ImmediatePurge[name];
        WriteString[IF done THEN "ok"L ELSE "no change"];
        END;
   END;

WriteCommandLine: PROC[str: GlassDefs.Handle, user: STRING]
               RETURNS[ ok: BOOLEAN ] =
   BEGIN
   OPEN str;
   handle: VMDefs.FileHandle = VMDefs.OpenFile[options: oldOrNew,
                                               name: "Rem.cm"L !
      VMDefs.Error, VMDefs.CantOpen => GOTO noOpen];
   pos: VMDefs.Position ← VMDefs.GetFileLength[handle];
   length: CARDINAL = pos.page*2*VMDefs.pageSize + pos.byte;
   page: VMDefs.Page = IF pos = [0,0]
                       THEN VMDefs.UsePage[ [handle,0] ]
                       ELSE VMDefs.ReadPage[ [handle,0], 0];
   pageChars: POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[page];
   BEGIN
      ENABLE UNWIND =>
        { VMDefs.Release[page]; VMDefs.CloseFile[handle] };
      buffer: STRING = [64];
      FOR i: CARDINAL IN [0..MIN[buffer.maxlength,length])
      DO buffer[i] ← pageChars[i]; buffer.length ← i; ENDLOOP;
      IF buffer.length > 0 AND buffer[buffer.length-1] = Ascii.CR
      THEN buffer.length ← buffer.length-1;
      IF buffer.length = 0
      THEN String.AppendString[buffer, "@Server.cm@"L];
      WriteChar[Ascii.CR];
      IF ReadString["New command line: "L, buffer, line] = Ascii.DEL
      THEN ERROR Del[];
      FOR i: CARDINAL IN [0..buffer.length)
      DO pageChars[i] ← buffer[i] ENDLOOP;
      pageChars[buffer.length] ← Ascii.CR;
      VMDefs.MarkStartWait[page];
      VMDefs.SetFileLength[handle, [page:0, byte:buffer.length+1] ];
      LogAction[user, "New Rem.cm"L, buffer];
   END;
   VMDefs.Release[page];
   VMDefs.CloseFile[handle];
   ok ← TRUE;
   EXITS noOpen => { WriteString["Can't open Rem.cm"L]; ok ← FALSE };
   END;

Stop: ENTRY PROC[str: GlassDefs.Handle, user: STRING]
         RETURNS[ stopping: BOOLEAN ] =
   BEGIN
   OPEN str;
   ENABLE UNWIND => NULL;
   reason: STRING = [64];
   stopping ← FALSE;
   WriteChar[Ascii.CR];
   IF ReadString["Restart reason: "L, reason, line] = Ascii.DEL
   THEN ERROR Del[];
   IF NOT WriteCommandLine[str, user] THEN RETURN;
   WriteChar[Ascii.CR];
   WriteString["Do you really want me to stop the server?"L];
   IF Confirm[str]
   THEN BEGIN
        p: PROCESS;
        LogAction[user, "Restart"L, reason];
        p ← FORK ReallyStop[];
        stopping ← TRUE;
        END;
   END;

ReallyStop: PROC =
   BEGIN
   PolicyDefs.SetOperationAllowed[work, FALSE];
   PolicyDefs.Wait[secs:5]; ImageDefs.StopMesa[];
   END;

Archive: PROC[str: GlassDefs.Handle, user: STRING] =
   BEGIN
   OPEN str;
   target: BodyDefs.RName = [BodyDefs.maxRNameLength];
   IF ReadString["mailbox: "L, target, word] = Ascii.DEL
   THEN ERROR Del[];
   IF Confirm[str]
   THEN BEGIN
        IF NOT EnquiryDefs.Poll[target] THEN GOTO none;
        EnquiryDefs.Archive[target, 0 ! EnquiryDefs.InaccessibleArchive =>
                            GOTO failed];
        LogAction[user, "Mailbox archived"L];
        WriteString["ok"L];
        EXITS
           failed => WriteString["failed"L];
           none => WriteString["no mail"L];
        END;
   END;

ArchiveDays: PROC[str: GlassDefs.Handle] =
   BEGIN
   OPEN str;
   days: STRING = [2];
   count: INTEGER;
   IF ReadString["to be: "L, days, word] = Ascii.DEL
   THEN ERROR Del[];
   count ← String.StringToDecimal[days ! String.InvalidNumber => GOTO bad];
   IF count < 0
   THEN WriteString[" Silly!"L]
   ELSE EnquiryDefs.SetArchiveDays[count];
   EXITS bad => WriteString[" invalid number"L];
   END;

DisplayTime: PROC[str: GlassDefs.Handle] =
   BEGIN
   OPEN str;
   now: Time.Packed = Time.Current[];
   nowText: STRING = [22]; -- 23-Jun-63 12:12:12 PDT or 0#0@1234512345 --
   Time.Append[nowText, Time.Unpack[now], TRUE];
   WriteString[nowText];
   WriteString[" = "];
   nowText.length ← 0; ProtocolDefs.AppendTimestamp[nowText,[0,0,now]];
   WriteString[nowText];
   END;

WaitUntilIdle: ENTRY PROC[str: GlassDefs.Handle] =
   BEGIN
   OPEN str;
   ENABLE UNWIND => NULL;
   cond: CONDITION ← [timeout: Process.SecondsToTicks[1]];
   DO IF DelTyped[] THEN ERROR Del[];
      IF PolicyDefs.ReadOperationCurrent[work] =
              PolicyDefs.ReadOperationCurrent[telnet] THEN EXIT;
      WAIT cond;
      WriteChar['!]; SendNow[];
   ENDLOOP;
   END;

ObserveLog: PROCEDURE[str: GlassDefs.Handle, user: STRING] =
   BEGIN
   OPEN str;
   p: LogWatchDefs.LogPosition;
   WriteChar[Ascii.CR]; WriteChar[Ascii.CR];
   WriteString["*** Type DEL to stop log observation. ***"L];
   WriteChar[Ascii.CR]; WriteChar[Ascii.CR];
   SendNow[];
   p ← LogWatchDefs.GetLogPosition[];
   LogAction[user, "Obs. started"L];
   [] ← LogWatchDefs.PutLogChars[p, WriteChar, SendNow, DelTyped
         ! UNWIND => LogAction[user, "Obs. abandonded"L]];
   LogAction[user, "Obs. stopped"L];
   WriteChar[Ascii.CR];
   ERROR Del[];
   END; -- ObserveLog --
   

LogAction: PROC[user, action: STRING, content: STRING ← NIL] =
   BEGIN
   log: STRING = [128];
   String.AppendString[log, action];
   String.AppendString[log, " by "L];
   String.AppendString[log, user];
   IF content#NIL THEN BEGIN
      String.AppendString[log, ": "L];
      String.AppendString[log, content];
      END;
   LogDefs.WriteLogEntry[log];
   END;

LowerCase: PROC[c: CHARACTER] RETURNS[CHARACTER] = INLINE
   { RETURN[ IF c IN ['A..'Z] THEN 'a + (c-'A) ELSE c ] };

BeginsWith: PROC[a, b: STRING] RETURNS[ BOOLEAN ] =
   BEGIN
   FOR i: CARDINAL IN [0..b.length)
   DO IF i >= a.length OR LowerCase[a[i]] # LowerCase[b[i]]
      THEN RETURN[FALSE];
   ENDLOOP;
   RETURN[TRUE]
   END;

ChooseCommand: PROC[str: GlassDefs.Handle, state: LoginState]
            RETURNS[Command] =
   BEGIN
   OPEN str;
   names: ARRAY Command OF STRING = [
     addRegistry:   "Add-registry"L,
     histograms:    "Display Histograms"L,
     inboxes:       "Display Inboxes"L,
     servers:       "Display Other-servers"L,
     displayPolicy: "Display Policy-controls"L,
     queues:        "Display Queues"L,
     statistics:    "Display Statistics"L,
     time:          "Display Time"L,
     enable:        "Enable"L,
     archive:       "Force Archive"L,
     background:    "Force Background-process"L,
     msMail:        "Force MSMail-login"L,
     purge:         "Force Purge"L,
     rsMail:        "Force RSMail-login"L,
     login:         "Login"L,
     maintain:      "Maintain"L,
     observeLog:    "Observe-log"L,
     quit:          "Quit"L,
     restart:       "Restart"L,
     archiveDays:   "Set Archive-days"L, 
     setPolicy:     "Set Policy-control"L,
     wait:          "Wait-until-idle"L ];
   allowed: PACKED ARRAY Command OF { yes, no } = SELECT state FROM
     none => [
       addRegistry:   no,
       histograms:    yes,
       inboxes:       no,
       servers:       yes,
       displayPolicy: yes,
       queues:        no,
       statistics:    yes,
       time:          yes,
       enable:        no,
       archive:       no,
       background:    no,
       msMail:        no,
       purge:         no,
       rsMail:        no,
       login:         yes,
       maintain:      no,
       observeLog:    no,
       quit:          yes,
       restart:       no,
       archiveDays:   no,
       setPolicy:     no,
       wait:          no ],
     ok => [
       addRegistry:   no,
       histograms:    yes,
       inboxes:       yes,
       servers:       yes,
       displayPolicy: yes,
       queues:        yes,
       statistics:    yes,
       time:          yes,
       enable:        yes,
       archive:       no,
       background:    no,
       msMail:        no,
       purge:         no,
       rsMail:        no,
       login:         yes,
       maintain:      no,
       observeLog:    no,
       quit:          yes,
       restart:       no,
       archiveDays:   no,
       setPolicy:     no,
       wait:          no ],
     enabled => [
       addRegistry:   yes,
       histograms:    yes,
       inboxes:       yes,
       servers:       yes,
       displayPolicy: yes,
       queues:        yes,
       statistics:    yes,
       time:          yes,
       enable:        no,
       archive:       yes,
       background:    yes,
       msMail:        yes,
       purge:         yes,
       rsMail:        yes,
       login:         yes,
       maintain:      yes,
       observeLog:    yes,
       quit:          yes,
       restart:       yes,
       archiveDays:   yes,
       setPolicy:     yes,
       wait:          yes ],
     ENDCASE => ERROR;
   buff: STRING = [64];
   prompt: STRING = IF state = enabled THEN "V! "L ELSE "V: "L;
   WriteString[prompt];
   DO c: CHARACTER = LowerCase[ReadChar[]];
      FOR i: Command IN Command
      DO IF allowed[i] = yes
         AND BeginsWith[names[i], buff]
         AND c = LowerCase[names[i][buff.length]]
         THEN BEGIN
              FOR j: CARDINAL IN [buff.length..names[i].length)
              DO nChar: CHARACTER = names[i][j];
                 String.AppendChar[buff, nChar]; WriteChar[nChar];
                 IF nChar = Ascii.SP THEN EXIT;
              REPEAT FINISHED => RETURN[i]
              ENDLOOP;
              EXIT
              END
      REPEAT FINISHED =>
         BEGIN
         SELECT c FROM
           '? =>
              BEGIN
              first: BOOLEAN ← TRUE;
              WriteString["? One of: "L];
              FOR i: Command IN Command
              DO IF allowed[i] = yes
                 AND BeginsWith[names[i], buff]
                 THEN BEGIN
                      IF first THEN first←FALSE ELSE WriteString[", "L];
                      FOR j: CARDINAL IN [buff.length..names[i].length)
                      DO WriteChar[names[i][j]] ENDLOOP;
                      END;
              ENDLOOP;
              END;
           Ascii.DEL => ERROR Del[];
         ENDCASE => { WriteChar[c]; WriteChar['?] };
         WriteChar[Ascii.CR]; WriteString[prompt]; WriteString[buff];
         END;
      ENDLOOP;
   ENDLOOP;
   END;

Receive: PROC[str: GlassDefs.Handle] =
   BEGIN
   OPEN str;
   state: LoginState ← none;
   user: BodyDefs.RName = [BodyDefs.maxRNameLength];
   pwd: STRING = [16];
   stopping: BOOLEAN ← FALSE;
   Cleanup: PROC =
      BEGIN
      IF NOT stopping THEN PolicyDefs.SetTelnetAllowed[];
      END;
   CheckImpl: PROC[proc: UNSPECIFIED] RETURNS[ ok: BOOLEAN ] =
      BEGIN
      ok ← FrameDefs.IsBound[proc];
      IF NOT ok
      THEN WriteString["command not implemented on this server"L];
      END;
   WriteChar[Ascii.CR];
   WriteString["Grapevine Server Viticulturists' Entrance"L];
   WriteChar[Ascii.CR];
   DO ENABLE UNWIND => Cleanup[];
      WriteChar[Ascii.CR];
      BEGIN
      ENABLE Del => GOTO del;
      comm: Command ← ChooseCommand[str, state !
                                    GlassDefs.TimeOut => GOTO timeOut];
      WriteString[" ... "L]; SendNow[];
      SELECT comm FROM
        addRegistry => IF CheckImpl[EnquiryDefs.AddRegistry]
                  THEN DoAddRegistry[str];
      -- "Display" sub-commands --
        histograms =>   IF CheckImpl[EnquiryDefs.Histograms]
                  THEN EnquiryDefs.Histograms[str];
        inboxes =>    IF CheckImpl[EnquiryDefs.MailboxCount]
                  THEN EnquiryDefs.MailboxCount[str];
        servers => IF CheckImpl[EnquiryDefs.RemoteServers]
                  THEN EnquiryDefs.RemoteServers[str];
        displayPolicy => IF CheckImpl[EnquiryDefs.PolicyControls]
                  THEN EnquiryDefs.PolicyControls[str];
        queues =>    IF CheckImpl[EnquiryDefs.SLQueueCount]
                  THEN EnquiryDefs.SLQueueCount[str];
        statistics =>IF CheckImpl[EnquiryDefs.DisplayStats]
                  THEN EnquiryDefs.DisplayStats[str];
        time =>   DisplayTime[str];
      -- misc commands --
        enable => IF Confirm[str]
                  THEN { IF Enable[str, user] THEN state ← enabled };
      -- "Force" sub-commands --
        archive =>IF CheckImpl[EnquiryDefs.Archive]
                  THEN Archive[str, user];
        background =>  ForceActivation[str];
        msMail => IF CheckImpl[EnquiryDefs.LoginMSMail]
                  THEN { EnquiryDefs.LoginMSMail[]; WriteString["ok"L] };
        purge =>  IF CheckImpl[EnquiryDefs.ImmediatePurge]
                  THEN ImmediatePurge[str];
        rsMail => IF CheckImpl[EnquiryDefs.LoginRSMail]
                  THEN { EnquiryDefs.LoginRSMail[]; WriteString["ok"L] };
        login =>  { state ← none; -- because Login changes "user"
                    IF Login[str, user, pwd] THEN state ← ok };
      -- misc commands --
        maintain =>IF CheckImpl[MaintainDefs.DoIt] AND Confirm[str]
                  THEN MaintainDefs.DoIt[str, user, pwd];
        observeLog => IF Confirm[str]
                  THEN ObserveLog[str, user];
        quit =>   IF Confirm[str]
                  THEN EXIT;
        restart =>IF Confirm[str]
                  AND Stop[str, user]
                  THEN EXIT;
      -- "Set" sub-commands --
        archiveDays =>IF CheckImpl[EnquiryDefs.SetArchiveDays]
                  THEN ArchiveDays[str];
        setPolicy =>ControlOperation[str];
      -- misc commands --
        wait =>   WaitUntilIdle[str];
      ENDCASE =>  WriteString["Unimplemented command"L];
      EXITS
        timeOut =>
          BEGIN
          WriteString["Type any character to continue ... "L];
          [] ← ReadChar[ ! GlassDefs.TimeOut => GOTO end ];
          EXITS end =>
            { WriteString["good-bye"L]; EXIT }
          END;
        del =>
          { Flush[]; WriteString[" XXX"L] };
      END;
   ENDLOOP;
   Cleanup[];
   END;

Work: PROC =
   { DO GlassDefs.Listen[Receive] ENDLOOP };

Process.Detach[FORK Work[]];

END.