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