s4-s3-upgrade do not convert min password length as a time
[kai/samba-autobuild/.git] / source4 / scripting / python / samba / upgrade.py
1 # backend code for upgrading from Samba3
2 # Copyright Jelmer Vernooij 2005-2007
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17
18 """Support code for upgrading from Samba 3 to Samba 4."""
19
20 __docformat__ = "restructuredText"
21
22 import grp
23 import ldb
24 import time
25 import pwd
26
27 from samba import Ldb, registry
28 from samba.param import LoadParm
29 from samba.provision import provision, FILL_FULL, ProvisioningError
30 from samba.samba3 import passdb
31 from samba.samba3 import param as s3param
32 from samba.dcerpc import lsa, samr, security
33 from samba.dcerpc.security import dom_sid
34 from samba import dsdb
35 from samba.ndr import ndr_pack
36 from samba import unix2nttime
37
38 def import_sam_policy(samdb, policy, logger):
39     """Import a Samba 3 policy.
40
41     :param samdb: Samba4 SAM database
42     :param policy: Samba3 account policy
43     :param logger: Logger object
44     """
45
46     # Following entries are used -
47     #    min password length, password history, minimum password age,
48     #    maximum password age, lockout duration
49     #
50     # Following entries are not used -
51     #    reset count minutes, user must logon to change password,
52     #    bad lockout minutes, disconnect time
53
54     m = ldb.Message()
55     m.dn = samdb.get_default_basedn()
56     m['a01'] = ldb.MessageElement(str(policy['min password length']), ldb.FLAG_MOD_REPLACE,
57                             'minPwdLength')
58     m['a02'] = ldb.MessageElement(str(policy['password history']), ldb.FLAG_MOD_REPLACE,
59                             'pwdHistoryLength')
60
61     min_pw_age_unix = policy['minimum password age']
62     min_pw_age_nt = 0 - unix2nttime(min_pw_age_unix)
63     m['a03'] = ldb.MessageElement(str(min_pw_age_nt), ldb.FLAG_MOD_REPLACE, 'minPwdAge')
64
65     max_pw_age_unix = policy['maximum password age']
66     if (max_pw_age_unix == 0xFFFFFFFF):
67         max_pw_age_nt = 0
68     else:
69         max_pw_age_nt = unix2nttime(max_pw_age_unix)
70
71     m['a04'] = ldb.MessageElement(str(max_pw_age_nt), ldb.FLAG_MOD_REPLACE,
72                                   'maxPwdAge')
73
74     lockout_duration_mins = policy['lockout duration']
75     lockout_duration_nt = unix2nttime(lockout_duration_mins * 60)
76
77     m['a05'] = ldb.MessageElement(str(lockout_duration_nt), ldb.FLAG_MOD_REPLACE,
78                                   'lockoutDuration')
79
80     try:
81         samdb.modify(m)
82     except ldb.LdbError, e:
83         logger.warn("Could not set account policy, (%s)", str(e))
84
85
86 def add_idmap_entry(idmapdb, sid, xid, xid_type, logger):
87     """Create idmap entry
88
89     :param idmapdb: Samba4 IDMAP database
90     :param sid: user/group sid
91     :param xid: user/group id
92     :param xid_type: type of id (UID/GID)
93     :param logger: Logger object
94     """
95
96     # First try to see if we already have this entry
97     found = False
98     msg = idmapdb.search(expression='objectSid=%s' % str(sid))
99     if msg.count == 1:
100         found = True
101
102     if found:
103         try:
104             m = ldb.Message()
105             m.dn = msg[0]['dn']
106             m['xidNumber'] = ldb.MessageElement(str(xid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
107             m['type'] = ldb.MessageElement(xid_type, ldb.FLAG_MOD_REPLACE, 'type')
108             idmapdb.modify(m)
109         except ldb.LdbError, e:
110             logger.warn('Could not modify idmap entry for sid=%s, id=%s, type=%s (%s)',
111                             str(sid), str(xid), xid_type, str(e))
112     else:
113         try:
114             idmapdb.add({"dn": "CN=%s" % str(sid),
115                         "cn": str(sid),
116                         "objectClass": "sidMap",
117                         "objectSid": ndr_pack(sid),
118                         "type": xid_type,
119                         "xidNumber": str(xid)})
120         except ldb.LdbError, e:
121             logger.warn('Could not add idmap entry for sid=%s, id=%s, type=%s (%s)',
122                             str(sid), str(xid), xid_type, str(e))
123
124
125 def import_idmap(idmapdb, samba3, logger):
126     """Import idmap data.
127
128     :param idmapdb: Samba4 IDMAP database
129     :param samba3_idmap: Samba3 IDMAP database to import from
130     :param logger: Logger object
131     """
132
133     try:
134         samba3_idmap = samba3.get_idmap_db()
135     except IOError as (errno, strerror):
136         logger.warn('Cannot open idmap database, Ignoring: ({0}): {1}'.format(errno, strerror))
137         return
138
139     currentxid = max(samba3_idmap.get_user_hwm(), samba3_idmap.get_group_hwm())
140     lowerbound = currentxid
141     # FIXME: upperbound
142
143     m = ldb.Message()
144     m.dn = ldb.Dn(idmapdb, 'CN=CONFIG')
145     m['lowerbound'] = ldb.MessageElement(str(lowerbound), ldb.FLAG_MOD_REPLACE, 'lowerBound')
146     m['xidNumber'] = ldb.MessageElement(str(currentxid), ldb.FLAG_MOD_REPLACE, 'xidNumber')
147     idmapdb.modify(m)
148
149     for id_type, xid in samba3_idmap.ids():
150         if id_type == 'UID':
151             xid_type = 'ID_TYPE_UID'
152         elif id_type == 'GID':
153             xid_type = 'ID_TYPE_GID'
154         else:
155             logger.warn('Wrong type of entry in idmap (%s), Ignoring', id_type)
156             continue
157
158         sid = samba3_idmap.get_sid(xid, id_type)
159         add_idmap_entry(idmapdb, dom_sid(sid), xid, xid_type, logger)
160
161
162 def add_group_from_mapping_entry(samdb, groupmap, logger):
163     """Add or modify group from group mapping entry
164
165     param samdb: Samba4 SAM database
166     param groupmap: Groupmap entry
167     param logger: Logger object
168     """
169
170     # First try to see if we already have this entry
171     try:
172         msg = samdb.search(base='<SID=%s>' % str(groupmap.sid), scope=ldb.SCOPE_BASE)
173         found = True
174     except ldb.LdbError, (ecode, emsg):
175         if ecode == ldb.ERR_NO_SUCH_OBJECT:
176             found = False
177         else:
178             raise ldb.LdbError(ecode, emsg)
179
180     if found:
181         logger.warn('Group already exists sid=%s, groupname=%s existing_groupname=%s, Ignoring.',
182                             str(groupmap.sid), groupmap.nt_name, msg[0]['sAMAccountName'][0])
183     else:
184         if groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
185             # In a lot of Samba3 databases, aliases are marked as well known groups
186             (group_dom_sid, rid) = group.sid.split()
187             if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
188                 return
189
190         m = ldb.Message()
191         m.dn = ldb.Dn(samdb, "CN=%s,CN=Users,%s" % (groupmap.nt_name, samdb.get_default_basedn()))
192         m['a01'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'cn')
193         m['a02'] = ldb.MessageElement('group', ldb.FLAG_MOD_ADD, 'objectClass')
194         m['a03'] = ldb.MessageElement(ndr_pack(groupmap.sid), ldb.FLAG_MOD_ADD, 'objectSid')
195         m['a04'] = ldb.MessageElement(groupmap.comment, ldb.FLAG_MOD_ADD, 'description')
196         m['a05'] = ldb.MessageElement(groupmap.nt_name, ldb.FLAG_MOD_ADD, 'sAMAccountName')
197
198         # Fix up incorrect 'well known' groups that are actually builtin (per test above) to be aliases
199         if groupmap.sid_name_use == lsa.SID_NAME_ALIAS or groupmap.sid_name_use == lsa.SID_NAME_WKN_GRP:
200             m['a06'] = ldb.MessageElement(str(dsdb.GTYPE_SECURITY_DOMAIN_LOCAL_GROUP), ldb.FLAG_MOD_ADD, 'groupType')
201
202         try:
203             samdb.add(m, controls=["relax:0"])
204         except ldb.LdbError, e:
205             logger.warn('Could not add group name=%s (%s)', groupmap.nt_name, str(e))
206
207
208 def add_users_to_group(samdb, group, members, logger):
209     """Add user/member to group/alias
210
211     param samdb: Samba4 SAM database
212     param group: Groupmap object
213     param members: List of member SIDs
214     param logger: Logger object
215     """
216     for member_sid in members:
217         m = ldb.Message()
218         m.dn = ldb.Dn(samdb, "<SID=%s>" % str(group.sid))
219         m['a01'] = ldb.MessageElement("<SID=%s>" % str(member_sid), ldb.FLAG_MOD_ADD, 'member')
220
221         try:
222             samdb.modify(m)
223         except ldb.LdbError, (ecode, emsg):
224             if ecode == ldb.ERR_ENTRY_ALREADY_EXISTS:
225                 logger.info("skipped re-adding member '%s' to group '%s': %s", member_sid, group.sid, emsg)
226             elif ecode == ldb.ERR_NO_SUCH_OBJECT:
227                 raise ProvisioningError("Could not add member '%s' to group '%s' as either group or user record doesn't exist: %s" % (member_sid, group.sid, emsg))
228             else:
229                 raise ProvisioningError("Could not add member '%s' to group '%s': %s" % (member_sid, group.sid, emsg))
230
231
232 def import_wins(samba4_winsdb, samba3_winsdb):
233     """Import settings from a Samba3 WINS database.
234
235     :param samba4_winsdb: WINS database to import to
236     :param samba3_winsdb: WINS database to import from
237     """
238     version_id = 0
239
240     for (name, (ttl, ips, nb_flags)) in samba3_winsdb.items():
241         version_id+=1
242
243         type = int(name.split("#", 1)[1], 16)
244
245         if type == 0x1C:
246             rType = 0x2
247         elif type & 0x80:
248             if len(ips) > 1:
249                 rType = 0x2
250             else:
251                 rType = 0x1
252         else:
253             if len(ips) > 1:
254                 rType = 0x3
255             else:
256                 rType = 0x0
257
258         if ttl > time.time():
259             rState = 0x0 # active
260         else:
261             rState = 0x1 # released
262
263         nType = ((nb_flags & 0x60)>>5)
264
265         samba4_winsdb.add({"dn": "name=%s,type=0x%s" % tuple(name.split("#")),
266                            "type": name.split("#")[1],
267                            "name": name.split("#")[0],
268                            "objectClass": "winsRecord",
269                            "recordType": str(rType),
270                            "recordState": str(rState),
271                            "nodeType": str(nType),
272                            "expireTime": ldb.timestring(ttl),
273                            "isStatic": "0",
274                            "versionID": str(version_id),
275                            "address": ips})
276
277     samba4_winsdb.add({"dn": "cn=VERSION",
278                        "cn": "VERSION",
279                        "objectClass": "winsMaxVersion",
280                        "maxVersion": str(version_id)})
281
282 def enable_samba3sam(samdb, ldapurl):
283     """Enable Samba 3 LDAP URL database.
284
285     :param samdb: SAM Database.
286     :param ldapurl: Samba 3 LDAP URL
287     """
288     samdb.modify_ldif("""
289 dn: @MODULES
290 changetype: modify
291 replace: @LIST
292 @LIST: samldb,operational,objectguid,rdn_name,samba3sam
293 """)
294
295     samdb.add({"dn": "@MAP=samba3sam", "@MAP_URL": ldapurl})
296
297
298 smbconf_keep = [
299     "dos charset",
300     "unix charset",
301     "display charset",
302     "comment",
303     "path",
304     "directory",
305     "workgroup",
306     "realm",
307     "netbios name",
308     "netbios aliases",
309     "netbios scope",
310     "server string",
311     "interfaces",
312     "bind interfaces only",
313     "security",
314     "auth methods",
315     "encrypt passwords",
316     "null passwords",
317     "obey pam restrictions",
318     "password server",
319     "smb passwd file",
320     "private dir",
321     "passwd chat",
322     "password level",
323     "lanman auth",
324     "ntlm auth",
325     "client NTLMv2 auth",
326     "client lanman auth",
327     "client plaintext auth",
328     "read only",
329     "hosts allow",
330     "hosts deny",
331     "log level",
332     "debuglevel",
333     "log file",
334     "smb ports",
335     "large readwrite",
336     "max protocol",
337     "min protocol",
338     "unicode",
339     "read raw",
340     "write raw",
341     "disable netbios",
342     "nt status support",
343     "max mux",
344     "max xmit",
345     "name resolve order",
346     "max wins ttl",
347     "min wins ttl",
348     "time server",
349     "unix extensions",
350     "use spnego",
351     "server signing",
352     "client signing",
353     "max connections",
354     "paranoid server security",
355     "socket options",
356     "strict sync",
357     "max print jobs",
358     "printable",
359     "print ok",
360     "printer name",
361     "printer",
362     "map system",
363     "map hidden",
364     "map archive",
365     "preferred master",
366     "prefered master",
367     "local master",
368     "browseable",
369     "browsable",
370     "wins server",
371     "wins support",
372     "csc policy",
373     "strict locking",
374     "preload",
375     "auto services",
376     "lock dir",
377     "lock directory",
378     "pid directory",
379     "socket address",
380     "copy",
381     "include",
382     "available",
383     "volume",
384     "fstype",
385     "panic action",
386     "msdfs root",
387     "host msdfs",
388     "winbind separator"]
389
390 def upgrade_smbconf(oldconf,mark):
391     """Remove configuration variables not present in Samba4
392
393     :param oldconf: Old configuration structure
394     :param mark: Whether removed configuration variables should be
395         kept in the new configuration as "samba3:<name>"
396     """
397     data = oldconf.data()
398     newconf = LoadParm()
399
400     for s in data:
401         for p in data[s]:
402             keep = False
403             for k in smbconf_keep:
404                 if smbconf_keep[k] == p:
405                     keep = True
406                     break
407
408             if keep:
409                 newconf.set(s, p, oldconf.get(s, p))
410             elif mark:
411                 newconf.set(s, "samba3:"+p, oldconf.get(s,p))
412
413     return newconf
414
415 SAMBA3_PREDEF_NAMES = {
416         'HKLM': registry.HKEY_LOCAL_MACHINE,
417 }
418
419 def import_registry(samba4_registry, samba3_regdb):
420     """Import a Samba 3 registry database into the Samba 4 registry.
421
422     :param samba4_registry: Samba 4 registry handle.
423     :param samba3_regdb: Samba 3 registry database handle.
424     """
425     def ensure_key_exists(keypath):
426         (predef_name, keypath) = keypath.split("/", 1)
427         predef_id = SAMBA3_PREDEF_NAMES[predef_name]
428         keypath = keypath.replace("/", "\\")
429         return samba4_registry.create_key(predef_id, keypath)
430
431     for key in samba3_regdb.keys():
432         key_handle = ensure_key_exists(key)
433         for subkey in samba3_regdb.subkeys(key):
434             ensure_key_exists(subkey)
435         for (value_name, (value_type, value_data)) in samba3_regdb.values(key).items():
436             key_handle.set_value(value_name, value_type, value_data)
437
438
439 def upgrade_from_samba3(samba3, logger, targetdir, session_info=None, useeadb=False):
440     """Upgrade from samba3 database to samba4 AD database
441
442     :param samba3: samba3 object
443     :param logger: Logger object
444     :param targetdir: samba4 database directory
445     :param session_info: Session information
446     """
447
448     if samba3.lp.get("domain logons"):
449         serverrole = "domain controller"
450     else:
451         if samba3.lp.get("security") == "user":
452             serverrole = "standalone"
453         else:
454             serverrole = "member server"
455
456     domainname = samba3.lp.get("workgroup")
457     realm = samba3.lp.get("realm")
458     netbiosname = samba3.lp.get("netbios name")
459
460     # secrets db
461     secrets_db = samba3.get_secrets_db()
462
463     if not domainname:
464         domainname = secrets_db.domains()[0]
465         logger.warning("No workgroup specified in smb.conf file, assuming '%s'",
466                 domainname)
467
468     if not realm:
469         if serverrole == "domain controller":
470             raise ProvisioningError("No realm specified in smb.conf file and being a DC. That upgrade path doesn't work! Please add a 'realm' directive to your old smb.conf to let us know which one you want to use (it is the DNS name of the AD domain you wish to create.")
471         else:
472             realm = domainname.upper()
473             logger.warning("No realm specified in smb.conf file, assuming '%s'",
474                     realm)
475
476     # Find machine account and password
477     machinepass = None
478     machinerid = None
479     machinesid = None
480     next_rid = 1000
481
482     try:
483         machinepass = secrets_db.get_machine_password(netbiosname)
484     except:
485         pass
486
487     # We must close the direct pytdb database before the C code loads it
488     secrets_db.close()
489
490     # Connect to old password backend
491     passdb.set_secrets_dir(samba3.lp.get("private dir"))
492     s3db = samba3.get_sam_db()
493
494     # Get domain sid
495     try:
496         domainsid = passdb.get_global_sam_sid()
497     except passdb.error:
498         raise Exception("Can't find domain sid for '%s', Exiting." % domainname)
499
500     # Get machine account, sid, rid
501     try:
502         machineacct = s3db.getsampwnam('%s$' % netbiosname)
503         machinesid, machinerid = machineacct.user_sid.split()
504     except:
505         pass
506
507     # Export account policy
508     logger.info("Exporting account policy")
509     policy = s3db.get_account_policy()
510
511     # Export groups from old passdb backend
512     logger.info("Exporting groups")
513     grouplist = s3db.enum_group_mapping()
514     groupmembers = {}
515     for group in grouplist:
516         sid, rid = group.sid.split()
517         if sid == domainsid:
518             if rid >= next_rid:
519                next_rid = rid + 1
520
521         # Get members for each group/alias
522         if group.sid_name_use == lsa.SID_NAME_ALIAS:
523             members = s3db.enum_aliasmem(group.sid)
524         elif group.sid_name_use == lsa.SID_NAME_DOM_GRP:
525             try:
526                 members = s3db.enum_group_members(group.sid)
527             except:
528                 continue
529             groupmembers[group.nt_name] = members
530         elif group.sid_name_use == lsa.SID_NAME_WKN_GRP:
531             (group_dom_sid, rid) = group.sid.split()
532             if (group_dom_sid != security.dom_sid(security.SID_BUILTIN)):
533                 logger.warn("Ignoring 'well known' group '%s' (should already be in AD, and have no members)",
534                             group.nt_name)
535                 continue
536             # A number of buggy databases mix up well known groups and aliases.
537             members = s3db.enum_aliasmem(group.sid)
538         else:
539             logger.warn("Ignoring group '%s' with sid_name_use=%d",
540                         group.nt_name, group.sid_name_use)
541             continue
542
543
544     # Export users from old passdb backend
545     logger.info("Exporting users")
546     userlist = s3db.search_users(0)
547     userdata = {}
548     uids = {}
549     admin_user = None
550     for entry in userlist:
551         if machinerid and machinerid == entry['rid']:
552             continue
553         username = entry['account_name']
554         if entry['rid'] < 1000:
555             logger.info("  Skipping wellknown rid=%d (for username=%s)", entry['rid'], username)
556             continue
557         if entry['rid'] >= next_rid:
558             next_rid = entry['rid'] + 1
559
560         user = s3db.getsampwnam(username)
561         acct_type = (user.acct_ctrl & (samr.ACB_NORMAL|samr.ACB_WSTRUST|samr.ACB_SVRTRUST|samr.ACB_DOMTRUST))
562         if (acct_type == samr.ACB_NORMAL or acct_type == samr.ACB_WSTRUST or acct_type == samr.ACB_SVRTRUST):
563             pass
564         elif acct_type == samr.ACB_DOMTRUST:
565             logger.warn("  Skipping inter-domain trust from domain %s, this trust must be re-created as an AD trust" % username[:-1])
566             continue
567         elif acct_type == (samr.ACB_NORMAL|samr.ACB_WSTRUST) and username[-1] == '$':
568             logger.warn("  Fixing account %s which had both ACB_NORMAL (U) and ACB_WSTRUST (W) set.  Account will be marked as ACB_WSTRUST (W), i.e. as a domain member" % username)
569             user.acct_ctrl = (user.acct_ctrl & ~samr.ACB_NORMAL)
570         else:
571             raise ProvisioningError("""Failed to upgrade due to invalid account %s, account control flags 0x%08X must have exactly one of
572 ACB_NORMAL (N, 0x%08X), ACB_WSTRUST (W 0x%08X), ACB_SVRTRUST (S 0x%08X) or ACB_DOMTRUST (D 0x%08X).
573
574 Please fix this account before attempting to upgrade again
575 """
576                                     % (user.acct_flags, username,
577                                        samr.ACB_NORMAL, samr.ACB_WSTRUST, samr.ACB_SVRTRUST, samr.ACB_DOMTRUST))
578         
579         userdata[username] = user
580         try:
581             uids[username] = s3db.sid_to_id(user.user_sid)[0]
582         except:
583             try:
584                 uids[username] = pwd.getpwnam(username).pw_uid
585             except:
586                 pass
587
588         if not admin_user and username.lower() == 'root':
589             admin_user = username
590         if username.lower() == 'administrator':
591             admin_user = username
592
593     logger.info("Next rid = %d", next_rid)
594
595     # Do full provision
596     result = provision(logger, session_info, None,
597                        targetdir=targetdir, realm=realm, domain=domainname,
598                        domainsid=str(domainsid), next_rid=next_rid,
599                        dc_rid=machinerid,
600                        hostname=netbiosname, machinepass=machinepass,
601                        serverrole=serverrole, samdb_fill=FILL_FULL,
602                        useeadb=useeadb)
603
604     # Import WINS database
605     logger.info("Importing WINS database")
606     import_wins(Ldb(result.paths.winsdb), samba3.get_wins_db())
607
608     # Set Account policy
609     logger.info("Importing Account policy")
610     import_sam_policy(result.samdb, policy, logger)
611
612     # Migrate IDMAP database
613     logger.info("Importing idmap database")
614     import_idmap(result.idmap, samba3, logger)
615
616     # Set the s3 context for samba4 configuration
617     new_lp_ctx = s3param.get_context()
618     new_lp_ctx.load(result.lp.configfile)
619     new_lp_ctx.set("private dir", result.lp.get("private dir"))
620     new_lp_ctx.set("state directory", result.lp.get("state directory"))
621     new_lp_ctx.set("lock directory", result.lp.get("lock directory"))
622
623     # Connect to samba4 backend
624     s4_passdb = passdb.PDB(new_lp_ctx.get("passdb backend"))
625
626     # Export groups to samba4 backend
627     logger.info("Importing groups")
628     for g in grouplist:
629         # Ignore uninitialized groups (gid = -1)
630         if g.gid != 0xffffffff:
631             add_idmap_entry(result.idmap, g.sid, g.gid, "GID", logger)
632             add_group_from_mapping_entry(result.samdb, g, logger)
633
634     # Export users to samba4 backend
635     logger.info("Importing users")
636     for username in userdata:
637         if username.lower() == 'administrator' or username.lower() == 'root':
638             continue
639         s4_passdb.add_sam_account(userdata[username])
640         if username in uids:
641             add_idmap_entry(result.idmap, userdata[username].user_sid, uids[username], "UID", logger)
642
643     logger.info("Adding users to groups")
644     for g in grouplist:
645         if g.nt_name in groupmembers:
646             add_users_to_group(result.samdb, g, groupmembers[g.nt_name], logger)
647
648     # Set password for administrator
649     if admin_user:
650         logger.info("Setting password for administrator")
651         admin_userdata = s4_passdb.getsampwnam("administrator")
652         admin_userdata.nt_passwd = userdata[admin_user].nt_passwd
653         if userdata[admin_user].lanman_passwd:
654             admin_userdata.lanman_passwd = userdata[admin_user].lanman_passwd
655         admin_userdata.pass_last_set_time = userdata[admin_user].pass_last_set_time
656         if userdata[admin_user].pw_history:
657             admin_userdata.pw_history = userdata[admin_user].pw_history
658         s4_passdb.update_sam_account(admin_userdata)
659         logger.info("Administrator password has been set to password of user '%s'", admin_user)
660
661     # FIXME: import_registry(registry.Registry(), samba3.get_registry())
662     # FIXME: shares