Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/util/k5test.py
34878 views
1
# Copyright (C) 2010 by the Massachusetts Institute of Technology.
2
# All rights reserved.
3
4
# Export of this software from the United States of America may
5
# require a specific license from the United States Government.
6
# It is the responsibility of any person or organization contemplating
7
# export to obtain such a license before exporting.
8
#
9
# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
10
# distribute this software and its documentation for any purpose and
11
# without fee is hereby granted, provided that the above copyright
12
# notice appear in all copies and that both that copyright notice and
13
# this permission notice appear in supporting documentation, and that
14
# the name of M.I.T. not be used in advertising or publicity pertaining
15
# to distribution of the software without specific, written prior
16
# permission. Furthermore if you modify this software you must label
17
# your software as modified software and not distribute it in such a
18
# fashion that it might be confused with the original M.I.T. software.
19
# M.I.T. makes no representations about the suitability of
20
# this software for any purpose. It is provided "as is" without express
21
# or implied warranty.
22
23
"""A module for krb5 test scripts
24
25
To run test scripts during "make check" (if Python 2.5 or later is
26
available), add rules like the following to Makefile.in:
27
28
check-pytests::
29
$(RUNPYTEST) $(srcdir)/t_testname.py $(PYTESTFLAGS)
30
31
A sample test script:
32
33
from k5test import *
34
35
# Run a test program under a variety of configurations:
36
for realm in multipass_realms():
37
realm.run(['./testprog', 'arg'])
38
39
# Run a test server and client under just the default configuration:
40
realm = K5Realm()
41
realm.start_server(['./serverprog'], 'starting...')
42
realm.run(['./clientprog', realm.host_princ])
43
44
# Inform framework that tests completed successfully.
45
success('World peace and cure for cancer')
46
47
By default, the realm will have:
48
49
* The name KRBTEST.COM
50
* Listener ports starting at 61000
51
* krb5.conf and kdc.conf files
52
* A fresh DB2 KDB
53
* Running krb5kdc (but not kadmind)
54
* Principals named realm.user_princ and realm.admin_princ; call
55
password('user') and password('admin') to get the password
56
* Credentials for realm.user_princ in realm.ccache
57
* Admin rights for realm.admin_princ in the kadmind acl file
58
* A host principal named realm.host_princ with a random key
59
* A keytab for the host principal in realm.keytab
60
61
The realm's behaviour can be modified with the following constructor
62
keyword arguments:
63
64
* realm='realmname': Override the realm name
65
66
* portbase=NNN: Override the listener port base; currently three ports are
67
used
68
69
* testdir='dirname': Override the storage area for the realm's files
70
(path may be specified relative to the current working dir)
71
72
* krb5_conf={ ... }: krb5.conf options, expressed as a nested
73
dictionary, to be merged with the default krb5.conf settings. A key
74
may be mapped to None to delete a setting from the defaults. A key
75
may be mapped to a list in order to create multiple settings for the
76
same variable name. Keys and values undergo the following template
77
substitutions:
78
79
- $realm: The realm name
80
- $testdir: The realm storage directory (absolute path)
81
- $buildtop: The root of the build directory
82
- $srctop: The root of the source directory
83
- $plugins: The plugin directory in the build tree
84
- $certs: The PKINIT certificate directory in the source tree
85
- $hostname: The FQDN of the host
86
- $port0: The first listener port (portbase)
87
- ...
88
- $port9: The tenth listener port (portbase + 9)
89
90
When choosing ports, note the following:
91
92
- port0 is used in the default krb5.conf for the KDC
93
- port1 is used in the default krb5.conf for kadmind
94
- port2 is used in the default krb5.conf for kpasswd
95
- port3 is used in the default krb5.conf for kpropd
96
- port4 is used in the default krb5.conf for iprop (in kadmind)
97
- port5 is the return value of realm.server_port()
98
99
* kdc_conf={...}: kdc.conf options, expressed as a nested dictionary,
100
to be merged with the default kdc.conf settings. The same
101
conventions and substitutions for krb5_conf apply.
102
103
* create_kdb=False: Don't create a KDB. Implicitly disables all of
104
the other options since they all require a KDB.
105
106
* krbtgt_keysalt='enctype:salttype': After creating the KDB,
107
regenerate the krbtgt key using the specified key/salt combination,
108
using a kadmin.local cpw query.
109
110
* create_user=False: Don't create the user principal. Implies
111
get_creds=False.
112
113
* create_host=False: Don't create the host principal or the associated
114
keytab.
115
116
* start_kdc=False: Don't start the KDC. Implies get_creds=False.
117
118
* start_kadmind=True: Start kadmind.
119
120
* get_creds=False: Don't get user credentials.
121
122
* bdb_only=True: Use the DB2 KDB module even if K5TEST_LMDB is set in
123
the environment.
124
125
* pkinit=True: Configure a PKINIT anchor and KDC certificate.
126
127
Scripts may use the following functions and variables:
128
129
* fail(message): Display message (plus leading marker and trailing
130
newline) and explanatory messages about debugging.
131
132
* success(message): Indicate that the test script has completed
133
successfully. Suppresses the display of explanatory debugging
134
messages in the on-exit handler. message should briefly summarize
135
the operations tested; it will only be displayed (with leading
136
marker and trailing newline) if the script is running verbosely.
137
138
* skipped(whatmsg, whymsg): Indicate that some tests were skipped.
139
whatmsg should concisely say what was skipped (e.g. "LDAP KDB
140
tests") and whymsg should give the reason (e.g. "because LDAP module
141
not built").
142
143
* skip_rest(message): Indicate that some tests were skipped, then exit
144
the current script.
145
146
* output(message, force_verbose=False): Place message (without any
147
added newline) in testlog, and write it to stdout if running
148
verbosely.
149
150
* mark(message): Place a divider message in the test output, to make
151
it easier to determine what part of the test script a command
152
invocation belongs to. The last mark message will also be displayed
153
if a command invocation fails. Do not include a newline in message.
154
155
* which(progname): Return the location of progname in the executable
156
path, or None if it is not found.
157
158
* password(name): Return a weakly random password based on name. The
159
password will be consistent across calls with the same name.
160
161
* canonicalize_hostname(name, rdns=True): Return the DNS
162
canonicalization of name, optionally using reverse DNS. On error,
163
return name converted to lowercase.
164
165
* stop_daemon(proc): Stop a daemon process started with
166
realm.start_server() or realm.start_in_inetd(). Only necessary if
167
the port needs to be reused; daemon processes will be stopped
168
automatically when the script exits.
169
170
* multipass_realms(**keywords): This is an iterator function. Yields
171
a realm for each of the standard test passes, each of which alters
172
the default configuration in some way to exercise different parts of
173
the krb5 code base. keywords may contain any K5Realm initializer
174
keyword with the exception of krbtgt_keysalt, which will not be
175
honored. If keywords contains krb5_conf and/or kdc_conf fragments,
176
they will be merged with the default and per-pass specifications.
177
178
* multidb_realms(**keywords): Yields a realm for multiple DB modules.
179
Currently DB2 and LMDB are included. Ideally LDAP would be
180
included, but setting up a test LDAP server currently requires a
181
one-second delay, so all LDAP tests are currently confined to
182
t_kdb.py. keywords may contain any K5Realm initializer.
183
184
* cross_realms(num, xtgts=None, args=None, **keywords): This function
185
returns a list of num realms, where each realm's configuration knows
186
how to contact all of the realms. By default, each realm will
187
contain cross TGTs in both directions for all other realms; this
188
default may be overridden by specifying a collection of tuples in
189
the xtgts parameter, where each tuple is a pair of zero-based realm
190
indexes, indicating that the first realm can authenticate to the
191
second (i.e. krbtgt/secondrealm@firstrealm exists in both realm's
192
databases). If args is given, it should be a list of keyword
193
arguments specific to each realm; these will be merged with the
194
global keyword arguments passed to cross_realms, with specific
195
arguments taking priority.
196
197
* buildtop: The top of the build directory (absolute path).
198
199
* srctop: The top of the source directory (absolute path).
200
201
* plugins: The plugin directory in the build tree (absolute path).
202
203
* pkinit_enabled: True if the PKINIT plugin module is present in the
204
build directory.
205
206
* pkinit_certs: The directory containing test PKINIT certificates.
207
208
* hostname: The local hostname as it will initially appear in
209
krb5_sname_to_principal() results. (Shortname qualification is
210
turned off in the test environment to make this value easy to
211
discover from Python.)
212
213
* null_input: A file opened to read /dev/null.
214
215
* args: Positional arguments left over after flags are processed.
216
217
* runenv: The contents of $srctop/runenv.py, containing a dictionary
218
'env' which specifies additional variables to be added to the realm
219
environment, and a variable 'tls_impl', which indicates which TLS
220
implementation (if any) is being used by libkrb5's support for
221
contacting KDCs and kpasswd servers over HTTPS.
222
223
* verbose: Whether the script is running verbosely.
224
225
* testpass: The command-line test pass argument. The script does not
226
need to examine this argument in most cases; it will be honored in
227
multipass_realms().
228
229
* Pathname variables for programs within the build directory:
230
- krb5kdc
231
- kadmind
232
- kadmin
233
- kadminl (kadmin.local)
234
- kdb5_ldap_util
235
- kdb5_util
236
- ktutil
237
- kinit
238
- klist
239
- kswitch
240
- kvno
241
- kdestroy
242
- kpasswd
243
- t_inetd
244
- kproplog
245
- kpropd
246
- kprop
247
248
Scripts may use the following realm methods and attributes:
249
250
* realm.run(args, env=None, **keywords): Run a command in a specified
251
environment (or the realm's environment by default), obeying the
252
command-line debugging options. Fail if the command does not return
253
0. Log the command output appropriately, and return it as a single
254
multi-line string. Keyword arguments can contain input='string' to
255
send an input string to the command, expected_code=N to expect a
256
return code other than 0, expected_msg=MSG to expect a substring in
257
the command output, and expected_trace=('a', 'b', ...) to expect an
258
ordered series of line substrings in the command's KRB5_TRACE
259
output, or return_trace=True to return a tuple of the command output
260
and the trace output.
261
262
* realm.kprop_port(): Returns a port number based on realm.portbase
263
intended for use by kprop and kpropd.
264
265
* realm.server_port(): Returns a port number based on realm.portbase
266
intended for use by server processes.
267
268
* realm.start_server(args, sentinel, env=None): Start a daemon
269
process. Wait until sentinel appears as a substring of a line in
270
the server process's stdout or stderr (which are folded together).
271
Returns a subprocess.Popen object which can be passed to
272
stop_daemon() to stop the server, or used to read from the server's
273
output.
274
275
* realm.start_in_inetd(args, port=None, env=None): Begin a t_inetd
276
process which will spawn a server process after accepting a client
277
connection. If port is not specified, realm.server_port() will be
278
used. Returns a process object which can be passed to stop_daemon()
279
to stop the server.
280
281
* realm.create_kdb(): Create a new KDB.
282
283
* realm.start_kdc(args=[], env=None): Start a krb5kdc process. Errors
284
if a KDC is already running. If args is given, it contains a list
285
of additional krb5kdc arguments.
286
287
* realm.stop_kdc(): Stop the krb5kdc process. Errors if no KDC is
288
running.
289
290
* realm.start_kadmind(env=None): Start a kadmind process. Errors if a
291
kadmind is already running.
292
293
* realm.stop_kadmind(): Stop the kadmind process. Errors if no
294
kadmind is running.
295
296
* realm.stop(): Stop any daemon processes running on behalf of the
297
realm.
298
299
* realm.addprinc(princname, password=None): Using kadmin.local, create
300
a principal in the KDB named princname, with either a random or
301
specified key.
302
303
* realm.extract_keytab(princname, keytab): Using kadmin.local, create
304
a keytab for princname in the filename keytab. Uses the -norandkey
305
option to avoid re-randomizing princname's key.
306
307
* realm.kinit(princname, password=None, flags=[]): Acquire credentials
308
for princname using kinit, with additional flags []. If password is
309
specified, it will be used as input to the kinit process; otherwise
310
flags must cause kinit not to need a password (e.g. by specifying a
311
keytab).
312
313
* realm.pkinit(princ, **keywords): Acquire credentials for princ,
314
supplying a PKINIT identity of the basic user test certificate
315
(matching [email protected]).
316
317
* realm.klist(client_princ, service_princ=None, ccache=None): Using
318
klist, list the credentials cache ccache (must be a filename;
319
self.ccache if not specified) and verify that the output shows
320
credentials for client_princ and service_princ (self.krbtgt_princ if
321
not specified).
322
323
* realm.klist_keytab(princ, keytab=None): Using klist, list keytab
324
(must be a filename; self.keytab if not specified) and verify that
325
the output shows the keytab name and principal name.
326
327
* realm.prep_kadmin(princname=None, password=None, flags=[]): Populate
328
realm.kadmin_ccache with a ticket which can be used to run kadmin.
329
If princname is not specified, realm.admin_princ and its default
330
password will be used.
331
332
* realm.run_kadmin(args, **keywords): Run the specified query in
333
kadmin, using realm.kadmin_ccache to authenticate. Accepts the same
334
keyword arguments as run.
335
336
* realm.special_env(name, has_kdc_conf, krb5_conf=None,
337
kdc_conf=None): Create an environment with a modified krb5.conf
338
and/or kdc.conf. The specified krb5_conf and kdc_conf fragments, if
339
any, will be merged with the realm's existing configuration. If
340
has_kdc_conf is false, the new environment will have no kdc.conf.
341
The environment returned by this method can be used with realm.run()
342
or similar methods.
343
344
* realm.start_kpropd(env, args=[]): Start a kpropd process. Pass an
345
environment created with realm.special_env() for the replica. If
346
args is given, it contains a list of additional kpropd arguments.
347
Returns a handle to the kpropd process.
348
349
* realm.run_kpropd_once(env, args=[]): Run kpropd once, using the -t
350
flag. Pass an environment created with realm.special_env() for the
351
replica. If args is given, it contains a list of additional kpropd
352
arguments. Returns the kpropd output.
353
354
* realm.realm: The realm's name.
355
356
* realm.testdir: The realm's storage directory (absolute path).
357
358
* realm.portbase: The realm's first listener port.
359
360
* realm.user_princ: The principal name user@<realmname>.
361
362
* realm.admin_princ: The principal name user/admin@<realmname>.
363
364
* realm.host_princ: The name of the host principal for this machine,
365
with realm.
366
367
* realm.nfs_princ: The name of the nfs principal for this machine,
368
with realm.
369
370
* realm.krbtgt_princ: The name of the krbtgt principal for the realm.
371
372
* realm.keytab: A keytab file in realm.testdir. Initially contains a
373
host keytab unless disabled by the realm construction options.
374
375
* realm.client_keytab: A keytab file in realm.testdir. Initially
376
nonexistent.
377
378
* realm.ccache: A ccache file in realm.testdir. Initially contains
379
credentials for user unless disabled by the realm construction
380
options.
381
382
* realm.kadmin_ccache: The ccache file initialized by prep_kadmin and
383
used by run_kadmin.
384
385
* env: The realm's environment, extended from os.environ to point at
386
the realm's config files and the build tree's shared libraries.
387
388
When the test script is run, its behavior can be modified with
389
command-line flags. These are documented in the --help output.
390
391
"""
392
393
import atexit
394
import fcntl
395
import glob
396
import optparse
397
import os
398
import shlex
399
import shutil
400
import signal
401
import socket
402
import string
403
import subprocess
404
import sys
405
406
# Used when most things go wrong (other than programming errors) so
407
# that the user sees an error message rather than a Python traceback,
408
# without help from the test script. The on-exit handler will display
409
# additional explanatory text.
410
def fail(msg):
411
"""Print a message and exit with failure."""
412
global _current_pass
413
print("*** Failure:", msg)
414
if _last_mark:
415
print("*** Last mark: %s" % _last_mark)
416
if _last_cmd:
417
print("*** Last command (#%d): %s" % (_cmd_index - 1, _last_cmd))
418
if _failed_daemon_output:
419
print('*** Output of failed daemon:')
420
sys.stdout.write(_failed_daemon_output)
421
elif _last_cmd_output:
422
print("*** Output of last command:")
423
sys.stdout.write(_last_cmd_output)
424
if _current_pass:
425
print("*** Failed in test pass:", _current_pass)
426
if _current_db:
427
print("*** Failed with db:", _current_db)
428
sys.exit(1)
429
430
431
def success(msg):
432
global _success
433
_stop_daemons()
434
output('*** Success: %s\n' % msg)
435
_success = True
436
437
438
def mark(msg):
439
global _last_mark
440
output('\n====== %s ======\n' % msg)
441
_last_mark = msg
442
443
444
def skipped(whatmsg, whymsg):
445
output('*** Skipping: %s: %s\n' % (whatmsg, whymsg), force_verbose=True)
446
f = open(os.path.join(buildtop, 'skiptests'), 'a')
447
f.write('Skipped %s: %s\n' % (whatmsg, whymsg))
448
f.close()
449
450
451
def skip_rest(whatmsg, whymsg):
452
global _success
453
skipped(whatmsg, whymsg)
454
_stop_daemons()
455
_success = True
456
sys.exit(0)
457
458
459
def output(msg, force_verbose=False):
460
"""Output a message to testlog, and to stdout if running verbosely."""
461
_outfile.write(msg)
462
if verbose or force_verbose:
463
sys.stdout.write(msg)
464
465
466
# Return the location of progname in the executable path, or None if
467
# it is not found.
468
def which(progname):
469
for dir in os.environ["PATH"].split(os.pathsep):
470
path = os.path.join(dir, progname)
471
if os.access(path, os.X_OK):
472
return path
473
return None
474
475
476
def password(name):
477
"""Choose a weakly random password from name, consistent across calls."""
478
return name + str(os.getpid())
479
480
481
def canonicalize_hostname(name, rdns=True):
482
"""Canonicalize name using DNS, optionally with reverse DNS."""
483
try:
484
ai = socket.getaddrinfo(name, None, 0, 0, 0, socket.AI_CANONNAME)
485
except socket.gaierror as e:
486
return name.lower()
487
(family, socktype, proto, canonname, sockaddr) = ai[0]
488
489
if not rdns:
490
return canonname.lower()
491
492
try:
493
rname = socket.getnameinfo(sockaddr, socket.NI_NAMEREQD)
494
except socket.gaierror:
495
return canonname.lower()
496
return rname[0].lower()
497
498
499
# Exit handler which ensures processes are cleaned up and, on failure,
500
# prints messages to help developers debug the problem.
501
def _onexit():
502
global _daemons, _success, srctop, verbose
503
global _debug, _stop_before, _stop_after, _shell_before, _shell_after
504
if _debug or _stop_before or _stop_after or _shell_before or _shell_after:
505
# Wait before killing daemons in case one is being debugged.
506
sys.stdout.write('*** Press return to kill daemons and exit script: ')
507
sys.stdout.flush()
508
sys.stdin.readline()
509
for proc in _daemons:
510
os.kill(proc.pid, signal.SIGTERM)
511
_check_daemon(proc)
512
if not _success:
513
print
514
if not verbose:
515
testlogfile = os.path.join(os.getcwd(), 'testlog')
516
utildir = os.path.join(srctop, 'util')
517
print('For details, see: %s' % testlogfile)
518
print('Or re-run this test script with the -v flag:')
519
print(' cd %s' % os.getcwd())
520
print(' PYTHONPATH=%s %s %s -v' %
521
(utildir, sys.executable, sys.argv[0]))
522
print()
523
print('Use --debug=NUM to run a command under a debugger. Use')
524
print('--stop-after=NUM to stop after a daemon is started in order to')
525
print('attach to it with a debugger. Use --help to see other')
526
print('options.')
527
528
529
def _onsigint(signum, frame):
530
# Exit without displaying a stack trace. Suppress messages from _onexit.
531
global _success
532
_success = True
533
sys.exit(1)
534
535
536
# Find the parent of dir which is at the root of a build or source directory.
537
def _find_root(dir):
538
while True:
539
if os.path.exists(os.path.join(dir, 'lib', 'krb5', 'krb')):
540
break
541
parent = os.path.dirname(dir)
542
if (parent == dir):
543
return None
544
dir = parent
545
return dir
546
547
548
def _find_buildtop():
549
root = _find_root(os.getcwd())
550
if root is None:
551
fail('Cannot find root of krb5 build directory.')
552
if not os.path.exists(os.path.join(root, 'config.status')):
553
# Looks like an unbuilt source directory.
554
fail('This script must be run inside a krb5 build directory.')
555
return root
556
557
558
def _find_srctop():
559
scriptdir = os.path.abspath(os.path.dirname(sys.argv[0]))
560
if not scriptdir:
561
scriptdir = os.getcwd()
562
root = _find_root(scriptdir)
563
if root is None:
564
fail('Cannot find root of krb5 source directory.')
565
return os.path.abspath(root)
566
567
568
# Look for the system LLVM symbolizer, matching the logic the asan
569
# runtime would use as closely as possible.
570
def _find_symbolizer():
571
if sys.platform == 'darwin':
572
f = which('atos')
573
if f is not None:
574
return f
575
576
f = which('llvm-symbolizer')
577
if f is not None:
578
return f
579
580
# Debian-derived systems have versioned symbolizer names. If any
581
# exist, pick one of them.
582
l = glob.glob('/usr/bin/llvm-symbolizer-*')
583
if l:
584
return l[0]
585
586
f = which('addr2line')
587
if f is not None:
588
return f
589
590
return None
591
592
593
# Parse command line arguments, setting global option variables. Also
594
# sets the global variable args to the positional arguments, which may
595
# be used by the test script.
596
def _parse_args():
597
global args, verbose, testpass, _debug, _debugger_command
598
global _stop_before, _stop_after, _shell_before, _shell_after
599
parser = optparse.OptionParser()
600
parser.add_option('-v', '--verbose', action='store_true', dest='verbose',
601
default=False, help='Display verbose output')
602
parser.add_option('-p', '--pass', dest='testpass', metavar='PASS',
603
help='If a multi-pass test, run only PASS')
604
parser.add_option('--debug', dest='debug', metavar='NUM',
605
help='Debug numbered command (or "all")')
606
parser.add_option('--debugger', dest='debugger', metavar='COMMAND',
607
help='Debugger command (default is gdb --args)')
608
parser.add_option('--stop-before', dest='stopb', metavar='NUM',
609
help='Stop before numbered command (or "all")')
610
parser.add_option('--stop-after', dest='stopa', metavar='NUM',
611
help='Stop after numbered command (or "all")')
612
parser.add_option('--shell-before', dest='shellb', metavar='NUM',
613
help='Spawn shell before numbered command (or "all")')
614
parser.add_option('--shell-after', dest='shella', metavar='NUM',
615
help='Spawn shell after numbered command (or "all")')
616
(options, args) = parser.parse_args()
617
verbose = options.verbose
618
testpass = options.testpass
619
_debug = _parse_cmdnum('--debug', options.debug)
620
_stop_before = _parse_cmdnum('--stop-before', options.stopb)
621
_stop_after = _parse_cmdnum('--stop-after', options.stopa)
622
_shell_before = _parse_cmdnum('--shell-before', options.shellb)
623
_shell_after = _parse_cmdnum('--shell-after', options.shella)
624
625
if options.debugger is not None:
626
_debugger_command = shlex.split(options.debugger)
627
elif which('gdb') is not None:
628
_debugger_command = ['gdb', '--args']
629
elif which('lldb') is not None:
630
_debugger_command = ['lldb', '--']
631
elif options.debug is not None:
632
print('Cannot find a debugger; use --debugger=COMMAND')
633
sys.exit(1)
634
635
636
# Translate a command number spec. -1 means all, None means none.
637
def _parse_cmdnum(optname, str):
638
if not str:
639
return None
640
if str == 'all':
641
return -1
642
try:
643
return int(str)
644
except ValueError:
645
fail('%s value must be "all" or a number' % optname)
646
647
648
# Test if a command index matches a translated command number spec.
649
def _match_cmdnum(cmdnum, ind):
650
if cmdnum is None:
651
return False
652
elif cmdnum == -1:
653
return True
654
else:
655
return cmdnum == ind
656
657
658
# Return an environment suitable for running programs in the build
659
# tree. It is safe to modify the result.
660
def _build_env():
661
global buildtop, runenv
662
env = os.environ.copy()
663
for (k, v) in runenv.env.items():
664
if v.find('./') == 0:
665
env[k] = os.path.join(buildtop, v)
666
else:
667
env[k] = v
668
# Make sure we don't get confused by translated messages
669
# or localized times.
670
env['LC_ALL'] = 'C'
671
return env
672
673
674
# Merge the nested dictionaries cfg1 and cfg2 into a new dictionary.
675
# cfg1 or cfg2 may be None, in which case the other is returned. If
676
# cfg2 contains keys mapped to None, the corresponding keys will be
677
# mapped to None in the result. The result may contain references to
678
# parts of cfg1 or cfg2, so is not safe to modify.
679
def _cfg_merge(cfg1, cfg2):
680
if not cfg2:
681
return cfg1
682
if not cfg1:
683
return cfg2
684
result = cfg1.copy()
685
for key, value2 in cfg2.items():
686
if value2 is None:
687
result.pop(key, None)
688
elif key not in result:
689
result[key] = value2
690
else:
691
value1 = result[key]
692
if isinstance(value1, dict):
693
if not isinstance(value2, dict):
694
raise TypeError()
695
result[key] = _cfg_merge(value1, value2)
696
else:
697
result[key] = value2
698
return result
699
700
701
# We would like to use shlex.join() from Python 3.8. For now use
702
# shlex.quote() from Python 3.3.
703
def _shell_equiv(args):
704
return ' '.join(shlex.quote(x) for x in args)
705
706
707
# Add a valgrind prefix to the front of args if specified in the
708
# environment. Under normal circumstances this just returns args.
709
def _valgrind(args):
710
valgrind = os.getenv('VALGRIND')
711
if valgrind:
712
args = shlex.split(valgrind) + args
713
return args
714
715
716
def _stop_or_shell(stop, shell, env, ind):
717
if (_match_cmdnum(stop, ind)):
718
sys.stdout.write('*** [%d] Waiting for return: ' % ind)
719
sys.stdout.flush()
720
sys.stdin.readline()
721
if (_match_cmdnum(shell, ind)):
722
output('*** [%d] Spawning shell\n' % ind, True)
723
subprocess.call(os.getenv('SHELL'), env=env)
724
725
726
# Look for the expected strings in successive lines of trace.
727
def _check_trace(trace, expected):
728
i = 0
729
for line in trace.splitlines():
730
if i < len(expected) and expected[i] in line:
731
i += 1
732
if i < len(expected):
733
fail('Expected string not found in trace output: ' + expected[i])
734
735
736
def _run_cmd(args, env, input=None, expected_code=0, expected_msg=None,
737
expected_trace=None, return_trace=False):
738
global null_input, _cmd_index, _last_cmd, _last_cmd_output, _debug
739
global _stop_before, _stop_after, _shell_before, _shell_after
740
741
tracefile = None
742
if expected_trace is not None or return_trace:
743
tracefile = 'testtrace'
744
if os.path.exists(tracefile):
745
os.remove(tracefile)
746
env = env.copy()
747
env['KRB5_TRACE'] = tracefile
748
749
if (_match_cmdnum(_debug, _cmd_index)):
750
return _debug_cmd(args, env, input)
751
752
args = _valgrind(args)
753
_last_cmd = _shell_equiv(args)
754
755
output('*** [%d] Executing: %s\n' % (_cmd_index, _last_cmd))
756
_stop_or_shell(_stop_before, _shell_before, env, _cmd_index)
757
758
if input:
759
infile = subprocess.PIPE
760
else:
761
infile = null_input
762
763
# Run the command and log the result, folding stderr into stdout.
764
proc = subprocess.Popen(args, stdin=infile, stdout=subprocess.PIPE,
765
stderr=subprocess.STDOUT, env=env,
766
universal_newlines=True)
767
(outdata, dummy_errdata) = proc.communicate(input)
768
_last_cmd_output = outdata
769
code = proc.returncode
770
output(outdata)
771
output('*** [%d] Completed with return code %d\n' % (_cmd_index, code))
772
_stop_or_shell(_stop_after, _shell_after, env, _cmd_index)
773
_cmd_index += 1
774
775
# Check the return code and return the output.
776
if code != expected_code:
777
fail('%s failed with code %d.' % (args[0], code))
778
779
if expected_msg is not None and expected_msg not in outdata:
780
fail('Expected string not found in command output: ' + expected_msg)
781
782
if tracefile is not None:
783
with open(tracefile, 'r') as f:
784
trace = f.read()
785
output('*** Trace output for previous command:\n')
786
output(trace)
787
if expected_trace is not None:
788
_check_trace(trace, expected_trace)
789
790
return (outdata, trace) if return_trace else outdata
791
792
793
def _debug_cmd(args, env, input):
794
global _cmd_index, _debugger_command
795
796
args = _debugger_command + list(args)
797
output('*** [%d] Executing in debugger: %s\n' %
798
(_cmd_index, _shell_equiv(args)), True)
799
if input:
800
print
801
print('*** Enter the following input when appropriate:')
802
print()
803
print(input)
804
print()
805
code = subprocess.call(args, env=env)
806
output('*** [%d] Completed in debugger with return code %d\n' %
807
(_cmd_index, code))
808
_cmd_index += 1
809
810
811
# Start a daemon process with the specified args and env. Wait until
812
# we see sentinel as a substring of a line on either stdout or stderr.
813
# Clean up the daemon process on exit.
814
def _start_daemon(args, env, sentinel):
815
global null_input, _cmd_index, _last_cmd, _last_cmd_output, _debug
816
global _stop_before, _stop_after, _shell_before, _shell_after
817
818
if (_match_cmdnum(_debug, _cmd_index)):
819
output('*** [%d] Warning: ' % _cmd_index, True)
820
output( 'test script cannot proceed after debugging a daemon\n', True)
821
_debug_cmd(args, env, None)
822
output('*** Exiting after debugging daemon\n', True)
823
sys.exit(1)
824
825
args = _valgrind(args)
826
_last_cmd = _shell_equiv(args)
827
output('*** [%d] Starting: %s\n' % (_cmd_index, _last_cmd))
828
_stop_or_shell(_stop_before, _shell_before, env, _cmd_index)
829
830
# Start the daemon and look for the sentinel in stdout or stderr.
831
proc = subprocess.Popen(args, stdin=null_input, stdout=subprocess.PIPE,
832
stderr=subprocess.STDOUT, env=env,
833
universal_newlines=True)
834
_last_cmd_output = ''
835
while True:
836
line = proc.stdout.readline()
837
_last_cmd_output += line
838
if line == "":
839
code = proc.wait()
840
fail('%s failed to start with code %d.' % (args[0], code))
841
output(line)
842
if sentinel in line:
843
break
844
output('*** [%d] Started with pid %d\n' % (_cmd_index, proc.pid))
845
_stop_or_shell(_stop_after, _shell_after, env, _cmd_index)
846
_cmd_index += 1
847
848
# Save the daemon in a list for cleanup. Note that we won't read
849
# any more of the daemon's output after the sentinel, which will
850
# cause the daemon to block if it generates enough. For now we
851
# assume all daemon processes are quiet enough to avoid this
852
# problem. If it causes an issue, some alternatives are:
853
# - Output to a file and poll the file for the sentinel
854
# (undesirable because it slows down the test suite by the
855
# polling interval times the number of daemons started)
856
# - Create an intermediate subprocess which discards output
857
# after the sentinel.
858
_daemons.append(proc)
859
860
# Return the process; the caller can stop it with stop_daemon.
861
return proc
862
863
864
# Await a daemon process's exit status and display it if it isn't
865
# successful. Display any output it generated after the sentinel.
866
# Return the daemon's exit status (0 if it terminated with SIGTERM).
867
def _check_daemon(proc):
868
global _failed_daemon_output
869
code = proc.wait()
870
# If a daemon doesn't catch SIGTERM (like gss-server), treat it as
871
# a normal exit.
872
if code == -signal.SIGTERM:
873
code = 0
874
if code != 0:
875
output('*** Daemon pid %d exited with code %d\n' % (proc.pid, code))
876
877
out, err = proc.communicate()
878
if code != 0:
879
_failed_daemon_output = out
880
output('*** Daemon pid %d output:\n' % proc.pid)
881
output(out)
882
883
return code
884
885
886
# Terminate all active daemon processes. Fail out if any of them
887
# exited unsuccessfully.
888
def _stop_daemons():
889
global _daemons
890
daemon_error = False
891
for proc in _daemons:
892
os.kill(proc.pid, signal.SIGTERM)
893
code = _check_daemon(proc)
894
if code != 0:
895
daemon_error = True
896
_daemons = []
897
if daemon_error:
898
fail('One or more daemon processes exited with an error')
899
900
901
# Wait for a daemon process to exit. Fail out if it exits
902
# unsuccessfully.
903
def await_daemon_exit(proc):
904
code = _check_daemon(proc)
905
_daemons.remove(proc)
906
if code != 0:
907
fail('Daemon exited unsuccessfully')
908
909
910
# Terminate one daemon process. Fail out if it exits unsuccessfully.
911
def stop_daemon(proc):
912
os.kill(proc.pid, signal.SIGTERM)
913
return await_daemon_exit(proc)
914
915
916
class K5Realm(object):
917
"""An object representing a functional krb5 test realm."""
918
919
def __init__(self, realm='KRBTEST.COM', portbase=61000, testdir='testdir',
920
krb5_conf=None, kdc_conf=None, create_kdb=True,
921
krbtgt_keysalt=None, create_user=True, get_creds=True,
922
create_host=True, start_kdc=True, start_kadmind=False,
923
start_kpropd=False, bdb_only=False, pkinit=False):
924
global hostname, _default_krb5_conf, _default_kdc_conf
925
global _lmdb_kdc_conf, _current_db
926
927
self.realm = realm
928
self.testdir = os.path.join(os.getcwd(), testdir)
929
self.portbase = portbase
930
self.user_princ = 'user@' + self.realm
931
self.admin_princ = 'user/admin@' + self.realm
932
self.host_princ = 'host/%s@%s' % (hostname, self.realm)
933
self.nfs_princ = 'nfs/%s@%s' % (hostname, self.realm)
934
self.krbtgt_princ = 'krbtgt/%s@%s' % (self.realm, self.realm)
935
self.keytab = os.path.join(self.testdir, 'keytab')
936
self.client_keytab = os.path.join(self.testdir, 'client_keytab')
937
self.ccache = os.path.join(self.testdir, 'ccache')
938
self.gss_mech_config = os.path.join(self.testdir, 'mech.conf')
939
self.kadmin_ccache = os.path.join(self.testdir, 'kadmin_ccache')
940
base_krb5_conf = _default_krb5_conf
941
base_kdc_conf = _default_kdc_conf
942
if (os.getenv('K5TEST_LMDB') is not None and
943
not bdb_only and not _current_db):
944
base_kdc_conf = _cfg_merge(base_kdc_conf, _lmdb_kdc_conf)
945
if pkinit:
946
base_krb5_conf = _cfg_merge(base_krb5_conf, _pkinit_krb5_conf)
947
base_kdc_conf = _cfg_merge(base_kdc_conf, _pkinit_kdc_conf)
948
self._krb5_conf = _cfg_merge(base_krb5_conf, krb5_conf)
949
self._kdc_conf = _cfg_merge(base_kdc_conf, kdc_conf)
950
self._kdc_proc = None
951
self._kadmind_proc = None
952
self._kpropd_procs = []
953
krb5_conf_path = os.path.join(self.testdir, 'krb5.conf')
954
kdc_conf_path = os.path.join(self.testdir, 'kdc.conf')
955
self.env = self._make_env(krb5_conf_path, kdc_conf_path)
956
957
self._create_empty_dir()
958
self._create_conf(self._krb5_conf, krb5_conf_path)
959
self._create_conf(self._kdc_conf, kdc_conf_path)
960
self._create_acl()
961
self._create_dictfile()
962
963
if create_kdb:
964
self.create_kdb()
965
if krbtgt_keysalt and create_kdb:
966
self.run([kadminl, 'cpw', '-randkey', '-e', krbtgt_keysalt,
967
self.krbtgt_princ])
968
if create_user and create_kdb:
969
self.addprinc(self.user_princ, password('user'))
970
self.addprinc(self.admin_princ, password('admin'))
971
if create_host and create_kdb:
972
self.addprinc(self.host_princ)
973
self.extract_keytab(self.host_princ, self.keytab)
974
if start_kdc and create_kdb:
975
self.start_kdc()
976
if start_kadmind and create_kdb:
977
self.start_kadmind()
978
if get_creds and create_kdb and create_user and start_kdc:
979
self.kinit(self.user_princ, password('user'))
980
self.klist(self.user_princ)
981
self._setup_symbolizer()
982
983
def _create_empty_dir(self):
984
dir = self.testdir
985
shutil.rmtree(dir, True)
986
if (os.path.exists(dir)):
987
fail('Cannot remove %s to create test realm.' % dir)
988
os.mkdir(dir)
989
990
def _create_conf(self, profile, filename):
991
file = open(filename, 'w')
992
for section, contents in profile.items():
993
file.write('[%s]\n' % section)
994
self._write_cfg_section(file, contents, 1)
995
file.close()
996
997
def _write_cfg_section(self, file, contents, indent_level):
998
indent = '\t' * indent_level
999
for name, value in contents.items():
1000
name = self._subst_cfg_value(name)
1001
if isinstance(value, dict):
1002
# A dictionary value yields a list subsection.
1003
file.write('%s%s = {\n' % (indent, name))
1004
self._write_cfg_section(file, value, indent_level + 1)
1005
file.write('%s}\n' % indent)
1006
elif isinstance(value, list):
1007
# A list value yields multiple values for the same name.
1008
for item in value:
1009
item = self._subst_cfg_value(item)
1010
file.write('%s%s = %s\n' % (indent, name, item))
1011
elif isinstance(value, str):
1012
# A string value yields a straightforward variable setting.
1013
value = self._subst_cfg_value(value)
1014
file.write('%s%s = %s\n' % (indent, name, value))
1015
else:
1016
raise TypeError()
1017
1018
def _subst_cfg_value(self, value):
1019
global buildtop, srctop, hostname
1020
template = string.Template(value)
1021
subst = template.substitute(realm=self.realm,
1022
testdir=self.testdir,
1023
buildtop=buildtop,
1024
srctop=srctop,
1025
plugins=plugins,
1026
certs=pkinit_certs,
1027
hostname=hostname,
1028
port0=self.portbase,
1029
port1=self.portbase + 1,
1030
port2=self.portbase + 2,
1031
port3=self.portbase + 3,
1032
port4=self.portbase + 4,
1033
port5=self.portbase + 5,
1034
port6=self.portbase + 6,
1035
port7=self.portbase + 7,
1036
port8=self.portbase + 8,
1037
port9=self.portbase + 9)
1038
# Empty values must be quoted to avoid a syntax error.
1039
return subst if subst else '""'
1040
1041
def _create_acl(self):
1042
global hostname
1043
filename = os.path.join(self.testdir, 'acl')
1044
file = open(filename, 'w')
1045
file.write('%s *e\n' % self.admin_princ)
1046
file.write('kiprop/%s@%s p\n' % (hostname, self.realm))
1047
file.close()
1048
1049
def _create_dictfile(self):
1050
filename = os.path.join(self.testdir, 'dictfile')
1051
file = open(filename, 'w')
1052
file.write('weak_password\n')
1053
file.close()
1054
1055
def _make_env(self, krb5_conf_path, kdc_conf_path):
1056
env = _build_env()
1057
env['KRB5_CONFIG'] = krb5_conf_path
1058
env['KRB5_KDC_PROFILE'] = kdc_conf_path or os.devnull
1059
env['KRB5CCNAME'] = self.ccache
1060
env['KRB5_KTNAME'] = self.keytab
1061
env['KRB5_CLIENT_KTNAME'] = self.client_keytab
1062
env['KRB5RCACHEDIR'] = self.testdir
1063
env['KPROPD_PORT'] = str(self.kprop_port())
1064
env['KPROP_PORT'] = str(self.kprop_port())
1065
env['GSS_MECH_CONFIG'] = self.gss_mech_config
1066
return env
1067
1068
# The krb5 libraries may be included in the dependency chain of
1069
# llvm-symbolizer, which is invoked by asan when displaying stack
1070
# traces. If they are, asan-compiled krb5 libraries in
1071
# LD_LIBRARY_PATH (or similar) will cause a dynamic linker error
1072
# for the symbolizer at startup. Work around this problem by
1073
# wrapping the symbolizer in a script that unsets the dynamic
1074
# linker variables before calling the real symbolizer.
1075
def _setup_symbolizer(self):
1076
if runenv.asan != 'yes':
1077
return
1078
if 'ASAN_SYMBOLIZER_PATH' in self.env:
1079
return
1080
symbolizer_path = _find_symbolizer()
1081
if symbolizer_path is None:
1082
return
1083
wrapper_path = os.path.join(self.testdir, 'llvm-symbolizer')
1084
with open(wrapper_path, 'w') as f:
1085
f.write('#!/bin/sh\n')
1086
for v in runenv.env:
1087
f.write('unset %s\n' % v)
1088
f.write('exec %s "$@"\n' % symbolizer_path)
1089
os.chmod(wrapper_path, 0o755)
1090
self.env['ASAN_SYMBOLIZER_PATH'] = wrapper_path
1091
1092
def run(self, args, env=None, **keywords):
1093
if env is None:
1094
env = self.env
1095
return _run_cmd(args, env, **keywords)
1096
1097
def kprop_port(self):
1098
return self.portbase + 3
1099
1100
def server_port(self):
1101
return self.portbase + 5
1102
1103
def start_server(self, args, sentinel, env=None):
1104
if env is None:
1105
env = self.env
1106
return _start_daemon(args, env, sentinel)
1107
1108
def start_in_inetd(self, args, port=None, env=None):
1109
if not port:
1110
port = self.server_port()
1111
if env is None:
1112
env = self.env
1113
inetd_args = [t_inetd, str(port), args[0]] + args
1114
return _start_daemon(inetd_args, env, 'Ready!')
1115
1116
def create_kdb(self):
1117
global kdb5_util
1118
self.run([kdb5_util, 'create', '-s', '-P', 'master'])
1119
1120
def start_kdc(self, args=[], env=None):
1121
global krb5kdc
1122
if env is None:
1123
env = self.env
1124
assert(self._kdc_proc is None)
1125
self._kdc_proc = _start_daemon([krb5kdc, '-n'] + args, env,
1126
'starting...')
1127
1128
def stop_kdc(self):
1129
assert(self._kdc_proc is not None)
1130
stop_daemon(self._kdc_proc)
1131
self._kdc_proc = None
1132
1133
def start_kadmind(self, env=None):
1134
global krb5kdc
1135
if env is None:
1136
env = self.env
1137
assert(self._kadmind_proc is None)
1138
dump_path = os.path.join(self.testdir, 'dump')
1139
self._kadmind_proc = _start_daemon([kadmind, '-nofork',
1140
'-p', kdb5_util, '-K', kprop,
1141
'-F', dump_path], env,
1142
'starting...')
1143
1144
def stop_kadmind(self):
1145
assert(self._kadmind_proc is not None)
1146
stop_daemon(self._kadmind_proc)
1147
self._kadmind_proc = None
1148
1149
def _kpropd_args(self):
1150
datatrans_path = os.path.join(self.testdir, 'incoming-datatrans')
1151
kpropdacl_path = os.path.join(self.testdir, 'kpropd-acl')
1152
return [kpropd, '-D', '-P', str(self.kprop_port()),
1153
'-f', datatrans_path, '-p', kdb5_util, '-a', kpropdacl_path]
1154
1155
def start_kpropd(self, env, args=[]):
1156
proc = _start_daemon(self._kpropd_args() + args, env, 'ready')
1157
self._kpropd_procs.append(proc)
1158
return proc
1159
1160
def stop_kpropd(self, proc):
1161
stop_daemon(proc)
1162
self._kpropd_procs.remove(proc)
1163
1164
def run_kpropd_once(self, env, args=[]):
1165
return self.run(self._kpropd_args() + ['-t'] + args, env=env)
1166
1167
def stop(self):
1168
if self._kdc_proc:
1169
self.stop_kdc()
1170
if self._kadmind_proc:
1171
self.stop_kadmind()
1172
for p in self._kpropd_procs:
1173
stop_daemon(p)
1174
self._kpropd_procs = []
1175
1176
def addprinc(self, princname, password=None):
1177
if password:
1178
self.run([kadminl, 'addprinc', '-pw', password, princname])
1179
else:
1180
self.run([kadminl, 'addprinc', '-randkey', princname])
1181
1182
def extract_keytab(self, princname, keytab):
1183
self.run([kadminl, 'ktadd', '-k', keytab, '-norandkey', princname])
1184
1185
def kinit(self, princname, password=None, flags=[], **keywords):
1186
if password:
1187
input = password + "\n"
1188
else:
1189
input = None
1190
return self.run([kinit] + flags + [princname], input=input, **keywords)
1191
1192
def pkinit(self, princ, flags=[], **kw):
1193
id = 'FILE:%s,%s' % (os.path.join(pkinit_certs, 'user.pem'),
1194
os.path.join(pkinit_certs, 'privkey.pem'))
1195
flags = flags + ['-X', 'X509_user_identity=%s' % id]
1196
self.kinit(princ, flags=flags, **kw)
1197
1198
def klist(self, client_princ, service_princ=None, ccache=None, **keywords):
1199
if service_princ is None:
1200
service_princ = self.krbtgt_princ
1201
if ccache is None:
1202
ccache = self.ccache
1203
ccachestr = ccache
1204
if len(ccachestr) < 2 or ':' not in ccachestr[2:]:
1205
ccachestr = 'FILE:' + ccachestr
1206
output = self.run([klist, ccache], **keywords)
1207
if (('Ticket cache: %s\n' % ccachestr) not in output or
1208
('Default principal: %s\n' % client_princ) not in output or
1209
service_princ not in output):
1210
fail('Unexpected klist output.')
1211
1212
def klist_keytab(self, princ, keytab=None, **keywords):
1213
if keytab is None:
1214
keytab = self.keytab
1215
output = self.run([klist, '-k', keytab], **keywords)
1216
if (('Keytab name: FILE:%s\n' % keytab) not in output or
1217
'KVNO Principal\n----' not in output or
1218
princ not in output):
1219
fail('Unexpected klist output.')
1220
1221
def prep_kadmin(self, princname=None, pw=None, flags=[]):
1222
if princname is None:
1223
princname = self.admin_princ
1224
pw = password('admin')
1225
return self.kinit(princname, pw,
1226
flags=['-S', 'kadmin/admin',
1227
'-c', self.kadmin_ccache] + flags)
1228
1229
def run_kadmin(self, args, **keywords):
1230
return self.run([kadmin, '-c', self.kadmin_ccache] + args, **keywords)
1231
1232
def special_env(self, name, has_kdc_conf, krb5_conf=None, kdc_conf=None):
1233
krb5_conf_path = os.path.join(self.testdir, 'krb5.conf.%s' % name)
1234
krb5_conf = _cfg_merge(self._krb5_conf, krb5_conf)
1235
self._create_conf(krb5_conf, krb5_conf_path)
1236
if has_kdc_conf:
1237
kdc_conf_path = os.path.join(self.testdir, 'kdc.conf.%s' % name)
1238
kdc_conf = _cfg_merge(self._kdc_conf, kdc_conf)
1239
self._create_conf(kdc_conf, kdc_conf_path)
1240
else:
1241
kdc_conf_path = None
1242
return self._make_env(krb5_conf_path, kdc_conf_path)
1243
1244
1245
def multipass_realms(**keywords):
1246
global _current_pass, _passes, testpass
1247
caller_krb5_conf = keywords.get('krb5_conf')
1248
caller_kdc_conf = keywords.get('kdc_conf')
1249
for p in _passes:
1250
(name, krbtgt_keysalt, krb5_conf, kdc_conf) = p
1251
if testpass and name != testpass:
1252
continue
1253
output('*** Beginning pass %s\n' % name)
1254
keywords['krb5_conf'] = _cfg_merge(krb5_conf, caller_krb5_conf)
1255
keywords['kdc_conf'] = _cfg_merge(kdc_conf, caller_kdc_conf)
1256
keywords['krbtgt_keysalt'] = krbtgt_keysalt
1257
_current_pass = name
1258
realm = K5Realm(**keywords)
1259
yield realm
1260
realm.stop()
1261
_current_pass = None
1262
1263
1264
def multidb_realms(**keywords):
1265
global _current_db, _dbpasses
1266
caller_kdc_conf = keywords.get('kdc_conf')
1267
for p in _dbpasses:
1268
(name, kdc_conf) = p
1269
output('*** Using DB type %s\n' % name)
1270
keywords['kdc_conf'] = _cfg_merge(kdc_conf, caller_kdc_conf)
1271
_current_db = name
1272
realm = K5Realm(**keywords)
1273
yield realm
1274
realm.stop()
1275
_current_db = None
1276
1277
1278
def cross_realms(num, xtgts=None, args=None, **keywords):
1279
# Build keyword args for each realm.
1280
realm_args = []
1281
for i in range(num):
1282
realmnumber = i + 1
1283
# Start with any global keyword arguments to this function.
1284
a = keywords.copy()
1285
if args and args[i]:
1286
# Merge in specific arguments for this realm. Use
1287
# _cfg_merge for config fragments.
1288
a.update(args[i])
1289
for cf in ('krb5_conf', 'kdc_conf'):
1290
if cf in keywords and cf in args[i]:
1291
a[cf] = _cfg_merge(keywords[cf], args[i][cf])
1292
# Set defaults for the realm name, testdir, and portbase.
1293
if not 'realm' in a:
1294
a['realm'] = 'KRBTEST%d.COM' % realmnumber
1295
if not 'testdir' in a:
1296
a['testdir'] = os.path.join('testdir', str(realmnumber))
1297
if not 'portbase' in a:
1298
a['portbase'] = 61000 + 10 * realmnumber
1299
realm_args.append(a)
1300
1301
# Build a [realms] config fragment containing all of the realms.
1302
realmsection = { '$realm' : None }
1303
for a in realm_args:
1304
name = a['realm']
1305
portbase = a['portbase']
1306
realmsection[name] = {
1307
'kdc' : '$hostname:%d' % portbase,
1308
'admin_server' : '$hostname:%d' % (portbase + 1),
1309
'kpasswd_server' : '$hostname:%d' % (portbase + 2)
1310
}
1311
realmscfg = {'realms': realmsection}
1312
1313
# Set realmsection in each realm's krb5_conf keyword argument.
1314
for a in realm_args:
1315
a['krb5_conf'] = _cfg_merge(realmscfg, a.get('krb5_conf'))
1316
1317
if xtgts is None:
1318
# Default to cross tgts for every pair of realms.
1319
# (itertools.permutations would work here but is new in 2.6.)
1320
xtgts = [(x,y) for x in range(num) for y in range(num) if x != y]
1321
1322
# Create the realms.
1323
realms = []
1324
for i in range(num):
1325
r = K5Realm(**realm_args[i])
1326
# Create specified cross TGTs in this realm's db.
1327
for j in range(num):
1328
if j == i:
1329
continue
1330
iname = r.realm
1331
jname = realm_args[j]['realm']
1332
if (i, j) in xtgts:
1333
# This realm can authenticate to realm j.
1334
r.addprinc('krbtgt/%s' % jname, password('cr-%d-%d-' % (i, j)))
1335
if (j, i) in xtgts:
1336
# Realm j can authenticate to this realm.
1337
r.addprinc('krbtgt/%s@%s' % (iname, jname),
1338
password('cr-%d-%d-' % (j, i)))
1339
realms.append(r)
1340
return realms
1341
1342
1343
_default_krb5_conf = {
1344
'libdefaults': {
1345
'default_realm': '$realm',
1346
'dns_lookup_kdc': 'false',
1347
'dns_canonicalize_hostname': 'fallback',
1348
'qualify_shortname': '',
1349
'plugin_base_dir': '$plugins'},
1350
'realms': {'$realm': {
1351
'kdc': '$hostname:$port0',
1352
'admin_server': '$hostname:$port1',
1353
'kpasswd_server': '$hostname:$port2'}}}
1354
1355
1356
_default_kdc_conf = {
1357
'realms': {'$realm': {
1358
'database_module': 'db',
1359
'iprop_port': '$port4',
1360
'key_stash_file': '$testdir/stash',
1361
'acl_file': '$testdir/acl',
1362
'dict_file': '$testdir/dictfile',
1363
'kadmind_port': '$port1',
1364
'kpasswd_port': '$port2',
1365
'kdc_listen': '$port0',
1366
'kdc_tcp_listen': '$port0'}},
1367
'dbmodules': {
1368
'db_module_dir': '$plugins/kdb',
1369
'db': {'db_library': 'db2', 'database_name' : '$testdir/db'}},
1370
'logging': {
1371
'admin_server': 'FILE:$testdir/kadmind5.log',
1372
'kdc': 'FILE:$testdir/kdc.log',
1373
'default': 'FILE:$testdir/others.log'}}
1374
1375
1376
_lmdb_kdc_conf = {'dbmodules': {'db': {'db_library': 'klmdb',
1377
'nosync': 'true'}}}
1378
1379
1380
_pkinit_krb5_conf = {'realms': {'$realm': {
1381
'pkinit_anchors': 'FILE:$certs/ca.pem'}}}
1382
_pkinit_kdc_conf = {'realms': {'$realm': {
1383
'pkinit_identity': 'FILE:$certs/kdc.pem,$certs/privkey.pem'}}}
1384
1385
1386
# A pass is a tuple of: name, krbtgt_keysalt, krb5_conf, kdc_conf.
1387
_passes = [
1388
# No special settings; exercises AES256.
1389
('default', None, None, None),
1390
1391
# Exercise the DES3 enctype.
1392
('des3', None,
1393
{'libdefaults': {'permitted_enctypes': 'des3', 'allow_des3': 'true'}},
1394
{'realms': {'$realm': {
1395
'supported_enctypes': 'des3-cbc-sha1:normal',
1396
'master_key_type': 'des3-cbc-sha1'}}}),
1397
1398
# Exercise the arcfour enctype.
1399
('arcfour', None,
1400
{'libdefaults': {'permitted_enctypes': 'rc4', 'allow_rc4': 'true'}},
1401
{'realms': {'$realm': {
1402
'supported_enctypes': 'arcfour-hmac:normal',
1403
'master_key_type': 'arcfour-hmac'}}}),
1404
1405
# Exercise the AES128 enctype.
1406
('aes128', None,
1407
{'libdefaults': {'permitted_enctypes': 'aes128-cts'}},
1408
{'realms': {'$realm': {
1409
'supported_enctypes': 'aes128-cts:normal',
1410
'master_key_type': 'aes128-cts'}}}),
1411
1412
# Exercise the camellia256-cts enctype.
1413
('camellia256', None,
1414
{'libdefaults': {'permitted_enctypes': 'camellia256-cts'}},
1415
{'realms': {'$realm': {
1416
'supported_enctypes': 'camellia256-cts:normal',
1417
'master_key_type': 'camellia256-cts'}}}),
1418
1419
# Exercise the aes128-sha2 enctype.
1420
('aes128-sha2', None,
1421
{'libdefaults': {'permitted_enctypes': 'aes128-sha2'}},
1422
{'realms': {'$realm': {
1423
'supported_enctypes': 'aes128-sha2:normal',
1424
'master_key_type': 'aes128-sha2'}}}),
1425
1426
# Exercise the aes256-sha2 enctype.
1427
('aes256-sha2', None,
1428
{'libdefaults': {'permitted_enctypes': 'aes256-sha2'}},
1429
{'realms': {'$realm': {
1430
'supported_enctypes': 'aes256-sha2:normal',
1431
'master_key_type': 'aes256-sha2'}}}),
1432
1433
# Test a setup with modern principal keys but an old TGT key.
1434
('aes256.destgt', 'arcfour-hmac:normal',
1435
{'libdefaults': {'allow_weak_crypto': 'true'}},
1436
None)
1437
]
1438
1439
_success = False
1440
_current_pass = None
1441
_current_db = None
1442
_daemons = []
1443
_parse_args()
1444
atexit.register(_onexit)
1445
signal.signal(signal.SIGINT, _onsigint)
1446
_outfile = open('testlog', 'w')
1447
_cmd_index = 1
1448
_last_mark = None
1449
_last_cmd = None
1450
_last_cmd_output = None
1451
_failed_daemon_output = None
1452
buildtop = _find_buildtop()
1453
srctop = _find_srctop()
1454
plugins = os.path.join(buildtop, 'plugins')
1455
pkinit_enabled = os.path.exists(os.path.join(plugins, 'preauth', 'pkinit.so'))
1456
pkinit_certs = os.path.join(srctop, 'tests', 'pkinit-certs')
1457
hostname = socket.gethostname().lower()
1458
null_input = open(os.devnull, 'r')
1459
1460
if not os.path.exists(os.path.join(buildtop, 'runenv.py')):
1461
fail('You must run "make runenv.py" in %s first.' % buildtop)
1462
sys.path = [buildtop] + sys.path
1463
import runenv
1464
1465
# A DB pass is a tuple of: name, kdc_conf.
1466
_dbpasses = [('db2', None)]
1467
if runenv.have_lmdb == 'yes':
1468
_dbpasses.append(('lmdb', _lmdb_kdc_conf))
1469
1470
krb5kdc = os.path.join(buildtop, 'kdc', 'krb5kdc')
1471
kadmind = os.path.join(buildtop, 'kadmin', 'server', 'kadmind')
1472
kadmin = os.path.join(buildtop, 'kadmin', 'cli', 'kadmin')
1473
kadminl = os.path.join(buildtop, 'kadmin', 'cli', 'kadmin.local')
1474
kdb5_ldap_util = os.path.join(buildtop, 'plugins', 'kdb', 'ldap', 'ldap_util',
1475
'kdb5_ldap_util')
1476
kdb5_util = os.path.join(buildtop, 'kadmin', 'dbutil', 'kdb5_util')
1477
ktutil = os.path.join(buildtop, 'kadmin', 'ktutil', 'ktutil')
1478
kinit = os.path.join(buildtop, 'clients', 'kinit', 'kinit')
1479
klist = os.path.join(buildtop, 'clients', 'klist', 'klist')
1480
kswitch = os.path.join(buildtop, 'clients', 'kswitch', 'kswitch')
1481
kvno = os.path.join(buildtop, 'clients', 'kvno', 'kvno')
1482
kdestroy = os.path.join(buildtop, 'clients', 'kdestroy', 'kdestroy')
1483
kpasswd = os.path.join(buildtop, 'clients', 'kpasswd', 'kpasswd')
1484
t_inetd = os.path.join(buildtop, 'tests', 't_inetd')
1485
kproplog = os.path.join(buildtop, 'kprop', 'kproplog')
1486
kpropd = os.path.join(buildtop, 'kprop', 'kpropd')
1487
kprop = os.path.join(buildtop, 'kprop', 'kprop')
1488
1489