UNIT Address;

{$i platform.inc}

{ This unit contains the address extrapolation and verification routines }
{ that are used by the input mechanisms to check an address and get a    }
{ DestRecord in return. Bounce and nodelist lookup is performed here as  }
{ well and a reason plus return address is returned. Different routines  }
{ for FTN and RFC.                                                       }

INTERFACE

USES Database,
     Msgs;

PROCEDURE Address_AddRFCRaw (InAddress : STRING; DestType : DestTypes; Note : BOOLEAN; ByFiltr : BOOLEAN);
FUNCTION  Address_FoundRFCAddress (Address : STRING) : BOOLEAN;
PROCEDURE Address_CheckRFCRaw;
FUNCTION  Address_HasNoneFilterRfcDest : BOOLEAN;

PROCEDURE Address_SelectNewsgroups;
PROCEDURE Address_AddAreaToAreaRecNrsList (RecNr : AreaBaseRecordNrType);
PROCEDURE Address_AddNews;
PROCEDURE Address_AddEcho;

PROCEDURE Address_AddFTN (ToUser : STRING; ToAka : FidoAddrType; Note : BOOLEAN; ByFiltr : BOOLEAN);
PROCEDURE Address_AddRemoteGW (ToUser : STRING; ToAka : FidoAddrType; ToEmail : STRING; Note : BOOLEAN; ByFiltr : BOOLEAN);
PROCEDURE Address_AddCopyAreaDest (AreaName : STRING; Note : BOOLEAN);
PROCEDURE Address_AddMoveAreaDest (AreaName : STRING; Note : BOOLEAN);

PROCEDURE Address_AddAdmin;

PROCEDURE Address_ChangeRfc2FtnSide;


IMPLEMENTATION

USES Ramon,
     Logs,
     Cfg,
     Globals,    { ForceNoImport }
     AreaBase,   { AutoCreateArea }
     Routing,    { CheckFilter (newsgroup names) }
     UserBase,   { PacketUserData }
     Usenet,
     UseAdres,
     Trans,      { FtnizeUserName }
     Fido;       { Fido2Str }

{--------------------------------------------------------------------------}
{ AddDestRecord                                                            }
{                                                                          }
{ This routine adds a DestRecord to the chain, the start of which pointed  }
{ to by Msg.FirstDest. The record is added at the end of the chain, which  }
{ can be time consuming for larger lists, but does keep the delivery order }
{ in tact.                                                                 }
{                                                                          }
PROCEDURE AddDestRecord (DestPtr : DestRecordPtr);

VAR FindPtr : DestRecordPtr;

BEGIN
     { add to the end of the chain (time consuming, but keeps order) }
     FindPtr:=Msg.FirstDest;
     IF (FindPtr = NIL) THEN
        Msg.FirstDest:=DestPtr
     ELSE BEGIN
          WHILE (FindPtr^.NextDest <> NIL) DO
                FindPtr:=FindPtr^.NextDest;

          FindPtr^.NextDest:=DestPtr;
     END;
END;


{--------------------------------------------------------------------------}
{ Address_AddRFCRaw                                                        }
{                                                                          }
{ This routine creates a new DestRecord, adds it to the start of the chain }
{ and fills in the address and type destRFCRaw to indicate that the        }
{ address has not been completed/looked up yet and not been verified and   }
{ extrapolated yet.                                                        }
{ If the inbound address is too long, then it is bounced at once.          }
{                                                                          }
PROCEDURE Address_AddRFCRaw (InAddress : STRING; DestType : DestTypes; Note : BOOLEAN; ByFiltr : BOOLEAN);

VAR DestPtr : DestRecordPtr;

