#
# Copyright 2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author:	Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
# This is a separate "addons" file because it is closely tied to Python
# service definition for the IPsec service.
#

#
# Helper dialog for configuring the IPsec service
#
proc popupServiceConfig_IPsec { parent w node service btn } {
    global plugin_img_add plugin_img_del plugin_img_edit

    set f $w.note.ipsec
    ttk::frame $f
    set h "This IPsec service helper will assist with building an ipsec.sh file"
    set h "$h (located on the Files tab).\nThe IPsec service builds ESP"
    set h "$h tunnels between the specified peers using the racoon IKEv2"
    set h "$h\nkeying daemon. You need to provide keys and the addresses of"
    set h "$h peers, along with the\nsubnets to tunnel."
    ttk::label $f.help -text $h
    pack $f.help -side top -anchor w -padx 4 -pady 4
    $w.note add $f -text "IPsec" -underline 0

    global g_ipsec_key_dir g_ipsec_key_name
    set g_ipsec_key_dir "/etc/core/keys"
    set g_ipsec_key_name "ipsec1"
    ttk::labelframe $f.keys -text "Keys"

    ttk::frame $f.keys.dir
    ttk::label $f.keys.dir.lab -text "Key directory:"
    ttk::entry $f.keys.dir.ent -width 40 -textvariable g_ipsec_key_dir
    ttk::button $f.keys.dir.btn -width 5 -text "..." -command {
        set f .popupServicesCustomize.note.ipsec
	set d [$f.keys.dir.ent get]
	set d [tk_chooseDirectory -initialdir $d -title "Key directory"]
	if { $d != "" } {
	    $f.keys.dir.ent delete 0 end
	    $f.keys.dir.ent insert 0 $d
	}
    }
    pack $f.keys.dir.lab $f.keys.dir.ent $f.keys.dir.btn \
	-side left -padx 4 -pady 4
    pack $f.keys.dir -side top -anchor w

    ttk::frame $f.keys.name
    ttk::label $f.keys.name.lab -text "Key base name:"
    ttk::entry $f.keys.name.ent -width 10 -textvariable g_ipsec_key_name
    pack $f.keys.name.lab $f.keys.name.ent -side left -padx 4 -pady 4
    pack $f.keys.name -side top -anchor w

    set h "The (name).pem x509 certificate and (name).key RSA private key need"
    set h "$h to exist in the\nspecified directory. These can be generated"
    set h "$h using the openssl tool. Also, a ca-cert.pem\nfile should exist"
    set h "$h in the key directory for the CA that issued the certs."
    ttk::label $f.keys.help -text $h
    pack $f.keys.help -side top -anchor w -padx 4 -pady 4

    pack $f.keys -side top -pady 4 -pady 4 -expand true -fill x

    ttk::labelframe $f.t -text "IPsec Tunnel Endpoints"
    set h "(1) Define tunnel endpoints (select peer node using the button"
    set h "$h, then select address from the list)"
    ttk::label $f.t.lab1 -text $h 
    pack $f.t.lab1 -side top -anchor w -padx 4 -pady 4
    ttk::frame $f.t.ep
    ttk::label $f.t.ep.lab1 -text "Local:"
    ttk::combobox $f.t.ep.combo1 -width 12
    pack $f.t.ep.lab1 $f.t.ep.combo1 -side left -padx 4 -pady 4
    populateComboWithIPs $f.t.ep.combo1 $node

    global g_twoNodeSelect g_twoNodeSelectCallback
    set g_twoNodeSelect ""
    set g_twoNodeSelectCallback selectTwoNodeIPsecCallback

    set h "Choose a node by clicking it on the canvas"
    set h "$h or\nby selecting it from the list below."
    ttk::label $f.t.ep.lab2 -text "Peer node:"
    ttk::checkbutton $f.t.ep.node -text " (none) " -variable g_twoNodeSelect \
 	-onvalue "$f.t.ep.node" -style Toolbutton \
	-command "popupSelectNodes {$h} {} selectNodesIPsecCallback"

    ttk::label $f.t.ep.lab3 -text "Peer:"
    ttk::combobox $f.t.ep.combo2 -width 12
    ttk::button $f.t.ep.add -text "Add Endpoint" -image $plugin_img_add \
	-compound left -command "ipsecTreeHelper $f ep"
    pack $f.t.ep.lab2 $f.t.ep.node $f.t.ep.lab3 $f.t.ep.combo2 \
    	$f.t.ep.add -side left -padx 4 -pady 4
    pack $f.t.ep -side top -anchor w

    set h "(2) Select endpoints below and add the subnets to be encrypted" 
    ttk::label $f.t.lab2 -text $h
    pack $f.t.lab2 -side top -anchor w -padx 4 -pady 4

    ttk::frame $f.t.sub
    ttk::label $f.t.sub.lab1 -text "Local subnet:"
    ttk::combobox $f.t.sub.combo1 -width 12
    ttk::label $f.t.sub.lab2 -text "Remote subnet:"
    ttk::combobox $f.t.sub.combo2 -width 12
    ttk::button $f.t.sub.add -text "Add Subnet" -image $plugin_img_add \
	-compound left -command "ipsecTreeHelper $f sub"
    pack $f.t.sub.lab1 $f.t.sub.combo1 $f.t.sub.lab2 $f.t.sub.combo2 \
    	$f.t.sub.add -side left -padx 5 -pady 4
    pack $f.t.sub -side top -anchor w

    global node_list
    set net_list [ipv4SubnetList $node_list]
    $f.t.sub.combo1 configure -values $net_list
    $f.t.sub.combo2 configure -values $net_list
   
    ttk::treeview $f.t.tree -height 5 -selectmode browse -show tree

    pack $f.t.tree -side top -padx 4 -pady 4 -fill both
    pack $f.t -side top -expand true -fill both

    ttk::frame $f.bottom
    ttk::button $f.bottom.del -image $plugin_img_del \
	-command "ipsecTreeHelper $f del"
    ttk::button $f.bottom.gen -text "Generate ipsec.sh" \
	-image $plugin_img_edit -compound left -command "generateIPsecScript $w"
    pack $f.bottom.del $f.bottom.gen -side left -padx 4 -pady 4
    pack $f.bottom -side top
}

