-- 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.