xref: /freebsd/crypto/krb5/src/tests/t_iprop.py (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1import os
2import re
3
4from k5test import *
5
6# On macOS with System Integrity Protection enabled, this script hangs
7# in the wait_for_prop() call after starting the first kpropd process,
8# most likely due to signal restrictions preventing the listening
9# child from informing the parent that a full resync was processed.
10if which('csrutil'):
11    out = subprocess.check_output(['csrutil', 'status'],
12                                  universal_newlines=True)
13    if 'status: enabled' in out:
14        skip_rest('iprop tests', 'System Integrity Protection is enabled')
15
16# Read lines from kpropd output until we are synchronized.  Error if
17# full_expected is true and we didn't see a full propagation or vice
18# versa.
19def wait_for_prop(kpropd, full_expected, expected_old, expected_new):
20    output('*** Waiting for sync from kpropd\n')
21    full_seen = sleep_seen = False
22    old_sno = new_sno = -1
23    while True:
24        line = kpropd.stdout.readline()
25        if line == '':
26            fail('kpropd process exited unexpectedly')
27        output('kpropd: ' + line)
28
29        m = re.match(r'Calling iprop_get_updates_1 \(sno=(\d+) ', line)
30        if m:
31            if not full_seen:
32                old_sno = int(m.group(1))
33            # Also record this as the new sno, in case we get back
34            # UPDATE_NIL.
35            new_sno = int(m.group(1))
36
37        m = re.match(r'Got incremental updates \(sno=(\d+) ', line)
38        if m:
39            new_sno = int(m.group(1))
40
41        if 'KDC is synchronized' in line or 'Incremental updates:' in line:
42            break
43
44        # After a full resync request, these lines could appear in
45        # either order.
46        if 'Waiting for' in line:
47            sleep_seen = True
48        if 'load process for full propagation completed' in line:
49            full_seen = True
50
51        # Detect some failure conditions.
52        if 'Still waiting for full resync' in line:
53            fail('kadmind gave consecutive full resyncs')
54        if 'Rejected connection' in line:
55            fail('kpropd rejected kprop connection')
56        if 'get updates failed' in line:
57            fail('iprop_get_updates failed')
58        if 'permission denied' in line:
59            fail('kadmind denied update')
60        if ('error from primary' in line or
61            'error returned from primary' in line):
62            fail('kadmind reported error')
63        if 'invalid return' in line:
64            fail('kadmind returned invalid result')
65
66    if full_expected and not full_seen:
67        fail('Expected full dump but saw only incremental')
68    if full_seen and not full_expected:
69        fail('Expected incremental prop but saw full dump')
70    if old_sno != expected_old:
71         fail('Expected old serial %d from kpropd sync' % expected_old)
72    if new_sno != expected_new:
73         fail('Expected new serial %d from kpropd sync' % expected_new)
74
75    # Wait until kpropd is sleeping before continuing, to avoid races.
76    # (This is imperfect since there's there is a short window between
77    # the fprintf and the sleep; kpropd will need design changes to
78    # fix that.)
79    while True:
80        line = kpropd.stdout.readline()
81        output('kpropd: ' + line)
82        if 'Waiting for' in line:
83            break
84    output('*** Sync complete\n')
85
86# Verify the output of kproplog against the expected number of
87# entries, first and last serial number, and a list of principal names
88# for the update entrires.
89def check_ulog(num, first, last, entries, env=None):
90    out = realm.run([kproplog], env=env)
91    if 'Number of entries : ' + str(num) + '\n' not in out:
92        fail('Expected %d entries' % num)
93    if last:
94        firststr = first and str(first) or 'None'
95        if 'First serial # : ' + firststr + '\n' not in out:
96            fail('Expected first serial number %d' % first)
97    laststr = last and str(last) or 'None'
98    if 'Last serial # : ' + laststr + '\n' not in out:
99        fail('Expected last serial number %d' % last)
100    assert(len(entries) == num)
101    ser = first - 1
102    entindex = 0
103    for line in out.splitlines():
104        m = re.match(r'\tUpdate serial # : (\d+)$', line)
105        if m:
106            ser = ser + 1
107            if m.group(1) != str(ser):
108                fail('Expected serial number %d in update entry' % ser)
109        m = re.match(r'\tUpdate principal : (.*)$', line)
110        if m:
111            eprinc = entries[ser - first]
112            if eprinc == None:
113                fail('Expected dummy update entry %d' % ser)
114            elif m.group(1) != eprinc:
115                fail('Expected princ %s in update entry %d' % (eprinc, ser))
116        if line == '\tDummy entry':
117            eprinc = entries[ser - first]
118            if eprinc != None:
119                fail('Expected princ %s in update entry %d' % (eprinc, ser))
120
121# replica1 will receive updates from primary, and replica2 will
122# receive updates from replica1.  Because of the awkward way iprop and
123# kprop port configuration currently works, we need separate config
124# files for the replica and primary sides of replica1, but they use
125# the same DB and ulog file.
126conf = {'realms': {'$realm': {'iprop_enable': 'true',
127                              'iprop_logfile': '$testdir/db.ulog'}}}
128conf_rep1 = {'realms': {'$realm': {'iprop_replica_poll': '600',
129                                   'iprop_logfile': '$testdir/ulog.replica1'}},
130             'dbmodules': {'db': {'database_name': '$testdir/db.replica1'}}}
131conf_rep1m = {'realms': {'$realm': {'iprop_logfile': '$testdir/ulog.replica1',
132                                    'iprop_port': '$port8'}},
133              'dbmodules': {'db': {'database_name': '$testdir/db.replica1'}}}
134conf_rep2 = {'realms': {'$realm': {'iprop_replica_poll': '600',
135                                   'iprop_logfile': '$testdir/ulog.replica2',
136                                   'iprop_port': '$port8'}},
137             'dbmodules': {'db': {'database_name': '$testdir/db.replica2'}}}
138
139conf_foo = {'libdefaults': {'default_realm': 'FOO'},
140            'domain_realm': {hostname: 'FOO'}}
141conf_rep3 = {'realms': {'$realm': {'iprop_replica_poll': '600',
142                                   'iprop_logfile': '$testdir/ulog.replica3',
143                                   'iprop_port': '$port8'},
144                        'FOO': {'iprop_logfile': '$testdir/ulog.replica3'}},
145            'dbmodules': {'db': {'database_name': '$testdir/db.replica3'}}}
146
147krb5_conf_rep4 = {'domain_realm': {hostname: 'FOO'}}
148conf_rep4 = {'realms': {'$realm': {'iprop_replica_poll': '600',
149                                   'iprop_logfile': '$testdir/ulog.replica4',
150                                   'iprop_port': '$port8'}},
151             'dbmodules': {'db': {'database_name': '$testdir/db.replica4'}}}
152
153for realm in multidb_realms(kdc_conf=conf, create_user=False,
154                            start_kadmind=True):
155    replica1 = realm.special_env('replica1', True, kdc_conf=conf_rep1)
156    replica1m = realm.special_env('replica1m', True, krb5_conf=conf_foo,
157                                  kdc_conf=conf_rep1m)
158    replica2 = realm.special_env('replica2', True, kdc_conf=conf_rep2)
159
160    # A default_realm and domain_realm that do not match the KDC's
161    # realm.  The FOO realm iprop_logfile setting is needed to run
162    # kproplog during a replica3 test, since kproplog has no realm
163    # option.
164    replica3 = realm.special_env('replica3', True, krb5_conf=conf_foo,
165                                 kdc_conf=conf_rep3)
166
167    # A default realm and a domain realm map that differ.
168    replica4 = realm.special_env('replica4', True, krb5_conf=krb5_conf_rep4,
169                                 kdc_conf=conf_rep4)
170
171    # Define some principal names.  pr3 is long enough to cause internal
172    # reallocs, but not long enough to grow the basic ulog entry size.
173    pr1 = 'wakawaka@' + realm.realm
174    pr2 = 'w@' + realm.realm
175    c = 'chocolate-flavored-school-bus'
176    cs = c + '/'
177    pr3 = (cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + c +
178           '@' + realm.realm)
179
180    # Create the kpropd ACL file.
181    acl_file = os.path.join(realm.testdir, 'kpropd-acl')
182    acl = open(acl_file, 'w')
183    acl.write(realm.host_princ + '\n')
184    acl.close()
185
186    ulog = os.path.join(realm.testdir, 'db.ulog')
187    if not os.path.exists(ulog):
188        fail('update log not created: ' + ulog)
189
190    # Create the principal used to authenticate kpropd to kadmind.
191    kiprop_princ = 'kiprop/' + hostname
192    realm.addprinc(kiprop_princ)
193    realm.extract_keytab(kiprop_princ, realm.keytab)
194
195    # Create the initial replica databases.
196    dumpfile = os.path.join(realm.testdir, 'dump')
197    realm.run([kdb5_util, 'dump', dumpfile])
198    realm.run([kdb5_util, 'load', dumpfile], replica1)
199    realm.run([kdb5_util, 'load', dumpfile], replica2)
200    realm.run([kdb5_util, '-r', realm.realm, 'load', dumpfile], replica3)
201    realm.run([kdb5_util, 'load', dumpfile], replica4)
202
203    # Reinitialize the primary ulog so we know exactly what to expect
204    # in it.
205    realm.run([kproplog, '-R'])
206    check_ulog(1, 1, 1, [None])
207
208    # Make some changes to the primary DB.
209    realm.addprinc(pr1)
210    realm.addprinc(pr3)
211    realm.addprinc(pr2)
212    realm.run([kadminl, 'modprinc', '-allow_tix', pr2])
213    realm.run([kadminl, 'modprinc', '+allow_tix', pr2])
214    check_ulog(6, 1, 6, [None, pr1, pr3, pr2, pr2, pr2])
215
216    # Start kpropd for replica1 and get a full dump from primary.
217    mark('propagate M->1 full')
218    kpropd1 = realm.start_kpropd(replica1, ['-d'])
219    wait_for_prop(kpropd1, True, 1, 6)
220    out = realm.run([kadminl, 'listprincs'], env=replica1)
221    if pr1 not in out or pr2 not in out or pr3 not in out:
222        fail('replica1 does not have all principals from primary')
223    check_ulog(1, 6, 6, [None], replica1)
224
225    # Make a change and check that it propagates incrementally.
226    mark('propagate M->1 incremental')
227    realm.run([kadminl, 'modprinc', '-allow_tix', pr2])
228    check_ulog(7, 1, 7, [None, pr1, pr3, pr2, pr2, pr2, pr2])
229    kpropd1.send_signal(signal.SIGUSR1)
230    wait_for_prop(kpropd1, False, 6, 7)
231    check_ulog(2, 6, 7, [None, pr2], replica1)
232    realm.run([kadminl, 'getprinc', pr2], env=replica1,
233              expected_msg='Attributes: DISALLOW_ALL_TIX')
234
235    # Start kadmind -proponly for replica1.  (Use the replica1m
236    # environment which defines iprop_port to $port8.)
237    replica1_out_dump_path = os.path.join(realm.testdir, 'dump.replica1.out')
238    replica2_in_dump_path = os.path.join(realm.testdir, 'dump.replica2.in')
239    replica2_kprop_port = str(realm.portbase + 9)
240    kadmind_proponly = realm.start_server([kadmind, '-r', realm.realm,
241                                           '-nofork', '-proponly',
242                                           '-p', kdb5_util,
243                                           '-K', kprop, '-k',
244                                           replica2_kprop_port,
245                                           '-F', replica1_out_dump_path],
246                                          'starting...', replica1m)
247
248    # Test similar default_realm and domain_realm map settings with -r realm.
249    mark('propagate 1->3 full')
250    replica3_in_dump_path = os.path.join(realm.testdir, 'dump.replica3.in')
251    kpropd3 = realm.start_server([kpropd, '-d', '-D', '-r', realm.realm, '-P',
252                                  replica2_kprop_port, '-f',
253                                  replica3_in_dump_path, '-p', kdb5_util, '-a',
254                                  acl_file, '-A', hostname], 'ready', replica3)
255    wait_for_prop(kpropd3, True, 1, 7)
256    out = realm.run([kadminl, '-r', realm.realm, 'listprincs'], env=replica3)
257    if pr1 not in out or pr2 not in out or pr3 not in out:
258        fail('replica3 does not have all principals from replica1')
259    check_ulog(1, 7, 7, [None], env=replica3)
260
261    # Test an incremental propagation for the kpropd -r case.
262    mark('propagate M->1->3 incremental')
263    realm.run([kadminl, 'modprinc', '-maxlife', '20 minutes', pr1])
264    check_ulog(8, 1, 8, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1])
265    kpropd1.send_signal(signal.SIGUSR1)
266    wait_for_prop(kpropd1, False, 7, 8)
267    check_ulog(3, 6, 8, [None, pr2, pr1], replica1)
268    realm.run([kadminl, 'getprinc', pr1], env=replica1,
269              expected_msg='Maximum ticket life: 0 days 00:20:00')
270    kpropd3.send_signal(signal.SIGUSR1)
271    wait_for_prop(kpropd3, False, 7, 8)
272    check_ulog(2, 7, 8, [None, pr1], replica3)
273    realm.run([kadminl, '-r', realm.realm, 'getprinc', pr1], env=replica3,
274              expected_msg='Maximum ticket life: 0 days 00:20:00')
275    stop_daemon(kpropd3)
276
277    # Test dissimilar default_realm and domain_realm map settings (no
278    # -r realm).
279    mark('propagate 1->4 full')
280    replica4_in_dump_path = os.path.join(realm.testdir, 'dump.replica4.in')
281    kpropd4 = realm.start_server([kpropd, '-d', '-D', '-P',
282                                  replica2_kprop_port, '-f',
283                                  replica4_in_dump_path, '-p', kdb5_util,
284                                  '-a', acl_file, '-A', hostname], 'ready',
285                                 replica4)
286    wait_for_prop(kpropd4, True, 1, 8)
287    out = realm.run([kadminl, 'listprincs'], env=replica4)
288    if pr1 not in out or pr2 not in out or pr3 not in out:
289        fail('replica4 does not have all principals from replica1')
290    stop_daemon(kpropd4)
291
292    # Start kpropd for replica2.  The -A option isn't needed since
293    # we're talking to the same host as primary (we specify it anyway
294    # to exercise the code), but replica2 defines iprop_port to $port8
295    # so it will talk to replica1.  Get a full dump from replica1.
296    mark('propagate 1->2 full')
297    kpropd2 = realm.start_server([kpropd, '-d', '-D', '-P',
298                                  replica2_kprop_port, '-f',
299                                  replica2_in_dump_path, '-p', kdb5_util,
300                                  '-a', acl_file, '-A', hostname], 'ready',
301                                 replica2)
302    wait_for_prop(kpropd2, True, 1, 8)
303    check_ulog(2, 7, 8, [None, pr1], replica2)
304    out = realm.run([kadminl, 'listprincs'], env=replica1)
305    if pr1 not in out or pr2 not in out or pr3 not in out:
306        fail('replica2 does not have all principals from replica1')
307
308    # Make another change and check that it propagates incrementally
309    # to both replicas.
310    mark('propagate M->1->2 incremental')
311    realm.run([kadminl, 'modprinc', '-maxrenewlife', '22 hours', pr1])
312    check_ulog(9, 1, 9, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1, pr1])
313    kpropd1.send_signal(signal.SIGUSR1)
314    wait_for_prop(kpropd1, False, 8, 9)
315    check_ulog(4, 6, 9, [None, pr2, pr1, pr1], replica1)
316    realm.run([kadminl, 'getprinc', pr1], env=replica1,
317              expected_msg='Maximum renewable life: 0 days 22:00:00\n')
318    kpropd2.send_signal(signal.SIGUSR1)
319    wait_for_prop(kpropd2, False, 8, 9)
320    check_ulog(3, 7, 9, [None, pr1, pr1], replica2)
321    realm.run([kadminl, 'getprinc', pr1], env=replica2,
322              expected_msg='Maximum renewable life: 0 days 22:00:00\n')
323
324    # Reset the ulog on replica1 to force a full resync from primary.
325    # The resync will use the old dump file and then propagate
326    # changes.  replica2 should still be in sync with replica1 after
327    # the resync, so make sure it doesn't take a full resync.
328    mark('propagate M->1->2 full')
329    realm.run([kproplog, '-R'], replica1)
330    check_ulog(1, 1, 1, [None], replica1)
331    kpropd1.send_signal(signal.SIGUSR1)
332    wait_for_prop(kpropd1, True, 1, 9)
333    check_ulog(4, 6, 9, [None, pr2, pr1, pr1], replica1)
334    kpropd2.send_signal(signal.SIGUSR1)
335    wait_for_prop(kpropd2, False, 9, 9)
336    check_ulog(3, 7, 9, [None, pr1, pr1], replica2)
337
338    # Make another change and check that it propagates incrementally to
339    # both replicas.
340    mark('propagate M->1->2 incremental (after reset)')
341    realm.run([kadminl, 'modprinc', '+allow_tix', pr2])
342    check_ulog(10, 1, 10, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1, pr1, pr2])
343    kpropd1.send_signal(signal.SIGUSR1)
344    wait_for_prop(kpropd1, False, 9, 10)
345    check_ulog(5, 6, 10, [None, pr2, pr1, pr1, pr2], replica1)
346    realm.run([kadminl, 'getprinc', pr2], env=replica1,
347              expected_msg='Attributes:\n')
348    kpropd2.send_signal(signal.SIGUSR1)
349    wait_for_prop(kpropd2, False, 9, 10)
350    check_ulog(4, 7, 10, [None, pr1, pr1, pr2], replica2)
351    realm.run([kadminl, 'getprinc', pr2], env=replica2,
352              expected_msg='Attributes:\n')
353
354    # Create a policy and check that it propagates via full resync.
355    mark('propagate M->1->2 full (new policy)')
356    realm.run([kadminl, 'addpol', '-minclasses', '2', 'testpol'])
357    check_ulog(1, 1, 1, [None])
358    kpropd1.send_signal(signal.SIGUSR1)
359    wait_for_prop(kpropd1, True, 10, 1)
360    check_ulog(1, 1, 1, [None], replica1)
361    realm.run([kadminl, 'getpol', 'testpol'], env=replica1,
362              expected_msg='Minimum number of password character classes: 2')
363    kpropd2.send_signal(signal.SIGUSR1)
364    wait_for_prop(kpropd2, True, 10, 1)
365    check_ulog(1, 1, 1, [None], replica2)
366    realm.run([kadminl, 'getpol', 'testpol'], env=replica2,
367              expected_msg='Minimum number of password character classes: 2')
368
369    # Modify the policy and test that it also propagates via full resync.
370    mark('propagate M->1->2 full (policy change)')
371    realm.run([kadminl, 'modpol', '-minlength', '17', 'testpol'])
372    check_ulog(1, 1, 1, [None])
373    kpropd1.send_signal(signal.SIGUSR1)
374    wait_for_prop(kpropd1, True, 1, 1)
375    check_ulog(1, 1, 1, [None], replica1)
376    realm.run([kadminl, 'getpol', 'testpol'], env=replica1,
377              expected_msg='Minimum password length: 17')
378    kpropd2.send_signal(signal.SIGUSR1)
379    wait_for_prop(kpropd2, True, 1, 1)
380    check_ulog(1, 1, 1, [None], replica2)
381    realm.run([kadminl, 'getpol', 'testpol'], env=replica2,
382              expected_msg='Minimum password length: 17')
383
384    # Delete the policy and test that it propagates via full resync.
385    mark('propgate M->1->2 full (policy delete)')
386    realm.run([kadminl, 'delpol', 'testpol'])
387    check_ulog(1, 1, 1, [None])
388    kpropd1.send_signal(signal.SIGUSR1)
389    wait_for_prop(kpropd1, True, 1, 1)
390    check_ulog(1, 1, 1, [None], replica1)
391    realm.run([kadminl, 'getpol', 'testpol'], env=replica1, expected_code=1,
392              expected_msg='Policy does not exist')
393    kpropd2.send_signal(signal.SIGUSR1)
394    wait_for_prop(kpropd2, True, 1, 1)
395    check_ulog(1, 1, 1, [None], replica2)
396    realm.run([kadminl, 'getpol', 'testpol'], env=replica2, expected_code=1,
397              expected_msg='Policy does not exist')
398
399    # Modify a principal on the primary and test that it propagates
400    # incrementally.
401    mark('propagate M->1->2 incremental (after policy changes)')
402    realm.run([kadminl, 'modprinc', '-maxlife', '10 minutes', pr1])
403    check_ulog(2, 1, 2, [None, pr1])
404    kpropd1.send_signal(signal.SIGUSR1)
405    wait_for_prop(kpropd1, False, 1, 2)
406    check_ulog(2, 1, 2, [None, pr1], replica1)
407    realm.run([kadminl, 'getprinc', pr1], env=replica1,
408              expected_msg='Maximum ticket life: 0 days 00:10:00')
409    kpropd2.send_signal(signal.SIGUSR1)
410    wait_for_prop(kpropd2, False, 1, 2)
411    check_ulog(2, 1, 2, [None, pr1], replica2)
412    realm.run([kadminl, 'getprinc', pr1], env=replica2,
413              expected_msg='Maximum ticket life: 0 days 00:10:00')
414
415    # Delete a principal and test that it propagates incrementally.
416    mark('propagate M->1->2 incremental (princ delete)')
417    realm.run([kadminl, 'delprinc', pr3])
418    check_ulog(3, 1, 3, [None, pr1, pr3])
419    kpropd1.send_signal(signal.SIGUSR1)
420    wait_for_prop(kpropd1, False, 2, 3)
421    check_ulog(3, 1, 3, [None, pr1, pr3], replica1)
422    realm.run([kadminl, 'getprinc', pr3], env=replica1, expected_code=1,
423              expected_msg='Principal does not exist')
424    kpropd2.send_signal(signal.SIGUSR1)
425    wait_for_prop(kpropd2, False, 2, 3)
426    check_ulog(3, 1, 3, [None, pr1, pr3], replica2)
427    realm.run([kadminl, 'getprinc', pr3], env=replica2, expected_code=1,
428              expected_msg='Principal does not exist')
429
430    # Rename a principal and test that it propagates incrementally.
431    mark('propagate M->1->2 incremental (princ rename)')
432    renpr = "quacked@" + realm.realm
433    realm.run([kadminl, 'renprinc', pr1, renpr])
434    check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr])
435    kpropd1.send_signal(signal.SIGUSR1)
436    wait_for_prop(kpropd1, False, 3, 6)
437    check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr], replica1)
438    realm.run([kadminl, 'getprinc', pr1], env=replica1, expected_code=1,
439              expected_msg='Principal does not exist')
440    realm.run([kadminl, 'getprinc', renpr], env=replica1)
441    kpropd2.send_signal(signal.SIGUSR1)
442    wait_for_prop(kpropd2, False, 3, 6)
443    check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr], replica2)
444    realm.run([kadminl, 'getprinc', pr1], env=replica2, expected_code=1,
445              expected_msg='Principal does not exist')
446    realm.run([kadminl, 'getprinc', renpr], env=replica2)
447
448    pr1 = renpr
449
450    # Reset the ulog on the primary to force a full resync.
451    mark('propagate M->1->2 full (ulog reset)')
452    realm.run([kproplog, '-R'])
453    check_ulog(1, 1, 1, [None])
454    kpropd1.send_signal(signal.SIGUSR1)
455    wait_for_prop(kpropd1, True, 6, 1)
456    check_ulog(1, 1, 1, [None], replica1)
457    kpropd2.send_signal(signal.SIGUSR1)
458    wait_for_prop(kpropd2, True, 6, 1)
459    check_ulog(1, 1, 1, [None], replica2)
460
461    # Stop the kprop daemons so we can test kpropd -t.
462    realm.stop_kpropd(kpropd1)
463    stop_daemon(kpropd2)
464    stop_daemon(kadmind_proponly)
465    mark('kpropd -t')
466
467    # Test the case where no updates are needed.
468    out = realm.run_kpropd_once(replica1, ['-d'])
469    if 'KDC is synchronized' not in out:
470        fail('Expected synchronized from kpropd -t')
471    check_ulog(1, 1, 1, [None], replica1)
472
473    # Make a change on the primary and fetch it incrementally.
474    realm.run([kadminl, 'modprinc', '-maxlife', '5 minutes', pr1])
475    check_ulog(2, 1, 2, [None, pr1])
476    out = realm.run_kpropd_once(replica1, ['-d'])
477    if 'Got incremental updates (sno=2 ' not in out:
478        fail('Expected full dump and synchronized from kpropd -t')
479    check_ulog(2, 1, 2, [None, pr1], replica1)
480    realm.run([kadminl, 'getprinc', pr1], env=replica1,
481              expected_msg='Maximum ticket life: 0 days 00:05:00')
482
483    # Propagate a policy change via full resync.
484    realm.run([kadminl, 'addpol', '-minclasses', '3', 'testpol'])
485    check_ulog(1, 1, 1, [None])
486    out = realm.run_kpropd_once(replica1, ['-d'])
487    if ('Full propagation transfer finished' not in out or
488        'KDC is synchronized' not in out):
489        fail('Expected full dump and synchronized from kpropd -t')
490    check_ulog(1, 1, 1, [None], replica1)
491    realm.run([kadminl, 'getpol', 'testpol'], env=replica1,
492              expected_msg='Minimum number of password character classes: 3')
493
494success('iprop tests')
495