Bind delphi SOAP client to a specific local port/range - delphi

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;

Related

Opening a Firebird database file on a network share

I thought converting a mapped drive letter to a UNC path would be enough to be able to open a .GDB file,
but alas:
function ConvertToUNCPath(AMappedDrive: string) : string;
var
lRemoteString : array[0..255] of char;
lpRemote : PChar;
lStringLen : Cardinal;
begin
lpRemote := #lRemoteString;
lStringLen := 255;
If WNetGetConnection(Pchar(ExtractFileDrive(AMappedDrive)) ,
lpRemote,
lStringLen) = NO_ERROR Then
Result := lRemoteString
else
Result := ''; // No mapping found
end;
function TDataModuleData.OpenGDBDatabase(AGDBName: string) : Boolean;
var
lDlgLogin: TFrmLogin;
p : Integer;
lUNC,
lErrMsg : String;
begin
Result := False;
with FDConnection do // TFDConnection
begin
Close;
TxOptions.Isolation := xiDirtyRead;
p := Pos(':',AGDBName);
if p = 2 then
begin
lUNC := ConvertToUNCPath(Copy(AGDBName,1,2));
if lUNC <> '' then
begin
lUNC := Copy(lUNC,3);
p := pos('\',lUNC);
AGDBName := Copy(lUNC,p) + Copy(AGDBName,3);
lUNC := copy(lUNC,1,p-1);
end;
end;
DriverName := S_FD_IBId;
Params.Database := AGDBName;
if lUNC <> '' then
Params.Add('Server=' + lUNC)
else
Params.Add('Server=localhost'); // Not strictly necessary
Params.UserName := 'SYSDBA';
Params.Password := 'masterkey';
try
Open;
Result := Connected;
except
on E:Exception do
begin
lErrMsg := LowerCase(E.Message);
end;
end;
end;
end;
Depending on how I parse the ConvertToUNCPath result I get different error messages:
[firedac][phys][ib]unavailable database
[firedac][phys][ib]i/o error during "createfile (open)" operation for file "persoonlijk\jan\klanten.gdb"'#$D#$A'error while trying to open file'#$D#$A'the system cannot find the path specified.
The part of the code using ConvertToUNCPath succesfully converts e.g. P:\Jan\KLANTEN.GDB to \\tt2012server\persoonlijk\Jan\KLANTEN.GDB.
How can I open a GDB file when the path points to a mapped drive letter?
Added: I tried these hardcoded variations, they all fail:
// lUNC := '\\2012server'; // Unable to complete network request to host
lUNC := 'tt2012server';
//AGDBName := '\\tt2012server\persoonlijk\jan\klanten.gdb';
//AGDBName := 'tt2012server\persoonlijk\jan\klanten.gdb';
//AGDBName := '\persoonlijk\jan\klanten.gdb';
//AGDBName := 'persoonlijk\jan\klanten.gdb';
//AGDBName := '\jan\klanten.gdb';
//AGDBName := 'jan\klanten.gdb';
//AGDBName := 'p:\jan\klanten.gdb'; (original input)
(P: maps to \\tt2012server\persoonlijk)
Added:
Sorry, I was not clear in my initial text: this is not about connecting to a database on a remote server per se. I just want my local 'DB inspection' tool to be able to open a GDB file if someone places it in my network share for inspection (instead of having to copy it to local disk first).
To only intention of using WNetGetConnection was to resolve drive letter to UNC path (some I code I found on the web).
1. Firebird explicitly denies attempts to open database files on non-local disks
Firebird is database server, and as such it focuses on performance and reliability.
http://www.firebirdfaq.org/faq46/
Performance means lots of data is cached, both cached for reading and cached for writing.
Reliability means Firebird has to gain worthy warrants from OS that:
a. no other process would tinker with the database file while the server has some data from it cached for reading.
b. at any moment in time the server might wish to write any data to the file from its cache and it is warranted that that data - at any moment in time - ends persistently written to the persistent media.
Network-connected disks nullify both warranties and consequently Firebird Server refuses to trust them.
You may hack Firebird configuration or source files on your own discretion to remove this safety check and open network-shared files, if you really need this more than safety and speed.
But proper solution would be installing Firebird server on the machine whose disks do carry the database file.
2. Connection String is not a database file name
AGDBName := '\\tt2012server\persoonlijk\jan\klanten.gdb'
This does NOT mean "local Firebird server should connect to tt2012server server using LOCAL_SYSTEM credentials and read the database file from persoonlijk shared resource", as you probably intended it to mean.
http://www.firebirdfaq.org/faq260/
If anything, Windows LOCAL_SYSTEM user is explicitly barred from most network operations to contain intruders and viruses. Even if you hack Firebird into opening network files, most probably Windows would prohibit this access anyway, unless you would setup your Windows to run Firebird Server service with some user account other than the default LOCAL_SYSTEM.
Anyway, what \\tt2012server\persoonlijk\jan\klanten.gdb Connection String actually means is that you request your application to connect to tt2012server using WNET (aka Microsoft Named Pipes) protocol and find Firebird server running on that server and communicating by WNET protocol, as opposed to TCP/IP protocol.
Judging by the error you quote - lUNC := '\\2012server'; // Unable to complete network request to host - the said tt2012server computer perhaps does not have a Firebird Server running and accepting Named Pipes connections.
The WNET protocol is considered obsoleted and would most probably be removed from the future Firebird Server versions. As of now it is working, but few people use it, thus little up to date experience exists in that area. It is suggested you would use TCP/IP protocol by default to connect your application to the Firebird Server running on the tt2012server machine, not WNET protocol.
PS. This question has duplicates:
Connecting to Firebird database from Windows local network
ibase_connect: remote computer host and shared db file from windows
PPS. Firebird is a multi-generation database engine.
Consequently, there is no "dirty read" transactions possible in Interbase/Yaffil/Firebird family.
TxOptions.Isolation := xiDirtyRead; - this line would not work. Most probably it would silently change the transaction class to "READ COMMITTED", less probably it would give an explicit error.

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!

DataSnap migration from Delphi 7 to XE6

Sorry again for my english,
I already migrating client and the server and everything worked well,
until I noticed that Remote Data Module of the server is not working what is expected. When I run query from a client that requires more time, the other Remote Data Modules remain on hold (including and the main thred). It behaves like thread model is tmSingle. I looked around and tried everything I found with no success. Even more strange is that where I registered the new server (builded with XE6), the old one (builded with D7) started giving the same symptoms.
When installing the new server on machine that is alredy running the old one I use
xxxxx.exe /unregserver (for the old one) and
xxxxx.exe /regserver (for the new one), and then is noticeable the problem. Even if I unregister the new server, and register the old one the problem stays.
The client and the server communicate via DataSnap Socket (the client with TSocketConnection and TConnectionBroker).
Here is some info for the server
Server_TLB
unit Server_TLB;
uses Windows, ActiveX, Classes, Graphics, Midas, StdVCL, Variants;
const
LIBID_Server: TGUID = '{xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}';
IID_IrdmServer: TGUID = '{xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}';
CLASS_rdmServer: TGUID = '{xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}';
IrdmServer = interface;
IrdmServerDisp = dispinterface;
rdmServer = IrdmServer;
IrdmServer = interface(IAppServer)
['{xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}']
........... methods..........
IrdmServerDisp = dispinterface
['{xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}']
........... methods..........
CordmServer = class
class function Create: IrdmServer;
class function CreateRemote(const MachineName: string): IrdmServer;
implementation
uses ComObj;
class function CordmServer.Create: IrdmServer;
begin
Result := CreateComObject(CLASS_rdmServer) as IrdmServer;
end;
class function CordmServer.CreateRemote(const MachineName: string): IrdmServer;
begin
Result := CreateRemoteComObject(MachineName, CLASS_rdmServer) as IrdmServer;
end;
end.
..initialisation of the RDM
initialization
TComponentFactory.Create(ComServer, TrdmServer,
Class_rdmServer, ciMultiInstance, tmFree);
..the sequence of creation
Forms.Application.ShowMainForm := False;
Forms.Application.Initialize;
Forms.Application.CreateForm(TdmServer, dmServer);
Forms.Application.CreateForm(TfMain, fMain);
windows.PostMessage(fMain.Handle, MSG_INITIALIZE, 0, 0);
Forms.Application.Run;
with standard SocketDispatcher and standart SConnect
Also alredy tryed with setting key
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}\InprocServer32]
"ThreadingModel"="Free" or "Both"
I miss something else?
Thank you for the help!
In this situation I would create a simple DataSnap client / server application with the correct threading model, test it, and then compare the auto-generated server module code with the code in your migrated project.
This way, you can detect the critical differences, adjust the code to be the equivalent in the test project server module.
I suggest you to check:
Security on a Midas / DataSnap server: http://qc.embarcadero.com/wc/qcmain.aspx?d=8814
Also, the socket server is deprecated and has some drawbacks:
It bypasses DCOM security. Everything is run in the security context of the user which runs the server (if it is localsystem, it has the most powerful privileges - beware of that)
Connection is not encrypted unless you write a module for it, which lacks basic securee key exchange features. DCOM can encrypt and authenticate packets (although its encryption is today not really strong) - you just need to configure it in dcomcnfg.
It doesn't support 64 bit values and other types, see http://qc.embarcadero.com/wc/qcmain.aspx?d=69924

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.

Using Indy Server's multiple bindings as separate sockets?

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;

Resources