'
' This is a remake of my Password Manager program. I am actually using this
'   program to store all my passwords.
'
' Blowfish-cbc encyption with:
'   openssl enc -e -a -salt -bf -in data.txt -out data.pwd -pass pass:test
'
' Simple frontend for SSL to keep passwords in a safe place.
'
' (c) Peter van Eerten, February 2010 - GPL. Use with HUG 0.16 or higher.
'
' Version 1.0: Initial release
' Version 1.1: Capture wrong file reading, show total entries
' Version 1.2: Adapted for Cancel-button in FILEDIALOG
' Version 1.3: Compliancy with BaCon 1.0 build 11
'-------------------------------------------------------------------

OPTION BASE 1

SETENVIRON "LANG", "C"

INCLUDE "hug.bac"

' For now, we can store a 100 passwords.
CONST Max_Entries = 100

GLOBAL instance$[Max_Entries]
GLOBAL Tot_Entries, Current_Action

'-------------------------------------------------------------------

SUB Toggle_Visible

    LOCAL status

    status = GET(chk)

    IF status IS 1 THEN SET(pw, TRUE)
    ELSE SET(pw, 0)

END SUB

'-------------------------------------------------------------------

SUB Show_File_Dlg

    SHOW(file_dlg)

END SUB

'-------------------------------------------------------------------

SUB Show_Save_Dlg

    IF Tot_Entries > 0 THEN SHOW(save_dlg)

END SUB

'-------------------------------------------------------------------
' Can close multiple DIALOG widgets

SUB Close_Dialog(NUMBER widget)

    HIDE(widget)
    FOCUS(pw)

END SUB

'-------------------------------------------------------------------

SUB Hide_File_Dlg(NUMBER dialog, int button)

    LOCAL result$
    LOCAL size, x, i

    HIDE(dialog)

    IF button ISNOT GTK_RESPONSE_CANCEL THEN

        ' Decrypt using unlock key and put into list
        result$ = EXEC$(CONCAT$("openssl enc -d -a -salt -bf -in ", GRAB$(dialog), " -pass pass:", GRAB$(pw), " 2>&1"))

        IF INSTR(result$, "bad decrypt") THEN
            SHOW(errdlg1)
        ELIF INSTR(result$, "error") THEN
            SHOW(errdlg3)
        ELIF FILETYPE(GRAB$(dialog)) IS 1 THEN
            ' Cleanup array
            FOR i = 1 TO Max_Entries
                instance$[i] = ""
            NEXT

            SPLIT result$ BY NL$ TO record$ SIZE Tot_Entries

            ' Last entry closes with NL$, therefore one less to split
            DECR Tot_Entries

            FOR x = 1 TO Tot_Entries
                instance$[x] = record$[x]
            NEXT
        END IF

        SORT instance$

        ' Put into list
        TEXT(lst, "")
        FOR x = 1 TO Tot_Entries
            SPLIT instance$[Max_Entries - Tot_Entries + x] BY CHR$(9) TO field$ SIZE size
            TEXT(lst, field$[1])
        NEXT

        TEXT(user_lbl_name, "")
        TEXT(pass_lbl_name, "")
        TEXT(mainwin, CONCAT$("Password Manager - ", STR$(Tot_Entries), " entries."))

    END IF

END SUB

'-------------------------------------------------------------------

SUB Save_Data(NUMBER dialog, int button)

    LOCAL x
    LOCAL data$

    HIDE(dialog)

    IF button IS GTK_RESPONSE_YES THEN
        SORT instance$

        FOR x = 1 TO Tot_Entries
            data$ = CONCAT$(data$, instance$[Max_Entries - Tot_Entries + x], NL$)
        NEXT

        ' Encrypt using unlock key and store as file
        SYSTEM CONCAT$("echo \"", CHOP$(data$), "\" | openssl enc -e -a -salt -bf -out \"", GRAB$(save_dlg), "\" -pass pass:", GRAB$(pw), " 2>&1")

        SHOW(cfrmdlg3)
    END IF

END SUB

'-------------------------------------------------------------------

SUB Hide_Save_Dlg(NUMBER dialog, int button)

    HIDE(dialog)

    IF button ISNOT GTK_RESPONSE_CANCEL THEN

        IF FILEEXISTS(GRAB$(save_dlg)) > 4 THEN
            SHOW(cfrmdlg2)
        ELIF LEN(GRAB$(save_dlg)) > 0 THEN
            Save_Data(cfrmdlg2, GTK_RESPONSE_YES)
        END IF
    END IF

