password.bac

'
' 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 -md md5 -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
' Version 1.4: Compliancy with BaCon 1.0 build 26
' Version 1.5: - Major code updates, now requires BaCon 3.5 and higher
'              - Added md5 hash to let newer OpenSSL open old .pwd files
' Version 1.6: Added -pbkdf2 option.
' Version 1.7: Added providers for decrypt and encrypt (OpenSSL 1.3).
'-------------------------------------------------------------------

SETENVIRON "LANG", "C"
INCLUDE hug

CONST GTK_JUSTIFY_LEFT = 0

' Data is stored as a delim string
GLOBAL Pw_Data$

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

SUB Toggle_Visible

    IF GET(chk) = 1 THEN
        SET(pw, TRUE)
    ELSE
        SET(pw, FALSE)
    ENDIF

END SUB

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

SUB Show_File_Dlg

    SHOW(file_dlg)

END SUB

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

SUB Show_Save_Dlg

    IF AMOUNT(Pw_Data$, NL$) THEN SHOW(save_dlg)

END SUB

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

SUB Close_Dialog(NUMBER widget)

    HIDE(widget)
    FOCUS(pw)

END SUB

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

SUB Refresh_List

    LOCAL line$

    ' Put into list
    TEXT(lst, "")
    FOR line$ IN Pw_Data$ STEP NL$
        TEXT(lst, TOKEN$(line$, 1, CHR$(9)))
    NEXT

    TEXT(user_lbl_name, "")
    TEXT(pass_lbl_name, "")
    TEXT(mainwin, "Password Manager - " & STR$(AMOUNT(Pw_Data$, NL$)) & " entries.")

ENDSUB

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

SUB Hide_File_Dlg(NUMBER dialog, int button)

    HIDE(dialog)

    IF button <> GTK_RESPONSE_CANCEL THEN

        ' Decrypt using unlock key and put into list
        Pw_Data$ = SORT$(CHOP$(EXEC$("openssl enc -d -a -salt -pbkdf2 -bf -md md5 -provider legacy -provider default -in " & GRAB$(dialog) & " -pass pass:" & GRAB$(pw) & " 2>&1")), NL$)

        IF TALLY(Pw_Data$, "bad decrypt") THEN
            SHOW(errdlg1)
            EXIT SUB
        ELIF TALLY(Pw_Data$, "error") THEN
            SHOW(errdlg3)
            EXIT SUB
        END IF

        Refresh_List()
    ENDIF

ENDSUB

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

SUB Save_Data(NUMBER dialog, int button)

    HIDE(dialog)

    IF button = GTK_RESPONSE_YES THEN
        ' Encrypt using unlock key and store as file
        SYSTEM "echo \"" & Pw_Data$ & "\" | openssl enc -e -a -salt -pbkdf2 -bf -md md5 -provider legacy -provider default -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 <> GTK_RESPONSE_CANCEL THEN
        IF FILEEXISTS(GRAB$(save_dlg)) THEN
            SHOW(cfrmdlg2)
        ELIF LEN(GRAB$(save_dlg)) > 0 THEN
            Save_Data(cfrmdlg2, GTK_RESPONSE_YES)
        ENDIF
    ENDIF

ENDSUB

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

SUB Lookup_Data

    LOCAL line$

    line$ = TOKEN$(Pw_Data$, GET(lst)+1, NL$)

    ' Show data
    TEXT(user_lbl_name, TOKEN$(line$, 2, CHR$(9)))
    TEXT(pass_lbl_name, TOKEN$(line$, 3, CHR$(9)))

END SUB

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

SUB Delete_Entry(NUMBER widget, int button)

    ' Hide the confirmation dialog
    HIDE(widget)

    IF button = GTK_RESPONSE_YES THEN

        ' Delete the selected entry
        Pw_Data$ = DEL$(Pw_Data$, GET(lst)+1, NL$)

        Refresh_List()
    END IF

END SUB

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

SUB Delete_Confirm

    IF AMOUNT(Pw_Data$, NL$) AND GET(lst) >= 0 THEN SHOW(cfrmdlg1)

END SUB

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

SUB Process_Entry

    ' Hide the dialog
    HIDE(subwin)

    ' Are we editing or adding
    IF GRAB$(subwin) = "Edit existing entry" THEN
        Pw_Data$ = CHANGE$(Pw_Data$, GET(lst)+1, GRAB$(entry1) & CHR$(9) & GRAB$(entry2) & CHR$(9) & GRAB$(entry3), NL$)
    ELSE
        Pw_Data$ = SORT$(APPEND$(Pw_Data$, 0, GRAB$(entry1) & CHR$(9) & GRAB$(entry2) & CHR$(9) & GRAB$(entry3), NL$), NL$)
    END IF

    Refresh_List()

END SUB

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

SUB Add_Entry

    TEXT(subwin, "Add new entry")

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

    SHOW(subwin)

ENDSUB

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

SUB Edit_Entry

    LOCAL line$

    TEXT(subwin, "Edit existing entry")

    IF GET(lst) >= 0 THEN
        line$ = TOKEN$(Pw_Data$, GET(lst)+1, NL$)
        TEXT(entry1, TOKEN$(line$, 1, CHR$(9)))
        TEXT(entry2, TOKEN$(line$, 2, CHR$(9)))
        TEXT(entry3, TOKEN$(line$, 3, CHR$(9)))
        SHOW(subwin)
    ENDIF

ENDSUB

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

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

' 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("|------------------------------------|", 260, 30)
SET(user_lbl_name, 1)
gtk_misc_set_alignment(user_lbl_name, 0, 0.5)
ATTACH(mainwin, user_lbl_name, 95, 230)
pass_label = MARK("Password: ", 80, 30)
ATTACH(mainwin, pass_label, 15, 260)
pass_lbl_name = MARK("|------------------------------------|", 260, 30)
SET(pass_lbl_name, 1)
gtk_misc_set_alignment(pass_lbl_name, 0, 0.5)
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("Add new entry", 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!", 350, 140, 3, 2)
errdlg3 = MSGDIALOG("Could not read file!", 250, 120, 3, 2)
cfrmdlg1 = MSGDIALOG("Are you sure to delete this entry?", 350, 140, 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

Generated by GNU Enscript 1.6.5.90.