# $Id$

namespace eval otr {
    # Prepare to load the local TclOTR library

    set scriptdir [file dirname [info script]]
    set tclotr [file join $scriptdir tclotr]

    if {[file isdirectory $tclotr]} {
	lappend ::auto_path $scriptdir
	package forget otr
    }

    # Load message catalogs:

    package require msgcat
    ::msgcat::mcload [file join $scriptdir msgs]
    ::trans::load [file join $scriptdir trans]

    # Check if Tkabber is sufficiently new

    if {[hook::is_empty send_normal_message_hook]} {
	puts stderr [::msgcat::mc "Tkabber is too old for the OTR plugin"]
	# Clean up what's already here and bail out:
	namespace delete [namespace current]
	return
    }

    # Attempt to load TclOTR. Warn and quit loading plugin if we can't:

    if {[catch { package require otr } err]} {
	puts stderr $err
	puts stderr [::msgcat::mc "Problem loading TclOTR. The OTR\
				   functionality will be disabled.\nRefer\
				   to the README file of the OTR plugin."]
	# Clean up what's already here and bail out:
	namespace delete [namespace current]
	return
    }

    # Load DSA keys and authenticaton infrastructure

    source [file join $scriptdir auth.tcl]
    source [file join $scriptdir key.tcl]

    # Auxiliary namespace. Rewrite_message_hook will use it to add some
    # supplemetary info.

    set ns tkabber:otr

    # PLugin's options

    variable options

    variable themes
    set dirs \
	[glob -nocomplain -directory [file join [file dirname [info script]] \
						pixmaps] *]
    foreach dir $dirs {
	pixmaps::load_theme_name [namespace current]::themes $dir
    }
    set values {}
    foreach theme [lsort [array names themes]] {
	lappend values $theme $theme
    }

    custom::defgroup Plugins [::msgcat::mc "Plugins options."] \
	-group Tkabber

    custom::defgroup OTR [::msgcat::mc "Off-the-record messaging plugin options."] \
	-group Plugins

    custom::defvar options(theme) OTR \
	[::msgcat::mc "OTR icons theme."] -group OTR \
	-type options -values $values \
	-command [namespace current]::load_stored_theme

    custom::defvar options(allow-encryption) 1 \
	[::msgcat::mc "Allow Tkabber to use Off-the-Record protocol (version 2 or 3)."] \
	-group OTR -type boolean -command [namespace current]::update_default_policy

    custom::defvar options(require-encryption) 0 \
	[::msgcat::mc "Refuse to send unencrypted messages. Note that if this option\
		       is enabled and the previous one is not then you will not be\
		       able to send any chat or normal messages at all"] \
	-group OTR -type boolean -command [namespace current]::update_default_policy

    custom::defvar options(send-whitespace-tag) 1 \
	[::msgcat::mc "Advertise your support of OTR using the whitespace tag."] \
	-group OTR -type boolean -command [namespace current]::update_default_policy

    custom::defvar options(whitespace-or-error-start-ake) 1 \
	[::msgcat::mc "Start the OTR authenticated key exchange when you\
		       receive a whitespace tag or an OTR error message."] \
	-group OTR -type boolean -command [namespace current]::update_default_policy

    custom::defvar options(heartbeat-interval) 60 \
	[::msgcat::mc "If the interval between two consecutive incoming OTR messages\
		       exceeds the specified interval (in minutes, zero means no\
		       heartbeat at all) the heartbeat message will be sent to\
		       change the session keys."] \
	-group OTR -type integer -command [namespace current]::update_heartbeat

    custom::defvar options(dont-log-otr-messages) 0 \
	[::msgcat::mc "Do not log OTR messages."] \
	-group OTR -type boolean

    # Hidden option for storing contacts' personal preferences
    # {myjid1 {jid1 {allow-encryption 0 ...} jid2 {...}} myjid2 {jid1 {...} jid2 {...}}}

    custom::defvar options(personal-preferences) {} \
	[::msgcat::mc "Presonal OTR preferences."] \
	-type string -group Hidden

    # Correspondence between GUI options and OTR policy flags

    array set PolicyFlags {allow-encryption {ALLOW_V2 ALLOW_V3}
			   require-encryption {REQUIRE_ENCRYPTION}
			   send-whitespace-tag {SEND_WHITESPACE_TAG}
			   whitespace-or-error-start-ake {WHITESPACE_START_AKE ERROR_START_AKE}} 
}

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

proc otr::load_stored_theme {args} {
    variable options
    variable themes

    pixmaps::load_dir $themes($options(theme))
}

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

proc otr::draw_encrypted {chatid from type body x} {
    variable ns
    variable ctx

    if {$type ne "chat"} return

    set xlib [chat::get_xlib $chatid]
    set jid [chat::get_jid $chatid]

    if {![::xmpp::jid::equal $jid $from]} return

    foreach xe $x {
	::xmpp::xml::split $xe tag xmlns attrs cdata subels

	if {$tag eq "" && $xmlns eq $ns} {
	    set chatw [chat::chat_win $chatid]
	    $chatw image create end -image [::xmpp::xml::getAttr $attrs icon]
	    break
	}
    }
}

hook::add draw_message_hook [namespace current]::otr::draw_encrypted 6

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

proc otr::process_x_encrypted {rowvar bodyvar f x xlib from id type replyP} {
    variable ns
    variable ctx
    upvar 2 $rowvar row
    upvar 2 $bodyvar body

    if {$type eq "error" || $type eq "groupchat" || $type eq "headline"} return

    if {$body eq ""} return

    foreach xe $x {
	::xmpp::xml::split $xe tag xmlns attrs cdata subels

	if {$tag eq "" && $xmlns eq $ns} {
	    set lb [join [lrange [split $f .] 0 end-1] .].title.otrmsgstate
	    if {[winfo exists $lb]} {
		destroy $lb
	    }
	    Label $lb -image [::xmpp::xml::getAttr $attrs icon]
	    grid $lb -row 1 -column 4 -sticky e
	    break
	}
    }
}

hook::add message_process_x_hook [namespace current]::otr::process_x_encrypted 21

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

