# $Id: gpgme.tcl 1502 2008-09-13 09:37:16Z sergei $

namespace eval ::ssj {}

#############################################################################
# Draw icons aside encrypted messages even if no GPG support

proc ::ssj::draw_encrypted {chatid from type body x} {
    # we already deciphered it in rewrite_message_hook

    set chatw [chat::chat_win $chatid]

    foreach xe $x {
	jlib::wrapper:splitxml $xe tag vars isempty cdata children

	if {![cequal [jlib::wrapper:getattr $vars xmlns] $::NS(encrypted)]} {
	    continue
	}

	if {[cequal $cdata ""] || \
	    [cequal [info commands ::ssj::encrypted:input] ""]} {
	    $chatw image create end -image gpg/badencrypted
	} else {
	    $chatw image create end -image gpg/encrypted
	}
	break
    }
}

hook::add draw_message_hook ::ssj::draw_encrypted 6

#############################################################################

proc ::ssj::process_x_encrypted {rowvar bodyvar f x connid from id type replyP} {
    upvar 2 $rowvar row
    upvar 2 $bodyvar body

    if {!$replyP || [cequal $type error]} {
	return
    }

    foreach xa $x {
	jlib::wrapper:splitxml $xa tag vars isempty cdata children
	set xmlns [jlib::wrapper:getattr $vars xmlns]

	if {$xmlns != $::NS(encrypted)} continue

	# we already deciphered it in rewrite_message_hook
	set lb [join [lrange [split $f .] 0 end-1] .].title.encrypted
	if {[winfo exists $lb]} {
	    destroy $lb
	}

	if {[cequal $cdata ""] || \
	    [cequal [info commands ::ssj::encrypted:input] ""]} {
	    Label $lb -image gpg/badencrypted
	} else {
	    Label $lb -image gpg/encrypted
	}
	grid $lb -row 1 -column 3 -sticky e
    }

    return
}

hook::add message_process_x_hook ::ssj::process_x_encrypted 21

#############################################################################

if {![catch {package require gpg}]} {
    set gpgPkg gpg
} elseif {![catch {package require gpgme}]} {
    set gpgPkg gpgme
} else {
    debugmsg ssj "unable to load the GPG package, so no crypto!"
    return
}

namespace eval ::ssj {
    variable options

    custom::defgroup GPG [::msgcat::mc "GPG options (signing and encryption)."] \
	-group Tkabbur

    custom::defvar options(one-passphrase) 1 \
	[::msgcat::mc "Use the same passphrase for signing and decrypting messages."] \
	-group GPG -type boolean

    custom::defvar options(sign-traffic) 0 \
	[::msgcat::mc "GPG-sign outgoing messages and presence updates."] \
	-group GPG -type boolean

    custom::defvar options(encrypt-traffic) 0 \
	[::msgcat::mc "GPG-encrypt outgoing messages where possible."] \
	-group GPG -type boolean

    custom::defvar options(key) "" \
	[::msgcat::mc "Use specified key ID for signing and decrypting messages."] \
	-group GPG -type string

    custom::defvar options(display_sig_warnings) 1 \
	[::msgcat::mc "Display warning dialogs when signature verification fails."] \
	-group GPG -type boolean
}


package require base64


namespace eval ::ssj {
    variable ctx
    variable e4me
    variable j2k
    variable options
    variable passphrase
    variable s2e
    variable signers
    variable warnings
    variable gpg_error_id 0

    array set ctx {}

    array set j2k {}

    array set options {}

    array set passphrase {}

    array set s2e \
          [list none       [::msgcat::mc "No information available"] \
                bad        [::msgcat::mc "Invalid signature"] \
                nokey      [::msgcat::mc "Signature not processed due to missing key"] \
                nosig      [::msgcat::mc "Malformed signature block"] \
                error      [::msgcat::mc "Error in signature processing"] \
                diff       [::msgcat::mc "Multiple signatures having different authenticity"] \
                expired    [::msgcat::mc "The signature is good but has expired"] \
                expiredkey [::msgcat::mc "The signature is good but the key has expired"]]

    catch {unset warnings}
    array set warnings {}

    variable signedid 0
}


