-- Copyright (C) 1981, 1982, 1983, 1984, 1985 by Xerox Corporation. All rights reserved. -- Grapevine: FTP server using VMDefs -- FTPServer.mesa, HGM, 16-Sep-85 22:07:59 -- Randy Gobbel 9-Jul-82 13:48:05 -- -- Andrew Birrell 17-Mar-81 14:46:23 -- -- Mark Johnson 21-Jan-82 15:34:28 -- -- Brenda Hankins 23-Aug-84 11:03:21 change TimeExtraDefs to TimeExtra DIRECTORY BodyDefs USING [maxRNameLength, RName], FTPDefs, Heap USING [systemZone], LogDefs USING [WriteLogEntry], MFile USING [ Acquire, dontRelease, EnumerateDirectory, EnumerateProc, Error, Handle, maxNameLength, Release, Rename], NameInfoDefs USING [ Authenticate, AuthenticateInfo, IsMemberClosure, Membership], PolicyDefs USING [CheckOperation, EndOperation], PupDefs USING [AppendMyName, ParsePupAddressConstant, PupAddress], String USING [AppendChar, AppendNumber, AppendString, EquivalentStrings], Time USING [Append, Packed, Unpack], TimeExtra USING [PackedTimeFromString], VMDefs USING [ AbandonFile, CantOpen, CantReadBackingStore, CantWriteBackingStore, CloseFile, DestroyFile, Error, FileHandle, FileTime, GetFileLength, GetFileTimes, MarkStart, OpenFile, Page, pageSize, Position, ReadPage, Release, SetCreationTime, SetFileLength, UsePage, WaitFile]; FTPServer: PROGRAM IMPORTS FTPDefs, Heap, LogDefs, MFile, NameInfoDefs, PolicyDefs, PupDefs, String, Time, TimeExtra, VMDefs = BEGIN WhoIsHe: SIGNAL RETURNS [net, host: [0..256)] = CODE; --communication between LogAction and Backstop -- LogAction: PROC [ user: BodyDefs.RName, file: LONG STRING, action: {read, write, rename, delete}] = BEGIN log: LONG STRING ¬ Heap.systemZone.NEW[StringBody[128]]; String.AppendString[log, "FTP server: "L]; BEGIN net, host: [0..256); [net, host] ¬ SIGNAL WhoIsHe; String.AppendNumber[log, net, 8]; String.AppendChar[log, '#]; String.AppendNumber[log, host, 8]; String.AppendChar[log, '#]; END; String.AppendChar[log, ' ]; String.AppendString[log, user]; String.AppendString[ log, SELECT action FROM read => " reading "L, write => " writing "L, rename => " renaming "L, delete => " deleting "L ENDCASE => ERROR]; String.AppendString[log, file]; LogDefs.WriteLogEntry[log]; Heap.systemZone.FREE[@log]; END; MyCreate: PROC [bufferSize: CARDINAL] RETURNS [fileSystem: FTPDefs.FileSystem] = BEGIN real: BodyDefs.RName = Heap.systemZone.NEW[StringBody[BodyDefs.maxRNameLength]]; RETURN[LOOPHOLE[real]]; END; MyDestroy: PROC [fileSystem: FTPDefs.FileSystem] = {Heap.systemZone.FREE[@fileSystem]}; MyDecompose: PROC [ fileSystem: FTPDefs.FileSystem, absoluteFilename: STRING, virtualFilename: FTPDefs.VirtualFilename] = BEGIN virtualFilename.device.length ¬ 0; virtualFilename.directory.length ¬ 0; virtualFilename.name.length ¬ 0; String.AppendString[virtualFilename.name, absoluteFilename]; virtualFilename.version.length ¬ 0; END; MyCompose: PROC [ fileSystem: FTPDefs.FileSystem, absoluteFilename: STRING, virtualFilename: FTPDefs.VirtualFilename] = BEGIN IF virtualFilename.device.length = 0 AND virtualFilename.directory.length = 0 AND virtualFilename.name.length = 0 AND virtualFilename.version.length = 0 THEN NULL -- that's what the spec says! -- ELSE BEGIN absoluteFilename.length ¬ 0; String.AppendString[absoluteFilename, virtualFilename.name]; END; END; DefaultReg: PROC [name: BodyDefs.RName] = BEGIN default: STRING = ".pa"L; FOR i: CARDINAL DECREASING IN [0..name.length) DO IF name[i] = '. THEN EXIT; REPEAT FINISHED => IF name.length + default.length <= name.maxlength THEN String.AppendString[name, default]; ENDLOOP; END; MyInspectCredentials: PROC [ fileSystem: FTPDefs.FileSystem, status: FTPDefs.Status, user, password: STRING] = BEGIN real: BodyDefs.RName = LOOPHOLE[fileSystem]; IF status = primary THEN BEGIN outcome: NameInfoDefs.AuthenticateInfo; IF user = NIL OR user.length > BodyDefs.maxRNameLength THEN { real.length ¬ 0; outcome ¬ notFound} ELSE BEGIN IF password = NIL THEN outcome ¬ badPwd ELSE BEGIN real.length ¬ 0; String.AppendString[real, user]; DefaultReg[real]; outcome ¬ NameInfoDefs.Authenticate[real, password]; END; END; IF outcome # individual THEN BEGIN error: FTPDefs.FtpError = SELECT outcome FROM notFound, group => noSuchPrimaryUser, badPwd => incorrectPrimaryPassword, allDown => unidentifiedTransientError, ENDCASE => ERROR; string: STRING = SELECT outcome FROM notFound => "Incorrect registry or user name"L, group => "Can't login as a group"L, badPwd => "Incorrect password"L, allDown => "Can't reach any authentication server", ENDCASE => ERROR; real.length ¬ 0; ERROR FTPDefs.FTPError[error, string]; END; END; END; CheckAccess: PROC [ user: BodyDefs.RName, why: {list, read, write}, file: STRING] = BEGIN info: NameInfoDefs.Membership ¬ no; IF user = NIL OR user.length = 0 THEN ERROR FTPDefs.FTPError[credentialsMissing, "No valid user/password given"L]; IF why # list AND String.EquivalentStrings[file, "Heap.data"L] THEN FTPDefs.FTPError[requestedAccessDenied, "'Heap.data' is private!"L]; IF (why = list OR why = read) AND String.EquivalentStrings[file, "GV.log"L] THEN info ¬ NameInfoDefs.IsMemberClosure["LogReaders^.ms"L, user]; IF info # yes THEN info ¬ NameInfoDefs.IsMemberClosure["Transport^.ms"L, user]; SELECT info FROM yes => NULL; no, notGroup => ERROR FTPDefs.FTPError[ requestedAccessDenied, "You're not allowed access to this server"L]; allDown => ERROR FTPDefs.FTPError[ requestedAccessDenied, "Can't contact access control server"L]; ENDCASE => ERROR; END; OpenVMFile: PROC [file: STRING, action: {read, write, delete, enumerate}] RETURNS [handle: VMDefs.FileHandle] = BEGIN ENABLE VMDefs.CantOpen => SELECT reason FROM notFound => ERROR FTPDefs.FTPError[noSuchFile, "File does not exist"L]; alreadyExists => ERROR FTPDefs.FTPError[fileAlreadyExists, "File already exists"L]; accessDenied => ERROR FTPDefs.FTPError[ requestedAccessDenied, "File not available at present"L]; illegalFileName => ERROR FTPDefs.FTPError[illegalFilename, "Illegal file title"L]; ENDCASE => ERROR FTPDefs.FTPError[ unidentifiedPermanentError, "Unidentified ""CantOpen"" from VM"L]; handle ¬ VMDefs.OpenFile[ options: SELECT action FROM read, enumerate => oldReadOnly, write => oldOrNew, delete => old, ENDCASE => ERROR, name: file, cacheFraction: 2]; END; GetInfo: PROC [handle: VMDefs.FileHandle, info: FTPDefs.FileInfo] = BEGIN IF info = NIL THEN RETURN; BEGIN length: VMDefs.Position = VMDefs.GetFileLength[handle]; info.byteCount ¬ LONG[length.page] * 2 * VMDefs.pageSize + length.byte; END; info.fileType ¬ binary; info.byteSize ¬ 8; BEGIN Append: PROC [s: STRING, t: Time.Packed] = BEGIN IF s # NIL THEN {s.length ¬ 0; Time.Append[s, Time.Unpack[t], TRUE]}; END; create, write, read: Time.Packed; [create: LOOPHOLE[create, VMDefs.FileTime], read: LOOPHOLE[read, VMDefs.FileTime], write: LOOPHOLE[write, VMDefs.FileTime]] ¬ VMDefs.GetFileTimes[handle]; Append[info.creationDate, create]; Append[info.writeDate, write]; Append[info.readDate, read]; END; END; MyEnumerateFiles: PROC [ fileSystem: FTPDefs.FileSystem, files: STRING, intent: FTPDefs.EnumerateFilesIntent, processFile: PROC [UNSPECIFIED, STRING, FTPDefs.FileInfo], processFileData: UNSPECIFIED] = BEGIN real: BodyDefs.RName = LOOPHOLE[fileSystem]; creation: STRING = [22]; -- 31-Oct-83 25:61:61 BST -- write: STRING = [22]; -- 1234567890123456789012 -- read: STRING = [22]; fileInfoObject: FTPDefs.FileInfoObject ¬ [ fileType: binary, byteSize: 8, byteCount: 0, creationDate: creation, writeDate: write, readDate: read, author: NIL]; SingleFile: MFile.EnumerateProc = BEGIN shortName: STRING = [MFile.maxNameLength]; handle: VMDefs.FileHandle; String.AppendString[shortName, name]; handle ¬ VMDefs.OpenFile[name: shortName]; GetInfo[handle, @fileInfoObject]; VMDefs.AbandonFile[handle]; processFile[processFileData, shortName, @fileInfoObject]; RETURN[FALSE] END; CheckAccess[real, list, files]; IF intent = enumeration AND files.length = 1 AND files[0] = '* THEN MFile.EnumerateDirectory["*"L, SingleFile, filesOnly] ELSE BEGIN IF intent = enumeration THEN BEGIN handle: VMDefs.FileHandle = OpenVMFile[files, enumerate]; GetInfo[handle, @fileInfoObject]; VMDefs.AbandonFile[handle]; END; processFile[processFileData, files, @fileInfoObject]; END; END; MyOpenFile: PROC [ fileSystem: FTPDefs.FileSystem, file: STRING, mode: FTPDefs.Mode, fileTypePlease: BOOLEAN, info: FTPDefs.FileInfo] RETURNS [fileHandle: FTPDefs.FileHandle, fileType: FTPDefs.FileType] = BEGIN real: BodyDefs.RName = LOOPHOLE[fileSystem]; SELECT mode FROM read => BEGIN handle: VMDefs.FileHandle; CheckAccess[real, read, file]; handle ¬ OpenVMFile[file, read]; LogAction[real, file, read]; GetInfo[handle, info]; fileType ¬ binary; fileHandle ¬ LOOPHOLE[handle]; END; write => BEGIN CheckAccess[real, write, file]; fileHandle ¬ LOOPHOLE[OpenVMFile[file, write]]; LogAction[real, file, write]; IF info # NIL AND info.creationDate # NIL AND info.creationDate.length > 0 THEN VMDefs.SetCreationTime[ file: LOOPHOLE[fileHandle], create: TimeExtra.PackedTimeFromString[info.creationDate]]; END; ENDCASE => ERROR FTPDefs.FTPError[requestedAccessDenied, "Unexpected access mode"L]; END; MyReadFile: PROC [ fileSystem: FTPDefs.FileSystem, fileHandle: FTPDefs.FileHandle, sendBlock: PROC [LONG UNSPECIFIED, LONG POINTER, CARDINAL], sendBlockData: LONG UNSPECIFIED] = BEGIN handle: VMDefs.FileHandle = LOOPHOLE[fileHandle]; length: VMDefs.Position = VMDefs.GetFileLength[handle]; FOR p: CARDINAL IN [0..length.page) --complete pages-- DO page: VMDefs.Page = VMDefs.ReadPage[[handle, p], 2]; sendBlock[ sendBlockData, page, 2 * VMDefs.pageSize ! UNWIND => VMDefs.Release[page]]; VMDefs.Release[page]; ENDLOOP; IF length.byte > 0 THEN --partial page-- BEGIN page: VMDefs.Page = VMDefs.ReadPage[[handle, length.page], 0]; sendBlock[ sendBlockData, page, length.byte ! UNWIND => VMDefs.Release[page]]; VMDefs.Release[page]; END; END; MyWriteFile: PROC [ fileSystem: FTPDefs.FileSystem, fileHandle: FTPDefs.FileHandle, receiveBlock: PROC [LONG UNSPECIFIED, LONG POINTER, CARDINAL] RETURNS [CARDINAL], receiveBlockData: LONG UNSPECIFIED] = BEGIN handle: VMDefs.FileHandle = LOOPHOLE[fileHandle]; pos: VMDefs.Position ¬ [0, 0]; page: VMDefs.Page ¬ VMDefs.UsePage[[handle, pos.page]]; DO ENABLE UNWIND => IF page # NIL THEN VMDefs.Release[page]; amount: CARDINAL = receiveBlock[ receiveBlockData, page + (pos.byte / 2), VMDefs.pageSize - (pos.byte / 2)]; IF amount = 0 THEN EXIT; pos.byte ¬ pos.byte + amount; IF amount MOD 2 # 0 THEN BEGIN temp: UNSPECIFIED; IF receiveBlock[receiveBlockData, @temp, SIZE[UNSPECIFIED]] = 0 THEN EXIT ELSE ERROR FTPDefs.FTPError[ requestedAccessDenied, "Odd-byte block boundary not implemented"L]; END; IF pos.byte >= 2 * VMDefs.pageSize THEN BEGIN IF pos.byte > 2 * VMDefs.pageSize THEN ERROR; VMDefs.MarkStart[page]; VMDefs.Release[page]; page ¬ NIL; pos.page ¬ pos.page + 1; pos.byte ¬ 0; page ¬ VMDefs.UsePage[[handle, pos.page]]; END; ENDLOOP; IF pos.byte > 0 THEN VMDefs.MarkStart[page]; VMDefs.Release[page]; VMDefs.WaitFile[handle]; VMDefs.SetFileLength[handle, pos]; END; MyCloseFile: PROC [ fileSystem: FTPDefs.FileSystem, fileHandle: FTPDefs.FileHandle, aborted: BOOLEAN] = BEGIN VMDefs.CloseFile[LOOPHOLE[fileHandle]]; END; MyDeleteFile: PROC [fileSystem: FTPDefs.FileSystem, file: STRING] = BEGIN real: BodyDefs.RName = LOOPHOLE[fileSystem]; handle: VMDefs.FileHandle; CheckAccess[real, write, file]; handle ¬ OpenVMFile[file, delete]; LogAction[real, file, delete]; VMDefs.DestroyFile[handle]; END; MyRenameFile: PROC [ fileSystem: FTPDefs.FileSystem, currentFile, newFile: STRING] = BEGIN real: BodyDefs.RName = LOOPHOLE[fileSystem]; fh: MFile.Handle; CheckAccess[real, write, newFile]; CheckAccess[real, read, currentFile]; LogAction[real, currentFile, rename]; fh ¬ MFile.Acquire[ currentFile, rename, MFile.dontRelease ! MFile.Error => ERROR FTPDefs.FTPError[ requestedAccessDenied, "source of rename doesn't exist"L] ]; MFile.Rename[fh, newFile ! MFile.Error => { MFile.Release[fh]; ERROR FTPDefs.FTPError[ requestedAccessDenied, "destination already exists"L]; } ]; MFile.Release[fh]; END; -- Backstop and Filter for listeners -- Backstop: FTPDefs.BackstopServer ¬ BEGIN addr: PupDefs.PupAddress; IF NOT PupDefs.ParsePupAddressConstant[@addr, originOfRequest] THEN BEGIN addr.net ¬ [0]; addr.host ¬ [0]; END; localInsignia.length ¬ 0; String.AppendString[localInsignia, "Grapevine FTP server on host "L]; PupDefs.AppendMyName[localInsignia]; server[ ! FTPDefs.FTPError => SELECT ftpError FROM IN FTPDefs.CommunicationError, IN FTPDefs.ProtocolError => CONTINUE; IN FTPDefs.UnidentifiedError => CONTINUE; ENDCASE => RESUME ; WhoIsHe => RESUME [addr.net, addr.host]; VMDefs.Error => SELECT reason FROM io => ERROR FTPDefs.FTPError[fileDataError, "Unrecoverable i/o error"L]; resources => ERROR FTPDefs.FTPError[noRoomForFile, "Backing store full"L]; ENDCASE => ERROR FTPDefs.FTPError[ unidentifiedPermanentError, "Unidentified ""VMDefs.Error"""L]; VMDefs.CantWriteBackingStore => ERROR FTPDefs.FTPError[ fileDataError, "Can't write page to backing store"L]; VMDefs.CantReadBackingStore => ERROR FTPDefs.FTPError[ fileDataError, "Can't read page from backing store"L]; ]; PolicyDefs.EndOperation[FTP]; END; Filter: PROC [from: STRING, purpose: FTPDefs.Purpose] = BEGIN IF NOT PolicyDefs.CheckOperation[FTP] THEN BEGIN LogDefs.WriteLogEntry["Rejected FTP connection"L]; ERROR FTPDefs.RejectThisConnection["Server full"L]; END; END; -- Initialization -- myFilePrimitives: FTPDefs.FilePrimitivesObject ¬ [ CreateFileSystem: MyCreate, DestroyFileSystem: MyDestroy, DecomposeFilename: MyDecompose, ComposeFilename: MyCompose, InspectCredentials: MyInspectCredentials, EnumerateFiles: MyEnumerateFiles, OpenFile: MyOpenFile, ReadFile: MyReadFile, WriteFile: MyWriteFile, CloseFile: MyCloseFile, DeleteFile: MyDeleteFile, RenameFile: MyRenameFile]; FTPDefs.FTPInitialize[]; FTPDefs.FTPCatchUnidentifiedErrors[FALSE]; [] ¬ FTPDefs.FTPCreateListener[ --purpose-- files, --file system-- @myFilePrimitives, --mail system-- NIL, --comm system-- FTPDefs.PupCommunicationPrimitives[], --backstop-- @Backstop, --backstopData-- 0, --filter-- Filter]; END.