# many of these tests use as_root
if {! [installtest_p]} { return }
if {! [nss_p]} { return }

# Let's start with a clean slate in terms of trust.
exec rm -fr $env(SYSTEMTAP_DIR)/ssl

# Compare two arrays. If equal, return 1, otherwise 0.
# (Borrowed from http://wiki.tcl.tk/1032.)
proc array_compare {array1 array2} {
    upvar 1 $array1 foo $array2 bar

    if {![array exists foo]} {
        return -code error "$array1 is not an array"
    }
    if {![array exists bar]} {
        return -code error "$array2 is not an array"
    }
    if {[array size foo] != [array size bar]} {
        return 0
    }
    if {[array size foo] == 0} {
        return 1
    }

    set keys [lsort -unique [concat [array names foo] [array names bar]]]
    if {[llength $keys] != [array size foo]} {
       return 0
    }

    foreach key $keys {
        if {$foo($key) ne $bar($key)} {
            return 0
        }
    }
    return 1
}

# Test the --list-servers option and return an array of the servers found.
proc list_servers { TEST_NAME SERVER_SPEC args } {
    set failed 0
    set n 0
    array unset ::servers
    set cmd [concat stap --list-servers=$SERVER_SPEC $args]
    send_log "executing: $cmd\n"
    eval spawn $cmd
    expect {
	-timeout 150
	-re "^Systemtap Compile Server Status for '${SERVER_SPEC}'\r\n" {
	    exp_continue
	}
	-re {^No servers found\r\n} {
	}
	-re {^ host=[^\r]*\r\n} {
	    set ::servers($n) "$expect_out(0,string)"
	    incr n
	    exp_continue
	}
	-re {^No certificate found in database [^\r]*\r\n} {
	    exp_continue
	}
	-re {^[^\r]*\r\n} {
	    set failed 1
	}
	timeout { 
	    exec kill -INT -- -[exp_pid]
	    set failed 1
	}
    }
    catch {close}; catch {wait}

    if {$failed != 0} {
	fail "$TEST_NAME"
    } else {
	pass "$TEST_NAME"
    }
}

# There may be other servers running. Let's keep track of
# them.
#
# Sometimes, we'll see a server running from the last test, if it
# hasn't quite died yet. So, make sure we get the same result twice.
list_servers "List existing online servers" online
array unset eos1
array set existing_online_servers [array get servers]
set i 0
while {1} {
    list_servers "List existing online servers" online
    array unset eos2
    array set eos2 [array get servers]
    set i [expr $i + 1]
    if {$i > 10} {
	fail "List existing online servers: never got stable"
	return
    }
    
    verbose -log "verify existing online servers - attempt $i: [array size existing_online_servers] [array size eos2]"
    if {[array_compare existing_online_servers eos2]} {
	# Arrays are equal, we're done
	break
    }
    array unset existing_online_servers
    array set existing_online_servers [array get eos2]
}

# There may be existing trusted servers. Keep track of them.
list_servers "List existing trusted servers" trusted
array unset existing_trusted_servers
array set existing_trusted_servers [array get servers]

# There may be existing trusted signers. Keep track of them.
list_servers "List existing signing servers" signer
array unset existing_signing_servers
array set existing_signing_servers [array get servers]

# If we query all known servers, it should contain exactly the union of the
# above three queries.
list_servers "List all existing servers" all
array unset all_existing_servers
array set all_existing_servers [array get servers]

set test "Verify existing online server list"
if {[array_compare existing_online_servers all_existing_servers]} {
    pass "$test"
} else {
    fail "$test"
}

set test "Verify existing trusted server list"
if {[array_compare existing_trusted_servers all_existing_servers]} {
    pass "$test"
} else {
    fail "$test"
}

set test "Verify existing signing server list"
if {[array_compare existing_signing_servers all_existing_servers]} {
    pass "$test"
} else {
    fail "$test"
}

set test "Verify all existing server list"
if {[array_compare existing_online_servers all_existing_servers]} {
    pass "$test"
} else {
    fail "$test"
}

list_servers "List existing online servers (before start)" online
array unset existing_online_servers1
array set existing_online_servers1 [array get servers]

# Now start our own server and make sure we can work with it.
if {! [setup_server] || $avahi_ok_p != 1} {
    untested "Compile-server client tests against a server"
    return;
}

# Our server should now appear online, separate from the previously discovered
# online servers. Note that our server could generate serveral listings
# because it could appear at more than one ip address,
list_servers "List current online servers" online
array unset current_online_servers
array set current_online_servers [array get servers]

