<> <> <> <> <> DIRECTORY Ascii USING [Lower], Basics USING [Comparison], BasicTime, BTree, BTreeVM, Commander USING [CommandProc, Handle, Register], CommandTool USING [ArgumentVector, Failed, Parse], FS, FSBackdoor, GVBasics USING [MakeKey, Password, Timestamp], GVNames, GVSend, IO, Menus, PrincOpsUtils, Rope, TEditOps, UserCredentials, ViewerClasses, ViewerEvents, ViewerOps; PasswordAdmin: CEDAR MONITOR IMPORTS Ascii, BasicTime, BTree, BTreeVM, Commander, CommandTool, FS, FSBackdoor, GVBasics, GVNames, GVSend, IO, Menus, PrincOpsUtils, Rope, TEditOps, UserCredentials, ViewerEvents, ViewerOps SHARES Rope = BEGIN ROPE: TYPE = Rope.ROPE; <> RememberPasswordsCmd: Commander.CommandProc = BEGIN argv: CommandTool.ArgumentVector = CommandTool.Parse[cmd ! CommandTool.Failed => {cmd.out.PutRope[errorMsg]; GOTO stop}]; append: BOOLEAN _ FALSE; fileName: ROPE _ NIL; patterns: LIST OF ROPE _ NIL; FOR arg: NAT IN [1..argv.argc) DO SELECT TRUE FROM argv[arg].Length=0 => {cmd.out.PutRope["Syntax error"]; GOTO stop}; argv[arg].Fetch[0]='- => IF argv[arg].Length=2 AND Ascii.Lower[argv[arg].Fetch[1]]='a THEN append _ TRUE ELSE {cmd.out.PutRope["Illegal switch"]; GOTO stop}; fileName=NIL => fileName _ argv[arg]; ENDCASE => IF patterns=NIL THEN patterns _ LIST[argv[arg]] ELSE FOR item: LIST OF ROPE _ patterns, item.rest DO IF item.rest=NIL THEN {item.rest _ LIST[argv[arg]]; EXIT}; ENDLOOP; ENDLOOP; IF fileName=NIL OR patterns=NIL THEN {cmd.out.PutRope["Syntax error"]; GOTO stop}; IF ~GetWizardPassword[cmd] THEN GOTO stop; OpenBTree[fileName: fileName, create: NOT append ! FS.Error => {cmd.out.PutRope[error.explanation]; GOTO stop}]; FOR item: LIST OF ROPE _ patterns, item.rest UNTIL item=NIL DO cmd.out.PutF["Remember[\"%g\"]\n", [rope[item.first]]]; Remember[pattern: item.first, out: cmd.out]; ENDLOOP; CloseBTree[]; EXITS stop => cmd.out.PutChar['\n]; END; CheckPasswordsCmd: Commander.CommandProc = BEGIN argv: CommandTool.ArgumentVector = CommandTool.Parse[cmd ! CommandTool.Failed => {cmd.out.PutRope[errorMsg]; GOTO stop}]; append: BOOLEAN _ FALSE; fileName: ROPE _ NIL; patterns: LIST OF ROPE _ NIL; FOR arg: NAT IN [1..argv.argc) DO SELECT TRUE FROM argv[arg].Length=0 => {cmd.out.PutRope["Syntax error"]; GOTO stop}; argv[arg].Fetch[0]='- => IF argv[arg].Length=2 THEN {cmd.out.PutRope["Illegal switch"]; GOTO stop}; fileName=NIL => fileName _ argv[arg]; ENDCASE => IF patterns=NIL THEN patterns _ LIST[argv[arg]] ELSE FOR item: LIST OF ROPE _ patterns, item.rest DO IF item.rest=NIL THEN {item.rest _ LIST[argv[arg]]; EXIT}; ENDLOOP; ENDLOOP; IF fileName=NIL OR patterns=NIL THEN {cmd.out.PutRope["Syntax error"]; GOTO stop}; IF ~GetWizardPassword[cmd] THEN GOTO stop; OpenBTree[fileName: fileName, lock: $read ! FS.Error => {cmd.out.PutRope[error.explanation]; GOTO stop}]; FOR item: LIST OF ROPE _ patterns, item.rest UNTIL item=NIL DO invalid, unchanged, tooShort: LIST OF ROPE; cmd.out.PutF["Check[\"%g\"]\n", [rope[item.first]]]; [invalid: invalid, unchanged:unchanged, tooShort: tooShort] _ Check[pattern: item.first, out: cmd.out]; invalidNames _ ConcRopeLists[invalidNames, invalid]; unchangedNames _ ConcRopeLists[unchangedNames, unchanged]; tooShortNames _ ConcRopeLists[tooShortNames, tooShort]; ENDLOOP; CloseBTree[]; EXITS stop => cmd.out.PutChar['\n]; END; invalidNames, unchangedNames, tooShortNames: LIST OF ROPE _ NIL; GetWizardPassword: PROC [cmd: Commander.Handle] RETURNS [ok: BOOLEAN] = BEGIN IF wizardPassword=NIL THEN BEGIN password: ROPE; authenticateInfo: GVNames.AuthenticateInfo; cmd.out.PutRope["Enter wizard's password: "]; <> [token: password] _ cmd.in.GetTokenRope[]; <> cmd.out.PutRope[" ... "]; authenticateInfo _ GVNames.Authenticate[wizardName, password]; IF authenticateInfo#individual THEN { cmd.out.PutRope[Code[authenticateInfo]]; RETURN [FALSE]}; cmd.out.PutRope["ok\n"]; wizardPassword _ password; END; RETURN [TRUE]; END; wizardName: ROPE _ "Wizard.GV"; wizardPassword: ROPE _ NIL; <
> Remember: PROC [pattern: Rope.ROPE, out: IO.STREAM] = <> BEGIN RememberWork: EntryEnumProc --[info: REF GVNames.GetEntryInfo] RETURNS [exit: BOOL _ FALSE]-- = BEGIN WITH info^ SELECT FROM i: GVNames.GetEntryInfo.individual => Insert[name: i.name, password: i.password, stamp: i.passwordStamp]; ENDCASE => out.PutF["Name \"%g\" is not an individual.\n", [rope[info.name]]]; END; IF tree=NIL OR tree.GetState[].state#open THEN {out.PutRope["Tree not open yet; call OpenBTree first.\n"]; RETURN}; EnumAllEntries[pattern: pattern, which: individuals, work: RememberWork, out: out]; END; Check: PROC [pattern: Rope.ROPE, out: IO.STREAM] RETURNS [invalid, unchanged, tooShort: LIST OF ROPE] = <> BEGIN CheckWork: EntryEnumProc --[info: REF GVNames.GetEntryInfo] RETURNS [exit: BOOL _ FALSE]-- = BEGIN WITH info^ SELECT FROM i: GVNames.GetEntryInfo.individual => BEGIN found, changed: BOOLEAN; password: GVBasics.Password; stamp: GVBasics.Timestamp; [found: found, password: password, stamp: stamp] _ Find[i.name]; IF ~found THEN { out.PutF["Name \"%g\" did not previously exist.\n", [rope[info.name]]]; RETURN}; changed _ password#i.password; SELECT KosherPassword[i.password] FROM $ok => IF ~changed THEN unchanged _ CONS[info.name, unchanged]; $invalid => invalid _ CONS[info.name, invalid]; $tooShort => IF changed THEN tooShort _ CONS[info.name, tooShort] ELSE unchanged _ CONS[info.name, unchanged]; ENDCASE; END; ENDCASE => out.PutF["Name \"%g\" is not an individual.\n", [rope[info.name]]]; END; invalid _ unchanged _ tooShort _ NIL; IF tree=NIL OR tree.GetState[].state#open THEN {out.PutRope["Tree not open yet; call OpenBTree first.\n"]; RETURN}; EnumAllEntries[pattern: pattern, which: individuals, work: CheckWork, out: out]; END; KosherPassword: PROC [password: GVBasics.Password] RETURNS [status: {ok, invalid, tooShort}] = BEGIN p: PasswordRep = LOOPHOLE[password]; FOR i: CARDINAL DECREASING IN [0..7] DO IF p[i].char#0 THEN RETURN [IF i<5 THEN $tooShort ELSE $ok]; REPEAT FINISHED => RETURN [$invalid]; ENDLOOP; END; RopeFromPassword: PROC [password: GVBasics.Password] RETURNS [ROPE] = BEGIN p: PasswordRep = LOOPHOLE[password]; FOR i: CARDINAL DECREASING IN [0..7] DO IF p[i].char#0 THEN BEGIN s: IO.STREAM _ IO.ROS[]; FOR j: CARDINAL IN [0..i] DO s.PutChar[LOOPHOLE[p[j].char]]; ENDLOOP; RETURN [IO.RopeFromROS[s]]; END; REPEAT FINISHED => RETURN [NIL]; ENDLOOP; END; PasswordRep: TYPE = PACKED ARRAY [0..7] OF RECORD [char: [0..177B], parity: BOOL]; ReadNamesFromTree: PROC RETURNS [names: LIST OF ROPE] = BEGIN Proc: BTreeEnumProc --[name: ROPE, password: GVBasics.Password, stamp: GVBasics.Timestamp] RETURNS [continue: BOOLEAN _ TRUE]-- = BEGIN IF KosherPassword[password]#invalid THEN names _ CONS[name, names]; END; names _ NIL; [] _ BTreeEnumerate[proc: Proc]; END; ConcRopeLists: PROC [a, b: LIST OF ROPE] RETURNS [LIST OF ROPE] = BEGIN IF a=NIL THEN RETURN[b]; IF b#NIL THEN FOR pred: LIST OF ROPE _ a, pred.rest DO IF pred.rest=NIL THEN {pred.rest _ b; EXIT}; ENDLOOP; RETURN[a]; END; <> BreakName: PROC [name: Rope.ROPE] RETURNS [sn, reg: Rope.ROPE] = BEGIN FOR i: INT DECREASING IN [0..name.Length[]) DO IF name.Fetch[i] = '. THEN RETURN[ sn: name.Substr[start: 0, len: i], reg: name.Substr[start: i+1, len: name.Length[]-(i+1)] ]; ENDLOOP; RETURN[ sn: NIL, reg: name ] END; Code: PROC [type: GVNames.Outcome] RETURNS [Rope.ROPE] = { RETURN[ SELECT type FROM noChange => "no change", group => "that's a group", individual => "that's an individual", notFound => "name not found", protocolError => "protocol error", wrongServer => "wrong server", allDown => "all suitable servers are down or inaccessible", badPwd => "incorrect password", outOfDate => "out of date", notAllowed => "you're not authorized to do that", ENDCASE => "unknown return code" ] }; NameClass: TYPE = { individuals, groups, dead }; NameEnumProc: TYPE = PROC [name: Rope.ROPE] RETURNS[exit: BOOL _ FALSE]; EnumAllNames: PROC [pattern: Rope.ROPE, which: NameClass, work: NameEnumProc, out: IO.STREAM] = BEGIN sn, reg: Rope.ROPE; [sn, reg] _ BreakName[pattern]; SELECT TRUE FROM reg.Find["*"]>=0 => BEGIN RegWork: NameEnumProc = { EnumAllNames[Rope.Cat[sn, ".", BreakName[name].sn], which, work, out]}; out.PutRope["Finding the registries . . .\n"]; EnumAllNames[Rope.Cat[reg, ".GV"], groups, RegWork, out]; END; sn.Find["*"]>=0 => TRUSTED BEGIN -- allegedly unsafe variant record assignments in here enumName: Rope.ROPE; info: GVNames.MemberInfo _ [noChange[]]; out.PutF["Enumerating names in %g . . .\n", [rope[reg]] ]; enumName _ Rope.Cat[ SELECT which FROM individuals => "Individuals.", groups => "Groups.", dead => "Dead.", ENDCASE => ERROR, reg]; info _ GVNames.GetMembers[enumName]; WITH info SELECT FROM i: GVNames.MemberInfo.group => BEGIN FOR m: GVNames.RListHandle _ i.members, m.rest UNTIL m = NIL DO IF Rope.Match[pattern: pattern, object: m.first, case: FALSE] THEN { IF work[m.first] THEN EXIT }; ENDLOOP; END; ENDCASE => out.PutF["Couldn't get members of \"%g\": %g\n", [rope[enumName]], [rope[Code[info.type]]]]; END; ENDCASE => [] _ work[pattern]; END; EntryEnumProc: TYPE = PROC [info: REF GVNames.GetEntryInfo] RETURNS [exit: BOOL _ FALSE]; EnumAllEntries: PROC [pattern: Rope.ROPE, which: NameClass, work: EntryEnumProc, out: IO.STREAM] = BEGIN EntryWork: NameEnumProc = BEGIN info: REF GVNames.GetEntryInfo; rc: GVNames.NameType; [rc, info] _ GVNames.AuthenticatedGetEntry[user: wizardName, password: pwd, name: name]; IF rc IN [group..individual] THEN exit _ work[info] ELSE out.PutF["Couldn't read entry for \"%g\": %g\n", [rope[name]], [rope[Code[rc]]]]; END; pwd: GVBasics.Password = GVBasics.MakeKey[wizardPassword]; EnumAllNames[pattern: pattern, which: which, work: EntryWork, out: out]; END; <> GetMessageText: PROC RETURNS [message: ROPE] = BEGIN initialForm: ROPE _ "Subject: \001subject\002\nTo: distribution:;\n\n\001message\002\n"; dateAndFrom: ROPE; RemoveMenuItem: PROC [name: ROPE] = BEGIN entry: Menus.MenuEntry = Menus.FindEntry[menu: viewer.menu, entryName: name]; IF entry#NIL THEN Menus.ReplaceMenuEntry[menu: viewer.menu, oldEntry: entry, newEntry: NIL]; END; AwaitComposedMessage: ENTRY PROC RETURNS [ok: BOOLEAN] = BEGIN action _ wait; WHILE action=wait DO WAIT messageComposed; ENDLOOP; RETURN [action=send]; END; IF viewer#NIL THEN {ViewerOps.DestroyViewer[viewer]; viewer _ NIL}; viewer _ ViewerOps.CreateViewer[flavor: $Text, info: [name: "PasswordAdmin composed message", iconic: FALSE]]; RemoveMenuItem["Reset"]; RemoveMenuItem["Get"]; RemoveMenuItem["GetImpl"]; RemoveMenuItem["PrevFile"]; RemoveMenuItem["Store"]; RemoveMenuItem["Save"]; Menus.AppendMenuEntry[menu: viewer.menu, entry: Menus.CreateEntry[name: "Send", proc: SendButton, fork: FALSE]]; Menus.AppendMenuEntry[menu: viewer.menu, entry: Menus.CreateEntry[name: "Abort", proc: AbortButton, fork: FALSE]]; ViewerOps.PaintViewer[viewer: viewer, hint: menu]; TEditOps.SetTextContents[viewer, initialForm]; [] _ ViewerEvents.RegisterEventProc[proc: DestroyButton, event: destroy]; IF ~AwaitComposedMessage[] THEN RETURN [NIL]; dateAndFrom _ IO.PutFR["Date: %g\nFrom: %g\n", , [rope[ArpaDate[BasicTime.Now[]]]], [rope[UserCredentials.Get[].name]]]; RETURN [Rope.Cat[dateAndFrom, TEditOps.GetTextContents[viewer]]]; END; SendMessage: PROC [recipients: LIST OF ROPE, message: ROPE, out: IO.STREAM] = BEGIN handle: GVSend.Handle = GVSend.Create[]; BEGIN ENABLE GVSend.SendFailed => { out.PutF["%g ... Retrying...\n", [rope[why]]]; RETRY}; startSendInfo: GVSend.StartSendInfo = handle.StartSend[senderPwd: UserCredentials.Get[].password, sender: UserCredentials.Get[].name, validate: FALSE]; IF startSendInfo#ok THEN {out.PutRope["StartSend failed unexpectedly\n"]; RETURN}; out.PutF["Sending recipient list... \n"]; FOR item: LIST OF ROPE _ recipients, item.rest UNTIL item=NIL DO handle.AddRecipient[item.first]; ENDLOOP; out.PutF["Sending message... "]; handle.StartText[]; handle.AddToItem[message]; handle.Send[]; out.PutF["Sent OK.\n"]; END; END; viewer: ViewerClasses.Viewer _ NIL; action: {wait, abort, send}; messageComposed: CONDITION; SendButton: ENTRY Menus.MenuProc = { action _ send; NOTIFY messageComposed}; AbortButton: ENTRY Menus.MenuProc = { action _ abort; NOTIFY messageComposed}; DestroyButton: ENTRY ViewerEvents.EventProc = { viewer _ NIL; action _ abort; NOTIFY messageComposed}; ArpaDate: PROC [gmt: BasicTime.GMT] RETURNS [text: ROPE] = BEGIN unpacked: BasicTime.Unpacked = BasicTime.Unpack[gmt]; zoneHour: CARDINAL = ABS[unpacked.zone]/60; zoneMinute: CARDINAL = ABS[unpacked.zone] MOD 60; zoneLetters: ARRAY [4..11] OF CHAR = ['A, 'E, 'C, 'M, 'P, 'Y, 'H, 'B]; dstLetters: ARRAY [0..2] OF CHAR = ['D, 'S, '?]; monthNames: ARRAY BasicTime.MonthOfYear OF ROPE = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???"]; zoneText: ROPE = SELECT TRUE FROM unpacked.zone=721 => NIL, unpacked.zone>0 AND zoneHour IN [4..11] AND zoneMinute=0 => IO.PutFR["%g%gT", [character[zoneLetters[zoneHour]]], [character[dstLetters[LOOPHOLE[unpacked.dst]]]]], ENDCASE => IO.PutFR["%g%g:%02g", [character[IF unpacked.zone<0 THEN '- ELSE '+]], [cardinal[zoneHour]], [cardinal[zoneMinute]]]; date: ROPE = IO.PutFR["%2g %g %2g ", [cardinal[unpacked.day]], [rope[monthNames[unpacked.month]]], [cardinal[unpacked.year MOD 100]]]; time: ROPE = IO.PutFR["%2g:%02g:%02g %g", [cardinal[unpacked.hour]], [cardinal[unpacked.minute]], [cardinal[unpacked.second]], [rope[zoneText]]]; RETURN[Rope.Cat[date, time]]; END; <> tree: BTree.Tree _ NIL; file: FS.OpenFile _ FS.nullOpenFile; OpenBTree: PROC [fileName: ROPE, create: BOOL _ FALSE, lock: FS.Lock _ $write] = BEGIN vmHandle: BTreeVM.Handle; IF create AND lock#$write THEN ERROR; file _ IF create THEN FS.Create[name: fileName, pages: 40] ELSE FS.Open[name: fileName, lock: lock]; vmHandle _ BTreeVM.Open[file: FSBackdoor.GetFileHandle[file], filePagesPerPage: 4, cacheSize: 25]; IF tree=NIL THEN tree _ BTree.New[repPrim: [compare: BTreeCompare, compareEntries: NIL, entrySize: BTreeEntrySize, entryFromRecord: BTreeEntryFromRecord, newRecordFromEntry: BTreeNewRecordFromEntry], storPrim: [referencePage: BTreeVM.ReferencePage, releasePage: BTreeVM.ReleasePage], minEntrySize: BTreeEntry[0].SIZE]; BTree.Open[tree: tree, storage: vmHandle, pageSize: FS.WordsForPages[4], initialize: create]; END; CloseBTree: PROC = BEGIN IF tree#NIL THEN tree.SetState[closed]; IF file#FS.nullOpenFile THEN BEGIN pages, bytes: INT; [pages: pages, bytes: bytes] _ file.GetInfo[]; IF bytes#FS.BytesForPages[pages] THEN file.SetByteCountAndCreatedTime[bytes: FS.BytesForPages[pages]]; file.Close[]; END; file _ FS.nullOpenFile; END; Find: PROC [name: ROPE] RETURNS [found: BOOLEAN, password: GVBasics.Password, stamp: GVBasics.Timestamp] = TRUSTED BEGIN textName: Rope.Text = name.Flatten[]; entry: REF BTreeEntry = NARROW[tree.ReadRecord[key: textName]]; IF (found _ entry#NIL) THEN {password _ entry.password; stamp _ entry.stamp}; END; Insert: PROC [name: ROPE, password: GVBasics.Password, stamp: GVBasics.Timestamp] = TRUSTED BEGIN textName: Rope.Text = name.Flatten[]; entry: REF BTreeEntry = NEW[BTreeEntry[textName.Length[]] _ [password: password, stamp: stamp, name: ]]; PrincOpsUtils.LongCopy[to: BASE[DESCRIPTOR[entry^]], from: BASE[DESCRIPTOR[textName^]], nwords: (textName.Length[]+1)/2]; tree.UpdateRecord[key: textName, record: entry]; END; BTreeEnumerate: PROC [startingKey: ROPE _ NIL, relation: BTree.Relation _ equal, proc: BTreeEnumProc] RETURNS [exhausted: BOOLEAN] = TRUSTED BEGIN EnumEntry: UNSAFE PROC [entry: BTree.Entry] RETURNS [continue: BOOLEAN] = BEGIN e: LONG POINTER TO BTreeEntry = NARROW[entry]; t: Rope.Text = Rope.NewText[e.length]; PrincOpsUtils.LongCopy[to: BASE[DESCRIPTOR[t.text]], from: BASE[DESCRIPTOR[e.name]], nwords: (e.length+1)/2]; continue _ proc[name: t, password: e.password, stamp: e.stamp]; END; textName: Rope.Text = startingKey.Flatten[]; exhausted _ tree.EnumerateEntries[key: textName, relation: relation, Proc: EnumEntry]; END; BTreeEnumProc: TYPE = PROC [name: ROPE, password: GVBasics.Password, stamp: GVBasics.Timestamp] RETURNS [continue: BOOLEAN _ TRUE]; BTreeCompare: BTree.Compare --[key: Key, entry: Entry] RETURNS [Comparison]-- = UNCHECKED BEGIN k: REF TEXT = LOOPHOLE[NARROW[key, Rope.Text]]; e: LONG POINTER TO BTreeEntry = LOOPHOLE[entry]; FOR i: CARDINAL IN [0 .. MIN[k.length, e.length]) DO kc: CHAR _ Ascii.Lower[k[i]]; ec: CHAR _ Ascii.Lower[e[i]]; IF kcec THEN RETURN [greater]; ENDLOOP; IF k.lengthe.length THEN RETURN [greater]; RETURN [equal]; END; BTreeEntrySize: BTree.EntrySize --[entry: Entry] RETURNS [words: EntSize]-- = UNCHECKED BEGIN RETURN [BTreeEntry[LOOPHOLE[entry, LONG POINTER TO BTreeEntry].length].SIZE]; END; BTreeEntryFromRecord: BTree.EntryFromRecord --[record: Record] RETURNS [entry: Entry]-- = UNCHECKED { RETURN [LOOPHOLE [record]] }; BTreeNewRecordFromEntry: BTree.NewRecordFromEntry --[entry: Entry] RETURNS [record: Record]-- = UNCHECKED BEGIN e: LONG POINTER TO BTreeEntry = LOOPHOLE[entry]; record _ NEW [BTreeEntry[e.length]]; PrincOpsUtils.LongCopy[to: LOOPHOLE[record], from: e, nwords: BTreeEntry[e.length].SIZE]; END; BTreeEntry: TYPE = MACHINE DEPENDENT RECORD [ password: GVBasics.Password, stamp: GVBasics.Timestamp, -- most recent password update name: PACKED SEQUENCE length: CARDINAL OF CHAR]; <> Commander.Register[key: "RememberPasswords", proc: RememberPasswordsCmd, doc: "[-a] filename pattern1 pattern2 ..."]; Commander.Register[key: "CheckPasswords", proc: CheckPasswordsCmd, doc: "filename pattern1 pattern2 ..."]; END.