#!/bin/sh  
# \
exec oagwish "$0" "$@"

# $Log: not supported by cvs2svn $
# Revision 1.7  2008/06/05 20:15:56  soliday
# Updated to work with new des.
#
# Revision 1.6  2006/01/10 19:32:33  soliday
# Added blank line at end of code.
#
# Revision 1.5  2004/04/13 19:35:23  soliday
# Fixed issue when run directly from the command line.
#
# Revision 1.4  2002/12/19 21:02:04  soliday
# Fixed a bug when a period is used in the name
#
# Revision 1.3  2001/08/16 21:53:58  soliday
# This program is now used with
# procomp passwordManager
# prowrap -out passManager passwordManager.tbc
#
# Revision 1.2  2001/08/14 23:22:21  soliday
# Added an idle timeout that exits the program if it is left on.
#
# Revision 1.1  2001/08/14 23:14:33  soliday
# Stores personal passwords in encripted files.
#
#

wm geometry . +80+80
pack [frame .userFrame]
wm withdraw .
    
proc APSButton {widget args} {
    global apsContextHelp
    set parent ""
    set noPack 0
    set packOption "-side left -padx 1 -pady 1"
    set text "notext"
    set command ""
    set highlight 0
    set highlightColor black
    set size medium
    set contextHelp ""
    set fastClick 0
    set gridPack ""
    APSStrictParseArguments {parent noPack packOption text command highlight \
                               size contextHelp highlightColor fastClick gridPack}

    if {$contextHelp != ""} {
        set apsContextHelp($parent$widget) $contextHelp
    }

    if {$parent == ""} {
        if {$highlight} {
            toplevel $widget -bd 2 -bg $highlightColor
        } else {
            toplevel $widget -bd 2
        }
        wm title $widget $text
    } else {
        if {$highlight} {
            frame $parent$widget -bd 2 -bg $highlightColor
        } else {
            frame $parent$widget -bd 2
        }
        if {!$noPack} {
            if {[string length $gridPack]} {
                eval grid $parent$widget $gridPack
            } else {
                eval pack $parent$widget $packOption
            }
        }
    }
    if {$size == "small"} {
        button $parent$widget.button -text $text -command $command \
          -highlightthickness 0 -padx 3 -pady 1 -font \
          -adobe-courier-medium-r-normal-*-10-*-*-*-*-*-*-*
    } else {
        button $parent$widget.button -text $text -command $command \
          -highlightthickness 0 -padx 6 -pady 3
    }
    if {!$fastClick} {
        bind $parent$widget.button <Double-Button-1> break
    }
    pack $parent$widget.button -side top -fill x
}

proc APSStrictParseArguments {optlist} {
    upvar args arguments
    set length [llength $arguments]
    set index 0
    set leftovers {}
    while {$index<$length} {
	set arg [lindex $arguments $index]
	if {[string index $arg 0]=="-"} {
	    set keywordName [string range $arg 1 end]
	    if {[lsearch -exact $optlist $keywordName]!=-1} {
		incr index
		if {$index==$length} {
		    lappend leftovers $arg
		} else {
		    set valueString [lindex $arguments $index]
		    uplevel "set $keywordName {$valueString}"
		    incr index
		}
	    } else {
		incr index
		lappend leftovers $arg
	    }
	} else {
	    lappend leftovers $arg
	    incr index
	}
    }
    set arguments [concat $leftovers]
    if {$arguments != ""} {
	set procName [lindex [info level [expr {[info level] - 1}]] 0]
	puts stderr "Unknown option(s) given to $procName \"$arguments\""
	return -1
    } else {
	return 0
    }
}

proc APSSetVarAndUpdate {variable value} {
    global $variable
    set $variable $value
    update
}

proc APSDialogBox {widget args} {
    global apsContextHelp
    set parent ""
    set noPack 0
    set packOption "-side top -fill x"
    set name "NoName"
    set width 200
    set height 100
    set contextHelp ""
    set buttonSize medium
    set okCommand ""
    set cancelCommand ""
    APSStrictParseArguments {parent name noPack packOption width height contextHelp buttonSize okCommand cancelCommand}

    if {$contextHelp != ""} {
	set apsContextHelp($parent$widget) $contextHelp
    }

    if {$parent == ""} {
	toplevel $widget
	wm title $widget $name
    } else {
	frame $parent$widget
	if {!$noPack} {
	    eval pack $parent$widget $packOption
	}
    }

    frame $parent$widget.userFrame -relief raised -bd 2 -width $width \
      -height $height
    pack $parent$widget.userFrame -side top -fill both -expand 1

    frame $parent$widget.buttonRow -relief raised -bd 2
    pack $parent$widget.buttonRow -side bottom -fill x

    APSButton .ok -parent $parent$widget.buttonRow -text OK \
      -command "destroy $parent$widget; $okCommand" -highlight 1 -contextHelp \
      "Done interacting with this dialog box. Remove window." -size $buttonSize
    APSButton .cancel -parent $parent$widget.buttonRow -text Cancel \
      -command "destroy $parent$widget; $cancelCommand" -contextHelp \
      "Cancel interaction with dialog box. Remove window." -size $buttonSize
}

