2 Unix SMB/CIFS implementation.
4 Extract the user/system database from a remote SamSync server
6 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 #include "libnet/libnet.h"
26 #include "libcli/auth/libcli_auth.h"
27 #include "auth/gensec/gensec.h"
28 #include "auth/credentials/credentials.h"
29 #include "auth/gensec/schannel_proto.h"
30 #include "librpc/gen_ndr/ndr_netlogon.h"
31 #include "librpc/gen_ndr/ndr_netlogon_c.h"
35 * Decrypt and extract the user's passwords.
37 * The writes decrypted (no longer 'RID encrypted' or arcfour encrypted) passwords back into the structure
39 static NTSTATUS
fix_user(TALLOC_CTX
*mem_ctx
,
40 struct creds_CredentialState
*creds
,
41 enum netr_SamDatabaseID database
,
42 struct netr_DELTA_ENUM
*delta
,
46 uint32_t rid
= delta
->delta_id_union
.rid
;
47 struct netr_DELTA_USER
*user
= delta
->delta_union
.user
;
48 struct samr_Password lm_hash
;
49 struct samr_Password nt_hash
;
50 const char *username
= user
->account_name
.string
;
53 if (user
->lm_password_present
) {
54 sam_rid_crypt(rid
, user
->lmpassword
.hash
, lm_hash
.hash
, 0);
55 user
->lmpassword
= lm_hash
;
58 if (user
->nt_password_present
) {
59 sam_rid_crypt(rid
, user
->ntpassword
.hash
, nt_hash
.hash
, 0);
60 user
->ntpassword
= nt_hash
;
63 if (user
->user_private_info
.SensitiveData
) {
65 struct netr_USER_KEYS keys
;
66 data
.data
= user
->user_private_info
.SensitiveData
;
67 data
.length
= user
->user_private_info
.DataLength
;
68 creds_arcfour_crypt(creds
, data
.data
, data
.length
);
69 user
->user_private_info
.SensitiveData
= data
.data
;
70 user
->user_private_info
.DataLength
= data
.length
;
72 nt_status
= ndr_pull_struct_blob(&data
, mem_ctx
, &keys
, (ndr_pull_flags_fn_t
)ndr_pull_netr_USER_KEYS
);
73 if (NT_STATUS_IS_OK(nt_status
)) {
74 if (keys
.keys
.keys2
.lmpassword
.length
== 16) {
75 sam_rid_crypt(rid
, keys
.keys
.keys2
.lmpassword
.pwd
.hash
, lm_hash
.hash
, 0);
76 user
->lmpassword
= lm_hash
;
77 user
->lm_password_present
= True
;
79 if (keys
.keys
.keys2
.ntpassword
.length
== 16) {
80 sam_rid_crypt(rid
, keys
.keys
.keys2
.ntpassword
.pwd
.hash
, nt_hash
.hash
, 0);
81 user
->ntpassword
= nt_hash
;
82 user
->nt_password_present
= True
;
85 *error_string
= talloc_asprintf(mem_ctx
, "Failed to parse Sensitive Data for %s:", username
);
86 dump_data(10, data
.data
, data
.length
);
94 * Decrypt and extract the secrets
96 * The writes decrypted secrets back into the structure
98 static NTSTATUS
fix_secret(TALLOC_CTX
*mem_ctx
,
99 struct creds_CredentialState
*creds
,
100 enum netr_SamDatabaseID database
,
101 struct netr_DELTA_ENUM
*delta
,
104 struct netr_DELTA_SECRET
*secret
= delta
->delta_union
.secret
;
105 creds_arcfour_crypt(creds
, secret
->current_cipher
.cipher_data
,
106 secret
->current_cipher
.maxlen
);
108 creds_arcfour_crypt(creds
, secret
->old_cipher
.cipher_data
,
109 secret
->old_cipher
.maxlen
);
115 * Fix up the delta, dealing with encryption issues so that the final
116 * callback need only do the printing or application logic
119 static NTSTATUS
fix_delta(TALLOC_CTX
*mem_ctx
,
120 struct creds_CredentialState
*creds
,
121 enum netr_SamDatabaseID database
,
122 struct netr_DELTA_ENUM
*delta
,
125 NTSTATUS nt_status
= NT_STATUS_OK
;
126 *error_string
= NULL
;
127 switch (delta
->delta_type
) {
128 case NETR_DELTA_USER
:
130 nt_status
= fix_user(mem_ctx
,
137 case NETR_DELTA_SECRET
:
139 nt_status
= fix_secret(mem_ctx
,
152 NTSTATUS
libnet_SamSync_netlogon(struct libnet_context
*ctx
, TALLOC_CTX
*mem_ctx
, struct libnet_SamSync
*r
)
154 NTSTATUS nt_status
, dbsync_nt_status
;
155 TALLOC_CTX
*samsync_ctx
, *loop_ctx
, *delta_ctx
;
156 struct creds_CredentialState
*creds
;
157 struct netr_DatabaseSync dbsync
;
158 struct cli_credentials
*machine_account
;
159 struct dcerpc_pipe
*p
;
160 struct libnet_context
*machine_net_ctx
;
161 struct libnet_RpcConnect
*c
;
162 struct libnet_SamSync_state
*state
;
163 const enum netr_SamDatabaseID database_ids
[] = {SAM_DATABASE_DOMAIN
, SAM_DATABASE_BUILTIN
, SAM_DATABASE_PRIVS
};
166 samsync_ctx
= talloc_named(mem_ctx
, 0, "SamSync top context");
168 if (!r
->in
.machine_account
) {
169 machine_account
= cli_credentials_init(samsync_ctx
);
170 if (!machine_account
) {
171 talloc_free(samsync_ctx
);
172 return NT_STATUS_NO_MEMORY
;
174 cli_credentials_set_conf(machine_account
);
175 nt_status
= cli_credentials_set_machine_account(machine_account
);
176 if (!NT_STATUS_IS_OK(nt_status
)) {
177 r
->out
.error_string
= talloc_strdup(mem_ctx
, "Could not obtain machine account password - are we joined to the domain?");
178 talloc_free(samsync_ctx
);
182 machine_account
= r
->in
.machine_account
;
185 /* We cannot do this unless we are a BDC. Check, before we get odd errors later */
186 if (cli_credentials_get_secure_channel_type(machine_account
) != SEC_CHAN_BDC
) {
188 = talloc_asprintf(mem_ctx
,
189 "Our join to domain %s is not as a BDC (%d), please rejoin as a BDC",
190 cli_credentials_get_domain(machine_account
),
191 cli_credentials_get_secure_channel_type(machine_account
));
192 talloc_free(samsync_ctx
);
193 return NT_STATUS_CANT_ACCESS_DOMAIN_INFO
;
196 c
= talloc(samsync_ctx
, struct libnet_RpcConnect
);
198 r
->out
.error_string
= NULL
;
199 talloc_free(samsync_ctx
);
200 return NT_STATUS_NO_MEMORY
;
203 c
->level
= LIBNET_RPC_CONNECT_DC_INFO
;
204 if (r
->in
.binding_string
) {
205 c
->in
.binding
= r
->in
.binding_string
;
208 c
->in
.binding
= NULL
;
209 c
->in
.name
= cli_credentials_get_domain(machine_account
);
212 /* prepare connect to the NETLOGON pipe of PDC */
213 c
->in
.dcerpc_iface
= &dcerpc_table_netlogon
;
215 /* We must do this as the machine, not as any command-line
216 * user. So we override the credentials in the
218 machine_net_ctx
= talloc(samsync_ctx
, struct libnet_context
);
219 if (!machine_net_ctx
) {
220 r
->out
.error_string
= NULL
;
221 talloc_free(samsync_ctx
);
222 return NT_STATUS_NO_MEMORY
;
224 *machine_net_ctx
= *ctx
;
225 machine_net_ctx
->cred
= machine_account
;
227 /* connect to the NETLOGON pipe of the PDC */
228 nt_status
= libnet_RpcConnect(machine_net_ctx
, samsync_ctx
, c
);
229 if (!NT_STATUS_IS_OK(nt_status
)) {
230 if (r
->in
.binding_string
) {
231 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
232 "Connection to NETLOGON pipe of DC %s failed: %s",
233 r
->in
.binding_string
, c
->out
.error_string
);
235 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
236 "Connection to NETLOGON pipe of DC for %s failed: %s",
237 c
->in
.name
, c
->out
.error_string
);
239 talloc_free(samsync_ctx
);
243 /* This makes a new pipe, on which we can do schannel. We
244 * should do this in the RpcConnect code, but the abstaction
245 * layers do not suit yet */
247 nt_status
= dcerpc_secondary_connection(c
->out
.dcerpc_pipe
, &p
,
248 c
->out
.dcerpc_pipe
->binding
);
250 if (!NT_STATUS_IS_OK(nt_status
)) {
251 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
252 "Secondary connection to NETLOGON pipe of DC %s failed: %s",
253 dcerpc_server_name(p
), nt_errstr(nt_status
));
254 talloc_free(samsync_ctx
);
258 nt_status
= dcerpc_bind_auth_schannel(samsync_ctx
, p
, &dcerpc_table_netlogon
,
259 machine_account
, DCERPC_AUTH_LEVEL_PRIVACY
);
261 if (!NT_STATUS_IS_OK(nt_status
)) {
262 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
263 "SCHANNEL authentication to NETLOGON pipe of DC %s failed: %s",
264 dcerpc_server_name(p
), nt_errstr(nt_status
));
265 talloc_free(samsync_ctx
);
269 state
= talloc(samsync_ctx
, struct libnet_SamSync_state
);
271 r
->out
.error_string
= NULL
;
272 talloc_free(samsync_ctx
);
276 state
->domain_name
= c
->out
.domain_name
;
277 state
->domain_sid
= c
->out
.domain_sid
;
278 state
->realm
= c
->out
.realm
;
279 state
->domain_guid
= c
->out
.guid
;
280 state
->machine_net_ctx
= machine_net_ctx
;
281 state
->netlogon_pipe
= p
;
283 /* initialise the callback layer. It may wish to contact the
284 * server with ldap, now we know the name */
288 nt_status
= r
->in
.init_fn(samsync_ctx
,
292 if (!NT_STATUS_IS_OK(nt_status
)) {
293 r
->out
.error_string
= talloc_steal(mem_ctx
, error_string
);
294 talloc_free(samsync_ctx
);
299 /* get NETLOGON credentails */
301 nt_status
= dcerpc_schannel_creds(p
->conn
->security_state
.generic_state
, samsync_ctx
, &creds
);
302 if (!NT_STATUS_IS_OK(nt_status
)) {
303 r
->out
.error_string
= talloc_strdup(mem_ctx
, "Could not obtain NETLOGON credentials from DCERPC/GENSEC layer");
304 talloc_free(samsync_ctx
);
308 /* Setup details for the synchronisation */
309 dbsync
.in
.logon_server
= talloc_asprintf(samsync_ctx
, "\\\\%s", dcerpc_server_name(p
));
310 dbsync
.in
.computername
= cli_credentials_get_workstation(machine_account
);
311 dbsync
.in
.preferredmaximumlength
= (uint32_t)-1;
312 ZERO_STRUCT(dbsync
.in
.return_authenticator
);
314 for (i
=0;i
< ARRAY_SIZE(database_ids
); i
++) {
315 dbsync
.in
.sync_context
= 0;
316 dbsync
.in
.database_id
= database_ids
[i
];
320 loop_ctx
= talloc_named(samsync_ctx
, 0, "DatabaseSync loop context");
321 creds_client_authenticator(creds
, &dbsync
.in
.credential
);
323 dbsync_nt_status
= dcerpc_netr_DatabaseSync(p
, loop_ctx
, &dbsync
);
324 if (!NT_STATUS_IS_OK(dbsync_nt_status
) &&
325 !NT_STATUS_EQUAL(dbsync_nt_status
, STATUS_MORE_ENTRIES
)) {
326 r
->out
.error_string
= talloc_asprintf(mem_ctx
, "DatabaseSync failed - %s", nt_errstr(nt_status
));
327 talloc_free(samsync_ctx
);
331 if (!creds_client_check(creds
, &dbsync
.out
.return_authenticator
.cred
)) {
332 r
->out
.error_string
= talloc_strdup(mem_ctx
, "Credential chaining on incoming DatabaseSync failed");
333 talloc_free(samsync_ctx
);
334 return NT_STATUS_ACCESS_DENIED
;
337 dbsync
.in
.sync_context
= dbsync
.out
.sync_context
;
339 /* For every single remote 'delta' entry: */
340 for (d
=0; d
< dbsync
.out
.delta_enum_array
->num_deltas
; d
++) {
341 char *error_string
= NULL
;
342 delta_ctx
= talloc_named(loop_ctx
, 0, "DatabaseSync delta context");
343 /* 'Fix' elements, by decrypting and
344 * de-obfuscating the data */
345 nt_status
= fix_delta(delta_ctx
,
347 dbsync
.in
.database_id
,
348 &dbsync
.out
.delta_enum_array
->delta_enum
[d
],
350 if (!NT_STATUS_IS_OK(nt_status
)) {
351 r
->out
.error_string
= talloc_steal(mem_ctx
, error_string
);
352 talloc_free(samsync_ctx
);
356 /* Now call the callback. This will
357 * do something like print the data or
359 nt_status
= r
->in
.delta_fn(delta_ctx
,
361 dbsync
.in
.database_id
,
362 &dbsync
.out
.delta_enum_array
->delta_enum
[d
],
364 if (!NT_STATUS_IS_OK(nt_status
)) {
365 r
->out
.error_string
= talloc_steal(mem_ctx
, error_string
);
366 talloc_free(samsync_ctx
);
369 talloc_free(delta_ctx
);
371 talloc_free(loop_ctx
);
372 } while (NT_STATUS_EQUAL(dbsync_nt_status
, STATUS_MORE_ENTRIES
));
374 if (!NT_STATUS_IS_OK(dbsync_nt_status
)) {
375 r
->out
.error_string
= talloc_asprintf(mem_ctx
, "libnet_SamSync_netlogon failed: unexpected inconsistancy. Should not get error %s here", nt_errstr(nt_status
));
376 talloc_free(samsync_ctx
);
377 return dbsync_nt_status
;
379 nt_status
= NT_STATUS_OK
;
381 talloc_free(samsync_ctx
);