class CredentialsOptionsDouble(CredentialsOptions): """Command line options for specifying credentials of two servers.""" def __init__(self, parser): CredentialsOptions.__init__(self, parser) self.no_pass2 = True self.add_option("--simple-bind-dn2", metavar="DN2", action="callback", callback=self._set_simple_bind_dn2, type=str, help="DN to use for a simple bind") self.add_option("--password2", metavar="PASSWORD2", action="callback", help="Password", type=str, callback=self._set_password2) self.add_option("--username2", metavar="USERNAME2", action="callback", type=str, help="Username for second server", callback=self._parse_username2) self.add_option("--workgroup2", metavar="WORKGROUP2", action="callback", type=str, help="Workgroup for second server", callback=self._parse_workgroup2) self.add_option("--no-pass2", action="store_true", help="Don't ask for a password for the second server") self.add_option("--kerberos2", metavar="KERBEROS2", action="callback", type=str, help="Use Kerberos", callback=self._set_kerberos2) self.creds2 = Credentials() def _parse_username2(self, option, opt_str, arg, parser): self.creds2.parse_string(arg) def _parse_workgroup2(self, option, opt_str, arg, parser): self.creds2.set_domain(arg) def _set_password2(self, option, opt_str, arg, parser): self.creds2.set_password(arg) self.no_pass2 = False def _set_kerberos2(self, option, opt_str, arg, parser): if bool(arg) or arg.lower() == "yes": self.creds2.set_kerberos_state(MUST_USE_KERBEROS) else: self.creds2.set_kerberos_state(DONT_USE_KERBEROS) def _set_simple_bind_dn2(self, option, opt_str, arg, parser): self.creds2.set_bind_dn(arg) def get_credentials2(self, lp, guess=True): """Obtain the credentials set on the command-line. :param lp: Loadparm object to use. :param guess: Try guess Credentials from environment :return: Credentials object """ if guess: self.creds2.guess(lp) elif not self.creds2.get_username(): self.creds2.set_anonymous() if self.no_pass2: self.creds2.set_cmdline_callbacks() return self.creds2
def insta_creds(self, template=None, username=None, userpass=None, kerberos_state=None): if template is None: assert template is not None if username is not None: assert userpass is not None if username is None: assert userpass is None username = template.get_username() userpass = template.get_password() if kerberos_state is None: kerberos_state = template.get_kerberos_state() # get a copy of the global creds or a the passed in creds c = Credentials() c.set_username(username) c.set_password(userpass) c.set_domain(template.get_domain()) c.set_realm(template.get_realm()) c.set_workstation(template.get_workstation()) c.set_gensec_features(c.get_gensec_features() | gensec.FEATURE_SEAL) c.set_kerberos_state(kerberos_state) return c
def _get_smb_connection(self, service='SysVol'): # Connect to SMB using kerberos parm = param.LoadParm() creds = Credentials() creds.set_kerberos_state(MUST_USE_KERBEROS) creds.guess(parm) conn = smb.SMB(self._get_server_name(), service, lp=parm, creds=creds) return conn
def join_replicate(ctx): '''replicate the SAM''' print "Starting replication" ctx.local_samdb.transaction_start() try: source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id()) if ctx.ntds_guid is None: print("Using DS_BIND_GUID_W2K3") destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3) else: destination_dsa_guid = ctx.ntds_guid if ctx.RODC: repl_creds = Credentials() repl_creds.guess(ctx.lp) repl_creds.set_kerberos_state(DONT_USE_KERBEROS) repl_creds.set_username(ctx.samname) repl_creds.set_password(ctx.acct_pass) else: repl_creds = ctx.creds binding_options = "seal" if int(ctx.lp.get("log level")) >= 5: binding_options += ",print" repl = drs_utils.drs_Replicate( "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options), ctx.lp, repl_creds, ctx.local_samdb) repl.replicate(ctx.schema_dn, source_dsa_invocation_id, destination_dsa_guid, schema=True, rodc=ctx.RODC, replica_flags=ctx.replica_flags) repl.replicate(ctx.config_dn, source_dsa_invocation_id, destination_dsa_guid, rodc=ctx.RODC, replica_flags=ctx.replica_flags) if not ctx.subdomain: repl.replicate(ctx.base_dn, source_dsa_invocation_id, destination_dsa_guid, rodc=ctx.RODC, replica_flags=ctx.domain_replica_flags) if ctx.RODC: repl.replicate(ctx.acct_dn, source_dsa_invocation_id, destination_dsa_guid, exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True) repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id, destination_dsa_guid, exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True) ctx.repl = repl ctx.source_dsa_invocation_id = source_dsa_invocation_id ctx.destination_dsa_guid = destination_dsa_guid print "Committing SAM database" except: ctx.local_samdb.transaction_cancel() raise else: ctx.local_samdb.transaction_commit()
class NtlmDisabledTests(TestCase): def setUp(self): super(NtlmDisabledTests, self).setUp() self.lp = self.get_loadparm() self.server = os.getenv("SERVER") self.creds = Credentials() self.creds.guess(self.lp) self.creds.set_username(os.getenv("USERNAME")) self.creds.set_domain(self.server) self.creds.set_password(os.getenv("PASSWORD")) self.creds.set_kerberos_state(DONT_USE_KERBEROS) def tearDown(self): super(NtlmDisabledTests, self).tearDown() def test_ntlm_connection(self): try: conn = srvsvc.srvsvc("ncacn_np:%s[smb2,ntlm]" % self.server, self.lp, self.creds) self.assertIsNotNone(conn) except NTSTATUSError as e: # NTLM might be blocked on this server enum = ctypes.c_uint32(e[0]).value if enum == ntstatus.NT_STATUS_NTLM_BLOCKED: self.fail("NTLM is disabled on this server") else: raise def test_samr_change_password(self): self.creds.set_kerberos_state(MUST_USE_KERBEROS) conn = samr.samr("ncacn_np:%s[krb5,seal,smb2]" % os.getenv("SERVER")) # we want to check whether this gets rejected outright because NTLM is # disabled, so we don't actually need to encrypt a valid password here server = lsa.String() server.string = self.server username = lsa.String() username.string = os.getenv("USERNAME") try: conn.ChangePasswordUser2(server, username, None, None, True, None, None) except NTSTATUSError as e: # changing passwords should be rejected when NTLM is disabled enum = ctypes.c_uint32(e[0]).value if enum == ntstatus.NT_STATUS_NTLM_BLOCKED: self.fail("NTLM is disabled on this server") elif enum == ntstatus.NT_STATUS_WRONG_PASSWORD: # expected error case when NTLM is enabled pass else: raise
def get_creds(self, target_username, target_password): creds_tmp = Credentials() creds_tmp.set_username(target_username) creds_tmp.set_password(target_password) creds_tmp.set_domain(creds.get_domain()) creds_tmp.set_realm(creds.get_realm()) creds_tmp.set_workstation(creds.get_workstation()) creds_tmp.set_gensec_features(creds_tmp.get_gensec_features() | gensec.FEATURE_SEAL) creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop return creds_tmp
def get_ldb_connection(self, target_username, target_password): creds_tmp = Credentials() creds_tmp.set_username(target_username) creds_tmp.set_password(target_password) creds_tmp.set_domain(creds.get_domain()) creds_tmp.set_realm(creds.get_realm()) creds_tmp.set_workstation(creds.get_workstation()) creds_tmp.set_gensec_features(creds_tmp.get_gensec_features() | gensec.FEATURE_SEAL) creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop ldb_target = SamDB(url=ldaphost, credentials=creds_tmp, lp=lp) return ldb_target
def get_user_and_ldb(self, username, password, hostname=ldaphost): """Get a connection for a temporarily user that will vanish as soon as the test is over.""" user = self.ldb.newuser(username, password) creds_tmp = Credentials() creds_tmp.set_username(username) creds_tmp.set_password(password) creds_tmp.set_domain(creds.get_domain()) creds_tmp.set_realm(creds.get_realm()) creds_tmp.set_workstation(creds.get_workstation()) creds_tmp.set_gensec_features(creds_tmp.get_gensec_features() | gensec.FEATURE_SEAL) creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) ldb_target = SamDB(url=hostname, credentials=creds_tmp, lp=lp) self.addCleanup(delete_force, self.ldb, self.get_user_dn(username)) return (user, ldb_target)
def netlogon(self): server = os.environ["SERVER"] host = os.environ["SERVER_IP"] lp = self.get_loadparm() credentials = self.get_credentials() session = system_session() ldb = SamDB(url="ldap://%s" % host, session_info=session, credentials=credentials, lp=lp) machine_pass = samba.generate_random_password(32, 32) machine_name = MACHINE_NAME machine_dn = "cn=%s,%s" % (machine_name, ldb.domain_dn()) delete_force(ldb, machine_dn) utf16pw = ('"%s"' % get_string(machine_pass)).encode('utf-16-le') ldb.add({ "dn": machine_dn, "objectclass": "computer", "sAMAccountName": "%s$" % machine_name, "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD), "unicodePwd": utf16pw }) machine_creds = Credentials() machine_creds.guess(lp) machine_creds.set_secure_channel_type(SEC_CHAN_WKSTA) machine_creds.set_kerberos_state(DONT_USE_KERBEROS) machine_creds.set_password(machine_pass) machine_creds.set_username(machine_name + "$") machine_creds.set_workstation(machine_name) netlogon.netlogon("ncacn_ip_tcp:%s[schannel,seal]" % server, lp, machine_creds) delete_force(ldb, machine_dn)
def _get_smb_connection(self, service='SysVol'): # Create options like if we were using command line parser = optparse.OptionParser() sambaopts = options.SambaOptions(parser) # Samba options parm = sambaopts.get_loadparm() s3_lp = s3param.get_context() s3_lp.load(parm.configfile) # Build credentials from credential options creds = Credentials() # Credentials need username and realm to be not empty strings to work creds.set_username('NOTEMPTY') creds.set_realm('NOTEMPTY') # Connect to SMB using kerberos creds.set_kerberos_state(MUST_USE_KERBEROS) # Create connection conn = libsmb.Conn(self._get_server_name(), service, lp=parm, creds=creds, sign=False) return conn
def make_creds(username, password, kerberos_state=None): # use the global CREDS as a template c = Credentials() c.set_username(username) c.set_password(password) c.set_domain(CREDS.get_domain()) c.set_realm(CREDS.get_realm()) c.set_workstation(CREDS.get_workstation()) if kerberos_state is None: kerberos_state = CREDS.get_kerberos_state() c.set_kerberos_state(kerberos_state) print '-' * 73 if kerberos_state == MUST_USE_KERBEROS: print "we seem to be using kerberos for %s %s" % (username, password) elif kerberos_state == DONT_USE_KERBEROS: print "NOT using kerberos for %s %s" % (username, password) else: print "kerberos state is %s" % kerberos_state c.set_gensec_features(c.get_gensec_features() | gensec.FEATURE_SEAL) return c
class ProvisionModel(BaseModel): def __init__(self,username,password): BaseModel.__init__(self,username,password); #if self.isAuthenticate(): self.opts = Options(); #self.creds = self.opts.get_credentials(self.opts.lp) self.creds = Credentials() self.creds.guess(self.opts.lp) self.creds.set_kerberos_state(DONT_USE_KERBEROS) self.session = system_session() def SetupDomain(self): #dom_for_fun_level result = True if self.opts.function_level is None: self.opts.dom_for_fun_level = DS_DOMAIN_FUNCTION_2003 #default elif self.opts.function_level == "2000": self.opts.dom_for_fun_level = DS_DOMAIN_FUNCTION_2000 elif self.opts.function_level == "2003": self.opts.dom_for_fun_level = DS_DOMAIN_FUNCTION_2003 elif self.opts.function_level == "2008": self.opts.dom_for_fun_level = DS_DOMAIN_FUNCTION_2008 elif self.opts.function_level == "2008_R2": self.opts.dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2 if self.opts.blank: self.opts.samdb_fill = FILL_NT4SYNC elif self.opts.partitions_only: self.opts.samdb_fill = FILL_DRS self.opts.eadb = True if self.opts.use_xattrs == "yes": self.opts.eadb = False elif self.opts.use_xattrs == "auto" and not self.opts.lp.get("posix:eadb"): file = tempfile.NamedTemporaryFile() try: samba.ntacls.setntacl(self.opts.lp, file.name, "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native") self.opts.eadb = False except: logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. " "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.") file.close() #logger.addHandler(logging.StreamHandler(sys.stdout)) #if opts.quiet: # logger.setLevel(logging.WARNING) #else: # logger.setLevel(logging.INFO) # buffer debug messages so they can be sent with error emails #memory_handler = logging.handlers.MemoryHandler(1024*10) #memory_handler.setLevel(logging.DEBUG) # attach handlers msg = "data: start provision\n\n" response.write(msg); self.logger = logging.getLogger(__name__) LogOutput = ListBufferingHandler(1000); self.logger.addHandler(LogOutput) self.logger.setLevel(logging.WARNING) self.logger.setLevel(logging.INFO) try: provision(self.logger ,self.session ,self.creds , smbconf=self.opts.smbconf # ,targetdir=self.opts.targetdir ,samdb_fill=self.opts.samdb_fill ,realm=self.opts.realm ,domain=self.opts.domain, # domainguid=self.opts.domain_guid, domainsid=self.opts.domain_sid, # hostname=self.opts.host_name, # hostip=self.opts.host_ip, hostip6=self.opts.host_ip6, # ntdsguid=self.opts.ntds_guid, # invocationid=self.opts.invocationid, adminpass=self.opts.adminpass, # krbtgtpass=self.opts.krbtgtpass, machinepass=self.opts.machinepass, # dns_backend=self.opts.dns_backend, # dnspass=self.opts.dnspass, root=self.opts.root, nobody=self.opts.nobody, # wheel=self.opts.wheel, users=self.opts.users, serverrole=self.opts.server_role, dom_for_fun_level=self.opts.dom_for_fun_level, # ldap_backend_extra_port=self.opts.ldap_backend_extra_port, # ldap_backend_forced_uri=self.opts.ldap_backend_forced_uri, # backend_type=self.opts.ldap_backend_type, # ldapadminpass=self.opts.ldapadminpass, ol_mmr_urls=self.opts.ol_mmr_urls, # slapd_path=self.opts.slapd_path, setup_ds_path=self.opts.setup_ds_path, # nosync=self.opts.ldap_backend_nosync, ldap_dryrun_mode=self.opts.ldap_dryrun_mode, useeadb=self.opts.eadb, # next_rid=self.opts.next_rid, lp=self.opts.lp) #except ProvisioningError, e: except Exception,e: if(len(e.args)>1): self.SetError(e.args[1],e.args[0]) self.logger.warning('Error: %s :%s',e.args[1], e.args[0]) else: self.logger.warning('Error: %s :%s',e.args, 0) self.SetError(e.args,0) result = False; import traceback TraceBackLines = traceback.format_exc().splitlines() for t in TraceBackLines: self.logger.warning('Error: %s',t) LogOutput.flush(); msg = "data: end provision\n\n" response.write(msg); return result;
class CredentialsOptions(optparse.OptionGroup): """Command line options for specifying credentials.""" def __init__(self, parser, special_name=None): self.special_name = special_name if special_name is not None: self.section = "Credentials Options (%s)" % special_name else: self.section = "Credentials Options" self.ask_for_password = True self.ipaddress = None self.machine_pass = False optparse.OptionGroup.__init__(self, parser, self.section) self._add_option("--simple-bind-dn", metavar="DN", action="callback", callback=self._set_simple_bind_dn, type=str, help="DN to use for a simple bind") self._add_option("--password", metavar="PASSWORD", action="callback", help="Password", type=str, callback=self._set_password) self._add_option("-U", "--username", metavar="USERNAME", action="callback", type=str, help="Username", callback=self._parse_username) self._add_option("-W", "--workgroup", metavar="WORKGROUP", action="callback", type=str, help="Workgroup", callback=self._parse_workgroup) self._add_option("-N", "--no-pass", action="callback", help="Don't ask for a password", callback=self._set_no_password) self._add_option("-k", "--kerberos", metavar="KERBEROS", action="callback", type=str, help="Use Kerberos", callback=self._set_kerberos) self._add_option("", "--ipaddress", metavar="IPADDRESS", action="callback", type=str, help="IP address of server", callback=self._set_ipaddress) self._add_option("-P", "--machine-pass", action="callback", help="Use stored machine account password", callback=self._set_machine_pass) self._add_option("--krb5-ccache", metavar="KRB5CCNAME", action="callback", type=str, help="Kerberos Credentials cache", callback=self._set_krb5_ccache) self.creds = Credentials() def _add_option(self, *args1, **kwargs): if self.special_name is None: return self.add_option(*args1, **kwargs) args2 = () for a in args1: if not a.startswith("--"): continue args2 += (a.replace("--", "--%s-" % self.special_name),) self.add_option(*args2, **kwargs) def _parse_username(self, option, opt_str, arg, parser): self.creds.parse_string(arg) self.machine_pass = False def _parse_workgroup(self, option, opt_str, arg, parser): self.creds.set_domain(arg) def _set_password(self, option, opt_str, arg, parser): self.creds.set_password(arg) self.ask_for_password = False self.machine_pass = False def _set_no_password(self, option, opt_str, arg, parser): self.ask_for_password = False def _set_machine_pass(self, option, opt_str, arg, parser): self.machine_pass = True def _set_ipaddress(self, option, opt_str, arg, parser): self.ipaddress = arg def _set_kerberos(self, option, opt_str, arg, parser): self.creds.set_kerberos_state(parse_kerberos_arg(arg, opt_str)) def _set_simple_bind_dn(self, option, opt_str, arg, parser): self.creds.set_bind_dn(arg) def _set_krb5_ccache(self, option, opt_str, arg, parser): self.creds.set_named_ccache(arg) def get_credentials(self, lp, fallback_machine=False): """Obtain the credentials set on the command-line. :param lp: Loadparm object to use. :return: Credentials object """ self.creds.guess(lp) if self.machine_pass: self.creds.set_machine_account(lp) elif self.ask_for_password: self.creds.set_cmdline_callbacks() # possibly fallback to using the machine account, if we have # access to the secrets db if fallback_machine and not self.creds.authentication_requested(): try: self.creds.set_machine_account(lp) except Exception: pass return self.creds
class PyCredentialsTests(TestCase): def setUp(self): super(PyCredentialsTests, self).setUp() self.server = os.environ["SERVER"] self.domain = os.environ["DOMAIN"] self.host = os.environ["SERVER_IP"] self.lp = self.get_loadparm() self.credentials = self.get_credentials() self.session = system_session() self.ldb = SamDB(url="ldap://%s" % self.host, session_info=self.session, credentials=self.credentials, lp=self.lp) self.create_machine_account() self.create_user_account() def tearDown(self): super(PyCredentialsTests, self).tearDown() delete_force(self.ldb, self.machine_dn) delete_force(self.ldb, self.user_dn) # Until a successful netlogon connection has been established there will # not be a valid authenticator associated with the credentials # and new_client_authenticator should throw a ValueError def test_no_netlogon_connection(self): self.assertRaises(ValueError, self.machine_creds.new_client_authenticator) # Once a netlogon connection has been established, # new_client_authenticator should return a value # def test_have_netlogon_connection(self): c = self.get_netlogon_connection() a = self.machine_creds.new_client_authenticator() self.assertIsNotNone(a) # Get an authenticator and use it on a sequence of operations requiring # an authenticator def test_client_authenticator(self): c = self.get_netlogon_connection() (authenticator, subsequent) = self.get_authenticator(c) self.do_NetrLogonSamLogonWithFlags(c, authenticator, subsequent) (authenticator, subsequent) = self.get_authenticator(c) self.do_NetrLogonGetDomainInfo(c, authenticator, subsequent) (authenticator, subsequent) = self.get_authenticator(c) self.do_NetrLogonGetDomainInfo(c, authenticator, subsequent) (authenticator, subsequent) = self.get_authenticator(c) self.do_NetrLogonGetDomainInfo(c, authenticator, subsequent) def test_SamLogonEx(self): c = self.get_netlogon_connection() logon = samlogon_logon_info(self.domain, self.machine_name, self.user_creds) logon_level = netlogon.NetlogonNetworkTransitiveInformation validation_level = netlogon.NetlogonValidationSamInfo4 netr_flags = 0 try: c.netr_LogonSamLogonEx(self.server, self.user_creds.get_workstation(), logon_level, logon, validation_level, netr_flags) except NTSTATUSError as e: enum = ctypes.c_uint32(e.args[0]).value if enum == ntstatus.NT_STATUS_WRONG_PASSWORD: self.fail("got wrong password error") else: raise def test_SamLogonEx_no_domain(self): c = self.get_netlogon_connection() self.user_creds.set_domain('') logon = samlogon_logon_info(self.domain, self.machine_name, self.user_creds) logon_level = netlogon.NetlogonNetworkTransitiveInformation validation_level = netlogon.NetlogonValidationSamInfo4 netr_flags = 0 try: c.netr_LogonSamLogonEx(self.server, self.user_creds.get_workstation(), logon_level, logon, validation_level, netr_flags) except NTSTATUSError as e: enum = ctypes.c_uint32(e.args[0]).value if enum == ntstatus.NT_STATUS_WRONG_PASSWORD: self.fail("got wrong password error") else: self.fail("got unexpected error" + str(e)) def test_SamLogonExNTLM(self): c = self.get_netlogon_connection() logon = samlogon_logon_info(self.domain, self.machine_name, self.user_creds, flags=CLI_CRED_NTLM_AUTH) logon_level = netlogon.NetlogonNetworkTransitiveInformation validation_level = netlogon.NetlogonValidationSamInfo4 netr_flags = 0 try: c.netr_LogonSamLogonEx(self.server, self.user_creds.get_workstation(), logon_level, logon, validation_level, netr_flags) except NTSTATUSError as e: enum = ctypes.c_uint32(e.args[0]).value if enum == ntstatus.NT_STATUS_WRONG_PASSWORD: self.fail("got wrong password error") else: raise def test_SamLogonExMSCHAPv2(self): c = self.get_netlogon_connection() logon = samlogon_logon_info(self.domain, self.machine_name, self.user_creds, flags=CLI_CRED_NTLM_AUTH) logon.identity_info.parameter_control = MSV1_0_ALLOW_MSVCHAPV2 logon_level = netlogon.NetlogonNetworkTransitiveInformation validation_level = netlogon.NetlogonValidationSamInfo4 netr_flags = 0 try: c.netr_LogonSamLogonEx(self.server, self.user_creds.get_workstation(), logon_level, logon, validation_level, netr_flags) except NTSTATUSError as e: enum = ctypes.c_uint32(e.args[0]).value if enum == ntstatus.NT_STATUS_WRONG_PASSWORD: self.fail("got wrong password error") else: raise # Test Credentials.encrypt_netr_crypt_password # By performing a NetrServerPasswordSet2 # And the logging on using the new password. def test_encrypt_netr_password(self): # Change the password self.do_Netr_ServerPasswordSet2() # Now use the new password to perform an operation srvsvc.srvsvc("ncacn_np:%s" % (self.server), self.lp, self.machine_creds) # Change the current machine account password with a # netr_ServerPasswordSet2 call. def do_Netr_ServerPasswordSet2(self): c = self.get_netlogon_connection() (authenticator, subsequent) = self.get_authenticator(c) PWD_LEN = 32 DATA_LEN = 512 newpass = samba.generate_random_password(PWD_LEN, PWD_LEN) encoded = newpass.encode('utf-16-le') pwd_len = len(encoded) filler = [x if isinstance(x, int) else ord(x) for x in os.urandom(DATA_LEN - pwd_len)] pwd = netlogon.netr_CryptPassword() pwd.length = pwd_len pwd.data = filler + [x if isinstance(x, int) else ord(x) for x in encoded] self.machine_creds.encrypt_netr_crypt_password(pwd) c.netr_ServerPasswordSet2(self.server, self.machine_creds.get_workstation(), SEC_CHAN_WKSTA, self.machine_name, authenticator, pwd) self.machine_pass = newpass self.machine_creds.set_password(newpass) # Establish sealed schannel netlogon connection over TCP/IP # def get_netlogon_connection(self): return netlogon.netlogon("ncacn_ip_tcp:%s[schannel,seal]" % self.server, self.lp, self.machine_creds) # # Create the machine account def create_machine_account(self): self.machine_pass = samba.generate_random_password(32, 32) self.machine_name = MACHINE_NAME self.machine_dn = "cn=%s,%s" % (self.machine_name, self.ldb.domain_dn()) # remove the account if it exists, this will happen if a previous test # run failed delete_force(self.ldb, self.machine_dn) utf16pw = ('"%s"' % get_string(self.machine_pass)).encode('utf-16-le') self.ldb.add({ "dn": self.machine_dn, "objectclass": "computer", "sAMAccountName": "%s$" % self.machine_name, "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD), "unicodePwd": utf16pw}) self.machine_creds = Credentials() self.machine_creds.guess(self.get_loadparm()) self.machine_creds.set_secure_channel_type(SEC_CHAN_WKSTA) self.machine_creds.set_kerberos_state(DONT_USE_KERBEROS) self.machine_creds.set_password(self.machine_pass) self.machine_creds.set_username(self.machine_name + "$") self.machine_creds.set_workstation(self.machine_name) # # Create a test user account def create_user_account(self): self.user_pass = samba.generate_random_password(32, 32) self.user_name = USER_NAME self.user_dn = "cn=%s,%s" % (self.user_name, self.ldb.domain_dn()) # remove the account if it exists, this will happen if a previous test # run failed delete_force(self.ldb, self.user_dn) utf16pw = ('"%s"' % get_string(self.user_pass)).encode('utf-16-le') self.ldb.add({ "dn": self.user_dn, "objectclass": "user", "sAMAccountName": "%s" % self.user_name, "userAccountControl": str(UF_NORMAL_ACCOUNT), "unicodePwd": utf16pw}) self.user_creds = Credentials() self.user_creds.guess(self.get_loadparm()) self.user_creds.set_password(self.user_pass) self.user_creds.set_username(self.user_name) self.user_creds.set_workstation(self.machine_name) pass # # Get the authenticator from the machine creds. def get_authenticator(self, c): auth = self.machine_creds.new_client_authenticator() current = netr_Authenticator() current.cred.data = [x if isinstance(x, int) else ord(x) for x in auth["credential"]] current.timestamp = auth["timestamp"] subsequent = netr_Authenticator() return (current, subsequent) def do_NetrLogonSamLogonWithFlags(self, c, current, subsequent): logon = samlogon_logon_info(self.domain, self.machine_name, self.user_creds) logon_level = netlogon.NetlogonNetworkTransitiveInformation validation_level = netlogon.NetlogonValidationSamInfo4 netr_flags = 0 c.netr_LogonSamLogonWithFlags(self.server, self.user_creds.get_workstation(), current, subsequent, logon_level, logon, validation_level, netr_flags) def do_NetrLogonGetDomainInfo(self, c, current, subsequent): query = netr_WorkstationInformation() c.netr_LogonGetDomainInfo(self.server, self.user_creds.get_workstation(), current, subsequent, 2, query)
class LDAPBackend(ProvisionBackend): def __init__(self, backend_type, paths=None, lp=None, names=None, logger=None, domainsid=None, schema=None, hostname=None, ldapadminpass=None, slapd_path=None, ldap_backend_extra_port=None, ldap_backend_forced_uri=None, ldap_dryrun_mode=False): super(LDAPBackend, self).__init__(backend_type=backend_type, paths=paths, lp=lp, names=names, logger=logger) self.domainsid = domainsid self.schema = schema self.hostname = hostname self.ldapdir = os.path.join(paths.private_dir, "ldap") self.ldapadminpass = ldapadminpass self.slapd_path = slapd_path self.slapd_command = None self.slapd_command_escaped = None self.slapd_pid = os.path.join(self.ldapdir, "slapd.pid") self.ldap_backend_extra_port = ldap_backend_extra_port self.ldap_dryrun_mode = ldap_dryrun_mode if ldap_backend_forced_uri is not None: self.ldap_uri = ldap_backend_forced_uri else: self.ldap_uri = "ldapi://%s" % urllib_quote( os.path.join(self.ldapdir, "ldapi"), safe="") if not os.path.exists(self.ldapdir): os.mkdir(self.ldapdir) def init(self): from samba.provision import ProvisioningError # we will shortly start slapd with ldapi for final provisioning. first # check with ldapsearch -> rootDSE via self.ldap_uri if another # instance of slapd is already running try: ldapi_db = Ldb(self.ldap_uri) ldapi_db.search(base="", scope=SCOPE_BASE, expression="(objectClass=OpenLDAProotDSE)") try: f = open(self.slapd_pid, "r") except IOError as err: if err != errno.ENOENT: raise else: try: p = f.read() finally: f.close() self.logger.info("Check for slapd process with PID: %s and terminate it manually." % p) raise SlapdAlreadyRunning(self.ldap_uri) except LdbError: # XXX: We should never be catching all Ldb errors pass # Try to print helpful messages when the user has not specified the # path to slapd if self.slapd_path is None: raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!") if not os.path.exists(self.slapd_path): self.logger.warning("Path (%s) to slapd does not exist!", self.slapd_path) if not os.path.isdir(self.ldapdir): os.makedirs(self.ldapdir, 0o700) # Put the LDIF of the schema into a database so we can search on # it to generate schema-dependent configurations in Fedora DS and # OpenLDAP schemadb_path = os.path.join(self.ldapdir, "schema-tmp.ldb") try: os.unlink(schemadb_path) except OSError: pass self.schema.write_to_tmp_ldb(schemadb_path) self.credentials = Credentials() self.credentials.guess(self.lp) # Kerberos to an ldapi:// backend makes no sense (we also force EXTERNAL) self.credentials.set_kerberos_state(DONT_USE_KERBEROS) self.credentials.set_username("samba-admin") self.credentials.set_password(self.ldapadminpass) self.credentials.set_forced_sasl_mech("EXTERNAL") self.provision() def provision(self): pass def start(self): from samba.provision import ProvisioningError self.slapd_command_escaped = "\'" + "\' \'".join(self.slapd_command) + "\'" ldap_backend_script = os.path.join(self.ldapdir, "ldap_backend_startup.sh") f = open(ldap_backend_script, 'w') try: f.write("#!/bin/sh\n" + self.slapd_command_escaped + " $@\n") finally: f.close() os.chmod(ldap_backend_script, 0o755) # Now start the slapd, so we can provision onto it. We keep the # subprocess context around, to kill this off at the successful # end of the script self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False) count = 0 while self.slapd.poll() is None: # Wait until the socket appears try: time.sleep(1) ldapi_db = Ldb(self.ldap_uri, lp=self.lp, credentials=self.credentials) ldapi_db.search(base="", scope=SCOPE_BASE, expression="(objectClass=OpenLDAProotDSE)") # If we have got here, then we must have a valid connection to # the LDAP server! return except LdbError: count = count + 1 if count > 15: self.logger.error("Could not connect to slapd started with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'") raise ProvisioningError("slapd never accepted a connection within 15 seconds of starting") self.logger.error("Could not start slapd with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'") raise ProvisioningError("slapd died before we could make a connection to it") def shutdown(self): # if an LDAP backend is in use, terminate slapd after final provision # and check its proper termination if self.slapd.poll() is None: # Kill the slapd if getattr(self.slapd, "terminate", None) is not None: self.slapd.terminate() else: # Older python versions don't have .terminate() import signal os.kill(self.slapd.pid, signal.SIGTERM) # and now wait for it to die self.slapd.communicate() def post_setup(self): return LDAPBackendResult(self.slapd_command_escaped, self.ldapdir)
class BasePasswordTestCase(PasswordTestCase): def _open_samr_user(self, res): self.assertTrue("objectSid" in res[0]) (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split() self.assertEquals(self.domain_sid, domain_sid) return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid) def _check_attribute(self, res, name, value): if value is None: self.assertTrue(name not in res[0], msg="attr[%s]=%r on dn[%s]" % (name, res[0], res[0].dn)) return if isinstance(value, tuple): (mode, value) = value else: mode = "equal" if mode == "ignore": return if mode == "absent": self.assertFalse(name in res[0], msg="attr[%s] not missing on dn[%s]" % (name, res[0].dn)) return self.assertTrue(name in res[0], msg="attr[%s] missing on dn[%s]" % (name, res[0].dn)) self.assertTrue(len(res[0][name]) == 1, msg="attr[%s]=%r on dn[%s]" % (name, res[0][name], res[0].dn)) print("%s = '%s'" % (name, res[0][name][0])) if mode == "present": return if mode == "equal": v = int(res[0][name][0]) value = int(value) msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n" "(diff %d; actual value is %s than expected)" % (name, v, value, res[0].dn, v - value, ('less' if v < value else 'greater'))) self.assertTrue(v == value, msg) return if mode == "greater": v = int(res[0][name][0]) self.assertTrue(v > int(value), msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" % (name, v, int(value), res[0].dn, v - int(value))) return if mode == "less": v = int(res[0][name][0]) self.assertTrue(v < int(value), msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" % (name, v, int(value), res[0].dn, v - int(value))) return self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode) def _check_account_initial(self, userdn): self._check_account(userdn, badPwdCount=0, badPasswordTime=0, logonCount=0, lastLogon=0, lastLogonTimestamp=("absent", None), userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) def _check_account(self, dn, badPwdCount=None, badPasswordTime=None, logonCount=None, lastLogon=None, lastLogonTimestamp=None, lockoutTime=None, userAccountControl=None, msDSUserAccountControlComputed=None, effective_bad_password_count=None, msg=None, badPwdCountOnly=False): print('-=' * 36) if msg is not None: print("\033[01;32m %s \033[00m\n" % msg) attrs = [ "objectSid", "badPwdCount", "badPasswordTime", "lastLogon", "lastLogonTimestamp", "logonCount", "lockoutTime", "userAccountControl", "msDS-User-Account-Control-Computed" ] # in order to prevent some time resolution problems we sleep for # 10 micro second time.sleep(0.01) res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs) self.assertTrue(len(res) == 1) self._check_attribute(res, "badPwdCount", badPwdCount) self._check_attribute(res, "lockoutTime", lockoutTime) self._check_attribute(res, "badPasswordTime", badPasswordTime) if not badPwdCountOnly: self._check_attribute(res, "logonCount", logonCount) self._check_attribute(res, "lastLogon", lastLogon) self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp) self._check_attribute(res, "userAccountControl", userAccountControl) self._check_attribute(res, "msDS-User-Account-Control-Computed", msDSUserAccountControlComputed) lastLogon = int(res[0]["lastLogon"][0]) logonCount = int(res[0]["logonCount"][0]) samr_user = self._open_samr_user(res) uinfo3 = self.samr.QueryUserInfo(samr_user, 3) uinfo5 = self.samr.QueryUserInfo(samr_user, 5) uinfo16 = self.samr.QueryUserInfo(samr_user, 16) uinfo21 = self.samr.QueryUserInfo(samr_user, 21) self.samr.Close(samr_user) expected_acb_info = 0 if not badPwdCountOnly: if userAccountControl & dsdb.UF_NORMAL_ACCOUNT: expected_acb_info |= samr.ACB_NORMAL if userAccountControl & dsdb.UF_ACCOUNTDISABLE: expected_acb_info |= samr.ACB_DISABLED if userAccountControl & dsdb.UF_PASSWD_NOTREQD: expected_acb_info |= samr.ACB_PWNOTREQ if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT: expected_acb_info |= samr.ACB_AUTOLOCK if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED: expected_acb_info |= samr.ACB_PW_EXPIRED self.assertEquals(uinfo3.acct_flags, expected_acb_info) self.assertEquals(uinfo3.last_logon, lastLogon) self.assertEquals(uinfo3.logon_count, logonCount) expected_bad_password_count = 0 if badPwdCount is not None: expected_bad_password_count = badPwdCount if effective_bad_password_count is None: effective_bad_password_count = expected_bad_password_count self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count) if not badPwdCountOnly: self.assertEquals(uinfo5.acct_flags, expected_acb_info) self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count) self.assertEquals(uinfo5.last_logon, lastLogon) self.assertEquals(uinfo5.logon_count, logonCount) self.assertEquals(uinfo16.acct_flags, expected_acb_info) self.assertEquals(uinfo21.acct_flags, expected_acb_info) self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count) self.assertEquals(uinfo21.last_logon, lastLogon) self.assertEquals(uinfo21.logon_count, logonCount) # check LDAP again and make sure the samr.QueryUserInfo # doesn't have any impact. res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs) self.assertEquals(res[0], res2[0]) # in order to prevent some time resolution problems we sleep for # 10 micro second time.sleep(0.01) return res def update_lockout_settings(self, threshold, duration, observation_window): """Updates the global user lockout settings""" m = Message() m.dn = Dn(self.ldb, self.base_dn) account_lockout_duration_ticks = -int(duration * (1e7)) m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks), FLAG_MOD_REPLACE, "lockoutDuration") m["lockoutThreshold"] = MessageElement(str(threshold), FLAG_MOD_REPLACE, "lockoutThreshold") lockout_observation_window_ticks = -int(observation_window * (1e7)) m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks), FLAG_MOD_REPLACE, "lockOutObservationWindow") self.ldb.modify(m) def _readd_user(self, creds, lockOutObservationWindow=0): username = creds.get_username() userpass = creds.get_password() userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) delete_force(self.ldb, userdn) self.ldb.add({ "dn": userdn, "objectclass": "user", "sAMAccountName": username}) self.addCleanup(delete_force, self.ldb, userdn) # Sets the initial user password with a "special" password change # I think that this internally is a password set operation and it can # only be performed by someone which has password set privileges on the # account (at least in s4 we do handle it like that). self.ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify delete: userPassword add: userPassword userPassword: """ + userpass + """ """) # Enables the user account self.ldb.enable_account("(sAMAccountName=%s)" % username) use_kerberos = creds.get_kerberos_state() fail_creds = self.insta_creds(self.template_creds, username=username, userpass=userpass+"X", kerberos_state=use_kerberos) self._check_account_initial(userdn) # Fail once to get a badPasswordTime try: ldb = SamDB(url=self.host_url, credentials=fail_creds, lp=self.lp) self.fail() except LdbError as e: (num, msg) = e.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) # Succeed to reset everything to 0 ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp) return ldb def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS): try: ldb = SamDB(url=url, credentials=creds, lp=lp) self.fail("Login unexpectedly succeeded") except LdbError as e1: (num, msg) = e1.args if errno is not None: self.assertEquals(num, errno, ("Login failed in the wrong way" "(got err %d, expected %d)" % (num, errno))) def setUp(self): super(BasePasswordTestCase, self).setUp() self.global_creds.set_gensec_features(self.global_creds.get_gensec_features() | gensec.FEATURE_SEAL) self.template_creds = Credentials() self.template_creds.set_username("testuser") self.template_creds.set_password("thatsAcomplPASS1") self.template_creds.set_domain(self.global_creds.get_domain()) self.template_creds.set_realm(self.global_creds.get_realm()) self.template_creds.set_workstation(self.global_creds.get_workstation()) self.template_creds.set_gensec_features(self.global_creds.get_gensec_features()) self.template_creds.set_kerberos_state(self.global_creds.get_kerberos_state()) # Gets back the basedn base_dn = self.ldb.domain_dn() # Gets back the configuration basedn configuration_dn = self.ldb.get_config_basedn().get_linearized() res = self.ldb.search(base_dn, scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"]) if "lockoutDuration" in res[0]: lockoutDuration = res[0]["lockoutDuration"][0] else: lockoutDuration = 0 if "lockoutObservationWindow" in res[0]: lockoutObservationWindow = res[0]["lockoutObservationWindow"][0] else: lockoutObservationWindow = 0 if "lockoutThreshold" in res[0]: lockoutThreshold = res[0]["lockoutThreshold"][0] else: lockoutTreshold = 0 self.addCleanup(self.ldb.modify_ldif, """ dn: """ + base_dn + """ changetype: modify replace: lockoutDuration lockoutDuration: """ + str(lockoutDuration) + """ replace: lockoutObservationWindow lockoutObservationWindow: """ + str(lockoutObservationWindow) + """ replace: lockoutThreshold lockoutThreshold: """ + str(lockoutThreshold) + """ """) self.base_dn = self.ldb.domain_dn() self.account_lockout_duration = 2 self.lockout_observation_window = 2 self.update_lockout_settings(threshold=3, duration=2, observation_window=2) # update DC to allow password changes for the duration of this test self.allow_password_changes() self.domain_sid = security.dom_sid(self.ldb.get_domain_sid()) self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % self.host, self.lp, self.global_creds) self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED) self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid) self.addCleanup(self.delete_ldb_connections) # (Re)adds the test user accounts self.lockout1krb5_creds = self.insta_creds(self.template_creds, username="******", userpass="******", kerberos_state=MUST_USE_KERBEROS) self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds) self.lockout1ntlm_creds = self.insta_creds(self.template_creds, username="******", userpass="******", kerberos_state=DONT_USE_KERBEROS) self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds) def delete_ldb_connections(self): del self.lockout1krb5_ldb del self.lockout1ntlm_ldb del self.ldb def tearDown(self): super(BasePasswordTestCase, self).tearDown() def _test_login_lockout(self, creds): username = creds.get_username() userpass = creds.get_password() userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) use_kerberos = creds.get_kerberos_state() # This unlocks by waiting for account_lockout_duration if use_kerberos == MUST_USE_KERBEROS: logoncount_relation = 'greater' lastlogon_relation = 'greater' print("Performs a lockout attempt against LDAP using Kerberos") else: logoncount_relation = 'equal' lastlogon_relation = 'equal' print("Performs a lockout attempt against LDAP using NTLM") # Change password on a connection as another user res = self._check_account(userdn, badPwdCount=0, badPasswordTime=("greater", 0), logonCount=(logoncount_relation, 0), lastLogon=("greater", 0), lastLogonTimestamp=("greater", 0), userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) logonCount = int(res[0]["logonCount"][0]) lastLogon = int(res[0]["lastLogon"][0]) firstLogon = lastLogon lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) print(firstLogon) print(lastLogonTimestamp) self.assertGreater(lastLogon, badPasswordTime) self.assertGreaterEqual(lastLogon, lastLogonTimestamp) # Open a second LDB connection with the user credentials. Use the # command line credentials for informations like the domain, the realm # and the workstation. creds_lockout = self.insta_creds(creds) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") self.assertLoginFailure(self.host_url, creds_lockout, self.lp) res = self._check_account(userdn, badPwdCount=1, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0, msg='lastlogontimestamp with wrong password') badPasswordTime = int(res[0]["badPasswordTime"][0]) # Correct old password creds_lockout.set_password(userpass) ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) # lastLogonTimestamp should not change # lastLogon increases if badPwdCount is non-zero (!) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=(logoncount_relation, logonCount), lastLogon=('greater', lastLogon), lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0, msg='LLTimestamp is updated to lastlogon') logonCount = int(res[0]["logonCount"][0]) lastLogon = int(res[0]["lastLogon"][0]) self.assertGreater(lastLogon, badPasswordTime) self.assertGreaterEqual(lastLogon, lastLogonTimestamp) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") self.assertLoginFailure(self.host_url, creds_lockout, self.lp) res = self._check_account(userdn, badPwdCount=1, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e2: (num, msg) = e2.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=2, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) print("two failed password change") # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e3: (num, msg) = e3.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=3, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, lockoutTime=("greater", badPasswordTime), userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) badPasswordTime = int(res[0]["badPasswordTime"][0]) lockoutTime = int(res[0]["lockoutTime"][0]) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e4: (num, msg) = e4.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=3, badPasswordTime=badPasswordTime, logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, lockoutTime=lockoutTime, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e5: (num, msg) = e5.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=3, badPasswordTime=badPasswordTime, logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, lockoutTime=lockoutTime, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) # The correct password, but we are locked out creds_lockout.set_password(userpass) try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e6: (num, msg) = e6.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=3, badPasswordTime=badPasswordTime, logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, lockoutTime=lockoutTime, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) # wait for the lockout to end time.sleep(self.account_lockout_duration + 1) print(self.account_lockout_duration + 1) res = self._check_account(userdn, badPwdCount=3, effective_bad_password_count=0, badPasswordTime=badPasswordTime, logonCount=logonCount, lockoutTime=lockoutTime, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) # The correct password after letting the timeout expire creds_lockout.set_password(userpass) creds_lockout2 = self.insta_creds(creds_lockout) ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout2, lp=self.lp) time.sleep(3) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=(logoncount_relation, logonCount), lastLogon=(lastlogon_relation, lastLogon), lastLogonTimestamp=lastLogonTimestamp, lockoutTime=0, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0, msg="lastLogon is way off") logonCount = int(res[0]["logonCount"][0]) lastLogon = int(res[0]["lastLogon"][0]) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e7: (num, msg) = e7.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=1, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e8: (num, msg) = e8.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=2, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) time.sleep(self.lockout_observation_window + 1) res = self._check_account(userdn, badPwdCount=2, effective_bad_password_count=0, badPasswordTime=badPasswordTime, logonCount=logonCount, lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e9: (num, msg) = e9.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=1, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) # The correct password without letting the timeout expire creds_lockout.set_password(userpass) ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=(logoncount_relation, logonCount), lockoutTime=0, lastLogon=("greater", lastLogon), lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) def _test_multiple_logon(self, creds): # Test the happy case in which a user logs on correctly, then # logs on correctly again, so that the bad password and # lockout times are both zero the second time. The lastlogon # time should increase. # Open a second LDB connection with the user credentials. Use the # command line credentials for informations like the domain, the realm # and the workstation. username = creds.get_username() userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) use_kerberos = creds.get_kerberos_state() if use_kerberos == MUST_USE_KERBEROS: print("Testing multiple logon with Kerberos") logoncount_relation = 'greater' lastlogon_relation = 'greater' else: print("Testing multiple logon with NTLM") logoncount_relation = 'equal' lastlogon_relation = 'equal' SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=("greater", 0), logonCount=(logoncount_relation, 0), lastLogon=("greater", 0), lastLogonTimestamp=("greater", 0), userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) logonCount = int(res[0]["logonCount"][0]) lastLogon = int(res[0]["lastLogon"][0]) lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) firstLogon = lastLogon print("last logon is %d" % lastLogon) self.assertGreater(lastLogon, badPasswordTime) self.assertGreaterEqual(lastLogon, lastLogonTimestamp) time.sleep(1) SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=(logoncount_relation, logonCount), lastLogon=(lastlogon_relation, lastLogon), lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0, msg=("second logon, firstlogon was %s" % firstLogon)) lastLogon = int(res[0]["lastLogon"][0]) time.sleep(1) SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=(logoncount_relation, logonCount), lastLogon=(lastlogon_relation, lastLogon), lastLogonTimestamp=lastLogonTimestamp, userAccountControl= dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0)
class CredentialsOptions(optparse.OptionGroup): """Command line options for specifying credentials.""" def __init__(self, parser): self.no_pass = True self.ipaddress = None optparse.OptionGroup.__init__(self, parser, "Credentials Options") self.add_option("--simple-bind-dn", metavar="DN", action="callback", callback=self._set_simple_bind_dn, type=str, help="DN to use for a simple bind") self.add_option("--password", metavar="PASSWORD", action="callback", help="Password", type=str, callback=self._set_password) self.add_option("-U", "--username", metavar="USERNAME", action="callback", type=str, help="Username", callback=self._parse_username) self.add_option("-W", "--workgroup", metavar="WORKGROUP", action="callback", type=str, help="Workgroup", callback=self._parse_workgroup) self.add_option("-N", "--no-pass", action="store_true", help="Don't ask for a password") self.add_option("-k", "--kerberos", metavar="KERBEROS", action="callback", type=str, help="Use Kerberos", callback=self._set_kerberos) self.add_option("", "--ipaddress", metavar="IPADDRESS", action="callback", type=str, help="IP address of server", callback=self._set_ipaddress) self.creds = Credentials() def _parse_username(self, option, opt_str, arg, parser): self.creds.parse_string(arg) def _parse_workgroup(self, option, opt_str, arg, parser): self.creds.set_domain(arg) def _set_password(self, option, opt_str, arg, parser): self.creds.set_password(arg) self.no_pass = False def _set_ipaddress(self, option, opt_str, arg, parser): self.ipaddress = arg def _set_kerberos(self, option, opt_str, arg, parser): if arg.lower() in ["yes", 'true', '1']: self.creds.set_kerberos_state(MUST_USE_KERBEROS) elif arg.lower() in ["no", 'false', '0']: self.creds.set_kerberos_state(DONT_USE_KERBEROS) else: raise optparse.BadOptionErr("invalid kerberos option: %s" % arg) def _set_simple_bind_dn(self, option, opt_str, arg, parser): self.creds.set_bind_dn(arg) def get_credentials(self, lp, fallback_machine=False): """Obtain the credentials set on the command-line. :param lp: Loadparm object to use. :return: Credentials object """ self.creds.guess(lp) if self.no_pass: self.creds.set_cmdline_callbacks() # possibly fallback to using the machine account, if we have # access to the secrets db if fallback_machine and not self.creds.authentication_requested(): try: self.creds.set_machine_account(lp) except Exception: pass return self.creds
def join_replicate(ctx): """Replicate the SAM.""" print "Starting replication" ctx.local_samdb.transaction_start() try: source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id()) if ctx.ntds_guid is None: print("Using DS_BIND_GUID_W2K3") destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3) else: destination_dsa_guid = ctx.ntds_guid if ctx.RODC: repl_creds = Credentials() repl_creds.guess(ctx.lp) repl_creds.set_kerberos_state(DONT_USE_KERBEROS) repl_creds.set_username(ctx.samname) repl_creds.set_password(ctx.acct_pass) else: repl_creds = ctx.creds binding_options = "seal" if int(ctx.lp.get("log level")) >= 5: binding_options += ",print" repl = drs_utils.drs_Replicate( "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options), ctx.lp, repl_creds, ctx.local_samdb) repl.replicate(ctx.schema_dn, source_dsa_invocation_id, destination_dsa_guid, schema=True, rodc=ctx.RODC, replica_flags=ctx.replica_flags) repl.replicate(ctx.config_dn, source_dsa_invocation_id, destination_dsa_guid, rodc=ctx.RODC, replica_flags=ctx.replica_flags) if not ctx.subdomain: # Replicate first the critical object for the basedn if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY: print "Replicating critical objects from the base DN of the domain" ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi.DRSUAPI_DRS_GET_ANC repl.replicate(ctx.base_dn, source_dsa_invocation_id, destination_dsa_guid, rodc=ctx.RODC, replica_flags=ctx.domain_replica_flags) ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi.DRSUAPI_DRS_GET_ANC else: ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_GET_ANC repl.replicate(ctx.base_dn, source_dsa_invocation_id, destination_dsa_guid, rodc=ctx.RODC, replica_flags=ctx.domain_replica_flags) print "Done with always replicated NC (base, config, schema)" for nc in (ctx.domaindns_zone, ctx.forestdns_zone): if nc in ctx.nc_list: print "Replicating %s" % (str(nc)) repl.replicate(nc, source_dsa_invocation_id, destination_dsa_guid, rodc=ctx.RODC, replica_flags=ctx.replica_flags) if 'DC=ForestDnsZones,%s' % ctx.root_dn in ctx.nc_list: repl.replicate('DC=ForestDnsZones,%s' % ctx.root_dn, source_dsa_invocation_id, destination_dsa_guid, rodc=ctx.RODC, replica_flags=ctx.replica_flags) # FIXME At this point we should add an entry in the forestdns and domaindns NC # (those under CN=Partions,DC=...) # in order to indicate that we hold a replica for this NC if ctx.RODC: repl.replicate(ctx.acct_dn, source_dsa_invocation_id, destination_dsa_guid, exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True) repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id, destination_dsa_guid, exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True) ctx.repl = repl ctx.source_dsa_invocation_id = source_dsa_invocation_id ctx.destination_dsa_guid = destination_dsa_guid print "Committing SAM database" except: ctx.local_samdb.transaction_cancel() raise else: ctx.local_samdb.transaction_commit()
def join_replicate(ctx): '''replicate the SAM''' print "Starting replication" ctx.local_samdb.transaction_start() try: source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id()) if ctx.ntds_guid is None: print("Using DS_BIND_GUID_W2K3") destination_dsa_guid = misc.GUID( drsuapi.DRSUAPI_DS_BIND_GUID_W2K3) else: destination_dsa_guid = ctx.ntds_guid if ctx.RODC: repl_creds = Credentials() repl_creds.guess(ctx.lp) repl_creds.set_kerberos_state(DONT_USE_KERBEROS) repl_creds.set_username(ctx.samname) repl_creds.set_password(ctx.acct_pass) else: repl_creds = ctx.creds binding_options = "seal" if int(ctx.lp.get("log level")) >= 5: binding_options += ",print" repl = drs_utils.drs_Replicate( "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options), ctx.lp, repl_creds, ctx.local_samdb) repl.replicate(ctx.schema_dn, source_dsa_invocation_id, destination_dsa_guid, schema=True, rodc=ctx.RODC, replica_flags=ctx.replica_flags) repl.replicate(ctx.config_dn, source_dsa_invocation_id, destination_dsa_guid, rodc=ctx.RODC, replica_flags=ctx.replica_flags) if not ctx.subdomain: # Replicate first the critical object for the basedn if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY: print "Replicating critical objects from the base DN of the domain" ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi.DRSUAPI_DRS_GET_ANC repl.replicate(ctx.base_dn, source_dsa_invocation_id, destination_dsa_guid, rodc=ctx.RODC, replica_flags=ctx.domain_replica_flags) ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY | drsuapi.DRSUAPI_DRS_GET_ANC else: ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_GET_ANC repl.replicate(ctx.base_dn, source_dsa_invocation_id, destination_dsa_guid, rodc=ctx.RODC, replica_flags=ctx.domain_replica_flags) if ctx.RODC: repl.replicate(ctx.acct_dn, source_dsa_invocation_id, destination_dsa_guid, exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True) repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id, destination_dsa_guid, exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True) ctx.repl = repl ctx.source_dsa_invocation_id = source_dsa_invocation_id ctx.destination_dsa_guid = destination_dsa_guid print "Committing SAM database" except: ctx.local_samdb.transaction_cancel() raise else: ctx.local_samdb.transaction_commit()
class CredentialsOptions(optparse.OptionGroup): """Command line options for specifying credentials.""" def __init__(self, parser, special_name=None): self.special_name = special_name if special_name is not None: self.section = "Credentials Options (%s)" % special_name else: self.section = "Credentials Options" self.ask_for_password = True self.ipaddress = None self.machine_pass = False optparse.OptionGroup.__init__(self, parser, self.section) self._add_option("--simple-bind-dn", metavar="DN", action="callback", callback=self._set_simple_bind_dn, type=str, help="DN to use for a simple bind") self._add_option("--password", metavar="PASSWORD", action="callback", help="Password", type=str, callback=self._set_password) self._add_option("-U", "--username", metavar="USERNAME", action="callback", type=str, help="Username", callback=self._parse_username) self._add_option("-W", "--workgroup", metavar="WORKGROUP", action="callback", type=str, help="Workgroup", callback=self._parse_workgroup) self._add_option("-N", "--no-pass", action="callback", help="Don't ask for a password", callback=self._set_no_password) self._add_option("-k", "--kerberos", metavar="KERBEROS", action="callback", type=str, help="Use Kerberos", callback=self._set_kerberos) self._add_option("", "--ipaddress", metavar="IPADDRESS", action="callback", type=str, help="IP address of server", callback=self._set_ipaddress) self._add_option("-P", "--machine-pass", action="callback", help="Use stored machine account password", callback=self._set_machine_pass) self._add_option("--krb5-ccache", metavar="KRB5CCNAME", action="callback", type=str, help="Kerberos Credentials cache", callback=self._set_krb5_ccache) self.creds = Credentials() def _ensure_secure_proctitle(self, opt_str, secret_data, data_type="password"): """ Make sure no sensitive data (e.g. password) resides in proctitle. """ import re try: import setproctitle except ModuleNotFoundError: msg = ("WARNING: Using %s on command line is insecure. " "Please install the setproctitle python module.\n" % data_type) sys.stderr.write(msg) sys.stderr.flush() return False # Regex to search and replace secret data + option with. # .*[ ]+ -> Before the option must be one or more spaces. # [= ] -> The option and the secret data might be separated by space # or equal sign. # [ ]*.* -> After the secret data might be one, many or no space. pass_opt_re_str = "(.*[ ]+)(%s[= ]%s)([ ]*.*)" % (opt_str, secret_data) pass_opt_re = re.compile(pass_opt_re_str) # Get current proctitle. cur_proctitle = setproctitle.getproctitle() # Make sure we build the correct regex. if not pass_opt_re.match(cur_proctitle): msg = ("Unable to hide %s in proctitle. This is most likely " "a bug!\n" % data_type) sys.stderr.write(msg) sys.stderr.flush() return False # String to replace secret data with. secret_data_replacer = "xxx" # Build string to replace secret data and option with. And as we dont # want to change anything else than the secret data within the proctitle # we have to check if the option was passed with space or equal sign as # separator. opt_pass_with_eq = "%s=%s" % (opt_str, secret_data) opt_pass_part = re.sub(pass_opt_re_str, r'\2', cur_proctitle) if opt_pass_part == opt_pass_with_eq: replace_str = "%s=%s" % (opt_str, secret_data_replacer) else: replace_str = "%s %s" % (opt_str, secret_data_replacer) # Build new proctitle: new_proctitle = re.sub(pass_opt_re_str, r'\1' + replace_str + r'\3', cur_proctitle) # Set new proctitle. setproctitle.setproctitle(new_proctitle) def _add_option(self, *args1, **kwargs): if self.special_name is None: return self.add_option(*args1, **kwargs) args2 = () for a in args1: if not a.startswith("--"): continue args2 += (a.replace("--", "--%s-" % self.special_name),) self.add_option(*args2, **kwargs) def _parse_username(self, option, opt_str, arg, parser): self.creds.parse_string(arg) self.machine_pass = False def _parse_workgroup(self, option, opt_str, arg, parser): self.creds.set_domain(arg) def _set_password(self, option, opt_str, arg, parser): self._ensure_secure_proctitle(opt_str, arg, "password") self.creds.set_password(arg) self.ask_for_password = False self.machine_pass = False def _set_no_password(self, option, opt_str, arg, parser): self.ask_for_password = False def _set_machine_pass(self, option, opt_str, arg, parser): self.machine_pass = True def _set_ipaddress(self, option, opt_str, arg, parser): self.ipaddress = arg def _set_kerberos(self, option, opt_str, arg, parser): self.creds.set_kerberos_state(parse_kerberos_arg(arg, opt_str)) def _set_simple_bind_dn(self, option, opt_str, arg, parser): self.creds.set_bind_dn(arg) def _set_krb5_ccache(self, option, opt_str, arg, parser): self.creds.set_named_ccache(arg) def get_credentials(self, lp, fallback_machine=False): """Obtain the credentials set on the command-line. :param lp: Loadparm object to use. :return: Credentials object """ self.creds.guess(lp) if self.machine_pass: self.creds.set_machine_account(lp) elif self.ask_for_password: self.creds.set_cmdline_callbacks() # possibly fallback to using the machine account, if we have # access to the secrets db if fallback_machine and not self.creds.authentication_requested(): try: self.creds.set_machine_account(lp) except Exception: pass return self.creds
class LDAPBackend(ProvisionBackend): def __init__(self, backend_type, paths=None, lp=None, names=None, logger=None, domainsid=None, schema=None, hostname=None, ldapadminpass=None, slapd_path=None, ldap_backend_extra_port=None, ldap_backend_forced_uri=None, ldap_dryrun_mode=False): super(LDAPBackend, self).__init__(backend_type=backend_type, paths=paths, lp=lp, names=names, logger=logger) self.domainsid = domainsid self.schema = schema self.hostname = hostname self.ldapdir = os.path.join(paths.private_dir, "ldap") self.ldapadminpass = ldapadminpass self.slapd_path = slapd_path self.slapd_command = None self.slapd_command_escaped = None self.slapd_pid = os.path.join(self.ldapdir, "slapd.pid") self.ldap_backend_extra_port = ldap_backend_extra_port self.ldap_dryrun_mode = ldap_dryrun_mode if ldap_backend_forced_uri is not None: self.ldap_uri = ldap_backend_forced_uri else: self.ldap_uri = "ldapi://%s" % urllib_quote( os.path.join(self.ldapdir, "ldapi"), safe="") if not os.path.exists(self.ldapdir): os.mkdir(self.ldapdir) def init(self): from samba.provision import ProvisioningError # we will shortly start slapd with ldapi for final provisioning. first # check with ldapsearch -> rootDSE via self.ldap_uri if another # instance of slapd is already running try: ldapi_db = Ldb(self.ldap_uri) ldapi_db.search(base="", scope=SCOPE_BASE, expression="(objectClass=OpenLDAProotDSE)") try: f = open(self.slapd_pid, "r") except IOError as err: if err != errno.ENOENT: raise else: try: p = f.read() finally: f.close() self.logger.info( "Check for slapd process with PID: %s and terminate it manually." % p) raise SlapdAlreadyRunning(self.ldap_uri) except LdbError: # XXX: We should never be catching all Ldb errors pass # Try to print helpful messages when the user has not specified the # path to slapd if self.slapd_path is None: raise ProvisioningError( "Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!" ) if not os.path.exists(self.slapd_path): self.logger.warning("Path (%s) to slapd does not exist!", self.slapd_path) if not os.path.isdir(self.ldapdir): os.makedirs(self.ldapdir, 0o700) # Put the LDIF of the schema into a database so we can search on # it to generate schema-dependent configurations in Fedora DS and # OpenLDAP schemadb_path = os.path.join(self.ldapdir, "schema-tmp.ldb") try: os.unlink(schemadb_path) except OSError: pass self.schema.write_to_tmp_ldb(schemadb_path) self.credentials = Credentials() self.credentials.guess(self.lp) # Kerberos to an ldapi:// backend makes no sense (we also force EXTERNAL) self.credentials.set_kerberos_state(DONT_USE_KERBEROS) self.credentials.set_username("samba-admin") self.credentials.set_password(self.ldapadminpass) self.credentials.set_forced_sasl_mech("EXTERNAL") self.provision() def provision(self): pass def start(self): from samba.provision import ProvisioningError self.slapd_command_escaped = "\'" + "\' \'".join( self.slapd_command) + "\'" ldap_backend_script = os.path.join(self.ldapdir, "ldap_backend_startup.sh") f = open(ldap_backend_script, 'w') try: f.write("#!/bin/sh\n" + self.slapd_command_escaped + " $@\n") finally: f.close() os.chmod(ldap_backend_script, 0o755) # Now start the slapd, so we can provision onto it. We keep the # subprocess context around, to kill this off at the successful # end of the script self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False) count = 0 while self.slapd.poll() is None: # Wait until the socket appears try: time.sleep(1) ldapi_db = Ldb(self.ldap_uri, lp=self.lp, credentials=self.credentials) ldapi_db.search(base="", scope=SCOPE_BASE, expression="(objectClass=OpenLDAProotDSE)") # If we have got here, then we must have a valid connection to # the LDAP server! return except LdbError: count = count + 1 if count > 15: self.logger.error( "Could not connect to slapd started with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'") raise ProvisioningError( "slapd never accepted a connection within 15 seconds of starting" ) self.logger.error("Could not start slapd with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'") raise ProvisioningError( "slapd died before we could make a connection to it") def shutdown(self): # if an LDAP backend is in use, terminate slapd after final provision # and check its proper termination if self.slapd.poll() is None: # Kill the slapd if getattr(self.slapd, "terminate", None) is not None: self.slapd.terminate() else: # Older python versions don't have .terminate() import signal os.kill(self.slapd.pid, signal.SIGTERM) # and now wait for it to die self.slapd.communicate() def post_setup(self): return LDAPBackendResult(self.slapd_command_escaped, self.ldapdir)
class CredentialsOptions(optparse.OptionGroup): """Command line options for specifying credentials.""" def __init__(self, parser): self.no_pass = True self.ipaddress = None optparse.OptionGroup.__init__(self, parser, "Credentials Options") self.add_option("--simple-bind-dn", metavar="DN", action="callback", callback=self._set_simple_bind_dn, type=str, help="DN to use for a simple bind") self.add_option("--password", metavar="PASSWORD", action="callback", help="Password", type=str, callback=self._set_password) self.add_option("-U", "--username", metavar="USERNAME", action="callback", type=str, help="Username", callback=self._parse_username) self.add_option("-W", "--workgroup", metavar="WORKGROUP", action="callback", type=str, help="Workgroup", callback=self._parse_workgroup) self.add_option("-N", "--no-pass", action="store_true", help="Don't ask for a password") self.add_option("-k", "--kerberos", metavar="KERBEROS", action="callback", type=str, help="Use Kerberos", callback=self._set_kerberos) self.add_option("", "--ipaddress", metavar="IPADDRESS", action="callback", type=str, help="IP address of server", callback=self._set_ipaddress) self.creds = Credentials() def _parse_username(self, option, opt_str, arg, parser): self.creds.parse_string(arg) def _parse_workgroup(self, option, opt_str, arg, parser): self.creds.set_domain(arg) def _set_password(self, option, opt_str, arg, parser): self.creds.set_password(arg) self.no_pass = False def _set_ipaddress(self, option, opt_str, arg, parser): self.ipaddress = arg def _set_kerberos(self, option, opt_str, arg, parser): self.creds.set_kerberos_state(parse_kerberos_arg(arg, opt_str)) def _set_simple_bind_dn(self, option, opt_str, arg, parser): self.creds.set_bind_dn(arg) def get_credentials(self, lp, fallback_machine=False): """Obtain the credentials set on the command-line. :param lp: Loadparm object to use. :return: Credentials object """ self.creds.guess(lp) if self.no_pass: self.creds.set_cmdline_callbacks() # possibly fallback to using the machine account, if we have # access to the secrets db if fallback_machine and not self.creds.authentication_requested(): try: self.creds.set_machine_account(lp) except Exception: pass return self.creds
class CredentialsOptionsDouble(CredentialsOptions): """Command line options for specifying credentials of two servers.""" def __init__(self, parser): CredentialsOptions.__init__(self, parser) self.no_pass2 = True self.add_option("--simple-bind-dn2", metavar="DN2", action="callback", callback=self._set_simple_bind_dn2, type=str, help="DN to use for a simple bind") self.add_option("--password2", metavar="PASSWORD2", action="callback", help="Password", type=str, callback=self._set_password2) self.add_option("--username2", metavar="USERNAME2", action="callback", type=str, help="Username for second server", callback=self._parse_username2) self.add_option("--workgroup2", metavar="WORKGROUP2", action="callback", type=str, help="Workgroup for second server", callback=self._parse_workgroup2) self.add_option("--no-pass2", action="store_true", help="Don't ask for a password for the second server") self.add_option("--kerberos2", metavar="KERBEROS2", action="callback", type=str, help="Use Kerberos", callback=self._set_kerberos2) self.creds2 = Credentials() def _parse_username2(self, option, opt_str, arg, parser): self.creds2.parse_string(arg) def _parse_workgroup2(self, option, opt_str, arg, parser): self.creds2.set_domain(arg) def _set_password2(self, option, opt_str, arg, parser): self.creds2.set_password(arg) self.no_pass2 = False def _set_kerberos2(self, option, opt_str, arg, parser): self.creds2.set_kerberos_state(parse_kerberos_arg(arg, opt_str)) def _set_simple_bind_dn2(self, option, opt_str, arg, parser): self.creds2.set_bind_dn(arg) def get_credentials2(self, lp, guess=True): """Obtain the credentials set on the command-line. :param lp: Loadparm object to use. :param guess: Try guess Credentials from environment :return: Credentials object """ if guess: self.creds2.guess(lp) elif not self.creds2.get_username(): self.creds2.set_anonymous() if self.no_pass2: self.creds2.set_cmdline_callbacks() return self.creds2
class CredentialsOptions(optparse.OptionGroup): """Command line options for specifying credentials.""" def __init__(self, parser, special_name=None): self.special_name = special_name if special_name is not None: self.section = "Credentials Options (%s)" % special_name else: self.section = "Credentials Options" self.ask_for_password = True self.ipaddress = None self.machine_pass = False optparse.OptionGroup.__init__(self, parser, self.section) self._add_option("--simple-bind-dn", metavar="DN", action="callback", callback=self._set_simple_bind_dn, type=str, help="DN to use for a simple bind") self._add_option("--password", metavar="PASSWORD", action="callback", help="Password", type=str, callback=self._set_password) self._add_option("-U", "--username", metavar="USERNAME", action="callback", type=str, help="Username", callback=self._parse_username) self._add_option("-W", "--workgroup", metavar="WORKGROUP", action="callback", type=str, help="Workgroup", callback=self._parse_workgroup) self._add_option("-N", "--no-pass", action="callback", help="Don't ask for a password", callback=self._set_no_password) self._add_option("-k", "--kerberos", metavar="KERBEROS", action="callback", type=str, help="Use Kerberos", callback=self._set_kerberos) self._add_option("", "--ipaddress", metavar="IPADDRESS", action="callback", type=str, help="IP address of server", callback=self._set_ipaddress) self._add_option("-P", "--machine-pass", action="callback", help="Use stored machine account password", callback=self._set_machine_pass) self.creds = Credentials() def _add_option(self, *args1, **kwargs): if self.special_name is None: return self.add_option(*args1, **kwargs) args2 = () for a in args1: if not a.startswith("--"): continue args2 += (a.replace("--", "--%s-" % self.special_name), ) self.add_option(*args2, **kwargs) def _parse_username(self, option, opt_str, arg, parser): self.creds.parse_string(arg) self.machine_pass = False def _parse_workgroup(self, option, opt_str, arg, parser): self.creds.set_domain(arg) def _set_password(self, option, opt_str, arg, parser): self.creds.set_password(arg) self.ask_for_password = False self.machine_pass = False def _set_no_password(self, option, opt_str, arg, parser): self.ask_for_password = False def _set_machine_pass(self, option, opt_str, arg, parser): self.machine_pass = True def _set_ipaddress(self, option, opt_str, arg, parser): self.ipaddress = arg def _set_kerberos(self, option, opt_str, arg, parser): self.creds.set_kerberos_state(parse_kerberos_arg(arg, opt_str)) def _set_simple_bind_dn(self, option, opt_str, arg, parser): self.creds.set_bind_dn(arg) def get_credentials(self, lp, fallback_machine=False): """Obtain the credentials set on the command-line. :param lp: Loadparm object to use. :return: Credentials object """ self.creds.guess(lp) if self.machine_pass: self.creds.set_machine_account(lp) elif self.ask_for_password: self.creds.set_cmdline_callbacks() # possibly fallback to using the machine account, if we have # access to the secrets db if fallback_machine and not self.creds.authentication_requested(): try: self.creds.set_machine_account(lp) except Exception: pass return self.creds
class BasePasswordTestCase(PasswordTestCase): def _open_samr_user(self, res): self.assertTrue("objectSid" in res[0]) (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split() self.assertEquals(self.domain_sid, domain_sid) return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid) def _check_attribute(self, res, name, value): if value is None: self.assertTrue(name not in res[0], msg="attr[%s]=%r on dn[%s]" % (name, res[0], res[0].dn)) return if isinstance(value, tuple): (mode, value) = value else: mode = "equal" if mode == "ignore": return if mode == "absent": self.assertFalse(name in res[0], msg="attr[%s] not missing on dn[%s]" % (name, res[0].dn)) return self.assertTrue(name in res[0], msg="attr[%s] missing on dn[%s]" % (name, res[0].dn)) self.assertTrue(len(res[0][name]) == 1, msg="attr[%s]=%r on dn[%s]" % (name, res[0][name], res[0].dn)) print("%s = '%s'" % (name, res[0][name][0])) if mode == "present": return if mode == "equal": v = int(res[0][name][0]) value = int(value) msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n" "(diff %d; actual value is %s than expected)" % (name, v, value, res[0].dn, v - value, ('less' if v < value else 'greater'))) self.assertTrue(v == value, msg) return if mode == "greater": v = int(res[0][name][0]) self.assertTrue(v > int(value), msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" % (name, v, int(value), res[0].dn, v - int(value))) return if mode == "less": v = int(res[0][name][0]) self.assertTrue(v < int(value), msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" % (name, v, int(value), res[0].dn, v - int(value))) return self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode) def _check_account_initial(self, userdn): self._check_account(userdn, badPwdCount=0, badPasswordTime=0, logonCount=0, lastLogon=0, lastLogonTimestamp=("absent", None), userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) def _check_account(self, dn, badPwdCount=None, badPasswordTime=None, logonCount=None, lastLogon=None, lastLogonTimestamp=None, lockoutTime=None, userAccountControl=None, msDSUserAccountControlComputed=None, effective_bad_password_count=None, msg=None, badPwdCountOnly=False): print('-=' * 36) if msg is not None: print("\033[01;32m %s \033[00m\n" % msg) attrs = [ "objectSid", "badPwdCount", "badPasswordTime", "lastLogon", "lastLogonTimestamp", "logonCount", "lockoutTime", "userAccountControl", "msDS-User-Account-Control-Computed" ] # in order to prevent some time resolution problems we sleep for # 10 micro second time.sleep(0.01) res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs) self.assertTrue(len(res) == 1) self._check_attribute(res, "badPwdCount", badPwdCount) self._check_attribute(res, "lockoutTime", lockoutTime) self._check_attribute(res, "badPasswordTime", badPasswordTime) if not badPwdCountOnly: self._check_attribute(res, "logonCount", logonCount) self._check_attribute(res, "lastLogon", lastLogon) self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp) self._check_attribute(res, "userAccountControl", userAccountControl) self._check_attribute(res, "msDS-User-Account-Control-Computed", msDSUserAccountControlComputed) lastLogon = int(res[0]["lastLogon"][0]) logonCount = int(res[0]["logonCount"][0]) samr_user = self._open_samr_user(res) uinfo3 = self.samr.QueryUserInfo(samr_user, 3) uinfo5 = self.samr.QueryUserInfo(samr_user, 5) uinfo16 = self.samr.QueryUserInfo(samr_user, 16) uinfo21 = self.samr.QueryUserInfo(samr_user, 21) self.samr.Close(samr_user) expected_acb_info = 0 if not badPwdCountOnly: if userAccountControl & dsdb.UF_NORMAL_ACCOUNT: expected_acb_info |= samr.ACB_NORMAL if userAccountControl & dsdb.UF_ACCOUNTDISABLE: expected_acb_info |= samr.ACB_DISABLED if userAccountControl & dsdb.UF_PASSWD_NOTREQD: expected_acb_info |= samr.ACB_PWNOTREQ if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT: expected_acb_info |= samr.ACB_AUTOLOCK if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED: expected_acb_info |= samr.ACB_PW_EXPIRED self.assertEquals(uinfo3.acct_flags, expected_acb_info) self.assertEquals(uinfo3.last_logon, lastLogon) self.assertEquals(uinfo3.logon_count, logonCount) expected_bad_password_count = 0 if badPwdCount is not None: expected_bad_password_count = badPwdCount if effective_bad_password_count is None: effective_bad_password_count = expected_bad_password_count self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count) if not badPwdCountOnly: self.assertEquals(uinfo5.acct_flags, expected_acb_info) self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count) self.assertEquals(uinfo5.last_logon, lastLogon) self.assertEquals(uinfo5.logon_count, logonCount) self.assertEquals(uinfo16.acct_flags, expected_acb_info) self.assertEquals(uinfo21.acct_flags, expected_acb_info) self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count) self.assertEquals(uinfo21.last_logon, lastLogon) self.assertEquals(uinfo21.logon_count, logonCount) # check LDAP again and make sure the samr.QueryUserInfo # doesn't have any impact. res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs) self.assertEquals(res[0], res2[0]) # in order to prevent some time resolution problems we sleep for # 10 micro second time.sleep(0.01) return res def update_lockout_settings(self, threshold, duration, observation_window): """Updates the global user lockout settings""" m = Message() m.dn = Dn(self.ldb, self.base_dn) account_lockout_duration_ticks = -int(duration * (1e7)) m["lockoutDuration"] = MessageElement( str(account_lockout_duration_ticks), FLAG_MOD_REPLACE, "lockoutDuration") m["lockoutThreshold"] = MessageElement(str(threshold), FLAG_MOD_REPLACE, "lockoutThreshold") lockout_observation_window_ticks = -int(observation_window * (1e7)) m["lockOutObservationWindow"] = MessageElement( str(lockout_observation_window_ticks), FLAG_MOD_REPLACE, "lockOutObservationWindow") self.ldb.modify(m) def _readd_user(self, creds, lockOutObservationWindow=0): username = creds.get_username() userpass = creds.get_password() userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) delete_force(self.ldb, userdn) self.ldb.add({ "dn": userdn, "objectclass": "user", "sAMAccountName": username }) self.addCleanup(delete_force, self.ldb, userdn) # Sets the initial user password with a "special" password change # I think that this internally is a password set operation and it can # only be performed by someone which has password set privileges on the # account (at least in s4 we do handle it like that). self.ldb.modify_ldif(""" dn: """ + userdn + """ changetype: modify delete: userPassword add: userPassword userPassword: """ + userpass + """ """) # Enables the user account self.ldb.enable_account("(sAMAccountName=%s)" % username) use_kerberos = creds.get_kerberos_state() fail_creds = self.insta_creds(self.template_creds, username=username, userpass=userpass + "X", kerberos_state=use_kerberos) self._check_account_initial(userdn) # Fail once to get a badPasswordTime try: ldb = SamDB(url=self.host_url, credentials=fail_creds, lp=self.lp) self.fail() except LdbError as e: (num, msg) = e.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) # Succeed to reset everything to 0 ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp) return ldb def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS): try: ldb = SamDB(url=url, credentials=creds, lp=lp) self.fail("Login unexpectedly succeeded") except LdbError as e1: (num, msg) = e1.args if errno is not None: self.assertEquals(num, errno, ("Login failed in the wrong way" "(got err %d, expected %d)" % (num, errno))) def setUp(self): super(BasePasswordTestCase, self).setUp() self.global_creds.set_gensec_features( self.global_creds.get_gensec_features() | gensec.FEATURE_SEAL) self.template_creds = Credentials() self.template_creds.set_username("testuser") self.template_creds.set_password("thatsAcomplPASS1") self.template_creds.set_domain(self.global_creds.get_domain()) self.template_creds.set_realm(self.global_creds.get_realm()) self.template_creds.set_workstation( self.global_creds.get_workstation()) self.template_creds.set_gensec_features( self.global_creds.get_gensec_features()) self.template_creds.set_kerberos_state( self.global_creds.get_kerberos_state()) # Gets back the basedn base_dn = self.ldb.domain_dn() # Gets back the configuration basedn configuration_dn = self.ldb.get_config_basedn().get_linearized() res = self.ldb.search(base_dn, scope=SCOPE_BASE, attrs=[ "lockoutDuration", "lockOutObservationWindow", "lockoutThreshold" ]) if "lockoutDuration" in res[0]: lockoutDuration = res[0]["lockoutDuration"][0] else: lockoutDuration = 0 if "lockoutObservationWindow" in res[0]: lockoutObservationWindow = res[0]["lockoutObservationWindow"][0] else: lockoutObservationWindow = 0 if "lockoutThreshold" in res[0]: lockoutThreshold = res[0]["lockoutThreshold"][0] else: lockoutTreshold = 0 self.addCleanup( self.ldb.modify_ldif, """ dn: """ + base_dn + """ changetype: modify replace: lockoutDuration lockoutDuration: """ + str(lockoutDuration) + """ replace: lockoutObservationWindow lockoutObservationWindow: """ + str(lockoutObservationWindow) + """ replace: lockoutThreshold lockoutThreshold: """ + str(lockoutThreshold) + """ """) self.base_dn = self.ldb.domain_dn() # # Some test cases sleep() for self.account_lockout_duration # so allow it to be controlled via the subclass # if not hasattr(self, 'account_lockout_duration'): self.account_lockout_duration = 3 if not hasattr(self, 'lockout_observation_window'): self.lockout_observation_window = 3 self.update_lockout_settings( threshold=3, duration=self.account_lockout_duration, observation_window=self.lockout_observation_window) # update DC to allow password changes for the duration of this test self.allow_password_changes() self.domain_sid = security.dom_sid(self.ldb.get_domain_sid()) self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % self.host, self.lp, self.global_creds) self.samr_handle = self.samr.Connect2( None, security.SEC_FLAG_MAXIMUM_ALLOWED) self.samr_domain = self.samr.OpenDomain( self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid) self.addCleanup(self.delete_ldb_connections) # (Re)adds the test user accounts self.lockout1krb5_creds = self.insta_creds( self.template_creds, username="******", userpass="******", kerberos_state=MUST_USE_KERBEROS) self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds) self.lockout1ntlm_creds = self.insta_creds( self.template_creds, username="******", userpass="******", kerberos_state=DONT_USE_KERBEROS) self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds) def delete_ldb_connections(self): del self.lockout1krb5_ldb del self.lockout1ntlm_ldb del self.ldb def tearDown(self): super(BasePasswordTestCase, self).tearDown() def _test_login_lockout(self, creds): username = creds.get_username() userpass = creds.get_password() userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) use_kerberos = creds.get_kerberos_state() # This unlocks by waiting for account_lockout_duration if use_kerberos == MUST_USE_KERBEROS: logoncount_relation = 'greater' lastlogon_relation = 'greater' print("Performs a lockout attempt against LDAP using Kerberos") else: logoncount_relation = 'equal' lastlogon_relation = 'equal' print("Performs a lockout attempt against LDAP using NTLM") # Change password on a connection as another user res = self._check_account(userdn, badPwdCount=0, badPasswordTime=("greater", 0), logonCount=(logoncount_relation, 0), lastLogon=("greater", 0), lastLogonTimestamp=("greater", 0), userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) logonCount = int(res[0]["logonCount"][0]) lastLogon = int(res[0]["lastLogon"][0]) firstLogon = lastLogon lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) print(firstLogon) print(lastLogonTimestamp) self.assertGreater(lastLogon, badPasswordTime) self.assertGreaterEqual(lastLogon, lastLogonTimestamp) # Open a second LDB connection with the user credentials. Use the # command line credentials for informations like the domain, the realm # and the workstation. creds_lockout = self.insta_creds(creds) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") self.assertLoginFailure(self.host_url, creds_lockout, self.lp) res = self._check_account(userdn, badPwdCount=1, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0, msg='lastlogontimestamp with wrong password') badPasswordTime = int(res[0]["badPasswordTime"][0]) # Correct old password creds_lockout.set_password(userpass) ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) # lastLogonTimestamp should not change # lastLogon increases if badPwdCount is non-zero (!) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=(logoncount_relation, logonCount), lastLogon=('greater', lastLogon), lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0, msg='LLTimestamp is updated to lastlogon') logonCount = int(res[0]["logonCount"][0]) lastLogon = int(res[0]["lastLogon"][0]) self.assertGreater(lastLogon, badPasswordTime) self.assertGreaterEqual(lastLogon, lastLogonTimestamp) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") self.assertLoginFailure(self.host_url, creds_lockout, self.lp) res = self._check_account(userdn, badPwdCount=1, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e2: (num, msg) = e2.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=2, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) print("two failed password change") # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e3: (num, msg) = e3.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account( userdn, badPwdCount=3, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, lockoutTime=("greater", badPasswordTime), userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) badPasswordTime = int(res[0]["badPasswordTime"][0]) lockoutTime = int(res[0]["lockoutTime"][0]) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e4: (num, msg) = e4.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account( userdn, badPwdCount=3, badPasswordTime=badPasswordTime, logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, lockoutTime=lockoutTime, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e5: (num, msg) = e5.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account( userdn, badPwdCount=3, badPasswordTime=badPasswordTime, logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, lockoutTime=lockoutTime, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) # The correct password, but we are locked out creds_lockout.set_password(userpass) try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e6: (num, msg) = e6.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account( userdn, badPwdCount=3, badPasswordTime=badPasswordTime, logonCount=logonCount, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, lockoutTime=lockoutTime, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=dsdb.UF_LOCKOUT) # wait for the lockout to end time.sleep(self.account_lockout_duration + 1) print(self.account_lockout_duration + 1) res = self._check_account(userdn, badPwdCount=3, effective_bad_password_count=0, badPasswordTime=badPasswordTime, logonCount=logonCount, lockoutTime=lockoutTime, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) # The correct password after letting the timeout expire creds_lockout.set_password(userpass) creds_lockout2 = self.insta_creds(creds_lockout) ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout2, lp=self.lp) time.sleep(3) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=(logoncount_relation, logonCount), lastLogon=(lastlogon_relation, lastLogon), lastLogonTimestamp=lastLogonTimestamp, lockoutTime=0, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0, msg="lastLogon is way off") logonCount = int(res[0]["logonCount"][0]) lastLogon = int(res[0]["lastLogon"][0]) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e7: (num, msg) = e7.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=1, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e8: (num, msg) = e8.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=2, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) time.sleep(self.lockout_observation_window + 1) res = self._check_account(userdn, badPwdCount=2, effective_bad_password_count=0, badPasswordTime=badPasswordTime, logonCount=logonCount, lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) # The wrong password creds_lockout.set_password("thatsAcomplPASS1x") try: ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) self.fail() except LdbError as e9: (num, msg) = e9.args self.assertEquals(num, ERR_INVALID_CREDENTIALS) res = self._check_account(userdn, badPwdCount=1, badPasswordTime=("greater", badPasswordTime), logonCount=logonCount, lockoutTime=0, lastLogon=lastLogon, lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) # The correct password without letting the timeout expire creds_lockout.set_password(userpass) ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=(logoncount_relation, logonCount), lockoutTime=0, lastLogon=("greater", lastLogon), lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) def _test_multiple_logon(self, creds): # Test the happy case in which a user logs on correctly, then # logs on correctly again, so that the bad password and # lockout times are both zero the second time. The lastlogon # time should increase. # Open a second LDB connection with the user credentials. Use the # command line credentials for informations like the domain, the realm # and the workstation. username = creds.get_username() userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) use_kerberos = creds.get_kerberos_state() if use_kerberos == MUST_USE_KERBEROS: print("Testing multiple logon with Kerberos") logoncount_relation = 'greater' lastlogon_relation = 'greater' else: print("Testing multiple logon with NTLM") logoncount_relation = 'equal' lastlogon_relation = 'equal' SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=("greater", 0), logonCount=(logoncount_relation, 0), lastLogon=("greater", 0), lastLogonTimestamp=("greater", 0), userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0) badPasswordTime = int(res[0]["badPasswordTime"][0]) logonCount = int(res[0]["logonCount"][0]) lastLogon = int(res[0]["lastLogon"][0]) lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) firstLogon = lastLogon print("last logon is %d" % lastLogon) self.assertGreater(lastLogon, badPasswordTime) self.assertGreaterEqual(lastLogon, lastLogonTimestamp) time.sleep(1) SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=(logoncount_relation, logonCount), lastLogon=(lastlogon_relation, lastLogon), lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0, msg=("second logon, firstlogon was %s" % firstLogon)) lastLogon = int(res[0]["lastLogon"][0]) time.sleep(1) SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp) res = self._check_account(userdn, badPwdCount=0, badPasswordTime=badPasswordTime, logonCount=(logoncount_relation, logonCount), lastLogon=(lastlogon_relation, lastLogon), lastLogonTimestamp=lastLogonTimestamp, userAccountControl=dsdb.UF_NORMAL_ACCOUNT, msDSUserAccountControlComputed=0)
class PyCredentialsTests(TestCase): def setUp(self): super(PyCredentialsTests, self).setUp() self.server = os.environ["SERVER"] self.domain = os.environ["DOMAIN"] self.host = os.environ["SERVER_IP"] self.lp = self.get_loadparm() self.credentials = self.get_credentials() self.session = system_session() self.ldb = SamDB(url="ldap://%s" % self.host, session_info=self.session, credentials=self.credentials, lp=self.lp) self.create_machine_account() self.create_user_account() def tearDown(self): super(PyCredentialsTests, self).tearDown() delete_force(self.ldb, self.machine_dn) delete_force(self.ldb, self.user_dn) # Until a successful netlogon connection has been established there will # not be a valid authenticator associated with the credentials # and new_client_authenticator should throw a ValueError def test_no_netlogon_connection(self): self.assertRaises(ValueError, self.machine_creds.new_client_authenticator) # Once a netlogon connection has been established, # new_client_authenticator should return a value # def test_have_netlogon_connection(self): c = self.get_netlogon_connection() a = self.machine_creds.new_client_authenticator() self.assertIsNotNone(a) # Get an authenticator and use it on a sequence of operations requiring # an authenticator def test_client_authenticator(self): c = self.get_netlogon_connection() (authenticator, subsequent) = self.get_authenticator(c) self.do_NetrLogonSamLogonWithFlags(c, authenticator, subsequent) (authenticator, subsequent) = self.get_authenticator(c) self.do_NetrLogonGetDomainInfo(c, authenticator, subsequent) (authenticator, subsequent) = self.get_authenticator(c) self.do_NetrLogonGetDomainInfo(c, authenticator, subsequent) (authenticator, subsequent) = self.get_authenticator(c) self.do_NetrLogonGetDomainInfo(c, authenticator, subsequent) def test_SamLogonEx(self): c = self.get_netlogon_connection() logon = samlogon_logon_info(self.domain, self.machine_name, self.user_creds) logon_level = netlogon.NetlogonNetworkTransitiveInformation validation_level = netlogon.NetlogonValidationSamInfo4 netr_flags = 0 try: c.netr_LogonSamLogonEx(self.server, self.user_creds.get_workstation(), logon_level, logon, validation_level, netr_flags) except NTSTATUSError as e: enum = ctypes.c_uint32(e.args[0]).value if enum == ntstatus.NT_STATUS_WRONG_PASSWORD: self.fail("got wrong password error") else: raise def test_SamLogonEx_no_domain(self): c = self.get_netlogon_connection() self.user_creds.set_domain('') logon = samlogon_logon_info(self.domain, self.machine_name, self.user_creds) logon_level = netlogon.NetlogonNetworkTransitiveInformation validation_level = netlogon.NetlogonValidationSamInfo4 netr_flags = 0 try: c.netr_LogonSamLogonEx(self.server, self.user_creds.get_workstation(), logon_level, logon, validation_level, netr_flags) except NTSTATUSError as e: enum = ctypes.c_uint32(e.args[0]).value if enum == ntstatus.NT_STATUS_WRONG_PASSWORD: self.fail("got wrong password error") else: self.fail("got unexpected error" + str(e)) def test_SamLogonExNTLM(self): c = self.get_netlogon_connection() logon = samlogon_logon_info(self.domain, self.machine_name, self.user_creds, flags=CLI_CRED_NTLM_AUTH) logon_level = netlogon.NetlogonNetworkTransitiveInformation validation_level = netlogon.NetlogonValidationSamInfo4 netr_flags = 0 try: c.netr_LogonSamLogonEx(self.server, self.user_creds.get_workstation(), logon_level, logon, validation_level, netr_flags) except NTSTATUSError as e: enum = ctypes.c_uint32(e.args[0]).value if enum == ntstatus.NT_STATUS_WRONG_PASSWORD: self.fail("got wrong password error") else: raise def test_SamLogonExMSCHAPv2(self): c = self.get_netlogon_connection() logon = samlogon_logon_info(self.domain, self.machine_name, self.user_creds, flags=CLI_CRED_NTLM_AUTH) logon.identity_info.parameter_control = MSV1_0_ALLOW_MSVCHAPV2 logon_level = netlogon.NetlogonNetworkTransitiveInformation validation_level = netlogon.NetlogonValidationSamInfo4 netr_flags = 0 try: c.netr_LogonSamLogonEx(self.server, self.user_creds.get_workstation(), logon_level, logon, validation_level, netr_flags) except NTSTATUSError as e: enum = ctypes.c_uint32(e.args[0]).value if enum == ntstatus.NT_STATUS_WRONG_PASSWORD: self.fail("got wrong password error") else: raise # Test Credentials.encrypt_netr_crypt_password # By performing a NetrServerPasswordSet2 # And the logging on using the new password. def test_encrypt_netr_password(self): # Change the password self.do_Netr_ServerPasswordSet2() # Now use the new password to perform an operation srvsvc.srvsvc("ncacn_np:%s" % (self.server), self.lp, self.machine_creds) # Change the current machine account password with a # netr_ServerPasswordSet2 call. def do_Netr_ServerPasswordSet2(self): c = self.get_netlogon_connection() (authenticator, subsequent) = self.get_authenticator(c) PWD_LEN = 32 DATA_LEN = 512 newpass = samba.generate_random_password(PWD_LEN, PWD_LEN) encoded = newpass.encode('utf-16-le') pwd_len = len(encoded) filler = [ord(x) for x in os.urandom(DATA_LEN-pwd_len)] pwd = netlogon.netr_CryptPassword() pwd.length = pwd_len pwd.data = filler + [ord(x) for x in encoded] self.machine_creds.encrypt_netr_crypt_password(pwd) c.netr_ServerPasswordSet2(self.server, self.machine_creds.get_workstation(), SEC_CHAN_WKSTA, self.machine_name, authenticator, pwd) self.machine_pass = newpass self.machine_creds.set_password(newpass) # Establish sealed schannel netlogon connection over TCP/IP # def get_netlogon_connection(self): return netlogon.netlogon("ncacn_ip_tcp:%s[schannel,seal]" % self.server, self.lp, self.machine_creds) # # Create the machine account def create_machine_account(self): self.machine_pass = samba.generate_random_password(32, 32) self.machine_name = MACHINE_NAME self.machine_dn = "cn=%s,%s" % (self.machine_name, self.ldb.domain_dn()) # remove the account if it exists, this will happen if a previous test # run failed delete_force(self.ldb, self.machine_dn) utf16pw = unicode( '"' + self.machine_pass.encode('utf-8') + '"', 'utf-8' ).encode('utf-16-le') self.ldb.add({ "dn": self.machine_dn, "objectclass": "computer", "sAMAccountName": "%s$" % self.machine_name, "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD), "unicodePwd": utf16pw}) self.machine_creds = Credentials() self.machine_creds.guess(self.get_loadparm()) self.machine_creds.set_secure_channel_type(SEC_CHAN_WKSTA) self.machine_creds.set_kerberos_state(DONT_USE_KERBEROS) self.machine_creds.set_password(self.machine_pass) self.machine_creds.set_username(self.machine_name + "$") self.machine_creds.set_workstation(self.machine_name) # # Create a test user account def create_user_account(self): self.user_pass = samba.generate_random_password(32, 32) self.user_name = USER_NAME self.user_dn = "cn=%s,%s" % (self.user_name, self.ldb.domain_dn()) # remove the account if it exists, this will happen if a previous test # run failed delete_force(self.ldb, self.user_dn) utf16pw = unicode( '"' + self.user_pass.encode('utf-8') + '"', 'utf-8' ).encode('utf-16-le') self.ldb.add({ "dn": self.user_dn, "objectclass": "user", "sAMAccountName": "%s" % self.user_name, "userAccountControl": str(UF_NORMAL_ACCOUNT), "unicodePwd": utf16pw}) self.user_creds = Credentials() self.user_creds.guess(self.get_loadparm()) self.user_creds.set_password(self.user_pass) self.user_creds.set_username(self.user_name) self.user_creds.set_workstation(self.machine_name) pass # # Get the authenticator from the machine creds. def get_authenticator(self, c): auth = self.machine_creds.new_client_authenticator(); current = netr_Authenticator() current.cred.data = [ord(x) for x in auth["credential"]] current.timestamp = auth["timestamp"] subsequent = netr_Authenticator() return (current, subsequent) def do_NetrLogonSamLogonWithFlags(self, c, current, subsequent): logon = samlogon_logon_info(self.domain, self.machine_name, self.user_creds) logon_level = netlogon.NetlogonNetworkTransitiveInformation validation_level = netlogon.NetlogonValidationSamInfo4 netr_flags = 0 c.netr_LogonSamLogonWithFlags(self.server, self.user_creds.get_workstation(), current, subsequent, logon_level, logon, validation_level, netr_flags) def do_NetrLogonGetDomainInfo(self, c, current, subsequent): query = netr_WorkstationInformation() c.netr_LogonGetDomainInfo(self.server, self.user_creds.get_workstation(), current, subsequent, 2, query)