proc otr::once_only {xlib jid} {
    variable options
    variable ctx

    if {[info exists ctx($xlib,$jid)]} {
	return 1
    }

    debugmsg otr "ONCE_ONLY $xlib $jid"

    set myjid [::xmpp::jid::removeResource [connection_jid $xlib]]
    set privkey [key::get_key $key::Keys $myjid]
    if {[llength $privkey] == 0} {
	return 0
    }

    set ctx(msgstate,$xlib,$jid) MSGSTATE_PLAINTEXT
    msgstate:trace {} $xlib $jid

    set ctx($xlib,$jid) \
	[::otr::new $privkey \
		-policy [get_policy $xlib $jid] \
		-heartbeat $options(heartbeat-interval) \
		-authstatecommand [namespace code [list on_authstate_change $xlib $jid]] \
		-msgstatecommand [namespace code [list on_msgstate_change $xlib $jid]] \
		-smpstatecommand [namespace code [list on_smpstate_change $xlib $jid]] \
		-smpprogresscommand [namespace code [list progress_smp $xlib $jid]] \
		-infocommand [namespace code [list show_info $xlib $jid]] \
		-errorcommand [namespace code [list show_error $xlib $jid]] \
		-sendcommand [namespace code [list send $xlib $jid]]]
    return 1
}

proc otr::clear {xlib jid} {
    variable ctx

    if {[info exists ctx($xlib,$jid)]} {
	finish_session $xlib $jid ""
	::otr::free $ctx($xlib,$jid)
	unset ctx(authstate,$xlib,$jid)
	unset ctx(msgstate,$xlib,$jid)
	unset ctx(smpstate,$xlib,$jid)
	unset ctx($xlib,$jid)

	set chatid [chat::chatid $xlib $jid]
	set chatw [chat::chat_win $chatid]
	if {![winfo exists $chatw]} return

	if {[key::get_key $key::Keys \
		[::xmpp::jid::removeResource [connection_jid $xlib]]] eq ""} {
	    chat::add_message $chatid $jid info \
		      "[::msgcat::mc {OTR Info:}]\
		       [::msgcat::mc {OTR is disabled because you have deleted your\
				      OTR key. Generate or import a new one if you\
				      want to enable it again.}]" {}
	} else {
	    chat::add_message $chatid $jid info \
		      "[::msgcat::mc {OTR Info:}]\
		       [::msgcat::mc {OTR conversation is lost because you have\
				      changed your OTR key. The next private chat\
				      will use the new key.}]" {}
	}
    }
}

proc otr::clear_all {xlib} {
    variable ctx

    foreach idx [array names ctx $xlib,*] {
	regexp {[^,]*,(.*)} $idx -> jid
	clear $xlib $jid
    }
}

proc otr::clear_all_jid {myjid} {
    foreach xlib [connections] {
	if {[::xmpp::jid::equal $myjid \
		[::xmpp::jid::removeResource [connection_jid $xlib]]]} {
	    clear_all $xlib
	}
    }
}

proc otr::reset_auth {myjid} {
    variable ctx

    foreach xlib [connections] {
	if {![::xmpp::jid::equal $myjid \
		[::xmpp::jid::removeResource [connection_jid $xlib]]]} continue

	foreach idx [array names ctx msgstate,$xlib,*] {
	    regexp {msgstate,[^,]*,(.*)} $idx -> fjid
	    on_msgstate_change $xlib $fjid $ctx($idx)
	}
    }

}

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

proc otr::on_authstate_change {xlib jid authstate} {
    variable ctx

    set ctx(authstate,$xlib,$jid) $authstate
}

proc otr::on_msgstate_change {xlib jid msgstate} {
    variable ctx

    set ctx(msgstate,$xlib,$jid) $msgstate
}

proc otr::on_smpstate_change {xlib jid smpstate} {
    variable ctx

    set ctx(smpstate,$xlib,$jid) $smpstate
}

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

proc otr::show_info {xlib jid info} {
    set chatid [::chat::chatid $xlib $jid]
    chat::add_message $chatid $jid info \
		      "[::msgcat::mc {OTR Info:}] [::msgcat::mc $info]" {}
}

proc otr::show_error {xlib jid error} {
    set chatid [::chat::chatid $xlib $jid]
    chat::add_message $chatid $jid error \
		      "[::msgcat::mc {OTR Error:}] $error" {}
}

proc otr::send {xlib jid message} {
    upvar 2 type type

    debugmsg otr "SEND: $xlib; $jid; $message;"

    # Dirty hack to recover message type
    for {set i 1} {$i < 12} {incr i} {
	if {[catch {upvar $i type type}]} break
	if {[info exists type] && $type eq "chat"} break
    }

    # Another dirty hack to translate OTR error messages
    if {![catch {::otr::data::findErrorMessage $message} error]} {
	set lang [get_jid_presence_info lang $xlib $jid]

	set message [::otr::data::errorMessage [::trans::trans $lang $error]]
    }

    if {[info exists type] && $type eq "chat"} {
	set targs [list -type $type]
    } else {
	set targs {}
    }
    ::xmpp::sendMessage $xlib $jid -body $message {*}$targs
}

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

proc otr::update_heartbeat {args} {
    variable options
    variable ctx

    foreach idx [array names ctx ::*] {
	::otr::configure $ctx($idx) -heartbeat $options(heartbeat-interval)
    }
}

proc otr::get_policy {xlib jid} {
    variable options
    variable PolicyFlags

    set njid [::xmpp::jid::normalize $jid]

    set policy {}
    foreach key {allow-encryption require-encryption
		 send-whitespace-tag whitespace-or-error-start-ake} {
	if {[info exists options($key,$xlib,$njid)]} {
	    set flag $options($key,$xlib,$njid)
	} else {
	    set flag $options($key)
	}
	if {$flag} {
	    lappend policy {*}$PolicyFlags($key)
	}
    }
    debugmsg otr "GET_POLICY $xlib $jid $policy"
    set policy
}

proc otr::update_default_policy {args} {
    update_policy
}

proc otr::update_policy {{xlib ""} {jid ""}} {
    variable ctx

    if {$xlib eq ""} {
	set xlibs [connections]
    } else {
	set xlibs [list $xlib]
    }
    foreach xl $xlibs {
	if {$jid eq ""} {
	    set jids {}
	    foreach idx [array names ctx $xl,*] {
		regexp "$xl,(.*)" $idx -> j
		lappend jids $j
	    }
	} else {
	    if {[info exists ctx($xl,$jid)]} {
		set jids [list $jid]
	    } else {
		set jids {}
	    }
	}
	foreach j $jids {
	    ::otr::configure $ctx($xl,$j) -policy [get_policy $xl $j]
	}
    }
}

proc otr::reset_policy {xlib jid} {
    variable options

    set njid [::xmpp::jid::normalize $jid]

    foreach key {allow-encryption require-encryption
		 send-whitespace-tag whitespace-or-error-start-ake} {
	if {[info exists options($key,$xlib,$njid)]} {
	    unset options($key,$xlib,$njid)
	}
    }

    update_policy $xlib $jid
}

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

