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 3 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, see <http://www.gnu.org/licenses/>.
24 #include "libnet/libnet.h"
25 #include "libcli/auth/libcli_auth.h"
26 #include "auth/gensec/gensec.h"
27 #include "auth/credentials/credentials.h"
28 #include "auth/gensec/schannel_proto.h"
29 #include "librpc/gen_ndr/ndr_netlogon.h"
30 #include "librpc/gen_ndr/ndr_netlogon_c.h"
31 #include "param/param.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
,
42 enum netr_SamDatabaseID database
,
43 struct netr_DELTA_ENUM
*delta
,
47 uint32_t rid
= delta
->delta_id_union
.rid
;
48 struct netr_DELTA_USER
*user
= delta
->delta_union
.user
;
49 struct samr_Password lm_hash
;
50 struct samr_Password nt_hash
;
51 const char *username
= user
->account_name
.string
;
54 if (user
->lm_password_present
) {
55 sam_rid_crypt(rid
, user
->lmpassword
.hash
, lm_hash
.hash
, 0);
56 user
->lmpassword
= lm_hash
;
59 if (user
->nt_password_present
) {
60 sam_rid_crypt(rid
, user
->ntpassword
.hash
, nt_hash
.hash
, 0);
61 user
->ntpassword
= nt_hash
;
65 if (user
->user_private_info
.SensitiveData
) {
67 struct netr_USER_KEYS keys
;
68 enum ndr_err_code ndr_err
;
69 data
.data
= user
->user_private_info
.SensitiveData
;
70 data
.length
= user
->user_private_info
.DataLength
;
71 creds_arcfour_crypt(creds
, data
.data
, data
.length
);
72 user
->user_private_info
.SensitiveData
= data
.data
;
73 user
->user_private_info
.DataLength
= data
.length
;
75 ndr_err
= ndr_pull_struct_blob(&data
, mem_ctx
, NULL
, &keys
, (ndr_pull_flags_fn_t
)ndr_pull_netr_USER_KEYS
);
76 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
77 *error_string
= talloc_asprintf(mem_ctx
, "Failed to parse Sensitive Data for %s:", username
);
78 dump_data(10, data
.data
, data
.length
);
79 return ndr_map_error2ntstatus(ndr_err
);
82 if (keys
.keys
.keys2
.lmpassword
.length
== 16) {
84 sam_rid_crypt(rid
, keys
.keys
.keys2
.lmpassword
.pwd
.hash
, lm_hash
.hash
, 0);
85 user
->lmpassword
= lm_hash
;
87 user
->lmpassword
= keys
.keys
.keys2
.lmpassword
.pwd
;
89 user
->lm_password_present
= true;
91 if (keys
.keys
.keys2
.ntpassword
.length
== 16) {
93 sam_rid_crypt(rid
, keys
.keys
.keys2
.ntpassword
.pwd
.hash
, nt_hash
.hash
, 0);
94 user
->ntpassword
= nt_hash
;
96 user
->ntpassword
= keys
.keys
.keys2
.ntpassword
.pwd
;
98 user
->nt_password_present
= true;
100 /* TODO: rid decrypt history fields */
106 * Decrypt and extract the secrets
108 * The writes decrypted secrets back into the structure
110 static NTSTATUS
fix_secret(TALLOC_CTX
*mem_ctx
,
111 struct creds_CredentialState
*creds
,
112 enum netr_SamDatabaseID database
,
113 struct netr_DELTA_ENUM
*delta
,
116 struct netr_DELTA_SECRET
*secret
= delta
->delta_union
.secret
;
117 creds_arcfour_crypt(creds
, secret
->current_cipher
.cipher_data
,
118 secret
->current_cipher
.maxlen
);
120 creds_arcfour_crypt(creds
, secret
->old_cipher
.cipher_data
,
121 secret
->old_cipher
.maxlen
);
127 * Fix up the delta, dealing with encryption issues so that the final
128 * callback need only do the printing or application logic
131 static NTSTATUS
fix_delta(TALLOC_CTX
*mem_ctx
,
132 struct creds_CredentialState
*creds
,
134 enum netr_SamDatabaseID database
,
135 struct netr_DELTA_ENUM
*delta
,
138 NTSTATUS nt_status
= NT_STATUS_OK
;
139 *error_string
= NULL
;
140 switch (delta
->delta_type
) {
141 case NETR_DELTA_USER
:
143 nt_status
= fix_user(mem_ctx
,
151 case NETR_DELTA_SECRET
:
153 nt_status
= fix_secret(mem_ctx
,
166 NTSTATUS
libnet_SamSync_netlogon(struct libnet_context
*ctx
, TALLOC_CTX
*mem_ctx
, struct libnet_SamSync
*r
)
168 NTSTATUS nt_status
, dbsync_nt_status
;
169 TALLOC_CTX
*samsync_ctx
, *loop_ctx
, *delta_ctx
;
170 struct creds_CredentialState
*creds
;
171 struct netr_DatabaseSync dbsync
;
172 struct netr_Authenticator credential
, return_authenticator
;
173 struct netr_DELTA_ENUM_ARRAY
*delta_enum_array
= NULL
;
174 struct cli_credentials
*machine_account
;
175 struct dcerpc_pipe
*p
;
176 struct libnet_context
*machine_net_ctx
;
177 struct libnet_RpcConnect
*c
;
178 struct libnet_SamSync_state
*state
;
179 const enum netr_SamDatabaseID database_ids
[] = {SAM_DATABASE_DOMAIN
, SAM_DATABASE_BUILTIN
, SAM_DATABASE_PRIVS
};
182 samsync_ctx
= talloc_named(mem_ctx
, 0, "SamSync top context");
184 if (!r
->in
.machine_account
) {
185 machine_account
= cli_credentials_init(samsync_ctx
);
186 if (!machine_account
) {
187 talloc_free(samsync_ctx
);
188 return NT_STATUS_NO_MEMORY
;
190 cli_credentials_set_conf(machine_account
, ctx
->lp_ctx
);
191 nt_status
= cli_credentials_set_machine_account(machine_account
, ctx
->lp_ctx
);
192 if (!NT_STATUS_IS_OK(nt_status
)) {
193 r
->out
.error_string
= talloc_strdup(mem_ctx
, "Could not obtain machine account password - are we joined to the domain?");
194 talloc_free(samsync_ctx
);
198 machine_account
= r
->in
.machine_account
;
201 /* We cannot do this unless we are a BDC. Check, before we get odd errors later */
202 if (cli_credentials_get_secure_channel_type(machine_account
) != SEC_CHAN_BDC
) {
204 = talloc_asprintf(mem_ctx
,
205 "Our join to domain %s is not as a BDC (%d), please rejoin as a BDC",
206 cli_credentials_get_domain(machine_account
),
207 cli_credentials_get_secure_channel_type(machine_account
));
208 talloc_free(samsync_ctx
);
209 return NT_STATUS_CANT_ACCESS_DOMAIN_INFO
;
212 c
= talloc(samsync_ctx
, struct libnet_RpcConnect
);
214 r
->out
.error_string
= NULL
;
215 talloc_free(samsync_ctx
);
216 return NT_STATUS_NO_MEMORY
;
219 c
->level
= LIBNET_RPC_CONNECT_DC_INFO
;
220 if (r
->in
.binding_string
) {
221 c
->in
.binding
= r
->in
.binding_string
;
224 c
->in
.binding
= NULL
;
225 c
->in
.name
= cli_credentials_get_domain(machine_account
);
228 /* prepare connect to the NETLOGON pipe of PDC */
229 c
->in
.dcerpc_iface
= &ndr_table_netlogon
;
231 /* We must do this as the machine, not as any command-line
232 * user. So we override the credentials in the
234 machine_net_ctx
= talloc(samsync_ctx
, struct libnet_context
);
235 if (!machine_net_ctx
) {
236 r
->out
.error_string
= NULL
;
237 talloc_free(samsync_ctx
);
238 return NT_STATUS_NO_MEMORY
;
240 *machine_net_ctx
= *ctx
;
241 machine_net_ctx
->cred
= machine_account
;
243 /* connect to the NETLOGON pipe of the PDC */
244 nt_status
= libnet_RpcConnect(machine_net_ctx
, samsync_ctx
, c
);
245 if (!NT_STATUS_IS_OK(nt_status
)) {
246 if (r
->in
.binding_string
) {
247 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
248 "Connection to NETLOGON pipe of DC %s failed: %s",
249 r
->in
.binding_string
, c
->out
.error_string
);
251 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
252 "Connection to NETLOGON pipe of DC for %s failed: %s",
253 c
->in
.name
, c
->out
.error_string
);
255 talloc_free(samsync_ctx
);
259 /* This makes a new pipe, on which we can do schannel. We
260 * should do this in the RpcConnect code, but the abstaction
261 * layers do not suit yet */
263 nt_status
= dcerpc_secondary_connection(c
->out
.dcerpc_pipe
, &p
,
264 c
->out
.dcerpc_pipe
->binding
);
266 if (!NT_STATUS_IS_OK(nt_status
)) {
267 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
268 "Secondary connection to NETLOGON pipe of DC %s failed: %s",
269 dcerpc_server_name(p
), nt_errstr(nt_status
));
270 talloc_free(samsync_ctx
);
274 nt_status
= dcerpc_bind_auth_schannel(samsync_ctx
, p
, &ndr_table_netlogon
,
275 machine_account
, ctx
->lp_ctx
, DCERPC_AUTH_LEVEL_PRIVACY
);
277 if (!NT_STATUS_IS_OK(nt_status
)) {
278 r
->out
.error_string
= talloc_asprintf(mem_ctx
,
279 "SCHANNEL authentication to NETLOGON pipe of DC %s failed: %s",
280 dcerpc_server_name(p
), nt_errstr(nt_status
));
281 talloc_free(samsync_ctx
);
285 state
= talloc(samsync_ctx
, struct libnet_SamSync_state
);
287 r
->out
.error_string
= NULL
;
288 talloc_free(samsync_ctx
);
292 state
->domain_name
= c
->out
.domain_name
;
293 state
->domain_sid
= c
->out
.domain_sid
;
294 state
->realm
= c
->out
.realm
;
295 state
->domain_guid
= c
->out
.guid
;
296 state
->machine_net_ctx
= machine_net_ctx
;
297 state
->netlogon_pipe
= p
;
299 /* initialise the callback layer. It may wish to contact the
300 * server with ldap, now we know the name */
304 nt_status
= r
->in
.init_fn(samsync_ctx
,
308 if (!NT_STATUS_IS_OK(nt_status
)) {
309 r
->out
.error_string
= talloc_steal(mem_ctx
, error_string
);
310 talloc_free(samsync_ctx
);
315 /* get NETLOGON credentials */
317 nt_status
= dcerpc_schannel_creds(p
->conn
->security_state
.generic_state
, samsync_ctx
, &creds
);
318 if (!NT_STATUS_IS_OK(nt_status
)) {
319 r
->out
.error_string
= talloc_strdup(mem_ctx
, "Could not obtain NETLOGON credentials from DCERPC/GENSEC layer");
320 talloc_free(samsync_ctx
);
324 /* Setup details for the synchronisation */
326 ZERO_STRUCT(return_authenticator
);
328 dbsync
.in
.logon_server
= talloc_asprintf(samsync_ctx
, "\\\\%s", dcerpc_server_name(p
));
329 dbsync
.in
.computername
= cli_credentials_get_workstation(machine_account
);
330 dbsync
.in
.preferredmaximumlength
= (uint32_t)-1;
331 dbsync
.in
.return_authenticator
= &return_authenticator
;
332 dbsync
.out
.return_authenticator
= &return_authenticator
;
333 dbsync
.out
.delta_enum_array
= &delta_enum_array
;
335 for (i
=0;i
< ARRAY_SIZE(database_ids
); i
++) {
337 uint32_t sync_context
= 0;
339 dbsync
.in
.database_id
= database_ids
[i
];
340 dbsync
.in
.sync_context
= &sync_context
;
341 dbsync
.out
.sync_context
= &sync_context
;
345 loop_ctx
= talloc_named(samsync_ctx
, 0, "DatabaseSync loop context");
346 creds_client_authenticator(creds
, &credential
);
348 dbsync
.in
.credential
= &credential
;
350 dbsync_nt_status
= dcerpc_netr_DatabaseSync(p
, loop_ctx
, &dbsync
);
351 if (!NT_STATUS_IS_OK(dbsync_nt_status
) &&
352 !NT_STATUS_EQUAL(dbsync_nt_status
, STATUS_MORE_ENTRIES
)) {
353 r
->out
.error_string
= talloc_asprintf(mem_ctx
, "DatabaseSync failed - %s", nt_errstr(nt_status
));
354 talloc_free(samsync_ctx
);
358 if (!creds_client_check(creds
, &dbsync
.out
.return_authenticator
->cred
)) {
359 r
->out
.error_string
= talloc_strdup(mem_ctx
, "Credential chaining on incoming DatabaseSync failed");
360 talloc_free(samsync_ctx
);
361 return NT_STATUS_ACCESS_DENIED
;
364 dbsync
.in
.sync_context
= dbsync
.out
.sync_context
;
366 /* For every single remote 'delta' entry: */
367 for (d
=0; d
< delta_enum_array
->num_deltas
; d
++) {
368 char *error_string
= NULL
;
369 delta_ctx
= talloc_named(loop_ctx
, 0, "DatabaseSync delta context");
370 /* 'Fix' elements, by decrypting and
371 * de-obfuscating the data */
372 nt_status
= fix_delta(delta_ctx
,
375 dbsync
.in
.database_id
,
376 &delta_enum_array
->delta_enum
[d
],
378 if (!NT_STATUS_IS_OK(nt_status
)) {
379 r
->out
.error_string
= talloc_steal(mem_ctx
, error_string
);
380 talloc_free(samsync_ctx
);
384 /* Now call the callback. This will
385 * do something like print the data or
387 nt_status
= r
->in
.delta_fn(delta_ctx
,
389 dbsync
.in
.database_id
,
390 &delta_enum_array
->delta_enum
[d
],
392 if (!NT_STATUS_IS_OK(nt_status
)) {
393 r
->out
.error_string
= talloc_steal(mem_ctx
, error_string
);
394 talloc_free(samsync_ctx
);
397 talloc_free(delta_ctx
);
399 talloc_free(loop_ctx
);
400 } while (NT_STATUS_EQUAL(dbsync_nt_status
, STATUS_MORE_ENTRIES
));
402 if (!NT_STATUS_IS_OK(dbsync_nt_status
)) {
403 r
->out
.error_string
= talloc_asprintf(mem_ctx
, "libnet_SamSync_netlogon failed: unexpected inconsistancy. Should not get error %s here", nt_errstr(nt_status
));
404 talloc_free(samsync_ctx
);
405 return dbsync_nt_status
;
407 nt_status
= NT_STATUS_OK
;
409 talloc_free(samsync_ctx
);