proc server_pids { } {
    global env

    return [eval [concat exec $env(PS_ALL) | \
	    awk {{/kadmind4/ && !/awk/ && !/expect/ {printf("%d ", $2)}}}]]
}

proc server_exit { name status } {
    global wait_error_index wait_errno_index wait_status_index
    global server_id
    global kill

    verbose "$name: stopping V4 kpasswd server." 1

    # We can't know whether the process exists or not, so we have
    # to ignore errors.  XXX will close ever time out?
    catch {close $server_id}
    set pids [server_pids]
    if {$pids != {}} {
	verbose "server_exit killing process(es) $pids"
	catch {exec $kill $pids}
    } else {
	verbose "server_exit: couldn't find running server(s) to kill"
    }

    # wait hangs on AIX if the process was killed; since status == -1
    # in that case, solve the problem by not waiting; the zombies will
    # be cleaned up when the test finishes
    if {$status == -1} {
	return 1
    }

    set ret [wait -i $server_id]
    verbose "% Exit $ret" 2

    if {[lindex $ret $wait_error_index] == -1} {
	fail "$name: wait returned error [lindex $ret $wait_errno_index]"
	return 0
    } else {
	if { [lindex $ret $wait_status_index] == $status ||
	(($status<0) && ([lindex $ret $wait_status_index] == ($status+256))) } {
	    pass "$name"
	} else {
	    fail "$name: unexpected return status [lindex $ret $wait_status_index], should be $status"
	    return 0
	}
    }

    return 1
}

proc myfail { msg } {
	global mytest_status
	fail "$msg"
	set mytest_status 1
}

proc server_start { name cmdline should_listen args } {
    global spawn_id server_id env
    global VFOURSERVER
    global mytest_status
    global sleep hostname

    set max_tries 60

    verbose "$name: starting V4 kpasswd server." 1

    for {set num_tries 0} {$num_tries <= $max_tries} {incr num_tries} {
	if {$num_tries} {
	    exec $sleep 5
	    verbose "Couldn't connect to V4 kpasswd server; retrying ($num_tries so far)."
	}

	spawn $VFOURSERVER $cmdline
	set server_id $spawn_id

	foreach test $args {
	    set mytest_status 0
	    uplevel 1 "expect {
		-i $server_id
		$test
		timeout { myfail \"$name: timeout\" }
		eof { myfail \"$name: eof while expecting string\" }
	    }"

	    if {$mytest_status == 1} { 
		return 0
	    }
	}

	set pids [server_pids]

	if {$should_listen} {
	    exec $sleep 1
	    set save_spawn_id $spawn_id
	    spawn telnet $hostname kerberos_master
	    expect {
		{Connection refused} {
		    close -i $save_spawn_id
		    wait -i $save_spawn_id
		    close
		    wait
		    continue
		}
		{Connected} {
		    send "close\n"
		    close
		    wait
		    set spawn_id $save_spawn_id
		    break
		}
		default {
		    close -i $save_spawn_id
		    wait -i $save_spawn_id
		    catch {close}
		    wait
		    continue
		}
	    }
	} else {
	    break
	}
    }

    if {$pids == {}} {
	# Try twice to find the server processes.  Not sure why,
	# but there seems to be some sort of race condition in the OS.

	verbose "server_start: couldn't find server process(es) -- trying again"
	exec $sleep 1
	set pids [server_pids]
    }

    if {$num_tries > $max_tries} {
	myfail "$name: couldn't connect to V4 kpasswd server"
	return 0
    } else {
	if {$pids != {}} {
	    verbose "server_start: server process ID(s) is/are $pids"
	}
	pass "$name"
	return 1
    }
}

proc exp_prog { name prog cmdline status args } {
	global spawn_id spawn_pid
	global mytest_status
	global wait_error_index wait_errno_index wait_status_index

	verbose "$name: spawning $prog $cmdline" 1

	set spawn_pid [eval "spawn $prog $cmdline"]

	# at the end, eof is success

#	lappend args { eof { if {[regexp "\[\r\n\]$" $expect_out(buffer)] == 0} { myfail "final status message not newline-terminated" } } }
	lappend args { eof {} }

	foreach test $args {
		set mytest_status 0
		uplevel 1 "expect {
			$test
			timeout { close; myfail \"$name: timeout\" }
		      eof { myfail \"$name: eof while expecting string\" }
		}"

		if {$mytest_status == 1} { return 0 }
	}

	# at this point, the id is closed and we can wait on it.

	set ret [wait]
	verbose "% Exit $ret" 2

	if {$status == -1} { return 1 }

	if {[lindex $ret $wait_error_index] == -1} {
		fail "$name: wait returned error [lindex $ret $wait_errno_index]"
	} else {
		if { [lindex $ret $wait_status_index] == $status ||
		     (($status<0) && ([lindex $ret $wait_status_index] == ($status+256))) } {
			pass "$name"
		} else {
			fail "$name: unexpected return status [lindex $ret $wait_status_index], should be $status"
		}
	}

	return 1
}

proc fix_salt { name fullname oldpw newpw } {
	global kadmin_local

	exp_prog "$name: kadmin.local" $kadmin_local "" 0 {
		"kadmin.local:" { send "cpw $fullname\n" }
	} {
		"Enter password for principal \"$fullname\":" { 
		    send "$newpw\n" 
		}
	} {
		"Re-enter password for principal \"$fullname\":" { 
		    send "$newpw\n"
		}
	} {
	        # if we get cannot reuse pw, the salt is already right
	        -re "Password .* changed." { send "quit\n" }
		-re "Cannot reuse password" { send "quit\n" }
	}
}

proc unexpire { name fullname } {
    global kadmin_local
    
    # While we're at it, make sure they aren't expired.
    exp_prog "$name: kadmin.local" $kadmin_local "" 0 {
	"kadmin.local:" {
	    send "modprinc -expire \"May 6, 2029\" $fullname\n" 
	}
    } { 
	-re "Principal .* modified." { send "quit\n" }
    }
}

proc kpasswd_v4 { name fullname status oldpw newpw args } {
	global kpasswd_v4 s

	eval [concat {
		exp_prog $name $kpasswd_v4 "-u $fullname" $status {
			-re "Old password for $fullname:" { send "$oldpw\n" }
		} {
			-re "New Password for $fullname:" { send "$newpw\n" }
		} {
		   -re "Verifying, please re-enter New Password for $fullname:"
				{ send "$newpw\n" }
		}
		} $args]
}
