-- Transport Mechanism Mail Server - server for MTP --
-- [Indigo]<Grapevine>MS>MTPServer.mesa
-- Randy Gobbel 29-May-81 14:46:16 --
-- Andrew Birrell 29-Dec-81 15:00:20 --
-- Mark Johnson May 28, 1981 2:39 PM --
DIRECTORY
BodyDefs USING[ ItemLength, maxRNameLength, RName, Timestamp ],
FTPDefs,
HeapDefs USING[ HeapAbandonWrite, HeapEndRead, HeapReadData,
HeapReadRName, HeapStartRead, HeapStartWrite,
HeapWriteData, HeapWriteRName, ObjectNumber,
objectStart, ReaderHandle, SetReaderOffset,
WriterHandle ],
LocalNameDefs USING[ ReadMSName ],
LogDefs USING[ WriteLogEntry ],
NameInfoDefs USING[ CheckStamp, Close,
Enumerate, GetMembers, GetRemark, MemberInfo,
NameType, RListHandle ],
PolicyDefs USING [CheckOperation, EndOperation, WaitOperation],
Process USING[ Detach ],
ProtocolDefs USING[ AppendTimestamp, maxRemarkLength, Remark ],
PupDefs USING[ ParsePupAddressConstant, PupAddress ],
ReturnDefs USING[ CopyItem, ParseBody, RejectedByMTP ],
RestartDefs USING[]--EXPORT only--,
ServerDefs USING[ DownServer, ServerUp ],
SiteCacheDefs USING[ SingleFlush, ValidateRName ],
SLDefs USING[ GetCount, SLHeader, SLEndRead, SLStartRead,
SLReadHandle, SLTransfer, SLWrite ],
Storage USING[ Free, FreeString, Node, String ],
String USING[ AppendDecimal, AppendString, EquivalentStrings ],
Time USING[ Current ];
MTPServer: MONITOR
IMPORTS FTPDefs, HeapDefs, LocalNameDefs, LogDefs,
NameInfoDefs, PolicyDefs, Process,
ProtocolDefs, PupDefs, ReturnDefs, ServerDefs, SiteCacheDefs,
SLDefs, Storage, String, Time
EXPORTS RestartDefs --PROGRAM-- =
BEGIN
RNameFromString: PROCEDURE[ s: STRING] RETURNS[BOOLEAN] =
BEGIN
RETURN[ s.length <= BodyDefs.maxRNameLength ]
END;
MailSystemObject: TYPE = RECORD[
net, host: [0..256), -- FTPServerMail assumes these are first --
credentialsOK: BOOLEAN ];
WhoIsHe: SIGNAL RETURNS[ net, host: [0..256) ] = CODE;
--communication between CreateMailSystem and Backstop --
CreateMailSystem: PROCEDURE[
filePrimitives: FTPDefs.FilePrimitives,
bufferSize: CARDINAL]
RETURNS[ mailSystem: FTPDefs.MailSystem, forwardingProvided: BOOLEAN ] =
BEGIN
real: POINTER TO MailSystemObject = Storage.Node[SIZE[MailSystemObject]];
real.credentialsOK ← FALSE;
[real.net, real.host] ← SIGNAL WhoIsHe[];
RETURN[ LOOPHOLE[real,FTPDefs.MailSystem], FALSE ]
END;
DestroyMailSystem: PROCEDURE[ mailSystem: FTPDefs.MailSystem ] =
BEGIN
Storage.Free[mailSystem];
END;
InspectCredentials: PROCEDURE[ mailSystem: FTPDefs.MailSystem,
status: FTPDefs.Status, user, password: STRING ] =
BEGIN
END;
LocateMailboxes: PROCEDURE[ mailSystem: FTPDefs.MailSystem,
localMailboxList: FTPDefs.Mailbox ] =
BEGIN
mbx: FTPDefs.Mailbox;
FOR mbx ← localMailboxList, mbx.nextMailbox WHILE mbx # NIL
DO IF mbx.located THEN LOOP;
mbx.located ← RNameFromString[mbx.mailbox]
AND SiteCacheDefs.ValidateRName[mbx.mailbox];
ENDLOOP;
END;
StageMessage: PROCEDURE[ mailSystem: FTPDefs.MailSystem,
receiveBlock: PROCEDURE[UNSPECIFIED,POINTER,CARDINAL]RETURNS[CARDINAL],
receiveBlockData: UNSPECIFIED ] =
BEGIN
ERROR;
END;
DeliverMessage: PROCEDURE[ mailSystem: FTPDefs.MailSystem,
localMailboxList: FTPDefs.Mailbox ] =
BEGIN
ERROR;
END;
DummyForward: PROCEDURE[ mailSystem: FTPDefs.MailSystem,
remoteMailboxList: FTPDefs.Mailbox ] =
BEGIN
ERROR;
END;
RetrieveMessages: PROCEDURE[ mailSystem: FTPDefs.MailSystem,
localMailbox: FTPDefs.Mailbox,
processMessage: PROCEDURE[FTPDefs.MessageInfo],
sendBlock: PROCEDURE[UNSPECIFIED,POINTER,CARDINAL],
sendBlockData: UNSPECIFIED ] =
BEGIN
ERROR FTPDefs.FTPError[unidentifiedPermanentError,
"MTP retrieval not supported"L];
END;
myMailPrimitives: FTPDefs.MailPrimitivesObject ←
[ CreateMailSystem,
DestroyMailSystem,
InspectCredentials,
LocateMailboxes,
StageMessage,
DeliverMessage,
DummyForward,
RetrieveMessages ];
-- DL expansion for MTP socket --
CreateDL: PROC[bufferSize: CARDINAL]
RETURNS[fileSystem: FTPDefs.FileSystem] =
BEGIN
RETURN[LOOPHOLE[NIL]];
END;
DestroyDL: PROC[ fileSystem: FTPDefs.FileSystem ] =
{ };
DecomposeDL: 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;
ComposeDL: 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;
InspectCredentialsDL: PROC[ fileSystem: FTPDefs.FileSystem,
status: FTPDefs.Status,
user, password: STRING ] =
{};
EnumerateDL: PROCEDURE[fileSystem: FTPDefs.FileSystem, files: STRING,
intent: FTPDefs.EnumerateFilesIntent,
processFile: PROC[UNSPECIFIED,STRING, FTPDefs.FileInfo],
processFileData: UNSPECIFIED ] =
BEGIN
fileInfoObject: FTPDefs.FileInfoObject ← [
fileType: text, byteSize: 8, byteCount: 0,
creationDate: NIL, writeDate: NIL, readDate: NIL, author: NIL ];
processFile[processFileData, files, @fileInfoObject];
END;
MyDLHandle: TYPE = RECORD[name: STRING, members: NameInfoDefs.RListHandle];
OpenDL: PROCEDURE[ fileSystem: FTPDefs.FileSystem,
file: STRING, mode: FTPDefs.Mode, fileTypePlease: BOOLEAN,
info: FTPDefs.FileInfo ]
RETURNS[ fileHandle: FTPDefs.FileHandle,
fileType: FTPDefs.FileType] =
BEGIN
IF mode = read
THEN BEGIN
myHandle: POINTER TO MyDLHandle =
Storage.Node[SIZE[MyDLHandle]];
info: NameInfoDefs.MemberInfo = NameInfoDefs.GetMembers[file];
WITH info SELECT FROM
allDown =>
FTPDefs.FTPError[fileBusy,"Registration server not available"L];
notFound =>
FTPDefs.FTPError[noSuchFile,"Distribution list not found"L];
individual =>
FTPDefs.FTPError[noSuchFile,"Not a distribution list"L];
group =>
myHandle.members ← members;
ENDCASE => ERROR;
myHandle.name ← file;
fileHandle ← LOOPHOLE[myHandle];
fileType ← text;
END
ELSE FTPDefs.FTPError[requestedAccessDenied,"Distribution lists are read-only"L];
END;
ReadDL: PROC[fileSystem: FTPDefs.FileSystem, fileHandle: FTPDefs.FileHandle,
sendBlock: PROC[UNSPECIFIED, POINTER, CARDINAL],
sendBlockData: UNSPECIFIED] =
BEGIN
myHandle: POINTER TO MyDLHandle = LOOPHOLE[fileHandle];
head: STRING = ": "L;
pad: STRING = ", "L;
tail: STRING = ";"L;
first: BOOLEAN ← TRUE;
SendDL: PROC[memberList: NameInfoDefs.RListHandle,
testRecursion: PROC[new:BodyDefs.RName]RETURNS[BOOLEAN] ] =
BEGIN
Work: PROC[member: BodyDefs.RName] RETURNS[done: BOOLEAN] =
BEGIN
DoneThis: PROC[new:BodyDefs.RName]RETURNS[BOOLEAN] =
BEGIN
-- Mechanism for eliminating recursive loops --
RETURN[ IF String.EquivalentStrings[member,new]
THEN TRUE
ELSE testRecursion[new] ]
END;
info: NameInfoDefs.MemberInfo;
skip: BOOLEAN ← FALSE;
FOR index: CARDINAL IN [0..member.length)
DO IF member[index] = '↑
THEN BEGIN -- consider group --
IF testRecursion[member]
THEN skip ← TRUE
ELSE info ← NameInfoDefs.GetMembers[member];
EXIT
END;
REPEAT
FINISHED => info ← [individual[]];
ENDLOOP;
done ← FALSE;
IF NOT skip
THEN WITH info SELECT FROM
allDown, notFound, individual =>
BEGIN
IF first
THEN first ← FALSE
ELSE sendBlock[sendBlockData, @(pad.text), pad.length];
sendBlock[sendBlockData, @(member.text), member.length];
END;
group =>
BEGIN
SendDL[members, DoneThis !
UNWIND => NameInfoDefs.Close[members] ];
NameInfoDefs.Close[members];
END;
ENDCASE => ERROR;
END;
NameInfoDefs.Enumerate[memberList, Work ];
END;
DoneTopLevel: PROC[new: BodyDefs.RName] RETURNS[ BOOLEAN ] =
-- top level of recursive loop elimination --
{ RETURN[ String.EquivalentStrings[myHandle.name,new] ] };
BEGIN
remark: ProtocolDefs.Remark= [ProtocolDefs.maxRemarkLength];
info: NameInfoDefs.NameType = NameInfoDefs.GetRemark[myHandle.name,
remark];
IF info # group
THEN String.AppendString[remark, myHandle.name];
IF remark.length > 0
THEN BEGIN
sendBlock[sendBlockData, @(remark.text), remark.length];
sendBlock[sendBlockData, @(head.text), head.length];
END;
SendDL[myHandle.members, DoneTopLevel ];
IF remark.length > 0
THEN sendBlock[sendBlockData, @(tail.text), tail.length];
END;
END;
WriteDL: PROC[fileSystem: FTPDefs.FileSystem,
fileHandle: FTPDefs.FileHandle,
receiveBlock: PROC[UNSPECIFIED,POINTER,CARDINAL]
RETURNS[CARDINAL],
receiveBlockData: UNSPECIFIED ] =
{ ERROR FTPDefs.FTPError[requestedAccessDenied, "Don't be silly!"L] };
DeleteDL: PROC[fileSystem: FTPDefs.FileSystem, file: STRING] =
{ ERROR FTPDefs.FTPError[requestedAccessDenied, "Don't be silly!"L] };
RenameDL: PROC[fileSystem: FTPDefs.FileSystem,
currentFile, newFile: STRING] =
{ ERROR FTPDefs.FTPError[requestedAccessDenied, "Don't be silly!"L] };
CloseDL: PROC[fileSystem: FTPDefs.FileSystem, fileHandle: FTPDefs.FileHandle,
aborted: BOOLEAN] =
BEGIN
myHandle: POINTER TO MyDLHandle = LOOPHOLE[fileHandle];
NameInfoDefs.Close[myHandle.members];
Storage.Free[myHandle];
END;
myDLPrimitives: FTPDefs.FilePrimitivesObject ← [
CreateFileSystem: CreateDL,
DestroyFileSystem: DestroyDL,
DecomposeFilename: DecomposeDL,
ComposeFilename: ComposeDL,
InspectCredentials: InspectCredentialsDL,
EnumerateFiles: EnumerateDL,
OpenFile: OpenDL,
ReadFile: ReadDL,
WriteFile: WriteDL,
CloseFile: CloseDL,
DeleteFile: DeleteDL,
RenameFile: RenameDL
];
-- Forwarding to foreign servers --
ftpUser: FTPDefs.FTPUser = FTPDefs.FTPCreateUser[filePrimitives:NIL,
communicationPrimitives: FTPDefs.PupCommunicationPrimitives[] ];
ForwardOutcome: TYPE = {ok, bad, tempFailure, totalFailure};
ForwardMessage: ENTRY PROCEDURE[host:STRING, SLhandle: SLDefs.SLReadHandle,
SLobj: HeapDefs.ReaderHandle,
body: HeapDefs.ObjectNumber,
slHeader: POINTER TO SLDefs.SLHeader] =
BEGIN
bodyReader: HeapDefs.ReaderHandle ← HeapDefs.HeapStartRead[body];
ended: BOOLEAN;
outcome: ForwardOutcome ← ok;
goodCount: CARDINAL ← 0;
badCount: CARDINAL ← 0;
wrongCount: CARDINAL ← 0;
badList: HeapDefs.WriterHandle ← NIL;
badString: STRING ← NIL;
wrongList: HeapDefs.WriterHandle ← NIL;
MakeBad: PROC[bad: BodyDefs.RName] =
BEGIN
SiteCacheDefs.SingleFlush[bad];
IF NameInfoDefs.CheckStamp[bad] = notFound
OR slHeader.created.time + 24 * LONG[60*60] < Time.Current[]
THEN -- Either name went bad, or name is foreign and bad,
-- or GV disagrees with MTP and we've waited long enough --
BEGIN
IF badList = NIL THEN badList ← HeapDefs.HeapStartWrite[temp];
HeapDefs.HeapWriteRName[badList, bad];
badCount ← badCount+1;
END
ELSE -- GV database disagrees with MTP host: wait until they converge,
-- or until the long-term timeout on the message. --
BEGIN
IF wrongList = NIL
THEN BEGIN
wrongList ← HeapDefs.HeapStartWrite[SLpending];
HeapDefs.HeapWriteData[wrongList,
[slHeader, SIZE[SLDefs.SLHeader]] ];
END;
HeapDefs.HeapWriteRName[wrongList, bad];
wrongCount ← wrongCount + 1;
END;
END;
CheckRecipients: PROCEDURE =
BEGIN
msg: STRING = [128];
badRecipient: BodyDefs.RName = [BodyDefs.maxRNameLength];
error: FTPDefs.RecipientError;
number: CARDINAL;
ended: BOOLEAN;
DO [number,error] ← FTPDefs.FTPIdentifyNextRejectedRecipient[
ftpUser, msg ];
IF number = 0 THEN EXIT;
goodCount ← goodCount - 1;
-- search SL for recipient --
HeapDefs.SetReaderOffset[SLobj, SIZE[SLDefs.SLHeader]];
ended ← FALSE;
UNTIL ended OR number = 0
DO ended ← HeapDefs.HeapReadRName[SLobj, badRecipient];
number ← number - 1;
ENDLOOP;
IF number = 0
THEN BEGIN
outcome ← bad;
IF badString = NIL
THEN BEGIN
badString ← Storage.String[msg.length];
String.AppendString[badString, msg];
END;
MakeBad[badRecipient];
END;
ENDLOOP;
END;
IF NOT ServerDefs.ServerUp[slHeader.server]
THEN outcome ← tempFailure
ELSE BEGIN
ENABLE FTPDefs.FTPError =>
SELECT ftpError FROM
noNameLookupResponse, connectionTimedOut, connectionClosed,
connectionRejected, noRouteToNetwork,
unidentifiedTransientError => GOTO tempFailure;
noValidRecipients => GOTO nobody;
ENDCASE -- includes: noSuchHost, unidentifiedPermanentError -- =>
BEGIN
OPEN String;
IF ftpError = noSuchHost THEN message ← "server does not exist"L;
IF message = NIL OR message.length = 0
THEN message ← "No message given"L;
IF badString # NIL THEN Storage.FreeString[badString];
badString ← Storage.String[message.length];
AppendString[badString, message];
GOTO totalFailure
END;
bodyLength: BodyDefs.ItemLength;
BEGIN
sender: BodyDefs.RName = [BodyDefs.maxRNameLength];
bodyLength ← ReturnDefs.ParseBody[reader: bodyReader,
sender: sender];
FTPDefs.FTPOpenConnection[ftpUser, host, mail, NIL];
FTPDefs.FTPSetCredentials[ftpUser, primary, sender, NIL];
END;
FTPDefs.FTPBeginDeliveryOfMessage[ftpUser];
BEGIN
recipient: BodyDefs.RName = [BodyDefs.maxRNameLength];
[ended,] ← HeapDefs.HeapReadData[SLobj, [recipient,0] ];
UNTIL ended
DO ended ← HeapDefs.HeapReadRName[SLobj, recipient];
FTPDefs.FTPSendRecipientOfMessage[ftpUser, recipient];
goodCount ← goodCount + 1;
ENDLOOP;
END;
CheckRecipients[];
ReturnDefs.CopyItem[bodyReader, bodyLength,
FTPDefs.FTPSendBlockOfMessage, ftpUser];
FTPDefs.FTPSendBlockOfMessage[ftpUser, NIL, 0]; --end of message--
CheckRecipients[];
FTPDefs.FTPEndDeliveryOfMessage[ftpUser];
EXITS
tempFailure => outcome ← tempFailure;
totalFailure =>
BEGIN
recipient: BodyDefs.RName = [BodyDefs.maxRNameLength];
ended: BOOLEAN;
HeapDefs.SetReaderOffset[SLobj,
HeapDefs.objectStart+SIZE[SLDefs.SLHeader] ];
IF badList # NIL
THEN { HeapDefs.HeapAbandonWrite[badList]; badList ← NIL };
IF wrongList # NIL
THEN { HeapDefs.HeapAbandonWrite[wrongList]; wrongList ← NIL };
badCount ← wrongCount ← goodCount ← 0;
[ended,] ← HeapDefs.HeapReadData[SLobj, [recipient,0] ];
UNTIL ended
DO ended ← HeapDefs.HeapReadRName[SLobj, recipient];
MakeBad[recipient];
ENDLOOP;
outcome ← totalFailure;
END;
nobody =>
BEGIN
badCount ← badCount + goodCount; goodCount ← 0;
END;
END;
FTPDefs.FTPCloseConnection[ftpUser];
LogForwarding[outcome, host, slHeader.created,
goodCount, badCount, wrongCount];
SELECT outcome FROM
ok, bad, totalFailure =>
BEGIN
IF badList # NIL
THEN ReturnDefs.RejectedByMTP[badList, body, host, badString];
IF wrongList # NIL
THEN SLDefs.SLWrite[body, wrongList, pending];
SLDefs.SLEndRead[SLhandle];
END;
tempFailure =>
BEGIN
IF badList # NIL THEN HeapDefs.HeapAbandonWrite[badList];
IF wrongList # NIL THEN HeapDefs.HeapAbandonWrite[wrongList];
SLDefs.SLTransfer[SLhandle, input];
ServerDefs.DownServer[slHeader.server];
END;
ENDCASE => ERROR;
IF badString # NIL THEN Storage.FreeString[badString];
HeapDefs.HeapEndRead[bodyReader];
HeapDefs.HeapEndRead[SLobj];
END;
LogForwarding: PROC[outcome: ForwardOutcome, host: STRING,
postmark: BodyDefs.Timestamp,
goodCount, badCount, wrongCount: CARDINAL] =
BEGIN
log: STRING = [140];
log.length ← 0;
String.AppendString[log, "Forwarded "L];
ProtocolDefs.AppendTimestamp[log, postmark];
String.AppendString[log, " to "L];
String.AppendString[log, host];
String.AppendString[log, ": "];
SELECT outcome FROM
ok, bad, totalFailure =>
BEGIN
String.AppendString[log, "good="L];
String.AppendDecimal[log, goodCount];
IF badCount # 0
THEN BEGIN
String.AppendString[log, ", bad="L];
String.AppendDecimal[log, badCount];
END;
IF wrongCount # 0
THEN BEGIN
String.AppendString[log, ", wrong="L];
String.AppendDecimal[log, wrongCount];
END;
END;
tempFailure =>
BEGIN
String.AppendString[log, "failed temporarily"L];
END;
ENDCASE => ERROR;
LogDefs.WriteLogEntry[log];
END;
NoRecipients: ERROR = CODE; --not caught; should not occur--
NotForeignSite: ERROR = CODE; --not caught; should not occur--
BadForeignSite: ERROR = CODE; --not caught; should not occur--
ForwardMain: PROCEDURE =
BEGIN
-- multiple instantiations of this procedure are allowed --
DO SLobj: HeapDefs.ReaderHandle ← NIL;
SLhandle: SLDefs.SLReadHandle;
bodyObj: HeapDefs.ObjectNumber;
slHeader: SLDefs.SLHeader;
[SLhandle, bodyObj, SLobj] ← SLDefs.SLStartRead[foreign];
PolicyDefs.WaitOperation[readForward];
BEGIN
-- read SL header --
ended: BOOLEAN;
used: CARDINAL;
[ended,used] ← HeapDefs.HeapReadData[SLobj,
[@slHeader,SIZE[SLDefs.SLHeader]] ];
IF ended THEN ERROR NoRecipients[];
END;
IF slHeader.server.type # foreign THEN ERROR NotForeignSite[];
WITH slHeader.server.name SELECT FROM
connect => ForwardMessage[host:value, SLhandle: SLhandle,
SLobj: SLobj, body: bodyObj,
slHeader: @slHeader];
ENDCASE => ERROR BadForeignSite[];
-- reader was closed by ForwardMessage --
PolicyDefs.EndOperation[readForward];
ENDLOOP;
END;
ForwardRestart: PROCEDURE =
BEGIN
-- on restart, must transfer everything to input, since ServerHandles
-- are no longer valid --
THROUGH [1..SLDefs.GetCount[foreign]]
DO BEGIN
handle: SLDefs.SLReadHandle;
body: HeapDefs.ObjectNumber;
SL: HeapDefs.ReaderHandle;
[handle, body, SL] ←
SLDefs.SLStartRead[foreign];
HeapDefs.HeapEndRead[SL];
SLDefs.SLTransfer[handle, input];
END;
ENDLOOP;
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 MTP server "L];
String.AppendString[localInsignia, LocalNameDefs.ReadMSName[].name ];
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] ];
PolicyDefs.EndOperation[MTP];
END;
Filter: PROCEDURE[from: STRING, purpose: FTPDefs.Purpose] =
BEGIN
IF NOT PolicyDefs.CheckOperation[MTP]
THEN BEGIN
LogDefs.WriteLogEntry["Rejected MTP connection"L];
ERROR FTPDefs.RejectThisConnection[
"Server full"L];
END;
END;
-- Initialization --
FTPDefs.FTPInitialize[];
FTPDefs.FTPCatchUnidentifiedErrors[FALSE];
ForwardRestart[];
Process.Detach[ FORK ForwardMain[] ];
STOP;
[] ← FTPDefs.FTPCreateListener[
--purpose-- mail,
--DL kludge-- @myDLPrimitives,
--mail system-- @myMailPrimitives,
--comm system-- FTPDefs.PupCommunicationPrimitives[],
--backstop-- @Backstop,
--backstopData-- 0,
--filter-- Filter];
END.