#
# Callback invoked when receiving configuration values
# from a Configuration Message; this service helper depends on the ipsec.sh
# file, not the other configuration values.
#
#proc popupServiceConfig_IPsec_vals { node values services w } {
#}

#
# Callback invoked when receiving service file data from a File Message
proc popupServiceConfig_IPsec_file { node name data w } {
    if { $name == "ipsec.sh" } {
	readIPsecScript $w
    }
}

# helper to insert all of a node's IP addresses into a combo
proc populateComboWithIPs { combo node } {
    set ip_list [ipv4List $node 0]
    $combo configure -values $ip_list
    $combo delete 0 end
    $combo insert 0 [lindex $ip_list 0]
}

# called from editor.tcl:button1 when user clicks on a node
# search for IP address and populate
proc selectTwoNodeIPsecCallback {} {
    set w .popupServicesCustomize
    set f $w.note.ipsec
    
    if { ![winfo exists $w] } { return }; # user has closed window
    catch {destroy .nodeselect}

    set node [string trim [$f.t.ep.node cget -text]]
    if { [set node] == "(none)" } { set node "" }

    # populate peer interface combo with list of IPs
    populateComboWithIPs $f.t.ep.combo2 $node
}

# called from popupSelectNodes dialog when a node selection has been made
proc selectNodesIPsecCallback { nodes } {
    global g_twoNodeSelect
    set w .popupServicesCustomize
    set f $w.note.ipsec

    set g_twoNodeSelect ""
    set node [lindex $nodes 0]
    if { $node == "" } {
	$f.t.ep.node configure -text "(none)"
	return
    }
    $f.t.ep.node configure -text " $node "

    # populate peer interface combo with list of IPs
    populateComboWithIPs $f.t.ep.combo2 $node
}

# helper to manipulate tree; cmd is "del", "ep" or "sub"
proc ipsecTreeHelper { f cmd } {

    if { $cmd == "del" } {
	set sel [$f.t.tree selection]
	$f.t.tree delete $sel
	return
    }

    # add endpoint (ep) or subnet (sub)
    set l [string trim [$f.t.$cmd.combo1 get]]
    set p [string trim [$f.t.$cmd.combo2 get]]
    if { $l == "" || $p == "" } {
	if { $cmd == "ep" } {
	    set h "tunnel interface addresses"
	} else {
	    set h "subnet addresses"
	}
	tk_messageBox -type ok -icon warning -message \
	    "You need to select local and peer $h."
	return
    }

    if { $cmd == "ep" } {
        set item [$f.t.tree insert {} end -text "$l <--> $p" -open true]
	$f.t.tree selection set $item
    } elseif { $cmd == "sub" } {
	set parent [$f.t.tree selection]
	if { $parent == "" } {
	    tk_messageBox -type ok -icon warning -message \
	        "You need to first select endpoints, then configure their subnets."
	    return
	}
	if { [$f.t.tree parent $parent] != {} } {
	    set parent [$f.t.tree parent $parent]
	}
	$f.t.tree insert $parent end -text "$l <===> $p"
    }
}

