SendMail with TIdSMTP Connection Time Out - delphi

I am not able to send emails using TIdSMTP I am getting the following message: Socket Error # 10060 / Connection timed out.
I am using version Delphi XE6
Here is my code:
procedure TForm1.Button1Click(Sender: TObject);
var
IdSMTP : TIdSMTP;
IdMessage : TIdMessage;
begin
IdSMTP := TIdSMTP.Create (Nil);
IdMessage := TIdMessage.Create (Nil);
IdSMTP.Host := 'mail.mysmtp.com';
IdSMTP.Port := 25;
IdSMTP.Connect ();
IdMessage.From.Address := 'test#mysmtp.com';
IdMessage.From.Name := 'Contato';
IdMessage.Recipients.EMailAddresses := 'test#hotmail.com';
IdMessage.Subject := 'Contato test';
IdMessage.Body.Text := 'test';
IdSMTP.Send (IdMessage);
IdSMTP.Disconnect ();
FreeAndNil (IdMessage);
FreeAndNil (IdSMTP);
end;

From Google: A socket error in the 10060 range is a Winsock error. It is generally caused by either outgoing connection problems or connection problems on the host end.
I don't know if you sanitized this code to post it or not, but I'd say the culprit is either the hostname or the username on the from address.
Winsock will attempt to create a connection to the hostname. If it fails to get the expected ACK, it'll generate a timeout error. I've also seen this happen when the domain name isn't resolved by DNS.
Also, what was mentioned earlier regarding authentication ... the lack of response from the SMTP host could be due to improper authentication. It all depends on how the host's SMTP service was configured, so it could just ignore unauthorized requests.
You need to see if you must pass in a username/pwd with the SMTP request, or read the mailbox first (read before write, so to speak). I cannot imagine anybody configuring an SMTP server without requiring some kind of authentication, because otherwise you've got what amounts to an "open relay" where any process can send out unlimited traffic through it.
Also, the from address might be required to be valid. That is, 'test#mysmtp.com' would require a user/mailbox for 'test' to exist, as opposed to '*#mysmtp.com' that would work with ANY user/mailbox name.
All of these could result in a timeout because the SMTP host could be configured to simply ignore improper and unauthenticated requests.

Related

Can't connect to Indy TIdHttpServer with TLS

