# Kerberos ftp test.
# This is a DejaGnu test script.
# This script tests Kerberos ftp.
# Originally written by Ian Lance Taylor, Cygnus Support, <ian@cygnus.com>.
# Modified bye Ezra Peisach for GSSAPI support.

# Find the programs we need.  We use the binaries from the build tree
# if they exist.  If they do not, then they must be in PATH.  We
# expect $objdir to be .../kerberos/build/tests/dejagnu

if ![info exists FTP] {
    set FTP [findfile $objdir/../../appl/gssftp/ftp/ftp]
}

if ![info exists FTPD] {
    set FTPD [findfile $objdir/../../appl/gssftp/ftpd/ftpd]
}

# If we do not have what is for a V4 test - return
if ![v4_compatible_enctype] {
    return
}

# Make sure .klogin is reasonable.
if ![check_k5login ftp] {
    return
}

if ![check_klogin ftp] {
    return
}

# Set up the kerberos database.
if {![get_hostname] \
    || ![setup_kerberos_files] \
    || ![setup_kerberos_env] \
    || ![setup_kerberos_db 0]} {
    return
}

# A procedure to start up the ftp daemon.

proc start_ftp_daemon { } {
    global FTPD
    global tmppwd
    global ftpd_spawn_id
    global ftpd_pid
    global portbase

    # The -p argument tells it to accept a single connection, so we
    # don't need to use inetd.  Portbase+8 is the port to listen at.
    # We rely on KRB5_KTNAME being set to the proper keyfile as there is
    # no way to cleanly set it with the gssapi API.
    # The -U argument tells it to use an alternate ftpusers file (using
    # /dev/null will allow root to login regardless of /etc/ftpusers).
    # The -a argument requires authorization, to mitigate any
    # vulnerability introduced by circumventing ftpusers.
    spawn $FTPD -p [expr 8 + $portbase] -a -U /dev/null -r $tmppwd/krb.conf
    set ftpd_spawn_id $spawn_id
    set ftpd_pid [exp_pid]

    # Give the ftp daemon a few seconds to get set up.
    catch "exec sleep 2"
}

# A procedure to stop the ftp daemon.

proc stop_ftp_daemon { } {
    global ftpd_spawn_id
    global ftpd_pid

    if [info exists ftpd_pid] {
	catch "close -i $ftpd_spawn_id"
	catch "exec kill $ftpd_pid"
	catch "wait -i $ftpd_spawn_id"
	unset ftpd_pid
    }
}

# Create a file to use for ftp testing.
set file [open $tmppwd/ftp-test w]
puts $file "This file is used for ftp testing."
close $file

# Create a large file to use for ftp testing. File needs to be 
# larger that 2^20 or 1MB for PBSZ testing.
set file [open $tmppwd/bigftp-test w]
puts $file "This file is used for ftp testing.\n"
seek $file 1048576 current
puts $file "This file is used for ftp testing."
close $file

# Test that a file was copied correctly.
proc check_file { filename {bigfile 0}} {
    if ![file exists $filename] {
	verbose "$filename does not exist"
	send_log "$filename does not exist\n"
	return 0
    }

    set file [open $filename r]
    if { [gets $file line] == -1 } {
	verbose "$filename is empty"
	send_log "$filename is empty\n"
	close $file
	return 0
    }

    if ![string match "This file is used for ftp testing." $line] {
	verbose "$filename contains $line"
	send_log "$filename contains $line\n"
	close $file
	return 0
    }

    if {$bigfile} {
	# + 1 for the newline
	seek $file 1048577 current
	if { [gets $file line] == -1 } {
	    verbose "$filename is truncated"
	    send_log "$filename is truncated\n"
	    close $file
	    return 0
	}

	if ![string match "This file is used for ftp testing." $line] {
	    verbose "$filename contains $line"
	    send_log "$filename contains $line\n"
	    close $file
	    return 0
	}
    }

    if { [gets $file line] != -1} {
	verbose "$filename is too long ($line)"
	send_log "$filename is too long ($line)\n"
	close $file
	return 0
    }

    close $file

    return 1
}

#
# Restore environment variables possibly set.
#
proc ftp_restore_env { } {
    global env
    global ftp_save_ktname
    global ftp_save_ccname

    catch "unset env(KRB5_KTNAME)"
    if [info exists ftp_save_ktname] {
	set env(KRB5_KTNAME) $ftp_save_ktname
	unset ftp_save_ktname
    }

    catch "unset env(KRB5CCNAME)"
    if [info exists ftp_save_ccname] {
	set env(KRB5CCNAME) $ftp_save_ccname
	unset ftp_save_ccname
    }
}

# Wrap the tests in a procedure, so that we can kill the daemons if
# we get some sort of error.