END SUB

'-------------------------------------------------------------------

SUB Lookup_Data

    LOCAL dim

    SPLIT instance$[Max_Entries - Tot_Entries + GET(lst) + 1] BY CHR$(9) TO entry$ SIZE dim

    ' Check if there is data left
    IF dim > 2 THEN
        TEXT(user_lbl_name, entry$[2])
        TEXT(pass_lbl_name, entry$[3])
    END IF

END SUB

'-------------------------------------------------------------------

SUB Delete_Entry(NUMBER widget, int button)

    LOCAL x, size

    ' Hide the confirmation dialog
    HIDE(widget)

    IF button IS GTK_RESPONSE_YES THEN

        instance$[Max_Entries - Tot_Entries + GET(lst) + 1] = ""
        DECR Tot_Entries

        TEXT(lst, "")

        ' Only when there are entries fill the list
        IF Tot_Entries > 0 THEN

            SORT instance$

            FOR x = 1 TO Tot_Entries
                SPLIT instance$[Max_Entries - Tot_Entries + x] BY CHR$(9) TO field$ SIZE size
                TEXT(lst, field$[1])
            NEXT
        END IF

        TEXT(user_lbl_name, "")
        TEXT(pass_lbl_name, "")
        TEXT(mainwin, CONCAT$("Password Manager - ", STR$(Tot_Entries), " entries."))
    END IF

END SUB

'-------------------------------------------------------------------

SUB Delete_Confirm

    IF Tot_Entries > 0 AND GET(lst) >= 0 THEN SHOW(cfrmdlg1)

END SUB

'-------------------------------------------------------------------

SUB Process_Entry

    LOCAL x

    ' Hide the dialog
    HIDE(subwin)

    ' Are we editing or adding
    IF Current_Action ISNOT 0 THEN
        instance$[Max_Entries - Tot_Entries + GET(lst) + 1] = ""
        Tot_Entries = Tot_Entries - 1
    END IF

    IF Tot_Entries < Max_Entries THEN

        SORT instance$
        instance$[1] = CONCAT$(GRAB$(entry1), CHR$(9), GRAB$(entry2), CHR$(9), GRAB$(entry3))
        Tot_Entries = Tot_Entries + 1

        TEXT(lst, "")

        ' Fill the list
        SORT instance$

        FOR x = 1 TO Tot_Entries
            SPLIT instance$[Max_Entries - Tot_Entries + x] BY CHR$(9) TO field$ SIZE size
            TEXT(lst, field$[1])
        NEXT

        TEXT(user_lbl_name, "")
        TEXT(pass_lbl_name, "")
        TEXT(mainwin, CONCAT$("Password Manager - ", STR$(Tot_Entries), " entries."))
    ELSE
        SHOW(errdlg2)
    END IF

END SUB

'-------------------------------------------------------------------

SUB Add_Entry

    ' We are adding data
    Current_Action = 0

    ' Cleanup
    TEXT(entry1, "")
    TEXT(entry2, "")
    TEXT(entry3, "")
    FOCUS(entry1)

    SHOW(subwin)

END SUB

'-------------------------------------------------------------------

SUB Edit_Entry

    LOCAL dim

    IF GET(lst) >= 0 THEN
        SPLIT instance$[Max_Entries - Tot_Entries + GET(lst) + 1] BY CHR$(9) TO entry$ SIZE dim
        TEXT(entry1, entry$[1])
        TEXT(entry2, entry$[2])
        TEXT(entry3, entry$[3])
        SHOW(subwin)
        Current_Action = 1
    END IF

END SUB

'-------------------------------------------------------------------

SUB Close_Subwin

    HIDE(subwin)

END SUB

'-------------------------------------------------------------------

' Check for OpenSSL on the system
IF ISFALSE(LEN(EXEC$("which openssl 2>/dev/null"))) THEN
    PRINT "No OpenSSL found on this system! Exiting..."
    END
ENDIF

' Initialize array
FOR i = 1 TO Max_Entries
    instance$[i] = ""
NEXT

' Create main window
mainwin = WINDOW("Password Manager", 400, 300)

' Top frame
frame1 = FRAME(390, 50)
ATTACH(mainwin, frame1, 5, 5)
label = MARK("Unlock key: ", 80, 30)
ATTACH(mainwin, label, 15, 15)
pw = PASSWORD(195, 30)
ATTACH(mainwin, pw, 100, 15)
chk = CHECK("Visible", 70, 30)
ATTACH(mainwin, chk, 315, 15)

