s4:repl_cleartext_pwd.py: add 'attmode' parameter to convert the attname to utf8
[Samba.git] / source4 / scripting / devel / repl_cleartext_pwd.py
blobd4ad6f0846dd1099192df2fdb091831ee25cf271
1 #!/usr/bin/env python
3 # Copyright Stefan Metzmacher 2011-2012
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 # This is useful to sync passwords from an AD domain.
20 # $
21 # $ source4/scripting/devel/repl_cleartext_pwd.py \
22 # -Uadministrator%A1b2C3d4 \
23 # 172.31.9.219 DC=bla,DC=base /tmp/cookie cleartext_utf8 131085 displayName
24 # # starting at usn[0]
25 # dn: CN=Test User1,CN=Users,DC=bla,DC=base
26 # cleartext_utf8: A1b2C3d4
27 # displayName:: VABlAHMAdAAgAFUAcwBlAHIAMQA=
29 # # up to usn[16449]
30 # $
31 # $ source4/scripting/devel/repl_cleartext_pwd.py \
32 # -Uadministrator%A1b2C3d4
33 # 172.31.9.219 DC=bla,DC=base cookie_file cleartext_utf8 131085 displayName
34 # # starting at usn[16449]
35 # # up to usn[16449]
36 # $
39 import sys
41 # Find right direction when running from source tree
42 sys.path.insert(0, "bin/python")
44 import samba.getopt as options
45 from optparse import OptionParser
47 from samba.dcerpc import drsuapi, drsblobs, misc
48 from samba.ndr import ndr_pack, ndr_unpack, ndr_print
50 import binascii
51 import hashlib
52 import Crypto.Cipher.ARC4
53 import struct
54 import os
56 from ldif import LDIFWriter
58 class globals:
59 def __init__(self):
60 self.global_objs = {}
61 self.ldif = LDIFWriter(sys.stdout)
63 def add_attr(self, dn, attname, vals):
64 if dn not in self.global_objs:
65 self.global_objs[dn] = {}
66 self.global_objs[dn][attname] = vals
68 def print_all(self):
69 for dn, obj in self.global_objs.items():
70 self.ldif.unparse(dn, obj)
71 continue
72 self.global_objs = {}
74 def attid_equal(a1,a2):
75 return (a1 & 0xffffffff) == (a2 & 0xffffffff)
77 ########### main code ###########
78 if __name__ == "__main__":
79 parser = OptionParser("repl_cleartext_pwd.py [options] server dn cookie_file cleartext_name [attid attname attmode]")
80 sambaopts = options.SambaOptions(parser)
81 credopts = options.CredentialsOptions(parser)
82 parser.add_option_group(credopts)
84 (opts, args) = parser.parse_args()
86 if len(args) == 4:
87 pass
88 elif len(args) >= 7:
89 pass
90 else:
91 parser.error("more arguments required - given=%d" % (len(args)))
93 server = args[0]
94 dn = args[1]
95 cookie_file = args[2]
96 if len(cookie_file) == 0:
97 cookie_file = None
98 cleartext_name = args[3]
99 if len(args) >= 7:
100 try:
101 attid = int(args[4], 16)
102 except:
103 attid = int(args[4])
104 attname = args[5]
105 attmode = args[6]
106 if attmode not in ["raw", "utf8"]:
107 parser.error("attmode should be 'raw' or 'utf8'")
108 else:
109 attid = -1
110 attname = None
111 attmode = "raw"
113 lp = sambaopts.get_loadparm()
114 creds = credopts.get_credentials(lp)
116 if not creds.authentication_requested():
117 parser.error("You must supply credentials")
119 gls = globals()
120 try:
121 f = open(cookie_file, 'r')
122 store_blob = f.read()
123 f.close()
125 store_hdr = store_blob[0:28]
126 (store_version, \
127 store_dn_len, store_dn_ofs, \
128 store_hwm_len, store_hwm_ofs, \
129 store_utdv_len, store_utdv_ofs) = \
130 struct.unpack("<LLLLLLL", store_hdr)
132 store_dn = store_blob[store_dn_ofs:store_dn_ofs+store_dn_len]
133 store_hwm_blob = store_blob[store_hwm_ofs:store_hwm_ofs+store_hwm_len]
134 store_utdv_blob = store_blob[store_utdv_ofs:store_utdv_ofs+store_utdv_len]
136 store_hwm = ndr_unpack(drsuapi.DsReplicaHighWaterMark, store_hwm_blob)
137 store_utdv = ndr_unpack(drsblobs.replUpToDateVectorBlob, store_utdv_blob)
139 assert store_dn == dn
140 #print "%s" % ndr_print(store_hwm)
141 #print "%s" % ndr_print(store_utdv)
142 except:
143 store_dn = dn
144 store_hwm = drsuapi.DsReplicaHighWaterMark()
145 store_hwm.tmp_highest_usn = 0
146 store_hwm.reserved_usn = 0
147 store_hwm.highest_usn = 0
148 store_utdv = None
150 binding_str = "ncacn_ip_tcp:%s[spnego,seal]" % server
152 drs_conn = drsuapi.drsuapi(binding_str, lp, creds)
154 bind_info = drsuapi.DsBindInfoCtr()
155 bind_info.length = 28
156 bind_info.info = drsuapi.DsBindInfo28()
157 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_BASE
158 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION
159 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI
160 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2
161 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS
162 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1
163 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION
164 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE
165 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2
166 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION
167 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2
168 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD
169 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND
170 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO
171 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION
172 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01
173 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP
174 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY
175 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3
176 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2
177 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6
178 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS
179 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8
180 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5
181 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6
182 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3
183 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7
184 bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT
185 (info, drs_handle) = drs_conn.DsBind(misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID), bind_info)
187 null_guid = misc.GUID()
189 naming_context = drsuapi.DsReplicaObjectIdentifier()
190 naming_context.dn = dn
191 highwatermark = store_hwm
192 uptodateness_vector = None
193 if store_utdv is not None:
194 uptodateness_vector = drsuapi.DsReplicaCursorCtrEx()
195 if store_utdv.version == 1:
196 uptodateness_vector.cursors = store_utdv.cursors
197 elif store_utdv.version == 2:
198 cursors = []
199 for i in range(0, store_utdv.ctr.count):
200 cursor = drsuapi.DsReplicaCursor()
201 cursor.source_dsa_invocation_id = store_utdv.ctr.cursors[i].source_dsa_invocation_id
202 cursor.highest_usn = store_utdv.ctr.cursors[i].highest_usn
203 cursors.append(cursor)
204 uptodateness_vector.cursors = cursors
206 req8 = drsuapi.DsGetNCChangesRequest8()
208 req8.destination_dsa_guid = null_guid
209 req8.source_dsa_invocation_id = null_guid
210 req8.naming_context = naming_context
211 req8.highwatermark = highwatermark
212 req8.uptodateness_vector = uptodateness_vector
213 req8.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
214 drsuapi.DRSUAPI_DRS_PER_SYNC |
215 drsuapi.DRSUAPI_DRS_GET_ANC |
216 drsuapi.DRSUAPI_DRS_NEVER_SYNCED |
217 drsuapi.DRSUAPI_DRS_WRIT_REP)
218 req8.max_object_count = 402
219 req8.max_ndr_size = 402116
220 req8.extended_op = 0
221 req8.fsmo_info = 0
222 req8.partial_attribute_set = None
223 req8.partial_attribute_set_ex = None
224 req8.mapping_ctr.num_mappings = 0
225 req8.mapping_ctr.mappings = None
227 user_session_key = drs_conn.user_session_key
229 print "# starting at usn[%d]" % (highwatermark.highest_usn)
231 while True:
232 (level, ctr) = drs_conn.DsGetNCChanges(drs_handle, 8, req8)
233 if ctr.first_object == None and ctr.object_count != 0:
234 raise RuntimeError("DsGetNCChanges: NULL first_object with object_count=%u" % (ctr.object_count))
236 obj_item = ctr.first_object
237 while obj_item is not None:
238 obj = obj_item.object
240 if obj.identifier is None:
241 obj_item = obj_item.next_object
242 continue
244 #print '%s' % obj.identifier.dn
246 is_deleted = False
247 for i in range(0, obj.attribute_ctr.num_attributes):
248 attr = obj.attribute_ctr.attributes[i]
249 if attid_equal(attr.attid, drsuapi.DRSUAPI_ATTID_isDeleted):
250 is_deleted = True
251 if is_deleted:
252 obj_item = obj_item.next_object
253 continue
255 spl_crypt = None
256 attvals = None
257 for i in range(0, obj.attribute_ctr.num_attributes):
258 attr = obj.attribute_ctr.attributes[i]
259 if attid_equal(attr.attid, attid):
260 attvals = []
261 for j in range(0, attr.value_ctr.num_values):
262 assert attr.value_ctr.values[j].blob is not None
263 val_raw = attr.value_ctr.values[j].blob
264 val = None
265 if attmode == "utf8":
266 val_unicode = unicode(val_raw, 'utf-16-le')
267 val = val_unicode.encode('utf-8')
268 elif attmode == "raw":
269 val = val_raw
270 else:
271 assert False, "attmode[%s]" % attmode
272 attvals.append(val)
273 if not attid_equal(attr.attid, drsuapi.DRSUAPI_ATTID_supplementalCredentials):
274 continue
275 assert attr.value_ctr.num_values <= 1
276 if attr.value_ctr.num_values == 0:
277 break
278 assert attr.value_ctr.values[0].blob is not None
279 spl_crypt = attr.value_ctr.values[0].blob
281 if spl_crypt is None:
282 obj_item = obj_item.next_object
283 continue
285 assert len(spl_crypt) >= 20
286 confounder = spl_crypt[0:16]
287 enc_buffer = spl_crypt[16:]
289 m5 = hashlib.md5()
290 m5.update(user_session_key)
291 m5.update(confounder)
292 enc_key = m5.digest()
294 rc4 = Crypto.Cipher.ARC4.new(enc_key)
295 plain_buffer = rc4.decrypt(enc_buffer)
297 (crc32_v) = struct.unpack("<L", plain_buffer[0:4])
298 attr_val = plain_buffer[4:]
299 crc32_c = binascii.crc32(attr_val) & 0xffffffff
300 assert int(crc32_v[0]) == int(crc32_c), "CRC32 0x%08X != 0x%08X" % (crc32_v[0], crc32_c)
302 spl = ndr_unpack(drsblobs.supplementalCredentialsBlob, attr_val)
304 #print '%s' % ndr_print(spl)
306 cleartext_hex = None
308 for i in range(0, spl.sub.num_packages):
309 pkg = spl.sub.packages[i]
310 if pkg.name != "Primary:CLEARTEXT":
311 continue
312 cleartext_hex = pkg.data
314 if cleartext_hex is not None:
315 cleartext_utf16 = binascii.a2b_hex(cleartext_hex)
316 cleartext_unicode = unicode(cleartext_utf16, 'utf-16-le')
317 cleartext_utf8 = cleartext_unicode.encode('utf-8')
319 gls.add_attr(obj.identifier.dn, cleartext_name, [cleartext_utf8])
321 if attvals is not None:
322 gls.add_attr(obj.identifier.dn, attname, attvals)
324 krb5_old_hex = None
326 for i in range(0, spl.sub.num_packages):
327 pkg = spl.sub.packages[i]
328 if pkg.name != "Primary:Kerberos":
329 continue
330 krb5_old_hex = pkg.data
332 if krb5_old_hex is not None:
333 krb5_old_raw = binascii.a2b_hex(krb5_old_hex)
334 krb5_old = ndr_unpack(drsblobs.package_PrimaryKerberosBlob, krb5_old_raw, allow_remaining=True)
336 #print '%s' % ndr_print(krb5_old)
338 krb5_new_hex = None
340 for i in range(0, spl.sub.num_packages):
341 pkg = spl.sub.packages[i]
342 if pkg.name != "Primary:Kerberos-Newer-Keys":
343 continue
344 krb5_new_hex = pkg.data
346 if krb5_new_hex is not None:
347 krb5_new_raw = binascii.a2b_hex(krb5_new_hex)
348 krb5_new = ndr_unpack(drsblobs.package_PrimaryKerberosBlob, krb5_new_raw, allow_remaining=True)
350 #print '%s' % ndr_print(krb5_new)
352 obj_item = obj_item.next_object
354 gls.print_all()
356 if ctr.more_data == 0:
357 store_hwm = ctr.new_highwatermark
359 store_utdv = drsblobs.replUpToDateVectorBlob()
360 store_utdv.version = ctr.uptodateness_vector.version
361 store_utdv_ctr = store_utdv.ctr
362 store_utdv_ctr.count = ctr.uptodateness_vector.count
363 store_utdv_ctr.cursors = ctr.uptodateness_vector.cursors
364 store_utdv.ctr = store_utdv_ctr
366 #print "%s" % ndr_print(store_hwm)
367 #print "%s" % ndr_print(store_utdv)
369 store_hwm_blob = ndr_pack(store_hwm)
370 store_utdv_blob = ndr_pack(store_utdv)
373 # uint32_t version '1'
374 # uint32_t dn_str_len
375 # uint32_t dn_str_ofs
376 # uint32_t hwm_blob_len
377 # uint32_t hwm_blob_ofs
378 # uint32_t utdv_blob_len
379 # uint32_t utdv_blob_ofs
380 store_hdr_len = 7 * 4
381 dn_ofs = store_hdr_len
382 hwm_ofs = dn_ofs + len(dn)
383 utdv_ofs = hwm_ofs + len(store_hwm_blob)
384 store_blob = struct.pack("<LLLLLLL", 1, \
385 len(dn), dn_ofs,
386 len(store_hwm_blob), hwm_ofs, \
387 len(store_utdv_blob), utdv_ofs) + \
388 dn + store_hwm_blob + store_utdv_blob
390 tmp_file = "%s.tmp" % cookie_file
391 f = open(tmp_file, 'wb')
392 f.write(store_blob)
393 f.close()
394 os.rename(tmp_file, cookie_file)
396 print "# up to usn[%d]" % (ctr.new_highwatermark.highest_usn)
397 break
398 print "# up to tmp_usn[%d]" % (ctr.new_highwatermark.highest_usn)
399 req8.highwatermark.tmp_highest_usn = ctr.new_highwatermark.tmp_highest_usn