proc v4ftp_test { } {
    global FTP
    global KEY
    global REALMNAME
    global hostname
    global localhostname
    global env
    global ftpd_spawn_id
    global ftpd_pid
    global spawn_id
    global tmppwd
    global ftp_save_ktname
    global ftp_save_ccname
    global des3_krbtgt
    global portbase

    if {$des3_krbtgt} {
	return
    }
    # Start up the kerberos and kadmind daemons and get a srvtab and a
    # ticket file.
    if {![start_kerberos_daemons 0] \
        || ![add_random_key ftp/$hostname 0] \
        || ![setup_srvtab 0 ftp] \
	|| ![add_kerberos_key $env(USER) 0] \
	|| ![v4kinit $env(USER) $env(USER)$KEY 0]} {
	return
    }

    #
    # Save settings of KRB5_KTNAME
    #
    if [info exists env(KRB5_KTNAME)] {
	set ftp_save_ktname $env(KRB5_KTNAME)
    }

    #
    # set KRB5_KTNAME
    #
    set env(KRB5_KTNAME) FILE:$tmppwd/srvtab
    verbose "KRB5_KTNAME=$env(KRB5_KTNAME)"

    #
    # Save settings of KRB5CCNAME
    # These tests fail if the krb5 cache happens to have a valid credential
    # which can result from running the gssftp.exp test immediately
    # preceeding these tests.
    #
    if [info exists env(KRB5CCNAME)] {
	set ftp_save_ccname $env(KRB5CCNAME)
    }

    #
    # set KRB5_KTNAME
    #
    set env(KRB5CCNAME) FILE:$tmppwd/non-existant-cache
    verbose "KRB5CCNAME=$env(KRB5CCNAME)"

    # Start the ftp daemon.
    start_ftp_daemon

    # Make an ftp client connection to it.
    spawn $FTP $hostname [expr 8 + $portbase]

    expect_after {
	timeout {
	    fail "$testname (timeout)"
	    catch "expect_after"
	    return
	}
	eof {
	    fail "$testname (eof)"
	    catch "expect_after"
	    return
	}
    }

    set testname "ftp connection(v4)"
    expect -nocase "connected to $hostname"
    expect -nocase -re "$localhostname.*ftp server .version \[0-9.\]*. ready."
    expect -re "Using authentication type GSSAPI; ADAT must follow"
    expect "GSSAPI accepted as authentication type"
    expect "GSSAPI error major: Miscellaneous failure"
    expect {
	"GSSAPI error minor: Unsupported credentials cache format version number" {}
	"GSSAPI error minor: No credentials cache found" {}
	"GSSAPI error minor: Decrypt integrity check failed" {}
    }
    expect "GSSAPI error: initializing context"
    expect "GSSAPI authentication failed"
    expect -re "Using authentication type KERBEROS_V4; ADAT must follow"
    expect {
	"Kerberos V4 authentication succeeded" { pass "ftp authentication" }
	eof	{ fail "ftp authentication" ; catch "expect_after" ; return }
	-re "Kerberos V4 .* failed.*\r" {
	    fail "ftp authentication";
	    send "quit\r"; catch "expect_after";
	    return
	}
    }
    expect -nocase "name ($hostname:$env(USER)): "
    send "$env(USER)\r"
    expect "Kerberos user $env(USER)@$REALMNAME is authorized as $env(USER)"
    expect "Remote system type is UNIX."
    expect "Using binary mode to transfer files."
    expect "ftp> " {
	pass $testname
    }

    set testname "binary(v4)"
    send "binary\r"
    expect "ftp> " {
	pass $testname
    }

    set testname "status(v4)"
    send "status\r"
    expect -nocase "connected to $hostname."
    expect "Authentication type: KERBEROS_V4"
    expect "ftp> " {
	pass $testname
    }

    set testname "ls(v4)"
    send "ls $tmppwd/ftp-test\r"
    expect -re "Opening ASCII mode data connection for .*ls."
    expect -re ".* $tmppwd/ftp-test"
    expect "ftp> " {
	pass $testname
    } 

    set testname "nlist(v4)"
    send "nlist $tmppwd/ftp-test\r"
    expect -re "Opening ASCII mode data connection for file list."
    expect -re "$tmppwd/ftp-test"
    expect -re ".* Transfer complete."
    expect "ftp> " {
	pass $testname
    } 

    set testname "ls missing(v4)"
    send "ls $tmppwd/ftp-testmiss\r"
    expect -re "Opening ASCII mode data connection for .*ls."
    expect {
	-re "$tmppwd/ftp-testmiss not found" {}
	-re "$tmppwd/ftp-testmiss: No such file or directory"
    }
    expect "ftp> " {
	pass $testname
    } 


    set testname "get(v4)"
    catch "exec rm -f $tmppwd/copy"
    send "get $tmppwd/ftp-test $tmppwd/copy\r"
    expect "Opening BINARY mode data connection for $tmppwd/ftp-test"
    expect "Transfer complete"
    expect -re "\[0-9\]+ bytes received in \[0-9.e-\]+ seconds"
    expect "ftp> "
    if [check_file $tmppwd/copy] {
	pass $testname
    } else {
	fail $testname
    }

    set testname "put(v4)"
    catch "exec rm -f $tmppwd/copy"
    send "put $tmppwd/ftp-test $tmppwd/copy\r"
    expect "Opening BINARY mode data connection for $tmppwd/copy"
    expect "Transfer complete"
    expect -re "\[0-9\]+ bytes sent in \[0-9.e-\]+ seconds"
    expect "ftp> "
    if [check_file $tmppwd/copy] {
	pass $testname
    } else {
	fail $testname
    }

    set testname "cd(v4)"
    send "cd $tmppwd\r"
    expect "CWD command successful."
    expect "ftp> " {
	pass $testname
    }

    set testname "lcd(v4)"
    send "lcd $tmppwd\r"
    expect "Local directory now $tmppwd"
    expect "ftp> " {
	pass $testname
    }

    set testname "local get(v4)"
    catch "exec rm -f $tmppwd/copy"
    send "get ftp-test copy\r"
    expect "Opening BINARY mode data connection for ftp-test"
    expect "Transfer complete"
    expect -re "\[0-9\]+ bytes received in \[0-9.e-\]+ seconds"
    expect "ftp> "
    if [check_file $tmppwd/copy] {
	pass $testname
    } else {
	fail $testname
    }

    set testname "big local get(v4)"
    catch "exec rm -f $tmppwd/copy"
    send "get bigftp-test copy\r"
    expect "Opening BINARY mode data connection for bigftp-test"
    expect "Transfer complete"
    expect -re "\[0-9\]+ bytes received in \[0-9.e-\]+ seconds"
    expect "ftp> "
    if [check_file $tmppwd/copy 1] {
	pass $testname
    } else {
	fail $testname
    }

    set testname "start encryption(v4)"
    send "private\r"
    expect "Data channel protection level set to private"
    expect "ftp> " {
	pass $testname
    }

    set testname "status(v4)"
    send "status\r"
    expect "Protection Level: private"
    expect "ftp> " {
	pass $testname
    }

    set testname "encrypted get(v4)"
    catch "exec rm -f $tmppwd/copy"
    send "get ftp-test copy\r"
    expect "Opening BINARY mode data connection for ftp-test"
    expect "Transfer complete"
    expect {
	-re "\[0-9\]+ bytes received in \[0-9.e-\]+ seconds" {}
	-re "krb_rd_priv failed for KERBEROS_V4" {
	    fail $testname
	    send "quit\r"
	    catch "expect_after"
	    return
	}
    }
    expect "ftp> "
    if [check_file $tmppwd/copy] {
	pass $testname
    } else {
	fail $testname
    }


    # Test a large file that will overflow PBSZ size
    set testname "big encrypted get(v4)"
    catch "exec rm -f $tmppwd/copy"
    send "get bigftp-test copy\r"
    expect "Opening BINARY mode data connection for bigftp-test"
    expect "Transfer complete"
    expect {
	-re "\[0-9\]+ bytes received in \[0-9.e+-\]+ seconds" {}
	-re "krb_rd_priv failed for KERBEROS_V4" {
	    fail $testname
	    send "quit\r"
	    catch "expect_after"
	    return
	}
    }
    expect "ftp> "
    if [check_file $tmppwd/copy 1] {
	pass $testname
    } else {
	fail $testname
    }

    set testname "close(v4)"
    send "close\r"
    expect "Goodbye."
    expect "ftp> "
    set status_list [wait -i $ftpd_spawn_id]
    verbose "wait -i $ftpd_spawn_id returned $status_list ($testname)"
    catch "close -i $ftpd_spawn_id"
    if { [lindex $status_list 2] != 0 || [lindex $status_list 3] != 0 } {
	send_log "exit status: $status_list\n"
	verbose "exit status: $status_list"
	fail $testname
    } else {
	pass $testname
	unset ftpd_pid
    }

    set testname "quit(v4)"
    send "quit\r"
    expect_after
    expect eof
    if [check_exit_status $testname] {
	pass $testname
    }

}

# The ftp client will look in $HOME/.netrc for the user name to use.
# To avoid confusing the testsuite, point $HOME at a directory where
# we know there is no .netrc file.
if [info exists env(HOME)] {
    set home $env(HOME)
} elseif [info exists home] {
    unset home
}
set env(HOME) $tmppwd

# Run the test.  Logging in sometimes takes a while, so increase the
# timeout.
set oldtimeout $timeout
set timeout 60
set status [catch v4ftp_test msg]
set timeout $oldtimeout

# Shut down the kerberos daemons and the ftp daemon.
stop_kerberos_daemons

stop_ftp_daemon

ftp_restore_env

# Reset $HOME, for safety in case we are going to run more tests.
if [info exists home] {
    set env(HOME) $home
} else {
    unset env(HOME)
}

if { $status != 0 } {
    perror "error in gssftp.exp: $msg"
}