# update an ipsec.sh file that was generated by the IPsec service
proc generateIPsecScript { w } {
    #puts "generateIPsecScript $w..."
    set cfg [$w.note.files.txt get 0.0 end-1c]
    set newcfg ""

    #
    # Gather data for a new config
    #
    set f $w.note.ipsec
    set keydir [$f.keys.dir.ent get]
    set keyname [$f.keys.name.ent get]

    set tunnelhosts ""
    set subnet_list ""
    set ti 0
    set th_items [$f.t.tree children {}]
    foreach th $th_items {
	set ep [$f.t.tree item $th -text]
	set i [string first " " $ep]
	# replace " <--> " with "AND"
	set ep [string replace $ep $i $i+5 "AND"]
	# build a list e.g.:
	#   tunnelhosts="172.16.0.1AND172.16.0.2 172.16.0.1AND172.16.2.1"
	lappend tunnelhosts $ep

	set subnets ""
	foreach subnet_item [$f.t.tree children $th] {
	     set sn [$f.t.tree item $subnet_item -text]
	     set i [string first " " $sn]
	     # replace " <===> " with "AND"
	     set sn [string replace $sn $i $i+6 "AND"]
	     lappend subnets $sn
	}
	incr ti
	set subnetstxt [join $subnets " "]
	# build a list e.g.:
	#   T2="172.16.4.0/24AND172.16.5.0/24 172.16.4.0/24AND172.16.6.0/24"
	set subnets "T$ti=\"$subnetstxt\""
	lappend subnet_list $subnets
    }

    #
    # Perform replacements in existing ipsec.sh file.
    #
    set have_subnets 0
    foreach line [split $cfg "\n"] {
	if { [string range $line 0 6] == "keydir=" } {
	    set line "keydir=$keydir"
	} elseif { [string range $line 0 8] == "certname=" } {
	    set line "certname=$keyname"
	} elseif { [string range $line 0 11] == "tunnelhosts=" } {
	    set tunnelhosts [join $tunnelhosts " "]
	    set line "tunnelhosts=\"$tunnelhosts\""
	} elseif { [string range $line 0 0] == "T" && \
	           [string is digit [string range $line 1 1]] } {
	    if { $have_subnets } {
		continue ;# skip this line
	    } else {
		set line [join $subnet_list "\n"]
		set have_subnets 1
	    }
	}
	lappend newcfg $line
    }
    $w.note.files.txt delete 0.0 end
    $w.note.files.txt insert 0.0 [join $newcfg "\n"]
    $w.note select $w.note.files
    $w.btn.apply configure -state normal
}

proc readIPsecScript { w } {
    set cfg [$w.note.files.txt get 0.0 end-1c]

    set f $w.note.ipsec
    $f.keys.dir.ent delete 0 end
    $f.keys.name.ent delete 0 end
    $f.t.tree delete [$f.t.tree children {}]

    set ti 1
    foreach line [split $cfg "\n"] {
	if { [string range $line 0 6] == "keydir=" } {
	    $f.keys.dir.ent insert 0 [string range $line 7 end]
	} elseif { [string range $line 0 8] == "certname=" } {
	    $f.keys.name.ent insert 0 [string range $line 9 end]
	} elseif { [string range $line 0 11] == "tunnelhosts=" } {
	    set tunnelhosts [string range $line 13 end-1]
	    set ti 0
	    foreach ep [split $tunnelhosts " "] {
		incr ti
		set i [string first "AND" $ep]
		set ep [string replace $ep $i $i+2 " <--> "]
		$f.t.tree insert {} end -id "T$ti" -text "$ep" -open true
	    }
	} elseif { [string range $line 0 0] == "T" && \
	           [string is digit [string range $line 1 1]] } {
	    set i [string first "=" $line]
	    set ti [string range $line 0 $i-1]
	    set subnets [split [string range $line $i+2 end-1] " "]
	    foreach sn $subnets {
		set i [string first "AND" $sn]
		set sn [string replace $sn $i $i+2 " <===> "]
		if { [catch {$f.t.tree insert $ti end -text "$sn"} e] } {
		    puts "IPsec service ignoring line '$ti='"
		}
	    }
	}
    }
}