/*
  ICS SSL X509 Certificates Demo - C++Builder translation of the Delphi demo
  OverbyteIcsX509CertsTst1.pas (demos-delphi-vcl), ICS V9.8.

  Orders SSL/TLS X509 certificates with the TSslX509Certs component: free certificates
  from Let's Encrypt and other ACME V2 suppliers (Google Trust Services, ZeroSSL,
  Digicert, SSL.com), domain validated by a local challenge web server, a UNC file
  share, DNS (Windows DNS Server over WMI, or by hand), or a running ICS server; and
  certificates signed by your own CA or self signed. Supplier accounts are kept in an
  accounts database, and each account's orders in its own control database. Usage is
  described in OverbyteIcsSslX509Certs.pas and at
  https://wiki.overbyte.eu/wiki/index.php/FAQ_Order_SSL_Certificates

  How this C++ version differs from the Delphi original, apart from syntax:
  - The challenge web server's country/ASN lookup takes the Delphi path for compilers
    without USE_IcsGeoTools (no lookup).
  - The Windows certificate store location is set only when the ICS C++ library has
    OverbyteIcsMsSslUtils.hpp (ICS_HAVE_MSCRYPT, below the includes); the standard ICS
    build undefines MSCRYPT_Tools for C++Builder.
  - OverbyteIcsOAuthFormVclE, in the Delphi uses clause, is not used by this demo, so it
    is not linked.
  - The log file stream has no Owner and is held in std::unique_ptr.
  - The ini settings are saved and restored from tables of controls, with the key derived
    from the control name; the keys are the ones the Delphi demo writes.
  - Delphi demo bugs are fixed here; each fix has a one-line comment.
*/

#include <vcl.h>
#pragma hdrstop

#include <algorithm>
#include <initializer_list>
#include <memory>

#include "OverbyteIcsX509CertsTst1.h"
#include "OverbyteIcsUtils.hpp"
#include "OverbyteIcsIniFiles.hpp"
#include "OverbyteIcsWSocket.hpp"
#include "OverbyteIcsSslBase.hpp"
#include "OverbyteIcsSslHttpOAuth.hpp"
#include "OverbyteIcsWmi.hpp"
#include "OverbyteIcsWinUtils.hpp"
#if __has_include(<OverbyteIcsMsSslUtils.hpp>)
#define ICS_HAVE_MSCRYPT 1
#include "OverbyteIcsMsSslUtils.hpp"
#endif

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

TX509CertsForm *X509CertsForm;

namespace {

const wchar_t *const SectionMainWindow = L"MainWindow";
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 KeyCertSANGrid    = L"CertSANGrid";

const int DnsChlgWindows = 0;
const int DnsChlgCloudflare = 1;

// Windows DNS Server login for WMI, empty for the local server.
const String DnsNetComputer = "";
const String DnsNetUserName = "";
const String DnsNetPassword = "";

// Delphi gets these names with GetEnumName(TypeInfo(TAcmeSupplier)); C++ has no RTTI for
// the enum, so they are listed in TAcmeSupplier order.
const wchar_t *const AcmeSupplierNames[] = { L"AcmeLetsEncrypt", L"AcmeLetsEncryptTest",
    L"AcmeZeroSSL", L"AcmeGoogle", L"AcmeGoogleTest", L"AcmeDigicert", L"AcmeDigicertTest",
    L"AcmeSslcomRSA", L"AcmeSslcomECC" };
static_assert(sizeof(AcmeSupplierNames) / sizeof(AcmeSupplierNames[0]) == AcmeSslcomECC + 1,
              "AcmeSupplierNames must list every TAcmeSupplier");

String AcmeSupplierName(int Supplier)
{
    if (Supplier < 0 || Supplier > AcmeSslcomECC)
        return "";
    return AcmeSupplierNames[Supplier];
}

// Dates before 10 (January 1900) mean "not set" and display as blank.
String MyDateToStr(TDateTime DT)
{
    return DT.Val < 10 ? String() : IcsDateToAStr(DT);
}

String MyDateTimeToStr(TDateTime DT)
{
    return DT.Val < 10 ? String() : IcsDateTimeToAStr(DT);
}

// Delphi: "Value in [A, B, C]" on an enum.
template <typename T>
bool IsOneOf(T Value, std::initializer_list<T> Values)
{
    return std::find(Values.begin(), Values.end(), Value) != Values.end();
}

} // namespace

__fastcall TX509CertsForm::TX509CertsForm(TComponent *Owner)
    : TForm(Owner)
{
}

void TX509CertsForm::TouchActivity()
{
    FLastActivityTick = IcsGetTickCountX();
}

void __fastcall TX509CertsForm::FormCreate(TObject *Sender)
{
    Caption = Caption + IcsVerLitSample;   // common ICS sample caption: version and date
    ReportMemoryLeaksOnShutdown = (DebugHook != 0);
    FProgDir = ExtractFilePath(ParamStr(0));
    FIniFileName = GetIcsIniFileName();

    GSSL_DLL_DIR = FProgDir;             // only from our directory, unless loaded already
    GSSL_SignTest_Check = true;          // check OpenSSL DLLs are digitally signed
    GSSL_SignTest_Certificate = true;    // and check the signing certificate
    CertSANGrid->Cells[0][0] = "Domain Name";
    CertSANGrid->Cells[1][0] = "Web Server UNC HTTP .Well-Known";
    CertSANGrid->Cells[2][0] = "Web Server UNC Public Certs Dir";
    CertSANGrid->Cells[3][0] = "Approval Email";
    OpenFileDlg->Filter = SslCertFileOpenExts;

    // replace the .dfm list items with the ICS literals, keeping the .dfm default
    int J = CertSignDigestType->ItemIndex;
    CertSignDigestType->Items->Clear();
    for (int I = 0; I <= DigestListLitsLast; I++)
        CertSignDigestType->Items->Add(DigestListLits[I]);
    CertSignDigestType->ItemIndex = J;
    J = PrivKeyType->ItemIndex;
    PrivKeyType->Items->Clear();
    for (int PK = PrivKeyNone; PK <= SslPrivKeyTypeLastCert; PK++)
        PrivKeyType->Items->Add(SslPrivKeyTypeLits[PK]);
    PrivKeyType->ItemIndex = J;
    J = SuppCertChallenge->ItemIndex;
    SuppCertChallenge->Items->Clear();
    for (int CT = ChallNone; CT <= ChallManual; CT++)
        SuppCertChallenge->Items->Add(ChallengeTypeLits[CT]);
    SuppCertChallenge->ItemIndex = J;
    J = PrivKeyCipher->ItemIndex;
    PrivKeyCipher->Items->Clear();
    for (int KC = PrivKeyEncNone; KC <= PrivKeyEncAES256; KC++)
        PrivKeyCipher->Items->Add(SslPrivKeyCipherLits[KC]);
    PrivKeyCipher->ItemIndex = J;
    PageControl1->ActivePage = TabAcmeSuppliers;
}

// Bound in the .dfm; the Delphi handler is empty too.
void __fastcall TX509CertsForm::FormDestroy(TObject *Sender)
{
}

/* Ini keys are "<control name>_Text", "_Checked" or "_ItemIndex" in [Data], the same keys
   the Delphi demo writes, so both versions share one ini file layout. */
void TX509CertsForm::LoadSettings()
{
    std::unique_ptr<TIcsIniFile> IniFile(new TIcsIniFile(FIniFileName, true));
    Width = IniFile->ReadInteger(SectionMainWindow, KeyWidth, Width);
    Height = IniFile->ReadInteger(SectionMainWindow, KeyHeight, Height);
    Top = IniFile->ReadInteger(SectionMainWindow, KeyTop, (Screen->Height - Height) / 2);
    Left = IniFile->ReadInteger(SectionMainWindow, KeyLeft, (Screen->Width - Width) / 2);

    // The grid is saved as a comma list of rows, each row '|' delimited.
    std::unique_ptr<TStringList> SL(new TStringList);
    std::unique_ptr<TStringList> SR(new TStringList);
    SL->Delimiter = L',';
    SR->Delimiter = L'|';
    SR->StrictDelimiter = true;   // Delphi also splits a row at spaces, breaking UNC paths with spaces
    SL->DelimitedText = IniFile->ReadString(SectionData, KeyCertSANGrid, "");
    CertSANGrid->RowCount = SL->Count + 10;
    int Row = 1;
    for (int I = 0; I < SL->Count; I++) {
        SR->DelimitedText = SL->Strings[I];
        if (SR->Count >= 4 && Trim(SR->Strings[0]).Length() > 2) {
            for (int Col = 0; Col < 4; Col++)
                CertSANGrid->Cells[Col][Row] = SR->Strings[Col];
            Row++;
        }
    }

    // An edit's default is its current .dfm text.
    for (TEdit *Edit : { CACertFile, CAPkeyFile, CAPkeyPw, CertAddress, CertCommonName,
            CertContactEmail, CertContactFirst, CertContactLast, CertContactTitle, CertCountry,
            CertLocality, CertOldCsrFile, CertOldPrvKey, CertOrganization, CertOrganizationalUnit,
            CertPhone, CertPostCode, CertState, CertValidity, DirLogs, DirPubWebCert, DirWellKnown,
            OwnCACertDir, PrivKeyPassword, AccCloseMins })
        Edit->Text = IniFile->ReadString(SectionData, Edit->Name + "_Text", Edit->Text);
    CertProfile->Text = IniFile->ReadString(SectionData, "CertProfile_Text", "");
    for (TRadioGroup *Radio : { CertCsrOrigin, CertSerNumType, CertSignDigestType, PrivKeyCipher,
            PrivKeyType, SuppCertChallenge, CertMsStoreLoc })
        Radio->ItemIndex = IniFile->ReadInteger(SectionData, Radio->Name + "_ItemIndex", Radio->ItemIndex);
    DnsChlgType->ItemIndex = IniFile->ReadInteger(SectionData, "DnsChlgType_ItemIndex", DnsChlgType->ItemIndex);
    for (TCheckBox *Check : { CertOutFmtBudl, CertOutFmtP12, CertOutFmtP7, CertOutFmtReq,
            CertOutFmtSep, SelfSignedCA, CertPwPemKeys, CertWindowsStore, DnsSkipLocalTest })
        Check->Checked = (IniFile->ReadString(SectionData, Check->Name + "_Checked", "False") == "True");
    CertPwPkcs12->Checked = (IniFile->ReadString(SectionData, "CertPwPkcs12_Checked", "True") == "True");
}

