[GLUE] Rsync SAMBA_3_0 SVN r25598 in order to create the v3-0-test branch.
[Samba.git] / source / libads / sasl.c
blob0067a19d3b602156f1fd3c226ca222a4cbd6dcf0
1 /*
2 Unix SMB/CIFS implementation.
3 ads sasl code
4 Copyright (C) Andrew Tridgell 2001
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include "includes.h"
23 #ifdef HAVE_LDAP
25 /*
26 perform a LDAP/SASL/SPNEGO/NTLMSSP bind (just how many layers can
27 we fit on one socket??)
29 static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
31 DATA_BLOB msg1 = data_blob(NULL, 0);
32 DATA_BLOB blob = data_blob(NULL, 0);
33 DATA_BLOB blob_in = data_blob(NULL, 0);
34 DATA_BLOB blob_out = data_blob(NULL, 0);
35 struct berval cred, *scred = NULL;
36 int rc;
37 NTSTATUS nt_status;
38 int turn = 1;
40 struct ntlmssp_state *ntlmssp_state;
42 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_client_start(&ntlmssp_state))) {
43 return ADS_ERROR_NT(nt_status);
45 ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN;
47 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_username(ntlmssp_state, ads->auth.user_name))) {
48 return ADS_ERROR_NT(nt_status);
50 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_domain(ntlmssp_state, ads->auth.realm))) {
51 return ADS_ERROR_NT(nt_status);
53 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_password(ntlmssp_state, ads->auth.password))) {
54 return ADS_ERROR_NT(nt_status);
57 blob_in = data_blob(NULL, 0);
59 do {
60 nt_status = ntlmssp_update(ntlmssp_state,
61 blob_in, &blob_out);
62 data_blob_free(&blob_in);
63 if ((NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)
64 || NT_STATUS_IS_OK(nt_status))
65 && blob_out.length) {
66 if (turn == 1) {
67 /* and wrap it in a SPNEGO wrapper */
68 msg1 = gen_negTokenInit(OID_NTLMSSP, blob_out);
69 } else {
70 /* wrap it in SPNEGO */
71 msg1 = spnego_gen_auth(blob_out);
74 data_blob_free(&blob_out);
76 cred.bv_val = (char *)msg1.data;
77 cred.bv_len = msg1.length;
78 scred = NULL;
79 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
80 data_blob_free(&msg1);
81 if ((rc != LDAP_SASL_BIND_IN_PROGRESS) && (rc != 0)) {
82 if (scred) {
83 ber_bvfree(scred);
86 ntlmssp_end(&ntlmssp_state);
87 return ADS_ERROR(rc);
89 if (scred) {
90 blob = data_blob(scred->bv_val, scred->bv_len);
91 ber_bvfree(scred);
92 } else {
93 blob = data_blob(NULL, 0);
96 } else {
98 ntlmssp_end(&ntlmssp_state);
99 data_blob_free(&blob_out);
100 return ADS_ERROR_NT(nt_status);
103 if ((turn == 1) &&
104 (rc == LDAP_SASL_BIND_IN_PROGRESS)) {
105 DATA_BLOB tmp_blob = data_blob(NULL, 0);
106 /* the server might give us back two challenges */
107 if (!spnego_parse_challenge(blob, &blob_in,
108 &tmp_blob)) {
110 ntlmssp_end(&ntlmssp_state);
111 data_blob_free(&blob);
112 DEBUG(3,("Failed to parse challenges\n"));
113 return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
115 data_blob_free(&tmp_blob);
116 } else if (rc == LDAP_SASL_BIND_IN_PROGRESS) {
117 if (!spnego_parse_auth_response(blob, nt_status,
118 &blob_in)) {
120 ntlmssp_end(&ntlmssp_state);
121 data_blob_free(&blob);
122 DEBUG(3,("Failed to parse auth response\n"));
123 return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
126 data_blob_free(&blob);
127 data_blob_free(&blob_out);
128 turn++;
129 } while (rc == LDAP_SASL_BIND_IN_PROGRESS && !NT_STATUS_IS_OK(nt_status));
131 /* we have a reference conter on ntlmssp_state, if we are signing
132 then the state will be kept by the signing engine */
134 ntlmssp_end(&ntlmssp_state);
136 return ADS_ERROR(rc);
139 #ifdef HAVE_KRB5
141 perform a LDAP/SASL/SPNEGO/KRB5 bind
143 static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads, const char *principal)
145 DATA_BLOB blob = data_blob(NULL, 0);
146 struct berval cred, *scred = NULL;
147 DATA_BLOB session_key = data_blob(NULL, 0);
148 int rc;
150 rc = spnego_gen_negTokenTarg(principal, ads->auth.time_offset, &blob, &session_key, 0,
151 &ads->auth.tgs_expire);
153 if (rc) {
154 return ADS_ERROR_KRB5(rc);
157 /* now send the auth packet and we should be done */
158 cred.bv_val = (char *)blob.data;
159 cred.bv_len = blob.length;
161 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
163 data_blob_free(&blob);
164 data_blob_free(&session_key);
165 if(scred)
166 ber_bvfree(scred);
168 return ADS_ERROR(rc);
170 #endif
173 this performs a SASL/SPNEGO bind
175 static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
177 struct berval *scred=NULL;
178 int rc, i;
179 ADS_STATUS status;
180 DATA_BLOB blob;
181 char *principal = NULL;
182 char *OIDs[ASN1_MAX_OIDS];
183 #ifdef HAVE_KRB5
184 BOOL got_kerberos_mechanism = False;
185 #endif
187 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", NULL, NULL, NULL, &scred);
189 if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
190 status = ADS_ERROR(rc);
191 goto failed;
194 blob = data_blob(scred->bv_val, scred->bv_len);
196 ber_bvfree(scred);
198 #if 0
199 file_save("sasl_spnego.dat", blob.data, blob.length);
200 #endif
202 /* the server sent us the first part of the SPNEGO exchange in the negprot
203 reply */
204 if (!spnego_parse_negTokenInit(blob, OIDs, &principal)) {
205 data_blob_free(&blob);
206 status = ADS_ERROR(LDAP_OPERATIONS_ERROR);
207 goto failed;
209 data_blob_free(&blob);
211 /* make sure the server understands kerberos */
212 for (i=0;OIDs[i];i++) {
213 DEBUG(3,("ads_sasl_spnego_bind: got OID=%s\n", OIDs[i]));
214 #ifdef HAVE_KRB5
215 if (strcmp(OIDs[i], OID_KERBEROS5_OLD) == 0 ||
216 strcmp(OIDs[i], OID_KERBEROS5) == 0) {
217 got_kerberos_mechanism = True;
219 #endif
220 free(OIDs[i]);
222 DEBUG(3,("ads_sasl_spnego_bind: got server principal name = %s\n", principal));
224 #ifdef HAVE_KRB5
225 if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) &&
226 got_kerberos_mechanism)
228 /* I've seen a child Windows 2000 domain not send
229 the principal name back in the first round of
230 the SASL bind reply. So we guess based on server
231 name and realm. --jerry */
232 if ( !principal ) {
233 if ( ads->server.realm && ads->server.ldap_server ) {
234 char *server, *server_realm;
236 server = SMB_STRDUP( ads->server.ldap_server );
237 server_realm = SMB_STRDUP( ads->server.realm );
239 if ( !server || !server_realm )
240 return ADS_ERROR(LDAP_NO_MEMORY);
242 strlower_m( server );
243 strupper_m( server_realm );
244 asprintf( &principal, "ldap/%s@%s", server, server_realm );
246 SAFE_FREE( server );
247 SAFE_FREE( server_realm );
249 if ( !principal )
250 return ADS_ERROR(LDAP_NO_MEMORY);
255 status = ads_sasl_spnego_krb5_bind(ads, principal);
256 if (ADS_ERR_OK(status)) {
257 SAFE_FREE(principal);
258 return status;
261 DEBUG(10,("ads_sasl_spnego_krb5_bind failed with: %s, "
262 "calling kinit\n", ads_errstr(status)));
264 status = ADS_ERROR_KRB5(ads_kinit_password(ads));
266 if (ADS_ERR_OK(status)) {
267 status = ads_sasl_spnego_krb5_bind(ads, principal);
270 /* only fallback to NTLMSSP if allowed */
271 if (ADS_ERR_OK(status) ||
272 !(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
273 SAFE_FREE(principal);
274 return status;
277 #endif
279 SAFE_FREE(principal);
281 /* lets do NTLMSSP ... this has the big advantage that we don't need
282 to sync clocks, and we don't rely on special versions of the krb5
283 library for HMAC_MD4 encryption */
284 return ads_sasl_spnego_ntlmssp_bind(ads);
286 failed:
287 return status;
290 #ifdef HAVE_GSSAPI
291 #define MAX_GSS_PASSES 3
293 /* this performs a SASL/gssapi bind
294 we avoid using cyrus-sasl to make Samba more robust. cyrus-sasl
295 is very dependent on correctly configured DNS whereas
296 this routine is much less fragile
297 see RFC2078 and RFC2222 for details
299 static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
301 uint32 minor_status;
302 gss_name_t serv_name;
303 gss_buffer_desc input_name;
304 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
305 gss_OID mech_type = GSS_C_NULL_OID;
306 gss_buffer_desc output_token, input_token;
307 uint32 ret_flags, conf_state;
308 struct berval cred;
309 struct berval *scred = NULL;
310 int i=0;
311 int gss_rc, rc;
312 uint8 *p;
313 uint32 max_msg_size = 0;
314 char *sname = NULL;
315 ADS_STATUS status;
316 krb5_principal principal = NULL;
317 krb5_context ctx = NULL;
318 krb5_enctype enc_types[] = {
319 #ifdef ENCTYPE_ARCFOUR_HMAC
320 ENCTYPE_ARCFOUR_HMAC,
321 #endif
322 ENCTYPE_DES_CBC_MD5,
323 ENCTYPE_NULL};
324 gss_OID_desc nt_principal =
325 {10, CONST_DISCARD(char *, "\052\206\110\206\367\022\001\002\002\002")};
327 /* we need to fetch a service ticket as the ldap user in the
328 servers realm, regardless of our realm */
329 asprintf(&sname, "ldap/%s@%s", ads->config.ldap_server_name, ads->config.realm);
331 initialize_krb5_error_table();
332 status = ADS_ERROR_KRB5(krb5_init_context(&ctx));
333 if (!ADS_ERR_OK(status)) {
334 SAFE_FREE(sname);
335 return status;
337 status = ADS_ERROR_KRB5(krb5_set_default_tgs_ktypes(ctx, enc_types));
338 if (!ADS_ERR_OK(status)) {
339 SAFE_FREE(sname);
340 krb5_free_context(ctx);
341 return status;
343 status = ADS_ERROR_KRB5(smb_krb5_parse_name(ctx, sname, &principal));
344 if (!ADS_ERR_OK(status)) {
345 SAFE_FREE(sname);
346 krb5_free_context(ctx);
347 return status;
350 input_name.value = &principal;
351 input_name.length = sizeof(principal);
353 gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &serv_name);
356 * The MIT libraries have a *HORRIBLE* bug - input_value.value needs
357 * to point to the *address* of the krb5_principal, and the gss libraries
358 * to a shallow copy of the krb5_principal pointer - so we need to keep
359 * the krb5_principal around until we do the gss_release_name. MIT *SUCKS* !
360 * Just one more way in which MIT engineers screwed me over.... JRA.
363 SAFE_FREE(sname);
365 if (gss_rc) {
366 krb5_free_principal(ctx, principal);
367 krb5_free_context(ctx);
368 return ADS_ERROR_GSS(gss_rc, minor_status);
371 input_token.value = NULL;
372 input_token.length = 0;
374 for (i=0; i < MAX_GSS_PASSES; i++) {
375 gss_rc = gss_init_sec_context(&minor_status,
376 GSS_C_NO_CREDENTIAL,
377 &context_handle,
378 serv_name,
379 mech_type,
380 GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
382 NULL,
383 &input_token,
384 NULL,
385 &output_token,
386 &ret_flags,
387 NULL);
389 if (input_token.value) {
390 gss_release_buffer(&minor_status, &input_token);
393 if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
394 status = ADS_ERROR_GSS(gss_rc, minor_status);
395 goto failed;
398 cred.bv_val = (char *)output_token.value;
399 cred.bv_len = output_token.length;
401 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL,
402 &scred);
403 if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
404 status = ADS_ERROR(rc);
405 goto failed;
408 if (output_token.value) {
409 gss_release_buffer(&minor_status, &output_token);
412 if (scred) {
413 input_token.value = scred->bv_val;
414 input_token.length = scred->bv_len;
415 } else {
416 input_token.value = NULL;
417 input_token.length = 0;
420 if (gss_rc == 0) break;
423 gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token,
424 (int *)&conf_state,NULL);
425 if (gss_rc) {
426 status = ADS_ERROR_GSS(gss_rc, minor_status);
427 goto failed;
430 gss_release_buffer(&minor_status, &input_token);
432 p = (uint8 *)output_token.value;
434 #if 0
435 file_save("sasl_gssapi.dat", output_token.value, output_token.length);
436 #endif
438 if (p) {
439 max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3];
442 gss_release_buffer(&minor_status, &output_token);
444 output_token.value = SMB_MALLOC(strlen(ads->config.bind_path) + 8);
445 p = (uint8 *)output_token.value;
447 *p++ = 1; /* no sign & seal selection */
448 /* choose the same size as the server gave us */
449 *p++ = max_msg_size>>16;
450 *p++ = max_msg_size>>8;
451 *p++ = max_msg_size;
452 snprintf((char *)p, strlen(ads->config.bind_path)+4, "dn:%s", ads->config.bind_path);
453 p += strlen((const char *)p);
455 output_token.length = PTR_DIFF(p, output_token.value);
457 gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
458 &output_token, (int *)&conf_state,
459 &input_token);
460 if (gss_rc) {
461 status = ADS_ERROR_GSS(gss_rc, minor_status);
462 goto failed;
465 free(output_token.value);
467 cred.bv_val = (char *)input_token.value;
468 cred.bv_len = input_token.length;
470 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL,
471 &scred);
472 status = ADS_ERROR(rc);
474 gss_release_buffer(&minor_status, &input_token);
476 failed:
478 gss_release_name(&minor_status, &serv_name);
479 if (context_handle != GSS_C_NO_CONTEXT)
480 gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
481 krb5_free_principal(ctx, principal);
482 krb5_free_context(ctx);
484 if(scred)
485 ber_bvfree(scred);
486 return status;
488 #endif /* HAVE_GGSAPI */
490 /* mapping between SASL mechanisms and functions */
491 static struct {
492 const char *name;
493 ADS_STATUS (*fn)(ADS_STRUCT *);
494 } sasl_mechanisms[] = {
495 {"GSS-SPNEGO", ads_sasl_spnego_bind},
496 #ifdef HAVE_GSSAPI
497 {"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */
498 #endif
499 {NULL, NULL}
502 ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
504 const char *attrs[] = {"supportedSASLMechanisms", NULL};
505 char **values;
506 ADS_STATUS status;
507 int i, j;
508 LDAPMessage *res;
510 /* get a list of supported SASL mechanisms */
511 status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
512 if (!ADS_ERR_OK(status)) return status;
514 values = ldap_get_values(ads->ld, res, "supportedSASLMechanisms");
516 /* try our supported mechanisms in order */
517 for (i=0;sasl_mechanisms[i].name;i++) {
518 /* see if the server supports it */
519 for (j=0;values && values[j];j++) {
520 if (strcmp(values[j], sasl_mechanisms[i].name) == 0) {
521 DEBUG(4,("Found SASL mechanism %s\n", values[j]));
522 status = sasl_mechanisms[i].fn(ads);
523 ldap_value_free(values);
524 ldap_msgfree(res);
525 return status;
530 ldap_value_free(values);
531 ldap_msgfree(res);
532 return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED);
535 #endif /* HAVE_LDAP */