/*
  ICS Simple SSL Server - C++Builder translation of the Delphi demo OverbyteIcsSimpleSslServer1.pas
  (demos-delphi-vcl), ICS V9.8.

  A basic SSL server using TSslWSocketServer, the counterpart of the Simple SSL Client demo.
  Listens on 127.0.0.1 with the certificate named in the form, and for each client that
  completes the SSL handshake reads lines and answers "Unknown command: ...".

  How this C++ version differs from the Delphi original, apart from syntax:
  - FTrustedList is a std::unique_ptr member (Delphi: Create and FreeAndNil).
  - The unused BigConsole helper and the unused IniFileName property are not translated.
  - The handshake handler tests ErrCode and counts clients through the server component; the
    Delphi tests the identifier Error (which resolves to the Windows constant ERROR, 0) and casts
    the client to TWSocketServer to read ClientCount.
*/

#include <vcl.h>
#pragma hdrstop

#include "OverbyteIcsSimpleSslServer1.h"
#include "OverbyteIcsIniFiles.hpp"
#include "OverbyteIcsUtils.hpp"

#pragma package(smart_init)
#pragma resource "*.dfm"

TSimpleSslServerForm *SimpleSslServerForm;

namespace {
const wchar_t *const SectionWindow = L"Window";
const wchar_t *const KeyTop = L"Top";
const wchar_t *const KeyLeft = L"Left";
const wchar_t *const KeyWidth = L"Width";
const wchar_t *const KeyHeight = L"Height";
const wchar_t *const SectionData = L"Data";
const wchar_t *const KeyPort = L"Port";
const wchar_t *const KeyCertFile = L"CertFile";
const wchar_t *const KeyPassPhrase = L"PassPhrase";
const wchar_t *const KeyPrivKeyFile = L"PrivKeyFile";
const wchar_t *const KeyCAFile = L"CAFile";
const wchar_t *const KeyCAPath = L"CAPath";
const wchar_t *const KeyVerifyPeer = L"VerifyPeer";
}

__fastcall TSimpleSslServerForm::TSimpleSslServerForm(TComponent *Owner)
    : TForm(Owner), FInitialized(false)
{
}

__fastcall TSimpleSslServerForm::~TSimpleSslServerForm()
{
    // Free the components (and so close the server and its clients) while FTrustedList still exists.
    DestroyComponents();
}

void __fastcall TSimpleSslServerForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    FIniFileName = GetIcsIniFileName();
    FTrustedList.reset(new TStringList);
}

void __fastcall TSimpleSslServerForm::FormDestroy(TObject *Sender)
{
}

void __fastcall TSimpleSslServerForm::FormShow(TObject *Sender)
{
    if (FInitialized)
        return;
    FInitialized = true;

    std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(FIniFileName, true));
    Width = ini->ReadInteger(SectionWindow, KeyWidth, Width);
    Height = ini->ReadInteger(SectionWindow, KeyHeight, Height);
    Top = ini->ReadInteger(SectionWindow, KeyTop, (Screen->Height - Height) / 2);
    Left = ini->ReadInteger(SectionWindow, KeyLeft, (Screen->Width - Width) / 2);
    PortEdit->Text = ini->ReadString(SectionData, KeyPort, "443");
    CertFileEdit->Text = ini->ReadString(SectionData, KeyCertFile,
        "C:\\ProgramData\\ICS-OpenSSL\\ICS-Certs\\localhost-bundle.pem");
    PrivKeyFileEdit->Text = ini->ReadString(SectionData, KeyPrivKeyFile, "");
    PassPhraseEdit->Text = ini->ReadString(SectionData, KeyPassPhrase, "password");
    CAFileEdit->Text = ini->ReadString(SectionData, KeyCAFile, "");
    CAPathEdit->Text = ini->ReadString(SectionData, KeyCAPath, "");
    VerifyPeerCheckBox->Checked = (ini->ReadInteger(SectionData, KeyVerifyPeer, 0) != 0);
    DisplayMemo->Clear();
}

