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