xref: /freebsd/crypto/krb5/src/util/k5test.py (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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 optparse
396import os
397import shlex
398import shutil
399import signal
400import socket
401import string
402import subprocess
403import sys
404
405# Used when most things go wrong (other than programming errors) so
406# that the user sees an error message rather than a Python traceback,
407# without help from the test script.  The on-exit handler will display
408# additional explanatory text.
409def fail(msg):
410    """Print a message and exit with failure."""
411    global _current_pass
412    print("*** Failure:", msg)
413    if _last_mark:
414        print("*** Last mark: %s" % _last_mark)
415    if _last_cmd:
416        print("*** Last command (#%d): %s" % (_cmd_index - 1, _last_cmd))
417    if _failed_daemon_output:
418        print('*** Output of failed daemon:')
419        sys.stdout.write(_failed_daemon_output)
420    elif _last_cmd_output:
421        print("*** Output of last command:")
422        sys.stdout.write(_last_cmd_output)
423    if _current_pass:
424        print("*** Failed in test pass:", _current_pass)
425    if _current_db:
426        print("*** Failed with db:", _current_db)
427    sys.exit(1)
428
429
430def success(msg):
431    global _success
432    _stop_daemons()
433    output('*** Success: %s\n' % msg)
434    _success = True
435
436
437def mark(msg):
438    global _last_mark
439    output('\n====== %s ======\n' % msg)
440    _last_mark = msg
441
442
443def skipped(whatmsg, whymsg):
444    output('*** Skipping: %s: %s\n' % (whatmsg, whymsg), force_verbose=True)
445    f = open(os.path.join(buildtop, 'skiptests'), 'a')
446    f.write('Skipped %s: %s\n' % (whatmsg, whymsg))
447    f.close()
448
449
450def skip_rest(whatmsg, whymsg):
451    global _success
452    skipped(whatmsg, whymsg)
453    _stop_daemons()
454    _success = True
455    sys.exit(0)
456
457
458def output(msg, force_verbose=False):
459    """Output a message to testlog, and to stdout if running verbosely."""
460    _outfile.write(msg)
461    if verbose or force_verbose:
462        sys.stdout.write(msg)
463
464
465# Return the location of progname in the executable path, or None if
466# it is not found.
467def which(progname):
468    for dir in os.environ["PATH"].split(os.pathsep):
469        path = os.path.join(dir, progname)
470        if os.access(path, os.X_OK):
471            return path
472    return None
473
474
475def password(name):
476    """Choose a weakly random password from name, consistent across calls."""
477    return name + str(os.getpid())
478
479
480def canonicalize_hostname(name, rdns=True):
481    """Canonicalize name using DNS, optionally with reverse DNS."""
482    try:
483        ai = socket.getaddrinfo(name, None, 0, 0, 0, socket.AI_CANONNAME)
484    except socket.gaierror as e:
485        return name.lower()
486    (family, socktype, proto, canonname, sockaddr) = ai[0]
487
488    if not rdns:
489        return canonname.lower()
490
491    try:
492        rname = socket.getnameinfo(sockaddr, socket.NI_NAMEREQD)
493    except socket.gaierror:
494        return canonname.lower()
495    return rname[0].lower()
496
497
498# Exit handler which ensures processes are cleaned up and, on failure,
499# prints messages to help developers debug the problem.
500def _onexit():
501    global _daemons, _success, srctop, verbose
502    global _debug, _stop_before, _stop_after, _shell_before, _shell_after
503    if _debug or _stop_before or _stop_after or _shell_before or _shell_after:
504        # Wait before killing daemons in case one is being debugged.
505        sys.stdout.write('*** Press return to kill daemons and exit script: ')
506        sys.stdout.flush()
507        sys.stdin.readline()
508    for proc in _daemons:
509        os.kill(proc.pid, signal.SIGTERM)
510        _check_daemon(proc)
511    if not _success:
512        print
513        if not verbose:
514            testlogfile = os.path.join(os.getcwd(), 'testlog')
515            utildir = os.path.join(srctop, 'util')
516            print('For details, see: %s' % testlogfile)
517            print('Or re-run this test script with the -v flag:')
518            print('    cd %s' % os.getcwd())
519            print('    PYTHONPATH=%s %s %s -v' %
520                  (utildir, sys.executable, sys.argv[0]))
521            print()
522        print('Use --debug=NUM to run a command under a debugger.  Use')
523        print('--stop-after=NUM to stop after a daemon is started in order to')
524        print('attach to it with a debugger.  Use --help to see other')
525        print('options.')
526
527
528def _onsigint(signum, frame):
529    # Exit without displaying a stack trace.  Suppress messages from _onexit.
530    global _success
531    _success = True
532    sys.exit(1)
533
534
535# Find the parent of dir which is at the root of a build or source directory.
536def _find_root(dir):
537    while True:
538        if os.path.exists(os.path.join(dir, 'lib', 'krb5', 'krb')):
539            break
540        parent = os.path.dirname(dir)
541        if (parent == dir):
542            return None
543        dir = parent
544    return dir
545
546
547def _find_buildtop():
548    root = _find_root(os.getcwd())
549    if root is None:
550        fail('Cannot find root of krb5 build directory.')
551    if not os.path.exists(os.path.join(root, 'config.status')):
552        # Looks like an unbuilt source directory.
553        fail('This script must be run inside a krb5 build directory.')
554    return root
555
556
557def _find_srctop():
558    scriptdir = os.path.abspath(os.path.dirname(sys.argv[0]))
559    if not scriptdir:
560        scriptdir = os.getcwd()
561    root = _find_root(scriptdir)
562    if root is None:
563        fail('Cannot find root of krb5 source directory.')
564    return os.path.abspath(root)
565
566
567# Parse command line arguments, setting global option variables.  Also
568# sets the global variable args to the positional arguments, which may
569# be used by the test script.
570def _parse_args():
571    global args, verbose, testpass, _debug, _debugger_command
572    global _stop_before, _stop_after, _shell_before, _shell_after
573    parser = optparse.OptionParser()
574    parser.add_option('-v', '--verbose', action='store_true', dest='verbose',
575                      default=False, help='Display verbose output')
576    parser.add_option('-p', '--pass', dest='testpass', metavar='PASS',
577                      help='If a multi-pass test, run only PASS')
578    parser.add_option('--debug', dest='debug', metavar='NUM',
579                      help='Debug numbered command (or "all")')
580    parser.add_option('--debugger', dest='debugger', metavar='COMMAND',
581                      help='Debugger command (default is gdb --args)')
582    parser.add_option('--stop-before', dest='stopb', metavar='NUM',
583                      help='Stop before numbered command (or "all")')
584    parser.add_option('--stop-after', dest='stopa', metavar='NUM',
585                      help='Stop after numbered command (or "all")')
586    parser.add_option('--shell-before', dest='shellb', metavar='NUM',
587                      help='Spawn shell before numbered command (or "all")')
588    parser.add_option('--shell-after', dest='shella', metavar='NUM',
589                      help='Spawn shell after numbered command (or "all")')
590    (options, args) = parser.parse_args()
591    verbose = options.verbose
592    testpass = options.testpass
593    _debug = _parse_cmdnum('--debug', options.debug)
594    _stop_before = _parse_cmdnum('--stop-before', options.stopb)
595    _stop_after = _parse_cmdnum('--stop-after', options.stopa)
596    _shell_before = _parse_cmdnum('--shell-before', options.shellb)
597    _shell_after = _parse_cmdnum('--shell-after', options.shella)
598
599    if options.debugger is not None:
600        _debugger_command = shlex.split(options.debugger)
601    elif which('gdb') is not None:
602        _debugger_command = ['gdb', '--args']
603    elif which('lldb') is not None:
604        _debugger_command = ['lldb', '--']
605    elif options.debug is not None:
606        print('Cannot find a debugger; use --debugger=COMMAND')
607        sys.exit(1)
608
609
610# Translate a command number spec.  -1 means all, None means none.
611def _parse_cmdnum(optname, str):
612    if not str:
613        return None
614    if str == 'all':
615        return -1
616    try:
617        return int(str)
618    except ValueError:
619        fail('%s value must be "all" or a number' % optname)
620
621
622# Test if a command index matches a translated command number spec.
623def _match_cmdnum(cmdnum, ind):
624    if cmdnum is None:
625        return False
626    elif cmdnum == -1:
627        return True
628    else:
629        return cmdnum == ind
630
631
632# Return an environment suitable for running programs in the build
633# tree.  It is safe to modify the result.
634def _build_env():
635    global buildtop, runenv
636    env = os.environ.copy()
637    for (k, v) in runenv.env.items():
638        if v.find('./') == 0:
639            env[k] = os.path.join(buildtop, v)
640        else:
641            env[k] = v
642    # Make sure we don't get confused by translated messages
643    # or localized times.
644    env['LC_ALL'] = 'C'
645    return env
646
647
648# Merge the nested dictionaries cfg1 and cfg2 into a new dictionary.
649# cfg1 or cfg2 may be None, in which case the other is returned.  If
650# cfg2 contains keys mapped to None, the corresponding keys will be
651# mapped to None in the result.  The result may contain references to
652# parts of cfg1 or cfg2, so is not safe to modify.
653def _cfg_merge(cfg1, cfg2):
654    if not cfg2:
655        return cfg1
656    if not cfg1:
657        return cfg2
658    result = cfg1.copy()
659    for key, value2 in cfg2.items():
660        if value2 is None:
661            result.pop(key, None)
662        elif key not in result:
663            result[key] = value2
664        else:
665            value1 = result[key]
666            if isinstance(value1, dict):
667                if not isinstance(value2, dict):
668                    raise TypeError()
669                result[key] = _cfg_merge(value1, value2)
670            else:
671                result[key] = value2
672    return result
673
674
675# Python gives us shlex.split() to turn a shell command into a list of
676# arguments, but oddly enough, not the easier reverse operation.  For
677# now, do a bad job of faking it.
678def _shell_equiv(args):
679    return " ".join(args)
680
681
682# Add a valgrind prefix to the front of args if specified in the
683# environment.  Under normal circumstances this just returns args.
684def _valgrind(args):
685    valgrind = os.getenv('VALGRIND')
686    if valgrind:
687        args = shlex.split(valgrind) + args
688    return args
689
690
691def _stop_or_shell(stop, shell, env, ind):
692    if (_match_cmdnum(stop, ind)):
693        sys.stdout.write('*** [%d] Waiting for return: ' % ind)
694        sys.stdout.flush()
695        sys.stdin.readline()
696    if (_match_cmdnum(shell, ind)):
697        output('*** [%d] Spawning shell\n' % ind, True)
698        subprocess.call(os.getenv('SHELL'), env=env)
699
700
701# Look for the expected strings in successive lines of trace.
702def _check_trace(trace, expected):
703    i = 0
704    for line in trace.splitlines():
705        if i < len(expected) and expected[i] in line:
706            i += 1
707    if i < len(expected):
708        fail('Expected string not found in trace output: ' + expected[i])
709
710
711def _run_cmd(args, env, input=None, expected_code=0, expected_msg=None,
712             expected_trace=None, return_trace=False):
713    global null_input, _cmd_index, _last_cmd, _last_cmd_output, _debug
714    global _stop_before, _stop_after, _shell_before, _shell_after
715
716    tracefile = None
717    if expected_trace is not None or return_trace:
718        tracefile = 'testtrace'
719        if os.path.exists(tracefile):
720            os.remove(tracefile)
721        env = env.copy()
722        env['KRB5_TRACE'] = tracefile
723
724    if (_match_cmdnum(_debug, _cmd_index)):
725        return _debug_cmd(args, env, input)
726
727    args = _valgrind(args)
728    _last_cmd = _shell_equiv(args)
729
730    output('*** [%d] Executing: %s\n' % (_cmd_index, _last_cmd))
731    _stop_or_shell(_stop_before, _shell_before, env, _cmd_index)
732
733    if input:
734        infile = subprocess.PIPE
735    else:
736        infile = null_input
737
738    # Run the command and log the result, folding stderr into stdout.
739    proc = subprocess.Popen(args, stdin=infile, stdout=subprocess.PIPE,
740                            stderr=subprocess.STDOUT, env=env,
741                            universal_newlines=True)
742    (outdata, dummy_errdata) = proc.communicate(input)
743    _last_cmd_output = outdata
744    code = proc.returncode
745    output(outdata)
746    output('*** [%d] Completed with return code %d\n' % (_cmd_index, code))
747    _stop_or_shell(_stop_after, _shell_after, env, _cmd_index)
748    _cmd_index += 1
749
750    # Check the return code and return the output.
751    if code != expected_code:
752        fail('%s failed with code %d.' % (args[0], code))
753
754    if expected_msg is not None and expected_msg not in outdata:
755        fail('Expected string not found in command output: ' + expected_msg)
756
757    if tracefile is not None:
758        with open(tracefile, 'r') as f:
759            trace = f.read()
760        output('*** Trace output for previous command:\n')
761        output(trace)
762        if expected_trace is not None:
763            _check_trace(trace, expected_trace)
764
765    return (outdata, trace) if return_trace else outdata
766
767
768def _debug_cmd(args, env, input):
769    global _cmd_index, _debugger_command
770
771    args = _debugger_command + list(args)
772    output('*** [%d] Executing in debugger: %s\n' %
773           (_cmd_index, _shell_equiv(args)), True)
774    if input:
775        print
776        print('*** Enter the following input when appropriate:')
777        print()
778        print(input)
779        print()
780    code = subprocess.call(args, env=env)
781    output('*** [%d] Completed in debugger with return code %d\n' %
782           (_cmd_index, code))
783    _cmd_index += 1
784
785
786# Start a daemon process with the specified args and env.  Wait until
787# we see sentinel as a substring of a line on either stdout or stderr.
788# Clean up the daemon process on exit.
789def _start_daemon(args, env, sentinel):
790    global null_input, _cmd_index, _last_cmd, _last_cmd_output, _debug
791    global _stop_before, _stop_after, _shell_before, _shell_after
792
793    if (_match_cmdnum(_debug, _cmd_index)):
794        output('*** [%d] Warning: ' % _cmd_index, True)
795        output( 'test script cannot proceed after debugging a daemon\n', True)
796        _debug_cmd(args, env, None)
797        output('*** Exiting after debugging daemon\n', True)
798        sys.exit(1)
799
800    args = _valgrind(args)
801    _last_cmd = _shell_equiv(args)
802    output('*** [%d] Starting: %s\n' % (_cmd_index, _last_cmd))
803    _stop_or_shell(_stop_before, _shell_before, env, _cmd_index)
804
805    # Start the daemon and look for the sentinel in stdout or stderr.
806    proc = subprocess.Popen(args, stdin=null_input, stdout=subprocess.PIPE,
807                            stderr=subprocess.STDOUT, env=env,
808                            universal_newlines=True)
809    _last_cmd_output = ''
810    while True:
811        line = proc.stdout.readline()
812        _last_cmd_output += line
813        if line == "":
814            code = proc.wait()
815            fail('%s failed to start with code %d.' % (args[0], code))
816        output(line)
817        if sentinel in line:
818            break
819    output('*** [%d] Started with pid %d\n' % (_cmd_index, proc.pid))
820    _stop_or_shell(_stop_after, _shell_after, env, _cmd_index)
821    _cmd_index += 1
822
823    # Save the daemon in a list for cleanup.  Note that we won't read
824    # any more of the daemon's output after the sentinel, which will
825    # cause the daemon to block if it generates enough.  For now we
826    # assume all daemon processes are quiet enough to avoid this
827    # problem.  If it causes an issue, some alternatives are:
828    #   - Output to a file and poll the file for the sentinel
829    #     (undesirable because it slows down the test suite by the
830    #     polling interval times the number of daemons started)
831    #   - Create an intermediate subprocess which discards output
832    #     after the sentinel.
833    _daemons.append(proc)
834
835    # Return the process; the caller can stop it with stop_daemon.
836    return proc
837
838
839# Await a daemon process's exit status and display it if it isn't
840# successful.  Display any output it generated after the sentinel.
841# Return the daemon's exit status (0 if it terminated with SIGTERM).
842def _check_daemon(proc):
843    global _failed_daemon_output
844    code = proc.wait()
845    # If a daemon doesn't catch SIGTERM (like gss-server), treat it as
846    # a normal exit.
847    if code == -signal.SIGTERM:
848        code = 0
849    if code != 0:
850        output('*** Daemon pid %d exited with code %d\n' % (proc.pid, code))
851
852    out, err = proc.communicate()
853    if code != 0:
854        _failed_daemon_output = out
855    output('*** Daemon pid %d output:\n' % proc.pid)
856    output(out)
857
858    return code
859
860
861# Terminate all active daemon processes.  Fail out if any of them
862# exited unsuccessfully.
863def _stop_daemons():
864    global _daemons
865    daemon_error = False
866    for proc in _daemons:
867        os.kill(proc.pid, signal.SIGTERM)
868        code = _check_daemon(proc)
869        if code != 0:
870            daemon_error = True
871    _daemons = []
872    if daemon_error:
873        fail('One or more daemon processes exited with an error')
874
875
876# Wait for a daemon process to exit.  Fail out if it exits
877# unsuccessfully.
878def await_daemon_exit(proc):
879    code = _check_daemon(proc)
880    _daemons.remove(proc)
881    if code != 0:
882        fail('Daemon exited unsuccessfully')
883
884
885# Terminate one daemon process.  Fail out if it exits unsuccessfully.
886def stop_daemon(proc):
887    os.kill(proc.pid, signal.SIGTERM)
888    return await_daemon_exit(proc)
889
890
891class K5Realm(object):
892    """An object representing a functional krb5 test realm."""
893
894    def __init__(self, realm='KRBTEST.COM', portbase=61000, testdir='testdir',
895                 krb5_conf=None, kdc_conf=None, create_kdb=True,
896                 krbtgt_keysalt=None, create_user=True, get_creds=True,
897                 create_host=True, start_kdc=True, start_kadmind=False,
898                 start_kpropd=False, bdb_only=False, pkinit=False):
899        global hostname, _default_krb5_conf, _default_kdc_conf
900        global _lmdb_kdc_conf, _current_db
901
902        self.realm = realm
903        self.testdir = os.path.join(os.getcwd(), testdir)
904        self.portbase = portbase
905        self.user_princ = 'user@' + self.realm
906        self.admin_princ = 'user/admin@' + self.realm
907        self.host_princ = 'host/%s@%s' % (hostname, self.realm)
908        self.nfs_princ = 'nfs/%s@%s' % (hostname, self.realm)
909        self.krbtgt_princ = 'krbtgt/%s@%s' % (self.realm, self.realm)
910        self.keytab = os.path.join(self.testdir, 'keytab')
911        self.client_keytab = os.path.join(self.testdir, 'client_keytab')
912        self.ccache = os.path.join(self.testdir, 'ccache')
913        self.gss_mech_config = os.path.join(self.testdir, 'mech.conf')
914        self.kadmin_ccache = os.path.join(self.testdir, 'kadmin_ccache')
915        base_krb5_conf = _default_krb5_conf
916        base_kdc_conf = _default_kdc_conf
917        if (os.getenv('K5TEST_LMDB') is not None and
918            not bdb_only and not _current_db):
919            base_kdc_conf = _cfg_merge(base_kdc_conf, _lmdb_kdc_conf)
920        if pkinit:
921            base_krb5_conf = _cfg_merge(base_krb5_conf, _pkinit_krb5_conf)
922            base_kdc_conf = _cfg_merge(base_kdc_conf, _pkinit_kdc_conf)
923        self._krb5_conf = _cfg_merge(base_krb5_conf, krb5_conf)
924        self._kdc_conf = _cfg_merge(base_kdc_conf, kdc_conf)
925        self._kdc_proc = None
926        self._kadmind_proc = None
927        self._kpropd_procs = []
928        krb5_conf_path = os.path.join(self.testdir, 'krb5.conf')
929        kdc_conf_path = os.path.join(self.testdir, 'kdc.conf')
930        self.env = self._make_env(krb5_conf_path, kdc_conf_path)
931
932        self._create_empty_dir()
933        self._create_conf(self._krb5_conf, krb5_conf_path)
934        self._create_conf(self._kdc_conf, kdc_conf_path)
935        self._create_acl()
936        self._create_dictfile()
937
938        if create_kdb:
939            self.create_kdb()
940        if krbtgt_keysalt and create_kdb:
941            self.run([kadminl, 'cpw', '-randkey', '-e', krbtgt_keysalt,
942                      self.krbtgt_princ])
943        if create_user and create_kdb:
944            self.addprinc(self.user_princ, password('user'))
945            self.addprinc(self.admin_princ, password('admin'))
946        if create_host and create_kdb:
947            self.addprinc(self.host_princ)
948            self.extract_keytab(self.host_princ, self.keytab)
949        if start_kdc and create_kdb:
950            self.start_kdc()
951        if start_kadmind and create_kdb:
952            self.start_kadmind()
953        if get_creds and create_kdb and create_user and start_kdc:
954            self.kinit(self.user_princ, password('user'))
955            self.klist(self.user_princ)
956
957    def _create_empty_dir(self):
958        dir = self.testdir
959        shutil.rmtree(dir, True)
960        if (os.path.exists(dir)):
961            fail('Cannot remove %s to create test realm.' % dir)
962        os.mkdir(dir)
963
964    def _create_conf(self, profile, filename):
965        file = open(filename, 'w')
966        for section, contents in profile.items():
967            file.write('[%s]\n' % section)
968            self._write_cfg_section(file, contents, 1)
969        file.close()
970
971    def _write_cfg_section(self, file, contents, indent_level):
972        indent = '\t' * indent_level
973        for name, value in contents.items():
974            name = self._subst_cfg_value(name)
975            if isinstance(value, dict):
976                # A dictionary value yields a list subsection.
977                file.write('%s%s = {\n' % (indent, name))
978                self._write_cfg_section(file, value, indent_level + 1)
979                file.write('%s}\n' % indent)
980            elif isinstance(value, list):
981                # A list value yields multiple values for the same name.
982                for item in value:
983                    item = self._subst_cfg_value(item)
984                    file.write('%s%s = %s\n' % (indent, name, item))
985            elif isinstance(value, str):
986                # A string value yields a straightforward variable setting.
987                value = self._subst_cfg_value(value)
988                file.write('%s%s = %s\n' % (indent, name, value))
989            else:
990                raise TypeError()
991
992    def _subst_cfg_value(self, value):
993        global buildtop, srctop, hostname
994        template = string.Template(value)
995        subst = template.substitute(realm=self.realm,
996                                    testdir=self.testdir,
997                                    buildtop=buildtop,
998                                    srctop=srctop,
999                                    plugins=plugins,
1000                                    certs=pkinit_certs,
1001                                    hostname=hostname,
1002                                    port0=self.portbase,
1003                                    port1=self.portbase + 1,
1004                                    port2=self.portbase + 2,
1005                                    port3=self.portbase + 3,
1006                                    port4=self.portbase + 4,
1007                                    port5=self.portbase + 5,
1008                                    port6=self.portbase + 6,
1009                                    port7=self.portbase + 7,
1010                                    port8=self.portbase + 8,
1011                                    port9=self.portbase + 9)
1012        # Empty values must be quoted to avoid a syntax error.
1013        return subst if subst else '""'
1014
1015    def _create_acl(self):
1016        global hostname
1017        filename = os.path.join(self.testdir, 'acl')
1018        file = open(filename, 'w')
1019        file.write('%s *e\n' % self.admin_princ)
1020        file.write('kiprop/%s@%s p\n' % (hostname, self.realm))
1021        file.close()
1022
1023    def _create_dictfile(self):
1024        filename = os.path.join(self.testdir, 'dictfile')
1025        file = open(filename, 'w')
1026        file.write('weak_password\n')
1027        file.close()
1028
1029    def _make_env(self, krb5_conf_path, kdc_conf_path):
1030        env = _build_env()
1031        env['KRB5_CONFIG'] = krb5_conf_path
1032        env['KRB5_KDC_PROFILE'] = kdc_conf_path or os.devnull
1033        env['KRB5CCNAME'] = self.ccache
1034        env['KRB5_KTNAME'] = self.keytab
1035        env['KRB5_CLIENT_KTNAME'] = self.client_keytab
1036        env['KRB5RCACHEDIR'] = self.testdir
1037        env['KPROPD_PORT'] = str(self.kprop_port())
1038        env['KPROP_PORT'] = str(self.kprop_port())
1039        env['GSS_MECH_CONFIG'] = self.gss_mech_config
1040        return env
1041
1042    def run(self, args, env=None, **keywords):
1043        if env is None:
1044            env = self.env
1045        return _run_cmd(args, env, **keywords)
1046
1047    def kprop_port(self):
1048        return self.portbase + 3
1049
1050    def server_port(self):
1051        return self.portbase + 5
1052
1053    def start_server(self, args, sentinel, env=None):
1054        if env is None:
1055            env = self.env
1056        return _start_daemon(args, env, sentinel)
1057
1058    def start_in_inetd(self, args, port=None, env=None):
1059        if not port:
1060            port = self.server_port()
1061        if env is None:
1062            env = self.env
1063        inetd_args = [t_inetd, str(port), args[0]] + args
1064        return _start_daemon(inetd_args, env, 'Ready!')
1065
1066    def create_kdb(self):
1067        global kdb5_util
1068        self.run([kdb5_util, 'create', '-s', '-P', 'master'])
1069
1070    def start_kdc(self, args=[], env=None):
1071        global krb5kdc
1072        if env is None:
1073            env = self.env
1074        assert(self._kdc_proc is None)
1075        self._kdc_proc = _start_daemon([krb5kdc, '-n'] + args, env,
1076                                       'starting...')
1077
1078    def stop_kdc(self):
1079        assert(self._kdc_proc is not None)
1080        stop_daemon(self._kdc_proc)
1081        self._kdc_proc = None
1082
1083    def start_kadmind(self, env=None):
1084        global krb5kdc
1085        if env is None:
1086            env = self.env
1087        assert(self._kadmind_proc is None)
1088        dump_path = os.path.join(self.testdir, 'dump')
1089        self._kadmind_proc = _start_daemon([kadmind, '-nofork',
1090                                            '-p', kdb5_util, '-K', kprop,
1091                                            '-F', dump_path], env,
1092                                           'starting...')
1093
1094    def stop_kadmind(self):
1095        assert(self._kadmind_proc is not None)
1096        stop_daemon(self._kadmind_proc)
1097        self._kadmind_proc = None
1098
1099    def _kpropd_args(self):
1100        datatrans_path = os.path.join(self.testdir, 'incoming-datatrans')
1101        kpropdacl_path = os.path.join(self.testdir, 'kpropd-acl')
1102        return [kpropd, '-D', '-P', str(self.kprop_port()),
1103                '-f', datatrans_path, '-p', kdb5_util, '-a', kpropdacl_path]
1104
1105    def start_kpropd(self, env, args=[]):
1106        proc = _start_daemon(self._kpropd_args() + args, env, 'ready')
1107        self._kpropd_procs.append(proc)
1108        return proc
1109
1110    def stop_kpropd(self, proc):
1111        stop_daemon(proc)
1112        self._kpropd_procs.remove(proc)
1113
1114    def run_kpropd_once(self, env, args=[]):
1115        return self.run(self._kpropd_args() + ['-t'] + args, env=env)
1116
1117    def stop(self):
1118        if self._kdc_proc:
1119            self.stop_kdc()
1120        if self._kadmind_proc:
1121            self.stop_kadmind()
1122        for p in self._kpropd_procs:
1123            stop_daemon(p)
1124        self._kpropd_procs = []
1125
1126    def addprinc(self, princname, password=None):
1127        if password:
1128            self.run([kadminl, 'addprinc', '-pw', password, princname])
1129        else:
1130            self.run([kadminl, 'addprinc', '-randkey', princname])
1131
1132    def extract_keytab(self, princname, keytab):
1133        self.run([kadminl, 'ktadd', '-k', keytab, '-norandkey', princname])
1134
1135    def kinit(self, princname, password=None, flags=[], **keywords):
1136        if password:
1137            input = password + "\n"
1138        else:
1139            input = None
1140        return self.run([kinit] + flags + [princname], input=input, **keywords)
1141
1142    def pkinit(self, princ, flags=[], **kw):
1143        id = 'FILE:%s,%s' % (os.path.join(pkinit_certs, 'user.pem'),
1144                             os.path.join(pkinit_certs, 'privkey.pem'))
1145        flags = flags + ['-X', 'X509_user_identity=%s' % id]
1146        self.kinit(princ, flags=flags, **kw)
1147
1148    def klist(self, client_princ, service_princ=None, ccache=None, **keywords):
1149        if service_princ is None:
1150            service_princ = self.krbtgt_princ
1151        if ccache is None:
1152            ccache = self.ccache
1153        ccachestr = ccache
1154        if len(ccachestr) < 2 or ':' not in ccachestr[2:]:
1155            ccachestr = 'FILE:' + ccachestr
1156        output = self.run([klist, ccache], **keywords)
1157        if (('Ticket cache: %s\n' % ccachestr) not in output or
1158            ('Default principal: %s\n' % client_princ) not in output or
1159            service_princ not in output):
1160            fail('Unexpected klist output.')
1161
1162    def klist_keytab(self, princ, keytab=None, **keywords):
1163        if keytab is None:
1164            keytab = self.keytab
1165        output = self.run([klist, '-k', keytab], **keywords)
1166        if (('Keytab name: FILE:%s\n' % keytab) not in output or
1167            'KVNO Principal\n----' not in output or
1168            princ not in output):
1169            fail('Unexpected klist output.')
1170
1171    def prep_kadmin(self, princname=None, pw=None, flags=[]):
1172        if princname is None:
1173            princname = self.admin_princ
1174            pw = password('admin')
1175        return self.kinit(princname, pw,
1176                          flags=['-S', 'kadmin/admin',
1177                                 '-c', self.kadmin_ccache] + flags)
1178
1179    def run_kadmin(self, args, **keywords):
1180        return self.run([kadmin, '-c', self.kadmin_ccache] + args, **keywords)
1181
1182    def special_env(self, name, has_kdc_conf, krb5_conf=None, kdc_conf=None):
1183        krb5_conf_path = os.path.join(self.testdir, 'krb5.conf.%s' % name)
1184        krb5_conf = _cfg_merge(self._krb5_conf, krb5_conf)
1185        self._create_conf(krb5_conf, krb5_conf_path)
1186        if has_kdc_conf:
1187            kdc_conf_path = os.path.join(self.testdir, 'kdc.conf.%s' % name)
1188            kdc_conf = _cfg_merge(self._kdc_conf, kdc_conf)
1189            self._create_conf(kdc_conf, kdc_conf_path)
1190        else:
1191            kdc_conf_path = None
1192        return self._make_env(krb5_conf_path, kdc_conf_path)
1193
1194
1195def multipass_realms(**keywords):
1196    global _current_pass, _passes, testpass
1197    caller_krb5_conf = keywords.get('krb5_conf')
1198    caller_kdc_conf = keywords.get('kdc_conf')
1199    for p in _passes:
1200        (name, krbtgt_keysalt, krb5_conf, kdc_conf) = p
1201        if testpass and name != testpass:
1202            continue
1203        output('*** Beginning pass %s\n' % name)
1204        keywords['krb5_conf'] = _cfg_merge(krb5_conf, caller_krb5_conf)
1205        keywords['kdc_conf'] = _cfg_merge(kdc_conf, caller_kdc_conf)
1206        keywords['krbtgt_keysalt'] = krbtgt_keysalt
1207        _current_pass = name
1208        realm = K5Realm(**keywords)
1209        yield realm
1210        realm.stop()
1211        _current_pass = None
1212
1213
1214def multidb_realms(**keywords):
1215    global _current_db, _dbpasses
1216    caller_kdc_conf = keywords.get('kdc_conf')
1217    for p in _dbpasses:
1218        (name, kdc_conf) = p
1219        output('*** Using DB type %s\n' % name)
1220        keywords['kdc_conf'] = _cfg_merge(kdc_conf, caller_kdc_conf)
1221        _current_db = name
1222        realm = K5Realm(**keywords)
1223        yield realm
1224        realm.stop()
1225        _current_db = None
1226
1227
1228def cross_realms(num, xtgts=None, args=None, **keywords):
1229    # Build keyword args for each realm.
1230    realm_args = []
1231    for i in range(num):
1232        realmnumber = i + 1
1233        # Start with any global keyword arguments to this function.
1234        a = keywords.copy()
1235        if args and args[i]:
1236            # Merge in specific arguments for this realm.  Use
1237            # _cfg_merge for config fragments.
1238            a.update(args[i])
1239            for cf in ('krb5_conf', 'kdc_conf'):
1240                if cf in keywords and cf in args[i]:
1241                    a[cf] = _cfg_merge(keywords[cf], args[i][cf])
1242        # Set defaults for the realm name, testdir, and portbase.
1243        if not 'realm' in a:
1244            a['realm'] = 'KRBTEST%d.COM' % realmnumber
1245        if not 'testdir' in a:
1246            a['testdir'] = os.path.join('testdir', str(realmnumber))
1247        if not 'portbase' in a:
1248            a['portbase'] = 61000 + 10 * realmnumber
1249        realm_args.append(a)
1250
1251    # Build a [realms] config fragment containing all of the realms.
1252    realmsection = { '$realm' : None }
1253    for a in realm_args:
1254        name = a['realm']
1255        portbase = a['portbase']
1256        realmsection[name] = {
1257            'kdc' : '$hostname:%d' % portbase,
1258            'admin_server' : '$hostname:%d' % (portbase + 1),
1259            'kpasswd_server' : '$hostname:%d' % (portbase + 2)
1260            }
1261    realmscfg = {'realms': realmsection}
1262
1263    # Set realmsection in each realm's krb5_conf keyword argument.
1264    for a in realm_args:
1265        a['krb5_conf'] = _cfg_merge(realmscfg, a.get('krb5_conf'))
1266
1267    if xtgts is None:
1268        # Default to cross tgts for every pair of realms.
1269        # (itertools.permutations would work here but is new in 2.6.)
1270        xtgts = [(x,y) for x in range(num) for y in range(num) if x != y]
1271
1272    # Create the realms.
1273    realms = []
1274    for i in range(num):
1275        r = K5Realm(**realm_args[i])
1276        # Create specified cross TGTs in this realm's db.
1277        for j in range(num):
1278            if j == i:
1279                continue
1280            iname = r.realm
1281            jname = realm_args[j]['realm']
1282            if (i, j) in xtgts:
1283                # This realm can authenticate to realm j.
1284                r.addprinc('krbtgt/%s' % jname, password('cr-%d-%d-' % (i, j)))
1285            if (j, i) in xtgts:
1286                # Realm j can authenticate to this realm.
1287                r.addprinc('krbtgt/%s@%s' % (iname, jname),
1288                           password('cr-%d-%d-' % (j, i)))
1289        realms.append(r)
1290    return realms
1291
1292
1293_default_krb5_conf = {
1294    'libdefaults': {
1295        'default_realm': '$realm',
1296        'dns_lookup_kdc': 'false',
1297        'dns_canonicalize_hostname': 'fallback',
1298        'qualify_shortname': '',
1299        'plugin_base_dir': '$plugins'},
1300    'realms': {'$realm': {
1301            'kdc': '$hostname:$port0',
1302            'admin_server': '$hostname:$port1',
1303            'kpasswd_server': '$hostname:$port2'}}}
1304
1305
1306_default_kdc_conf = {
1307    'realms': {'$realm': {
1308            'database_module': 'db',
1309            'iprop_port': '$port4',
1310            'key_stash_file': '$testdir/stash',
1311            'acl_file': '$testdir/acl',
1312            'dict_file': '$testdir/dictfile',
1313            'kadmind_port': '$port1',
1314            'kpasswd_port': '$port2',
1315            'kdc_listen': '$port0',
1316            'kdc_tcp_listen': '$port0'}},
1317    'dbmodules': {
1318        'db_module_dir': '$plugins/kdb',
1319        'db': {'db_library': 'db2', 'database_name' : '$testdir/db'}},
1320    'logging': {
1321        'admin_server': 'FILE:$testdir/kadmind5.log',
1322        'kdc': 'FILE:$testdir/kdc.log',
1323        'default': 'FILE:$testdir/others.log'}}
1324
1325
1326_lmdb_kdc_conf = {'dbmodules': {'db': {'db_library': 'klmdb',
1327                                       'nosync': 'true'}}}
1328
1329
1330_pkinit_krb5_conf = {'realms': {'$realm': {
1331    'pkinit_anchors': 'FILE:$certs/ca.pem'}}}
1332_pkinit_kdc_conf = {'realms': {'$realm': {
1333    'pkinit_identity': 'FILE:$certs/kdc.pem,$certs/privkey.pem'}}}
1334
1335
1336# A pass is a tuple of: name, krbtgt_keysalt, krb5_conf, kdc_conf.
1337_passes = [
1338    # No special settings; exercises AES256.
1339    ('default', None, None, None),
1340
1341    # Exercise the DES3 enctype.
1342    ('des3', None,
1343     {'libdefaults': {'permitted_enctypes': 'des3 aes256-sha1'}},
1344     {'realms': {'$realm': {
1345                    'supported_enctypes': 'des3-cbc-sha1:normal',
1346                    'master_key_type': 'des3-cbc-sha1'}}}),
1347
1348    # Exercise the arcfour enctype.
1349    ('arcfour', None,
1350     {'libdefaults': {'permitted_enctypes': 'rc4 aes256-sha1'}},
1351     {'realms': {'$realm': {
1352                    'supported_enctypes': 'arcfour-hmac:normal',
1353                    'master_key_type': 'arcfour-hmac'}}}),
1354
1355    # Exercise the AES128 enctype.
1356    ('aes128', None,
1357      {'libdefaults': {'permitted_enctypes': 'aes128-cts'}},
1358      {'realms': {'$realm': {
1359                    'supported_enctypes': 'aes128-cts:normal',
1360                    'master_key_type': 'aes128-cts'}}}),
1361
1362    # Exercise the camellia256-cts enctype.
1363    ('camellia256', None,
1364      {'libdefaults': {'permitted_enctypes': 'camellia256-cts'}},
1365      {'realms': {'$realm': {
1366                    'supported_enctypes': 'camellia256-cts:normal',
1367                    'master_key_type': 'camellia256-cts'}}}),
1368
1369    # Exercise the aes128-sha2 enctype.
1370    ('aes128-sha2', None,
1371      {'libdefaults': {'permitted_enctypes': 'aes128-sha2'}},
1372      {'realms': {'$realm': {
1373                    'supported_enctypes': 'aes128-sha2:normal',
1374                    'master_key_type': 'aes128-sha2'}}}),
1375
1376    # Exercise the aes256-sha2 enctype.
1377    ('aes256-sha2', None,
1378      {'libdefaults': {'permitted_enctypes': 'aes256-sha2'}},
1379      {'realms': {'$realm': {
1380                    'supported_enctypes': 'aes256-sha2:normal',
1381                    'master_key_type': 'aes256-sha2'}}}),
1382
1383    # Test a setup with modern principal keys but an old TGT key.
1384    ('aes256.destgt', 'arcfour-hmac:normal',
1385     {'libdefaults': {'allow_weak_crypto': 'true'}},
1386     None)
1387]
1388
1389_success = False
1390_current_pass = None
1391_current_db = None
1392_daemons = []
1393_parse_args()
1394atexit.register(_onexit)
1395signal.signal(signal.SIGINT, _onsigint)
1396_outfile = open('testlog', 'w')
1397_cmd_index = 1
1398_last_mark = None
1399_last_cmd = None
1400_last_cmd_output = None
1401_failed_daemon_output = None
1402buildtop = _find_buildtop()
1403srctop = _find_srctop()
1404plugins = os.path.join(buildtop, 'plugins')
1405pkinit_enabled = os.path.exists(os.path.join(plugins, 'preauth', 'pkinit.so'))
1406pkinit_certs = os.path.join(srctop, 'tests', 'pkinit-certs')
1407hostname = socket.gethostname().lower()
1408null_input = open(os.devnull, 'r')
1409
1410if not os.path.exists(os.path.join(buildtop, 'runenv.py')):
1411    fail('You must run "make runenv.py" in %s first.' % buildtop)
1412sys.path = [buildtop] + sys.path
1413import runenv
1414
1415# A DB pass is a tuple of: name, kdc_conf.
1416_dbpasses = [('db2', None)]
1417if runenv.have_lmdb == 'yes':
1418    _dbpasses.append(('lmdb', _lmdb_kdc_conf))
1419
1420krb5kdc = os.path.join(buildtop, 'kdc', 'krb5kdc')
1421kadmind = os.path.join(buildtop, 'kadmin', 'server', 'kadmind')
1422kadmin = os.path.join(buildtop, 'kadmin', 'cli', 'kadmin')
1423kadminl = os.path.join(buildtop, 'kadmin', 'cli', 'kadmin.local')
1424kdb5_ldap_util = os.path.join(buildtop, 'plugins', 'kdb', 'ldap', 'ldap_util',
1425                              'kdb5_ldap_util')
1426kdb5_util = os.path.join(buildtop, 'kadmin', 'dbutil', 'kdb5_util')
1427ktutil = os.path.join(buildtop, 'kadmin', 'ktutil', 'ktutil')
1428kinit = os.path.join(buildtop, 'clients', 'kinit', 'kinit')
1429klist = os.path.join(buildtop, 'clients', 'klist', 'klist')
1430kswitch = os.path.join(buildtop, 'clients', 'kswitch', 'kswitch')
1431kvno = os.path.join(buildtop, 'clients', 'kvno', 'kvno')
1432kdestroy = os.path.join(buildtop, 'clients', 'kdestroy', 'kdestroy')
1433kpasswd = os.path.join(buildtop, 'clients', 'kpasswd', 'kpasswd')
1434t_inetd = os.path.join(buildtop, 'tests', 't_inetd')
1435kproplog = os.path.join(buildtop, 'kprop', 'kproplog')
1436kpropd = os.path.join(buildtop, 'kprop', 'kpropd')
1437kprop = os.path.join(buildtop, 'kprop', 'kprop')
1438