proc APSLabeledEntry {widget args} {
    global apsContextHelp
    set parent ""
    set noPack 0
    set packOption "-side top -fill x"
    set label "NoLabel"
    set textVariable ""
    set width 20
    set contextHelp ""
    set numberButtons 0
    set type ""
    set gridPack ""
    APSStrictParseArguments {parent noPack packOption label textVariable width \
                               contextHelp numberButtons type gridPack}

    if {$contextHelp != ""} {
        set apsContextHelp($parent$widget) $contextHelp
    }
    if {$parent == ""} {
        toplevel $widget -bd 1
        wm title $widget $label
    } else {
        frame $parent$widget -bd 1
        if {!$noPack} {
            if {[string length $gridPack]} {
                eval grid $parent$widget $gridPack 
            } else {
                eval pack $parent$widget $packOption
            }
        }
    }
    label $parent$widget.label -text $label
    pack $parent$widget.label -side left -fill x
    if {$numberButtons} {
        frame $parent$widget.eframe
        pack $parent$widget.eframe -side right -fill x
        set w $parent$widget.eframe
        entry $parent$widget.entry -relief sunken -width $width \
          -textvariable $textVariable
        pack $parent$widget.entry -side right -fill x -in $parent$widget.eframe
        APSEntryIncrementButtons .eib -parent $w -variableList $textVariable
    } else {
        entry $parent$widget.entry -relief sunken -width $width \
          -textvariable $textVariable
        pack $parent$widget.entry -side right -fill x
    }
    if {([llength $type]) && ([llength $textVariable])} {
	set index [string first "\(" $textVariable]
	if {([string index $textVariable [expr {[string length $textVariable] - 1}]] == "\)") && ($index > 0)} {
	    global [string range $textVariable 0 [expr {$index - 1}]]
	    global [string range $textVariable 0 [expr {$index - 1}]]_old
	    set textVariableOld [string range $textVariable 0 [expr {$index - 1}]]_old[string range $textVariable $index end]
	} else {
	    global $textVariable ${textVariable}_old
	    set textVariableOld ${textVariable}_old
	}
	if {![info exists $textVariable]} { set $textVariable "" }
	if {![info exists $textVariableOld]} { set $textVariableOld [set $textVariable] }
	trace variable $textVariable w "APSForceType $type"
    }
}

proc APSLabeledOutput {widget args} {
    global apsContextHelp
    set parent ""
    set noPack 0
    set packOption "-side top -fill x"
    set label "NoLabel"
    set textVariable ""
    set width 20
    set contextHelp ""
    set gridPack ""
    APSStrictParseArguments {parent noPack packOption label textVariable \
                               width contextHelp gridPack}
    if {$contextHelp != ""} {
        set apsContextHelp($parent$widget) $contextHelp    
    }
    if {$parent == ""} {
        toplevel $widget -bd 1
        wm title $widget $label
    } else {
        frame $parent$widget -bd 1
        if {!$noPack} {
	    if {[string length $gridPack]} {
                eval grid $parent$widget $gridPack
            } else {
                eval pack $parent$widget $packOption
            }
        }
    }
    label $parent$widget.label -text $label
    pack $parent$widget.label -side left -fill x
    global tcl_version
    if {$tcl_version == 8.4} {
        entry $parent$widget.entry -relief ridge -width $width \
          -textvariable $textVariable -readonlybackground black -foreground white -state readonly
    } else {
        entry $parent$widget.entry -relief ridge -width $width \
          -textvariable $textVariable -state disabled
    }
    pack $parent$widget.entry -side right -fill x
}

proc APSRepack {widget args} {
    eval pack $widget [pack info $widget] $args
}

if {[info commands load_unsupported] == "load_unsupported"} {
    if {[catch {load_unsupported {} Expect} results]} {
        tk_messageBox -icon error -message "$results" -type ok
        exit 1
    }
} else {
    if {[catch {package require Expect} results]} {
        tk_messageBox -icon error -message "$results" -type ok
        exit 1
    }
}

