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