proc otr::get_personal_prefs {xlib} {
    variable options

    set connjid [::xmpp::jid::normalize [connection_bare_jid $xlib]]
    array set p $options(personal-preferences)

    if {[info exists p($connjid)]} {
	foreach {jid prefs} $p($connjid) {
	    foreach {key val} $prefs {
		set options($key,$xlib,$jid) $val
	    }
	}
    }
}

hook::add connected_hook [namespace current]::otr::get_personal_prefs 1

proc otr::set_personal_prefs {xlib} {
    variable options

    set connjid [::xmpp::jid::normalize [connection_bare_jid $xlib]]
    array set p $options(personal-preferences)

    set p($connjid) {}
    set jids {}
    foreach idx [array names options *,$xlib,*] {
	lappend jids [join [lrange [split $idx ,] 2 end] ,]
    }
    set jids [lsort -unique $jids]

    foreach j $jids {
	set prefs {}
	foreach key {allow-encryption require-encryption send-whitespace-tag
		     whitespace-or-error-start-ake dont-log-otr-messages} {
	    if {[info exists options($key,$xlib,$j)] && \
		    $options($key,$xlib,$j) != $options($key)} {
		lappend prefs $key $options($key,$xlib,$j)
	    }
	}
	if {[llength $prefs] > 0} {
	    lappend p($connjid) $j $prefs
	}
    }

    if {[llength $p($connjid)] == 0} {
	unset p($connjid)
    }

    set options(personal-preferences) [array get p]
}

hook::add disconnected_hook [namespace current]::otr::set_personal_prefs 40

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

proc otr::request_session {xlib jid type} {
    variable ctx

    once_only $xlib $jid

    set lang [get_jid_presence_info lang $xlib $jid]

    ::otr::requestConversation $ctx($xlib,$jid) \
	  \n[format [::trans::trans $lang \
		    "%s has requested an Off-the-Record private conversation.\
		    However, you do not have a plugin to support that.\nSee\
		    http://otr.cypherpunks.ca/ for more information."] \
		    [my_jid $xlib $jid]]]
}

proc otr::finish_session {xlib jid type} {
    variable ctx

    once_only $xlib $jid

    ::otr::finishConversation $ctx($xlib,$jid)
}

proc otr::toggle_session {xlib jid type} {
    if {[msgstate:index $xlib $jid] == 0} {
	request_session $xlib $jid $type
    } else {
	finish_session $xlib $jid $type
    }
}

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

proc otr::peer_jid {xlib jid} {
    set bjid [::xmpp::jid::removeResource $jid]
    if {![chat::is_groupchat [chat::chatid $xlib $bjid]]} {
	return $bjid
    } else {
	return $jid
    }
}

proc otr::begin_smp_dialog {xlib jid} {
    variable ctx

    set w .otrstartauth[jid_to_tag $jid]

    Dialog $w -title [::msgcat::mc "Authenticate %s" [peer_jid $xlib $jid]] \
	      -separator 1 -anchor e -default 0 -cancel 1

    wm resizable $w 0 0

    $w add -text [::msgcat::mc "Authenticate"] \
	   -command [namespace code [list begin_smp $w $xlib $jid]]
    $w add -text [::msgcat::mc "Cancel"] \
	   -command [list destroy $w]

    bind $w <Destroy> [namespace code [clear_dialog [double% $xlib] [double% $jid]]]
    bind $w <<Copy>> [namespace code [add_to_clipboard [double% $xlib] [double% $jid]]]

    set f [$w getframe]

    set m [message $f.header -text [::msgcat::mc "Authenticate %s\n\nAuthenticating\
					   a peer helps ensure that\
					   the person you are talking to is who he\
					   or she claims to be.\n\nHow would you\
					   like to authenticate your peer?" \
					   $jid] \
			     -width 12c]
    grid $m -row 1 -column 0 -sticky nswe
    tk_optionMenu $f.choice [namespace current]::ctx(authmethod,$xlib,$jid) \
			 [::msgcat::mc "Question and answer"] \
			 [::msgcat::mc "Shared secret"] \
			 [::msgcat::mc "Manual fingerprint verification"]
    trace add variable [namespace current]::ctx(authmethod,$xlib,$jid) \
	  write [namespace code [list smp_dialog_switch_page $w $xlib $jid]]
	  
    grid $f.choice -row 2 -column 0 -sticky nswe

    set pm [PagesManager $f.method]
    grid $pm -row 3 -column 0 -sticky nswe

    $pm add qa
    set pf1 [$pm getframe qa]
    set m1 [message $pf1.instr -text [::msgcat::mc "To authenticate using a question,\
					pick a question whose answer is known only to\
					you and your peer. Enter this question and\
					this answer, then wait for your peer to enter\
					the answer too. If the answers don't match\
					then you may be talking to an imposter."] \
			      -width 12c]
    grid $m1 -row 0 -column 0 -sticky nswe
    set l1 [label $pf1.l1 -text [::msgcat::mc "Enter question here:"]]
    grid $l1 -row 1 -column 0 -sticky nsw
    set e1 [entry $pf1.e1]
    grid $e1 -row 2 -column 0 -sticky nswe
    set l2 [label $pf1.l2 -text [::msgcat::mc "Enter secret answer here (case sensitive):"]]
    grid $l2 -row 3 -column 0 -sticky nsw
    set e2 [entry $pf1.e2]
    grid $e2 -row 4 -column 0 -sticky nswe
    ProgressBar $pf1.pb -variable [namespace current]::ctx(progress,$xlib,$jid) -maximum 3
    grid $pf1.pb -row 5 -column 0 -sticky ew
    set l4 [label $pf1.l4 -textvariable [namespace current]::ctx(status,$xlib,$jid)]
    grid $l4 -row 6 -column 0

    $pm add ss
    set pf2 [$pm getframe ss]
    set m2 [message $pf2.instr -text [::msgcat::mc "To authenticate, pick a secret known\
					only to you and your peer. Enter this secret,\
					then wait for your peer to enter\
					it too. If the secrets don't match\
					then you may be talking to an imposter."] \
			      -width 12c]
    grid $m2 -row 0 -column 0 -sticky nswe
    set l3 [label $pf2.l3 -text [::msgcat::mc "Enter secret here (case sensitive):"]]
    grid $l3 -row 1 -column 0 -sticky nsw
    set e3 [entry $pf2.e3]
    grid $e3 -row 2 -column 0 -sticky nswe
    ProgressBar $pf2.pb -variable [namespace current]::ctx(progress,$xlib,$jid) -maximum 3
    grid $pf2.pb -row 3 -column 0 -sticky ew
    set l4 [label $pf2.l4 -textvariable [namespace current]::ctx(status,$xlib,$jid)]
    grid $l4 -row 4 -column 0

    set ctx(progress,$xlib,$jid) 0
    set ctx(status,$xlib,$jid) ""
    set ctx(smpdialog,$xlib,$jid) $w

    $pm add mfv
    set pf3 [$pm getframe mfv]
    set m3 [message $pf3.instr -text [::msgcat::mc "To verify the fingerprint, contact\
					your peer via some other authenticated channel,\
					such as the phone or GPG-signed email. Each of\
					you should tell your fingerprint to the other.\
					if everything matches up, you should indicate in\
					this dialog that you have verified the\
					fingerprint."] \
			      -width 12c]
    grid $m3 -row 0 -column 0 -columnspan 2 -sticky nswe
    set m4 [message $pf3.mefp -text [::msgcat::mc "Fingerprint for you, %s:\n%s" \
					    [connection_jid $xlib] \
					    [::otr::fingerprint $ctx($xlib,$jid) 1]] \
			      -width 12c]
    grid $m4 -row 1 -column 0 -columnspan 2 -sticky nswe
    set m5 [message $pf3.hefp -text [::msgcat::mc "Purported fingerprint for %s:\n%s" \
					    $jid \
					    [::otr::fingerprint $ctx($xlib,$jid) 0]] \
			      -width 12c]
    grid $m5 -row 2 -column 0 -columnspan 2 -sticky nswe
    set myjid [::xmpp::jid::removeResource [connection_jid $xlib]]
    set fingerprint [::otr::fingerprint $ctx($xlib,$jid)]
    set tjid [peer_jid $xlib $jid]
    if {![auth::get_auth $auth::Auth $myjid $tjid $fingerprint]} {
	set ctx(ihave,$xlib,$jid) [::msgcat::mc "I have not verified"]
    } else {
	set ctx(ihave,$xlib,$jid) [::msgcat::mc "I have verified"]
    }
    tk_optionMenu $pf3.ihave [namespace current]::ctx(ihave,$xlib,$jid) \
			 [::msgcat::mc "I have not verified"] \
			 [::msgcat::mc "I have verified"]
    grid $pf3.ihave -row 3 -column 0 -sticky nwe
    set m6 [message $pf3.ver -text [::msgcat::mc "that this is in fact the\
						  correct fingerprint for %s" \
						  $jid] \
			     -width 8c]
    grid $pf3.ver -row 3 -column 1 -sticky nw
    grid columnconfigure $pf3 0 -minsize 4c

    $pm compute_size
    $pm raise qa

    $w draw
}