void TX509CertsForm::SaveSettings()
{
    std::unique_ptr<TIcsIniFile> IniFile(new TIcsIniFile(FIniFileName, true));
    IniFile->WriteInteger(SectionMainWindow, KeyTop, Top);
    IniFile->WriteInteger(SectionMainWindow, KeyLeft, Left);
    IniFile->WriteInteger(SectionMainWindow, KeyWidth, Width);
    IniFile->WriteInteger(SectionMainWindow, KeyHeight, Height);

    std::unique_ptr<TStringList> SL(new TStringList);
    SL->Delimiter = L',';
    for (int I = 1; I < CertSANGrid->RowCount; I++) {
        if (Trim(CertSANGrid->Cells[0][I]).Length() > 2)
            SL->Add(CertSANGrid->Cells[0][I] + "|" + CertSANGrid->Cells[1][I] + "|" +
                    CertSANGrid->Cells[2][I] + "|" + CertSANGrid->Cells[3][I]);
    }
    IniFile->WriteString(SectionData, KeyCertSANGrid, SL->DelimitedText);

    for (TEdit *Edit : { CACertFile, CAPkeyFile, CAPkeyPw, CertAddress, CertCommonName,
            CertContactEmail, CertContactFirst, CertContactLast, CertContactTitle, CertCountry,
            CertLocality, CertOldCsrFile, CertOldPrvKey, CertOrganization, CertOrganizationalUnit,
            CertPhone, CertPostCode, CertState, CertValidity, DirLogs, DirPubWebCert, DirWellKnown,
            OwnCACertDir, PrivKeyPassword, AccCloseMins })
        IniFile->WriteString(SectionData, Edit->Name + "_Text", Edit->Text);
    IniFile->WriteString(SectionData, "CertProfile_Text", CertProfile->Text);
    for (TRadioGroup *Radio : { CertCsrOrigin, CertSerNumType, CertSignDigestType, PrivKeyCipher,
            PrivKeyType, SuppCertChallenge, CertMsStoreLoc })
        IniFile->WriteInteger(SectionData, Radio->Name + "_ItemIndex", Radio->ItemIndex);
    IniFile->WriteInteger(SectionData, "DnsChlgType_ItemIndex", DnsChlgType->ItemIndex);
    for (TCheckBox *Check : { CertOutFmtBudl, CertOutFmtP12, CertOutFmtP7, CertOutFmtReq,
            CertOutFmtSep, SelfSignedCA, CertPwPemKeys, CertPwPkcs12, CertWindowsStore,
            DnsSkipLocalTest })
        IniFile->WriteString(SectionData, Check->Name + "_Checked", Check->Checked ? "True" : "False");
    IniFile->UpdateFile();
}

void __fastcall TX509CertsForm::FormShow(TObject *Sender)
{
    if (!FInitialized) {
        FInitialized = true;
        LoadSettings();
    }

    DomWebSrvIP->Items->Assign(LocalIPList(sfAny));   // IPv4 and IPv6 addresses
    DomWebSrvIP->Items->Insert(0, ICS_LOCAL_HOST_V4);
    DomWebSrvIP2->Items->Assign(DomWebSrvIP->Items);
    ResetOrderButtons();
    IcsSslRootCAStore->Initialise();   // load the internal root CA bundle if not loaded
    if (static_cast<TSslPrivKeyCipher>(PrivKeyCipher->ItemIndex) == PrivKeyEncTripleDES)
        PrivKeyCipher->ItemIndex = PrivKeyEncAES256;   // 3DES needs the OpenSSL legacy provider
    ReadAcmeAccounts();

    OpenLogFile();
    AddLog(String(IcsVerLitRelDate) + ", SSL/TLS " + IcsReportOpenSSLVer(true) + ", Compiler: " + IcsBuiltWithEx());

    // A Windows Firewall rule lets the local challenge web server be reached; needs admin rights.
    if (IcsIsProgAdmin()) {
        try {
            const String S = IcsFireWallRulesAdd("OverbyteIcsX509CertsTst", "ICS", "OverbyteIcsX509CertsTst",
                                                 ParamStr(0), FirewallBoth);
            AddLog("Windows Firewall Rule: " + S);
        }
        catch (Exception &E) {
            AddLog("Failed to Access Firewall - " + IcsGetExceptMess(&E));
        }
    }
}

/* Opens the log file, one per day, written as UTF-8, or changes its name if the log
   directory was changed. */
void TX509CertsForm::OpenLogFile()
{
    if (DirLogs->Text.IsEmpty())
        return;
    const String FName = "\"" + IncludeTrailingPathDelimiter(DirLogs->Text) + "ics-certlog-\"yyyy-mm-dd\".log\"";
    if (!FIcsBuffLogStream)
        FIcsBuffLogStream.reset(new TIcsBuffLogStream(nullptr, FName, Caption + IcsCRLF, FileCPUtf8));
    else {
        if (FName == FIcsBuffLogStream->NameMask)
            return;
        if (FIcsBuffLogStream->LogSize > 0)
            FIcsBuffLogStream->FlushFile(true);   // changing log path, write the old log first
        FIcsBuffLogStream->NameMask = FName;
    }
    AddLog(String(IcsCRLF) + "Opened log file: " + FIcsBuffLogStream->FullName);
}

void __fastcall TX509CertsForm::doExitClick(TObject *Sender)
{
    Close();
}

void __fastcall TX509CertsForm::FormClose(TObject *Sender, TCloseAction &Action)
{
    doAccCloseClick(this);
    X509Certs1->StopDomSrv();
    FIcsBuffLogStream.reset();   // writes the log file
    SaveSettings();
}

void TX509CertsForm::AddLog(const String &S)
{
    try {
        if (S.Pos(IcsLF) > 0)
            LogWin->Lines->Text = LogWin->Lines->Text + IcsCRLF + S;
        else
            LogWin->Lines->Add(S);
        SendMessage(LogWin->Handle, EM_LINESCROLL, 0, 999999);
        if (DirLogs->Text.IsEmpty() || !FIcsBuffLogStream)
            return;
        FIcsBuffLogStream->WriteLine(S);
    }
    catch (Exception &) {
        // as in Delphi a logging error is ignored: reporting it would log again
    }
}

// Called by the challenge web server for each new connection, to log the remote country
// and ASN. Without TIcsGeoTools in C++ there is nothing to look up.
void __fastcall TX509CertsForm::X509Certs1GeoEvent(TObject *Sender, const String IpStr, String &Geo)
{
    Geo = "";
}

void __fastcall TX509CertsForm::X509Certs1CertProg(TObject *Sender, TLogOption LogOption, const String Msg)
{
    AddLog(Msg);
}

void __fastcall TX509CertsForm::X509Certs1ChallengeDNS(TObject *Sender, const TChallengeItem &ChallengeItem, bool &ChlgOK)
{
    ChlgOK = false;
    const bool Removing = (ChallengeItem.CIssueState == IssStateCancel);
    if (IsOneOf(ChallengeItem.CType, { ChallDnsMan, ChallDnsPersMan })) {
        if (Removing)
            AddLog("Manually Remove DNS TXT Record for: " + ChallengeItem.CPage + ", with: " + ChallengeItem.CDNSValue);
        else
            AddLog("Manually Add DNS TXT Record for: " + ChallengeItem.CPage + ", with: " + ChallengeItem.CDNSValue);
        ChlgOK = true;
    }
    else if (DnsChlgType->ItemIndex == DnsChlgWindows) {
        if (!IcsIsProgAdmin())
            AddLog("WARNING!!! - Application Needs Administrator Rights to Modify DNS");

        // check we can talk to the DNS server
        String IpAddrs, Errinfo;
        const String SrvName = IcsWmiChkDnsSrv(DnsNetComputer, DnsNetUserName, DnsNetPassword, IpAddrs, Errinfo);
        if (SrvName.IsEmpty() || !Errinfo.IsEmpty()) {
            AddLog("Can Not Find Windows DNS Server: " + Errinfo);
            return;
        }
        AddLog("Windows DNS Server Name: " + SrvName);

        String Zone = ChallengeItem.CDomain;
        // Delphi strips the wildcard prefix only after listing the zone, so a wildcard domain
        // looks up zone "*.domain" and always fails with "Can Not Find DNS Zone".
        if (Zone.Pos("*.") == 1)
            Zone = Zone.SubString(3, Zone.Length());   // Delphi copies at most 99 characters
        TWmiDnsRecs WmiDnsRecs;
        const int Tot = IcsWmiListDnsRecs(DnsNetComputer, DnsNetUserName, DnsNetPassword, Zone, "", WmiDnsRecs, Errinfo);
        if (Tot == 0 || !Errinfo.IsEmpty()) {
            AddLog("Can Not Find DNS Zone: " + Errinfo);
            return;
        }

        AddLog(Removing ? "Removing Windows DNS Server TXT Record" : "Updating Windows DNS Server TXT Record");
        TWmiDnsRec WmiDnsRec;
        WmiDnsRec.HostName = ChallengeItem.CPage;
        WmiDnsRec.RecType = "TXT";
        WmiDnsRec.RecData = Trim(ChallengeItem.CDNSValue);
        AddLog("Zone: " + Zone + ": " + WmiDnsRec.HostName + " IN " + WmiDnsRec.RecType + " " + WmiDnsRec.RecData);
        // Delphi passes empty login details to the check after a delete and to the add, and the
        // DNS login to the others; the same login is used for all calls here.
        if (Removing) {
            if (IcsWmiUpdDnsRec(DnsNetComputer, DnsNetUserName, DnsNetPassword, Zone, WmiDnsRec, EdtFuncDel, Errinfo) != 0)
                AddLog("Failed to Delete DNS Server TXT Record: " + Errinfo);
            else if (IcsWmiChkDnsRec(DnsNetComputer, DnsNetUserName, DnsNetPassword, Zone, WmiDnsRec, Errinfo))
                AddLog("Failed to Delete DNS Server TXT Record: " + Errinfo);   // still there
            else if (!Errinfo.IsEmpty())   // Delphi takes a failed WMI check as a successful delete
                AddLog("Can Not Check DNS Server TXT Record Was Deleted: " + Errinfo);
            else {
                ChlgOK = true;
                AddLog("Deleted Windows DNS Server TXT Record OK");
            }
        }
        else {
            // EdtFuncAdd leaves old records
            if (IcsWmiUpdDnsRec(DnsNetComputer, DnsNetUserName, DnsNetPassword, Zone, WmiDnsRec, EdtFuncAdd, Errinfo) != 0)
                AddLog("Failed to Update Windows DNS Server: " + Errinfo);
            else if (IcsWmiChkDnsRec(DnsNetComputer, DnsNetUserName, DnsNetPassword, Zone, WmiDnsRec, Errinfo)) {
                AddLog("Updated Windows DNS Server TXT Record OK");
                ChlgOK = true;
            }
            else
                AddLog("Check Update Windows DNS Server Failed: " + Errinfo);
        }
    }
    else if (DnsChlgType->ItemIndex == DnsChlgCloudflare) {
        AddLog("Cloudfare DNS Server Not Supported Yet");
    }
}

