diff options
author | Stefan Metzmacher <metze@samba.org> | 2011-12-15 16:28:08 +0100 |
---|---|---|
committer | Stefan Metzmacher <metze@samba.org> | 2012-01-09 19:06:06 +0100 |
commit | 73d96ed239a1616f1074186d5ba0d02f716cae0e (patch) | |
tree | c32ee79c18e6fe32a5c97e189e36cf0bab8bafd8 | |
parent | 88258c3b9337b7c384871d159ea4f12c280e1b2e (diff) | |
download | samba-73d96ed239a1616f1074186d5ba0d02f716cae0e.tar.gz samba-73d96ed239a1616f1074186d5ba0d02f716cae0e.tar.bz2 samba-73d96ed239a1616f1074186d5ba0d02f716cae0e.zip |
s4:scripting/devel: add repl_cleartext_pwd.py script
This is useful to sync passwords from an AD domain.
$
$ source4/scripting/devel/repl_cleartext_pwd.py \
-Uadministrator%A1b2C3d4 \
172.31.9.219 DC=bla,DC=base /tmp/cookie cleartext_utf8 131085 displayName
# starting at usn[0]
dn: CN=Test User1,CN=Users,DC=bla,DC=base
cleartext_utf8: A1b2C3d4
displayName:: VABlAHMAdAAgAFUAcwBlAHIAMQA=
# up to usn[16449]
$
$ source4/scripting/devel/repl_cleartext_pwd.py \
-Uadministrator%A1b2C3d4
172.31.9.219 DC=bla,DC=base /tmp/cookie cleartext_utf8 131085 displayName
# starting at usn[16449]
# up to usn[16449]
$
metze
Autobuild-User: Stefan Metzmacher <metze@samba.org>
Autobuild-Date: Mon Jan 9 19:06:06 CET 2012 on sn-devel-104
-rwxr-xr-x | source4/scripting/devel/repl_cleartext_pwd.py | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/source4/scripting/devel/repl_cleartext_pwd.py b/source4/scripting/devel/repl_cleartext_pwd.py new file mode 100755 index 0000000000..ac650d95d0 --- /dev/null +++ b/source4/scripting/devel/repl_cleartext_pwd.py @@ -0,0 +1,377 @@ +#!/usr/bin/env python +# +# Copyright Stefan Metzmacher 2011-2012 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# This is useful to sync passwords from an AD domain. +# +# $ +# $ source4/scripting/devel/repl_cleartext_pwd.py \ +# -Uadministrator%A1b2C3d4 \ +# 172.31.9.219 DC=bla,DC=base /tmp/cookie cleartext_utf8 131085 displayName +# # starting at usn[0] +# dn: CN=Test User1,CN=Users,DC=bla,DC=base +# cleartext_utf8: A1b2C3d4 +# displayName:: VABlAHMAdAAgAFUAcwBlAHIAMQA= +# +# # up to usn[16449] +# $ +# $ source4/scripting/devel/repl_cleartext_pwd.py \ +# -Uadministrator%A1b2C3d4 +# 172.31.9.219 DC=bla,DC=base cookie_file cleartext_utf8 131085 displayName +# # starting at usn[16449] +# # up to usn[16449] +# $ +# + +import sys + +# Find right direction when running from source tree +sys.path.insert(0, "bin/python") + +import samba.getopt as options +from optparse import OptionParser + +from samba.dcerpc import drsuapi, drsblobs, misc +from samba.ndr import ndr_pack, ndr_unpack, ndr_print + +import binascii +import hashlib +import Crypto.Cipher.ARC4 +import struct +import os + +from ldif import LDIFWriter + +class globals: + def __init__(self): + self.global_objs = {} + self.ldif = LDIFWriter(sys.stdout) + + def add_attr(self, dn, attname, vals): + if dn not in self.global_objs: + self.global_objs[dn] = {} + self.global_objs[dn][attname] = vals + + def print_all(self): + for dn, obj in self.global_objs.items(): + self.ldif.unparse(dn, obj) + continue + self.global_objs = {} + +########### main code ########### +if __name__ == "__main__": + parser = OptionParser("repl_cleartext_pwd.py [options] server dn cookie_file cleartext_name [attid attname]") + sambaopts = options.SambaOptions(parser) + credopts = options.CredentialsOptions(parser) + parser.add_option_group(credopts) + + (opts, args) = parser.parse_args() + + if len(args) < 4 or len(args) == 5: + parser.error("more arguments required") + + server = args[0] + dn = args[1] + cookie_file = args[2] + if len(cookie_file) == 0: + cookie_file = None + cleartext_name = args[3] + if len(args) >= 5: + attid = int(args[4]) + attname = args[5] + else: + attid = -1 + attname = None + + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + + if not creds.authentication_requested(): + parser.error("You must supply credentials") + + gls = globals() + try: + f = open(cookie_file, 'r') + store_blob = f.read() + f.close() + + store_hdr = store_blob[0:28] + (store_version, \ + store_dn_len, store_dn_ofs, \ + store_hwm_len, store_hwm_ofs, \ + store_utdv_len, store_utdv_ofs) = \ + struct.unpack("<LLLLLLL", store_hdr) + + store_dn = store_blob[store_dn_ofs:store_dn_ofs+store_dn_len] + store_hwm_blob = store_blob[store_hwm_ofs:store_hwm_ofs+store_hwm_len] + store_utdv_blob = store_blob[store_utdv_ofs:store_utdv_ofs+store_utdv_len] + + store_hwm = ndr_unpack(drsuapi.DsReplicaHighWaterMark, store_hwm_blob) + store_utdv = ndr_unpack(drsblobs.replUpToDateVectorBlob, store_utdv_blob) + + assert store_dn == dn + #print "%s" % ndr_print(store_hwm) + #print "%s" % ndr_print(store_utdv) + except: + store_dn = dn + store_hwm = drsuapi.DsReplicaHighWaterMark() + store_hwm.tmp_highest_usn = 0 + store_hwm.reserved_usn = 0 + store_hwm.highest_usn = 0 + store_utdv = None + + binding_str = "ncacn_ip_tcp:%s[spnego,seal]" % server + + drs_conn = drsuapi.drsuapi(binding_str, lp, creds) + + bind_info = drsuapi.DsBindInfoCtr() + bind_info.length = 28 + bind_info.info = drsuapi.DsBindInfo28() + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_BASE + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7 + bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT + (info, drs_handle) = drs_conn.DsBind(misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID), bind_info) + + null_guid = misc.GUID() + + naming_context = drsuapi.DsReplicaObjectIdentifier() + naming_context.dn = dn + highwatermark = store_hwm + uptodateness_vector = None + if store_utdv is not None: + uptodateness_vector = drsuapi.DsReplicaCursorCtrEx() + if store_utdv.version == 1: + uptodateness_vector.cursors = store_utdv.cursors + elif store_utdv.version == 2: + cursors = [] + for i in range(0, store_utdv.ctr.count): + cursor = drsuapi.DsReplicaCursor() + cursor.source_dsa_invocation_id = store_utdv.ctr.cursors[i].source_dsa_invocation_id + cursor.highest_usn = store_utdv.ctr.cursors[i].highest_usn + cursors.append(cursor) + uptodateness_vector.cursors = cursors + + req8 = drsuapi.DsGetNCChangesRequest8() + + req8.destination_dsa_guid = null_guid + req8.source_dsa_invocation_id = null_guid + req8.naming_context = naming_context + req8.highwatermark = highwatermark + req8.uptodateness_vector = uptodateness_vector + req8.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC | + drsuapi.DRSUAPI_DRS_PER_SYNC | + drsuapi.DRSUAPI_DRS_GET_ANC | + drsuapi.DRSUAPI_DRS_NEVER_SYNCED | + drsuapi.DRSUAPI_DRS_WRIT_REP) + req8.max_object_count = 402 + req8.max_ndr_size = 402116 + req8.extended_op = 0 + req8.fsmo_info = 0 + req8.partial_attribute_set = None + req8.partial_attribute_set_ex = None + req8.mapping_ctr.num_mappings = 0 + req8.mapping_ctr.mappings = None + + user_session_key = drs_conn.user_session_key + + print "# starting at usn[%d]" % (highwatermark.highest_usn) + + while True: + (level, ctr) = drs_conn.DsGetNCChanges(drs_handle, 8, req8) + if ctr.first_object == None and ctr.object_count != 0: + raise RuntimeError("DsGetNCChanges: NULL first_object with object_count=%u" % (ctr.object_count)) + + obj_item = ctr.first_object + while obj_item is not None: + obj = obj_item.object + + if obj.identifier is None: + obj_item = obj_item.next_object + continue + + #print '%s' % obj.identifier.dn + + is_deleted = False + for i in range(0, obj.attribute_ctr.num_attributes): + attr = obj.attribute_ctr.attributes[i] + if attr.attid == drsuapi.DRSUAPI_ATTID_isDeleted: + is_deleted = True + if is_deleted: + obj_item = obj_item.next_object + continue + + spl_crypt = None + attvals = None + for i in range(0, obj.attribute_ctr.num_attributes): + attr = obj.attribute_ctr.attributes[i] + if attr.attid == attid: + attvals = [] + for j in range(0, attr.value_ctr.num_values): + assert attr.value_ctr.values[j].blob is not None + attvals.append(attr.value_ctr.values[j].blob) + if attr.attid != drsuapi.DRSUAPI_ATTID_supplementalCredentials: + continue + assert attr.value_ctr.num_values <= 1 + if attr.value_ctr.num_values == 0: + break + assert attr.value_ctr.values[0].blob is not None + spl_crypt = attr.value_ctr.values[0].blob + break + + if spl_crypt is None: + obj_item = obj_item.next_object + continue + + assert len(spl_crypt) >= 20 + confounder = spl_crypt[0:16] + enc_buffer = spl_crypt[16:] + + m5 = hashlib.md5() + m5.update(user_session_key) + m5.update(confounder) + enc_key = m5.digest() + + rc4 = Crypto.Cipher.ARC4.new(enc_key) + plain_buffer = rc4.decrypt(enc_buffer) + + (crc32_v) = struct.unpack("<L", plain_buffer[0:4]) + attr_val = plain_buffer[4:] + crc32_c = binascii.crc32(attr_val) & 0xffffffff + assert int(crc32_v[0]) == int(crc32_c), "CRC32 0x%08X != 0x%08X" % (crc32_v[0], crc32_c) + + spl = ndr_unpack(drsblobs.supplementalCredentialsBlob, attr_val) + + #print '%s' % ndr_print(spl) + + cleartext_hex = None + + for i in range(0, spl.sub.num_packages): + pkg = spl.sub.packages[i] + if pkg.name != "Primary:CLEARTEXT": + continue + cleartext_hex = pkg.data + + if cleartext_hex is not None: + cleartext_utf16 = binascii.a2b_hex(cleartext_hex) + cleartext_unicode = unicode(cleartext_utf16, 'utf-16-le') + cleartext_utf8 = cleartext_unicode.encode('utf-8') + + gls.add_attr(obj.identifier.dn, cleartext_name, [cleartext_utf8]) + + if attvals is not None: + gls.add_attr(obj.identifier.dn, attname, attvals) + + krb5_old_hex = None + + for i in range(0, spl.sub.num_packages): + pkg = spl.sub.packages[i] + if pkg.name != "Primary:Kerberos": + continue + krb5_old_hex = pkg.data + + if krb5_old_hex is not None: + krb5_old_raw = binascii.a2b_hex(krb5_old_hex) + krb5_old = ndr_unpack(drsblobs.package_PrimaryKerberosBlob, krb5_old_raw, allow_remaining=True) + + #print '%s' % ndr_print(krb5_old) + + krb5_new_hex = None + + for i in range(0, spl.sub.num_packages): + pkg = spl.sub.packages[i] + if pkg.name != "Primary:Kerberos-Newer-Keys": + continue + krb5_new_hex = pkg.data + + if krb5_new_hex is not None: + krb5_new_raw = binascii.a2b_hex(krb5_new_hex) + krb5_new = ndr_unpack(drsblobs.package_PrimaryKerberosBlob, krb5_new_raw, allow_remaining=True) + + #print '%s' % ndr_print(krb5_new) + + obj_item = obj_item.next_object + + gls.print_all() + + if ctr.more_data == 0: + store_hwm = ctr.new_highwatermark + + store_utdv = drsblobs.replUpToDateVectorBlob() + store_utdv.version = ctr.uptodateness_vector.version + store_utdv_ctr = store_utdv.ctr + store_utdv_ctr.count = ctr.uptodateness_vector.count + store_utdv_ctr.cursors = ctr.uptodateness_vector.cursors + store_utdv.ctr = store_utdv_ctr + + #print "%s" % ndr_print(store_hwm) + #print "%s" % ndr_print(store_utdv) + + store_hwm_blob = ndr_pack(store_hwm) + store_utdv_blob = ndr_pack(store_utdv) + + # + # uint32_t version '1' + # uint32_t dn_str_len + # uint32_t dn_str_ofs + # uint32_t hwm_blob_len + # uint32_t hwm_blob_ofs + # uint32_t utdv_blob_len + # uint32_t utdv_blob_ofs + store_hdr_len = 7 * 4 + dn_ofs = store_hdr_len + hwm_ofs = dn_ofs + len(dn) + utdv_ofs = hwm_ofs + len(store_hwm_blob) + store_blob = struct.pack("<LLLLLLL", 1, \ + len(dn), dn_ofs, + len(store_hwm_blob), hwm_ofs, \ + len(store_utdv_blob), utdv_ofs) + \ + dn + store_hwm_blob + store_utdv_blob + + tmp_file = "%s.tmp" % cookie_file + f = open(tmp_file, 'wb') + f.write(store_blob) + f.close() + os.rename(tmp_file, cookie_file) + + print "# up to usn[%d]" % (ctr.new_highwatermark.highest_usn) + break + print "# up to tmp_usn[%d]" % (ctr.new_highwatermark.highest_usn) + req8.highwatermark.tmp_highest_usn = ctr.new_highwatermark.tmp_highest_usn |