proc otr::clear_dialog {xlib jid} {
    variable ctx

    unset -nocomplain ctx(status,$xlib,$jid)
    unset -nocomplain ctx(progress,$xlib,$jid)
    unset -nocomplain ctx(authmethod,$xlib,$jid)
    unset -nocomplain ctx(ihave,$xlib,$jid)
    unset -nocomplain ctx(smpdialog,$xlib,$jid)
}

proc otr::add_to_clipboard {xlib jid} {
    variable ctx

    clipboard clear
    if {![once_only $xlib $jid]} return

    clipboard append [::otr::fingerprint $ctx($xlib,$jid) 1]
    clipboard append \n
    clipboard append [::otr::fingerprint $ctx($xlib,$jid) 0]
}

proc otr::smp_dialog_switch_page {w xlib jid name1 name2 op} {
    variable ctx

    set f [$w getframe]
    set pm $f.method

    if {$ctx(authmethod,$xlib,$jid) eq [::msgcat::mc "Question and answer"]} {
	$pm raise qa
    } elseif {$ctx(authmethod,$xlib,$jid) eq [::msgcat::mc "Shared secret"]} {
	$pm raise ss
    } else {
	$pm raise mfv
    }
}

proc otr::begin_smp {w xlib jid} {
    variable ctx

    if {$ctx(msgstate,$xlib,$jid) ne "MSGSTATE_ENCRYPTED"} {
	MessageDlg .beginsmperr -aspect 50000 -icon error \
	    -message [::msgcat::mc "You cannot verify the peer's identity using\
				    SMP\nif an OTR session is not in progress"] \
	    -type user -buttons ok -default 0 -cancel 0
	return
    }

    if {$ctx(smpstate,$xlib,$jid) ne "SMPSTATE_EXPECT1"} {
	MessageDlg .beginsmperr -aspect 50000 -icon error \
	    -message [::msgcat::mc "SMP verification is in progress. If you want to\
				    start new one, abort the current one first"] \
	    -type user -buttons ok -default 0 -cancel 0
	return
    }

    $w itemconfigure 0 -state disabled
    $w itemconfigure 1 -command [namespace code [list abort_smp $w $xlib $jid]]

    set f [$w getframe]
    set pm $f.method

    $f.choice configure -state disabled

    set page [$pm raise]

    set pf [$pm getframe $page]
    switch -- $page {
	qa  {
	    $pf.e1 configure -state readonly
	    $pf.e2 configure -state readonly
	    set secret [$pf.e2 get]
	    set question [list -question [$pf.e1 get]]
	}
	ss  {
	    $pf.e3 configure -state readonly
	    set secret [$pf.e3 get]
	    set question {}
	}
	mfv {
	    if {$ctx(ihave,$xlib,$jid) eq [::msgcat::mc "I have verified"]} {
		set val 1
	    } else {
		set val 0
	    }
	    set myjid [::xmpp::jid::removeResource [connection_jid $xlib]]
	    set fingerprint [::otr::fingerprint $ctx($xlib,$jid)]
	    set tjid [peer_jid $xlib $jid]
	    auth::set_auth auth::Auth $myjid $tjid $fingerprint $val
	    auth::store $auth::Auth
	    foreach idx [array names ctx msgstate,$xlib,*] {
		regexp {msgstate,[^,]*,(.*)} $idx -> fjid
		if {[peer_jid $xlib $fjid] eq $tjid} {
		    on_msgstate_change $xlib $fjid $ctx($idx)
		}
	    }
	    destroy $w
	    return
	}
    }

    ::otr::startSMP $ctx($xlib,$jid) $secret {*}$question
}

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

