source: branches/samba-3.0/source/libads/sasl.c@ 411

Last change on this file since 411 was 124, checked in by Paul Smedley, 17 years ago

Update source to 3.0.28a

File size: 15.8 KB
Line 
1/*
2 Unix SMB/CIFS implementation.
3 ads sasl code
4 Copyright (C) Andrew Tridgell 2001
5
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.
10
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.
15
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.
19*/
20
21#include "includes.h"
22
23#ifdef HAVE_LDAP
24
25/*
26 perform a LDAP/SASL/SPNEGO/NTLMSSP bind (just how many layers can
27 we fit on one socket??)
28*/
29static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
30{
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;
39
40 struct ntlmssp_state *ntlmssp_state;
41
42 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_client_start(&ntlmssp_state))) {
43 return ADS_ERROR_NT(nt_status);
44 }
45 ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN;
46
47 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_username(ntlmssp_state, ads->auth.user_name))) {
48 return ADS_ERROR_NT(nt_status);
49 }
50 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_domain(ntlmssp_state, ads->auth.realm))) {
51 return ADS_ERROR_NT(nt_status);
52 }
53 if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_password(ntlmssp_state, ads->auth.password))) {
54 return ADS_ERROR_NT(nt_status);
55 }
56
57 blob_in = data_blob(NULL, 0);
58
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);
72 }
73
74 data_blob_free(&blob_out);
75
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);
84 }
85
86 ntlmssp_end(&ntlmssp_state);
87 return ADS_ERROR(rc);
88 }
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);
94 }
95
96 } else {
97
98 ntlmssp_end(&ntlmssp_state);
99 data_blob_free(&blob_out);
100 return ADS_ERROR_NT(nt_status);
101 }
102
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)) {
109
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);
114 }
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)) {
119
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);
124 }
125 }
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));
130
131 /* we have a reference conter on ntlmssp_state, if we are signing
132 then the state will be kept by the signing engine */
133
134 ntlmssp_end(&ntlmssp_state);
135
136 return ADS_ERROR(rc);
137}
138
139#ifdef HAVE_KRB5
140struct ads_service_principal {
141 char *string;
142#ifdef HAVE_GSSAPI
143 gss_name_t name;
144#endif
145};
146
147static void ads_free_service_principal(struct ads_service_principal *p)
148{
149 SAFE_FREE(p->string);
150
151#ifdef HAVE_GSSAPI
152 if (p->name) {
153 uint32 minor_status;
154 gss_release_name(&minor_status, &p->name);
155 }
156#endif
157 ZERO_STRUCTP(p);
158}
159
160static ADS_STATUS ads_generate_service_principal(ADS_STRUCT *ads,
161 const char *given_principal,
162 struct ads_service_principal *p)
163{
164 ADS_STATUS status;
165#ifdef HAVE_GSSAPI
166 gss_buffer_desc input_name;
167 /* GSS_KRB5_NT_PRINCIPAL_NAME */
168 gss_OID_desc nt_principal =
169 {10, CONST_DISCARD(char *, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x01")};
170 uint32 minor_status;
171 int gss_rc;
172#endif
173
174 ZERO_STRUCTP(p);
175
176 /* I've seen a child Windows 2000 domain not send
177 the principal name back in the first round of
178 the SASL bind reply. So we guess based on server
179 name and realm. --jerry */
180 /* Also try best guess when we get the w2k8 ignore
181 principal back - gd */
182
183 if (!given_principal ||
184 strequal(given_principal, ADS_IGNORE_PRINCIPAL)) {
185
186 status = ads_guess_service_principal(ads, &p->string);
187 if (!ADS_ERR_OK(status)) {
188 return status;
189 }
190 } else {
191 p->string = SMB_STRDUP(given_principal);
192 if (!p->string) {
193 return ADS_ERROR(LDAP_NO_MEMORY);
194 }
195 }
196
197#ifdef HAVE_GSSAPI
198 input_name.value = p->string;
199 input_name.length = strlen(p->string);
200
201 gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &p->name);
202 if (gss_rc) {
203 ads_free_service_principal(p);
204 return ADS_ERROR_GSS(gss_rc, minor_status);
205 }
206#endif
207
208 return ADS_SUCCESS;
209}
210
211/*
212 perform a LDAP/SASL/SPNEGO/KRB5 bind
213*/
214static ADS_STATUS ads_sasl_spnego_rawkrb5_bind(ADS_STRUCT *ads, const char *principal)
215{
216 DATA_BLOB blob = data_blob(NULL, 0);
217 struct berval cred, *scred = NULL;
218 DATA_BLOB session_key = data_blob(NULL, 0);
219 int rc;
220
221 rc = spnego_gen_negTokenTarg(principal, ads->auth.time_offset, &blob, &session_key, 0,
222 &ads->auth.tgs_expire);
223
224 if (rc) {
225 return ADS_ERROR_KRB5(rc);
226 }
227
228 /* now send the auth packet and we should be done */
229 cred.bv_val = (char *)blob.data;
230 cred.bv_len = blob.length;
231
232 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
233
234 data_blob_free(&blob);
235 data_blob_free(&session_key);
236 if(scred)
237 ber_bvfree(scred);
238
239 return ADS_ERROR(rc);
240}
241
242static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads,
243 struct ads_service_principal *p)
244{
245 return ads_sasl_spnego_rawkrb5_bind(ads, p->string);
246}
247
248#endif
249
250/*
251 this performs a SASL/SPNEGO bind
252*/
253static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
254{
255 struct berval *scred=NULL;
256 int rc, i;
257 ADS_STATUS status;
258 DATA_BLOB blob;
259 char *given_principal = NULL;
260 char *OIDs[ASN1_MAX_OIDS];
261#ifdef HAVE_KRB5
262 BOOL got_kerberos_mechanism = False;
263#endif
264
265 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSS-SPNEGO", NULL, NULL, NULL, &scred);
266
267 if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
268 status = ADS_ERROR(rc);
269 goto failed;
270 }
271
272 blob = data_blob(scred->bv_val, scred->bv_len);
273
274 ber_bvfree(scred);
275
276#if 0
277 file_save("sasl_spnego.dat", blob.data, blob.length);
278#endif
279
280 /* the server sent us the first part of the SPNEGO exchange in the negprot
281 reply */
282 if (!spnego_parse_negTokenInit(blob, OIDs, &given_principal)) {
283 data_blob_free(&blob);
284 status = ADS_ERROR(LDAP_OPERATIONS_ERROR);
285 goto failed;
286 }
287 data_blob_free(&blob);
288
289 /* make sure the server understands kerberos */
290 for (i=0;OIDs[i];i++) {
291 DEBUG(3,("ads_sasl_spnego_bind: got OID=%s\n", OIDs[i]));
292#ifdef HAVE_KRB5
293 if (strcmp(OIDs[i], OID_KERBEROS5_OLD) == 0 ||
294 strcmp(OIDs[i], OID_KERBEROS5) == 0) {
295 got_kerberos_mechanism = True;
296 }
297#endif
298 free(OIDs[i]);
299 }
300 DEBUG(3,("ads_sasl_spnego_bind: got server principal name = %s\n", given_principal));
301
302#ifdef HAVE_KRB5
303 if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) &&
304 got_kerberos_mechanism)
305 {
306 struct ads_service_principal p;
307
308 status = ads_generate_service_principal(ads, given_principal, &p);
309 SAFE_FREE(given_principal);
310 if (!ADS_ERR_OK(status)) {
311 return status;
312 }
313
314 status = ads_sasl_spnego_krb5_bind(ads, &p);
315 if (ADS_ERR_OK(status)) {
316 ads_free_service_principal(&p);
317 return status;
318 }
319
320 DEBUG(10,("ads_sasl_spnego_krb5_bind failed with: %s, "
321 "calling kinit\n", ads_errstr(status)));
322
323 status = ADS_ERROR_KRB5(ads_kinit_password(ads));
324
325 if (ADS_ERR_OK(status)) {
326 status = ads_sasl_spnego_krb5_bind(ads, &p);
327 if (!ADS_ERR_OK(status)) {
328 DEBUG(0,("kinit succeeded but "
329 "ads_sasl_spnego_krb5_bind failed: %s\n",
330 ads_errstr(status)));
331 }
332 }
333
334 ads_free_service_principal(&p);
335
336 /* only fallback to NTLMSSP if allowed */
337 if (ADS_ERR_OK(status) ||
338 !(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
339 return status;
340 }
341 } else
342#endif
343 {
344 SAFE_FREE(given_principal);
345 }
346
347 /* lets do NTLMSSP ... this has the big advantage that we don't need
348 to sync clocks, and we don't rely on special versions of the krb5
349 library for HMAC_MD4 encryption */
350 return ads_sasl_spnego_ntlmssp_bind(ads);
351
352failed:
353 return status;
354}
355
356#ifdef HAVE_GSSAPI
357#define MAX_GSS_PASSES 3
358
359/* this performs a SASL/gssapi bind
360 we avoid using cyrus-sasl to make Samba more robust. cyrus-sasl
361 is very dependent on correctly configured DNS whereas
362 this routine is much less fragile
363 see RFC2078 and RFC2222 for details
364*/
365static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
366{
367 uint32 minor_status;
368 gss_name_t serv_name;
369 gss_buffer_desc input_name;
370 gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
371 gss_OID mech_type = GSS_C_NULL_OID;
372 gss_buffer_desc output_token, input_token;
373 uint32 ret_flags, conf_state;
374 struct berval cred;
375 struct berval *scred = NULL;
376 int i=0;
377 int gss_rc, rc;
378 uint8 *p;
379 uint32 max_msg_size = 0;
380 char *sname = NULL;
381 ADS_STATUS status;
382 krb5_principal principal = NULL;
383 krb5_context ctx = NULL;
384 krb5_enctype enc_types[] = {
385#ifdef ENCTYPE_ARCFOUR_HMAC
386 ENCTYPE_ARCFOUR_HMAC,
387#endif
388 ENCTYPE_DES_CBC_MD5,
389 ENCTYPE_NULL};
390 gss_OID_desc nt_principal =
391 {10, CONST_DISCARD(char *, "\052\206\110\206\367\022\001\002\002\002")};
392
393 /* we need to fetch a service ticket as the ldap user in the
394 servers realm, regardless of our realm */
395 asprintf(&sname, "ldap/%s@%s", ads->config.ldap_server_name, ads->config.realm);
396
397 initialize_krb5_error_table();
398 status = ADS_ERROR_KRB5(krb5_init_context(&ctx));
399 if (!ADS_ERR_OK(status)) {
400 SAFE_FREE(sname);
401 return status;
402 }
403 status = ADS_ERROR_KRB5(krb5_set_default_tgs_ktypes(ctx, enc_types));
404 if (!ADS_ERR_OK(status)) {
405 SAFE_FREE(sname);
406 krb5_free_context(ctx);
407 return status;
408 }
409 status = ADS_ERROR_KRB5(smb_krb5_parse_name(ctx, sname, &principal));
410 if (!ADS_ERR_OK(status)) {
411 SAFE_FREE(sname);
412 krb5_free_context(ctx);
413 return status;
414 }
415
416 input_name.value = &principal;
417 input_name.length = sizeof(principal);
418
419 gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &serv_name);
420
421 /*
422 * The MIT libraries have a *HORRIBLE* bug - input_value.value needs
423 * to point to the *address* of the krb5_principal, and the gss libraries
424 * to a shallow copy of the krb5_principal pointer - so we need to keep
425 * the krb5_principal around until we do the gss_release_name. MIT *SUCKS* !
426 * Just one more way in which MIT engineers screwed me over.... JRA.
427 */
428
429 SAFE_FREE(sname);
430
431 if (gss_rc) {
432 krb5_free_principal(ctx, principal);
433 krb5_free_context(ctx);
434 return ADS_ERROR_GSS(gss_rc, minor_status);
435 }
436
437 input_token.value = NULL;
438 input_token.length = 0;
439
440 for (i=0; i < MAX_GSS_PASSES; i++) {
441 gss_rc = gss_init_sec_context(&minor_status,
442 GSS_C_NO_CREDENTIAL,
443 &context_handle,
444 serv_name,
445 mech_type,
446 GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
447 0,
448 NULL,
449 &input_token,
450 NULL,
451 &output_token,
452 &ret_flags,
453 NULL);
454
455 if (input_token.value) {
456 gss_release_buffer(&minor_status, &input_token);
457 }
458
459 if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
460 status = ADS_ERROR_GSS(gss_rc, minor_status);
461 goto failed;
462 }
463
464 cred.bv_val = (char *)output_token.value;
465 cred.bv_len = output_token.length;
466
467 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL,
468 &scred);
469 if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
470 status = ADS_ERROR(rc);
471 goto failed;
472 }
473
474 if (output_token.value) {
475 gss_release_buffer(&minor_status, &output_token);
476 }
477
478 if (scred) {
479 input_token.value = scred->bv_val;
480 input_token.length = scred->bv_len;
481 } else {
482 input_token.value = NULL;
483 input_token.length = 0;
484 }
485
486 if (gss_rc == 0) break;
487 }
488
489 gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token,
490 (int *)&conf_state,NULL);
491 if (gss_rc) {
492 status = ADS_ERROR_GSS(gss_rc, minor_status);
493 goto failed;
494 }
495
496 gss_release_buffer(&minor_status, &input_token);
497
498 p = (uint8 *)output_token.value;
499
500#if 0
501 file_save("sasl_gssapi.dat", output_token.value, output_token.length);
502#endif
503
504 if (p) {
505 max_msg_size = (p[1]<<16) | (p[2]<<8) | p[3];
506 }
507
508 gss_release_buffer(&minor_status, &output_token);
509
510 output_token.value = SMB_MALLOC(strlen(ads->config.bind_path) + 8);
511 p = (uint8 *)output_token.value;
512
513 *p++ = 1; /* no sign & seal selection */
514 /* choose the same size as the server gave us */
515 *p++ = max_msg_size>>16;
516 *p++ = max_msg_size>>8;
517 *p++ = max_msg_size;
518 snprintf((char *)p, strlen(ads->config.bind_path)+4, "dn:%s", ads->config.bind_path);
519 p += strlen((const char *)p);
520
521 output_token.length = PTR_DIFF(p, output_token.value);
522
523 gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
524 &output_token, (int *)&conf_state,
525 &input_token);
526 if (gss_rc) {
527 status = ADS_ERROR_GSS(gss_rc, minor_status);
528 goto failed;
529 }
530
531 free(output_token.value);
532
533 cred.bv_val = (char *)input_token.value;
534 cred.bv_len = input_token.length;
535
536 rc = ldap_sasl_bind_s(ads->ld, NULL, "GSSAPI", &cred, NULL, NULL,
537 &scred);
538 status = ADS_ERROR(rc);
539
540 gss_release_buffer(&minor_status, &input_token);
541
542failed:
543
544 gss_release_name(&minor_status, &serv_name);
545 if (context_handle != GSS_C_NO_CONTEXT)
546 gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
547 krb5_free_principal(ctx, principal);
548 krb5_free_context(ctx);
549
550 if(scred)
551 ber_bvfree(scred);
552 return status;
553}
554#endif /* HAVE_GGSAPI */
555
556/* mapping between SASL mechanisms and functions */
557static struct {
558 const char *name;
559 ADS_STATUS (*fn)(ADS_STRUCT *);
560} sasl_mechanisms[] = {
561 {"GSS-SPNEGO", ads_sasl_spnego_bind},
562#ifdef HAVE_GSSAPI
563 {"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */
564#endif
565 {NULL, NULL}
566};
567
568ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
569{
570 const char *attrs[] = {"supportedSASLMechanisms", NULL};
571 char **values;
572 ADS_STATUS status;
573 int i, j;
574 LDAPMessage *res;
575
576 /* get a list of supported SASL mechanisms */
577 status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
578 if (!ADS_ERR_OK(status)) return status;
579
580 values = ldap_get_values(ads->ld, res, "supportedSASLMechanisms");
581
582 /* try our supported mechanisms in order */
583 for (i=0;sasl_mechanisms[i].name;i++) {
584 /* see if the server supports it */
585 for (j=0;values && values[j];j++) {
586 if (strcmp(values[j], sasl_mechanisms[i].name) == 0) {
587 DEBUG(4,("Found SASL mechanism %s\n", values[j]));
588 status = sasl_mechanisms[i].fn(ads);
589 ldap_value_free(values);
590 ldap_msgfree(res);
591 return status;
592 }
593 }
594 }
595
596 ldap_value_free(values);
597 ldap_msgfree(res);
598 return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED);
599}
600
601#endif /* HAVE_LDAP */
602
Note: See TracBrowser for help on using the repository browser.