proc ::ssj::once_only {connid {armorP 0}} {
    global env gpgPkg
    variable options
    variable ctx

    debugmsg ssj "ONCE_ONLY $connid"

    if {[info exists ctx($connid)] && ![cequal $ctx($connid) ""]} {
        $ctx($connid) -operation set   \
		      -property  armor \
		      -value     $armorP

        return
    }

    set ctx($connid) [${gpgPkg}::context]
    $ctx($connid) -operation set   \
		  -property  armor \
		  -value     $armorP


    if {![info exists env(GPG_AGENT_INFO)]} {
        $ctx($connid) -operation set                 \
		      -property  passphrase-callback \
		      -value     [list ::ssj::passphrase $connid]
    }

    set pattern [jlib::connection_bare_jid $connid]

    set firstP 1
    if {$options(key) != ""} {
	set patterns [list $options(key)]
    } else {
	set patterns {}
    }
    lappend patterns $pattern ""
    foreach p $patterns {
        set command [list $ctx($connid) -operation start-key -secretonly true]
        if {![cequal $p ""]} {
            lappend command -patterns [list $p]
        }
        eval $command

        for {set keys {}} \
            {![cequal [set key [$ctx($connid) -operation next-key]] ""]} \
            {lappend keys $key} {}
        $ctx($connid) -operation done-key

        if {[llength $keys] > 0} {
            break
        }
        if {[cequal $p ""]} {
            return
        }
        set firstP 0
    }

    switch -- [llength $keys] {
        0 {
            return
        }

        1 {
            if {$firstP} {
                e4meP $connid $keys
                return
            }
        }

        default {
        }
    }

    set dw .selectkey$connid
    catch {destroy $dw}

    set titles {}
    set balloons {}
    foreach key $keys {
	set key_info [$ctx($connid) -operation info-key -key $key]
        foreach {k v} $key_info {
	    if {[string equal $k email]} {
		lappend titles $key $v
		lappend balloons $key [key_balloon_text $key_info]
		break
	    }
	}
        foreach {k v} [$ctx($connid) -operation info-key -key $key] {
	    if {![string equal $k subkeys]} {
		continue
	    }

	    foreach subkey $v {
		foreach {k1 v1} $subkey {
		    if {[string equal $k1 email]} {
			lappend titles $key $v1
			lappend balloons $key [key_balloon_text $subkey]
			break
		    }
		}
	    }
	}
    }

    CbDialog $dw [::msgcat::mc "Select Key for Signing %s Traffic" $pattern] \
        [list [::msgcat::mc "Select"] "::ssj::once_only_aux $dw $connid" \
	      [::msgcat::mc "Cancel"] "destroy $dw"] \
	::ssj::selectkey$connid $titles $balloons \
	-modal local
}

proc ::ssj::key_balloon_text {key} {
    array set params $key
    if {[catch {format "%d%s/%s %s" $params(length)          \
                       [string range $params(algorithm) 0 0]   \
                       [string range $params(keyid) end-7 end] \
                       [clock format $params(created)          \
                                     -format "%Y-%m-%d"]} text]} {
	return ""
    }

    foreach {k v} $key {
	switch -- $k {
	    userid {
		append text [format "\n\t%s" $v]
	    }
	}
    }
    return $text
}

proc ::ssj::once_only_aux {dw connid} {
    variable selectkey$connid

    set keys {}
    foreach key [array names selectkey$connid] {
        if {[set selectkey${connid}($key)]} {
            lappend keys $key
        }
    }

    destroy $dw

    if {[llength $keys] > 0} {
        e4meP $connid $keys
    }
}


proc ::ssj::passphrase {connid data} {
    variable passphrase
    variable options

    array set params $data
    set lines [split [string trimright $params(description)] "\n"]
    set text [lindex $lines 0]

    if {[set x [string first " " [set keyid [lindex $lines 1]]]] > 0} {
        set userid [string range $keyid [expr $x+1] end]
        if {!$options(one-passphrase)} {
            set keyid [string range $keyid 0 [expr $x-1]]
        } else {
            regexp { +([^ ]+)} [lindex $lines 2] ignore keyid
        }
    } else {
        set userid unknown!
    }

    if {([cequal $text ENTER]) \
            && ([info exists passphrase($keyid)]) \
            && (![cequal $passphrase($keyid) ""])} {
        return $passphrase($keyid)
    }

    set pw .passphrase$connid
    if {[winfo exists $pw]} {
        destroy $pw
    }

    set title [::msgcat::mc "Please enter passphrase"]
    switch -- $text {
        ENTER {
        }

        TRY_AGAIN {
            set title [::msgcat::mc "Please try again"]
        }

        default {
            append title ": " $text
        }
    }
    Dialog $pw -title $title -separator 1 -anchor e -default 0 -cancel 1

    set pf [$pw getframe]
    grid columnconfigure $pf 1 -weight 1

    foreach {k v} [list keyid  [::msgcat::mc "Key ID"] \
			userid [::msgcat::mc "User ID"]] {
        label $pf.l$k -text ${v}:
        entry $pf.$k
        $pf.$k insert 0 [set $k]
        if {[string length [set $k]] <= 72} {
            $pf.$k configure -width 0
        }
        if {[info tclversion] >= 8.4} {
            set bgcolor [lindex [$pf.$k configure -background] 4]
            $pf.$k configure -state readonly -readonlybackground $bgcolor
        } else {
            $pf.$k configure -state disabled
        }
    }

    label $pf.lpassword -text [::msgcat::mc "Passphrase:"]
    entry $pf.password  \
	  -textvariable ::ssj::passphrase($connid,$keyid) \
          -show *
    set passphrase($connid,$keyid) ""

    grid $pf.lkeyid    -row 0 -column 0 -sticky e
    grid $pf.keyid     -row 0 -column 1 -sticky ew
    grid $pf.luserid   -row 1 -column 0 -sticky e
    grid $pf.userid    -row 1 -column 1 -sticky ew
    grid $pf.lpassword -row 2 -column 0 -sticky e
    grid $pf.password  -row 2 -column 1 -sticky ew

    $pw add -text [::msgcat::mc "OK"] -command "$pw enddialog 0"
    $pw add -text [::msgcat::mc "Cancel"] -command "$pw enddialog 1"

    if {[set abort [$pw draw $pf.password]]} {
        $params(token) -operation cancel
	# TODO: unset options(sign-traffic) etc. ?
    }

    destroy $pw

    if {!$abort} {
	set passphrase($keyid) $passphrase($connid,$keyid)
	unset passphrase($connid,$keyid)
        return $passphrase($keyid)
    }
}


