1# Basic expect script for Kerberos tests.
2# This is a DejaGnu test script.
3# Written by Ian Lance Taylor, Cygnus Support, <ian@cygnus.com>.
4# This script is automatically run by DejaGnu before running any of
5# the Kerberos test scripts.
6
7# This file provides several functions which deal with a local
8# Kerberos database.  We have to do this such that we don't interfere
9# with any existing Kerberos database.  We will create all the files
10# in the directory $tmppwd, which will have been created by the
11# testsuite default script.  We will use $REALMNAME as our Kerberos
12# realm name, defaulting to KRBTEST.COM.
13
14set timeout 100
15set stty_init {erase \^h kill \^u}
16set env(TERM) dumb
17
18set des3_krbtgt 0
19set tgt_support_desmd5 0
20set supported_enctypes "des-cbc-crc:normal"
21set kdc_supported_enctypes "des-cbc-crc:normal"
22
23# The names of the individual passes must be unique; lots of things
24# depend on it.  The PASSES variable may not contain comments; only
25# small pieces get evaluated, so comments will do strange things.
26
27# Most of the purpose of using multiple passes is to exercise the
28# dependency of various bugs on configuration file settings,
29# particularly with regards to encryption types.
30
31# The des.no-kdc-md5 pass will fail if the KDC does not constrain
32# session key enctypes to those in its permitted_enctypes list.  It
33# works by assuming enctype similarity, thus allowing the client to
34# request a des-cbc-md4 session key.  Since only des-cbc-crc is in the
35# KDC's permitted_enctypes list, the TGT will be unusable.
36
37# KLUDGE for tracking down leaking ptys
38if 0 {
39    rename spawn oldspawn
40    rename wait oldwait
41    proc spawn { args } {
42	upvar 1 spawn_id spawn_id
43	verbose "spawn: args=$args"
44	set pid [eval oldspawn $args]
45	verbose "spawn: pid=$pid spawn_id=$spawn_id"
46	return $pid
47    }
48    proc wait { args } {
49	upvar 1 spawn_id spawn_id
50	verbose "wait: args=$args"
51	set ret [eval oldwait $args]
52	verbose "wait: $ret"
53	return $ret
54    }
55}
56
57if { [string length $VALGRIND] } {
58    rename spawn valgrind_aux_spawn
59    proc spawn { args } {
60	global VALGRIND
61	upvar 1 spawn_id spawn_id
62	set newargs {}
63	set inflags 1
64	set eatnext 0
65	foreach arg $args {
66	    if { $arg == "-ignore" \
67		     || $arg == "-open" \
68		     || $arg == "-leaveopen" } {
69		lappend newargs $arg
70		set eatnext 1
71		continue
72	    }
73	    if [string match "-*" $arg] {
74		lappend newargs $arg
75		continue
76	    }
77	    if { $eatnext } {
78		set eatnext 0
79		lappend newargs $arg
80		continue
81	    }
82	    if { $inflags } {
83		set inflags 0
84		# Only run valgrind for local programs, not
85		# system ones.
86#&&![string match "/bin/sh" $arg] sh is used to start kadmind!
87		if [string match "/" [string index $arg 0]]&&![string match "/bin/ls" $arg]&&![regexp {/kshd$} $arg] {
88		    set newargs [concat $newargs $VALGRIND]
89		}
90	    }
91	    lappend newargs $arg
92	}
93	set pid [eval valgrind_aux_spawn $newargs]
94	return $pid
95    }
96}
97
98# The des.des3-tgt.no-kdc-des3 pass will fail if the KDC doesn't
99# constrain ticket key enctypes to those in permitted_enctypes.  It
100# does this by not putting des3 in the permitted_enctypes, while
101# creating a TGT princpal that has a des3 key as well as a des key.
102
103# XXX -- master_key_type is fragile w.r.t. permitted_enctypes; it is
104# possible to configure things such that you have a master_key_type
105# that is not permitted, and the error message used to be cryptic.
106
107set passes {
108    {
109	des
110	mode=udp
111	des3_krbtgt=0
112	{supported_enctypes=des-cbc-crc:normal}
113	{kdc_supported_enctypes=des-cbc-crc:normal}
114	{dummy=[verbose -log "DES TGT, DES enctype"]}
115    }
116    {
117	des.des3tgt
118	mode=udp
119	des3_krbtgt=1
120	{supported_enctypes=des-cbc-crc:normal}
121	{kdc_supported_enctypes=des3-cbc-sha1:normal des-cbc-crc:normal}
122	{dummy=[verbose -log "DES3 TGT, DES enctype"]}
123    }
124    {
125	des3
126	mode=udp
127	des3_krbtgt=1
128	{supported_enctypes=des3-cbc-sha1:normal des-cbc-crc:normal}
129	{kdc_supported_enctypes=des3-cbc-sha1:normal des-cbc-crc:normal}
130	{dummy=[verbose -log "DES3 TGT, DES3 + DES enctypes"]}
131    }
132    {
133	aes
134	mode=udp
135	des3_krbtgt=0
136	{supported_enctypes=aes256-cts-hmac-sha1-96:normal des-cbc-crc:normal}
137	{kdc_supported_enctypes=aes256-cts-hmac-sha1-96:normal des-cbc-crc:normal}
138	{permitted_enctypes(kdc)=aes256-cts-hmac-sha1-96 des-cbc-crc}
139	{permitted_enctypes(client)=aes256-cts-hmac-sha1-96 des-cbc-crc}
140	{permitted_enctypes(server)=aes256-cts-hmac-sha1-96 des-cbc-crc}
141	{master_key_type=aes256-cts-hmac-sha1-96}
142	{dummy=[verbose -log "AES + DES enctypes"]}
143    }
144    {
145	aes-des3
146	mode=udp
147	des3_krbtgt=0
148	{supported_enctypes=aes256-cts-hmac-sha1-96:normal des3-cbc-sha1:normal des-cbc-crc:normal}
149	{kdc_supported_enctypes=aes256-cts-hmac-sha1-96:normal des3-cbc-sha1:normal des-cbc-crc:normal}
150	{permitted_enctypes(kdc)=aes256-cts-hmac-sha1-96 des3-cbc-sha1 des-cbc-crc}
151	{permitted_enctypes(client)=aes256-cts-hmac-sha1-96 des3-cbc-sha1 des-cbc-crc}
152	{permitted_enctypes(server)=aes256-cts-hmac-sha1-96 des3-cbc-sha1 des-cbc-crc}
153	{master_key_type=aes256-cts-hmac-sha1-96}
154	{dummy=[verbose -log "AES + DES enctypes"]}
155    }
156    {
157	des3-aes
158	mode=udp
159	des3_krbtgt=1
160	{supported_enctypes=aes256-cts-hmac-sha1-96:normal des3-cbc-sha1:normal des-cbc-crc:normal}
161	{kdc_supported_enctypes=aes256-cts-hmac-sha1-96:normal des3-cbc-sha1:normal des-cbc-crc:normal}
162	{permitted_enctypes(kdc)=aes256-cts-hmac-sha1-96 des3-cbc-sha1 des-cbc-crc}
163	{permitted_enctypes(client)=aes256-cts-hmac-sha1-96 des3-cbc-sha1 des-cbc-crc}
164	{permitted_enctypes(server)=aes256-cts-hmac-sha1-96 des3-cbc-sha1 des-cbc-crc}
165	{master_key_type=aes256-cts-hmac-sha1-96}
166	{dummy=[verbose -log "AES + DES enctypes, DES3 TGT"]}
167    }
168    {
169	des-v4
170	mode=udp
171	des3_krbtgt=0
172	{supported_enctypes=des-cbc-crc:v4}
173	{kdc_supported_enctypes=des-cbc-crc:v4}
174	{default_tkt_enctypes(client)=des-cbc-crc}
175	{dummy=[verbose -log "DES TGT, DES-CRC enctype, V4 salt"]}
176    }
177    {
178	des-md5-v4
179	mode=udp
180	des3_krbtgt=0
181	{supported_enctypes=des-cbc-md5:v4 des-cbc-crc:v4}
182	{kdc_supported_enctypes=des-cbc-md5:v4 des-cbc-crc:v4}
183	{default_tkt_enctypes(client)=des-cbc-md5 des-cbc-crc}
184	{dummy=[verbose -log "DES TGT, DES-MD5 and -CRC enctypes, V4 salt"]}
185    }
186    {
187	all-des-des3-enctypes
188	mode=udp
189	des3_krbtgt=1
190	{supported_enctypes=des3-cbc-sha1:normal des-cbc-crc:normal \
191		des-cbc-md5:normal des-cbc-crc:v4 des-cbc-md5:norealm \
192		des-cbc-md4:normal}
193	{kdc_supported_enctypes=des3-cbc-sha1:normal des-cbc-crc:normal \
194		des-cbc-md5:normal des-cbc-crc:v4 des-cbc-md5:norealm \
195		des-cbc-md4:normal}
196	{dummy=[verbose -log "DES3 TGT, many DES3 + DES enctypes"]}
197    }
198    {
199	des.no-kdc-md5
200	mode=udp
201	des3_krbtgt=0
202	tgt_support_desmd5=0
203	{permitted_enctypes(kdc)=des-cbc-crc}
204	{default_tgs_enctypes(client)=des-cbc-md5 des-cbc-md4 des-cbc-crc}
205	{default_tkt_enctypes(client)=des-cbc-md5 des-cbc-md4 des-cbc-crc}
206	{supported_enctypes=des-cbc-crc:normal}
207	{kdc_supported_enctypes=des-cbc-crc:normal}
208	{master_key_type=des-cbc-crc}
209	{dummy=[verbose -log \
210		"DES TGT, KDC permitting only des-cbc-crc"]}
211    }
212    {
213	des.des3-tgt.no-kdc-des3
214	mode=udp
215	tgt_support_desmd5=0
216	{permitted_enctypes(kdc)=des-cbc-crc}
217	{default_tgs_enctypes(client)=des-cbc-crc}
218	{default_tkt_enctypes(client)=des-cbc-crc}
219	{supported_enctypes=des3-cbc-sha1:normal des-cbc-crc:normal}
220	{kdc_supported_enctypes=des3-cbc-sha1:normal des-cbc-crc:normal}
221	{master_key_type=des-cbc-crc}
222	{dummy=[verbose -log \
223		"DES3 TGT, KDC permitting only des-cbc-crc"]}
224    }
225}
226
227# des.md5-tgt is set as unused, since it won't trigger the error case
228# if SUPPORT_DESMD5 isn't honored.
229
230# The des.md5-tgt pass will fail if enctype similarity is inconsisent;
231# between 1.0.x and 1.1, the decrypt functions became more strict
232# about matching enctypes, while the KDB retrieval functions didn't
233# coerce the enctype to match what was requested.  It works by setting
234# SUPPORT_DESMD5 on the TGT principal, forcing an enctype of
235# des-cbc-md5 on the TGT key.  Since the database only contains a
236# des-cbc-crc key, the decrypt will fail if enctypes are not coerced.
237
238# des.no-kdc-md5.client-md4-skey is retained in unsed_passes, even
239# though des.no-kdc-md5 is roughly equivalent, since the associated
240# comment needs additional investigation at some point re the kadmin
241# client.
242
243# The des.no-kdc-md5.client-md4-skey will fail on TGS requests due to
244# the KDC issuing session keys that it won't accept.  It will also
245# fail for a kadmin client, but for different reasons, since the kadm5
246# library does some curious filtering of enctypes, and also uses
247# get_in_tkt() rather than get_init_creds(); the former does an
248# intersection of the enctypes provided by the caller and those listed
249# in the config file!
250
251set unused_passes {
252    {
253	des.md5-tgt
254	des3_krbtgt=0
255	tgt_support_desmd5=1
256	supported_enctypes=des-cbc-crc:normal
257	kdc_supported_enctypes=des-cbc-crc:normal
258	{permitted_enctypes(kdc)=des-cbc-md5 des-cbc-md4 des-cbc-crc}
259	{permitted_enctypes(client)=des-cbc-md5 des-cbc-md4 des-cbc-crc}
260	{dummy=[verbose -log "DES TGT, SUPPORTS_DESMD5"]}
261    }
262    {
263	des.md5-tgt.no-kdc-md5
264	des3_krbtgt=0
265	tgt_support_desmd5=1
266	{permitted_enctypes(kdc)=des-cbc-crc}
267	{default_tgs_enctypes(client)=des-cbc-crc}
268	{default_tkt_enctypes(client)=des-cbc-crc}
269	{supported_enctypes=des-cbc-crc:normal}
270	{kdc_supported_enctypes=des-cbc-crc:normal}
271	{master_key_type=des-cbc-crc}
272	{dummy=[verbose -log \
273		"DES TGT, SUPPORTS_DESMD5, KDC permitting only des-cbc-crc"]}
274    }
275    {
276	des.no-kdc-md5.client-md4-skey
277	des3_krbtgt=0
278	{permitted_enctypes(kdc)=des-cbc-crc}
279	{permitted_enctypes(client)=des-cbc-crc des-cbc-md4}
280	{default_tgs_enctypes(client)=des-cbc-crc des-cbc-md4}
281	{default_tkt_enctypes(client)=des-cbc-md4}
282	{supported_enctypes=des-cbc-crc:normal}
283	{kdc_supported_enctypes=des-cbc-crc:normal}
284	{dummy=[verbose -log \
285		"DES TGT, DES enctype, KDC permitting only des-cbc-crc, client requests des-cbc-md4 session key"]}
286    }
287    {
288	all-enctypes
289	des3_krbtgt=1
290	{supported_enctypes=\
291	aes256-cts-hmac-sha1-96:normal aes256-cts-hmac-sha1-96:norealm \
292	aes128-cts-hmac-sha1-96:normal aes128-cts-hmac-sha1-96:norealm \
293	des3-cbc-sha1:normal des3-cbc-sha1:none \
294	des-cbc-md5:normal des-cbc-md4:normal des-cbc-crc:normal \
295	des-cbc-md5:v4 des-cbc-md4:v4 des-cbc-crc:v4 \
296	}
297	{kdc_supported_enctypes=\
298	des3-cbc-sha1:normal des3-cbc-sha1:none \
299	des-cbc-md5:normal des-cbc-md4:normal des-cbc-crc:normal \
300	des-cbc-md5:v4 des-cbc-md4:v4 des-cbc-crc:v4 \
301	}
302	{dummy=[verbose -log "DES3 TGT, default enctypes"]}
303    }
304    {
305	aes-tcp
306	mode=tcp
307	des3_krbtgt=0
308	{supported_enctypes=aes256-cts-hmac-sha1-96:normal}
309	{kdc_supported_enctypes=aes256-cts-hmac-sha1-96:normal}
310	{permitted_enctypes(kdc)=aes256-cts-hmac-sha1-96}
311	{permitted_enctypes(client)=aes256-cts-hmac-sha1-96}
312	{permitted_enctypes(server)=aes256-cts-hmac-sha1-96}
313	{master_key_type=aes256-cts-hmac-sha1-96}
314	{dummy=[verbose -log "AES via TCP"]}
315    }
316}
317#	{supported_enctypes=des-cbc-md5:normal des-cbc-crc:normal twofish256-hmac-sha1:normal }
318#	{kdc_supported_enctypes= des-cbc-md5:normal des-cbc-crc:normal twofish256-hmac-sha1:normal}
319
320# This shouldn't be necessary on dejagnu-1.4 and later, but 1.3 seems
321# to need it because its runtest.exp doesn't deal with PASS at all.
322if [info exists PASS] {
323    foreach pass $passes {
324	if { [lsearch -exact $PASS [lindex $pass 0]] >= 0 } {
325	    lappend MULTIPASS $pass
326	}
327    }
328} else {
329    set MULTIPASS $passes
330}
331
332set last_passname_conf ""
333set last_passname_db ""
334
335# We do everything in a temporary directory.
336if ![info exists TMPDIR] {
337    set tmppwd "[pwd]/tmpdir"
338    if ![file isdirectory $tmppwd] {
339	catch "exec mkdir $tmppwd" status
340    }
341} else {
342    set tmppwd $TMPDIR
343}
344verbose "tmppwd=$tmppwd"
345
346# On Ultrix, use /bin/sh5 in preference to /bin/sh.
347if ![info exists BINSH] {
348    if [file exists /bin/sh5] {
349	set BINSH /bin/sh5
350    } else {
351	set BINSH /bin/sh
352    }
353}
354
355# For security, we must not use generally known passwords.  This is
356# because some of the tests may be run as root.  If the passwords were
357# generally know, then somebody could work out the appropriate
358# Kerberos ticket to use, and come in when, say, the telnetd daemon
359# was being tested by root.  The window for doing this is very very
360# small, so the password does not have to be perfect, it just can't be
361# constant.
362if ![info exists KEY] {
363    catch {exec $BINSH -c "echo $$"} KEY
364    verbose "KEY is $KEY"
365    set keyfile [open $tmppwd/KEY w]
366    puts $keyfile "$KEY"
367    close $keyfile
368}
369
370# Clear away any files left over from a previous run.
371# We can't use them now because we don't know the right KEY.
372# krb5.conf might change if running tests on another host
373file delete $tmppwd/krb5.conf $tmppwd/kdc.conf $tmppwd/slave.conf \
374    $tmppwd/krb5.client.conf $tmppwd/krb5.server.conf \
375    $tmppwd/krb5.kdc.conf $tmppwd/krb5.slave.conf
376
377proc delete_db {} {
378    global tmppwd
379    # Master and slave db files
380    file delete $tmppwd/kdc-db $tmppwd/kdc-db.ok $tmppwd/kdc-db.kadm5 \
381	$tmppwd/kdc-db.kadm5.lock \
382	$tmppwd/kdc-db.ulog \
383	$tmppwd/slave-db $tmppwd/slave-db.ok $tmppwd/slave-db.kadm5 $tmppwd/slave-db.kadm5.lock \
384	$tmppwd/slave-db~ $tmppwd/slave-db~.ok $tmppwd/slave-db~.kadm5 $tmppwd/slave-db~.kadm5.lock
385    # Creating a new database means we need a new srvtab.
386    file delete $tmppwd/srvtab $tmppwd/cpw_srvtab
387}
388
389delete_db
390
391# Put the installed kerberos directories on PATH.
392# This needs to be fixed for V5.
393# set env(PATH) $env(PATH):/usr/kerberos/bin:/usr/kerberos/etc
394# verbose "PATH=$env(PATH)"
395
396# Some of the tests expect $env(USER) to be set.
397if ![info exists env(USER)] {
398    if [info exists env(LOGNAME)] {
399	set env(USER) $env(LOGNAME)
400    } else {
401	if [info exists logname] {
402	    set env(USER) $logname
403	} else {
404	    catch "exec whoami" env(USER)
405	}
406    }
407}
408
409# set the realm. The user can override this on the runtest line.
410if ![info exists REALMNAME] {
411    set REALMNAME "KRBTEST.COM"
412}
413verbose "Test realm is $REALMNAME"
414
415# Find some programs we need.  We use the binaries from the build tree
416# if they exist.  If they do not, then they must be in PATH.  We
417# expect $objdir to be ...tests/dejagnu.
418
419foreach i {
420    {KDB5_UTIL $KRB5_SBINDIR/kdb5_util}
421    {KRB5KDC $KRB5_SBINDIR/krb5kdc}
422    {KADMIND $KRB5_SBINDIR/kadmind}
423    {KADMIN $KRB5_BINDIR/kadmin}
424    {KADMIN_LOCAL $KRB5_SBINDIR/kadmin.local}
425    {KINIT $KRB5_BINDIR/kinit}
426    {KTUTIL $KRB5_BINDIR/ktutil}
427    {KLIST $KRB5_BINDIR/klist}
428    {KDESTROY $KRB5_BINDIR/kdestroy}
429    {RESOLVE $objdir/resolve/resolve}
430    {T_INETD $objdir/t_inetd}
431} {
432    set varname [lindex $i 0]
433    if ![info exists $varname] {
434	eval set varval [lindex $i 1]
435	set varval [findfile $varval]
436	set $varname $varval
437	verbose "$varname=$varval"
438    } {
439	eval set varval \$$varname
440	verbose "$varname already set to $varval"
441    }
442}
443
444if ![info exists RLOGIN] {
445    set RLOGIN rlogin
446}
447
448if ![info exists RLOGIN_FLAGS] {
449    set RLOGIN_FLAGS "-x"
450}
451
452# We use a couple of variables to hold shell prompts which may be
453# overridden by the user.
454
455if ![info exists ROOT_PROMPT] {
456    set ROOT_PROMPT "(%|#|>|\\$) $"
457}
458
459if ![info exists SHELL_PROMPT] {
460    set SHELL_PROMPT "(%|#|>|\\$) $"
461}
462
463verbose "setting up onexit handler (old handler=[exit -onexit])"
464exit -onexit [concat {
465    verbose "calling stop_kerberos_daemons (onexit handler)"
466    stop_kerberos_daemons;
467} [exit -onexit]]
468
469# run_once
470
471# Many tests are independent of the actual enctypes used, which is
472# what our passes are (currently) all about.  Use this to prevent
473# multiple invocations.  If a test depends on, say, the master key
474# type but nothing else, you could also use the master key type in the
475# tag name, and avoid redundant tests in additional passes using the
476# same master key type.
477
478proc run_once { tag body } {
479    global run_once_tags
480    if ![info exists run_once_tags($tag)] {
481	set run_once_tags($tag) 1
482	uplevel 1 $body
483    }
484}
485
486# check_k5login
487
488# Most of the tests won't work if the user has a .k5login file, unless
489# the user's name appears with $REALMNAME in .k5login
490
491# This procedure returns 1 if the .k5login file appears to be OK, 0
492# otherwise.  This check is not foolproof.
493
494# Note that this previously checked for a username with no realm; this
495# works for krb4's kuserok() but not for krb5_kuserok(), due to some
496# implementation details.  *sigh*
497
498proc check_k5login { testname } {
499    global env
500    global REALMNAME
501
502    if {![file exists ~/.k5login]} {
503	if {$env(USER) == "root"} {
504	    return 0
505	} else {
506	    return 1
507	}
508    }
509
510    verbose "looking for $env(USER)@$REALMNAME in ~/.k5login" 2
511    set file [open ~/.k5login r]
512    while { [gets $file principal] != -1 } {
513	verbose " found $principal" 2
514	if { $principal == "$env(USER)@$REALMNAME" } {
515	    close $file
516	    return 1
517	}
518    }
519    close $file
520
521    note "$testname test requires that your name appear in your ~/.k5login"
522    note "file in the form $env(USER)@$REALMNAME"
523    unsupported "$testname"
524
525    return 0
526}
527
528proc check_klogin { testname } {
529    global env
530    global REALMNAME
531
532    if {![file exists ~/.klogin]} {
533	if {$env(USER) == "root"} {
534	    return 0
535	} else {
536	    return 1
537	}
538    }
539
540    verbose "looking for $env(USER) in ~/.klogin" 2
541    set file [open ~/.klogin r]
542    while { [gets $file principal] != -1 } {
543	verbose " found $principal" 2
544	if { $principal == "$env(USER)" \
545		|| $principal == "$env(USER)@$REALMNAME" } {
546	    close $file
547	    return 1
548	}
549    }
550    close $file
551
552    note "$testname test requires that your name appear in your ~/.klogin"
553    note "file without a realm."
554    unsupported "$testname"
555
556    return 0
557}
558
559# check_exit_status
560# Check the exit status of a spawned program (using the caller's value
561# of spawn_id).  Returns 1 if the program succeeded, 0 if it failed.
562
563proc check_exit_status { testname } {
564    upvar 1 spawn_id spawn_id
565
566    verbose "about to wait ($testname)"
567    set status_list [wait -i $spawn_id]
568    verbose "wait -i $spawn_id returned $status_list ($testname)"
569    catch "close -i $spawn_id"
570    if { [lindex $status_list 2] != 0 || [lindex $status_list 3] != 0 } {
571	verbose -log "exit status: $status_list"
572	fail "$testname"
573	return 0
574    } else {
575	return 1
576    }
577}
578
579#
580# ENVSTACK
581#
582
583# These procedures implement an environment variable stack.  They use
584# the global variable $envvars_tosave for the purpose of identifying
585# which environment variables to save.  They also track which ones are
586# unset at any particular point.  The stack pointer is $envstackp,
587# which is an integer.  The arrays $envstack$envstackp and
588# $unenvstack$envstackp store respectively the set of old environment
589# variables/values pushed onto the stack and the set of old unset
590# environment variables for a given value of $envstackp.
591
592# Changing the value of $envvars_tosave after performing the first
593# push operation may result in strangeness.
594
595#
596# envstack_push
597#
598# Push set of current environment variables.
599#
600proc envstack_push { } {
601    global env
602    global envvars_tosave
603    global envstackp
604    global envstack$envstackp
605    global unenvstack$envstackp
606
607    verbose "envstack_push: starting, sp=$envstackp"
608    foreach i $envvars_tosave {
609	if [info exists env($i)] {
610	    verbose "envstack_push: saving $i=$env($i)"
611	    set envstack${envstackp}($i) $env($i)
612	} {
613	    verbose "envstack_push: marking $i as unset"
614	    set unenvstack${envstackp}($i) unset
615	}
616    }
617    incr envstackp
618    verbose "envstack_push: exiting, sp=$envstackp"
619}
620
621#
622# envstack_pop
623#
624# Pop set of current environment variables.
625#
626proc envstack_pop { } {
627    global env
628    global envstackp
629
630    verbose "envstack_pop: starting, sp=$envstackp"
631    incr envstackp -1
632    global envstack$envstackp	# YUCK!!! no obvious better way though...
633    global unenvstack$envstackp
634    if {$envstackp < 0} {
635	perror "envstack_pop: stack underflow!"
636	return
637    }
638    if [info exists envstack$envstackp] {
639	foreach i [array names envstack$envstackp] {
640	    if [info exists env($i)] {
641		verbose "envstack_pop: $i was $env($i)"
642	    }
643	    eval set env($i) \$envstack${envstackp}($i)
644	    verbose "envstack_pop: restored $i to $env($i)"
645	}
646	unset envstack$envstackp
647    }
648    if [info exists unenvstack$envstackp] {
649	foreach i [array names unenvstack$envstackp] {
650	    if [info exists env($i)] {
651		verbose "envstack_pop: $i was $env($i)"
652		unset env($i)
653		verbose "envstack_pop: $i unset"
654	    } {
655		verbose "envstack_pop: ignoring already unset $i"
656	    }
657	}
658	unset unenvstack$envstackp
659    }
660    verbose "envstack_pop: exiting, sp=$envstackp"
661}
662
663#
664# Initialize the envstack
665#
666set envvars_tosave {
667    KRB5_CONFIG KRB5CCNAME KRBTKFILE KRB5RCACHEDIR
668    KERBEROS_SERVER KRB5_KDC_PROFILE
669}
670set envstackp 0
671envstack_push
672
673# get_hostname
674# This procedure will get the local hostname.  It sets the global
675# variables hostname (the full name) and localhostname (the first part
676# of the name).  Returns 1 on success, 0 on failure.
677
678proc get_hostname { } {
679    global RESOLVE
680    global hostname
681    global localhostname
682    global domain
683    global tmppwd
684
685    if {[info exists hostname] && [info exists localhostname]} {
686	return 1
687    }
688
689    envstack_push
690    catch "exec $RESOLVE -q >$tmppwd/hostname" exec_output
691    envstack_pop
692    if ![string match "" $exec_output] {
693	verbose -log $exec_output
694	perror "can't get hostname"
695	return 0
696    }
697    set file [open $tmppwd/hostname r]
698    if { [ gets $file hostname ] == -1 } {
699	perror "no output from hostname"
700	return 0
701    }
702    close $file
703    file delete $tmppwd/hostname
704    regexp "^(\[^.\]*)\\.(.*)$" $hostname foo localhostname domain
705
706    set hostname [string tolower $hostname]
707    set localhostname [string tolower $localhostname]
708    set domain [string tolower $domain]
709    verbose "hostname: $hostname; localhostname: $localhostname; domain $domain"
710
711    return 1
712}
713
714# modify_principal name options...
715
716proc modify_principal { name args } {
717    global KADMIN_LOCAL
718    global REALMNAME
719
720    envstack_push
721    setup_kerberos_env kdc
722    spawn $KADMIN_LOCAL -r $REALMNAME
723    envstack_pop
724    expect_after {
725	eof {
726	    fail "modprinc (kadmin.local)"
727	    return 0
728	}
729	timeout {
730	    fail "modprinc (kadmin.local)"
731	    return 0
732	}
733    }
734    expect "kadmin.local: "
735    send "modprinc $args $name\r"
736    expect -re "modprinc \[^\n\r\]* $name"
737    expect -re "Principal .* modified."
738    send "quit\r"
739    expect eof
740    catch expect_after
741    if ![check_exit_status "kadmin.local modprinc"] {
742	perror "kadmin.local modprinc exited abnormally"
743    }
744    return 1
745}
746
747# kdc listens on +0..+3, depending whether we're testing reachable or not
748# client tries +1 and +6
749# kadmind +4
750# kpasswd +5
751# (nothing) +6
752# application servers (krlogind, telnetd, krshd, ftpd, etc) +8
753# iprop +9 (if enabled)
754# kpropd +10
755if [info exists PORTBASE] {
756    set portbase $PORTBASE
757} else {
758    set portbase 3085
759}
760
761set ulog 0
762
763# setup_kerberos_files
764# This procedure will create some Kerberos files which must be created
765# manually before trying to run any Kerberos programs.  Returns 1 on
766# success, 0 on failure.
767
768proc setup_kerberos_files { } {
769    global REALMNAME
770    global hostname
771    global domain
772    global tmppwd
773    global supported_enctypes
774    global kdc_supported_enctypes
775    global last_passname_conf
776    global multipass_name
777    global master_key_type
778    global mode
779    global portbase
780    global ulog
781
782    if ![get_hostname] {
783	return 0
784    }
785
786    setup_krb5_conf client
787    setup_krb5_conf server
788    setup_krb5_conf kdc
789    setup_krb5_conf slave
790
791    # Create a kdc.conf file.
792    if { ![file exists $tmppwd/kdc.conf] \
793	    || $last_passname_conf != $multipass_name } {
794	if ![info exists master_key_type] {
795	    set master_key_type des-cbc-md5
796	}
797	set conffile [open $tmppwd/kdc.conf w]
798	puts $conffile "\[kdcdefaults\]"
799	puts $conffile "	kdc_ports = $portbase,[expr 1 + $portbase],[expr 2 + $portbase]"
800	puts $conffile "	kdc_tcp_ports = $portbase,[expr 1 + $portbase],[expr 2 + $portbase]"
801	puts $conffile ""
802	puts $conffile "\[realms\]"
803	puts $conffile "	$REALMNAME = \{"
804#	puts $conffile "		database_name = $tmppwd/db"
805	puts $conffile "		admin_database_name = $tmppwd/adb"
806	puts $conffile "		admin_database_lockfile = $tmppwd/adb.lock"
807	# Testing with a colon in the name exercises default handling
808	# for pathnames.
809	puts $conffile "		key_stash_file = $tmppwd/stash:foo"
810	puts $conffile "		acl_file = $tmppwd/acl"
811	puts $conffile "		kadmind_port = [expr 4 + $portbase]"
812	puts $conffile "		kpasswd_port = [expr 5 + $portbase]"
813	puts $conffile "		max_life = 1:00:00"
814	puts $conffile "		max_renewable_life = 3:00:00"
815	puts $conffile "		master_key_type = $master_key_type"
816	puts $conffile "		master_key_name = master/key"
817	puts $conffile "		supported_enctypes = $supported_enctypes"
818	puts $conffile "		kdc_supported_enctypes = $kdc_supported_enctypes"
819	if { $mode == "tcp" } {
820	    puts $conffile "		kdc_ports = [expr 3 + $portbase]"
821	    puts $conffile "		kdc_tcp_ports = [expr 1 + $portbase],[expr 3 + $portbase]"
822	} else {
823	    puts $conffile "		kdc_ports = [expr 1 + $portbase]"
824	    puts $conffile "		kdc_tcp_ports = [expr 3 + $portbase]"
825	}
826	puts $conffile "		default_principal_expiration = 2037.12.31.23.59.59"
827	puts $conffile "		default_principal_flags = -postdateable forwardable"
828	puts $conffile "		dict_file = $tmppwd/dictfile"
829	if { $ulog != 0 } {
830	    puts $conffile "		iprop_enable = true"
831	    puts $conffile "		iprop_port = [expr 9 + $portbase]"
832	    puts $conffile "		iprop_logfile = $tmppwd/db.ulog"
833	} else {
834	    puts $conffile "# no ulog"
835	}
836	puts $conffile "	\}"
837	puts $conffile ""
838	close $conffile
839    }
840
841    # Create a config file for the slave KDC (kpropd only, no normal
842    # KDC processes).
843    if { ![file exists $tmppwd/slave.conf] \
844	    || $last_passname_conf != $multipass_name } {
845	if ![info exists master_key_type] {
846	    set master_key_type des-cbc-md5
847	}
848	set conffile [open $tmppwd/slave.conf w]
849	puts $conffile "\[kdcdefaults\]"
850	puts $conffile "	kdc_ports = $portbase,[expr 1 + $portbase],[expr 2 + $portbase]"
851	puts $conffile "	kdc_tcp_ports = $portbase,[expr 1 + $portbase],[expr 2 + $portbase]"
852	puts $conffile ""
853	puts $conffile "\[realms\]"
854	puts $conffile "	$REALMNAME = \{"
855#	puts $conffile "		database_name = $tmppwd/slave-db"
856	puts $conffile "		admin_database_name = $tmppwd/slave-adb"
857	puts $conffile "		admin_database_lockfile = $tmppwd/slave-adb.lock"
858	# Testing with a colon in the name exercises default handling
859	# for pathnames.
860	puts $conffile "		key_stash_file = $tmppwd/slave-stash"
861	puts $conffile "		acl_file = $tmppwd/slave-acl"
862	puts $conffile "		kadmind_port = [expr 4 + $portbase]"
863	puts $conffile "		kpasswd_port = [expr 5 + $portbase]"
864	puts $conffile "		max_life = 1:00:00"
865	puts $conffile "		max_renewable_life = 3:00:00"
866	puts $conffile "		master_key_type = $master_key_type"
867	puts $conffile "		master_key_name = master/key"
868	puts $conffile "		supported_enctypes = $supported_enctypes"
869	puts $conffile "		kdc_supported_enctypes = $kdc_supported_enctypes"
870	if { $mode == "tcp" } {
871	    puts $conffile "		kdc_ports = [expr 3 + $portbase]"
872	    puts $conffile "		kdc_tcp_ports = [expr 1 + $portbase],[expr 3 + $portbase]"
873	} else {
874	    puts $conffile "		kdc_ports = [expr 1 + $portbase]"
875	    puts $conffile "		kdc_tcp_ports = [expr 3 + $portbase]"
876	}
877	puts $conffile "		default_principal_expiration = 2037.12.31.23.59.59"
878	puts $conffile "		default_principal_flags = -postdateable forwardable"
879	puts $conffile "		dict_file = $tmppwd/dictfile"
880	if { $ulog != 0 } {
881	    puts $conffile "		iprop_enable = true"
882	    puts $conffile "		iprop_port = [expr 9 + $portbase]"
883	    puts $conffile "		iprop_logfile = $tmppwd/slave-db.ulog"
884	} else {
885	    puts $conffile "# no ulog"
886	}
887	puts $conffile "	\}"
888	puts $conffile ""
889	close $conffile
890    }
891
892    # Create ACL file.
893    if ![file exists $tmppwd/acl] {
894	set aclfile [open $tmppwd/acl w]
895	puts $aclfile "krbtest/admin@$REALMNAME *"
896	puts $aclfile "kiprop/$hostname@$REALMNAME p"
897	close $aclfile
898    }
899
900    # Create dictfile file.
901    if ![file exists $tmppwd/dictfile] {
902	set dictfile [open $tmppwd/dictfile w]
903	puts $dictfile "weak_password"
904	close $dictfile
905    }
906
907    set last_passname_conf $multipass_name
908    return 1
909}
910
911proc reset_kerberos_files { } {
912    global tmppwd
913    file delete $tmppwd/kdc.conf $tmppwd/slave.conf $tmppwd/krb5.client.conf \
914	$tmppwd/krb5.server.conf $tmppwd/krb5.kdc.conf
915    setup_kerberos_files
916}
917
918proc setup_krb5_conf { {type client} } {
919    global tmppwd
920    global hostname
921    global domain
922    global REALMNAME
923    global last_passname_conf
924    global multipass_name
925    global default_tgs_enctypes
926    global default_tkt_enctypes
927    global permitted_enctypes
928    global mode
929    global portbase
930
931    # Create a krb5.conf file.
932    if { ![file exists $tmppwd/krb5.$type.conf] \
933	    || $last_passname_conf != $multipass_name } {
934	set conffile [open $tmppwd/krb5.$type.conf w]
935	puts $conffile "\[libdefaults\]"
936	puts $conffile "	default_realm = $REALMNAME"
937	puts $conffile "	dns_lookup_kdc = false"
938	puts $conffile "	allow_weak_crypto = true"
939	if [info exists default_tgs_enctypes($type)] {
940	    puts $conffile \
941		    "	default_tgs_enctypes = $default_tgs_enctypes($type)"
942	}
943	if [info exists default_tkt_enctypes($type)] {
944	    puts $conffile \
945		    "	default_tkt_enctypes = $default_tkt_enctypes($type)"
946	}
947	if [info exists permitted_enctypes($type)] {
948	    puts $conffile \
949		    "	permitted_enctypes = $permitted_enctypes($type)"
950	}
951	if { $mode == "tcp" } {
952	    puts $conffile "	udp_preference_limit = 1"
953	}
954	puts $conffile ""
955	puts $conffile "\[realms\]"
956	puts $conffile "	$REALMNAME = \{"
957	# There's probably nothing listening here.  It would be a good
958	# test for the handling of a non-responsive KDC address.  However,
959	# on some systems, like Tru64, we often wind up with the client's
960	# socket bound to this address, causing our request to appear in
961	# our incoming queue as if it were a response, which causes test
962	# failures.  If we were running the client and KDC on different
963	# hosts, this would be okay....
964	#puts $conffile "		kdc = $hostname:[expr 6 + $portbase]"
965	puts $conffile "		kdc = $hostname:[expr 1 + $portbase]"
966	puts $conffile "		admin_server = $hostname:[expr 4 + $portbase]"
967	puts $conffile "		kpasswd_server = $hostname:[expr 5 + $portbase]"
968	puts $conffile "		default_domain = $domain"
969	puts $conffile "		database_module = foo_db2"
970	puts $conffile "	\}"
971	puts $conffile ""
972	puts $conffile "\[domain_realm\]"
973	puts $conffile "	.$domain = $REALMNAME"
974	puts $conffile "	$domain = $REALMNAME"
975	puts $conffile ""
976	puts $conffile "\[logging\]"
977	puts $conffile "	admin_server = FILE:$tmppwd/kadmind5.log"
978	puts $conffile "	kdc = FILE:$tmppwd/kdc.log"
979	puts $conffile "	default = FILE:$tmppwd/others.log"
980	puts $conffile ""
981	puts $conffile "\[dbmodules\]"
982	puts $conffile "	foo_db2 = {"
983	puts $conffile "		db_library = db2"
984	puts $conffile "		database_name = $tmppwd/$type-db"
985	puts $conffile "	}"
986	close $conffile
987    }
988}
989
990# Save the original values of the environment variables we are going
991# to muck with.
992
993# XXX deal with envstack later.
994
995if [info exists env(KRB5_CONFIG)] {
996    set orig_krb5_conf $env(KRB5_CONFIG)
997} else {
998    catch "unset orig_krb5_config"
999}
1000
1001if [info exists env(KRB5CCNAME)] {
1002    set orig_krb5ccname $env(KRB5CCNAME)
1003} else {
1004    catch "unset orig_krb5ccname"
1005}
1006
1007if [ info exists env(KRB5RCACHEDIR)] {
1008    set orig_krb5rcachedir $env(KRB5RCACHEDIR)
1009} else {
1010    catch "unset orig_krb5rcachedir"
1011}
1012
1013if [ info exists env(KERBEROS_SERVER)] {
1014    set orig_kerberos_server $env(KERBEROS_SERVER)
1015} else {
1016    catch "unset orig_kerberos_server"
1017}
1018
1019# setup_kerberos_env
1020# Set the environment variables needed to run Kerberos programs.
1021
1022proc setup_kerberos_env { {type client} } {
1023    global REALMNAME
1024    global env
1025    global tmppwd
1026    global hostname
1027    global portbase
1028
1029    # Set the environment variable KRB5_CONFIG to point to our krb5.conf file.
1030    # All the Kerberos tools check KRB5_CONFIG.
1031    # Actually, V5 doesn't currently use this.
1032    set env(KRB5_CONFIG) $tmppwd/krb5.$type.conf
1033    verbose "KRB5_CONFIG=$env(KRB5_CONFIG)"
1034
1035    # Direct the Kerberos programs at a local ticket file.
1036    set env(KRB5CCNAME) $tmppwd/tkt
1037    verbose "KRB5CCNAME=$env(KRB5CCNAME)"
1038
1039    # Direct the Kerberos server at a cache file stored in the
1040    # temporary directory.
1041    set env(KRB5RCACHEDIR) $tmppwd
1042    verbose "KRB5RCACHEDIR=$env(KRB5RCACHEDIR)"
1043
1044    # Tell the Kerberos tools how to contact the $REALMNAME server.
1045    set env(KERBEROS_SERVER) "$REALMNAME:$hostname:[expr 1 + $portbase]"
1046    verbose "KERBEROS_SERVER=$env(KERBEROS_SERVER)"
1047
1048    # Set our kdc config file, if needed.
1049    switch $type {
1050	client	-
1051	server	{ catch {unset env(KRB5_KDC_PROFILE)} }
1052	kdc	{ set env(KRB5_KDC_PROFILE) $tmppwd/kdc.conf }
1053	slave	{ set env(KRB5_KDC_PROFILE) $tmppwd/slave.conf }
1054	default	{ error "unknown config file type $type" }
1055    }
1056    if [info exists env(KRB5_KDC_PROFILE)] {
1057	verbose "KRB5_KDC_PROFILE=$env(KRB5_KDC_PROFILE)"
1058    }
1059
1060    # Create an environment setup script.  (For convenience)
1061    if ![file exists $tmppwd/$type-env.sh] {
1062	set envfile [open $tmppwd/$type-env.sh w]
1063	puts $envfile "KRB5_CONFIG=$env(KRB5_CONFIG)"
1064	puts $envfile "KRB5CCNAME=$env(KRB5CCNAME)"
1065	puts $envfile "KRB5RCACHEDIR=$env(KRB5RCACHEDIR)"
1066	puts $envfile "KERBEROS_SERVER=$env(KERBEROS_SERVER)"
1067	if [info exists env(KRB5_KDC_PROFILE)] {
1068	    puts $envfile "KRB5_KDC_PROFILE=$env(KRB5_KDC_PROFILE)"
1069	} else {
1070	    puts $envfile "unset KRB5_KDC_PROFILE"
1071	}
1072	puts $envfile "export KRB5_CONFIG KRB5CCNAME KRB5RCACHEDIR"
1073	puts $envfile "export KERBEROS_SERVER KRB5_KDC_PROFILE"
1074	close $envfile
1075    }
1076    if ![file exists $tmppwd/$type-env.csh] {
1077	set envfile [open $tmppwd/$type-env.csh w]
1078	puts $envfile "setenv KRB5_CONFIG $env(KRB5_CONFIG)"
1079	puts $envfile "setenv KRB5CCNAME $env(KRB5CCNAME)"
1080	puts $envfile "setenv KRB5RCACHEDIR $env(KRB5RCACHEDIR)"
1081	puts $envfile "setenv KERBEROS_SERVER $env(KERBEROS_SERVER)"
1082	if [info exists env(KRB5_KDC_PROFILE)] {
1083	    puts $envfile "setenv KRB5_KDC_PROFILE $env(KRB5_KDC_PROFILE)"
1084	} else {
1085	    puts $envfile "unsetenv KRB5_KDC_PROFILE"
1086	}
1087	close $envfile
1088    }
1089    return 1
1090}
1091
1092# Restore the Kerberos environment, in case setup_kerberos_env was
1093# already called by an earlier test.
1094
1095proc restore_kerberos_env { } {
1096    global env
1097    global orig_krb5_config
1098    global orig_krb5ccname
1099    global orig_krb5rcachedir
1100    global orig_kerberos_server
1101
1102    if [info exists orig_krb5_config] {
1103    set env(KRB5_CONFIG) $orig_krb5_config
1104    } else {
1105    catch "unset env(KRB5_CONFIG)"
1106    }
1107
1108    if [info exists orig_krb5ccname] {
1109	set env(KRB5CCNAME) $orig_krb5ccname
1110    } else {
1111	catch "unset env(KRB5CCNAME)"
1112    }
1113
1114    if [info exists orig_krb5rcachedir] {
1115	set env(KRB5RCACHEDIR) $orig_krb5rcachedir
1116    } else {
1117	catch "unset env(KRB5RCACHEDIR)"
1118    }
1119
1120    if [info exists orig_kerberos_server] {
1121	set env(KERBEROS_SERVER) $orig_kerberos_server
1122    } else {
1123	catch "unset env(KERBEROS_SERVER)"
1124    }
1125
1126}
1127
1128# setup_kerberos_db
1129# Initialize the Kerberos database.  If the argument is non-zero, call
1130# pass at relevant points.  Returns 1 on success, 0 on failure.
1131
1132proc setup_kerberos_db { standalone } {
1133    global REALMNAME KDB5_UTIL KADMIN_LOCAL KEY
1134    global tmppwd hostname
1135    global spawn_id
1136    global des3_krbtgt tgt_support_desmd5
1137    global multipass_name last_passname_db
1138
1139    set failall 0
1140
1141    if {!$standalone && [file exists $tmppwd/kdc-db.ok] \
1142	&& $last_passname_db == $multipass_name} {
1143	return 1
1144    }
1145
1146    delete_db
1147
1148    envstack_push
1149    if { ![setup_kerberos_files] || ![setup_kerberos_env kdc] } {
1150	set failall 1
1151    }
1152
1153    # Set up a common expect_after for use in multiple places.
1154    set def_exp_after {
1155	timeout {
1156	    set test "$test (timeout)"
1157	    break
1158	}
1159	eof {
1160	    set test "$test (eof)"
1161	    break
1162	}
1163    }
1164
1165    set test "kdb5_util create"
1166    set body {
1167	if $failall {
1168	    break
1169	}
1170	#exec xterm
1171	verbose "starting $test"
1172	spawn $KDB5_UTIL -r $REALMNAME create -W
1173	expect_after $def_exp_after
1174
1175	expect "Enter KDC database master key:"
1176
1177	set test "kdb5_util create (verify)"
1178	send "masterkey$KEY\r"
1179	expect "Re-enter KDC database master key to verify:"
1180
1181	set test "kdb5_util create"
1182	send "masterkey$KEY\r"
1183	expect {
1184	    -re "\[Cc\]ouldn't" {
1185		expect eof
1186		break
1187	    }
1188	    "Cannot find/read stored" exp_continue
1189	    "Warning: proceeding without master key" exp_continue
1190	    eof { }
1191	}
1192	catch expect_after
1193	if ![check_exit_status kdb5_util] {
1194	    break
1195	}
1196    }
1197    set ret [catch $body]
1198    catch expect_after
1199    if $ret {
1200	set failall 1
1201	if $standalone {
1202	    fail $test
1203	}
1204    } else {
1205	if $standalone {
1206	    pass $test
1207	}
1208    }
1209
1210    # Stash the master key in a file.
1211    set test "kdb5_util stash"
1212    set body {
1213	if $failall {
1214	    break
1215	}
1216	spawn $KDB5_UTIL  -r $REALMNAME stash
1217	verbose "starting $test"
1218	expect_after $def_exp_after
1219	expect "Enter KDC database master key:"
1220	send "masterkey$KEY\r"
1221	expect eof
1222	catch expect_after
1223	if ![check_exit_status kdb5_util] {
1224	    break
1225	}
1226    }
1227    set ret [catch $body]
1228    catch "expect eof"
1229    catch expect_after
1230    if $ret {
1231	set failall 1
1232	if $standalone {
1233	    fail $test
1234	} else {
1235	    delete_db
1236	}
1237    } else {
1238	if $standalone {
1239	    pass $test
1240	}
1241    }
1242
1243    # Add an admin user.
1244    set test "kadmin.local ank krbtest/admin"
1245    set body {
1246	if $failall {
1247	    break
1248	}
1249	spawn $KADMIN_LOCAL -r $REALMNAME
1250	verbose "starting $test"
1251	expect_after $def_exp_after
1252
1253	expect "kadmin.local: "
1254	send "ank krbtest/admin@$REALMNAME\r"
1255	# It echos...
1256	expect "ank krbtest/admin@$REALMNAME\r"
1257	expect "Enter password for principal \"krbtest/admin@$REALMNAME\":"
1258	send "adminpass$KEY\r"
1259	expect "Re-enter password for principal \"krbtest/admin@$REALMNAME\":"
1260	send "adminpass$KEY\r"
1261	expect {
1262	    "Principal \"krbtest/admin@$REALMNAME\" created" { }
1263	    "Principal or policy already exists while creating*" { }
1264	}
1265	expect "kadmin.local: "
1266	send "quit\r"
1267	expect eof
1268	catch expect_after
1269	if ![check_exit_status kadmin_local] {
1270	    break
1271	}
1272    }
1273    set ret [catch $body]
1274    catch "expect eof"
1275    catch expect_after
1276    if $ret {
1277	set failall 1
1278	if $standalone {
1279	    fail $test
1280	} else {
1281	    delete_db
1282	}
1283    } else {
1284	if $standalone {
1285	    pass $test
1286	}
1287    }
1288
1289    # Add an incremental-propagation service.
1290    set test "kadmin.local ank kiprop/$hostname"
1291    set body {
1292	if $failall {
1293	    break
1294	}
1295	spawn $KADMIN_LOCAL -r $REALMNAME
1296	verbose "starting $test"
1297	expect_after $def_exp_after
1298
1299	expect "kadmin.local: "
1300	send "ank kiprop/$hostname@$REALMNAME\r"
1301	# It echos...
1302	expect "ank kiprop/$hostname@$REALMNAME\r"
1303	expect "Enter password for principal \"kiprop/$hostname@$REALMNAME\":"
1304	send "kiproppass$KEY\r"
1305	expect "Re-enter password for principal \"kiprop/$hostname@$REALMNAME\":"
1306	send "kiproppass$KEY\r"
1307	expect {
1308	    "Principal \"kiprop/$hostname@$REALMNAME\" created" { }
1309	    "Principal or policy already exists while creating*" { }
1310	}
1311	expect "kadmin.local: "
1312	send "quit\r"
1313	expect eof
1314	catch expect_after
1315	if ![check_exit_status kadmin_local] {
1316	    break
1317	}
1318    }
1319    set ret [catch $body]
1320    catch "expect eof"
1321    catch expect_after
1322    if $ret {
1323	set failall 1
1324	if $standalone {
1325	    fail $test
1326	} else {
1327	    delete_db
1328	}
1329    } else {
1330	if $standalone {
1331	    pass $test
1332	}
1333    }
1334
1335    if $des3_krbtgt {
1336	# Set the TGT key to DES3.
1337	set test "kadmin.local TGT to DES3"
1338	set body {
1339	    if $failall {
1340		break
1341	    }
1342	    spawn $KADMIN_LOCAL -r $REALMNAME -e des3-cbc-sha1:normal
1343	    verbose "starting $test"
1344	    expect_after $def_exp_after
1345
1346	    expect "kadmin.local: "
1347	    send "cpw -randkey krbtgt/$REALMNAME@$REALMNAME\r"
1348	    # It echos...
1349	    expect "cpw -randkey krbtgt/$REALMNAME@$REALMNAME\r"
1350	    expect {
1351		"Key for \"krbtgt/$REALMNAME@$REALMNAME\" randomized." { }
1352	    }
1353	    expect "kadmin.local: "
1354	    send "quit\r"
1355	    expect eof
1356	    catch expect_after
1357	    if ![check_exit_status kadmin_local] {
1358		break
1359	    }
1360	}
1361	set ret [catch $body]
1362	catch "expect eof"
1363	catch expect_after
1364	if $ret {
1365	    set failall 1
1366	    if $standalone {
1367		fail $test
1368	    } else {
1369		delete_db
1370	    }
1371	} else {
1372	    if $standalone {
1373		pass $test
1374	    }
1375	}
1376    }
1377    if $tgt_support_desmd5 {
1378	# Make TGT support des-cbc-md5
1379	set test "kadmin.local TGT to SUPPORT_DESMD5"
1380	set body {
1381	    if $failall {
1382		break
1383	    }
1384	    spawn $KADMIN_LOCAL -r $REALMNAME
1385	    verbose "starting $test"
1386	    expect_after $def_exp_after
1387
1388	    expect "kadmin.local: "
1389	    send "modprinc +support_desmd5 krbtgt/$REALMNAME@$REALMNAME\r"
1390	    # It echos...
1391	    expect "modprinc +support_desmd5 krbtgt/$REALMNAME@$REALMNAME\r"
1392	    expect {
1393		"Principal \"krbtgt/$REALMNAME@$REALMNAME\" modified.\r\n" { }
1394	    }
1395	    expect "kadmin.local: "
1396	    send "quit\r"
1397	    expect eof
1398	    catch expect_after
1399	    if ![check_exit_status kadmin_local] {
1400		break
1401	    }
1402	}
1403	set ret [catch $body]
1404	catch "expect eof"
1405	catch expect_after
1406	if $ret {
1407	    set failall 1
1408	    if $standalone {
1409		fail $test
1410	    } else {
1411		delete_db
1412	    }
1413	} else {
1414	    if $standalone {
1415		pass $test
1416	    }
1417	}
1418    }
1419    envstack_pop
1420
1421    # create the admin database lock file
1422    catch "exec touch $tmppwd/adb.lock"
1423
1424    set last_passname_db $multipass_name
1425    return 1
1426}
1427
1428proc start_tail { fname spawnid_var pid_var which standalone } {
1429    upvar $spawnid_var spawnid
1430    upvar $pid_var pid
1431    global timeout
1432
1433    set f [open $fname a]
1434
1435    spawn tail -f $fname
1436    set spawnid $spawn_id
1437    set pid [exp_pid]
1438
1439    set markstr "===MARK $pid ==="
1440    puts $f $markstr
1441    flush $f
1442
1443    set p 0
1444    set otimeout $timeout
1445    set timeout 3
1446    set ok 0
1447    while { $ok == 0 && $p < 3 } {
1448	expect {
1449	    -i $spawn_id
1450	    -ex "$markstr" { set ok 1 }
1451	    -re "\[^\r\n\]*\r\n" { exp_continue }
1452	    timeout {
1453		# Some versions of GNU tail had a race condition where
1454		# the first batch of data would be read from the end
1455		# of the file, and then there was a brief window
1456		# before calling stat and recording the size of the
1457		# file.  If the marker is written during that window,
1458		# then yet another file modification is needed to get
1459		# the first one noticed.
1460		if { $p < 3 } {
1461		    verbose -log "no tail output yet, prodding with a blank line"
1462		    incr p
1463		    puts $f ""
1464		    flush $f
1465		    exp_continue
1466		} else {
1467		    close $f
1468		    verbose -log "tail $fname output:"
1469		    verbose -log [exec tail $fname]
1470		    if {$standalone} {
1471			verbose -log "tail -f timed out ($timeout sec) looking for mark in $which log"
1472			fail "$which"
1473		    } else {
1474			perror "$which tail -f timed out ($timeout sec) looking for mark in $which log"
1475		    }
1476		    stop_kerberos_daemons
1477		    exec kill $pid
1478		    expect -i $spawn_id eof
1479		    wait -i $spawn_id
1480		    set timeout $otimeout
1481		    return 0
1482		}
1483	    }
1484	}
1485    }
1486    close $f
1487    set timeout $otimeout
1488    return 1
1489}
1490
1491# start_kerberos_daemons
1492# A procedure to build a Kerberos database and start up the kerberos
1493# and kadmind daemons.  This sets the global variables kdc_pid,
1494# kdc_spawn_id, kadmind_pid, and kadmind_spawn_id.  The procedure
1495# stop_kerberos_daemons should be used to stop the daemons.  If the
1496# argument is non-zero, call pass at relevant points.  Returns 1 on
1497# success, 0 on failure.
1498
1499proc start_kerberos_daemons { standalone } {
1500    global BINSH
1501    global REALMNAME
1502    global KRB5KDC
1503    global KADMIND
1504    global KEY
1505    global kdc_pid
1506    global kdc_spawn_id
1507    global kadmind_pid
1508    global kadmind_spawn_id
1509    global tmppwd
1510    global env
1511    global timeout
1512
1513    if ![setup_kerberos_db 0] {
1514	return 0
1515    }
1516
1517    if {$standalone} {
1518        file delete $tmppwd/krb.log $tmppwd/kadmind.log $tmppwd/krb5kdc_rcache
1519    }
1520
1521    # Start up the kerberos daemon
1522    # Why are we doing all this with the log file you may ask.
1523    #   We need a handle on when the server starts. If we log the output
1524    #   of the server to say stderr, then if we stop looking for output,
1525    #   buffers will fill and the server will stop working....
1526    #   So, we look to see when a line is added to the log file and then
1527    #   check it..
1528    # The same thing is done a little later for the kadmind
1529    set kdc_lfile $tmppwd/kdc.log
1530    set kadmind_lfile $tmppwd/kadmind5.log
1531
1532    if ![start_tail $kdc_lfile tailf_spawn_id tailf_pid krb5kdc $standalone] {
1533	return 0
1534    }
1535
1536    envstack_push
1537    setup_kerberos_env kdc
1538    spawn $KRB5KDC -r $REALMNAME -n full
1539    envstack_pop
1540    set kdc_pid [exp_pid]
1541    set kdc_spawn_id $spawn_id
1542
1543    expect {
1544	-i $tailf_spawn_id
1545	-re "commencing operation\r\n" { }
1546	-re "krb5kdc: \[a-zA-Z\]* - Cannot bind server socket to \[ 0-9a-fA-F:.\]*\r\n" {
1547	    verbose -log "warning: $expect_out(0,string)"
1548	    exp_continue
1549	}
1550	"no sockets set up?" {
1551	    if {$standalone} {
1552		verbose -log "krb5kdc startup failed to bind listening sockets"
1553		fail "krb5kdc"
1554	    } else {
1555		perror "krb5kdc startup failed to bind listening sockets"
1556	    }
1557	    stop_kerberos_daemons
1558	    exec kill $tailf_pid
1559	    expect -i $tailf_spawn_id eof
1560	    wait -i $tailf_spawn_id
1561	    return 0
1562	}
1563	timeout {
1564	    if {$standalone} {
1565		verbose -log "krb5kdc startup timed out"
1566		fail "krb5kdc"
1567	    } else {
1568		perror "krb5kdc startup timed out"
1569	    }
1570	    stop_kerberos_daemons
1571	    exec kill $tailf_pid
1572	    expect -i $tailf_spawn_id eof
1573	    wait -i $tailf_spawn_id
1574	    return 0
1575	}
1576    }
1577    exec kill $tailf_pid
1578    expect -i $tailf_spawn_id eof
1579    wait -i $tailf_spawn_id
1580
1581    if {$standalone} {
1582	pass "krb5kdc"
1583    }
1584
1585    # Give the kerberos daemon a few seconds to get set up.
1586#    sleep 2
1587
1588    #
1589    # Save setting of KRB5_KTNAME. We do not want to override kdc.conf
1590    # file during kadmind startup. (this is in case user has KRB5_KTNAME
1591    # set before starting make check)
1592    #
1593    if [info exists env(KRB5_KTNAME)] {
1594	set start_save_ktname $env(KRB5_KTNAME)
1595    }
1596    catch "unset env(KRB5_KTNAME)"
1597
1598    if ![start_tail $kadmind_lfile tailf_spawn_id tailf_pid kadmind $standalone] {
1599	return 0
1600    }
1601
1602    # Start up the kadmind daemon
1603    # XXXX kadmind uses stderr a lot.  the sh -c and redirect can be
1604    # removed when this is fixed
1605    envstack_push
1606    setup_kerberos_env kdc
1607    spawn $BINSH -c "exec $KADMIND -r $REALMNAME -W -nofork 2>>$kadmind_lfile"
1608    envstack_pop
1609    set kadmind_pid [exp_pid]
1610    set kadmind_spawn_id $spawn_id
1611
1612    # Restore KRB5_KTNAME
1613    if [info exists start_save_ktname] {
1614        set env(KRB5_KTNAME) $start_save_ktname
1615        unset start_save_ktname
1616    }
1617
1618    expect {
1619	-i $tailf_spawn_id
1620	"Seeding random number" exp_continue
1621	"cannot initialize network" {
1622	    if {$standalone} {
1623		verbose -log "kadmind failed network init"
1624		fail "kadmind"
1625	    } else {
1626		perror "kadmind failed network init"
1627	    }
1628	    stop_kerberos_daemons
1629	    exec kill $tailf_pid
1630	    expect -i $tailf_spawn_id eof
1631	    wait -i $tailf_spawn_id
1632	    return 0
1633	}
1634	"cannot bind to network address" {
1635	    if {$standalone} {
1636		verbose -log "kadmind failed to bind socket"
1637		fail "kadmind"
1638	    } else {
1639		perror "kadmind failed to bind socket"
1640	    }
1641	    stop_kerberos_daemons
1642	    exec kill $tailf_pid
1643	    expect -i $tailf_spawn_id eof
1644	    wait -i $tailf_spawn_id
1645	    return 0
1646	}
1647	"No principal in keytab matches desired name" {
1648	    dump_db
1649	    exp_continue
1650	}
1651	"starting" { }
1652	timeout {
1653	    if {$standalone} {
1654		verbose -log "kadmind failed to start"
1655		fail "kadmind"
1656	    } else {
1657		verbose -log "kadmind failed to start"
1658		perror "kadmind failed to start"
1659	    }
1660#sleep 10
1661	    stop_kerberos_daemons
1662	    exec kill $tailf_pid
1663	    expect -i $tailf_spawn_id eof
1664	    wait -i $tailf_spawn_id
1665	    return 0
1666	}
1667    }
1668    exec kill $tailf_pid
1669    expect -i $tailf_spawn_id eof
1670    wait -i $tailf_spawn_id
1671
1672    if {$standalone} {
1673	pass "kadmind"
1674    }
1675
1676    # Give the kadmind daemon a few seconds to get set up.
1677#    sleep 2
1678
1679    return 1
1680}
1681
1682# stop_kerberos_daemons
1683# Stop the kerberos daemons.  Returns 1 on success, 0 on failure.
1684
1685proc stop_kerberos_daemons { } {
1686    global kdc_pid
1687    global kdc_spawn_id
1688    global kadmind_pid
1689    global kadmind_spawn_id
1690
1691    verbose "entered stop_kerberos_daemons"
1692
1693    if [info exists kdc_pid] {
1694	if [catch "exec kill $kdc_pid" msg] {
1695	    verbose "kill kdc: $msg"
1696	}
1697	if [catch "expect -i $kdc_spawn_id eof" msg] {
1698	    verbose "expect kdc eof: $msg"
1699	}
1700	set kdc_list [wait -i $kdc_spawn_id]
1701	verbose "wait -i $kdc_spawn_id returned $kdc_list (kdc)"
1702	unset kdc_pid
1703	unset kdc_list
1704    }
1705
1706    if [info exists kadmind_pid] {
1707	if [catch "exec kill $kadmind_pid" msg] {
1708	    verbose "kill kadmind: $msg"
1709	}
1710	if [catch "expect -i $kadmind_spawn_id eof" msg] {
1711	    verbose "expect kadmind eof: $msg"
1712	}
1713	set kadmind_list [wait -i $kadmind_spawn_id]
1714	verbose "wait -i $kadmind_spawn_id returned $kadmind_list (kadmind5)"
1715	unset kadmind_pid
1716	unset kadmind_list
1717    }
1718
1719    verbose "exiting stop_kerberos_daemons"
1720
1721    return 1
1722}
1723
1724# add_kerberos_key
1725# Add an key to the Kerberos database.  start_kerberos_daemons must be
1726# called before this procedure.  If the standalone argument is
1727# non-zero, call pass at relevant points.  Returns 1 on success, 0 on
1728# failure.
1729
1730proc add_kerberos_key { kkey standalone } {
1731    global REALMNAME
1732    global KADMIN
1733    global KEY
1734    global spawn_id
1735
1736    # Use kadmin to add an key.
1737    set test "kadmin ank $kkey"
1738    set body {
1739	envstack_push
1740	setup_kerberos_env client
1741	spawn $KADMIN -p krbtest/admin@$REALMNAME -q "ank $kkey@$REALMNAME"
1742	envstack_pop
1743	verbose "starting $test"
1744	expect_after {
1745	    "Cannot contact any KDC" {
1746		set test "$test (lost KDC)"
1747		break
1748	    }
1749	    timeout {
1750		set test "$test (timeout)"
1751		break
1752	    }
1753	    eof {
1754		set test "$test (eof)"
1755		break
1756	    }
1757	}
1758	expect -re "assword\[^\r\n\]*: *"
1759	send "adminpass$KEY\r"
1760	expect "Enter password for principal \"$kkey@$REALMNAME\":"
1761	send "$kkey"
1762	send "$KEY\r"
1763	expect "Re-enter password for principal \"$kkey@$REALMNAME\":"
1764	send "$kkey"
1765	send "$KEY\r"
1766	expect {
1767	    "Principal \"$kkey@$REALMNAME\" created" { }
1768	    "Principal or policy already exists while creating*" { }
1769	}
1770	expect eof
1771	if ![check_exit_status kadmin] {
1772	    break
1773	}
1774    }
1775    set ret [catch $body]
1776    catch "expect eof"
1777    catch expect_after
1778    if $ret {
1779	if $standalone {
1780	    fail $test
1781	}
1782	return 0
1783    } else {
1784	if $standalone {
1785	    pass $test
1786	}
1787	return 1
1788    }
1789}
1790
1791# dump_db
1792proc dump_db { } {
1793    global KADMIN_LOCAL
1794    global REALMNAME
1795
1796    spawn $KADMIN_LOCAL -r $REALMNAME
1797    expect_after {
1798	eof {
1799	    perror "failed to get debugging dump of database (eof)"
1800	}
1801	timeout {
1802	    perror "failed to get debugging dump of database (timeout)"
1803	}
1804    }
1805    expect "kadmin.local: "
1806    send "getprincs\r"
1807    expect "kadmin.local: "
1808    send "quit\r"
1809    expect eof
1810    catch expect_after
1811}
1812
1813# add_random_key
1814# Add a key with a random password to the Kerberos database.
1815# start_kerberos_daemons must be called before this procedure.  If the
1816# standalone argument is non-zero, call pass at relevant points.
1817# Returns 1 on success, 0 on failure.
1818
1819proc add_random_key { kkey standalone } {
1820    global REALMNAME
1821    global KADMIN
1822    global KEY
1823    global spawn_id
1824
1825    # Use kadmin to add an key.
1826    set test "kadmin ark $kkey"
1827    set body {
1828	envstack_push
1829	setup_kerberos_env client
1830	spawn $KADMIN -p krbtest/admin@$REALMNAME -q "ank -randkey $kkey@$REALMNAME"
1831	envstack_pop
1832	expect_after {
1833	    timeout {
1834		set test "$test (timeout)"
1835		break
1836	    }
1837	    eof {
1838		set test "$test (eof)"
1839		break
1840	    }
1841	}
1842	expect -re "assword\[^\r\n\]*: *"
1843	send "adminpass$KEY\r"
1844	expect {
1845	    "Principal \"$kkey@$REALMNAME\" created" { }
1846	    "Principal or policy already exists while creating*" { }
1847	}
1848	expect eof
1849	if ![check_exit_status kadmin] {
1850	    break
1851	}
1852    }
1853    if [catch $body] {
1854	catch expect_after
1855	if $standalone {
1856	    fail $test
1857	}
1858	return 0
1859    } else {
1860	catch expect_after
1861	if $standalone {
1862	    pass $test
1863	}
1864	return 1
1865    }
1866}
1867
1868# setup_srvtab
1869# Set up a srvtab file.  start_kerberos_daemons and add_random_key
1870# $id/$hostname must be called before this procedure.  If the
1871# argument is non-zero, call pass at relevant points.  Returns 1 on
1872# success, 0 on failure. If the id field is not provided, host is used.
1873
1874proc setup_srvtab { standalone {id host} } {
1875    global REALMNAME
1876    global KADMIN_LOCAL
1877    global KEY
1878    global tmppwd
1879    global hostname
1880    global spawn_id
1881    global last_service
1882
1883    if {!$standalone && [file exists $tmppwd/srvtab] && $last_service == $id} {
1884	return 1
1885    }
1886
1887    file delete $tmppwd/srvtab $tmppwd/srvtab.old
1888
1889    if ![get_hostname] {
1890	return 0
1891    }
1892
1893    file delete $hostname-new-srvtab
1894
1895    envstack_push
1896    setup_kerberos_env kdc
1897    spawn $KADMIN_LOCAL -r $REALMNAME
1898    envstack_pop
1899    expect_after {
1900	-re "(.*)\r\nkadmin.local:  " {
1901	    fail "kadmin.local srvtab (unmatched output: $expect_out(1,string))"
1902	    if {!$standalone} {
1903		file delete $tmppwd/srvtab
1904	    }
1905	    catch "expect_after"
1906	    return 0
1907	}
1908	timeout {
1909	    fail "kadmin.local srvtab"
1910	    if {!$standalone} {
1911		file delete $tmppwd/srvtab
1912	    }
1913	    catch "expect_after"
1914	    return 0
1915	}
1916	eof {
1917	    fail "kadmin.local srvtab"
1918	    if {!$standalone} {
1919		file delete $tmppwd/srvtab
1920	    }
1921	    catch "expect_after"
1922	    return 0
1923	}
1924    }
1925    expect "kadmin.local:  "
1926    send "xst -k $hostname-new-srvtab $id/$hostname kiprop/$hostname\r"
1927    expect "xst -k $hostname-new-srvtab $id/$hostname kiprop/$hostname\r\n"
1928    expect {
1929	-re ".*Entry for principal $id/$hostname.* added to keytab WRFILE:$hostname-new-srvtab." { }
1930	-re "\r\nkadmin.local:  " {
1931	    if {$standalone} {
1932		fail "kadmin.local srvtab"
1933	    } else {
1934		file delete $tmppwd/srvtab
1935	    }
1936	    catch expect_after
1937	    return 0
1938	}
1939    }
1940    expect "kadmin.local:  "
1941    send "quit\r"
1942    expect eof
1943    catch expect_after
1944    if ![check_exit_status "kadmin.local srvtab"] {
1945	if {!$standalone} {
1946	    file delete $tmppwd/srvtab
1947	}
1948	return 0
1949    }
1950
1951    catch "exec mv -f $hostname-new-srvtab $tmppwd/srvtab" exec_output
1952    if ![string match "" $exec_output] {
1953	verbose -log "$exec_output"
1954	perror "can't mv new srvtab"
1955	return 0
1956    }
1957
1958    if {$standalone} {
1959	pass "kadmin.local srvtab"
1960    }
1961
1962    # Make the srvtab file globally readable in case we are using a
1963    # root shell and the srvtab is NFS mounted.
1964    catch "exec chmod a+r $tmppwd/srvtab"
1965
1966    # Remember what we just extracted
1967    set last_service $id
1968
1969    return 1
1970}
1971
1972# kinit
1973# Use kinit to get a ticket.  If the argument is non-zero, call pass
1974# at relevant points.  Returns 1 on success, 0 on failure.
1975
1976proc kinit { name pass standalone } {
1977    global REALMNAME
1978    global KINIT
1979    global spawn_id
1980
1981    # Use kinit to get a ticket.
1982	#
1983	# For now always get forwardable tickets. Later when we need to make
1984	# tests that distiguish between forwardable tickets and otherwise
1985	# we should but another option to this proc. --proven
1986	#
1987    spawn $KINIT -5 -f $name@$REALMNAME
1988    expect {
1989	"Password for $name@$REALMNAME:" {
1990	    verbose "kinit started"
1991	}
1992	timeout {
1993	    fail "kinit"
1994	    return 0
1995	}
1996	eof {
1997	    fail "kinit"
1998	    return 0
1999	}
2000    }
2001    send "$pass\r"
2002    expect eof
2003    if ![check_exit_status kinit] {
2004	return 0
2005    }
2006
2007    if {$standalone} {
2008	pass "kinit"
2009    }
2010
2011    return 1
2012}
2013
2014proc kinit_kt { name keytab standalone testname } {
2015    global REALMNAME
2016    global KINIT
2017    global spawn_id
2018
2019    # Use kinit to get a ticket.
2020	#
2021	# For now always get forwardable tickets. Later when we need to make
2022	# tests that distiguish between forwardable tickets and otherwise
2023	# we should but another option to this proc. --proven
2024	#
2025    spawn $KINIT -5 -f -k -t $keytab $name@$REALMNAME
2026    expect {
2027	timeout {
2028	    fail "kinit $testname"
2029	    return 0
2030	}
2031	eof { }
2032    }
2033    if ![check_exit_status "kinit $testname"] {
2034	return 0
2035    }
2036
2037    if {$standalone} {
2038	pass "kinit $testname"
2039    }
2040
2041    return 1
2042}
2043
2044# List tickets.  Requires client and server names, and test name.
2045# Checks that klist exist status is zero.
2046# Records pass or fail, and returns 1 or 0.
2047proc do_klist { myname servname testname } {
2048    global KLIST
2049    global tmppwd
2050
2051    spawn $KLIST -5 -e
2052    expect {
2053	-re "Ticket cache:\[ 	\]*(.+:)?$tmppwd/tkt.*Default principal:\[ 	\]*$myname.*$servname\r\n" {
2054	    verbose "klist started"
2055	}
2056	timeout {
2057	    fail $testname
2058	    return 0
2059	}
2060	eof {
2061	    fail $testname
2062	    return 0
2063	}
2064    }
2065
2066    expect eof
2067
2068    if ![check_exit_status $testname] {
2069	return 0
2070    }
2071    pass $testname
2072    return 1
2073}
2074
2075proc do_klist_kt { keytab testname } {
2076    global KLIST
2077    global tmppwd
2078
2079    spawn $KLIST -5 -e -k $keytab
2080    expect {
2081	-re "Keytab name:\[ 	\]*(.+:)?.*KVNO Principal\r\n---- -*\r\n" {
2082	    verbose "klist started"
2083	}
2084	timeout {
2085	    fail $testname
2086	    return 0
2087	}
2088	eof {
2089	    fail $testname
2090	    return 0
2091	}
2092    }
2093    set more 1
2094    while {$more} {
2095	expect {
2096	    -re { *[0-9][0-9]* *[a-zA-Z/@.-]* \([/a-zA-Z 0-9-]*\) *\r\n} {
2097		verbose -log "key: $expect_out(buffer)"
2098	    }
2099	    eof { set more 0 }
2100	}
2101    }
2102
2103    if ![check_exit_status $testname] {
2104	return 0
2105    }
2106    pass $testname
2107    return 1
2108}
2109
2110proc do_klist_err { testname } {
2111    global KLIST
2112    global spawn_id
2113
2114    spawn $KLIST -5
2115    # Might say "credentials cache" or "credentials cache file".
2116    expect {
2117	-re "klist: No credentials cache.*found.*\r\n" {
2118	    verbose "klist started"
2119	}
2120	timeout {
2121	    fail $testname
2122	    return 0
2123	}
2124	eof {
2125	    fail $testname
2126	    return 0
2127	}
2128    }
2129    # We can't use check_exit_status, because we expect an exit status
2130    # of 1.
2131    catch "expect eof"
2132    set status_list [wait -i $spawn_id]
2133    verbose "wait -i $spawn_id returned $status_list ($testname)"
2134    if { [lindex $status_list 2] != 0 } {
2135	fail "$testname (bad exit status) $status_list"
2136	return 0
2137    } else { if { [lindex $status_list 3] != 1 } {
2138	fail "$testname (bad exit status) $status_list"
2139	return 0
2140    } else {
2141	pass $testname
2142    } }
2143    return 1
2144}
2145
2146proc do_kdestroy { testname } {
2147    global KDESTROY
2148    global spawn_id
2149
2150    spawn $KDESTROY -5
2151    if ![check_exit_status $testname] {
2152	fail $testname
2153	return 0
2154    }
2155    pass $testname
2156    return 1
2157}
2158
2159proc xst { keytab name } {
2160    global KADMIN_LOCAL
2161    global REALMNAME
2162
2163    envstack_push
2164    setup_kerberos_env kdc
2165    spawn $KADMIN_LOCAL -r $REALMNAME
2166    envstack_pop
2167    catch expect_after
2168    expect_after {
2169	-re "(.*)\r\nkadmin.local:  " {
2170	    fail "kadmin.local xst $keytab (unmatched output: $expect_out(1,string)"
2171	    catch "expect_after"
2172	    return 0
2173	}
2174	timeout {
2175	    fail "kadmin.local xst $keytab (timeout)"
2176	    catch "expect_after"
2177	    return 0
2178	}
2179	eof {
2180	    fail "kadmin.local xst $keytab (eof)"
2181	    catch "expect_after"
2182	    return 0
2183	}
2184    }
2185    expect "kadmin.local:  "
2186    send "xst -k $keytab $name\r"
2187    expect -re "xst -k \[^\r\n\]*\r\n.*Entry for principal .* added to keytab WRFILE:.*\r\nkadmin.local:  "
2188    send "quit\r"
2189    expect eof
2190    catch expect_after
2191    if ![check_exit_status "kadmin.local $keytab"] {
2192	perror "kadmin.local xst $keytab exited abnormally"
2193	return 0
2194    }
2195    return 1
2196}
2197
2198# v4_compatible_enctype
2199# Returns 1 if v4 testing is enabled this passes encryption types are compatable with kerberos 4 work
2200proc v4_compatible_enctype {} {
2201    global supported_enctypes
2202    global KRBIV
2203
2204    if ![info exists KRBIV] {
2205	return 0;
2206    }
2207
2208    if { $KRBIV && [string first des-cbc-crc:v4 "$supported_enctypes"] >= 0} {
2209	return 1
2210    } else {
2211	return 0
2212    }
2213}
2214
2215# Set up a root shell using rlogin $hostname -l root.  This is used
2216# when testing the daemons that must be run as root, such as telnetd
2217# or rlogind.  This sets the global variables rlogin_spawn_id and
2218# rlogin_pid.  Returns 1 on success, 0 on failure.
2219#
2220# This procedure will only succeed if the person running the test has
2221# a valid ticket for a name listed in the /.klogin file.  Naturally,
2222# Kerberos must already be installed on this machine.  It's a pain,
2223# but I can't think of a better approach.
2224
2225if ![info exists can_get_root] { set can_get_root yes }
2226
2227proc setup_root_shell { testname } {
2228    global BINSH
2229    global ROOT_PROMPT
2230    global KEY
2231    global RLOGIN
2232    global RLOGIN_FLAGS
2233    global hostname
2234    global rlogin_spawn_id
2235    global rlogin_pid
2236    global tmppwd
2237    global env
2238    global krb5_init_vars
2239    global can_get_root
2240
2241    global timeout
2242
2243    if [string match $can_get_root no] {
2244	note "$testname test requires ability to log in as root"
2245	unsupported $testname
2246	return 0
2247    }
2248
2249    # Make sure we are using the original values of the environment
2250    # variables.  This means that the caller must call
2251    # setup_kerberos_env after calling this procedure.
2252
2253    # XXX fixme to deal with envstack
2254    restore_kerberos_env
2255
2256    set me [exec whoami]
2257    if [string match root $me] {
2258	return [setup_root_shell_noremote $testname]
2259    }
2260
2261    if ![get_hostname] {
2262	set can_get_root no
2263	return 0
2264    }
2265
2266    # If you have not installed Kerberos on your system, and you want
2267    # to run these tests, you can do it if you are willing to put your
2268    # root password in this file (this is not a very good idea, but
2269    # it's safe enough if you disconnect from the network and remember
2270    # to remove the password later).  Change the rlogin in the next
2271    # line to be /usr/ucb/rlogin (or whatever is appropriate for your
2272    # system).  Then change the lines after "word:" a few lines
2273    # farther down to be
2274    #    send "rootpassword\r"
2275    #    exp_continue
2276
2277    eval spawn $RLOGIN $hostname -l root $RLOGIN_FLAGS
2278    set rlogin_spawn_id $spawn_id
2279    set rlogin_pid [exp_pid]
2280    set old_timeout $timeout
2281    set timeout 300
2282    set got_refused 0
2283
2284    expect {
2285	-re {connect to address [0-9a-fA-F.:]*: Connection refused} {
2286	    note $expect_out(buffer)
2287	    set got_refused 1
2288	    exp_continue
2289	}
2290	-re "word:|erberos rlogin failed|ection refused|ection reset by peer|not authorized|Ticket expired|authenticity of" {
2291	    note "$testname test requires ability to rlogin as root"
2292	    unsupported "$testname"
2293	    set timeout $old_timeout
2294	    stop_root_shell
2295	    set can_get_root no
2296	    return 0
2297	}
2298	"Cannot assign requested address" {
2299	    note "$testname: rlogin as root 'cannot assign requested address'"
2300	    unsupported "$testname"
2301	    set timeout $old_timeout
2302	    stop_root_shell
2303	    set can_get_root no
2304	    return 0
2305	}
2306	-re "usage: rlogin|illegal option -- x|invalid option -- x" {
2307	    note "$testname: rlogin doesn't like command-line flags"
2308	    unsupported "$testname"
2309	    set timeout $old_timeout
2310	    stop_root_shell
2311	    set can_get_root no
2312	    return 0
2313	}
2314	-re "$ROOT_PROMPT" { }
2315	timeout {
2316	    perror "timeout from rlogin $hostname -l root"
2317	    perror "If you have an unusual root prompt,"
2318	    perror "try running with ROOT_PROMPT=\"regexp\""
2319	    set timeout $old_timeout
2320	    stop_root_shell
2321	    set can_get_root no
2322	    return 0
2323	}
2324	eof {
2325	    if {$got_refused} {
2326		# reported some errors, continued, and failed
2327		note "$testname test requires ability to log in as root"
2328		unsupported $testname
2329	    } else {
2330		# unknown problem?
2331#		perror "eof from rlogin $hostname -l root"
2332		note "eof (and unrecognized messages?) from rlogin $hostname -l root"
2333		note "$testname test requires ability to log in as root"
2334		unsupported $testname
2335	    }
2336	    stop_root_shell
2337	    set timeout $old_timeout
2338	    catch "expect_after"
2339	    set can_get_root no
2340	    return 0
2341	}
2342    }
2343
2344    expect_after {
2345	timeout {
2346	    perror "timeout from rlogin $hostname -l root"
2347	    stop_root_shell
2348	    set timeout $old_timeout
2349	    catch "expect_after"
2350	    set can_get_root no
2351	    return 0
2352	}
2353	eof {
2354	    perror "eof from rlogin $hostname -l root"
2355	    stop_root_shell
2356	    set timeout $old_timeout
2357	    catch "expect_after"
2358	    set can_get_root no
2359	    return 0
2360	}
2361    }
2362
2363    # Make sure the root shell is using /bin/sh.
2364    send "$BINSH\r"
2365    expect {
2366	-re "$ROOT_PROMPT" { }
2367    }
2368
2369    # Set up a shell variable tmppwd.  The callers use this to keep
2370    # command line lengths down.  The command line length is important
2371    # because we are feeding input to a shell via a pty.  On some
2372    # systems a pty will only accept 255 characters.
2373    send "tmppwd=$tmppwd\r"
2374    expect {
2375	-re "$ROOT_PROMPT" { }
2376    }
2377
2378    # Set up our krb5.conf
2379    send "KRB5_CONFIG=$tmppwd/krb5.server.conf\r"
2380    expect {
2381	-re "$ROOT_PROMPT" { }
2382    }
2383    send "export KRB5_CONFIG\r"
2384    expect {
2385	-re "$ROOT_PROMPT" { }
2386    }
2387
2388    # Move over to the right directory.
2389    set dir [pwd]
2390    send "cd $dir\r"
2391    expect {
2392	-re "$ROOT_PROMPT" { }
2393	"$dir:" {
2394	    perror "root shell can not cd to $dir"
2395	    set timeout $old_timeout
2396	    stop_root_shell
2397	    set can_get_root no
2398	    return 0
2399	}
2400    }
2401
2402    expect_after
2403    set timeout $old_timeout
2404
2405    return 1
2406}
2407
2408proc setup_root_shell_noremote { testname } {
2409    global BINSH
2410    global ROOT_PROMPT
2411    global KEY
2412    global hostname
2413    global rlogin_spawn_id
2414    global rlogin_pid
2415    global tmppwd
2416    global env
2417
2418    eval spawn $BINSH
2419    set rlogin_spawn_id $spawn_id
2420    set rlogin_pid [exp_pid]
2421
2422    expect_after {
2423	timeout {
2424	    perror "timeout from root shell"
2425	    stop_root_shell
2426	    catch "expect_after"
2427	    return 0
2428	}
2429	eof {
2430	    perror "eof from root shell"
2431	    stop_root_shell
2432	    catch "expect_after"
2433	    return 0
2434	}
2435    }
2436    expect {
2437	-re "$ROOT_PROMPT" { }
2438    }
2439
2440    # Set up a shell variable tmppwd.  The callers use this to keep
2441    # command line lengths down.  The command line length is important
2442    # because we are feeding input to a shell via a pty.  On some
2443    # systems a pty will only accept 255 characters.
2444    send "tmppwd=$tmppwd\r"
2445    expect {
2446	-re "$ROOT_PROMPT" { }
2447    }
2448
2449    # Set up our krb5.conf
2450    send "KRB5_CONFIG=$tmppwd/krb5.server.conf\r"
2451    expect {
2452	-re "$ROOT_PROMPT" { }
2453    }
2454    send "export KRB5_CONFIG\r"
2455    expect {
2456	-re "$ROOT_PROMPT" { }
2457    }
2458
2459    # Move over to the right directory.
2460    set dir [pwd]
2461    send "cd $dir\r"
2462    expect {
2463	-re "$ROOT_PROMPT" { }
2464	"$dir:" {
2465	    perror "root shell can not cd to $dir"
2466	    stop_root_shell
2467	    return 0
2468	}
2469    }
2470
2471    expect_after
2472
2473    return 1
2474}
2475
2476# Kill off a root shell started by setup_root_shell.
2477
2478proc stop_root_shell { } {
2479    global rlogin_spawn_id
2480    global rlogin_pid
2481
2482    catch "close -i $rlogin_spawn_id"
2483    catch "exec kill $rlogin_pid"
2484    sleep 1
2485    catch "exec kill -9 $rlogin_pid"
2486    catch "wait -i $rlogin_spawn_id"
2487}
2488
2489# Check the date.  The string will be the output of date on this
2490# system, and we must make sure that it is in the same timezone as the
2491# output of date run a second time.  The first date will be run on an
2492# rlogin or some such connection to the local system.  This is to test
2493# to make sure that the TZ environment variable is handled correctly.
2494# Returns 1 on sucess, 0 on failure.
2495
2496proc check_date { date } {
2497    catch "exec date" ndate
2498    set atz ""
2499    set ntz ""
2500    scan $date "%s %s %d %d:%d:%d %s %d" adow amon adom ahr amn asc atz ayr
2501    scan $ndate "%s %s %d %d:%d:%d %s %d" ndow nmon ndom nhr nmn nsc ntz nyr
2502    if { $atz != $ntz } {
2503	verbose -log "date check failed: $atz != $ntz"
2504	return 0
2505    }
2506    return 1
2507}
2508
2509proc touch { file } {
2510    set f [open $file "a"]
2511    puts $f ""
2512    close $f
2513}
2514
2515# Implement this in tcl someday?
2516proc tail1 { file } {
2517    exec tail -1 $file
2518}
2519
2520# setup_wrapper
2521# Sets up a wraper script to set the runtime shared library environment
2522# variables and then executes a specific command. This is used to allow
2523# a "rsh klist" or telnetd to execute login.krb5.
2524proc setup_wrapper { file command } {
2525    global BINSH
2526    global env
2527
2528    # We will start with a BINSH script
2529    file delete $file
2530
2531    set f [open $file "w" 0777]
2532    puts $f "#!$BINSH"
2533    puts $f "KRB5_CONFIG=$env(KRB5_CONFIG)"
2534    puts $f "export KRB5_CONFIG"
2535    puts $f "exec $command"
2536    close $f
2537
2538    return 1
2539}
2540
2541proc krb_exit { } {
2542    stop_kerberos_daemons
2543}
2544
2545# helpful sometimes for debugging the test suite
2546proc export_debug_envvars { } {
2547    global env
2548    foreach i {KDB5_UTIL KRB5KDC KADMIND KADMIN KADMIN_LOCAL KINIT KTUTIL KLIST RLOGIN RLOGIN_FLAGS RLOGIND FTP FTPD KPASSWD REALMNAME GSSCLIENT KPROPLOG} {
2549	global $i
2550	if [info exists $i] { set env($i) [set $i] }
2551    }
2552}
2553proc spawn_xterm { } {
2554    export_debug_envvars
2555    exec "xterm"
2556}
2557proc spawn_shell { } {
2558    export_debug_envvars
2559    spawn "sh"
2560    exp_interact
2561}
2562