<> <> <> <> <> <<>> <<*** Remember to update the date in heraldMessage when you make changes.>> <<>> DIRECTORY Ascii USING [Digit, Letter], Atom USING [GetPropFromList, PutPropOnList], Basics USING [bytesPerWord, LongNumber, SwapHalves], BasicTime USING [GMT, MonthOfYear, Now, nullGMT, Period, ToPupTime, Unpack, Unpacked], CedarProcess USING [Abort, CheckAbort, Fork, ForkableProc, Process], Commander USING [CommandProc, Register], CommandTool USING [ArgumentVector, Failed, Parse], Convert USING [Error, IntFromRope, RopeFromInt], File USING [GetVolumeName, SystemVolume], FS USING [Close, ComponentPositions, Delete, EnumerateForInfo, EnumerateForNames, Error, ErrorGroup, ExpandName, FileInfo, GetInfo, GetName, OpenFile, OpenFileFromStream, Rename, SetByteCountAndCreatedTime, StreamOpen], FSRemoteFile USING [FTPTimeToGMT], GVBasics USING [MakeKey, Password], GVNames USING [AuthenticateKey, IsMemberClosure], Icons USING [DrawIcon, IconFileFormat, IconFlavor, iconH, IconRef, IconRep, iconW, NewIcon], Imager USING [Context, MaskBox, SetColor], ImagerBackdoor USING [invert], IO USING [Backup, Close, EndOf, EndOfStream, Error, GetBlock, GetChar, GetLength, PutBlock, PutChar, PutF, PutF1, PutRope, RIS, RopeFromROS, ROS, SetIndex, STREAM, UnsafeGetBlock], List USING [LORA, Reverse], Process USING [MsecToTicks, Pause], PupDefs USING [GetHopsToNetwork, GetHostName, GetPupContentsBytes, PupBuffer, PupSocket, PupSocketDestroy, PupSocketMake, ReturnPup, veryLongWait], PupStream USING [CloseReason, ConsumeMark, CreatePupByteStreamListener, DestroyPupListener, PupListener, SecondsToTocks, SendMark, StreamClosing, TimeOut], PupTypes USING [fillInPupAddress, ftpSoc, Pair, PupAddress, PupSocketID, PupType], Real USING [RoundLI], RefText USING [AppendChar, ObtainScratch, ReleaseScratch, TrustTextAsRope], Rope USING [Cat, Concat, Equal, Fetch, Find, Flatten, FromRefText, Length, Replace, ROPE, SkipTo, Substr], RopeFile USING [Create], RuntimeError USING [UNCAUGHT], STP USING [ValidProperties], STPOps USING [markComment, markDelete, markDirectory, markEOC, markHereIsFile, markHereIsPList, markIAmVersion, markNewDirectory, markNewStore, markNo, markRetrieve, markStore, markYes], STPReplyCode USING [ReplyCode], SymTab USING [Create, Delete, Fetch, Ref, Store], TiogaOps USING [GetSelection], TypeScript USING [Create], UserCredentials USING [Get], ViewerClasses USING [PaintProc, Viewer, ViewerClass, ViewerClassRec], ViewerIO USING [CreateViewerStreams, GetViewerFromStream], ViewerOps USING [AddProp, DestroyViewer, FetchProp, FetchViewerClass, PaintViewer]; STPServerImpl: CEDAR MONITOR IMPORTS Ascii, Atom, Basics, BasicTime, Commander, CommandTool, Convert, CedarProcess, File, FS, FSRemoteFile, Imager, ImagerBackdoor, GVBasics, GVNames, Icons, IO, List, Process, PupDefs, PupStream, Real, RefText, Rope, RopeFile, RuntimeError, SymTab, TiogaOps, TypeScript, UserCredentials, ViewerIO, ViewerOps ~ BEGIN heraldMessage: ROPE _ "Cedar STP Server of August 22, 1985 3:31:09 pm PDT"; GMT: TYPE ~ BasicTime.GMT; LongNumber: TYPE ~ Basics.LongNumber; LORA: TYPE = LIST OF REF ANY; ROPE: TYPE ~ Rope.ROPE; STREAM: TYPE ~ IO.STREAM; debugging: BOOL _ FALSE; promiscuous: BOOL _ FALSE; useSingle: BOOL _ TRUE; mute: BOOL _ FALSE; UserNameAndPassword: TYPE ~ RECORD [ userName: ROPE, userPassword: GVBasics.Password ]; ValidUser: PROC [userName, password: ROPE, mark: NAT] RETURNS [valid: BOOL _ TRUE] ~ { ENABLE UNWIND => NULL; codedPassword: GVBasics.Password _ GVBasics.MakeKey[password]; authenticated: BOOL _ FALSE; myName, myPassword: ROPE; myLengthToDot, lengthToDot: INT; [myName, myPassword] _ UserCredentials.Get[]; lengthToDot _ userName.Find["."]; myLengthToDot _ myName.Find["."]; IF lengthToDot < 0 AND myLengthToDot > 0 THEN { userName _ userName.Concat[myName.Substr[myLengthToDot]]; <> }; IF userName.Equal[myName, FALSE] AND password.Equal[myPassword, FALSE] THEN <> RETURN [TRUE]; SELECT mark FROM STPOps.markRetrieve => RETURN [CanAccess[userName, codedPassword, $read]]; STPOps.markStore, STPOps.markNewStore => RETURN [CanAccess[userName, codedPassword, $create]]; STPOps.markNewDirectory, STPOps.markDirectory => RETURN [CanAccess[userName, codedPassword, NIL]]; ENDCASE => RETURN [CanAccess[userName, codedPassword, $any]]; }; accessCache: SymTab.Ref _ SymTab.Create[127, FALSE]; cacheEntryLife: INT _ 12*LONG[3600]; <> AccessEntry: TYPE = REF AccessEntryRep; AccessEntryRep: TYPE = RECORD [ codedPassword: GVBasics.Password, lastChecked: BasicTime.GMT _ BasicTime.nullGMT, readFriend: AccessValue _ unknown, createFriend: AccessValue _ unknown, anyFriend: AccessValue _ unknown ]; AccessValue: TYPE = {unknown, yes, no}; CanAccess: PROC [name: ROPE, codedPassword: GVBasics.Password, class: ATOM] RETURNS [BOOL] = { masterName: ROPE _ SELECT class FROM $read => "[]<>STPServer.readAccess", $create => "[]<>STPServer.createAccess", $any => "[]<>STPServer.anyAccess", ENDCASE => NIL; accessEntry: AccessEntry _ NIL; now: BasicTime.GMT _ BasicTime.Now[]; WITH SymTab.Fetch[accessCache, name].val SELECT FROM access: AccessEntry => IF codedPassword = access.codedPassword AND BasicTime.Period[from: access.lastChecked, to: now] < cacheEntryLife THEN accessEntry _ access <> ELSE [] _ SymTab.Delete[accessCache, name]; <> ENDCASE; IF accessEntry = NIL THEN { <> accessEntry _ NEW[AccessEntryRep _ [ codedPassword: codedPassword, lastChecked: now ]]; IF GVNames.AuthenticateKey[name, codedPassword] # individual THEN RETURN [FALSE]; [] _ SymTab.Store[accessCache, name, accessEntry]; }; IF promiscuous THEN RETURN [TRUE]; <> SELECT class FROM $read, $create, $any => { value: AccessValue _ yes; SELECT class FROM $read => value _ accessEntry.readFriend; $create => value _ accessEntry.createFriend; $any => value _ accessEntry.anyFriend; ENDCASE; SELECT value FROM yes => RETURN [TRUE]; no => RETURN [FALSE]; ENDCASE; }; ENDCASE => RETURN [TRUE]; IF masterName # NIL THEN { friends: ROPE _ RopeFile.Create[masterName ! FS.Error => GO TO bad]; offset: INT _ 0; before, after: CHAR _ ' ; offset _ Rope.Find[friends, name, 0, FALSE]; WHILE offset >= 0 DO IF offset > 0 THEN before _ friends.Fetch[offset-1]; IF offset+name.Length < friends.Length THEN after _ friends.Fetch[offset+name.Length]; IF Ascii.Letter[before] OR Ascii.Digit[before] OR Ascii.Letter[after] OR Ascii.Digit[after] THEN offset _ Rope.Find[friends, name, offset+1, FALSE] ELSE GO TO good; ENDLOOP; offset _ Rope.Find[friends, "^"]; WHILE offset >= 0 DO ropeSize: INT ~ Rope.Length[friends]; size: INT _ 0; UNTIL offset = 0 OR IsDelimiter[Rope.Fetch[friends, offset-1]] DO offset _ offset-1; ENDLOOP; UNTIL offset+size = ropeSize OR IsDelimiter[Rope.Fetch[friends, offset+size]] DO size _ size + 1; ENDLOOP; IF GVNames.IsMemberClosure[Rope.Substr[friends, offset, size], name] = yes THEN GO TO good; offset _ Rope.Find[friends, "^", offset+size]; ENDLOOP; GO TO bad; EXITS bad => SELECT class FROM $read => accessEntry.readFriend _ no; $create => accessEntry.createFriend _ no; $any => accessEntry.anyFriend _ no; ENDCASE; good => SELECT class FROM $read => accessEntry.readFriend _ yes; $create => accessEntry.createFriend _ accessEntry.readFriend _ yes; $any => accessEntry.anyFriend _ accessEntry.createFriend _ accessEntry.readFriend _ yes; ENDCASE; }; RETURN [FALSE]; }; IsDelimiter: PROC [c: CHAR] RETURNS [BOOL] ~ { RETURN [SELECT c FROM ' , '\t, ',, '; , '\n => TRUE, ENDCASE => FALSE] }; HidePassword: PROC [rope: ROPE] RETURNS [ROPE] ~ { i: INT _ rope.Find["Password ", 0, FALSE]; e: INT _ 0; IF i >= 0 THEN i _ i + 9; IF i >= 0 THEN e _ rope.Find[")", i]; IF e > 0 THEN rope _ rope.Replace[i, e-i, Rope.Substr["****************", 0, e-i]]; RETURN [rope] }; monthName: ARRAY BasicTime.MonthOfYear OF ROPE ~ ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???"]; PutFileDate: PROC [stream: STREAM, date: BasicTime.GMT] ~ { unpacked: BasicTime.Unpacked _ BasicTime.Unpack[date]; zone: CHAR _ ' ; IO.PutF[stream, "%02g-%g-%02g ", [integer[unpacked.day]], [rope[monthName[unpacked.month]]], [integer[unpacked.year MOD 100]]]; IO.PutF[stream, "%02g:%02g:%02g", [integer[unpacked.hour]], [integer[unpacked.minute]], [integer[unpacked.second]]]; zone _ SELECT unpacked.zone/60 FROM 0 => 'G, 5 => 'E, 6 => 'C, 7 => 'M, 8 => 'P, ENDCASE => ' ; IF zone # ' THEN { IO.PutChar[stream, ' ]; IO.PutChar[stream, zone]; IO.PutChar[stream, IF unpacked.dst = yes THEN 'D ELSE 'S]; IO.PutChar[stream, 'T]; }; }; SendPropList: PROC [stream: STREAM, userProps: UserProperties, fullFName: ROPE, created: BasicTime.GMT, bytes: INT] RETURNS [refused: BOOL _ FALSE] ~ { cp: FS.ComponentPositions; desiredProperty: ARRAY STP.ValidProperties OF BOOL _ userProps.desiredProperty; desiredProperty[serverName] _ desiredProperty[directory] _ desiredProperty[nameBody] _ desiredProperty[version] _ desiredProperty[byteSize] _ TRUE; [fullFName, cp] _ FS.ExpandName[fullFName]; IO.PutChar[stream, '(]; IF desiredProperty[serverName] THEN { IO.PutRope[stream, "(Server-Filename <"]; IF cp.dir.length = 0 THEN IO.PutRope[stream, File.GetVolumeName[File.SystemVolume[]]] ELSE IO.PutRope[stream, fullFName.Substr[cp.dir.start, cp.dir.length]]; IO.PutChar[stream, '>]; IO.PutRope[stream, fullFName.Substr[cp.subDirs.start]]; IO.PutChar[stream, ')]; }; IF desiredProperty[directory] THEN { IO.PutRope[stream, "(Directory "]; IF cp.dir.length = 0 THEN IO.PutRope[stream, File.GetVolumeName[File.SystemVolume[]]] ELSE IO.PutRope[stream, fullFName.Substr[cp.dir.start, cp.dir.length]]; IF cp.subDirs.length > 0 THEN IO.PutChar[stream, '>]; IO.PutRope[stream, fullFName.Substr[cp.subDirs.start, cp.subDirs.length]]; IO.PutChar[stream, ')]; }; IF desiredProperty[nameBody] THEN { IO.PutRope[stream, "(Name-Body "]; IO.PutRope[stream, fullFName.Substr[cp.base.start, cp.ext.start+cp.ext.length-cp.base.start]]; IO.PutChar[stream, ')]; }; IF desiredProperty[version] THEN { IO.PutRope[stream, "(Version "]; IO.PutRope[stream, fullFName.Substr[cp.ver.start, cp.ver.length]]; IO.PutChar[stream, ')]; }; IF desiredProperty[createDate] THEN { IO.PutRope[stream, "(Creation-Date "]; PutFileDate[stream, created]; IO.PutChar[stream, ')]; }; IF desiredProperty[byteSize] THEN { IO.PutRope[stream, "(Byte-Size 8)"]; }; IF desiredProperty[type] THEN { IO.PutRope[stream, "(Type Binary)"]; }; IF desiredProperty[size] THEN { IO.PutF1[stream, "(Size %g)", [integer[bytes]]]; }; IO.PutChar[stream, ')]; }; RetrieveFile: PROC [stream: STREAM, userProps: UserProperties, fullFName: ROPE, created: BasicTime.GMT, bytes: INT] RETURNS [refused: BOOL _ FALSE] ~ { expl: ROPE _ NIL; group: FS.ErrorGroup _ environment; cs: CommandString; HereIsPList[stream, userProps, fullFName, created, bytes]; cs _ GetCommandString[stream]; SELECT cs.mark FROM STPOps.markYes => { ENABLE { FS.Error => {group _ error.group; expl _ error.explanation; GO TO bogus}; IO.Error => {expl _ "Error while reading file"; GO TO bogus}; }; local: STREAM; local _ FS.StreamOpen[fullFName, $read, [tiogaRead: FALSE, commitAndReopenTransOnFlush: TRUE, truncatePagesOnClose: TRUE, finishTransOnClose: TRUE, closeFSOpenFileOnClose: TRUE] ]; PupStream.SendMark[stream, STPOps.markHereIsFile]; CopyStream[from: local, to: stream, fileByteSize: local.GetLength]; IO.Close[local]; Yes[stream, "Transfer complete"]; EXITS bogus => GenerateFileError[stream, group, expl] }; STPOps.markNo => refused _ TRUE; ENDCASE; }; DeleteFile: PROC [stream: STREAM, userProps: UserProperties, fullFName: ROPE, created: BasicTime.GMT, bytes: INT] RETURNS [refused: BOOL _ FALSE] ~ { expl: ROPE _ NIL; group: FS.ErrorGroup _ ok; cs: CommandString; HereIsPList[stream, userProps, fullFName, created, bytes]; cs _ GetCommandString[stream]; SELECT cs.mark FROM STPOps.markYes => { FS.Delete[fullFName, created ! FS.Error => {group _ error.group; expl _ error.explanation; GO TO bogus} ]; Yes[stream, "Deleted"]; EXITS bogus => GenerateFileError[stream, group, expl]; }; STPOps.markNo => refused _ TRUE; ENDCASE; }; FullFNameFromUserProperties: PROC [userProps: UserProperties] RETURNS [fullFName: ROPE] ~ { IF userProps.serverName # NIL THEN { sName: ROPE _ userProps.serverName; IF sName.Length > 0 AND sName.Fetch[0] = '< THEN sName _ Rope.Concat["[]", sName]; fullFName _ FS.ExpandName[sName, Rope.Cat["[]<", userProps.directory, ">"]].fullFName } ELSE { s: STREAM _ IO.ROS[]; dlen: INT _ userProps.directory.Length; s.PutRope["[]"]; IF dlen = 0 OR userProps.directory.Fetch[0] # '< THEN s.PutChar['<]; s.PutRope[userProps.directory]; IF dlen = 0 OR userProps.directory.Fetch[dlen-1] # '> THEN s.PutChar['>]; s.PutRope[userProps.nameBody]; fullFName _ s.RopeFromROS; }; }; StoreFile: PROC [stream: STREAM, userProps: UserProperties, cs: CommandString, newStore: BOOL _ TRUE] ~ { local: STREAM _ NIL; expl: ROPE _ NIL; group: FS.ErrorGroup _ environment; createByteCount: INT _ 2560; fullFName: ROPE _ NIL; desiredVersion: INT _ 0; desiredCreate: GMT _ BasicTime.nullGMT; GetUserProperties[cs, userProps]; IF NOT ValidUser[userProps.userName, userProps.userPassword, cs.mark] THEN { No[stream, accessDenied, "Access denied"]; RETURN; }; { fullFName _ FullFNameFromUserProperties[userProps ! FS.Error => {group _ error.group; expl _ error.explanation; GO TO bogus}]; createByteCount _ Convert.IntFromRope[userProps.size ! Convert.Error => CONTINUE]; desiredVersion _ ParseInteger[userProps.version, 0]; IF userProps.createdTime # NIL THEN desiredCreate _ FSRemoteFile.FTPTimeToGMT[userProps.createdTime ! RuntimeError.UNCAUGHT => CONTINUE]; IF fullFName # NIL THEN { local _ FS.StreamOpen[ fileName: fullFName, accessOptions: $create, streamOptions: [tiogaRead: FALSE, commitAndReopenTransOnFlush: TRUE, truncatePagesOnClose: TRUE, finishTransOnClose: TRUE, closeFSOpenFileOnClose: FALSE], keep: 2, createByteCount: createByteCount ! FS.Error => {group _ error.group; expl _ error.explanation; GO TO bogus} ]; }; IF newStore THEN { realName: ROPE _ NIL; sendCreated: GMT; openFile: FS.OpenFile _ FS.OpenFileFromStream[local]; realName _ FS.GetName[openFile].fullFName; sendCreated _ FS.GetInfo[openFile].created; IF desiredVersion = 0 THEN fullFName _ realName; IF desiredCreate # BasicTime.nullGMT THEN sendCreated _ desiredCreate; HereIsPList[stream, userProps, fullFName, sendCreated, createByteCount]; } ELSE Yes[stream, "Isn't it time you implemented the new store protocol?"]; SELECT cs.mark _ PupStream.ConsumeMark[stream] FROM STPOps.markNo => { cs _ GetCommandString[stream: stream, markConsumed: TRUE, mark: cs.mark]; }; STPOps.markHereIsFile => { CopyStream[from: stream, to: local, fileByteSize: createByteCount ! IO.Error => {expl _ "Error while reading file"; GO TO bogus}; ]; cs _ GetCommandString[stream]; SELECT cs.mark FROM STPOps.markYes => { openFile: FS.OpenFile _ FS.OpenFileFromStream[local]; name: ROPE _ FS.GetName[openFile].fullFName; local.Close[ ! IO.Error => { expl _ "Error while reading file"; GO TO bogus} ]; IF desiredCreate # BasicTime.nullGMT AND expl = NIL THEN { FS.SetByteCountAndCreatedTime[file: openFile, created: desiredCreate ! FS.Error => {group _ error.group; expl _ error.explanation; GO TO bogus} ]; }; IF expl = NIL THEN openFile.Close[ ! FS.Error => {group _ error.group; expl _ error.explanation; GO TO bogus} ]; IF expl # NIL THEN {GenerateFileError[stream, group, expl]; RETURN}; Yes[stream, "Transfer Completed"]; IF desiredVersion > 0 THEN IF SetVersion[name, desiredVersion] THEN IO.PutF1[stream, "\000Version set for %g.", [rope[name]]] ELSE IO.PutF[stream, "\000Version not set for %g, %g desired.", [rope[name]], [integer[desiredVersion]]]; }; STPOps.markNo => { openFile: FS.OpenFile _ FS.OpenFileFromStream[local]; name: ROPE _ FS.GetName[openFile].fullFName; created: BasicTime.GMT _ FS.GetInfo[openFile].created; local.Close[abort: TRUE ! IO.Error => {expl _ "Error while reading file"; GO TO bogus}]; FS.Delete[name ! FS.Error => CONTINUE]; No[stream, notCompleted, "Store not completed"]; IF expl # NIL THEN GO TO bogus; }; ENDCASE => SIGNAL ProtocolError; }; ENDCASE => SIGNAL ProtocolError; EXITS bogus => {GenerateFileError[stream, group, expl]; RETURN}; }; }; SetVersion: PROC [name: ROPE, version: INT] RETURNS [ok: BOOL _ FALSE] = { <> IF CurrentVersion[name] IN [1..version) THEN { sansVersion: ROPE _ Rope.Flatten[name, 0, Rope.SkipTo[name, 0, "!"]]; curName: ROPE _ name; DO FS.Rename[from: curName, to: sansVersion ! FS.Error => EXIT; ]; curName _ FS.FileInfo[name: sansVersion, remoteCheck: FALSE ! FS.Error => EXIT; ].fullFName; IF CurrentVersion[curName] >= version THEN RETURN [TRUE]; ENDLOOP; }; }; CurrentVersion: PROC [name: ROPE] RETURNS [version: INT _ 0] = { len: INT _ Rope.Length[name]; pos: INT _ Rope.SkipTo[name, 0, "!"]; IF pos # len THEN version _ ParseInteger[name, pos+1]; }; ParseInteger: PROC [name: ROPE, pos: INT] RETURNS [n: INT _ 0] = { len: INT _ Rope.Length[name]; FOR i: INT IN [pos..len) DO c: CHAR _ Rope.Fetch[name, i]; IF c IN ['0..'9] THEN n _ n * 10 + (c-'0) ELSE EXIT; IF n > LAST[INTEGER] THEN RETURN [-1]; ENDLOOP; }; GenerateFileError: PROC [stream: STREAM, group: FS.ErrorGroup, expl: ROPE] ~ { No[stream, IF group = lock THEN fileBusy ELSE permanentError, expl]; }; No: PROC [stream: STREAM, replyCode: STPReplyCode.ReplyCode, expl: ROPE] ~ { WITH Atom.GetPropFromList[stream.propList, $STPServerViewerStream] SELECT FROM viewerOut: STREAM => viewerOut.PutF["No (%g), %g\n", [integer[ORD[replyCode]]], [rope[expl]] ! IO.Error => CONTINUE; ]; ENDCASE; PupStream.SendMark[stream, STPOps.markNo]; IO.PutChar[stream, LOOPHOLE[replyCode]]; Finish[stream, expl]; }; Yes: PROC [stream: STREAM, expl: ROPE] ~ { PupStream.SendMark[stream, STPOps.markYes]; IO.PutChar[stream, 0C]; Finish[stream, expl]; }; Finish: PROC [stream: STREAM, expl: ROPE _ NIL] ~ { IF expl # NIL THEN IO.PutRope[stream, expl]; PupStream.SendMark[stream, STPOps.markEOC]; }; HereIsPList: PROC [stream: STREAM, userProps: UserProperties, fullFName: ROPE, created: BasicTime.GMT, bytes: INT] ~ { PupStream.SendMark[stream, STPOps.markHereIsPList]; [] _ SendPropList[stream, userProps, fullFName, created, bytes]; Finish[stream]; }; DoFiles: PROC [stream: STREAM, userProps: UserProperties, cs: CommandString, action: PROC [stream: STREAM, userProps: UserProperties, fullFName: ROPE, created: BasicTime.GMT, bytes: INT] RETURNS [refused: BOOL _ FALSE]] ~ { matches: BOOL _ FALSE; first: BOOL _ TRUE; Info: PROC [fullFName, attachedTo: ROPE, created: BasicTime.GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOL _ TRUE] ~ { refused: BOOL; IF (first AND cs.mark = STPOps.markNewDirectory) OR cs.mark = STPOps.markDirectory THEN PupStream.SendMark[stream, STPOps.markHereIsPList]; refused _ action[stream: stream, userProps: userProps, fullFName: fullFName, created: created, bytes: bytes]; IF NOT refused THEN matches _ TRUE; first _ FALSE; }; Name: PROC [fullFName: ROPE] RETURNS [continue: BOOL _ TRUE] ~ { refused: BOOL; IF (first AND cs.mark = STPOps.markNewDirectory) OR cs.mark = STPOps.markDirectory THEN PupStream.SendMark[stream, STPOps.markHereIsPList]; refused _ action[stream: stream, userProps: userProps, fullFName: fullFName, created: BasicTime.nullGMT, bytes: 0]; IF NOT refused THEN matches _ TRUE; first _ FALSE; }; expl: ROPE _ NIL; group: FS.ErrorGroup; pattern: ROPE _ NIL; GetUserProperties[cs, userProps]; IF NOT ValidUser[userProps.userName, userProps.userPassword, cs.mark] THEN { No[stream, accessDenied, "Access denied"]; RETURN; }; IF userProps.version = NIL AND cs.mark = STPOps.markRetrieve THEN { userProps.version _ "H"; }; pattern _ Rope.Cat[ FullFNameFromUserProperties[userProps ! FS.Error => {expl _ error.explanation; group _ error.group; CONTINUE} ], IF userProps.version.Length > 0 THEN "!" ELSE NIL, userProps.version ]; IF expl # NIL THEN {GenerateFileError[stream, group, expl]; RETURN}; IF userProps.desiredProperty[createDate] OR userProps.desiredProperty[size] THEN { FS.EnumerateForInfo[pattern: pattern, proc: Info ! FS.Error => {expl _ error.explanation; group _ error.group; CONTINUE} ]; } ELSE { FS.EnumerateForNames[pattern: pattern, proc: Name ! FS.Error => {expl _ error.explanation; group _ error.group; CONTINUE} ]; }; IF expl # NIL THEN {GenerateFileError[stream, group, expl]; RETURN}; IF matches AND (cs.mark = STPOps.markNewDirectory OR cs.mark = STPOps.markDirectory) THEN Finish[stream, ""]; IF NOT matches THEN No[stream, fileNotFound, "File not found"]; }; RopeFromMark: PROC [mark: NAT] RETURNS [ROPE] ~ { RETURN [SELECT mark FROM STPOps.markRetrieve => "Retrieve", STPOps.markNewStore => "New-Store", STPOps.markComment => "Comment", STPOps.markIAmVersion => "Version", STPOps.markDirectory => "Enumerate", STPOps.markNewDirectory => "New-Enumerate", STPOps.markDelete => "Delete", STPOps.markYes => "Yes", STPOps.markNo => "No", ENDCASE => Convert.RopeFromInt[mark, 8] ] }; MakeNewServerViewer: PROC [stream: STREAM, pupAddress: PupTypes.PupAddress] ~ { otherGuy: ROPE _ PupDefs.GetHostName[pupAddress]; userProps: UserProperties _ NEW[UserPropertiesRep]; cs: CommandString; closing: BOOL _ FALSE; closeReason: PupStream.CloseReason _ localAbort; viewer: ViewerClasses.Viewer _ MakeMyKindOfTypescript[Rope.Concat["STPServer ", otherGuy]]; viewerOut: STREAM _ IF viewer = NIL THEN NIL ELSE ViewerIO.CreateViewerStreams[name: viewer.name, viewer: viewer, editedStream: FALSE].out; stream.propList _ Atom.PutPropOnList[stream.propList, $STPServerViewerStream, viewerOut]; { ENABLE { PupStream.StreamClosing => {closeReason _ why; closing _ TRUE; GOTO Exit}; PupStream.TimeOut => {closeReason _ transmissionTimeout; closing _ TRUE; GOTO Exit}; RuntimeError.UNCAUGHT => IF NOT debugging THEN {closeReason _ localAbort; GOTO Exit}; ABORTED => {closeReason _ localAbort; GOTO Exit}; }; AwaitCallingMessage[stream]; IF NOT closing THEN SendHerald[stream]; UNTIL closing OR (viewerOut # NIL AND ViewerIO.GetViewerFromStream[viewerOut].destroyed) DO ok: BOOL _ TRUE; cs _ GetCommandString[stream]; SELECT cs.mark FROM STPOps.markDirectory => DoFiles[stream, userProps, cs, SendPropList]; STPOps.markNewDirectory => DoFiles[stream, userProps, cs, SendPropList]; STPOps.markRetrieve => DoFiles[stream, userProps, cs, RetrieveFile]; STPOps.markStore => StoreFile[stream, userProps, cs, FALSE]; STPOps.markNewStore => StoreFile[stream, userProps, cs]; STPOps.markDelete => DoFiles[stream, userProps, cs, DeleteFile]; STPOps.markComment => NULL; ENDCASE => No[stream, STPReplyCode.ReplyCode.badCommand, "Command undefined or unimplemented"]; ENDLOOP; EXITS Exit => NULL; }; IO.Close[stream ! IO.Error => CONTINUE]; IF viewerOut # NIL THEN { IO.PutF1[viewerOut, "\nClosing %g\n", [refAny[NEW[PupStream.CloseReason _ closeReason]]] ! IO.Error => CONTINUE]; Process.Pause[Process.MsecToTicks[1500]]; IF viewer.iconic AND NOT debugging THEN ViewerOps.DestroyViewer[viewer]; }; }; CommandString: TYPE ~ RECORD [ mark: NAT _ 0, string: ROPE _ NIL ]; UserProperties: TYPE ~ REF UserPropertiesRep; UserPropertiesRep: TYPE ~ RECORD [ userName: ROPE _ NIL, userPassword: ROPE _ NIL, directory: ROPE _ NIL, nameBody: ROPE _ NIL, serverName: ROPE _ NIL, version: ROPE _ NIL, createdTime: ROPE _ NIL, size: ROPE _ NIL, desiredProperty: ARRAY STP.ValidProperties OF BOOL _ ALL[FALSE] ]; GetCommandString: PROC [stream: STREAM, markConsumed: BOOL _ FALSE, mark: NAT _ 0] RETURNS [cs: CommandString] ~ { <> isMark: BOOL _ FALSE; char: CHAR _ '?; closed: BOOL _ FALSE; text: REF TEXT _ RefText.ObtainScratch[100]; GetChar: PROC ~ { ok: BOOL _ TRUE; char _ '?; isMark _ FALSE; char _ stream.GetChar[ ! IO.Error => {closed _ TRUE; CONTINUE}; IO.EndOfStream => {isMark _ TRUE; CONTINUE}; ]; }; GetTextToMark: PROC ~ { text.length _ 0; GetChar[]; UNTIL isMark OR closed DO text _ RefText.AppendChar[text, char]; GetChar[]; ENDLOOP; }; cs.string _ NIL; cs.mark _ mark; IF NOT markConsumed THEN { cs.mark _ 0; GetTextToMark[]; IF text.length # 0 THEN SIGNAL ProtocolError; IF closed THEN RETURN; cs.mark _ PupStream.ConsumeMark[stream]; }; GetTextToMark[]; cs.string _ Rope.FromRefText[text]; RefText.ReleaseScratch[text]; { t: NAT _ PupStream.ConsumeMark[stream]; IF t#STPOps.markEOC THEN SIGNAL ProtocolError }; WITH Atom.GetPropFromList[stream.propList, $STPServerViewerStream] SELECT FROM viewerOut: STREAM => { IO.PutF[viewerOut, "\n%g %g\n", [rope[RopeFromMark[cs.mark]]], [rope[HidePassword[cs.string]]] ! IO.Error => CONTINUE]; }; ENDCASE; }; ProtocolError: SIGNAL ~ CODE; AwaitCallingMessage: PROC [stream: STREAM] ~ { cs: CommandString _ GetCommandString[stream]; WHILE cs.mark # STPOps.markIAmVersion DO SIGNAL ProtocolError; cs _ GetCommandString[stream]; ENDLOOP; }; SendHerald: PROC [stream: STREAM] ~ { PupStream.SendMark[stream, STPOps.markIAmVersion]; IO.PutChar[stream, VAL[1]]; Finish[stream, heraldMessage]; }; NarrowToList: PROC [lisp: REF] RETURNS [LORA _ NIL] ~ { WITH lisp SELECT FROM list: LORA => RETURN [list]; ENDCASE; }; NarrowToRope: PROC [lisp: REF] RETURNS [ROPE _ NIL] ~ { WITH lisp SELECT FROM rope: ROPE => RETURN [rope]; ENDCASE; }; propertyNames: ARRAY STP.ValidProperties OF ROPE ~ [ userName: "User-Name", userPassword: "User-Password", connectName: "Connect-Name", connectPassword: "Connect-Password", byteSize: "Byte-Size", type: "Type", size: "Size", directory: "Directory", nameBody: "Name-Body", version: "Version", createDate: "Creation-Date", readDate: "Read-Date", writeDate: "Write-Date", author: "Author", eolConversion: "End-of-Line-Convention", account: "Account", userAccount: "User-Account", device: "Device", serverName: "Server-Filename" ]; ValidPropertyFromRope: PROC [propName: ROPE] RETURNS [prop: STP.ValidProperties] ~ { FOR p: STP.ValidProperties IN STP.ValidProperties DO IF Rope.Equal[propName, propertyNames[p], FALSE] THEN RETURN [p]; ENDLOOP; SIGNAL ProtocolError; }; GetUserProperties: PROC [cs: CommandString, userProperties: UserProperties] ~ { stream: STREAM _ IO.RIS[cs.string]; lisp: REF; userProperties.directory _ NIL; userProperties.nameBody _ NIL; userProperties.serverName _ NIL; userProperties.version _ NIL; userProperties.size _ NIL; userProperties.createdTime _ NIL; lisp _ ParseLisp[stream ! IO.Error, IO.EndOfStream => {SIGNAL ProtocolError; CONTINUE}]; IF NOT stream.EndOf THEN SIGNAL ProtocolError; FOR p: LORA _ NarrowToList[lisp], p.rest UNTIL p = NIL DO q: LORA _ NarrowToList[p.first]; IF q = NIL OR q.rest = NIL THEN SIGNAL ProtocolError; WITH q.first SELECT FROM key: ROPE => { arg: ROPE _ NarrowToRope[q.rest.first]; IF key.Equal["Desired-property", FALSE] THEN { prop: STP.ValidProperties _ ValidPropertyFromRope[arg]; userProperties.desiredProperty[prop] _ TRUE; } ELSE { prop: STP.ValidProperties _ ValidPropertyFromRope[key]; SELECT prop FROM userName => userProperties.userName _ arg; userPassword => userProperties.userPassword _ arg; connectName => NULL; connectPassword => NULL; byteSize => NULL; type => NULL; size => userProperties.size _ arg; directory => userProperties.directory _ arg; nameBody => userProperties.nameBody _ arg; version => userProperties.version _ arg; createDate => { userProperties.createdTime _ arg; <> FOR t: LORA _ q.rest.rest, t.rest UNTIL t = NIL DO r: ROPE _ NarrowToRope[t.first]; userProperties.createdTime _ userProperties.createdTime.Cat[" ", r]; ENDLOOP; }; readDate => NULL; writeDate => NULL; author => NULL; eolConversion => NULL; account => NULL; userAccount => NULL; device => NULL; serverName => userProperties.serverName _ arg; ENDCASE => NULL; }; }; ENDCASE => SIGNAL ProtocolError; ENDLOOP; }; ParseLisp: PROC [stream: STREAM] RETURNS [ref: REF ANY _ NIL] ~ { char: CHAR _ IO.GetChar[stream]; WHILE char = ' DO char _ IO.GetChar[stream] ENDLOOP; IF char # '( THEN { text: REF TEXT _ RefText.ObtainScratch[100]; nextUpper: BOOL _ TRUE; text.length _ 0; WHILE char # ' AND char # '( AND char # ') DO IF char = '\' -- single quote -- THEN { char _ IO.GetChar[stream]; }; text _ RefText.AppendChar[text, char]; char _ IO.GetChar[stream]; ENDLOOP; ref _ Rope.FromRefText[text]; RefText.ReleaseScratch[text]; stream.Backup[char]; } ELSE { list: LORA _ NIL; char _ IO.GetChar[stream]; WHILE char = ' DO char _ IO.GetChar[stream] ENDLOOP; WHILE char # ') DO stream.Backup[char]; list _ CONS[ParseLisp[stream], list]; char _ IO.GetChar[stream]; ENDLOOP; ref _ List.Reverse[list]; }; }; CopyStream: PROC [from, to: STREAM, fileByteSize: INT] ~ { text: REF TEXT _ RefText.ObtainScratch[512]; viewer: ViewerClasses.Viewer _ FindMyViewer[from, to]; toGo: INT _ fileByteSize; WHILE IO.GetBlock[from, text, 0] > 0 DO IF viewer # NIL THEN SetPieFraction[viewer, REAL[toGo]/MAX[fileByteSize, 1]]; IO.PutBlock[to, text]; toGo _ toGo - text.length; ENDLOOP; IF viewer # NIL THEN SetPieFraction[viewer, REAL[toGo]/MAX[fileByteSize, 1]]; RefText.ReleaseScratch[text]; }; ftpListener: PupStream.PupListener _ NIL; STPServerCommand: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd, starExpand: FALSE ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; out: STREAM = cmd.out; ShowOption: PROC [option: ROPE] = { out.PutF1[" STP Server %g.\n", [rope[option]]]; }; FOR i: NAT IN [1..argv.argc) DO <> arg: ROPE = argv[i]; SELECT TRUE FROM Rope.Equal[arg, "stop", FALSE] => { IF ftpListener # NIL THEN { PupStream.DestroyPupListener[ftpListener]; ftpListener _ NIL; }; StopSingle[]; ShowOption["stopped"]; }; Rope.Equal[arg, "start", FALSE], ftpListener = NIL => { ftpListener _ PupStream.CreatePupByteStreamListener[ local: PupTypes.ftpSoc, proc: MakeNewServerViewer, ticks: PupStream.SecondsToTocks[60] ]; IF useSingle THEN StartSingle[]; ShowOption["started"]; }; Rope.Equal[arg, "loose", FALSE] => { promiscuous _ TRUE; ShowOption["promiscuous"]; }; Rope.Equal[arg, "tight", FALSE] => { promiscuous _ FALSE; ShowOption["hard to get"]; }; Rope.Equal[arg, "mute", FALSE] => { mute _ TRUE; ShowOption["mute"]; }; Rope.Equal[arg, "noisy", FALSE] => { mute _ FALSE; ShowOption["noisy"]; }; Rope.Equal[arg, "single", FALSE] => { useSingle _ TRUE; ShowOption["single"]; }; Rope.Equal[arg, "noSingle", FALSE] => { useSingle _ FALSE; ShowOption["noSingle"]; }; ENDCASE => { ShowOption["running"]; }; ENDLOOP; EXITS failed => result _ $Failed; }; MakeMyKindOfTypescript: PROC [name: ROPE] RETURNS [viewer: ViewerClasses.Viewer _ NIL] ~ { IF NOT mute THEN { viewer _ TypeScript.Create[info: [name: name, iconic: TRUE], paint: FALSE]; ViewerOps.AddProp[viewer, $PieData, NEW[PieDataRep _ [iconNumber: maxIcon]]]; viewer.icon _ private; viewer.class _ fakeTypescriptClass; ViewerOps.PaintViewer[viewer, all, TRUE]; }; }; FindMyViewer: PROCEDURE [from, to: IO.STREAM] RETURNS [viewer: ViewerClasses.Viewer _ NIL] ~ { viewerStream: IO.STREAM _ NARROW[Atom.GetPropFromList[from.propList, $STPServerViewerStream]]; IF viewerStream = NIL THEN viewerStream _ NARROW[Atom.GetPropFromList[to.propList, $STPServerViewerStream]]; IF viewerStream # NIL THEN viewer _ ViewerIO.GetViewerFromStream[viewerStream]; }; PieData: TYPE ~ REF PieDataRep; PieDataRep: TYPE ~ RECORD [ iconNumber: INT ]; maxIcon: NAT _ 19; SetPieFraction: PROC [self: ViewerClasses.Viewer, fraction: REAL] ~ { iconNumber: INT _ Real.RoundLI[MAX[MIN[fraction, 1.0], 0.0]*maxIcon]; old: INT _ -1; IF iconNumber = 0 AND fraction>0 THEN iconNumber _ 1; WITH ViewerOps.FetchProp[self, $PieData] SELECT FROM pieData: PieData => { old _ pieData.iconNumber; pieData.iconNumber _ iconNumber; }; ENDCASE => NULL; IF iconNumber # old AND self.iconic THEN { ViewerOps.PaintViewer[self, all, FALSE]; }; }; MyPaint: ViewerClasses.PaintProc ~ { IF self.iconic THEN { iconNumber: INT _ maxIcon; WITH ViewerOps.FetchProp[self, $PieData] SELECT FROM pieData: PieData => { iconNumber _ pieData.iconNumber; }; ENDCASE => NULL; MyPaintIcon[context, iconNumber, self.name]; IF self = TiogaOps.GetSelection[primary].viewer THEN { <> [] _ Imager.SetColor[context, ImagerBackdoor.invert]; Imager.MaskBox[context, [0, 0, Icons.iconW, Icons.iconH]]; }; } ELSE [] _ typescriptPaint[self, context, whatChanged, clear]; }; iconStream: STREAM _ NIL; myIconRef: Icons.IconRef _ NEW[Icons.IconRep]; myIconFlavor: Icons.IconFlavor _ Icons.NewIcon[myIconRef]; MyPaintIcon: ENTRY PROC [context: Imager.Context, iconNumber: INT, name: ROPE] ~ TRUSTED { ENABLE {UNWIND => NULL; RuntimeError.UNCAUGHT => CONTINUE}; bytes: INT _ SIZE[Icons.IconFileFormat]*Basics.bytesPerWord; block: Icons.IconFileFormat; base: LONG POINTER _ @block; iconStream.SetIndex[iconNumber*bytes]; [] _ iconStream.UnsafeGetBlock[[base: base, startIndex: 0, count: bytes]]; myIconRef.bits _ block.bits; myIconRef.label _ block.label; myIconRef.invertLabel _ block.invertLabel; myIconRef.lx _ block.lx; myIconRef.ly _ block.ly; myIconRef.lw _ block.lw; myIconRef.lh _ block.lh; Icons.DrawIcon[myIconFlavor, context, 0, 0, name]; }; fakeTypescriptClass: ViewerClasses.ViewerClass _ NIL; <> typescriptPaint: ViewerClasses.PaintProc _ NIL; iconFileName: ROPE _ "STPServer.icons"; <> <<>> LookupFileType: PupTypes.PupType = LOOPHOLE[200B]; LookupFileReplyType: PupTypes.PupType = LOOPHOLE[201B]; LookupFileErrorType: PupTypes.PupType = LOOPHOLE[202B]; LookupFileSocket: PupTypes.PupSocketID = [0, 61B]; singleSocket: PupDefs.PupSocket _ NIL; singleProcess: CedarProcess.Process _ NIL; StartSingle: ENTRY PROC = { ENABLE UNWIND => NULL; IF singleProcess = NIL THEN { singleSocket _ PupDefs.PupSocketMake [local: LookupFileSocket, remote: PupTypes.fillInPupAddress, ticks: PupDefs.veryLongWait ]; singleProcess _ CedarProcess.Fork[ SinglePacketListener, NIL, [priority: foreground, usePriority: TRUE]]; }; }; StopSingle: ENTRY PROC = { ENABLE UNWIND => NULL; IF singleProcess # NIL THEN { CedarProcess.Abort[singleProcess]; PupDefs.PupSocketDestroy[singleSocket ! RuntimeError.UNCAUGHT => CONTINUE]; singleSocket _ NIL; }; }; ReplyRecord: TYPE = MACHINE DEPENDENT RECORD[v: CARDINAL, c, l: LongNumber]; ReplyRecordBytes: CARDINAL = SIZE[ReplyRecord]*Basics.bytesPerWord; SinglePacketListener: CedarProcess.ForkableProc = TRUSTED { ENABLE UNWIND => singleProcess _ NIL; text: REF TEXT _ NEW[TEXT[256]]; DO receiver: PupDefs.PupBuffer = singleSocket.get[]; replyType: PupTypes.PupType _ error; replyBytes: CARDINAL _ 0; IF receiver # NIL THEN { addr: PupTypes.PupAddress _ receiver.source; msWait: CARDINAL = 2000+500*MIN[8, PupDefs.GetHopsToNetwork[addr.net]]; IF receiver.pupType = LookupFileType THEN { id: PupTypes.Pair _ receiver.pupID; len: INT _ PupDefs.GetPupContentsBytes[receiver]; IF len IN [1..256] THEN { ENABLE RuntimeError.UNCAUGHT => GO TO dropIt; sLen: NAT _ len; name: ROPE _ NIL; fileLen: INT _ 0; fileDate: GMT _ BasicTime.nullGMT; rPtr: LONG POINTER TO ReplyRecord = LOOPHOLE[@receiver.pupBody]; FOR i: NAT IN [0 .. sLen) DO text[i] _ receiver.pupChars[i]; ENDLOOP; text.length _ sLen; name _ Rope.Flatten[Rope.Concat["[]", RefText.TrustTextAsRope[text]]]; [fullFName: name, created: fileDate, bytes: fileLen] _ FS.FileInfo[name: name ! FS.Error => { IF error.code = $unknownFile THEN { replyType _ LookupFileErrorType; }; CONTINUE }; ]; IF fileDate # BasicTime.nullGMT AND fileLen >= 0 THEN { rPtr.v _ CurrentVersion[name]; rPtr.c _ Basics.SwapHalves[[lc[BasicTime.ToPupTime[fileDate]]]]; rPtr.l _ Basics.SwapHalves[[li[fileLen]]]; replyType _ LookupFileReplyType; replyBytes _ ReplyRecordBytes; }; }; EXITS dropIt => {}; }; receiver.pupType _ replyType; IF replyType = error THEN receiver.errorCode _ noProcessPupErrorCode; <> PupDefs.ReturnPup[receiver, replyType, replyBytes]; }; CedarProcess.CheckAbort[singleProcess]; ENDLOOP; }; <<>> <> Init: PROC ~ { fakeTypescriptClass _ NEW[ViewerClasses.ViewerClassRec _ ViewerOps.FetchViewerClass[$Typescript]^]; typescriptPaint _ fakeTypescriptClass.paint; fakeTypescriptClass.paint _ MyPaint; iconStream _ FS.StreamOpen[ fileName: iconFileName, accessOptions: $read, streamOptions: [ tiogaRead: FALSE, commitAndReopenTransOnFlush: TRUE, truncatePagesOnClose: FALSE, finishTransOnClose: TRUE, closeFSOpenFileOnClose: TRUE ], streamBufferParms: [vmPagesPerBuffer: 2, nBuffers: 1] ]; Commander.Register["STPServer", STPServerCommand, "STPServer [ start | stop ]\n[]<>STPServer.readAccess should have list of permissible readers"]; }; Init[]; END.