I'm writing a test Delphi 10.4 Win32 application that uses TIdHTTPServer and TIdServerIOHandlerSSLOpenSSL from Indy 10.6.2.0 installed by Delphi 10.4, using a port on localhost for the purposes of communicating between a C# (.NET 5) client application and this Win32 Delphi VCL (server) application.
I know very little about TLS, SSL, and certificates but have been learning as I attempt to build this; from the examples I have seen, I had expected this to be simple.
The "general" page for the .pfx certificate properties says "Enable all purposes for this certificate".
I started partly by reviewing examples and also Remy Lebeau's postings here:
https://stackoverflow.com/a/34422109/17371832
https://stackoverflow.com/a/63082235/17371832
Using Indy 10 IdHTTP with TLS 1.2
I have provided it with OpenSSL 1.0.2.17 DLLs:
libeay32.dll
ssleay32.dll
These DLLs load successfully.
The problem is that clients can't connect to it. .NET says "The SSL connection could not be established" (more about the error inside Indy later).
WireShark shows "Client Hello" (TLS 1.2 with about 20 cipher types) but no "Server Hello", alerts, or errors are sent. In fact, only one packet marked as TLS 1.2 is sent (when the client said "Client Hello").
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:(and many others)
I had tried providing the full list (as formatted above) to the TIdHTTPServer's CipherList property, but got an error (I think it was "SetCipherList failed").
If the cipher list is left empty, it seems to internally use "AES:ALL:!aNULL:!eNULL:+RC4:#STRENGTH". I left it that way.
Here are the components involved:
object srv: TIdHTTPServer
Bindings = <
item
IP = '127.0.0.1'
Port = 5500
end>
DefaultPort = 5500
IOHandler = iohSSL
MaxConnections = 25
AutoStartSession = True
KeepAlive = True
SessionState = True
OnCreatePostStream = srvCreatePostStream
OnDoneWithPostStream = srvDoneWithPostStream
OnCommandGet = srvCommandGet
Left = 183
Top = 118
end
object iohSSL: TIdServerIOHandlerSSLOpenSSL
SSLOptions.CertFile = 'C:\Users\Mike\AppData\Local\Somewhere\cert.pfx'
SSLOptions.KeyFile = 'C:\Users\Mike\AppData\Local\Somewhere\cert.pfx'
SSLOptions.Method = sslvTLSv1_2
SSLOptions.SSLVersions = [sslvTLSv1_2]
SSLOptions.Mode = sslmServer
OnGetPasswordEx = iohSSLGetPasswordEx
Left = 256
Top = 120
end
The code isn't interesting:
procedure TdmCloud.DataModuleCreate(Sender: TObject);
begin
iohSSL.SSLOptions.CertFile:='.\cert.pfx';
//iohSSL.SSLOptions.RootCertFile:='.\root2.pem';
//iohSSL.SSLOptions.KeyFile:='.\key.pem';
srv.Active:=true;
end;
procedure TdmCloud.iohSSLGetPasswordEx(ASender: TObject; var VPassword: string;
const AIsWrite: Boolean);
begin
VPassword := 'password';
end;
procedure TdmCloud.srvCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
begin
AResponseInfo.ResponseNo := HTTP_OK;
AResponseInfo.ContentType := 'text/html';
AResponseInfo.ContentText := '<html><head><title>Title</title></head><body>HELLO</body></html>';
end;
procedure TdmCloud.srvCreatePostStream(AContext: TIdContext;
AHeaders: TIdHeaderList; var VPostStream: TStream);
begin
VPostStream := TMemoryStream.Create;
end;
procedure TdmCloud.srvDoneWithPostStream(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; var VCanFree: Boolean);
begin
VCanFree := true;
end;
I have seen in the debugger that the certificate file is loaded successfully with the password provided by the event handler. The listener thread runs ok until it receives a request.
TIdHTTPServer's OnCommandGet event doesn't fire.
I've seen in the debugger that InternalReadLn() returns a string that looks encrypted; it's definitely binary. Anyway, below, I get "error in parsing command"; judging from the data it's looking at, I'm not surprised.
var
i: integer;
s, LInputLine, LRawHTTPCommand, LCmd, LContentType, LAuthType: String;
LURI: TIdURI;
LContinueProcessing, LCloseConnection: Boolean;
LConn: TIdTCPConnection;
LEncoding: IIdTextEncoding;
begin
LContinueProcessing := True;
Result := False;
LCloseConnection := not KeepAlive;
try
try
LConn := AContext.Connection;
repeat
// InternalReadLn( ) returns a string that looks encrypted; it's definitely binary
LInputLine := InternalReadLn(LConn.IOHandler);
i := RPos(' ', LInputLine, -1); {Do not Localize}
if i = 0 then begin // true
raise EIdHTTPErrorParsingCommand.Create(RSHTTPErrorParsingCommand);
end;
As it happens, InternalReadLn() calls TIdServerIOHandlerSSLOpenSSL's Readln() method, which has provided this binary data.
Remy Lebeau said this about the error in an online posting:
The error is actually coming from several lines further down:
i := RPos(' ', LInputLine, -1); {Do not Localize}
if i = 0 then begin
raise EIdHTTPErrorParsingCommand.Create(RSHTTPErrorParsingCommand);
end;
Which means your client is sending a malformed HTTP request. The first
line in EVERY request must end with an HTTP version number, ie: GET /login.html HTTP/1.0 Hence the call to RPos(). The error means there
is no space character anywhere in the line.
Although it could certainly be a malformed request, I'm told that the application's requests have been tested and found to be valid/working.
I've tried a browser directed to https://localhost:5500/ with similar results.
That brings me back to the conclusion that TLS negotiation didn't start at "Client hello". I have no idea why. I don't know where that process is handled in Indy.
Here's a typical, working TLS negotiation: https://tls.ulfheim.net/
Key areas of suspicion for me; I've made some effort to explore each possibility:
might the .pfx certificate missing something important (even though I'm told it's a good certificate)? It has the "Server Authentication" option checked in the properties.
which of CertFile, RootCertFile, KeyFile, and CipherList do I need to provide for TLS 1.2 to work? (and how do I choose what to use?)
is Delphi 10.4 update 2's Indy SSL broken or incompatible with OpenSSL 1.0.2q?
Unhelpfully, all of documentation links at https://www.indyproject.org/documentation/ are broken! My Delphi install didn't include any Indy help either.
Help being absent, I'm uncertain which to provide of RootCertFile, CertFile, and KeyFile. I've tried the .pfx which was accepted for CertFile and KeyFile but later saw https://stackoverflow.com/a/15416234/17371832 so I followed the instructions and was able to make a .pem file (root file was not accepted by Indy). This provided no improvement.
Is Indy dying? Is there something else that's better? Haven't looked into that at all.
Why won't it connect?
The standard TCP port for HTTPS is 443, but you are running your TIdHTTPServer on port 5500 instead. As such, you must use the TIdHTTPServer.OnQuerySSLPort event to explicitly tell TIdHTTPServer that you want to use SSL/TLS on that port, eg:
procedure TdmCloud.srvQuerySSLPort(APort: TIdPort; var VUseSSL: Boolean);
begin
VUseSSL := APort = 5500;
end;
This event allows HTTP and HTTPS to co-exist on a single server (via multiple TIdHTTPServer.Bindings items) even when using non-standard ports. Without this event handler assigned, this is why your server is not handling the client's SSL/TLS handshake correctly.