BEGIN
     IF (Pos ('@@',InAddress) > 0) THEN
     BEGIN
          LogMessage (liGeneral,'Ignoring invalid e-mail address: "'+InAddress+'"');
          Exit;
     END;

     IF (Pos (' ',InAddress) > 0) THEN
     BEGIN
          { In some weird cases, we have an address with a comment at this }
          { point (from SMTP .WRK files, for example).  Accept these       }
          { addresses.  e.g. tibbsa@softhome.net (Anthony Tibbs)           }

          IF (Pos ('(',InAddress) > 0) THEN
          BEGIN
               LogMessage (liDebug, '[Address_AddRFCRaw] Trimming comment from address "'+InAddress+'"');
               Delete (InAddress, Pos (' ', InAddress), Length (InAddress));
               LogMessage (liDebug, '                    New: '+InAddress);
          END ELSE
          BEGIN
               LogMessage (liGeneral,'Ignoring invalid e-mail address (contains space): "'+InAddress+'"');
               Exit;     { ## EXIT ## }
          END;
     END;

     IF (Length (InAddress) > MaxLenDomain) THEN
     BEGIN
          { too long, must bounce }
          Exit;
     END;

     { search for and ignore duplicates }
     DestPtr:=Msg.FirstDest;
     WHILE (DestPtr <> NIL) DO
     BEGIN
          { check for duplicate }
          IF (DestPtr^.ToType = DestType) AND (DestPtr^.To_U = InAddress) THEN
          BEGIN
               IF Config.LogDebug THEN
                  LogMessage (liTrivialLog,'Discarding duplicate: '+InAddress);

               Exit;    { ## EXIT ## }
          END;

          DestPtr:=DestPtr^.NextDest;
     END;

     GetMem (DestPtr,SizeOf (DestRecord));
     {$IFDEF LogGetMem} LogGetMem (DestPtr,SizeOf (DestRecord),'DestRecord'); {$ENDIF}

     FillChar (DestPtr^,SizeOf (DestRecord),0);

     WITH DestPtr^ DO
     BEGIN
          Status:=destRFCRaw;
          ByFilter:=ByFiltr;
          To_U:=InAddress;
          ToType:=DestType; { to/cc/bcc }
          AddNote:=Note;
          NextDest:=NIL;
     END; { with }

     AddDestRecord (DestPtr);
END;


{--------------------------------------------------------------------------}
{ Address_FoundRFCAddress                                                  }
{                                                                          }
{ This routine is called when the header scan has turned up with an        }
{ address that is local to us. We scan the Dest list for complete          }
{ addresses and complete where ever possible.                              }
{ If we have nothing to complete, then TRUE is returned to indicate that   }
{ the search can be stopped.                                               }
{                                                                          }
FUNCTION Address_FoundRFCAddress (Address : STRING) : BOOLEAN;

VAR DestPtr : DestRecordPtr;

BEGIN
     Address_FoundRFCAddress:=TRUE; { can stop searching after this call }

     DestPtr:=Msg.FirstDest;

     WHILE (DestPtr <> NIL) DO
     BEGIN
          IF (Destptr^.Status = destRFCRaw) AND
             (Pos ('@',DestPtr^.To_U) = 0) AND
             (Pos ('!',DestPtr^.To_U) = 0) THEN
          BEGIN
               IF CaselessStartMatch (Address,DestPtr^.To_U) THEN
               BEGIN
                    { found it! }
                    IF Config.LogXFix THEN
                    BEGIN
                         LogMessage (liTrivial,'Found complete recipient address in headers:');
                         LogExtraMessage (DestPtr^.To_U+' -> '+Address);
                    END;

                    DestPtr^.To_U:=Address;
                    DestPtr^.Status:=destRFC; { no need to check this one anymore }
               END ELSE
                   Address_FoundRFCAddress:=FALSE; { keep searching }
          END;

          DestPtr:=DestPtr^.NextDest;
     END; { while }
END;


{--------------------------------------------------------------------------}
{ Address_HasNoneFilterRfcDest                                             }
{                                                                          }
{ This routine returns TRUE if it is apparent that the message must be     }
{ gated to RFC, apart from the ones added by the Filters. This is used     }
{ when an FTN message has been found that results in something that has to }
{ pass the gateway. We then check whether the source is allowed to use the }
{ gateway and use this routine to see what it addressed. Anything added    }
{ by the internal configuration (like Filters) are ignored since it is not }
{ the poster's fault if those messages cannot be gated.                    }
{                                                                          }
FUNCTION Address_HasNoneFilterRfcDest : BOOLEAN;

VAR DestPtr : DestRecordPtr;

BEGIN
     DestPtr:=Msg.FirstDest;

     WHILE (DestPtr <> NIL) DO
     BEGIN
          IF (NOT DestPtr^.ByFilter) AND
             (Destptr^.Status IN [destRFCRaw,destRFC]) THEN
          BEGIN
               Address_HasNoneFilterRfcDest:=TRUE; { found one }
               Exit;
          END;

          DestPtr:=DestPtr^.NextDest;
     END; { while }

     Address_HasNoneFilterRfcDest:=FALSE; { not found }
END;


{--------------------------------------------------------------------------}
{ Address_CheckRFCRaw                                                      }
{                                                                          }
{ This routine checks all RFC addresses with the status destRFCRaw. If it  }
{ is not complete, then the domain is looked up in the headers (when       }
{ configured to do so) or the first domain address is added. After that,   }
{ the address is run through the MAP-RFC and Filters and the DestRecord    }
{ is changed according to the result.                                      }
{                                                                          }
PROCEDURE Address_CheckRFCRaw;

VAR DestPtr : DestRecordPtr;

BEGIN
     {## comments above says MAP-RFC and Filters... don't see any}
     DestPtr:=Msg.FirstDest;

     WHILE (DestPtr <> NIL) DO
     BEGIN
          IF (DestPtr^.Status = destRFCRaw) THEN
          BEGIN
               { final option in address completion }
               IF (Pos ('@',DestPtr^.To_U) = 0) AND
                  (Pos ('!',DestPtr^.To_U) = 0) THEN
               BEGIN
                    IF Config.LogXFix THEN
                    BEGIN
                         IF (Config.RMailCorrect <> ctAddDomain) THEN
                            LogMessage (liGeneral,'Incomplete recipient address and nothing found in headers');
                         LogExtraMessage ('Adding first system domain: '+DestPtr^.To_U+
                                          ' -> '+DestPtr^.To_U+'@'+Config.Domains[1]);
                    END;

                    DestPtr^.To_U:=DestPtr^.To_U+'@'+Config.Domains[1];
               END;

               DestPtr^.Status:=destRFC;
          END;

          DestPtr:=DestPtr^.NextDest;
     END; { while }
END;


{==========================================================================}
{                           NEWSGROUP PROCESSING                           }
{==========================================================================}

CONST MAX_NOT_ENTRIES = 50;

VAR AddNewsState    : BYTE;    { 0 = Find "Newsgroups:" }
                               { 1 = Process continued headers }
    AddNewsLine     : STRING;  { previous part }
    NewsCount       : BYTE;    { limited to Config.MaxXPostCount }
    NotCount        : BYTE;
    NewsNot         : ARRAY[1..MAX_NOT_ENTRIES] OF StringPtr;
    NewsSenderRecNr : UserBaseRecordNrType;
    NewsSenderRec   : UserBaseRecord;
    FoundMsgBase    : BOOLEAN;

{--------------------------------------------------------------------------}
{ AddNews_ProcessNewsgroup                                                 }
{                                                                          }
{ This routine is called from AddNews_ProcessHeader for each extracted     }
{ newsgroup name. Here we verify the existance of the newgroup and create  }
{ it if the user is allowed to do so. The access rights are then verified  }
{ (read-only check) and if everything is OK, the area is added to the      }
{ Msg.AreaRecNrs[] list.                                                   }
{                                                                          }
PROCEDURE AddNews_ProcessNewsgroup;

    PROCEDURE AddToNotList;
    BEGIN
         IF (NotCount < MAX_NOT_ENTRIES) THEN
         BEGIN
              Inc (NotCount);
              GetMem (NewsNot[NotCount],Length (AddNewsLine)+1);
              NewsNot[NotCount]^:=AddNewsLine;
         END ELSE
         BEGIN
              AddNewsLine:='<rest not remembered>';
              FreeMem (NewsNot[NotCount],Length (NewsNot[NotCount]^)+1);
              GetMem (NewsNot[NotCount],Length (AddNewsLine)+1);
              NewsNot[NotCount]^:=AddNewsLine;
         END;
    END;

VAR AreaRecNr : AreaBaseRecordNrType;
    NewPath   : STRING;
    Lp        : BYTE;

BEGIN
     {LogExtraMessage ('  "'+AddNewsLine+'"');}

     { try to find the area }
     AreaRecNr:=GetAreaBaseRecordNrByAreaName_U (AddNewsLine);

     { if not found, then try to create it }
     IF (AreaRecNr = NILRecordNr) AND
        (NewsSenderRecNr <> NILRecordNr) AND
        (NewsSenderRec.AllowCreate) AND
        CheckFilter (AddNewsLine,NewPath) THEN
     BEGIN
          { unknown area but the supplying user is allowed to create areas }
          { and the filter let it through as well, so create it.           }
          LogMessage (liGeneral,'Auto-Created area: '+AddNewsLine);
          AreaCreatorUserBaseRecNr:=NewsSenderRecNr;
          AreaRecNr:=AutoCreateArea (AddNewsLine,NewPath);
     END;

     { if AreaRecNr still equals NILRecordNr then the user was either not }
     { allowed to create the area, or the filter didn't allow it.         }

     IF (AreaRecNr = NILRecordNr) THEN
     BEGIN
          IF Config.LogDebug THEN
             LogExtraMessage ('Unknown/Rejected '+AddNewsLine);

          AddToNotList;
          Exit;
     END;

     { known newsgroup }

     { verify that the user is allowed to post in this newsgroup,     }
     { check 1: user must be subscribed to the area.                  }
     { check 2: user must have write access for one of the groups the }
     {          area and the user are both in.                        }

     ReadAreaBaseRecord (AreaRecNr,AreaData);

     IF (NewsSenderRecNr <> NILRecordNr{System Sends}) THEN
     BEGIN
          { check 1 }
          IF (NOT TestIfUserIsInAreaRec_UserList (AreaData.UserList,UserDataRecNr)) THEN
          BEGIN
               LogMessage (liFatal,'Sender is not subscribed to '+AddNewsLine);
               AddToNotList;
               Exit;
          END;

          { check 2 }

          { the user must either be in none of the groups the area is in }
          { (can't remember why), or the user and the area must both be  }
          { in one of the read/write groups.                             }

          IF TestIfGroupCommon (PacketUserData.Groups,AreaData.IsInGroups) THEN
             IF (NOT TestIfGroupCommon3 (PacketUserData.Groups,AreaData.IsInGroups,ReadWriteGroupsFilter)) THEN
             BEGIN
                  { user does not have write access }
                  IF Config.LogRODeny THEN
                     LogMessage (liFatal,'No permission to post in '+AddNewsLine);

                  AddToNotList;
                  Exit;
             END;
     END;

     { user IS allowed to post in this newsgroup }
     FOR Lp:=1 TO MAX_AREA_CROSS_POSTS DO
         IF (Msg.AreaRecNrs[Lp] = AreaRecNr) THEN
            Exit;  { already there - happens with CopyHeaders }

     FOR Lp:=1 TO MAX_AREA_CROSS_POSTS DO
         IF (Msg.AreaRecNrs[Lp] = NILRecordNr) THEN
         BEGIN
              Inc (NewsCount);
              Msg.AreaRecNrs[Lp]:=AreaRecNr;
              IF (AreaData.FidoMsgStyle <> NoneType) THEN
                 FoundMsgBase:=TRUE;
              Exit;
         END;

     { too many cross-posts in known areas }
     LogMessage (liFatal,'Too many areas, cannot add '+AddNewsLine);
     AddToNotList;
END;


{--------------------------------------------------------------------------}
{ AddNews_ProcessHeader                                                    }
{                                                                          }
{ This routine first searches for the Newsgroups: header and then starts   }
{ extracting newsgroup names from that header and lines with more          }
{ newsgroup names. FALSE is returned during the search until the           }
{ Newsgroups: and following lines have been processed.                     }
{ Each extracted newsgroup is checked for access restrictions for this     }
{ user.                                                                    }
{                                                                          }
FUNCTION AddNews_ProcessHeader (VAR Regel : STRING) : BOOLEAN; FAR;

VAR Line : STRING;
    P    : BYTE;

BEGIN
     AddNews_ProcessHeader:=FALSE; { continue feeding me lines }

     IF (AddNewsState = 0) THEN
     BEGIN
          { search for the Newsgroups: header }
          IF CaselessStartMatch (Regel,'Newsgroups: ') THEN
          BEGIN
               Line:=Copy (Regel,12{keep space},255);
               AddNewsState:=1; { process newsgroups header }
          END ELSE
              { not interesting }
              Exit; { ## EXIT ## }
     END ELSE
         IF (AddNewsState = 1) AND (Pos (':',Regel) > 0) THEN
         BEGIN
              { found start of a new header so stop feeding me }
              AddNews_ProcessHeader:=TRUE;
              Exit; { ## EXIT ## }
         END ELSE
             Line:=Regel; { don't change original }

     { is this the end of the newsgroups header? }
     P:=Pos (#13,Line);
     IF (P > 0) THEN
        Line:=Copy (Line,1,P-1)+',';

     { remove all tabs and spaces }
     REPEAT
           P:=Pos (' ',Line);
           IF (P > 0) THEN
              Delete (Line,P,1);
     UNTIL (P = 0);

     REPEAT
           P:=Pos (#9,Line);
           IF (P > 0) THEN
              Delete (Line,P,1);
     UNTIL (P = 0);

     { Now extract all newsgroup names from Line. Each name is always }
     { followed by a comma, even the last one on the list because the }
     { #13 has been replaced by a comma above. Cut-off names are kept }
     { in AddNewsLine until the next round.                           }

     WHILE (Line <> '') DO
     BEGIN
          P:=Pos (',',Line);

          IF (P > 0) THEN
          BEGIN
               {## why this?}
               AddNewsLine:=AddNewsLine+Copy (Line,1,P-1);
               Delete (Line,1,P);
          END ELSE
          BEGIN
               AddNewsLine:=Line;
               Exit;
          END;

          IF (AddNewsLine <> ''{xxx,,yyy" situation}) THEN
             AddNews_ProcessNewsgroup;

          AddNewsLine:=''; { ready for the next round }
     END;
END;


{--------------------------------------------------------------------------}
{ Address_SelectNewsgroups                                                 }
{                                                                          }
{ This routine must be called before a News article can be distributed. It }
{ scans the Newsgroups: header and extract all the newsgroups names. Each  }
{ is then verified for read-only access.                                   }
{ Note: this routine is used by Translate_Echomail2News as well when a     }
{       Newsgroups: header was copied from the message body.               }
{                                                                          }
{##Moderator handling can be added here.                                   }
{                                                                          }
{--OLD:                                                                    }
{ Requirement: PacketUserData has to be loaded and of type _U or _B and    }
{              UserDataRecNr and AreaCreatorUserBaseRecNr must contain the }
{              UserBase record number of the same record. This because of  }
{              read-only access checking and new area creation.            }
{                                                                          }
{                                                                          }
PROCEDURE Address_SelectNewsgroups;

    PROCEDURE CleanNotList;
    BEGIN
         WHILE (NotCount > 0) DO
         BEGIN
              FreeMem (NewsNot[NotCount],Length (NewsNot[NotCount]^)+1);
              Dec (NotCount);
         END;
    END;

    PROCEDURE CleanAreas;

    VAR Lp : BYTE;

    BEGIN
         { we can do this and still not disturb the Filters because   }
         { this routine is used right after the message has been read }
         { from the job. No filters have been checked yet.            }
         { We have to clean it to avoid distribution, since AddNews   }
         { now is a separate call.                                    }
         FOR Lp:=1 TO MAX_AREA_CROSS_POSTS DO
             Msg.AreaRecNrs[Lp]:=NILRecordNr;
    END;

{Address_SelectNewsgroups}

VAR Lp : BYTE;

BEGIN
     NewsSenderRecNr:=Msg.DeliveringUserRecNr;
     IF (NewsSenderRecNr <> NILRecordNr) THEN
        ReadUserBaseRecord (NewsSenderRecNr,NewsSenderRec);

     NewsCount:=0; { for Config.MaxXPostNewsgroups checking }
     FOR Lp:=1 TO MAX_AREA_CROSS_POSTS DO
         IF (Msg.AreaRecNrs[Lp] <> NILRecordNr) THEN
            Inc (NewsCount);

     NotCount:=0;
     FoundMsgBase:=FALSE;

     AddNewsState:=0; { search for Newsgroups: }
     AddNewsLine:='';

     MsgsForEach (Msg.HeaderTop_U,AddNews_ProcessHeader);

     { --- check results and decide whether to accept or not }

     IF (NewsCount > Config.MaxXPostNewsgroups) THEN
     BEGIN
          LogMessage (liFatal,'Ignoring over-cross-posted article ('+
                      Byte2String (NewsCount)+' > '+Byte2String (Config.MaxXPostNewsgroups)+')');
          CleanNotList;
          CleanAreas;
          Exit;
     END;

     IF (NewsCount = 0) THEN
     BEGIN
          { geen areas kunnen vinden, of we kennen al deze areas niet, }
          { of de user was er niet op aangesloten, of de area was      }
          { read-only voor deze user.                                  }

          IF Config.LogErrNewsgroups THEN
          BEGIN
               LogMessage (liFatal,'Article in the following newsgroups not processed:');
               LogExtraMessage ('('+Msg.MessageID_U+')');
               FOR Lp:=1 TO NotCount DO
                   LogExtraMessage ('  '+NewsNot[Lp]^);
          END;

          CleanNotList;
          CleanAreas; { probably empty anyway }
          Exit;
     END;

     { following check does not hurt during Translate_Echomail2News }
     { because of protection boolean in the Msg structure.          }
     IF FoundMsgBase AND (NOT ForceNoImport) THEN
        Address_AddEcho; { force translation for import }

     IF Config.LogDebug THEN
        LogMessage (liTrivial,'Accepted '+Byte2String (NewsCount)+
                    ' of '+Byte2String (NewsCount+NotCount)+
                    ' areas article is posted in');

     CleanNotList;
END;


{--------------------------------------------------------------------------}
{ Address_AddNews                                                          }
{                                                                          }
{ This routine adds a destination record with the simple type destNews to  }
{ the distribution list. This is used when a news article is ready for     }
{ distribution, but also during echomail distribution to indicate that the }
{ message must be gated to news.                                           }
{                                                                          }
PROCEDURE Address_AddNews;

VAR DestPtr : DestRecordPtr;

BEGIN
     { already been here? }
     IF Msg.AddedNews THEN
        Exit;

     { --- found any areas? }

     { is normal in case no newsgroup was allowed through
     IF (Msg.AreaRecNrs[1] = NILRecordNr) THEN
     BEGIN
          LogMessage (liReport,'[AddNews] No areas listed for distribution!');
          Exit;
     END;
     }

     { --- add destNews type }

     GetMem (DestPtr,SizeOf (DestRecord));
     {$IFDEF LogGetMem} LogGetMem (DestPtr,SizeOf (DestRecord),'DestRecord'); {$ENDIF}
     FillChar (DestPtr^,SizeOf (DestRecord),0);

     DestPtr^.Status:=destNews;

     AddDestRecord (DestPtr);

     Msg.AddedNews:=TRUE;
END;


{--------------------------------------------------------------------------}
{ Address_AddEcho                                                          }
{                                                                          }
{ This routine adds a destination record with the simple type destEcho to  }
{ the distribution list. This is used on a new echomail message and by the }
{ news distribution when an echomail user has been detected.               }
{                                                                          }
PROCEDURE Address_AddEcho;

VAR DestPtr : DestRecordPtr;

BEGIN
     IF Msg.AddedEcho THEN
        Exit; { already distributed or added }

     IF (Msg.AreaRecNrs[1] = NILRecordNr) THEN
     BEGIN
          LogMessage (liReport,'[AddEcho] No areas listed for distribution!');
          Exit;
     END;

     GetMem (DestPtr,SizeOf (DestRecord));
     {$IFDEF LogGetMem} LogGetMem (DestPtr,SizeOf (DestRecord),'DestRecord'); {$ENDIF}
     FillChar (DestPtr^,SizeOf (DestRecord),0);

     DestPtr^.Status:=destEchomail;

     AddDestRecord (DestPtr);

     Msg.AddedEcho:=TRUE;
END;



{--------------------------------------------------------------------------}
{ Address_AddFTN                                                           }
{                                                                          }
{ This routine extrapolates the address (there is normally only one FTN    }
{ address to add, so it will be the final destination at once) and adds an }
{ appropriate DestRecord for it.                                           }
{                                                                          }
PROCEDURE Address_AddFTN (ToUser : STRING; ToAka : FidoAddrType; Note : BOOLEAN; ByFiltr : BOOLEAN);

VAR DestPtr : DestRecordPtr;

BEGIN
     GetMem (DestPtr,SizeOf (DestRecord));
     {$IFDEF LogGetMem} LogGetMem (DestPtr,SizeOf (DestRecord),'DestRecord (ftn)'); {$ENDIF}

     FillChar (DestPtr^,SizeOf (DestRecord),0);

     WITH DestPtr^ DO
     BEGIN
          Status:=destFTN;
          ByFilter:=ByFiltr;
          ToUser_F:=ToUser;
          ToAddr_F:=ToAka;
          ToType:=destTo;
          AddNote:=Note;
     END; { with }

     AddDestRecord (DestPtr);
END;


{--------------------------------------------------------------------------}
{ Address_AddRemoteGW                                                      }
{                                                                          }
{ This routine adds the address as-is by creating a DestRecord for it and  }
{ adding it to the chain of destinations for the top most Msg structure.   }
{                                                                          }
PROCEDURE Address_AddRemoteGW (ToUser : STRING; ToAka : FidoAddrType; ToEmail : STRING; Note : BOOLEAN; ByFiltr : BOOLEAN);

VAR DestPtr : DestRecordPtr;

BEGIN
     GetMem (DestPtr,SizeOf (DestRecord));
     {$IFDEF LogGetMem} LogGetMem (DestPtr,SizeOf (DestRecord),'DestRecord (remote gw)'); {$ENDIF}

     FillChar (DestPtr^,SizeOf (DestRecord),0);

     WITH DestPtr^ DO
     BEGIN
          Status:=destRemoteGW;
          ByFilter:=ByFiltr;
          ToUser_F:=ToUser;
          ToAddr_F:=ToAka;
          To_U:=ToEMail;
          ToType:=destTo;
          AddNote:=Note;
     END; { with }

     AddDestRecord (DestPtr);
END;


{--------------------------------------------------------------------------}
{ AddAreaDestRecord                                                        }
{                                                                          }
{ This routine is used to add a destNetArea, destEchoArea or destNewsArea  }
{ DestRecord.                                                              }
{                                                                          }
PROCEDURE AddAreaDestRecord (DestType : DestStatus; RecNr : AreaBaseRecordNrType; Note : BOOLEAN);

VAR DestPtr : DestRecordPtr;

BEGIN
     GetMem (DestPtr,SizeOf (DestRecord));
     {$IFDEF LogGetMem} LogGetMem (DestPtr,SizeOf (DestRecord),'DestRecord (AddAreaDestRecord)'); {$ENDIF}
     FillChar (DestPtr^,SizeOf (DestRecord),0);

     WITH DestPtr^ DO
     BEGIN
          Status:=DestType;
          AreaRecNr:=RecNr;
          AddNote:=Note;
     END; { with }

     AddDestRecord (DestPtr);
END;


{--------------------------------------------------------------------------}
{ Address_AddAreaToAreaRecNrsList                                          }
{                                                                          }
{ This routine adds the give RecNr to the Msg.AreaRecNrs array, complains  }
{ when this list is full and prevents duplicates.                          }
{                                                                          }
PROCEDURE Address_AddAreaToAreaRecNrsList (RecNr : AreaBaseRecordNrType);

VAR Lp : 1..MAX_AREA_CROSS_POSTS;

BEGIN
     IF (RecNr = NILRecordNr) THEN
     BEGIN
          LogMessage (liReport,'Address_AddAreaToAreaRecNrsList: RecNr is NILRecordNr!');
          Exit;
     END;

     { news or echomail: add the area to the distribution list }
     {                   or it won't be distributed at all!    }

     { check for duplicates - have to check from first to last slot! }
     FOR Lp:=1 TO MAX_AREA_CROSS_POSTS DO
         IF (Msg.AreaRecNrs[Lp] = RecNr) THEN
            Exit;  { already there }

     { find free slot and insert }
     FOR Lp:=1 TO MAX_AREA_CROSS_POSTS DO
         IF (Msg.AreaRecNrs[Lp] = NILRecordNr) THEN
         BEGIN
              Msg.AreaRecNrs[Lp]:=RecNr;
              Exit;
         END;

     LogMessage (liReport,'No free slot in AreaRecNrs!');
END;


{--------------------------------------------------------------------------}
{ Address_AddMoveAreaDest                                                  }
{                                                                          }
{ When a move or copy filter triggers, this routine is used to add a       }
{ DestRecord for that area.                                                }
{                                                                          }
PROCEDURE Address_AddMoveAreaDest (AreaName : STRING; Note : BOOLEAN);

VAR DestPtr : DestRecordPtr;
    RecNr   : AreaBaseRecordNrType;
    AreaRec : AreaBaseRecord;
    Needs   : BYTE;

BEGIN
     RecNr:=GetAreaBaseRecordNrByAreaName (AreaName);

     IF (RecNr = NILRecordNr) THEN
     BEGIN
          LogMessage (liConfig,'Cannot find Filter destination area "'+AreaName+'"');
          Exit;
     END;

     { work out whether there are echomail or news subscribers and   }
     { whether there is a message base. If none, then ignore the     }
     { message, otherwise make sure it is translated and distributed }
     { in the appropriate formats.                                   }
     Needs:=GetAreaSubscriberTypes (RecNr);

     IF (Needs = 0) THEN
     BEGIN
          { Echomail type area, no message base, no subscribers }
          LogMessage (liConfig,'Area has no subscribers or message base; ignoring');
          Exit;
     END;

     IF (Needs = 4) THEN
     BEGIN
          { move to Netmail type area }
          AddAreaDestRecord (destNetArea,RecNr,Note);
          Exit;
     END;

     { needs 1,2 and 3 left }

     Address_AddAreaToAreaRecNrsList (RecNr);

     IF ((Needs AND 1) <> 0) THEN
        Address_AddEcho;

     IF ((Needs AND 2) <> 0) THEN
        Address_AddNews;
END;


{--------------------------------------------------------------------------}
{ Address_AddCopyAreaDest                                                  }
{                                                                          }
{ When a copy filter triggers or the list server wants to distribute in an }
{ area then this routine is used to add a DestRecord for that area.        }
{                                                                          }
PROCEDURE Address_AddCopyAreaDest (AreaName : STRING; Note : BOOLEAN);

VAR RecNr   : AreaBaseRecordNrType;
    AreaRec : AreaBaseRecord;
    Needs   : BYTE;

BEGIN
     RecNr:=GetAreaBaseRecordNrByAreaName (AreaName);

     IF (RecNr = NILRecordNr) THEN
     BEGIN
          LogMessage (liConfig,'Cannot find Filter destination area "'+AreaName+'"');
          Exit;
     END;

     { work out whether there are echomail or news subscribers and   }
     { whether there is a message base. If none, then ignore the     }
     { message, otherwise make sure it is translated and distributed }
     { in the appropriate formats.                                   }
     Needs:=GetAreaSubscriberTypes (RecNr);

     IF (Needs = 0) THEN
     BEGIN
          { Echomail type area, no message base, no subscribers }
          LogMessage (liConfig,'Area has no subscribers or message base; ignoring');
          Exit;
     END;

     IF (Needs = 4) THEN
     BEGIN
          AddAreaDestRecord (destNetArea,RecNr,Note);
          Exit;
     END;

     IF ((Needs AND 1) <> 0) THEN
        AddAreaDestRecord (destEchoArea,RecNr,Note);

     IF ((Needs AND 2) <> 0) THEN
        AddAreaDestRecord (destNewsArea,RecNr,Note);
END;


{--------------------------------------------------------------------------}
{ Address_AddAdmin                                                         }
{                                                                          }
{ This routine adds the address of the administrator to the message.       }
{ Note must be used in the future to have an argument inform the admin     }
{ about what this s/he gets is all about.                                  }
{                                                                          }
PROCEDURE Address_AddAdmin;
BEGIN
     CASE Config.AdminAddrType OF
          0 : {none};
          1 : Address_AddFTN (Config.AdminFidoName,Config.AdminFidoAddr,FALSE,FALSE);
          2 : Address_AddRFCRaw (Config.AdminUUCPAddr,destTo,FALSE,FALSE);
     END; { case }
END;


{--------------------------------------------------------------------------}
{ Address_ChangeRfc2FtnSide                                                }
{                                                                          }
{ This routine is used when a netmail is process that is addressed to the  }
{ local system and might have resulted in RFC addresses (To:, GW-TO, etc). }
{ These RFC addresses might actually become FTN after processing, so we    }
{ check for that here and might actually avoid a translation to RFC        }
{ format. This is required when a messages from a mailing list has an RFC  }
{ side reply address with the full name in the From: field, but a          }
{ REPLYTO/ALSO to the list itself. This then comes back as a message to    }
{ the gateway, but this must be kept on the FTN side to make sure the      }
{ subscriber check works.                                                  }
{ We only check for mailing lists for the moment but can extend this in    }
{ the future with other addresses that would anyway require gating back to }
{ FTN format, like local users.                                            }
{ The code here looks very much like Deliver.pas:HandleRfcDest             }
{                                                                          }
PROCEDURE Address_ChangeRfc2FtnSide;

VAR DestPtr      : DestRecordPtr;
    HulpAddrType : EForm;
    HulpUser,
    HulpDomain   : STRING;

BEGIN
     IF Msg.IsListDist THEN
        Exit;

     DestPtr:=Msg.FirstDest;
     WHILE (DestPtr <> NIL) DO
     BEGIN
          IF (DestPtr^.Status = destRFC) THEN
          BEGIN
               HulpAddrType:=UseAdresParse (DestPtr^.To_U,HulpDomain,HulpUser);

               IF UsenetIsOurDomain (HulpDomain) THEN
                  HulpAddrType:=LOKAAL;

               IF (HulpAddrType = LOKAAL) THEN
               BEGIN
                    DestPtr^.ToUser_F:=FtnizeUserName (HulpUser);
                    DestPtr^.ToAddr_F:=Config.NodeNrs[Config.GatewayAKA];
                    DestPtr^.Status:=destFTN;

                    IF Config.LogDebug THEN
                    BEGIN
                         LogMessage (liTrivial,'Changing RFC destination to FTN');
                         LogExtraMessage (DestPtr^.To_U+' -> "'+DestPtr^.ToUser_F+'" at '+Fido2Str (DestPtr^.ToAddr_F));
                    END;
               END;
          END;

          (* could do..
          IF (DestPtr^.Status = destRemoteGW) THEN
          BEGIN
               HulpAddrType:=UseAdresParse (DestPtr^.To_U,HulpDomain,HulpUser);

               IF UsenetIsOurDomain (HulpDomain) THEN
                  HulpAddrType:=LOKAAL;

               IF (HulpAddrType = LOKAAL) THEN
               BEGIN
                    DestPtr^.ToUser_F:=FtnizeUserName (HulpUser);
                    DestPtr^.ToAddr_F:=Config.NodeNrs[Config.GatewayAKA];
                    DestPtr^.Status:=destFTN;

                    IF Config.LogDebug THEN
                    BEGIN
                         LogMessage (liTrivial,'Changing RemoteGW destination to FTN');
                         LogExtraMessage (DestPtr^.To_U+' -> "'+DestPtr^.ToUser_F+'" at '+Fido2Str (DestPtr^.ToAddr_F));
                    END;
               END;
          END;
          *)

          DestPtr:=DestPtr^.NextDest;
     END; { while }
END;


END.
