{
  $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.0    27-03-05 10:04:20  MterWoord
  Second import, first time the filenames weren't prefixed with Id

  Rev 1.0    27-03-05 09:08:54  MterWoord
  Created
}

unit IdServerIOHandlerTls;

interface

uses
  IdSSL, IdTlsServerOptions, Mono.Security.Protocol.Tls, IdCarrierStream,
  IdSocketStream, System.IO, System.Security.Cryptography, IdGlobal, IdYarn,
  System.Security.Cryptography.X509Certificates, Mono.Security.Authenticode,
  IdIOHandler, IdSocketHandle, IdThread;

type
  TIdServerIOHandlerTls = class(TIdServerIOHandlerSSLBase)
  protected
    FOptions: TIdTlsServerOptions;
    function NewServerSideIOHandlerTls: TIdSSLIOHandlerSocketBase;
    procedure InitComponent; override;
  public
    function MakeFTPSvrPasv: TIdSSLIOHandlerSocketBase; override;
    function MakeFTPSvrPort: TIdSSLIOHandlerSocketBase; override;
    function MakeClientIOHandler(AYarn: TIdYarn) : TIdIOHandler; override;
    function MakeClientIOHandler: TIdSSLIOHandlerSocketBase; override;
    function Accept(ASocket: TIdSocketHandle; AListenerThread: TIdThread; AYarn: TIdYarn): TIdIOHandler; override;
  published
    property Options: TIdTlsServerOptions read FOptions write FOptions;
  end;


implementation

type
  TIdServerSideIOHandlerTls = class(TIdSSLIOHandlerSocketBase)
  protected
    FOptions: TIdTlsServerOptions;
    FTlsServerStream: SslServerStream;
    FTlsClientStream: SslClientStream;
    FCarrierStream: TIdCarrierStream;
    FSocketStream: TIdSocketStream;
    FActiveStream: Stream;
    FPassThrough: Boolean;
    function PrivateKeySelection(certificate: X509Certificate; TargetHost: string): AsymmetricAlgorithm;
    function ReadFromSource(ARaiseExceptionIfDisconnected: Boolean; ATimeout: Integer; ARaiseExceptionOnTimeOut: Boolean): Integer; override;
    procedure SetPassThrough(const AValue: Boolean); override;
  public
    procedure CheckForDataOnSource(ATimeOut: Integer); override;
    procedure StartSSL; override;
    procedure AfterAccept; override;
    procedure CheckForDisconnect(ARaiseExceptionIfDisconnected: Boolean; AIgnoreBuffer: Boolean); override;
    function Clone: TIdSSLIOHandlerSocketBase; override;
    procedure WriteDirect(var ABuffer: TIdBytes); override;
    procedure Close; override;
  published
    property Options: TIdTlsServerOptions read FOptions write FOptions;
  end;

{ TServerSideIOHandlerTls }

function TIdServerSideIOHandlerTls.Clone: TIdSSLIOHandlerSocketBase;
var
  TEmpResult : TIdServerSideIOHandlerTls;
begin
  TempResult := TIdServerSideIOHandlerTls.Create;
  TempResult.Options.ClientNeedsCertificate := Options.ClientNeedsCertificate;
  TempResult.Options.PrivateKey := Options.PrivateKey;
  TempResult.Options.Protocol := Options.Protocol;
  TempResult.Options.PublicCertificate := Options.PublicCertificate;
	TempResult.IsPeer := IsPeer;
	TempResult.PassThrough := PassThrough;
  Result := TempResult;
end;

procedure TIdServerSideIOHandlerTls.StartSSL;
begin
  inherited;
  PassThrough := False;
end;

function TIdServerSideIOHandlerTls.PrivateKeySelection(
  certificate: X509Certificate; TargetHost: string): AsymmetricAlgorithm;
begin
  Result := FOptions.PrivateKey.RSA;
end;

function TIdServerSideIOHandlerTls.ReadFromSource(
  ARaiseExceptionIfDisconnected: Boolean; ATimeout: Integer;
  ARaiseExceptionOnTimeOut: Boolean): Integer;
