{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
  Rev 1.14    10/26/2004 9:09:36 PM  JPMugaas
  Updated references.

  Rev 1.13    2004.02.03 5:45:36 PM  czhower
  Name changes

  Rev 1.12    1/25/2004 3:52:28 PM  JPMugaas
  Fixes for abstract SSL interface to work in NET.

  Rev 1.11    1/21/2004 1:23:38 PM  JPMugaas
  InitComponent.

  Rev 1.10    5/25/2003 12:06:16 AM  JPMugaas
  TLS checking code moved into a protected method for reuse in TIdDirectSMTP.
  Note that TLS support is different in that component because of the way it
  works.

    Rev 1.9    5/21/2003 3:36:42 PM  BGooijen
  Fixed design time bug regarding the Active property

  Rev 1.8    5/8/2003 11:27:38 AM  JPMugaas
  Moved feature negoation properties down to the ExplicitTLSClient level as
  feature negotiation goes hand in hand with explicit TLS support.

  Rev 1.7    4/13/2003 05:38:02 PM  JPMugaas
  Fix for SetTLS exception problem with IdMessage.SaveToFile.

  Rev 1.6    4/5/2003 02:06:48 PM  JPMugaas
  TLS handshake itself can now be handled.

  Rev 1.5    3/27/2003 05:46:22 AM  JPMugaas
  Updated framework with an event if the TLS negotiation command fails.
  Cleaned up some duplicate code in the clients.

  Rev 1.4    3/26/2003 04:19:18 PM  JPMugaas
  Cleaned-up some code and illiminated some duplicate things.

    Rev 1.3    3/23/2003 11:45:02 PM  BGooijen
  classes -> Classes

  Rev 1.2    3/18/2003 04:36:52 PM  JPMugaas

  Rev 1.1    3/16/2003 06:08:34 PM  JPMugaas
  Fixed a bug where the wrong port number was being set.  I also expanded a few
  things for the server.

  Rev 1.0    3/16/2003 02:38:08 PM  JPMugaas
  Base class for some clients that use both implicit and explicit TLS.
}

unit IdExplicitTLSClientServerBase;

interface

{$i IdCompilerDefines.inc}

uses
  Classes,
  IdCmdTCPServer,
  IdException,
  IdGlobal,
  IdIOHandler,
  IdServerIOHandler,
  IdTCPClient;

type
  TIdUseTLS = (
    utNoTLSSupport,
    utUseImplicitTLS, // ssl iohandler req, allways tls
    utUseRequireTLS, // ssl iohandler req, user command only accepted when in tls
    utUseExplicitTLS // < user can choose to use tls
    );

const
  ExplicitTLSVals = [utUseRequireTLS,utUseExplicitTLS];
  DEF_USETLS = utNoTLSSupport; //we can't assume the user wants to use a SSL IOHandler

type
  TIdOnTLSNegotiationFailure = procedure(Asender : TObject; var VContinue : Boolean) of object;

  TIdExplicitTLSServer = class(TIdCmdTCPServer)
  protected
    FRegularProtPort : TIdPort;
    FImplicitTLSProtPort : TIdPort;
    FUseTLS : TIdUseTLS;
    procedure Loaded; override;
    procedure SetIOHandler(const AValue: TIdServerIOHandler); override;
    procedure SetUseTLS(AValue : TIdUseTLS); virtual;
    property UseTLS : TIdUseTLS read FUseTLS write SetUseTLS default  DEF_USETLS;
    procedure InitComponent; override;
  end;

  TIdExplicitTLSClient = class(TIdTCPClientCustom)
  protected
    FRegularProtPort : TIdPort;
    FImplicitTLSProtPort : TIdPort;
    FUseTLS : TIdUseTLS;
    FOnTLSNotAvailable : TIdOnTLSNegotiationFailure;
    FOnTLSNegCmdFailed : TIdOnTLSNegotiationFailure;
    FOnTLSHandShakeFailed : TIdOnTLSNegotiationFailure;

    //feature negotiation stuff
    FCapabilities : TStrings;
    function GetSupportsTLS : Boolean; virtual; 
    procedure CheckIfCanUseTLS; virtual;
    procedure Loaded; override;
    procedure TLSNotAvailable;
    procedure DoOnTLSNotAvailable;
    procedure ProcessTLSNotAvail;

    procedure TLSNegCmdFailed;
    procedure DoOnTLSNegCmdFailed;
    procedure ProcessTLSNegCmdFailed;

    procedure TLSHandShakeFailed;
    procedure DoOnTLSHandShakeFailed;
    procedure ProcessTLSHandShakeFailed;

    procedure SetIOHandler(AValue: TIdIOHandler); override;
    procedure SetUseTLS(AValue : TIdUseTLS); virtual;
    //Note TLSHandshake should be the ONLY method to do the actual TLS
    //or SSL handshake for explicit TLS clients.
    procedure TLSHandshake; virtual;
    procedure InitComponent; override;
    property UseTLS : TIdUseTLS read FUseTLS write SetUseTLS default DEF_USETLS;
  public
    destructor Destroy; override;
    procedure Connect; override;
    property SupportsTLS: boolean read GetSupportsTLS;
    property Capabilities : TStrings read FCapabilities;
    property OnTLSHandShakeFailed : TIdOnTLSNegotiationFailure read FOnTLSHandShakeFailed write FOnTLSHandShakeFailed;
    property OnTLSNotAvailable : TIdOnTLSNegotiationFailure read FOnTLSNotAvailable write FOnTLSNotAvailable;
    property OnTLSNegCmdFailed : TIdOnTLSNegotiationFailure read FOnTLSNegCmdFailed write FOnTLSNegCmdFailed;
  end;

  EIdTLSClientException = class(EIdException);
  EIdTLSClientSSLIOHandlerRequred = class(EIdTLSClientException);
  EIdTLSClientCanNotSetWhileConnected = class(EIdTLSClientException);
  EIdTLSClientTLSNotAvailable = class(EIdTLSClientException);
  EIdTLSClientTLSNegCmdFailed = class(EIdTLSClientException);
  EIdTLSClientTLSHandShakeFailed = class(EIdTLSClientException);
  EIdTLSServerException = class(EIdException);
  EIdTLSServerSSLIOHandlerRequired = class(EIdTLSServerException);
  EIdTLSClientCanNotSetWhileActive = class(EIdTLSClientException);