proc ::ssj::armor:encode {text} {
    if {[set x [string first "\n\n" $text]] >= 0} {
        set text [string range $text [expr $x+2] end]
    }
    if {[set x [string first "\n-----" $text]] > 0} {
        set text [string range $text 0 [expr $x-1]]
    }

    return $text
}

proc ::ssj::armor:decode {text} {
    return "-----BEGIN PGP MESSAGE-----\n\n$text\n-----END PGP MESSAGE-----"
}

proc ::ssj::signed:input {connid from signature data what} {
    variable ctx
    variable j2k
    variable s2e
    variable warnings
    variable gpg_error_id
    variable options

    once_only $connid

    if {[catch {$ctx($connid) -operation verify \
			      -input     [binary format a* [encoding convertto utf-8 $data]]  \
			      -signature [armor:decode $signature]} result]} {
        debugmsg ssj "verify processing error ($connid): $result ($from)"

        if {![info exists warnings(verify-traffic,$connid)]} {

            set warnings(verify-traffic,$connid) 1
            after idle [list NonmodalMessageDlg .verify_error$connid -aspect 50000 -icon error \
                -message [::msgcat::mc "Error in signature verification software: %s." \
				       $result]]
        }

        set params(reason) $result

        return [array get params]
    }

    debugmsg ssj "VERIFY: $connid $from ($data); $result"

    array set params $result
    set result $params(status)

    set signatures {}
    foreach signature $params(signatures) {
        catch {unset sparams}
        array set sparams $signature

        if {[info exists sparams(key)]} {
            set sparams(key) [$ctx($connid) -operation info-key -key $sparams(key)]
            foreach {k v} $sparams(key) {
		switch -- $k {
		    keyid {
			set j2k($from) $v
			break
		    }
		    subkeys {
			foreach subkey $v {
			    catch {unset kparams}
			    array set kparams $subkey
			    if {[info exists kparams(keyid)]} {
				set j2k($from) $kparams(keyid)
				break
			    }
			}
		    }
		}
            }
        }

        lappend signatures [array get sparams]
    }
    catch {unset params}
    array set params [list signatures $signatures]

    if {![cequal $result good]} {
        if {[info exists s2e($result)]} {
            set result $s2e($result)
        }
        set params(reason) $result

        if {![info exists warnings(verify,$from)] && $options(display_sig_warnings)} {
            set warnings(verify,$from) 1
            incr gpg_error_id
            after idle [list NonmodalMessageDlg .verify_error$gpg_error_id -aspect 50000 -icon error \
                -message [::msgcat::mc "%s purportedly signed by %s can't be verified.\n\n%s." \
				       $what $from $result]]
        }
    }

    return [array get params]
}


proc ::ssj::signed:output {connid data args} {
    variable ctx
    variable options
    variable warnings
    variable gpg_error_id

    if {(!$options(sign-traffic)) || ([cequal $data ""])} {
        return
    }

    once_only $connid 1

    if {[catch {$ctx($connid) -operation sign  \
			      -input     [binary format a* [encoding convertto utf-8 $data]] \
			      -mode      detach} result]} {
        set options(sign-traffic) 0

        debugmsg ssj "signature processing error ($connid): $result ($data)"

        if {[llength $args] == 0} {
            set buttons ok
            set cancel 0
            set message [::msgcat::mc "Unable to sign presence information:\
				       %s.\n\nPresence will be sent, but\
				       signing traffic is now disabled." $result]
        } else {
            set buttons {ok cancel}
            set cancel 1
            set message [::msgcat::mc "Unable to sign message body:\
				       %s.\n\nSigning traffic is now\
				       disabled.\n\nSend it WITHOUT a signature?"\
				      $result]
        }

        incr gpg_error_id
        if {[MessageDlg .sign_error$gpg_error_id -aspect 50000 -icon error -type user \
                        -buttons $buttons -default 0 -cancel $cancel \
                        -message $message]} {
            error ""
        }           

        return
    }
    set result [armor:encode $result]

    debugmsg ssj "SIGN: $data; $result"
    whichkeys $connid sign

    return $result
}

proc ::ssj::signed:info {pinfo} {

    set text ""
    array set params $pinfo

    foreach {k v} $pinfo {
	if {![cequal $k signatures]} {
	    if {![cequal $v ""]} {
		append text [format "%s: %s\n" $k $v]
	    }
	}
    }

    foreach signature $params(signatures) {
	set info ""
	set addrs ""
	set s ""
	foreach {k v} $signature {
	    switch -- $k {
		key {
		    foreach {k v} $v {
			if {![cequal $k subkeys]} {
			    continue
			}
			foreach subkey $v {
			    catch {unset sparams}
			    array set sparams $subkey
			    if {[info exists sparams(email)]} {
				append addrs $s $sparams(email)
                                set s "\n     "
			    }
			}
		    }
		}
		created {
		    append info "created: [clock format $v]\n"
		}
		expires {
		    append info "expires: [clock format $v]\n"
		}
		fingerprint {
		    append info [format "keyid: 0x%s\n" [string range $v end-7 end]]
		    append info [format "%s: %s\n" $k $v]
		}
		default {
		    if {![cequal $v ""]} {
			append info [format "%s: %s\n" $k $v]
		    }
		}
	    }
	}

	if {![cequal $addrs ""]} {
	    set info "email: $addrs\n$info"
	}
	if {![cequal $info ""]} {
	    append text "\n" [string trimright $info]
	}
    }

    return [string trimleft $text]
}