var
  TempBuff: array of byte;
  TotalBytesRead: Integer;
  StartTime: Cardinal;
  BytesRead: Integer;
  TempBytes: array of byte;
begin
  Result := 0;
  if FInputBuffer = nil then
    Exit;
  if FActiveStream <> nil then
  begin
    SetLength(TempBuff, 512);
    TotalBytesRead := 0;
    StartTime := Ticks;
    repeat
      BytesRead := FActiveStream.Read(TempBuff, 0, 512);
      if BytesRead <> 0 then
      begin
        TempBytes := ToBytes(TempBuff, BytesRead);
        FInputBuffer.Write(TempBytes);
        TotalBytesRead := TotalBytesRead + BytesRead;
      end;
      if BytesRead <> 512 then
      begin
        Result := TotalBytesRead;
        Exit;
      end;
      Sleep(2);
    until (   (Abs(GetTickDiff(StartTime, Ticks)) > ATimeOut)
           and (not ((ATimeOut = IdTimeoutDefault) or (ATimeOut = IdTimeoutInfinite)))
           );
    Result := TotalBytesRead;
  end;
end;

procedure TIdServerSideIOHandlerTls.CheckForDisconnect(
  ARaiseExceptionIfDisconnected, AIgnoreBuffer: Boolean);
begin
  try
    if FActiveStream = nil then
    begin
      if AIgnoreBuffer then
      begin
        CloseGracefully;
      end
      else
      begin
        if FInputBuffer.Size = 0 then
        begin
          CloseGracefully;
        end;
      end;
    end
    else
    begin
      if (    (not FActiveStream.CanRead)
          or  (not FActiveStream.CanWrite)
          ) then
      begin
        if AIgnoreBuffer then
        begin
          CloseGracefully;
        end
        else
        begin
          if FInputBuffer.Size = 0 then
          begin
            CloseGracefully;
          end;
        end;
      end;
    end;
  except
    on E: Exception do
    begin
      CloseGracefully;
    end;
  end;
  if (    (ARaiseExceptionIfDisconnected)
      and (ClosedGracefully)
      ) then
    RaiseConnClosedGracefully;
end;

procedure TIdServerSideIOHandlerTls.CheckForDataOnSource(ATimeOut: Integer);
begin
  if Connected then
  begin
    ReadFromSource(false, ATimeOut, false);
  end;
end;

procedure TIdServerSideIOHandlerTls.AfterAccept;
begin
  inherited;
  FSocketStream := TIdSocketStream.Create(Binding.Handle);
  FCarrierStream := TIdCarrierStream.Create(FSocketStream);
  FTlsServerStream := SslServerStream.Create(FCarrierStream, FOptions.PublicCertificate, FOptions.ClientNeedsCertificate, true, FOptions.Protocol);
  GC.SuppressFinalize(FSocketStream);
  GC.SuppressFinalize(FCarrierStream);
  GC.SuppressFinalize(FTlsServerStream);
  FActiveStream := FCarrierStream;
  FTlsServerStream.PrivateKeyCertSelectionDelegate := PrivateKeySelection;
  IsPeer := true;
  PassThrough := true;
end;

procedure TIdServerSideIOHandlerTls.Close;
begin
  if not PassThrough then
  begin
    if IsPeer then
    begin
      if FTlsServerStream <> nil then
      begin
        FTlsServerStream.Close;
        FTlsServerStream := nil;
      end;
    end
    else
    begin
      if FTlsClientStream <> nil then
      begin
        FTlsClientStream.Close;
        FTlsClientStream := nil;
      end;
    end;
  end;
  if FCarrierStream <> nil then
  begin
    FCarrierStream.Close;
    FCarrierStream := nil;
  end;
  if FSocketStream <> nil then
  begin
    FSocketStream.Close;
    FSocketStream := nil;
  end;
  inherited;
end;

