(**************************************************************************)
(*                                                                        *)
(*  Setup for Weasel                                                      *)
(*  Copyright (C) 2025   Peter Moylan                                     *)
(*                                                                        *)
(*  This program is free software: you can redistribute it and/or modify  *)
(*  it under the terms of the GNU General Public License as published by  *)
(*  the Free Software Foundation, either version 3 of the License, or     *)
(*  (at your option) any later version.                                   *)
(*                                                                        *)
(*  This program is distributed in the hope that it will be useful,       *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
(*  GNU General Public License for more details.                          *)
(*                                                                        *)
(*  You should have received a copy of the GNU General Public License     *)
(*  along with this program.  If not, see <http://www.gnu.org/licenses/>. *)
(*                                                                        *)
(*  To contact author:   http://www.pmoylan.org   peter@pmoylan.org       *)
(*                                                                        *)
(**************************************************************************)

IMPLEMENTATION MODULE SUTLS;

        (****************************************************************)
        (*                                                              *)
        (*                    PM Setup for WebServe                     *)
        (*                      TLS page of Setup                       *)
        (*                                                              *)
        (*        Started:        28 April 2025                         *)
        (*        Last edited:    3 September 2025                      *)
        (*        Status:         OK                                    *)
        (*                                                              *)
        (****************************************************************)


FROM SYSTEM IMPORT ADDRESS, INT16, CAST, ADR;

IMPORT OS2, OS2RTL, OneLine;

IMPORT DID, Strings, CommonSettings, WSUINI;

FROM Languages IMPORT
    (* type *)  LangHandle,
    (* proc *)  StrToBuffer, StrToBufferN;

FROM RINIData IMPORT
    (* type *)  StringReadState,
    (* proc *)  RemoteOperation,
                INIPut, INIPutString, INIDeleteKey,
                INIFetch, INIGetCard, INIGetTwoShort, INIGetString,
                INIPutBinary, MakeDirectory,
                GetStringList, NextString, CloseStringList;

FROM Remote IMPORT
    (* proc *)  OurDirectory;

FROM MiscFuncs IMPORT
    (* type *)  CharArrayPointer,
    (* proc *)  EVAL, ToLower;

FROM FileOps IMPORT
    (* type *)  FilenameString;

FROM Names IMPORT
    (* type *)  DomainName, FilenameIndex;

FROM Storage IMPORT
    (* proc *)  ALLOCATE, DEALLOCATE;

(**************************************************************************)

CONST
    Nul = CHR(0);
    NameLength = MAX(FilenameIndex) + 1;

VAR
    ChangeInProgress: BOOLEAN;
    UseTNI: BOOLEAN;
    OurPageID: CARDINAL;
    OurLang: LangHandle;

    (* Handle to the window that belongs to this page.  We can save     *)
    (* this as a static value because there is never more than one      *)
    (* instance of this page at any time.                               *)

    pagehandle: OS2.HWND;
    notebookhandle: OS2.HWND;

(************************************************************************)
(*                    OPERATIONS ON DIALOGUE LABELS                     *)
(************************************************************************)

PROCEDURE SetLanguage (lang: LangHandle);

    (* Relabels this page in the new language. *)

    VAR stringval: ARRAY [0..511] OF CHAR;

    BEGIN
        OurLang := lang;
        StrToBuffer (lang, "TLS.tab", stringval);
        OS2.WinSendMsg (notebookhandle, OS2.BKM_SETTABTEXT,
                        CAST(ADDRESS,OurPageID), ADR(stringval));
        StrToBuffer (lang, "TLS.enable", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.TLSEnableLabel, stringval);
        StrToBuffer (lang, "TLS.POP3", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.TLSenablePOP, stringval);
        StrToBuffer (lang, "TLS.SMTPin", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.TLSenableSMTPin, stringval);
        StrToBuffer (lang, "TLS.MSA", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.TLSenableMSA, stringval);
        StrToBuffer (lang, "TLS.SMTPout", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.TLSenableSMTPout, stringval);
        StrToBuffer (lang, "TLS.POP3Snote", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.POPSnote, stringval);
        StrToBuffer (lang, "TLS.PrivateKey", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.PrivateKeyLabel, stringval);
        StrToBuffer (lang, "TLS.ChainLabel", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.ChainLabel, stringval);
        StrToBuffer (lang, "Buttons.Add", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.AddCert, stringval);
        StrToBuffer (lang, "Buttons.Promote", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.PromoteCert, stringval);
        StrToBuffer (lang, "Buttons.Delete", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.DeleteCert, stringval);
        StrToBuffer (lang, "Buttons.Revise", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.ReviseCert, stringval);
        StrToBuffer (lang, "Buttons.Revise", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.ReviseCert, stringval);
    END SetLanguage;

(************************************************************************)
(*                     LOADING DATA FROM THE INI FILE                   *)
(************************************************************************)

PROCEDURE LoadValues (hwnd: OS2.HWND);

    (* Fills the dialogue elements on page 1 with data from the INI file,       *)
    (* or loads default values if they're not in the INI file.                  *)

    VAR state: StringReadState;
        name: FilenameString;
        enable: CARDINAL;

    BEGIN
        WSUINI.OpenINIforDomain ("");

        (* The TLS enable flags. *)

        IF NOT INIFetch ("$SYS", "TLSenable", enable) THEN
            enable := 0;
        END (*IF*);
        OS2.WinSendDlgItemMsg (hwnd, DID.TLSenablePOP, OS2.BM_SETCHECK,
                                    OS2.MPFROMSHORT(ORD(ODD(enable))), NIL);
        enable := enable DIV 2;
        OS2.WinSendDlgItemMsg (hwnd, DID.TLSenableSMTPin, OS2.BM_SETCHECK,
                                    OS2.MPFROMSHORT(ORD(ODD(enable))), NIL);
        enable := enable DIV 2;
        OS2.WinSendDlgItemMsg (hwnd, DID.TLSenableMSA, OS2.BM_SETCHECK,
                                    OS2.MPFROMSHORT(ORD(ODD(enable))), NIL);
        enable := enable DIV 2;
        OS2.WinSendDlgItemMsg (hwnd, DID.TLSenableSMTPout, OS2.BM_SETCHECK,
                                    OS2.MPFROMSHORT(ORD(ODD(enable))), NIL);

        (* Private key. *)

        IF NOT INIGetString ("$SYS", 'PrivateKey', name) THEN
            name := "";
        END (*IF*);
        OS2.WinSetDlgItemText (hwnd, DID.PrivateKey, name);

        (* The list of certificates. *)

        GetStringList ("$SYS", "CertChain", state);
        REPEAT
            NextString (state, name);
            IF name[0] <> Nul THEN
                OS2.WinSendDlgItemMsg (hwnd, DID.CertChain, OS2.LM_INSERTITEM,
                     OS2.MPFROMSHORT(OS2.LIT_END), ADR(name));
            END (*IF*);
        UNTIL name[0] = Nul;
        CloseStringList (state);
        WSUINI.CloseINIFile;

    END LoadValues;

(************************************************************************)
(*                      STORING DATA TO THE INI FILE                    *)
(************************************************************************)

PROCEDURE QueryButton (hwnd: OS2.HWND;  B: CARDINAL): CARDINAL;

    BEGIN
        RETURN OS2.LONGFROMMR (OS2.WinSendDlgItemMsg (hwnd, B,
                                              OS2.BM_QUERYCHECK, NIL, NIL));
    END QueryButton;

(**************************************************************************)

PROCEDURE StoreList (hwnd: OS2.HWND);

    (* Stores the context of the listbox whose handle is hwnd.  *)
    (* The INI file has already been opened.                    *)

    VAR bufptr: CharArrayPointer;
        BufferSize: CARDINAL;
        j, k, count, index: CARDINAL;
        name: FilenameString;

    BEGIN
        (* Work out how much buffer space we need. *)

        BufferSize := 0;
        count := OS2.ULONGFROMMR(OS2.WinSendMsg (hwnd, OS2.LM_QUERYITEMCOUNT, NIL, NIL));
        IF count > 0 THEN
            FOR index := 0 TO count-1 DO
                INC (BufferSize,
                     OS2.ULONGFROMMR(OS2.WinSendMsg (hwnd, OS2.LM_QUERYITEMTEXTLENGTH,
                                   OS2.MPFROMUSHORT(index), NIL)) + 1);
            END (*FOR*);
        END (*IF*);

        (* Create the string buffer. *)

        IF BufferSize = 0 THEN
            bufptr := NIL;
        ELSE
            INC (BufferSize);
            ALLOCATE (bufptr, BufferSize);
        END (*IF*);

        (* Store all the strings into the buffer. *)

        IF count > 0 THEN
            j := 0;
            FOR index := 0 TO count-1 DO
                OS2.WinSendMsg (hwnd, OS2.LM_QUERYITEMTEXT,
                                OS2.MPFROM2USHORT(index, NameLength), ADR(name));
                k := 0;
                REPEAT
                    bufptr^[j] := name[k];
                    INC (k);  INC (j);
                UNTIL (name[k] = Nul) OR (k = NameLength);
                bufptr^[j] := Nul;
                INC (j);
            END (*FOR*);

            bufptr^[j] := Nul;

        END (*IF*);

        (* Write the buffer to the INI file. *)

        IF BufferSize = 0 THEN
            INIPutBinary ("$SYS", "CertChain", j, 0);
        ELSE
            INIPutBinary ("$SYS", "CertChain", bufptr^, BufferSize);
        END (*IF*);

        (* Deallocate the buffer space. *)

        IF BufferSize > 0 THEN
            DEALLOCATE (bufptr, BufferSize);
        END (*IF*);

    END StoreList;

(************************************************************************)

PROCEDURE StoreData (hwnd: OS2.HWND);

    (* Stores the values on page 1 back into the INI file.  *)

    VAR str: FilenameString;
        enable: CARDINAL;

    BEGIN
        WSUINI.OpenINIforDomain ("");

        (* "TLS enabled" flags. *)

        enable := QueryButton (hwnd, DID.TLSenablePOP)
                   + 2 * QueryButton (hwnd, DID.TLSenableSMTPin)
                   + 4 * QueryButton (hwnd, DID.TLSenableMSA)
                   + 8 * QueryButton (hwnd, DID.TLSenableSMTPout);
        INIPut ('$SYS', 'TLSenable', enable);

        (* Private key file. *)

        OS2.WinQueryDlgItemText (hwnd, DID.PrivateKey, HIGH(str), str);
        INIPutString ("$SYS", 'PrivateKey', str);

        (* The list of certificates. *)

        StoreList(OS2.WinWindowFromID (hwnd, DID.CertChain));

        WSUINI.CloseINIFile;

    END StoreData;

(************************************************************************)

PROCEDURE Add (hwnd: OS2.HWND;  VAR (*IN*) name: ARRAY OF CHAR);

    (* Creates a new list entry.  *)

    VAR index: INTEGER;

    BEGIN
        index := OS2.LONGFROMMR(
                   OS2.WinSendDlgItemMsg (hwnd, DID.CertChain, OS2.LM_QUERYSELECTION, NIL, NIL));
        IF index = OS2.LIT_NONE THEN
            index := 0;
        ELSE
            INC(index);
        END (*IF*);
        OS2.WinSendDlgItemMsg (hwnd, DID.CertChain, OS2.LM_INSERTITEM,
               OS2.MPFROMSHORT(index), ADR(name));
        OS2.WinSendDlgItemMsg (hwnd, DID.CertChain, OS2.LM_SELECTITEM,
               OS2.MPFROMSHORT(index), OS2.MPFROMSHORT(ORD(TRUE)));
    END Add;

(**************************************************************************)

PROCEDURE Unselect (hwnd: OS2.HWND);

    (* Disables all buttons except "Add".  *)

    BEGIN
        OS2.WinEnableWindow (OS2.WinWindowFromID(hwnd, DID.ReviseCert), FALSE);
        OS2.WinEnableWindow (OS2.WinWindowFromID(hwnd, DID.PromoteCert), FALSE);
        OS2.WinEnableWindow (OS2.WinWindowFromID(hwnd, DID.DeleteCert), FALSE);
    END Unselect;

(**************************************************************************)

PROCEDURE ["SysCall"] DialogueProc (hwnd: OS2.HWND;  msg: OS2.ULONG;
                                      mp1, mp2: OS2.MPARAM): OS2.MRESULT;

    VAR id, code: OS2.USHORT;
        ButtonID, value: CARDINAL;
        index: INTEGER;
        listwindow: OS2.HWND;
        name: FilenameString;
        text: ARRAY [0..127] OF CHAR;

    BEGIN
        listwindow := OS2.WinWindowFromID(hwnd,DID.CertChain);
        index := OS2.LONGFROMMR(
                   OS2.WinSendDlgItemMsg (hwnd, DID.CertChain, OS2.LM_QUERYSELECTION, NIL, NIL));

        IF msg = OS2.WM_INITDLG THEN

            OS2.WinSetWindowPos (hwnd, 0, 0, 0, 0, 0, OS2.SWP_MOVE);
            LoadValues (hwnd);
            Unselect (hwnd);
            RETURN NIL;

        ELSIF msg = OS2.WM_PRESPARAMCHANGED THEN

            IF ChangeInProgress THEN
                RETURN OS2.WinDefDlgProc(hwnd, msg, mp1, mp2);
            ELSE
                ChangeInProgress := TRUE;
                CommonSettings.UpdateFontFrom (hwnd, CommonSettings.MainNotebook);
                ChangeInProgress := FALSE;
                RETURN NIL;
            END (*IF*);

        ELSIF msg = OS2.WM_COMMAND THEN

            ButtonID := OS2.SHORT1FROMMP(mp1);
            IF index = OS2.LIT_NONE THEN
                name := "";
            ELSE
                OS2.WinSendMsg (listwindow, OS2.LM_QUERYITEMTEXT,
                                OS2.MPFROM2USHORT(index, NameLength), ADR(name));
            END (*IF*);

            IF ButtonID = DID.AddCert THEN
                   name := "";
                   StrToBuffer (OurLang, "TLS.EnterName", text);
                   OneLine.Edit (hwnd, text, name, UseTNI);
                   IF name[0] <> Nul THEN
                       Add (hwnd, name);
                   END (*IF*);

            ELSIF ButtonID = DID.ReviseCert THEN

                   StrToBuffer (OurLang, "TLS.ReviseName", text);
                   OneLine.Edit (hwnd, text, name, UseTNI);
                   ToLower (name);
                   OS2.WinSendMsg (listwindow, OS2.LM_SETITEMTEXT,
                                    OS2.MPFROMSHORT(index), ADR(name));

            ELSIF ButtonID = DID.PromoteCert THEN

                   OS2.WinSendMsg (listwindow, OS2.LM_DELETEITEM,
                                          OS2.MPFROMSHORT(index), NIL);
                   DEC (index);
                   OS2.WinSendMsg (listwindow, OS2.LM_INSERTITEM,
                          OS2.MPFROMSHORT(index), ADR(name));
                   OS2.WinSendMsg (listwindow, OS2.LM_SELECTITEM,
                          OS2.MPFROMSHORT(index), OS2.MPFROMSHORT(ORD(TRUE)));

            ELSIF ButtonID = DID.DeleteCert THEN

                   OS2.WinSendDlgItemMsg (hwnd, DID.CertChain, OS2.LM_DELETEITEM,
                                          OS2.MPFROMSHORT(index), NIL);
                   Unselect (hwnd);
            END (*IF*);
            RETURN NIL;

        ELSIF msg = OS2.WM_CONTROL THEN

            id := OS2.USHORT1FROMMP (mp1);
            code := OS2.USHORT2FROMMP (mp1);
            value := OS2.ULONGFROMMP(mp2);

            IF id = DID.CertChain THEN
                IF code = OS2.LN_SELECT THEN

                    (* For some reason the more obvious code doesn't work below, so     *)
                    (* we have to use an if/then/else construct.                        *)

                    IF index > 0 THEN
                        OS2.WinEnableWindow (OS2.WinWindowFromID(hwnd, DID.PromoteCert), TRUE);
                    ELSE
                        OS2.WinEnableWindow (OS2.WinWindowFromID(hwnd, DID.PromoteCert), FALSE);
                    END (*IF*);
                    OS2.WinEnableWindow (OS2.WinWindowFromID(hwnd, DID.ReviseCert), TRUE);
                    OS2.WinEnableWindow (OS2.WinWindowFromID(hwnd, DID.DeleteCert), TRUE);
                    RETURN NIL;
                ELSIF code = OS2.LN_ENTER THEN
                    (* Treat this one as if the edit button had been clicked. *)
                    OS2.WinSendMsg (hwnd, OS2.WM_COMMAND,
                          OS2.MPFROMSHORT(DID.ReviseCert), NIL);
                    RETURN NIL;
                ELSE
                    RETURN OS2.WinDefDlgProc(hwnd, msg, mp1, mp2);
                END (*IF*);

            END (*IF*);

        END (*IF*);

        RETURN OS2.WinDefDlgProc (hwnd, msg, mp1, mp2);

    END DialogueProc;

(**************************************************************************)

PROCEDURE CreatePage (notebook: OS2.HWND;  VAR (*OUT*) PageID: CARDINAL;
                                                TNImode: BOOLEAN): OS2.HWND;

    (* Creates the TLS page and adds it to the notebook. *)

    VAR Label: ARRAY [0..31] OF CHAR;

    BEGIN
        UseTNI := TNImode;
        notebookhandle := notebook;
        pagehandle := OS2.WinLoadDlg(notebook, notebook,
                       DialogueProc,    (* dialogue procedure *)
                       0,                   (* use resources in EXE *)
                       DID.TLS,             (* dialogue ID *)
                       NIL);                 (* creation parameters *)
        PageID := OS2.ULONGFROMMR (OS2.WinSendMsg (notebook, OS2.BKM_INSERTPAGE,
                         NIL,
                         OS2.MPFROM2SHORT (OS2.BKA_MAJOR+OS2.BKA_AUTOPAGESIZE, OS2.BKA_LAST)));
        OurPageID := PageID;
        Label := "TLS";
        OS2.WinSendMsg (notebook, OS2.BKM_SETTABTEXT,
                        CAST(ADDRESS,PageID), ADR(Label));
        OS2.WinSendMsg (notebook, OS2.BKM_SETPAGEWINDOWHWND,
                        CAST(ADDRESS,PageID), CAST(ADDRESS,pagehandle));
        RETURN pagehandle;
    END CreatePage;

(**************************************************************************)

PROCEDURE SetFont (VAR (*IN*) name: CommonSettings.FontName);

    (* Sets the font of the text on this page. *)

    CONST bufsize = CommonSettings.FontNameSize;

    BEGIN
        OS2.WinSetPresParam (pagehandle, OS2.PP_FONTNAMESIZE, bufsize, name);
    END SetFont;

(************************************************************************)

PROCEDURE Close (notebook, hwnd: OS2.HWND);

    (* Shuts down this window and removes it from the notebook. *)

    BEGIN
        StoreData (hwnd);
        OS2.WinSendMsg (notebook, OS2.BKM_DELETEPAGE,
                        CAST(ADDRESS, OurPageID),
                        OS2.MPFROMLONG (OS2.BKA_SINGLE));
        OS2.WinSendMsg (hwnd, OS2.WM_CLOSE, NIL, NIL);
    END Close;

(**************************************************************************)

BEGIN
    ChangeInProgress := FALSE;
    pagehandle := OS2.NULLHANDLE;
END SUTLS.