proc ::ssj::signed:Label {lb connid jid pinfo} {
    if {[set rjid [muc::get_real_jid $connid $jid]] == ""} {
	set rjid [node_and_server_from_jid $jid]
    } else {
	set rjid [node_and_server_from_jid $rjid]
    }

    array set params $pinfo

    set checks {}
    set trust 0
    foreach signature $params(signatures) {
	set emails {}
	set valid 0
	foreach {k v} $signature {
	    switch -- $k {
		key {
		    foreach {k v} $v {
			if {![cequal $k subkeys]} {
			    continue
			}
			foreach subkey $v {
			    catch {unset sparams}
			    array set sparams $subkey
			    if {[info exists sparams(email)]} {
				lappend emails $sparams(email)
			    }
			}
		    }
		}
		validity {
		    switch -- $v {
			ultimate -
			full -
			marginal {
			    set valid 1
			}
			never -
			undefined -
			unknown -
			default {
			    set valid 0
			}
		    }
		}
	    }
	}
	if {$valid && ([lsearch -exact $emails $rjid] >= 0)} {
	    set trust 1
	    break
	}
    }

    if {[info exists params(reason)]} {
	set args [list -image gpg/badsigned]
    } elseif {$trust} {
	set args [list -image gpg/signed]
    } else {
	set args [list -image gpg/vsigned]
    }

    if {![cequal [set info [signed:info $pinfo]] ""]} {
	lappend args -helptext $info -helptype balloon
    }

    eval [list Label $lb] $args -cursor arrow \
	 -padx 0 -pady 0 -borderwidth 0 -highlightthickness 0

    if {[info exists params(reason)] && [cequal $params(reason) nokey]} {
	bind $lb <3> [list ::ssj::signed:popup $pinfo]
    }
    return $lb
}

###############################################################################

proc ::ssj::signed:popup {pinfo} {
    set m .signed_label_popupmenu
    if {[winfo exists $m]} {
	destroy $m
    }
    menu $m -tearoff 0
    $m add command -label [::msgcat::mc "Fetch GPG key"] \
	-command [list ::ssj::fetchkeys $pinfo]
    tk_popup $m [winfo pointerx .] [winfo pointery .]
}

proc ::ssj::signed:user_menu {m connid jid} {
    variable signed
    global curuser

    if {[cequal $jid "\$curuser"]} {
	set jid $curuser
    }
    if {[info exists signed($connid,$jid)]} {
	array set params $signed($connid,$jid)
	if {[info exists params(status)] && [cequal $params(status) nokey]} {
	    $m add command -label [::msgcat::mc "Fetch GPG key"] \
		-command [list ::ssj::fetchkeys \
			       $signed($connid,$jid)]
	}
    }
}

hook::add chat_create_user_menu_hook ::ssj::signed:user_menu 78

###############################################################################

proc ::ssj::fetchkeys {pinfo} {
    variable gpg_error_id

    array set params $pinfo

    set keyids {}        
    foreach signature $params(signatures) {
	catch {unset sparams}
	array set sparams $signature

	if {[info exists sparams(fingerprint)]} {
	    lappend keyids [string range $sparams(fingerprint) end-7 end]
	}
    }
    set res [catch {set output [eval [list exec gpg --recv-keys] $keyids]} errMsg]
    incr gpg_error_id
    if {$res} {
        NonmodalMessageDlg .keyfetch_ok$gpg_error_id -aspect 50000 -icon error \
            -message "Key fetch error\n\n$errMsg"
    } else {
        NonmodalMessageDlg .keyfetch_error$gpg_error_id -aspect 50000 -icon info \
            -message "Key fetch result\n\n$output"
    }
}

###############################################################################

proc ::ssj::rewrite_message_body \
     {vconnid vfrom vid vtype vis_subject vsubject vbody verr vthread vpriority vx} {
    upvar 2 $vconnid connid
    upvar 2 $vfrom from
    upvar 2 $vbody body
    upvar 2 $vx x

    set badenc 0
    set xs {}
    foreach xe $x {
	jlib::wrapper:splitxml $xe tag vars isempty cdata children

	if {![cequal [jlib::wrapper:getattr $vars xmlns] $::NS(encrypted)]} {
	    lappend xs $xe
	} elseif {[cequal $cdata ""]} {
	    # in case the sender didn't check the exit code from gpg we ignore
	    # jabber:x:encrypted
	} elseif {[catch {ssj::encrypted:input $connid $from $cdata} msg]} {
	    set body [::msgcat::mc ">>> Unable to decipher data: %s <<<" $msg]
	    # Add empty x tag to show problems with gpg
	    lappend xs [jlib::wrapper:createtag x \
			    -vars [list xmlns $::NS(encrypted)]]
	    set badenc 1
	} else {
	    set body $msg
	    lappend xs $xe
	}
    }

    set x $xs

    if {!$badenc} return

    # if decryption failed, then remove signature. It can't be correct.
    set xs {}
    foreach xe $x {
	jlib::wrapper:splitxml $xe tag vars isempty cdata children

	if {![cequal [jlib::wrapper:getattr $vars xmlns] $::NS(signed)]} {
	    lappend xs $xe
	}
    }

    set x $xs
}

