Enable IPv6 listen address (ListenAddress6)
authorMegaBrutal <code+git@megabrutal.com>
Wed, 7 Nov 2018 06:25:00 +0000 (07:25 +0100)
committerMegaBrutal <code+git@megabrutal.com>
Sun, 11 Nov 2018 20:40:00 +0000 (21:40 +0100)
ListenAddress6 can be used to specify IPv6 addresses to listen on.
Hostnames are resolved, numeric IPv6 addresses must be put in brackets.

modified:   Common.pas
modified:   DNSResolve.pas
modified:   Listener.pas
modified:   MgSMTP.pas
modified:   Network.pas

Common.pas
DNSResolve.pas
Listener.pas
MgSMTP.pas
Network.pas

index fadccc59f8b17f0ca294d84740c7af03e946d546..2280c2b5358f968ba6d8f0c5c070e445088be0d3 100644 (file)
@@ -26,7 +26,7 @@
 unit Common;
 
 interface
-uses Windows, SysUtils, DateUtils, Classes, INIFiles;
+uses Windows, SysUtils, DateUtils, Classes, INIFiles, RFCSMTP;
 
 type
 
@@ -73,10 +73,11 @@ type
       {FTimeCorrection: integer;}
       FTimeOffset: integer;
       FTimeOffsetStr: string;
-      FListenAddresses: TStrings;
+      FListenAddresses, FListenAddresses6: TStrings;
    public
       function GetVersionStr: string;
       property ListenAddresses: TStrings read FListenAddresses;
+      property ListenAddresses6: TStrings read FListenAddresses6;
       property Databytes: longint read FDatabytes;
       {property TimeCorrection: integer read FTimeCorrection;}
       property TimeOffset: integer read FTimeOffset;
@@ -165,6 +166,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;
@@ -385,6 +387,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
@@ -483,6 +533,10 @@ begin
       portlist.Free;
    end;
 
+   FListenAddresses6:= TStringList.Create;
+   FListenAddresses6.Delimiter:= ',';
+   FListenAddresses6.DelimitedText:= Config.ReadString('Server', 'ListenAddress6', '');
+
    FDatabytes:=      Config.ReadInteger('Server', 'Databytes', 1024 * 1024 * 1024);
    FTimeOffset:=     Config.ReadInteger('Server', 'TimeOffset', Config.ReadInteger('Server', 'TimeCorrection', 0) * 100);
    FTimeOffsetStr:=  MakeTimeOffsetStr(FTimeOffset);
index fb4b467bd6424946f05825e80f7dde6f2958a09f..27913b0b8bf15f0e0e8c9da840ca3ca2a0c3ca20 100644 (file)
@@ -55,7 +55,8 @@ type
 
    function ResolveHost(HostName: ansistring; Family: cint): TGAIResult;
    procedure FreeHost(var GAIResult: TGAIResult);
-   function ResolveIP(AddrInfo: PAddrInfo): ansistring;
+   function ResolveIP(AddrInfo: PAddrInfo): ansistring; overload;
+   function ResolveIP(SockAddr: PSockAddr): ansistring; overload;
    function IPToStr(SockAddr: PSockAddr): ansistring;
 
 
@@ -93,6 +94,22 @@ begin
    else ResolveIP:= IPToStr(AddrInfo^.ai_addr);
 end;
 
+function ResolveIP(SockAddr: PSockAddr): ansistring;
+var
+   AddrInfo: TAddrInfo;
+begin
+   AddrInfo.ai_addr:= SockAddr;
+
+   if SockAddr^.sa_family = AF_INET then
+      AddrInfo.ai_addrlen:= SizeOf(TSockAddr)
+   else if SockAddr^.sa_family = AF_INET6 then
+      AddrInfo.ai_addrlen:= SizeOf(TSockAddr6)
+   else
+      AddrInfo.ai_addrlen:= 0;
+
+   ResolveIP:= ResolveIP(PAddrInfo(@AddrInfo));
+end;
+
 function IPToStr(SockAddr: PSockAddr): ansistring;
 begin
    if SockAddr^.sa_family = AF_INET then
index fc92edd86a77c31ac0a808354e03fc3710940c71..bff8dcf0ce9429d9ffb130433a0410cec9cf5ae2 100644 (file)
@@ -38,7 +38,7 @@ uses SysUtils, Classes, Base64, Network, NetRFC, RFCSMTP,
 type
 
    TMgSMTPListener = class(TTCPListener)