Indy IdFTP fails on Active connection

I'm trying to use Indy's IdFTP to send and receive some files via FTP.
function TDatosFTP.TransfiereFTP(Fm: TForm): boolean;
var
TimeoutFTP: integer;
begin
Result := false;
with TIdFTP.Create(Fm) do
try
try
TimeoutFTP := 2000;
Host := Servidor;
Port := 21;
UserName := Usuario;
PassWord := Contra;
Passive := Pasivo;
Connect(true, TimeoutFTP);
if not Connected then
begin
Error := true;
end
else
begin
TransferType := ftASCII;
if Binario then
TransferType := ftBinary;
OnWorkEnd := FinDeTransmision;
if Descargar then
Get(Remoto , Local, True)
else
Put(InterpretarRutaEspecial(Local), Remoto, True);
if Descargar and Borrar then
Delete(Remoto);
Disconnect;
Result := true;
Fm.Hide;
end;
Except on E: Exception do
Mensaje := E.Message;
end;
finally
Free;
end;
if not Result then
ErrorTransmision;
end;
Whenever I try to do a PUT/GET on Active mode I get the following error: EIdProtocolReplyError: 'Failed to establish connection". It works fine on Passive mode.
The thing is that I want to use Indy (used elsewhere in the project) but the previous version of the code, using OverbyteIcsFtpCli works fine both in Active and Passive mode.
This is the code using OverbyteIcsFtpCli:
function TDatosFTP.TransfiereFTP(Fm: TForm): boolean;
begin
with TFtpClient.Create(Fm) do
try
HostName := Servidor;
Port := '21';
UserName := Usuario;
PassWord := Contra;
HostDirName := '';
HostFileName := Origen;
LocalFileName := InterpretarRutaEspecial(Destino);
Binary := Binario;
Passive := Pasivo;
OnRequestDone := FinDeTransmision;
if Descargar then
Result := Receive
else
Result := Transmit;
OnRequestDone := nil;
if Descargar and Borrar then
Delete;
Result := Result and not Error;
Fm.Hide;
if not Result then
ErrorTransmision;
finally
Free;
end;
end;
So I took a look under the hood using wireshark and I found that Indy's FTP is not answering some messages from the server.
This is the file-transmission handshake with OverBytes' FTP:
I've highlighted in yellow the two packets sent between server and client that start the data transmission.
Now let's see what happens with Indy's FTP:
The server is sending the packet to start the file transmission but IdFTP is not answering.
I've seen this question but this two tests where ran in the same computer, same network connection, same firewall, etc. Also this one, but I want the FTP to work both in active and passive modes.
What's happening?
In an Active mode transfer, an FTP server creates an outgoing TCP connection to the receiver.
Your Wireshark captures clearly show that the FTP server in question is creating that transfer connection BEFORE sending a response to the RETR command to let your client know that the connection is proceeding. TFtpClient is accepting that connection before receiving the RETR response. But TIdFTP waits for the RETR response before it will then accept the transfer connection (this also applies to TIdFTP's handling of STOR/STOU/APPE commands, too).
LPortSv.BeginListen; // <-- opens a listening port for transfer
...
SendPort(LPortSv.Binding); // <-- sends the PORT command
...
SendCmd(ACommand, [125, 150, 154]); // <-- sends the RETR command and waits for a response!
...
LPortSv.Listen(ListenTimeout); // <-- accepts the transfer connection
...
Re-reading RFC 959, it says the following:
The passive data transfer process (this may be a user-DTP or a second server-DTP) shall "listen" on the data port prior to sending a transfer request command. The FTP request command determines the direction of the data transfer. The server, upon receiving the transfer request, will initiate the data connection to the port. When the connection is established, the data transfer begins between DTP's, and the server-PI sends a confirming reply to the user-PI.
ICS is asynchronous, so this situation is not a big deal for it to handle. But Indy uses blocking sockets, so TIdFTP will need to be updated to account for this situation, likely by monitoring both command and transfer ports simultaneously so it can act accordingly regardless of the order in which the transfer connection and the command response arrive in.
I have opened a ticket in Indy's issue tracker for this:
#300: TIdFTP fails on Active mode transfer connection with vsFTPd
UPDATE: the fix has been merged into the main code now.

TIdHTTPProxyServer raising "Unknown Protocol" RSHTTPUnknownProtocol exception

I'm recoding an old Delphi XE program using Delphi 10.3 Rio. It uses the TIdHTTPProxyServer Indy component listening on 127.0.0.1:80.
with IdHTTPProxyServer.Bindings.Add do begin
IP := '127.0.0.1';
Port := 80;
end;
IdHTTPProxyServer.Active := True;
For testing, I added 127.0.0.1 localtest123.com and 127.0.0.1 www.localtest123.com to the hosts file and disabled the DNS cache service. Then in multiple browers I requested http://localtest123.com/ and http://www.localtest123.com/. With OutputDebugString() I can see the connections accepted but then raises an "Unknown Protocol" error.
I debugged the exception in the TIdHTTPProxyServer.CommandPassThrough procedure in IdHTTPProxyServer.pas. It seems LURI.Protocol is an empty string which is why the RSHTTPUnknownProtocol is raised.
LContext := TIdHTTPProxyServerContext(ASender.Context);
LContext.FCommand := ASender.CommandHandler.Command; //<-'GET'
LContext.FTarget := ASender.Params.Strings[0]; //<-'/'
LContext.FOutboundClient := TIdTCPClient.Create(nil);
try
LURI := TIdURI.Create(LContext.Target); //<-'/'
try
TIdTCPClient(LContext.FOutboundClient).Host := LURI.Host; //<-''
if LURI.Port <> '' then begin //<-''
TIdTCPClient(LContext.FOutboundClient).Port := IndyStrToInt(LURI.Port, 80);
end
else if TextIsSame(LURI.Protocol, 'http') then begin //<-'' {do not localize}
TIdTCPClient(LContext.FOutboundClient).Port := IdPORT_HTTP;
end
else if TextIsSame(LURI.Protocol, 'https') then begin //<-'' {do not localize}
TIdTCPClient(LContext.FOutboundClient).Port := IdPORT_https;
end else begin
raise EIdException.Create(RSHTTPUnknownProtocol);
end;
I'm probably missing something but TIdHTTPProxyServer just works without much code so I have to ask for help on this exception. Thanks in advance!
You can't just redirect the domains in your HOSTS file and expect things to magically work. That is not how proxying works.
You must explicitly configure web browsers to make HTTP requests through an HTTP proxy so that they format the proper requests that a proxy would understand. Sending an HTTP request directly to a target web server is handled differently than sending the same HTTP request through a proxy.
You are getting the exception because the browser requests are not targeting your proxy properly.
For example, when a browser sends an HTTP GET request directly to a target web server, it connects directly to that server and then sends a request that looks something like this:
GET /path HTTP/1.1
Host: server.com
But, when it sends the same request through an HTTP proxy, it connects to the proxy and sends a request that looks more like this instead:
GET http://server.com/path HTTP/1.1
That extra path information in the GET line is missing in your browser requests, because you do not have your browsers configured for proxying, thus the exception when TIdHTTPProxyServer is trying to determine the info it needs to make a connection to the target web server and forward the current request to it.
This is fundamentally to how HTTP works, and how TIdHTTPProxyServer is designed to work.
Things are a bit more complicated when HTTPS is involved, but I'm leaving that detail out for now, as it is not relevant to your question about the exception.
UPDATE: in comments, you say:
In the XE version it never raised an exception when checking for the protocol which would still work today because I manually set the host and port in DoHTTPBeforeCommand.
In that old version, there was no exception raised, because TIdHTTPProxyServer did not check the protocol yet to differentiate between HTTP and HTTPS. You were able to manually fill in missing info when a request was received that was not specifically targeting your proxy. That is why things worked for you before.
In a later version, TIdHTTPProxyServer was updated to differentiate between HTTP and HTTPS when no port is explicitly specified in the request, so a default port is set based on protocol requested. That check happens before DoHTTPBeforeCommand() is called.
To get the old behavior back, you would have to alter TIdHTTPProxyServer's source code to delay the raising of the exception until after DoHTTPBeforeCommand() returns, so you have a chance to fill in missing values again.
If you file a feature request for that, I might consider adding it to Indy's official code.

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