set test "New online servers"
set n 0
foreach idx1 [array names current_online_servers] {
    set found 0
    foreach idx2 [array names existing_online_servers] {
	if {"$existing_online_servers($idx2)" == "$current_online_servers($idx1)"} {
	    set found 1
	    break
	}
    }
    if {$found == 0} {
	set new_online_servers($n) "$current_online_servers($idx1)"
	incr n
    }
}
if {$n == 0} {
    fail "$test"
} else {
    pass "$test"
}

# Our server should now be trusted, separate from the previously discovered
# trusted servers.
list_servers "List current trusted servers" online,trusted
array unset current_trusted_servers
array set current_trusted_servers [array get servers]

set test "New trusted servers"
set n 0
foreach idx1 [array names current_trusted_servers] {
    set found 0
    foreach idx2 [array names existing_trusted_servers] {
	if {"$existing_trusted_servers($idx2)" == "$current_trusted_servers($idx1)"} {
	    set found 1
	    break
	}
    }
    if {$found == 0} {
	set new_trusted_servers($n) "$current_trusted_servers($idx1)"
	incr n
    }
}
if {$n == 0} {
    fail "$test"
} else {
    pass "$test"
}

# The new servers should automatically be trusted, so the new_trusted_servers
# array should be a subset of the new_online_servers
# array, but not necessarilty vice-versa, since new servers may have come
# online independently of our testing.
set test "Verify new trusted server list"
set failed 0
foreach idx1 [array names new_trusted_servers] {
    set found 0
    foreach idx2 [array names new_online_servers] {
	if {"$new_trusted_servers($idx1)" == "$new_online_servers($idx2)"} {
	    set found 1
	    break
	}
    }
    if {$found == 0} {
	set failed 1
	fail "$test $idx1"
    }
}
if {$failed == 0} {
    pass "$test"
}

# The newly trusted servers represent the server we just started.
array unset our_servers
array set our_servers [array get new_trusted_servers]

# The new servers should not be trusted as signers so there should be no new
# signing servers.
list_servers "List current signing servers" signer
array unset current_signing_servers
array set current_signing_servers [array get servers]

set test "No new signing servers"
if {[array_compare current_signing_servers existing_signing_servers]} {
    pass "$test"
} else {
    fail "$test"
}

# Revoke trust in our server. Specify the server by host name.
set test "Server has host name"
if {[regexp {^ host=([^ ]*).*} $our_servers(0) match host_name]} {
    pass $test
} else {
    fail $test
}

# Compile and run a simple hello test, selecting the server automatically
if {[installtest_p]} then {
    set test "Hello from server"
    set rc [stap_run_batch $srcdir/systemtap.server/hello.stp $use_server]
    if {$rc == 0} { pass $test } else { fail $test }
}

set cmd [concat stap --trust-servers=ssl,revoke,no-prompt --use-server=$host_name]
send_log "executing: $cmd\n"
eval spawn $cmd
expect {
    -timeout 150
    -re {^.*\r\n} { exp_continue }
    timeout { 
	exec kill -INT -- -[exp_pid]
	set failed 1
    }
}
catch {close}; catch {wait}

# Our server should no longer be trusted.
list_servers "List current trusted servers after revokation by host name" trusted
array unset current_trusted_servers
array set current_trusted_servers [array get servers]

set test "No longer trusted after revokation by host name"
if {[array_compare current_trusted_servers existing_trusted_servers]} {
    pass "$test"
} else {
    fail "$test"
}

# Reinstate trust in our server. Specify the server by ip address.
# The default for --trusted servers is 'ssl'.
set test "Server has ip address"
if {[regexp {^.*address=([^ ]*).*} $our_servers(0) match ip_address]} {
    pass $test
} else {
    fail $test
}
set cmd [concat stap --trust-servers=no-prompt --use-server=$ip_address]
send_log "executing: $cmd\n"
eval spawn $cmd
expect {
    -timeout 150
    -re {^.*\r\n} { exp_continue }
    timeout { 
	exec kill -INT -- -[exp_pid]
	set failed 1
    }
}
catch {close}; catch {wait}

# Our server should be trusted again, separate from the previously discovered
# trusted servers.
list_servers "List current trusted servers after reinstatement by ip address" online,trusted
array unset current_trusted_servers
array set current_trusted_servers [array get servers]