set idleTime [clock seconds]
proc ExitProgram {args} {
    global idleTime
    if {[clock seconds] > [expr {$idleTime + 60}]} {
	exit
    }
    after 5000 ExitProgram
}

set ready 0

proc NewSetup {state} {
    APSSetVarAndUpdate idleTime [clock seconds]
    if {$state == "cancel"} exit
    global safeCombination safeCombination2
    if {$safeCombination != $safeCombination2} {
	tk_messageBox -type ok \
	    -icon error \
	    -message "The two entries do not match." \
	    -parent .newSetup \
	    -title "Password Manager"
	return
    }
    if {[string length $safeCombination] == 0} {
	tk_messageBox -type ok \
	    -icon warning \
	    -message "Please enter a key and verify it." \
	    -parent .newSetup \
	    -title "Password Manager"
	return
    }
    destroy .newSetup
    .userFrame.f1.add.button configure -state normal
    .userFrame.f1.save.button configure -state normal
    AddEntry
    
}

set names ""
set systems ""
set passwords ""

proc ShowEntries {args} {
    APSSetVarAndUpdate idleTime [clock seconds]
    global names passwords systems
    global pw
    set i 0
    foreach name $names password $passwords system $systems {
        if {$system == ""} {
            set label "Username: $name"
        } else {
            set label "System: $system  Username: $name"
        }
        incr i
        destroy .userFrame.name${i}
        set pw($name) $password
        APSLabeledOutput .name${i} \
          -parent .userFrame \
          -label $label \
          -textVariable pw($name)
        .userFrame.name${i}.entry configure -show "*"
        APSButton .remove \
          -parent .userFrame.name${i} \
          -text "Remove" \
          -size small \
          -packOption "-side right" \
          -command "destroy .userFrame.name${i}; RemoveName $name"
        APSButton .edit \
          -parent .userFrame.name${i} \
          -text "Edit" \
          -size small \
          -packOption "-side right" \
          -command "destroy .userFrame.name${i}; EditName $name"
        APSButton .hide \
          -parent .userFrame.name${i} \
          -text "Hide" \
          -size small \
          -packOption "-side right" \
          -command ".userFrame.name${i}.entry configure -show \"*\""
        APSButton .show \
          -parent .userFrame.name${i} \
          -text "Show" \
          -size small \
          -packOption "-side right" \
          -command ".userFrame.name${i}.entry configure -show \"\""
        APSRepack .userFrame.name${i}.entry -side right
    }
}

proc RemoveName {name} {
    APSSetVarAndUpdate idleTime [clock seconds]
    global names passwords systems
    set index [lsearch $names $name]
    set names [lreplace $names $index $index]
    set passwords [lreplace $passwords $index $index]
    set systems [lreplace $systems $index $index]
}
proc EditName {name} {
    APSSetVarAndUpdate idleTime [clock seconds]
    global names passwords systems
    set index [lsearch $names $name]
    set name [lindex $names $index]
    set password [lindex $passwords $index]
    set system [lindex $systems $index]
    set names [lreplace $names $index $index]
    set passwords [lreplace $passwords $index $index]
    set systems [lreplace $systems $index $index]
    AddEntry -name $name -pass $password -system $system
}

proc AddEntry {args} {
    set name ""
    set pass ""
    set system ""
    APSStrictParseArguments {name pass system}
    APSSetVarAndUpdate idleTime [clock seconds]
    wm withdraw .

    .userFrame.f1.add.button configure -state disabled

    global userName passWord passWord2 computerSystem
    set userName ""
    set passWord ""
    set passWord2 ""
    set computerSystem $system
    set userName $name
    set passWord $pass
    set passWord2 $pass
    APSDialogBox .addEntry \
	-name "Add Entry" \
	-okCommand "AddPassword ok" \
	-cancelCommand "AddPassword cancel"
    wm geometry .addEntry +80+80
    .addEntry.buttonRow.ok.button configure -command "AddPassword ok"
    APSLabeledEntry .system \
	-parent .addEntry.userFrame \
	-label "         System:" \
	-textVariable computerSystem -width 20 -packOption "-anchor ne"
    APSLabeledEntry .username \
	-parent .addEntry.userFrame \
	-label "       Username:" \
	-textVariable userName -width 20 -packOption "-anchor ne"
    APSLabeledEntry .password \
	-parent .addEntry.userFrame \
	-label "       Password:" \
	-textVariable passWord -width 20 -packOption "-anchor ne"
    .addEntry.userFrame.password.entry configure -show "*"
    APSLabeledEntry .password2 \
	-parent .addEntry.userFrame \
	-label "Verify Password:" \
	-textVariable passWord2 -width 20 -packOption "-anchor ne"
    .addEntry.userFrame.password2.entry configure -show "*"
}