procedure TIdServerSideIOHandlerTls.WriteDirect(var ABuffer: TIdBytes);
begin
  if Intercept <> nil then
    Intercept.Send(ABuffer);

  if FActiveStream <> nil then
  begin
    FActiveStream.Write(ABuffer, 0, Length(ABuffer));
    FActiveStream.Flush;
  end
  else
    raise Exception.Create('No active stream!');
end;

procedure TIdServerSideIOHandlerTls.SetPassThrough(const AValue: Boolean);
var
  TempBuff: array[0..0] of byte;
begin
  inherited;
  if AValue then
  begin
    if FActiveStream <> nil then
    begin
      FActiveStream.Close;
      FActiveStream := nil;
    end;
    FActiveStream := FSocketStream;
    if IsPeer then
    begin
      if FTlsServerStream <> nil then
      begin
        FTlsServerStream.Close;
        FTlsServerStream := nil;
      end;
      FTlsServerStream := SslServerStream.Create(FCarrierStream, FOptions.PublicCertificate, FOptions.ClientNeedsCertificate, true, FOptions.Protocol);
      GC.SuppressFinalize(FTlsServerStream);
      FTlsServerStream.PrivateKeyCertSelectionDelegate := PrivateKeySelection;
    end
    else
    begin
      if FTlsClientStream <> nil then
      begin
        FTlsClientStream.Close;
        FTlsClientStream := nil;
      end;
      FTlsClientStream := SslClientStream.Create(FCarrierStream, Destination, true, FOptions.Protocol);
      GC.SuppressFinalize(FTlsClientStream);
    end;
  end
  else
  begin
    if IsPeer then
    begin
      FActiveStream := FTlsServerStream;
    end
    else
    begin
      FActiveStream := FTlsClientStream;
    end;
    FActiveStream.Read(TempBuff, 0, 0);
  end;
end;

{ TServerIOHandlerTls }

procedure TIdServerIOHandlerTls.InitComponent;
begin
  inherited;
  FOptions := TIdTlsServerOptions.Create;
end;

function TIdServerIOHandlerTls.NewServerSideIOHandlerTls: TIdSSLIOHandlerSocketBase;
var
  TempResult: TIdServerSideIOHandlerTls;
begin
  TempResult := TIdServerSideIOHandlerTls.Create;
  TempResult.Options := FOptions;
  Result := TempResult;
end;

function TIdServerIOHandlerTls.MakeClientIOHandler: TIdSSLIOHandlerSocketBase;
begin
  Result := NewServerSideIOHandlerTls;
end;

function TIdServerIOHandlerTls.MakeClientIOHandler(AYarn: TIdYarn): TIdIOHandler;
begin
  Result := NewServerSideIOHandlerTls;
end;

function TIdServerIOHandlerTls.MakeFTPSvrPort: TIdSSLIOHandlerSocketBase;
begin
  Result := NewServerSideIOHandlerTls;
end;

function TIdServerIOHandlerTls.Accept(ASocket: TIdSocketHandle;
  AListenerThread: TIdThread; AYarn: TIdYarn): TIdIOHandler;
var
  LIOHandler: TIdServerSideIOHandlerTls;
begin
  LIOHandler := TIdServerSideIOHandlerTls.Create;
  LIOHandler.Options := FOptions;
  LIOHandler.Open;
  while not AListenerThread.Stopped do
  begin
    try
      if ASocket.Select(250) then
      begin
        if LIOHandler.Binding.Accept(ASocket.Handle) then
        begin
          LIOHandler.AfterAccept;
          Result := LIOHandler;
          Exit;
        end
        else
        begin
          LIOHandler.Close;
          Result := nil;
          Exit;
        end;
      end;
    finally
      if AListenerThread.Stopped then
      begin
        LIOHandler.Close;
      end;
    end;
  end;
  Result := nil;
end;

function TIdServerIOHandlerTls.MakeFTPSvrPasv: TIdSSLIOHandlerSocketBase;
begin
  Result := NewServerSideIOHandlerTls;
end;

end.