hook::add rewrite_message_hook ::ssj::rewrite_message_body 10

###############################################################################

proc ::ssj::encrypted:input {connid from data} {
    variable ctx
    variable warnings
    variable gpg_error_id

    once_only $connid

    if {[catch {$ctx($connid) -operation decrypt \
			      -input     [armor:decode $data]} result]} {
        debugmsg ssj "decryption processing error ($connid): $result ($from)"

        if {![info exists warnings(decrypt,$from)]} {
            set warnings(decrypt,$from) 1
            incr gpg_error_id
            after idle [list NonmodalMessageDlg .decrypt_error$gpg_error_id -aspect 50000 -icon error \
                -message [::msgcat::mc "Data purported sent by %s can't be deciphered.\n\n%s." \
				       $from $result]]
        }

        error $result
    }

    debugmsg ssj "DECRYPT: $connid; $from; $result"

    array set params $result
    binary scan $params(plaintext) a* temp_utf8
    return [encoding convertfrom utf-8 $temp_utf8]
}


proc ::ssj::encrypted:output {connid data to} {
    global gpgPkg
    variable ctx
    variable e4me
    variable j2k
    variable options
    variable gpg_error_id

    if {[cequal $data ""]} {
        return
    }

    if {![encryptP $connid $to]} {
        return
    }

    set bto [node_and_server_from_jid $to]

    if {[info exists j2k($to)]} {
        set name $j2k($to)
    } elseif {[llength [set k [array names j2k $to/*]]] > 0} {
        set name $j2k([lindex $k 0])
    } else {
        set name $bto
    }

    set recipient [${gpgPkg}::recipient]
    $recipient -operation add   \
	       -name      $name \
               -validity  full
    foreach signer $e4me($connid) {
        $recipient -operation add \
                   -name      $signer \
                   -validity  full
    }

    once_only $connid 1

    set code \
	[catch {
	    $ctx($connid) \
		-operation encrypt \
		-input [binary format a* [encoding convertto utf-8 $data]] \
		-recipients $recipient
	 } result]

    rename $recipient {}

    if {$code} {
        debugmsg ssj "encryption processing error ($connid): $result ($data)"

        set options(encrypt,$connid,$to) 0
        incr gpg_error_id
        if {[MessageDlg .encrypt_error$gpg_error_id \
		-aspect 50000 \
		-icon error \
		-type user \
                -buttons {ok cancel} \
		-default 0 \
		-cancel 1 \
                -message [::msgcat::mc \
			      "Unable to encipher data for %s:\
			       %s.\n\nEncrypting traffic to this user is\
			       now disabled.\n\nSend it as PLAINTEXT?" \
			      $to $result]]} {
            error ""
        }

        return
    }
    set result [armor:encode $result]

    debugmsg ssj "ENCRYPT: $connid; $data; $result"

    return $result
}

proc ::ssj::whichkeys {connid what} {
    variable ctx
    variable warnings

    set s [$ctx($connid) -operation get -property last-op-info]
    if {[cequal $s ""]} {
        return
    }

    set keys {}
    while {([set x [string first <fpr> $s]] > 0) \
                && ([set y [string first </fpr> $s]] > $x) \
                && ($x+45 == $y)} {
        lappend keys [string range $s [expr $x+20] [expr $y-1]]
        set s [string range $s $y end]
    }

    if {![info exists warnings($what)]} {
        set warnings($what) ""
    } elseif {[cequal $warnings($what) $keys]} {
        return
    }

    set warnings($what) $keys
    debugmsg ssj "${what}ing with $keys"
}

#############################################################################

proc ::ssj::prefs {connid jid} {
    variable ctx
    variable options
    variable optionsX

    set w [win_id security_preferences [list $connid $jid]]

    if {[winfo exists $w]} {
        focus -force $w
        return
    }

    Dialog $w \
	   -title [::msgcat::mc "Change security preferences for %s" $jid] \
	   -separator 1 -anchor e -default 0 -cancel 1

    $w add -text [::msgcat::mc "OK"] \
	   -command [list ::ssj::prefs_ok $w $connid $jid]
    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]

    set f [$w getframe]

    if {![info exists options(encrypt,$connid,$jid)]} {
        set options(encrypt,$connid,$jid) [encryptP $connid $jid]
    }

    set optionsX(encrypt,$connid,$jid) $options(encrypt,$connid,$jid)
    checkbutton $f.encrypt \
        -text     [::msgcat::mc "Encrypt traffic"] \
        -variable ::ssj::optionsX(encrypt,$connid,$jid)

    pack $f.encrypt -side left
    pack [frame $f.f -width 9c -height 2c]

    $w draw $f.name
}

proc ::ssj::prefs_ok {w connid jid} {
    variable options
    variable optionsX

    set options(encrypt,$connid,$jid) $optionsX(encrypt,$connid,$jid)

    destroy $w
}

proc ::ssj::prefs_user_menu {m connid jid} {
    $m add command -label [::msgcat::mc "Edit security..."] \
	-command [list ::ssj::prefs $connid $jid]
}

hook::add chat_create_user_menu_hook ::ssj::prefs_user_menu 78
hook::add roster_conference_popup_menu_hook ::ssj::prefs_user_menu 78
hook::add roster_service_popup_menu_hook ::ssj::prefs_user_menu 78
hook::add roster_jid_popup_menu_hook ::ssj::prefs_user_menu 78

#############################################################################

proc ::ssj::signP {} {
    variable options

    return $options(sign-traffic)
}

proc ::ssj::encryptP {connid jid} {
    global gpgPkg
    variable ctx
    variable j2k
    variable options

    if {[cequal $jid ""]} {
	return $options(encrypt-traffic)
    }

    lassign [roster::get_category_and_subtype $connid $jid] \
            category subtype
    switch -- $category {
	conference -
	server     -
	gateway    -
	service {
	    set resP 0
	}

	default {
	    set resP 1
	}
    }

    set bjid [node_and_server_from_jid $jid]

    if {[info exists options(encrypt,$connid,$jid)]} {
        return $options(encrypt,$connid,$jid)
    } elseif {[info exists options(encrypt,$connid,$bjid)]} {
        return $options(encrypt,$connid,$bjid)
    } elseif {[info exists options(encrypt,$jid)]} {
	return $options(encrypt,$jid)
    } elseif {[info exists options(encrypt,$bjid)]} {
	return $options(encrypt,$jid)
    }

    if {!$options(encrypt-traffic)} {
        return 0
    }

    if {[info exists options(encrypt-tried,$connid,$jid)]} {
        return $options(encrypt-tried,$connid,$jid)
    }

    once_only $connid

    if {[info exists j2k($jid)]} {
        set name $j2k($jid)
    } elseif {($resP) && ([llength [set k [array names j2k $jid/*]]] > 0)} {
        set name $j2k([lindex $k 0])
    } else {
        set name $bjid
    }

    [set recipient [${gpgPkg}::recipient]] \
            -operation add   \
            -name      $name \
            -validity  full

    if {[catch {$ctx($connid) -operation  encrypt        \
			      -input      "Hello world." \
			      -recipients $recipient}]} {
        set options(encrypt-tried,$connid,$jid) 0
    } else {
        set options(encrypt-tried,$connid,$jid) 1
    }

    rename $recipient {}

    return $options(encrypt-tried,$connid,$jid)
}

#############################################################################

proc ::ssj::e4meP {connid keys} {
    global gpgPkg
    variable ctx
    variable e4me
    variable signers

    $ctx($connid) -operation set     \
		  -property  signers \
		  -value     [set signers($connid) $keys]

    set e4me($connid) {}
    foreach signer $signers($connid) {
        [set recipient [${gpgPkg}::recipient]] \
                -operation add     \
                -name      $signer \
                -validity  full

        if {![catch {$ctx($connid) -operation  encrypt        \
				   -input      "Hello world." \
				   -recipients $recipient} result]} {
            lappend e4me($connid) $signer
        }

        rename $recipient {}
    }
}

#############################################################################

proc ::ssj::sign:toggleP {} {
    variable options

    set options(sign-traffic) [expr {!$options(sign-traffic)}]
}

proc ::ssj::encrypt:toggleP {{connid ""} {jid ""}} {
    variable options

    if {[cequal $jid ""]} {
	set options(encrypt-traffic) [expr {!$options(encrypt-traffic)}]
        return
    }

    if {![cequal $connid ""]} {
	if {![info exists options(encrypt,$connid,$jid)]} {
	    set options(encrypt,$connid,$jid) [encryptP $connid $jid]
	}
	set options(encrypt,$connid,$jid) \
	    [expr {!$options(encrypt,$connid,$jid)}]
    } else {
	return -code error \
	    "::ssj::encrypt:toggleP: connid is empty and jid is not"
    }
}

#############################################################################

proc ::ssj::signed:trace {script} {
    variable options
    variable trace

    if {![info exists trace(sign-traffic)]} {
        set trace(sign-traffic) {}

        ::trace variable ::ssj::options(sign-traffic) w ::ssj::trace
    }

    lappend trace(sign-traffic) $script
}

proc ::ssj::encrypted:trace {script {connid ""} {jid ""}} {
    variable options
    variable trace

    if {[cequal $jid ""]} {
	set k encrypt-traffic
    } else {
	if {![cequal $connid ""]} {
	    set k encrypt,$connid,$jid
	} else {
	    return -code error \
		"::ssj::encrypted:trace: connid is empty and jid is not"
	}
    }
    if {![info exists trace($k)]} {
        set trace($k) {}

        ::trace variable ::ssj::options($k) w ::ssj::trace
    }

    lappend trace($k) $script
}

proc ::ssj::trace {name1 name2 op} {
    variable trace

    set new {}
    foreach script $trace($name2) {
        if {[catch {eval $script} result]} {
            debugmsg ssj "$result -- $script"
        } else {
            lappend new $script
        }
    }
    set trace($name2) $new
}

#############################################################################

proc ::ssj::clear_signatures {connid} {
    variable signed

    if {$connid == {}} {
	catch {array unset signed}
    } else {
	catch {array unset signed $connid,*}
    }
}

hook::add disconnected_hook ::ssj::clear_signatures

#############################################################################

proc ::ssj::check_signature {connid from type x args} {
    variable signed

    switch -- $type {
	unavailable -
	available {
	    catch {unset signed($connid,$from)}

	    set signature ""
	    foreach xs $x {
		jlib::wrapper:splitxml $xs tag vars isempty cdata children
		if {[jlib::wrapper:getattr $vars xmlns] == $::NS(signed)} {
		    set signature $cdata
		    break
		}
	    }

	    # in case the sender didn't check the exit code from gpg...
	    if {[cequal $signature ""]} return

	    set status ""
	    foreach {key val} $args {
		switch -- $key {
		    -status { set status $val }
		}
	    }

	    set signed($connid,$from) \
		[signed:input $connid $from $signature $status \
		     [::msgcat::mc "Presence information"]]
	}
    }
}

hook::add client_presence_hook ::ssj::check_signature

#############################################################################

proc ::ssj::make_signature {varname connid status} {
    upvar 2 $varname var
    if {![cequal $status ""] && \
	    ![catch {signed:output $connid $status} cdata] && \
	    ![cequal $cdata ""]} {
	lappend var [jlib::wrapper:createtag x \
			 -vars [list xmlns $::NS(signed)] \
			 -chdata $cdata]
    }
    return
}

hook::add presence_xlist_hook ::ssj::make_signature

#############################################################################

proc ::ssj::userinfo {tab connid jid editable} {
    variable signed

    if {$editable} return

    set bare_jid [node_and_server_from_jid $jid]
    set chatid [chat::chatid $connid $bare_jid]
    if {[chat::is_groupchat $chatid]} {
	if {[info exists signed($connid,$jid)]} {
	    set jids [list $connid,$jid]
	} else {
	    set jids [list]
	}
    } else {
	set jids [array names signed $connid,$bare_jid/*]
    }
    if {[llength $jids] > 0} {
	set presenceinfo [$tab insert end presenceinfo \
			      -text [::msgcat::mc "Presence"]]
	set i 0
	foreach j $jids {
	    regexp {[^,]*,(.*)} $j -> fjid
	    set x [userinfo::pack_frame $presenceinfo.presence_$i $fjid]
	    catch {array unset params}
	    array set params $signed($j)

	    set kv {}
	    set addrs ""
	    set s ""
	    foreach signature $params(signatures) {
		foreach {k v} $signature {
		    switch -- $k {
			key {
			    foreach {k v} $v {
				if {![cequal $k subkeys]} continue

				foreach subkey $v {
				    catch {unset sparams}
				    array set sparams $subkey
				    if {[info exists sparams(email)]} {
					append addrs $s $sparams(email)
					set s ", "
				    }
				}
			    }
			    continue
			}
			status { continue }
			created -
			expires { set v [clock format $v] }
			fingerprint {
			    lappend kv keyid \
				[format "0x%s" [string range $v end-7 end]]
			}
			default {
			    if {[cequal $v ""]} { continue }
			}	
		    }

		    lappend kv $k $v
		}
	    }


	    userinfo::pack_entry $jid $x $i presence_$i [::msgcat::mc "Reason:"]
	    if {![info exists params(reason)]} {
		set params(reason) [::msgcat::mc "Presence is signed"]
		if {![cequal $addrs ""]} {
		    append params(reason) [::msgcat::mc " by "] $addrs
		}
	    }
	    set userinfo::userinfo(presence_$i,$jid) $params(reason)
	    incr i

	    foreach {k v} $kv {
		userinfo::pack_entry $jid $x $i presence_$i \
		    [::msgcat::mc [string totitle ${k}:]]
		set userinfo::userinfo(presence_$i,$jid) $v
		incr i
	    }
	}
    }
}

hook::add userinfo_hook ::ssj::userinfo 90

#############################################################################

proc ::ssj::message_buttons {mw connid jid} {
    set bbox1 [ButtonBox $mw.bottom.buttons1 -spacing 0]

    set b [$bbox1 add \
		  -image [signed:icon] \
		  -helptype balloon \
		  -helptext [::msgcat::mc "Toggle signing"] \
		  -height 24 \
		  -width 24 \
		  -relief link \
		  -bd $::tk_borderwidth \
		  -command ::ssj::sign:toggleP]
    signed:trace "$b configure -image \[::ssj::signed:icon\]"
    
    # TODO reflect changes of connid
    set b [$bbox1 add \
		  -image [encrypted:icon $connid $jid] \
		  -helptype balloon \
		  -helptext [::msgcat::mc "Toggle encryption"] \
		  -height 24 \
		  -width 24 \
		  -relief link \
		  -bd $::tk_borderwidth \
		  -command [list ::ssj::encrypt:toggleP $connid $jid]]
    encrypted:trace \
	"$b configure -image \[::ssj::encrypted:icon [list $connid] [list $jid]\]" \
	$connid $jid

    pack $bbox1 -side left -fill x -padx 2m -pady 2m
}

hook::add open_message_post_hook ::ssj::message_buttons

#############################################################################

proc ::ssj::process_x_signed {rowvar bodyvar f x connid from id type replyP} {
    upvar 2 $rowvar row
    upvar 2 $bodyvar body

    if {!$replyP || [cequal $type error]} {
	return
    }

    foreach xa $x {
	jlib::wrapper:splitxml $xa tag vars isempty chdata children
	set xmlns [jlib::wrapper:getattr $vars xmlns]

	if {$xmlns != $::NS(signed)} continue

	# in case the sender didn't check the exit code from gpg...
	if {[cequal $chdata ""]} {
	    return
	}

	set lb [join [lrange [split $f .] 0 end-1] .].title.signed
	if {[winfo exists $lb]} {
	    destroy $lb
	}

	signed:Label $lb $connid $from \
		     [signed:input $connid $from $chdata $body \
				   [::msgcat::mc "Message body"]]
	grid $lb -row 1 -column 2 -sticky e
    }

    return
}

hook::add message_process_x_hook ::ssj::process_x_signed 20

#############################################################################

proc ::ssj::signed:icon {} {
    return [lindex [list toolbar/gpg-unsigned toolbar/gpg-signed] \
                   [signP]]
}

proc ::ssj::encrypted:icon {{connid ""} {jid ""}} {
    return [lindex [list toolbar/gpg-unencrypted toolbar/gpg-encrypted] \
                   [encryptP $connid $jid]]
}

#############################################################################

proc ::ssj::draw_signed {chatid from type body x} {
    variable signedid

    set chatw [chat::chat_win $chatid]

    foreach xe $x {
        jlib::wrapper:splitxml $xe tag vars isempty chdata children

	# in case the sender didn't check the exit code from gpg...
        if {[cequal $chdata ""] || \
		![cequal [jlib::wrapper:getattr $vars xmlns] $::NS(signed)]} {
            continue
        }

        incr signedid
	set connid [chat::get_connid $chatid]
        catch {
            $chatw window create end \
                  -window [signed:Label $chatw.signed$signedid $connid $from \
                              [signed:input $connid $from $chdata $body \
                                  [::msgcat::mc "Message body"]]]
        }
    }
}

hook::add draw_message_hook ::ssj::draw_signed 7

###############################################################################

proc ::ssj::chat_window_button {chatid type} {
    set connid [chat::get_connid $chatid]
    set jid [chat::get_jid $chatid]
    set cw [chat::winid $chatid]

    Button $cw.status.encrypted \
	   -relief flat \
           -image [encrypted:icon $connid $jid] \
           -helptype balloon \
           -helptext [::msgcat::mc "Toggle encryption"] \
           -command [list ::ssj::encrypt:toggleP $connid $jid]

    encrypted:trace "$cw.status.encrypted configure \
		-image \[::ssj::encrypted:icon $connid $jid\]" \
        $connid $jid
    pack $cw.status.encrypted -side left -before $cw.status.mb
}

hook::add open_chat_post_hook ::ssj::chat_window_button

###############################################################################

proc ::ssj::toolbar {} {
    set idx [ifacetk::add_toolbar_button \
		 [signed:icon] \
		 ::ssj::sign:toggleP \
		 [::msgcat::mc "Toggle signing"]]
    signed:trace \
	[list ifacetk::set_toolbar_icon $idx ::ssj::signed:icon]

    set idx [ifacetk::add_toolbar_button \
		 [encrypted:icon] \
		 ::ssj::encrypt:toggleP \
		 [::msgcat::mc "Toggle encryption (when possible)"]]
    encrypted:trace \
	[list ifacetk::set_toolbar_icon $idx ::ssj::encrypted:icon]
}

hook::add finload_hook ::ssj::toolbar

###############################################################################

proc ::ssj::setup_menu {} {
    variable options

    catch {
	set m [.mainframe getmenu tkabbur]
	set ind [$m index [::msgcat::mc "View"]]
	incr ind -1

	set mm .ssj_menu
	menu $mm -tearoff $::ifacetk::options(show_tearoffs)
	$mm add checkbutton -label [::msgcat::mc "Sign traffic"] \
	    -variable ::ssj::options(sign-traffic)
	$mm add checkbutton -label [::msgcat::mc "Encrypt traffic (when possible)"] \
	    -variable ::ssj::options(encrypt-traffic)

	$m insert $ind cascade -label [::msgcat::mc "Encryption"] \
	    -menu $mm
    }
}


hook::add finload_hook ::ssj::setup_menu

###############################################################################

proc ::ssj::add_user_popup_info {infovar connid jid} {
    variable signed

    upvar 0 $infovar info

    if {[info exists signed($connid,$jid)]} {
	set signed_info [signed:info $signed($connid,$jid)]
	append info [::msgcat::mc "\n\tPresence is signed:"]
	regsub -all {(\n)} "\n$signed_info" "\\1\t    " extra
	append info $extra
    }
}

hook::add roster_user_popup_info_hook ::ssj::add_user_popup_info 99

###############################################################################