proc AddPassword {state} {
    APSSetVarAndUpdate idleTime [clock seconds]
    .userFrame.f1.add.button configure -state normal

    if {$state == "cancel"} {
	wm deiconify .
	return
    }
    global userName passWord passWord2 computerSystem
    global names passwords systems
    if {$passWord != $passWord2} {
	tk_messageBox -type ok \
	    -icon error \
	    -message "The two password entries do not match." \
	    -parent .addEntry \
	    -title "Password Manager"
	return
    }
    if {[string length $passWord] == 0} {
	tk_messageBox -type ok \
	    -icon warning \
	    -message "Please enter a password and verify it." \
	    -parent .addEntry \
	    -title "Password Manager"
	return
    }
    set computerSystem [string trim $computerSystem]
    if {[llength $computerSystem] < 1} {
	tk_messageBox -type ok \
	    -icon warning \
	    -message "Please enter a valid system." \
	    -parent .addEntry \
	    -title "Password Manager"
	return
    }
    set userName [string trim $userName]
    if {[llength $userName] != 1} {
	tk_messageBox -type ok \
	    -icon warning \
	    -message "Please enter a valid username." \
	    -parent .addEntry \
	    -title "Password Manager"
	return
    }
    destroy .addEntry
    lappend names $userName
    lappend systems $computerSystem
    lappend passwords $passWord
    ShowEntries
    wm deiconify .
}

proc SaveEnties {args} {
    APSSetVarAndUpdate idleTime [clock seconds]
    global timeout expect_out safeCombination names systems passwords env tcl_platform

    set data "VERSION2\n"
    foreach system $systems name $names password $passwords {
	append data "${system}\n${name}\n${password}\n"
    }
    set timeout 10

    if {$tcl_platform(os) == "Linux"} {
        spawn -noecho des -e /dev/stdin $env(HOME)/.oagpwsafe
        match_max -i $spawn_id 10000
        log_user 0
        
        expect "Enter key:"
        exp_send "${safeCombination}\r"

        expect "Verifying password - Enter key:"
        exp_send "${safeCombination}\r${data}\r"

    } else {
        spawn -noecho /usr/bin/des -f -e /dev/stdin $env(HOME)/.oagpwsafe
        match_max -i $spawn_id 10000
        log_user 0
        
        expect "Enter key:"
        exp_send "${safeCombination}\r"
        
        exp_send "${data}\r"
    }
    after 1000
    
    exp_close
    exp_wait
}