proc otr::reply_smp_dialog_qa {xlib jid question} {
    variable ctx

    set w .otrreplauth[jid_to_tag $jid]

    Dialog $w -title [::msgcat::mc "Authentication from %s" [peer_jid $xlib $jid]] \
	      -separator 1 -anchor e -default 0 -cancel 1

    wm resizable $w 0 0

    $w add -text [::msgcat::mc "Authenticate"] \
	   -command [namespace code [list reply_smp $w $xlib $jid]]
    $w add -text [::msgcat::mc "Cancel"] \
	   -command [list destroy $w]

    bind $w <Destroy> [namespace code [clear_dialog [double% $xlib] [double% $jid]]]

    set f [$w getframe]

    set m [message $f.header -text [::msgcat::mc "Authenticate %s\n\nAuthenticating\
					   a peer helps ensure that\
					   the person you are talking to is who he\
					   or she claims to be." \
					   $jid] \
			     -width 12c]
    grid $m -row 1 -column 0 -sticky nswe

    set m1 [message $f.instr -text [::msgcat::mc "Your peer is attempting to determine\
					if he or she is really talking to you, or if\
					it's someone pretending to be you. Your peer\
					has asked a question, indicated below. To\
					authenticate to your peer, enter the answer\
					and click 'Authenticate'."] \
			      -width 12c]
    grid $m1 -row 2 -column 0 -sticky nswe
    set l1 [label $f.l1 -text [::msgcat::mc "This is the question asked by your peer:"]]
    grid $l1 -row 3 -column 0 -sticky nsw
    set m2 [message $f.m2 -text $question -width 12c]
    grid $m2 -row 4 -column 0 -sticky nsw
    set l2 [label $f.l2 -text [::msgcat::mc "Enter secret answer here (case sensitive):"]]
    grid $l2 -row 5 -column 0 -sticky nsw
    set e1 [entry $f.e1]
    grid $e1 -row 6 -column 0 -sticky nswe
    ProgressBar $f.pb -variable [namespace current]::ctx(progress,$xlib,$jid) -maximum 2
    grid $f.pb -row 7 -column 0 -sticky ew
    set l4 [label $f.l4 -textvariable [namespace current]::ctx(status,$xlib,$jid)]
    grid $l4 -row 8 -column 0

    set ctx(progress,$xlib,$jid) 0
    set ctx(status,$xlib,$jid) ""
    set ctx(smpdialog,$xlib,$jid) $w

    $w draw
}

proc otr::reply_smp_dialog_ss {xlib jid} {
    variable ctx

    set w .otrreplauth[jid_to_tag $jid]

    Dialog $w -title [::msgcat::mc "Authentication from %s" [peer_jid $xlib $jid]] \
	      -separator 1 -anchor e -default 0 -cancel 1

    wm resizable $w 0 0

    $w add -text [::msgcat::mc "Authenticate"] \
	   -command [namespace code [list reply_smp $w $xlib $jid]]
    $w add -text [::msgcat::mc "Cancel"] \
	   -command [list destroy $w]

    bind $w <Destroy> [namespace code [clear_dialog [double% $xlib] [double% $jid]]]

    set f [$w getframe]

    set m [message $f.header -text [::msgcat::mc "Authenticate %s\n\nAuthenticating\
					   a peer helps ensure that\
					   the person you are talking to is who he\
					   or she claims to be." \
					   $jid] \
			     -width 12c]
    grid $m -row 1 -column 0 -sticky nswe

    set m2 [message $f.instr -text [::msgcat::mc "Your peer is attempting to determine\
					if he or she is really talking to you, or if\
					it's someone pretending to be you. Your peer\
					has picked a secret known only to him or her\
					and you. To authenticate to your peer, enter\
					this secret and click 'Authenticate'."] \
			      -width 12c]
    grid $m2 -row 2 -column 0 -sticky nswe
    set l3 [label $f.l3 -text [::msgcat::mc "Enter secret here (case sensitive):"]]
    grid $l3 -row 3 -column 0 -sticky nsw
    set e1 [entry $f.e1]
    grid $e1 -row 4 -column 0 -sticky nswe
    ProgressBar $f.pb -variable [namespace current]::ctx(progress,$xlib,$jid) -maximum 2
    grid $f.pb -row 5 -column 0 -sticky ew
    set l4 [label $f.l4 -textvariable [namespace current]::ctx(status,$xlib,$jid)]
    grid $l4 -row 6 -column 0

    set ctx(status,$xlib,$jid) ""
    set ctx(progress,$xlib,$jid) 0
    set ctx(smpdialog,$xlib,$jid) $w

    $w draw
}

proc otr::reply_smp {w xlib jid} {
    variable ctx

    if {$ctx(msgstate,$xlib,$jid) ne "MSGSTATE_ENCRYPTED"} {
	MessageDlg .beginsmperr -aspect 50000 -icon error \
	    -message [::msgcat::mc "You cannot verify the peer's identity using\
				    SMP\nif an OTR session is not in progress"] \
	    -type user -buttons ok -default 0 -cancel 0
	return
    }

    if {$ctx(smpstate,$xlib,$jid) ne "SMPSTATE_EXPECT1"} {
	MessageDlg .beginsmperr -aspect 50000 -icon error \
	    -message [::msgcat::mc "SMP verification is in progress. If you want to\
				    start new one, abort the current one first"] \
	    -type user -buttons ok -default 0 -cancel 0
	return
    }

    $w itemconfigure 0 -state disabled
    $w itemconfigure 1 -command [namespace code [list abort_smp $w $xlib $jid]]

    set f [$w getframe]
    $f.e1 configure -state readonly
    set secret [$f.e1 get]

    ::otr::replySMP $ctx($xlib,$jid) $secret
}

proc otr::abort_smp {w xlib jid} {
    variable ctx

    ::otr::abortSMP $ctx($xlib,$jid)

    destroy $w
}

proc otr::result_smp {w xlib jid} {
    if {![winfo exists $w]} return

    $w itemconfigure 0 -state normal -text [::msgcat::mc "OK"] -command [list destroy $w]
    $w itemconfigure 1 -state disabled -command [list destroy $w]
}

