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