-      constructor Create(const Address: string; Port: word);
+      constructor Create(const Address: string; Port, Family: word);
    protected
       procedure HandleClient(Connection: TTCPConnection); override;
       procedure ReceiveEMailData(TCP: TTCPRFCConnection; Response: TRFCReply; SpoolObject: TSpoolObjectCreator);
@@ -57,14 +57,20 @@ var
 
 
 procedure StartListeners;
-var i: integer; address, port: string;
+var i, j: integer; address, port: string; nport: word;
 begin
-   SetLength(MgSMTPListeners, MainServerConfig.ListenAddresses.Count);
-   for i:= 0 to Length(MgSMTPListeners) - 1 do begin
+   SetLength(MgSMTPListeners, MainServerConfig.ListenAddresses.Count + MainServerConfig.ListenAddresses6.Count);
+   for i:= 0 to MainServerConfig.ListenAddresses.Count - 1 do begin
       SplitParameters(MainServerConfig.ListenAddresses.Strings[i], address, port, ':');
-      MgSMTPListeners[i]:= TMgSMTPListener.Create(address, StrToIntDef(port, STANDARD_SMTP_PORT));
+      MgSMTPListeners[i]:= TMgSMTPListener.Create(address, StrToIntDef(port, STANDARD_SMTP_PORT), AF_INET);
       MgSMTPListeners[i].StartListen;
    end;
+   j:= MainServerConfig.ListenAddresses.Count;
+   for i:= 0 to MainServerConfig.ListenAddresses6.Count - 1 do begin
+      ParseIPv6Address(MainServerConfig.ListenAddresses6.Strings[i], address, nport);
+      MgSMTPListeners[j+i]:= TMgSMTPListener.Create(address, nport, AF_INET6);
+      MgSMTPListeners[j+i].StartListen;
+   end;
 end;
 
 procedure StopListeners;
@@ -128,10 +134,10 @@ begin
 end;
 
 
-constructor TMgSMTPListener.Create(const Address: string; Port: word);
+constructor TMgSMTPListener.Create(const Address: string; Port, Family: word);
 begin
    { Request connection objects with support for RFC-style commands & responses. }
-   inherited Create(Address, Port, NET_TCP_RFCSUPPORT);
+   inherited Create(Address, Port, Family, NET_TCP_RFCSUPPORT);
    Logger.AddLine('Server', 'Listening on address: ' + Address + ':' + IntToStr(Port));
 end;
 
index a9c51e7d2e1ca64260742f5b1888f195d81b0ade..154ad6344f3424d87e9e9f92deeb9dd1fae9b1b6 100644 (file)
@@ -49,7 +49,7 @@ const
      document what bugfix/feature are you testing with the actual build.
      This will be logged to help you differentiate outputs of subsequent
      builds in your logs. If left empty, it won't be added to the logs. }
-   DEVCOMMENT  =  'Use getaddrinfo & getnameinfo';
+   DEVCOMMENT  =  'IPv6 listen addresses (ListenAddress6)';
 
 var
 
index b19dd7dcbe144741369c301e3dfc71a03f24e8cc..7521ff1877091bce6667fe4f3f92cbdb195b6dfe 100644 (file)
@@ -41,6 +41,13 @@ uses Classes, Sockets, SocketUtils, DNSResolve, NetRFC, Common;
 
 const
 
+   { Address families: }
+   { These are here so users of this unit don't necessarily have to
+     use Sockets as well. }
+   AF_UNSPEC            =  Sockets.AF_UNSPEC;
+   AF_INET              =  Sockets.AF_INET;
+   AF_INET6             =  Sockets.AF_INET6;
+
    { Connection feature requests: }
    NET_TCP_BASIC        =  0;
    NET_TCP_RFCSUPPORT   =  1;
@@ -218,7 +225,7 @@ procedure TTCPConnection.ReverseDNSLookup;
 var NHostIP: TIPNamePair;
 begin
    if FConnected then begin
-      NHostIP:= TIPNamePair.Create(ResolveIP(@SockAddr), FHostIP.IP);
+      NHostIP:= TIPNamePair.Create(ResolveIP(PSockAddr(@SockAddr)), FHostIP.IP);
       FHostIP.Free;
       FHostIP:= NHostIP;
    end;