Using Indy Server's multiple bindings as separate sockets? - delphi

I'm still getting used to Indy, being a multi-threaded socket system with vast capabilities. One of the big things I've seen is how a server socket can have a number of different bindings. For example, you could have 3 bindings for 3 ports on the same IP address. I'm using Indy 10 on Delphi XE2.
I'm re-building an old system of mine which uses the old fashioned TServerSocket and TClientSocket components from ScktComps and re-doing it with Indy TIdTCPServer and TIdTCPClient. The old system actually consists of 3 completely different server/client sockets on each end, each socket serving a distinct purpose, and working together - similar to how FTP uses one socket for binary data and the other socket for commands.
Is it possible to mimic three separate server/client sockets within the same component using these bindings? It would be great if I can declare just one server socket with 3 ports bound, and same on the client, connected to 3 different ports on the server. All I would like to do is eliminate the need to create 3 separate server/client socket components and combine them into one.

Yes, you can use a single TIdTCPServer to manage multiple ports at a time. On the client side, you still need 3 distinct client components to connect to the different ports, though.
Create 3 entries in the TIdTCPServer.Bindings collection, one for each local IP/Port that you want to listen on, where the TIdSocketHandle.Port property would be the equivilent of the TServerSocket.Port property. TServerSocket does not natively support binding to a specific IP (though it can be done with some manual work), but the TIdSocketHandle.IP property is used for that purpose, where a blank string is equivilent to INADDR_ANY.
In the TIdCPServer.OnConnect, TIdCPServer.OnDisconnect, and TIdCPServer.OnExecute events, you can use the TIdContext.Binding.IP and TIdContext.Binding.Port properties to differentiate which binding the calling socket is connected to.
A common use of this is to support SSL and non-SSL clients on different ports, such as for protocols like POP3 and SMTP which support implicit and explicit SSL/TLS on different ports. TIdHTTPServer does this for supporting HTTP and HTTPS urls on a single server (you can use the TIdHTTPServer.OnQuerySSLPort to customize which ports use SSL/TLS versus not).
For example:
procedure TForm1.StartButtonCick(Sender: TObject);
begin
IdTCPServer1.Active := False;
IdTCPServer1.Bindings.Clear;
with IdTCPServer1.Bindings.Add do
begin
IP := ...;
Port := 2000;
end;
with IdTCPServer1.Bindings.Add do
begin
IP := ...;
Port := 2001;
end;
with IdTCPServer1.Bindings.Add do
begin
IP := ...;
Port := 2002;
end;
IdTCPServer1.Active := True;
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
begin
case AContext.Binding.Port of
2000: begin
// do something...
end;
2001: begin
// do something else...
end;
2002: begin
// do yet something else ...
end;
end;
end;

Related

Bind delphi SOAP client to a specific local port/range

I am consuming a web service with Delphi SOAP library (using THTTPRIO). One of the customers has strict networking policy requiring specific ports to be used on both sides of connection. So I need the web service client to use a specific local port or small local port range to make the connections to the server. Is there a way to do it (either through programming or using a Windows setting)? I am using Delphi 10.4.
I am not sure if I get the question right, but I think it would be as simple as specifying a port number as part of the URL property of a THTTPRIO object as below.
// Example of AUrl: 'http://COMPNAMEORIPADD:9878'
function SoapClientClass.CreateSoapClient(AUrl: string; AInterfaceName: string): THTTPRIO;
begin
result := THTTPRIO.Create(nil);
result.URL := AUrl + AInterfaceName;
end;

How to set ConnectTimeout/ReadTimeout in Indy SSL