void __fastcall TSimpleSslServerForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    std::unique_ptr<TIcsIniFile> ini(new TIcsIniFile(FIniFileName, true));
    ini->WriteInteger(SectionWindow, KeyTop, Top);
    ini->WriteInteger(SectionWindow, KeyLeft, Left);
    ini->WriteInteger(SectionWindow, KeyWidth, Width);
    ini->WriteInteger(SectionWindow, KeyHeight, Height);
    ini->WriteString(SectionData, KeyPort, PortEdit->Text);
    ini->WriteString(SectionData, KeyCertFile, CertFileEdit->Text);
    ini->WriteString(SectionData, KeyPrivKeyFile, PrivKeyFileEdit->Text);
    ini->WriteString(SectionData, KeyPassPhrase, PassPhraseEdit->Text);
    ini->WriteString(SectionData, KeyCAFile, CAFileEdit->Text);
    ini->WriteString(SectionData, KeyCAPath, CAPathEdit->Text);
    ini->WriteInteger(SectionData, KeyVerifyPeer, VerifyPeerCheckBox->Checked ? 1 : 0);
    ini->UpdateFile();
}

// Appends a line, keeping the memo to the last 200 lines, and scrolls to the end.
void __fastcall TSimpleSslServerForm::Display(const String &Msg)
{
    DisplayMemo->Lines->BeginUpdate();
    __try {
        while (DisplayMemo->Lines->Count > 200)
            DisplayMemo->Lines->Delete(0);
        DisplayMemo->Lines->Add(Msg);
    }
    __finally {
        DisplayMemo->Lines->EndUpdate();
        SendMessage(DisplayMemo->Handle, EM_SCROLLCARET, 0, 0);
    }
}

void __fastcall TSimpleSslServerForm::StartButtonClick(TObject *Sender)
{
    if (!FileExists(CertFileEdit->Text)) {
        Display("SSL server certificate required");
        return;
    }
    IcsSslRootCAStore->Initialise();   // load OpenSSL and the internal roots
    Display("SSL/TLS DLL: " + IcsReportOpenSSLVer(true));
    SslWSocketServer1->Proto = "tcp";
    SslWSocketServer1->Addr = "127.0.0.1";   // localhost only
    SslWSocketServer1->Port = PortEdit->Text;
    SslWSocketServer1->SslEnable = true;
    SslWSocketServer1->WSDebugOptions = DefWSDebugServer;   // what to log to the OnDebugDisplay event
    SslContext1->SslCertFile = CertFileEdit->Text;
    SslContext1->SslPassPhrase = PassPhraseEdit->Text;
    SslContext1->SslPrivKeyFile = PrivKeyFileEdit->Text;
    SslContext1->SslCAFile = CAFileEdit->Text;
    SslContext1->SslCAPath = CAPathEdit->Text;
    SslContext1->SslVerifyPeer = VerifyPeerCheckBox->Checked;

    try {
        SslContext1->InitContext();
    }
    catch (Exception &E) {
        Display("Failed to initialize SSL Context: " + E.Message);
        return;
    }

    SslWSocketServer1->SetAcceptableHostsList(AcceptableHostsEdit->Text);
    SslWSocketServer1->Listen();
    SslWSocketServer1->ClientClass = __classid(TTcpSrvClient);   // the server creates our client class
    Display("Listening...");
}

void __fastcall TSimpleSslServerForm::SslWSocketServer1ClientConnect(TObject *Sender, TWSocketClient *Client, WORD Error)
{
    // A successful connect is only reported once the SSL handshake is done.
    if (Error != 0)
        Display("Client connection failed");
}

void __fastcall TSimpleSslServerForm::SslWSocketServer1DebugDisplay(TObject *Sender, const String Msg)
{
    Display(Msg);
}

