Bind to user-specified address (BindAddress6)
[mgsmtp.git] / Common.pas
index b7a6ec84f0a163429ee7b05bca17f1d05e399aae..f7a88542fff0a6ba0160e3f9eabf6d6d326e60ad 100644 (file)
@@ -1,5 +1,5 @@
 {
-   Copyright (C) 2010-2014 MegaBrutal
+   Copyright (C) 2010-2018 MegaBrutal
 
    This unit is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
@@ -26,7 +26,7 @@
 unit Common;
 
 interface
-uses Windows, SysUtils, DateUtils, Classes, INIFiles;
+uses Windows, SysUtils, DateUtils, Classes, INIFiles, RFCSMTP;
 
 type
 
@@ -40,16 +40,16 @@ type
 
 
    TArgumentParser = class
-      constructor Create(RawArguments: TStringArray; AllowedPrefixes: TStringArray = []);
+      constructor Create(RawArguments: array of string; AllowedPrefixes: array of string);
       destructor Destroy; override;
    private
       Arguments: array of TArgument;
-      procedure ParseArgument(Arg: string; const AllowedPrefixes: TStringArray);
+      procedure ParseArgument(Arg: string; const AllowedPrefixes: array of string);
    public
       function GetArgument(ID: integer): TArgument;
       function IsPresent(ArgumentName: string): boolean;
       function GetValue(ArgumentName: string; DefValue: string = ''): string;
-      function ValidateArguments(ValidArguments: TStringArray): integer;
+      function ValidateArguments(ValidArguments: array of string): integer;
    end;
 
 
@@ -73,10 +73,14 @@ type
       {FTimeCorrection: integer;}
       FTimeOffset: integer;
       FTimeOffsetStr: string;
-      FListenPorts: TStrings;
+      FListenAddresses, FListenAddresses6: TStrings;
+      FBindAddress, FBindAddress6: string;
    public
       function GetVersionStr: string;
-      property ListenPorts: TStrings read FListenPorts;
+      property ListenAddresses: TStrings read FListenAddresses;
+      property ListenAddresses6: TStrings read FListenAddresses6;
+      property BindAddress: string read FBindAddress;
+      property BindAddress6: string read FBindAddress6;
       property Databytes: longint read FDatabytes;
       {property TimeCorrection: integer read FTimeCorrection;}
       property TimeOffset: integer read FTimeOffset;
@@ -138,6 +142,7 @@ type
       procedure AddRecipient(Recipient: TRecipient); overload;
       procedure SetReturnPath(Address: string);
       procedure SetRecipientData(Index, Data: integer; RMsg: string = '');
+      procedure SetAllRecipientData(Data: integer; RMsg: string = '');
       procedure SetRelayHost(HostName: string);
       property ReturnPath: string read FReturnPath write SetReturnPath;
       property RelayHost: string read FRelayHost write SetRelayHost;
@@ -154,7 +159,7 @@ type
    function EMailTimeStamp(DateTime: TDateTime): string;
    function EMailTimeStampCorrected(DateTime: TDateTime): string;
    function StatusToStr(Status: integer): string;
-   procedure AssignDeliveryStatusToSMTPCodes(Envelope: TEnvelope);
+   procedure AssignDeliveryStatusToSMTPCodes(Envelope: TEnvelope; TransactionComplete: boolean);
 
    function CleanEOLN(S: string): string;
    function GenerateRandomString(Length: integer): string;
@@ -164,6 +169,7 @@ type
    function IsPrintableString(S: string): boolean;
    function UnixTimeStamp(DateTime: TDateTime): TUnixTimeStamp;
    function CmdlineToStringArray: TStringArray;
+   procedure ParseIPv6Address(S: string; var Address: string; var Port: word);
    procedure SplitParameters(S: string; var FirstPrm, Remainder: string; Separator: char = #32);
 
    function ReadLineFromStream(Stream: TStream): string;
@@ -173,7 +179,7 @@ type
 const
 
    { MgSMTP version: }
-   VERSION_STR       = '0.9s';
+   VERSION_STR       = '0.9t';
 
    { Architecture: }
 {$IFDEF CPU64}
@@ -219,6 +225,14 @@ implementation
 
 { Unit-private functions/prodecures: }
 
+function InStringArray(const S: string; const SA: array of string): boolean;
+var i: integer;
+begin
+   i:= 0;
+   while (i < Length(SA)) and (SA[i] <> S) do Inc(i);
+   Result:= i < Length(SA);
+end;
+
 function MakeTimeOffsetStr(TimeOffset: integer): string;
 var CorrS: string; CorrI: integer;
 begin
@@ -295,7 +309,7 @@ begin
       + '+' + IntToStr(Status and DS_SMTPREPLYMASK);
 end;
 
-procedure AssignDeliveryStatusToSMTPCodes(Envelope: TEnvelope);
+procedure AssignDeliveryStatusToSMTPCodes(Envelope: TEnvelope; TransactionComplete: boolean);
 var i, code, cond, status: integer; Recipient: TRecipient;
 begin
    for i:= 0 to Envelope.GetNumberOfRecipients - 1 do begin
@@ -304,7 +318,8 @@ begin
       cond:= code div 100;
       case cond of
          0: status:= DS_DELAYED or DS_UNEXPECTEDFAIL;
-         2: status:= DS_DELIVERED;
+         2: if TransactionComplete then status:= DS_DELIVERED
+            else status:= DS_DELAYED or DS_UNEXPECTEDFAIL;
          4: status:= DS_DELAYED;
          5: status:= DS_PERMANENT;
          else status:= DS_PERMANENT or DS_UNEXPECTEDFAIL;
@@ -375,6 +390,54 @@ begin
    end;
 end;
 
+procedure ParseIPv6Address(S: string; var Address: string; var Port: word);
+{ IPv6 addresses can be supplied in the following formats:
+      [<IPv6 address>]:<port>    e.g.  [::1]:25
+  or
+      <hostname>:<port>          e.g.  mail.example.com:25 }
+var SPort: string; c: integer;
+begin
+   if S[1] = '[' then begin
+      { Guess format is "[<IPv6 address>]:<port>". }
+      c:= pos(']', S);
+      if c > 1 then begin
+         Address:= Copy(S, 2, c - 2);
+         if c = Length(S) then begin
+            { There is no port to extract. }
+            Port:= STANDARD_SMTP_PORT;
+         end
+         else begin
+            { The closing bracket should be followed by a colon. }
+            if S[c+1] = ':' then
+               { Extract port number. }
+               Port:= StrToIntDef(Copy(S, c+2, Length(S) - (c+1)), 0)
+            else
+               { Invalid format. }
+               Port:= 0;
+         end;
+      end
+      else begin
+         { Format is incorrect, return invalid data. }
+         Address:= '';
+         Port:= 0;
+      end;
+   end
+   else begin
+      { Guess format is "<hostname>:<port>". }
+      SplitParameters(S, Address, SPort, ':');
+      if (pos(':', Address) = 0) and (pos(':', SPort) = 0) then begin
+         { Format seems correct. }
+         if SPort = '' then Port:= STANDARD_SMTP_PORT
+         else Port:= StrToIntDef(SPort, 0);
+      end
+      else begin
+         { Format is incorrect, return invalid data. }
+         Address:= '';
+         Port:= 0;
+      end;
+   end;
+end;
+
 procedure SplitParameters(S: string; var FirstPrm, Remainder: string; Separator: char = #32);
 var i: integer;
 begin
@@ -389,6 +452,14 @@ begin
    end;
 end;
 
+function CmdlineToStringArray: TStringArray;
+var i: integer;
+begin
+   SetLength(Result, ParamCount);
+   for i:= 1 to ParamCount do
+      Result[i-1]:= ParamStr(i);
+end;
+
 function UnixTimeStamp(DateTime: TDateTime): TUnixTimeStamp;
 begin
    {Result:= Trunc((DateTime - EncodeDate(1970, 1 ,1)) * 24 * 60 * 60);}
@@ -425,7 +496,7 @@ end;
 
 { Object constructors/destructors: }
 
-constructor TArgumentParser.Create(RawArguments: TStringArray; AllowedPrefixes: TStringArray = []);
+constructor TArgumentParser.Create(RawArguments: array of string; AllowedPrefixes: array of string);
 var i: integer;
 begin
    for i:= 0 to Length(RawArguments) - 1 do
@@ -447,14 +518,32 @@ begin
 end;
 
 constructor TMainServerConfig.Create(Config: TINIFile);
+var i: integer; rawaddresslist: string; portlist: TStringList;
 begin
    inherited Create(Config.ReadString('Server', 'Name', ''), Config, 'Server');
-   FListenPorts:= TStringList.Create;
-   FListenPorts.Delimiter:= ',';
-   FListenPorts.DelimitedText:= Config.ReadString('Server', 'ListenPort', '25');
+   FListenAddresses:= TStringList.Create;
+   FListenAddresses.Delimiter:= ',';
+
+   rawaddresslist:= Config.ReadString('Server', 'ListenAddress', '');
+   if rawaddresslist <> '' then
+      FListenAddresses.DelimitedText:= rawaddresslist
+   else begin
+      portlist:= TStringList.Create;
+      portlist.Delimiter:= ',';
+      portlist.DelimitedText:= Config.ReadString('Server', 'ListenPort', '25');
+      for i:= 0 to portlist.Count - 1 do
+         FListenAddresses.Add('0.0.0.0:' + portlist.Strings[i]);
+      portlist.Free;
+   end;
+
+   FListenAddresses6:= TStringList.Create;
+   FListenAddresses6.Delimiter:= ',';
+   FListenAddresses6.DelimitedText:= Config.ReadString('Server', 'ListenAddress6', '');
+
+   FBindAddress:=    Config.ReadString('Server', 'BindAddress', '0.0.0.0');
+   FBindAddress6:=   Config.ReadString('Server', 'BindAddress6', '[::]');
 
    FDatabytes:=      Config.ReadInteger('Server', 'Databytes', 1024 * 1024 * 1024);
-   {FTimeCorrection:= Config.ReadInteger('Server', 'TimeCorrection', 0);}
    FTimeOffset:=     Config.ReadInteger('Server', 'TimeOffset', Config.ReadInteger('Server', 'TimeCorrection', 0) * 100);
    FTimeOffsetStr:=  MakeTimeOffsetStr(FTimeOffset);
 
@@ -495,7 +584,7 @@ end;
 
 { Object methods: }
 
-procedure TArgumentParser.ParseArgument(Arg: string; const AllowedPrefixes: TStringArray);
+procedure TArgumentParser.ParseArgument(Arg: string; const AllowedPrefixes: array of string);
 var i, n: integer; found: boolean;
 begin
    { Strip prefix if present. }
@@ -548,16 +637,16 @@ begin
       Result:= DefValue;
 end;
 
-function TArgumentParser.ValidateArguments(ValidArguments: TStringArray): integer;
+function TArgumentParser.ValidateArguments(ValidArguments: array of string): integer;
 { Returns -1 if all arguments are valid. Otherwise, returns the ID of the first
   invalid parameter. }
 var i: integer;
 begin
    i:= 0;
-   while (i < Length(Arguments)) and (Arguments[i] in ValidArguments) do
+   while (i < Length(Arguments)) and InStringArray(Arguments[i].Option, ValidArguments) do
       Inc(i);
 
-   if i < Length(Arguments) then
+   if i >= Length(Arguments) then
       Result:= -1
    else
       Result:= i;
@@ -642,6 +731,13 @@ begin
    FRecipients[Index].Data:= Data;
 end;
 
+procedure TEnvelope.SetAllRecipientData(Data: integer; RMsg: string = '');
+var i: integer;
+begin
+   for i:= 0 to Length(FRecipients) - 1 do
+      SetRecipientData(i, Data, RMsg);
+end;
+
 procedure TEnvelope.SetReturnPath(Address: string);
 begin
    FReturnPath:= Address;