a31c1287a71c65192ce4a9f5033abd774c15dc7e
[garming/samba-autobuild/.git] / source4 / scripting / devel / repl_cleartext_pwd.py
1 #!/usr/bin/env python
2 #
3 # Copyright Stefan Metzmacher 2011-2012
4 #
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.
9 #
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.
14 #
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/>.
17 #
18 # This is useful to sync passwords from an AD domain.
19 #
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=
28 #
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 #  $
37 #
38
39 from __future__ import print_function
40 import sys
41
42 # Find right direction when running from source tree
43 sys.path.insert(0, "bin/python")
44
45 import samba.getopt as options
46 from optparse import OptionParser
47
48 from samba.dcerpc import drsuapi, drsblobs, misc
49 from samba.ndr import ndr_pack, ndr_unpack, ndr_print
50
51 import binascii
52 import hashlib
53 import Crypto.Cipher.ARC4
54 import struct
55 import os
56
57 from ldif import LDIFWriter
58
59
60 class globals:
61     def __init__(self):
62         self.global_objs = {}
63         self.ldif = LDIFWriter(sys.stdout)
64
65     def add_attr(self, dn, attname, vals):
66         if dn not in self.global_objs:
67             self.global_objs[dn] = {}
68         self.global_objs[dn][attname] = vals
69
70     def print_all(self):
71         for dn, obj in self.global_objs.items():
72             self.ldif.unparse(dn, obj)
73             continue
74         self.global_objs = {}
75
76
77 def attid_equal(a1, a2):
78     return (a1 & 0xffffffff) == (a2 & 0xffffffff)
79
80 ########### main code ###########
81 if __name__ == "__main__":
82     parser = OptionParser("repl_cleartext_pwd.py [options] server dn cookie_file clear_utf8_name [attid attname attmode] [clear_utf16_name")
83     sambaopts = options.SambaOptions(parser)
84     credopts = options.CredentialsOptions(parser)
85     parser.add_option_group(credopts)
86
87     (opts, args) = parser.parse_args()
88
89     if len(args) == 4:
90         pass
91     elif len(args) == 7:
92         pass
93     elif len(args) >= 8:
94         pass
95     else:
96         parser.error("more arguments required - given=%d" % (len(args)))
97
98     server = args[0]
99     dn = args[1]
100     cookie_file = args[2]
101     if len(cookie_file) == 0:
102         cookie_file = None
103     clear_utf8_name = args[3]
104     if len(args) >= 7:
105         try:
106             attid = int(args[4], 16)
107         except Exception:
108             attid = int(args[4])
109         attname = args[5]
110         attmode = args[6]
111         if attmode not in ["raw", "utf8"]:
112             parser.error("attmode should be 'raw' or 'utf8'")
113     else:
114         attid = -1
115         attname = None
116         attmode = "raw"
117     if len(args) >= 8:
118         clear_utf16_name = args[7]
119     else:
120         clear_utf16_name = None
121
122     lp = sambaopts.get_loadparm()
123     creds = credopts.get_credentials(lp)
124
125     if not creds.authentication_requested():
126         parser.error("You must supply credentials")
127
128     gls = globals()
129     try:
130         f = open(cookie_file, 'r')
131         store_blob = f.read()
132         f.close()
133
134         store_hdr = store_blob[0:28]
135         (store_version, \
136          store_dn_len, store_dn_ofs, \
137          store_hwm_len, store_hwm_ofs, \
138          store_utdv_len, store_utdv_ofs) = \
139         struct.unpack("<LLLLLLL", store_hdr)
140
141         store_dn = store_blob[store_dn_ofs:store_dn_ofs + store_dn_len]
142         store_hwm_blob = store_blob[store_hwm_ofs:store_hwm_ofs + store_hwm_len]
143         store_utdv_blob = store_blob[store_utdv_ofs:store_utdv_ofs + store_utdv_len]
144
145         store_hwm = ndr_unpack(drsuapi.DsReplicaHighWaterMark, store_hwm_blob)
146         store_utdv = ndr_unpack(drsblobs.replUpToDateVectorBlob, store_utdv_blob)
147
148         assert store_dn == dn
149         # print "%s" % ndr_print(store_hwm)
150         # print "%s" % ndr_print(store_utdv)
151     except Exception:
152         store_dn = dn
153         store_hwm = drsuapi.DsReplicaHighWaterMark()
154         store_hwm.tmp_highest_usn  = 0
155         store_hwm.reserved_usn     = 0
156         store_hwm.highest_usn      = 0
157         store_utdv = None
158
159     binding_str = "ncacn_ip_tcp:%s[spnego,seal]" % server
160
161     drs_conn = drsuapi.drsuapi(binding_str, lp, creds)
162
163     bind_info = drsuapi.DsBindInfoCtr()
164     bind_info.length = 28
165     bind_info.info = drsuapi.DsBindInfo28()
166     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_BASE
167     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION
168     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI
169     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2
170     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS
171     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1
172     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION
173     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE
174     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2
175     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION
176     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2
177     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD
178     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND
179     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO
180     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION
181     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01
182     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP
183     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY
184     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3
185     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2
186     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6
187     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS
188     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8
189     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5
190     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6
191     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3
192     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7
193     bind_info.info.supported_extensions |= drsuapi.DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT
194     (info, drs_handle) = drs_conn.DsBind(misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID), bind_info)
195
196     null_guid = misc.GUID()
197
198     naming_context = drsuapi.DsReplicaObjectIdentifier()
199     naming_context.dn              = dn
200     highwatermark                  = store_hwm
201     uptodateness_vector            = None
202     if store_utdv is not None:
203         uptodateness_vector = drsuapi.DsReplicaCursorCtrEx()
204         if store_utdv.version == 1:
205             uptodateness_vector.cursors = store_utdv.cursors
206         elif store_utdv.version == 2:
207             cursors = []
208             for i in range(0, store_utdv.ctr.count):
209                 cursor = drsuapi.DsReplicaCursor()
210                 cursor.source_dsa_invocation_id = store_utdv.ctr.cursors[i].source_dsa_invocation_id
211                 cursor.highest_usn = store_utdv.ctr.cursors[i].highest_usn
212                 cursors.append(cursor)
213             uptodateness_vector.cursors = cursors
214
215     req8 = drsuapi.DsGetNCChangesRequest8()
216
217     req8.destination_dsa_guid           = null_guid
218     req8.source_dsa_invocation_id       = null_guid
219     req8.naming_context                 = naming_context
220     req8.highwatermark                  = highwatermark
221     req8.uptodateness_vector            = uptodateness_vector
222     req8.replica_flags                  = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
223                                            drsuapi.DRSUAPI_DRS_PER_SYNC |
224                                            drsuapi.DRSUAPI_DRS_GET_ANC |
225                                            drsuapi.DRSUAPI_DRS_NEVER_SYNCED |
226                                            drsuapi.DRSUAPI_DRS_WRIT_REP)
227     req8.max_object_count = 402
228     req8.max_ndr_size = 402116
229     req8.extended_op = 0
230     req8.fsmo_info = 0
231     req8.partial_attribute_set = None
232     req8.partial_attribute_set_ex = None
233     req8.mapping_ctr.num_mappings = 0
234     req8.mapping_ctr.mappings = None
235
236     user_session_key = drs_conn.user_session_key
237
238     print("# starting at usn[%d]" % (highwatermark.highest_usn))
239
240     while True:
241         (level, ctr) = drs_conn.DsGetNCChanges(drs_handle, 8, req8)
242         if ctr.first_object == None and ctr.object_count != 0:
243             raise RuntimeError("DsGetNCChanges: NULL first_object with object_count=%u" % (ctr.object_count))
244
245         obj_item = ctr.first_object
246         while obj_item is not None:
247             obj = obj_item.object
248
249             if obj.identifier is None:
250                 obj_item = obj_item.next_object
251                 continue
252
253             # print '%s' % obj.identifier.dn
254
255             is_deleted = False
256             for i in range(0, obj.attribute_ctr.num_attributes):
257                 attr = obj.attribute_ctr.attributes[i]
258                 if attid_equal(attr.attid, drsuapi.DRSUAPI_ATTID_isDeleted):
259                     is_deleted = True
260             if is_deleted:
261                 obj_item = obj_item.next_object
262                 continue
263
264             spl_crypt = None
265             attvals = None
266             for i in range(0, obj.attribute_ctr.num_attributes):
267                 attr = obj.attribute_ctr.attributes[i]
268                 if attid_equal(attr.attid, attid):
269                     attvals = []
270                     for j in range(0, attr.value_ctr.num_values):
271                         assert attr.value_ctr.values[j].blob is not None
272                         val_raw = attr.value_ctr.values[j].blob
273                         val = None
274                         if attmode == "utf8":
275                             val_unicode = unicode(val_raw, 'utf-16-le')
276                             val = val_unicode.encode('utf-8')
277                         elif attmode == "raw":
278                             val = val_raw
279                         else:
280                             assert False, "attmode[%s]" % attmode
281                         attvals.append(val)
282                 if not attid_equal(attr.attid, drsuapi.DRSUAPI_ATTID_supplementalCredentials):
283                     continue
284                 assert attr.value_ctr.num_values <= 1
285                 if attr.value_ctr.num_values == 0:
286                     break
287                 assert attr.value_ctr.values[0].blob is not None
288                 spl_crypt = attr.value_ctr.values[0].blob
289
290             if spl_crypt is None:
291                 obj_item = obj_item.next_object
292                 continue
293
294             assert len(spl_crypt) >= 20
295             confounder = spl_crypt[0:16]
296             enc_buffer = spl_crypt[16:]
297
298             m5 = hashlib.md5()
299             m5.update(user_session_key)
300             m5.update(confounder)
301             enc_key = m5.digest()
302
303             rc4 = Crypto.Cipher.ARC4.new(enc_key)
304             plain_buffer = rc4.decrypt(enc_buffer)
305
306             (crc32_v) = struct.unpack("<L", plain_buffer[0:4])
307             attr_val = plain_buffer[4:]
308             crc32_c = binascii.crc32(attr_val) & 0xffffffff
309             assert int(crc32_v[0]) == int(crc32_c), "CRC32 0x%08X != 0x%08X" % (crc32_v[0], crc32_c)
310
311             spl = ndr_unpack(drsblobs.supplementalCredentialsBlob, attr_val)
312
313             # print '%s' % ndr_print(spl)
314
315             cleartext_hex = None
316
317             for i in range(0, spl.sub.num_packages):
318                 pkg = spl.sub.packages[i]
319                 if pkg.name != "Primary:CLEARTEXT":
320                     continue
321                 cleartext_hex = pkg.data
322
323             if cleartext_hex is not None:
324                 cleartext_utf16 = binascii.a2b_hex(cleartext_hex)
325                 if clear_utf16_name is not None:
326                     gls.add_attr(obj.identifier.dn, clear_utf16_name, [cleartext_utf16])
327                 try:
328                     cleartext_unicode = unicode(cleartext_utf16, 'utf-16-le')
329                     cleartext_utf8 = cleartext_unicode.encode('utf-8')
330                     gls.add_attr(obj.identifier.dn, clear_utf8_name, [cleartext_utf8])
331                 except Exception:
332                     pass
333
334                 if attvals is not None:
335                     gls.add_attr(obj.identifier.dn, attname, attvals)
336
337             krb5_old_hex = None
338
339             for i in range(0, spl.sub.num_packages):
340                 pkg = spl.sub.packages[i]
341                 if pkg.name != "Primary:Kerberos":
342                     continue
343                 krb5_old_hex = pkg.data
344
345             if krb5_old_hex is not None:
346                 krb5_old_raw = binascii.a2b_hex(krb5_old_hex)
347                 krb5_old = ndr_unpack(drsblobs.package_PrimaryKerberosBlob, krb5_old_raw, allow_remaining=True)
348
349                 # print '%s' % ndr_print(krb5_old)
350
351             krb5_new_hex = None
352
353             for i in range(0, spl.sub.num_packages):
354                 pkg = spl.sub.packages[i]
355                 if pkg.name != "Primary:Kerberos-Newer-Keys":
356                     continue
357                 krb5_new_hex = pkg.data
358
359             if krb5_new_hex is not None:
360                 krb5_new_raw = binascii.a2b_hex(krb5_new_hex)
361                 krb5_new = ndr_unpack(drsblobs.package_PrimaryKerberosBlob, krb5_new_raw, allow_remaining=True)
362
363                 # print '%s' % ndr_print(krb5_new)
364
365             obj_item = obj_item.next_object
366
367         gls.print_all()
368
369         if ctr.more_data == 0:
370             store_hwm = ctr.new_highwatermark
371
372             store_utdv = drsblobs.replUpToDateVectorBlob()
373             store_utdv.version = ctr.uptodateness_vector.version
374             store_utdv_ctr = store_utdv.ctr
375             store_utdv_ctr.count = ctr.uptodateness_vector.count
376             store_utdv_ctr.cursors = ctr.uptodateness_vector.cursors
377             store_utdv.ctr = store_utdv_ctr
378
379             # print "%s" % ndr_print(store_hwm)
380             # print "%s" % ndr_print(store_utdv)
381
382             store_hwm_blob = ndr_pack(store_hwm)
383             store_utdv_blob = ndr_pack(store_utdv)
384
385             #
386             # uint32_t version '1'
387             # uint32_t dn_str_len
388             # uint32_t dn_str_ofs
389             # uint32_t hwm_blob_len
390             # uint32_t hwm_blob_ofs
391             # uint32_t utdv_blob_len
392             # uint32_t utdv_blob_ofs
393             store_hdr_len = 7 * 4
394             dn_ofs = store_hdr_len
395             hwm_ofs = dn_ofs + len(dn)
396             utdv_ofs = hwm_ofs + len(store_hwm_blob)
397             store_blob = struct.pack("<LLLLLLL", 1, \
398                                      len(dn), dn_ofs,
399                                      len(store_hwm_blob), hwm_ofs, \
400                                      len(store_utdv_blob), utdv_ofs) + \
401             dn + store_hwm_blob + store_utdv_blob
402
403             tmp_file = "%s.tmp" % cookie_file
404             f = open(tmp_file, 'wb')
405             f.write(store_blob)
406             f.close()
407             os.rename(tmp_file, cookie_file)
408
409             print("# up to usn[%d]" % (ctr.new_highwatermark.highest_usn))
410             break
411         print("# up to tmp_usn[%d]" % (ctr.new_highwatermark.highest_usn))
412         req8.highwatermark = ctr.new_highwatermark