void __fastcall TX509CertsForm::X509Certs1ChallengeEmail(TObject *Sender, const TChallengeItem &ChallengeItem, bool &ChlgOK)
{
    ChlgOK = true;
}

// The demo has no FTP code: the file must be copied to the server before the challenge is
// started, or it fails.
void __fastcall TX509CertsForm::X509Certs1ChallengeFTP(TObject *Sender, const TChallengeItem &ChallengeItem, bool &ChlgOK)
{
    const String S = ".Well-Known Challenge: " + String(IcsCRLF) +
        "Use FTP to Copy file: " + ChallengeItem.CWKFullName +
        " to server .Well-Known directory for domain: " + ChallengeItem.CDomain;
    if (ChallengeItem.CSupplierProto == SuppProtoAcmeV2)
        LabelAcme2Info->Caption = S;
    ChlgOK = false;
}

void TX509CertsForm::RefreshDomains()
{
    const TDomainItems Domains = X509Certs1->DomainItems;
    const int Tot = Domains.Length;
    if (Tot == 0 || DatabaseDomains->Items->Count > Tot) {
        DatabaseDomains->Items->Clear();
        ResetDomButtons();
        return;
    }
    if (PageControl1->ActivePage == TabSupplierOrders)
        DatabaseDomains->SetFocus();
    const String OldDom = DatabaseDomains->ItemIndex >= 0 ?
        DatabaseDomains->Items->Item[DatabaseDomains->ItemIndex]->Caption : String();
    while (DatabaseDomains->Items->Count < Tot) {
        DatabaseDomains->Items->Add();
        ResetDomButtons();
    }
    for (int I = 0; I < Tot; I++) {
        const TDomainItem &Dom = Domains[I];
        TListItem *Item = DatabaseDomains->Items->Item[I];
        Item->Caption = Dom.DCommonName;
        while (Item->SubItems->Count <= 9)
            Item->SubItems->Add("");
        Item->SubItems->Strings[0] = MyDateToStr(Dom.DIssueStartDT);
        Item->SubItems->Strings[1] = IssueStateLits[Dom.DIssueState];
        Item->SubItems->Strings[2] = Dom.DSuppOrderId;
        Item->SubItems->Strings[3] = MyDateToStr(Dom.DCertStartDT);
        Item->SubItems->Strings[4] = MyDateToStr(Dom.DCertEndDT);
        Item->SubItems->Strings[5] = "";   // Delphi keeps a reused row's old renewal text
        if (Dom.DCertEndDT.Val > 10) {
            if (Dom.DCertEndDT < Now())
                Item->SubItems->Strings[5] = "Expired";
            else if (Dom.DCertRenewDays > 0)
                Item->SubItems->Strings[5] = MyDateToStr(TDateTime(Dom.DCertEndDT.Val - Dom.DCertRenewDays));
        }
        Item->SubItems->Strings[6] = ChallengeTypeLits[Dom.DSuppCertChallg];
        Item->SubItems->Strings[7] = Dom.DCertProfile.IsEmpty() ? Dom.DProduct : Dom.DCertProfile;
        Item->SubItems->Strings[8] = Dom.DCertSANs;
        if (OldDom == Dom.DCommonName) {
            Item->Selected = true;
            Item->Focused = true;
        }
    }
    SetDomButtons();
}

void __fastcall TX509CertsForm::X509Certs1DomainsRefresh(TObject *Sender)
{
    RefreshDomains();
}

void __fastcall TX509CertsForm::X509Certs1NewCert(TObject *Sender)
{
    if (static_cast<TSslX509Certs *>(Sender)->SupplierProto == SuppProtoAcmeV2)
        LabelAcme2Info->Caption = X509Certs1->GetOrderResult();
}

// Shows the file dialog starting at Edit's file and puts an existing file chosen in Edit.
bool TX509CertsForm::SelectFile(TEdit *Edit)
{
    OpenFileDlg->InitialDir = Edit->Text;
    if (!OpenFileDlg->Execute())
        return false;
    const String FName = OpenFileDlg->FileName;
    if (FName.IsEmpty() || !FileExists(FName))
        return false;
    Edit->Text = FName;
    return true;
}

// OpenDirDiag is a file dialog used to pick a directory: the path of the file name is kept.
void TX509CertsForm::SelectDir(TEdit *Edit)
{
    OpenDirDiag->InitialDir = Edit->Text;
    if (OpenDirDiag->Execute())
        Edit->Text = ExtractFilePath(OpenDirDiag->FileName);
}

void __fastcall TX509CertsForm::SelCertOldCsrFileClick(TObject *Sender)
{
    SelectFile(CertOldCsrFile);
}

void __fastcall TX509CertsForm::SelCertOldPrvKeyClick(TObject *Sender)
{
    SelectFile(CertOldPrvKey);
}

void __fastcall TX509CertsForm::SelDirAcmeAccClick(TObject *Sender)
{
    SelectDir(DirCertWork);
}

void __fastcall TX509CertsForm::SelDirLogsClick(TObject *Sender)
{
    SelectDir(DirLogs);
}

void __fastcall TX509CertsForm::SelDirPubWebCertClick(TObject *Sender)
{
    SelectDir(DirPubWebCert);
}

void __fastcall TX509CertsForm::SelDirWellKnownClick(TObject *Sender)
{
    SelectDir(DirWellKnown);
}

void __fastcall TX509CertsForm::SelOwnCACertDirClick(TObject *Sender)
{
    SelectDir(OwnCACertDir);
}

void __fastcall TX509CertsForm::SelCACertFileClick(TObject *Sender)
{
    SelectFile(CACertFile);
}

void __fastcall TX509CertsForm::SelCAPkeyFileClick(TObject *Sender)
{
    SelectFile(CAPkeyFile);
}

// Copies the order settings from the form to the component. CACert: an intermediate CA
// certificate, which has no alternate names. False when the order needs a CSR file that is missing.
bool TX509CertsForm::SetCertParams(bool CACert)
{
    X509Certs1->CertCsrOrigin = static_cast<TCertCsrOrigin>(CertCsrOrigin->ItemIndex);
    if (!SelfSignedCA->Checked)
        CertCommonName->Text = IcsLowerCase(Trim(CertCommonName->Text));
    X509Certs1->CertCommonName = "";   // set later, may come from the CSR
    X509Certs1->DirWellKnown = DirWellKnown->Text;
    X509Certs1->DirPubWebCert->Text = DirPubWebCert->Text;
    X509Certs1->PrivKeyPassword = PrivKeyPassword->Text;
    X509Certs1->PrivKeyType = static_cast<TSslPrivKeyType>(PrivKeyType->ItemIndex);
    X509Certs1->PrivKeyCipher = static_cast<TSslPrivKeyCipher>(PrivKeyCipher->ItemIndex);
    X509Certs1->CertSignDigestType = DigestDispList[CertSignDigestType->ItemIndex];
    X509Certs1->SuppCertChallenge = static_cast<TChallengeType>(SuppCertChallenge->ItemIndex);
    X509Certs1->CertOldCsrFile = CertOldCsrFile->Text;
    X509Certs1->CertOldPrvKey = CertOldPrvKey->Text;
    X509Certs1->CertSerNumType = static_cast<TSerNumType>(CertSerNumType->ItemIndex);
    X509Certs1->SuppOrderRef = "";
    X509Certs1->SuppOrderId = "";
#ifdef ICS_HAVE_MSCRYPT
    X509Certs1->MsCertLoc = static_cast<TMsCertLocation>(CertMsStoreLoc->ItemIndex);
#endif
    X509Certs1->CertAcmeProfile = CertProfile->Text;
    X509Certs1->SuppCertProduct = AcmeSupplierLits[X509Certs1->AcmeSupplier] + " - " + CertProfile->Text;
    X509Certs1->AutoOrderComplete = AutoOrderComplete->Checked;
    X509Certs1->DomWebSrvIP = DomWebSrvIP->Text;   // local server addresses may differ from the account's
    X509Certs1->DomWebSrvIP2 = DomWebSrvIP2->Text;

    // must have one sensible certificate output format
    if (!CertOutFmtBudl->Checked && !CertOutFmtSep->Checked && !CertOutFmtP12->Checked)
        CertOutFmtSep->Checked = true;
    TCertOutFmts Fmts;
    if (CertOutFmtBudl->Checked) Fmts << OutFmtBudl;
    if (CertOutFmtP12->Checked) Fmts << OutFmtP12;
    if (CertOutFmtP7->Checked) Fmts << OutFmtP7;
    if (CertOutFmtReq->Checked) Fmts << OutFmtReq;
    if (CertOutFmtSep->Checked) Fmts << OutFmtSep;
    if (CertPwPemKeys->Checked) Fmts << OutFmtPwPem;
    if (CertPwPkcs12->Checked) Fmts << OutFmtPwP12;
    if (CertWindowsStore->Checked) Fmts << OutFmtWinStore;
    X509Certs1->CertOutFmts = Fmts;

    if (X509Certs1->CertCsrOrigin != CsrOriginProps) {
        if (CertOldCsrFile->Text.IsEmpty() || !FileExists(CertOldCsrFile->Text)) {
            AddLog("Must Specify CSR File Name");
            return false;   // Delphi only logs, and its callers carry on with no CSR
        }
        return true;
    }

    // creating our own CSR from the properties
    X509Certs1->CertCommonName = CertCommonName->Text;
    X509Certs1->CertOrganization = CertOrganization->Text;
    X509Certs1->CertOrgUnit = CertOrganizationalUnit->Text;
    X509Certs1->CertAddress = CertAddress->Text;
    X509Certs1->CertLocality = CertLocality->Text;
    X509Certs1->CertState = CertState->Text;
    X509Certs1->CertPostCode = CertPostCode->Text;
    X509Certs1->CertCountry = CertCountry->Text;
    X509Certs1->CertContactEmail = CertContactEmail->Text;
    X509Certs1->CertContactTitle = CertContactTitle->Text;
    X509Certs1->CertContactFirst = CertContactFirst->Text;
    X509Certs1->CertContactLast = CertContactLast->Text;
    X509Certs1->CertPhone = CertPhone->Text;
    X509Certs1->CertValidity = StrToIntDef(Trim(CertValidity->Text), 0);

    // build the list of alternate names from the grid
    X509Certs1->CertSubAltNames->Clear();
    if (CACert)
        return true;
    int CommRow = -1;
    int NewRow = -1;
    for (int I = 1; I < CertSANGrid->RowCount; I++) {
        CertSANGrid->Cells[0][I] = IcsLowerCase(Trim(CertSANGrid->Cells[0][I]));
        if (!CertSANGrid->Cells[0][I].IsEmpty() && Trim(CertSANGrid->Cells[1][I]).IsEmpty())
            CertSANGrid->Cells[1][I] = X509Certs1->DirWellKnown;
        if (CertSANGrid->Cells[0][I] == CertCommonName->Text)
            CommRow = I;
        if (CertSANGrid->Cells[0][I].IsEmpty() && NewRow < 0)
            NewRow = I;   // first blank row
    }
    // add the common name to the alternate names if missing, and keep its .Well-Known dir current
    if (CommRow == -1 && NewRow >= 1 && X509Certs1->DirPubWebCert->Count > 0) {
        CertSANGrid->Cells[0][NewRow] = CertCommonName->Text;
        CertSANGrid->Cells[1][NewRow] = X509Certs1->DirWellKnown;
        CertSANGrid->Cells[2][NewRow] = X509Certs1->DirPubWebCert->Strings[0];
        CertSANGrid->Cells[3][NewRow] = X509Certs1->CertApprovEmail;
    }
    else if (CommRow >= 1 && CertSANGrid->Cells[1][CommRow] != X509Certs1->DirWellKnown)
        CertSANGrid->Cells[1][CommRow] = X509Certs1->DirWellKnown;
    for (int I = 1; I < CertSANGrid->RowCount; I++) {
        if (!CertSANGrid->Cells[0][I].IsEmpty())
            X509Certs1->CertSubAltNames->AddItem(CertSANGrid->Cells[0][I], CertSANGrid->Cells[1][I],
                                                 CertSANGrid->Cells[2][I], CertSANGrid->Cells[3][I]);
    }
    return true;
}

