-- Copyright (C) 1982, 1983, 1984, 1985  by Xerox Corporation. All rights reserved. 
-- Enquiry.mesa, Transport mechanism: Viticulturists' interface

-- HGM, 15-Sep-85  8:20:24
-- Andrew Birrell  27-Oct-82 16:07:51
-- Michael Schroeder, 25-Jan-83 13:03:03
-- Hankins:	23-Aug-84 10:30:40	

DIRECTORY
  Ascii USING [CR, DEL, SP],
  BodyDefs USING [maxRNameLength, RName],
  EnquiryDefs USING [
    Archive, AddRegistry, AddSelfToRegistry, DisplayStats, DriverStats,
    Histograms, ImmediatePurge, InaccessibleArchive, LoginMSMail, LoginRSMail,
    MailboxCount, PolicyControls, Poll, PupEcho, PupRoutingTable,
    RemoteServers, SetArchiveDays, SLQueueCount],
  GlassDefs USING [Handle, Listen, TimeOut],
  Heap USING [systemZone],
  LogDefs USING [WriteLogEntry],
  LogPrivateDefs USING [AppendElapsedTime, startUpTime, uptimeHouse],
  LogWatchDefs USING [GetLogPosition, LogPosition, PutLogChars],
  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],
  TemporaryBooting USING [BootFromVolume],
  Time USING [Append, Current, Packed, Unpack],
  Volume USING [systemID];

Enquiry: MONITOR
  IMPORTS
    EnquiryDefs, GlassDefs, Heap, LogDefs, LogPrivateDefs, LogWatchDefs,
    NameInfoDefs, PolicyDefs, Process, ProtocolDefs,
    String, TemporaryBooting, Time, Volume
  EXPORTS RestartDefs --PROGRAM-- =

  BEGIN

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

  Del: ERROR = CODE;

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

  Login: PROC [str: GlassDefs.Handle, user: BodyDefs.RName, pwd: LONG 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[];
      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: BodyDefs.RName]
    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,
      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 {PolicyDefs.Activate[pr]; WriteString["ok"L]; };
          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;

  Stop: ENTRY PROC [str: GlassDefs.Handle, user: LONG 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[];
    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];
    TemporaryBooting.BootFromVolume[Volume.systemID, ];
    END;

  Archive: PROC [str: GlassDefs.Handle, user: LONG 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: LONG 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: LONG STRING, content: LONG STRING ← NIL] =
    BEGIN
    log: LONG STRING ← Heap.systemZone.NEW[StringBody[200]];
    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];
    Heap.systemZone.FREE[@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,
      inbox: "Display Inbox"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,
      observeLog: "Observe-log"L, pupEcho: "Pup Echo"L,
      pupRoute: "Pup Routing-Table"L, pupStats: "Pup Statistics"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, inbox: 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, observeLog: no, pupEcho: no, pupRoute: no,
	  pupStats: no, quit: yes, restart: no,
          archiveDays: no, setPolicy: no, wait: no],
        ok => [
          addRegistry: no, histograms: yes, inbox: 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, observeLog: no, pupEcho: yes, pupRoute: yes,
	  pupStats: yes, quit: yes, restart: no, archiveDays: no,
	  setPolicy: no, wait: no],
        enabled => [
          addRegistry: yes, histograms: yes, inbox: 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, observeLog: yes, pupEcho: yes, pupRoute: yes, pupStats: 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];
    target: STRING = [20];
    stopping: BOOLEAN ← FALSE;
    Cleanup: PROC = {IF NOT stopping THEN PolicyDefs.SetTelnetAllowed[]; };

    WriteChar[Ascii.CR];
    -- put in msg with name and uptime:
    BEGIN
    s: STRING = [20];
    LogPrivateDefs.AppendElapsedTime[
      s, Time.Current[] - LogPrivateDefs.startUpTime];
    WriteString[LogPrivateDefs.uptimeHouse.caption];
    WriteString[s];
    WriteChar[Ascii.CR];
    END;
    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 => DoAddRegistry[str];
        -- "Display" sub-commands --
        histograms => EnquiryDefs.Histograms[str];
        inbox =>
            BEGIN
            name: BodyDefs.RName = [BodyDefs.maxRNameLength];
            IF ReadString["Inbox Name (* or CR for all): "L, name, word] = Ascii.DEL
              THEN ERROR Del[];
            IF name.length = 0 THEN String.AppendString[name, "*"L];
	    SendNow[];
            EnquiryDefs.MailboxCount[str, name];
            END;
        servers => EnquiryDefs.RemoteServers[str];
        displayPolicy => EnquiryDefs.PolicyControls[str];
        queues => EnquiryDefs.SLQueueCount[str];
        statistics => EnquiryDefs.DisplayStats[str];
        time => DisplayTime[str];
        -- misc commands --
        enable =>
          IF Confirm[str] THEN {IF Enable[str, user] THEN state ← enabled};
        -- "Force" sub-commands --
        archive => Archive[str, user];
        background => ForceActivation[str];
        msMail => {EnquiryDefs.LoginMSMail[]; WriteString["ok"L]; };
        purge => ImmediatePurge[str];
        rsMail => {EnquiryDefs.LoginRSMail[]; WriteString["ok"L]; };
        login => {
          state ← none;  -- because Login changes "user"
          IF Login[str, user, pwd] THEN state ← ok};
        -- misc commands --
        observeLog => IF Confirm[str] THEN ObserveLog[str, user];
        pupEcho => {
	  IF ReadString["Target: "L, target, word] = Ascii.DEL THEN ERROR Del[];
	  EnquiryDefs.PupEcho[str, target]; };
        pupRoute => EnquiryDefs.PupRoutingTable[str];
        pupStats => EnquiryDefs.DriverStats[str];
        quit => IF Confirm[str] THEN EXIT;
        restart => IF Confirm[str] AND Stop[str, user] THEN EXIT;
        -- "Set" sub-commands --
        archiveDays => 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.

Aug: 	Klamath conv (IsBound takes a LONG)	BLH
10-Aug-84 16:24:49	change DisplayInboxes to take indiv mailbox name.  BLH