set test "New trusted servers after reinstatement by ip address"
array unset new_trusted_servers
set n 0
foreach idx1 [array names current_trusted_servers] {
    set found 0
    foreach idx2 [array names existing_trusted_servers] {
	if {"$existing_trusted_servers($idx2)" == "$current_trusted_servers($idx1)"} {
	    set found 1
	    break
	}
    }
    if {$found == 0} {
	set new_trusted_servers($n) "$current_trusted_servers($idx1)"
	incr n
    }
}
if {$n == 0} {
    fail "$test"
} else {
    pass "$test"
}

# The new_trusted_servers array should now match the our_servers array, since
# the our_servers array is a copy of the original new_trusted_servers array.
set test "Number of new trusted servers matches after reinstatement by ip address"
if {[array size new_trusted_servers] == [array size our_servers]} {
    pass "$test"
} else {
    fail "$test"
}
set test "New trusted servers matches after reinstatement by ip address"
set n 0
foreach idx1 [array names new_trusted_servers] {
    set found 0
    foreach idx2 [array names our_servers] {
	if {"$our_servers($idx2)" == "$new_trusted_servers($idx1)"} {
	    set found 1
	    break
	}
    }
    if {$found == 1} {
	incr n
    }
}
if {$n != [array size new_trusted_servers]} {
    fail "$test"
} else {
    pass "$test"
}

# Trust our server as a module signer. This must be done as root. Specify
# the server by certificate serial number.
set test "Server has certificate serial number"
if {[regexp {^.*certinfo="([^ ]*)".*} $our_servers(0) match cert_info]} {
    pass $test
} else {
    fail $test
}

# Even though this action must be performed as root, Do not perform it if we
# are already root, since it alters the state of the install tree. See PR 11442.
# The related tests should then be expected failures.
set effective_pid [exec /usr/bin/id -u]
if {$effective_pid != 0} {
    set cmd [concat [exec which stap] --trust-servers=signer,no-prompt --use-server=$cert_info]
    eval as_root { $cmd }
}

# Our server should now be trusted as a signer, separate from the previously
# discovered trusted signing servers.
list_servers "List current online signing servers" online,signer
array unset current_signing_servers
array set current_signing_servers [array get servers]

set test "New signing servers"
set n 0
foreach idx1 [array names current_signing_servers] {
    set found 0
    foreach idx2 [array names existing_signing_servers] {
	if {"$existing_signing_servers($idx2)" == "$current_signing_servers($idx1)"} {
	    set found 1
	    break
	}
    }
    if {$found == 0} {
	set new_signing_servers($n) "$current_signing_servers($idx1)"
	incr n
    }
}
if {$effective_pid == 0} {
    setup_xfail *-*-*
}
if {$n == 0} {
    fail "$test"
} else {
    pass "$test"
}

# The new_signing_servers array should now match the our_servers array, since
# the we specified our server by certificate serial number so that we don't
# accidentally trust another server on the same host.
set test "Number of new signing servers matches"
if {$effective_pid == 0} {
    setup_xfail *-*-*
}
if {[array size new_signing_servers] == [array size our_servers]} {
    pass "$test"
    set test "New signing servers matches"
    set n 0
    foreach idx1 [array names new_signing_servers] {
	set found 0
	foreach idx2 [array names our_servers] {
	    if {"$our_servers($idx2)" == "$new_signing_servers($idx1)"} {
		set found 1
		break
	    }
	}
	if {$found == 1} {
	    incr n
	}
    }
    if {$effective_pid == 0} {
	setup_xfail *-*-*
    }
    if {$n != [array size new_signing_servers]} {
	fail "$test"
    } else {
	pass "$test"
    }
} else {
    fail "$test"
    set test "New signing servers matches"
    if {$effective_pid == 0} {
	setup_xfail *-*-*
    }
    fail "$test"
}

set test "Server has port number"
if {[regexp {^.*port=([^ ]*).*} $our_servers(0) match port_num]} {
    pass $test
} else {
    fail $test
}