// Reads the ACME supplier accounts database and fills the list view.
void TX509CertsForm::ReadAcmeAccounts()
{
    TouchActivity();
    X509Certs1->DBSuppliersRead();
    SupplierList->Items->Clear();
    for (int I = 0; I < X509Certs1->AcmeSuppTot; I++) {
        const TAcmeSuppRec &Rec = X509Certs1->AcmeSuppRecs[I];
        TListItem *Item = SupplierList->Items->Add();
        Item->Caption = Rec.ASuppTitle;
        Item->SubItems->Add(AcmeSupplierLits[Rec.ASupplier]);
        Item->SubItems->Add(Rec.ASuppDir);
    }
}

void __fastcall TX509CertsForm::PageControl1Changing(TObject *Sender, bool &AllowChange)
{
    TouchActivity();
    if (PageControl1->ActivePage == TabSupplierDetails && FSuplierDetailsChanged) {
        AllowChange = false;
        AddLog("Supplier Details Have Changed, Must Save First");
        System::Sysutils::Beep();
    }
}

void __fastcall TX509CertsForm::SupplierChanged(TObject *Sender)
{
    TouchActivity();
    FSuplierDetailsChanged = true;
    doAccSuppSave->Enabled = true;
}

// A supplier was selected: show its details and allow its account to be opened.
void __fastcall TX509CertsForm::SupplierListClick(TObject *Sender)
{
    TouchActivity();
    const int Idx = SupplierList->ItemIndex;
    // Delphi indexes the accounts with ItemIndex -1 when the click is below the last row.
    if (Idx < 0 || Idx >= X509Certs1->AcmeSuppTot)
        return;
    doAccOpenOld->Enabled = true;
    const TAcmeSuppRec &Rec = X509Certs1->AcmeSuppRecs[Idx];
    SupplierTitle->Text = Rec.ASuppTitle;
    AcmeSupplier->ItemIndex = Rec.ASupplier;
    DirCertWork->Text = Rec.ASuppDir;
    OpenLogFile();
}

void __fastcall TX509CertsForm::SupplierListDblClick(TObject *Sender)
{
    doAccOpenOldClick(this);
}

void __fastcall TX509CertsForm::doAccDeleteClick(TObject *Sender)
{
    const int Idx = SupplierList->ItemIndex;
    if (Idx < 0)
        return;
    doAccCloseClick(this);   // resets SupplierList->ItemIndex
    const TAcmeSuppRec MySupplier = X509Certs1->AcmeSuppRecs[Idx];
    const String Prompt = "Supplier: " + MySupplier.ASuppTitle + IcsCRLF +
                          "Do you want to remove this supplier from the database";
    if (Application->MessageBox(Prompt.c_str(), L"Supplier Accounts", MB_OKCANCEL) != IDOK)
        return;
    AddLog("Removing Supplier from Account Database: " + MySupplier.ASuppTitle);
    AddLog("Now manually delete directory: " + MySupplier.ASuppDir);
    X509Certs1->AcmeSuppRecs[Idx].ASuppTitle = "";   // a blank title is not saved
    if (X509Certs1->DBSuppliersSave())
        ReadAcmeAccounts();
}

// Closes the supplier account after AccCloseMins minutes (default 5) without activity.
void __fastcall TX509CertsForm::CloseTimerTimer(TObject *Sender)
{
    int WaitMins = Overbyteicsutils::atoi(AccCloseMins->Text);   // ICS atoi reads leading digits, as Delphi
    if (WaitMins == 0)
        WaitMins = 5;
    if (IcsElapsedMins(FLastActivityTick) >= WaitMins) {
        AddLog("Supplier Account Closing on Inactivity");
        CloseTimer->Enabled = false;
        doAccCloseClick(this);
    }
}

void __fastcall TX509CertsForm::doAccOpenOldClick(TObject *Sender)
{
    if (SupplierList->ItemIndex < 0)
        return;
    const TAcmeSuppRec MySupplier = X509Certs1->AcmeSuppRecs[SupplierList->ItemIndex];
    doAccCloseClick(this);   // resets SupplierList->ItemIndex
    X509Certs1->SupplierTitle = MySupplier.ASuppTitle;
    X509Certs1->AcmeSupplier = MySupplier.ASupplier;
    SupplierTitle->Text = MySupplier.ASuppTitle;
    AcmeSupplier->ItemIndex = MySupplier.ASupplier;
    DirCertWork->Text = MySupplier.ASuppDir;
    doAccOpenOld->Enabled = false;
    doAccClose->Enabled = true;
    AddLog("Opening Supplier Account: " + SupplierTitle->Text);
    X509Certs1->ClearAccount();
    X509Certs1->ClearCertOrder();

    // open the account control database ics-control.db in the account directory
    if (!X509Certs1->DBReadAccount(DirCertWork->Text)) {
        AddLog("Failed to Open Supplier Account: " + X509Certs1->LastResponse);
        return;   // the result is also checked in OnSuppDBRefresh
    }
    const String SupplierName = AcmeSupplierLits[X509Certs1->AcmeSupplier];
    BoxSupplierDetails->Caption = "Supplier Account: " + SupplierTitle->Text + " - " + SupplierName;
    TabSupplierOrders->Caption = "Supplier Orders for: " + SupplierTitle->Text + " - " + SupplierName;
    PageControl1->ActivePage = TabSupplierDetails;
    AddLog("Opened Supplier Account OK from: " + DirCertWork->Text);

    String S = "Account Database Title: " + X509Certs1->SupplierTitle + IcsCRLF +
               "Protocol: " + SupplierProtoLits[X509Certs1->SupplierProto] + IcsCRLF +
               "Supplier: " + SupplierName + IcsCRLF +
               "Directory: " + X509Certs1->DirCertWork + IcsCRLF;
    // if the account was created before, contact the ACME server
    if (!X509Certs1->AcmeAccountNum.IsEmpty()) {
        X509Certs1->SetAcmeAccount(false);   // only open the old account
        if (X509Certs1->SupplierProto > SuppProtoNone) {
            S += "Supplier Server: " + X509Certs1->SupplierServer + IcsCRLF +
                 "Account Number: " + X509Certs1->AcmeAccountNum + IcsCRLF +
                 "Account URL: " + X509Certs1->AcmeAccountUrl + IcsCRLF +
                 "External Account Required: " + BoolToStr(X509Certs1->AcmeExtAccount, true) + IcsCRLF +
                 "Web Site: " + X509Certs1->AcmeWebsite + IcsCRLF +
                 "Total Saved Domain Orders: " + IntToStr(X509Certs1->DomainItems.Length) + IcsCRLF +
                 "Total Pending Challenges: " + IntToStr(X509Certs1->ChallengesTot) + IcsCRLF;
            const TStringDynArray Names = X509Certs1->AcmeProfileNames;
            const TStringDynArray Urls = X509Certs1->AcmeProfileURL;
            for (int I = 0; I < Names.Length; I++)
                S += "Profile: " + Names[I] + " - " + Urls[I] + IcsCRLF;
            const TStringDynArray Caa = X509Certs1->AcmeCaaIndenties;
            if (Caa.Length > 0) {
                S += "CAA Identifies: ";
                for (int I = 0; I < Caa.Length; I++)
                    S += Caa[I];
                S += IcsCRLF;
            }
            LabelDB->Caption = S;
            AddLog(S);
            doAcmeCheckOrder->Enabled = true;
            PageControl1->ActivePage = TabSupplierOrders;
            AddLog("Opened Supplier Account OK: " + SupplierTitle->Text);
        }
    }
    else {
        S += "Supplier Account Not Yet Created";
        LabelDB->Caption = S;
        AddLog(S);
    }
    AccAcmeKeyType->ItemIndex = X509Certs1->AcmeAccKeyType;
    SupplierEmail->Text = X509Certs1->SupplierEmail;
    AcmeEABKid->Text = X509Certs1->AcmeEABKid;
    AcmeEABHmacKey->Text = X509Certs1->AcmeEABHmacKey;
    DomWebSrvIP->Text = X509Certs1->DomWebSrvIP;
    DomWebSrvIP2->Text = X509Certs1->DomWebSrvIP2;
    AutoOrderComplete->Checked = X509Certs1->AutoOrderComplete;
    IpSocFamily->ItemIndex = X509Certs1->SocketFamily;
    ProxyURL->Text = X509Certs1->ProxyURL;
    DebugLogging->ItemIndex = X509Certs1->DebugLevel;
    LogJson->Checked = X509Certs1->LogJson;
    LogPkeys->Checked = X509Certs1->LogPkeys;
    if (AcmeAccountNum->Text != X509Certs1->AcmeAccountNum)
        AcmeAccountNum->Text = X509Certs1->AcmeAccountNum;
    CertProfile->Items->Clear();   // drop down list of certificate profiles
    const TStringDynArray Names = X509Certs1->AcmeProfileNames;
    for (int I = 0; I < Names.Length; I++)
        CertProfile->Items->Add(Names[I]);

    // the edits above fire SupplierChanged; only a new account still needs saving
    doAccSuppSave->Enabled = X509Certs1->AcmeAccountNum.IsEmpty();
    FSuplierDetailsChanged = false;

    // the old database may hold different values
    if (SupplierTitle->Text != MySupplier.ASuppTitle)
        SupplierTitle->Text = MySupplier.ASuppTitle;
    if (AcmeSupplier->ItemIndex != MySupplier.ASupplier)
        AcmeSupplier->ItemIndex = MySupplier.ASupplier;
    if (DirCertWork->Text != MySupplier.ASuppDir)
        DirCertWork->Text = MySupplier.ASuppDir;
    DirCertWork->Enabled = false;   // the directory may not change now
    TouchActivity();
    CloseTimer->Enabled = true;
}