How i can set ConnectTimeout/ReadTimeout in Indy when using SSL ?
MCVE:
program mcve;
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}SysUtils, IdHTTP, IdSSLOpenSSL, DateUtils;
var
HTTP : TIdHTTP;
SSL : TIdSSLIOHandlerSocketOpenSSL;
Started : TDateTime;
begin
HTTP := TIdHTTP.Create();
try
HTTP.ReadTimeout := 1000;
HTTP.ConnectTimeout := 2000;
SSL := TIdSSLIOHandlerSocketOpenSSL.Create(HTTP);
SSL.ConnectTimeout := HTTP.ConnectTimeout;
SSL.ReadTimeout := HTTP.ReadTimeout;
SSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
HTTP.IOHandler := SSL;
Started := Now;
try
HTTP.Get(ParamStr(1));
except
On E: Exception do WriteLn(E.Message);
end;
Writeln(FormatDateTime('hh:nn:ss', SecondsBetween(Started, Now) / SecsPerDay));
finally
HTTP.Free;
end;
end.
When using http ConnectTimeout/ReadTimeout work fine the issue only when using https see below:
:~$ ./mcve http://x.x.x.x
Read timed out.
00:00:01 <-- Correct.
:~$ ./mcve https://x.x.x.x
Socket Error # 0
00:03:38 <-- NOT Correct / More than SSL.ReadTimeout value.
Lazarus 2.0.6 Indy installed from OPM version 10.6.2.5494.
Note: On Windows same code using Delphi with shipped Indy 10.6.2.5366, The results works as expected
You don't need to manually set the ConnectTimeout and ReadTimeout on the IOHandler itself, only on the client component (in this case, TIdHTTP) . TIdTCPClient.Connect() will assign the values to the IOHandler for you.
The ConnectTimeout applies when the underlying socket is being connected to the server, before any SSL/TLS session is created, so it operates the same whether you use SSL/TLS or not.
The ReadTimeout applies when Indy attempts to read bytes from the IOHandler's internal connection. When not using SSL/TLS, that means it goes straight to the socket, and thus times out when no bytes arrive on the socket. But when using SSL/TLS, Indy uses OpenSSL's legacy SSL_...() APIs, not its newer BIO_...() APIs, which means OpenSSL is doing its own socket reading and buffering on Indy's behalf, and thus Indy times out when OpenSSL does not provide any decrypted application bytes.
One difference in how TIdSSLIOHandlerSocketOpenSSL operates on Windows vs other platforms is that on Windows Vista+ only, TIdSSLIOHandlerSocketOpenSSL does apply the ReadTimeout to the underlying socket's SO_RCVTIMEO and SO_SNDTIMEO timeouts via the IOHandler's Binding.SetSockOpt() method, as a workaround to an OpenSSL bug on Windows. For other platforms, Indy does not currently set those two socket timeouts.
A good place to set those timeouts manually would be in the IOHandler's OnBeforeConnect event, which is fired after the socket is connected to the server and before any SSL/TLS session is created.

indy ssl delphi server

I use Delphi 10.1 Berlin and Indy 10.6.2, 32bit on a Windows Server 2012.
I implemented a server based on TIdHTTPServer. Works like a charm for many years.
Now my customer wants the traffic secured. SSL is an area I have little knowledge of.
There are several helpful pointers on the web that have helped me make CA certificate and key files with OpenSSL. And from several examples I've put together the code below, using TIdServerIOHandlerSSLOpenSSL.
The cert/key files are in the exe directory and so are the OpenSSL dlls ssleay32 and libeay32.
The server responds to http://localhost:8080 but there is no response when addressing it via https://localhost. It behaves as if the TIdServerIOHandlerSSLOpenSSL is not there at all. (The IOHandler does read the cert/key files and it complains when I remove the OpenSSL DLLs). It is as if I've forgotten to throw a switch somewhere.
The analysis of Windows Network Diagnostics (in IEdge) is 'The device or resource (localhost) is not set up to accept connections on port "https".'
I tried to log a message via the OnConnect event, but that stage is never reached with HTTPS.
I have run out of ideas, and can not find relevant suggestions on the web.
Here is my code (the components are all declared in code):
procedure TServerForm.FormCreate(Sender: TObject);
var
ServerSSLIOHandler: TIdServerIOHandlerSSLOpenSSL;
rootdir : string;
begin
inherited;
rootdir:=ExtractFilePath(Application.ExeName);
ServerSSLIOHandler:=TIdServerIOHandlerSSLOpenSSL.Create(self);
ServerSSLIOhandler.SSLOptions.RootCertFile:=rootdir+'ca.cert.pem';
ServerSSLIOhandler.SSLOptions.CertFile:=rootdir+'localhost.cert.pem';
ServerSSLIOhandler.SSLOptions.KeyFile:=rootdir+'localhost.key.pem';
ServerSSLIOhandler.SSLOptions.Method:=sslvSSLv23;
ServerSSLIOhandler.SSLOptions.Mode:=sslmServer;
ServerSSLIOhandler.OnGetPassword:=NIL;
ServerSSLIOhandler.OnVerifyPeer:=OnVerifyPeer;
HTTPServer:=TIdHTTPServer.Create(self);
HTTPServer.IOhandler:=ServerSSLIOHandler;
HTTPserver.Bindings.Add.Port:=443;
HTTPserver.Bindings.Add.Port:=8080;
HTTPServer.Active:=True;
HTTPServer.AutoStartSession:=True;
HTTPServer.SessionTimeOut:=1200000;
HTTPserver.OnQuerySSLPort:=OnQuerySSLPort;
HTTPServer.OnCommandGet:=HTTPServerCommandGet;
...
end;
procedure TServerForm.OnQuerySSLPort(APort: Word; var VUseSSL: Boolean);
// This will not be called when the request is a HTTPS request
// It facilitates the use of the server for testing via HTTP://localhost:8080 (i.e. without SSL)
begin
VUseSSL := (APort<>8080);
end;
function TServerForm.OnVerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean;
begin
result:=AOk;
end;
Thank you. Remy's remark about OnConnect and his suggestion to use netstat did the trick. I.e. it lead me to discover that the problem was elsewhere. In the past I had to move away from port 80 because it became in use by a Windows service. From then on I specified a port number (8080) in an ini file and acted as follows.
Code:
prt:=parameters.ReadInteger('settings','port',80);
if prt<>HTTPserver.DefaultPort
then begin HTTPserver.Active:=false;
HTTPserver.Bindings.Clear;
HTTPserver.DefaultPort:=prt;
HTTPserver.Active:=true;
end;
Since this piece of code was still there, obviously only the specified port (8080) was active. netstat revealed that immediately.
Will you believe that I am very happy with your quick response!

