-- File: GateFTPServer.mesa - last edit: -- WIrish 5-Feb-88 12:10:52 -- HGM 25-Jun-85 4:32:14 -- Copyright (C) 1983, 1984, 1985, 1988 by Xerox Corporation. All rights reserved. DIRECTORY Ascii USING [CR], CmFile USING [Handle, TableError], Event USING [aboutToSwap], EventTypes USING [aboutToBoot, aboutToBootPhysicalVolume], Format USING [StringProc], Heap USING [systemZone], Process USING [SecondsToTicks], Profile USING [GetDefaultRegistry], Put USING [Text], Runtime USING [GetBcdTime], STP USING [GetProperty], STPExtra USING [Action, Denied, ApprovalProc, Server], Stream USING [Delete, Handle], String USING [AppendChar, AppendString, AppendNumber, CopyToNewString, Equivalent], StringLookUp USING [noMatch, TableDesc], Supervisor USING [ AddDependency, AgentProcedure, CreateSubsystem, RemoveDependency, SubsystemHandle], Time USING [Append, AppendCurrent, Unpack], Token USING [Item, Octal], Tool USING [Create, MakeSWsProc, MakeMsgSW], ToolWindow USING [TransitionProcType], Window USING [Handle], Indirect USING [Close, GetParmFileName, NextValue, OpenSection], Password USING [Check, Encrypted, Status, ValidMemberOfGroup], PupDefs USING [AppendHostName], PupStream USING [ CreatePupByteStreamListener, DestroyPupListener, PupAddress, PupPackageDestroy, PupPackageMake, PupListener, RejectThisRequest, SecondsToTocks]; GateFTPServer: MONITOR IMPORTS CmFile, Event, Heap, Process, Profile, Put, Runtime, STP, STPExtra, Stream, String, Supervisor, Time, Token, Tool, Indirect, Password, PupDefs, PupStream = BEGIN herald: LONG STRING = [100]; myGroup: LONG STRING ← NIL; pupListener: PupStream.PupListener ← NIL; parmFileName: LONG STRING ← Indirect.GetParmFileName[]; firstPassword: LONG POINTER TO PasswordBlock ← NIL; PasswordBlock: TYPE = RECORD [ next: LONG POINTER TO PasswordBlock, user: LONG STRING, encrypted: Password.Encrypted]; maxServers: CARDINAL = 2; servers: CARDINAL ← 0; scratchFileNumber: CARDINAL ← 0; tool, msg: Window.Handle ← NIL; broom: Supervisor.SubsystemHandle = Supervisor.CreateSubsystem[Broom]; useCount: CARDINAL ← 0; pleaseStop, running: BOOLEAN ← FALSE; z: UNCOUNTED ZONE = Heap.systemZone; FTPServerOn: PUBLIC ENTRY PROCEDURE = BEGIN IF (useCount ← useCount + 1) = 1 THEN BEGIN Supervisor.AddDependency[client: broom, implementor: Event.aboutToSwap]; running ← TRUE; Starter[]; END; UpdatePicture[]; END; CreateHerald: PROCEDURE = BEGIN herald: LONG STRING = [100]; String.AppendString[herald, "FTP Server of "L]; Time.Append[herald, Time.Unpack[Runtime.GetBcdTime[]]]; END; Starter: PROCEDURE = BEGIN pleaseStop ← FALSE; Announce["Starting "L, herald]; FindParameters[]; CreateListener[]; END; FindParameters: PROCEDURE = BEGIN cmFile: CmFile.Handle; Option: TYPE = MACHINE DEPENDENT{ myGroup(0), password, noMatch(StringLookUp.noMatch)}; DefinedOption: TYPE = Option [myGroup..password]; CheckType: PROCEDURE [h: CmFile.Handle, table: StringLookUp.TableDesc] RETURNS [index: CARDINAL] = Indirect.NextValue; MyNextValue: PROCEDURE [ h: CmFile.Handle, table: LONG DESCRIPTOR FOR ARRAY DefinedOption OF LONG STRING] RETURNS [index: Option] = LOOPHOLE[CheckType]; optionTable: ARRAY DefinedOption OF LONG STRING ← [ myGroup: "My Group"L, password: "Password"L]; myGroup ← String.CopyToNewString[parmFileName, z]; FOR i: CARDINAL DECREASING IN [0..myGroup.length) DO IF myGroup[i] = '. THEN myGroup.length ← i; ENDLOOP; BEGIN builtin: ARRAY [0..8) OF WORD = [ 172427B, 11553B, 61543B, 113154B, 161500B, 47164B, 173546B, 120250B]; firstPassword ← z.NEW[PasswordBlock]; firstPassword↑ ← [NIL, String.CopyToNewString["Magic"L, z], LOOPHOLE[builtin]]; END; cmFile ← Indirect.OpenSection["FTPServer"L]; IF cmFile = NIL THEN RETURN; -- Nothing needed, so don't complain. DO option: Option; option ← MyNextValue[cmFile, DESCRIPTOR[optionTable] ! CmFile.TableError => BEGIN IF name[0] # '; THEN Message["Unrecognized parameter: ", name]; RETRY; END]; SELECT option FROM noMatch => EXIT; myGroup => BEGIN z.FREE[@myGroup]; myGroup ← Token.Item[cmFile, FALSE]; Message["My Group for FTP passwords is "L, myGroup]; END; password => BEGIN user: LONG STRING; pwd: STRING = [8*8]; e: Password.Encrypted; p: POINTER ← @e; temp: LONG POINTER TO PasswordBlock; user ← Token.Item[cmFile]; FOR i: CARDINAL IN [0..SIZE[Password.Encrypted]) DO (p + i)↑ ← Token.Octal[cmFile]; ENDLOOP; FOR i: CARDINAL IN [0..SIZE[Password.Encrypted]) DO IF i # 0 THEN String.AppendChar[pwd, ' ]; String.AppendNumber[pwd, (p + i)↑, 8]; ENDLOOP; temp ← z.NEW[PasswordBlock ← [firstPassword, user, e]]; firstPassword ← temp; Message["FTP's Password for "L, user, " is "L, pwd]; END; ENDCASE => ERROR; ENDLOOP; Indirect.Close[cmFile]; END; ForgetParameters: PROCEDURE = BEGIN UNTIL firstPassword = NIL DO temp: LONG POINTER TO PasswordBlock ← firstPassword; firstPassword ← firstPassword.next; z.FREE[@temp.user]; z.FREE[@temp]; ENDLOOP; z.FREE[@myGroup]; END; FTPServerOff: PUBLIC ENTRY PROCEDURE = BEGIN IF useCount # 0 AND (useCount ← useCount - 1) = 0 THEN BEGIN running ← FALSE; Stopper[]; Supervisor.RemoveDependency[client: broom, implementor: Event.aboutToSwap]; END; END; StopperLocked: ENTRY PROCEDURE = INLINE {Stopper[]}; Stopper: INTERNAL PROCEDURE = BEGIN pause: CONDITION ← [timeout: Process.SecondsToTicks[1]]; pleaseStop ← TRUE; UNTIL servers = 0 DO WAIT pause; ENDLOOP; DestroyListener[]; ForgetParameters[]; Announce["Killed "L, herald]; END; UpdatePicture: PROCEDURE = BEGIN END; CreateListener: PROCEDURE = BEGIN IF pupListener # NIL THEN RETURN; [] ← PupStream.PupPackageMake[]; pupListener ← PupStream.CreatePupByteStreamListener[ local: [0, 3], proc: CreateSTPServer, ticks: PupStream.SecondsToTocks[3*60], filter: CheckConnectionRequest]; END; DestroyListener: PROCEDURE = BEGIN IF pupListener # NIL THEN BEGIN PupStream.DestroyPupListener[pupListener]; PupStream.PupPackageDestroy[]; pupListener ← NIL; END; END; CheckConnectionRequest: ENTRY PROCEDURE [address: PupStream.PupAddress] = BEGIN ENABLE UNWIND => NULL; IF pleaseStop THEN ERROR PupStream.RejectThisRequest["Sorry, we are trying to go away"]; IF servers = maxServers THEN ERROR PupStream.RejectThisRequest["Sorry, we are full now"]; servers ← servers + 1; END; DecrementConnections: ENTRY PROCEDURE = BEGIN servers ← servers - 1; END; CreateSTPServer: PROCEDURE [byteStream: Stream.Handle, address: PupStream.PupAddress] = BEGIN -- this guy gets FORKed for every accepted RFC hostName: STRING = [50]; buffer: STRING = [200]; Write: Format.StringProc = BEGIN FOR i: CARDINAL IN [0..s.length) DO char: CHARACTER ← s[i]; SELECT TRUE FROM buffer.length = buffer.maxlength => BEGIN Announce[buffer, NIL]; buffer.length ← 0; String.AppendChar[buffer, char]; END; char = Ascii.CR => BEGIN Announce[buffer, NIL]; buffer.length ← 0; END; ENDCASE => String.AppendChar[buffer, char]; ENDLOOP; END; Approval: STPExtra.ApprovalProc = BEGIN user: LONG STRING = STP.GetProperty[stp, userName]; password: LONG STRING = STP.GetProperty[stp, userPassword]; IF FALSE THEN -- Barf. Property list has been flused for retrieve and delete IF user = NIL THEN ERROR STPExtra.Denied["Name missing"L]; SELECT action FROM retrieve => AnnounceFor["Retrieving "L, name, user, hostName]; store => BEGIN AnnounceFor["Storing "L, name, user, hostName]; InspectCredentials[user, password]; END; delete => BEGIN AnnounceFor["Deleting "L, name, user, hostName]; InspectCredentials[user, password]; END; rename => BEGIN AnnounceFor["Renaming "L, name, user, hostName]; InspectCredentials[user, password]; END; list => AnnounceFor["Listing "L, name, user, hostName]; ENDCASE => ERROR; END; PupDefs.AppendHostName[hostName, address]; STPExtra.Server[byteStream, hostName, Write, Approval ! ABORTED => CONTINUE]; Stream.Delete[byteStream]; DecrementConnections[]; END; InspectCredentials: PROCEDURE [user, password: LONG STRING] = BEGIN registry: STRING = [100]; CopyRegistry: PROCEDURE [s: LONG STRING] = BEGIN String.AppendString[registry, s]; END; Profile.GetDefaultRegistry[CopyRegistry]; IF user = NIL OR password = NIL THEN ERROR STPExtra.Denied["Name or Password missing"L]; -- check passwords from parameter file FOR finger: LONG POINTER TO PasswordBlock ← firstPassword, finger.next UNTIL finger = NIL DO IF String.Equivalent[finger.user, user] THEN BEGIN matched: BOOLEAN ← Password.Check[password, finger.encrypted]; IF matched THEN RETURN ELSE ERROR STPExtra.Denied["Password rejected by info from parameter file"L]; END; ENDLOOP; -- try Grapevine if that fails IF myGroup = NIL THEN ERROR STPExtra.Denied["Don't know our Grapevine group"L] ELSE BEGIN luser: LONG STRING ← MaybeAppend[user, registry]; machine: LONG STRING ← MaybeAppend[myGroup, "internet"L]; status: Password.Status; status ← Password.ValidMemberOfGroup[luser, password, machine]; z.FREE[@machine]; z.FREE[@luser]; SELECT status FROM yes => NULL; nil => ERROR STPExtra.Denied["Confusion about NIL"L]; allDown => ERROR STPExtra.Denied["All Grapevine servers appear to be down"L]; notFound => ERROR STPExtra.Denied["Grapevine doesn't like your name"L]; badPwd => ERROR STPExtra.Denied["Grapevine doesn't like your password"L]; group => ERROR STPExtra.Denied["Grapevine thinks you are a group"L]; no => ERROR STPExtra.Denied["You are not in the appropriate group"L]; notGroup => ERROR STPExtra.Denied["Grapevine doesn't recognize this machine's group"L]; error => ERROR STPExtra.Denied["Error from GrapevineUser package"L]; ENDCASE => ERROR; END; END; MaybeAppend: PROCEDURE [string, tail: LONG STRING] RETURNS [new: LONG STRING] = BEGIN IF tail = NIL THEN RETURN[String.CopyToNewString[string, z]]; FOR i: CARDINAL IN [0..string.length) DO IF string[i] = '. THEN RETURN[String.CopyToNewString[string, z]]; ENDLOOP; new ← String.CopyToNewString[string, z, 1 + tail.length]; String.AppendChar[new, '.]; String.AppendString[new, tail]; END; Announce: PROCEDURE [one, two: LONG STRING] = BEGIN OPEN String; text: STRING = [200]; Time.AppendCurrent[text]; AppendString[text, " "L]; AppendString[text, one]; AppendString[text, two]; LogString[text]; END; AnnounceFor: PROCEDURE [one, two: LONG STRING, who, where: LONG STRING] = BEGIN OPEN String; text: STRING = [200]; Time.AppendCurrent[text]; AppendString[text, " "L]; AppendString[text, one]; AppendString[text, two]; AppendString[text, " for "L]; AppendString[text, who]; AppendString[text, " on "L]; AppendString[text, where]; LogString[text]; END; Message: PROCEDURE [one, two, three, four: LONG STRING ← NIL] = BEGIN text: STRING = [100]; String.AppendString[text, one]; IF two # NIL THEN String.AppendString[text, two]; IF three # NIL THEN String.AppendString[text, three]; IF four # NIL THEN String.AppendString[text, four]; LogString[text]; END; LogString: PROCEDURE [text: LONG STRING] = BEGIN String.AppendChar[text, '.]; String.AppendChar[text, Ascii.CR]; Put.Text[NIL, text]; IF msg # NIL THEN Put.Text[msg, text]; END; MakeSWs: Tool.MakeSWsProc = BEGIN msg ← Tool.MakeMsgSW[window: window, lines: 5]; END; ClientTransition: ToolWindow.TransitionProcType = BEGIN IF new = inactive THEN msg ← NIL; END; restarting: BOOLEAN ← FALSE; Restart: ENTRY PROCEDURE = BEGIN Stopper[]; restarting ← FALSE; Starter[]; END; Broom: Supervisor.AgentProcedure = BEGIN SELECT event FROM EventTypes.aboutToBoot, EventTypes.aboutToBootPhysicalVolume => IF running THEN StopperLocked[]; ENDCASE => NULL; END; -- Initialization CreateHerald[]; tool ← Tool.Create[ name: herald, makeSWsProc: MakeSWs, clientTransition: ClientTransition]; FTPServerOn[]; END.