// Called when the supplier account is opened, closed or changes.
void __fastcall TX509CertsForm::X509Certs1SuppDBRefresh(TObject *Sender)
{
    if (X509Certs1->SupplierProto > SuppProtoNone) {
        doAccClose->Enabled = true;
        doDBListChallg->Enabled = (X509Certs1->ChallengesTot != 0);
    }
    else {
        LabelDB->Caption = "No Supplier Account Database Open";
        DatabaseDomains->Items->Clear();
        doAccClose->Enabled = false;
        doDBListChallg->Enabled = false;
        FSuplierDetailsChanged = false;
    }
}

void __fastcall TX509CertsForm::doAccCloseClick(TObject *Sender)
{
    CloseTimer->Enabled = false;
    X509Certs1->CloseAccount();
    PageControl1->ActivePage = TabAcmeSuppliers;
    TabSupplierOrders->Caption = "Supplier Orders for: None";
    ResetDomButtons();
    DatabaseDomains->Items->Clear();
    AccAcmeKeyType->ItemIndex = PrivKeyRsa2048;
    SupplierEmail->Text = "";
    AcmeEABKid->Text = "";
    AcmeEABHmacKey->Text = "";
    DomWebSrvIP->ItemIndex = 0;
    DomWebSrvIP2->Text = "";
    AutoOrderComplete->Checked = false;
    IpSocFamily->ItemIndex = 0;
    ProxyURL->Text = "";
    DebugLogging->ItemIndex = DebugParams;
    LogJson->Checked = false;
    LogPkeys->Checked = false;
    AcmeAccountNum->Text = "";
    DirCertWork->Text = "";
    LabelDB->Caption = "";
    doAccClose->Enabled = false;
    FSuplierDetailsChanged = false;
    SupplierTitle->Text = "";
    SupplierList->ItemIndex = -1;
    DirCertWork->Enabled = true;
}

// For a new account, suggests a title and directory from the supplier name.
void __fastcall TX509CertsForm::AcmeSupplierClick(TObject *Sender)
{
    if (!doAccSaveNew->Enabled || SupplierList->ItemIndex != -1 || AcmeSupplier->ItemIndex < 0)
        return;
    SupplierTitle->Text = AcmeSupplierName(AcmeSupplier->ItemIndex) + "-1";
    DirCertWork->Text = IncludeTrailingPathDelimiter(Trim(X509Certs1->AccSuppDir + SupplierTitle->Text));
}

void __fastcall TX509CertsForm::doAccSaveNewClick(TObject *Sender)
{
    TouchActivity();
    X509Certs1->ClearAccount();
    SupplierTitle->Text = Trim(SupplierTitle->Text);
    if (SupplierTitle->Text.IsEmpty()) {
        AddLog("Must Specify Supplier Account Title");
        System::Sysutils::Beep();
        SupplierTitle->Text = AcmeSupplierName(AcmeSupplier->ItemIndex) + "-1";
        DirCertWork->Text = IncludeTrailingPathDelimiter(Trim(X509Certs1->AccSuppDir + SupplierTitle->Text));
        return;
    }

    // the directory must not belong to another account
    for (int I = 0; I < X509Certs1->AcmeSuppTot; I++) {
        if (X509Certs1->AcmeSuppRecs[I].ASuppDir == DirCertWork->Text) {
            AddLog("Directory Already Used by Another Account");
            System::Sysutils::Beep();
            PageControl1->ActivePage = TabSupplierDetails;
            FSuplierDetailsChanged = false;
            return;
        }
    }

    // Delphi accepts a folder that already holds an account database here, and the account in it
    // is then rewritten from the screen; an existing account is added with Import Old Account.
    if (!Trim(DirCertWork->Text).IsEmpty() &&
            FileExists(IncludeTrailingPathDelimiter(Trim(DirCertWork->Text)) + FileIcsCntlDB)) {
        AddLog("Directory Already Holds an Account Database, Use Import Old Account Instead");
        System::Sysutils::Beep();
        return;
    }

    // creates the account directory, adds the supplier to the list and a bare database,
    // with no private key or account yet
    if (!X509Certs1->DBAddAccSupp(SupplierTitle->Text, DirCertWork->Text,
                                  static_cast<TAcmeSupplier>(AcmeSupplier->ItemIndex), false)) {
        AddLog("Failed to Create New Supplier Account");
        System::Sysutils::Beep();
        return;
    }
    AddLog("Created New Supplier Account, Now Update Supplier Details and Save Again");
    PageControl1->ActivePage = TabSupplierDetails;
    ReadAcmeAccounts();
    FSuplierDetailsChanged = true;
    doAccSuppSave->Enabled = true;
}

// Imports an existing account database directory into the accounts list.
void __fastcall TX509CertsForm::doAccImportClick(TObject *Sender)
{
    TouchActivity();
    AddLog("Start Import of Old Supplier Account Database");
    OpenDirDiag->InitialDir = DirCertWork->Text;
    if (!OpenDirDiag->Execute())
        return;
    const String ImportDir = IncludeTrailingPathDelimiter(Trim(ExtractFilePath(OpenDirDiag->FileName)));
    // Delphi logs DirCertWork here, the previous directory, not the one being imported.
    AddLog("Trying to Open Old Supplier Account Database: " + ImportDir);
    if (!X509Certs1->DBReadAccount(ImportDir)) {
        AddLog("Can Not Find Supplier Account Database");
        return;
    }
    AddLog("Found Old Supplier Account Database OK");
    AcmeSupplier->ItemIndex = X509Certs1->AcmeSupplier;
    SupplierTitle->Text = X509Certs1->SupplierTitle;
    DirCertWork->Text = ImportDir;   // setting the supplier may have set a default
    if (SupplierTitle->Text.Pos("ACME V2") == 1) {
        SupplierTitle->Text = AcmeSupplierName(AcmeSupplier->ItemIndex) + "-Old";
        if (X509Certs1->DBFindAccSupp(SupplierTitle->Text) >= 0) {
            AddLog("Supplier Account Name Already Exists");
            return;
        }
    }
    X509Certs1->AcmeSuppTot = X509Certs1->AcmeSuppTot + 1;
    X509Certs1->AcmeSuppRecs.Length = X509Certs1->AcmeSuppTot;
    TAcmeSuppRec &Rec = X509Certs1->AcmeSuppRecs[X509Certs1->AcmeSuppTot - 1];
    Rec.ASuppTitle = SupplierTitle->Text;
    Rec.ASupplier = static_cast<TAcmeSupplier>(AcmeSupplier->ItemIndex);
    Rec.ASuppDir = ImportDir;
    if (!X509Certs1->DBSuppliersSave()) {
        AddLog("Failed to Create New Supplier Account");
        X509Certs1->AcmeSuppTot = X509Certs1->AcmeSuppTot - 1;   // Delphi keeps the unsaved account in the list
        X509Certs1->AcmeSuppRecs.Length = X509Certs1->AcmeSuppTot;
        return;
    }
    AddLog("Imported Supplier Account: " + SupplierTitle->Text);
    ReadAcmeAccounts();
}

// Saves the supplier details and registers the account with the ACME server.
void __fastcall TX509CertsForm::doAccSuppSaveClick(TObject *Sender)
{
    AddLog("Saving and Registering Supplier Account");
    ResetOrderButtons();
    X509Certs1->ClearAccount();
    X509Certs1->SupplierTitle = SupplierTitle->Text;
    X509Certs1->DirCertWork = Trim(DirCertWork->Text);
    X509Certs1->SupplierProto = SuppProtoAcmeV2;
    X509Certs1->AcmeSupplier = static_cast<TAcmeSupplier>(AcmeSupplier->ItemIndex);
    X509Certs1->SupplierServer = AcmeSupplierApiURLs[X509Certs1->AcmeSupplier];
    X509Certs1->AcmeAccKeyType = static_cast<TSslPrivKeyType>(AccAcmeKeyType->ItemIndex);
    X509Certs1->SupplierEmail = SupplierEmail->Text;
    X509Certs1->AcmeAccountNum = AcmeAccountNum->Text;
    X509Certs1->AcmeEABKid = AcmeEABKid->Text;   // External Account Binding
    X509Certs1->AcmeEABHmacKey = AcmeEABHmacKey->Text;
    X509Certs1->DomWebSrvIP = DomWebSrvIP->Text;
    X509Certs1->DomWebSrvIP2 = DomWebSrvIP2->Text;
    X509Certs1->SocketFamily = static_cast<TSocketFamily>(IpSocFamily->ItemIndex);
    X509Certs1->ProxyURL = ProxyURL->Text;
    X509Certs1->DebugLevel = static_cast<THttpDebugLevel>(DebugLogging->ItemIndex);
    X509Certs1->LogJson = LogJson->Checked;
    X509Certs1->LogPkeys = LogPkeys->Checked;

    if (!X509Certs1->DBWriteAccount()) {
        AddLog("Failed to Save Supplier Account");
        return;
    }
    if (!X509Certs1->DBOpenINI(X509Certs1->DirCertWork, true)) {
        AddLog("Failed to Create Supplier Account Database in Dir: " + X509Certs1->DirCertWork);
        return;
    }
    FSuplierDetailsChanged = false;
    doAccSuppSave->Enabled = false;
    if (X509Certs1->SetAcmeAccount(!X509Certs1->AcmeAccountUrl.IsEmpty())) {
        doAcmeCheckOrder->Enabled = true;
        AddLog("Saved Supplier Account OK");
        AcmeAccountNum->Text = X509Certs1->AcmeAccountNum;
    }
    else
        AddLog("Failed to Save Supplier Account");
    X509Certs1SuppDBRefresh(this);
}