proc otr::progress_smp {xlib jid progress} {
    variable ctx

    if {![info exists ctx(smpdialog,$xlib,$jid)]} return

    set w $ctx(smpdialog,$xlib,$jid)

    switch -- $progress {
	SMP_PROGRESS {
	    incr ctx(progress,$xlib,$jid)
	}
	SMP_ABORT {
	    set ctx(progress,$xlib,$jid) 3
	    set ctx(status,$xlib,$jid) [::msgcat::mc "Authentication aborted"]
	    result_smp $w $xlib $jid
	}
	SMP_CHEATING {
	    set ctx(progress,$xlib,$jid) 3
	    set ctx(status,$xlib,$jid) [::msgcat::mc "Protocol error"]
	    result_smp $w $xlib $jid
	}
	SMP_SUCCESS {
	    set ctx(progress,$xlib,$jid) 3
	    set ctx(status,$xlib,$jid) [::msgcat::mc "Authentication succeeded"]
	    result_smp $w $xlib $jid

	    set myjid [::xmpp::jid::removeResource [connection_jid $xlib]]
	    set fingerprint [::otr::fingerprint $ctx($xlib,$jid)]
	    set tjid [peer_jid $xlib $jid]
	    auth::set_auth auth::Auth $myjid $tjid $fingerprint 1
	    auth::store $auth::Auth
	    foreach idx [array names ctx msgstate,$xlib,*] {
		regexp {msgstate,[^,]*,(.*)} $idx -> fjid
		if {[peer_jid $xlib $fjid] eq $tjid} {
		    on_msgstate_change $xlib $fjid $ctx($idx)
		}
	    }
	}
	SMP_FAILURE {
	    set ctx(progress,$xlib,$jid) 3
	    set ctx(status,$xlib,$jid) [::msgcat::mc "Authentication failed"]
	    result_smp $w $xlib $jid

	    set myjid [::xmpp::jid::removeResource [connection_jid $xlib]]
	    set fingerprint [::otr::fingerprint $ctx($xlib,$jid)]
	    set tjid [peer_jid $xlib $jid]
	    auth::set_auth auth::Auth $myjid $tjid $fingerprint 0
	    auth::store $auth::Auth
	    foreach idx [array names ctx msgstate,$xlib,*] {
		regexp {msgstate,[^,]*,(.*)} $idx -> fjid
		if {[peer_jid $xlib $fjid] eq $tjid} {
		    on_msgstate_change $xlib $fjid $ctx($idx)
		}
	    }
	}
    }
}

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

proc otr::rewrite_message_body \
     {vxlib vfrom vid vtype vis_subject vsubject vbody verr vthread vpriority vx} {
    upvar 2 $vxlib xlib
    upvar 2 $vfrom from
    upvar 2 $vtype type
    upvar 2 $vbody body
    upvar 2 $verr err
    upvar 2 $vx x
    variable ns
    variable ctx

    # Work only in chats and normal messages
    if {$type ne "" && $type ne "normal" && $type ne "chat"} return

    # We can't distinguish between bodyless message and message with empty
    # body, so be it
    if {$body eq ""} return

    if {![once_only $xlib $from]} return

    set result [::otr::incomingMessage $ctx($xlib,$from) $body] 

    debugmsg otr "INCOMING MESSAGE: $xlib; $from; $result;"

    array set res $result

    if {[info exists res(message)]} {
	set body $res(message)
	if {[info exists res(warn)]} {
	    lappend x [::xmpp::xml::create "" \
				-xmlns $ns \
				-attrs {icon otr/notprivate}]
	} elseif {[msgstate:index $xlib $from] != 0} {
	    lappend x [::xmpp::xml::create "" \
				-xmlns $ns \
				-attrs [list icon [msgstate:icon $xlib $from]]]
	}
    } else {
	lappend x [::xmpp::xml::create "" -xmlns $ns -attrs {discard 1}]
    }

    if {[info exists res(question)]} {
	after idle [namespace code [list reply_smp_dialog_qa \
					 $xlib $from $res(question)]]
    }

    if {[info exists res(secret)]} {
	after idle [namespace code [list reply_smp_dialog_ss \
					 $xlib $from]]
    }
    return
}

hook::add rewrite_message_hook [namespace current]::otr::rewrite_message_body 80

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

proc otr::discard_message {xlib from id type is_subject \
			   subject body err thread priority x} {
    variable ns

    foreach xel $x {
	::xmpp::xml::split $xel tag xmlns attrs cdata subels

	if {$tag eq "" && $xmlns eq $ns && \
		[::xmpp::xml::getAttr $attrs discard] == 1} {
	    return -code break
	}
    }
    return
}

hook::add process_message_hook [namespace current]::otr::discard_message 0.1

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

proc otr::rewrite_outgoing_message_body \
     {vxlib vto vid vtype vsubject vbody verr vthread vx} {
    upvar 2 $vxlib xlib
    upvar 2 $vto to
    upvar 2 $vtype type
    upvar 2 $vbody body
    upvar 2 $vx x
    variable ctx

    # Only chat and normal messages are subject to OTR
    if {[info exists type] &&
	$type ne "" && $type ne "normal" && $type ne "chat"} return

    # Only messages with body are subject to OTR
    if {![info exists body]} return

    if {![once_only $xlib $to]} return

    # Only the message body is encrypted if appropriate
    set result [::otr::outgoingMessage $ctx($xlib,$to) $body]

    debugmsg otr "OUTGOING MESSAGE: $xlib; $to; $result;"

    array set res $result

    if {[info exists res(message)]} {
	set body $res(message)
	if {$type eq "chat" && [msgstate:index $xlib $to] > 0} {
	    set chatid [chat::chatid $xlib $to]
	    set chatw [chat::chat_win $chatid]
	    if {[winfo exists $chatw]} {
		$chatw image create end -image [msgstate:icon $xlib $to]
	    }
	}
    } else {
	# Disable sending
	hook::unset_flag rewrite_outgoing_message_hook send

	# Don't show this message in the chat log window
	# This flag makes sense only for chat messages, but causes no harm for
	# normal ones
	hook::unset_flag chat_send_message_hook draw
    }
    return
}

hook::add rewrite_outgoing_message_hook \
	  [namespace current]::otr::rewrite_outgoing_message_body 10

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

proc otr::disable_log {chatid from type body x} {
    set xlib [chat::get_xlib $chatid]
    set jid [chat::get_jid $chatid]

    if {[log_is_disabled $xlib $jid]} {
	hook::unset_flag draw_message_hook log
    }
    return
}

hook::add draw_message_hook [namespace current]::otr::disable_log 10

proc otr::disable_archive_in {xlib from id type is_subject subject body err thread priority x} {
    if {[log_is_disabled $xlib $from]} {
	hook::unset_flag process_message_hook log
    }
    return
}

hook::add process_message_hook [namespace current]::otr::disable_archive_in 10

proc otr::disable_archive_out {xlib to subject body thread} {
    if {[log_is_disabled $xlib $to]} {
	hook::unset_flag send_normal_message_hook log
    }
    return
}

hook::add send_normal_message_hook [namespace current]::otr::disable_archive_out 10

proc otr::log_is_disabled {xlib jid} {
    variable options
    variable ctx

    if {![info exists ctx($xlib,$jid)]} {
	return 0
    }

    set njid [::xmpp::jid::normalize $jid]

    switch -- $ctx(msgstate,$xlib,$jid) {
	MSGSTATE_ENCRYPTED {
	    if {[info exists options(dont-log-otr-messages,$xlib,$njid)]} {
		return $options(dont-log-otr-messages,$xlib,$njid)
	    } else {
		return $options(dont-log-otr-messages)
	    }
	}
    }
    return 0
}

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

