-- Transport Mechanism Mail Server - server for MTP --

-- [Indigo]<Grapevine>MS>MTPServer.mesa

-- HGM, 13-Nov-84  1:51:24
-- Randy Gobbel		29-May-81 14:46:16 --
-- Andrew Birrell	29-Dec-81 15:00:20 --
-- Mark Johnson		May 28, 1981  2:39 PM --
-- Brenda Hankins	10-Aug-84 15:41:12  	Klamath update (replace STOP with 2 init procs)

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 =

  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 --


  InitMTPServer1: PUBLIC PROCEDURE =
    BEGIN
    FTPDefs.FTPInitialize[];
    FTPDefs.FTPCatchUnidentifiedErrors[FALSE];

    ForwardRestart[];
    Process.Detach[FORK ForwardMain[]];
    END;

  InitMTPServer2: PUBLIC PROCEDURE =
    BEGIN
    [] ← FTPDefs.FTPCreateListener[
      --purpose-- mail,
      --DL kludge-- @myDLPrimitives,
      --mail system-- @myMailPrimitives,
      --comm system-- FTPDefs.PupCommunicationPrimitives[],
      --backstop-- @Backstop,
      --backstopData-- 0,
      --filter-- Filter];
    END;

  END.