-- Transport mechanism: Viticulturists' interface -- [Indigo]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 AppendString[log, ": "L]; 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.