proc otr::msgstate:trace {script xlib jid} {
    variable trace

    if {![info exists trace(msgstate,$xlib,$jid)]} {
        set trace(msgstate,$xlib,$jid) {}
    }

    if {[list write [namespace current]::trace] ni
	    [trace info variable [namespace current]::ctx(msgstate,$xlib,$jid)]} {
        trace add variable [namespace current]::ctx(msgstate,$xlib,$jid) write \
			   [namespace current]::tracevar
    }

    if {$script ne {} && ($script ni $trace(msgstate,$xlib,$jid))} {
	lappend trace(msgstate,$xlib,$jid) $script
    }
}

proc otr::tracevar {name1 name2 op} {
    variable trace

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

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

proc otr::message_button {mw xlib jid} {
    set bbox [ButtonBox $mw.bottom.otrbutton -spacing 0]

    set b [$bbox add \
		 -image [msgstate:icon $xlib $jid] \
		 -helptype balloon \
		 -helptext [msgstate:helptext $xlib $jid] \
		 -height 24 \
		 -width 24 \
		 -relief link \
		 -bd $::tk_borderwidth \
		 -state [msgstate:enabled $xlib $jid] \
		 -command [namespace code [list toggle_session $xlib $jid ""]]]

    msgstate:trace [list [namespace current]::msgstate:button_conf $b $xlib $jid] \
		   $xlib $jid

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

hook::add open_message_post_hook [namespace current]::otr::message_button 55

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

proc otr::chat_window_button {chatid type} {
    if {$type ne "chat"} {
	return
    }

    set xlib [chat::get_xlib $chatid]
    set jid [chat::get_jid $chatid]
    set cw [chat::winid $chatid]

    set b [Button $cw.status.otrmsgstate \
		  -relief flat \
		  -image [msgstate:icon $xlib $jid] \
		  -helptype balloon \
		  -helptext [msgstate:helptext $xlib $jid] \
		  -state [msgstate:enabled $xlib $jid] \
		  -command [namespace code [list toggle_session $xlib $jid $type]]]

    msgstate:trace [list [namespace current]::msgstate:button_conf $b $xlib $jid] \
		   $xlib $jid

    pack $b -side left -before $cw.status.mb
}

hook::add open_chat_post_hook [namespace current]::otr::chat_window_button 55

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

proc otr::msgstate:button_conf {b xlib jid} {
    $b configure -image [msgstate:icon $xlib $jid] \
		 -helptext [msgstate:helptext $xlib $jid] \
		 -state [msgstate:enabled $xlib $jid]
}

proc otr::msgstate:icon {xlib jid} {
    lindex [list otr/notprivate \
		 otr/unverified \
		 otr/private \
		 otr/finished] \
           [msgstate:index $xlib $jid]
}

proc otr::msgstate:helptext {xlib jid} {
    variable ctx

    set text [::msgcat::mc "State:"]
    append text " " [msgstate:state $xlib $jid]
    switch -- [msgstate:index $xlib $jid] {
	1 - 2 {
	    append text \n [::msgcat::mc "Fingerprint:"] " " \
			[::otr::fingerprint $ctx($xlib,$jid)] \
			\n [::msgcat::mc "SSID:"] " " \
			[::otr::ssid $ctx($xlib,$jid)]
	}
    }
    set text
}

proc otr::msgstate:state {xlib jid} {
    lindex [list [::msgcat::mc "Not private"] \
		 [::msgcat::mc "Unverified"] \
		 [::msgcat::mc "Private"] \
		 [::msgcat::mc "Finished"]] \
           [msgstate:index $xlib $jid]
}

proc otr::msgstate:index {xlib jid} {
    variable ctx

    if {[info exists ctx($xlib,$jid)]} {
	switch -- $ctx(msgstate,$xlib,$jid) {
	    MSGSTATE_PLAINTEXT {
		return 0
	    }
	    MSGSTATE_ENCRYPTED {
		set myjid [::xmpp::jid::removeResource [connection_jid $xlib]]
		set fingerprint [::otr::fingerprint $ctx($xlib,$jid)]
		set tjid [peer_jid $xlib $jid]
		if {![auth::get_auth $auth::Auth $myjid $tjid $fingerprint]} {
		    return 1
		} else {
		    return 2
		}
	    }
	    MSGSTATE_FINISHED {
		return 3
	    }
	}
    }
    return 0
}

proc otr::msgstate:enabled {xlib jid} {
    if {[lsearch -exact [connections] $xlib] < 0
	    || [::xmpp::jid::equal [my_jid $xlib $jid] $jid]
	    || ![once_only $xlib $jid]} {
	return disabled
    } else {
	return normal
    }
}

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

proc otr::enable_disable_buttons {xlib} {
    variable ctx

    foreach chatid [chat::opened $xlib] {
	set jid [chat::get_jid $chatid]
	if {[info exists ctx(msgstate,$xlib,$jid)]} {
	    on_msgstate_change $xlib $jid $ctx(msgstate,$xlib,$jid)
	} else {
	    on_msgstate_change $xlib $jid MSGSTATE_PLAINTEXT
	}
    }
}

hook::add connected_hook [namespace current]::otr::enable_disable_buttons
hook::add disconnected_hook [namespace current]::otr::enable_disable_buttons

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

proc otr::user_popup_info {infovar xlib jid} {
    variable ctx

    upvar 0 $infovar info

    if {[info exists ctx($xlib,$jid)]} {
	append info [::msgcat::mc "\n\tOTR:"]
	append info [regsub -all {\n} \n[msgstate:helptext $xlib $jid] "\n\t    "]
    }
}

hook::add roster_user_popup_info_hook [namespace current]::otr::user_popup_info 99

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

proc otr::userinfo {tab xlib jid editable} {
    variable ctx

    if {$editable} return

    set bjid [::xmpp::jid::removeResource $jid]
    set chatid [chat::chatid $xlib $bjid]
    if {[chat::is_groupchat $chatid]} {
	if {[info exists ctx(authstate,$xlib,$jid)]} {
	    set jids [list authstate,$xlib,$jid]
	} else {
	    set jids [list]
	}
    } else {
	set bjpat [regsub -all {(.)} $bjid {\\\1}]
	set jids [array names ctx authstate,$xlib,$bjpat/*]
    }
    if {[llength $jids] > 0} {
	set otrinfo [$tab insert end otrinfo \
			      -text [::msgcat::mc "OTR"]]

	set sw [ScrolledWindow $otrinfo.sw -scrollbar vertical]
	pack $sw -fill both -expand yes
	set sf [ScrollableFrame $otrinfo.sf -constrainedwidth 1]
	$sw setwidget $sf

	set i 0
	foreach j $jids {
	    regexp {authstate,[^,]*,(.*)} $j -> fjid
	    set x [userinfo::pack_frame [$sf getframe].otr_$i $fjid]

	    userinfo::pack_entry $jid $x 1 otr_state_$i [::msgcat::mc "State:"]
	    set userinfo::userinfo(otr_state_$i,$jid) [msgstate:state $xlib $fjid]

	    userinfo::pack_entry $jid $x 2 otr_fingerprint_$i [::msgcat::mc "Fingerprint:"]
	    set userinfo::userinfo(otr_fingerprint_$i,$jid) [::otr::fingerprint $ctx($xlib,$fjid)]

	    userinfo::pack_entry $jid $x 3 otr_ssid_$i [::msgcat::mc "SSID:"]
	    set userinfo::userinfo(otr_ssid_$i,$jid) [::otr::ssid $ctx($xlib,$fjid)]

	    bindscrolldeep $sf $sf
	    incr i
	}
    }
}

hook::add userinfo_hook [namespace current]::otr::userinfo 90

proc otr::bindscrolldeep {w f} {
    bindscroll $w $f
    foreach sw [winfo children $w] {
	bindscrolldeep $sw $f
    }
}

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

proc otr::main_menu {} {
    variable options

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

	set mm .otr_menu
	menu $mm -tearoff $::ifacetk::options(show_tearoffs)
	$mm add command -label [::msgcat::mc "Manage private keys"] \
	    -command [namespace code key::manage_keys_dialog]
	$mm add command -label [::msgcat::mc "Edit authentication"] \
	    -command [namespace code auth::edit_auth_dialog]
	$mm add separator
	$mm add checkbutton -label [::msgcat::mc "Allow encryption"] \
	    -variable [namespace current]::options(allow-encryption)
	$mm add checkbutton -label [::msgcat::mc "Require encryption"] \
	    -variable [namespace current]::options(require-encryption)
	$mm add checkbutton -label [::msgcat::mc "Attach whitespace tag"] \
	    -variable [namespace current]::options(send-whitespace-tag)
	$mm add checkbutton -label [::msgcat::mc "Start AKE on whitespace tag or OTR error"] \
	    -variable [namespace current]::options(whitespace-or-error-start-ake)
	$mm add checkbutton -label [::msgcat::mc "Don't log OTR messages"] \
	    -variable [namespace current]::options(dont-log-otr-messages)

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

hook::add finload_hook [namespace current]::otr::main_menu 55

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

proc otr::user_menu {type m xlib jid} {
    variable options
    variable ctx

    set njid [::xmpp::jid::normalize $jid]

    foreach key {allow-encryption require-encryption send-whitespace-tag
		 whitespace-or-error-start-ake dont-log-otr-messages} {
	if {![info exists options($key,$xlib,$njid)]} {
	    set options($key,$xlib,$njid) $options($key)
	}
    }

    if {[::xmpp::jid::equal [my_jid $xlib $jid] $jid] || ![once_only $xlib $jid]} {
	set state disabled
	set qstate disabled
	set rstate disabled
	set fstate disabled
	set astate disabled
    } else {
	switch -- $ctx(msgstate,$xlib,$jid) {
	    MSGSTATE_PLAINTEXT {
		set qstate normal
		set rstate disabled
		set fstate disabled
		set astate disabled
	    }
	    MSGSTATE_ENCRYPTED {
		set qstate disabled
		set rstate normal
		set fstate normal
		set astate normal
	    }
	    MSGSTATE_FINISHED {
		set qstate disabled
		set rstate normal
		set fstate normal
		set astate disabled
	    }
	}
    }

    if {![info exists state] && [lsearch -exact [connections] $xlib] >= 0} {
	set state normal
    } else {
	set state disabled
    }

    set mm $m.otr_menu
    menu $mm -tearoff 0
    $mm add command -label [::msgcat::mc "Start OTR session"] \
	-command [list [namespace current]::request_session $xlib $jid $type] \
	-state $qstate
    $mm add command -label [::msgcat::mc "Refresh OTR session"] \
	-command [list [namespace current]::request_session $xlib $jid $type] \
	-state $rstate
    $mm add command -label [::msgcat::mc "End OTR session"] \
	-command [list [namespace current]::finish_session $xlib $jid $type] \
	-state $fstate
    $mm add separator
    $mm add command -label [::msgcat::mc "Authenticate peer..."] \
	-command [list [namespace current]::begin_smp_dialog $xlib $jid] \
	-state $astate
    $mm add separator
    $mm add command -label [::msgcat::mc "Reset to default policy"] \
	-command [list [namespace current]::reset_policy $xlib $jid]
    $mm add checkbutton -label [::msgcat::mc "Allow encryption"] \
	-variable [namespace current]::options(allow-encryption,$xlib,$njid) \
	-command [list [namespace current]::update_policy $xlib $jid]
    $mm add checkbutton -label [::msgcat::mc "Require encryption"] \
	-variable [namespace current]::options(require-encryption,$xlib,$njid) \
	-command [list [namespace current]::update_policy $xlib $jid]
    $mm add checkbutton -label [::msgcat::mc "Attach whitespace tag"] \
	-variable [namespace current]::options(send-whitespace-tag,$xlib,$njid) \
	-command [list [namespace current]::update_policy $xlib $jid]
    $mm add checkbutton -label [::msgcat::mc "Start AKE on whitespace tag or OTR error"] \
	-variable [namespace current]::options(whitespace-or-error-start-ake,$xlib,$njid) \
	-command [list [namespace current]::update_policy $xlib $jid]
    $mm add checkbutton -label [::msgcat::mc "Don't log OTR messages"] \
	-variable [namespace current]::options(dont-log-otr-messages,$xlib,$njid) \
	-command [list [namespace current]::update_policy $xlib $jid]

    $m add cascade -label [::msgcat::mc "OTR"] -menu $mm -state $state
}

hook::add chat_create_user_menu_hook [list [namespace current]::otr::user_menu chat] 43.8
hook::add roster_jid_popup_menu_hook [list [namespace current]::otr::user_menu ""] 43.8
hook::add message_dialog_menu_hook [list [namespace current]::otr::user_menu ""] 43.8
hook::add roster_create_groupchat_user_menu_hook [list [namespace current]::otr::user_menu ""] 43.8

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

# vim:ts=8:sw=4:sts=4:noet
