rlebeau on OpenSSL-1.1.x
rlebeau on master
Correctly define timezone funct… Merge pull request #272 from Bi… (compare)
rlebeau on master
Fixing compiler errors in TIdIP… (compare)
rlebeau on master
Fix for missing declaration of … Fix for compiler error in Local… Merge branch 'master' of https:… and 2 more (compare)
rlebeau on master
Update IdGlobal.pas Fix for co… (compare)
rlebeau on master
Update IdGlobal.pas Fix for mi… (compare)
type TAuth = class procedure DoSelectAuthorization(Sender: TObject; var AuthenticationClass: TIdAuthenticationClass; AuthInfo: TIdHeaderList); procedure DoProxyAuthorization (Sender: TObject; Authentication: TIdAuthentication; var Handled: Boolean); end; procedure TAuth.DoSelectAuthorization(Sender: TObject; var AuthenticationClass: TIdAuthenticationClass; AuthInfo: TIdHeaderList); begin //It turns out AuthenticateClass is already set to TIdSSPINTLMAuthentication by //TIdCustomHTTP.DoOnProxyAuthorization when this event is called. // //It does this by inspecting AResponse.ProxyAuthenticate //list to see what protocols are requested/supported by the proxy and then //trying to look up a suitable registered class to use to handle the requirement. // //In our case this of course contains 'NTLM' which results in TIdSSPINTLMAuthentication //being looked up as appropriate class to use. //TIdSSPINTLMAuthentication gets looked up because this class was previously //automatically registered by idAuthenticationSSPI unit initialization //via line 1320 call to RegisterAuthenticationMethod('NTLM', TIdSSPINTLMAuthentication); //which registers it into global "AuthList". It is registered by inclusion in uses clause //via IdAuthenticationSSPI (or IdAllAuthentications). //Consequently we don't have to provide anything here and I instead just //assert here that we're using the expected auth class instead: Assert(AuthenticationClass = TIdSSPINTLMAuthentication); end; procedure TAuth.DoProxyAuthorization (Sender: TObject; Authentication: TIdAuthentication; var Handled: Boolean); begin //This is never called? (Presumably since not applicable because with SSP (single sign-on protocol) //the NTLM response is generated automatically.) ShowMessage(Authentication.Authentication); Assert(Authentication.Authentication <> ''); end; procedure TTextMagicTests.test_SSL_proxy_access_to_google_via_NTLMSSP; var LIdHTTP : TIdHTTP; LSSLIOHandler : TIdSSLIOHandlerSocketOpenSSL; LRequestStr, LResult : String; LAuth : TAuth; begin //Using 'https://www.google.com' as LRequestStr results in HTTP 302 from google //which seems to result in what ends up looking like a 407 Indy Exception to //the Delphi code... // //Why is this? It seems incorrect behaviour. Indy should just deal with the //redirect, like a browser does faced with the same situation. // //Probably not the right diagnosis, but currently it seems Indy is seemingly //conflating the 302 as the tail-end "error" of proxy auth protocol (maybe?) //and is then surfacing the whole lot as the original 407 exception/response. // //However when using the redirected URL instead, the proxy auth works as expected //and no problems occur, and the test passes. So there the SSPINTLM auth works //correctly, aside from this specfic situation, it seems. LRequestStr := 'https://www.google.co.uk/?gfe_rd=cr&ei=aeJQWc2JM6nHXpaklfgG'; LIdHTTP := TIdHTTP.Create(nil); LAuth := TAuth.Create; try LSSLIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(LIdHTTP); LIdHTTP.IOHandler := LSSLIOHandler; LSSLIOHandler.SSLOptions.Method := TIdSSLVersion.sslvTLSv1_2; //The following 2 lines are unnecesary as SSPI is used automatically due to //registration of TIdSSPINTLMAuthentication as handler for NTLM. //The proxy below only supports NTLM as well. They are kept in for context. LIdHTTP.OnSelectProxyAuthorization := LAuth.DoSelectAuthorization; LIdHTTP.OnProxyAuthorization := LAuth.DoProxyAuthorization; LIdHTTP.HandleRedirects := True; LIdHTTP.AllowCookies := True; LIdHTTP.ConnectTimeout := 10000; LIdHTTP.ReadTimeout := 10000; LIdHTTP.HTTPOptions := LIdHTTP.HTTPOptions + [hoKeepOrigProtocol] + [hoInProcessAuth]; LIdHTTP.ProtocolVersion := pv1_1; LIdHTTP.ProxyParams.Clear; LIdHTTP.ProxyParams.BasicAuthentication := False; LIdHTTP.ProxyParams.ProxyServer := 'proxy'; LIdHTTP.ProxyParams.ProxyPort := 3128;
try LResult := LIdHTTP.Get(LRequestStr); except on E:EIdHTTPProtocolException do begin Fail('HTTP Error code: ' + IntToStr(E.ErrorCode)); end; end; CheckEquals(200, LIdHTTP.ResponseCode); finally LAuth.Free; LIdHTTP.Free; end; end;
TIdHTTPProtocol.ProcessResponse(). The very first thing it does after validating the response headers is check for a 3xx redirect and handle it, otherwise it checks for 4xx authentication and handle it, otherwise it checks for 2xx success and handles it, otherwise it fails.
try with AContext.Connection.IOHandler do begin CheckForDataOnSource(10); if not InputBufferIsEmpty then begin RxBufStr := InputBuffer.ExtractToString(-1, IndyTextEncoding_UTF8); Log(RxBufStr); AContext.Binding.SendTo(AContext.Binding.PeerIP, 7, RxBufStr, Id_IPv4); IdBuffer.Extract() end; end; finally end;
TIdBuffer.ExtractString(), especially with a text encoding involved. You are reading arbitrary bytes and assuming they constitute a complete sequence of characters, which is not guaranteed, so the bytes may not decode to a string properly. And, you shouldn't be calling
Binding.SendTo()directly at all (and besides, using
sendto()on a TCP/IP socket is meaningless anyway, the destination parameters are ignored so it acts the same as
send()). This kind of code is a good way to bypass TIdTCPServer's ability to auto-stop its client threads. It expects an exception to be raised when the socket is closed, but you are not allowing it to raise anything. The correct thing to do would be to use
TIdIOHandler.ReadBytes()instead, letting it block until new bytes arrive and raise an exception if the client disconnects or the server is being shutting down. And use
var RxBufStr: string; begin with AContext.Connection.IOHandler do begin CheckForDataOnSource(10); CheckForDisconnect; // <-- add this if not InputBufferIsEmpty then begin RxBufStr := InputBufferAsString(IndyTextEncoding_8Bit); // <-- don't assume any encoding Log(RxBufStr); Write(RxBufStr, IndyTextEncoding_8Bit); // <-- use TIdIOHandler.Write() instead end; end; end;
var RxBuf: TIdBytes; begin with AContext.Connection.IOHandler do begin ReadBytes(RxBuf, -1); Log(RxBuf); Write(RxBuf); end; end;
TIdTCPServer. For logging, you can assign a
TIdLogEvent, etc) to the
AContext.Connection.IOHandler.Interceptproperty, such as in the
TIdHTTPmore chances to continue authentication attempts. Please don't assume what you think is happening, debug and find out exactly what is really happening.
TIdTCPServeris to use blocking I/O that follows a defined protocol, raising exceptions on errors/disconnects/shutdowns, and let the server handle the exceptions. If you skip that logic, you become responsible for handling certain things manually, like shutdown. For instance, you could set a variable before setting
Active=False(or, just look at
Activeitself, since it is toggled to false before thread shutdowns occur), and then have
OnExecutelook at that variable, and if set then call
AContext.Connection.Disconnect, or raise your own exception, before exiting the event.
Log()itself is deadlocking, such as if it tries to synchronize with the main UI thread while the main UI thread is blocked waiting for the server to finish shutdown. Do not synchronize with the thread that is shutting down the server, that is a guaranteed deadlock. Either skip the synched operation during shutdown, or use a separate worker thread to shutdown the server so syncs can still be processed.
charand your code is not accounting for that. You can't use UTF-8 unless you ensure complete byte sequences for multi-byte characters, but your original code was not doing that. What kind of protocol is your server implementing? If the text is line-based, for instance, then you could just use
TIdIOHandler.ReadLn(), in which case using UTF-8 would be OK. But if you use
InputBufferAsStringthen all guarantees about the completeness of multi-byte sequences go out the window
@rlebeau this has worked for me and solved my problem:
with IdTCPServer1.Contexts.LockList do try for iA := Count - 1 downto 0 do begin Context := Items[iA]; if Context = nil then Continue; Context.Connection.IOHandler.WriteBufferClear; Context.Connection.IOHandler.InputBuffer.Clear; Context.Connection.IOHandler.Close; if Context.Connection.Connected then Context.Connection.Disconnect; end; finally IdTCPServer1.Contexts.UnlockList; IdTCPServer1.Active := False; end;
What do you think?
This code also suggested by you works very well:
with AContext.Connection.IOHandler do begin CheckForDataOnSource(10); CheckForDisconnect; // <-- add this if not InputBufferIsEmpty then begin RxBufStr := InputBufferAsString(IndyTextEncoding_8Bit); // <-- don't assume any encoding Log(RxBufStr); Write(RxBufStr, IndyTextEncoding_8Bit); // <-- use TIdIOHandler.Write() instead end; end;
Now my question:
Activeto false, it already disconnects the clients for you, and then waits for their threads to terminate. Your issue is that your code is blocking the threads from terminating correctly. That is what you need to fix properly, not hack around it.
procedure TForm2.Log(const s: string); begin mLog.Lines.Add(s); end;
Log() function is directly accessing a UI component (a
TMemo?). THAT IS NOT THREAD-SAFE! That could easily cause deadlocks (amongst many other problems).
OnExecute is fired in a worker thread, so
Log() MUST synchronize with the main UI thread. I suggest it use
TIdNotify for that purpose (depending on your version of Delphi). That would avoid any cross-thread access issues, avoid any deadlock scenarios, and avoid blocking the
OnExecute code (which should not need to wait on the UI to display log messages). For example:
procedure TForm2.Log(const s: string); begin TThread.Queue(nil, procedure begin mLog.Lines.Add(s); end ); end;
uses ..., IdSync; type TLog = class(TIdNotify) protected FMsg: string; procedure DoNotify; override; public constructor Create(const s: string); reintroduce; end; constructor TLog.Create(const s: string); begin inherited Create; FMsg := s; end; procedure TLog.DoNotify; begin Form2.mLog.Lines.Add(FMsg); end; procedure TForm2.Log(const s: string); begin TLog.Create(s).Notify; end;
procedure TForm1.FormCreate(Sender: TObject); var I: Integer; begin BroadcastListenerIPv6.DefaultPort := 6000; for I := 0 to BroadcastListenerIPv6.Bindings.Count - 1 do BroadcastListenerIPv6.Bindings.Items[I].Port := BroadcastListenerIPv6.DefaultPort; BroadcastListenerIPv6.MulticastGroup := 'FF02:0:0:0:0:0:0:1'; BroadcastListenerIPv6.Active := True; end;
procedure TForm1.FormCreate(Sender: TObject); var I: Integer; begin BroadcastServerIPv6.Port := 6000; BroadcastServerIPv6.MulticastGroup := 'FF02:0:0:0:0:0:0:1'; BroadcastServerIPv6.Active := True; BroadcastListenerIPv6.DefaultPort := 6000; for I := 0 to BroadcastListenerIPv6.Bindings.Count - 1 do BroadcastListenerIPv6.Bindings.Items[I].Port := BroadcastListenerIPv6.DefaultPort; BroadcastListenerIPv6.MulticastGroup := 'FF02:0:0:0:0:0:0:1'; BroadcastListenerIPv6.Active := True; end;