diff options
-rwxr-xr-x | source4/scripting/bin/upgradeprovision | 195 | ||||
-rwxr-xr-x | source4/scripting/python/samba/upgradehelpers.py | 42 |
2 files changed, 205 insertions, 32 deletions
diff --git a/source4/scripting/bin/upgradeprovision b/source4/scripting/bin/upgradeprovision index 3ce6ae422a..ac9ab64e37 100755 --- a/source4/scripting/bin/upgradeprovision +++ b/source4/scripting/bin/upgradeprovision @@ -68,6 +68,8 @@ GUESS = 0x04 PROVISION = 0x08 CHANGEALL = 0xff +__docformat__ = "restructuredText" + # Attributes that are never copied from the reference provision (even if they # do not exist in the destination object). # This is most probably because they are populated automatcally when object is @@ -127,12 +129,17 @@ opts = parser.parse_args()[0] whatToLog = define_what_to_log(opts) def messageprovision(text): - """print a message if quiet is not set.""" + """Print a message if quiet is not set + + :param text: Message to print """ if opts.debugprovision or opts.debugall: print text def message(what,text): - """print a message if quiet is not set.""" + """Print a message if this message type has been selected to be printed + + :param what: Category of the message + :param text: Message to print """ if (whatToLog & what) or (what <= 0 ): print text @@ -145,35 +152,57 @@ creds = credopts.get_credentials(lp) creds.set_kerberos_state(DONT_USE_KERBEROS) setup_dir = opts.setupdir if setup_dir is None: - setup_dir = find_setup_dir() + setup_dir = find_setup_dir() session = system_session() -# simple helper to allow back and forth rename def identic_rename(ldbobj,dn): + """Perform a back and forth rename to trigger renaming on attribute that can't be directly modified. + + :param lbdobj: An Ldb Object + :param dn: DN of the object to manipulate """ (before,sep,after)=str(dn).partition('=') ldbobj.rename(dn,Dn(ldbobj,"%s=foo%s"%(before,after))) ldbobj.rename(Dn(ldbobj,"%s=foo%s"%(before,after)),dn) -# Create an array of backlinked attributes def populate_backlink(newpaths,creds,session,schemadn): + """Populate an array with all the back linked attributes + + This attributes that are modified automaticaly when + front attibutes are changed + + :param newpaths: a list of paths for different provision objects + :param creds: credential for the authentification + :param session: session for connexion + :param schemadn: DN of the schema for the partition""" newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp) linkedAttHash = get_linked_attributes(Dn(newsam_ldb,str(schemadn)),newsam_ldb) backlinked.extend(linkedAttHash.values()) # Create an array of attributes with a dn synthax (2.5.5.1) def populate_dnsyntax(newpaths,creds,session,schemadn): + """Populate an array with all the attributes that have DN synthax (oid 2.5.5.1) + + :param newpaths: a list of paths for different provision objects + :param creds: credential for the authentification + :param session: session for connexion + :param schemadn: DN of the schema for the partition""" newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp) res = newsam_ldb.search(expression="(attributeSyntax=2.5.5.1)",base=Dn(newsam_ldb,str(schemadn)), scope=SCOPE_SUBTREE, attrs=["lDAPDisplayName"]) for elem in res: dn_syntax_att.append(elem["lDAPDisplayName"]) - - def sanitychecks(credentials,session_info,names,paths): + """Populate an array with all the attributes that have DN synthax (oid 2.5.5.1) + + :param creds: credential for the authentification + :param session_info: session for connexion + :param names: list of key provision parameters + :param paths: list of path to provision object + :return: Status of check (1 for Ok, 0 for not Ok) """ sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"]) - # First update the SD for the rootdn + sam_ldb.set_session_info(session) res = sam_ldb.search(expression="objectClass=ntdsdsa",base=str(names.configdn), scope=SCOPE_SUBTREE,attrs=["dn"],controls=["search_options:1:2"]) @@ -188,8 +217,10 @@ domain with more than one DC, please demote the other(s) DC(s) before upgrading" return 1 -# Debug a little bit def print_provision_key_parameters(names): + """Do a a pretty print of provision parameters + + :param names: list of key provision parameters """ message(GUESS, "rootdn :"+str(names.rootdn)) message(GUESS, "configdn :"+str(names.configdn)) message(GUESS, "schemadn :"+str(names.schemadn)) @@ -208,9 +239,18 @@ def print_provision_key_parameters(names): message(GUESS, "ntdsguid :"+names.ntdsguid) message(GUESS, "domainlevel :"+str(names.domainlevel)) -# Check for security descriptors modifications return 1 if it is and 0 otherwise -# it also populate hash structure for later use in the upgrade process def handle_security_desc(ischema, att, msgElt, hashallSD, old, new): + """Check if the security descriptor has been modified. + + This function also populate a hash used for the upgrade process. + :param ischema: Boolean that indicate if it's the schema that is updated + :param att: Name of the attribute + :param msgElt: MessageElement object + :param hashallSD: Hash table with DN as key and the old SD as value + :param old: The updated LDAP object + :param new: The reference LDAP object + :return: 1 to indicate that the attribute should be kept, 0 for discarding it + """ if ischema == 1 and att == "defaultSecurityDescriptor" and msgElt.flags() == FLAG_MOD_REPLACE: hashSD = {} hashSD["oldSD"] = old[0][att] @@ -226,11 +266,16 @@ def handle_security_desc(ischema, att, msgElt, hashallSD, old, new): return 0 return 0 -# Handle special cases ... That's when we want to update a particular attribute -# only, e.g. if it has a certain value or if it's for a certain object or -# a class of object. -# It can be also if we want to do a merge of value instead of a simple replace def handle_special_case(att, delta, new, old, ischema): + """Define more complicate update rules for some attributes + + :param att: The attribute to be updated + :param delta: A messageElement object that correspond to the difference between the updated object and the reference one + :param new: The reference object + :param old: The Updated object + :param ischema: A boolean that indicate that the attribute is part of a schema object + :return: 1 to indicate that the attribute should be kept, 0 for discarding it + """ flag = delta.get(att).flags() if (att == "gPLink" or att == "gPCFileSysPath") and \ flag == FLAG_MOD_REPLACE and str(new[0].dn).lower() == str(old[0].dn).lower(): @@ -277,6 +322,13 @@ def handle_special_case(att, delta, new, old, ischema): return 0 def update_secrets(newpaths, paths, creds, session): + """Update secrets.ldb + + :param newpaths: a list of paths for different provision objects from the reference provision + :param paths: a list of paths for different provision objects from the upgraded provision + :param creds: credential for the authentification + :param session: session for connexion""" + message(SIMPLE,"update secrets.ldb") newsecrets_ldb = Ldb(newpaths.secrets, session_info=session, credentials=creds,lp=lp) secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp, options=["modules:samba_secrets"]) @@ -309,6 +361,7 @@ def update_secrets(newpaths, paths, creds, session): listMissing.append(hash_new[k]) else: listPresent.append(hash_new[k]) + for entry in listMissing: reference = newsecrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE) current = secrets_ldb.search(expression="dn=%s"%entry,base="", scope=SCOPE_SUBTREE) @@ -353,6 +406,14 @@ def update_secrets(newpaths, paths, creds, session): secrets_ldb.modify(delta) def dump_denied_change(dn,att,flagtxt,current,reference): + """Print detailed information about why a changed is denied + + :param dn: DN of the object which attribute is denied + :param att: Attribute that was supposed to be upgraded + :param flagtxt: Type of the update that should be performed (add, change, remove, ...) + :param current: Value(s) of the current attribute + :param reference: Value(s) of the reference attribute""" + message(CHANGE, "dn= "+str(dn)+" "+att+" with flag "+flagtxt+" is not allowed to be changed/removed, I discard this change ...") if att != "objectSid" : i = 0 @@ -368,9 +429,13 @@ def dump_denied_change(dn,att,flagtxt,current,reference): message(CHANGE,"old : %s"%str(ndr_unpack( security.dom_sid,current[0]))) message(CHANGE,"new : %s"%str(ndr_unpack( security.dom_sid,reference[0]))) -#This function is for doing case by case treatment on special object - def handle_special_add(sam_ldb,dn,names): + """Handle special operation (like remove) on some object needed during upgrade + + This is mostly due to wrong creation of the object in previous provision. + :param sam_ldb: An Ldb object representing the SAM database + :param dn: DN of the object to inspect + :param names: list of key provision parameters""" dntoremove=None if str(dn).lower() == ("CN=Certificate Service DCOM Access,CN=Builtin,%s"%names.rootdn).lower(): #This entry was misplaced lets remove it if it exists @@ -395,6 +460,16 @@ def handle_special_add(sam_ldb,dn,names): #First dn to be created has the creation order 0, second has 1, ... #Index contain the current creation order def check_dn_nottobecreated(hash,index,listdn): + """Check if one of the DN present in the list has a creation order greater than the current. + + Hash is indexed by dn to be created, with each key is associated the creation order + First dn to be created has the creation order 0, second has 1, ... + Index contain the current creation order + :param hash: Hash holding the different DN of the object to be created as key + :param index: Current creation order + :param listdn: List of DNs on which the current DN depends on + :return: None if the current object do not depend on other object or if all object have been + created before.""" if listdn == None: return None for dn in listdn: @@ -403,9 +478,18 @@ def check_dn_nottobecreated(hash,index,listdn): return str(dn) return None -#This function tries to add the missing object "dn" if this object depends on some others -# the function returns 0, if the object was created 1 is returned def add_missing_object(newsam_ldb, sam_ldb, dn, names, basedn, hash, index): + """Add a new object if the dependencies are satisfied + + The function add the object if the object on which it depends are already created + :param newsam_ldb: Ldb object representing the SAM db of the reference provision + :param sam_ldb: Ldb object representing the SAM db of the upgraded provision + :param dn: DN of the object to be added + :param names: List of key provision parameters + :param basedn: DN of the partition to be updated + :param hash: Hash holding the different DN of the object to be created as key + :param index: Current creation order + :return: 1 if the object was created 0 otherwise""" handle_special_add(sam_ldb,dn,names) reference = newsam_ldb.search(expression="dn=%s"%(str(dn)),base=basedn, scope=SCOPE_SUBTREE,controls=["search_options:1:2"]) @@ -428,12 +512,25 @@ def add_missing_object(newsam_ldb, sam_ldb, dn, names, basedn, hash, index): return 1 def gen_dn_index_hash(listMissing): + """Generate a hash associating the DN to its creation order + + :param listMissing: List of DN + :return: Hash with DN as keys and creation order as values""" hash = {} for i in range(0,len(listMissing)): hash[str(listMissing[i]).lower()] = i return hash def add_missing_entries(newsam_ldb, sam_ldb, names, basedn,list): + """Add the missing object whose DN is the list + + The function add the object if the object on which it depends are already created + :param newsam_ldb: Ldb object representing the SAM db of the reference provision + :param sam_ldb: Ldb object representing the SAM db of the upgraded provision + :param dn: DN of the object to be added + :param names: List of key provision parameters + :param basedn: DN of the partition to be updated + :param list: List of DN to be added in the upgraded provision""" listMissing = [] listDefered = list @@ -459,6 +556,19 @@ def add_missing_entries(newsam_ldb, sam_ldb, names, basedn,list): # the scan is done in cross partition mode. # If "ischema" is true, then special handling is done for dealing with schema def check_diff_name(newpaths, paths, creds, session, basedn, names, ischema): + """Check differences between the reference provision and the upgraded one. + + This function will also add the missing object and update existing object to add + or remove attributes that were missing. + :param newpaths: List of paths for different provision objects from the reference provision + :param paths: List of paths for different provision objects from the upgraded provision + :param creds: Credential for the authentification + :param session: Session for connexion + :param basedn: DN of the partition to update + :param names: List of key provision parameters + :param ischema: Boolean indicating if the update is about the schema only + :return: Hash of security descriptor to update""" + hash_new = {} hash = {} hashallSD = {} @@ -573,8 +683,15 @@ def check_diff_name(newpaths, paths, creds, session, basedn, names, ischema): message(SIMPLE,"There are %d changed objects"%(changed)) return hashallSD -# Check that SD are correct def check_updated_sd(newpaths, paths, creds, session, names): + """Check if the security descriptor in the upgraded provision are the same as the reference + + :param newpaths: List of paths for different provision objects from the reference provision + :param paths: List of paths for different provision objects from the upgraded provision + :param creds: Credential for the authentification + :param session: Session for connexion + :param basedn: DN of the partition to update + :param names: List of key provision parameters""" newsam_ldb = Ldb(newpaths.samdb, session_info=session, credentials=creds,lp=lp) sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp) reference = newsam_ldb.search(expression="objectClass=*",base=str(names.rootdn), scope=SCOPE_SUBTREE,attrs=["dn","nTSecurityDescriptor"],controls=["search_options:1:2"]) @@ -591,10 +708,18 @@ def check_updated_sd(newpaths, paths, creds, session, names): print "%s new sddl/sddl in ref"%key print "%s\n%s"%(sddl,hash_new[key]) -# Simple update method for updating the SD that rely on the fact that nobody -# should have modified the SD -# This assumption is safe right now (alpha9) but should be removed asap def update_sd(paths, creds, session, names): + """Update security descriptor of the current provision + + During the different pre release of samba4 security descriptors (SD) were notarly broken (up to alpha11 included) + This function allow to get them back in order, this function make the assumption that nobody has modified manualy an SD + and so SD can be safely recalculated from scratch to get them right. + + :param paths: List of paths for different provision objects from the upgraded provision + :param creds: Credential for the authentification + :param session: Session for connexion + :param names: List of key provision parameters""" + sam_ldb = Ldb(paths.samdb, session_info=session, credentials=creds,lp=lp,options=["modules:samba_dsdb"]) sam_ldb.transaction_start() # First update the SD for the rootdn @@ -649,6 +774,12 @@ def update_sd(paths, creds, session, names): def update_basesamdb(newpaths, paths, names): + """Update the provision container db: sam.ldb + + :param newpaths: List of paths for different provision objects from the reference provision + :param paths: List of paths for different provision objects from the upgraded provision + :param names: List of key provision parameters""" + message(SIMPLE,"Copy samdb") shutil.copy(newpaths.samdb,paths.samdb) @@ -672,11 +803,22 @@ def update_basesamdb(newpaths, paths, names): os.remove(configldb) def update_privilege(newpaths, paths): + """Update the privilege database + + :param newpaths: List of paths for different provision objects from the reference provision + :param paths: List of paths for different provision objects from the upgraded provision""" message(SIMPLE,"Copy privilege") shutil.copy(os.path.join(newpaths.private_dir,"privilege.ldb"),os.path.join(paths.private_dir,"privilege.ldb")) # For each partition check the differences def update_samdb(newpaths, paths, creds, session, names): + """Upgrade the SAM DB contents for all the provision + + :param newpaths: List of paths for different provision objects from the reference provision + :param paths: List of paths for different provision objects from the upgraded provision + :param creds: Credential for the authentification + :param session: Session for connexion + :param names: List of key provision parameters""" message(SIMPLE, "Doing schema update") hashdef = check_diff_name(newpaths,paths,creds,session,str(names.schemadn),names,1) @@ -686,6 +828,13 @@ def update_samdb(newpaths, paths, creds, session, names): message(SIMPLE,"Done with scanning") def update_machine_account_password(paths, creds, session, names): + """Update (change) the password of the current DC both in the SAM db and in secret one + + :param paths: List of paths for different provision objects from the upgraded provision + :param creds: Credential for the authentification + :param session: Session for connexion + :param names: List of key provision parameters""" + secrets_ldb = Ldb(paths.secrets, session_info=session, credentials=creds,lp=lp) secrets_ldb.transaction_start() secrets_msg = secrets_ldb.search(expression=("samAccountName=%s$" % names.netbiosname), attrs=["secureChannelType"]) diff --git a/source4/scripting/python/samba/upgradehelpers.py b/source4/scripting/python/samba/upgradehelpers.py index f4560601b4..190a8f76c9 100755 --- a/source4/scripting/python/samba/upgradehelpers.py +++ b/source4/scripting/python/samba/upgradehelpers.py @@ -26,7 +26,6 @@ import os import sys import string import re -# Find right directory when running from source tree import samba from samba import Ldb, DS_DOMAIN_FUNCTION_2000 @@ -37,8 +36,13 @@ from samba.provisionexceptions import ProvisioningError from samba.dcerpc import misc, security from samba.ndr import ndr_pack, ndr_unpack -# Get Paths for important objects (ldb, keytabs ...) def get_paths(param,targetdir=None,smbconf=None): + """Get paths to important provision objects (smb.conf, ldb files, ...) + + :param param: Param object + :param targetdir: Directory where the provision is (or will be) stored + :param smbconf: Path to the smb.conf file + :return: A list with the path of important provision objects""" if targetdir is not None: if (not os.path.exists(os.path.join(targetdir, "etc"))): os.makedirs(os.path.join(targetdir, "etc")) @@ -55,10 +59,16 @@ def get_paths(param,targetdir=None,smbconf=None): return paths -# This function guesses (fetches) informations needed to make a fresh provision -# from the current provision -# It includes: realm, workgroup, partitions, netbiosname, domain guid, ... def find_provision_key_parameters(param,credentials,session_info,paths,smbconf): + """Get key provision parameters (realm, domain, ...) from a given provision + + :param param: Param object + :param credentials: Credentials for the authentification + :param session_info: Session object + :param paths: A list of path to provision object + :param smbconf: Path to the smb.conf file + :return: A list of key provision parameters""" + lp = param.LoadParm() lp.load(paths.smbconf) names = ProvisionNames() @@ -145,6 +155,15 @@ def find_provision_key_parameters(param,credentials,session_info,paths,smbconf): # This provision will be the reference for knowing what has changed in the # since the latest upgrade in the current provision def newprovision(names,setup_dir,creds,session,smbconf,provdir,messagefunc): + """Create a new provision + + :param names: List of provision parameters + :param setup_dis: Directory where the setup files are stored + :param creds: Credentials for the authentification + :param session: Session object + :param smbconf: Path to the smb.conf file + :param provdir: Directory where the provision will be stored + :param messagefunc: A function for displaying the message of the provision""" if os.path.isdir(provdir): rmall(provdir) logstd=os.path.join(provdir,"log.std") @@ -176,11 +195,13 @@ def newprovision(names,setup_dir,creds,session,smbconf,provdir,messagefunc): dom_for_fun_level=names.domainlevel, ldap_dryrun_mode=None,useeadb=True) -# This function sorts two DNs in the lexicographical order and put higher level -# DN before. -# So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller -# (-1) as it has less level def dn_sort(x,y): + """Sorts two DNs in the lexicographical order it and put higher level DN before. + + So given the dns cn=bar,cn=foo and cn=foo the later will be return as smaller + :param x: First object to compare + :param y: Second object to compare + """ p = re.compile(r'(?<!\\),') tab1 = p.split(str(x)) tab2 = p.split(str(y)) @@ -210,6 +231,9 @@ def dn_sort(x,y): def rmall(topdir): + """Remove a directory its contents and all its subdirectory + + :param topdir: Directory to remove""" for root, dirs, files in os.walk(topdir, topdown=False): for name in files: os.remove(os.path.join(root, name)) |