foreach privilege {"--unprivileged" "--privilege=stapusr" "--privilege=stapsys"} {
    # Compile a simple test using an unprivileged setting. This will ask the server to
    # check and sign the module. Specify the server using host name and port.
    set test "Compile module using server with $privilege"
    set failed 1
    set module_name ""
    set cmd [concat stap -p4 $privilege $srcdir/systemtap.server/hello.stp --use-server=$host_name:$port_num]
    send_log "executing: $cmd\n"
    eval spawn $cmd
    expect {
	-timeout 150
	-re {^stap_[^ \t\r\n]*\.ko\r\n} {
	    set module_name [string trim "$expect_out(0,string)" \r\n]
	    set failed 0
	    exp_continue
	}
	-re {^.*\r\n} { exp_continue }
	timeout { 
	    exec kill -INT -- -[exp_pid]
	}
    }
    catch {close}; catch {wait}
    if {$failed == 0} {
	pass "$test"
    }
    send_log "'$module_name'\n"

    # Make sure that the module was returned
    set no_module 0
    set test "Module was created with $privilege"
    catch {exec /bin/ls $module_name $module_name.sgn} result
    send_log "$result\n"
    if {[file exists $module_name]} {
	pass "$test"
    } else {
	fail "$test"
	set no_module 1
    }

    # Make sure that the module was signed
    set no_signature 0
    set test "Module was signed with $privilege"
    if {[file exists $module_name.sgn]} {
	pass "$test"
    } else {
	fail "$test"
	set no_signature 1
    }

    # Make sure we can load the module. This will verify that the signature
    # is correct and trusted.
    # There must be a module to load.
    if {$no_module == 0} {
	set expect_failure 1
	# If we are root, we can load anything.
	if {$effective_pid == 0} {
	    set expect_failure 0
	} else {
	    set id_info [exec /usr/bin/id]
	    # Not root, so we must be a member of stapusr.
	    if {[regexp {.*\(stapusr\).*} "$id_info"]} {
		# Members of stapdev can load an unsigned module.
		if {[regexp {.*\(stapdev\).*} "$id_info"]} {
		    set expect_failure 0
		} else {
		    # Otherwise, the module must be signed.
		    if {$no_signature == 0} {
			# We must have the necessary credentials. We already know that we are
			# stapusr. See if we need to be stapsys.
			if {$privilege == "--privilege=stapsys"} {
			    if {[regexp {.*\(stapsys\).*} "$id_info"]} {
				set expect_failure 0
			    }
			} else {
			    # stapusr is good enough.
			    set expect_failure 0
			}
		    }
		}
	    }    
	}
	if {$expect_failure} {
	    setup_xfail *-*-*
	}
    }
    set test "Load and run signed module when trusted with $privilege"
    if {$no_module == 1 || ! [installtest_p]} {
	untested "$test"
    } else {
	set failed 1
	set cmd [concat staprun $module_name]
	send_log "executing: $cmd\n"
	eval spawn $cmd
	expect {
	    -timeout 150
	    -re {^Hello From Server\r\n} {
		set failed 0
		exp_continue
	    }
	    timeout { 
		exec kill -INT -- -[exp_pid]
		set failed 1
	    }
	}
	catch {close}; catch {wait}
	if {$failed == 0} {
	    pass "$test"
	} else {
	    fail "$test"
	}
    }
}

# Revoke trust in our server as a module signer. This must be done as root.
# Specify the server by certificate serial number so that we don't accidentally
# revoke trust in a previously trusted server.
# Even though this action must be performed as root, Do not perform it if we
# are already root, since it alters the state of the install tree. See PR 11442.
if {$effective_pid != 0} {
    set cmd [concat [exec which stap] --trust-servers=revoke,signer,no-prompt --use-server=$cert_info]
    eval as_root { $cmd }
}

# Our server should no longer be trusted as a signer
list_servers "List current signing servers after revokation" signer
array unset current_signing_servers
array set current_signing_servers [array get servers]

set test "No longer trusted as a signer after revokation"
if {[array_compare current_signing_servers existing_signing_servers]} {
    pass "$test"
} else {
    fail "$test"
}

# Since our server is no longer a trusted signer, attempting
# to load and run the module now should fail unless we
# are root or a member of the groups stapdev and stapusr.
if {$no_module == 0} {
    set expect_failure 1
    if {[exec /usr/bin/id -u] == 0} {
	set expect_failure 0
    } else {
	if {[regexp {.*\(stapdev\).*} "$id_info"]} {
	    if {[regexp {.*\(stapusr\).*} "$id_info"]} {
		set expect_failure 0
	    }
	}
    }
    if {$expect_failure} {
	setup_xfail *-*-*
    }
}
set test "Load and run signed module when not trusted"
if {$no_module == 1 || ! [installtest_p]} {
    untested "$test"
} else {
    set failed 1
    set cmd [concat staprun $module_name]
    send_log "executing: $cmd\n"
    eval spawn $cmd
    expect {
	-timeout 150
	-re {^Hello From Server\r\n} {
	    set failed 0
	    exp_continue;
	}
	timeout { 
	    exec kill -INT -- -[exp_pid]
	    set failed 1
	}
    }
    catch {close}; catch {wait}
    if {$failed == 0} {
	pass "$test"
    } else {
	fail "$test"
    }
}

# Shutdown the server we started
shutdown_server