// Enables the order buttons as the order state advances.
void __fastcall TX509CertsForm::X509Certs1ChallgRefresh(TObject *Sender)
{
    TouchActivity();
    TSslX509Certs *Certs = static_cast<TSslX509Certs *>(Sender);
    const TIssueState State = Certs->IssueState;
    const String S = TimeToStr(Time()) + " " + Certs->CertCommonName + " - Issue State: " + IssueStateLits[State];
    if (State >= IssStateChecked) {
        doDBStartOrder->Enabled = true;
        doAcmeStartOrder->Enabled = true;
    }
    if (State >= IssStateChallgReq)
        doAcmeTestChallng->Enabled = true;
    if (State >= IssStateChallgTest)
        doAcmeStartChallng->Enabled = true;
    if (State >= IssStateChallgPend) {
        doDBOrderStatus->Enabled = true;
        doAcmeOrderStatus->Enabled = true;
    }
    if (State >= IssStateChallgOK) {
        doDBFinalOrder->Enabled = true;
        doAcmeFinalOrder->Enabled = true;
    }
    if (State >= IssStateIssued) {
        doDBCollect->Enabled = true;
        doAcmeCollectCert->Enabled = true;
    }
    if (State >= IssStateCollected) {
        doDBRenewal->Enabled = true;
        doDBRevoke->Enabled = true;
        doDBCancel->Enabled = true;
        doDBRemove->Enabled = true;
        doDBRedist->Enabled = true;
    }
    AddLog(S);
}

// Disables the buttons on the Supplier Orders tab.
void TX509CertsForm::ResetDomButtons()
{
    TouchActivity();
    for (TButton *Btn : { doDBCheck, doDBStartOrder, doDBOrderStatus, doDBFinalOrder, doDBCollect,
            doDBRevoke, doDBCancel, doDBRemove, doDBRedist, doDBListChallg, doDBResetOrder,
            doDBEditOrder, doDBRenewal })
        Btn->Enabled = false;
    LabelInfoDomain->Caption = "Order Information:";
}

// Disables the buttons on the New Order tab.
void TX509CertsForm::ResetOrderButtons()
{
    TouchActivity();
    for (TButton *Btn : { doAcmeStartOrder, doAcmeTestChallng, doAcmeStartChallng,
            doAcmeOrderStatus, doAcmeFinalOrder, doAcmeCollectCert })
        Btn->Enabled = false;
    LabelAcme2Info->Caption = "";
}

// Shows the loaded order and enables the Supplier Orders buttons for its state.
void TX509CertsForm::SetDomButtons()
{
    ResetDomButtons();
    TSslX509Certs *X = X509Certs1;
    const TIssueState State = X->IssueState;
    doDBCheck->Enabled = true;
    doDBRemove->Enabled = true;
    doDBEditOrder->Enabled = true;
    doDBResetOrder->Enabled = true;

    // orders can only be started here for the local challenge server, UNC or DNS;
    // Delphi leaves out the persistent DNS types, which its New Order check accepts
    if (IsOneOf(X->SuppCertChallenge, { ChallFileSrv, ChallFileUNC, ChallAlpnSrv, ChallDnsAuto,
                                        ChallDnsMan, ChallDnsAcnt, ChallManual,
                                        ChallDnsPersAuto, ChallDnsPersMan }) &&
            State < IssStateFinished) {
        if (State >= IssStateChecked)
            doDBStartOrder->Enabled = true;
        if (State >= IssStateChallgReq)
            doDBOrderStatus->Enabled = true;
    }
    // orders started by applications can be completed here
    if (State >= IssStateChallgOK)
        doDBFinalOrder->Enabled = true;
    if (State >= IssStateIssued) {
        doDBCollect->Enabled = true;
        doDBCancel->Enabled = true;
    }

    String CInfo = ChallengeTypeLits[X->SuppCertChallenge];
    if (IsOneOf(X->SuppCertChallenge, { ChallFileSrv, ChallAlpnSrv })) {
        CInfo += " on " + X->ChallgSrvIP;
        if (!X->ChallgSrvIP2.IsEmpty())
            CInfo += " and " + X->ChallgSrvIP2;
    }
    String S = "Certificate Common Name Domain: " + X->CertCommonName + IcsCRLF +
               "Alternate Names: " + X->CertSANs->CommaText + IcsCRLF +
               "Challenge Type: " + CInfo + IcsCRLF +
               "Product: " + X->SuppCertProduct + IcsCRLF +
               "Cert Profile: " + X->CertAcmeProfile + IcsCRLF +
               "Order Expires: " + MyDateTimeToStr(X->ChallgExpireDT) + IcsCRLF +
               "Issue Started: " + MyDateTimeToStr(X->ChallgStartDT) + IcsCRLF +
               "Issue State: " + IssueStateLits[State] + IcsCRLF +
               "Cert Issued: " + MyDateTimeToStr(X->NewCertStartDT) + IcsCRLF +
               "Cert Expires: " + MyDateTimeToStr(X->NewCertEndDT) + IcsCRLF;
    const TDateTime EndDT = X->NewCertEndDT;
    if (X->CertRenewNow)
        S += "!! Server has been asked to immediately renew certificate, may take a few hours" + String(IcsCRLF);
    else if (EndDT.Val > 10) {
        if (EndDT < Now())
            S += "Certificate Expired";
        else if (X->CertRenewDays > 0)
            S += "Renew From: " + MyDateToStr(TDateTime(EndDT.Val - X->CertRenewDays)) + IcsCRLF +
                 "Next Check: " + MyDateTimeToStr(X->CertRenewRetryDT) + IcsCRLF +
                 "Last Check: " + MyDateTimeToStr(X->CertRenewCheckDT) + IcsCRLF;
    }
    S += "Web Cert Dir: " + Trim(X->DirPubWebCert->Text) + IcsCRLF +
         "Well-Known Dir: " + X->DirWellKnown + IcsCRLF;
#ifdef ICS_HAVE_MSCRYPT
    if (X->CertOutFmts.Contains(OutFmtWinStore))
        S += "Windows Store: " + String(MsCertLocTitles[X->MsCertLoc]) + IcsCRLF;
#endif
    S += X->AcmeChallngReport;
    LabelInfoDomain->Caption = S;
    if (State >= IssStateCollected || EndDT > Now()) {
        doDBRevoke->Enabled = true;
        doDBRedist->Enabled = true;
        doDBRenewal->Enabled = true;
    }
}

// Enables the New Order buttons for the order state.
// Delphi then logs an unassigned string, adding a blank log line after each step.
void TX509CertsForm::SetAcmeOrderButtons()
{
    ResetOrderButtons();
    doAcmeCheckOrder->Enabled = true;
    const TIssueState State = X509Certs1->IssueState;
    if (State >= IssStateFinished)
        return;
    if (State >= IssStateChecked)
        doAcmeStartOrder->Enabled = true;
    if (State >= IssStateChallgReq)
        doAcmeTestChallng->Enabled = true;
    if (State >= IssStateChallgTest)
        doAcmeStartChallng->Enabled = true;
    if (State >= IssStateChallgPend)
        doAcmeOrderStatus->Enabled = true;
    if (State >= IssStateChallgOK)
        doAcmeFinalOrder->Enabled = true;
    if (State >= IssStateIssued)
        doAcmeCollectCert->Enabled = true;
}

// True when the open account is the one shown, otherwise logs and beeps.
bool TX509CertsForm::AccountMatches()
{
    if (X509Certs1->SupplierTitle == SupplierTitle->Text)
        return true;
    AddLog("Must Select or Create ACME Account First");
    System::Sysutils::Beep();
    return false;
}

// Saves the order in the database without checking the challenges locally.
void __fastcall TX509CertsForm::doAcmeSaveOrderClick(TObject *Sender)
{
    TouchActivity();
    if (!AccountMatches())
        return;
    if (X509Certs1->CertCommonName != CertCommonName->Text) {   // a new order: clear everything
        AddLog("Saving New ACME Order for: " + CertCommonName->Text);
        X509Certs1->ClearCertOrder();
        ResetOrderButtons();
    }
    else
        AddLog("Updating Old ACME Order");
    LabelAcme2Info->Caption = "";
    if (!SetCertParams())
        return;
    X509Certs1->ChallgSrvIP = DomWebSrvIP->Text;   // the latest local server addresses
    X509Certs1->ChallgSrvIP2 = DomWebSrvIP2->Text;
    if (X509Certs1->DBWriteCNDomain())
        AddLog("Saved order to database OK");
    else
        AddLog("Failed to save domain to database");
    SetAcmeOrderButtons();
    AddLog("Saved Certificate Order");
}

// Step 1 - local checks of the challenges: copying and reading files by URL, DNS, and so on.
void __fastcall TX509CertsForm::doAcmeCheckOrderClick(TObject *Sender)
{
    TouchActivity();
    if (!AccountMatches())
        return;
    AddLog("Check ACME Order Locally (1)");
    ResetOrderButtons();
    if (X509Certs1->CertCommonName != CertCommonName->Text)   // a new order: clear everything
        X509Certs1->ClearCertOrder();
    if (!SetCertParams())
        return;
    // orders can only be placed here for the local challenge server, UNC or DNS
    if (!IsOneOf(X509Certs1->SuppCertChallenge, { ChallFileSrv, ChallFileUNC, ChallAlpnSrv, ChallDnsAuto,
            ChallDnsMan, ChallDnsAcnt, ChallManual, ChallDnsPersAuto, ChallDnsPersMan })) {
        AddLog("Can Not Place Orders using Application Challenges Here");
        return;
    }
    if (!X509Certs1->AcmeCheckSaveOrder(true, true))   // check the local challenge and save
        return;
    SetAcmeOrderButtons();
}

// Step 2 - get the ACME challenges, copy files to servers, set up DNS. Challenges and
// their results stay valid for one week.
void __fastcall TX509CertsForm::doAcmeStartOrderClick(TObject *Sender)
{
    TouchActivity();
    if (!AccountMatches())
        return;
    if (X509Certs1->IssueState < IssStateChecked) {
        AddLog("Must Check New Certificate First");
        return;
    }
    AddLog("Start Order, Get ACME Challenges (2)");
    if (!X509Certs1->AcmeV2GetChallgs())
        return;
    SetAcmeOrderButtons();
    if (SkipLocalDnsTest())
        doAcmeStartChallng->Enabled = true;
}

// Step 3 - test the ACME challenges locally by URL; files or DNS may have been set up by hand.
void __fastcall TX509CertsForm::doAcmeTestChallngClick(TObject *Sender)
{
    TouchActivity();
    if (X509Certs1->IssueState < IssStateChallgReq) {
        AddLog("Must Request Acme Challenges First");
        return;
    }
    AddLog("Test ACME Challenges Locally (3)");
    const bool Skip = SkipLocalDnsTest();
    if (Skip)
        AddLog("Skipped Local DNS Challenge Testing");
    else if (!X509Certs1->AcmeV2TestChallgs())
        return;
    SetAcmeOrderButtons();
    if (Skip)
        doAcmeStartChallng->Enabled = true;   // Delphi enables it before SetAcmeOrderButtons, which disables it again
}

