-- file: FTPServerMail.mesa -- Edited by Mark Johnson: 19-May-81 13:24:24 -- Edited by Andrew Birrell: 18-Jan-82 10:25:37 -- Edited by: HGM March 6, 1979 4:05 PM -- Edited by: JEW October 31, 1978 3:28 PM DIRECTORY BodyDefs USING[ maxRNameLength ], FTPDefs: FROM "FTPDefs", FTPPrivateDefs: FROM "FTPPrivateDefs", SendDefs USING[ AddRecipient, AddToItem, Create, Destroy, CheckValidity, Handle, Send, SendFromClient, StartSendInfo, StartText ], String USING [AppendChar, AppendDecimal, AppendLongNumber], Storage USING [Node, Free, FreeString]; FTPServerMail: PROGRAM -- import list IMPORTS SendDefs, String, Storage, FTPDefs, FTPPrivateDefs -- export list EXPORTS FTPPrivateDefs -- share list SHARES FTPDefs, FTPPrivateDefs = BEGIN OPEN FTPDefs, FTPPrivateDefs; -- **********************! Constants !*********************** ftpsystem: POINTER TO FTPSystem = LocateFtpSystemObject[]; -- **********************! Module Presence Test Procedure !*********************** ServerMailLoaded: PUBLIC PROCEDURE = BEGIN -- report in ftpsystem.serverMailLoaded ← TRUE; END; -- **********************! Mail Commands !*********************** PTFStoreMail: PUBLIC PROCEDURE [ftpserver: FTPServer] = BEGIN OPEN ftpserver, ftpserver.ftplistener; -- PutMailboxException procedure sender: STRING; PutMailboxException: PROCEDURE [number: CARDINAL, recipientError: RecipientError] = BEGIN -- local constants message: STRING = [maxStringLength]; -- construct error message String.AppendDecimal[message, number]; String.AppendChar[message, mailboxExceptionIndexTerminator]; IF ftpsystem.accessoriesLoaded THEN VerbalizeRecipientError[recipientError, message]; -- send mailbox exception command PutCommand[ftper, markMailboxException, RecipientErrorToExceptionCode[recipientError]]; -- send error message PutString[ftper, message]; END; EarlyAbort: PROC[why: SendDefs.StartSendInfo] = BEGIN DO GetPropertyList[ftper, propertyList ! FTPError => IF ftpError = protocolParameterListMissing THEN EXIT]; ENDLOOP; SELECT why FROM badPwd => AbortWithExplanation[unidentifiedPermanentError, "Incorrect sender password"L]; badSender, badReturnTo => AbortWithExplanation[unidentifiedPermanentError, SELECT TRUE FROM sender = NIL => "Sender name missing"L, sender.length > BodyDefs.maxRNameLength => "Sender name too long"L, ENDCASE => "Sender name not valid"L]; ENDCASE => ERROR -- allDown, ok --; END; -- local variables gvHandle: SendDefs.Handle = SendDefs.Create[]; -- intercept errors BEGIN ENABLE UNWIND => SendDefs.Destroy[gvHandle]; -- start message; first property list must have sender -- recipients: CARDINAL ← 0; -- BEWARE: assumes representation of MailSystemObject -- mailInfo: POINTER TO RECORD[ net, host: [0..256)] = LOOPHOLE[mailSystem]; GetPropertyList[ftper, propertyList]; --must be present-- sender ← propertyList[sender]; IF sender = NIL THEN EarlyAbort[badSender]; IF sender.length > BodyDefs.maxRNameLength THEN EarlyAbort[badSender]; BEGIN sendInfo: SendDefs.StartSendInfo = SendDefs.SendFromClient[handle: gvHandle, fromNet:mailInfo.net, fromHost:mailInfo.host, senderKey: [0,0,0,0], sender: sender, returnTo: sender, validate: TRUE]; IF sendInfo # ok THEN EarlyAbort[sendInfo]; END; -- receive distribution list and EOC DO -- enqueue for recipient SendDefs.AddRecipient[gvHandle, propertyList[mailbox]]; recipients ← recipients + 1; -- receive next property list GetPropertyList[ftper, propertyList ! FTPError => IF ftpError = protocolParameterListMissing THEN EXIT]; ENDLOOP; GetEOC[ftper]; -- reject undefined recipients BEGIN Notify: PROCEDURE[number: CARDINAL, recipient: STRING] = BEGIN recipients ← recipients - 1; PutMailboxException[number, noSuchMailbox]; END; [] ← SendDefs.CheckValidity[gvHandle, Notify]; END; -- send preliminary approval of distribution list IF recipients # 0 THEN PutCommandAndEOC[ftper, markYes, 0] ELSE Abort[noValidRecipients]; -- accept message text GetSpecificCommand[ftper, markHereIsFile]; StageMessage[gvHandle, ReceiveBlock, ftper]; GetYesAndEOC[ftper]; -- deliver message SendDefs.Send[gvHandle]; -- send Yes and EOC PutCommandAndEOC[ftper, markYes, 0]; -- tidy up SendDefs.Destroy[gvHandle]; END; -- enable END; PTFRetrieveMail: PUBLIC PROCEDURE [ftpserver: FTPServer] = BEGIN OPEN ftpserver, ftpserver.ftplistener; -- NextMessage procedure NextMessage: PROCEDURE [messageInfo: MessageInfo] = BEGIN OPEN messageInfo; -- next message IF byteCount # 0 THEN BEGIN -- send message information PutCommand[ftper, markHereIsPropertyList, 0]; PutMessageInfo[messageInfo]; -- signal transmission of text PutCommand[ftper, markHereIsFile, 0]; END -- no more messages ELSE BEGIN -- send Yes and EOC PutCommandAndEOC[ftper, markYes, 0]; -- receive OK to flush mailbox GetSpecificCommand[ftper, markFlushMailbox]; GetEOC[ftper]; END; END; -- PutMessageInfo procedure PutMessageInfo: PROCEDURE [messageInfo: MessageInfo] = BEGIN OPEN messageInfo; -- local constants messageLength: STRING = [maxStringLength]; boolean: STRING = [maxStringLength]; -- reset property list ResetPropertyList[propertyList]; -- encode byte count String.AppendLongNumber[messageLength, byteCount, 10]; WriteProperty[propertyList, length, messageLength]; -- encode delivery date WriteProperty[propertyList, dateReceived, deliveryDate]; -- encode opened and deleted EncodeBooleanProperty[opened, boolean]; WriteProperty[propertyList, opened, boolean]; EncodeBooleanProperty[deleted, boolean]; WriteProperty[propertyList, deleted, boolean]; -- send property list PutPropertyList[ftper, propertyList]; END; -- local variables mailbox: Mailbox ← NIL; -- receive property list and EOC GetPropertyList[ftper, propertyList]; GetEOC[ftper]; -- inspect credentials IF propertyList[userName] # NIL THEN mailPrimitives.InspectCredentials[mailSystem, primary, propertyList[userName], propertyList[userPassword]]; IF propertyList[connectName] # NIL THEN mailPrimitives.InspectCredentials[mailSystem, secondary, propertyList[connectName], propertyList[connectPassword]]; -- enqueue mailbox mailbox ← CreateMailbox[propertyList, 1]; BEGIN ENABLE UNWIND => DestroyMailbox[mailbox]; -- locate local mailbox mailPrimitives.LocateMailboxes[mailSystem, mailbox]; IF ~mailbox.located THEN Abort[noSuchMailbox]; -- retrieve messages mailPrimitives.RetrieveMessages[mailSystem, mailbox, NextMessage, SendBlock, ftper]; PutCommandAndEOC[ftper, markYes, 0]; -- destroy mailbox END; -- enable DestroyMailbox[mailbox]; END; -- **********************! Mailbox Primitives !*********************** StageMessage: PROCEDURE[ gvHandle: SendDefs.Handle, receiveBlock: PROCEDURE[UNSPECIFIED,POINTER,CARDINAL]RETURNS[CARDINAL], receiveBlockData: UNSPECIFIED ] = BEGIN length: CARDINAL = 65; prevOdd: BOOLEAN ← FALSE; buffer: POINTER = Storage.Node[length]; SendDefs.StartText[gvHandle]; DO BEGIN ENABLE UNWIND => Storage.Free[buffer]; count: CARDINAL = receiveBlock[receiveBlockData,buffer,length]; IF count = 0 THEN EXIT; IF prevOdd THEN ERROR FTPDefs.FTPError[requestedAccessDenied, "Odd-byte block boundary not implemented"L]; SendDefs.AddToItem[gvHandle, DESCRIPTOR[buffer, count] ]; IF count MOD 2 # 0 THEN prevOdd ← TRUE; END; ENDLOOP; Storage.Free[buffer]; END; CreateMailbox: PROCEDURE [propertyList: PropertyList, number: CARDINAL] RETURNS [mailbox: Mailbox] = BEGIN -- Note: Initializes nextMailbox to NIL; -- removes mailbox property from property list. -- allocate and initialize mailbox object mailbox ← Storage.Node[SIZE[MailboxObject]]; mailbox↑ ← MailboxObject[ number: number, mailbox: propertyList[mailbox], location: NIL, located: FALSE, delivered: FALSE, nextMailbox: NIL]; -- remove mailbox, host, and dms name from property list -- Note: WriteProperty was circumvented above. propertyList[mailbox] ← NIL; END; DestroyMailbox: PROCEDURE [mailbox: Mailbox] = BEGIN -- release mailbox if any IF mailbox.mailbox # NIL THEN Storage.FreeString[mailbox.mailbox]; -- release location if any IF mailbox.location # NIL THEN Storage.FreeString[mailbox.location]; -- release mailbox object Storage.Free[mailbox]; END; -- **********************! Main Program !*********************** -- no operation END. -- of FTPServerMail