proc ReadEntries {state} {
    APSSetVarAndUpdate idleTime [clock seconds]
    if {$state == "cancel"} exit
    global timeout expect_out safeCombination names passwords systems env tcl_platform

    set results ""
    set err ""

    set timeout 10

    if {$tcl_platform(os) == "Linux"} {
        spawn -noecho des -d $env(HOME)/.oagpwsafe
        match_max -i $spawn_id 10000
        log_user 0
        
        expect "Enter key:"
        exp_send "${safeCombination}\r"
        expect {
            "The file was not decrypted correctly." {
                set err "oldDES"
                exp_continue
            } "\n" {
                set data [string trim [string trim $expect_out(buffer) \n] \r]
                if {[string length $data]} {
                    lappend results $data
                }
                exp_continue
            } eof {
            }
        }
        catch {exp_close}


        if {$err == "oldDES"} {
            spawn -noecho des -D $env(HOME)/.oagpwsafe
            match_max -i $spawn_id 10000
            log_user 0
            
            expect "Enter key:"
            exp_send "${safeCombination}\r"
            expect {
                "The file was not decrypted correctly." {
                    set err "error"
                    exp_continue
                } "\n" {
                    set data [string trim [string trim $expect_out(buffer) \n] \r]
                    if {[string length $data]} {
                        lappend results $data
                    }
                    exp_continue
                } eof {
                }
            }
            catch {exp_close}
        }

    } else {
        spawn -noecho /usr/bin/des -f -d $env(HOME)/.oagpwsafe
        match_max -i $spawn_id 10000
        log_user 0
        
        expect "Enter key:"
        exp_send "${safeCombination}\r"
        expect {
            "The file was not decrypted correctly." {
                set err "error"
                exp_continue
            } "decryption failed" {
                set err "oldDES"
                exp_continue
            } "\n" {
                set data [string trim [string trim $expect_out(buffer) \n] \r]
                if {[string length $data]} {
                    lappend results $data
                }
                exp_continue
            } eof {
            }
        }
        catch {exp_close}
        
        if {$err == "oldDES"} {
            set err ""
            set results ""
            spawn -noecho /opt/local/bin/des -D $env(HOME)/.oagpwsafe
            match_max -i $spawn_id 10000
            log_user 0
            
            expect "Enter key:"
            exp_send "${safeCombination}\r"
            expect {
                "The file was not decrypted correctly." {
                    set err "error"
                    exp_continue
                } "\n" {
                    set data [string trim [string trim $expect_out(buffer) \n] \r]
                    if {[string length $data]} {
                        lappend results $data
                    }
                    exp_continue
                } eof {
                }
            }
            catch {exp_close}
        }
    }

    if {$err == "error"} {
	tk_messageBox -type ok \
	    -icon warning \
	    -message "Invalid Safe Combination." \
	    -parent .queryCombination \
	    -title "Password Manager"
	return
    }
    set names ""
    set systems ""
    set passwords ""
    if {[lindex $results 0] == "VERSION2"} {
        foreach "system name password" [lrange $results 1 end] {
            lappend names $name
            lappend systems $system
            lappend passwords $password
        }
    } else {
        foreach "name password" $results {
            lappend names $name
            lappend passwords $password
            lappend systems "---"
        }
    }
    ShowEntries
    destroy .queryCombination
    .userFrame.f1.add.button configure -state normal
    .userFrame.f1.save.button configure -state normal
    wm deiconify .

}

pack [frame .userFrame.f1] -side bottom -fill x

APSButton .add \
    -parent .userFrame.f1 \
    -text "Add Entry" \
    -command "AddEntry"
.userFrame.f1.add.button configure -state disabled
APSButton .save \
    -parent .userFrame.f1 \
    -text "Save" \
    -command "SaveEnties"
.userFrame.f1.save.button configure -state disabled
APSButton .exit \
    -parent .userFrame.f1 \
    -text "Exit" \
    -command "exit"

ShowEntries

set safeCombination ""
set safeCombination2 ""

if {!([file exists $env(HOME)/.oagpwsafe])} {
    wm withdraw .
    APSDialogBox .newSetup \
	-name "Safe Combination Setup" \
	-okCommand "NewSetup ok" \
	-cancelCommand "NewSetup cancel"
    wm geometry .newSetup +80+80
    .newSetup.buttonRow.ok.button configure -command "NewSetup ok"
    label .newSetup.userFrame.l1 \
	-wraplength 300 \
	-justify left \
	-text "A new password database will be created. The safe combination you enter will be used to encrypt the password database file. The Safe Comination can use any keyboard characters and it is case-censitive"
    frame .newSetup.userFrame.f1

    APSLabeledEntry .safeCombo \
	-parent .newSetup.userFrame.f1 \
	-label "Safe Combination:" \
	-textVariable safeCombination -width 20
    .newSetup.userFrame.f1.safeCombo.entry configure -show "*"

    APSLabeledEntry .safeCombo2 \
	-parent .newSetup.userFrame.f1 \
	-label "          Verify:" \
	-textVariable safeCombination2 -width 20
    .newSetup.userFrame.f1.safeCombo2.entry configure -show "*"

    pack .newSetup.userFrame.l1
    pack .newSetup.userFrame.f1
        
} else {
    wm withdraw .
    APSDialogBox .queryCombination \
	-name "Safe Combination Query" \
	-okCommand "ReadEntries ok" \
	-cancelCommand "ReadEntries cancel"
    .queryCombination.buttonRow.ok.button configure -command "ReadEntries ok"
    wm geometry .queryCombination +80+80
    label .queryCombination.userFrame.l1 \
	-wraplength 300 \
	-justify left \
	-text "The safe combination you enter will be used to decrypt the password database file. The Safe Comination can use any keyboard characters and it is case-censitive"
    frame .queryCombination.userFrame.f1

    APSLabeledEntry .safeCombo \
	-parent .queryCombination.userFrame.f1 \
	-label "Safe Combination:" \
	-textVariable safeCombination -width 20
    .queryCombination.userFrame.f1.safeCombo.entry configure -show "*"

    pack .queryCombination.userFrame.l1
    pack .queryCombination.userFrame.f1
}

ExitProgram