// True when the order's DNS challenge is not tested locally; its state then stays at
// IssStateChallgReq, which AcmeV2StartChallgs accepts.
bool TX509CertsForm::SkipLocalDnsTest()
{
    return DnsSkipLocalTest->Checked && X509Certs1->SuppCertChallenge == ChallDnsAuto;
}

// Step 4 - once the challenges are checked locally, start the order so the supplier checks
// them and approves the order. Normally runs step 6 too unless there is an error.
void __fastcall TX509CertsForm::doAcmeStartChallngClick(TObject *Sender)
{
    TouchActivity();
    if (!AccountMatches())
        return;
    // Delphi rejects a skipped local DNS test here, although Skip Local DNS Test enabled this button.
    if (X509Certs1->IssueState < IssStateChallgWaitTest &&
            !(SkipLocalDnsTest() && X509Certs1->IssueState == IssStateChallgReq)) {
        AddLog("Must Test Acme Challenges First");
        return;
    }
    AddLog("Start ACME Challenges (4)");
    if (!X509Certs1->AcmeV2StartChallgs())
        return;
    SetAcmeOrderButtons();
    if (IsOneOf(X509Certs1->IssueState, { IssStateChallgOK, IssStateFinalPend }))
        doAcmeFinalOrderClick(this);
    if (X509Certs1->AutoOrderComplete && X509Certs1->IssueState < IssStateCollected)
        AddLog("Waiting for challenges to complete, checks every 5 seconds");
}

// Step 5 - check the order status, to see if the challenges passed.
void __fastcall TX509CertsForm::doAcmeOrderStatusClick(TObject *Sender)
{
    TouchActivity();
    if (!AccountMatches())
        return;
    if (X509Certs1->IssueState < IssStateChallgPend) {
        AddLog("Must Start Acme Challenges First");
        return;
    }
    AddLog("Check Order Status (5)");
    X509Certs1->AcmeV2OrderStatus();
    LabelAcme2Info->Caption = X509Certs1->AcmeChallngReport;
    SetAcmeOrderButtons();
}

// Step 6 - finalise the order with the CSR; the certificate may be available at once, or
// later (Google).
void __fastcall TX509CertsForm::doAcmeFinalOrderClick(TObject *Sender)
{
    TouchActivity();
    if (!AccountMatches())
        return;
    if (X509Certs1->IssueState < IssStateChallgOK) {
        AddLog("Must Start Acme Challenges First");
        return;
    }
    AddLog("Finalise Order (6)");
    if (!X509Certs1->AcmeV2OrderFinal(true))
        return;
    if (X509Certs1->IssueState == IssStateIssued)
        doAcmeCollectCertClick(this);
    else if (X509Certs1->IssueState == IssStateIssuePend)
        AddLog("Certificate issue being processing, try to collect in a few Seconds");
    SetAcmeOrderButtons();
}

// Step 7 - collect the issued certificate and save the certificate, private key,
// intermediate and bundle files. Can be repeated if the files were not saved correctly.
void __fastcall TX509CertsForm::doAcmeCollectCertClick(TObject *Sender)
{
    TouchActivity();
    if (!AccountMatches())
        return;
    if (X509Certs1->IssueState < IssStateChallgPend) {
        AddLog("Must Start Acme Order First");
        return;
    }
    if (X509Certs1->IssueState < IssStateIssuePend) {
        AddLog("Must Finalise Order First");
        return;
    }
    AddLog("Collect ACME Certificate (7)");
    X509Certs1->AcmeV2CollectCert();   // may not work while IssStateIssuePend
    SetAcmeOrderButtons();
}

void __fastcall TX509CertsForm::doCheckCSRClick(TObject *Sender)
{
    TouchActivity();
    if (CertOldCsrFile->Text.IsEmpty() || !FileExists(CertOldCsrFile->Text)) {
        AddLog("Can Not Find Old Certificate Signing Request File");
        return;
    }
    if (!CertOldPrvKey->Text.IsEmpty() && !FileExists(CertOldPrvKey->Text)) {
        AddLog("Can Not Find Old Private Key File");
        return;
    }
    X509Certs1->CertOldCsrFile = CertOldCsrFile->Text;
    X509Certs1->CertOldPrvKey = CertOldPrvKey->Text;
    X509Certs1->CheckCSR(false);
}

void __fastcall TX509CertsForm::doClearDomainClick(TObject *Sender)
{
    ResetOrderButtons();
    X509Certs1->ClearCertOrder();
    CertCommonName->Text = "";
    DirPubWebCert->Text = "";
    DirWellKnown->Text = "";
    for (int I = 1; I < CertSANGrid->RowCount; I++) {
        for (int J = 0; J < 4; J++)
            CertSANGrid->Cells[J][I] = "";
    }
}

// Loads the order selected in the Supplier Orders list.
void TX509CertsForm::LoadSelectedOrder()
{
    TouchActivity();
    const int SelNr = DatabaseDomains->ItemIndex;
    if (SelNr < 0)
        return;
    const String NewName = DatabaseDomains->Items->Item[SelNr]->Caption;
    if (NewName == X509Certs1->CertCommonName)
        return;
    doClearDomainClick(this);
    SetDomButtons();
    ResetDomButtons();
    AddLog("Loading Old Order for Domain Common Name - " + NewName);
    if (!X509Certs1->DBReadCNDomain(NewName)) {
        AddLog("Faled to read selected order");
        return;
    }
    LabelInfoDomain->Caption = "";
    SetDomButtons();
}

void __fastcall TX509CertsForm::DatabaseDomainsClick(TObject *Sender)
{
    LoadSelectedOrder();
}

void __fastcall TX509CertsForm::DatabaseDomainsSelectItem(TObject *Sender, TListItem *Item, bool Selected)
{
    LoadSelectedOrder();
}

// Copies the loaded order to the order tabs, for editing and saving again.
void __fastcall TX509CertsForm::doDBEditOrderClick(TObject *Sender)
{
    TouchActivity();
    TSslX509Certs *X = X509Certs1;
    CertCsrOrigin->ItemIndex = X->CertCsrOrigin;
    CertCommonName->Text = X->CertCommonName;
    DirWellKnown->Text = X->DirWellKnown;
    DirPubWebCert->Text = X->DirPubWebCert->Text;
    PrivKeyPassword->Text = X->PrivKeyPassword;
    PrivKeyType->ItemIndex = X->PrivKeyType;
    PrivKeyCipher->ItemIndex = X->PrivKeyCipher;
    for (int I = 0; I < static_cast<int>(DigestDispList.size()); I++) {
        if (DigestDispList[I] == X->CertSignDigestType)
            CertSignDigestType->ItemIndex = I;
    }
    SuppCertChallenge->ItemIndex = X->SuppCertChallenge;
    CertOldCsrFile->Text = X->CertOldCsrFile;
    CertOldPrvKey->Text = X->CertOldPrvKey;
    CertSerNumType->ItemIndex = X->CertSerNumType;
#ifdef ICS_HAVE_MSCRYPT
    CertMsStoreLoc->ItemIndex = X->MsCertLoc;
#endif
    CertProfile->Text = X->CertAcmeProfile;
    const TCertOutFmts Fmts = X->CertOutFmts;
    CertOutFmtBudl->Checked = Fmts.Contains(OutFmtBudl);
    CertOutFmtP12->Checked = Fmts.Contains(OutFmtP12);
    CertOutFmtP7->Checked = Fmts.Contains(OutFmtP7);
    CertOutFmtReq->Checked = Fmts.Contains(OutFmtReq);
    CertOutFmtSep->Checked = Fmts.Contains(OutFmtSep);
    CertPwPemKeys->Checked = Fmts.Contains(OutFmtPwPem);
    CertPwPkcs12->Checked = Fmts.Contains(OutFmtPwP12);
    CertWindowsStore->Checked = Fmts.Contains(OutFmtWinStore);
    CertOrganization->Text = X->CertOrganization;
    CertOrganizationalUnit->Text = X->CertOrgUnit;
    CertAddress->Text = X->CertAddress;
    CertLocality->Text = X->CertLocality;
    CertState->Text = X->CertState;
    CertPostCode->Text = X->CertPostCode;
    CertCountry->Text = X->CertCountry;
    CertContactEmail->Text = X->CertContactEmail;
    CertContactTitle->Text = X->CertContactTitle;
    CertContactFirst->Text = X->CertContactFirst;
    CertContactLast->Text = X->CertContactLast;
    CertPhone->Text = X->CertPhone;
    CertValidity->Text = IntToStr(X->CertValidity);

    // fill the grid with the alternate names; row 0 is the title row
    const int Count = X->CertSubAltNames->Count;
    if (Count > 0) {
        // Delphi grows the grid only when RowCount < Count, one row short of Count names.
        if (CertSANGrid->RowCount <= Count)
            CertSANGrid->RowCount = Count + 10;
        for (int I = 1; I <= Count; I++) {
            TSubAltName *San = X->CertSubAltNames->Items[I - 1];
            CertSANGrid->Cells[0][I] = San->Domain;
            CertSANGrid->Cells[1][I] = San->DirWellKnown;
            CertSANGrid->Cells[2][I] = San->DirPubWebCert;
            CertSANGrid->Cells[3][I] = San->ApprovalEmail;
        }
    }
    PageControl1->ActivePage = TabDomain;
    AddLog("Read domain OK: " + CertCommonName->Text + " - Save Again if Properties Changed");
}

void __fastcall TX509CertsForm::DirLogsExit(TObject *Sender)
{
    OpenLogFile();
}

// The selected order in the Supplier Orders list, or -1.
// Delphi tests SelNr > Length(DomainItems), so SelNr = Length reads past the array.
int TX509CertsForm::SelectedDomain()
{
    const int SelNr = DatabaseDomains->ItemIndex;
    if (SelNr < 0 || SelNr >= X509Certs1->DomainItems.Length)
        return -1;
    return SelNr;
}

void __fastcall TX509CertsForm::doDBCheckClick(TObject *Sender)
{
    TouchActivity();
    LabelInfoDomain->Caption = "";
    const int SelNr = SelectedDomain();
    if (SelNr < 0)
        return;
    AddLog("Check ACME Order Locally (DB)");
    if (!X509Certs1->DBReadCNDomain(X509Certs1->DomainItems[SelNr].DCommonName))
        return;
    if (IsOneOf(X509Certs1->SuppCertChallenge, { ChallFileApp, ChallAlpnApp })) {
        AddLog("!!! Warning, running server will be asked to order certifcate, will not be done here");
        doDBStartOrder->Enabled = true;
        return;
    }
    if (!X509Certs1->AcmeCheckSaveOrder(true, false))   // do not update the database yet
        return;
    SetDomButtons();
    if (X509Certs1->IssueState >= IssStateChecked)
        doDBStartOrder->Enabled = true;
}