Check remote port access using Delphi - Telnet style

I deploy my application in environments heavily stricken with firewalls. Frequently I find myself using Telnet to check if a port is open and accessible in the network.
Now I would like to implement an equivalent functionality of the command, Telnet [domainname or ip] [port], in Delphi.
Is it adequate that I just attempt to open and close a TCP/IP socket without sending or receiving any data?
Is there any risk that I might crash the arbitrary application/service listening on the other end?
Here's my code:
function IsPortActive(AHost : string; APort : Word):boolean;
var IdTCPClient : TIdTCPClient;
begin
IdTCPClient := TIdTCPClient.Create(nil);
try
try
IdTCPClient.Host := AHost;
IdTCPClient.Port := APort;
IdTCPClient.Connect;
except
//Igonre exceptions
end;
finally
result := IdTCPClient.Connected;
IdTCPClient.Disconnect;
FreeAndNil(IdTCPClient);
end;
end;
If you just want to check whether the port is open, then you can use this:
function IsPortActive(AHost : string; APort : Word): boolean;
var
IdTCPClient : TIdTCPClient;
begin
Result := False;
try
IdTCPClient := TIdTCPClient.Create(nil);
try
IdTCPClient.Host := AHost;
IdTCPClient.Port := APort;
IdTCPClient.Connect;
Result := True;
finally
IdTCPClient.Free;
end;
except
//Ignore exceptions
end;
end;
But that only tells you if any server app has opened the port. If you want to make sure that YOUR server app opened the port, then you will have to actually communicate with the server and make sure its responses are what you are expecting. For this reason, many common server protocols provide an initial greeting so clients can identify the type of server they are connected to. You might consider adding a similar greeting to your server, if you are at liberty to make changes to your communication protocol.
Simply opening a connection to the server does not impose any risk of crashing the server, all it does is momentarily occupy a slot in the server's client list. However, if you actually send data to the server, and the server app you are connected to is not your app, then you do run a small risk if the server cannot handle arbitrary data that does not conform it its expected protocol. But that is pretty rare. Sending a small command is not uncommon and usually pretty safe, you will either get back a reply (which may be in a format that does not conform to your protocol, so just assume failure), or you may not get any reply at all (like if the server is waiting for more data, or simply is not designed to return a reply) in which case you can simply time out the reading and assume failure.

Indy SSL to plain socket pump

I have to provide an SSL front for a plain TCP server, so I'm making a "pump" application that will provide an SSL connection to the outside while the original server can stay plain.
I'm trying to use the Indy components to support my SSL needs, but I can't seem to get any data from the SSL port. I assigned the TIdTCPServer.OnExecute to the following event handler:
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
c:TIdTCPClient;
cs,ss:TIdIOHandler;
b:TBytes;
begin
c:=TIdTCPClient.Create(Self);
try
c.Host:='127.0.0.1';
c.Port:=60675;
c.ConnectTimeout:=500;
c.Connect;
ss:=c.IOHandler;
cs:=AContext.Connection.IOHandler;
while (cs.Connected) and (ss.Connected) do
begin
if cs.CheckForDataOnSource(1) then
begin
try
cs.ReadBytes(b,1,False);
except on e:Exception do
Memo1.Lines.Add(e.Message); //BAD out of Thread context
end;
if Length(b)>0 then
ss.Write(b);
end;
if ss.CheckForDataOnSource(1) then
begin
ss.ReadBytes(b,1,False);
if Length(b)>0 then
cs.Write(b);
end;
end;
finally
c.Free;
end;
end;
The TCP server has an SSL handler attached. I did the same on a plain HTTP server and it worked fine, so I'm assuming my SSL setup is not the issue.
cs=Client Side (the server socket) and ss=Server side (the client for the TCP server I'm trying to add SSL to).
Now, I know it needs cleanup and doing 1ms waits isn't pretty, but before I can attack that issue, I'd like to receive some data.
Neither of my ReadBytes get called. When I used cs.Readable(), I get true just once, but I still couldn't read.
What can I do to make a pump? Why am I not getting data?
Try using the TIdMappedPortTCP component instead of TIdTCPServer directly. TIdMappedPortTCP handles all the work of passing data back and forth between a client and another server. By default, the outbound connection to the second server is not encrypted, even if the inbound connection to TIdMappedPortTCP is encrypted.

Resources