implementation

uses
  IdResourceStringsProtocols, IdSSL, IdBaseComponent, SysUtils;

{ TIdExplicitTLSServer }

procedure TIdExplicitTLSServer.InitComponent;
begin
  inherited InitComponent;
  FUseTLS := DEF_USETLS;
end;

procedure TIdExplicitTLSServer.Loaded;
begin
  inherited Loaded;
  if not (IOHandler is TIdServerIOHandler) then begin
    SetUseTLS(utNoTLSSupport);
  end;
end;

procedure TIdExplicitTLSServer.SetIOHandler(const AValue: TIdServerIOHandler);
begin
  inherited SetIOHandler(AValue);
  if not (IOHandler is TIdServerIOHandlerSSLBase) then begin
    SetUseTLS(utNoTLSSupport);
  end;
end;

procedure TIdExplicitTLSServer.SetUseTLS(AValue: TIdUseTLS);
begin
  if (not Active) or IsDesignTime then
  begin
    if IsLoading then begin
      FUseTLS := AValue;
      Exit;
    end;
    if (not (IOHandler is TIdServerIOHandlerSSLBase)) and (AValue <> utNoTLSSupport) then begin
      raise EIdTLSServerSSLIOHandlerRequired.Create(RSTLSSSLIOHandlerRequired);
    end;
    if FUseTLS <> AValue then
    begin
      if AValue = utUseImplicitTLS then
      begin
        if DefaultPort = FRegularProtPort then begin
          DefaultPort := FImplicitTLSProtPort;
        end;
      end else
      begin
        if DefaultPort = FImplicitTLSProtPort then begin
          DefaultPort := FRegularProtPort;
        end;
      end;
      FUseTLS := AValue;
    end;
  end else begin
    raise EIdTLSClientCanNotSetWhileActive.Create(RSTLSSLCanNotSetWhileConnected);
  end;
end;

{ TIdExplicitTLSClient }

procedure TIdExplicitTLSClient.CheckIfCanUseTLS;
begin
  if not (IOHandler is TIdSSLIOHandlerSocketBase) then begin
    raise EIdTLSClientSSLIOHandlerRequred.Create(RSTLSSSLIOHandlerRequired);
  end;
end;