' Body frame
frame2 = FRAME(300, 235)
ATTACH(mainwin, frame2, 5, 60)
lst = LIST(280, 150)
ATTACH(mainwin, lst, 15, 70)
user_label = MARK("Username: ", 80, 30)
ATTACH(mainwin, user_label, 15, 230)
user_lbl_name = MARK("|------------------------------------|", 190, 30)
SET(user_lbl_name, 1)
ATTACH(mainwin, user_lbl_name, 95, 230)
pass_label = MARK("Password: ", 80, 30)
ATTACH(mainwin, pass_label, 15, 260)
pass_lbl_name = MARK("|------------------------------------|", 190, 30)
SET(pass_lbl_name, 1)
ATTACH(mainwin, pass_lbl_name, 95, 260)

' Buttons
file_btn = STOCK("gtk-open", 85, 35)
ATTACH(mainwin, file_btn, 310, 60)
add_btn = STOCK("gtk-add", 85, 35)
ATTACH(mainwin, add_btn, 310, 100)
edit_btn = STOCK("gtk-edit", 85, 35)
ATTACH(mainwin, edit_btn, 310, 140)
del_btn = STOCK("gtk-delete", 85, 35)
ATTACH(mainwin, del_btn, 310, 180)
save_btn = STOCK("gtk-save", 85, 35)
ATTACH(mainwin, save_btn, 310, 220)
quit_btn = STOCK("gtk-quit", 85, 35)
ATTACH(mainwin, quit_btn, 310, 260)

' Define subwindow for adding/editing entries
subwin = WINDOW("Entry definition", 300, 175)
frame3 = FRAME(290, 165)
ATTACH(subwin, frame3, 5, 5)
label1 = MARK("     Instance: ", 90, 30)
ATTACH(subwin, label1, 15, 15)
entry1 = ENTRY("", 170, 30)
ATTACH(subwin, entry1, 115, 15)
label2 = MARK("    User name: ", 90, 30)
ATTACH(subwin, label2, 15, 50)
entry2 = ENTRY("", 170, 30)
ATTACH(subwin, entry2, 115, 50)
label3 = MARK("     Password: ", 90, 30)
ATTACH(subwin, label3, 15, 85)
entry3 = ENTRY("", 170, 30)
ATTACH(subwin, entry3, 115, 85)
ok_btn = STOCK("gtk-ok", 80, 35)
ATTACH(subwin, ok_btn, 205, 125)
can_btn = STOCK("gtk-cancel", 80, 35)
ATTACH(subwin, can_btn, 115, 125)
Close_Subwin

' Create filebrowser, save dialog
file_dlg = FILEDIALOG("Select file...", "gtk-open", 200, 80, 0)
save_dlg = FILEDIALOG("Save as file...", "gtk-save", 200, 80, 1)

' Some info messages
errdlg1 = MSGDIALOG("Wrong unlock key entered!", 250, 120, 3, 2)
errdlg2 = MSGDIALOG("No space left to store new entries!", 250, 120, 3, 2)
errdlg3 = MSGDIALOG("Could not read file!", 250, 120, 3, 2)
cfrmdlg1 = MSGDIALOG("Are you sure to delete this entry?", 250, 120, 2, 4)
cfrmdlg2 = MSGDIALOG("File exists! Overwrite?", 250, 120, 2, 4)
cfrmdlg3 = MSGDIALOG("File saved.", 250, 120, 0, 1)

' Define the callbacks
CALLBACK(quit_btn, QUIT)
CALLBACK(chk, Toggle_Visible)
CALLBACK(file_btn, Show_File_Dlg)
CALLBACK(file_dlg, Hide_File_Dlg)
CALLBACK(add_btn, Add_Entry)
CALLBACK(edit_btn, Edit_Entry)
CALLBACK(del_btn, Delete_Confirm)
CALLBACK(lst, Lookup_Data)
CALLBACK(save_btn, Show_Save_Dlg)
CALLBACK(save_dlg, Hide_Save_Dlg)
CALLBACK(can_btn, Close_Subwin)
CALLBACK(ok_btn, Process_Entry)
CALLBACK(errdlg1, Close_Dialog)
CALLBACK(errdlg2, Close_Dialog)
CALLBACK(errdlg3, Close_Dialog)
CALLBACK(cfrmdlg1, Delete_Entry)
CALLBACK(cfrmdlg2, Save_Data)
CALLBACK(cfrmdlg3, Close_Dialog)

FOCUS(pw)

' Endless GTK loop
DISPLAY