xref: /freebsd/crypto/krb5/src/util/k5test.py (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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
25To run test scripts during "make check" (if Python 2.5 or later is
26available), add rules like the following to Makefile.in:
27
28    check-pytests::
29	$(RUNPYTEST) $(srcdir)/t_testname.py $(PYTESTFLAGS)
30
31A 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
47By 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
61The realm's behaviour can be modified with the following constructor
62keyword 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
127Scripts 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
248Scripts 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 user@KRBTEST.COM).
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
388When the test script is run, its behavior can be modified with
389command-line flags.  These are documented in the --help output.
390
391"""
392
393import atexit
394import fcntl
395import glob
396import optparse
397import os
398import shlex
399import shutil
400import signal
401import socket
402import string
403import subprocess
404import 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.
410def 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
431def success(msg):
432    global _success
433    _stop_daemons()
434    output('*** Success: %s\n' % msg)
435    _success = True
436
437
438def mark(msg):
439    global _last_mark
440    output('\n====== %s ======\n' % msg)
441    _last_mark = msg
442
443
444def 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
451def skip_rest(whatmsg, whymsg):
452    global _success
453    skipped(whatmsg, whymsg)
454    _stop_daemons()
455    _success = True
456    sys.exit(0)
457
458
459def 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.
468def 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
476def password(name):
477    """Choose a weakly random password from name, consistent across calls."""
478    return name + str(os.getpid())
479
480
481def 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.
501def _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
529def _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.
537def _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
548def _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
558def _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.
570def _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.
596def _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.
637def _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.
649def _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.
660def _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.
679def _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.
703def _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.
709def _valgrind(args):
710    valgrind = os.getenv('VALGRIND')
711    if valgrind:
712        args = shlex.split(valgrind) + args
713    return args
714
715
716def _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.
727def _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
736def _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
793def _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.
814def _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).
867def _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.
888def _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.
903def 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.
911def stop_daemon(proc):
912    os.kill(proc.pid, signal.SIGTERM)
913    return await_daemon_exit(proc)
914
915
916class 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
1245def 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
1264def 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
1278def 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()
1444atexit.register(_onexit)
1445signal.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
1452buildtop = _find_buildtop()
1453srctop = _find_srctop()
1454plugins = os.path.join(buildtop, 'plugins')
1455pkinit_enabled = os.path.exists(os.path.join(plugins, 'preauth', 'pkinit.so'))
1456pkinit_certs = os.path.join(srctop, 'tests', 'pkinit-certs')
1457hostname = socket.gethostname().lower()
1458null_input = open(os.devnull, 'r')
1459
1460if not os.path.exists(os.path.join(buildtop, 'runenv.py')):
1461    fail('You must run "make runenv.py" in %s first.' % buildtop)
1462sys.path = [buildtop] + sys.path
1463import runenv
1464
1465# A DB pass is a tuple of: name, kdc_conf.
1466_dbpasses = [('db2', None)]
1467if runenv.have_lmdb == 'yes':
1468    _dbpasses.append(('lmdb', _lmdb_kdc_conf))
1469
1470krb5kdc = os.path.join(buildtop, 'kdc', 'krb5kdc')
1471kadmind = os.path.join(buildtop, 'kadmin', 'server', 'kadmind')
1472kadmin = os.path.join(buildtop, 'kadmin', 'cli', 'kadmin')
1473kadminl = os.path.join(buildtop, 'kadmin', 'cli', 'kadmin.local')
1474kdb5_ldap_util = os.path.join(buildtop, 'plugins', 'kdb', 'ldap', 'ldap_util',
1475                              'kdb5_ldap_util')
1476kdb5_util = os.path.join(buildtop, 'kadmin', 'dbutil', 'kdb5_util')
1477ktutil = os.path.join(buildtop, 'kadmin', 'ktutil', 'ktutil')
1478kinit = os.path.join(buildtop, 'clients', 'kinit', 'kinit')
1479klist = os.path.join(buildtop, 'clients', 'klist', 'klist')
1480kswitch = os.path.join(buildtop, 'clients', 'kswitch', 'kswitch')
1481kvno = os.path.join(buildtop, 'clients', 'kvno', 'kvno')
1482kdestroy = os.path.join(buildtop, 'clients', 'kdestroy', 'kdestroy')
1483kpasswd = os.path.join(buildtop, 'clients', 'kpasswd', 'kpasswd')
1484t_inetd = os.path.join(buildtop, 'tests', 't_inetd')
1485kproplog = os.path.join(buildtop, 'kprop', 'kproplog')
1486kpropd = os.path.join(buildtop, 'kprop', 'kpropd')
1487kprop = os.path.join(buildtop, 'kprop', 'kprop')
1488