void __fastcall TSimpleSslServerForm::SslWSocketServer1SslHandshakeDone(TObject *Sender, WORD ErrCode, TX509Base *PeerCert, bool &Disconnect)
{
    TTcpSrvClient *client = static_cast<TTcpSrvClient*>(Sender);
    Display(client->SslHandshakeRespMsg);

    if (ErrCode != 0)   // Delphi tests the Windows constant ERROR here, so it never skips a failed handshake
        return;

    // Delphi casts the client to TWSocketServer to get ClientCount; ask the server itself.
    Display("There is now " + IntToStr(SslWSocketServer1->ClientCount) + " clients connected.");
    client->LineMode = true;
    client->LineEdit = true;
    client->LineLimit = 80;   // do not accept long lines
    client->OnDataAvailable = ClientDataAvailable;
    client->OnLineLimitExceeded = ClientLineLimitExceeded;
    client->OnBgException = ClientBgException;
    client->OnSslVerifyPeer = ClientVerifyPeer;
    client->ConnectTime = Now();
}

void __fastcall TSimpleSslServerForm::ClientBgException(TObject *Sender, Exception *E, bool &CanClose)
{
    Display("Client exception occured: " + E->ClassName() + ": " + E->Message);
    CanClose = true;   // Goodbye client !
}

void __fastcall TSimpleSslServerForm::ClientDataAvailable(TObject *Sender, WORD Error)
{
    TTcpSrvClient *client = static_cast<TTcpSrvClient*>(Sender);
    // Line mode: we receive complete lines.
    client->RcvdLine = client->ReceiveStr();
    while (client->RcvdLine.Length() > 0) {
        wchar_t last = client->RcvdLine[client->RcvdLine.Length()];
        if (last != L'\r' && last != L'\n')
            break;
        client->RcvdLine.SetLength(client->RcvdLine.Length() - 1);
    }
    Display("Received from " + client->GetPeerAddr() + ": '" + client->RcvdLine + "'");
    ProcessData(client);
}

void __fastcall TSimpleSslServerForm::ClientLineLimitExceeded(TObject *Sender, int Cnt, bool &ClearData)
{
    TTcpSrvClient *client = static_cast<TTcpSrvClient*>(Sender);
    Display("Line limit exceeded from " + client->GetPeerAddr() + ". Closing.");
    ClearData = true;
    client->Close();
}

void __fastcall TSimpleSslServerForm::ProcessData(TTcpSrvClient *Client)
{
    if (Client->State == wsConnected)
        Client->SendStr("Unknown command: '" + Client->RcvdLine + "'\r\n");
}

void __fastcall TSimpleSslServerForm::StopButtonClick(TObject *Sender)
{
    SslWSocketServer1->Close();
    Display("Server stopped");
}

void __fastcall TSimpleSslServerForm::WMSslNotTrusted(TMessage &Msg)
{
    if (Application->MessageBox(("Do you want to trust this certificate ?\n" + FNotTrusted).c_str(),
            Caption.c_str(), MB_YESNO + MB_DEFBUTTON2) == ID_YES)
        FTrustedList->Add(FNotTrusted);
}

// Called when a client sends a certificate (very rare).
void __fastcall TSimpleSslServerForm::ClientVerifyPeer(TObject *Sender, int &Ok, TX509Base *Cert)
{
    String issuer = Cert->IssuerOneLine;
    if (Ok != 0) {
        Display("Received certificate. Issuer = \"" + issuer + "\"");
        return;
    }
    if (Cert->VerifyResult == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) {
        if (FTrustedList->IndexOf(issuer) >= 0) {
            Display("Received certificate. Issuer = \"" + issuer + "\"");
            Display("We trust this one");
            Ok = 1;
            return;
        }
        FNotTrusted = issuer;
        static_cast<TSslWSocket*>(Sender)->CloseDelayed();
        PostMessage(Handle, WM_SSL_NOT_TRUSTED, 0, 0);   // ask the operator from the message loop, not inside the handshake
        return;
    }
    Display("Can't verify certificate:");
    Display("  Issuer = \"" + issuer + "\"");
    Display("  Error  = " + IntToStr(Cert->VerifyResult) + " (" + Cert->VerifyErrMsg + ")");
}
