'
' 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
' Version 1.4: Compliancy with BaCon 1.0 build 26
'-------------------------------------------------------------------

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$ SIZE Tot_Entries

	' Put into list
	TEXT(lst, "")
	FOR x = 1 TO Tot_Entries
	    SPLIT instance$[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$ SIZE Tot_Entries

	FOR x = 1 TO Tot_Entries
	    data$ = CONCAT$(data$, instance$[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$[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

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

	    instance$[GET(lst) + 1] = "zzz"
	    SORT instance$ SIZE Tot_Entries
	    instance$[Tot_Entries] = ""

	    TEXT(lst, "")

	    DECR Tot_Entries

	    FOR x = 1 TO Tot_Entries
		SPLIT instance$[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)

    IF Tot_Entries < Max_Entries THEN

    ' Are we editing or adding
	IF Current_Action ISNOT 0 THEN
	    instance$[GET(lst)+1] = CONCAT$(GRAB$(entry1), CHR$(9), GRAB$(entry2), CHR$(9), GRAB$(entry3))
	ELSE
	    INCR Tot_Entries
	    instance$[Tot_Entries] = CONCAT$(GRAB$(entry1), CHR$(9), GRAB$(entry2), CHR$(9), GRAB$(entry3))
	END IF

	TEXT(lst, "")

	' Fill the list
	SORT instance$ SIZE Tot_Entries

	FOR x = 1 TO Tot_Entries
	    SPLIT instance$[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$[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