procedure TIdExplicitTLSClient.Connect;
begin
  if UseTLS in ExplicitTLSVals then begin
    // TLS only enabled later in this case!
    (IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := True;
  end;
  if (IOHandler is TIdSSLIOHandlerSocketBase) then begin
    case FUseTLS of
      utNoTLSSupport :
        begin
          (IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := True;
        end;
      utUseImplicitTLS :
        begin
          (IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := False;
        end;
      else
        begin
          if FUseTLS <> utUseImplicitTLS then begin
            (IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := True;
          end;
        end;
    end;
  end;
  inherited Connect;
end;

procedure TIdExplicitTLSClient.InitComponent; 
begin
  inherited InitComponent;
  FCapabilities := TStringList.Create;
  FUseTLS := DEF_USETLS;
end;

destructor TIdExplicitTLSClient.Destroy;
begin
  FreeAndNil(FCapabilities);
  inherited Destroy;
end;

//OnTLSHandShakeFailed
procedure TIdExplicitTLSClient.DoOnTLSHandShakeFailed;
var
  LContinue : Boolean;
begin
  LContinue := False;
  if Assigned(OnTLSHandShakeFailed) then begin
    FOnTLSHandShakeFailed(Self, LContinue);
  end;
  if not LContinue then begin
    TLSHandShakeFailed;
  end;
end;

procedure TIdExplicitTLSClient.DoOnTLSNegCmdFailed;
var
  LContinue : Boolean;
begin
  LContinue := False;
  if Assigned(OnTLSNegCmdFailed) then begin
    FOnTLSNegCmdFailed(Self, LContinue);
  end;
  if not LContinue then begin
    TLSNegCmdFailed;
  end;
end;

procedure TIdExplicitTLSClient.DoOnTLSNotAvailable;
var
  LContinue : Boolean;
begin
  LContinue := True;
  if Assigned(FOnTLSNotAvailable) then begin
    FOnTLSNotAvailable(Self, LContinue);
  end;
  if not LContinue then begin
    TLSNotAvailable;
  end;
end;

procedure TIdExplicitTLSClient.Loaded;
begin
  inherited Loaded;
  if not (IOHandler is TIdSSLIOHandlerSocketBase) then begin
    SetUseTLS(utNoTLSSupport);
  end;
end;

procedure TIdExplicitTLSClient.ProcessTLSHandShakeFailed;
begin
  if FUseTLS = utUseRequireTLS then begin
    TLSHandShakeFailed;
  end else begin
    DoOnTLSHandShakeFailed;
  end;
end;

procedure TIdExplicitTLSClient.ProcessTLSNegCmdFailed;
begin
  if FUseTLS = utUseRequireTLS then begin
    TLSNegCmdFailed;
  end else begin
    DoOnTLSNegCmdFailed;
  end;
end;

procedure TIdExplicitTLSClient.ProcessTLSNotAvail;
begin
  if FUseTLS = utUseRequireTLS then begin
    TLSNotAvailable;
  end else begin
    DoOnTLSNotAvailable;
  end;
end;

procedure TIdExplicitTLSClient.SetIOHandler(AValue: TIdIOHandler);
begin
  inherited SetIOHandler(AValue);
  if not (IOHandler is TIdSSLIOHandlerSocketBase) then begin
    if FUseTLS <> utNoTLSSupport then begin
      SetUseTLS(utNoTLSSupport);
    end;
  end;
end;

procedure TIdExplicitTLSClient.SetUseTLS(AValue: TIdUseTLS);
begin
  if Connected then begin
    raise EIdTLSClientCanNotSetWhileConnected.Create(RSTLSSLCanNotSetWhileConnected);
  end;
  if IsLoading then begin
    FUseTLS := AValue;
    Exit;
  end;
  if AValue <> utNoTLSSupport then begin
    CheckIfCanUseTLS;
  end;
  if FUseTLS <> AValue then begin
    if AValue = utUseImplicitTLS then begin
      if Port = FRegularProtPort then begin
        Port := FImplicitTLSProtPort;
      end;
    end else begin
      if Port = FImplicitTLSProtPort then begin
        Port := FRegularProtPort;
      end;
    end;
    FUseTLS := AValue;
  end;
end;

procedure TIdExplicitTLSClient.TLSHandshake;
begin
  try
    if (IOHandler is TIdSSLIOHandlerSocketBase) then begin
      (IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := False;
    end;
  except
    ProcessTLSHandShakeFailed;
  end;
end;

procedure TIdExplicitTLSClient.TLSHandShakeFailed;
begin
  if Connected then begin
    // RLebeau 9/19/2013: do not send a goodbye command to the peer.
    // The socket data may be in a bad state at this point!
    Disconnect(False);
  end;
  // This method should always be called in the context of an active 'except'
  // block, so use IndyRaiseOuterException() to capture the inner exception
  // (if possible) when raising this outer exception...
  IndyRaiseOuterException(EIdTLSClientTLSHandShakeFailed.Create(RSTLSSLSSLHandshakeFailed));
end;

procedure TIdExplicitTLSClient.TLSNegCmdFailed;
begin
  if Connected then begin
    Disconnect;
  end;
  // This method should never be called in the context of an active 'except'
  // block, so do not use IndyRaiseOuterException() to capture an inner exception
  // when raising this exception...
  raise EIdTLSClientTLSNegCmdFailed.Create(RSTLSSLSSLCmdFailed);
end;

procedure TIdExplicitTLSClient.TLSNotAvailable;
begin
  if Connected then begin
    Disconnect;
  end;
  raise EIdTLSClientTLSNotAvailable.Create(RSTLSSLSSLNotAvailable);
end;

function TIdExplicitTLSClient.GetSupportsTLS: boolean;
begin
  //this is a dummy for descendants to override.  NET doesn't support
  //abstract methods.
  Result := False;
end;

end.