void __fastcall TX509CertsForm::doDBStartOrderClick(TObject *Sender)
{
    TouchActivity();
    const int SelNr = SelectedDomain();
    if (SelNr < 0)
        return;
    const String CommonName = X509Certs1->DomainItems[SelNr].DCommonName;
    if (X509Certs1->CertCommonName != CommonName && !X509Certs1->DBReadCNDomain(CommonName))
        return;

    // an application challenge order can only be placed by an ICS server running IcsHosts
    if (IsOneOf(X509Certs1->SuppCertChallenge, { ChallFileApp, ChallAlpnApp })) {
        X509Certs1->CertRenewNow = true;
        X509Certs1->CertRenewRetryDT = Now();   // do not wait until the next retry check
        if (X509Certs1->DBWriteCNDomain())
            AddLog("Running server has been asked to order certifcate, may take a few hours");
        else
            AddLog("Failed to save domain to database");
        SetDomButtons();
        return;
    }

    AddLog("Start Order, Get/Test/Start ACME Challenges (DB)");
    if (!X509Certs1->AcmeV2GetChallgs())
        return;
    SetDomButtons();
    if (X509Certs1->IssueState < IssStateChallgOK) {
        if (DnsSkipLocalTest->Checked && X509Certs1->SuppCertChallenge == ChallDnsAuto)
            AddLog("Skipped Local DNS Challenge Testing");
        else if (!X509Certs1->AcmeV2TestChallgs())
            return;
        SetDomButtons();
        if (!X509Certs1->AcmeV2StartChallgs())   // starts the order timer if AutoOrderComplete
            return;
        if (X509Certs1->AutoOrderComplete && X509Certs1->IssueState < IssStateChallgOK)
            AddLog("Waiting for challenges to complete, checks every 5 seconds");
        SetDomButtons();
    }
    if (IsOneOf(X509Certs1->IssueState, { IssStateChallgOK, IssStateFinalPend }))
        doAcmeFinalOrderClick(this);
    SetDomButtons();
}

void __fastcall TX509CertsForm::doDBOrderStatusClick(TObject *Sender)
{
    TouchActivity();
    LabelInfoDomain->Caption = "";
    const int SelNr = SelectedDomain();
    if (SelNr < 0)
        return;
    const String CommonName = X509Certs1->DomainItems[SelNr].DCommonName;
    if (X509Certs1->CertCommonName != CommonName && !X509Certs1->DBReadCNDomain(CommonName))
        return;
    AddLog("Check Order Status (DB)");
    X509Certs1->AcmeV2OrderStatus();   // both order and challenge status
    LabelAcme2Info->Caption = X509Certs1->AcmeChallngReport;
    SetDomButtons();
    if (X509Certs1->IssueState >= IssStateChecked)
        doDBStartOrder->Enabled = true;   // the refresh event is not called for a check
}

void __fastcall TX509CertsForm::doDBFinalOrderClick(TObject *Sender)
{
    TouchActivity();
    const int SelNr = SelectedDomain();
    if (SelNr < 0)
        return;
    const String CommonName = X509Certs1->DomainItems[SelNr].DCommonName;
    if (X509Certs1->CertCommonName != CommonName && !X509Certs1->DBReadCNDomain(CommonName))
        return;
    AddLog("Finalise Order (DB)");
    if (IsOneOf(X509Certs1->IssueState, { IssStateChallgOK, IssStateFinalPend }) &&
            !X509Certs1->AcmeV2OrderFinal(true))
        return;
    SetDomButtons();
    if (X509Certs1->IssueState >= IssStateIssued && !X509Certs1->AcmeV2CollectCert())
        return;
    SetDomButtons();
}

void __fastcall TX509CertsForm::doDBCollectClick(TObject *Sender)
{
    TouchActivity();
    const int SelNr = SelectedDomain();
    if (SelNr < 0)
        return;
    const String CommonName = X509Certs1->DomainItems[SelNr].DCommonName;
    if (X509Certs1->CertCommonName != CommonName && !X509Certs1->DBReadCNDomain(CommonName))
        return;
    AddLog("Collect ACME Certificate (DB)");
    if (X509Certs1->IssueState >= IssStateIssuePend && !X509Certs1->AcmeV2CollectCert())
        return;
    SetDomButtons();
}

void __fastcall TX509CertsForm::doDBCancelClick(TObject *Sender)
{
    TouchActivity();
    const int SelNr = SelectedDomain();
    if (SelNr < 0)
        return;
    AddLog("Cancel Order (DB)");
    if (!X509Certs1->CertCancelDomain(X509Certs1->DomainItems[SelNr].DCommonName))
        return;
    SetDomButtons();
}

void __fastcall TX509CertsForm::doDBRedistClick(TObject *Sender)
{
    TouchActivity();
    const int SelNr = SelectedDomain();
    if (SelNr < 0)
        return;
    AddLog("Redestribute Order (DB)");
    if (!X509Certs1->CertRedistDomain(X509Certs1->DomainItems[SelNr].DCommonName))
        return;
    SetDomButtons();
}

void __fastcall TX509CertsForm::doDBRemoveClick(TObject *Sender)
{
    TouchActivity();
    const int SelNr = SelectedDomain();
    if (SelNr < 0)
        return;
    const String CommonName = X509Certs1->DomainItems[SelNr].DCommonName;
    const String Prompt = "Confirm Delete Order for: " + CommonName + " from database";
    if (Application->MessageBox(Prompt.c_str(), L"Supplier Database", MB_OKCANCEL) != IDOK)
        return;
    AddLog("Remove Order (DB)");
    if (!X509Certs1->CertRemoveDomain(CommonName))
        return;
    SetDomButtons();
}

void __fastcall TX509CertsForm::doDBRenewalClick(TObject *Sender)
{
    TouchActivity();
    const int SelNr = SelectedDomain();
    if (SelNr < 0)
        return;
    AddLog("Renewal Information (DB)");
    X509Certs1->CertRenewalDomain(X509Certs1->DomainItems[SelNr].DCommonName);
    RefreshDomains();
    SetDomButtons();
}

// Resets the order state to None to start again, if the order is confused after errors.
void __fastcall TX509CertsForm::doDBResetOrderClick(TObject *Sender)
{
    TouchActivity();
    const int SelNr = SelectedDomain();
    if (SelNr < 0)
        return;
    AddLog("Reset Order (DB)");
    if (!X509Certs1->CertResetDomain(X509Certs1->DomainItems[SelNr].DCommonName))
        return;
    SetDomButtons();
}

void __fastcall TX509CertsForm::doDBRevokeClick(TObject *Sender)
{
    TouchActivity();
    const int SelNr = SelectedDomain();
    if (SelNr < 0)
        return;
    const String CommonName = X509Certs1->DomainItems[SelNr].DCommonName;
    const String Prompt = "Confirm Revoke Order for: " + CommonName + " from database";
    if (Application->MessageBox(Prompt.c_str(), L"Supplier Database", MB_OKCANCEL) != IDOK)
        return;
    AddLog("Revoke Order (DB)");
    if (!X509Certs1->CertRevokeDomain(CommonName))
        return;
    SetDomButtons();
}

void __fastcall TX509CertsForm::doDBListChallgClick(TObject *Sender)
{
    TouchActivity();
    if (X509Certs1->ChallengesTot == 0) {
        AddLog("No Pending Challenges in Database");
        return;
    }
    AddLog("Pending Challenges in Database:");
    const TChallengeItems Items = X509Certs1->ChallengeItems;
    for (int J = 0; J < Items.Length; J++) {
        const TChallengeItem &Item = Items[J];
        if (Item.CDomain.IsEmpty())
            continue;
        AddLog("Domain: " + Item.CDomain + ", Issue State: " + IssueStateLits[Item.CIssueState] +
               ", Challenge Type: " + ChallengeTypeLits[Item.CType] +
               ", Proto: " + SupplierProtoLits[Item.CSupplierProto] +
               ", Started: " + MyDateTimeToStr(Item.CStartDT));
    }
}

void __fastcall TX509CertsForm::doLoadCAClick(TObject *Sender)
{
    X509Certs1->CACertFile = CACertFile->Text;
    X509Certs1->CAPkeyFile = CAPkeyFile->Text;
    X509Certs1->CAPkeyPw = CAPkeyPw->Text;
    if (X509Certs1->LoadOwnCA())
        LabelOwnCA->Caption = X509Certs1->NewSslCert->CertInfo(true);
    else
        LabelOwnCA->Caption = "Failed to load Own CA";
}

void __fastcall TX509CertsForm::doCASignCertClick(TObject *Sender)
{
    // Delphi turns an empty directory into "\" and writes the files to the drive root.
    if (Trim(OwnCACertDir->Text).IsEmpty()) {
        AddLog("Must Specify Own CA Certificate Directory");
        return;
    }
    if (!SetCertParams(SelfSignedCA->Checked))
        return;
    X509Certs1->SupplierProto = SuppProtoOwnCA;
    OwnCACertDir->Text = IncludeTrailingPathDelimiter(OwnCACertDir->Text);
    X509Certs1->DirCertWork = OwnCACertDir->Text;
    X509Certs1->OwnCASign(SelfSignedCA->Checked);
}

void __fastcall TX509CertsForm::doSelfSignedClick(TObject *Sender)
{
    // Delphi turns an empty directory into "\" and writes the files to the drive root.
    if (Trim(OwnCACertDir->Text).IsEmpty()) {
        AddLog("Must Specify Own CA Certificate Directory");
        return;
    }
    OwnCACertDir->Text = IncludeTrailingPathDelimiter(OwnCACertDir->Text);
    X509Certs1->DirCertWork = OwnCACertDir->Text;
    SetCertParams(SelfSignedCA->Checked);   // result ignored as in Delphi; with a CSR file origin SelfSign stops on "No Common Name"
    X509Certs1->SelfSign(SelfSignedCA->Checked);
}

void __fastcall TX509CertsForm::doSupplierWebClick(TObject *Sender)
{
    const String Url = AcmeSupplierWebURLs[AcmeSupplier->ItemIndex];
    if (IcsShellExec(Url))
        AddLog("Launched Browser to console: " + Url);
}

void __fastcall TX509CertsForm::doWebHelpPageClick(TObject *Sender)
{
    const String Url = "https://wiki.overbyte.eu/wiki/index.php/FAQ_Order_SSL_Certificates";
    if (IcsShellExec(Url))
        AddLog("Launched Browser to Help Page: " + Url);
}
